win32-file 0.6.9 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGES +10 -4
- data/MANIFEST +6 -6
- data/README +30 -63
- data/Rakefile +3 -21
- data/lib/win32/file.rb +347 -1154
- data/lib/win32/file/constants.rb +27 -0
- data/lib/win32/file/functions.rb +49 -0
- data/lib/win32/file/structs.rb +24 -0
- data/test/test_win32_file_link.rb +141 -0
- data/test/test_win32_file_misc.rb +16 -0
- data/test/test_win32_file_path.rb +42 -35
- data/test/test_win32_file_stat.rb +275 -105
- data/win32-file.gemspec +5 -4
- metadata +19 -26
- data/test/test_win32_file_attributes.rb +0 -285
- data/test/test_win32_file_constants.rb +0 -46
- data/test/test_win32_file_encryption.rb +0 -44
- data/test/test_win32_file_security.rb +0 -66
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe733f3c66c36ad77e79b95f169dbfbea6d495d0
|
4
|
+
data.tar.gz: fc11dce1680290901e70b7423e63a361cf5e0118
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 062129c39980b67de8373c48dcdbd612d6001fd7afd49e74c19bdf06f3779a8ea1d5658da4c3fb5b931a04dcfa00447b2de64711d76fee31aea250fbb1f41919
|
7
|
+
data.tar.gz: 0035f29a6657abf5535498e77a38acdf106133f922134ec164c14dad5b3b5f9d2e7550cff363ac5a2e470e93b501822e589d6d2c1718715ef902c8ea2fe52fff
|
data/CHANGES
CHANGED
@@ -1,7 +1,13 @@
|
|
1
|
-
== 0.
|
2
|
-
*
|
3
|
-
*
|
4
|
-
*
|
1
|
+
== 0.7.0 - 16-Dec-2013
|
2
|
+
* Now requires Ruby 1.9 or later.
|
3
|
+
* Converted to use FFI instead of win32-api. Now works with JRuby, too.
|
4
|
+
* Removed the atribute methods (hidden?, normal?, etc). These are now in
|
5
|
+
the win32-file-attributes gem instead.
|
6
|
+
* The encryption and security related methods were removed. These are now in
|
7
|
+
the win32-security and win32-file-security gems instead.
|
8
|
+
* Added implementations of readable?, readable_real?, writable?,
|
9
|
+
writable_real?, world_readable? and world_writable?, courtesy of the
|
10
|
+
win32-file-stat library.
|
5
11
|
|
6
12
|
== 0.6.8 - 6-Apr-2012
|
7
13
|
* Fixed some unused variable warnings for 1.9.3.
|
data/MANIFEST
CHANGED
@@ -4,10 +4,10 @@
|
|
4
4
|
* README
|
5
5
|
* win32-file.gemspec
|
6
6
|
* lib/win32/file.rb
|
7
|
-
*
|
8
|
-
*
|
9
|
-
*
|
10
|
-
* test/test_win32_file_encryption.rb
|
7
|
+
* lib/win32/file/constants.rb
|
8
|
+
* lib/win32/file/functions.rb
|
9
|
+
* lib/win32/file/structs.rb
|
11
10
|
* test/test_win32_file_path.rb
|
12
|
-
* test/
|
13
|
-
* test/
|
11
|
+
* test/test_win32_file_link.rb
|
12
|
+
* test/test_win32_file_misc.rb
|
13
|
+
* test/test_win32_file_stat.rb
|
data/README
CHANGED
@@ -12,63 +12,41 @@
|
|
12
12
|
== Synopsis
|
13
13
|
require 'win32/file'
|
14
14
|
|
15
|
-
p File.
|
16
|
-
p File.
|
15
|
+
p File.long_path("C:/Progra~1") # => C:\Program Files
|
16
|
+
p File.short_path("C:/Program Files") # => C:\Progra~1
|
17
17
|
|
18
|
-
|
19
|
-
fh.hidden = true
|
20
|
-
}
|
18
|
+
# See redefined methods below
|
21
19
|
|
22
20
|
== Singleton Methods Added
|
23
|
-
* File.
|
24
|
-
* File.
|
25
|
-
* File.compressed?
|
26
|
-
* File.decrypt
|
27
|
-
* File.encrypt
|
28
|
-
* File.encrypted?
|
29
|
-
* File.get_permissions
|
30
|
-
* File.hidden?
|
31
|
-
* File.indexed?
|
32
|
-
* File.longpath
|
33
|
-
* File.normal?
|
34
|
-
* File.offline?
|
35
|
-
* File.readonly?
|
36
|
-
* File.remove_attributes
|
37
|
-
* File.reparse_point?
|
38
|
-
* File.securities
|
39
|
-
* File.set_attributes
|
40
|
-
* File.set_permissions
|
41
|
-
* File.shortpath
|
42
|
-
* File.sparse?
|
43
|
-
* File.system?
|
44
|
-
* File.temporary?
|
45
|
-
|
46
|
-
== Instance Methods Added
|
47
|
-
* File#archive=
|
48
|
-
* File#compressed=
|
49
|
-
* File#hidden=
|
50
|
-
* File#indexed=
|
51
|
-
* File#normal=
|
52
|
-
* File#offline=
|
53
|
-
* File#readonly=
|
54
|
-
* File#sparse=
|
55
|
-
* File#system=
|
56
|
-
* File#temporary=
|
21
|
+
* File.long_path
|
22
|
+
* File.short_path
|
57
23
|
|
58
24
|
== Singleton Methods Redefined
|
59
|
-
* File.basename
|
60
|
-
* File.blksize
|
61
|
-
* File.blockdev?
|
62
|
-
* File.chardev?
|
63
|
-
* File.directory?
|
64
|
-
* File.dirname
|
65
|
-
* File.
|
66
|
-
* File.
|
67
|
-
* File.
|
68
|
-
* File.
|
69
|
-
* File.
|
70
|
-
* File.
|
71
|
-
* File.
|
25
|
+
* File.basename # UNC path issues in MRI
|
26
|
+
* File.blksize # Not implemented in MRI
|
27
|
+
* File.blockdev? # Not implemented in MRI
|
28
|
+
* File.chardev? # Not implemented in MRI
|
29
|
+
* File.directory? # Better wide character string handling than MRI
|
30
|
+
* File.dirname # UNC path issues in MRI
|
31
|
+
* File.executable? # Not implemented in MRI
|
32
|
+
* File.file? # Handles non-regular files better than MRI
|
33
|
+
* File.ftype # Handles non-regular files better than MRI
|
34
|
+
* File.join # For uniform handling of path separators.
|
35
|
+
* File.grpowned? # Not implemented in MRI
|
36
|
+
* File.lstat # Not implemented in MRI
|
37
|
+
* File.owned? # Not implemented in MRI
|
38
|
+
* File.pipe? # Not implemented in MRI
|
39
|
+
* File.readable? # Not implemented in MRI
|
40
|
+
* File.realpath # MRI doesn't handle symlinks
|
41
|
+
* File.realdirpath # MRI doesn't handle symlinks
|
42
|
+
* File.socket? # Not implemented in MRI
|
43
|
+
* File.readlink # Not implemented in MRI
|
44
|
+
* File.split # UNC path issues in MRI
|
45
|
+
* File.stat # Uses object returned by win32-file-stat
|
46
|
+
* File.symlink # Not implemented in MRI
|
47
|
+
* File.symlink? # Not implemented in MRI
|
48
|
+
* File.writable? # Not implemented in MRI
|
49
|
+
* File.world_writable? # Not implemented in MRI
|
72
50
|
|
73
51
|
== Known issues or bugs
|
74
52
|
None that I'm aware of.
|
@@ -81,17 +59,6 @@
|
|
81
59
|
|
82
60
|
http://www.rubyforge.org/projects/win32utils.
|
83
61
|
|
84
|
-
== Version Issues
|
85
|
-
As of 0.7.0, this gem has been significantly altered. Many of the
|
86
|
-
methods included here have been split out into separate gems, and
|
87
|
-
other methods have been overridden. In addition, as of 0.7.0, this
|
88
|
-
gem uses FFI instead of win32-api. The 0.6.x series is to be
|
89
|
-
considered deprecated.
|
90
|
-
|
91
|
-
If you ware using win32-file 0.7.0 or later, you will be using
|
92
|
-
a different version of win32-file-stat as well. Don't worry, the
|
93
|
-
gemspec has been updated to use the correct gems.
|
94
|
-
|
95
62
|
== License
|
96
63
|
Artistic 2.0
|
97
64
|
|
data/Rakefile
CHANGED
@@ -13,7 +13,7 @@ namespace 'gem' do
|
|
13
13
|
else
|
14
14
|
require 'rubygems/package'
|
15
15
|
Gem::Package.build(spec)
|
16
|
-
end
|
16
|
+
end
|
17
17
|
end
|
18
18
|
|
19
19
|
desc 'Install the win32-file gem'
|
@@ -29,22 +29,10 @@ namespace 'test' do
|
|
29
29
|
t.warning = true
|
30
30
|
end
|
31
31
|
|
32
|
-
Rake::TestTask.new("
|
32
|
+
Rake::TestTask.new("link") do |t|
|
33
33
|
t.verbose = true
|
34
34
|
t.warning = true
|
35
|
-
t.test_files = FileList['test/
|
36
|
-
end
|
37
|
-
|
38
|
-
Rake::TestTask.new("constants") do |t|
|
39
|
-
t.verbose = true
|
40
|
-
t.warning = true
|
41
|
-
t.test_files = FileList['test/test_win32_file_constants.rb']
|
42
|
-
end
|
43
|
-
|
44
|
-
Rake::TestTask.new("encryption") do |t|
|
45
|
-
t.verbose = true
|
46
|
-
t.warning = true
|
47
|
-
t.test_files = FileList['test/test_win32_file_encryption.rb']
|
35
|
+
t.test_files = FileList['test/test_win32_file_link.rb']
|
48
36
|
end
|
49
37
|
|
50
38
|
Rake::TestTask.new("path") do |t|
|
@@ -53,12 +41,6 @@ namespace 'test' do
|
|
53
41
|
t.test_files = FileList['test/test_win32_file_path.rb']
|
54
42
|
end
|
55
43
|
|
56
|
-
Rake::TestTask.new("security") do |t|
|
57
|
-
t.verbose = true
|
58
|
-
t.warning = true
|
59
|
-
t.test_files = FileList['test/test_win32_file_security.rb']
|
60
|
-
end
|
61
|
-
|
62
44
|
Rake::TestTask.new("stat") do |t|
|
63
45
|
t.verbose = true
|
64
46
|
t.warning = true
|
data/lib/win32/file.rb
CHANGED
@@ -1,1296 +1,489 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require File.join(File.dirname(__FILE__), 'file', 'constants')
|
2
|
+
require File.join(File.dirname(__FILE__), 'file', 'structs')
|
3
|
+
require File.join(File.dirname(__FILE__), 'file', 'functions')
|
3
4
|
require 'win32/file/stat'
|
4
5
|
|
5
6
|
class File
|
6
|
-
include Windows::
|
7
|
-
include Windows::File
|
8
|
-
|
9
|
-
include Windows::Limits
|
10
|
-
include Windows::DeviceIO
|
11
|
-
include Windows::Handle
|
12
|
-
|
13
|
-
extend Windows::Error
|
14
|
-
extend Windows::File
|
15
|
-
extend Windows::Path
|
16
|
-
extend Windows::Security
|
17
|
-
extend Windows::MSVCRT::Buffer
|
18
|
-
extend Windows::Limits
|
19
|
-
extend Windows::Handle
|
7
|
+
include Windows::File::Constants
|
8
|
+
include Windows::File::Structs
|
9
|
+
extend Windows::File::Functions
|
20
10
|
|
21
11
|
# The version of the win32-file library
|
22
|
-
WIN32_FILE_VERSION = '0.
|
23
|
-
|
24
|
-
# Abbreviated attribute constants for convenience
|
25
|
-
|
26
|
-
# The file or directory is an archive. Typically used to mark files for
|
27
|
-
# backup or removal.
|
28
|
-
ARCHIVE = FILE_ATTRIBUTE_ARCHIVE
|
29
|
-
|
30
|
-
# The file or directory is encrypted. For a file, this means that all
|
31
|
-
# data in the file is encrypted. For a directory, this means that
|
32
|
-
# encryption is # the default for newly created files and subdirectories.
|
33
|
-
COMPRESSED = FILE_ATTRIBUTE_COMPRESSED
|
34
|
-
|
35
|
-
# The file is hidden. Not included in an ordinary directory listing.
|
36
|
-
HIDDEN = FILE_ATTRIBUTE_HIDDEN
|
37
|
-
|
38
|
-
# A file that does not have any other attributes set.
|
39
|
-
NORMAL = FILE_ATTRIBUTE_NORMAL
|
40
|
-
|
41
|
-
# The data of a file is not immediately available. This attribute indicates
|
42
|
-
# that file data is physically moved to offline storage.
|
43
|
-
OFFLINE = FILE_ATTRIBUTE_OFFLINE
|
44
|
-
|
45
|
-
# The file is read only. Apps can read it, but not write to it or delete it.
|
46
|
-
READONLY = FILE_ATTRIBUTE_READONLY
|
47
|
-
|
48
|
-
# The file is part of or used exclusively by an operating system.
|
49
|
-
SYSTEM = FILE_ATTRIBUTE_SYSTEM
|
50
|
-
|
51
|
-
# The file is being used for temporary storage.
|
52
|
-
TEMPORARY = FILE_ATTRIBUTE_TEMPORARY
|
53
|
-
|
54
|
-
# The file or directory is to be indexed by the content indexing service.
|
55
|
-
# Note that we have inverted the traditional definition.
|
56
|
-
INDEXED = 0x0002000
|
57
|
-
|
58
|
-
# Synonym for File::INDEXED.
|
59
|
-
CONTENT_INDEXED = INDEXED
|
60
|
-
|
61
|
-
# Custom Security rights
|
62
|
-
|
63
|
-
# Full security rights - read, write, append, execute, and delete.
|
64
|
-
FULL = STANDARD_RIGHTS_ALL | FILE_READ_DATA | FILE_WRITE_DATA |
|
65
|
-
FILE_APPEND_DATA | FILE_READ_EA | FILE_WRITE_EA | FILE_EXECUTE |
|
66
|
-
FILE_DELETE_CHILD | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES
|
67
|
-
|
68
|
-
# Generic write, generic read, execute and delete privileges
|
69
|
-
CHANGE = FILE_GENERIC_WRITE | FILE_GENERIC_READ | FILE_EXECUTE | DELETE
|
70
|
-
|
71
|
-
# Read and execute privileges
|
72
|
-
READ = FILE_GENERIC_READ | FILE_EXECUTE
|
73
|
-
|
74
|
-
# Add privileges
|
75
|
-
ADD = 0x001201bf
|
76
|
-
|
77
|
-
# :stopdoc:
|
78
|
-
|
79
|
-
SECURITY_RIGHTS = {
|
80
|
-
'FULL' => FULL,
|
81
|
-
'DELETE' => DELETE,
|
82
|
-
'READ' => READ,
|
83
|
-
'CHANGE' => CHANGE,
|
84
|
-
'ADD' => ADD
|
85
|
-
}
|
86
|
-
|
87
|
-
# :startdoc:
|
88
|
-
|
89
|
-
### Class Methods
|
12
|
+
WIN32_FILE_VERSION = '0.7.0'
|
90
13
|
|
91
14
|
class << self
|
15
|
+
alias_method :join_orig, :join
|
16
|
+
alias_method :realpath_orig, :realpath
|
17
|
+
alias_method :realdirpath_orig, :realdirpath
|
18
|
+
|
19
|
+
remove_method :basename, :blockdev?, :chardev?, :dirname, :directory?
|
20
|
+
remove_method :executable?, :executable_real?, :file?, :ftype, :grpowned?
|
21
|
+
remove_method :join, :lstat, :owned?, :pipe?, :socket?
|
22
|
+
remove_method :readable?, :readable_real?, :readlink, :realpath
|
23
|
+
remove_method :realdirpath
|
24
|
+
remove_method :split, :stat
|
25
|
+
remove_method :symlink
|
26
|
+
remove_method :symlink?
|
27
|
+
remove_method :world_readable?, :world_writable?
|
28
|
+
remove_method :writable?, :writable_real?
|
29
|
+
end
|
92
30
|
|
93
|
-
|
94
|
-
|
95
|
-
# Strictly for making this code -w clean. They are removed later.
|
96
|
-
alias basename_orig basename
|
97
|
-
alias blockdev_orig blockdev?
|
98
|
-
alias chardev_orig chardev?
|
99
|
-
alias directory_orig? directory?
|
100
|
-
alias dirname_orig dirname
|
101
|
-
alias join_orig join
|
102
|
-
alias lstat_orig lstat
|
103
|
-
alias readlink_orig readlink
|
104
|
-
alias size_orig size
|
105
|
-
alias split_orig split
|
106
|
-
alias stat_orig stat
|
107
|
-
alias symlink_orig symlink
|
108
|
-
alias symlink_orig? symlink?
|
109
|
-
|
110
|
-
# :startdoc:
|
111
|
-
|
112
|
-
## Security
|
113
|
-
|
114
|
-
# Sets the file permissions for the given file name. The 'permissions'
|
115
|
-
# argument is a hash with an account name as the key, and the various
|
116
|
-
# permission constants as possible values. The possible constant values
|
117
|
-
# are:
|
118
|
-
#
|
119
|
-
# * FILE_READ_DATA
|
120
|
-
# * FILE_WRITE_DATA
|
121
|
-
# * FILE_APPEND_DATA
|
122
|
-
# * FILE_READ_EA
|
123
|
-
# * FILE_WRITE_EA
|
124
|
-
# * FILE_EXECUTE
|
125
|
-
# * FILE_DELETE_CHILD
|
126
|
-
# * FILE_READ_ATTRIBUTES
|
127
|
-
# * FILE_WRITE_ATTRIBUTES
|
128
|
-
# * STANDARD_RIGHTS_ALL
|
129
|
-
# * FULL
|
130
|
-
# * READ
|
131
|
-
# * ADD
|
132
|
-
# * CHANGE
|
133
|
-
# * DELETE
|
134
|
-
# * READ_CONTROL
|
135
|
-
# * WRITE_DAC
|
136
|
-
# * WRITE_OWNER
|
137
|
-
# * SYNCHRONIZE
|
138
|
-
# * STANDARD_RIGHTS_REQUIRED
|
139
|
-
# * STANDARD_RIGHTS_READ
|
140
|
-
# * STANDARD_RIGHTS_WRITE
|
141
|
-
# * STANDARD_RIGHTS_EXECUTE
|
142
|
-
# * STANDARD_RIGHTS_ALL
|
143
|
-
# * SPECIFIC_RIGHTS_ALL
|
144
|
-
# * ACCESS_SYSTEM_SECURITY
|
145
|
-
# * MAXIMUM_ALLOWED
|
146
|
-
# * GENERIC_READ
|
147
|
-
# * GENERIC_WRITE
|
148
|
-
# * GENERIC_EXECUTE
|
149
|
-
# * GENERIC_ALL
|
150
|
-
#
|
151
|
-
def set_permissions(file, perms)
|
152
|
-
raise TypeError unless file.is_a?(String)
|
153
|
-
raise TypeError unless perms.kind_of?(Hash)
|
154
|
-
|
155
|
-
file = multi_to_wide(file)
|
156
|
-
|
157
|
-
account_rights = 0
|
158
|
-
sec_desc = 0.chr * SECURITY_DESCRIPTOR_MIN_LENGTH
|
159
|
-
|
160
|
-
unless InitializeSecurityDescriptor(sec_desc, 1)
|
161
|
-
raise ArgumentError, get_last_error
|
162
|
-
end
|
163
|
-
|
164
|
-
cb_acl = 1024
|
165
|
-
cb_sid = 1024
|
166
|
-
|
167
|
-
acl_new = 0.chr * cb_acl
|
168
|
-
|
169
|
-
unless InitializeAcl(acl_new, cb_acl, ACL_REVISION2)
|
170
|
-
raise ArgumentError, get_last_error
|
171
|
-
end
|
172
|
-
|
173
|
-
sid = 0.chr * cb_sid
|
174
|
-
snu_type = 0.chr * cb_sid
|
175
|
-
|
176
|
-
all_ace = 0.chr * ALLOW_ACE_LENGTH
|
177
|
-
all_ace_ptr = memset(all_ace, 0, 0) # address of all_ace
|
178
|
-
|
179
|
-
|
180
|
-
# all_ace_ptr->Header.AceType = ACCESS_ALLOWED_ACE_TYPE
|
181
|
-
all_ace[0] = 0.chr
|
182
|
-
|
183
|
-
perms.each{ |account, mask|
|
184
|
-
next if mask.nil?
|
185
|
-
|
186
|
-
cch_domain = [80].pack('L')
|
187
|
-
cb_sid = [1024].pack('L')
|
188
|
-
domain_buf = 0.chr * 80
|
189
|
-
|
190
|
-
server, account = account.split("\\")
|
191
|
-
|
192
|
-
if ['BUILTIN', 'NT AUTHORITY'].include?(server.upcase)
|
193
|
-
server = nil
|
194
|
-
end
|
195
|
-
|
196
|
-
val = LookupAccountName(
|
197
|
-
server,
|
198
|
-
account,
|
199
|
-
sid,
|
200
|
-
cb_sid,
|
201
|
-
domain_buf,
|
202
|
-
cch_domain,
|
203
|
-
snu_type
|
204
|
-
)
|
205
|
-
|
206
|
-
if val == 0
|
207
|
-
raise ArgumentError, get_last_error
|
208
|
-
end
|
209
|
-
|
210
|
-
size = [0,0,0,0,0].pack('CCSLL').length # sizeof(ACCESS_ALLOWED_ACE)
|
211
|
-
|
212
|
-
val = CopySid(
|
213
|
-
ALLOW_ACE_LENGTH - size,
|
214
|
-
all_ace_ptr + 8, # address of all_ace_ptr->SidStart
|
215
|
-
sid
|
216
|
-
)
|
217
|
-
|
218
|
-
if val == 0
|
219
|
-
raise ArgumentError, get_last_error
|
220
|
-
end
|
221
|
-
|
222
|
-
if (GENERIC_ALL & mask).nonzero?
|
223
|
-
account_rights = GENERIC_ALL & mask
|
224
|
-
elsif (GENERIC_RIGHTS_CHK & mask).nonzero?
|
225
|
-
account_rights = GENERIC_RIGHTS_MASK & mask
|
226
|
-
end
|
227
|
-
|
228
|
-
# all_ace_ptr->Header.AceFlags = INHERIT_ONLY_ACE|OBJECT_INHERIT_ACE
|
229
|
-
all_ace[1] = (INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE).chr
|
230
|
-
|
231
|
-
# WHY DO I NEED THIS RUBY CORE TEAM? WHY?!?!?!?!?!?
|
232
|
-
all_ace.force_encoding('ASCII-8BIT') if RUBY_VERSION.to_f >= 1.9
|
233
|
-
|
234
|
-
2.times{
|
235
|
-
if account_rights != 0
|
236
|
-
all_ace[2,2] = [12 - 4 + GetLengthSid(sid)].pack('S')
|
237
|
-
all_ace[4,4] = [account_rights].pack('L')
|
238
|
-
|
239
|
-
val = AddAce(
|
240
|
-
acl_new,
|
241
|
-
ACL_REVISION2,
|
242
|
-
MAXDWORD,
|
243
|
-
all_ace_ptr,
|
244
|
-
all_ace[2,2].unpack('S').first
|
245
|
-
)
|
246
|
-
|
247
|
-
if val == 0
|
248
|
-
raise ArgumentError, get_last_error
|
249
|
-
end
|
250
|
-
|
251
|
-
# all_ace_ptr->Header.AceFlags = CONTAINER_INHERIT_ACE
|
252
|
-
all_ace[1] = CONTAINER_INHERIT_ACE.chr
|
253
|
-
else
|
254
|
-
# all_ace_ptr->Header.AceFlags = 0
|
255
|
-
all_ace[1] = 0.chr
|
256
|
-
end
|
257
|
-
|
258
|
-
account_rights = REST_RIGHTS_MASK & mask
|
259
|
-
}
|
260
|
-
}
|
261
|
-
|
262
|
-
unless SetSecurityDescriptorDacl(sec_desc, 1, acl_new, 0)
|
263
|
-
raise ArgumentError, get_last_error
|
264
|
-
end
|
265
|
-
|
266
|
-
unless SetFileSecurityW(file, DACL_SECURITY_INFORMATION, sec_desc)
|
267
|
-
raise ArgumentError, get_last_error
|
268
|
-
end
|
269
|
-
|
270
|
-
self
|
271
|
-
end
|
272
|
-
|
273
|
-
# Returns an array of human-readable strings that correspond to the
|
274
|
-
# permission flags.
|
275
|
-
#
|
276
|
-
def securities(mask)
|
277
|
-
sec_array = []
|
278
|
-
if mask == 0
|
279
|
-
sec_array.push('NONE')
|
280
|
-
else
|
281
|
-
if (mask & FULL) ^ FULL == 0
|
282
|
-
sec_array.push('FULL')
|
283
|
-
else
|
284
|
-
SECURITY_RIGHTS.each{ |string, numeric|
|
285
|
-
if (numeric & mask) ^ numeric == 0
|
286
|
-
sec_array.push(string)
|
287
|
-
end
|
288
|
-
}
|
289
|
-
end
|
290
|
-
end
|
291
|
-
sec_array
|
292
|
-
end
|
293
|
-
|
294
|
-
# Returns a hash describing the current file permissions for the given
|
295
|
-
# file. The account name is the key, and the value is an integer
|
296
|
-
# representing an or'd value that corresponds to the security
|
297
|
-
# permissions for that file.
|
298
|
-
#
|
299
|
-
# To get a human readable version of the permissions, pass the value to
|
300
|
-
# the +File.securities+ method.
|
301
|
-
#
|
302
|
-
def get_permissions(file, host=nil)
|
303
|
-
length_needed = [0].pack('L')
|
304
|
-
sec_buf = ''
|
305
|
-
|
306
|
-
loop do
|
307
|
-
bool = GetFileSecurityW(
|
308
|
-
multi_to_wide(file),
|
309
|
-
DACL_SECURITY_INFORMATION,
|
310
|
-
sec_buf,
|
311
|
-
sec_buf.length,
|
312
|
-
length_needed
|
313
|
-
)
|
314
|
-
|
315
|
-
if bool == 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER
|
316
|
-
raise ArgumentError, get_last_error
|
317
|
-
end
|
318
|
-
|
319
|
-
break if sec_buf.length >= length_needed.unpack('L').first
|
320
|
-
sec_buf += ' ' * length_needed.unpack('L').first
|
321
|
-
end
|
322
|
-
|
323
|
-
control = [0].pack('L')
|
324
|
-
revision = [0].pack('L')
|
325
|
-
|
326
|
-
unless GetSecurityDescriptorControl(sec_buf, control, revision)
|
327
|
-
raise ArgumentError, get_last_error
|
328
|
-
end
|
329
|
-
|
330
|
-
# No DACL exists
|
331
|
-
if (control.unpack('L').first & SE_DACL_PRESENT) == 0
|
332
|
-
raise ArgumentError, 'No DACL present: explicit deny all'
|
333
|
-
end
|
334
|
-
|
335
|
-
dacl_present = [0].pack('L')
|
336
|
-
dacl_defaulted = [0].pack('L')
|
337
|
-
dacl_ptr = [0].pack('L')
|
338
|
-
|
339
|
-
val = GetSecurityDescriptorDacl(
|
340
|
-
sec_buf,
|
341
|
-
dacl_present,
|
342
|
-
dacl_ptr,
|
343
|
-
dacl_defaulted
|
344
|
-
)
|
345
|
-
|
346
|
-
if val == 0
|
347
|
-
raise ArgumentError, get_last_error
|
348
|
-
end
|
349
|
-
|
350
|
-
acl_buf = 0.chr * 8 # byte, byte, word, word, word (struct ACL)
|
351
|
-
memcpy(acl_buf, dacl_ptr.unpack('L').first, acl_buf.size)
|
352
|
-
|
353
|
-
if acl_buf.unpack('CCSSS').first == 0
|
354
|
-
raise ArgumentError, 'DACL is NULL: implicit access grant'
|
355
|
-
end
|
356
|
-
|
357
|
-
ace_ptr = [0].pack('L')
|
358
|
-
ace_count = acl_buf.unpack('CCSSS')[3]
|
359
|
-
|
360
|
-
perms_hash = {}
|
361
|
-
|
362
|
-
0.upto(ace_count - 1){ |i|
|
363
|
-
unless GetAce(dacl_ptr.unpack('L').first, i, ace_ptr)
|
364
|
-
next
|
365
|
-
end
|
366
|
-
|
367
|
-
ace_buf = 0.chr * 12 # ACE_HEADER, dword, dword (ACCESS_ALLOWED_ACE)
|
368
|
-
memcpy(ace_buf, ace_ptr.unpack('L').first, ace_buf.size)
|
369
|
-
|
370
|
-
if ace_buf.unpack('CCS').first == ACCESS_ALLOWED_ACE_TYPE
|
371
|
-
name = 0.chr * MAXPATH
|
372
|
-
name_size = [name.size].pack('L')
|
373
|
-
domain = 0.chr * MAXPATH
|
374
|
-
domain_size = [domain.size].pack('L')
|
375
|
-
snu_ptr = 0.chr * 4
|
376
|
-
|
377
|
-
val = LookupAccountSid(
|
378
|
-
host,
|
379
|
-
ace_ptr.unpack('L').first + 8, # address of ace_ptr->SidStart
|
380
|
-
name,
|
381
|
-
name_size,
|
382
|
-
domain,
|
383
|
-
domain_size,
|
384
|
-
snu_ptr
|
385
|
-
)
|
386
|
-
|
387
|
-
if val == 0
|
388
|
-
raise ArgumentError, get_last_error
|
389
|
-
end
|
390
|
-
|
391
|
-
name = name[0..name_size.unpack('L').first].split(0.chr)[0]
|
392
|
-
domain = domain[0..domain_size.unpack('L').first].split(0.chr)[0]
|
393
|
-
mask = ace_buf.unpack('LLL')[1]
|
394
|
-
|
395
|
-
unless domain.nil? || domain.empty?
|
396
|
-
name = domain + '\\' + name
|
397
|
-
end
|
398
|
-
|
399
|
-
perms_hash[name] = mask
|
400
|
-
end
|
401
|
-
}
|
402
|
-
perms_hash
|
403
|
-
end
|
404
|
-
|
405
|
-
## Encryption
|
406
|
-
|
407
|
-
# Encrypts a file or directory. All data streams in a file are encrypted.
|
408
|
-
# All new files created in an encrypted directory are encrypted.
|
409
|
-
#
|
410
|
-
# The caller must have the FILE_READ_DATA, FILE_WRITE_DATA,
|
411
|
-
# FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, and SYNCHRONIZE access
|
412
|
-
# rights.
|
413
|
-
#
|
414
|
-
# Requires exclusive access to the file being encrypted, and will fail if
|
415
|
-
# another process is using the file. If the file is compressed,
|
416
|
-
# EncryptFile will decompress the file before encrypting it.
|
417
|
-
#
|
418
|
-
def encrypt(file)
|
419
|
-
unless EncryptFileW(multi_to_wide(file))
|
420
|
-
raise ArgumentError, get_last_error
|
421
|
-
end
|
422
|
-
self
|
423
|
-
end
|
424
|
-
|
425
|
-
# Decrypts an encrypted file or directory.
|
426
|
-
#
|
427
|
-
# The caller must have the FILE_READ_DATA, FILE_WRITE_DATA,
|
428
|
-
# FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, and SYNCHRONIZE access
|
429
|
-
# rights.
|
430
|
-
#
|
431
|
-
# Requires exclusive access to the file being decrypted, and will fail if
|
432
|
-
# another process is using the file. If the file is not encrypted an error
|
433
|
-
# is NOT raised.
|
434
|
-
#
|
435
|
-
# Windows 2000 or later only.
|
436
|
-
#
|
437
|
-
def decrypt(file)
|
438
|
-
unless DecryptFileW(multi_to_wide(file), 0)
|
439
|
-
raise ArgumentError, get_last_error
|
440
|
-
end
|
441
|
-
self
|
442
|
-
end
|
443
|
-
|
444
|
-
## Path methods
|
445
|
-
|
446
|
-
# Returns the last component of the filename given in +filename+. If
|
447
|
-
# +suffix+ is given and present at the end of +filename+, it is removed.
|
448
|
-
# Any extension can be removed by giving an extension of ".*".
|
449
|
-
#
|
450
|
-
# This was reimplemented because the current version does not handle UNC
|
451
|
-
# paths properly, i.e. it should not return anything less than the root.
|
452
|
-
# In all other respects it is identical to the current implementation.
|
453
|
-
#
|
454
|
-
# Unlike MRI, this version will convert all forward slashes to
|
455
|
-
# backslashes automatically.
|
456
|
-
#
|
457
|
-
# Examples:
|
458
|
-
#
|
459
|
-
# File.basename("C:\\foo\\bar.txt") -> "bar.txt"
|
460
|
-
# File.basename("C:\\foo\\bar.txt", ".txt") -> "bar"
|
461
|
-
# File.basename("\\\\foo\\bar") -> "\\\\foo\\bar"
|
462
|
-
#
|
463
|
-
def basename(file, suffix = nil)
|
464
|
-
raise TypeError unless file.is_a?(String)
|
465
|
-
raise TypeError unless suffix.is_a?(String) if suffix
|
466
|
-
|
467
|
-
return file if file.empty? # Return an empty path as-is.
|
468
|
-
file = multi_to_wide(file)
|
469
|
-
|
470
|
-
# Required for Windows API functions to work properly.
|
471
|
-
file.tr!(File::SEPARATOR, File::ALT_SEPARATOR)
|
472
|
-
|
473
|
-
# Return a root path as-is.
|
474
|
-
return wide_to_multi(file) if PathIsRootW(file)
|
475
|
-
|
476
|
-
PathStripPathW(file) # Gives us the basename
|
477
|
-
|
478
|
-
if suffix
|
479
|
-
if suffix == '.*'
|
480
|
-
PathRemoveExtensionW(file)
|
481
|
-
else
|
482
|
-
if PathFindExtensionA(wide_to_multi(file)) == suffix
|
483
|
-
PathRemoveExtensionW(file)
|
484
|
-
end
|
485
|
-
end
|
486
|
-
end
|
487
|
-
|
488
|
-
file = wide_to_multi(file)
|
489
|
-
file.sub!(/\\+\z/, '') # Trim trailing slashes
|
490
|
-
|
491
|
-
file
|
492
|
-
end
|
493
|
-
|
494
|
-
# Returns true if +file+ is a directory, false otherwise.
|
495
|
-
#--
|
496
|
-
# This method was redefined to handle wide character strings.
|
497
|
-
#
|
498
|
-
def directory?(file)
|
499
|
-
file = multi_to_wide(file)
|
500
|
-
attributes = GetFileAttributesW(file)
|
501
|
-
(attributes != INVALID_FILE_ATTRIBUTES) && (attributes & FILE_ATTRIBUTE_DIRECTORY > 0)
|
502
|
-
end
|
503
|
-
|
504
|
-
# Returns all components of the filename given in +filename+ except the
|
505
|
-
# last one.
|
506
|
-
#
|
507
|
-
# This was reimplemented because the current version does not handle UNC
|
508
|
-
# paths properly, i.e. it should not return anything less than the root.
|
509
|
-
# In all other respects it is identical to the current implementation.
|
510
|
-
#
|
511
|
-
# Also, this method will convert all forward slashes to backslashes.
|
512
|
-
#
|
513
|
-
# Examples:
|
514
|
-
#
|
515
|
-
# File.dirname("C:\\foo\\bar\\baz.txt") -> "C:\\foo\\bar"
|
516
|
-
# File.dirname("\\\\foo\\bar") -> "\\\\foo\\bar"
|
517
|
-
#
|
518
|
-
def dirname(file)
|
519
|
-
raise TypeError unless file.is_a?(String)
|
520
|
-
|
521
|
-
# Short circuit for empty paths
|
522
|
-
return '.' if file.empty?
|
523
|
-
|
524
|
-
# Temporarily convert to wide-byte
|
525
|
-
file = multi_to_wide(file)
|
526
|
-
|
527
|
-
# Convert slashes to backslashes for the Windows API functions
|
528
|
-
file.tr!(File::SEPARATOR, File::ALT_SEPARATOR)
|
529
|
-
|
530
|
-
# Return a root path as-is.
|
531
|
-
return wide_to_multi(file) if PathIsRootW(file)
|
532
|
-
|
533
|
-
# Remove trailing slashes if present
|
534
|
-
while result = PathRemoveBackslashW(file)
|
535
|
-
break unless result.empty?
|
536
|
-
end
|
537
|
-
|
538
|
-
# Remove trailing file name if present
|
539
|
-
PathRemoveFileSpecW(file)
|
31
|
+
## Path methods
|
540
32
|
|
541
|
-
|
542
|
-
|
33
|
+
# Returns the last component of the filename given in +filename+. If
|
34
|
+
# +suffix+ is given and present at the end of +filename+, it is removed.
|
35
|
+
# Any extension can be removed by giving an extension of ".*".
|
36
|
+
#
|
37
|
+
# This was reimplemented because the current version does not handle UNC
|
38
|
+
# paths properly, i.e. it should not return anything less than the root.
|
39
|
+
# In most other respects it is identical to the current implementation.
|
40
|
+
#
|
41
|
+
# Unlike MRI, this version will convert all forward slashes to
|
42
|
+
# backslashes automatically.
|
43
|
+
#
|
44
|
+
# Examples:
|
45
|
+
#
|
46
|
+
# File.basename("C:\\foo\\bar.txt") -> "bar.txt"
|
47
|
+
# File.basename("C:\\foo\\bar.txt", ".txt") -> "bar"
|
48
|
+
# File.basename("\\\\foo\\bar") -> "\\\\foo\\bar"
|
49
|
+
#
|
50
|
+
def self.basename(file, suffix = nil)
|
51
|
+
raise TypeError unless file.is_a?(String)
|
52
|
+
raise TypeError unless suffix.is_a?(String) if suffix
|
543
53
|
|
544
|
-
|
545
|
-
if file.nil? || (file && file.empty?)
|
546
|
-
return '.'
|
547
|
-
end
|
54
|
+
return file if file.empty? # Return an empty path as-is.
|
548
55
|
|
549
|
-
|
550
|
-
|
56
|
+
encoding = file.encoding
|
57
|
+
wfile = file.wincode
|
551
58
|
|
552
|
-
#
|
553
|
-
|
554
|
-
|
555
|
-
# forward slashes to backslashes. It is otherwise identical to
|
556
|
-
# the core File.join method.
|
557
|
-
#
|
558
|
-
# Examples:
|
559
|
-
#
|
560
|
-
# File.join("C:", "foo", "bar") # => C:\foo\bar
|
561
|
-
# File.join("foo", "bar") # => foo\bar
|
562
|
-
#
|
563
|
-
def join(*args)
|
564
|
-
return join_orig(*args).tr("/", "\\")
|
59
|
+
# Return a root path as-is.
|
60
|
+
if PathIsRootW(wfile)
|
61
|
+
return file.tr(File::SEPARATOR, File::ALT_SEPARATOR)
|
565
62
|
end
|
566
63
|
|
567
|
-
|
568
|
-
# was the argument provided, and the short representation for
|
569
|
-
# 'somefile.txt', then this method would return 'somefile.txt'.
|
570
|
-
#
|
571
|
-
# Note that certain file system optimizations may prevent this method
|
572
|
-
# from working as expected. In that case, you will get back the file
|
573
|
-
# name in 8.3 format.
|
574
|
-
#
|
575
|
-
def long_path(path)
|
576
|
-
buf = 0.chr * MAXPATH
|
577
|
-
if GetLongPathNameW(multi_to_wide(path), buf, buf.size) == 0
|
578
|
-
raise ArgumentError, get_last_error
|
579
|
-
end
|
580
|
-
wide_to_multi(buf)
|
581
|
-
end
|
64
|
+
ptr = FFI::MemoryPointer.from_string(wfile)
|
582
65
|
|
583
|
-
#
|
584
|
-
#
|
585
|
-
# Requires Windows Vista or later. On older versions of Windows it
|
586
|
-
# will raise a NotImplementedError, as per MRI.
|
587
|
-
#
|
588
|
-
def readlink(file)
|
589
|
-
if defined? GetFinalPathNameByHandle
|
590
|
-
file = multi_to_wide(file)
|
591
|
-
|
592
|
-
begin
|
593
|
-
handle = CreateFileW(
|
594
|
-
file,
|
595
|
-
GENERIC_READ,
|
596
|
-
FILE_SHARE_READ,
|
597
|
-
nil,
|
598
|
-
OPEN_EXISTING,
|
599
|
-
FILE_ATTRIBUTE_NORMAL,
|
600
|
-
nil
|
601
|
-
)
|
602
|
-
|
603
|
-
if handle == INVALID_HANDLE_VALUE
|
604
|
-
raise ArgumentError, get_last_error
|
605
|
-
end
|
606
|
-
|
607
|
-
path = 0.chr * MAXPATH
|
608
|
-
|
609
|
-
GetFinalPathNameByHandleW(handle, path, path.size, 0)
|
610
|
-
ensure
|
611
|
-
CloseHandle(handle)
|
612
|
-
end
|
66
|
+
PathStripPathW(ptr) # Gives us the basename
|
613
67
|
|
614
|
-
|
68
|
+
if suffix
|
69
|
+
if suffix == '.*'
|
70
|
+
PathRemoveExtensionW(ptr)
|
615
71
|
else
|
616
|
-
|
617
|
-
raise NotImplementedError, msg
|
618
|
-
end
|
619
|
-
end
|
72
|
+
ext = PathFindExtensionW(ptr).read_string(suffix.length * 2).wstrip
|
620
73
|
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
def short_path(path)
|
625
|
-
buf = 0.chr * MAXPATH
|
626
|
-
if GetShortPathNameW(multi_to_wide(path), buf, buf.size) == 0
|
627
|
-
raise ArgumentError, get_last_error
|
74
|
+
if ext == suffix
|
75
|
+
PathRemoveExtensionW(ptr)
|
76
|
+
end
|
628
77
|
end
|
629
|
-
wide_to_multi(buf)
|
630
78
|
end
|
631
79
|
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
#
|
636
|
-
def split(file)
|
637
|
-
array = []
|
80
|
+
wfile = ptr.read_bytes(wfile.size * 2).split("\000\000").first.tr(0.chr, '')
|
81
|
+
file = wfile.encode(encoding)[/^[^\0]*/]
|
82
|
+
file.sub!(/\\+\z/, '') # Trim trailing slashes
|
638
83
|
|
639
|
-
|
640
|
-
|
641
|
-
else
|
642
|
-
array.push(File.dirname(file), File.basename(file))
|
643
|
-
end
|
644
|
-
array
|
645
|
-
end
|
84
|
+
file
|
85
|
+
end
|
646
86
|
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
nil
|
664
|
-
end
|
665
|
-
end
|
87
|
+
# Returns all components of the filename given in +filename+ except the
|
88
|
+
# last one.
|
89
|
+
#
|
90
|
+
# This was reimplemented because the current version does not handle UNC
|
91
|
+
# paths properly, i.e. it should not return anything less than the root.
|
92
|
+
# In all other respects it is identical to the current implementation.
|
93
|
+
#
|
94
|
+
# Also, this method will convert all forward slashes to backslashes.
|
95
|
+
#
|
96
|
+
# Examples:
|
97
|
+
#
|
98
|
+
# File.dirname("C:\\foo\\bar\\baz.txt") -> "C:\\foo\\bar"
|
99
|
+
# File.dirname("\\\\foo\\bar") -> "\\\\foo\\bar"
|
100
|
+
#
|
101
|
+
def self.dirname(file)
|
102
|
+
raise TypeError unless file.is_a?(String)
|
666
103
|
|
667
|
-
#
|
668
|
-
|
669
|
-
# This method requires Windows Vista or later to work. Otherwise, it
|
670
|
-
# always returns false as per MRI.
|
671
|
-
#
|
672
|
-
def symlink?(file)
|
673
|
-
bool = false
|
674
|
-
file = multi_to_wide(file)
|
675
|
-
attr = GetFileAttributesW(file)
|
676
|
-
|
677
|
-
# Differentiate between a symlink and other kinds of reparse points
|
678
|
-
if attr & FILE_ATTRIBUTE_REPARSE_POINT > 0
|
679
|
-
begin
|
680
|
-
buffer = 0.chr * 278 # WIN32_FIND_DATA
|
681
|
-
handle = FindFirstFileW(file, buffer)
|
682
|
-
|
683
|
-
if handle == INVALID_HANDLE_VALUE
|
684
|
-
raise ArgumentError, get_last_error
|
685
|
-
end
|
686
|
-
|
687
|
-
if buffer[36,4].unpack('L')[0] == IO_REPARSE_TAG_SYMLINK
|
688
|
-
bool = true
|
689
|
-
end
|
690
|
-
ensure
|
691
|
-
CloseHandle(handle)
|
692
|
-
end
|
693
|
-
end
|
104
|
+
# Short circuit for empty paths
|
105
|
+
return '.' if file.empty?
|
694
106
|
|
695
|
-
|
696
|
-
|
107
|
+
# Store original encoding, restore it later
|
108
|
+
encoding = file.encoding
|
697
109
|
|
698
|
-
|
110
|
+
# Convert to UTF-16LE
|
111
|
+
wfile = file.wincode
|
699
112
|
|
700
|
-
#
|
701
|
-
|
702
|
-
|
703
|
-
File::Stat.new(file)
|
113
|
+
# Return a root path as-is.
|
114
|
+
if PathIsRootW(wfile)
|
115
|
+
return file.tr(File::SEPARATOR, File::ALT_SEPARATOR)
|
704
116
|
end
|
705
117
|
|
706
|
-
|
707
|
-
#
|
708
|
-
def lstat(file)
|
709
|
-
File::Stat.new(file)
|
710
|
-
end
|
118
|
+
ptr = FFI::MemoryPointer.from_string(wfile)
|
711
119
|
|
712
|
-
#
|
713
|
-
|
714
|
-
|
715
|
-
File::Stat.new(file).blksize
|
120
|
+
# Remove trailing slashes if present
|
121
|
+
while result = PathRemoveBackslashW(ptr)
|
122
|
+
break unless result.empty?
|
716
123
|
end
|
717
124
|
|
718
|
-
#
|
719
|
-
|
720
|
-
|
721
|
-
def blockdev?(file)
|
722
|
-
File::Stat.new(file).blockdev?
|
125
|
+
# Remove trailing file name if present
|
126
|
+
unless PathRemoveFileSpecW(ptr)
|
127
|
+
raise SystemCallError.new("PathRemoveFileSpec", FFI.errno)
|
723
128
|
end
|
724
129
|
|
725
|
-
|
726
|
-
# current Ruby implementation which always returns false.
|
727
|
-
#
|
728
|
-
def chardev?(file)
|
729
|
-
File::Stat.new(file).chardev?
|
730
|
-
end
|
130
|
+
wfile = ptr.read_bytes(wfile.size * 2).split("\000\000").first
|
731
131
|
|
732
|
-
#
|
733
|
-
|
734
|
-
|
735
|
-
# sizes greater than 2gb.
|
736
|
-
#
|
737
|
-
def size(file)
|
738
|
-
File::Stat.new(file).size
|
132
|
+
# Empty paths, short relative paths
|
133
|
+
if wfile.nil? or wfile.empty?
|
134
|
+
return '.'
|
739
135
|
end
|
740
136
|
|
741
|
-
#
|
742
|
-
|
743
|
-
remove_method(:dirname_orig, :lstat_orig, :size_orig, :split_orig)
|
744
|
-
remove_method(:stat_orig, :symlink_orig, :symlink_orig?, :readlink_orig)
|
745
|
-
remove_method(:directory_orig?)
|
746
|
-
end # class << self
|
747
|
-
|
748
|
-
## Attribute methods
|
137
|
+
# Return to original encoding
|
138
|
+
file = wfile.tr(0.chr, '').encode(encoding)
|
749
139
|
|
750
|
-
|
751
|
-
# use this attribute to mark files for backup or removal.
|
752
|
-
#
|
753
|
-
def self.archive?(file)
|
754
|
-
File::Stat.new(file).archive?
|
140
|
+
file
|
755
141
|
end
|
756
142
|
|
757
|
-
#
|
758
|
-
# means that all of the data in the file is compressed. For a directory,
|
759
|
-
# this means that compression is the default for newly created files and
|
760
|
-
# subdirectories.
|
143
|
+
# Join path string components together into a single string.
|
761
144
|
#
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
# Returns true if the file or directory is encrypted. For a file, this
|
767
|
-
# means that all data in the file is encrypted. For a directory, this
|
768
|
-
# means that encryption is the default for newly created files and
|
769
|
-
# subdirectories.
|
145
|
+
# This method was reimplemented so that it automatically converts
|
146
|
+
# forward slashes to backslashes. It is otherwise identical to
|
147
|
+
# the core File.join method.
|
770
148
|
#
|
771
|
-
|
772
|
-
File::Stat.new(file).encrypted?
|
773
|
-
end
|
774
|
-
|
775
|
-
# Returns true if the file or directory is hidden. It is not included
|
776
|
-
# in an ordinary directory listing.
|
149
|
+
# Examples:
|
777
150
|
#
|
778
|
-
|
779
|
-
|
780
|
-
end
|
781
|
-
|
782
|
-
# Returns true if the file or directory is indexed by the content indexing
|
783
|
-
# service.
|
151
|
+
# File.join("C:", "foo", "bar") # => C:\foo\bar
|
152
|
+
# File.join("foo", "bar") # => foo\bar
|
784
153
|
#
|
785
|
-
def self.
|
786
|
-
|
154
|
+
def self.join(*args)
|
155
|
+
return join_orig(*args).tr("/", "\\")
|
787
156
|
end
|
788
157
|
|
789
|
-
#
|
158
|
+
# Splits the given string into a directory and a file component and
|
159
|
+
# returns them in a two element array. This was reimplemented because
|
160
|
+
# the current version does not handle UNC paths properly.
|
790
161
|
#
|
791
|
-
def self.
|
792
|
-
|
793
|
-
end
|
162
|
+
def self.split(file)
|
163
|
+
array = []
|
794
164
|
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
#
|
801
|
-
def self.offline?(file)
|
802
|
-
File::Stat.new(file).offline?
|
803
|
-
end
|
165
|
+
if file.empty? || PathIsRootW(file.wincode)
|
166
|
+
array.push(file, '')
|
167
|
+
else
|
168
|
+
array.push(File.dirname(file), File.basename(file))
|
169
|
+
end
|
804
170
|
|
805
|
-
|
806
|
-
# read the file but cannot write to it or delete it. In the case of a
|
807
|
-
# directory, applications cannot delete it.
|
808
|
-
#
|
809
|
-
def self.readonly?(file)
|
810
|
-
File::Stat.new(file).readonly?
|
171
|
+
array
|
811
172
|
end
|
812
173
|
|
813
|
-
# Returns
|
814
|
-
#
|
815
|
-
#
|
816
|
-
# http://msdn.microsoft.com.
|
174
|
+
# Returns +path+ in long format. For example, if 'SOMEFI~1.TXT'
|
175
|
+
# was the argument provided, and the short representation for
|
176
|
+
# 'somefile.txt', then this method would return 'somefile.txt'.
|
817
177
|
#
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
# Returns true if the file is a sparse file. A sparse file is a file in
|
823
|
-
# which much of the data is zeros, typically image files. See
|
824
|
-
# http://msdn.microsoft.com for more details.
|
178
|
+
# Note that certain file system optimizations may prevent this method
|
179
|
+
# from working as expected. In that case, you will get back the file
|
180
|
+
# name in 8.3 format.
|
825
181
|
#
|
826
|
-
def self.
|
827
|
-
|
828
|
-
|
182
|
+
def self.long_path(file)
|
183
|
+
buffer = 0.chr * 1024
|
184
|
+
wfile = file.wincode
|
829
185
|
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
def self.system?(file)
|
834
|
-
File::Stat.new(file).system?
|
835
|
-
end
|
186
|
+
if GetLongPathNameW(wfile, buffer, buffer.size/2) == 0
|
187
|
+
raise SystemCallError.new('GetLongPathName', FFI.errno)
|
188
|
+
end
|
836
189
|
|
837
|
-
|
838
|
-
#
|
839
|
-
# File systems avoid writing data back to mass storage if sufficient cache
|
840
|
-
# memory is available, because often the application deletes the temporary
|
841
|
-
# file shortly after the handle is closed. In that case, the system can
|
842
|
-
# entirely avoid writing the data. Otherwise, the data will be written after
|
843
|
-
# the handle is closed.
|
844
|
-
#
|
845
|
-
def self.temporary?(file)
|
846
|
-
File::Stat.new(file).temporary?
|
190
|
+
buffer.wstrip
|
847
191
|
end
|
848
192
|
|
849
|
-
# Returns
|
850
|
-
#
|
851
|
-
#
|
852
|
-
# archive
|
853
|
-
# compressed
|
854
|
-
# directory
|
855
|
-
# encrypted
|
856
|
-
# hidden
|
857
|
-
# indexed
|
858
|
-
# normal
|
859
|
-
# offline
|
860
|
-
# readonly
|
861
|
-
# reparse_point
|
862
|
-
# sparse
|
863
|
-
# system
|
864
|
-
# temporary
|
193
|
+
# Returns +path+ in 8.3 format. For example, 'c:\documentation.doc'
|
194
|
+
# would be returned as 'c:\docume~1.doc'.
|
865
195
|
#
|
866
|
-
def self.
|
867
|
-
|
196
|
+
def self.short_path(file)
|
197
|
+
buffer = 0.chr * 1024
|
198
|
+
wfile = file.wincode
|
868
199
|
|
869
|
-
if
|
870
|
-
raise
|
200
|
+
if GetShortPathNameW(wfile, buffer, buffer.size/2) == 0
|
201
|
+
raise SystemCallError.new('GetShortPathName', FFI.errno)
|
871
202
|
end
|
872
203
|
|
873
|
-
|
874
|
-
|
875
|
-
arr << 'archive' if attributes & FILE_ATTRIBUTE_ARCHIVE > 0
|
876
|
-
arr << 'compressed' if attributes & FILE_ATTRIBUTE_COMPRESSED > 0
|
877
|
-
arr << 'directory' if attributes & FILE_ATTRIBUTE_DIRECTORY > 0
|
878
|
-
arr << 'encrypted' if attributes & FILE_ATTRIBUTE_ENCRYPTED > 0
|
879
|
-
arr << 'hidden' if attributes & FILE_ATTRIBUTE_HIDDEN > 0
|
880
|
-
arr << 'indexed' if attributes & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED == 0
|
881
|
-
arr << 'normal' if attributes & FILE_ATTRIBUTE_NORMAL > 0
|
882
|
-
arr << 'offline' if attributes & FILE_ATTRIBUTE_OFFLINE > 0
|
883
|
-
arr << 'readonly' if attributes & FILE_ATTRIBUTE_READONLY > 0
|
884
|
-
arr << 'reparse_point' if attributes & FILE_ATTRIBUTE_REPARSE_POINT > 0
|
885
|
-
arr << 'sparse' if attributes & FILE_ATTRIBUTE_SPARSE_FILE > 0
|
886
|
-
arr << 'system' if attributes & FILE_ATTRIBUTE_SYSTEM > 0
|
887
|
-
arr << 'temporary' if attributes & FILE_ATTRIBUTE_TEMPORARY > 0
|
888
|
-
|
889
|
-
arr
|
204
|
+
buffer.wstrip
|
890
205
|
end
|
891
206
|
|
892
|
-
#
|
893
|
-
#
|
207
|
+
# Creates a symbolic link called +new_name+ for the file or directory
|
208
|
+
# +old_name+.
|
894
209
|
#
|
895
|
-
|
896
|
-
|
897
|
-
|
210
|
+
# This method requires Windows Vista or later to work. Otherwise, it
|
211
|
+
# returns nil as per MRI.
|
212
|
+
#
|
213
|
+
def self.symlink(target, link)
|
214
|
+
raise TypeError unless target.is_a?(String)
|
215
|
+
raise TypeError unless link.is_a?(String)
|
898
216
|
|
899
|
-
|
900
|
-
raise ArgumentError, get_last_error
|
901
|
-
end
|
217
|
+
flags = File.directory?(target) ? 1 : 0
|
902
218
|
|
903
|
-
|
219
|
+
wlink = link.wincode
|
220
|
+
wtarget = target.wincode
|
904
221
|
|
905
|
-
|
906
|
-
raise
|
222
|
+
unless CreateSymbolicLinkW(wlink, wtarget, flags)
|
223
|
+
raise SystemCallError.new('CreateSymbolicLink', FFI.errno)
|
907
224
|
end
|
908
225
|
|
909
|
-
|
226
|
+
0 # Comply with spec
|
910
227
|
end
|
911
228
|
|
912
|
-
#
|
229
|
+
# Returns whether or not +file+ is a symlink.
|
913
230
|
#
|
914
|
-
def self.
|
915
|
-
|
916
|
-
|
231
|
+
def self.symlink?(file)
|
232
|
+
return false unless File.exists?(file)
|
233
|
+
bool = false
|
234
|
+
wfile = file.wincode
|
917
235
|
|
918
|
-
|
919
|
-
raise ArgumentError, get_last_error
|
920
|
-
end
|
921
|
-
|
922
|
-
attributes &= ~flags
|
236
|
+
attrib = GetFileAttributesW(wfile)
|
923
237
|
|
924
|
-
if
|
925
|
-
raise
|
238
|
+
if attrib == INVALID_FILE_ATTRIBUTES
|
239
|
+
raise SystemCallError.new('GetFileAttributes', FFI.errno)
|
926
240
|
end
|
927
241
|
|
928
|
-
|
929
|
-
|
242
|
+
if attrib & FILE_ATTRIBUTE_REPARSE_POINT > 0
|
243
|
+
begin
|
244
|
+
find_data = WIN32_FIND_DATA.new
|
245
|
+
handle = FindFirstFileW(wfile, find_data)
|
930
246
|
|
931
|
-
|
247
|
+
if handle == INVALID_HANDLE_VALUE
|
248
|
+
raise SystemCallError.new('FindFirstFile', FFI.errno)
|
249
|
+
end
|
932
250
|
|
933
|
-
|
934
|
-
|
251
|
+
if find_data[:dwReserved0] == IO_REPARSE_TAG_SYMLINK
|
252
|
+
bool = true
|
253
|
+
end
|
254
|
+
ensure
|
255
|
+
CloseHandle(handle)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
bool
|
935
260
|
end
|
936
261
|
|
937
|
-
#
|
262
|
+
# Converts path to a full file path, with all symlinks resolved and relative
|
263
|
+
# paths made absolute. If a second parameter if present, it is used as the
|
264
|
+
# base for resolving leading relative path segments.
|
938
265
|
#
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
266
|
+
# Unlike File.realpath, an error is not raised if the final path created
|
267
|
+
# using a relative path argument doesn't exist.
|
268
|
+
#--
|
269
|
+
# On Windows we only modify the realpath method if the file is a symlink.
|
270
|
+
#
|
271
|
+
def self.realdirpath(file, relative_to = nil)
|
272
|
+
if symlink?(file)
|
273
|
+
if relative_to
|
274
|
+
File.join(relative_to, File.basename(readlink(file)))
|
275
|
+
else
|
276
|
+
readlink(file)
|
277
|
+
end
|
278
|
+
else
|
279
|
+
realdirpath_orig(file, relative_to)
|
945
280
|
end
|
281
|
+
end
|
946
282
|
|
947
|
-
|
948
|
-
|
283
|
+
# Converts path to a full file path, with all symlinks resolved and relative
|
284
|
+
# paths made absolute. If a second parameter if present, it is used as the
|
285
|
+
# base for resolving leading relative path segments.
|
286
|
+
#--
|
287
|
+
# On Windows we only modify the realpath method if the file is a symlink.
|
288
|
+
#
|
289
|
+
def self.realpath(file, relative_to = nil)
|
290
|
+
if symlink?(file)
|
291
|
+
if relative_to
|
292
|
+
result = File.join(relative_to, File.basename(readlink(file)))
|
293
|
+
if File.exists?(result)
|
294
|
+
result
|
295
|
+
else
|
296
|
+
raise SystemCallError.new(result, 2) # Errno::ENOENT
|
297
|
+
end
|
298
|
+
else
|
299
|
+
readlink(file)
|
300
|
+
end
|
949
301
|
else
|
950
|
-
|
302
|
+
realpath_orig(file, relative_to)
|
951
303
|
end
|
304
|
+
end
|
952
305
|
|
953
|
-
|
954
|
-
|
306
|
+
# Returns the path of the of the symbolic link referred to by +file+.
|
307
|
+
#
|
308
|
+
def self.readlink(file)
|
309
|
+
if exists?(file) && !symlink?(file)
|
310
|
+
raise SystemCallError.new(22) # EINVAL, match the spec
|
955
311
|
end
|
956
312
|
|
957
|
-
|
958
|
-
|
313
|
+
wfile = file.wincode
|
314
|
+
path = 0.chr * 512
|
959
315
|
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
in_buf = [in_buf].pack('L')
|
965
|
-
bytes = [0].pack('L')
|
966
|
-
|
967
|
-
# We can't use get_osfhandle here because we need specific attributes
|
968
|
-
handle = CreateFileW(
|
969
|
-
multi_to_wide(self.path),
|
970
|
-
FILE_READ_DATA | FILE_WRITE_DATA,
|
971
|
-
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
972
|
-
0,
|
973
|
-
OPEN_EXISTING,
|
974
|
-
0,
|
975
|
-
0
|
976
|
-
)
|
977
|
-
|
978
|
-
if handle == INVALID_HANDLE_VALUE
|
979
|
-
raise ArgumentError, get_last_error
|
316
|
+
if File.directory?(file)
|
317
|
+
flags = FILE_FLAG_BACKUP_SEMANTICS
|
318
|
+
else
|
319
|
+
flags = FILE_ATTRIBUTE_NORMAL
|
980
320
|
end
|
981
321
|
|
982
322
|
begin
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
bytes,
|
323
|
+
handle = CreateFileW(
|
324
|
+
wfile,
|
325
|
+
GENERIC_READ,
|
326
|
+
FILE_SHARE_READ,
|
327
|
+
nil,
|
328
|
+
OPEN_EXISTING,
|
329
|
+
flags,
|
991
330
|
0
|
992
331
|
)
|
993
332
|
|
994
|
-
|
995
|
-
raise
|
333
|
+
if handle == INVALID_HANDLE_VALUE
|
334
|
+
raise SystemCallError.new('CreateFile', FFI.errno)
|
335
|
+
end
|
336
|
+
|
337
|
+
if GetFinalPathNameByHandleW(handle, path, path.size/2, 0) == 0
|
338
|
+
raise SystemCallError.new('GetFinalPathNameByHandle', FFI.errno)
|
996
339
|
end
|
997
340
|
ensure
|
998
341
|
CloseHandle(handle)
|
999
342
|
end
|
1000
343
|
|
1001
|
-
|
344
|
+
path.wstrip[4..-1] # Remove leading backslashes + question mark
|
1002
345
|
end
|
1003
346
|
|
1004
|
-
|
1005
|
-
# true means that the file is not included in an ordinary directory listing.
|
1006
|
-
#
|
1007
|
-
def hidden=(bool)
|
1008
|
-
wide_path = multi_to_wide(self.path)
|
1009
|
-
attributes = GetFileAttributesW(wide_path)
|
1010
|
-
|
1011
|
-
if attributes == INVALID_FILE_ATTRIBUTES
|
1012
|
-
raise ArgumentError, get_last_error
|
1013
|
-
end
|
1014
|
-
|
1015
|
-
if bool
|
1016
|
-
attributes |= FILE_ATTRIBUTE_HIDDEN;
|
1017
|
-
else
|
1018
|
-
attributes &= ~FILE_ATTRIBUTE_HIDDEN;
|
1019
|
-
end
|
1020
|
-
|
1021
|
-
if SetFileAttributesW(wide_path, attributes) == 0
|
1022
|
-
raise ArgumentError, get_last_error
|
1023
|
-
end
|
347
|
+
## STAT METHODS
|
1024
348
|
|
1025
|
-
|
349
|
+
# Returns the filesystem's block size.
|
350
|
+
#
|
351
|
+
def self.blksize(file)
|
352
|
+
File::Stat.new(file).blksize
|
1026
353
|
end
|
1027
354
|
|
1028
|
-
#
|
1029
|
-
#
|
1030
|
-
# service.
|
355
|
+
# Returns whether or not the file is a block device. For MS Windows a
|
356
|
+
# block device is a removable drive, cdrom or ramdisk.
|
1031
357
|
#
|
1032
|
-
def
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
if attributes == INVALID_FILE_ATTRIBUTES
|
1037
|
-
raise ArgumentError, get_last_error
|
1038
|
-
end
|
1039
|
-
|
1040
|
-
if bool
|
1041
|
-
attributes &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
|
1042
|
-
else
|
1043
|
-
attributes |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
|
1044
|
-
end
|
1045
|
-
|
1046
|
-
if SetFileAttributes(wide_path, attributes) == 0
|
1047
|
-
raise ArgumentError, get_last_error
|
1048
|
-
end
|
1049
|
-
|
1050
|
-
self
|
358
|
+
def self.blockdev?(file)
|
359
|
+
return false unless File.exists?(file)
|
360
|
+
File::Stat.new(file).blockdev?
|
1051
361
|
end
|
1052
362
|
|
1053
|
-
|
1054
|
-
|
1055
|
-
# Sets the normal attribute. Note that only 'true' is a valid argument,
|
1056
|
-
# which has the effect of removing most other attributes. Attempting to
|
1057
|
-
# pass any value except true will raise an ArgumentError.
|
363
|
+
# Returns whether or not the file is a character device.
|
1058
364
|
#
|
1059
|
-
def
|
1060
|
-
unless
|
1061
|
-
|
1062
|
-
end
|
1063
|
-
|
1064
|
-
if SetFileAttributesW(multi_to_wide(self.path),FILE_ATTRIBUTE_NORMAL) == 0
|
1065
|
-
raise ArgumentError, get_last_error
|
1066
|
-
end
|
1067
|
-
|
1068
|
-
self
|
365
|
+
def self.chardev?(file)
|
366
|
+
return false unless File.exists?(file)
|
367
|
+
File::Stat.new(file).chardev?
|
1069
368
|
end
|
1070
369
|
|
1071
|
-
#
|
1072
|
-
# that the data of the file is not immediately available. This attribute
|
1073
|
-
# indicates that the file data has been physically moved to offline storage.
|
1074
|
-
# This attribute is used by Remote Storage, the hierarchical storage
|
1075
|
-
# management software.
|
1076
|
-
#
|
1077
|
-
# Applications should not arbitrarily change this attribute.
|
370
|
+
# Returns whether or not the file is a directory.
|
1078
371
|
#
|
1079
|
-
def
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
if attributes == INVALID_FILE_ATTRIBUTES
|
1084
|
-
raise ArgumentError, get_last_error
|
1085
|
-
end
|
1086
|
-
|
1087
|
-
if bool
|
1088
|
-
attributes |= FILE_ATTRIBUTE_OFFLINE;
|
1089
|
-
else
|
1090
|
-
attributes &= ~FILE_ATTRIBUTE_OFFLINE;
|
1091
|
-
end
|
1092
|
-
|
1093
|
-
if SetFileAttributesW(wide_path, attributes) == 0
|
1094
|
-
raise ArgumentError, get_last_error
|
1095
|
-
end
|
1096
|
-
|
1097
|
-
self
|
372
|
+
def self.directory?(file)
|
373
|
+
return false unless File.exists?(file)
|
374
|
+
File::Stat.new(file).directory?
|
1098
375
|
end
|
1099
376
|
|
1100
|
-
#
|
1101
|
-
# readonly. Applications can read the file but cannot write to it or delete
|
1102
|
-
# it. In the case of a directory, applications cannot delete it.
|
377
|
+
# Returns whether or not the file is executable.
|
1103
378
|
#
|
1104
|
-
def
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
if attributes == INVALID_FILE_ATTRIBUTES
|
1109
|
-
raise ArgumentError, get_last_error
|
1110
|
-
end
|
1111
|
-
|
1112
|
-
if bool
|
1113
|
-
attributes |= FILE_ATTRIBUTE_READONLY;
|
1114
|
-
else
|
1115
|
-
attributes &= ~FILE_ATTRIBUTE_READONLY;
|
1116
|
-
end
|
1117
|
-
|
1118
|
-
if SetFileAttributesW(wide_path, attributes) == 0
|
1119
|
-
raise ArgumentError, get_last_error
|
1120
|
-
end
|
1121
|
-
|
1122
|
-
self
|
379
|
+
def self.executable?(file)
|
380
|
+
return false unless File.exists?(file)
|
381
|
+
File::Stat.new(file).executable?
|
1123
382
|
end
|
1124
383
|
|
1125
|
-
#
|
1126
|
-
# remove the sparse property from a file.
|
384
|
+
# Returns whether or not the file is a regular file.
|
1127
385
|
#
|
1128
|
-
def
|
1129
|
-
unless
|
1130
|
-
|
1131
|
-
return
|
1132
|
-
end
|
1133
|
-
|
1134
|
-
bytes = [0].pack('L')
|
1135
|
-
|
1136
|
-
handle = CreateFileW(
|
1137
|
-
multi_to_wide(self.path),
|
1138
|
-
FILE_READ_DATA | FILE_WRITE_DATA,
|
1139
|
-
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
1140
|
-
0,
|
1141
|
-
OPEN_EXISTING,
|
1142
|
-
FSCTL_SET_SPARSE(),
|
1143
|
-
0
|
1144
|
-
)
|
1145
|
-
|
1146
|
-
if handle == INVALID_HANDLE_VALUE
|
1147
|
-
raise ArgumentError, get_last_error
|
1148
|
-
end
|
1149
|
-
|
1150
|
-
begin
|
1151
|
-
bool = DeviceIoControl(
|
1152
|
-
handle,
|
1153
|
-
FSCTL_SET_SPARSE(),
|
1154
|
-
0,
|
1155
|
-
0,
|
1156
|
-
0,
|
1157
|
-
0,
|
1158
|
-
bytes,
|
1159
|
-
0
|
1160
|
-
)
|
1161
|
-
|
1162
|
-
unless bool == 0
|
1163
|
-
raise ArgumentError, get_last_error
|
1164
|
-
end
|
1165
|
-
ensure
|
1166
|
-
CloseHandle(handle)
|
1167
|
-
end
|
1168
|
-
|
1169
|
-
self
|
386
|
+
def self.file?(file)
|
387
|
+
return false unless File.exists?(file)
|
388
|
+
File::Stat.new(file).file?
|
1170
389
|
end
|
1171
390
|
|
1172
|
-
#
|
1173
|
-
|
391
|
+
# Identifies the type of file. The return string is one of 'file',
|
392
|
+
# 'directory', 'characterSpecial', 'socket' or 'unknown'.
|
1174
393
|
#
|
1175
|
-
def
|
1176
|
-
|
1177
|
-
|
394
|
+
def self.ftype(file)
|
395
|
+
File::Stat.new(file).ftype
|
396
|
+
end
|
1178
397
|
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
398
|
+
# Returns true if the process owner's ID is the same as one of the file's groups.
|
399
|
+
#
|
400
|
+
def self.grpowned?(file)
|
401
|
+
return false unless File.exists?(file)
|
402
|
+
File::Stat.new(file).grpowned?
|
403
|
+
end
|
1182
404
|
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
405
|
+
# Returns whether or not the current process owner is the owner of the file.
|
406
|
+
#
|
407
|
+
def self.owned?(file)
|
408
|
+
return false unless File.exists?(file)
|
409
|
+
File::Stat.new(file).owned?
|
410
|
+
end
|
1188
411
|
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
412
|
+
# Returns whether or not the file is a pipe.
|
413
|
+
#
|
414
|
+
def self.pipe?(file)
|
415
|
+
return false unless File.exists?(file)
|
416
|
+
File::Stat.new(file).pipe?
|
417
|
+
end
|
1192
418
|
|
1193
|
-
|
419
|
+
# Returns whether or not the file is readable by the process owner.
|
420
|
+
#
|
421
|
+
def self.readable?(file)
|
422
|
+
return false unless File.exists?(file)
|
423
|
+
File::Stat.new(file).readable?
|
1194
424
|
end
|
1195
425
|
|
1196
|
-
#
|
426
|
+
# Synonym for File.readable?
|
1197
427
|
#
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
428
|
+
def self.readable_real?(file)
|
429
|
+
return false unless File.exists?(file)
|
430
|
+
File::Stat.new(file).readable_real?
|
431
|
+
end
|
432
|
+
|
433
|
+
# Returns a File::Stat object as defined in the win32-file-stat library.
|
1203
434
|
#
|
1204
|
-
def
|
1205
|
-
|
1206
|
-
|
435
|
+
def self.stat(file)
|
436
|
+
File::Stat.new(file)
|
437
|
+
end
|
1207
438
|
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
439
|
+
# Returns whether or not the file is readable by others. Note that this
|
440
|
+
# merely returns true or false, not permission bits (or nil).
|
441
|
+
#
|
442
|
+
def self.world_readable?(file)
|
443
|
+
return false unless File.exists?(file)
|
444
|
+
File::Stat.new(file).world_readable?
|
445
|
+
end
|
1211
446
|
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
447
|
+
# Returns whether or not the file is writable by others. Note that this
|
448
|
+
# merely returns true or false, not permission bits (or nil).
|
449
|
+
#
|
450
|
+
def self.world_writable?(file)
|
451
|
+
return false unless File.exists?(file)
|
452
|
+
File::Stat.new(file).world_writable?
|
453
|
+
end
|
1217
454
|
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
455
|
+
# Returns whether or not the file is writable by the current process owner.
|
456
|
+
#
|
457
|
+
def self.writable?(file)
|
458
|
+
return false unless File.exists?(file)
|
459
|
+
File::Stat.new(file).writable?
|
460
|
+
end
|
1221
461
|
|
1222
|
-
|
462
|
+
# Synonym for File.writable?
|
463
|
+
#
|
464
|
+
def self.writable_real?(file)
|
465
|
+
return false unless File.exists?(file)
|
466
|
+
File::Stat.new(file).writable_real?
|
1223
467
|
end
|
1224
468
|
|
1225
|
-
# Singleton aliases
|
469
|
+
# Singleton aliases
|
1226
470
|
class << self
|
1227
|
-
alias
|
1228
|
-
alias
|
1229
|
-
alias
|
1230
|
-
alias :unset_attr :remove_attributes
|
471
|
+
alias lstat stat
|
472
|
+
alias executable_real? executable?
|
473
|
+
alias socket? pipe?
|
1231
474
|
end
|
1232
475
|
|
1233
|
-
|
476
|
+
## Instance Methods
|
1234
477
|
|
1235
|
-
#
|
478
|
+
# Same as MRI, except it returns a stat object using the win32-file-stat gem.
|
1236
479
|
#
|
1237
|
-
def
|
1238
|
-
|
1239
|
-
file,
|
1240
|
-
GENERIC_READ,
|
1241
|
-
FILE_SHARE_READ,
|
1242
|
-
0,
|
1243
|
-
OPEN_EXISTING,
|
1244
|
-
0,
|
1245
|
-
0
|
1246
|
-
)
|
1247
|
-
|
1248
|
-
if handle == INVALID_HANDLE_VALUE
|
1249
|
-
raise ArgumentError, get_last_error
|
1250
|
-
end
|
1251
|
-
|
1252
|
-
sid_ptr = [0].pack('L')
|
1253
|
-
|
1254
|
-
begin
|
1255
|
-
rv = GetSecurityInfo(
|
1256
|
-
handle,
|
1257
|
-
SE_FILE_OBJECT,
|
1258
|
-
OWNER_SECURITY_INFORMATION,
|
1259
|
-
sid_ptr,
|
1260
|
-
nil,
|
1261
|
-
nil,
|
1262
|
-
nil,
|
1263
|
-
nil
|
1264
|
-
)
|
1265
|
-
|
1266
|
-
if rv != 0
|
1267
|
-
raise get_last_error
|
1268
|
-
end
|
1269
|
-
|
1270
|
-
name_buf = 0.chr * 80
|
1271
|
-
name_cch = [name_buf.size].pack('L')
|
1272
|
-
|
1273
|
-
domain_buf = 0.chr * 80
|
1274
|
-
domain_cch = [domain_buf.size].pack('L')
|
1275
|
-
sid_name = 0.chr * 4
|
1276
|
-
|
1277
|
-
bool = LookupAccountSid(
|
1278
|
-
nil,
|
1279
|
-
sid_ptr.unpack('L').first,
|
1280
|
-
name_buf,
|
1281
|
-
name_cch,
|
1282
|
-
domain_buf,
|
1283
|
-
domain_cch,
|
1284
|
-
sid_name
|
1285
|
-
)
|
1286
|
-
|
1287
|
-
unless bool
|
1288
|
-
raise get_last_error
|
1289
|
-
end
|
1290
|
-
ensure
|
1291
|
-
CloseHandle(handle)
|
1292
|
-
end
|
1293
|
-
|
1294
|
-
name_buf.strip
|
480
|
+
def stat
|
481
|
+
File::Stat.new(self.path)
|
1295
482
|
end
|
1296
483
|
end
|
484
|
+
|
485
|
+
if $0 == __FILE__
|
486
|
+
p File.dirname("C:/Users/djberge/test.txt////")
|
487
|
+
p File.basename("C:/Users/djberge/test.txt")
|
488
|
+
p File.basename("C:/Users/djberge/test.txt", ".txt")
|
489
|
+
end
|