• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 15057632786

15 May 2025 09:01PM UTC coverage: 72.267% (+0.02%) from 72.244%
15057632786

push

github

bluca
man: document how to hook stuff into system wakeup

Fixes: #6364

298523 of 413084 relevant lines covered (72.27%)

738132.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/bless-boot/bless-boot.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <getopt.h>
4
#include <unistd.h>
5

6
#include "alloc-util.h"
7
#include "build.h"
8
#include "devnum-util.h"
9
#include "efivars.h"
10
#include "fd-util.h"
11
#include "find-esp.h"
12
#include "fs-util.h"
13
#include "log.h"
14
#include "main-func.h"
15
#include "parse-util.h"
16
#include "path-util.h"
17
#include "pretty-print.h"
18
#include "string-util.h"
19
#include "strv.h"
20
#include "sync-util.h"
21
#include "verbs.h"
22
#include "virt.h"
23

24
static char **arg_path = NULL;
25

26
STATIC_DESTRUCTOR_REGISTER(arg_path, strv_freep);
×
27

28
static int help(int argc, char *argv[], void *userdata) {
×
29
        _cleanup_free_ char *link = NULL;
×
30
        int r;
×
31

32
        r = terminal_urlify_man("systemd-bless-boot.service", "8", &link);
×
33
        if (r < 0)
×
34
                return log_oom();
×
35

36
        printf("%s [OPTIONS...] COMMAND\n"
×
37
               "\n%sMark the boot process as good or bad.%s\n"
38
               "\nCommands:\n"
39
               "     status          Show status of current boot loader entry\n"
40
               "     good            Mark this boot as good\n"
41
               "     bad             Mark this boot as bad\n"
42
               "     indeterminate   Undo any marking as good or bad\n"
43
               "\nOptions:\n"
44
               "  -h --help          Show this help\n"
45
               "     --version       Print version\n"
46
               "     --path=PATH     Path to the $BOOT partition (may be used multiple times)\n"
47
               "\nSee the %s for details.\n",
48
               program_invocation_short_name,
49
               ansi_highlight(),
50
               ansi_normal(),
51
               link);
52

53
        return 0;
54
}
55

56
static int parse_argv(int argc, char *argv[]) {
×
57
        enum {
×
58
                ARG_PATH = 0x100,
59
                ARG_VERSION,
60
        };
61

62
        static const struct option options[] = {
×
63
                { "help",         no_argument,       NULL, 'h'              },
64
                { "version",      no_argument,       NULL, ARG_VERSION      },
65
                { "path",         required_argument, NULL, ARG_PATH         },
66
                {}
67
        };
68

69
        int c, r;
×
70

71
        assert(argc >= 0);
×
72
        assert(argv);
×
73

74
        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
×
75
                switch (c) {
×
76

77
                case 'h':
×
78
                        help(0, NULL, NULL);
×
79
                        return 0;
×
80

81
                case ARG_VERSION:
×
82
                        return version();
×
83

84
                case ARG_PATH:
×
85
                        r = strv_extend(&arg_path, optarg);
×
86
                        if (r < 0)
×
87
                                return log_oom();
×
88
                        break;
89

90
                case '?':
91
                        return -EINVAL;
92

93
                default:
×
94
                        assert_not_reached();
×
95
                }
96

97
        return 1;
98
}
99

100
static int acquire_path(void) {
×
101
        _cleanup_free_ char *esp_path = NULL, *xbootldr_path = NULL;
×
102
        dev_t esp_devid = 0, xbootldr_devid = 0;
×
103
        char **a;
×
104
        int r;
×
105

106
        if (!strv_isempty(arg_path))
×
107
                return 0;
108

109
        r = find_esp_and_warn(NULL, NULL, /* unprivileged_mode= */ false, &esp_path, NULL, NULL, NULL, NULL, &esp_devid);
×
110
        if (r < 0 && r != -ENOKEY) /* ENOKEY means not found, and is the only error the function won't log about on its own */
×
111
                return r;
112

113
        r = find_xbootldr_and_warn(NULL, NULL, /* unprivileged_mode= */ false, &xbootldr_path, NULL, &xbootldr_devid);
×
114
        if (r < 0 && r != -ENOKEY)
×
115
                return r;
116

117
        if (!esp_path && !xbootldr_path)
×
118
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
119
                                       "Couldn't find $BOOT partition. It is recommended to mount it to /boot.\n"
120
                                       "Alternatively, use --path= to specify path to mount point.");
