]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/sysext/sysext.c
Merge pull request #30284 from YHNdnzj/fstab-wantedby-defaultdeps
[thirdparty/systemd.git] / src / sysext / sysext.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <fcntl.h>
4 #include <getopt.h>
5 #include <linux/loop.h>
6 #include <sys/file.h>
7 #include <sys/mount.h>
8 #include <unistd.h>
9
10 #include "sd-bus.h"
11
12 #include "build.h"
13 #include "bus-locator.h"
14 #include "bus-error.h"
15 #include "bus-unit-util.h"
16 #include "bus-util.h"
17 #include "capability-util.h"
18 #include "chase.h"
19 #include "constants.h"
20 #include "devnum-util.h"
21 #include "discover-image.h"
22 #include "dissect-image.h"
23 #include "env-util.h"
24 #include "escape.h"
25 #include "extension-util.h"
26 #include "fd-util.h"
27 #include "fileio.h"
28 #include "format-table.h"
29 #include "fs-util.h"
30 #include "hashmap.h"
31 #include "initrd-util.h"
32 #include "log.h"
33 #include "main-func.h"
34 #include "missing_magic.h"
35 #include "mkdir.h"
36 #include "mount-util.h"
37 #include "mountpoint-util.h"
38 #include "os-util.h"
39 #include "pager.h"
40 #include "parse-argument.h"
41 #include "parse-util.h"
42 #include "pretty-print.h"
43 #include "process-util.h"
44 #include "sort-util.h"
45 #include "terminal-util.h"
46 #include "user-util.h"
47 #include "varlink.h"
48 #include "varlink-io.systemd.sysext.h"
49 #include "verbs.h"
50
51 static char **arg_hierarchies = NULL; /* "/usr" + "/opt" by default for sysext and /etc by default for confext */
52 static char *arg_root = NULL;
53 static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
54 static PagerFlags arg_pager_flags = 0;
55 static bool arg_legend = true;
56 static bool arg_force = false;
57 static bool arg_no_reload = false;
58 static int arg_noexec = -1;
59 static ImagePolicy *arg_image_policy = NULL;
60 static bool arg_varlink = false;
61
62 /* Is set to IMAGE_CONFEXT when systemd is called with the confext functionality instead of the default */
63 static ImageClass arg_image_class = IMAGE_SYSEXT;
64
65 STATIC_DESTRUCTOR_REGISTER(arg_hierarchies, strv_freep);
66 STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
67 STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
68
69 /* Helper struct for naming simplicity and reusability */
70 static const struct {
71 const char *dot_directory_name;
72 const char *directory_name;
73 const char *short_identifier;
74 const char *short_identifier_plural;
75 const char *level_env;
76 const char *scope_env;
77 const char *name_env;
78 const ImagePolicy *default_image_policy;
79 unsigned long default_mount_flags;
80 } image_class_info[_IMAGE_CLASS_MAX] = {
81 [IMAGE_SYSEXT] = {
82 .dot_directory_name = ".systemd-sysext",
83 .directory_name = "systemd-sysext",
84 .short_identifier = "sysext",
85 .short_identifier_plural = "extensions",
86 .level_env = "SYSEXT_LEVEL",
87 .scope_env = "SYSEXT_SCOPE",
88 .name_env = "SYSTEMD_SYSEXT_HIERARCHIES",
89 .default_image_policy = &image_policy_sysext,
90 .default_mount_flags = MS_RDONLY|MS_NODEV,
91 },
92 [IMAGE_CONFEXT] = {
93 .dot_directory_name = ".systemd-confext",
94 .directory_name = "systemd-confext",
95 .short_identifier = "confext",
96 .short_identifier_plural = "confexts",
97 .level_env = "CONFEXT_LEVEL",
98 .scope_env = "CONFEXT_SCOPE",
99 .name_env = "SYSTEMD_CONFEXT_HIERARCHIES",
100 .default_image_policy = &image_policy_confext,
101 .default_mount_flags = MS_RDONLY|MS_NODEV|MS_NOSUID|MS_NOEXEC,
102 }
103 };
104
105 static int is_our_mount_point(
106 ImageClass image_class,
107 const char *p) {
108
109 _cleanup_free_ char *buf = NULL, *f = NULL;
110 struct stat st;
111 dev_t dev;
112 int r;
113
114 assert(p);
115
116 r = path_is_mount_point(p, NULL, 0);
117 if (r == -ENOENT) {
118 log_debug_errno(r, "Hierarchy '%s' doesn't exist.", p);
119 return false;
120 }
121 if (r < 0)
122 return log_error_errno(r, "Failed to determine whether '%s' is a mount point: %m", p);
123 if (r == 0) {
124 log_debug("Hierarchy '%s' is not a mount point, skipping.", p);
125 return false;
126 }
127
128 /* So we know now that it's a mount point. Now let's check if it's one of ours, so that we don't
129 * accidentally unmount the user's own /usr/ but just the mounts we established ourselves. We do this
130 * check by looking into the metadata directory we place in merged mounts: if the file
131 * ../dev contains the major/minor device pair of the mount we have a good reason to
132 * believe this is one of our mounts. This thorough check has the benefit that we aren't easily
133 * confused if people tar up one of our merged trees and untar them elsewhere where we might mistake
134 * them for a live sysext tree. */
135
136 f = path_join(p, image_class_info[image_class].dot_directory_name, "dev");
137 if (!f)
138 return log_oom();
139
140 r = read_one_line_file(f, &buf);
141 if (r == -ENOENT) {
142 log_debug("Hierarchy '%s' does not carry a %s/dev file, not a merged tree.", p, image_class_info[image_class].dot_directory_name);
143 return false;
144 }
145 if (r < 0)
146 return log_error_errno(r, "Failed to determine whether hierarchy '%s' contains '%s/dev': %m", p, image_class_info[image_class].dot_directory_name);
147
148 r = parse_devnum(buf, &dev);
149 if (r < 0)
150 return log_error_errno(r, "Failed to parse device major/minor stored in '%s/dev' file on '%s': %m", image_class_info[image_class].dot_directory_name, p);
151
152 if (lstat(p, &st) < 0)
153 return log_error_errno(r, "Failed to stat %s: %m", p);
154
155 if (st.st_dev != dev) {
156 log_debug("Hierarchy '%s' reports a different device major/minor than what we are seeing, assuming offline copy.", p);
157 return false;
158 }
159
160 return true;
161 }
162
163 static int need_reload(
164 ImageClass image_class,
165 char **hierarchies,
166 bool no_reload) {
167
168 /* Parse the mounted images to find out if we need to reload the daemon. */
169 int r;
170
171 if (no_reload)
172 return false;
173
174 STRV_FOREACH(p, hierarchies) {
175 _cleanup_free_ char *f = NULL, *buf = NULL, *resolved = NULL;
176 _cleanup_strv_free_ char **mounted_extensions = NULL;
177
178 r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
179 if (r == -ENOENT) {
180 log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p);
181 continue;
182 }
183 if (r < 0) {
184 log_warning_errno(r, "Failed to resolve path to hierarchy '%s%s': %m, ignoring.", strempty(arg_root), *p);
185 continue;
186 }
187
188 r = is_our_mount_point(image_class, resolved);
189 if (r < 0)
190 return r;
191 if (!r)
192 continue;
193
194 f = path_join(resolved, image_class_info[image_class].dot_directory_name, image_class_info[image_class].short_identifier_plural);
195 if (!f)
196 return log_oom();
197
198 r = read_full_file(f, &buf, NULL);
199 if (r < 0)
200 return log_error_errno(r, "Failed to open '%s': %m", f);
201
202 mounted_extensions = strv_split_newlines(buf);
203 if (!mounted_extensions)
204 return log_oom();
205
206 STRV_FOREACH(extension, mounted_extensions) {
207 _cleanup_strv_free_ char **extension_release = NULL;
208 const char *extension_reload_manager = NULL;
209 int b;
210
211 r = load_extension_release_pairs(arg_root, image_class, *extension, /* relax_extension_release_check */ true, &extension_release);
212 if (r < 0) {
213 log_debug_errno(r, "Failed to parse extension-release metadata of %s, ignoring: %m", *extension);
214 continue;
215 }
216
217 extension_reload_manager = strv_env_pairs_get(extension_release, "EXTENSION_RELOAD_MANAGER");
218 if (isempty(extension_reload_manager))
219 continue;
220
221 b = parse_boolean(extension_reload_manager);
222 if (b < 0) {
223 log_warning_errno(b, "Failed to parse the extension metadata to know if the manager needs to be reloaded, ignoring: %m");
224 continue;
225 }
226
227 if (b)
228 /* If at least one extension wants a reload, we reload. */
229 return true;
230 }
231 }
232
233 return false;
234 }
235
236 static int daemon_reload(void) {
237 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
238 int r;
239
240 r = bus_connect_system_systemd(&bus);
241 if (r < 0)
242 return log_error_errno(r, "Failed to get D-Bus connection: %m");
243
244 return bus_service_manager_reload(bus);
245 }
246
247 static int unmerge_hierarchy(
248 ImageClass image_class,
249 const char *p) {
250
251 int r;
252
253 assert(p);
254
255 for (;;) {
256 /* We only unmount /usr/ if it is a mount point and really one of ours, in order not to break
257 * systems where /usr/ is a mount point of its own already. */
258
259 r = is_our_mount_point(image_class, p);
260 if (r < 0)
261 return r;
262 if (r == 0)
263 break;
264
265 r = umount_verbose(LOG_ERR, p, MNT_DETACH|UMOUNT_NOFOLLOW);
266 if (r < 0)
267 return log_error_errno(r, "Failed to unmount file system '%s': %m", p);
268
269 log_info("Unmerged '%s'.", p);
270 }
271
272 return 0;
273 }
274
275 static int unmerge(
276 ImageClass image_class,
277 char **hierarchies,
278 bool no_reload) {
279
280 int r, ret = 0;
281 bool need_to_reload;
282
283 r = need_reload(image_class, hierarchies, no_reload);
284 if (r < 0)
285 return r;
286 need_to_reload = r > 0;
287
288 STRV_FOREACH(p, hierarchies) {
289 _cleanup_free_ char *resolved = NULL;
290
291 r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
292 if (r == -ENOENT) {
293 log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p);
294 continue;
295 }
296 if (r < 0) {
297 log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root), *p);
298 if (ret == 0)
299 ret = r;
300
301 continue;
302 }
303
304 r = unmerge_hierarchy(image_class, resolved);
305 if (r < 0 && ret == 0)
306 ret = r;
307 }
308
309 if (need_to_reload) {
310 r = daemon_reload();
311 if (r < 0)
312 return r;
313 }
314
315 return ret;
316 }
317
318 static int verb_unmerge(int argc, char **argv, void *userdata) {
319 int r;
320
321 r = have_effective_cap(CAP_SYS_ADMIN);
322 if (r < 0)
323 return log_error_errno(r, "Failed to check if we have enough privileges: %m");
324 if (r == 0)
325 return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged.");
326
327 return unmerge(arg_image_class,
328 arg_hierarchies,
329 arg_no_reload);
330 }
331
332 static int parse_image_class_parameter(Varlink *link, const char *value, ImageClass *image_class, char ***hierarchies) {
333 _cleanup_strv_free_ char **h = NULL;
334 ImageClass c;
335 int r;
336
337 assert(link);
338 assert(image_class);
339
340 if (!value)
341 return 0;
342
343 c = image_class_from_string(value);
344 if (!IN_SET(c, IMAGE_SYSEXT, IMAGE_CONFEXT))
345 return varlink_error_invalid_parameter_name(link, "class");
346
347 if (hierarchies) {
348 r = parse_env_extension_hierarchies(&h, image_class_info[c].name_env);
349 if (r < 0)
350 return log_error_errno(r, "Failed to parse environment variable: %m");
351
352 strv_free_and_replace(*hierarchies, h);
353 }
354
355 *image_class = c;
356 return 0;
357 }
358
359 typedef struct MethodUnmergeParameters {
360 const char *class;
361 int no_reload;
362 } MethodUnmergeParameters;
363
364 static int vl_method_unmerge(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
365
366 static const JsonDispatch dispatch_table[] = {
367 { "class", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodUnmergeParameters, class), 0 },
368 { "noReload", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(MethodUnmergeParameters, no_reload), 0 },
369 {}
370 };
371 MethodUnmergeParameters p = {
372 .no_reload = -1,
373 };
374 _cleanup_strv_free_ char **hierarchies = NULL;
375 ImageClass image_class = arg_image_class;
376 int r;
377
378 assert(link);
379
380 r = varlink_dispatch(link, parameters, dispatch_table, &p);
381 if (r != 0)
382 return r;
383
384 r = parse_image_class_parameter(link, p.class, &image_class, &hierarchies);
385 if (r < 0)
386 return r;
387
388 r = unmerge(image_class,
389 hierarchies ?: arg_hierarchies,
390 p.no_reload >= 0 ? p.no_reload : arg_no_reload);
391 if (r < 0)
392 return r;
393
394 return varlink_reply(link, NULL);
395 }
396
397 static int verb_status(int argc, char **argv, void *userdata) {
398 _cleanup_(table_unrefp) Table *t = NULL;
399 int r, ret = 0;
400
401 t = table_new("hierarchy", "extensions", "since");
402 if (!t)
403 return log_oom();
404
405 table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
406
407 STRV_FOREACH(p, arg_hierarchies) {
408 _cleanup_free_ char *resolved = NULL, *f = NULL, *buf = NULL;
409 _cleanup_strv_free_ char **l = NULL;
410 struct stat st;
411
412 r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
413 if (r == -ENOENT) {
414 log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p);
415 continue;
416 }
417 if (r < 0) {
418 log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root), *p);
419 goto inner_fail;
420 }
421
422 r = is_our_mount_point(arg_image_class, resolved);
423 if (r < 0)
424 goto inner_fail;
425 if (r == 0) {
426 r = table_add_many(
427 t,
428 TABLE_PATH, *p,
429 TABLE_STRING, "none",
430 TABLE_SET_COLOR, ansi_grey(),
431 TABLE_EMPTY);
432 if (r < 0)
433 return table_log_add_error(r);
434
435 continue;
436 }
437
438 f = path_join(resolved, image_class_info[arg_image_class].dot_directory_name, image_class_info[arg_image_class].short_identifier_plural);
439 if (!f)
440 return log_oom();
441
442 r = read_full_file(f, &buf, NULL);
443 if (r < 0)
444 return log_error_errno(r, "Failed to open '%s': %m", f);
445
446 l = strv_split_newlines(buf);
447 if (!l)
448 return log_oom();
449
450 if (stat(*p, &st) < 0)
451 return log_error_errno(r, "Failed to stat() '%s': %m", *p);
452
453 r = table_add_many(
454 t,
455 TABLE_PATH, *p,
456 TABLE_STRV, l,
457 TABLE_TIMESTAMP, timespec_load(&st.st_mtim));
458 if (r < 0)
459 return table_log_add_error(r);
460
461 continue;
462
463 inner_fail:
464 if (ret == 0)
465 ret = r;
466 }
467
468 (void) table_set_sort(t, (size_t) 0);
469
470 r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
471 if (r < 0)
472 return r;
473
474 return ret;
475 }
476
477 static int mount_overlayfs(
478 ImageClass image_class,
479 int noexec,
480 const char *where,
481 char **layers) {
482
483 _cleanup_free_ char *options = NULL;
484 bool separator = false;
485 unsigned long flags;
486 int r;
487
488 assert(where);
489
490 options = strdup("lowerdir=");
491 if (!options)
492 return log_oom();
493
494 STRV_FOREACH(l, layers) {
495 _cleanup_free_ char *escaped = NULL;
496
497 escaped = shell_escape(*l, ",:");
498 if (!escaped)
499 return log_oom();
500
501 if (!strextend(&options, separator ? ":" : "", escaped))
502 return log_oom();
503
504 separator = true;
505 }
506
507 flags = image_class_info[image_class].default_mount_flags;
508 if (noexec >= 0)
509 SET_FLAG(flags, MS_NOEXEC, noexec);
510
511 /* Now mount the actual overlayfs */
512 r = mount_nofollow_verbose(LOG_ERR, image_class_info[image_class].short_identifier, where, "overlay", flags, options);
513 if (r < 0)
514 return r;
515
516 return 0;
517 }
518
519 static int merge_hierarchy(
520 ImageClass image_class,
521 const char *hierarchy,
522 int noexec,
523 char **extensions,
524 char **paths,
525 const char *meta_path,
526 const char *overlay_path) {
527
528 _cleanup_free_ char *resolved_hierarchy = NULL, *f = NULL, *buf = NULL;
529 _cleanup_strv_free_ char **layers = NULL;
530 struct stat st;
531 int r;
532
533 assert(hierarchy);
534 assert(meta_path);
535 assert(overlay_path);
536
537 /* Resolve the path of the host's version of the hierarchy, i.e. what we want to use as lowest layer
538 * in the overlayfs stack. */
539 r = chase(hierarchy, arg_root, CHASE_PREFIX_ROOT, &resolved_hierarchy, NULL);
540 if (r == -ENOENT)
541 log_debug_errno(r, "Hierarchy '%s' on host doesn't exist, not merging.", hierarchy);
542 else if (r < 0)
543 return log_error_errno(r, "Failed to resolve host hierarchy '%s': %m", hierarchy);
544 else {
545 r = dir_is_empty(resolved_hierarchy, /* ignore_hidden_or_backup= */ false);
546 if (r < 0)
547 return log_error_errno(r, "Failed to check if host hierarchy '%s' is empty: %m", resolved_hierarchy);
548 if (r > 0) {
549 log_debug("Host hierarchy '%s' is empty, not merging.", resolved_hierarchy);
550 resolved_hierarchy = mfree(resolved_hierarchy);
551 }
552 }
553
554 /* Let's generate a metadata file that lists all extensions we took into account for this
555 * hierarchy. We include this in the final fs, to make things nicely discoverable and
556 * recognizable. */
557 f = path_join(meta_path, image_class_info[image_class].dot_directory_name, image_class_info[image_class].short_identifier_plural);
558 if (!f)
559 return log_oom();
560
561 buf = strv_join(extensions, "\n");
562 if (!buf)
563 return log_oom();
564
565 r = write_string_file(f, buf, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755);
566 if (r < 0)
567 return log_error_errno(r, "Failed to write extension meta file '%s': %m", f);
568
569 /* Put the meta path (i.e. our synthesized stuff) at the top of the layer stack */
570 layers = strv_new(meta_path);
571 if (!layers)
572 return log_oom();
573
574 /* Put the extensions in the middle */
575 STRV_FOREACH(p, paths) {
576 _cleanup_free_ char *resolved = NULL;
577
578 r = chase(hierarchy, *p, CHASE_PREFIX_ROOT, &resolved, NULL);
579 if (r == -ENOENT) {
580 log_debug_errno(r, "Hierarchy '%s' in extension '%s' doesn't exist, not merging.", hierarchy, *p);
581 continue;
582 }
583 if (r < 0)
584 return log_error_errno(r, "Failed to resolve hierarchy '%s' in extension '%s': %m", hierarchy, *p);
585
586 r = dir_is_empty(resolved, /* ignore_hidden_or_backup= */ false);
587 if (r < 0)
588 return log_error_errno(r, "Failed to check if hierarchy '%s' in extension '%s' is empty: %m", resolved, *p);
589 if (r > 0) {
590 log_debug("Hierarchy '%s' in extension '%s' is empty, not merging.", hierarchy, *p);
591 continue;
592 }
593
594 r = strv_consume(&layers, TAKE_PTR(resolved));
595 if (r < 0)
596 return log_oom();
597 }
598
599 if (!layers[1]) /* No extension with files in this hierarchy? Then don't do anything. */
600 return 0;
601
602 if (resolved_hierarchy) {
603 /* Add the host hierarchy as last (lowest) layer in the stack */
604 r = strv_consume(&layers, TAKE_PTR(resolved_hierarchy));
605 if (r < 0)
606 return log_oom();
607 }
608
609 r = mkdir_p(overlay_path, 0700);
610 if (r < 0)
611 return log_error_errno(r, "Failed to make directory '%s': %m", overlay_path);
612
613 r = mount_overlayfs(image_class, noexec, overlay_path, layers);
614 if (r < 0)
615 return r;
616
617 /* The overlayfs superblock is read-only. Let's also mark the bind mount read-only. Extra turbo safety 😎 */
618 r = bind_remount_recursive(overlay_path, MS_RDONLY, MS_RDONLY, NULL);
619 if (r < 0)
620 return log_error_errno(r, "Failed to make bind mount '%s' read-only: %m", overlay_path);
621
622 /* Now we have mounted the new file system. Let's now figure out its .st_dev field, and make that
623 * available in the metadata directory. This is useful to detect whether the metadata dir actually
624 * belongs to the fs it is found on: if .st_dev of the top-level mount matches it, it's pretty likely
625 * we are looking at a live tree, and not an unpacked tar or so of one. */
626 if (stat(overlay_path, &st) < 0)
627 return log_error_errno(r, "Failed to stat mount '%s': %m", overlay_path);
628
629 free(f);
630 f = path_join(meta_path, image_class_info[image_class].dot_directory_name, "dev");
631 if (!f)
632 return log_oom();
633
634 r = write_string_file(f, FORMAT_DEVNUM(st.st_dev), WRITE_STRING_FILE_CREATE);
635 if (r < 0)
636 return log_error_errno(r, "Failed to write '%s': %m", f);
637
638 /* Make sure the top-level dir has an mtime marking the point we established the merge */
639 if (utimensat(AT_FDCWD, meta_path, NULL, AT_SYMLINK_NOFOLLOW) < 0)
640 return log_error_errno(r, "Failed fix mtime of '%s': %m", meta_path);
641
642 return 1;
643 }
644
645 static int strverscmp_improvedp(char *const* a, char *const* b) {
646 /* usable in qsort() for sorting a string array with strverscmp_improved() */
647 return strverscmp_improved(*a, *b);
648 }
649
650 static const ImagePolicy *pick_image_policy(const Image *img) {
651 assert(img);
652 assert(img->path);
653
654 /* Explicitly specified policy always wins */
655 if (arg_image_policy)
656 return arg_image_policy;
657
658 /* If located in /.extra/sysext/ in the initrd, then it was placed there by systemd-stub, and was
659 * picked up from an untrusted ESP. Thus, require a stricter policy by default for them. (For the
660 * other directories we assume the appropriate level of trust was already established already. */
661
662 if (in_initrd() && path_startswith(img->path, "/.extra/sysext/"))
663 return &image_policy_sysext_strict;
664
665 return image_class_info[img->class].default_image_policy;
666 }
667
668 static int merge_subprocess(
669 ImageClass image_class,
670 char **hierarchies,
671 bool force,
672 int noexec,
673 Hashmap *images,
674 const char *workspace) {
675
676 _cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL, *host_os_release_api_level = NULL, *buf = NULL;
677 _cleanup_strv_free_ char **extensions = NULL, **paths = NULL;
678 size_t n_extensions = 0;
679 unsigned n_ignored = 0;
680 Image *img;
681 int r;
682
683 /* Mark the whole of /run as MS_SLAVE, so that we can mount stuff below it that doesn't show up on
684 * the host otherwise. */
685 r = mount_nofollow_verbose(LOG_ERR, NULL, "/run", NULL, MS_SLAVE|MS_REC, NULL);
686 if (r < 0)
687 return log_error_errno(r, "Failed to remount /run/ MS_SLAVE: %m");
688
689 /* Let's create the workspace if it's missing */
690 r = mkdir_p(workspace, 0700);
691 if (r < 0)
692 return log_error_errno(r, "Failed to create '%s': %m", workspace);
693
694 /* Let's mount a tmpfs to our workspace. This way we don't need to clean up the inodes we mount over,
695 * but let the kernel do that entirely automatically, once our namespace dies. Note that this file
696 * system won't be visible to anyone but us, since we opened our own namespace and then made the
697 * /run/ hierarchy (which our workspace is contained in) MS_SLAVE, see above. */
698 r = mount_nofollow_verbose(LOG_ERR, image_class_info[image_class].short_identifier, workspace, "tmpfs", 0, "mode=0700");
699 if (r < 0)
700 return r;
701
702 /* Acquire host OS release info, so that we can compare it with the extension's data */
703 r = parse_os_release(
704 arg_root,
705 "ID", &host_os_release_id,
706 "VERSION_ID", &host_os_release_version_id,
707 image_class_info[image_class].level_env, &host_os_release_api_level);
708 if (r < 0)
709 return log_error_errno(r, "Failed to acquire 'os-release' data of OS tree '%s': %m", empty_to_root(arg_root));
710 if (isempty(host_os_release_id))
711 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
712 "'ID' field not found or empty in 'os-release' data of OS tree '%s': %m",
713 empty_to_root(arg_root));
714
715 /* Let's now mount all images */
716 HASHMAP_FOREACH(img, images) {
717 _cleanup_free_ char *p = NULL;
718
719 p = path_join(workspace, image_class_info[image_class].short_identifier_plural, img->name);
720 if (!p)
721 return log_oom();
722
723 r = mkdir_p(p, 0700);
724 if (r < 0)
725 return log_error_errno(r, "Failed to create %s: %m", p);
726
727 switch (img->type) {
728 case IMAGE_DIRECTORY:
729 case IMAGE_SUBVOLUME:
730
731 if (!force) {
732 r = extension_has_forbidden_content(p);
733 if (r < 0)
734 return r;
735 if (r > 0) {
736 n_ignored++;
737 continue;
738 }
739 }
740
741 r = mount_nofollow_verbose(LOG_ERR, img->path, p, NULL, MS_BIND, NULL);
742 if (r < 0)
743 return r;
744
745 /* Make this a read-only bind mount */
746 r = bind_remount_recursive(p, MS_RDONLY, MS_RDONLY, NULL);
747 if (r < 0)
748 return log_error_errno(r, "Failed to make bind mount '%s' read-only: %m", p);
749
750 break;
751
752 case IMAGE_RAW:
753 case IMAGE_BLOCK: {
754 _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
755 _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
756 _cleanup_(verity_settings_done) VeritySettings verity_settings = VERITY_SETTINGS_DEFAULT;
757 DissectImageFlags flags =
758 DISSECT_IMAGE_READ_ONLY |
759 DISSECT_IMAGE_GENERIC_ROOT |
760 DISSECT_IMAGE_REQUIRE_ROOT |
761 DISSECT_IMAGE_MOUNT_ROOT_ONLY |
762 DISSECT_IMAGE_USR_NO_ROOT |
763 DISSECT_IMAGE_ADD_PARTITION_DEVICES |
764 DISSECT_IMAGE_PIN_PARTITION_DEVICES;
765
766 r = verity_settings_load(&verity_settings, img->path, NULL, NULL);
767 if (r < 0)
768 return log_error_errno(r, "Failed to read verity artifacts for %s: %m", img->path);
769
770 if (verity_settings.data_path)
771 flags |= DISSECT_IMAGE_NO_PARTITION_TABLE;
772
773 if (!force)
774 flags |= DISSECT_IMAGE_VALIDATE_OS_EXT;
775
776 r = loop_device_make_by_path(
777 img->path,
778 O_RDONLY,
779 /* sector_size= */ UINT32_MAX,
780 FLAGS_SET(flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN,
781 LOCK_SH,
782 &d);
783 if (r < 0)
784 return log_error_errno(r, "Failed to set up loopback device for %s: %m", img->path);
785
786 r = dissect_loop_device_and_warn(
787 d,
788 &verity_settings,
789 /* mount_options= */ NULL,
790 pick_image_policy(img),
791 flags,
792 &m);
793 if (r < 0)
794 return r;
795
796 r = dissected_image_load_verity_sig_partition(
797 m,
798 d->fd,
799 &verity_settings);
800 if (r < 0)
801 return r;
802
803 r = dissected_image_decrypt_interactively(
804 m, NULL,
805 &verity_settings,
806 flags);
807 if (r < 0)
808 return r;
809
810 r = dissected_image_mount_and_warn(
811 m,
812 p,
813 /* uid_shift= */ UID_INVALID,
814 /* uid_range= */ UID_INVALID,
815 /* userns_fd= */ -EBADF,
816 flags);
817 if (r < 0 && r != -ENOMEDIUM)
818 return r;
819 if (r == -ENOMEDIUM && !force) {
820 n_ignored++;
821 continue;
822 }
823
824 r = dissected_image_relinquish(m);
825 if (r < 0)
826 return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
827 break;
828 }
829 default:
830 assert_not_reached();
831 }
832
833 if (force)
834 log_debug("Force mode enabled, skipping version validation.");
835 else {
836 r = extension_release_validate(
837 img->name,
838 host_os_release_id,
839 host_os_release_version_id,
840 host_os_release_api_level,
841 in_initrd() ? "initrd" : "system",
842 image_extension_release(img, image_class),
843 image_class);
844 if (r < 0)
845 return r;
846 if (r == 0) {
847 n_ignored++;
848 continue;
849 }
850 }
851
852 /* Nice! This one is an extension we want. */
853 r = strv_extend(&extensions, img->name);
854 if (r < 0)
855 return log_oom();
856
857 n_extensions ++;
858 }
859
860 /* Nothing left? Then shortcut things */
861 if (n_extensions == 0) {
862 if (n_ignored > 0)
863 log_info("No suitable extensions found (%u ignored due to incompatible image(s)).", n_ignored);
864 else
865 log_info("No extensions found.");
866 return 0;
867 }
868
869 /* Order by version sort with strverscmp_improved() */
870 typesafe_qsort(extensions, n_extensions, strverscmp_improvedp);
871
872 buf = strv_join(extensions, "', '");
873 if (!buf)
874 return log_oom();
875
876 log_info("Using extensions '%s'.", buf);
877
878 /* Build table of extension paths (in reverse order) */
879 paths = new0(char*, n_extensions + 1);
880 if (!paths)
881 return log_oom();
882
883 for (size_t k = 0; k < n_extensions; k++) {
884 _cleanup_free_ char *p = NULL;
885
886 assert_se(img = hashmap_get(images, extensions[n_extensions - 1 - k]));
887
888 p = path_join(workspace, image_class_info[image_class].short_identifier_plural, img->name);
889 if (!p)
890 return log_oom();
891
892 paths[k] = TAKE_PTR(p);
893 }
894
895 /* Let's now unmerge the status quo ante, since to build the new overlayfs we need a reference to the
896 * underlying fs. */
897 STRV_FOREACH(h, hierarchies) {
898 _cleanup_free_ char *resolved = NULL;
899
900 r = chase(*h, arg_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
901 if (r < 0)
902 return log_error_errno(r, "Failed to resolve hierarchy '%s%s': %m", strempty(arg_root), *h);
903
904 r = unmerge_hierarchy(image_class, resolved);
905 if (r < 0)
906 return r;
907 }
908
909 /* Create overlayfs mounts for all hierarchies */
910 STRV_FOREACH(h, hierarchies) {
911 _cleanup_free_ char *meta_path = NULL, *overlay_path = NULL;
912
913 meta_path = path_join(workspace, "meta", *h); /* The place where to store metadata about this instance */
914 if (!meta_path)
915 return log_oom();
916
917 overlay_path = path_join(workspace, "overlay", *h); /* The resulting overlayfs instance */
918 if (!overlay_path)
919 return log_oom();
920
921 r = merge_hierarchy(
922 image_class,
923 *h,
924 noexec,
925 extensions,
926 paths,
927 meta_path,
928 overlay_path);
929 if (r < 0)
930 return r;
931 }
932
933 /* And move them all into place. This is where things appear in the host namespace */
934 STRV_FOREACH(h, hierarchies) {
935 _cleanup_free_ char *p = NULL, *resolved = NULL;
936
937 p = path_join(workspace, "overlay", *h);
938 if (!p)
939 return log_oom();
940
941 if (laccess(p, F_OK) < 0) {
942 if (errno != ENOENT)
943 return log_error_errno(errno, "Failed to check if '%s' exists: %m", p);
944
945 /* Hierarchy apparently was empty in all extensions, and wasn't mounted, ignoring. */
946 continue;
947 }
948
949 r = chase(*h, arg_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
950 if (r < 0)
951 return log_error_errno(r, "Failed to resolve hierarchy '%s%s': %m", strempty(arg_root), *h);
952
953 r = mkdir_p(resolved, 0755);
954 if (r < 0)
955 return log_error_errno(r, "Failed to create hierarchy mount point '%s': %m", resolved);
956
957 r = mount_nofollow_verbose(LOG_ERR, p, resolved, NULL, MS_BIND, NULL);
958 if (r < 0)
959 return r;
960
961 log_info("Merged extensions into '%s'.", resolved);
962 }
963
964 return 1;
965 }
966
967 static int merge(ImageClass image_class,
968 char **hierarchies,
969 bool force,
970 bool no_reload,
971 int noexec,
972 Hashmap *images) {
973 pid_t pid;
974 int r;
975
976 r = safe_fork("(sd-merge)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_NEW_MOUNTNS, &pid);
977 if (r < 0)
978 return log_error_errno(r, "Failed to fork off child: %m");
979 if (r == 0) {
980 /* Child with its own mount namespace */
981
982 r = merge_subprocess(image_class, hierarchies, force, noexec, images, "/run/systemd/sysext");
983 if (r < 0)
984 _exit(EXIT_FAILURE);
985
986 /* Our namespace ceases to exist here, also implicitly detaching all temporary mounts we
987 * created below /run. Nice! */
988
989 _exit(r > 0 ? EXIT_SUCCESS : 123); /* 123 means: didn't find any extensions */
990 }
991
992 r = wait_for_terminate_and_check("(sd-merge)", pid, WAIT_LOG_ABNORMAL);
993 if (r < 0)
994 return r;
995
996 if (r == 123) /* exit code 123 means: didn't do anything */
997 return 0;
998
999 r = need_reload(image_class, hierarchies, no_reload);
1000 if (r < 0)
1001 return r;
1002 if (r > 0) {
1003 r = daemon_reload();
1004 if (r < 0)
1005 return r;
1006 }
1007
1008 return 1;
1009 }
1010
1011 static int image_discover_and_read_metadata(
1012 ImageClass image_class,
1013 Hashmap **ret_images) {
1014 _cleanup_hashmap_free_ Hashmap *images = NULL;
1015 Image *img;
1016 int r;
1017
1018 assert(ret_images);
1019
1020 images = hashmap_new(&image_hash_ops);
1021 if (!images)
1022 return log_oom();
1023
1024 r = image_discover(image_class, arg_root, images);
1025 if (r < 0)
1026 return log_error_errno(r, "Failed to discover images: %m");
1027
1028 HASHMAP_FOREACH(img, images) {
1029 r = image_read_metadata(img, image_class_info[image_class].default_image_policy);
1030 if (r < 0)
1031 return log_error_errno(r, "Failed to read metadata for image %s: %m", img->name);
1032 }
1033
1034 *ret_images = TAKE_PTR(images);
1035
1036 return 0;
1037 }
1038
1039 static int look_for_merged_hierarchies(
1040 ImageClass image_class,
1041 char **hierarchies,
1042 const char **ret_which) {
1043 int r;
1044
1045 assert(ret_which);
1046
1047 /* In merge mode fail if things are already merged. (In --refresh mode below we'll unmerge if we find
1048 * things are already merged...) */
1049 STRV_FOREACH(p, hierarchies) {
1050 _cleanup_free_ char *resolved = NULL;
1051
1052 r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
1053 if (r == -ENOENT) {
1054 log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p);
1055 continue;
1056 }
1057 if (r < 0)
1058 return log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root), *p);
1059
1060 r = is_our_mount_point(image_class, resolved);
1061 if (r < 0)
1062 return r;
1063 if (r > 0) {
1064 *ret_which = *p;
1065 return 1;
1066 }
1067 }
1068
1069 *ret_which = NULL;
1070 return 0;
1071 }
1072
1073 static int verb_merge(int argc, char **argv, void *userdata) {
1074 _cleanup_hashmap_free_ Hashmap *images = NULL;
1075 const char *which;
1076 int r;
1077
1078 r = have_effective_cap(CAP_SYS_ADMIN);
1079 if (r < 0)
1080 return log_error_errno(r, "Failed to check if we have enough privileges: %m");
1081 if (r == 0)
1082 return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged.");
1083
1084 r = image_discover_and_read_metadata(arg_image_class, &images);
1085 if (r < 0)
1086 return r;
1087
1088 r = look_for_merged_hierarchies(arg_image_class, arg_hierarchies, &which);
1089 if (r < 0)
1090 return r;
1091 if (r > 0)
1092 return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Hierarchy '%s' is already merged.", which);
1093
1094 return merge(arg_image_class,
1095 arg_hierarchies,
1096 arg_force,
1097 arg_no_reload,
1098 arg_noexec,
1099 images);
1100 }
1101
1102 typedef struct MethodMergeParameters {
1103 const char *class;
1104 int force;
1105 int no_reload;
1106 int noexec;
1107 } MethodMergeParameters;
1108
1109 static int parse_merge_parameters(Varlink *link, JsonVariant *parameters, MethodMergeParameters *p) {
1110
1111 static const JsonDispatch dispatch_table[] = {
1112 { "class", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodMergeParameters, class), 0 },
1113 { "force", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(MethodMergeParameters, force), 0 },
1114 { "noReload", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(MethodMergeParameters, no_reload), 0 },
1115 { "noexec", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(MethodMergeParameters, noexec), 0 },
1116 {}
1117 };
1118
1119 assert(link);
1120 assert(parameters);
1121 assert(p);
1122
1123 return varlink_dispatch(link, parameters, dispatch_table, p);
1124 }
1125
1126 static int vl_method_merge(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
1127 _cleanup_hashmap_free_ Hashmap *images = NULL;
1128 MethodMergeParameters p = {
1129 .force = -1,
1130 .no_reload = -1,
1131 .noexec = -1,
1132 };
1133 _cleanup_strv_free_ char **hierarchies = NULL;
1134 ImageClass image_class = arg_image_class;
1135 int r;
1136
1137 assert(link);
1138
1139 r = parse_merge_parameters(link, parameters, &p);
1140 if (r != 0)
1141 return r;
1142
1143 r = parse_image_class_parameter(link, p.class, &image_class, &hierarchies);
1144 if (r < 0)
1145 return r;
1146
1147 r = image_discover_and_read_metadata(image_class, &images);
1148 if (r < 0)
1149 return r;
1150
1151 const char *which;
1152 r = look_for_merged_hierarchies(
1153 image_class,
1154 hierarchies ?: arg_hierarchies,
1155 &which);
1156 if (r < 0)
1157 return r;
1158 if (r > 0)
1159 return varlink_errorb(link, "io.systemd.sysext.AlreadyMerged", JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("hierarchy", which)));
1160
1161 r = merge(image_class,
1162 hierarchies ?: arg_hierarchies,
1163 p.force >= 0 ? p.force : arg_force,
1164 p.no_reload >= 0 ? p.no_reload : arg_no_reload,
1165 p.noexec >= 0 ? p.noexec : arg_noexec,
1166 images);
1167 if (r < 0)
1168 return r;
1169
1170 return varlink_reply(link, NULL);
1171 }
1172
1173 static int refresh(
1174 ImageClass image_class,
1175 char **hierarchies,
1176 bool force,
1177 bool no_reload,
1178 int noexec) {
1179
1180 _cleanup_hashmap_free_ Hashmap *images = NULL;
1181 int r;
1182
1183 r = image_discover_and_read_metadata(image_class, &images);
1184 if (r < 0)
1185 return r;
1186
1187 /* Returns > 0 if it did something, i.e. a new overlayfs is mounted now. When it does so it
1188 * implicitly unmounts any overlayfs placed there before. Returns == 0 if it did nothing, i.e. no
1189 * extension images found. In this case the old overlayfs remains in place if there was one. */
1190 r = merge(image_class, hierarchies, force, no_reload, noexec, images);
1191 if (r < 0)
1192 return r;
1193 if (r == 0) /* No images found? Then unmerge. The goal of --refresh is after all that after having
1194 * called there's a guarantee that the merge status matches the installed extensions. */
1195 r = unmerge(image_class, hierarchies, no_reload);
1196
1197 /* Net result here is that:
1198 *
1199 * 1. If an overlayfs was mounted before and no extensions exist anymore, we'll have unmerged things.
1200 *
1201 * 2. If an overlayfs was mounted before, and there are still extensions installed' we'll have
1202 * unmerged and then merged things again.
1203 *
1204 * 3. If an overlayfs so far wasn't mounted, and there are extensions installed, we'll have it
1205 * mounted now.
1206 *
1207 * 4. If there was no overlayfs mount so far, and no extensions installed, we implement a NOP.
1208 */
1209
1210 return 0;
1211 }
1212
1213 static int verb_refresh(int argc, char **argv, void *userdata) {
1214 int r;
1215
1216 r = have_effective_cap(CAP_SYS_ADMIN);
1217 if (r < 0)
1218 return log_error_errno(r, "Failed to check if we have enough privileges: %m");
1219 if (r == 0)
1220 return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged.");
1221
1222 return refresh(arg_image_class,
1223 arg_hierarchies,
1224 arg_force,
1225 arg_no_reload,
1226 arg_noexec);
1227 }
1228
1229 static int vl_method_refresh(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
1230
1231 MethodMergeParameters p = {
1232 .force = -1,
1233 .no_reload = -1,
1234 .noexec = -1,
1235 };
1236 _cleanup_strv_free_ char **hierarchies = NULL;
1237 ImageClass image_class = arg_image_class;
1238 int r;
1239
1240 assert(link);
1241
1242 r = parse_merge_parameters(link, parameters, &p);
1243 if (r != 0)
1244 return r;
1245
1246 r = parse_image_class_parameter(link, p.class, &image_class, &hierarchies);
1247 if (r < 0)
1248 return r;
1249
1250 r = refresh(image_class,
1251 hierarchies ?: arg_hierarchies,
1252 p.force >= 0 ? p.force : arg_force,
1253 p.no_reload >= 0 ? p.no_reload : arg_no_reload,
1254 p.noexec >= 0 ? p.noexec : arg_noexec);
1255 if (r < 0)
1256 return r;
1257
1258 return varlink_reply(link, NULL);
1259 }
1260
1261 static int verb_list(int argc, char **argv, void *userdata) {
1262 _cleanup_hashmap_free_ Hashmap *images = NULL;
1263 _cleanup_(table_unrefp) Table *t = NULL;
1264 Image *img;
1265 int r;
1266
1267 images = hashmap_new(&image_hash_ops);
1268 if (!images)
1269 return log_oom();
1270
1271 r = image_discover(arg_image_class, arg_root, images);
1272 if (r < 0)
1273 return log_error_errno(r, "Failed to discover images: %m");
1274
1275 if ((arg_json_format_flags & JSON_FORMAT_OFF) && hashmap_isempty(images)) {
1276 log_info("No OS extensions found.");
1277 return 0;
1278 }
1279
1280 t = table_new("name", "type", "path", "time");
1281 if (!t)
1282 return log_oom();
1283
1284 HASHMAP_FOREACH(img, images) {
1285 r = table_add_many(
1286 t,
1287 TABLE_STRING, img->name,
1288 TABLE_STRING, image_type_to_string(img->type),
1289 TABLE_PATH, img->path,
1290 TABLE_TIMESTAMP, img->mtime != 0 ? img->mtime : img->crtime);
1291 if (r < 0)
1292 return table_log_add_error(r);
1293 }
1294
1295 (void) table_set_sort(t, (size_t) 0);
1296
1297 return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
1298 }
1299
1300 typedef struct MethodListParameters {
1301 const char *class;
1302 } MethodListParameters;
1303
1304 static int vl_method_list(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
1305
1306 static const JsonDispatch dispatch_table[] = {
1307 { "class", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodListParameters, class), 0 },
1308 {}
1309 };
1310 MethodListParameters p = {
1311 };
1312 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
1313 _cleanup_hashmap_free_ Hashmap *images = NULL;
1314 ImageClass image_class = arg_image_class;
1315 Image *img;
1316 int r;
1317
1318 assert(link);
1319
1320 r = varlink_dispatch(link, parameters, dispatch_table, &p);
1321 if (r != 0)
1322 return r;
1323
1324 r = parse_image_class_parameter(link, p.class, &image_class, NULL);
1325 if (r < 0)
1326 return r;
1327
1328 images = hashmap_new(&image_hash_ops);
1329 if (!images)
1330 return -ENOMEM;
1331
1332 r = image_discover(image_class, arg_root, images);
1333 if (r < 0)
1334 return r;
1335
1336 HASHMAP_FOREACH(img, images) {
1337 if (v) {
1338 /* Send previous item with more=true */
1339 r = varlink_notify(link, v);
1340 if (r < 0)
1341 return r;
1342 }
1343
1344 v = json_variant_unref(v);
1345
1346 r = image_to_json(img, &v);
1347 if (r < 0)
1348 return r;
1349 }
1350
1351 if (v) /* Send final item with more=false */
1352 return varlink_reply(link, v);
1353
1354 return varlink_error(link, "io.systemd.sysext.NoImagesFound", NULL);
1355 }
1356
1357 static int verb_help(int argc, char **argv, void *userdata) {
1358 _cleanup_free_ char *link = NULL;
1359 int r;
1360
1361 r = terminal_urlify_man("systemd-sysext", "8", &link);
1362 if (r < 0)
1363 return log_oom();
1364
1365 printf("%1$s [OPTIONS...] COMMAND\n"
1366 "\n%5$sMerge extension images into /usr/ and /opt/ hierarchies for\n"
1367 " sysext and into the /etc/ hierarchy for confext.%6$s\n"
1368 " status Show current merge status (default)\n"
1369 " merge Merge extensions into relevant hierarchies\n"
1370 " unmerge Unmerge extensions from relevant hierarchies\n"
1371 " refresh Unmerge/merge extensions again\n"
1372 " list List installed extensions\n"
1373 " -h --help Show this help\n"
1374 " --version Show package version\n"
1375 "\n%3$sOptions:%4$s\n"
1376 " --no-pager Do not pipe output into a pager\n"
1377 " --no-legend Do not show the headers and footers\n"
1378 " --root=PATH Operate relative to root path\n"
1379 " --json=pretty|short|off\n"
1380 " Generate JSON output\n"
1381 " --force Ignore version incompatibilities\n"
1382 " --no-reload Do not reload the service manager\n"
1383 " --image-policy=POLICY\n"
1384 " Specify disk image dissection policy\n"
1385 " --noexec=BOOL Whether to mount extension overlay with noexec\n"
1386 "\nSee the %2$s for details.\n",
1387 program_invocation_short_name,
1388 link,
1389 ansi_underline(),
1390 ansi_normal(),
1391 ansi_highlight(),
1392 ansi_normal());
1393
1394 return 0;
1395 }
1396
1397 static int parse_argv(int argc, char *argv[]) {
1398
1399 enum {
1400 ARG_VERSION = 0x100,
1401 ARG_NO_PAGER,
1402 ARG_NO_LEGEND,
1403 ARG_ROOT,
1404 ARG_JSON,
1405 ARG_FORCE,
1406 ARG_IMAGE_POLICY,
1407 ARG_NOEXEC,
1408 ARG_NO_RELOAD,
1409 };
1410
1411 static const struct option options[] = {
1412 { "help", no_argument, NULL, 'h' },
1413 { "version", no_argument, NULL, ARG_VERSION },
1414 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
1415 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
1416 { "root", required_argument, NULL, ARG_ROOT },
1417 { "json", required_argument, NULL, ARG_JSON },
1418 { "force", no_argument, NULL, ARG_FORCE },
1419 { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
1420 { "noexec", required_argument, NULL, ARG_NOEXEC },
1421 { "no-reload", no_argument, NULL, ARG_NO_RELOAD },
1422 {}
1423 };
1424
1425 int c, r;
1426
1427 assert(argc >= 0);
1428 assert(argv);
1429
1430 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
1431
1432 switch (c) {
1433
1434 case 'h':
1435 return verb_help(argc, argv, NULL);
1436
1437 case ARG_VERSION:
1438 return version();
1439
1440 case ARG_NO_PAGER:
1441 arg_pager_flags |= PAGER_DISABLE;
1442 break;
1443
1444 case ARG_NO_LEGEND:
1445 arg_legend = false;
1446 break;
1447
1448 case ARG_ROOT:
1449 r = parse_path_argument(optarg, false, &arg_root);
1450 if (r < 0)
1451 return r;
1452 /* If --root= is provided, do not reload the service manager */
1453 arg_no_reload = true;
1454 break;
1455
1456 case ARG_JSON:
1457 r = parse_json_argument(optarg, &arg_json_format_flags);
1458 if (r <= 0)
1459 return r;
1460
1461 break;
1462
1463 case ARG_FORCE:
1464 arg_force = true;
1465 break;
1466
1467 case ARG_IMAGE_POLICY:
1468 r = parse_image_policy_argument(optarg, &arg_image_policy);
1469 if (r < 0)
1470 return r;
1471 break;
1472
1473 case ARG_NOEXEC:
1474 r = parse_boolean_argument("--noexec", optarg, NULL);
1475 if (r < 0)
1476 return r;
1477
1478 arg_noexec = r;
1479 break;
1480
1481 case ARG_NO_RELOAD:
1482 arg_no_reload = true;
1483 break;
1484
1485 case '?':
1486 return -EINVAL;
1487
1488 default:
1489 assert_not_reached();
1490 }
1491
1492 r = varlink_invocation(VARLINK_ALLOW_ACCEPT);
1493 if (r < 0)
1494 return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
1495 if (r > 0)
1496 arg_varlink = true;
1497
1498 return 1;
1499 }
1500
1501 static int sysext_main(int argc, char *argv[]) {
1502
1503 static const Verb verbs[] = {
1504 { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
1505 { "merge", VERB_ANY, 1, 0, verb_merge },
1506 { "unmerge", VERB_ANY, 1, 0, verb_unmerge },
1507 { "refresh", VERB_ANY, 1, 0, verb_refresh },
1508 { "list", VERB_ANY, 1, 0, verb_list },
1509 { "help", VERB_ANY, 1, 0, verb_help },
1510 {}
1511 };
1512
1513 return dispatch_verb(argc, argv, verbs, NULL);
1514 }
1515
1516 static int run(int argc, char *argv[]) {
1517 int r;
1518
1519 log_setup();
1520
1521 arg_image_class = invoked_as(argv, "systemd-confext") ? IMAGE_CONFEXT : IMAGE_SYSEXT;
1522
1523 r = parse_argv(argc, argv);
1524 if (r <= 0)
1525 return r;
1526
1527 /* For debugging purposes it might make sense to do this for other hierarchies than /usr/ and
1528 * /opt/, but let's make that a hacker/debugging feature, i.e. env var instead of cmdline
1529 * switch. */
1530 r = parse_env_extension_hierarchies(&arg_hierarchies, image_class_info[arg_image_class].name_env);
1531 if (r < 0)
1532 return log_error_errno(r, "Failed to parse environment variable: %m");
1533
1534 if (arg_varlink) {
1535 _cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL;
1536
1537 /* Invocation as Varlink service */
1538
1539 r = varlink_server_new(&varlink_server, VARLINK_SERVER_ROOT_ONLY);
1540 if (r < 0)
1541 return log_error_errno(r, "Failed to allocate Varlink server: %m");
1542
1543 r = varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_sysext);
1544 if (r < 0)
1545 return log_error_errno(r, "Failed to add Varlink interface: %m");
1546
1547 r = varlink_server_bind_method_many(
1548 varlink_server,
1549 "io.systemd.sysext.Merge", vl_method_merge,
1550 "io.systemd.sysext.Unmerge", vl_method_unmerge,
1551 "io.systemd.sysext.Refresh", vl_method_refresh,
1552 "io.systemd.sysext.List", vl_method_list);
1553 if (r < 0)
1554 return log_error_errno(r, "Failed to bind Varlink methods: %m");
1555
1556 r = varlink_server_loop_auto(varlink_server);
1557 if (r == -EPERM)
1558 return log_error_errno(r, "Invoked by unprivileged Varlink peer, refusing.");
1559 if (r < 0)
1560 return log_error_errno(r, "Failed to run Varlink event loop: %m");
1561
1562 return EXIT_SUCCESS;
1563 }
1564
1565 return sysext_main(argc, argv);
1566 }
1567
1568 DEFINE_MAIN_FUNCTION(run);