1
0
mirror of https://github.com/systemd/systemd synced 2026-04-23 07:24:51 +02:00

Compare commits

..

18 Commits

Author SHA1 Message Date
Jan Janssen
592d576552 boot: Draw unicode separator line for status messages 2022-03-18 19:33:13 +01:00
Zbigniew Jędrzejewski-Szmek
5f1077af1e
Merge pull request #22519 from poettering/boot-order-title-revert
sd-boot: rework boot entry sorting
2022-03-18 19:04:48 +01:00
Yu Watanabe
d0b4f13ef8 strv: use STRV_FOREACH() at two more places 2022-03-18 18:38:08 +01:00
Yu Watanabe
9eb814818d strv: rewrite strv_copy() with cleanup attribute and STRV_FOREACH() 2022-03-18 18:36:48 +01:00
Lennart Poettering
52bb308c13 time-util: add macros around timespec_store() that operates on compund literal allocated timespec struct
This way we can convert usec_t to timespec on-the-fly, without a buffer.

No actual behaviour change just some shortening of code.
2022-03-18 17:13:36 +01:00
Nishal Kulkarni
de0988f9d2 shell-completion: Add completion for oomctl
Added bash and zsh completions for oomctl arguments and commands.

Related To: #22118
2022-03-18 13:41:19 +00:00
Lennart Poettering
7d469b63a1
Merge pull request #22787 from poettering/bootspec-split
shared: split bootspec.c in two
2022-03-18 14:22:07 +01:00
AlexCatze
1a34f913a6
Add HP Elitebook 2760p support (#22766) 2022-03-18 13:45:43 +01:00
Lennart Poettering
b7df2c78ea meson: sort shared source file list again 2022-03-18 12:01:45 +01:00
Lennart Poettering
e94830c0a1 shared: split out ESP/XBOOTLDR search stuff from bootspec.c
The code is quite different from the rest of bootspec.c, with different
deps and stuff. There's even a /***/ line to separate the two parts.
Given how large the file already is, let#s just split it into two.

No code changes, just some splitting out.
2022-03-18 12:01:45 +01:00
Lennart Poettering
f620a36865 update TODO 2022-03-18 11:59:30 +01:00
Lennart Poettering
1fe368e526 test: add test that verifies correct order of boot entries 2022-03-18 11:59:30 +01:00
Lennart Poettering
d23b3bfdd6 kernel-install: automatically generate "sort-key" field
Let's order by IMAGE_ID=/ID= by default.
2022-03-18 11:59:30 +01:00
Lennart Poettering
cf5d9598b6 sd-boot: add comments highlighting type 1 vs. type 2 2022-03-18 11:59:30 +01:00
Lennart Poettering
20ec8f534f sd-boot: make use of new "sort-key" boot loader spec field 2022-03-18 11:59:30 +01:00
Lennart Poettering
1011935785 docs: add new "sort-key" field to boot loader spec
This allows snippet generators to explicitly order entries: any string
can be set as an entry's "sort key". If set, sd-boot will use it to sort
entries on display.

New logic is hence (ignore the boot counting logic)

  sort-key is set → primary sort key: sort-key (lexicographically increasing order)
                  → secondary sort key: machine-id (also increasing order)
                  → tertiary sort key: version (lexicographically decreasing order!)

  sort-key is not set → primary sort key: entry filename (aka id), lexicographically increasing order)

With this scheme we can order OSes by their names from A-Z but then put
within the same OS still the newest version first. This should clean up
the order to match expectations more.

Based on discussions here:

https://github.com/systemd/systemd/pull/22391#issuecomment-1040092633
2022-03-18 11:59:30 +01:00
Lennart Poettering
f65a33269e Revert "boot: Change boot entry sorting"
This reverts commit 9818ec8ea56e14902ac8e548a0f366dbb259f051.
2022-03-18 11:59:30 +01:00
Lennart Poettering
09d4d60360 doc: mention that setfsuid() is a reason why UIDs >= 2147483648 are icky 2022-03-18 11:54:45 +01:00
33 changed files with 1193 additions and 874 deletions

4
TODO
View File

@ -1129,6 +1129,10 @@ Features:
- teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation - teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation
- teach it to copy in unified kernel images and maybe type #1 boot loader spec entries from host - teach it to copy in unified kernel images and maybe type #1 boot loader spec entries from host
- make it operate on loopback files, dissecting enough to find ESP to operate on - make it operate on loopback files, dissecting enough to find ESP to operate on
- bootspec: properly support boot attempt counters when parsing entry file names
* kernel-install:
- optionally, support generating type #2 entries instead of type #1, including signing them
* logind: * logind:
- logind: optionally, ignore idle-hint logic for autosuspend, block suspend as long as a session is around - logind: optionally, ignore idle-hint logic for autosuspend, block suspend as long as a session is around

View File

@ -232,6 +232,16 @@ spaces from its value. The following keys are known:
other installed operating systems. This ID shall be formatted as 32 lower other installed operating systems. This ID shall be formatted as 32 lower
case hexadecimal characters (i.e. without any UUID formatting). This key is case hexadecimal characters (i.e. without any UUID formatting). This key is
optional. Example: `4098b3f648d74c13b1f04ccfba7798e8`. optional. Example: `4098b3f648d74c13b1f04ccfba7798e8`.
* `sort-key` shall contain a short string used for sorting entries on
display. This can be defined freely though should typically be initialized
from `IMAGE_ID=` or `ID=` from `/etc/os-release` of the relevant entry,
possibly suffixed. This field is optional. If set, it is used as primary
sorting key for the entries on display (lexicographically increasing). It
does not have to be unique (and usually is not). If non-unique the the
`machine-id` (lexicographically increasing) and `version` (lexicographically
decreasing, i.e. newest version first) fields described above are used as
secondary/ternary sorting keys. If this field is not set entries are
typically sorted by the `.conf` file name of the entry.
* `linux` refers to the Linux kernel to spawn and shall be a path relative to * `linux` refers to the Linux kernel to spawn and shall be a path relative to
`$BOOT`. It is recommended that every distribution creates a machine id and `$BOOT`. It is recommended that every distribution creates a machine id and
version specific subdirectory below `$BOOT` and places its kernels and version specific subdirectory below `$BOOT` and places its kernels and
@ -269,8 +279,9 @@ key and is otherwise not valid. Here's an example for a complete drop-in file:
# /boot/loader/entries/6a9857a393724b7a981ebb5b8495b9ea-3.8.0-2.fc19.x86_64.conf # /boot/loader/entries/6a9857a393724b7a981ebb5b8495b9ea-3.8.0-2.fc19.x86_64.conf
title Fedora 19 (Rawhide) title Fedora 19 (Rawhide)
version 3.8.0-2.fc19.x86_64 sort-key fedora
machine-id 6a9857a393724b7a981ebb5b8495b9ea machine-id 6a9857a393724b7a981ebb5b8495b9ea
version 3.8.0-2.fc19.x86_64
options root=UUID=6d3376e4-fc93-4509-95ec-a21d68011da2 options root=UUID=6d3376e4-fc93-4509-95ec-a21d68011da2
architecture x64 architecture x64
linux /6a9857a393724b7a981ebb5b8495b9ea/3.8.0-2.fc19.x86_64/linux linux /6a9857a393724b7a981ebb5b8495b9ea/3.8.0-2.fc19.x86_64/linux
@ -358,10 +369,10 @@ simply reads all files `$BOOT/loader/entries/*.conf`, and populates its boot
menu with this. On EFI, it then extends this with any unified kernel images menu with this. On EFI, it then extends this with any unified kernel images
found in `$BOOT/EFI/Linux/*.efi`. It may also add additional entries, for found in `$BOOT/EFI/Linux/*.efi`. It may also add additional entries, for
example a "Reboot into firmware" option. Optionally it may sort the menu based example a "Reboot into firmware" option. Optionally it may sort the menu based
on the `machine-id` and `version` fields, and possibly others. It uses the file on the `sort-key`, `machine-id` and `version` fields, and possibly others. It
name to identify specific items, for example in case it supports storing away uses the file name to identify specific items, for example in case it supports
default entry information somewhere. A boot loader should generally not modify storing away default entry information somewhere. A boot loader should
these files. generally not modify these files.
For "Boot Loader Specification Entries" (Type #1), the _kernel package For "Boot Loader Specification Entries" (Type #1), the _kernel package
installer_ installs the kernel and initrd images to `$BOOT` (it is recommended installer_ installs the kernel and initrd images to `$BOOT` (it is recommended

View File

@ -283,11 +283,12 @@ pre-defined purposes between Linux, generic low-level distributions and
ranges. ranges.
Note that the range 2147483648…4294967294 (i.e. 2^31…2^32-2) should be handled Note that the range 2147483648…4294967294 (i.e. 2^31…2^32-2) should be handled
with care. Various programs (including kernel file systems, see `devpts`) have with care. Various programs (including kernel file systems — see `devpts` — or
trouble with UIDs outside of the signed 32bit range, i.e any UIDs equal to or even kernel syscalls see `setfsuid()`) have trouble with UIDs outside of the
above 2147483648. It is thus strongly recommended to stay away from this range signed 32bit range, i.e any UIDs equal to or above 2147483648. It is thus
in order to avoid complications. This range should be considered reserved for strongly recommended to stay away from this range in order to avoid
future, special purposes. complications. This range should be considered reserved for future, special
purposes.
## Notes on resolvability of user and group names ## Notes on resolvability of user and group names

View File

@ -602,6 +602,18 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnHewlett-Packard*:pnHP*G60*Notebook*PC:*
evdev:atkbd:dmi:bvn*:bvr*:bd*:svnHewlett-Packard*:pn*2570p*:* evdev:atkbd:dmi:bvn*:bvr*:bd*:svnHewlett-Packard*:pn*2570p*:*
KEYBOARD_KEY_f8=wlan # Wireless HW switch button KEYBOARD_KEY_f8=wlan # Wireless HW switch button
# Elitebook 2760p
evdev:atkbd:dmi:bvn*:bvr*:bd*:svnHewlett-Packard*:pn*2760p*:*
KEYBOARD_KEY_89=battery # Fn+F8
KEYBOARD_KEY_f8=unknown # rfkill is also reported by HP Wireless hotkeys
KEYBOARD_KEY_86=volumeup
KEYBOARD_KEY_87=volumedown
KEYBOARD_KEY_92=brightnessdown
KEYBOARD_KEY_97=brightnessup
KEYBOARD_KEY_d8=!f23 # touchpad off
KEYBOARD_KEY_d9=!f22 # touchpad on
KEYBOARD_KEY_b3=unknown # FIXME: Auto brightness
# TX2 # TX2
evdev:atkbd:dmi:bvn*:bvr*:bd*:svnHewlett-Packard*:pn*[tT][xX]2*:* evdev:atkbd:dmi:bvn*:bvr*:bd*:svnHewlett-Packard*:pn*[tT][xX]2*:*
KEYBOARD_KEY_c2=media KEYBOARD_KEY_c2=media

View File

@ -501,10 +501,10 @@
considered 'good' from then on.</para> considered 'good' from then on.</para>
<para>The boot menu takes the 'tries left' counter into account when sorting the menu entries: entries in 'bad' <para>The boot menu takes the 'tries left' counter into account when sorting the menu entries: entries in 'bad'
state are ordered towards the end of the list, and entries in 'good' or 'indeterminate' towards the beginning. state are ordered at the beginning of the list, and entries in 'good' or 'indeterminate' at the end. The user can
The user can freely choose to boot any entry of the menu, including those already marked 'bad'. If the menu entry freely choose to boot any entry of the menu, including those already marked 'bad'. If the menu entry to boot is
to boot is automatically determined, this means that 'good' or 'indeterminate' entries are generally preferred as automatically determined, this means that 'good' or 'indeterminate' entries are generally preferred (as the bottom
boot entries are tried in sort order, and 'bad' entries will only be considered if there are no 'good' or item of the menu is the one booted by default), and 'bad' entries will only be considered if there are no 'good' or
'indeterminate' entries left.</para> 'indeterminate' entries left.</para>
<para>The <citerefentry><refentrytitle>kernel-install</refentrytitle><manvolnum>8</manvolnum></citerefentry> kernel <para>The <citerefentry><refentrytitle>kernel-install</refentrytitle><manvolnum>8</manvolnum></citerefentry> kernel

View File

@ -40,6 +40,7 @@ items = [['busctl', ''],
['loginctl', 'ENABLE_LOGIND'], ['loginctl', 'ENABLE_LOGIND'],
['machinectl', 'ENABLE_MACHINED'], ['machinectl', 'ENABLE_MACHINED'],
['networkctl', 'ENABLE_NETWORKD'], ['networkctl', 'ENABLE_NETWORKD'],
['oomctl', 'ENABLE_OOMD'],
['portablectl', 'ENABLE_PORTABLED'], ['portablectl', 'ENABLE_PORTABLED'],
['resolvectl', 'ENABLE_RESOLVE'], ['resolvectl', 'ENABLE_RESOLVE'],
['systemd-resolve', 'ENABLE_RESOLVE'], ['systemd-resolve', 'ENABLE_RESOLVE'],

View File

@ -0,0 +1,57 @@
# oomctl(1) completion -*- shell-script -*-
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# systemd is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with systemd; If not, see <http://www.gnu.org/licenses/>.
__contains_word () {
local w word=$1; shift
for w in "$@"; do
[[ $w = "$word" ]] && return
done
}
_oomctl() {
local i verb comps
local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]}
local OPTS='-h --help --version --no-pager'
if [[ "$cur" = -* ]]; then
COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") )
return 0
fi
local -A VERBS=(
[STANDALONE]='help dump'
)
for ((i=0; i < COMP_CWORD; i++)); do
if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]}; then
verb=${COMP_WORDS[i]}
break
fi
done
if [[ -z ${verb-} ]]; then
comps=${VERBS[*]}
elif __contains_word "$verb" ${VERBS[STANDALONE]}; then
comps=''
fi
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
return 0
}
complete -F _oomctl oomctl

