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

systemd / systemd / 25893429527

14 May 2026 09:08PM UTC coverage: 72.364% (-0.2%) from 72.584%
25893429527

push

github

bluca
ci: switch SUSE mkosi mirror to cdn.o.o

The cdn mirror is preferred by SUSE for clouds/CIs. There have been issues with some
mirrors, which fail to download from GHA quite often lately, so hopefully this will
make it reliable again.

328159 of 453485 relevant lines covered (72.36%)

1405869.02 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 "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 "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(source->rfd >= 0 || source->rfd == AT_FDCWD || source->rfd == XAT_FDROOT);
×
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, &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
                r = dirent_ensure_type(fd, *d);
×
1256
                if (r < 0) {
×
1257
                        if (r != -ENOENT) /* don't log if just gone by now */
×
1258
                                log_debug_errno(r, "Failed to check if '%s/usr/lib/modules/%s' is a directory, ignoring: %m", strempty(arg_root), (*d)->d_name);
×
1259
                        continue;
×
1260
                }
1261

1262
                if ((*d)->d_type != DT_DIR)
×
1263
                        continue;
×
1264

1265
                _cleanup_free_ char *fn = path_join((*d)->d_name, "vmlinuz");
×
1266
                if (!fn)
×
1267
                        return log_oom();
×
1268

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

1273
                        log_notice("Not adding version '%s', because kernel image not found.", (*d)->d_name);
×
1274
                        continue;
×
1275
                }
1276

1277
                _cleanup_(context_done) Context copy = CONTEXT_NULL;
×
1278

1279
                r = context_copy(&c, &copy);
×
1280
                if (r < 0)
×
1281
                        return log_error_errno(r, "Failed to copy execution context: %m");
×
1282

1283
                /* do_add() will look up the path in the correct root directory so we don't need to prefix it
1284
                 * with arg_root here. */
1285
                _cleanup_free_ char *full = path_join("/usr/lib/modules/", fn);
×
1286
                if (!full)
×
1287
                        return log_oom();
×
1288

1289
                r = do_add(&copy,
×
1290
                           /* version= */ (*d)->d_name,
×
1291
                           /* kernel= */ full,
1292
                           /* initrds= */ NULL);
1293
                if (r == 0)
×
1294
                        n++;
×
1295
                else if (ret == 0)
×
1296
                        ret = r;
×
1297
        }
1298

1299
        if (n > 0)
×
1300
                log_debug("Installed %zu kernel(s).", n);
×
1301
        else if (ret == 0)
×
1302
                ret = log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No kernels to install found.");
×
1303

1304
        return ret;
1305
}
1306

1307
static int run_as_installkernel(char **args) {
×
1308
        /* kernel's install.sh invokes us as
1309
         *   /sbin/installkernel <version> <vmlinuz> <map> <installation-dir>
1310
         * We ignore the last two arguments. */
1311
        if (strv_length(args) < 2)
×
1312
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "'installkernel' command requires at least two arguments.");
×
1313

1314
        return verb_add(3, STRV_MAKE("add", args[0], args[1]), /* data= */ 0, /* userdata= */ NULL);
×
1315
}
1316

1317
VERB(verb_remove, "remove", "KERNEL-VERSION", 2, VERB_ANY, 0,
1318
     "Remove a kernel from the boot partition");
1319
static int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
1320
        int r;
×
1321

1322
        assert(argc >= 2);
×
1323
        assert(argv);
×
1324

1325
        if (arg_root)
×
1326
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'remove' does not support --root= or --image=.");
×
1327

1328
        if (argc > 2)
×
1329
                log_debug("Too many arguments specified. 'kernel-install remove' takes only kernel version. "
×
1330
                          "Ignoring residual arguments.");
1331

1332
        if (bypass())
×
1333
                return 0;
1334

1335
        _cleanup_(context_done) Context c = CONTEXT_NULL;
×
1336
        r = context_from_cmdline(&c, ACTION_REMOVE);
×
1337
        if (r < 0)
×
1338
                return r;
1339

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

