summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuca Boccassi <luca.boccassi@microsoft.com>2020-06-23 14:09:42 +0200
committerLuca Boccassi <luca.boccassi@gmail.com>2021-03-31 10:56:44 +0200
commit907952bbc92dd6656807d9b2eb0d0c94a4c9e865 (patch)
treeb5a1f594b1191699443798922b2c94e6d7fb0c76
parentgcrypt: do not ignore return values (diff)
downloadsystemd-907952bbc92dd6656807d9b2eb0d0c94a4c9e865.tar.xz
systemd-907952bbc92dd6656807d9b2eb0d0c94a4c9e865.zip
portabled: add --extension parameter for layered images support
Add an --extension parameter to portablectl, and new DBUS methods to attach/detach/reattach/inspect. Allows to append separate images on top of the root directory (os-release will be searched in there) and mount the images using an overlay-like setup (unit files will be searched in there) using the new ExtensionImages service option.
-rw-r--r--man/org.freedesktop.portable1.xml119
-rw-r--r--man/portablectl.xml13
-rw-r--r--src/portable/portable.c323
-rw-r--r--src/portable/portable.h16
-rw-r--r--src/portable/portablectl.c181
-rw-r--r--src/portable/portabled-bus.c74
-rw-r--r--src/portable/portabled-image-bus.c198
-rwxr-xr-xtest/TEST-29-PORTABLE/test.sh1
-rwxr-xr-xtest/units/testsuite-29.sh30
9 files changed, 823 insertions, 132 deletions
diff --git a/man/org.freedesktop.portable1.xml b/man/org.freedesktop.portable1.xml
index e6d2f4f536..46dca55745 100644
--- a/man/org.freedesktop.portable1.xml
+++ b/man/org.freedesktop.portable1.xml
@@ -48,6 +48,13 @@ node /org/freedesktop/portable1 {
out s image,
out ay os_release,
out a{say} units);
+ GetImageMetadataWithExtensions(in s image,
+ in as extensions,
+ in as matches,
+ in t flags,
+ out s image,
+ out ay os_release,
+ out a{say} units);
GetImageState(in s image,
out s state);
AttachImage(in s image,
@@ -56,9 +63,20 @@ node /org/freedesktop/portable1 {
in b runtime,
in s copy_mode,
out a(sss) changes);
+ AttachImageWithExtensions(in s image,
+ in as extensions,
+ in as matches,
+ in s profile,
+ in s copy_mode,
+ in t flags,
+ out a(sss) changes);
DetachImage(in s image,
in b runtime,
out a(sss) changes);
+ DetachImageWithExtensions(in s image,
+ in as extensions,
+ in t flags,
+ out a(sss) changes);
ReattachImage(in s image,
in as matches,
in s profile,
@@ -66,6 +84,14 @@ node /org/freedesktop/portable1 {
in s copy_mode,
out a(sss) changes_removed,
out a(sss) changes_updated);
+ ReattachImageWithExtensions(in s image,
+ in as extensions,
+ in as matches,
+ in s profile,
+ in s copy_mode,
+ in t flags,
+ out a(sss) changes_removed,
+ out a(sss) changes_updated);
RemoveImage(in s image);
MarkImageReadOnly(in s image,
in b read_only);
@@ -102,14 +128,22 @@ node /org/freedesktop/portable1 {
<variablelist class="dbus-method" generated="True" extra-ref="GetImageMetadata()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="GetImageMetadataWithExtensions()"/>
+
<variablelist class="dbus-method" generated="True" extra-ref="GetImageState()"/>
<variablelist class="dbus-method" generated="True" extra-ref="AttachImage()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="AttachImageWithExtensions()"/>
+
<variablelist class="dbus-method" generated="True" extra-ref="DetachImage()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="DetachImageWithExtensions()"/>
+
<variablelist class="dbus-method" generated="True" extra-ref="ReattachImage()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="ReattachImageWithExtensions()"/>
+
<variablelist class="dbus-method" generated="True" extra-ref="RemoveImage()"/>
<variablelist class="dbus-method" generated="True" extra-ref="MarkImageReadOnly()"/>
@@ -149,6 +183,12 @@ node /org/freedesktop/portable1 {
and a list of portable units contained in the image, in the form of a string (unit name) and
an array of bytes with the content.</para>
+ <para><function>GetImageMetadataWithExtensions()</function> retrieves metadata associated with an image.
+ This method is a superset of <function>GetImageMetadata()</function> with the addition of
+ a list of extensions as input parameter, which were overlayed on top of the main
+ image via <function>AttachImageWithExtensions()</function>.
+ The <varname>flag</varname> parameter is currently unused and reserved for future purposes.</para>
+
<para><function>GetImageState()</function> retrieves the image state as one of the following
strings:
<itemizedlist>
@@ -197,6 +237,16 @@ node /org/freedesktop/portable1 {
Note that an image cannot be attached if a unit that it contains is already present
on the system.</para>
+ <para><function>AttachImageWithExtensions()</function> attaches a portable image to the system.
+ This method is a superset of <function>AttachImage()</function> with the addition of
+ a list of extensions as input parameter, which will be overlayed on top of the main
+ image. When this method is used, detaching must be done by passing the same arguments via the
+ <function>DetachImageWithExtensions()</function> method. For more details on this functionality,
+ see the <varname>MountImages=</varname> entry on
+ <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ and <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+ The <varname>flag</varname> parameter is currently unused and reserved for future purposes.</para>
+
<para><function>DetachImage()</function> detaches a portable image from the system.
This method takes an image path or name, and a boolean indicating whether the image to
detach was attached only for the current boot session or persistently. This method
@@ -209,6 +259,12 @@ node /org/freedesktop/portable1 {
</itemizedlist>
Note that an image cannot be detached if a unit that it contains is running.</para>
+ <para><function>DetachImageWithExtensions()</function> detaches a portable image from the system.
+ This method is a superset of <function>DetachImage()</function> with the addition of
+ a list of extensions as input parameter, which were overlayed on top of the main
+ image via <function>AttachImageWithExtensions()</function>.
+ The <varname>flag</varname> parameter is currently unused and reserved for future purposes.</para>
+
<para><function>ReattachImage()</function> combines the effects of the
<function>AttachImage()</function> method and the <function>DetachImage()</function> method.
The difference is that it is allowed to reattach an image while one or more of its units
@@ -218,6 +274,14 @@ node /org/freedesktop/portable1 {
<function>DetachImage()</function> method (first array, units that were removed) and the
<function>AttachImage()</function> method (second array, units that were updated or added).</para>
+ <para><function>ReattachImageWithExtensions()</function> reattaches a portable image to the system.
+ This method is a superset of <function>ReattachImage()</function> with the addition of
+ a list of extensions as input parameter, which will be overlayed on top of the main
+ image. For more details on this functionality, see the <varname>MountImages=</varname> entry on
+ <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ and <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+ The <varname>flag</varname> parameter is currently unused and reserved for future purposes</para>
+
<para><function>RemoveImage()</function> removes the image with the specified name.</para>
<para><function>MarkImageReadOnly()</function> toggles the read-only flag of an image.</para>
@@ -225,6 +289,15 @@ node /org/freedesktop/portable1 {
<para><function>SetPoolLimit()</function> sets an overall quota limit on the pool of images.</para>
<para><function>SetImageLimit()</function> sets a per-image quota limit.</para>
+
+ <para>The <function>AttachImageWithExtensions()</function>,
+ <function>DetachImageWithExtensions()</function> and
+ <function>ReattachImageWithExtensions()</function> methods take in options as flags instead of
+ booleans to allow for extendability, defined as follows:</para>
+
+ <programlisting>
+#define SD_SYSTEMD_PORTABLE_RUNTIME (UINT64_C(1) &lt;&lt; 0)
+ </programlisting>
</refsect2>
<refsect2>
@@ -254,20 +327,42 @@ node /org/freedesktop/portable1 {
out s image,
out ay os_release,
out a{say} units);
+ GetMetadataWithExtensions(in as extensions,
+ in as matches,
+ in t flags,
+ out s image,
+ out ay os_release,
+ out a{say} units);
GetState(out s UNNAMED);
Attach(in as matches,
in s profile,
in b runtime,
in s copy_mode,
out a(sss) changes);
+ AttachWithExtensions(in as extensions,
+ in as matches,
+ in s profile,
+ in s copy_mode,
+ in t flags,
+ out a(sss) changes);
Detach(in b runtime,
out a(sss) changes);
+ DetachWithExtensions(in as extensions,
+ in t flags,
+ out a(sss) changes);
Reattach(in as matches,
in s profile,
in b runtime,
in s copy_mode,
out a(sss) changes_removed,
out a(sss) changes_updated);
+ ReattacheWithExtensions(in as extensions,
+ in as matches,
+ in s profile,
+ in s copy_mode,
+ in t flags,
+ out a(sss) changes_removed,
+ out a(sss) changes_updated);
Remove();
MarkReadOnly(in b read_only);
SetLimit(in t limit);
@@ -303,14 +398,22 @@ node /org/freedesktop/portable1 {
<!--method GetMetadata is not documented!-->
+ <!--method GetMetadataWithExtensions is not documented!-->
+
<!--method GetState is not documented!-->
<!--method Attach is not documented!-->
+ <!--method AttachWithExtensions is not documented!-->
+
<!--method Detach is not documented!-->
+ <!--method DetachWithExtensions is not documented!-->
+
<!--method Reattach is not documented!-->
+ <!--method ReattacheWithExtensions is not documented!-->
+
<!--method Remove is not documented!-->
<!--method MarkReadOnly is not documented!-->
@@ -327,14 +430,22 @@ node /org/freedesktop/portable1 {
<variablelist class="dbus-method" generated="True" extra-ref="GetMetadata()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="GetMetadataWithExtensions()"/>
+
<variablelist class="dbus-method" generated="True" extra-ref="GetState()"/>
<variablelist class="dbus-method" generated="True" extra-ref="Attach()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="AttachWithExtensions()"/>
+
<variablelist class="dbus-method" generated="True" extra-ref="Detach()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="DetachWithExtensions()"/>
+
<variablelist class="dbus-method" generated="True" extra-ref="Reattach()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="ReattacheWithExtensions()"/>
+
<variablelist class="dbus-method" generated="True" extra-ref="Remove()"/>
<variablelist class="dbus-method" generated="True" extra-ref="MarkReadOnly()"/>
@@ -377,14 +488,22 @@ node /org/freedesktop/portable1 {
<listitem><para>GetMetadata()</para></listitem>
+ <listitem><para>GetMetadataWithExtensions()</para></listitem>
+
<listitem><para>GetState()</para></listitem>
<listitem><para>Attach()</para></listitem>
+ <listitem><para>AttachWithExtensions()</para></listitem>
+
<listitem><para>Detach()</para></listitem>
+ <listitem><para>DetachWithExtensions()</para></listitem>
+
<listitem><para>Reattach()</para></listitem>
+ <listitem><para>ReattacheWithExtensions()</para></listitem>
+
<listitem><para>Remove()</para></listitem>
<listitem><para>MarkReadOnly()</para></listitem>
diff --git a/man/portablectl.xml b/man/portablectl.xml
index 2dae537a40..d798219d45 100644
--- a/man/portablectl.xml
+++ b/man/portablectl.xml
@@ -352,6 +352,19 @@
<listitem><para>Don't block waiting for attach --now to complete.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--extension=</option><replaceable>PATH</replaceable></term>
+
+ <listitem><para>Add an additional image <replaceable>PATH</replaceable> as an overlay on
+ top of <replaceable>IMAGE</replaceable> when attaching/detaching. This argument can be specified
+ multiple times, in which case the order in which images are laid down follows the rules specified in
+ <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for the <varname>ExtensionImages=</varname> directive.</para>
+
+ <para>Note that the same extensions have to be specified, in the same order, when attaching
+ and detaching.</para></listitem>
+ </varlistentry>
+
<xi:include href="user-system-options.xml" xpointer="host" />
<xi:include href="user-system-options.xml" xpointer="machine" />
diff --git a/src/portable/portable.c b/src/portable/portable.c
index 5651db6722..02d1d64195 100644
--- a/src/portable/portable.c
+++ b/src/portable/portable.c
@@ -11,6 +11,7 @@
#include "discover-image.h"
#include "dissect-image.h"
#include "errno-list.h"
+#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
@@ -74,17 +75,26 @@ static bool unit_match(const char *unit, char **matches) {
return false;
}
-static PortableMetadata *portable_metadata_new(const char *name, int fd) {
+static PortableMetadata *portable_metadata_new(const char *name, const char *path, int fd) {
PortableMetadata *m;
m = malloc0(offsetof(PortableMetadata, name) + strlen(name) + 1);
if (!m)
return NULL;
+ /* In case of a layered attach, we want to remember which image the unit came from */
+ if (path) {
+ m->image_path = strdup(path);
+ if (!m->image_path) {
+ free(m);
+ return NULL;
+ }
+ }
+
strcpy(m->name, name);
m->fd = fd;
- return m;
+ return TAKE_PTR(m);
}
PortableMetadata *portable_metadata_unref(PortableMetadata *i) {
@@ -93,6 +103,7 @@ PortableMetadata *portable_metadata_unref(PortableMetadata *i) {
safe_close(i->fd);
free(i->source);
+ free(i->image_path);
return mfree(i);
}
@@ -255,7 +266,7 @@ static int extract_now(
}
if (ret_os_release) {
- os_release = portable_metadata_new("/etc/os-release", os_release_fd);
+ os_release = portable_metadata_new("/etc/os-release", NULL, os_release_fd);
if (!os_release)
return -ENOMEM;
@@ -316,7 +327,7 @@ static int extract_now(
return log_debug_errno(r, "Failed to send unit metadata to parent: %m");
}
- m = portable_metadata_new(de->d_name, fd);
+ m = portable_metadata_new(de->d_name, NULL, fd);
if (!m)
return -ENOMEM;
fd = -1;
@@ -342,6 +353,7 @@ static int extract_now(
static int portable_extract_by_path(
const char *path,
+ bool extract_os_release,
char **matches,
PortableMetadata **ret_os_release,
Hashmap **ret_unit_files,
@@ -412,7 +424,7 @@ static int portable_extract_by_path(
if (r == 0) {
seq[0] = safe_close(seq[0]);
- r = dissected_image_mount(m, tmpdir, UID_INVALID, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_VALIDATE_OS);
+ r = dissected_image_mount(m, tmpdir, UID_INVALID, DISSECT_IMAGE_READ_ONLY);
if (r < 0) {
log_debug_errno(r, "Failed to mount dissected image: %m");
goto child_finish;
@@ -448,7 +460,7 @@ static int portable_extract_by_path(
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid item sent from child.");
- add = portable_metadata_new(name, fd);
+ add = portable_metadata_new(name, path, fd);
if (!add)
return -ENOMEM;
fd = -1;
@@ -478,10 +490,12 @@ static int portable_extract_by_path(
child = 0;
}
- if (!os_release)
+ /* When the portable image is layered, the image with units will not
+ * have a full filesystem, so no os-release - it will be in the root layer */
+ if (extract_os_release && !os_release)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image '%s' lacks os-release data, refusing.", path);
- if (hashmap_isempty(unit_files))
+ if (!extract_os_release && hashmap_isempty(unit_files))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Couldn't find any matching unit files in image '%s', refusing.", path);
if (ret_unit_files)
@@ -496,11 +510,16 @@ static int portable_extract_by_path(
int portable_extract(
const char *name_or_path,
char **matches,
+ char **extension_image_paths,
PortableMetadata **ret_os_release,
Hashmap **ret_unit_files,
sd_bus_error *error) {
+ _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
+ _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL;
+ _cleanup_hashmap_free_ Hashmap *unit_files = NULL;
_cleanup_(image_unrefp) Image *image = NULL;
+ Image *ext;
int r;
assert(name_or_path);
@@ -509,7 +528,46 @@ int portable_extract(
if (r < 0)
return r;
- return portable_extract_by_path(image->path, matches, ret_os_release, ret_unit_files, error);
+ if (!strv_isempty(extension_image_paths)) {
+ char **p;
+
+ extension_images = ordered_hashmap_new(&image_hash_ops);
+ if (!extension_images)
+ return -ENOMEM;
+
+ STRV_FOREACH(p, extension_image_paths) {
+ _cleanup_(image_unrefp) Image *new = NULL;
+
+ r = image_find_harder(IMAGE_PORTABLE, *p, NULL, &new);
+ if (r < 0)
+ return r;
+
+ r = ordered_hashmap_put(extension_images, new->name, new);
+ if (r < 0)
+ return r;
+ TAKE_PTR(new);
+ }
+ }
+
+ r = portable_extract_by_path(image->path, true, matches, &os_release, &unit_files, error);
+ if (r < 0)
+ return r;
+
+ ORDERED_HASHMAP_FOREACH(ext, extension_images) {
+ _cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL;
+
+ r = portable_extract_by_path(ext->path, false, matches, NULL, &extra_unit_files, error);
+ if (r < 0)
+ return r;
+ r = hashmap_move(unit_files, extra_unit_files);
+ if (r < 0)
+ return r;
+ }
+
+ *ret_os_release = TAKE_PTR(os_release);
+ *ret_unit_files = TAKE_PTR(unit_files);
+
+ return 0;
}
static int unit_file_is_active(
@@ -684,9 +742,49 @@ void portable_changes_free(PortableChange *changes, size_t n_changes) {
free(changes);
}
+static const char *root_setting_from_image(ImageType type) {
+ return IN_SET(type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) ? "RootDirectory=" : "RootImage=";
+}
+
+static int make_marker_text(const char *image_path, OrderedHashmap *extension_images, char **ret_text) {
+ _cleanup_free_ char *text = NULL, *escaped_image_path = NULL;
+ Image *ext;
+
+ assert(image_path);
+ assert(ret_text);
+
+ escaped_image_path = xescape(image_path, ":");
+ if (!escaped_image_path)
+ return -ENOMEM;
+
+ /* If the image is layered, include all layers in the marker as a colon-separated
+ * list of paths, so that we can do exact matches on removal. */
+ text = strjoin(PORTABLE_DROPIN_MARKER_BEGIN, escaped_image_path);
+ if (!text)
+ return -ENOMEM;
+
+ ORDERED_HASHMAP_FOREACH(ext, extension_images) {
+ _cleanup_free_ char *escaped = NULL;
+
+ escaped = xescape(ext->path, ":");
+ if (!escaped)
+ return -ENOMEM;
+
+ if (!strextend(&text, ":", escaped))
+ return -ENOMEM;
+ }
+
+ if (!strextend(&text, PORTABLE_DROPIN_MARKER_END "\n"))
+ return -ENOMEM;
+
+ *ret_text = TAKE_PTR(text);
+ return 0;
+}
+
static int install_chroot_dropin(
const char *image_path,
ImageType type,
+ OrderedHashmap *extension_images,
const PortableMetadata *m,
const char *dropin_dir,
char **ret_dropin,
@@ -694,6 +792,7 @@ static int install_chroot_dropin(
size_t *n_changes) {
_cleanup_free_ char *text = NULL, *dropin = NULL;
+ Image *ext;
int r;
assert(image_path);
@@ -704,12 +803,15 @@ static int install_chroot_dropin(
if (!dropin)
return -ENOMEM;
- text = strjoin(PORTABLE_DROPIN_MARKER_BEGIN, image_path, PORTABLE_DROPIN_MARKER_END "\n");
- if (!text)
- return -ENOMEM;
+ r = make_marker_text(image_path, extension_images, &text);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to generate marker string for portable drop-in: %m");
if (endswith(m->name, ".service")) {
- const char *os_release_source;
+ const char *os_release_source, *root_type;
+ _cleanup_free_ char *base_name = NULL;
+
+ root_type = root_setting_from_image(type);
if (access("/etc/os-release", F_OK) < 0) {
if (errno != ENOENT)
@@ -719,14 +821,23 @@ static int install_chroot_dropin(
} else
os_release_source = "/etc/os-release";
+ r = path_extract_filename(m->image_path ?: image_path, &base_name);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to extract basename from '%s': %m", m->image_path ?: image_path);
+
if (!strextend(&text,
"\n"
"[Service]\n",
- IN_SET(type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) ? "RootDirectory=" : "RootImage=", image_path, "\n"
- "Environment=PORTABLE=", basename(image_path), "\n"
+ root_type, image_path, "\n"
+ "Environment=PORTABLE=", base_name, "\n"
"BindReadOnlyPaths=", os_release_source, ":/run/host/os-release\n"
- "LogExtraFields=PORTABLE=", basename(image_path), "\n"))
+ "LogExtraFields=PORTABLE=", base_name, "\n"))
return -ENOMEM;
+
+ if (m->image_path && !path_equal(m->image_path, image_path))
+ ORDERED_HASHMAP_FOREACH(ext, extension_images)
+ if (!strextend(&text, "ExtensionImages=", ext->path, "\n"))
+ return -ENOMEM;
}
r = write_string_file(dropin, text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
@@ -841,6 +952,7 @@ static int attach_unit_file(
const LookupPaths *paths,
const char *image_path,
ImageType type,
+ OrderedHashmap *extension_images,
const PortableMetadata *m,
const char *profile,
PortableFlags flags,
@@ -881,7 +993,7 @@ static int attach_unit_file(
* is reloaded while we are creating things here: as long as only the drop-ins exist the unit doesn't exist at
* all for PID 1. */
- r = install_chroot_dropin(image_path, type, m, dropin_dir, &chroot_dropin, changes, n_changes);
+ r = install_chroot_dropin(image_path, type, extension_images, m, dropin_dir, &chroot_dropin, changes, n_changes);
if (r < 0)
return r;
@@ -984,20 +1096,49 @@ static int install_image_symlink(
return 0;
}
+static int install_image_and_extensions_symlinks(
+ const Image *image,
+ OrderedHashmap *extension_images,
+ PortableFlags flags,
+ PortableChange **changes,
+ size_t *n_changes) {
+
+ Image *ext;
+ int r;
+
+ assert(image);
+
+ ORDERED_HASHMAP_FOREACH(ext, extension_images) {
+ r = install_image_symlink(ext->path, flags, changes, n_changes);
+ if (r < 0)
+ return r;
+ }
+
+ r = install_image_symlink(image->path, flags, changes, n_changes);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
int portable_attach(
sd_bus *bus,
const char *name_or_path,
char **matches,
const char *profile,
+ char **extension_image_paths,
PortableFlags flags,
PortableChange **changes,
size_t *n_changes,
sd_bus_error *error) {
+ _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL;
_cleanup_hashmap_free_ Hashmap *unit_files = NULL;
_cleanup_(lookup_paths_free) LookupPaths paths = {};
_cleanup_(image_unrefp) Image *image = NULL;
PortableMetadata *item;
+ Image *ext;
+ char **p;
int r;
assert(name_or_path);
@@ -1005,11 +1146,40 @@ int portable_attach(
r = image_find_harder(IMAGE_PORTABLE, name_or_path, NULL, &image);
if (r < 0)
return r;
+ if (!strv_isempty(extension_image_paths)) {
+ extension_images = ordered_hashmap_new(&image_hash_ops);
+ if (!extension_images)
+ return -ENOMEM;
+
+ STRV_FOREACH(p, extension_image_paths) {
+ _cleanup_(image_unrefp) Image *new = NULL;
+
+ r = image_find_harder(IMAGE_PORTABLE, *p, NULL, &new);
+ if (r < 0)
+ return r;
+
+ r = ordered_hashmap_put(extension_images, new->name, new);
+ if (r < 0)
+ return r;
+ TAKE_PTR(new);
+ }
+ }
- r = portable_extract_by_path(image->path, matches, NULL, &unit_files, error);
+ r = portable_extract_by_path(image->path, true, matches, NULL, &unit_files, error);
if (r < 0)
return r;
+ ORDERED_HASHMAP_FOREACH(ext, extension_images) {
+ _cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL;
+
+ r = portable_extract_by_path(ext->path, false, matches, NULL, &extra_unit_files, error);
+ if (r < 0)
+ return r;
+ r = hashmap_move(unit_files, extra_unit_files);
+ if (r < 0)
+ return r;
+ }
+
r = lookup_paths_init(&paths, UNIT_FILE_SYSTEM, LOOKUP_PATHS_SPLIT_USR, NULL);
if (r < 0)
return r;
@@ -1029,62 +1199,95 @@ int portable_attach(
}
HASHMAP_FOREACH(item, unit_files) {
- r = attach_unit_file(&paths, image->path, image->type, item, profile, flags, changes, n_changes);
+ r = attach_unit_file(&paths, image->path, image->type, extension_images,
+ item, profile, flags, changes, n_changes);
if (r < 0)
return r;
}
/* We don't care too much for the image symlink, it's just a convenience thing, it's not necessary for proper
* operation otherwise. */
- (void) install_image_symlink(image->path, flags, changes, n_changes);
+ (void) install_image_and_extensions_symlinks(image, extension_images, flags, changes, n_changes);
return 0;
}
-static bool marker_matches_image(const char *marker, const char *name_or_path) {
+static bool marker_matches_images(const char *marker, const char *name_or_path, char **extension_image_paths) {
+ _cleanup_strv_free_ char **root_and_extensions = NULL;
+ char **image_name_or_path;
const char *a;
+ int r;
assert(marker);
assert(name_or_path);
- a = last_path_component(marker);
+ /* If extensions were used when attaching, the marker will be a colon-separated
+ * list of images/paths. We enforce strict 1:1 matching, so that we are sure
+ * we are detaching exactly what was attached.
+ * For each image, starting with the root, we look for a token in the marker,
+ * and return a negative answer on any non-matching combination. */
- if (image_name_is_valid(name_or_path)) {
- const char *e, *underscore;
+ root_and_extensions = strv_new(name_or_path);
+ if (!root_and_extensions)
+ return -ENOMEM;
- /* We shall match against an image name. In that case let's compare the last component, and optionally
- * allow either a suffix of ".raw" or a series of "/".
- * But allow matching on a different version of the same image, when a "_" is used as a separator. */
- underscore = strchr(name_or_path, '_');
- if (underscore)
- return strneq(a, name_or_path, underscore - name_or_path);
+ r = strv_extend_strv(&root_and_extensions, extension_image_paths, false);
+ if (r < 0)
+ return r;
- e = startswith(a, name_or_path);
- if (!e)
+ STRV_FOREACH(image_name_or_path, root_and_extensions) {
+ _cleanup_free_ char *image = NULL;
+
+ r = extract_first_word(&marker, &image, ":", EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse marker: %s", marker);
+ if (r == 0)
return false;
- return
- e[strspn(e, "/")] == 0 ||
- streq(e, ".raw");
- } else {
- const char *b, *underscore;
- size_t l;
+ a = last_path_component(image);
+
+ if (image_name_is_valid(*image_name_or_path)) {
+ const char *e, *underscore;
- /* We shall match against a path. Let's ignore any prefix here though, as often there are many ways to
- * reach the same file. However, in this mode, let's validate any file suffix. */
+ /* We shall match against an image name. In that case let's compare the last component, and optionally
+ * allow either a suffix of ".raw" or a series of "/".
+ * But allow matching on a different version of the same image, when a "_" is used as a separator. */
+ underscore = strchr(*image_name_or_path, '_');
+ if (underscore) {
+ if (strneq(a, *image_name_or_path, underscore - *image_name_or_path))
+ continue;
+ return false;
+ }
- l = strcspn(a, "/");
- b = last_path_component(name_or_path);
+ e = startswith(a, *image_name_or_path);
+ if (!e)
+ return false;
- if (strcspn(b, "/") != l)
- return false;
+ if(!(e[strspn(e, "/")] == 0 || streq(e, ".raw")))
+ return false;
+ } else {
+ const char *b, *underscore;
+ size_t l;
+
+ /* We shall match against a path. Let's ignore any prefix here though, as often there are many ways to
+ * reach the same file. However, in this mode, let's validate any file suffix. */
+
+ l = strcspn(a, "/");
+ b = last_path_component(*image_name_or_path);
+
+ if (strcspn(b, "/") != l)
+ return false;
- underscore = strchr(b, '_');
- if (underscore)
- l = underscore - b;
+ underscore = strchr(b, '_');
+ if (underscore)
+ l = underscore - b;
- return strneq(a, b, l);
+ if (!strneq(a, b, l))
+ return false;
+ }
}
+
+ return true;
}
static int test_chroot_dropin(
@@ -1092,6 +1295,7 @@ static int test_chroot_dropin(
const char *where,
const char *fname,
const char *name_or_path,
+ char **extension_image_paths,
char **ret_marker) {
_cleanup_free_ char *line = NULL, *marker = NULL;
@@ -1138,7 +1342,7 @@ static int test_chroot_dropin(
if (!name_or_path)
r = true;
else
- r = marker_matches_image(marker, name_or_path);
+ r = marker_matches_images(marker, name_or_path, extension_image_paths);
if (ret_marker)
*ret_marker = TAKE_PTR(marker);
@@ -1149,6 +1353,7 @@ static int test_chroot_dropin(
int portable_detach(
sd_bus *bus,
const char *name_or_path,
+ char **extension_image_paths,
PortableFlags flags,
PortableChange **changes,
size_t *n_changes,
@@ -1193,7 +1398,7 @@ int portable_detach(
if (!IN_SET(de->d_type, DT_LNK, DT_REG))
continue;
- r = test_chroot_dropin(d, where, de->d_name, name_or_path, &marker);
+ r = test_chroot_dropin(d, where, de->d_name, name_or_path, extension_image_paths, &marker);
if (r < 0)
return r;
if (r == 0)
@@ -1215,12 +1420,20 @@ int portable_detach(
if (r < 0)
return log_debug_errno(r, "Failed to add unit name '%s' to set: %m", de->d_name);
- if (path_is_absolute(marker) &&
- !image_in_search_path(IMAGE_PORTABLE, NULL, marker)) {
+ for (const char *p = marker;;) {
+ _cleanup_free_ char *image = NULL;
- r = set_ensure_consume(&markers, &path_hash_ops_free, TAKE_PTR(marker));
+ r = extract_first_word(&p, &image, ":", EXTRACT_UNESCAPE_SEPARATORS|EXTRACT_RETAIN_ESCAPE);
if (r < 0)
- return r;
+ return log_debug_errno(r, "Failed to parse marker: %s", p);
+ if (r == 0)
+ break;
+
+ if (path_is_absolute(image) && !image_in_search_path(IMAGE_PORTABLE, NULL, image)) {
+ r = set_ensure_consume(&markers, &path_hash_ops_free, TAKE_PTR(image));
+ if (r < 0)
+ return r;
+ }
}
}
@@ -1358,7 +1571,7 @@ static int portable_get_state_internal(
if (!IN_SET(de->d_type, DT_LNK, DT_REG))
continue;
- r = test_chroot_dropin(d, where, de->d_name, name_or_path, NULL);
+ r = test_chroot_dropin(d, where, de->d_name, name_or_path, NULL, NULL);
if (r < 0)
return r;
if (r == 0)
diff --git a/src/portable/portable.h b/src/portable/portable.h
index 5694bd2b62..dd080edf4e 100644
--- a/src/portable/portable.h
+++ b/src/portable/portable.h
@@ -11,6 +11,7 @@
typedef struct PortableMetadata {
int fd;
char *source;
+ char *image_path;
char name[];
} PortableMetadata;
@@ -18,10 +19,13 @@ typedef struct PortableMetadata {
#define PORTABLE_METADATA_IS_UNIT(m) (!IN_SET((m)->name[0], 0, '/'))
typedef enum PortableFlags {
- PORTABLE_PREFER_COPY = 1 << 0,
- PORTABLE_PREFER_SYMLINK = 1 << 1,
- PORTABLE_RUNTIME = 1 << 2,
+ 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_TYPE_MAX,
+ _PORTABLE_TYPE_INVALID = -EINVAL,
} PortableFlags;
/* This enum is anonymous, since we usually store it in an 'int', as we overload it with negative errno
@@ -59,10 +63,10 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(PortableMetadata*, portable_metadata_unref);
int portable_metadata_hashmap_to_sorted_array(Hashmap *unit_files, PortableMetadata ***ret);
-int portable_extract(const char *image, char **matches, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, sd_bus_error *error);
+int portable_extract(const char *image, char **matches, char **extension_image_paths, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, sd_bus_error *error);
-int portable_attach(sd_bus *bus, const char *name_or_path, char **matches, const char *profile, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
-int portable_detach(sd_bus *bus, const char *name_or_path, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
+int portable_attach(sd_bus *bus, const char *name_or_path, char **matches, const char *profile, char **extension_images, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
+int portable_detach(sd_bus *bus, const char *name_or_path, char **extension_image_paths, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
int portable_get_state(sd_bus *bus, const char *name_or_path, PortableFlags flags, PortableState *ret, sd_bus_error *error);
diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c
index 77fcd4fe6f..fa6df9054a 100644
--- a/src/portable/portablectl.c
+++ b/src/portable/portablectl.c
@@ -21,9 +21,11 @@
#include "main-func.h"
#include "os-util.h"
#include "pager.h"
+#include "parse-argument.h"
#include "parse-util.h"
#include "path-util.h"
#include "pretty-print.h"
+#include "portable.h"
#include "spawn-polkit-agent.h"
#include "string-util.h"
#include "strv.h"
@@ -44,6 +46,9 @@ static const char *arg_host = NULL;
static bool arg_enable = false;
static bool arg_now = false;
static bool arg_no_block = false;
+static char **arg_extension_images = NULL;
+
+STATIC_DESTRUCTOR_REGISTER(arg_extension_images, strv_freep);
static bool is_portable_managed(const char *unit) {
return ENDSWITH_SET(unit, ".service", ".target", ".socket", ".path", ".timer");
@@ -83,6 +88,38 @@ static int determine_image(const char *image, bool permit_non_existing, char **r
return 0;
}
+static int attach_extensions_to_message(sd_bus_message *m, char **extensions) {
+ char **p;
+ int r;
+
+ assert(m);
+
+ if (strv_isempty(extensions))
+ return 0;
+
+ r = sd_bus_message_open_container(m, 'a', "s");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ STRV_FOREACH(p, extensions) {
+ _cleanup_free_ char *resolved_extension_image = NULL;
+
+ r = determine_image(*p, false, &resolved_extension_image);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(m, "s", resolved_extension_image);
+ 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);
+
+ return 0;
+}
+
static int extract_prefix(const char *path, char **ret) {
_cleanup_free_ char *name = NULL;
const char *bn, *underscore;
@@ -219,15 +256,55 @@ static int maybe_reload(sd_bus **bus) {
return 0;
}
-static int inspect_image(int argc, char *argv[], void *userdata) {
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+static int get_image_metadata(sd_bus *bus, const char *image, char **matches, sd_bus_message **reply) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ const char *method;
+ uint64_t flags = 0;
+ int r;
+
+ assert(bus);
+ assert(reply);
+
+ method = strv_isempty(arg_extension_images) ? "GetImageMetadata" : "GetImageMetadataWithExtensions";
+
+ r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append(m, "s", image);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = attach_extensions_to_message(m, arg_extension_images);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append_strv(m, matches);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ if (!strv_isempty(arg_extension_images)) {
+ r = sd_bus_message_append(m, "t", flags);
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
+
+ r = sd_bus_call(bus, m, 0, &error, reply);
+ if (r < 0)
+ return log_error_errno(r, "Failed to inspect image metadata: %s", bus_error_message(&error, r));
+
+ return 0;
+}
+
+static int inspect_image(int argc, char *argv[], void *userdata) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_strv_free_ char **matches = NULL;
_cleanup_free_ char *image = NULL;
bool nl = false, header = false;
- const void *data;
const char *path;
+ const void *data;
size_t sz;
int r;
@@ -243,21 +320,9 @@ static int inspect_image(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
- r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "GetImageMetadata");
- if (r < 0)
- return bus_log_create_error(r);
-
- r = sd_bus_message_append(m, "s", image);
- if (r < 0)
- return bus_log_create_error(r);
-
- r = sd_bus_message_append_strv(m, matches);
- if (r < 0)
- return bus_log_create_error(r);
-
- r = sd_bus_call(bus, m, 0, &error, &reply);
+ r = get_image_metadata(bus, image, matches, &reply);
if (r < 0)
- return log_error_errno(r, "Failed to inspect image metadata: %s", bus_error_message(&error, r));
+ return r;
r = sd_bus_message_read(reply, "s", &path);
if (r < 0)
@@ -607,8 +672,7 @@ static int maybe_stop_enable_restart(sd_bus *bus, sd_bus_message *reply) {
static int maybe_stop_disable(sd_bus *bus, char *image, char *argv[]) {
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL;
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_strv_free_ char **matches = NULL;
int r;
@@ -623,21 +687,9 @@ static int maybe_stop_disable(sd_bus *bus, char *image, char *argv[]) {
if (r < 0)
return log_error_errno(r, "Could not watch jobs: %m");
- r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "GetImageMetadata");
+ r = get_image_metadata(bus, image, matches, &reply);
if (r < 0)
- return bus_log_create_error(r);
-
- r = sd_bus_message_append(m, "s", image);
- if (r < 0)
- return bus_log_create_error(r);
-
- r = sd_bus_message_append_strv(m, matches);
- if (r < 0)
- return bus_log_create_error(r);
-
- r = sd_bus_call(bus, m, 0, &error, &reply);
- if (r < 0)
- return log_error_errno(r, "Failed to inspect image metadata: %s", bus_error_message(&error, r));
+ return r;
r = sd_bus_message_skip(reply, "say");
if (r < 0)
@@ -693,7 +745,7 @@ static int attach_reattach_image(int argc, char *argv[], const char *method) {
int r;
assert(method);
- assert(STR_IN_SET(method, "AttachImage", "ReattachImage"));
+ assert(STR_IN_SET(method, "AttachImage", "ReattachImage", "AttachImageWithExtensions", "ReattachImageWithExtensions"));
r = determine_image(argv[1], false, &image);
if (r < 0)
@@ -717,11 +769,24 @@ static int attach_reattach_image(int argc, char *argv[], const char *method) {
if (r < 0)
return bus_log_create_error(r);
+ r = attach_extensions_to_message(m, arg_extension_images);
+ if (r < 0)
+ return r;
+
r = sd_bus_message_append_strv(m, matches);
if (r < 0)
return bus_log_create_error(r);
- r = sd_bus_message_append(m, "sbs", arg_profile, arg_runtime, arg_copy_mode);
+ r = sd_bus_message_append(m, "s", arg_profile);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ if (STR_IN_SET(method, "AttachImageWithExtensions", "ReattachImageWithExtensions")) {
+ uint64_t flags = arg_runtime ? PORTABLE_RUNTIME : 0;
+
+ r = sd_bus_message_append(m, "st", arg_copy_mode, flags);
+ } else
+ r = sd_bus_message_append(m, "bs", arg_runtime, arg_copy_mode);
if (r < 0)
return bus_log_create_error(r);
@@ -733,7 +798,7 @@ static int attach_reattach_image(int argc, char *argv[], const char *method) {
print_changes(reply);
- if (streq(method, "AttachImage"))
+ if (STR_IN_SET(method, "AttachImage", "AttachImageWithExtensions"))
(void) maybe_enable_start(bus, reply);
else {
/* ReattachImage returns 2 lists - removed units first, and changed/added second */
@@ -745,18 +810,19 @@ static int attach_reattach_image(int argc, char *argv[], const char *method) {
}
static int attach_image(int argc, char *argv[], void *userdata) {
- return attach_reattach_image(argc, argv, "AttachImage");
+ return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) ? "AttachImage" : "AttachImageWithExtensions");
}
static int reattach_image(int argc, char *argv[], void *userdata) {
- return attach_reattach_image(argc, argv, "ReattachImage");
+ return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) ? "ReattachImage" : "ReattachImageWithExtensions");
}
static int detach_image(int argc, char *argv[], void *userdata) {
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_free_ char *image = NULL;
+ const char *method;
int r;
r = determine_image(argv[1], true, &image);
@@ -771,9 +837,32 @@ static int detach_image(int argc, char *argv[], void *userdata) {
(void) maybe_stop_disable(bus, image, argv);
- r = bus_call_method(bus, bus_portable_mgr, "DetachImage", &error, &reply, "sb", image, arg_runtime);
+ method = strv_isempty(arg_extension_images) ? "DetachImage" : "DetachImageWithExtensions";
+
+ r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append(m, "s", image);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = attach_extensions_to_message(m, arg_extension_images);
+ if (r < 0)
+ return r;
+
+ if (!strv_isempty(arg_extension_images)) {
+ uint64_t flags = arg_runtime ? PORTABLE_RUNTIME : 0;
+
+ r = sd_bus_message_append(m, "t", flags);
+ } else
+ r = sd_bus_message_append(m, "b", arg_runtime);
if (r < 0)
- return log_error_errno(r, "Failed to detach image: %s", bus_error_message(&error, r));
+ return bus_log_create_error(r);
+
+ r = sd_bus_call(bus, m, 0, &error, &reply);
+ if (r < 0)
+ return log_error_errno(r, "%s failed: %s", method, bus_error_message(&error, r));
(void) maybe_reload(&bus);
@@ -1045,6 +1134,7 @@ static int help(int argc, char *argv[], void *userdata) {
" --now Immediately start/stop the portable service after\n"
" 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"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
@@ -1055,6 +1145,7 @@ static int help(int argc, char *argv[], void *userdata) {
}
static int parse_argv(int argc, char *argv[]) {
+ int r;
enum {
ARG_VERSION = 0x100,
@@ -1068,6 +1159,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_ENABLE,
ARG_NOW,
ARG_NO_BLOCK,
+ ARG_EXTENSION,
};
static const struct option options[] = {
@@ -1087,6 +1179,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "enable", no_argument, NULL, ARG_ENABLE },
{ "now", no_argument, NULL, ARG_NOW },
{ "no-block", no_argument, NULL, ARG_NO_BLOCK },
+ { "extension", required_argument, NULL, ARG_EXTENSION },
{}
};
@@ -1185,6 +1278,12 @@ static int parse_argv(int argc, char *argv[]) {
arg_no_block = true;
break;
+ case ARG_EXTENSION:
+ r = strv_extend(&arg_extension_images, optarg);
+ if (r < 0)
+ return log_oom();
+ break;
+
case '?':
return -EINVAL;
diff --git a/src/portable/portabled-bus.c b/src/portable/portabled-bus.c
index 6d0dee99c3..72f685f76d 100644
--- a/src/portable/portabled-bus.c
+++ b/src/portable/portabled-bus.c
@@ -252,11 +252,13 @@ static int method_attach_image(sd_bus_message *message, void *userdata, sd_bus_e
}
static int method_detach_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_strv_free_ char **extension_images = NULL;
PortableChange *changes = NULL;
+ PortableFlags flags = 0;
Manager *m = userdata;
size_t n_changes = 0;
const char *name_or_path;
- int r, runtime;
+ int r;
assert(message);
assert(m);
@@ -265,10 +267,37 @@ static int method_detach_image(sd_bus_message *message, void *userdata, sd_bus_e
* detach already deleted images too, in case the user already deleted an image before properly detaching
* it. */
- r = sd_bus_message_read(message, "sb", &name_or_path, &runtime);
+ r = sd_bus_message_read(message, "s", &name_or_path);
if (r < 0)
return r;
+ if (sd_bus_message_is_method_call(message, NULL, "DetachImageWithExtensions")) {
+ uint64_t input_flags = 0;
+
+ r = sd_bus_message_read_strv(message, &extension_images);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(message, "t", &input_flags);
+ if (r < 0)
+ return r;
+
+ if ((input_flags & ~_PORTABLE_MASK_PUBLIC) != 0)
+ return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid 'flags' parameter '%" PRIu64 "'",
+ input_flags);
+ flags |= input_flags;
+ } else {
+ int runtime;
+
+ r = sd_bus_message_read(message, "b", &runtime);
+ if (r < 0)
+ return r;
+
+ if (runtime)
+ flags |= PORTABLE_RUNTIME;
+ }
+
r = bus_verify_polkit_async(
message,
CAP_SYS_ADMIN,
@@ -286,7 +315,8 @@ static int method_detach_image(sd_bus_message *message, void *userdata, sd_bus_e
r = portable_detach(
sd_bus_message_get_bus(message),
name_or_path,
- runtime ? PORTABLE_RUNTIME : 0,
+ extension_images,
+ flags,
&changes,
&n_changes,
error);
@@ -383,6 +413,16 @@ const sd_bus_vtable manager_vtable[] = {
"a{say}", units),
method_get_image_metadata,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("GetImageMetadataWithExtensions",
+ SD_BUS_ARGS("s", image,
+ "as", extensions,
+ "as", matches,
+ "t", flags),
+ SD_BUS_RESULT("s", image,
+ "ay", os_release,
+ "a{say}", units),
+ method_get_image_metadata,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("GetImageState",
SD_BUS_ARGS("s", image),
SD_BUS_RESULT("s", state),
@@ -397,12 +437,29 @@ const sd_bus_vtable manager_vtable[] = {
SD_BUS_RESULT("a(sss)", changes),
method_attach_image,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("AttachImageWithExtensions",
+ SD_BUS_ARGS("s", image,
+ "as", extensions,
+ "as", matches,
+ "s", profile,
+ "s", copy_mode,
+ "t", flags),
+ SD_BUS_RESULT("a(sss)", changes),
+ method_attach_image,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("DetachImage",
SD_BUS_ARGS("s", image,
"b", runtime),
SD_BUS_RESULT("a(sss)", changes),
method_detach_image,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("DetachImageWithExtensions",
+ SD_BUS_ARGS("s", image,
+ "as", extensions,
+ "t", flags),
+ SD_BUS_RESULT("a(sss)", changes),
+ method_detach_image,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("ReattachImage",
SD_BUS_ARGS("s", image,
"as", matches,
@@ -413,6 +470,17 @@ const sd_bus_vtable manager_vtable[] = {
"a(sss)", changes_updated),
method_reattach_image,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("ReattachImageWithExtensions",
+ SD_BUS_ARGS("s", image,
+ "as", extensions,
+ "as", matches,
+ "s", profile,
+ "s", copy_mode,
+ "t", flags),
+ SD_BUS_RESULT("a(sss)", changes_removed,
+ "a(sss)", changes_updated),
+ method_reattach_image,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("RemoveImage",
SD_BUS_ARGS("s", image),
SD_BUS_NO_RESULT,
diff --git a/src/portable/portabled-image-bus.c b/src/portable/portabled-image-bus.c
index 8332332c91..88d8f914e0 100644
--- a/src/portable/portabled-image-bus.c
+++ b/src/portable/portabled-image-bus.c
@@ -75,20 +75,22 @@ static int bus_image_method_get_os_release(sd_bus_message *message, void *userda
static int append_fd(sd_bus_message *m, PortableMetadata *d) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *buf = NULL;
- size_t n;
+ size_t n = 0;
int r;
assert(m);
- assert(d);
- assert(d->fd >= 0);
- f = take_fdopen(&d->fd, "r");
- if (!f)
- return -errno;
+ if (d) {
+ assert(d->fd >= 0);
- r = read_full_stream(f, &buf, &n);
- if (r < 0)
- return r;
+ f = take_fdopen(&d->fd, "r");
+ if (!f)
+ return -errno;
+
+ r = read_full_stream(f, &buf, &n);
+ if (r < 0)
+ return r;
+ }
return sd_bus_message_append_array(m, 'y', buf, n);
}
@@ -101,10 +103,12 @@ int bus_image_common_get_metadata(
sd_bus_error *error) {
_cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
+ _cleanup_strv_free_ char **matches = NULL, **extension_images = NULL;
_cleanup_hashmap_free_ Hashmap *unit_files = NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_free_ PortableMetadata **sorted = NULL;
- _cleanup_strv_free_ char **matches = NULL;
+ /* Unused for now, but added to the DBUS methods for future-proofing */
+ uint64_t input_flags = 0;
size_t i;
int r;
@@ -116,10 +120,29 @@ int bus_image_common_get_metadata(
m = image->userdata;
}
+ if (sd_bus_message_is_method_call(message, NULL, "GetImageMetadataWithExtensions") ||
+ sd_bus_message_is_method_call(message, NULL, "GetMetadataWithExtensions")) {
+ r = sd_bus_message_read_strv(message, &extension_images);
+ if (r < 0)
+ return r;
+ }
+
r = sd_bus_message_read_strv(message, &matches);
if (r < 0)
return r;
+ if (sd_bus_message_is_method_call(message, NULL, "GetImageMetadataWithExtensions") ||
+ sd_bus_message_is_method_call(message, NULL, "GetMetadataWithExtensions")) {
+ r = sd_bus_message_read(message, "t", &input_flags);
+ if (r < 0)
+ return r;
+ /* Let clients know that this version doesn't support any flags */
+ if (input_flags != 0)
+ return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid 'flags' parameter '%" PRIu64 "'",
+ input_flags);
+ }
+
r = bus_image_acquire(m,
message,
name_or_path,
@@ -136,6 +159,7 @@ int bus_image_common_get_metadata(
r = portable_extract(
image->path,
matches,
+ extension_images,
&os_release,
&unit_files,
error);
@@ -223,12 +247,12 @@ int bus_image_common_attach(
Image *image,
sd_bus_error *error) {
- _cleanup_strv_free_ char **matches = NULL;
+ _cleanup_strv_free_ char **matches = NULL, **extension_images = NULL;
PortableChange *changes = NULL;
PortableFlags flags = 0;
const char *profile, *copy_mode;
size_t n_changes = 0;
- int runtime, r;
+ int r;
assert(message);
assert(name_or_path || image);
@@ -238,14 +262,44 @@ int bus_image_common_attach(
m = image->userdata;
}
+ if (sd_bus_message_is_method_call(message, NULL, "AttachImageWithExtensions") ||
+ sd_bus_message_is_method_call(message, NULL, "AttachWithExtensions")) {
+ r = sd_bus_message_read_strv(message, &extension_images);
+ if (r < 0)
+ return r;
+ }
+
r = sd_bus_message_read_strv(message, &matches);
if (r < 0)
return r;
- r = sd_bus_message_read(message, "sbs", &profile, &runtime, &copy_mode);
+ r = sd_bus_message_read(message, "s", &profile);
if (r < 0)
return r;
+ if (sd_bus_message_is_method_call(message, NULL, "AttachImageWithExtensions") ||
+ sd_bus_message_is_method_call(message, NULL, "AttachWithExtensions")) {
+ uint64_t input_flags = 0;
+
+ r = sd_bus_message_read(message, "st", &copy_mode, &input_flags);
+ if (r < 0)
+ return r;
+ if ((input_flags & ~_PORTABLE_MASK_PUBLIC) != 0)
+ return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid 'flags' parameter '%" PRIu64 "'",
+ input_flags);
+ flags |= input_flags;
+ } else {
+ int runtime;
+
+ r = sd_bus_message_read(message, "bs", &runtime, &copy_mode);
+ if (r < 0)
+ return r;
+
+ if (runtime)
+ flags |= PORTABLE_RUNTIME;
+ }
+
if (streq(copy_mode, "symlink"))
flags |= PORTABLE_PREFER_SYMLINK;
else if (streq(copy_mode, "copy"))
@@ -253,9 +307,6 @@ int bus_image_common_attach(
else if (!isempty(copy_mode))
return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Unknown copy mode '%s'", copy_mode);
- if (runtime)
- flags |= PORTABLE_RUNTIME;
-
r = bus_image_acquire(m,
message,
name_or_path,
@@ -274,6 +325,7 @@ int bus_image_common_attach(
image->path,
matches,
profile,
+ extension_images,
flags,
&changes,
&n_changes,
@@ -297,19 +349,46 @@ static int bus_image_method_detach(
void *userdata,
sd_bus_error *error) {
+ _cleanup_strv_free_ char **extension_images = NULL;
PortableChange *changes = NULL;
Image *image = userdata;
Manager *m = image->userdata;
+ PortableFlags flags = 0;
size_t n_changes = 0;
- int r, runtime;
+ int r;
assert(message);
assert(image);
assert(m);
- r = sd_bus_message_read(message, "b", &runtime);
- if (r < 0)
- return r;
+ if (sd_bus_message_is_method_call(message, NULL, "DetachWithExtensions")) {
+ r = sd_bus_message_read_strv(message, &extension_images);
+ if (r < 0)
+ return r;
+ }
+
+ if (sd_bus_message_is_method_call(message, NULL, "DetachWithExtensions")) {
+ uint64_t input_flags = 0;
+
+ r = sd_bus_message_read(message, "t", &input_flags);
+ if (r < 0)
+ return r;
+
+ if ((input_flags & ~_PORTABLE_MASK_PUBLIC) != 0)
+ return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid 'flags' parameter '%" PRIu64 "'",
+ input_flags);
+ flags |= input_flags;
+ } else {
+ int runtime;
+
+ r = sd_bus_message_read(message, "b", &runtime);
+ if (r < 0)
+ return r;
+
+ if (runtime)
+ flags |= PORTABLE_RUNTIME;
+ }
r = bus_verify_polkit_async(
message,
@@ -328,7 +407,8 @@ static int bus_image_method_detach(
r = portable_detach(
sd_bus_message_get_bus(message),
image->path,
- runtime ? PORTABLE_RUNTIME : 0,
+ extension_images,
+ flags,
&changes,
&n_changes,
error);
@@ -510,10 +590,10 @@ int bus_image_common_reattach(
PortableChange *changes_detached = NULL, *changes_attached = NULL, *changes_gone = NULL;
size_t n_changes_detached = 0, n_changes_attached = 0, n_changes_gone = 0;
- _cleanup_strv_free_ char **matches = NULL;
+ _cleanup_strv_free_ char **matches = NULL, **extension_images = NULL;
PortableFlags flags = PORTABLE_REATTACH;
const char *profile, *copy_mode;
- int runtime, r;
+ int r;
assert(message);
assert(name_or_path || image);
@@ -523,14 +603,45 @@ int bus_image_common_reattach(
m = image->userdata;
}
+ if (sd_bus_message_is_method_call(message, NULL, "ReattachImageWithExtensions") ||
+ sd_bus_message_is_method_call(message, NULL, "ReattachWithExtensions")) {
+ r = sd_bus_message_read_strv(message, &extension_images);
+ if (r < 0)
+ return r;
+ }
+
r = sd_bus_message_read_strv(message, &matches);
if (r < 0)
return r;
- r = sd_bus_message_read(message, "sbs", &profile, &runtime, &copy_mode);
+ r = sd_bus_message_read(message, "s", &profile);
if (r < 0)
return r;
+ if (sd_bus_message_is_method_call(message, NULL, "ReattachImageWithExtensions") ||
+ sd_bus_message_is_method_call(message, NULL, "ReattachWithExtensions")) {
+ uint64_t input_flags = 0;
+
+ r = sd_bus_message_read(message, "st", &copy_mode, &input_flags);
+ if (r < 0)
+ return r;
+
+ if ((input_flags & ~_PORTABLE_MASK_PUBLIC) != 0)
+ return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid 'flags' parameter '%" PRIu64 "'",
+ input_flags);
+ flags |= input_flags;
+ } else {
+ int runtime;
+
+ r = sd_bus_message_read(message, "bs", &runtime, &copy_mode);
+ if (r < 0)
+ return r;
+
+ if (runtime)
+ flags |= PORTABLE_RUNTIME;
+ }
+
if (streq(copy_mode, "symlink"))
flags |= PORTABLE_PREFER_SYMLINK;
else if (streq(copy_mode, "copy"))
@@ -538,9 +649,6 @@ int bus_image_common_reattach(
else if (!isempty(copy_mode))
return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Unknown copy mode '%s'", copy_mode);
- if (runtime)
- flags |= PORTABLE_RUNTIME;
-
r = bus_image_acquire(m,
message,
name_or_path,
@@ -557,6 +665,7 @@ int bus_image_common_reattach(
r = portable_detach(
sd_bus_message_get_bus(message),
image->path,
+ extension_images,
flags,
&changes_detached,
&n_changes_detached,
@@ -569,6 +678,7 @@ int bus_image_common_reattach(
image->path,
matches,
profile,
+ extension_images,
flags,
&changes_attached,
&n_changes_attached,
@@ -721,6 +831,15 @@ const sd_bus_vtable image_vtable[] = {
"a{say}", units),
bus_image_method_get_metadata,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("GetMetadataWithExtensions",
+ SD_BUS_ARGS("as", extensions,
+ "as", matches,
+ "t", flags),
+ SD_BUS_RESULT("s", image,
+ "ay", os_release,
+ "a{say}", units),
+ bus_image_method_get_metadata,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("GetState",
SD_BUS_NO_ARGS,
SD_BUS_RESULT("s", state),
@@ -734,11 +853,26 @@ const sd_bus_vtable image_vtable[] = {
SD_BUS_RESULT("a(sss)", changes),
bus_image_method_attach,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("AttachWithExtensions",
+ SD_BUS_ARGS("as", extensions,
+ "as", matches,
+ "s", profile,
+ "s", copy_mode,
+ "t", flags),
+ SD_BUS_RESULT("a(sss)", changes),
+ bus_image_method_attach,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("Detach",
SD_BUS_ARGS("b", runtime),
SD_BUS_RESULT("a(sss)", changes),
bus_image_method_detach,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("DetachWithExtensions",
+ SD_BUS_ARGS("as", extensions,
+ "t", flags),
+ SD_BUS_RESULT("a(sss)", changes),
+ bus_image_method_detach,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("Reattach",
SD_BUS_ARGS("as", matches,
"s", profile,
@@ -748,6 +882,16 @@ const sd_bus_vtable image_vtable[] = {
"a(sss)", changes_updated),
bus_image_method_reattach,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("ReattacheWithExtensions",
+ SD_BUS_ARGS("as", extensions,
+ "as", matches,
+ "s", profile,
+ "s", copy_mode,
+ "t", flags),
+ SD_BUS_RESULT("a(sss)", changes_removed,
+ "a(sss)", changes_updated),
+ bus_image_method_reattach,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("Remove",
SD_BUS_NO_ARGS,
SD_BUS_NO_RESULT,
diff --git a/test/TEST-29-PORTABLE/test.sh b/test/TEST-29-PORTABLE/test.sh
index 801e74c13d..cd421efdae 100755
--- a/test/TEST-29-PORTABLE/test.sh
+++ b/test/TEST-29-PORTABLE/test.sh
@@ -15,6 +15,7 @@ test_append_files() {
instmods loop =block
instmods squashfs =squashfs
instmods dm_verity =md
+ instmods overlay =overlayfs
install_dmevent
generate_module_dependencies
inst_binary losetup
diff --git a/test/units/testsuite-29.sh b/test/units/testsuite-29.sh
index b5b05b42d9..2e55c275bf 100755
--- a/test/units/testsuite-29.sh
+++ b/test/units/testsuite-29.sh
@@ -63,6 +63,36 @@ portablectl detach --now --enable --runtime /tmp/minimal_1 app0
portablectl list | grep -q -F "No images."
+root="/usr/share/minimal_0.raw"
+app1="/usr/share/app1.raw"
+
+portablectl attach --now --runtime --extension ${app1} ${root} app1
+
+systemctl is-active app1.service
+
+portablectl reattach --now --runtime --extension ${app1} ${root} app1
+
+systemctl is-active app1.service
+
+portablectl detach --now --runtime --extension ${app1} ${root} app1
+
+# portablectl also works with directory paths rather than images
+
+mkdir /tmp/rootdir /tmp/app1 /tmp/overlay
+mount ${app1} /tmp/app1
+mount ${root} /tmp/rootdir
+mount -t overlay overlay -o lowerdir=/tmp/app1:/tmp/rootdir /tmp/overlay
+
+portablectl attach --copy=symlink --now --runtime /tmp/overlay app1
+
+systemctl is-active app1.service
+
+portablectl detach --now --runtime overlay app1
+
+umount /tmp/overlay
+umount /tmp/rootdir
+umount /tmp/app1
+
echo OK > /testok
exit 0