win32-dir 0.4.0 → 0.4.1
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 +62 -59
- data/README +265 -265
- data/Rakefile +32 -32
- data/lib/win32/dir/constants.rb +18 -18
- data/lib/win32/dir/functions.rb +35 -35
- data/lib/win32/dir/structs.rb +29 -29
- data/lib/win32/dir.rb +419 -331
- data/test/test_win32_dir.rb +397 -385
- data/win32-dir.gemspec +28 -28
- metadata +2 -2
data/lib/win32/dir.rb
CHANGED
@@ -1,331 +1,419 @@
|
|
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')
|
4
|
-
|
5
|
-
class Dir
|
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
|
-
|
28
|
-
# The version of the win32-dir library.
|
29
|
-
VERSION = '0.4.
|
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
|
-
]
|
90
|
-
|
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')
|
96
|
-
|
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
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
Dir.const_set(key, path) if path
|
114
|
-
}
|
115
|
-
|
116
|
-
# Set Dir::MYDOCUMENTS to the same as Dir::PERSONAL if undefined
|
117
|
-
unless defined? MYDOCUMENTS
|
118
|
-
MYDOCUMENTS = PERSONAL
|
119
|
-
end
|
120
|
-
|
121
|
-
class << self
|
122
|
-
alias old_glob glob
|
123
|
-
|
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
|
131
|
-
|
132
|
-
alias old_ref []
|
133
|
-
|
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
|
141
|
-
|
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
|
167
|
-
end
|
168
|
-
|
169
|
-
path2.encode!('UTF-16LE')
|
170
|
-
|
171
|
-
if GetShortPathNameW(path1, path2, path2.size) == 0
|
172
|
-
raise SystemCallError,
|
173
|
-
end
|
174
|
-
|
175
|
-
path3.encode!('UTF-16LE')
|
176
|
-
|
177
|
-
if GetLongPathNameW(path2, path3, path3.size) == 0
|
178
|
-
raise SystemCallError, FFI.errno
|
179
|
-
end
|
180
|
-
|
181
|
-
path3.strip.encode(Encoding.default_external)
|
182
|
-
end
|
183
|
-
|
184
|
-
alias :pwd :getwd
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
# Creates the symlink +to+, linked to the existing directory +from+. If the
|
189
|
-
# +to+ directory already exists, it must be empty or an error is raised.
|
190
|
-
#
|
191
|
-
# Example:
|
192
|
-
#
|
193
|
-
# Dir.mkdir('C:/from')
|
194
|
-
# Dir.create_junction('C:/to', 'C:/from')
|
195
|
-
#
|
196
|
-
def self.create_junction(to, from)
|
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)
|
207
|
-
|
208
|
-
if length == 0
|
209
|
-
raise SystemCallError, FFI.errno
|
210
|
-
else
|
211
|
-
from_path.strip!
|
212
|
-
end
|
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)
|
219
|
-
|
220
|
-
if length == 0
|
221
|
-
raise SystemCallError, FFI.errno
|
222
|
-
else
|
223
|
-
to_path.strip!
|
224
|
-
end
|
225
|
-
|
226
|
-
# You can create a junction to a directory that already exists, so
|
227
|
-
# long as it's empty.
|
228
|
-
unless CreateDirectoryW(to_path, nil)
|
229
|
-
if FFI.errno != ERROR_ALREADY_EXISTS
|
230
|
-
raise SystemCallError, FFI.errno
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
begin
|
235
|
-
# Generic read & write + open existing + reparse point & backup semantics
|
236
|
-
handle = CreateFileW(
|
237
|
-
to_path,
|
238
|
-
GENERIC_READ | GENERIC_WRITE,
|
239
|
-
0,
|
240
|
-
nil,
|
241
|
-
OPEN_EXISTING,
|
242
|
-
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
|
243
|
-
0
|
244
|
-
)
|
245
|
-
|
246
|
-
if handle == INVALID_HANDLE_VALUE
|
247
|
-
raise SystemCallError, FFI.errno
|
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
|
281
|
-
end
|
282
|
-
ensure
|
283
|
-
CloseHandle(handle)
|
284
|
-
end
|
285
|
-
end
|
286
|
-
|
287
|
-
self
|
288
|
-
end
|
289
|
-
|
290
|
-
# Returns
|
291
|
-
#
|
292
|
-
#
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
#
|
300
|
-
#
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
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')
|
4
|
+
|
5
|
+
class Dir
|
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
|
+
|
28
|
+
# The version of the win32-dir library.
|
29
|
+
VERSION = '0.4.1'
|
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
|
+
]
|
90
|
+
|
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')
|
96
|
+
|
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
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
Dir.const_set(key, path) if path
|
114
|
+
}
|
115
|
+
|
116
|
+
# Set Dir::MYDOCUMENTS to the same as Dir::PERSONAL if undefined
|
117
|
+
unless defined? MYDOCUMENTS
|
118
|
+
MYDOCUMENTS = PERSONAL
|
119
|
+
end
|
120
|
+
|
121
|
+
class << self
|
122
|
+
alias old_glob glob
|
123
|
+
|
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
|
131
|
+
|
132
|
+
alias old_ref []
|
133
|
+
|
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
|
141
|
+
|
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.new("GetCurrentDirectoryW", FFI.errno)
|
167
|
+
end
|
168
|
+
|
169
|
+
path2.encode!('UTF-16LE')
|
170
|
+
|
171
|
+
if GetShortPathNameW(path1, path2, path2.size) == 0
|
172
|
+
raise SystemCallError.new("GetShortPathNameW", FFI.errno)
|
173
|
+
end
|
174
|
+
|
175
|
+
path3.encode!('UTF-16LE')
|
176
|
+
|
177
|
+
if GetLongPathNameW(path2, path3, path3.size) == 0
|
178
|
+
raise SystemCallError.new("GetLongPathNameW", FFI.errno)
|
179
|
+
end
|
180
|
+
|
181
|
+
path3.strip.encode(Encoding.default_external)
|
182
|
+
end
|
183
|
+
|
184
|
+
alias :pwd :getwd
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Creates the symlink +to+, linked to the existing directory +from+. If the
|
189
|
+
# +to+ directory already exists, it must be empty or an error is raised.
|
190
|
+
#
|
191
|
+
# Example:
|
192
|
+
#
|
193
|
+
# Dir.mkdir('C:/from')
|
194
|
+
# Dir.create_junction('C:/to', 'C:/from')
|
195
|
+
#
|
196
|
+
def self.create_junction(to, from)
|
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)
|
207
|
+
|
208
|
+
if length == 0
|
209
|
+
raise SystemCallError.new("GetFullPathNameW", FFI.errno)
|
210
|
+
else
|
211
|
+
from_path.strip!
|
212
|
+
end
|
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)
|
219
|
+
|
220
|
+
if length == 0
|
221
|
+
raise SystemCallError.new("GetFullPathNameW", FFI.errno)
|
222
|
+
else
|
223
|
+
to_path.strip!
|
224
|
+
end
|
225
|
+
|
226
|
+
# You can create a junction to a directory that already exists, so
|
227
|
+
# long as it's empty.
|
228
|
+
unless CreateDirectoryW(to_path, nil)
|
229
|
+
if FFI.errno != ERROR_ALREADY_EXISTS
|
230
|
+
raise SystemCallError.new("CreateDirectoryW", FFI.errno)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
begin
|
235
|
+
# Generic read & write + open existing + reparse point & backup semantics
|
236
|
+
handle = CreateFileW(
|
237
|
+
to_path,
|
238
|
+
GENERIC_READ | GENERIC_WRITE,
|
239
|
+
0,
|
240
|
+
nil,
|
241
|
+
OPEN_EXISTING,
|
242
|
+
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
|
243
|
+
0
|
244
|
+
)
|
245
|
+
|
246
|
+
if handle == INVALID_HANDLE_VALUE
|
247
|
+
raise SystemCallError.new("CreateFileW", FFI.errno)
|
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.new("DeviceIoControl", error)
|
281
|
+
end
|
282
|
+
ensure
|
283
|
+
CloseHandle(handle)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
self
|
288
|
+
end
|
289
|
+
|
290
|
+
# Returns the+ +path+ that a given +symlink+ points to.
|
291
|
+
# Raises +ENOENT+ if given path does not exist, returns +false+
|
292
|
+
# if it is not a junction.
|
293
|
+
#
|
294
|
+
# Note that regardless of the encoding of the string passed in,
|
295
|
+
# +read_junction()+ will always return a result as UTF-16LE, as it's
|
296
|
+
# actually written in the reparse point.
|
297
|
+
#
|
298
|
+
# Example:
|
299
|
+
#
|
300
|
+
# Dir.mkdir('C:/from')
|
301
|
+
# Dir.create_junction('C:/to', 'C:/from')
|
302
|
+
# Dir.read_junction("c:/to") => "c:/from"
|
303
|
+
#
|
304
|
+
def self.read_junction(junction)
|
305
|
+
return false unless Dir.junction?(junction)
|
306
|
+
junction = junction.tr(File::SEPARATOR, File::ALT_SEPARATOR) + "\0" # Normalize path
|
307
|
+
|
308
|
+
junction.encode!('UTF-16LE')
|
309
|
+
junction_path = 0.chr * 1024
|
310
|
+
junction_path.encode!('UTF-16LE')
|
311
|
+
|
312
|
+
length = GetFullPathNameW(junction, junction_path.size, junction_path, nil)
|
313
|
+
|
314
|
+
if length == 0
|
315
|
+
raise SystemCallError.new("GetFullPathNameW", FFI.errno)
|
316
|
+
else
|
317
|
+
junction_path.strip!
|
318
|
+
end
|
319
|
+
|
320
|
+
begin
|
321
|
+
# Generic read & write + open existing + reparse point & backup semantics
|
322
|
+
handle = CreateFileW(
|
323
|
+
junction_path,
|
324
|
+
GENERIC_READ | GENERIC_WRITE,
|
325
|
+
0,
|
326
|
+
nil,
|
327
|
+
OPEN_EXISTING,
|
328
|
+
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
|
329
|
+
0
|
330
|
+
)
|
331
|
+
|
332
|
+
if handle == INVALID_HANDLE_VALUE
|
333
|
+
raise SystemCallError.new("CreateFileW", FFI.errno)
|
334
|
+
end
|
335
|
+
|
336
|
+
rdb = REPARSE_JDATA_BUFFER.new
|
337
|
+
rdb[:ReparseTag] = 0
|
338
|
+
rdb[:ReparseDataLength] = 0
|
339
|
+
rdb[:Reserved] = 0
|
340
|
+
rdb[:SubstituteNameOffset] = 0
|
341
|
+
rdb[:SubstituteNameLength] = 0
|
342
|
+
rdb[:PrintNameOffset] = 0
|
343
|
+
rdb[:PrintNameLength] = 0
|
344
|
+
rdb[:PathBuffer] = ''
|
345
|
+
|
346
|
+
bytes = FFI::MemoryPointer.new(:ulong)
|
347
|
+
|
348
|
+
begin
|
349
|
+
bool = DeviceIoControl(
|
350
|
+
handle,
|
351
|
+
CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, 0),
|
352
|
+
nil,
|
353
|
+
0,
|
354
|
+
rdb,
|
355
|
+
1024,
|
356
|
+
bytes,
|
357
|
+
nil
|
358
|
+
)
|
359
|
+
|
360
|
+
error = FFI.errno
|
361
|
+
|
362
|
+
unless bool
|
363
|
+
raise SystemCallError.new("DeviceIoControl", error)
|
364
|
+
end
|
365
|
+
ensure
|
366
|
+
CloseHandle(handle)
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
# MSDN says print and substitute names can be in any order
|
371
|
+
jname = (rdb[:PathBuffer].to_ptr + rdb[:SubstituteNameOffset]).read_string(rdb[:SubstituteNameLength])
|
372
|
+
jname = jname.bytes.to_a.pack('C*')
|
373
|
+
jname = jname.force_encoding("UTF-16LE")
|
374
|
+
raise "Junction name came back as #{jname}" unless jname[0..3] == "\\??\\".encode("UTF-16LE")
|
375
|
+
return jname[4..-1].gsub("\\".encode("UTF-16LE"), "/".encode("UTF-16LE"))
|
376
|
+
end
|
377
|
+
|
378
|
+
# Returns whether or not +path+ is empty. Returns false if +path+ is not
|
379
|
+
# a directory, or contains any files other than '.' or '..'.
|
380
|
+
#
|
381
|
+
def self.empty?(path)
|
382
|
+
path = path + "\0"
|
383
|
+
path = path.encode('UTF-16LE')
|
384
|
+
PathIsDirectoryEmptyW(path)
|
385
|
+
end
|
386
|
+
|
387
|
+
# Returns whether or not +path+ is a junction.
|
388
|
+
#
|
389
|
+
def self.junction?(path)
|
390
|
+
bool = true
|
391
|
+
path = path + "\0"
|
392
|
+
path.encode!('UTF-16LE')
|
393
|
+
|
394
|
+
attrib = GetFileAttributesW(path)
|
395
|
+
|
396
|
+
# Only directories with a reparse point attribute can be junctions
|
397
|
+
if attrib == INVALID_FILE_ATTRIBUTES ||
|
398
|
+
attrib & FILE_ATTRIBUTE_DIRECTORY == 0 ||
|
399
|
+
attrib & FILE_ATTRIBUTE_REPARSE_POINT == 0
|
400
|
+
then
|
401
|
+
bool = false
|
402
|
+
end
|
403
|
+
|
404
|
+
bool
|
405
|
+
end
|
406
|
+
|
407
|
+
# Class level aliases
|
408
|
+
#
|
409
|
+
class << self
|
410
|
+
alias reparse_dir? junction?
|
411
|
+
end
|
412
|
+
|
413
|
+
private
|
414
|
+
|
415
|
+
# Macro from Windows header file, used by the create_junction method.
|
416
|
+
def self.CTL_CODE(device, function, method, access)
|
417
|
+
((device) << 16) | ((access) << 14) | ((function) << 2) | (method)
|
418
|
+
end
|
419
|
+
end
|