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.
This commit is contained in:
Luca Boccassi 2020-06-23 13:09:42 +01:00 committed by Luca Boccassi
parent 248b1e0aa4
commit 907952bbc9
9 changed files with 823 additions and 132 deletions

View File

@ -48,6 +48,13 @@ node /org/freedesktop/portable1 {
out s image, out s image,
out ay os_release, out ay os_release,
out a{say} units); 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, GetImageState(in s image,
out s state); out s state);
AttachImage(in s image, AttachImage(in s image,
@ -56,9 +63,20 @@ node /org/freedesktop/portable1 {
in b runtime, in b runtime,
in s copy_mode, in s copy_mode,
out a(sss) changes); 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, DetachImage(in s image,
in b runtime, in b runtime,
out a(sss) changes); out a(sss) changes);
DetachImageWithExtensions(in s image,
in as extensions,
in t flags,
out a(sss) changes);
ReattachImage(in s image, ReattachImage(in s image,
in as matches, in as matches,
in s profile, in s profile,
@ -66,6 +84,14 @@ node /org/freedesktop/portable1 {
in s copy_mode, in s copy_mode,
out a(sss) changes_removed, out a(sss) changes_removed,
out a(sss) changes_updated); 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); RemoveImage(in s image);
MarkImageReadOnly(in s image, MarkImageReadOnly(in s image,
in b read_only); 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="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="GetImageState()"/>
<variablelist class="dbus-method" generated="True" extra-ref="AttachImage()"/> <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="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="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="RemoveImage()"/>
<variablelist class="dbus-method" generated="True" extra-ref="MarkImageReadOnly()"/> <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 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> 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 <para><function>GetImageState()</function> retrieves the image state as one of the following
strings: strings:
<itemizedlist> <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 Note that an image cannot be attached if a unit that it contains is already present
on the system.</para> 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. <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 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 detach was attached only for the current boot session or persistently. This method
@ -209,6 +259,12 @@ node /org/freedesktop/portable1 {
</itemizedlist> </itemizedlist>
Note that an image cannot be detached if a unit that it contains is running.</para> 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 <para><function>ReattachImage()</function> combines the effects of the
<function>AttachImage()</function> method and the <function>DetachImage()</function> method. <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 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>DetachImage()</function> method (first array, units that were removed) and the
<function>AttachImage()</function> method (second array, units that were updated or added).</para> <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>RemoveImage()</function> removes the image with the specified name.</para>
<para><function>MarkImageReadOnly()</function> toggles the read-only flag of an image.</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>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><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>
<refsect2> <refsect2>
@ -254,20 +327,42 @@ node /org/freedesktop/portable1 {
out s image, out s image,
out ay os_release, out ay os_release,
out a{say} units); 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); GetState(out s UNNAMED);
Attach(in as matches, Attach(in as matches,
in s profile, in s profile,
in b runtime, in b runtime,
in s copy_mode, in s copy_mode,
out a(sss) changes); 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, Detach(in b runtime,
out a(sss) changes); out a(sss) changes);
DetachWithExtensions(in as extensions,
in t flags,
out a(sss) changes);
Reattach(in as matches, Reattach(in as matches,
in s profile, in s profile,
in b runtime, in b runtime,
in s copy_mode, in s copy_mode,
out a(sss) changes_removed, out a(sss) changes_removed,
out a(sss) changes_updated); 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(); Remove();
MarkReadOnly(in b read_only); MarkReadOnly(in b read_only);
SetLimit(in t limit); SetLimit(in t limit);
@ -303,14 +398,22 @@ node /org/freedesktop/portable1 {
<!--method GetMetadata is not documented!--> <!--method GetMetadata is not documented!-->
<!--method GetMetadataWithExtensions is not documented!-->
<!--method GetState is not documented!--> <!--method GetState is not documented!-->
<!--method Attach is not documented!--> <!--method Attach is not documented!-->
<!--method AttachWithExtensions is not documented!-->
<!--method Detach is not documented!--> <!--method Detach is not documented!-->
<!--method DetachWithExtensions is not documented!-->
<!--method Reattach is not documented!--> <!--method Reattach is not documented!-->
<!--method ReattacheWithExtensions is not documented!-->
<!--method Remove is not documented!--> <!--method Remove is not documented!-->
<!--method MarkReadOnly 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="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="GetState()"/>
<variablelist class="dbus-method" generated="True" extra-ref="Attach()"/> <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="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="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="Remove()"/>
<variablelist class="dbus-method" generated="True" extra-ref="MarkReadOnly()"/> <variablelist class="dbus-method" generated="True" extra-ref="MarkReadOnly()"/>
@ -377,14 +488,22 @@ node /org/freedesktop/portable1 {
<listitem><para>GetMetadata()</para></listitem> <listitem><para>GetMetadata()</para></listitem>
<listitem><para>GetMetadataWithExtensions()</para></listitem>
<listitem><para>GetState()</para></listitem> <listitem><para>GetState()</para></listitem>
<listitem><para>Attach()</para></listitem> <listitem><para>Attach()</para></listitem>
<listitem><para>AttachWithExtensions()</para></listitem>
<listitem><para>Detach()</para></listitem> <listitem><para>Detach()</para></listitem>
<listitem><para>DetachWithExtensions()</para></listitem>
<listitem><para>Reattach()</para></listitem> <listitem><para>Reattach()</para></listitem>
<listitem><para>ReattacheWithExtensions()</para></listitem>
<listitem><para>Remove()</para></listitem> <listitem><para>Remove()</para></listitem>
<listitem><para>MarkReadOnly()</para></listitem> <listitem><para>MarkReadOnly()</para></listitem>

View File

@ -352,6 +352,19 @@
<listitem><para>Don't block waiting for attach --now to complete.</para></listitem> <listitem><para>Don't block waiting for attach --now to complete.</para></listitem>
</varlistentry> </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="host" />
<xi:include href="user-system-options.xml" xpointer="machine" /> <xi:include href="user-system-options.xml" xpointer="machine" />

View File

@ -11,6 +11,7 @@
#include "discover-image.h" #include "discover-image.h"
#include "dissect-image.h" #include "dissect-image.h"
#include "errno-list.h" #include "errno-list.h"
#include "escape.h"
#include "fd-util.h" #include "fd-util.h"
#include "fileio.h" #include "fileio.h"
#include "fs-util.h" #include "fs-util.h"
@ -74,17 +75,26 @@ static bool unit_match(const char *unit, char **matches) {
return false; 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; PortableMetadata *m;
m = malloc0(offsetof(PortableMetadata, name) + strlen(name) + 1); m = malloc0(offsetof(PortableMetadata, name) + strlen(name) + 1);
if (!m) if (!m)
return NULL; 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); strcpy(m->name, name);
m->fd = fd; m->fd = fd;
return m; return TAKE_PTR(m);
} }
PortableMetadata *portable_metadata_unref(PortableMetadata *i) { PortableMetadata *portable_metadata_unref(PortableMetadata *i) {
@ -93,6 +103,7 @@ PortableMetadata *portable_metadata_unref(PortableMetadata *i) {
safe_close(i->fd); safe_close(i->fd);
free(i->source); free(i->source);
free(i->image_path);
return mfree(i); return mfree(i);
} }
@ -255,7 +266,7 @@ static int extract_now(
} }
if (ret_os_release) { 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) if (!os_release)
return -ENOMEM; return -ENOMEM;
@ -316,7 +327,7 @@ static int extract_now(
return log_debug_errno(r, "Failed to send unit metadata to parent: %m"); 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) if (!m)
return -ENOMEM; return -ENOMEM;
fd = -1; fd = -1;
@ -342,6 +353,7 @@ static int extract_now(
static int portable_extract_by_path( static int portable_extract_by_path(
const char *path, const char *path,
bool extract_os_release,
char **matches, char **matches,
PortableMetadata **ret_os_release, PortableMetadata **ret_os_release,
Hashmap **ret_unit_files, Hashmap **ret_unit_files,
@ -412,7 +424,7 @@ static int portable_extract_by_path(
if (r == 0) { if (r == 0) {
seq[0] = safe_close(seq[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) { if (r < 0) {
log_debug_errno(r, "Failed to mount dissected image: %m"); log_debug_errno(r, "Failed to mount dissected image: %m");
goto child_finish; goto child_finish;
@ -448,7 +460,7 @@ static int portable_extract_by_path(
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid item sent from child."); "Invalid item sent from child.");
add = portable_metadata_new(name, fd); add = portable_metadata_new(name, path, fd);
if (!add) if (!add)
return -ENOMEM; return -ENOMEM;
fd = -1; fd = -1;
@ -478,10 +490,12 @@ static int portable_extract_by_path(
child = 0; 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); 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); 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) if (ret_unit_files)
@ -496,11 +510,16 @@ static int portable_extract_by_path(
int portable_extract( int portable_extract(
const char *name_or_path, const char *name_or_path,
char **matches, char **matches,
char **extension_image_paths,
PortableMetadata **ret_os_release, PortableMetadata **ret_os_release,
Hashmap **ret_unit_files, Hashmap **ret_unit_files,
sd_bus_error *error) { 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; _cleanup_(image_unrefp) Image *image = NULL;
Image *ext;
int r; int r;
assert(name_or_path); assert(name_or_path);
@ -509,7 +528,46 @@ int portable_extract(
if (r < 0) if (r < 0)
return r; 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( static int unit_file_is_active(
@ -684,9 +742,49 @@ void portable_changes_free(PortableChange *changes, size_t n_changes) {
free(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( static int install_chroot_dropin(
const char *image_path, const char *image_path,
ImageType type, ImageType type,
OrderedHashmap *extension_images,
const PortableMetadata *m, const PortableMetadata *m,
const char *dropin_dir, const char *dropin_dir,
char **ret_dropin, char **ret_dropin,
@ -694,6 +792,7 @@ static int install_chroot_dropin(
size_t *n_changes) { size_t *n_changes) {
_cleanup_free_ char *text = NULL, *dropin = NULL; _cleanup_free_ char *text = NULL, *dropin = NULL;
Image *ext;
int r; int r;
assert(image_path); assert(image_path);
@ -704,12 +803,15 @@ static int install_chroot_dropin(
if (!dropin) if (!dropin)
return -ENOMEM; return -ENOMEM;
text = strjoin(PORTABLE_DROPIN_MARKER_BEGIN, image_path, PORTABLE_DROPIN_MARKER_END "\n"); r = make_marker_text(image_path, extension_images, &text);
if (!text) if (r < 0)
return -ENOMEM; return log_debug_errno(r, "Failed to generate marker string for portable drop-in: %m");
if (endswith(m->name, ".service")) { 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 (access("/etc/os-release", F_OK) < 0) {
if (errno != ENOENT) if (errno != ENOENT)
@ -719,14 +821,23 @@ static int install_chroot_dropin(
} else } else
os_release_source = "/etc/os-release"; 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, if (!strextend(&text,
"\n" "\n"
"[Service]\n", "[Service]\n",
IN_SET(type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) ? "RootDirectory=" : "RootImage=", image_path, "\n" root_type, image_path, "\n"
"Environment=PORTABLE=", basename(image_path), "\n" "Environment=PORTABLE=", base_name, "\n"
"BindReadOnlyPaths=", os_release_source, ":/run/host/os-release\n" "BindReadOnlyPaths=", os_release_source, ":/run/host/os-release\n"
"LogExtraFields=PORTABLE=", basename(image_path), "\n")) "LogExtraFields=PORTABLE=", base_name, "\n"))
return -ENOMEM; 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); 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 LookupPaths *paths,
const char *image_path, const char *image_path,
ImageType type, ImageType type,
OrderedHashmap *extension_images,
const PortableMetadata *m, const PortableMetadata *m,
const char *profile, const char *profile,
PortableFlags flags, 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 * 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. */ * 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) if (r < 0)
return r; return r;
@ -984,20 +1096,49 @@ static int install_image_symlink(
return 0; 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( int portable_attach(
sd_bus *bus, sd_bus *bus,
const char *name_or_path, const char *name_or_path,
char **matches, char **matches,
const char *profile, const char *profile,
char **extension_image_paths,
PortableFlags flags, PortableFlags flags,
PortableChange **changes, PortableChange **changes,
size_t *n_changes, size_t *n_changes,
sd_bus_error *error) { sd_bus_error *error) {
_cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL;
_cleanup_hashmap_free_ Hashmap *unit_files = NULL; _cleanup_hashmap_free_ Hashmap *unit_files = NULL;
_cleanup_(lookup_paths_free) LookupPaths paths = {}; _cleanup_(lookup_paths_free) LookupPaths paths = {};
_cleanup_(image_unrefp) Image *image = NULL; _cleanup_(image_unrefp) Image *image = NULL;
PortableMetadata *item; PortableMetadata *item;
Image *ext;
char **p;
int r; int r;
assert(name_or_path); assert(name_or_path);
@ -1005,11 +1146,40 @@ int portable_attach(
r = image_find_harder(IMAGE_PORTABLE, name_or_path, NULL, &image); r = image_find_harder(IMAGE_PORTABLE, name_or_path, NULL, &image);
if (r < 0) if (r < 0)
return r; return r;
if (!strv_isempty(extension_image_paths)) {
extension_images = ordered_hashmap_new(&image_hash_ops);
if (!extension_images)
return -ENOMEM;
r = portable_extract_by_path(image->path, matches, NULL, &unit_files, error); 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, NULL, &unit_files, error);
if (r < 0) if (r < 0)
return r; 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); r = lookup_paths_init(&paths, UNIT_FILE_SYSTEM, LOOKUP_PATHS_SPLIT_USR, NULL);
if (r < 0) if (r < 0)
return r; return r;
@ -1029,62 +1199,95 @@ int portable_attach(
} }
HASHMAP_FOREACH(item, unit_files) { 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) if (r < 0)
return r; return r;
} }
/* We don't care too much for the image symlink, it's just a convenience thing, it's not necessary for proper /* We don't care too much for the image symlink, it's just a convenience thing, it's not necessary for proper
* operation otherwise. */ * 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; 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; const char *a;
int r;
assert(marker); assert(marker);
assert(name_or_path); 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)) { root_and_extensions = strv_new(name_or_path);
const char *e, *underscore; if (!root_and_extensions)
return -ENOMEM;
/* We shall match against an image name. In that case let's compare the last component, and optionally r = strv_extend_strv(&root_and_extensions, extension_image_paths, false);
* allow either a suffix of ".raw" or a series of "/". if (r < 0)
* But allow matching on a different version of the same image, when a "_" is used as a separator. */ return r;
underscore = strchr(name_or_path, '_');
if (underscore)
return strneq(a, name_or_path, underscore - name_or_path);
e = startswith(a, name_or_path); STRV_FOREACH(image_name_or_path, root_and_extensions) {
if (!e) _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 false;
return a = last_path_component(image);
e[strspn(e, "/")] == 0 ||
streq(e, ".raw");
} 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 if (image_name_is_valid(*image_name_or_path)) {
* reach the same file. However, in this mode, let's validate any file suffix. */ const char *e, *underscore;
l = strcspn(a, "/"); /* We shall match against an image name. In that case let's compare the last component, and optionally
b = last_path_component(name_or_path); * 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;
}
if (strcspn(b, "/") != l) e = startswith(a, *image_name_or_path);
return false; if (!e)
return false;
underscore = strchr(b, '_'); if(!(e[strspn(e, "/")] == 0 || streq(e, ".raw")))
if (underscore) return false;
l = underscore - b; } else {
const char *b, *underscore;
size_t l;
return strneq(a, b, 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;
if (!strneq(a, b, l))
return false;
}
} }
return true;
} }
static int test_chroot_dropin( static int test_chroot_dropin(
@ -1092,6 +1295,7 @@ static int test_chroot_dropin(
const char *where, const char *where,
const char *fname, const char *fname,
const char *name_or_path, const char *name_or_path,
char **extension_image_paths,
char **ret_marker) { char **ret_marker) {
_cleanup_free_ char *line = NULL, *marker = NULL; _cleanup_free_ char *line = NULL, *marker = NULL;
@ -1138,7 +1342,7 @@ static int test_chroot_dropin(
if (!name_or_path) if (!name_or_path)
r = true; r = true;
else else
r = marker_matches_image(marker, name_or_path); r = marker_matches_images(marker, name_or_path, extension_image_paths);
if (ret_marker) if (ret_marker)
*ret_marker = TAKE_PTR(marker); *ret_marker = TAKE_PTR(marker);
@ -1149,6 +1353,7 @@ static int test_chroot_dropin(
int portable_detach( int portable_detach(
sd_bus *bus, sd_bus *bus,
const char *name_or_path, const char *name_or_path,
char **extension_image_paths,
PortableFlags flags, PortableFlags flags,
PortableChange **changes, PortableChange **changes,
size_t *n_changes, size_t *n_changes,
@ -1193,7 +1398,7 @@ int portable_detach(
if (!IN_SET(de->d_type, DT_LNK, DT_REG)) if (!IN_SET(de->d_type, DT_LNK, DT_REG))
continue; 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) if (r < 0)
return r; return r;
if (r == 0) if (r == 0)
@ -1215,12 +1420,20 @@ int portable_detach(
if (r < 0) if (r < 0)
return log_debug_errno(r, "Failed to add unit name '%s' to set: %m", de->d_name); return log_debug_errno(r, "Failed to add unit name '%s' to set: %m", de->d_name);
if (path_is_absolute(marker) && for (const char *p = marker;;) {
!image_in_search_path(IMAGE_PORTABLE, NULL, 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) 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)) if (!IN_SET(de->d_type, DT_LNK, DT_REG))
continue; 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) if (r < 0)
return r; return r;
if (r == 0) if (r == 0)

View File

@ -11,6 +11,7 @@
typedef struct PortableMetadata { typedef struct PortableMetadata {
int fd; int fd;
char *source; char *source;
char *image_path;
char name[]; char name[];
} PortableMetadata; } PortableMetadata;
@ -18,10 +19,13 @@ typedef struct PortableMetadata {
#define PORTABLE_METADATA_IS_UNIT(m) (!IN_SET((m)->name[0], 0, '/')) #define PORTABLE_METADATA_IS_UNIT(m) (!IN_SET((m)->name[0], 0, '/'))
typedef enum PortableFlags { typedef enum PortableFlags {
PORTABLE_PREFER_COPY = 1 << 0, PORTABLE_RUNTIME = 1 << 0, /* Public API via DBUS, do not change */
PORTABLE_PREFER_SYMLINK = 1 << 1, PORTABLE_PREFER_COPY = 1 << 1,
PORTABLE_RUNTIME = 1 << 2, PORTABLE_PREFER_SYMLINK = 1 << 2,
PORTABLE_REATTACH = 1 << 3, PORTABLE_REATTACH = 1 << 3,
_PORTABLE_MASK_PUBLIC = PORTABLE_RUNTIME,
_PORTABLE_TYPE_MAX,
_PORTABLE_TYPE_INVALID = -EINVAL,
} PortableFlags; } PortableFlags;
/* This enum is anonymous, since we usually store it in an 'int', as we overload it with negative errno /* 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_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_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, 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); int portable_get_state(sd_bus *bus, const char *name_or_path, PortableFlags flags, PortableState *ret, sd_bus_error *error);

View File

@ -21,9 +21,11 @@
#include "main-func.h" #include "main-func.h"
#include "os-util.h" #include "os-util.h"
#include "pager.h" #include "pager.h"
#include "parse-argument.h"
#include "parse-util.h" #include "parse-util.h"
#include "path-util.h" #include "path-util.h"
#include "pretty-print.h" #include "pretty-print.h"
#include "portable.h"
#include "spawn-polkit-agent.h" #include "spawn-polkit-agent.h"
#include "string-util.h" #include "string-util.h"
#include "strv.h" #include "strv.h"
@ -44,6 +46,9 @@ static const char *arg_host = NULL;
static bool arg_enable = false; static bool arg_enable = false;
static bool arg_now = false; static bool arg_now = false;
static bool arg_no_block = 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) { static bool is_portable_managed(const char *unit) {
return ENDSWITH_SET(unit, ".service", ".target", ".socket", ".path", ".timer"); 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; 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) { static int extract_prefix(const char *path, char **ret) {
_cleanup_free_ char *name = NULL; _cleanup_free_ char *name = NULL;
const char *bn, *underscore; const char *bn, *underscore;
@ -219,15 +256,55 @@ static int maybe_reload(sd_bus **bus) {
return 0; return 0;
} }
static int inspect_image(int argc, char *argv[], void *userdata) { 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, *reply = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_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_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_strv_free_ char **matches = NULL; _cleanup_strv_free_ char **matches = NULL;
_cleanup_free_ char *image = NULL; _cleanup_free_ char *image = NULL;
bool nl = false, header = false; bool nl = false, header = false;
const void *data;
const char *path; const char *path;
const void *data;
size_t sz; size_t sz;
int r; int r;
@ -243,21 +320,9 @@ static int inspect_image(int argc, char *argv[], void *userdata) {
if (r < 0) if (r < 0)
return r; return r;
r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "GetImageMetadata"); r = get_image_metadata(bus, image, matches, &reply);
if (r < 0) if (r < 0)
return bus_log_create_error(r); return 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));
r = sd_bus_message_read(reply, "s", &path); r = sd_bus_message_read(reply, "s", &path);
if (r < 0) 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[]) { static int maybe_stop_disable(sd_bus *bus, char *image, char *argv[]) {
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL; _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_strv_free_ char **matches = NULL; _cleanup_strv_free_ char **matches = NULL;
int r; int r;
@ -623,21 +687,9 @@ static int maybe_stop_disable(sd_bus *bus, char *image, char *argv[]) {
if (r < 0) if (r < 0)
return log_error_errno(r, "Could not watch jobs: %m"); 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) if (r < 0)
return bus_log_create_error(r); return 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));
r = sd_bus_message_skip(reply, "say"); r = sd_bus_message_skip(reply, "say");
if (r < 0) if (r < 0)
@ -693,7 +745,7 @@ static int attach_reattach_image(int argc, char *argv[], const char *method) {
int r; int r;
assert(method); 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); r = determine_image(argv[1], false, &image);
if (r < 0) if (r < 0)
@ -717,11 +769,24 @@ static int attach_reattach_image(int argc, char *argv[], const char *method) {
if (r < 0) if (r < 0)
return bus_log_create_error(r); 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); r = sd_bus_message_append_strv(m, matches);
if (r < 0) if (r < 0)
return bus_log_create_error(r); 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) if (r < 0)
return bus_log_create_error(r); 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); print_changes(reply);
if (streq(method, "AttachImage")) if (STR_IN_SET(method, "AttachImage", "AttachImageWithExtensions"))
(void) maybe_enable_start(bus, reply); (void) maybe_enable_start(bus, reply);
else { else {
/* ReattachImage returns 2 lists - removed units first, and changed/added second */ /* 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) { 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) { 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) { 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_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_free_ char *image = NULL; _cleanup_free_ char *image = NULL;
const char *method;
int r; int r;
r = determine_image(argv[1], true, &image); 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); (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) 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_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 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); (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" " --now Immediately start/stop the portable service after\n"
" attach/before detach\n" " attach/before detach\n"
" --no-block Don't block waiting for attach --now to complete\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", "\nSee the %s for details.\n",
program_invocation_short_name, program_invocation_short_name,
ansi_highlight(), ansi_highlight(),
@ -1055,6 +1145,7 @@ static int help(int argc, char *argv[], void *userdata) {
} }
static int parse_argv(int argc, char *argv[]) { static int parse_argv(int argc, char *argv[]) {
int r;
enum { enum {
ARG_VERSION = 0x100, ARG_VERSION = 0x100,
@ -1068,6 +1159,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_ENABLE, ARG_ENABLE,
ARG_NOW, ARG_NOW,
ARG_NO_BLOCK, ARG_NO_BLOCK,
ARG_EXTENSION,
}; };
static const struct option options[] = { static const struct option options[] = {
@ -1087,6 +1179,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "enable", no_argument, NULL, ARG_ENABLE }, { "enable", no_argument, NULL, ARG_ENABLE },
{ "now", no_argument, NULL, ARG_NOW }, { "now", no_argument, NULL, ARG_NOW },
{ "no-block", no_argument, NULL, ARG_NO_BLOCK }, { "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; arg_no_block = true;
break; break;
case ARG_EXTENSION:
r = strv_extend(&arg_extension_images, optarg);
if (r < 0)
return log_oom();
break;
case '?': case '?':
return -EINVAL; return -EINVAL;

View File

@ -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) { 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; PortableChange *changes = NULL;
PortableFlags flags = 0;
Manager *m = userdata; Manager *m = userdata;
size_t n_changes = 0; size_t n_changes = 0;
const char *name_or_path; const char *name_or_path;
int r, runtime; int r;
assert(message); assert(message);
assert(m); 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 * detach already deleted images too, in case the user already deleted an image before properly detaching
* it. */ * 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) if (r < 0)
return r; 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( r = bus_verify_polkit_async(
message, message,
CAP_SYS_ADMIN, CAP_SYS_ADMIN,
@ -286,7 +315,8 @@ static int method_detach_image(sd_bus_message *message, void *userdata, sd_bus_e
r = portable_detach( r = portable_detach(
sd_bus_message_get_bus(message), sd_bus_message_get_bus(message),
name_or_path, name_or_path,
runtime ? PORTABLE_RUNTIME : 0, extension_images,
flags,
&changes, &changes,
&n_changes, &n_changes,
error); error);
@ -383,6 +413,16 @@ const sd_bus_vtable manager_vtable[] = {
"a{say}", units), "a{say}", units),
method_get_image_metadata, method_get_image_metadata,
SD_BUS_VTABLE_UNPRIVILEGED), 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_METHOD_WITH_ARGS("GetImageState",
SD_BUS_ARGS("s", image), SD_BUS_ARGS("s", image),
SD_BUS_RESULT("s", state), SD_BUS_RESULT("s", state),
@ -397,12 +437,29 @@ const sd_bus_vtable manager_vtable[] = {
SD_BUS_RESULT("a(sss)", changes), SD_BUS_RESULT("a(sss)", changes),
method_attach_image, method_attach_image,
SD_BUS_VTABLE_UNPRIVILEGED), 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_METHOD_WITH_ARGS("DetachImage",
SD_BUS_ARGS("s", image, SD_BUS_ARGS("s", image,
"b", runtime), "b", runtime),
SD_BUS_RESULT("a(sss)", changes), SD_BUS_RESULT("a(sss)", changes),
method_detach_image, method_detach_image,
SD_BUS_VTABLE_UNPRIVILEGED), 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_METHOD_WITH_ARGS("ReattachImage",
SD_BUS_ARGS("s", image, SD_BUS_ARGS("s", image,
"as", matches, "as", matches,
@ -413,6 +470,17 @@ const sd_bus_vtable manager_vtable[] = {
"a(sss)", changes_updated), "a(sss)", changes_updated),
method_reattach_image, method_reattach_image,
SD_BUS_VTABLE_UNPRIVILEGED), 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_METHOD_WITH_ARGS("RemoveImage",
SD_BUS_ARGS("s", image), SD_BUS_ARGS("s", image),
SD_BUS_NO_RESULT, SD_BUS_NO_RESULT,

View File

@ -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) { static int append_fd(sd_bus_message *m, PortableMetadata *d) {
_cleanup_fclose_ FILE *f = NULL; _cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *buf = NULL; _cleanup_free_ char *buf = NULL;
size_t n; size_t n = 0;
int r; int r;
assert(m); assert(m);
assert(d);
assert(d->fd >= 0);
f = take_fdopen(&d->fd, "r"); if (d) {
if (!f) assert(d->fd >= 0);
return -errno;
r = read_full_stream(f, &buf, &n); f = take_fdopen(&d->fd, "r");
if (r < 0) if (!f)
return r; return -errno;
r = read_full_stream(f, &buf, &n);
if (r < 0)
return r;
}
return sd_bus_message_append_array(m, 'y', buf, n); return sd_bus_message_append_array(m, 'y', buf, n);
} }
@ -101,10 +103,12 @@ int bus_image_common_get_metadata(
sd_bus_error *error) { sd_bus_error *error) {
_cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL; _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_hashmap_free_ Hashmap *unit_files = NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_free_ PortableMetadata **sorted = 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; size_t i;
int r; int r;
@ -116,10 +120,29 @@ int bus_image_common_get_metadata(
m = image->userdata; 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); r = sd_bus_message_read_strv(message, &matches);
if (r < 0) if (r < 0)
return r; 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, r = bus_image_acquire(m,
message, message,
name_or_path, name_or_path,
@ -136,6 +159,7 @@ int bus_image_common_get_metadata(
r = portable_extract( r = portable_extract(
image->path, image->path,
matches, matches,
extension_images,
&os_release, &os_release,
&unit_files, &unit_files,
error); error);
@ -223,12 +247,12 @@ int bus_image_common_attach(
Image *image, Image *image,
sd_bus_error *error) { sd_bus_error *error) {
_cleanup_strv_free_ char **matches = NULL; _cleanup_strv_free_ char **matches = NULL, **extension_images = NULL;
PortableChange *changes = NULL; PortableChange *changes = NULL;
PortableFlags flags = 0; PortableFlags flags = 0;
const char *profile, *copy_mode; const char *profile, *copy_mode;
size_t n_changes = 0; size_t n_changes = 0;
int runtime, r; int r;
assert(message); assert(message);
assert(name_or_path || image); assert(name_or_path || image);
@ -238,14 +262,44 @@ int bus_image_common_attach(
m = image->userdata; 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); r = sd_bus_message_read_strv(message, &matches);
if (r < 0) if (r < 0)
return r; return r;
r = sd_bus_message_read(message, "sbs", &profile, &runtime, &copy_mode); r = sd_bus_message_read(message, "s", &profile);
if (r < 0) if (r < 0)
return r; 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")) if (streq(copy_mode, "symlink"))
flags |= PORTABLE_PREFER_SYMLINK; flags |= PORTABLE_PREFER_SYMLINK;
else if (streq(copy_mode, "copy")) else if (streq(copy_mode, "copy"))
@ -253,9 +307,6 @@ int bus_image_common_attach(
else if (!isempty(copy_mode)) else if (!isempty(copy_mode))
return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Unknown copy mode '%s'", 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, r = bus_image_acquire(m,
message, message,
name_or_path, name_or_path,
@ -274,6 +325,7 @@ int bus_image_common_attach(
image->path, image->path,
matches, matches,
profile, profile,
extension_images,
flags, flags,
&changes, &changes,
&n_changes, &n_changes,
@ -297,19 +349,46 @@ static int bus_image_method_detach(
void *userdata, void *userdata,
sd_bus_error *error) { sd_bus_error *error) {
_cleanup_strv_free_ char **extension_images = NULL;
PortableChange *changes = NULL; PortableChange *changes = NULL;
Image *image = userdata; Image *image = userdata;
Manager *m = image->userdata; Manager *m = image->userdata;
PortableFlags flags = 0;
size_t n_changes = 0; size_t n_changes = 0;
int r, runtime; int r;
assert(message); assert(message);
assert(image); assert(image);
assert(m); assert(m);
r = sd_bus_message_read(message, "b", &runtime); if (sd_bus_message_is_method_call(message, NULL, "DetachWithExtensions")) {
if (r < 0) r = sd_bus_message_read_strv(message, &extension_images);
return r; 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( r = bus_verify_polkit_async(
message, message,
@ -328,7 +407,8 @@ static int bus_image_method_detach(
r = portable_detach( r = portable_detach(
sd_bus_message_get_bus(message), sd_bus_message_get_bus(message),
image->path, image->path,
runtime ? PORTABLE_RUNTIME : 0, extension_images,
flags,
&changes, &changes,
&n_changes, &n_changes,
error); error);
@ -510,10 +590,10 @@ int bus_image_common_reattach(
PortableChange *changes_detached = NULL, *changes_attached = NULL, *changes_gone = NULL; PortableChange *changes_detached = NULL, *changes_attached = NULL, *changes_gone = NULL;
size_t n_changes_detached = 0, n_changes_attached = 0, n_changes_gone = 0; 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; PortableFlags flags = PORTABLE_REATTACH;
const char *profile, *copy_mode; const char *profile, *copy_mode;
int runtime, r; int r;
assert(message); assert(message);
assert(name_or_path || image); assert(name_or_path || image);
@ -523,14 +603,45 @@ int bus_image_common_reattach(
m = image->userdata; 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); r = sd_bus_message_read_strv(message, &matches);
if (r < 0) if (r < 0)
return r; return r;
r = sd_bus_message_read(message, "sbs", &profile, &runtime, &copy_mode); r = sd_bus_message_read(message, "s", &profile);
if (r < 0) if (r < 0)
return r; 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")) if (streq(copy_mode, "symlink"))
flags |= PORTABLE_PREFER_SYMLINK; flags |= PORTABLE_PREFER_SYMLINK;
else if (streq(copy_mode, "copy")) else if (streq(copy_mode, "copy"))
@ -538,9 +649,6 @@ int bus_image_common_reattach(
else if (!isempty(copy_mode)) else if (!isempty(copy_mode))
return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Unknown copy mode '%s'", 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, r = bus_image_acquire(m,
message, message,
name_or_path, name_or_path,
@ -557,6 +665,7 @@ int bus_image_common_reattach(
r = portable_detach( r = portable_detach(
sd_bus_message_get_bus(message), sd_bus_message_get_bus(message),
image->path, image->path,
extension_images,
flags, flags,
&changes_detached, &changes_detached,
&n_changes_detached, &n_changes_detached,
@ -569,6 +678,7 @@ int bus_image_common_reattach(
image->path, image->path,
matches, matches,
profile, profile,
extension_images,
flags, flags,
&changes_attached, &changes_attached,
&n_changes_attached, &n_changes_attached,
@ -721,6 +831,15 @@ const sd_bus_vtable image_vtable[] = {
"a{say}", units), "a{say}", units),
bus_image_method_get_metadata, bus_image_method_get_metadata,
SD_BUS_VTABLE_UNPRIVILEGED), 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_METHOD_WITH_ARGS("GetState",
SD_BUS_NO_ARGS, SD_BUS_NO_ARGS,
SD_BUS_RESULT("s", state), SD_BUS_RESULT("s", state),
@ -734,11 +853,26 @@ const sd_bus_vtable image_vtable[] = {
SD_BUS_RESULT("a(sss)", changes), SD_BUS_RESULT("a(sss)", changes),
bus_image_method_attach, bus_image_method_attach,
SD_BUS_VTABLE_UNPRIVILEGED), 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_METHOD_WITH_ARGS("Detach",
SD_BUS_ARGS("b", runtime), SD_BUS_ARGS("b", runtime),
SD_BUS_RESULT("a(sss)", changes), SD_BUS_RESULT("a(sss)", changes),
bus_image_method_detach, bus_image_method_detach,
SD_BUS_VTABLE_UNPRIVILEGED), 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_METHOD_WITH_ARGS("Reattach",
SD_BUS_ARGS("as", matches, SD_BUS_ARGS("as", matches,
"s", profile, "s", profile,
@ -748,6 +882,16 @@ const sd_bus_vtable image_vtable[] = {
"a(sss)", changes_updated), "a(sss)", changes_updated),
bus_image_method_reattach, bus_image_method_reattach,
SD_BUS_VTABLE_UNPRIVILEGED), 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_METHOD_WITH_ARGS("Remove",
SD_BUS_NO_ARGS, SD_BUS_NO_ARGS,
SD_BUS_NO_RESULT, SD_BUS_NO_RESULT,

View File

@ -15,6 +15,7 @@ test_append_files() {
instmods loop =block instmods loop =block
instmods squashfs =squashfs instmods squashfs =squashfs
instmods dm_verity =md instmods dm_verity =md
instmods overlay =overlayfs
install_dmevent install_dmevent
generate_module_dependencies generate_module_dependencies
inst_binary losetup inst_binary losetup

View File

@ -63,6 +63,36 @@ portablectl detach --now --enable --runtime /tmp/minimal_1 app0
portablectl list | grep -q -F "No images." 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 echo OK > /testok
exit 0 exit 0