sys-filesystem 0.3.4-x86-mingw32 → 1.0.0-x86-mingw32
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 +49 -45
- data/MANIFEST +12 -13
- data/README +83 -83
- data/Rakefile +49 -83
- data/examples/example_stat.rb +24 -24
- data/lib/sys/filesystem.rb +5 -408
- data/lib/unix/sys/filesystem.rb +561 -0
- data/lib/windows/sys/filesystem.rb +408 -0
- data/sys-filesystem.gemspec +24 -25
- data/test/test_sys_filesystem.rb +7 -9
- data/test/test_sys_filesystem_unix.rb +261 -253
- data/test/test_sys_filesystem_windows.rb +198 -198
- metadata +41 -66
@@ -0,0 +1,408 @@
|
|
1
|
+
require 'windows/error'
|
2
|
+
require 'windows/path'
|
3
|
+
require 'windows/filesystem'
|
4
|
+
require 'windows/volume'
|
5
|
+
require 'windows/handle'
|
6
|
+
require 'windows/limits'
|
7
|
+
require 'socket'
|
8
|
+
require 'win32ole'
|
9
|
+
require 'date'
|
10
|
+
require 'time'
|
11
|
+
|
12
|
+
# The Sys module serves as a namespace only.
|
13
|
+
module Sys
|
14
|
+
|
15
|
+
# The Filesystem class encapsulates information about your filesystem.
|
16
|
+
class Filesystem
|
17
|
+
include Windows::Error
|
18
|
+
include Windows::Handle
|
19
|
+
include Windows::Limits
|
20
|
+
|
21
|
+
extend Windows::Error
|
22
|
+
extend Windows::FileSystem
|
23
|
+
extend Windows::Volume
|
24
|
+
extend Windows::Path
|
25
|
+
|
26
|
+
# Error typically raised if any of the Sys::Filesystem methods fail.
|
27
|
+
class Error < StandardError; end
|
28
|
+
|
29
|
+
CASE_SENSITIVE_SEARCH = 0x00000001
|
30
|
+
CASE_PRESERVED_NAMES = 0x00000002
|
31
|
+
UNICODE_ON_DISK = 0x00000004
|
32
|
+
PERSISTENT_ACLS = 0x00000008
|
33
|
+
FILE_COMPRESSION = 0x00000010
|
34
|
+
VOLUME_QUOTAS = 0x00000020
|
35
|
+
SUPPORTS_SPARSE_FILES = 0x00000040
|
36
|
+
SUPPORTS_REPARSE_POINTS = 0x00000080
|
37
|
+
SUPPORTS_REMOTE_STORAGE = 0x00000100
|
38
|
+
VOLUME_IS_COMPRESSED = 0x00008000
|
39
|
+
SUPPORTS_OBJECT_IDS = 0x00010000
|
40
|
+
SUPPORTS_ENCRYPTION = 0x00020000
|
41
|
+
NAMED_STREAMS = 0x00040000
|
42
|
+
READ_ONLY_VOLUME = 0x00080000
|
43
|
+
|
44
|
+
# The version of the sys-filesystem library.
|
45
|
+
VERSION = '1.0.0'
|
46
|
+
|
47
|
+
class Mount
|
48
|
+
# The name of the volume. This is the device mapping.
|
49
|
+
attr_reader :name
|
50
|
+
|
51
|
+
# The last time the volume was mounted. For MS Windows this equates
|
52
|
+
# to your system's boot time.
|
53
|
+
attr_reader :mount_time
|
54
|
+
|
55
|
+
# The type of mount, e.g. NTFS, UDF, etc.
|
56
|
+
attr_reader :mount_type
|
57
|
+
|
58
|
+
# The volume mount point, e.g. 'C:\'
|
59
|
+
attr_reader :mount_point
|
60
|
+
|
61
|
+
# Various comma separated options that reflect the volume's features
|
62
|
+
attr_reader :options
|
63
|
+
|
64
|
+
# Always nil on MS Windows. Provided for interface compatibility only.
|
65
|
+
attr_reader :pass_number
|
66
|
+
|
67
|
+
# Always nil on MS Windows. Provided for interface compatibility only.
|
68
|
+
attr_reader :frequency
|
69
|
+
|
70
|
+
alias fsname name
|
71
|
+
alias dir mount_point
|
72
|
+
alias opts options
|
73
|
+
alias passno pass_number
|
74
|
+
alias freq frequency
|
75
|
+
end
|
76
|
+
|
77
|
+
class Stat
|
78
|
+
# The path of the file system.
|
79
|
+
attr_reader :path
|
80
|
+
|
81
|
+
# The file system block size. MS Windows typically defaults to 4096.
|
82
|
+
attr_reader :block_size
|
83
|
+
|
84
|
+
# Fragment size. Meaningless at the moment.
|
85
|
+
attr_reader :fragment_size
|
86
|
+
|
87
|
+
# The total number of blocks available (used or unused) on the file
|
88
|
+
# system.
|
89
|
+
attr_reader :blocks
|
90
|
+
|
91
|
+
# The total number of unused blocks.
|
92
|
+
attr_reader :blocks_free
|
93
|
+
|
94
|
+
# The total number of unused blocks available to unprivileged
|
95
|
+
# processes. Identical to +blocks+ at the moment.
|
96
|
+
attr_reader :blocks_available
|
97
|
+
|
98
|
+
# Total number of files/inodes that can be created on the file system.
|
99
|
+
# This attribute is always nil on MS Windows.
|
100
|
+
attr_reader :files
|
101
|
+
|
102
|
+
# Total number of free files/inodes that can be created on the file
|
103
|
+
# system. This attribute is always nil on MS Windows.
|
104
|
+
attr_reader :files_free
|
105
|
+
|
106
|
+
# Total number of available files/inodes for unprivileged processes
|
107
|
+
# that can be created on the file system. This attribute is always
|
108
|
+
# nil on MS Windows.
|
109
|
+
attr_reader :files_available
|
110
|
+
|
111
|
+
# The file system volume id.
|
112
|
+
attr_reader :filesystem_id
|
113
|
+
|
114
|
+
# A bit mask of file system flags.
|
115
|
+
attr_reader :flags
|
116
|
+
|
117
|
+
# The maximum length of a file name permitted on the file system.
|
118
|
+
attr_reader :name_max
|
119
|
+
|
120
|
+
# The file system type, e.g. NTFS, FAT, etc.
|
121
|
+
attr_reader :base_type
|
122
|
+
|
123
|
+
alias inodes files
|
124
|
+
alias inodes_free files_free
|
125
|
+
alias inodes_available files_available
|
126
|
+
end
|
127
|
+
|
128
|
+
# Yields a Filesystem::Mount object for each volume on your system in
|
129
|
+
# block form. Returns an array of Filesystem::Mount objects in non-block
|
130
|
+
# form.
|
131
|
+
#
|
132
|
+
# Example:
|
133
|
+
#
|
134
|
+
# Sys::Filesystem.mounts{ |mount|
|
135
|
+
# p mt.name # => \\Device\\HarddiskVolume1
|
136
|
+
# p mt.mount_point # => C:\
|
137
|
+
# p mt.mount_time # => Thu Dec 18 20:12:08 -0700 2008
|
138
|
+
# p mt.mount_type # => NTFS
|
139
|
+
# p mt.options # => casepres,casesens,ro,unicode
|
140
|
+
# p mt.pass_number # => nil
|
141
|
+
# p mt.dump_freq # => nil
|
142
|
+
# }
|
143
|
+
#
|
144
|
+
# This method is a bit of a fudge for MS Windows in the name of interface
|
145
|
+
# compatibility because this method deals with volumes, not actual mount
|
146
|
+
# points. But, I believe it provides the sort of information many users
|
147
|
+
# want at a glance.
|
148
|
+
#
|
149
|
+
# The possible values for the +options+ and their meanings are as follows:
|
150
|
+
#
|
151
|
+
# casepres => The filesystem preserves the case of file names when it places a name on disk.
|
152
|
+
# casesens => The filesystem supports case-sensitive file names.
|
153
|
+
# compression => The filesystem supports file-based compression.
|
154
|
+
# namedstreams => The filesystem supports named streams.
|
155
|
+
# pacls => The filesystem preserves and enforces access control lists.
|
156
|
+
# ro => The filesystem is read-only.
|
157
|
+
# encryption => The filesystem supports the Encrypted File System (EFS).
|
158
|
+
# objids => The filesystem supports object identifiers.
|
159
|
+
# rpoints => The filesystem supports reparse points.
|
160
|
+
# sparse => The filesystem supports sparse files.
|
161
|
+
# unicode => The filesystem supports Unicode in file names as they appear on disk.
|
162
|
+
# compressed => The filesystem is compressed.
|
163
|
+
#
|
164
|
+
def self.mounts
|
165
|
+
buffer = 0.chr * MAXPATH
|
166
|
+
length = GetLogicalDriveStrings(buffer.size, buffer)
|
167
|
+
|
168
|
+
if length == 0
|
169
|
+
raise Error, get_last_error
|
170
|
+
end
|
171
|
+
|
172
|
+
mounts = block_given? ? nil : []
|
173
|
+
|
174
|
+
# Try again if it fails because the buffer is too small
|
175
|
+
if length > buffer.size
|
176
|
+
buffer = 0.chr * length
|
177
|
+
if GetLogicalDriveStrings(buffer.size, buffer) == 0
|
178
|
+
raise Error, get_last_error
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
boot_time = get_boot_time
|
183
|
+
|
184
|
+
drives = buffer.strip.split("\0")
|
185
|
+
|
186
|
+
drives.each{ |drive|
|
187
|
+
mount = Mount.new
|
188
|
+
volume = 0.chr * MAXPATH
|
189
|
+
fsname = 0.chr * MAXPATH
|
190
|
+
|
191
|
+
mount.instance_variable_set(:@mount_point, drive)
|
192
|
+
mount.instance_variable_set(:@mount_time, boot_time)
|
193
|
+
|
194
|
+
volume_serial_number = [0].pack('L')
|
195
|
+
max_component_length = [0].pack('L')
|
196
|
+
filesystem_flags = [0].pack('L')
|
197
|
+
|
198
|
+
bool = GetVolumeInformation(
|
199
|
+
drive,
|
200
|
+
volume,
|
201
|
+
volume.size,
|
202
|
+
volume_serial_number,
|
203
|
+
max_component_length,
|
204
|
+
filesystem_flags,
|
205
|
+
fsname,
|
206
|
+
fsname.size
|
207
|
+
)
|
208
|
+
|
209
|
+
# Skip unmounted floppies or cd-roms
|
210
|
+
unless bool
|
211
|
+
errnum = GetLastError()
|
212
|
+
if errnum == ERROR_NOT_READY
|
213
|
+
next
|
214
|
+
else
|
215
|
+
raise Error, get_last_error(errnum)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
filesystem_flags = filesystem_flags.unpack('L')[0]
|
220
|
+
|
221
|
+
name = 0.chr * MAXPATH
|
222
|
+
|
223
|
+
if QueryDosDevice(drive[0,2], name, name.size) == 0
|
224
|
+
raise Error, get_last_error
|
225
|
+
end
|
226
|
+
|
227
|
+
mount.instance_variable_set(:@name, name.strip)
|
228
|
+
mount.instance_variable_set(:@mount_type, fsname.strip)
|
229
|
+
mount.instance_variable_set(:@options, get_options(filesystem_flags))
|
230
|
+
|
231
|
+
if block_given?
|
232
|
+
yield mount
|
233
|
+
else
|
234
|
+
mounts << mount
|
235
|
+
end
|
236
|
+
}
|
237
|
+
|
238
|
+
mounts # Nil if the block form was used.
|
239
|
+
end
|
240
|
+
|
241
|
+
# Returns the mount point for the given +file+. For MS Windows this
|
242
|
+
# means the root of the path.
|
243
|
+
#
|
244
|
+
# Example:
|
245
|
+
#
|
246
|
+
# File.mount_point("C:\\Documents and Settings") # => "C:\\'
|
247
|
+
#
|
248
|
+
def self.mount_point(file)
|
249
|
+
file = file.tr(File::SEPARATOR, File::ALT_SEPARATOR)
|
250
|
+
PathStripToRoot(file)
|
251
|
+
file[/^[^\0]*/]
|
252
|
+
end
|
253
|
+
|
254
|
+
# Returns a Filesystem::Stat object that contains information about the
|
255
|
+
# +path+ file system.
|
256
|
+
#
|
257
|
+
# Examples:
|
258
|
+
#
|
259
|
+
# File.stat("C:\\")
|
260
|
+
# File.stat("C:\\Documents and Settings\\some_user")
|
261
|
+
#
|
262
|
+
def self.stat(path)
|
263
|
+
bytes_avail = [0].pack('Q')
|
264
|
+
bytes_free = [0].pack('Q')
|
265
|
+
total_bytes = [0].pack('Q')
|
266
|
+
|
267
|
+
unless GetDiskFreeSpaceEx(path, bytes_avail, total_bytes, bytes_free)
|
268
|
+
raise Error, get_last_error
|
269
|
+
end
|
270
|
+
|
271
|
+
bytes_avail = bytes_avail.unpack('Q').first
|
272
|
+
bytes_free = bytes_free.unpack('Q').first
|
273
|
+
total_bytes = total_bytes.unpack('Q').first
|
274
|
+
|
275
|
+
sectors = [0].pack('Q')
|
276
|
+
bytes = [0].pack('Q')
|
277
|
+
free = [0].pack('Q')
|
278
|
+
total = [0].pack('Q')
|
279
|
+
|
280
|
+
unless GetDiskFreeSpace(path, sectors, bytes, free, total)
|
281
|
+
raise Error, get_last_error
|
282
|
+
end
|
283
|
+
|
284
|
+
sectors = sectors.unpack('Q').first
|
285
|
+
bytes = bytes.unpack('Q').first
|
286
|
+
free = free.unpack('Q').first
|
287
|
+
total = total.unpack('Q').first
|
288
|
+
|
289
|
+
block_size = sectors * bytes
|
290
|
+
blocks_avail = total_bytes / block_size
|
291
|
+
blocks_free = bytes_free / block_size
|
292
|
+
|
293
|
+
vol_name = 0.chr * 260
|
294
|
+
base_type = 0.chr * 260
|
295
|
+
vol_serial = [0].pack('L')
|
296
|
+
name_max = [0].pack('L')
|
297
|
+
flags = [0].pack('L')
|
298
|
+
|
299
|
+
bool = GetVolumeInformation(
|
300
|
+
path,
|
301
|
+
vol_name,
|
302
|
+
vol_name.size,
|
303
|
+
vol_serial,
|
304
|
+
name_max,
|
305
|
+
flags,
|
306
|
+
base_type,
|
307
|
+
base_type.size
|
308
|
+
)
|
309
|
+
|
310
|
+
unless bool
|
311
|
+
raise Error, get_last_error
|
312
|
+
end
|
313
|
+
|
314
|
+
vol_serial = vol_serial.unpack('L').first
|
315
|
+
name_max = name_max.unpack('L').first
|
316
|
+
flags = flags.unpack('L').first
|
317
|
+
base_type = base_type[/^[^\0]*/]
|
318
|
+
|
319
|
+
stat_obj = Stat.new
|
320
|
+
stat_obj.instance_variable_set(:@path, path)
|
321
|
+
stat_obj.instance_variable_set(:@block_size, block_size)
|
322
|
+
stat_obj.instance_variable_set(:@blocks, blocks_avail)
|
323
|
+
stat_obj.instance_variable_set(:@blocks_available, blocks_avail)
|
324
|
+
stat_obj.instance_variable_set(:@blocks_free, blocks_free)
|
325
|
+
stat_obj.instance_variable_set(:@name_max, name_max)
|
326
|
+
stat_obj.instance_variable_set(:@base_type, base_type)
|
327
|
+
stat_obj.instance_variable_set(:@flags, flags)
|
328
|
+
stat_obj.instance_variable_set(:@filesystem_id, vol_serial)
|
329
|
+
|
330
|
+
stat_obj.freeze # Read-only object
|
331
|
+
end
|
332
|
+
|
333
|
+
private
|
334
|
+
|
335
|
+
# This method is used to get the boot time of the system, which is used
|
336
|
+
# for the mount_time attribute within the File.mounts method.
|
337
|
+
#
|
338
|
+
def self.get_boot_time
|
339
|
+
host = Socket.gethostname
|
340
|
+
cs = "winmgmts://#{host}/root/cimv2"
|
341
|
+
begin
|
342
|
+
wmi = WIN32OLE.connect(cs)
|
343
|
+
rescue WIN32OLERuntimeError => e
|
344
|
+
raise Error, e
|
345
|
+
else
|
346
|
+
query = 'select LastBootupTime from Win32_OperatingSystem'
|
347
|
+
results = wmi.ExecQuery(query)
|
348
|
+
results.each{ |ole|
|
349
|
+
time_array = Time.parse(ole.LastBootupTime.split('.').first)
|
350
|
+
return Time.mktime(*time_array)
|
351
|
+
}
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
# Private method that converts filesystem flags into a comma separated
|
356
|
+
# list of strings. The presentation is meant as a rough analogue to the
|
357
|
+
# way options are presented for Unix filesystems.
|
358
|
+
#
|
359
|
+
def self.get_options(flags)
|
360
|
+
str = ""
|
361
|
+
str << " casepres" if CASE_PRESERVED_NAMES & flags > 0
|
362
|
+
str << " casesens" if CASE_SENSITIVE_SEARCH & flags > 0
|
363
|
+
str << " compression" if FILE_COMPRESSION & flags > 0
|
364
|
+
str << " namedstreams" if NAMED_STREAMS & flags > 0
|
365
|
+
str << " pacls" if PERSISTENT_ACLS & flags > 0
|
366
|
+
str << " ro" if READ_ONLY_VOLUME & flags > 0
|
367
|
+
str << " encryption" if SUPPORTS_ENCRYPTION & flags > 0
|
368
|
+
str << " objids" if SUPPORTS_OBJECT_IDS & flags > 0
|
369
|
+
str << " rpoints" if SUPPORTS_REPARSE_POINTS & flags > 0
|
370
|
+
str << " sparse" if SUPPORTS_SPARSE_FILES & flags > 0
|
371
|
+
str << " unicode" if UNICODE_ON_DISK & flags > 0
|
372
|
+
str << " compressed" if VOLUME_IS_COMPRESSED & flags > 0
|
373
|
+
|
374
|
+
str.tr!(' ', ',')
|
375
|
+
str = str[1..-1] # Ignore the first comma
|
376
|
+
str
|
377
|
+
end
|
378
|
+
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
# Some convenient methods for converting bytes to kb, mb, and gb.
|
383
|
+
#
|
384
|
+
class Fixnum
|
385
|
+
# call-seq:
|
386
|
+
# <tt>fix</tt>.to_kb
|
387
|
+
#
|
388
|
+
# Returns +fix+ in terms of kilobytes.
|
389
|
+
def to_kb
|
390
|
+
self / 1024
|
391
|
+
end
|
392
|
+
|
393
|
+
# call-seq:
|
394
|
+
# <tt>fix</tt>.to_mb
|
395
|
+
#
|
396
|
+
# Returns +fix+ in terms of megabytes.
|
397
|
+
def to_mb
|
398
|
+
self / 1048576
|
399
|
+
end
|
400
|
+
|
401
|
+
# call-seq:
|
402
|
+
# <tt>fix</tt>.to_gb
|
403
|
+
#
|
404
|
+
# Returns +fix+ in terms of gigabytes.
|
405
|
+
def to_gb
|
406
|
+
self / 1073741824
|
407
|
+
end
|
408
|
+
end
|
data/sys-filesystem.gemspec
CHANGED
@@ -1,25 +1,24 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
|
3
|
-
Gem::Specification.new do |spec|
|
4
|
-
spec.name = 'sys-filesystem'
|
5
|
-
spec.version = '0.
|
6
|
-
spec.author = 'Daniel J. Berger'
|
7
|
-
spec.email = 'djberg96@gmail.com'
|
8
|
-
spec.homepage = 'http://www.rubyforge.org/projects/sysutils'
|
9
|
-
spec.platform = Gem::Platform::RUBY
|
10
|
-
spec.summary = 'A Ruby interface for getting file system information.'
|
11
|
-
spec.test_file = 'test/test_sys_filesystem.rb'
|
12
|
-
spec.
|
13
|
-
spec.
|
14
|
-
|
15
|
-
|
16
|
-
spec.
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'sys-filesystem'
|
5
|
+
spec.version = '1.0.0'
|
6
|
+
spec.author = 'Daniel J. Berger'
|
7
|
+
spec.email = 'djberg96@gmail.com'
|
8
|
+
spec.homepage = 'http://www.rubyforge.org/projects/sysutils'
|
9
|
+
spec.platform = Gem::Platform::RUBY
|
10
|
+
spec.summary = 'A Ruby interface for getting file system information.'
|
11
|
+
spec.test_file = 'test/test_sys_filesystem.rb'
|
12
|
+
spec.files = Dir['**/*'].reject{ |f| f.include?('git') }
|
13
|
+
spec.license = 'Artistic 2.0'
|
14
|
+
|
15
|
+
spec.extra_rdoc_files = ['CHANGES', 'README', 'MANIFEST']
|
16
|
+
spec.rubyforge_project = 'sysutils'
|
17
|
+
|
18
|
+
spec.add_development_dependency('test-unit', '>= 2.1.1')
|
19
|
+
|
20
|
+
spec.description = <<-EOF
|
21
|
+
The sys-filesystem library provides an interface for gathering filesystem
|
22
|
+
information, such as disk space and mount point data.
|
23
|
+
EOF
|
24
|
+
end
|
data/test/test_sys_filesystem.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
|
-
$LOAD_PATH.unshift File.dirname(File.expand_path(__FILE__))
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
require '
|
7
|
-
|
8
|
-
require 'test_sys_filesystem_unix'
|
9
|
-
end
|
1
|
+
$LOAD_PATH.unshift File.dirname(File.expand_path(__FILE__))
|
2
|
+
|
3
|
+
if File::ALT_SEPARATOR
|
4
|
+
require 'test_sys_filesystem_windows'
|
5
|
+
else
|
6
|
+
require 'test_sys_filesystem_unix'
|
7
|
+
end
|