View File

@ -0,0 +1,28 @@
#compdef oomctl
# SPDX-License-Identifier: LGPL-2.1-or-later
(( $+functions[_oomctl_commands] )) || _oomctl_commands()
{
local -a _oomctl_cmds
_oomctl_cmds=(
"dump:Show the current state of the cgroup(s) and system context(s)"
"help:Prints a short help text and exits."
)
if (( CURRENT == 1 )); then
_describe -t commands 'oomctl command' _oomctl_cmds
else
local curcontext="$curcontext"
cmd="${${_oomctl_cmds[(r)$words[1]:*]%%:*}}"
if (( $+functions[_oomctl_$cmd] )); then
_oomctl_$cmd
else
_message "no more options"
fi
fi
}
_arguments \
{-h,--help}'[Prints a short help text and exits.]' \
'--version[Prints a short version string and exits.]' \
'--no-pager[Do not pipe output into a pager]' \
'*::oomctl command:_oomctl_commands'

View File

@ -34,6 +34,7 @@ items = [['_busctl', ''],
['_loginctl', 'ENABLE_LOGIND'], ['_loginctl', 'ENABLE_LOGIND'],
['_machinectl', 'ENABLE_MACHINED'], ['_machinectl', 'ENABLE_MACHINED'],
['_networkctl', 'ENABLE_NETWORKD'], ['_networkctl', 'ENABLE_NETWORKD'],
['_oomctl', 'ENABLE_OOMD'],
['_systemd-inhibit', 'ENABLE_LOGIND'], ['_systemd-inhibit', 'ENABLE_LOGIND'],
['_resolvectl', 'ENABLE_RESOLVE'], ['_resolvectl', 'ENABLE_RESOLVE'],
['_systemd-tmpfiles', 'ENABLE_TMPFILES'], ['_systemd-tmpfiles', 'ENABLE_TMPFILES'],

View File

@ -159,7 +159,6 @@ int pipe_eof(int fd) {
} }
int ppoll_usec(struct pollfd *fds, size_t nfds, usec_t timeout) { int ppoll_usec(struct pollfd *fds, size_t nfds, usec_t timeout) {
struct timespec ts;
int r; int r;
assert(fds || nfds == 0); assert(fds || nfds == 0);
@ -167,7 +166,7 @@ int ppoll_usec(struct pollfd *fds, size_t nfds, usec_t timeout) {
if (nfds == 0) if (nfds == 0)
return 0; return 0;
r = ppoll(fds, nfds, timeout == USEC_INFINITY ? NULL : timespec_store(&ts, timeout), NULL); r = ppoll(fds, nfds, timeout == USEC_INFINITY ? NULL : TIMESPEC_STORE(timeout), NULL);
if (r < 0) if (r < 0)
return -errno; return -errno;
if (r == 0) if (r == 0)

View File

@ -820,13 +820,12 @@ int wait_for_terminate_with_timeout(pid_t pid, usec_t timeout) {
for (;;) { for (;;) {
usec_t n; usec_t n;
siginfo_t status = {}; siginfo_t status = {};
struct timespec ts;
n = now(CLOCK_MONOTONIC); n = now(CLOCK_MONOTONIC);
if (n >= until) if (n >= until)
break; break;
r = RET_NERRNO(sigtimedwait(&mask, NULL, timespec_store(&ts, until - n))); r = RET_NERRNO(sigtimedwait(&mask, NULL, TIMESPEC_STORE(until - n)));
/* Assuming we woke due to the child exiting. */ /* Assuming we woke due to the child exiting. */
if (waitid(P_PID, pid, &status, WEXITED|WNOHANG) == 0) { if (waitid(P_PID, pid, &status, WEXITED|WNOHANG) == 0) {
if (status.si_pid == pid) { if (status.si_pid == pid) {

View File

@ -70,10 +70,9 @@ char* strv_find_startswith(char * const *l, const char *name) {
} }
char** strv_free(char **l) { char** strv_free(char **l) {
if (!l) char **k;
return NULL;
for (char **k = l; *k; k++) STRV_FOREACH(k, l)
free(*k); free(*k);
return mfree(l); return mfree(l);
@ -89,32 +88,30 @@ char** strv_free_erase(char **l) {
} }
char** strv_copy(char * const *l) { char** strv_copy(char * const *l) {
char **r, **k; _cleanup_strv_free_ char **result = NULL;
char **k, * const *i;
k = r = new(char*, strv_length(l) + 1); result = new(char*, strv_length(l) + 1);
if (!r) if (!result)
return NULL; return NULL;
if (l) k = result;
for (; *l; k++, l++) { STRV_FOREACH(i, l) {
*k = strdup(*l); *k = strdup(*i);
if (!*k) { if (!*k)
strv_free(r); return NULL;
return NULL; k++;
} }
}
*k = NULL; *k = NULL;
return r; return TAKE_PTR(result);
} }
size_t strv_length(char * const *l) { size_t strv_length(char * const *l) {
char * const *i;
size_t n = 0; size_t n = 0;
if (!l) STRV_FOREACH(i, l)
return 0;
for (; *l; l++)
n++; n++;
return n; return n;

View File

@ -115,9 +115,13 @@ nsec_t timespec_load_nsec(const struct timespec *ts) _pure_;
struct timespec* timespec_store(struct timespec *ts, usec_t u); struct timespec* timespec_store(struct timespec *ts, usec_t u);
struct timespec* timespec_store_nsec(struct timespec *ts, nsec_t n); struct timespec* timespec_store_nsec(struct timespec *ts, nsec_t n);
#define TIMESPEC_STORE(u) timespec_store(&(struct timespec) {}, (u))
usec_t timeval_load(const struct timeval *tv) _pure_; usec_t timeval_load(const struct timeval *tv) _pure_;
struct timeval* timeval_store(struct timeval *tv, usec_t u); struct timeval* timeval_store(struct timeval *tv, usec_t u);
#define TIMEVAL_STORE(u) timeval_store(&(struct timeval) {}, (u))
char* format_timestamp_style(char *buf, size_t l, usec_t t, TimestampStyle style) _warn_unused_result_; char* format_timestamp_style(char *buf, size_t l, usec_t t, TimestampStyle style) _warn_unused_result_;
char* format_timestamp_relative(char *buf, size_t l, usec_t t) _warn_unused_result_; char* format_timestamp_relative(char *buf, size_t l, usec_t t) _warn_unused_result_;
char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) _warn_unused_result_; char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) _warn_unused_result_;

View File

@ -8,6 +8,7 @@
#include "efi-loader.h" #include "efi-loader.h"
#include "efivars.h" #include "efivars.h"
#include "fd-util.h" #include "fd-util.h"
#include "find-esp.h"
#include "fs-util.h" #include "fs-util.h"
#include "log.h" #include "log.h"
#include "main-func.h" #include "main-func.h"

View File

@ -24,6 +24,7 @@
#include "escape.h" #include "escape.h"
#include "fd-util.h" #include "fd-util.h"
#include "fileio.h" #include "fileio.h"
#include "find-esp.h"
#include "fs-util.h" #include "fs-util.h"
#include "glyph-util.h" #include "glyph-util.h"
#include "main-func.h" #include "main-func.h"
@ -590,6 +591,8 @@ static int boot_entry_show(
printf(" source: %s\n", link ?: e->path); printf(" source: %s\n", link ?: e->path);
} }
if (e->sort_key)
printf(" sort-key: %s\n", e->sort_key);
if (e->version) if (e->version)
printf(" version: %s\n", e->version); printf(" version: %s\n", e->version);
if (e->machine_id) if (e->machine_id)

View File

@ -53,6 +53,7 @@ typedef struct {
CHAR16 *id; /* The unique identifier for this entry (typically the filename of the file defining the entry) */ 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_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 *title; /* The raw (human readable) title string of the entry (not necessarily unique) */
CHAR16 *sort_key; /* The string to use as primary sory key, usually ID= from os-release, possibly suffixed */
CHAR16 *version; /* The raw (human readable) version string of the entry */ CHAR16 *version; /* The raw (human readable) version string of the entry */
CHAR16 *machine_id; CHAR16 *machine_id;
EFI_HANDLE *device; EFI_HANDLE *device;
@ -428,6 +429,17 @@ static CHAR16 *update_timeout_efivar(UINT32 *t, BOOLEAN inc) {
} }
} }
static BOOLEAN unicode_supported(void) {
static INTN cache = -1;
if (cache < 0)
/* Basic unicode box drawing support is mandated by the spec, but it does
* not hurt to make sure it works. */
cache = !EFI_ERROR(ST->ConOut->TestString(ST->ConOut, (CHAR16 *) L""));
return cache;
}
static void ps_string(const CHAR16 *fmt, const void *value) { static void ps_string(const CHAR16 *fmt, const void *value) {
assert(fmt); assert(fmt);
if (value) if (value)
@ -443,7 +455,11 @@ static BOOLEAN ps_continue(void) {
UINT64 key; UINT64 key;
EFI_STATUS err; EFI_STATUS err;
Print(L"\n--- Press any key to continue, ESC or q to quit. ---\n\n"); if (unicode_supported())
Print(L"\n─── Press any key to continue, ESC or q to quit. ───\n\n");
else
Print(L"\n--- Press any key to continue, ESC or q to quit. ---\n\n");
err = console_key_read(&key, UINT64_MAX); err = console_key_read(&key, UINT64_MAX);
return !EFI_ERROR(err) && !IN_SET(key, KEYPRESS(0, SCAN_ESC, 0), KEYPRESS(0, 0, 'q'), KEYPRESS(0, 0, 'Q')); return !EFI_ERROR(err) && !IN_SET(key, KEYPRESS(0, SCAN_ESC, 0), KEYPRESS(0, 0, 'q'), KEYPRESS(0, 0, 'Q'));
} }
@ -540,6 +556,7 @@ static void print_status(Config *config, CHAR16 *loaded_image_path) {
ps_string(L" id: %s\n", entry->id); ps_string(L" id: %s\n", entry->id);
ps_string(L" title: %s\n", entry->title); ps_string(L" title: %s\n", entry->title);
ps_string(L" title show: %s\n", streq_ptr(entry->title, entry->title_show) ? NULL : entry->title_show); ps_string(L" title show: %s\n", streq_ptr(entry->title, entry->title_show) ? NULL : entry->title_show);
ps_string(L" sort key: %s\n", entry->sort_key);
ps_string(L" version: %s\n", entry->version); ps_string(L" version: %s\n", entry->version);
ps_string(L" machine-id: %s\n", entry->machine_id); ps_string(L" machine-id: %s\n", entry->machine_id);
if (entry->device) if (entry->device)
@ -598,7 +615,7 @@ static BOOLEAN menu_run(
UINTN x_start = 0, y_start = 0, y_status = 0; UINTN x_start = 0, y_start = 0, y_status = 0;
UINTN x_max, y_max; UINTN x_max, y_max;
_cleanup_(strv_freep) CHAR16 **lines = NULL; _cleanup_(strv_freep) CHAR16 **lines = NULL;
_cleanup_freepool_ CHAR16 *clearline = NULL, *status = NULL; _cleanup_freepool_ CHAR16 *clearline = NULL, *separator = NULL, *status = NULL;
UINT32 timeout_efivar_saved = config->timeout_sec_efivar; UINT32 timeout_efivar_saved = config->timeout_sec_efivar;
UINT32 timeout_remain = config->timeout_sec == TIMEOUT_MENU_FORCE ? 0 : config->timeout_sec; UINT32 timeout_remain = config->timeout_sec == TIMEOUT_MENU_FORCE ? 0 : config->timeout_sec;
BOOLEAN exit = FALSE, run = TRUE, firmware_setup = FALSE; BOOLEAN exit = FALSE, run = TRUE, firmware_setup = FALSE;
@ -619,12 +636,11 @@ static BOOLEAN menu_run(
log_error_stall(L"Error switching console mode: %r", err); log_error_stall(L"Error switching console mode: %r", err);
} }
UINTN line_width = 0, entry_padding = 3;
while (!exit) { while (!exit) {
UINT64 key; UINT64 key;
if (new_mode) { if (new_mode) {
UINTN line_width = 0, entry_padding = 3;
console_query_mode(&x_max, &y_max); console_query_mode(&x_max, &y_max);
/* account for padding+status */ /* account for padding+status */
@ -643,6 +659,7 @@ static BOOLEAN menu_run(
idx_last = idx_first + visible_max - 1; idx_last = idx_first + visible_max - 1;
/* length of the longest entry */ /* length of the longest entry */
line_width = 0;
for (UINTN i = 0; i < config->entry_count; i++) for (UINTN i = 0; i < config->entry_count; i++)
line_width = MAX(line_width, StrLen(config->entries[i]->title_show)); line_width = MAX(line_width, StrLen(config->entries[i]->title_show));
line_width = MIN(line_width + 2 * entry_padding, x_max); line_width = MIN(line_width + 2 * entry_padding, x_max);
@ -655,10 +672,11 @@ static BOOLEAN menu_run(
y_start = 0; y_start = 0;
/* Put status line after the entry list, but give it some breathing room. */ /* Put status line after the entry list, but give it some breathing room. */
y_status = MIN(y_start + MIN(visible_max, config->entry_count) + 4, y_max - 1); y_status = MIN(y_start + MIN(visible_max, config->entry_count) + 1, y_max - 1);
lines = strv_free(lines); lines = strv_free(lines);
clearline = mfree(clearline); clearline = mfree(clearline);
separator = mfree(separator);
/* menu entries title lines */ /* menu entries title lines */
lines = xnew(CHAR16*, config->entry_count + 1); lines = xnew(CHAR16*, config->entry_count + 1);
@ -682,9 +700,13 @@ static BOOLEAN menu_run(
lines[config->entry_count] = NULL; lines[config->entry_count] = NULL;
clearline = xnew(CHAR16, x_max + 1); clearline = xnew(CHAR16, x_max + 1);
for (UINTN i = 0; i < x_max; i++) separator = xnew(CHAR16, x_max + 1);
for (UINTN i = 0; i < x_max; i++) {
clearline[i] = ' '; clearline[i] = ' ';
separator[i] = unicode_supported() ? L'' : L'-';
}
clearline[x_max] = 0; clearline[x_max] = 0;
separator[x_max] = 0;
new_mode = FALSE; new_mode = FALSE;
clear = TRUE; clear = TRUE;
@ -704,16 +726,16 @@ static BOOLEAN menu_run(
if (i == config->idx_default_efivar) if (i == config->idx_default_efivar)
print_at(x_start, y_start + i - idx_first, print_at(x_start, y_start + i - idx_first,
(i == idx_highlight) ? COLOR_HIGHLIGHT : COLOR_ENTRY, (i == idx_highlight) ? COLOR_HIGHLIGHT : COLOR_ENTRY,
(CHAR16*) L"=>"); (CHAR16*) (unicode_supported() ? L"" : L"=>"));
} }
refresh = FALSE; refresh = FALSE;
} else if (highlight) { } else if (highlight) {
print_at(x_start, y_start + idx_highlight_prev - idx_first, COLOR_ENTRY, lines[idx_highlight_prev]); print_at(x_start, y_start + idx_highlight_prev - idx_first, COLOR_ENTRY, lines[idx_highlight_prev]);
print_at(x_start, y_start + idx_highlight - idx_first, COLOR_HIGHLIGHT, lines[idx_highlight]); print_at(x_start, y_start + idx_highlight - idx_first, COLOR_HIGHLIGHT, lines[idx_highlight]);
if (idx_highlight_prev == config->idx_default_efivar) if (idx_highlight_prev == config->idx_default_efivar)
print_at(x_start , y_start + idx_highlight_prev - idx_first, COLOR_ENTRY, (CHAR16*) L"=>"); print_at(x_start , y_start + idx_highlight_prev - idx_first, COLOR_ENTRY, (CHAR16*) (unicode_supported() ? L"" : L"=>"));
if (idx_highlight == config->idx_default_efivar) if (idx_highlight == config->idx_default_efivar)
print_at(x_start, y_start + idx_highlight - idx_first, COLOR_HIGHLIGHT, (CHAR16*) L"=>"); print_at(x_start, y_start + idx_highlight - idx_first, COLOR_HIGHLIGHT, (CHAR16*) (unicode_supported() ? L"" : L"=>"));
highlight = FALSE; highlight = FALSE;
} }
@ -722,20 +744,24 @@ static BOOLEAN menu_run(
status = xpool_print(L"Boot in %u s.", timeout_remain); status = xpool_print(L"Boot in %u s.", timeout_remain);
} }
/* print status at last line of screen */
if (status) { if (status) {
UINTN len; /* If we draw the last char of the last line, the screen will scroll and break our
UINTN x; * input. Therefore, draw one less character then we could for the status message.
* Note that the same does not apply for the separator line as it will never be drawn
/* center line */ * on the last line. */
len = StrLen(status); UINTN len = StrnLen(status, x_max - 1);
if (len < x_max) UINTN x = (x_max - len) / 2;
x = (x_max - len) / 2; status[len] = '\0';
else print_at(0, y_status, COLOR_NORMAL, clearline + x_max - x);
x = 0;
print_at(0, y_status, COLOR_NORMAL, clearline + (x_max - x));
ST->ConOut->OutputString(ST->ConOut, status); ST->ConOut->OutputString(ST->ConOut, status);
ST->ConOut->OutputString(ST->ConOut, clearline + 1 + x + len); ST->ConOut->OutputString(ST->ConOut, clearline + 1 + x + len);
len = MIN(MAX(len, line_width) + 2 * entry_padding, x_max);
x = (x_max - len) / 2;
print_at(x, y_status - 1, COLOR_NORMAL, separator + x_max - len);
} else {
print_at(0, y_status - 1, COLOR_NORMAL, clearline);
print_at(0, y_status, COLOR_NORMAL, clearline + 1); /* See comment above. */
} }
/* Beep several times so that the selected entry can be distinguished. */ /* Beep several times so that the selected entry can be distinguished. */
@ -766,11 +792,7 @@ static BOOLEAN menu_run(
timeout_remain = 0; timeout_remain = 0;
/* clear status after keystroke */ /* clear status after keystroke */
if (status) { status = mfree(status);
FreePool(status);
status = NULL;
print_at(0, y_status, COLOR_NORMAL, clearline + 1);
}
idx_highlight_prev = idx_highlight; idx_highlight_prev = idx_highlight;
@ -1026,6 +1048,7 @@ static void config_entry_free(ConfigEntry *entry) {
FreePool(entry->id); FreePool(entry->id);
FreePool(entry->title_show); FreePool(entry->title_show);
FreePool(entry->title); FreePool(entry->title);
FreePool(entry->sort_key);
FreePool(entry->version); FreePool(entry->version);
FreePool(entry->machine_id); FreePool(entry->machine_id);
FreePool(entry->loader); FreePool(entry->loader);
@ -1427,6 +1450,12 @@ static void config_entry_add_from_file(
continue; continue;
} }
if (strcmpa((CHAR8 *)"sort-key", key) == 0) {
FreePool(entry->sort_key);
entry->sort_key = xstra_to_str(value);
continue;
}
if (strcmpa((CHAR8 *)"version", key) == 0) { if (strcmpa((CHAR8 *)"version", key) == 0) {
FreePool(entry->version); FreePool(entry->version);
entry->version = xstra_to_str(value); entry->version = xstra_to_str(value);
@ -1531,6 +1560,7 @@ static void config_entry_add_from_file(
entry->device = device; entry->device = device;
entry->id = xstrdup(file); entry->id = xstrdup(file);
StrLwr(entry->id);
config_add_entry(config, entry); config_add_entry(config, entry);
@ -1609,6 +1639,8 @@ static void config_load_entries(
assert(device); assert(device);
assert(root_dir); assert(root_dir);
/* Adds Boot Loader Type #1 entries (i.e. /loader/entries/….conf) */
err = open_directory(root_dir, L"\\loader\\entries", &entries_dir); err = open_directory(root_dir, L"\\loader\\entries", &entries_dir);
if (EFI_ERROR(err)) if (EFI_ERROR(err))
return; return;
@ -1642,24 +1674,40 @@ static INTN config_entry_compare(const ConfigEntry *a, const ConfigEntry *b) {
assert(a); assert(a);
assert(b); assert(b);
/* Order entries that have no tries left towards the end of the list. They have /* Order entries that have no tries left to the end of the list */
* proven to be bad and should not be selected automatically. */
if (a->tries_left != 0 && b->tries_left == 0)
return -1;
if (a->tries_left == 0 && b->tries_left != 0) if (a->tries_left == 0 && b->tries_left != 0)
return 1; return 1;
if (a->tries_left != 0 && b->tries_left == 0)
return -1;
r = strcasecmp_ptr(a->title ?: a->id, b->title ?: b->id); /* If there's a sort key defined for *both* entries, then we do new-style ordering, i.e. by
if (r != 0) * sort-key/machine-id/version, with a final fallback to id. If there's no sort key for either, we do
* old-style ordering, i.e. by id only. If one has sort key and the other does not, we put new-style
* before old-style. */
r = CMP(!a->sort_key, !b->sort_key);
if (r != 0) /* one is old-style, one new-style */
return r; return r;
if (a->sort_key && b->sort_key) {
/* Sort by machine id now so that different installations don't interleave their versions. */ r = strcmp(a->sort_key, b->sort_key);
r = strcasecmp_ptr(a->machine_id, b->machine_id); if (r != 0)
if (r != 0) return r;
return r;
/* Reverse version comparison order so that higher versions are preferred. */ /* If multiple installations of the same OS are around, group by machine ID */
r = strverscmp_improved(b->version, a->version); r = strcmp_ptr(a->machine_id, b->machine_id);
if (r != 0)
return r;
/* If the sort key was defined, then order by version now (downwards, putting the newest first) */
r = -strverscmp_improved(a->version, b->version);
if (r != 0)
return r;
}
/* Now order by ID (the version is likely part of the ID, thus note that this might put the oldest
* version last, not first, i.e. specifying a sort key explicitly is thus generally preferable, to
* take benefit of the explicit sorting above.) */
r = strverscmp_improved(a->id, b->id);
if (r != 0) if (r != 0)
return r; return r;
@ -1668,19 +1716,18 @@ static INTN config_entry_compare(const ConfigEntry *a, const ConfigEntry *b) {
return 0; return 0;
/* If both items have boot counting, and otherwise are identical, put the entry with more tries left first */ /* If both items have boot counting, and otherwise are identical, put the entry with more tries left first */
if (a->tries_left > b->tries_left)
return -1;
if (a->tries_left < b->tries_left) if (a->tries_left < b->tries_left)
return 1; return 1;
if (a->tries_left > b->tries_left)
return -1;
/* If they have the same number of tries left, then let the one win which was tried fewer times so far */ /* If they have the same number of tries left, then let the one win which was tried fewer times so far */
if (a->tries_done < b->tries_done)
return -1;
if (a->tries_done > b->tries_done) if (a->tries_done > b->tries_done)
return 1; return 1;
if (a->tries_done < b->tries_done)
return -1;
/* As a last resort, use the id (file name). */ return 0;
return strverscmp_improved(a->id, b->id);
} }
static UINTN config_entry_find(Config *config, const CHAR16 *needle) { static UINTN config_entry_find(Config *config, const CHAR16 *needle) {
@ -1724,7 +1771,7 @@ static void config_default_entry_select(Config *config) {
return; return;
} }
/* Select the first suitable entry. */ /* select the first suitable entry */
for (i = 0; i < config->entry_count; i++) { for (i = 0; i < config->entry_count; i++) {
if (config->entries[i]->type == LOADER_AUTO || config->entries[i]->call) if (config->entries[i]->type == LOADER_AUTO || config->entries[i]->call)
continue; continue;
@ -1853,6 +1900,7 @@ static ConfigEntry *config_entry_add_loader(
CHAR16 key, CHAR16 key,
const CHAR16 *title, const CHAR16 *title,
const CHAR16 *loader, const CHAR16 *loader,
const CHAR16 *sort_key,
const CHAR16 *version) { const CHAR16 *version) {
ConfigEntry *entry; ConfigEntry *entry;
@ -1871,11 +1919,14 @@ static ConfigEntry *config_entry_add_loader(
.device = device, .device = device,
.loader = xstrdup(loader), .loader = xstrdup(loader),
.id = xstrdup(id), .id = xstrdup(id),
.sort_key = xstrdup(sort_key),
.key = key, .key = key,
.tries_done = UINTN_MAX, .tries_done = UINTN_MAX,
.tries_left = UINTN_MAX, .tries_left = UINTN_MAX,
}; };
StrLwr(entry->id);
config_add_entry(config, entry); config_add_entry(config, entry);
return entry; return entry;
} }
@ -1943,7 +1994,7 @@ static ConfigEntry *config_entry_add_loader_auto(
if (EFI_ERROR(err)) if (EFI_ERROR(err))
return NULL; return NULL;
return config_entry_add_loader(config, device, LOADER_AUTO, id, key, title, loader, NULL); return config_entry_add_loader(config, device, LOADER_AUTO, id, key, title, loader, NULL, NULL);
} }
static void config_entry_add_osx(Config *config) { static void config_entry_add_osx(Config *config) {
@ -2097,6 +2148,8 @@ static void config_entry_add_linux(
UINTN f_size = 0; UINTN f_size = 0;
EFI_STATUS err; EFI_STATUS err;
/* Adds Boot Loader Type #2 entries (i.e. /EFI/Linux/….efi) */
assert(config); assert(config);
assert(device); assert(device);
assert(root_dir); assert(root_dir);
@ -2121,7 +2174,7 @@ static void config_entry_add_linux(
_cleanup_freepool_ CHAR16 *os_pretty_name = NULL, *os_image_id = NULL, *os_name = NULL, *os_id = 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, *os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL,
*path = NULL; *path = NULL;
const CHAR16 *good_name, *good_version; const CHAR16 *good_name, *good_version, *good_sort_key;
_cleanup_freepool_ CHAR8 *content = NULL; _cleanup_freepool_ CHAR8 *content = NULL;
UINTN offs[_SECTION_MAX] = {}; UINTN offs[_SECTION_MAX] = {};
UINTN szs[_SECTION_MAX] = {}; UINTN szs[_SECTION_MAX] = {};
@ -2202,7 +2255,7 @@ static void config_entry_add_linux(
} }
} }
if (!bootspec_pick_name_version( if (!bootspec_pick_name_version_sort_key(
os_pretty_name, os_pretty_name,
os_image_id, os_image_id,
os_name, os_name,
@ -2212,7 +2265,8 @@ static void config_entry_add_linux(
os_version_id, os_version_id,
os_build_id, os_build_id,
&good_name, &good_name,
&good_version)) &good_version,
&good_sort_key))
continue; continue;
path = xpool_print(L"\\EFI\\Linux\\%s", f->FileName); path = xpool_print(L"\\EFI\\Linux\\%s", f->FileName);
@ -2220,10 +2274,11 @@ static void config_entry_add_linux(
config, config,
device, device,
LOADER_UNIFIED_LINUX, LOADER_UNIFIED_LINUX,
f->FileName, /* id= */ f->FileName,
/* key= */ 'l', /* key= */ 'l',
good_name, /* title= */ good_name,
path, /* loader= */ path,
/* sort_key= */ good_sort_key,
good_version); good_version);
config_entry_parse_tries(entry, L"\\EFI\\Linux", f->FileName, L".efi"); config_entry_parse_tries(entry, L"\\EFI\\Linux", f->FileName, L".efi");
@ -2449,14 +2504,12 @@ static void config_load_all_entries(
/* Similar, but on any XBOOTLDR partition */ /* Similar, but on any XBOOTLDR partition */
config_load_xbootldr(config, loaded_image->DeviceHandle); config_load_xbootldr(config, loaded_image->DeviceHandle);
/* Add these now, so they get sorted with the rest. */
config_entry_add_osx(config);
config_entry_add_windows(config, loaded_image->DeviceHandle, root_dir);
/* sort entries after version number */ /* sort entries after version number */
sort_pointer_array((void **) config->entries, config->entry_count, (compare_pointer_func_t) config_entry_compare); sort_pointer_array((void **) config->entries, config->entry_count, (compare_pointer_func_t) config_entry_compare);
/* if we find some well-known loaders, add them to the end of the list */ /* if we find some well-known loaders, add them to the end of the list */
config_entry_add_osx(config);
config_entry_add_windows(config, loaded_image->DeviceHandle, root_dir);
config_entry_add_loader_auto(config, loaded_image->DeviceHandle, root_dir, NULL, config_entry_add_loader_auto(config, loaded_image->DeviceHandle, root_dir, NULL,
L"auto-efi-shell", 's', L"EFI Shell", L"\\shell" EFI_MACHINE_TYPE_NAME ".efi"); L"auto-efi-shell", 's', L"EFI Shell", L"\\shell" EFI_MACHINE_TYPE_NAME ".efi");
config_entry_add_loader_auto(config, loaded_image->DeviceHandle, root_dir, loaded_image_path, config_entry_add_loader_auto(config, loaded_image->DeviceHandle, root_dir, loaded_image_path,

View File

@ -1571,8 +1571,6 @@ static void initialize_clock(void) {
} }
static void apply_clock_update(void) { static void apply_clock_update(void) {
struct timespec ts;
/* This is called later than initialize_clock(), i.e. after we parsed configuration files/kernel /* This is called later than initialize_clock(), i.e. after we parsed configuration files/kernel
* command line and such. */ * command line and such. */
@ -1582,7 +1580,7 @@ static void apply_clock_update(void) {
if (getpid_cached() != 1) if (getpid_cached() != 1)
return; return;
if (clock_settime(CLOCK_REALTIME, timespec_store(&ts, arg_clock_usec)) < 0) if (clock_settime(CLOCK_REALTIME, TIMESPEC_STORE(arg_clock_usec)) < 0)
log_error_errno(errno, "Failed to set system clock to time specified on kernel command line: %m"); log_error_errno(errno, "Failed to set system clock to time specified on kernel command line: %m");
else else
log_info("Set system clock to %s, as specified on the kernel command line.", log_info("Set system clock to %s, as specified on the kernel command line.",

View File

@ -2,7 +2,7 @@
#include "bootspec-fundamental.h" #include "bootspec-fundamental.h"
sd_bool bootspec_pick_name_version( sd_bool bootspec_pick_name_version_sort_key(
const sd_char *os_pretty_name, const sd_char *os_pretty_name,
const sd_char *os_image_id, const sd_char *os_image_id,
const sd_char *os_name, const sd_char *os_name,
@ -12,12 +12,14 @@ sd_bool bootspec_pick_name_version(
const sd_char *os_version_id, const sd_char *os_version_id,
const sd_char *os_build_id, const sd_char *os_build_id,
const sd_char **ret_name, const sd_char **ret_name,
const sd_char **ret_version) { const sd_char **ret_version,
const sd_char **ret_sort_key) {
const sd_char *good_name, *good_version; const sd_char *good_name, *good_version, *good_sort_key;
/* Find the best human readable title and version string for a boot entry (using the os-release(5) /* Find the best human readable title, version string and sort key for a boot entry (using the
* fields). Precise is preferred over vague, and human readable over machine readable. Thus: * 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, * 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 * and should already contain both a nice description and a version indication (if that concept
@ -37,11 +39,12 @@ sd_bool bootspec_pick_name_version(
* which case the version is shown too. * which case the version is shown too.
* *
* Note that name/version determined here are used only for display purposes. Boot entry preference * 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" * sorting (i.e. algorithmic ordering of boot entries) is done based on the order of the sort key (if
* string (i.e. not on os-release(5) data). */ * defined) or entry "id" string (i.e. entry file name) otherwise. */
good_name = os_pretty_name ?: (os_image_id ?: (os_name ?: os_id)); 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)); good_version = os_image_version ?: (os_version ?: (os_version_id ? : os_build_id));
good_sort_key = os_image_id ?: os_id;
if (!good_name || !good_version) if (!good_name || !good_version)
return sd_false; return sd_false;
@ -52,5 +55,8 @@ sd_bool bootspec_pick_name_version(
if (ret_version) if (ret_version)
*ret_version = good_version; *ret_version = good_version;
if (ret_sort_key)
*ret_sort_key = good_sort_key;
return sd_true; return sd_true;
} }

View File

@ -3,7 +3,7 @@
#include "types-fundamental.h" #include "types-fundamental.h"
sd_bool bootspec_pick_name_version( sd_bool bootspec_pick_name_version_sort_key(
const sd_char *os_pretty_name, const sd_char *os_pretty_name,
const sd_char *os_image_id, const sd_char *os_image_id,
const sd_char *os_name, const sd_char *os_name,
@ -13,4 +13,5 @@ sd_bool bootspec_pick_name_version(
const sd_char *os_version_id, const sd_char *os_version_id,
const sd_char *os_build_id, const sd_char *os_build_id,
const sd_char **ret_name, const sd_char **ret_name,
const sd_char **ret_version); const sd_char **ret_version,
const sd_char **ret_sort_key);

View File

@ -62,6 +62,9 @@ fi
[ -n "$PRETTY_NAME" ] || PRETTY_NAME="Linux $KERNEL_VERSION" [ -n "$PRETTY_NAME" ] || PRETTY_NAME="Linux $KERNEL_VERSION"
SORT_KEY="$IMAGE_ID"
[ -z "$SORT_KEY" ] && SORT_KEY="$ID"
if [ -r /etc/kernel/cmdline ]; then if [ -r /etc/kernel/cmdline ]; then
BOOT_OPTIONS="$(tr -s "$IFS" ' ' </etc/kernel/cmdline)" BOOT_OPTIONS="$(tr -s "$IFS" ' ' </etc/kernel/cmdline)"
elif [ -r /usr/lib/kernel/cmdline ]; then elif [ -r /usr/lib/kernel/cmdline ]; then
@ -130,6 +133,7 @@ mkdir -p "${LOADER_ENTRY%/*}" || {
# See similar logic above for the systemd.machine_id= kernel command line option # See similar logic above for the systemd.machine_id= kernel command line option
echo "machine-id $MACHINE_ID" echo "machine-id $MACHINE_ID"
fi fi
[ -n "$SORT_KEY" ] && echo "sort-key $SORT_KEY"
echo "options $BOOT_OPTIONS" echo "options $BOOT_OPTIONS"
echo "linux $ENTRY_DIR/linux" echo "linux $ENTRY_DIR/linux"

View File

@ -3906,6 +3906,7 @@ static int epoll_wait_usec(
int msec; int msec;
#if 0 #if 0
static bool epoll_pwait2_absent = false; static bool epoll_pwait2_absent = false;
int r;
/* A wrapper that uses epoll_pwait2() if available, and falls back to epoll_wait() if not. /* A wrapper that uses epoll_pwait2() if available, and falls back to epoll_wait() if not.
* *
@ -3914,12 +3915,10 @@ static int epoll_wait_usec(
* https://github.com/systemd/systemd/issues/19052. */ * https://github.com/systemd/systemd/issues/19052. */
if (!epoll_pwait2_absent && timeout != USEC_INFINITY) { if (!epoll_pwait2_absent && timeout != USEC_INFINITY) {
struct timespec ts;
r = epoll_pwait2(fd, r = epoll_pwait2(fd,
events, events,
maxevents, maxevents,
timespec_store(&ts, timeout), TIMESPEC_STORE(timeout),
NULL); NULL);
if (r >= 0) if (r >= 0)
return r; return r;

View File

@ -142,10 +142,8 @@ int stub_pid1(sd_id128_t uuid) {
if (quit_usec == USEC_INFINITY) if (quit_usec == USEC_INFINITY)
r = sigwaitinfo(&waitmask, &si); r = sigwaitinfo(&waitmask, &si);
else { else
struct timespec ts; r = sigtimedwait(&waitmask, &si, TIMESPEC_STORE(quit_usec - current_usec));
r = sigtimedwait(&waitmask, &si, timespec_store(&ts, quit_usec - current_usec));
}
if (r < 0) { if (r < 0) {
if (errno == EINTR) /* strace -p attach can result in EINTR, let's handle this nicely. */ if (errno == EINTR) /* strace -p attach can result in EINTR, let's handle this nicely. */
continue; continue;

View File

@ -1,40 +1,22 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stdio.h>
#include <linux/magic.h>
#include <unistd.h> #include <unistd.h>
#include "sd-device.h"
#include "sd-id128.h"
#include "alloc-util.h"
#include "blkid-util.h"
#include "bootspec-fundamental.h"
#include "bootspec.h" #include "bootspec.h"
#include "bootspec-fundamental.h"
#include "conf-files.h" #include "conf-files.h"
#include "def.h"
#include "device-nodes.h"
#include "dirent-util.h" #include "dirent-util.h"
#include "efivars.h"
#include "efi-loader.h" #include "efi-loader.h"
#include "env-file.h" #include "env-file.h"
#include "env-util.h"
#include "errno-util.h"
#include "fd-util.h" #include "fd-util.h"
#include "fileio.h" #include "fileio.h"
#include "gpt.h" #include "find-esp.h"
#include "id128-util.h"
#include "parse-util.h"
#include "path-util.h" #include "path-util.h"
#include "pe-header.h" #include "pe-header.h"
#include "sort-util.h" #include "sort-util.h"
#include "stat-util.h" #include "stat-util.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h" #include "strv.h"
#include "unaligned.h" #include "unaligned.h"
#include "util.h"
#include "virt.h"
static void boot_entry_free(BootEntry *entry) { static void boot_entry_free(BootEntry *entry) {
assert(entry); assert(entry);
@ -45,6 +27,7 @@ static void boot_entry_free(BootEntry *entry) {
free(entry->root); free(entry->root);
free(entry->title); free(entry->title);
free(entry->show_title); free(entry->show_title);
free(entry->sort_key);
free(entry->version); free(entry->version);
free(entry->machine_id); free(entry->machine_id);
free(entry->architecture); free(entry->architecture);
@ -131,6 +114,8 @@ static int boot_entry_load(
if (streq(field, "title")) if (streq(field, "title"))
r = free_and_strdup(&tmp.title, p); r = free_and_strdup(&tmp.title, p);
else if (streq(field, "sort-key"))
r = free_and_strdup(&tmp.sort_key, p);
else if (streq(field, "version")) else if (streq(field, "version"))
r = free_and_strdup(&tmp.version, p); r = free_and_strdup(&tmp.version, p);
else if (streq(field, "machine-id")) else if (streq(field, "machine-id"))
@ -263,6 +248,28 @@ static int boot_loader_read_conf(const char *path, BootConfig *config) {
} }
static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
int r;
assert(a);
assert(b);
r = CMP(!a->sort_key, !b->sort_key);
if (r != 0)
return r;
if (a->sort_key && b->sort_key) {
r = strcmp(a->sort_key, b->sort_key);
if (r != 0)
return r;
r = strcmp_ptr(a->machine_id, b->machine_id);
if (r != 0)
return r;
r = -strverscmp_improved(a->version, b->version);
if (r != 0)
return r;
}
return strverscmp_improved(a->id, b->id); return strverscmp_improved(a->id, b->id);
} }
@ -311,7 +318,7 @@ static int boot_entry_load_unified(
_cleanup_(boot_entry_free) BootEntry tmp = { _cleanup_(boot_entry_free) BootEntry tmp = {
.type = BOOT_ENTRY_UNIFIED, .type = BOOT_ENTRY_UNIFIED,
}; };
const char *k, *good_name, *good_version; const char *k, *good_name, *good_version, *good_sort_key;
_cleanup_fclose_ FILE *f = NULL; _cleanup_fclose_ FILE *f = NULL;
int r; int r;
@ -339,7 +346,7 @@ static int boot_entry_load_unified(
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to parse os-release data from unified kernel image %s: %m", path); return log_error_errno(r, "Failed to parse os-release data from unified kernel image %s: %m", path);
if (!bootspec_pick_name_version( if (!bootspec_pick_name_version_sort_key(
os_pretty_name, os_pretty_name,
os_image_id, os_image_id,
os_name, os_name,
@ -349,7 +356,8 @@ static int boot_entry_load_unified(
os_version_id, os_version_id,
os_build_id, os_build_id,
&good_name, &good_name,
&good_version)) &good_version,
&good_sort_key))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing fields in os-release data from unified kernel image %s, refusing.", path); return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing fields in os-release data from unified kernel image %s, refusing.", path);
r = path_extract_filename(path, &tmp.id); r = path_extract_filename(path, &tmp.id);
@ -387,6 +395,10 @@ static int boot_entry_load_unified(
if (!tmp.title) if (!tmp.title)
return log_oom(); return log_oom();
tmp.sort_key = strdup(good_sort_key);
if (!tmp.sort_key)
return log_oom();
tmp.version = strdup(good_version); tmp.version = strdup(good_version);
if (!tmp.version) if (!tmp.version)
return log_oom(); return log_oom();
@ -634,6 +646,19 @@ static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
return 0; return 0;
} }
static int boot_config_find(const BootConfig *config, const char *id) {
assert(config);
if (!id)
return -1;
for (size_t i = 0; i < config->n_entries; i++)
if (fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0)
return i;
return -1;
}
static int boot_entries_select_default(const BootConfig *config) { static int boot_entries_select_default(const BootConfig *config) {
int i; int i;
@ -645,47 +670,45 @@ static int boot_entries_select_default(const BootConfig *config) {
return -1; /* -1 means "no default" */ return -1; /* -1 means "no default" */
} }
if (config->entry_oneshot) if (config->entry_oneshot) {
for (i = config->n_entries - 1; i >= 0; i--) i = boot_config_find(config, config->entry_oneshot);
if (streq(config->entry_oneshot, config->entries[i].id)) { if (i >= 0) {
log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot", log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
config->entries[i].id); config->entries[i].id);
return i; return i;
} }
}
if (config->entry_default) if (config->entry_default) {
for (i = config->n_entries - 1; i >= 0; i--) i = boot_config_find(config, config->entry_default);
if (streq(config->entry_default, config->entries[i].id)) { if (i >= 0) {
log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault", log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
config->entries[i].id); config->entries[i].id);
return i; return i;
} }
}
if (config->default_pattern) if (config->default_pattern) {
for (i = config->n_entries - 1; i >= 0; i--) i = boot_config_find(config, config->default_pattern);
if (fnmatch(config->default_pattern, config->entries[i].id, FNM_CASEFOLD) == 0) { if (i >= 0) {
log_debug("Found default: id \"%s\" is matched by pattern \"%s\"", log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
config->entries[i].id, config->default_pattern); config->entries[i].id, config->default_pattern);
return i; return i;
} }
}
log_debug("Found default: last entry \"%s\"", config->entries[config->n_entries - 1].id); log_debug("Found default: first entry \"%s\"", config->entries[0].id);
return config->n_entries - 1; return 0;
} }
static int boot_entries_select_selected(const BootConfig *config) { static int boot_entries_select_selected(const BootConfig *config) {
assert(config); assert(config);
assert(config->entries || config->n_entries == 0); assert(config->entries || config->n_entries == 0);
if (!config->entry_selected || config->n_entries == 0) if (!config->entry_selected || config->n_entries == 0)
return -1; return -1;
for (int i = config->n_entries - 1; i >= 0; i--) return boot_config_find(config, config->entry_selected);
if (streq(config->entry_selected, config->entries[i].id))
return i;
return -1;
} }
static int boot_load_efi_entry_pointers(BootConfig *config) { static int boot_load_efi_entry_pointers(BootConfig *config) {
@ -884,696 +907,3 @@ int boot_entries_augment_from_loader(
return 0; return 0;
} }
/********************************************************************************/
static int verify_esp_blkid(
dev_t devid,
bool searching,
uint32_t *ret_part,
uint64_t *ret_pstart,
uint64_t *ret_psize,
sd_id128_t *ret_uuid) {
sd_id128_t uuid = SD_ID128_NULL;
uint64_t pstart = 0, psize = 0;
uint32_t part = 0;
#if HAVE_BLKID
_cleanup_(blkid_free_probep) blkid_probe b = NULL;
_cleanup_free_ char *node = NULL;
const char *v;
int r;
r = device_path_make_major_minor(S_IFBLK, devid, &node);
if (r < 0)
return log_error_errno(r, "Failed to format major/minor device path: %m");
errno = 0;
b = blkid_new_probe_from_filename(node);
if (!b)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(ENOMEM), "Failed to open file system \"%s\": %m", node);
blkid_probe_enable_superblocks(b, 1);
blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE);
blkid_probe_enable_partitions(b, 1);
blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
errno = 0;
r = blkid_do_safeprobe(b);
if (r == -2)
return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" is ambiguous.", node);
else if (r == 1)
return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" does not contain a label.", node);
else if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe file system \"%s\": %m", node);
r = blkid_probe_lookup_value(b, "TYPE", &v, NULL);
if (r != 0)
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"No filesystem found on \"%s\": %m", node);
if (!streq(v, "vfat"))
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"File system \"%s\" is not FAT.", node);
r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL);
if (r != 0)
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"File system \"%s\" is not located on a partitioned block device.", node);
if (!streq(v, "gpt"))
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"File system \"%s\" is not on a GPT partition table.", node);
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
if (r != 0)
return log_error_errno(errno ?: EIO, "Failed to probe partition type UUID of \"%s\": %m", node);
if (id128_equal_string(v, GPT_ESP) <= 0)
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"File system \"%s\" has wrong type for an EFI System Partition (ESP).", node);
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL);
if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition entry UUID of \"%s\": %m", node);
r = sd_id128_from_string(v, &uuid);
if (r < 0)
return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v);
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_NUMBER", &v, NULL);
if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition number of \"%s\": %m", node);
r = safe_atou32(v, &part);
if (r < 0)
return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field.");
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL);
if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition offset of \"%s\": %m", node);
r = safe_atou64(v, &pstart);
if (r < 0)
return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field.");
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL);
if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition size of \"%s\": %m", node);
r = safe_atou64(v, &psize);
if (r < 0)
return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field.");
#endif
if (ret_part)
*ret_part = part;
if (ret_pstart)
*ret_pstart = pstart;
if (ret_psize)
*ret_psize = psize;
if (ret_uuid)
*ret_uuid = uuid;
return 0;
}
static int verify_esp_udev(
dev_t devid,
bool searching,
uint32_t *ret_part,
uint64_t *ret_pstart,
uint64_t *ret_psize,
sd_id128_t *ret_uuid) {
_cleanup_(sd_device_unrefp) sd_device *d = NULL;
_cleanup_free_ char *node = NULL;
sd_id128_t uuid = SD_ID128_NULL;
uint64_t pstart = 0, psize = 0;
uint32_t part = 0;
const char *v;
int r;
r = device_path_make_major_minor(S_IFBLK, devid, &node);
if (r < 0)
return log_error_errno(r, "Failed to format major/minor device path: %m");
r = sd_device_new_from_devnum(&d, 'b', devid);
if (r < 0)
return log_error_errno(r, "Failed to get device from device number: %m");
r = sd_device_get_property_value(d, "ID_FS_TYPE", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
if (!streq(v, "vfat"))
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"File system \"%s\" is not FAT.", node );
r = sd_device_get_property_value(d, "ID_PART_ENTRY_SCHEME", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
if (!streq(v, "gpt"))
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"File system \"%s\" is not on a GPT partition table.", node);
r = sd_device_get_property_value(d, "ID_PART_ENTRY_TYPE", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
if (id128_equal_string(v, GPT_ESP) <= 0)
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"File system \"%s\" has wrong type for an EFI System Partition (ESP).", node);
r = sd_device_get_property_value(d, "ID_PART_ENTRY_UUID", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
r = sd_id128_from_string(v, &uuid);
if (r < 0)
return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v);
r = sd_device_get_property_value(d, "ID_PART_ENTRY_NUMBER", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
r = safe_atou32(v, &part);
if (r < 0)
return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field.");
r = sd_device_get_property_value(d, "ID_PART_ENTRY_OFFSET", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
r = safe_atou64(v, &pstart);
if (r < 0)
return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field.");
r = sd_device_get_property_value(d, "ID_PART_ENTRY_SIZE", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
r = safe_atou64(v, &psize);
if (r < 0)
return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field.");
if (ret_part)
*ret_part = part;
if (ret_pstart)
*ret_pstart = pstart;
if (ret_psize)
*ret_psize = psize;
if (ret_uuid)
*ret_uuid = uuid;
return 0;
}
static int verify_fsroot_dir(
const char *path,
bool searching,
bool unprivileged_mode,
dev_t *ret_dev) {
struct stat st, st2;
const char *t2, *trigger;
int r;
assert(path);
assert(ret_dev);
/* So, the ESP and XBOOTLDR partition are commonly located on an autofs mount. stat() on the
* directory won't trigger it, if it is not mounted yet. Let's hence explicitly trigger it here,
* before stat()ing */
trigger = strjoina(path, "/trigger"); /* Filename doesn't matter... */
(void) access(trigger, F_OK);
if (stat(path, &st) < 0)
return log_full_errno((searching && errno == ENOENT) ||
(unprivileged_mode && errno == EACCES) ? LOG_DEBUG : LOG_ERR, errno,
"Failed to determine block device node of \"%s\": %m", path);
if (major(st.st_dev) == 0)
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"Block device node of \"%s\" is invalid.", path);
if (path_equal(path, "/")) {
/* Let's assume that the root directory of the OS is always the root of its file system
* (which technically doesn't have to be the case, but it's close enough, and it's not easy
* to be fully correct for it, since we can't look further up than the root dir easily.) */
if (ret_dev)
*ret_dev = st.st_dev;
return 0;
}
t2 = strjoina(path, "/..");
if (stat(t2, &st2) < 0) {
if (errno != EACCES)
r = -errno;
else {
_cleanup_free_ char *parent = NULL;
/* If going via ".." didn't work due to EACCESS, then let's determine the parent path
* directly instead. It's not as good, due to symlinks and such, but we can't do
* anything better here. */
parent = dirname_malloc(path);
if (!parent)
return log_oom();
r = RET_NERRNO(stat(parent, &st2));
}
if (r < 0)
return log_full_errno(unprivileged_mode && r == -EACCES ? LOG_DEBUG : LOG_ERR, r,
"Failed to determine block device node of parent of \"%s\": %m", path);
}
if (st.st_dev == st2.st_dev)
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"Directory \"%s\" is not the root of the file system.", path);
if (ret_dev)
*ret_dev = st.st_dev;
return 0;
}
static int verify_esp(
const char *p,
bool searching,
bool unprivileged_mode,
uint32_t *ret_part,
uint64_t *ret_pstart,
uint64_t *ret_psize,
sd_id128_t *ret_uuid,
dev_t *ret_devid) {
bool relax_checks;
dev_t devid;
int r;
assert(p);
/* This logs about all errors, except:
*
* -ENOENT if 'searching' is set, and the dir doesn't exist
* -EADDRNOTAVAIL if 'searching' is set, and the dir doesn't look like an ESP
* -EACESS if 'unprivileged_mode' is set, and we have trouble accessing the thing
*/
relax_checks = getenv_bool("SYSTEMD_RELAX_ESP_CHECKS") > 0;
/* Non-root user can only check the status, so if an error occurred in the following, it does not cause any
* issues. Let's also, silence the error messages. */
if (!relax_checks) {
struct statfs sfs;
if (statfs(p, &sfs) < 0)
/* If we are searching for the mount point, don't generate a log message if we can't find the path */
return log_full_errno((searching && errno == ENOENT) ||
(unprivileged_mode && errno == EACCES) ? LOG_DEBUG : LOG_ERR, errno,
"Failed to check file system type of \"%s\": %m", p);
if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC))
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p);
}
r = verify_fsroot_dir(p, searching, unprivileged_mode, &devid);
if (r < 0)
return r;
/* In a container we don't have access to block devices, skip this part of the verification, we trust
* the container manager set everything up correctly on its own. */
if (detect_container() > 0 || relax_checks)
goto finish;
/* If we are unprivileged we ask udev for the metadata about the partition. If we are privileged we
* use blkid instead. Why? Because this code is called from 'bootctl' which is pretty much an
* emergency recovery tool that should also work when udev isn't up (i.e. from the emergency shell),
* however blkid can't work if we have no privileges to access block devices directly, which is why
* we use udev in that case. */
if (unprivileged_mode)
r = verify_esp_udev(devid, searching, ret_part, ret_pstart, ret_psize, ret_uuid);
else
r = verify_esp_blkid(devid, searching, ret_part, ret_pstart, ret_psize, ret_uuid);
if (r < 0)
return r;
if (ret_devid)
*ret_devid = devid;
return 0;
finish:
if (ret_part)
*ret_part = 0;
if (ret_pstart)
*ret_pstart = 0;
if (ret_psize)
*ret_psize = 0;
if (ret_uuid)
*ret_uuid = SD_ID128_NULL;
if (ret_devid)
*ret_devid = 0;
return 0;
}
int find_esp_and_warn(
const char *path,
bool unprivileged_mode,
char **ret_path,
uint32_t *ret_part,
uint64_t *ret_pstart,
uint64_t *ret_psize,
sd_id128_t *ret_uuid,
dev_t *ret_devid) {
int r;
/* This logs about all errors except:
*
* -ENOKEY when we can't find the partition
* -EACCESS when unprivileged_mode is true, and we can't access something
*/
if (path) {
r = verify_esp(path, /* searching= */ false, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid);
if (r < 0)
return r;
goto found;
}
path = getenv("SYSTEMD_ESP_PATH");
if (path) {
struct stat st;
if (!path_is_valid(path) || !path_is_absolute(path))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"$SYSTEMD_ESP_PATH does not refer to absolute path, refusing to use it: %s",
path);
/* Note: when the user explicitly configured things with an env var we won't validate the
* path beyond checking it refers to a directory. After all we want this to be useful for
* testing. */
if (stat(path, &st) < 0)
return log_error_errno(errno, "Failed to stat '%s': %m", path);
if (!S_ISDIR(st.st_mode))
return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "ESP path '%s' is not a directory.", path);
if (ret_part)
*ret_part = 0;
if (ret_pstart)
*ret_pstart = 0;
if (ret_psize)
*ret_psize = 0;
if (ret_uuid)
*ret_uuid = SD_ID128_NULL;
if (ret_devid)
*ret_devid = st.st_dev;
goto found;
}
FOREACH_STRING(path, "/efi", "/boot", "/boot/efi") {
r = verify_esp(path, /* searching= */ true, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid);
if (r >= 0)
goto found;
if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */
return r;
}
/* No logging here */
return -ENOKEY;
found:
if (ret_path) {
char *c;
c = strdup(path);
if (!c)
return log_oom();
*ret_path = c;
}
return 0;
}
static int verify_xbootldr_blkid(
dev_t devid,
bool searching,
sd_id128_t *ret_uuid) {
sd_id128_t uuid = SD_ID128_NULL;
#if HAVE_BLKID
_cleanup_(blkid_free_probep) blkid_probe b = NULL;
_cleanup_free_ char *node = NULL;
const char *v;
int r;
r = device_path_make_major_minor(S_IFBLK, devid, &node);
if (r < 0)
return log_error_errno(r, "Failed to format major/minor device path: %m");
errno = 0;
b = blkid_new_probe_from_filename(node);
if (!b)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(ENOMEM), "Failed to open file system \"%s\": %m", node);
blkid_probe_enable_partitions(b, 1);
blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
errno = 0;
r = blkid_do_safeprobe(b);
if (r == -2)
return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" is ambiguous.", node);
else if (r == 1)
return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" does not contain a label.", node);
else if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe file system \"%s\": %m", node);
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL);
if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition scheme of \"%s\": %m", node);
if (streq(v, "gpt")) {
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition type UUID of \"%s\": %m", node);
if (id128_equal_string(v, GPT_XBOOTLDR) <= 0)
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
"File system \"%s\" has wrong type for extended boot loader partition.", node);
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL);
if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition entry UUID of \"%s\": %m", node);
r = sd_id128_from_string(v, &uuid);
if (r < 0)
return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v);
} else if (streq(v, "dos")) {
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition type UUID of \"%s\": %m", node);
if (!streq(v, "0xea"))
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
"File system \"%s\" has wrong type for extended boot loader partition.", node);
} else
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
"File system \"%s\" is not on a GPT or DOS partition table.", node);
#endif
if (ret_uuid)
*ret_uuid = uuid;
return 0;
}
static int verify_xbootldr_udev(
dev_t devid,
bool searching,
sd_id128_t *ret_uuid) {
_cleanup_(sd_device_unrefp) sd_device *d = NULL;
_cleanup_free_ char *node = NULL;
sd_id128_t uuid = SD_ID128_NULL;
const char *v;
int r;
r = device_path_make_major_minor(S_IFBLK, devid, &node);
if (r < 0)
return log_error_errno(r, "Failed to format major/minor device path: %m");
r = sd_device_new_from_devnum(&d, 'b', devid);
if (r < 0)
return log_error_errno(r, "Failed to get device from device number: %m");
r = sd_device_get_property_value(d, "ID_PART_ENTRY_SCHEME", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
if (streq(v, "gpt")) {
r = sd_device_get_property_value(d, "ID_PART_ENTRY_TYPE", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
if (id128_equal_string(v, GPT_XBOOTLDR))
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
"File system \"%s\" has wrong type for extended boot loader partition.", node);
r = sd_device_get_property_value(d, "ID_PART_ENTRY_UUID", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
r = sd_id128_from_string(v, &uuid);
if (r < 0)
return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v);
} else if (streq(v, "dos")) {
r = sd_device_get_property_value(d, "ID_PART_ENTRY_TYPE", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
if (!streq(v, "0xea"))
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
"File system \"%s\" has wrong type for extended boot loader partition.", node);
} else
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
"File system \"%s\" is not on a GPT or DOS partition table.", node);
if (ret_uuid)
*ret_uuid = uuid;
return 0;
}
static int verify_xbootldr(
const char *p,
bool searching,
bool unprivileged_mode,
sd_id128_t *ret_uuid,
dev_t *ret_devid) {
bool relax_checks;
dev_t devid;
int r;
assert(p);
relax_checks = getenv_bool("SYSTEMD_RELAX_XBOOTLDR_CHECKS") > 0;
r = verify_fsroot_dir(p, searching, unprivileged_mode, &devid);
if (r < 0)
return r;
if (detect_container() > 0 || relax_checks)
goto finish;
if (unprivileged_mode)
r = verify_xbootldr_udev(devid, searching, ret_uuid);
else
r = verify_xbootldr_blkid(devid, searching, ret_uuid);
if (r < 0)
return r;
if (ret_devid)
*ret_devid = devid;
return 0;
finish:
if (ret_uuid)
*ret_uuid = SD_ID128_NULL;
if (ret_devid)
*ret_devid = 0;
return 0;
}
int find_xbootldr_and_warn(
const char *path,
bool unprivileged_mode,
char **ret_path,
sd_id128_t *ret_uuid,
dev_t *ret_devid) {
int r;
/* Similar to find_esp_and_warn(), but finds the XBOOTLDR partition. Returns the same errors. */
if (path) {
r = verify_xbootldr(path, /* searching= */ false, unprivileged_mode, ret_uuid, ret_devid);
if (r < 0)
return r;
goto found;
}
path = getenv("SYSTEMD_XBOOTLDR_PATH");
if (path) {
struct stat st;
if (!path_is_valid(path) || !path_is_absolute(path))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"$SYSTEMD_XBOOTLDR_PATH does not refer to absolute path, refusing to use it: %s",
path);
if (stat(path, &st) < 0)
return log_error_errno(errno, "Failed to stat '%s': %m", path);
if (!S_ISDIR(st.st_mode))
return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "XBOOTLDR path '%s' is not a directory.", path);
if (ret_uuid)
*ret_uuid = SD_ID128_NULL;
if (ret_devid)
*ret_devid = st.st_dev;
goto found;
}
r = verify_xbootldr("/boot", true, unprivileged_mode, ret_uuid, ret_devid);
if (r >= 0) {
path = "/boot";
goto found;
}
if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */
return r;
return -ENOKEY;
found:
if (ret_path) {
char *c;
c = strdup(path);
if (!c)
return log_oom();
*ret_path = c;
}
return 0;
}

