X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=lib%2Fbup%2Fmetadata.py;h=5ffddd4b2bf11623365f71cdfa09a3f1aa8795ef;hb=7ccf0a9f15b5fc4bc0e257cda7658b72bd04b8a1;hp=6feef21ca01267db43b03ad261b43fb0a1873b49;hpb=f7830d63f195f6ff37fa5a55f9088625fc369aad;p=bup.git diff --git a/lib/bup/metadata.py b/lib/bup/metadata.py index 6feef21..5ffddd4 100644 --- a/lib/bup/metadata.py +++ b/lib/bup/metadata.py @@ -4,13 +4,12 @@ # # This code is covered under the terms of the GNU Library General # Public License as described in the bup LICENSE file. -import errno, os, sys, stat, time, pwd, grp, struct, re +import errno, os, sys, stat, time, pwd, grp from cStringIO import StringIO from bup import vint, xstat from bup.drecurse import recursive_dirlist from bup.helpers import add_error, mkdirp, log, is_superuser -from bup.xstat import utime, lutime, lstat -import bup._helpers as _helpers +from bup.xstat import utime, lutime try: import xattr @@ -165,6 +164,7 @@ _rec_tag_posix1e_acl = 4 # getfacl(1), setfacl(1), etc. _rec_tag_nfsv4_acl = 5 # intended to supplant posix1e acls? _rec_tag_linux_attr = 6 # lsattr(1) chattr(1) _rec_tag_linux_xattr = 7 # getfattr(1) setfattr(1) +_rec_tag_hardlink_target = 8 # hard link target path class ApplyError(Exception): @@ -183,6 +183,9 @@ class Metadata: # "bup save", for example, as a placeholder in cases where # from_path() fails. + # NOTE: if any relevant fields are added or removed, be sure to + # update same_file() below. + ## Common records # Timestamps are (sec, ns), relative to 1970-01-01 00:00:00, ns @@ -208,6 +211,17 @@ class Metadata: pass self.mode = st.st_mode + def _same_common(self, other): + """Return true or false to indicate similarity in the hardlink sense.""" + return self.uid == other.uid \ + and self.gid == other.gid \ + and self.rdev == other.rdev \ + and self.atime == other.atime \ + and self.mtime == other.mtime \ + and self.ctime == other.ctime \ + and self.user == other.user \ + and self.group == other.group + def _encode_common(self): if not self.mode: return None @@ -411,6 +425,22 @@ class Metadata: self.symlink_target = vint.read_bvec(port) + ## Hardlink targets + + def _add_hardlink_target(self, target): + self.hardlink_target = target + + def _same_hardlink_target(self, other): + """Return true or false to indicate similarity in the hardlink sense.""" + return self.hardlink_target == other.hardlink_target + + def _encode_hardlink_target(self): + return self.hardlink_target + + def _load_hardlink_target_rec(self, port): + self.hardlink_target = vint.read_bvec(port) + + ## POSIX1e ACL records # Recorded as a list: @@ -433,6 +463,10 @@ class Metadata: if e.errno != errno.EOPNOTSUPP: raise + def _same_posix1e_acl(self, other): + """Return true or false to indicate similarity in the hardlink sense.""" + return self.posix1e_acl == other.posix1e_acl + def _encode_posix1e_acl(self): # Encode as two strings (w/default ACL string possibly empty). if self.posix1e_acl: @@ -506,6 +540,10 @@ class Metadata: else: raise + def _same_linux_attr(self, other): + """Return true or false to indicate similarity in the hardlink sense.""" + return self.linux_attr == other.linux_attr + def _encode_linux_attr(self): if self.linux_attr: return vint.pack('V', self.linux_attr) @@ -541,6 +579,10 @@ class Metadata: if e.errno != errno.EOPNOTSUPP: raise + def _same_linux_xattr(self, other): + """Return true or false to indicate similarity in the hardlink sense.""" + return self.linux_xattr == other.linux_xattr + def _encode_linux_xattr(self): if self.linux_xattr: result = vint.pack('V', len(self.linux_xattr)) @@ -595,6 +637,7 @@ class Metadata: self.path = None self.size = None self.symlink_target = None + self.hardlink_target = None self.linux_attr = None self.linux_xattr = None self.posix1e_acl = None @@ -603,7 +646,10 @@ class Metadata: def write(self, port, include_path=True): records = include_path and [(_rec_tag_path, self._encode_path())] or [] records.extend([(_rec_tag_common, self._encode_common()), - (_rec_tag_symlink_target, self._encode_symlink_target()), + (_rec_tag_symlink_target, + self._encode_symlink_target()), + (_rec_tag_hardlink_target, + self._encode_hardlink_target()), (_rec_tag_posix1e_acl, self._encode_posix1e_acl()), (_rec_tag_linux_attr, self._encode_linux_attr()), (_rec_tag_linux_xattr, self._encode_linux_xattr())]) @@ -637,6 +683,8 @@ class Metadata: result._load_common_rec(port) elif tag == _rec_tag_symlink_target: result._load_symlink_target_rec(port) + elif tag == _rec_tag_hardlink_target: + result._load_hardlink_target_rec(port) elif tag == _rec_tag_posix1e_acl: result._load_posix1e_acl_rec(port) elif tag ==_rec_tag_nfsv4_acl: @@ -664,7 +712,7 @@ class Metadata: if not path: path = self.path if not path: - raise Exception('Metadata.apply_to_path() called with no path'); + raise Exception('Metadata.apply_to_path() called with no path') if not self._recognized_file_type(): add_error('not applying metadata to "%s"' % path + ' with unrecognized mode "0x%x"\n' % self.mode) @@ -678,8 +726,20 @@ class Metadata: except ApplyError, e: add_error(e) + def same_file(self, other): + """Compare this to other for equivalency. Return true if + their information implies they could represent the same file + on disk, in the hardlink sense. Assume they're both regular + files.""" + return self._same_common(other) \ + and self._same_hardlink_target(other) \ + and self._same_posix1e_acl(other) \ + and self._same_linux_attr(other) \ + and self._same_linux_xattr(other) + -def from_path(path, statinfo=None, archive_path=None, save_symlinks=True): +def from_path(path, statinfo=None, archive_path=None, + save_symlinks=True, hardlink_target=None): result = Metadata() result.path = archive_path st = statinfo or xstat.lstat(path) @@ -687,6 +747,7 @@ def from_path(path, statinfo=None, archive_path=None, save_symlinks=True): result._add_common(path, st) if save_symlinks: result._add_symlink_target(path, st) + result._add_hardlink_target(hardlink_target) result._add_posix1e_acl(path, st) result._add_linux_attr(path, st) result._add_linux_xattr(path, st) @@ -870,7 +931,8 @@ def display_archive(file): for meta in _ArchiveIterator(file): if not meta.path: print >> sys.stderr, \ - 'bup: no metadata path, but asked to only display path (increase verbosity?)' + 'bup: no metadata path, but asked to only display path', \ + '(increase verbosity?)' sys.exit(1) print meta.path