From 2281af528499689796c5fa3b0a6ef78282b48cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Borgstr=C3=B6m?= Date: Sat, 27 Jul 2013 22:33:43 +0200 Subject: [PATCH] Rewrote xattr support --- attic/archive.py | 4 +- attic/fuse.py | 4 +- attic/testsuite/__init__.py | 6 +- attic/testsuite/archiver.py | 2 +- attic/testsuite/xattr.py | 41 ++--- attic/xattr.py | 315 ++++++++++++++++++------------------ 6 files changed, 176 insertions(+), 196 deletions(-) diff --git a/attic/archive.py b/attic/archive.py index 7897d91b3..774bfc6e3 100644 --- a/attic/archive.py +++ b/attic/archive.py @@ -280,7 +280,7 @@ class Archive(object): xattrs = item.get(b'xattrs') if xattrs: for k, v in xattrs.items(): - xattr.set(fd or path, k, v) + xattr.setxattr(fd or path, k, v) uid = gid = None if not self.numeric_owner: uid = user2uid(item[b'user']) @@ -350,7 +350,7 @@ class Archive(object): if self.numeric_owner: item[b'user'] = item[b'group'] = None try: - xattrs = xattr.get_all(path) + xattrs = xattr.get_all(path, follow_symlinks=False) if xattrs: item[b'xattrs'] = xattrs except PermissionError: diff --git a/attic/fuse.py b/attic/fuse.py index a504fbfb1..2d477ce6c 100644 --- a/attic/fuse.py +++ b/attic/fuse.py @@ -102,12 +102,10 @@ class AtticOperations(llfuse.Operations): def listxattr(self, inode): item = self.items[inode] - return [b'user.' + name for name in item.get(b'xattrs', {}).keys()] + return item.get(b'xattrs', {}).keys() def getxattr(self, inode, name): item = self.items[inode] - if name.startswith(b'user.'): - name = name[5:] try: return item.get(b'xattrs', {})[name] except KeyError: diff --git a/attic/testsuite/__init__.py b/attic/testsuite/__init__.py index b35b966cb..b0a98b695 100644 --- a/attic/testsuite/__init__.py +++ b/attic/testsuite/__init__.py @@ -3,7 +3,8 @@ import os import sys import time import unittest -from attic import xattr +from attic.xattr import get_all + has_mtime_ns = sys.version >= '3.3' utime_supports_fd = os.utime in getattr(os, 'supports_fd', {}) @@ -18,7 +19,7 @@ class AtticTestCase(unittest.TestCase): def _get_xattrs(self, path): try: - return xattr.get_all(path) + return get_all(path, follow_symlinks=False) except EnvironmentError: return {} @@ -81,6 +82,7 @@ def get_tests(suite): class TestLoader(unittest.TestLoader): """A customzied test loader that properly detects and filters our test cases """ + def loadTestsFromName(self, pattern, module=None): suite = self.discover('attic.testsuite', '*.py') tests = unittest.TestSuite() diff --git a/attic/testsuite/archiver.py b/attic/testsuite/archiver.py index f3bb5a511..71e834431 100644 --- a/attic/testsuite/archiver.py +++ b/attic/testsuite/archiver.py @@ -117,7 +117,7 @@ class ArchiverTestCase(AtticTestCase): # Char device os.mknod('input/cdev', 0o600 | stat.S_IFCHR, os.makedev(30, 40)) if xattr.is_enabled(): - xattr.set(os.path.join(self.input_path, 'file1'), b'foo', b'bar') + xattr.setxattr(os.path.join(self.input_path, 'file1'), 'user.foo', b'bar') # Hard link os.link(os.path.join(self.input_path, 'file1'), os.path.join(self.input_path, 'hardlink')) diff --git a/attic/testsuite/xattr.py b/attic/testsuite/xattr.py index 123995d5a..0f9331aae 100644 --- a/attic/testsuite/xattr.py +++ b/attic/testsuite/xattr.py @@ -2,8 +2,7 @@ import os import tempfile import unittest from attic.testsuite import AtticTestCase -from attic.xattr import lsetxattr, llistxattr, lgetxattr, get_all, set, flistxattr, fgetxattr, fsetxattr, is_enabled - +from attic.xattr import is_enabled, getxattr, setxattr, listxattr @unittest.skipUnless(is_enabled(), 'xattr not enabled on filesystem') class XattrTestCase(AtticTestCase): @@ -16,28 +15,16 @@ class XattrTestCase(AtticTestCase): def tearDown(self): os.unlink(self.symlink) - def test_low_level(self): - self.assert_equal(llistxattr(self.tmpfile.name), []) - self.assert_equal(llistxattr(self.symlink), []) - lsetxattr(self.tmpfile.name, b'foo', b'bar') - self.assert_equal(llistxattr(self.tmpfile.name), [b'foo']) - self.assert_equal(lgetxattr(self.tmpfile.name, b'foo'), b'bar') - self.assert_equal(llistxattr(self.symlink), []) - - def test_low_level_fileno(self): - self.assert_equal(flistxattr(self.tmpfile.fileno()), []) - fsetxattr(self.tmpfile.fileno(), b'foo', b'bar') - self.assert_equal(flistxattr(self.tmpfile.fileno()), [b'foo']) - self.assert_equal(fgetxattr(self.tmpfile.fileno(), b'foo'), b'bar') - - def test_high_level(self): - self.assert_equal(get_all(self.tmpfile.name), {}) - self.assert_equal(get_all(self.symlink), {}) - set(self.tmpfile.name, b'foo', b'bar') - self.assert_equal(get_all(self.tmpfile.name), {b'foo': b'bar'}) - self.assert_equal(get_all(self.symlink), {}) - - def test_high_level_fileno(self): - self.assert_equal(get_all(self.tmpfile.fileno()), {}) - set(self.tmpfile.fileno(), b'foo', b'bar') - self.assert_equal(get_all(self.tmpfile.fileno()), {b'foo': b'bar'}) + def test(self): + self.assert_equal(listxattr(self.tmpfile.name), []) + self.assert_equal(listxattr(self.tmpfile.fileno()), []) + self.assert_equal(listxattr(self.symlink), []) + setxattr(self.tmpfile.name, 'user.foo', b'bar') + setxattr(self.tmpfile.fileno(), 'user.bar', b'foo') + self.assert_equal(set(listxattr(self.tmpfile.name)), set(['user.foo', 'user.bar'])) + self.assert_equal(set(listxattr(self.tmpfile.fileno())), set(['user.foo', 'user.bar'])) + self.assert_equal(set(listxattr(self.symlink)), set(['user.foo', 'user.bar'])) + self.assert_equal(listxattr(self.symlink, follow_symlinks=False), []) + self.assert_equal(getxattr(self.tmpfile.name, 'user.foo'), b'bar') + self.assert_equal(getxattr(self.tmpfile.fileno(), 'user.foo'), b'bar') + self.assert_equal(getxattr(self.symlink, 'user.foo'), b'bar') diff --git a/attic/xattr.py b/attic/xattr.py index 31d8d8b41..2f1aea77c 100644 --- a/attic/xattr.py +++ b/attic/xattr.py @@ -1,14 +1,8 @@ """A basic extended attributes (xattr) implementation for Linux and MacOS X - -On Linux only the "user." namespace is accessed """ import os import sys import tempfile -from ctypes import CDLL, create_string_buffer, c_ssize_t, c_size_t, c_char_p, c_int, c_uint32, get_errno -from ctypes.util import find_library - -libc = CDLL(find_library('c'), use_errno=True) def is_enabled(): @@ -16,163 +10,162 @@ def is_enabled(): """ with tempfile.TemporaryFile() as fd: try: - set(fd.fileno(), b'name', b'value') + setxattr(fd.fileno(), 'user.name', b'value') except OSError: return False - return get_all(fd.fileno()) == {b'name': b'value'} + return getxattr(fd.fileno(), 'user.name') == b'value' -def set(path_or_fd, name, value): - if isinstance(path_or_fd, int): - fsetxattr(path_or_fd, name, value) +def get_all(path, follow_symlinks=True): + return dict((name, getxattr(path, name, follow_symlinks=follow_symlinks)) + for name in listxattr(path, follow_symlinks=follow_symlinks)) + + +try: + # Currently only available on Python 3.3+ on Linux + from os import getxattr, setxattr, listxattr2 +except ImportError: + from ctypes import CDLL, create_string_buffer, c_ssize_t, c_size_t, c_char_p, c_int, c_uint32, get_errno + from ctypes.util import find_library + libc = CDLL(find_library('c'), use_errno=True) + + def _check(rv, path=None): + if rv < 0: + raise OSError(get_errno(), path) + return rv + + if sys.platform.startswith('linux'): + libc.llistxattr.argtypes = (c_char_p, c_char_p, c_size_t) + libc.llistxattr.restype = c_ssize_t + libc.flistxattr.argtypes = (c_int, c_char_p, c_size_t) + libc.flistxattr.restype = c_ssize_t + libc.lsetxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t, c_int) + libc.lsetxattr.restype = c_int + libc.fsetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t, c_int) + libc.fsetxattr.restype = c_int + libc.lgetxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t) + libc.lgetxattr.restype = c_ssize_t + libc.fgetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t) + libc.fgetxattr.restype = c_ssize_t + + def listxattr(path, *, follow_symlinks=True): + if isinstance(path, str): + path = os.fsencode(path) + if isinstance(path, int): + func = libc.flistxattr + elif follow_symlinks: + func = libc.listxattr + else: + func = libc.llistxattr + n = _check(func(path, None, 0), path) + if n == 0: + return [] + namebuf = create_string_buffer(n) + n2 = _check(func(path, namebuf, n), path) + if n2 != n: + raise Exception('listxattr failed') + return [os.fsdecode(name) for name in namebuf.raw.split(b'\0')[:-1]] + + def getxattr(path, name, *, follow_symlinks=True): + name = os.fsencode(name) + if isinstance(path, str): + path = os.fsencode(path) + if isinstance(path, int): + func = libc.fgetxattr + elif follow_symlinks: + func = libc.getxattr + else: + func = libc.lgetxattr + n = _check(func(path, name, None, 0)) + if n == 0: + return + valuebuf = create_string_buffer(n) + n2 = _check(func(path, name, valuebuf, n), path) + if n2 != n: + raise Exception('getxattr failed') + return valuebuf.raw + + def setxattr(path, name, value, *, follow_symlinks=True): + name = os.fsencode(name) + value = os.fsencode(value) + if isinstance(path, str): + path = os.fsencode(path) + if isinstance(path, int): + func = libc.fsetxattr + elif follow_symlinks: + func = libc.setxattr + else: + func = libc.lsetxattr + _check(func(path, name, value, len(value), 0), path) + + elif sys.platform == 'darwin': + libc.listxattr.argtypes = (c_char_p, c_char_p, c_size_t, c_int) + libc.listxattr.restype = c_ssize_t + libc.flistxattr.argtypes = (c_int, c_char_p, c_size_t) + libc.flistxattr.restype = c_ssize_t + libc.setxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t, c_uint32, c_int) + libc.setxattr.restype = c_int + libc.fsetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t, c_uint32, c_int) + libc.fsetxattr.restype = c_int + libc.getxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t, c_uint32, c_int) + libc.getxattr.restype = c_ssize_t + libc.fgetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t, c_uint32, c_int) + libc.fgetxattr.restype = c_ssize_t + + XATTR_NOFOLLOW = 0x0001 + + def listxattr(path, *, follow_symlinks=True): + func = libc.listxattr + flags = 0 + if isinstance(path, str): + path = os.fsencode(path) + if isinstance(path, int): + func = libc.flistxattr + elif not follow_symlinks: + flags = XATTR_NOFOLLOW + n = _check(func(path, None, 0, flags), path) + if n == 0: + return [] + namebuf = create_string_buffer(n) + n2 = _check(func(path, namebuf, n, flags), path) + if n2 != n: + raise Exception('listxattr failed') + return [os.fsdecode(name) for name in namebuf.raw.split(b'\0')[:-1]] + + def getxattr(path, name, *, follow_symlinks=True): + name = os.fsencode(name) + func = libc.getxattr + flags = 0 + if isinstance(path, str): + path = os.fsencode(path) + func = libc.fgetxattr + if isinstance(path, int): + func = libc.fgetxattr + elif not follow_symlinks: + flags = XATTR_NOFOLLOW + else: + func = libc.lgetxattr + n = _check(func(path, name, None, 0, 0, flags)) + if n == 0: + return + valuebuf = create_string_buffer(n) + n2 = _check(func(path, name, valuebuf, n, 0, flags), path) + if n2 != n: + raise Exception('getxattr failed') + return valuebuf.raw + + def setxattr(path, name, value, *, follow_symlinks=True): + name = os.fsencode(name) + value = os.fsencode(value) + func = libc.setxattr + flags = 0 + if isinstance(path, str): + path = os.fsencode(path) + if isinstance(path, int): + func = libc.fsetxattr + elif not follow_symlinks: + flags = XATTR_NOFOLLOW + _check(func(path, name, value, len(value), 0, flags), path) + else: - lsetxattr(path_or_fd, name, value) - - -def get_all(path_or_fd): - """Return a dictionary with all (user) xattrs for "path_or_fd" - - Symbolic links are not followed - """ - if isinstance(path_or_fd, int): - return dict((name, fgetxattr(path_or_fd, name)) for name in flistxattr(path_or_fd)) - else: - return dict((name, lgetxattr(path_or_fd, name)) for name in llistxattr(path_or_fd)) - - -def _check(rv, path=None): - if rv < 0: - raise OSError(get_errno(), path) - return rv - - -if sys.platform.startswith('linux'): - libc.llistxattr.argtypes = (c_char_p, c_char_p, c_size_t) - libc.llistxattr.restype = c_ssize_t - libc.flistxattr.argtypes = (c_int, c_char_p, c_size_t) - libc.flistxattr.restype = c_ssize_t - libc.lsetxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t, c_int) - libc.lsetxattr.restype = c_int - libc.fsetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t, c_int) - libc.fsetxattr.restype = c_int - libc.lgetxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t) - libc.lgetxattr.restype = c_ssize_t - libc.fgetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t) - libc.fgetxattr.restype = c_ssize_t - - def llistxattr(path): - path = os.fsencode(path) - n = _check(libc.llistxattr(path, None, 0), path) - if n == 0: - [] - namebuf = create_string_buffer(n) - n2 = _check(libc.llistxattr(path, namebuf, n)) - if n2 != n: - raise Exception('llistxattr failed') - return [name[5:] for name in namebuf.raw.split(b'\0')[:-1] if name.startswith(b'user.')] - - def flistxattr(fd): - n = _check(libc.flistxattr(fd, None, 0)) - if n == 0: - [] - namebuf = create_string_buffer(n) - n2 = _check(libc.flistxattr(fd, namebuf, n)) - if n2 != n: - raise Exception('flistxattr failed') - return [name[5:] for name in namebuf.raw.split(b'\0')[:-1] if name.startswith(b'user.')] - - def lsetxattr(path, name, value, flags=0): - _check(libc.lsetxattr(os.fsencode(path), b'user.' + name, value, len(value), flags), path) - - def fsetxattr(fd, name, value, flags=0): - _check(libc.fsetxattr(fd, b'user.' + name, value, len(value), flags)) - - def lgetxattr(path, name): - path = os.fsencode(path) - name = b'user.' + name - n = _check(libc.lgetxattr(path, name, None, 0)) - if n == 0: - return None - valuebuf = create_string_buffer(n) - n2 = _check(libc.lgetxattr(path, name, valuebuf, n), path) - if n2 != n: - raise Exception('lgetxattr failed') - return valuebuf.raw - - def fgetxattr(fd, name): - name = b'user.' + name - n = _check(libc.fgetxattr(fd, name, None, 0)) - if n == 0: - return None - valuebuf = create_string_buffer(n) - n2 = _check(libc.fgetxattr(fd, name, valuebuf, n)) - if n2 != n: - raise Exception('fgetxattr failed') - return valuebuf.raw - -elif sys.platform == 'darwin': - libc.listxattr.argtypes = (c_char_p, c_char_p, c_size_t, c_int) - libc.listxattr.restype = c_ssize_t - libc.flistxattr.argtypes = (c_int, c_char_p, c_size_t) - libc.flistxattr.restype = c_ssize_t - libc.setxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t, c_uint32, c_int) - libc.setxattr.restype = c_int - libc.fsetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t, c_uint32, c_int) - libc.fsetxattr.restype = c_int - libc.getxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t, c_uint32, c_int) - libc.getxattr.restype = c_ssize_t - libc.fgetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t, c_uint32, c_int) - libc.fgetxattr.restype = c_ssize_t - - XATTR_NOFOLLOW = 0x0001 - - def llistxattr(path): - path = os.fsencode(path) - n = _check(libc.listxattr(path, None, 0, XATTR_NOFOLLOW), path) - if n == 0: - [] - namebuf = create_string_buffer(n) - n2 = _check(libc.listxattr(path, namebuf, n, XATTR_NOFOLLOW)) - if n2 != n: - raise Exception('llistxattr failed') - return namebuf.raw.split(b'\0')[:-1] - - def flistxattr(fd): - n = _check(libc.flistxattr(fd, None, 0, 0)) - if n == 0: - [] - namebuf = create_string_buffer(n) - n2 = _check(libc.flistxattr(fd, namebuf, n, 0)) - if n2 != n: - raise Exception('flistxattr failed') - return namebuf.raw.split(b'\0')[:-1] - - def lsetxattr(path, name, value, flags=XATTR_NOFOLLOW): - _check(libc.setxattr(os.fsencode(path), name, value, len(value), 0, flags), path) - - def fsetxattr(fd, name, value, flags=0): - _check(libc.fsetxattr(fd, name, value, len(value), 0, flags)) - - def lgetxattr(path, name): - path = os.fsencode(path) - n = _check(libc.getxattr(path, name, None, 0, 0, XATTR_NOFOLLOW), path) - if n == 0: - return None - valuebuf = create_string_buffer(n) - n2 = _check(libc.getxattr(path, name, valuebuf, n, 0, XATTR_NOFOLLOW)) - if n2 != n: - raise Exception('getxattr failed') - return valuebuf.raw - - def fgetxattr(fd, name): - n = _check(libc.fgetxattr(fd, name, None, 0, 0, 0)) - if n == 0: - return None - valuebuf = create_string_buffer(n) - n2 = _check(libc.fgetxattr(fd, name, valuebuf, n, 0, 0)) - if n2 != n: - Exception('fgetxattr failed') - return valuebuf.raw - -else: - raise Exception('Unsupported platform: %s' % sys.platform) + raise Exception('Unsupported platform: %s' % sys.platform)