1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <getopt.h>
4 #include <stdlib.h>
5
6 #include "alloc-util.h"
7 #include "bootspec.h"
8 #include "devnum-util.h"
9 #include "efi-api.h"
10 #include "efi-loader.h"
11 #include "efivars.h"
12 #include "fd-util.h"
13 #include "find-esp.h"
14 #include "fs-util.h"
15 #include "log.h"
16 #include "main-func.h"
17 #include "parse-util.h"
18 #include "path-util.h"
19 #include "pretty-print.h"
20 #include "sync-util.h"
21 #include "terminal-util.h"
22 #include "util.h"
23 #include "verbs.h"
24 #include "virt.h"
25
26 static char **arg_path = NULL;
27
28 STATIC_DESTRUCTOR_REGISTER(arg_path, strv_freep);
29
help(int argc,char * argv[],void * userdata)30 static int help(int argc, char *argv[], void *userdata) {
31 _cleanup_free_ char *link = NULL;
32 int r;
33
34 r = terminal_urlify_man("systemd-bless-boot.service", "8", &link);
35 if (r < 0)
36 return log_oom();
37
38 printf("%s [OPTIONS...] COMMAND\n"
39 "\n%sMark the boot process as good or bad.%s\n"
40 "\nCommands:\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"
45 "\nOptions:\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,
51 ansi_highlight(),
52 ansi_normal(),
53 link);
54
55 return 0;
56 }
57
parse_argv(int argc,char * argv[])58 static int parse_argv(int argc, char *argv[]) {
59 enum {
60 ARG_PATH = 0x100,
61 ARG_VERSION,
62 };
63
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 },
68 {}
69 };
70
71 int c, r;
72
73 assert(argc >= 0);
74 assert(argv);
75
76 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
77 switch (c) {
78
79 case 'h':
80 help(0, NULL, NULL);
81 return 0;
82
83 case ARG_VERSION:
84 return version();
85
86 case ARG_PATH:
87 r = strv_extend(&arg_path, optarg);
88 if (r < 0)
89 return log_oom();
90 break;
91
92 case '?':
93 return -EINVAL;
94
95 default:
96 assert_not_reached();
97 }
98
99 return 1;
100 }
101
acquire_path(void)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;
105 char **a;
106 int r;
107
108 if (!strv_isempty(arg_path))
109 return 0;
110
111 r = find_esp_and_warn(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 */
113 return r;
114
115 r = find_xbootldr_and_warn(NULL, /* unprivileged_mode= */ false, &xbootldr_path, NULL, &xbootldr_devid);
116 if (r < 0 && r != -ENOKEY)
117 return r;
118
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.");
123
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);
126 else if (esp_path)
127 a = strv_new(esp_path);
128 else
129 a = strv_new(xbootldr_path);
130 if (!a)
131 return log_oom();
132
133 strv_free_and_replace(arg_path, a);
134
135 if (DEBUG_LOGGING) {
136 _cleanup_free_ char *j = NULL;
137
138 j = strv_join(arg_path, ":");
139 log_debug("Using %s as boot loader drop-in search path.", strna(j));
140 }
141
142 return 0;
143 }
144
parse_counter(const char * path,const char ** p,uint64_t * ret_left,uint64_t * ret_done)145 static int parse_counter(
146 const char *path,
147 const char **p,
148 uint64_t *ret_left,
149 uint64_t *ret_done) {
150
151 uint64_t left, done;
152 const char *z, *e;
153 size_t k;
154 int r;
155
156 assert(path);
157 assert(p);
158
159 e = *p;
160 assert(e);
161 assert(*e == '+');
162
163 e++;
164
165 k = strspn(e, DIGITS);
166 if (k == 0)
167 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
168 "Can't parse empty 'tries left' counter from LoaderBootCountPath: %s",
169 path);
170
171 z = strndupa_safe(e, k);
172 r = safe_atou64(z, &left);
173 if (r < 0)
174 return log_error_errno(r, "Failed to parse 'tries left' counter from LoaderBootCountPath: %s", path);
175
176 e += k;
177
178 if (*e == '-') {
179 e++;
180
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",
185 path);
186
187 z = strndupa_safe(e, k);
188 r = safe_atou64(z, &done);
189 if (r < 0)
190 return log_error_errno(r, "Failed to parse 'tries done' counter from LoaderBootCountPath: %s", path);
191
192 e += k;
193 } else
194 done = 0;
195
196 if (done == 0)
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.");
198
199 *p = e;
200
201 if (ret_left)
202 *ret_left = left;
203
204 if (ret_done)
205 *ret_done = done;
206
207 return 0;
208 }
209
acquire_boot_count_path(char ** ret_path,char ** ret_prefix,uint64_t * ret_left,uint64_t * ret_done,char ** ret_suffix)210 static int acquire_boot_count_path(
211 char **ret_path,
212 char **ret_prefix,
213 uint64_t *ret_left,
214 uint64_t *ret_done,
215 char **ret_suffix) {
216
217 _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL;
218 const char *last, *e;
219 uint64_t left, done;
220 int r;
221
222 r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderBootCountPath), &path);
223 if (r == -ENOENT)
224 return -EUNATCH; /* in this case, let the caller print a message */
225 if (r < 0)
226 return log_error_errno(r, "Failed to read LoaderBootCountPath EFI variable: %m");
227
228 efi_tilt_backslashes(path);
229
230 if (!path_is_normalized(path))
231 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
232 "Path read from LoaderBootCountPath is not normalized, refusing: %s",
233 path);
234
235 if (!path_is_absolute(path))
236 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
237 "Path read from LoaderBootCountPath is not absolute, refusing: %s",
238 path);
239
240 last = last_path_component(path);
241 e = strrchr(last, '+');
242 if (!e)
243 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
244 "Path read from LoaderBootCountPath does not contain a counter, refusing: %s",
245 path);
246
247 if (ret_prefix) {
248 prefix = strndup(path, e - path);
249 if (!prefix)
250 return log_oom();
251 }
252
253 r = parse_counter(path, &e, &left, &done);
254 if (r < 0)
255 return r;
256
257 if (ret_suffix) {
258 suffix = strdup(e);
259 if (!suffix)
260 return log_oom();
261
262 *ret_suffix = TAKE_PTR(suffix);
263 }
264
265 if (ret_path)
266 *ret_path = TAKE_PTR(path);
267 if (ret_prefix)
268 *ret_prefix = TAKE_PTR(prefix);
269 if (ret_left)
270 *ret_left = left;
271 if (ret_done)
272 *ret_done = done;
273
274 return 0;
275 }
276
make_good(const char * prefix,const char * suffix,char ** ret)277 static int make_good(const char *prefix, const char *suffix, char **ret) {
278 _cleanup_free_ char *good = NULL;
279
280 assert(prefix);
281 assert(suffix);
282 assert(ret);
283
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. */
287
288 good = strjoin(prefix, suffix);
289 if (!good)
290 return -ENOMEM;
291
292 *ret = TAKE_PTR(good);
293 return 0;
294 }
295
make_bad(const char * prefix,uint64_t done,const char * suffix,char ** ret)296 static int make_bad(const char *prefix, uint64_t done, const char *suffix, char **ret) {
297 _cleanup_free_ char *bad = NULL;
298
299 assert(prefix);
300 assert(suffix);
301 assert(ret);
302
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. */
305
306 if (done == 0) {
307 bad = strjoin(prefix, "+0", suffix);
308 if (!bad)
309 return -ENOMEM;
310 } else {
311 if (asprintf(&bad, "%s+0-%" PRIu64 "%s", prefix, done, suffix) < 0)
312 return -ENOMEM;
313 }
314
315 *ret = TAKE_PTR(bad);
316 return 0;
317 }
318
skip_slash(const char * path)319 static const char *skip_slash(const char *path) {
320 assert(path);
321 assert(path[0] == '/');
322
323 return path + 1;
324 }
325
verb_status(int argc,char * argv[],void * userdata)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;
328 uint64_t left, done;
329 int r;
330
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. */
333 puts("clean");
334 return 0;
335 }
336 if (r < 0)
337 return r;
338
339 r = acquire_path();
340 if (r < 0)
341 return r;
342
343 r = make_good(prefix, suffix, &good);
344 if (r < 0)
345 return log_oom();
346
347 r = make_bad(prefix, done, suffix, &bad);
348 if (r < 0)
349 return log_oom();
350
351 log_debug("Booted file: %s\n"
352 "The same modified for 'good': %s\n"
353 "The same modified for 'bad': %s\n",
354 path,
355 good,
356 bad);
357
358 log_debug("Tries left: %" PRIu64"\n"
359 "Tries done: %" PRIu64"\n",
360 left, done);
361
362 STRV_FOREACH(p, arg_path) {
363 _cleanup_close_ int fd = -1;
364
365 fd = open(*p, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
366 if (fd < 0) {
367 if (errno == ENOENT)
368 continue;
369
370 return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p);
371 }
372
373 if (faccessat(fd, skip_slash(path), F_OK, 0) >= 0) {
374 puts("indeterminate");
375 return 0;
376 }
377 if (errno != ENOENT)
378 return log_error_errno(errno, "Failed to check if '%s' exists: %m", path);
379
380 if (faccessat(fd, skip_slash(good), F_OK, 0) >= 0) {
381 puts("good");
382 return 0;
383 }
384
385 if (errno != ENOENT)
386 return log_error_errno(errno, "Failed to check if '%s' exists: %m", good);
387
388 if (faccessat(fd, skip_slash(bad), F_OK, 0) >= 0) {
389 puts("bad");
390 return 0;
391 }
392 if (errno != ENOENT)
393 return log_error_errno(errno, "Failed to check if '%s' exists: %m", bad);
394
395 /* We didn't find any of the three? If so, let's try the next directory, before we give up. */
396 }
397
398 return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Couldn't determine boot state: %m");
399 }
400
verb_set(int argc,char * argv[],void * userdata)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, *parent = NULL;
403 const char *target, *source1, *source2;
404 uint64_t done;
405 int r;
406
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.");
410 if (r < 0)
411 return r;
412
413 r = acquire_path();
414 if (r < 0)
415 return r;
416
417 r = make_good(prefix, suffix, &good);
418 if (r < 0)
419 return log_oom();
420
421 r = make_bad(prefix, done, suffix, &bad);
422 if (r < 0)
423 return log_oom();
424
425 /* Figure out what rename to what */
426 if (streq(argv[0], "good")) {
427 target = good;
428 source1 = path;
429 source2 = bad; /* Maybe this boot was previously marked as 'bad'? */
430 } else if (streq(argv[0], "bad")) {
431 target = bad;
432 source1 = path;
433 source2 = good; /* Maybe this boot was previously marked as 'good'? */
434 } else {
435 assert(streq(argv[0], "indeterminate"));
436 target = path;
437 source1 = good;
438 source2 = bad;
439 }
440
441 STRV_FOREACH(p, arg_path) {
442 _cleanup_close_ int fd = -1;
443
444 fd = open(*p, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
445 if (fd < 0)
446 return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p);
447
448 r = rename_noreplace(fd, skip_slash(source1), fd, skip_slash(target));
449 if (r == -EEXIST)
450 goto exists;
451 else if (r == -ENOENT) {
452
453 r = rename_noreplace(fd, skip_slash(source2), fd, skip_slash(target));
454 if (r == -EEXIST)
455 goto exists;
456 else if (r == -ENOENT) {
457
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? */
459 goto exists;
460
461 if (errno != ENOENT)
462 return log_error_errno(errno, "Failed to determine if %s already exists: %m", target);
463
464 /* We found none of the snippets here, try the next directory */
465 continue;
466 } else if (r < 0)
467 return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source2, target);
468 else
469 log_debug("Successfully renamed '%s' to '%s'.", source2, target);
470
471 } else if (r < 0)
472 return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source1, target);
473 else
474 log_debug("Successfully renamed '%s' to '%s'.", source1, target);
475
476 /* First, fsync() the directory these files are located in */
477 parent = dirname_malloc(target);
478 if (!parent)
479 return log_oom();
480
481 r = fsync_path_at(fd, skip_slash(parent));
482 if (r < 0)
483 log_debug_errno(errno, "Failed to synchronize image directory, ignoring: %m");
484
485 /* Secondly, syncfs() the whole file system these files are located in */
486 if (syncfs(fd) < 0)
487 log_debug_errno(errno, "Failed to synchronize $BOOT partition, ignoring: %m");
488
489 log_info("Marked boot as '%s'. (Boot attempt counter is at %" PRIu64".)", argv[0], done);
490 }
491
492 log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Can't find boot counter source file for '%s': %m", target);
493 return 1;
494
495 exists:
496 log_debug("Operation already executed before, not doing anything.");
497 return 0;
498 }
499
run(int argc,char * argv[])500 static int run(int argc, char *argv[]) {
501 static const Verb verbs[] = {
502 { "help", VERB_ANY, VERB_ANY, 0, help },
503 { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
504 { "good", VERB_ANY, 1, 0, verb_set },
505 { "bad", VERB_ANY, 1, 0, verb_set },
506 { "indeterminate", VERB_ANY, 1, 0, verb_set },
507 {}
508 };
509
510 int r;
511
512 log_parse_environment();
513 log_open();
514
515 r = parse_argv(argc, argv);
516 if (r <= 0)
517 return r;
518
519 if (detect_container() > 0)
520 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
521 "Marking a boot is not supported in containers.");
522
523 if (!is_efi_boot())
524 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
525 "Marking a boot is only supported on EFI systems.");
526
527 return dispatch_verb(argc, argv, verbs, NULL);
528 }
529
530 DEFINE_MAIN_FUNCTION(run);
531