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

systemd / systemd / 26546993077

27 May 2026 08:34PM UTC coverage: 72.995% (+0.3%) from 72.667%
26546993077

push

github

bluca
test-pressure: set timeout to make not wait forever

If this runs on a slow or busy machine, then we may not get enough
pressure to trigger the event sources. In such case, the test does not
finish. It is problematic when the test is _not_ run with 'meson test',
e.g. debian/ubuntu CIs.

Let's introduce a timeout for each event loop, and skip test cases
gracefully.

8 of 12 new or added lines in 1 file covered. (66.67%)

19671 existing lines in 226 files now uncovered.

337119 of 461841 relevant lines covered (72.99%)

1326365.62 hits per line

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

74.84
/src/bootctl/bootctl-install.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

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

6
#include "sd-device.h"
7
#include "sd-varlink.h"
8

9
#include "alloc-util.h"
10
#include "ask-password-api.h"
11
#include "blockdev-util.h"
12
#include "boot-entry.h"
13
#include "bootctl.h"
14
#include "bootctl-install.h"
15
#include "bootctl-random-seed.h"
16
#include "bootctl-util.h"
17
#include "chase.h"
18
#include "copy.h"
19
#include "crypto-util.h"
20
#include "dirent-util.h"
21
#include "efi-api.h"
22
#include "efi.h"
23
#include "efivars.h"
24
#include "env-file.h"
25
#include "fd-util.h"
26
#include "fileio.h"
27
#include "find-esp.h"
28
#include "fs-util.h"
29
#include "glyph-util.h"
30
#include "id128-util.h"
31
#include "install-file.h"
32
#include "io-util.h"
33
#include "json-util.h"
34
#include "kernel-config.h"
35
#include "log.h"
36
#include "parse-argument.h"
37
#include "path-util.h"
38
#include "pe-binary.h"
39
#include "rm-rf.h"
40
#include "stat-util.h"
41
#include "string-table.h"
42
#include "string-util.h"
43
#include "strv.h"
44
#include "time-util.h"
45
#include "tmpfile-util.h"
46
#include "umask-util.h"
47
#include "utf8.h"
48

49
typedef enum InstallOperation {
50
        INSTALL_NEW,
51
        INSTALL_UPDATE,
52
        INSTALL_REMOVE,
53
        INSTALL_TEST,
54
        _INSTALL_OPERATION_MAX,
55
        _INSTALL_OPERATION_INVALID = -1,
56
} InstallOperation;
57

58
typedef struct InstallContext {
59
        InstallOperation operation;
60
        bool graceful;
61
        char *root;
62
        int root_fd;
63
        sd_id128_t machine_id;
64
        char *install_layout;
65
        BootEntryTokenType entry_token_type;
66
        char *entry_token;
67
        int make_entry_directory; /* tri-state */
68
        InstallSource install_source;
69
        char *esp_path;
70
        int esp_fd;
71
        uint32_t esp_part;
72
        uint64_t esp_pstart;
73
        uint64_t esp_psize;
74
        sd_id128_t esp_uuid;
75
        char *xbootldr_path;
76
        int xbootldr_fd;
77
#if HAVE_OPENSSL
78
        X509 *secure_boot_certificate;
79
        EVP_PKEY *secure_boot_private_key;
80
#endif
81
        int touch_variables; /* tri-state */
82
} InstallContext;
83

84
#define INSTALL_CONTEXT_NULL                                            \
85
        (InstallContext) {                                              \
86
                .operation = _INSTALL_OPERATION_INVALID,                \
87
                .root_fd = -EBADF,                                      \
88
                .entry_token_type = _BOOT_ENTRY_TOKEN_TYPE_INVALID,     \
89
                .make_entry_directory = -1,                             \
90
                .install_source = _INSTALL_SOURCE_INVALID,              \
91
                .esp_part = UINT32_MAX,                                 \
92
                .esp_pstart = UINT64_MAX,                               \
93
                .esp_psize = UINT64_MAX,                                \
94
                .esp_fd = -EBADF,                                       \
95
                .xbootldr_fd = -EBADF,                                  \
96
                .touch_variables = -1,                                  \
97
        }
98

99
static const char* install_operation_table[_INSTALL_OPERATION_MAX] = {
100
        [INSTALL_NEW]    = "new",
101
        [INSTALL_UPDATE] = "update",
102
        [INSTALL_REMOVE] = "remove",
103
        [INSTALL_TEST]   = "test",
104
};
105

106
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(install_operation, InstallOperation);
×
107

108
static void install_context_done(InstallContext *c) {
344✔
109
        assert(c);
344✔
110

111
        c->root = mfree(c->root);
344✔
112
        c->root_fd = safe_close(c->root_fd);
344✔
113
        c->install_layout = mfree(c->install_layout);
344✔
114
        c->entry_token = mfree(c->entry_token);
344✔
115
        c->esp_path = mfree(c->esp_path);
344✔
116
        c->esp_fd = safe_close(c->esp_fd);
344✔
117
        c->xbootldr_path = mfree(c->xbootldr_path);
344✔
118
        c->xbootldr_fd = safe_close(c->xbootldr_fd);
344✔
119
#if HAVE_OPENSSL
120
        if (c->secure_boot_private_key) {
344✔
121
                sym_EVP_PKEY_free(c->secure_boot_private_key);
1✔
122
                c->secure_boot_private_key = NULL;
1✔
123
        }
124
        if (c->secure_boot_certificate) {
344✔
125
                sym_X509_free(c->secure_boot_certificate);
1✔
126
                c->secure_boot_certificate = NULL;
1✔
127
        }
128
#endif
129
}
344✔
130

131
static int install_context_from_cmdline(
172✔
132
                InstallContext *ret,
133
                InstallOperation operation) {
134

135
        int r;
172✔
136

137
        assert(ret);
172✔
138
        assert(operation >= 0);
172✔
139
        assert(operation < _INSTALL_OPERATION_MAX);
172✔
140

141
        _cleanup_(install_context_done) InstallContext b = INSTALL_CONTEXT_NULL;
172✔
142
        b.operation = operation;
172✔
143
        b.graceful = arg_graceful() == ARG_GRACEFUL_FORCE ||
172✔
144
                (operation == INSTALL_UPDATE && arg_graceful() != ARG_GRACEFUL_NO);
130✔
145
        b.machine_id = arg_machine_id;
172✔
146
        b.entry_token_type = arg_entry_token_type;
172✔
147
        b.make_entry_directory = arg_make_entry_directory;
172✔
148
        b.install_source = arg_install_source;
172✔
149

150
        if (strdup_to(&b.entry_token, arg_entry_token) < 0 ||
172✔
151
            strdup_to(&b.install_layout, arg_install_layout) < 0)
172✔
152
                return log_oom();
×
153

154
        if (arg_root) {
172✔
155
                b.root_fd = open(arg_root, O_CLOEXEC|O_DIRECTORY|O_PATH);
28✔
156
                if (b.root_fd < 0)
28✔
157
                        return log_error_errno(errno, "Failed to open root directory '%s': %m", arg_root);
×
158

159
                r = strdup_to(&b.root, arg_root);
28✔
160
                if (r < 0)
28✔
161
                        return log_oom();
×
162
        } else
163
                b.root_fd = XAT_FDROOT;
144✔
164

165
        r = acquire_esp(/* unprivileged_mode= */ false,
172✔
166
                        b.graceful,
167
                        &b.esp_fd,
168
                        &b.esp_part,
169
                        &b.esp_pstart,
170
                        &b.esp_psize,
171
                        &b.esp_uuid,
172
                        /* ret_devid= */ NULL);
173
        /* If --graceful is specified and we can't find an ESP, handle this cleanly */
174
        if (r < 0 && (!b.graceful || r != -ENOKEY))
172✔
175
                return r;
176

177
        if (r >= 0) { /* An ESP has been found */
172✔
178
                assert(arg_esp_path);
172✔
179

180
                if (arg_root) {
172✔
181
                        const char *e = path_startswith(arg_esp_path, arg_root);
28✔
182
                        if (!e)
28✔
183
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ESP path '%s' not below specified root '%s', refusing.", arg_esp_path, arg_root);
×
184

185
                        r = strdup_to(&b.esp_path, e);
28✔
186
                } else
187
                        r = strdup_to(&b.esp_path, arg_esp_path);
144✔
188
                if (r < 0)
172✔
189
                        return log_oom();
×
190
        }
191

192
        r = acquire_xbootldr(
172✔
193
                        /* unprivileged_mode= */ false,
194
                        &b.xbootldr_fd,
195
                        /* ret_uuid= */ NULL,
196
                        /* ret_devid= */ NULL);
197
        if (r < 0)
172✔
198
                return r;
199
        if (r > 0) { /* XBOOTLDR has been found */
172✔
200
                assert(arg_xbootldr_path);
28✔
201

202
                if (arg_root) {
28✔
203
                        const char *e = path_startswith(arg_xbootldr_path, arg_root);
28✔
204
                        if (!e)
28✔
205
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "XBOOTLDR path '%s' not below specified root '%s', refusing.", arg_xbootldr_path, arg_root);
×
206

207
                        r = strdup_to(&b.xbootldr_path, e);
28✔
208
                } else
209
                        r = strdup_to(&b.xbootldr_path, arg_xbootldr_path);
×
210
                if (r < 0)
28✔
211
                        return log_oom();
×
212
        }
213

214
        *ret = TAKE_GENERIC(b, InstallContext, INSTALL_CONTEXT_NULL);
172✔
215

216
        return !!ret->esp_path; /* return positive if we found an ESP */
172✔
217
}
218

219
static int acquire_dollar_boot_fd(InstallContext *c) {
308✔
220
        assert(c);
308✔
221

222
        if (c->xbootldr_fd >= 0)
308✔
223
                return c->xbootldr_fd;
224

225
        if (c->esp_fd >= 0)
274✔
226
                return c->esp_fd;
227

228
        return log_error_errno(SYNTHETIC_ERRNO(EBADF), "Cannot access $BOOT, as neither ESP nor XBOOTLDR have been found.");
×
229
}
230

231
static const char* dollar_boot_path(InstallContext *c) {
308✔
232
        assert(c);
308✔
233

234
        return c->xbootldr_path ?: c->esp_path;
308✔
235
}
236

237
static bool should_touch_install_variables(InstallContext *c) {
155✔
238
        assert(c);
155✔
239

240
        if (c->touch_variables >= 0)
155✔
241
                return c->touch_variables;
×
242

243
        if (!is_efi_boot())  /* NB: this internally checks if we run in a container */
155✔
244
                return false;
245

246
        return empty_or_root(c->root);
45✔
247
}
248

249
static int load_etc_machine_id(InstallContext *c) {
158✔
250
        int r;
158✔
251

252
        assert(c);
158✔
253

254
        r = id128_get_machine_at(c->root_fd, &c->machine_id);
158✔
255
        if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r)) /* Not set or empty */
158✔
256
                return 0;
257
        if (r < 0)
138✔
258
                return log_error_errno(r, "Failed to get machine-id: %m");
×
259

260
        log_debug("Loaded machine ID %s from '%s/etc/machine-id'.", strempty(c->root), SD_ID128_TO_STRING(c->machine_id));
262✔
261
        return 0;
138✔
262
}
263

264
static int load_etc_machine_info(InstallContext *c) {
158✔
265
        /* systemd v250 added support to store the kernel-install layout setting and the machine ID to use
266
         * for setting up the ESP in /etc/machine-info. The newer /etc/kernel/entry-token file, as well as
267
         * the $layout field in /etc/kernel/install.conf are better replacements for this though, hence this
268
         * has been deprecated and is only returned for compatibility. */
269
        _cleanup_free_ char *s = NULL, *layout = NULL;
×
270
        int r;
158✔
271

272
        assert(c);
158✔
273

274
        _cleanup_free_ char *j = path_join(c->root, "/etc/machine-info");
316✔
275
        if (!j)
158✔
276
                return log_oom();
×
277

278
        _cleanup_close_ int fd =
158✔
279
                chase_and_openat(
158✔
280
                                c->root_fd,
281
                                c->root_fd,
282
                                "/etc/machine-info",
283
                                CHASE_MUST_BE_REGULAR,
284
                                O_RDONLY|O_CLOEXEC,
285
                                /* ret_path= */ NULL);
286
        if (fd == -ENOENT)
158✔
287
                return 0;
288
        if (fd < 0)
×
289
                return log_error_errno(fd, "Failed to open '%s': %m", j);
×
290

291
        r = parse_env_file_fd(
×
292
                        fd, "/etc/machine-info",
293
                        "KERNEL_INSTALL_LAYOUT", &layout,
294
                        "KERNEL_INSTALL_MACHINE_ID", &s);
295
        if (r < 0)
×
296
                return log_error_errno(r, "Failed to parse '%s': %m", j);
×
297

298
        if (!isempty(s)) {
×
299
                if (!arg_quiet)
×
300
                        log_notice("Read $KERNEL_INSTALL_MACHINE_ID from '%s'. "
×
301
                                   "Please move it to '%s/etc/kernel/entry-token'.", j, strempty(c->root));
302

303
                r = sd_id128_from_string(s, &c->machine_id);
×
304
                if (r < 0)
×
305
                        return log_error_errno(r, "Failed to parse KERNEL_INSTALL_MACHINE_ID=\"%s\" in '%s': %m", s, j);
×
306

307
                log_debug("Loaded KERNEL_INSTALL_MACHINE_ID=\"%s\" from '%s'.",
×
308
                          SD_ID128_TO_STRING(c->machine_id), j);
309
        }
310

311
        if (!isempty(layout)) {
158✔
312
                if (!arg_quiet)
×
313
                        log_notice("Read $KERNEL_INSTALL_LAYOUT from '%s'. "
×
314
                                   "Please move it to the layout= setting of '%s/etc/kernel/install.conf'.", j, strempty(c->root));
315

316
                log_debug("KERNEL_INSTALL_LAYOUT=\"%s\" is specified in '%s'.", layout, j);
×
317
                free_and_replace(c->install_layout, layout);
×
318
        }
319

320
        return 0;
321
}
322

323
static int load_kernel_install_layout(InstallContext *c) {
158✔
324
        _cleanup_free_ char *layout = NULL;
158✔
325
        int r;
158✔
326

327
        assert(c);
158✔
328

329
        const char *e = secure_getenv("KERNEL_INSTALL_CONF_ROOT");
158✔
330
        r = load_kernel_install_conf_at(
158✔
331
                        e ? NULL : c->root,
332
                        e ? XAT_FDROOT : c->root_fd,
333
                        e,
334
                        /* ret_machine_id= */ NULL,
335
                        /* ret_boot_root= */ NULL,
336
                        &layout,
337
                        /* ret_initrd_generator= */ NULL,
338
                        /* ret_uki_generator= */ NULL);
339
        if (r <= 0)
158✔
340
                return r;
341

342
        if (!isempty(layout)) {
158✔
343
                log_debug("layout=\"%s\" is specified in config.", layout);
×
344
                free_and_replace(c->install_layout, layout);
×
345
        }
346

347
        return 0;
348
}
349

350
static bool use_boot_loader_spec_type1(InstallContext *c) {
158✔
351
        assert(c);
158✔
352
        /* If the layout is not specified, or if it is set explicitly to "bls" we assume Boot Loader
353
         * Specification Type #1 is the chosen format for our boot loader entries */
354
        return !c->install_layout || streq(c->install_layout, "bls");
158✔
355
}
356

357
static int settle_make_entry_directory(InstallContext *c) {
158✔
358
        int r;
158✔
359

360
        assert(c);
158✔
361

362
        r = load_etc_machine_id(c);
158✔
363
        if (r < 0)
158✔
364
                return r;
365

366
        r = load_etc_machine_info(c);
158✔
367
        if (r < 0)
158✔
368
                return r;
369

370
        r = load_kernel_install_layout(c);
158✔
371
        if (r < 0)
158✔
372
                return r;
373

374
        const char *e = secure_getenv("KERNEL_INSTALL_CONF_ROOT");
158✔
375
        r = boot_entry_token_ensure_at(
158✔
376
                        e ? XAT_FDROOT : c->root_fd,
377
                        e,
378
                        c->machine_id,
379
                        /* machine_id_is_random= */ false,
380
                        &c->entry_token_type,
381
                        &c->entry_token);
382
        if (r < 0)
158✔
383
                return r;
384

385
        log_debug("Using entry token: %s", c->entry_token);
158✔
386

387
        bool layout_type1 = use_boot_loader_spec_type1(c);
158✔
388
        if (c->make_entry_directory < 0) { /* Automatic mode */
158✔
389
                if (layout_type1) {
×
390
                        if (c->entry_token_type == BOOT_ENTRY_TOKEN_MACHINE_ID) {
×
391
                                _cleanup_free_ char *j = path_join(c->root, "/etc/machine-id");
×
392
                                if (!j)
×
393
                                        return log_oom();
×
394

395
                                _cleanup_close_ int fd = -EBADF;
×
396
                                r = chaseat(c->root_fd,
×
397
                                            c->root_fd,
398
                                            "/etc/machine-id",
399
                                            CHASE_MUST_BE_REGULAR,
400
                                            /* ret_path= */ NULL,
401
                                            &fd);
402
                                if (r < 0)
×
403
                                        return log_debug_errno(r, "Unable to open '%s': %m", j);
×
404

405
                                r = fd_is_temporary_fs(fd);
×
406
                                if (r < 0)
×
407
                                        return log_debug_errno(r, "Couldn't determine whether '%s' is on a temporary file system: %m", j);
×
408

409
                                c->make_entry_directory = r == 0;
×
410
                        } else
411
                                c->make_entry_directory = true;
×
412
                } else
413
                        c->make_entry_directory = false;
×
414
        }
415

416
        if (c->make_entry_directory > 0 && !layout_type1)
158✔
417
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
418
                                       "KERNEL_INSTALL_LAYOUT=\"%s\" is configured, but Boot Loader Specification Type #1 entry directory creation was requested.",
419
                                       c->install_layout);
420

421
        return 0;
422
}
423

424
static int compare_product(const char *a, const char *b) {
260✔
425
        size_t x, y;
260✔
426

427
        assert(a);
260✔
428
        assert(b);
260✔
429

430
        x = strcspn(a, " ");
260✔
431
        y = strcspn(b, " ");
260✔
432
        if (x != y)
260✔
433
                return x < y ? -1 : x > y ? 1 : 0;
×
434

435
        return strncmp(a, b, x);
260✔
436
}
437

438
static int compare_version(const char *a, const char *b) {
260✔
439
        assert(a);
260✔
440
        assert(b);
260✔
441

442
        a += strcspn(a, " ");
260✔
443
        a += strspn(a, " ");
260✔
444
        b += strcspn(b, " ");
260✔
445
        b += strspn(b, " ");
260✔
446

447
        return strverscmp_improved(a, b);
260✔
448
}
449

450
static int version_check(int fd_from, const char *from, int fd_to, const char *to) {
260✔
451
        _cleanup_free_ char *a = NULL, *b = NULL;
260✔
452
        int r;
260✔
453

454
        assert(fd_from >= 0);
260✔
455
        assert(from);
260✔
456
        assert(fd_to >= 0);
260✔
457
        assert(to);
260✔
458

459
        /* Does not reposition file offset */
460

461
        r = get_file_version(fd_from, &a);
260✔
462
        if (r == -ESRCH)
260✔
463
                return log_notice_errno(r, "Source file \"%s\" does not carry version information!", from);
×
464
        if (r < 0)
260✔
UNCOV
465
                return log_error_errno(r, "Failed to get file version of '%s': %m", from);
×
466

467
        r = get_file_version(fd_to, &b);
260✔
468
        if (r == -ESRCH)
260✔
469
                return log_info_errno(r, "Skipping \"%s\", it's owned by another boot loader (no version info found).", to);
×
470
        if (r < 0)
260✔
UNCOV
471
                return log_error_errno(r, "Failed to get file version of '%s': %m", to);
×
472
        if (compare_product(a, b) != 0)
260✔
473
                return log_info_errno(SYNTHETIC_ERRNO(ESRCH),
×
474
                                      "Skipping \"%s\", it's owned by another boot loader.", to);
475

476
        r = compare_version(a, b);
260✔
477
        log_debug("Comparing versions: \"%s\" %s \"%s\"", a, comparison_operator(r), b);
508✔
478
        if (r < 0)
260✔
479
                return log_warning_errno(SYNTHETIC_ERRNO(ESTALE),
×
480
                                         "Skipping \"%s\", newer boot loader version in place already.", to);
481
        if (r == 0)
260✔
482
                return log_info_errno(SYNTHETIC_ERRNO(ESTALE),
260✔
483
                                      "Skipping \"%s\", same boot loader version in place already.", to);
484

485
        return 0;
486
}
487

488
static int copy_file_with_version_check(
172✔
489
                const char *source_path,
490
                int source_fd,
491
                const char *dest_path,
492
                int dest_parent_fd,
493
                const char *dest_filename,
494
                int dest_fd,
495
                bool force) {
496

497
        int r;
172✔
498

499
        assert(source_path);
172✔
500
        assert(source_fd >= 0);
172✔
501
        assert(dest_path);
172✔
502
        assert(dest_parent_fd >= 0);
172✔
503
        assert(dest_filename);
172✔
504

505
        if (!force && dest_fd >= 0) {
172✔
506
                r = version_check(source_fd, source_path, dest_fd, dest_path);
130✔
507
                if (r < 0)
130✔
508
                        return r;
172✔
509
        }
510

511
        _cleanup_free_ char *t = NULL;
42✔
512
        _cleanup_close_ int write_fd = -EBADF;
42✔
513
        write_fd = open_tmpfile_linkable_at(dest_parent_fd, dest_filename, O_WRONLY|O_CLOEXEC, &t);
42✔
514
        if (write_fd < 0)
42✔
515
                return log_error_errno(write_fd, "Failed to open \"%s\" for writing: %m", dest_path);
×
516

517
        CLEANUP_TMPFILE_AT(dest_parent_fd, t);
42✔
518

519
        /* Reset file offset before we start copying, since we copy this file multiple times, and the offset
520
         * might be left at the end of the file. (Resetting before rather than after a copy attempt is safer
521
         * because a previous attempt might have failed half-way, leaving the file offset at some undefined
522
         * place.) */
523
        r = copy_bytes(source_fd, write_fd, UINT64_MAX, COPY_REFLINK|COPY_SEEK0_SOURCE);
42✔
524
        if (r < 0)
42✔
525
                return log_error_errno(r, "Failed to copy data from \"%s\" to \"%s\": %m", source_path, dest_path);
×
526

527
        (void) copy_times(source_fd, write_fd, /* flags= */ 0);
42✔
528
        (void) fchmod(write_fd, 0644);
42✔
529

530
        r = link_tmpfile_at(write_fd, dest_parent_fd, t, dest_filename, LINK_TMPFILE_REPLACE|LINK_TMPFILE_SYNC);
42✔
531
        if (r < 0)
42✔
532
                return log_error_errno(r, "Failed to move data from \"%s\" to \"%s\": %m", source_path, dest_path);
×
533

534
        t = mfree(t); /* disarm CLEANUP_TMPFILE_AT() */
42✔
535

536
        log_info("Copied \"%s\" to \"%s\".", source_path, dest_path);
42✔
537
        return 0;
538
}
539

540
static int mkdir_one(const char *root, int root_fd, const char *path) {
143✔
541
        int r;
143✔
542

543
        assert(root);
143✔
544
        assert(root_fd >= 0);
143✔
545
        assert(path);
143✔
546

547
        _cleanup_free_ char *p = path_join(empty_to_root(root), path);
286✔
548
        if (!p)
143✔
549
                return log_oom();
×
550

551
        r = chaseat(root_fd,
143✔
552
                    root_fd,
553
                    path,
554
                    CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY,
555
                    /* ret_path= */ NULL,
556
                    /* ret_fd= */ NULL);
557
        if (r < 0)
143✔
558
                return log_error_errno(r, "Failed to create \"%s\": %m", p);
×
559

560
        log_info("Created directory \"%s\".", p);
143✔
561
        return 0;
562
}
563

564
static const char *const esp_subdirs[] = {
565
        /* The directories to place in the ESP */
566
        "EFI",
567
        "EFI/systemd",
568
        "EFI/BOOT",
569
        "loader",
570
        "loader/keys",
571
        NULL
572
};
573

574
static const char *const dollar_boot_subdirs[] = {
575
        /* The directories to place in the XBOOTLDR partition or the ESP, depending what exists */
576
        "loader",
577
        "loader/entries",  /* Type #1 entries */
578
        "EFI",
579
        "EFI/Linux",       /* Type #2 entries */
580
        NULL
581
};
582

583
static int create_subdirs(const char *root, int root_fd, const char * const *subdirs) {
30✔
584
        int r;
30✔
585

586
        assert(root);
30✔
587
        assert(root_fd >= 0);
30✔
588

589
        STRV_FOREACH(i, subdirs) {
165✔
590
                r = mkdir_one(root, root_fd, *i);
135✔
591
                if (r < 0)
135✔
592
                        return r;
593
        }
594

595
        return 0;
596
}
597

598
static int update_efi_boot_binaries(
130✔
599
                InstallContext *c,
600
                const char *source_path,
601
                int source_fd,
602
                const char *ignore_filename) {
603

604
        int r, ret = 0;
130✔
605

606
        assert(c);
130✔
607
        assert(source_path);
130✔
608

609
        if (c->esp_fd < 0)
130✔
610
                return c->esp_fd;
130✔
611

612
        _cleanup_free_ char *j = path_join(c->root, c->esp_path);
260✔
613
        if (!j)
130✔
614
                return log_oom();
×
615

616
        _cleanup_closedir_ DIR *d = NULL;
130✔
617
        r = chase_and_opendirat(
130✔
618
                        c->esp_fd,
619
                        c->esp_fd,
620
                        "/EFI/BOOT",
621
                        CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY,
622
                        /* ret_path= */ NULL,
623
                        &d);
624
        if (r == -ENOENT)
130✔
625
                return 0;
626
        if (r < 0)
130✔
627
                return log_error_errno(r, "Failed to open directory \"%s/EFI/BOOT\": %m", j);
×
628

629
        FOREACH_DIRENT(de, d, break) {
650✔
630
                _cleanup_close_ int fd = -EBADF;
650✔
631

632
                if (!endswith_no_case(de->d_name, ".efi"))
260✔
633
                        continue;
×
634

635
                if (strcaseeq_ptr(ignore_filename, de->d_name))
260✔
636
                        continue;
130✔
637

638
                fd = xopenat_full(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY|O_NOFOLLOW, XO_REGULAR, /* mode= */ MODE_INVALID);
130✔
639
                if (fd < 0)
130✔
640
                        return log_error_errno(fd, "Failed to open \"%s/%s\" for reading: %m", j, de->d_name);
×
641

642
                r = pe_is_native_fd(fd);
130✔
643
                if (r < 0) {
130✔
644
                        log_warning_errno(r, "Failed to detect if \"%s/%s\" is for native architecture, ignoring: %m", j, de->d_name);
×
645
                        continue;
×
646
                }
647
                if (r == 0)
130✔
648
                        continue;
130✔
649

650
                _cleanup_free_ char *dest_path = path_join(j, "/EFI/BOOT", de->d_name);
×
651
                if (!dest_path)
×
652
                        return log_oom();
×
653

654
                r = copy_file_with_version_check(source_path, source_fd, dest_path, dirfd(d), de->d_name, fd, /* force= */ false);
×
655
                if (IN_SET(r, -ESTALE, -ESRCH))
×
656
                        continue;
×
657
                RET_GATHER(ret, r);
×
658
        }
659

660
        return ret;
661
}
662

663
static int copy_one_file(
151✔
664
                InstallContext *c,
665
                const char *name,
666
                bool force) {
667

668
        int r, ret = 0;
151✔
669

670
        assert(c);
151✔
671

672
        _cleanup_free_ char *dest_name = strdup(name);
151✔
673
        if (!dest_name)
151✔
674
                return log_oom();
×
675
        char *s = endswith_no_case(dest_name, ".signed");
151✔
676
        if (s)
151✔
677
                *s = 0;
151✔
678

679
        _cleanup_free_ char *sp = path_join(BOOTLIBDIR, name);
302✔
680
        if (!sp)
151✔
681
                return log_oom();
×
682

683
        _cleanup_free_ char *source_path = NULL;
151✔
684
        _cleanup_close_ int source_fd = -EBADF;
151✔
685
        if (IN_SET(c->install_source, INSTALL_SOURCE_AUTO, INSTALL_SOURCE_IMAGE)) {
151✔
686
                source_fd = chase_and_openat(
151✔
687
                                c->root_fd,
688
                                c->root_fd,
689
                                sp,
690
                                CHASE_MUST_BE_REGULAR,
691
                                O_RDONLY|O_CLOEXEC,
692
                                &source_path);
693
                if (source_fd < 0 && (source_fd != -ENOENT || c->install_source != INSTALL_SOURCE_AUTO))
151✔
694
                        return log_error_errno(source_fd, "Failed to resolve path '%s' under directory '%s': %m", sp, c->root);
×
695

696
                /* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */
697
        }
698
        if (source_fd < 0) {
16✔
699
                source_fd = chase_and_open(
16✔
700
                                sp,
701
                                /* root= */ NULL,
702
                                CHASE_MUST_BE_REGULAR,
703
                                O_RDONLY|O_CLOEXEC,
704
                                &source_path);
705
                if (source_fd < 0)
16✔
706
                        return log_error_errno(source_fd, "Failed to resolve path '%s': %m", sp);
×
707
        }
708

709
        if (c->esp_fd < 0)
151✔
710
                return c->esp_fd;
711

712
        _cleanup_free_ char *j = path_join(c->root, c->esp_path);
302✔
713
        if (!j)
151✔
714
                return log_oom();
×
715

716
        _cleanup_close_ int dest_parent_fd = -EBADF;
151✔
717
        r = chaseat(c->esp_fd,
151✔
718
                    c->esp_fd,
719
                    "/EFI/systemd",
720
                    CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY,
721
                    /* ret_path= */ NULL,
722
                    &dest_parent_fd);
723
        if (r < 0)
151✔
724
                return log_error_errno(r, "Failed to resolve path '/EFI/systemd' under directory '%s': %m", j);
×
725

726
        _cleanup_free_ char *dest_path = path_join(j, "/EFI/systemd", dest_name);
302✔
727
        if (!dest_path)
151✔
728
                return log_oom();
×
729

730
        _cleanup_close_ int dest_fd = xopenat_full(dest_parent_fd, dest_name, O_RDONLY|O_CLOEXEC, XO_REGULAR, MODE_INVALID);
302✔
731
        if (dest_fd < 0 && dest_fd != -ENOENT)
151✔
732
                return log_error_errno(dest_fd, "Failed to open '%s' under '%s/EFI/systemd' directory: %m", dest_name, j);
×
733

734
        const char *e = startswith(dest_name, "systemd-boot");
151✔
735

736
        /* If a primary sd-boot binary already exists and the source is a newer version, copy
737
         * the existing primary to systemd-boot-fallback{arch}.efi before installing the new
738
         * one, so firmware has a fallback to the previous binary. The fallback is left alone
739
         * when its product and version match the currently booted bootloader (from LoaderInfo),
740
         * so a known good binary stays as the fallback. In all other cases, like no fallback yet,
741
         * LoaderInfo is unavailable, or product/version differs from what booted, it is
742
         * overwritten with the current primary. */
743
        if (e && dest_fd >= 0 && !force) {
151✔
744
                r = version_check(source_fd, source_path, dest_fd, dest_path);
130✔
745
                if (r < 0)
130✔
746
                        /* Stash the error and fall through; the BOOT{arch}.EFI updates below still run. */
747
                        ret = r;
748
                else {
UNCOV
749
                        _cleanup_free_ char *fallback_name = strjoin("systemd-boot-fallback", e);
×
UNCOV
750
                        if (!fallback_name)
×
UNCOV
751
                                return log_oom();
×
752

UNCOV
753
                        _cleanup_free_ char *fallback_path = path_join(j, "/EFI/systemd", fallback_name);
×
UNCOV
754
                        if (!fallback_path)
×
UNCOV
755
                                return log_oom();
×
756

757
                        /* Leave the fallback alone if it already holds the currently booted product
758
                         * and version, so a known good binary stays as the fallback. If there is no
759
                         * fallback yet, LoaderInfo is unavailable, or there is a mismatch, then
760
                         * overwrite it with the current primary. */
UNCOV
761
                        bool should_rotate = true;
×
UNCOV
762
                        _cleanup_close_ int fallback_fd = xopenat_full(dest_parent_fd, fallback_name, O_RDONLY|O_CLOEXEC, XO_REGULAR, MODE_INVALID);
×
UNCOV
763
                        if (fallback_fd >= 0) {
×
764
                                _cleanup_free_ char *loader_info = NULL, *fallback_version = NULL;
×
765

UNCOV
766
                                if (efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderInfo"), &loader_info) >= 0 &&
×
UNCOV
767
                                    get_file_version(fallback_fd, &fallback_version) >= 0)
×
UNCOV
768
                                        should_rotate = compare_product(loader_info, fallback_version) != 0 ||
×
UNCOV
769
                                                        compare_version(loader_info, fallback_version) != 0;
×
770
                        }
771

UNCOV
772
                        if (should_rotate) {
×
UNCOV
773
                                r = copy_file_with_version_check(dest_path, dest_fd, fallback_path, dest_parent_fd, fallback_name, /* dest_fd= */ -EBADF, /* force= */ true);
×
UNCOV
774
                                if (r < 0)
×
UNCOV
775
                                        log_warning_errno(r, "Failed to back up sd-boot binary to fallback path, continuing: %m");
×
776
                        }
777

UNCOV
778
                        ret = copy_file_with_version_check(source_path, source_fd, dest_path, dest_parent_fd, dest_name, dest_fd, /* force= */ true);
×
779
                }
780
        } else
781
                /* Note that if this fails we do the second copy anyway, but return this error code,
782
                 * so we stash it away in a separate variable. */
783
                ret = copy_file_with_version_check(source_path, source_fd, dest_path, dest_parent_fd, dest_name, dest_fd, force);
21✔
784

785
        if (e) {
151✔
786

787
                /* Create the EFI default boot loader name (specified for removable devices) */
788
                _cleanup_free_ char *boot_dot_efi = strjoin("BOOT", e);
302✔
789
                if (!boot_dot_efi)
151✔
UNCOV
790
                        return log_oom();
×
791

792
                ascii_strupper(boot_dot_efi);
151✔
793

794
                _cleanup_close_ int default_dest_parent_fd = -EBADF;
151✔
795
                r = chaseat(c->esp_fd,
151✔
796
                            c->esp_fd,
797
                            "/EFI/BOOT",
798
                            CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY,
799
                            /* ret_path= */ NULL,
800
                            &default_dest_parent_fd);
801
                if (r < 0)
151✔
UNCOV
802
                        return log_error_errno(r, "Failed to resolve path '/EFI/BOOT/' under directory '%s': %m", j);
×
803

804
                _cleanup_free_ char *default_dest_path = path_join(j, "/EFI/BOOT", boot_dot_efi);
302✔
805
                if (!default_dest_path)
151✔
UNCOV
806
                        return log_oom();
×
807

808
                _cleanup_close_ int default_dest_fd = xopenat_full(default_dest_parent_fd, boot_dot_efi, O_RDONLY|O_CLOEXEC, XO_REGULAR, MODE_INVALID);
302✔
809
                if (default_dest_fd < 0 && default_dest_fd != -ENOENT)
151✔
UNCOV
810
                        return log_error_errno(default_dest_fd, "Failed to open '%s' under '%s/EFI/BOOT' directory: %m", boot_dot_efi, j);
×
811

812
                RET_GATHER(ret, copy_file_with_version_check(source_path, source_fd, default_dest_path, default_dest_parent_fd, boot_dot_efi, default_dest_fd, force));
151✔
813

814
                /* If we were installed under any other name in /EFI/BOOT/, make sure we update those
815
                 * binaries as well. */
816
                if (!force)
151✔
817
                        RET_GATHER(ret, update_efi_boot_binaries(c, source_path, source_fd, boot_dot_efi));
130✔
818
        }
819

820
        return ret;
821
}
822

823
static int install_binaries(
145✔
824
                InstallContext *c,
825
                const char *arch) {
826

827
        int r;
145✔
828

829
        assert(c);
145✔
830

831
        _cleanup_free_ char *source_path = NULL;
145✔
832
        _cleanup_closedir_ DIR *d = NULL;
145✔
833
        if (IN_SET(c->install_source, INSTALL_SOURCE_AUTO, INSTALL_SOURCE_IMAGE)) {
145✔
834
                r = chase_and_opendirat(
145✔
835
                                c->root_fd,
836
                                c->root_fd,
837
                                BOOTLIBDIR,
838
                                CHASE_MUST_BE_DIRECTORY,
839
                                &source_path,
840
                                &d);
841
                if (r < 0 && (r != -ENOENT || c->install_source != INSTALL_SOURCE_AUTO))
145✔
UNCOV
842
                        return log_error_errno(r, "Failed to resolve path '%s' under directory '%s': %m", BOOTLIBDIR, c->root);
×
843

844
                /* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */
845
        }
846
        if (!d) {
145✔
847
                r = chase_and_opendir(
12✔
848
                                BOOTLIBDIR,
849
                                /* root= */ NULL,
850
                                CHASE_MUST_BE_DIRECTORY,
851
                                &source_path,
852
                                &d);
853
                if (r == -ENOENT && c->graceful) {
12✔
UNCOV
854
                        log_debug("Source directory '%s' does not exist, ignoring.", BOOTLIBDIR);
×
855
                        return 0;
856
                }
857
                if (r < 0)
12✔
UNCOV
858
                        return log_error_errno(r, "Failed to resolve path '%s': %m", BOOTLIBDIR);
×
859
        }
860

861
        const char *suffix = strjoina(arch, ".efi");
725✔
862
        const char *suffix_signed = strjoina(arch, ".efi.signed");
725✔
863

864
        FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read \"%s\": %m", source_path)) {
1,595✔
865
                int k;
1,160✔
866

867
                if (endswith_no_case(de->d_name, suffix)) {
1,160✔
868
                        /* skip the .efi file, if there's a .signed version of it */
869
                        _cleanup_free_ const char *s = strjoin(de->d_name, ".signed");
302✔
870
                        if (!s)
151✔
UNCOV
871
                                return log_oom();
×
872
                        if (faccessat(dirfd(d), s, F_OK, 0) >= 0)
151✔
873
                                continue;
151✔
874
                } else if (!endswith_no_case(de->d_name, suffix_signed))
1,009✔
875
                        continue;
858✔
876

877
                k = copy_one_file(c, de->d_name, c->operation == INSTALL_NEW);
151✔
878
                /* Don't propagate an error code if no update necessary, installed version already equal or
879
                 * newer version, or other boot loader in place. */
880
                if (c->graceful && IN_SET(k, -ESTALE, -ESRCH))
151✔
881
                        continue;
127✔
882
                RET_GATHER(r, k);
24✔
883
        }
884

885
        return r;
886
}
887

888
static int install_loader_config(InstallContext *c) {
15✔
889
        int r;
15✔
890

891
        assert(c);
15✔
892
        assert(c->make_entry_directory >= 0);
15✔
893

894
        if (c->esp_fd < 0)
15✔
895
                return c->esp_fd;
15✔
896

897
        _cleanup_free_ char *j = path_join(c->root, c->esp_path);
30✔
898
        if (!j)
15✔
UNCOV
899
                return log_oom();
×
900

901
        _cleanup_close_ int loader_dir_fd = -EBADF;
15✔
902
        r = chaseat(c->esp_fd,
15✔
903
                    c->esp_fd,
904
                    "loader",
905
                    CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY,
906
                    /* ret_path= */ NULL,
907
                    &loader_dir_fd);
908
        if (r < 0)
15✔
UNCOV
909
                return log_error_errno(r, "Failed to open '/loader/' directory below '%s': %m", j);
×
910

911
        if (faccessat(loader_dir_fd, "loader.conf", F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
15✔
912
                if (errno != ENOENT)
15✔
UNCOV
913
                        return log_error_errno(errno, "Failed to check if '/loader/loader.conf' exists below '%s': %m", j);
×
914
        } else /* Silently skip creation if the file already exists (early check) */
915
                return 0;
916

917
        _cleanup_free_ char *t = NULL;
15✔
918
        _cleanup_fclose_ FILE *f = NULL;
15✔
919
        r = fopen_tmpfile_linkable_at(loader_dir_fd, "loader.conf", O_WRONLY|O_CLOEXEC, &t, &f);
15✔
920
        if (r < 0)
15✔
UNCOV
921
                return log_error_errno(r, "Failed to open '%s/loader/loader.conf' for writing: %m", j);
×
922

923
        CLEANUP_TMPFILE_AT(loader_dir_fd, t);
15✔
924

925
        fprintf(f, "#timeout 3\n"
15✔
926
                   "#console-mode keep\n");
927

928
        if (c->make_entry_directory) {
15✔
929
                assert(c->entry_token);
8✔
930
                fprintf(f, "default %s-*\n", c->entry_token);
8✔
931
        }
932

933
        r = flink_tmpfile_at(f, loader_dir_fd, t, "loader.conf", LINK_TMPFILE_SYNC);
15✔
934
        if (r == -EEXIST)
15✔
935
                return 0; /* Silently skip creation if the file exists now (recheck) */
936
        if (r < 0)
15✔
UNCOV
937
                return log_error_errno(r, "Failed to move '%s/loader/loader.conf' into place: %m", j);
×
938

939
        t = mfree(t); /* disarm CLEANUP_TMPFILE_AT() */
15✔
940
        return 1;
15✔
941
}
942

943
static int install_loader_specification(InstallContext *c) {
142✔
944
        int r;
142✔
945

946
        assert(c);
142✔
947

948
        int dollar_boot_fd = acquire_dollar_boot_fd(c);
142✔
949
        if (dollar_boot_fd < 0)
142✔
950
                return dollar_boot_fd;
142✔
951

952
        _cleanup_free_ char *j = path_join(c->root, dollar_boot_path(c));
284✔
953
        if (!j)
142✔
UNCOV
954
                return log_oom();
×
955

956
        _cleanup_close_ int loader_dir_fd = -EBADF;
142✔
957
        r = chaseat(dollar_boot_fd,
142✔
958
                    dollar_boot_fd,
959
                    "loader",
960
                    CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY,
961
                    /* ret_path= */ NULL,
962
                    &loader_dir_fd);
963
        if (r < 0)
142✔
UNCOV
964
                return log_error_errno(r, "Failed to pin '/loader' directory below '%s': %m", j);
×
965

966
        if (faccessat(loader_dir_fd, "entries.srel", F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
142✔
967
                if (errno != ENOENT)
15✔
UNCOV
968
                        return log_error_errno(errno, "Failed to check if '/loader/entries.srel' exists below '%s': %m", j);
×
969
        } else /* Silently skip creation if the file already exists (early check) */
970
                return 0;
971

972
        _cleanup_free_ char *t = NULL;
15✔
973
        _cleanup_fclose_ FILE *f = NULL;
15✔
974
        r = fopen_tmpfile_linkable_at(loader_dir_fd, "entries.srel", O_WRONLY|O_CLOEXEC, &t, &f);
15✔
975
        if (r < 0)
15✔
UNCOV
976
                return log_error_errno(r, "Failed to open '%s/loader/entries.srel' for writing: %m", j);
×
977

978
        CLEANUP_TMPFILE_AT(loader_dir_fd, t);
15✔
979

980
        fprintf(f, "type1\n");
15✔
981

982
        r = flink_tmpfile_at(f, loader_dir_fd, t, "entries.srel", LINK_TMPFILE_SYNC);
15✔
983
        if (r == -EEXIST)
15✔
984
                return 0; /* Silently skip creation if the file exists now (recheck) */
985
        if (r < 0)
15✔
UNCOV
986
                return log_error_errno(r, "Failed to move '%s/loader/entries.srel' into place: %m", j);
×
987

988
        t = mfree(t); /* disarm CLEANUP_TMPFILE_AT() */
15✔
989
        return 1;
15✔
990
}
991

992
static int install_entry_directory(InstallContext *c) {
15✔
993
        assert(c);
15✔
994
        assert(c->make_entry_directory >= 0);
15✔
995

996
        if (!c->make_entry_directory)
15✔
997
                return 0;
15✔
998

999
        assert(c->entry_token);
8✔
1000

1001
        int dollar_boot_fd = acquire_dollar_boot_fd(c);
8✔
1002
        if (dollar_boot_fd < 0)
8✔
1003
                return dollar_boot_fd;
1004

1005
        _cleanup_free_ char *j = path_join(c->root, dollar_boot_path(c));
16✔
1006
        if (!j)
8✔
UNCOV
1007
                return log_oom();
×
1008

1009
        return mkdir_one(j, dollar_boot_fd, c->entry_token);
8✔
1010
}
1011

1012
static int install_entry_token(InstallContext *c) {
15✔
1013
        int r;
15✔
1014

1015
        assert(c);
15✔
1016
        assert(c->make_entry_directory >= 0);
15✔
1017
        assert(c->entry_token);
15✔
1018

1019
        /* Let's save the used entry token in /etc/kernel/entry-token if we used it to create the entry
1020
         * directory, or if anything else but the machine ID */
1021

1022
        if (!c->make_entry_directory && c->entry_token_type == BOOT_ENTRY_TOKEN_MACHINE_ID)
15✔
1023
                return 0;
15✔
1024

1025
        const char *confdir = secure_getenv("KERNEL_INSTALL_CONF_ROOT") ?: "/etc/kernel/";
15✔
1026

1027
        _cleanup_free_ char *j = path_join(c->root, confdir);
30✔
1028
        if (!j)
15✔
UNCOV
1029
                return log_oom();
×
1030

1031
        _cleanup_close_ int dfd = -EBADF;
15✔
1032
        r = chaseat(c->root_fd,
15✔
1033
                    c->root_fd,
1034
                    confdir,
1035
                    CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY,
1036
                    /* ret_path= */ NULL,
1037
                    &dfd);
1038
        if (r < 0)
15✔
UNCOV
1039
                return log_error_errno(r, "Failed to open '%s': %m", j);
×
1040

1041
        r = write_string_file_at(dfd, "entry-token", c->entry_token, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MKDIR_0755);
15✔
1042
        if (r < 0)
15✔
UNCOV
1043
                return log_error_errno(r, "Failed to write entry token '%s' to '%s/entry-token': %m", c->entry_token, j);
×
1044

1045
        return 0;
1046
}
1047

1048
#if HAVE_OPENSSL
1049
static int efi_timestamp(EFI_TIME *ret) {
1✔
1050
        struct tm tm = {};
1✔
1051
        int r;
1✔
1052

1053
        assert(ret);
1✔
1054

1055
        r = localtime_or_gmtime_usec(source_date_epoch_or_now(), /* utc= */ true, &tm);
1✔
1056
        if (r < 0)
1✔
UNCOV
1057
                return log_error_errno(r, "Failed to convert timestamp to calendar time: %m");
×
1058

1059
        *ret = (EFI_TIME) {
1✔
1060
                .Year = 1900 + tm.tm_year,
1✔
1061
                /* tm_mon starts at 0, EFI_TIME months start at 1. */
1062
                .Month = tm.tm_mon + 1,
1✔
1063
                .Day = tm.tm_mday,
1✔
1064
                .Hour = tm.tm_hour,
1✔
1065
                .Minute = tm.tm_min,
1✔
1066
                .Second = tm.tm_sec,
1✔
1067
        };
1068

1069
        return 0;
1✔
1070
}
1071
#endif
1072

1073
static int install_secure_boot_auto_enroll(InstallContext *c) {
15✔
1074
#if HAVE_OPENSSL
1075
        int r;
15✔
1076
#endif
1077

1078
        if (!arg_secure_boot_auto_enroll)
15✔
1079
                return 0;
15✔
1080

1081
#if HAVE_OPENSSL
1082
        if (!c->secure_boot_certificate || !c->secure_boot_private_key)
1✔
1083
                return 0;
1084

1085
        r = dlopen_libcrypto(LOG_DEBUG);
1✔
1086
        if (r < 0)
1✔
1087
                return r;
1088

1089
        _cleanup_free_ uint8_t *dercert = NULL;
1✔
1090
        int dercertsz;
1✔
1091
        dercertsz = sym_i2d_X509(c->secure_boot_certificate, &dercert);
1✔
1092
        if (dercertsz < 0)
1✔
1093
                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert X.509 certificate to DER: %s",
×
1094
                                       sym_ERR_error_string(sym_ERR_get_error(), NULL));
1095

1096
        if (c->esp_fd < 0)
1✔
1097
                return c->esp_fd;
1098

1099
        _cleanup_free_ char *j = path_join(c->root, c->esp_path);
2✔
1100
        if (!j)
1✔
1101
                return log_oom();
×
1102

1103
        _cleanup_close_ int keys_fd = -EBADF;
1✔
1104
        r = chaseat(c->esp_fd,
1✔
1105
                    c->esp_fd,
1106
                    "loader/keys/auto",
1107
                    CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY,
1108
                    /* ret_path= */ NULL,
1109
                    &keys_fd);
1110
        if (r < 0)
1✔
UNCOV
1111
                return log_error_errno(r, "Failed to chase /loader/keys/auto/ below '%s': %m", j);
×
1112

1113
        uint32_t siglistsz = offsetof(EFI_SIGNATURE_LIST, Signatures) + offsetof(EFI_SIGNATURE_DATA, SignatureData) + dercertsz;
1✔
1114
        /* We use malloc0() to zero-initialize the SignatureOwner field of Signatures[0]. */
1115
        _cleanup_free_ EFI_SIGNATURE_LIST *siglist = malloc0(siglistsz);
2✔
1116
        if (!siglist)
1✔
UNCOV
1117
                return log_oom();
×
1118

1119
        *siglist = (EFI_SIGNATURE_LIST) {
1✔
1120
                .SignatureType = EFI_CERT_X509_GUID,
1121
                .SignatureListSize = siglistsz,
1122
                .SignatureSize = offsetof(EFI_SIGNATURE_DATA, SignatureData) + dercertsz,
1✔
1123
        };
1124

1125
        memcpy(siglist->Signatures[0].SignatureData, dercert, dercertsz);
1✔
1126

1127
        EFI_TIME timestamp;
1✔
1128
        r = efi_timestamp(&timestamp);
1✔
1129
        if (r < 0)
1✔
1130
                return r;
1131

1132
        uint32_t attrs =
1✔
1133
                EFI_VARIABLE_NON_VOLATILE|
1134
                EFI_VARIABLE_BOOTSERVICE_ACCESS|
1135
                EFI_VARIABLE_RUNTIME_ACCESS|
1136
                EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS;
1137

1138
        FOREACH_STRING(db, "PK", "KEK", "db") {
4✔
UNCOV
1139
                _cleanup_(BIO_freep) BIO *bio = NULL;
×
1140

1141
                bio = sym_BIO_new(sym_BIO_s_mem());
3✔
1142
                if (!bio)
3✔
UNCOV
1143
                        return log_oom();
×
1144

1145
                _cleanup_free_ char16_t *db16 = utf8_to_utf16(db, SIZE_MAX);
6✔
1146
                if (!db16)
3✔
UNCOV
1147
                        return log_oom();
×
1148

1149
                /* Don't count the trailing NUL terminator. */
1150
                if (sym_BIO_write(bio, db16, char16_strsize(db16) - sizeof(char16_t)) < 0)
3✔
UNCOV
1151
                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write variable name to bio");
×
1152

1153
                EFI_GUID *guid = STR_IN_SET(db, "PK", "KEK") ? &(EFI_GUID) EFI_GLOBAL_VARIABLE : &(EFI_GUID) EFI_IMAGE_SECURITY_DATABASE_GUID;
3✔
1154

1155
                if (sym_BIO_write(bio, guid, sizeof(*guid)) < 0)
3✔
UNCOV
1156
                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write variable GUID to bio");
×
1157

1158
                if (sym_BIO_write(bio, &attrs, sizeof(attrs)) < 0)
3✔
1159
                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write variable attributes to bio");
×
1160

1161
                if (sym_BIO_write(bio, &timestamp, sizeof(timestamp)) < 0)
3✔
UNCOV
1162
                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write timestamp to bio");
×
1163

1164
                if (sym_BIO_write(bio, siglist, siglistsz) < 0)
3✔
1165
                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write signature list to bio");
×
1166

UNCOV
1167
                _cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
×
1168
                p7 = sym_PKCS7_sign(c->secure_boot_certificate, c->secure_boot_private_key, /* certs= */ NULL, bio, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY|PKCS7_NOSMIMECAP);
3✔
1169
                if (!p7)
3✔
UNCOV
1170
                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to calculate PKCS7 signature: %s",
×
1171
                                               sym_ERR_error_string(sym_ERR_get_error(), NULL));
1172

1173
                _cleanup_free_ uint8_t *sig = NULL;
×
1174
                int sigsz = sym_i2d_PKCS7(p7, &sig);
3✔
1175
                if (sigsz < 0)
3✔
UNCOV
1176
                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert PKCS7 signature to DER: %s",
×
1177
                                               sym_ERR_error_string(sym_ERR_get_error(), NULL));
1178

1179
                size_t authsz = offsetof(EFI_VARIABLE_AUTHENTICATION_2, AuthInfo.CertData) + sigsz;
3✔
UNCOV
1180
                _cleanup_free_ EFI_VARIABLE_AUTHENTICATION_2 *auth = malloc(authsz);
×
1181
                if (!auth)
3✔
UNCOV
1182
                        return log_oom();
×
1183

1184
                *auth = (EFI_VARIABLE_AUTHENTICATION_2) {
3✔
1185
                        .TimeStamp = timestamp,
1186
                        .AuthInfo = {
1187
                                .Hdr = {
1188
                                        .dwLength = offsetof(WIN_CERTIFICATE_UEFI_GUID, CertData) + sigsz,
3✔
1189
                                        .wRevision = 0x0200,
1190
                                        .wCertificateType = 0x0EF1, /* WIN_CERT_TYPE_EFI_GUID */
1191
                                },
1192
                                .CertType = EFI_CERT_TYPE_PKCS7_GUID,
1193
                        }
1194
                };
1195

1196
                memcpy(auth->AuthInfo.CertData, sig, sigsz);
3✔
1197

1198
                _cleanup_free_ char *filename = strjoin(db, ".auth");
3✔
1199
                if (!filename)
3✔
UNCOV
1200
                        return log_oom();
×
1201

1202
                _cleanup_free_ char *t = NULL;
3✔
1203
                _cleanup_close_ int fd = open_tmpfile_linkable_at(keys_fd, filename, O_WRONLY|O_CLOEXEC, &t);
6✔
1204
                if (fd < 0)
3✔
UNCOV
1205
                        return log_error_errno(fd, "Failed to open secure boot auto-enrollment file for writing: %m");
×
1206

UNCOV
1207
                CLEANUP_TMPFILE_AT(keys_fd, t);
×
1208

1209
                r = loop_write(fd, auth, authsz);
3✔
1210
                if (r < 0)
3✔
UNCOV
1211
                        return log_error_errno(r, "Failed to write authentication descriptor to secure boot auto-enrollment file: %m");
×
1212

1213
                r = loop_write(fd, siglist, siglistsz);
3✔
1214
                if (r < 0)
3✔
UNCOV
1215
                        return log_error_errno(r, "Failed to write signature list to secure boot auto-enrollment file: %m");
×
1216

1217
                r = link_tmpfile_at(fd, keys_fd, t, filename, LINK_TMPFILE_SYNC);
3✔
1218
                if (r < 0)
3✔
UNCOV
1219
                        return log_error_errno(errno, "Failed to link secure boot auto-enrollment file: %m");
×
1220

1221
                t = mfree(t); /* Disarm CLEANUP_TMPFILE_AT() */
3✔
1222

1223
                log_info("Secure boot auto-enrollment file '%s/loader/keys/auto/%s' successfully written.", j, filename);
3✔
1224
        }
1225

1226
        return 0;
1✔
1227
#else
1228
        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Built without OpenSSL support, cannot set up auto-enrollment.");
1229
#endif
1230
}
1231

1232
static bool same_entry(uint16_t id, sd_id128_t uuid, const char *path) {
183✔
1233
        _cleanup_free_ char *opath = NULL;
183✔
1234
        sd_id128_t ouuid;
183✔
1235
        int r;
183✔
1236

1237
        r = efi_get_boot_option(id, NULL, &ouuid, &opath, NULL);
183✔
1238
        if (r < 0)
183✔
1239
                return false;
1240
        if (!sd_id128_equal(uuid, ouuid))
183✔
1241
                return false;
144✔
1242

1243
        /* Some motherboards convert the path to uppercase under certain circumstances
1244
         * (e.g. after booting into the Boot Menu in the ASUS ROG STRIX B350-F GAMING),
1245
         * so use case-insensitive checking */
1246
        if (!strcaseeq_ptr(path, opath))
39✔
1247
                return false;
20✔
1248

1249
        return true;
1250
}
1251

1252
static int find_slot(sd_id128_t uuid, const char *path, uint16_t *id) {
46✔
1253
        _cleanup_free_ uint16_t *options = NULL;
46✔
1254

1255
        assert(id);
46✔
1256

1257
        int n = efi_get_boot_options(&options);
46✔
1258
        if (n < 0)
46✔
1259
                return n;
1260

1261
        /* find already existing systemd-boot entry */
1262
        for (int i = 0; i < n; i++)
210✔
1263
                if (same_entry(options[i], uuid, path)) {
183✔
1264
                        *id = options[i];
19✔
1265
                        return 1;
19✔
1266
                }
1267

1268
        /* find free slot in the sorted BootXXXX variable list */
1269
        for (int i = 0; i < n; i++)
127✔
1270
                if (i != options[i]) {
100✔
UNCOV
1271
                        *id = i;
×
UNCOV
1272
                        return 0;
×
1273
                }
1274

1275
        /* use the next one */
1276
        if (n == 0xffff)
27✔
1277
                return -ENOSPC;
1278
        *id = n;
27✔
1279
        return 0;
27✔
1280
}
1281

1282
static int insert_into_order(InstallContext *c, uint16_t slot, uint16_t after_slot) {
40✔
1283
        _cleanup_free_ uint16_t *order = NULL;
40✔
1284
        uint16_t *t;
40✔
1285
        int n;
40✔
1286

1287
        assert(c);
40✔
1288

1289
        n = efi_get_boot_order(&order);
40✔
1290
        if (n <= 0)
40✔
1291
                /* no entry, add us */
1292
                return efi_set_boot_order(&slot, 1);
×
1293

1294
        /* are we the first and only one? */
1295
        if (n == 1 && order[0] == slot)
40✔
1296
                return 0;
1297

1298
        /* are we already in the boot order? */
1299
        for (int i = 0; i < n; i++) {
160✔
1300
                if (order[i] != slot)
133✔
1301
                        continue;
120✔
1302

1303
                /* we do not require to be the first one, all is fine */
1304
                /* if after_slot is set, leave existing position alone to preserve user reordering. */
1305
                if (i == 0 || c->operation != INSTALL_NEW || after_slot != UINT16_MAX)
13✔
1306
                        return 0;
1307

1308
                /* move us to the first slot */
1309
                memmove(order + 1, order, i * sizeof(uint16_t));
1✔
1310
                order[0] = slot;
1✔
1311
                return efi_set_boot_order(order, n);
1✔
1312
        }
1313

1314
        /* slot is not yet in the order, so insert after a specific slot if requested */
1315
        if (after_slot != UINT16_MAX) {
27✔
1316
                t = reallocarray(order, n + 1, sizeof(uint16_t));
15✔
1317
                if (!t)
15✔
1318
                        return -ENOMEM;
1319
                order = t;
15✔
1320

1321
                for (int i = 0; i < n; i++) {
21✔
1322
                        if (order[i] != after_slot)
21✔
1323
                                continue;
6✔
1324

1325
                        memmove(order + i + 2, order + i + 1, (n - i - 1) * sizeof(uint16_t));
15✔
1326
                        order[i + 1] = slot;
15✔
1327
                        return efi_set_boot_order(order, n + 1);
15✔
1328
                }
1329

1330
                log_warning("Boot entry %04" PRIx16 " not found in BootOrder, appending new entry at the end.", after_slot);
×
UNCOV
1331
                order[n] = slot;
×
UNCOV
1332
                return efi_set_boot_order(order, n + 1);
×
1333
        }
1334

1335
        /* extend array */
1336
        t = reallocarray(order, n + 1, sizeof(uint16_t));
12✔
1337
        if (!t)
12✔
1338
                return -ENOMEM;
1339
        order = t;
12✔
1340

1341
        /* add us to the top or end of the list */
1342
        if (c->operation != INSTALL_NEW) {
12✔
1343
                memmove(order + 1, order, n * sizeof(uint16_t));
10✔
1344
                order[0] = slot;
10✔
1345
        } else
1346
                order[n] = slot;
2✔
1347

1348
        return efi_set_boot_order(order, n + 1);
12✔
1349
}
1350

1351
static int remove_from_order(uint16_t slot) {
6✔
1352
        _cleanup_free_ uint16_t *order = NULL;
6✔
1353
        int n;
6✔
1354

1355
        n = efi_get_boot_order(&order);
6✔
1356
        if (n <= 0)
6✔
1357
                return n;
1358

1359
        for (int i = 0; i < n; i++) {
15✔
1360
                if (order[i] != slot)
15✔
1361
                        continue;
9✔
1362

1363
                if (i + 1 < n)
6✔
1364
                        memmove(order + i, order + i+1, (n - i) * sizeof(uint16_t));
4✔
1365
                return efi_set_boot_order(order, n - 1);
6✔
1366
        }
1367

1368
        return 0;
1369
}
1370

1371
static int pick_efi_boot_option_description(int esp_fd, char **ret) {
20✔
1372
        int r;
20✔
1373

1374
        assert(esp_fd >= 0);
20✔
1375
        assert(ret);
20✔
1376

1377
        /* early declarations, so that they are definitely initialized even if we follow any of the gotos */
1378
        _cleanup_(sd_device_unrefp) sd_device *d = NULL;
20✔
1379
        _cleanup_free_ char *j = NULL;
20✔
1380

1381
        const char *b = arg_efi_boot_option_description ?: "Linux Boot Manager";
20✔
1382
        if (!arg_efi_boot_option_description_with_device)
20✔
1383
                goto fallback;
20✔
1384

1385
        r = block_device_new_from_fd(
×
1386
                        esp_fd,
1387
                        BLOCK_DEVICE_LOOKUP_WHOLE_DISK|BLOCK_DEVICE_LOOKUP_BACKING,
1388
                        &d);
UNCOV
1389
        if (r < 0) {
×
UNCOV
1390
                log_debug_errno(r, "Failed to find backing device of ESP: %m");
×
UNCOV
1391
                goto fallback;
×
1392
        }
1393

UNCOV
1394
        const char *serial;
×
UNCOV
1395
        r = sd_device_get_property_value(d, "ID_SERIAL", &serial);
×
UNCOV
1396
        if (r < 0) {
×
UNCOV
1397
                log_debug_errno(r, "Unable to read ID_SERIAL field of backing device of ESP: %m");
×
UNCOV
1398
                goto fallback;
×
1399
        }
1400

UNCOV
1401
        j = strjoin(b, " (", serial, ")");
×
UNCOV
1402
        if (!j)
×
UNCOV
1403
                return log_oom();
×
1404

UNCOV
1405
        if (strlen(j) > EFI_BOOT_OPTION_DESCRIPTION_MAX) {
×
UNCOV
1406
                log_debug("Boot option string suffixed with device serial would be too long, skipping: %s", j);
×
UNCOV
1407
                j = mfree(j);
×
UNCOV
1408
                goto fallback;
×
1409
        }
1410

UNCOV
1411
        *ret = TAKE_PTR(j);
×
UNCOV
1412
        return 0;
×
1413

1414
fallback:
20✔
1415
        j = strdup(b);
20✔
1416
        if (!j)
20✔
1417
                return log_oom();
×
1418

1419
        *ret = TAKE_PTR(j);
20✔
1420
        return 0;
20✔
1421
}
1422

1423
static int install_boot_option(
40✔
1424
                InstallContext *c,
1425
                const char *path,
1426
                const char *description,
1427
                bool require_existing,
1428
                uint16_t after_slot,
1429
                uint16_t *ret_slot) {
1430

1431
        uint16_t slot;
40✔
1432
        int r;
40✔
1433

1434
        assert(c);
40✔
1435
        assert(path);
40✔
1436
        assert(description);
40✔
1437

1438
        if (c->esp_fd < 0)
40✔
1439
                return c->esp_fd;
40✔
1440

1441
        _cleanup_free_ char *j = path_join(c->root, c->esp_path);
80✔
1442
        if (!j)
40✔
UNCOV
1443
                return log_oom();
×
1444

1445
        r = chase_and_accessat(
40✔
1446
                        c->esp_fd,
1447
                        c->esp_fd,
1448
                        path,
1449
                        CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_REGULAR,
1450
                        F_OK,
1451
                        /* ret_path= */ NULL);
1452
        if (r == -ENOENT && require_existing)
40✔
1453
                return 0;
1454
        if (r < 0 && r != -ENOENT)
40✔
UNCOV
1455
                return log_error_errno(r, "Cannot access \"%s/%s\": %m", j, skip_leading_slash(path));
×
1456

1457
        r = find_slot(c->esp_uuid, path, &slot);
40✔
1458
        if (r < 0) {
40✔
UNCOV
1459
                int level = c->graceful ? arg_quiet ? LOG_DEBUG : LOG_INFO : LOG_ERR;
×
UNCOV
1460
                const char *skip = c->graceful ? ", skipping" : "";
×
1461

UNCOV
1462
                log_full_errno(level, r,
×
1463
                               r == -ENOENT ?
1464
                               "Failed to access EFI variables%s. Is the \"efivarfs\" filesystem mounted?" :
1465
                               "Failed to determine current boot order%s: %m", skip);
1466

UNCOV
1467
                return c->graceful ? 0 : r;
×
1468
        }
1469

1470
        bool existing = r > 0;
40✔
1471

1472
        if (c->operation == INSTALL_NEW || !existing) {
40✔
1473
                r = efi_add_boot_option(
33✔
1474
                                slot,
1475
                                description,
1476
                                c->esp_part,
1477
                                c->esp_pstart,
1478
                                c->esp_psize,
1479
                                c->esp_uuid,
1480
                                path);
1481
                if (r < 0) {
33✔
UNCOV
1482
                        int level = c->graceful ? arg_quiet ? LOG_DEBUG : LOG_INFO : LOG_ERR;
×
UNCOV
1483
                        const char *skip = c->graceful ? ", skipping" : "";
×
1484

UNCOV
1485
                        log_full_errno(level, r, "Failed to create EFI Boot variable entry%s: %m", skip);
×
1486

UNCOV
1487
                        return c->graceful ? 0 : r;
×
1488
                }
1489

1490
                log_info("%s EFI boot entry \"%s\".",
60✔
1491
                         existing ? "Updated" : "Created",
1492
                         description);
1493
        }
1494

1495
        r = insert_into_order(c, slot, after_slot);
40✔
1496
        if (r < 0)
40✔
1497
                return r;
1498

1499
        if (ret_slot)
40✔
1500
                *ret_slot = slot;
20✔
1501

1502
        return 0;
1503
}
1504

1505
static int are_we_installed(InstallContext *c) {
144✔
1506
        int r;
144✔
1507

1508
        assert(c);
144✔
1509

1510
        /* Tests whether systemd-boot is installed. It's not obvious what to use as check here: we could
1511
         * check EFI variables, we could check what binary /EFI/BOOT/BOOT*.EFI points to, or whether the
1512
         * loader entries directory exists. Here we opted to check whether /EFI/systemd/ is non-empty, which
1513
         * should be a suitable and very minimal check for a number of reasons:
1514
         *
1515
         *  → The check is architecture independent (i.e. we check if any systemd-boot loader is installed,
1516
         *    not a specific one.)
1517
         *
1518
         *  → It doesn't assume we are the only boot loader (i.e doesn't check if we own the main
1519
         *    /EFI/BOOT/BOOT*.EFI fallback binary.
1520
         *
1521
         *  → It specifically checks for systemd-boot, not for other boot loaders (which a check for
1522
         *    /boot/loader/entries would do). */
1523

1524
        _cleanup_free_ char *p = path_join(c->esp_path, "/EFI/systemd");
288✔
1525
        if (!p)
144✔
UNCOV
1526
                return log_oom();
×
1527

1528
        if (c->esp_fd < 0)
144✔
1529
                return c->esp_fd;
1530

1531
        _cleanup_close_ int fd = chase_and_openat(
288✔
1532
                        c->esp_fd,
1533
                        c->esp_fd,
1534
                        "/EFI/systemd",
1535
                        CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY,
1536
                        O_RDONLY|O_CLOEXEC|O_DIRECTORY,
1537
                        /* ret_path= */ NULL);
1538
        if (fd == -ENOENT)
144✔
1539
                return 0;
1540
        if (fd < 0)
137✔
UNCOV
1541
                return log_error_errno(fd, "Failed to open '%s': %m", p);
×
1542

1543
        log_debug("Checking whether '%s' contains any files%s", p, glyph(GLYPH_ELLIPSIS));
261✔
1544
        r = dir_is_empty_at(fd, /* path= */ NULL, /* ignore_hidden_or_backup= */ false);
137✔
1545
        if (r < 0 && r != -ENOENT)
137✔
UNCOV
1546
                return log_error_errno(r, "Failed to check whether '%s' contains any files: %m", p);
×
1547

1548
        return r == 0;
137✔
1549
}
1550

1551
#if HAVE_OPENSSL
1552
static int load_secure_boot_auto_enroll(
145✔
1553
                X509 **ret_certificate,
1554
                EVP_PKEY **ret_private_key,
1555
                OpenSSLAskPasswordUI **ret_ui) {
1556

1557
        int r;
145✔
1558

1559
        assert(ret_certificate);
145✔
1560
        assert(ret_private_key);
145✔
1561
        assert(ret_ui);
145✔
1562

1563
        if (!arg_secure_boot_auto_enroll) {
145✔
1564
                *ret_certificate = NULL;
144✔
1565
                *ret_private_key = NULL;
144✔
1566
                return 0;
145✔
1567
        }
1568

1569
        if (arg_certificate_source_type == OPENSSL_CERTIFICATE_SOURCE_FILE) {
1✔
1570
                r = parse_path_argument(arg_certificate, /* suppress_root= */ false, &arg_certificate);
1✔
1571
                if (r < 0)
1✔
1572
                        return r;
1573
        }
1574

1575
        _cleanup_(X509_freep) X509 *certificate = NULL;
1✔
1576
        r = openssl_load_x509_certificate(
1✔
1577
                        arg_certificate_source_type,
1578
                        arg_certificate_source,
1579
                        arg_certificate,
1580
                        &certificate);
1581
        if (r < 0)
1✔
UNCOV
1582
                return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate);
×
1583

1584
        if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) {
1✔
1585
                r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key);
1✔
1586
                if (r < 0)
1✔
1587
                        return r;
1588
        }
1589

1590
        r = openssl_load_private_key(
2✔
1591
                        arg_private_key_source_type,
1592
                        arg_private_key_source,
1593
                        arg_private_key,
1594
                        &(AskPasswordRequest) {
1✔
1595
                                .tty_fd = -EBADF,
1596
                                .id = "bootctl-private-key-pin",
1597
                                .keyring = arg_private_key,
1598
                                .credential = "bootctl.private-key-pin",
1599
                                .until = USEC_INFINITY,
1600
                                .hup_fd = -EBADF,
1601
                        },
1602
                        ret_private_key,
1603
                        ret_ui);
1604
        if (r < 0)
1✔
UNCOV
1605
                return log_error_errno(r, "Failed to load private key from %s: %m", arg_private_key);
×
1606

1607
        *ret_certificate = TAKE_PTR(certificate);
1✔
1608

1609
        return 0;
1✔
1610
}
1611
#endif
1612

1613
static int install_variables(InstallContext *c, const char *arch) {
20✔
1614
        int r;
20✔
1615

1616
        assert(c);
20✔
1617

1618
        const char *path = strjoina("/EFI/systemd/systemd-boot", arch, ".efi");
140✔
1619

1620
        _cleanup_free_ char *description = NULL;
20✔
1621
        r = pick_efi_boot_option_description(c->esp_fd, &description);
20✔
1622
        if (r < 0)
20✔
1623
                return r;
1624

1625
        uint16_t primary_slot = UINT16_MAX;
20✔
1626
        r = install_boot_option(c, path, description, /* require_existing= */ true, /* after_slot= */ UINT16_MAX, &primary_slot);
20✔
1627
        if (r < 0)
20✔
1628
                return r;
1629
        /* If primary registration was skipped (e.g. binary not on ESP), skip the fallback too
1630
         * or else it would land at position 0 in BootOrder with no primary ahead of it. */
1631
        if (primary_slot == UINT16_MAX)
20✔
1632
                return 0;
1633

1634
        const char *fallback_path = strjoina("/EFI/systemd/systemd-boot-fallback", arch, ".efi");
140✔
1635

1636
        _cleanup_free_ char *fallback_description = strjoin("Fallback ", description);
40✔
1637
        if (!fallback_description)
20✔
UNCOV
1638
                return log_oom();
×
1639

1640
        strshorten(fallback_description, EFI_BOOT_OPTION_DESCRIPTION_MAX);
20✔
1641

1642
        return install_boot_option(c, fallback_path, fallback_description, /* require_existing= */ false, /* after_slot= */ primary_slot, /* ret_slot= */ NULL);
20✔
1643
}
1644

1645
static int run_install(InstallContext *c) {
145✔
1646
        int r;
145✔
1647

1648
        assert(c);
145✔
1649
        assert(c->operation >= 0);
145✔
1650

1651
        if (c->operation == INSTALL_UPDATE) {
145✔
1652
                /* If we are updating, don't do anything if sd-boot wasn't actually installed. */
1653
                r = are_we_installed(c);
130✔
1654
                if (r < 0)
130✔
1655
                        return r;
145✔
1656
                if (r == 0) {
130✔
UNCOV
1657
                        log_debug("Skipping update because sd-boot is not installed in the ESP.");
×
1658
                        return 0;
1659
                }
1660
        }
1661

1662
        r = settle_make_entry_directory(c);
145✔
1663
        if (r < 0)
145✔
1664
                return r;
1665

1666
        const char *arch = arg_arch_all ? "" : get_efi_arch();
145✔
1667

1668
        if (c->esp_fd < 0)
145✔
1669
                return c->esp_fd;
1670

1671
        _cleanup_free_ char *j = path_join(c->root, c->esp_path);
290✔
1672
        if (!j)
145✔
UNCOV
1673
                return log_oom();
×
1674

1675
        int dollar_boot_fd = acquire_dollar_boot_fd(c);
145✔
1676
        if (dollar_boot_fd < 0)
145✔
1677
                return dollar_boot_fd;
1678

1679
        _cleanup_free_ char *w = path_join(c->root, dollar_boot_path(c));
290✔
1680
        if (!w)
145✔
UNCOV
1681
                return log_oom();
×
1682

1683
        WITH_UMASK(0002) {
290✔
1684
                if (c->operation == INSTALL_NEW) {
145✔
1685
                        /* Don't create any of these directories when we are just updating. When we update
1686
                         * we'll drop-in our files (unless there are newer ones already), but we won't create
1687
                         * the directories for them in the first place. */
1688

1689
                        r = create_subdirs(j, c->esp_fd, esp_subdirs);
15✔
1690
                        if (r < 0)
15✔
1691
                                return r;
1692

1693
                        r = create_subdirs(w, dollar_boot_fd, dollar_boot_subdirs);
15✔
1694
                        if (r < 0)
15✔
1695
                                return r;
1696
                }
1697

1698
                r = install_binaries(c, arch);
145✔
1699
                if (r < 0)
145✔
1700
                        return r;
1701

1702
                if (c->operation == INSTALL_NEW) {
142✔
1703
                        r = install_loader_config(c);
15✔
1704
                        if (r < 0)
15✔
1705
                                return r;
1706

1707
                        r = install_entry_directory(c);
15✔
1708
                        if (r < 0)
15✔
1709
                                return r;
1710

1711
                        r = install_entry_token(c);
15✔
1712
                        if (r < 0)
15✔
1713
                                return r;
1714

1715
                        if (arg_install_random_seed && !c->root) {
15✔
1716
                                r = install_random_seed(c->esp_path, c->esp_fd);
7✔
1717
                                if (r < 0)
7✔
1718
                                        return r;
1719
                        }
1720

1721
                        r = install_secure_boot_auto_enroll(c);
15✔
1722
                        if (r < 0)
15✔
1723
                                return r;
1724
                }
1725

1726
                r = install_loader_specification(c);
142✔
1727
                if (r < 0)
142✔
1728
                        return r;
1729
        }
1730

1731
        (void) sync_everything();
142✔
1732

1733
        if (!should_touch_install_variables(c))
142✔
1734
                return 0;
1735

1736
        if (arg_arch_all) {
22✔
1737
                log_info("Not changing EFI variables with --all-architectures.");
2✔
1738
                return 0;
1739
        }
1740

1741
        return install_variables(c, arch);
20✔
1742
}
1743

1744
int verb_install(int argc, char *argv[], uintptr_t _data, void *userdata) {
145✔
1745
        int r;
145✔
1746

1747
        /* Invoked for both "update" and "install" */
1748

UNCOV
1749
        _cleanup_(install_context_done) InstallContext c = INSTALL_CONTEXT_NULL;
×
1750
        r = install_context_from_cmdline(&c, streq(argv[0], "install") ? INSTALL_NEW : INSTALL_UPDATE);
145✔
1751
        if (r < 0)
145✔
1752
                return r;
1753
        if (r == 0) {
145✔
UNCOV
1754
                log_debug("No ESP found and operating in graceful mode, skipping.");
×
1755
                return 0;
1756
        }
1757

1758
#if HAVE_OPENSSL
1759
        _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL;
145✔
1760
        r = load_secure_boot_auto_enroll(&c.secure_boot_certificate, &c.secure_boot_private_key, &ui);
145✔
1761
        if (r < 0)
145✔
1762
                return r;
1763
#endif
1764

1765
        return run_install(&c);
145✔
1766
}
1767

1768
static int remove_boot_efi(InstallContext *c) {
13✔
1769
        int r, n = 0;
13✔
1770

1771
        assert(c);
13✔
1772

1773
        if (c->esp_fd < 0)
13✔
1774
                return c->esp_fd;
13✔
1775

1776
        _cleanup_free_ char *w = path_join(c->root, c->esp_path);
26✔
1777
        if (!w)
13✔
UNCOV
1778
                return log_oom();
×
1779

1780
        _cleanup_closedir_ DIR *d = NULL;
13✔
1781
        _cleanup_free_ char *p = NULL;
13✔
1782
        r = chase_and_opendirat(
13✔
1783
                        c->esp_fd,
1784
                        c->esp_fd,
1785
                        "/EFI/BOOT",
1786
                        CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY,
1787
                        &p,
1788
                        &d);
1789
        if (r == -ENOENT)
13✔
1790
                return 0;
1791
        if (r < 0)
13✔
UNCOV
1792
                return log_error_errno(r, "Failed to open directory \"%s/EFI/BOOT\": %m", w);
×
1793

1794
        _cleanup_free_ char *j = path_join(w, p);
26✔
1795
        if (!j)
13✔
UNCOV
1796
                return log_oom();
×
1797

1798
        FOREACH_DIRENT(de, d, break) {
61✔
1799
                _cleanup_close_ int fd = -EBADF;
22✔
1800

1801
                if (!endswith_no_case(de->d_name, ".efi"))
22✔
UNCOV
1802
                        continue;
×
1803

1804
                _cleanup_free_ char *z = path_join(j, de->d_name);
31✔
1805
                if (!z)
22✔
UNCOV
1806
                        return log_oom();
×
1807

1808
                fd = xopenat_full(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY|O_NOFOLLOW, XO_REGULAR, /* mode= */ MODE_INVALID);
22✔
1809
                if (fd == -ENOENT)
22✔
UNCOV
1810
                        continue;
×
1811
                if (fd < 0)
22✔
UNCOV
1812
                        return log_error_errno(fd, "Failed to open '%s' for reading: %m", z);
×
1813

1814
                r = pe_is_native_fd(fd);
22✔
1815
                if (r < 0) {
22✔
UNCOV
1816
                        log_warning_errno(r, "Failed to detect if '%s' is native architecture, ignoring: %m", z);
×
UNCOV
1817
                        continue;
×
1818
                }
1819
                if (r == 0)
22✔
1820
                        continue;
9✔
1821

1822
                _cleanup_free_ char *v = NULL;
13✔
1823
                r = get_file_version(fd, &v);
13✔
1824
                if (r == -ESRCH)
13✔
1825
                        continue;  /* No version information */
×
1826
                if (r < 0) {
13✔
UNCOV
1827
                        log_warning_errno(r, "Failed to get file version of '%s', skipping: %m", de->d_name);
×
UNCOV
1828
                        continue;
×
1829
                }
1830
                if (!startswith(v, "systemd-boot "))
13✔
UNCOV
1831
                        continue;
×
1832

1833
                if (unlinkat(dirfd(d), de->d_name, 0) < 0)
13✔
UNCOV
1834
                        return log_error_errno(errno, "Failed to remove '%s': %m", z);
×
1835

1836
                log_info("Removed '%s'.", z);
13✔
1837

1838
                n++;
13✔
1839
        }
1840

1841
        log_debug("Removed %i EFI binaries from '%s'.", n, j);
13✔
1842
        return n;
1843
}
1844

1845
static int unlink_inode(const char *root, int root_fd, const char *path, mode_t type) {
271✔
1846
        int r;
271✔
1847

1848
        assert(root);
271✔
1849
        assert(root_fd >= 0);
271✔
1850
        assert(path);
271✔
1851
        assert(IN_SET(type, S_IFREG, S_IFDIR));
271✔
1852

1853
        _cleanup_free_ char *p = path_join(empty_to_root(root), path);
542✔
1854
        if (!p)
271✔
UNCOV
1855
                return log_oom();
×
1856

1857
        r = chase_and_unlinkat(
271✔
1858
                        root_fd,
1859
                        root_fd,
1860
                        path,
1861
                        CHASE_PROHIBIT_SYMLINKS,
1862
                        S_ISDIR(type) ? AT_REMOVEDIR : 0,
271✔
1863
                        /* ret_path= */ NULL);
1864
        if (r < 0) {
271✔
1865
                bool ignore = IN_SET(r, -ENOENT, -ENOTEMPTY);
152✔
1866
                log_full_errno(ignore ? LOG_DEBUG : LOG_ERR, r, "Failed to remove '%s': %m", p);
152✔
1867
                return ignore ? 0 : r;
152✔
1868
        }
1869

1870
        log_info("Removed %s\"%s\".", S_ISDIR(type) ? "directory " : "", p);
155✔
1871
        return 0;
1872
}
1873

1874
static int remove_subdirs(const char *root, int root_fd, const char *const *subdirs) {
34✔
1875
        int r = 0;
34✔
1876

1877
        assert(root);
34✔
1878
        assert(root_fd);
34✔
1879

1880
        STRV_FOREACH_BACKWARDS(i, (char**) subdirs)
183✔
1881
                RET_GATHER(r, unlink_inode(root, root_fd, *i, S_IFDIR));
149✔
1882

1883
        return r;
34✔
1884
}
1885

1886
static int remove_entry_directory(InstallContext *c, const char *path, int fd) {
21✔
1887
        assert(c);
21✔
1888
        assert(c->make_entry_directory >= 0);
21✔
1889
        assert(path);
21✔
1890
        assert(fd >= 0);
21✔
1891

1892
        if (!c->make_entry_directory || !c->entry_token)
21✔
1893
                return 0;
1894

1895
        return unlink_inode(path, fd, c->entry_token, S_IFDIR);
10✔
1896
}
1897

1898
static int remove_binaries(InstallContext *c) {
13✔
1899
        int r;
13✔
1900

1901
        _cleanup_free_ char *p = path_join(c->root, "/EFI/systemd");
26✔
1902
        if (!p)
13✔
UNCOV
1903
                return log_oom();
×
1904

1905
        _cleanup_close_ int efi_fd = -EBADF;
13✔
1906
        r = chaseat(c->esp_fd,
13✔
1907
                    c->esp_fd,
1908
                    "EFI",
1909
                    CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY,
1910
                    /* ret_path= */ NULL,
1911
                    &efi_fd);
1912
        if (r < 0) {
13✔
UNCOV
1913
                if (r != -ENOENT)
×
UNCOV
1914
                        return log_error_errno(r, "Failed to remove '%s': %m", p);
×
1915

1916
                r = 0;
1917
        } else
1918
                r = rm_rf_at(efi_fd, "systemd", REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_MISSING_OK);
13✔
1919

1920
        return RET_GATHER(r, remove_boot_efi(c));
13✔
1921
}
1922

1923
static int remove_boot_option(sd_id128_t uuid, const char *path, bool in_order) {
6✔
1924
        uint16_t slot;
6✔
1925
        int r;
6✔
1926

1927
        r = find_slot(uuid, path, &slot);
6✔
1928
        if (r != 1)
6✔
1929
                return 0;
6✔
1930

1931
        r = efi_remove_boot_option(slot);
6✔
1932
        if (r < 0)
6✔
1933
                return r;
1934

1935
        if (in_order)
6✔
1936
                return remove_from_order(slot);
6✔
1937

1938
        return 0;
1939
}
1940

