]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
portabled: add --extension parameter for layered images support
authorLuca Boccassi <luca.boccassi@microsoft.com>
Tue, 23 Jun 2020 12:09:42 +0000 (13:09 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Wed, 31 Mar 2021 08:56:44 +0000 (09:56 +0100)
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.

man/org.freedesktop.portable1.xml
man/portablectl.xml
src/portable/portable.c
src/portable/portable.h
src/portable/portablectl.c
src/portable/portabled-bus.c
src/portable/portabled-image-bus.c
test/TEST-29-PORTABLE/test.sh
test/units/testsuite-29.sh

index e6d2f4f536dd510c88afa4425ccd8ae04e2cec28..46dca55745c2c3ee22d0433ab6ba3c48d389e1fb 100644 (file)
@@ -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>
index 2dae537a40ab2ef3cca94d88b6a32695e7935959..d798219d459f6661f85490a5fac23bb2008bcdcc 100644 (file)
         <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" />
 
index 5651db67227d7d0136136cbed6785ebdcfe4ba0e..02d1d641950d15ad1a44aa8446b3a03805ea4e72 100644 (file)
@@ -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)
index 5694bd2b623eaf36f2b82ef730abdad2ecae484a..dd080edf4e0a449134849dc244aef4e137dba382 100644 (file)
@@ -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);
 
index 77fcd4fe6fcbc374b4cff8055103448163cb3d25..fa6df9054ae697faaba34de12864e6993650cc4f 100644 (file)
 #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;
 
index 6d0dee99c3f703494329f3cfa65c9d6c821d027a..72f685f76db10f57de784705d75c336d00bc82da 100644 (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) {
+        _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,
index 8332332c9159ce1989766fede7ccd4905c25528b..88d8f914e031d7d25bde6e8f0457a3f41ca0605f 100644 (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) {
         _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,
index 801e74c13d828a4e109fc065ea1e8c69811dad9d..cd421efdaead35c059c3025228120ee01792d6a8 100755 (executable)
@@ -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
index b5b05b42d9bf40c13a64bb6abf438e19c2fa996b..2e55c275bf1d8443cc60fbbf50117aa60819e082 100755 (executable)
@@ -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