dissect: add --make-archive option to convert DDI to tarball

This commit is contained in:
Lennart Poettering 2024-01-24 12:28:41 +01:00
parent 6811774510
commit b68f4cade4
18 changed files with 374 additions and 5 deletions

View File

@ -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

View File

@ -53,6 +53,9 @@
<cmdsynopsis>
<command>systemd-dissect</command> <arg choice="opt" rep="repeat">OPTIONS</arg> <arg>--copy-to</arg> <arg choice="plain"><replaceable>IMAGE</replaceable></arg> <arg choice="opt"><replaceable>SOURCE</replaceable></arg> <arg choice="plain"><replaceable>PATH</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-dissect</command> <arg choice="opt" rep="repeat">OPTIONS</arg> <arg>--make-archive</arg> <arg choice="plain"><replaceable>IMAGE</replaceable></arg> <arg choice="opt"><replaceable>TARGET</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-dissect</command> <arg choice="opt" rep="repeat">OPTIONS</arg> <arg>--discover</arg>
</cmdsynopsis>
@ -305,6 +308,21 @@
<xi:include href="version-info.xml" xpointer="v247"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--make-archive</option></term>
<listitem><para>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 <literal>.tar.xz</literal> will
result in an xz compressed UNIX tarball (if the path is omitted an uncompressed UNIX tarball is
created). See
<citerefentry><refentrytitle>libarchive</refentrytitle><manvolnum>3</manvolnum></citerefentry> for a
list of supported archive formats and compression schemes.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--discover</option></term>
@ -535,10 +553,18 @@
<title>Examples</title>
<example>
<title>Generate a tarball from an OS disk image</title>
<title>Generate a tarball from an OS disk image (<option>--with</option>)</title>
<programlisting># systemd-dissect --with foo.raw tar cz . >foo.tar.gz</programlisting>
</example>
<para>or alternatively just:</para>
<example>
<title>Generate a tarball from an OS disk image (<option>--make-archive</option>)</title>
<programlisting># systemd-dissect --make-archive foo.raw foo.tar.gz</programlisting>
</example>
</refsect1>
<refsect1>

View File

@ -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'))

View File

@ -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')

View File

@ -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/<arch-triplet>/.

View File

@ -8,6 +8,7 @@ Packages=
cryptsetup
dbus
gnutls
libarchive
libbpf
libfido2
libmicrohttpd

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -238,6 +238,12 @@ const char* const systemd_features =
" -SYSVINIT"
#endif
#if HAVE_LIBARCHIVE
" +LIBARCHIVE"
#else
" -LIBARCHIVE"
#endif
" default-hierarchy=" DEFAULT_HIERARCHY_NAME
;

View File

@ -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);

View File

@ -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

View File

@ -0,0 +1,45 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "dlfcn-util.h"
#if HAVE_LIBARCHIVE
#include <archive.h>
#include <archive_entry.h>
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

View File

@ -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',

View File

@ -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;
}

View File

@ -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

View File

@ -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")"

View File

@ -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"'