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 "path-util.h"
43 #include "pretty-print.h"
44 #include "process-util.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"
52 #include "varlink-io.systemd.sysext.h"
55 typedef enum MutableMode
{
61 MUTABLE_EPHEMERAL_IMPORT
,
63 _MUTABLE_INVALID
= -EINVAL
,
66 static const char* const mutable_mode_table
[_MUTABLE_MAX
] = {
68 [MUTABLE_YES
] = "yes",
69 [MUTABLE_AUTO
] = "auto",
70 [MUTABLE_IMPORT
] = "import",
71 [MUTABLE_EPHEMERAL
] = "ephemeral",
72 [MUTABLE_EPHEMERAL_IMPORT
] = "ephemeral-import",
75 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(mutable_mode
, MutableMode
, MUTABLE_YES
);
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
;
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
;
92 #define MUTABLE_EXTENSIONS_BASE_DIR "/var/lib/extensions.mutable"
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
);
98 /* Helper struct for naming simplicity and reusability */
100 const char *full_identifier
;
101 const char *short_identifier
;
102 const char *short_identifier_plural
;
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
] = {
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
,
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
,
141 static int parse_mutable_mode(const char *p
) {
142 return mutable_mode_from_string(p
);
145 static int is_our_mount_point(
146 ImageClass image_class
,
149 _cleanup_free_
char *buf
= NULL
, *f
= NULL
;
156 r
= path_is_mount_point(p
);
158 log_debug_errno(r
, "Hierarchy '%s' doesn't exist.", p
);
162 return log_error_errno(r
, "Failed to determine whether '%s' is a mount point: %m", p
);
164 log_debug("Hierarchy '%s' is not a mount point, skipping.", p
);
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. */
176 f
= path_join(p
, image_class_info
[image_class
].dot_directory_name
, "dev");
180 r
= read_one_line_file(f
, &buf
);
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
);
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
);
188 r
= parse_devnum(buf
, &dev
);
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
);
192 if (lstat(p
, &st
) < 0)
193 return log_error_errno(errno
, "Failed to stat %s: %m", p
);
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
);
203 static int need_reload(
204 ImageClass image_class
,
208 /* Parse the mounted images to find out if we need to reload the daemon. */
214 STRV_FOREACH(p
, hierarchies
) {
215 _cleanup_free_
char *f
= NULL
, *buf
= NULL
, *resolved
= NULL
;
216 _cleanup_strv_free_
char **mounted_extensions
= NULL
;
218 r
= chase(*p
, arg_root
, CHASE_PREFIX_ROOT
, &resolved
, NULL
);
220 log_debug_errno(r
, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root
), *p
);
224 log_warning_errno(r
, "Failed to resolve path to hierarchy '%s%s': %m, ignoring.", strempty(arg_root
), *p
);
228 r
= is_our_mount_point(image_class
, resolved
);
234 f
= path_join(resolved
, image_class_info
[image_class
].dot_directory_name
, image_class_info
[image_class
].short_identifier_plural
);
238 r
= read_full_file(f
, &buf
, NULL
);
240 return log_error_errno(r
, "Failed to open '%s': %m", f
);
242 mounted_extensions
= strv_split_newlines(buf
);
243 if (!mounted_extensions
)
246 STRV_FOREACH(extension
, mounted_extensions
) {
247 _cleanup_strv_free_
char **extension_release
= NULL
;
248 const char *extension_reload_manager
= NULL
;
251 r
= load_extension_release_pairs(arg_root
, image_class
, *extension
, /* relax_extension_release_check */ true, &extension_release
);
253 log_debug_errno(r
, "Failed to parse extension-release metadata of %s, ignoring: %m", *extension
);
257 extension_reload_manager
= strv_env_pairs_get(extension_release
, "EXTENSION_RELOAD_MANAGER");
258 if (isempty(extension_reload_manager
))
261 b
= parse_boolean(extension_reload_manager
);
263 log_warning_errno(b
, "Failed to parse the extension metadata to know if the manager needs to be reloaded, ignoring: %m");
268 /* If at least one extension wants a reload, we reload. */
276 static int daemon_reload(void) {
277 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
280 r
= bus_connect_system_systemd(&bus
);
282 return log_error_errno(r
, "Failed to get D-Bus connection: %m");
284 return bus_service_manager_reload(bus
);
287 static int unmerge_hierarchy(
288 ImageClass image_class
,
291 _cleanup_free_
char *dot_dir
= NULL
, *work_dir_info_file
= NULL
;
296 dot_dir
= path_join(p
, image_class_info
[image_class
].dot_directory_name
);
300 work_dir_info_file
= path_join(dot_dir
, "work_dir");
301 if (!work_dir_info_file
)
305 _cleanup_free_
char *escaped_work_dir_in_root
= NULL
, *work_dir
= NULL
;
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. */
310 r
= is_our_mount_point(image_class
, p
);
316 r
= read_one_line_file(work_dir_info_file
, &escaped_work_dir_in_root
);
319 return log_error_errno(r
, "Failed to read '%s': %m", work_dir_info_file
);
321 _cleanup_free_
char *work_dir_in_root
= NULL
;
324 l
= cunescape_length(escaped_work_dir_in_root
, r
, 0, &work_dir_in_root
);
326 return log_error_errno(l
, "Failed to unescape work directory path: %m");
327 work_dir
= path_join(arg_root
, work_dir_in_root
);
332 r
= umount_verbose(LOG_DEBUG
, dot_dir
, MNT_DETACH
|UMOUNT_NOFOLLOW
);
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. */
338 return log_error_errno(r
, "Failed to unmount '%s': %m", dot_dir
);
341 r
= umount_verbose(LOG_ERR
, p
, MNT_DETACH
|UMOUNT_NOFOLLOW
);
346 r
= rm_rf(work_dir
, REMOVE_ROOT
| REMOVE_MISSING_OK
| REMOVE_PHYSICAL
);
348 return log_error_errno(r
, "Failed to remove '%s': %m", work_dir
);
351 log_info("Unmerged '%s'.", p
);
358 ImageClass image_class
,
365 r
= need_reload(image_class
, hierarchies
, no_reload
);
368 need_to_reload
= r
> 0;
370 STRV_FOREACH(p
, hierarchies
) {
371 _cleanup_free_
char *resolved
= NULL
;
373 r
= chase(*p
, arg_root
, CHASE_PREFIX_ROOT
, &resolved
, NULL
);
375 log_debug_errno(r
, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root
), *p
);
379 log_error_errno(r
, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root
), *p
);
386 r
= unmerge_hierarchy(image_class
, resolved
);
387 if (r
< 0 && ret
== 0)
391 if (need_to_reload
) {
400 static int verb_unmerge(int argc
, char **argv
, void *userdata
) {
403 r
= have_effective_cap(CAP_SYS_ADMIN
);
405 return log_error_errno(r
, "Failed to check if we have enough privileges: %m");
407 return log_error_errno(SYNTHETIC_ERRNO(EPERM
), "Need to be privileged.");
409 return unmerge(arg_image_class
,
414 static int parse_image_class_parameter(Varlink
*link
, const char *value
, ImageClass
*image_class
, char ***hierarchies
) {
415 _cleanup_strv_free_
char **h
= NULL
;
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");
430 r
= parse_env_extension_hierarchies(&h
, image_class_info
[c
].name_env
);
432 return log_error_errno(r
, "Failed to parse environment variable: %m");
434 strv_free_and_replace(*hierarchies
, h
);
441 typedef struct MethodUnmergeParameters
{
444 } MethodUnmergeParameters
;
446 static int vl_method_unmerge(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
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 },
453 MethodUnmergeParameters p
= {
456 _cleanup_strv_free_
char **hierarchies
= NULL
;
457 ImageClass image_class
= arg_image_class
;
462 r
= varlink_dispatch(link
, parameters
, dispatch_table
, &p
);
466 r
= parse_image_class_parameter(link
, p
.class, &image_class
, &hierarchies
);
470 r
= unmerge(image_class
,
471 hierarchies
?: arg_hierarchies
,
472 p
.no_reload
>= 0 ? p
.no_reload
: arg_no_reload
);
476 return varlink_reply(link
, NULL
);
479 static int verb_status(int argc
, char **argv
, void *userdata
) {
480 _cleanup_(table_unrefp
) Table
*t
= NULL
;
483 t
= table_new("hierarchy", "extensions", "since");
487 table_set_ersatz_string(t
, TABLE_ERSATZ_DASH
);
489 STRV_FOREACH(p
, arg_hierarchies
) {
490 _cleanup_free_
char *resolved
= NULL
, *f
= NULL
, *buf
= NULL
;
491 _cleanup_strv_free_
char **l
= NULL
;
494 r
= chase(*p
, arg_root
, CHASE_PREFIX_ROOT
, &resolved
, NULL
);
496 log_debug_errno(r
, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root
), *p
);
500 log_error_errno(r
, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root
), *p
);
504 r
= is_our_mount_point(arg_image_class
, resolved
);
511 TABLE_STRING
, "none",
512 TABLE_SET_COLOR
, ansi_grey(),
515 return table_log_add_error(r
);
520 f
= path_join(resolved
, image_class_info
[arg_image_class
].dot_directory_name
, image_class_info
[arg_image_class
].short_identifier_plural
);
524 r
= read_full_file(f
, &buf
, NULL
);
526 return log_error_errno(r
, "Failed to open '%s': %m", f
);
528 l
= strv_split_newlines(buf
);
532 if (stat(*p
, &st
) < 0)
533 return log_error_errno(errno
, "Failed to stat() '%s': %m", *p
);
539 TABLE_TIMESTAMP
, timespec_load(&st
.st_mtim
));
541 return table_log_add_error(r
);
550 (void) table_set_sort(t
, (size_t) 0);
552 r
= table_print_with_pager(t
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
559 static int append_overlayfs_path_option(
561 const char *separator
,
565 _cleanup_free_
char *escaped
= NULL
;
571 escaped
= shell_escape(path
, ",:");
576 if (!strextend(options
, separator
, option
, "=", escaped
))
578 } else if (!strextend(options
, separator
, escaped
))
584 static int mount_overlayfs(
585 ImageClass image_class
,
589 const char *upper_dir
,
590 const char *work_dir
) {
592 _cleanup_free_
char *options
= NULL
;
593 bool separator
= false;
598 assert((upper_dir
&& work_dir
) || (!upper_dir
&& !work_dir
));
600 options
= strdup("lowerdir=");
604 STRV_FOREACH(l
, layers
) {
605 r
= append_overlayfs_path_option(&options
, separator
? ":" : "", NULL
, *l
);
612 flags
= image_class_info
[image_class
].default_mount_flags
;
614 SET_FLAG(flags
, MS_NOEXEC
, noexec
);
616 if (upper_dir
&& work_dir
) {
617 r
= append_overlayfs_path_option(&options
, ",", "upperdir", upper_dir
);
623 r
= append_overlayfs_path_option(&options
, ",", "workdir", work_dir
);
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"))
632 /* Now mount the actual overlayfs */
633 r
= mount_nofollow_verbose(LOG_ERR
, image_class_info
[image_class
].short_identifier
, where
, "overlay", flags
, options
);
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
;
652 stripped
+= strspn(stripped
, "/");
654 dir_name
= strdup(stripped
);
657 delete_trailing_chars(dir_name
, "/");
658 string_replace_char(dir_name
, '/', '.');
659 return TAKE_PTR(dir_name
);
662 static int paths_on_same_fs(const char *path1
, const char *path2
) {
663 struct stat st1
, st2
;
668 if (stat(path1
, &st1
) < 0)
669 return log_error_errno(errno
, "Failed to stat '%s': %m", path1
);
671 if (stat(path2
, &st2
) < 0)
672 return log_error_errno(errno
, "Failed to stat '%s': %m", path2
);
674 return st1
.st_dev
== st2
.st_dev
;
677 static int work_dir_for_hierarchy(
678 const char *hierarchy
,
679 const char *resolved_upper_dir
,
680 char **ret_work_dir
) {
682 _cleanup_free_
char *parent
= NULL
;
686 assert(resolved_upper_dir
);
687 assert(ret_work_dir
);
689 r
= path_extract_directory(resolved_upper_dir
, &parent
);
691 return log_error_errno(r
, "Failed to get parent directory of upperdir '%s': %m", resolved_upper_dir
);
693 /* TODO: paths_in_same_superblock? partition? device? */
694 r
= paths_on_same_fs(resolved_upper_dir
, parent
);
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
);
700 _cleanup_free_
char *f
= NULL
, *dir_name
= NULL
;
702 f
= hierarchy_as_single_path_component(hierarchy
);
705 dir_name
= strjoin(".systemd-", f
, "-workdir");
710 f
= path_join(parent
, dir_name
);
714 *ret_work_dir
= TAKE_PTR(f
);
718 typedef struct OverlayFSPaths
{
720 mode_t hierarchy_mode
;
721 char *resolved_hierarchy
;
722 char *resolved_mutable_directory
;
724 /* NULL if merged fs is read-only */
726 /* NULL if merged fs is read-only */
728 /* lowest index is top lowerdir, highest index is bottom lowerdir */
732 static OverlayFSPaths
*overlayfs_paths_free(OverlayFSPaths
*op
) {
737 free(op
->resolved_hierarchy
);
738 free(op
->resolved_mutable_directory
);
742 strv_free(op
->lower_dirs
);
746 DEFINE_TRIVIAL_CLEANUP_FUNC(OverlayFSPaths
*, overlayfs_paths_free
);
748 static int resolve_hierarchy(const char *hierarchy
, char **ret_resolved_hierarchy
) {
749 _cleanup_free_
char *resolved_path
= NULL
;
753 assert(ret_resolved_hierarchy
);
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
);
759 *ret_resolved_hierarchy
= TAKE_PTR(resolved_path
);
763 static int mutable_directory_mode_matches_hierarchy(
764 const char *root_or_null
,
766 mode_t hierarchy_mode
) {
768 _cleanup_free_
char *path_in_root
= NULL
;
774 path_in_root
= path_join(root_or_null
, path
);
778 if (stat(path_in_root
, &st
) < 0) {
781 return log_error_errno(errno
, "Failed to stat mutable directory '%s': %m", path_in_root
);
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
);
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
) {
797 _cleanup_free_
char *path
= NULL
, *resolved_path
= NULL
, *dir_name
= NULL
;
798 const char *root
= arg_root
, *base
= MUTABLE_EXTENSIONS_BASE_DIR
;
802 assert(ret_resolved_mutable_directory
);
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
;
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. */
817 dir_name
= hierarchy_as_single_path_component(hierarchy
);
821 path
= path_join(base
, dir_name
);
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
829 r
= mutable_directory_mode_matches_hierarchy(root
, path
, hierarchy_mode
);
834 if (IN_SET(arg_mutable
, MUTABLE_YES
, MUTABLE_EPHEMERAL
, MUTABLE_EPHEMERAL_IMPORT
)) {
835 _cleanup_free_
char *path_in_root
= NULL
;
837 path_in_root
= path_join(root
, path
);
841 r
= mkdir_p(path_in_root
, 0700);
843 return log_error_errno(r
, "Failed to create a directory '%s': %m", path_in_root
);
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
);
850 *ret_resolved_mutable_directory
= TAKE_PTR(resolved_path
);
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
;
863 hierarchy_copy
= strdup(hierarchy
);
867 r
= resolve_hierarchy(hierarchy
, &resolved_hierarchy
);
871 if (resolved_hierarchy
) {
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;
878 hierarchy_mode
= 0755;
880 r
= resolve_mutable_directory(hierarchy
, hierarchy_mode
, workspace_path
, &resolved_mutable_directory
);
885 op
= new(OverlayFSPaths
, 1);
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
),
896 *ret_op
= TAKE_PTR(op
);
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
;
907 assert(ret_used_paths
);
908 assert(ret_extensions_used
);
910 STRV_FOREACH(p
, paths
) {
911 _cleanup_free_
char *resolved
= NULL
;
913 r
= chase(hierarchy
, *p
, CHASE_PREFIX_ROOT
, &resolved
, NULL
);
915 log_debug_errno(r
, "Hierarchy '%s' in extension '%s' doesn't exist, not merging.", hierarchy
, *p
);
919 return log_error_errno(r
, "Failed to resolve hierarchy '%s' in extension '%s': %m", hierarchy
, *p
);
921 r
= dir_is_empty(resolved
, /* ignore_hidden_or_backup= */ false);
923 return log_error_errno(r
, "Failed to check if hierarchy '%s' in extension '%s' is empty: %m", resolved
, *p
);
925 log_debug("Hierarchy '%s' in extension '%s' is empty, not merging.", hierarchy
, *p
);
929 r
= strv_consume_with_size (&used_paths
, &n
, TAKE_PTR(resolved
));
934 *ret_used_paths
= TAKE_PTR(used_paths
);
935 *ret_extensions_used
= n
;
939 static int maybe_import_mutable_directory(OverlayFSPaths
*op
) {
944 /* If importing mutable layer and it actually exists and is not a hierarchy itself, add it just below
947 if (arg_mutable
!= MUTABLE_IMPORT
|| !op
->resolved_mutable_directory
)
950 r
= path_equal_or_inode_same_full(op
->resolved_hierarchy
, op
->resolved_mutable_directory
, 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
);
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
);
956 r
= strv_extend(&op
->lower_dirs
, op
->resolved_mutable_directory
);
963 static int maybe_import_ignored_mutable_directory(OverlayFSPaths
*op
) {
964 _cleanup_free_
char *dir_name
= NULL
, *path
= NULL
, *resolved_path
= NULL
;
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
)
974 dir_name
= hierarchy_as_single_path_component(op
->hierarchy
);
978 path
= path_join(MUTABLE_EXTENSIONS_BASE_DIR
, dir_name
);
982 r
= chase(path
, arg_root
, CHASE_PREFIX_ROOT
, &resolved_path
, NULL
);
984 log_debug("Mutable directory for %s does not exist, not importing", op
->hierarchy
);
988 return log_error_errno(r
, "Failed to resolve mutable directory '%s': %m", path
);
990 r
= path_equal_or_inode_same_full(op
->resolved_hierarchy
, resolved_path
, 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
);
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
);
997 r
= strv_consume(&op
->lower_dirs
, TAKE_PTR(resolved_path
));
1004 static int determine_top_lower_dirs(OverlayFSPaths
*op
, const char *meta_path
) {
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
);
1015 r
= maybe_import_mutable_directory(op
);
1019 r
= maybe_import_ignored_mutable_directory(op
);
1026 static int determine_middle_lower_dirs(OverlayFSPaths
*op
, char **paths
) {
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);
1040 static int hierarchy_as_lower_dir(OverlayFSPaths
*op
) {
1043 /* return 0 if hierarchy should be used as lower dir, >0, if not */
1047 if (!op
->resolved_hierarchy
) {
1048 log_debug("Host hierarchy '%s' does not exist, will not be used as lowerdir", op
->hierarchy
);
1052 r
= dir_is_empty(op
->resolved_hierarchy
, /* ignore_hidden_or_backup= */ false);
1054 return log_error_errno(r
, "Failed to check if host hierarchy '%s' is empty: %m", op
->resolved_hierarchy
);
1056 log_debug("Host hierarchy '%s' is empty, will not be used as lower dir.", op
->resolved_hierarchy
);
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
);
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
);
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
);
1075 r
= path_equal_or_inode_same_full(op
->resolved_hierarchy
, op
->resolved_mutable_directory
, 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
);
1079 log_debug("Host hierarchy '%s' will serve as upperdir.", op
->resolved_hierarchy
);
1086 static int determine_bottom_lower_dirs(OverlayFSPaths
*op
) {
1091 r
= hierarchy_as_lower_dir(op
);
1095 r
= strv_extend(&op
->lower_dirs
, op
->resolved_hierarchy
);
1103 static int determine_lower_dirs(
1106 const char *meta_path
) {
1114 r
= determine_top_lower_dirs(op
, meta_path
);
1118 r
= determine_middle_lower_dirs(op
, paths
);
1122 r
= determine_bottom_lower_dirs(op
);
1129 static int determine_upper_dir(OverlayFSPaths
*op
) {
1133 assert(!op
->upper_dir
);
1135 if (arg_mutable
== MUTABLE_IMPORT
) {
1136 log_debug("Mutability is disabled, there will be no upperdir for host hierarchy '%s'", op
->hierarchy
);
1140 if (!op
->resolved_mutable_directory
) {
1141 log_debug("No mutable directory found for host hierarchy '%s', there will be no upperdir", op
->hierarchy
);
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
);
1149 return log_error_errno(r
, "Failed to determine if mutable directory '%s' is on read-only filesystem: %m", op
->resolved_mutable_directory
);
1151 return log_error_errno(SYNTHETIC_ERRNO(EROFS
), "Can't use '%s' as an upperdir as it is read-only.", op
->resolved_mutable_directory
);
1153 op
->upper_dir
= strdup(op
->resolved_mutable_directory
);
1160 static int determine_work_dir(OverlayFSPaths
*op
) {
1161 _cleanup_free_
char *work_dir
= NULL
;
1165 assert(!op
->work_dir
);
1170 if (arg_mutable
== MUTABLE_IMPORT
)
1173 r
= work_dir_for_hierarchy(op
->hierarchy
, op
->upper_dir
, &work_dir
);
1177 op
->work_dir
= TAKE_PTR(work_dir
);
1181 static int mount_overlayfs_with_op(
1183 ImageClass image_class
,
1185 const char *overlay_path
,
1186 const char *meta_path
) {
1189 const char *top_layer
= NULL
;
1192 assert(overlay_path
);
1194 r
= mkdir_p(overlay_path
, 0700);
1196 return log_error_errno(r
, "Failed to make directory '%s': %m", overlay_path
);
1198 r
= mkdir_p(meta_path
, 0700);
1200 return log_error_errno(r
, "Failed to make directory '%s': %m", meta_path
);
1202 if (op
->upper_dir
&& op
->work_dir
) {
1203 r
= mkdir_p(op
->work_dir
, 0700);
1205 return log_error_errno(r
, "Failed to make directory '%s': %m", op
->work_dir
);
1206 top_layer
= op
->upper_dir
;
1208 assert(!strv_isempty(op
->lower_dirs
));
1209 top_layer
= op
->lower_dirs
[0];
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
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
);
1219 r
= mount_overlayfs(image_class
, noexec
, overlay_path
, op
->lower_dirs
, op
->upper_dir
, op
->work_dir
);
1226 static int write_extensions_file(ImageClass image_class
, char **extensions
, const char *meta_path
) {
1227 _cleanup_free_
char *f
= NULL
, *buf
= NULL
;
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
1236 f
= path_join(meta_path
, image_class_info
[image_class
].dot_directory_name
, image_class_info
[image_class
].short_identifier_plural
);
1240 buf
= strv_join(extensions
, "\n");
1244 r
= write_string_file(f
, buf
, WRITE_STRING_FILE_CREATE
|WRITE_STRING_FILE_MKDIR_0755
);
1246 return log_error_errno(r
, "Failed to write extension meta file '%s': %m", f
);
1251 static int write_dev_file(ImageClass image_class
, const char *meta_path
, const char *overlay_path
) {
1252 _cleanup_free_
char *f
= NULL
;
1257 assert(overlay_path
);
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
);
1266 f
= path_join(meta_path
, image_class_info
[image_class
].dot_directory_name
, "dev");
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
);
1275 return log_error_errno(r
, "Failed to write '%s': %m", f
);
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
;
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
))
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
));
1298 f
= path_join(meta_path
, image_class_info
[image_class
].dot_directory_name
, "work_dir");
1302 /* Paths can have newlines for whatever reason, so better escape them to really get a single
1304 escaped_work_dir_in_root
= cescape(work_dir_in_root
);
1305 if (!escaped_work_dir_in_root
)
1307 r
= write_string_file(f
, escaped_work_dir_in_root
, WRITE_STRING_FILE_CREATE
);
1309 return log_error_errno(r
, "Failed to write '%s': %m", f
);
1314 static int store_info_in_meta(
1315 ImageClass image_class
,
1317 const char *meta_path
,
1318 const char *overlay_path
,
1319 const char *work_dir
) {
1325 assert(overlay_path
);
1326 /* work_dir may be NULL */
1328 r
= write_extensions_file(image_class
, extensions
, meta_path
);
1332 r
= write_dev_file(image_class
, meta_path
, overlay_path
);
1336 r
= write_work_dir_file(image_class
, meta_path
, work_dir
);
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
);
1347 static int make_mounts_read_only(ImageClass image_class
, const char *overlay_path
, bool mutable) {
1350 assert(overlay_path
);
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
;
1358 f
= path_join(overlay_path
, image_class_info
[image_class
].dot_directory_name
);
1362 r
= mount_nofollow_verbose(LOG_ERR
, f
, f
, NULL
, MS_BIND
, NULL
);
1366 r
= bind_remount_one(f
, MS_RDONLY
, MS_RDONLY
);
1368 return log_error_errno(r
, "Failed to remount '%s' as read-only: %m", f
);
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
);
1374 return log_error_errno(r
, "Failed to make bind mount '%s' read-only: %m", overlay_path
);
1380 static int merge_hierarchy(
1381 ImageClass image_class
,
1382 const char *hierarchy
,
1386 const char *meta_path
,
1387 const char *overlay_path
,
1388 const char *workspace_path
) {
1390 _cleanup_(overlayfs_paths_freep
) OverlayFSPaths
*op
= NULL
;
1391 _cleanup_strv_free_
char **used_paths
= NULL
;
1392 size_t extensions_used
= 0;
1399 assert(overlay_path
);
1400 assert(workspace_path
);
1402 r
= determine_used_extensions(hierarchy
, paths
, &used_paths
, &extensions_used
);
1406 if (extensions_used
== 0) /* No extension with files in this hierarchy? Then don't do anything. */
1409 r
= overlayfs_paths_new(hierarchy
, workspace_path
, &op
);
1413 r
= determine_lower_dirs(op
, used_paths
, meta_path
);
1417 r
= determine_upper_dir(op
);
1421 r
= determine_work_dir(op
);
1425 r
= mount_overlayfs_with_op(op
, image_class
, noexec
, overlay_path
, meta_path
);
1429 r
= store_info_in_meta(image_class
, extensions
, meta_path
, overlay_path
, op
->work_dir
);
1433 r
= make_mounts_read_only(image_class
, overlay_path
, op
->upper_dir
&& op
->work_dir
);
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
);
1445 static const ImagePolicy
*pick_image_policy(const Image
*img
) {
1449 /* Explicitly specified policy always wins */
1450 if (arg_image_policy
)
1451 return arg_image_policy
;
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. */
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
;
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
;
1468 return image_class_info
[img
->class].default_image_policy
;
1471 static int merge_subprocess(
1472 ImageClass image_class
,
1477 const char *workspace
) {
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;
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
);
1490 return log_error_errno(r
, "Failed to remount /run/ MS_SLAVE: %m");
1492 /* Let's create the workspace if it's missing */
1493 r
= mkdir_p(workspace
, 0700);
1495 return log_error_errno(r
, "Failed to create '%s': %m", workspace
);
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");
1505 /* Acquire host OS release info, so that we can compare it with the extension's data */
1506 r
= parse_os_release(
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
);
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
));
1518 /* Let's now mount all images */
1519 HASHMAP_FOREACH(img
, images
) {
1520 _cleanup_free_
char *p
= NULL
;
1522 p
= path_join(workspace
, image_class_info
[image_class
].short_identifier_plural
, img
->name
);
1526 r
= mkdir_p(p
, 0700);
1528 return log_error_errno(r
, "Failed to create %s: %m", p
);
1530 switch (img
->type
) {
1531 case IMAGE_DIRECTORY
:
1532 case IMAGE_SUBVOLUME
:
1535 r
= extension_has_forbidden_content(p
);
1544 r
= mount_nofollow_verbose(LOG_ERR
, img
->path
, p
, NULL
, MS_BIND
, NULL
);
1548 /* Make this a read-only bind mount */
1549 r
= bind_remount_recursive(p
, MS_RDONLY
, MS_RDONLY
, NULL
);
1551 return log_error_errno(r
, "Failed to make bind mount '%s' read-only: %m", p
);
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
;
1570 r
= verity_settings_load(&verity_settings
, img
->path
, NULL
, NULL
);
1572 return log_error_errno(r
, "Failed to read verity artifacts for %s: %m", img
->path
);
1574 if (verity_settings
.data_path
)
1575 flags
|= DISSECT_IMAGE_NO_PARTITION_TABLE
;
1578 flags
|= DISSECT_IMAGE_VALIDATE_OS_EXT
;
1580 r
= loop_device_make_by_path(
1583 /* sector_size= */ UINT32_MAX
,
1584 FLAGS_SET(flags
, DISSECT_IMAGE_NO_PARTITION_TABLE
) ? 0 : LO_FLAGS_PARTSCAN
,
1588 return log_error_errno(r
, "Failed to set up loopback device for %s: %m", img
->path
);
1590 r
= dissect_loop_device_and_warn(
1593 /* mount_options= */ NULL
,
1594 pick_image_policy(img
),
1600 r
= dissected_image_load_verity_sig_partition(
1607 r
= dissected_image_decrypt_interactively(
1614 r
= dissected_image_mount_and_warn(
1617 /* uid_shift= */ UID_INVALID
,
1618 /* uid_range= */ UID_INVALID
,
1619 /* userns_fd= */ -EBADF
,
1621 if (r
< 0 && r
!= -ENOMEDIUM
)
1623 if (r
== -ENOMEDIUM
&& !force
) {
1628 r
= dissected_image_relinquish(m
);
1630 return log_error_errno(r
, "Failed to relinquish DM and loopback block devices: %m");
1634 assert_not_reached();
1638 log_debug("Force mode enabled, skipping version validation.");
1640 r
= extension_release_validate(
1643 host_os_release_version_id
,
1644 host_os_release_api_level
,
1645 in_initrd() ? "initrd" : "system",
1646 image_extension_release(img
, image_class
),
1656 /* Nice! This one is an extension we want. */
1657 r
= strv_extend(&extensions
, img
->name
);
1664 /* Nothing left? Then shortcut things */
1665 if (n_extensions
== 0) {
1667 log_info("No suitable extensions found (%u ignored due to incompatible image(s)).", n_ignored
);
1669 log_info("No extensions found.");
1673 /* Order by version sort with strverscmp_improved() */
1674 typesafe_qsort(extensions
, n_extensions
, strverscmp_improvedp
);
1676 buf
= strv_join(extensions
, "', '");
1680 log_info("Using extensions '%s'.", buf
);
1682 /* Build table of extension paths (in reverse order) */
1683 paths
= new0(char*, n_extensions
+ 1);
1687 for (size_t k
= 0; k
< n_extensions
; k
++) {
1688 _cleanup_free_
char *p
= NULL
;
1690 assert_se(img
= hashmap_get(images
, extensions
[n_extensions
- 1 - k
]));
1692 p
= path_join(workspace
, image_class_info
[image_class
].short_identifier_plural
, img
->name
);
1696 paths
[k
] = TAKE_PTR(p
);
1699 /* Let's now unmerge the status quo ante, since to build the new overlayfs we need a reference to the
1701 STRV_FOREACH(h
, hierarchies
) {
1702 _cleanup_free_
char *resolved
= NULL
;
1704 r
= chase(*h
, arg_root
, CHASE_PREFIX_ROOT
|CHASE_NONEXISTENT
, &resolved
, NULL
);
1706 return log_error_errno(r
, "Failed to resolve hierarchy '%s%s': %m", strempty(arg_root
), *h
);
1708 r
= unmerge_hierarchy(image_class
, resolved
);
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
;
1717 meta_path
= path_join(workspace
, "meta", *h
); /* The place where to store metadata about this instance */
1721 overlay_path
= path_join(workspace
, "overlay", *h
); /* The resulting overlayfs instance */
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
)
1730 r
= merge_hierarchy(
1738 merge_hierarchy_workspace
);
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
;
1747 p
= path_join(workspace
, "overlay", *h
);
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
);
1755 /* Hierarchy apparently was empty in all extensions, and wasn't mounted, ignoring. */
1759 r
= chase(*h
, arg_root
, CHASE_PREFIX_ROOT
|CHASE_NONEXISTENT
, &resolved
, NULL
);
1761 return log_error_errno(r
, "Failed to resolve hierarchy '%s%s': %m", strempty(arg_root
), *h
);
1763 r
= mkdir_p(resolved
, 0755);
1765 return log_error_errno(r
, "Failed to create hierarchy mount point '%s': %m", resolved
);
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
);
1772 log_info("Merged extensions into '%s'.", resolved
);
1778 static int merge(ImageClass image_class
,
1787 r
= safe_fork("(sd-merge)", FORK_DEATHSIG_SIGTERM
|FORK_LOG
|FORK_NEW_MOUNTNS
, &pid
);
1789 return log_error_errno(r
, "Failed to fork off child: %m");
1791 /* Child with its own mount namespace */
1793 r
= merge_subprocess(image_class
, hierarchies
, force
, noexec
, images
, "/run/systemd/sysext");
1795 _exit(EXIT_FAILURE
);
1797 /* Our namespace ceases to exist here, also implicitly detaching all temporary mounts we
1798 * created below /run. Nice! */
1800 _exit(r
> 0 ? EXIT_SUCCESS
: 123); /* 123 means: didn't find any extensions */
1803 r
= wait_for_terminate_and_check("(sd-merge)", pid
, WAIT_LOG_ABNORMAL
);
1806 if (r
== 123) /* exit code 123 means: didn't do anything */
1809 return log_error_errno(SYNTHETIC_ERRNO(EPROTO
), "Failed to merge hierarchies");
1811 r
= need_reload(image_class
, hierarchies
, no_reload
);
1815 r
= daemon_reload();
1823 static int image_discover_and_read_metadata(
1824 ImageClass image_class
,
1825 Hashmap
**ret_images
) {
1826 _cleanup_hashmap_free_ Hashmap
*images
= NULL
;
1832 images
= hashmap_new(&image_hash_ops
);
1836 r
= image_discover(image_class
, arg_root
, images
);
1838 return log_error_errno(r
, "Failed to discover images: %m");
1840 HASHMAP_FOREACH(img
, images
) {
1841 r
= image_read_metadata(img
, image_class_info
[image_class
].default_image_policy
);
1843 return log_error_errno(r
, "Failed to read metadata for image %s: %m", img
->name
);
1846 *ret_images
= TAKE_PTR(images
);
1851 static int look_for_merged_hierarchies(
1852 ImageClass image_class
,
1854 const char **ret_which
) {
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
;
1864 r
= chase(*p
, arg_root
, CHASE_PREFIX_ROOT
, &resolved
, NULL
);
1866 log_debug_errno(r
, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root
), *p
);
1870 return log_error_errno(r
, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root
), *p
);
1872 r
= is_our_mount_point(image_class
, resolved
);
1885 static int verb_merge(int argc
, char **argv
, void *userdata
) {
1886 _cleanup_hashmap_free_ Hashmap
*images
= NULL
;
1890 r
= have_effective_cap(CAP_SYS_ADMIN
);
1892 return log_error_errno(r
, "Failed to check if we have enough privileges: %m");
1894 return log_error_errno(SYNTHETIC_ERRNO(EPERM
), "Need to be privileged.");
1896 r
= image_discover_and_read_metadata(arg_image_class
, &images
);
1900 r
= look_for_merged_hierarchies(arg_image_class
, arg_hierarchies
, &which
);
1904 return log_error_errno(SYNTHETIC_ERRNO(EBUSY
), "Hierarchy '%s' is already merged.", which
);
1906 return merge(arg_image_class
,
1914 typedef struct MethodMergeParameters
{
1919 } MethodMergeParameters
;
1921 static int parse_merge_parameters(Varlink
*link
, JsonVariant
*parameters
, MethodMergeParameters
*p
) {
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 },
1935 return varlink_dispatch(link
, parameters
, dispatch_table
, p
);
1938 static int vl_method_merge(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
1939 _cleanup_hashmap_free_ Hashmap
*images
= NULL
;
1940 MethodMergeParameters p
= {
1945 _cleanup_strv_free_
char **hierarchies
= NULL
;
1946 ImageClass image_class
= arg_image_class
;
1951 r
= parse_merge_parameters(link
, parameters
, &p
);
1955 r
= parse_image_class_parameter(link
, p
.class, &image_class
, &hierarchies
);
1959 r
= image_discover_and_read_metadata(image_class
, &images
);
1964 r
= look_for_merged_hierarchies(
1966 hierarchies
?: arg_hierarchies
,
1971 return varlink_errorb(link
, "io.systemd.sysext.AlreadyMerged", JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("hierarchy", which
)));
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
,
1982 return varlink_reply(link
, NULL
);
1986 ImageClass image_class
,
1992 _cleanup_hashmap_free_ Hashmap
*images
= NULL
;
1995 r
= image_discover_and_read_metadata(image_class
, &images
);
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
);
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
);
2009 /* Net result here is that:
2011 * 1. If an overlayfs was mounted before and no extensions exist anymore, we'll have unmerged things.
2013 * 2. If an overlayfs was mounted before, and there are still extensions installed' we'll have
2014 * unmerged and then merged things again.
2016 * 3. If an overlayfs so far wasn't mounted, and there are extensions installed, we'll have it
2019 * 4. If there was no overlayfs mount so far, and no extensions installed, we implement a NOP.
2025 static int verb_refresh(int argc
, char **argv
, void *userdata
) {
2028 r
= have_effective_cap(CAP_SYS_ADMIN
);
2030 return log_error_errno(r
, "Failed to check if we have enough privileges: %m");
2032 return log_error_errno(SYNTHETIC_ERRNO(EPERM
), "Need to be privileged.");
2034 return refresh(arg_image_class
,
2041 static int vl_method_refresh(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
2043 MethodMergeParameters p
= {
2048 _cleanup_strv_free_
char **hierarchies
= NULL
;
2049 ImageClass image_class
= arg_image_class
;
2054 r
= parse_merge_parameters(link
, parameters
, &p
);
2058 r
= parse_image_class_parameter(link
, p
.class, &image_class
, &hierarchies
);
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
);
2070 return varlink_reply(link
, NULL
);
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
;
2079 images
= hashmap_new(&image_hash_ops
);
2083 r
= image_discover(arg_image_class
, arg_root
, images
);
2085 return log_error_errno(r
, "Failed to discover images: %m");
2087 if ((arg_json_format_flags
& JSON_FORMAT_OFF
) && hashmap_isempty(images
)) {
2088 log_info("No OS extensions found.");
2092 t
= table_new("name", "type", "path", "time");
2096 HASHMAP_FOREACH(img
, images
) {
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
);
2104 return table_log_add_error(r
);
2107 (void) table_set_sort(t
, (size_t) 0);
2109 return table_print_with_pager(t
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
2112 typedef struct MethodListParameters
{
2114 } MethodListParameters
;
2116 static int vl_method_list(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
2118 static const JsonDispatch dispatch_table
[] = {
2119 { "class", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(MethodListParameters
, class), 0 },
2122 MethodListParameters p
= {
2124 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
2125 _cleanup_hashmap_free_ Hashmap
*images
= NULL
;
2126 ImageClass image_class
= arg_image_class
;
2132 r
= varlink_dispatch(link
, parameters
, dispatch_table
, &p
);
2136 r
= parse_image_class_parameter(link
, p
.class, &image_class
, NULL
);
2140 images
= hashmap_new(&image_hash_ops
);
2144 r
= image_discover(image_class
, arg_root
, images
);
2148 HASHMAP_FOREACH(img
, images
) {
2150 /* Send previous item with more=true */
2151 r
= varlink_notify(link
, v
);
2156 v
= json_variant_unref(v
);
2158 r
= image_to_json(img
, &v
);
2163 if (v
) /* Send final item with more=false */
2164 return varlink_reply(link
, v
);
2166 return varlink_error(link
, "io.systemd.sysext.NoImagesFound", NULL
);
2169 static int verb_help(int argc
, char **argv
, void *userdata
) {
2170 _cleanup_free_
char *link
= NULL
;
2173 r
= terminal_urlify_man(image_class_info
[arg_image_class
].full_identifier
, "8", &link
);
2177 printf("%1$s [OPTIONS...] COMMAND\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
,
2207 image_class_info
[arg_image_class
].blurb
);
2212 static int parse_argv(int argc
, char *argv
[]) {
2215 ARG_VERSION
= 0x100,
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
},
2247 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
2252 return verb_help(argc
, argv
, NULL
);
2258 arg_pager_flags
|= PAGER_DISABLE
;
2266 r
= parse_path_argument(optarg
, false, &arg_root
);
2269 /* If --root= is provided, do not reload the service manager */
2270 arg_no_reload
= true;
2274 r
= parse_json_argument(optarg
, &arg_json_format_flags
);
2284 case ARG_IMAGE_POLICY
:
2285 r
= parse_image_policy_argument(optarg
, &arg_image_policy
);
2291 r
= parse_boolean_argument("--noexec", optarg
, NULL
);
2299 arg_no_reload
= true;
2303 r
= parse_mutable_mode(optarg
);
2305 return log_error_errno(r
, "Failed to parse argument to --mutable=: %s", optarg
);
2313 assert_not_reached();
2316 r
= varlink_invocation(VARLINK_ALLOW_ACCEPT
);
2318 return log_error_errno(r
, "Failed to check if invoked in Varlink mode: %m");
2325 static int sysext_main(int argc
, char *argv
[]) {
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
},
2337 return dispatch_verb(argc
, argv
, verbs
, NULL
);
2340 static int run(int argc
, char *argv
[]) {
2341 const char *env_var
;
2346 arg_image_class
= invoked_as(argv
, "systemd-confext") ? IMAGE_CONFEXT
: IMAGE_SYSEXT
;
2348 env_var
= getenv(image_class_info
[arg_image_class
].mode_env
);
2350 r
= parse_mutable_mode(env_var
);
2352 log_warning("Failed to parse %s environment variable value '%s'. Ignoring.",
2353 image_class_info
[arg_image_class
].mode_env
, env_var
);
2358 r
= parse_argv(argc
, argv
);
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
2365 r
= parse_env_extension_hierarchies(&arg_hierarchies
, image_class_info
[arg_image_class
].name_env
);
2367 return log_error_errno(r
, "Failed to parse environment variable: %m");
2370 _cleanup_(varlink_server_unrefp
) VarlinkServer
*varlink_server
= NULL
;
2372 /* Invocation as Varlink service */
2374 r
= varlink_server_new(&varlink_server
, VARLINK_SERVER_ROOT_ONLY
);
2376 return log_error_errno(r
, "Failed to allocate Varlink server: %m");
2378 r
= varlink_server_add_interface(varlink_server
, &vl_interface_io_systemd_sysext
);
2380 return log_error_errno(r
, "Failed to add Varlink interface: %m");
2382 r
= varlink_server_bind_method_many(
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
);
2389 return log_error_errno(r
, "Failed to bind Varlink methods: %m");
2391 r
= varlink_server_loop_auto(varlink_server
);
2393 return log_error_errno(r
, "Invoked by unprivileged Varlink peer, refusing.");
2395 return log_error_errno(r
, "Failed to run Varlink event loop: %m");
2397 return EXIT_SUCCESS
;
2400 return sysext_main(argc
, argv
);
2403 DEFINE_MAIN_FUNCTION(run
);