]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysext: make some calls available via varlink
authorLennart Poettering <lennart@poettering.net>
Mon, 9 Oct 2023 16:57:41 +0000 (18:57 +0200)
committerLennart Poettering <lennart@poettering.net>
Mon, 16 Oct 2023 10:08:39 +0000 (12:08 +0200)
src/shared/discover-image.c
src/shared/discover-image.h
src/shared/meson.build
src/shared/varlink-io.systemd.sysext.c [new file with mode: 0644]
src/shared/varlink-io.systemd.sysext.h [new file with mode: 0644]
src/sysext/sysext.c
src/test/test-varlink-idl.c
test/units/testsuite-50.sh
units/meson.build
units/systemd-sysext.socket [new file with mode: 0644]
units/systemd-sysext@.service [new file with mode: 0644]

index 094337616daacdbabaf8269aa6e7fd3997ade078..aefe921bd496851a6abe60aa912d006aac081074 100644 (file)
@@ -1353,6 +1353,24 @@ bool image_in_search_path(
         return false;
 }
 
+int image_to_json(const struct Image *img, JsonVariant **ret) {
+        assert(img);
+
+        return json_build(ret,
+                          JSON_BUILD_OBJECT(
+                                          JSON_BUILD_PAIR_STRING("Type", image_type_to_string(img->type)),
+                                          JSON_BUILD_PAIR_STRING("Class", image_class_to_string(img->class)),
+                                          JSON_BUILD_PAIR_STRING("Name", img->name),
+                                          JSON_BUILD_PAIR_CONDITION(img->path, "Path", JSON_BUILD_STRING(img->path)),
+                                          JSON_BUILD_PAIR_BOOLEAN("ReadOnly", img->read_only),
+                                          JSON_BUILD_PAIR_CONDITION(img->crtime != 0, "CreationTimestamp", JSON_BUILD_UNSIGNED(img->crtime)),
+                                          JSON_BUILD_PAIR_CONDITION(img->mtime != 0, "ModificationTimestamp", JSON_BUILD_UNSIGNED(img->mtime)),
+                                          JSON_BUILD_PAIR_CONDITION(img->usage != UINT64_MAX, "Usage", JSON_BUILD_UNSIGNED(img->usage)),
+                                          JSON_BUILD_PAIR_CONDITION(img->usage_exclusive != UINT64_MAX, "UsageExclusive", JSON_BUILD_UNSIGNED(img->usage_exclusive)),
+                                          JSON_BUILD_PAIR_CONDITION(img->limit != UINT64_MAX, "Limit", JSON_BUILD_UNSIGNED(img->limit)),
+                                          JSON_BUILD_PAIR_CONDITION(img->limit_exclusive != UINT64_MAX, "LimitExclusive", JSON_BUILD_UNSIGNED(img->limit_exclusive))));
+}
+
 static const char* const image_type_table[_IMAGE_TYPE_MAX] = {
         [IMAGE_DIRECTORY] = "directory",
         [IMAGE_SUBVOLUME] = "subvolume",
index bb046fae1ec4ced9aea5411f7ceeee83e9ffc434..a30a3d92ba00b56a7a1cb8aa34d7915238bfbf77 100644 (file)
@@ -8,6 +8,7 @@
 
 #include "hashmap.h"
 #include "image-policy.h"
+#include "json.h"
 #include "lock-util.h"
 #include "macro.h"
 #include "os-util.h"
@@ -116,4 +117,6 @@ static inline bool IMAGE_IS_HOST(const struct Image *i) {
         return false;
 }
 
+int image_to_json(const struct Image *i, JsonVariant **ret);
+
 extern const struct hash_ops image_hash_ops;
index 569b865636d1e1c7bc9617ab515d7ce04d3daec2..662cb53dda2ad50c5d5530da5bcdac005ac9817d 100644 (file)
@@ -175,6 +175,7 @@ shared_sources = files(
         'varlink-io.systemd.Resolve.c',
         'varlink-io.systemd.UserDatabase.c',
         'varlink-io.systemd.oom.c',
+        'varlink-io.systemd.sysext.c',
         'varlink-org.varlink.service.c',
         'verb-log-control.c',
         'verbs.c',
diff --git a/src/shared/varlink-io.systemd.sysext.c b/src/shared/varlink-io.systemd.sysext.c
new file mode 100644 (file)
index 0000000..66e3534
--- /dev/null
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "varlink-io.systemd.sysext.h"
+
+static VARLINK_DEFINE_ENUM_TYPE(
+                ImageClass,
+                VARLINK_DEFINE_ENUM_VALUE(sysext),
+                VARLINK_DEFINE_ENUM_VALUE(confext));
+
+static VARLINK_DEFINE_ENUM_TYPE(
+                ImageType,
+                VARLINK_DEFINE_ENUM_VALUE(directory),
+                VARLINK_DEFINE_ENUM_VALUE(subvolume),
+                VARLINK_DEFINE_ENUM_VALUE(raw),
+                VARLINK_DEFINE_ENUM_VALUE(block));
+
+static VARLINK_DEFINE_METHOD(
+                Merge,
+                VARLINK_DEFINE_INPUT_BY_TYPE(class, ImageClass, VARLINK_NULLABLE),
+                VARLINK_DEFINE_INPUT(force, VARLINK_BOOL, VARLINK_NULLABLE),
+                VARLINK_DEFINE_INPUT(noReload, VARLINK_BOOL, VARLINK_NULLABLE),
+                VARLINK_DEFINE_INPUT(noexec, VARLINK_BOOL, VARLINK_NULLABLE));
+
+static VARLINK_DEFINE_METHOD(
+                Unmerge,
+                VARLINK_DEFINE_INPUT_BY_TYPE(class, ImageClass, VARLINK_NULLABLE),
+                VARLINK_DEFINE_INPUT(noReload, VARLINK_BOOL, VARLINK_NULLABLE));
+
+static VARLINK_DEFINE_METHOD(
+                Refresh,
+                VARLINK_DEFINE_INPUT_BY_TYPE(class, ImageClass, VARLINK_NULLABLE),
+                VARLINK_DEFINE_INPUT(force, VARLINK_BOOL, VARLINK_NULLABLE),
+                VARLINK_DEFINE_INPUT(noReload, VARLINK_BOOL, VARLINK_NULLABLE),
+                VARLINK_DEFINE_INPUT(noexec, VARLINK_BOOL, VARLINK_NULLABLE));
+
+static VARLINK_DEFINE_METHOD(
+                List,
+                VARLINK_DEFINE_INPUT_BY_TYPE(class, ImageClass, VARLINK_NULLABLE),
+                VARLINK_DEFINE_OUTPUT_BY_TYPE(Class, ImageClass, 0),
+                VARLINK_DEFINE_OUTPUT_BY_TYPE(Type, ImageType, 0),
+                VARLINK_DEFINE_OUTPUT(Name, VARLINK_STRING, 0),
+                VARLINK_DEFINE_OUTPUT(Path, VARLINK_STRING, VARLINK_NULLABLE),
+                VARLINK_DEFINE_OUTPUT(ReadOnly, VARLINK_BOOL, 0),
+                VARLINK_DEFINE_OUTPUT(CreationTimestamp, VARLINK_INT, VARLINK_NULLABLE),
+                VARLINK_DEFINE_OUTPUT(ModificationTimestamp, VARLINK_INT, VARLINK_NULLABLE),
+                VARLINK_DEFINE_OUTPUT(Usage, VARLINK_INT, VARLINK_NULLABLE),
+                VARLINK_DEFINE_OUTPUT(UsageExclusive, VARLINK_INT, VARLINK_NULLABLE),
+                VARLINK_DEFINE_OUTPUT(Limit, VARLINK_INT, VARLINK_NULLABLE),
+                VARLINK_DEFINE_OUTPUT(LimitExclusive, VARLINK_INT, VARLINK_NULLABLE));
+
+static VARLINK_DEFINE_ERROR(NoImagesFound);
+
+static VARLINK_DEFINE_ERROR(
+                AlreadyMerged,
+                VARLINK_DEFINE_FIELD(hierarchy, VARLINK_STRING, 0));
+
+VARLINK_DEFINE_INTERFACE(
+                io_systemd_sysext,
+                "io.systemd.sysext",
+                &vl_type_ImageClass,
+                &vl_type_ImageType,
+                &vl_method_Merge,
+                &vl_method_Unmerge,
+                &vl_method_Refresh,
+                &vl_method_List,
+                &vl_error_NoImagesFound,
+                &vl_error_AlreadyMerged);
diff --git a/src/shared/varlink-io.systemd.sysext.h b/src/shared/varlink-io.systemd.sysext.h
new file mode 100644 (file)
index 0000000..ee649c6
--- /dev/null
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "varlink-idl.h"
+
+extern const VarlinkInterface vl_interface_io_systemd_sysext;
index becfbab44ec660a5832e3dab07d4f36012148cf7..4d720199662e3eb5b79bba95e7c45fc5b4bc9eb4 100644 (file)
@@ -44,6 +44,8 @@
 #include "sort-util.h"
 #include "terminal-util.h"
 #include "user-util.h"
+#include "varlink.h"
+#include "varlink-io.systemd.sysext.h"
 #include "verbs.h"
 
 static char **arg_hierarchies = NULL; /* "/usr" + "/opt" by default for sysext and /etc by default for confext */
@@ -55,6 +57,7 @@ static bool arg_force = false;
 static bool arg_no_reload = false;
 static int arg_noexec = -1;
 static ImagePolicy *arg_image_policy = NULL;
+static bool arg_varlink = 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;
@@ -99,12 +102,17 @@ static const struct {
         }
 };
 
-static int is_our_mount_point(const char *p) {
+static int is_our_mount_point(
+                ImageClass image_class,
+                const char *p) {
+
         _cleanup_free_ char *buf = NULL, *f = NULL;
         struct stat st;
         dev_t dev;
         int r;
 
+        assert(p);
+
         r = path_is_mount_point(p, NULL, 0);
         if (r == -ENOENT) {
                 log_debug_errno(r, "Hierarchy '%s' doesn't exist.", p);
@@ -125,21 +133,21 @@ static int is_our_mount_point(const char *p) {
          * confused if people tar up one of our merged trees and untar them elsewhere where we might mistake
          * them for a live sysext tree. */
 
-        f = path_join(p, image_class_info[arg_image_class].dot_directory_name, "dev");
+        f = path_join(p, image_class_info[image_class].dot_directory_name, "dev");
         if (!f)
                 return log_oom();
 
         r = read_one_line_file(f, &buf);
         if (r == -ENOENT) {
-                log_debug("Hierarchy '%s' does not carry a %s/dev file, not a merged tree.", p, image_class_info[arg_image_class].dot_directory_name);
+                log_debug("Hierarchy '%s' does not carry a %s/dev file, not a merged tree.", p, image_class_info[image_class].dot_directory_name);
                 return false;
         }
         if (r < 0)
-                return log_error_errno(r, "Failed to determine whether hierarchy '%s' contains '%s/dev': %m", p, image_class_info[arg_image_class].dot_directory_name);
+                return log_error_errno(r, "Failed to determine whether hierarchy '%s' contains '%s/dev': %m", p, image_class_info[image_class].dot_directory_name);
 
         r = parse_devnum(buf, &dev);
         if (r < 0)
-                return log_error_errno(r, "Failed to parse device major/minor stored in '%s/dev' file on '%s': %m", image_class_info[arg_image_class].dot_directory_name, p);
+                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);
 
         if (lstat(p, &st) < 0)
                 return log_error_errno(r, "Failed to stat %s: %m", p);
@@ -152,15 +160,18 @@ static int is_our_mount_point(const char *p) {
         return true;
 }
 
-static int need_reload(void) {
-        /* Parse the mounted images to find out if we need
-        to reload the daemon. */
+static int need_reload(
+                ImageClass image_class,
+                char **hierarchies,
+                bool no_reload) {
+
+        /* Parse the mounted images to find out if we need to reload the daemon. */
         int r;
 
-        if (arg_no_reload)
+        if (no_reload)
                 return false;
 
-        STRV_FOREACH(p, arg_hierarchies) {
+        STRV_FOREACH(p, hierarchies) {
                 _cleanup_free_ char *f = NULL, *buf = NULL, *resolved = NULL;
                 _cleanup_strv_free_ char **mounted_extensions = NULL;
 
@@ -174,13 +185,13 @@ static int need_reload(void) {
                         continue;
                 }
 
-                r = is_our_mount_point(resolved);
+                r = is_our_mount_point(image_class, resolved);
                 if (r < 0)
                         return r;
                 if (!r)
                         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[image_class].dot_directory_name, image_class_info[image_class].short_identifier_plural);
                 if (!f)
                         return log_oom();
 
@@ -197,7 +208,7 @@ static int need_reload(void) {
                         const char *extension_reload_manager = NULL;
                         int b;
 
-                        r = load_extension_release_pairs(arg_root, arg_image_class, *extension, /* relax_extension_release_check */ true, &extension_release);
+                        r = load_extension_release_pairs(arg_root, 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;
@@ -233,14 +244,19 @@ static int daemon_reload(void) {
         return bus_service_manager_reload(bus);
 }
 
-static int unmerge_hierarchy(const char *p) {
+static int unmerge_hierarchy(
+                ImageClass image_class,
+                const char *p) {
+
         int r;
 
+        assert(p);
+
         for (;;) {
                 /* 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(p);
+                r = is_our_mount_point(image_class, p);
                 if (r < 0)
                         return r;
                 if (r == 0)
@@ -256,16 +272,20 @@ static int unmerge_hierarchy(const char *p) {
         return 0;
 }
 
-static int unmerge(void) {
+static int unmerge(
+                ImageClass image_class,
+                char **hierarchies,
+                bool no_reload) {
+
         int r, ret = 0;
         bool need_to_reload;
 
-        r = need_reload();
+        r = need_reload(image_class, hierarchies, no_reload);
         if (r < 0)
                 return r;
         need_to_reload = r > 0;
 
-        STRV_FOREACH(p, arg_hierarchies) {
+        STRV_FOREACH(p, hierarchies) {
                 _cleanup_free_ char *resolved = NULL;
 
                 r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
@@ -281,7 +301,7 @@ static int unmerge(void) {
                         continue;
                 }
 
-                r = unmerge_hierarchy(resolved);
+                r = unmerge_hierarchy(image_class, resolved);
                 if (r < 0 && ret == 0)
                         ret = r;
         }
@@ -304,7 +324,74 @@ static int verb_unmerge(int argc, char **argv, void *userdata) {
         if (r == 0)
                 return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged.");
 
-        return unmerge();
+        return unmerge(arg_image_class,
+                       arg_hierarchies,
+                       arg_no_reload);
+}
+
+static int parse_image_class_parameter(Varlink *link, const char *value, ImageClass *image_class, char ***hierarchies) {
+        _cleanup_strv_free_ char **h = NULL;
+        ImageClass c;
+        int r;
+
+        assert(link);
+        assert(image_class);
+
+        if (!value)
+                return 0;
+
+        c = image_class_from_string(value);
+        if (!IN_SET(c, IMAGE_SYSEXT, IMAGE_CONFEXT))
+                return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", "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;
+}
+
+typedef struct MethodUnmergeParameters {
+        const char *class;
+        int no_reload;
+} MethodUnmergeParameters;
+
+static int vl_method_unmerge(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+
+        static const JsonDispatch dispatch_table[] = {
+                { "class",    JSON_VARIANT_STRING,  json_dispatch_const_string, offsetof(MethodUnmergeParameters, class),     0 },
+                { "noReload", JSON_VARIANT_BOOLEAN, json_dispatch_boolean,      offsetof(MethodUnmergeParameters, no_reload), 0 },
+                {}
+        };
+        MethodUnmergeParameters p = {
+                .no_reload = -1,
+        };
+        _cleanup_strv_free_ char **hierarchies = NULL;
+        ImageClass image_class = arg_image_class;
+        int r;
+
+        assert(link);
+
+        r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
+        if (r < 0)
+                return r;
+
+        r = parse_image_class_parameter(link, p.class, &image_class, &hierarchies);
+        if (r < 0)
+                return r;
+
+        r = unmerge(image_class,
+                    hierarchies ?: arg_hierarchies,
+                    p.no_reload >= 0 ? p.no_reload : arg_no_reload);
+        if (r < 0)
+                return r;
+
+        return varlink_reply(link, NULL);
 }
 
 static int verb_status(int argc, char **argv, void *userdata) {
@@ -332,7 +419,7 @@ static int verb_status(int argc, char **argv, void *userdata) {
                         goto inner_fail;
                 }
 
-                r = is_our_mount_point(resolved);
+                r = is_our_mount_point(arg_image_class, resolved);
                 if (r < 0)
                         goto inner_fail;
                 if (r == 0) {
@@ -388,6 +475,8 @@ static int verb_status(int argc, char **argv, void *userdata) {
 }
 
 static int mount_overlayfs(
+                ImageClass image_class,
+                int noexec,
                 const char *where,
                 char **layers) {
 
@@ -415,12 +504,12 @@ static int mount_overlayfs(
                 separator = true;
         }
 
-        flags = image_class_info[arg_image_class].default_mount_flags;
-        if (arg_noexec >= 0)
-                SET_FLAG(flags, MS_NOEXEC, arg_noexec);
+        flags = image_class_info[image_class].default_mount_flags;
+        if (noexec >= 0)
+                SET_FLAG(flags, MS_NOEXEC, noexec);
 
         /* Now mount the actual overlayfs */
-        r = mount_nofollow_verbose(LOG_ERR, image_class_info[arg_image_class].short_identifier, where, "overlay", flags, options);
+        r = mount_nofollow_verbose(LOG_ERR, image_class_info[image_class].short_identifier, where, "overlay", flags, options);
         if (r < 0)
                 return r;
 
@@ -428,7 +517,9 @@ static int mount_overlayfs(
 }
 
 static int merge_hierarchy(
+                ImageClass image_class,
                 const char *hierarchy,
+                int noexec,
                 char **extensions,
                 char **paths,
                 const char *meta_path,
@@ -463,7 +554,7 @@ static int merge_hierarchy(
         /* Let's generate a metadata file that lists all extensions we took into account for this
          * hierarchy. We include this in the final fs, to make things nicely discoverable and
          * recognizable. */
-        f = path_join(meta_path, image_class_info[arg_image_class].dot_directory_name, image_class_info[arg_image_class].short_identifier_plural);
+        f = path_join(meta_path, image_class_info[image_class].dot_directory_name, image_class_info[image_class].short_identifier_plural);
         if (!f)
                 return log_oom();
 
@@ -519,7 +610,7 @@ static int merge_hierarchy(
         if (r < 0)
                 return log_error_errno(r, "Failed to make directory '%s': %m", overlay_path);
 
-        r = mount_overlayfs(overlay_path, layers);
+        r = mount_overlayfs(image_class, noexec, overlay_path, layers);
         if (r < 0)
                 return r;
 
@@ -536,7 +627,7 @@ static int merge_hierarchy(
                 return log_error_errno(r, "Failed to stat mount '%s': %m", overlay_path);
 
         free(f);
-        f = path_join(meta_path, image_class_info[arg_image_class].dot_directory_name, "dev");
+        f = path_join(meta_path, image_class_info[image_class].dot_directory_name, "dev");
         if (!f)
                 return log_oom();
 
@@ -574,7 +665,14 @@ static const ImagePolicy *pick_image_policy(const Image *img) {
         return image_class_info[img->class].default_image_policy;
 }
 
-static int merge_subprocess(Hashmap *images, const char *workspace) {
+static int merge_subprocess(
+                ImageClass image_class,
+                char **hierarchies,
+                bool force,
+                int noexec,
+                Hashmap *images,
+                const char *workspace) {
+
         _cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL, *host_os_release_api_level = NULL, *buf = NULL;
         _cleanup_strv_free_ char **extensions = NULL, **paths = NULL;
         size_t n_extensions = 0;
@@ -597,7 +695,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
          * 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[arg_image_class].short_identifier, workspace, "tmpfs", 0, "mode=0700");
+        r = mount_nofollow_verbose(LOG_ERR, image_class_info[image_class].short_identifier, workspace, "tmpfs", 0, "mode=0700");
         if (r < 0)
                 return r;
 
@@ -606,7 +704,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                         arg_root,
                         "ID", &host_os_release_id,
                         "VERSION_ID", &host_os_release_version_id,
-                        image_class_info[arg_image_class].level_env, &host_os_release_api_level);
+                        image_class_info[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));
         if (isempty(host_os_release_id))
@@ -618,7 +716,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
         HASHMAP_FOREACH(img, images) {
                 _cleanup_free_ char *p = NULL;
 
-                p = path_join(workspace, image_class_info[arg_image_class].short_identifier_plural, img->name);
+                p = path_join(workspace, image_class_info[image_class].short_identifier_plural, img->name);
                 if (!p)
                         return log_oom();
 
@@ -630,7 +728,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                 case IMAGE_DIRECTORY:
                 case IMAGE_SUBVOLUME:
 
-                        if (!arg_force) {
+                        if (!force) {
                                 r = extension_has_forbidden_content(p);
                                 if (r < 0)
                                         return r;
@@ -672,7 +770,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                         if (verity_settings.data_path)
                                 flags |= DISSECT_IMAGE_NO_PARTITION_TABLE;
 
-                        if (!arg_force)
+                        if (!force)
                                 flags |= DISSECT_IMAGE_VALIDATE_OS_EXT;
 
                         r = loop_device_make_by_path(
@@ -718,7 +816,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                                         flags);
                         if (r < 0 && r != -ENOMEDIUM)
                                 return r;
-                        if (r == -ENOMEDIUM && !arg_force) {
+                        if (r == -ENOMEDIUM && !force) {
                                 n_ignored++;
                                 continue;
                         }
@@ -732,7 +830,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                         assert_not_reached();
                 }
 
-                if (arg_force)
+                if (force)
                         log_debug("Force mode enabled, skipping version validation.");
                 else {
                         r = extension_release_validate(
@@ -741,8 +839,8 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                                         host_os_release_version_id,
                                         host_os_release_api_level,
                                         in_initrd() ? "initrd" : "system",
-                                        image_extension_release(img, arg_image_class),
-                                        arg_image_class);
+                                        image_extension_release(img, image_class),
+                                        image_class);
                         if (r < 0)
                                 return r;
                         if (r == 0) {
@@ -787,7 +885,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
 
                 assert_se(img = hashmap_get(images, extensions[n_extensions - 1 - k]));
 
-                p = path_join(workspace, image_class_info[arg_image_class].short_identifier_plural, img->name);
+                p = path_join(workspace, image_class_info[image_class].short_identifier_plural, img->name);
                 if (!p)
                         return log_oom();
 
@@ -796,20 +894,20 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
 
         /* 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, arg_hierarchies) {
+        STRV_FOREACH(h, hierarchies) {
                 _cleanup_free_ char *resolved = NULL;
 
                 r = chase(*h, arg_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);
 
-                r = unmerge_hierarchy(resolved);
+                r = unmerge_hierarchy(image_class, resolved);
                 if (r < 0)
                         return r;
         }
 
         /* Create overlayfs mounts for all hierarchies */
-        STRV_FOREACH(h, arg_hierarchies) {
+        STRV_FOREACH(h, hierarchies) {
                 _cleanup_free_ char *meta_path = NULL, *overlay_path = NULL;
 
                 meta_path = path_join(workspace, "meta", *h); /* The place where to store metadata about this instance */
@@ -820,13 +918,20 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                 if (!overlay_path)
                         return log_oom();
 
-                r = merge_hierarchy(*h, extensions, paths, meta_path, overlay_path);
+                r = merge_hierarchy(
+                                image_class,
+                                *h,
+                                noexec,
+                                extensions,
+                                paths,
+                                meta_path,
+                                overlay_path);
                 if (r < 0)
                         return r;
         }
 
         /* And move them all into place. This is where things appear in the host namespace */
-        STRV_FOREACH(h, arg_hierarchies) {
+        STRV_FOREACH(h, hierarchies) {
                 _cleanup_free_ char *p = NULL, *resolved = NULL;
 
                 p = path_join(workspace, "overlay", *h);
@@ -859,7 +964,12 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
         return 1;
 }
 
-static int merge(Hashmap *images) {
+static int merge(ImageClass image_class,
+                 char **hierarchies,
+                 bool force,
+                 bool no_reload,
+                 int noexec,
+                 Hashmap *images) {
         pid_t pid;
         int r;
 
@@ -869,7 +979,7 @@ static int merge(Hashmap *images) {
         if (r == 0) {
                 /* Child with its own mount namespace */
 
-                r = merge_subprocess(images, "/run/systemd/sysext");
+                r = merge_subprocess(image_class, hierarchies, force, noexec, images, "/run/systemd/sysext");
                 if (r < 0)
                         _exit(EXIT_FAILURE);
 
@@ -886,7 +996,7 @@ static int merge(Hashmap *images) {
         if (r == 123) /* exit code 123 means: didn't do anything */
                 return 0;
 
-        r = need_reload();
+        r = need_reload(image_class, hierarchies, no_reload);
         if (r < 0)
                 return r;
         if (r > 0) {
@@ -898,7 +1008,9 @@ static int merge(Hashmap *images) {
         return 1;
 }
 
-static int image_discover_and_read_metadata(Hashmap **ret_images) {
+static int image_discover_and_read_metadata(
+                ImageClass image_class,
+                Hashmap **ret_images) {
         _cleanup_hashmap_free_ Hashmap *images = NULL;
         Image *img;
         int r;
@@ -909,12 +1021,12 @@ static int image_discover_and_read_metadata(Hashmap **ret_images) {
         if (!images)
                 return log_oom();
 
-        r = image_discover(arg_image_class, arg_root, images);
+        r = image_discover(image_class, arg_root, images);
         if (r < 0)
                 return log_error_errno(r, "Failed to discover images: %m");
 
         HASHMAP_FOREACH(img, images) {
-                r = image_read_metadata(img, image_class_info[arg_image_class].default_image_policy);
+                r = image_read_metadata(img, image_class_info[image_class].default_image_policy);
                 if (r < 0)
                         return log_error_errno(r, "Failed to read metadata for image %s: %m", img->name);
         }
@@ -924,23 +1036,17 @@ static int image_discover_and_read_metadata(Hashmap **ret_images) {
         return 0;
 }
 
-static int verb_merge(int argc, char **argv, void *userdata) {
-        _cleanup_hashmap_free_ Hashmap *images = NULL;
+static int look_for_merged_hierarchies(
+                ImageClass image_class,
+                char **hierarchies,
+                const char **ret_which) {
         int r;
 
-        r = have_effective_cap(CAP_SYS_ADMIN);
-        if (r < 0)
-                return log_error_errno(r, "Failed to check if we have enough privileges: %m");
-        if (r == 0)
-                return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged.");
-
-        r = image_discover_and_read_metadata(&images);
-        if (r < 0)
-                return r;
+        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, arg_hierarchies) {
+        STRV_FOREACH(p, hierarchies) {
                 _cleanup_free_ char *resolved = NULL;
 
                 r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
@@ -951,19 +1057,22 @@ static int verb_merge(int argc, char **argv, void *userdata) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root), *p);
 
-                r = is_our_mount_point(resolved);
+                r = is_our_mount_point(image_class, resolved);
                 if (r < 0)
                         return r;
-                if (r > 0)
-                        return log_error_errno(SYNTHETIC_ERRNO(EBUSY),
-                                               "Hierarchy '%s' is already merged.", *p);
+                if (r > 0) {
+                        *ret_which = *p;
+                        return 1;
+                }
         }
 
-        return merge(images);
+        *ret_which = NULL;
+        return 0;
 }
 
-static int verb_refresh(int argc, char **argv, void *userdata) {
+static int verb_merge(int argc, char **argv, void *userdata) {
         _cleanup_hashmap_free_ Hashmap *images = NULL;
+        const char *which;
         int r;
 
         r = have_effective_cap(CAP_SYS_ADMIN);
@@ -972,19 +1081,123 @@ static int verb_refresh(int argc, char **argv, void *userdata) {
         if (r == 0)
                 return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged.");
 
-        r = image_discover_and_read_metadata(&images);
+        r = image_discover_and_read_metadata(arg_image_class, &images);
+        if (r < 0)
+                return r;
+
+        r = look_for_merged_hierarchies(arg_image_class, arg_hierarchies, &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_noexec,
+                     images);
+}
+
+typedef struct MethodMergeParameters {
+        const char *class;
+        int force;
+        int no_reload;
+        int noexec;
+} MethodMergeParameters;
+
+static int parse_merge_parameters(Varlink *link, JsonVariant *parameters, MethodMergeParameters *p) {
+
+        static const JsonDispatch dispatch_table[] = {
+                { "class",    JSON_VARIANT_STRING,  json_dispatch_const_string, offsetof(MethodMergeParameters, class),     0 },
+                { "force",    JSON_VARIANT_BOOLEAN, json_dispatch_boolean,      offsetof(MethodMergeParameters, force),     0 },
+                { "noReload", JSON_VARIANT_BOOLEAN, json_dispatch_boolean,      offsetof(MethodMergeParameters, no_reload), 0 },
+                { "noexec",   JSON_VARIANT_BOOLEAN, json_dispatch_boolean,      offsetof(MethodMergeParameters, noexec),    0 },
+                {}
+        };
+        int r;
+
+        assert(link);
+        assert(parameters);
+        assert(p);
+
+        r = json_dispatch(parameters, dispatch_table, NULL, 0, p);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int vl_method_merge(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+        _cleanup_hashmap_free_ Hashmap *images = NULL;
+        MethodMergeParameters p = {
+                .force = -1,
+                .no_reload = -1,
+                .noexec = -1,
+        };
+        _cleanup_strv_free_ char **hierarchies = NULL;
+        ImageClass image_class = arg_image_class;
+        int r;
+
+        assert(link);
+
+        r = parse_merge_parameters(link, parameters, &p);
+        if (r < 0)
+                return r;
+
+        r = parse_image_class_parameter(link, p.class, &image_class, &hierarchies);
+        if (r < 0)
+                return r;
+
+        r = image_discover_and_read_metadata(image_class, &images);
+        if (r < 0)
+                return r;
+
+        const char *which;
+        r = look_for_merged_hierarchies(
+                        image_class,
+                        hierarchies ?: arg_hierarchies,
+                        &which);
+        if (r < 0)
+                return r;
+        if (r > 0)
+                return varlink_errorb(link, "io.systemd.sysext.AlreadyMerged", JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("hierarchy", which)));
+
+        r = merge(image_class,
+                  hierarchies ?: arg_hierarchies,
+                  p.force >= 0 ? p.force : arg_force,
+                  p.no_reload >= 0 ? p.no_reload : arg_no_reload,
+                  p.noexec >= 0 ? p.noexec : arg_noexec,
+                  images);
         if (r < 0)
                 return r;
 
-        r = merge(images); /* Returns > 0 if it did something, i.e. a new overlayfs is mounted now. When it
-                            * does so it implicitly unmounts any overlayfs placed there before. 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. */
+        return varlink_reply(link, NULL);
+}
+
+static int refresh(
+                ImageClass image_class,
+                char **hierarchies,
+                bool force,
+                bool no_reload,
+                int noexec) {
+
+        _cleanup_hashmap_free_ Hashmap *images = NULL;
+        int r;
+
+        r = image_discover_and_read_metadata(image_class, &images);
+        if (r < 0)
+                return r;
+
+        /* Returns > 0 if it did something, i.e. a new overlayfs is mounted now. When it does so it
+         * implicitly unmounts any overlayfs placed there before. 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, noexec, 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();
+                r = unmerge(image_class, hierarchies, no_reload);
 
         /* Net result here is that:
          *
@@ -1002,6 +1215,54 @@ static int verb_refresh(int argc, char **argv, void *userdata) {
         return 0;
 }
 
+static int verb_refresh(int argc, char **argv, void *userdata) {
+        int r;
+
+        r = have_effective_cap(CAP_SYS_ADMIN);
+        if (r < 0)
+                return log_error_errno(r, "Failed to check if we have enough privileges: %m");
+        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_noexec);
+}
+
+static int vl_method_refresh(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+
+        MethodMergeParameters p = {
+                .force = -1,
+                .no_reload = -1,
+                .noexec = -1,
+        };
+        _cleanup_strv_free_ char **hierarchies = NULL;
+        ImageClass image_class = arg_image_class;
+        int r;
+
+        assert(link);
+
+        r = parse_merge_parameters(link, parameters, &p);
+        if (r < 0)
+                return r;
+
+        r = parse_image_class_parameter(link, p.class, &image_class, &hierarchies);
+        if (r < 0)
+                return r;
+
+        r = refresh(image_class,
+                    hierarchies ?: arg_hierarchies,
+                    p.force >= 0 ? p.force : arg_force,
+                    p.no_reload >= 0 ? p.no_reload : arg_no_reload,
+                    p.noexec >= 0 ? p.noexec : arg_noexec);
+        if (r < 0)
+                return r;
+
+        return varlink_reply(link, NULL);
+}
+
 static int verb_list(int argc, char **argv, void *userdata) {
         _cleanup_hashmap_free_ Hashmap *images = NULL;
         _cleanup_(table_unrefp) Table *t = NULL;
@@ -1041,6 +1302,63 @@ static int verb_list(int argc, char **argv, void *userdata) {
         return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
 }
 
+typedef struct MethodListParameters {
+        const char *class;
+} MethodListParameters;
+
+static int vl_method_list(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+
+        static const JsonDispatch dispatch_table[] = {
+                { "class",    JSON_VARIANT_STRING,  json_dispatch_const_string, offsetof(MethodListParameters, class),     0 },
+                {}
+        };
+        MethodListParameters p = {
+        };
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        _cleanup_hashmap_free_ Hashmap *images = NULL;
+        ImageClass image_class = arg_image_class;
+        Image *img;
+        int r;
+
+        assert(link);
+
+        r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
+        if (r < 0)
+                return r;
+
+        r = parse_image_class_parameter(link, p.class, &image_class, NULL);
+        if (r < 0)
+                return r;
+
+        images = hashmap_new(&image_hash_ops);
+        if (!images)
+                return -ENOMEM;
+
+        r = image_discover(image_class, arg_root, images);
+        if (r < 0)
+                return r;
+
+        HASHMAP_FOREACH(img, images) {
+                if (v) {
+                        /* Send previous item with more=true */
+                        r = varlink_notify(link, v);
+                        if (r < 0)
+                                return r;
+                }
+
+                v = json_variant_unref(v);
+
+                r = image_to_json(img, &v);
+                if (r < 0)
+                        return r;
+        }
+
+        if (v)  /* Send final item with more=false */
+                return varlink_reply(link, v);
+
+        return varlink_error(link, "io.systemd.sysext.NoImagesFound", NULL);
+}
+
 static int verb_help(int argc, char **argv, void *userdata) {
         _cleanup_free_ char *link = NULL;
         int r;
@@ -1176,6 +1494,12 @@ static int parse_argv(int argc, char *argv[]) {
                         assert_not_reached();
                 }
 
+        r = varlink_invocation(VARLINK_ALLOW_ACCEPT);
+        if (r < 0)
+                return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
+        if (r > 0)
+                arg_varlink = true;
+
         return 1;
 }
 
@@ -1196,10 +1520,11 @@ static int sysext_main(int argc, char *argv[]) {
 
 static int run(int argc, char *argv[]) {
         int r;
-        arg_image_class = invoked_as(argv, "systemd-confext") ? IMAGE_CONFEXT : IMAGE_SYSEXT;
 
         log_setup();
 
+        arg_image_class = invoked_as(argv, "systemd-confext") ? IMAGE_CONFEXT : IMAGE_SYSEXT;
+
         r = parse_argv(argc, argv);
         if (r <= 0)
                 return r;
@@ -1211,6 +1536,37 @@ static int run(int argc, char *argv[]) {
         if (r < 0)
                 return log_error_errno(r, "Failed to parse environment variable: %m");
 
+        if (arg_varlink) {
+                _cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL;
+
+                /* Invocation as Varlink service */
+
+                r = varlink_server_new(&varlink_server, VARLINK_SERVER_ROOT_ONLY);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to allocate Varlink server: %m");
+
+                r = varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_sysext);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add Varlink interface: %m");
+
+                r = varlink_server_bind_method_many(
+                                varlink_server,
+                                "io.systemd.sysext.Merge", vl_method_merge,
+                                "io.systemd.sysext.Unmerge", vl_method_unmerge,
+                                "io.systemd.sysext.Refresh", vl_method_refresh,
+                                "io.systemd.sysext.List", vl_method_list);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to bind Varlink methods: %m");
+
+                r = varlink_server_loop_auto(varlink_server);
+                if (r == -EPERM)
+                        return log_error_errno(r, "Invoked by unprivileged Varlink peer, refusing.");
+                if (r < 0)
+                        return log_error_errno(r, "Failed to run Varlink event loop: %m");
+
+                return EXIT_SUCCESS;
+        }
+
         return sysext_main(argc, argv);
 }
 
index 028408b16e63ea30f7ae1701f7e9fe47685fdf1e..ff3033a6b4d3f3c862b226045c8188fc7456f8c9 100644 (file)
@@ -15,6 +15,7 @@
 #include "varlink-io.systemd.Resolve.h"
 #include "varlink-io.systemd.UserDatabase.h"
 #include "varlink-io.systemd.oom.h"
+#include "varlink-io.systemd.sysext.h"
 #include "varlink-org.varlink.service.h"
 
 static VARLINK_DEFINE_ENUM_TYPE(
@@ -137,6 +138,8 @@ TEST(parse_format) {
         print_separator();
         test_parse_format_one(&vl_interface_io_systemd_PCRExtend);
         print_separator();
+        test_parse_format_one(&vl_interface_io_systemd_sysext);
+        print_separator();
         test_parse_format_one(&vl_interface_xyz_test);
 }
 
index 0cec747397bb1453e9416c7ab2b34ea8944dd13c..3a719d20597fbb09f6434c1dadf265a6bf14a709 100755 (executable)
@@ -457,6 +457,16 @@ test ! -e /usr/lib/systemd/system/some_file
 systemd-sysext unmerge
 rmdir /etc/extensions/app-nodistro
 
+# Similar, but go via varlink
+varlinkctl call /run/systemd/io.systemd.sysext io.systemd.sysext.List '{}'
+(! grep -q -F "MARKER=1" /usr/lib/systemd/system/some_file )
+varlinkctl call /run/systemd/io.systemd.sysext io.systemd.sysext.Merge '{}'
+grep -q -F "MARKER=1" /usr/lib/systemd/system/some_file
+varlinkctl call /run/systemd/io.systemd.sysext io.systemd.sysext.Refresh '{}'
+grep -q -F "MARKER=1" /usr/lib/systemd/system/some_file
+varlinkctl call /run/systemd/io.systemd.sysext io.systemd.sysext.Unmerge '{}'
+(! grep -q -F "MARKER=1" /usr/lib/systemd/system/some_file )
+
 # Check that extensions cannot contain os-release
 mkdir -p /run/extensions/app-reject/usr/lib/{extension-release.d/,systemd/system}
 echo "ID=_any" >/run/extensions/app-reject/usr/lib/extension-release.d/extension-release.app-reject
index b089cde8119c7081f4df24c688519b67e4ca82e4..493e294002ae28ac8c02fcb6b3efde923dc20358 100644 (file)
@@ -529,6 +529,15 @@ units = [
           'file' : 'systemd-sysext.service',
           'conditions' : ['ENABLE_SYSEXT'],
         },
+        {
+          'file' : 'systemd-sysext.socket',
+          'conditions' : ['ENABLE_SYSEXT'],
+          'symlinks' : ['sockets.target.wants/'],
+        },
+        {
+          'file' : 'systemd-sysext@.service',
+          'conditions' : ['ENABLE_SYSEXT'],
+        },
         {
           'file' : 'systemd-sysupdate-reboot.service.in',
           'conditions' : ['ENABLE_SYSUPDATE'],
diff --git a/units/systemd-sysext.socket b/units/systemd-sysext.socket
new file mode 100644 (file)
index 0000000..ad870c5
--- /dev/null
@@ -0,0 +1,25 @@
+#  SPDX-License-Identifier: LGPL-2.1-or-later
+#
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=System Extension Image Management (Varlink)
+Documentation=man:systemd-sysext(8)
+DefaultDependencies=no
+After=local-fs.target
+Before=sockets.target
+ConditionCapability=CAP_SYS_ADMIN
+
+[Socket]
+ListenStream=/run/systemd/io.systemd.sysext
+FileDescriptorName=varlink
+SocketMode=0600
+Accept=yes
+
+[Install]
+WantedBy=sockets.target
diff --git a/units/systemd-sysext@.service b/units/systemd-sysext@.service
new file mode 100644 (file)
index 0000000..544e22f
--- /dev/null
@@ -0,0 +1,20 @@
+#  SPDX-License-Identifier: LGPL-2.1-or-later
+#
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=System Extension Image Management (Varlink)
+Documentation=man:systemd-sysext(8)
+DefaultDependencies=no
+After=local-fs.target
+Conflicts=shutdown.target initrd-switch-root.target
+Before=shutdown.target initrd-switch-root.target
+
+[Service]
+Environment=LISTEN_FDNAMES=varlink
+ExecStart=-systemd-sysext