121

122
        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 */
×
123
                a = strv_new(esp_path, xbootldr_path);
×
124
        else if (esp_path)
×
125
                a = strv_new(esp_path);
×
126
        else
127
                a = strv_new(xbootldr_path);
×
128
        if (!a)
×
129
                return log_oom();
×
130

131
        strv_free_and_replace(arg_path, a);
×
132

133
        if (DEBUG_LOGGING) {
×
134
                _cleanup_free_ char *j = NULL;
×
135

136
                j = strv_join(arg_path, ":");
×
137
                log_debug("Using %s as boot loader drop-in search path.", strna(j));
×
138
        }
139

140
        return 0;
141
}
142

143
static int parse_counter(
×
144
                const char *path,
145
                const char **p,
146
                uint64_t *ret_left,
147
                uint64_t *ret_done) {
148

149
        uint64_t left, done;
×
150
        const char *z, *e;
×
151
        size_t k;
×
152
        int r;
×
153

154
        assert(path);
×
155
        assert(p);
×
156

157
        e = *p;
×
158
        assert(e);
×
159
        assert(*e == '+');
×
160

161
        e++;
×
162

163
        k = strspn(e, DIGITS);
×
164
        if (k == 0)
×
165
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
166
                                       "Can't parse empty 'tries left' counter from LoaderBootCountPath: %s",
167
                                       path);
168

169
        z = strndupa_safe(e, k);
×
170
        r = safe_atou64(z, &left);
×
171
        if (r < 0)
×
172
                return log_error_errno(r, "Failed to parse 'tries left' counter from LoaderBootCountPath: %s", path);
×
173

174
        e += k;
×
175

176
        if (*e == '-') {
×
177
                e++;
×
178

179
                k = strspn(e, DIGITS);
×
180
                if (k == 0) /* If there's a "-" there also needs to be at least one digit */
×
181
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
182
                                               "Can't parse empty 'tries done' counter from LoaderBootCountPath: %s",
183
                                               path);
184

185
                z = strndupa_safe(e, k);
×
186
                r = safe_atou64(z, &done);
×
187
                if (r < 0)
×
188
                        return log_error_errno(r, "Failed to parse 'tries done' counter from LoaderBootCountPath: %s", path);
×
189

190
                e += k;
×
191
        } else
192
                done = 0;
×
193

194
        if (done == 0)
×
195
                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.");
×
196

197
        *p = e;
×
198

199
        if (ret_left)
×
200
                *ret_left = left;
×
201

202
        if (ret_done)
×
203
                *ret_done = done;
×
204

205
        return 0;
206
}
207

208
static int acquire_boot_count_path(
×
209
                char **ret_path,
210
                char **ret_prefix,
211
                uint64_t *ret_left,
212
                uint64_t *ret_done,
213
                char **ret_suffix) {
214

215
        int r;
×
216

217
        _cleanup_free_ char *path = NULL;
×
218
        r = efi_get_variable_path(EFI_LOADER_VARIABLE_STR("LoaderBootCountPath"), &path);
×
219
        if (r == -ENOENT)
×
220
                return -EUNATCH; /* in this case, let the caller print a message */
221
        if (r < 0)
×
222
                return log_error_errno(r, "Failed to read LoaderBootCountPath EFI variable: %m");
×
223

224
        if (!path_is_normalized(path))
×
225
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
226
                                       "Path read from LoaderBootCountPath is not normalized, refusing: %s",
227
                                       path);
228

229
        if (!path_is_absolute(path))
×
230
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
231
                                       "Path read from LoaderBootCountPath is not absolute, refusing: %s",
232
                                       path);
233

234
        const char *last = NULL;
×
235
        r = path_find_last_component(path, /* accept_dot_dot= */ false, /* next= */ NULL, &last);
×
236
        if (r < 0)
×
237
                return log_error_errno(r, "Failed to extract filename from LoaderBootCountPath '%s': %m", path);
