diff --git a/src/borg/archive.py b/src/borg/archive.py index adfcf7a28..020193ff0 100644 --- a/src/borg/archive.py +++ b/src/borg/archive.py @@ -719,7 +719,7 @@ Utilization of max. archive size: {csize_max:.0%} except OSError: # some systems don't support calling utime on a symlink pass - acl_set(path, item, self.numeric_owner) + acl_set(path, item, self.numeric_owner, fd=fd) # chown removes Linux capabilities, so set the extended attributes at the end, after chown, since they include # the Linux capabilities in the "security.capability" attribute. warning = xattr.set_all(fd or path, item.get('xattrs', {}), follow_symlinks=False) @@ -954,7 +954,7 @@ class MetadataCollector: xattrs = xattr.get_all(fd or path, follow_symlinks=False) if not self.nobsdflags: bsdflags = get_flags(path, st) - acl_get(path, attrs, st, self.numeric_owner) + acl_get(path, attrs, st, self.numeric_owner, fd=fd) if xattrs: attrs['xattrs'] = StableDict(xattrs) if bsdflags: diff --git a/src/borg/platform/darwin.pyx b/src/borg/platform/darwin.pyx index 3d412d7a5..ecd287ec7 100644 --- a/src/borg/platform/darwin.pyx +++ b/src/borg/platform/darwin.pyx @@ -29,7 +29,9 @@ cdef extern from "sys/acl.h": int acl_free(void *obj) acl_t acl_get_link_np(const char *path, int type) + acl_t acl_get_fd_np(int fd, int type) int acl_set_link_np(const char *path, int type, acl_t acl) + int acl_set_fd_np(int fd, acl_t acl, int type) acl_t acl_from_text(const char *buf) char *acl_to_text(acl_t acl, ssize_t *len_p) int ACL_TYPE_EXTENDED @@ -108,11 +110,16 @@ def _remove_non_numeric_identifier(acl): return safe_encode('\n'.join(entries)) -def acl_get(path, item, st, numeric_owner=False): +def acl_get(path, item, st, numeric_owner=False, fd=None): cdef acl_t acl = NULL cdef char *text = NULL + if isinstance(path, str): + path = os.fsencode(path) try: - acl = acl_get_link_np(os.fsencode(path), ACL_TYPE_EXTENDED) + if fd is not None: + acl = acl_get_fd_np(fd, ACL_TYPE_EXTENDED) + else: + acl = acl_get_link_np(path, ACL_TYPE_EXTENDED) if acl == NULL: return text = acl_to_text(acl, NULL) @@ -127,7 +134,7 @@ def acl_get(path, item, st, numeric_owner=False): acl_free(acl) -def acl_set(path, item, numeric_owner=False): +def acl_set(path, item, numeric_owner=False, fd=None): cdef acl_t acl = NULL acl_text = item.get('acl_extended') if acl_text is not None: @@ -138,7 +145,11 @@ def acl_set(path, item, numeric_owner=False): acl = acl_from_text(_remove_numeric_id_if_possible(acl_text)) if acl == NULL: return - if acl_set_link_np(os.fsencode(path), ACL_TYPE_EXTENDED, acl): - return + if isinstance(path, str): + path = os.fsencode(path) + if fd is not None: + acl_set_fd_np(fd, acl, ACL_TYPE_EXTENDED) + else: + acl_set_link_np(path, ACL_TYPE_EXTENDED, acl) finally: acl_free(acl) diff --git a/src/borg/platform/freebsd.pyx b/src/borg/platform/freebsd.pyx index f44adeafe..92ee430a2 100644 --- a/src/borg/platform/freebsd.pyx +++ b/src/borg/platform/freebsd.pyx @@ -37,7 +37,9 @@ cdef extern from "sys/acl.h": int acl_free(void *obj) acl_t acl_get_link_np(const char *path, int type) + acl_t acl_get_fd_np(int fd, int type) int acl_set_link_np(const char *path, int type, acl_t acl) + int acl_set_fd_np(int fd, acl_t acl, int type) 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 @@ -89,10 +91,13 @@ def setxattr(path, name, value, *, follow_symlinks=True): _setxattr_inner(func, path, name, value) -cdef _get_acl(p, type, item, attribute, int flags): +cdef _get_acl(p, type, item, attribute, flags, fd=None): cdef acl_t acl cdef char *text - acl = acl_get_link_np(p, type) + if fd is not None: + acl = acl_get_fd_np(fd, type) + else: + acl = acl_get_link_np(p, type) if acl: text = acl_to_text_np(acl, NULL, flags) if text: @@ -101,25 +106,26 @@ cdef _get_acl(p, type, item, attribute, int flags): acl_free(acl) -def acl_get(path, item, st, numeric_owner=False): +def acl_get(path, item, st, numeric_owner=False, fd=None): """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 isinstance(path, str): + path = os.fsencode(path) + ret = lpathconf(path, _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, 'acl_nfs4', flags) + _get_acl(path, ACL_TYPE_NFS4, item, 'acl_nfs4', flags, fd=fd) else: - _get_acl(p, ACL_TYPE_ACCESS, item, 'acl_access', flags) - _get_acl(p, ACL_TYPE_DEFAULT, item, 'acl_default', flags) + _get_acl(path, ACL_TYPE_ACCESS, item, 'acl_access', flags, fd=fd) + _get_acl(path, ACL_TYPE_DEFAULT, item, 'acl_default', flags, fd=fd) -cdef _set_acl(p, type, item, attribute, numeric_owner=False): +cdef _set_acl(p, type, item, attribute, numeric_owner=False, fd=None): cdef acl_t acl text = item.get(attribute) if text: @@ -129,7 +135,10 @@ cdef _set_acl(p, type, item, attribute, numeric_owner=False): text = posix_acl_use_stored_uid_gid(text) acl = acl_from_text(text) if acl: - acl_set_link_np(p, type, acl) + if fd is not None: + acl_set_fd_np(fd, acl, type) + else: + acl_set_link_np(p, type, acl) acl_free(acl) @@ -147,13 +156,14 @@ cdef _nfs4_use_stored_uid_gid(acl): return safe_encode('\n'.join(entries)) -def acl_set(path, item, numeric_owner=False): +def acl_set(path, item, numeric_owner=False, fd=None): """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, 'acl_nfs4', numeric_owner) - _set_acl(p, ACL_TYPE_ACCESS, item, 'acl_access', numeric_owner) - _set_acl(p, ACL_TYPE_DEFAULT, item, 'acl_default', numeric_owner) + if isinstance(path, str): + path = os.fsencode(path) + _set_acl(path, ACL_TYPE_NFS4, item, 'acl_nfs4', numeric_owner, fd=fd) + _set_acl(path, ACL_TYPE_ACCESS, item, 'acl_access', numeric_owner, fd=fd) + _set_acl(path, ACL_TYPE_DEFAULT, item, 'acl_default', numeric_owner, fd=fd) diff --git a/src/borg/platform/linux.pyx b/src/borg/platform/linux.pyx index e230381a7..41476ed02 100644 --- a/src/borg/platform/linux.pyx +++ b/src/borg/platform/linux.pyx @@ -39,12 +39,15 @@ cdef extern from "sys/acl.h": int acl_free(void *obj) acl_t acl_get_file(const char *path, int type) + acl_t acl_get_fd(int fd) int acl_set_file(const char *path, int type, acl_t acl) + int acl_set_fd(int fd, acl_t acl) acl_t acl_from_text(const char *buf) char *acl_to_text(acl_t acl, ssize_t *len) cdef extern from "acl/libacl.h": int acl_extended_file(const char *path) + int acl_extended_fd(int fd) cdef extern from "fcntl.h": int sync_file_range(int fd, int64_t offset, int64_t nbytes, unsigned int flags) @@ -221,26 +224,37 @@ cdef acl_numeric_ids(acl): return safe_encode('\n'.join(entries)) -def acl_get(path, item, st, numeric_owner=False): +def acl_get(path, item, st, numeric_owner=False, fd=None): cdef acl_t default_acl = NULL cdef acl_t access_acl = NULL cdef char *default_text = NULL cdef char *access_text = NULL - p = os.fsencode(path) - if stat.S_ISLNK(st.st_mode) or acl_extended_file(p) <= 0: + if fd is None and isinstance(path, str): + path = os.fsencode(path) + if stat.S_ISLNK(st.st_mode): + return + if (fd is not None and acl_extended_fd(fd) <= 0 + or + fd is None and acl_extended_file(path) <= 0): return if numeric_owner: converter = acl_numeric_ids else: converter = acl_append_numeric_ids try: - access_acl = acl_get_file(p, ACL_TYPE_ACCESS) + if fd is not None: + # we only have a fd for FILES (not other fs objects), so we can get the access_acl: + assert stat.S_ISREG(st.st_mode) + access_acl = acl_get_fd(fd) + else: + # if we have no fd, it can be anything + access_acl = acl_get_file(path, ACL_TYPE_ACCESS) + default_acl = acl_get_file(path, ACL_TYPE_DEFAULT) if access_acl: access_text = acl_to_text(access_acl, NULL) if access_text: item['acl_access'] = converter(access_text) - default_acl = acl_get_file(p, ACL_TYPE_DEFAULT) if default_acl: default_text = acl_to_text(default_acl, NULL) if default_text: @@ -252,32 +266,41 @@ def acl_get(path, item, st, numeric_owner=False): acl_free(access_acl) -def acl_set(path, item, numeric_owner=False): +def acl_set(path, item, numeric_owner=False, fd=None): cdef acl_t access_acl = NULL cdef acl_t default_acl = NULL - p = os.fsencode(path) + if fd is None and isinstance(path, str): + path = os.fsencode(path) if numeric_owner: converter = posix_acl_use_stored_uid_gid else: converter = acl_use_local_uid_gid access_text = item.get('acl_access') - default_text = item.get('acl_default') if access_text: try: access_acl = acl_from_text(converter(access_text)) if access_acl: - acl_set_file(p, ACL_TYPE_ACCESS, access_acl) + if fd is not None: + acl_set_fd(fd, access_acl) + else: + acl_set_file(path, ACL_TYPE_ACCESS, access_acl) finally: acl_free(access_acl) + default_text = item.get('acl_default') if default_text: try: default_acl = acl_from_text(converter(default_text)) if default_acl: - acl_set_file(p, ACL_TYPE_DEFAULT, default_acl) + # default acls apply only to directories + if False and fd is not None: # Linux API seems to not support this + acl_set_fd(fd, default_acl) + else: + acl_set_file(path, ACL_TYPE_DEFAULT, default_acl) finally: acl_free(default_acl) + cdef _sync_file_range(fd, offset, length, flags): assert offset & PAGE_MASK == 0, "offset %d not page-aligned" % offset assert length & PAGE_MASK == 0, "length %d not page-aligned" % length