bless-boot: some fixes (#37375)
This mostly makes sure we do something reasonable when our tool is called from a boot of an entry that was already marked as definitely "bad" on a previous boot. Such an entry we can return into a "good" state, but we cannot return it into an "indeterminate" state, because the status quo ante is already known. Fixes: #37350
This commit is contained in:
@@ -57,13 +57,17 @@
|
||||
<varlistentry>
|
||||
<term><option>status</option></term>
|
||||
|
||||
<listitem><para>The current status of the boot loader entry file or unified kernel image file is shown. This
|
||||
outputs one of <literal>good</literal>, <literal>bad</literal>, <literal>indeterminate</literal>,
|
||||
<literal>clean</literal>, depending on the state and previous invocations of the command. The string
|
||||
<literal>indeterminate</literal> is shown initially after boot, before it has been marked as "good" or
|
||||
"bad". The string <literal>good</literal> is shown after the boot was marked as "good" with the
|
||||
<option>good</option> command below, and "bad" conversely after the <option>bad</option> command was
|
||||
invoked. The string <literal>clean</literal> is returned when boot counting is currently not in effect.</para>
|
||||
<listitem><para>The current status of the boot loader entry file or unified kernel image file is
|
||||
shown. This outputs one of <literal>good</literal>, <literal>bad</literal>,
|
||||
<literal>indeterminate</literal>, <literal>clean</literal>, <literal>dirty</literal>, depending on
|
||||
the state and previous invocations of the command. The string <literal>indeterminate</literal> is
|
||||
shown initially after boot, before it has been marked as "good" or "bad". The string
|
||||
<literal>good</literal> is shown after the boot was marked as "good" with the <option>good</option>
|
||||
command below, and "bad" conversely after the <option>bad</option> command was invoked. The string
|
||||
<literal>clean</literal> is returned when boot counting is currently not in effect (which includes
|
||||
the case where the current entry was already marked as persistently good). The string
|
||||
<literal>dirty</literal> is returned when the system is booted up with a known-bad kernel (i.e. one
|
||||
where the tries left counter has previously reached zero already).</para>
|
||||
|
||||
<para>This command is implied if no command argument is specified.</para>
|
||||
|
||||
@@ -96,9 +100,13 @@
|
||||
<varlistentry>
|
||||
<term><option>indeterminate</option></term>
|
||||
|
||||
<listitem><para>This command undoes any marking of the current boot loader entry file or unified kernel image
|
||||
file as good or bad. This is implemented by renaming the boot loader entry file or unified kernel image file
|
||||
back to the path encoded in the <varname>LoaderBootCountPath</varname> EFI variable.</para>
|
||||
<listitem><para>This command undoes any marking of the current boot loader entry file or unified
|
||||
kernel image file as good or bad. This is implemented by renaming the boot loader entry file or
|
||||
unified kernel image file back to the path encoded in the <varname>LoaderBootCountPath</varname> EFI
|
||||
variable. Note that operation will fail if the current kernel is not booted with boot counting
|
||||
enabled (i.e. if the EFI variable is not set). If the boot counter already reached zero tries left on
|
||||
a previous boot this operation will fail too: once an entry is marked <option>bad</option> it can
|
||||
only be reset to <option>good</option> again, but not to <option>indeterminate</option>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v240"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
+61
-12
@@ -215,11 +215,9 @@ static int acquire_boot_count_path(
|
||||
uint64_t *ret_done,
|
||||
char **ret_suffix) {
|
||||
|
||||
_cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL;
|
||||
const char *last, *e;
|
||||
uint64_t left, done;
|
||||
int r;
|
||||
|
||||
_cleanup_free_ char *path = NULL;
|
||||
r = efi_get_variable_path(EFI_LOADER_VARIABLE_STR("LoaderBootCountPath"), &path);
|
||||
if (r == -ENOENT)
|
||||
return -EUNATCH; /* in this case, let the caller print a message */
|
||||
@@ -236,23 +234,34 @@ static int acquire_boot_count_path(
|
||||
"Path read from LoaderBootCountPath is not absolute, refusing: %s",
|
||||
path);
|
||||
|
||||
last = last_path_component(path);
|
||||
e = strrchr(last, '+');
|
||||
const char *last = NULL;
|
||||
r = path_find_last_component(path, /* accept_dot_dot= */ false, /* next= */ NULL, &last);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to extract filename from LoaderBootCountPath '%s': %m", path);
|
||||
if (r == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), "LoaderBootCountPath '%s' refers to the root directory: %m", path);
|
||||
if (strlen(last) > (size_t) r)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "LoaderBootCountPath '%s' refers to directory path, refusing.", path);
|
||||
|
||||
const char *e = strrchr(last, '+');
|
||||
if (!e)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Path read from LoaderBootCountPath does not contain a counter, refusing: %s",
|
||||
path);
|
||||
|
||||
_cleanup_free_ char *prefix = NULL;
|
||||
if (ret_prefix) {
|
||||
prefix = strndup(path, e - path);
|
||||
if (!prefix)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
uint64_t left, done;
|
||||
r = parse_counter(path, &e, &left, &done);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_free_ char *suffix = NULL;
|
||||
if (ret_suffix) {
|
||||
suffix = strdup(e);
|
||||
if (!suffix)
|
||||
@@ -363,7 +372,14 @@ static int verb_status(int argc, char *argv[], void *userdata) {
|
||||
}
|
||||
|
||||
if (faccessat(fd, skip_leading_slash(path), F_OK, 0) >= 0) {
|
||||
puts("indeterminate");
|
||||
/* If the item we booted with still exists under its name, it means we have not
|
||||
* change the current boot's marking so far. This may have two reasons: because we
|
||||
* simply didn't do that yet but still plan to, or because the left tries counter is
|
||||
* already at zero, hence we cannot further decrease it to mark it even
|
||||
* "worse"... Here we check the current counter to detect the latter case and return
|
||||
* "dirty", since the item is already marked bad from a previous boot, but otherwise
|
||||
* report "indeterminate" since we just didn't make a decision yet. */
|
||||
puts(left == 0 ? "dirty" : "indeterminate");
|
||||
return 0;
|
||||
}
|
||||
if (errno != ENOENT)
|
||||
@@ -390,13 +406,37 @@ static int verb_status(int argc, char *argv[], void *userdata) {
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Couldn't determine boot state.");
|
||||
}
|
||||
|
||||
static int rename_in_dir_idempotent(int fd, const char *from, const char *to) {
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(from);
|
||||
assert(to);
|
||||
|
||||
/* A wrapper around rename_noreplace() which executes no operation if the source and target are the
|
||||
* same. */
|
||||
|
||||
if (streq(from, to)) {
|
||||
if (faccessat(fd, from, F_OK, AT_SYMLINK_NOFOLLOW) < 0)
|
||||
return -errno;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = rename_noreplace(fd, from, fd, to);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int verb_set(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL;
|
||||
const char *target, *source1, *source2;
|
||||
uint64_t done;
|
||||
uint64_t left, done;
|
||||
int r;
|
||||
|
||||
r = acquire_boot_count_path(&path, &prefix, NULL, &done, &suffix);
|
||||
r = acquire_boot_count_path(&path, &prefix, &left, &done, &suffix);
|
||||
if (r == -EUNATCH) /* acquire_boot_count_path() won't log on its own for this specific error */
|
||||
return log_error_errno(r, "Not booted with boot counting in effect.");
|
||||
if (r < 0)
|
||||
@@ -425,6 +465,10 @@ static int verb_set(int argc, char *argv[], void *userdata) {
|
||||
source2 = good; /* Maybe this boot was previously marked as 'good'? */
|
||||
} else {
|
||||
assert(streq(argv[0], "indeterminate"));
|
||||
|
||||
if (left == 0)
|
||||
return log_error_errno(r, "Current boot entry was already marked bad in a previous boot, cannot reset to indeterminate.");
|
||||
|
||||
target = path;
|
||||
source1 = good;
|
||||
source2 = bad;
|
||||
@@ -437,12 +481,12 @@ static int verb_set(int argc, char *argv[], void *userdata) {
|
||||
if (fd < 0)
|
||||
return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p);
|
||||
|
||||
r = rename_noreplace(fd, skip_leading_slash(source1), fd, skip_leading_slash(target));
|
||||
r = rename_in_dir_idempotent(fd, skip_leading_slash(source1), skip_leading_slash(target));
|
||||
if (r == -EEXIST)
|
||||
goto exists;
|
||||
if (r == -ENOENT) {
|
||||
|
||||
r = rename_noreplace(fd, skip_leading_slash(source2), fd, skip_leading_slash(target));
|
||||
r = rename_in_dir_idempotent(fd, skip_leading_slash(source2), skip_leading_slash(target));
|
||||
if (r == -EEXIST)
|
||||
goto exists;
|
||||
if (r == -ENOENT) {
|
||||
@@ -459,11 +503,16 @@ static int verb_set(int argc, char *argv[], void *userdata) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source2, target);
|
||||
|
||||
log_debug("Successfully renamed '%s' to '%s'.", source2, target);
|
||||
if (r > 0)
|
||||
log_debug("Successfully renamed '%s' to '%s'.", source2, target);
|
||||
else
|
||||
log_debug("Not renaming, as '%s' already matches target name.", source2);
|
||||
} else if (r < 0)
|
||||
return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source1, target);
|
||||
else
|
||||
else if (r > 0)
|
||||
log_debug("Successfully renamed '%s' to '%s'.", source1, target);
|
||||
else
|
||||
log_debug("Not renaming, as '%s' already matches target name.", source1);
|
||||
|
||||
/* First, fsync() the directory these files are located in */
|
||||
r = fsync_parent_at(fd, skip_leading_slash(target));
|
||||
|
||||
Reference in New Issue
Block a user