]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/boot/bless-boot.c
Merge pull request #12753 from jrouleau/fix/hibernate-resume-timeout
[thirdparty/systemd.git] / src / boot / bless-boot.c
CommitLineData
36695e88
LP
1/* SPDX-License-Identifier: LGPL-2.1+ */
2
3#include <getopt.h>
4#include <stdlib.h>
5
6#include "alloc-util.h"
7#include "bootspec.h"
8#include "efivars.h"
9#include "fd-util.h"
10#include "fs-util.h"
11#include "log.h"
5e332028 12#include "main-func.h"
36695e88
LP
13#include "parse-util.h"
14#include "path-util.h"
15#include "util.h"
16#include "verbs.h"
17#include "virt.h"
18
2f88b2a0 19static char **arg_path = NULL;
36695e88 20
2f88b2a0 21STATIC_DESTRUCTOR_REGISTER(arg_path, strv_freep);
a8a7e5fc 22
36695e88
LP
23static int help(int argc, char *argv[], void *userdata) {
24
25 printf("%s [COMMAND] [OPTIONS...]\n"
26 "\n"
27 "Mark the boot process as good or bad.\n\n"
28 " -h --help Show this help\n"
29 " --version Print version\n"
2f88b2a0 30 " --path=PATH Path to the $BOOT partition (may be used multiple times)\n"
36695e88
LP
31 "\n"
32 "Commands:\n"
33 " good Mark this boot as good\n"
34 " bad Mark this boot as bad\n"
35 " indeterminate Undo any marking as good or bad\n",
36 program_invocation_short_name);
37
38 return 0;
39}
40
41static int parse_argv(int argc, char *argv[]) {
42 enum {
43 ARG_PATH = 0x100,
44 ARG_VERSION,
45 };
46
47 static const struct option options[] = {
48 { "help", no_argument, NULL, 'h' },
49 { "version", no_argument, NULL, ARG_VERSION },
50 { "path", required_argument, NULL, ARG_PATH },
51 {}
52 };
53
54 int c, r;
55
56 assert(argc >= 0);
57 assert(argv);
58
59 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
60 switch (c) {
61
62 case 'h':
63 help(0, NULL, NULL);
64 return 0;
65
66 case ARG_VERSION:
67 return version();
68
69 case ARG_PATH:
2f88b2a0 70 r = strv_extend(&arg_path, optarg);
36695e88
LP
71 if (r < 0)
72 return log_oom();
73 break;
74
75 case '?':
76 return -EINVAL;
77
78 default:
79 assert_not_reached("Unknown option");
80 }
81
82 return 1;
83}
84
2f88b2a0
LP
85static int acquire_path(void) {
86 _cleanup_free_ char *esp_path = NULL, *xbootldr_path = NULL;
87 char **a;
36695e88
LP
88 int r;
89
2f88b2a0
LP
90 if (!strv_isempty(arg_path))
91 return 0;
92
93 r = find_esp_and_warn(NULL, false, &esp_path, NULL, NULL, NULL, NULL);
94 if (r < 0 && r != -ENOKEY) /* ENOKEY means not found, and is the only error the function won't log about on its own */
95 return r;
96
97 r = find_xbootldr_and_warn(NULL, false, &xbootldr_path, NULL);
98 if (r < 0 && r != -ENOKEY)
36695e88
LP
99 return r;
100
2f88b2a0
LP
101 if (!esp_path && !xbootldr_path)
102 return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
103 "Couldn't find $BOOT partition. It is recommended to mount it to /boot.\n"
104 "Alternatively, use --path= to specify path to mount point.");
105
106 if (esp_path)
107 a = strv_new(esp_path, xbootldr_path);
108 else
109 a = strv_new(xbootldr_path);
110 if (!a)
111 return log_oom();
112
113 strv_free_and_replace(arg_path, a);
114
115 if (DEBUG_LOGGING) {
116 _cleanup_free_ char *j;
117
118 j = strv_join(arg_path, ":");
119 log_debug("Using %s as boot loader drop-in search path.", j);
120 }
36695e88
LP
121
122 return 0;
123}
124
125static int parse_counter(
126 const char *path,
127 const char **p,
128 uint64_t *ret_left,
129 uint64_t *ret_done) {
130
131 uint64_t left, done;
132 const char *z, *e;
133 size_t k;
134 int r;
135
136 assert(path);
137 assert(p);
138
139 e = *p;
140 assert(e);
141 assert(*e == '+');
142
143 e++;
144
145 k = strspn(e, DIGITS);
baaa35ad
ZJS
146 if (k == 0)
147 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
148 "Can't parse empty 'tries left' counter from LoaderBootCountPath: %s",
149 path);
36695e88
LP
150
151 z = strndupa(e, k);
152 r = safe_atou64(z, &left);
153 if (r < 0)
154 return log_error_errno(r, "Failed to parse 'tries left' counter from LoaderBootCountPath: %s", path);
155
156 e += k;
157
158 if (*e == '-') {
159 e++;
160
161 k = strspn(e, DIGITS);
baaa35ad
ZJS
162 if (k == 0) /* If there's a "-" there also needs to be at least one digit */
163 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
164 "Can't parse empty 'tries done' counter from LoaderBootCountPath: %s",
165 path);
36695e88
LP
166
167 z = strndupa(e, k);
168 r = safe_atou64(z, &done);
169 if (r < 0)
170 return log_error_errno(r, "Failed to parse 'tries done' counter from LoaderBootCountPath: %s", path);
171
172 e += k;
173 } else
174 done = 0;
175
176 if (done == 0)
177 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.");
178
179 *p = e;
180
181 if (ret_left)
182 *ret_left = left;
183
184 if (ret_done)
185 *ret_done = done;
186
187 return 0;
188}
189
190static int acquire_boot_count_path(
191 char **ret_path,
192 char **ret_prefix,
193 uint64_t *ret_left,
194 uint64_t *ret_done,
195 char **ret_suffix) {
196
197 _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL;
198 const char *last, *e;
199 uint64_t left, done;
200 int r;
201
202 r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderBootCountPath", &path);
203 if (r == -ENOENT)
204 return -EUNATCH; /* in this case, let the caller print a message */
205 if (r < 0)
206 return log_error_errno(r, "Failed to read LoaderBootCountPath EFI variable: %m");
207
208 efi_tilt_backslashes(path);
209
baaa35ad
ZJS
210 if (!path_is_normalized(path))
211 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
212 "Path read from LoaderBootCountPath is not normalized, refusing: %s",
213 path);
36695e88 214
baaa35ad
ZJS
215 if (!path_is_absolute(path))
216 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
217 "Path read from LoaderBootCountPath is not absolute, refusing: %s",
218 path);
36695e88
LP
219
220 last = last_path_component(path);
221 e = strrchr(last, '+');
baaa35ad
ZJS
222 if (!e)
223 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
224 "Path read from LoaderBootCountPath does not contain a counter, refusing: %s",
225 path);
36695e88
LP
226
227 if (ret_prefix) {
228 prefix = strndup(path, e - path);
229 if (!prefix)
230 return log_oom();
231 }
232
233 r = parse_counter(path, &e, &left, &done);
234 if (r < 0)
235 return r;
236
237 if (ret_suffix) {
238 suffix = strdup(e);
239 if (!suffix)
240 return log_oom();
241
242 *ret_suffix = TAKE_PTR(suffix);
243 }
244
245 if (ret_path)
246 *ret_path = TAKE_PTR(path);
247 if (ret_prefix)
248 *ret_prefix = TAKE_PTR(prefix);
249 if (ret_left)
250 *ret_left = left;
251 if (ret_done)
252 *ret_done = done;
253
254 return 0;
255}
256
257static int make_good(const char *prefix, const char *suffix, char **ret) {
258 _cleanup_free_ char *good = NULL;
259
260 assert(prefix);
261 assert(suffix);
262 assert(ret);
263
264 /* Generate the path we'd use on good boots. This one is easy. If we are successful, we simple drop the counter
265 * pair entirely from the name. After all, we know all is good, and the logs will contain information about the
266 * tries we needed to come here, hence it's safe to drop the counters from the name. */
267
268 good = strjoin(prefix, suffix);
269 if (!good)
270 return -ENOMEM;
271
272 *ret = TAKE_PTR(good);
273 return 0;
274}
275
276static int make_bad(const char *prefix, uint64_t done, const char *suffix, char **ret) {
277 _cleanup_free_ char *bad = NULL;
278
279 assert(prefix);
280 assert(suffix);
281 assert(ret);
282
283 /* Generate the path we'd use on bad boots. Let's simply set the 'left' counter to zero, and keep the 'done'
284 * counter. The information might be interesting to boot loaders, after all. */
285
286 if (done == 0) {
287 bad = strjoin(prefix, "+0", suffix);
288 if (!bad)
289 return -ENOMEM;
290 } else {
291 if (asprintf(&bad, "%s+0-%" PRIu64 "%s", prefix, done, suffix) < 0)
292 return -ENOMEM;
293 }
294
295 *ret = TAKE_PTR(bad);
296 return 0;
297}
298
299static const char *skip_slash(const char *path) {
300 assert(path);
301 assert(path[0] == '/');
302
303 return path + 1;
304}
305
306static int verb_status(int argc, char *argv[], void *userdata) {
36695e88 307 _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL;
36695e88 308 uint64_t left, done;
2f88b2a0 309 char **p;
36695e88
LP
310 int r;
311
312 r = acquire_boot_count_path(&path, &prefix, &left, &done, &suffix);
313 if (r == -EUNATCH) { /* No boot count in place, then let's consider this a "clean" boot, as "good", "bad" or "indeterminate" don't apply. */
314 puts("clean");
315 return 0;
316 }
317 if (r < 0)
318 return r;
319
2f88b2a0 320 r = acquire_path();
36695e88
LP
321 if (r < 0)
322 return r;
323
324 r = make_good(prefix, suffix, &good);
325 if (r < 0)
326 return log_oom();
327
328 r = make_bad(prefix, done, suffix, &bad);
329 if (r < 0)
330 return log_oom();
331
2f88b2a0
LP
332 log_debug("Booted file: %s\n"
333 "The same modified for 'good': %s\n"
334 "The same modified for 'bad': %s\n",
335 path,
336 good,
337 bad);
36695e88
LP
338
339 log_debug("Tries left: %" PRIu64"\n"
340 "Tries done: %" PRIu64"\n",
341 left, done);
342
2f88b2a0
LP
343 STRV_FOREACH(p, arg_path) {
344 _cleanup_close_ int fd = -1;
36695e88 345
2f88b2a0
LP
346 fd = open(*p, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
347 if (fd < 0) {
348 if (errno == ENOENT)
349 continue;
36695e88 350
2f88b2a0
LP
351 return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p);
352 }
36695e88 353
2f88b2a0
LP
354 if (faccessat(fd, skip_slash(path), F_OK, 0) >= 0) {
355 puts("indeterminate");
356 return 0;
357 }
358 if (errno != ENOENT)
359 return log_error_errno(errno, "Failed to check if '%s' exists: %m", path);
360
361 if (faccessat(fd, skip_slash(good), F_OK, 0) >= 0) {
362 puts("good");
363 return 0;
364 }
365
366 if (errno != ENOENT)
367 return log_error_errno(errno, "Failed to check if '%s' exists: %m", good);
368
369 if (faccessat(fd, skip_slash(bad), F_OK, 0) >= 0) {
370 puts("bad");
371 return 0;
372 }
373 if (errno != ENOENT)
374 return log_error_errno(errno, "Failed to check if '%s' exists: %m", bad);
375
376 /* We didn't find any of the three? If so, let's try the next directory, before we give up. */
36695e88 377 }
36695e88 378
2f88b2a0 379 return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Couldn't determine boot state: %m");
36695e88
LP
380}
381
382static int verb_set(int argc, char *argv[], void *userdata) {
383 _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL, *parent = NULL;
384 const char *target, *source1, *source2;
36695e88 385 uint64_t done;
2f88b2a0 386 char **p;
36695e88
LP
387 int r;
388
389 r = acquire_boot_count_path(&path, &prefix, NULL, &done, &suffix);
390 if (r == -EUNATCH) /* acquire_boot_count_path() won't log on its own for this specific error */
391 return log_error_errno(r, "Not booted with boot counting in effect.");
392 if (r < 0)
393 return r;
394
2f88b2a0 395 r = acquire_path();
36695e88
LP
396 if (r < 0)
397 return r;
398
399 r = make_good(prefix, suffix, &good);
400 if (r < 0)
401 return log_oom();
402
403 r = make_bad(prefix, done, suffix, &bad);
404 if (r < 0)
405 return log_oom();
406
36695e88
LP
407 /* Figure out what rename to what */
408 if (streq(argv[0], "good")) {
409 target = good;
410 source1 = path;
411 source2 = bad; /* Maybe this boot was previously marked as 'bad'? */
412 } else if (streq(argv[0], "bad")) {
413 target = bad;
414 source1 = path;
415 source2 = good; /* Maybe this boot was previously marked as 'good'? */
416 } else {
417 assert(streq(argv[0], "indeterminate"));
418 target = path;
419 source1 = good;
420 source2 = bad;
421 }
422
2f88b2a0
LP
423 STRV_FOREACH(p, arg_path) {
424 _cleanup_close_ int fd = -1;
425
426 fd = open(*p, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
427 if (fd < 0)
428 return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p);
36695e88 429
2f88b2a0 430 r = rename_noreplace(fd, skip_slash(source1), fd, skip_slash(target));
36695e88
LP
431 if (r == -EEXIST)
432 goto exists;
433 else if (r == -ENOENT) {
434
2f88b2a0
LP
435 r = rename_noreplace(fd, skip_slash(source2), fd, skip_slash(target));
436 if (r == -EEXIST)
36695e88 437 goto exists;
2f88b2a0
LP
438 else if (r == -ENOENT) {
439
440 if (faccessat(fd, skip_slash(target), F_OK, 0) >= 0) /* Hmm, if we can't find either source file, maybe the destination already exists? */
441 goto exists;
442
443 if (errno != ENOENT)
444 return log_error_errno(errno, "Failed to determine if %s already exists: %m", target);
445
446 /* We found none of the snippets here, try the next directory */
447 continue;
448 } else if (r < 0)
449 return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source2, target);
450 else
451 log_debug("Successfully renamed '%s' to '%s'.", source2, target);
36695e88 452
36695e88 453 } else if (r < 0)
2f88b2a0 454 return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source1, target);
36695e88 455 else
2f88b2a0 456 log_debug("Successfully renamed '%s' to '%s'.", source1, target);
36695e88 457
2f88b2a0
LP
458 /* First, fsync() the directory these files are located in */
459 parent = dirname_malloc(target);
460 if (!parent)
461 return log_oom();
36695e88 462
2f88b2a0
LP
463 r = fsync_path_at(fd, skip_slash(parent));
464 if (r < 0)
465 log_debug_errno(errno, "Failed to synchronize image directory, ignoring: %m");
36695e88 466
2f88b2a0
LP
467 /* Secondly, syncfs() the whole file system these files are located in */
468 if (syncfs(fd) < 0)
469 log_debug_errno(errno, "Failed to synchronize $BOOT partition, ignoring: %m");
36695e88 470
2f88b2a0
LP
471 log_info("Marked boot as '%s'. (Boot attempt counter is at %" PRIu64".)", argv[0], done);
472 }
36695e88 473
2f88b2a0 474 log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Can't find boot counter source file for '%s': %m", target);
36695e88
LP
475 return 1;
476
477exists:
478 log_debug("Operation already executed before, not doing anything.");
479 return 0;
480}
481
a8a7e5fc 482static int run(int argc, char *argv[]) {
36695e88 483 static const Verb verbs[] = {
ded3a403
ZJS
484 { "help", VERB_ANY, VERB_ANY, 0, help },
485 { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
486 { "good", VERB_ANY, 1, 0, verb_set },
487 { "bad", VERB_ANY, 1, 0, verb_set },
488 { "indeterminate", VERB_ANY, 1, 0, verb_set },
36695e88
LP
489 {}
490 };
491
492 int r;
493
494 log_parse_environment();
495 log_open();
496
497 r = parse_argv(argc, argv);
498 if (r <= 0)
a8a7e5fc 499 return r;
36695e88 500
baaa35ad
ZJS
501 if (detect_container() > 0)
502 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
503 "Marking a boot is not supported in containers.");
36695e88 504
baaa35ad
ZJS
505 if (!is_efi_boot())
506 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
507 "Marking a boot is only supported on EFI systems.");
36695e88 508
a8a7e5fc 509 return dispatch_verb(argc, argv, verbs, NULL);
36695e88 510}
a8a7e5fc
YW
511
512DEFINE_MAIN_FUNCTION(run);