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

systemd / systemd / 21419361949

27 Jan 2026 02:53PM UTC coverage: 72.793% (-0.03%) from 72.821%
21419361949

push

github

keszybz
kernel-install: handle removal unsuccessful UKIs and loader entries separately

When a tries file exists, 90-uki-copy.install removes a previous UKI of the
same kernel version and all it's unbooted variants. This removal is guarded
behind a check for the existence of the already booted UKI, i.e. if uki.efi
already exists, uki.efi and uki+*.efi will be removed.

This leaves the edge case that if uki.efi does not exist, but only an unbooted,
e.g. uki+3.efi, it will not be removed. This is not a problem, if the number of
tries is constant between both builds, since a new uki+3.efi would overwrite
the existing one, but if the number of tries is changed to, e.g. uki+5.efi, we
are left with both uki+3.efi and uki+5.efi.

The same is done for loader entries.

311334 of 427698 relevant lines covered (72.79%)

1157141.18 hits per line

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

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

3
#include <getopt.h>
4
#include <stdlib.h>
5
#include <sys/utsname.h>
6
#include <unistd.h>
7

8
#include "argv-util.h"
9
#include "boot-entry.h"
10
#include "bootspec.h"
11
#include "build.h"
12
#include "chase.h"
13
#include "conf-files.h"
14
#include "dirent-util.h"
15
#include "dissect-image.h"
16
#include "env-file.h"
17
#include "env-util.h"
18
#include "exec-util.h"
19
#include "extract-word.h"
20
#include "fd-util.h"
21
#include "fileio.h"
22
#include "find-esp.h"
23
#include "format-table.h"
24
#include "fs-util.h"
25
#include "id128-util.h"
26
#include "image-policy.h"
27
#include "kernel-config.h"
28
#include "kernel-image.h"
29
#include "loop-util.h"
30
#include "main-func.h"
31
#include "mount-util.h"
32
#include "parse-argument.h"
33
#include "path-util.h"
34
#include "pretty-print.h"
35
#include "recurse-dir.h"
36
#include "rm-rf.h"
37
#include "stat-util.h"
38
#include "string-table.h"
39
#include "string-util.h"
40
#include "strv.h"
41
#include "tmpfile-util.h"
42
#include "verbs.h"
43

44
static bool arg_verbose = false;
45
static char *arg_esp_path = NULL;
46
static char *arg_xbootldr_path = NULL;
47
static int arg_make_entry_directory = -1; /* tristate */
48
static PagerFlags arg_pager_flags = 0;
49
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
50
static char *arg_root = NULL;
51
static char *arg_image = NULL;
52
static ImagePolicy *arg_image_policy = NULL;
53
static bool arg_legend = true;
54

55
STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep);
×
56
STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep);
×
57
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
×
58
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
×
59
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
×
60

61
typedef enum Action {
62
        ACTION_ADD,
63
        ACTION_REMOVE,
64
        ACTION_INSPECT,
65
        _ACTION_MAX,
66
        _ACTION_INVALID = -EINVAL,
67
} Action;
68

69
typedef enum Layout {
70
        LAYOUT_AUTO,
71
        LAYOUT_UKI,
72
        LAYOUT_BLS,
73
        LAYOUT_OTHER,
74
        _LAYOUT_MAX,
75
        _LAYOUT_INVALID = -EINVAL,
76
} Layout;
77

78
static const char * const layout_table[_LAYOUT_MAX] = {
79
        [LAYOUT_AUTO]  = "auto",
80
        [LAYOUT_UKI]   = "uki",
81
        [LAYOUT_BLS]   = "bls",
82
        [LAYOUT_OTHER] = "other",
83
};
84

85
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(layout, Layout);
×
86

87
typedef struct Context {
88
        int rfd;
89
        Action action;
90
        sd_id128_t machine_id;
91
        bool machine_id_is_random;
92
        BootEntryType entry_type;
93
        KernelImageType kernel_image_type;
94
        Layout layout;
95
        char *layout_other;
96
        char *conf_root;
97
        char *boot_root;
98
        BootEntryTokenType entry_token_type;
99
        char *entry_token;
100
        char *entry_dir;
101
        char *version;
102
        char *kernel;
103
        char **initrds;
104
        char *initrd_generator;
105
        char *uki_generator;
106
        char *staging_area;
107
        char **plugins;
108
        char **argv;
109
        char **envp;
110
} Context;
111

112
#define CONTEXT_NULL (Context) { .rfd = -EBADF }
113

114
static void context_done(Context *c) {
×
115
        assert(c);
×
116

117
        free(c->layout_other);
×
118
        free(c->conf_root);
×
119
        free(c->boot_root);
×
120
        free(c->entry_token);
×
121
        free(c->entry_dir);
×
122
        free(c->version);
×
123
        free(c->kernel);
×
124
        strv_free(c->initrds);
×
125
        free(c->initrd_generator);
×
126
        free(c->uki_generator);
×
127
        if (c->action == ACTION_INSPECT)
×
128
                free(c->staging_area);
×
129
        else
130
                rm_rf_physical_and_free(c->staging_area);
×
131
        strv_free(c->plugins);
×
132
        strv_free(c->argv);
×
133
        strv_free(c->envp);
×
134

135
        safe_close(c->rfd);
×
136
}
×
137

138
static int context_copy(const Context *source, Context *ret) {
×
139
        int r;
×
140

141
        assert(source);
×
142
        assert(ret);
×
143
        assert(source->rfd >= 0 || source->rfd == AT_FDCWD);
×
144

145
        _cleanup_(context_done) Context copy = (Context) {
×
146
                .rfd = AT_FDCWD,
147
                .action = source->action,
×
148
                .machine_id = source->machine_id,
149
                .machine_id_is_random = source->machine_id_is_random,
×
150
                .kernel_image_type = source->kernel_image_type,
×
151
                .layout = source->layout,
×
152
                .entry_token_type = source->entry_token_type,
×
153
        };
154

155
        if (source->rfd >= 0) {
×
156
                copy.rfd = fd_reopen(source->rfd, O_CLOEXEC|O_DIRECTORY|O_PATH);
×
157
                if (copy.rfd < 0)
×
158
                        return copy.rfd;
159
        }
160

161
        r = strdup_to(&copy.layout_other, source->layout_other);
×
162
        if (r < 0)
×
163
                return r;
164
        r = strdup_to(&copy.conf_root, source->conf_root);
×
165
        if (r < 0)
×
166
                return r;
167
        r = strdup_to(&copy.boot_root, source->boot_root);
×
168
        if (r < 0)
×
169
                return r;
170
        r = strdup_to(&copy.entry_token, source->entry_token);
×
171
        if (r < 0)
×
172
                return r;
173
        r = strdup_to(&copy.entry_dir, source->entry_dir);
×
174
        if (r < 0)
×
175
                return r;
176
        r = strdup_to(&copy.version, source->version);
×
177
        if (r < 0)
×
178
                return r;
179
        r = strdup_to(&copy.kernel, source->kernel);
×
180
        if (r < 0)
×
181
                return r;
182
        r = strv_copy_unless_empty(source->initrds, &copy.initrds);
×
183
        if (r < 0)
×
184
                return r;
185
        r = strdup_to(&copy.initrd_generator, source->initrd_generator);
×
186
        if (r < 0)
×
187
                return r;
188
        r = strdup_to(&copy.uki_generator, source->uki_generator);
×
189
        if (r < 0)
×
190
                return r;
191
        r = strdup_to(&copy.staging_area, source->staging_area);
×
192
        if (r < 0)
×
193
                return r;
194
        r = strv_copy_unless_empty(source->plugins, &copy.plugins);
×
195
        if (r < 0)
×
196
                return r;
197
        r = strv_copy_unless_empty(source->argv, &copy.argv);
×
198
        if (r < 0)
×
199
                return r;
200
        r = strv_copy_unless_empty(source->envp, &copy.envp);
×
201
        if (r < 0)
×
202
                return r;
203

204
        *ret = copy;
×
205
        copy = CONTEXT_NULL;
×
206

207
        return 0;
×
208
}
209

210
static int context_open_root(Context *c) {
×
211
        int r;
×
212

213
        assert(c);
×
214
        assert(c->rfd < 0);
×
215

216
        if (isempty(arg_root))
×
217
                return 0;
218

219
        r = path_is_root(arg_root);
×
220
        if (r < 0)
×
221
                return log_error_errno(r, "Failed to determine if '%s' is the root directory: %m", arg_root);
×
222
        if (r > 0)
×
223
                return 0;
224

225
        c->rfd = open(arg_root, O_CLOEXEC | O_DIRECTORY | O_PATH);
×
226
        if (c->rfd < 0)
×
227
                return log_error_errno(errno, "Failed to open root directory '%s': %m", arg_root);
×
228

229
        return 0;
230
}
231

