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

systemd / systemd / 29132483780

10 Jul 2026 08:43PM UTC coverage: 72.912% (+0.2%) from 72.702%
29132483780

push

github

bluca
man: run forgotten 'update-man-rules'

344600 of 472622 relevant lines covered (72.91%)

1365091.83 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 <stdlib.h>
4
#include <sys/utsname.h>
5
#include <unistd.h>
6

7
#include "ansi-color.h"
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 "dissect-image.h"
15
#include "dlopen-note.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 "help-util.h"
26
#include "id128-util.h"
27
#include "image-policy.h"
28
#include "kernel-config.h"
29
#include "kernel-image.h"
30
#include "loop-util.h"
31
#include "main-func.h"
32
#include "mount-util.h"
33
#include "options.h"
34
#include "parse-argument.h"
35
#include "path-util.h"
36
#include "recurse-dir.h"
37
#include "rm-rf.h"
38
#include "stat-util.h"
39
#include "string-table.h"
40
#include "string-util.h"
41
#include "strv.h"
42
#include "tmpfile-util.h"
43
#include "verbs.h"
44

45
static bool arg_verbose = false;
46
static char *arg_esp_path = NULL;
47
static char *arg_xbootldr_path = NULL;
48
static int arg_make_entry_directory = -1; /* tristate */
49
static PagerFlags arg_pager_flags = 0;
50
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
51
static char *arg_root = NULL;
52
static char *arg_image = NULL;
53
static ImagePolicy *arg_image_policy = NULL;
54
static bool arg_legend = true;
55
static BootEntryTokenType arg_entry_token_type = BOOT_ENTRY_TOKEN_AUTO;
56
static char *arg_entry_token = NULL;
57
static BootEntryType arg_boot_entry_type = _BOOT_ENTRY_TYPE_INVALID;
58

59
STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep);
×
60
STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep);
×
61
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
×
62
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
×
63
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
×
64
STATIC_DESTRUCTOR_REGISTER(arg_entry_token, freep);
×
65

66
typedef enum Action {
67
        ACTION_ADD,
68
        ACTION_REMOVE,
69
        ACTION_INSPECT,
70
        _ACTION_MAX,
71
        _ACTION_INVALID = -EINVAL,
72
} Action;
73

74
typedef enum Layout {
75
        LAYOUT_AUTO,
76
        LAYOUT_UKI,
77
        LAYOUT_BLS,
78
        LAYOUT_OTHER,
79
        _LAYOUT_MAX,
80
        _LAYOUT_INVALID = -EINVAL,
81
} Layout;
82

83
static const char * const layout_table[_LAYOUT_MAX] = {
84
        [LAYOUT_AUTO]  = "auto",
85
        [LAYOUT_UKI]   = "uki",
86
        [LAYOUT_BLS]   = "bls",
87
        [LAYOUT_OTHER] = "other",
88
};
89

90
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(layout, Layout);
×
91

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

117
#define CONTEXT_NULL                                                    \
118
        (Context) {                                                     \
119
                .rfd = XAT_FDROOT,                                      \
120
                .action = _ACTION_INVALID,                              \
121
                .kernel_image_type = _KERNEL_IMAGE_TYPE_INVALID,        \
122
                .layout = _LAYOUT_INVALID,                              \
123
                .entry_type = _BOOT_ENTRY_TYPE_INVALID,                 \
124
                .entry_token_type = _BOOT_ENTRY_TOKEN_TYPE_INVALID,     \
125
        }
126

127
static void context_done(Context *c) {
×
128
        assert(c);
×
129

130
        free(c->layout_other);
×
131
        free(c->conf_root);
×
132
        free(c->boot_root);
×
133
        free(c->entry_token);
×
134
        free(c->entry_dir);
×
135
        free(c->version);
×
136
        free(c->kernel);
×
137
        strv_free(c->initrds);
×
138
        free(c->initrd_generator);
×
139
        free(c->uki_generator);
×
140
        if (c->action == ACTION_INSPECT)
×
141
                free(c->staging_area);
×
142
        else
143
                rm_rf_physical_and_free(c->staging_area);
×
144
        strv_free(c->plugins);
×
145
        strv_free(c->argv);
×
146
        strv_free(c->envp);
×
147

148
        safe_close(c->rfd);
×
149
}
×
150

151
static int context_copy(const Context *source, Context *ret) {
×
152
        int r;
×
153

154
        assert(source);
×
155
        assert(ret);
×
156
        assert(wildcard_fd_is_valid(source->rfd));
×
157

158
        _cleanup_(context_done) Context copy = (Context) {
×
159
                .rfd = source->rfd,
×
160
                .action = source->action,
×
161
                .machine_id = source->machine_id,
162
                .machine_id_is_random = source->machine_id_is_random,
×
163
                .kernel_image_type = source->kernel_image_type,
×
164
                .layout = source->layout,
×
165
                .entry_token_type = source->entry_token_type,
×
166
        };
167

168
        if (source->rfd >= 0) {
×
169
                copy.rfd = fd_reopen(source->rfd, O_CLOEXEC|O_DIRECTORY|O_PATH);
×
170
                if (copy.rfd < 0)
×
171
                        return copy.rfd;
172
        }
173

174
        r = strdup_to(&copy.layout_other, source->layout_other);
×
175
        if (r < 0)
×
176
                return r;
177
        r = strdup_to(&copy.conf_root, source->conf_root);
×
178
        if (r < 0)
×
179
                return r;
180
        r = strdup_to(&copy.boot_root, source->boot_root);
×
181
        if (r < 0)
×
182
                return r;
183
        r = strdup_to(&copy.entry_token, source->entry_token);
×
184
        if (r < 0)
×
185
                return r;
186
        r = strdup_to(&copy.entry_dir, source->entry_dir);
×
187
        if (r < 0)
×
188
                return r;
189
        r = strdup_to(&copy.version, source->version);
×
190
        if (r < 0)
×
191
                return r;
192
        r = strdup_to(&copy.kernel, source->kernel);
×
193
        if (r < 0)
×
194
                return r;
195
        r = strv_copy_unless_empty(source->initrds, &copy.initrds);
×
196
        if (r < 0)
×
197
                return r;
198
        r = strdup_to(&copy.initrd_generator, source->initrd_generator);
×
199
        if (r < 0)
×
200
                return r;
201
        r = strdup_to(&copy.uki_generator, source->uki_generator);
×
202
        if (r < 0)
×
203
                return r;
204
        r = strdup_to(&copy.staging_area, source->staging_area);
×
205
        if (r < 0)
×
206
                return r;
207
        r = strv_copy_unless_empty(source->plugins, &copy.plugins);
×
208
        if (r < 0)
×
209
                return r;
210
        r = strv_copy_unless_empty(source->argv, &copy.argv);
×
211
        if (r < 0)
×
212
                return r;
213
        r = strv_copy_unless_empty(source->envp, &copy.envp);
×
214
        if (r < 0)
×
215
                return r;
216

217
        *ret = copy;
×
218
        copy = CONTEXT_NULL;
×
219

220
        return 0;
×
221
}
222