1344
        r = context_set_version(&c, argv[1]);
×
1345
        if (r < 0)
×
1346
                return r;
1347

1348
        r = context_prepare_execution(&c);
×
1349
        if (r < 0)
×
1350
                return r;
1351

1352
        return context_execute(&c);
×
1353
}
1354

1355
VERB(verb_inspect, "inspect", "[[[KERNEL-VERSION] KERNEL-IMAGE] [INITRD ...]]", 1, VERB_ANY, VERB_DEFAULT,
1356
     "Print details about the installation");
1357
static int verb_inspect(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
1358
        _cleanup_(table_unrefp) Table *t = NULL;
×
1359
        _cleanup_free_ char *vmlinuz = NULL;
×
1360
        const char *version, *kernel;
×
1361
        char **initrds;
×
1362
        struct utsname un;
×
1363
        int r;
×
1364

1365
        _cleanup_(context_done) Context c = CONTEXT_NULL;
×
1366
        r = context_from_cmdline(&c, ACTION_INSPECT);
×
1367
        if (r < 0)
×
1368
                return r;
1369

1370
        /* When only a single parameter is specified 'inspect' it's the kernel image path, and not the kernel
1371
         * version. i.e. it's the first argument that is optional, not the 2nd. That's a bit unfortunate, but
1372
         * we keep the behaviour for compatibility. If users want to specify only the version (and have the
1373
         * kernel image path derived automatically), then they may specify an empty string or "dash" as
1374
         * kernel image path. */
1375
        version = argc > 2 ? empty_or_dash_to_null(argv[1]) : NULL;
×
1376
        kernel = argc > 2 ? empty_or_dash_to_null(argv[2]) :
×
1377
                (argc > 1 ? empty_or_dash_to_null(argv[1]) : NULL);
×
1378
        initrds = strv_skip(argv, 3);
×
1379

1380
        if (!version && !arg_root) {
×
1381
                assert_se(uname(&un) >= 0);
×
1382
                version = un.release;
1383
        }
1384

1385
        if (!kernel && version) {
×
1386
                r = kernel_from_version(version, &vmlinuz);
×
1387
                if (r < 0)
×
1388
                        return r;
1389

1390
                kernel = vmlinuz;
×
1391
        }
1392

1393
        r = context_set_version(&c, version);
×
1394
        if (r < 0)
×
1395
                return r;
1396

1397
        r = context_set_kernel(&c, kernel);
×
1398
        if (r < 0)
×
1399
                return r;
1400

1401
        r = context_set_initrds(&c, initrds);
×
1402
        if (r < 0)
×
1403
                return r;
1404

1405
        r = context_prepare_execution(&c);
×
1406
        if (r < 0)
×
1407
                return r;
1408

1409
        t = table_new_vertical();
×
1410
        if (!t)
×
1411
                return log_oom();
×
1412

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

1445
        if (!sd_json_format_enabled(arg_json_format_flags)) {
×
1446
                r = table_add_many(t,
×
1447
                                   TABLE_FIELD, "Plugin Arguments",
1448
                                   TABLE_STRV, strv_skip(c.argv, 1));
1449
                if (r < 0)
×
1450
                        return table_log_add_error(r);
×
1451
        }
1452

1453
        table_set_ersatz_string(t, TABLE_ERSATZ_UNSET);
×
1454

1455
        for (size_t row = 1; row < table_get_rows(t); row++) {
×
1456
                _cleanup_free_ char *name = NULL;
×
1457

1458
                name = strdup(table_get_at(t, row, 0));
×
1459
                if (!name)
×
1460
                        return log_oom();
×
1461

1462
                r = table_set_json_field_name(t, row - 1, delete_chars(name, " "));
×
1463
                if (r < 0)
×
1464
                        return log_error_errno(r, "Failed to set JSON field name: %m");
×
1465
        }
1466

1467
        return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, /* show_header= */ false);
×
1468
}
1469

1470
VERB_NOARG(verb_list, "list",
1471
           "List installed kernels");
1472
static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
1473
        _cleanup_close_ int fd = -EBADF;