×
238
        if (r == 0)
×
239
                return log_error_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), "LoaderBootCountPath '%s' refers to the root directory: %m", path);
×
240
        if (strlen(last) > (size_t) r)
×
241
                return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "LoaderBootCountPath '%s' refers to directory path, refusing.", path);
×
242

243
        const char *e = strrchr(last, '+');
×
244
        if (!e)
×
245
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
246
                                       "Path read from LoaderBootCountPath does not contain a counter, refusing: %s",
247
                                       path);
248

249
        _cleanup_free_ char *prefix = NULL;
×
250
        if (ret_prefix) {
×
251
                prefix = strndup(path, e - path);
×
252
                if (!prefix)
×
253
                        return log_oom();
×
254
        }
255

256
        uint64_t left, done;
×
257
        r = parse_counter(path, &e, &left, &done);
×
258
        if (r < 0)
×
259
                return r;
260

261
        _cleanup_free_ char *suffix = NULL;
×
262
        if (ret_suffix) {
×
263
                suffix = strdup(e);
×
264
                if (!suffix)
×
265
                        return log_oom();
×
266

267
                *ret_suffix = TAKE_PTR(suffix);
×
268
        }
269

270
        if (ret_path)
×
271
                *ret_path = TAKE_PTR(path);
×
272
        if (ret_prefix)
×
273
                *ret_prefix = TAKE_PTR(prefix);
×
274
        if (ret_left)
×
275
                *ret_left = left;
×
276
        if (ret_done)
×
277
                *ret_done = done;
×
278

279
        return 0;
280
}
281

282
static int make_good(const char *prefix, const char *suffix, char **ret) {
×
283
        _cleanup_free_ char *good = NULL;
×
284

285
        assert(prefix);
×
286
        assert(suffix);
×
287
        assert(ret);
×
288

289
        /* Generate the path we'd use on good boots. This one is easy. If we are successful, we simple drop the counter
290
         * pair entirely from the name. After all, we know all is good, and the logs will contain information about the
291
         * tries we needed to come here, hence it's safe to drop the counters from the name. */
292

293
        good = strjoin(prefix, suffix);
×
294
        if (!good)
×
295
                return -ENOMEM;
296

297
        *ret = TAKE_PTR(good);
×
298
        return 0;
×
299
}
300

301
static int make_bad(const char *prefix, uint64_t done, const char *suffix, char **ret) {
×
302
        _cleanup_free_ char *bad = NULL;
×
303

304
        assert(prefix);
×
305
        assert(suffix);
×
306
        assert(ret);
×
307

308
        /* Generate the path we'd use on bad boots. Let's simply set the 'left' counter to zero, and keep the 'done'
309
         * counter. The information might be interesting to boot loaders, after all. */
310

311
        if (done == 0) {
×
312
                bad = strjoin(prefix, "+0", suffix);
×
313
                if (!bad)
×
314
                        return -ENOMEM;
315
        } else {
316
                if (asprintf(&bad, "%s+0-%" PRIu64 "%s", prefix, done, suffix) < 0)
×
317
                        return -ENOMEM;
318
        }
319

320
        *ret = TAKE_PTR(bad);
×
321
        return 0;
×
322
}
323

