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