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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/CHANGES +225 -220
- data/MANIFEST +15 -13
- data/README +70 -70
- data/Rakefile +49 -52
- data/certs/djberg96_pub.pem +21 -0
- data/lib/win32-file.rb +1 -0
- data/lib/win32/file.rb +516 -516
- data/lib/win32/file/constants.rb +27 -27
- data/lib/win32/file/functions.rb +49 -49
- data/lib/win32/file/structs.rb +24 -24
- data/test/test_win32_file_link.rb +141 -141
- data/test/test_win32_file_misc.rb +16 -16
- data/test/test_win32_file_path.rb +282 -282
- data/test/test_win32_file_stat.rb +284 -284
- data/win32-file.gemspec +32 -32
- metadata +41 -17
- metadata.gz.sig +1 -0
data/MANIFEST
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
-
* CHANGES
|
2
|
-
* MANIFEST
|
3
|
-
* Rakefile
|
4
|
-
* README
|
5
|
-
* win32-file.gemspec
|
6
|
-
*
|
7
|
-
* lib/win32
|
8
|
-
* lib/win32/file
|
9
|
-
* lib/win32/file/
|
10
|
-
*
|
11
|
-
*
|
12
|
-
* test/
|
13
|
-
* test/
|
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-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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-----
|
data/lib/win32-file.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'win32/file'
|
data/lib/win32/file.rb
CHANGED
@@ -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.
|
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
|