]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/boot/bless-boot.c
Merge pull request #31754 from YHNdnzj/journal-fd-namespace
[thirdparty/systemd.git] / src / boot / bless-boot.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
36695e88
LP
2
3#include <getopt.h>
4#include <stdlib.h>
5
6#include "alloc-util.h"
7#include "bootspec.h"
d6b4d1c7 8#include "build.h"
7176f06c 9#include "devnum-util.h"
7be4b236 10#include "efi-api.h"
0bb2f0f1 11#include "efi-loader.h"
36695e88
LP
12#include "efivars.h"
13#include "fd-util.h"
e94830c0 14#include "find-esp.h"
36695e88
LP
15#include "fs-util.h"
16#include "log.h"
5e332028 17#include "main-func.h"
36695e88
LP
18#include "parse-util.h"
19#include "path-util.h"
353b2baa 20#include "pretty-print.h"
bf819d3a 21#include "sync-util.h"
353b2baa 22#include "terminal-util.h"
36695e88
LP
23#include "verbs.h"
24#include "virt.h"
25
2f88b2a0 26static char **arg_path = NULL;
36695e88 27
2f88b2a0 28STATIC_DESTRUCTOR_REGISTER(arg_path, strv_freep);
a8a7e5fc 29
36695e88 30static int help(int argc, char *argv[], void *userdata) {
353b2baa
LP
31 _cleanup_free_ char *link = NULL;
32 int r;
36695e88 33
353b2baa
LP
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"
ddd8e23d 41 " status Show status of current boot loader entry\n"
353b2baa
LP
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"
36695e88
LP
46 " -h --help Show this help\n"
47 " --version Print version\n"
2f88b2a0 48 " --path=PATH Path to the $BOOT partition (may be used multiple times)\n"
bc556335
DDM
49 "\nSee the %s for details.\n",
50 program_invocation_short_name,
51 ansi_highlight(),
52 ansi_normal(),
53 link);
36695e88
LP
54
55 return 0;
56}
57
58static 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:
2f88b2a0 87 r = strv_extend(&arg_path, optarg);
36695e88
LP
88 if (r < 0)
89 return log_oom();
90 break;
91
92 case '?':
93 return -EINVAL;
94
95 default:
04499a70 96 assert_not_reached();
36695e88
LP
97 }
98
99 return 1;
100}
101
2f88b2a0
LP
102static int acquire_path(void) {
103 _cleanup_free_ char *esp_path = NULL, *xbootldr_path = NULL;
f63b5ad9 104 dev_t esp_devid = 0, xbootldr_devid = 0;
2f88b2a0 105 char **a;
36695e88
LP
106 int r;
107
2f88b2a0
LP
108 if (!strv_isempty(arg_path))
109 return 0;
110
80a2381d 111 r = find_esp_and_warn(NULL, NULL, /* unprivileged_mode= */ false, &esp_path, NULL, NULL, NULL, NULL, &esp_devid);
2f88b2a0
LP
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
80a2381d 115 r = find_xbootldr_and_warn(NULL, NULL, /* unprivileged_mode= */ false, &xbootldr_path, NULL, &xbootldr_devid);
2f88b2a0 116 if (r < 0 && r != -ENOKEY)
36695e88
LP
117 return r;
118
2f88b2a0
LP
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
7176f06c 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 */
2f88b2a0 125 a = strv_new(esp_path, xbootldr_path);
f63b5ad9
LP
126 else if (esp_path)
127 a = strv_new(esp_path);
2f88b2a0
LP
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) {
c2b2df60 136 _cleanup_free_ char *j = NULL;
2f88b2a0
LP
137
138 j = strv_join(arg_path, ":");
f63b5ad9 139 log_debug("Using %s as boot loader drop-in search path.", strna(j));
2f88b2a0 140 }
36695e88
LP
141
142 return 0;
143}
144
145static 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);
baaa35ad
ZJS
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);
36695e88 170
2f82562b 171 z = strndupa_safe(e, k);
36695e88
LP
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);
baaa35ad
ZJS
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);
36695e88 186
2f82562b 187 z = strndupa_safe(e, k);
36695e88
LP
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
210static 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
e6f055cb 222 r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderBootCountPath), &path);
36695e88
LP
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
baaa35ad
ZJS
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);
36695e88 234
baaa35ad
ZJS
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);
36695e88
LP
239
240 last = last_path_component(path);
241 e = strrchr(last, '+');
baaa35ad
ZJS
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);
36695e88
LP
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
277static 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
296static 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
36695e88 319static int verb_status(int argc, char *argv[], void *userdata) {
36695e88 320 _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL;
36695e88
LP
321 uint64_t left, done;
322 int r;
323
324 r = acquire_boot_count_path(&path, &prefix, &left, &done, &suffix);
325 if (r == -EUNATCH) { /* No boot count in place, then let's consider this a "clean" boot, as "good", "bad" or "indeterminate" don't apply. */
326 puts("clean");
327 return 0;
328 }
329 if (r < 0)
330 return r;
331
2f88b2a0 332 r = acquire_path();
36695e88
LP
333 if (r < 0)
334 return r;
335
336 r = make_good(prefix, suffix, &good);
337 if (r < 0)
338 return log_oom();
339
340 r = make_bad(prefix, done, suffix, &bad);
341 if (r < 0)
342 return log_oom();
343
2f88b2a0
LP
344 log_debug("Booted file: %s\n"
345 "The same modified for 'good': %s\n"
346 "The same modified for 'bad': %s\n",
347 path,
348 good,
349 bad);
36695e88
LP
350
351 log_debug("Tries left: %" PRIu64"\n"
352 "Tries done: %" PRIu64"\n",
353 left, done);
354
2f88b2a0 355 STRV_FOREACH(p, arg_path) {
254d1313 356 _cleanup_close_ int fd = -EBADF;
36695e88 357
2f88b2a0
LP
358 fd = open(*p, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
359 if (fd < 0) {
360 if (errno == ENOENT)
361 continue;
36695e88 362
2f88b2a0
LP
363 return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p);
364 }
36695e88 365
baaca3db 366 if (faccessat(fd, skip_leading_slash(path), F_OK, 0) >= 0) {
2f88b2a0
LP
367 puts("indeterminate");
368 return 0;
369 }
370 if (errno != ENOENT)
371 return log_error_errno(errno, "Failed to check if '%s' exists: %m", path);
372
baaca3db 373 if (faccessat(fd, skip_leading_slash(good), F_OK, 0) >= 0) {
2f88b2a0
LP
374 puts("good");
375 return 0;
376 }
377
378 if (errno != ENOENT)
379 return log_error_errno(errno, "Failed to check if '%s' exists: %m", good);
380
baaca3db 381 if (faccessat(fd, skip_leading_slash(bad), F_OK, 0) >= 0) {
2f88b2a0
LP
382 puts("bad");
383 return 0;
384 }
385 if (errno != ENOENT)
386 return log_error_errno(errno, "Failed to check if '%s' exists: %m", bad);
387
388 /* We didn't find any of the three? If so, let's try the next directory, before we give up. */
36695e88 389 }
36695e88 390
2f88b2a0 391 return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Couldn't determine boot state: %m");
36695e88
LP
392}
393
394static int verb_set(int argc, char *argv[], void *userdata) {
45519d13 395 _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL;
36695e88 396 const char *target, *source1, *source2;
36695e88
LP
397 uint64_t done;
398 int r;
399
400 r = acquire_boot_count_path(&path, &prefix, NULL, &done, &suffix);
401 if (r == -EUNATCH) /* acquire_boot_count_path() won't log on its own for this specific error */
402 return log_error_errno(r, "Not booted with boot counting in effect.");
403 if (r < 0)
404 return r;
405
2f88b2a0 406 r = acquire_path();
36695e88
LP
407 if (r < 0)
408 return r;
409
410 r = make_good(prefix, suffix, &good);
411 if (r < 0)
412 return log_oom();
413
414 r = make_bad(prefix, done, suffix, &bad);
415 if (r < 0)
416 return log_oom();
417
36695e88
LP
418 /* Figure out what rename to what */
419 if (streq(argv[0], "good")) {
420 target = good;
421 source1 = path;
422 source2 = bad; /* Maybe this boot was previously marked as 'bad'? */
423 } else if (streq(argv[0], "bad")) {
424 target = bad;
425 source1 = path;
426 source2 = good; /* Maybe this boot was previously marked as 'good'? */
427 } else {
428 assert(streq(argv[0], "indeterminate"));
429 target = path;
430 source1 = good;
431 source2 = bad;
432 }
433
2f88b2a0 434 STRV_FOREACH(p, arg_path) {
254d1313 435 _cleanup_close_ int fd = -EBADF;
2f88b2a0
LP
436
437 fd = open(*p, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
438 if (fd < 0)
439 return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p);
36695e88 440
baaca3db 441 r = rename_noreplace(fd, skip_leading_slash(source1), fd, skip_leading_slash(target));
36695e88
LP
442 if (r == -EEXIST)
443 goto exists;
c06103be 444 if (r == -ENOENT) {
36695e88 445
baaca3db 446 r = rename_noreplace(fd, skip_leading_slash(source2), fd, skip_leading_slash(target));
2f88b2a0 447 if (r == -EEXIST)
36695e88 448 goto exists;
c06103be 449 if (r == -ENOENT) {
2f88b2a0 450
baaca3db 451 if (faccessat(fd, skip_leading_slash(target), F_OK, 0) >= 0) /* Hmm, if we can't find either source file, maybe the destination already exists? */
2f88b2a0
LP
452 goto exists;
453
454 if (errno != ENOENT)
455 return log_error_errno(errno, "Failed to determine if %s already exists: %m", target);
456
457 /* We found none of the snippets here, try the next directory */
458 continue;
45519d13
LP
459 }
460 if (r < 0)
2f88b2a0 461 return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source2, target);
36695e88 462
45519d13 463 log_debug("Successfully renamed '%s' to '%s'.", source2, target);
36695e88 464 } else if (r < 0)
2f88b2a0 465 return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source1, target);
36695e88 466 else
2f88b2a0 467 log_debug("Successfully renamed '%s' to '%s'.", source1, target);
36695e88 468
2f88b2a0 469 /* First, fsync() the directory these files are located in */
baaca3db 470 r = fsync_parent_at(fd, skip_leading_slash(target));
2f88b2a0
LP
471 if (r < 0)
472 log_debug_errno(errno, "Failed to synchronize image directory, ignoring: %m");
36695e88 473
2f88b2a0
LP
474 /* Secondly, syncfs() the whole file system these files are located in */
475 if (syncfs(fd) < 0)
476 log_debug_errno(errno, "Failed to synchronize $BOOT partition, ignoring: %m");
36695e88 477
2f88b2a0 478 log_info("Marked boot as '%s'. (Boot attempt counter is at %" PRIu64".)", argv[0], done);
8f30a066 479 return 0;
2f88b2a0 480 }
36695e88 481
2f88b2a0 482 log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Can't find boot counter source file for '%s': %m", target);
36695e88
LP
483 return 1;
484
485exists:
486 log_debug("Operation already executed before, not doing anything.");
487 return 0;
488}
489
a8a7e5fc 490static int run(int argc, char *argv[]) {
36695e88 491 static const Verb verbs[] = {
ded3a403
ZJS
492 { "help", VERB_ANY, VERB_ANY, 0, help },
493 { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
494 { "good", VERB_ANY, 1, 0, verb_set },
495 { "bad", VERB_ANY, 1, 0, verb_set },
496 { "indeterminate", VERB_ANY, 1, 0, verb_set },
36695e88
LP
497 {}
498 };
499
500 int r;
501
502 log_parse_environment();
503 log_open();
504
505 r = parse_argv(argc, argv);
506 if (r <= 0)
a8a7e5fc 507 return r;
36695e88 508
baaa35ad
ZJS
509 if (detect_container() > 0)
510 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
511 "Marking a boot is not supported in containers.");
36695e88 512
baaa35ad
ZJS
513 if (!is_efi_boot())
514 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
515 "Marking a boot is only supported on EFI systems.");
36695e88 516
a8a7e5fc 517 return dispatch_verb(argc, argv, verbs, NULL);
36695e88 518}
a8a7e5fc
YW
519
520DEFINE_MAIN_FUNCTION(run);