win32-dir 0.3.7 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES +6 -0
- data/MANIFEST +8 -8
- data/README +4 -9
- data/Rakefile +6 -1
- data/examples/dir_example.rb +22 -22
- data/lib/win32/dir.rb +264 -176
- data/lib/win32/dir/constants.rb +18 -0
- data/lib/win32/dir/functions.rb +35 -0
- data/lib/win32/dir/structs.rb +29 -0
- data/test/test_win32_dir.rb +143 -137
- data/win32-dir.gemspec +4 -4
- metadata +62 -64
data/CHANGES
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
== 0.4.0 - 29-Jun-2012
|
2
|
+
* Conversion to FFI. Should work with JRuby now.
|
3
|
+
* If current versions of Dir::XXX constant values cannot be found
|
4
|
+
then default values are tried.
|
5
|
+
* Now requires Ruby 1.9 or later.
|
6
|
+
|
1
7
|
== 0.3.7 - 18-Jul-2010
|
2
8
|
* Modified Dir.glob and Dir[] to handle backslashes in path names.
|
3
9
|
* Added tests for the modified Dir.glob and Dir[] behavior.
|
data/MANIFEST
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
* README
|
2
|
-
* MANIFEST
|
3
|
-
* CHANGES
|
4
|
-
* Rakefile
|
5
|
-
* win32-dir.gemspec
|
6
|
-
* examples/dir_example.rb
|
7
|
-
* lib/win32/dir.rb
|
8
|
-
* test/test_win32_dir.rb
|
1
|
+
* README
|
2
|
+
* MANIFEST
|
3
|
+
* CHANGES
|
4
|
+
* Rakefile
|
5
|
+
* win32-dir.gemspec
|
6
|
+
* examples/dir_example.rb
|
7
|
+
* lib/win32/dir.rb
|
8
|
+
* test/test_win32_dir.rb
|
data/README
CHANGED
@@ -233,16 +233,11 @@ Dir::TEMPLATES
|
|
233
233
|
only instead of an actual path.
|
234
234
|
|
235
235
|
== Known Bugs
|
236
|
-
|
237
|
-
|
238
|
-
with regards to the character set.
|
239
|
-
|
240
|
-
Please log any other bug reports on the RubyForge project page at
|
241
|
-
http://www.rubyforge.net/projects/win32utils.
|
236
|
+
Please log any bug reports on the project page at
|
237
|
+
http://www.github.com/djberg96/win32-dir
|
242
238
|
|
243
239
|
== Future Plans
|
244
|
-
|
245
|
-
Other suggestions welcome.
|
240
|
+
Suggestions welcome.
|
246
241
|
|
247
242
|
== Acknowledgements
|
248
243
|
Shashank Date and Zach Dennis for the suggestion and supporting comments
|
@@ -258,7 +253,7 @@ Dir::TEMPLATES
|
|
258
253
|
Artistic 2.0
|
259
254
|
|
260
255
|
== Copyright
|
261
|
-
(C) 2003-
|
256
|
+
(C) 2003-2012 Daniel J. Berger, All Rights Reserved
|
262
257
|
|
263
258
|
== Warranty
|
264
259
|
This package is provided "as is" and without any express or
|
data/Rakefile
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
require 'rake'
|
2
|
+
require 'rake/clean'
|
2
3
|
require 'rake/testtask'
|
3
4
|
|
5
|
+
CLEAN.include('**/*.gem', '**/*.log')
|
6
|
+
|
4
7
|
namespace 'gem' do
|
5
8
|
desc "Create the win32-dir gem"
|
6
|
-
task :build do
|
9
|
+
task :build => [:clean] do
|
7
10
|
Dir["*.gem"].each{ |f| File.delete(f) }
|
8
11
|
spec = eval(IO.read('win32-dir.gemspec'))
|
9
12
|
Gem::Builder.new(spec).build
|
@@ -25,3 +28,5 @@ Rake::TestTask.new do |t|
|
|
25
28
|
t.warning = true
|
26
29
|
t.verbose = true
|
27
30
|
end
|
31
|
+
|
32
|
+
task :default => :test
|
data/examples/dir_example.rb
CHANGED
@@ -1,23 +1,23 @@
|
|
1
|
-
####################################################################
|
2
|
-
# dir_example.rb
|
3
|
-
#
|
4
|
-
# Generic test script for general futzing. Modify as you see fit.
|
5
|
-
# You can run this via the 'rake example' task.
|
6
|
-
####################################################################
|
7
|
-
require 'win32/dir'
|
8
|
-
|
9
|
-
puts "Admin Tools:\t\t" + Dir::ADMINTOOLS
|
10
|
-
puts "Common Admin Tools:\t" + Dir::COMMON_ADMINTOOLS
|
11
|
-
puts "App Data:\t\t" + Dir::APPDATA
|
12
|
-
puts "Common App Data:\t" + Dir::COMMON_APPDATA
|
13
|
-
puts "Common Documents:\t" + Dir::COMMON_DOCUMENTS
|
14
|
-
puts "Cookies:\t\t" + Dir::COOKIES
|
15
|
-
puts "History:\t\t" + Dir::HISTORY
|
16
|
-
puts "Internet Cache:\t\t" + Dir::INTERNET_CACHE
|
17
|
-
puts "Local App Data:\t\t" + Dir::LOCAL_APPDATA
|
18
|
-
puts "My Pictures:\t\t" + Dir::MYPICTURES
|
19
|
-
puts "Personal:\t\t" + Dir::PERSONAL
|
20
|
-
puts "Program Files:\t\t" + Dir::PROGRAM_FILES
|
21
|
-
puts "Program Files Common:\t" + Dir::PROGRAM_FILES_COMMON
|
22
|
-
puts "System:\t\t\t" + Dir::SYSTEM
|
1
|
+
####################################################################
|
2
|
+
# dir_example.rb
|
3
|
+
#
|
4
|
+
# Generic test script for general futzing. Modify as you see fit.
|
5
|
+
# You can run this via the 'rake example' task.
|
6
|
+
####################################################################
|
7
|
+
require 'win32/dir'
|
8
|
+
|
9
|
+
puts "Admin Tools:\t\t" + Dir::ADMINTOOLS
|
10
|
+
puts "Common Admin Tools:\t" + Dir::COMMON_ADMINTOOLS
|
11
|
+
puts "App Data:\t\t" + Dir::APPDATA
|
12
|
+
puts "Common App Data:\t" + Dir::COMMON_APPDATA
|
13
|
+
puts "Common Documents:\t" + Dir::COMMON_DOCUMENTS
|
14
|
+
puts "Cookies:\t\t" + Dir::COOKIES
|
15
|
+
puts "History:\t\t" + Dir::HISTORY
|
16
|
+
puts "Internet Cache:\t\t" + Dir::INTERNET_CACHE
|
17
|
+
puts "Local App Data:\t\t" + Dir::LOCAL_APPDATA
|
18
|
+
puts "My Pictures:\t\t" + Dir::MYPICTURES
|
19
|
+
puts "Personal:\t\t" + Dir::PERSONAL
|
20
|
+
puts "Program Files:\t\t" + Dir::PROGRAM_FILES
|
21
|
+
puts "Program Files Common:\t" + Dir::PROGRAM_FILES_COMMON
|
22
|
+
puts "System:\t\t\t" + Dir::SYSTEM
|
23
23
|
puts "Windows:\t\t" + Dir::WINDOWS
|
data/lib/win32/dir.rb
CHANGED
@@ -1,119 +1,188 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
require 'windows/error'
|
5
|
-
require 'windows/device_io'
|
6
|
-
require 'windows/unicode'
|
7
|
-
require 'windows/directory'
|
8
|
-
require 'windows/handle'
|
9
|
-
require 'windows/path'
|
10
|
-
require 'windows/limits'
|
11
|
-
require 'windows/system_info'
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'dir', 'constants')
|
2
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'dir', 'functions')
|
3
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'dir', 'structs')
|
12
4
|
|
13
5
|
class Dir
|
14
|
-
include
|
15
|
-
include
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
6
|
+
include Dir::Structs
|
7
|
+
include Dir::Constants
|
8
|
+
extend Dir::Functions
|
9
|
+
|
10
|
+
private_class_method(
|
11
|
+
:SHGetFolderPathW,
|
12
|
+
:SHGetFolderLocation,
|
13
|
+
:SHGetFileInfo,
|
14
|
+
:PathIsDirectoryEmptyW,
|
15
|
+
:CloseHandle,
|
16
|
+
:CreateDirectoryW,
|
17
|
+
:CreateFileW,
|
18
|
+
:DeviceIoControl,
|
19
|
+
:GetCurrentDirectoryW,
|
20
|
+
:GetFileAttributesW,
|
21
|
+
:GetLastError,
|
22
|
+
:GetShortPathNameW,
|
23
|
+
:GetLongPathNameW,
|
24
|
+
:GetFullPathNameW,
|
25
|
+
:RemoveDirectoryW
|
26
|
+
)
|
27
|
+
|
32
28
|
# The version of the win32-dir library.
|
33
|
-
VERSION = '0.
|
34
|
-
|
35
|
-
#
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
29
|
+
VERSION = '0.4.0'
|
30
|
+
|
31
|
+
# CSIDL constants
|
32
|
+
csidl = Hash[
|
33
|
+
'DESKTOP', 0x0000,
|
34
|
+
'INTERNET', 0x0001,
|
35
|
+
'PROGRAMS', 0x0002,
|
36
|
+
'CONTROLS', 0x0003,
|
37
|
+
'PRINTERS', 0x0004,
|
38
|
+
'PERSONAL', 0x0005,
|
39
|
+
'FAVORITES', 0x0006,
|
40
|
+
'STARTUP', 0x0007,
|
41
|
+
'RECENT', 0x0008,
|
42
|
+
'SENDTO', 0x0009,
|
43
|
+
'BITBUCKET', 0x000a,
|
44
|
+
'STARTMENU', 0x000b,
|
45
|
+
'MYDOCUMENTS', 0x000c,
|
46
|
+
'MYMUSIC', 0x000d,
|
47
|
+
'MYVIDEO', 0x000e,
|
48
|
+
'DESKTOPDIRECTORY', 0x0010,
|
49
|
+
'DRIVES', 0x0011,
|
50
|
+
'NETWORK', 0x0012,
|
51
|
+
'NETHOOD', 0x0013,
|
52
|
+
'FONTS', 0x0014,
|
53
|
+
'TEMPLATES', 0x0015,
|
54
|
+
'COMMON_STARTMENU', 0x0016,
|
55
|
+
'COMMON_PROGRAMS', 0X0017,
|
56
|
+
'COMMON_STARTUP', 0x0018,
|
57
|
+
'COMMON_FAVORITES', 0x001f,
|
58
|
+
'COMMON_DESKTOPDIRECTORY', 0x0019,
|
59
|
+
'APPDATA', 0x001a,
|
60
|
+
'PRINTHOOD', 0x001b,
|
61
|
+
'LOCAL_APPDATA', 0x001c,
|
62
|
+
'ALTSTARTUP', 0x001d,
|
63
|
+
'COMMON_ALTSTARTUP', 0x001e,
|
64
|
+
'INTERNET_CACHE', 0x0020,
|
65
|
+
'COOKIES', 0x0021,
|
66
|
+
'HISTORY', 0x0022,
|
67
|
+
'COMMON_APPDATA', 0x0023,
|
68
|
+
'WINDOWS', 0x0024,
|
69
|
+
'SYSTEM', 0x0025,
|
70
|
+
'PROGRAM_FILES', 0x0026,
|
71
|
+
'MYPICTURES', 0x0027,
|
72
|
+
'PROFILE', 0x0028,
|
73
|
+
'SYSTEMX86', 0x0029,
|
74
|
+
'PROGRAM_FILESX86', 0x002a,
|
75
|
+
'PROGRAM_FILES_COMMON', 0x002b,
|
76
|
+
'PROGRAM_FILES_COMMONX86', 0x002c,
|
77
|
+
'COMMON_TEMPLATES', 0x002d,
|
78
|
+
'COMMON_DOCUMENTS', 0x002e,
|
79
|
+
'CONNECTIONS', 0x0031,
|
80
|
+
'COMMON_MUSIC', 0x0035,
|
81
|
+
'COMMON_PICTURES', 0x0036,
|
82
|
+
'COMMON_VIDEO', 0x0037,
|
83
|
+
'RESOURCES', 0x0038,
|
84
|
+
'RESOURCES_LOCALIZED', 0x0039,
|
85
|
+
'COMMON_OEM_LINKS', 0x003a,
|
86
|
+
'CDBURN_AREA', 0x003b,
|
87
|
+
'COMMON_ADMINTOOLS', 0x002f,
|
88
|
+
'ADMINTOOLS', 0x0030
|
89
|
+
]
|
45
90
|
|
46
|
-
|
47
|
-
|
48
|
-
|
91
|
+
# Dynamically set each of the CSIDL constants
|
92
|
+
csidl.each{ |key, value|
|
93
|
+
buf = 0.chr * 1024
|
94
|
+
path = nil
|
95
|
+
buf.encode!('UTF-16LE')
|
49
96
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
97
|
+
if SHGetFolderPathW(0, value, 0, 0, buf) == 0 # Current path
|
98
|
+
path = buf.strip
|
99
|
+
elsif SHGetFolderPathW(0, value, 0, 1, buf) == 0 # Default path
|
100
|
+
path = buf.strip
|
101
|
+
else
|
102
|
+
ptr = FFI::MemoryPointer.new(:long)
|
103
|
+
info = SHFILEINFO.new
|
104
|
+
flags = SHGFI_DISPLAYNAME | SHGFI_PIDL
|
105
|
+
|
106
|
+
if SHGetFolderLocation(0, value, 0, 0, ptr) == 0
|
107
|
+
if SHGetFileInfo(ptr.read_long, 0, info, info.size, flags) != 0
|
108
|
+
path = info[:szDisplayName].to_s
|
109
|
+
end
|
55
110
|
end
|
56
111
|
end
|
57
112
|
|
58
|
-
Dir.const_set(
|
113
|
+
Dir.const_set(key, path) if path
|
59
114
|
}
|
60
115
|
|
61
116
|
# Set Dir::MYDOCUMENTS to the same as Dir::PERSONAL if undefined
|
62
117
|
unless defined? MYDOCUMENTS
|
63
|
-
# Same as Dir::PERSONAL
|
64
118
|
MYDOCUMENTS = PERSONAL
|
65
119
|
end
|
66
|
-
|
120
|
+
|
67
121
|
class << self
|
68
|
-
|
69
|
-
remove_method :pwd
|
70
|
-
alias :old_glob :glob
|
71
|
-
alias :old_ref :[]
|
72
|
-
remove_method :glob
|
73
|
-
remove_method :[]
|
74
|
-
end
|
122
|
+
alias old_glob glob
|
75
123
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
124
|
+
# Same as the standard MRI Dir.glob method except that it handles
|
125
|
+
# backslashes in path names.
|
126
|
+
#
|
127
|
+
def glob(glob_pattern, flags = 0, &block)
|
128
|
+
glob_pattern = glob_pattern.tr("\\", "/")
|
129
|
+
old_glob(glob_pattern, flags, &block)
|
130
|
+
end
|
83
131
|
|
84
|
-
|
85
|
-
# backslashes in path names.
|
86
|
-
#
|
87
|
-
def self.[](glob_pattern)
|
88
|
-
glob_pattern = glob_pattern.tr("\\", "/")
|
89
|
-
old_ref(glob_pattern)
|
90
|
-
end
|
91
|
-
|
92
|
-
# Returns the present working directory. Unlike MRI, this method always
|
93
|
-
# normalizes the path.
|
94
|
-
#
|
95
|
-
# Examples:
|
96
|
-
#
|
97
|
-
# Dir.chdir("C:/Progra~1")
|
98
|
-
# Dir.getwd # => C:\Program Files
|
99
|
-
#
|
100
|
-
# Dir.chdir("C:/PROGRAM FILES")
|
101
|
-
# Dir.getwd # => C:\Program Files
|
102
|
-
#
|
103
|
-
def self.getwd
|
104
|
-
path1 = 0.chr * MAXPATH
|
105
|
-
path2 = 0.chr * MAXPATH
|
106
|
-
path3 = 0.chr * MAXPATH
|
132
|
+
alias old_ref []
|
107
133
|
|
108
|
-
|
109
|
-
|
110
|
-
|
134
|
+
# Same as the standard MRI Dir[] method except that it handles
|
135
|
+
# backslashes in path names.
|
136
|
+
#
|
137
|
+
def [](glob_pattern)
|
138
|
+
glob_pattern = glob_pattern.tr("\\", "/")
|
139
|
+
old_ref(glob_pattern)
|
140
|
+
end
|
111
141
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
142
|
+
# JRuby normalizes the path by default.
|
143
|
+
unless RUBY_PLATFORM == 'java'
|
144
|
+
alias oldgetwd getwd
|
145
|
+
alias oldpwd pwd
|
146
|
+
|
147
|
+
# Returns the present working directory. Unlike MRI, this method always
|
148
|
+
# normalizes the path.
|
149
|
+
#
|
150
|
+
# Examples:
|
151
|
+
#
|
152
|
+
# Dir.chdir("C:/Progra~1")
|
153
|
+
# Dir.getwd # => C:\Program Files
|
154
|
+
#
|
155
|
+
# Dir.chdir("C:/PROGRAM FILES")
|
156
|
+
# Dir.getwd # => C:\Program Files
|
157
|
+
#
|
158
|
+
def getwd
|
159
|
+
path1 = 0.chr * 1024
|
160
|
+
path2 = 0.chr * 1024
|
161
|
+
path3 = 0.chr * 1024
|
162
|
+
|
163
|
+
path1.encode!('UTF-16LE')
|
164
|
+
|
165
|
+
if GetCurrentDirectoryW(path1.size, path1) == 0
|
166
|
+
raise SystemCallError, FFI.errno, "GetCurrentDirectoryW"
|
167
|
+
end
|
168
|
+
|
169
|
+
path2.encode!('UTF-16LE')
|
170
|
+
|
171
|
+
if GetShortPathNameW(path1, path2, path2.size) == 0
|
172
|
+
raise SystemCallError, FFi.errno, "GetShortPathNameW"
|
173
|
+
end
|
174
|
+
|
175
|
+
path3.encode!('UTF-16LE')
|
176
|
+
|
177
|
+
if GetLongPathNameW(path2, path3, path3.size) == 0
|
178
|
+
raise SystemCallError, FFI.errno, "GetLongPathNameW"
|
179
|
+
end
|
180
|
+
|
181
|
+
path3.strip.encode(Encoding.default_external)
|
182
|
+
end
|
183
|
+
|
184
|
+
alias :pwd :getwd
|
185
|
+
end
|
117
186
|
end
|
118
187
|
|
119
188
|
# Creates the symlink +to+, linked to the existing directory +from+. If the
|
@@ -123,121 +192,140 @@ class Dir
|
|
123
192
|
#
|
124
193
|
# Dir.mkdir('C:/from')
|
125
194
|
# Dir.create_junction('C:/to', 'C:/from')
|
126
|
-
#
|
195
|
+
#
|
127
196
|
def self.create_junction(to, from)
|
128
|
-
to = to.tr(File::SEPARATOR, File::ALT_SEPARATOR) # Normalize path
|
129
|
-
from = from.tr(File::SEPARATOR, File::ALT_SEPARATOR) # Normalize path
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
197
|
+
to = to.tr(File::SEPARATOR, File::ALT_SEPARATOR) + "\0" # Normalize path
|
198
|
+
from = from.tr(File::SEPARATOR, File::ALT_SEPARATOR) + "\0" # Normalize path
|
199
|
+
|
200
|
+
to.encode!('UTF-16LE')
|
201
|
+
from.encode!('UTF-16LE')
|
202
|
+
|
203
|
+
from_path = 0.chr * 1024
|
204
|
+
from_path.encode!('UTF-16LE')
|
205
|
+
|
206
|
+
length = GetFullPathNameW(from, from_path.size, from_path, nil)
|
136
207
|
|
137
208
|
if length == 0
|
138
|
-
raise
|
209
|
+
raise SystemCallError, FFI.errno, "GetFullPathNameW"
|
139
210
|
else
|
140
|
-
from_path
|
211
|
+
from_path.strip!
|
141
212
|
end
|
142
|
-
|
143
|
-
|
213
|
+
|
214
|
+
to_path = 0.chr * 1024
|
215
|
+
to.encode!('UTF-16LE')
|
216
|
+
to_path.encode!('UTF-16LE')
|
217
|
+
|
218
|
+
length = GetFullPathNameW(to, to_path.size, to_path, nil)
|
144
219
|
|
145
220
|
if length == 0
|
146
|
-
raise
|
221
|
+
raise SystemCallError, FFI.errno, "GetFullPathNameW"
|
147
222
|
else
|
148
|
-
to_path
|
223
|
+
to_path.strip!
|
149
224
|
end
|
150
225
|
|
151
226
|
# You can create a junction to a directory that already exists, so
|
152
227
|
# long as it's empty.
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
228
|
+
unless CreateDirectoryW(to_path, nil)
|
229
|
+
if FFI.errno != ERROR_ALREADY_EXISTS
|
230
|
+
raise SystemCallError, FFI.errno, "CreateDirectoryW"
|
231
|
+
end
|
157
232
|
end
|
158
|
-
|
159
|
-
handle = CreateFile(
|
160
|
-
to_path,
|
161
|
-
GENERIC_READ | GENERIC_WRITE,
|
162
|
-
0,
|
163
|
-
0,
|
164
|
-
OPEN_EXISTING,
|
165
|
-
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
|
166
|
-
0
|
167
|
-
)
|
168
|
-
|
169
|
-
if handle == INVALID_HANDLE_VALUE
|
170
|
-
raise StandardError, 'CreateFile() failed: ' + get_last_error
|
171
|
-
end
|
172
|
-
|
173
|
-
buf_target = buf_target.split(0.chr).first
|
174
|
-
buf_target = "\\??\\" << from_path
|
175
|
-
wide_string = multi_to_wide(buf_target)[0..-3]
|
176
|
-
|
177
|
-
# REPARSE_JDATA_BUFFER
|
178
|
-
rdb = [
|
179
|
-
"0xA0000003L".hex, # ReparseTag (IO_REPARSE_TAG_MOUNT_POINT)
|
180
|
-
wide_string.size + 12, # ReparseDataLength
|
181
|
-
0, # Reserved
|
182
|
-
0, # SubstituteNameOffset
|
183
|
-
wide_string.size, # SubstituteNameLength
|
184
|
-
wide_string.size + 2, # PrintNameOffset
|
185
|
-
0, # PrintNameLength
|
186
|
-
wide_string # PathBuffer
|
187
|
-
].pack('LSSSSSSa' + (wide_string.size + 4).to_s)
|
188
|
-
|
189
|
-
bytes = [0].pack('L')
|
190
233
|
|
191
234
|
begin
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
rdb.size,
|
235
|
+
# Generic read & write + open existing + reparse point & backup semantics
|
236
|
+
handle = CreateFileW(
|
237
|
+
to_path,
|
238
|
+
GENERIC_READ | GENERIC_WRITE,
|
197
239
|
0,
|
198
|
-
|
199
|
-
|
240
|
+
nil,
|
241
|
+
OPEN_EXISTING,
|
242
|
+
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
|
200
243
|
0
|
201
244
|
)
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
245
|
+
|
246
|
+
if handle == INVALID_HANDLE_VALUE
|
247
|
+
raise SystemCallError, FFI.errno, "CreateFileW"
|
248
|
+
end
|
249
|
+
|
250
|
+
target = "\\??\\".encode('UTF-16LE') + from_path
|
251
|
+
|
252
|
+
rdb = REPARSE_JDATA_BUFFER.new
|
253
|
+
rdb[:ReparseTag] = 2684354563 # IO_REPARSE_TAG_MOUNT_POINT
|
254
|
+
rdb[:ReparseDataLength] = target.bytesize + 12
|
255
|
+
rdb[:Reserved] = 0
|
256
|
+
rdb[:SubstituteNameOffset] = 0
|
257
|
+
rdb[:SubstituteNameLength] = target.bytesize
|
258
|
+
rdb[:PrintNameOffset] = target.bytesize + 2
|
259
|
+
rdb[:PrintNameLength] = 0
|
260
|
+
rdb[:PathBuffer] = target
|
261
|
+
|
262
|
+
bytes = FFI::MemoryPointer.new(:ulong)
|
263
|
+
|
264
|
+
begin
|
265
|
+
bool = DeviceIoControl(
|
266
|
+
handle,
|
267
|
+
CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, 0),
|
268
|
+
rdb,
|
269
|
+
rdb[:ReparseDataLength] + 8,
|
270
|
+
nil,
|
271
|
+
0,
|
272
|
+
bytes,
|
273
|
+
nil
|
274
|
+
)
|
275
|
+
|
276
|
+
error = FFI.errno
|
277
|
+
|
278
|
+
unless bool
|
279
|
+
RemoveDirectoryW(to_path)
|
280
|
+
raise SystemCallError, error, "DeviceIoControl"
|
281
|
+
end
|
282
|
+
ensure
|
283
|
+
CloseHandle(handle)
|
207
284
|
end
|
208
|
-
ensure
|
209
|
-
CloseHandle(handle)
|
210
285
|
end
|
211
|
-
|
212
|
-
self
|
286
|
+
|
287
|
+
self
|
213
288
|
end
|
214
|
-
|
289
|
+
|
215
290
|
# Returns whether or not +path+ is empty. Returns false if +path+ is not
|
216
291
|
# a directory, or contains any files other than '.' or '..'.
|
217
|
-
#
|
292
|
+
#
|
218
293
|
def self.empty?(path)
|
219
|
-
|
294
|
+
path = path + "\0"
|
295
|
+
path = path.encode('UTF-16LE')
|
296
|
+
PathIsDirectoryEmptyW(path)
|
220
297
|
end
|
221
|
-
|
298
|
+
|
222
299
|
# Returns whether or not +path+ is a junction.
|
223
|
-
#
|
300
|
+
#
|
224
301
|
def self.junction?(path)
|
225
|
-
bool
|
226
|
-
|
227
|
-
|
302
|
+
bool = true
|
303
|
+
path = path + "\0"
|
304
|
+
path.encode!('UTF-16LE')
|
305
|
+
|
306
|
+
attrib = GetFileAttributesW(path)
|
307
|
+
|
308
|
+
# Only directories with a reparse point attribute can be junctions
|
228
309
|
if attrib == INVALID_FILE_ATTRIBUTES ||
|
229
310
|
attrib & FILE_ATTRIBUTE_DIRECTORY == 0 ||
|
230
311
|
attrib & FILE_ATTRIBUTE_REPARSE_POINT == 0
|
231
312
|
then
|
232
|
-
|
313
|
+
bool = false
|
233
314
|
end
|
234
|
-
|
315
|
+
|
235
316
|
bool
|
236
|
-
end
|
237
|
-
|
317
|
+
end
|
318
|
+
|
238
319
|
# Class level aliases
|
239
320
|
#
|
240
321
|
class << self
|
241
322
|
alias reparse_dir? junction?
|
242
323
|
end
|
324
|
+
|
325
|
+
private
|
326
|
+
|
327
|
+
# Macro from Windows header file, used by the create_junction method.
|
328
|
+
def self.CTL_CODE(device, function, method, access)
|
329
|
+
((device) << 16) | ((access) << 14) | ((function) << 2) | (method)
|
330
|
+
end
|
243
331
|
end
|