232
static const char* context_get_layout(const Context *c) {
×
233
        assert(c);
×
234
        assert(c->layout >= 0);
×
235

236
        return c->layout_other ?: layout_to_string(c->layout);
×
237
}
238

239
static int context_set_layout(Context *c, const char *s, const char *source) {
×
240
        Layout t;
×
241

242
        assert(c);
×
243
        assert(source);
×
244

245
        if (c->layout >= 0 || !s)
×
246
                return 0;
247

248
        assert(!c->layout_other);
×
249

250
        t = layout_from_string(s);
×
251
        if (t >= 0)
×
252
                c->layout = t;
×
253
        else if (isempty(s))
×
254
                c->layout = LAYOUT_AUTO;
×
255
        else {
256
                c->layout_other = strdup(s);
×
257
                if (!c->layout_other)
×
258
                        return log_oom();
×
259

260
                c->layout = LAYOUT_OTHER;
×
261
        }
262

263
        log_debug("layout=%s set via %s", context_get_layout(c), source);
×
264
        return 1;
265
}
266

267
static int context_set_machine_id(Context *c, const char *s, const char *source) {
×
268
        int r;
×
269

270
        assert(c);
×
271
        assert(source);
×
272

273
        if (!sd_id128_is_null(c->machine_id) || !s)
×
274
                return 0;
×
275

276
        r = sd_id128_from_string(s, &c->machine_id);
×
277
        if (r < 0)
×
278
                return log_warning_errno(r, "Failed to parse machine ID specified via %s, ignoring.", source);
×
279

280
        if (sd_id128_is_null(c->machine_id))
×
281
                return 0;
×
282

283
        log_debug("MACHINE_ID=%s set via %s.", SD_ID128_TO_STRING(c->machine_id), source);
×
284
        return 1;
×
285
}
286

287
static int context_set_string(const char *s, const char *source, const char *name, char **dest) {
×
288
        char *p;
×
289

290
        assert(source);
×
291
        assert(name);
×
292
        assert(dest);
×
293

294
        if (*dest || !s)
×
295
                return 0;
296

297
        p = strdup(s);
×
298
        if (!p)
×
299
                return log_oom();
×
300

301
        log_debug("%s (%s) set via %s.", name, p, source);
×
302

303
        *dest = p;
×
304
        return 1;
×
305
}
306

307
static int context_set_initrd_generator(Context *c, const char *s, const char *source) {
×
308
        assert(c);
×
309
        return context_set_string(s, source, "INITRD_GENERATOR", &c->initrd_generator);
×
310
}
311

312
static int context_set_uki_generator(Context *c, const char *s, const char *source) {
×
313
        assert(c);
×
314
        return context_set_string(s, source, "UKI_GENERATOR", &c->uki_generator);
×
315
}
316

317
static int context_set_version(Context *c, const char *s) {
×
318
        assert(c);
×
319

320
        if (s && !filename_is_valid(s))
×
321
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid version specified: %s", s);
×
322

323
        return context_set_string(s, "command line", "kernel version", &c->version);
×
324
}
325

326
static int context_set_path(Context *c, const char *s, const char *source, const char *name, char **dest) {
×
327
        char *p;
×
328
        int r;
×
329

330
        assert(c);
×
331
        assert(source);
×
332
        assert(name);
×
333
        assert(dest);
×
334

335
        if (*dest || !s)
×
336
                return 0;
×
337

338
        if (c->rfd >= 0) {
×
339
                r = chaseat(c->rfd, s, CHASE_AT_RESOLVE_IN_ROOT, &p, /* ret_fd= */ NULL);
×
340
                if (r < 0)
×
341
                        return log_warning_errno(r, "Failed to chase path %s for %s specified via %s, ignoring: %m",
×
342
                                                 s, name, source);
343
        } else {
344
                r = path_make_absolute_cwd(s, &p);
×
345
                if (r < 0)
×
346
                        return log_warning_errno(r, "Failed to make path '%s' for %s specified via %s absolute, ignoring: %m",
×
347
                                                 s, name, source);
348
        }
349

350
        log_debug("%s (%s) set via %s.", name, p, source);
×
351

352
        *dest = p;
×
353
        return 1;
×
354
}
355

356
static int context_set_boot_root(Context *c, const char *s, const char *source) {
×
357
        assert(c);
×
358
        return context_set_path(c, s, source, "BOOT_ROOT", &c->boot_root);
×
359
}
360

361
static int context_set_conf_root(Context *c, const char *s, const char *source) {
×
362
        assert(c);
×
363
        return context_set_path(c, s, source, "CONF_ROOT", &c->conf_root);
×
364
}
365

366
static int context_set_kernel(Context *c, const char *s) {
×
367
        assert(c);
×
368
        return context_set_path(c, s, "command line", "kernel image file", &c->kernel);
×
369
}
370

371
static int context_set_path_strv(Context *c, char* const* strv, const char *source, const char *name, char ***dest) {
×
372
        _cleanup_strv_free_ char **w = NULL;
×
373
        int r;
×
374

375
        assert(c);
×
376
        assert(source);
×
377
        assert(name);
×
378
        assert(dest);
×
379

380
        if (*dest)
×
381
                return 0;
382

383
        STRV_FOREACH(s, strv) {
×
384
                char *p;
×
385

386
                if (c->rfd >= 0) {
×
387
                        r = chaseat(c->rfd, *s, CHASE_AT_RESOLVE_IN_ROOT, &p, /* ret_fd= */ NULL);
×
388
                        if (r < 0)
×
389
                                return log_warning_errno(r, "Failed to chase path %s for %s specified via %s: %m",
×
390
                                                         *s, name, source);
391
                } else {
392
                        r = path_make_absolute_cwd(*s, &p);
×
393
                        if (r < 0)
×
394
                                return log_warning_errno(r, "Failed to make path '%s' for %s specified via %s absolute, ignoring: %m",
×
395
                                                         *s, name, source);
396
                }
397
                r = strv_consume(&w, p);
×
398
                if (r < 0)
×
399
                        return log_oom();
×
400
        }
401

402
        if (strv_isempty(w))
×
403
                return 0;
404

405
        log_debug("%s set via %s", name, source);
×
406

407
        *dest = TAKE_PTR(w);
×
408
        return 1;
×
409
}
410

411
static int context_set_plugins(Context *c, const char *s, const char *source) {
×
412
        _cleanup_strv_free_ char **v = NULL;
×
413
        int r;
×
414

415
        assert(c);
×
416

417
        if (c->plugins || !s)
×
418
                return 0;
419

420
        r = strv_split_full(&v, s, NULL, EXTRACT_UNQUOTE);
×
421
        if (r < 0)
×
422
                return log_error_errno(r, "Failed to parse plugin paths from %s: %m", source);
×
423

424
        return context_set_path_strv(c, v, source, "plugins", &c->plugins);
×
425
}
426

427
static int context_set_initrds(Context *c, char* const* strv) {
×
428
        assert(c);
×
429
        return context_set_path_strv(c, strv, "command line", "initrds", &c->initrds);
×
430
}
431

432
static int context_set_entry_type(Context *c, const char *s) {
×
433
        assert(c);
×
434
        BootEntryType e;
×
435
        if (isempty(s) || streq(s, "all")) {
×
436
                c->entry_type = _BOOT_ENTRY_TYPE_INVALID;
×
437
                return 0;
×
438
        }
439
        e = boot_entry_type_from_string(s);
×
440
        if (e < 0)
×
441
                return log_error_errno(e, "Invalid entry type: %s", s);
×
442
        c->entry_type = e;
×
443
        return 1;
×
444
}
445

446
static int context_load_environment(Context *c) {
×
447
        assert(c);
×
448

449
        (void) context_set_machine_id(c, getenv("MACHINE_ID"), "environment");
×
450
        (void) context_set_boot_root(c, getenv("BOOT_ROOT"), "environment");
×
451
        (void) context_set_conf_root(c, getenv("KERNEL_INSTALL_CONF_ROOT"), "environment");
×
452
        (void) context_set_plugins(c, getenv("KERNEL_INSTALL_PLUGINS"), "environment");
×
453
        return 0;
×
454
}
455