View File

@ -2,12 +2,11 @@
#pragma once #pragma once
#include <errno.h>
#include <inttypes.h> #include <inttypes.h>
#include <stdbool.h> #include <stdbool.h>
#include <sys/types.h> #include <sys/types.h>
#include "sd-id128.h"
#include "string-util.h" #include "string-util.h"
typedef enum BootEntryType { typedef enum BootEntryType {
@ -28,6 +27,7 @@ typedef struct BootEntry {
char *root; /* The root path in which the drop-in was found, i.e. to which 'kernel', 'efi' and 'initrd' are relative */ char *root; /* The root path in which the drop-in was found, i.e. to which 'kernel', 'efi' and 'initrd' are relative */
char *title; char *title;
char *show_title; char *show_title;
char *sort_key;
char *version; char *version;
char *machine_id; char *machine_id;
char *architecture; char *architecture;
@ -90,6 +90,3 @@ static inline const char* boot_entry_title(const BootEntry *entry) {
return entry->show_title ?: entry->title ?: entry->id; return entry->show_title ?: entry->title ?: entry->id;
} }
int find_esp_and_warn(const char *path, bool unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid);
int find_xbootldr_and_warn(const char *path, bool unprivileged_mode, char **ret_path,sd_id128_t *ret_uuid, dev_t *ret_devid);

View File

@ -134,9 +134,8 @@ int clock_reset_timewarp(void) {
#define EPOCH_FILE "/usr/lib/clock-epoch" #define EPOCH_FILE "/usr/lib/clock-epoch"
int clock_apply_epoch(ClockChangeDirection *ret_attempted_change) { int clock_apply_epoch(ClockChangeDirection *ret_attempted_change) {
struct stat st;
struct timespec ts;
usec_t epoch_usec, now_usec; usec_t epoch_usec, now_usec;
struct stat st;
/* NB: we update *ret_attempted_change in *all* cases, both /* NB: we update *ret_attempted_change in *all* cases, both
* on success and failure, to indicate what we intended to do! */ * on success and failure, to indicate what we intended to do! */
@ -161,7 +160,7 @@ int clock_apply_epoch(ClockChangeDirection *ret_attempted_change) {
return 0; return 0;
} }
if (clock_settime(CLOCK_REALTIME, timespec_store(&ts, epoch_usec)) < 0) if (clock_settime(CLOCK_REALTIME, TIMESPEC_STORE(epoch_usec)) < 0)
return -errno; return -errno;
return 1; return 1;

709
src/shared/find-esp.c Normal file
View File

@ -0,0 +1,709 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <linux/magic.h>
#include "sd-device.h"
#include "alloc-util.h"
#include "blkid-util.h"
#include "env-util.h"
#include "errno-util.h"
#include "find-esp.h"
#include "gpt.h"
#include "id128-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "stat-util.h"
#include "string-util.h"
#include "virt.h"
static int verify_esp_blkid(
dev_t devid,
bool searching,
uint32_t *ret_part,
uint64_t *ret_pstart,
uint64_t *ret_psize,
sd_id128_t *ret_uuid) {
sd_id128_t uuid = SD_ID128_NULL;
uint64_t pstart = 0, psize = 0;
uint32_t part = 0;
#if HAVE_BLKID
_cleanup_(blkid_free_probep) blkid_probe b = NULL;
_cleanup_free_ char *node = NULL;
const char *v;
int r;
r = device_path_make_major_minor(S_IFBLK, devid, &node);
if (r < 0)
return log_error_errno(r, "Failed to format major/minor device path: %m");
errno = 0;
b = blkid_new_probe_from_filename(node);
if (!b)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(ENOMEM), "Failed to open file system \"%s\": %m", node);
blkid_probe_enable_superblocks(b, 1);
blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE);
blkid_probe_enable_partitions(b, 1);
blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
errno = 0;
r = blkid_do_safeprobe(b);
if (r == -2)
return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" is ambiguous.", node);
else if (r == 1)
return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" does not contain a label.", node);
else if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe file system \"%s\": %m", node);
r = blkid_probe_lookup_value(b, "TYPE", &v, NULL);
if (r != 0)
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"No filesystem found on \"%s\": %m", node);
if (!streq(v, "vfat"))
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"File system \"%s\" is not FAT.", node);
r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL);
if (r != 0)
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"File system \"%s\" is not located on a partitioned block device.", node);
if (!streq(v, "gpt"))
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"File system \"%s\" is not on a GPT partition table.", node);
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
if (r != 0)
return log_error_errno(errno ?: EIO, "Failed to probe partition type UUID of \"%s\": %m", node);
if (id128_equal_string(v, GPT_ESP) <= 0)
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"File system \"%s\" has wrong type for an EFI System Partition (ESP).", node);
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL);
if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition entry UUID of \"%s\": %m", node);
r = sd_id128_from_string(v, &uuid);
if (r < 0)
return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v);
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_NUMBER", &v, NULL);
if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition number of \"%s\": %m", node);
r = safe_atou32(v, &part);
if (r < 0)
return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field.");
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL);
if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition offset of \"%s\": %m", node);
r = safe_atou64(v, &pstart);
if (r < 0)
return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field.");
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL);
if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition size of \"%s\": %m", node);
r = safe_atou64(v, &psize);
if (r < 0)
return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field.");
#endif
if (ret_part)
*ret_part = part;
if (ret_pstart)
*ret_pstart = pstart;
if (ret_psize)
*ret_psize = psize;
if (ret_uuid)
*ret_uuid = uuid;
return 0;
}
static int verify_esp_udev(
dev_t devid,
bool searching,
uint32_t *ret_part,
uint64_t *ret_pstart,
uint64_t *ret_psize,
sd_id128_t *ret_uuid) {
_cleanup_(sd_device_unrefp) sd_device *d = NULL;
_cleanup_free_ char *node = NULL;
sd_id128_t uuid = SD_ID128_NULL;
uint64_t pstart = 0, psize = 0;
uint32_t part = 0;
const char *v;
int r;
r = device_path_make_major_minor(S_IFBLK, devid, &node);
if (r < 0)
return log_error_errno(r, "Failed to format major/minor device path: %m");
r = sd_device_new_from_devnum(&d, 'b', devid);
if (r < 0)
return log_error_errno(r, "Failed to get device from device number: %m");
r = sd_device_get_property_value(d, "ID_FS_TYPE", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
if (!streq(v, "vfat"))
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"File system \"%s\" is not FAT.", node );
r = sd_device_get_property_value(d, "ID_PART_ENTRY_SCHEME", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
if (!streq(v, "gpt"))
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"File system \"%s\" is not on a GPT partition table.", node);
r = sd_device_get_property_value(d, "ID_PART_ENTRY_TYPE", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
if (id128_equal_string(v, GPT_ESP) <= 0)
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"File system \"%s\" has wrong type for an EFI System Partition (ESP).", node);
r = sd_device_get_property_value(d, "ID_PART_ENTRY_UUID", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
r = sd_id128_from_string(v, &uuid);
if (r < 0)
return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v);
r = sd_device_get_property_value(d, "ID_PART_ENTRY_NUMBER", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
r = safe_atou32(v, &part);
if (r < 0)
return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field.");
r = sd_device_get_property_value(d, "ID_PART_ENTRY_OFFSET", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
r = safe_atou64(v, &pstart);
if (r < 0)
return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field.");
r = sd_device_get_property_value(d, "ID_PART_ENTRY_SIZE", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
r = safe_atou64(v, &psize);
if (r < 0)
return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field.");
if (ret_part)
*ret_part = part;
if (ret_pstart)
*ret_pstart = pstart;
if (ret_psize)
*ret_psize = psize;
if (ret_uuid)
*ret_uuid = uuid;
return 0;
}
static int verify_fsroot_dir(
const char *path,
bool searching,
bool unprivileged_mode,
dev_t *ret_dev) {
struct stat st, st2;
const char *t2, *trigger;
int r;
assert(path);
assert(ret_dev);
/* So, the ESP and XBOOTLDR partition are commonly located on an autofs mount. stat() on the
* directory won't trigger it, if it is not mounted yet. Let's hence explicitly trigger it here,
* before stat()ing */
trigger = strjoina(path, "/trigger"); /* Filename doesn't matter... */
(void) access(trigger, F_OK);
if (stat(path, &st) < 0)
return log_full_errno((searching && errno == ENOENT) ||
(unprivileged_mode && errno == EACCES) ? LOG_DEBUG : LOG_ERR, errno,
"Failed to determine block device node of \"%s\": %m", path);
if (major(st.st_dev) == 0)
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"Block device node of \"%s\" is invalid.", path);
if (path_equal(path, "/")) {
/* Let's assume that the root directory of the OS is always the root of its file system
* (which technically doesn't have to be the case, but it's close enough, and it's not easy
* to be fully correct for it, since we can't look further up than the root dir easily.) */
if (ret_dev)
*ret_dev = st.st_dev;
return 0;
}
t2 = strjoina(path, "/..");
if (stat(t2, &st2) < 0) {
if (errno != EACCES)
r = -errno;
else {
_cleanup_free_ char *parent = NULL;
/* If going via ".." didn't work due to EACCESS, then let's determine the parent path
* directly instead. It's not as good, due to symlinks and such, but we can't do
* anything better here. */
parent = dirname_malloc(path);
if (!parent)
return log_oom();
r = RET_NERRNO(stat(parent, &st2));
}
if (r < 0)
return log_full_errno(unprivileged_mode && r == -EACCES ? LOG_DEBUG : LOG_ERR, r,
"Failed to determine block device node of parent of \"%s\": %m", path);
}
if (st.st_dev == st2.st_dev)
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"Directory \"%s\" is not the root of the file system.", path);
if (ret_dev)
*ret_dev = st.st_dev;
return 0;
}
static int verify_esp(
const char *p,
bool searching,
bool unprivileged_mode,
uint32_t *ret_part,
uint64_t *ret_pstart,
uint64_t *ret_psize,
sd_id128_t *ret_uuid,
dev_t *ret_devid) {
bool relax_checks;
dev_t devid;
int r;
assert(p);
/* This logs about all errors, except:
*
* -ENOENT if 'searching' is set, and the dir doesn't exist
* -EADDRNOTAVAIL if 'searching' is set, and the dir doesn't look like an ESP
* -EACESS if 'unprivileged_mode' is set, and we have trouble accessing the thing
*/
relax_checks = getenv_bool("SYSTEMD_RELAX_ESP_CHECKS") > 0;
/* Non-root user can only check the status, so if an error occurred in the following, it does not cause any
* issues. Let's also, silence the error messages. */
if (!relax_checks) {
struct statfs sfs;
if (statfs(p, &sfs) < 0)
/* If we are searching for the mount point, don't generate a log message if we can't find the path */
return log_full_errno((searching && errno == ENOENT) ||
(unprivileged_mode && errno == EACCES) ? LOG_DEBUG : LOG_ERR, errno,
"Failed to check file system type of \"%s\": %m", p);
if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC))
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
"File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p);
}
r = verify_fsroot_dir(p, searching, unprivileged_mode, &devid);
if (r < 0)
return r;
/* In a container we don't have access to block devices, skip this part of the verification, we trust
* the container manager set everything up correctly on its own. */
if (detect_container() > 0 || relax_checks)
goto finish;
/* If we are unprivileged we ask udev for the metadata about the partition. If we are privileged we
* use blkid instead. Why? Because this code is called from 'bootctl' which is pretty much an
* emergency recovery tool that should also work when udev isn't up (i.e. from the emergency shell),
* however blkid can't work if we have no privileges to access block devices directly, which is why
* we use udev in that case. */
if (unprivileged_mode)
r = verify_esp_udev(devid, searching, ret_part, ret_pstart, ret_psize, ret_uuid);
else
r = verify_esp_blkid(devid, searching, ret_part, ret_pstart, ret_psize, ret_uuid);
if (r < 0)
return r;
if (ret_devid)
*ret_devid = devid;
return 0;
finish:
if (ret_part)
*ret_part = 0;
if (ret_pstart)
*ret_pstart = 0;
if (ret_psize)
*ret_psize = 0;
if (ret_uuid)
*ret_uuid = SD_ID128_NULL;
if (ret_devid)
*ret_devid = 0;
return 0;
}
int find_esp_and_warn(
const char *path,
bool unprivileged_mode,
char **ret_path,
uint32_t *ret_part,
uint64_t *ret_pstart,
uint64_t *ret_psize,
sd_id128_t *ret_uuid,
dev_t *ret_devid) {
int r;
/* This logs about all errors except:
*
* -ENOKEY when we can't find the partition
* -EACCESS when unprivileged_mode is true, and we can't access something
*/
if (path) {
r = verify_esp(path, /* searching= */ false, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid);
if (r < 0)
return r;
goto found;
}
path = getenv("SYSTEMD_ESP_PATH");
if (path) {
struct stat st;
if (!path_is_valid(path) || !path_is_absolute(path))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"$SYSTEMD_ESP_PATH does not refer to absolute path, refusing to use it: %s",
path);
/* Note: when the user explicitly configured things with an env var we won't validate the
* path beyond checking it refers to a directory. After all we want this to be useful for
* testing. */
if (stat(path, &st) < 0)
return log_error_errno(errno, "Failed to stat '%s': %m", path);
if (!S_ISDIR(st.st_mode))
return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "ESP path '%s' is not a directory.", path);
if (ret_part)
*ret_part = 0;
if (ret_pstart)
*ret_pstart = 0;
if (ret_psize)
*ret_psize = 0;
if (ret_uuid)
*ret_uuid = SD_ID128_NULL;
if (ret_devid)
*ret_devid = st.st_dev;
goto found;
}
FOREACH_STRING(path, "/efi", "/boot", "/boot/efi") {
r = verify_esp(path, /* searching= */ true, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid);
if (r >= 0)
goto found;
if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */
return r;
}
/* No logging here */
return -ENOKEY;
found:
if (ret_path) {
char *c;
c = strdup(path);
if (!c)
return log_oom();
*ret_path = c;
}
return 0;
}
static int verify_xbootldr_blkid(
dev_t devid,
bool searching,
sd_id128_t *ret_uuid) {
sd_id128_t uuid = SD_ID128_NULL;
#if HAVE_BLKID
_cleanup_(blkid_free_probep) blkid_probe b = NULL;
_cleanup_free_ char *node = NULL;
const char *v;
int r;
r = device_path_make_major_minor(S_IFBLK, devid, &node);
if (r < 0)
return log_error_errno(r, "Failed to format major/minor device path: %m");
errno = 0;
b = blkid_new_probe_from_filename(node);
if (!b)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(ENOMEM), "Failed to open file system \"%s\": %m", node);
blkid_probe_enable_partitions(b, 1);
blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
errno = 0;
r = blkid_do_safeprobe(b);
if (r == -2)
return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" is ambiguous.", node);
else if (r == 1)
return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" does not contain a label.", node);
else if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe file system \"%s\": %m", node);
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL);
if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition scheme of \"%s\": %m", node);
if (streq(v, "gpt")) {
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition type UUID of \"%s\": %m", node);
if (id128_equal_string(v, GPT_XBOOTLDR) <= 0)
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
"File system \"%s\" has wrong type for extended boot loader partition.", node);
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL);
if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition entry UUID of \"%s\": %m", node);
r = sd_id128_from_string(v, &uuid);
if (r < 0)
return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v);
} else if (streq(v, "dos")) {
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition type UUID of \"%s\": %m", node);
if (!streq(v, "0xea"))
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
"File system \"%s\" has wrong type for extended boot loader partition.", node);
} else
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
"File system \"%s\" is not on a GPT or DOS partition table.", node);
#endif
if (ret_uuid)
*ret_uuid = uuid;
return 0;
}
static int verify_xbootldr_udev(
dev_t devid,
bool searching,
sd_id128_t *ret_uuid) {
_cleanup_(sd_device_unrefp) sd_device *d = NULL;
_cleanup_free_ char *node = NULL;
sd_id128_t uuid = SD_ID128_NULL;
const char *v;
int r;
r = device_path_make_major_minor(S_IFBLK, devid, &node);
if (r < 0)
return log_error_errno(r, "Failed to format major/minor device path: %m");
r = sd_device_new_from_devnum(&d, 'b', devid);
if (r < 0)
return log_error_errno(r, "Failed to get device from device number: %m");
r = sd_device_get_property_value(d, "ID_PART_ENTRY_SCHEME", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
if (streq(v, "gpt")) {
r = sd_device_get_property_value(d, "ID_PART_ENTRY_TYPE", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
if (id128_equal_string(v, GPT_XBOOTLDR))
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
"File system \"%s\" has wrong type for extended boot loader partition.", node);
r = sd_device_get_property_value(d, "ID_PART_ENTRY_UUID", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
r = sd_id128_from_string(v, &uuid);
if (r < 0)
return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v);
} else if (streq(v, "dos")) {
r = sd_device_get_property_value(d, "ID_PART_ENTRY_TYPE", &v);
if (r < 0)
return log_error_errno(r, "Failed to get device property: %m");
if (!streq(v, "0xea"))
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
"File system \"%s\" has wrong type for extended boot loader partition.", node);
} else
return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
"File system \"%s\" is not on a GPT or DOS partition table.", node);
if (ret_uuid)
*ret_uuid = uuid;
return 0;
}
static int verify_xbootldr(
const char *p,
bool searching,
bool unprivileged_mode,
sd_id128_t *ret_uuid,
dev_t *ret_devid) {
bool relax_checks;
dev_t devid;
int r;
assert(p);
relax_checks = getenv_bool("SYSTEMD_RELAX_XBOOTLDR_CHECKS") > 0;
r = verify_fsroot_dir(p, searching, unprivileged_mode, &devid);
if (r < 0)
return r;
if (detect_container() > 0 || relax_checks)
goto finish;
if (unprivileged_mode)
r = verify_xbootldr_udev(devid, searching, ret_uuid);
else
r = verify_xbootldr_blkid(devid, searching, ret_uuid);
if (r < 0)
return r;
if (ret_devid)
*ret_devid = devid;
return 0;
finish:
if (ret_uuid)
*ret_uuid = SD_ID128_NULL;
if (ret_devid)
*ret_devid = 0;
return 0;
}
int find_xbootldr_and_warn(
const char *path,
bool unprivileged_mode,
char **ret_path,
sd_id128_t *ret_uuid,
dev_t *ret_devid) {
int r;
/* Similar to find_esp_and_warn(), but finds the XBOOTLDR partition. Returns the same errors. */
if (path) {
r = verify_xbootldr(path, /* searching= */ false, unprivileged_mode, ret_uuid, ret_devid);
if (r < 0)
return r;
goto found;
}
path = getenv("SYSTEMD_XBOOTLDR_PATH");
if (path) {
struct stat st;
if (!path_is_valid(path) || !path_is_absolute(path))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"$SYSTEMD_XBOOTLDR_PATH does not refer to absolute path, refusing to use it: %s",
path);
if (stat(path, &st) < 0)
return log_error_errno(errno, "Failed to stat '%s': %m", path);
if (!S_ISDIR(st.st_mode))
return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "XBOOTLDR path '%s' is not a directory.", path);
if (ret_uuid)
*ret_uuid = SD_ID128_NULL;
if (ret_devid)
*ret_devid = st.st_dev;
goto found;
}
r = verify_xbootldr("/boot", true, unprivileged_mode, ret_uuid, ret_devid);
if (r >= 0) {
path = "/boot";
goto found;
}
if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */
return r;
return -ENOKEY;
found:
if (ret_path) {
char *c;
c = strdup(path);
if (!c)
return log_oom();
*ret_path = c;
}
return 0;
}

