#include "string-table.h"
#include "strv.h"
#include "terminal-util.h"
+#include "uki.h"
#include "unaligned.h"
static const char* const boot_entry_type_table[_BOOT_ENTRY_TYPE_MAX] = {
free(entry->id);
free(entry->id_old);
+ free(entry->id_without_profile);
free(entry->path);
free(entry->root);
free(entry->title);
return r;
}
- r = -strverscmp_improved(a->id, b->id);
+ r = -strverscmp_improved(a->id_without_profile ?: a->id, b->id_without_profile ?: b->id);
if (r != 0)
return r;
+ if (a->id_without_profile && b->id_without_profile) {
+ /* The strverscmp_improved() call above already established that we are talking about the
+ * same image here, hence order by profile, if there is one */
+ r = CMP(a->profile, b->profile);
+ if (r != 0)
+ return r;
+ }
+
if (a->tries_left != UINT_MAX || b->tries_left != UINT_MAX)
return 0;
static int boot_entry_load_unified(
const char *root,
const char *path,
- const char *osrelease,
- const char *cmdline,
+ unsigned profile,
+ const char *osrelease_text,
+ const char *profile_text,
+ const char *cmdline_text,
BootEntry *ret) {
_cleanup_free_ char *fname = NULL, *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 = BOOT_ENTRY_INIT(BOOT_ENTRY_UNIFIED);
const char *k, *good_name, *good_version, *good_sort_key;
_cleanup_fclose_ FILE *f = NULL;
int r;
assert(root);
assert(path);
- assert(osrelease);
+ assert(osrelease_text);
+ assert(ret);
k = path_startswith(path, root);
if (!k)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path);
- f = fmemopen_unlocked((void*) osrelease, strlen(osrelease), "r");
+ f = fmemopen_unlocked((void*) osrelease_text, strlen(osrelease_text), "r");
if (!f)
- return log_error_errno(errno, "Failed to open os-release buffer: %m");
+ return log_oom();
r = parse_env_file(f, "os-release",
"PRETTY_NAME", &os_pretty_name,
&good_sort_key))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing fields in os-release data from unified kernel image %s, refusing.", path);
+ _cleanup_free_ char *profile_id = NULL, *profile_title = NULL;
+ if (profile_text) {
+ fclose(f);
+
+ f = fmemopen_unlocked((void*) profile_text, strlen(profile_text), "r");
+ if (!f)
+ return log_oom();
+
+ r = parse_env_file(
+ f, "profile",
+ "ID", &profile_id,
+ "TITLE", &profile_title);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse profile data from unified kernel image '%s': %m", path);
+ }
+
r = path_extract_filename(path, &fname);
if (r < 0)
return log_error_errno(r, "Failed to extract file name from '%s': %m", path);
+ _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_UNIFIED);
+
r = boot_filename_extract_tries(fname, &tmp.id, &tmp.tries_left, &tmp.tries_done);
if (r < 0)
return r;
if (!efi_loader_entry_name_valid(tmp.id))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", tmp.id);
+ tmp.profile = profile;
+
+ if (profile_id || profile > 0) {
+ tmp.id_without_profile = TAKE_PTR(tmp.id);
+
+ if (profile_id)
+ tmp.id = strjoin(tmp.id_without_profile, "@", profile_id);
+ else
+ (void) asprintf(&tmp.id, "%s@%u", tmp.id_without_profile, profile);
+ if (!tmp.id)
+ return log_oom();
+ }
+
if (os_id && os_version_id) {
tmp.id_old = strjoin(os_id, "-", os_version_id);
if (!tmp.id_old)
if (!tmp.kernel)
return log_oom();
- tmp.options = strv_new(skip_leading_chars(cmdline, WHITESPACE));
+ tmp.options = strv_new(cmdline_text);
if (!tmp.options)
return log_oom();
- delete_trailing_chars(tmp.options[0], WHITESPACE);
-
- tmp.title = strdup(good_name);
+ if (profile_title)
+ tmp.title = strjoin(good_name, " (", profile_title, ")");
+ else if (profile_id)
+ tmp.title = strjoin(good_name, " (", profile_id, ")");
+ else if (profile > 0)
+ (void) asprintf(&tmp.title, "%s (@%u)", good_name, profile);
+ else
+ tmp.title = strdup(good_name);
if (!tmp.title)
return log_oom();
return 0;
}
-/* Maximum PE section we are willing to load (Note that sections we are not interested in may be larger, but
- * the ones we do care about and we are willing to load into memory have this size limit.) */
-#define PE_SECTION_SIZE_MAX (4U*1024U*1024U)
-
-static int find_sections(
+static int pe_load_headers_and_sections(
int fd,
const char *path,
IMAGE_SECTION_HEADER **ret_sections,
return 0;
}
-static int find_cmdline_section(
- int fd,
- const char *path,
- IMAGE_SECTION_HEADER *sections,
- PeHeader *pe_header,
- char **ret_cmdline) {
+static const IMAGE_SECTION_HEADER* pe_find_profile_section_table(
+ const PeHeader *pe_header,
+ const IMAGE_SECTION_HEADER *sections,
+ unsigned profile,
+ size_t *ret_n_sections) {
- int r;
- char *cmdline = NULL, *t = NULL;
- _cleanup_free_ char *word = NULL;
+ assert(pe_header);
- assert(path);
+ /* Looks for the part of the section table that defines the specified profile. If 'profile' is
+ * specified as UINT_MAX this will look for the base profile. */
- if (!ret_cmdline)
- return 0;
+ if (le16toh(pe_header->pe.NumberOfSections) == 0)
+ return NULL;
- r = pe_read_section_data_by_name(fd, pe_header, sections, ".cmdline", PE_SECTION_SIZE_MAX, (void**) &cmdline, NULL);
- if (r == -ENXIO) { /* cmdline is optional */
- *ret_cmdline = NULL;
- return 0;
+ assert(sections);
+
+ const IMAGE_SECTION_HEADER
+ *p = sections,
+ *e = sections + le16toh(pe_header->pe.NumberOfSections),
+ *start = profile == UINT_MAX ? sections : NULL,
+ *end;
+ unsigned current_profile = UINT_MAX;
+
+ for (;;) {
+ p = pe_section_table_find(p, e - p, ".profile");
+ if (!p) {
+ end = e;
+ break;
+ }
+ if (current_profile == profile) {
+ end = p;
+ break;
+ }
+
+ if (current_profile == UINT_MAX)
+ current_profile = 0;
+ else
+ current_profile++;
+
+ if (current_profile == profile)
+ start = p;
+
+ p++; /* Continue scanning after the .profile entry we just found */
}
- if (r < 0)
- return log_warning_errno(r, "Failed to read .cmdline section of '%s': %m", path);
- word = strdup(cmdline);
- if (!word)
- return log_oom();
+ if (!start)
+ return NULL;
- /* Quick test to check if there is actual content in the addon cmdline */
- t = delete_chars(word, NULL);
- if (isempty(t))
- *ret_cmdline = NULL;
- else
- *ret_cmdline = TAKE_PTR(cmdline);
+ if (ret_n_sections)
+ *ret_n_sections = end - start;
- return 0;
+ return start;
}
-static int find_osrel_section(
- int fd,
- const char *path,
- IMAGE_SECTION_HEADER *sections,
- PeHeader *pe_header,
- char **ret_osrelease) {
+static int trim_cmdline(char **cmdline) {
+ assert(cmdline);
- int r;
+ /* Strips leading and trailing whitespace from command line */
- if (!ret_osrelease)
+ if (!*cmdline)
return 0;
- r = pe_read_section_data_by_name(fd, pe_header, sections, ".osrel", PE_SECTION_SIZE_MAX, (void**) ret_osrelease, NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to read .osrel section of '%s': %m", path);
+ const char *skipped = skip_leading_chars(*cmdline, WHITESPACE);
- return 0;
+ if (isempty(skipped)) {
+ *cmdline = mfree(*cmdline);
+ return 0;
+ }
+
+ if (skipped != *cmdline) {
+ _cleanup_free_ char *c = strdup(skipped);
+ if (!c)
+ return -ENOMEM;
+
+ free_and_replace(*cmdline, c);
+ }
+
+ delete_trailing_chars(*cmdline, WHITESPACE);
+ return 1;
}
-static int find_uki_sections(
+/* Maximum PE section we are willing to load (Note that sections we are not interested in may be larger, but
+ * the ones we do care about and we are willing to load into memory have this size limit.) */
+#define PE_SECTION_SIZE_MAX (4U*1024U*1024U)
+
+static int pe_find_uki_sections(
int fd,
const char *path,
+ unsigned profile,
char **ret_osrelease,
+ char **ret_profile,
char **ret_cmdline) {
+ _cleanup_free_ char *osrelease_text = NULL, *profile_text = NULL, *cmdline_text = NULL;
_cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
_cleanup_free_ PeHeader *pe_header = NULL;
int r;
- r = find_sections(fd, path, §ions, &pe_header);
+ assert(fd >= 0);
+ assert(path);
+ assert(profile != UINT_MAX);
+ assert(ret_osrelease);
+ assert(ret_profile);
+ assert(ret_cmdline);
+
+ r = pe_load_headers_and_sections(fd, path, §ions, &pe_header);
if (r < 0)
return r;
if (!pe_is_uki(pe_header, sections))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Parsed PE file '%s' is not a UKI.", path);
- r = find_osrel_section(fd, path, sections, pe_header, ret_osrelease);
- if (r < 0)
- return r;
+ /* Find part of the section table for this profile */
+ size_t n_psections = 0;
+ const IMAGE_SECTION_HEADER *psections = pe_find_profile_section_table(pe_header, sections, profile, &n_psections);
+ if (!psections && profile != 0) /* Profile not found? (Profile @0 needs no explicit .profile!) */
+ goto nothing;
- r = find_cmdline_section(fd, path, sections, pe_header, ret_cmdline);
- if (r < 0)
- return r;
+ /* Find base profile part of section table */
+ size_t n_bsections;
+ const IMAGE_SECTION_HEADER *bsections = ASSERT_PTR(pe_find_profile_section_table(pe_header, sections, UINT_MAX, &n_bsections));
+
+ struct {
+ const char *name;
+ char **data;
+ } table[] = {
+ { ".osrel", &osrelease_text },
+ { ".profile", &profile_text },
+ { ".cmdline", &cmdline_text },
+ };
+
+ FOREACH_ELEMENT(t, table) {
+ const IMAGE_SECTION_HEADER *found;
+
+ /* First look in the profile part of the section table, and if we don't find anything there, look into the base part */
+ found = pe_section_table_find(psections, n_psections, t->name);
+ if (!found) {
+ found = pe_section_table_find(bsections, n_bsections, t->name);
+ if (!found)
+ continue;
+ }
+ /* Permit "masking" of sections in the base profile */
+ if (found->VirtualSize == 0)
+ continue;
+
+ r = pe_read_section_data(fd, found, PE_SECTION_SIZE_MAX, (void**) t->data, /* ret_data= */ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to load contents of section '%s': %m", t->name);
+ }
+
+ if (!osrelease_text)
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unified kernel image lacks .osrel data for profile @%u, refusing.", profile);
+
+ if (trim_cmdline(&cmdline_text) < 0)
+ return log_oom();
+
+ *ret_osrelease = TAKE_PTR(osrelease_text);
+ *ret_profile = TAKE_PTR(profile_text);
+ *ret_cmdline = TAKE_PTR(cmdline_text);
+ return 1;
+
+nothing:
+ *ret_osrelease = *ret_profile = *ret_cmdline = NULL;
return 0;
}
-static int find_addon_sections(
+static int pe_find_addon_sections(
int fd,
const char *path,
char **ret_cmdline) {
_cleanup_free_ PeHeader *pe_header = NULL;
int r;
- r = find_sections(fd, path, §ions, &pe_header);
+ assert(fd >= 0);
+ assert(path);
+
+ r = pe_load_headers_and_sections(fd, path, §ions, &pe_header);
if (r < 0)
return r;
- r = find_cmdline_section(fd, path, sections, pe_header, ret_cmdline);
- /* If addon cmdline is empty or contains just separators,
- * don't bother tracking it.
- * Don't check r because it cannot return <0 if cmdline is empty,
- * as cmdline is always optional. */
- if (!ret_cmdline)
- return log_warning_errno(SYNTHETIC_ERRNO(ENOENT), "Addon %s contains empty cmdline and will be therefore ignored.", path);
+ if (!pe_is_addon(pe_header, sections))
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Parse PE file '%s' is not an add-on.", path);
+
+ /* Define early, before the goto below */
+ _cleanup_free_ char *cmdline_text = NULL;
- return r;
+ const IMAGE_SECTION_HEADER *found = pe_section_table_find(sections, le16toh(pe_header->pe.NumberOfSections), ".cmdline");
+ if (!found)
+ goto nothing;
+
+ r = pe_read_section_data(fd, found, PE_SECTION_SIZE_MAX, (void**) &cmdline_text, /* ret_size= */ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to load contents of section '.cmdline': %m");
+
+ if (trim_cmdline(&cmdline_text) < 0)
+ return log_oom();
+
+ *ret_cmdline = TAKE_PTR(cmdline_text);
+ return 1;
+
+nothing:
+ *ret_cmdline = NULL;
+ return 0;
}
static int insert_boot_entry_addon(
if (!j)
return log_oom();
- if (find_addon_sections(fd, j, &cmdline) < 0)
+ if (pe_find_addon_sections(fd, j, &cmdline) <= 0)
continue;
location = strdup(j);
return log_error_errno(r, "Failed to open '%s/%s': %m", root, dir);
FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) {
- _cleanup_free_ char *j = NULL, *osrelease = NULL, *cmdline = NULL;
- _cleanup_close_ int fd = -EBADF;
-
if (!dirent_is_file(de))
continue;
if (!endswith_no_case(de->d_name, ".efi"))
continue;
- if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1))
- return log_oom();
-
- fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY);
+ _cleanup_close_ int fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY);
if (fd < 0) {
log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", full, de->d_name);
continue;
if (r == 0) /* inode already seen or otherwise not relevant */
continue;
- j = path_join(full, de->d_name);
+ _cleanup_free_ char *j = path_join(full, de->d_name);
if (!j)
return log_oom();
- if (find_uki_sections(fd, j, &osrelease, &cmdline) < 0)
- continue;
+ for (unsigned p = 0; p < UNIFIED_PROFILES_MAX; p++) {
+ _cleanup_free_ char *osrelease = NULL, *profile = NULL, *cmdline = NULL;
- r = boot_entry_load_unified(root, j, osrelease, cmdline, config->entries + config->n_entries);
- if (r < 0)
- continue;
+ r = pe_find_uki_sections(fd, j, p, &osrelease, &profile, &cmdline);
+ if (r == 0) /* this profile does not exist, we are done */
+ break;
+ if (r < 0)
+ continue;
- /* look for .efi.extra.d */
- r = boot_entries_find_unified_local_addons(config, dirfd(d), de->d_name, full, config->entries + config->n_entries);
- if (r < 0)
- continue;
+ if (!GREEDY_REALLOC0(config->entries, config->n_entries + 2))
+ return log_oom();
+
+ if (boot_entry_load_unified(root, j, p, osrelease, profile, cmdline, config->entries + config->n_entries) < 0)
+ continue;
- config->n_entries++;
+ config->n_entries++;
+
+ /* look for .efi.extra.d */
+ (void) boot_entries_find_unified_local_addons(config, dirfd(d), de->d_name, full, config->entries + config->n_entries);
+ }
}
return 0;
putchar('\n');
- if (e->id)
- printf(" id: %s\n", e->id);
+ if (e->id) {
+ printf(" id: %s", e->id);
+
+ if (e->id_without_profile && !streq_ptr(e->id, e->id_without_profile))
+ printf(" (without profile: %s)\n", e->id_without_profile);
+ else
+ putchar('\n');
+ }
if (e->path) {
_cleanup_free_ char *text = NULL, *link = NULL;
if (e->tries_done != UINT_MAX)
printf("; %u done\n", e->tries_done);
else
- printf("\n");
+ putchar('\n');
}
if (e->sort_key)