456
static int context_load_install_conf(Context *c) {
×
457
        _cleanup_free_ char *machine_id = NULL, *boot_root = NULL, *layout = NULL,
×
458
                            *initrd_generator = NULL, *uki_generator = NULL;
×
459
        int r;
×
460

461
        assert(c);
×
462

463
        r = load_kernel_install_conf_at(
×
464
                        c->conf_root ? NULL : arg_root,
465
                        c->conf_root ? XAT_FDROOT : c->rfd,
466
                        c->conf_root,
×
467
                        &machine_id,
468
                        &boot_root,
469
                        &layout,
470
                        &initrd_generator,
471
                        &uki_generator);
472
        if (r <= 0)
×
473
                return r;
474

475
        (void) context_set_machine_id(c, machine_id, "config");
×
476
        (void) context_set_boot_root(c, boot_root, "config");
×
477
        (void) context_set_layout(c, layout, "config");
×
478
        (void) context_set_initrd_generator(c, initrd_generator, "config");
×
479
        (void) context_set_uki_generator(c, uki_generator, "config");
×
480

481
        log_debug("Loaded config.");
×
482
        return 0;
483
}
484

485
static int context_load_machine_info(Context *c) {
×
486
        _cleanup_fclose_ FILE *f = NULL;
×
487
        _cleanup_free_ char *machine_id = NULL, *layout = NULL;
×
488
        static const char *path = "/etc/machine-info";
×
489
        int r;
×
490

491
        assert(c);
×
492

493
        /* If the user configured an explicit machine ID in /etc/machine-info to use for our purpose, we'll
494
         * use that instead (for compatibility). */
495

496
        if (!sd_id128_is_null(c->machine_id) && c->layout >= 0)
×
497
                return 0;
498

499
        /* For testing. To make not read host's /etc/machine-info. */
500
        r = getenv_bool("KERNEL_INSTALL_READ_MACHINE_INFO");
×
501
        if (r < 0 && r != -ENXIO)
×
502
                log_warning_errno(r, "Failed to read $KERNEL_INSTALL_READ_MACHINE_INFO, assuming yes: %m");
×
503
        if (r == 0) {
×
504
                log_debug("Skipping reading of /etc/machine-info.");
×
505
                return 0;
×
506
        }
507

508
        r = chase_and_fopenat_unlocked(c->rfd, path, CHASE_AT_RESOLVE_IN_ROOT, "re", NULL, &f);
×
509
        if (r == -ENOENT)
×
510
                return 0;
511
        if (r < 0)
×
512
                return log_error_errno(r, "Failed to chase %s: %m", path);
×
513

514
        log_debug("Loading %s…", path);
×
515

516
        r = parse_env_file(f, path,
×
517
                           "KERNEL_INSTALL_MACHINE_ID", &machine_id,
518
                           "KERNEL_INSTALL_LAYOUT", &layout);
519
        if (r < 0)
×
520
                return log_error_errno(r, "Failed to parse '%s': %m", path);
×
521

522
        (void) context_set_machine_id(c, machine_id, path);
×
523
        (void) context_set_layout(c, layout, path);
×
524
        return 0;
525
}
526

527
static int context_load_machine_id(Context *c) {
×
528
        int r;
×
529

530
        assert(c);
×
531

532
        r = id128_get_machine_at(c->rfd, &c->machine_id);
×
533
        if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r))
×
534
                return 0;
535
        if (r < 0)
×
536
                return log_error_errno(r, "Failed to load machine ID from /etc/machine-id: %m");
×
537

538
        log_debug("MACHINE_ID=%s set via /etc/machine-id.", SD_ID128_TO_STRING(c->machine_id));
×
539
        return 1; /* loaded */
×
540
}
541

542
static int context_ensure_machine_id(Context *c) {
×
543
        int r;
×
544

545
        assert(c);
×
546

547
        if (!sd_id128_is_null(c->machine_id))
×
548
                return 0;
×
549

550
        /* If /etc/machine-id is initialized we'll use it. */
551
        r = context_load_machine_id(c);
×
552
        if (r != 0)
×
553
                return r;
554

555
        /* Otherwise we'll use a freshly generated one. */
556
        r = sd_id128_randomize(&c->machine_id);
×
557
        if (r < 0)
×
558
                return log_error_errno(r, "Failed to generate random ID: %m");
×
559

560
        c->machine_id_is_random = true;
×
561
        log_debug("New machine ID '%s' generated.", SD_ID128_TO_STRING(c->machine_id));
×
562
        return 0;
×
563
}
564

565
static int context_acquire_xbootldr(Context *c) {
×
566
        int r;
×
567

568
        assert(c);
×
569
        assert(!c->boot_root);
×
570

571
        r = find_xbootldr_and_warn_at(
×
572
                        /* rfd= */ c->rfd,
573
                        /* path= */ arg_xbootldr_path,
574
                        /* unprivileged_mode= */ -1,
575
                        /* ret_path= */ &c->boot_root,
576
                        /* ret_uuid= */ NULL,
577
                        /* ret_devid= */ NULL);
578
        if (r == -ENOKEY) {
×
579
                log_debug_errno(r, "Couldn't find an XBOOTLDR partition.");
×
580
                return 0;
×
581
        }
582
        if (r == -EACCES && geteuid() != 0)
×
583
                return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m");
×
584
        if (r < 0)
×
585
                return r;
586

587
        log_debug("Using XBOOTLDR partition at %s as $BOOT_ROOT.", c->boot_root);
×
588
        return 1; /* found */
589
}
590

591
static int context_acquire_esp(Context *c) {
×
592
        int r;
×
593

594
        assert(c);
×
595
        assert(!c->boot_root);
×
596

597
        r = find_esp_and_warn_at(
×
598
                        /* rfd= */ c->rfd,
599
                        /* path= */ arg_esp_path,
600
                        /* unprivileged_mode= */ -1,
601
                        /* ret_path= */ &c->boot_root,
602
                        /* ret_part= */ NULL,
603
                        /* ret_pstart= */ NULL,
604
                        /* ret_psize= */ NULL,
605
                        /* ret_uuid= */ NULL,
606
                        /* ret_devid= */ NULL);
607
        if (r == -ENOKEY) {
×
608
                log_debug_errno(r, "Couldn't find EFI system partition, ignoring.");
×
609
                return 0;
×
610
        }
611
        if (r == -EACCES && geteuid() != 0)
×
612
                return log_error_errno(r, "Failed to determine EFI system partition: %m");
×
613
        if (r < 0)
×
614
                return r;
615

616
        log_debug("Using EFI System Partition at %s as $BOOT_ROOT.", c->boot_root);
×
617
        return 1; /* found */
618
}
619

620
static int context_ensure_boot_root(Context *c) {
×
621
        int r;
×
622

623
        assert(c);
×
624

625
        /* If BOOT_ROOT is specified via environment or install.conf, then use it. */
626
        if (c->boot_root)
×
627
                return 0;
628

629
        /* Otherwise, use XBOOTLDR partition, if mounted. */
630
        r = context_acquire_xbootldr(c);
×
631
        if (r != 0)
×
632
                return r;
633

634
        /* Otherwise, use EFI system partition, if mounted. */
635
        r = context_acquire_esp(c);
×
636
        if (r != 0)
×
637
                return r;
638

639
        /* If all else fails, use /boot. */
640
        if (c->rfd >= 0) {
×
641
                r = chaseat(c->rfd, "/boot", CHASE_AT_RESOLVE_IN_ROOT, &c->boot_root, /* ret_fd= */ NULL);
×
642
                if (r < 0)
×
643
                        return log_error_errno(r, "Failed to chase '/boot/': %m");
×
644
        } else {
645
                c->boot_root = strdup("/boot");
×
646
                if (!c->boot_root)
×
647
                        return log_oom();
×
648
        }
649

650
        log_debug("KERNEL_INSTALL_BOOT_ROOT autodetection yielded no candidates, using \"%s\".", c->boot_root);
×
651
        return 0;
652
}
653

654
static int context_ensure_entry_token(Context *c) {
×
655
        int r;
×
656

657
        assert(c);
×
658

659
        /* Now that we determined the machine ID to use, let's determine the "token" for the boot loader
660
         * entry to generate. We use that for naming the directory below $BOOT where we want to place the
661
         * kernel/initrd and related resources, as well for naming the .conf boot loader spec entry.
662
         * Typically this is just the machine ID, but it can be anything else, too, if we are told so. */
663

664
        r = boot_entry_token_ensure_at(
×
665
                        c->rfd,
666
                        c->conf_root,
×
667
                        c->machine_id,
668
                        c->machine_id_is_random,
×
669
                        &c->entry_token_type,
670
                        &c->entry_token);
671
        if (r < 0)
×
672
                return r;
673

674
        log_debug("Using entry token: %s", c->entry_token);
×
675
        return 0;
676
}
677

