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