1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-09 12:58:26 +03:00

Merge pull request #25302 from poettering/dissect-with

dissect: add new --with command for excuting commands with a DDI mounted
This commit is contained in:
Lennart Poettering 2022-11-09 18:31:54 +01:00 committed by GitHub
commit 0b24845d1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 235 additions and 7 deletions

View File

@ -34,6 +34,9 @@
<cmdsynopsis>
<command>systemd-dissect <arg choice="opt" rep="repeat">OPTIONS</arg> <option>--list</option> <arg choice="plain"><replaceable>IMAGE</replaceable></arg></command>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-dissect <arg choice="opt" rep="repeat">OPTIONS</arg> <option>--with</option> <arg choice="plain"><replaceable>IMAGE</replaceable></arg> <arg choice="opt" rep="repeat"><replaceable>COMMAND</replaceable></arg></command>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-dissect <arg choice="opt" rep="repeat">OPTIONS</arg> <option>--copy-from</option> <arg choice="plain"><replaceable>IMAGE</replaceable></arg> <arg choice="plain"><replaceable>PATH</replaceable></arg> <arg choice="opt"><replaceable>TARGET</replaceable></arg></command>
</cmdsynopsis>
@ -160,6 +163,19 @@
standard output.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--with</option></term>
<listitem><para>Runs the specified command with the specified OS image mounted. This will mount the
image to a temporary directory, switch the current working directory to it, and invoke the specified
command line as child process. Once the process ends it will unmount the image again, and remove the
temporary directory. If no command is specified a shell is invoked. The image is mounted writable,
use <option>--read-only</option> to switch to read-only operation. The invoked process will have the
<varname>$SYSTEMD_DISSECT_ROOT</varname> environment variable set, containing the absolute path name
of the temporary mount point, i.e. the same directory that is set as the current working
directory.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--copy-from</option></term>
<term><option>-x</option></term>
@ -294,14 +310,24 @@
<xi:include href="standard-options.xml" xpointer="no-legend" />
<xi:include href="standard-options.xml" xpointer="json" />
</variablelist>
</refsect1>
<refsect1>
<title>Exit status</title>
<para>On success, 0 is returned, a non-zero failure code
otherwise.</para>
<para>On success, 0 is returned, a non-zero failure code otherwise. If the <option>--with</option>
command is used the exit status of the invoked command is propagated.</para>
</refsect1>
<refsect1>
<title>Examples</title>
<example>
<title>Generate a tarball from an OS disk image</title>
<programlisting>$ systemd-dissect --with foo.raw tar cz . > foo.tar.gz</programlisting>
</example>
</refsect1>
<refsect1>

View File

