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

systemd / systemd / 29790165204

20 Jul 2026 10:30PM UTC coverage: 72.837% (-0.05%) from 72.889%
29790165204

push

github

yuwata
hwdb: classify PlayStation controller audio as controller form-factor

Add Sony PlayStation controller entries to 70-sound-card.hwdb so that their
ALSA sound devices are tagged with SOUND_FORM_FACTOR=controller:
 - DualSense (054c:0ce6)
 - DualSense Edge (054c:0df2)
 - DualShock 4 CUH-ZCT1x (054c:05c4)
 - DualShock 4 CUH-ZCT2x (054c:09cc)

These controllers expose USB audio but are neither headsets nor speakers.
Pinning them in the hwdb ensures they are identified correctly before any
fallback matching occurs.

345467 of 474302 relevant lines covered (72.84%)

1388252.76 hits per line

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

65.14
/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 "set.h"
36
#include "stat-util.h"
37
#include "stdio-util.h"
38
#include "string-util.h"
39
#include "strv.h"
40
#include "tmpfile-util.h"
41
#include "uki.h"
42
#include "utf8.h"
43
#include "varlink-util.h"
44
#include "vpick.h"
45

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

54
#define EXTRA_FILE_NULL                     \
55
        (const ExtraFile) {                 \
56
                .source_fd = -EBADF,        \
57
                .temp_fd = -EBADF,          \
58
        }
59

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

67
typedef struct LinkContext {
68
        char *root;
69
        int root_fd;
70

71
        sd_id128_t machine_id;
72
        BootEntryTokenType entry_token_type;
73
        char *entry_token;
74

75
        char *entry_title;
76
        char *entry_version;
77
        uint64_t entry_commit;
78

79
        BootEntrySource dollar_boot_source;
80
        char *dollar_boot_path;
81
        int dollar_boot_fd;
82
        int entry_token_dir_fd;
83
        int loader_entries_dir_fd;
84

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

90
        ExtraFile *extra;
91
        size_t n_extra;
92

93
        Profile *profiles;
94
        size_t n_profiles;
95

96
        unsigned tries_left;
97

98
        uint64_t keep_free;
99

100
        char **linked_ids;
101
} LinkContext;
102

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

116
static void extra_file_done(ExtraFile *x) {
8✔
117
        assert(x);
8✔
118

119
        x->source_fd = safe_close(x->source_fd);
8✔
120
        x->temp_fd = safe_close(x->temp_fd);
8✔
121
        x->filename = mfree(x->filename);
8✔
122
        x->temp_filename = mfree(x->temp_filename);
8✔
123
        iovec_done(&x->data);
8✔
124
}
8✔
125

126
static void profile_done(Profile *p) {
10✔
127
        assert(p);
10✔
128

129
        p->entry_filename = mfree(p->entry_filename);
10✔
130
        p->entry_temp_filename = mfree(p->entry_temp_filename);
10✔
131
        p->entry_temp_fd = safe_close(p->entry_temp_fd);
10✔
132
}
10✔
133

134
static void link_context_unlink_temporary(LinkContext *c) {
26✔
135
        assert(c);
26✔
136

137
        if (c->kernel_temp_filename) {
26✔
138
                if (c->entry_token_dir_fd >= 0)
×
139
                        (void) unlinkat(c->entry_token_dir_fd, c->kernel_temp_filename, /* flags= */ 0);
×
140

141
                c->kernel_temp_fd = safe_close(c->kernel_temp_fd);
×
142
                c->kernel_temp_filename = mfree(c->kernel_temp_filename);
×
143
        }
144

145
        FOREACH_ARRAY(x, c->extra, c->n_extra) {
34✔
146
                if (!x->temp_filename)
8✔
147
                        continue;
8✔
148

149
                if (c->entry_token_dir_fd >= 0)
×
150
                        (void) unlinkat(c->entry_token_dir_fd, x->temp_filename, /* flags= */ 0);
×
151

152
                x->temp_fd = safe_close(x->temp_fd);
×
153
                x->temp_filename = mfree(x->temp_filename);
×
154
        }
155

156
        FOREACH_ARRAY(p, c->profiles, c->n_profiles) {
36✔
157
                if (!p->entry_temp_filename)
10✔
158
                        continue;
10✔
159

160
                if (c->loader_entries_dir_fd >= 0)
×
161
                        (void) unlinkat(c->loader_entries_dir_fd, p->entry_temp_filename, /* flags= */ 0);
×
162

163
                p->entry_temp_fd = safe_close(p->entry_temp_fd);
×
164
                p->entry_temp_filename = mfree(p->entry_temp_filename);
×
165
        }
166
}
26✔
167

168
static void link_context_clear_profiles(LinkContext *c) {
26✔
169
        assert(c);
26✔
170

171
        FOREACH_ARRAY(p, c->profiles, c->n_profiles)
36✔
172
                profile_done(p);
10✔
173

174
        c->profiles = mfree(c->profiles);
26✔
175
        c->n_profiles = 0;
26✔
176
}
26✔
177

178
static void link_context_done(LinkContext *c) {
25✔
179
        assert(c);
25✔
180

181
        link_context_unlink_temporary(c);
25✔
182

183
        FOREACH_ARRAY(x, c->extra, c->n_extra)
33✔
184
                extra_file_done(x);
8✔
185

186
        c->extra = mfree(c->extra);
25✔
187
        c->n_extra = 0;
25✔
188

189
        link_context_clear_profiles(c);
25✔
190

191
        c->kernel_filename = mfree(c->kernel_filename);
25✔
192
        c->kernel_fd = safe_close(c->kernel_fd);
25✔
193
        c->kernel_temp_filename = mfree(c->kernel_temp_filename);
25✔
194
        c->kernel_temp_fd = safe_close(c->kernel_temp_fd);
25✔
195

196
        c->root = mfree(c->root);
25✔
197
        c->root_fd = safe_close(c->root_fd);
25✔
198

199
        c->entry_token = mfree(c->entry_token);
25✔
200
        c->entry_title = mfree(c->entry_title);
25✔
201
        c->entry_version = mfree(c->entry_version);
25✔
202

203
        c->dollar_boot_path = mfree(c->dollar_boot_path);
25✔
204
        c->dollar_boot_fd = safe_close(c->dollar_boot_fd);
25✔
205
        c->entry_token_dir_fd = safe_close(c->entry_token_dir_fd);
25✔
206
        c->loader_entries_dir_fd = safe_close(c->loader_entries_dir_fd);
25✔
207

208
        c->linked_ids = strv_free(c->linked_ids);
25✔
209
}
25✔
210

211
static int link_context_acquire_dollar_boot(LinkContext *b) {
12✔
212
        int r;
12✔
213

214
        assert(b);
12✔
215

216
        r = acquire_xbootldr(
12✔
217
                        /* unprivileged_mode= */ false,
218
                        &b->dollar_boot_fd,
219
                        /* ret_uuid= */ NULL,
220
                        /* ret_devid= */ NULL);
221
        if (r < 0)
12✔
222
                return r;
223
        if (r > 0) { /* XBOOTLDR has been found */
12✔
224
                assert(arg_xbootldr_path);
×
225

226
                if (arg_root) {
×
227
                        const char *e = path_startswith(arg_xbootldr_path, arg_root);
×
228
                        if (!e)
×
229
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "XBOOTLDR path '%s' not below specified root '%s', refusing.", arg_xbootldr_path, arg_root);
×
230

231
                        r = strdup_to(&b->dollar_boot_path, e);
×
232
                } else
233
                        r = strdup_to(&b->dollar_boot_path, arg_xbootldr_path);
×
234
                if (r < 0)
×
235
                        return log_oom();
×
236

237
                b->dollar_boot_source = BOOT_ENTRY_XBOOTLDR;
×
238
        } else {
239
                /* No XBOOTLDR has been found, look for ESP */
240

241
                r = acquire_esp(/* unprivileged_mode= */ false,
12✔
242
                                /* graceful= */ false,
243
                                &b->dollar_boot_fd,
244
                                /* ret_part= */ NULL,
245
                                /* ret_pstart= */ NULL,
246
                                /* ret_psize= */ NULL,
247
                                /* ret_uuid= */ NULL,
248
                                /* ret_devid= */ NULL);
249
                if (r < 0)
12✔
250
                        return r;
251

252
                assert(arg_esp_path);
12✔
253

254
                if (arg_root) {
12✔
255
                        const char *e = path_startswith(arg_esp_path, arg_root);
×
256
                        if (!e)
×
257
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ESP path '%s' not below specified root '%s', refusing.", arg_esp_path, arg_root);
×
258

259
                        r = strdup_to(&b->dollar_boot_path, e);
×
260
                } else
261
                        r = strdup_to(&b->dollar_boot_path, arg_esp_path);
12✔
262
                if (r < 0)
12✔
263
                        return log_oom();
×
264

265
                b->dollar_boot_source = BOOT_ENTRY_ESP;
12✔
266
        }
267

268
        return 0;
269
}
270

271
/* Fills the bits of a LinkContext that both "bootctl link" and "bootctl link-auto" derive from the global
272
 * command line options (everything except the kernel image and extra files themselves). */