678
static int context_load_plugins(Context *c) {
×
679
        int r;
×
680

681
        assert(c);
×
682

683
        if (c->plugins)
×
684
                return 0;
×
685

686
        r = conf_files_list_strv_at(
×
687
                        &c->plugins,
688
                        ".install",
689
                        c->rfd,
690
                        CONF_FILES_EXECUTABLE | CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED | CONF_FILES_WARN,
691
                        STRV_MAKE_CONST("/etc/kernel/install.d", "/usr/lib/kernel/install.d"));
×
692
        if (r < 0)
×
693
                return log_error_errno(r, "Failed to find plugins: %m");
×
694

695
        return 0;
696
}
697

698
static int context_init(Context *c) {
×
699
        int r;
×
700

701
        assert(c);
×
702

703
        r = context_open_root(c);
×
704
        if (r < 0)
×
705
                return r;
706

707
        r = context_load_environment(c);
×
708
        if (r < 0)
×
709
                return r;
710

711
        r = context_load_install_conf(c);
×
712
        if (r < 0)
×
713
                return r;
714

715
        r = context_load_machine_info(c);
×
716
        if (r < 0)
×
717
                return r;
718

719
        r = context_ensure_machine_id(c);
×
720
        if (r < 0)
×
721
                return r;
722

723
        r = context_ensure_boot_root(c);
×
724
        if (r < 0)
×
725
                return r;
726

727
        r = context_ensure_entry_token(c);
×
728
        if (r < 0)
×
729
                return r;
730

731
        r = context_load_plugins(c);
×
732
        if (r < 0)
×
733
                return r;
×
734

735
        return 0;
736
}
737

738
static int context_inspect_kernel(Context *c) {
×
739
        assert(c);
×
740

741
        if (!c->kernel)
×
742
                return 0;
743

744
        return inspect_kernel(c->rfd, c->kernel, &c->kernel_image_type, NULL, NULL, NULL);
×
745
}
746

747
static int context_ensure_layout(Context *c) {
×
748
        int r;
×
749

750
        assert(c);
×
751
        assert(c->boot_root);
×
752
        assert(c->entry_token);
×
753

754
        if (c->layout >= 0 && c->layout != LAYOUT_AUTO)
×
755
                return 0;
×
756

757
        /* No layout configured by the administrator. Let's try to figure it out automatically from metadata
758
         * already contained in $BOOT_ROOT. */
759

760
        if (c->kernel_image_type == KERNEL_IMAGE_TYPE_UKI) {
×
761
                c->layout = LAYOUT_UKI;
×
762
                log_debug("Kernel image type is %s, using layout=%s.",
×
763
                          kernel_image_type_to_string(c->kernel_image_type), layout_to_string(c->layout));
764
                return 0;
×
765
        }
766

767
        _cleanup_free_ char *srel_path = path_join(c->boot_root, "loader/entries.srel");
×
768
        if (!srel_path)
×
769
                return log_oom();
×
770

771
        _cleanup_free_ char *srel = NULL;
×
772
        r = read_one_line_file_at(c->rfd, srel_path, &srel);
×
773
        if (r >= 0) {
×
774
                if (streq(srel, "type1"))
×
775
                        /* The loader/entries.srel file clearly indicates that the installed boot loader
776
                         * implements the proper standard upstream boot loader spec for Type #1 entries.
777
                         * Let's default to that, then. */
778
                        c->layout = LAYOUT_BLS;
×
779
                else
780
                        /* The loader/entries.srel file indicates some other spec is implemented and owns the
781
                         * /loader/entries/ directory. Since we have no idea what that means, let's stay away
782
                         * from it by default. */
783
                        c->layout = LAYOUT_OTHER;
×
784

785
                log_debug("%s with '%s' found, using layout=%s.", srel_path, srel, layout_to_string(c->layout));
×
786
                return 0;
×
787
        } else if (r != -ENOENT)
×
788
                return log_error_errno(r, "Failed to read %s: %m", srel_path);
×
789

790
        _cleanup_free_ char *entry_token_path = path_join(c->boot_root, c->entry_token);
×
791
        if (!entry_token_path)
×
792
                return log_oom();
×
793

794
        r = is_dir_at(c->rfd, entry_token_path, /* follow= */ false);
×
795
        if (r < 0 && r != -ENOENT)
×
796
                return log_error_errno(r, "Failed to check if '%s' is a directory: %m", entry_token_path);
×
797
        if (r > 0) {
×
798
                /* If the metadata in $BOOT_ROOT doesn't tell us anything, then check if the entry token
799
                 * directory already exists. If so, let's assume it's the standard boot loader spec, too. */
800
                c->layout = LAYOUT_BLS;
×
801
                log_debug("%s exists, using layout=%s.", entry_token_path, layout_to_string(c->layout));
×
802
                return 0;
×
803
        }
804

805
        /* There's no metadata in $BOOT_ROOT, and apparently no entry token directory installed? Then we
806
         * really don't know anything. */
807
        c->layout = LAYOUT_OTHER;
×
808
        log_debug("Entry-token directory %s not found, using layout=%s.",
×
809
                  entry_token_path,
810
                  layout_to_string(c->layout));
811
        return 0;
812
}
813

814
static int context_set_up_staging_area(Context *c) {
×
815
        int r;
×
816

817
        assert(c);
×
818

819
        if (c->staging_area)
×
820
                return 0;
×
821

822
        const char *d;
×
823
        r = var_tmp_dir(&d);
×
824
        if (r < 0)
×
825
                return log_error_errno(r, "Failed to determine temporary directory location: %m");
×
826

827
        _cleanup_free_ char *template = path_join(d, "kernel-install.staging.XXXXXX");
×
828
        if (!template)
×
829
                return log_oom();
×
830

831
        if (c->action == ACTION_INSPECT)
×
832
                /* This is only used for display. The directory will not be created. */
833
                c->staging_area = TAKE_PTR(template);
×
834
        else {
835
                r = mkdtemp_malloc(template, &c->staging_area);
×
836
                if (r < 0)
×
837
                        return log_error_errno(r, "Failed to create staging area: %m");
×
838
        }
839

840
        return 0;
841
}
842

843
static int context_build_entry_dir(Context *c) {
×
844
        assert(c);
×
845
        assert(c->boot_root);
×
846
        assert(c->entry_token);
×
847
        assert(c->version || c->action == ACTION_INSPECT);
×
848

849
        if (c->entry_dir)
×
850
                return 0;
851

852
        c->entry_dir = path_join(c->boot_root, c->entry_token, c->version ?: "KERNEL_VERSION");
×
853
        if (!c->entry_dir)
×
854
                return log_oom();
×
855

856
        log_debug("Using ENTRY_DIR=%s", c->entry_dir);
×
857
        return 0;
858
}
859

860
static bool context_should_make_entry_dir(Context *c) {
×
861
        assert(c);
×
862

863
        /* Compatibility with earlier versions that used the presence of $BOOT_ROOT/$ENTRY_TOKEN to signal to
864
         * 00-entry-directory to create $ENTRY_DIR to serve as the indication to use or to not use the BLS */
865

866
        if (arg_make_entry_directory < 0)
×
867
                return c->layout == LAYOUT_BLS;
×
868

869
        return arg_make_entry_directory;
×
870
}
871

872
static int context_make_entry_dir(Context *c) {
×
873
        _cleanup_close_ int fd = -EBADF;
×
874

875
        assert(c);
×
876
        assert(c->entry_dir);
×
877

878
        if (c->action != ACTION_ADD)
×
879
                return 0;
880

881
        if (!context_should_make_entry_dir(c))
×
882
                return 0;
883

884
        log_debug("mkdir -p %s", c->entry_dir);
×
885
        fd = chase_and_openat(c->rfd, c->entry_dir, CHASE_AT_RESOLVE_IN_ROOT | CHASE_MKDIR_0755,
×
886
                              O_CLOEXEC | O_CREAT | O_DIRECTORY | O_PATH, NULL);
887
        if (fd < 0)
×
888
                return log_error_errno(fd, "Failed to make directory '%s': %m", c->entry_dir);
×
889

890
        return 0;
891
}
892

893
static int context_remove_entry_dir(Context *c) {
×
894
        _cleanup_free_ char *p = NULL;
×
895
        _cleanup_close_ int fd = -EBADF;
×
896
        struct stat st;
×
897
        int r;
×
898

899
        assert(c);
×
900
        assert(c->entry_dir);
×
901

902
        if (c->action != ACTION_REMOVE)
×
903
                return 0;
904

905
        if (!context_should_make_entry_dir(c))
×
906
                return 0;
907

908
        log_debug("rm -rf %s", c->entry_dir);
×
909
        fd = chase_and_openat(c->rfd, c->entry_dir, CHASE_AT_RESOLVE_IN_ROOT, O_CLOEXEC | O_DIRECTORY, &p);
×
910
        if (fd < 0) {
×
911
                if (IN_SET(fd, -ENOTDIR, -ENOENT))
×
912
                        return 0;
913
                return log_debug_errno(fd, "Failed to chase and open %s, ignoring: %m", c->entry_dir);
×
914
        }
915

916
        if (fstat(fd, &st) < 0)
×
917
                return log_debug_errno(errno, "Failed to stat %s: %m", p);
×
918

919
        r = rm_rf_children(TAKE_FD(fd), REMOVE_PHYSICAL|REMOVE_MISSING_OK|REMOVE_CHMOD, &st);
×
920
        if (r < 0)
×
921
                log_debug_errno(r, "Failed to remove children of %s, ignoring: %m", p);
×
922

923
        if (unlinkat(c->rfd, p, AT_REMOVEDIR) < 0)
×
924
                log_debug_errno(errno, "Failed to remove %s, ignoring: %m", p);
×
925

926
        return 0;
927
}
928

929
static int context_build_arguments(Context *c) {
×
930
        _cleanup_strv_free_ char **a = NULL;
×
931
        const char *verb;
×
932
        int r;
×
933

934
        assert(c);
×
935
        assert(c->entry_dir);
×
936

937
        if (c->argv)
×
938
                return 0;
939

940
        switch (c->action) {
×
941
        case ACTION_ADD:
×
942
                assert(c->version);
×
943
                assert(c->kernel);
×
944
                verb = "add";
945
                break;
946

947
        case ACTION_REMOVE:
×
948
                assert(c->version);
×
949
                assert(!c->kernel);
×
950
                assert(!c->initrds);
×
951
                verb = "remove";
952
                break;
953

954
        case ACTION_INSPECT:
955
                verb = "add|remove";
956
                break;
957

958
        default:
×
959
                assert_not_reached();
×
960
        }
961

962
        a = strv_new("dummy-arg", /* to make strv_free() works for this variable. */
×
963
                     verb,
964
                     c->version ?: "KERNEL_VERSION",
965
                     c->entry_dir);
966
        if (!a)
×
967
                return log_oom();
×
968

969
        if (c->action == ACTION_ADD) {
×
970
                r = strv_extend(&a, c->kernel);
×
971
                if (r < 0)
×
972
                        return log_oom();
×
973

974
                r = strv_extend_strv(&a, c->initrds, /* filter_duplicates= */ false);
×
975
                if (r < 0)
×
976
                        return log_oom();
×
977

978
        } else if (c->action == ACTION_INSPECT) {
×
979
                r = strv_extend_many(
×
980
                                &a,
981
                                c->kernel ?: "[KERNEL_IMAGE]",
982
                                "[INITRD...]");
983
                if (r < 0)
×
984
                        return log_oom();
×
985
        }
986

987
        c->argv = TAKE_PTR(a);
×
988
        return 0;
×
989
}
990

991
static int context_build_environment(Context *c) {
×
992
        _cleanup_strv_free_ char **e = NULL;
×
993
        int r;
×
994

995
        assert(c);
×
996

997
        if (c->envp)
×
998
                return 0;
999

1000
        r = strv_env_assign_many(&e,
×
1001
                                 "LC_COLLATE",                      SYSTEMD_DEFAULT_LOCALE,
1002
                                 "KERNEL_INSTALL_VERBOSE",          one_zero(arg_verbose),
1003
                                 "KERNEL_INSTALL_IMAGE_TYPE",       kernel_image_type_to_string(c->kernel_image_type),
1004
                                 "KERNEL_INSTALL_MACHINE_ID",       SD_ID128_TO_STRING(c->machine_id),
1005
                                 "KERNEL_INSTALL_ENTRY_TOKEN",      c->entry_token,
1006
                                 "KERNEL_INSTALL_BOOT_ROOT",        c->boot_root,
1007
                                 "KERNEL_INSTALL_LAYOUT",           context_get_layout(c),
1008
                                 "KERNEL_INSTALL_INITRD_GENERATOR", strempty(c->initrd_generator),
1009
                                 "KERNEL_INSTALL_UKI_GENERATOR",    strempty(c->uki_generator),
1010
                                 "KERNEL_INSTALL_BOOT_ENTRY_TYPE",  boot_entry_type_to_string(c->entry_type),
1011
                                 "KERNEL_INSTALL_STAGING_AREA",     c->staging_area);
1012
        if (r < 0)
×
1013
                return log_error_errno(r, "Failed to build environment variables for plugins: %m");
×
1014

1015
        c->envp = TAKE_PTR(e);
×
1016
        return 0;
×
1017
}
1018

1019
static int context_prepare_execution(Context *c) {
×
1020
        int r;
×
1021

1022
        assert(c);
×
1023

1024
        r = context_inspect_kernel(c);
×
1025
        if (r < 0)
×
1026
                return r;
1027

1028
        r = context_ensure_layout(c);
×
1029
        if (r < 0)
×
1030
                return r;
1031

1032
        r = context_set_up_staging_area(c);
×
1033
        if (r < 0)
×
1034
                return r;
1035

1036
        r = context_build_entry_dir(c);
×
1037
        if (r < 0)
×
1038
                return r;
1039

1040
        r = context_build_arguments(c);
×
1041
        if (r < 0)
×
1042
                return r;
1043

1044
        r = context_build_environment(c);
×
1045
        if (r < 0)
×
1046
                return r;
×
1047

1048
        return 0;
1049
}
1050

1051
static int context_execute(Context *c) {
×
1052
        int r, ret;
×
1053

1054
        assert(c);
×
1055

1056
        r = context_make_entry_dir(c);
×
1057
        if (r < 0)
×
1058
                return r;
1059

1060
        if (DEBUG_LOGGING) {
×
1061
                _cleanup_free_ char *x = strv_join_full(c->plugins, "", "\n  ", /* escape_separator= */ false);
×
1062
                log_debug("Using plugins: %s", strna(x));
×
1063

1064
                _cleanup_free_ char *y = strv_join_full(c->envp, "", "\n  ", /* escape_separator= */ false);
×
1065
                log_debug("Plugin environment: %s", strna(y));
×
1066

1067
                _cleanup_free_ char *z = strv_join(strv_skip(c->argv, 1), " ");
×
1068
                log_debug("Plugin arguments: %s", strna(z));
×
1069
        }
1070

1071
        ret = execute_strv(
×
1072
                        "plugins",
1073
                        c->plugins,
×
1074
                        /* root= */ NULL,
1075
                        USEC_INFINITY,
1076
                        /* callbacks= */ NULL,
1077
                        /* callback_args= */ NULL,
1078
                        c->argv,
1079
                        c->envp,
1080
                        EXEC_DIR_SKIP_REMAINING);
1081

1082
        r = context_remove_entry_dir(c);
×
1083
        if (r < 0)
×
1084
                return r;
×
1085

1086
        /* This returns 0 on success, positive exit code on plugin failure, negative errno on other failures. */
1087
        return ret;
1088
}
1089

1090
static bool bypass(void) {
×
1091
        return should_bypass("KERNEL_INSTALL");
×
1092
}
1093

1094
static int do_add(
×
1095
                Context *c,
1096
                const char *version,
1097
                const char *kernel,
1098
                char **initrds) {
1099

1100
        int r;
×
1101

1102
        assert(c);
×
1103
        assert(version);
×
1104
        assert(kernel);
×
1105

1106
        r = context_set_version(c, version);
×
1107
        if (r < 0)
×
1108
                return r;
1109

1110
        r = context_set_kernel(c, kernel);
×
1111
        if (r < 0)
×
1112
                return r;
1113

1114
        r = context_set_initrds(c, initrds);
×
1115
        if (r < 0)
×
1116
                return r;
1117

1118
        r = context_prepare_execution(c);
×
1119
        if (r < 0)
×
1120
                return r;
1121

1122
        return context_execute(c);
×
1123
}
1124

