win32-file 0.7.2 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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