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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bc3e4da61ae4cf054befbe20694d8977bb5e9aa7
4
- data.tar.gz: cf28cb16d84a87265e5de6be73513e3de01b0ad2
3
+ metadata.gz: fe733f3c66c36ad77e79b95f169dbfbea6d495d0
4
+ data.tar.gz: fc11dce1680290901e70b7423e63a361cf5e0118
5
5
  SHA512:
6
- metadata.gz: 72c1985a82ca669a19b1728e86c0d8bcb354150fabb2cedc2cb8fb314fddcad96cb931c192d16bbd6ae77e12c4e235c80bb7b5479ec54df53bf255dee7a25212
7
- data.tar.gz: 2632f743be21c9f4b3f523bcd8b0ae37c49527760864e97a57cd57e89221e8b290ad4c27eaa65af7a9e2d9937354508e82b31404fa0040f4e510785ad8ba02ab
6
+ metadata.gz: 062129c39980b67de8373c48dcdbd612d6001fd7afd49e74c19bdf06f3779a8ea1d5658da4c3fb5b931a04dcfa00447b2de64711d76fee31aea250fbb1f41919
7
+ data.tar.gz: 0035f29a6657abf5535498e77a38acdf106133f922134ec164c14dad5b3b5f9d2e7550cff363ac5a2e470e93b501822e589d6d2c1718715ef902c8ea2fe52fff
data/CHANGES CHANGED
@@ -1,7 +1,13 @@
1
- == 0.6.9 - 11-Dec-2013
2
- * Added back some missing require statements.
3
- * Updated the dependency versions and added Rake as a development dependency.
4
- * Updated the create task in the Rakefile for Rubygems 2.x.
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
- * test/sometestfile.txt
8
- * test/test_win32_file_attributes.rb
9
- * test/test_win32_file_constants.rb
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/test_win32_file_security.rb
13
- * test/test_win32_file_stat.rb
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.hidden?(somefile)
16
- p File.attributes(somefile)
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
- File.open(somefile){ |fh|
19
- fh.hidden = true
20
- }
18
+ # See redefined methods below
21
19
 
22
20
  == Singleton Methods Added
23
- * File.attributes
24
- * File.archive?
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 # UNC path issues
60
- * File.blksize # Not implemented in MRI
61
- * File.blockdev? # Not implemented in MRI
62
- * File.chardev? # Not implemented in MRI
63
- * File.directory? # Wide character string handling
64
- * File.dirname # UNC path issues
65
- * File.lstat # Not implemented in MRI
66
- * File.readlink # Not implemented in MRI
67
- * File.size # 2GB limitation
68
- * File.split # UNC path issues
69
- * File.stat # Uses object returned by win32-file-stat
70
- * File.symlink # Not implemented in MRI
71
- * File.symlink? # Not implemented in MRI
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("attributes") do |t|
32
+ Rake::TestTask.new("link") do |t|
33
33
  t.verbose = true
34
34
  t.warning = true
35
- t.test_files = FileList['test/test_win32_file_attributes.rb']
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 'windows/security'
2
- require 'windows/limits'
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::Error
7
- include Windows::File
8
- include Windows::Security
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.6.9'
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
- # :stopdoc:
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
- # Return to multi-byte
542
- file = wide_to_multi(file)
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
- # Empty paths, short relative paths
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
- file
550
- end
56
+ encoding = file.encoding
57
+ wfile = file.wincode
551
58
 
552
- # Join path string components together into a single string.
553
- #
554
- # This method was reimplemented so that it automatically converts
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
- # Returns +path+ in long format. For example, if 'SOMEFI~1.TXT'
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
- # Returns the path of the of the symbolic link referred to by +file+.
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
- wide_to_multi(path).strip[4..-1] # get rid of prepending "\\?\"
68
+ if suffix
69
+ if suffix == '.*'
70
+ PathRemoveExtensionW(ptr)
615
71
  else
616
- msg = "readlink() function is unimplemented on this machine"
617
- raise NotImplementedError, msg
618
- end
619
- end
72
+ ext = PathFindExtensionW(ptr).read_string(suffix.length * 2).wstrip
620
73
 