1125
static int kernel_from_version(const char *version, char **ret_kernel) {
×
1126
        _cleanup_free_ char *vmlinuz = NULL;
×
1127
        int r;
×
1128

1129
        assert(version);
×
1130

1131
        vmlinuz = path_join("/usr/lib/modules/", version, "/vmlinuz");
×
1132
        if (!vmlinuz)
×
1133
                return log_oom();
×
1134

1135
        r = access_nofollow(vmlinuz, F_OK);
×
1136
        if (r == -ENOENT)
×
1137
                return log_error_errno(r, "Kernel image not installed to '%s', requiring manual kernel image path specification.", vmlinuz);
×
1138
        if (r < 0)
×
1139
                return log_error_errno(r, "Failed to determine if kernel image is installed to '%s': %m", vmlinuz);
×
1140

1141
        *ret_kernel = TAKE_PTR(vmlinuz);
×
1142
        return 0;
×
1143
}
1144

1145
static int verb_add(int argc, char *argv[], void *userdata) {
×
1146
        Context *c = ASSERT_PTR(userdata);
×
1147
        _cleanup_free_ char *vmlinuz = NULL;
×
1148
        const char *version, *kernel;
×
1149
        char **initrds;
×
1150
        struct utsname un;
×
1151
        int r;
×
1152

1153
        assert(argv);
×
1154

1155
        if (arg_root)
×
1156
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'add' does not support --root= or --image=.");
×
1157

1158
        if (bypass())
×
1159
                return 0;
1160

1161
        c->action = ACTION_ADD;
×
1162

1163
        /* We use the same order of arguments that "inspect" introduced, i.e. if only on argument is
1164
         * specified we take it as the kernel path, not the version, i.e. it's the first argument that is
1165
         * optional, not the 2nd. */
1166
        version = argc > 2 ? empty_or_dash_to_null(argv[1]) : NULL;
×
1167
        kernel = argc > 2 ? empty_or_dash_to_null(argv[2]) :
×
1168
                (argc > 1 ? empty_or_dash_to_null(argv[1]) : NULL);
×
1169
        initrds = strv_skip(argv, 3);
×
1170

1171
        if (!version) {
×
1172
                assert_se(uname(&un) >= 0);
×
1173
                version = un.release;
1174
        }
1175

1176
        if (!kernel) {
×
1177
                r = kernel_from_version(version, &vmlinuz);
×
1178
                if (r < 0)
×
1179
                        return r;
1180

1181
                kernel = vmlinuz;
×
1182
        }
1183

1184
        return do_add(c, version, kernel, initrds);
×
1185
}
1186

1187
static int verb_add_all(int argc, char *argv[], void *userdata) {
×
1188
        Context *c = ASSERT_PTR(userdata);
×
1189
        _cleanup_close_ int fd = -EBADF;
×
1190
        size_t n = 0;
×
1191
        int ret = 0, r;
×
1192

1193
        assert(argv);
×
1194

1195
        if (arg_root)
×
1196
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'add-all' does not support --root= or --image=.");
×
1197

1198
        if (bypass())
×
1199
                return 0;
1200

1201
        c->action = ACTION_ADD;
×
1202

1203
        fd = chase_and_openat(c->rfd, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL);
×
1204
        if (fd < 0)
×
1205
                return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root));
×
1206

1207
        _cleanup_free_ DirectoryEntries *de = NULL;
×
1208
        r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
×
1209
        if (r < 0)
×
1210
                return log_error_errno(r, "Failed to numerate /usr/lib/modules/ contents: %m");
×
1211

1212
        FOREACH_ARRAY(d, de->entries, de->n_entries) {
×
1213
                r = dirent_ensure_type(fd, *d);
×
1214
                if (r < 0) {
×
1215
                        if (r != -ENOENT) /* don't log if just gone by now */
×
1216
                                log_debug_errno(r, "Failed to check if '%s/usr/lib/modules/%s' is a directory, ignoring: %m", strempty(arg_root), (*d)->d_name);
×
1217
                        continue;
×
1218
                }
1219

1220
                if ((*d)->d_type != DT_DIR)
×
1221
                        continue;
×
1222

1223
                _cleanup_free_ char *fn = path_join((*d)->d_name, "vmlinuz");
×
1224
                if (!fn)
×
1225
                        return log_oom();
×
1226

1227
                if (faccessat(fd, fn, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
×
1228
                        if (errno != ENOENT)
×
1229
                                log_debug_errno(errno, "Failed to check if '%s/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", strempty(arg_root), (*d)->d_name);
×
1230

1231
                        log_notice("Not adding version '%s', because kernel image not found.", (*d)->d_name);
×
1232
                        continue;
×
1233
                }
1234

1235
                _cleanup_(context_done) Context copy = CONTEXT_NULL;
×
1236

1237
                r = context_copy(c, &copy);
×
1238
                if (r < 0)
×
1239
                        return log_error_errno(r, "Failed to copy execution context: %m");
×
1240

1241
                /* do_add() will look up the path in the correct root directory so we don't need to prefix it
1242
                 * with arg_root here. */
1243
                _cleanup_free_ char *full = path_join("/usr/lib/modules/", fn);
×
1244
                if (!full)
×
1245
                        return log_oom();
×
1246

1247
                r = do_add(&copy,
×
1248
                           /* version= */ (*d)->d_name,
×
1249
                           /* kernel= */ full,
1250
                           /* initrds= */ NULL);
1251
                if (r == 0)
×
1252
                        n++;
×
1253
                else if (ret == 0)
×
1254
                        ret = r;
×
1255
        }
1256

1257
        if (n > 0)
×
1258
                log_debug("Installed %zu kernel(s).", n);
×
1259
        else if (ret == 0)
×
1260
                ret = log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No kernels to install found.");
×
1261

1262
        return ret;
1263
}
1264

1265
static int run_as_installkernel(int argc, char *argv[], Context *c) {
×
1266
        /* kernel's install.sh invokes us as
1267
         *   /sbin/installkernel <version> <vmlinuz> <map> <installation-dir>
1268
         * We ignore the last two arguments. */
1269
        if (optind + 2 > argc)
×
1270
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "'installkernel' command requires at least two arguments.");
×
1271

1272
        return verb_add(3, STRV_MAKE("add", argv[optind], argv[optind+1]), c);
×
1273
}
1274

1275
static int verb_remove(int argc, char *argv[], void *userdata) {
×
1276
        Context *c = ASSERT_PTR(userdata);
×
1277
        int r;
×
1278

1279
        assert(argc >= 2);
×
1280
        assert(argv);
×
1281

1282
        if (arg_root)
×
1283
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'remove' does not support --root= or --image=.");
×
1284

1285
        if (argc > 2)
×
1286
                log_debug("Too many arguments specified. 'kernel-install remove' takes only kernel version. "
×
1287
                          "Ignoring residual arguments.");
1288

1289
        if (bypass())
×
1290
                return 0;
1291

1292
        c->action = ACTION_REMOVE;
×
1293

1294
        /* Note, we do not automatically derive the kernel version to remove from uname() here (unlike we do
1295
         * it for the "add" verb), since we don't want to make it too easy to uninstall your running
1296
         * kernel, as a safety precaution */
1297

1298
        r = context_set_version(c, argv[1]);
×
1299
        if (r < 0)
×
1300
                return r;
1301

1302
        r = context_prepare_execution(c);
×
1303
        if (r < 0)
×
1304
                return r;
1305

1306
        return context_execute(c);
×
1307
}
1308

