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

systemd / systemd / 25615496818

09 May 2026 05:08PM UTC coverage: 72.511% (-0.1%) from 72.64%
25615496818

push

github

bluca
hwdb/keyboard: fix KP_Enter on Clevo PA70ES

The ITE keyboard controller firmware (version 0xAB83) is shared
between the Clevo PA70ES and the X+ piccolo series.

The piccolo's hwdb rule matches by input device ID
(evdev:input:b0011v0001p0001eAB83*) and remaps scan code 0x9c
(KP_Enter) to Enter, since the piccolo has no numpad and its
main Enter key sends the wrong scan code.

The Clevo PA70ES has a real numpad. The piccolo rule matches it
because both laptops use the same ITE controller firmware, which
breaks KP_Enter on the PA70ES.

Add a DMI-specific override that restores KEY_KPENTER for 0x9c
on the PA70ES.

The piccolo rule should ideally be narrowed to use DMI matching
instead of input device ID to avoid catching other laptops with
the same ITE controller firmware.

326196 of 449859 relevant lines covered (72.51%)

1210161.93 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

138
                c->kernel_temp_fd = safe_close(c->kernel_temp_fd);
×
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

149
                x->temp_fd = safe_close(x->temp_fd);
×
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

160
                p->entry_temp_fd = safe_close(p->entry_temp_fd);
×
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✔
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

230
                if (strdup_to(&b.root, arg_root) < 0)
×
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✔
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✔
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✔
247
                return log_error_errno(r, "Kernel image '%s' is not valid.", kernel);
×
248
        if (r < 0)
10✔
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✔
257
                        return log_error_errno(r, "Failed to extract filename from path '%s': %m", *x);
×
258
                if (r == O_DIRECTORY)
3✔
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✔
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) {
×
287
                        const char *e = path_startswith(arg_xbootldr_path, arg_root);
×
288
                        if (!e)
×
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
293
                        r = strdup_to(&b.dollar_boot_path, arg_xbootldr_path);
×
294
                if (r < 0)
×
295
                        return log_oom();
×
296

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✔
315
                        const char *e = path_startswith(arg_esp_path, arg_root);
×
316
                        if (!e)
×
317
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ESP path '%s' not below specified root '%s', refusing.", arg_esp_path, arg_root);
×
318

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✔
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✔
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✔
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✔
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✔
410
        } else if (iovec_is_set(data)) {
×
411
                r = loop_write(write_fd, data->iov_base, data->iov_len);
×
412
                if (r < 0)
×
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✔
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✔
460
                r = parse_env_data(
×
461
                                profile_text, /* size= */ SIZE_MAX,
462
                                ".profile",
463
                                "ID", &profile_id,
464
                                "TITLE", &profile_title);
465
                if (r < 0)
×
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✔
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✔
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✔
493
                return log_error_errno(write_fd, "Failed to create '%s': %m", filename);
×
494

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✔
500
                _title = strjoin(c->entry_title ?: good_name, " (", profile_title ?: profile_id, ")");
×
501
                if (!_title)
×
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);
×
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✔
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✔
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✔
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✔
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)
547
                        return log_oom();
×
548
        }
549

550
        r = loop_write(write_fd, text, /* nbytes= */ SIZE_MAX);
10✔
551
        if (r < 0)
10✔
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✔
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✔
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✔
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

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

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) {
×
621
                        log_debug_errno(r, "Cannot extract entry token/commit number from '%s', ignoring.", de->d_name);
×
622
                        continue;
×
623
                }
624

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

628
                log_debug("Found existing commit %" PRIu64 ".", ec);
×
629
                if (ec > m)
×
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✔
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✔
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✔
664
                return log_error_errno(r, "Failed to enumerate contents of directory: %m");
×
665

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

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

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

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);
10✔
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

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

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

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

725
        if (ret < 0)
×
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✔
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✔
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✔
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✔
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✔
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✔
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✔
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 */
960
                c->entry_token_dir_fd = safe_close(c->entry_token_dir_fd);
×
961

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

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

970
        if (n_removals > 0)
10✔
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

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

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

1019
        extra_file_done(&p->extra_file);
×
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✔
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✔
1037
                return json_log_oom(variant, flags);
×
1038

1039
        return 0;
1040
}
1041

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

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

1049
        sd_json_variant *i;
×
1050
        JSON_VARIANT_ARRAY_FOREACH(i, v) {
×
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

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

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

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

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

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

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)
×
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

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

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

1146
                        if (empty_or_root(p.context.root))
×
1147
                                p.context.root = mfree(p.context.root);
×
1148
                }
1149
        } else if (p.context.root) {
3✔
1150
                p.context.root_fd = open(p.context.root, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
×
1151
                if (p.context.root_fd < 0)
×
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✔
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✔
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✔
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✔
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✔
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✔
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
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✔
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