621
- # Returns +path+ in 8.3 format. For example, 'c:\documentation.doc'
622
- # would be returned as 'c:\docume~1.doc'.
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
- # Splits the given string into a directory and a file component and
633
- # returns them in a two element array. This was reimplemented because
634
- # the current version does not handle UNC paths properly.
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
- if file.empty? || PathIsRootW(multi_to_wide(file))
640
- array.push(file, '')
641
- else
642
- array.push(File.dirname(file), File.basename(file))
643
- end
644
- array
645
- end
84
+ file
85
+ end
646
86
 
647
- # Creates a symbolic link called +new_name+ for the file or directory
648
- # +old_name+.
649
- #
650
- # This method requires Windows Vista or later to work. Otherwise, it
651
- # returns nil as per MRI.
652
- #
653
- def symlink(old_name, new_name)
654
- if defined? CreateSymbolicLink
655
- old_name = multi_to_wide(old_name)
656
- new_name = multi_to_wide(new_name)
657
- flags = File.directory?(wide_to_multi(old_name)) ? 1 : 0
658
-
659
- unless CreateSymbolicLinkW(new_name, old_name, flags)
660
- raise ArgumentError, get_last_error
661
- end
662
- else
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
- # Return true if the named file is a symbolic link, false otherwise.
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
- bool
696
- end
107
+ # Store original encoding, restore it later
108
+ encoding = file.encoding
697
109
 
698
- ## Stat methods
110
+ # Convert to UTF-16LE
111
+ wfile = file.wincode
699
112
 
700
- # Returns a File::Stat object, as defined in the win32-file-stat package.
701
- #
702
- def stat(file)
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
- # Identical to File.stat on Windows.
707
- #
708
- def lstat(file)
709
- File::Stat.new(file)
710
- end
118
+ ptr = FFI::MemoryPointer.from_string(wfile)
711
119
 
712
- # Returns the file system's block size.
713
- #
714
- def blksize(file)
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
- # Returns whether or not +file+ is a block device. For MS Windows this
719
- # means a removable drive, cdrom or ramdisk.
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
- # Returns true if the file is a character device. This replaces the
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
- # Returns the size of the file in bytes.
733
- #
734
- # This was reimplemented because the current version does not handle file
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
- # We no longer need the aliases, so remove them
742
- remove_method(:basename_orig, :blockdev_orig, :chardev_orig)
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
- # Returns true if the file or directory is an archive file. Applications
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
- # Returns true if the file or directory is compressed. For a file, this
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
- def self.compressed?(file)
763
- File::Stat.new(file).compressed?
764
- end
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
- def self.encrypted?(file)
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
- def self.hidden?(file)
779
- File::Stat.new(file).hidden?
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.indexed?(file)
786
- File::Stat.new(file).indexed?
154
+ def self.join(*args)
155
+ return join_orig(*args).tr("/", "\\")
787
156
  end
788
157
 
789
- # Returns true if the file or directory has no other attributes set.
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.normal?(file)
792
- File::Stat.new(file).normal?
793
- end
162
+ def self.split(file)
163
+ array = []
794
164
 
795
- # Returns true if the data of the file is not immediately available. This
796
- # attribute indicates that the file data has been physically moved to
797
- # offline storage. This attribute is used by Remote Storage, the
798
- # hierarchical storage management software. Applications should not
799
- # arbitrarily change this attribute.
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
- # Returns true if The file or directory is read-only. Applications can
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 true if the file or directory has an associated reparse point. A
814
- # reparse point is a collection of user defined data associated with a file
815
- # or directory. For more on reparse points, search
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
- def self.reparse_point?(file)
819
- File::Stat.new(file).reparse_point?
820
- end
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.sparse?(file)
827
- File::Stat.new(file).sparse?
828
- end
182
+ def self.long_path(file)
183
+ buffer = 0.chr * 1024
184
+ wfile = file.wincode
829
185
 
830
- # Returns true if the file or directory is part of the operating system
831
- # or is used exclusively by the operating system.
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
- # Returns true if the file is being used for temporary storage.
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 an array of strings indicating the attributes for that file. The
850
- # possible values are:
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.attributes(file)
867
- attributes = GetFileAttributesW(multi_to_wide(file))
196
+ def self.short_path(file)
197
+ buffer = 0.chr * 1024
198
+ wfile = file.wincode
868
199
 