223
static int context_open_root(Context *c) {
×
224
        int r;
×
225

226
        assert(c);
×
227
        assert(c->rfd < 0);
×
228

229
        if (isempty(arg_root))
×
230
                return 0;
231

232
        r = path_is_root(arg_root);
×
233
        if (r < 0)
×
234
                return log_error_errno(r, "Failed to determine if '%s' is the root directory: %m", arg_root);
×
235
        if (r > 0)
×
236
                return 0;
237

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

242
        return 0;
243
}
244

245
static const char* context_get_layout(const Context *c) {
×
246
        assert(c);
×
247
        assert(c->layout >= 0);
×
248

249
        return c->layout_other ?: layout_to_string(c->layout);
×
250
}
251

252
static int context_set_layout(Context *c, const char *s, const char *source) {
×
253
        Layout t;
×
254

255
        assert(c);
×
256
        assert(source);
×
257

258
        if (c->layout >= 0 || !s)
×
259
                return 0;
260

261
        assert(!c->layout_other);
×
262

263
        t = layout_from_string(s);
×
264
        if (t >= 0)
×
265
                c->layout = t;
×
266
        else if (isempty(s))
×
267
                c->layout = LAYOUT_AUTO;
×
268
        else {
269
                c->layout_other = strdup(s);
×
270
                if (!c->layout_other)
×
271
                        return log_oom();
×
272

273
                c->layout = LAYOUT_OTHER;
×
274
        }
275

276
        log_debug("layout=%s set via %s", context_get_layout(c), source);
×
277
        return 1;
278
}
279

280
static int context_set_machine_id(Context *c, const char *s, const char *source) {
×
281
        int r;
×
282

283
        assert(c);
×
284
        assert(source);
×
285

286
        if (!sd_id128_is_null(c->machine_id) || !s)
×
287
                return 0;
288

289
        r = sd_id128_from_string(s, &c->machine_id);
×
290
        if (r < 0)
×
291
                return log_warning_errno(r, "Failed to parse machine ID specified via %s, ignoring.", source);
×
292

293
        if (sd_id128_is_null(c->machine_id))
×
294
                return 0;
×
295

296
        log_debug("MACHINE_ID=%s set via %s.", SD_ID128_TO_STRING(c->machine_id), source);
×
297
        return 1;
×
298
}
299

300
static int context_set_string(const char *s, const char *source, const char *name, char **dest) {
×
301
        char *p;
×
302

303
        assert(source);
×
304
        assert(name);
×
305
        assert(dest);
×
306

307
        if (*dest || !s)
×
308
                return 0;
309

310
        p = strdup(s);
×
311
        if (!p)
×
312
                return log_oom();
×
313

314
        log_debug("%s (%s) set via %s.", name, p, source);
×
315

316
        *dest = p;
×
317
        return 1;
×
318
}
319

320
static int context_set_initrd_generator(Context *c, const char *s, const char *source) {
×
321
        assert(c);
×
322
        return context_set_string(s, source, "INITRD_GENERATOR", &c->initrd_generator);
×
323
}
324

325
static int context_set_uki_generator(Context *c, const char *s, const char *source) {
×
326
        assert(c);
×
327
        return context_set_string(s, source, "UKI_GENERATOR", &c->uki_generator);
×
328
}
329

330
static int context_set_version(Context *c, const char *s) {
×
331
        assert(c);
×
332

333
        if (s && !filename_is_valid(s))
×
334
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid version specified: %s", s);
×
335

336
        return context_set_string(s, "command line", "kernel version", &c->version);
×
337
}
338

339
static int context_set_path(Context *c, const char *s, const char *source, const char *name, char **dest) {
×
340
        char *p;
×
341
        int r;
×
342

343
        assert(c);
×
344
        assert(source);
×
345
        assert(name);
×
346
        assert(dest);
×
347

348
        if (*dest || !s)
×
349
                return 0;
×
350

351
        if (c->rfd >= 0) {
×
352
                r = chaseat(c->rfd, c->rfd, s, /* flags= */ 0, &p, /* ret_fd= */ NULL);
×
353
                if (r < 0)
×
354
                        return log_warning_errno(r, "Failed to chase path %s for %s specified via %s, ignoring: %m",
×
355
                                                 s, name, source);
356
        } else {
357
                r = path_make_absolute_cwd(s, &p);
×
358
                if (r < 0)
×
359
                        return log_warning_errno(r, "Failed to make path '%s' for %s specified via %s absolute, ignoring: %m",
×
360
                                                 s, name, source);
361
        }
362

363
        log_debug("%s (%s) set via %s.", name, p, source);
×
364

365
        *dest = p;
×
366
        return 1;
×
367
}
368

369
static int context_set_boot_root(Context *c, const char *s, const char *source) {
×
370
        assert(c);
×
371
        return context_set_path(c, s, source, "BOOT_ROOT", &c->boot_root);
×
372
}
373

374
static int context_set_conf_root(Context *c, const char *s, const char *source) {
×
375
        assert(c);
×
376
        return context_set_path(c, s, source, "CONF_ROOT", &c->conf_root);
×
377
}
378

379
static int context_set_kernel(Context *c, const char *s) {
×
380
        assert(c);
×
381
        return context_set_path(c, s, "command line", "kernel image file", &c->kernel);
×
382
}
383

384
static int context_set_path_strv(Context *c, char* const* strv, const char *source, const char *name, char ***dest) {
×
385
        _cleanup_strv_free_ char **w = NULL;
×
386
        int r;
×
387

388
        assert(c);
×
389
        assert(source);
×
390
        assert(name);
×
391
        assert(dest);
×
392

393
        if (*dest)
×
394
                return 0;
395

396
        STRV_FOREACH(s, strv) {
×
397
                char *p;
×
398

399
                if (c->rfd >= 0) {
×
400
                        r = chaseat(c->rfd, c->rfd, *s, /* flags= */ 0, &p, /* ret_fd= */ NULL);
×
401
                        if (r < 0)
×
402
                                return log_warning_errno(r, "Failed to chase path %s for %s specified via %s: %m",
×
403
                                                         *s, name, source);
404
                } else {
405
                        r = path_make_absolute_cwd(*s, &p);
×
406
                        if (r < 0)
×
407
                                return log_warning_errno(r, "Failed to make path '%s' for %s specified via %s absolute, ignoring: %m",
×
408
                                                         *s, name, source);
409
                }
410
                r = strv_consume(&w, p);
×
411
                if (r < 0)
×
412
                        return log_oom();
×
413
        }
414

415
        if (strv_isempty(w))
×
416
                return 0;
417

418
        log_debug("%s set via %s", name, source);
×
419

420
        *dest = TAKE_PTR(w);
×
421
        return 1;
×
422
}
423

