]>
Commit | Line | Data |
---|---|---|
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 | ||
38 | static bool arg_verbose = false; | |
4cff5e92 YW |
39 | static char *arg_esp_path = NULL; |
40 | static char *arg_xbootldr_path = NULL; | |
b79621aa | 41 | static int arg_make_entry_directory = -1; /* tristate */ |
bdd36c00 DDM |
42 | static PagerFlags arg_pager_flags = 0; |
43 | static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; | |
04a8c2ce DDM |
44 | static char *arg_root = NULL; |
45 | static char *arg_image = NULL; | |
46 | static ImagePolicy *arg_image_policy = NULL; | |
658e6cc4 | 47 | static bool arg_legend = true; |
4cff5e92 YW |
48 | |
49 | STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep); | |
50 | STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep); | |
04a8c2ce | 51 | STATIC_DESTRUCTOR_REGISTER(arg_root, freep); |
d1b1cf8f | 52 | STATIC_DESTRUCTOR_REGISTER(arg_image, freep); |
04a8c2ce | 53 | STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); |
42551ea7 YW |
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 | ||
ec1399f5 LP |
105 | #define CONTEXT_NULL (Context) { .rfd = -EBADF } |
106 | ||
42551ea7 YW |
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 | ||
ec1399f5 LP |
131 | static 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, ©.layout_other); | |
152 | if (r < 0) | |
153 | return r; | |
154 | r = strdup_or_null(source->conf_root, ©.conf_root); | |
155 | if (r < 0) | |
156 | return r; | |
157 | r = strdup_or_null(source->boot_root, ©.boot_root); | |
158 | if (r < 0) | |
159 | return r; | |
160 | r = strdup_or_null(source->entry_token, ©.entry_token); | |
161 | if (r < 0) | |
162 | return r; | |
163 | r = strdup_or_null(source->entry_dir, ©.entry_dir); | |
164 | if (r < 0) | |
165 | return r; | |
166 | r = strdup_or_null(source->version, ©.version); | |
167 | if (r < 0) | |
168 | return r; | |
169 | r = strdup_or_null(source->kernel, ©.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, ©.initrd_generator); | |
176 | if (r < 0) | |
177 | return r; | |
178 | r = strdup_or_null(source->uki_generator, ©.uki_generator); | |
179 | if (r < 0) | |
180 | return r; | |
181 | r = strdup_or_null(source->staging_area, ©.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 | 200 | static 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 | ||
222 | static 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 | ||
229 | static 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 | ||
257 | static 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 | ||
277 | static 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 | ||
297 | static 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 | ||
302 | static 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 | ||
307 | static 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 | 316 | static 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 | ||
346 | static 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 | ||
351 | static 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 | ||
356 | static 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 | 361 | static 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 | ||
401 | static 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 | ||
416 | static 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 | ||
421 | static 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 | 431 | static 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 | ||
472 | static 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 | ||
492 | static 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 | ||
534 | static 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 | ||
549 | static 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 | ||
572 | static 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 | ||
598 | static 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 | ||
627 | static 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 | ||
661 | static 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 | ||
685 | static 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 | ||
705 | static 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 | ||
745 | static 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 | ||
754 | static 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 | ||
819 | static 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 | ||
842 | static 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 | ||
859 | static 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 | ||
871 | static 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 | ||
892 | static 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 | ||
928 | static 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 | ||
991 | static 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 | ||
1018 | static 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 | ||
1050 | static 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 |
1089 | static 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 |
1102 | static 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 |
1133 | static 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 |
1155 | static 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 |
1197 | static 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, ©); | |
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(©, | |
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 | ||
1275 | static 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 | ||
1285 | static 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 | ||
1319 | static 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 | 1430 | static 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 |
1492 | static 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 | 1541 | static 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 | ||
1665 | static 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 | 1722 | DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); |