]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/boot/bless-boot.c
tree-wide: use -EBADF for fd initialization
[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
319static const char *skip_slash(const char *path) {
320 assert(path);
321 assert(path[0] == '/');
322
323 return path + 1;
324}
325
326static int verb_status(int argc, char *argv[], void *userdata) {
36695e88 327 _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL;
36695e88
LP
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
2f88b2a0 339 r = acquire_path();
36695e88
LP
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
2f88b2a0
LP
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);
36695e88
LP
357
358 log_debug("Tries left: %" PRIu64"\n"
359 "Tries done: %" PRIu64"\n",
360 left, done);
361
2f88b2a0 362 STRV_FOREACH(p, arg_path) {
254d1313 363 _cleanup_close_ int fd = -EBADF;
36695e88 364
2f88b2a0
LP
365 fd = open(*p, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
366 if (fd < 0) {
367 if (errno == ENOENT)
368 continue;
36695e88 369
2f88b2a0
LP
370 return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p);
371 }
36695e88 372
2f88b2a0
LP
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. */
36695e88 396 }
36695e88 397
2f88b2a0 398 return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Couldn't determine boot state: %m");
36695e88
LP
399}
400
401static int verb_set(int argc, char *argv[], void *userdata) {
45519d13 402 _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL;
36695e88 403 const char *target, *source1, *source2;
36695e88
LP
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
2f88b2a0 413 r = acquire_path();
36695e88
LP
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
36695e88
LP
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
2f88b2a0 441 STRV_FOREACH(p, arg_path) {
254d1313 442 _cleanup_close_ int fd = -EBADF;
2f88b2a0
LP
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);
36695e88 447
2f88b2a0 448 r = rename_noreplace(fd, skip_slash(source1), fd, skip_slash(target));
36695e88
LP
449 if (r == -EEXIST)
450 goto exists;
c06103be 451 if (r == -ENOENT) {
36695e88 452
2f88b2a0
LP
453 r = rename_noreplace(fd, skip_slash(source2), fd, skip_slash(target));
454 if (r == -EEXIST)
36695e88 455 goto exists;
c06103be 456 if (r == -ENOENT) {
2f88b2a0
LP
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;
45519d13
LP
466 }
467 if (r < 0)
2f88b2a0 468 return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source2, target);
36695e88 469
45519d13 470 log_debug("Successfully renamed '%s' to '%s'.", source2, target);
36695e88 471 } else if (r < 0)
2f88b2a0 472 return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source1, target);
36695e88 473 else
2f88b2a0 474 log_debug("Successfully renamed '%s' to '%s'.", source1, target);
36695e88 475
2f88b2a0 476 /* First, fsync() the directory these files are located in */
45519d13 477 r = fsync_parent_at(fd, skip_slash(target));
2f88b2a0
LP
478 if (r < 0)
479 log_debug_errno(errno, "Failed to synchronize image directory, ignoring: %m");
36695e88 480
2f88b2a0
LP
481 /* Secondly, syncfs() the whole file system these files are located in */
482 if (syncfs(fd) < 0)
483 log_debug_errno(errno, "Failed to synchronize $BOOT partition, ignoring: %m");
36695e88 484
2f88b2a0
LP
485 log_info("Marked boot as '%s'. (Boot attempt counter is at %" PRIu64".)", argv[0], done);
486 }
36695e88 487
2f88b2a0 488 log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Can't find boot counter source file for '%s': %m", target);
36695e88
LP
489 return 1;
490
491exists:
492 log_debug("Operation already executed before, not doing anything.");
493 return 0;
494}
495
a8a7e5fc 496static int run(int argc, char *argv[]) {
36695e88 497 static const Verb verbs[] = {
ded3a403
ZJS
498 { "help", VERB_ANY, VERB_ANY, 0, help },
499 { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
500 { "good", VERB_ANY, 1, 0, verb_set },
501 { "bad", VERB_ANY, 1, 0, verb_set },
502 { "indeterminate", VERB_ANY, 1, 0, verb_set },
36695e88
LP
503 {}
504 };
505
506 int r;
507
508 log_parse_environment();
509 log_open();
510
511 r = parse_argv(argc, argv);
512 if (r <= 0)
a8a7e5fc 513 return r;
36695e88 514
baaa35ad
ZJS
515 if (detect_container() > 0)
516 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
517 "Marking a boot is not supported in containers.");
36695e88 518
baaa35ad
ZJS
519 if (!is_efi_boot())
520 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
521 "Marking a boot is only supported on EFI systems.");
36695e88 522
a8a7e5fc 523 return dispatch_verb(argc, argv, verbs, NULL);
36695e88 524}
a8a7e5fc
YW
525
526DEFINE_MAIN_FUNCTION(run);