424
static int context_set_plugins(Context *c, const char *s, const char *source) {
×
425
        _cleanup_strv_free_ char **v = NULL;
×
426
        int r;
×
427

428
        assert(c);
×
429

430
        if (c->plugins || !s)
×
431
                return 0;
432

433
        r = strv_split_full(&v, s, NULL, EXTRACT_UNQUOTE);
×
434
        if (r < 0)
×
435
                return log_error_errno(r, "Failed to parse plugin paths from %s: %m", source);
×
436

437
        return context_set_path_strv(c, v, source, "plugins", &c->plugins);
×
438
}
439

440
static int context_set_initrds(Context *c, char* const* strv) {
×
441
        assert(c);
×
442
        return context_set_path_strv(c, strv, "command line", "initrds", &c->initrds);
×
443
}
444

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

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

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

460
        assert(c);
×
461

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

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

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

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

490
        assert(c);
×
491

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

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

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

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

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

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

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

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

529
        assert(c);
×
530

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

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

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

544
        assert(c);
×
545

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

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

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

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

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

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

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

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

589
static int context_acquire_esp(Context *c) {
×
590
        int r;
×
591

592
        assert(c);
×
593
        assert(!c->boot_root);
×
594

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

610
        log_debug("Using EFI System Partition at %s as $BOOT_ROOT.", c->boot_root);
×
611
        return 1; /* found */
612
}
613

614
static int context_ensure_boot_root(Context *c) {
×
615
        int r;
×
616

617
        assert(c);
×
618

619
        /* If BOOT_ROOT is specified via environment or install.conf, then use it. */
620
        if (c->boot_root)
×
621
                return 0;
622

623
        /* Otherwise, use XBOOTLDR partition, if mounted. */
624
        r = context_acquire_xbootldr(c);
×
625
        if (r != 0)
×
626
                return r;
627

628
        /* Otherwise, use EFI system partition, if mounted. */
629
        r = context_acquire_esp(c);
×
630
        if (r != 0)
×
631
                return r;
632

633
        /* If all else fails, use /boot. */
634
        if (c->rfd >= 0) {
×
635
                r = chaseat(c->rfd, c->rfd, "/boot", 0, &c->boot_root, /* ret_fd= */ NULL);
×
636
                if (r < 0)
×
637
                        return log_error_errno(r, "Failed to chase '/boot/': %m");
×
638
        } else {
639
                c->boot_root = strdup("/boot");
×
640
                if (!c->boot_root)
×
641
                        return log_oom();
×
642
        }
643

644
        log_debug("KERNEL_INSTALL_BOOT_ROOT autodetection yielded no candidates, using \"%s\".", c->boot_root);
×
645
        return 0;
646
}
647

648
static int context_ensure_entry_token(Context *c) {
×
649
        int r;
×
650

651
        assert(c);
×
652

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

658
        r = boot_entry_token_ensure_at(
×
659
                        c->rfd,
660
                        c->conf_root,
×
661
                        c->machine_id,
662
                        c->machine_id_is_random,
663
                        &c->entry_token_type,
664
                        &c->entry_token);
665
        if (r < 0)
×
666
                return r;
667

668
        log_debug("Using entry token: %s", c->entry_token);
×
669
        return 0;
670
}
671

672
static int context_load_plugins(Context *c) {
×
673
        int r;
×
674

675
        assert(c);
×
676

677
        if (c->plugins)
×
678
                return 0;
×
679

680
        r = conf_files_list_strv_at(
×
681
                        &c->plugins,
682
                        ".install",
683
                        c->rfd,
684
                        CONF_FILES_EXECUTABLE | CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED | CONF_FILES_WARN,
685
                        STRV_MAKE_CONST("/etc/kernel/install.d", "/usr/lib/kernel/install.d"));
×
686
        if (r < 0)
×
687
                return log_error_errno(r, "Failed to find plugins: %m");
×
688

689
        return 0;
690
}
691

692
static int context_setup(Context *c) {
×
693
        int r;
×
694

695
        assert(c);
×
696

697
        if (c->kernel_image_type < 0)
×
698
                c->kernel_image_type = KERNEL_IMAGE_TYPE_UNKNOWN;
×
699
        if (c->entry_token_type < 0)
×
700
                c->entry_token_type = BOOT_ENTRY_TOKEN_AUTO;
×
701

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

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

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

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

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

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

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

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

734
        return 0;
735
}
736

737
static int context_from_cmdline(Context *c, Action action) {
×
738
        int r;
×
739

740
        assert(c);
×
741

742
        c->action = action;
×
743

744
        c->entry_type = arg_boot_entry_type;
×
745

746
        r = free_and_strdup_warn(&c->entry_token, arg_entry_token);
×
747
        if (r < 0)
×
748
                return r;
749

750
        c->entry_token_type = arg_entry_token_type;
×
751

752
        return context_setup(c);
×
753
}
754

755
static int context_inspect_kernel(Context *c) {
×
756
        int r;
×
757

758
        assert(c);
×
759

760
        if (!c->kernel)
×
761
                return 0;
762

763
        r = inspect_kernel(
×
764
                        c->rfd,
765
                        c->kernel,
766
                        &c->kernel_image_type);
767
        if (r < 0)
×
768
                return log_error_errno(r, "Failed to inspect kernel image '%s': %m", c->kernel);
×
769

770
        return 0;
771
}
772

