diff --git a/CHANGES b/CHANGES index e54ff83ec..9c728fa31 100644 --- a/CHANGES +++ b/CHANGES @@ -9,7 +9,7 @@ Version 0.13 (feature release, released on X) - Reduced memory usage when backing up many small files (#69) -- Experimental Linux and FreeBSD ACL support (#66) +- Experimental Linux, OS X and FreeBSD ACL support (#66) - Added support for backup and restore of BSDFlags (OSX, FreeBSD) (#56) - Fix bug where xattrs on symlinks were not correctly restored - Added cachedir support. CACHEDIR.TAG compatible cache directories diff --git a/attic/platform.py b/attic/platform.py index 9f443003a..5e0ec917b 100644 --- a/attic/platform.py +++ b/attic/platform.py @@ -6,6 +6,8 @@ if platform == 'Linux': from attic.platform_linux import acl_get, acl_set, API_VERSION elif platform == 'FreeBSD': from attic.platform_freebsd import acl_get, acl_set, API_VERSION +elif platform == 'Darwin': + from attic.platform_darwin import acl_get, acl_set, API_VERSION else: API_VERSION = 1 diff --git a/attic/platform_darwin.pyx b/attic/platform_darwin.pyx new file mode 100644 index 000000000..c63bb1ff2 --- /dev/null +++ b/attic/platform_darwin.pyx @@ -0,0 +1,46 @@ +import os +API_VERSION = 1 + +cdef extern from "sys/acl.h": + ctypedef struct _acl_t: + pass + ctypedef _acl_t *acl_t + + int acl_free(void *obj) + acl_t acl_get_link_np(const char *path, int type) + acl_t acl_set_link_np(const char *path, int type, acl_t acl) + acl_t acl_from_text(const char *buf) + char *acl_to_text(acl_t acl, ssize_t *len_p) + int ACL_TYPE_EXTENDED + + +def acl_get(path, item, numeric_owner=False): + cdef acl_t acl = NULL + cdef char *text = NULL + try: + acl = acl_get_link_np(os.fsencode(path), ACL_TYPE_EXTENDED) + if acl == NULL: + return + text = acl_to_text(acl, NULL) + if text == NULL: + return + item[b'acl_extended'] = text + finally: + acl_free(text) + acl_free(acl) + + +def acl_set(path, item, numeric_owner=False): + cdef acl_t acl = NULL + try: + try: + acl = acl_from_text(item[b'acl_extended']) + except KeyError: + return + if acl == NULL: + return + if acl_set_link_np(os.fsencode(path), ACL_TYPE_EXTENDED, acl): + return + finally: + acl_free(acl) + diff --git a/attic/testsuite/platform.py b/attic/testsuite/platform.py index 6625efeb5..7521b2077 100644 --- a/attic/testsuite/platform.py +++ b/attic/testsuite/platform.py @@ -71,3 +71,30 @@ class PlatformLinuxTestCase(AtticTestCase): self.assert_equal(self.get_acl(self.tmpdir)[b'acl_access'], ACCESS_ACL) self.assert_equal(self.get_acl(self.tmpdir)[b'acl_default'], DEFAULT_ACL) + +@unittest.skipUnless(sys.platform.startswith('darwin'), 'OS X only test') +@unittest.skipIf(fakeroot_detected(), 'not compatible with fakeroot') +class PlatformDarwinTestCase(AtticTestCase): + + def setUp(self): + self.tmpdir = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self.tmpdir) + + def get_acl(self, path, numeric_owner=False): + item = {} + acl_get(path, item, numeric_owner=numeric_owner) + return item + + def set_acl(self, path, acl, numeric_owner=False): + item = {b'acl_extended': acl} + acl_set(path, item, numeric_owner=numeric_owner) + + def test_access_acl(self): + file = tempfile.NamedTemporaryFile() + self.assert_equal(self.get_acl(file.name), {}) + self.set_acl(file.name, b'!#acl 1\ngroup:ABCDEFAB-CDEF-ABCD-EFAB-CDEF00000014:staff:9999:allow:read\nuser:FFFFEEEE-DDDD-CCCC-BBBB-AAAA00000000:root:0:allow:read\n', numeric_owner=False) + self.assert_in(b'group:ABCDEFAB-CDEF-ABCD-EFAB-CDEF00000014:staff:20:allow:read', self.get_acl(file.name)[b'acl_extended']) + self.assert_in(b'user:FFFFEEEE-DDDD-CCCC-BBBB-AAAA00000000:root:0:allow:read', self.get_acl(file.name)[b'acl_extended']) + diff --git a/setup.py b/setup.py index b0bff66c5..bdc96cded 100644 --- a/setup.py +++ b/setup.py @@ -25,6 +25,7 @@ crypto_source = 'attic/crypto.pyx' chunker_source = 'attic/chunker.pyx' hashindex_source = 'attic/hashindex.pyx' platform_linux_source = 'attic/platform_linux.pyx' +platform_darwin_source = 'attic/platform_darwin.pyx' platform_freebsd_source = 'attic/platform_freebsd.pyx' try: @@ -39,7 +40,7 @@ try: versioneer.cmd_sdist.__init__(self, *args, **kwargs) def make_distribution(self): - self.filelist.extend(['attic/crypto.c', 'attic/chunker.c', 'attic/_chunker.c', 'attic/hashindex.c', 'attic/_hashindex.c', 'attic/platform_linux.c', 'attic/platform_freebsd.c']) + self.filelist.extend(['attic/crypto.c', 'attic/chunker.c', 'attic/_chunker.c', 'attic/hashindex.c', 'attic/_hashindex.c', 'attic/platform_linux.c', 'attic/platform_freebsd.c', 'attic/platform_darwin.c']) super(Sdist, self).make_distribution() except ImportError: @@ -52,6 +53,7 @@ except ImportError: hashindex_source = hashindex_source.replace('.pyx', '.c') platform_linux_source = platform_linux_source.replace('.pyx', '.c') platform_freebsd_source = platform_freebsd_source.replace('.pyx', '.c') + platform_darwin_source = platform_darwin_source.replace('.pyx', '.c') from distutils.command.build_ext import build_ext if not all(os.path.exists(path) for path in [crypto_source, chunker_source, hashindex_source, platform_linux_source, platform_freebsd_source]): raise ImportError('The GIT version of Attic needs Cython. Install Cython or use a released version') @@ -91,6 +93,8 @@ if platform == 'Linux': ext_modules.append(Extension('attic.platform_linux', [platform_linux_source], libraries=['acl'])) elif platform == 'FreeBSD': ext_modules.append(Extension('attic.platform_freebsd', [platform_freebsd_source])) +elif platform == 'Darwin': + ext_modules.append(Extension('attic.platform_darwin', [platform_darwin_source])) setup( name='Attic',