273
static int link_context_from_cmdline_common(LinkContext *b) {
14✔
274
        assert(b);
14✔
275

276
        b->entry_token_type = arg_entry_token_type;
14✔
277
        b->tries_left = arg_tries_left;
14✔
278
        b->entry_commit = arg_entry_commit;
14✔
279
        b->keep_free = arg_keep_free;
14✔
280

281
        if (strdup_to(&b->entry_token, arg_entry_token) < 0 ||
28✔
282
            strdup_to(&b->entry_title, arg_entry_title) < 0 ||
28✔
283
            strdup_to(&b->entry_version, arg_entry_version) < 0)
14✔
284
                return log_oom();
×
285

286
        if (arg_root) {
14✔
287
                b->root_fd = open(arg_root, O_CLOEXEC|O_DIRECTORY|O_PATH);
×
288
                if (b->root_fd < 0)
×
289
                        return log_error_errno(errno, "Failed to open root directory '%s': %m", arg_root);
×
290

291
                if (strdup_to(&b->root, arg_root) < 0)
×
292
                        return log_oom();
×
293
        } else
294
                b->root_fd = XAT_FDROOT;
14✔
295

296
        return 0;
297
}
298

299
/* Appends the files passed via --extra= on the command line as extra resources to the link context. */
300
static int link_context_add_cmdline_extras(LinkContext *b) {
12✔
301
        int r;
12✔
302

303
        assert(b);
12✔
304

305
        STRV_FOREACH(x, arg_extras) {
15✔
306
                _cleanup_free_ char *fn = NULL;
3✔
307
                r = path_extract_filename(*x, &fn);
3✔
308
                if (r < 0)
3✔
309
                        return log_error_errno(r, "Failed to extract filename from path '%s': %m", *x);
×
310
                if (r == O_DIRECTORY)
3✔
311
                        return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Extra file path '%s' does not refer to regular file.", *x);
×
312

313
                _cleanup_close_ int fd = xopenat_full(AT_FDCWD, *x, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, XO_REGULAR, /* mode= */ MODE_INVALID);
6✔
314
                if (fd < 0)
3✔
315
                        return log_error_errno(fd, "Failed to open '%s': %m", *x);
×
316

317
                if (!GREEDY_REALLOC(b->extra, b->n_extra+1))
3✔
318
                        return log_oom();
×
319

320
                b->extra[b->n_extra++] = (ExtraFile) {
3✔
321
                        .source_fd = TAKE_FD(fd),
3✔
322
                        .filename = TAKE_PTR(fn),
3✔
323
                        .temp_fd = -EBADF,
324
                };
325
        }
326

327
        return 0;
328
}
329

330
static int validate_kernel(int kernel_fd, const char *filename) {
13✔
331
        int r;
13✔
332

333
        assert(kernel_fd >= 0);
13✔
334

335
        KernelImageType kit = _KERNEL_IMAGE_TYPE_INVALID;
13✔
336
        r = inspect_kernel(kernel_fd, /* filename= */ NULL, &kit);
13✔
337
        if (r == -EBADMSG)
13✔
338
                return log_error_errno(r, "UKI '%s' is not valid.", filename);
×
339
        if (r < 0)
13✔
340
                return log_error_errno(r, "Failed to determine kernel image type of '%s': %m", filename);
×
341
        if (kit != KERNEL_IMAGE_TYPE_UKI)
13✔
342
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Image '%s' is not a UKI.", filename);
1✔
343

344
        return 0;
345
}
346

347
static int link_context_from_cmdline(LinkContext *ret, const char *kernel) {
10✔
348
        int r;
10✔
349

350
        assert(ret);
10✔
351
        assert(kernel);
10✔
352

353
        _cleanup_(link_context_done) LinkContext b = LINK_CONTEXT_NULL;
10✔
354
        r = link_context_from_cmdline_common(&b);
10✔
355
        if (r < 0)
10✔
356
                return r;
357

358
        r = path_extract_filename(kernel, &b.kernel_filename);
10✔
359
        if (r < 0)
10✔
360
                return log_error_errno(r, "Failed to extract filename from kernel path '%s': %m", kernel);
×
361
        if (!efi_loader_entry_resource_filename_valid(b.kernel_filename))
10✔
362
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Kernel '%s' is not suitable for reference in a boot menu entry.", kernel);
×
363
        b.kernel_fd = xopenat_full(AT_FDCWD, kernel, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, XO_REGULAR, /* mode= */ MODE_INVALID);
10✔
364
        if (b.kernel_fd < 0)
10✔
365
                return log_error_errno(b.kernel_fd, "Failed to open kernel path '%s': %m", kernel);
×
366

367
        r = validate_kernel(b.kernel_fd, kernel);
10✔
368
        if (r < 0)
10✔
369
                return r;
370

371
        r = link_context_add_cmdline_extras(&b);
9✔
372
        if (r < 0)
9✔
373
                return r;
374

375
        r = link_context_acquire_dollar_boot(&b);
9✔
376
        if (r < 0)
9✔
377
                return r;
378

379
        *ret = TAKE_GENERIC(b, LinkContext, LINK_CONTEXT_NULL);
9✔
380
        return 0;
9✔
381
}
382

383
static int link_context_load_etc_machine_id(LinkContext *c) {
12✔
384
        int r;
12✔
385

386
        assert(c);
12✔
387

388
        r = id128_get_machine_at(c->root_fd, &c->machine_id);
12✔
389
        if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r)) /* Not set or empty */
12✔
390
                return 0;
391
        if (r < 0)
12✔
392
                return log_error_errno(r, "Failed to get machine-id: %m");
×
393

394
        log_debug("Loaded machine ID %s from '%s/etc/machine-id'.", SD_ID128_TO_STRING(c->machine_id), strempty(c->root));
12✔
395
        return 0;
12✔
396
}
397

398
static int link_context_pick_entry_token(LinkContext *c) {
12✔
399
        int r;
12✔
400

401
        assert(c);
12✔
402

403
        r = link_context_load_etc_machine_id(c);
12✔
404
        if (r < 0)
12✔
405
                return r;
406

407
        const char *e = secure_getenv("KERNEL_INSTALL_CONF_ROOT");
12✔
408
        r = boot_entry_token_ensure_at(
12✔
409
                        e ? XAT_FDROOT : c->root_fd,
410
                        e,
411
                        c->machine_id,
412
                        /* machine_id_is_random= */ false,
413
                        &c->entry_token_type,
414
                        &c->entry_token);
415
        if (r < 0)
12✔
416
                return r;
417

418
        log_debug("Using entry token: %s", c->entry_token);
12✔
419
        return 0;
420
}
421

