win32-file 0.5.3 → 0.5.4
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 +15 -16
- data/README +13 -17
- data/Rakefile +66 -0
- data/lib/win32/file.rb +459 -454
- data/test/tc_file_attributes.rb +5 -12
- data/test/tc_file_constants.rb +4 -11
- data/test/tc_file_encryption.rb +6 -11
- data/test/tc_file_path.rb +4 -11
- data/test/tc_file_security.rb +2 -9
- data/test/tc_file_stat.rb +6 -12
- data/test/ts_all.rb +0 -5
- data/win32-file.gemspec +1 -1
- metadata +6 -3
data/CHANGES
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
== 0.5.4 - 8-Apr-2007
|
2
|
+
* Now runs -w clean.
|
3
|
+
* Added a Rakefile. Manual installation and testing should now be handled
|
4
|
+
by the Rake tasks.
|
5
|
+
* Some updates to the README file.
|
6
|
+
|
1
7
|
== 0.5.3 - 2-Nov-2006
|
2
8
|
* Added the File.lstat method. It's abscence caused problems for cross
|
3
9
|
platform packages (such as the 'find' module) which were expecting a result
|
data/MANIFEST
CHANGED
@@ -1,16 +1,15 @@
|
|
1
|
-
CHANGES
|
2
|
-
MANIFEST
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
lib/win32/file.rb
|
8
|
-
|
9
|
-
test/
|
10
|
-
test/
|
11
|
-
test/
|
12
|
-
test/
|
13
|
-
test/
|
14
|
-
test/
|
15
|
-
test/
|
16
|
-
test/ts_all.rb
|
1
|
+
* CHANGES
|
2
|
+
* MANIFEST
|
3
|
+
* Rakefile
|
4
|
+
* README
|
5
|
+
* install.rb
|
6
|
+
* win32-file.gemspec
|
7
|
+
* lib/win32/file.rb
|
8
|
+
* test/sometestfile.txt
|
9
|
+
* test/tc_file_attributes.rb
|
10
|
+
* test/tc_file_constants.rb
|
11
|
+
* test/tc_file_encryption.rb
|
12
|
+
* test/tc_file_path.rb
|
13
|
+
* test/tc_file_security.rb
|
14
|
+
* test/tc_file_stat.rb
|
15
|
+
* test/ts_all.rb
|
data/README
CHANGED
@@ -2,27 +2,23 @@
|
|
2
2
|
Extra or redefined methods for the File class on MS Windows.
|
3
3
|
|
4
4
|
= Prerequisites
|
5
|
-
Ruby 1.8.0 or later
|
6
|
-
windows-pr 0.3.0 or later
|
7
|
-
win32-file-stat 1.2.0 or later
|
5
|
+
* Ruby 1.8.0 or later
|
6
|
+
* windows-pr 0.3.0 or later
|
7
|
+
* win32-file-stat 1.2.0 or later
|
8
8
|
|
9
9
|
= Installation
|
10
|
-
|
11
|
-
|
12
|
-
ruby install.rb
|
13
|
-
== Gem Install
|
14
|
-
ruby win32-file.gemspec
|
15
|
-
gem install win32-file-X.Y.Z-mswin32.gem
|
10
|
+
rake test (optional)
|
11
|
+
rake install (standard) or rake install_gem (rubygems)
|
16
12
|
|
17
13
|
= Synopsis
|
18
|
-
require 'win32/file'
|
14
|
+
require 'win32/file'
|
19
15
|
|
20
|
-
p File.hidden?(somefile)
|
21
|
-
p File.attributes(somefile)
|
16
|
+
p File.hidden?(somefile)
|
17
|
+
p File.attributes(somefile)
|
22
18
|
|
23
|
-
File.open(somefile){ |fh|
|
24
|
-
|
25
|
-
}
|
19
|
+
File.open(somefile){ |fh|
|
20
|
+
fh.hidden = true
|
21
|
+
}
|
26
22
|
|
27
23
|
= Class methods added - see documentation for details
|
28
24
|
* File.attributes
|
@@ -94,5 +90,5 @@ implied warranties, including, without limitation, the implied
|
|
94
90
|
warranties of merchantability and fitness for a particular purpose.
|
95
91
|
|
96
92
|
= Authors
|
97
|
-
Daniel J. Berger
|
98
|
-
Park Heesob
|
93
|
+
* Daniel J. Berger
|
94
|
+
* Park Heesob
|
data/Rakefile
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
task :clean do
|
5
|
+
rm 'sometestfile.txt' if File.exists? 'sometestfile.txt'
|
6
|
+
end
|
7
|
+
|
8
|
+
desc 'Install the win32-file package (non-gem)'
|
9
|
+
task :install => [:clean] do
|
10
|
+
ruby 'install.rb'
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Install the win32-file package as a gem'
|
14
|
+
task :install_gem => [:clean] do
|
15
|
+
ruby 'win32-file.gemspec'
|
16
|
+
file = Dir['win32-file*.gem'].first
|
17
|
+
sh "gem install #{file}"
|
18
|
+
end
|
19
|
+
|
20
|
+
Rake::TestTask.new("test") do |t|
|
21
|
+
t.libs << 'test'
|
22
|
+
t.verbose = true
|
23
|
+
t.warning = true
|
24
|
+
t.test_files = FileList['test/ts_all.rb']
|
25
|
+
end
|
26
|
+
|
27
|
+
Rake::TestTask.new("test_attributes") do |t|
|
28
|
+
cp('test/sometestfile.txt', '.')
|
29
|
+
t.verbose = true
|
30
|
+
t.warning = true
|
31
|
+
t.test_files = FileList['test/tc_file_attributes.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
Rake::TestTask.new("test_constants") do |t|
|
35
|
+
t.verbose = true
|
36
|
+
t.warning = true
|
37
|
+
t.test_files = FileList['test/tc_file_constants.rb']
|
38
|
+
end
|
39
|
+
|
40
|
+
Rake::TestTask.new("test_encryption") do |t|
|
41
|
+
cp('test/sometestfile.txt', '.')
|
42
|
+
t.verbose = true
|
43
|
+
t.warning = true
|
44
|
+
t.test_files = FileList['test/tc_file_encryption.rb']
|
45
|
+
end
|
46
|
+
|
47
|
+
Rake::TestTask.new("test_path") do |t|
|
48
|
+
cp('test/sometestfile.txt', '.')
|
49
|
+
t.verbose = true
|
50
|
+
t.warning = true
|
51
|
+
t.test_files = FileList['test/tc_file_path.rb']
|
52
|
+
end
|
53
|
+
|
54
|
+
Rake::TestTask.new("test_security") do |t|
|
55
|
+
cp('test/sometestfile.txt', '.')
|
56
|
+
t.verbose = true
|
57
|
+
t.warning = true
|
58
|
+
t.test_files = FileList['test/tc_file_security.rb']
|
59
|
+
end
|
60
|
+
|
61
|
+
Rake::TestTask.new("test_stat") do |t|
|
62
|
+
cp('test/sometestfile.txt', '.')
|
63
|
+
t.verbose = true
|
64
|
+
t.warning = true
|
65
|
+
t.test_files = FileList['test/tc_file_stat.rb']
|
66
|
+
end
|
data/lib/win32/file.rb
CHANGED
@@ -14,8 +14,9 @@ class File
|
|
14
14
|
extend Windows::Path
|
15
15
|
extend Windows::Security
|
16
16
|
extend Windows::MSVCRT::Buffer
|
17
|
+
extend Windows::Limits
|
17
18
|
|
18
|
-
VERSION = '0.5.
|
19
|
+
VERSION = '0.5.4'
|
19
20
|
MAX_PATH = 260
|
20
21
|
|
21
22
|
# Abbreviated attribute constants for convenience
|
@@ -48,510 +49,529 @@ class File
|
|
48
49
|
}
|
49
50
|
|
50
51
|
### Class Methods
|
51
|
-
|
52
|
-
## Security
|
53
|
-
|
54
|
-
# Sets the file permissions for the given file name. The 'permissions'
|
55
|
-
# argument is a hash with an account name as the key, and the various
|
56
|
-
# permission constants as possible values. The possible constant values
|
57
|
-
# are:
|
58
|
-
#
|
59
|
-
# FILE_READ_DATA
|
60
|
-
# FILE_WRITE_DATA
|
61
|
-
# FILE_APPEND_DATA
|
62
|
-
# FILE_READ_EA
|
63
|
-
# FILE_WRITE_EA
|
64
|
-
# FILE_EXECUTE
|
65
|
-
# FILE_DELETE_CHILD
|
66
|
-
# FILE_READ_ATTRIBUTES
|
67
|
-
# FILE_WRITE_ATTRIBUTES
|
68
|
-
# STANDARD_RIGHTS_ALL
|
69
|
-
# FULL
|
70
|
-
# READ
|
71
|
-
# ADD
|
72
|
-
# CHANGE
|
73
|
-
# DELETE
|
74
|
-
# READ_CONTROL
|
75
|
-
# WRITE_DAC
|
76
|
-
# WRITE_OWNER
|
77
|
-
# SYNCHRONIZE
|
78
|
-
# STANDARD_RIGHTS_REQUIRED
|
79
|
-
# STANDARD_RIGHTS_READ
|
80
|
-
# STANDARD_RIGHTS_WRITE
|
81
|
-
# STANDARD_RIGHTS_EXECUTE
|
82
|
-
# STANDARD_RIGHTS_ALL
|
83
|
-
# SPECIFIC_RIGHTS_ALL
|
84
|
-
# ACCESS_SYSTEM_SECURITY
|
85
|
-
# MAXIMUM_ALLOWED
|
86
|
-
# GENERIC_READ
|
87
|
-
# GENERIC_WRITE
|
88
|
-
# GENERIC_EXECUTE
|
89
|
-
# GENERIC_ALL
|
90
|
-
#
|
91
|
-
def self.set_permissions(file, perms)
|
92
|
-
raise TypeError unless perms.kind_of?(Hash)
|
93
|
-
|
94
|
-
account_rights = 0
|
95
|
-
sec_desc = 0.chr * SECURITY_DESCRIPTOR_MIN_LENGTH
|
96
|
-
|
97
|
-
unless InitializeSecurityDescriptor(sec_desc, 1)
|
98
|
-
raise ArgumentError, get_last_error
|
99
|
-
end
|
100
|
-
|
101
|
-
cb_acl = 1024
|
102
|
-
cb_sid = 1024
|
103
|
-
|
104
|
-
acl_new = 0.chr * cb_acl
|
105
|
-
|
106
|
-
unless InitializeAcl(acl_new, cb_acl, ACL_REVISION2)
|
107
|
-
raise ArgumentError, get_last_error
|
108
|
-
end
|
109
|
-
|
110
|
-
sid = 0.chr * cb_sid
|
111
|
-
snu_type = 0.chr * cb_sid
|
112
|
-
|
113
|
-
all_ace = 0.chr * ALLOW_ACE_LENGTH
|
114
|
-
all_ace_ptr = memset(all_ace, 0, 0) # address of all_ace
|
115
52
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
53
|
+
class << self
|
54
|
+
# Strictly for making this code -w clean. They are removed later.
|
55
|
+
alias basename_orig basename
|
56
|
+
alias blockdev_orig blockdev?
|
57
|
+
alias chardev_orig chardev?
|
58
|
+
alias dirname_orig dirname
|
59
|
+
alias lstat_orig lstat
|
60
|
+
alias size_orig size
|
61
|
+
alias split_orig split
|
62
|
+
alias stat_orig stat
|
63
|
+
|
64
|
+
## Security
|
65
|
+
|
66
|
+
# Sets the file permissions for the given file name. The 'permissions'
|
67
|
+
# argument is a hash with an account name as the key, and the various
|
68
|
+
# permission constants as possible values. The possible constant values
|
69
|
+
# are:
|
70
|
+
#
|
71
|
+
# FILE_READ_DATA
|
72
|
+
# FILE_WRITE_DATA
|
73
|
+
# FILE_APPEND_DATA
|
74
|
+
# FILE_READ_EA
|
75
|
+
# FILE_WRITE_EA
|
76
|
+
# FILE_EXECUTE
|
77
|
+
# FILE_DELETE_CHILD
|
78
|
+
# FILE_READ_ATTRIBUTES
|
79
|
+
# FILE_WRITE_ATTRIBUTES
|
80
|
+
# STANDARD_RIGHTS_ALL
|
81
|
+
# FULL
|
82
|
+
# READ
|
83
|
+
# ADD
|
84
|
+
# CHANGE
|
85
|
+
# DELETE
|
86
|
+
# READ_CONTROL
|
87
|
+
# WRITE_DAC
|
88
|
+
# WRITE_OWNER
|
89
|
+
# SYNCHRONIZE
|
90
|
+
# STANDARD_RIGHTS_REQUIRED
|
91
|
+
# STANDARD_RIGHTS_READ
|
92
|
+
# STANDARD_RIGHTS_WRITE
|
93
|
+
# STANDARD_RIGHTS_EXECUTE
|
94
|
+
# STANDARD_RIGHTS_ALL
|
95
|
+
# SPECIFIC_RIGHTS_ALL
|
96
|
+
# ACCESS_SYSTEM_SECURITY
|
97
|
+
# MAXIMUM_ALLOWED
|
98
|
+
# GENERIC_READ
|
99
|
+
# GENERIC_WRITE
|
100
|
+
# GENERIC_EXECUTE
|
101
|
+
# GENERIC_ALL
|
102
|
+
#
|
103
|
+
def set_permissions(file, perms)
|
104
|
+
raise TypeError unless perms.kind_of?(Hash)
|
105
|
+
|
106
|
+
account_rights = 0
|
107
|
+
sec_desc = 0.chr * SECURITY_DESCRIPTOR_MIN_LENGTH
|
108
|
+
|
109
|
+
unless InitializeSecurityDescriptor(sec_desc, 1)
|
110
|
+
raise ArgumentError, get_last_error
|
130
111
|
end
|
131
112
|
|
132
|
-
|
133
|
-
|
134
|
-
account,
|
135
|
-
sid,
|
136
|
-
cb_sid,
|
137
|
-
domain_buf,
|
138
|
-
cch_domain,
|
139
|
-
snu_type
|
140
|
-
)
|
113
|
+
cb_acl = 1024
|
114
|
+
cb_sid = 1024
|
141
115
|
|
142
|
-
|
116
|
+
acl_new = 0.chr * cb_acl
|
117
|
+
|
118
|
+
unless InitializeAcl(acl_new, cb_acl, ACL_REVISION2)
|
143
119
|
raise ArgumentError, get_last_error
|
144
120
|
end
|
145
121
|
|
146
|
-
|
147
|
-
|
148
|
-
val = CopySid(
|
149
|
-
ALLOW_ACE_LENGTH - size,
|
150
|
-
all_ace_ptr + 8, # address of all_ace_ptr->SidStart
|
151
|
-
sid
|
152
|
-
)
|
122
|
+
sid = 0.chr * cb_sid
|
123
|
+
snu_type = 0.chr * cb_sid
|
153
124
|
|
154
|
-
|
155
|
-
|
156
|
-
end
|
125
|
+
all_ace = 0.chr * ALLOW_ACE_LENGTH
|
126
|
+
all_ace_ptr = memset(all_ace, 0, 0) # address of all_ace
|
157
127
|
|
158
|
-
|
159
|
-
|
160
|
-
elsif (GENERIC_RIGHTS_CHK & mask).nonzero?
|
161
|
-
account_rights = GENERIC_RIGHTS_MASK & mask
|
162
|
-
end
|
128
|
+
# all_ace_ptr->Header.AceType = ACCESS_ALLOWED_ACE_TYPE
|
129
|
+
all_ace[0] = 0
|
163
130
|
|
164
|
-
|
165
|
-
|
131
|
+
perms.each{ |account, mask|
|
132
|
+
next if mask.nil?
|
133
|
+
|
134
|
+
cch_domain = [80].pack('L')
|
135
|
+
cb_sid = [1024].pack('L')
|
136
|
+
domain_buf = 0.chr * 80
|
166
137
|
|
167
|
-
|
168
|
-
if account_rights != 0
|
169
|
-
all_ace[2,2] = [12 - 4 + GetLengthSid(sid)].pack('S')
|
170
|
-
all_ace[4,4] = [account_rights].pack('L')
|
138
|
+
server, account = account.split("\\")
|
171
139
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
MAXDWORD,
|
176
|
-
all_ace_ptr,
|
177
|
-
all_ace[2,2].unpack('S').first
|
178
|
-
)
|
140
|
+
if ['BUILTIN', 'NT AUTHORITY'].include?(server.upcase)
|
141
|
+
server = nil
|
142
|
+
end
|
179
143
|
|
180
|
-
|
181
|
-
|
182
|
-
|
144
|
+
val = LookupAccountName(
|
145
|
+
server,
|
146
|
+
account,
|
147
|
+
sid,
|
148
|
+
cb_sid,
|
149
|
+
domain_buf,
|
150
|
+
cch_domain,
|
151
|
+
snu_type
|
152
|
+
)
|
183
153
|
|
184
|
-
|
185
|
-
|
186
|
-
else
|
187
|
-
# all_ace_ptr->Header.AceFlags = 0
|
188
|
-
all_ace[1] = 0
|
154
|
+
if val == 0
|
155
|
+
raise ArgumentError, get_last_error
|
189
156
|
end
|
190
157
|
|
191
|
-
|
192
|
-
|
193
|
-
|
158
|
+
size = [0,0,0,0,0].pack('CCSLL').length # sizeof(ACCESS_ALLOWED_ACE)
|
159
|
+
|
160
|
+
val = CopySid(
|
161
|
+
ALLOW_ACE_LENGTH - size,
|
162
|
+
all_ace_ptr + 8, # address of all_ace_ptr->SidStart
|
163
|
+
sid
|
164
|
+
)
|
194
165
|
|
195
|
-
|
196
|
-
|
197
|
-
|
166
|
+
if val == 0
|
167
|
+
raise ArgumentError, get_last_error
|
168
|
+
end
|
198
169
|
|
199
|
-
|
200
|
-
|
201
|
-
|
170
|
+
if (GENERIC_ALL & mask).nonzero?
|
171
|
+
account_rights = GENERIC_ALL & mask
|
172
|
+
elsif (GENERIC_RIGHTS_CHK & mask).nonzero?
|
173
|
+
account_rights = GENERIC_RIGHTS_MASK & mask
|
174
|
+
end
|
202
175
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
176
|
+
# all_ace_ptr->Header.AceFlags = INHERIT_ONLY_ACE|OBJECT_INHERIT_ACE
|
177
|
+
all_ace[1] = INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE
|
178
|
+
|
179
|
+
2.times{
|
180
|
+
if account_rights != 0
|
181
|
+
all_ace[2,2] = [12 - 4 + GetLengthSid(sid)].pack('S')
|
182
|
+
all_ace[4,4] = [account_rights].pack('L')
|
183
|
+
|
184
|
+
val = AddAce(
|
185
|
+
acl_new,
|
186
|
+
ACL_REVISION2,
|
187
|
+
MAXDWORD,
|
188
|
+
all_ace_ptr,
|
189
|
+
all_ace[2,2].unpack('S').first
|
190
|
+
)
|
191
|
+
|
192
|
+
if val == 0
|
193
|
+
raise ArgumentError, get_last_error
|
194
|
+
end
|
195
|
+
|
196
|
+
# all_ace_ptr->Header.AceFlags = CONTAINER_INHERIT_ACE
|
197
|
+
all_ace[1] = CONTAINER_INHERIT_ACE
|
198
|
+
else
|
199
|
+
# all_ace_ptr->Header.AceFlags = 0
|
200
|
+
all_ace[1] = 0
|
220
201
|
end
|
202
|
+
|
203
|
+
account_rights = REST_RIGHTS_MASK & mask
|
221
204
|
}
|
222
|
-
|
223
|
-
end
|
224
|
-
sec_array
|
225
|
-
end
|
205
|
+
}
|
226
206
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
#
|
231
|
-
# To get a human readable version of the permissions, pass the value to the
|
232
|
-
# +File.securities+ method.
|
233
|
-
#
|
234
|
-
def self.get_permissions(file, host=nil)
|
235
|
-
current_length = 0
|
236
|
-
length_needed = [0].pack('L')
|
237
|
-
sec_buf = ''
|
238
|
-
|
239
|
-
loop do
|
240
|
-
bool = GetFileSecurity(
|
241
|
-
file,
|
242
|
-
DACL_SECURITY_INFORMATION,
|
243
|
-
sec_buf,
|
244
|
-
sec_buf.length,
|
245
|
-
length_needed
|
246
|
-
)
|
207
|
+
unless SetSecurityDescriptorDacl(sec_desc, 1, acl_new, 0)
|
208
|
+
raise ArgumentError, get_last_error
|
209
|
+
end
|
247
210
|
|
248
|
-
|
211
|
+
unless SetFileSecurity(file, DACL_SECURITY_INFORMATION, sec_desc)
|
249
212
|
raise ArgumentError, get_last_error
|
250
213
|
end
|
251
|
-
|
252
|
-
|
253
|
-
|
214
|
+
|
215
|
+
self
|
216
|
+
end
|
217
|
+
|
218
|
+
# Returns an array of human-readable strings that correspond to the
|
219
|
+
# permission flags.
|
220
|
+
#
|
221
|
+
def securities(mask)
|
222
|
+
sec_array = []
|
223
|
+
if mask == 0
|
224
|
+
sec_array.push('NONE')
|
225
|
+
else
|
226
|
+
if (mask & FULL) ^ FULL == 0
|
227
|
+
sec_array.push('FULL')
|
228
|
+
else
|
229
|
+
SECURITY_RIGHTS.each{ |string, numeric|
|
230
|
+
if (numeric & mask) ^ numeric == 0
|
231
|
+
sec_array.push(string)
|
232
|
+
end
|
233
|
+
}
|
234
|
+
end
|
235
|
+
end
|
236
|
+
sec_array
|
254
237
|
end
|
255
238
|
|
256
|
-
|
257
|
-
|
239
|
+
# Returns a hash describing the current file permissions for the given
|
240
|
+
# file. The account name is the key, and the value is an integer
|
241
|
+
# representing an or'd value that corresponds to the security
|
242
|
+
# permissions for that file.
|
243
|
+
#
|
244
|
+
# To get a human readable version of the permissions, pass the value to
|
245
|
+
# the +File.securities+ method.
|
246
|
+
#
|
247
|
+
def get_permissions(file, host=nil)
|
248
|
+
current_length = 0
|
249
|
+
length_needed = [0].pack('L')
|
250
|
+
sec_buf = ''
|
251
|
+
|
252
|
+
loop do
|
253
|
+
bool = GetFileSecurity(
|
254
|
+
file,
|
255
|
+
DACL_SECURITY_INFORMATION,
|
256
|
+
sec_buf,
|
257
|
+
sec_buf.length,
|
258
|
+
length_needed
|
259
|
+
)
|
258
260
|
|
259
|
-
|
260
|
-
|
261
|
-
|
261
|
+
if bool == 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER
|
262
|
+
raise ArgumentError, get_last_error
|
263
|
+
end
|
264
|
+
|
265
|
+
break if sec_buf.length >= length_needed.unpack('L').first
|
266
|
+
sec_buf += ' ' * length_needed.unpack('L').first
|
267
|
+
end
|
262
268
|
|
263
|
-
|
264
|
-
|
265
|
-
raise ArgumentError, 'No DACL present: explicit deny all'
|
266
|
-
end
|
269
|
+
control = [0].pack('L')
|
270
|
+
revision = [0].pack('L')
|
267
271
|
|
268
|
-
|
269
|
-
|
270
|
-
|
272
|
+
unless GetSecurityDescriptorControl(sec_buf, control, revision)
|
273
|
+
raise ArgumentError, get_last_error
|
274
|
+
end
|
271
275
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
dacl_defaulted
|
277
|
-
)
|
276
|
+
# No DACL exists
|
277
|
+
if (control.unpack('L').first & SE_DACL_PRESENT) == 0
|
278
|
+
raise ArgumentError, 'No DACL present: explicit deny all'
|
279
|
+
end
|
278
280
|
|
279
|
-
|
280
|
-
|
281
|
-
|
281
|
+
dacl_present = [0].pack('L')
|
282
|
+
dacl_defaulted = [0].pack('L')
|
283
|
+
dacl_ptr = [0].pack('L')
|
282
284
|
|
283
|
-
|
284
|
-
|
285
|
+
val = GetSecurityDescriptorDacl(
|
286
|
+
sec_buf,
|
287
|
+
dacl_present,
|
288
|
+
dacl_ptr,
|
289
|
+
dacl_defaulted
|
290
|
+
)
|
285
291
|
|
286
|
-
|
287
|
-
|
288
|
-
|
292
|
+
if val == 0
|
293
|
+
raise ArgumentError, get_last_error
|
294
|
+
end
|
289
295
|
|
290
|
-
|
291
|
-
|
296
|
+
acl_buf = 0.chr * 8 # byte, byte, word, word, word (struct ACL)
|
297
|
+
memcpy(acl_buf, dacl_ptr.unpack('L').first, acl_buf.size)
|
292
298
|
|
293
|
-
|
294
|
-
|
295
|
-
unless GetAce(dacl_ptr.unpack('L').first, i, ace_ptr)
|
296
|
-
next
|
299
|
+
if acl_buf.unpack('CCSSS').first == 0
|
300
|
+
raise ArgumentError, 'DACL is NULL: implicit access grant'
|
297
301
|
end
|
298
302
|
|
299
|
-
|
300
|
-
|
303
|
+
ace_ptr = [0].pack('L')
|
304
|
+
ace_count = acl_buf.unpack('CCSSS')[3]
|
301
305
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
domain_size = [domain.size].pack('L')
|
307
|
-
snu_ptr = 0.chr * 4
|
308
|
-
|
309
|
-
val = LookupAccountSid(
|
310
|
-
host,
|
311
|
-
ace_ptr.unpack('L').first + 8, # address of ace_ptr->SidStart
|
312
|
-
name,
|
313
|
-
name_size,
|
314
|
-
domain,
|
315
|
-
domain_size,
|
316
|
-
snu_ptr
|
317
|
-
)
|
318
|
-
|
319
|
-
if val == 0
|
320
|
-
raise ArgumentError, get_last_error
|
306
|
+
perms_hash = {}
|
307
|
+
0.upto(ace_count - 1){ |i|
|
308
|
+
unless GetAce(dacl_ptr.unpack('L').first, i, ace_ptr)
|
309
|
+
next
|
321
310
|
end
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
311
|
+
|
312
|
+
ace_buf = 0.chr * 12 # ACE_HEADER, dword, dword (ACCESS_ALLOWED_ACE)
|
313
|
+
memcpy(ace_buf, ace_ptr.unpack('L').first, ace_buf.size)
|
314
|
+
|
315
|
+
if ace_buf.unpack('CCS').first == ACCESS_ALLOWED_ACE_TYPE
|
316
|
+
name = 0.chr * MAX_PATH
|
317
|
+
name_size = [name.size].pack('L')
|
318
|
+
domain = 0.chr * MAX_PATH
|
319
|
+
domain_size = [domain.size].pack('L')
|
320
|
+
snu_ptr = 0.chr * 4
|
321
|
+
|
322
|
+
val = LookupAccountSid(
|
323
|
+
host,
|
324
|
+
ace_ptr.unpack('L').first + 8, # address of ace_ptr->SidStart
|
325
|
+
name,
|
326
|
+
name_size,
|
327
|
+
domain,
|
328
|
+
domain_size,
|
329
|
+
snu_ptr
|
330
|
+
)
|
331
|
+
|
332
|
+
if val == 0
|
333
|
+
raise ArgumentError, get_last_error
|
334
|
+
end
|
335
|
+
|
336
|
+
name = name[0..name_size.unpack('L').first].split(0.chr)[0]
|
337
|
+
domain = domain[0..domain_size.unpack('L').first].split(0.chr)[0]
|
338
|
+
mask = ace_buf.unpack('LLL')[1]
|
339
|
+
|
340
|
+
unless domain.nil? || domain.empty?
|
341
|
+
name = domain + '\\' + name
|
342
|
+
end
|
343
|
+
|
344
|
+
perms_hash[name] = mask
|
329
345
|
end
|
330
|
-
|
331
|
-
|
346
|
+
}
|
347
|
+
perms_hash
|
348
|
+
end
|
349
|
+
|
350
|
+
## Encryption
|
351
|
+
|
352
|
+
# Encrypts a file or directory. All data streams in a file are encrypted.
|
353
|
+
# All new files created in an encrypted directory are encrypted.
|
354
|
+
#
|
355
|
+
# The caller must have the FILE_READ_DATA, FILE_WRITE_DATA,
|
356
|
+
# FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, and SYNCHRONIZE access
|
357
|
+
# rights.
|
358
|
+
#
|
359
|
+
# Requires exclusive access to the file being encrypted, and will fail if
|
360
|
+
# another process is using the file. If the file is compressed,
|
361
|
+
# EncryptFile will decompress the file before encrypting it.
|
362
|
+
#
|
363
|
+
# Windows 2000 or later only.
|
364
|
+
#
|
365
|
+
def encrypt(file)
|
366
|
+
unless EncryptFile(file)
|
367
|
+
raise ArgumentError, get_last_error
|
332
368
|
end
|
333
|
-
|
334
|
-
perms_hash
|
335
|
-
end
|
336
|
-
|
337
|
-
## Encryption
|
338
|
-
|
339
|
-
# Encrypts a file or directory. All data streams in a file are encrypted.
|
340
|
-
# All new files created in an encrypted directory are encrypted.
|
341
|
-
#
|
342
|
-
# The caller must have the FILE_READ_DATA, FILE_WRITE_DATA,
|
343
|
-
# FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, and SYNCHRONIZE access
|
344
|
-
# rights.
|
345
|
-
#
|
346
|
-
# Requires exclusive access to the file being encrypted, and will fail if
|
347
|
-
# another process is using the file. If the file is compressed, EncryptFile
|
348
|
-
# will decompress the file before encrypting it.
|
349
|
-
#
|
350
|
-
# Windows 2000 or later only.
|
351
|
-
#
|
352
|
-
def self.encrypt(file)
|
353
|
-
unless EncryptFile(file)
|
354
|
-
raise ArgumentError, get_last_error
|
369
|
+
self
|
355
370
|
end
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
371
|
+
|
372
|
+
# Decrypts an encrypted file or directory.
|
373
|
+
#
|
374
|
+
# The caller must have the FILE_READ_DATA, FILE_WRITE_DATA,
|
375
|
+
# FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, and SYNCHRONIZE access
|
376
|
+
# rights.
|
377
|
+
#
|
378
|
+
# Requires exclusive access to the file being decrypted, and will fail if
|
379
|
+
# another process is using the file. If the file is not encrypted an error
|
380
|
+
# is NOT raised.
|
381
|
+
#
|
382
|
+
# Windows 2000 or later only.
|
383
|
+
#
|
384
|
+
def decrypt(file)
|
385
|
+
unless DecryptFile(file, 0)
|
386
|
+
raise ArgumentError, get_last_error
|
387
|
+
end
|
388
|
+
self
|
374
389
|
end
|
375
|
-
self
|
376
|
-
end
|
377
390
|
|
378
|
-
|
379
|
-
|
380
|
-
# Returns the last component of the filename given in +filename+. If
|
381
|
-
# +suffix+ is given and present at the end of +filename+, it is removed.
|
382
|
-
# Any extension can be removed by giving an extension of ".*".
|
383
|
-
#
|
384
|
-
# This was reimplemented because the current version does not handle UNC
|
385
|
-
# paths properly, i.e. it should not return anything less than the root.
|
386
|
-
# In all other respects it is identical to the current implementation.
|
387
|
-
#
|
388
|
-
# File.basename("C:\\foo\\bar.txt") -> "bar.txt"
|
389
|
-
# File.basename("C:\\foo\\bar.txt", ".txt") -> "bar"
|
390
|
-
# File.basename("\\\\foo\\bar") -> "\\\\foo\\bar"
|
391
|
-
#
|
392
|
-
def self.basename(file, suffix = nil)
|
393
|
-
fpath = false
|
394
|
-
file = file.dup # Don't modify original string
|
391
|
+
## Path methods
|
395
392
|
|
396
|
-
#
|
397
|
-
#
|
398
|
-
|
399
|
-
|
400
|
-
|
393
|
+
# Returns the last component of the filename given in +filename+. If
|
394
|
+
# +suffix+ is given and present at the end of +filename+, it is removed.
|
395
|
+
# Any extension can be removed by giving an extension of ".*".
|
396
|
+
#
|
397
|
+
# This was reimplemented because the current version does not handle UNC
|
398
|
+
# paths properly, i.e. it should not return anything less than the root.
|
399
|
+
# In all other respects it is identical to the current implementation.
|
400
|
+
#
|
401
|
+
# File.basename("C:\\foo\\bar.txt") -> "bar.txt"
|
402
|
+
# File.basename("C:\\foo\\bar.txt", ".txt") -> "bar"
|
403
|
+
# File.basename("\\\\foo\\bar") -> "\\\\foo\\bar"
|
404
|
+
#
|
405
|
+
def basename(file, suffix = nil)
|
406
|
+
fpath = false
|
407
|
+
file = file.dup # Don't modify original string
|
408
|
+
|
409
|
+
# We have to convert forward slashes to backslashes for the Windows
|
410
|
+
# functions to work properly.
|
411
|
+
if file.include?('/')
|
412
|
+
file.tr!('/', '\\')
|
413
|
+
fpath = true
|
414
|
+
end
|
415
|
+
|
416
|
+
# Return an empty or root path as-is.
|
417
|
+
if file.empty? || PathIsRoot(file)
|
418
|
+
file.tr!("\\", '/') if fpath
|
419
|
+
return file
|
420
|
+
end
|
421
|
+
|
422
|
+
PathStripPath(file) # Gives us the basename
|
423
|
+
|
424
|
+
if suffix
|
425
|
+
if suffix == '.*'
|
426
|
+
PathRemoveExtension(file)
|
427
|
+
else
|
428
|
+
if PathFindExtension(file) == suffix
|
429
|
+
PathRemoveExtension(file)
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
file = file.split(0.chr).first
|
435
|
+
|
436
|
+
# Trim trailing slashes
|
437
|
+
while file[-1].chr == "\\"
|
438
|
+
file.chop!
|
439
|
+
end
|
440
|
+
|
441
|
+
# Return forward slashes if that's how the path was passed in.
|
442
|
+
if fpath
|
443
|
+
file.tr!("\\", '/')
|
444
|
+
end
|
445
|
+
|
446
|
+
file
|
401
447
|
end
|
402
|
-
|
403
|
-
#
|
404
|
-
|
448
|
+
|
449
|
+
# Returns all components of the filename given in +filename+ except the
|
450
|
+
# last one.
|
451
|
+
#
|
452
|
+
# This was reimplemented because the current version does not handle UNC
|
453
|
+
# paths properly, i.e. it should not return anything less than the root.
|
454
|
+
# In all other respects it is identical to the current implementation.
|
455
|
+
#
|
456
|
+
# File.dirname("C:\\foo\\bar\\baz.txt") -> "C:\\foo\\bar"
|
457
|
+
# File.dirname("\\\\foo\\bar") -> "\\\\foo\\bar"
|
458
|
+
#
|
459
|
+
def dirname(file)
|
460
|
+
fpath = false
|
461
|
+
file = file.dup
|
462
|
+
|
463
|
+
if file.include?('/')
|
464
|
+
file.tr!('/', "\\")
|
465
|
+
fpath = true
|
466
|
+
end
|
467
|
+
|
468
|
+
if PathIsRelative(file)
|
469
|
+
return '.'
|
470
|
+
end
|
471
|
+
|
472
|
+
if PathIsRoot(file)
|
473
|
+
file.tr!("\\", '/') if fpath
|
474
|
+
return file
|
475
|
+
end
|
476
|
+
|
477
|
+
PathRemoveFileSpec(file)
|
478
|
+
file = file.split(0.chr).first
|
479
|
+
PathRemoveBackslash(file)
|
480
|
+
|
405
481
|
file.tr!("\\", '/') if fpath
|
406
|
-
|
482
|
+
file
|
407
483
|
end
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
484
|
+
|
485
|
+
# Returns +file+ in long format. For example, if 'SOMEFI~1.TXT'
|
486
|
+
# was the argument provided, and the short representation for
|
487
|
+
# 'somefile.txt', then this method would return 'somefile.txt'.
|
488
|
+
#
|
489
|
+
# Note that certain file system optimizations may prevent this method
|
490
|
+
# from working as expected. In that case, you will get back the file
|
491
|
+
# name in 8.3 format.
|
492
|
+
#
|
493
|
+
def long_path(file)
|
494
|
+
buf = 0.chr * MAX_PATH
|
495
|
+
if GetLongPathName(file, buf, buf.size) == 0
|
496
|
+
raise ArgumentError, get_last_error
|
418
497
|
end
|
498
|
+
File.basename(buf.split(0.chr).first.strip)
|
419
499
|
end
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
#
|
424
|
-
|
425
|
-
|
500
|
+
|
501
|
+
# Returns 'file_name' in 8.3 format. For example, 'c:\documentation.doc'
|
502
|
+
# would be returned as 'c:\docume~1.doc'.
|
503
|
+
#
|
504
|
+
def short_path(file)
|
505
|
+
buf = 0.chr * MAX_PATH
|
506
|
+
if GetShortPathName(file, buf, buf.size) == 0
|
507
|
+
raise ArgumentError, get_last_error
|
508
|
+
end
|
509
|
+
File.basename(buf.split(0.chr).first.strip)
|
426
510
|
end
|
427
|
-
|
428
|
-
#
|
429
|
-
|
430
|
-
|
511
|
+
|
512
|
+
# Splits the given string into a directory and a file component and
|
513
|
+
# returns them in a two element array. This was reimplemented because
|
514
|
+
# the current version does not handle UNC paths properly.
|
515
|
+
#
|
516
|
+
def split(file)
|
517
|
+
array = []
|
518
|
+
|
519
|
+
if file.empty? || PathIsRoot(file)
|
520
|
+
array.push(file, '')
|
521
|
+
else
|
522
|
+
array.push(File.dirname(file), File.basename(file))
|
523
|
+
end
|
524
|
+
array
|
431
525
|
end
|
432
|
-
|
433
|
-
file
|
434
|
-
end
|
435
526
|
|
436
|
-
|
437
|
-
# last one.
|
438
|
-
#
|
439
|
-
# This was reimplemented because the current version does not handle UNC
|
440
|
-
# paths properly, i.e. it should not return anything less than the root.
|
441
|
-
# In all other respects it is identical to the current implementation.
|
442
|
-
#
|
443
|
-
# File.dirname("C:\\foo\\bar\\baz.txt") -> "C:\\foo\\bar"
|
444
|
-
# File.dirname("\\\\foo\\bar") -> "\\\\foo\\bar"
|
445
|
-
#
|
446
|
-
def self.dirname(file)
|
447
|
-
fpath = false
|
448
|
-
file = file.dup
|
527
|
+
## Stat methods
|
449
528
|
|
450
|
-
|
451
|
-
|
452
|
-
|
529
|
+
# Returns a File::Stat object, as defined in the win32-file-stat package.
|
530
|
+
#
|
531
|
+
def stat(file)
|
532
|
+
File::Stat.new(file)
|
453
533
|
end
|
454
534
|
|
455
|
-
|
456
|
-
|
535
|
+
# Identical to File.stat on Windows.
|
536
|
+
#
|
537
|
+
def lstat(file)
|
538
|
+
File::Stat.new(file)
|
457
539
|
end
|
458
540
|
|
459
|
-
|
460
|
-
|
461
|
-
|
541
|
+
# Returns the file system's block size.
|
542
|
+
#
|
543
|
+
def blksize(file)
|
544
|
+
File::Stat.new(file).blksize
|
462
545
|
end
|
463
|
-
|
464
|
-
PathRemoveFileSpec(file)
|
465
|
-
file = file.split(0.chr).first
|
466
|
-
PathRemoveBackslash(file)
|
467
|
-
|
468
|
-
file.tr!("\\", '/') if fpath
|
469
|
-
file
|
470
|
-
end
|
471
546
|
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
# from working as expected. In that case, you will get back the file
|
478
|
-
# name in 8.3 format.
|
479
|
-
#
|
480
|
-
def self.long_path(file)
|
481
|
-
buf = 0.chr * MAX_PATH
|
482
|
-
if GetLongPathName(file, buf, buf.size) == 0
|
483
|
-
raise ArgumentError, get_last_error
|
547
|
+
# Returns whether or not +file+ is a block device. For MS Windows this
|
548
|
+
# means a removable drive, cdrom or ramdisk.
|
549
|
+
#
|
550
|
+
def blockdev?(file)
|
551
|
+
File::Stat.new(file).blockdev?
|
484
552
|
end
|
485
|
-
File.basename(buf.split(0.chr).first.strip)
|
486
|
-
end
|
487
553
|
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
if GetShortPathName(file, buf, buf.size) == 0
|
494
|
-
raise ArgumentError, get_last_error
|
554
|
+
# Returns true if the file is a character device. This replaces the
|
555
|
+
# current Ruby implementation which always returns false.
|
556
|
+
#
|
557
|
+
def chardev?(file)
|
558
|
+
File::Stat.new(file).chardev?
|
495
559
|
end
|
496
|
-
File.basename(buf.split(0.chr).first.strip)
|
497
|
-
end
|
498
560
|
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
if file.empty? || PathIsRoot(file)
|
507
|
-
array.push(file, '')
|
508
|
-
else
|
509
|
-
array.push(File.dirname(file), File.basename(file))
|
561
|
+
# Returns the size of the file in bytes.
|
562
|
+
#
|
563
|
+
# This was reimplemented because the current version does not handle file
|
564
|
+
# sizes greater than 2gb.
|
565
|
+
#
|
566
|
+
def size(file)
|
567
|
+
File::Stat.new(file).size
|
510
568
|
end
|
511
|
-
array
|
512
|
-
end
|
513
|
-
|
514
|
-
## Stat methods
|
515
|
-
|
516
|
-
# Returns a File::Stat object, as defined in the win32-file-stat package.
|
517
|
-
#
|
518
|
-
def self.stat(file)
|
519
|
-
File::Stat.new(file)
|
520
|
-
end
|
521
|
-
|
522
|
-
# Identical to File.stat on Windows.
|
523
|
-
#
|
524
|
-
def self.lstat(file)
|
525
|
-
File::Stat.new(file)
|
526
|
-
end
|
527
|
-
|
528
|
-
# Returns the file system's block size.
|
529
|
-
#
|
530
|
-
def self.blksize(file)
|
531
|
-
File::Stat.new(file).blksize
|
532
|
-
end
|
533
|
-
|
534
|
-
# Returns whether or not +file+ is a block device.
|
535
|
-
#
|
536
|
-
def self.blockdev?(file)
|
537
|
-
File::Stat.new(file).blockdev?
|
538
|
-
end
|
539
|
-
|
540
|
-
# Returns true if the file is a character device. This replaces the current
|
541
|
-
# Ruby implementation which always returns false.
|
542
|
-
#
|
543
|
-
def self.chardev?(file)
|
544
|
-
File::Stat.new(file).chardev?
|
545
|
-
end
|
546
569
|
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
#
|
552
|
-
def self.size(file)
|
553
|
-
File::Stat.new(file).size
|
554
|
-
end
|
570
|
+
# We no longer need the aliases, so remove them
|
571
|
+
remove_method(:basename_orig, :blockdev_orig, :chardev_orig)
|
572
|
+
remove_method(:dirname_orig, :lstat_orig, :size_orig)
|
573
|
+
remove_method(:split_orig, :stat_orig)
|
574
|
+
end # class << self
|
555
575
|
|
556
576
|
## Attribute methods
|
557
577
|
|
@@ -923,7 +943,7 @@ class File
|
|
923
943
|
#
|
924
944
|
def sparse=(bool)
|
925
945
|
unless bool
|
926
|
-
warn '
|
946
|
+
warn 'cannot remove sparse property from a file - operation ignored'
|
927
947
|
return
|
928
948
|
end
|
929
949
|
|
@@ -1019,19 +1039,4 @@ class File
|
|
1019
1039
|
alias :set_attr :set_attributes
|
1020
1040
|
alias :unset_attr :remove_attributes
|
1021
1041
|
end
|
1022
|
-
|
1023
|
-
private
|
1024
|
-
|
1025
|
-
# This is based on the CTL_CODE macro in WinIoCtl.h
|
1026
|
-
def CTL_CODE(device, function, method, access)
|
1027
|
-
((device) << 16) | ((access) << 14) | ((function) << 2) | (method)
|
1028
|
-
end
|
1029
|
-
|
1030
|
-
def FSCTL_SET_COMPRESSION
|
1031
|
-
CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 16, 0, FILE_READ_DATA | FILE_WRITE_DATA)
|
1032
|
-
end
|
1033
|
-
|
1034
|
-
def FSCTL_SET_SPARSE
|
1035
|
-
CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 49, 0, FILE_SPECIAL_ACCESS)
|
1036
|
-
end
|
1037
1042
|
end
|