1309
static int verb_inspect(int argc, char *argv[], void *userdata) {
×
1310
        Context *c = ASSERT_PTR(userdata);
×
1311
        _cleanup_(table_unrefp) Table *t = NULL;
×
1312
        _cleanup_free_ char *vmlinuz = NULL;
×
1313
        const char *version, *kernel;
×
1314
        char **initrds;
×
1315
        struct utsname un;
×
1316
        int r;
×
1317

1318
        c->action = ACTION_INSPECT;
×
1319

1320
        /* When only a single parameter is specified 'inspect' it's the kernel image path, and not the kernel
1321
         * version. i.e. it's the first argument that is optional, not the 2nd. That's a bit unfortunate, but
1322
         * we keep the behaviour for compatibility. If users want to specify only the version (and have the
1323
         * kernel image path derived automatically), then they may specify an empty string or "dash" as
1324
         * kernel image path. */
1325
        version = argc > 2 ? empty_or_dash_to_null(argv[1]) : NULL;
×
1326
        kernel = argc > 2 ? empty_or_dash_to_null(argv[2]) :
×
1327
                (argc > 1 ? empty_or_dash_to_null(argv[1]) : NULL);
×
1328
        initrds = strv_skip(argv, 3);
×
1329

1330
        if (!version && !arg_root) {
×
1331
                assert_se(uname(&un) >= 0);
×
1332
                version = un.release;
1333
        }
1334

1335
        if (!kernel && version) {
×
1336
                r = kernel_from_version(version, &vmlinuz);
×
1337
                if (r < 0)
×
1338
                        return r;
1339

1340
                kernel = vmlinuz;
×
1341
        }
1342

1343
        r = context_set_version(c, version);
×
1344
        if (r < 0)
×
1345
                return r;
1346

1347
        r = context_set_kernel(c, kernel);
×
1348
        if (r < 0)
×
1349
                return r;
1350

1351
        r = context_set_initrds(c, initrds);
×
1352
        if (r < 0)
×
1353
                return r;
1354

1355
        r = context_prepare_execution(c);
×
1356
        if (r < 0)
×
1357
                return r;
1358

1359
        t = table_new_vertical();
×
1360
        if (!t)
×
1361
                return log_oom();
×
1362

1363
        r = table_add_many(t,
×
1364
                           TABLE_FIELD, "Machine ID",
1365
                           TABLE_ID128, c->machine_id,
1366
                           TABLE_FIELD, "Kernel Image Type",
1367
                           TABLE_STRING, kernel_image_type_to_string(c->kernel_image_type),
1368
                           TABLE_FIELD, "Layout",
1369
                           TABLE_STRING, context_get_layout(c),
1370
                           TABLE_FIELD, "Boot Root",
1371
                           TABLE_STRING, c->boot_root,
1372
                           TABLE_FIELD, "Entry Token Type",
1373
                           TABLE_STRING, boot_entry_token_type_to_string(c->entry_token_type),
1374
                           TABLE_FIELD, "Entry Token",
1375
                           TABLE_STRING, c->entry_token,
1376
                           TABLE_FIELD, "Entry Directory",
1377
                           TABLE_STRING, c->entry_dir,
1378
                           TABLE_FIELD, "Kernel Version",
1379
                           TABLE_VERSION, c->version,
1380
                           TABLE_FIELD, "Kernel",
1381
                           TABLE_STRING, c->kernel,
1382
                           TABLE_FIELD, "Initrds",
1383
                           TABLE_STRV, c->initrds,
1384
                           TABLE_FIELD, "Initrd Generator",
1385
                           TABLE_STRING, c->initrd_generator,
1386
                           TABLE_FIELD, "UKI Generator",
1387
                           TABLE_STRING, c->uki_generator,
1388
                           TABLE_FIELD, "Plugins",
1389
                           TABLE_STRV, c->plugins,
1390
                           TABLE_FIELD, "Plugin Environment",
1391
                           TABLE_STRV, c->envp);
1392
        if (r < 0)
×
1393
                return table_log_add_error(r);
×
1394

1395
        if (!sd_json_format_enabled(arg_json_format_flags)) {
×
1396
                r = table_add_many(t,
×
1397
                                   TABLE_FIELD, "Plugin Arguments",
1398
                                   TABLE_STRV, strv_skip(c->argv, 1));
1399
                if (r < 0)
×
1400
                        return table_log_add_error(r);
×
1401
        }
1402

1403
        table_set_ersatz_string(t, TABLE_ERSATZ_UNSET);
×
1404

1405
        for (size_t row = 1; row < table_get_rows(t); row++) {
×
1406
                _cleanup_free_ char *name = NULL;
×
1407

1408
                name = strdup(table_get_at(t, row, 0));
×
1409
                if (!name)
×
1410
                        return log_oom();
×
1411

1412
                r = table_set_json_field_name(t, row - 1, delete_chars(name, " "));
×
1413
                if (r < 0)
×
1414
                        return log_error_errno(r, "Failed to set JSON field name: %m");
×
1415
        }
1416

1417
        return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, /* show_header= */ false);
×
1418
}
1419

1420
static int verb_list(int argc, char *argv[], void *userdata) {
×
1421
        Context *c = ASSERT_PTR(userdata);
×
1422
        _cleanup_close_ int fd = -EBADF;
×
1423
        int r;
×
1424

1425
        fd = chase_and_openat(c->rfd, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL);
×
1426
        if (fd < 0)
×
1427
                return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root));
×
1428

1429
        _cleanup_free_ DirectoryEntries *de = NULL;
×
1430
        r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
×
1431
        if (r < 0)
×
1432
                return log_error_errno(r, "Failed to numerate /usr/lib/modules/ contents: %m");
×
1433

1434
        _cleanup_(table_unrefp) Table *table = NULL;
×
1435
        table = table_new("version", "has kernel", "path");
×
1436
        if (!table)
×
1437
                return log_oom();
×
1438

1439
        table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
×
1440
        table_set_align_percent(table, table_get_cell(table, 0, 1), 100);
×
1441
        (void) table_set_sort(table, (size_t) 0);
×
1442

1443
        FOREACH_ARRAY(d, de->entries, de->n_entries) {
×
1444
                _cleanup_free_ char *j = path_join("/usr/lib/modules/", (*d)->d_name);
×
1445
                if (!j)
×
1446
                        return log_oom();
×
1447

1448
                r = dirent_ensure_type(fd, *d);
×
1449
                if (r < 0) {
×
1450
                        if (r != -ENOENT) /* don't log if just gone by now */
×
1451
                                log_debug_errno(r, "Failed to check if '%s/%s' is a directory, ignoring: %m", strempty(arg_root), j);
×
1452
                        continue;
×
1453
                }
1454

1455
                if ((*d)->d_type != DT_DIR)
×
1456
                        continue;
×
1457

1458
                _cleanup_free_ char *fn = path_join((*d)->d_name, "vmlinuz");
×
1459
                if (!fn)
×
1460
                        return log_oom();
×
1461

1462
                bool exists;
×
1463
                if (faccessat(fd, fn, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
×
1464
                        if (errno != ENOENT)
×
1465
                                log_debug_errno(errno, "Failed to check if '%s/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", strempty(arg_root), (*d)->d_name);
×
1466

1467
                        exists = false;
1468
                } else
1469
                        exists = true;
1470

1471
                r = table_add_many(table,
×
1472
                                   TABLE_VERSION, (*d)->d_name,
1473
                                   TABLE_BOOLEAN_CHECKMARK, exists,
1474
                                   TABLE_SET_COLOR, ansi_highlight_green_red(exists),
1475
                                   TABLE_PATH, j);
1476
                if (r < 0)
×
1477
                        return table_log_add_error(r);
×
1478
        }
1479

1480
        return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
×
1481
}
1482

1483
static int help(void) {
×
1484
        _cleanup_free_ char *link = NULL;
×
1485
        int r;
×
1486

1487
        r = terminal_urlify_man("kernel-install", "8", &link);
×
1488
        if (r < 0)
×
1489
                return log_oom();
×
1490

1491
        printf("%1$s [OPTIONS...] COMMAND ...\n\n"
×
1492
               "%5$sAdd and remove kernel and initrd images to and from the boot partition.%6$s\n"
1493
               "\n%3$sUsage:%4$s\n"
1494
               "  kernel-install [OPTIONS...] add [[[KERNEL-VERSION] KERNEL-IMAGE] [INITRD ...]]\n"
1495
               "  kernel-install [OPTIONS...] add-all\n"
1496
               "  kernel-install [OPTIONS...] remove KERNEL-VERSION\n"
1497
               "  kernel-install [OPTIONS...] inspect [[[KERNEL-VERSION] KERNEL-IMAGE]\n"
1498
               "                                      [INITRD ...]]\n"
1499
               "  kernel-install [OPTIONS...] list\n"
1500
               "\n%3$sOptions:%4$s\n"
1501
               "  -h --help                    Show this help\n"
1502
               "     --version                 Show package version\n"
1503
               "  -v --verbose                 Increase verbosity\n"
1504
               "     --esp-path=PATH           Path to the EFI System Partition (ESP)\n"
1505
               "     --boot-path=PATH          Path to the $BOOT partition\n"
1506
               "     --make-entry-directory=yes|no|auto\n"
1507
               "                               Create $BOOT/ENTRY-TOKEN/ directory\n"
1508
               "     --entry-type=type1|type2|all\n"
1509
               "                               Operate only on the specified bootloader\n"
1510
               "                               entry type\n"
1511
               "     --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n"
1512
               "                               Entry token to be used for this installation\n"
1513
               "     --no-pager                Do not pipe inspect output into a pager\n"
1514
               "     --json=pretty|short|off   Generate JSON output\n"
1515
               "     --no-legend               Do not show the headers and footers\n"
1516
               "     --root=PATH               Operate on an alternate filesystem root\n"
1517
               "     --image=PATH              Operate on disk image as filesystem root\n"
1518
               "     --image-policy=POLICY     Specify disk image dissection policy\n"
1519
               "\n"
1520
               "This program may also be invoked as 'installkernel':\n"
1521
               "  installkernel  [OPTIONS...] VERSION VMLINUZ [MAP] [INSTALLATION-DIR]\n"
1522
               "(The optional arguments are passed by kernel build system, but ignored.)\n"
1523
               "\n"
1524
               "See the %2$s for details.\n",
1525
               program_invocation_short_name,
1526
               link,
1527
               ansi_underline(),
1528
               ansi_normal(),
1529
               ansi_highlight(),
1530
               ansi_normal());
1531

1532
        return 0;
1533
}
1534

