]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/kernel-install/kernel-install.c
Fixes for vscode/intellisense parsing (#38040)
[thirdparty/systemd.git] / src / kernel-install / kernel-install.c
CommitLineData
42551ea7
YW
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <getopt.h>
8857aa74 4#include <stdlib.h>
9d9e99a8 5#include <sys/utsname.h>
8857aa74 6#include <unistd.h>
42551ea7 7
76d62b63 8#include "argv-util.h"
42551ea7 9#include "boot-entry.h"
658e6cc4 10#include "build.h"
42551ea7
YW
11#include "chase.h"
12#include "conf-files.h"
658e6cc4 13#include "dirent-util.h"
8857aa74 14#include "dissect-image.h"
42551ea7
YW
15#include "env-file.h"
16#include "env-util.h"
17#include "exec-util.h"
6553db60 18#include "extract-word.h"
42551ea7
YW
19#include "fd-util.h"
20#include "fileio.h"
21#include "find-esp.h"
bdd36c00 22#include "format-table.h"
9d9e99a8 23#include "fs-util.h"
42551ea7 24#include "id128-util.h"
04a8c2ce 25#include "image-policy.h"
3f5196ff 26#include "kernel-config.h"
42551ea7 27#include "kernel-image.h"
8857aa74 28#include "loop-util.h"
42551ea7 29#include "main-func.h"
04a8c2ce 30#include "mount-util.h"
4cff5e92 31#include "parse-argument.h"
42551ea7
YW
32#include "path-util.h"
33#include "pretty-print.h"
658e6cc4 34#include "recurse-dir.h"
42551ea7
YW
35#include "rm-rf.h"
36#include "stat-util.h"
37#include "string-table.h"
38#include "string-util.h"
39#include "strv.h"
40#include "tmpfile-util.h"
41#include "verbs.h"
42
43static bool arg_verbose = false;
4cff5e92
YW
44static char *arg_esp_path = NULL;
45static char *arg_xbootldr_path = NULL;
b79621aa 46static int arg_make_entry_directory = -1; /* tristate */
bdd36c00 47static PagerFlags arg_pager_flags = 0;
309a747f 48static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
04a8c2ce
DDM
49static char *arg_root = NULL;
50static char *arg_image = NULL;
51static ImagePolicy *arg_image_policy = NULL;
658e6cc4 52static bool arg_legend = true;
4cff5e92
YW
53
54STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep);
55STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep);
04a8c2ce 56STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
d1b1cf8f 57STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
04a8c2ce 58STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
42551ea7
YW
59
60typedef enum Action {
61 ACTION_ADD,
62 ACTION_REMOVE,
63 ACTION_INSPECT,
64 _ACTION_MAX,
65 _ACTION_INVALID = -EINVAL,
66} Action;
67
68typedef enum Layout {
69 LAYOUT_AUTO,
70 LAYOUT_UKI,
71 LAYOUT_BLS,
72 LAYOUT_OTHER,
73 _LAYOUT_MAX,
74 _LAYOUT_INVALID = -EINVAL,
75} Layout;
76
77static const char * const layout_table[_LAYOUT_MAX] = {
78 [LAYOUT_AUTO] = "auto",
79 [LAYOUT_UKI] = "uki",
80 [LAYOUT_BLS] = "bls",
81 [LAYOUT_OTHER] = "other",
82};
83
84DEFINE_PRIVATE_STRING_TABLE_LOOKUP(layout, Layout);
85
86typedef struct Context {
87 int rfd;
88 Action action;
89 sd_id128_t machine_id;
90 bool machine_id_is_random;
91 KernelImageType kernel_image_type;
92 Layout layout;
93 char *layout_other;
94 char *conf_root;
95 char *boot_root;
96 BootEntryTokenType entry_token_type;
97 char *entry_token;
98 char *entry_dir;
99 char *version;
100 char *kernel;
101 char **initrds;
102 char *initrd_generator;
103 char *uki_generator;
104 char *staging_area;
105 char **plugins;
106 char **argv;
107 char **envp;
108} Context;
109
ec1399f5
LP
110#define CONTEXT_NULL (Context) { .rfd = -EBADF }
111
42551ea7
YW
112static void context_done(Context *c) {
113 assert(c);
114
115 free(c->layout_other);
116 free(c->conf_root);
117 free(c->boot_root);
118 free(c->entry_token);
119 free(c->entry_dir);
120 free(c->version);
121 free(c->kernel);
122 strv_free(c->initrds);
123 free(c->initrd_generator);
124 free(c->uki_generator);
125 if (c->action == ACTION_INSPECT)
126 free(c->staging_area);
127 else
128 rm_rf_physical_and_free(c->staging_area);
129 strv_free(c->plugins);
130 strv_free(c->argv);
131 strv_free(c->envp);
132
133 safe_close(c->rfd);
134}
135
ec1399f5
LP
136static int context_copy(const Context *source, Context *ret) {
137 int r;
138
139 assert(source);
140 assert(ret);
27d420f4 141 assert(source->rfd >= 0 || source->rfd == AT_FDCWD);
ec1399f5
LP
142
143 _cleanup_(context_done) Context copy = (Context) {
27d420f4 144 .rfd = AT_FDCWD,
ec1399f5
LP
145 .action = source->action,
146 .machine_id = source->machine_id,
147 .machine_id_is_random = source->machine_id_is_random,
148 .kernel_image_type = source->kernel_image_type,
149 .layout = source->layout,
150 .entry_token_type = source->entry_token_type,
151 };
152
27d420f4
LN
153 if (source->rfd >= 0) {
154 copy.rfd = fd_reopen(source->rfd, O_CLOEXEC|O_DIRECTORY|O_PATH);
155 if (copy.rfd < 0)
156 return copy.rfd;
157 }
ec1399f5 158
6a705f12 159 r = strdup_to(&copy.layout_other, source->layout_other);
ec1399f5
LP
160 if (r < 0)
161 return r;
6a705f12 162 r = strdup_to(&copy.conf_root, source->conf_root);
ec1399f5
LP
163 if (r < 0)
164 return r;
6a705f12 165 r = strdup_to(&copy.boot_root, source->boot_root);
ec1399f5
LP
166 if (r < 0)
167 return r;
6a705f12 168 r = strdup_to(&copy.entry_token, source->entry_token);
ec1399f5
LP
169 if (r < 0)
170 return r;
6a705f12 171 r = strdup_to(&copy.entry_dir, source->entry_dir);
ec1399f5
LP
172 if (r < 0)
173 return r;
6a705f12 174 r = strdup_to(&copy.version, source->version);
ec1399f5
LP
175 if (r < 0)
176 return r;
6a705f12 177 r = strdup_to(&copy.kernel, source->kernel);
ec1399f5
LP
178 if (r < 0)
179 return r;
27d420f4
LN
180 r = strv_copy_unless_empty(source->initrds, &copy.initrds);
181 if (r < 0)
182 return r;
6a705f12 183 r = strdup_to(&copy.initrd_generator, source->initrd_generator);
ec1399f5
LP
184 if (r < 0)
185 return r;
6a705f12 186 r = strdup_to(&copy.uki_generator, source->uki_generator);
ec1399f5
LP
187 if (r < 0)
188 return r;
6a705f12 189 r = strdup_to(&copy.staging_area, source->staging_area);
ec1399f5
LP
190 if (r < 0)
191 return r;
27d420f4
LN
192 r = strv_copy_unless_empty(source->plugins, &copy.plugins);
193 if (r < 0)
194 return r;
195 r = strv_copy_unless_empty(source->argv, &copy.argv);
196 if (r < 0)
197 return r;
198 r = strv_copy_unless_empty(source->envp, &copy.envp);
199 if (r < 0)
200 return r;
ec1399f5
LP
201
202 *ret = copy;
203 copy = CONTEXT_NULL;
204
205 return 0;
206}
207
42551ea7 208static int context_open_root(Context *c) {
54bc114f
YW
209 int r;
210
42551ea7
YW
211 assert(c);
212 assert(c->rfd < 0);
213
54bc114f
YW
214 if (isempty(arg_root))
215 return 0;
216
217 r = path_is_root(arg_root);
218 if (r < 0)
219 return log_error_errno(r, "Failed to determine if '%s' is the root directory: %m", arg_root);
220 if (r > 0)
221 return 0;
222
04a8c2ce 223 c->rfd = open(empty_to_root(arg_root), O_CLOEXEC | O_DIRECTORY | O_PATH);
42551ea7 224 if (c->rfd < 0)
04a8c2ce 225 return log_error_errno(errno, "Failed to open root directory '%s': %m", empty_to_root(arg_root));
42551ea7
YW
226
227 return 0;
228}
229
230static const char* context_get_layout(const Context *c) {
231 assert(c);
232 assert(c->layout >= 0);
233
234 return c->layout_other ?: layout_to_string(c->layout);
235}
236
237static int context_set_layout(Context *c, const char *s, const char *source) {
238 Layout t;
239
240 assert(c);
241 assert(source);
242
243 if (c->layout >= 0 || !s)
244 return 0;
245
246 assert(!c->layout_other);
247
248 t = layout_from_string(s);
249 if (t >= 0)
250 c->layout = t;
251 else if (isempty(s))
252 c->layout = LAYOUT_AUTO;
253 else {
254 c->layout_other = strdup(s);
255 if (!c->layout_other)
256 return log_oom();
257
258 c->layout = LAYOUT_OTHER;
259 }
260
261 log_debug("layout=%s set via %s", context_get_layout(c), source);
262 return 1;
263}
264
265static int context_set_machine_id(Context *c, const char *s, const char *source) {
266 int r;
267
268 assert(c);
269 assert(source);
270
271 if (!sd_id128_is_null(c->machine_id) || !s)
272 return 0;
273
274 r = sd_id128_from_string(s, &c->machine_id);
275 if (r < 0)
276 return log_warning_errno(r, "Failed to parse machine ID specified via %s, ignoring.", source);
277
278 if (sd_id128_is_null(c->machine_id))
279 return 0;
280
281 log_debug("MACHINE_ID=%s set via %s.", SD_ID128_TO_STRING(c->machine_id), source);
282 return 1;
283}
284
285static int context_set_string(const char *s, const char *source, const char *name, char **dest) {
286 char *p;
287
288 assert(source);
289 assert(name);
290 assert(dest);
291
292 if (*dest || !s)
293 return 0;
294
295 p = strdup(s);
296 if (!p)
297 return log_oom();
298
299 log_debug("%s (%s) set via %s.", name, p, source);
300
301 *dest = p;
302 return 1;
303}
304
305static int context_set_initrd_generator(Context *c, const char *s, const char *source) {
306 assert(c);
307 return context_set_string(s, source, "INITRD_GENERATOR", &c->initrd_generator);
308}
309
310static int context_set_uki_generator(Context *c, const char *s, const char *source) {
311 assert(c);
312 return context_set_string(s, source, "UKI_GENERATOR", &c->uki_generator);
313}
314
315static int context_set_version(Context *c, const char *s) {
316 assert(c);
317
dbab0056 318 if (s && !filename_is_valid(s))
42551ea7
YW
319 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid version specified: %s", s);
320
321 return context_set_string(s, "command line", "kernel version", &c->version);
322}
323
54bc114f 324static int context_set_path(Context *c, const char *s, const char *source, const char *name, char **dest) {
42551ea7
YW
325 char *p;
326 int r;
327
328 assert(c);
329 assert(source);
330 assert(name);
331 assert(dest);
332
333 if (*dest || !s)
334 return 0;
335
54bc114f
YW
336 if (c->rfd >= 0) {
337 r = chaseat(c->rfd, s, CHASE_AT_RESOLVE_IN_ROOT, &p, /* ret_fd = */ NULL);
338 if (r < 0)
339 return log_warning_errno(r, "Failed to chase path %s for %s specified via %s, ignoring: %m",
340 s, name, source);
341 } else {
342 r = path_make_absolute_cwd(s, &p);
343 if (r < 0)
344 return log_warning_errno(r, "Failed to make path '%s' for %s specified via %s absolute, ignoring: %m",
345 s, name, source);
346 }
42551ea7
YW
347
348 log_debug("%s (%s) set via %s.", name, p, source);
349
350 *dest = p;
351 return 1;
352}
353
354static int context_set_boot_root(Context *c, const char *s, const char *source) {
355 assert(c);
54bc114f 356 return context_set_path(c, s, source, "BOOT_ROOT", &c->boot_root);
42551ea7
YW
357}
358
359static int context_set_conf_root(Context *c, const char *s, const char *source) {
360 assert(c);
54bc114f 361 return context_set_path(c, s, source, "CONF_ROOT", &c->conf_root);
42551ea7
YW
362}
363
364static int context_set_kernel(Context *c, const char *s) {
365 assert(c);
54bc114f 366 return context_set_path(c, s, "command line", "kernel image file", &c->kernel);
42551ea7
YW
367}
368
54bc114f 369static int context_set_path_strv(Context *c, char* const* strv, const char *source, const char *name, char ***dest) {
42551ea7
YW
370 _cleanup_strv_free_ char **w = NULL;
371 int r;
372
373 assert(c);
374 assert(source);
375 assert(name);
376 assert(dest);
377
378 if (*dest)
379 return 0;
380
381 STRV_FOREACH(s, strv) {
382 char *p;
383
54bc114f
YW
384 if (c->rfd >= 0) {
385 r = chaseat(c->rfd, *s, CHASE_AT_RESOLVE_IN_ROOT, &p, /* ret_fd = */ NULL);
386 if (r < 0)
387 return log_warning_errno(r, "Failed to chase path %s for %s specified via %s: %m",
388 *s, name, source);
389 } else {
390 r = path_make_absolute_cwd(*s, &p);
391 if (r < 0)
392 return log_warning_errno(r, "Failed to make path '%s' for %s specified via %s absolute, ignoring: %m",
393 *s, name, source);
394 }
42551ea7
YW
395 r = strv_consume(&w, p);
396 if (r < 0)
397 return log_oom();
398 }
399
400 if (strv_isempty(w))
401 return 0;
402
403 log_debug("%s set via %s", name, source);
404
405 *dest = TAKE_PTR(w);
406 return 1;
407}
408
409static int context_set_plugins(Context *c, const char *s, const char *source) {
410 _cleanup_strv_free_ char **v = NULL;
6e1816ef 411 int r;
42551ea7
YW
412
413 assert(c);
414
415 if (c->plugins || !s)
416 return 0;
417
6e1816ef
YW
418 r = strv_split_full(&v, s, NULL, EXTRACT_UNQUOTE);
419 if (r < 0)
420 return log_error_errno(r, "Failed to parse plugin paths from %s: %m", source);
42551ea7 421
54bc114f 422 return context_set_path_strv(c, v, source, "plugins", &c->plugins);
42551ea7
YW
423}
424
425static int context_set_initrds(Context *c, char* const* strv) {
426 assert(c);
54bc114f 427 return context_set_path_strv(c, strv, "command line", "initrds", &c->initrds);
42551ea7
YW
428}
429
430static int context_load_environment(Context *c) {
431 assert(c);
432
433 (void) context_set_machine_id(c, getenv("MACHINE_ID"), "environment");
434 (void) context_set_boot_root(c, getenv("BOOT_ROOT"), "environment");
435 (void) context_set_conf_root(c, getenv("KERNEL_INSTALL_CONF_ROOT"), "environment");
436 (void) context_set_plugins(c, getenv("KERNEL_INSTALL_PLUGINS"), "environment");
437 return 0;
438}
439
db26d802
ZJS
440static int context_load_install_conf(Context *c) {
441 _cleanup_free_ char *machine_id = NULL, *boot_root = NULL, *layout = NULL,
442 *initrd_generator = NULL, *uki_generator = NULL;
42551ea7
YW
443 int r;
444
445 assert(c);
42551ea7 446
3f5196ff
ZJS
447 r = load_kernel_install_conf(arg_root,
448 c->conf_root,
449 &machine_id,
450 &boot_root,
451 &layout,
452 &initrd_generator,
453 &uki_generator);
454 if (r <= 0)
455 return r;
42551ea7 456
db26d802
ZJS
457 (void) context_set_machine_id(c, machine_id, "config");
458 (void) context_set_boot_root(c, boot_root, "config");
459 (void) context_set_layout(c, layout, "config");
460 (void) context_set_initrd_generator(c, initrd_generator, "config");
461 (void) context_set_uki_generator(c, uki_generator, "config");
42551ea7 462
db26d802 463 log_debug("Loaded config.");
42551ea7
YW
464 return 0;
465}
466
467static int context_load_machine_info(Context *c) {
a0613123 468 _cleanup_fclose_ FILE *f = NULL;
1fd2af1a 469 _cleanup_free_ char *machine_id = NULL, *layout = NULL;
42551ea7
YW
470 static const char *path = "/etc/machine-info";
471 int r;
472
473 assert(c);
474
475 /* If the user configured an explicit machine ID in /etc/machine-info to use for our purpose, we'll
476 * use that instead (for compatibility). */
477
1fd2af1a 478 if (!sd_id128_is_null(c->machine_id) && c->layout >= 0)
42551ea7
YW
479 return 0;
480
a5ebaf79
YW
481 /* For testing. To make not read host's /etc/machine-info. */
482 r = getenv_bool("KERNEL_INSTALL_READ_MACHINE_INFO");
483 if (r < 0 && r != -ENXIO)
484 log_warning_errno(r, "Failed to read $KERNEL_INSTALL_READ_MACHINE_INFO, assuming yes: %m");
485 if (r == 0) {
5ea4afcf 486 log_debug("Skipping reading of /etc/machine-info.");
a5ebaf79
YW
487 return 0;
488 }
489
a0613123 490 r = chase_and_fopenat_unlocked(c->rfd, path, CHASE_AT_RESOLVE_IN_ROOT, "re", NULL, &f);
42551ea7
YW
491 if (r == -ENOENT)
492 return 0;
493 if (r < 0)
494 return log_error_errno(r, "Failed to chase %s: %m", path);
495
496 log_debug("Loading %s…", path);
497
a0613123
DDM
498 r = parse_env_file(f, path,
499 "KERNEL_INSTALL_MACHINE_ID", &machine_id,
500 "KERNEL_INSTALL_LAYOUT", &layout);
42551ea7
YW
501 if (r < 0)
502 return log_error_errno(r, "Failed to parse '%s': %m", path);
503
504 (void) context_set_machine_id(c, machine_id, path);
1fd2af1a 505 (void) context_set_layout(c, layout, path);
42551ea7
YW
506 return 0;
507}
508
509static int context_load_machine_id(Context *c) {
510 int r;
511
512 assert(c);
513
514 r = id128_get_machine_at(c->rfd, &c->machine_id);
d66ad6ff
ZJS
515 if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r))
516 return 0;
517 if (r < 0)
42551ea7 518 return log_error_errno(r, "Failed to load machine ID from /etc/machine-id: %m");
42551ea7
YW
519
520 log_debug("MACHINE_ID=%s set via /etc/machine-id.", SD_ID128_TO_STRING(c->machine_id));
521 return 1; /* loaded */
522}
523
524static int context_ensure_machine_id(Context *c) {
525 int r;
526
527 assert(c);
528
529 if (!sd_id128_is_null(c->machine_id))
530 return 0;
531
532 /* If /etc/machine-id is initialized we'll use it. */
533 r = context_load_machine_id(c);
534 if (r != 0)
535 return r;
536
537 /* Otherwise we'll use a freshly generated one. */
538 r = sd_id128_randomize(&c->machine_id);
539 if (r < 0)
540 return log_error_errno(r, "Failed to generate random ID: %m");
541
542 c->machine_id_is_random = true;
543 log_debug("New machine ID '%s' generated.", SD_ID128_TO_STRING(c->machine_id));
544 return 0;
545}
546
547static int context_acquire_xbootldr(Context *c) {
548 int r;
549
550 assert(c);
551 assert(!c->boot_root);
552
553 r = find_xbootldr_and_warn_at(
554 /* rfd = */ c->rfd,
4cff5e92 555 /* path = */ arg_xbootldr_path,
42551ea7
YW
556 /* unprivileged_mode= */ -1,
557 /* ret_path = */ &c->boot_root,
558 /* ret_uuid = */ NULL,
559 /* ret_devid = */ NULL);
560 if (r == -ENOKEY) {
561 log_debug_errno(r, "Couldn't find an XBOOTLDR partition.");
562 return 0;
563 }
564 if (r == -EACCES && geteuid() != 0)
565 return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m");
566 if (r < 0)
567 return r;
568
569 log_debug("Using XBOOTLDR partition at %s as $BOOT_ROOT.", c->boot_root);
570 return 1; /* found */
571}
572
573static int context_acquire_esp(Context *c) {
574 int r;
575
576 assert(c);
577 assert(!c->boot_root);
578
579 r = find_esp_and_warn_at(
580 /* rfd = */ c->rfd,
4cff5e92 581 /* path = */ arg_esp_path,
42551ea7
YW
582 /* unprivileged_mode= */ -1,
583 /* ret_path = */ &c->boot_root,
584 /* ret_part = */ NULL,
585 /* ret_pstart = */ NULL,
586 /* ret_psize = */ NULL,
587 /* ret_uuid = */ NULL,
588 /* ret_devid = */ NULL);
589 if (r == -ENOKEY) {
590 log_debug_errno(r, "Couldn't find EFI system partition, ignoring.");
591 return 0;
592 }
593 if (r == -EACCES && geteuid() != 0)
594 return log_error_errno(r, "Failed to determine EFI system partition: %m");
595 if (r < 0)
596 return r;
597
598 log_debug("Using EFI System Partition at %s as $BOOT_ROOT.", c->boot_root);
599 return 1; /* found */
600}
601
602static int context_ensure_boot_root(Context *c) {
603 int r;
604
605 assert(c);
606
607 /* If BOOT_ROOT is specified via environment or install.conf, then use it. */
608 if (c->boot_root)
609 return 0;
610
611 /* Otherwise, use XBOOTLDR partition, if mounted. */
612 r = context_acquire_xbootldr(c);
613 if (r != 0)
614 return r;
615
616 /* Otherwise, use EFI system partition, if mounted. */
617 r = context_acquire_esp(c);
618 if (r != 0)
619 return r;
620
621 /* If all else fails, use /boot. */
54bc114f
YW
622 if (c->rfd >= 0) {
623 r = chaseat(c->rfd, "/boot", CHASE_AT_RESOLVE_IN_ROOT, &c->boot_root, /* ret_fd = */ NULL);
624 if (r < 0)
37252381 625 return log_error_errno(r, "Failed to chase '/boot/': %m");
54bc114f
YW
626 } else {
627 c->boot_root = strdup("/boot");
628 if (!c->boot_root)
629 return log_oom();
630 }
42551ea7
YW
631
632 log_debug("KERNEL_INSTALL_BOOT_ROOT autodetection yielded no candidates, using \"%s\".", c->boot_root);
633 return 0;
634}
635
636static int context_ensure_entry_token(Context *c) {
637 int r;
638
639 assert(c);
640
641 /* Now that we determined the machine ID to use, let's determine the "token" for the boot loader
642 * entry to generate. We use that for naming the directory below $BOOT where we want to place the
643 * kernel/initrd and related resources, as well for naming the .conf boot loader spec entry.
644 * Typically this is just the machine ID, but it can be anything else, too, if we are told so. */
645
646 r = boot_entry_token_ensure_at(
647 c->rfd,
648 c->conf_root,
649 c->machine_id,
650 c->machine_id_is_random,
651 &c->entry_token_type,
652 &c->entry_token);
653 if (r < 0)
654 return r;
655
656 log_debug("Using entry token: %s", c->entry_token);
657 return 0;
658}
659
660static int context_load_plugins(Context *c) {
661 int r;
662
663 assert(c);
664
665 if (c->plugins)
666 return 0;
667
668 r = conf_files_list_strv_at(
669 &c->plugins,
670 ".install",
671 c->rfd,
672 CONF_FILES_EXECUTABLE | CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED,
673 STRV_MAKE_CONST("/etc/kernel/install.d", "/usr/lib/kernel/install.d"));
674 if (r < 0)
675 return log_error_errno(r, "Failed to find plugins: %m");
676
677 return 0;
678}
679
680static int context_init(Context *c) {
681 int r;
682
683 assert(c);
684
685 r = context_open_root(c);
686 if (r < 0)
687 return r;
688
689 r = context_load_environment(c);
690 if (r < 0)
691 return r;
692
42551ea7
YW
693 r = context_load_install_conf(c);
694 if (r < 0)
695 return r;
696
697 r = context_load_machine_info(c);
698 if (r < 0)
699 return r;
700
701 r = context_ensure_machine_id(c);
702 if (r < 0)
703 return r;
704
705 r = context_ensure_boot_root(c);
706 if (r < 0)
707 return r;
708
709 r = context_ensure_entry_token(c);
710 if (r < 0)
711 return r;
712
713 r = context_load_plugins(c);
714 if (r < 0)
715 return r;
716
717 return 0;
718}
719
720static int context_inspect_kernel(Context *c) {
721 assert(c);
722
723 if (!c->kernel)
724 return 0;
725
726 return inspect_kernel(c->rfd, c->kernel, &c->kernel_image_type, NULL, NULL, NULL);
727}
728
729static int context_ensure_layout(Context *c) {
730 int r;
731
732 assert(c);
733 assert(c->boot_root);
734 assert(c->entry_token);
735
736 if (c->layout >= 0 && c->layout != LAYOUT_AUTO)
737 return 0;
738
739 /* No layout configured by the administrator. Let's try to figure it out automatically from metadata
740 * already contained in $BOOT_ROOT. */
741
742 if (c->kernel_image_type == KERNEL_IMAGE_TYPE_UKI) {
743 c->layout = LAYOUT_UKI;
744 log_debug("Kernel image type is %s, using layout=%s.",
745 kernel_image_type_to_string(c->kernel_image_type), layout_to_string(c->layout));
746 return 0;
747 }
748
749 _cleanup_free_ char *srel_path = path_join(c->boot_root, "loader/entries.srel");
750 if (!srel_path)
751 return log_oom();
752
753 _cleanup_free_ char *srel = NULL;
754 r = read_one_line_file_at(c->rfd, srel_path, &srel);
755 if (r >= 0) {
756 if (streq(srel, "type1"))
757 /* The loader/entries.srel file clearly indicates that the installed boot loader
758 * implements the proper standard upstream boot loader spec for Type #1 entries.
759 * Let's default to that, then. */
760 c->layout = LAYOUT_BLS;
761 else
762 /* The loader/entries.srel file indicates some other spec is implemented and owns the
763 * /loader/entries/ directory. Since we have no idea what that means, let's stay away
764 * from it by default. */
765 c->layout = LAYOUT_OTHER;
766
767 log_debug("%s with '%s' found, using layout=%s.", srel_path, srel, layout_to_string(c->layout));
768 return 0;
769 } else if (r != -ENOENT)
770 return log_error_errno(r, "Failed to read %s: %m", srel_path);
771
772 _cleanup_free_ char *entry_token_path = path_join(c->boot_root, c->entry_token);
773 if (!entry_token_path)
774 return log_oom();
775
2560dcbf 776 r = is_dir_at(c->rfd, entry_token_path, /* follow = */ false);
42551ea7
YW
777 if (r < 0 && r != -ENOENT)
778 return log_error_errno(r, "Failed to check if '%s' is a directory: %m", entry_token_path);
779 if (r > 0) {
780 /* If the metadata in $BOOT_ROOT doesn't tell us anything, then check if the entry token
781 * directory already exists. If so, let's assume it's the standard boot loader spec, too. */
782 c->layout = LAYOUT_BLS;
783 log_debug("%s exists, using layout=%s.", entry_token_path, layout_to_string(c->layout));
784 return 0;
785 }
786
787 /* There's no metadata in $BOOT_ROOT, and apparently no entry token directory installed? Then we
788 * really don't know anything. */
789 c->layout = LAYOUT_OTHER;
4092f722
ZJS
790 log_debug("Entry-token directory %s not found, using layout=%s.",
791 entry_token_path,
792 layout_to_string(c->layout));
42551ea7
YW
793 return 0;
794}
795
796static int context_set_up_staging_area(Context *c) {
797 static const char *template = "/tmp/kernel-install.staging.XXXXXX";
798 int r;
799
800 assert(c);
801
802 if (c->staging_area)
803 return 0;
804
805 if (c->action == ACTION_INSPECT) {
806 /* This is only used for display. The directory will not be created. */
807 c->staging_area = strdup(template);
808 if (!c->staging_area)
809 return log_oom();
810 } else {
811 r = mkdtemp_malloc(template, &c->staging_area);
812 if (r < 0)
813 return log_error_errno(r, "Failed to create staging area: %m");
814 }
815
816 return 0;
817}
818
819static int context_build_entry_dir(Context *c) {
820 assert(c);
821 assert(c->boot_root);
822 assert(c->entry_token);
823 assert(c->version || c->action == ACTION_INSPECT);
824
825 if (c->entry_dir)
826 return 0;
827
828 c->entry_dir = path_join(c->boot_root, c->entry_token, c->version ?: "KERNEL_VERSION");
829 if (!c->entry_dir)
830 return log_oom();
831
832 log_debug("Using ENTRY_DIR=%s", c->entry_dir);
833 return 0;
834}
835
836static bool context_should_make_entry_dir(Context *c) {
837 assert(c);
838
839 /* Compatibility with earlier versions that used the presence of $BOOT_ROOT/$ENTRY_TOKEN to signal to
840 * 00-entry-directory to create $ENTRY_DIR to serve as the indication to use or to not use the BLS */
841
b79621aa
YW
842 if (arg_make_entry_directory < 0)
843 return c->layout == LAYOUT_BLS;
844
845 return arg_make_entry_directory;
42551ea7
YW
846}
847
848static int context_make_entry_dir(Context *c) {
849 _cleanup_close_ int fd = -EBADF;
850
851 assert(c);
852 assert(c->entry_dir);
853
854 if (c->action != ACTION_ADD)
855 return 0;
856
857 if (!context_should_make_entry_dir(c))
858 return 0;
859
860 log_debug("mkdir -p %s", c->entry_dir);
861 fd = chase_and_openat(c->rfd, c->entry_dir, CHASE_AT_RESOLVE_IN_ROOT | CHASE_MKDIR_0755,
862 O_CLOEXEC | O_CREAT | O_DIRECTORY | O_PATH, NULL);
863 if (fd < 0)
864 return log_error_errno(fd, "Failed to make directory '%s': %m", c->entry_dir);
865
866 return 0;
867}
868
869static int context_remove_entry_dir(Context *c) {
870 _cleanup_free_ char *p = NULL;
871 _cleanup_close_ int fd = -EBADF;
872 struct stat st;
873 int r;
874
875 assert(c);
876 assert(c->entry_dir);
877
878 if (c->action != ACTION_REMOVE)
879 return 0;
880
881 if (!context_should_make_entry_dir(c))
882 return 0;
883
884 log_debug("rm -rf %s", c->entry_dir);
885 fd = chase_and_openat(c->rfd, c->entry_dir, CHASE_AT_RESOLVE_IN_ROOT, O_CLOEXEC | O_DIRECTORY, &p);
886 if (fd < 0) {
887 if (IN_SET(fd, -ENOTDIR, -ENOENT))
888 return 0;
889 return log_debug_errno(fd, "Failed to chase and open %s, ignoring: %m", c->entry_dir);
890 }
891
892 if (fstat(fd, &st) < 0)
893 return log_debug_errno(errno, "Failed to stat %s: %m", p);
894
895 r = rm_rf_children(TAKE_FD(fd), REMOVE_PHYSICAL|REMOVE_MISSING_OK|REMOVE_CHMOD, &st);
896 if (r < 0)
897 log_debug_errno(r, "Failed to remove children of %s, ignoring: %m", p);
898
899 if (unlinkat(c->rfd, p, AT_REMOVEDIR) < 0)
900 log_debug_errno(errno, "Failed to remove %s, ignoring: %m", p);
901
902 return 0;
903}
904
905static int context_build_arguments(Context *c) {
906 _cleanup_strv_free_ char **a = NULL;
907 const char *verb;
908 int r;
909
910 assert(c);
911 assert(c->entry_dir);
912
913 if (c->argv)
914 return 0;
915
916 switch (c->action) {
917 case ACTION_ADD:
918 assert(c->version);
919 assert(c->kernel);
920 verb = "add";
921 break;
922
923 case ACTION_REMOVE:
924 assert(c->version);
925 assert(!c->kernel);
926 assert(!c->initrds);
927 verb = "remove";
928 break;
929
930 case ACTION_INSPECT:
42551ea7
YW
931 verb = "add|remove";
932 break;
933
934 default:
935 assert_not_reached();
936 }
937
938 a = strv_new("dummy-arg", /* to make strv_free() works for this variable. */
939 verb,
940 c->version ?: "KERNEL_VERSION",
941 c->entry_dir);
942 if (!a)
943 return log_oom();
944
945 if (c->action == ACTION_ADD) {
946 r = strv_extend(&a, c->kernel);
947 if (r < 0)
948 return log_oom();
949
950 r = strv_extend_strv(&a, c->initrds, /* filter_duplicates = */ false);
951 if (r < 0)
952 return log_oom();
953
954 } else if (c->action == ACTION_INSPECT) {
69f3c619
LP
955 r = strv_extend_many(
956 &a,
957 c->kernel ?: "[KERNEL_IMAGE]",
958 "[INITRD...]");
42551ea7
YW
959 if (r < 0)
960 return log_oom();
961 }
962
963 c->argv = TAKE_PTR(a);
964 return 0;
965}
966
967static int context_build_environment(Context *c) {
968 _cleanup_strv_free_ char **e = NULL;
969 int r;
970
971 assert(c);
972
973 if (c->envp)
974 return 0;
975
976 r = strv_env_assign_many(&e,
977 "LC_COLLATE", SYSTEMD_DEFAULT_LOCALE,
978 "KERNEL_INSTALL_VERBOSE", one_zero(arg_verbose),
979 "KERNEL_INSTALL_IMAGE_TYPE", kernel_image_type_to_string(c->kernel_image_type),
980 "KERNEL_INSTALL_MACHINE_ID", SD_ID128_TO_STRING(c->machine_id),
981 "KERNEL_INSTALL_ENTRY_TOKEN", c->entry_token,
982 "KERNEL_INSTALL_BOOT_ROOT", c->boot_root,
983 "KERNEL_INSTALL_LAYOUT", context_get_layout(c),
984 "KERNEL_INSTALL_INITRD_GENERATOR", strempty(c->initrd_generator),
985 "KERNEL_INSTALL_UKI_GENERATOR", strempty(c->uki_generator),
986 "KERNEL_INSTALL_STAGING_AREA", c->staging_area);
987 if (r < 0)
988 return log_error_errno(r, "Failed to build environment variables for plugins: %m");
989
990 c->envp = TAKE_PTR(e);
991 return 0;
992}
993
994static int context_prepare_execution(Context *c) {
995 int r;
996
997 assert(c);
998
999 r = context_inspect_kernel(c);
1000 if (r < 0)
1001 return r;
1002
1003 r = context_ensure_layout(c);
1004 if (r < 0)
1005 return r;
1006
1007 r = context_set_up_staging_area(c);
1008 if (r < 0)
1009 return r;
1010
1011 r = context_build_entry_dir(c);
1012 if (r < 0)
1013 return r;
1014
1015 r = context_build_arguments(c);
1016 if (r < 0)
1017 return r;
1018
1019 r = context_build_environment(c);
1020 if (r < 0)
1021 return r;
1022
1023 return 0;
1024}
1025
1026static int context_execute(Context *c) {
e42931b0 1027 int r, ret;
42551ea7
YW
1028
1029 assert(c);
1030
1031 r = context_make_entry_dir(c);
1032 if (r < 0)
1033 return r;
1034
1035 if (DEBUG_LOGGING) {
1036 _cleanup_free_ char *x = strv_join_full(c->plugins, "", "\n ", /* escape_separator = */ false);
1037 log_debug("Using plugins: %s", strna(x));
1038
1039 _cleanup_free_ char *y = strv_join_full(c->envp, "", "\n ", /* escape_separator = */ false);
1040 log_debug("Plugin environment: %s", strna(y));
1041
1042 _cleanup_free_ char *z = strv_join(strv_skip(c->argv, 1), " ");
1043 log_debug("Plugin arguments: %s", strna(z));
1044 }
1045
e42931b0 1046 ret = execute_strv(
42551ea7
YW
1047 /* name = */ NULL,
1048 c->plugins,
1049 /* root = */ NULL,
1050 USEC_INFINITY,
1051 /* callbacks = */ NULL,
1052 /* callback_args = */ NULL,
1053 c->argv,
1054 c->envp,
1055 EXEC_DIR_SKIP_REMAINING);
42551ea7
YW
1056
1057 r = context_remove_entry_dir(c);
1058 if (r < 0)
1059 return r;
1060
e42931b0
YW
1061 /* This returns 0 on success, positive exit code on plugin failure, negative errno on other failures. */
1062 return ret;
42551ea7
YW
1063}
1064
b4afa94c 1065static bool bypass(void) {
daa2547e 1066 return should_bypass("KERNEL_INSTALL");
b4afa94c
DDM
1067}
1068
ec1399f5
LP
1069static int do_add(
1070 Context *c,
1071 const char *version,
1072 const char *kernel,
1073 char **initrds) {
1074
1075 int r;
1076
1077 assert(c);
1078 assert(version);
1079 assert(kernel);
1080
1081 r = context_set_version(c, version);
1082 if (r < 0)
1083 return r;
1084
1085 r = context_set_kernel(c, kernel);
1086 if (r < 0)
1087 return r;
1088
1089 r = context_set_initrds(c, initrds);
1090 if (r < 0)
1091 return r;
1092
1093 r = context_prepare_execution(c);
1094 if (r < 0)
1095 return r;
1096
1097 return context_execute(c);
1098}
1099
758d1476
LP
1100static int kernel_from_version(const char *version, char **ret_kernel) {
1101 _cleanup_free_ char *vmlinuz = NULL;
1102 int r;
1103
1104 assert(version);
1105
1106 vmlinuz = path_join("/usr/lib/modules/", version, "/vmlinuz");
1107 if (!vmlinuz)
1108 return log_oom();
1109
3f8999a7 1110 r = access_nofollow(vmlinuz, F_OK);
7c1dd9e2
MY
1111 if (r == -ENOENT)
1112 return log_error_errno(r, "Kernel image not installed to '%s', requiring manual kernel image path specification.", vmlinuz);
1113 if (r < 0)
c62345ff 1114 return log_error_errno(r, "Failed to determine if kernel image is installed to '%s': %m", vmlinuz);
758d1476
LP
1115
1116 *ret_kernel = TAKE_PTR(vmlinuz);
1117 return 0;
1118}
1119
42551ea7
YW
1120static int verb_add(int argc, char *argv[], void *userdata) {
1121 Context *c = ASSERT_PTR(userdata);
9d9e99a8
LP
1122 _cleanup_free_ char *vmlinuz = NULL;
1123 const char *version, *kernel;
1124 char **initrds;
1125 struct utsname un;
42551ea7
YW
1126 int r;
1127
42551ea7
YW
1128 assert(argv);
1129
04a8c2ce 1130 if (arg_root)
398760c8 1131 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'add' does not support --root= or --image=.");
04a8c2ce 1132
b4afa94c
DDM
1133 if (bypass())
1134 return 0;
1135
42551ea7
YW
1136 c->action = ACTION_ADD;
1137
9d9e99a8
LP
1138 /* We use the same order of arguments that "inspect" introduced, i.e. if only on argument is
1139 * specified we take it as the kernel path, not the version, i.e. it's the first argument that is
1140 * optional, not the 2nd. */
1141 version = argc > 2 ? empty_or_dash_to_null(argv[1]) : NULL;
1142 kernel = argc > 2 ? empty_or_dash_to_null(argv[2]) :
1143 (argc > 1 ? empty_or_dash_to_null(argv[1]) : NULL);
1144 initrds = strv_skip(argv, 3);
1145
1146 if (!version) {
1147 assert_se(uname(&un) >= 0);
1148 version = un.release;
1149 }
1150
1151 if (!kernel) {
758d1476
LP
1152 r = kernel_from_version(version, &vmlinuz);
1153 if (r < 0)
1154 return r;
9d9e99a8
LP
1155
1156 kernel = vmlinuz;
1157 }
1158
ec1399f5
LP
1159 return do_add(c, version, kernel, initrds);
1160}
42551ea7 1161
ec1399f5
LP
1162static int verb_add_all(int argc, char *argv[], void *userdata) {
1163 Context *c = ASSERT_PTR(userdata);
1164 _cleanup_close_ int fd = -EBADF;
1165 size_t n = 0;
1166 int ret = 0, r;
42551ea7 1167
ec1399f5 1168 assert(argv);
42551ea7 1169
398760c8
AAF
1170 if (arg_root)
1171 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'add-all' does not support --root= or --image=.");
1172
ec1399f5
LP
1173 if (bypass())
1174 return 0;
1175
1176 c->action = ACTION_ADD;
1177
50cc0ee6 1178 fd = chase_and_openat(c->rfd, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL);
ec1399f5 1179 if (fd < 0)
50cc0ee6 1180 return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root));
ec1399f5
LP
1181
1182 _cleanup_free_ DirectoryEntries *de = NULL;
1183 r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
42551ea7 1184 if (r < 0)
ec1399f5 1185 return log_error_errno(r, "Failed to numerate /usr/lib/modules/ contents: %m");
42551ea7 1186
ec1399f5 1187 FOREACH_ARRAY(d, de->entries, de->n_entries) {
ec1399f5
LP
1188 r = dirent_ensure_type(fd, *d);
1189 if (r < 0) {
1190 if (r != -ENOENT) /* don't log if just gone by now */
50cc0ee6 1191 log_debug_errno(r, "Failed to check if '%s/usr/lib/modules/%s' is a directory, ignoring: %m", strempty(arg_root), (*d)->d_name);
ec1399f5
LP
1192 continue;
1193 }
1194
1195 if ((*d)->d_type != DT_DIR)
1196 continue;
1197
1198 _cleanup_free_ char *fn = path_join((*d)->d_name, "vmlinuz");
1199 if (!fn)
1200 return log_oom();
1201
1202 if (faccessat(fd, fn, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
1203 if (errno != ENOENT)
50cc0ee6 1204 log_debug_errno(errno, "Failed to check if '%s/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", strempty(arg_root), (*d)->d_name);
ec1399f5
LP
1205
1206 log_notice("Not adding version '%s', because kernel image not found.", (*d)->d_name);
1207 continue;
1208 }
1209
1210 _cleanup_(context_done) Context copy = CONTEXT_NULL;
1211
1212 r = context_copy(c, &copy);
1213 if (r < 0)
1214 return log_error_errno(r, "Failed to copy execution context: %m");
1215
50cc0ee6
DDM
1216 /* do_add() will look up the path in the correct root directory so we don't need to prefix it
1217 * with arg_root here. */
ec1399f5
LP
1218 _cleanup_free_ char *full = path_join("/usr/lib/modules/", fn);
1219 if (!full)
1220 return log_oom();
1221
1222 r = do_add(&copy,
1223 /* version= */ (*d)->d_name,
1224 /* kernel= */ full,
1225 /* initrds= */ NULL);
e42931b0 1226 if (r == 0)
ec1399f5 1227 n++;
e42931b0
YW
1228 else if (ret == 0)
1229 ret = r;
ec1399f5
LP
1230 }
1231
1232 if (n > 0)
ec9ff6ea 1233 log_debug("Installed %zu kernel(s).", n);
ec1399f5
LP
1234 else if (ret == 0)
1235 ret = log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No kernels to install found.");
1236
1237 return ret;
42551ea7
YW
1238}
1239
1240static int run_as_installkernel(int argc, char *argv[], Context *c) {
1241 /* kernel's install.sh invokes us as
1242 * /sbin/installkernel <version> <vmlinuz> <map> <installation-dir>
1243 * We ignore the last two arguments. */
1244 if (optind + 2 > argc)
1245 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "'installkernel' command requires at least two arguments.");
1246
1247 return verb_add(3, STRV_MAKE("add", argv[optind], argv[optind+1]), c);
1248}
1249
1250static int verb_remove(int argc, char *argv[], void *userdata) {
1251 Context *c = ASSERT_PTR(userdata);
1252 int r;
1253
149aedb8 1254 assert(argc >= 2);
42551ea7
YW
1255 assert(argv);
1256
04a8c2ce 1257 if (arg_root)
398760c8 1258 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'remove' does not support --root= or --image=.");
04a8c2ce 1259
149aedb8
YW
1260 if (argc > 2)
1261 log_debug("Too many arguments specified. 'kernel-install remove' takes only kernel version. "
1262 "Ignoring residual arguments.");
1263
b4afa94c
DDM
1264 if (bypass())
1265 return 0;
1266
42551ea7
YW
1267 c->action = ACTION_REMOVE;
1268
9d9e99a8
LP
1269 /* Note, we do not automatically derive the kernel version to remove from uname() here (unlike we do
1270 * it for the "add" verb), since we don't want to make it too easy to uninstall your running
1271 * kernel, as a safety precaution */
1272
42551ea7
YW
1273 r = context_set_version(c, argv[1]);
1274 if (r < 0)
1275 return r;
1276
1277 r = context_prepare_execution(c);
1278 if (r < 0)
1279 return r;
1280
1281 return context_execute(c);
1282}
1283
1284static int verb_inspect(int argc, char *argv[], void *userdata) {
1285 Context *c = ASSERT_PTR(userdata);
bdd36c00 1286 _cleanup_(table_unrefp) Table *t = NULL;
758d1476
LP
1287 _cleanup_free_ char *vmlinuz = NULL;
1288 const char *version, *kernel;
1289 char **initrds;
1290 struct utsname un;
42551ea7
YW
1291 int r;
1292
1293 c->action = ACTION_INSPECT;
1294
758d1476
LP
1295 /* When only a single parameter is specified 'inspect' it's the kernel image path, and not the kernel
1296 * version. i.e. it's the first argument that is optional, not the 2nd. That's a bit unfortunate, but
1297 * we keep the behaviour for compatibility. If users want to specify only the version (and have the
1298 * kernel image path derived automatically), then they may specify an empty string or "dash" as
1299 * kernel image path. */
1300 version = argc > 2 ? empty_or_dash_to_null(argv[1]) : NULL;
1301 kernel = argc > 2 ? empty_or_dash_to_null(argv[2]) :
1302 (argc > 1 ? empty_or_dash_to_null(argv[1]) : NULL);
1303 initrds = strv_skip(argv, 3);
bdd36c00 1304
dbab0056 1305 if (!version && !arg_root) {
758d1476
LP
1306 assert_se(uname(&un) >= 0);
1307 version = un.release;
1308 }
bdd36c00 1309
dbab0056 1310 if (!kernel && version) {
758d1476 1311 r = kernel_from_version(version, &vmlinuz);
bdd36c00
DDM
1312 if (r < 0)
1313 return r;
758d1476
LP
1314
1315 kernel = vmlinuz;
f7665b7f
YW
1316 }
1317
758d1476
LP
1318 r = context_set_version(c, version);
1319 if (r < 0)
1320 return r;
1321
1322 r = context_set_kernel(c, kernel);
1323 if (r < 0)
1324 return r;
1325
1326 r = context_set_initrds(c, initrds);
1327 if (r < 0)
1328 return r;
1329
42551ea7
YW
1330 r = context_prepare_execution(c);
1331 if (r < 0)
1332 return r;
1333
bdd36c00
DDM
1334 t = table_new_vertical();
1335 if (!t)
1336 return log_oom();
1337
1338 r = table_add_many(t,
1339 TABLE_FIELD, "Machine ID",
1340 TABLE_ID128, c->machine_id,
1341 TABLE_FIELD, "Kernel Image Type",
1342 TABLE_STRING, kernel_image_type_to_string(c->kernel_image_type),
1343 TABLE_FIELD, "Layout",
1344 TABLE_STRING, context_get_layout(c),
1345 TABLE_FIELD, "Boot Root",
1346 TABLE_STRING, c->boot_root,
1347 TABLE_FIELD, "Entry Token Type",
1348 TABLE_STRING, boot_entry_token_type_to_string(c->entry_token_type),
1349 TABLE_FIELD, "Entry Token",
1350 TABLE_STRING, c->entry_token,
1351 TABLE_FIELD, "Entry Directory",
1352 TABLE_STRING, c->entry_dir,
1353 TABLE_FIELD, "Kernel Version",
1354 TABLE_STRING, c->version,
1355 TABLE_FIELD, "Kernel",
1356 TABLE_STRING, c->kernel,
1357 TABLE_FIELD, "Initrds",
1358 TABLE_STRV, c->initrds,
1359 TABLE_FIELD, "Initrd Generator",
1360 TABLE_STRING, c->initrd_generator,
1361 TABLE_FIELD, "UKI Generator",
1362 TABLE_STRING, c->uki_generator,
1363 TABLE_FIELD, "Plugins",
1364 TABLE_STRV, c->plugins,
1365 TABLE_FIELD, "Plugin Environment",
1366 TABLE_STRV, c->envp);
1367 if (r < 0)
1368 return table_log_add_error(r);
1369
23441a3d 1370 if (!sd_json_format_enabled(arg_json_format_flags)) {
bdd36c00
DDM
1371 r = table_add_many(t,
1372 TABLE_FIELD, "Plugin Arguments",
1373 TABLE_STRV, strv_skip(c->argv, 1));
1374 if (r < 0)
1375 return table_log_add_error(r);
1376 }
42551ea7 1377
bdd36c00 1378 table_set_ersatz_string(t, TABLE_ERSATZ_UNSET);
42551ea7 1379
bdd36c00
DDM
1380 for (size_t row = 1; row < table_get_rows(t); row++) {
1381 _cleanup_free_ char *name = NULL;
42551ea7 1382
bdd36c00
DDM
1383 name = strdup(table_get_at(t, row, 0));
1384 if (!name)
1385 return log_oom();
42551ea7 1386
bdd36c00
DDM
1387 r = table_set_json_field_name(t, row - 1, delete_chars(name, " "));
1388 if (r < 0)
1389 return log_error_errno(r, "Failed to set JSON field name: %m");
1390 }
1391
1392 return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, /* show_header= */ false);
42551ea7
YW
1393}
1394
658e6cc4 1395static int verb_list(int argc, char *argv[], void *userdata) {
50cc0ee6 1396 Context *c = ASSERT_PTR(userdata);
658e6cc4
LP
1397 _cleanup_close_ int fd = -EBADF;
1398 int r;
1399
50cc0ee6 1400 fd = chase_and_openat(c->rfd, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL);
658e6cc4 1401 if (fd < 0)
50cc0ee6 1402 return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root));
658e6cc4
LP
1403
1404 _cleanup_free_ DirectoryEntries *de = NULL;
1405 r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
1406 if (r < 0)
1407 return log_error_errno(r, "Failed to numerate /usr/lib/modules/ contents: %m");
1408
1409 _cleanup_(table_unrefp) Table *table = NULL;
1410 table = table_new("version", "has kernel", "path");
1411 if (!table)
1412 return log_oom();
1413
1414 table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
1415 table_set_align_percent(table, table_get_cell(table, 0, 1), 100);
1416
1417 FOREACH_ARRAY(d, de->entries, de->n_entries) {
658e6cc4
LP
1418 _cleanup_free_ char *j = path_join("/usr/lib/modules/", (*d)->d_name);
1419 if (!j)
1420 return log_oom();
1421
1422 r = dirent_ensure_type(fd, *d);
1423 if (r < 0) {
1424 if (r != -ENOENT) /* don't log if just gone by now */
50cc0ee6 1425 log_debug_errno(r, "Failed to check if '%s/%s' is a directory, ignoring: %m", strempty(arg_root), j);
658e6cc4
LP
1426 continue;
1427 }
1428
1429 if ((*d)->d_type != DT_DIR)
1430 continue;
1431
1432 _cleanup_free_ char *fn = path_join((*d)->d_name, "vmlinuz");
1433 if (!fn)
1434 return log_oom();
1435
1436 bool exists;
1437 if (faccessat(fd, fn, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
1438 if (errno != ENOENT)
50cc0ee6 1439 log_debug_errno(errno, "Failed to check if '%s/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", strempty(arg_root), (*d)->d_name);
658e6cc4
LP
1440
1441 exists = false;
1442 } else
1443 exists = true;
1444
1445 r = table_add_many(table,
1446 TABLE_STRING, (*d)->d_name,
1447 TABLE_BOOLEAN_CHECKMARK, exists,
1448 TABLE_SET_COLOR, ansi_highlight_green_red(exists),
1449 TABLE_PATH, j);
1450 if (r < 0)
1451 return table_log_add_error(r);
1452 }
1453
1454 return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
1455}
1456
42551ea7
YW
1457static int help(void) {
1458 _cleanup_free_ char *link = NULL;
1459 int r;
1460
1461 r = terminal_urlify_man("kernel-install", "8", &link);
1462 if (r < 0)
1463 return log_oom();
1464
1465 printf("%1$s [OPTIONS...] COMMAND ...\n\n"
111f9889 1466 "%5$sAdd and remove kernel and initrd images to and from the boot partition.%6$s\n"
658e6cc4 1467 "\n%3$sUsage:%4$s\n"
9d9e99a8 1468 " kernel-install [OPTIONS...] add [[[KERNEL-VERSION] KERNEL-IMAGE] [INITRD ...]]\n"
ec1399f5 1469 " kernel-install [OPTIONS...] add-all\n"
eb25844f 1470 " kernel-install [OPTIONS...] remove KERNEL-VERSION\n"
758d1476
LP
1471 " kernel-install [OPTIONS...] inspect [[[KERNEL-VERSION] KERNEL-IMAGE]\n"
1472 " [INITRD ...]]\n"
658e6cc4
LP
1473 " kernel-install [OPTIONS...] list\n"
1474 "\n%3$sOptions:%4$s\n"
04a8c2ce
DDM
1475 " -h --help Show this help\n"
1476 " --version Show package version\n"
1477 " -v --verbose Increase verbosity\n"
1478 " --esp-path=PATH Path to the EFI System Partition (ESP)\n"
1479 " --boot-path=PATH Path to the $BOOT partition\n"
b79621aa 1480 " --make-entry-directory=yes|no|auto\n"
04a8c2ce 1481 " Create $BOOT/ENTRY-TOKEN/ directory\n"
1fd90ed3 1482 " --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n"
04a8c2ce
DDM
1483 " Entry token to use for this installation\n"
1484 " --no-pager Do not pipe inspect output into a pager\n"
1485 " --json=pretty|short|off Generate JSON output\n"
658e6cc4 1486 " --no-legend Do not show the headers and footers\n"
04a8c2ce
DDM
1487 " --root=PATH Operate on an alternate filesystem root\n"
1488 " --image=PATH Operate on disk image as filesystem root\n"
1489 " --image-policy=POLICY Specify disk image dissection policy\n"
eb25844f
ZJS
1490 "\n"
1491 "This program may also be invoked as 'installkernel':\n"
1492 " installkernel [OPTIONS...] VERSION VMLINUZ [MAP] [INSTALLATION-DIR]\n"
1493 "(The optional arguments are passed by kernel build system, but ignored.)\n"
1494 "\n"
658e6cc4 1495 "See the %2$s for details.\n",
42551ea7 1496 program_invocation_short_name,
658e6cc4
LP
1497 link,
1498 ansi_underline(),
42551ea7 1499 ansi_normal(),
658e6cc4
LP
1500 ansi_highlight(),
1501 ansi_normal());
42551ea7
YW
1502
1503 return 0;
1504}
1505
1fd90ed3 1506static int parse_argv(int argc, char *argv[], Context *c) {
42551ea7
YW
1507 enum {
1508 ARG_VERSION = 0x100,
658e6cc4 1509 ARG_NO_LEGEND,
4cff5e92
YW
1510 ARG_ESP_PATH,
1511 ARG_BOOT_PATH,
b79621aa 1512 ARG_MAKE_ENTRY_DIRECTORY,
1fd90ed3 1513 ARG_ENTRY_TOKEN,
bdd36c00
DDM
1514 ARG_NO_PAGER,
1515 ARG_JSON,
04a8c2ce
DDM
1516 ARG_ROOT,
1517 ARG_IMAGE,
1518 ARG_IMAGE_POLICY,
42551ea7
YW
1519 };
1520 static const struct option options[] = {
1521 { "help", no_argument, NULL, 'h' },
1522 { "version", no_argument, NULL, ARG_VERSION },
1523 { "verbose", no_argument, NULL, 'v' },
4cff5e92
YW
1524 { "esp-path", required_argument, NULL, ARG_ESP_PATH },
1525 { "boot-path", required_argument, NULL, ARG_BOOT_PATH },
b79621aa 1526 { "make-entry-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY },
1fd90ed3 1527 { "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN },
bdd36c00
DDM
1528 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
1529 { "json", required_argument, NULL, ARG_JSON },
04a8c2ce
DDM
1530 { "root", required_argument, NULL, ARG_ROOT },
1531 { "image", required_argument, NULL, ARG_IMAGE },
1532 { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
658e6cc4 1533 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
42551ea7
YW
1534 {}
1535 };
4cff5e92 1536 int t, r;
42551ea7
YW
1537
1538 assert(argc >= 0);
1539 assert(argv);
1fd90ed3 1540 assert(c);
42551ea7
YW
1541
1542 while ((t = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
1543 switch (t) {
1544 case 'h':
1545 return help();
1546
1547 case ARG_VERSION:
1548 return version();
1549
658e6cc4
LP
1550 case ARG_NO_LEGEND:
1551 arg_legend = false;
1552 break;
1553
42551ea7
YW
1554 case 'v':
1555 log_set_max_level(LOG_DEBUG);
1556 arg_verbose = true;
1557 break;
1558
4cff5e92
YW
1559 case ARG_ESP_PATH:
1560 r = parse_path_argument(optarg, /* suppress_root = */ false, &arg_esp_path);
1561 if (r < 0)
1562 return log_oom();
1563 break;
1564
1565 case ARG_BOOT_PATH:
1566 r = parse_path_argument(optarg, /* suppress_root = */ false, &arg_xbootldr_path);
1567 if (r < 0)
1568 return log_oom();
1569 break;
1570
b79621aa
YW
1571 case ARG_MAKE_ENTRY_DIRECTORY:
1572 if (streq(optarg, "auto"))
1573 arg_make_entry_directory = -1;
1574 else {
1575 r = parse_boolean_argument("--make-entry-directory=", optarg, NULL);
1576 if (r < 0)
1577 return r;
1578
1579 arg_make_entry_directory = r;
1580 }
1581 break;
1582
1fd90ed3
YW
1583 case ARG_ENTRY_TOKEN:
1584 r = parse_boot_entry_token_type(optarg, &c->entry_token_type, &c->entry_token);
1585 if (r < 0)
1586 return r;
1587 break;
1588
bdd36c00
DDM
1589 case ARG_NO_PAGER:
1590 arg_pager_flags |= PAGER_DISABLE;
1591 break;
1592
1593 case ARG_JSON:
1594 r = parse_json_argument(optarg, &arg_json_format_flags);
1595 if (r < 0)
1596 return r;
1597 break;
1598
04a8c2ce
DDM
1599 case ARG_ROOT:
1600 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root);
1601 if (r < 0)
1602 return r;
1603 break;
1604
1605 case ARG_IMAGE:
1606 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
1607 if (r < 0)
1608 return r;
1609 break;
1610
1611 case ARG_IMAGE_POLICY:
1612 r = parse_image_policy_argument(optarg, &arg_image_policy);
1613 if (r < 0)
1614 return r;
1615 break;
1616
42551ea7
YW
1617 case '?':
1618 return -EINVAL;
1619
1620 default:
1621 assert_not_reached();
1622 }
1623
04a8c2ce
DDM
1624 if (arg_image && arg_root)
1625 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported.");
1626
42551ea7
YW
1627 return 1;
1628}
1629
1630static int run(int argc, char* argv[]) {
1631 static const Verb verbs[] = {
9d9e99a8 1632 { "add", 1, VERB_ANY, 0, verb_add },
ec1399f5 1633 { "add-all", 1, 1, 0, verb_add_all },
149aedb8 1634 { "remove", 2, VERB_ANY, 0, verb_remove },
bdd36c00 1635 { "inspect", 1, VERB_ANY, VERB_DEFAULT, verb_inspect },
658e6cc4 1636 { "list", 1, 1, 0, verb_list },
42551ea7
YW
1637 {}
1638 };
1639 _cleanup_(context_done) Context c = {
54bc114f 1640 .rfd = AT_FDCWD,
42551ea7
YW
1641 .action = _ACTION_INVALID,
1642 .kernel_image_type = KERNEL_IMAGE_TYPE_UNKNOWN,
1643 .layout = _LAYOUT_INVALID,
1644 .entry_token_type = BOOT_ENTRY_TOKEN_AUTO,
1645 };
04a8c2ce
DDM
1646 _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
1647 _cleanup_(umount_and_freep) char *mounted_dir = NULL;
42551ea7
YW
1648 int r;
1649
1650 log_setup();
1651
1fd90ed3 1652 r = parse_argv(argc, argv, &c);
42551ea7
YW
1653 if (r <= 0)
1654 return r;
1655
04a8c2ce
DDM
1656 if (arg_image) {
1657 assert(!arg_root);
1658
1659 r = mount_image_privately_interactively(
1660 arg_image,
1661 arg_image_policy,
1662 DISSECT_IMAGE_GENERIC_ROOT |
1663 DISSECT_IMAGE_REQUIRE_ROOT |
1664 DISSECT_IMAGE_RELAX_VAR_CHECK |
f4a63ce2
LP
1665 DISSECT_IMAGE_VALIDATE_OS |
1666 DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
04a8c2ce
DDM
1667 &mounted_dir,
1668 /* ret_dir_fd= */ NULL,
1669 &loop_device);
1670 if (r < 0)
1671 return r;
1672
1673 arg_root = strdup(mounted_dir);
1674 if (!arg_root)
1675 return log_oom();
1676 }
1677
42551ea7
YW
1678 r = context_init(&c);
1679 if (r < 0)
1680 return r;
1681
1682 if (invoked_as(argv, "installkernel"))
1683 return run_as_installkernel(argc, argv, &c);
1684
1685 return dispatch_verb(argc, argv, verbs, &c);
1686}
1687
e42931b0 1688DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);