869
- if attributes == INVALID_FILE_ATTRIBUTES
870
- raise ArgumentError, get_last_error
200
+ if GetShortPathNameW(wfile, buffer, buffer.size/2) == 0
201
+ raise SystemCallError.new('GetShortPathName', FFI.errno)
871
202
  end
872
203
 
873
- arr = []
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
- # Sets the file attributes based on the given (numeric) +flags+. This does
893
- # not remove existing attributes, it merely adds to them.
207
+ # Creates a symbolic link called +new_name+ for the file or directory
208
+ # +old_name+.
894
209
  #
895
- def self.set_attributes(file, flags)
896
- file = multi_to_wide(file)
897
- attributes = GetFileAttributesW(file)
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
- if attributes == INVALID_FILE_ATTRIBUTES
900
- raise ArgumentError, get_last_error
901
- end
217
+ flags = File.directory?(target) ? 1 : 0
902
218
 
903
- attributes |= flags
219
+ wlink = link.wincode
220
+ wtarget = target.wincode
904
221
 
905
- if SetFileAttributesW(file, attributes) == 0
906
- raise ArgumentError, get_last_error
222
+ unless CreateSymbolicLinkW(wlink, wtarget, flags)
223
+ raise SystemCallError.new('CreateSymbolicLink', FFI.errno)
907
224
  end
908
225
 
909
- self
226
+ 0 # Comply with spec
910
227
  end
911
228
 
912
- # Removes the file attributes based on the given (numeric) +flags+.
229
+ # Returns whether or not +file+ is a symlink.
913
230
  #
914
- def self.remove_attributes(file, flags)
915
- file = multi_to_wide(file)
916
- attributes = GetFileAttributesW(file)
231
+ def self.symlink?(file)
232
+ return false unless File.exists?(file)
233
+ bool = false
234
+ wfile = file.wincode
917
235
 
918
- if attributes == INVALID_FILE_ATTRIBUTES
919
- raise ArgumentError, get_last_error
920
- end
921
-
922
- attributes &= ~flags
236
+ attrib = GetFileAttributesW(wfile)
923
237
 
924
- if SetFileAttributesW(file, attributes) == 0
925
- raise ArgumentError, get_last_error
238
+ if attrib == INVALID_FILE_ATTRIBUTES
239
+ raise SystemCallError.new('GetFileAttributes', FFI.errno)
926
240
  end
927
241
 
928
- self
929
- end
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
- # Instance methods
247
+ if handle == INVALID_HANDLE_VALUE
248
+ raise SystemCallError.new('FindFirstFile', FFI.errno)
249
+ end
932
250
 
933
- def stat
934
- File::Stat.new(self.path)
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
- # Sets whether or not the file is an archive file.
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
- def archive=(bool)
940
- wide_path = multi_to_wide(self.path)
941
- attributes = GetFileAttributesW(wide_path)
942
-
943
- if attributes == INVALID_FILE_ATTRIBUTES
944
- raise ArgumentError, get_last_error
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
- if bool
948
- attributes |= FILE_ATTRIBUTE_ARCHIVE;
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
- attributes &= ~FILE_ATTRIBUTE_ARCHIVE;
302
+ realpath_orig(file, relative_to)
951
303
  end
304
+ end
952
305
 
953
- if SetFileAttributesW(wide_path, attributes) == 0
954
- raise ArgumentError, get_last_error
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
- self
958
- end
313
+ wfile = file.wincode
314
+ path = 0.chr * 512
959
315
 
