1 /* SPDX-License-Identifier: LGPL-2.1+ */
6 #include "alloc-util.h"
12 #include "main-func.h"
13 #include "parse-util.h"
14 #include "path-util.h"
19 static char *arg_path
= NULL
;
21 static int help(int argc
, char *argv
[], void *userdata
) {
23 printf("%s [COMMAND] [OPTIONS...]\n"
25 "Mark the boot process as good or bad.\n\n"
26 " -h --help Show this help\n"
27 " --version Print version\n"
28 " --path=PATH Path to the EFI System Partition (ESP)\n"
31 " good Mark this boot as good\n"
32 " bad Mark this boot as bad\n"
33 " indeterminate Undo any marking as good or bad\n",
34 program_invocation_short_name
);
39 static int parse_argv(int argc
, char *argv
[]) {
45 static const struct option options
[] = {
46 { "help", no_argument
, NULL
, 'h' },
47 { "version", no_argument
, NULL
, ARG_VERSION
},
48 { "path", required_argument
, NULL
, ARG_PATH
},
57 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
68 r
= free_and_strdup(&arg_path
, optarg
);
77 assert_not_reached("Unknown option");
83 static int acquire_esp(void) {
84 _cleanup_free_
char *np
= NULL
;
87 r
= find_esp_and_warn(arg_path
, false, &np
, NULL
, NULL
, NULL
, NULL
);
88 if (r
== -ENOKEY
) /* find_esp_and_warn() doesn't warn in this one error case, but in all others */
89 return log_error_errno(r
,
90 "Couldn't find EFI system partition. It is recommended to mount it to /boot or /efi.\n"
91 "Alternatively, use --path= to specify path to mount point.");
95 free_and_replace(arg_path
, np
);
96 log_debug("Using EFI System Partition at %s.", arg_path
);
101 static int parse_counter(
105 uint64_t *ret_done
) {
121 k
= strspn(e
, DIGITS
);
123 log_error("Can't parse empty 'tries left' counter from LoaderBootCountPath: %s", path
);
128 r
= safe_atou64(z
, &left
);
130 return log_error_errno(r
, "Failed to parse 'tries left' counter from LoaderBootCountPath: %s", path
);
137 k
= strspn(e
, DIGITS
);
138 if (k
== 0) { /* If there's a "-" there also needs to be at least one digit */
139 log_error("Can't parse empty 'tries done' counter from LoaderBootCountPath: %s", path
);
144 r
= safe_atou64(z
, &done
);
146 return log_error_errno(r
, "Failed to parse 'tries done' counter from LoaderBootCountPath: %s", path
);
153 log_warning("The 'tries done' counter is currently at zero. This can't really be, after all we are running, and this boot must hence count as one. Proceeding anyway.");
166 static int acquire_boot_count_path(
173 _cleanup_free_
char *path
= NULL
, *prefix
= NULL
, *suffix
= NULL
;
174 const char *last
, *e
;
178 r
= efi_get_variable_string(EFI_VENDOR_LOADER
, "LoaderBootCountPath", &path
);
180 return -EUNATCH
; /* in this case, let the caller print a message */
182 return log_error_errno(r
, "Failed to read LoaderBootCountPath EFI variable: %m");
184 efi_tilt_backslashes(path
);
186 if (!path_is_normalized(path
)) {
187 log_error("Path read from LoaderBootCountPath is not normalized, refusing: %s", path
);
191 if (!path_is_absolute(path
)) {
192 log_error("Path read from LoaderBootCountPath is not absolute, refusing: %s", path
);
196 last
= last_path_component(path
);
197 e
= strrchr(last
, '+');
199 log_error("Path read from LoaderBootCountPath does not contain a counter, refusing: %s", path
);
204 prefix
= strndup(path
, e
- path
);
209 r
= parse_counter(path
, &e
, &left
, &done
);
218 *ret_suffix
= TAKE_PTR(suffix
);
222 *ret_path
= TAKE_PTR(path
);
224 *ret_prefix
= TAKE_PTR(prefix
);
233 static int make_good(const char *prefix
, const char *suffix
, char **ret
) {
234 _cleanup_free_
char *good
= NULL
;
240 /* Generate the path we'd use on good boots. This one is easy. If we are successful, we simple drop the counter
241 * pair entirely from the name. After all, we know all is good, and the logs will contain information about the
242 * tries we needed to come here, hence it's safe to drop the counters from the name. */
244 good
= strjoin(prefix
, suffix
);
248 *ret
= TAKE_PTR(good
);
252 static int make_bad(const char *prefix
, uint64_t done
, const char *suffix
, char **ret
) {
253 _cleanup_free_
char *bad
= NULL
;
259 /* Generate the path we'd use on bad boots. Let's simply set the 'left' counter to zero, and keep the 'done'
260 * counter. The information might be interesting to boot loaders, after all. */
263 bad
= strjoin(prefix
, "+0", suffix
);
267 if (asprintf(&bad
, "%s+0-%" PRIu64
"%s", prefix
, done
, suffix
) < 0)
271 *ret
= TAKE_PTR(bad
);
275 static const char *skip_slash(const char *path
) {
277 assert(path
[0] == '/');
282 static int verb_status(int argc
, char *argv
[], void *userdata
) {
284 _cleanup_free_
char *path
= NULL
, *prefix
= NULL
, *suffix
= NULL
, *good
= NULL
, *bad
= NULL
;
285 _cleanup_close_
int fd
= -1;
289 r
= acquire_boot_count_path(&path
, &prefix
, &left
, &done
, &suffix
);
290 if (r
== -EUNATCH
) { /* No boot count in place, then let's consider this a "clean" boot, as "good", "bad" or "indeterminate" don't apply. */
301 r
= make_good(prefix
, suffix
, &good
);
305 r
= make_bad(prefix
, done
, suffix
, &bad
);
309 log_debug("Booted file: %s%s\n"
310 "The same modified for 'good': %s%s\n"
311 "The same modified for 'bad': %s%s\n",
316 log_debug("Tries left: %" PRIu64
"\n"
317 "Tries done: %" PRIu64
"\n",
320 fd
= open(arg_path
, O_DIRECTORY
|O_CLOEXEC
|O_RDONLY
);
322 return log_error_errno(errno
, "Failed to open ESP '%s': %m", arg_path
);
324 if (faccessat(fd
, skip_slash(path
), F_OK
, 0) >= 0) {
325 puts("indeterminate");
329 return log_error_errno(errno
, "Failed to check if '%s' exists: %m", path
);
331 if (faccessat(fd
, skip_slash(good
), F_OK
, 0) >= 0) {
336 return log_error_errno(errno
, "Failed to check if '%s' exists: %m", good
);
338 if (faccessat(fd
, skip_slash(bad
), F_OK
, 0) >= 0) {
343 return log_error_errno(errno
, "Failed to check if '%s' exists: %m", bad
);
345 return log_error_errno(errno
, "Couldn't determine boot state: %m");
348 static int verb_set(int argc
, char *argv
[], void *userdata
) {
349 _cleanup_free_
char *path
= NULL
, *prefix
= NULL
, *suffix
= NULL
, *good
= NULL
, *bad
= NULL
, *parent
= NULL
;
350 const char *target
, *source1
, *source2
;
351 _cleanup_close_
int fd
= -1;
355 r
= acquire_boot_count_path(&path
, &prefix
, NULL
, &done
, &suffix
);
356 if (r
== -EUNATCH
) /* acquire_boot_count_path() won't log on its own for this specific error */
357 return log_error_errno(r
, "Not booted with boot counting in effect.");
365 r
= make_good(prefix
, suffix
, &good
);
369 r
= make_bad(prefix
, done
, suffix
, &bad
);
373 fd
= open(arg_path
, O_DIRECTORY
|O_CLOEXEC
|O_RDONLY
);
375 return log_error_errno(errno
, "Failed to open ESP '%s': %m", arg_path
);
377 /* Figure out what rename to what */
378 if (streq(argv
[0], "good")) {
381 source2
= bad
; /* Maybe this boot was previously marked as 'bad'? */
382 } else if (streq(argv
[0], "bad")) {
385 source2
= good
; /* Maybe this boot was previously marked as 'good'? */
387 assert(streq(argv
[0], "indeterminate"));
393 r
= rename_noreplace(fd
, skip_slash(source1
), fd
, skip_slash(target
));
396 else if (r
== -ENOENT
) {
398 r
= rename_noreplace(fd
, skip_slash(source2
), fd
, skip_slash(target
));
401 else if (r
== -ENOENT
) {
403 if (access(target
, F_OK
) >= 0) /* Hmm, if we can't find either source file, maybe the destination already exists? */
406 return log_error_errno(r
, "Can't find boot counter source file for '%s': %m", target
);
408 return log_error_errno(r
, "Failed to rename '%s' to '%s': %m", source2
, target
);
410 log_debug("Successfully renamed '%s' to '%s'.", source2
, target
);
413 return log_error_errno(r
, "Failed to rename '%s' to '%s': %m", source1
, target
);
415 log_debug("Successfully renamed '%s' to '%s'.", source1
, target
);
417 /* First, fsync() the directory these files are located in */
418 parent
= dirname_malloc(path
);
422 r
= fsync_path_at(fd
, skip_slash(parent
));
424 log_debug_errno(errno
, "Failed to synchronize image directory, ignoring: %m");
426 /* Secondly, syncfs() the whole file system these files are located in */
428 log_debug_errno(errno
, "Failed to synchronize ESP, ignoring: %m");
430 log_info("Marked boot as '%s'. (Boot attempt counter is at %" PRIu64
".)", argv
[0], done
);
435 log_debug("Operation already executed before, not doing anything.");
439 int main(int argc
, char *argv
[]) {
441 static const Verb verbs
[] = {
442 { "help", VERB_ANY
, VERB_ANY
, 0, help
},
443 { "status", VERB_ANY
, 1, VERB_DEFAULT
, verb_status
},
444 { "good", VERB_ANY
, 1, VERB_MUST_BE_ROOT
, verb_set
},
445 { "bad", VERB_ANY
, 1, VERB_MUST_BE_ROOT
, verb_set
},
446 { "indeterminate", VERB_ANY
, 1, VERB_MUST_BE_ROOT
, verb_set
},
452 log_parse_environment();
455 r
= parse_argv(argc
, argv
);
459 if (detect_container() > 0) {
460 log_error("Marking a boot is not supported in containers.");
465 if (!is_efi_boot()) {
466 log_error("Marking a boot is only supported on EFI systems.");
471 r
= dispatch_verb(argc
, argv
, verbs
, NULL
);
476 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;