cz_system_info 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+ require 'rbconfig'
5
+
6
+ module CzSystemInfo
7
+ class Filesystem
8
+ module Structs
9
+ # The Statfs struct is a subclass of FFI::Struct that corresponds to a struct statfs.
10
+ class Statfs < FFI::Struct
11
+ # Private method that will determine the layout of the struct on Linux.
12
+ def self.linux64?
13
+ if RUBY_PLATFORM == 'java'
14
+ ENV_JAVA['sun.arch.data.model'].to_i == 64
15
+ else
16
+ RbConfig::CONFIG['host_os'] =~ /linux/i &&
17
+ (RbConfig::CONFIG['arch'] =~ /64/ || RbConfig::CONFIG['DEFS'] =~ /64/)
18
+ end
19
+ end
20
+
21
+ private_class_method :linux64?
22
+
23
+ # FreeBSD 12.0 MNAMELEN from 88 => 1024.
24
+ MNAMELEN =
25
+ if RbConfig::CONFIG['host_os'] =~ /freebsd(.*)/i
26
+ Regexp.last_match(1).to_f < 12.0 ? 88 : 1024
27
+ else
28
+ 88
29
+ end
30
+
31
+ case RbConfig::CONFIG['host_os']
32
+ when /bsd/i
33
+ layout(
34
+ :f_version, :uint32,
35
+ :f_type, :uint32,
36
+ :f_flags, :uint64,
37
+ :f_bsize, :uint64,
38
+ :f_iosize, :int64,
39
+ :f_blocks, :uint64,
40
+ :f_bfree, :uint64,
41
+ :f_bavail, :int64,
42
+ :f_files, :uint64,
43
+ :f_ffree, :uint64,
44
+ :f_syncwrites, :uint64,
45
+ :f_asyncwrites, :uint64,
46
+ :f_syncreads, :uint64,
47
+ :f_asyncreads, :uint64,
48
+ :f_spare, [:uint64, 10],
49
+ :f_namemax, :uint32,
50
+ :f_owner, :int32,
51
+ :f_fsid, [:int32, 2],
52
+ :f_charspare, [:char, 80],
53
+ :f_fstypename, [:char, 16],
54
+ :f_mntfromname, [:char, MNAMELEN],
55
+ :f_mntonname, [:char, MNAMELEN]
56
+ )
57
+ when /linux/i
58
+ if linux64?
59
+ layout(
60
+ :f_type, :ulong,
61
+ :f_bsize, :ulong,
62
+ :f_blocks, :uint64,
63
+ :f_bfree, :uint64,
64
+ :f_bavail, :uint64,
65
+ :f_files, :uint64,
66
+ :f_ffree, :uint64,
67
+ :f_fsid, [:int, 2],
68
+ :f_namelen, :ulong,
69
+ :f_frsize, :ulong,
70
+ :f_flags, :ulong,
71
+ :f_spare, [:ulong, 4]
72
+ )
73
+ else
74
+ layout(
75
+ :f_type, :ulong,
76
+ :f_bsize, :ulong,
77
+ :f_blocks, :uint32,
78
+ :f_bfree, :uint32,
79
+ :f_bavail, :uint32,
80
+ :f_files, :uint32,
81
+ :f_ffree, :uint32,
82
+ :f_fsid, [:int, 2],
83
+ :f_namelen, :ulong,
84
+ :f_frsize, :ulong,
85
+ :f_flags, :ulong,
86
+ :f_spare, [:ulong, 4]
87
+ )
88
+ end
89
+ else
90
+ layout(
91
+ :f_bsize, :uint32,
92
+ :f_iosize, :int32,
93
+ :f_blocks, :uint64,
94
+ :f_bfree, :uint64,
95
+ :f_bavail, :uint64,
96
+ :f_files, :uint64,
97
+ :f_ffree, :uint64,
98
+ :f_fsid, [:int32, 2],
99
+ :f_owner, :int32,
100
+ :f_type, :uint32,
101
+ :f_flags, :uint32,
102
+ :f_fssubtype, :uint32,
103
+ :f_fstypename, [:char, 16],
104
+ :f_mntonname, [:char, 1024],
105
+ :f_mntfromname, [:char, 1024],
106
+ :f_reserved, [:uint32, 8]
107
+ )
108
+ end
109
+ end
110
+
111
+ # The Statvfs struct represents struct statvfs from CzSystemInfo/statvfs.h.
112
+ class Statvfs < FFI::Struct
113
+ if RbConfig::CONFIG['host_os'] =~ /darwin|osx|mach/i
114
+ layout(
115
+ :f_bsize, :ulong,
116
+ :f_frsize, :ulong,
117
+ :f_blocks, :uint,
118
+ :f_bfree, :uint,
119
+ :f_bavail, :uint,
120
+ :f_files, :uint,
121
+ :f_ffree, :uint,
122
+ :f_favail, :uint,
123
+ :f_fsid, :ulong,
124
+ :f_flag, :ulong,
125
+ :f_namemax, :ulong
126
+ )
127
+ elsif RbConfig::CONFIG['host'] =~ /bsd/i
128
+ layout(
129
+ :f_bavail, :uint64,
130
+ :f_bfree, :uint64,
131
+ :f_blocks, :uint64,
132
+ :f_favail, :uint64,
133
+ :f_ffree, :uint64,
134
+ :f_files, :uint64,
135
+ :f_bsize, :ulong,
136
+ :f_flag, :ulong,
137
+ :f_frsize, :ulong,
138
+ :f_fsid, :ulong,
139
+ :f_namemax, :ulong
140
+ )
141
+ elsif RbConfig::CONFIG['host'] =~ /sunos|solaris/i
142
+ layout(
143
+ :f_bsize, :ulong,
144
+ :f_frsize, :ulong,
145
+ :f_blocks, :uint64_t,
146
+ :f_bfree, :uint64_t,
147
+ :f_bavail, :uint64_t,
148
+ :f_files, :uint64_t,
149
+ :f_ffree, :uint64_t,
150
+ :f_favail, :uint64_t,
151
+ :f_fsid, :ulong,
152
+ :f_basetype, [:char, 16],
153
+ :f_flag, :ulong,
154
+ :f_namemax, :ulong,
155
+ :f_fstr, [:char, 32],
156
+ :f_filler, [:ulong, 16]
157
+ )
158
+ elsif RbConfig::CONFIG['host'] =~ /i686/i
159
+ layout(
160
+ :f_bsize, :ulong,
161
+ :f_frsize, :ulong,
162
+ :f_blocks, :uint,
163
+ :f_bfree, :uint,
164
+ :f_bavail, :uint,
165
+ :f_files, :uint,
166
+ :f_ffree, :uint,
167
+ :f_favail, :uint,
168
+ :f_fsid, :ulong,
169
+ :f_unused, :int,
170
+ :f_flag, :ulong,
171
+ :f_namemax, :ulong,
172
+ :f_spare, [:int, 6]
173
+ )
174
+ else
175
+ layout(
176
+ :f_bsize, :ulong,
177
+ :f_frsize, :ulong,
178
+ :f_blocks, :uint64,
179
+ :f_bfree, :uint64,
180
+ :f_bavail, :uint64,
181
+ :f_files, :uint64,
182
+ :f_ffree, :uint64,
183
+ :f_favail, :uint64,
184
+ :f_fsid, :ulong,
185
+ :f_flag, :ulong,
186
+ :f_namemax, :ulong,
187
+ :f_spare, [:int, 6]
188
+ )
189
+ end
190
+ end
191
+
192
+ # The Mnttab struct represents struct mnnttab from CzSystemInfo/mnttab.h on Solaris.
193
+ class Mnttab < FFI::Struct
194
+ layout(
195
+ :mnt_special, :string,
196
+ :mnt_mountp, :string,
197
+ :mnt_fstype, :string,
198
+ :mnt_mntopts, :string,
199
+ :mnt_time, :string
200
+ )
201
+ end
202
+
203
+ # The Mntent struct represents struct mntent from CzSystemInfo/mount.h on Unix.
204
+ class Mntent < FFI::Struct
205
+ layout(
206
+ :mnt_fsname, :string,
207
+ :mnt_dir, :string,
208
+ :mnt_type, :string,
209
+ :mnt_opts, :string,
210
+ :mnt_freq, :int,
211
+ :mnt_passno, :int
212
+ )
213
+ end
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,450 @@
1
+ require_relative 'filesystem/constants'
2
+ require_relative 'filesystem/structs'
3
+ require_relative 'filesystem/functions'
4
+
5
+ # The CzSystemInfo module serves as a namespace only.
6
+ module CzSystemInfo
7
+ # The Filesystem class serves as an abstract base class. Its methods
8
+ # return objects of other types. Do not instantiate.
9
+ class Filesystem
10
+ include CzSystemInfo::Filesystem::Constants
11
+ include CzSystemInfo::Filesystem::Structs
12
+ extend CzSystemInfo::Filesystem::Functions
13
+
14
+ private_class_method :new
15
+
16
+ # Readable versions of constant names
17
+ OPT_NAMES = {
18
+ MNT_RDONLY => 'read-only',
19
+ MNT_SYNCHRONOUS => 'synchronous',
20
+ MNT_NOEXEC => 'noexec',
21
+ MNT_NOSUID => 'nosuid',
22
+ MNT_NODEV => 'nodev',
23
+ MNT_UNION => 'union',
24
+ MNT_ASYNC => 'asynchronous',
25
+ MNT_CPROTECT => 'content-protection',
26
+ MNT_EXPORTED => 'exported',
27
+ MNT_QUARANTINE => 'quarantined',
28
+ MNT_LOCAL => 'local',
29
+ MNT_QUOTA => 'quotas',
30
+ MNT_ROOTFS => 'rootfs',
31
+ MNT_DONTBROWSE => 'nobrowse',
32
+ MNT_IGNORE_OWNERSHIP => 'noowners',
33
+ MNT_AUTOMOUNTED => 'automounted',
34
+ MNT_JOURNALED => 'journaled',
35
+ MNT_NOUSERXATTR => 'nouserxattr',
36
+ MNT_DEFWRITE => 'defwrite',
37
+ MNT_NOATIME => 'noatime'
38
+ }.freeze
39
+
40
+ private_constant :OPT_NAMES
41
+
42
+ # File used to read mount informtion from.
43
+ if File.exist?('/etc/mtab')
44
+ MOUNT_FILE = '/etc/mtab'.freeze
45
+ elsif File.exist?('/etc/mnttab')
46
+ MOUNT_FILE = '/etc/mnttab'.freeze
47
+ elsif File.exist?('/proc/mounts')
48
+ MOUNT_FILE = '/proc/mounts'.freeze
49
+ else
50
+ MOUNT_FILE = 'getmntinfo'.freeze
51
+ end
52
+
53
+ private_constant :MOUNT_FILE
54
+
55
+ # The error raised if any of the Filesystem methods fail.
56
+ class Error < StandardError; end
57
+
58
+ # Stat objects are returned by the CzSystemInfo::Filesystem.stat method.
59
+ class Stat
60
+ # Read-only filesystem
61
+ RDONLY = 1
62
+
63
+ # Filesystem does not support suid or sgid semantics.
64
+ NOSUID = 2
65
+
66
+ # Filesystem does not truncate file names longer than +name_max+.
67
+ NOTRUNC = 3
68
+
69
+ # The path of the filesystem.
70
+ attr_accessor :path
71
+
72
+ # The preferred system block size.
73
+ attr_accessor :block_size
74
+
75
+ # The fragment size, i.e. fundamental filesystem block size.
76
+ attr_accessor :fragment_size
77
+
78
+ # The total number of +fragment_size+ blocks in the filesystem.
79
+ attr_accessor :blocks
80
+
81
+ # The total number of free blocks in the filesystem.
82
+ attr_accessor :blocks_free
83
+
84
+ # The number of free blocks available to unprivileged processes.
85
+ attr_accessor :blocks_available
86
+
87
+ # The total number of files/inodes that can be created.
88
+ attr_accessor :files
89
+
90
+ # The total number of files/inodes on the filesystem.
91
+ attr_accessor :files_free
92
+
93
+ # The number of free files/inodes available to unprivileged processes.
94
+ attr_accessor :files_available
95
+
96
+ # The filesystem identifier.
97
+ attr_accessor :filesystem_id
98
+
99
+ # A bit mask of flags.
100
+ attr_accessor :flags
101
+
102
+ # The maximum length of a file name permitted on the filesystem.
103
+ attr_accessor :name_max
104
+
105
+ # The filesystem type, e.g. UFS.
106
+ attr_accessor :base_type
107
+
108
+ alias inodes files
109
+ alias inodes_free files_free
110
+ alias inodes_available files_available
111
+
112
+ # Creates a new CzSystemInfo::Filesystem::Stat object. This is meant for
113
+ # internal use only. Do not instantiate directly.
114
+ #
115
+ def initialize
116
+ @path = nil
117
+ @block_size = nil
118
+ @fragment_size = nil
119
+ @blocks = nil
120
+ @blocks_free = nil
121
+ @blocks_available = nil
122
+ @files = nil
123
+ @files_free = nil
124
+ @files_available = nil
125
+ @filesystem_id = nil
126
+ @flags = nil
127
+ @name_max = nil
128
+ @base_type = nil
129
+ end
130
+
131
+ # Returns the total space on the partition.
132
+ def bytes_total
133
+ blocks * fragment_size
134
+ end
135
+
136
+ # Returns the total amount of free space on the partition.
137
+ def bytes_free
138
+ blocks_free * fragment_size
139
+ end
140
+
141
+ # Returns the amount of free space available to unprivileged processes.
142
+ def bytes_available
143
+ blocks_available * fragment_size
144
+ end
145
+
146
+ # Returns the total amount of used space on the partition.
147
+ def bytes_used
148
+ bytes_total - bytes_free
149
+ end
150
+
151
+ # Returns the percentage of the partition that has been used.
152
+ def percent_used
153
+ 100 - (100.0 * bytes_free.to_f / bytes_total.to_f)
154
+ end
155
+ end
156
+
157
+ # Mount objects are returned by the CzSystemInfo::Filesystem.mounts method.
158
+ class Mount
159
+ # The name of the mounted resource.
160
+ attr_accessor :name
161
+
162
+ # The mount point/directory.
163
+ attr_accessor :mount_point
164
+
165
+ # The type of filesystem mount, e.g. ufs, nfs, etc.
166
+ attr_accessor :mount_type
167
+
168
+ # A list of comma separated options for the mount, e.g. nosuid, etc.
169
+ attr_accessor :options
170
+
171
+ # The time the filesystem was mounted. May be nil.
172
+ attr_accessor :mount_time
173
+
174
+ # The dump frequency in days. May be nil.
175
+ attr_accessor :dump_frequency
176
+
177
+ # The pass number of the filessytem check. May be nil.
178
+ attr_accessor :pass_number
179
+
180
+ alias fsname name
181
+ alias dir mount_point
182
+ alias opts options
183
+ alias passno pass_number
184
+ alias freq dump_frequency
185
+
186
+ # Creates a CzSystemInfo::Filesystem::Mount object. This is meant for internal
187
+ # use only. Do no instantiate directly.
188
+ #
189
+ def initialize
190
+ @name = nil
191
+ @mount_point = nil
192
+ @mount_type = nil
193
+ @options = nil
194
+ @mount_time = nil
195
+ @dump_frequency = nil
196
+ @pass_number = nil
197
+ end
198
+ end
199
+
200
+ # Returns a CzSystemInfo::Filesystem::Stat object containing information about the
201
+ # +path+ on the filesystem.
202
+ #
203
+ # Examples:
204
+ #
205
+ # # path
206
+ # CzSystemInfo::Filesystem.stat("path")
207
+ #
208
+ # # Pathname
209
+ # pathname = Pathname.new("path")
210
+ # CzSystemInfo::Filesystem.stat(pathname)
211
+ #
212
+ # # File
213
+ # file = File.open("file", "r")
214
+ # CzSystemInfo::Filesystem.stat(file)
215
+ #
216
+ # # Dir
217
+ # dir = Dir.open("/")
218
+ # CzSystemInfo::Filesystem.stat(dir)
219
+ #
220
+ def self.stat(path)
221
+ path = path.path if path.respond_to?(:path) # File, Dir
222
+ path = path.to_s if path.respond_to?(:to_s) # Pathname
223
+
224
+ fs = Statvfs.new
225
+
226
+ if statvfs(path, fs) < 0
227
+ raise Error, "statvfs() function failed: #{strerror(FFI.errno)}"
228
+ end
229
+
230
+ obj = CzSystemInfo::Filesystem::Stat.new
231
+ obj.path = path
232
+ obj.block_size = fs[:f_bsize]
233
+ obj.fragment_size = fs[:f_frsize]
234
+ obj.blocks = fs[:f_blocks]
235
+ obj.blocks_free = fs[:f_bfree]
236
+ obj.blocks_available = fs[:f_bavail]
237
+ obj.files = fs[:f_files]
238
+ obj.files_free = fs[:f_ffree]
239
+ obj.files_available = fs[:f_favail]
240
+ obj.filesystem_id = fs[:f_fsid]
241
+ obj.flags = fs[:f_flag]
242
+ obj.name_max = fs[:f_namemax]
243
+
244
+ # OSX does things a little differently
245
+ if RbConfig::CONFIG['host_os'] =~ /darwin|osx|mach/i
246
+ obj.block_size /= 256
247
+ end
248
+
249
+ if fs.members.include?(:f_basetype)
250
+ obj.base_type = fs[:f_basetype].to_s
251
+ end
252
+
253
+ obj.freeze
254
+ end
255
+
256
+ # In block form, yields a CzSystemInfo::Filesystem::Mount object for each mounted
257
+ # filesytem on the host. Otherwise it returns an array of Mount objects.
258
+ #
259
+ # Example:
260
+ #
261
+ # CzSystemInfo::Filesystem.mounts{ |fs|
262
+ # p fs.name # => '/dev/dsk/c0t0d0s0'
263
+ # p fs.mount_time # => Thu Dec 11 15:07:23 -0700 2008
264
+ # p fs.mount_type # => 'ufs'
265
+ # p fs.mount_point # => '/'
266
+ # p fs.options # => local, noowner, nosuid
267
+ # }
268
+ #
269
+ def self.mounts
270
+ array = block_given? ? nil : []
271
+
272
+ if respond_to?(:getmntinfo, true)
273
+ buf = FFI::MemoryPointer.new(:pointer)
274
+
275
+ num = getmntinfo(buf, 2)
276
+
277
+ if num == 0
278
+ raise Error, "getmntinfo() function failed: #{strerror(FFI.errno)}"
279
+ end
280
+
281
+ ptr = buf.get_pointer(0)
282
+
283
+ num.times do
284
+ mnt = Statfs.new(ptr)
285
+ obj = CzSystemInfo::Filesystem::Mount.new
286
+ obj.name = mnt[:f_mntfromname].to_s
287
+ obj.mount_point = mnt[:f_mntonname].to_s
288
+ obj.mount_type = mnt[:f_fstypename].to_s
289
+
290
+ string = ''
291
+ flags = mnt[:f_flags] & MNT_VISFLAGMASK
292
+
293
+ OPT_NAMES.each do |key, val|
294
+ if flags & key > 0
295
+ if string.empty?
296
+ string << val
297
+ else
298
+ string << ", #{val}"
299
+ end
300
+ end
301
+ flags &= ~key
302
+ end
303
+
304
+ obj.options = string
305
+
306
+ if block_given?
307
+ yield obj.freeze
308
+ else
309
+ array << obj.freeze
310
+ end
311
+
312
+ ptr += Statfs.size
313
+ end
314
+ else
315
+ begin
316
+ if respond_to?(:setmntent, true)
317
+ method_name = 'setmntent'
318
+ fp = setmntent(MOUNT_FILE, 'r')
319
+ else
320
+ method_name = 'fopen'
321
+ fp = fopen(MOUNT_FILE, 'r')
322
+ end
323
+
324
+ if fp.null?
325
+ raise SystemCallError.new(method_name, FFI.errno)
326
+ end
327
+
328
+ if RbConfig::CONFIG['host_os'] =~ /sunos|solaris/i
329
+ mt = Mnttab.new
330
+ while getmntent(fp, mt) == 0
331
+ obj = CzSystemInfo::Filesystem::Mount.new
332
+ obj.name = mt[:mnt_special].to_s
333
+ obj.mount_point = mt[:mnt_mountp].to_s
334
+ obj.mount_type = mt[:mnt_fstype].to_s
335
+ obj.options = mt[:mnt_mntopts].to_s
336
+ obj.mount_time = Time.at(Integer(mt[:mnt_time]))
337
+
338
+ if block_given?
339
+ yield obj.freeze
340
+ else
341
+ array << obj.freeze
342
+ end
343
+ end
344
+ else
345
+ while ptr = getmntent(fp)
346
+ break if ptr.null?
347
+ mt = Mntent.new(ptr)
348
+
349
+ obj = CzSystemInfo::Filesystem::Mount.new
350
+ obj.name = mt[:mnt_fsname]
351
+ obj.mount_point = mt[:mnt_dir]
352
+ obj.mount_type = mt[:mnt_type]
353
+ obj.options = mt[:mnt_opts]
354
+ obj.mount_time = nil
355
+ obj.dump_frequency = mt[:mnt_freq]
356
+ obj.pass_number = mt[:mnt_passno]
357
+
358
+ if block_given?
359
+ yield obj.freeze
360
+ else
361
+ array << obj.freeze
362
+ end
363
+ end
364
+ end
365
+ ensure
366
+ if fp && !fp.null?
367
+ if respond_to?(:endmntent, true)
368
+ endmntent(fp)
369
+ else
370
+ fclose(fp)
371
+ end
372
+ end
373
+ end
374
+ end
375
+
376
+ array
377
+ end
378
+
379
+ # Returns the mount point of the given +file+, or itself if it cannot
380
+ # be found.
381
+ #
382
+ # Example:
383
+ #
384
+ # CzSystemInfo::Filesystem.mount_point('/home/some_user') # => /home
385
+ #
386
+ def self.mount_point(file)
387
+ dev = File.stat(file).dev
388
+ val = file
389
+
390
+ mounts.each do |mnt|
391
+ mp = mnt.mount_point
392
+ begin
393
+ if File.stat(mp).dev == dev
394
+ val = mp
395
+ break
396
+ end
397
+ rescue Errno::EACCES
398
+ next
399
+ end
400
+ end
401
+
402
+ val
403
+ end
404
+
405
+ # Attach the filesystem specified by +source+ to the location (a directory
406
+ # or file) specified by the pathname in +target+.
407
+ #
408
+ # Note that the +source+ is often a pathname referring to a device, but
409
+ # can also be the pathname of a directory or file, or a dummy string.
410
+ #
411
+ # By default this method will assume 'ext2' as the filesystem type, but
412
+ # you should update this as needed.
413
+ #
414
+ # Typically requires admin privileges.
415
+ #
416
+ # Example:
417
+ #
418
+ # CzSystemInfo::Filesystem.mount('/dev/loop0', '/home/you/tmp', 'ext4', CzSystemInfo::Filesystem::MNT_RDONLY)
419
+ #
420
+ def self.mount(source, target, fstype = 'ext2', flags = 0, data = nil)
421
+ if mount_c(source, target, fstype, flags, data) != 0
422
+ raise Error, "mount() function failed: #{strerror(FFI.errno)}"
423
+ end
424
+
425
+ self
426
+ end
427
+
428
+ # Removes the attachment of the (topmost) filesystem mounted on target.
429
+ # Additional flags may be provided for operating systems that support
430
+ # the umount2 function. Otherwise this argument is ignored.
431
+ #
432
+ # Typically requires admin privileges.
433
+ #
434
+ def self.umount(target, flags = nil)
435
+ if flags && respond_to?(:umount2)
436
+ function = 'umount2'
437
+ rv = umount2_c(target, flags)
438
+ else
439
+ function = 'umount'
440
+ rv = umount_c(target)
441
+ end
442
+
443
+ if rv != 0
444
+ raise Error, "#{function} function failed: " + strerror(FFI.errno)
445
+ end
446
+
447
+ self
448
+ end
449
+ end
450
+ end