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