nspawn: introduce --cleanup option (#34776)

This is useful when the previous invocation is unexpectedly killed.

Otherwise, if systemd-nspawn is killed forcibly, then unix-export
directory is not cleared and unmounted, and the subsequent invocation
will fail. E.g.
```
[   18.895515] TEST-13-NSPAWN.sh[645]: + machinectl start long-running
[   18.945703] systemd-nspawn[1387]: Mount point '/run/systemd/nspawn/unix-export/long-running' exists already, refusing.
[   18.949236] systemd[1]: systemd-nspawn@long-running.service: Failed with result 'exit-code'.
[   18.949743] systemd[1]: Failed to start systemd-nspawn@long-running.service.
```
This commit is contained in:
Yu Watanabe
2025-03-17 06:53:46 +09:00
committed by GitHub
6 changed files with 64 additions and 1 deletions

View File

@@ -223,6 +223,20 @@
<xi:include href="version-info.xml" xpointer="v226"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--cleanup</option></term>
<listitem>
<para>Clean up left-over mounts and underlying mount points used by the container, and exit without
invoking any containers. This may be useful when the previous invocation of
<command>systemd-nspawn</command> was unexpectedly terminated. This requires at least one of
<option>-M/--machine=</option>, <option>-D/--directory=</option>, or <option>-i/--image=</option>
to determine the mounts to be cleaned up.</para>
<xi:include href="version-info.xml" xpointer="v257"/>
</listitem>
</varlistentry>
</variablelist>
<refsect2>

View File

@@ -66,7 +66,8 @@ _systemd_nspawn() {
local -A OPTS=(
[STANDALONE]='-h --help --version --private-network -b --boot --read-only -q --quiet --share-system
--keep-unit -n --network-veth -j -x --ephemeral -a --as-pid2 -U --suppress-sync=yes'
--keep-unit -n --network-veth -j -x --ephemeral -a --as-pid2 -U --suppress-sync=yes
--cleanup'
[ARG]='-D --directory -u --user --uuid --capability --drop-capability --link-journal --bind --bind-ro
-M --machine -S --slice -E --setenv -Z --selinux-context -L --selinux-apifs-context
--register --network-interface --network-bridge --personality -i --image --image-policy --tmpfs

View File

@@ -16,6 +16,7 @@ _arguments \
'(- *)'{-h,--help}'[Show this help.]' \
'(- *)--version[Print a short version string and exit.]' \
'(--quiet -q)'{--quiet,-q}'[Turns off any status output by the tool itself.]' \
'--cleanup[Cleanup left-over mounts and underlying mount points used by the container.]' \
'(--directory -D)'{--directory=,-D+}'[Directory to use as file system root for the namespace container. If omitted the current directory will be used.]:directories:_directories' \
'--template=[Initialize root directory from template directory, if missing.]:template:_directories' \
'(--ephemeral -x)'{--ephemeral,-x}'[Run container with snapshot of root directory, and remove it after exit.]' \

View File

@@ -236,6 +236,7 @@ static Architecture arg_architecture = _ARCHITECTURE_INVALID;
static ImagePolicy *arg_image_policy = NULL;
static char *arg_background = NULL;
static bool arg_privileged = false;
static bool arg_cleanup = false;
STATIC_DESTRUCTOR_REGISTER(arg_directory, freep);
STATIC_DESTRUCTOR_REGISTER(arg_template, freep);
@@ -327,6 +328,8 @@ static int help(void) {
" -q --quiet Do not show status information\n"
" --no-pager Do not pipe output into a pager\n"
" --settings=BOOLEAN Load additional settings from .nspawn file\n"
" --cleanup Clean up left-over mounts and underlying mount\n"
" points used by the container\n"
"\n%3$sImage:%4$s\n"
" -D --directory=PATH Root directory for the container\n"
" --template=PATH Initialize root directory from template directory,\n"
@@ -751,6 +754,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_SUPPRESS_SYNC,
ARG_IMAGE_POLICY,
ARG_BACKGROUND,
ARG_CLEANUP,
};
static const struct option options[] = {
@@ -826,6 +830,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "suppress-sync", required_argument, NULL, ARG_SUPPRESS_SYNC },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{ "background", required_argument, NULL, ARG_BACKGROUND },
{ "cleanup", no_argument, NULL, ARG_CLEANUP },
{}
};
@@ -1609,6 +1614,10 @@ static int parse_argv(int argc, char *argv[]) {
return r;
break;
case ARG_CLEANUP:
arg_cleanup = true;
break;
case '?':
return -EINVAL;
@@ -5919,6 +5928,34 @@ static void initialize_defaults(void) {
arg_private_network = !arg_privileged;
}
static void cleanup_propagation_and_export_directories(void) {
const char *p;
if (!arg_machine || !arg_privileged)
return;
p = strjoina("/run/systemd/nspawn/propagate/", arg_machine);
(void) rm_rf(p, REMOVE_ROOT);
p = strjoina("/run/systemd/nspawn/unix-export/", arg_machine);
(void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW);
(void) rmdir(p);
}
static int do_cleanup(void) {
int r;
if (arg_ephemeral)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot specify --ephemeral with --cleanup.");
r = determine_names();
if (r < 0)
return r;
cleanup_propagation_and_export_directories();
return 0;
}
static int run(int argc, char *argv[]) {
bool remove_directory = false, remove_image = false, veth_created = false;
_cleanup_close_ int master = -EBADF, userns_fd = -EBADF, mount_fd = -EBADF;
@@ -5941,6 +5978,9 @@ static int run(int argc, char *argv[]) {
if (r <= 0)
goto finish;
if (arg_cleanup)
return do_cleanup();
r = cant_be_in_netns();
if (r < 0)
goto finish;
@@ -6476,6 +6516,8 @@ finish:
(void) rmdir(p);
}
cleanup_propagation_and_export_directories();
expose_port_flush(&fw_ctx, arg_expose_ports, AF_INET, &expose_args.address4);
expose_port_flush(&fw_ctx, arg_expose_ports, AF_INET6, &expose_args.address6);

View File

@@ -287,6 +287,10 @@ rm -f /var/lib/machines/long-running/trap
varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Kill '{"name":"long-running", "whom": "leader", "signal": 5}'
timeout 120 bash -c "until test -e /var/lib/machines/long-running/trap; do sleep .5; done"
# sending KILL signal
varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Kill '{"name":"long-running", "signal": 9}'
timeout 30 bash -c "while varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{\"name\":\"long-running\"}'; do sleep 0.5; done"
# test io.systemd.Machine.Terminate
long_running_machine_start
rm -f /var/lib/machines/long-running/terminate

View File

@@ -19,6 +19,7 @@ RequiresMountsFor=/var/lib/machines/%i
[Service]
# Make sure the DeviceAllow= lines below can properly resolve the 'block-loop' expression (and others)
ExecStart=systemd-nspawn --quiet --keep-unit --boot --link-journal=try-guest --network-veth -U --settings=override --machine=%i
ExecStopPost=systemd-nspawn --cleanup --machine=%i
KillMode=mixed
Type=notify
RestartForceExitStatus=133