sys-filesystem 1.0.0 → 1.1.0

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 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