diff options
author | Matthew Bloch <matthew@bytemark.co.uk> | 2014-11-05 14:00:45 +0000 |
---|---|---|
committer | Matthew Bloch <matthew@bytemark.co.uk> | 2014-11-05 14:00:45 +0000 |
commit | c88333e19b459685f57ba3f246c3fdd684681542 (patch) | |
tree | 3be762cf0a9ebb57c8a8528bcf98433434d371ad /lib/sys | |
parent | f6c155ff0b261f23b6fc9465b7f2e6d85bab573a (diff) |
Split out -prune from -snapshot, tested the latter on various live setups.
Diffstat (limited to 'lib/sys')
-rw-r--r-- | lib/sys/filesystem.rb | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/lib/sys/filesystem.rb b/lib/sys/filesystem.rb new file mode 100644 index 0000000..34b2d3e --- /dev/null +++ b/lib/sys/filesystem.rb @@ -0,0 +1,591 @@ +require 'ffi' +require 'rbconfig' + +# The Sys module serves as a namespace only. +module Sys + # The Filesystem class serves as an abstract base class. Its methods + # return objects of other types. Do not instantiate. + class Filesystem + extend FFI::Library + ffi_lib FFI::Library::LIBC + + # The version of the sys-filesystem library. + VERSION = '1.1.3' + + private + + if RbConfig::CONFIG['host_os'] =~ /sunos|solaris/i + attach_function(:statvfs, :statvfs64, [:string, :pointer], :int) + else + attach_function(:statvfs, [:string, :pointer], :int) + end + + attach_function(:strerror, [:int], :string) + + private_class_method :statvfs, :strerror + + begin + if RbConfig::CONFIG['host_os'] =~ /sunos|solaris/i + attach_function(:fopen, [:string, :string], :pointer) + attach_function(:fclose, [:pointer], :int) + attach_function(:getmntent, [:pointer, :pointer], :int) + private_class_method :fopen, :fclose, :getmntent + else + attach_function(:getmntent, [:pointer], :pointer) + attach_function(:setmntent, [:string, :string], :pointer) + attach_function(:endmntent, [:pointer], :int) + private_class_method :getmntent, :setmntent, :endmntent + end + rescue FFI::NotFoundError + if RbConfig::CONFIG['host_os'] =~ /darwin|osx|mach/i + attach_function(:getmntinfo, :getmntinfo64, [:pointer, :int], :int) + else + attach_function(:getmntinfo, [:pointer, :int], :int) + end + private_class_method :getmntinfo + end + + MNT_RDONLY = 0x00000001 # read only filesystem + MNT_SYNCHRONOUS = 0x00000002 # file system written synchronously + MNT_NOEXEC = 0x00000004 # can't exec from filesystem + MNT_NOSUID = 0x00000008 # don't honor setuid bits on fs + MNT_NODEV = 0x00000010 # don't interpret special files + MNT_UNION = 0x00000020 # union with underlying filesystem + MNT_ASYNC = 0x00000040 # file system written asynchronously + MNT_CPROTECT = 0x00000080 # file system supports content protection + MNT_EXPORTED = 0x00000100 # file system is exported + MNT_QUARANTINE = 0x00000400 # file system is quarantined + MNT_LOCAL = 0x00001000 # filesystem is stored locally + MNT_QUOTA = 0x00002000 # quotas are enabled on filesystem + MNT_ROOTFS = 0x00004000 # identifies the root filesystem + MNT_DOVOLFS = 0x00008000 # FS supports volfs (deprecated) + MNT_DONTBROWSE = 0x00100000 # FS is not appropriate path to user data + MNT_IGNORE_OWNERSHIP = 0x00200000 # VFS will ignore ownership info on FS objects + MNT_AUTOMOUNTED = 0x00400000 # filesystem was mounted by automounter + MNT_JOURNALED = 0x00800000 # filesystem is journaled + MNT_NOUSERXATTR = 0x01000000 # Don't allow user extended attributes + MNT_DEFWRITE = 0x02000000 # filesystem should defer writes + MNT_MULTILABEL = 0x04000000 # MAC support for individual labels + MNT_NOATIME = 0x10000000 # disable update of file access time + + MNT_VISFLAGMASK = ( + MNT_RDONLY | MNT_SYNCHRONOUS | MNT_NOEXEC | + MNT_NOSUID | MNT_NODEV | MNT_UNION | + MNT_ASYNC | MNT_EXPORTED | MNT_QUARANTINE | + MNT_LOCAL | MNT_QUOTA | + MNT_ROOTFS | MNT_DOVOLFS | MNT_DONTBROWSE | + MNT_IGNORE_OWNERSHIP | MNT_AUTOMOUNTED | MNT_JOURNALED | + MNT_NOUSERXATTR | MNT_DEFWRITE | MNT_MULTILABEL | + MNT_NOATIME | MNT_CPROTECT + ) + + @@opt_names = { + MNT_RDONLY => 'read-only', + MNT_SYNCHRONOUS => 'synchronous', + MNT_NOEXEC => 'noexec', + MNT_NOSUID => 'nosuid', + MNT_NODEV => 'nodev', + MNT_UNION => 'union', + MNT_ASYNC => 'asynchronous', + MNT_CPROTECT => 'content-protection', + MNT_EXPORTED => 'exported', + MNT_QUARANTINE => 'quarantined', + MNT_LOCAL => 'local', + MNT_QUOTA => 'quotas', + MNT_ROOTFS => 'rootfs', + MNT_DONTBROWSE => 'nobrowse', + MNT_IGNORE_OWNERSHIP => 'noowners', + MNT_AUTOMOUNTED => 'automounted', + MNT_JOURNALED => 'journaled', + MNT_NOUSERXATTR => 'nouserxattr', + MNT_DEFWRITE => 'defwrite', + MNT_NOATIME => 'noatime' + } + + # File used to read mount informtion from. + if File.exist?('/etc/mtab') + MOUNT_FILE = '/etc/mtab' + elsif File.exist?('/etc/mnttab') + MOUNT_FILE = '/etc/mnttab' + else + MOUNT_FILE = 'getmntinfo' + end + + class Statfs < FFI::Struct + if RbConfig::CONFIG['host_os'] =~ /bsd/i + layout( + :f_version, :uint32, + :f_type, :uint32, + :f_flags, :uint64, + :f_bsize, :uint64, + :f_iosize, :int64, + :f_blocks, :uint64, + :f_bfree, :uint64, + :f_bavail, :int64, + :f_files, :uint64, + :f_ffree, :uint64, + :f_syncwrites, :uint64, + :f_asyncwrites, :uint64, + :f_syncreads, :uint64, + :f_asyncreads, :uint64, + :f_spare, [:uint64, 10], + :f_namemax, :uint32, + :f_owner, :int32, + :f_fsid, [:int32, 2], + :f_charspare, [:char, 80], + :f_fstypename, [:char, 16], + :f_mntfromname, [:char, 88], + :f_mntonname, [:char, 88] + ) + else + layout( + :f_bsize, :uint32, + :f_iosize, :int32, + :f_blocks, :uint64, + :f_bfree, :uint64, + :f_bavail, :uint64, + :f_files, :uint64, + :f_ffree, :uint64, + :f_fsid, [:int32, 2], + :f_owner, :int32, + :f_type, :uint32, + :f_flags, :uint32, + :f_fssubtype, :uint32, + :f_fstypename, [:char, 16], + :f_mntonname, [:char, 1024], + :f_mntfromname, [:char, 1024], + :f_reserved, [:uint32, 8] + ) + end + end + + # The Statvfs struct represents struct statvfs from sys/statvfs.h. + class Statvfs < FFI::Struct + if RbConfig::CONFIG['host_os'] =~ /darwin|osx|mach/i + layout( + :f_bsize, :ulong, + :f_frsize, :ulong, + :f_blocks, :uint, + :f_bfree, :uint, + :f_bavail, :uint, + :f_files, :uint, + :f_ffree, :uint, + :f_favail, :uint, + :f_fsid, :ulong, + :f_flag, :ulong, + :f_namemax, :ulong + ) + elsif RbConfig::CONFIG['host'] =~ /bsd/i + layout( + :f_bavail, :uint64, + :f_bfree, :uint64, + :f_blocks, :uint64, + :f_favail, :uint64, + :f_ffree, :uint64, + :f_files, :uint64, + :f_bsize, :ulong, + :f_flag, :ulong, + :f_frsize, :ulong, + :f_fsid, :ulong, + :f_namemax, :ulong + ) + elsif RbConfig::CONFIG['host'] =~ /sunos|solaris/i + layout( + :f_bsize, :ulong, + :f_frsize, :ulong, + :f_blocks, :uint64_t, + :f_bfree, :uint64_t, + :f_bavail, :uint64_t, + :f_files, :uint64_t, + :f_ffree, :uint64_t, + :f_favail, :uint64_t, + :f_fsid, :ulong, + :f_basetype, [:char, 16], + :f_flag, :ulong, + :f_namemax, :ulong, + :f_fstr, [:char, 32], + :f_filler, [:ulong, 16] + ) + else + layout( + :f_bsize, :ulong, + :f_frsize, :ulong, + :f_blocks, :ulong, + :f_bfree, :ulong, + :f_bavail, :ulong, + :f_files, :ulong, + :f_ffree, :ulong, + :f_favail, :ulong, + :f_fsid, :ulong, + :f_flag, :ulong, + :f_namemax, :ulong, + :f_ftype, :ulong, + :f_basetype, [:char, 16], + :f_str, [:char, 16] + ) + end + end + + # The Mnttab struct represents struct mnnttab from sys/mnttab.h on Solaris. + class Mnttab < FFI::Struct + layout( + :mnt_special, :string, + :mnt_mountp, :string, + :mnt_fstype, :string, + :mnt_mntopts, :string, + :mnt_time, :string + ) + end + + # The Mntent struct represents struct mntent from sys/mount.h on Unix. + class Mntent < FFI::Struct + layout( + :mnt_fsname, :string, + :mnt_dir, :string, + :mnt_type, :string, + :mnt_opts, :string, + :mnt_freq, :int, + :mnt_passno, :int + ) + end + + public + + # The error raised if any of the Filesystem methods fail. + class Error < StandardError; end + + # Stat objects are returned by the Sys::Filesystem.stat method. + class Stat + # Read-only filesystem + RDONLY = 1 + + # Filesystem does not support suid or sgid semantics. + NOSUID = 2 + + # Filesystem does not truncate file names longer than +name_max+. + NOTRUNC = 3 + + # The path of the filesystem. + attr_accessor :path + + # The preferred system block size. + attr_accessor :block_size + + # The fragment size, i.e. fundamental filesystem block size. + attr_accessor :fragment_size + + # The total number of +fragment_size+ blocks in the filesystem. + attr_accessor :blocks + + # The total number of free blocks in the filesystem. + attr_accessor :blocks_free + + # The number of free blocks available to unprivileged processes. + attr_accessor :blocks_available + + # The total number of files/inodes that can be created. + attr_accessor :files + + # The total number of files/inodes on the filesystem. + attr_accessor :files_free + + # The number of free files/inodes available to unprivileged processes. + attr_accessor :files_available + + # The filesystem identifier. + attr_accessor :filesystem_id + + # A bit mask of flags. + attr_accessor :flags + + # The maximum length of a file name permitted on the filesystem. + attr_accessor :name_max + + # The filesystem type, e.g. UFS. + attr_accessor :base_type + + alias inodes files + alias inodes_free files_free + alias inodes_available files_available + + # Creates a new Sys::Filesystem::Stat object. This is meant for + # internal use only. Do not instantiate directly. + # + def initialize + @path = nil + @block_size = nil + @fragment_size = nil + @blocks = nil + @blocks_free = nil + @blocks_available = nil + @files = nil + @files_free = nil + @files_available = nil + @filesystem_id = nil + @flags = nil + @name_max = nil + @base_type = nil + end + + # Returns the total space on the partition. + def bytes_total + blocks * block_size + end + + # Returns the total amount of free space on the partition. + def bytes_free + blocks_available * block_size + end + + # Returns the total amount of used space on the partition. + def bytes_used + bytes_total - bytes_free + end + + # Returns the percentage of the partition that has been used. + def percent_used + 100 - (100.0 * bytes_free.to_f / bytes_total.to_f) + end + end + + # Mount objects are returned by the Sys::Filesystem.mounts method. + class Mount + # The name of the mounted resource. + attr_accessor :name + + # The mount point/directory. + attr_accessor :mount_point + + # The type of filesystem mount, e.g. ufs, nfs, etc. + attr_accessor :mount_type + + # A list of comma separated options for the mount, e.g. nosuid, etc. + attr_accessor :options + + # The time the filesystem was mounted. May be nil. + attr_accessor :mount_time + + # The dump frequency in days. May be nil. + attr_accessor :dump_frequency + + # The pass number of the filessytem check. May be nil. + attr_accessor :pass_number + + alias fsname name + alias dir mount_point + alias opts options + alias passno pass_number + alias freq dump_frequency + + # Creates a Sys::Filesystem::Mount object. This is meant for internal + # use only. Do no instantiate directly. + # + def initialize + @name = nil + @mount_point = nil + @mount_type = nil + @options = nil + @mount_time = nil + @dump_frequency = nil + @pass_number = nil + end + end + + # Returns a Sys::Filesystem::Stat object containing information about the + # +path+ on the filesystem. + # + def self.stat(path) + fs = Statvfs.new + + if statvfs(path, fs) < 0 + raise Error, 'statvfs() function failed: ' + strerror(FFI.errno) + end + + obj = Sys::Filesystem::Stat.new + obj.path = path + obj.block_size = fs[:f_bsize] + obj.fragment_size = fs[:f_frsize] + obj.blocks = fs[:f_blocks] + obj.blocks_free = fs[:f_bfree] + obj.blocks_available = fs[:f_bavail] + obj.files = fs[:f_files] + obj.files_free = fs[:f_ffree] + obj.files_available = fs[:f_favail] + obj.filesystem_id = fs[:f_fsid] + obj.flags = fs[:f_flag] + obj.name_max = fs[:f_namemax] + + # OSX does things a little differently + if RbConfig::CONFIG['host_os'] =~ /darwin|osx|mach/i + obj.block_size /= 256 + end + + if fs.members.include?(:f_basetype) + obj.base_type = fs[:f_basetype].to_s + end + + obj.freeze + end + + # In block form, yields a Sys::Filesystem::Mount object for each mounted + # filesytem on the host. Otherwise it returns an array of Mount objects. + # + # Example: + # + # Sys::Filesystem.mounts{ |fs| + # p fs.name # => '/dev/dsk/c0t0d0s0' + # p fs.mount_time # => Thu Dec 11 15:07:23 -0700 2008 + # p fs.mount_type # => 'ufs' + # p fs.mount_point # => '/' + # p fs.options # => local, noowner, nosuid + # } + # + def self.mounts + array = block_given? ? nil : [] + + if respond_to?(:getmntinfo, true) + buf = FFI::MemoryPointer.new(:pointer) + + num = getmntinfo(buf, 2) + + if num == 0 + raise Error, 'getmntinfo() function failed: ' + strerror(FFI.errno) + end + + ptr = buf.get_pointer(0) + + num.times{ |i| + mnt = Statfs.new(ptr) + obj = Sys::Filesystem::Mount.new + obj.name = mnt[:f_mntfromname].to_s + obj.mount_point = mnt[:f_mntonname].to_s + obj.mount_type = mnt[:f_fstypename].to_s + + string = "" + flags = mnt[:f_flags] & MNT_VISFLAGMASK + + @@opt_names.each{ |key,val| + if flags & key > 0 + if string.empty? + string << val + else + string << ", #{val}" + end + end + flags &= ~key + } + + obj.options = string + + if block_given? + yield obj.freeze + else + array << obj.freeze + end + + ptr += Statfs.size + } + else + begin + if respond_to?(:setmntent, true) + fp = setmntent(MOUNT_FILE, 'r') + else + fp = fopen(MOUNT_FILE, 'r') + end + + if RbConfig::CONFIG['host_os'] =~ /sunos|solaris/i + mt = Mnttab.new + while getmntent(fp, mt) == 0 + obj = Sys::Filesystem::Mount.new + obj.name = mt[:mnt_special].to_s + obj.mount_point = mt[:mnt_mountp].to_s + obj.mount_type = mt[:mnt_fstype].to_s + obj.options = mt[:mnt_mntopts].to_s + obj.mount_time = Time.at(Integer(mt[:mnt_time])) + + if block_given? + yield obj.freeze + else + array << obj.freeze + end + end + else + while ptr = getmntent(fp) + break if ptr.null? + mt = Mntent.new(ptr) + + obj = Sys::Filesystem::Mount.new + obj.name = mt[:mnt_fsname] + obj.mount_point = mt[:mnt_dir] + obj.mount_type = mt[:mnt_type] + obj.options = mt[:mnt_opts] + obj.mount_time = nil + obj.dump_frequency = mt[:mnt_freq] + obj.pass_number = mt[:mnt_passno] + + if block_given? + yield obj.freeze + else + array << obj.freeze + end + end + end + ensure + if fp && !fp.null? + if respond_to?(:endmntent, true) + endmntent(fp) + else + fclose(fp) + end + end + end + end + + array + end + + # Returns the mount point of the given +file+, or itself if it cannot + # be found. + # + # Example: + # + # Sys::Filesystem.mount_point('/home/some_user') # => /home + # + def self.mount_point(file) + dev = File.stat(file).dev + val = file + + self.mounts.each{ |mnt| + mp = mnt.mount_point + if File.stat(mp).dev == dev + val = mp + break + end + } + + val + end + end +end + +class Numeric + # Converts a number to kilobytes. + def to_kb + self / 1024 + end + + # Converts a number to megabytes. + def to_mb + self / 1048576 + end + + # Converts a number to gigabytes. + def to_gb + self / 1073741824 + end + + # Converts a number to terabytes. + def to_tb + self / 1099511627776 + end +end |