324
static int verb_status(int argc, char *argv[], void *userdata) {
×
325
        _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL;
×
326
        uint64_t left, done;
×
327
        int r;
×
328

329
        r = acquire_boot_count_path(&path, &prefix, &left, &done, &suffix);
×
330
        if (r == -EUNATCH) { /* No boot count in place, then let's consider this a "clean" boot, as "good", "bad" or "indeterminate" don't apply. */
×
331
                puts("clean");
×
332
                return 0;
333
        }
334
        if (r < 0)
×
335
                return r;
336

337
        r = acquire_path();
×
338
        if (r < 0)
×
339
                return r;
340

341
        r = make_good(prefix, suffix, &good);
×
342
        if (r < 0)
×
343
                return log_oom();
×
344

345
        r = make_bad(prefix, done, suffix, &bad);
×
346
        if (r < 0)
×
347
                return log_oom();
×
348

349
        log_debug("Booted file: %s\n"
×
350
                  "The same modified for 'good': %s\n"
351
                  "The same modified for 'bad':  %s\n",
352
                  path,
353
                  good,
354
                  bad);
355

356
        log_debug("Tries left: %" PRIu64"\n"
×
357
                  "Tries done: %" PRIu64"\n",
358
                  left, done);
359

360
        STRV_FOREACH(p, arg_path) {
×
361
                _cleanup_close_ int fd = -EBADF;
×
362

363
                fd = open(*p, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
×
364
                if (fd < 0) {
×
365
                        if (errno == ENOENT)
×
366
                                continue;
×
367

368
                        return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p);
×
369
                }
370

371
                if (faccessat(fd, skip_leading_slash(path), F_OK, 0) >= 0) {
×
372
                        /* If the item we booted with still exists under its name, it means we have not
373
                         * change the current boot's marking so far. This may have two reasons: because we
374
                         * simply didn't do that yet but still plan to, or because the left tries counter is
375
                         * already at zero, hence we cannot further decrease it to mark it even
376
                         * "worse"... Here we check the current counter to detect the latter case and return
377
                         * "dirty", since the item is already marked bad from a previous boot, but otherwise
378
                         * report "indeterminate" since we just didn't make a decision yet. */
379
                        puts(left == 0 ? "dirty" : "indeterminate");
×
380
                        return 0;
381
                }
382
                if (errno != ENOENT)
×
383
                        return log_error_errno(errno, "Failed to check if '%s' exists: %m", path);
×
384

385
                if (faccessat(fd, skip_leading_slash(good), F_OK, 0) >= 0) {
×
386
                        puts("good");
×
387
                        return 0;
388
                }
389

390
                if (errno != ENOENT)
×
391
                        return log_error_errno(errno, "Failed to check if '%s' exists: %m", good);
×
392

393
                if (faccessat(fd, skip_leading_slash(bad), F_OK, 0) >= 0) {
×
394
                        puts("bad");
×
395
                        return 0;
396
                }
397
                if (errno != ENOENT)
×
398
                        return log_error_errno(errno, "Failed to check if '%s' exists: %m", bad);
×
399

400
                /* We didn't find any of the three? If so, let's try the next directory, before we give up. */
401
        }
402

403
        return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Couldn't determine boot state.");
×
404
}
405

406
static int rename_in_dir_idempotent(int fd, const char *from, const char *to) {
×
407
        int r;
×
408

409
        assert(fd >= 0);
×
410
        assert(from);
×
411
        assert(to);
×
412

413
        /* A wrapper around rename_noreplace() which executes no operation if the source and target are the
414
         * same. */
415

416
        if (streq(from, to)) {
×
417
                if (faccessat(fd, from, F_OK, AT_SYMLINK_NOFOLLOW) < 0)
×
418
                        return -errno;
×
419

420
                return 0;
421
        }
422

423
         r = rename_noreplace(fd, from, fd, to);
×
424
         if (r < 0)
×
425
                 return r;
×
426

427
         return 1;
428
}
429

