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.
This commit is contained in:
Luca Boccassi 2021-01-21 18:37:40 +00:00
parent af47713952
commit 6faecbd353
7 changed files with 126 additions and 2 deletions

View File

@ -567,6 +567,24 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<option>ExecStartPre=</option>, etc.) </para></listitem>
</varlistentry>
<varlistentry>
<term><command>mount-image</command> <replaceable>UNIT</replaceable> <replaceable>IMAGE</replaceable> [<replaceable>PATH</replaceable> [<replaceable>PARTITION_NAME</replaceable>:<replaceable>MOUNT_OPTIONS</replaceable>]]</term>
<listitem><para>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
<option>RootImage=</option>/<option>RootDirectory=</option>). 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 <option>MountImages=</option> setting. When combined with the <option>--read-only</option> switch, a
ready-only mount is created. When combined with the <option>--mkdir</option> 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 <option>RootImage=</option>, <option>PrivateMounts=</option>, 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.: <option>ExecReload=</option>,
<option>ExecStartPre=</option>, etc.). Example:
<programlisting>systemctl mount-image foo.service /tmp/img.raw /var/lib/image root:ro,nosuid</programlisting>
<programlisting>systemctl mount-image --mkdir bar.service /tmp/img.raw /var/lib/baz/img</programlisting></para></listitem>
</varlistentry>
<varlistentry>
<term><command>service-log-level</command> <replaceable>SERVICE</replaceable> [<replaceable>LEVEL</replaceable>]</term>

View File

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

View File

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

View File

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

View File

@ -2,3 +2,4 @@
#pragma once
int mount_bind(int argc, char *argv[], void *userdata);
int mount_image(int argc, char *argv[], void *userdata);

View File

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

View File

@ -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 <<EOF
[Service]
RuntimeMaxSec=300
Type=notify
RemainAfterExit=yes
MountAPIVFS=yes
PrivateTmp=yes
ExecStart=/bin/sh -c 'systemd-notify --ready; while ! grep -q -F MARKER /tmp/img/usr/lib/os-release; do sleep 0.1; done; mount | grep -F "/tmp/img" | grep -q -F "nosuid"'
EOF
systemctl start testservice-50d.service
systemctl mount-image --mkdir testservice-50d.service ${image}.raw /tmp/img root:nosuid
while systemctl show -P SubState testservice-50d.service | grep -q running
do
sleep 0.1
done
systemctl is-active testservice-50d.service
echo OK >/testok
exit 0