if (r == -ESRCH)
return log_notice_errno(r, "Source file \"%s\" does not carry version information!", from);
if (r < 0)
- return r;
+ return log_error_errno(r, "Failed to get file version of '%s': %m", from);
r = get_file_version(fd_to, &b);
if (r == -ESRCH)
return log_info_errno(r, "Skipping \"%s\", it's owned by another boot loader (no version info found).", to);
if (r < 0)
- return r;
+ return log_error_errno(r, "Failed to get file version of '%s': %m", to);
if (compare_product(a, b) != 0)
return log_info_errno(SYNTHETIC_ERRNO(ESRCH),
"Skipping \"%s\", it's owned by another boot loader.", to);
return log_oom();
fd = xopenat_full(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY|O_NOFOLLOW, XO_REGULAR, /* mode= */ MODE_INVALID);
+ if (fd == -ENOENT)
+ continue;
if (fd < 0)
return log_error_errno(fd, "Failed to open '%s' for reading: %m", z);
r = get_file_version(fd, &v);
if (r == -ESRCH)
continue; /* No version information */
- if (r < 0)
- return r;
+ if (r < 0) {
+ log_warning_errno(r, "Failed to get file version of '%s', skipping: %m", de->d_name);
+ continue;
+ }
if (!startswith(v, "systemd-boot "))
continue;
return log_oom();
LOG_SET_PREFIX(filename);
- fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC);
+ fd = RET_NERRNO(openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC));
+ if (fd == -ENOENT)
+ continue;
if (fd < 0)
- return log_error_errno(errno, "Failed to open file for reading: %m");
+ return log_error_errno(fd, "Failed to open file '%s' for reading: %m", de->d_name);
r = get_file_version(fd, &v);
if (r < 0 && r != -ESRCH)
- return r;
+ return log_error_errno(r, "Failed to get file version of '%s': %m", de->d_name);
if (*previous) { /* Let's output the previous entry now, since now we know that there will be
* one more, and can draw the tree glyph properly. */
/* Do not output this entry immediately, but store what should be printed in a state
* variable, because we only will know the tree glyph to print (branch or final edge) once we
* read one more entry */
- if (r == -ESRCH) /* No systemd-owned file but still interesting to print */
+ if (r == -ESRCH) /* Not systemd-owned file but still interesting to print */
r = asprintf(previous, "%s%s/%s/%s/%s",
ansi_grey(), esp_path, ansi_normal(), path, de->d_name);
else /* if (r >= 0) */
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stdlib.h>
-#include <sys/mman.h>
#include <unistd.h>
#include "alloc-util.h"
#include "errno-util.h"
#include "fileio.h"
#include "log.h"
-#include "stat-util.h"
+#include "pe-binary.h"
#include "string-util.h"
#include "sync-util.h"
#include "virt.h"
return EFI_MACHINE_TYPE_NAME;
}
-/* search for "#### LoaderInfo: systemd-boot 218 ####" string inside the binary */
int get_file_version(int fd, char **ret) {
- struct stat st;
- char *buf;
- const char *s, *e;
- char *marker = NULL;
int r;
assert(fd >= 0);
assert(ret);
- /* Does not reposition file offset (as it uses mmap()) */
+ /* Reads the version marker that systemd-boot/systemd-stub and friends store in their ".sdmagic" PE
+ * section, i.e. a string such as "#### LoaderInfo: systemd-boot 218 ####", and returns the inner
+ * part, e.g. "systemd-boot 218". Does not reposition the file offset (as it uses pread()). */
- if (fstat(fd, &st) < 0)
- return log_error_errno(errno, "Failed to stat EFI binary: %m");
-
- r = stat_verify_regular(&st);
- if (r < 0) {
- log_debug_errno(r, "EFI binary is not a regular file, assuming no version information: %m");
- return -ESRCH;
- }
+ _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
+ _cleanup_free_ PeHeader *pe_header = NULL;
+ r = pe_load_headers(fd, &dos_header, &pe_header);
+ if (r == -EBADMSG)
+ return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "EFI binary is not a valid PE file, assuming no version information.");
+ if (r < 0)
+ return r;
- if (st.st_size < 27 || file_offset_beyond_memory_size(st.st_size))
- return log_debug_errno(SYNTHETIC_ERRNO(ESRCH),
- "EFI binary size too %s: %"PRIi64,
- st.st_size < 27 ? "small" : "large", st.st_size);
+ _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
+ r = pe_load_sections(fd, dos_header, pe_header, §ions);
+ if (r == -EBADMSG)
+ return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "Failed to load PE section table, assuming no version information.");
+ if (r < 0)
+ return r;
- buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
- if (buf == MAP_FAILED)
- return log_error_errno(errno, "Failed to mmap EFI binary: %m");
+ _cleanup_free_ char *sdmagic = NULL;
+ r = pe_read_section_data_by_name(
+ fd,
+ pe_header,
+ sections,
+ ".sdmagic",
+ /* max_size= */ 4U*1024U,
+ (void**) &sdmagic,
+ /* ret_size= */ NULL);
+ if (IN_SET(r, -ENXIO, -EBADMSG))
+ return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "EFI binary has no .sdmagic section, assuming no version information.");
+ if (r < 0)
+ return log_debug_errno(r, "Failed to read .sdmagic section of EFI binary: %m");
- s = mempmem_safe(buf, st.st_size - 8, "#### LoaderInfo: ", 17);
- if (!s) {
- r = log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "EFI binary has no LoaderInfo marker.");
- goto finish;
- }
+ const char *p = startswith(sdmagic, "#### LoaderInfo: ");
+ if (!p)
+ return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "EFI binary .sdmagic section lacks LoaderInfo marker.");
- e = memmem_safe(s, st.st_size - (s - buf), " ####", 5);
- if (!e || e - s < 3) {
- r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "EFI binary has malformed LoaderInfo marker.");
- goto finish;
- }
+ const char *e = endswith(p, " ####");
+ if (!e || e <= p)
+ return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "EFI binary has malformed LoaderInfo marker.");
- marker = strndup(s, e - s);
- if (!marker) {
- r = log_oom();
- goto finish;
- }
+ char *marker = strndup(p, e - p);
+ if (!marker)
+ return log_oom_debug();
log_debug("EFI binary LoaderInfo marker: \"%s\"", marker);
- r = 0;
- *ret = marker;
-finish:
- (void) munmap(buf, st.st_size);
- return r;
+ *ret = TAKE_PTR(marker);
+ return 0;
}
int settle_entry_token(void) {
basic_tests
}
+cleanup_file_version() {
+ if [[ -n "${FILE_VERSION_GARBAGE:-}" ]]; then
+ rm -f "$FILE_VERSION_GARBAGE"
+ unset FILE_VERSION_GARBAGE
+ fi
+ restore_esp
+}
+
+testcase_bootctl_file_version() {
+ # Exercise get_file_version() (src/bootctl/bootctl-util.c), which reads the
+ # version marker that systemd-boot/-stub store in their ".sdmagic" PE
+ # section, e.g. "#### LoaderInfo: systemd-boot 257 ####". 'bootctl status'
+ # surfaces the inner part next to each .efi file it finds in the ESP, e.g.
+ # ".../systemd-bootx64.efi (systemd-boot 257)".
+
+ backup_esp
+ trap cleanup_file_version RETURN ERR
+
+ bootctl install
+
+ local ESP
+ ESP="$(bootctl --print-esp-path)"
+
+ # The freshly installed systemd-boot binary must be listed with the version
+ # extracted from its ".sdmagic" section.
+ bootctl status | grep -E "systemd-boot[a-z0-9]*\.efi \(systemd-boot [^)]+\)" >/dev/null
+
+ # A non-PE/non-systemd file must be handled gracefully (get_file_version()
+ # returns -ESRCH): it is still listed, but without a version annotation, and
+ # 'bootctl status' must not fail.
+ FILE_VERSION_GARBAGE="$ESP/EFI/systemd/not-a-loader.efi"
+ echo "this is not a PE binary" >"$FILE_VERSION_GARBAGE"
+
+ local status
+ status="$(bootctl status)"
+ grep -F "not-a-loader.efi" <<<"$status" >/dev/null
+ (! grep -E "not-a-loader\.efi .*\(" <<<"$status" >/dev/null)
+
+ rm -f "$FILE_VERSION_GARBAGE"
+ unset FILE_VERSION_GARBAGE
+}
+
cleanup_image() (
set +e