From: dongshengyuan <545258830@qq.com> Date: Mon, 6 Jul 2026 02:51:28 +0000 (+0800) Subject: sysext: honor confext config during notify refresh X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=34649e3d665560c8b9720105f29c091879295287;p=thirdparty%2Fsystemd.git sysext: honor confext config during notify refresh systemd-sysext handles the sysupdate notification hook, but the hook refreshes both sysexts and confexts. Build the refresh context for each image class so confext settings, such as Mutable=yes, are applied when confexts are refreshed. Add regression coverage for the mutable confext overlay. Fixes #42873 Signed-off-by: dongshengyuan --- diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 3c308bd8b6a..0f93322af04 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -107,7 +107,6 @@ enum { MERGE_EXIT_SKIP_REFRESH = 124, }; -static char **arg_hierarchies = NULL; /* "/usr" + "/opt" by default for sysext and /etc by default for confext */ static char *arg_root = NULL; static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; static PagerFlags arg_pager_flags = 0; @@ -117,11 +116,10 @@ static bool arg_no_reload = false; static bool arg_always_refresh = false; static int arg_noexec = -1; static ImagePolicy *arg_image_policy = NULL; -static bool arg_image_policy_set = false; /* Tracks initialization */ +static bool arg_image_policy_argv_set = false; static bool arg_varlink = false; static MutableMode arg_mutable = MUTABLE_NO; -static bool arg_mutable_set = false; /* Tracks initialization */ -static const char *arg_overlayfs_mount_options = NULL; +static bool arg_mutable_argv_set = false; /* Is set to IMAGE_CONFEXT when systemd is called with the confext functionality instead of the default */ static ImageClass arg_image_class = IMAGE_SYSEXT; @@ -132,7 +130,6 @@ static ImageClass arg_image_class = IMAGE_SYSEXT; * files from partial upcopies after umount, index=off allows reuse of the upper/work dirs */ #define MUTABLE_EXTENSIONS_MOUNT_OPTIONS "redirect_dir=on,noatime,metacopy=off,index=off" -STATIC_DESTRUCTOR_REGISTER(arg_hierarchies, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); @@ -194,25 +191,87 @@ static int parse_mutable_mode(const char *p) { static DEFINE_CONFIG_PARSE_ENUM(config_parse_mutable_mode, mutable_mode, MutableMode); -static int parse_config_file(ImageClass image_class) { +typedef struct Context { + ImageClass image_class; + char **hierarchies; + char *root; + bool force; + bool no_reload; + bool always_refresh; + int noexec; + MutableMode mutable; + bool mutable_set; + ImagePolicy *image_policy; + bool image_policy_set; + /* image_policy is usually borrowed from arg_image_policy. When parsed from configuration, this + * context owns it and frees it in context_done(). */ + bool image_policy_owned; + const char *overlayfs_mount_options; +} Context; + +#define CONTEXT_NULL \ + (Context) { \ + .image_class = IMAGE_SYSEXT, \ + .noexec = -1, \ + .mutable = MUTABLE_NO, \ + } + +static void context_done(Context *c) { + assert(c); + + c->hierarchies = strv_free(c->hierarchies); + c->root = mfree(c->root); + + if (c->image_policy_owned) + c->image_policy = image_policy_free(c->image_policy); +} + +static void parse_env_image_class_config(Context *c) { + const char *env_var; + int r; + + assert(c); + + env_var = secure_getenv(image_class_info[c->image_class].mode_env); + if (env_var) { + r = parse_mutable_mode(env_var); + if (r < 0) + log_warning("Failed to parse %s environment variable value '%s'. Ignoring.", + image_class_info[c->image_class].mode_env, env_var); + else { + c->mutable = r; + c->mutable_set = true; + } + } + + env_var = secure_getenv(image_class_info[c->image_class].opts_env); + if (env_var) + c->overlayfs_mount_options = env_var; +} + +static int parse_config_file(Context *c) { _cleanup_(image_policy_freep) ImagePolicy *config_image_policy = NULL; MutableMode config_mutable = MUTABLE_NO; - const char *section = image_class == IMAGE_SYSEXT ? "SysExt" : "ConfExt"; - const char *sections = image_class == IMAGE_SYSEXT ? "SysExt\0" : "ConfExt\0"; + const char *section, *sections; + _cleanup_free_ char *config_file = NULL; + int r; + + assert(c); + + section = c->image_class == IMAGE_SYSEXT ? "SysExt" : "ConfExt"; + sections = c->image_class == IMAGE_SYSEXT ? "SysExt\0" : "ConfExt\0"; const ConfigTableItem items[] = { { section, "Mutable", config_parse_mutable_mode, 0, &config_mutable }, { section, "ImagePolicy", config_parse_image_policy, 0, &config_image_policy }, {} }; - _cleanup_free_ char *config_file = NULL; - int r; - config_file = strjoin("systemd/", image_class_info[image_class].short_identifier, ".conf"); + config_file = strjoin("systemd/", image_class_info[c->image_class].short_identifier, ".conf"); if (!config_file) return log_oom(); r = config_parse_standard_file_with_dropins_full( - arg_root, + c->root, /* root_fd= */ -EBADF, config_file, sections, @@ -224,20 +283,64 @@ static int parse_config_file(ImageClass image_class) { if (r < 0) return r; - /* Because this runs after parse_argv we only overwrite when things aren't set yet. */ - if (!arg_mutable_set) { - arg_mutable = config_mutable; - arg_mutable_set = true; + /* Configuration has the lowest priority, so only fill in values that neither the environment nor + * argv initialized already. */ + if (!c->mutable_set) { + c->mutable = config_mutable; + c->mutable_set = true; } - if (!arg_image_policy_set) { - arg_image_policy = TAKE_PTR(config_image_policy); - arg_image_policy_set = true; + if (!c->image_policy_set) { + c->image_policy = TAKE_PTR(config_image_policy); + c->image_policy_set = true; + c->image_policy_owned = true; } return 0; } +static int context_from_cmdline(Context *ret, ImageClass image_class) { + _cleanup_(context_done) Context c = CONTEXT_NULL; + int r; + + assert(ret); + + /* Start with command-line settings shared by both image classes. Class-specific environment and + * configuration are applied below, with argv values explicitly overriding both. */ + c.image_class = image_class; + c.force = arg_force; + c.no_reload = arg_no_reload; + c.always_refresh = arg_always_refresh; + c.noexec = arg_noexec; + + if (arg_root && strdup_to(&c.root, arg_root) < 0) + return log_oom(); + + r = parse_env_extension_hierarchies(&c.hierarchies, image_class_info[image_class].name_env); + if (r < 0) + return log_error_errno(r, "Failed to determine %s hierarchies: %m", image_class_info[image_class].short_identifier); + + parse_env_image_class_config(&c); + + if (arg_mutable_argv_set) { + c.mutable = arg_mutable; + c.mutable_set = true; + } + + if (arg_image_policy_argv_set) { + c.image_policy = arg_image_policy; + c.image_policy_set = true; + c.image_policy_owned = false; + } + + r = parse_config_file(&c); + if (r < 0) + log_warning_errno(r, "Failed to parse %s config file, ignoring: %m", image_class_info[image_class].short_identifier); + + *ret = TAKE_GENERIC(c, Context, CONTEXT_NULL); + return 0; +} + static int is_our_mount_point( ImageClass image_class, const char *p) { @@ -326,9 +429,7 @@ static int split_unit_string(const char *s, const char *field, const char *exten } static int get_extension_release_metadata( - ImageClass image_class, - char **hierarchies, - bool no_reload, + const Context *c, bool *ret_need_reload, Set **ret_restart_units, Set **ret_reload_or_restart_units) { @@ -339,7 +440,9 @@ static int get_extension_release_metadata( bool need_to_reload = false; int r; - if (no_reload || !isempty(arg_root)) { + assert(c); + + if (c->no_reload || !isempty(c->root)) { /* With --root= we'd be talking to the host service manager about extensions merged * into a foreign root, which is never what we want. */ if (ret_need_reload) @@ -351,27 +454,27 @@ static int get_extension_release_metadata( return 0; } - STRV_FOREACH(p, hierarchies) { + STRV_FOREACH(p, c->hierarchies) { _cleanup_free_ char *f = NULL, *buf = NULL, *resolved = NULL; _cleanup_strv_free_ char **mounted_extensions = NULL; - r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL); + r = chase(*p, c->root, CHASE_PREFIX_ROOT, &resolved, NULL); if (r == -ENOENT) { - log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p); + log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(c->root), *p); continue; } if (r < 0) { - log_warning_errno(r, "Failed to resolve path to hierarchy '%s%s': %m, ignoring.", strempty(arg_root), *p); + log_warning_errno(r, "Failed to resolve path to hierarchy '%s%s': %m, ignoring.", strempty(c->root), *p); continue; } - r = is_our_mount_point(image_class, resolved); + r = is_our_mount_point(c->image_class, resolved); if (r < 0) return r; if (!r) continue; - f = path_join(resolved, image_class_info[image_class].dot_directory_name, image_class_info[image_class].short_identifier_plural); + f = path_join(resolved, image_class_info[c->image_class].dot_directory_name, image_class_info[c->image_class].short_identifier_plural); if (!f) return log_oom(); @@ -387,7 +490,7 @@ static int get_extension_release_metadata( _cleanup_strv_free_ char **extension_release = NULL; const char *value; - r = load_extension_release_pairs(arg_root, image_class, *extension, /* relax_extension_release_check= */ true, &extension_release); + r = load_extension_release_pairs(c->root, c->image_class, *extension, /* relax_extension_release_check= */ true, &extension_release); if (r < 0) { log_debug_errno(r, "Failed to parse extension-release metadata of %s, ignoring: %m", *extension); continue; @@ -770,40 +873,8 @@ static OverlayFSPaths *overlayfs_paths_free(OverlayFSPaths *op) { } DEFINE_TRIVIAL_CLEANUP_FUNC(OverlayFSPaths *, overlayfs_paths_free); -static int parse_env(void) { - const char *env_var; - int r; - - env_var = secure_getenv(image_class_info[arg_image_class].mode_env); - if (env_var) { - r = parse_mutable_mode(env_var); - if (r < 0) - log_warning("Failed to parse %s environment variable value '%s'. Ignoring.", - image_class_info[arg_image_class].mode_env, env_var); - else { - arg_mutable = r; - arg_mutable_set = true; - } - } - - env_var = secure_getenv(image_class_info[arg_image_class].opts_env); - if (env_var) - arg_overlayfs_mount_options = env_var; - - /* For debugging purposes it might make sense to do this for other hierarchies than /usr/ and - * /opt/, but let's make that a hacker/debugging feature, i.e. env var instead of cmdline - * switch. */ - r = parse_env_extension_hierarchies(&arg_hierarchies, image_class_info[arg_image_class].name_env); - if (r < 0) - return log_error_errno(r, "Failed to parse %s environment variable: %m", image_class_info[arg_image_class].name_env); - - return 0; -} - -static int parse_image_class_parameter(sd_varlink *link, const char *value, ImageClass *image_class, char ***hierarchies) { - _cleanup_strv_free_ char **h = NULL; +static int parse_image_class_parameter(sd_varlink *link, const char *value, ImageClass *image_class) { ImageClass c; - int r; assert(link); assert(image_class); @@ -815,26 +886,19 @@ static int parse_image_class_parameter(sd_varlink *link, const char *value, Imag if (!IN_SET(c, IMAGE_SYSEXT, IMAGE_CONFEXT)) return sd_varlink_error_invalid_parameter_name(link, "class"); - if (hierarchies) { - r = parse_env_extension_hierarchies(&h, image_class_info[c].name_env); - if (r < 0) - return log_error_errno(r, "Failed to parse environment variable: %m"); - - strv_free_and_replace(*hierarchies, h); - } - *image_class = c; return 0; } -static int resolve_hierarchy(const char *hierarchy, char **ret_resolved_hierarchy) { +static int resolve_hierarchy(const Context *c, const char *hierarchy, char **ret_resolved_hierarchy) { _cleanup_free_ char *resolved_path = NULL; int r; + assert(c); assert(hierarchy); assert(ret_resolved_hierarchy); - r = chase(hierarchy, arg_root, CHASE_PREFIX_ROOT, &resolved_path, NULL); + r = chase(hierarchy, c->root, CHASE_PREFIX_ROOT, &resolved_path, NULL); if (r < 0 && r != -ENOENT) return log_error_errno(r, "Failed to resolve hierarchy '%s': %m", hierarchy); @@ -871,27 +935,31 @@ static int mutable_directory_mode_matches_hierarchy( } static int resolve_mutable_directory( + const Context *c, const char *hierarchy, mode_t hierarchy_mode, const char *workspace, char **ret_resolved_mutable_directory) { _cleanup_free_ char *path = NULL, *resolved_path = NULL, *dir_name = NULL; - const char *root = arg_root, *base = MUTABLE_EXTENSIONS_BASE_DIR; + const char *root, *base = MUTABLE_EXTENSIONS_BASE_DIR; int r; + assert(c); assert(hierarchy); assert(ret_resolved_mutable_directory); - if (arg_mutable == MUTABLE_NO) { + root = c->root; + + if (c->mutable == MUTABLE_NO) { log_debug("Mutability for hierarchy '%s' is disabled, not resolving mutable directory.", hierarchy); *ret_resolved_mutable_directory = NULL; return 0; } - if (IN_SET(arg_mutable, MUTABLE_EPHEMERAL, MUTABLE_EPHEMERAL_IMPORT)) { + if (IN_SET(c->mutable, MUTABLE_EPHEMERAL, MUTABLE_EPHEMERAL_IMPORT)) { /* We create mutable directory inside the temporary tmpfs workspace, which is a fixed - * location that ignores arg_root. */ + * location that ignores the configured root. */ root = NULL; base = workspace; } @@ -904,7 +972,7 @@ static int resolve_mutable_directory( if (!path) return log_oom(); - if (IN_SET(arg_mutable, MUTABLE_YES, MUTABLE_AUTO)) { + if (IN_SET(c->mutable, MUTABLE_YES, MUTABLE_AUTO)) { /* If there already is a mutable directory, check if its mode matches hierarchy. Merged * hierarchy will have the same mode as the mutable directory, so we want no surprising mode * changes here. */ @@ -913,7 +981,7 @@ static int resolve_mutable_directory( return r; } - if (IN_SET(arg_mutable, MUTABLE_YES, MUTABLE_EPHEMERAL, MUTABLE_EPHEMERAL_IMPORT)) { + if (IN_SET(c->mutable, MUTABLE_YES, MUTABLE_EPHEMERAL, MUTABLE_EPHEMERAL_IMPORT)) { _cleanup_close_ int path_fd = -EBADF, chmod_fd = -EBADF; /* This also creates, e.g., /var/lib/extensions.mutable/usr if needed and all parent @@ -943,20 +1011,21 @@ static int resolve_mutable_directory( return 0; } -static int overlayfs_paths_new(const char *hierarchy, const char *workspace_path, OverlayFSPaths **ret_op) { +static int overlayfs_paths_new(const Context *c, const char *hierarchy, const char *workspace_path, OverlayFSPaths **ret_op) { _cleanup_free_ char *hierarchy_copy = NULL, *resolved_hierarchy = NULL, *resolved_mutable_directory = NULL; mode_t hierarchy_mode; int r; - assert (hierarchy); - assert (ret_op); + assert(c); + assert(hierarchy); + assert(ret_op); hierarchy_copy = strdup(hierarchy); if (!hierarchy_copy) return log_oom(); - r = resolve_hierarchy(hierarchy, &resolved_hierarchy); + r = resolve_hierarchy(c, hierarchy, &resolved_hierarchy); if (r < 0) return r; @@ -969,7 +1038,7 @@ static int overlayfs_paths_new(const char *hierarchy, const char *workspace_path } else hierarchy_mode = 0755; - r = resolve_mutable_directory(hierarchy, hierarchy_mode, workspace_path, &resolved_mutable_directory); + r = resolve_mutable_directory(c, hierarchy, hierarchy_mode, workspace_path, &resolved_mutable_directory); if (r < 0) return r; @@ -1028,15 +1097,16 @@ static int determine_used_extensions(const char *hierarchy, char **paths, char * return 0; } -static int maybe_import_mutable_directory(OverlayFSPaths *op) { +static int maybe_import_mutable_directory(const Context *c, OverlayFSPaths *op) { int r; + assert(c); assert(op); /* If importing mutable layer and it actually exists and is not a hierarchy itself, add it just below * the meta path */ - if (arg_mutable != MUTABLE_IMPORT || !op->resolved_mutable_directory) + if (c->mutable != MUTABLE_IMPORT || !op->resolved_mutable_directory) return 0; r = path_equal_or_inode_same_full(op->resolved_hierarchy, op->resolved_mutable_directory, 0); @@ -1052,15 +1122,16 @@ static int maybe_import_mutable_directory(OverlayFSPaths *op) { return 0; } -static int maybe_import_ignored_mutable_directory(OverlayFSPaths *op) { +static int maybe_import_ignored_mutable_directory(const Context *c, OverlayFSPaths *op) { _cleanup_free_ char *dir_name = NULL, *path = NULL, *resolved_path = NULL; int r; + assert(c); assert(op); /* If importing the ignored mutable layer and it actually exists and is not a hierarchy itself, add * it just below the meta path */ - if (arg_mutable != MUTABLE_EPHEMERAL_IMPORT) + if (c->mutable != MUTABLE_EPHEMERAL_IMPORT) return 0; dir_name = hierarchy_as_single_path_component(op->hierarchy); @@ -1071,7 +1142,7 @@ static int maybe_import_ignored_mutable_directory(OverlayFSPaths *op) { if (!path) return log_oom(); - r = chase(path, arg_root, CHASE_PREFIX_ROOT, &resolved_path, NULL); + r = chase(path, c->root, CHASE_PREFIX_ROOT, &resolved_path, NULL); if (r == -ENOENT) { log_debug("Mutable directory for %s does not exist, not importing", op->hierarchy); return 0; @@ -1093,9 +1164,10 @@ static int maybe_import_ignored_mutable_directory(OverlayFSPaths *op) { return 0; } -static int determine_top_lower_dirs(OverlayFSPaths *op, const char *meta_path) { +static int determine_top_lower_dirs(const Context *c, OverlayFSPaths *op, const char *meta_path) { int r; + assert(c); assert(op); assert(meta_path); @@ -1104,11 +1176,11 @@ static int determine_top_lower_dirs(OverlayFSPaths *op, const char *meta_path) { if (r < 0) return log_oom(); - r = maybe_import_mutable_directory(op); + r = maybe_import_mutable_directory(c, op); if (r < 0) return r; - r = maybe_import_ignored_mutable_directory(op); + r = maybe_import_ignored_mutable_directory(c, op); if (r < 0) return r; @@ -1125,11 +1197,12 @@ static int determine_middle_lower_dirs(OverlayFSPaths *op, char **paths) { return 0; } -static int hierarchy_as_lower_dir(OverlayFSPaths *op) { +static int hierarchy_as_lower_dir(const Context *c, OverlayFSPaths *op) { int r; /* return 0 if hierarchy should be used as lower dir, >0, if not */ + assert(c); assert(op); if (!op->resolved_hierarchy) { @@ -1145,12 +1218,12 @@ static int hierarchy_as_lower_dir(OverlayFSPaths *op) { return 1; } - if (arg_mutable == MUTABLE_IMPORT) { + if (c->mutable == MUTABLE_IMPORT) { log_debug("Mutability for host hierarchy '%s' is disabled, so host hierarchy will be a lowerdir", op->resolved_hierarchy); return 0; } - if (arg_mutable == MUTABLE_EPHEMERAL_IMPORT) { + if (c->mutable == MUTABLE_EPHEMERAL_IMPORT) { log_debug("Mutability for host hierarchy '%s' is ephemeral, so host hierarchy will be a lowerdir", op->resolved_hierarchy); return 0; } @@ -1171,13 +1244,14 @@ static int hierarchy_as_lower_dir(OverlayFSPaths *op) { return 0; } -static int determine_bottom_lower_dirs(OverlayFSPaths *op, dev_t *ret_backing_devnum) { +static int determine_bottom_lower_dirs(const Context *c, OverlayFSPaths *op, dev_t *ret_backing_devnum) { int r; + assert(c); assert(op); assert(ret_backing_devnum); - r = hierarchy_as_lower_dir(op); + r = hierarchy_as_lower_dir(c, op); if (r < 0) return r; if (!r) { @@ -1194,6 +1268,7 @@ static int determine_bottom_lower_dirs(OverlayFSPaths *op, dev_t *ret_backing_de } static int determine_lower_dirs( + const Context *c, OverlayFSPaths *op, char **paths, const char *meta_path, @@ -1201,11 +1276,12 @@ static int determine_lower_dirs( int r; + assert(c); assert(op); assert(meta_path); assert(ret_backing_devnum); - r = determine_top_lower_dirs(op, meta_path); + r = determine_top_lower_dirs(c, op, meta_path); if (r < 0) return r; @@ -1213,20 +1289,21 @@ static int determine_lower_dirs( if (r < 0) return r; - r = determine_bottom_lower_dirs(op, ret_backing_devnum); + r = determine_bottom_lower_dirs(c, op, ret_backing_devnum); if (r < 0) return r; return 0; } -static int determine_upper_dir(OverlayFSPaths *op) { +static int determine_upper_dir(const Context *c, OverlayFSPaths *op) { int r; + assert(c); assert(op); assert(!op->upper_dir); - if (arg_mutable == MUTABLE_IMPORT) { + if (c->mutable == MUTABLE_IMPORT) { log_debug("Mutability is disabled, there will be no upperdir for host hierarchy '%s'", op->hierarchy); return 0; } @@ -1251,17 +1328,18 @@ static int determine_upper_dir(OverlayFSPaths *op) { return 0; } -static int determine_work_dir(OverlayFSPaths *op) { +static int determine_work_dir(const Context *c, OverlayFSPaths *op) { _cleanup_free_ char *work_dir = NULL; int r; + assert(c); assert(op); assert(!op->work_dir); if (!op->upper_dir) return 0; - if (arg_mutable == MUTABLE_IMPORT) + if (c->mutable == MUTABLE_IMPORT) return 0; r = work_dir_for_hierarchy(op->hierarchy, op->upper_dir, &work_dir); @@ -1454,25 +1532,26 @@ static int write_backing_file(ImageClass image_class, const char *meta_path, con return 0; } -static int write_work_dir_file(ImageClass image_class, const char *meta_path, const char *work_dir, const char* hierarchy) { +static int write_work_dir_file(const Context *c, const char *meta_path, const char *work_dir, const char* hierarchy) { _cleanup_free_ char *escaped_work_dir_in_root = NULL, *f = NULL; char *work_dir_in_root = NULL; int r; + assert(c); assert(meta_path); if (!work_dir) return 0; /* Do not store work dir path for ephemeral mode, it will be gone once this process is done. */ - if (IN_SET(arg_mutable, MUTABLE_EPHEMERAL, MUTABLE_EPHEMERAL_IMPORT)) + if (IN_SET(c->mutable, MUTABLE_EPHEMERAL, MUTABLE_EPHEMERAL_IMPORT)) return 0; - work_dir_in_root = path_startswith(work_dir, empty_to_root(arg_root)); + work_dir_in_root = path_startswith(work_dir, empty_to_root(c->root)); if (!work_dir_in_root) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Workdir '%s' must not be outside root '%s'", work_dir, empty_to_root(arg_root)); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Workdir '%s' must not be outside root '%s'", work_dir, empty_to_root(c->root)); - f = path_join(meta_path, image_class_info[image_class].dot_directory_name, "work_dir"); + f = path_join(meta_path, image_class_info[c->image_class].dot_directory_name, "work_dir"); if (!f) return log_oom(); @@ -1482,7 +1561,7 @@ static int write_work_dir_file(ImageClass image_class, const char *meta_path, co if (!escaped_work_dir_in_root) return log_oom(); - _cleanup_free_ char *hierarchy_path = path_join(hierarchy, image_class_info[image_class].dot_directory_name, "work_dir"); + _cleanup_free_ char *hierarchy_path = path_join(hierarchy, image_class_info[c->image_class].dot_directory_name, "work_dir"); if (!hierarchy_path) return log_oom(); @@ -1494,7 +1573,7 @@ static int write_work_dir_file(ImageClass image_class, const char *meta_path, co } static int store_info_in_meta( - ImageClass image_class, + const Context *c, char **extensions, const char *origin_content, const char *meta_path, @@ -1505,11 +1584,12 @@ static int store_info_in_meta( int r; + assert(c); assert(meta_path); assert(overlay_path); /* work_dir may be NULL */ - _cleanup_free_ char *f = path_join(meta_path, image_class_info[image_class].dot_directory_name); + _cleanup_free_ char *f = path_join(meta_path, image_class_info[c->image_class].dot_directory_name); if (!f) return log_oom(); @@ -1525,23 +1605,23 @@ static int store_info_in_meta( if (r < 0) return log_error_errno(r, "Failed to fix SELinux label for '%s': %m", hierarchy); - r = write_extensions_file(image_class, extensions, meta_path, hierarchy); + r = write_extensions_file(c->image_class, extensions, meta_path, hierarchy); if (r < 0) return r; - r = write_origin_file(image_class, origin_content, meta_path, hierarchy); + r = write_origin_file(c->image_class, origin_content, meta_path, hierarchy); if (r < 0) return r; - r = write_dev_file(image_class, meta_path, overlay_path, hierarchy); + r = write_dev_file(c->image_class, meta_path, overlay_path, hierarchy); if (r < 0) return r; - r = write_work_dir_file(image_class, meta_path, work_dir, hierarchy); + r = write_work_dir_file(c, meta_path, work_dir, hierarchy); if (r < 0) return r; - r = write_backing_file(image_class, meta_path, overlay_path, hierarchy, backing); + r = write_backing_file(c->image_class, meta_path, overlay_path, hierarchy, backing); if (r < 0) return r; @@ -1586,9 +1666,8 @@ static int make_mounts_read_only(ImageClass image_class, const char *overlay_pat } static int merge_hierarchy( - ImageClass image_class, + const Context *c, const char *hierarchy, - int noexec, char **extensions, char **paths, const char *origin_content, @@ -1601,6 +1680,7 @@ static int merge_hierarchy( size_t extensions_used = 0; int r; + assert(c); assert(hierarchy); assert(paths); assert(meta_path); @@ -1613,35 +1693,35 @@ static int merge_hierarchy( if (r < 0) return r; - if (extensions_used == 0 && arg_mutable == MUTABLE_NO) /* No extension with files in this hierarchy? Then don't do anything. */ + if (extensions_used == 0 && c->mutable == MUTABLE_NO) /* No extension with files in this hierarchy? Then don't do anything. */ return 0; - r = overlayfs_paths_new(hierarchy, workspace_path, &op); + r = overlayfs_paths_new(c, hierarchy, workspace_path, &op); if (r < 0) return r; dev_t backing; - r = determine_lower_dirs(op, used_paths, meta_path, &backing); + r = determine_lower_dirs(c, op, used_paths, meta_path, &backing); if (r < 0) return r; - r = determine_upper_dir(op); + r = determine_upper_dir(c, op); if (r < 0) return r; - r = determine_work_dir(op); + r = determine_work_dir(c, op); if (r < 0) return r; - r = mount_overlayfs_with_op(op, image_class, noexec, overlay_path, meta_path, arg_overlayfs_mount_options); + r = mount_overlayfs_with_op(op, c->image_class, c->noexec, overlay_path, meta_path, c->overlayfs_mount_options); if (r < 0) return r; - r = store_info_in_meta(image_class, extensions, origin_content, meta_path, overlay_path, op->work_dir, op->hierarchy, backing); + r = store_info_in_meta(c, extensions, origin_content, meta_path, overlay_path, op->work_dir, op->hierarchy, backing); if (r < 0) return r; - r = make_mounts_read_only(image_class, overlay_path, op->upper_dir && op->work_dir); + r = make_mounts_read_only(c->image_class, overlay_path, op->upper_dir && op->work_dir); if (r < 0) return r; @@ -1656,21 +1736,22 @@ static int strverscmp_improvedp(char *const* a, char *const* b) { return strverscmp_improved(*a, *b); } -static const ImagePolicy* pick_image_policy(const Image *img) { +static const ImagePolicy* pick_image_policy(const Context *c, const Image *img) { + assert(c); assert(img); assert(img->path); /* Explicitly specified policy always wins */ - if (arg_image_policy) - return arg_image_policy; + if (c->image_policy) + return c->image_policy; /* If located in /.extra/ in the initrd, then it was placed there by systemd-stub, and was * picked up from an untrusted ESP. Thus, require a stricter policy by default for them. (For the * other directories we assume the appropriate level of trust was already established.) * With --root= we default to the regular policy, though. (To change that, the check would need - * to prepend (or cut away) arg_root.) */ + * to prepend (or cut away) c->root.) */ - if (in_initrd() && !arg_root) { + if (in_initrd() && !c->root) { if (path_startswith(img->path, "/.extra/sysext/")) return &image_policy_sysext_strict; if (path_startswith(img->path, "/.extra/global_sysext/")) @@ -1688,14 +1769,15 @@ static const ImagePolicy* pick_image_policy(const Image *img) { return image_class_info[img->class].default_image_policy; } -static int unmerge_hierarchy(ImageClass image_class, const char *p, const char *submounts_path) { +static int unmerge_hierarchy(const Context *c, const char *p, const char *submounts_path) { _cleanup_free_ char *dot_dir = NULL, *work_dir_info_file = NULL; int n_unmerged = 0; int r; + assert(c); assert(p); - dot_dir = path_join(p, image_class_info[image_class].dot_directory_name); + dot_dir = path_join(p, image_class_info[c->image_class].dot_directory_name); if (!dot_dir) return log_oom(); @@ -1709,7 +1791,7 @@ static int unmerge_hierarchy(ImageClass image_class, const char *p, const char * /* We only unmount /usr/ if it is a mount point and really one of ours, in order not to break * systems where /usr/ is a mount point of its own already. */ - r = is_our_mount_point(image_class, p); + r = is_our_mount_point(c->image_class, p); if (r < 0) return r; if (r == 0) @@ -1726,7 +1808,7 @@ static int unmerge_hierarchy(ImageClass image_class, const char *p, const char * l = cunescape_length(escaped_work_dir_in_root, r, 0, &work_dir_in_root); if (l < 0) return log_error_errno(l, "Failed to unescape work directory path: %m"); - work_dir = path_join(arg_root, work_dir_in_root); + work_dir = path_join(c->root, work_dir_in_root); if (!work_dir) return log_oom(); } @@ -1764,12 +1846,12 @@ static int unmerge_hierarchy(ImageClass image_class, const char *p, const char * } static int unmerge_subprocess( - ImageClass image_class, - char **hierarchies, + const Context *c, const char *workspace) { int r, ret = 0; + assert(c); assert(workspace); assert(path_startswith(workspace, "/run/")); @@ -1784,24 +1866,24 @@ static int unmerge_subprocess( if (r < 0) return log_error_errno(r, "Failed to create '%s': %m", workspace); - STRV_FOREACH(h, hierarchies) { + STRV_FOREACH(h, c->hierarchies) { _cleanup_free_ char *submounts_path = NULL, *resolved = NULL; submounts_path = path_join(workspace, "submounts", *h); if (!submounts_path) return log_oom(); - r = chase(*h, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL); + r = chase(*h, c->root, CHASE_PREFIX_ROOT, &resolved, NULL); if (r == -ENOENT) { - log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *h); + log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(c->root), *h); continue; } if (r < 0) { - RET_GATHER(ret, log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root), *h)); + RET_GATHER(ret, log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(c->root), *h)); continue; } - r = unmerge_hierarchy(image_class, resolved, submounts_path); + r = unmerge_hierarchy(c, resolved, submounts_path); if (r < 0) { RET_GATHER(ret, r); continue; @@ -1820,18 +1902,17 @@ static int unmerge_subprocess( return ret; } -static int unmerge( - ImageClass image_class, - char **hierarchies, - bool no_reload) { +static int unmerge(const Context *c) { _cleanup_set_free_ Set *units_to_restart = NULL, *units_to_reload_or_restart = NULL; bool need_to_reload; int r; + assert(c); + (void) DLOPEN_LIBMOUNT(LOG_DEBUG, required); - r = get_extension_release_metadata(image_class, hierarchies, no_reload, &need_to_reload, &units_to_restart, &units_to_reload_or_restart); + r = get_extension_release_metadata(c, &need_to_reload, &units_to_restart, &units_to_reload_or_restart); if (r < 0) return r; @@ -1844,7 +1925,7 @@ static int unmerge( if (r == 0) { /* Child with its own mount namespace */ - r = unmerge_subprocess(image_class, hierarchies, "/run/systemd/sysext"); + r = unmerge_subprocess(c, "/run/systemd/sysext"); /* Our namespace ceases to exist here, also implicitly detaching all temporary mounts we * created below /run. Nice! */ @@ -1867,11 +1948,7 @@ static int unmerge( } static int merge_subprocess( - ImageClass image_class, - char **hierarchies, - bool force, - bool always_refresh, - int noexec, + const Context *c, Hashmap *images, const char *workspace) { @@ -1887,10 +1964,12 @@ static int merge_subprocess( Image *img; int r; - if (!isempty(arg_root)) { - r = chase(arg_root, /* root= */ NULL, CHASE_MUST_BE_DIRECTORY, &root_resolved, /* ret_fd= */ NULL); + assert(c); + + if (!isempty(c->root)) { + r = chase(c->root, /* root= */ NULL, CHASE_MUST_BE_DIRECTORY, &root_resolved, /* ret_fd= */ NULL); if (r < 0) - return log_error_errno(r, "Failed to resolve --root='%s': %m", strempty(arg_root)); + return log_error_errno(r, "Failed to resolve --root='%s': %m", strempty(c->root)); } assert(path_startswith(workspace, "/run/")); @@ -1910,30 +1989,30 @@ static int merge_subprocess( * but let the kernel do that entirely automatically, once our namespace dies. Note that this file * system won't be visible to anyone but us, since we opened our own namespace and then made the * /run/ hierarchy (which our workspace is contained in) MS_SLAVE, see above. */ - r = mount_nofollow_verbose(LOG_ERR, image_class_info[image_class].short_identifier, workspace, "tmpfs", 0, "mode=0700"); + r = mount_nofollow_verbose(LOG_ERR, image_class_info[c->image_class].short_identifier, workspace, "tmpfs", 0, "mode=0700"); if (r < 0) return r; /* Acquire host OS release info, so that we can compare it with the extension's data */ r = parse_os_release( - arg_root, + c->root, "ID", &host_os_release_id, "ID_LIKE", &host_os_release_id_like, "VERSION_ID", &host_os_release_version_id, - image_class_info[image_class].level_env, &host_os_release_api_level); + image_class_info[c->image_class].level_env, &host_os_release_api_level); if (r < 0) - return log_error_errno(r, "Failed to acquire 'os-release' data of OS tree '%s': %m", empty_to_root(arg_root)); + return log_error_errno(r, "Failed to acquire 'os-release' data of OS tree '%s': %m", empty_to_root(c->root)); if (isempty(host_os_release_id)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "'ID' field not found or empty in 'os-release' data of OS tree '%s'.", - empty_to_root(arg_root)); + empty_to_root(c->root)); /* Let's now mount all images */ HASHMAP_FOREACH(img, images) { _cleanup_free_ char *p = NULL, *path_without_root = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *verity_hash = NULL; - p = path_join(workspace, image_class_info[image_class].short_identifier_plural, img->name); + p = path_join(workspace, image_class_info[c->image_class].short_identifier_plural, img->name); if (!p) return log_oom(); @@ -1945,7 +2024,7 @@ static int merge_subprocess( case IMAGE_DIRECTORY: case IMAGE_SUBVOLUME: - if (!force) { + if (!c->force) { r = extension_has_forbidden_content(p); if (r < 0) return r; @@ -1988,7 +2067,7 @@ static int merge_subprocess( if (verity_settings.data_path) flags |= DISSECT_IMAGE_NO_PARTITION_TABLE; - if (!force) + if (!c->force) flags |= DISSECT_IMAGE_VALIDATE_OS_EXT; r = loop_device_make_by_path( @@ -2005,7 +2084,7 @@ static int merge_subprocess( d, &verity_settings, /* mount_options= */ NULL, - pick_image_policy(img), + pick_image_policy(c, img), /* image_filter= */ NULL, flags, &m); @@ -2036,7 +2115,7 @@ static int merge_subprocess( return log_error_errno(r, "Failed to create origin verity entry for '%s': %m", img->name); } - r = dissected_image_decrypt(m, arg_root, /* passphrase= */ NULL, &verity_settings, pick_image_policy(img), flags); + r = dissected_image_decrypt(m, c->root, /* passphrase= */ NULL, &verity_settings, pick_image_policy(c, img), flags); if (r < 0) return r; @@ -2049,7 +2128,7 @@ static int merge_subprocess( flags); if (r < 0 && r != -ENOMEDIUM) return r; - if (r == -ENOMEDIUM && !force) { + if (r == -ENOMEDIUM && !c->force) { n_ignored++; continue; } @@ -2063,11 +2142,11 @@ static int merge_subprocess( assert_not_reached(); } - if (force) + if (c->force) log_debug("Force mode enabled, skipping version validation."); else { bool is_initrd; - r = chase_and_access("/etc/initrd-release", arg_root, CHASE_PREFIX_ROOT, F_OK, /* ret_path= */ NULL); + r = chase_and_access("/etc/initrd-release", c->root, CHASE_PREFIX_ROOT, F_OK, /* ret_path= */ NULL); if (r < 0 && r != -ENOENT) return log_error_errno(r, "Failed to check for /etc/initrd-release: %m"); is_initrd = r >= 0; @@ -2079,8 +2158,8 @@ static int merge_subprocess( host_os_release_version_id, host_os_release_api_level, is_initrd ? "initrd" : "system", - image_extension_release(img, image_class), - image_class); + image_extension_release(img, c->image_class), + c->image_class); if (r < 0) return r; if (r == 0) { @@ -2108,7 +2187,7 @@ static int merge_subprocess( /* Encode extension image origin to check if we can skip the refresh. * It can also be used to provide more detail in "systemd-sysext status". */ - if (!isempty(arg_root)) { + if (!isempty(c->root)) { const char *without_root = NULL; without_root = path_startswith(img->path, root_resolved); if (!isempty(without_root)) { @@ -2163,7 +2242,7 @@ static int merge_subprocess( } /* Nothing left? Then shortcut things */ - if (n_extensions == 0 && arg_mutable == MUTABLE_NO) { + if (n_extensions == 0 && c->mutable == MUTABLE_NO) { if (n_ignored > 0) log_info("No suitable extensions found (%u ignored due to incompatible image(s)).", n_ignored); else @@ -2175,16 +2254,16 @@ static int merge_subprocess( typesafe_qsort(extensions, n_extensions, strverscmp_improvedp); typesafe_qsort(extensions_v, n_extensions, strverscmp_improvedp); - STRV_FOREACH(h, hierarchies) { + STRV_FOREACH(h, c->hierarchies) { _cleanup_(overlayfs_paths_freep) OverlayFSPaths *op = NULL; _cleanup_free_ char *f = NULL, *buf = NULL, *resolved = NULL, *mutable_directory_without_root = NULL; /* The origin file includes the backing directories for mutable overlays. */ - r = overlayfs_paths_new(*h, workspace, &op); + r = overlayfs_paths_new(c, *h, workspace, &op); if (r < 0) return r; - if (op->resolved_mutable_directory && !isempty(arg_root)) { + if (op->resolved_mutable_directory && !isempty(c->root)) { const char *without_root = NULL; without_root = path_startswith(op->resolved_mutable_directory, root_resolved); if (!isempty(without_root)) { @@ -2206,15 +2285,15 @@ static int merge_subprocess( } /* Find existing origin file for comparison. */ - r = chase(*h, arg_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, /* ret_fd= */ NULL); + r = chase(*h, c->root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, /* ret_fd= */ NULL); if (r < 0) - return log_error_errno(r, "Failed to resolve hierarchy '%s%s': %m", strempty(arg_root), *h); + return log_error_errno(r, "Failed to resolve hierarchy '%s%s': %m", strempty(c->root), *h); - f = path_join(resolved, image_class_info[image_class].dot_directory_name, "origin"); + f = path_join(resolved, image_class_info[c->image_class].dot_directory_name, "origin"); if (!f) return log_oom(); - r = is_our_mount_point(image_class, resolved); + r = is_our_mount_point(c->image_class, resolved); if (r < 0) return r; if (r == 0) @@ -2234,13 +2313,13 @@ static int merge_subprocess( r = sd_json_buildo(&extensions_origin_json, SD_JSON_BUILD_PAIR_OBJECT("mutable", - SD_JSON_BUILD_PAIR_STRING("mode", mutable_mode_to_string(arg_mutable)), + SD_JSON_BUILD_PAIR_STRING("mode", mutable_mode_to_string(c->mutable)), SD_JSON_BUILD_PAIR_CONDITION(!!mutable_dir_entries, "mutableDirs", SD_JSON_BUILD_VARIANT(mutable_dir_entries))), - SD_JSON_BUILD_PAIR_CONDITION(!isempty(arg_overlayfs_mount_options), + SD_JSON_BUILD_PAIR_CONDITION(!isempty(c->overlayfs_mount_options), "mountOptions", - SD_JSON_BUILD_STRING(arg_overlayfs_mount_options)), + SD_JSON_BUILD_STRING(c->overlayfs_mount_options)), SD_JSON_BUILD_PAIR_CONDITION(!!extensions_origin_entries, "extensions", SD_JSON_BUILD_VARIANT(extensions_origin_entries))); @@ -2263,7 +2342,7 @@ static int merge_subprocess( /* This works well with unordered entries. */ if (sd_json_variant_equal(extensions_origin_json, old_origin_json)) { - if (!always_refresh) { + if (!c->always_refresh) { /* This only happens during refresh, not merge, thus talk about refresh here. */ log_info("Skipping extension refresh because no change was found, use --always-refresh=yes to always do a refresh."); return MERGE_SKIP_REFRESH; @@ -2275,7 +2354,7 @@ static int merge_subprocess( } if (n_extensions == 0) { - assert(arg_mutable != MUTABLE_NO); + assert(c->mutable != MUTABLE_NO); log_info("No extensions found, proceeding in mutable mode."); } else { _cleanup_free_ char *buf = strv_join(extensions_v, "', '"); @@ -2295,7 +2374,7 @@ static int merge_subprocess( assert_se(img = hashmap_get(images, extensions[n_extensions - 1 - k])); - p = path_join(workspace, image_class_info[image_class].short_identifier_plural, img->name); + p = path_join(workspace, image_class_info[c->image_class].short_identifier_plural, img->name); if (!p) return log_oom(); @@ -2304,18 +2383,18 @@ static int merge_subprocess( /* Let's now unmerge the status quo ante, since to build the new overlayfs we need a reference to the * underlying fs. */ - STRV_FOREACH(h, hierarchies) { + STRV_FOREACH(h, c->hierarchies) { _cleanup_free_ char *submounts_path = NULL, *resolved = NULL; submounts_path = path_join(workspace, "submounts", *h); if (!submounts_path) return log_oom(); - r = chase(*h, arg_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL); + r = chase(*h, c->root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL); if (r < 0) - return log_error_errno(r, "Failed to resolve hierarchy '%s%s': %m", strempty(arg_root), *h); + return log_error_errno(r, "Failed to resolve hierarchy '%s%s': %m", strempty(c->root), *h); - r = unmerge_hierarchy(image_class, resolved, submounts_path); + r = unmerge_hierarchy(c, resolved, submounts_path); if (r < 0) return r; if (r > 0) @@ -2330,7 +2409,7 @@ static int merge_subprocess( } /* Create overlayfs mounts for all hierarchies */ - STRV_FOREACH(h, hierarchies) { + STRV_FOREACH(h, c->hierarchies) { _cleanup_free_ char *meta_path = NULL, *overlay_path = NULL, *merge_hierarchy_workspace = NULL, *submounts_path = NULL; meta_path = path_join(workspace, "meta", *h); /* The place where to store metadata about this instance */ @@ -2351,9 +2430,8 @@ static int merge_subprocess( return log_oom(); r = merge_hierarchy( - image_class, + c, *h, - noexec, extensions, paths, extensions_origin_content, @@ -2372,7 +2450,7 @@ static int merge_subprocess( } /* And move them all into place. This is where things appear in the host namespace */ - STRV_FOREACH(h, hierarchies) { + STRV_FOREACH(h, c->hierarchies) { _cleanup_free_ char *p = NULL, *resolved = NULL; p = path_join(workspace, "overlay", *h); @@ -2385,9 +2463,9 @@ static int merge_subprocess( if (r < 0) return log_error_errno(r, "Failed to check if '%s' exists: %m", p); - r = chase(*h, arg_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL); + r = chase(*h, c->root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL); if (r < 0) - return log_error_errno(r, "Failed to resolve hierarchy '%s%s': %m", strempty(arg_root), *h); + return log_error_errno(r, "Failed to resolve hierarchy '%s%s': %m", strempty(c->root), *h); r = mkdir_p(resolved, 0755); if (r < 0) @@ -2404,16 +2482,12 @@ static int merge_subprocess( return MERGE_MOUNTED; } -static int merge(ImageClass image_class, - char **hierarchies, - bool force, - bool no_reload, - bool always_refresh, - int noexec, - Hashmap *images) { +static int merge(const Context *c, Hashmap *images) { int r; + assert(c); + (void) DLOPEN_CRYPTSETUP(LOG_DEBUG, recommended); (void) DLOPEN_LIBBLKID(LOG_DEBUG, required); (void) DLOPEN_LIBMOUNT(LOG_DEBUG, required); @@ -2425,7 +2499,7 @@ static int merge(ImageClass image_class, if (r == 0) { /* Child with its own mount namespace */ - r = merge_subprocess(image_class, hierarchies, force, always_refresh, noexec, images, "/run/systemd/sysext"); + r = merge_subprocess(c, images, "/run/systemd/sysext"); /* Our namespace ceases to exist here, also implicitly detaching all temporary mounts we * created below /run. Nice! */ @@ -2452,7 +2526,7 @@ static int merge(ImageClass image_class, _cleanup_set_free_ Set *units_to_restart = NULL, *units_to_reload_or_restart = NULL; bool need_to_reload; - r = get_extension_release_metadata(image_class, hierarchies, no_reload, &need_to_reload, &units_to_restart, &units_to_reload_or_restart); + r = get_extension_release_metadata(c, &need_to_reload, &units_to_restart, &units_to_reload_or_restart); if (r < 0) return r; if (need_to_reload) { @@ -2471,31 +2545,36 @@ static int merge(ImageClass image_class, VERB_DEFAULT_NOARG(verb_status, "status", "Show current merge status (default)"); static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(context_done) Context c = CONTEXT_NULL; _cleanup_(table_unrefp) Table *t = NULL; int r, ret = 0; + r = context_from_cmdline(&c, arg_image_class); + if (r < 0) + return r; + t = table_new("hierarchy", "extensions", "since"); if (!t) return log_oom(); table_set_ersatz_string(t, TABLE_ERSATZ_DASH); - STRV_FOREACH(p, arg_hierarchies) { + STRV_FOREACH(p, c.hierarchies) { _cleanup_free_ char *resolved = NULL, *f = NULL, *buf = NULL; _cleanup_strv_free_ char **l = NULL; struct stat st; - r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL); + r = chase(*p, c.root, CHASE_PREFIX_ROOT, &resolved, NULL); if (r == -ENOENT) { - log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p); + log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(c.root), *p); continue; } if (r < 0) { - log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root), *p); + log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(c.root), *p); goto inner_fail; } - r = is_our_mount_point(arg_image_class, resolved); + r = is_our_mount_point(c.image_class, resolved); if (r < 0) goto inner_fail; if (r == 0) { @@ -2511,7 +2590,7 @@ static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) continue; } - f = path_join(resolved, image_class_info[arg_image_class].dot_directory_name, image_class_info[arg_image_class].short_identifier_plural); + f = path_join(resolved, image_class_info[c.image_class].dot_directory_name, image_class_info[c.image_class].short_identifier_plural); if (!f) return log_oom(); @@ -2550,19 +2629,20 @@ static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) return ret; } -static int image_discover_and_read_metadata(ImageClass image_class, Hashmap **ret_images) { +static int image_discover_and_read_metadata(const Context *c, Hashmap **ret_images) { _cleanup_hashmap_free_ Hashmap *images = NULL; Image *img; int r; + assert(c); assert(ret_images); - r = image_discover(RUNTIME_SCOPE_SYSTEM, image_class, arg_root, &images); + r = image_discover(RUNTIME_SCOPE_SYSTEM, c->image_class, c->root, &images); if (r < 0) return log_error_errno(r, "Failed to discover images: %m"); HASHMAP_FOREACH(img, images) { - r = image_read_metadata(img, arg_root, image_class_info[image_class].default_image_policy, RUNTIME_SCOPE_SYSTEM); + r = image_read_metadata(img, c->root, image_class_info[c->image_class].default_image_policy, RUNTIME_SCOPE_SYSTEM); if (r < 0) return log_error_errno(r, "Failed to read metadata for image %s: %m", img->name); } @@ -2572,28 +2652,26 @@ static int image_discover_and_read_metadata(ImageClass image_class, Hashmap **re return 0; } -static int look_for_merged_hierarchies( - ImageClass image_class, - char **hierarchies, - const char **ret_which) { +static int look_for_merged_hierarchies(const Context *c, const char **ret_which) { int r; + assert(c); assert(ret_which); /* In merge mode fail if things are already merged. (In --refresh mode below we'll unmerge if we find * things are already merged...) */ - STRV_FOREACH(p, hierarchies) { + STRV_FOREACH(p, c->hierarchies) { _cleanup_free_ char *resolved = NULL; - r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL); + r = chase(*p, c->root, CHASE_PREFIX_ROOT, &resolved, NULL); if (r == -ENOENT) { - log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p); + log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(c->root), *p); continue; } if (r < 0) - return log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root), *p); + return log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(c->root), *p); - r = is_our_mount_point(image_class, resolved); + r = is_our_mount_point(c->image_class, resolved); if (r < 0) return r; if (r > 0) { @@ -2608,6 +2686,7 @@ static int look_for_merged_hierarchies( VERB_NOARG(verb_merge, "merge", "Merge extensions into relevant hierarchies"); static int verb_merge(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(context_done) Context c = CONTEXT_NULL; _cleanup_hashmap_free_ Hashmap *images = NULL; const char *which; int r; @@ -2618,23 +2697,21 @@ static int verb_merge(int argc, char *argv[], uintptr_t _data, void *userdata) { if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged."); - r = image_discover_and_read_metadata(arg_image_class, &images); + r = context_from_cmdline(&c, arg_image_class); if (r < 0) return r; - r = look_for_merged_hierarchies(arg_image_class, arg_hierarchies, &which); + r = image_discover_and_read_metadata(&c, &images); + if (r < 0) + return r; + + r = look_for_merged_hierarchies(&c, &which); if (r < 0) return r; if (r > 0) return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Hierarchy '%s' is already merged.", which); - return merge(arg_image_class, - arg_hierarchies, - arg_force, - arg_no_reload, - arg_always_refresh, - arg_noexec, - images); + return merge(&c, images); } typedef struct MethodMergeParameters { @@ -2666,6 +2743,7 @@ static int parse_merge_parameters(sd_varlink *link, sd_json_variant *parameters, static int vl_method_merge(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { Hashmap **polkit_registry = ASSERT_PTR(userdata); + _cleanup_(context_done) Context c = CONTEXT_NULL; _cleanup_hashmap_free_ Hashmap *images = NULL; MethodMergeParameters p = { .force = -1, @@ -2673,10 +2751,8 @@ static int vl_method_merge(sd_varlink *link, sd_json_variant *parameters, sd_var .always_refresh = -1, .noexec = -1, }; - _cleanup_strv_free_ char **hierarchies = NULL; ImageClass image_class = arg_image_class; - bool force, no_reload, always_refresh; - int r, noexec; + int r; assert(link); @@ -2684,43 +2760,48 @@ static int vl_method_merge(sd_varlink *link, sd_json_variant *parameters, sd_var if (r != 0) return r; - r = parse_image_class_parameter(link, p.class, &image_class, &hierarchies); + r = parse_image_class_parameter(link, p.class, &image_class); if (r < 0) return r; - force = p.force >= 0 ? p.force : arg_force; - no_reload = p.no_reload >= 0 ? p.no_reload : arg_no_reload; - always_refresh = p.always_refresh >= 0 ? p.always_refresh : arg_always_refresh; - noexec = p.noexec >= 0 ? p.noexec : arg_noexec; + r = context_from_cmdline(&c, image_class); + if (r < 0) + return r; + + if (p.force >= 0) + c.force = p.force; + if (p.no_reload >= 0) + c.no_reload = p.no_reload; + if (p.always_refresh >= 0) + c.always_refresh = p.always_refresh; + if (p.noexec >= 0) + c.noexec = p.noexec; r = varlink_verify_polkit_async( link, /* bus= */ NULL, - image_class_info[image_class].polkit_rw_action_id, + image_class_info[c.image_class].polkit_rw_action_id, (const char**) STRV_MAKE( "verb", "merge", - "force", one_zero(force), - "noReload", one_zero(no_reload), - "noexec", one_zero(noexec > 0)), + "force", one_zero(c.force), + "noReload", one_zero(c.no_reload), + "noexec", one_zero(c.noexec > 0)), polkit_registry); if (r <= 0) return r; - r = image_discover_and_read_metadata(image_class, &images); + r = image_discover_and_read_metadata(&c, &images); if (r < 0) return r; const char *which; - r = look_for_merged_hierarchies( - image_class, - hierarchies ?: arg_hierarchies, - &which); + r = look_for_merged_hierarchies(&c, &which); if (r < 0) return r; if (r > 0) return sd_varlink_errorbo(link, "io.systemd.sysext.AlreadyMerged", SD_JSON_BUILD_PAIR_STRING("hierarchy", which)); - r = merge(image_class, hierarchies ?: arg_hierarchies, force, no_reload, always_refresh, noexec, images); + r = merge(&c, images); if (r < 0) return r; @@ -2729,6 +2810,7 @@ static int vl_method_merge(sd_varlink *link, sd_json_variant *parameters, sd_var VERB_NOARG(verb_unmerge, "unmerge", "Unmerge extensions from relevant hierarchies"); static int verb_unmerge(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(context_done) Context c = CONTEXT_NULL; int r; r = have_effective_cap(CAP_SYS_ADMIN); @@ -2737,9 +2819,11 @@ static int verb_unmerge(int argc, char *argv[], uintptr_t _data, void *userdata) if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged."); - return unmerge(arg_image_class, - arg_hierarchies, - arg_no_reload); + r = context_from_cmdline(&c, arg_image_class); + if (r < 0) + return r; + + return unmerge(&c); } typedef struct MethodUnmergeParameters { @@ -2759,9 +2843,8 @@ static int vl_method_unmerge(sd_varlink *link, sd_json_variant *parameters, sd_v .no_reload = -1, }; Hashmap **polkit_registry = ASSERT_PTR(userdata); - _cleanup_strv_free_ char **hierarchies = NULL; + _cleanup_(context_done) Context c = CONTEXT_NULL; ImageClass image_class = arg_image_class; - bool no_reload; int r; assert(link); @@ -2770,42 +2853,43 @@ static int vl_method_unmerge(sd_varlink *link, sd_json_variant *parameters, sd_v if (r != 0) return r; - no_reload = p.no_reload >= 0 ? p.no_reload : arg_no_reload; + r = parse_image_class_parameter(link, p.class, &image_class); + if (r < 0) + return r; - r = parse_image_class_parameter(link, p.class, &image_class, &hierarchies); + r = context_from_cmdline(&c, image_class); if (r < 0) return r; + if (p.no_reload >= 0) + c.no_reload = p.no_reload; + r = varlink_verify_polkit_async( link, /* bus= */ NULL, - image_class_info[image_class].polkit_rw_action_id, + image_class_info[c.image_class].polkit_rw_action_id, (const char**) STRV_MAKE( "verb", "unmerge", - "noReload", one_zero(no_reload)), + "noReload", one_zero(c.no_reload)), polkit_registry); if (r <= 0) return r; - r = unmerge(image_class, hierarchies ?: arg_hierarchies, no_reload); + r = unmerge(&c); if (r < 0) return r; return sd_varlink_reply(link, NULL); } -static int refresh( - ImageClass image_class, - char **hierarchies, - bool force, - bool no_reload, - bool always_refresh, - int noexec) { +static int refresh(const Context *c) { _cleanup_hashmap_free_ Hashmap *images = NULL; int r; - r = image_discover_and_read_metadata(image_class, &images); + assert(c); + + r = image_discover_and_read_metadata(c, &images); if (r < 0) return r; @@ -2813,12 +2897,12 @@ static int refresh( * implicitly unmounts any overlayfs placed there before. It also returns == 1 if there were * no changes found to apply and the mount stays intact. Returns == 0 if it did nothing, i.e. no * extension images found. In this case the old overlayfs remains in place if there was one. */ - r = merge(image_class, hierarchies, force, no_reload, always_refresh, noexec, images); + r = merge(c, images); if (r < 0) return r; if (r == 0) /* No images found? Then unmerge. The goal of --refresh is after all that after having * called there's a guarantee that the merge status matches the installed extensions. */ - r = unmerge(image_class, hierarchies, no_reload); + r = unmerge(c); /* Net result here is that: * @@ -2839,6 +2923,7 @@ static int refresh( VERB_NOARG(verb_refresh, "refresh", "Unmerge/merge extensions again"); static int verb_refresh(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(context_done) Context c = CONTEXT_NULL; int r; r = have_effective_cap(CAP_SYS_ADMIN); @@ -2847,16 +2932,16 @@ static int verb_refresh(int argc, char *argv[], uintptr_t _data, void *userdata) if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged."); - return refresh(arg_image_class, - arg_hierarchies, - arg_force, - arg_no_reload, - arg_always_refresh, - arg_noexec); + r = context_from_cmdline(&c, arg_image_class); + if (r < 0) + return r; + + return refresh(&c); } static int vl_method_refresh(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + _cleanup_(context_done) Context c = CONTEXT_NULL; MethodMergeParameters p = { .force = -1, .no_reload = -1, @@ -2864,10 +2949,8 @@ static int vl_method_refresh(sd_varlink *link, sd_json_variant *parameters, sd_v .noexec = -1, }; Hashmap **polkit_registry = ASSERT_PTR(userdata); - _cleanup_strv_free_ char **hierarchies = NULL; ImageClass image_class = arg_image_class; - bool force, no_reload, always_refresh; - int r, noexec; + int r; assert(link); @@ -2875,29 +2958,37 @@ static int vl_method_refresh(sd_varlink *link, sd_json_variant *parameters, sd_v if (r != 0) return r; - r = parse_image_class_parameter(link, p.class, &image_class, &hierarchies); + r = parse_image_class_parameter(link, p.class, &image_class); + if (r < 0) + return r; + + r = context_from_cmdline(&c, image_class); if (r < 0) return r; - force = p.force >= 0 ? p.force : arg_force; - no_reload = p.no_reload >= 0 ? p.no_reload : arg_no_reload; - always_refresh = p.always_refresh >= 0 ? p.always_refresh : arg_always_refresh; - noexec = p.noexec >= 0 ? p.noexec : arg_noexec; + if (p.force >= 0) + c.force = p.force; + if (p.no_reload >= 0) + c.no_reload = p.no_reload; + if (p.always_refresh >= 0) + c.always_refresh = p.always_refresh; + if (p.noexec >= 0) + c.noexec = p.noexec; r = varlink_verify_polkit_async( link, /* bus= */ NULL, - image_class_info[image_class].polkit_rw_action_id, + image_class_info[c.image_class].polkit_rw_action_id, (const char**) STRV_MAKE( "verb", "refresh", - "force", one_zero(force), - "noReload", one_zero(no_reload), - "noexec", one_zero(noexec > 0)), + "force", one_zero(c.force), + "noReload", one_zero(c.no_reload), + "noexec", one_zero(c.noexec > 0)), polkit_registry); if (r <= 0) return r; - r = refresh(image_class, hierarchies ?: arg_hierarchies, force, no_reload, always_refresh, noexec); + r = refresh(&c); if (r < 0) return r; @@ -2905,14 +2996,16 @@ static int vl_method_refresh(sd_varlink *link, sd_json_variant *parameters, sd_v } static int refresh_class(ImageClass image_class) { - _cleanup_strv_free_ char **hierarchies = NULL; + _cleanup_(context_done) Context c = CONTEXT_NULL; int r; - r = parse_env_extension_hierarchies(&hierarchies, image_class_info[image_class].name_env); + /* The sysupdate notification service is systemd-sysext, but it refreshes both sysexts and + * confexts. Apply the relevant class-specific environment and configuration for each refresh. */ + r = context_from_cmdline(&c, image_class); if (r < 0) - return log_error_errno(r, "Failed to determine %s hierarchies: %m", image_class_info[image_class].short_identifier); + return r; - return refresh(image_class, hierarchies, arg_force, arg_no_reload, arg_always_refresh, arg_noexec); + return refresh(&c); } static int vl_method_on_completed_update(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { @@ -2995,7 +3088,7 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl return r; ImageClass image_class = arg_image_class; - r = parse_image_class_parameter(link, class, &image_class, NULL); + r = parse_image_class_parameter(link, class, &image_class); if (r < 0) return r; @@ -3113,16 +3206,14 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { if (r < 0) return log_error_errno(r, "Failed to parse argument to --mutable=: %s", opts.arg); arg_mutable = r; - arg_mutable_set = true; + arg_mutable_argv_set = true; break; OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): r = parse_image_policy_argument(opts.arg, &arg_image_policy); if (r < 0) return r; - /* When the CLI flag is given we initialize even if NULL - * so that the config file entry won't overwrite it */ - arg_image_policy_set = true; + arg_image_policy_argv_set = true; break; OPTION_LONG("noexec", "BOOL", "Whether to mount extension overlay with noexec"): @@ -3183,11 +3274,6 @@ static int run(int argc, char *argv[]) { arg_image_class = invoked_as(argv, "systemd-confext") ? IMAGE_CONFEXT : IMAGE_SYSEXT; - /* Parse environment variables first */ - r = parse_env(); - if (r < 0) - return r; - /* Parse command line */ r = parse_argv(argc, argv, &args); if (r <= 0) @@ -3209,13 +3295,6 @@ static int run(int argc, char *argv[]) { return 0; } - /* Parse configuration file after argv because it needs --root=. - * The config entries will not overwrite values set already by - * env/argv because we track initialization. */ - r = parse_config_file(arg_image_class); - if (r < 0) - log_warning_errno(r, "Failed to parse global config file, ignoring: %m"); - if (arg_varlink) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; _cleanup_hashmap_free_ Hashmap *polkit_registry = NULL; diff --git a/test/units/TEST-50-DISSECT.dissect.sh b/test/units/TEST-50-DISSECT.dissect.sh index 2fe6bf19d95..9fc15d04da7 100755 --- a/test/units/TEST-50-DISSECT.dissect.sh +++ b/test/units/TEST-50-DISSECT.dissect.sh @@ -1082,6 +1082,55 @@ systemd-confext status systemd-confext unmerge rm -rf /run/confexts/ +cleanup_sysupdate_notify_confext_mutable() { + local mutable_marker="$1" + local notify_socket_was_active="$2" + + if [[ "$notify_socket_was_active" == "yes" ]]; then + systemctl restart systemd-sysupdate-notify-sysext.socket || : + else + systemctl stop systemd-sysupdate-notify-sysext.socket || : + fi + + rm -f "$mutable_marker" + rm -f "/var/lib/extensions.mutable/etc/${mutable_marker#/etc/}" + systemd-confext unmerge || : + rm -f /run/systemd/confext.conf.d/50-test-mutable.conf + rm -rf /run/confexts/ + rmdir /var/lib/extensions.mutable/etc /var/lib/extensions.mutable 2>/dev/null || : +} + +test_sysupdate_notify_confext_mutable() { + local mutable_marker="/etc/test-confext-mutable-$RANDOM" + local notify_socket_was_active=no + + if systemctl is-active --quiet systemd-sysupdate-notify-sysext.socket; then + notify_socket_was_active=yes + fi + + trap 'cleanup_sysupdate_notify_confext_mutable "$mutable_marker" "$notify_socket_was_active"' RETURN ERR + + # Check that the sysupdate notification refresh honors confext-specific configuration. + mkdir -p /run/systemd/confext.conf.d + cat >/run/systemd/confext.conf.d/50-test-mutable.conf </run/confexts/test/etc/extension-release.d/extension-release.test + echo "ARCHITECTURE=_any" >>/run/confexts/test/etc/extension-release.d/extension-release.test + echo "MARKER_CONFEXT_123" >/run/confexts/test/etc/testfile + systemd-confext merge + echo "MARKER_CONFEXT_MUTABLE" >"$mutable_marker" + systemctl start systemd-sysupdate-notify-sysext.socket + varlinkctl call /run/systemd/sysupdate/notify/io.systemd.sysext io.systemd.SysUpdate.Notify.OnCompletedUpdate '{}' + grep -F "MARKER_CONFEXT_MUTABLE" "$mutable_marker" >/dev/null + trap - RETURN ERR + cleanup_sysupdate_notify_confext_mutable "$mutable_marker" "$notify_socket_was_active" +} + +test_sysupdate_notify_confext_mutable + unsquashfs -force -no-xattrs -d /tmp/img "$MINIMAL_IMAGE.raw" systemd-run --unit=test-root-ephemeral \ -p RootDirectory=/tmp/img \