diff --git a/darc/archive.py b/darc/archive.py index eb28c8107..b405fb86d 100644 --- a/darc/archive.py +++ b/darc/archive.py @@ -5,6 +5,7 @@ import socket import stat import sys +from xattr import xattr, XATTR_NOFOLLOW from . import NS_ARCHIVE_METADATA, NS_ARCHIVE_ITEMS, NS_ARCHIVE_CHUNKS, NS_CHUNK from .chunkifier import chunkify @@ -144,6 +145,13 @@ def extract_item(self, item, dest=None): raise Exception('Unknown archive item type %r' % item['mode']) def restore_attrs(self, path, item, symlink=False): + if item['xattrs']: + try: + xa = xattr(path, XATTR_NOFOLLOW) + for k, v in item['xattrs'].items(): + xa.set(k, v) + except IOError: + pass if have_lchmod: os.lchmod(path, item['mode']) elif not symlink: @@ -179,28 +187,33 @@ def delete(self, cache): self.store.commit() cache.save() - def stat_attrs(self, st): + def stat_attrs(self, st, path): + try: + xattrs = dict(xattr(path, XATTR_NOFOLLOW)) + except IOError: + xattrs = None return { 'mode': st.st_mode, 'uid': st.st_uid, 'user': uid2user(st.st_uid), 'gid': st.st_gid, 'group': gid2group(st.st_gid), 'atime': st.st_atime, 'mtime': st.st_mtime, + 'xattrs': xattrs, } def process_dir(self, path, st): item = {'path': path.lstrip('/\\:')} - item.update(self.stat_attrs(st)) + item.update(self.stat_attrs(st, path)) self.items.append(item) def process_fifo(self, path, st): item = {'path': path.lstrip('/\\:')} - item.update(self.stat_attrs(st)) + item.update(self.stat_attrs(st, path)) self.items.append(item) def process_symlink(self, path, st): source = os.readlink(path) item = {'path': path.lstrip('/\\:'), 'source': source} - item.update(self.stat_attrs(st)) + item.update(self.stat_attrs(st, path)) self.items.append(item) def process_file(self, path, st, cache): @@ -240,7 +253,7 @@ def process_file(self, path, st, cache): size += len(chunk) cache.memorize_file_chunks(path_hash, st, ids) item = {'path': safe_path, 'chunks': chunks, 'size': size} - item.update(self.stat_attrs(st)) + item.update(self.stat_attrs(st, path)) self.items.append(item) def process_chunk2(self, id, cache): diff --git a/darc/test.py b/darc/test.py index 9ffb79679..c772b6db9 100644 --- a/darc/test.py +++ b/darc/test.py @@ -6,6 +6,7 @@ import shutil import tempfile import unittest +from xattr import xattr, XATTR_NOFOLLOW from . import store from .archiver import Archiver @@ -57,25 +58,37 @@ def create_regual_file(self, name, size=0): with open(filename, 'wb') as fd: fd.write('X' * size) + def get_xattrs(self, path): + try: + return dict(xattr(path, XATTR_NOFOLLOW)) + except IOError: + return {} + def diff_dirs(self, dir1, dir2): diff = filecmp.dircmp(dir1, dir2) self.assertEqual(diff.left_only, []) self.assertEqual(diff.right_only, []) self.assertEqual(diff.diff_files, []) for filename in diff.common: - s1 = os.lstat(os.path.join(dir1, filename)) - s2 = os.lstat(os.path.join(dir2, filename)) + path1 = os.path.join(dir1, filename) + path2 = os.path.join(dir2, filename) + s1 = os.lstat(path1) + s2 = os.lstat(path2) attrs = ['st_mode', 'st_uid', 'st_gid'] # We can't restore symlink atime/mtime right now - if not os.path.islink(os.path.join(dir1, filename)): + if not os.path.islink(path1): attrs.append('st_mtime') d1 = [filename] + [getattr(s1, a) for a in attrs] d2 = [filename] + [getattr(s2, a) for a in attrs] + d1.append(self.get_xattrs(path1)) + d2.append(self.get_xattrs(path2)) self.assertEqual(d1, d2) def test_basic_functionality(self): self.create_regual_file('file1', size=1024*80) self.create_regual_file('dir2/file2', size=1024*80) + x = xattr(os.path.join(self.input_path, 'file1')) + x.set('user:foo', 'bar') os.symlink('somewhere', os.path.join(self.input_path, 'link1')) os.mkfifo(os.path.join(self.input_path, 'fifo1')) self.darc('create', self.store_path + '::test', 'input') @@ -87,7 +100,7 @@ def test_corrupted_store(self): self.create_src_archive('test') self.darc('verify', self.store_path + '::test') fd = open(os.path.join(self.tmpdir, 'store', 'bands', '0', '0'), 'r+') - fd.seek(1000) + fd.seek(100) fd.write('X') fd.close() self.darc('verify', self.store_path + '::test', exit_code=1)