960
- # Sets whether or not the file is a compressed file.
961
- #
962
- def compressed=(bool)
963
- in_buf = bool ? COMPRESSION_FORMAT_DEFAULT : COMPRESSION_FORMAT_NONE
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
- bool = DeviceIoControl(
984
- handle,
985
- FSCTL_SET_COMPRESSION(),
986
- in_buf,
987
- in_buf.length,
988
- 0,
989
- 0,
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
- unless bool
995
- raise ArgumentError, get_last_error
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
- self
344
+ path.wstrip[4..-1] # Remove leading backslashes + question mark
1002
345
  end
1003
346
 
1004
- # Sets the hidden attribute to true or false. Setting this attribute to
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
- self
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
- # Sets the 'indexed' attribute to true or false. Setting this to
1029
- # false means that the file will not be indexed by the content indexing
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 indexed=(bool)
1033
- wide_path = multi_to_wide(self.path)
1034
- attributes = GetFileAttributesW(wide_path)
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
- alias :content_indexed= :indexed=
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 normal=(bool)
1060
- unless bool
1061
- raise ArgumentError, "only 'true' may be passed as an argument"
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
- # Sets whether or not a file is online or not. Setting this to false means
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 offline=(bool)
1080
- wide_path = multi_to_wide(self.path)
1081
- attributes = GetFileAttributesW(wide_path)
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
- # Sets the readonly attribute. If set to true the the file or directory is
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 readonly=(bool)
1105
- wide_path = multi_to_wide(self.path)
1106
- attributes = GetFileAttributesW(wide_path)
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
- # Sets the file to a sparse (usually image) file. Note that you cannot
1126
- # remove the sparse property from a file.
384
+ # Returns whether or not the file is a regular file.
1127
385
  #
1128
- def sparse=(bool)
1129
- unless bool
1130
- warn 'cannot remove sparse property from a file - operation ignored'
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
- # Set whether or not the file is a system file. A system file is a file
1173
- # that is part of the operating system or is used exclusively by it.
391
+ # Identifies the type of file. The return string is one of 'file',
392
+ # 'directory', 'characterSpecial', 'socket' or 'unknown'.
1174
393
  #
1175
- def system=(bool)
1176
- wide_path = multi_to_wide(self.path)
1177
- attributes = GetFileAttributesW(wide_path)
394
+ def self.ftype(file)
395
+ File::Stat.new(file).ftype
396
+ end
1178
397
 
1179
- if attributes == INVALID_FILE_ATTRIBUTES
1180
- raise ArgumentError, get_last_error
1181
- end
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
- if bool
1184
- attributes |= FILE_ATTRIBUTE_SYSTEM;
1185
- else
1186
- attributes &= ~FILE_ATTRIBUTE_SYSTEM;
1187
- end
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
- if SetFileAttributesW(wide_path, attributes) == 0
1190
- raise ArgumentError, get_last_error
1191
- end
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
- self
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
- # Sets whether or not the file is being used for temporary storage.
426
+ # Synonym for File.readable?
1197
427
  #
1198
- # File systems avoid writing data back to mass storage if sufficient cache
1199
- # memory is available, because often the application deletes the temporary
1200
- # file shortly after the handle is closed. In that case, the system can
1201
- # entirely avoid writing the data. Otherwise, the data will be written
1202
- # after the handle is closed.
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 temporary=(bool)
1205
- wide_path = multi_to_wide(self.path)
1206
- attributes = GetFileAttributesW(wide_path)
435
+ def self.stat(file)
436
+ File::Stat.new(file)
437
+ end
1207
438
 
1208
- if attributes == INVALID_FILE_ATTRIBUTES
1209
- raise ArgumentError, get_last_error
1210
- end
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
- if bool
1213
- attributes |= FILE_ATTRIBUTE_TEMPORARY;
1214
- else
1215
- attributes &= ~FILE_ATTRIBUTE_TEMPORARY;
1216
- end
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
- if SetFileAttributesW(wide_path, attributes) == 0
1219
- raise ArgumentError, get_last_error
1220
- end
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
- self
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, mostly for backwards compatibility
469
+ # Singleton aliases
1226
470
  class << self
1227
- alias :read_only? :readonly?
1228
- alias :content_indexed? :indexed?
1229
- alias :set_attr :set_attributes
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
- private
476
+ ## Instance Methods
1234
477
 
1235
- # Get the owner of a given file by name.
478
+ # Same as MRI, except it returns a stat object using the win32-file-stat gem.
1236
479
  #
1237
- def get_owner(file)
1238
- handle = CreateFile(
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