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-util.h"
48 #include "terminal-util.h"
49 #include "user-util.h"
51 #include "varlink-io.systemd.sysext.h"
54 typedef enum MutableMode
{
60 _MUTABLE_INVALID
= -EINVAL
,
63 static char **arg_hierarchies
= NULL
; /* "/usr" + "/opt" by default for sysext and /etc by default for confext */
64 static char *arg_root
= NULL
;
65 static JsonFormatFlags arg_json_format_flags
= JSON_FORMAT_OFF
;
66 static PagerFlags arg_pager_flags
= 0;
67 static bool arg_legend
= true;
68 static bool arg_force
= false;
69 static bool arg_no_reload
= false;
70 static int arg_noexec
= -1;
71 static ImagePolicy
*arg_image_policy
= NULL
;
72 static bool arg_varlink
= false;
73 static MutableMode arg_mutable
= MUTABLE_NO
;
75 /* Is set to IMAGE_CONFEXT when systemd is called with the confext functionality instead of the default */
76 static ImageClass arg_image_class
= IMAGE_SYSEXT
;
78 STATIC_DESTRUCTOR_REGISTER(arg_hierarchies
, strv_freep
);
79 STATIC_DESTRUCTOR_REGISTER(arg_root
, freep
);
80 STATIC_DESTRUCTOR_REGISTER(arg_image_policy
, image_policy_freep
);
82 /* Helper struct for naming simplicity and reusability */
84 const char *full_identifier
;
85 const char *short_identifier
;
86 const char *short_identifier_plural
;
88 const char *dot_directory_name
;
89 const char *directory_name
;
90 const char *level_env
;
91 const char *scope_env
;
93 const ImagePolicy
*default_image_policy
;
94 unsigned long default_mount_flags
;
95 } image_class_info
[_IMAGE_CLASS_MAX
] = {
97 .full_identifier
= "systemd-sysext",
98 .short_identifier
= "sysext",
99 .short_identifier_plural
= "extensions",
100 .blurb
= "Merge system extension images into /usr/ and /opt/.",
101 .dot_directory_name
= ".systemd-sysext",
102 .level_env
= "SYSEXT_LEVEL",
103 .scope_env
= "SYSEXT_SCOPE",
104 .name_env
= "SYSTEMD_SYSEXT_HIERARCHIES",
105 .default_image_policy
= &image_policy_sysext
,
106 .default_mount_flags
= MS_RDONLY
|MS_NODEV
,
109 .full_identifier
= "systemd-confext",
110 .short_identifier
= "confext",
111 .short_identifier_plural
= "confexts",
112 .blurb
= "Merge configuration extension images into /etc/.",
113 .dot_directory_name
= ".systemd-confext",
114 .level_env
= "CONFEXT_LEVEL",
115 .scope_env
= "CONFEXT_SCOPE",
116 .name_env
= "SYSTEMD_CONFEXT_HIERARCHIES",
117 .default_image_policy
= &image_policy_confext
,
118 .default_mount_flags
= MS_RDONLY
|MS_NODEV
|MS_NOSUID
|MS_NOEXEC
,
122 static int is_our_mount_point(
123 ImageClass image_class
,
126 _cleanup_free_
char *buf
= NULL
, *f
= NULL
;
133 r
= path_is_mount_point(p
);
135 log_debug_errno(r
, "Hierarchy '%s' doesn't exist.", p
);
139 return log_error_errno(r
, "Failed to determine whether '%s' is a mount point: %m", p
);
141 log_debug("Hierarchy '%s' is not a mount point, skipping.", p
);
145 /* So we know now that it's a mount point. Now let's check if it's one of ours, so that we don't
146 * accidentally unmount the user's own /usr/ but just the mounts we established ourselves. We do this
147 * check by looking into the metadata directory we place in merged mounts: if the file
148 * ../dev contains the major/minor device pair of the mount we have a good reason to
149 * believe this is one of our mounts. This thorough check has the benefit that we aren't easily
150 * confused if people tar up one of our merged trees and untar them elsewhere where we might mistake
151 * them for a live sysext tree. */
153 f
= path_join(p
, image_class_info
[image_class
].dot_directory_name
, "dev");
157 r
= read_one_line_file(f
, &buf
);
159 log_debug("Hierarchy '%s' does not carry a %s/dev file, not a merged tree.", p
, image_class_info
[image_class
].dot_directory_name
);
163 return log_error_errno(r
, "Failed to determine whether hierarchy '%s' contains '%s/dev': %m", p
, image_class_info
[image_class
].dot_directory_name
);
165 r
= parse_devnum(buf
, &dev
);
167 return log_error_errno(r
, "Failed to parse device major/minor stored in '%s/dev' file on '%s': %m", image_class_info
[image_class
].dot_directory_name
, p
);
169 if (lstat(p
, &st
) < 0)
170 return log_error_errno(r
, "Failed to stat %s: %m", p
);
172 if (st
.st_dev
!= dev
) {
173 log_debug("Hierarchy '%s' reports a different device major/minor than what we are seeing, assuming offline copy.", p
);
180 static int need_reload(
181 ImageClass image_class
,
185 /* Parse the mounted images to find out if we need to reload the daemon. */
191 STRV_FOREACH(p
, hierarchies
) {
192 _cleanup_free_
char *f
= NULL
, *buf
= NULL
, *resolved
= NULL
;
193 _cleanup_strv_free_
char **mounted_extensions
= NULL
;
195 r
= chase(*p
, arg_root
, CHASE_PREFIX_ROOT
, &resolved
, NULL
);
197 log_debug_errno(r
, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root
), *p
);
201 log_warning_errno(r
, "Failed to resolve path to hierarchy '%s%s': %m, ignoring.", strempty(arg_root
), *p
);
205 r
= is_our_mount_point(image_class
, resolved
);
211 f
= path_join(resolved
, image_class_info
[image_class
].dot_directory_name
, image_class_info
[image_class
].short_identifier_plural
);
215 r
= read_full_file(f
, &buf
, NULL
);
217 return log_error_errno(r
, "Failed to open '%s': %m", f
);
219 mounted_extensions
= strv_split_newlines(buf
);
220 if (!mounted_extensions
)
223 STRV_FOREACH(extension
, mounted_extensions
) {
224 _cleanup_strv_free_
char **extension_release
= NULL
;
225 const char *extension_reload_manager
= NULL
;
228 r
= load_extension_release_pairs(arg_root
, image_class
, *extension
, /* relax_extension_release_check */ true, &extension_release
);
230 log_debug_errno(r
, "Failed to parse extension-release metadata of %s, ignoring: %m", *extension
);
234 extension_reload_manager
= strv_env_pairs_get(extension_release
, "EXTENSION_RELOAD_MANAGER");
235 if (isempty(extension_reload_manager
))
238 b
= parse_boolean(extension_reload_manager
);
240 log_warning_errno(b
, "Failed to parse the extension metadata to know if the manager needs to be reloaded, ignoring: %m");
245 /* If at least one extension wants a reload, we reload. */
253 static int daemon_reload(void) {
254 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
257 r
= bus_connect_system_systemd(&bus
);
259 return log_error_errno(r
, "Failed to get D-Bus connection: %m");
261 return bus_service_manager_reload(bus
);
264 static int unmerge_hierarchy(
265 ImageClass image_class
,
268 _cleanup_free_
char *dot_dir
= NULL
, *work_dir_info_file
= NULL
;
273 dot_dir
= path_join(p
, image_class_info
[image_class
].dot_directory_name
);
277 work_dir_info_file
= path_join(dot_dir
, "work_dir");
278 if (!work_dir_info_file
)
282 _cleanup_free_
char *escaped_work_dir_in_root
= NULL
, *work_dir
= NULL
;
284 /* We only unmount /usr/ if it is a mount point and really one of ours, in order not to break
285 * systems where /usr/ is a mount point of its own already. */
287 r
= is_our_mount_point(image_class
, p
);
293 r
= read_one_line_file(work_dir_info_file
, &escaped_work_dir_in_root
);
296 return log_error_errno(r
, "Failed to read '%s': %m", work_dir_info_file
);
298 _cleanup_free_
char *work_dir_in_root
= NULL
;
301 l
= cunescape_length(escaped_work_dir_in_root
, r
, 0, &work_dir_in_root
);
303 return log_error_errno(l
, "Failed to unescape work directory path: %m");
304 work_dir
= path_join(arg_root
, work_dir_in_root
);
309 r
= umount_verbose(LOG_DEBUG
, dot_dir
, MNT_DETACH
|UMOUNT_NOFOLLOW
);
311 /* EINVAL is possibly "not a mount point". Let it slide as it's expected to occur if
312 * the whole hierarchy was read-only, so the dot directory inside it was not
313 * bind-mounted as read-only. */
315 return log_error_errno(r
, "Failed to unmount '%s': %m", dot_dir
);
318 r
= umount_verbose(LOG_ERR
, p
, MNT_DETACH
|UMOUNT_NOFOLLOW
);
323 r
= rm_rf(work_dir
, REMOVE_ROOT
| REMOVE_MISSING_OK
| REMOVE_PHYSICAL
);
325 return log_error_errno(r
, "Failed to remove '%s': %m", work_dir
);
328 log_info("Unmerged '%s'.", p
);
335 ImageClass image_class
,
342 r
= need_reload(image_class
, hierarchies
, no_reload
);
345 need_to_reload
= r
> 0;
347 STRV_FOREACH(p
, hierarchies
) {
348 _cleanup_free_
char *resolved
= NULL
;
350 r
= chase(*p
, arg_root
, CHASE_PREFIX_ROOT
, &resolved
, NULL
);
352 log_debug_errno(r
, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root
), *p
);
356 log_error_errno(r
, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root
), *p
);
363 r
= unmerge_hierarchy(image_class
, resolved
);
364 if (r
< 0 && ret
== 0)
368 if (need_to_reload
) {
377 static int verb_unmerge(int argc
, char **argv
, void *userdata
) {
380 r
= have_effective_cap(CAP_SYS_ADMIN
);
382 return log_error_errno(r
, "Failed to check if we have enough privileges: %m");
384 return log_error_errno(SYNTHETIC_ERRNO(EPERM
), "Need to be privileged.");
386 return unmerge(arg_image_class
,
391 static int parse_image_class_parameter(Varlink
*link
, const char *value
, ImageClass
*image_class
, char ***hierarchies
) {
392 _cleanup_strv_free_
char **h
= NULL
;
402 c
= image_class_from_string(value
);
403 if (!IN_SET(c
, IMAGE_SYSEXT
, IMAGE_CONFEXT
))
404 return varlink_error_invalid_parameter_name(link
, "class");
407 r
= parse_env_extension_hierarchies(&h
, image_class_info
[c
].name_env
);
409 return log_error_errno(r
, "Failed to parse environment variable: %m");
411 strv_free_and_replace(*hierarchies
, h
);
418 typedef struct MethodUnmergeParameters
{
421 } MethodUnmergeParameters
;
423 static int vl_method_unmerge(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
425 static const JsonDispatch dispatch_table
[] = {
426 { "class", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(MethodUnmergeParameters
, class), 0 },
427 { "noReload", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(MethodUnmergeParameters
, no_reload
), 0 },
430 MethodUnmergeParameters p
= {
433 _cleanup_strv_free_
char **hierarchies
= NULL
;
434 ImageClass image_class
= arg_image_class
;
439 r
= varlink_dispatch(link
, parameters
, dispatch_table
, &p
);
443 r
= parse_image_class_parameter(link
, p
.class, &image_class
, &hierarchies
);
447 r
= unmerge(image_class
,
448 hierarchies
?: arg_hierarchies
,
449 p
.no_reload
>= 0 ? p
.no_reload
: arg_no_reload
);
453 return varlink_reply(link
, NULL
);
456 static int verb_status(int argc
, char **argv
, void *userdata
) {
457 _cleanup_(table_unrefp
) Table
*t
= NULL
;
460 t
= table_new("hierarchy", "extensions", "since");
464 table_set_ersatz_string(t
, TABLE_ERSATZ_DASH
);
466 STRV_FOREACH(p
, arg_hierarchies
) {
467 _cleanup_free_
char *resolved
= NULL
, *f
= NULL
, *buf
= NULL
;
468 _cleanup_strv_free_
char **l
= NULL
;
471 r
= chase(*p
, arg_root
, CHASE_PREFIX_ROOT
, &resolved
, NULL
);
473 log_debug_errno(r
, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root
), *p
);
477 log_error_errno(r
, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root
), *p
);
481 r
= is_our_mount_point(arg_image_class
, resolved
);
488 TABLE_STRING
, "none",
489 TABLE_SET_COLOR
, ansi_grey(),
492 return table_log_add_error(r
);
497 f
= path_join(resolved
, image_class_info
[arg_image_class
].dot_directory_name
, image_class_info
[arg_image_class
].short_identifier_plural
);
501 r
= read_full_file(f
, &buf
, NULL
);
503 return log_error_errno(r
, "Failed to open '%s': %m", f
);
505 l
= strv_split_newlines(buf
);
509 if (stat(*p
, &st
) < 0)
510 return log_error_errno(r
, "Failed to stat() '%s': %m", *p
);
516 TABLE_TIMESTAMP
, timespec_load(&st
.st_mtim
));
518 return table_log_add_error(r
);
527 (void) table_set_sort(t
, (size_t) 0);
529 r
= table_print_with_pager(t
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
536 static int append_overlayfs_path_option(
538 const char *separator
,
542 _cleanup_free_
char *escaped
= NULL
;
548 escaped
= shell_escape(path
, ",:");
553 if (!strextend(options
, separator
, option
, "=", escaped
))
555 } else if (!strextend(options
, separator
, escaped
))
561 static int mount_overlayfs(
562 ImageClass image_class
,
566 const char *upper_dir
,
567 const char *work_dir
) {
569 _cleanup_free_
char *options
= NULL
;
570 bool separator
= false;
575 assert((upper_dir
&& work_dir
) || (!upper_dir
&& !work_dir
));
577 options
= strdup("lowerdir=");
581 STRV_FOREACH(l
, layers
) {
582 r
= append_overlayfs_path_option(&options
, separator
? ":" : "", NULL
, *l
);
589 flags
= image_class_info
[image_class
].default_mount_flags
;
591 SET_FLAG(flags
, MS_NOEXEC
, noexec
);
593 if (upper_dir
&& work_dir
) {
594 r
= append_overlayfs_path_option(&options
, ",", "upperdir", upper_dir
);
600 r
= append_overlayfs_path_option(&options
, ",", "workdir", work_dir
);
603 /* redirect_dir=on and noatime prevent unnecessary upcopies, metacopy=off prevents broken
604 * files from partial upcopies after umount. */
605 if (!strextend(&options
, ",redirect_dir=on,noatime,metacopy=off"))
609 /* Now mount the actual overlayfs */
610 r
= mount_nofollow_verbose(LOG_ERR
, image_class_info
[image_class
].short_identifier
, where
, "overlay", flags
, options
);
617 static char *hierarchy_as_single_path_component(const char *hierarchy
) {
618 /* We normally expect hierarchy to be /usr, /opt or /etc, but for debugging purposes the hierarchy
619 * could very well be like /foo/bar/baz/. So for a given hierarchy we generate a directory name by
620 * stripping the leading and trailing separators and replacing the rest of separators with dots. This
621 * makes the generated name to be the same for /foo/bar/baz and for /foo/bar.baz, but, again,
622 * specifying a different hierarchy is a debugging feature, so non-unique mapping should not be an
623 * issue in general case. */
624 const char *stripped
= hierarchy
;
625 _cleanup_free_
char *dir_name
= NULL
;
629 stripped
+= strspn(stripped
, "/");
631 dir_name
= strdup(stripped
);
634 delete_trailing_chars(dir_name
, "/");
635 string_replace_char(dir_name
, '/', '.');
636 return TAKE_PTR(dir_name
);
639 static char *determine_mutable_directory_path_for_hierarchy(const char *hierarchy
) {
640 _cleanup_free_
char *dir_name
= NULL
;
643 dir_name
= hierarchy_as_single_path_component(hierarchy
);
647 return path_join("/var/lib/extensions.mutable", dir_name
);
650 static int paths_on_same_fs(const char *path1
, const char *path2
) {
651 struct stat st1
, st2
;
656 if (stat(path1
, &st1
))
657 return log_error_errno(errno
, "Failed to stat '%s': %m", path1
);
659 if (stat(path2
, &st2
))
660 return log_error_errno(errno
, "Failed to stat '%s': %m", path2
);
662 return st1
.st_dev
== st2
.st_dev
;
665 static int work_dir_for_hierarchy(
666 const char *hierarchy
,
667 const char *resolved_upper_dir
,
668 char **ret_work_dir
) {
670 _cleanup_free_
char *parent
= NULL
;
674 assert(resolved_upper_dir
);
675 assert(ret_work_dir
);
677 r
= path_extract_directory(resolved_upper_dir
, &parent
);
679 return log_error_errno(r
, "Failed to get parent directory of upperdir '%s': %m", resolved_upper_dir
);
681 /* TODO: paths_in_same_superblock? partition? device? */
682 r
= paths_on_same_fs(resolved_upper_dir
, parent
);
686 return log_error_errno(SYNTHETIC_ERRNO(EXDEV
), "Unable to find a suitable workdir location for upperdir '%s' for host hierarchy '%s' - parent directory of the upperdir is in a different filesystem", resolved_upper_dir
, hierarchy
);
688 _cleanup_free_
char *f
= NULL
, *dir_name
= NULL
;
690 f
= hierarchy_as_single_path_component(hierarchy
);
693 dir_name
= strjoin(".systemd-", f
, "-workdir");
698 f
= path_join(parent
, dir_name
);
702 *ret_work_dir
= TAKE_PTR(f
);
706 typedef struct OverlayFSPaths
{
708 char *resolved_hierarchy
;
709 char *resolved_mutable_directory
;
711 /* NULL if merged fs is read-only */
713 /* NULL if merged fs is read-only */
715 /* lowest index is top lowerdir, highest index is bottom lowerdir */
719 static OverlayFSPaths
*overlayfs_paths_free(OverlayFSPaths
*op
) {
724 free(op
->resolved_hierarchy
);
725 free(op
->resolved_mutable_directory
);
729 strv_free(op
->lower_dirs
);
734 DEFINE_TRIVIAL_CLEANUP_FUNC(OverlayFSPaths
*, overlayfs_paths_free
);
736 static int resolve_hierarchy(const char *hierarchy
, char **ret_resolved_hierarchy
) {
737 _cleanup_free_
char *resolved_path
= NULL
;
741 assert(ret_resolved_hierarchy
);
743 r
= chase(hierarchy
, arg_root
, CHASE_PREFIX_ROOT
, &resolved_path
, NULL
);
744 if (r
< 0 && r
!= -ENOENT
)
745 return log_error_errno(r
, "Failed to resolve hierarchy '%s': %m", hierarchy
);
747 *ret_resolved_hierarchy
= TAKE_PTR(resolved_path
);
751 static int resolve_mutable_directory(const char *hierarchy
, char **ret_resolved_mutable_directory
) {
752 _cleanup_free_
char *path
= NULL
, *resolved_path
= NULL
;
756 assert(ret_resolved_mutable_directory
);
758 if (arg_mutable
== MUTABLE_NO
) {
759 log_debug("Mutability for hierarchy '%s' is disabled, not resolving mutable directory.", hierarchy
);
760 *ret_resolved_mutable_directory
= NULL
;
764 path
= determine_mutable_directory_path_for_hierarchy(hierarchy
);
768 if (arg_mutable
== MUTABLE_YES
) {
769 _cleanup_free_
char *path_in_root
= NULL
;
771 path_in_root
= path_join(arg_root
, path
);
775 r
= mkdir_p(path_in_root
, 0700);
777 return log_error_errno(r
, "Failed to create a directory '%s': %m", path_in_root
);
780 r
= chase(path
, arg_root
, CHASE_PREFIX_ROOT
, &resolved_path
, NULL
);
781 if (r
< 0 && r
!= -ENOENT
)
782 return log_error_errno(r
, "Failed to resolve mutable directory '%s': %m", path
);
784 *ret_resolved_mutable_directory
= TAKE_PTR(resolved_path
);
788 static int overlayfs_paths_new(const char *hierarchy
, OverlayFSPaths
**ret_op
) {
789 _cleanup_free_
char *hierarchy_copy
= NULL
, *resolved_hierarchy
= NULL
, *resolved_mutable_directory
= NULL
;
795 hierarchy_copy
= strdup(hierarchy
);
799 r
= resolve_hierarchy(hierarchy
, &resolved_hierarchy
);
802 r
= resolve_mutable_directory(hierarchy
, &resolved_mutable_directory
);
807 op
= new(OverlayFSPaths
, 1);
811 *op
= (OverlayFSPaths
) {
812 .hierarchy
= TAKE_PTR(hierarchy_copy
),
813 .resolved_hierarchy
= TAKE_PTR(resolved_hierarchy
),
814 .resolved_mutable_directory
= TAKE_PTR(resolved_mutable_directory
),
817 *ret_op
= TAKE_PTR(op
);
821 static int determine_top_lower_dirs(OverlayFSPaths
*op
, const char *meta_path
) {
827 /* Put the meta path (i.e. our synthesized stuff) at the top of the layer stack */
828 r
= strv_extend(&op
->lower_dirs
, meta_path
);
832 /* If importing mutable layer and it actually exists, add it just below the meta path */
833 if (arg_mutable
== MUTABLE_IMPORT
&& op
->resolved_mutable_directory
) {
834 r
= strv_extend(&op
->lower_dirs
, op
->resolved_mutable_directory
);
842 static int determine_middle_lower_dirs(OverlayFSPaths
*op
, char **paths
, size_t *ret_extensions_used
) {
848 assert(ret_extensions_used
);
850 /* Put the extensions in the middle */
851 STRV_FOREACH(p
, paths
) {
852 _cleanup_free_
char *resolved
= NULL
;
854 r
= chase(op
->hierarchy
, *p
, CHASE_PREFIX_ROOT
, &resolved
, NULL
);
856 log_debug_errno(r
, "Hierarchy '%s' in extension '%s' doesn't exist, not merging.", op
->hierarchy
, *p
);
860 return log_error_errno(r
, "Failed to resolve hierarchy '%s' in extension '%s': %m", op
->hierarchy
, *p
);
862 r
= dir_is_empty(resolved
, /* ignore_hidden_or_backup= */ false);
864 return log_error_errno(r
, "Failed to check if hierarchy '%s' in extension '%s' is empty: %m", resolved
, *p
);
866 log_debug("Hierarchy '%s' in extension '%s' is empty, not merging.", op
->hierarchy
, *p
);
870 r
= strv_consume(&op
->lower_dirs
, TAKE_PTR(resolved
));
876 *ret_extensions_used
= n
;
880 static int hierarchy_as_lower_dir(OverlayFSPaths
*op
) {
883 /* return 0 if hierarchy should be used as lower dir, >0, if not */
887 if (!op
->resolved_hierarchy
) {
888 log_debug("Host hierarchy '%s' does not exist, will not be used as lowerdir", op
->hierarchy
);
892 r
= dir_is_empty(op
->resolved_hierarchy
, /* ignore_hidden_or_backup= */ false);
894 return log_error_errno(r
, "Failed to check if host hierarchy '%s' is empty: %m", op
->resolved_hierarchy
);
896 log_debug("Host hierarchy '%s' is empty, will not be used as lower dir.", op
->resolved_hierarchy
);
900 if (arg_mutable
== MUTABLE_IMPORT
) {
901 log_debug("Mutability for host hierarchy '%s' is disabled, so it will be a lowerdir", op
->resolved_hierarchy
);
905 if (!op
->resolved_mutable_directory
) {
906 log_debug("No mutable directory found, so host hierarchy '%s' will be used as lowerdir", op
->resolved_hierarchy
);
910 if (path_equal(op
->resolved_hierarchy
, op
->resolved_mutable_directory
)) {
911 log_debug("Host hierarchy '%s' will serve as upperdir.", op
->resolved_hierarchy
);
914 r
= inode_same(op
->resolved_hierarchy
, op
->resolved_mutable_directory
, 0);
916 return log_error_errno(r
, "Failed to check inode equality of hierarchy %s and its mutable directory %s: %m", op
->resolved_hierarchy
, op
->resolved_mutable_directory
);
918 log_debug("Host hierarchy '%s' will serve as upperdir.", op
->resolved_hierarchy
);
925 static int determine_bottom_lower_dirs(OverlayFSPaths
*op
) {
930 r
= hierarchy_as_lower_dir(op
);
934 r
= strv_extend(&op
->lower_dirs
, op
->resolved_hierarchy
);
942 static int determine_lower_dirs(
945 const char *meta_path
,
946 size_t *ret_extensions_used
) {
953 assert(ret_extensions_used
);
955 r
= determine_top_lower_dirs(op
, meta_path
);
959 r
= determine_middle_lower_dirs(op
, paths
, ret_extensions_used
);
963 r
= determine_bottom_lower_dirs(op
);
970 static int determine_upper_dir(OverlayFSPaths
*op
) {
974 assert(!op
->upper_dir
);
976 if (arg_mutable
== MUTABLE_IMPORT
) {
977 log_debug("Mutability is disabled, there will be no upperdir for host hierarchy '%s'", op
->hierarchy
);
981 if (!op
->resolved_mutable_directory
) {
982 log_debug("No mutable directory found for host hierarchy '%s', there will be no upperdir", op
->hierarchy
);
986 /* Require upper dir to be on writable filesystem if it's going to be used as an actual overlayfs
987 * upperdir, instead of a lowerdir as an imported path. */
988 r
= path_is_read_only_fs(op
->resolved_mutable_directory
);
990 return log_error_errno(r
, "Failed to determine if mutable directory '%s' is on read-only filesystem: %m", op
->resolved_mutable_directory
);
992 return log_error_errno(SYNTHETIC_ERRNO(EROFS
), "Can't use '%s' as an upperdir as it is read-only.", op
->resolved_mutable_directory
);
994 op
->upper_dir
= strdup(op
->resolved_mutable_directory
);
1001 static int determine_work_dir(OverlayFSPaths
*op
) {
1002 _cleanup_free_
char *work_dir
= NULL
;
1006 assert(!op
->work_dir
);
1011 if (arg_mutable
== MUTABLE_IMPORT
)
1014 r
= work_dir_for_hierarchy(op
->hierarchy
, op
->upper_dir
, &work_dir
);
1018 op
->work_dir
= TAKE_PTR(work_dir
);
1022 static int mount_overlayfs_with_op(
1024 ImageClass image_class
,
1026 const char *overlay_path
,
1027 const char *meta_path
) {
1032 assert(overlay_path
);
1034 r
= mkdir_p(overlay_path
, 0700);
1036 return log_error_errno(r
, "Failed to make directory '%s': %m", overlay_path
);
1038 r
= mkdir_p(meta_path
, 0700);
1040 return log_error_errno(r
, "Failed to make directory '%s': %m", meta_path
);
1042 if (op
->upper_dir
&& op
->work_dir
) {
1043 r
= mkdir_p(op
->work_dir
, 0700);
1045 return log_error_errno(r
, "Failed to make directory '%s': %m", op
->work_dir
);
1048 r
= mount_overlayfs(image_class
, noexec
, overlay_path
, op
->lower_dirs
, op
->upper_dir
, op
->work_dir
);
1055 static int write_extensions_file(ImageClass image_class
, char **extensions
, const char *meta_path
) {
1056 _cleanup_free_
char *f
= NULL
, *buf
= NULL
;
1062 /* Let's generate a metadata file that lists all extensions we took into account for this
1063 * hierarchy. We include this in the final fs, to make things nicely discoverable and
1065 f
= path_join(meta_path
, image_class_info
[image_class
].dot_directory_name
, image_class_info
[image_class
].short_identifier_plural
);
1069 buf
= strv_join(extensions
, "\n");
1073 r
= write_string_file(f
, buf
, WRITE_STRING_FILE_CREATE
|WRITE_STRING_FILE_MKDIR_0755
);
1075 return log_error_errno(r
, "Failed to write extension meta file '%s': %m", f
);
1080 static int write_dev_file(ImageClass image_class
, const char *meta_path
, const char *overlay_path
) {
1081 _cleanup_free_
char *f
= NULL
;
1086 assert(overlay_path
);
1088 /* Now we have mounted the new file system. Let's now figure out its .st_dev field, and make that
1089 * available in the metadata directory. This is useful to detect whether the metadata dir actually
1090 * belongs to the fs it is found on: if .st_dev of the top-level mount matches it, it's pretty likely
1091 * we are looking at a live tree, and not an unpacked tar or so of one. */
1092 if (stat(overlay_path
, &st
) < 0)
1093 return log_error_errno(errno
, "Failed to stat mount '%s': %m", overlay_path
);
1095 f
= path_join(meta_path
, image_class_info
[image_class
].dot_directory_name
, "dev");
1099 /* Modifying the underlying layers while the overlayfs is mounted is technically undefined, but at
1100 * least it won't crash or deadlock, as per the kernel docs about overlayfs:
1101 * https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html#changes-to-underlying-filesystems */
1102 r
= write_string_file(f
, FORMAT_DEVNUM(st
.st_dev
), WRITE_STRING_FILE_CREATE
);
1104 return log_error_errno(r
, "Failed to write '%s': %m", f
);
1109 static int write_work_dir_file(ImageClass image_class
, const char *meta_path
, const char *work_dir
) {
1110 _cleanup_free_
char *escaped_work_dir_in_root
= NULL
, *f
= NULL
;
1111 char *work_dir_in_root
= NULL
;
1119 work_dir_in_root
= path_startswith(work_dir
, empty_to_root(arg_root
));
1120 if (!work_dir_in_root
)
1121 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Workdir '%s' must not be outside root '%s'", work_dir
, empty_to_root(arg_root
));
1123 f
= path_join(meta_path
, image_class_info
[image_class
].dot_directory_name
, "work_dir");
1127 /* Paths can have newlines for whatever reason, so better escape them to really get a single
1129 escaped_work_dir_in_root
= cescape(work_dir_in_root
);
1130 if (!escaped_work_dir_in_root
)
1132 r
= write_string_file(f
, escaped_work_dir_in_root
, WRITE_STRING_FILE_CREATE
);
1134 return log_error_errno(r
, "Failed to write '%s': %m", f
);
1139 static int store_info_in_meta(
1140 ImageClass image_class
,
1142 const char *meta_path
,
1143 const char *overlay_path
,
1144 const char *work_dir
) {
1150 assert(overlay_path
);
1151 /* work_dir may be NULL */
1153 r
= write_extensions_file(image_class
, extensions
, meta_path
);
1157 r
= write_dev_file(image_class
, meta_path
, overlay_path
);
1161 r
= write_work_dir_file(image_class
, meta_path
, work_dir
);
1165 /* Make sure the top-level dir has an mtime marking the point we established the merge */
1166 if (utimensat(AT_FDCWD
, meta_path
, NULL
, AT_SYMLINK_NOFOLLOW
) < 0)
1167 return log_error_errno(r
, "Failed fix mtime of '%s': %m", meta_path
);
1172 static int make_mounts_read_only(ImageClass image_class
, const char *overlay_path
, bool mutable) {
1175 assert(overlay_path
);
1178 /* Bind mount the meta path as read-only on mutable overlays to avoid accidental
1179 * modifications of the contents of meta directory, which could lead to systemd thinking that
1180 * this hierarchy is not our mount. */
1181 _cleanup_free_
char *f
= NULL
;
1183 f
= path_join(overlay_path
, image_class_info
[image_class
].dot_directory_name
);
1187 r
= mount_nofollow_verbose(LOG_ERR
, f
, f
, NULL
, MS_BIND
, NULL
);
1191 r
= bind_remount_one(f
, MS_RDONLY
, MS_RDONLY
);
1193 return log_error_errno(r
, "Failed to remount '%s' as read-only: %m", f
);
1195 /* The overlayfs superblock is read-only. Let's also mark the bind mount read-only. Extra
1196 * turbo safety 😎 */
1197 r
= bind_remount_recursive(overlay_path
, MS_RDONLY
, MS_RDONLY
, NULL
);
1199 return log_error_errno(r
, "Failed to make bind mount '%s' read-only: %m", overlay_path
);
1205 static int merge_hierarchy(
1206 ImageClass image_class
,
1207 const char *hierarchy
,
1211 const char *meta_path
,
1212 const char *overlay_path
) {
1214 _cleanup_(overlayfs_paths_freep
) OverlayFSPaths
*op
= NULL
;
1215 size_t extensions_used
= 0;
1222 assert(overlay_path
);
1224 r
= overlayfs_paths_new(hierarchy
, &op
);
1228 r
= determine_lower_dirs(op
, paths
, meta_path
, &extensions_used
);
1232 if (extensions_used
== 0) /* No extension with files in this hierarchy? Then don't do anything. */
1235 r
= determine_upper_dir(op
);
1239 r
= determine_work_dir(op
);
1243 r
= mount_overlayfs_with_op(op
, image_class
, noexec
, overlay_path
, meta_path
);
1247 r
= store_info_in_meta(image_class
, extensions
, meta_path
, overlay_path
, op
->work_dir
);
1251 r
= make_mounts_read_only(image_class
, overlay_path
, op
->upper_dir
&& op
->work_dir
);
1258 static int strverscmp_improvedp(char *const* a
, char *const* b
) {
1259 /* usable in qsort() for sorting a string array with strverscmp_improved() */
1260 return strverscmp_improved(*a
, *b
);
1263 static const ImagePolicy
*pick_image_policy(const Image
*img
) {
1267 /* Explicitly specified policy always wins */
1268 if (arg_image_policy
)
1269 return arg_image_policy
;
1271 /* If located in /.extra/sysext/ in the initrd, then it was placed there by systemd-stub, and was
1272 * picked up from an untrusted ESP. Thus, require a stricter policy by default for them. (For the
1273 * other directories we assume the appropriate level of trust was already established already. */
1276 if (path_startswith(img
->path
, "/.extra/sysext/"))
1277 return &image_policy_sysext_strict
;
1278 if (path_startswith(img
->path
, "/.extra/confext/"))
1279 return &image_policy_confext_strict
;
1281 /* Better safe than sorry, refuse everything else passed in via the untrusted /.extra/ dir */
1282 if (path_startswith(img
->path
, "/.extra/"))
1283 return &image_policy_deny
;
1286 return image_class_info
[img
->class].default_image_policy
;
1289 static int merge_subprocess(
1290 ImageClass image_class
,
1295 const char *workspace
) {
1297 _cleanup_free_
char *host_os_release_id
= NULL
, *host_os_release_version_id
= NULL
, *host_os_release_api_level
= NULL
, *buf
= NULL
;
1298 _cleanup_strv_free_
char **extensions
= NULL
, **paths
= NULL
;
1299 size_t n_extensions
= 0;
1300 unsigned n_ignored
= 0;
1304 /* Mark the whole of /run as MS_SLAVE, so that we can mount stuff below it that doesn't show up on
1305 * the host otherwise. */
1306 r
= mount_nofollow_verbose(LOG_ERR
, NULL
, "/run", NULL
, MS_SLAVE
|MS_REC
, NULL
);
1308 return log_error_errno(r
, "Failed to remount /run/ MS_SLAVE: %m");
1310 /* Let's create the workspace if it's missing */
1311 r
= mkdir_p(workspace
, 0700);
1313 return log_error_errno(r
, "Failed to create '%s': %m", workspace
);
1315 /* Let's mount a tmpfs to our workspace. This way we don't need to clean up the inodes we mount over,
1316 * but let the kernel do that entirely automatically, once our namespace dies. Note that this file
1317 * system won't be visible to anyone but us, since we opened our own namespace and then made the
1318 * /run/ hierarchy (which our workspace is contained in) MS_SLAVE, see above. */
1319 r
= mount_nofollow_verbose(LOG_ERR
, image_class_info
[image_class
].short_identifier
, workspace
, "tmpfs", 0, "mode=0700");
1323 /* Acquire host OS release info, so that we can compare it with the extension's data */
1324 r
= parse_os_release(
1326 "ID", &host_os_release_id
,
1327 "VERSION_ID", &host_os_release_version_id
,
1328 image_class_info
[image_class
].level_env
, &host_os_release_api_level
);
1330 return log_error_errno(r
, "Failed to acquire 'os-release' data of OS tree '%s': %m", empty_to_root(arg_root
));
1331 if (isempty(host_os_release_id
))
1332 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1333 "'ID' field not found or empty in 'os-release' data of OS tree '%s': %m",
1334 empty_to_root(arg_root
));
1336 /* Let's now mount all images */
1337 HASHMAP_FOREACH(img
, images
) {
1338 _cleanup_free_
char *p
= NULL
;
1340 p
= path_join(workspace
, image_class_info
[image_class
].short_identifier_plural
, img
->name
);
1344 r
= mkdir_p(p
, 0700);
1346 return log_error_errno(r
, "Failed to create %s: %m", p
);
1348 switch (img
->type
) {
1349 case IMAGE_DIRECTORY
:
1350 case IMAGE_SUBVOLUME
:
1353 r
= extension_has_forbidden_content(p
);
1362 r
= mount_nofollow_verbose(LOG_ERR
, img
->path
, p
, NULL
, MS_BIND
, NULL
);
1366 /* Make this a read-only bind mount */
1367 r
= bind_remount_recursive(p
, MS_RDONLY
, MS_RDONLY
, NULL
);
1369 return log_error_errno(r
, "Failed to make bind mount '%s' read-only: %m", p
);
1375 _cleanup_(dissected_image_unrefp
) DissectedImage
*m
= NULL
;
1376 _cleanup_(loop_device_unrefp
) LoopDevice
*d
= NULL
;
1377 _cleanup_(verity_settings_done
) VeritySettings verity_settings
= VERITY_SETTINGS_DEFAULT
;
1378 DissectImageFlags flags
=
1379 DISSECT_IMAGE_READ_ONLY
|
1380 DISSECT_IMAGE_GENERIC_ROOT
|
1381 DISSECT_IMAGE_REQUIRE_ROOT
|
1382 DISSECT_IMAGE_MOUNT_ROOT_ONLY
|
1383 DISSECT_IMAGE_USR_NO_ROOT
|
1384 DISSECT_IMAGE_ADD_PARTITION_DEVICES
|
1385 DISSECT_IMAGE_PIN_PARTITION_DEVICES
;
1387 r
= verity_settings_load(&verity_settings
, img
->path
, NULL
, NULL
);
1389 return log_error_errno(r
, "Failed to read verity artifacts for %s: %m", img
->path
);
1391 if (verity_settings
.data_path
)
1392 flags
|= DISSECT_IMAGE_NO_PARTITION_TABLE
;
1395 flags
|= DISSECT_IMAGE_VALIDATE_OS_EXT
;
1397 r
= loop_device_make_by_path(
1400 /* sector_size= */ UINT32_MAX
,
1401 FLAGS_SET(flags
, DISSECT_IMAGE_NO_PARTITION_TABLE
) ? 0 : LO_FLAGS_PARTSCAN
,
1405 return log_error_errno(r
, "Failed to set up loopback device for %s: %m", img
->path
);
1407 r
= dissect_loop_device_and_warn(
1410 /* mount_options= */ NULL
,
1411 pick_image_policy(img
),
1417 r
= dissected_image_load_verity_sig_partition(
1424 r
= dissected_image_decrypt_interactively(
1431 r
= dissected_image_mount_and_warn(
1434 /* uid_shift= */ UID_INVALID
,
1435 /* uid_range= */ UID_INVALID
,
1436 /* userns_fd= */ -EBADF
,
1438 if (r
< 0 && r
!= -ENOMEDIUM
)
1440 if (r
== -ENOMEDIUM
&& !force
) {
1445 r
= dissected_image_relinquish(m
);
1447 return log_error_errno(r
, "Failed to relinquish DM and loopback block devices: %m");
1451 assert_not_reached();
1455 log_debug("Force mode enabled, skipping version validation.");
1457 r
= extension_release_validate(
1460 host_os_release_version_id
,
1461 host_os_release_api_level
,
1462 in_initrd() ? "initrd" : "system",
1463 image_extension_release(img
, image_class
),
1473 /* Nice! This one is an extension we want. */
1474 r
= strv_extend(&extensions
, img
->name
);
1481 /* Nothing left? Then shortcut things */
1482 if (n_extensions
== 0) {
1484 log_info("No suitable extensions found (%u ignored due to incompatible image(s)).", n_ignored
);
1486 log_info("No extensions found.");
1490 /* Order by version sort with strverscmp_improved() */
1491 typesafe_qsort(extensions
, n_extensions
, strverscmp_improvedp
);
1493 buf
= strv_join(extensions
, "', '");
1497 log_info("Using extensions '%s'.", buf
);
1499 /* Build table of extension paths (in reverse order) */
1500 paths
= new0(char*, n_extensions
+ 1);
1504 for (size_t k
= 0; k
< n_extensions
; k
++) {
1505 _cleanup_free_
char *p
= NULL
;
1507 assert_se(img
= hashmap_get(images
, extensions
[n_extensions
- 1 - k
]));
1509 p
= path_join(workspace
, image_class_info
[image_class
].short_identifier_plural
, img
->name
);
1513 paths
[k
] = TAKE_PTR(p
);
1516 /* Let's now unmerge the status quo ante, since to build the new overlayfs we need a reference to the
1518 STRV_FOREACH(h
, hierarchies
) {
1519 _cleanup_free_
char *resolved
= NULL
;
1521 r
= chase(*h
, arg_root
, CHASE_PREFIX_ROOT
|CHASE_NONEXISTENT
, &resolved
, NULL
);
1523 return log_error_errno(r
, "Failed to resolve hierarchy '%s%s': %m", strempty(arg_root
), *h
);
1525 r
= unmerge_hierarchy(image_class
, resolved
);
1530 /* Create overlayfs mounts for all hierarchies */
1531 STRV_FOREACH(h
, hierarchies
) {
1532 _cleanup_free_
char *meta_path
= NULL
, *overlay_path
= NULL
;
1534 meta_path
= path_join(workspace
, "meta", *h
); /* The place where to store metadata about this instance */
1538 overlay_path
= path_join(workspace
, "overlay", *h
); /* The resulting overlayfs instance */
1542 r
= merge_hierarchy(
1554 /* And move them all into place. This is where things appear in the host namespace */
1555 STRV_FOREACH(h
, hierarchies
) {
1556 _cleanup_free_
char *p
= NULL
, *resolved
= NULL
;
1558 p
= path_join(workspace
, "overlay", *h
);
1562 if (laccess(p
, F_OK
) < 0) {
1563 if (errno
!= ENOENT
)
1564 return log_error_errno(errno
, "Failed to check if '%s' exists: %m", p
);
1566 /* Hierarchy apparently was empty in all extensions, and wasn't mounted, ignoring. */
1570 r
= chase(*h
, arg_root
, CHASE_PREFIX_ROOT
|CHASE_NONEXISTENT
, &resolved
, NULL
);
1572 return log_error_errno(r
, "Failed to resolve hierarchy '%s%s': %m", strempty(arg_root
), *h
);
1574 r
= mkdir_p(resolved
, 0755);
1576 return log_error_errno(r
, "Failed to create hierarchy mount point '%s': %m", resolved
);
1578 /* Using MS_REC to potentially bring in our read-only bind mount of metadata. */
1579 r
= mount_nofollow_verbose(LOG_ERR
, p
, resolved
, NULL
, MS_BIND
|MS_REC
, NULL
);
1583 log_info("Merged extensions into '%s'.", resolved
);
1589 static int merge(ImageClass image_class
,
1598 r
= safe_fork("(sd-merge)", FORK_DEATHSIG_SIGTERM
|FORK_LOG
|FORK_NEW_MOUNTNS
, &pid
);
1600 return log_error_errno(r
, "Failed to fork off child: %m");
1602 /* Child with its own mount namespace */
1604 r
= merge_subprocess(image_class
, hierarchies
, force
, noexec
, images
, "/run/systemd/sysext");
1606 _exit(EXIT_FAILURE
);
1608 /* Our namespace ceases to exist here, also implicitly detaching all temporary mounts we
1609 * created below /run. Nice! */
1611 _exit(r
> 0 ? EXIT_SUCCESS
: 123); /* 123 means: didn't find any extensions */
1614 r
= wait_for_terminate_and_check("(sd-merge)", pid
, WAIT_LOG_ABNORMAL
);
1618 if (r
== 123) /* exit code 123 means: didn't do anything */
1621 r
= need_reload(image_class
, hierarchies
, no_reload
);
1625 r
= daemon_reload();
1633 static int image_discover_and_read_metadata(
1634 ImageClass image_class
,
1635 Hashmap
**ret_images
) {
1636 _cleanup_hashmap_free_ Hashmap
*images
= NULL
;
1642 images
= hashmap_new(&image_hash_ops
);
1646 r
= image_discover(image_class
, arg_root
, images
);
1648 return log_error_errno(r
, "Failed to discover images: %m");
1650 HASHMAP_FOREACH(img
, images
) {
1651 r
= image_read_metadata(img
, image_class_info
[image_class
].default_image_policy
);
1653 return log_error_errno(r
, "Failed to read metadata for image %s: %m", img
->name
);
1656 *ret_images
= TAKE_PTR(images
);
1661 static int look_for_merged_hierarchies(
1662 ImageClass image_class
,
1664 const char **ret_which
) {
1669 /* In merge mode fail if things are already merged. (In --refresh mode below we'll unmerge if we find
1670 * things are already merged...) */
1671 STRV_FOREACH(p
, hierarchies
) {
1672 _cleanup_free_
char *resolved
= NULL
;
1674 r
= chase(*p
, arg_root
, CHASE_PREFIX_ROOT
, &resolved
, NULL
);
1676 log_debug_errno(r
, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root
), *p
);
1680 return log_error_errno(r
, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root
), *p
);
1682 r
= is_our_mount_point(image_class
, resolved
);
1695 static int verb_merge(int argc
, char **argv
, void *userdata
) {
1696 _cleanup_hashmap_free_ Hashmap
*images
= NULL
;
1700 r
= have_effective_cap(CAP_SYS_ADMIN
);
1702 return log_error_errno(r
, "Failed to check if we have enough privileges: %m");
1704 return log_error_errno(SYNTHETIC_ERRNO(EPERM
), "Need to be privileged.");
1706 r
= image_discover_and_read_metadata(arg_image_class
, &images
);
1710 r
= look_for_merged_hierarchies(arg_image_class
, arg_hierarchies
, &which
);
1714 return log_error_errno(SYNTHETIC_ERRNO(EBUSY
), "Hierarchy '%s' is already merged.", which
);
1716 return merge(arg_image_class
,
1724 typedef struct MethodMergeParameters
{
1729 } MethodMergeParameters
;
1731 static int parse_merge_parameters(Varlink
*link
, JsonVariant
*parameters
, MethodMergeParameters
*p
) {
1733 static const JsonDispatch dispatch_table
[] = {
1734 { "class", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(MethodMergeParameters
, class), 0 },
1735 { "force", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(MethodMergeParameters
, force
), 0 },
1736 { "noReload", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(MethodMergeParameters
, no_reload
), 0 },
1737 { "noexec", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(MethodMergeParameters
, noexec
), 0 },
1745 return varlink_dispatch(link
, parameters
, dispatch_table
, p
);
1748 static int vl_method_merge(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
1749 _cleanup_hashmap_free_ Hashmap
*images
= NULL
;
1750 MethodMergeParameters p
= {
1755 _cleanup_strv_free_
char **hierarchies
= NULL
;
1756 ImageClass image_class
= arg_image_class
;
1761 r
= parse_merge_parameters(link
, parameters
, &p
);
1765 r
= parse_image_class_parameter(link
, p
.class, &image_class
, &hierarchies
);
1769 r
= image_discover_and_read_metadata(image_class
, &images
);
1774 r
= look_for_merged_hierarchies(
1776 hierarchies
?: arg_hierarchies
,
1781 return varlink_errorb(link
, "io.systemd.sysext.AlreadyMerged", JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("hierarchy", which
)));
1783 r
= merge(image_class
,
1784 hierarchies
?: arg_hierarchies
,
1785 p
.force
>= 0 ? p
.force
: arg_force
,
1786 p
.no_reload
>= 0 ? p
.no_reload
: arg_no_reload
,
1787 p
.noexec
>= 0 ? p
.noexec
: arg_noexec
,
1792 return varlink_reply(link
, NULL
);
1796 ImageClass image_class
,
1802 _cleanup_hashmap_free_ Hashmap
*images
= NULL
;
1805 r
= image_discover_and_read_metadata(image_class
, &images
);
1809 /* Returns > 0 if it did something, i.e. a new overlayfs is mounted now. When it does so it
1810 * implicitly unmounts any overlayfs placed there before. Returns == 0 if it did nothing, i.e. no
1811 * extension images found. In this case the old overlayfs remains in place if there was one. */
1812 r
= merge(image_class
, hierarchies
, force
, no_reload
, noexec
, images
);
1815 if (r
== 0) /* No images found? Then unmerge. The goal of --refresh is after all that after having
1816 * called there's a guarantee that the merge status matches the installed extensions. */
1817 r
= unmerge(image_class
, hierarchies
, no_reload
);
1819 /* Net result here is that:
1821 * 1. If an overlayfs was mounted before and no extensions exist anymore, we'll have unmerged things.
1823 * 2. If an overlayfs was mounted before, and there are still extensions installed' we'll have
1824 * unmerged and then merged things again.
1826 * 3. If an overlayfs so far wasn't mounted, and there are extensions installed, we'll have it
1829 * 4. If there was no overlayfs mount so far, and no extensions installed, we implement a NOP.
1835 static int verb_refresh(int argc
, char **argv
, void *userdata
) {
1838 r
= have_effective_cap(CAP_SYS_ADMIN
);
1840 return log_error_errno(r
, "Failed to check if we have enough privileges: %m");
1842 return log_error_errno(SYNTHETIC_ERRNO(EPERM
), "Need to be privileged.");
1844 return refresh(arg_image_class
,
1851 static int vl_method_refresh(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
1853 MethodMergeParameters p
= {
1858 _cleanup_strv_free_
char **hierarchies
= NULL
;
1859 ImageClass image_class
= arg_image_class
;
1864 r
= parse_merge_parameters(link
, parameters
, &p
);
1868 r
= parse_image_class_parameter(link
, p
.class, &image_class
, &hierarchies
);
1872 r
= refresh(image_class
,
1873 hierarchies
?: arg_hierarchies
,
1874 p
.force
>= 0 ? p
.force
: arg_force
,
1875 p
.no_reload
>= 0 ? p
.no_reload
: arg_no_reload
,
1876 p
.noexec
>= 0 ? p
.noexec
: arg_noexec
);
1880 return varlink_reply(link
, NULL
);
1883 static int verb_list(int argc
, char **argv
, void *userdata
) {
1884 _cleanup_hashmap_free_ Hashmap
*images
= NULL
;
1885 _cleanup_(table_unrefp
) Table
*t
= NULL
;
1889 images
= hashmap_new(&image_hash_ops
);
1893 r
= image_discover(arg_image_class
, arg_root
, images
);
1895 return log_error_errno(r
, "Failed to discover images: %m");
1897 if ((arg_json_format_flags
& JSON_FORMAT_OFF
) && hashmap_isempty(images
)) {
1898 log_info("No OS extensions found.");
1902 t
= table_new("name", "type", "path", "time");
1906 HASHMAP_FOREACH(img
, images
) {
1909 TABLE_STRING
, img
->name
,
1910 TABLE_STRING
, image_type_to_string(img
->type
),
1911 TABLE_PATH
, img
->path
,
1912 TABLE_TIMESTAMP
, img
->mtime
!= 0 ? img
->mtime
: img
->crtime
);
1914 return table_log_add_error(r
);
1917 (void) table_set_sort(t
, (size_t) 0);
1919 return table_print_with_pager(t
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
1922 typedef struct MethodListParameters
{
1924 } MethodListParameters
;
1926 static int vl_method_list(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
1928 static const JsonDispatch dispatch_table
[] = {
1929 { "class", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(MethodListParameters
, class), 0 },
1932 MethodListParameters p
= {
1934 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
1935 _cleanup_hashmap_free_ Hashmap
*images
= NULL
;
1936 ImageClass image_class
= arg_image_class
;
1942 r
= varlink_dispatch(link
, parameters
, dispatch_table
, &p
);
1946 r
= parse_image_class_parameter(link
, p
.class, &image_class
, NULL
);
1950 images
= hashmap_new(&image_hash_ops
);
1954 r
= image_discover(image_class
, arg_root
, images
);
1958 HASHMAP_FOREACH(img
, images
) {
1960 /* Send previous item with more=true */
1961 r
= varlink_notify(link
, v
);
1966 v
= json_variant_unref(v
);
1968 r
= image_to_json(img
, &v
);
1973 if (v
) /* Send final item with more=false */
1974 return varlink_reply(link
, v
);
1976 return varlink_error(link
, "io.systemd.sysext.NoImagesFound", NULL
);
1979 static int verb_help(int argc
, char **argv
, void *userdata
) {
1980 _cleanup_free_
char *link
= NULL
;
1983 r
= terminal_urlify_man(image_class_info
[arg_image_class
].full_identifier
, "8", &link
);
1987 printf("%1$s [OPTIONS...] COMMAND\n"
1989 "\n%3$sCommands:%4$s\n"
1990 " status Show current merge status (default)\n"
1991 " merge Merge extensions into relevant hierarchies\n"
1992 " unmerge Unmerge extensions from relevant hierarchies\n"
1993 " refresh Unmerge/merge extensions again\n"
1994 " list List installed extensions\n"
1995 " -h --help Show this help\n"
1996 " --version Show package version\n"
1997 "\n%3$sOptions:%4$s\n"
1998 " --no-pager Do not pipe output into a pager\n"
1999 " --no-legend Do not show the headers and footers\n"
2000 " --root=PATH Operate relative to root path\n"
2001 " --json=pretty|short|off\n"
2002 " Generate JSON output\n"
2003 " --force Ignore version incompatibilities\n"
2004 " --no-reload Do not reload the service manager\n"
2005 " --image-policy=POLICY\n"
2006 " Specify disk image dissection policy\n"
2007 " --noexec=BOOL Whether to mount extension overlay with noexec\n"
2008 "\nSee the %2$s for details.\n",
2009 program_invocation_short_name
,
2015 image_class_info
[arg_image_class
].blurb
);
2020 static int parse_argv(int argc
, char *argv
[]) {
2023 ARG_VERSION
= 0x100,
2035 static const struct option options
[] = {
2036 { "help", no_argument
, NULL
, 'h' },
2037 { "version", no_argument
, NULL
, ARG_VERSION
},
2038 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
2039 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
2040 { "root", required_argument
, NULL
, ARG_ROOT
},
2041 { "json", required_argument
, NULL
, ARG_JSON
},
2042 { "force", no_argument
, NULL
, ARG_FORCE
},
2043 { "image-policy", required_argument
, NULL
, ARG_IMAGE_POLICY
},
2044 { "noexec", required_argument
, NULL
, ARG_NOEXEC
},
2045 { "no-reload", no_argument
, NULL
, ARG_NO_RELOAD
},
2046 { "mutable", required_argument
, NULL
, ARG_MUTABLE
},
2055 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
2060 return verb_help(argc
, argv
, NULL
);
2066 arg_pager_flags
|= PAGER_DISABLE
;
2074 r
= parse_path_argument(optarg
, false, &arg_root
);
2077 /* If --root= is provided, do not reload the service manager */
2078 arg_no_reload
= true;
2082 r
= parse_json_argument(optarg
, &arg_json_format_flags
);
2092 case ARG_IMAGE_POLICY
:
2093 r
= parse_image_policy_argument(optarg
, &arg_image_policy
);
2099 r
= parse_boolean_argument("--noexec", optarg
, NULL
);
2107 arg_no_reload
= true;
2111 if (streq(optarg
, "auto"))
2112 arg_mutable
= MUTABLE_AUTO
;
2113 else if (streq(optarg
, "import"))
2114 arg_mutable
= MUTABLE_IMPORT
;
2116 r
= parse_boolean(optarg
);
2118 return log_error_errno(r
, "Failed to parse argument to --mutable=: %s", optarg
);
2119 arg_mutable
= r
? MUTABLE_YES
: MUTABLE_NO
;
2127 assert_not_reached();
2130 r
= varlink_invocation(VARLINK_ALLOW_ACCEPT
);
2132 return log_error_errno(r
, "Failed to check if invoked in Varlink mode: %m");
2139 static int sysext_main(int argc
, char *argv
[]) {
2141 static const Verb verbs
[] = {
2142 { "status", VERB_ANY
, 1, VERB_DEFAULT
, verb_status
},
2143 { "merge", VERB_ANY
, 1, 0, verb_merge
},
2144 { "unmerge", VERB_ANY
, 1, 0, verb_unmerge
},
2145 { "refresh", VERB_ANY
, 1, 0, verb_refresh
},
2146 { "list", VERB_ANY
, 1, 0, verb_list
},
2147 { "help", VERB_ANY
, 1, 0, verb_help
},
2151 return dispatch_verb(argc
, argv
, verbs
, NULL
);
2154 static int run(int argc
, char *argv
[]) {
2159 arg_image_class
= invoked_as(argv
, "systemd-confext") ? IMAGE_CONFEXT
: IMAGE_SYSEXT
;
2161 r
= parse_argv(argc
, argv
);
2165 /* For debugging purposes it might make sense to do this for other hierarchies than /usr/ and
2166 * /opt/, but let's make that a hacker/debugging feature, i.e. env var instead of cmdline
2168 r
= parse_env_extension_hierarchies(&arg_hierarchies
, image_class_info
[arg_image_class
].name_env
);
2170 return log_error_errno(r
, "Failed to parse environment variable: %m");
2173 _cleanup_(varlink_server_unrefp
) VarlinkServer
*varlink_server
= NULL
;
2175 /* Invocation as Varlink service */
2177 r
= varlink_server_new(&varlink_server
, VARLINK_SERVER_ROOT_ONLY
);
2179 return log_error_errno(r
, "Failed to allocate Varlink server: %m");
2181 r
= varlink_server_add_interface(varlink_server
, &vl_interface_io_systemd_sysext
);
2183 return log_error_errno(r
, "Failed to add Varlink interface: %m");
2185 r
= varlink_server_bind_method_many(
2187 "io.systemd.sysext.Merge", vl_method_merge
,
2188 "io.systemd.sysext.Unmerge", vl_method_unmerge
,
2189 "io.systemd.sysext.Refresh", vl_method_refresh
,
2190 "io.systemd.sysext.List", vl_method_list
);
2192 return log_error_errno(r
, "Failed to bind Varlink methods: %m");
2194 r
= varlink_server_loop_auto(varlink_server
);
2196 return log_error_errno(r
, "Invoked by unprivileged Varlink peer, refusing.");
2198 return log_error_errno(r
, "Failed to run Varlink event loop: %m");
2200 return EXIT_SUCCESS
;
2203 return sysext_main(argc
, argv
);
2206 DEFINE_MAIN_FUNCTION(run
);