1535
static int parse_argv(int argc, char *argv[], Context *c) {
×
1536
        enum {
×
1537
                ARG_VERSION = 0x100,
1538
                ARG_NO_LEGEND,
1539
                ARG_ESP_PATH,
1540
                ARG_BOOT_PATH,
1541
                ARG_MAKE_ENTRY_DIRECTORY,
1542
                ARG_ENTRY_TOKEN,
1543
                ARG_NO_PAGER,
1544
                ARG_JSON,
1545
                ARG_ROOT,
1546
                ARG_IMAGE,
1547
                ARG_IMAGE_POLICY,
1548
                ARG_BOOT_ENTRY_TYPE,
1549
        };
1550
        static const struct option options[] = {
×
1551
                { "help",                 no_argument,       NULL, 'h'                      },
1552
                { "version",              no_argument,       NULL, ARG_VERSION              },
1553
                { "verbose",              no_argument,       NULL, 'v'                      },
1554
                { "esp-path",             required_argument, NULL, ARG_ESP_PATH             },
1555
                { "boot-path",            required_argument, NULL, ARG_BOOT_PATH            },
1556
                { "make-entry-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY },
1557
                { "entry-token",          required_argument, NULL, ARG_ENTRY_TOKEN          },
1558
                { "no-pager",             no_argument,       NULL, ARG_NO_PAGER             },
1559
                { "json",                 required_argument, NULL, ARG_JSON                 },
1560
                { "root",                 required_argument, NULL, ARG_ROOT                 },
1561
                { "image",                required_argument, NULL, ARG_IMAGE                },
1562
                { "image-policy",         required_argument, NULL, ARG_IMAGE_POLICY         },
1563
                { "no-legend",            no_argument,       NULL, ARG_NO_LEGEND            },
1564
                { "entry-type",           required_argument, NULL, ARG_BOOT_ENTRY_TYPE      },
1565
                {}
1566
        };
1567
        int t, r;
×
1568

1569
        assert(argc >= 0);
×
1570
        assert(argv);
×
1571
        assert(c);
×
1572

1573
        while ((t = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
×
1574
                switch (t) {
×
1575
                case 'h':
×
1576
                        return help();
×
1577

1578
                case ARG_VERSION:
×
1579
                        return version();
×
1580

1581
                case ARG_NO_LEGEND:
×
1582
                        arg_legend = false;
×
1583
                        break;
×
1584

1585
                case 'v':
×
1586
                        log_set_max_level(LOG_DEBUG);
×
1587
                        arg_verbose = true;
×
1588
                        break;
×
1589

1590
                case ARG_ESP_PATH:
×
1591
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_esp_path);
×
1592
                        if (r < 0)
×
1593
                                return log_oom();
×
1594
                        break;
1595

1596
                case ARG_BOOT_PATH:
×
1597
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_xbootldr_path);
×
1598
                        if (r < 0)
×
1599
                                return log_oom();
×
1600
                        break;
1601

1602
                case ARG_MAKE_ENTRY_DIRECTORY:
×
1603
                        if (streq(optarg, "auto"))
×
1604
                                arg_make_entry_directory = -1;
×
1605
                        else {
1606
                                r = parse_boolean_argument("--make-entry-directory=", optarg, NULL);
×
1607
                                if (r < 0)
×
1608
                                        return r;
1609

1610
                                arg_make_entry_directory = r;
×
1611
                        }
1612
                        break;
1613

1614
                case ARG_ENTRY_TOKEN:
×
1615
                        r = parse_boot_entry_token_type(optarg, &c->entry_token_type, &c->entry_token);
×
1616
                        if (r < 0)
×
1617
                                return r;
1618
                        break;
1619

1620
                case ARG_NO_PAGER:
×
1621
                        arg_pager_flags |= PAGER_DISABLE;
×
1622
                        break;
×
1623

1624
                case ARG_JSON:
×
1625
                        r = parse_json_argument(optarg, &arg_json_format_flags);
×
1626
                        if (r <= 0)
×
1627
                                return r;
1628
                        break;
1629

1630
                case ARG_ROOT:
×
1631
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root);
×
1632
                        if (r < 0)
×
1633
                                return r;
1634
                        break;
1635

1636
                case ARG_IMAGE:
×
1637
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
×
1638
                        if (r < 0)
×
1639
                                return r;
1640
                        break;
1641

1642
                case ARG_IMAGE_POLICY:
×
1643
                        r = parse_image_policy_argument(optarg, &arg_image_policy);
×
1644
                        if (r < 0)
×
1645
                                return r;
1646
                        break;
1647

1648
                case ARG_BOOT_ENTRY_TYPE:
×
1649
                        r = context_set_entry_type(c, optarg);
×
1650
                        if (r < 0)
×
1651
                                return r;
1652
                        break;
1653

1654
                case '?':
1655
                        return -EINVAL;
1656

1657
                default:
×
1658
                        assert_not_reached();
×
1659
                }
1660

1661
        if (arg_image && arg_root)
×
1662
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported.");
×
1663

1664
        return 1;
1665
}
1666

1667
static int run(int argc, char* argv[]) {
×
1668
        static const Verb verbs[] = {
×
1669
                { "add",         1,        VERB_ANY, 0,            verb_add            },
1670
                { "add-all",     1,        1,        0,            verb_add_all        },
1671
                { "remove",      2,        VERB_ANY, 0,            verb_remove         },
1672
                { "inspect",     1,        VERB_ANY, VERB_DEFAULT, verb_inspect        },
1673
                { "list",        1,        1,        0,            verb_list           },
1674
                {}
1675
        };
1676
        _cleanup_(context_done) Context c = {
×
1677
                .rfd = AT_FDCWD,
1678
                .action = _ACTION_INVALID,
1679
                .kernel_image_type = KERNEL_IMAGE_TYPE_UNKNOWN,
1680
                .layout = _LAYOUT_INVALID,
1681
                .entry_type = _BOOT_ENTRY_TYPE_INVALID,
1682
                .entry_token_type = BOOT_ENTRY_TOKEN_AUTO,
1683
        };
1684
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
×
1685
        _cleanup_(umount_and_freep) char *mounted_dir = NULL;
×
1686
        int r;
×
1687

1688
        log_setup();
×
1689

1690
        r = parse_argv(argc, argv, &c);
×
1691
        if (r <= 0)
×
1692
                return r;
1693

1694
        if (arg_image) {
×
1695
                assert(!arg_root);
×
1696

1697
                r = mount_image_privately_interactively(
×
1698
                                arg_image,
1699
                                arg_image_policy,
1700
                                DISSECT_IMAGE_GENERIC_ROOT |
1701
                                DISSECT_IMAGE_REQUIRE_ROOT |
1702
                                DISSECT_IMAGE_RELAX_VAR_CHECK |
1703
                                DISSECT_IMAGE_VALIDATE_OS |
1704
                                DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
1705
                                &mounted_dir,
1706
                                /* ret_dir_fd= */ NULL,
1707
                                &loop_device);
1708
                if (r < 0)
×
1709
                        return r;
1710

1711
                arg_root = strdup(mounted_dir);
×
1712
                if (!arg_root)
×
1713
                        return log_oom();
×
1714
        }
1715

1716
        r = context_init(&c);
×
1717
        if (r < 0)
×
1718
                return r;
1719

1720
        if (invoked_as(argv, "installkernel"))
×
1721
                return run_as_installkernel(argc, argv, &c);
×
1722

1723
        return dispatch_verb(argc, argv, verbs, &c);
×
1724
}
1725

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

© 2026 Coveralls, Inc