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