1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include <linux/loop.h>
13 #include "bus-locator.h"
14 #include "bus-error.h"
15 #include "bus-unit-util.h"
17 #include "capability-util.h"
19 #include "constants.h"
20 #include "devnum-util.h"
21 #include "discover-image.h"
22 #include "dissect-image.h"
25 #include "extension-util.h"
28 #include "format-table.h"
31 #include "initrd-util.h"
33 #include "main-func.h"
34 #include "missing_magic.h"
36 #include "mount-util.h"
37 #include "mountpoint-util.h"
40 #include "parse-argument.h"
41 #include "parse-util.h"
42 #include "pretty-print.h"
43 #include "process-util.h"
44 #include "sort-util.h"
45 #include "terminal-util.h"
46 #include "user-util.h"
48 #include "varlink-io.systemd.sysext.h"
51 static char **arg_hierarchies
= NULL
; /* "/usr" + "/opt" by default for sysext and /etc by default for confext */
52 static char *arg_root
= NULL
;
53 static JsonFormatFlags arg_json_format_flags
= JSON_FORMAT_OFF
;
54 static PagerFlags arg_pager_flags
= 0;
55 static bool arg_legend
= true;
56 static bool arg_force
= false;
57 static bool arg_no_reload
= false;
58 static int arg_noexec
= -1;
59 static ImagePolicy
*arg_image_policy
= NULL
;
60 static bool arg_varlink
= false;
62 /* Is set to IMAGE_CONFEXT when systemd is called with the confext functionality instead of the default */
63 static ImageClass arg_image_class
= IMAGE_SYSEXT
;
65 STATIC_DESTRUCTOR_REGISTER(arg_hierarchies
, strv_freep
);
66 STATIC_DESTRUCTOR_REGISTER(arg_root
, freep
);
67 STATIC_DESTRUCTOR_REGISTER(arg_image_policy
, image_policy_freep
);
69 /* Helper struct for naming simplicity and reusability */
71 const char *dot_directory_name
;
72 const char *directory_name
;
73 const char *short_identifier
;
74 const char *short_identifier_plural
;
75 const char *level_env
;
76 const char *scope_env
;
78 const ImagePolicy
*default_image_policy
;
79 unsigned long default_mount_flags
;
80 } image_class_info
[_IMAGE_CLASS_MAX
] = {
82 .dot_directory_name
= ".systemd-sysext",
83 .directory_name
= "systemd-sysext",
84 .short_identifier
= "sysext",
85 .short_identifier_plural
= "extensions",
86 .level_env
= "SYSEXT_LEVEL",
87 .scope_env
= "SYSEXT_SCOPE",
88 .name_env
= "SYSTEMD_SYSEXT_HIERARCHIES",
89 .default_image_policy
= &image_policy_sysext
,
90 .default_mount_flags
= MS_RDONLY
|MS_NODEV
,
93 .dot_directory_name
= ".systemd-confext",
94 .directory_name
= "systemd-confext",
95 .short_identifier
= "confext",
96 .short_identifier_plural
= "confexts",
97 .level_env
= "CONFEXT_LEVEL",
98 .scope_env
= "CONFEXT_SCOPE",
99 .name_env
= "SYSTEMD_CONFEXT_HIERARCHIES",
100 .default_image_policy
= &image_policy_confext
,
101 .default_mount_flags
= MS_RDONLY
|MS_NODEV
|MS_NOSUID
|MS_NOEXEC
,
105 static int is_our_mount_point(
106 ImageClass image_class
,
109 _cleanup_free_
char *buf
= NULL
, *f
= NULL
;
116 r
= path_is_mount_point(p
, NULL
, 0);
118 log_debug_errno(r
, "Hierarchy '%s' doesn't exist.", p
);
122 return log_error_errno(r
, "Failed to determine whether '%s' is a mount point: %m", p
);
124 log_debug("Hierarchy '%s' is not a mount point, skipping.", p
);
128 /* So we know now that it's a mount point. Now let's check if it's one of ours, so that we don't
129 * accidentally unmount the user's own /usr/ but just the mounts we established ourselves. We do this
130 * check by looking into the metadata directory we place in merged mounts: if the file
131 * ../dev contains the major/minor device pair of the mount we have a good reason to
132 * believe this is one of our mounts. This thorough check has the benefit that we aren't easily
133 * confused if people tar up one of our merged trees and untar them elsewhere where we might mistake
134 * them for a live sysext tree. */
136 f
= path_join(p
, image_class_info
[image_class
].dot_directory_name
, "dev");
140 r
= read_one_line_file(f
, &buf
);
142 log_debug("Hierarchy '%s' does not carry a %s/dev file, not a merged tree.", p
, image_class_info
[image_class
].dot_directory_name
);
146 return log_error_errno(r
, "Failed to determine whether hierarchy '%s' contains '%s/dev': %m", p
, image_class_info
[image_class
].dot_directory_name
);
148 r
= parse_devnum(buf
, &dev
);
150 return log_error_errno(r
, "Failed to parse device major/minor stored in '%s/dev' file on '%s': %m", image_class_info
[image_class
].dot_directory_name
, p
);
152 if (lstat(p
, &st
) < 0)
153 return log_error_errno(r
, "Failed to stat %s: %m", p
);
155 if (st
.st_dev
!= dev
) {
156 log_debug("Hierarchy '%s' reports a different device major/minor than what we are seeing, assuming offline copy.", p
);
163 static int need_reload(
164 ImageClass image_class
,
168 /* Parse the mounted images to find out if we need to reload the daemon. */
174 STRV_FOREACH(p
, hierarchies
) {
175 _cleanup_free_
char *f
= NULL
, *buf
= NULL
, *resolved
= NULL
;
176 _cleanup_strv_free_
char **mounted_extensions
= NULL
;
178 r
= chase(*p
, arg_root
, CHASE_PREFIX_ROOT
, &resolved
, NULL
);
180 log_debug_errno(r
, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root
), *p
);
184 log_warning_errno(r
, "Failed to resolve path to hierarchy '%s%s': %m, ignoring.", strempty(arg_root
), *p
);
188 r
= is_our_mount_point(image_class
, resolved
);
194 f
= path_join(resolved
, image_class_info
[image_class
].dot_directory_name
, image_class_info
[image_class
].short_identifier_plural
);
198 r
= read_full_file(f
, &buf
, NULL
);
200 return log_error_errno(r
, "Failed to open '%s': %m", f
);
202 mounted_extensions
= strv_split_newlines(buf
);
203 if (!mounted_extensions
)
206 STRV_FOREACH(extension
, mounted_extensions
) {
207 _cleanup_strv_free_
char **extension_release
= NULL
;
208 const char *extension_reload_manager
= NULL
;
211 r
= load_extension_release_pairs(arg_root
, image_class
, *extension
, /* relax_extension_release_check */ true, &extension_release
);
213 log_debug_errno(r
, "Failed to parse extension-release metadata of %s, ignoring: %m", *extension
);
217 extension_reload_manager
= strv_env_pairs_get(extension_release
, "EXTENSION_RELOAD_MANAGER");
218 if (isempty(extension_reload_manager
))
221 b
= parse_boolean(extension_reload_manager
);
223 log_warning_errno(b
, "Failed to parse the extension metadata to know if the manager needs to be reloaded, ignoring: %m");
228 /* If at least one extension wants a reload, we reload. */
236 static int daemon_reload(void) {
237 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
240 r
= bus_connect_system_systemd(&bus
);
242 return log_error_errno(r
, "Failed to get D-Bus connection: %m");
244 return bus_service_manager_reload(bus
);
247 static int unmerge_hierarchy(
248 ImageClass image_class
,
256 /* We only unmount /usr/ if it is a mount point and really one of ours, in order not to break
257 * systems where /usr/ is a mount point of its own already. */
259 r
= is_our_mount_point(image_class
, p
);
265 r
= umount_verbose(LOG_ERR
, p
, MNT_DETACH
|UMOUNT_NOFOLLOW
);
267 return log_error_errno(r
, "Failed to unmount file system '%s': %m", p
);
269 log_info("Unmerged '%s'.", p
);
276 ImageClass image_class
,
283 r
= need_reload(image_class
, hierarchies
, no_reload
);
286 need_to_reload
= r
> 0;
288 STRV_FOREACH(p
, hierarchies
) {
289 _cleanup_free_
char *resolved
= NULL
;
291 r
= chase(*p
, arg_root
, CHASE_PREFIX_ROOT
, &resolved
, NULL
);
293 log_debug_errno(r
, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root
), *p
);
297 log_error_errno(r
, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root
), *p
);
304 r
= unmerge_hierarchy(image_class
, resolved
);
305 if (r
< 0 && ret
== 0)
309 if (need_to_reload
) {
318 static int verb_unmerge(int argc
, char **argv
, void *userdata
) {
321 r
= have_effective_cap(CAP_SYS_ADMIN
);
323 return log_error_errno(r
, "Failed to check if we have enough privileges: %m");
325 return log_error_errno(SYNTHETIC_ERRNO(EPERM
), "Need to be privileged.");
327 return unmerge(arg_image_class
,
332 static int parse_image_class_parameter(Varlink
*link
, const char *value
, ImageClass
*image_class
, char ***hierarchies
) {
333 _cleanup_strv_free_
char **h
= NULL
;
343 c
= image_class_from_string(value
);
344 if (!IN_SET(c
, IMAGE_SYSEXT
, IMAGE_CONFEXT
))
345 return varlink_error_invalid_parameter_name(link
, "class");
348 r
= parse_env_extension_hierarchies(&h
, image_class_info
[c
].name_env
);
350 return log_error_errno(r
, "Failed to parse environment variable: %m");
352 strv_free_and_replace(*hierarchies
, h
);
359 typedef struct MethodUnmergeParameters
{
362 } MethodUnmergeParameters
;
364 static int vl_method_unmerge(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
366 static const JsonDispatch dispatch_table
[] = {
367 { "class", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(MethodUnmergeParameters
, class), 0 },
368 { "noReload", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(MethodUnmergeParameters
, no_reload
), 0 },
371 MethodUnmergeParameters p
= {
374 _cleanup_strv_free_
char **hierarchies
= NULL
;
375 ImageClass image_class
= arg_image_class
;
380 r
= varlink_dispatch(link
, parameters
, dispatch_table
, &p
);
384 r
= parse_image_class_parameter(link
, p
.class, &image_class
, &hierarchies
);
388 r
= unmerge(image_class
,
389 hierarchies
?: arg_hierarchies
,
390 p
.no_reload
>= 0 ? p
.no_reload
: arg_no_reload
);
394 return varlink_reply(link
, NULL
);
397 static int verb_status(int argc
, char **argv
, void *userdata
) {
398 _cleanup_(table_unrefp
) Table
*t
= NULL
;
401 t
= table_new("hierarchy", "extensions", "since");
405 table_set_ersatz_string(t
, TABLE_ERSATZ_DASH
);
407 STRV_FOREACH(p
, arg_hierarchies
) {
408 _cleanup_free_
char *resolved
= NULL
, *f
= NULL
, *buf
= NULL
;
409 _cleanup_strv_free_
char **l
= NULL
;
412 r
= chase(*p
, arg_root
, CHASE_PREFIX_ROOT
, &resolved
, NULL
);
414 log_debug_errno(r
, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root
), *p
);
418 log_error_errno(r
, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root
), *p
);
422 r
= is_our_mount_point(arg_image_class
, resolved
);
429 TABLE_STRING
, "none",
430 TABLE_SET_COLOR
, ansi_grey(),
433 return table_log_add_error(r
);
438 f
= path_join(resolved
, image_class_info
[arg_image_class
].dot_directory_name
, image_class_info
[arg_image_class
].short_identifier_plural
);
442 r
= read_full_file(f
, &buf
, NULL
);
444 return log_error_errno(r
, "Failed to open '%s': %m", f
);
446 l
= strv_split_newlines(buf
);
450 if (stat(*p
, &st
) < 0)
451 return log_error_errno(r
, "Failed to stat() '%s': %m", *p
);
457 TABLE_TIMESTAMP
, timespec_load(&st
.st_mtim
));
459 return table_log_add_error(r
);
468 (void) table_set_sort(t
, (size_t) 0);
470 r
= table_print_with_pager(t
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
477 static int mount_overlayfs(
478 ImageClass image_class
,
483 _cleanup_free_
char *options
= NULL
;
484 bool separator
= false;
490 options
= strdup("lowerdir=");
494 STRV_FOREACH(l
, layers
) {
495 _cleanup_free_
char *escaped
= NULL
;
497 escaped
= shell_escape(*l
, ",:");
501 if (!strextend(&options
, separator
? ":" : "", escaped
))
507 flags
= image_class_info
[image_class
].default_mount_flags
;
509 SET_FLAG(flags
, MS_NOEXEC
, noexec
);
511 /* Now mount the actual overlayfs */
512 r
= mount_nofollow_verbose(LOG_ERR
, image_class_info
[image_class
].short_identifier
, where
, "overlay", flags
, options
);
519 static int merge_hierarchy(
520 ImageClass image_class
,
521 const char *hierarchy
,
525 const char *meta_path
,
526 const char *overlay_path
) {
528 _cleanup_free_
char *resolved_hierarchy
= NULL
, *f
= NULL
, *buf
= NULL
;
529 _cleanup_strv_free_
char **layers
= NULL
;
535 assert(overlay_path
);
537 /* Resolve the path of the host's version of the hierarchy, i.e. what we want to use as lowest layer
538 * in the overlayfs stack. */
539 r
= chase(hierarchy
, arg_root
, CHASE_PREFIX_ROOT
, &resolved_hierarchy
, NULL
);
541 log_debug_errno(r
, "Hierarchy '%s' on host doesn't exist, not merging.", hierarchy
);
543 return log_error_errno(r
, "Failed to resolve host hierarchy '%s': %m", hierarchy
);
545 r
= dir_is_empty(resolved_hierarchy
, /* ignore_hidden_or_backup= */ false);
547 return log_error_errno(r
, "Failed to check if host hierarchy '%s' is empty: %m", resolved_hierarchy
);
549 log_debug("Host hierarchy '%s' is empty, not merging.", resolved_hierarchy
);
550 resolved_hierarchy
= mfree(resolved_hierarchy
);
554 /* Let's generate a metadata file that lists all extensions we took into account for this
555 * hierarchy. We include this in the final fs, to make things nicely discoverable and
557 f
= path_join(meta_path
, image_class_info
[image_class
].dot_directory_name
, image_class_info
[image_class
].short_identifier_plural
);
561 buf
= strv_join(extensions
, "\n");
565 r
= write_string_file(f
, buf
, WRITE_STRING_FILE_CREATE
|WRITE_STRING_FILE_MKDIR_0755
);
567 return log_error_errno(r
, "Failed to write extension meta file '%s': %m", f
);
569 /* Put the meta path (i.e. our synthesized stuff) at the top of the layer stack */
570 layers
= strv_new(meta_path
);
574 /* Put the extensions in the middle */
575 STRV_FOREACH(p
, paths
) {
576 _cleanup_free_
char *resolved
= NULL
;
578 r
= chase(hierarchy
, *p
, CHASE_PREFIX_ROOT
, &resolved
, NULL
);
580 log_debug_errno(r
, "Hierarchy '%s' in extension '%s' doesn't exist, not merging.", hierarchy
, *p
);
584 return log_error_errno(r
, "Failed to resolve hierarchy '%s' in extension '%s': %m", hierarchy
, *p
);
586 r
= dir_is_empty(resolved
, /* ignore_hidden_or_backup= */ false);
588 return log_error_errno(r
, "Failed to check if hierarchy '%s' in extension '%s' is empty: %m", resolved
, *p
);
590 log_debug("Hierarchy '%s' in extension '%s' is empty, not merging.", hierarchy
, *p
);
594 r
= strv_consume(&layers
, TAKE_PTR(resolved
));
599 if (!layers
[1]) /* No extension with files in this hierarchy? Then don't do anything. */
602 if (resolved_hierarchy
) {
603 /* Add the host hierarchy as last (lowest) layer in the stack */
604 r
= strv_consume(&layers
, TAKE_PTR(resolved_hierarchy
));
609 r
= mkdir_p(overlay_path
, 0700);
611 return log_error_errno(r
, "Failed to make directory '%s': %m", overlay_path
);
613 r
= mount_overlayfs(image_class
, noexec
, overlay_path
, layers
);
617 /* The overlayfs superblock is read-only. Let's also mark the bind mount read-only. Extra turbo safety 😎 */
618 r
= bind_remount_recursive(overlay_path
, MS_RDONLY
, MS_RDONLY
, NULL
);
620 return log_error_errno(r
, "Failed to make bind mount '%s' read-only: %m", overlay_path
);
622 /* Now we have mounted the new file system. Let's now figure out its .st_dev field, and make that
623 * available in the metadata directory. This is useful to detect whether the metadata dir actually
624 * belongs to the fs it is found on: if .st_dev of the top-level mount matches it, it's pretty likely
625 * we are looking at a live tree, and not an unpacked tar or so of one. */
626 if (stat(overlay_path
, &st
) < 0)
627 return log_error_errno(r
, "Failed to stat mount '%s': %m", overlay_path
);
630 f
= path_join(meta_path
, image_class_info
[image_class
].dot_directory_name
, "dev");
634 r
= write_string_file(f
, FORMAT_DEVNUM(st
.st_dev
), WRITE_STRING_FILE_CREATE
);
636 return log_error_errno(r
, "Failed to write '%s': %m", f
);
638 /* Make sure the top-level dir has an mtime marking the point we established the merge */
639 if (utimensat(AT_FDCWD
, meta_path
, NULL
, AT_SYMLINK_NOFOLLOW
) < 0)
640 return log_error_errno(r
, "Failed fix mtime of '%s': %m", meta_path
);
645 static int strverscmp_improvedp(char *const* a
, char *const* b
) {
646 /* usable in qsort() for sorting a string array with strverscmp_improved() */
647 return strverscmp_improved(*a
, *b
);
650 static const ImagePolicy
*pick_image_policy(const Image
*img
) {
654 /* Explicitly specified policy always wins */
655 if (arg_image_policy
)
656 return arg_image_policy
;
658 /* If located in /.extra/sysext/ in the initrd, then it was placed there by systemd-stub, and was
659 * picked up from an untrusted ESP. Thus, require a stricter policy by default for them. (For the
660 * other directories we assume the appropriate level of trust was already established already. */
662 if (in_initrd() && path_startswith(img
->path
, "/.extra/sysext/"))
663 return &image_policy_sysext_strict
;
665 return image_class_info
[img
->class].default_image_policy
;
668 static int merge_subprocess(
669 ImageClass image_class
,
674 const char *workspace
) {
676 _cleanup_free_
char *host_os_release_id
= NULL
, *host_os_release_version_id
= NULL
, *host_os_release_api_level
= NULL
, *buf
= NULL
;
677 _cleanup_strv_free_
char **extensions
= NULL
, **paths
= NULL
;
678 size_t n_extensions
= 0;
679 unsigned n_ignored
= 0;
683 /* Mark the whole of /run as MS_SLAVE, so that we can mount stuff below it that doesn't show up on
684 * the host otherwise. */
685 r
= mount_nofollow_verbose(LOG_ERR
, NULL
, "/run", NULL
, MS_SLAVE
|MS_REC
, NULL
);
687 return log_error_errno(r
, "Failed to remount /run/ MS_SLAVE: %m");
689 /* Let's create the workspace if it's missing */
690 r
= mkdir_p(workspace
, 0700);
692 return log_error_errno(r
, "Failed to create '%s': %m", workspace
);
694 /* Let's mount a tmpfs to our workspace. This way we don't need to clean up the inodes we mount over,
695 * but let the kernel do that entirely automatically, once our namespace dies. Note that this file
696 * system won't be visible to anyone but us, since we opened our own namespace and then made the
697 * /run/ hierarchy (which our workspace is contained in) MS_SLAVE, see above. */
698 r
= mount_nofollow_verbose(LOG_ERR
, image_class_info
[image_class
].short_identifier
, workspace
, "tmpfs", 0, "mode=0700");
702 /* Acquire host OS release info, so that we can compare it with the extension's data */
703 r
= parse_os_release(
705 "ID", &host_os_release_id
,
706 "VERSION_ID", &host_os_release_version_id
,
707 image_class_info
[image_class
].level_env
, &host_os_release_api_level
);
709 return log_error_errno(r
, "Failed to acquire 'os-release' data of OS tree '%s': %m", empty_to_root(arg_root
));
710 if (isempty(host_os_release_id
))
711 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
712 "'ID' field not found or empty in 'os-release' data of OS tree '%s': %m",
713 empty_to_root(arg_root
));
715 /* Let's now mount all images */
716 HASHMAP_FOREACH(img
, images
) {
717 _cleanup_free_
char *p
= NULL
;
719 p
= path_join(workspace
, image_class_info
[image_class
].short_identifier_plural
, img
->name
);
723 r
= mkdir_p(p
, 0700);
725 return log_error_errno(r
, "Failed to create %s: %m", p
);
728 case IMAGE_DIRECTORY
:
729 case IMAGE_SUBVOLUME
:
732 r
= extension_has_forbidden_content(p
);
741 r
= mount_nofollow_verbose(LOG_ERR
, img
->path
, p
, NULL
, MS_BIND
, NULL
);
745 /* Make this a read-only bind mount */
746 r
= bind_remount_recursive(p
, MS_RDONLY
, MS_RDONLY
, NULL
);
748 return log_error_errno(r
, "Failed to make bind mount '%s' read-only: %m", p
);
754 _cleanup_(dissected_image_unrefp
) DissectedImage
*m
= NULL
;
755 _cleanup_(loop_device_unrefp
) LoopDevice
*d
= NULL
;
756 _cleanup_(verity_settings_done
) VeritySettings verity_settings
= VERITY_SETTINGS_DEFAULT
;
757 DissectImageFlags flags
=
758 DISSECT_IMAGE_READ_ONLY
|
759 DISSECT_IMAGE_GENERIC_ROOT
|
760 DISSECT_IMAGE_REQUIRE_ROOT
|
761 DISSECT_IMAGE_MOUNT_ROOT_ONLY
|
762 DISSECT_IMAGE_USR_NO_ROOT
|
763 DISSECT_IMAGE_ADD_PARTITION_DEVICES
|
764 DISSECT_IMAGE_PIN_PARTITION_DEVICES
;
766 r
= verity_settings_load(&verity_settings
, img
->path
, NULL
, NULL
);
768 return log_error_errno(r
, "Failed to read verity artifacts for %s: %m", img
->path
);
770 if (verity_settings
.data_path
)
771 flags
|= DISSECT_IMAGE_NO_PARTITION_TABLE
;
774 flags
|= DISSECT_IMAGE_VALIDATE_OS_EXT
;
776 r
= loop_device_make_by_path(
779 /* sector_size= */ UINT32_MAX
,
780 FLAGS_SET(flags
, DISSECT_IMAGE_NO_PARTITION_TABLE
) ? 0 : LO_FLAGS_PARTSCAN
,
784 return log_error_errno(r
, "Failed to set up loopback device for %s: %m", img
->path
);
786 r
= dissect_loop_device_and_warn(
789 /* mount_options= */ NULL
,
790 pick_image_policy(img
),
796 r
= dissected_image_load_verity_sig_partition(
803 r
= dissected_image_decrypt_interactively(
810 r
= dissected_image_mount_and_warn(
813 /* uid_shift= */ UID_INVALID
,
814 /* uid_range= */ UID_INVALID
,
815 /* userns_fd= */ -EBADF
,
817 if (r
< 0 && r
!= -ENOMEDIUM
)
819 if (r
== -ENOMEDIUM
&& !force
) {
824 r
= dissected_image_relinquish(m
);
826 return log_error_errno(r
, "Failed to relinquish DM and loopback block devices: %m");
830 assert_not_reached();
834 log_debug("Force mode enabled, skipping version validation.");
836 r
= extension_release_validate(
839 host_os_release_version_id
,
840 host_os_release_api_level
,
841 in_initrd() ? "initrd" : "system",
842 image_extension_release(img
, image_class
),
852 /* Nice! This one is an extension we want. */
853 r
= strv_extend(&extensions
, img
->name
);
860 /* Nothing left? Then shortcut things */
861 if (n_extensions
== 0) {
863 log_info("No suitable extensions found (%u ignored due to incompatible image(s)).", n_ignored
);
865 log_info("No extensions found.");
869 /* Order by version sort with strverscmp_improved() */
870 typesafe_qsort(extensions
, n_extensions
, strverscmp_improvedp
);
872 buf
= strv_join(extensions
, "', '");
876 log_info("Using extensions '%s'.", buf
);
878 /* Build table of extension paths (in reverse order) */
879 paths
= new0(char*, n_extensions
+ 1);
883 for (size_t k
= 0; k
< n_extensions
; k
++) {
884 _cleanup_free_
char *p
= NULL
;
886 assert_se(img
= hashmap_get(images
, extensions
[n_extensions
- 1 - k
]));
888 p
= path_join(workspace
, image_class_info
[image_class
].short_identifier_plural
, img
->name
);
892 paths
[k
] = TAKE_PTR(p
);
895 /* Let's now unmerge the status quo ante, since to build the new overlayfs we need a reference to the
897 STRV_FOREACH(h
, hierarchies
) {
898 _cleanup_free_
char *resolved
= NULL
;
900 r
= chase(*h
, arg_root
, CHASE_PREFIX_ROOT
|CHASE_NONEXISTENT
, &resolved
, NULL
);
902 return log_error_errno(r
, "Failed to resolve hierarchy '%s%s': %m", strempty(arg_root
), *h
);
904 r
= unmerge_hierarchy(image_class
, resolved
);
909 /* Create overlayfs mounts for all hierarchies */
910 STRV_FOREACH(h
, hierarchies
) {
911 _cleanup_free_
char *meta_path
= NULL
, *overlay_path
= NULL
;
913 meta_path
= path_join(workspace
, "meta", *h
); /* The place where to store metadata about this instance */
917 overlay_path
= path_join(workspace
, "overlay", *h
); /* The resulting overlayfs instance */
933 /* And move them all into place. This is where things appear in the host namespace */
934 STRV_FOREACH(h
, hierarchies
) {
935 _cleanup_free_
char *p
= NULL
, *resolved
= NULL
;
937 p
= path_join(workspace
, "overlay", *h
);
941 if (laccess(p
, F_OK
) < 0) {
943 return log_error_errno(errno
, "Failed to check if '%s' exists: %m", p
);
945 /* Hierarchy apparently was empty in all extensions, and wasn't mounted, ignoring. */
949 r
= chase(*h
, arg_root
, CHASE_PREFIX_ROOT
|CHASE_NONEXISTENT
, &resolved
, NULL
);
951 return log_error_errno(r
, "Failed to resolve hierarchy '%s%s': %m", strempty(arg_root
), *h
);
953 r
= mkdir_p(resolved
, 0755);
955 return log_error_errno(r
, "Failed to create hierarchy mount point '%s': %m", resolved
);
957 r
= mount_nofollow_verbose(LOG_ERR
, p
, resolved
, NULL
, MS_BIND
, NULL
);
961 log_info("Merged extensions into '%s'.", resolved
);
967 static int merge(ImageClass image_class
,
976 r
= safe_fork("(sd-merge)", FORK_DEATHSIG_SIGTERM
|FORK_LOG
|FORK_NEW_MOUNTNS
, &pid
);
978 return log_error_errno(r
, "Failed to fork off child: %m");
980 /* Child with its own mount namespace */
982 r
= merge_subprocess(image_class
, hierarchies
, force
, noexec
, images
, "/run/systemd/sysext");
986 /* Our namespace ceases to exist here, also implicitly detaching all temporary mounts we
987 * created below /run. Nice! */
989 _exit(r
> 0 ? EXIT_SUCCESS
: 123); /* 123 means: didn't find any extensions */
992 r
= wait_for_terminate_and_check("(sd-merge)", pid
, WAIT_LOG_ABNORMAL
);
996 if (r
== 123) /* exit code 123 means: didn't do anything */
999 r
= need_reload(image_class
, hierarchies
, no_reload
);
1003 r
= daemon_reload();
1011 static int image_discover_and_read_metadata(
1012 ImageClass image_class
,
1013 Hashmap
**ret_images
) {
1014 _cleanup_hashmap_free_ Hashmap
*images
= NULL
;
1020 images
= hashmap_new(&image_hash_ops
);
1024 r
= image_discover(image_class
, arg_root
, images
);
1026 return log_error_errno(r
, "Failed to discover images: %m");
1028 HASHMAP_FOREACH(img
, images
) {
1029 r
= image_read_metadata(img
, image_class_info
[image_class
].default_image_policy
);
1031 return log_error_errno(r
, "Failed to read metadata for image %s: %m", img
->name
);
1034 *ret_images
= TAKE_PTR(images
);
1039 static int look_for_merged_hierarchies(
1040 ImageClass image_class
,
1042 const char **ret_which
) {
1047 /* In merge mode fail if things are already merged. (In --refresh mode below we'll unmerge if we find
1048 * things are already merged...) */
1049 STRV_FOREACH(p
, hierarchies
) {
1050 _cleanup_free_
char *resolved
= NULL
;
1052 r
= chase(*p
, arg_root
, CHASE_PREFIX_ROOT
, &resolved
, NULL
);
1054 log_debug_errno(r
, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root
), *p
);
1058 return log_error_errno(r
, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root
), *p
);
1060 r
= is_our_mount_point(image_class
, resolved
);
1073 static int verb_merge(int argc
, char **argv
, void *userdata
) {
1074 _cleanup_hashmap_free_ Hashmap
*images
= NULL
;
1078 r
= have_effective_cap(CAP_SYS_ADMIN
);
1080 return log_error_errno(r
, "Failed to check if we have enough privileges: %m");
1082 return log_error_errno(SYNTHETIC_ERRNO(EPERM
), "Need to be privileged.");
1084 r
= image_discover_and_read_metadata(arg_image_class
, &images
);
1088 r
= look_for_merged_hierarchies(arg_image_class
, arg_hierarchies
, &which
);
1092 return log_error_errno(SYNTHETIC_ERRNO(EBUSY
), "Hierarchy '%s' is already merged.", which
);
1094 return merge(arg_image_class
,
1102 typedef struct MethodMergeParameters
{
1107 } MethodMergeParameters
;
1109 static int parse_merge_parameters(Varlink
*link
, JsonVariant
*parameters
, MethodMergeParameters
*p
) {
1111 static const JsonDispatch dispatch_table
[] = {
1112 { "class", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(MethodMergeParameters
, class), 0 },
1113 { "force", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(MethodMergeParameters
, force
), 0 },
1114 { "noReload", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(MethodMergeParameters
, no_reload
), 0 },
1115 { "noexec", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(MethodMergeParameters
, noexec
), 0 },
1123 return varlink_dispatch(link
, parameters
, dispatch_table
, p
);
1126 static int vl_method_merge(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
1127 _cleanup_hashmap_free_ Hashmap
*images
= NULL
;
1128 MethodMergeParameters p
= {
1133 _cleanup_strv_free_
char **hierarchies
= NULL
;
1134 ImageClass image_class
= arg_image_class
;
1139 r
= parse_merge_parameters(link
, parameters
, &p
);
1143 r
= parse_image_class_parameter(link
, p
.class, &image_class
, &hierarchies
);
1147 r
= image_discover_and_read_metadata(image_class
, &images
);
1152 r
= look_for_merged_hierarchies(
1154 hierarchies
?: arg_hierarchies
,
1159 return varlink_errorb(link
, "io.systemd.sysext.AlreadyMerged", JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("hierarchy", which
)));
1161 r
= merge(image_class
,
1162 hierarchies
?: arg_hierarchies
,
1163 p
.force
>= 0 ? p
.force
: arg_force
,
1164 p
.no_reload
>= 0 ? p
.no_reload
: arg_no_reload
,
1165 p
.noexec
>= 0 ? p
.noexec
: arg_noexec
,
1170 return varlink_reply(link
, NULL
);
1174 ImageClass image_class
,
1180 _cleanup_hashmap_free_ Hashmap
*images
= NULL
;
1183 r
= image_discover_and_read_metadata(image_class
, &images
);
1187 /* Returns > 0 if it did something, i.e. a new overlayfs is mounted now. When it does so it
1188 * implicitly unmounts any overlayfs placed there before. Returns == 0 if it did nothing, i.e. no
1189 * extension images found. In this case the old overlayfs remains in place if there was one. */
1190 r
= merge(image_class
, hierarchies
, force
, no_reload
, noexec
, images
);
1193 if (r
== 0) /* No images found? Then unmerge. The goal of --refresh is after all that after having
1194 * called there's a guarantee that the merge status matches the installed extensions. */
1195 r
= unmerge(image_class
, hierarchies
, no_reload
);
1197 /* Net result here is that:
1199 * 1. If an overlayfs was mounted before and no extensions exist anymore, we'll have unmerged things.
1201 * 2. If an overlayfs was mounted before, and there are still extensions installed' we'll have
1202 * unmerged and then merged things again.
1204 * 3. If an overlayfs so far wasn't mounted, and there are extensions installed, we'll have it
1207 * 4. If there was no overlayfs mount so far, and no extensions installed, we implement a NOP.
1213 static int verb_refresh(int argc
, char **argv
, void *userdata
) {
1216 r
= have_effective_cap(CAP_SYS_ADMIN
);
1218 return log_error_errno(r
, "Failed to check if we have enough privileges: %m");
1220 return log_error_errno(SYNTHETIC_ERRNO(EPERM
), "Need to be privileged.");
1222 return refresh(arg_image_class
,
1229 static int vl_method_refresh(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
1231 MethodMergeParameters p
= {
1236 _cleanup_strv_free_
char **hierarchies
= NULL
;
1237 ImageClass image_class
= arg_image_class
;
1242 r
= parse_merge_parameters(link
, parameters
, &p
);
1246 r
= parse_image_class_parameter(link
, p
.class, &image_class
, &hierarchies
);
1250 r
= refresh(image_class
,
1251 hierarchies
?: arg_hierarchies
,
1252 p
.force
>= 0 ? p
.force
: arg_force
,
1253 p
.no_reload
>= 0 ? p
.no_reload
: arg_no_reload
,
1254 p
.noexec
>= 0 ? p
.noexec
: arg_noexec
);
1258 return varlink_reply(link
, NULL
);
1261 static int verb_list(int argc
, char **argv
, void *userdata
) {
1262 _cleanup_hashmap_free_ Hashmap
*images
= NULL
;
1263 _cleanup_(table_unrefp
) Table
*t
= NULL
;
1267 images
= hashmap_new(&image_hash_ops
);
1271 r
= image_discover(arg_image_class
, arg_root
, images
);
1273 return log_error_errno(r
, "Failed to discover images: %m");
1275 if ((arg_json_format_flags
& JSON_FORMAT_OFF
) && hashmap_isempty(images
)) {
1276 log_info("No OS extensions found.");
1280 t
= table_new("name", "type", "path", "time");
1284 HASHMAP_FOREACH(img
, images
) {
1287 TABLE_STRING
, img
->name
,
1288 TABLE_STRING
, image_type_to_string(img
->type
),
1289 TABLE_PATH
, img
->path
,
1290 TABLE_TIMESTAMP
, img
->mtime
!= 0 ? img
->mtime
: img
->crtime
);
1292 return table_log_add_error(r
);
1295 (void) table_set_sort(t
, (size_t) 0);
1297 return table_print_with_pager(t
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
1300 typedef struct MethodListParameters
{
1302 } MethodListParameters
;
1304 static int vl_method_list(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
1306 static const JsonDispatch dispatch_table
[] = {
1307 { "class", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(MethodListParameters
, class), 0 },
1310 MethodListParameters p
= {
1312 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
1313 _cleanup_hashmap_free_ Hashmap
*images
= NULL
;
1314 ImageClass image_class
= arg_image_class
;
1320 r
= varlink_dispatch(link
, parameters
, dispatch_table
, &p
);
1324 r
= parse_image_class_parameter(link
, p
.class, &image_class
, NULL
);
1328 images
= hashmap_new(&image_hash_ops
);
1332 r
= image_discover(image_class
, arg_root
, images
);
1336 HASHMAP_FOREACH(img
, images
) {
1338 /* Send previous item with more=true */
1339 r
= varlink_notify(link
, v
);
1344 v
= json_variant_unref(v
);
1346 r
= image_to_json(img
, &v
);
1351 if (v
) /* Send final item with more=false */
1352 return varlink_reply(link
, v
);
1354 return varlink_error(link
, "io.systemd.sysext.NoImagesFound", NULL
);
1357 static int verb_help(int argc
, char **argv
, void *userdata
) {
1358 _cleanup_free_
char *link
= NULL
;
1361 r
= terminal_urlify_man("systemd-sysext", "8", &link
);
1365 printf("%1$s [OPTIONS...] COMMAND\n"
1366 "\n%5$sMerge extension images into /usr/ and /opt/ hierarchies for\n"
1367 " sysext and into the /etc/ hierarchy for confext.%6$s\n"
1368 " status Show current merge status (default)\n"
1369 " merge Merge extensions into relevant hierarchies\n"
1370 " unmerge Unmerge extensions from relevant hierarchies\n"
1371 " refresh Unmerge/merge extensions again\n"
1372 " list List installed extensions\n"
1373 " -h --help Show this help\n"
1374 " --version Show package version\n"
1375 "\n%3$sOptions:%4$s\n"
1376 " --no-pager Do not pipe output into a pager\n"
1377 " --no-legend Do not show the headers and footers\n"
1378 " --root=PATH Operate relative to root path\n"
1379 " --json=pretty|short|off\n"
1380 " Generate JSON output\n"
1381 " --force Ignore version incompatibilities\n"
1382 " --no-reload Do not reload the service manager\n"
1383 " --image-policy=POLICY\n"
1384 " Specify disk image dissection policy\n"
1385 " --noexec=BOOL Whether to mount extension overlay with noexec\n"
1386 "\nSee the %2$s for details.\n",
1387 program_invocation_short_name
,
1397 static int parse_argv(int argc
, char *argv
[]) {
1400 ARG_VERSION
= 0x100,
1411 static const struct option options
[] = {
1412 { "help", no_argument
, NULL
, 'h' },
1413 { "version", no_argument
, NULL
, ARG_VERSION
},
1414 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1415 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
1416 { "root", required_argument
, NULL
, ARG_ROOT
},
1417 { "json", required_argument
, NULL
, ARG_JSON
},
1418 { "force", no_argument
, NULL
, ARG_FORCE
},
1419 { "image-policy", required_argument
, NULL
, ARG_IMAGE_POLICY
},
1420 { "noexec", required_argument
, NULL
, ARG_NOEXEC
},
1421 { "no-reload", no_argument
, NULL
, ARG_NO_RELOAD
},
1430 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
1435 return verb_help(argc
, argv
, NULL
);
1441 arg_pager_flags
|= PAGER_DISABLE
;
1449 r
= parse_path_argument(optarg
, false, &arg_root
);
1452 /* If --root= is provided, do not reload the service manager */
1453 arg_no_reload
= true;
1457 r
= parse_json_argument(optarg
, &arg_json_format_flags
);
1467 case ARG_IMAGE_POLICY
:
1468 r
= parse_image_policy_argument(optarg
, &arg_image_policy
);
1474 r
= parse_boolean_argument("--noexec", optarg
, NULL
);
1482 arg_no_reload
= true;
1489 assert_not_reached();
1492 r
= varlink_invocation(VARLINK_ALLOW_ACCEPT
);
1494 return log_error_errno(r
, "Failed to check if invoked in Varlink mode: %m");
1501 static int sysext_main(int argc
, char *argv
[]) {
1503 static const Verb verbs
[] = {
1504 { "status", VERB_ANY
, 1, VERB_DEFAULT
, verb_status
},
1505 { "merge", VERB_ANY
, 1, 0, verb_merge
},
1506 { "unmerge", VERB_ANY
, 1, 0, verb_unmerge
},
1507 { "refresh", VERB_ANY
, 1, 0, verb_refresh
},
1508 { "list", VERB_ANY
, 1, 0, verb_list
},
1509 { "help", VERB_ANY
, 1, 0, verb_help
},
1513 return dispatch_verb(argc
, argv
, verbs
, NULL
);
1516 static int run(int argc
, char *argv
[]) {
1521 arg_image_class
= invoked_as(argv
, "systemd-confext") ? IMAGE_CONFEXT
: IMAGE_SYSEXT
;
1523 r
= parse_argv(argc
, argv
);
1527 /* For debugging purposes it might make sense to do this for other hierarchies than /usr/ and
1528 * /opt/, but let's make that a hacker/debugging feature, i.e. env var instead of cmdline
1530 r
= parse_env_extension_hierarchies(&arg_hierarchies
, image_class_info
[arg_image_class
].name_env
);
1532 return log_error_errno(r
, "Failed to parse environment variable: %m");
1535 _cleanup_(varlink_server_unrefp
) VarlinkServer
*varlink_server
= NULL
;
1537 /* Invocation as Varlink service */
1539 r
= varlink_server_new(&varlink_server
, VARLINK_SERVER_ROOT_ONLY
);
1541 return log_error_errno(r
, "Failed to allocate Varlink server: %m");
1543 r
= varlink_server_add_interface(varlink_server
, &vl_interface_io_systemd_sysext
);
1545 return log_error_errno(r
, "Failed to add Varlink interface: %m");
1547 r
= varlink_server_bind_method_many(
1549 "io.systemd.sysext.Merge", vl_method_merge
,
1550 "io.systemd.sysext.Unmerge", vl_method_unmerge
,
1551 "io.systemd.sysext.Refresh", vl_method_refresh
,
1552 "io.systemd.sysext.List", vl_method_list
);
1554 return log_error_errno(r
, "Failed to bind Varlink methods: %m");
1556 r
= varlink_server_loop_auto(varlink_server
);
1558 return log_error_errno(r
, "Invoked by unprivileged Varlink peer, refusing.");
1560 return log_error_errno(r
, "Failed to run Varlink event loop: %m");
1562 return EXIT_SUCCESS
;
1565 return sysext_main(argc
, argv
);
1568 DEFINE_MAIN_FUNCTION(run
);