summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO40
-rw-r--r--src/boot/efi/boot.c139
-rw-r--r--src/core/bpf-foreign.c20
-rw-r--r--src/core/bpf-foreign.h5
-rw-r--r--src/fundamental/bootspec-fundamental.c56
-rw-r--r--src/fundamental/bootspec-fundamental.h16
-rw-r--r--src/fundamental/macro-fundamental.h10
-rw-r--r--src/fundamental/meson.build6
-rw-r--r--src/fundamental/sha256.h2
-rw-r--r--src/fundamental/type.h21
-rw-r--r--src/fundamental/types-fundamental.h39
-rw-r--r--src/shared/bootspec.c49
-rw-r--r--src/shared/bootspec.h2
-rwxr-xr-xtest/units/testsuite-20.sh17
14 files changed, 309 insertions, 113 deletions
diff --git a/TODO b/TODO
index 9c8bdfdaa5..bec042ab9c 100644
--- a/TODO
+++ b/TODO
@@ -81,6 +81,46 @@ Janitorial Clean-ups:
Features:
+* userdb: when synthesizing NSS records, pick "best" password from defined
+ passwords, not just the first. i.e. if there are multiple defined, prefer
+ unlocked over locked and prefer non-empty over empty.
+
+* maybe add a tool inspired by the GPT auto discovery spec that runs in the
+ initrd and rearranges the rootfs hierarchy via bind mounts, if
+ enabled. Specifically in some top-level dir /@auto/ it will look for
+ dirs/symlinks/subvolumes that are named after their purpose, and optionally
+ encode a version as well as assessment counters, and then mount them into the
+ file system tree to boot into, similar to how we do that for the gpt auto
+ logic. Maybe then bind mount the original root into /.superior or something
+ like that (so that update tools can look there). Further discussion in this
+ thread:
+ https://lists.freedesktop.org/archives/systemd-devel/2021-November/047059.html
+ The GPT dissection logic should automatically enable this tool whenever we
+ detect a specially marked root fs (i.e introduce a new generic root gpt type
+ for this, that is arch independent). The also implement this in the image
+ dissection logic, so that nspawn/RootImage= and so on grok it. Maybe make
+ generic enough so that it can also work for ostrees arrangements.
+
+* if a path ending in ".auto.d/" is set for RootDirectory=/RootImage= then do a
+ strverscmp() of everything inside that dir and use that. i.e. implement very
+ simple version control. Also use this in systemd-nspawn --image= and so on.
+
+* homed: while a home dir is not activated generate slightly different NSS
+ records for it, that reports the home dir as "/" and the shell as some binary
+ provided by us. Then, when an SSH login happens and SSH permits it our binary
+ is invoked. This binary can then talk to homed and activate the homedir if
+ it's not around yet, prompting the user for a password. Once that succeeded
+ we'll switch to the real user record, i.e. home dir and shell, and our tool
+ exec()s the latter. Net effect: ssh'ing into a homed account will just work:
+ we'll neatly prompt for the homedir's password if its needed. –– Building on
+ this we could take this even further: since this tool will potentially have
+ access to the client's ssh-agent (if ssh-agent forwarding is enabled) we
+ could implement SSH unlocking of a homedir with that: when enrolling a new
+ ssh pubkey in a user record we'd ask the ssh-agent to sign some random value
+ with the privkey, then use that as luks key to unlock the home dir. Will not
+ work for ECDSA keys since their signatures contain a random component, but
+ will work for RSA and Ed25519 keys.
+
* add tiny service that decrypts encrypted user records passed via initrd
credential logic and drops them into /run where nss-systemd can pick them up,
similar to /run/host/userdb/. Usecase: drop a root user JSON record there,
diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c
index 5992a28cdd..0bacfb4bbe 100644
--- a/src/boot/efi/boot.c
+++ b/src/boot/efi/boot.c
@@ -4,6 +4,7 @@
#include <efigpt.h>
#include <efilib.h>
+#include "bootspec-fundamental.h"
#include "console.h"
#include "devicetree.h"
#include "disk.h"
@@ -28,8 +29,9 @@
#define TEXT_ATTR_SWAP(c) EFI_TEXT_ATTR(((c) & 0b11110000) >> 4, (c) & 0b1111)
-/* magic string to find in the binary image */
-_used_ _section_(".sdmagic") static const char magic[] = "#### LoaderInfo: systemd-boot " GIT_VERSION " ####";
+/* Magic string for recognizing our own binaries */
+_used_ _section_(".sdmagic") static const char magic[] =
+ "#### LoaderInfo: systemd-boot " GIT_VERSION " ####";
/* Makes systemd-boot available from \EFI\Linux\ for testing purposes. */
_used_ _section_(".osrel") static const char osrel[] =
@@ -44,10 +46,10 @@ enum loader_type {
};
typedef struct {
- CHAR16 *id; /* The unique identifier for this entry */
- CHAR16 *title_show;
- CHAR16 *title;
- CHAR16 *version;
+ CHAR16 *id; /* The unique identifier for this entry (typically the filename of the file defining the entry) */
+ CHAR16 *title_show; /* The string to actually display (this is made unique before showing) */
+ CHAR16 *title; /* The raw (human readable) title string of the entry (not necessarily unique) */
+ CHAR16 *version; /* The raw (human readable) version string of the entry */
CHAR16 *machine_id;
EFI_HANDLE *device;
enum loader_type type;
@@ -1651,14 +1653,14 @@ static void config_sort_entries(Config *config) {
sort_pointer_array((void**) config->entries, config->entry_count, (compare_pointer_func_t) config_entry_compare);
}
-static INTN config_entry_find(Config *config, CHAR16 *needle) {
+static INTN config_entry_find(Config *config, const CHAR16 *needle) {
assert(config);
if (!needle)
return -1;
for (UINTN i = 0; i < config->entry_count; i++)
- if (MetaiMatch(config->entries[i]->id, needle))
+ if (MetaiMatch(config->entries[i]->id, (CHAR16*) needle))
return (INTN) i;
return -1;
@@ -1732,13 +1734,9 @@ static void config_title_generate(Config *config) {
/* set title */
for (UINTN i = 0; i < config->entry_count; i++) {
- CHAR16 *title;
-
FreePool(config->entries[i]->title_show);
- title = config->entries[i]->title;
- if (!title)
- title = config->entries[i]->id;
- config->entries[i]->title_show = StrDuplicate(title);
+ config->entries[i]->title_show = StrDuplicate(
+ config->entries[i]->title ?: config->entries[i]->id);
}
if (!find_nonunique(config->entries, config->entry_count))
@@ -2044,8 +2042,10 @@ static void config_entry_add_linux(
NULL,
};
- _cleanup_freepool_ CHAR16 *os_name_pretty = NULL, *os_name = NULL, *os_id = NULL,
- *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL, *os_image_version = NULL;
+ _cleanup_freepool_ CHAR16 *os_pretty_name = NULL, *os_image_id = NULL, *os_name = NULL, *os_id = NULL,
+ *os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL,
+ *path = NULL;
+ const CHAR16 *good_name, *good_version;
_cleanup_freepool_ CHAR8 *content = NULL;
UINTN offs[_SECTION_MAX] = {};
UINTN szs[_SECTION_MAX] = {};
@@ -2077,82 +2077,101 @@ static void config_entry_add_linux(
/* read properties from the embedded os-release file */
while ((line = line_get_key_value(content, (CHAR8 *)"=", &pos, &key, &value))) {
- if (strcmpa((CHAR8 *)"PRETTY_NAME", key) == 0) {
- FreePool(os_name_pretty);
- os_name_pretty = stra_to_str(value);
+ if (strcmpa((const CHAR8*) "PRETTY_NAME", key) == 0) {
+ FreePool(os_pretty_name);
+ os_pretty_name = stra_to_str(value);
+ continue;
+ }
+
+ if (strcmpa((const CHAR8*) "IMAGE_ID", key) == 0) {
+ FreePool(os_image_id);
+ os_image_id = stra_to_str(value);
continue;
}
- if (strcmpa((CHAR8 *)"NAME", key) == 0) {
+ if (strcmpa((const CHAR8*) "NAME", key) == 0) {
FreePool(os_name);
os_name = stra_to_str(value);
continue;
}
- if (strcmpa((CHAR8 *)"ID", key) == 0) {
+ if (strcmpa((const CHAR8*) "ID", key) == 0) {
FreePool(os_id);
os_id = stra_to_str(value);
continue;
}
- if (strcmpa((CHAR8 *)"VERSION", key) == 0) {
+ if (strcmpa((const CHAR8*) "IMAGE_VERSION", key) == 0) {
+ FreePool(os_image_version);
+ os_image_version = stra_to_str(value);
+ continue;
+ }
+
+ if (strcmpa((const CHAR8*) "VERSION", key) == 0) {
FreePool(os_version);
os_version = stra_to_str(value);
continue;
}
- if (strcmpa((CHAR8 *)"VERSION_ID", key) == 0) {
+ if (strcmpa((const CHAR8*) "VERSION_ID", key) == 0) {
FreePool(os_version_id);
os_version_id = stra_to_str(value);
continue;
}
- if (strcmpa((CHAR8 *)"BUILD_ID", key) == 0) {
+ if (strcmpa((const CHAR8*) "BUILD_ID", key) == 0) {
FreePool(os_build_id);
os_build_id = stra_to_str(value);
continue;
}
-
- if (strcmpa((const CHAR8*) "IMAGE_VERSION", key) == 0) {
- FreePool(os_image_version);
- os_image_version = stra_to_str(value);
- continue;
- }
}
- if ((os_name_pretty || os_name) && os_id && (os_image_version || os_version || os_version_id || os_build_id)) {
- _cleanup_freepool_ CHAR16 *path = NULL;
-
- path = PoolPrint(L"\\EFI\\Linux\\%s", f->FileName);
-
- entry = config_entry_add_loader(
- config,
- device,
- LOADER_LINUX,
- f->FileName,
- /* key= */ 'l',
- os_name_pretty ?: os_name,
- path,
- os_image_version ?: (os_version ?: (os_version_id ? : os_build_id)));
-
- config_entry_parse_tries(entry, L"\\EFI\\Linux", f->FileName, L".efi");
-
- if (szs[SECTION_CMDLINE] == 0)
- continue;
+ if (!bootspec_pick_name_version(
+ os_pretty_name,
+ os_image_id,
+ os_name,
+ os_id,
+ os_image_version,
+ os_version,
+ os_version_id,
+ os_build_id,
+ &good_name,
+ &good_version))
+ continue;
- FreePool(content);
- content = NULL;
+ path = PoolPrint(L"\\EFI\\Linux\\%s", f->FileName);
+ if (!path)
+ return (void) log_oom();
+
+ entry = config_entry_add_loader(
+ config,
+ device,
+ LOADER_LINUX,
+ f->FileName,
+ /* key= */ 'l',
+ good_name,
+ path,
+ good_version);
+ if (!entry)
+ return (void) log_oom();
+
+ config_entry_parse_tries(entry, L"\\EFI\\Linux", f->FileName, L".efi");
+
+ if (szs[SECTION_CMDLINE] == 0)
+ continue;
- /* read the embedded cmdline file */
- err = file_read(linux_dir, f->FileName, offs[SECTION_CMDLINE], szs[SECTION_CMDLINE], &content, NULL);
- if (!EFI_ERROR(err)) {
+ content = mfree(content);
- /* chomp the newline */
- if (content[szs[SECTION_CMDLINE] - 1] == '\n')
- content[szs[SECTION_CMDLINE] - 1] = '\0';
+ /* read the embedded cmdline file */
+ err = file_read(linux_dir, f->FileName, offs[SECTION_CMDLINE], szs[SECTION_CMDLINE], &content, NULL);
+ if (!EFI_ERROR(err)) {
+ /* chomp the newline */
+ if (content[szs[SECTION_CMDLINE] - 1] == '\n')
+ content[szs[SECTION_CMDLINE] - 1] = '\0';
- entry->options = stra_to_str(content);
- }
+ entry->options = stra_to_str(content);
+ if (!entry->options)
+ return (void) log_oom();
}
}
}
@@ -2480,7 +2499,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
err = image_start(root_dir, image, &config, entry);
if (EFI_ERROR(err)) {
graphics_mode(FALSE);
- log_error_stall(L"Failed to execute %s (%s): %r", entry->title, entry->loader, err);
+ log_error_stall(L"Failed to execute %s (%s): %r", entry->title_show, entry->loader, err);
goto out;
}
diff --git a/src/core/bpf-foreign.c b/src/core/bpf-foreign.c
index 6b93b9785f..8538792b60 100644
--- a/src/core/bpf-foreign.c
+++ b/src/core/bpf-foreign.c
@@ -4,8 +4,10 @@
#include "bpf-program.h"
#include "cgroup.h"
#include "memory-util.h"
+#include "missing_magic.h"
#include "mountpoint-util.h"
#include "set.h"
+#include "stat-util.h"
typedef struct BPFForeignKey BPFForeignKey;
struct BPFForeignKey {
@@ -84,6 +86,14 @@ static int bpf_foreign_prepare(
assert(u);
assert(bpffs_path);
+ r = path_is_fs_type(bpffs_path, BPF_FS_MAGIC);
+ if (r < 0)
+ return log_unit_error_errno(u, r,
+ "Failed to determine filesystem type of %s: %m", bpffs_path);
+ if (r == 0)
+ return log_unit_error_errno(u, SYNTHETIC_ERRNO(EINVAL),
+ "Path in BPF filesystem is expected.");
+
r = bpf_program_new_from_bpffs_path(bpffs_path, &prog);
if (r < 0)
return log_unit_error_errno(u, r, "Failed to create foreign BPFProgram: %m");
@@ -111,16 +121,6 @@ static int bpf_foreign_prepare(
return 0;
}
-int bpf_foreign_supported(void) {
- int r;
-
- r = cg_all_unified();
- if (r <= 0)
- return r;
-
- return path_is_mount_point("/sys/fs/bpf", NULL, 0);
-}
-
int bpf_foreign_install(Unit *u) {
_cleanup_free_ char *cgroup_path = NULL;
CGroupBPFForeignProgram *p;
diff --git a/src/core/bpf-foreign.h b/src/core/bpf-foreign.h
index 9559cd7981..e387b1b1d3 100644
--- a/src/core/bpf-foreign.h
+++ b/src/core/bpf-foreign.h
@@ -4,7 +4,10 @@
#include "unit.h"
-int bpf_foreign_supported(void);
+static inline int bpf_foreign_supported(void) {
+ return cg_all_unified();
+}
+
/*
* Attach cgroup-bpf programs foreign to systemd, i.e. loaded to the kernel by an entity
* external to systemd.
diff --git a/src/fundamental/bootspec-fundamental.c b/src/fundamental/bootspec-fundamental.c
new file mode 100644
index 0000000000..9c4aee9744
--- /dev/null
+++ b/src/fundamental/bootspec-fundamental.c
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "bootspec-fundamental.h"
+
+sd_bool bootspec_pick_name_version(
+ const sd_char *os_pretty_name,
+ const sd_char *os_image_id,
+ const sd_char *os_name,
+ const sd_char *os_id,
+ const sd_char *os_image_version,
+ const sd_char *os_version,
+ const sd_char *os_version_id,
+ const sd_char *os_build_id,
+ const sd_char **ret_name,
+ const sd_char **ret_version) {
+
+ const sd_char *good_name, *good_version;
+
+ /* Find the best human readable title and version string for a boot entry (using the os-release(5)
+ * fields). Precise is preferred over vague, and human readable over machine readable. Thus:
+ *
+ * 1. First priority gets the PRETTY_NAME field, which is the primary string intended for display,
+ * and should already contain both a nice description and a version indication (if that concept
+ * applies).
+ *
+ * 2. Otherwise we go for IMAGE_ID and IMAGE_VERSION (thus we show details about the image,
+ * i.e. specific combination of packages and configuration), if that concept applies.
+ *
+ * 3. Otherwise we go for NAME and VERSION (i.e. human readable OS name and version)
+ *
+ * 4. Otherwise we go for ID and VERSION_ID (i.e. machine readable OS name and version)
+ *
+ * 5. Finally, for the version we'll use BUILD_ID (i.e. a machine readable version that identifies
+ * the original OS build used during installation)
+ *
+ * Note that the display logic will show only the name by default, except if that isn't unique in
+ * which case the version is shown too.
+ *
+ * Note that name/version determined here are used only for display purposes. Boot entry preference
+ * sorting (i.e. algorithmic ordering of boot entries) is done based on the order of the entry "id"
+ * string (i.e. not on os-release(5) data). */
+
+ good_name = os_pretty_name ?: (os_image_id ?: (os_name ?: os_id));
+ good_version = os_image_version ?: (os_version ?: (os_version_id ? : os_build_id));
+
+ if (!good_name || !good_version)
+ return sd_false;
+
+ if (ret_name)
+ *ret_name = good_name;
+
+ if (ret_version)
+ *ret_version = good_version;
+
+ return sd_true;
+}
diff --git a/src/fundamental/bootspec-fundamental.h b/src/fundamental/bootspec-fundamental.h
new file mode 100644
index 0000000000..2cb6d7daa1
--- /dev/null
+++ b/src/fundamental/bootspec-fundamental.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "types-fundamental.h"
+
+sd_bool bootspec_pick_name_version(
+ const sd_char *os_pretty_name,
+ const sd_char *os_image_id,
+ const sd_char *os_name,
+ const sd_char *os_id,
+ const sd_char *os_image_version,
+ const sd_char *os_version,
+ const sd_char *os_version_id,
+ const sd_char *os_build_id,
+ const sd_char **ret_name,
+ const sd_char **ret_version);
diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h
index 44af0bd0a0..1f640b3ae3 100644
--- a/src/fundamental/macro-fundamental.h
+++ b/src/fundamental/macro-fundamental.h
@@ -6,7 +6,7 @@
#endif
#include <limits.h>
-#include "type.h"
+#include "types-fundamental.h"
#define _align_(x) __attribute__((__aligned__(x)))
#define _const_ __attribute__((__const__))
@@ -85,8 +85,8 @@
#define ONCE __ONCE(UNIQ_T(_once_, UNIQ))
#define __ONCE(o) \
({ \
- static bool (o) = false; \
- __sync_bool_compare_and_swap(&(o), false, true); \
+ static sd_bool (o) = sd_false; \
+ __sync_bool_compare_and_swap(&(o), sd_false, sd_true); \
})
#undef MAX
@@ -236,7 +236,7 @@
#define IN_SET(x, ...) \
({ \
- sd_bool _found = false; \
+ sd_bool _found = sd_false; \
/* If the build breaks in the line below, you need to extend the case macros. (We use "long double" as \
* type for the array, in the hope that checkers such as ubsan don't complain that the initializers for \
* the array are not representable by the base type. Ideally we'd use typeof(x) as base type, but that \
@@ -245,7 +245,7 @@
assert_cc(ELEMENTSOF(__assert_in_set) <= 20); \
switch(x) { \
FOR_EACH_MAKE_CASE(__VA_ARGS__) \
- _found = true; \
+ _found = sd_true; \
break; \
default: \
break; \
diff --git a/src/fundamental/meson.build b/src/fundamental/meson.build
index 3c9f07b191..287f0fe36a 100644
--- a/src/fundamental/meson.build
+++ b/src/fundamental/meson.build
@@ -3,13 +3,15 @@
fundamental_path = meson.current_source_dir()
fundamental_headers = files(
+ 'bootspec-fundamental.h',
'efivars-fundamental.h',
'macro-fundamental.h',
- 'string-util-fundamental.h',
'sha256.h',
- 'type.h')
+ 'string-util-fundamental.h',
+ 'types-fundamental.h')
sources = '''
+ bootspec-fundamental.c
efivars-fundamental.c
string-util-fundamental.c
sha256.c
diff --git a/src/fundamental/sha256.h b/src/fundamental/sha256.h
index 9fc090b4e0..abc4167628 100644
--- a/src/fundamental/sha256.h
+++ b/src/fundamental/sha256.h
@@ -6,7 +6,7 @@
#include <efilib.h>
#endif
-#include "type.h"
+#include "types-fundamental.h"
struct sha256_ctx {
uint32_t H[8];
diff --git a/src/fundamental/type.h b/src/fundamental/type.h
deleted file mode 100644
index 2a9a114bbc..0000000000
--- a/src/fundamental/type.h
+++ /dev/null
@@ -1,21 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#pragma once
-
-#ifdef SD_BOOT
-#include <efi.h>
-
-typedef BOOLEAN sd_bool;
-typedef CHAR16 sd_char;
-typedef INTN sd_int;
-typedef UINTN size_t;
-
-#define true TRUE
-#define false FALSE
-#else
-#include <stdbool.h>
-#include <stdint.h>
-
-typedef bool sd_bool;
-typedef char sd_char;
-typedef int sd_int;
-#endif
diff --git a/src/fundamental/types-fundamental.h b/src/fundamental/types-fundamental.h
new file mode 100644
index 0000000000..5977e40c6c
--- /dev/null
+++ b/src/fundamental/types-fundamental.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/* This defines a number of basic types that are one thing in userspace and another in the UEFI environment,
+ * but mostly the same in concept and behaviour.
+ *
+ * Note: if the definition of these types/values has slightly different semantics in userspace and in the
+ * UEFI environment then please prefix its name with "sd_" to make clear these types have special semantics,
+ * and *we* defined them. Otherwise, if the types are effectively 100% identical in behaviour in userspace
+ * and UEFI environment you can omit the prefix. (Examples: sd_char is 8 bit in userspace and 16 bit in UEFI
+ * space hence it should have the sd_ prefix; but size_t in userspace and UINTN in UEFI environment are 100%
+ * defined the same way ultimately, hence it's OK to just define size_t as alias to UINTN in UEFI
+ * environment, so that size_t can be used everywhere, without any "sd_" prefix.)
+ *
+ * Note: we generally prefer the userspace names of types and concepts. i.e. if in doubt please name types
+ * after the userspace vocabulary, and let's keep UEFI vocabulary specific to the UEFI build environment. */
+
+#ifdef SD_BOOT
+#include <efi.h>
+
+typedef BOOLEAN sd_bool;
+typedef CHAR16 sd_char;
+typedef INTN sd_int;
+typedef UINTN size_t;
+
+#define sd_true TRUE
+#define sd_false FALSE
+#else
+#include <stdbool.h>
+#include <stdint.h>
+
+typedef bool sd_bool;
+typedef char sd_char;
+typedef int sd_int;
+
+#define sd_true true
+#define sd_false false
+
+#endif
diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c
index e538e2bd87..8fdb5272e9 100644
--- a/src/shared/bootspec.c
+++ b/src/shared/bootspec.c
@@ -9,6 +9,7 @@
#include "alloc-util.h"
#include "blkid-util.h"
+#include "bootspec-fundamental.h"
#include "bootspec.h"
#include "conf-files.h"
#include "def.h"
@@ -287,13 +288,13 @@ static int boot_entry_load_unified(
const char *cmdline,
BootEntry *ret) {
- _cleanup_free_ char *os_pretty_name = NULL, *os_id = NULL, *version_id = NULL, *build_id = NULL;
+ _cleanup_free_ char *os_pretty_name = NULL, *os_image_id = NULL, *os_name = NULL, *os_id = NULL,
+ *os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL;
_cleanup_(boot_entry_free) BootEntry tmp = {
.type = BOOT_ENTRY_UNIFIED,
};
+ const char *k, *good_name, *good_version;
_cleanup_fclose_ FILE *f = NULL;
- const char *k;
- char *b;
int r;
assert(root);
@@ -310,24 +311,42 @@ static int boot_entry_load_unified(
r = parse_env_file(f, "os-release",
"PRETTY_NAME", &os_pretty_name,
+ "IMAGE_ID", &os_image_id,
+ "NAME", &os_name,
"ID", &os_id,
- "VERSION_ID", &version_id,
- "BUILD_ID", &build_id);
+ "IMAGE_VERSION", &os_image_version,
+ "VERSION", &os_version,
+ "VERSION_ID", &os_version_id,
+ "BUILD_ID", &os_build_id);
if (r < 0)
return log_error_errno(r, "Failed to parse os-release data from unified kernel image %s: %m", path);
- if (!os_pretty_name || !os_id || !(version_id || build_id))
+ if (!bootspec_pick_name_version(
+ os_pretty_name,
+ os_image_id,
+ os_name,
+ os_id,
+ os_image_version,
+ os_version,
+ os_version_id,
+ os_build_id,
+ &good_name,
+ &good_version))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing fields in os-release data from unified kernel image %s, refusing.", path);
- b = basename(path);
- tmp.id = strdup(b);
- tmp.id_old = strjoin(os_id, "-", version_id ?: build_id);
- if (!tmp.id || !tmp.id_old)
- return log_oom();
+ r = path_extract_filename(path, &tmp.id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to extract file name from '%s': %m", path);
if (!efi_loader_entry_name_valid(tmp.id))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", tmp.id);
+ if (os_id && os_version_id) {
+ tmp.id_old = strjoin(os_id, "-", os_version_id);
+ if (!tmp.id_old)
+ return log_oom();
+ }
+
tmp.path = strdup(path);
if (!tmp.path)
return log_oom();
@@ -346,7 +365,13 @@ static int boot_entry_load_unified(
delete_trailing_chars(tmp.options[0], WHITESPACE);
- tmp.title = TAKE_PTR(os_pretty_name);
+ tmp.title = strdup(good_name);
+ if (!tmp.title)
+ return log_oom();
+
+ tmp.version = strdup(good_version);
+ if (!tmp.version)
+ return log_oom();
*ret = tmp;
tmp = (BootEntry) {};
diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h
index c7b4a2ee02..81845f47e3 100644
--- a/src/shared/bootspec.h
+++ b/src/shared/bootspec.h
@@ -20,7 +20,7 @@ typedef enum BootEntryType {
typedef struct BootEntry {
BootEntryType type;
- char *id; /* This is the file basename without extension */
+ char *id; /* This is the file basename (including extension!) */
char *id_old; /* Old-style ID, for deduplication purposes. */
char *path; /* This is the full path to the drop-in file */
char *root; /* The root path in which the drop-in was found, i.e. to which 'kernel', 'efi' and 'initrd' are relative */
diff --git a/test/units/testsuite-20.sh b/test/units/testsuite-20.sh
index 17a27d7d7c..fc87c18c4c 100755
--- a/test/units/testsuite-20.sh
+++ b/test/units/testsuite-20.sh
@@ -143,6 +143,23 @@ systemd-run --unit=test20-mainpidsh3.service \
# Test that this failed due to timeout, and not some other error
test "$(systemctl show -P Result test20-mainpidsh3.service)" = timeout
+# Test that scope units work
+systemd-run --scope --unit test20-true.scope /bin/true
+test "$(systemctl show -P Result test20-true.scope)" = success
+
+# Test that user scope units work as well
+
+runas() {
+ declare userid=$1
+ shift
+ # shellcheck disable=SC2016
+ su "$userid" -s /bin/sh -c 'XDG_RUNTIME_DIR=/run/user/$UID exec "$@"' -- sh "$@"
+}
+
+systemctl start user@4711.service
+runas testuser systemd-run --scope --user --unit test20-true.scope /bin/true
+test "$(systemctl show -P Result test20-true.scope)" = success
+
systemd-analyze log-level info
echo OK >/testok