From 8c25d024813c8a0a06333750f0c68187dccb5870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Borgstr=C3=B6m?= Date: Sun, 27 Apr 2014 14:17:09 +0200 Subject: [PATCH] Experimental FreeBSD ACL support (nfs4 and posix) (#66) --- CHANGES | 2 +- attic/helpers.py | 17 +------ attic/platform.py | 2 + attic/platform_freebsd.pyx | 100 +++++++++++++++++++++++++++++++++++++ attic/platform_linux.pyx | 24 +++++++-- setup.py | 12 +++-- 6 files changed, 132 insertions(+), 25 deletions(-) create mode 100644 attic/platform_freebsd.pyx diff --git a/CHANGES b/CHANGES index 05ca870df..e35c704d6 100644 --- a/CHANGES +++ b/CHANGES @@ -8,7 +8,7 @@ Version 0.13 (feature release, released on X) -- Experimental Linux ACL support (#66) +- Experimental Linux 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 diff --git a/attic/helpers.py b/attic/helpers.py index 1f3b50440..c99965c94 100644 --- a/attic/helpers.py +++ b/attic/helpers.py @@ -361,22 +361,7 @@ def group2gid(group, default=None): return default -def acl_use_local_uid_gid(acl): - """Replace the user/group field with the local uid/gid if possible - """ - entries = [] - for entry in acl.decode('ascii').split('\n'): - if entry: - fields = entry.split(':') - if fields[0] == 'user' and fields[1]: - fields[1] = user2uid(fields[1], fields[3]) - elif fields[0] == 'group' and fields[1]: - fields[1] = group2gid(fields[1], fields[3]) - entries.append(':'.join(entry.split(':')[:3])) - return ('\n'.join(entries)).encode('ascii') - - -def acl_use_stored_uid_gid(acl): +def posix_acl_use_stored_uid_gid(acl): """Replace the user/group field with the stored uid/gid """ entries = [] diff --git a/attic/platform.py b/attic/platform.py index c33ac7f9c..9f443003a 100644 --- a/attic/platform.py +++ b/attic/platform.py @@ -4,6 +4,8 @@ platform = os.uname()[0] 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 else: API_VERSION = 1 diff --git a/attic/platform_freebsd.pyx b/attic/platform_freebsd.pyx new file mode 100644 index 000000000..c3a9a0662 --- /dev/null +++ b/attic/platform_freebsd.pyx @@ -0,0 +1,100 @@ +import os +from attic.helpers import posix_acl_use_stored_uid_gid + +API_VERSION = 1 + +cdef extern from "errno.h": + int errno + int EINVAL + +cdef extern from "sys/types.h": + int ACL_TYPE_ACCESS + int ACL_TYPE_DEFAULT + int ACL_TYPE_NFS4 + +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_np(acl_t acl, ssize_t *len, int flags) + int ACL_TEXT_NUMERIC_IDS + int ACL_TEXT_APPEND_ID + +cdef extern from "unistd.h": + long lpathconf(const char *path, int name) + int _PC_ACL_NFS4 + + +cdef _get_acl(p, type, item, attribute, int flags): + cdef acl_t acl + cdef char *text + acl = acl_get_link_np(p, type) + if acl: + text = acl_to_text_np(acl, NULL, flags) + if text: + item[attribute] = text + acl_free(text) + acl_free(acl) + + +def acl_get(path, item, numeric_owner=False): + """Saves ACL Entries + + If `numeric_owner` is True the user/group field is not preserved only uid/gid + """ + cdef int flags = ACL_TEXT_APPEND_ID + p = os.fsencode(path) + ret = lpathconf(p, _PC_ACL_NFS4) + if ret < 0 and errno == EINVAL: + return + flags |= ACL_TEXT_NUMERIC_IDS if numeric_owner else 0 + if ret > 0: + _get_acl(p, ACL_TYPE_NFS4, item, b'acl_nfs4', flags) + else: + _get_acl(p, ACL_TYPE_ACCESS, item, b'acl_access', flags) + _get_acl(p, ACL_TYPE_DEFAULT, item, b'acl_default', flags) + + +cdef _set_acl(p, type, item, attribute, numeric_owner=False): + cdef acl_t acl + text = item.get(attribute) + if text: + if numeric_owner and type == ACL_TYPE_NFS4: + text = _nfs4_use_stored_uid_gid(text) + elif numeric_owner and type in(ACL_TYPE_ACCESS, ACL_TYPE_DEFAULT): + text = posix_acl_use_stored_uid_gid(text) + acl = acl_from_text(text) + if acl: + acl_set_link_np(p, type, acl) + acl_free(acl) + + +cdef _nfs4_use_stored_uid_gid(acl): + """Replace the user/group field with the stored uid/gid + """ + entries = [] + for entry in acl.decode('ascii').split('\n'): + if entry: + if entry.startswith('user:') or entry.startswith('group:'): + fields = entry.split(':') + entries.append(':'.join(fields[0], fields[5], *fields[2:-1])) + else: + entries.append(entry) + return ('\n'.join(entries)).encode('ascii') + + +def acl_set(path, item, numeric_owner=False): + """Restore ACL Entries + + If `numeric_owner` is True the stored uid/gid is used instead + of the user/group names + """ + p = os.fsencode(path) + _set_acl(p, ACL_TYPE_NFS4, item, b'acl_nfs4', numeric_owner) + _set_acl(p, ACL_TYPE_ACCESS, item, b'acl_access', numeric_owner) + _set_acl(p, ACL_TYPE_DEFAULT, item, b'acl_default', numeric_owner) diff --git a/attic/platform_linux.pyx b/attic/platform_linux.pyx index 368a2027b..d9aa14c3c 100644 --- a/attic/platform_linux.pyx +++ b/attic/platform_linux.pyx @@ -1,6 +1,6 @@ import os import re -from attic.helpers import acl_use_local_uid_gid, acl_use_stored_uid_gid, user2uid, group2gid +from attic.helpers import posix_acl_use_stored_uid_gid, user2uid, group2gid API_VERSION = 1 @@ -25,7 +25,23 @@ cdef extern from "acl/libacl.h": _comment_re = re.compile(' *#.*', re.M) -def acl_append_numeric_ids(acl): + +cdef acl_use_local_uid_gid(acl): + """Replace the user/group field with the local uid/gid if possible + """ + entries = [] + for entry in acl.decode('ascii').split('\n'): + if entry: + fields = entry.split(':') + if fields[0] == 'user' and fields[1]: + fields[1] = user2uid(fields[1], fields[3]) + elif fields[0] == 'group' and fields[1]: + fields[1] = group2gid(fields[1], fields[3]) + entries.append(':'.join(entry.split(':')[:3])) + return ('\n'.join(entries)).encode('ascii') + + +cdef acl_append_numeric_ids(acl): """Extend the "POSIX 1003.1e draft standard 17" format with an additional uid/gid field """ entries = [] @@ -41,7 +57,7 @@ def acl_append_numeric_ids(acl): return ('\n'.join(entries)).encode('ascii') -def acl_numeric_ids(acl): +cdef acl_numeric_ids(acl): """Replace the "POSIX 1003.1e draft standard 17" user/group field with uid/gid """ entries = [] @@ -100,7 +116,7 @@ def acl_set(path, item, numeric_owner=False): cdef acl_t access_acl = NULL cdef acl_t default_acl = NULL if numeric_owner: - converter = acl_use_stored_uid_gid + converter = posix_acl_use_stored_uid_gid else: converter = acl_use_local_uid_gid access_text = item.get(b'acl_access') diff --git a/setup.py b/setup.py index cd4769aad..b0bff66c5 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_freebsd_source = 'attic/platform_freebsd.pyx' try: from Cython.Distutils import build_ext @@ -38,7 +39,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']) + 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']) super(Sdist, self).make_distribution() except ImportError: @@ -49,9 +50,10 @@ except ImportError: crypto_source = crypto_source.replace('.pyx', '.c') chunker_source = chunker_source.replace('.pyx', '.c') hashindex_source = hashindex_source.replace('.pyx', '.c') - acl_source = platform_linux_source.replace('.pyx', '.c') + platform_linux_source = platform_linux_source.replace('.pyx', '.c') + platform_freebsd_source = platform_freebsd_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, acl_source]): + 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') @@ -87,11 +89,13 @@ ext_modules = [ ] 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])) setup( name='Attic', version=versioneer.get_version(), - author='Jonas Borgström', + author='Jonas Borgstrom', author_email='jonas@borgstrom.se', url='https://attic-backup.org/', description='Deduplicated backups',