]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/sysext/sysext.c
Merge pull request #31524 from poettering/secure-getenv-naming-fix
[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 "path-util.h"
43 #include "pretty-print.h"
44 #include "process-util.h"
45 #include "rm-rf.h"
46 #include "sort-util.h"
47 #include "string-util.h"
48 #include "terminal-util.h"
49 #include "user-util.h"
50 #include "varlink.h"
51 #include "varlink-io.systemd.sysext.h"
52 #include "verbs.h"
53
54 typedef enum MutableMode {
55 MUTABLE_YES,
56 MUTABLE_NO,
57 MUTABLE_AUTO,
58 MUTABLE_IMPORT,
59 _MUTABLE_MAX,
60 _MUTABLE_INVALID = -EINVAL,
61 } MutableMode;
62
63 static char **arg_hierarchies = NULL; /* "/usr" + "/opt" by default for sysext and /etc by default for confext */
64 static char *arg_root = NULL;
65 static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
66 static PagerFlags arg_pager_flags = 0;
67 static bool arg_legend = true;
68 static bool arg_force = false;
69 static bool arg_no_reload = false;
70 static int arg_noexec = -1;
71 static ImagePolicy *arg_image_policy = NULL;
72 static bool arg_varlink = false;
73 static MutableMode arg_mutable = MUTABLE_NO;
74
75 /* Is set to IMAGE_CONFEXT when systemd is called with the confext functionality instead of the default */
76 static ImageClass arg_image_class = IMAGE_SYSEXT;
77
78 STATIC_DESTRUCTOR_REGISTER(arg_hierarchies, strv_freep);
79 STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
80 STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
81
82 /* Helper struct for naming simplicity and reusability */
83 static const struct {
84 const char *full_identifier;
85 const char *short_identifier;
86 const char *short_identifier_plural;
87 const char *blurb;
88 const char *dot_directory_name;
89 const char *directory_name;
90 const char *level_env;
91 const char *scope_env;
92 const char *name_env;
93 const ImagePolicy *default_image_policy;
94 unsigned long default_mount_flags;
95 } image_class_info[_IMAGE_CLASS_MAX] = {
96 [IMAGE_SYSEXT] = {
97 .full_identifier = "systemd-sysext",
98 .short_identifier = "sysext",
99 .short_identifier_plural = "extensions",
100 .blurb = "Merge system extension images into /usr/ and /opt/.",
101 .dot_directory_name = ".systemd-sysext",
102 .level_env = "SYSEXT_LEVEL",
103 .scope_env = "SYSEXT_SCOPE",
104 .name_env = "SYSTEMD_SYSEXT_HIERARCHIES",
105 .default_image_policy = &image_policy_sysext,
106 .default_mount_flags = MS_RDONLY|MS_NODEV,
107 },
108 [IMAGE_CONFEXT] = {
109 .full_identifier = "systemd-confext",
110 .short_identifier = "confext",
111 .short_identifier_plural = "confexts",
112 .blurb = "Merge configuration extension images into /etc/.",
113 .dot_directory_name = ".systemd-confext",
114 .level_env = "CONFEXT_LEVEL",
115 .scope_env = "CONFEXT_SCOPE",
116 .name_env = "SYSTEMD_CONFEXT_HIERARCHIES",
117 .default_image_policy = &image_policy_confext,
118 .default_mount_flags = MS_RDONLY|MS_NODEV|MS_NOSUID|MS_NOEXEC,
119 }
120 };
121
122 static int is_our_mount_point(
123 ImageClass image_class,
124 const char *p) {
125
126 _cleanup_free_ char *buf = NULL, *f = NULL;
127 struct stat st;
128 dev_t dev;
129 int r;
130
131 assert(p);
132
133 r = path_is_mount_point(p);
134 if (r == -ENOENT) {
135 log_debug_errno(r, "Hierarchy '%s' doesn't exist.", p);
136 return false;
137 }
138 if (r < 0)
139 return log_error_errno(r, "Failed to determine whether '%s' is a mount point: %m", p);
140 if (r == 0) {
141 log_debug("Hierarchy '%s' is not a mount point, skipping.", p);
142 return false;
143 }
144
145 /* 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
146 * accidentally unmount the user's own /usr/ but just the mounts we established ourselves. We do this
147 * check by looking into the metadata directory we place in merged mounts: if the file
148 * ../dev contains the major/minor device pair of the mount we have a good reason to
149 * believe this is one of our mounts. This thorough check has the benefit that we aren't easily
150 * confused if people tar up one of our merged trees and untar them elsewhere where we might mistake
151 * them for a live sysext tree. */
152
153 f = path_join(p, image_class_info[image_class].dot_directory_name, "dev");
154 if (!f)
155 return log_oom();
156
157 r = read_one_line_file(f, &buf);
158 if (r == -ENOENT) {
159 log_debug("Hierarchy '%s' does not carry a %s/dev file, not a merged tree.", p, image_class_info[image_class].dot_directory_name);
160 return false;
161 }
162 if (r < 0)
163 return log_error_errno(r, "Failed to determine whether hierarchy '%s' contains '%s/dev': %m", p, image_class_info[image_class].dot_directory_name);
164
165 r = parse_devnum(buf, &dev);
166 if (r < 0)
167 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);
168
169 if (lstat(p, &st) < 0)
170 return log_error_errno(r, "Failed to stat %s: %m", p);
171
172 if (st.st_dev != dev) {
173 log_debug("Hierarchy '%s' reports a different device major/minor than what we are seeing, assuming offline copy.", p);
174 return false;
175 }
176
177 return true;
178 }
179
180 static int need_reload(
181 ImageClass image_class,
182 char **hierarchies,
183 bool no_reload) {
184
185 /* Parse the mounted images to find out if we need to reload the daemon. */
186 int r;
187
188 if (no_reload)
189 return false;
190
191 STRV_FOREACH(p, hierarchies) {
192 _cleanup_free_ char *f = NULL, *buf = NULL, *resolved = NULL;
193 _cleanup_strv_free_ char **mounted_extensions = NULL;
194
195 r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
196 if (r == -ENOENT) {
197 log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p);
198 continue;
199 }
200 if (r < 0) {
201 log_warning_errno(r, "Failed to resolve path to hierarchy '%s%s': %m, ignoring.", strempty(arg_root), *p);
202 continue;
203 }
204
205 r = is_our_mount_point(image_class, resolved);
206 if (r < 0)
207 return r;
208 if (!r)
209 continue;
210
211 f = path_join(resolved, image_class_info[image_class].dot_directory_name, image_class_info[image_class].short_identifier_plural);
212 if (!f)
213 return log_oom();
214
215 r = read_full_file(f, &buf, NULL);
216 if (r < 0)
217 return log_error_errno(r, "Failed to open '%s': %m", f);
218
219 mounted_extensions = strv_split_newlines(buf);
220 if (!mounted_extensions)
221 return log_oom();
222
223 STRV_FOREACH(extension, mounted_extensions) {
224 _cleanup_strv_free_ char **extension_release = NULL;
225 const char *extension_reload_manager = NULL;
226 int b;
227
228 r = load_extension_release_pairs(arg_root, image_class, *extension, /* relax_extension_release_check */ true, &extension_release);
229 if (r < 0) {
230 log_debug_errno(r, "Failed to parse extension-release metadata of %s, ignoring: %m", *extension);
231 continue;
232 }
233
234 extension_reload_manager = strv_env_pairs_get(extension_release, "EXTENSION_RELOAD_MANAGER");
235 if (isempty(extension_reload_manager))
236 continue;
237
238 b = parse_boolean(extension_reload_manager);
239 if (b < 0) {
240 log_warning_errno(b, "Failed to parse the extension metadata to know if the manager needs to be reloaded, ignoring: %m");
241 continue;
242 }
243
244 if (b)
245 /* If at least one extension wants a reload, we reload. */
246 return true;
247 }
248 }
249
250 return false;
251 }
252
253 static int daemon_reload(void) {
254 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
255 int r;
256
257 r = bus_connect_system_systemd(&bus);
258 if (r < 0)
259 return log_error_errno(r, "Failed to get D-Bus connection: %m");
260
261 return bus_service_manager_reload(bus);
262 }
263
264 static int unmerge_hierarchy(
265 ImageClass image_class,
266 const char *p) {
267
268 _cleanup_free_ char *dot_dir = NULL, *work_dir_info_file = NULL;
269 int r;
270
271 assert(p);
272
273 dot_dir = path_join(p, image_class_info[image_class].dot_directory_name);
274 if (!dot_dir)
275 return log_oom();
276
277 work_dir_info_file = path_join(dot_dir, "work_dir");
278 if (!work_dir_info_file)
279 return log_oom();
280
281 for (;;) {
282 _cleanup_free_ char *escaped_work_dir_in_root = NULL, *work_dir = NULL;
283
284 /* We only unmount /usr/ if it is a mount point and really one of ours, in order not to break
285 * systems where /usr/ is a mount point of its own already. */
286
287 r = is_our_mount_point(image_class, p);
288 if (r < 0)
289 return r;
290 if (r == 0)
291 break;
292
293 r = read_one_line_file(work_dir_info_file, &escaped_work_dir_in_root);
294 if (r < 0) {
295 if (r != -ENOENT)
296 return log_error_errno(r, "Failed to read '%s': %m", work_dir_info_file);
297 } else {
298 _cleanup_free_ char *work_dir_in_root = NULL;
299 ssize_t l;
300
301 l = cunescape_length(escaped_work_dir_in_root, r, 0, &work_dir_in_root);
302 if (l < 0)
303 return log_error_errno(l, "Failed to unescape work directory path: %m");
304 work_dir = path_join(arg_root, work_dir_in_root);
305 if (!work_dir)
306 return log_oom();
307 }
308
309 r = umount_verbose(LOG_DEBUG, dot_dir, MNT_DETACH|UMOUNT_NOFOLLOW);
310 if (r < 0) {
311 /* EINVAL is possibly "not a mount point". Let it slide as it's expected to occur if
312 * the whole hierarchy was read-only, so the dot directory inside it was not
313 * bind-mounted as read-only. */
314 if (r != -EINVAL)
315 return log_error_errno(r, "Failed to unmount '%s': %m", dot_dir);
316 }
317
318 r = umount_verbose(LOG_ERR, p, MNT_DETACH|UMOUNT_NOFOLLOW);
319 if (r < 0)
320 return r;
321
322 if (work_dir) {
323 r = rm_rf(work_dir, REMOVE_ROOT | REMOVE_MISSING_OK | REMOVE_PHYSICAL);
324 if (r < 0)
325 return log_error_errno(r, "Failed to remove '%s': %m", work_dir);
326 }
327
328 log_info("Unmerged '%s'.", p);
329 }
330
331 return 0;
332 }
333
334 static int unmerge(
335 ImageClass image_class,
336 char **hierarchies,
337 bool no_reload) {
338
339 int r, ret = 0;
340 bool need_to_reload;
341
342 r = need_reload(image_class, hierarchies, no_reload);
343 if (r < 0)
344 return r;
345 need_to_reload = r > 0;
346
347 STRV_FOREACH(p, hierarchies) {
348 _cleanup_free_ char *resolved = NULL;
349
350 r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
351 if (r == -ENOENT) {
352 log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p);
353 continue;
354 }
355 if (r < 0) {
356 log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root), *p);
357 if (ret == 0)
358 ret = r;
359
360 continue;
361 }
362
363 r = unmerge_hierarchy(image_class, resolved);
364 if (r < 0 && ret == 0)
365 ret = r;
366 }
367
368 if (need_to_reload) {
369 r = daemon_reload();
370 if (r < 0)
371 return r;
372 }
373
374 return ret;
375 }
376
377 static int verb_unmerge(int argc, char **argv, void *userdata) {
378 int r;
379
380 r = have_effective_cap(CAP_SYS_ADMIN);
381 if (r < 0)
382 return log_error_errno(r, "Failed to check if we have enough privileges: %m");
383 if (r == 0)
384 return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged.");
385
386 return unmerge(arg_image_class,
387 arg_hierarchies,
388 arg_no_reload);
389 }
390
391 static int parse_image_class_parameter(Varlink *link, const char *value, ImageClass *image_class, char ***hierarchies) {
392 _cleanup_strv_free_ char **h = NULL;
393 ImageClass c;
394 int r;
395
396 assert(link);
397 assert(image_class);
398
399 if (!value)
400 return 0;
401
402 c = image_class_from_string(value);
403 if (!IN_SET(c, IMAGE_SYSEXT, IMAGE_CONFEXT))
404 return varlink_error_invalid_parameter_name(link, "class");
405
406 if (hierarchies) {
407 r = parse_env_extension_hierarchies(&h, image_class_info[c].name_env);
408 if (r < 0)
409 return log_error_errno(r, "Failed to parse environment variable: %m");
410
411 strv_free_and_replace(*hierarchies, h);
412 }
413
414 *image_class = c;
415 return 0;
416 }
417
418 typedef struct MethodUnmergeParameters {
419 const char *class;
420 int no_reload;
421 } MethodUnmergeParameters;
422
423 static int vl_method_unmerge(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
424
425 static const JsonDispatch dispatch_table[] = {
426 { "class", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodUnmergeParameters, class), 0 },
427 { "noReload", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(MethodUnmergeParameters, no_reload), 0 },
428 {}
429 };
430 MethodUnmergeParameters p = {
431 .no_reload = -1,
432 };
433 _cleanup_strv_free_ char **hierarchies = NULL;
434 ImageClass image_class = arg_image_class;
435 int r;
436
437 assert(link);
438
439 r = varlink_dispatch(link, parameters, dispatch_table, &p);
440 if (r != 0)
441 return r;
442
443 r = parse_image_class_parameter(link, p.class, &image_class, &hierarchies);
444 if (r < 0)
445 return r;
446
447 r = unmerge(image_class,
448 hierarchies ?: arg_hierarchies,
449 p.no_reload >= 0 ? p.no_reload : arg_no_reload);
450 if (r < 0)
451 return r;
452
453 return varlink_reply(link, NULL);
454 }
455
456 static int verb_status(int argc, char **argv, void *userdata) {
457 _cleanup_(table_unrefp) Table *t = NULL;
458 int r, ret = 0;
459
460 t = table_new("hierarchy", "extensions", "since");
461 if (!t)
462 return log_oom();
463
464 table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
465
466 STRV_FOREACH(p, arg_hierarchies) {
467 _cleanup_free_ char *resolved = NULL, *f = NULL, *buf = NULL;
468 _cleanup_strv_free_ char **l = NULL;
469 struct stat st;
470
471 r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
472 if (r == -ENOENT) {
473 log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p);
474 continue;
475 }
476 if (r < 0) {
477 log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root), *p);
478 goto inner_fail;
479 }
480
481 r = is_our_mount_point(arg_image_class, resolved);
482 if (r < 0)
483 goto inner_fail;
484 if (r == 0) {
485 r = table_add_many(
486 t,
487 TABLE_PATH, *p,
488 TABLE_STRING, "none",
489 TABLE_SET_COLOR, ansi_grey(),
490 TABLE_EMPTY);
491 if (r < 0)
492 return table_log_add_error(r);
493
494 continue;
495 }
496
497 f = path_join(resolved, image_class_info[arg_image_class].dot_directory_name, image_class_info[arg_image_class].short_identifier_plural);
498 if (!f)
499 return log_oom();
500
501 r = read_full_file(f, &buf, NULL);
502 if (r < 0)
503 return log_error_errno(r, "Failed to open '%s': %m", f);
504
505 l = strv_split_newlines(buf);
506 if (!l)
507 return log_oom();
508
509 if (stat(*p, &st) < 0)
510 return log_error_errno(r, "Failed to stat() '%s': %m", *p);
511
512 r = table_add_many(
513 t,
514 TABLE_PATH, *p,
515 TABLE_STRV, l,
516 TABLE_TIMESTAMP, timespec_load(&st.st_mtim));
517 if (r < 0)
518 return table_log_add_error(r);
519
520 continue;
521
522 inner_fail:
523 if (ret == 0)
524 ret = r;
525 }
526
527 (void) table_set_sort(t, (size_t) 0);
528
529 r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
530 if (r < 0)
531 return r;
532
533 return ret;
534 }
535
536 static int append_overlayfs_path_option(
537 char **options,
538 const char *separator,
539 const char *option,
540 const char *path) {
541
542 _cleanup_free_ char *escaped = NULL;
543
544 assert(options);
545 assert(separator);
546 assert(path);
547
548 escaped = shell_escape(path, ",:");
549 if (!escaped)
550 return log_oom();
551
552 if (option) {
553 if (!strextend(options, separator, option, "=", escaped))
554 return log_oom();
555 } else if (!strextend(options, separator, escaped))
556 return log_oom();
557
558 return 0;
559 }
560
561 static int mount_overlayfs(
562 ImageClass image_class,
563 int noexec,
564 const char *where,
565 char **layers,
566 const char *upper_dir,
567 const char *work_dir) {
568
569 _cleanup_free_ char *options = NULL;
570 bool separator = false;
571 unsigned long flags;
572 int r;
573
574 assert(where);
575 assert((upper_dir && work_dir) || (!upper_dir && !work_dir));
576
577 options = strdup("lowerdir=");
578 if (!options)
579 return log_oom();
580
581 STRV_FOREACH(l, layers) {
582 r = append_overlayfs_path_option(&options, separator ? ":" : "", NULL, *l);
583 if (r < 0)
584 return r;
585
586 separator = true;
587 }
588
589 flags = image_class_info[image_class].default_mount_flags;
590 if (noexec >= 0)
591 SET_FLAG(flags, MS_NOEXEC, noexec);
592
593 if (upper_dir && work_dir) {
594 r = append_overlayfs_path_option(&options, ",", "upperdir", upper_dir);
595 if (r < 0)
596 return r;
597
598 flags &= ~MS_RDONLY;
599
600 r = append_overlayfs_path_option(&options, ",", "workdir", work_dir);
601 if (r < 0)
602 return r;
603 /* redirect_dir=on and noatime prevent unnecessary upcopies, metacopy=off prevents broken
604 * files from partial upcopies after umount. */
605 if (!strextend(&options, ",redirect_dir=on,noatime,metacopy=off"))
606 return log_oom();
607 }
608
609 /* Now mount the actual overlayfs */
610 r = mount_nofollow_verbose(LOG_ERR, image_class_info[image_class].short_identifier, where, "overlay", flags, options);
611 if (r < 0)
612 return r;
613
614 return 0;
615 }
616
617 static char *hierarchy_as_single_path_component(const char *hierarchy) {
618 /* We normally expect hierarchy to be /usr, /opt or /etc, but for debugging purposes the hierarchy
619 * could very well be like /foo/bar/baz/. So for a given hierarchy we generate a directory name by
620 * stripping the leading and trailing separators and replacing the rest of separators with dots. This
621 * makes the generated name to be the same for /foo/bar/baz and for /foo/bar.baz, but, again,
622 * specifying a different hierarchy is a debugging feature, so non-unique mapping should not be an
623 * issue in general case. */
624 const char *stripped = hierarchy;
625 _cleanup_free_ char *dir_name = NULL;
626
627 assert(hierarchy);
628
629 stripped += strspn(stripped, "/");
630
631 dir_name = strdup(stripped);
632 if (!dir_name)
633 return NULL;
634 delete_trailing_chars(dir_name, "/");
635 string_replace_char(dir_name, '/', '.');
636 return TAKE_PTR(dir_name);
637 }
638
639 static char *determine_mutable_directory_path_for_hierarchy(const char *hierarchy) {
640 _cleanup_free_ char *dir_name = NULL;
641
642 assert(hierarchy);
643 dir_name = hierarchy_as_single_path_component(hierarchy);
644 if (!dir_name)
645 return NULL;
646
647 return path_join("/var/lib/extensions.mutable", dir_name);
648 }
649
650 static int paths_on_same_fs(const char *path1, const char *path2) {
651 struct stat st1, st2;
652
653 assert(path1);
654 assert(path2);
655
656 if (stat(path1, &st1))
657 return log_error_errno(errno, "Failed to stat '%s': %m", path1);
658
659 if (stat(path2, &st2))
660 return log_error_errno(errno, "Failed to stat '%s': %m", path2);
661
662 return st1.st_dev == st2.st_dev;
663 }
664
665 static int work_dir_for_hierarchy(
666 const char *hierarchy,
667 const char *resolved_upper_dir,
668 char **ret_work_dir) {
669
670 _cleanup_free_ char *parent = NULL;
671 int r;
672
673 assert(hierarchy);
674 assert(resolved_upper_dir);
675 assert(ret_work_dir);
676
677 r = path_extract_directory(resolved_upper_dir, &parent);
678 if (r < 0)
679 return log_error_errno(r, "Failed to get parent directory of upperdir '%s': %m", resolved_upper_dir);
680
681 /* TODO: paths_in_same_superblock? partition? device? */
682 r = paths_on_same_fs(resolved_upper_dir, parent);
683 if (r < 0)
684 return r;
685 if (!r)
686 return log_error_errno(SYNTHETIC_ERRNO(EXDEV), "Unable to find a suitable workdir location for upperdir '%s' for host hierarchy '%s' - parent directory of the upperdir is in a different filesystem", resolved_upper_dir, hierarchy);
687
688 _cleanup_free_ char *f = NULL, *dir_name = NULL;
689
690 f = hierarchy_as_single_path_component(hierarchy);
691 if (!f)
692 return log_oom();
693 dir_name = strjoin(".systemd-", f, "-workdir");
694 if (!dir_name)
695 return log_oom();
696
697 free(f);
698 f = path_join(parent, dir_name);
699 if (!f)
700 return log_oom();
701
702 *ret_work_dir = TAKE_PTR(f);
703 return 0;
704 }
705
706 typedef struct OverlayFSPaths {
707 char *hierarchy;
708 char *resolved_hierarchy;
709 char *resolved_mutable_directory;
710
711 /* NULL if merged fs is read-only */
712 char *upper_dir;
713 /* NULL if merged fs is read-only */
714 char *work_dir;
715 /* lowest index is top lowerdir, highest index is bottom lowerdir */
716 char **lower_dirs;
717 } OverlayFSPaths;
718
719 static OverlayFSPaths *overlayfs_paths_free(OverlayFSPaths *op) {
720 if (!op)
721 return NULL;
722
723 free(op->hierarchy);
724 free(op->resolved_hierarchy);
725 free(op->resolved_mutable_directory);
726
727 free(op->upper_dir);
728 free(op->work_dir);
729 strv_free(op->lower_dirs);
730
731 free(op);
732 return NULL;
733 }
734 DEFINE_TRIVIAL_CLEANUP_FUNC(OverlayFSPaths *, overlayfs_paths_free);
735
736 static int resolve_hierarchy(const char *hierarchy, char **ret_resolved_hierarchy) {
737 _cleanup_free_ char *resolved_path = NULL;
738 int r;
739
740 assert(hierarchy);
741 assert(ret_resolved_hierarchy);
742
743 r = chase(hierarchy, arg_root, CHASE_PREFIX_ROOT, &resolved_path, NULL);
744 if (r < 0 && r != -ENOENT)
745 return log_error_errno(r, "Failed to resolve hierarchy '%s': %m", hierarchy);
746
747 *ret_resolved_hierarchy = TAKE_PTR(resolved_path);
748 return 0;
749 }
750
751 static int resolve_mutable_directory(const char *hierarchy, char **ret_resolved_mutable_directory) {
752 _cleanup_free_ char *path = NULL, *resolved_path = NULL;
753 int r;
754
755 assert(hierarchy);
756 assert(ret_resolved_mutable_directory);
757
758 if (arg_mutable == MUTABLE_NO) {
759 log_debug("Mutability for hierarchy '%s' is disabled, not resolving mutable directory.", hierarchy);
760 *ret_resolved_mutable_directory = NULL;
761 return 0;
762 }
763
764 path = determine_mutable_directory_path_for_hierarchy(hierarchy);
765 if (!path)
766 return log_oom();
767
768 if (arg_mutable == MUTABLE_YES) {
769 _cleanup_free_ char *path_in_root = NULL;
770
771 path_in_root = path_join(arg_root, path);
772 if (!path_in_root)
773 return log_oom();
774
775 r = mkdir_p(path_in_root, 0700);
776 if (r < 0)
777 return log_error_errno(r, "Failed to create a directory '%s': %m", path_in_root);
778 }
779
780 r = chase(path, arg_root, CHASE_PREFIX_ROOT, &resolved_path, NULL);
781 if (r < 0 && r != -ENOENT)
782 return log_error_errno(r, "Failed to resolve mutable directory '%s': %m", path);
783
784 *ret_resolved_mutable_directory = TAKE_PTR(resolved_path);
785 return 0;
786 }
787
788 static int overlayfs_paths_new(const char *hierarchy, OverlayFSPaths **ret_op) {
789 _cleanup_free_ char *hierarchy_copy = NULL, *resolved_hierarchy = NULL, *resolved_mutable_directory = NULL;
790 int r;
791
792 assert (hierarchy);
793 assert (ret_op);
794
795 hierarchy_copy = strdup(hierarchy);
796 if (!hierarchy_copy)
797 return log_oom();
798
799 r = resolve_hierarchy(hierarchy, &resolved_hierarchy);
800 if (r < 0)
801 return r;
802 r = resolve_mutable_directory(hierarchy, &resolved_mutable_directory);
803 if (r < 0)
804 return r;
805
806 OverlayFSPaths *op;
807 op = new(OverlayFSPaths, 1);
808 if (!op)
809 return log_oom();
810
811 *op = (OverlayFSPaths) {
812 .hierarchy = TAKE_PTR(hierarchy_copy),
813 .resolved_hierarchy = TAKE_PTR(resolved_hierarchy),
814 .resolved_mutable_directory = TAKE_PTR(resolved_mutable_directory),
815 };
816
817 *ret_op = TAKE_PTR(op);
818 return 0;
819 }
820
821 static int determine_top_lower_dirs(OverlayFSPaths *op, const char *meta_path) {
822 int r;
823
824 assert(op);
825 assert(meta_path);
826
827 /* Put the meta path (i.e. our synthesized stuff) at the top of the layer stack */
828 r = strv_extend(&op->lower_dirs, meta_path);
829 if (r < 0)
830 return log_oom();
831
832 /* If importing mutable layer and it actually exists, add it just below the meta path */
833 if (arg_mutable == MUTABLE_IMPORT && op->resolved_mutable_directory) {
834 r = strv_extend(&op->lower_dirs, op->resolved_mutable_directory);
835 if (r < 0)
836 return r;
837 }
838
839 return 0;
840 }
841
842 static int determine_middle_lower_dirs(OverlayFSPaths *op, char **paths, size_t *ret_extensions_used) {
843 size_t n = 0;
844 int r;
845
846 assert(op);
847 assert(paths);
848 assert(ret_extensions_used);
849
850 /* Put the extensions in the middle */
851 STRV_FOREACH(p, paths) {
852 _cleanup_free_ char *resolved = NULL;
853
854 r = chase(op->hierarchy, *p, CHASE_PREFIX_ROOT, &resolved, NULL);
855 if (r == -ENOENT) {
856 log_debug_errno(r, "Hierarchy '%s' in extension '%s' doesn't exist, not merging.", op->hierarchy, *p);
857 continue;
858 }
859 if (r < 0)
860 return log_error_errno(r, "Failed to resolve hierarchy '%s' in extension '%s': %m", op->hierarchy, *p);
861
862 r = dir_is_empty(resolved, /* ignore_hidden_or_backup= */ false);
863 if (r < 0)
864 return log_error_errno(r, "Failed to check if hierarchy '%s' in extension '%s' is empty: %m", resolved, *p);
865 if (r > 0) {
866 log_debug("Hierarchy '%s' in extension '%s' is empty, not merging.", op->hierarchy, *p);
867 continue;
868 }
869
870 r = strv_consume(&op->lower_dirs, TAKE_PTR(resolved));
871 if (r < 0)
872 return log_oom();
873 ++n;
874 }
875
876 *ret_extensions_used = n;
877 return 0;
878 }
879
880 static int hierarchy_as_lower_dir(OverlayFSPaths *op) {
881 int r;
882
883 /* return 0 if hierarchy should be used as lower dir, >0, if not */
884
885 assert(op);
886
887 if (!op->resolved_hierarchy) {
888 log_debug("Host hierarchy '%s' does not exist, will not be used as lowerdir", op->hierarchy);
889 return 1;
890 }
891
892 r = dir_is_empty(op->resolved_hierarchy, /* ignore_hidden_or_backup= */ false);
893 if (r < 0)
894 return log_error_errno(r, "Failed to check if host hierarchy '%s' is empty: %m", op->resolved_hierarchy);
895 if (r > 0) {
896 log_debug("Host hierarchy '%s' is empty, will not be used as lower dir.", op->resolved_hierarchy);
897 return 1;
898 }
899
900 if (arg_mutable == MUTABLE_IMPORT) {
901 log_debug("Mutability for host hierarchy '%s' is disabled, so it will be a lowerdir", op->resolved_hierarchy);
902 return 0;
903 }
904
905 if (!op->resolved_mutable_directory) {
906 log_debug("No mutable directory found, so host hierarchy '%s' will be used as lowerdir", op->resolved_hierarchy);
907 return 0;
908 }
909
910 if (path_equal(op->resolved_hierarchy, op->resolved_mutable_directory)) {
911 log_debug("Host hierarchy '%s' will serve as upperdir.", op->resolved_hierarchy);
912 return 1;
913 }
914 r = inode_same(op->resolved_hierarchy, op->resolved_mutable_directory, 0);
915 if (r < 0)
916 return log_error_errno(r, "Failed to check inode equality of hierarchy %s and its mutable directory %s: %m", op->resolved_hierarchy, op->resolved_mutable_directory);
917 if (r > 0) {
918 log_debug("Host hierarchy '%s' will serve as upperdir.", op->resolved_hierarchy);
919 return 1;
920 }
921
922 return 0;
923 }
924
925 static int determine_bottom_lower_dirs(OverlayFSPaths *op) {
926 int r;
927
928 assert(op);
929
930 r = hierarchy_as_lower_dir(op);
931 if (r < 0)
932 return r;
933 if (!r) {
934 r = strv_extend(&op->lower_dirs, op->resolved_hierarchy);
935 if (r < 0)
936 return r;
937 }
938
939 return 0;
940 }
941
942 static int determine_lower_dirs(
943 OverlayFSPaths *op,
944 char **paths,
945 const char *meta_path,
946 size_t *ret_extensions_used) {
947
948 int r;
949
950 assert(op);
951 assert(paths);
952 assert(meta_path);
953 assert(ret_extensions_used);
954
955 r = determine_top_lower_dirs(op, meta_path);
956 if (r < 0)
957 return r;
958
959 r = determine_middle_lower_dirs(op, paths, ret_extensions_used);
960 if (r < 0)
961 return r;
962
963 r = determine_bottom_lower_dirs(op);
964 if (r < 0)
965 return r;
966
967 return 0;
968 }
969
970 static int determine_upper_dir(OverlayFSPaths *op) {
971 int r;
972
973 assert(op);
974 assert(!op->upper_dir);
975
976 if (arg_mutable == MUTABLE_IMPORT) {
977 log_debug("Mutability is disabled, there will be no upperdir for host hierarchy '%s'", op->hierarchy);
978 return 0;
979 }
980
981 if (!op->resolved_mutable_directory) {
982 log_debug("No mutable directory found for host hierarchy '%s', there will be no upperdir", op->hierarchy);
983 return 0;
984 }
985
986 /* Require upper dir to be on writable filesystem if it's going to be used as an actual overlayfs
987 * upperdir, instead of a lowerdir as an imported path. */
988 r = path_is_read_only_fs(op->resolved_mutable_directory);
989 if (r < 0)
990 return log_error_errno(r, "Failed to determine if mutable directory '%s' is on read-only filesystem: %m", op->resolved_mutable_directory);
991 if (r > 0)
992 return log_error_errno(SYNTHETIC_ERRNO(EROFS), "Can't use '%s' as an upperdir as it is read-only.", op->resolved_mutable_directory);
993
994 op->upper_dir = strdup(op->resolved_mutable_directory);
995 if (!op->upper_dir)
996 return log_oom();
997
998 return 0;
999 }
1000
1001 static int determine_work_dir(OverlayFSPaths *op) {
1002 _cleanup_free_ char *work_dir = NULL;
1003 int r;
1004
1005 assert(op);
1006 assert(!op->work_dir);
1007
1008 if (!op->upper_dir)
1009 return 0;
1010
1011 if (arg_mutable == MUTABLE_IMPORT)
1012 return 0;
1013
1014 r = work_dir_for_hierarchy(op->hierarchy, op->upper_dir, &work_dir);
1015 if (r < 0)
1016 return r;
1017
1018 op->work_dir = TAKE_PTR(work_dir);
1019 return 0;
1020 }
1021
1022 static int mount_overlayfs_with_op(
1023 OverlayFSPaths *op,
1024 ImageClass image_class,
1025 int noexec,
1026 const char *overlay_path,
1027 const char *meta_path) {
1028
1029 int r;
1030
1031 assert(op);
1032 assert(overlay_path);
1033
1034 r = mkdir_p(overlay_path, 0700);
1035 if (r < 0)
1036 return log_error_errno(r, "Failed to make directory '%s': %m", overlay_path);
1037
1038 r = mkdir_p(meta_path, 0700);
1039 if (r < 0)
1040 return log_error_errno(r, "Failed to make directory '%s': %m", meta_path);
1041
1042 if (op->upper_dir && op->work_dir) {
1043 r = mkdir_p(op->work_dir, 0700);
1044 if (r < 0)
1045 return log_error_errno(r, "Failed to make directory '%s': %m", op->work_dir);
1046 }
1047
1048 r = mount_overlayfs(image_class, noexec, overlay_path, op->lower_dirs, op->upper_dir, op->work_dir);
1049 if (r < 0)
1050 return r;
1051
1052 return 0;
1053 }
1054
1055 static int write_extensions_file(ImageClass image_class, char **extensions, const char *meta_path) {
1056 _cleanup_free_ char *f = NULL, *buf = NULL;
1057 int r;
1058
1059 assert(extensions);
1060 assert(meta_path);
1061
1062 /* Let's generate a metadata file that lists all extensions we took into account for this
1063 * hierarchy. We include this in the final fs, to make things nicely discoverable and
1064 * recognizable. */
1065 f = path_join(meta_path, image_class_info[image_class].dot_directory_name, image_class_info[image_class].short_identifier_plural);
1066 if (!f)
1067 return log_oom();
1068
1069 buf = strv_join(extensions, "\n");
1070 if (!buf)
1071 return log_oom();
1072
1073 r = write_string_file(f, buf, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755);
1074 if (r < 0)
1075 return log_error_errno(r, "Failed to write extension meta file '%s': %m", f);
1076
1077 return 0;
1078 }
1079
1080 static int write_dev_file(ImageClass image_class, const char *meta_path, const char *overlay_path) {
1081 _cleanup_free_ char *f = NULL;
1082 struct stat st;
1083 int r;
1084
1085 assert(meta_path);
1086 assert(overlay_path);
1087
1088 /* Now we have mounted the new file system. Let's now figure out its .st_dev field, and make that
1089 * available in the metadata directory. This is useful to detect whether the metadata dir actually
1090 * belongs to the fs it is found on: if .st_dev of the top-level mount matches it, it's pretty likely
1091 * we are looking at a live tree, and not an unpacked tar or so of one. */
1092 if (stat(overlay_path, &st) < 0)
1093 return log_error_errno(errno, "Failed to stat mount '%s': %m", overlay_path);
1094
1095 f = path_join(meta_path, image_class_info[image_class].dot_directory_name, "dev");
1096 if (!f)
1097 return log_oom();
1098
1099 /* Modifying the underlying layers while the overlayfs is mounted is technically undefined, but at
1100 * least it won't crash or deadlock, as per the kernel docs about overlayfs:
1101 * https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html#changes-to-underlying-filesystems */
1102 r = write_string_file(f, FORMAT_DEVNUM(st.st_dev), WRITE_STRING_FILE_CREATE);
1103 if (r < 0)
1104 return log_error_errno(r, "Failed to write '%s': %m", f);
1105
1106 return 0;
1107 }
1108
1109 static int write_work_dir_file(ImageClass image_class, const char *meta_path, const char *work_dir) {
1110 _cleanup_free_ char *escaped_work_dir_in_root = NULL, *f = NULL;
1111 char *work_dir_in_root = NULL;
1112 int r;
1113
1114 assert(meta_path);
1115
1116 if (!work_dir)
1117 return 0;
1118
1119 work_dir_in_root = path_startswith(work_dir, empty_to_root(arg_root));
1120 if (!work_dir_in_root)
1121 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Workdir '%s' must not be outside root '%s'", work_dir, empty_to_root(arg_root));
1122
1123 f = path_join(meta_path, image_class_info[image_class].dot_directory_name, "work_dir");
1124 if (!f)
1125 return log_oom();
1126
1127 /* Paths can have newlines for whatever reason, so better escape them to really get a single
1128 * line file. */
1129 escaped_work_dir_in_root = cescape(work_dir_in_root);
1130 if (!escaped_work_dir_in_root)
1131 return log_oom();
1132 r = write_string_file(f, escaped_work_dir_in_root, WRITE_STRING_FILE_CREATE);
1133 if (r < 0)
1134 return log_error_errno(r, "Failed to write '%s': %m", f);
1135
1136 return 0;
1137 }
1138
1139 static int store_info_in_meta(
1140 ImageClass image_class,
1141 char **extensions,
1142 const char *meta_path,
1143 const char *overlay_path,
1144 const char *work_dir) {
1145
1146 int r;
1147
1148 assert(extensions);
1149 assert(meta_path);
1150 assert(overlay_path);
1151 /* work_dir may be NULL */
1152
1153 r = write_extensions_file(image_class, extensions, meta_path);
1154 if (r < 0)
1155 return r;
1156
1157 r = write_dev_file(image_class, meta_path, overlay_path);
1158 if (r < 0)
1159 return r;
1160
1161 r = write_work_dir_file(image_class, meta_path, work_dir);
1162 if (r < 0)
1163 return r;
1164
1165 /* Make sure the top-level dir has an mtime marking the point we established the merge */
1166 if (utimensat(AT_FDCWD, meta_path, NULL, AT_SYMLINK_NOFOLLOW) < 0)
1167 return log_error_errno(r, "Failed fix mtime of '%s': %m", meta_path);
1168
1169 return 0;
1170 }
1171
1172 static int make_mounts_read_only(ImageClass image_class, const char *overlay_path, bool mutable) {
1173 int r;
1174
1175 assert(overlay_path);
1176
1177 if (mutable) {
1178 /* Bind mount the meta path as read-only on mutable overlays to avoid accidental
1179 * modifications of the contents of meta directory, which could lead to systemd thinking that
1180 * this hierarchy is not our mount. */
1181 _cleanup_free_ char *f = NULL;
1182
1183 f = path_join(overlay_path, image_class_info[image_class].dot_directory_name);
1184 if (!f)
1185 return log_oom();
1186
1187 r = mount_nofollow_verbose(LOG_ERR, f, f, NULL, MS_BIND, NULL);
1188 if (r < 0)
1189 return r;
1190
1191 r = bind_remount_one(f, MS_RDONLY, MS_RDONLY);
1192 if (r < 0)
1193 return log_error_errno(r, "Failed to remount '%s' as read-only: %m", f);
1194 } else {
1195 /* The overlayfs superblock is read-only. Let's also mark the bind mount read-only. Extra
1196 * turbo safety 😎 */
1197 r = bind_remount_recursive(overlay_path, MS_RDONLY, MS_RDONLY, NULL);
1198 if (r < 0)
1199 return log_error_errno(r, "Failed to make bind mount '%s' read-only: %m", overlay_path);
1200 }
1201
1202 return 0;
1203 }
1204
1205 static int merge_hierarchy(
1206 ImageClass image_class,
1207 const char *hierarchy,
1208 int noexec,
1209 char **extensions,
1210 char **paths,
1211 const char *meta_path,
1212 const char *overlay_path) {
1213
1214 _cleanup_(overlayfs_paths_freep) OverlayFSPaths *op = NULL;
1215 size_t extensions_used = 0;
1216 int r;
1217
1218 assert(hierarchy);
1219 assert(extensions);
1220 assert(paths);
1221 assert(meta_path);
1222 assert(overlay_path);
1223
1224 r = overlayfs_paths_new(hierarchy, &op);
1225 if (r < 0)
1226 return r;
1227
1228 r = determine_lower_dirs(op, paths, meta_path, &extensions_used);
1229 if (r < 0)
1230 return r;
1231
1232 if (extensions_used == 0) /* No extension with files in this hierarchy? Then don't do anything. */
1233 return 0;
1234
1235 r = determine_upper_dir(op);
1236 if (r < 0)
1237 return r;
1238
1239 r = determine_work_dir(op);
1240 if (r < 0)
1241 return r;
1242
1243 r = mount_overlayfs_with_op(op, image_class, noexec, overlay_path, meta_path);
1244 if (r < 0)
1245 return r;
1246
1247 r = store_info_in_meta(image_class, extensions, meta_path, overlay_path, op->work_dir);
1248 if (r < 0)
1249 return r;
1250
1251 r = make_mounts_read_only(image_class, overlay_path, op->upper_dir && op->work_dir);
1252 if (r < 0)
1253 return r;
1254
1255 return 1;
1256 }
1257
1258 static int strverscmp_improvedp(char *const* a, char *const* b) {
1259 /* usable in qsort() for sorting a string array with strverscmp_improved() */
1260 return strverscmp_improved(*a, *b);
1261 }
1262
1263 static const ImagePolicy *pick_image_policy(const Image *img) {
1264 assert(img);
1265 assert(img->path);
1266
1267 /* Explicitly specified policy always wins */
1268 if (arg_image_policy)
1269 return arg_image_policy;
1270
1271 /* If located in /.extra/sysext/ in the initrd, then it was placed there by systemd-stub, and was
1272 * picked up from an untrusted ESP. Thus, require a stricter policy by default for them. (For the
1273 * other directories we assume the appropriate level of trust was already established already. */
1274
1275 if (in_initrd()) {
1276 if (path_startswith(img->path, "/.extra/sysext/"))
1277 return &image_policy_sysext_strict;
1278 if (path_startswith(img->path, "/.extra/confext/"))
1279 return &image_policy_confext_strict;
1280
1281 /* Better safe than sorry, refuse everything else passed in via the untrusted /.extra/ dir */
1282 if (path_startswith(img->path, "/.extra/"))
1283 return &image_policy_deny;
1284 }
1285
1286 return image_class_info[img->class].default_image_policy;
1287 }
1288
1289 static int merge_subprocess(
1290 ImageClass image_class,
1291 char **hierarchies,
1292 bool force,
1293 int noexec,
1294 Hashmap *images,
1295 const char *workspace) {
1296
1297 _cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL, *host_os_release_api_level = NULL, *buf = NULL;
1298 _cleanup_strv_free_ char **extensions = NULL, **paths = NULL;
1299 size_t n_extensions = 0;
1300 unsigned n_ignored = 0;
1301 Image *img;
1302 int r;
1303
1304 /* Mark the whole of /run as MS_SLAVE, so that we can mount stuff below it that doesn't show up on
1305 * the host otherwise. */
1306 r = mount_nofollow_verbose(LOG_ERR, NULL, "/run", NULL, MS_SLAVE|MS_REC, NULL);
1307 if (r < 0)
1308 return log_error_errno(r, "Failed to remount /run/ MS_SLAVE: %m");
1309
1310 /* Let's create the workspace if it's missing */
1311 r = mkdir_p(workspace, 0700);
1312 if (r < 0)
1313 return log_error_errno(r, "Failed to create '%s': %m", workspace);
1314
1315 /* Let's mount a tmpfs to our workspace. This way we don't need to clean up the inodes we mount over,
1316 * but let the kernel do that entirely automatically, once our namespace dies. Note that this file
1317 * system won't be visible to anyone but us, since we opened our own namespace and then made the
1318 * /run/ hierarchy (which our workspace is contained in) MS_SLAVE, see above. */
1319 r = mount_nofollow_verbose(LOG_ERR, image_class_info[image_class].short_identifier, workspace, "tmpfs", 0, "mode=0700");
1320 if (r < 0)
1321 return r;
1322
1323 /* Acquire host OS release info, so that we can compare it with the extension's data */
1324 r = parse_os_release(
1325 arg_root,
1326 "ID", &host_os_release_id,
1327 "VERSION_ID", &host_os_release_version_id,
1328 image_class_info[image_class].level_env, &host_os_release_api_level);
1329 if (r < 0)
1330 return log_error_errno(r, "Failed to acquire 'os-release' data of OS tree '%s': %m", empty_to_root(arg_root));
1331 if (isempty(host_os_release_id))
1332 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1333 "'ID' field not found or empty in 'os-release' data of OS tree '%s': %m",
1334 empty_to_root(arg_root));
1335
1336 /* Let's now mount all images */
1337 HASHMAP_FOREACH(img, images) {
1338 _cleanup_free_ char *p = NULL;
1339
1340 p = path_join(workspace, image_class_info[image_class].short_identifier_plural, img->name);
1341 if (!p)
1342 return log_oom();
1343
1344 r = mkdir_p(p, 0700);
1345 if (r < 0)
1346 return log_error_errno(r, "Failed to create %s: %m", p);
1347
1348 switch (img->type) {
1349 case IMAGE_DIRECTORY:
1350 case IMAGE_SUBVOLUME:
1351
1352 if (!force) {
1353 r = extension_has_forbidden_content(p);
1354 if (r < 0)
1355 return r;
1356 if (r > 0) {
1357 n_ignored++;
1358 continue;
1359 }
1360 }
1361
1362 r = mount_nofollow_verbose(LOG_ERR, img->path, p, NULL, MS_BIND, NULL);
1363 if (r < 0)
1364 return r;
1365
1366 /* Make this a read-only bind mount */
1367 r = bind_remount_recursive(p, MS_RDONLY, MS_RDONLY, NULL);
1368 if (r < 0)
1369 return log_error_errno(r, "Failed to make bind mount '%s' read-only: %m", p);
1370
1371 break;
1372
1373 case IMAGE_RAW:
1374 case IMAGE_BLOCK: {
1375 _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
1376 _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
1377 _cleanup_(verity_settings_done) VeritySettings verity_settings = VERITY_SETTINGS_DEFAULT;
1378 DissectImageFlags flags =
1379 DISSECT_IMAGE_READ_ONLY |
1380 DISSECT_IMAGE_GENERIC_ROOT |
1381 DISSECT_IMAGE_REQUIRE_ROOT |
1382 DISSECT_IMAGE_MOUNT_ROOT_ONLY |
1383 DISSECT_IMAGE_USR_NO_ROOT |
1384 DISSECT_IMAGE_ADD_PARTITION_DEVICES |
1385 DISSECT_IMAGE_PIN_PARTITION_DEVICES;
1386
1387 r = verity_settings_load(&verity_settings, img->path, NULL, NULL);
1388 if (r < 0)
1389 return log_error_errno(r, "Failed to read verity artifacts for %s: %m", img->path);
1390
1391 if (verity_settings.data_path)
1392 flags |= DISSECT_IMAGE_NO_PARTITION_TABLE;
1393
1394 if (!force)
1395 flags |= DISSECT_IMAGE_VALIDATE_OS_EXT;
1396
1397 r = loop_device_make_by_path(
1398 img->path,
1399 O_RDONLY,
1400 /* sector_size= */ UINT32_MAX,
1401 FLAGS_SET(flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN,
1402 LOCK_SH,
1403 &d);
1404 if (r < 0)
1405 return log_error_errno(r, "Failed to set up loopback device for %s: %m", img->path);
1406
1407 r = dissect_loop_device_and_warn(
1408 d,
1409 &verity_settings,
1410 /* mount_options= */ NULL,
1411 pick_image_policy(img),
1412 flags,
1413 &m);
1414 if (r < 0)
1415 return r;
1416
1417 r = dissected_image_load_verity_sig_partition(
1418 m,
1419 d->fd,
1420 &verity_settings);
1421 if (r < 0)
1422 return r;
1423
1424 r = dissected_image_decrypt_interactively(
1425 m, NULL,
1426 &verity_settings,
1427 flags);
1428 if (r < 0)
1429 return r;
1430
1431 r = dissected_image_mount_and_warn(
1432 m,
1433 p,
1434 /* uid_shift= */ UID_INVALID,
1435 /* uid_range= */ UID_INVALID,
1436 /* userns_fd= */ -EBADF,
1437 flags);
1438 if (r < 0 && r != -ENOMEDIUM)
1439 return r;
1440 if (r == -ENOMEDIUM && !force) {
1441 n_ignored++;
1442 continue;
1443 }
1444
1445 r = dissected_image_relinquish(m);
1446 if (r < 0)
1447 return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
1448 break;
1449 }
1450 default:
1451 assert_not_reached();
1452 }
1453
1454 if (force)
1455 log_debug("Force mode enabled, skipping version validation.");
1456 else {
1457 r = extension_release_validate(
1458 img->name,
1459 host_os_release_id,
1460 host_os_release_version_id,
1461 host_os_release_api_level,
1462 in_initrd() ? "initrd" : "system",
1463 image_extension_release(img, image_class),
1464 image_class);
1465 if (r < 0)
1466 return r;
1467 if (r == 0) {
1468 n_ignored++;
1469 continue;
1470 }
1471 }
1472
1473 /* Nice! This one is an extension we want. */
1474 r = strv_extend(&extensions, img->name);
1475 if (r < 0)
1476 return log_oom();
1477
1478 n_extensions++;
1479 }
1480
1481 /* Nothing left? Then shortcut things */
1482 if (n_extensions == 0) {
1483 if (n_ignored > 0)
1484 log_info("No suitable extensions found (%u ignored due to incompatible image(s)).", n_ignored);
1485 else
1486 log_info("No extensions found.");
1487 return 0;
1488 }
1489
1490 /* Order by version sort with strverscmp_improved() */
1491 typesafe_qsort(extensions, n_extensions, strverscmp_improvedp);
1492
1493 buf = strv_join(extensions, "', '");
1494 if (!buf)
1495 return log_oom();
1496
1497 log_info("Using extensions '%s'.", buf);
1498
1499 /* Build table of extension paths (in reverse order) */
1500 paths = new0(char*, n_extensions + 1);
1501 if (!paths)
1502 return log_oom();
1503
1504 for (size_t k = 0; k < n_extensions; k++) {
1505 _cleanup_free_ char *p = NULL;
1506
1507 assert_se(img = hashmap_get(images, extensions[n_extensions - 1 - k]));
1508
1509 p = path_join(workspace, image_class_info[image_class].short_identifier_plural, img->name);
1510 if (!p)
1511 return log_oom();
1512
1513 paths[k] = TAKE_PTR(p);
1514 }
1515
1516 /* Let's now unmerge the status quo ante, since to build the new overlayfs we need a reference to the
1517 * underlying fs. */
1518 STRV_FOREACH(h, hierarchies) {
1519 _cleanup_free_ char *resolved = NULL;
1520
1521 r = chase(*h, arg_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
1522 if (r < 0)
1523 return log_error_errno(r, "Failed to resolve hierarchy '%s%s': %m", strempty(arg_root), *h);
1524
1525 r = unmerge_hierarchy(image_class, resolved);
1526 if (r < 0)
1527 return r;
1528 }
1529
1530 /* Create overlayfs mounts for all hierarchies */
1531 STRV_FOREACH(h, hierarchies) {
1532 _cleanup_free_ char *meta_path = NULL, *overlay_path = NULL;
1533
1534 meta_path = path_join(workspace, "meta", *h); /* The place where to store metadata about this instance */
1535 if (!meta_path)
1536 return log_oom();
1537
1538 overlay_path = path_join(workspace, "overlay", *h); /* The resulting overlayfs instance */
1539 if (!overlay_path)
1540 return log_oom();
1541
1542 r = merge_hierarchy(
1543 image_class,
1544 *h,
1545 noexec,
1546 extensions,
1547 paths,
1548 meta_path,
1549 overlay_path);
1550 if (r < 0)
1551 return r;
1552 }
1553
1554 /* And move them all into place. This is where things appear in the host namespace */
1555 STRV_FOREACH(h, hierarchies) {
1556 _cleanup_free_ char *p = NULL, *resolved = NULL;
1557
1558 p = path_join(workspace, "overlay", *h);
1559 if (!p)
1560 return log_oom();
1561
1562 if (laccess(p, F_OK) < 0) {
1563 if (errno != ENOENT)
1564 return log_error_errno(errno, "Failed to check if '%s' exists: %m", p);
1565
1566 /* Hierarchy apparently was empty in all extensions, and wasn't mounted, ignoring. */
1567 continue;
1568 }
1569
1570 r = chase(*h, arg_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
1571 if (r < 0)
1572 return log_error_errno(r, "Failed to resolve hierarchy '%s%s': %m", strempty(arg_root), *h);
1573
1574 r = mkdir_p(resolved, 0755);
1575 if (r < 0)
1576 return log_error_errno(r, "Failed to create hierarchy mount point '%s': %m", resolved);
1577
1578 /* Using MS_REC to potentially bring in our read-only bind mount of metadata. */
1579 r = mount_nofollow_verbose(LOG_ERR, p, resolved, NULL, MS_BIND|MS_REC, NULL);
1580 if (r < 0)
1581 return r;
1582
1583 log_info("Merged extensions into '%s'.", resolved);
1584 }
1585
1586 return 1;
1587 }
1588
1589 static int merge(ImageClass image_class,
1590 char **hierarchies,
1591 bool force,
1592 bool no_reload,
1593 int noexec,
1594 Hashmap *images) {
1595 pid_t pid;
1596 int r;
1597
1598 r = safe_fork("(sd-merge)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_NEW_MOUNTNS, &pid);
1599 if (r < 0)
1600 return log_error_errno(r, "Failed to fork off child: %m");
1601 if (r == 0) {
1602 /* Child with its own mount namespace */
1603
1604 r = merge_subprocess(image_class, hierarchies, force, noexec, images, "/run/systemd/sysext");
1605 if (r < 0)
1606 _exit(EXIT_FAILURE);
1607
1608 /* Our namespace ceases to exist here, also implicitly detaching all temporary mounts we
1609 * created below /run. Nice! */
1610
1611 _exit(r > 0 ? EXIT_SUCCESS : 123); /* 123 means: didn't find any extensions */
1612 }
1613
1614 r = wait_for_terminate_and_check("(sd-merge)", pid, WAIT_LOG_ABNORMAL);
1615 if (r < 0)
1616 return r;
1617
1618 if (r == 123) /* exit code 123 means: didn't do anything */
1619 return 0;
1620
1621 r = need_reload(image_class, hierarchies, no_reload);
1622 if (r < 0)
1623 return r;
1624 if (r > 0) {
1625 r = daemon_reload();
1626 if (r < 0)
1627 return r;
1628 }
1629
1630 return 1;
1631 }
1632
1633 static int image_discover_and_read_metadata(
1634 ImageClass image_class,
1635 Hashmap **ret_images) {
1636 _cleanup_hashmap_free_ Hashmap *images = NULL;
1637 Image *img;
1638 int r;
1639
1640 assert(ret_images);
1641
1642 images = hashmap_new(&image_hash_ops);
1643 if (!images)
1644 return log_oom();
1645
1646 r = image_discover(image_class, arg_root, images);
1647 if (r < 0)
1648 return log_error_errno(r, "Failed to discover images: %m");
1649
1650 HASHMAP_FOREACH(img, images) {
1651 r = image_read_metadata(img, image_class_info[image_class].default_image_policy);
1652 if (r < 0)
1653 return log_error_errno(r, "Failed to read metadata for image %s: %m", img->name);
1654 }
1655
1656 *ret_images = TAKE_PTR(images);
1657
1658 return 0;
1659 }
1660
1661 static int look_for_merged_hierarchies(
1662 ImageClass image_class,
1663 char **hierarchies,
1664 const char **ret_which) {
1665 int r;
1666
1667 assert(ret_which);
1668
1669 /* In merge mode fail if things are already merged. (In --refresh mode below we'll unmerge if we find
1670 * things are already merged...) */
1671 STRV_FOREACH(p, hierarchies) {
1672 _cleanup_free_ char *resolved = NULL;
1673
1674 r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
1675 if (r == -ENOENT) {
1676 log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p);
1677 continue;
1678 }
1679 if (r < 0)
1680 return log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root), *p);
1681
1682 r = is_our_mount_point(image_class, resolved);
1683 if (r < 0)
1684 return r;
1685 if (r > 0) {
1686 *ret_which = *p;
1687 return 1;
1688 }
1689 }
1690
1691 *ret_which = NULL;
1692 return 0;
1693 }
1694
1695 static int verb_merge(int argc, char **argv, void *userdata) {
1696 _cleanup_hashmap_free_ Hashmap *images = NULL;
1697 const char *which;
1698 int r;
1699
1700 r = have_effective_cap(CAP_SYS_ADMIN);
1701 if (r < 0)
1702 return log_error_errno(r, "Failed to check if we have enough privileges: %m");
1703 if (r == 0)
1704 return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged.");
1705
1706 r = image_discover_and_read_metadata(arg_image_class, &images);
1707 if (r < 0)
1708 return r;
1709
1710 r = look_for_merged_hierarchies(arg_image_class, arg_hierarchies, &which);
1711 if (r < 0)
1712 return r;
1713 if (r > 0)
1714 return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Hierarchy '%s' is already merged.", which);
1715
1716 return merge(arg_image_class,
1717 arg_hierarchies,
1718 arg_force,
1719 arg_no_reload,
1720 arg_noexec,
1721 images);
1722 }
1723
1724 typedef struct MethodMergeParameters {
1725 const char *class;
1726 int force;
1727 int no_reload;
1728 int noexec;
1729 } MethodMergeParameters;
1730
1731 static int parse_merge_parameters(Varlink *link, JsonVariant *parameters, MethodMergeParameters *p) {
1732
1733 static const JsonDispatch dispatch_table[] = {
1734 { "class", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodMergeParameters, class), 0 },
1735 { "force", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(MethodMergeParameters, force), 0 },
1736 { "noReload", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(MethodMergeParameters, no_reload), 0 },
1737 { "noexec", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(MethodMergeParameters, noexec), 0 },
1738 {}
1739 };
1740
1741 assert(link);
1742 assert(parameters);
1743 assert(p);
1744
1745 return varlink_dispatch(link, parameters, dispatch_table, p);
1746 }
1747
1748 static int vl_method_merge(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
1749 _cleanup_hashmap_free_ Hashmap *images = NULL;
1750 MethodMergeParameters p = {
1751 .force = -1,
1752 .no_reload = -1,
1753 .noexec = -1,
1754 };
1755 _cleanup_strv_free_ char **hierarchies = NULL;
1756 ImageClass image_class = arg_image_class;
1757 int r;
1758
1759 assert(link);
1760
1761 r = parse_merge_parameters(link, parameters, &p);
1762 if (r != 0)
1763 return r;
1764
1765 r = parse_image_class_parameter(link, p.class, &image_class, &hierarchies);
1766 if (r < 0)
1767 return r;
1768
1769 r = image_discover_and_read_metadata(image_class, &images);
1770 if (r < 0)
1771 return r;
1772
1773 const char *which;
1774 r = look_for_merged_hierarchies(
1775 image_class,
1776 hierarchies ?: arg_hierarchies,
1777 &which);
1778 if (r < 0)
1779 return r;
1780 if (r > 0)
1781 return varlink_errorb(link, "io.systemd.sysext.AlreadyMerged", JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("hierarchy", which)));
1782
1783 r = merge(image_class,
1784 hierarchies ?: arg_hierarchies,
1785 p.force >= 0 ? p.force : arg_force,
1786 p.no_reload >= 0 ? p.no_reload : arg_no_reload,
1787 p.noexec >= 0 ? p.noexec : arg_noexec,
1788 images);
1789 if (r < 0)
1790 return r;
1791
1792 return varlink_reply(link, NULL);
1793 }
1794
1795 static int refresh(
1796 ImageClass image_class,
1797 char **hierarchies,
1798 bool force,
1799 bool no_reload,
1800 int noexec) {
1801
1802 _cleanup_hashmap_free_ Hashmap *images = NULL;
1803 int r;
1804
1805 r = image_discover_and_read_metadata(image_class, &images);
1806 if (r < 0)
1807 return r;
1808
1809 /* Returns > 0 if it did something, i.e. a new overlayfs is mounted now. When it does so it
1810 * implicitly unmounts any overlayfs placed there before. Returns == 0 if it did nothing, i.e. no
1811 * extension images found. In this case the old overlayfs remains in place if there was one. */
1812 r = merge(image_class, hierarchies, force, no_reload, noexec, images);
1813 if (r < 0)
1814 return r;
1815 if (r == 0) /* No images found? Then unmerge. The goal of --refresh is after all that after having
1816 * called there's a guarantee that the merge status matches the installed extensions. */
1817 r = unmerge(image_class, hierarchies, no_reload);
1818
1819 /* Net result here is that:
1820 *
1821 * 1. If an overlayfs was mounted before and no extensions exist anymore, we'll have unmerged things.
1822 *
1823 * 2. If an overlayfs was mounted before, and there are still extensions installed' we'll have
1824 * unmerged and then merged things again.
1825 *
1826 * 3. If an overlayfs so far wasn't mounted, and there are extensions installed, we'll have it
1827 * mounted now.
1828 *
1829 * 4. If there was no overlayfs mount so far, and no extensions installed, we implement a NOP.
1830 */
1831
1832 return 0;
1833 }
1834
1835 static int verb_refresh(int argc, char **argv, void *userdata) {
1836 int r;
1837
1838 r = have_effective_cap(CAP_SYS_ADMIN);
1839 if (r < 0)
1840 return log_error_errno(r, "Failed to check if we have enough privileges: %m");
1841 if (r == 0)
1842 return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged.");
1843
1844 return refresh(arg_image_class,
1845 arg_hierarchies,
1846 arg_force,
1847 arg_no_reload,
1848 arg_noexec);
1849 }
1850
1851 static int vl_method_refresh(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
1852
1853 MethodMergeParameters p = {
1854 .force = -1,
1855 .no_reload = -1,
1856 .noexec = -1,
1857 };
1858 _cleanup_strv_free_ char **hierarchies = NULL;
1859 ImageClass image_class = arg_image_class;
1860 int r;
1861
1862 assert(link);
1863
1864 r = parse_merge_parameters(link, parameters, &p);
1865 if (r != 0)
1866 return r;
1867
1868 r = parse_image_class_parameter(link, p.class, &image_class, &hierarchies);
1869 if (r < 0)
1870 return r;
1871
1872 r = refresh(image_class,
1873 hierarchies ?: arg_hierarchies,
1874 p.force >= 0 ? p.force : arg_force,
1875 p.no_reload >= 0 ? p.no_reload : arg_no_reload,
1876 p.noexec >= 0 ? p.noexec : arg_noexec);
1877 if (r < 0)
1878 return r;
1879
1880 return varlink_reply(link, NULL);
1881 }
1882
1883 static int verb_list(int argc, char **argv, void *userdata) {
1884 _cleanup_hashmap_free_ Hashmap *images = NULL;
1885 _cleanup_(table_unrefp) Table *t = NULL;
1886 Image *img;
1887 int r;
1888
1889 images = hashmap_new(&image_hash_ops);
1890 if (!images)
1891 return log_oom();
1892
1893 r = image_discover(arg_image_class, arg_root, images);
1894 if (r < 0)
1895 return log_error_errno(r, "Failed to discover images: %m");
1896
1897 if ((arg_json_format_flags & JSON_FORMAT_OFF) && hashmap_isempty(images)) {
1898 log_info("No OS extensions found.");
1899 return 0;
1900 }
1901
1902 t = table_new("name", "type", "path", "time");
1903 if (!t)
1904 return log_oom();
1905
1906 HASHMAP_FOREACH(img, images) {
1907 r = table_add_many(
1908 t,
1909 TABLE_STRING, img->name,
1910 TABLE_STRING, image_type_to_string(img->type),
1911 TABLE_PATH, img->path,
1912 TABLE_TIMESTAMP, img->mtime != 0 ? img->mtime : img->crtime);
1913 if (r < 0)
1914 return table_log_add_error(r);
1915 }
1916
1917 (void) table_set_sort(t, (size_t) 0);
1918
1919 return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
1920 }
1921
1922 typedef struct MethodListParameters {
1923 const char *class;
1924 } MethodListParameters;
1925
1926 static int vl_method_list(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
1927
1928 static const JsonDispatch dispatch_table[] = {
1929 { "class", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodListParameters, class), 0 },
1930 {}
1931 };
1932 MethodListParameters p = {
1933 };
1934 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
1935 _cleanup_hashmap_free_ Hashmap *images = NULL;
1936 ImageClass image_class = arg_image_class;
1937 Image *img;
1938 int r;
1939
1940 assert(link);
1941
1942 r = varlink_dispatch(link, parameters, dispatch_table, &p);
1943 if (r != 0)
1944 return r;
1945
1946 r = parse_image_class_parameter(link, p.class, &image_class, NULL);
1947 if (r < 0)
1948 return r;
1949
1950 images = hashmap_new(&image_hash_ops);
1951 if (!images)
1952 return -ENOMEM;
1953
1954 r = image_discover(image_class, arg_root, images);
1955 if (r < 0)
1956 return r;
1957
1958 HASHMAP_FOREACH(img, images) {
1959 if (v) {
1960 /* Send previous item with more=true */
1961 r = varlink_notify(link, v);
1962 if (r < 0)
1963 return r;
1964 }
1965
1966 v = json_variant_unref(v);
1967
1968 r = image_to_json(img, &v);
1969 if (r < 0)
1970 return r;
1971 }
1972
1973 if (v) /* Send final item with more=false */
1974 return varlink_reply(link, v);
1975
1976 return varlink_error(link, "io.systemd.sysext.NoImagesFound", NULL);
1977 }
1978
1979 static int verb_help(int argc, char **argv, void *userdata) {
1980 _cleanup_free_ char *link = NULL;
1981 int r;
1982
1983 r = terminal_urlify_man(image_class_info[arg_image_class].full_identifier, "8", &link);
1984 if (r < 0)
1985 return log_oom();
1986
1987 printf("%1$s [OPTIONS...] COMMAND\n"
1988 "\n%5$s%7$s%6$s\n"
1989 "\n%3$sCommands:%4$s\n"
1990 " status Show current merge status (default)\n"
1991 " merge Merge extensions into relevant hierarchies\n"
1992 " unmerge Unmerge extensions from relevant hierarchies\n"
1993 " refresh Unmerge/merge extensions again\n"
1994 " list List installed extensions\n"
1995 " -h --help Show this help\n"
1996 " --version Show package version\n"
1997 "\n%3$sOptions:%4$s\n"
1998 " --no-pager Do not pipe output into a pager\n"
1999 " --no-legend Do not show the headers and footers\n"
2000 " --root=PATH Operate relative to root path\n"
2001 " --json=pretty|short|off\n"
2002 " Generate JSON output\n"
2003 " --force Ignore version incompatibilities\n"
2004 " --no-reload Do not reload the service manager\n"
2005 " --image-policy=POLICY\n"
2006 " Specify disk image dissection policy\n"
2007 " --noexec=BOOL Whether to mount extension overlay with noexec\n"
2008 "\nSee the %2$s for details.\n",
2009 program_invocation_short_name,
2010 link,
2011 ansi_underline(),
2012 ansi_normal(),
2013 ansi_highlight(),
2014 ansi_normal(),
2015 image_class_info[arg_image_class].blurb);
2016
2017 return 0;
2018 }
2019
2020 static int parse_argv(int argc, char *argv[]) {
2021
2022 enum {
2023 ARG_VERSION = 0x100,
2024 ARG_NO_PAGER,
2025 ARG_NO_LEGEND,
2026 ARG_ROOT,
2027 ARG_JSON,
2028 ARG_FORCE,
2029 ARG_IMAGE_POLICY,
2030 ARG_NOEXEC,
2031 ARG_NO_RELOAD,
2032 ARG_MUTABLE,
2033 };
2034
2035 static const struct option options[] = {
2036 { "help", no_argument, NULL, 'h' },
2037 { "version", no_argument, NULL, ARG_VERSION },
2038 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
2039 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
2040 { "root", required_argument, NULL, ARG_ROOT },
2041 { "json", required_argument, NULL, ARG_JSON },
2042 { "force", no_argument, NULL, ARG_FORCE },
2043 { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
2044 { "noexec", required_argument, NULL, ARG_NOEXEC },
2045 { "no-reload", no_argument, NULL, ARG_NO_RELOAD },
2046 { "mutable", required_argument, NULL, ARG_MUTABLE },
2047 {}
2048 };
2049
2050 int c, r;
2051
2052 assert(argc >= 0);
2053 assert(argv);
2054
2055 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
2056
2057 switch (c) {
2058
2059 case 'h':
2060 return verb_help(argc, argv, NULL);
2061
2062 case ARG_VERSION:
2063 return version();
2064
2065 case ARG_NO_PAGER:
2066 arg_pager_flags |= PAGER_DISABLE;
2067 break;
2068
2069 case ARG_NO_LEGEND:
2070 arg_legend = false;
2071 break;
2072
2073 case ARG_ROOT:
2074 r = parse_path_argument(optarg, false, &arg_root);
2075 if (r < 0)
2076 return r;
2077 /* If --root= is provided, do not reload the service manager */
2078 arg_no_reload = true;
2079 break;
2080
2081 case ARG_JSON:
2082 r = parse_json_argument(optarg, &arg_json_format_flags);
2083 if (r <= 0)
2084 return r;
2085
2086 break;
2087
2088 case ARG_FORCE:
2089 arg_force = true;
2090 break;
2091
2092 case ARG_IMAGE_POLICY:
2093 r = parse_image_policy_argument(optarg, &arg_image_policy);
2094 if (r < 0)
2095 return r;
2096 break;
2097
2098 case ARG_NOEXEC:
2099 r = parse_boolean_argument("--noexec", optarg, NULL);
2100 if (r < 0)
2101 return r;
2102
2103 arg_noexec = r;
2104 break;
2105
2106 case ARG_NO_RELOAD:
2107 arg_no_reload = true;
2108 break;
2109
2110 case ARG_MUTABLE:
2111 if (streq(optarg, "auto"))
2112 arg_mutable = MUTABLE_AUTO;
2113 else if (streq(optarg, "import"))
2114 arg_mutable = MUTABLE_IMPORT;
2115 else {
2116 r = parse_boolean(optarg);
2117 if (r < 0)
2118 return log_error_errno(r, "Failed to parse argument to --mutable=: %s", optarg);
2119 arg_mutable = r ? MUTABLE_YES : MUTABLE_NO;
2120 }
2121 break;
2122
2123 case '?':
2124 return -EINVAL;
2125
2126 default:
2127 assert_not_reached();
2128 }
2129
2130 r = varlink_invocation(VARLINK_ALLOW_ACCEPT);
2131 if (r < 0)
2132 return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
2133 if (r > 0)
2134 arg_varlink = true;
2135
2136 return 1;
2137 }
2138
2139 static int sysext_main(int argc, char *argv[]) {
2140
2141 static const Verb verbs[] = {
2142 { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
2143 { "merge", VERB_ANY, 1, 0, verb_merge },
2144 { "unmerge", VERB_ANY, 1, 0, verb_unmerge },
2145 { "refresh", VERB_ANY, 1, 0, verb_refresh },
2146 { "list", VERB_ANY, 1, 0, verb_list },
2147 { "help", VERB_ANY, 1, 0, verb_help },
2148 {}
2149 };
2150
2151 return dispatch_verb(argc, argv, verbs, NULL);
2152 }
2153
2154 static int run(int argc, char *argv[]) {
2155 int r;
2156
2157 log_setup();
2158
2159 arg_image_class = invoked_as(argv, "systemd-confext") ? IMAGE_CONFEXT : IMAGE_SYSEXT;
2160
2161 r = parse_argv(argc, argv);
2162 if (r <= 0)
2163 return r;
2164
2165 /* For debugging purposes it might make sense to do this for other hierarchies than /usr/ and
2166 * /opt/, but let's make that a hacker/debugging feature, i.e. env var instead of cmdline
2167 * switch. */
2168 r = parse_env_extension_hierarchies(&arg_hierarchies, image_class_info[arg_image_class].name_env);
2169 if (r < 0)
2170 return log_error_errno(r, "Failed to parse environment variable: %m");
2171
2172 if (arg_varlink) {
2173 _cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL;
2174
2175 /* Invocation as Varlink service */
2176
2177 r = varlink_server_new(&varlink_server, VARLINK_SERVER_ROOT_ONLY);
2178 if (r < 0)
2179 return log_error_errno(r, "Failed to allocate Varlink server: %m");
2180
2181 r = varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_sysext);
2182 if (r < 0)
2183 return log_error_errno(r, "Failed to add Varlink interface: %m");
2184
2185 r = varlink_server_bind_method_many(
2186 varlink_server,
2187 "io.systemd.sysext.Merge", vl_method_merge,
2188 "io.systemd.sysext.Unmerge", vl_method_unmerge,
2189 "io.systemd.sysext.Refresh", vl_method_refresh,
2190 "io.systemd.sysext.List", vl_method_list);
2191 if (r < 0)
2192 return log_error_errno(r, "Failed to bind Varlink methods: %m");
2193
2194 r = varlink_server_loop_auto(varlink_server);
2195 if (r == -EPERM)
2196 return log_error_errno(r, "Invoked by unprivileged Varlink peer, refusing.");
2197 if (r < 0)
2198 return log_error_errno(r, "Failed to run Varlink event loop: %m");
2199
2200 return EXIT_SUCCESS;
2201 }
2202
2203 return sysext_main(argc, argv);
2204 }
2205
2206 DEFINE_MAIN_FUNCTION(run);