diff --git a/man/org.freedesktop.portable1.xml b/man/org.freedesktop.portable1.xml
index 0007e0c2de..0e926fe850 100644
--- a/man/org.freedesktop.portable1.xml
+++ b/man/org.freedesktop.portable1.xml
@@ -307,10 +307,13 @@ node /org/freedesktop/portable1 {
The AttachImageWithExtensions(),
DetachImageWithExtensions() and
ReattachImageWithExtensions() methods take in options as flags instead of
- booleans to allow for extendability, defined as follows:
+ booleans to allow for extendability. SD_SYSTEMD_PORTABLE_FORCE will cause
+ safety checks that ensure the units are not running while the new image is attached or detached
+ to be skipped. They are defined as follows:
#define SD_SYSTEMD_PORTABLE_RUNTIME (UINT64_C(1) << 0)
+#define SD_SYSTEMD_PORTABLE_FORCE (UINT64_C(1) << 1)
diff --git a/man/portablectl.xml b/man/portablectl.xml
index ed2089c63a..64927a2fe6 100644
--- a/man/portablectl.xml
+++ b/man/portablectl.xml
@@ -374,6 +374,13 @@
and detaching.
+
+
+
+ Skip safety checks and attach or detach images (with extensions) without first ensuring
+ that the units are not running.
+
+
diff --git a/src/portable/portable.c b/src/portable/portable.c
index 5d0a965db5..202442903f 100644
--- a/src/portable/portable.c
+++ b/src/portable/portable.c
@@ -1357,19 +1357,20 @@ int portable_attach(
if (r < 0)
return r;
- HASHMAP_FOREACH(item, unit_files) {
- r = unit_file_exists(LOOKUP_SCOPE_SYSTEM, &paths, item->name);
- if (r < 0)
- return sd_bus_error_set_errnof(error, r, "Failed to determine whether unit '%s' exists on the host: %m", item->name);
- if (!FLAGS_SET(flags, PORTABLE_REATTACH) && r > 0)
- return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, "Unit file '%s' exists on the host already, refusing.", item->name);
+ if (!FLAGS_SET(flags, PORTABLE_REATTACH) && !FLAGS_SET(flags, PORTABLE_FORCE))
+ HASHMAP_FOREACH(item, unit_files) {
+ r = unit_file_exists(LOOKUP_SCOPE_SYSTEM, &paths, item->name);
+ if (r < 0)
+ return sd_bus_error_set_errnof(error, r, "Failed to determine whether unit '%s' exists on the host: %m", item->name);
+ if (r > 0)
+ return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, "Unit file '%s' exists on the host already, refusing.", item->name);
- r = unit_file_is_active(bus, item->name, error);
- if (r < 0)
- return r;
- if (!FLAGS_SET(flags, PORTABLE_REATTACH) && r > 0)
- return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, "Unit file '%s' is active already, refusing.", item->name);
- }
+ r = unit_file_is_active(bus, item->name, error);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, "Unit file '%s' is active already, refusing.", item->name);
+ }
HASHMAP_FOREACH(item, unit_files) {
r = attach_unit_file(&paths, image->path, image->type, extension_images,
@@ -1599,11 +1600,13 @@ int portable_detach(
if (r == 0)
continue;
- r = unit_file_is_active(bus, unit_name, error);
- if (r < 0)
- return r;
- if (!FLAGS_SET(flags, PORTABLE_REATTACH) && r > 0)
- return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, "Unit file '%s' is active, can't detach.", unit_name);
+ if (!FLAGS_SET(flags, PORTABLE_REATTACH) && !FLAGS_SET(flags, PORTABLE_FORCE)) {
+ r = unit_file_is_active(bus, unit_name, error);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, "Unit file '%s' is active, can't detach.", unit_name);
+ }
r = set_ensure_consume(&unit_files, &string_hash_ops_free, TAKE_PTR(unit_name));
if (r < 0)
diff --git a/src/portable/portable.h b/src/portable/portable.h
index c6061cc589..dc2f8781b7 100644
--- a/src/portable/portable.h
+++ b/src/portable/portable.h
@@ -22,10 +22,11 @@ typedef struct PortableMetadata {
typedef enum PortableFlags {
PORTABLE_RUNTIME = 1 << 0, /* Public API via DBUS, do not change */
- PORTABLE_PREFER_COPY = 1 << 1,
- PORTABLE_PREFER_SYMLINK = 1 << 2,
- PORTABLE_REATTACH = 1 << 3,
- _PORTABLE_MASK_PUBLIC = PORTABLE_RUNTIME,
+ PORTABLE_FORCE = 1 << 1, /* Public API via DBUS, do not change */
+ PORTABLE_PREFER_COPY = 1 << 2,
+ PORTABLE_PREFER_SYMLINK = 1 << 3,
+ PORTABLE_REATTACH = 1 << 4,
+ _PORTABLE_MASK_PUBLIC = PORTABLE_RUNTIME | PORTABLE_FORCE,
_PORTABLE_TYPE_MAX,
_PORTABLE_TYPE_INVALID = -EINVAL,
} PortableFlags;
diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c
index c3c22220c2..7e3627d1da 100644
--- a/src/portable/portablectl.c
+++ b/src/portable/portablectl.c
@@ -48,6 +48,7 @@ static bool arg_enable = false;
static bool arg_now = false;
static bool arg_no_block = false;
static char **arg_extension_images = NULL;
+static bool arg_force = false;
STATIC_DESTRUCTOR_REGISTER(arg_extension_images, strv_freep);
@@ -868,7 +869,7 @@ static int attach_reattach_image(int argc, char *argv[], const char *method) {
return bus_log_create_error(r);
if (STR_IN_SET(method, "AttachImageWithExtensions", "ReattachImageWithExtensions")) {
- uint64_t flags = arg_runtime ? PORTABLE_RUNTIME : 0;
+ uint64_t flags = (arg_runtime ? PORTABLE_RUNTIME : 0) | (arg_force ? PORTABLE_FORCE : 0);
r = sd_bus_message_append(m, "st", arg_copy_mode, flags);
} else
@@ -940,7 +941,7 @@ static int detach_image(int argc, char *argv[], void *userdata) {
if (strv_isempty(arg_extension_images))
r = sd_bus_message_append(m, "b", arg_runtime);
else {
- uint64_t flags = arg_runtime ? PORTABLE_RUNTIME : 0;
+ uint64_t flags = (arg_runtime ? PORTABLE_RUNTIME : 0) | (arg_force ? PORTABLE_FORCE : 0);
r = sd_bus_message_append(m, "t", flags);
}
@@ -1241,6 +1242,8 @@ static int help(int argc, char *argv[], void *userdata) {
" attach/before detach\n"
" --no-block Don't block waiting for attach --now to complete\n"
" --extension=PATH Extend the image with an overlay\n"
+ " --force Skip 'already active' check when attaching or\n"
+ " detaching an image (with extensions)\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
@@ -1266,6 +1269,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_NOW,
ARG_NO_BLOCK,
ARG_EXTENSION,
+ ARG_FORCE,
};
static const struct option options[] = {
@@ -1286,6 +1290,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "now", no_argument, NULL, ARG_NOW },
{ "no-block", no_argument, NULL, ARG_NO_BLOCK },
{ "extension", required_argument, NULL, ARG_EXTENSION },
+ { "force", no_argument, NULL, ARG_FORCE },
{}
};
@@ -1390,6 +1395,10 @@ static int parse_argv(int argc, char *argv[]) {
return log_oom();
break;
+ case ARG_FORCE:
+ arg_force = true;
+ break;
+
case '?':
return -EINVAL;
diff --git a/test/units/testsuite-29.sh b/test/units/testsuite-29.sh
index 4b32161e5f..072ca921b3 100755
--- a/test/units/testsuite-29.sh
+++ b/test/units/testsuite-29.sh
@@ -124,7 +124,16 @@ systemctl is-active app1.service
status="$(portablectl is-attached --extension app1 minimal_1)"
[[ "${status}" == "running-runtime" ]]
-portablectl detach --now --runtime --extension /usr/share/app1.raw /usr/share/minimal_1.raw app1
+portablectl detach --force --no-reload --runtime --extension /usr/share/app1.raw /usr/share/minimal_1.raw app1
+portablectl "${ARGS[@]}" attach --force --no-reload --runtime --extension /usr/share/app1.raw /usr/share/minimal_0.raw app1
+systemctl daemon-reload
+systemctl restart app1.service
+
+systemctl is-active app1.service
+status="$(portablectl is-attached --extension app1 minimal_0)"
+[[ "${status}" == "running-runtime" ]]
+
+portablectl detach --now --runtime --extension /usr/share/app1.raw /usr/share/minimal_0.raw app1
# Ensure that the combination of read-only images, state directory and dynamic user works, and that
# state is retained. Check after detaching, as on slow systems (eg: sanitizers) it might take a while