1
0
mirror of https://github.com/systemd/systemd.git synced 2024-12-21 13:34:21 +03:00

discover-image: introduce per-user image directories

We nowadays support unprivileged invocation of systemd-nspawn +
systemd-vmspawn, but there was no support for discovering suitable disk
images (i.e. no per-user counterpart of /var/lib/machines). Add this
now, and hook it up everywhere.

Instead of hardcoding machined's, importd's, portabled's, sysupdated's
image discovery to RUNTIME_SCOPE_SYSTEM I introduced a field that make
the scope variable, even if this field is always initialized to
RUNTIME_SCOPE_SYSTEM for now. I think these four services should
eventually be updated to support a per-user concept too, this is
preparation for that, even though it doesn't outright add support for
this.

This is for the largest part not user visible, except for in nspawn,
vmspawn and the dissect tool. For the latter I added a pair of
--user/--system switches to select the discovery scope.
This commit is contained in:
Lennart Poettering 2024-12-09 11:38:05 +01:00
parent 06ffa66a5b
commit 1c0ade2e1f
30 changed files with 317 additions and 85 deletions

View File

@ -503,6 +503,17 @@
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--system</option></term>
<term><option>--user</option></term>
<listitem><para>When used together with <option>--discover</option> controls whether to search for
images installed system-wide or in the user's directories in <varname>$HOME</varname>. If neither
switch is specified, will search within both scopes.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="image-policy-open" />
<xi:include href="standard-options.xml" xpointer="no-pager" />
<xi:include href="standard-options.xml" xpointer="no-legend" />

View File

