]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
tar-util: add support for file flags
authorLennart Poettering <lennart@poettering.net>
Thu, 21 Aug 2025 20:40:59 +0000 (22:40 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 4 Nov 2025 13:12:39 +0000 (14:12 +0100)
src/shared/libarchive-util.c
src/shared/libarchive-util.h
src/shared/tar-util.c

index e3387e6e97cac4b9e749c6a87cf7b429a94d5316..8d8e775c5174fee4dc0be12c6e7af80eecc9bf5c 100644 (file)
@@ -7,6 +7,7 @@
 #if HAVE_LIBARCHIVE
 static void *libarchive_dl = NULL;
 
+DLSYM_PROTOTYPE(archive_entry_fflags) = NULL;
 DLSYM_PROTOTYPE(archive_entry_filetype) = NULL;
 DLSYM_PROTOTYPE(archive_entry_free) = NULL;
 DLSYM_PROTOTYPE(archive_entry_gid) = NULL;
@@ -26,6 +27,7 @@ DLSYM_PROTOTYPE(archive_entry_pathname) = NULL;
 DLSYM_PROTOTYPE(archive_entry_rdevmajor) = NULL;
 DLSYM_PROTOTYPE(archive_entry_rdevminor) = NULL;
 DLSYM_PROTOTYPE(archive_entry_set_ctime) = NULL;
+DLSYM_PROTOTYPE(archive_entry_set_fflags) = NULL;
 DLSYM_PROTOTYPE(archive_entry_set_filetype) = NULL;
 DLSYM_PROTOTYPE(archive_entry_set_gid) = NULL;
 DLSYM_PROTOTYPE(archive_entry_set_hardlink) = NULL;
@@ -74,6 +76,7 @@ int dlopen_libarchive(void) {
                         &libarchive_dl,
                         "libarchive.so.13",
                         LOG_DEBUG,
+                        DLSYM_ARG(archive_entry_fflags),
                         DLSYM_ARG(archive_entry_filetype),
                         DLSYM_ARG(archive_entry_free),
                         DLSYM_ARG(archive_entry_gid),
@@ -93,6 +96,7 @@ int dlopen_libarchive(void) {
                         DLSYM_ARG(archive_entry_rdevmajor),
                         DLSYM_ARG(archive_entry_rdevminor),
                         DLSYM_ARG(archive_entry_set_ctime),
+                        DLSYM_ARG(archive_entry_set_fflags),
                         DLSYM_ARG(archive_entry_set_filetype),
                         DLSYM_ARG(archive_entry_set_gid),
                         DLSYM_ARG(archive_entry_set_hardlink),
index 7534b0d016e1ee5511c0c97a9e0ad44128247056..9a5d4e0b402b187a8c96aefe527e2d2d0786f3fe 100644 (file)
@@ -9,6 +9,7 @@
 
 #include "dlfcn-util.h"
 
+extern DLSYM_PROTOTYPE(archive_entry_fflags);
 extern DLSYM_PROTOTYPE(archive_entry_filetype);
 extern DLSYM_PROTOTYPE(archive_entry_free);
 extern DLSYM_PROTOTYPE(archive_entry_gid);
@@ -22,6 +23,7 @@ extern DLSYM_PROTOTYPE(archive_entry_pathname);
 extern DLSYM_PROTOTYPE(archive_entry_rdevmajor);
 extern DLSYM_PROTOTYPE(archive_entry_rdevminor);
 extern DLSYM_PROTOTYPE(archive_entry_set_ctime);
+extern DLSYM_PROTOTYPE(archive_entry_set_fflags);
 extern DLSYM_PROTOTYPE(archive_entry_set_filetype);
 extern DLSYM_PROTOTYPE(archive_entry_set_gid);
 extern DLSYM_PROTOTYPE(archive_entry_set_hardlink);
index 83b346d52ce4a13fedc3e09c39db393fef60c99d..54f3644ad1bd7f10ed4ac1b53edfda710e7ac2f9 100644 (file)
@@ -10,6 +10,7 @@
 
 #include "alloc-util.h"
 #include "chase.h"
+#include "chattr-util.h"
 #include "fd-util.h"
 #include "fs-util.h"
 #include "hexdecoct.h"
 
 #define DEPTH_MAX 128U
 
+/* We are a bit conservative with the flags we save/restore in tar files */
+#define CHATTR_TAR_FL                                                   \
+        (FS_NOATIME_FL     |                                            \
+         FS_NOCOW_FL       |                                            \
+         FS_PROJINHERIT_FL |                                            \
+         FS_NODUMP_FL      |                                            \
+         FS_SYNC_FL        |                                            \
+         FS_DIRSYNC_FL)
+
 typedef struct XAttr {
         char *name;
         struct iovec data;
@@ -44,6 +54,7 @@ typedef struct OpenInode {
         struct timespec mtime;
         uid_t uid;
         gid_t gid;
+        unsigned fflags;
         XAttr *xattr;
         size_t n_xattr;
 } OpenInode;
@@ -105,6 +116,20 @@ static int open_inode_finalize(OpenInode *of) {
                                 RET_GATHER(r, log_error_errno(k, "Failed to adjust ownership/mode 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,
+                                        /* value= */ of->fflags,
+                                        /* mask= */ of->fflags & ~CHATTR_EARLY_FL,
+                                        /* ret_previous= */ NULL,
+                                        /* ret_final= */ NULL,
+                                        CHATTR_FALLBACK_BITWISE);
+                        if (ERRNO_IS_NEG_NOT_SUPPORTED(k))
+                                log_warning_errno(k, "Failed to apply chattr of '%s', ignoring: %m", of->path);
+                        else if (k < 0)
+                                RET_GATHER(r, log_error_errno(k, "Failed to adjust chattr of '%s': %m", of->path));
+                }
+
                 /* We also adjust the mtime only after leaving a dir, since it might otherwise change again
                  * because we make modifications inside it */
                 if (of->mtime.tv_nsec != UTIME_OMIT) {
@@ -155,7 +180,8 @@ static int archive_unpack_regular(
                 struct archive_entry *entry,
                 int parent_fd,
                 const char *filename,
-                const char *path) {
+                const char *path,
+                unsigned fflags) {
 
         int r;
 
@@ -170,6 +196,22 @@ static int archive_unpack_regular(
         if (fd < 0)
                 return log_error_errno(fd, "Failed to create regular file '%s': %m", path);
 
+        if ((fflags & CHATTR_EARLY_FL) != 0) {
+                r = chattr_full(fd,
+                                /* path= */ NULL,
+                                /* value= */ fflags,
+                                /* mask= */ fflags & CHATTR_EARLY_FL,
+                                /* ret_previous= */ NULL,
+                                /* ret_final= */ NULL,
+                                CHATTR_FALLBACK_BITWISE);
+                if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
+                        log_warning_errno(r, "Failed to apply chattr of '%s', ignoring: %m", path);
+                else if (r < 0) {
+                        log_error_errno(r, "Failed to adjust chattr of '%s': %m", path);
+                        goto fail;
+                }
+        }
+
         r = sym_archive_read_data_into_fd(a, fd);
         if (r != ARCHIVE_OK) {
                 r = log_error_errno(
@@ -210,7 +252,10 @@ static int archive_unpack_directory(
                 struct archive_entry *entry,
                 int parent_fd,
                 const char *filename,
-                const char *path) {
+                const char *path,
+                unsigned fflags) {
+
+        int r;
 
         assert(a);
         assert(entry);
@@ -226,6 +271,20 @@ static int archive_unpack_directory(
         if (fd < 0)
                 return log_error_errno(fd, "Failed to create directory '%s': %m", path);
 
+        if ((fflags & CHATTR_EARLY_FL) != 0) {
+                r = chattr_full(fd,
+                                /* path= */ NULL,
+                                /* value= */ fflags,
+                                /* mask= */ fflags & CHATTR_EARLY_FL,
+                                /* ret_previous= */ NULL,
+                                /* ret_final= */ NULL,
+                                CHATTR_FALLBACK_BITWISE);
+                if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
+                        log_warning_errno(r, "Failed to apply chattr of '%s', ignoring: %m", path);
+                else if (r < 0)
+                        return log_error_errno(r, "Failed to adjust chattr of '%s': %m", path);
+        }
+
         return TAKE_FD(fd);
 }
 
@@ -334,6 +393,7 @@ static int archive_entry_read_stat(
                 struct timespec *mtime,
                 uid_t *uid,
                 gid_t *gid,
+                unsigned *fflags,
                 XAttr **xa,
                 size_t *n_xa,
                 TarFlags flags) {
@@ -361,6 +421,12 @@ static int archive_entry_read_stat(
         if (gid && sym_archive_entry_gid_is_set(entry))
                 *gid = sym_archive_entry_gid(entry);
 
+        if (fflags) {
+                unsigned long fs = 0, fc = 0;
+                sym_archive_entry_fflags(entry, &fs, &fc);
+                *fflags = (fs & ~fc) & CHATTR_TAR_FL;
+        }
+
         (void) sym_archive_entry_xattr_reset(entry);
         for (;;) {
                 const char *name = NULL;
@@ -476,6 +542,7 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
                                         &open_inodes[0].mtime,
                                         &open_inodes[0].uid,
                                         &open_inodes[0].gid,
+                                        &open_inodes[0].fflags,
                                         &open_inodes[0].xattr,
                                         &open_inodes[0].n_xattr,
                                         flags);
@@ -546,6 +613,7 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
                         uid_t uid = UID_INVALID;
                         gid_t gid = GID_INVALID;
                         struct timespec mtime = { .tv_nsec = UTIME_OMIT };
+                        unsigned fflags = 0;
                         XAttr *xa = NULL;
                         size_t n_xa = 0;
                         CLEANUP_ARRAY(xa, n_xa, xattr_done_many);
@@ -620,6 +688,7 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
                                                 &mtime,
                                                 &uid,
                                                 &gid,
+                                                &fflags,
                                                 &xa,
                                                 &n_xa,
                                                 flags);
@@ -629,11 +698,11 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
                                 switch (filetype) {
 
                                 case S_IFREG:
-                                        fd = archive_unpack_regular(a, entry, parent_fd, e, j);
+                                        fd = archive_unpack_regular(a, entry, parent_fd, e, j, fflags);
                                         break;
 
                                 case S_IFDIR:
-                                        fd = archive_unpack_directory(a, entry, parent_fd, e, j);
+                                        fd = archive_unpack_directory(a, entry, parent_fd, e, j, fflags);
                                         break;
 
                                 case S_IFLNK:
@@ -678,6 +747,7 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
                                 .mtime = mtime,
                                 .uid = uid,
                                 .gid = gid,
+                                .fflags = fflags,
                                 .xattr = TAKE_PTR(xa),
                                 .n_xattr = n_xa,
                         };
@@ -975,6 +1045,18 @@ static int archive_item(
                         return r;
         }
 
+        if (inode_type_can_chattr(sx->stx_mode)) {
+                unsigned f = 0;
+
+                r = read_attr_fd(data_fd >= 0 ? data_fd : inode_fd, &f);
+                if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r))
+                        return log_error_errno(r, "Failed to read file flags of '%s': %m", path);
+
+                f &= CHATTR_TAR_FL;
+                if (f != 0)
+                        sym_archive_entry_set_fflags(entry, f, /* clear= */ 0);
+        }
+
         if (sym_archive_write_header(d->archive, entry) != ARCHIVE_OK)
                 return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive entry header: %s", sym_archive_error_string(d->archive));