430
static int verb_set(int argc, char *argv[], void *userdata) {
×
431
        _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL;
×
432
        const char *target, *source1, *source2;
×
433
        uint64_t left, done;
×
434
        int r;
×
435

436
        r = acquire_boot_count_path(&path, &prefix, &left, &done, &suffix);
×
437
        if (r == -EUNATCH) /* acquire_boot_count_path() won't log on its own for this specific error */
×
438
                return log_error_errno(r, "Not booted with boot counting in effect.");
×
439
        if (r < 0)
×
440
                return r;
441

442
        r = acquire_path();
×
443
        if (r < 0)
×
444
                return r;
445

446
        r = make_good(prefix, suffix, &good);
×
447
        if (r < 0)
×
448
                return log_oom();
×
449

450
        r = make_bad(prefix, done, suffix, &bad);
×
451
        if (r < 0)
×
452
                return log_oom();
×
453

454
        /* Figure out what rename to what */
455
        if (streq(argv[0], "good")) {
×
456
                target = good;
×
457
                source1 = path;
×
458
                source2 = bad;      /* Maybe this boot was previously marked as 'bad'? */
×
459
        } else if (streq(argv[0], "bad")) {
×
460
                target = bad;
×
461
                source1 = path;
×
462
                source2 = good;     /* Maybe this boot was previously marked as 'good'? */
×
463
        } else {
464
                assert(streq(argv[0], "indeterminate"));
×
465

466
                if (left == 0)
×
467
                        return log_error_errno(r, "Current boot entry was already marked bad in a previous boot, cannot reset to indeterminate.");
×
468

469
                target = path;
×
470
                source1 = good;
×
471
                source2 = bad;
×
472
        }
473

474
        STRV_FOREACH(p, arg_path) {
×
475
                _cleanup_close_ int fd = -EBADF;
×
476

477
                fd = open(*p, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
×
478
                if (fd < 0)
×
479
                        return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p);
×
480

481
                r = rename_in_dir_idempotent(fd, skip_leading_slash(source1), skip_leading_slash(target));
×
482
                if (r == -EEXIST)
×
483
                        goto exists;
×
484
                if (r == -ENOENT) {
×
485

486
                        r = rename_in_dir_idempotent(fd, skip_leading_slash(source2), skip_leading_slash(target));
×
487
                        if (r == -EEXIST)
×
488
                                goto exists;
×
489
                        if (r == -ENOENT) {
×
490

491
                                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? */
×
492
                                        goto exists;
×
493

494
                                if (errno != ENOENT)
×
495
                                        return log_error_errno(errno, "Failed to determine if %s already exists: %m", target);
×
496

497
                                /* We found none of the snippets here, try the next directory */
498
                                continue;
×
499
                        }
500
                        if (r < 0)
×
501
                                return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source2, target);
×
502

503
                        if (r > 0)
×
504
                                log_debug("Successfully renamed '%s' to '%s'.", source2, target);
×
505
                        else
506
                                log_debug("Not renaming, as '%s' already matches target name.", source2);
×
507
                } else if (r < 0)
×
508
                        return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source1, target);
×
509
                else if (r > 0)
×
510
                        log_debug("Successfully renamed '%s' to '%s'.", source1, target);
×
511
                else
512
                        log_debug("Not renaming, as '%s' already matches target name.", source1);
×
513

514
                /* First, fsync() the directory these files are located in */
515
                r = fsync_parent_at(fd, skip_leading_slash(target));
×
516
                if (r < 0)
×
517
                        log_debug_errno(r, "Failed to synchronize image directory, ignoring: %m");
×
518

519
                /* Secondly, syncfs() the whole file system these files are located in */
520
                if (syncfs(fd) < 0)
×
521
                        log_debug_errno(errno, "Failed to synchronize $BOOT partition, ignoring: %m");
×
522

523
                log_info("Marked boot as '%s'. (Boot attempt counter is at %" PRIu64".)", argv[0], done);
×
524
                return 0;
525
        }
526

527
        return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Can't find boot counter source file for '%s'.", target);
×
528

529
exists:
×
530
        log_debug("Operation already executed before, not doing anything.");
×
531
        return 0;
532
}
533

534
static int run(int argc, char *argv[]) {
×
535
        static const Verb verbs[] = {
×
536
                { "help",          VERB_ANY, VERB_ANY, 0,            help        },
537
                { "status",        VERB_ANY, 1,        VERB_DEFAULT, verb_status },
538
                { "good",          VERB_ANY, 1,        0,            verb_set    },
539
                { "bad",           VERB_ANY, 1,        0,            verb_set    },
540
                { "indeterminate", VERB_ANY, 1,        0,            verb_set    },
541
                {}
542
        };
543

544
        int r;
×
545

546
        log_setup();
×
547

548
        r = parse_argv(argc, argv);
×
549
        if (r <= 0)
×
550
                return r;
551

552
        if (detect_container() > 0)
×
553
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
×
554
                                       "Marking a boot is not supported in containers.");
555

556
        if (!is_efi_boot())
×
557
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
×
558
                                       "Marking a boot is only supported on EFI systems.");
559

560
        return dispatch_verb(argc, argv, verbs, NULL);
×
561
}
562

563
DEFINE_MAIN_FUNCTION(run);
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc