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

systemd / systemd / 25409762285

05 May 2026 08:45PM UTC coverage: 72.658% (-0.02%) from 72.674%
25409762285

push

github

web-flow
Couple of coverity fixes (#41951)

0 of 11 new or added lines in 2 files covered. (0.0%)

2705 existing lines in 63 files now uncovered.

326249 of 449021 relevant lines covered (72.66%)

1212712.0 hits per line

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

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

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

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

9
#include "boot-entry.h"
10
#include "bootctl.h"
11
#include "bootctl-link.h"
12
#include "bootctl-unlink.h"
13
#include "bootspec.h"
14
#include "bootspec-util.h"
15
#include "chase.h"
16
#include "copy.h"
17
#include "dirent-util.h"
18
#include "efi-loader.h"
19
#include "env-file.h"
20
#include "errno-util.h"
21
#include "fd-util.h"
22
#include "find-esp.h"
23
#include "format-util.h"
24
#include "fs-util.h"
25
#include "hashmap.h"
26
#include "id128-util.h"
27
#include "io-util.h"
28
#include "iovec-util.h"
29
#include "json-util.h"
30
#include "kernel-image.h"
31
#include "log.h"
32
#include "parse-argument.h"
33
#include "path-util.h"
34
#include "recurse-dir.h"
35
#include "stat-util.h"
36
#include "stdio-util.h"
37
#include "string-util.h"
38
#include "strv.h"
39
#include "tmpfile-util.h"
40
#include "uki.h"
41
#include "utf8.h"
42

43
/* Keeps track of an "extra" file to associate with the type 1 entries to generate */
44
typedef struct ExtraFile {
45
        /* The source and the temporary file we copy it into */
46
        int source_fd, temp_fd;
47
        char *filename, *temp_filename;
48
        struct iovec data; /* Alternative to 'source_fd': literal data */
49
} ExtraFile;
50

51
#define EXTRA_FILE_NULL                     \
52
        (const ExtraFile) {                 \
53
                .source_fd = -EBADF,        \
54
                .temp_fd = -EBADF,          \
55
        }
56

57
/* Keeps track of a specific UKI profile we need to generate a type entry for */
58
typedef struct Profile {
59
        /* The final and the temporary file for the .conf entry file, while we write it */
60
        char *entry_filename, *entry_temp_filename;
61
        int entry_temp_fd;
62
} Profile;
63

64
typedef struct LinkContext {
65
        char *root;
66
        int root_fd;
67

68
        sd_id128_t machine_id;
69
        BootEntryTokenType entry_token_type;
70
        char *entry_token;
71

72
        char *entry_title;
73
        char *entry_version;
74
        uint64_t entry_commit;
75

76
        BootEntrySource dollar_boot_source;
77
        char *dollar_boot_path;
78
        int dollar_boot_fd;
79
        int entry_token_dir_fd;
80
        int loader_entries_dir_fd;
81

82
        /* The UKI source and temporary target while we write it. Note that for now we exclusively support
83
         * UKIs, but let's keep things somewhat generic to keep options open for the future. */
84
        char *kernel_filename, *kernel_temp_filename;
85
        int kernel_fd, kernel_temp_fd;
86

87
        ExtraFile *extra;
88
        size_t n_extra;
89

90
        Profile *profiles;
91
        size_t n_profiles;
92

93
        unsigned tries_left;
94

95
        uint64_t keep_free;
96

97
        char **linked_ids;
98
} LinkContext;
99

100
#define LINK_CONTEXT_NULL                                               \
101
        (LinkContext) {                                                 \
102
                .root_fd = -EBADF,                                      \
103
                .entry_token_type = _BOOT_ENTRY_TOKEN_TYPE_INVALID,     \
104
                .dollar_boot_fd = -EBADF,                               \
105
                .loader_entries_dir_fd = -EBADF,                        \
106
                .entry_token_dir_fd = -EBADF,                           \
107
                .kernel_fd = -EBADF,                                    \
108
                .kernel_temp_fd = -EBADF,                               \
109
                .tries_left = UINT_MAX,                                 \
110
                .keep_free = UINT64_MAX,                                \
111
        }
112

113
static void extra_file_done(ExtraFile *x) {
3✔
114
        assert(x);
3✔
115

116
        x->source_fd = safe_close(x->source_fd);
3✔
117
        x->temp_fd = safe_close(x->temp_fd);
3✔
118
        x->filename = mfree(x->filename);
3✔
119
        x->temp_filename = mfree(x->temp_filename);
3✔
120
        iovec_done(&x->data);
3✔
121
}
3✔
122

123
static void profile_done(Profile *p) {
10✔
124
        assert(p);
10✔
125

126
        p->entry_filename = mfree(p->entry_filename);
10✔
127
        p->entry_temp_filename = mfree(p->entry_temp_filename);
10✔
128
        p->entry_temp_fd = safe_close(p->entry_temp_fd);
10✔
129
}
10✔
130

131
static void link_context_unlink_temporary(LinkContext *c) {
24✔
132
        assert(c);
24✔
133

134
        if (c->kernel_temp_filename) {
24✔
135
                if (c->entry_token_dir_fd >= 0)
×
136
                        (void) unlinkat(c->entry_token_dir_fd, c->kernel_temp_filename, /* flags= */ 0);
×
137

UNCOV
138
                c->kernel_temp_fd = safe_close(c->kernel_temp_fd);
×
UNCOV
139
                c->kernel_temp_filename = mfree(c->kernel_temp_filename);
×
140
        }
141

142
        FOREACH_ARRAY(x, c->extra, c->n_extra) {
27✔
143
                if (!x->temp_filename)
3✔
144
                        continue;
3✔
145

146
                if (c->entry_token_dir_fd >= 0)
×
147
                        (void) unlinkat(c->entry_token_dir_fd, x->temp_filename, /* flags= */ 0);
×
148

UNCOV
149
                x->temp_fd = safe_close(x->temp_fd);
×
UNCOV
150
                x->temp_filename = mfree(x->temp_filename);
×
151
        }
152

153
        FOREACH_ARRAY(p, c->profiles, c->n_profiles) {
34✔
154
                if (!p->entry_temp_filename)
10✔
155
                        continue;
10✔
156

157
                if (c->loader_entries_dir_fd >= 0)
×
158
                        (void) unlinkat(c->loader_entries_dir_fd, p->entry_temp_filename, /* flags= */ 0);
×
159

UNCOV
160
                p->entry_temp_fd = safe_close(p->entry_temp_fd);
×
UNCOV
161
                p->entry_temp_filename = mfree(p->entry_temp_filename);
×
162
        }
163
}
24✔
164

165
static void link_context_clear_profiles(LinkContext *c) {
24✔
166
        assert(c);
24✔
167

168
        FOREACH_ARRAY(p, c->profiles, c->n_profiles)
34✔
169
                profile_done(p);
10✔
170

171
        c->profiles = mfree(c->profiles);
24✔
172
        c->n_profiles = 0;
24✔
173
}
24✔
174

175
static void link_context_done(LinkContext *c) {
23✔
176
        assert(c);
23✔
177

178
        link_context_unlink_temporary(c);
23✔
179

180
        FOREACH_ARRAY(x, c->extra, c->n_extra)
26✔
181
                extra_file_done(x);
3✔
182

183
        c->extra = mfree(c->extra);
23✔
184
        c->n_extra = 0;
23✔
185

186
        link_context_clear_profiles(c);
23✔
187

188
        c->kernel_filename = mfree(c->kernel_filename);
23✔
189
        c->kernel_fd = safe_close(c->kernel_fd);
23✔
190
        c->kernel_temp_filename = mfree(c->kernel_temp_filename);
23✔
191
        c->kernel_temp_fd = safe_close(c->kernel_temp_fd);
23✔
192

193
        c->root = mfree(c->root);
23✔
194
        c->root_fd = safe_close(c->root_fd);
23✔
195

196
        c->entry_token = mfree(c->entry_token);
23✔
197
        c->entry_title = mfree(c->entry_title);
23✔
198
        c->entry_version = mfree(c->entry_version);
23✔
199

200
        c->dollar_boot_path = mfree(c->dollar_boot_path);
23✔
201
        c->dollar_boot_fd = safe_close(c->dollar_boot_fd);
23✔
202
        c->entry_token_dir_fd = safe_close(c->entry_token_dir_fd);
23✔
203
        c->loader_entries_dir_fd = safe_close(c->loader_entries_dir_fd);
23✔
204

205
        c->linked_ids = strv_free(c->linked_ids);
23✔
206
}
23✔
207

208
static int link_context_from_cmdline(LinkContext *ret, const char *kernel) {
10✔
209
        int r;
10✔
210

211
        assert(ret);
10✔
212
        assert(kernel);
10✔
213

214
        _cleanup_(link_context_done) LinkContext b = LINK_CONTEXT_NULL;
10✔
215
        b.entry_token_type = arg_entry_token_type;
10✔
216
        b.tries_left = arg_tries_left;
10✔
217
        b.entry_commit = arg_entry_commit;
10✔
218
        b.keep_free = arg_keep_free;
10✔
219

220
        if (strdup_to(&b.entry_token, arg_entry_token) < 0 ||
10✔
221
            strdup_to(&b.entry_title, arg_entry_title) < 0 ||
10✔
222
            strdup_to(&b.entry_version, arg_entry_version) < 0)
10✔
223
                return log_oom();
×
224

225
        if (arg_root) {
10✔
UNCOV
226
                b.root_fd = open(arg_root, O_CLOEXEC|O_DIRECTORY|O_PATH);
×
227
                if (b.root_fd < 0)
×
228
                        return log_error_errno(errno, "Failed to open root directory '%s': %m", arg_root);
×
229

UNCOV
230
                if (strdup_to(&b.root, arg_root) < 0)
×
UNCOV
231
                        return log_oom();
×
232
        } else
233
                b.root_fd = XAT_FDROOT;
10✔
234

235
        r = path_extract_filename(kernel, &b.kernel_filename);
10✔
236
        if (r < 0)
10✔
UNCOV
237
                return log_error_errno(r, "Failed to extract filename from kernel path '%s': %m", kernel);
×
238
        if (!efi_loader_entry_resource_filename_valid(b.kernel_filename))
10✔
239
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Kernel '%s' is not suitable for reference in a boot menu entry.", kernel);
×
240
        b.kernel_fd = xopenat_full(AT_FDCWD, kernel, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, XO_REGULAR, /* mode= */ MODE_INVALID);
10✔
241
        if (b.kernel_fd < 0)
10✔
UNCOV
242
                return log_error_errno(b.kernel_fd, "Failed to open kernel path '%s': %m", kernel);
×
243

244
        KernelImageType kit = _KERNEL_IMAGE_TYPE_INVALID;
10✔
245
        r = inspect_kernel(b.kernel_fd, /* filename= */ NULL, &kit);
10✔
246
        if (r == -EBADMSG)
10✔
UNCOV
247
                return log_error_errno(r, "Kernel image '%s' is not valid.", kernel);
×
248
        if (r < 0)
10✔
UNCOV
249
                return log_error_errno(r, "Failed to determine kernel image type of '%s': %m", kernel);
×
250
        if (kit != KERNEL_IMAGE_TYPE_UKI)
10✔
251
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Kernel image '%s' is not a UKI.", kernel);
1✔
252

253
        STRV_FOREACH(x, arg_extras) {
12✔
254
                _cleanup_free_ char *fn = NULL;
3✔
255
                r = path_extract_filename(*x, &fn);
3✔
256
                if (r < 0)
3✔
UNCOV
257
                        return log_error_errno(r, "Failed to extract filename from path '%s': %m", *x);
×
258
                if (r == O_DIRECTORY)
3✔
UNCOV
259
                        return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Extra file path '%s' does not refer to regular file.", *x);
×
260

261
                _cleanup_close_ int fd = -EBADF;
3✔
262
                fd = xopenat_full(AT_FDCWD, *x, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, XO_REGULAR, /* mode= */ MODE_INVALID);
3✔
263
                if (fd < 0)
3✔
264
                        return log_error_errno(fd, "Failed to open '%s': %m", *x);
×
265

266
                if (!GREEDY_REALLOC(b.extra, b.n_extra+1))
3✔
UNCOV
267
                        return log_oom();
×
268

269
                b.extra[b.n_extra++] = (ExtraFile) {
3✔
270
                        .source_fd = TAKE_FD(fd),
3✔
271
                        .filename = TAKE_PTR(fn),
3✔
272
                        .temp_fd = -EBADF,
273
                };
274
        }
275

276
        r = acquire_xbootldr(
9✔
277
                        /* unprivileged_mode= */ false,
278
                        &b.dollar_boot_fd,
279
                        /* ret_uuid= */ NULL,
280
                        /* ret_devid= */ NULL);
281
        if (r < 0)
9✔
282
                return r;
283
        if (r > 0) { /* XBOOTLDR has been found */
9✔
284
                assert(arg_xbootldr_path);
×
285

286
                if (arg_root) {
×
UNCOV
287
                        const char *e = path_startswith(arg_xbootldr_path, arg_root);
×
288
                        if (!e)
×
UNCOV
289
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "XBOOTLDR path '%s' not below specified root '%s', refusing.", arg_xbootldr_path, arg_root);
×
290

291
                        r = strdup_to(&b.dollar_boot_path, e);
×
292
                } else
UNCOV
293
                        r = strdup_to(&b.dollar_boot_path, arg_xbootldr_path);
×
294
                if (r < 0)
×
UNCOV
295
                        return log_oom();
×
296

UNCOV
297
                b.dollar_boot_source = BOOT_ENTRY_XBOOTLDR;
×
298
        } else {
299
                /* No XBOOTLDR has been found, look for ESP */
300

301
                r = acquire_esp(/* unprivileged_mode= */ false,
9✔
302
                                /* graceful= */ false,
303
                                &b.dollar_boot_fd,
304
                                /* ret_part= */ NULL,
305
                                /* ret_pstart= */ NULL,
306
                                /* ret_psize= */ NULL,
307
                                /* ret_uuid= */ NULL,
308
                                /* ret_devid= */ NULL);
309
                if (r < 0)
9✔
310
                        return r;
311

312
                assert(arg_esp_path);
9✔
313

314
                if (arg_root) {
9✔
UNCOV
315
                        const char *e = path_startswith(arg_esp_path, arg_root);
×
316
                        if (!e)
×
UNCOV
317
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ESP path '%s' not below specified root '%s', refusing.", arg_esp_path, arg_root);
×
318

UNCOV
319
                        r = strdup_to(&b.dollar_boot_path, e);
×
320
                } else
321
                        r = strdup_to(&b.dollar_boot_path, arg_esp_path);
9✔
322
                if (r < 0)
9✔
UNCOV
323
                        return log_oom();
×
324

325
                b.dollar_boot_source = BOOT_ENTRY_ESP;
9✔
326
        }
327

328
        *ret = TAKE_GENERIC(b, LinkContext, LINK_CONTEXT_NULL);
9✔
329
        return 0;
9✔
330
}
331

332
static int link_context_load_etc_machine_id(LinkContext *c) {
12✔
333
        int r;
12✔
334

335
        assert(c);
12✔
336

337
        r = id128_get_machine_at(c->root_fd, &c->machine_id);
12✔
338
        if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r)) /* Not set or empty */
12✔
339
                return 0;
340
        if (r < 0)
12✔
UNCOV
341
                return log_error_errno(r, "Failed to get machine-id: %m");
×
342

343
        log_debug("Loaded machine ID %s from '%s/etc/machine-id'.", SD_ID128_TO_STRING(c->machine_id), strempty(c->root));
12✔
344
        return 0;
12✔
345
}
346

347
static int link_context_pick_entry_token(LinkContext *c) {
12✔
348
        int r;
12✔
349

350
        assert(c);
12✔
351

352
        r = link_context_load_etc_machine_id(c);
12✔
353
        if (r < 0)
12✔
354
                return r;
355

356
        const char *e = secure_getenv("KERNEL_INSTALL_CONF_ROOT");
12✔
357
        r = boot_entry_token_ensure_at(
12✔
358
                        e ? XAT_FDROOT : c->root_fd,
359
                        e,
360
                        c->machine_id,
361
                        /* machine_id_is_random= */ false,
362
                        &c->entry_token_type,
363
                        &c->entry_token);
364
        if (r < 0)
12✔
365
                return r;
366

367
        log_debug("Using entry token: %s", c->entry_token);
12✔
368
        return 0;
369
}
370

371
static int begin_copy_file(
13✔
372
                int source_fd,               /* Either the source fd is specified, or the 'data' below, not both */
373
                const struct iovec *data,
374
                const char *filename,
375
                int target_dir_fd,
376
                int *ret_tmpfile_fd,
377
                char **ret_tmpfile_filename) {
378

379
        int r;
13✔
380

381
        assert(filename);
13✔
382
        assert(target_dir_fd >= 0);
13✔
383
        assert(ret_tmpfile_fd);
13✔
384
        assert(ret_tmpfile_filename);
13✔
385

386
        if (faccessat(target_dir_fd, filename, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
13✔
387
                if (errno != ENOENT)
10✔
388
                        return log_error_errno(errno, "Failed to check if '%s' exists already: %m", filename);
13✔
389
        } else {
390
                log_info("'%s' already in place, not copying.", filename);
3✔
391

392
                *ret_tmpfile_fd = -EBADF;
3✔
393
                *ret_tmpfile_filename = NULL;
3✔
394
                return 0;
3✔
395
        }
396

397
        _cleanup_free_ char *t = NULL;
10✔
398
        _cleanup_close_ int write_fd = open_tmpfile_linkable_at(target_dir_fd, filename, O_WRONLY|O_CLOEXEC, &t);
20✔
399
        if (write_fd < 0)
10✔
UNCOV
400
                return log_error_errno(write_fd, "Failed to create '%s': %m", filename);
×
401

402
        CLEANUP_TMPFILE_AT(target_dir_fd, t);
10✔
403

404
        if (source_fd >= 0) {
10✔
405
                r = copy_bytes(source_fd, write_fd, UINT64_MAX, COPY_REFLINK|COPY_SEEK0_SOURCE);
10✔
406
                if (r < 0)
10✔
UNCOV
407
                        return log_error_errno(r, "Failed to copy data into '%s': %m", filename);
×
408

409
                (void) copy_times(source_fd, write_fd, /* flags= */ 0);
10✔
UNCOV
410
        } else if (iovec_is_set(data)) {
×
UNCOV
411
                r = loop_write(write_fd, data->iov_base, data->iov_len);
×
UNCOV
412
                if (r < 0)
×
UNCOV
413
                        return log_error_errno(r, "Failed to write data into '%s': %m", filename);
×
414
        }
415

416
        (void) fchmod(write_fd, 0644);
10✔
417

418
        *ret_tmpfile_fd = TAKE_FD(write_fd);
10✔
419
        *ret_tmpfile_filename = TAKE_PTR(t);
10✔
420

421
        return 1;
10✔
422
}
423

424
static int begin_write_entry_file(
11✔
425
                LinkContext *c,
426
                unsigned profile_nr,
427
                const char *osrelease_text,
428
                const char *profile_text,
429
                Profile *ret) {
430

431
        int r;
11✔
432

433
        assert(c);
11✔
434
        assert(osrelease_text);
11✔
435
        assert(ret);
11✔
436

437
        assert(c->entry_token);
11✔
438
        assert(c->kernel_filename);
11✔
439
        assert(c->loader_entries_dir_fd >= 0);
11✔
440

441
        _cleanup_free_ char *good_name = NULL, *good_sort_key = NULL, *os_version_id = NULL, *image_version = NULL;
11✔
442
        r = bootspec_extract_osrelease(
11✔
443
                        osrelease_text,
444
                        /* These three fields are used by systemd-stub for showing entries + sorting them */
445
                        &good_name,     /* human readable */
446
                        /* ret_good_version= */ NULL,
447
                        &good_sort_key,
448
                        /* These four fields are the raw fields provided in os-release */
449
                        /* ret_os_id= */ NULL,
450
                        &os_version_id,
451
                        /* ret_image_id= */ NULL,
452
                        &image_version);
453
        if (r < 0)
11✔
UNCOV
454
                return log_error_errno(r, "Failed to extract name/version/sort-key from os-release data from unified kernel image, refusing.");
×
455

456
        assert(good_name); /* This one is the only field guaranteed to be defined once the above succeeds */
11✔
457

458
        _cleanup_free_ char *profile_id = NULL, *profile_title = NULL;
11✔
459
        if (profile_text) {
11✔
UNCOV
460
                r = parse_env_data(
×
461
                                profile_text, /* size= */ SIZE_MAX,
462
                                ".profile",
463
                                "ID", &profile_id,
464
                                "TITLE", &profile_title);
UNCOV
465
                if (r < 0)
×
UNCOV
466
                        return log_error_errno(r, "Failed to parse profile data from unified kernel image: %m");
×
467
        }
468

469
        const char *version = c->entry_version ?: image_version ?: os_version_id;
11✔
470

471
        _cleanup_free_ char *filename = NULL;
11✔
472
        r = boot_entry_make_commit_filename(
22✔
473
                        c->entry_token,
11✔
474
                        c->entry_commit,
475
                        version,
476
                        profile_nr,
477
                        c->tries_left,
478
                        &filename);
479
        if (r < 0)
11✔
UNCOV
480
                return log_error_errno(r, "Failed to generate filename for entry file: %m");
×
481

482
        if (faccessat(c->loader_entries_dir_fd, filename, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
11✔
483
                if (errno != ENOENT)
10✔
UNCOV
484
                        return log_error_errno(errno, "Failed to check if '%s' exists: %m", filename);
×
485
        } else
486
                return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Boot menu entry '%s' exists already, refusing.", filename);
1✔
487

488
        log_info("Writing new boot menu entry '%s/loader/entries/%s' for profile %u.", c->dollar_boot_path, filename, profile_nr);
10✔
489

490
        _cleanup_free_ char *t = NULL;
10✔
491
        _cleanup_close_ int write_fd = open_tmpfile_linkable_at(c->loader_entries_dir_fd, filename, O_WRONLY|O_CLOEXEC, &t);
20✔
492
        if (write_fd < 0)
10✔
UNCOV
493
                return log_error_errno(write_fd, "Failed to create '%s': %m", filename);
×
494

UNCOV
495
        CLEANUP_TMPFILE_AT(c->loader_entries_dir_fd, t);
×
496

497
        _cleanup_free_ char *_title = NULL;
10✔
498
        const char *title;
10✔
499
        if (profile_title || profile_id) {
10✔
UNCOV
500
                _title = strjoin(c->entry_title ?: good_name, " (", profile_title ?: profile_id, ")");
×
UNCOV
501
                if (!_title)
×
UNCOV
502
                        return log_oom();
×
503

504
                title = _title;
505
        } else if (profile_nr > 0) {
10✔
506
                _title = asprintf_safe("%s (Profile #%u)", c->entry_title ?: good_name, profile_nr);
×
UNCOV
507
                if (!_title)
×
508
                        return log_oom();
×
509

510
                title = _title;
511
        } else
512
                title = c->entry_title ?: good_name;
10✔
513

514
        /* Do some validation that this will result in a valid type #1 entry before we write this out */
515
        if (string_has_cc(title, /* ok= */ NULL) || !utf8_is_valid(title))
20✔
UNCOV
516
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to generate valid title for new commit: %s", title);
×
517
        if (string_has_cc(c->kernel_filename, /* ok= */ NULL) || !utf8_is_valid(c->kernel_filename))
10✔
518
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UKI filename is not suitable for inclusion in new commit: %s", c->kernel_filename);
×
519

520
        _cleanup_free_ char *text = NULL;
10✔
521
        if (asprintf(&text,
20✔
522
                     "title %s\n"
523
                     "uki /%s/%s\n"
524
                     "version %" PRIu64 "%s%s\n",
525
                     title,
526
                     c->entry_token, c->kernel_filename,
527
                     c->entry_commit, isempty(version) ? "" : ".", strempty(version)) < 0)
10✔
UNCOV
528
                return log_oom();
×
529

530
        if (good_sort_key && strextendf(&text, "sort-key %s\n", good_sort_key) < 0)
10✔
531
                return log_oom();
×
532

533
        if (profile_nr > 0 && strextendf(&text, "profile %u\n", profile_nr) < 0)
10✔
UNCOV
534
                return log_oom();
×
535

536
        if (!sd_id128_is_null(c->machine_id) && strextendf(&text, "machine-id " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(c->machine_id)) < 0)
10✔
537
                return log_oom();
×
538

539
        FOREACH_ARRAY(x, c->extra, c->n_extra) {
13✔
540
                if (string_has_cc(x->filename, /* ok= */ NULL) || !utf8_is_valid(x->filename))
3✔
UNCOV
541
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extra filename is not suitable for inclusion in new commit: %s", x->filename);
×
542

543
                if (strextendf(&text,
3✔
544
                               "extra /%s/%s\n",
545
                               c->entry_token,
546
                               x->filename) < 0)
UNCOV
547
                        return log_oom();
×
548
        }
549

550
        r = loop_write(write_fd, text, /* nbytes= */ SIZE_MAX);
10✔
551
        if (r < 0)
10✔
UNCOV
552
                return log_error_errno(r, "Failed to write entry file: %m");
×
553

554
        *ret = (Profile) {
10✔
555
                .entry_filename = TAKE_PTR(filename),
10✔
556
                .entry_temp_filename = TAKE_PTR(t),
10✔
557
                .entry_temp_fd = TAKE_FD(write_fd),
10✔
558
        };
559

560
        return 0;
10✔
561
}
562

563
static int finalize_file(
23✔
564
                const char *filename,
565
                int target_dir_fd,
566
                int tmpfile_fd,
567
                const char *tmpfile_filename) {
568

569
        int r;
23✔
570

571
        assert(filename);
23✔
572
        assert(target_dir_fd >= 0);
23✔
573

574
        if (tmpfile_fd < 0) /* If the file already existed, we don't move anything into place. */
23✔
575
                return 0;
576

577
        r = link_tmpfile_at(tmpfile_fd, target_dir_fd, tmpfile_filename, filename, LINK_TMPFILE_REPLACE|LINK_TMPFILE_SYNC);
20✔
578
        if (r < 0)
20✔
UNCOV
579
                return log_error_errno(r, "Failed to move from '%s' into place: %m", filename);
×
580

581
        log_info("Installed '%s' into place.", filename);
20✔
582
        return 1;
583
}
584

585
static int link_context_pick_entry_commit(LinkContext *c) {
12✔
586
        int r;
12✔
587

588
        assert(c);
12✔
589
        assert(c->loader_entries_dir_fd >= 0);
12✔
590
        assert(c->entry_token);
12✔
591

592
        /* Already have a commit nr? */
593
        if (c->entry_commit != 0)
12✔
594
                return 0;
12✔
595

596
        _cleanup_close_ int opened_fd = fd_reopen(c->loader_entries_dir_fd, O_DIRECTORY|O_CLOEXEC);
14✔
597
        if (opened_fd < 0)
2✔
UNCOV
598
                return log_error_errno(opened_fd, "Failed to reopen loader entries dir: %m");
×
599

600
        _cleanup_free_ DirectoryEntries *dentries = NULL;
2✔
601
        r = readdir_all(opened_fd, RECURSE_DIR_IGNORE_DOT, &dentries);
2✔
602
        if (r < 0)
2✔
UNCOV
603
                return log_error_errno(r, "Failed to read loader entries directory: %m");
×
604

605
        uint64_t m = 0; /* largest commit number seen */
2✔
606
        FOREACH_ARRAY(i, dentries->entries, dentries->n_entries) {
2✔
607
                const struct dirent *de = *i;
×
608

609
                /* We look for files named <token>-commit_<commit>[.<version>][.p<profile>].conf */
610

611
                if (!dirent_is_file(de))
×
612
                        continue;
×
613

UNCOV
614
                if (!efi_loader_entry_name_valid(de->d_name))
×
615
                        continue;
×
616

UNCOV
617
                _cleanup_free_ char *et = NULL;
×
618
                uint64_t ec;
×
619
                r = boot_entry_parse_commit_filename(de->d_name, &et, &ec);
×
620
                if (r < 0) {
×
UNCOV
621
                        log_debug_errno(r, "Cannot extract entry token/commit number from '%s', ignoring.", de->d_name);
×
UNCOV
622
                        continue;
×
623
                }
624

UNCOV
625
                if (!streq(c->entry_token, et))
×
UNCOV
626
                        continue;
×
627

UNCOV
628
                log_debug("Found existing commit %" PRIu64 ".", ec);
×
UNCOV
629
                if (ec > m)
×
UNCOV
630
                        m = ec;
×
631
        }
632

633
        assert(m < UINT64_MAX);
2✔
634
        uint64_t next = m + 1;
2✔
635

636
        if (!entry_commit_valid(next))
2✔
UNCOV
637
                return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many commits already in place, refusing.");
×
638

639
        log_debug("Picking commit %" PRIu64 " for new commit.", next);
2✔
640
        c->entry_commit = next;
2✔
641
        return 0;
2✔
642
}
643

644
static int clean_temporary_files(int fd) {
24✔
645
        int r;
24✔
646

647
        assert(fd >= 0);
24✔
648

649
        /* Before we create any new files let's clear any possible left-overs from a previous run. We look
650
         * specifically for all temporary files whose name starts with .# because that's what we create, via
651
         * open_tmpfile_linkable_at().
652
         *
653
         * Ideally, this would not be necessary because O_TMPFILE would ensure that files are not
654
         * materialized before they are fully written. However, vfat currently does not support O_TMPFILE,
655
         * hence we need to clean things up manually. */
656

657
        _cleanup_close_ int dfd = fd_reopen(fd, O_CLOEXEC|O_DIRECTORY);
48✔
658
        if (dfd < 0)
24✔
UNCOV
659
                return log_error_errno(dfd, "Failed to open directory: %m");
×
660

661
        _cleanup_free_ DirectoryEntries *de = NULL;
24✔
662
        r = readdir_all(dfd, RECURSE_DIR_ENSURE_TYPE, &de);
24✔
663
        if (r < 0)
24✔
UNCOV
664
                return log_error_errno(r, "Failed to enumerate contents of directory: %m");
×
665

666
        FOREACH_ARRAY(i, de->entries, de->n_entries) {
33✔
667
                struct dirent *e = *i;
9✔
668

669
                if (e->d_type != DT_REG)
9✔
UNCOV
670
                        continue;
×
671

672
                if (!startswith_no_case(e->d_name, ".#"))
9✔
673
                        continue;
9✔
674

UNCOV
675
                if (unlinkat(dfd, e->d_name, /* flags= */ 0) < 0 && errno != ENOENT)
×
676
                        log_warning_errno(errno, "Failed to remove temporary file '%s', ignoring: %m", e->d_name);
9✔
677
        }
678

679
        return 0;
680
}
681

682
static int link_context_unlink_oldest(LinkContext *c) {
1✔
683
        int r;
1✔
684

685
        assert(c);
1✔
686

687
        /* We only load the entries from the partition we want to make space on (!) */
688
        _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL;
1✔
689
        r = boot_config_load_and_select(
1✔
690
                        &config,
691
                        c->root,
1✔
692
                        c->dollar_boot_source == BOOT_ENTRY_ESP ? c->dollar_boot_path : NULL,
693
                        /* esp_devid= */ 0,
694
                        c->dollar_boot_source == BOOT_ENTRY_XBOOTLDR ? c->dollar_boot_path : NULL,
1✔
695
                        /* xbootldr_devid= */ 0);
696
        if (r < 0)
1✔
697
                return r;
698

699
        _cleanup_(strv_freep) char **ids = NULL;
1✔
700
        r = boot_config_find_oldest_commit(
2✔
701
                        &config,
702
                        c->entry_token,
1✔
703
                        &ids);
704
        if (r == -ENXIO)
1✔
705
                return log_error_errno(r, "No suitable boot menu entry to delete found.");
1✔
706
        if (r == -EBUSY)
×
707
                return log_error_errno(r, "Refusing to remove currently booted boot menu entry.");
×
708
        if (r < 0)
×
709
                return log_error_errno(r, "Failed to find suitable oldest boot menu entry: %m");
×
710

UNCOV
711
        _cleanup_(hashmap_freep) Hashmap *known_files = NULL;
×
712
        r = boot_config_count_known_files(&config, c->dollar_boot_source, &known_files);
×
UNCOV
713
        if (r < 0)
×
714
                return r;
715

716
        int ret = 0;
×
UNCOV
717
        STRV_FOREACH(id, ids) {
×
UNCOV
718
                const BootEntry *entry = boot_config_find_entry(&config, *id);
×
UNCOV
719
                if (!entry)
×
UNCOV
720
                        continue;
×
721

UNCOV
722
                RET_GATHER(ret, boot_entry_unlink(entry, c->dollar_boot_path, c->dollar_boot_fd, known_files, /* dry_run= */ false));
×
723
        }
724

UNCOV
725
        if (ret < 0)
×
UNCOV
726
                return ret;
×
727

728
        return 1;
729
}
730

731
static int verify_keep_free(LinkContext *c) {
22✔
732
        int r;
22✔
733

734
        assert(c);
22✔
735

736
        if (c->keep_free == 0)
22✔
737
                return 0;
22✔
738

739
        uint64_t f;
22✔
740
        r = vfs_free_bytes(ASSERT_FD(c->dollar_boot_fd), &f);
22✔
741
        if (r < 0)
22✔
UNCOV
742
                return log_error_errno(r, "Failed to statvfs() the $BOOT partition: %m");
×
743

744
        if (f < c->keep_free)
22✔
745
                return log_error_errno(
1✔
746
                                SYNTHETIC_ERRNO(EDQUOT),
747
                                "Not installing boot menu entry, free space after installation of %s would be below configured keep free size %s.",
748
                                FORMAT_BYTES(f), FORMAT_BYTES(c->keep_free));
749

750
        return 0;
751
}
752

753
static int run_link_now(LinkContext *c) {
12✔
754
        int r;
12✔
755

756
        assert(c);
12✔
757
        assert(c->dollar_boot_fd >= 0);
12✔
758

759
        _cleanup_free_ char *j = path_join(empty_to_root(c->root), c->dollar_boot_path);
24✔
760
        if (!j)
12✔
761
                return log_oom();
×
762

763
        if (c->loader_entries_dir_fd < 0) {
12✔
764
                r = chaseat(/* root_fd= */ c->dollar_boot_fd,
12✔
765
                            /* dir_fd= */ c->dollar_boot_fd,
766
                            "loader/entries",
767
                            CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY,
768
                            /* ret_path= */ NULL,
769
                            &c->loader_entries_dir_fd);
770
                if (r < 0)
12✔
UNCOV
771
                        return log_error_errno(r, "Failed to pin '/loader/entries' directory below '%s': %m", j);
×
772
        }
773

774
        /* Remove any left-overs from an earlier run before we write new stuff */
775
        (void) clean_temporary_files(c->loader_entries_dir_fd);
12✔
776

777
        r = link_context_pick_entry_commit(c);
12✔
778
        if (r < 0)
12✔
779
                return r;
780

781
        log_info("Will create commit %" PRIu64 ".", c->entry_commit);
12✔
782

783
        if (c->entry_token_dir_fd < 0) {
12✔
784
                r = chaseat(/* root_fd= */ c->dollar_boot_fd,
24✔
785
                            /* dir_fd= */ c->dollar_boot_fd,
786
                            c->entry_token,
12✔
787
                            CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY,
788
                            /* ret_path= */ NULL,
789
                            &c->entry_token_dir_fd);
790
                if (r < 0)
12✔
UNCOV
791
                        return log_error_errno(r, "Failed to pin '/%s' directory below '%s': %m", c->entry_token, j);
×
792
        }
793

794
        /* As above */
795
        (void) clean_temporary_files(c->entry_token_dir_fd);
12✔
796

797
        /* Synchronize everything to disk before we verify the disk space, to ensure the counters are
798
         * accurate (some file systems delay accurate counters) */
799
        (void) syncfs(c->dollar_boot_fd);
12✔
800

801
        /* Before we start copying things, let's see if there's even a remote chance to get this copied
802
         * in. Note that we do not try to be overly smart here, i.e. we do not try to calculate how much
803
         * extra space we'll need here. Doing that is not trivial since after all the same resources can be
804
         * referenced by multiple entries, which makes copying them multiple times unnecessary. */
805
        r = verify_keep_free(c);
12✔
806
        if (r < 0)
12✔
807
                return r;
808

809
        for (unsigned p = 0; p < UNIFIED_PROFILES_MAX; p++) {
21✔
810
                _cleanup_free_ char *osrelease = NULL, *profile = NULL;
21✔
811
                r = pe_find_uki_sections(c->kernel_fd, j, p, &osrelease, &profile, /* ret_cmdline= */ NULL);
21✔
812
                if (r < 0)
21✔
813
                        return r;
814
                if (r == 0) /* this profile does not exist, we are done */
21✔
815
                        break;
816

817
                if (!GREEDY_REALLOC(c->profiles, c->n_profiles+1))
11✔
UNCOV
818
                        return log_oom();
×
819

820
                r = begin_write_entry_file(
22✔
821
                                c,
822
                                p,
823
                                osrelease,
824
                                profile,
825
                                c->profiles + c->n_profiles);
11✔
826
                if (r < 0)
11✔
827
                        return r;
828

829
                c->n_profiles++;
10✔
830
        }
831

832
        if (c->n_profiles == 0)
10✔
UNCOV
833
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "UKI with no valid profile, refusing.");
×
834

835
        r = begin_copy_file(
20✔
836
                        c->kernel_fd,
837
                        /* data= */ NULL,
838
                        c->kernel_filename,
10✔
839
                        c->entry_token_dir_fd,
840
                        &c->kernel_temp_fd,
841
                        &c->kernel_temp_filename);
842
        if (r < 0)
10✔
843
                return r;
844

845
        FOREACH_ARRAY(x, c->extra, c->n_extra) {
13✔
846
                r = begin_copy_file(
6✔
847
                                x->source_fd,
848
                                &x->data,
3✔
849
                                x->filename,
3✔
850
                                c->entry_token_dir_fd,
851
                                &x->temp_fd,
852
                                &x->temp_filename);
853
                if (r < 0)
3✔
854
                        return r;
855
        }
856

857
        /* We copied all files into place, but they are not materialized yet. Let's ensure the data hits the
858
         * disk before we proceed */
859
        (void) syncfs(c->dollar_boot_fd);
10✔
860

861
        /* Before we materialize things, let's ensure the space to keep free is not taken */
862
        r = verify_keep_free(c);
10✔
863
        if (r < 0)
10✔
864
                return r;
865

866
        /* We successfully managed to put all resources we need into the $BOOT partition. Now, let's
867
         * "materialize" them by linking them into the file system. Before this point we'd get rid of every
868
         * file we created on error again. But from now on we switch modes: what we manage to move into place
869
         * we leave in place even on error. These are not lost resources after all, the GC logic implemented
870
         * by "bootctl cleanup" will take care of removing things again if necessary. */
871

872
        r = finalize_file(
20✔
873
                        c->kernel_filename,
10✔
874
                        c->entry_token_dir_fd,
875
                        c->kernel_temp_fd,
876
                        c->kernel_temp_filename);
10✔
877
        if (r < 0)
10✔
878
                return r;
879

880
        c->kernel_temp_fd = safe_close(c->kernel_temp_fd);
10✔
881
        c->kernel_temp_filename = mfree(c->kernel_temp_filename);
10✔
882

883
        FOREACH_ARRAY(x, c->extra, c->n_extra) {
13✔
884
                r = finalize_file(
6✔
885
                                x->filename,
3✔
886
                                c->entry_token_dir_fd,
887
                                x->temp_fd,
888
                                x->temp_filename);
3✔
889
                if (r < 0)
3✔
890
                        return r;
891

892
                x->temp_fd = safe_close(x->temp_fd);
3✔
893
                x->temp_filename = mfree(x->temp_filename);
3✔
894
        }
895

896
        /* Finally, after all our resources are in place, also materialize the menu entry files themselves */
897
        FOREACH_ARRAY(profile, c->profiles, c->n_profiles) {
20✔
898
                r = finalize_file(
20✔
899
                                profile->entry_filename,
10✔
900
                                c->loader_entries_dir_fd,
901
                                profile->entry_temp_fd,
902
                                profile->entry_temp_filename);
10✔
903
                if (r < 0)
10✔
904
                        return r;
×
905

906
                profile->entry_temp_fd = safe_close(profile->entry_temp_fd);
10✔
907
                profile->entry_temp_filename = mfree(profile->entry_temp_filename);
10✔
908

909
                _cleanup_free_ char *stripped = NULL;
10✔
910
                r = boot_filename_extract_tries(
20✔
911
                                profile->entry_filename,
10✔
912
                                &stripped,
913
                                /* ret_tries_left= */ NULL,
914
                                /* ret_tries_done= */ NULL);
915
                if (r < 0)
10✔
UNCOV
916
                        return log_warning_errno(r, "Failed to extract tries counters from id '%s'", profile->entry_filename);
×
917

918
                if (strv_consume(&c->linked_ids, TAKE_PTR(stripped)) < 0)
10✔
UNCOV
919
                        return log_oom();
×
920
        }
921

922
        (void) syncfs(c->dollar_boot_fd);
10✔
923
        return 0;
10✔
924
}
925

926
static int run_link(LinkContext *c) {
12✔
927
        int r;
12✔
928

929
        assert(c);
12✔
930
        assert(c->dollar_boot_path);
12✔
931
        assert(c->dollar_boot_fd >= 0);
12✔
932

933
        if (c->keep_free == UINT64_MAX)
12✔
934
                c->keep_free = KEEP_FREE_BYTES_DEFAULT;
3✔
935

936
        r = link_context_pick_entry_token(c);
12✔
937
        if (r < 0)
12✔
938
                return r;
12✔
939

940
        unsigned n_removals = 0;
941
        for (;;) {
12✔
942
                r = run_link_now(c);
12✔
943
                if (r < 0) {
12✔
944
                        if (!ERRNO_IS_NEG_DISK_SPACE(r))
13✔
945
                                return r;
946
                } else
947
                        break;
948

949
                log_notice("Attempt to link entry failed due to exhausted disk space, trying to remove oldest boot menu entry.");
1✔
950

951
                link_context_unlink_temporary(c);
1✔
952
                link_context_clear_profiles(c);
1✔
953

954
                if (link_context_unlink_oldest(c) <= 0) {
1✔
955
                        log_warning("Attempted to make space on $BOOT, but this failed, attempt to link entry failed.");
1✔
956
                        return r; /* propagate original error */
957
                }
958

959
                /* Close entry token dir here, quite possible the unlinking above might have removed it too, in case it was empty */
UNCOV
960
                c->entry_token_dir_fd = safe_close(c->entry_token_dir_fd);
×
961

UNCOV
962
                log_info("Removing oldest boot menu entry succeeded, will retry to create boot loader menu entry.");
×
UNCOV
963
                n_removals++;
×
964
        }
965

966
        _cleanup_free_ char *j = strv_join(c->linked_ids, "', '");
20✔
967
        if (!j)
10✔
UNCOV
968
                return log_oom();
×
969

970
        if (n_removals > 0)
10✔
UNCOV
971
                log_info("Successfully installed boot loader entries '%s', after removing %u old entries.", j, n_removals);
×
972
        else
973
                log_info("Successfully installed boot loader entries '%s'.", j);
10✔
974

975
        return 0;
976
}
977

978
int verb_link(int argc, char *argv[], uintptr_t data, void *userdata) {
10✔
979
        int r;
10✔
980

981
        assert(argc == 2);
10✔
982

983
        _cleanup_free_ char *x = NULL;
10✔
984
        r = parse_path_argument(argv[1], /* suppress_root= */ false, &x);
10✔
985
        if (r < 0)
10✔
986
                return r;
987

988
        _cleanup_(link_context_done) LinkContext c = LINK_CONTEXT_NULL;
10✔
989
        r = link_context_from_cmdline(&c, x);
10✔
990
        if (r < 0)
10✔
991
                return r;
992

993
        return run_link(&c);
9✔
994
}
995

UNCOV
996
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_boot_entry_token_type, BootEntryTokenType, boot_entry_token_type_from_string);
×
997

998
typedef struct LinkParameters {
999
        LinkContext context;
1000
        unsigned root_fd_index;
1001
        unsigned kernel_fd_index;
1002
        sd_varlink *link;
1003
} LinkParameters;
1004

1005
static void link_parameters_done(LinkParameters *p) {
3✔
1006
        assert(p);
3✔
1007

1008
        link_context_done(&p->context);
3✔
1009
}
3✔
1010

1011
typedef struct ExtraParameters {
1012
        ExtraFile extra_file;
1013
        unsigned fd_index;
1014
} ExtraParameters;
1015

UNCOV
1016
static void extra_parameters_done(ExtraParameters *p) {
×
UNCOV
1017
        assert(p);
×
1018

UNCOV
1019
        extra_file_done(&p->extra_file);
×
UNCOV
1020
}
×
1021

1022
static int json_dispatch_loader_entry_resource_filename(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
3✔
1023
        char **n = ASSERT_PTR(userdata);
3✔
1024
        const char *filename;
3✔
1025
        int r;
3✔
1026

1027
        assert(variant);
3✔
1028

1029
        r = json_dispatch_const_filename(name, variant, flags, &filename);
3✔
1030
        if (r < 0)
3✔
1031
                return r;
3✔
1032

1033
        if (filename && !efi_loader_entry_resource_filename_valid(filename))
3✔
UNCOV
1034
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid boot entry resource filename.", strna(name));
×
1035

1036
        if (free_and_strdup(n, filename) < 0)
3✔
UNCOV
1037
                return json_log_oom(variant, flags);
×
1038

1039
        return 0;
1040
}
1041

UNCOV
1042
static int dispatch_extras(const char *name, sd_json_variant *v, sd_json_dispatch_flags_t flags, void *userdata) {
×
UNCOV
1043
        LinkParameters *c = ASSERT_PTR(userdata);
×
UNCOV
1044
        int r;
×
1045

UNCOV
1046
        if (!sd_json_variant_is_array(v))
×
UNCOV
1047
                return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
×
1048

UNCOV
1049
        sd_json_variant *i;
×
UNCOV
1050
        JSON_VARIANT_ARRAY_FOREACH(i, v) {
×
UNCOV
1051
                _cleanup_(extra_parameters_done) ExtraParameters xp = {
×
1052
                        .extra_file = EXTRA_FILE_NULL,
1053
                        .fd_index = UINT_MAX,
1054
                };
1055

1056
                static const sd_json_dispatch_field dispatch_table[] = {
×
1057
                        { "filename",       SD_JSON_VARIANT_STRING,        json_dispatch_loader_entry_resource_filename, offsetof(ExtraParameters, extra_file.filename),  SD_JSON_MANDATORY },
1058
                        { "fileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint,                        offsetof(ExtraParameters, fd_index),             0                 },
1059
                        { "data",           SD_JSON_VARIANT_STRING,        json_dispatch_unbase64_iovec,                 offsetof(ExtraParameters, extra_file.data),      0                 },
1060
                        {},
1061
                };
1062

UNCOV
1063
                r = sd_json_dispatch(i, dispatch_table, /* flags= */ 0, &xp);
×
1064
                if (r < 0)
×
1065
                        return r;
1066

1067
                if (iovec_is_set(&xp.extra_file.data) == (xp.fd_index != UINT_MAX))
×
UNCOV
1068
                        return sd_varlink_error_invalid_parameter_name(c->link, name);
×
UNCOV
1069
                if (xp.fd_index != UINT_MAX) {
×
UNCOV
1070
                        xp.extra_file.source_fd = sd_varlink_peek_dup_fd(c->link, xp.fd_index);
×
UNCOV
1071
                        if (xp.extra_file.source_fd < 0)
×
UNCOV
1072
                                return log_debug_errno(xp.extra_file.source_fd, "Failed to acquire extra fd from Varlink: %m");
×
1073

UNCOV
1074
                        r = fd_verify_safe_flags(xp.extra_file.source_fd);
×
UNCOV
1075
                        if (r < 0)
×
UNCOV
1076
                                return sd_varlink_error_invalid_parameter_name(c->link, name);
×
1077

UNCOV
1078
                        r = fd_verify_regular(xp.extra_file.source_fd);
×
UNCOV
1079
                        if (r < 0)
×
UNCOV
1080
                                return log_debug_errno(r, "Failed to validate that the extra file is a regular file descriptor: %m");
×
1081
                }
1082

UNCOV
1083
                if (!GREEDY_REALLOC(c->context.extra, c->context.n_extra+1))
×
UNCOV
1084
                        return log_oom();
×
1085

UNCOV
1086
                c->context.extra[c->context.n_extra++] = TAKE_GENERIC(xp.extra_file, ExtraFile, EXTRA_FILE_NULL);
×
1087
        }
1088

UNCOV
1089
        return 0;
×
1090
}
1091

1092
int vl_method_link(
3✔
1093
                sd_varlink *link,
1094
                sd_json_variant *parameters,
1095
                sd_varlink_method_flags_t flags,
1096
                void *userdata) {
1097

1098
        int r;
3✔
1099

1100
        assert(link);
3✔
1101

1102
        _cleanup_(link_parameters_done) LinkParameters p = {
3✔
1103
                .context = LINK_CONTEXT_NULL,
1104
                .root_fd_index = UINT_MAX,
1105
                .kernel_fd_index = UINT_MAX,
1106
                .link = link,
1107
        };
1108

1109
        static const sd_json_dispatch_field dispatch_table[] = {
3✔
1110
                { "rootFileDescriptor",   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint,                        voffsetof(p, root_fd_index),            0                 },
1111
                { "rootDirectory",        SD_JSON_VARIANT_STRING,        json_dispatch_path,                           voffsetof(p, context.root),             0                 },
1112
                { "bootEntryTokenType",   SD_JSON_VARIANT_STRING,        json_dispatch_boot_entry_token_type,          voffsetof(p, context.entry_token_type), 0                 },
1113
                { "entryTitle",           SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,                      voffsetof(p, context.entry_title),      0                 },
1114
                { "entryVersion",         SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,                      voffsetof(p, context.entry_version),    0                 },
1115
                { "entryCommit",          SD_JSON_VARIANT_INTEGER,       sd_json_dispatch_uint64,                      voffsetof(p, context.entry_commit),     0                 },
1116
                { "kernelFilename",       SD_JSON_VARIANT_STRING,        json_dispatch_loader_entry_resource_filename, voffsetof(p, context.kernel_filename),  SD_JSON_MANDATORY },
1117
                { "kernelFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint,                        voffsetof(p, kernel_fd_index),          SD_JSON_MANDATORY },
1118
                { "extraFiles",           SD_JSON_VARIANT_ARRAY,         dispatch_extras,                              0,                                      0                 },
1119
                { "triesLeft",            _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint,                        voffsetof(p, context.tries_left),       0                 },
1120
                { "keepFree",             _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,                      voffsetof(p, context.keep_free),        0                 },
1121
                {},
1122
        };
1123

1124
        r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
3✔
1125
        if (r != 0)
3✔
1126
                return r;
1127

1128
        if (p.root_fd_index != UINT_MAX) {
3✔
1129
                p.context.root_fd = sd_varlink_peek_dup_fd(link, p.root_fd_index);
×
1130
                if (p.context.root_fd < 0)
×
UNCOV
1131
                        return log_debug_errno(p.context.root_fd, "Failed to acquire root fd from Varlink: %m");
×
1132

1133
                r = fd_verify_safe_flags_full(p.context.root_fd, O_DIRECTORY);
×
1134
                if (r < 0)
×
1135
                        return sd_varlink_error_invalid_parameter_name(link, "rootFileDescriptor");
×
1136

UNCOV
1137
                r = fd_verify_directory(p.context.root_fd);
×
UNCOV
1138
                if (r < 0)
×
UNCOV
1139
                        return log_debug_errno(r, "Specified file descriptor does not refer to a directory: %m");
×
1140

UNCOV
1141
                if (!p.context.root) {
×
UNCOV
1142
                        r = fd_get_path(p.context.root_fd, &p.context.root);
×
1143
                        if (r < 0)
×
UNCOV
1144
                                return log_debug_errno(r, "Failed to get path of file descriptor: %m");
×
1145

1146
                        if (empty_or_root(p.context.root))
×
UNCOV
1147
                                p.context.root = mfree(p.context.root);
×
1148
                }
1149
        } else if (p.context.root) {
3✔
UNCOV
1150
                p.context.root_fd = open(p.context.root, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
×
UNCOV
1151
                if (p.context.root_fd < 0)
×
UNCOV
1152
                        return log_debug_errno(errno, "Failed to open '%s': %m", p.context.root);
×
1153
        } else
1154
                p.context.root_fd = XAT_FDROOT;
3✔
1155

1156
        if (p.context.entry_token_type < 0)
3✔
1157
                p.context.entry_token_type = BOOT_ENTRY_TOKEN_AUTO;
3✔
1158

1159
        if (p.context.entry_title && !efi_loader_entry_title_valid(p.context.entry_title))
3✔
1160
                return sd_varlink_error_invalid_parameter_name(link, "entryTitle");
×
1161

1162
        if (p.context.entry_version && !version_is_valid_versionspec(p.context.entry_version))
3✔
UNCOV
1163
                return sd_varlink_error_invalid_parameter_name(link, "entryVersion");
×
1164

1165
        if (p.context.entry_commit != 0 && !entry_commit_valid(p.context.entry_commit))
3✔
1166
                return sd_varlink_error_invalid_parameter_name(link, "entryCommit");
×
1167

1168
        p.context.kernel_fd = sd_varlink_peek_dup_fd(link, p.kernel_fd_index);
3✔
1169
        if (p.context.kernel_fd < 0)
3✔
1170
                return log_debug_errno(p.context.kernel_fd, "Failed to acquire kernel fd from Varlink: %m");
×
1171

1172
        r = fd_verify_safe_flags(p.context.kernel_fd);
3✔
1173
        if (r < 0)
3✔
UNCOV
1174
                return sd_varlink_error_invalid_parameter_name(link, "kernelFileDescriptor");
×
1175
        r = fd_verify_regular(p.context.kernel_fd);
3✔
1176
        if (r < 0)
3✔
UNCOV
1177
                return log_debug_errno(r, "Failed to validate that kernel image file is a regular file descriptor: %m");
×
1178

1179
        /* Refuse non-UKIs for now. */
1180
        KernelImageType kit = _KERNEL_IMAGE_TYPE_INVALID;
3✔
1181
        r = inspect_kernel(p.context.kernel_fd, /* filename= */ NULL, &kit);
3✔
1182
        if (r == -EBADMSG)
3✔
UNCOV
1183
                return sd_varlink_error(link, "io.systemd.BootControl.InvalidKernelImage", NULL);
×
1184
        if (r < 0)
3✔
1185
                return r;
1186
        if (kit != KERNEL_IMAGE_TYPE_UKI)
3✔
UNCOV
1187
                return sd_varlink_error(link, "io.systemd.BootControl.InvalidKernelImage", NULL);
×
1188

1189
        r = find_xbootldr_and_warn_at(
3✔
1190
                        p.context.root_fd,
1191
                        /* path= */ NULL,
1192
                        /* unprivileged_mode= */ false,
1193
                        &p.context.dollar_boot_path,
1194
                        &p.context.dollar_boot_fd);
1195
        if (r < 0) {
3✔
1196
                if (r != -ENOKEY)
3✔
1197
                        return r;
1198

1199
                /* No XBOOTLDR found, let's look for ESP then. */
1200

1201
                r = find_esp_and_warn_at(
3✔
1202
                                p.context.root_fd,
1203
                                /* path= */ NULL,
1204
                                /* unprivileged_mode= */ false,
1205
                                &p.context.dollar_boot_path,
1206
                                &p.context.dollar_boot_fd);
1207
                if (r == -ENOKEY)
3✔
UNCOV
1208
                        return sd_varlink_error(link, "io.systemd.BootControl.NoDollarBootFound", NULL);
×
1209
                if (r < 0)
3✔
1210
                        return r;
1211

1212
                p.context.dollar_boot_source = BOOT_ENTRY_ESP;
3✔
1213
        } else
UNCOV
1214
                p.context.dollar_boot_source = BOOT_ENTRY_XBOOTLDR;
×
1215

1216
        r = run_link(&p.context);
3✔
1217
        if (r == -EUNATCH) /* no boot entry token is set */
3✔
UNCOV
1218
                return sd_varlink_error(link, "io.systemd.BootControl.BootEntryTokenUnavailable", NULL);
×
1219
        if (r < 0)
3✔
1220
                return r;
1221

1222
        return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRV("ids", p.context.linked_ids));
3✔
1223
}
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