]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
tar-util: add support for acls
authorLennart Poettering <lennart@poettering.net>
Fri, 22 Aug 2025 09:05:38 +0000 (11:05 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 4 Nov 2025 13:12:39 +0000 (14:12 +0100)
src/shared/acl-util.c
src/shared/acl-util.h
src/shared/libarchive-util.c
src/shared/libarchive-util.h
src/shared/tar-util.c

index 554d4738213a1c8ba8da6310fd2662e2e604b273..d16c165ed712dc5d2c12908519bef59298cf39c9 100644 (file)
@@ -23,6 +23,7 @@ DLSYM_PROTOTYPE(acl_delete_entry);
 DLSYM_PROTOTYPE(acl_delete_perm);
 DLSYM_PROTOTYPE(acl_dup);
 DLSYM_PROTOTYPE(acl_entries);
+DLSYM_PROTOTYPE(acl_extended_file);
 DLSYM_PROTOTYPE(acl_free);
 DLSYM_PROTOTYPE(acl_from_mode);
 DLSYM_PROTOTYPE(acl_from_text);
@@ -36,6 +37,7 @@ DLSYM_PROTOTYPE(acl_get_tag_type);
 DLSYM_PROTOTYPE(acl_init);
 DLSYM_PROTOTYPE(acl_set_fd);
 DLSYM_PROTOTYPE(acl_set_file);
+DLSYM_PROTOTYPE(acl_set_permset);
 DLSYM_PROTOTYPE(acl_set_qualifier);
 DLSYM_PROTOTYPE(acl_set_tag_type);
 DLSYM_PROTOTYPE(acl_to_any_text);
@@ -58,6 +60,7 @@ int dlopen_libacl(void) {
                         DLSYM_ARG(acl_delete_perm),
                         DLSYM_ARG(acl_dup),
                         DLSYM_ARG(acl_entries),
+                        DLSYM_ARG(acl_extended_file),
                         DLSYM_ARG(acl_free),
                         DLSYM_ARG(acl_from_mode),
                         DLSYM_ARG(acl_from_text),
@@ -71,6 +74,7 @@ int dlopen_libacl(void) {
                         DLSYM_ARG(acl_init),
                         DLSYM_ARG(acl_set_fd),
                         DLSYM_ARG(acl_set_file),
+                        DLSYM_ARG(acl_set_permset),
                         DLSYM_ARG(acl_set_qualifier),
                         DLSYM_ARG(acl_set_tag_type),
                         DLSYM_ARG(acl_to_any_text));
index ea4aca7bdbe74f8ace6094bbb7872c495f667177..7f6650013b3c7df82e5d41c33dbfa76f77656b5a 100644 (file)
@@ -20,6 +20,7 @@ extern DLSYM_PROTOTYPE(acl_delete_entry);
 extern DLSYM_PROTOTYPE(acl_delete_perm);
 extern DLSYM_PROTOTYPE(acl_dup);
 extern DLSYM_PROTOTYPE(acl_entries);
+extern DLSYM_PROTOTYPE(acl_extended_file);
 extern DLSYM_PROTOTYPE(acl_free);
 extern DLSYM_PROTOTYPE(acl_from_mode);
 extern DLSYM_PROTOTYPE(acl_from_text);
@@ -33,6 +34,7 @@ extern DLSYM_PROTOTYPE(acl_get_tag_type);
 extern DLSYM_PROTOTYPE(acl_init);
 extern DLSYM_PROTOTYPE(acl_set_fd);
 extern DLSYM_PROTOTYPE(acl_set_file);
+extern DLSYM_PROTOTYPE(acl_set_permset);
 extern DLSYM_PROTOTYPE(acl_set_qualifier);
 extern DLSYM_PROTOTYPE(acl_set_tag_type);
 extern DLSYM_PROTOTYPE(acl_to_any_text);
index 8d8e775c5174fee4dc0be12c6e7af80eecc9bf5c..09f036895ce2cceec944a2bb796fc66e45fc0c23 100644 (file)
@@ -7,6 +7,9 @@
 #if HAVE_LIBARCHIVE
 static void *libarchive_dl = NULL;
 
+DLSYM_PROTOTYPE(archive_entry_acl_add_entry) = NULL;
+DLSYM_PROTOTYPE(archive_entry_acl_next) = NULL;
+DLSYM_PROTOTYPE(archive_entry_acl_reset) = NULL;
 DLSYM_PROTOTYPE(archive_entry_fflags) = NULL;
 DLSYM_PROTOTYPE(archive_entry_filetype) = NULL;
 DLSYM_PROTOTYPE(archive_entry_free) = NULL;
@@ -76,6 +79,9 @@ int dlopen_libarchive(void) {
                         &libarchive_dl,
                         "libarchive.so.13",
                         LOG_DEBUG,
+                        DLSYM_ARG(archive_entry_acl_add_entry),
+                        DLSYM_ARG(archive_entry_acl_next),
+                        DLSYM_ARG(archive_entry_acl_reset),
                         DLSYM_ARG(archive_entry_fflags),
                         DLSYM_ARG(archive_entry_filetype),
                         DLSYM_ARG(archive_entry_free),
index 9a5d4e0b402b187a8c96aefe527e2d2d0786f3fe..615cd1ec810ce0fb72a61f7ae23656291aaab193 100644 (file)
@@ -9,6 +9,9 @@
 
 #include "dlfcn-util.h"
 
+extern DLSYM_PROTOTYPE(archive_entry_acl_add_entry);
+extern DLSYM_PROTOTYPE(archive_entry_acl_next);
+extern DLSYM_PROTOTYPE(archive_entry_acl_reset);
 extern DLSYM_PROTOTYPE(archive_entry_fflags);
 extern DLSYM_PROTOTYPE(archive_entry_filetype);
 extern DLSYM_PROTOTYPE(archive_entry_free);
index 54f3644ad1bd7f10ed4ac1b53edfda710e7ac2f9..a4d0578b255e45fab048ca36f953afd8e6bbb0aa 100644 (file)
@@ -8,6 +8,7 @@
 #include <sys/mount.h>
 #include <sys/sysmacros.h>
 
+#include "acl-util.h"
 #include "alloc-util.h"
 #include "chase.h"
 #include "chattr-util.h"
@@ -57,6 +58,7 @@ typedef struct OpenInode {
         unsigned fflags;
         XAttr *xattr;
         size_t n_xattr;
+        acl_t acl_access, acl_default;
 } OpenInode;
 
 static void xattr_done(XAttr *xa) {
@@ -85,6 +87,10 @@ static void open_inode_done(OpenInode *of) {
                 of->path = mfree(of->path);
         }
         xattr_done_many(of->xattr, of->n_xattr);
+        if (of->acl_access)
+                sym_acl_free(of->acl_access);
+        if (of->acl_default)
+                sym_acl_free(of->acl_default);
 }
 
 static void open_inode_done_many(OpenInode *array, size_t n) {
@@ -96,6 +102,29 @@ static void open_inode_done_many(OpenInode *array, size_t n) {
         free(array);
 }
 
+static int open_inode_apply_acl(OpenInode *of) {
+        int r = 0;
+
+        assert(of);
+        assert(of->fd >= 0);
+
+        if (!inode_type_can_acl(of->filetype))
+                return 0;
+
+        if (of->acl_access) {
+                if (sym_acl_set_fd(of->fd, of->acl_access) < 0)
+                        RET_GATHER(r, log_error_errno(errno, "Failed to adjust ACLs of '%s': %m", of->path));
+        }
+
+        if (of->filetype == S_IFDIR && of->acl_default) {
+                /* There's no API to set default ACLs by fd, hence go by /proc/self/fd/ path */
+                if (sym_acl_set_file(FORMAT_PROC_FD_PATH(of->fd), ACL_TYPE_DEFAULT, of->acl_default) < 0)
+                        RET_GATHER(r, log_error_errno(errno, "Failed to adjust default ACLs of '%s': %m", of->path));
+        }
+
+        return r;
+}
+
 static int open_inode_finalize(OpenInode *of) {
         int r = 0;
 
@@ -116,6 +145,10 @@ static int open_inode_finalize(OpenInode *of) {
                                 RET_GATHER(r, log_error_errno(k, "Failed to adjust ownership/mode of '%s': %m", of->path));
                 }
 
+                k = open_inode_apply_acl(of);
+                if (k < 0)
+                        RET_GATHER(r, log_error_errno(k, "Failed to adjust ACL of '%s': %m", of->path));
+
                 if ((of->fflags & ~CHATTR_EARLY_FL) != 0 && inode_type_can_chattr(of->filetype)) {
                         k = chattr_full(of->fd,
                                         /* path= */ NULL,
@@ -386,6 +419,119 @@ static int archive_entry_pathname_safe(struct archive_entry *entry, const char *
         return 0;
 }
 
+static int archive_entry_read_acl(
+                struct archive_entry *entry,
+                acl_type_t ntype,
+                acl_t *acl) {
+
+        int r;
+
+        assert(entry);
+        assert(acl);
+
+        int type;
+        if (ntype == ACL_TYPE_ACCESS)
+                type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS;
+        else if (ntype == ACL_TYPE_DEFAULT)
+                type = ARCHIVE_ENTRY_ACL_TYPE_DEFAULT;
+        else
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unexpected ACL type");
+
+        int c = sym_archive_entry_acl_reset(entry, type);
+        if (c == 0)
+                return 0;
+        assert(c > 0);
+
+        r = dlopen_libacl();
+        if (r < 0) {
+                log_debug_errno(r, "Not restoring ACL data on inode as libacl is not available: %m");
+                return 0;
+        }
+
+        _cleanup_(acl_freep) acl_t a = NULL;
+        a = sym_acl_init(c);
+        if (!a)
+                return log_oom();
+
+        for (;;) {
+                int rtype, permset, tag, qual;
+                const char *name;
+                r = sym_archive_entry_acl_next(
+                                entry,
+                                type,
+                                &rtype,
+                                &permset,
+                                &tag,
+                                &qual,
+                                &name);
+                if (r == ARCHIVE_EOF)
+                        break;
+                if (r != ARCHIVE_OK)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unexpected error while iterating through ACLs.");
+
+                assert(rtype == type);
+
+                acl_entry_t e;
+                if (sym_acl_create_entry(&a, &e) < 0)
+                        return log_error_errno(errno, "Failed to create ACL entry: %m");
+
+                static const struct {
+                        int libarchive;
+                        acl_tag_t libacl;
+                } tag_map[] = {
+                        { ARCHIVE_ENTRY_ACL_USER,      ACL_USER      },
+                        { ARCHIVE_ENTRY_ACL_GROUP,     ACL_GROUP     },
+                        { ARCHIVE_ENTRY_ACL_USER_OBJ,  ACL_USER_OBJ  },
+                        { ARCHIVE_ENTRY_ACL_GROUP_OBJ, ACL_GROUP_OBJ },
+                        { ARCHIVE_ENTRY_ACL_MASK,      ACL_MASK      },
+                        { ARCHIVE_ENTRY_ACL_OTHER,     ACL_OTHER     },
+                };
+
+                acl_tag_t ntag = ACL_UNDEFINED_TAG;
+                FOREACH_ELEMENT(t, tag_map)
+                        if (t->libarchive == tag) {
+                                ntag = t->libacl;
+                                break;
+                        }
+                if (ntag == ACL_UNDEFINED_TAG)
+                        continue;
+
+                if (sym_acl_set_tag_type(e, ntag) < 0)
+                        return log_error_errno(errno, "Failed to set ACL entry tag: %m");
+
+                if (IN_SET(ntag, ACL_USER, ACL_GROUP)) {
+                        id_t id = qual;
+
+                        if (sym_acl_set_qualifier(e, &id) < 0)
+                                return log_error_errno(errno, "Failed to set ACL entry qualifier: %m");
+                }
+
+                acl_permset_t p;
+                if (sym_acl_get_permset(e, &p) < 0)
+                        return log_error_errno(errno, "Failed to get ACL entry permission set: %m");
+
+                r = acl_set_perm(p, ACL_READ, permset & ARCHIVE_ENTRY_ACL_READ);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to set ACL entry read bit: %m");
+
+                r = acl_set_perm(p, ACL_WRITE, permset & ARCHIVE_ENTRY_ACL_WRITE);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to set ACL entry write bit: %m");
+
+                r = acl_set_perm(p, ACL_EXECUTE, permset & ARCHIVE_ENTRY_ACL_EXECUTE);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to set ACL entry excute bit: %m");
+
+                if (sym_acl_set_permset(e, p) < 0)
+                        return log_error_errno(errno, "Failed to set ACL entry permission set: %m");
+        }
+
+        if (*acl)
+                sym_acl_free(*acl);
+        *acl = TAKE_PTR(a);
+        return 0;
+}
+
 static int archive_entry_read_stat(
                 struct archive_entry *entry,
                 mode_t *filetype,
@@ -394,6 +540,8 @@ static int archive_entry_read_stat(
                 uid_t *uid,
                 gid_t *gid,
                 unsigned *fflags,
+                acl_t *acl_access,
+                acl_t *acl_default,
                 XAttr **xa,
                 size_t *n_xa,
                 TarFlags flags) {
@@ -468,6 +616,18 @@ static int archive_entry_read_stat(
                 };
         }
 
+        if (acl_access) {
+                r = archive_entry_read_acl(entry, ACL_TYPE_ACCESS, acl_access);
+                if (r < 0)
+                        return r;
+        }
+
+        if (acl_default) {
+                r = archive_entry_read_acl(entry, ACL_TYPE_DEFAULT, acl_default);
+                if (r < 0)
+                        return r;
+        }
+
         return 0;
 }
 
@@ -543,6 +703,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
                                         &open_inodes[0].uid,
                                         &open_inodes[0].gid,
                                         &open_inodes[0].fflags,
+                                        &open_inodes[0].acl_access,
+                                        &open_inodes[0].acl_default,
                                         &open_inodes[0].xattr,
                                         &open_inodes[0].n_xattr,
                                         flags);
@@ -614,6 +776,7 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
                         gid_t gid = GID_INVALID;
                         struct timespec mtime = { .tv_nsec = UTIME_OMIT };
                         unsigned fflags = 0;
+                        _cleanup_(acl_freep) acl_t acl_access = NULL, acl_default = NULL;
                         XAttr *xa = NULL;
                         size_t n_xa = 0;
                         CLEANUP_ARRAY(xa, n_xa, xattr_done_many);
@@ -689,6 +852,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
                                                 &uid,
                                                 &gid,
                                                 &fflags,
+                                                &acl_access,
+                                                &acl_default,
                                                 &xa,
                                                 &n_xa,
                                                 flags);
@@ -748,6 +913,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
                                 .uid = uid,
                                 .gid = gid,
                                 .fflags = fflags,
+                                .acl_access = TAKE_PTR(acl_access),
+                                .acl_default = TAKE_PTR(acl_default),
                                 .xattr = TAKE_PTR(xa),
                                 .n_xattr = n_xa,
                         };
@@ -925,6 +1092,88 @@ static int archive_generate_sparse(struct archive_entry *entry, int fd) {
         return 0;
 }
 
+static int archive_write_acl(
+                struct archive_entry *entry,
+                acl_type_t ntype,
+                acl_t acl) {
+        int r;
+
+        assert(entry);
+        assert(acl);
+
+        int type;
+        if (ntype == ACL_TYPE_ACCESS)
+                type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS;
+        else if (ntype == ACL_TYPE_DEFAULT)
+                type = ARCHIVE_ENTRY_ACL_TYPE_DEFAULT;
+        else
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unexpected ACL type");
+
+        acl_entry_t e;
+        r = sym_acl_get_entry(acl, ACL_FIRST_ENTRY, &e);
+        for (;;) {
+                if (r < 0)
+                        return log_error_errno(errno, "Failed to get ACL entry: %m");
+                if (r == 0)
+                        break;
+
+                acl_tag_t ntag;
+                if (sym_acl_get_tag_type(e, &ntag) < 0)
+                        return log_error_errno(errno, "Failed to get ACL entry tag: %m");
+
+                static const int tag_map[] = {
+                        [ACL_USER]      = ARCHIVE_ENTRY_ACL_USER,
+                        [ACL_GROUP]     = ARCHIVE_ENTRY_ACL_GROUP,
+                        [ACL_USER_OBJ]  = ARCHIVE_ENTRY_ACL_USER_OBJ,
+                        [ACL_GROUP_OBJ] = ARCHIVE_ENTRY_ACL_GROUP_OBJ,
+                        [ACL_MASK]      = ARCHIVE_ENTRY_ACL_MASK,
+                        [ACL_OTHER]     = ARCHIVE_ENTRY_ACL_OTHER,
+                };
+                assert_cc(ACL_UNDEFINED_TAG == 0);   /* safety check, we assume that holes are filled with ACL_UNDEFINED_TAG */
+                assert_cc(ELEMENTSOF(tag_map) <= 64); /* safety check, we assume that the tag ids are all packed and low */
+
+                int tag = ntag >= 0 && ntag <= (acl_tag_t) ELEMENTSOF(tag_map) ? tag_map[ntag] : ACL_UNDEFINED_TAG;
+
+                id_t qualifier = UID_INVALID;
+                if (IN_SET(ntag, ACL_USER, ACL_GROUP)) {
+                        id_t *q = sym_acl_get_qualifier(e);
+                        if (!q)
+                                return log_error_errno(errno, "Failed to get ACL entry qualifier: %m");
+
+                        qualifier = *q;
+                        sym_acl_free(q);
+                }
+
+                acl_permset_t p;
+                if (sym_acl_get_permset(e, &p) < 0)
+                        return log_error_errno(errno, "Failed to get ACL entry permission set: %m");
+
+                int permset = 0;
+                r = sym_acl_get_perm(p, ACL_READ);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to get ACL entry read bit: %m");
+                SET_FLAG(permset, ARCHIVE_ENTRY_ACL_READ, r);
+
+                r = sym_acl_get_perm(p, ACL_WRITE);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to get ACL entry write bit: %m");
+                SET_FLAG(permset, ARCHIVE_ENTRY_ACL_WRITE, r);
+
+                r = sym_acl_get_perm(p, ACL_EXECUTE);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to get ACL entry execute bit: %m");
+                SET_FLAG(permset, ARCHIVE_ENTRY_ACL_EXECUTE, r);
+
+                r = sym_archive_entry_acl_add_entry(entry, type, permset, tag, qualifier, /* name= */ NULL);
+                if (r != ARCHIVE_OK)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to add ACL entry.");
+
+                r = sym_acl_get_entry(acl, ACL_NEXT_ENTRY, &e);
+        }
+
+        return 0;
+}
+
 static int archive_item(
                 RecurseDirEvent event,
                 const char *path,
@@ -1009,6 +1258,36 @@ static int archive_item(
                 sym_archive_entry_set_symlink(entry, s);
         }
 
+        if (inode_type_can_acl(sx->stx_mode)) {
+
+                r = dlopen_libacl();
+                if (r < 0)
+                        log_debug_errno(r, "No trying to read ACL off inode, as libacl support is not available: %m");
+                else {
+                        r = sym_acl_extended_file(FORMAT_PROC_FD_PATH(inode_fd));
+                        if (r < 0 && !ERRNO_IS_NOT_SUPPORTED(errno))
+                                return log_error_errno(errno, "Failed check if '%s' has ACLs: %m", path);
+                        if (r > 0) {
+                                _cleanup_(acl_freep) acl_t acl = NULL;
+                                acl = sym_acl_get_file(FORMAT_PROC_FD_PATH(inode_fd), ACL_TYPE_ACCESS);
+                                if (!acl)
+                                        return log_error_errno(errno, "Failed read access ACLs of '%s': %m", path);
+
+                                archive_write_acl(entry, ACL_TYPE_ACCESS, acl);
+
+                                if (S_ISDIR(sx->stx_mode)) {
+                                        sym_acl_free(acl);
+
+                                        acl = sym_acl_get_file(FORMAT_PROC_FD_PATH(inode_fd), ACL_TYPE_DEFAULT);
+                                        if (!acl)
+                                                return log_error_errno(errno, "Failed to read default ACLs of '%s': %m", path);
+
+                                        archive_write_acl(entry, ACL_TYPE_DEFAULT, acl);
+                                }
+                        }
+                }
+        }
+
         _cleanup_free_ char *xattrs = NULL;
         r = flistxattr_malloc(inode_fd, &xattrs);
         if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r) && r != -ENODATA)