422
static int begin_copy_file(
18✔
423
                int source_fd,               /* Either the source fd is specified, or the 'data' below, not both */
424
                const struct iovec *data,
425
                const char *filename,
426
                int target_dir_fd,
427
                int *ret_tmpfile_fd,
428
                char **ret_tmpfile_filename) {
429

430
        int r;
18✔
431

432
        assert(filename);
18✔
433
        assert(target_dir_fd >= 0);
18✔
434
        assert(ret_tmpfile_fd);
18✔
435
        assert(ret_tmpfile_filename);
18✔
436

437
        if (faccessat(target_dir_fd, filename, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
18✔
438
                if (errno != ENOENT)
16✔
439
                        return log_error_errno(errno, "Failed to check if '%s' exists already: %m", filename);
18✔
440
        } else {
441
                log_info("'%s' already in place, not copying.", filename);
2✔
442

443
                *ret_tmpfile_fd = -EBADF;
2✔
444
                *ret_tmpfile_filename = NULL;
2✔
445
                return 0;
2✔
446
        }
447

448
        _cleanup_free_ char *t = NULL;
16✔
449
        _cleanup_close_ int write_fd = open_tmpfile_linkable_at(target_dir_fd, filename, O_WRONLY|O_CLOEXEC, &t);
32✔
450
        if (write_fd < 0)
16✔
451
                return log_error_errno(write_fd, "Failed to create '%s': %m", filename);
×
452

453
        CLEANUP_TMPFILE_AT(target_dir_fd, t);
16✔
454

455
        if (source_fd >= 0) {
16✔
456
                r = copy_bytes(source_fd, write_fd, UINT64_MAX, COPY_SEEK0_SOURCE);
16✔
457
                if (r < 0)
16✔
458
                        return log_error_errno(r, "Failed to copy data into '%s': %m", filename);
×
459

460
                (void) copy_times(source_fd, write_fd, /* flags= */ 0);
16✔
461
        } else if (iovec_is_set(data)) {
×
462
                r = loop_write(write_fd, data->iov_base, data->iov_len);
×
463
                if (r < 0)
×
464
                        return log_error_errno(r, "Failed to write data into '%s': %m", filename);
×
465
        }
466

467
        (void) fchmod(write_fd, 0644);
16✔
468

469
        *ret_tmpfile_fd = TAKE_FD(write_fd);
16✔
470
        *ret_tmpfile_filename = TAKE_PTR(t);
16✔
471

472
        return 1;
16✔
473
}
474

475
static int begin_write_entry_file(
11✔
476
                LinkContext *c,
477
                unsigned profile_nr,
478
                const char *osrelease_text,
479
                const char *profile_text,
480
                Profile *ret) {
481

482
        int r;
11✔
483

484
        assert(c);
11✔
485
        assert(osrelease_text);
11✔
486
        assert(ret);
11✔
487

488
        assert(c->entry_token);
11✔
489
        assert(c->kernel_filename);
11✔
490
        assert(c->loader_entries_dir_fd >= 0);
11✔
491

492
        _cleanup_free_ char *good_name = NULL, *good_sort_key = NULL, *os_version_id = NULL, *image_version = NULL;
11✔
493
        r = bootspec_extract_osrelease(
11✔
494
                        osrelease_text,
495
                        /* These three fields are used by systemd-stub for showing entries + sorting them */
496
                        &good_name,     /* human readable */
497
                        /* ret_good_version= */ NULL,
498
                        &good_sort_key,
499
                        /* These four fields are the raw fields provided in os-release */
500
                        /* ret_os_id= */ NULL,
501
                        &os_version_id,
502
                        /* ret_image_id= */ NULL,
503
                        &image_version);
504
        if (r < 0)
11✔
505
                return log_error_errno(r, "Failed to extract name/version/sort-key from os-release data from unified kernel image, refusing.");
×
506

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

509
        _cleanup_free_ char *profile_id = NULL, *profile_title = NULL;
11✔
510
        if (profile_text) {
11✔
511
                r = parse_env_data(
×
512
                                profile_text, /* size= */ SIZE_MAX,
513
                                ".profile",
514
                                "ID", &profile_id,
515
                                "TITLE", &profile_title);
516
                if (r < 0)
×
517
                        return log_error_errno(r, "Failed to parse profile data from unified kernel image: %m");
×
518
        }
519

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

522
        _cleanup_free_ char *filename = NULL;
11✔
523
        r = boot_entry_make_commit_filename(
22✔
524
                        c->entry_token,
11✔
525
                        c->entry_commit,
526
                        version,
527
                        profile_nr,
528
                        c->tries_left,
529
                        &filename);
530
        if (r < 0)
11✔
531
                return log_error_errno(r, "Failed to generate filename for entry file: %m");
×
532

533
        if (faccessat(c->loader_entries_dir_fd, filename, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
11✔
534
                if (errno != ENOENT)
10✔
535
                        return log_error_errno(errno, "Failed to check if '%s' exists: %m", filename);
×
536
        } else
537
                return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Boot menu entry '%s' exists already, refusing.", filename);
1✔
538

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

541
        _cleanup_free_ char *t = NULL;
10✔
542
        _cleanup_close_ int write_fd = open_tmpfile_linkable_at(c->loader_entries_dir_fd, filename, O_WRONLY|O_CLOEXEC, &t);
20✔
543
        if (write_fd < 0)
10✔
544
                return log_error_errno(write_fd, "Failed to create '%s': %m", filename);
×
545

546
        CLEANUP_TMPFILE_AT(c->loader_entries_dir_fd, t);
×
547

548
        _cleanup_free_ char *_title = NULL;
10✔
549
        const char *title;
10✔
550
        if (profile_title || profile_id) {
10✔
551
                _title = strjoin(c->entry_title ?: good_name, " (", profile_title ?: profile_id, ")");
×
552
                if (!_title)
×
553
                        return log_oom();
×
554

555
                title = _title;
556
        } else if (profile_nr > 0) {
10✔
557
                _title = asprintf_safe("%s (Profile #%u)", c->entry_title ?: good_name, profile_nr);
×
558
                if (!_title)
×
559
                        return log_oom();
×
560

561
                title = _title;
562
        } else
563
                title = c->entry_title ?: good_name;
10✔
564

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

571
        _cleanup_free_ char *text = NULL;
10✔
572
        if (asprintf(&text,
20✔
573
                     "title %s\n"
574
                     "uki /%s/%s\n"
575
                     "version %" PRIu64 "%s%s\n",
576
                     title,
577
                     c->entry_token, c->kernel_filename,
578
                     c->entry_commit, isempty(version) ? "" : ".", strempty(version)) < 0)
10✔
579
                return log_oom();
×
580

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

584
        if (profile_nr > 0 && strextendf(&text, "profile %u\n", profile_nr) < 0)
10✔
585
                return log_oom();
×
586

587
        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✔
588
                return log_oom();
×
589

590
        FOREACH_ARRAY(x, c->extra, c->n_extra) {
18✔
591
                if (string_has_cc(x->filename, /* ok= */ NULL) || !utf8_is_valid(x->filename))
8✔
592
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extra filename is not suitable for inclusion in new commit: %s", x->filename);
×
593

594
                if (strextendf(&text,
8✔
595
                               "extra /%s/%s\n",
596
                               c->entry_token,
597
                               x->filename) < 0)
598
                        return log_oom();
×
599
        }
600

601
        r = loop_write(write_fd, text, /* nbytes= */ SIZE_MAX);
10✔
602
        if (r < 0)
10✔
603
                return log_error_errno(r, "Failed to write entry file: %m");
×
604

605
        *ret = (Profile) {
10✔
606
                .entry_filename = TAKE_PTR(filename),
10✔
607
                .entry_temp_filename = TAKE_PTR(t),
10✔
608
                .entry_temp_fd = TAKE_FD(write_fd),
10✔
609
        };
610

611
        return 0;
10✔
612
}
613

614
static int finalize_file(
28✔
615
                const char *filename,
616
                int target_dir_fd,
617
                int tmpfile_fd,
618
                const char *tmpfile_filename) {
619

620
        int r;
28✔
621

622
        assert(filename);
28✔
623
        assert(target_dir_fd >= 0);
28✔
624

625
        if (tmpfile_fd < 0) /* If the file already existed, we don't move anything into place. */
28✔
626
                return 0;
627

628
        r = link_tmpfile_at(tmpfile_fd, target_dir_fd, tmpfile_filename, filename, LINK_TMPFILE_REPLACE|LINK_TMPFILE_SYNC);
26✔
629
        if (r < 0)
26✔
630
                return log_error_errno(r, "Failed to move from '%s' into place: %m", filename);
×
631

632
        log_info("Installed '%s' into place.", filename);
26✔
633
        return 1;
634
}
635

636
static int link_context_pick_entry_commit(LinkContext *c) {
12✔
637
        int r;
12✔
638

639
        assert(c);
12✔
640
        assert(c->loader_entries_dir_fd >= 0);
12✔
641
        assert(c->entry_token);
12✔
642

643
        /* Already have a commit nr? */
644
        if (c->entry_commit != 0)
12✔
645
                return 0;
12✔
646

647
        _cleanup_close_ int opened_fd = fd_reopen(c->loader_entries_dir_fd, O_DIRECTORY|O_CLOEXEC);
16✔
648
        if (opened_fd < 0)
4✔
649
                return log_error_errno(opened_fd, "Failed to reopen loader entries dir: %m");
×
650

651
        _cleanup_free_ DirectoryEntries *dentries = NULL;
4✔
652
        r = readdir_all(opened_fd, RECURSE_DIR_IGNORE_DOT, &dentries);
4✔
653
        if (r < 0)
4✔
654
                return log_error_errno(r, "Failed to read loader entries directory: %m");
×
655

656
        uint64_t m = 0; /* largest commit number seen */
4✔
657
        FOREACH_ARRAY(i, dentries->entries, dentries->n_entries) {
4✔
658
                const struct dirent *de = *i;
×
659

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

662
                if (!dirent_is_file(de))
×
663
                        continue;
×
664

665
                if (!efi_loader_entry_name_valid(de->d_name))
×
666
                        continue;
×
667

668
                _cleanup_free_ char *et = NULL;
×
669
                uint64_t ec;
×
670
                r = boot_entry_parse_commit_filename(de->d_name, &et, &ec);
×
671
                if (r < 0) {
×
672
                        log_debug_errno(r, "Cannot extract entry token/commit number from '%s', ignoring.", de->d_name);
×
673
                        continue;
×
674
                }
675

676
                if (!streq(c->entry_token, et))
×
677
                        continue;
×
678

679
                log_debug("Found existing commit %" PRIu64 ".", ec);
×
680
                if (ec > m)
×
681
                        m = ec;
×
682
        }
683

684
        assert(m < UINT64_MAX);
4✔
685
        uint64_t next = m + 1;
4✔
686

687
        if (!entry_commit_valid(next))
4✔
688
                return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many commits already in place, refusing.");
×
689

690
        log_debug("Picking commit %" PRIu64 " for new commit.", next);
4✔
691
        c->entry_commit = next;
4✔
692
        return 0;
4✔
693
}
694

695
static int clean_temporary_files(int fd) {
24✔
696
        int r;
24✔
697

698
        assert(fd >= 0);
24✔
699

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

708
        _cleanup_close_ int dfd = fd_reopen(fd, O_CLOEXEC|O_DIRECTORY);
48✔
709
        if (dfd < 0)
24✔
710
                return log_error_errno(dfd, "Failed to open directory: %m");
×
711

712
        _cleanup_free_ DirectoryEntries *de = NULL;
24✔
713
        r = readdir_all(dfd, RECURSE_DIR_ENSURE_TYPE, &de);
24✔
714
        if (r < 0)
24✔
715
                return log_error_errno(r, "Failed to enumerate contents of directory: %m");
×
716

717
        FOREACH_ARRAY(i, de->entries, de->n_entries) {
31✔
718
                struct dirent *e = *i;
7✔
719

720
                if (e->d_type != DT_REG)
7✔
721
                        continue;
×
722

723
                if (!startswith_no_case(e->d_name, ".#"))
7✔
724
                        continue;
7✔
725

726
                if (unlinkat(dfd, e->d_name, /* flags= */ 0) < 0 && errno != ENOENT)
×
727
                        log_warning_errno(errno, "Failed to remove temporary file '%s', ignoring: %m", e->d_name);
7✔
728
        }
729

730
        return 0;
731
}
732

733
static int link_context_unlink_oldest(LinkContext *c) {
1✔
734
        int r;
1✔
735

736
        assert(c);
1✔
737

738
        /* We only load the entries from the partition we want to make space on (!) */
739
        _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL;
1✔
740
        r = boot_config_load_and_select(
1✔
741
                        &config,
742
                        c->root,
1✔
743
                        c->dollar_boot_source == BOOT_ENTRY_ESP ? c->dollar_boot_path : NULL,
744
                        /* esp_devid= */ 0,
745
                        c->dollar_boot_source == BOOT_ENTRY_XBOOTLDR ? c->dollar_boot_path : NULL,
1✔
746
                        /* xbootldr_devid= */ 0);
747
        if (r < 0)
1✔
748
                return r;
749

750
        _cleanup_(strv_freep) char **ids = NULL;
1✔
751
        r = boot_config_find_oldest_commit(
2✔
752
                        &config,
753
                        c->entry_token,
1✔
754
                        &ids);
755
        if (r == -ENXIO)
1✔
756
                return log_error_errno(r, "No suitable boot menu entry to delete found.");
1✔
757
        if (r == -EBUSY)
×
758
                return log_error_errno(r, "Refusing to remove currently booted boot menu entry.");
×
759
        if (r < 0)
×
760
                return log_error_errno(r, "Failed to find suitable oldest boot menu entry: %m");
×
761

762
        _cleanup_(hashmap_freep) Hashmap *known_files = NULL;
×
763
        r = boot_config_count_known_files(&config, c->dollar_boot_source, &known_files);
×
764
        if (r < 0)
×
765
                return r;
766

767
        int ret = 0;
×
768
        STRV_FOREACH(id, ids) {
×
769
                const BootEntry *entry = boot_config_find_entry(&config, *id);
×
770
                if (!entry)
×
771
                        continue;
×
772

773
                RET_GATHER(ret, boot_entry_unlink(entry, c->dollar_boot_path, c->dollar_boot_fd, known_files, /* dry_run= */ false));
×
774
        }
775

776
        if (ret < 0)
×
777
                return ret;
×
778

779
        return 1;
780
}
781

782
static int verify_keep_free(LinkContext *c) {
22✔
783
        int r;
22✔
784

785
        assert(c);
22✔
786

787
        if (c->keep_free == 0)
22✔
788
                return 0;
22✔
789

790
        uint64_t f;
22✔
791
        r = vfs_free_bytes(ASSERT_FD(c->dollar_boot_fd), &f);
22✔
792
        if (r < 0)
22✔
793
                return log_error_errno(r, "Failed to statvfs() the $BOOT partition: %m");
×
794

795
        if (f < c->keep_free)
22✔
796
                return log_error_errno(
1✔
797
                                SYNTHETIC_ERRNO(EDQUOT),
798
                                "Not installing boot menu entry, free space after installation of %s would be below configured keep free size %s.",
799
                                FORMAT_BYTES(f), FORMAT_BYTES(c->keep_free));
800

801
        return 0;
802
}
803

804
static int run_link_now(LinkContext *c) {
12✔
805
        int r;
12✔
806

807
        assert(c);
12✔
808
        assert(c->dollar_boot_fd >= 0);
12✔
809

810
        _cleanup_free_ char *j = path_join(empty_to_root(c->root), c->dollar_boot_path);
24✔
811
        if (!j)
12✔
812
                return log_oom();
×
813

814
        if (c->loader_entries_dir_fd < 0) {
12✔
815
                r = chaseat(/* root_fd= */ c->dollar_boot_fd,
12✔
816
                            /* dir_fd= */ c->dollar_boot_fd,
817
                            "loader/entries",
818
                            CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY,
819
                            /* ret_path= */ NULL,
820
                            &c->loader_entries_dir_fd);
821
                if (r < 0)
12✔
822
                        return log_error_errno(r, "Failed to pin '/loader/entries' directory below '%s': %m", j);
×
823
        }
824

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

828
        r = link_context_pick_entry_commit(c);
12✔
829
        if (r < 0)
12✔
830
                return r;
831

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

834
        if (c->entry_token_dir_fd < 0) {
12✔
835
                r = chaseat(/* root_fd= */ c->dollar_boot_fd,
24✔
836
                            /* dir_fd= */ c->dollar_boot_fd,
837
                            c->entry_token,
12✔
838
                            CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY,
839
                            /* ret_path= */ NULL,
840
                            &c->entry_token_dir_fd);
841
                if (r < 0)
12✔
842
                        return log_error_errno(r, "Failed to pin '/%s' directory below '%s': %m", c->entry_token, j);
×
843
        }
844

845
        /* As above */
846
        (void) clean_temporary_files(c->entry_token_dir_fd);
12✔
847

848
        /* Synchronize everything to disk before we verify the disk space, to ensure the counters are
849
         * accurate (some file systems delay accurate counters) */
850
        (void) syncfs(c->dollar_boot_fd);
12✔
851

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

860
        for (unsigned p = 0; p < UNIFIED_PROFILES_MAX; p++) {
21✔
861
                _cleanup_free_ char *osrelease = NULL, *profile = NULL;
21✔
862
                r = pe_find_uki_sections(c->kernel_fd, j, p, &osrelease, &profile, /* ret_cmdline= */ NULL);
21✔
863
                if (r < 0)
21✔
864
                        return r;
865
                if (r == 0) /* this profile does not exist, we are done */
21✔
866
                        break;
867

868
                if (!GREEDY_REALLOC(c->profiles, c->n_profiles+1))
11✔
869
                        return log_oom();
×
870

871
                r = begin_write_entry_file(
22✔
872
                                c,
873
                                p,
874
                                osrelease,
875
                                profile,
876
                                c->profiles + c->n_profiles);
11✔
877
                if (r < 0)
11✔
878
                        return r;
879

880
                c->n_profiles++;
10✔
881
        }
882

883
        if (c->n_profiles == 0)
10✔
884
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "UKI with no valid profile, refusing.");
×
885

886
        r = begin_copy_file(
20✔
887
                        c->kernel_fd,
888
                        /* data= */ NULL,
889
                        c->kernel_filename,
10✔
890
                        c->entry_token_dir_fd,
891
                        &c->kernel_temp_fd,
892
                        &c->kernel_temp_filename);
893
        if (r < 0)
10✔
894
                return r;
895

896
        FOREACH_ARRAY(x, c->extra, c->n_extra) {
18✔
897
                r = begin_copy_file(
16✔
898
                                x->source_fd,
899
                                &x->data,
8✔
900
                                x->filename,
8✔
901
                                c->entry_token_dir_fd,
902
                                &x->temp_fd,
903
                                &x->temp_filename);
904
                if (r < 0)
8✔
905
                        return r;
906
        }
907

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

912
        /* Before we materialize things, let's ensure the space to keep free is not taken */
913
        r = verify_keep_free(c);
10✔
914
        if (r < 0)
10✔
915
                return r;
916

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

923
        r = finalize_file(
20✔
924
                        c->kernel_filename,
10✔
925
                        c->entry_token_dir_fd,
926
                        c->kernel_temp_fd,
927
                        c->kernel_temp_filename);
10✔
928
        if (r < 0)
10✔
929
                return r;
930

931
        c->kernel_temp_fd = safe_close(c->kernel_temp_fd);
10✔
932
        c->kernel_temp_filename = mfree(c->kernel_temp_filename);
10✔
933

934
        FOREACH_ARRAY(x, c->extra, c->n_extra) {
18✔
935
                r = finalize_file(
16✔
936
                                x->filename,
8✔
937
                                c->entry_token_dir_fd,
938
                                x->temp_fd,
939
                                x->temp_filename);
8✔
940
                if (r < 0)
8✔
941
                        return r;
942

943
                x->temp_fd = safe_close(x->temp_fd);
8✔
944
                x->temp_filename = mfree(x->temp_filename);
8✔
945
        }
946

947
        /* Finally, after all our resources are in place, also materialize the menu entry files themselves */
948
        FOREACH_ARRAY(profile, c->profiles, c->n_profiles) {
20✔
949
                r = finalize_file(
20✔
950
                                profile->entry_filename,
10✔
951
                                c->loader_entries_dir_fd,
952
                                profile->entry_temp_fd,
953
                                profile->entry_temp_filename);
10✔
954
                if (r < 0)
10✔
955
                        return r;
×
956

957
                profile->entry_temp_fd = safe_close(profile->entry_temp_fd);
10✔
958
                profile->entry_temp_filename = mfree(profile->entry_temp_filename);
10✔
959

960
                _cleanup_free_ char *stripped = NULL;
10✔
961
                r = boot_filename_extract_tries(
20✔
962
                                profile->entry_filename,
10✔
963
                                &stripped,
964
                                /* ret_tries_left= */ NULL,
965
                                /* ret_tries_done= */ NULL);
966
                if (r < 0)
10✔
967
                        return log_warning_errno(r, "Failed to extract tries counters from id '%s'", profile->entry_filename);
×
968

969
                if (strv_consume(&c->linked_ids, TAKE_PTR(stripped)) < 0)
10✔
970
                        return log_oom();
×
971
        }
972

973
        (void) syncfs(c->dollar_boot_fd);
10✔
974
        return 0;
10✔
975
}
976

977
static int run_link(LinkContext *c) {
12✔
978
        int r;
12✔
979

980
        assert(c);
12✔
981
        assert(c->dollar_boot_path);
12✔
982
        assert(c->dollar_boot_fd >= 0);
12✔
983

984
        if (c->keep_free == UINT64_MAX)
12✔
985
                c->keep_free = KEEP_FREE_BYTES_DEFAULT;
×
986

987
        r = link_context_pick_entry_token(c);
12✔
988
        if (r < 0)
12✔
989
                return r;
12✔
990

991
        unsigned n_removals = 0;
992
        for (;;) {
12✔
993
                r = run_link_now(c);
12✔
994
                if (r < 0) {
12✔
995
                        if (!ERRNO_IS_NEG_DISK_SPACE(r))
13✔
996
                                return r;
997
                } else
998
                        break;
999

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

1002
                link_context_unlink_temporary(c);
1✔
1003
                link_context_clear_profiles(c);
1✔
1004

1005
                if (link_context_unlink_oldest(c) <= 0) {
1✔
1006
                        log_warning("Attempted to make space on $BOOT, but this failed, attempt to link entry failed.");
1✔
1007
                        return r; /* propagate original error */
1008
                }
1009

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

1013
                log_info("Removing oldest boot menu entry succeeded, will retry to create boot loader menu entry.");
×
1014
                n_removals++;
×
1015
        }
1016

1017
        _cleanup_free_ char *j = strv_join(c->linked_ids, "', '");
20✔
1018
        if (!j)
10✔
1019
                return log_oom();
×
1020

1021
        if (n_removals > 0)
10✔
1022
                log_info("Successfully installed boot loader entries '%s', after removing %u old entries.", j, n_removals);
×
1023
        else
1024
                log_info("Successfully installed boot loader entries '%s'.", j);
10✔
1025

1026
        return 0;
1027
}
1028

1029
int verb_link(int argc, char *argv[], uintptr_t data, void *userdata) {
10✔
1030
        int r;
10✔
1031

1032
        assert(argc == 2);
10✔
1033

1034
        _cleanup_free_ char *x = NULL;
10✔
1035
        r = parse_path_argument(argv[1], /* suppress_root= */ false, &x);
10✔
1036
        if (r < 0)
10✔
1037
                return r;
1038

1039
        _cleanup_(link_context_done) LinkContext c = LINK_CONTEXT_NULL;
10✔
1040
        r = link_context_from_cmdline(&c, x);
10✔
1041
        if (r < 0)
10✔
1042
                return r;
1043

1044
        return run_link(&c);
9✔
1045
}
1046

1047
/* Directories (relative to the operative root) below which "bootctl link-auto" looks for a staged UKI and
1048
 * extra resources, in decreasing priority order. /var/lib/systemd/uki/ is where systemd-sysupdate stages
1049
 * downloaded resources; the others follow the usual configuration search path precedence, with
1050
 * /var/lib/ slotted in just below the volatile locations and above the vendor trees. */
1051
static const char* const uki_auto_dirs[] = {
1052
        "etc/systemd/uki",
1053
        "run/systemd/uki",
1054
        "var/lib/systemd/uki",
1055
        "usr/local/lib/systemd/uki",
1056
        "usr/lib/systemd/uki",
1057
};
1058

1059
static const PickFilter pick_filter_uki = {
1060
        .type_mask = UINT32_C(1) << DT_REG,
1061
        .architecture = _ARCHITECTURE_INVALID,
1062
        .suffix = ".efi",
1063
};
1064

1065
/* The recognized suffixes of extra resources we pick up alongside the UKI. Each may appear as a plain file
1066
 * (e.g. foo.sysext.raw) or as a versioned directory (e.g. foo.sysext.raw.v/) resolved via the vpick
1067
 * mechanism. */
1068
static const char* const auto_link_extra_suffixes[] = {
1069
        ".sysext.raw",
1070
        ".confext.raw",
1071
        ".cred",
1072
};
1073

1074
static int link_context_add_extra(LinkContext *c, int dir_fd, const char *path, const char *filename) {
5✔
1075
        assert(c);
5✔
1076
        assert(dir_fd >= 0);
5✔
1077
        assert(path);
5✔
1078
        assert(filename);
5✔
1079

1080
        if (!efi_loader_entry_resource_filename_valid(filename))
5✔
1081
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extra file '%s' is not suitable for reference in a boot menu entry, refusing.", filename);
×
1082

1083
        _cleanup_close_ int fd = xopenat_full(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, XO_REGULAR, /* mode= */ MODE_INVALID);
10✔
1084
        if (fd < 0)
5✔
1085
                return log_error_errno(fd, "Failed to open extra file '%s': %m", path);
×
1086

1087
        _cleanup_free_ char *fn = strdup(filename);
5✔
1088
        if (!fn)
5✔
1089
                return log_oom();
×
1090

1091
        if (!GREEDY_REALLOC(c->extra, c->n_extra + 1))
5✔
1092
                return log_oom();
×
1093

1094
        c->extra[c->n_extra++] = (ExtraFile) {
5✔
1095
                .source_fd = TAKE_FD(fd),
5✔
1096
                .filename = TAKE_PTR(fn),
5✔
1097
                .temp_fd = -EBADF,
1098
        };
1099

1100
        return 0;
5✔
1101
}
1102

1103
/* Adds the extra resources found in <uki_dir_fd>/extras.d/ to the link context. The 'seen' set tracks the
1104
 * logical resource names already added by a higher-priority directory, so the same resource appearing in
1105
 * multiple directories is taken from the highest-priority one only. */
1106
static int link_context_add_extras(LinkContext *c, int uki_dir_fd, Set **seen) {
4✔
1107
        int r;
4✔
1108

1109
        assert(c);
4✔
1110
        assert(uki_dir_fd >= 0);
4✔
1111
        assert(seen);
4✔
1112

1113
        _cleanup_close_ int extras_dir_fd = chase_and_openat(
8✔
1114
                        c->root_fd,
1115
                        uki_dir_fd,
1116
                        "extras.d",
1117
                        /* chase_flags= */ 0,
1118
                        O_DIRECTORY|O_CLOEXEC,
1119
                        /* ret_path= */ NULL);
1120
        if (extras_dir_fd == -ENOENT)
4✔
1121
                return 0;
1122
        if (extras_dir_fd < 0)
4✔
1123
                return log_error_errno(extras_dir_fd, "Failed to open extras.d/ directory: %m");
×
1124

1125
        _cleanup_free_ DirectoryEntries *des = NULL;
4✔
1126
        r = readdir_all(extras_dir_fd, RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE|RECURSE_DIR_SORT, &des);
4✔
1127
        if (r < 0)
4✔
1128
                return log_error_errno(r, "Failed to enumerate extras.d/ directory: %m");
×
1129

1130
        FOREACH_ARRAY(i, des->entries, des->n_entries) {
10✔
1131
                struct dirent *de = *i;
6✔
1132

1133
                if (!IN_SET(de->d_type, DT_DIR, DT_REG))
6✔
1134
                        continue;
×
1135

1136
                FOREACH_ELEMENT(suffix, auto_link_extra_suffixes) {
13✔
1137
                        _cleanup_free_ char *dot_v = strjoin(*suffix, ".v");
20✔
1138
                        if (!dot_v)
13✔
1139
                                return log_oom();
×
1140

1141
                        bool versioned;
13✔
1142
                        _cleanup_free_ char *key = NULL;
13✔
1143
                        if (endswith(de->d_name, *suffix)) {
13✔
1144
                                if (de->d_type != DT_REG)
5✔
1145
                                        continue;
×
1146

1147
                                versioned = false;
5✔
1148
                                key = strdup(de->d_name);
5✔
1149

1150
                        } else if (endswith(de->d_name, dot_v)) {
8✔
1151
                                if (de->d_type != DT_DIR)
1✔
1152
                                        continue;
×
1153

1154
                                /* The dedup key is the directory entry name with any trailing ".v" stripped, so that
1155
                                 * a plain file and a versioned directory of the same resource collapse, and a
1156
                                 * higher-priority directory wins. */
1157
                                versioned = true;
1✔
1158
                                key = strndup(de->d_name, strlen(de->d_name) - strlen(".v"));
1✔
1159
                        } else
1160
                                continue;
7✔
1161
                        if (!key)
6✔
1162
                                return log_oom();
×
1163

1164
                        if (set_contains(*seen, key))
6✔
1165
                                break;
1166

1167
                        if (versioned) {
5✔
1168
                                /* A versioned directory: resolve the best version via vpick, relative to the
1169
                                 * extras directory fd. We deliberately do not honour boot-counting suffixes
1170
                                 * here. */
1171
                                _cleanup_(pick_result_done) PickResult pick = PICK_RESULT_NULL;
×
1172
                                const PickFilter filter = {
1✔
1173
                                        .type_mask = UINT32_C(1) << DT_REG,
1174
                                        .architecture = _ARCHITECTURE_INVALID,
1175
                                        .suffix = *suffix,
1✔
1176
                                };
1177

1178
                                r = path_pick(/* root_path= */ NULL,
1✔
1179
                                              /* root_fd= */ c->root_fd,
1180
                                              /* dir_fd= */ extras_dir_fd,
1181
                                              de->d_name,
1182
                                              &filter,
1183
                                              /* n_filters= */ 1,
1184
                                              /* flags= */ 0,
1185
                                              &pick);
1186
                                if (r == -ENOENT || r == 0) {
1✔
1187
                                        log_debug("No matching entry in versioned directory '%s', skipping.", de->d_name);
×
1188
                                        break;
×
1189
                                }
1190
                                if (r < 0)
1✔
1191
                                        return log_error_errno(r, "Failed to pick version in '%s': %m", de->d_name);
×
1192

1193
                                _cleanup_free_ char *fn = NULL;
1✔
1194
                                r = path_extract_filename(pick.path, &fn);
1✔
1195
                                if (r < 0)
1✔
1196
                                        return log_error_errno(r, "Failed to extract filename from '%s': %m", pick.path);
×
1197

1198
                                r = link_context_add_extra(c, extras_dir_fd, pick.path, fn);
1✔
1199
                        } else
1200
                                /* A plain file. */
1201
                                r = link_context_add_extra(c, extras_dir_fd, de->d_name, de->d_name);
4✔
1202
                        if (r < 0)
5✔
1203
                                return r;
1204

1205
                        r = set_ensure_consume(seen, &string_hash_ops_free, TAKE_PTR(key));
5✔
1206
                        if (r < 0)
5✔
1207
                                return log_oom();
×
1208

1209
                        break;
1210
                }
1211
        }
1212

1213
        return 0;
1214
}
1215

1216
/* Looks for a UKI (kernel.efi, or the best version in kernel.efi.v/) in the directory referenced by
1217
 * uki_dir_fd and, if found, fills c->kernel_fd and c->kernel_filename. Returns 1 if found, 0 if not, and a
1218
 * negative errno on error. We deliberately do not honour boot-counting suffixes here. */
1219
static int link_context_find_kernel(LinkContext *c, int uki_dir_fd) {
3✔
1220
        int r;
3✔
1221

1222
        assert(c);
3✔
1223
        assert(uki_dir_fd >= 0);
3✔
1224

1225
        _cleanup_free_ char *filename = NULL;
3✔
1226
        _cleanup_close_ int kernel_fd = xopenat_full(
6✔
1227
                        uki_dir_fd, "kernel.efi",
1228
                        O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY|O_NOFOLLOW,
1229
                        XO_REGULAR,
1230
                        /* mode= */ MODE_INVALID);
1231
        if (kernel_fd == -ENOENT) {
3✔
1232
                _cleanup_(pick_result_done) PickResult pick = PICK_RESULT_NULL;
1✔
1233
                r = path_pick(/* root_path= */ NULL,
1✔
1234
                              /* root_fd= */ c->root_fd,
1235
                              /* dir_fd= */ uki_dir_fd,
1236
                              "kernel.efi.v",
1237
                              &pick_filter_uki,
1238
                              /* n_filters= */ 1,
1239
                              /* flags= */ 0,
1240
                              &pick);
1241
                if (r == -ENOENT || r == 0)
1✔
1242
                        return 0;
1243
                if (r < 0)
1✔
1244
                        return log_error_errno(r, "Failed to pick UKI version in 'kernel.efi.v': %m");
×
1245

1246
                r = path_extract_filename(pick.path, &filename);
1✔
1247
                if (r < 0)
1✔
1248
                        return log_error_errno(r, "Failed to extract filename from '%s': %m", pick.path);
×
1249

1250
                kernel_fd = fd_reopen(pick.fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
1✔
1251
                if (kernel_fd < 0)
1✔
1252
                        return log_error_errno(kernel_fd, "Failed to open UKI '%s': %m", pick.path);
×
1253

1254
                r = fd_verify_regular(kernel_fd);
1✔
1255
                if (r < 0)
1✔
1256
                        return log_error_errno(r, "UKI '%s' is not a regular file: %m", pick.path);
×
1257

1258
        } else if (kernel_fd < 0)
2✔
1259
                return log_error_errno(kernel_fd, "Failed to open 'kernel.efi': %m");
×
1260
        else {
1261
                filename = strdup("kernel.efi");
2✔
1262
                if (!filename)
2✔
1263
                        return log_oom();
×
1264
        }
1265

1266
        if (!efi_loader_entry_resource_filename_valid(filename))
3✔
1267
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UKI '%s' is not suitable for reference in a boot menu entry.", filename);
×
1268

1269
        r = validate_kernel(kernel_fd, filename);
3✔
1270
        if (r < 0)
3✔
1271
                return r;
1272

1273
        c->kernel_fd = TAKE_FD(kernel_fd);
3✔
1274
        c->kernel_filename = TAKE_PTR(filename);
3✔
1275
        return 1;
3✔
1276
}
1277

1278
/* Discovers the staged UKI and extra resources below the uki_auto_dirs[] (relative to the already-pinned
1279
 * c->root_fd) and fills c->kernel_fd, c->kernel_filename and c->extra[] accordingly. The directories are
1280
 * searched in priority order: the UKI is taken from the first directory that has one, and extra resources
1281
 * are combined across all directories with higher-priority directories winning on conflicts. Returns 1 if a
1282
 * UKI was found, 0 if there is nothing to link, and a negative errno on error. */
1283
static int link_context_discover_resources(LinkContext *c) {
5✔
1284
        _cleanup_set_free_ Set *seen = NULL;
×
1285
        int r;
5✔
1286

1287
        assert(c);
5✔
1288

1289
        FOREACH_ELEMENT(dir, uki_auto_dirs) {
30✔
1290
                _cleanup_close_ int uki_dir_fd = chase_and_openat(
55✔
1291
                                /* root_fd= */ c->root_fd,
1292
                                /* dir_fd= */ c->root_fd,
1293
                                *dir,
1294
                                CHASE_MUST_BE_DIRECTORY,
1295
                                O_RDONLY|O_DIRECTORY|O_CLOEXEC,
1296
                                /* ret_path= */ NULL);
1297
                if (uki_dir_fd == -ENOENT)
25✔
1298
                        continue;
21✔
1299
                if (uki_dir_fd < 0)
4✔
1300
                        return log_error_errno(uki_dir_fd, "Failed to open '%s': %m", *dir);
×
1301

1302
                /* Take the UKI from the first (highest-priority) directory that has one. */
1303
                if (c->kernel_fd < 0) {
4✔
1304
                        r = link_context_find_kernel(c, uki_dir_fd);
3✔
1305
                        if (r < 0)
3✔
1306
                                return r;
1307
                }
1308

1309
                r = link_context_add_extras(c, uki_dir_fd, &seen);
4✔
1310
                if (r < 0)
4✔
1311
                        return r;
1312
        }
1313

1314
        if (c->kernel_fd < 0) {
5✔
1315
                log_debug("No UKI found in any of the search directories, nothing to link.");
2✔
1316
                return 0;
1317
        }
1318

1319
        return 1;
1320
}
1321

1322
int verb_link_auto(int argc, char *argv[], uintptr_t data, void *userdata) {
4✔
1323
        int r;
4✔
1324

1325
        assert(argc == 1);
4✔
1326

1327
        _cleanup_(link_context_done) LinkContext c = LINK_CONTEXT_NULL;
4✔
1328
        r = link_context_from_cmdline_common(&c);
4✔
1329
        if (r < 0)
4✔
1330
                return r;
1331

1332
        r = link_context_discover_resources(&c);
4✔
1333
        if (r < 0)
4✔
1334
                return r;
1335
        if (r == 0) {
4✔
1336
                log_info("No UKI staged for linking, nothing to do.");
1✔
1337
                return 0;
1338
        }
1339

1340
        /* In addition to the auto-discovered resources, also honour any files passed via --extra=. */
1341
        r = link_context_add_cmdline_extras(&c);
3✔
1342
        if (r < 0)
3✔
1343
                return r;
1344

1345
        r = link_context_acquire_dollar_boot(&c);
3✔
1346
        if (r < 0)
3✔
1347
                return r;
1348

1349
        return run_link(&c);
3✔
1350
}
1351

1352
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_boot_entry_token_type, BootEntryTokenType, boot_entry_token_type_from_string);
×
1353

1354
typedef struct LinkParameters {
1355
        LinkContext context;
1356
        unsigned root_fd_index;
1357
        unsigned kernel_fd_index;
1358
        char *esp_path;
1359
        char *xbootldr_path;
1360
        sd_varlink *link;
1361
} LinkParameters;
1362

1363
static void link_parameters_done(LinkParameters *p) {
1✔
1364
        assert(p);
1✔
1365

1366
        link_context_done(&p->context);
1✔
1367
        free(p->esp_path);
1✔
1368
        free(p->xbootldr_path);
1✔
1369
}
1✔
1370

1371
typedef struct ExtraParameters {
1372
        ExtraFile extra_file;
1373
        unsigned fd_index;
1374
} ExtraParameters;
1375

1376
static void extra_parameters_done(ExtraParameters *p) {
×
1377
        assert(p);
×
1378

1379
        extra_file_done(&p->extra_file);
×
1380
}
×
1381

1382
static int json_dispatch_loader_entry_resource_filename(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
1383
        char **n = ASSERT_PTR(userdata);
×
1384
        const char *filename;
×
1385
        int r;
×
1386

1387
        assert(variant);
×
1388

1389
        r = json_dispatch_const_filename(name, variant, flags, &filename);
×
1390
        if (r < 0)
×
1391
                return r;
×
1392

1393
        if (filename && !efi_loader_entry_resource_filename_valid(filename))
×
1394
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid boot entry resource filename.", strna(name));
×
1395

1396
        if (free_and_strdup(n, filename) < 0)
×
1397
                return json_log_oom(variant, flags);
×
1398

1399
        return 0;
1400
}
1401

1402
static int dispatch_extras(const char *name, sd_json_variant *v, sd_json_dispatch_flags_t flags, void *userdata) {
×
1403
        LinkParameters *c = ASSERT_PTR(userdata);
×
1404
        int r;
×
1405

1406
        if (!sd_json_variant_is_array(v))
×
1407
                return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
×
1408

1409
        sd_json_variant *i;
×
1410
        JSON_VARIANT_ARRAY_FOREACH(i, v) {
×
1411
                _cleanup_(extra_parameters_done) ExtraParameters xp = {
×
1412
                        .extra_file = EXTRA_FILE_NULL,
1413
                        .fd_index = UINT_MAX,
1414
                };
1415

1416
                static const sd_json_dispatch_field dispatch_table[] = {
×
1417
                        { "filename",       SD_JSON_VARIANT_STRING,        json_dispatch_loader_entry_resource_filename, offsetof(ExtraParameters, extra_file.filename),  SD_JSON_MANDATORY },
1418
                        { "fileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint,                        offsetof(ExtraParameters, fd_index),             0                 },
1419
                        { "data",           SD_JSON_VARIANT_STRING,        json_dispatch_unbase64_iovec,                 offsetof(ExtraParameters, extra_file.data),      0                 },
1420
                        {},
1421
                };
1422

1423
                r = sd_json_dispatch(i, dispatch_table, /* flags= */ 0, &xp);
×
1424
                if (r < 0)
×
1425
                        return r;
1426

1427
                if (iovec_is_set(&xp.extra_file.data) == (xp.fd_index != UINT_MAX))
×
1428
                        return sd_varlink_error_invalid_parameter_name(c->link, name);
×
1429
                if (xp.fd_index != UINT_MAX) {
×
1430
                        xp.extra_file.source_fd = sd_varlink_peek_dup_fd(c->link, xp.fd_index);
×
1431
                        if (xp.extra_file.source_fd < 0)
×
1432
                                return log_debug_errno(xp.extra_file.source_fd, "Failed to acquire extra fd from Varlink: %m");
×
1433

1434
                        r = fd_verify_safe_flags(xp.extra_file.source_fd);
×
1435
                        if (r < 0)
×
1436
                                return sd_varlink_error_invalid_parameter_name(c->link, name);
×
1437

1438
                        r = fd_verify_regular(xp.extra_file.source_fd);
×
1439
                        if (r < 0)
×
1440
                                return log_debug_errno(r, "Failed to validate that the extra file is a regular file descriptor: %m");
×
1441
                }
1442

1443
                if (!GREEDY_REALLOC(c->context.extra, c->context.n_extra+1))
×
1444
                        return log_oom();
×
1445

1446
                c->context.extra[c->context.n_extra++] = TAKE_GENERIC(xp.extra_file, ExtraFile, EXTRA_FILE_NULL);
×
1447
        }
1448

1449
        return 0;
×
1450
}
1451

1452
/* Resolves the root_fd of the LinkContext from the dispatched Link/LinkAuto parameters, fills in the
1453
 * boot entry token type default, and validates the entry title/version/commit fields. Returns 0 on success,
1454
 * non-zero (Varlink error or negative errno) otherwise. */
1455
static int vl_link_prepare(sd_varlink *link, LinkParameters *p) {
1✔
1456
        int r;
1✔
1457

1458
        assert(link);
1✔
1459
        assert(p);
1✔
1460

1461
        if (p->root_fd_index != UINT_MAX) {
1✔
1462
                p->context.root_fd = sd_varlink_peek_dup_fd(link, p->root_fd_index);
×
1463
                if (p->context.root_fd < 0)
×
1464
                        return log_debug_errno(p->context.root_fd, "Failed to acquire root fd from Varlink: %m");
×
1465

1466
                r = fd_verify_safe_flags_full(p->context.root_fd, O_DIRECTORY);
×
1467
                if (r < 0)
×
1468
                        return sd_varlink_error_invalid_parameter_name(link, "rootFileDescriptor");
×
1469

1470
                r = fd_verify_directory(p->context.root_fd);
×
1471
                if (r < 0)
×
1472
                        return log_debug_errno(r, "Specified file descriptor does not refer to a directory: %m");
×
1473

1474
                if (!p->context.root) {
×
1475
                        r = fd_get_path(p->context.root_fd, &p->context.root);
×
1476
                        if (r < 0)
×
1477
                                return log_debug_errno(r, "Failed to get path of file descriptor: %m");
×
1478

1479
                        if (empty_or_root(p->context.root))
×
1480
                                p->context.root = mfree(p->context.root);
×
1481
                }
1482
        } else if (p->context.root) {
1✔
1483
                p->context.root_fd = open(p->context.root, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
×
1484
                if (p->context.root_fd < 0)
×
1485
                        return log_debug_errno(errno, "Failed to open '%s': %m", p->context.root);
×
1486
        } else
1487
                p->context.root_fd = XAT_FDROOT;
1✔
1488

1489
        if (p->context.entry_token_type < 0)
1✔
1490
                p->context.entry_token_type = BOOT_ENTRY_TOKEN_AUTO;
1✔
1491

1492
        if (p->context.entry_title && !efi_loader_entry_title_valid(p->context.entry_title))
1✔
1493
                return sd_varlink_error_invalid_parameter_name(link, "entryTitle");
×
1494

1495
        if (p->context.entry_version && !version_is_valid(p->context.entry_version, /* flags= */ 0))
1✔
1496
                return sd_varlink_error_invalid_parameter_name(link, "entryVersion");
×
1497

1498
        if (p->context.entry_commit != 0 && !entry_commit_valid(p->context.entry_commit))
1✔
1499
                return sd_varlink_error_invalid_parameter_name(link, "entryCommit");
×
1500

1501
        return 0;
1502
}
1503

1504
/* Resolves $BOOT, runs the link operation and sends the reply. Expects the kernel and extra files to already
1505
 * be set up in the context. If with_ids is true the reply carries the list of created entry IDs (as expected
1506
 * by Link()/LinkAuto()), otherwise an empty reply is sent (as expected by
1507
 * io.systemd.SysUpdate.Notify.OnCompletedUpdate(), which declares no output fields). */
1508
static int vl_link_finish(sd_varlink *link, LinkParameters *p, bool with_ids) {
×
1509
        int r;
×
1510

1511
        assert(link);
×
1512
        assert(p);
×
1513

1514
        r = find_xbootldr_and_warn_at(
×
1515
                        p->context.root_fd,
1516
                        p->xbootldr_path,
×
1517
                        /* unprivileged_mode= */ false,
1518
                        &p->context.dollar_boot_path,
1519
                        &p->context.dollar_boot_fd);
1520
        if (r < 0) {
×
1521
                if (r != -ENOKEY)
×
1522
                        return r;
1523

1524
                /* No XBOOTLDR found, let's look for ESP then. */
1525

1526
                r = find_esp_and_warn_at(
×
1527
                                p->context.root_fd,
1528
                                p->esp_path,
×
1529
                                /* unprivileged_mode= */ false,
1530
                                &p->context.dollar_boot_path,
1531
                                &p->context.dollar_boot_fd);
1532
                if (r == -ENOKEY)
×
1533
                        return sd_varlink_error(link, "io.systemd.BootControl.NoDollarBootFound", NULL);
×
1534
                if (r < 0)
×
1535
                        return r;
1536

1537
                p->context.dollar_boot_source = BOOT_ENTRY_ESP;
×
1538
        } else
1539
                p->context.dollar_boot_source = BOOT_ENTRY_XBOOTLDR;
×
1540

1541
        r = run_link(&p->context);
×
1542
        if (r == -EUNATCH) /* no boot entry token is set */
×
1543
                return sd_varlink_error(link, "io.systemd.BootControl.BootEntryTokenUnavailable", NULL);
×
1544
        if (r < 0)
×
1545
                return r;
1546

1547
        if (with_ids)
×
1548
                return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRV("ids", p->context.linked_ids));
×
1549

1550
        return sd_varlink_reply(link, NULL);
×
1551
}
1552

1553
int vl_method_link(
×
1554
                sd_varlink *link,
1555
                sd_json_variant *parameters,
1556
                sd_varlink_method_flags_t flags,
1557
                void *userdata) {
1558

1559
        int r;
×
1560

1561
        assert(link);
×
1562

1563
        _cleanup_(link_parameters_done) LinkParameters p = {
×
1564
                .context = LINK_CONTEXT_NULL,
1565
                .root_fd_index = UINT_MAX,
1566
                .kernel_fd_index = UINT_MAX,
1567
                .link = link,
1568
        };
1569

1570
        static const sd_json_dispatch_field dispatch_table[] = {
×
1571
                { "rootFileDescriptor",   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint,                        voffsetof(p, root_fd_index),            0                 },
1572
                { "rootDirectory",        SD_JSON_VARIANT_STRING,        json_dispatch_path,                           voffsetof(p, context.root),             0                 },
1573
                { "espPath",              SD_JSON_VARIANT_STRING,        json_dispatch_path,                           voffsetof(p, esp_path),                 0                 },
1574
                { "xbootldrPath",         SD_JSON_VARIANT_STRING,        json_dispatch_path,                           voffsetof(p, xbootldr_path),            0                 },
1575
                { "bootEntryTokenType",   SD_JSON_VARIANT_STRING,        json_dispatch_boot_entry_token_type,          voffsetof(p, context.entry_token_type), 0                 },
1576
                { "entryTitle",           SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,                      voffsetof(p, context.entry_title),      0                 },
1577
                { "entryVersion",         SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,                      voffsetof(p, context.entry_version),    0                 },
1578
                { "entryCommit",          SD_JSON_VARIANT_INTEGER,       sd_json_dispatch_uint64,                      voffsetof(p, context.entry_commit),     0                 },
1579
                { "kernelFilename",       SD_JSON_VARIANT_STRING,        json_dispatch_loader_entry_resource_filename, voffsetof(p, context.kernel_filename),  SD_JSON_MANDATORY },
1580
                { "kernelFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint,                        voffsetof(p, kernel_fd_index),          SD_JSON_MANDATORY },
1581
                { "extraFiles",           SD_JSON_VARIANT_ARRAY,         dispatch_extras,                              0,                                      0                 },
1582
                { "triesLeft",            _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint,                        voffsetof(p, context.tries_left),       0                 },
1583
                { "keepFree",             _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,                      voffsetof(p, context.keep_free),        0                 },
1584
                {},
1585
        };
1586

1587
        r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
×
1588
        if (r != 0)
×
1589
                return r;
1590

1591
        r = varlink_check_privileged_peer(link);
×
1592
        if (r < 0)
×
1593
                return r;
1594

1595
        r = vl_link_prepare(link, &p);
×
1596
        if (r != 0)
×
1597
                return r;
1598

1599
        p.context.kernel_fd = sd_varlink_peek_dup_fd(link, p.kernel_fd_index);
×
1600
        if (p.context.kernel_fd < 0)
×
1601
                return log_debug_errno(p.context.kernel_fd, "Failed to acquire kernel fd from Varlink: %m");
×
1602

1603
        r = fd_verify_safe_flags(p.context.kernel_fd);
×
1604
        if (r < 0)
×
1605
                return sd_varlink_error_invalid_parameter_name(link, "kernelFileDescriptor");
×
1606
        r = fd_verify_regular(p.context.kernel_fd);
×
1607
        if (r < 0)
×
1608
                return log_debug_errno(r, "Failed to validate that kernel image file is a regular file descriptor: %m");
×
1609

1610
        /* Refuse non-UKIs for now. */
1611
        KernelImageType kit = _KERNEL_IMAGE_TYPE_INVALID;
×
1612
        r = inspect_kernel(p.context.kernel_fd, /* filename= */ NULL, &kit);
×
1613
        if (r == -EBADMSG)
×
1614
                return sd_varlink_error(link, "io.systemd.BootControl.InvalidKernelImage", NULL);
×
1615
        if (r < 0)
×
1616
                return r;
1617
        if (kit != KERNEL_IMAGE_TYPE_UKI)
×
1618
                return sd_varlink_error(link, "io.systemd.BootControl.InvalidKernelImage", NULL);
×
1619

1620
        return vl_link_finish(link, &p, /* with_ids= */ true);
×
1621
}
1622

1623
int vl_method_link_auto(
1✔
1624
                sd_varlink *link,
1625
                sd_json_variant *parameters,
1626
                sd_varlink_method_flags_t flags,
1627
                void *userdata) {
1628

1629
        int r;
1✔
1630

1631
        assert(link);
1✔
1632

1633
        _cleanup_(link_parameters_done) LinkParameters p = {
1✔
1634
                .context = LINK_CONTEXT_NULL,
1635
                .root_fd_index = UINT_MAX,
1636
                .kernel_fd_index = UINT_MAX,
1637
                .link = link,
1638
        };
1639

1640
        static const sd_json_dispatch_field dispatch_table[] = {
1✔
1641
                { "rootFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint,               voffsetof(p, root_fd_index),            0 },
1642
                { "rootDirectory",      SD_JSON_VARIANT_STRING,        json_dispatch_path,                  voffsetof(p, context.root),             0 },
1643
                { "espPath",            SD_JSON_VARIANT_STRING,        json_dispatch_path,                  voffsetof(p, esp_path),                 0 },
1644
                { "xbootldrPath",       SD_JSON_VARIANT_STRING,        json_dispatch_path,                  voffsetof(p, xbootldr_path),            0 },
1645
                { "bootEntryTokenType", SD_JSON_VARIANT_STRING,        json_dispatch_boot_entry_token_type, voffsetof(p, context.entry_token_type), 0 },
1646
                { "entryTitle",         SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,             voffsetof(p, context.entry_title),      0 },
1647
                { "entryVersion",       SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,             voffsetof(p, context.entry_version),    0 },
1648
                { "entryCommit",        SD_JSON_VARIANT_INTEGER,       sd_json_dispatch_uint64,             voffsetof(p, context.entry_commit),     0 },
1649
                { "triesLeft",          _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint,               voffsetof(p, context.tries_left),       0 },
1650
                { "keepFree",           _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,             voffsetof(p, context.keep_free),        0 },
1651
                {},
1652
        };
1653

1654
        r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
1✔
1655
        if (r != 0)
1✔
1656
                return r;
1657

1658
        r = varlink_check_privileged_peer(link);
1✔
1659
        if (r < 0)
1✔
1660
                return r;
1661

1662
        r = vl_link_prepare(link, &p);
1✔
1663
        if (r != 0)
1✔
1664
                return r;
1665

1666
        r = link_context_discover_resources(&p.context);
1✔
1667
        if (r < 0)
1✔
1668
                return r;
1669
        if (r == 0) /* Nothing staged for linking. */
1✔
1670
                return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRV("ids", STRV_EMPTY));
1✔
1671

1672
        return vl_link_finish(link, &p, /* with_ids= */ true);
×
1673
}
1674

1675
int vl_method_on_completed_update(
×
1676
                sd_varlink *link,
1677
                sd_json_variant *parameters,
1678
                sd_varlink_method_flags_t flags,
1679
                void *userdata) {
1680

1681
        int r;
×
1682

1683
        assert(link);
×
1684

1685
        /* Only honour update notifications if they come from root */
1686
        r = varlink_check_privileged_peer(link);
×
1687
        if (r < 0)
×
1688
                return r;
×
1689

1690
        /* Triggered by systemd-sysupdate after an update completed. We deliberately ignore all parameters
1691
         * (we don't even dispatch them) and behave like LinkAuto() with default parameters: discover the
1692
         * staged UKI and extra resources and link them in. Contrary to LinkAuto() we reply without any
1693
         * parameters, matching the io.systemd.SysUpdate.Notify.OnCompletedUpdate() signature. */
1694

1695
        _cleanup_(link_parameters_done) LinkParameters p = {
×
1696
                .context = LINK_CONTEXT_NULL,
1697
                .root_fd_index = UINT_MAX,
1698
                .kernel_fd_index = UINT_MAX,
1699
                .link = link,
1700
        };
1701

1702
        r = vl_link_prepare(link, &p);
×
1703
        if (r != 0)
×
1704
                return r;
1705

1706
        r = link_context_discover_resources(&p.context);
×
1707
        if (r < 0)
×
1708
                return r;
1709
        if (r == 0) /* Nothing staged for linking. */
×
1710
                return sd_varlink_reply(link, NULL);
×
1711

1712
        return vl_link_finish(link, &p, /* with_ids= */ false);
×
1713
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc