sys-filesystem 0.3.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -2,20 +2,19 @@ require 'rubygems'
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = 'sys-filesystem'
5
- spec.version = '0.3.4'
5
+ spec.version = '1.0.0'
6
6
  spec.author = 'Daniel J. Berger'
7
7
  spec.email = 'djberg96@gmail.com'
8
8
  spec.homepage = 'http://www.rubyforge.org/projects/sysutils'
9
9
  spec.platform = Gem::Platform::RUBY
10
10
  spec.summary = 'A Ruby interface for getting file system information.'
11
11
  spec.test_file = 'test/test_sys_filesystem.rb'
12
- spec.has_rdoc = true
13
12
  spec.files = Dir['**/*'].reject{ |f| f.include?('git') }
14
13
  spec.license = 'Artistic 2.0'
15
14
 
16
15
  spec.extra_rdoc_files = ['CHANGES', 'README', 'MANIFEST']
17
16
  spec.rubyforge_project = 'sysutils'
18
-
17
+
19
18
  spec.add_development_dependency('test-unit', '>= 2.1.1')
20
19
 
21
20
  spec.description = <<-EOF
@@ -1,8 +1,6 @@
1
1
  $LOAD_PATH.unshift File.dirname(File.expand_path(__FILE__))
2
2
 
3
- require 'rbconfig'
4
-
5
- if Config::CONFIG['host_os'] =~ /mswin32|mingw|cygwin|windows|dos/i
3
+ if File::ALT_SEPARATOR
6
4
  require 'test_sys_filesystem_windows'
7
5
  else
8
6
  require 'test_sys_filesystem_unix'
@@ -13,10 +13,10 @@ include Sys
13
13
 
14
14
  class TC_Sys_Filesystem_Unix < Test::Unit::TestCase
15
15
  def self.startup
16
- @@solaris = Config::CONFIG['host_os'] =~ /solaris/i
17
- @@linux = Config::CONFIG['host_os'] =~ /linux/i
18
- @@freebsd = Config::CONFIG['host_os'] =~ /freebsd/i
19
- @@darwin = Config::CONFIG['host_os'] =~ /darwin/i
16
+ @@solaris = RbConfig::CONFIG['host_os'] =~ /solaris/i
17
+ @@linux = RbConfig::CONFIG['host_os'] =~ /linux/i
18
+ @@freebsd = RbConfig::CONFIG['host_os'] =~ /freebsd/i
19
+ @@darwin = RbConfig::CONFIG['host_os'] =~ /darwin/i
20
20
  end
21
21
 
22
22
  def setup
@@ -28,7 +28,7 @@ class TC_Sys_Filesystem_Unix < Test::Unit::TestCase
28
28
  end
29
29
 
30
30
  def test_version
31
- assert_equal('0.3.4', Filesystem::VERSION)
31
+ assert_equal('1.0.0', Filesystem::VERSION)
32
32
  end
33
33
 
34
34
  def test_stat_path
@@ -211,7 +211,8 @@ class TC_Sys_Filesystem_Unix < Test::Unit::TestCase
211
211
  end
212
212
 
213
213
  def test_mount_dump_frequency
214
- omit_if(@@solaris || @@freebsd || @@darwin, 'dump_frequency test skipped on this platform')
214
+ msg = 'dump_frequency test skipped on this platform'
215
+ omit_if(@@solaris || @@freebsd || @@darwin, msg)
215
216
  assert_respond_to(@mnt, :dump_frequency)
216
217
  assert_kind_of(Fixnum, @mnt.dump_frequency)
217
218
  end
@@ -222,7 +223,8 @@ class TC_Sys_Filesystem_Unix < Test::Unit::TestCase
222
223
  end
223
224
 
224
225
  def test_mount_pass_number
225
- omit_if(@@solaris || @@freebsd || @@darwin, 'pass_number test skipped on this platform')
226
+ msg = 'pass_number test skipped on this platform'
227
+ omit_if(@@solaris || @@freebsd || @@darwin, msg)
226
228
  assert_respond_to(@mnt, :pass_number)
227
229
  assert_kind_of(Fixnum, @mnt.pass_number)
228
230
  end
@@ -238,6 +240,12 @@ class TC_Sys_Filesystem_Unix < Test::Unit::TestCase
238
240
  assert_kind_of(String, Filesystem.mount_point(Dir.pwd))
239
241
  end
240
242
 
243
+ def test_ffi_functions_are_private
244
+ methods = Filesystem.methods(false).map{ |e| e.to_s }
245
+ assert_false(Filesystem.methods.include?('statvfs'))
246
+ assert_false(Filesystem.methods.include?('strerror'))
247
+ end
248
+
241
249
  def teardown
242
250
  @dir = nil
243
251
  @stat = nil