773
static int context_ensure_layout(Context *c) {
×
774
        int r;
×
775

776
        assert(c);
×
777
        assert(c->boot_root);
×
778
        assert(c->entry_token);
×
779

780
        if (c->layout >= 0 && c->layout != LAYOUT_AUTO)
×
781
                return 0;
×
782

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

786
        if (c->kernel_image_type == KERNEL_IMAGE_TYPE_UKI) {
×
787
                c->layout = LAYOUT_UKI;
×
788
                log_debug("Kernel image type is %s, using layout=%s.",
×
789
                          kernel_image_type_to_string(c->kernel_image_type), layout_to_string(c->layout));
790
                return 0;
791
        }
792

793
        _cleanup_free_ char *srel_path = path_join(c->boot_root, "loader/entries.srel");
×
794
        if (!srel_path)
×
795
                return log_oom();
×
796

797
        _cleanup_fclose_ FILE *f = NULL;
×
798
        r = chase_and_fopenat_unlocked(c->rfd, c->rfd, srel_path, CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f);
×
799
        if (r < 0) {
×
800
                if (r != -ENOENT)
×
801
                        return log_error_errno(r, "Failed to open '%s': %m", srel_path);
×
802
        } else {
803
                _cleanup_free_ char *srel = NULL;
×
804

805
                r = read_line(f, LONG_LINE_MAX, &srel);
×
806
                if (r < 0)
×
807
                        return log_error_errno(r, "Failed to read %s: %m", srel_path);
×
808

809
                if (streq(srel, "type1"))
×
810
                        /* The loader/entries.srel file clearly indicates that the installed boot loader
811
                         * implements the proper standard upstream boot loader spec for Type #1 entries.
812
                         * Let's default to that, then. */
813
                        c->layout = LAYOUT_BLS;
×
814
                else
815
                        /* The loader/entries.srel file indicates some other spec is implemented and owns the
816
                         * /loader/entries/ directory. Since we have no idea what that means, let's stay away
817
                         * from it by default. */
818
                        c->layout = LAYOUT_OTHER;
×
819

820
                log_debug("%s with '%s' found, using layout=%s.", srel_path, srel, layout_to_string(c->layout));
×
821
                return 0;
822
        }
823

824
        _cleanup_free_ char *entry_token_path = path_join(c->boot_root, c->entry_token);
×
825
        if (!entry_token_path)
×
826
                return log_oom();
×
827

828
        r = chaseat(c->rfd, c->rfd, entry_token_path, CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, /* ret_fd= */ NULL);
×
829
        if (r < 0) {
×
830
                if (!IN_SET(r, -ENOENT, -ENOTDIR))
×
831
                        return log_error_errno(r, "Failed to check if '%s' exists and is a directory: %m", entry_token_path);
×
832
        } else {
833
                /* If the metadata in $BOOT_ROOT doesn't tell us anything, then check if the entry token
834
                 * directory already exists. If so, let's assume it's the standard boot loader spec, too. */
835
                c->layout = LAYOUT_BLS;
×
836
                log_debug("%s exists, using layout=%s.", entry_token_path, layout_to_string(c->layout));
×
837
                return 0;
838
        }
839

840
        /* There's no metadata in $BOOT_ROOT, and apparently no entry token directory installed? Then we
841
         * really don't know anything. */
842
        c->layout = LAYOUT_OTHER;
×
843
        log_debug("Entry-token directory %s not found, using layout=%s.",
×
844
                  entry_token_path,
845
                  layout_to_string(c->layout));
846
        return 0;
847
}
848

849
static int context_set_up_staging_area(Context *c) {
×
850
        int r;
×
851

852
        assert(c);
×
853

854
        if (c->staging_area)
×
855
                return 0;
×
856

857
        const char *d;
×
858
        r = var_tmp_dir(&d);
×
859
        if (r < 0)
×
860
                return log_error_errno(r, "Failed to determine temporary directory location: %m");
×
861

862
        _cleanup_free_ char *template = path_join(d, "kernel-install.staging.XXXXXX");
×
863
        if (!template)
×
864
                return log_oom();
×
865

866
        if (c->action == ACTION_INSPECT)
×
867
                /* This is only used for display. The directory will not be created. */
868
                c->staging_area = TAKE_PTR(template);
×
869
        else {
870
                r = mkdtemp_malloc(template, &c->staging_area);
×
871
                if (r < 0)
×
872
                        return log_error_errno(r, "Failed to create staging area: %m");
×
873
        }
874

875
        return 0;
876
}
877

878
static int context_build_entry_dir(Context *c) {
×
879
        assert(c);
×
880
        assert(c->boot_root);
×
881
        assert(c->entry_token);
×
882
        assert(c->version || c->action == ACTION_INSPECT);
×
883

884
        if (c->entry_dir)
×
885
                return 0;
886

887
        c->entry_dir = path_join(c->boot_root, c->entry_token, c->version ?: "KERNEL_VERSION");
×
888
        if (!c->entry_dir)
×
889
                return log_oom();
×
890

891
        log_debug("Using ENTRY_DIR=%s", c->entry_dir);
×
892
        return 0;
893
}
894

895
static bool context_should_make_entry_dir(Context *c) {
×
896
        assert(c);
×
897

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

901
        if (arg_make_entry_directory < 0)
×
902
                return c->layout == LAYOUT_BLS;
×
903

904
        return arg_make_entry_directory;
×
905
}
906

907
static int context_make_entry_dir(Context *c) {
×
908
        _cleanup_close_ int fd = -EBADF;
×
909

910
        assert(c);
×
911
        assert(c->entry_dir);
×
912

913
        if (c->action != ACTION_ADD)
×
914
                return 0;
915

916
        if (!context_should_make_entry_dir(c))
×
917
                return 0;
918

919
        log_debug("mkdir -p %s", c->entry_dir);
×
920
        fd = chase_and_openat(c->rfd, c->rfd, c->entry_dir, CHASE_MKDIR_0755,
×
921
                              O_CLOEXEC | O_CREAT | O_DIRECTORY | O_PATH, NULL);
922
        if (fd < 0)
×
923
                return log_error_errno(fd, "Failed to make directory '%s': %m", c->entry_dir);
×
924

925
        return 0;
926
}
927

928
static int context_remove_entry_dir(Context *c) {
×
929
        _cleanup_free_ char *p = NULL;
×
930
        _cleanup_close_ int fd = -EBADF;
×
931
        struct stat st;
×
932
        int r;
×
933

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

937
        if (c->action != ACTION_REMOVE)
×
938
                return 0;
939

940
        if (!context_should_make_entry_dir(c))
×
941
                return 0;
942

943
        log_debug("rm -rf %s", c->entry_dir);
×
944
        fd = chase_and_openat(c->rfd, c->rfd, c->entry_dir, /* chase_flags= */ 0, O_CLOEXEC | O_DIRECTORY, &p);
×
945
        if (fd < 0) {
×
946
                if (IN_SET(fd, -ENOTDIR, -ENOENT))
×
947
                        return 0;
948
                return log_debug_errno(fd, "Failed to chase and open %s, ignoring: %m", c->entry_dir);
×
949
        }
950

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

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

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

961
        return 0;
962
}
963

964
static int context_build_arguments(Context *c) {
×
965
        _cleanup_strv_free_ char **a = NULL;
×
966
        const char *verb;
×
967
        int r;
×
968

969
        assert(c);
×
970
        assert(c->entry_dir);
×
971

972
        if (c->argv)
×
973
                return 0;
974

975
        switch (c->action) {
×
976
        case ACTION_ADD:
×
977
                assert(c->version);
×
978
                assert(c->kernel);
×
979
                verb = "add";
980
                break;
981

982
        case ACTION_REMOVE:
×
983
                assert(c->version);
×
984
                assert(!c->kernel);
×
985
                assert(!c->initrds);
×
986
                verb = "remove";
987
                break;
988

989
        case ACTION_INSPECT:
990
                verb = "add|remove";
991
                break;
992

993
        default:
×
994
                assert_not_reached();
×
995
        }
996

997
        a = strv_new("dummy-arg", /* to make strv_free() works for this variable. */
×
998
                     verb,
999
                     c->version ?: "KERNEL_VERSION",
1000
                     c->entry_dir);
1001
        if (!a)
×
1002
                return log_oom();
×
1003

1004
        if (c->action == ACTION_ADD) {
×
1005
                r = strv_extend(&a, c->kernel);
×
1006
                if (r < 0)
×
1007
                        return log_oom();
×
1008

1009
                r = strv_extend_strv(&a, c->initrds, /* filter_duplicates= */ false);
×
1010
                if (r < 0)
×
1011
                        return log_oom();
×
1012

1013
        } else if (c->action == ACTION_INSPECT) {
×
1014
                r = strv_extend_many(
×
1015
                                &a,
1016
                                c->kernel ?: "[KERNEL_IMAGE]",
1017
                                "[INITRD...]");
1018
                if (r < 0)
×
1019
                        return log_oom();
×
1020
        }
1021

1022
        c->argv = TAKE_PTR(a);
×
1023
        return 0;
×
1024
}
1025

