win32-file 0.7.2 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
data/MANIFEST CHANGED
@@ -1,13 +1,15 @@
1
- * CHANGES
2
- * MANIFEST
3
- * Rakefile
4
- * README
5
- * win32-file.gemspec
6
- * lib/win32/file.rb
7
- * lib/win32/file/constants.rb
8
- * lib/win32/file/functions.rb
9
- * lib/win32/file/structs.rb
10
- * test/test_win32_file_path.rb
11
- * test/test_win32_file_link.rb
12
- * test/test_win32_file_misc.rb
13
- * test/test_win32_file_stat.rb
1
+ * CHANGES
2
+ * MANIFEST
3
+ * Rakefile
4
+ * README
5
+ * win32-file.gemspec
6
+ * certs/djberg96_pub.pem
7
+ * lib/win32-file.rb
8
+ * lib/win32/file.rb
9
+ * lib/win32/file/constants.rb
10
+ * lib/win32/file/functions.rb
11
+ * lib/win32/file/structs.rb
12
+ * test/test_win32_file_path.rb
13
+ * test/test_win32_file_link.rb
14
+ * test/test_win32_file_misc.rb
15
+ * test/test_win32_file_stat.rb
data/README CHANGED
@@ -1,70 +1,70 @@
1
- == Description
2
- Additional methods for the File class on MS Windows. Plus, several existing
3
- methods have been redefined to make them work properly on MS Windows.
4
-
5
- == Prerequisites
6
- * win32-file-stat
7
-
8
- == Installation
9
- gem install win32-file
10
-
11
- == Synopsis
12
- require 'win32/file'
13
-
14
- p File.long_path("C:/Progra~1") # => C:\Program Files
15
- p File.short_path("C:/Program Files") # => C:\Progra~1
16
-
17
- # See redefined methods below
18
-
19
- == Singleton Methods Added
20
- * File.long_path
21
- * File.short_path
22
-
23
- == Singleton Methods Redefined
24
- * File.basename # UNC path issues, root path differences.
25
- * File.blksize # Not implemented in MRI
26
- * File.blockdev? # Not implemented in MRI
27
- * File.chardev? # Not implemented in MRI
28
- * File.directory? # Better wide character string handling than MRI
29
- * File.dirname # UNC path issues in MRI
30
- * File.executable? # Not implemented in MRI
31
- * File.file? # Handles non-regular files better than MRI
32
- * File.ftype # Handles non-regular files better than MRI
33
- * File.join # For uniform handling of path separators.
34
- * File.grpowned? # Not implemented in MRI
35
- * File.lstat # Not implemented in MRI
36
- * File.owned? # Not implemented in MRI
37
- * File.pipe? # Not implemented in MRI
38
- * File.readable? # Not implemented in MRI
39
- * File.realpath # MRI doesn't handle symlinks
40
- * File.realdirpath # MRI doesn't handle symlinks
41
- * File.socket? # Not implemented in MRI
42
- * File.readlink # Not implemented in MRI
43
- * File.split # UNC path issues in MRI
44
- * File.stat # Uses object returned by win32-file-stat
45
- * File.symlink # Not implemented in MRI
46
- * File.symlink? # Not implemented in MRI
47
- * File.writable? # Not implemented in MRI
48
- * File.world_writable? # Not implemented in MRI
49
-
50
- == Known issues or bugs
51
- The File.exist? method will return true on stale symlinks.
52
-
53
- Please report any other issues you find on the github page at:
54
-
55
- https://github.com/djberg96/win32-file/issues
56
-
57
- == License
58
- Artistic 2.0
59
-
60
- == Copyright
61
- (C) 2003-2014, Daniel J. Berger, All Rights Reserved
62
-
63
- == Warranty
64
- This package is provided "as is" and without any express or
65
- implied warranties, including, without limitation, the implied
66
- warranties of merchantability and fitness for a particular purpose.
67
-
68
- == Authors
69
- * Daniel J. Berger
70
- * Park Heesob
1
+ == Description
2
+ Additional methods for the File class on MS Windows. Plus, several existing
3
+ methods have been redefined to make them work properly on MS Windows.
4
+
5
+ == Prerequisites
6
+ * win32-file-stat
7
+
8
+ == Installation
9
+ gem install win32-file
10
+
11
+ == Synopsis
12
+ require 'win32/file'
13
+
14
+ p File.long_path("C:/Progra~1") # => C:\Program Files
15
+ p File.short_path("C:/Program Files") # => C:\Progra~1
16
+
17
+ # See redefined methods below
18
+
19
+ == Singleton Methods Added
20
+ * File.long_path
21
+ * File.short_path
22
+
23
+ == Singleton Methods Redefined
24
+ * File.basename # UNC path issues, root path differences.
25
+ * File.blksize # Not implemented in MRI
26
+ * File.blockdev? # Not implemented in MRI
27
+ * File.chardev? # Not implemented in MRI
28
+ * File.directory? # Better wide character string handling than MRI
29
+ * File.dirname # UNC path issues in MRI
30
+ * File.executable? # Not implemented in MRI
31
+ * File.file? # Handles non-regular files better than MRI
32
+ * File.ftype # Handles non-regular files better than MRI
33
+ * File.join # For uniform handling of path separators.
34
+ * File.grpowned? # Not implemented in MRI
35
+ * File.lstat # Not implemented in MRI
36
+ * File.owned? # Not implemented in MRI
37
+ * File.pipe? # Not implemented in MRI
38
+ * File.readable? # Not implemented in MRI
39
+ * File.realpath # MRI doesn't handle symlinks
40
+ * File.realdirpath # MRI doesn't handle symlinks
41
+ * File.socket? # Not implemented in MRI
42
+ * File.readlink # Not implemented in MRI
43
+ * File.split # UNC path issues in MRI
44
+ * File.stat # Uses object returned by win32-file-stat
45
+ * File.symlink # Not implemented in MRI
46
+ * File.symlink? # Not implemented in MRI
47
+ * File.writable? # Not implemented in MRI
48
+ * File.world_writable? # Not implemented in MRI
49
+
50
+ == Known issues or bugs
51
+ The File.exist? method will return true on stale symlinks.
52
+
53
+ Please report any other issues you find on the github page at:
54
+
55
+ https://github.com/djberg96/win32-file/issues
56
+
57
+ == License
58
+ Artistic 2.0
59
+
60
+ == Copyright
61
+ (C) 2003-2015, Daniel J. Berger, All Rights Reserved
62
+
63
+ == Warranty
64
+ This package is provided "as is" and without any express or
65
+ implied warranties, including, without limitation, the implied
66
+ warranties of merchantability and fitness for a particular purpose.
67
+
68
+ == Authors
69
+ * Daniel J. Berger
70
+ * Park Heesob
data/Rakefile CHANGED
@@ -1,52 +1,49 @@
1
- require 'rake'
2
- require 'rake/testtask'
3
- require 'rake/clean'
4
-
5
- CLEAN.include("**/*.txt", "**/*.gem", "**/*.rbc")
6
-
7
- namespace 'gem' do
8
- desc 'Create the win32-file gem'
9
- task :create => [:clean] do
10
- spec = eval(IO.read('win32-file.gemspec'))
11
- if Gem::VERSION < "2.0"
12
- Gem::Builder.new(spec).build
13
- else
14
- require 'rubygems/package'
15
- Gem::Package.build(spec)
16
- end
17
- end
18
-
19
- desc 'Install the win32-file gem'
20
- task :install => [:create] do
21
- file = Dir['win32-file*.gem'].first
22
- sh "gem install -l #{file}"
23
- end
24
- end
25
-
26
- namespace 'test' do
27
- Rake::TestTask.new("all") do |t|
28
- t.verbose = true
29
- t.warning = true
30
- end
31
-
32
- Rake::TestTask.new("link") do |t|
33
- t.verbose = true
34
- t.warning = true
35
- t.test_files = FileList['test/test_win32_file_link.rb']
36
- end
37
-
38
- Rake::TestTask.new("path") do |t|
39
- t.verbose = true
40
- t.warning = true
41
- t.test_files = FileList['test/test_win32_file_path.rb']
42
- end
43
-
44
- Rake::TestTask.new("stat") do |t|
45
- t.verbose = true
46
- t.warning = true
47
- t.test_files = FileList['test/test_win32_file_stat.rb']
48
- end
49
- end
50
-
51
- task :test => ['test:all']
52
- task :default => ['test:all']
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/clean'
4
+
5
+ CLEAN.include("**/*.txt", "**/*.gem", "**/*.rbc")
6
+
7
+ namespace 'gem' do
8
+ desc 'Create the win32-file gem'
9
+ task :create => [:clean] do
10
+ require 'rubygems/package'
11
+ spec = eval(IO.read('win32-file.gemspec'))
12
+ spec.signing_key = File.join(Dir.home, '.ssh', 'gem-private_key.pem')
13
+ Gem::Package.build(spec)
14
+ end
15
+
16
+ desc 'Install the win32-file gem'
17
+ task :install => [:create] do
18
+ file = Dir['win32-file*.gem'].first
19
+ sh "gem install -l #{file}"
20
+ end
21
+ end
22
+
23
+ namespace 'test' do
24
+ Rake::TestTask.new("all") do |t|
25
+ t.verbose = true
26
+ t.warning = true
27
+ end
28
+
29
+ Rake::TestTask.new("link") do |t|
30
+ t.verbose = true
31
+ t.warning = true
32
+ t.test_files = FileList['test/test_win32_file_link.rb']
33
+ end
34
+
35
+ Rake::TestTask.new("path") do |t|
36
+ t.verbose = true
37
+ t.warning = true
38
+ t.test_files = FileList['test/test_win32_file_path.rb']
39
+ end
40
+
41
+ Rake::TestTask.new("stat") do |t|
42
+ t.verbose = true
43
+ t.warning = true
44
+ t.test_files = FileList['test/test_win32_file_stat.rb']
45
+ end
46
+ end
47
+
48
+ task :test => ['test:all']
49
+ task :default => ['test:all']
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDcDCCAligAwIBAgIBATANBgkqhkiG9w0BAQUFADA/MREwDwYDVQQDDAhkamJl
3
+ cmc5NjEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPyLGQBGRYDY29t
4
+ MB4XDTE1MDkwMjIwNDkxOFoXDTE2MDkwMTIwNDkxOFowPzERMA8GA1UEAwwIZGpi
5
+ ZXJnOTYxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkWA2Nv
6
+ bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMyTkvXqRp6hLs9eoJOS
7
+ Hmi8kRYbq9Vkf15/hMxJpotYMgJVHHWrmDcC5Dye2PbnXjTkKf266Zw0PtT9h+lI
8
+ S3ts9HO+vaCFSMwFFZmnWJSpQ3CNw2RcHxjWkk9yF7imEM8Kz9ojhiDXzBetdV6M
9
+ gr0lV/alUr7TNVBDngbXEfTWscyXh1qd7xZ4EcOdsDktCe5G45N/o3662tPQvJsi
10
+ FOF0CM/KuBsa/HL1/eoEmF4B3EKIRfTHrQ3hu20Kv3RJ88QM4ec2+0dd97uX693O
11
+ zv6981fyEg+aXLkxrkViM/tz2qR2ZE0jPhHTREPYeMEgptRkTmWSKAuLVWrJEfgl
12
+ DtkCAwEAAaN3MHUwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFEwe
13
+ nn6bfJADmuIDiMSOzedOrL+xMB0GA1UdEQQWMBSBEmRqYmVyZzk2QGdtYWlsLmNv
14
+ bTAdBgNVHRIEFjAUgRJkamJlcmc5NkBnbWFpbC5jb20wDQYJKoZIhvcNAQEFBQAD
15
+ ggEBAHmNOCWoDVD75zHFueY0viwGDVP1BNGFC+yXcb7u2GlK+nEMCORqzURbYPf7
16
+ tL+/hzmePIRz7i30UM//64GI1NLv9jl7nIwjhPpXpf7/lu2I9hOTsvwSumb5UiKC
17
+ /sqBxI3sfj9pr79Wpv4MuikX1XPik7Ncb7NPsJPw06Lvyc3Hkg5X2XpPtLtS+Gr2
18
+ wKJnmzb5rIPS1cmsqv0M9LPWflzfwoZ/SpnmhagP+g05p8bRNKjZSA2iImM/GyYZ
19
+ EJYzxdPOrx2n6NYR3Hk+vHP0U7UBSveI6+qx+ndQYaeyCn+GRX2PKS9h66YF/Q1V
20
+ tGSHgAmcLlkdGgan182qsE/4kKM=
21
+ -----END CERTIFICATE-----
@@ -0,0 +1 @@
1
+ require_relative 'win32/file'
@@ -1,516 +1,516 @@
1
- require_relative 'file/constants'
2
- require_relative 'file/structs'
3
- require_relative 'file/functions'
4
- require 'win32/file/stat'
5
-
6
- class File
7
- include Windows::File::Constants
8
- include Windows::File::Structs
9
- extend Windows::File::Functions
10
-
11
- # The version of the win32-file library
12
- WIN32_FILE_VERSION = '0.7.2'
13
-
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
30
-
31
- ## Path methods
32
-
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
- # except that it does not strip the drive letter on a root path.
41
- #
42
- # Unlike MRI, this version will convert all forward slashes to
43
- # backslashes automatically.
44
- #
45
- # Examples:
46
- #
47
- # File.basename("C:\\foo\\bar.txt") -> "bar.txt"
48
- # File.basename("C:\\foo\\bar.txt", ".txt") -> "bar"
49
- # File.basename("\\\\foo\\bar") -> "\\\\foo\\bar"
50
- #
51
- def self.basename(file, suffix = nil)
52
- file = string_check(file)
53
- suffix = string_check(suffix) if suffix
54
-
55
- return file if file.empty? # Return an empty path as-is.
56
-
57
- encoding = file.encoding
58
- wfile = file.wincode
59
-
60
- # Return a root path as-is.
61
- if PathIsRootW(wfile)
62
- return file.tr(File::SEPARATOR, File::ALT_SEPARATOR)
63
- end
64
-
65
- ptr = FFI::MemoryPointer.from_string(wfile)
66
-
67
- PathStripPathW(ptr) # Gives us the basename
68
-
69
- if suffix
70
- if suffix == '.*'
71
- PathRemoveExtensionW(ptr)
72
- else
73
- ext = PathFindExtensionW(ptr).read_string(suffix.length * 2).wstrip
74
-
75
- if ext == suffix
76
- PathRemoveExtensionW(ptr)
77
- end
78
- end
79
- end
80
-
81
- wfile = ptr.read_bytes(wfile.size * 2).split("\000\000").first.tr(0.chr, '')
82
- file = wfile.encode(encoding)[/^[^\0]*/]
83
- file.sub!(/\\+\z/, '') # Trim trailing slashes
84
-
85
- ptr.free
86
-
87
- file
88
- end
89
-
90
- # Returns all components of the filename given in +filename+ except the
91
- # last one.
92
- #
93
- # This was reimplemented because the current version does not handle UNC
94
- # paths properly, i.e. it should not return anything less than the root.
95
- # In all other respects it is identical to the current implementation.
96
- #
97
- # Also, this method will convert all forward slashes to backslashes.
98
- #
99
- # Examples:
100
- #
101
- # File.dirname("C:\\foo\\bar\\baz.txt") -> "C:\\foo\\bar"
102
- # File.dirname("\\\\foo\\bar") -> "\\\\foo\\bar"
103
- #
104
- def self.dirname(file)
105
- file = string_check(file)
106
-
107
- # Short circuit for empty paths
108
- return '.' if file.empty?
109
-
110
- # Store original encoding, restore it later
111
- encoding = file.encoding
112
-
113
- # Convert to UTF-16LE
114
- wfile = file.wincode
115
-
116
- # Return a root path as-is.
117
- if PathIsRootW(wfile)
118
- return file.tr(File::SEPARATOR, File::ALT_SEPARATOR)
119
- end
120
-
121
- ptr = FFI::MemoryPointer.from_string(wfile)
122
-
123
- # Remove trailing slashes if present
124
- while result = PathRemoveBackslashW(ptr)
125
- break unless result.empty?
126
- end
127
-
128
- # Remove trailing file name if present
129
- unless PathRemoveFileSpecW(ptr)
130
- raise SystemCallError.new("PathRemoveFileSpec", FFI.errno)
131
- end
132
-
133
- wfile = ptr.read_bytes(wfile.size * 2).split("\000\000").first
134
-
135
- # Empty paths, short relative paths
136
- if wfile.nil? or wfile.empty?
137
- return '.'
138
- end
139
-
140
- # Return to original encoding
141
- file = wfile.tr(0.chr, '').encode(encoding)
142
-
143
- ptr.free
144
-
145
- file
146
- end
147
-
148
- # Join path string components together into a single string.
149
- #
150
- # This method was reimplemented so that it automatically converts
151
- # forward slashes to backslashes. It is otherwise identical to
152
- # the core File.join method.
153
- #
154
- # Examples:
155
- #
156
- # File.join("C:", "foo", "bar") # => C:\foo\bar
157
- # File.join("foo", "bar") # => foo\bar
158
- #
159
- def self.join(*args)
160
- return join_orig(*args).tr("/", "\\")
161
- end
162
-
163
- # Splits the given string into a directory and a file component and
164
- # returns them in a two element array. This was reimplemented because
165
- # the current version does not handle UNC paths properly.
166
- #
167
- def self.split(file)
168
- file = string_check(file)
169
- array = []
170
-
171
- if file.empty? || PathIsRootW(file.wincode)
172
- array.push(file, '')
173
- else
174
- array.push(File.dirname(file), File.basename(file))
175
- end
176
-
177
- array
178
- end
179
-
180
- # Returns +path+ in long format. For example, if 'SOMEFI~1.TXT'
181
- # was the argument provided, and the short representation for
182
- # 'somefile.txt', then this method would return 'somefile.txt'.
183
- #
184
- # Note that certain file system optimizations may prevent this method
185
- # from working as expected. In that case, you will get back the file
186
- # name in 8.3 format.
187
- #
188
- def self.long_path(file)
189
- buffer = FFI::Buffer.new(:wint_t, 1024, true)
190
- wfile = string_check(file).wincode
191
-
192
- length = GetLongPathNameW(wfile, buffer, buffer.size)
193
-
194
- if length == 0 || length > buffer.size / 2
195
- raise SystemCallError.new('GetLongPathName', FFI.errno)
196
- end
197
-
198
- buffer.read_bytes(length * 2).wstrip
199
- end
200
-
201
- # Returns +path+ in 8.3 format. For example, 'c:\documentation.doc'
202
- # would be returned as 'c:\docume~1.doc'.
203
- #
204
- def self.short_path(file)
205
- buffer = FFI::Buffer.new(:wint_t, 1024, true)
206
- wfile = string_check(file).wincode
207
-
208
- length = GetShortPathNameW(wfile, buffer, buffer.size)
209
-
210
- if length == 0 || length > buffer.size / 2
211
- raise SystemCallError.new('GetShortPathName', FFI.errno)
212
- end
213
-
214
- buffer.read_bytes(length * 2).wstrip
215
- end
216
-
217
- # Creates a symbolic link called +new_name+ for the file or directory
218
- # +old_name+.
219
- #
220
- # This method requires Windows Vista or later to work. Otherwise, it
221
- # returns nil as per MRI.
222
- #
223
- def self.symlink(target, link)
224
- target = string_check(target)
225
- link = string_check(link)
226
-
227
- flags = File.directory?(target) ? 1 : 0
228
-
229
- wlink = link.wincode
230
- wtarget = target.wincode
231
-
232
- unless CreateSymbolicLinkW(wlink, wtarget, flags)
233
- raise SystemCallError.new('CreateSymbolicLink', FFI.errno)
234
- end
235
-
236
- 0 # Comply with spec
237
- end
238
-
239
- # Returns whether or not +file+ is a symlink.
240
- #
241
- def self.symlink?(file)
242
- return false unless File.exist?(file)
243
-
244
- bool = false
245
- wfile = string_check(file).wincode
246
-
247
- attrib = GetFileAttributesW(wfile)
248
-
249
- if attrib == INVALID_FILE_ATTRIBUTES
250
- raise SystemCallError.new('GetFileAttributes', FFI.errno)
251
- end
252
-
253
- if attrib & FILE_ATTRIBUTE_REPARSE_POINT > 0
254
- begin
255
- find_data = WIN32_FIND_DATA.new
256
- handle = FindFirstFileW(wfile, find_data)
257
-
258
- if handle == INVALID_HANDLE_VALUE
259
- raise SystemCallError.new('FindFirstFile', FFI.errno)
260
- end
261
-
262
- if find_data[:dwReserved0] == IO_REPARSE_TAG_SYMLINK
263
- bool = true
264
- end
265
- ensure
266
- CloseHandle(handle)
267
- end
268
- end
269
-
270
- bool
271
- end
272
-
273
- # Converts path to a full file path, with all symlinks resolved and relative
274
- # paths made absolute. If a second parameter if present, it is used as the
275
- # base for resolving leading relative path segments.
276
- #
277
- # Unlike File.realpath, an error is not raised if the final path created
278
- # using a relative path argument doesn't exist.
279
- #--
280
- # On Windows we only modify the realpath method if the file is a symlink.
281
- #
282
- def self.realdirpath(file, relative_to = nil)
283
- file = string_check(file)
284
-
285
- if symlink?(file)
286
- if relative_to
287
- File.join(relative_to, File.basename(readlink(file)))
288
- else
289
- readlink(file)
290
- end
291
- else
292
- realdirpath_orig(file, relative_to)
293
- end
294
- end
295
-
296
- # Converts path to a full file path, with all symlinks resolved and relative
297
- # paths made absolute. If a second parameter if present, it is used as the
298
- # base for resolving leading relative path segments.
299
- #--
300
- # On Windows we only modify the realpath method if the file is a symlink.
301
- #
302
- def self.realpath(file, relative_to = nil)
303
- file = string_check(file)
304
- relative_to = string_check(relative_to) if relative_to
305
-
306
- if symlink?(file)
307
- if relative_to
308
- result = File.join(relative_to, File.basename(readlink(file)))
309
- if File.exist?(result)
310
- result
311
- else
312
- raise SystemCallError.new(result, 2) # Errno::ENOENT
313
- end
314
- else
315
- readlink(file)
316
- end
317
- else
318
- realpath_orig(file, relative_to)
319
- end
320
- end
321
-
322
- # Returns the path of the of the symbolic link referred to by +file+.
323
- #
324
- # Unlike unixy platforms, this will raise an error if the link is stale.
325
- #
326
- def self.readlink(file)
327
- file = string_check(file)
328
-
329
- if exist?(file) && !symlink?(file)
330
- raise SystemCallError.new(22) # EINVAL, match the spec
331
- end
332
-
333
- wfile = file.wincode
334
- path = 0.chr * 1024
335
-
336
- if File.directory?(file)
337
- flags = FILE_FLAG_BACKUP_SEMANTICS
338
- else
339
- flags = FILE_ATTRIBUTE_NORMAL
340
- end
341
-
342
- begin
343
- handle = CreateFileW(
344
- wfile,
345
- GENERIC_READ,
346
- FILE_SHARE_READ,
347
- nil,
348
- OPEN_EXISTING,
349
- flags,
350
- 0
351
- )
352
-
353
- if handle == INVALID_HANDLE_VALUE
354
- raise SystemCallError.new('CreateFile', FFI.errno)
355
- end
356
-
357
- if GetFinalPathNameByHandleW(handle, path, path.size/2, 0) == 0
358
- raise SystemCallError.new('GetFinalPathNameByHandle', FFI.errno)
359
- end
360
- ensure
361
- CloseHandle(handle)
362
- end
363
-
364
- path.wstrip[4..-1] # Remove leading backslashes + question mark
365
- end
366
-
367
- ## STAT METHODS
368
-
369
- # Returns the filesystem's block size.
370
- #
371
- def self.blksize(file)
372
- File::Stat.new(file).blksize
373
- end
374
-
375
- # Returns whether or not the file is a block device. For MS Windows a
376
- # block device is a removable drive, cdrom or ramdisk.
377
- #
378
- def self.blockdev?(file)
379
- return false unless File.exist?(file)
380
- File::Stat.new(file).blockdev?
381
- end
382
-
383
- # Returns whether or not the file is a character device.
384
- #
385
- def self.chardev?(file)
386
- return false unless File.exist?(file)
387
- File::Stat.new(file).chardev?
388
- end
389
-
390
- # Returns whether or not the file is a directory.
391
- #
392
- def self.directory?(file)
393
- return false unless File.exist?(file)
394
- File::Stat.new(file).directory?
395
- end
396
-
397
- # Returns whether or not the file is executable.
398
- #
399
- def self.executable?(file)
400
- return false unless File.exist?(file)
401
- File::Stat.new(file).executable?
402
- end
403
-
404
- # Returns whether or not the file is a regular file.
405
- #
406
- def self.file?(file)
407
- return false unless File.exist?(file)
408
- File::Stat.new(file).file?
409
- end
410
-
411
- # Identifies the type of file. The return string is one of 'file',
412
- # 'directory', 'characterSpecial', 'socket' or 'unknown'.
413
- #
414
- def self.ftype(file)
415
- File::Stat.new(file).ftype
416
- end
417
-
418
- # Returns true if the process owner's ID is the same as one of the file's groups.
419
- #
420
- def self.grpowned?(file)
421
- return false unless File.exist?(file)
422
- File::Stat.new(file).grpowned?
423
- end
424
-
425
- # Returns whether or not the current process owner is the owner of the file.
426
- #
427
- def self.owned?(file)
428
- return false unless File.exist?(file)
429
- File::Stat.new(file).owned?
430
- end
431
-
432
- # Returns whether or not the file is a pipe.
433
- #
434
- def self.pipe?(file)
435
- return false unless File.exist?(file)
436
- File::Stat.new(file).pipe?
437
- end
438
-
439
- # Returns whether or not the file is readable by the process owner.
440
- #
441
- def self.readable?(file)
442
- return false unless File.exist?(file)
443
- File::Stat.new(file).readable?
444
- end
445
-
446
- # Synonym for File.readable?
447
- #
448
- def self.readable_real?(file)
449
- return false unless File.exist?(file)
450
- File::Stat.new(file).readable_real?
451
- end
452
-
453
- # Returns a File::Stat object as defined in the win32-file-stat library.
454
- #
455
- def self.stat(file)
456
- File::Stat.new(file)
457
- end
458
-
459
- # Returns whether or not the file is readable by others. Note that this
460
- # merely returns true or false, not permission bits (or nil).
461
- #
462
- def self.world_readable?(file)
463
- return false unless File.exist?(file)
464
- File::Stat.new(file).world_readable?
465
- end
466
-
467
- # Returns whether or not the file is writable by others. Note that this
468
- # merely returns true or false, not permission bits (or nil).
469
- #
470
- def self.world_writable?(file)
471
- return false unless File.exist?(file)
472
- File::Stat.new(file).world_writable?
473
- end
474
-
475
- # Returns whether or not the file is writable by the current process owner.
476
- #
477
- def self.writable?(file)
478
- return false unless File.exist?(file)
479
- File::Stat.new(file).writable?
480
- end
481
-
482
- # Synonym for File.writable?
483
- #
484
- def self.writable_real?(file)
485
- return false unless File.exist?(file)
486
- File::Stat.new(file).writable_real?
487
- end
488
-
489
- # Singleton aliases
490
- class << self
491
- alias lstat stat
492
- alias executable_real? executable?
493
- alias socket? pipe?
494
- end
495
-
496
- ## Instance Methods
497
-
498
- # Same as MRI, except it returns a stat object using the win32-file-stat gem.
499
- #
500
- def stat
501
- File::Stat.new(self.path)
502
- end
503
-
504
- # Private singleton methods
505
- class << self
506
- private
507
-
508
- # Simulate Ruby's string checking
509
- def string_check(arg)
510
- return arg if arg.is_a?(String)
511
- return arg.send(:to_str) if arg.respond_to?(:to_str, true) # MRI allows private to_str
512
- return arg.to_path if arg.respond_to?(:to_path)
513
- raise TypeError
514
- end
515
- end
516
- end
1
+ require_relative 'file/constants'
2
+ require_relative 'file/structs'
3
+ require_relative 'file/functions'
4
+ require 'win32/file/stat'
5
+
6
+ class File
7
+ include Windows::File::Constants
8
+ include Windows::File::Structs
9
+ extend Windows::File::Functions
10
+
11
+ # The version of the win32-file library
12
+ WIN32_FILE_VERSION = '0.7.3'
13
+
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
30
+
31
+ ## Path methods
32
+
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
+ # except that it does not strip the drive letter on a root path.
41
+ #
42
+ # Unlike MRI, this version will convert all forward slashes to
43
+ # backslashes automatically.
44
+ #
45
+ # Examples:
46
+ #
47
+ # File.basename("C:\\foo\\bar.txt") -> "bar.txt"
48
+ # File.basename("C:\\foo\\bar.txt", ".txt") -> "bar"
49
+ # File.basename("\\\\foo\\bar") -> "\\\\foo\\bar"
50
+ #
51
+ def self.basename(file, suffix = nil)
52
+ file = string_check(file)
53
+ suffix = string_check(suffix) if suffix
54
+
55
+ return file if file.empty? # Return an empty path as-is.
56
+
57
+ encoding = file.encoding
58
+ wfile = file.wincode
59
+
60
+ # Return a root path as-is.
61
+ if PathIsRootW(wfile)
62
+ return file.tr(File::SEPARATOR, File::ALT_SEPARATOR)
63
+ end
64
+
65
+ ptr = FFI::MemoryPointer.from_string(wfile)
66
+
67
+ PathStripPathW(ptr) # Gives us the basename
68
+
69
+ if suffix
70
+ if suffix == '.*'
71
+ PathRemoveExtensionW(ptr)
72
+ else
73
+ ext = PathFindExtensionW(ptr).read_string(suffix.length * 2).wstrip
74
+
75
+ if ext == suffix
76
+ PathRemoveExtensionW(ptr)
77
+ end
78
+ end
79
+ end
80
+
81
+ wfile = ptr.read_bytes(wfile.size * 2).split("\000\000").first.tr(0.chr, '')
82
+ file = wfile.encode(encoding)[/^[^\0]*/]
83
+ file.sub!(/\\+\z/, '') # Trim trailing slashes
84
+
85
+ ptr.free
86
+
87
+ file
88
+ end
89
+
90
+ # Returns all components of the filename given in +filename+ except the
91
+ # last one.
92
+ #
93
+ # This was reimplemented because the current version does not handle UNC
94
+ # paths properly, i.e. it should not return anything less than the root.
95
+ # In all other respects it is identical to the current implementation.
96
+ #
97
+ # Also, this method will convert all forward slashes to backslashes.
98
+ #
99
+ # Examples:
100
+ #
101
+ # File.dirname("C:\\foo\\bar\\baz.txt") -> "C:\\foo\\bar"
102
+ # File.dirname("\\\\foo\\bar") -> "\\\\foo\\bar"
103
+ #
104
+ def self.dirname(file)
105
+ file = string_check(file)
106
+
107
+ # Short circuit for empty paths
108
+ return '.' if file.empty?
109
+
110
+ # Store original encoding, restore it later
111
+ encoding = file.encoding
112
+
113
+ # Convert to UTF-16LE
114
+ wfile = file.wincode
115
+
116
+ # Return a root path as-is.
117
+ if PathIsRootW(wfile)
118
+ return file.tr(File::SEPARATOR, File::ALT_SEPARATOR)
119
+ end
120
+
121
+ ptr = FFI::MemoryPointer.from_string(wfile)
122
+
123
+ # Remove trailing slashes if present
124
+ while result = PathRemoveBackslashW(ptr)
125
+ break unless result.empty?
126
+ end
127
+
128
+ # Remove trailing file name if present
129
+ unless PathRemoveFileSpecW(ptr)
130
+ raise SystemCallError.new("PathRemoveFileSpec", FFI.errno)
131
+ end
132
+
133
+ wfile = ptr.read_bytes(wfile.size * 2).split("\000\000").first
134
+
135
+ # Empty paths, short relative paths
136
+ if wfile.nil? or wfile.empty?
137
+ return '.'
138
+ end
139
+
140
+ # Return to original encoding
141
+ file = wfile.tr(0.chr, '').encode(encoding)
142
+
143
+ ptr.free
144
+
145
+ file
146
+ end
147
+
148
+ # Join path string components together into a single string.
149
+ #
150
+ # This method was reimplemented so that it automatically converts
151
+ # forward slashes to backslashes. It is otherwise identical to
152
+ # the core File.join method.
153
+ #
154
+ # Examples:
155
+ #
156
+ # File.join("C:", "foo", "bar") # => C:\foo\bar
157
+ # File.join("foo", "bar") # => foo\bar
158
+ #
159
+ def self.join(*args)
160
+ return join_orig(*args).tr("/", "\\")
161
+ end
162
+
163
+ # Splits the given string into a directory and a file component and
164
+ # returns them in a two element array. This was reimplemented because
165
+ # the current version does not handle UNC paths properly.
166
+ #
167
+ def self.split(file)
168
+ file = string_check(file)
169
+ array = []
170
+
171
+ if file.empty? || PathIsRootW(file.wincode)
172
+ array.push(file, '')
173
+ else
174
+ array.push(File.dirname(file), File.basename(file))
175
+ end
176
+
177
+ array
178
+ end
179
+
180
+ # Returns +path+ in long format. For example, if 'SOMEFI~1.TXT'
181
+ # was the argument provided, and the short representation for
182
+ # 'somefile.txt', then this method would return 'somefile.txt'.
183
+ #
184
+ # Note that certain file system optimizations may prevent this method
185
+ # from working as expected. In that case, you will get back the file
186
+ # name in 8.3 format.
187
+ #
188
+ def self.long_path(file)
189
+ buffer = FFI::Buffer.new(:wint_t, 1024, true)
190
+ wfile = string_check(file).wincode
191
+
192
+ length = GetLongPathNameW(wfile, buffer, buffer.size)
193
+
194
+ if length == 0 || length > buffer.size / 2
195
+ raise SystemCallError.new('GetLongPathName', FFI.errno)
196
+ end
197
+
198
+ buffer.read_bytes(length * 2).wstrip
199
+ end
200
+
201
+ # Returns +path+ in 8.3 format. For example, 'c:\documentation.doc'
202
+ # would be returned as 'c:\docume~1.doc'.
203
+ #
204
+ def self.short_path(file)
205
+ buffer = FFI::Buffer.new(:wint_t, 1024, true)
206
+ wfile = string_check(file).wincode
207
+
208
+ length = GetShortPathNameW(wfile, buffer, buffer.size)
209
+
210
+ if length == 0 || length > buffer.size / 2
211
+ raise SystemCallError.new('GetShortPathName', FFI.errno)
212
+ end
213
+
214
+ buffer.read_bytes(length * 2).wstrip
215
+ end
216
+
217
+ # Creates a symbolic link called +new_name+ for the file or directory
218
+ # +old_name+.
219
+ #
220
+ # This method requires Windows Vista or later to work. Otherwise, it
221
+ # returns nil as per MRI.
222
+ #
223
+ def self.symlink(target, link)
224
+ target = string_check(target)
225
+ link = string_check(link)
226
+
227
+ flags = File.directory?(target) ? 1 : 0
228
+
229
+ wlink = link.wincode
230
+ wtarget = target.wincode
231
+
232
+ unless CreateSymbolicLinkW(wlink, wtarget, flags)
233
+ raise SystemCallError.new('CreateSymbolicLink', FFI.errno)
234
+ end
235
+
236
+ 0 # Comply with spec
237
+ end
238
+
239
+ # Returns whether or not +file+ is a symlink.
240
+ #
241
+ def self.symlink?(file)
242
+ return false unless File.exist?(file)
243
+
244
+ bool = false
245
+ wfile = string_check(file).wincode
246
+
247
+ attrib = GetFileAttributesW(wfile)
248
+
249
+ if attrib == INVALID_FILE_ATTRIBUTES
250
+ raise SystemCallError.new('GetFileAttributes', FFI.errno)
251
+ end
252
+
253
+ if attrib & FILE_ATTRIBUTE_REPARSE_POINT > 0
254
+ begin
255
+ find_data = WIN32_FIND_DATA.new
256
+ handle = FindFirstFileW(wfile, find_data)
257
+
258
+ if handle == INVALID_HANDLE_VALUE
259
+ raise SystemCallError.new('FindFirstFile', FFI.errno)
260
+ end
261
+
262
+ if find_data[:dwReserved0] == IO_REPARSE_TAG_SYMLINK
263
+ bool = true
264
+ end
265
+ ensure
266
+ CloseHandle(handle)
267
+ end
268
+ end
269
+
270
+ bool
271
+ end
272
+
273
+ # Converts path to a full file path, with all symlinks resolved and relative
274
+ # paths made absolute. If a second parameter if present, it is used as the
275
+ # base for resolving leading relative path segments.
276
+ #
277
+ # Unlike File.realpath, an error is not raised if the final path created
278
+ # using a relative path argument doesn't exist.
279
+ #--
280
+ # On Windows we only modify the realpath method if the file is a symlink.
281
+ #
282
+ def self.realdirpath(file, relative_to = nil)
283
+ file = string_check(file)
284
+
285
+ if symlink?(file)
286
+ if relative_to
287
+ File.join(relative_to, File.basename(readlink(file)))
288
+ else
289
+ readlink(file)
290
+ end
291
+ else
292
+ realdirpath_orig(file, relative_to)
293
+ end
294
+ end
295
+
296
+ # Converts path to a full file path, with all symlinks resolved and relative
297
+ # paths made absolute. If a second parameter if present, it is used as the
298
+ # base for resolving leading relative path segments.
299
+ #--
300
+ # On Windows we only modify the realpath method if the file is a symlink.
301
+ #
302
+ def self.realpath(file, relative_to = nil)
303
+ file = string_check(file)
304
+ relative_to = string_check(relative_to) if relative_to
305
+
306
+ if symlink?(file)
307
+ if relative_to
308
+ result = File.join(relative_to, File.basename(readlink(file)))
309
+ if File.exist?(result)
310
+ result
311
+ else
312
+ raise SystemCallError.new(result, 2) # Errno::ENOENT
313
+ end
314
+ else
315
+ readlink(file)
316
+ end
317
+ else
318
+ realpath_orig(file, relative_to)
319
+ end
320
+ end
321
+
322
+ # Returns the path of the of the symbolic link referred to by +file+.
323
+ #
324
+ # Unlike unixy platforms, this will raise an error if the link is stale.
325
+ #
326
+ def self.readlink(file)
327
+ file = string_check(file)
328
+
329
+ if exist?(file) && !symlink?(file)
330
+ raise SystemCallError.new(22) # EINVAL, match the spec
331
+ end
332
+
333
+ wfile = file.wincode
334
+ path = 0.chr * 1024
335
+
336
+ if File.directory?(file)
337
+ flags = FILE_FLAG_BACKUP_SEMANTICS
338
+ else
339
+ flags = FILE_ATTRIBUTE_NORMAL
340
+ end
341
+
342
+ begin
343
+ handle = CreateFileW(
344
+ wfile,
345
+ GENERIC_READ,
346
+ FILE_SHARE_READ,
347
+ nil,
348
+ OPEN_EXISTING,
349
+ flags,
350
+ 0
351
+ )
352
+
353
+ if handle == INVALID_HANDLE_VALUE
354
+ raise SystemCallError.new('CreateFile', FFI.errno)
355
+ end
356
+
357
+ if GetFinalPathNameByHandleW(handle, path, path.size/2, 0) == 0
358
+ raise SystemCallError.new('GetFinalPathNameByHandle', FFI.errno)
359
+ end
360
+ ensure
361
+ CloseHandle(handle)
362
+ end
363
+
364
+ path.wstrip[4..-1] # Remove leading backslashes + question mark
365
+ end
366
+
367
+ ## STAT METHODS
368
+
369
+ # Returns the filesystem's block size.
370
+ #
371
+ def self.blksize(file)
372
+ File::Stat.new(file).blksize
373
+ end
374
+
375
+ # Returns whether or not the file is a block device. For MS Windows a
376
+ # block device is a removable drive, cdrom or ramdisk.
377
+ #
378
+ def self.blockdev?(file)
379
+ return false unless File.exist?(file)
380
+ File::Stat.new(file).blockdev?
381
+ end
382
+
383
+ # Returns whether or not the file is a character device.
384
+ #
385
+ def self.chardev?(file)
386
+ return false unless File.exist?(file)
387
+ File::Stat.new(file).chardev?
388
+ end
389
+
390
+ # Returns whether or not the file is a directory.
391
+ #
392
+ def self.directory?(file)
393
+ return false unless File.exist?(file)
394
+ File::Stat.new(file).directory?
395
+ end
396
+
397
+ # Returns whether or not the file is executable.
398
+ #
399
+ def self.executable?(file)
400
+ return false unless File.exist?(file)
401
+ File::Stat.new(file).executable?
402
+ end
403
+
404
+ # Returns whether or not the file is a regular file.
405
+ #
406
+ def self.file?(file)
407
+ return false unless File.exist?(file)
408
+ File::Stat.new(file).file?
409
+ end
410
+
411
+ # Identifies the type of file. The return string is one of 'file',
412
+ # 'directory', 'characterSpecial', 'socket' or 'unknown'.
413
+ #
414
+ def self.ftype(file)
415
+ File::Stat.new(file).ftype
416
+ end
417
+
418
+ # Returns true if the process owner's ID is the same as one of the file's groups.
419
+ #
420
+ def self.grpowned?(file)
421
+ return false unless File.exist?(file)
422
+ File::Stat.new(file).grpowned?
423
+ end
424
+
425
+ # Returns whether or not the current process owner is the owner of the file.
426
+ #
427
+ def self.owned?(file)
428
+ return false unless File.exist?(file)
429
+ File::Stat.new(file).owned?
430
+ end
431
+
432
+ # Returns whether or not the file is a pipe.
433
+ #
434
+ def self.pipe?(file)
435
+ return false unless File.exist?(file)
436
+ File::Stat.new(file).pipe?
437
+ end
438
+
439
+ # Returns whether or not the file is readable by the process owner.
440
+ #
441
+ def self.readable?(file)
442
+ return false unless File.exist?(file)
443
+ File::Stat.new(file).readable?
444
+ end
445
+
446
+ # Synonym for File.readable?
447
+ #
448
+ def self.readable_real?(file)
449
+ return false unless File.exist?(file)
450
+ File::Stat.new(file).readable_real?
451
+ end
452
+
453
+ # Returns a File::Stat object as defined in the win32-file-stat library.
454
+ #
455
+ def self.stat(file)
456
+ File::Stat.new(file)
457
+ end
458
+
459
+ # Returns whether or not the file is readable by others. Note that this
460
+ # merely returns true or false, not permission bits (or nil).
461
+ #
462
+ def self.world_readable?(file)
463
+ return false unless File.exist?(file)
464
+ File::Stat.new(file).world_readable?
465
+ end
466
+
467
+ # Returns whether or not the file is writable by others. Note that this
468
+ # merely returns true or false, not permission bits (or nil).
469
+ #
470
+ def self.world_writable?(file)
471
+ return false unless File.exist?(file)
472
+ File::Stat.new(file).world_writable?
473
+ end
474
+
475
+ # Returns whether or not the file is writable by the current process owner.
476
+ #
477
+ def self.writable?(file)
478
+ return false unless File.exist?(file)
479
+ File::Stat.new(file).writable?
480
+ end
481
+
482
+ # Synonym for File.writable?
483
+ #
484
+ def self.writable_real?(file)
485
+ return false unless File.exist?(file)
486
+ File::Stat.new(file).writable_real?
487
+ end
488
+
489
+ # Singleton aliases
490
+ class << self
491
+ alias lstat stat
492
+ alias executable_real? executable?
493
+ alias socket? pipe?
494
+ end
495
+
496
+ ## Instance Methods
497
+
498
+ # Same as MRI, except it returns a stat object using the win32-file-stat gem.
499
+ #
500
+ def stat
501
+ File::Stat.new(self.path)
502
+ end
503
+
504
+ # Private singleton methods
505
+ class << self
506
+ private
507
+
508
+ # Simulate Ruby's string checking
509
+ def string_check(arg)
510
+ return arg if arg.is_a?(String)
511
+ return arg.send(:to_str) if arg.respond_to?(:to_str, true) # MRI allows private to_str
512
+ return arg.to_path if arg.respond_to?(:to_path)
513
+ raise TypeError
514
+ end
515
+ end
516
+ end