win32-file 0.6.9 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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