12
src/shared/find-esp.h Normal file
View File

@ -0,0 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <inttypes.h>
#include <stdbool.h>
#include <sys/types.h>
#include "sd-id128.h"
int find_esp_and_warn(const char *path, bool unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid);
int find_xbootldr_and_warn(const char *path, bool unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid);

View File

@ -125,6 +125,8 @@ shared_sources = files(
'fdset.h', 'fdset.h',
'fileio-label.c', 'fileio-label.c',
'fileio-label.h', 'fileio-label.h',
'find-esp.c',
'find-esp.h',
'firewall-util-nft.c', 'firewall-util-nft.c',
'firewall-util-private.h', 'firewall-util-private.h',
'firewall-util.c', 'firewall-util.c',
@ -176,8 +178,8 @@ shared_sources = files(
'json.h', 'json.h',
'kbd-util.c', 'kbd-util.c',
'kbd-util.h', 'kbd-util.h',
'keyring-util.h',
'keyring-util.c', 'keyring-util.c',
'keyring-util.h',
'killall.c', 'killall.c',
'killall.h', 'killall.h',
'label.c', 'label.c',

View File

@ -513,6 +513,8 @@ tests += [
[files('test-strbuf.c')], [files('test-strbuf.c')],
[files('test-bootspec.c')],
[files('test-strv.c')], [files('test-strv.c')],
[files('test-path-util.c')], [files('test-path-util.c')],

96
src/test/test-bootspec.c Normal file
View File

@ -0,0 +1,96 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "bootspec.h"
#include "fileio.h"
#include "path-util.h"
#include "rm-rf.h"
#include "tests.h"
#include "tmpfile-util.h"
TEST_RET(bootspec_sort) {
static const struct {
const char *fname;
const char *contents;
} entries[] = {
{
.fname = "a-10.conf",
.contents =
"title A\n"
"version 10\n"
"machine-id dd235d00696545768f6f693bfd23b15f\n",
},
{
.fname = "a-5.conf",
.contents =
"title A\n"
"version 5\n"
"machine-id dd235d00696545768f6f693bfd23b15f\n",
},
{
.fname = "b.conf",
.contents =
"title B\n"
"version 3\n"
"machine-id b75451ad92f94feeab50b0b442768dbd\n",
},
{
.fname = "c.conf",
.contents =
"title C\n"
"sort-key xxxx\n"
"version 5\n"
"machine-id 309de666fd5044268a9a26541ac93176\n",
},
{
.fname = "cx.conf",
.contents =
"title C\n"
"sort-key xxxx\n"
"version 10\n"
"machine-id 309de666fd5044268a9a26541ac93176\n",
},
{
.fname = "d.conf",
.contents =
"title D\n"
"sort-key kkkk\n"
"version 100\n"
"machine-id 81c6e3147cf544c19006af023e22b292\n",
},
};
_cleanup_(rm_rf_physical_and_freep) char *d = NULL;
_cleanup_(boot_config_free) BootConfig config = {};
assert_se(mkdtemp_malloc("/tmp/bootspec-testXXXXXX", &d) >= 0);
for (size_t i = 0; i < ELEMENTSOF(entries); i++) {
_cleanup_free_ char *j = NULL;
j = path_join(d, "/loader/entries/", entries[i].fname);
assert_se(j);
assert_se(write_string_file(j, entries[i].contents, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) >= 0);
}
assert_se(boot_entries_load_config(d, NULL, &config) >= 0);
assert_se(config.n_entries == 6);
/* First, because has sort key, and its the lowest one */
assert_se(streq(config.entries[0].id, "d.conf"));
/* These two have a sort key, and newest must be first */
assert_se(streq(config.entries[1].id, "cx.conf"));
assert_se(streq(config.entries[2].id, "c.conf"));
/* The following ones have no sort key, hence order by version compared ids, lowest first */
assert_se(streq(config.entries[3].id, "a-5.conf"));
assert_se(streq(config.entries[4].id, "a-10.conf"));
assert_se(streq(config.entries[5].id, "b.conf"));
return 0;
}
DEFINE_TEST_MAIN(LOG_INFO);

View File

@ -72,13 +72,12 @@ static int load_clock_timestamp(uid_t uid, gid_t gid) {
settime: settime:
ct = now(CLOCK_REALTIME); ct = now(CLOCK_REALTIME);
if (ct < min) { if (ct < min) {
struct timespec ts;
char date[FORMAT_TIMESTAMP_MAX]; char date[FORMAT_TIMESTAMP_MAX];
log_info("System clock time unset or jumped backwards, restoring from recorded timestamp: %s", log_info("System clock time unset or jumped backwards, restoring from recorded timestamp: %s",
format_timestamp(date, sizeof(date), min)); format_timestamp(date, sizeof(date), min));
if (clock_settime(CLOCK_REALTIME, timespec_store(&ts, min)) < 0) if (clock_settime(CLOCK_REALTIME, TIMESPEC_STORE(min)) < 0)
log_error_errno(errno, "Failed to restore system clock, ignoring: %m"); log_error_errno(errno, "Failed to restore system clock, ignoring: %m");
} }

View File

@ -581,8 +581,6 @@ static int ask_on_this_console(const char *tty, pid_t *ret_pid, char **arguments
} }
static void terminate_agents(Set *pids) { static void terminate_agents(Set *pids) {
struct timespec ts;
siginfo_t status = {};
sigset_t set; sigset_t set;
void *p; void *p;
int r, signum; int r, signum;
@ -599,11 +597,10 @@ static void terminate_agents(Set *pids) {
*/ */
assert_se(sigemptyset(&set) >= 0); assert_se(sigemptyset(&set) >= 0);
assert_se(sigaddset(&set, SIGCHLD) >= 0); assert_se(sigaddset(&set, SIGCHLD) >= 0);
timespec_store(&ts, 50 * USEC_PER_MSEC);
while (!set_isempty(pids)) { while (!set_isempty(pids)) {
siginfo_t status = {};
zero(status);
r = waitid(P_ALL, 0, &status, WEXITED|WNOHANG); r = waitid(P_ALL, 0, &status, WEXITED|WNOHANG);
if (r < 0 && errno == EINTR) if (r < 0 && errno == EINTR)
continue; continue;
@ -613,7 +610,7 @@ static void terminate_agents(Set *pids) {
continue; continue;
} }
signum = sigtimedwait(&set, NULL, &ts); signum = sigtimedwait(&set, NULL, TIMESPEC_STORE(50 * USEC_PER_MSEC));
if (signum < 0) { if (signum < 0) {
if (errno != EAGAIN) if (errno != EAGAIN)
log_error_errno(errno, "sigtimedwait() failed: %m"); log_error_errno(errno, "sigtimedwait() failed: %m");

View File

@ -251,7 +251,6 @@ static int start_workers(Manager *m, bool explicit_request) {
} }
int manager_startup(Manager *m) { int manager_startup(Manager *m) {
struct timeval ts;
int n, r; int n, r;
assert(m); assert(m);
@ -300,7 +299,7 @@ int manager_startup(Manager *m) {
/* Let's make sure every accept() call on this socket times out after 25s. This allows workers to be /* Let's make sure every accept() call on this socket times out after 25s. This allows workers to be
* GC'ed on idle */ * GC'ed on idle */
if (setsockopt(m->listen_fd, SOL_SOCKET, SO_RCVTIMEO, timeval_store(&ts, LISTEN_TIMEOUT_USEC), sizeof(ts)) < 0) if (setsockopt(m->listen_fd, SOL_SOCKET, SO_RCVTIMEO, TIMEVAL_STORE(LISTEN_TIMEOUT_USEC), sizeof(struct timeval)) < 0)
return log_error_errno(errno, "Failed to se SO_RCVTIMEO: %m"); return log_error_errno(errno, "Failed to se SO_RCVTIMEO: %m");
return start_workers(m, false); return start_workers(m, false);