From 6faecbd353f9eb5aebe65a7159c5e61191e4330f Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 21 Jan 2021 18:37:40 +0000 Subject: [PATCH] systemctl: add new option to mount image inside a running service namespace Use the new DBUS method and follow the same pattern as the systemctl bind command. --- man/systemctl.xml | 18 +++++++ shell-completion/bash/systemctl.in | 2 +- shell-completion/zsh/_systemctl.in | 5 ++ src/systemctl/systemctl-mount.c | 75 ++++++++++++++++++++++++++++++ src/systemctl/systemctl-mount.h | 1 + src/systemctl/systemctl.c | 5 +- test/units/testsuite-50.sh | 22 +++++++++ 7 files changed, 126 insertions(+), 2 deletions(-) diff --git a/man/systemctl.xml b/man/systemctl.xml index 6177d1a0dd5..f316fb5eb83 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -567,6 +567,24 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err , etc.) + + mount-image UNIT IMAGE [PATH [PARTITION_NAME:MOUNT_OPTIONS]] + + Mounts an image from the host into the specified unit's view. The first path argument is the source + image on the host, the second path argument is the destination directory in the unit's view (ie: inside + /). Any following argument is interpreted as a + colon-separated tuple of partition name and comma-separated list of mount options for that partition. The format is the + same as the service setting. When combined with the switch, a + ready-only mount is created. When combined with the switch, the destination path is first + created before the mount is applied. Note that this option is currently only supported for units that run within a mount + namespace (e.g.: with , , etc.). + Note that the namespace mentioned here, where the image mount will be added to, is the one where the main service + process runs, as other processes run in distinct namespaces (e.g.: , + , etc.). Example: + systemctl mount-image foo.service /tmp/img.raw /var/lib/image root:ro,nosuid + systemctl mount-image --mkdir bar.service /tmp/img.raw /var/lib/baz/img + + service-log-level SERVICE [LEVEL] diff --git a/shell-completion/bash/systemctl.in b/shell-completion/bash/systemctl.in index 7e99ef4bf39..6c5717d8ccb 100644 --- a/shell-completion/bash/systemctl.in +++ b/shell-completion/bash/systemctl.in @@ -214,7 +214,7 @@ _systemctl () { list-timers list-units list-unit-files poweroff reboot rescue show-environment suspend get-default is-system-running preset-all' - [FILE]='link switch-root bind' + [FILE]='link switch-root bind mount-image' [TARGETS]='set-default' [MACHINES]='list-machines' [LOG_LEVEL]='log-level' diff --git a/shell-completion/zsh/_systemctl.in b/shell-completion/zsh/_systemctl.in index c4ea78b3c10..03586de9fdc 100644 --- a/shell-completion/zsh/_systemctl.in +++ b/shell-completion/zsh/_systemctl.in @@ -32,6 +32,7 @@ "list-dependencies:Show unit dependency tree" "clean:Remove configuration, state, cache, logs or runtime data of units" "bind:Bind mount a path from the host into a unit's namespace" + "mount-image:Mount an image from the host into a unit's namespace" ) local -a machine_commands=( @@ -383,6 +384,10 @@ done _files } +(( $+functions[_systemctl_mount-image] )) || _systemctl_mount-image() { + _files + } + # no systemctl completion for: # [STANDALONE]='daemon-reexec daemon-reload default # emergency exit halt kexec list-jobs list-units diff --git a/src/systemctl/systemctl-mount.c b/src/systemctl/systemctl-mount.c index 513a876f218..646f9b77df3 100644 --- a/src/systemctl/systemctl-mount.c +++ b/src/systemctl/systemctl-mount.c @@ -2,6 +2,7 @@ #include "bus-error.h" #include "bus-locator.h" +#include "dissect-image.h" #include "systemctl-mount.h" #include "systemctl-util.h" #include "systemctl.h" @@ -39,3 +40,77 @@ int mount_bind(int argc, char *argv[], void *userdata) { return 0; } + +int mount_image(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *unit = argv[1], *src = argv[2], *dest = argv[3]; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *n = NULL; + sd_bus *bus; + int r; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + polkit_agent_open_maybe(); + + r = unit_name_mangle(unit, arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN, &n); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + + r = bus_message_new_method_call( + bus, + &m, + bus_systemd_mgr, + "MountImageUnit"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssbb", + n, + src, + dest, + arg_read_only, + arg_mkdir); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(ss)"); + if (r < 0) + return bus_log_create_error(r); + + if (argc > 4) { + _cleanup_free_ char *partition = NULL, *mount_options = NULL; + const char *options = argv[4]; + + r = extract_many_words(&options, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options, NULL); + if (r < 0) + return r; + /* Single set of options, applying to the root partition/single filesystem */ + if (r == 1) { + r = sd_bus_message_append(m, "(ss)", "root", partition); + if (r < 0) + return bus_log_create_error(r); + } else if (r > 1) { + if (partition_designator_from_string(partition) < 0) + return bus_log_create_error(-EINVAL); + + r = sd_bus_message_append(m, "(ss)", partition, mount_options); + if (r < 0) + return bus_log_create_error(r); + } + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, -1, &error, NULL); + if (r < 0) + return log_error_errno(r, "Failed to mount image: %s", bus_error_message(&error, r)); + + return 0; +} diff --git a/src/systemctl/systemctl-mount.h b/src/systemctl/systemctl-mount.h index 1f9b3879fb9..db0343f8312 100644 --- a/src/systemctl/systemctl-mount.h +++ b/src/systemctl/systemctl-mount.h @@ -2,3 +2,4 @@ #pragma once int mount_bind(int argc, char *argv[], void *userdata); +int mount_image(int argc, char *argv[], void *userdata); diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 4726f65f970..4739faae39a 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -162,6 +162,8 @@ static int systemctl_help(void) { " set-property UNIT PROPERTY=VALUE... Sets one or more properties of a unit\n" " bind UNIT PATH [PATH] Bind-mount a path from the host into a\n" " unit's namespace\n" + " mount-image UNIT PATH [PATH [OPTS]] Mount an image from the host into a\n" + " unit's namespace\n" " service-log-level SERVICE [LEVEL] Get/set logging threshold for service\n" " service-log-target SERVICE [TARGET] Get/set logging target for service\n" " reset-failed [PATTERN...] Reset failed state for all, one, or more\n" @@ -292,7 +294,7 @@ static int systemctl_help(void) { " 'utc': 'Day YYYY-MM-DD HH:MM:SS UTC\n" " 'us+utc': 'Day YYYY-MM-DD HH:MM:SS.UUUUUU UTC\n" " --read-only Create read-only bind mount\n" - " --mkdir Create directory before bind-mounting, if missing\n" + " --mkdir Create directory before mounting, if missing\n" "\nSee the %2$s for details.\n" , program_invocation_short_name , link @@ -1065,6 +1067,7 @@ static int systemctl_main(int argc, char *argv[]) { { "add-requires", 3, VERB_ANY, 0, add_dependency }, { "edit", 2, VERB_ANY, VERB_ONLINE_ONLY, edit }, { "bind", 3, 4, VERB_ONLINE_ONLY, mount_bind }, + { "mount-image", 4, 5, VERB_ONLINE_ONLY, mount_image }, {} }; diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index d615ac2ea7e..783dfbf50e8 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -205,6 +205,28 @@ grep -q -F "MARKER=1" ${image_dir}/result/c grep -F "squashfs" ${image_dir}/result/c | grep -q -F "noatime" grep -F "squashfs" ${image_dir}/result/c | grep -q -F -v "nosuid" +# Adding a new mounts at runtime works if the unit is in the active state, +# so use Type=notify to make sure there's no race condition in the test +cat > /run/systemd/system/testservice-50d.service </testok exit 0