diff --git a/TODO b/TODO
index f72dd0f718..b78892a916 100644
--- a/TODO
+++ b/TODO
@@ -15,13 +15,6 @@ Janitorial Clean-ups:
* rework mount.c and swap.c to follow proper state enumeration/deserialization
semantics, like we do for device.c now
-Before v240:
-
-* move portablectl into /usr/bin
-
-* portables: introduce a new unit file directory /etc/systemd/system.attached/
- or so, where we attach portable services to
-
Features:
* consider splitting out all temporary file creation APIs (we have so many in
diff --git a/docs/PORTABLE_SERVICES.md b/docs/PORTABLE_SERVICES.md
index 1833244447..55397f4639 100644
--- a/docs/PORTABLE_SERVICES.md
+++ b/docs/PORTABLE_SERVICES.md
@@ -98,16 +98,17 @@ This command does the following:
`foobar@.{service|socket|target|timer|path}` as well as
`foobar.*.{service|socket|target|timer|path}` and
`foobar.{service|socket|target|timer|path}` are copied out. These unit files
- are placed in `/etc/systemd/system/` like regular unit files. Within the
- images the unit files are looked for at the usual locations, i.e. in
- `/usr/lib/systemd/system/` and `/etc/systemd/system/` and so on, relative to
- the image's root.
+ are placed in `/etc/systemd/system.attached/` (which is part of the normal
+ unit file search path of PID 1, and thus loaded exactly like regular unit
+ files). Within the images the unit files are looked for at the usual
+ locations, i.e. in `/usr/lib/systemd/system/` and `/etc/systemd/system/` and
+ so on, relative to the image's root.
3. For each such unit file a drop-in file is created. Let's say
`foobar-waldo.service` was one of the unit files copied to
- `/etc/systemd/system/`, then a drop-in file
- `/etc/systemd/system/foobar-waldo.service.d/20-portable.conf` is created,
- containing a few lines of additional configuration:
+ `/etc/systemd/system.attached/`, then a drop-in file
+ `/etc/systemd/system.attached/foobar-waldo.service.d/20-portable.conf` is
+ created, containing a few lines of additional configuration:
```
[Service]
diff --git a/man/portablectl.xml b/man/portablectl.xml
index 24b9f4ead1..3a5517a4ad 100644
--- a/man/portablectl.xml
+++ b/man/portablectl.xml
@@ -59,7 +59,7 @@
btrfs subvolumes containing OS trees, similar to normal directory trees.Binary "raw" disk images containing MBR or GPT partition tables and Linux file system
- partitions.
+ partitions. (These must be regular files, with the .raw suffix.)
@@ -101,9 +101,9 @@
When specified the unit and drop-in files are placed in
- /run/systemd/system/ instead of /etc/systemd/system/. Images attached
- with this option set hence remain attached only until the next reboot, while they are normally attached
- persistently.
+ /run/systemd/system.attached/ instead of
+ /etc/systemd/system.attached/. Images attached with this option set hence remain attached
+ only until the next reboot, while they are normally attached persistently.
@@ -167,8 +167,10 @@
All unit files of types .service, .socket,
.target, .timer and .path which match the
indicated unit file name prefix are copied from the image to the host's
- /etc/systemd/system/ directory (or /run/systemd/system/ — depending
- whether is specified, see above).
+ /etc/systemd/system.attached/ directory (or
+ /run/systemd/system.attached/ — depending whether is
+ specified, see above), which is included in the built-in unit search path of the system service
+ manager.
For unit files of type .service a drop-in is added to these copies that
adds RootDirectory= or RootImage= settings (see
@@ -330,6 +332,10 @@
to place image files directly in /etc/portables/ or
/run/systemd/portables/ (as these are generally not suitable for storing large or non-textual
data), but use these directories only for linking images located elsewhere into the image search path.
+
+ When a portable service image is attached, matching unit files are copied onto the host into the
+ /etc/systemd/system.attached/ and /run/systemd/system.attached/
+ directories. When an image is detached, the unit files are removed again from these directories.
diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml
index c615739e33..3522133532 100644
--- a/man/systemd.unit.xml
+++ b/man/systemd.unit.xml
@@ -39,19 +39,26 @@
slice.slice,
scope.scope
- /etc/systemd/system.control/*
+
+ System Unit Search Path
+
+ /etc/systemd/system.control/*/run/systemd/system.control/*/run/systemd/transient/*/run/systemd/generator.early/*/etc/systemd/system/*
+/etc/systemd/systemd.attached/*/run/systemd/system/*
+/run/systemd/systemd.attached/*/run/systemd/generator/*…/usr/lib/systemd/system/*
-/run/systemd/generator.late/*
-
+/run/systemd/generator.late/*
+
- ~/.config/systemd/user.control/*
+
+ User Unit Search Path
+ ~/.config/systemd/user.control/*$XDG_RUNTIME_DIR/systemd/user.control/*$XDG_RUNTIME_DIR/systemd/transient/*$XDG_RUNTIME_DIR/systemd/generator.early/*
@@ -63,8 +70,9 @@
~/.local/share/systemd/user/*…/usr/lib/systemd/user/*
-$XDG_RUNTIME_DIR/systemd/generator.late/*
-
+$XDG_RUNTIME_DIR/systemd/generator.late/*
+
+
diff --git a/meson.build b/meson.build
index 5e653b8b6f..e3f06616b5 100644
--- a/meson.build
+++ b/meson.build
@@ -1834,7 +1834,7 @@ if conf.get('ENABLE_PORTABLED') == 1
dependencies : [threads],
install_rpath : rootlibexecdir,
install : true,
- install_dir : rootlibexecdir)
+ install_dir : rootbindir)
public_programs += exe
endif
diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c
index b6e40ee8a9..6e5fe00e06 100644
--- a/src/libsystemd/sd-bus/bus-common-errors.c
+++ b/src/libsystemd/sd-bus/bus-common-errors.c
@@ -41,6 +41,9 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_USER_MAPPING, ENXIO),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_GROUP_MAPPING, ENXIO),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_PORTABLE_IMAGE, ENOENT),
+ SD_BUS_ERROR_MAP(BUS_ERROR_BAD_PORTABLE_IMAGE_TYPE, EMEDIUMTYPE),
+
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SESSION, ENXIO),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SESSION_FOR_PID, ENXIO),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_USER, ENXIO),
@@ -71,6 +74,8 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_LINK, ENXIO),
SD_BUS_ERROR_MAP(BUS_ERROR_LINK_BUSY, EBUSY),
SD_BUS_ERROR_MAP(BUS_ERROR_NETWORK_DOWN, ENETDOWN),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_DNSSD_SERVICE, ENOENT),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNSSD_SERVICE_EXISTS, EEXIST),
SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "FORMERR", EBADMSG),
SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "SERVFAIL", EHOSTDOWN),
diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h
index a76a93644c..8339feb768 100644
--- a/src/libsystemd/sd-bus/bus-common-errors.h
+++ b/src/libsystemd/sd-bus/bus-common-errors.h
@@ -37,6 +37,7 @@
#define BUS_ERROR_NO_SUCH_GROUP_MAPPING "org.freedesktop.machine1.NoSuchGroupMapping"
#define BUS_ERROR_NO_SUCH_PORTABLE_IMAGE "org.freedesktop.portable1.NoSuchImage"
+#define BUS_ERROR_BAD_PORTABLE_IMAGE_TYPE "org.freedesktop.portable1.BadImageType"
#define BUS_ERROR_NO_SUCH_SESSION "org.freedesktop.login1.NoSuchSession"
#define BUS_ERROR_NO_SESSION_FOR_PID "org.freedesktop.login1.NoSessionForPID"
diff --git a/src/portable/portable.c b/src/portable/portable.c
index 3491723aa1..ca8043b41e 100644
--- a/src/portable/portable.c
+++ b/src/portable/portable.c
@@ -815,15 +815,15 @@ static int install_profile_dropin(
return 0;
}
-static const char *config_path(const LookupPaths *paths, PortableFlags flags) {
+static const char *attached_path(const LookupPaths *paths, PortableFlags flags) {
const char *where;
assert(paths);
if (flags & PORTABLE_RUNTIME)
- where = paths->runtime_config;
+ where = paths->runtime_attached;
else
- where = paths->persistent_config;
+ where = paths->persistent_attached;
assert(where);
return where;
@@ -849,15 +849,25 @@ static int attach_unit_file(
assert(m);
assert(PORTABLE_METADATA_IS_UNIT(m));
- where = config_path(paths, flags);
- path = strjoina(where, "/", m->name);
+ where = attached_path(paths, flags);
+ (void) mkdir_parents(where, 0755);
+ if (mkdir(where, 0755) < 0) {
+ if (errno != EEXIST)
+ return -errno;
+ } else
+ (void) portable_changes_add(changes, n_changes, PORTABLE_MKDIR, where, NULL);
+
+ path = strjoina(where, "/", m->name);
dropin_dir = strjoin(path, ".d");
if (!dropin_dir)
return -ENOMEM;
- (void) mkdir_p(dropin_dir, 0755);
- (void) portable_changes_add(changes, n_changes, PORTABLE_MKDIR, dropin_dir, NULL);
+ if (mkdir(dropin_dir, 0755) < 0) {
+ if (errno != EEXIST)
+ return -errno;
+ } else
+ (void) portable_changes_add(changes, n_changes, PORTABLE_MKDIR, dropin_dir, NULL);
/* We install the drop-ins first, and the actual unit file last to achieve somewhat atomic behaviour if PID 1
* is reloaded while we are creating things here: as long as only the drop-ins exist the unit doesn't exist at
@@ -1147,11 +1157,15 @@ int portable_detach(
if (r < 0)
return r;
- where = config_path(&paths, flags);
+ where = attached_path(&paths, flags);
d = opendir(where);
- if (!d)
+ if (!d) {
+ if (errno == ENOENT)
+ goto not_found;
+
return log_debug_errno(errno, "Failed to open '%s' directory: %m", where);
+ }
unit_files = set_new(&string_hash_ops);
if (!unit_files)
@@ -1213,10 +1227,8 @@ int portable_detach(
}
}
- if (set_isempty(unit_files)) {
- log_debug("No unit files associated with '%s' found. Image not attached?", name_or_path);
- return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "No unit files associated with '%s' found. Image not attached?", name_or_path);
- }
+ if (set_isempty(unit_files))
+ goto not_found;
SET_FOREACH(item, unit_files, iterator) {
_cleanup_free_ char *md = NULL;
@@ -1289,7 +1301,15 @@ int portable_detach(
portable_changes_add(changes, n_changes, PORTABLE_UNLINK, sl, NULL);
}
+ /* Try to remove the unit file directory, if we can */
+ if (rmdir(where) >= 0)
+ portable_changes_add(changes, n_changes, PORTABLE_UNLINK, where, NULL);
+
return ret;
+
+not_found:
+ log_debug("No unit files associated with '%s' found. Image not attached?", name_or_path);
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "No unit files associated with '%s' found. Image not attached?", name_or_path);
}
static int portable_get_state_internal(
@@ -1314,11 +1334,18 @@ static int portable_get_state_internal(
if (r < 0)
return r;
- where = config_path(&paths, flags);
+ where = attached_path(&paths, flags);
d = opendir(where);
- if (!d)
+ if (!d) {
+ if (errno == ENOENT) {
+ /* If the 'attached' directory doesn't exist at all, then we know for sure this image isn't attached. */
+ *ret = PORTABLE_DETACHED;
+ return 0;
+ }
+
return log_debug_errno(errno, "Failed to open '%s' directory: %m", where);
+ }
unit_files = set_new(&string_hash_ops);
if (!unit_files)
diff --git a/src/portable/portabled-image-bus.c b/src/portable/portabled-image-bus.c
index 02a2db2352..0e1f7c5e87 100644
--- a/src/portable/portabled-image-bus.c
+++ b/src/portable/portabled-image-bus.c
@@ -636,6 +636,10 @@ int bus_image_acquire(
r = image_from_path(name_or_path, &loaded);
}
+ if (r == -EMEDIUMTYPE) {
+ sd_bus_error_setf(error, BUS_ERROR_BAD_PORTABLE_IMAGE_TYPE, "Typ of image '%s' not recognized; supported image types are directories/btrfs subvolumes, block devices, and raw disk image files with suffix '.raw'.", name_or_path);
+ return r;
+ }
if (r < 0)
return r;
diff --git a/src/shared/path-lookup.c b/src/shared/path-lookup.c
index 42a5b62d5d..7d7b890b47 100644
--- a/src/shared/path-lookup.c
+++ b/src/shared/path-lookup.c
@@ -142,9 +142,9 @@ int xdg_user_dirs(char ***ret_config_dirs, char ***ret_data_dirs) {
if (!data_dirs)
return -ENOMEM;
- *ret_config_dirs = config_dirs;
- *ret_data_dirs = data_dirs;
- config_dirs = data_dirs = NULL;
+ *ret_config_dirs = TAKE_PTR(config_dirs);
+ *ret_data_dirs = TAKE_PTR(data_dirs);
+
return 0;
}
@@ -421,6 +421,34 @@ static int acquire_control_dirs(UnitFileScope scope, char **persistent, char **r
return 0;
}
+static int acquire_attached_dirs(
+ UnitFileScope scope,
+ char **ret_persistent,
+ char **ret_runtime) {
+
+ _cleanup_free_ char *a = NULL, *b = NULL;
+
+ assert(ret_persistent);
+ assert(ret_runtime);
+
+ /* Portable services are not available to regular users for now. */
+ if (scope != UNIT_FILE_SYSTEM)
+ return -EOPNOTSUPP;
+
+ a = strdup("/etc/systemd/system.attached");
+ if (!a)
+ return -ENOMEM;
+
+ b = strdup("/run/systemd/system.attached");
+ if (!b)
+ return -ENOMEM;
+
+ *ret_persistent = TAKE_PTR(a);
+ *ret_runtime = TAKE_PTR(b);
+
+ return 0;
+}
+
static int patch_root_prefix(char **p, const char *root_dir) {
char *c;
@@ -468,7 +496,8 @@ int lookup_paths_init(
*global_persistent_config = NULL, *global_runtime_config = NULL,
*generator = NULL, *generator_early = NULL, *generator_late = NULL,
*transient = NULL,
- *persistent_control = NULL, *runtime_control = NULL;
+ *persistent_control = NULL, *runtime_control = NULL,
+ *persistent_attached = NULL, *runtime_attached = NULL;
bool append = false; /* Add items from SYSTEMD_UNIT_PATH before normal directories */
_cleanup_strv_free_ char **paths = NULL;
const char *e;
@@ -532,6 +561,10 @@ int lookup_paths_init(
if (r < 0 && r != -EOPNOTSUPP)
return r;
+ r = acquire_attached_dirs(scope, &persistent_attached, &runtime_attached);
+ if (r < 0 && r != -EOPNOTSUPP)
+ return r;
+
/* First priority is whatever has been passed to us via env vars */
e = getenv("SYSTEMD_UNIT_PATH");
if (e) {
@@ -574,8 +607,10 @@ int lookup_paths_init(
persistent_config,
SYSTEM_CONFIG_UNIT_PATH,
"/etc/systemd/system",
+ STRV_IFNOTNULL(persistent_attached),
runtime_config,
"/run/systemd/system",
+ STRV_IFNOTNULL(runtime_attached),
STRV_IFNOTNULL(generator),
"/usr/local/lib/systemd/system",
SYSTEM_DATA_UNIT_PATH,
@@ -658,33 +693,44 @@ int lookup_paths_init(
r = patch_root_prefix(&persistent_control, root);
if (r < 0)
return r;
-
r = patch_root_prefix(&runtime_control, root);
if (r < 0)
return r;
+ r = patch_root_prefix(&persistent_attached, root);
+ if (r < 0)
+ return r;
+ r = patch_root_prefix(&runtime_attached, root);
+ if (r < 0)
+ return r;
+
r = patch_root_prefix_strv(paths, root);
if (r < 0)
return -ENOMEM;
- p->search_path = strv_uniq(paths);
+ *p = (LookupPaths) {
+ .search_path = strv_uniq(paths),
+
+ .persistent_config = TAKE_PTR(persistent_config),
+ .runtime_config = TAKE_PTR(runtime_config),
+
+ .generator = TAKE_PTR(generator),
+ .generator_early = TAKE_PTR(generator_early),
+ .generator_late = TAKE_PTR(generator_late),
+
+ .transient = TAKE_PTR(transient),
+
+ .persistent_control = TAKE_PTR(persistent_control),
+ .runtime_control = TAKE_PTR(runtime_control),
+
+ .persistent_attached = TAKE_PTR(persistent_attached),
+ .runtime_attached = TAKE_PTR(runtime_attached),
+
+ .root_dir = TAKE_PTR(root),
+ .temporary_dir = TAKE_PTR(tempdir),
+ };
+
paths = NULL;
-
- p->persistent_config = TAKE_PTR(persistent_config);
- p->runtime_config = TAKE_PTR(runtime_config);
-
- p->generator = TAKE_PTR(generator);
- p->generator_early = TAKE_PTR(generator_early);
- p->generator_late = TAKE_PTR(generator_late);
-
- p->transient = TAKE_PTR(transient);
-
- p->persistent_control = TAKE_PTR(persistent_control);
- p->runtime_control = TAKE_PTR(runtime_control);
-
- p->root_dir = TAKE_PTR(root);
- p->temporary_dir = TAKE_PTR(tempdir);
-
return 0;
}
@@ -697,6 +743,9 @@ void lookup_paths_free(LookupPaths *p) {
p->persistent_config = mfree(p->persistent_config);
p->runtime_config = mfree(p->runtime_config);
+ p->persistent_attached = mfree(p->persistent_attached);
+ p->runtime_attached = mfree(p->runtime_attached);
+
p->generator = mfree(p->generator);
p->generator_early = mfree(p->generator_early);
p->generator_late = mfree(p->generator_late);
diff --git a/src/shared/path-lookup.h b/src/shared/path-lookup.h
index 963e09db67..cb7d4d537f 100644
--- a/src/shared/path-lookup.h
+++ b/src/shared/path-lookup.h
@@ -24,6 +24,10 @@ struct LookupPaths {
char *persistent_config;
char *runtime_config;
+ /* Where units from a portable service image shall be placed. */
+ char *persistent_attached;
+ char *runtime_attached;
+
/* Where to place generated unit files (i.e. those a "generator" tool generated). Note the special semantics of
* this directory: the generators are flushed each time a "systemctl daemon-reload" is issued. The user should
* not alter these directories directly. */
@@ -50,10 +54,12 @@ struct LookupPaths {
};
int lookup_paths_init(LookupPaths *p, UnitFileScope scope, LookupPathsFlags flags, const char *root_dir);
+
int xdg_user_dirs(char ***ret_config_dirs, char ***ret_data_dirs);
int xdg_user_runtime_dir(char **ret, const char *suffix);
int xdg_user_config_dir(char **ret, const char *suffix);
int xdg_user_data_dir(char **ret, const char *suffix);
+
bool path_is_user_data_dir(const char *path);
bool path_is_user_config_dir(const char *path);