1026
static int context_build_environment(Context *c) {
×
1027
        _cleanup_strv_free_ char **e = NULL;
×
1028
        int r;
×
1029

1030
        assert(c);
×
1031

1032
        if (c->envp)
×
1033
                return 0;
1034

1035
        r = strv_env_assign_many(&e,
×
1036
                                 "LC_COLLATE",                      SYSTEMD_DEFAULT_LOCALE,
1037
                                 "KERNEL_INSTALL_VERBOSE",          one_zero(arg_verbose),
1038
                                 "KERNEL_INSTALL_IMAGE_TYPE",       kernel_image_type_to_string(c->kernel_image_type),
1039
                                 "KERNEL_INSTALL_MACHINE_ID",       SD_ID128_TO_STRING(c->machine_id),
1040
                                 "KERNEL_INSTALL_ENTRY_TOKEN",      c->entry_token,
1041
                                 "KERNEL_INSTALL_BOOT_ROOT",        c->boot_root,
1042
                                 "KERNEL_INSTALL_LAYOUT",           context_get_layout(c),
1043
                                 "KERNEL_INSTALL_INITRD_GENERATOR", strempty(c->initrd_generator),
1044
                                 "KERNEL_INSTALL_UKI_GENERATOR",    strempty(c->uki_generator),
1045
                                 "KERNEL_INSTALL_BOOT_ENTRY_TYPE",  boot_entry_type_to_string(c->entry_type),
1046
                                 "KERNEL_INSTALL_STAGING_AREA",     c->staging_area);
1047
        if (r < 0)
×
1048
                return log_error_errno(r, "Failed to build environment variables for plugins: %m");
×
1049

1050
        c->envp = TAKE_PTR(e);
×
1051
        return 0;
×
1052
}
1053

1054
static int context_prepare_execution(Context *c) {
×
1055
        int r;
×
1056

1057
        assert(c);
×
1058

1059
        r = context_inspect_kernel(c);
×
1060
        if (r < 0)
×
1061
                return r;
1062

1063
        r = context_ensure_layout(c);
×
1064
        if (r < 0)
×
1065
                return r;
1066

1067
        r = context_set_up_staging_area(c);
×
1068
        if (r < 0)
×
1069
                return r;
1070

1071
        r = context_build_entry_dir(c);
×
1072
        if (r < 0)
×
1073
                return r;
1074

1075
        r = context_build_arguments(c);
×
1076
        if (r < 0)
×
1077
                return r;
1078

1079
        r = context_build_environment(c);
×
1080
        if (r < 0)
×
1081
                return r;
×
1082

1083
        return 0;
1084
}
1085

1086
static int context_execute(Context *c) {
×
1087
        int r, ret;
×
1088

1089
        assert(c);
×
1090

1091
        r = context_make_entry_dir(c);
×
1092
        if (r < 0)
×
1093
                return r;
1094

1095
        if (DEBUG_LOGGING) {
×
1096
                _cleanup_free_ char *x = strv_join_full(c->plugins, "", "\n  ", /* escape_separator= */ false);
×
1097
                log_debug("Using plugins: %s", strna(x));
×
1098

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

1102
                _cleanup_free_ char *z = strv_join(strv_skip(c->argv, 1), " ");
×
1103
                log_debug("Plugin arguments: %s", strna(z));
×
1104
        }
1105

1106
        ret = execute_strv(
×
1107
                        "plugins",
1108
                        c->plugins,
×
1109
                        /* root= */ NULL,
1110
                        USEC_INFINITY,
1111
                        /* callbacks= */ NULL,
1112
                        /* callback_args= */ NULL,
1113
                        c->argv,
1114
                        c->envp,
1115
                        EXEC_DIR_SKIP_REMAINING);
1116

1117
        r = context_remove_entry_dir(c);
×
1118
        if (r < 0)
×
1119
                return r;
×
1120

1121
        /* This returns 0 on success, positive exit code on plugin failure, negative errno on other failures. */
1122
        return ret;
1123
}
1124

1125
static bool bypass(void) {
×
1126
        return should_bypass("KERNEL_INSTALL");
×
1127
}
1128

1129
static int kernel_from_version(const char *version, char **ret_kernel) {
×
1130
        _cleanup_free_ char *vmlinuz = NULL;
×
1131
        int r;
×
1132

1133
        assert(version);
×
1134
        assert(ret_kernel);
×
1135

1136
        vmlinuz = path_join("/usr/lib/modules/", version, "/vmlinuz");
×
1137
        if (!vmlinuz)
×
1138
                return log_oom();
×
1139

1140
        r = access_nofollow(vmlinuz, F_OK);
×
1141
        if (r == -ENOENT)
×
1142
                return log_error_errno(r, "Kernel image not installed to '%s', specify kernel kernel image path explicitly.", vmlinuz);
×
1143
        if (r < 0)
×
1144
                return log_error_errno(r, "Failed to determine if kernel image is installed to '%s': %m", vmlinuz);
×
1145

1146
        *ret_kernel = TAKE_PTR(vmlinuz);
×
1147
        return 0;
×
1148
}
1149

1150
static int do_add(
×
1151
                Context *c,
1152
                const char *version,
1153
                const char *kernel,
1154
                char **initrds) {
1155

1156
        int r;
×
1157

1158
        assert(c);
×
1159

1160
        struct utsname un;
×
1161
        if (!version) {
×
1162
                assert_se(uname(&un) >= 0);
×
1163
                version = un.release;
1164
        }
1165

1166
        _cleanup_free_ char *vmlinuz = NULL;
×
1167
        if (!kernel) {
×
1168
                r = kernel_from_version(version, &vmlinuz);
×
1169
                if (r < 0)
×
1170
                        return r;
1171

1172
                kernel = vmlinuz;
×
1173
        }
1174

1175
        r = context_set_version(c, version);
×
1176
        if (r < 0)
×
1177
                return r;
1178

1179
        r = context_set_kernel(c, kernel);
×
1180
        if (r < 0)
×
1181
                return r;
1182

1183
        r = context_set_initrds(c, initrds);
×
1184
        if (r < 0)
×
1185
                return r;
1186

1187
        r = context_prepare_execution(c);
×
1188
        if (r < 0)
×
1189
                return r;
1190

1191
        return context_execute(c);
×
1192
}
1193

