1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
6 #include "alloc-util.h"
9 #include "devnum-util.h"
11 #include "efi-loader.h"
17 #include "main-func.h"
18 #include "parse-util.h"
19 #include "path-util.h"
20 #include "pretty-print.h"
21 #include "sync-util.h"
22 #include "terminal-util.h"
26 static char **arg_path
= NULL
;
28 STATIC_DESTRUCTOR_REGISTER(arg_path
, strv_freep
);
30 static int help(int argc
, char *argv
[], void *userdata
) {
31 _cleanup_free_
char *link
= NULL
;
34 r
= terminal_urlify_man("systemd-bless-boot.service", "8", &link
);
38 printf("%s [OPTIONS...] COMMAND\n"
39 "\n%sMark the boot process as good or bad.%s\n"
41 " status Show status of current boot loader entry\n"
42 " good Mark this boot as good\n"
43 " bad Mark this boot as bad\n"
44 " indeterminate Undo any marking as good or bad\n"
46 " -h --help Show this help\n"
47 " --version Print version\n"
48 " --path=PATH Path to the $BOOT partition (may be used multiple times)\n"
49 "\nSee the %s for details.\n",
50 program_invocation_short_name
,
58 static int parse_argv(int argc
, char *argv
[]) {
64 static const struct option options
[] = {
65 { "help", no_argument
, NULL
, 'h' },
66 { "version", no_argument
, NULL
, ARG_VERSION
},
67 { "path", required_argument
, NULL
, ARG_PATH
},
76 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
87 r
= strv_extend(&arg_path
, optarg
);
102 static int acquire_path(void) {
103 _cleanup_free_
char *esp_path
= NULL
, *xbootldr_path
= NULL
;
104 dev_t esp_devid
= 0, xbootldr_devid
= 0;
108 if (!strv_isempty(arg_path
))
111 r
= find_esp_and_warn(NULL
, NULL
, /* unprivileged_mode= */ false, &esp_path
, NULL
, NULL
, NULL
, NULL
, &esp_devid
);
112 if (r
< 0 && r
!= -ENOKEY
) /* ENOKEY means not found, and is the only error the function won't log about on its own */
115 r
= find_xbootldr_and_warn(NULL
, NULL
, /* unprivileged_mode= */ false, &xbootldr_path
, NULL
, &xbootldr_devid
);
116 if (r
< 0 && r
!= -ENOKEY
)
119 if (!esp_path
&& !xbootldr_path
)
120 return log_error_errno(SYNTHETIC_ERRNO(ENOENT
),
121 "Couldn't find $BOOT partition. It is recommended to mount it to /boot.\n"
122 "Alternatively, use --path= to specify path to mount point.");
124 if (esp_path
&& xbootldr_path
&& !devnum_set_and_equal(esp_devid
, xbootldr_devid
)) /* in case the two paths refer to the same inode, suppress one */
125 a
= strv_new(esp_path
, xbootldr_path
);
127 a
= strv_new(esp_path
);
129 a
= strv_new(xbootldr_path
);
133 strv_free_and_replace(arg_path
, a
);
136 _cleanup_free_
char *j
= NULL
;
138 j
= strv_join(arg_path
, ":");
139 log_debug("Using %s as boot loader drop-in search path.", strna(j
));
145 static int parse_counter(
149 uint64_t *ret_done
) {
165 k
= strspn(e
, DIGITS
);
167 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
168 "Can't parse empty 'tries left' counter from LoaderBootCountPath: %s",
171 z
= strndupa_safe(e
, k
);
172 r
= safe_atou64(z
, &left
);
174 return log_error_errno(r
, "Failed to parse 'tries left' counter from LoaderBootCountPath: %s", path
);
181 k
= strspn(e
, DIGITS
);
182 if (k
== 0) /* If there's a "-" there also needs to be at least one digit */
183 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
184 "Can't parse empty 'tries done' counter from LoaderBootCountPath: %s",
187 z
= strndupa_safe(e
, k
);
188 r
= safe_atou64(z
, &done
);
190 return log_error_errno(r
, "Failed to parse 'tries done' counter from LoaderBootCountPath: %s", path
);
197 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.");
210 static int acquire_boot_count_path(
217 _cleanup_free_
char *path
= NULL
, *prefix
= NULL
, *suffix
= NULL
;
218 const char *last
, *e
;
222 r
= efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderBootCountPath
), &path
);
224 return -EUNATCH
; /* in this case, let the caller print a message */
226 return log_error_errno(r
, "Failed to read LoaderBootCountPath EFI variable: %m");
228 efi_tilt_backslashes(path
);
230 if (!path_is_normalized(path
))
231 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
232 "Path read from LoaderBootCountPath is not normalized, refusing: %s",
235 if (!path_is_absolute(path
))
236 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
237 "Path read from LoaderBootCountPath is not absolute, refusing: %s",
240 last
= last_path_component(path
);
241 e
= strrchr(last
, '+');
243 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
244 "Path read from LoaderBootCountPath does not contain a counter, refusing: %s",
248 prefix
= strndup(path
, e
- path
);
253 r
= parse_counter(path
, &e
, &left
, &done
);
262 *ret_suffix
= TAKE_PTR(suffix
);
266 *ret_path
= TAKE_PTR(path
);
268 *ret_prefix
= TAKE_PTR(prefix
);
277 static int make_good(const char *prefix
, const char *suffix
, char **ret
) {
278 _cleanup_free_
char *good
= NULL
;
284 /* Generate the path we'd use on good boots. This one is easy. If we are successful, we simple drop the counter
285 * pair entirely from the name. After all, we know all is good, and the logs will contain information about the
286 * tries we needed to come here, hence it's safe to drop the counters from the name. */
288 good
= strjoin(prefix
, suffix
);
292 *ret
= TAKE_PTR(good
);
296 static int make_bad(const char *prefix
, uint64_t done
, const char *suffix
, char **ret
) {
297 _cleanup_free_
char *bad
= NULL
;
303 /* Generate the path we'd use on bad boots. Let's simply set the 'left' counter to zero, and keep the 'done'
304 * counter. The information might be interesting to boot loaders, after all. */
307 bad
= strjoin(prefix
, "+0", suffix
);
311 if (asprintf(&bad
, "%s+0-%" PRIu64
"%s", prefix
, done
, suffix
) < 0)
315 *ret
= TAKE_PTR(bad
);
319 static const char *skip_slash(const char *path
) {
321 assert(path
[0] == '/');
326 static int verb_status(int argc
, char *argv
[], void *userdata
) {
327 _cleanup_free_
char *path
= NULL
, *prefix
= NULL
, *suffix
= NULL
, *good
= NULL
, *bad
= NULL
;
331 r
= acquire_boot_count_path(&path
, &prefix
, &left
, &done
, &suffix
);
332 if (r
== -EUNATCH
) { /* No boot count in place, then let's consider this a "clean" boot, as "good", "bad" or "indeterminate" don't apply. */
343 r
= make_good(prefix
, suffix
, &good
);
347 r
= make_bad(prefix
, done
, suffix
, &bad
);
351 log_debug("Booted file: %s\n"
352 "The same modified for 'good': %s\n"
353 "The same modified for 'bad': %s\n",
358 log_debug("Tries left: %" PRIu64
"\n"
359 "Tries done: %" PRIu64
"\n",
362 STRV_FOREACH(p
, arg_path
) {
363 _cleanup_close_
int fd
= -EBADF
;
365 fd
= open(*p
, O_DIRECTORY
|O_CLOEXEC
|O_RDONLY
);
370 return log_error_errno(errno
, "Failed to open $BOOT partition '%s': %m", *p
);
373 if (faccessat(fd
, skip_slash(path
), F_OK
, 0) >= 0) {
374 puts("indeterminate");
378 return log_error_errno(errno
, "Failed to check if '%s' exists: %m", path
);
380 if (faccessat(fd
, skip_slash(good
), F_OK
, 0) >= 0) {
386 return log_error_errno(errno
, "Failed to check if '%s' exists: %m", good
);
388 if (faccessat(fd
, skip_slash(bad
), F_OK
, 0) >= 0) {
393 return log_error_errno(errno
, "Failed to check if '%s' exists: %m", bad
);
395 /* We didn't find any of the three? If so, let's try the next directory, before we give up. */
398 return log_error_errno(SYNTHETIC_ERRNO(EBUSY
), "Couldn't determine boot state: %m");
401 static int verb_set(int argc
, char *argv
[], void *userdata
) {
402 _cleanup_free_
char *path
= NULL
, *prefix
= NULL
, *suffix
= NULL
, *good
= NULL
, *bad
= NULL
;
403 const char *target
, *source1
, *source2
;
407 r
= acquire_boot_count_path(&path
, &prefix
, NULL
, &done
, &suffix
);
408 if (r
== -EUNATCH
) /* acquire_boot_count_path() won't log on its own for this specific error */
409 return log_error_errno(r
, "Not booted with boot counting in effect.");
417 r
= make_good(prefix
, suffix
, &good
);
421 r
= make_bad(prefix
, done
, suffix
, &bad
);
425 /* Figure out what rename to what */
426 if (streq(argv
[0], "good")) {
429 source2
= bad
; /* Maybe this boot was previously marked as 'bad'? */
430 } else if (streq(argv
[0], "bad")) {
433 source2
= good
; /* Maybe this boot was previously marked as 'good'? */
435 assert(streq(argv
[0], "indeterminate"));
441 STRV_FOREACH(p
, arg_path
) {
442 _cleanup_close_
int fd
= -EBADF
;
444 fd
= open(*p
, O_DIRECTORY
|O_CLOEXEC
|O_RDONLY
);
446 return log_error_errno(errno
, "Failed to open $BOOT partition '%s': %m", *p
);
448 r
= rename_noreplace(fd
, skip_slash(source1
), fd
, skip_slash(target
));
453 r
= rename_noreplace(fd
, skip_slash(source2
), fd
, skip_slash(target
));
458 if (faccessat(fd
, skip_slash(target
), F_OK
, 0) >= 0) /* Hmm, if we can't find either source file, maybe the destination already exists? */
462 return log_error_errno(errno
, "Failed to determine if %s already exists: %m", target
);
464 /* We found none of the snippets here, try the next directory */
468 return log_error_errno(r
, "Failed to rename '%s' to '%s': %m", source2
, target
);
470 log_debug("Successfully renamed '%s' to '%s'.", source2
, target
);
472 return log_error_errno(r
, "Failed to rename '%s' to '%s': %m", source1
, target
);
474 log_debug("Successfully renamed '%s' to '%s'.", source1
, target
);
476 /* First, fsync() the directory these files are located in */
477 r
= fsync_parent_at(fd
, skip_slash(target
));
479 log_debug_errno(errno
, "Failed to synchronize image directory, ignoring: %m");
481 /* Secondly, syncfs() the whole file system these files are located in */
483 log_debug_errno(errno
, "Failed to synchronize $BOOT partition, ignoring: %m");
485 log_info("Marked boot as '%s'. (Boot attempt counter is at %" PRIu64
".)", argv
[0], done
);
489 log_error_errno(SYNTHETIC_ERRNO(EBUSY
), "Can't find boot counter source file for '%s': %m", target
);
493 log_debug("Operation already executed before, not doing anything.");
497 static int run(int argc
, char *argv
[]) {
498 static const Verb verbs
[] = {
499 { "help", VERB_ANY
, VERB_ANY
, 0, help
},
500 { "status", VERB_ANY
, 1, VERB_DEFAULT
, verb_status
},
501 { "good", VERB_ANY
, 1, 0, verb_set
},
502 { "bad", VERB_ANY
, 1, 0, verb_set
},
503 { "indeterminate", VERB_ANY
, 1, 0, verb_set
},
509 log_parse_environment();
512 r
= parse_argv(argc
, argv
);
516 if (detect_container() > 0)
517 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
),
518 "Marking a boot is not supported in containers.");
521 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
),
522 "Marking a boot is only supported on EFI systems.");
524 return dispatch_verb(argc
, argv
, verbs
, NULL
);
527 DEFINE_MAIN_FUNCTION(run
);