@ -36,6 +36,7 @@
#include "parse-util.h"
#include "path-util.h"
#include "pretty-print.h"
#include "process-util.h"
#include "recurse-dir.h"
#include "stat-util.h"
#include "string-util.h"
@ -49,6 +50,7 @@ static enum {
ACTION_MOUNT,
ACTION_UMOUNT,
ACTION_LIST,
ACTION_WITH,
ACTION_COPY_FROM,
ACTION_COPY_TO,
} arg_action = ACTION_DISSECT;
@ -68,8 +70,10 @@ static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
static PagerFlags arg_pager_flags = 0;
static bool arg_legend = true;
static bool arg_rmdir = false;
static char **arg_argv = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_verity_settings, verity_settings_done);
STATIC_DESTRUCTOR_REGISTER(arg_argv, strv_freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
@ -83,6 +87,7 @@ static int help(void) {
"%1$s [OPTIONS...] --mount IMAGE PATH\n"
"%1$s [OPTIONS...] --umount PATH\n"
"%1$s [OPTIONS...] --list IMAGE\n"
"%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\n"
"%5$sDissect a Discoverable Disk Image (DDI).%6$s\n\n"
@ -113,6 +118,7 @@ static int help(void) {
" -U Shortcut for --umount --rmdir\n"
" -l --list List all the files and directories of the specified\n"
" OS image\n"
" --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"
"\nSee the %2$s for details.\n",
@ -126,12 +132,56 @@ static int help(void) {
return 0;
}
static int patch_argv(int *argc, char ***argv, char ***buf) {
_cleanup_free_ char **l = NULL;
char **e;
assert(argc);
assert(*argc >= 0);
assert(argv);
assert(*argv);
assert(buf);
/* Ugly hack: if --with is included in command line, also insert "--" immediately after it, to make
* getopt_long() stop processing switches */
for (e = *argv + 1; e < *argv + *argc; e++) {
assert(*e);
if (streq(*e, "--with"))
break;
}
if (e >= *argv + *argc || streq_ptr(e[1], "--")) {
/* No --with used? Or already followed by "--"? Then don't do anything */
*buf = NULL;
return 0;
}
/* Insert the extra "--" right after the --with */
l = new(char*, *argc + 2);
if (!l)
return log_oom();
size_t idx = e - *argv + 1;
memcpy(l, *argv, sizeof(char*) * idx); /* copy everything up to and including the --with */
l[idx] = (char*) "--"; /* insert "--" */
memcpy(l + idx + 1, e + 1, sizeof(char*) * (*argc - idx + 1)); /* copy the rest, including trailing NULL entry */
(*argc)++;
(*argv) = l;
*buf = TAKE_PTR(l);
return 1;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_NO_PAGER,
ARG_NO_LEGEND,
ARG_WITH,
ARG_DISCARD,
ARG_FSCK,
ARG_GROWFS,
@ -150,6 +200,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "mount", no_argument, NULL, 'm' },
{ "umount", no_argument, NULL, 'u' },
{ "with", no_argument, NULL, ARG_WITH },
{ "read-only", no_argument, NULL, 'r' },
{ "discard", required_argument, NULL, ARG_DISCARD },
{ "fsck", required_argument, NULL, ARG_FSCK },
@ -166,11 +217,16 @@ static int parse_argv(int argc, char *argv[]) {
{}
};
_cleanup_free_ char **buf = NULL; /* we use free(), not strv_free() here, as we don't copy the strings here */
int c, r;
assert(argc >= 0);
assert(argv);
r = patch_argv(&argc, &argv, &buf);
if (r < 0)
return r;
while ((c = getopt_long(argc, argv, "hmurMUlxa", options, NULL)) >= 0) {
switch (c) {
@ -222,6 +278,10 @@ static int parse_argv(int argc, char *argv[]) {
arg_flags |= DISSECT_IMAGE_READ_ONLY;
break;
case ARG_WITH:
arg_action = ACTION_WITH;
break;
case 'x':
arg_action = ACTION_COPY_FROM;
arg_flags |= DISSECT_IMAGE_READ_ONLY;
@ -332,7 +392,6 @@ static int parse_argv(int argc, char *argv[]) {
default:
assert_not_reached();
}
}
switch (arg_action) {
@ -403,6 +462,20 @@ static int parse_argv(int argc, char *argv[]) {
arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT;
break;
case ACTION_WITH:
if (optind >= argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected an image file path and an optional command line.");
arg_image = argv[optind];
if (argc > optind + 1) {
arg_argv = strv_copy(argv + optind + 1);
if (!arg_argv)
return log_oom();
}
break;
default:
assert_not_reached();
}
@ -518,6 +591,8 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) {
"Mach. Info:");
strv_pair_print(m->os_release,
"OS Release:");
strv_pair_print(m->initrd_release,
"initrd R.:");
strv_pair_print(m->extension_release,
" Ext. Rel.:");
@ -525,6 +600,7 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) {
!sd_id128_is_null(m->machine_id) ||
!strv_isempty(m->machine_info) ||
!strv_isempty(m->os_release) ||
!strv_isempty(m->initrd_release) ||
!strv_isempty(m->extension_release))
putc('\n', stdout);
@ -535,6 +611,8 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) {
printf(" %s portable service\n",
COLOR_MARK_BOOL(strv_env_pairs_get(m->os_release, "PORTABLE_PREFIXES")));
printf(" %s initrd\n",
COLOR_MARK_BOOL(!strv_isempty(m->initrd_release)));
r = get_sysext_scopes(m, &sysext_scopes);
if (r < 0)
@ -549,7 +627,7 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) {
putc('\n', stdout);
} else {
_cleanup_(json_variant_unrefp) JsonVariant *mi = NULL, *osr = NULL, *exr = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *mi = NULL, *osr = NULL, *irdr = NULL, *exr = NULL;
_cleanup_strv_free_ char **sysext_scopes = NULL;
if (!strv_isempty(m->machine_info)) {
@ -564,6 +642,12 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) {
return log_oom();
}
if (!strv_isempty(m->initrd_release)) {
r = strv_pair_to_json(m->initrd_release, &irdr);
if (r < 0)
return log_oom();
}
if (!strv_isempty(m->extension_release)) {
r = strv_pair_to_json(m->extension_release, &exr);
if (r < 0)
@ -581,9 +665,11 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) {
JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->machine_id), "machineId", JSON_BUILD_ID128(m->machine_id)),
JSON_BUILD_PAIR_CONDITION(mi, "machineInfo", JSON_BUILD_VARIANT(mi)),
JSON_BUILD_PAIR_CONDITION(osr, "osRelease", JSON_BUILD_VARIANT(osr)),
JSON_BUILD_PAIR_CONDITION(osr, "initrdRelease", JSON_BUILD_VARIANT(irdr)),
JSON_BUILD_PAIR_CONDITION(exr, "extensionRelease", JSON_BUILD_VARIANT(exr)),
JSON_BUILD_PAIR("useBootableUefi", JSON_BUILD_BOOLEAN(m->partitions[PARTITION_ESP].found)),
JSON_BUILD_PAIR_CONDITION(m->has_init_system >= 0, "useBootableContainer", JSON_BUILD_BOOLEAN(m->has_init_system)),
JSON_BUILD_PAIR("useInitrd", JSON_BUILD_BOOLEAN(!strv_isempty(m->initrd_release))),
JSON_BUILD_PAIR("usePortableService", JSON_BUILD_BOOLEAN(strv_env_pairs_get(m->os_release, "PORTABLE_MATCHES"))),
JSON_BUILD_PAIR("useSystemExtension", JSON_BUILD_BOOLEAN(strv_contains(sysext_scopes, "system"))),
JSON_BUILD_PAIR("useInitRDExtension", JSON_BUILD_BOOLEAN(strv_contains(sysext_scopes, "initrd"))),
@ -958,6 +1044,97 @@ static int action_umount(const char *path) {
return 0;
}
static int action_with(DissectedImage *m, LoopDevice *d) {
_cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
_cleanup_(rmdir_and_freep) char *created_dir = NULL;
_cleanup_free_ char *temp = NULL;
int r, rcode;
r = dissected_image_decrypt_interactively(
m, NULL,
&arg_verity_settings,
arg_flags);
if (r < 0)
return r;
r = tempfn_random_child(NULL, program_invocation_short_name, &temp);
if (r < 0)
return log_error_errno(r, "Failed to generate temporary mount directory: %m");
r = mkdir_p(temp, 0700);
if (r < 0)
return log_error_errno(r, "Failed to create mount point: %m");
created_dir = TAKE_PTR(temp);
r = dissected_image_mount_and_warn(m, created_dir, UID_INVALID, UID_INVALID, arg_flags);
if (r < 0)
return r;
mounted_dir = TAKE_PTR(created_dir);
r = dissected_image_relinquish(m);
if (r < 0)
return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
r = loop_device_flock(d, LOCK_UN);
if (r < 0)
return log_error_errno(r, "Failed to unlock loopback block device: %m");
rcode = safe_fork("(with)", FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, NULL);
if (rcode == 0) {
/* Child */
if (chdir(mounted_dir) < 0) {
log_error_errno(errno, "Failed to change to '%s' directory: %m", mounted_dir);
_exit(EXIT_FAILURE);
}
if (setenv("SYSTEMD_DISSECT_ROOT", mounted_dir, /* overwrite= */ true) < 0) {
log_error_errno(errno, "Failed to set $SYSTEMD_DISSECT_ROOT: %m");
_exit(EXIT_FAILURE);
}
if (strv_isempty(arg_argv)) {
const char *sh;
sh = secure_getenv("SHELL");
if (sh) {
execvp(sh, STRV_MAKE(sh));
log_warning_errno(errno, "Failed to execute $SHELL, falling back to /bin/sh: %m");
}
execl("/bin/sh", "sh", NULL);
log_error_errno(errno, "Failed to invoke /bin/sh: %m");
} else {
execvp(arg_argv[0], arg_argv);
log_error_errno(errno, "Failed to execute '%s': %m", arg_argv[0]);
}
_exit(EXIT_FAILURE);
}
/* Let's manually detach everything, to make things synchronous */
r = loop_device_flock(d, LOCK_SH);
if (r < 0)
log_warning_errno(r, "Failed to lock loopback block device, ignoring: %m");
r = umount_recursive(mounted_dir, 0);
if (r < 0)
log_warning_errno(r, "Failed to unmount '%s', ignoring: %m", mounted_dir);
else
loop_device_unrelinquish(d); /* Let's try to destroy the loopback device */
created_dir = TAKE_PTR(mounted_dir);
if (rmdir(created_dir) < 0)
log_warning_errno(r, "Failed to remove directory '%s', ignoring: %m", created_dir);
temp = TAKE_PTR(created_dir);
return rcode;
}
static int run(int argc, char *argv[]) {
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
@ -1026,6 +1203,10 @@ static int run(int argc, char *argv[]) {
r = action_list_or_copy(m, d);
break;
case ACTION_WITH:
r = action_with(m, d);
break;
default:
assert_not_reached();
}

View File

@ -1296,6 +1296,7 @@ DissectedImage* dissected_image_unref(DissectedImage *m) {
free(m->hostname);
strv_free(m->machine_info);
strv_free(m->os_release);
strv_free(m->initrd_release);
strv_free(m->extension_release);
return mfree(m);
@ -2771,6 +2772,7 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_
META_MACHINE_ID,
META_MACHINE_INFO,
META_OS_RELEASE,
META_INITRD_RELEASE,
META_EXTENSION_RELEASE,
META_HAS_INIT_SYSTEM,
_META_MAX,
@ -2782,11 +2784,13 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_
[META_MACHINE_INFO] = "/etc/machine-info\0",
[META_OS_RELEASE] = ("/etc/os-release\0"
"/usr/lib/os-release\0"),
[META_INITRD_RELEASE] = ("/etc/initrd-release\0"
"/usr/lib/initrd-release\0"),
[META_EXTENSION_RELEASE] = "extension-release\0", /* Used only for logging. */
[META_HAS_INIT_SYSTEM] = "has-init-system\0", /* ditto */
};
_cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL, **extension_release = NULL;
_cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL, **initrd_release = NULL, **extension_release = NULL;
_cleanup_close_pair_ int error_pipe[2] = { -1, -1 };
_cleanup_(rmdir_and_freep) char *t = NULL;
_cleanup_(sigkill_waitp) pid_t child = 0;
@ -2982,6 +2986,13 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_
break;
case META_INITRD_RELEASE:
r = load_env_file_pairs(f, "initrd-release", &initrd_release);
if (r < 0)
log_debug_errno(r, "Failed to read initrd release file of image: %m");
break;
case META_EXTENSION_RELEASE:
r = load_env_file_pairs(f, "extension-release", &extension_release);
if (r < 0)
@ -3024,6 +3035,7 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_
m->machine_id = machine_id;
strv_free_and_replace(m->machine_info, machine_info);
strv_free_and_replace(m->os_release, os_release);
strv_free_and_replace(m->initrd_release, initrd_release);
strv_free_and_replace(m->extension_release, extension_release);
m->has_init_system = has_init_system;

View File

@ -233,6 +233,7 @@ struct DissectedImage {
sd_id128_t machine_id;
char **machine_info;
char **os_release;
char **initrd_release;
char **extension_release;
int has_init_system;
};

View File

@ -12,7 +12,6 @@ TEST_INSTALL_VERITY_MINIMAL=1
# shellcheck source=test/test-functions
. "${TEST_BASE_DIR:?}/test-functions"
command -v mksquashfs >/dev/null 2>&1 || exit 0
command -v veritysetup >/dev/null 2>&1 || exit 0
command -v sfdisk >/dev/null 2>&1 || exit 0
@ -27,6 +26,7 @@ test_append_files() {
install_dmevent
generate_module_dependencies
inst_binary wc
inst_binary sha256sum
if command -v openssl >/dev/null 2>&1; then
inst_binary openssl
fi

View File

@ -41,6 +41,14 @@ systemd-dissect --json=short "${image}.raw" | grep -q -F '{"rw":"ro","designator
systemd-dissect "${image}.raw" | grep -q -F "MARKER=1"
systemd-dissect "${image}.raw" | grep -q -F -f <(sed 's/"//g' "$os_release")
systemd-dissect --list "${image}.raw" | grep -q '^etc/os-release$'
read -r SHA256SUM1 _ < <(systemd-dissect --copy-from "${image}.raw" etc/os-release | sha256sum)
test "$SHA256SUM1" != ""
read -r SHA256SUM2 _ < <(systemd-dissect --read-only --with "${image}.raw" sha256sum etc/os-release)
test "$SHA256SUM2" != ""
test "$SHA256SUM1" = "$SHA256SUM2"
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"'