sys-filesystem 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES CHANGED
@@ -1,4 +1,9 @@
1
- == 1.0.0 - ???
1
+ == 1.1.0 - 19-Jan-2013
2
+ * Converted the Windows source code to use FFI. Consequently, there is now
3
+ a single gem rather than separate gems for Windows and Unix.
4
+ * Revamped the Windows tests.
5
+
6
+ == 1.0.0 - 11-Jan-2012
2
7
  * Converted everything from C to FFI for the Unix flavors. The Windows
3
8
  source code remains untouched.
4
9
 
data/README CHANGED
@@ -4,11 +4,6 @@
4
4
  = Installation
5
5
  gem install sys-filesystem
6
6
 
7
- == Windows
8
- If the installation command above doesn't work try this:
9
-
10
- gem install sys-filesystem --platform x86-mingw32
11
-
12
7
  = Synopsis
13
8
  require 'sys/filesystem'
14
9
  include Sys
@@ -41,12 +36,8 @@
41
36
  puts Filesystem.mount_point('/home/djberge/some_file.txt') => '/home'
42
37
 
43
38
  = Notes
44
- === MS Windows
45
- This is a pure Ruby implementation using the windows-pr library, which in
46
- turn wraps native Windows functions.
47
-
48
- === UNIX
49
- This is a C extension that wraps statvfs, etc.
39
+ This is a pure Ruby implementation that uses FFI. This means it should work
40
+ with JRuby, too.
50
41
 
51
42
  = Sample code
52
43
  Run 'rake example' if you want to see a basic sample run. The actual code
@@ -54,7 +45,7 @@
54
45
 
55
46
  = Known Bugs
56
47
  None that I'm aware of. Please report bugs on the project page at
57
- http://www.rubyforge.org/projects/sysutils.
48
+ https://github.com/djberg96/sys-filesystem
58
49
 
59
50
  = Future Plans
60
51
  Suggestions welcome.
@@ -65,13 +56,13 @@
65
56
 
66
57
  Park Heesob, for implementation and API ideas for the MS Windows version.
67
58
 
68
- Nobuyoshi Miyokawa, for adding FreeBSD and OS X support.
59
+ Nobuyoshi Miyokawa, for adding the original FreeBSD and OS X support.
69
60
 
70
61
  = License
71
62
  Artistic 2.0
72
63
 
73
64
  = Copyright
74
- (C) 2003-2010 Daniel J. Berger
65
+ (C) 2003-2013 Daniel J. Berger
75
66
  All Rights Reserved
76
67
 
77
68
  = Warranty
data/Rakefile CHANGED
@@ -26,16 +26,6 @@ namespace :gem do
26
26
  desc "Build the sys-filesystem gem"
27
27
  task :create do |t|
28
28
  spec = eval(IO.read('sys-filesystem.gemspec'))
29
-
30
- if File::ALT_SEPARATOR
31
- spec.add_dependency('windows-pr', '>= 1.0.5')
32
- spec.platform = Gem::Platform::CURRENT
33
- spec.platform.cpu = 'universal'
34
- spec.platform.version = nil
35
- else
36
- spec.add_dependency('ffi', '>= 1.0.0')
37
- end
38
-
39
29
  Gem::Builder.new(spec).build
40
30
  end
41
31
 
@@ -10,7 +10,7 @@ module Sys
10
10
  ffi_lib(FFI::Library::LIBC)
11
11
 
12
12
  # The version of the sys-filesystem library.
13
- VERSION = '1.0.0'
13
+ VERSION = '1.1.0'
14
14
 
15
15
  private
16
16
 
@@ -1,9 +1,7 @@
1
- require 'windows/error'
2
- require 'windows/path'
3
- require 'windows/filesystem'
4
- require 'windows/volume'
5
- require 'windows/handle'
6
- require 'windows/limits'
1
+ require File.join(File.dirname(__FILE__), 'filesystem', 'constants')
2
+ require File.join(File.dirname(__FILE__), 'filesystem', 'functions')
3
+ require File.join(File.dirname(__FILE__), 'filesystem', 'helper')
4
+
7
5
  require 'socket'