1194
VERB(verb_add, "add", "[[[KERNEL-VERSION] KERNEL-IMAGE] [INITRD ...]]", 1, VERB_ANY, 0,
1195
     "Add a kernel and initrd images to the boot partition");
1196
static int verb_add(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
1197
        const char *version, *kernel;
×
1198
        char **initrds;
×
1199
        int r;
×
1200

1201
        assert(argv);
×
1202

1203
        if (arg_root)
×
1204
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'add' does not support --root= or --image=.");
×
1205

1206
        if (bypass())
×
1207
                return 0;
1208

1209
        _cleanup_(context_done) Context c = CONTEXT_NULL;
×
1210
        r = context_from_cmdline(&c, ACTION_ADD);
×
1211
        if (r < 0)
×
1212
                return r;
1213

1214
        /* We use the same order of arguments that "inspect" introduced, i.e. if only on argument is
1215
         * specified we take it as the kernel path, not the version, i.e. it's the first argument that is
1216
         * optional, not the 2nd. */
1217
        version = argc > 2 ? empty_or_dash_to_null(argv[1]) : NULL;
×
1218
        kernel = argc > 2 ? empty_or_dash_to_null(argv[2]) :
×
1219
                (argc > 1 ? empty_or_dash_to_null(argv[1]) : NULL);
×
1220
        initrds = strv_skip(argv, 3);
×
1221

1222
        return do_add(&c, version, kernel, initrds);
×
1223
}
1224

1225
VERB_NOARG(verb_add_all, "add-all",
1226
           "Add all kernels found in /usr/lib/modules/");
1227
static int verb_add_all(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
1228
        _cleanup_close_ int fd = -EBADF;
×
1229
        size_t n = 0;
×
1230
        int ret = 0, r;
×
1231

1232
        assert(argv);
×
1233

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

1237
        if (bypass())
×
1238
                return 0;
1239

1240
        _cleanup_(context_done) Context c = CONTEXT_NULL;
×
1241
        r = context_from_cmdline(&c, ACTION_ADD);
×
1242
        if (r < 0)
×
1243
                return r;
1244

1245
        fd = chase_and_openat(c.rfd, c.rfd, "/usr/lib/modules", /* chase_flags= */ 0, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL);
×
1246
        if (fd < 0)
×
1247
                return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root));
×
1248

1249
        _cleanup_free_ DirectoryEntries *de = NULL;
×
1250
        r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_MUST_BE_DIRECTORY, &de);
×
1251
        if (r < 0)
×
1252
                return log_error_errno(r, "Failed to numerate /usr/lib/modules/ contents: %m");
×
1253

1254
        FOREACH_ARRAY(d, de->entries, de->n_entries) {
×
1255
                _cleanup_free_ char *fn = path_join((*d)->d_name, "vmlinuz");
×
1256
                if (!fn)
×
1257
                        return log_oom();
×
1258

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

1263
                        log_notice("Not adding version '%s', because kernel image not found.", (*d)->d_name);
×
1264
                        continue;
×
1265
                }
1266

1267
                _cleanup_(context_done) Context copy = CONTEXT_NULL;
×
1268

1269
                r = context_copy(&c, &copy);
×
1270
                if (r < 0)
×
1271
                        return log_error_errno(r, "Failed to copy execution context: %m");
×
1272

1273
                /* do_add() will look up the path in the correct root directory so we don't need to prefix it
1274
                 * with arg_root here. */
1275
                _cleanup_free_ char *full = path_join("/usr/lib/modules/", fn);
×
1276
                if (!full)
×
1277
                        return log_oom();
×
1278

1279
                r = do_add(&copy,
×
1280
                           /* version= */ (*d)->d_name,
×
1281
                           /* kernel= */ full,
1282
                           /* initrds= */ NULL);
1283
                if (r == 0)
×
1284
                        n++;
×
1285
                else if (ret == 0)
×
1286
                        ret = r;
×
1287
        }
1288

1289
        if (n > 0)
×
1290
                log_debug("Installed %zu kernel(s).", n);
×
1291
        else if (ret == 0)
×
1292
                ret = log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No kernels to install found.");
×
1293

1294
        return ret;
1295
}
1296

1297
static int run_as_installkernel(char **args) {
×
1298
        /* kernel's install.sh invokes us as
1299
         *   /sbin/installkernel <version> <vmlinuz> <map> <installation-dir>
1300
         * We ignore the last two arguments. */
1301
        if (strv_length(args) < 2)
×
1302
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "'installkernel' command requires at least two arguments.");
×
1303

1304
        return verb_add(3, STRV_MAKE("add", args[0], args[1]), /* data= */ 0, /* userdata= */ NULL);
×
1305
}
1306

1307
VERB(verb_remove, "remove", "KERNEL-VERSION", 2, VERB_ANY, 0,
1308
     "Remove a kernel from the boot partition");
1309
static int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
1310
        int r;
×
1311

1312
        assert(argc >= 2);
×
1313
        assert(argv);
×
1314

1315
        if (arg_root)
×
1316
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'remove' does not support --root= or --image=.");
×
1317

1318
        if (argc > 2)
×
1319
                log_debug("Too many arguments specified. 'kernel-install remove' takes only kernel version. "
×
1320
                          "Ignoring residual arguments.");
1321

1322
        if (bypass())
×
1323
                return 0;
1324

1325
        _cleanup_(context_done) Context c = CONTEXT_NULL;
×
1326
        r = context_from_cmdline(&c, ACTION_REMOVE);
×
1327
        if (r < 0)
×
1328
                return r;
1329

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

1334
        r = context_set_version(&c, argv[1]);
×
1335
        if (r < 0)
×
1336
                return r;
1337

1338
        r = context_prepare_execution(&c);
×
1339
        if (r < 0)
×
1340
                return r;
1341

1342
        return context_execute(&c);
×
1343
}
1344

1345
VERB(verb_inspect, "inspect", "[[[KERNEL-VERSION] KERNEL-IMAGE] [INITRD ...]]", 1, VERB_ANY, VERB_DEFAULT,
1346
     "Print details about the installation");