×
1474
        int r;
×
1475

1476
        _cleanup_(context_done) Context c = CONTEXT_NULL;
×
1477
        r = context_from_cmdline(&c, ACTION_INSPECT);
×
1478
        if (r < 0)
×
1479
                return r;
1480

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

1485
        _cleanup_free_ DirectoryEntries *de = NULL;
×
1486
        r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
×
1487
        if (r < 0)
×
1488
                return log_error_errno(r, "Failed to numerate /usr/lib/modules/ contents: %m");
×
1489

1490
        _cleanup_(table_unrefp) Table *table = NULL;
×
1491
        table = table_new("version", "has kernel", "path");
×
1492
        if (!table)
×
1493
                return log_oom();
×
1494

1495
        table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
×
1496
        table_set_align_percent(table, table_get_cell(table, 0, 1), 100);
×
1497
        (void) table_set_sort(table, (size_t) 0);
×
1498

1499
        FOREACH_ARRAY(d, de->entries, de->n_entries) {
×
1500
                _cleanup_free_ char *j = path_join("/usr/lib/modules/", (*d)->d_name);
×
1501
                if (!j)
×
1502
                        return log_oom();
×
1503

1504
                r = dirent_ensure_type(fd, *d);
×
1505
                if (r < 0) {
×
1506
                        if (r != -ENOENT) /* don't log if just gone by now */
×
1507
                                log_debug_errno(r, "Failed to check if '%s/%s' is a directory, ignoring: %m", strempty(arg_root), j);
×
1508
                        continue;
×
1509
                }
1510

1511
                if ((*d)->d_type != DT_DIR)
×
1512
                        continue;
×
1513

1514
                _cleanup_free_ char *fn = path_join((*d)->d_name, "vmlinuz");
×
1515
                if (!fn)
×
1516
                        return log_oom();
×
1517

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

1523
                        exists = false;
1524
                } else
1525
                        exists = true;
1526

1527
                r = table_add_many(table,
×
1528
                                   TABLE_VERSION, (*d)->d_name,
1529
                                   TABLE_BOOLEAN_CHECKMARK, exists,
1530
                                   TABLE_SET_COLOR, ansi_highlight_green_red(exists),
1531
                                   TABLE_PATH, j);
1532
                if (r < 0)
×
1533
                        return table_log_add_error(r);
×
1534
        }
1535

1536
        return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
×
1537
}
1538

1539
static int help(void) {
×
1540
        _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL;
×
1541
        int r;
×
1542

1543
        r = verbs_get_help_table(&verbs);
×
1544
        if (r < 0)
×
1545
                return r;
1546

1547
        r = option_parser_get_help_table(&options);
×
1548
        if (r < 0)
×
1549
                return r;
1550

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

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

1556
        help_section("Commands");
×
1557
        r = table_print_or_warn(verbs);
×
1558
        if (r < 0)
×
1559
                return r;
1560

1561
        help_section("Options");
×
1562
        r = table_print_or_warn(options);
×
1563
        if (r < 0)
×
1564
                return r;
1565

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

1571
        help_man_page_reference("kernel-install", "8");
×
1572

1573
        return 0;
1574
}
1575

1576
VERB_COMMON_HELP(help);
×
1577

1578
static int parse_argv(int argc, char *argv[], char ***remaining_args) {
×
1579
        assert(argc >= 0);
×
1580
        assert(argv);
×
1581
        assert(remaining_args);
×
1582

1583
        OptionParser opts = { argc, argv };
×
1584
        int r;
×
1585

1586
        FOREACH_OPTION_OR_RETURN(c, &opts)
×
1587
                switch (c) {
×
1588

1589
                OPTION_COMMON_HELP:
×
1590
                        return help();
×
1591

1592
                OPTION_COMMON_VERSION:
×
1593
                        return version();
×
1594

1595
                OPTION_COMMON_NO_LEGEND:
×
1596
                        arg_legend = false;
×
1597
                        break;
×
1598

1599
                OPTION('v', "verbose", NULL, "Increase verbosity"):
×
1600
                        log_set_max_level(LOG_DEBUG);
×
1601
                        arg_verbose = true;
×
1602
                        break;
×
1603

1604
                OPTION_LONG("esp-path", "PATH", "Path to the EFI System Partition (ESP)"):
×
1605
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_esp_path);
×
1606
                        if (r < 0)
×
1607
                                return r;
1608
                        break;
1609

1610
                OPTION_LONG("boot-path", "PATH", "Path to the $BOOT partition"):
×
1611
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_xbootldr_path);
×
1612
                        if (r < 0)