8
6
  require 'win32ole'
9
7
  require 'date'
@@ -12,397 +10,385 @@ require 'time'
12
10
  # The Sys module serves as a namespace only.
13
11
  module Sys
14
12
 
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
13
+ # The Filesystem class encapsulates information about your filesystem.
14
+ class Filesystem
15
+ include Sys::Filesystem::Constants
16
+ extend Sys::Filesystem::Functions
101
17
 
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
18
+ # Error typically raised if any of the Sys::Filesystem methods fail.
19
+ class Error < StandardError; end
20
+
21
+ # The version of the sys-filesystem library.
22
+ VERSION = '1.1.0'
23
+
24
+ class Mount
25
+ # The name of the volume. This is the device mapping.
26
+ attr_reader :name
27
+
28
+ # The last time the volume was mounted. For MS Windows this equates
29
+ # to your system's boot time.
30
+ attr_reader :mount_time
31
+
32
+ # The type of mount, e.g. NTFS, UDF, etc.
33
+ attr_reader :mount_type
34
+
35
+ # The volume mount point, e.g. 'C:\'
36
+ attr_reader :mount_point
37
+
38
+ # Various comma separated options that reflect the volume's features
39
+ attr_reader :options
40
+
41
+ # Always nil on MS Windows. Provided for interface compatibility only.
42
+ attr_reader :pass_number
43
+
44
+ # Always nil on MS Windows. Provided for interface compatibility only.
45
+ attr_reader :frequency
46
+
47
+ alias fsname name
48
+ alias dir mount_point
49
+ alias opts options
50
+ alias passno pass_number
51
+ alias freq frequency
52
+ end
53
+
54
+ class Stat
55
+ # The path of the file system.
56
+ attr_reader :path
57
+
58
+ # The file system block size. MS Windows typically defaults to 4096.
59
+ attr_reader :block_size
60
+
61
+ # Fragment size. Meaningless at the moment.
62
+ attr_reader :fragment_size
63
+
64
+ # The total number of blocks available (used or unused) on the file
65
+ # system.
66
+ attr_reader :blocks
67
+
68
+ # The total number of unused blocks.
69
+ attr_reader :blocks_free
70
+
71
+ # The total number of unused blocks available to unprivileged
72
+ # processes. Identical to +blocks+ at the moment.
73
+ attr_reader :blocks_available
74
+
75
+ # Total number of files/inodes that can be created on the file system.
76
+ # This attribute is always nil on MS Windows.
77
+ attr_reader :files
78
+
79
+ # Total number of free files/inodes that can be created on the file
80
+ # system. This attribute is always nil on MS Windows.
81
+ attr_reader :files_free
82
+
83
+ # Total number of available files/inodes for unprivileged processes
84
+ # that can be created on the file system. This attribute is always
85
+ # nil on MS Windows.
86
+ attr_reader :files_available
87
+
88
+ # The file system volume id.
89
+ attr_reader :filesystem_id
90
+
91
+ # A bit mask of file system flags.
92
+ attr_reader :flags
93
+
94
+ # The maximum length of a file name permitted on the file system.
95
+ attr_reader :name_max
96
+
97
+ # The file system type, e.g. NTFS, FAT, etc.
98
+ attr_reader :base_type
99
+
100
+ alias inodes files
101
+ alias inodes_free files_free
102
+ alias inodes_available files_available
103
+ end
104
+
105
+ # Yields a Filesystem::Mount object for each volume on your system in
106
+ # block form. Returns an array of Filesystem::Mount objects in non-block
107
+ # form.
108
+ #
109
+ # Example:
110
+ #
111
+ # Sys::Filesystem.mounts{ |mount|
112
+ # p mt.name # => \\Device\\HarddiskVolume1
113
+ # p mt.mount_point # => C:\
114
+ # p mt.mount_time # => Thu Dec 18 20:12:08 -0700 2008
115
+ # p mt.mount_type # => NTFS
116
+ # p mt.options # => casepres,casesens,ro,unicode
117
+ # p mt.pass_number # => nil
118
+ # p mt.dump_freq # => nil
119
+ # }
120
+ #
121
+ # This method is a bit of a fudge for MS Windows in the name of interface
122
+ # compatibility because this method deals with volumes, not actual mount
123
+ # points. But, I believe it provides the sort of information many users
124
+ # want at a glance.
125
+ #
126
+ # The possible values for the +options+ and their meanings are as follows:
127
+ #
128
+ # casepres => The filesystem preserves the case of file names when it places a name on disk.
129
+ # casesens => The filesystem supports case-sensitive file names.
130
+ # compression => The filesystem supports file-based compression.
131
+ # namedstreams => The filesystem supports named streams.
132
+ # pacls => The filesystem preserves and enforces access control lists.
133
+ # ro => The filesystem is read-only.
134
+ # encryption => The filesystem supports the Encrypted File System (EFS).
135
+ # objids => The filesystem supports object identifiers.
136
+ # rpoints => The filesystem supports reparse points.
137
+ # sparse => The filesystem supports sparse files.
138
+ # unicode => The filesystem supports Unicode in file names as they appear on disk.
139
+ # compressed => The filesystem is compressed.
140
+ #
141
+ #--
142
+ # I couldn't really find a good reason to use the wide functions for this
143
+ # method. If you have one, patches welcome.
144
+ #
145
+ def self.mounts
146
+ # First call, get needed buffer size
147
+ buffer = 0.chr
148
+ length = GetLogicalDriveStringsA(buffer.size, buffer)
149
+
150
+ if length == 0
151
+ raise SystemCallError.new('GetLogicalDriveStrings', FFI.errno)
152
+ else
153
+ buffer = 0.chr * length
154
+ end
119
155
 