1347
static int verb_inspect(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
1348
        _cleanup_(table_unrefp) Table *t = NULL;
×
1349
        _cleanup_free_ char *vmlinuz = NULL;
×
1350
        const char *version, *kernel;
×
1351
        char **initrds;
×
1352
        struct utsname un;
×
1353
        int r;
×
1354

1355
        _cleanup_(context_done) Context c = CONTEXT_NULL;
×
1356
        r = context_from_cmdline(&c, ACTION_INSPECT);
×
1357
        if (r < 0)
×
1358
                return r;
1359

1360
        /* When only a single parameter is specified 'inspect' it's the kernel image path, and not the kernel
1361
         * version. i.e. it's the first argument that is optional, not the 2nd. That's a bit unfortunate, but
1362
         * we keep the behaviour for compatibility. If users want to specify only the version (and have the
1363
         * kernel image path derived automatically), then they may specify an empty string or "dash" as
1364
         * kernel image path. */
1365
        version = argc > 2 ? empty_or_dash_to_null(argv[1]) : NULL;
×
1366
        kernel = argc > 2 ? empty_or_dash_to_null(argv[2]) :
×
1367
                (argc > 1 ? empty_or_dash_to_null(argv[1]) : NULL);
×
1368
        initrds = strv_skip(argv, 3);
×
1369

1370
        if (!version && !arg_root) {
×
1371
                assert_se(uname(&un) >= 0);
×
1372
                version = un.release;
1373
        }
1374

1375
        if (!kernel && version) {
×
1376
                r = kernel_from_version(version, &vmlinuz);
×
1377
                if (r < 0)
×
1378
                        return r;
1379

1380
                kernel = vmlinuz;
×
1381
        }
1382

1383
        r = context_set_version(&c, version);
×
1384
        if (r < 0)
×
1385
                return r;
1386

1387
        r = context_set_kernel(&c, kernel);
×
1388
        if (r < 0)
×
1389
                return r;
1390

1391
        r = context_set_initrds(&c, initrds);
×
1392
        if (r < 0)
×
1393
                return r;
1394

1395
        r = context_prepare_execution(&c);
×
1396
        if (r < 0)
×
1397
                return r;
1398

1399
        t = table_new_vertical();
×
1400
        if (!t)
×
1401
                return log_oom();
×
1402

1403
        r = table_add_many(t,
×
1404
                           TABLE_FIELD, "Machine ID",
1405
                           TABLE_ID128, c.machine_id,
1406
                           TABLE_FIELD, "Kernel Image Type",
1407
                           TABLE_STRING, kernel_image_type_to_string(c.kernel_image_type),
1408
                           TABLE_FIELD, "Layout",
1409
                           TABLE_STRING, context_get_layout(&c),
1410
                           TABLE_FIELD, "Boot Root",
1411
                           TABLE_STRING, c.boot_root,
1412
                           TABLE_FIELD, "Entry Token Type",
1413
                           TABLE_STRING, boot_entry_token_type_to_string(c.entry_token_type),
1414
                           TABLE_FIELD, "Entry Token",
1415
                           TABLE_STRING, c.entry_token,
1416
                           TABLE_FIELD, "Entry Directory",
1417
                           TABLE_STRING, c.entry_dir,
1418
                           TABLE_FIELD, "Kernel Version",
1419
                           TABLE_VERSION, c.version,
1420
                           TABLE_FIELD, "Kernel",
1421
                           TABLE_STRING, c.kernel,
1422
                           TABLE_FIELD, "Initrds",
1423
                           TABLE_STRV, c.initrds,
1424
                           TABLE_FIELD, "Initrd Generator",
1425
                           TABLE_STRING, c.initrd_generator,
1426
                           TABLE_FIELD, "UKI Generator",
1427
                           TABLE_STRING, c.uki_generator,
1428
                           TABLE_FIELD, "Plugins",
1429
                           TABLE_STRV, c.plugins,
1430
                           TABLE_FIELD, "Plugin Environment",
1431
                           TABLE_STRV, c.envp);
1432
        if (r < 0)
×
1433
                return table_log_add_error(r);
×
1434

1435
        if (!sd_json_format_enabled(arg_json_format_flags)) {
×
1436
                r = table_add_many(t,
×
1437
                                   TABLE_FIELD, "Plugin Arguments",
1438
                                   TABLE_STRV, strv_skip(c.argv, 1));
1439
                if (r < 0)
×
1440
                        return table_log_add_error(r);
×
1441
        }
1442

1443
        table_set_ersatz_string(t, TABLE_ERSATZ_UNSET);
×
1444

1445
        for (size_t row = 1; row < table_get_rows(t); row++) {
×
1446
                _cleanup_free_ char *name = NULL;
×
1447

1448
                name = strdup(table_get_at(t, row, 0));
×
1449
                if (!name)
×
1450
                        return log_oom();
×
1451

1452
                r = table_set_json_field_name(t, row - 1, delete_chars(name, " "));
×
1453
                if (r < 0)
×
1454
                        return log_error_errno(r, "Failed to set JSON field name: %m");
×
1455
        }
1456

1457
        return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, /* show_header= */ false);
×
1458
}
1459

1460
VERB_NOARG(verb_list, "list",
1461
           "List installed kernels");
1462
static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
1463
        _cleanup_close_ int fd = -EBADF;
×
1464
        int r;
×
1465

1466
        _cleanup_(context_done) Context c = CONTEXT_NULL;
×
1467
        r = context_from_cmdline(&c, ACTION_INSPECT);
×
1468
        if (r < 0)
×
1469
                return r;
1470

1471
        fd = chase_and_openat(c.rfd, c.rfd, "/usr/lib/modules", /* chase_flags= */ 0, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL);
×
1472
        if (fd < 0)
×
1473
                return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root));
×
1474

1475
        _cleanup_free_ DirectoryEntries *de = NULL;
×
1476
        r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_MUST_BE_DIRECTORY, &de);
×
1477
        if (r < 0)
×
1478
                return log_error_errno(r, "Failed to numerate /usr/lib/modules/ contents: %m");
×
1479

1480
        _cleanup_(table_unrefp) Table *table = NULL;
×
1481
        table = table_new("version", "has kernel", "path");
×
1482
        if (!table)
×
1483
                return log_oom();
×
1484

1485
        table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
×
1486
        table_set_align_percent(table, table_get_cell(table, 0, 1), 100);
×
1487
        (void) table_set_sort(table, (size_t) 0);
×
1488

1489
        FOREACH_ARRAY(d, de->entries, de->n_entries) {
×
1490
                _cleanup_free_ char *j = path_join("/usr/lib/modules/", (*d)->d_name);
×
1491
                if (!j)
×
1492
                        return log_oom();
×
1493

1494
                _cleanup_free_ char *fn = path_join((*d)->d_name, "vmlinuz");
×
1495
                if (!fn)
×
1496
                        return log_oom();
×
1497

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

1503
                        exists = false;
1504
                } else
1505
                        exists = true;
1506

1507
                r = table_add_many(table,
×
1508
                                   TABLE_VERSION, (*d)->d_name,
1509
                                   TABLE_BOOLEAN_CHECKMARK, exists,
1510
                                   TABLE_SET_COLOR, ansi_highlight_green_red(exists),
1511
                                   TABLE_PATH, j);
1512
                if (r < 0)
×
1513
                        return table_log_add_error(r);
×
1514
        }
1515

1516
        return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
×
1517
}
1518

