瀏覽代碼

Experimental FreeBSD ACL support (nfs4 and posix) (#66)

Jonas Borgström 11 年之前
父節點
當前提交
8c25d02481
共有 6 個文件被更改,包括 132 次插入25 次删除
  1. 1 1
      CHANGES
  2. 1 16
      attic/helpers.py
  3. 2 0
      attic/platform.py
  4. 100 0
      attic/platform_freebsd.pyx
  5. 20 4
      attic/platform_linux.pyx
  6. 8 4
      setup.py

+ 1 - 1
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
 

+ 1 - 16
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 = []

+ 2 - 0
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
 

+ 100 - 0
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(<bytes>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)

+ 20 - 4
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')

+ 8 - 4
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',