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,
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,
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);
<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()"/>
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>
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
</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
<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>
<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) << 0)
+ </programlisting>
</refsect2>
<refsect2>
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);
<!--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!-->
<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()"/>
<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>
<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" />
#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"
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) {
safe_close(i->fd);
free(i->source);
+ free(i->image_path);
return mfree(i);
}
}
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;
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;
static int portable_extract_by_path(
const char *path,
+ bool extract_os_release,
char **matches,
PortableMetadata **ret_os_release,
Hashmap **ret_unit_files,
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;
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;
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)
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);
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(
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,
size_t *n_changes) {
_cleanup_free_ char *text = NULL, *dropin = NULL;
+ Image *ext;
int r;
assert(image_path);
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)
} 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);
const LookupPaths *paths,
const char *image_path,
ImageType type,
+ OrderedHashmap *extension_images,
const PortableMetadata *m,
const char *profile,
PortableFlags flags,
* 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;
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);
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;
}
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(
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;
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);
int portable_detach(
sd_bus *bus,
const char *name_or_path,
+ char **extension_image_paths,
PortableFlags flags,
PortableChange **changes,
size_t *n_changes,
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)
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;
+ }
}
}
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)
typedef struct PortableMetadata {
int fd;
char *source;
+ char *image_path;
char name[];
} 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
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);
#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"
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");
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;
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;
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)
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;
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)
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)
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);
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 */
}
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);
(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);
" --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(),
}
static int parse_argv(int argc, char *argv[]) {
+ int r;
enum {
ARG_VERSION = 0x100,
ARG_ENABLE,
ARG_NOW,
ARG_NO_BLOCK,
+ ARG_EXTENSION,
};
static const struct option options[] = {
{ "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 },
{}
};
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;
}
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);
* 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,
r = portable_detach(
sd_bus_message_get_bus(message),
name_or_path,
- runtime ? PORTABLE_RUNTIME : 0,
+ extension_images,
+ flags,
&changes,
&n_changes,
error);
"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),
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,
"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,
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);
}
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;
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,
r = portable_extract(
image->path,
matches,
+ extension_images,
&os_release,
&unit_files,
error);
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);
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, ©_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", ©_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, ©_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"))
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,
image->path,
matches,
profile,
+ extension_images,
flags,
&changes,
&n_changes,
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,
r = portable_detach(
sd_bus_message_get_bus(message),
image->path,
- runtime ? PORTABLE_RUNTIME : 0,
+ extension_images,
+ flags,
&changes,
&n_changes,
error);
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);
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, ©_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", ©_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, ©_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"))
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,
r = portable_detach(
sd_bus_message_get_bus(message),
image->path,
+ extension_images,
flags,
&changes_detached,
&n_changes_detached,
image->path,
matches,
profile,
+ extension_images,
flags,
&changes_attached,
&n_changes_attached,
"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),
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,
"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,
instmods loop =block
instmods squashfs =squashfs
instmods dm_verity =md
+ instmods overlay =overlayfs
install_dmevent
generate_module_dependencies
inst_binary losetup
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