120
- # The file system type, e.g. NTFS, FAT, etc.
121
- attr_reader :base_type
156
+ mounts = block_given? ? nil : []
122
157
 
123
- alias inodes files
124
- alias inodes_free files_free
125
- alias inodes_available files_available
158
+ # Try again with new buffer size
159
+ if GetLogicalDriveStringsA(buffer.size, buffer) == 0
160
+ raise SystemCallError.new('GetLogicalDriveStrings', FFI.errno)
126
161
  end
127
162
 
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.
163
+ drives = buffer.split(0.chr)
164
+
165
+ boot_time = get_boot_time
166
+
167
+ drives.each{ |drive|
168
+ mount = Mount.new
169
+ volume = FFI::MemoryPointer.new(:char, MAXPATH)
170
+ fsname = FFI::MemoryPointer.new(:char, MAXPATH)
171
+
172
+ mount.instance_variable_set(:@mount_point, drive)
173
+ mount.instance_variable_set(:@mount_time, boot_time)
174
+
175
+ volume_serial_number = FFI::MemoryPointer.new(:ulong)
176
+ max_component_length = FFI::MemoryPointer.new(:ulong)
177
+ filesystem_flags = FFI::MemoryPointer.new(:ulong)
178
+
179
+ bool = GetVolumeInformationA(
180
+ drive,
181
+ volume,
182
+ volume.size,
183
+ volume_serial_number,
184
+ max_component_length,
185
+ filesystem_flags,
186
+ fsname,
187
+ fsname.size
188
+ )
189
+
190
+ # Skip unmounted floppies or cd-roms, or inaccessible drives
191
+ unless bool
192
+ if [5,21].include?(FFI.errno) # ERROR_NOT_READY or ERROR_ACCESS_DENIED
193
+ next
194
+ else
195
+ raise SystemCallError.new('GetVolumeInformation', FFI.errno)
196
+ end
197
+ end
198
+
199
+ filesystem_flags = filesystem_flags.read_ulong
200
+ fsname = fsname.read_string
201
+
202
+ name = 0.chr * MAXPATH
203
+
204
+ if QueryDosDeviceA(drive[0,2], name, name.size) == 0
205
+ raise SystemCallError.new('QueryDosDevice', FFI.errno)
206
+ end
207
+
208
+ mount.instance_variable_set(:@name, name.strip)
209
+ mount.instance_variable_set(:@mount_type, fsname)
210
+ mount.instance_variable_set(:@options, get_options(filesystem_flags))
211
+
212
+ if block_given?
213
+ yield mount
214
+ else
215
+ mounts << mount
216
+ end
217
+ }
218
+
219
+ mounts # Nil if the block form was used.
220
+ end
221
+
222
+ # Returns the mount point for the given +file+. For MS Windows this
223
+ # means the root of the path.
224
+ #
225
+ # Example:
226
+ #
227
+ # File.mount_point("C:\\Documents and Settings") # => "C:\\'
228
+ #
229
+ def self.mount_point(file)
230
+ wfile = FFI::MemoryPointer.from_string(file.wincode)
231
+
232
+ if PathStripToRootW(wfile)
233
+ wfile.read_string(wfile.size).split("\000\000").first.tr(0.chr, '')
234
+ else
235
+ nil
239
236
  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]*/]
