From e1e170feca99274604b7d6c4120d291343759b92 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Aug 2025 22:40:59 +0200 Subject: [PATCH] tar-util: add support for file flags --- src/shared/libarchive-util.c | 4 ++ src/shared/libarchive-util.h | 2 + src/shared/tar-util.c | 90 ++++++++++++++++++++++++++++++++++-- 3 files changed, 92 insertions(+), 4 deletions(-) diff --git a/src/shared/libarchive-util.c b/src/shared/libarchive-util.c index e3387e6e97c..8d8e775c517 100644 --- a/src/shared/libarchive-util.c +++ b/src/shared/libarchive-util.c @@ -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), diff --git a/src/shared/libarchive-util.h b/src/shared/libarchive-util.h index 7534b0d016e..9a5d4e0b402 100644 --- a/src/shared/libarchive-util.h +++ b/src/shared/libarchive-util.h @@ -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); diff --git a/src/shared/tar-util.c b/src/shared/tar-util.c index 83b346d52ce..54f3644ad1b 100644 --- a/src/shared/tar-util.c +++ b/src/shared/tar-util.c @@ -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" @@ -29,6 +30,15 @@ #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)); -- 2.47.3