1519
static int help(void) {
×
1520
        _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL;
×
1521
        int r;
×
1522

1523
        r = verbs_get_help_table(&verbs);
×
1524
        if (r < 0)
×
1525
                return r;
1526

1527
        r = option_parser_get_help_table(&options);
×
1528
        if (r < 0)
×
1529
                return r;
1530

1531
        /* Note: column widths are not synced, because the verbs table is very wide. */
1532

1533
        help_cmdline("[OPTIONS…] COMMAND …");
×
1534
        help_abstract("Add and remove kernel and initrd images to and from the boot partition.");
×
1535

1536
        help_section("Commands");
×
1537
        r = table_print_or_warn(verbs);
×
1538
        if (r < 0)
×
1539
                return r;
1540

1541
        help_section("Options");
×
1542
        r = table_print_or_warn(options);
×
1543
        if (r < 0)
×
1544
                return r;
1545

1546
        printf("\n"
×
1547
               "This program may also be invoked as 'installkernel':\n"
1548
               "  installkernel [OPTIONS...] VERSION VMLINUZ [MAP] [INSTALLATION-DIR]\n"
1549
               "(The optional arguments are passed by kernel build system, but ignored.)\n");
1550

1551
        help_man_page_reference("kernel-install", "8");
×
1552

1553
        return 0;
1554
}
1555

1556
VERB_COMMON_HELP(help);
×
1557

1558
static int parse_argv(int argc, char *argv[], char ***remaining_args) {
×
1559
        assert(argc >= 0);
×
1560
        assert(argv);
×
1561
        assert(remaining_args);
×
1562

1563
        OptionParser opts = { argc, argv };
×
1564
        int r;
×
1565

1566
        FOREACH_OPTION_OR_RETURN(c, &opts)
×
1567
                switch (c) {
×
1568

1569
                OPTION_COMMON_HELP:
×
1570
                        return help();
×
1571

1572
                OPTION_COMMON_VERSION:
×
1573
                        return version();
×
1574

1575
                OPTION_COMMON_NO_LEGEND:
×
1576
                        arg_legend = false;
×
1577
                        break;
×
1578

1579
                OPTION('v', "verbose", NULL, "Increase verbosity"):
×
1580
                        log_set_max_level(LOG_DEBUG);
×
1581
                        arg_verbose = true;
×
1582
                        break;
×
1583

1584
                OPTION_LONG("esp-path", "PATH", "Path to the EFI System Partition (ESP)"):
×
1585
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_esp_path);
×
1586
                        if (r < 0)
×
1587
                                return r;
1588
                        break;
1589

1590
                OPTION_LONG("boot-path", "PATH", "Path to the $BOOT partition"):
×
1591
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_xbootldr_path);
×
1592
                        if (r < 0)
×
1593
                                return r;
1594
                        break;
1595

1596
                OPTION_COMMON_MAKE_ENTRY_DIRECTORY:
×
1597
                        if (streq(opts.arg, "auto"))
×
1598
                                arg_make_entry_directory = -1;
×
1599
                        else {
1600
                                r = parse_boolean_argument("--make-entry-directory=", opts.arg, NULL);
×
1601
                                if (r < 0)
×
1602
                                        return r;
1603

1604
                                arg_make_entry_directory = r;
×
1605
                        }
1606
                        break;
1607

1608
                OPTION_COMMON_ENTRY_TOKEN:
×
1609
                        r = parse_boot_entry_token_type(opts.arg, &arg_entry_token_type, &arg_entry_token);
×
1610
                        if (r < 0)
×
1611
                                return r;
1612
                        break;
1613

1614
                OPTION_LONG("entry-type", "TYPE",
×
1615
                            "Operate only on the specified bootloader entry type (type1, type2, all)"): {
1616
                        if (isempty(opts.arg) || streq(opts.arg, "all")) {
×
1617
                                arg_boot_entry_type = _BOOT_ENTRY_TYPE_INVALID;
×
1618
                                break;
×
1619
                        }
1620

1621
                        BootEntryType e = boot_entry_type_from_string(opts.arg);
×
1622
                        if (e < 0)
×
1623
                                return log_error_errno(e, "Invalid entry type: %s", opts.arg);
×
1624
                        arg_boot_entry_type = e;
×
1625
                        break;
×
1626
                }
1627

1628
                OPTION_COMMON_NO_PAGER:
×
1629
                        arg_pager_flags |= PAGER_DISABLE;
×
1630
                        break;
×
1631

1632
                OPTION_COMMON_JSON:
×
1633
                        r = parse_json_argument(opts.arg, &arg_json_format_flags);
×
1634
                        if (r <= 0)
×
1635
                                return r;
1636
                        break;
1637

1638
                OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"):
×
1639
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_root);
×
1640
                        if (r < 0)
×
1641
                                return r;
1642
                        break;
1643

1644
                OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"):
×
1645
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_image);
×
1646
                        if (r < 0)
×
1647
                                return r;
1648
                        break;
1649

1650
                OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"):
×
1651
                        r = parse_image_policy_argument(opts.arg, &arg_image_policy);
×
1652
                        if (r < 0)
×
1653
                                return r;
1654
                        break;
1655
                }
1656

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

1661
        *remaining_args = option_parser_get_args(&opts);
×
1662
        return 1;
×
1663
}
1664

1665
static int run(int argc, char* argv[]) {
×
1666
        int r;
×
1667

1668
        LIBBLKID_NOTE(recommended);
×
1669
        LIBCRYPTO_NOTE(suggested);
×
1670
        LIBCRYPTSETUP_NOTE(suggested);
×
1671
        LIBMOUNT_NOTE(recommended);
×
1672

1673
        log_setup();
×
1674

1675
        char **args = NULL;
×
1676
        r = parse_argv(argc, argv, &args);
×
1677
        if (r <= 0)
×
1678
                return r;
×
1679

1680
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
×
1681
        _cleanup_(umount_and_freep) char *mounted_dir = NULL;
×
1682
        if (arg_image) {
×
1683
                assert(!arg_root);
×
1684

1685
                r = mount_image_privately_interactively(
×
1686
                                arg_image,
1687
                                arg_image_policy,
1688
                                DISSECT_IMAGE_GENERIC_ROOT |
1689
                                DISSECT_IMAGE_REQUIRE_ROOT |
1690
                                DISSECT_IMAGE_RELAX_VAR_CHECK |
1691
                                DISSECT_IMAGE_VALIDATE_OS |
1692
                                DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
1693
                                &mounted_dir,
1694
                                /* ret_dir_fd= */ NULL,
1695
                                &loop_device);
1696
                if (r < 0)
×
1697
                        return r;
1698

1699
                arg_root = strdup(mounted_dir);
×
1700
                if (!arg_root)
×
1701
                        return log_oom();
×
1702
        }
1703

1704
        if (invoked_as(argv, "installkernel"))
×
1705
                return run_as_installkernel(args);
×
1706

1707
        return dispatch_verb(args, /* userdata= */ NULL);
×
1708
}
1709

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

© 2026 Coveralls, Inc