237
+ end
238
+
239
+ # Returns a Filesystem::Stat object that contains information about the
240
+ # +path+ file system.
241
+ #
242
+ # Examples:
243
+ #
244
+ # File.stat("C:\\")
245
+ # File.stat("C:\\Documents and Settings\\some_user")
246
+ #
247
+ def self.stat(path)
248
+ bytes_avail = FFI::MemoryPointer.new(:ulong_long)
249
+ bytes_free = FFI::MemoryPointer.new(:ulong_long)
250
+ total_bytes = FFI::MemoryPointer.new(:ulong_long)
251
+
252
+ wpath = path.wincode
253
+
254
+ unless GetDiskFreeSpaceExW(wpath, bytes_avail, total_bytes, bytes_free)
255
+ raise SystemCallError.new('GetDiskFreeSpaceEx', FFI.errno)
252
256
  end
253
257
 
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
258
+ bytes_avail = bytes_avail.read_ulong_long
259
+ bytes_free = bytes_free.read_ulong_long
260
+ total_bytes = total_bytes.read_ulong_long
332
261
 
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
262
+ sectors = FFI::MemoryPointer.new(:ulong_long)
263
+ bytes = FFI::MemoryPointer.new(:ulong_long)
264
+ free = FFI::MemoryPointer.new(:ulong_long)
265
+ total = FFI::MemoryPointer.new(:ulong_long)
266
+
267
+ unless GetDiskFreeSpaceW(wpath, sectors, bytes, free, total)
268
+ raise SystemCallError.new('GetDiskFreeSpace', FFI.errno)
353
269
  end
354
270
 
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
271
+ sectors = sectors.read_ulong_long
272
+ bytes = bytes.read_ulong_long
273
+ free = free.read_ulong_long
274
+ total = total.read_ulong_long
275
+
276
+ block_size = sectors * bytes
277
+ blocks_avail = total_bytes / block_size
278
+ blocks_free = bytes_free / block_size
279
+
280
+ vol_name = FFI::MemoryPointer.new(:char, MAXPATH)
281
+ base_type = FFI::MemoryPointer.new(:char, MAXPATH)
282
+ vol_serial = FFI::MemoryPointer.new(:ulong)
283
+ name_max = FFI::MemoryPointer.new(:ulong)
284
+ flags = FFI::MemoryPointer.new(:ulong)
285
+
286
+ bool = GetVolumeInformationW(
287
+ wpath,
288
+ vol_name,
289
+ vol_name.size,
290
+ vol_serial,
291
+ name_max,
292
+ flags,
293
+ base_type,
294
+ base_type.size
295
+ )
296
+
297
+ unless bool
298
+ raise SystemCallError.new('GetVolumInformation', FFI.errno)
377
299
  end
378
300
 