×
1613
                                return r;
1614
                        break;
1615

1616
                OPTION_COMMON_MAKE_ENTRY_DIRECTORY:
×
1617
                        if (streq(opts.arg, "auto"))
×
1618
                                arg_make_entry_directory = -1;
×
1619
                        else {
1620
                                r = parse_boolean_argument("--make-entry-directory=", opts.arg, NULL);
×
1621
                                if (r < 0)
×
1622
                                        return r;
1623

1624
                                arg_make_entry_directory = r;
×
1625
                        }
1626
                        break;
1627

1628
                OPTION_COMMON_ENTRY_TOKEN:
×
1629
                        r = parse_boot_entry_token_type(opts.arg, &arg_entry_token_type, &arg_entry_token);
×
1630
                        if (r < 0)
×
1631
                                return r;
1632
                        break;
1633

1634
                OPTION_LONG("entry-type", "TYPE",
×
1635
                            "Operate only on the specified bootloader entry type (type1, type2, all)"): {
1636
                        if (isempty(opts.arg) || streq(opts.arg, "all")) {
×
1637
                                arg_boot_entry_type = _BOOT_ENTRY_TYPE_INVALID;
×
1638
                                break;
×
1639
                        }
1640

1641
                        BootEntryType e = boot_entry_type_from_string(opts.arg);
×
1642
                        if (e < 0)
×
1643
                                return log_error_errno(e, "Invalid entry type: %s", opts.arg);
×
1644
                        arg_boot_entry_type = e;
×
1645
                        break;
×
1646
                }
1647

1648
                OPTION_COMMON_NO_PAGER:
×
1649
                        arg_pager_flags |= PAGER_DISABLE;
×
1650
                        break;
×
1651

1652
                OPTION_COMMON_JSON:
×
1653
                        r = parse_json_argument(opts.arg, &arg_json_format_flags);
×
1654
                        if (r <= 0)
×
1655
                                return r;
1656
                        break;
1657

1658
                OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"):
×
1659
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_root);
×
1660
                        if (r < 0)
×
1661
                                return r;
1662
                        break;
1663

1664
                OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"):
×
1665
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_image);
×
1666
                        if (r < 0)
×
1667
                                return r;
1668
                        break;
1669

1670
                OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"):
×
1671
                        r = parse_image_policy_argument(opts.arg, &arg_image_policy);
×
1672
                        if (r < 0)
×
1673
                                return r;
1674
                        break;
1675
                }
1676

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

1681
        *remaining_args = option_parser_get_args(&opts);
×
1682
        return 1;
×
1683
}
1684

1685
static int run(int argc, char* argv[]) {
×
1686
        int r;
×
1687

1688
        log_setup();
×
1689

1690
        char **args = NULL;
×
1691
        r = parse_argv(argc, argv, &args);
×
1692
        if (r <= 0)
×
1693
                return r;
×
1694

1695
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
×
1696
        _cleanup_(umount_and_freep) char *mounted_dir = NULL;
×
1697
        if (arg_image) {
×
1698
                assert(!arg_root);
×
1699

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

1714
                arg_root = strdup(mounted_dir);
×
1715
                if (!arg_root)
×
1716
                        return log_oom();
×
1717
        }
1718

1719
        if (invoked_as(argv, "installkernel"))
×
1720
                return run_as_installkernel(args);
×
1721

1722
        return dispatch_verb_with_args(args, /* userdata= */ NULL);
×
1723
}
1724

1725
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