diff options
Diffstat (limited to 'lib/byteback/restore_file.rb')
-rw-r--r-- | lib/byteback/restore_file.rb | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/lib/byteback/restore_file.rb b/lib/byteback/restore_file.rb new file mode 100644 index 0000000..e9282d0 --- /dev/null +++ b/lib/byteback/restore_file.rb @@ -0,0 +1,215 @@ +require 'ffi-xattr' +require 'scanf' +require 'pp' + +module Byteback + class RestoreFile + S_IFMT = 0170000 # bit mask for the file type bit fields + + S_IFSOCK = 0140000 # socket + S_IFLNK = 0120000 # symbolic link + S_IFREG = 0100000 # regular file + S_IFBLK = 0060000 # block device + S_IFDIR = 0040000 # directory + S_IFCHR = 0020000 # character device + S_IFIFO = 0010000 # FIFO + + S_ISUID = 0004000 # set-user-ID bit + S_ISGID = 0002000 # set-group-ID bit (see below) + S_ISVTX = 0001000 # sticky bit (see below) + + S_IRWXU = 00700 # mask for file owner permissions + S_IRUSR = 00400 # owner has read permission + S_IWUSR = 00200 # owner has write permission + S_IXUSR = 00100 # owner has execute permission + + S_IRWXG = 00070 # mask for group permissions + S_IRGRP = 00040 # group has read permission + S_IWGRP = 00020 # group has write permission + S_IXGRP = 00010 # group has execute permission + + S_IRWXO = 00007 # mask for permissions for others (not in group) + S_IROTH = 00004 # others have read permission + S_IWOTH = 00002 # others have write permission + S_IXOTH = 00001 # others have execute permission + + include Comparable + + def initialize(full_path, byteback_root=".", now = Time.now) + @full_path = full_path + @byteback_root = byteback_root + @now = now + + # + # The snapshot is the first directory after the byteback_root + # + @snapshot = full_path.sub(%r(^#{Regexp.escape @byteback_root}),'').split("/")[1] + + if @snapshot == "current" + @snapshot_time = @now + else + @snapshot_time = Time.parse(@snapshot) + end + + # + # Restore path + # + @path = full_path.sub(%r(^#{Regexp.escape @byteback_root}/#{Regexp.escape @snapshot}),'') + + @stat = @mode = @dev_major = @dev_minor = @uid = @gid = nil + end + + def <=>(other) + [self.path, self.mtime.to_i, self.size] <=> [other.path, other.mtime.to_i, other.size] + end + + def stat + @stat = ::File.lstat(@full_path) unless @stat.is_a?(File::Stat) + @stat + end + + def snapshot + @snapshot + end + + def snapshot_time + @snapshot_time + end + + def path + @path + end + + def to_s + sprintf("%10s %i %4i %4i %s %s %s", self.modestring, self.size, self.uid, self.gid, self.mtime.strftime("%b %2d %H:%M"), @snapshot, @path) + end + + def read_rsync_xattrs + xattr = Xattr.new(@full_path, :no_follow => false) + rsync_xattrs = xattr["user.rsync.%stat"] + if rsync_xattrs + @mode, @dev_major, @dev_minor, @uid, @gid = rsync_xattrs.scanf("%o %d,%d %d:%d") + raise ArgumentError, "Corrupt rsync stat xattr found for #{@full_path} (#{rsync_xattrs})" unless [@mode, @dev_major, @dev_minor, @uid, @gid].all?{|i| i.is_a?(Integer)} + else + warn "No rsync stat xattr found for #{@full_path}" + @mode, @dev_major, @dev_minor, @uid, @gid = %w(mode dev_major dev_minor uid gid).collect{|m| self.stat.__send__(m.to_sym)} + end + end + + def mode + return self.stat.mode if self.stat.symlink? + read_rsync_xattrs unless @mode + @mode + end + + def dev_minor + read_rsync_xattrs unless @dev_minor + @dev_minor + end + + def dev_major + read_rsync_xattrs unless @dev_major + @dev_major + end + + def uid + read_rsync_xattrs unless @uid + @uid + end + + def gid + read_rsync_xattrs unless @gid + @gid + end + + # + # This returns the type of file as a single character. + # + def ftypelet + if file? + "-" + elsif directory? + "d" + elsif blockdev? + "b" + elsif chardev? + "c" + elsif symlink? + "l" + elsif fifo? + "p" + elsif socket? + "s" + else + "?" + end + end + + # + # This returns a modestring from the octal, like drwxr-xr-x. + # This has mostly been copied from strmode from filemode.h in coreutils. + # + def modestring + str = "" + str << ftypelet + str << ((mode & S_IRUSR == S_IRUSR) ? 'r' : '-') + str << ((mode & S_IWUSR == S_IWUSR) ? 'w' : '-') + str << ((mode & S_ISUID == S_ISUID) ? + ((mode & S_IXUSR == S_IXUSR) ? 's' : 'S') : + ((mode & S_IXUSR == S_IXUSR) ? 'x' : '-')) + str << ((mode & S_IRGRP == S_IRGRP) ? 'r' : '-') + str << ((mode & S_IWGRP == S_IWGRP) ? 'w' : '-') + str << ((mode & S_ISGID == S_ISGID) ? + ((mode & S_IXGRP == S_IXGRP) ? 's' : 'S') : + ((mode & S_IXGRP == S_IXGRP) ? 'x' : '-')) + str << ((mode & S_IROTH == S_IROTH) ? 'r' : '-') + str << ((mode & S_IWOTH == S_IWOTH) ? 'w' : '-') + str << ((mode & S_ISVTX == S_ISVTX) ? + ((mode & S_IXOTH == S_IXOTH) ? 't' : 'T') : + ((mode & S_IXOTH == S_IXOTH) ? 'x' : '-')) + return str + end + + def socket? + (mode & S_IFMT) == S_IFSOCK + end + + def symlink? + self.stat.symlink? || (mode & S_IFMT) == S_IFLNK + end + + def file? + (mode & S_IFMT) == S_IFREG + end + + def blockdev? + (mode & S_IFMT) == S_IFBLK + end + + def directory? + (mode & S_IFMT) == S_IFDIR + end + + def chardev? + (mode & S_IFMT) == S_IFCHR + end + + def fifo? + (mode & S_IFMT) == S_IFIFO + end + + def readlink + if self.stat.symlink? + File.readlink(@full_path) + else + File.read(@full_path).chomp + end + end + + def method_missing(m, *args, &blk) + return self.stat.__send__(m) if self.stat.respond_to?(m) + + raise NoMethodError, m + end + end +end |