1941
static int remove_loader_variables(void) {
3✔
1942
        int r = 0;
3✔
1943

1944
        /* Remove all persistent loader variables we define */
1945

1946
        FOREACH_STRING(var,
30✔
1947
                       EFI_LOADER_VARIABLE_STR("LoaderConfigConsoleMode"),
1948
                       EFI_LOADER_VARIABLE_STR("LoaderConfigTimeout"),
1949
                       EFI_LOADER_VARIABLE_STR("LoaderConfigTimeoutOneShot"),
1950
                       EFI_LOADER_VARIABLE_STR("LoaderEntryPreferred"),
1951
                       EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"),
1952
                       EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"),
1953
                       EFI_LOADER_VARIABLE_STR("LoaderEntryLastBooted"),
1954
                       EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"),
1955
                       EFI_LOADER_VARIABLE_STR("LoaderSystemToken")) {
1956

1957
                int q;
27✔
1958

1959
                q = efi_set_variable(var, NULL, 0);
27✔
1960
                if (q == -ENOENT)
27✔
1961
                        continue;
24✔
1962
                if (q < 0)
3✔
UNCOV
1963
                        RET_GATHER(r, log_warning_errno(q, "Failed to remove EFI variable %s: %m", var));
×
1964
                else
1965
                        log_info("Removed EFI variable %s.", var);
3✔
1966
        }
1967

1968
        return r;
3✔
1969
}
1970

1971
static int remove_variables(sd_id128_t uuid) {
3✔
1972
        int r = 0;
3✔
1973

1974
        const char *path = strjoina("/EFI/systemd/systemd-boot", get_efi_arch(), ".efi");
21✔
1975
        RET_GATHER(r, remove_boot_option(uuid, path, /* in_order= */ true));
3✔
1976

1977
        const char *fallback_path = strjoina("/EFI/systemd/systemd-boot-fallback", get_efi_arch(), ".efi");
21✔
1978
        RET_GATHER(r, remove_boot_option(uuid, fallback_path, /* in_order= */ true));
3✔
1979

1980
        return RET_GATHER(r, remove_loader_variables());
3✔
1981
}
1982

1983
int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata) {
13✔
1984
        int r;
13✔
1985

1986
        _cleanup_(install_context_done) InstallContext c = INSTALL_CONTEXT_NULL;
13✔
1987
        r = install_context_from_cmdline(&c, INSTALL_REMOVE);
13✔
1988
        if (r < 0)
13✔
1989
                return r;
1990
        if (r == 0) {
13✔
UNCOV
1991
                log_debug("No ESP found and operating in graceful mode, skipping.");
×
1992
                return 0;
1993
        }
1994

1995
        r = settle_make_entry_directory(&c);
13✔
1996
        if (r < 0)
13✔
1997
                return r;
1998

1999
        if (c.esp_fd < 0)
13✔
2000
                return c.esp_fd;
2001

2002
        _cleanup_free_ char *j = path_join(c.root, c.esp_path);
26✔
2003
        if (!j)
13✔
UNCOV
2004
                return log_oom();
×
2005

2006
        int dollar_boot_fd = acquire_dollar_boot_fd(&c);
13✔
2007
        if (dollar_boot_fd < 0)
13✔
2008
                return dollar_boot_fd;
2009

2010
        _cleanup_free_ char *w = path_join(c.root, dollar_boot_path(&c));
26✔
2011
        if (!w)
13✔
2012
                return log_oom();
×
2013

2014
        r = remove_binaries(&c);
13✔
2015
        RET_GATHER(r, unlink_inode(j, c.esp_fd, "/loader/loader.conf", S_IFREG));
13✔
2016
        RET_GATHER(r, unlink_inode(j, c.esp_fd, "/loader/random-seed", S_IFREG));
13✔
2017
        RET_GATHER(r, unlink_inode(j, c.esp_fd, "/loader/entries.srel", S_IFREG));
13✔
2018

2019
        FOREACH_STRING(db, "PK.auth", "KEK.auth", "db.auth") {
52✔
2020
                _cleanup_free_ char *p = path_join("/loader/keys/auto", db);
78✔
2021
                if (!p)
39✔
2022
                        return log_oom();
×
2023

2024
                RET_GATHER(r, unlink_inode(j, c.esp_fd, p, S_IFREG));
39✔
2025
        }
2026
        RET_GATHER(r, unlink_inode(j, c.esp_fd, "/loader/keys/auto", S_IFDIR));
13✔
2027
        RET_GATHER(r, unlink_inode(j, c.esp_fd, "/loader/entries.srel", S_IFREG));
13✔
2028

2029
        RET_GATHER(r, remove_subdirs(j, c.esp_fd, esp_subdirs));
13✔
2030
        RET_GATHER(r, remove_subdirs(j, c.esp_fd, dollar_boot_subdirs));
13✔
2031
        RET_GATHER(r, remove_entry_directory(&c, j, c.esp_fd));
13✔
2032

2033
        if (c.xbootldr_fd >= 0) {
13✔
2034
                /* Remove a subset of these also from the XBOOTLDR partition if it exists */
2035
                RET_GATHER(r, unlink_inode(w, c.xbootldr_fd, "/loader/entries.srel", S_IFREG));
8✔
2036
                RET_GATHER(r, remove_subdirs(w, c.xbootldr_fd, dollar_boot_subdirs));
8✔
2037
                RET_GATHER(r, remove_entry_directory(&c, w, c.xbootldr_fd));
8✔
2038
        }
2039

2040
        (void) sync_everything();
13✔
2041

2042
        if (!should_touch_install_variables(&c))
13✔
2043
                return r;
2044

2045
        if (arg_arch_all) {
5✔
2046
                log_info("Not changing EFI variables with --all-architectures.");
2✔
2047
                return r;
2048
        }
2049

2050
        return remove_variables(c.esp_uuid);
3✔
2051
}
2052

2053
int verb_is_installed(int argc, char *argv[], uintptr_t _data, void *userdata) {
14✔
2054
        int r;
14✔
2055

2056
        _cleanup_(install_context_done) InstallContext c = INSTALL_CONTEXT_NULL;
14✔
2057
        r = install_context_from_cmdline(&c, INSTALL_TEST);
14✔
2058
        if (r < 0)
14✔
2059
                return r;
2060
        if (r == 0) {
14✔
UNCOV
2061
                log_debug("No ESP found and operating in graceful mode, claiming not installed.");
×
UNCOV
2062
                if (!arg_quiet)
×
UNCOV
2063
                        puts("no");
×
2064
                return EXIT_FAILURE;
2065
        }
2066

2067
        r = are_we_installed(&c);
14✔
2068
        if (r < 0)
14✔
2069
                return r;
2070

2071
        if (r > 0) {
14✔
2072
                if (!arg_quiet)
7✔
2073
                        puts("yes");
7✔
2074
                return EXIT_SUCCESS;
2075
        } else {
2076
                if (!arg_quiet)
7✔
2077
                        puts("no");
7✔
2078
                return EXIT_FAILURE;
2079
        }
2080
}
2081

UNCOV
2082
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_install_operation, InstallOperation, install_operation_from_string);
×
UNCOV
2083
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_boot_entry_token_type, BootEntryTokenType, boot_entry_token_type_from_string);
×
2084

2085
typedef struct InstallParameters {
2086
        InstallContext context;
2087
        unsigned root_fd_index;
2088
} InstallParameters;
2089

UNCOV
2090
static void install_parameters_done(InstallParameters *p) {
×
UNCOV
2091
        assert(p);
×
2092

UNCOV
2093
        install_context_done(&p->context);
×
UNCOV
2094
}
×
2095

UNCOV
2096
int vl_method_install(
×
2097
                sd_varlink *link,
2098
                sd_json_variant *parameters,
2099
                sd_varlink_method_flags_t flags,
2100
                void *userdata) {
2101

UNCOV
2102
        int r;
×
2103

UNCOV
2104
        assert(link);
×
2105

UNCOV
2106
        _cleanup_(install_parameters_done) InstallParameters p = {
×
2107
                .context = INSTALL_CONTEXT_NULL,
2108
                .root_fd_index = UINT_MAX,
2109
        };
2110

UNCOV
2111
        static const sd_json_dispatch_field dispatch_table[] = {
×
2112
                { "operation",          SD_JSON_VARIANT_STRING,        json_dispatch_install_operation,     voffsetof(p, context.operation),        SD_JSON_MANDATORY },
2113
                { "graceful",           SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_stdbool,            voffsetof(p, context.graceful),         0                 },
2114
                { "rootFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint,               voffsetof(p, root_fd_index),            0                 },
2115
                { "rootDirectory",      SD_JSON_VARIANT_STRING,        json_dispatch_path,                  voffsetof(p, context.root),             0                 },
2116
                { "bootEntryTokenType", SD_JSON_VARIANT_STRING,        json_dispatch_boot_entry_token_type, voffsetof(p, context.entry_token_type), 0                 },
2117
                { "touchVariables",     SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,           voffsetof(p, context.touch_variables),  0                 },
2118
                {},
2119
        };
2120

UNCOV
2121
        r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
×
UNCOV
2122
        if (r != 0)
×
2123
                return r;
2124

UNCOV
2125
        if (!IN_SET(p.context.operation, INSTALL_NEW, INSTALL_UPDATE))
×
UNCOV
2126
                return sd_varlink_error_invalid_parameter_name(link, "operation");
×
2127

UNCOV
2128
        if (p.root_fd_index != UINT_MAX) {
×
UNCOV
2129
                p.context.root_fd = sd_varlink_peek_dup_fd(link, p.root_fd_index);
×
UNCOV
2130
                if (p.context.root_fd < 0)
×
UNCOV
2131
                        return log_debug_errno(p.context.root_fd, "Failed to acquire root fd from Varlink: %m");
×
2132

UNCOV
2133
                r = fd_verify_safe_flags_full(p.context.root_fd, O_DIRECTORY);
×
UNCOV
2134
                if (r < 0)
×
UNCOV
2135
                        return sd_varlink_error_invalid_parameter_name(link, "rootFileDescriptor");
×
2136

UNCOV
2137
                r = fd_verify_directory(p.context.root_fd);
×
UNCOV
2138
                if (r < 0)
×
UNCOV
2139
                        return log_debug_errno(r, "Specified file descriptor does not refer to a directory: %m");
×
2140

UNCOV
2141
                if (!p.context.root) {
×
UNCOV
2142
                        r = fd_get_path(p.context.root_fd, &p.context.root);
×
UNCOV
2143
                        if (r < 0)
×
UNCOV
2144
                                return log_debug_errno(r, "Failed to get path of file descriptor: %m");
×
2145

UNCOV
2146
                        if (empty_or_root(p.context.root))
×
UNCOV
2147
                                p.context.root = mfree(p.context.root);
×
2148
                }
UNCOV
2149
        } else if (p.context.root) {
×
UNCOV
2150
                p.context.root_fd = open(p.context.root, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
×
UNCOV
2151
                if (p.context.root_fd < 0)
×
UNCOV
2152
                        return log_debug_errno(errno, "Failed to open '%s': %m", p.context.root);
×
2153
        } else
UNCOV
2154
                p.context.root_fd = XAT_FDROOT;
×
2155

UNCOV
2156
        if (p.context.entry_token_type < 0)
×
UNCOV
2157
                p.context.entry_token_type = BOOT_ENTRY_TOKEN_AUTO;
×
2158

UNCOV
2159
        r = find_esp_and_warn_at_full(
×
2160
                        p.context.root_fd,
2161
                        /* path= */ NULL,
2162
                        /* unprivileged_mode= */ false,
2163
                        &p.context.esp_path,
2164
                        &p.context.esp_fd,
2165
                        &p.context.esp_part,
2166
                        &p.context.esp_pstart,
2167
                        &p.context.esp_psize,
2168
                        &p.context.esp_uuid,
2169
                        /* ret_devid= */ NULL);
UNCOV
2170
        if (r == -ENOKEY)
×
UNCOV
2171
                return sd_varlink_error(link, "io.systemd.BootControl.NoESPFound", NULL);
×
UNCOV
2172
        if (r < 0)
×
2173
                return r;
2174

UNCOV
2175
        r = find_xbootldr_and_warn_at(
×
2176
                        p.context.root_fd,
2177
                        /* path= */ NULL,
2178
                        /* unprivileged_mode= */ false,
2179
                        &p.context.xbootldr_path,
2180
                        &p.context.xbootldr_fd);
UNCOV
2181
        if (r == -ENOKEY)
×
UNCOV
2182
                log_debug_errno(r, "Didn't find an XBOOTLDR partition, using ESP as $BOOT.");
×
UNCOV
2183
        else if (r < 0)
×
2184
                return r;
2185

UNCOV
2186
        r = run_install(&p.context);
×
UNCOV
2187
        if (r == -EUNATCH) /* no boot entry token is set */
×
UNCOV
2188
                return sd_varlink_error(link, "io.systemd.BootControl.BootEntryTokenUnavailable", NULL);
×
UNCOV
2189
        if (r < 0)
×
2190
                return r;
2191

UNCOV
2192
        return sd_varlink_reply(link, NULL);
×
2193
}
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