summaryrefslogtreecommitdiff
path: root/rdiff-backup/rdiff_backup/eas_acls.py
diff options
context:
space:
mode:
authorbescoto <bescoto@2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109>2003-06-30 03:00:18 +0000
committerbescoto <bescoto@2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109>2003-06-30 03:00:18 +0000
commit5e09353fe5ce80b724a30db11cb10523d147c6ec (patch)
tree083998f6893121da5d651ee7a475fde3372c78f9 /rdiff-backup/rdiff_backup/eas_acls.py
parent46afa3e6b3aacfe05d1586a583a2606cc139f658 (diff)
downloadrdiff-backup-5e09353fe5ce80b724a30db11cb10523d147c6ec.tar.gz
Many changes - added extended attribute support and file system
ability detection git-svn-id: http://svn.savannah.nongnu.org/svn/rdiff-backup/trunk@334 2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109
Diffstat (limited to 'rdiff-backup/rdiff_backup/eas_acls.py')
-rw-r--r--rdiff-backup/rdiff_backup/eas_acls.py185
1 files changed, 185 insertions, 0 deletions
diff --git a/rdiff-backup/rdiff_backup/eas_acls.py b/rdiff-backup/rdiff_backup/eas_acls.py
new file mode 100644
index 0000000..748d8bb
--- /dev/null
+++ b/rdiff-backup/rdiff_backup/eas_acls.py
@@ -0,0 +1,185 @@
+# Copyright 2003 Ben Escoto
+#
+# This file is part of rdiff-backup.
+#
+# rdiff-backup is free software; you can redistribute it and/or modify
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# rdiff-backup is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with rdiff-backup; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+
+"""Store and retrieve extended attributes and access control lists
+
+Not all file systems will have EAs and ACLs, but if they do, store
+this information in separate files in the rdiff-backup-data directory,
+called extended_attributes.<time>.snapshot and
+access_control_lists.<time>.snapshot.
+
+"""
+
+from __future__ import generators
+import base64, errno, re
+import static, Globals, metadata, connection, rorpiter, log
+
+
+class ExtendedAttributes:
+ """Hold a file's extended attribute information"""
+ def __init__(self, index, attr_dict = None):
+ """Initialize EA object with no attributes"""
+ self.index = index
+ if attr_dict is None: self.attr_dict = {}
+ else: self.attr_dict = attr_dict
+
+ def __eq__(self, ea):
+ """Equal if all attributes and index are equal"""
+ assert isinstance(ea, ExtendedAttributes)
+ return ea.index == self.index and ea.attr_dict == self.attr_dict
+
+ def __ne__(self, ea): return not self.__eq__(ea)
+
+ def get_indexpath(self): return self.index and '/'.join(self.index) or '.'
+
+ def read_from_rp(self, rp):
+ """Set the extended attributes from an rpath"""
+ try: attr_list = rp.conn.xattr.listxattr(rp.path)
+ except IOError, exc:
+ if exc[0] == errno.EOPNOTSUPP: return # if not sup, consider empty
+ raise
+ for attr in attr_list:
+ try: self.attr_dict[attr] = rp.conn.xattr.getxattr(rp.path, attr)
+ except IOError, exc:
+ # File probably modified while reading, just continue
+ if exc[0] == errno.ENODATA: continue
+ elif exc[0] == errno.ENOENT: break
+ else: raise
+
+ def clear_rp(self, rp):
+ """Delete all the extended attributes in rpath"""
+ for name in rp.conn.xattr.listxattr(rp.path):
+ rp.conn.xattr.removexattr(rp.path, name)
+
+ def write_to_rp(self, rp):
+ """Write extended attributes to rpath rp"""
+ self.clear_rp(rp)
+ for (name, value) in self.attr_dict.iteritems():
+ rp.conn.xattr.setxattr(rp.path, name, value)
+
+ def get(self, name):
+ """Return attribute attached to given name"""
+ return self.attr_dict[name]
+
+ def set(self, name, value = ""):
+ """Set given name to given value. Does not write to disk"""
+ self.attr_dict[name] = value
+
+ def delete(self, name):
+ """Delete value associated with given name"""
+ del self.attr_dict[name]
+
+ def empty(self):
+ """Return true if no extended attributes are set"""
+ return not self.attr_dict
+
+def compare_rps(rp1, rp2):
+ """Return true if rp1 and rp2 have same extended attributes"""
+ ea1 = ExtendedAttributes(rp1.index)
+ ea1.read_from_rp(rp1)
+ ea2 = ExtendedAttributes(rp2.index)
+ ea2.read_from_rp(rp2)
+ return ea1 == ea2
+
+
+def EA2Record(ea):
+ """Convert ExtendedAttributes object to text record"""
+ str_list = ['# file: %s' % ea.get_indexpath()]
+ for (name, val) in ea.attr_dict.iteritems():
+ if not val: str_list.append(name)
+ else:
+ encoded_val = base64.encodestring(val).replace('\n', '')
+ str_list.append('%s=0s%s' % (name, encoded_val))
+ return '\n'.join(str_list)+'\n'
+
+def Record2EA(record):
+ """Convert text record to ExtendedAttributes object"""
+ lines = record.split('\n')
+ first = lines.pop(0)
+ if not first[:8] == "# file: ":
+ raise metadata.ParsingError("Bad record beginning: " + first[:8])
+ filename = first[8:]
+ if filename == '.': index = ()
+ else: index = tuple(filename.split('/'))
+ ea = ExtendedAttributes(index)
+
+ for line in lines:
+ line = line.strip()
+ if not line: continue
+ assert line[0] != '#', line
+ eq_pos = line.find('=')
+ if eq_pos == -1: ea.set(line)
+ else:
+ name = line[:eq_pos]
+ assert line[eq_pos+1:eq_pos+3] == '0s', \
+ "Currently only base64 encoding supported"
+ encoded_val = line[eq_pos+3:]
+ ea.set(name, base64.decodestring(encoded_val))
+ return ea
+
+def quote_path(path):
+ """Quote a path for use EA/ACL records.
+
+ Right now no quoting!!! Change this to reflect the updated
+ quoting style of getfattr/setfattr when they are changed.
+
+ """
+ return path
+
+
+class EAExtractor(metadata.FlatExtractor):
+ """Iterate ExtendedAttributes objects from the EA information file"""
+ record_boundary_regexp = re.compile("\\n# file:")
+ record_to_object = staticmethod(Record2EA)
+ def get_index_re(self, index):
+ """Find start of EA record with given index"""
+ if not index: indexpath = '.'
+ else: indexpath = '/'.join(index)
+ # Right now there is no quoting, due to a bug in
+ # getfacl/setfacl. Replace later when bug fixed.
+ return re.compile('(^|\\n)(# file: %s\\n)' % indexpath)
+
+class ExtendedAttributesFile(metadata.FlatFile):
+ """Store/retrieve EAs from extended_attributes file"""
+ _prefix = "extended_attributes"
+ _extractor = EAExtractor
+ _object_to_record = staticmethod(EA2Record)
+
+ def get_combined_iter_at_time(cls, rbdir, rest_time,
+ restrict_index = None):
+ """Return an iter of rorps with extended attributes added"""
+ def join_eas(basic_iter, ea_iter):
+ """Join basic_iter with ea iter"""
+ collated = rorpiter.CollateIterators(basic_iter, ea_iter)
+ for rorp, ea in collated:
+ assert rorp, (rorp, (ea.index, ea.attr_dict), rest_time)
+ if not ea: ea = ExtendedAttributes(rorp.index)
+ rorp.set_ea(ea)
+ yield rorp
+
+ basic_iter = metadata.MetadataFile.get_objects_at_time(
+ Globals.rbdir, rest_time, restrict_index)
+ if not basic_iter: return None
+ ea_iter = cls.get_objects_at_time(rbdir, rest_time, restrict_index)
+ if not ea_iter:
+ log.Log("Warning: Extended attributes file not found", 2)
+ ea_iter = iter([])
+ return join_eas(basic_iter, ea_iter)
+
+static.MakeClass(ExtendedAttributesFile)