cz_system_info 0.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.
@@ -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