379
- end
301
+ vol_serial = vol_serial.read_ulong
302
+ name_max = name_max.read_ulong
303
+ flags = flags.read_ulong
304
+ base_type = base_type.read_string(base_type.size).tr(0.chr, '')
305
+
306
+ stat_obj = Stat.new
307
+ stat_obj.instance_variable_set(:@path, path)
308
+ stat_obj.instance_variable_set(:@block_size, block_size)
309
+ stat_obj.instance_variable_set(:@blocks, blocks_avail)
310
+ stat_obj.instance_variable_set(:@blocks_available, blocks_avail)
311
+ stat_obj.instance_variable_set(:@blocks_free, blocks_free)
312
+ stat_obj.instance_variable_set(:@name_max, name_max)
313
+ stat_obj.instance_variable_set(:@base_type, base_type)
314
+ stat_obj.instance_variable_set(:@flags, flags)
315
+ stat_obj.instance_variable_set(:@filesystem_id, vol_serial)
316
+
317
+ stat_obj.freeze # Read-only object
318
+ end
319
+
320
+ private
321
+
322
+ # This method is used to get the boot time of the system, which is used
323
+ # for the mount_time attribute within the File.mounts method.
324
+ #
325
+ def self.get_boot_time
326
+ host = Socket.gethostname
327
+ cs = "winmgmts://#{host}/root/cimv2"
328
+ begin
329
+ wmi = WIN32OLE.connect(cs)
330
+ rescue WIN32OLERuntimeError => e
331
+ raise Error, e
332
+ else
333
+ query = 'select LastBootupTime from Win32_OperatingSystem'
334
+ results = wmi.ExecQuery(query)
335
+ results.each{ |ole|
336
+ time_array = Time.parse(ole.LastBootupTime.split('.').first)
337
+ return Time.mktime(*time_array)
338
+ }
339
+ end
340
+ end
341
+
342
+ # Private method that converts filesystem flags into a comma separated
343
+ # list of strings. The presentation is meant as a rough analogue to the
344
+ # way options are presented for Unix filesystems.
345
+ #
346
+ def self.get_options(flags)
347
+ str = ""
348
+ str << " casepres" if CASE_PRESERVED_NAMES & flags > 0
349
+ str << " casesens" if CASE_SENSITIVE_SEARCH & flags > 0
350
+ str << " compression" if FILE_COMPRESSION & flags > 0
351
+ str << " namedstreams" if NAMED_STREAMS & flags > 0
352
+ str << " pacls" if PERSISTENT_ACLS & flags > 0
353
+ str << " ro" if READ_ONLY_VOLUME & flags > 0
354
+ str << " encryption" if SUPPORTS_ENCRYPTION & flags > 0
355
+ str << " objids" if SUPPORTS_OBJECT_IDS & flags > 0
356
+ str << " rpoints" if SUPPORTS_REPARSE_POINTS & flags > 0
357
+ str << " sparse" if SUPPORTS_SPARSE_FILES & flags > 0
358
+ str << " unicode" if UNICODE_ON_DISK & flags > 0
359
+ str << " compressed" if VOLUME_IS_COMPRESSED & flags > 0
360
+
361
+ str.tr!(' ', ',')
362
+ str = str[1..-1] # Ignore the first comma
363
+ str
364
+ end
365
+ end
380
366
  end
381
367
 
382
368
  # Some convenient methods for converting bytes to kb, mb, and gb.
383
- #
369
+ #
384
370
  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
371
+ # call-seq:
372
+ # <tt>fix</tt>.to_kb
373
+ #
374
+ # Returns +fix+ in terms of kilobytes.
375
+ def to_kb
376
+ self / 1024
377
+ end
378
+
379
+ # call-seq:
380
+ # <tt>fix</tt>.to_mb
381
+ #
382
+ # Returns +fix+ in terms of megabytes.
383
+ def to_mb
384
+ self / 1048576
385
+ end
386
+
387
+ # call-seq:
388
+ # <tt>fix</tt>.to_gb
389
+ #
390
+ # Returns +fix+ in terms of gigabytes.
391
+ def to_gb
392
+ self / 1073741824
393
+ end
408
394
  end