From b68f4cade487aef4aab880d39cc553dbd6fed364 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 24 Jan 2024 12:28:41 +0100 Subject: [PATCH] dissect: add --make-archive option to convert DDI to tarball --- .packit.yml | 3 + man/systemd-dissect.xml | 28 ++- meson.build | 5 + meson_options.txt | 2 + mkosi.images/base/mkosi.build.chroot | 1 + mkosi.images/base/mkosi.conf.d/10-arch.conf | 1 + .../base/mkosi.conf.d/10-centos-fedora.conf | 2 + .../base/mkosi.conf.d/10-debian-ubuntu.conf | 2 + .../base/mkosi.conf.d/10-opensuse.conf | 2 + src/basic/build.c | 6 + src/dissect/dissect.c | 200 +++++++++++++++++- src/shared/libarchive-util.c | 62 ++++++ src/shared/libarchive-util.h | 45 ++++ src/shared/meson.build | 1 + src/test/test-dlopen-so.c | 5 + test/TEST-50-DISSECT/test.sh | 1 + test/test-functions | 2 +- test/units/testsuite-50.sh | 11 + 18 files changed, 374 insertions(+), 5 deletions(-) create mode 100644 src/shared/libarchive-util.c create mode 100644 src/shared/libarchive-util.h diff --git a/.packit.yml b/.packit.yml index 1775221bd1..8fb2cc878a 100644 --- a/.packit.yml +++ b/.packit.yml @@ -41,6 +41,9 @@ actions: - 'sed -i "/^CONFIGURE_OPTS=(/a--werror" .packit_rpm/systemd.spec' # Ignore unpackaged standalone binaries - "sed -i 's/assert False,.*/pass/' .packit_rpm/split-files.py" + # Temporarily add libarchive-devel build dep until the change propagates to + # Rawhide's specfile + - "sed -ri '0,/^BuildRequires: .+$/s//&\\nBuildRequires: libarchive-devel/' .packit_rpm/systemd.spec" jobs: - job: copr_build diff --git a/man/systemd-dissect.xml b/man/systemd-dissect.xml index 85166f23d3..e72c66386a 100644 --- a/man/systemd-dissect.xml +++ b/man/systemd-dissect.xml @@ -53,6 +53,9 @@ systemd-dissect OPTIONS --copy-to IMAGE SOURCE PATH + + systemd-dissect OPTIONS --make-archive IMAGE TARGET + systemd-dissect OPTIONS --discover @@ -305,6 +308,21 @@ + + + + Generates an archive file from the specified disk image. Expects two arguments: the + path to the disk image and optionally the output archive file path. If the latter is omitted the + archive is written to standard output. The archive file format is determined automatically from the + specified output archive file name, e.g. any path suffixed with .tar.xz will + result in an xz compressed UNIX tarball (if the path is omitted an uncompressed UNIX tarball is + created). See + libarchive3 for a + list of supported archive formats and compression schemes. + + + + @@ -535,10 +553,18 @@ Examples - Generate a tarball from an OS disk image + Generate a tarball from an OS disk image (<option>--with</option>) # systemd-dissect --with foo.raw tar cz . >foo.tar.gz + + or alternatively just: + + + Generate a tarball from an OS disk image (<option>--make-archive</option>) + + # systemd-dissect --make-archive foo.raw foo.tar.gz + diff --git a/meson.build b/meson.build index 02a0e9b8f8..136b1eac21 100644 --- a/meson.build +++ b/meson.build @@ -1413,6 +1413,11 @@ elif compression == 'xz' and not libxz.found() endif conf.set('DEFAULT_COMPRESSION', 'COMPRESSION_@0@'.format(compression.to_upper())) +libarchive = dependency('libarchive', + version : '>= 3.0', + required : get_option('libarchive')) +conf.set10('HAVE_LIBARCHIVE', libarchive.found()) + libxkbcommon = dependency('xkbcommon', version : '>= 0.3.0', required : get_option('xkbcommon')) diff --git a/meson_options.txt b/meson_options.txt index b74f949189..9a6abd4981 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -451,6 +451,8 @@ option('glib', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'd description : 'libglib support (for tests only)') option('dbus', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' }, description : 'libdbus support (for tests only)') +option('libarchive', type : 'feature', + description : 'libarchive support') option('bootloader', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' }, description : 'sd-boot/stub and userspace tools') diff --git a/mkosi.images/base/mkosi.build.chroot b/mkosi.images/base/mkosi.build.chroot index 9524fdb103..f164c954b7 100755 --- a/mkosi.images/base/mkosi.build.chroot +++ b/mkosi.images/base/mkosi.build.chroot @@ -153,6 +153,7 @@ if [ ! -f "$BUILDDIR"/build.ninja ]; then -D initrd=true -D fexecve=true -D default-keymap="$DEFAULT_KEYMAP" + -D libarchive=enabled ) # On debian-like systems the library directory is not /usr/lib64 but /usr/lib//. diff --git a/mkosi.images/base/mkosi.conf.d/10-arch.conf b/mkosi.images/base/mkosi.conf.d/10-arch.conf index 7ab0c712ae..385c73979f 100644 --- a/mkosi.images/base/mkosi.conf.d/10-arch.conf +++ b/mkosi.images/base/mkosi.conf.d/10-arch.conf @@ -8,6 +8,7 @@ Packages= cryptsetup dbus gnutls + libarchive libbpf libfido2 libmicrohttpd diff --git a/mkosi.images/base/mkosi.conf.d/10-centos-fedora.conf b/mkosi.images/base/mkosi.conf.d/10-centos-fedora.conf index 4dec24cc20..def5eaa2de 100644 --- a/mkosi.images/base/mkosi.conf.d/10-centos-fedora.conf +++ b/mkosi.images/base/mkosi.conf.d/10-centos-fedora.conf @@ -9,6 +9,7 @@ Packages= audit-libs cryptsetup-libs gnutls + libarchive libasan libbpf libfido2 @@ -39,6 +40,7 @@ BuildPackages= pkgconfig(glib-2.0) pkgconfig(gnutls) pkgconfig(libacl) + pkgconfig(libarchive) pkgconfig(libbpf) pkgconfig(libcap) pkgconfig(libcryptsetup) diff --git a/mkosi.images/base/mkosi.conf.d/10-debian-ubuntu.conf b/mkosi.images/base/mkosi.conf.d/10-debian-ubuntu.conf index c529e0b176..fc684a4482 100644 --- a/mkosi.images/base/mkosi.conf.d/10-debian-ubuntu.conf +++ b/mkosi.images/base/mkosi.conf.d/10-debian-ubuntu.conf @@ -8,6 +8,7 @@ Distribution=|ubuntu Packages= dmsetup libapparmor1 + libarchive13 libfdisk1 libfido2-1 libglib2.0-0 @@ -30,6 +31,7 @@ BuildPackages= g++ libacl1-dev libapparmor-dev + libarchive-dev libaudit-dev libblkid-dev libbpf-dev diff --git a/mkosi.images/base/mkosi.conf.d/10-opensuse.conf b/mkosi.images/base/mkosi.conf.d/10-opensuse.conf index ec91b4901f..1c00b78322 100644 --- a/mkosi.images/base/mkosi.conf.d/10-opensuse.conf +++ b/mkosi.images/base/mkosi.conf.d/10-opensuse.conf @@ -14,6 +14,7 @@ Packages= grep gzip libbpf1 + libarchive13 libcrypt1 libcryptsetup12 libdw1 @@ -53,6 +54,7 @@ BuildPackages= intltool libacl-devel libapparmor-devel + libarchive-devel libblkid-devel libbpf-devel libcap-devel diff --git a/src/basic/build.c b/src/basic/build.c index 8fb32ab9b6..1c52d9a935 100644 --- a/src/basic/build.c +++ b/src/basic/build.c @@ -238,6 +238,12 @@ const char* const systemd_features = " -SYSVINIT" #endif +#if HAVE_LIBARCHIVE + " +LIBARCHIVE" +#else + " -LIBARCHIVE" +#endif + " default-hierarchy=" DEFAULT_HIERARCHY_NAME ; diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 4e44a23793..9ef991426c 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -27,6 +27,7 @@ #include "format-util.h" #include "fs-util.h" #include "hexdecoct.h" +#include "libarchive-util.h" #include "log.h" #include "loop-util.h" #include "main-func.h" @@ -63,6 +64,7 @@ static enum { ACTION_COPY_TO, ACTION_DISCOVER, ACTION_VALIDATE, + ACTION_MAKE_ARCHIVE, } arg_action = ACTION_DISSECT; static char *arg_image = NULL; static char *arg_root = NULL; @@ -116,6 +118,7 @@ static int help(void) { "%1$s [OPTIONS...] --with IMAGE [COMMAND…]\n" "%1$s [OPTIONS...] --copy-from IMAGE PATH [TARGET]\n" "%1$s [OPTIONS...] --copy-to IMAGE [SOURCE] PATH\n" + "%1$s [OPTIONS...] --make-archive IMAGE [TARGET]\n" "%1$s [OPTIONS...] --discover\n" "%1$s [OPTIONS...] --validate IMAGE\n" "\n%5$sDissect a Discoverable Disk Image (DDI).%6$s\n\n" @@ -157,6 +160,7 @@ static int help(void) { " --with Mount, run command, unmount\n" " -x --copy-from Copy files from image to host\n" " -a --copy-to Copy files from host to image\n" + " --make-archive Convert the DDI to an archive file\n" " --discover Discover DDIs in well known directories\n" " --validate Validate image and image policy\n" "\nSee the %2$s for details.\n", @@ -263,6 +267,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_IMAGE_POLICY, ARG_VALIDATE, ARG_MTREE_HASH, + ARG_MAKE_ARCHIVE, }; static const struct option options[] = { @@ -295,6 +300,7 @@ static int parse_argv(int argc, char *argv[]) { { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, { "validate", no_argument, NULL, ARG_VALIDATE }, { "mtree-hash", required_argument, NULL, ARG_MTREE_HASH }, + { "make-archive", no_argument, NULL, ARG_MAKE_ARCHIVE }, {} }; @@ -518,6 +524,15 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_MAKE_ARCHIVE: + + r = dlopen_libarchive(); + if (r < 0) + return log_error_errno(r, "Archive support not available (compiled without libarchive, or libarchive not installed?)."); + + arg_action = ACTION_MAKE_ARCHIVE; + break; + case '?': return -EINVAL; @@ -600,6 +615,19 @@ static int parse_argv(int argc, char *argv[]) { arg_flags |= DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_REQUIRE_ROOT; break; + case ACTION_MAKE_ARCHIVE: + if (argc < optind + 1 || argc > optind + 2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Expected an image file, and an optional target path as only arguments."); + + r = parse_image_path_argument(argv[optind], &arg_root, &arg_image); + if (r < 0) + return r; + + arg_target = argc > optind + 1 ? empty_or_dash_to_null(argv[optind + 1]) : NULL; + arg_flags |= DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_REQUIRE_ROOT; + break; + case ACTION_COPY_FROM: if (argc < optind + 2 || argc > optind + 3) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -1274,13 +1302,116 @@ static int mtree_print_item( return RECURSE_DIR_CONTINUE; } -static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) { +#if HAVE_LIBARCHIVE +static int archive_item( + RecurseDirEvent event, + const char *path, + int dir_fd, + int inode_fd, + const struct dirent *de, + const struct statx *sx, + void *userdata) { + + struct archive *a = ASSERT_PTR(userdata); + int r; + + assert(path); + + if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY)) + return RECURSE_DIR_CONTINUE; + + assert(inode_fd >= 0); + assert(sx); + + log_debug("Archiving %s\n", path); + + _cleanup_(sym_archive_entry_freep) struct archive_entry *entry = NULL; + entry = sym_archive_entry_new(); + if (!entry) + return log_oom(); + + assert(FLAGS_SET(sx->stx_mask, STATX_TYPE|STATX_MODE)); + sym_archive_entry_set_pathname(entry, path); + sym_archive_entry_set_filetype(entry, sx->stx_mode); + + if (!S_ISLNK(sx->stx_mode)) + sym_archive_entry_set_perm(entry, sx->stx_mode); + + if (FLAGS_SET(sx->stx_mask, STATX_UID)) + sym_archive_entry_set_uid(entry, sx->stx_uid); + if (FLAGS_SET(sx->stx_mask, STATX_GID)) + sym_archive_entry_set_gid(entry, sx->stx_gid); + + if (S_ISREG(sx->stx_mode)) { + if (!FLAGS_SET(sx->stx_mask, STATX_SIZE)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Unable to determine file size of '%s'.", path); + + sym_archive_entry_set_size(entry, sx->stx_size); + } + + if (S_ISCHR(sx->stx_mode) || S_ISBLK(sx->stx_mode)) { + sym_archive_entry_set_rdevmajor(entry, sx->stx_rdev_major); + sym_archive_entry_set_rdevminor(entry, sx->stx_rdev_minor); + } + + /* We care about a modicum of reproducibility here, hence we don't save atime/btime here */ + if (FLAGS_SET(sx->stx_mask, STATX_MTIME)) + sym_archive_entry_set_mtime(entry, sx->stx_mtime.tv_sec, sx->stx_mtime.tv_nsec); + if (FLAGS_SET(sx->stx_mask, STATX_CTIME)) + sym_archive_entry_set_ctime(entry, sx->stx_ctime.tv_sec, sx->stx_ctime.tv_nsec); + + if (S_ISLNK(sx->stx_mode)) { + _cleanup_free_ char *s = NULL; + + assert(dir_fd >= 0); + assert(de); + + r = readlinkat_malloc(dir_fd, de->d_name, &s); + if (r < 0) + return log_error_errno(r, "Failed to read symlink target of '%s': %m", path); + + sym_archive_entry_set_symlink(entry, s); + } + + if (sym_archive_write_header(a, entry) != ARCHIVE_OK) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive entry header: %s", sym_archive_error_string(a)); + + if (S_ISREG(sx->stx_mode)) { + _cleanup_close_ int data_fd = -EBADF; + + /* Convert the O_PATH fd in a proper fd */ + data_fd = fd_reopen(inode_fd, O_RDONLY|O_CLOEXEC); + if (data_fd < 0) + return log_error_errno(data_fd, "Failed to open '%s': %m", path); + + for (;;) { + char buffer[64*1024]; + ssize_t l; + + l = read(data_fd, buffer, sizeof(buffer)); + if (l < 0) + return log_error_errno(errno, "Failed to read '%s': %m", path); + if (l == 0) + break; + + la_ssize_t k; + k = sym_archive_write_data(a, buffer, l); + if (k < 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive data: %s", sym_archive_error_string(a)); + } + } + + return RECURSE_DIR_CONTINUE; +} +#endif + +static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopDevice *d) { _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; _cleanup_free_ char *t = NULL; const char *root; int r; - assert(IN_SET(arg_action, ACTION_LIST, ACTION_MTREE, ACTION_COPY_FROM, ACTION_COPY_TO)); + assert(IN_SET(arg_action, ACTION_LIST, ACTION_MTREE, ACTION_COPY_FROM, ACTION_COPY_TO, ACTION_MAKE_ARCHIVE)); if (arg_image) { assert(m); @@ -1466,6 +1597,68 @@ static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) { return 0; } + case ACTION_MAKE_ARCHIVE: { +#if HAVE_LIBARCHIVE + _cleanup_(unlink_and_freep) char *tar = NULL; + _cleanup_close_ int dfd = -EBADF; + _cleanup_fclose_ FILE *f = NULL; + + dfd = open(root, O_DIRECTORY|O_CLOEXEC|O_RDONLY); + if (dfd < 0) + return log_error_errno(errno, "Failed to open mount directory: %m"); + + _cleanup_(sym_archive_write_freep) struct archive *a = sym_archive_write_new(); + if (!a) + return log_oom(); + + if (arg_target) + r = sym_archive_write_set_format_filter_by_ext(a, arg_target); + else + r = sym_archive_write_set_format_gnutar(a); + if (r != ARCHIVE_OK) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output format: %s", sym_archive_error_string(a)); + + if (arg_target) { + r = fopen_tmpfile_linkable(arg_target, O_WRONLY|O_CLOEXEC, &tar, &f); + if (r < 0) + return log_error_errno(r, "Failed to create target file '%s': %m", arg_target); + + r = sym_archive_write_open_FILE(a, f); + } else { + if (isatty(STDOUT_FILENO)) + return log_error_errno(SYNTHETIC_ERRNO(EBADF), "Refusing to write archive to TTY."); + + r = sym_archive_write_open_fd(a, STDOUT_FILENO); + } + if (r != ARCHIVE_OK) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output file: %s", sym_archive_error_string(a)); + + r = recurse_dir(dfd, + ".", + STATX_TYPE|STATX_MODE|STATX_UID|STATX_GID|STATX_SIZE|STATX_ATIME|STATX_CTIME, + UINT_MAX, + RECURSE_DIR_SORT|RECURSE_DIR_INODE_FD|RECURSE_DIR_TOPLEVEL, + archive_item, + a); + if (r < 0) + return log_error_errno(r, "Failed to make archive: %m"); + + r = sym_archive_write_close(a); + if (r != ARCHIVE_OK) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to finish writing archive: %s", sym_archive_error_string(a)); + + if (arg_target) { + r = flink_tmpfile(f, tar, arg_target, LINK_TMPFILE_REPLACE); + if (r < 0) + return log_error_errno(r, "Failed to move archive file into place: %m"); + } + + return 0; +#else + assert_not_reached(); +#endif + } + default: assert_not_reached(); } @@ -1914,7 +2107,8 @@ static int run(int argc, char *argv[]) { case ACTION_MTREE: case ACTION_COPY_FROM: case ACTION_COPY_TO: - return action_list_or_mtree_or_copy(m, d); + case ACTION_MAKE_ARCHIVE: + return action_list_or_mtree_or_copy_or_make_archive(m, d); case ACTION_WITH: return action_with(m, d); diff --git a/src/shared/libarchive-util.c b/src/shared/libarchive-util.c new file mode 100644 index 0000000000..e6d6581597 --- /dev/null +++ b/src/shared/libarchive-util.c @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "libarchive-util.h" + +#if HAVE_LIBARCHIVE +static void *libarchive_dl = NULL; + +DLSYM_FUNCTION(archive_entry_free); +DLSYM_FUNCTION(archive_entry_new); +DLSYM_FUNCTION(archive_entry_set_ctime); +DLSYM_FUNCTION(archive_entry_set_filetype); +DLSYM_FUNCTION(archive_entry_set_gid); +DLSYM_FUNCTION(archive_entry_set_mtime); +DLSYM_FUNCTION(archive_entry_set_pathname); +DLSYM_FUNCTION(archive_entry_set_perm); +DLSYM_FUNCTION(archive_entry_set_rdevmajor); +DLSYM_FUNCTION(archive_entry_set_rdevminor); +DLSYM_FUNCTION(archive_entry_set_symlink); +DLSYM_FUNCTION(archive_entry_set_size); +DLSYM_FUNCTION(archive_entry_set_uid); +DLSYM_FUNCTION(archive_error_string); +DLSYM_FUNCTION(archive_write_close); +DLSYM_FUNCTION(archive_write_data); +DLSYM_FUNCTION(archive_write_free); +DLSYM_FUNCTION(archive_write_header); +DLSYM_FUNCTION(archive_write_new); +DLSYM_FUNCTION(archive_write_open_FILE); +DLSYM_FUNCTION(archive_write_open_fd); +DLSYM_FUNCTION(archive_write_set_format_filter_by_ext); +DLSYM_FUNCTION(archive_write_set_format_gnutar); + +int dlopen_libarchive(void) { + return dlopen_many_sym_or_warn( + &libarchive_dl, + "libarchive.so.13", + LOG_DEBUG, + DLSYM_ARG(archive_entry_free), + DLSYM_ARG(archive_entry_new), + DLSYM_ARG(archive_entry_set_ctime), + DLSYM_ARG(archive_entry_set_filetype), + DLSYM_ARG(archive_entry_set_gid), + DLSYM_ARG(archive_entry_set_mtime), + DLSYM_ARG(archive_entry_set_pathname), + DLSYM_ARG(archive_entry_set_perm), + DLSYM_ARG(archive_entry_set_rdevmajor), + DLSYM_ARG(archive_entry_set_rdevminor), + DLSYM_ARG(archive_entry_set_size), + DLSYM_ARG(archive_entry_set_symlink), + DLSYM_ARG(archive_entry_set_uid), + DLSYM_ARG(archive_error_string), + DLSYM_ARG(archive_write_close), + DLSYM_ARG(archive_write_data), + DLSYM_ARG(archive_write_free), + DLSYM_ARG(archive_write_header), + DLSYM_ARG(archive_write_new), + DLSYM_ARG(archive_write_open_FILE), + DLSYM_ARG(archive_write_open_fd), + DLSYM_ARG(archive_write_set_format_filter_by_ext), + DLSYM_ARG(archive_write_set_format_gnutar)); +} + +#endif diff --git a/src/shared/libarchive-util.h b/src/shared/libarchive-util.h new file mode 100644 index 0000000000..8003da9c0b --- /dev/null +++ b/src/shared/libarchive-util.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "dlfcn-util.h" + +#if HAVE_LIBARCHIVE +#include +#include + +DLSYM_PROTOTYPE(archive_entry_free); +DLSYM_PROTOTYPE(archive_entry_new); +DLSYM_PROTOTYPE(archive_entry_set_ctime); +DLSYM_PROTOTYPE(archive_entry_set_filetype); +DLSYM_PROTOTYPE(archive_entry_set_gid); +DLSYM_PROTOTYPE(archive_entry_set_mtime); +DLSYM_PROTOTYPE(archive_entry_set_pathname); +DLSYM_PROTOTYPE(archive_entry_set_perm); +DLSYM_PROTOTYPE(archive_entry_set_rdevmajor); +DLSYM_PROTOTYPE(archive_entry_set_rdevminor); +DLSYM_PROTOTYPE(archive_entry_set_symlink); +DLSYM_PROTOTYPE(archive_entry_set_size); +DLSYM_PROTOTYPE(archive_entry_set_uid); +DLSYM_PROTOTYPE(archive_error_string); +DLSYM_PROTOTYPE(archive_write_close); +DLSYM_PROTOTYPE(archive_write_data); +DLSYM_PROTOTYPE(archive_write_free); +DLSYM_PROTOTYPE(archive_write_header); +DLSYM_PROTOTYPE(archive_write_new); +DLSYM_PROTOTYPE(archive_write_open_FILE); +DLSYM_PROTOTYPE(archive_write_open_fd); +DLSYM_PROTOTYPE(archive_write_set_format_filter_by_ext); +DLSYM_PROTOTYPE(archive_write_set_format_gnutar); + +int dlopen_libarchive(void); + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct archive_entry*, sym_archive_entry_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct archive*, sym_archive_write_free, NULL); + +#else + +static inline int dlopen_libarchive(void) { + return -EOPNOTSUPP; +} + +#endif diff --git a/src/shared/meson.build b/src/shared/meson.build index 533c08d20a..2a39bb04df 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -103,6 +103,7 @@ shared_sources = files( 'keyring-util.c', 'killall.c', 'label-util.c', + 'libarchive-util.c', 'libcrypt-util.c', 'libfido2-util.c', 'libmount-util.c', diff --git a/src/test/test-dlopen-so.c b/src/test/test-dlopen-so.c index e98b8dae1e..d2dbe72981 100644 --- a/src/test/test-dlopen-so.c +++ b/src/test/test-dlopen-so.c @@ -7,6 +7,7 @@ #include "cryptsetup-util.h" #include "elf-util.h" #include "idn-util.h" +#include "libarchive-util.h" #include "libfido2-util.h" #include "macro.h" #include "main-func.h" @@ -70,6 +71,10 @@ static int run(int argc, char **argv) { assert_se(dlopen_p11kit() >= 0); #endif +#if HAVE_LIBARCHIVE + assert_se(dlopen_libarchive() >= 0); +#endif + return 0; } diff --git a/test/TEST-50-DISSECT/test.sh b/test/TEST-50-DISSECT/test.sh index f1abce8887..613bb086ce 100755 --- a/test/TEST-50-DISSECT/test.sh +++ b/test/TEST-50-DISSECT/test.sh @@ -21,6 +21,7 @@ test_append_files() { generate_module_dependencies inst_binary wc inst_binary sha256sum + inst_binary tar if command -v openssl >/dev/null 2>&1; then inst_binary openssl fi diff --git a/test/test-functions b/test/test-functions index 92bb7d532b..80fdcd26b9 100644 --- a/test/test-functions +++ b/test/test-functions @@ -1551,7 +1551,7 @@ install_missing_libraries() { local lib path # A number of dependencies is now optional via dlopen, so the install # script will not pick them up, since it looks at linkage. - for lib in libcryptsetup libidn libidn2 pwquality libqrencode tss2-esys tss2-rc tss2-mu tss2-tcti-device libfido2 libbpf libelf libdw xkbcommon p11-kit-1; do + for lib in libcryptsetup libidn libidn2 pwquality libqrencode tss2-esys tss2-rc tss2-mu tss2-tcti-device libfido2 libbpf libelf libdw xkbcommon p11-kit-1 libarchive; do ddebug "Searching for $lib via pkg-config" if pkg-config --exists "$lib"; then path="$(pkg-config --variable=libdir "$lib")" diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index 0e33ec9a26..af379a4d8b 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -54,6 +54,17 @@ read -r SHA256SUM2 _ < <(systemd-dissect --read-only --with "${image}.raw" sha25 test "$SHA256SUM2" != "" test "$SHA256SUM1" = "$SHA256SUM2" +if systemctl --version | grep -qF -- "+LIBARCHIVE" ; then + # Make sure tarballs are reproducible + read -r SHA256SUM1 _ < <(systemd-dissect --make-archive "${image}.raw" | sha256sum) + test "$SHA256SUM1" != "" + read -r SHA256SUM2 _ < <(systemd-dissect --make-archive "${image}.raw" | sha256sum) + test "$SHA256SUM2" != "" + test "$SHA256SUM1" = "$SHA256SUM2" + # Also check that a file we expect to be there is there + systemd-dissect --make-archive "${image}.raw" | tar t | grep etc/os-release +fi + mv "${image}.verity" "${image}.fooverity" mv "${image}.roothash" "${image}.foohash" systemd-dissect --json=short "${image}.raw" --root-hash="${roothash}" --verity-data="${image}.fooverity" | grep -q -F '{"rw":"ro","designator":"root","partition_uuid":null,"partition_label":null,"fstype":"squashfs","architecture":null,"verity":"external"'