@ -29,6 +29,8 @@ _systemd_dissect() {
local cur=${COMP_WORDS[COMP_CWORD]} prev_1=${COMP_WORDS[COMP_CWORD-1]} prev_2=${COMP_WORDS[COMP_CWORD-2]} words cword
local -A OPTS=(
[STANDALONE]='-h --help --version
--user
--system
--discover
--no-pager
--no-legend

View File

@ -95,6 +95,7 @@ static char *arg_loop_ref = NULL;
static ImagePolicy *arg_image_policy = NULL;
static bool arg_mtree_hash = true;
static bool arg_via_service = false;
static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID;
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
@ -151,6 +152,8 @@ static int help(void) {
" Generate JSON output\n"
" --loop-ref=NAME Set reference string for loopback device\n"
" --mtree-hash=BOOL Whether to include SHA256 hash in the mtree output\n"
" --user Discover user images\n"
" --system Discover system images\n"
"\n%3$sCommands:%4$s\n"
" -h --help Show this help\n"
" --version Show package version\n"
@ -274,6 +277,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_VALIDATE,
ARG_MTREE_HASH,
ARG_MAKE_ARCHIVE,
ARG_SYSTEM,
ARG_USER,
};
static const struct option options[] = {
@ -307,10 +312,13 @@ static int parse_argv(int argc, char *argv[]) {
{ "validate", no_argument, NULL, ARG_VALIDATE },
{ "mtree-hash", required_argument, NULL, ARG_MTREE_HASH },
{ "make-archive", no_argument, NULL, ARG_MAKE_ARCHIVE },
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "user", no_argument, NULL, ARG_USER },
{}
};
_cleanup_free_ char **buf = NULL; /* we use free(), not strv_free() here, as we don't copy the strings here */
bool system_scope_requested = false, user_scope_requested = false;
int c, r;
assert(argc >= 0);
@ -531,7 +539,6 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_MAKE_ARCHIVE:
r = dlopen_libarchive();
if (r < 0)
return log_error_errno(r, "Archive support not available (compiled without libarchive, or libarchive not installed?).");
@ -539,6 +546,14 @@ static int parse_argv(int argc, char *argv[]) {
arg_action = ACTION_MAKE_ARCHIVE;
break;
case ARG_SYSTEM:
system_scope_requested = true;
break;
case ARG_USER:
user_scope_requested = true;
break;
case '?':
return -EINVAL;
@ -547,6 +562,10 @@ static int parse_argv(int argc, char *argv[]) {
}
}
if (system_scope_requested || user_scope_requested)
arg_runtime_scope = system_scope_requested && user_scope_requested ? _RUNTIME_SCOPE_INVALID :
system_scope_requested ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER;
switch (arg_action) {
case ACTION_DISSECT:
@ -1851,7 +1870,7 @@ static int action_discover(void) {
return log_oom();
for (ImageClass cl = 0; cl < _IMAGE_CLASS_MAX; cl++) {
r = image_discover(cl, NULL, images);
r = image_discover(arg_runtime_scope, cl, NULL, images);
if (r < 0)
return log_error_errno(r, "Failed to discover images: %m");
}

View File

@ -25,6 +25,7 @@
static ImportCompressType arg_compress = IMPORT_COMPRESS_UNKNOWN;
static ImageClass arg_class = IMAGE_MACHINE;
static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID;
static void determine_compression_from_filename(const char *p) {
@ -66,7 +67,7 @@ static int export_tar(int argc, char *argv[], void *userdata) {
local = argv[1];
if (image_name_is_valid(local)) {
r = image_find(arg_class, local, NULL, &image);
r = image_find(arg_runtime_scope, arg_class, local, NULL, &image);
if (r == -ENOENT)
return log_error_errno(r, "Image %s not found.", local);
if (r < 0)
@ -139,7 +140,7 @@ static int export_raw(int argc, char *argv[], void *userdata) {
local = argv[1];
if (image_name_is_valid(local)) {
r = image_find(arg_class, local, NULL, &image);
r = image_find(arg_runtime_scope, arg_class, local, NULL, &image);
if (r == -ENOENT)
return log_error_errno(r, "Image %s not found.", local);
if (r < 0)

View File

@ -34,6 +34,7 @@ static bool arg_sync = true;
static bool arg_direct = false;
static const char *arg_image_root = NULL;
static ImageClass arg_class = IMAGE_MACHINE;
static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID;
typedef struct ProgressInfo {
RateLimit limit;
@ -145,7 +146,7 @@ static int import_fs(int argc, char *argv[], void *userdata) {
return log_oom();
if (!arg_force) {
r = image_find(arg_class, local, NULL, NULL);
r = image_find(arg_runtime_scope, arg_class, local, NULL, NULL);
if (r < 0) {
if (r != -ENOENT)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);

View File

@ -30,6 +30,7 @@ static const char *arg_image_root = NULL;
static ImportFlags arg_import_flags = IMPORT_BTRFS_SUBVOL | IMPORT_BTRFS_QUOTA | IMPORT_CONVERT_QCOW2 | IMPORT_SYNC;
static uint64_t arg_offset = UINT64_MAX, arg_size_max = UINT64_MAX;
static ImageClass arg_class = IMAGE_MACHINE;
static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID;
static int normalize_local(const char *local, char **ret) {
_cleanup_free_ char *ll = NULL;
@ -63,7 +64,7 @@ static int normalize_local(const char *local, char **ret) {
local = "imported";
if (!FLAGS_SET(arg_import_flags, IMPORT_FORCE)) {
r = image_find(arg_class, local, NULL, NULL);
r = image_find(arg_runtime_scope, arg_class, local, NULL, NULL);
if (r < 0) {
if (r != -ENOENT)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);

View File

@ -111,6 +111,8 @@ struct Manager {
bool use_btrfs_subvol;
bool use_btrfs_quota;
RuntimeScope runtime_scope; /* for now: always RUNTIME_SCOPE_SYSTEM */
};
#define TRANSFERS_MAX 64
@ -721,6 +723,7 @@ static int manager_new(Manager **ret) {
*m = (Manager) {
.use_btrfs_subvol = true,
.use_btrfs_quota = true,
.runtime_scope = RUNTIME_SCOPE_SYSTEM,
};
r = sd_event_default(&m->event);
@ -1332,6 +1335,7 @@ static int method_cancel_transfer(sd_bus_message *msg, void *userdata, sd_bus_er
static int method_list_images(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
ImageClass class = _IMAGE_CLASS_INVALID;
Manager *m = ASSERT_PTR(userdata);
int r;
assert(msg);
@ -1372,7 +1376,7 @@ static int method_list_images(sd_bus_message *msg, void *userdata, sd_bus_error
if (!h)
return -ENOMEM;
r = image_discover(c, /* root= */ NULL, h);
r = image_discover(m->runtime_scope, c, /* root= */ NULL, h);
if (r < 0) {
if (class >= 0)
return r;

View File

@ -33,6 +33,7 @@ static ImportFlags arg_import_flags = IMPORT_PULL_SETTINGS | IMPORT_PULL_ROOTHAS
static uint64_t arg_offset = UINT64_MAX, arg_size_max = UINT64_MAX;
static char *arg_checksum = NULL;
static ImageClass arg_class = IMAGE_MACHINE;
static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID;
STATIC_DESTRUCTOR_REGISTER(arg_checksum, freep);
@ -66,7 +67,7 @@ static int normalize_local(const char *local, const char *url, char **ret) {
local);
if (!FLAGS_SET(arg_import_flags, IMPORT_FORCE)) {
r = image_find(arg_class, local, NULL, NULL);
r = image_find(arg_runtime_scope, arg_class, local, NULL, NULL);
if (r < 0) {
if (r != -ENOENT)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);

View File

@ -178,7 +178,7 @@ int bus_image_method_clone(
return sd_bus_error_set_errnof(error, r, "Failed to fork(): %m");
if (r == 0) {
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
r = image_clone(image, new_name, read_only);
r = image_clone(image, new_name, read_only, m->runtime_scope);
report_errno_and_exit(errno_pipe_fd[1], r);
}
@ -402,6 +402,7 @@ char* image_bus_path(const char *name) {
static int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
_cleanup_hashmap_free_ Hashmap *images = NULL;
_cleanup_strv_free_ char **l = NULL;
Manager *m = ASSERT_PTR(userdata);
Image *image;
int r;
@ -413,7 +414,7 @@ static int image_node_enumerator(sd_bus *bus, const char *path, void *userdata,
if (!images)
return -ENOMEM;
r = image_discover(IMAGE_MACHINE, NULL, images);
r = image_discover(m->runtime_scope, IMAGE_MACHINE, NULL, images);
if (r < 0)
return r;

View File

@ -148,7 +148,7 @@ int vl_method_clone_image(sd_varlink *link, sd_json_variant *parameters, sd_varl
return log_debug_errno(r, "Failed to fork: %m");
if (r == 0) {
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
r = image_clone(image, p.new_name, p.read_only > 0);
r = image_clone(image, p.new_name, p.read_only > 0, manager->runtime_scope);
report_errno_and_exit(errno_pipe_fd[1], r);
}

View File

@ -440,7 +440,7 @@ int manager_acquire_image(Manager *m, const char *name, Image **ret) {
return log_debug_errno(r, "Failed to enable source: %m") ;
_cleanup_(image_unrefp) Image *image = NULL;
r = image_find(IMAGE_MACHINE, name, NULL, &image);
r = image_find(m->runtime_scope, IMAGE_MACHINE, name, NULL, &image);
if (r < 0)
return log_debug_errno(r, "Failed to find image: %m");
@ -467,7 +467,7 @@ int rename_image_and_update_cache(Manager *m, Image *image, const char* new_name
/* The image is cached with its name, hence it is necessary to remove from the cache before renaming. */
assert_se(hashmap_remove_value(m->image_cache, image->name, image));
r = image_rename(image, new_name);
r = image_rename(image, new_name, m->runtime_scope);
if (r < 0) {
image = image_unref(image);
return r;

View File

@ -123,7 +123,7 @@ static int method_get_image(sd_bus_message *message, void *userdata, sd_bus_erro
if (r < 0)
return r;
r = image_find(IMAGE_MACHINE, name, NULL, NULL);
r = image_find(m->runtime_scope, IMAGE_MACHINE, name, NULL, NULL);
if (r == -ENOENT)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
if (r < 0)
@ -476,7 +476,7 @@ static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_er
if (!images)
return -ENOMEM;
r = image_discover(IMAGE_MACHINE, NULL, images);
r = image_discover(m->runtime_scope, IMAGE_MACHINE, NULL, images);
if (r < 0)
return r;
@ -753,7 +753,7 @@ static int method_clean_pool(sd_bus_message *message, void *userdata, sd_bus_err
goto child_fail;
}
r = image_discover(IMAGE_MACHINE, NULL, images);
r = image_discover(m->runtime_scope, IMAGE_MACHINE, NULL, images);
if (r < 0)
goto child_fail;

View File

@ -641,6 +641,7 @@ static int list_image_one_and_maybe_read_metadata(sd_varlink *link, Image *image
}
static int vl_method_list_images(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
Manager *m = ASSERT_PTR(userdata);
struct params {
const char *image_name;
AcquireMetadata acquire_metadata;
@ -667,7 +668,7 @@ static int vl_method_list_images(sd_varlink *link, sd_json_variant *parameters,
if (!image_name_is_valid(p.image_name))
return sd_varlink_error_invalid_parameter_name(link, "name");
r = image_find(IMAGE_MACHINE, p.image_name, /* root = */ NULL, &found);
r = image_find(m->runtime_scope, IMAGE_MACHINE, p.image_name, /* root = */ NULL, &found);
if (r == -ENOENT)
return sd_varlink_error(link, "io.systemd.MachineImage.NoSuchImage", NULL);
if (r < 0)
@ -683,7 +684,7 @@ static int vl_method_list_images(sd_varlink *link, sd_json_variant *parameters,
if (!images)
return -ENOMEM;
r = image_discover(IMAGE_MACHINE, /* root = */ NULL, images);
r = image_discover(m->runtime_scope, IMAGE_MACHINE, /* root = */ NULL, images);
if (r < 0)
return log_debug_errno(r, "Failed to discover images: %m");

View File

@ -40,10 +40,14 @@ static int manager_new(Manager **ret) {
assert(ret);
m = new0(Manager, 1);
m = new(Manager, 1);
if (!m)
return -ENOMEM;
*m = (Manager) {
.runtime_scope = RUNTIME_SCOPE_SYSTEM,
};
m->machines = hashmap_new(&machine_hash_ops);
if (!m->machines)
return -ENOMEM;

View File

@ -42,6 +42,8 @@ struct Manager {
sd_varlink_server *varlink_userdb_server;
sd_varlink_server *varlink_machine_server;
RuntimeScope runtime_scope; /* for now: always RUNTIME_SCOPE_SYSTEM */
};
int manager_add_machine(Manager *m, const char *name, Machine **ret);

View File

@ -3167,7 +3167,8 @@ static int determine_names(void) {
if (arg_machine) {
_cleanup_(image_unrefp) Image *i = NULL;
r = image_find(IMAGE_MACHINE, arg_machine, NULL, &i);
r = image_find(arg_privileged ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER,
IMAGE_MACHINE, arg_machine, NULL, &i);
if (r == -ENOENT)
return log_error_errno(r, "No image for machine '%s'.", arg_machine);
if (r < 0)

View File

@ -173,6 +173,7 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(portable_metadata_hash_ops, char,
PortableMetadata, portable_metadata_unref);
static int extract_now(
RuntimeScope scope,
const char *where,
char **matches,
const char *image_name,
@ -199,6 +200,7 @@ static int extract_now(
* parent. To handle both cases in one call this function also gets a 'socket_fd' parameter, which when >= 0 is
* used to send the data to the parent. */
assert(scope < _RUNTIME_SCOPE_MAX);
assert(where);
/* First, find os-release/extension-release and send it upstream (or just save it). */
@ -248,7 +250,7 @@ static int extract_now(
/* Then, send unit file data to the parent (or/and add it to the hashmap). For that we use our usual unit
* discovery logic. Note that we force looking inside of /lib/systemd/system/ for units too, as the
* image might have a legacy split-usr layout. */
r = lookup_paths_init(&paths, RUNTIME_SCOPE_SYSTEM, LOOKUP_PATHS_SPLIT_USR, where);
r = lookup_paths_init(&paths, scope, LOOKUP_PATHS_SPLIT_USR, where);
if (r < 0)
return log_debug_errno(r, "Failed to acquire lookup paths: %m");
@ -348,6 +350,7 @@ static int extract_now(
}
static int portable_extract_by_path(
RuntimeScope scope,
const char *path,
bool path_is_extension,
bool relax_extension_release_check,
@ -381,7 +384,7 @@ static int portable_extract_by_path(
if (r < 0)
return log_error_errno(r, "Failed to extract image name from path '%s': %m", path);
r = extract_now(path, matches, image_name, path_is_extension, /* relax_extension_release_check= */ false, -1, &os_release, &unit_files);
r = extract_now(scope, path, matches, image_name, path_is_extension, /* relax_extension_release_check= */ false, -1, &os_release, &unit_files);
if (r < 0)
return r;
@ -458,7 +461,7 @@ static int portable_extract_by_path(
goto child_finish;
}
r = extract_now(tmpdir, matches, m->image_name, path_is_extension, relax_extension_release_check, seq[1], NULL, NULL);
r = extract_now(scope, tmpdir, matches, m->image_name, path_is_extension, relax_extension_release_check, seq[1], NULL, NULL);
child_finish:
_exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
@ -549,6 +552,7 @@ static int portable_extract_by_path(
}
static int extract_image_and_extensions(
RuntimeScope scope,
const char *name_or_path,
char **matches,
char **extension_image_paths,
@ -595,7 +599,7 @@ static int extract_image_and_extensions(
name_or_path = result.path;
}
r = image_find_harder(IMAGE_PORTABLE, name_or_path, /* root= */ NULL, &image);
r = image_find_harder(scope, IMAGE_PORTABLE, name_or_path, /* root= */ NULL, &image);
if (r < 0)
return r;
@ -633,7 +637,7 @@ static int extract_image_and_extensions(
path = ext_result.path;
}
r = image_find_harder(IMAGE_PORTABLE, path, NULL, &new);
r = image_find_harder(scope, IMAGE_PORTABLE, path, NULL, &new);
if (r < 0)
return r;
@ -645,6 +649,7 @@ static int extract_image_and_extensions(
}
r = portable_extract_by_path(
scope,
image->path,
/* path_is_extension= */ false,
/* relax_extension_release_check= */ false,
@ -687,6 +692,7 @@ static int extract_image_and_extensions(
const char *e;
r = portable_extract_by_path(
scope,
ext->path,
/* path_is_extension= */ true,
relax_extension_release_check,
@ -754,6 +760,7 @@ static int extract_image_and_extensions(
}
int portable_extract(
RuntimeScope scope,
const char *name_or_path,
char **matches,
char **extension_image_paths,
@ -775,6 +782,7 @@ int portable_extract(
assert(name_or_path);
r = extract_image_and_extensions(
scope,
name_or_path,
matches,
extension_image_paths,
@ -1426,6 +1434,7 @@ static int image_target_path(
}
static int install_image(
RuntimeScope scope,
const char *image_path,
PortableFlags flags,
PortableChange **changes,
@ -1434,13 +1443,14 @@ static int install_image(
_cleanup_free_ char *target = NULL;
int r;
assert(scope < _RUNTIME_SCOPE_MAX);
assert(image_path);
/* If the image is outside of the image search also link it into it, so that it can be found with
* short image names and is listed among the images. If we are operating in mixed mode, the image is
* copied instead. */
if (image_in_search_path(IMAGE_PORTABLE, NULL, image_path))
if (image_in_search_path(scope, IMAGE_PORTABLE, NULL, image_path))
return 0;
r = image_target_path(image_path, flags, &target);
@ -1485,6 +1495,7 @@ static int install_image(
}
static int install_image_and_extensions(
RuntimeScope scope,
const Image *image,
OrderedHashmap *extension_images,
PortableFlags flags,
@ -1497,12 +1508,12 @@ static int install_image_and_extensions(
assert(image);
ORDERED_HASHMAP_FOREACH(ext, extension_images) {
r = install_image(ext->path, flags, changes, n_changes);
r = install_image(scope, ext->path, flags, changes, n_changes);
if (r < 0)
return r;
}
r = install_image(image->path, flags, changes, n_changes);
r = install_image(scope, image->path, flags, changes, n_changes);
if (r < 0)
return r;
@ -1595,6 +1606,7 @@ static void log_portable_verb(
}
int portable_attach(
RuntimeScope scope,
sd_bus *bus,
const char *name_or_path,
char **matches,
@ -1615,7 +1627,10 @@ int portable_attach(
PortableMetadata *item;
int r;
assert(scope < _RUNTIME_SCOPE_MAX);
r = extract_image_and_extensions(
scope,
name_or_path,
matches,
extension_image_paths,
@ -1672,13 +1687,13 @@ int portable_attach(
strempty(extensions_joined));
}
r = lookup_paths_init(&paths, RUNTIME_SCOPE_SYSTEM, /* flags= */ 0, NULL);
r = lookup_paths_init(&paths, scope, /* flags= */ 0, NULL);
if (r < 0)
return r;
if (!FLAGS_SET(flags, PORTABLE_REATTACH) && !FLAGS_SET(flags, PORTABLE_FORCE_ATTACH))
HASHMAP_FOREACH(item, unit_files) {
r = unit_file_exists(RUNTIME_SCOPE_SYSTEM, &paths, item->name);
r = unit_file_exists(scope, &paths, item->name);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to determine whether unit '%s' exists on the host: %m", item->name);
if (r > 0)
@ -1700,7 +1715,7 @@ int portable_attach(
/* We don't care too much for the image symlink/copy, it's just a convenience thing, it's not necessary for
* proper operation otherwise. */
(void) install_image_and_extensions(image, extension_images, flags, changes, n_changes);
(void) install_image_and_extensions(scope, image, extension_images, flags, changes, n_changes);
log_portable_verb(
"attached",
@ -1844,6 +1859,7 @@ static int test_chroot_dropin(
}
int portable_detach(
RuntimeScope scope,
sd_bus *bus,
const char *name_or_path,
char **extension_image_paths,
@ -1857,12 +1873,12 @@ int portable_detach(
_cleanup_free_ char *extensions = NULL;
_cleanup_closedir_ DIR *d = NULL;
const char *where, *item;
int ret = 0;
int r;
int r, ret = 0;
assert(scope < _RUNTIME_SCOPE_MAX);
assert(name_or_path);
r = lookup_paths_init(&paths, RUNTIME_SCOPE_SYSTEM, /* flags= */ 0, NULL);
r = lookup_paths_init(&paths, scope, /* flags= */ 0, NULL);
if (r < 0)
return r;
@ -1930,7 +1946,7 @@ int portable_detach(
if (r == 0)
break;
if (path_is_absolute(image) && !image_in_search_path(IMAGE_PORTABLE, NULL, image)) {
if (path_is_absolute(image) && !image_in_search_path(scope, IMAGE_PORTABLE, NULL, image)) {
r = set_ensure_consume(&markers, &path_hash_ops_free, TAKE_PTR(image));
if (r < 0)
return r;
@ -2031,6 +2047,7 @@ not_found:
}
static int portable_get_state_internal(
RuntimeScope scope,
sd_bus *bus,
const char *name_or_path,
char **extension_image_paths,
@ -2045,10 +2062,11 @@ static int portable_get_state_internal(
const char *where;
int r;
assert(scope < _RUNTIME_SCOPE_MAX);
assert(name_or_path);
assert(ret);
r = lookup_paths_init(&paths, RUNTIME_SCOPE_SYSTEM, /* flags= */ 0, NULL);
r = lookup_paths_init(&paths, scope, /* flags= */ 0, NULL);
if (r < 0)
return r;
@ -2084,7 +2102,7 @@ static int portable_get_state_internal(
if (r == 0)
continue;
r = unit_file_lookup_state(RUNTIME_SCOPE_SYSTEM, &paths, de->d_name, &state);
r = unit_file_lookup_state(scope, &paths, de->d_name, &state);
if (r < 0)
return log_debug_errno(r, "Failed to determine unit file state of '%s': %m", de->d_name);
if (!IN_SET(state, UNIT_FILE_STATIC, UNIT_FILE_DISABLED, UNIT_FILE_LINKED, UNIT_FILE_LINKED_RUNTIME))
@ -2109,6 +2127,7 @@ static int portable_get_state_internal(
}
int portable_get_state(
RuntimeScope scope,
sd_bus *bus,
const char *name_or_path,
char **extension_image_paths,
@ -2125,12 +2144,19 @@ int portable_get_state(
/* We look for matching units twice: once in the regular directories, and once in the runtime directories — but
* the latter only if we didn't find anything in the former. */
r = portable_get_state_internal(bus, name_or_path, extension_image_paths, flags & ~PORTABLE_RUNTIME, &state, error);
r = portable_get_state_internal(
scope,
bus,
name_or_path,
extension_image_paths,
flags & ~PORTABLE_RUNTIME,
&state,
error);
if (r < 0)
return r;
if (state == PORTABLE_DETACHED) {
r = portable_get_state_internal(bus, name_or_path, extension_image_paths, flags | PORTABLE_RUNTIME, &state, error);
r = portable_get_state_internal(scope, bus, name_or_path, extension_image_paths, flags | PORTABLE_RUNTIME, &state, error);
if (r < 0)
return r;
}

View File

@ -6,6 +6,7 @@
#include "dissect-image.h"
#include "hashmap.h"
#include "macro.h"
#include "runtime-scope.h"
#include "set.h"
#include "string-util.h"
@ -69,12 +70,12 @@ 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, char **extension_image_paths, const ImagePolicy *image_policy, PortableFlags flags, PortableMetadata **ret_os_release, OrderedHashmap **ret_extension_releases, Hashmap **ret_unit_files, char ***ret_valid_prefixes, sd_bus_error *error);
int portable_extract(RuntimeScope scope, const char *image, char **matches, char **extension_image_paths, const ImagePolicy *image_policy, PortableFlags flags, PortableMetadata **ret_os_release, OrderedHashmap **ret_extension_releases, Hashmap **ret_unit_files, char ***ret_valid_prefixes, sd_bus_error *error);
int portable_attach(sd_bus *bus, const char *name_or_path, char **matches, const char *profile, char **extension_images, const ImagePolicy* image_policy, 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_attach(RuntimeScope scope, sd_bus *bus, const char *name_or_path, char **matches, const char *profile, char **extension_images, const ImagePolicy* image_policy, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
int portable_detach(RuntimeScope scope, 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, char **extension_image_paths, PortableFlags flags, PortableState *ret, sd_bus_error *error);
int portable_get_state(RuntimeScope scope, sd_bus *bus, const char *name_or_path, char **extension_image_paths, PortableFlags flags, PortableState *ret, sd_bus_error *error);
int portable_get_profiles(char ***ret);

View File

@ -165,6 +165,7 @@ static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_er
return r;
r = portable_get_state(
m->runtime_scope,
sd_bus_message_get_bus(message),
image->path,
NULL,
@ -225,6 +226,7 @@ static int method_get_image_metadata(sd_bus_message *message, void *userdata, sd
static int method_get_image_state(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_strv_free_ char **extension_images = NULL;
Manager *m = ASSERT_PTR(userdata);
const char *name_or_path;
PortableState state;
int r;
@ -254,6 +256,7 @@ static int method_get_image_state(sd_bus_message *message, void *userdata, sd_bu
}
r = portable_get_state(
m->runtime_scope,
sd_bus_message_get_bus(message),
name_or_path,
extension_images,
@ -330,6 +333,7 @@ static int method_detach_image(sd_bus_message *message, void *userdata, sd_bus_e
return 1; /* Will call us back */
r = portable_detach(
m->runtime_scope,
sd_bus_message_get_bus(message),
name_or_path,
extension_images,

View File

@ -114,10 +114,8 @@ int bus_image_common_get_metadata(
assert(name_or_path || image);
assert(message);
if (!m) {
assert(image);
m = image->userdata;
}
if (!m)
m = ASSERT_PTR(ASSERT_PTR(image)->userdata);
bool have_exti = sd_bus_message_is_method_call(message, NULL, "GetImageMetadataWithExtensions") ||
sd_bus_message_is_method_call(message, NULL, "GetMetadataWithExtensions");
@ -160,6 +158,7 @@ int bus_image_common_get_metadata(
return 1;
r = portable_extract(
m->runtime_scope,
image->path,
matches,
extension_images,
@ -264,6 +263,7 @@ static int bus_image_method_get_state(
_cleanup_strv_free_ char **extension_images = NULL;
Image *image = ASSERT_PTR(userdata);
Manager *m = ASSERT_PTR(image->userdata);
PortableState state;
int r;
@ -288,6 +288,7 @@ static int bus_image_method_get_state(
}
r = portable_get_state(
m->runtime_scope,
sd_bus_message_get_bus(message),
image->path,
extension_images,
@ -385,6 +386,7 @@ int bus_image_common_attach(
return 1;
r = portable_attach(
m->runtime_scope,
sd_bus_message_get_bus(message),
image->path,
matches,
@ -463,6 +465,7 @@ static int bus_image_method_detach(
return 1; /* Will call us back */
r = portable_detach(
m->runtime_scope,
sd_bus_message_get_bus(message),
image->path,
extension_images,
@ -513,6 +516,7 @@ int bus_image_common_remove(
return 1; /* Will call us back */
r = portable_get_state(
m->runtime_scope,
sd_bus_message_get_bus(message),
image->path,
NULL,
@ -716,6 +720,7 @@ int bus_image_common_reattach(
return 1;
r = portable_detach(
m->runtime_scope,
sd_bus_message_get_bus(message),
image->path,
extension_images,
@ -727,6 +732,7 @@ int bus_image_common_reattach(
return r;
r = portable_attach(
m->runtime_scope,
sd_bus_message_get_bus(message),
image->path,
matches,
@ -1039,7 +1045,7 @@ int bus_image_acquire(
if (image_name_is_valid(name_or_path)) {
/* If it's a short name, let's search for it */
r = image_find(IMAGE_PORTABLE, name_or_path, NULL, &loaded);
r = image_find(m->runtime_scope, IMAGE_PORTABLE, name_or_path, NULL, &loaded);
if (r == -ENOENT)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PORTABLE_IMAGE,
"No image '%s' found.", name_or_path);

View File

@ -91,7 +91,7 @@ int manager_image_cache_discover(Manager *m, Hashmap *images, sd_bus_error *erro
/* A wrapper around image_discover() (for finding images in search path) and portable_discover_attached() (for
* finding attached images). */
r = image_discover(IMAGE_PORTABLE, NULL, images);
r = image_discover(m->runtime_scope, IMAGE_PORTABLE, NULL, images);
if (r < 0)
return r;

View File

@ -28,10 +28,14 @@ static int manager_new(Manager **ret) {
assert(ret);
m = new0(Manager, 1);
m = new(Manager, 1);
if (!m)
return -ENOMEM;
*m = (Manager) {
.runtime_scope = RUNTIME_SCOPE_SYSTEM,
};
r = sd_event_default(&m->event);
if (r < 0)
return r;

View File

@ -7,6 +7,7 @@
#include "bus-object.h"
#include "hashmap.h"
#include "list.h"
#include "runtime-scope.h"
typedef struct Manager Manager;
@ -23,6 +24,8 @@ struct Manager {
LIST_HEAD(Operation, operations);
unsigned n_operations;
RuntimeScope runtime_scope; /* for now always RUNTIME_SCOPE_SYSTEM */
};
extern const BusObjectImplementation manager_object;

View File

@ -12,6 +12,8 @@
#include <sys/stat.h>
#include <unistd.h>
#include "sd-path.h"
#include "alloc-util.h"
#include "blockdev-util.h"
#include "btrfs-util.h"
@ -553,12 +555,95 @@ static int image_make(
return -EMEDIUMTYPE;
}
static const char *pick_image_search_path(ImageClass class) {
if (class < 0 || class >= _IMAGE_CLASS_MAX)
return NULL;
static int pick_image_search_path(
RuntimeScope scope,
ImageClass class,
char ***ret) {
/* Use the initrd search path if there is one, otherwise use the common one */
return in_initrd() && image_search_path_initrd[class] ? image_search_path_initrd[class] : image_search_path[class];
int r;
assert(scope < _RUNTIME_SCOPE_MAX && scope != RUNTIME_SCOPE_GLOBAL);
assert(class < _IMAGE_CLASS_MAX);
assert(ret);
if (class < 0) {
*ret = NULL;
return 0;
}
if (scope < 0) {
_cleanup_strv_free_ char **a = NULL, **b = NULL;
r = pick_image_search_path(RUNTIME_SCOPE_USER, class, &a);
if (r < 0)
return r;
r = pick_image_search_path(RUNTIME_SCOPE_SYSTEM, class, &b);
if (r < 0)
return r;
r = strv_extend_strv(&a, b, /* filter_duplicates= */ false);
if (r < 0)
return r;
*ret = TAKE_PTR(a);
return 0;
}
switch (scope) {
case RUNTIME_SCOPE_SYSTEM: {
const char *ns;
/* Use the initrd search path if there is one, otherwise use the common one */
ns = in_initrd() && image_search_path_initrd[class] ?
image_search_path_initrd[class] :
image_search_path[class];
if (!ns)
break;
_cleanup_strv_free_ char **search = strv_split_nulstr(ns);
if (!search)
return -ENOMEM;
*ret = TAKE_PTR(search);
return 0;
}
case RUNTIME_SCOPE_USER: {
if (class != IMAGE_MACHINE)
break;
static const uint64_t dirs[] = {
SD_PATH_USER_RUNTIME,
SD_PATH_USER_STATE_PRIVATE,
SD_PATH_USER_LIBRARY_PRIVATE,
};
_cleanup_strv_free_ char **search = NULL;
FOREACH_ELEMENT(d, dirs) {
_cleanup_free_ char *p = NULL;
r = sd_path_lookup(*d, "machines", &p);
if (r == -ENXIO) /* No XDG_RUNTIME_DIR set */
continue;
if (r < 0)
return r;
r = strv_consume(&search, TAKE_PTR(p));
if (r < 0)
return r;
}
*ret = TAKE_PTR(search);
return 0;
}
default:
assert_not_reached();
}
*ret = NULL;
return 0;
}
static char **make_possible_filenames(ImageClass class, const char *image_name) {
@ -592,7 +677,8 @@ static char **make_possible_filenames(ImageClass class, const char *image_name)
return TAKE_PTR(l);
}
int image_find(ImageClass class,
int image_find(RuntimeScope scope,
ImageClass class,
const char *name,
const char *root,
Image **ret) {
@ -602,6 +688,7 @@ int image_find(ImageClass class,
* some root directory.) */
int open_flags = root ? O_NOFOLLOW : 0, r;
assert(scope < _RUNTIME_SCOPE_MAX && scope != RUNTIME_SCOPE_GLOBAL);
assert(class >= 0);
assert(class < _IMAGE_CLASS_MAX);
assert(name);
@ -614,11 +701,16 @@ int image_find(ImageClass class,
if (!names)
return -ENOMEM;
NULSTR_FOREACH(path, pick_image_search_path(class)) {
_cleanup_strv_free_ char **search = NULL;
r = pick_image_search_path(scope, class, &search);
if (r < 0)
return r;
STRV_FOREACH(path, search) {
_cleanup_free_ char *resolved = NULL;
_cleanup_closedir_ DIR *d = NULL;
r = chase_and_opendir(path, root, CHASE_PREFIX_ROOT, &resolved, &d);
r = chase_and_opendir(*path, root, CHASE_PREFIX_ROOT, &resolved, &d);
if (r == -ENOENT)
continue;
if (r < 0)
@ -722,7 +814,7 @@ int image_find(ImageClass class,
}
}
if (class == IMAGE_MACHINE && streq(name, ".host")) {
if (scope == RUNTIME_SCOPE_SYSTEM && class == IMAGE_MACHINE && streq(name, ".host")) {
r = image_make(class,
".host",
/* dir_fd= */ AT_FDCWD,
@ -771,14 +863,21 @@ int image_from_path(const char *path, Image **ret) {
ret);
}
int image_find_harder(ImageClass class, const char *name_or_path, const char *root, Image **ret) {
int image_find_harder(
RuntimeScope scope,
ImageClass class,
const char *name_or_path,
const char *root,
Image **ret) {
if (image_name_is_valid(name_or_path))
return image_find(class, name_or_path, root, ret);
return image_find(scope, class, name_or_path, root, ret);
return image_from_path(name_or_path, ret);
}
int image_discover(
RuntimeScope scope,
ImageClass class,
const char *root,
Hashmap *h) {
@ -788,15 +887,21 @@ int image_discover(
* some root directory.) */
int open_flags = root ? O_NOFOLLOW : 0, r;
assert(scope < _RUNTIME_SCOPE_MAX && scope != RUNTIME_SCOPE_GLOBAL);
assert(class >= 0);
assert(class < _IMAGE_CLASS_MAX);
assert(h);
NULSTR_FOREACH(path, pick_image_search_path(class)) {
_cleanup_strv_free_ char **search = NULL;
r = pick_image_search_path(scope, class, &search);
if (r < 0)
return r;
STRV_FOREACH(path, search) {
_cleanup_free_ char *resolved = NULL;
_cleanup_closedir_ DIR *d = NULL;
r = chase_and_opendir(path, root, CHASE_PREFIX_ROOT, &resolved, &d);
r = chase_and_opendir(*path, root, CHASE_PREFIX_ROOT, &resolved, &d);
if (r == -ENOENT)
continue;
if (r < 0)
@ -946,7 +1051,7 @@ int image_discover(
}
}
if (class == IMAGE_MACHINE && !hashmap_contains(h, ".host")) {
if (scope == RUNTIME_SCOPE_SYSTEM && class == IMAGE_MACHINE && !hashmap_contains(h, ".host")) {
_cleanup_(image_unrefp) Image *image = NULL;
r = image_make(IMAGE_MACHINE,
@ -1063,7 +1168,7 @@ static int rename_auxiliary_file(const char *path, const char *new_name, const c
return rename_noreplace(AT_FDCWD, path, AT_FDCWD, rs);
}
int image_rename(Image *i, const char *new_name) {
int image_rename(Image *i, const char *new_name, RuntimeScope scope) {
_cleanup_(release_lock_file) LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT, name_lock = LOCK_FILE_INIT;
_cleanup_free_ char *new_path = NULL, *nn = NULL, *roothash = NULL;
_cleanup_strv_free_ char **settings = NULL;
@ -1098,7 +1203,7 @@ int image_rename(Image *i, const char *new_name) {
if (r < 0)
return r;
r = image_find(IMAGE_MACHINE, new_name, NULL, NULL);
r = image_find(scope, IMAGE_MACHINE, new_name, NULL, NULL);
if (r >= 0)
return -EEXIST;
if (r != -ENOENT)
@ -1185,7 +1290,7 @@ static int clone_auxiliary_file(const char *path, const char *new_name, const ch
return copy_file_atomic(path, rs, 0664, COPY_REFLINK);
}
int image_clone(Image *i, const char *new_name, bool read_only) {
int image_clone(Image *i, const char *new_name, bool read_only, RuntimeScope scope) {
_cleanup_(release_lock_file) LockFile name_lock = LOCK_FILE_INIT;
_cleanup_strv_free_ char **settings = NULL;
_cleanup_free_ char *roothash = NULL;
@ -1212,7 +1317,7 @@ int image_clone(Image *i, const char *new_name, bool read_only) {
if (r < 0)
return r;
r = image_find(IMAGE_MACHINE, new_name, NULL, NULL);
r = image_find(scope, IMAGE_MACHINE, new_name, NULL, NULL);
if (r >= 0)
return -EEXIST;
if (r != -ENOENT)
@ -1646,24 +1751,35 @@ int image_name_lock(const char *name, int operation, LockFile *ret) {
}
bool image_in_search_path(
RuntimeScope scope,
ImageClass class,
const char *root,
const char *image) {
int r;
assert(scope < _RUNTIME_SCOPE_MAX && scope != RUNTIME_SCOPE_GLOBAL);
assert(class >= 0);
assert(class < _IMAGE_CLASS_MAX);
assert(image);
NULSTR_FOREACH(path, pick_image_search_path(class)) {
_cleanup_strv_free_ char **search = NULL;
r = pick_image_search_path(scope, class, &search);
if (r < 0)
return r;
STRV_FOREACH(path, search) {
const char *p, *q;
size_t k;
if (!empty_or_root(root)) {
q = path_startswith(path, root);
q = path_startswith(*path, root);
if (!q)
continue;
} else
q = path;
q = *path;
p = path_startswith(q, path);
p = path_startswith(q, *path);
if (!p)
continue;

View File

@ -13,6 +13,7 @@
#include "macro.h"
#include "os-util.h"
#include "path-util.h"
#include "runtime-scope.h"
#include "string-util.h"
#include "time-util.h"
@ -60,14 +61,14 @@ Image *image_ref(Image *i);
DEFINE_TRIVIAL_CLEANUP_FUNC(Image*, image_unref);
int image_find(ImageClass class, const char *name, const char *root, Image **ret);
int image_find(RuntimeScope scope, ImageClass class, const char *name, const char *root, Image **ret);
int image_from_path(const char *path, Image **ret);
int image_find_harder(ImageClass class, const char *name_or_path, const char *root, Image **ret);
int image_discover(ImageClass class, const char *root, Hashmap *map);
int image_find_harder(RuntimeScope scope, ImageClass class, const char *name_or_path, const char *root, Image **ret);
int image_discover(RuntimeScope scope, ImageClass class, const char *root, Hashmap *map);
int image_remove(Image *i);
int image_rename(Image *i, const char *new_name);
int image_clone(Image *i, const char *new_name, bool read_only);
int image_rename(Image *i, const char *new_name, RuntimeScope scope);
int image_clone(Image *i, const char *new_name, bool read_only, RuntimeScope scope);
int image_read_only(Image *i, bool b);
const char* image_type_to_string(ImageType t) _const_;
@ -80,7 +81,7 @@ int image_set_limit(Image *i, uint64_t referenced_max);
int image_read_metadata(Image *i, const ImagePolicy *image_policy);
bool image_in_search_path(ImageClass class, const char *root, const char *image);
bool image_in_search_path(RuntimeScope scope, ImageClass class, const char *root, const char *image);
static inline char **image_extension_release(Image *image, ImageClass class) {
assert(image);

View File

@ -2031,7 +2031,7 @@ static int image_discover_and_read_metadata(
if (!images)
return log_oom();
r = image_discover(image_class, arg_root, images);
r = image_discover(RUNTIME_SCOPE_SYSTEM, image_class, arg_root, images);
if (r < 0)
return log_error_errno(r, "Failed to discover images: %m");
@ -2278,7 +2278,7 @@ static int verb_list(int argc, char **argv, void *userdata) {
if (!images)
return log_oom();
r = image_discover(arg_image_class, arg_root, images);
r = image_discover(RUNTIME_SCOPE_SYSTEM, arg_image_class, arg_root, images);
if (r < 0)
return log_error_errno(r, "Failed to discover images: %m");
@ -2339,7 +2339,7 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl
if (!images)
return -ENOMEM;
r = image_discover(image_class, arg_root, images);
r = image_discover(RUNTIME_SCOPE_SYSTEM, image_class, arg_root, images);
if (r < 0)
return r;

View File

@ -46,6 +46,8 @@ typedef struct Manager {
Hashmap *polkit_registry;
sd_event_source *notify_event;
RuntimeScope runtime_scope; /* For now only RUNTIME_SCOPE_SYSTEM */
} Manager;
/* Forward declare so that jobs can call it on exit */
@ -1730,10 +1732,14 @@ static int manager_new(Manager **ret) {
assert(ret);
m = new0(Manager, 1);
m = new(Manager, 1);
if (!m)
return -ENOMEM;
*m = (Manager) {
.runtime_scope = RUNTIME_SCOPE_SYSTEM,
};
r = sd_event_default(&m->event);
if (r < 0)
return r;
@ -1795,7 +1801,7 @@ static int manager_enumerate_image_class(Manager *m, TargetClass class) {
if (!images)
return -ENOMEM;
r = image_discover((ImageClass) class, NULL, images);
r = image_discover(m->runtime_scope, (ImageClass) class, NULL, images);
if (r < 0)
return r;

View File

@ -2236,7 +2236,8 @@ static int determine_names(void) {
if (arg_machine) {
_cleanup_(image_unrefp) Image *i = NULL;
r = image_find(IMAGE_MACHINE, arg_machine, NULL, &i);
r = image_find(arg_privileged ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER,
IMAGE_MACHINE, arg_machine, NULL, &i);
if (r == -ENOENT)
return log_error_errno(r, "No image for machine '%s'.", arg_machine);
if (r < 0)

View File

@ -1129,7 +1129,7 @@ testcase_unpriv() {
local tmpdir name
tmpdir="$(mktemp -d /var/tmp/TEST-13-NSPAWN.unpriv.XXX)"
name="unpriv-${tmpdir##*.}"
name="unprv-${tmpdir##*.}"
trap 'rm -fr ${tmpdir@Q} || true; rm -f /run/verity.d/test-13-nspawn-${name@Q} || true' RETURN ERR
create_dummy_ddi "$tmpdir" "$name"
chown --recursive testuser: "$tmpdir"
@ -1141,6 +1141,17 @@ testcase_unpriv() {
-- \
systemd-nspawn --pipe --private-network --register=no --keep-unit --image="$tmpdir/$name.raw" echo hello >"$tmpdir/stdout.txt"
echo hello | cmp "$tmpdir/stdout.txt" -
# Make sure per-user search path logic works
systemd-run --pipe --uid=testuser mkdir -p /home/testuser/.local/state/machines
systemd-run --pipe --uid=testuser ln -s "$tmpdir/$name.raw" /home/testuser/.local/state/machines/"x$name.raw"
systemd-run \
--pipe \
--uid=testuser \
--property=Delegate=yes \
-- \
systemd-nspawn --pipe --private-network --register=no --keep-unit --machine="x$name" echo hello >"$tmpdir/stdout.txt"
echo hello | cmp "$tmpdir/stdout.txt" -
}
testcase_fuse() {

View File

@ -615,6 +615,10 @@ grep -q -F '{"name":"b","type":"raw","class":"portable","ro":false,"path":"/run/
grep -q -F '{"name":"c","type":"raw","class":"sysext","ro":false,"path":"/run/extensions/c.raw"' /tmp/discover.json
rm /tmp/discover.json /run/machines/a.raw /run/portables/b.raw /run/extensions/c.raw
systemd-dissect --discover --system
systemd-dissect --discover --user
systemd-dissect --discover --system --user
LOOP="$(systemd-dissect --attach --loop-ref=waldo "$MINIMAL_IMAGE.raw")"
# Wait until the symlinks we want to test are established