From 1d0796739c28ba8be67f42110eb36f231a6ca953 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 27 Jan 2021 12:52:10 +0000 Subject: [PATCH 1/9] os-util: add path_is_extension_tree helper --- src/shared/os-util.c | 7 ++++--- src/shared/os-util.h | 8 ++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/shared/os-util.c b/src/shared/os-util.c index 45c10ca9c5..3ecc03b022 100644 --- a/src/shared/os-util.c +++ b/src/shared/os-util.c @@ -11,7 +11,7 @@ #include "string-util.h" #include "strv.h" -int path_is_os_tree(const char *path) { +int path_is_extension_tree(const char *path, const char *extension) { int r; assert(path); @@ -22,8 +22,9 @@ int path_is_os_tree(const char *path) { if (laccess(path, F_OK) < 0) return -errno; - /* We use {/etc|/usr/lib}/os-release as flag file if something is an OS */ - r = open_os_release(path, NULL, NULL); + /* We use /usr/lib/extension-release.d/extension-release.NAME as flag file if something is a system extension, + * and {/etc|/usr/lib}/os-release as flag file if something is an OS (in case extension == NULL) */ + r = open_extension_release(path, extension, NULL, NULL); if (r == -ENOENT) /* We got nothing */ return 0; if (r < 0) diff --git a/src/shared/os-util.h b/src/shared/os-util.h index 5b724eb7ac..a6cedc6087 100644 --- a/src/shared/os-util.h +++ b/src/shared/os-util.h @@ -3,10 +3,14 @@ #include -int path_is_os_tree(const char *path); - /* The *_extension_release flavours will look for /usr/lib/extension-release/extension-release.NAME * in accordance with the OS extension specification, rather than for /usr/lib/ or /etc/os-release. */ + +int path_is_extension_tree(const char *path, const char *extension); +static inline int path_is_os_tree(const char *path) { + return path_is_extension_tree(path, NULL); +} + int open_extension_release(const char *root, const char *extension, char **ret_path, int *ret_fd); static inline int open_os_release(const char *root, char **ret_path, int *ret_fd) { return open_extension_release(root, NULL, ret_path, ret_fd); From eb590035b9d7421e2273cb08d48530427604a740 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 27 Jan 2021 12:53:13 +0000 Subject: [PATCH 2/9] os-util: add load_extension_release_pairs helper --- src/shared/os-util.c | 12 ++++++++++++ src/shared/os-util.h | 1 + 2 files changed, 13 insertions(+) diff --git a/src/shared/os-util.c b/src/shared/os-util.c index 3ecc03b022..2916dfeba4 100644 --- a/src/shared/os-util.c +++ b/src/shared/os-util.c @@ -180,3 +180,15 @@ int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char return 0; } + +int load_extension_release_pairs(const char *root, const char *extension, char ***ret) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *p = NULL; + int r; + + r = fopen_extension_release(root, extension, &p, &f); + if (r < 0) + return r; + + return load_env_file_pairs(f, p, ret); +} diff --git a/src/shared/os-util.h b/src/shared/os-util.h index a6cedc6087..bdb9e6adea 100644 --- a/src/shared/os-util.h +++ b/src/shared/os-util.h @@ -23,5 +23,6 @@ static inline int fopen_os_release(const char *root, char **ret_path, FILE **ret int parse_extension_release(const char *root, const char *extension, ...) _sentinel_; int parse_os_release(const char *root, ...) _sentinel_; +int load_extension_release_pairs(const char *root, const char *extension, char ***ret); int load_os_release_pairs(const char *root, char ***ret); int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret); From 42e6a77bc5ceb337e9f7a1bb9deebdac1fc40a8b Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 27 Jan 2021 12:51:17 +0000 Subject: [PATCH 3/9] env-util: add strv_env_pairs_get helper --- src/basic/env-util.c | 12 ++++++++++++ src/basic/env-util.h | 1 + src/test/test-env-util.c | 12 ++++++++++++ 3 files changed, 25 insertions(+) diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 25c4b7c5a4..137d6b1f3c 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -455,6 +455,18 @@ char *strv_env_get(char **l, const char *name) { return strv_env_get_n(l, name, strlen(name), 0); } +char *strv_env_pairs_get(char **l, const char *name) { + char **key, **value, *result = NULL; + + assert(name); + + STRV_FOREACH_PAIR(key, value, l) + if (streq(*key, name)) + result = *value; + + return result; +} + char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) { char **p, **q; int k = 0; diff --git a/src/basic/env-util.h b/src/basic/env-util.h index 347ea33e66..79307a4a5f 100644 --- a/src/basic/env-util.h +++ b/src/basic/env-util.h @@ -50,6 +50,7 @@ int strv_env_assign(char ***l, const char *key, const char *value); char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) _pure_; char *strv_env_get(char **x, const char *n) _pure_; +char *strv_env_pairs_get(char **l, const char *name) _pure_; int getenv_bool(const char *p); int getenv_bool_secure(const char *p); diff --git a/src/test/test-env-util.c b/src/test/test-env-util.c index fd121d975e..77b8952f83 100644 --- a/src/test/test-env-util.c +++ b/src/test/test-env-util.c @@ -44,6 +44,17 @@ static void test_strv_env_get(void) { assert_se(streq(strv_env_get(l, "FOUR"), "4")); } +static void test_strv_env_pairs_get(void) { + log_info("/* %s */", __func__); + + char **l = STRV_MAKE("ONE_OR_TWO", "1", "THREE", "3", "ONE_OR_TWO", "2", "FOUR", "4", "FIVE", "5", "SIX", "FIVE", "SEVEN", "7"); + + assert_se(streq(strv_env_pairs_get(l, "ONE_OR_TWO"), "2")); + assert_se(streq(strv_env_pairs_get(l, "THREE"), "3")); + assert_se(streq(strv_env_pairs_get(l, "FOUR"), "4")); + assert_se(streq(strv_env_pairs_get(l, "FIVE"), "5")); +} + static void test_strv_env_unset(void) { log_info("/* %s */", __func__); @@ -390,6 +401,7 @@ int main(int argc, char *argv[]) { test_strv_env_delete(); test_strv_env_get(); + test_strv_env_pairs_get(); test_strv_env_unset(); test_strv_env_merge(); test_strv_env_replace_strdup(); From 593fe6c04df0a9f9a2d93390837c4c756729d2cf Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 27 Jan 2021 12:57:27 +0000 Subject: [PATCH 4/9] dissect: store image name, following usual parsing rules The name of '/foo/bar/baz.raw' name is 'baz' --- src/shared/dissect-image.c | 32 +++++++++++++++++++++++++++++++- src/shared/dissect-image.h | 1 + 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 62050daecd..d557b46e90 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -34,6 +34,7 @@ #include "hexdecoct.h" #include "hostname-setup.h" #include "id128-util.h" +#include "import-util.h" #include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" @@ -472,7 +473,7 @@ int dissect_image( _cleanup_(blkid_free_probep) blkid_probe b = NULL; _cleanup_free_ char *generic_node = NULL; sd_id128_t generic_uuid = SD_ID128_NULL; - const char *pttype = NULL; + const char *pttype = NULL, *sysname = NULL; blkid_partlist pl; int r, generic_nr, n_partitions; struct stat st; @@ -579,6 +580,34 @@ int dissect_image( if (!m) return -ENOMEM; + r = sd_device_get_sysname(d, &sysname); + if (r < 0) + return log_debug_errno(r, "Failed to get device sysname: %m"); + if (startswith(sysname, "loop")) { + _cleanup_free_ char *name_stripped = NULL; + const char *full_path; + + r = sd_device_get_sysattr_value(d, "loop/backing_file", &full_path); + if (r < 0) + log_debug_errno(r, "Failed to lookup image name via loop device backing file sysattr, ignoring: %m"); + else { + r = raw_strip_suffixes(basename(full_path), &name_stripped); + if (r < 0) + return r; + } + + free_and_replace(m->image_name, name_stripped); + } else { + r = free_and_strdup(&m->image_name, sysname); + if (r < 0) + return r; + } + + if (!image_name_is_valid(m->image_name)) { + log_debug("Image name %s is not valid, ignoring", strempty(m->image_name)); + m->image_name = mfree(m->image_name); + } + if ((!(flags & DISSECT_IMAGE_GPT_ONLY) && (flags & DISSECT_IMAGE_REQUIRE_ROOT)) || (flags & DISSECT_IMAGE_NO_PARTITION_TABLE)) { @@ -1197,6 +1226,7 @@ DissectedImage* dissected_image_unref(DissectedImage *m) { free(m->partitions[i].mount_options); } + free(m->image_name); free(m->hostname); strv_free(m->machine_info); strv_free(m->os_release); diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 70363ded48..fc120b29b4 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -97,6 +97,7 @@ struct DissectedImage { DissectedPartition partitions[_PARTITION_DESIGNATOR_MAX]; + char *image_name; char *hostname; sd_id128_t machine_id; char **machine_info; From 7718ac97219d3bcfcc79b5a10e3e46c2099154d5 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 27 Jan 2021 13:03:37 +0000 Subject: [PATCH 5/9] dissect: parse, store and show extension-release info --- src/dissect/dissect.c | 21 +++++++++++-- src/shared/dissect-image.c | 62 ++++++++++++++++++++++++++++++-------- src/shared/dissect-image.h | 1 + 3 files changed, 69 insertions(+), 15 deletions(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index f8f34dcc4d..85102d3efa 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -412,13 +412,23 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) { *p, *q); } + if (!strv_isempty(m->extension_release)) { + char **p, **q; + + STRV_FOREACH_PAIR(p, q, m->extension_release) + printf("%s %s=%s\n", + p == m->extension_release ? "Extension Release:" : " ", + *p, *q); + } + if (m->hostname || !sd_id128_is_null(m->machine_id) || !strv_isempty(m->machine_info) || + !strv_isempty(m->extension_release) || !strv_isempty(m->os_release)) putc('\n', stdout); } else { - _cleanup_(json_variant_unrefp) JsonVariant *mi = NULL, *osr = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *mi = NULL, *osr = NULL, *exr = NULL; if (!strv_isempty(m->machine_info)) { r = strv_pair_to_json(m->machine_info, &mi); @@ -432,13 +442,20 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) { return log_oom(); } + if (!strv_isempty(m->extension_release)) { + r = strv_pair_to_json(m->extension_release, &exr); + if (r < 0) + return log_oom(); + } + r = json_build(&v, JSON_BUILD_OBJECT( JSON_BUILD_PAIR("name", JSON_BUILD_STRING(basename(arg_image))), JSON_BUILD_PAIR("size", JSON_BUILD_INTEGER(size)), JSON_BUILD_PAIR_CONDITION(m->hostname, "hostname", JSON_BUILD_STRING(m->hostname)), JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->machine_id), "machineId", JSON_BUILD_ID128(m->machine_id)), JSON_BUILD_PAIR_CONDITION(mi, "machineInfo", JSON_BUILD_VARIANT(mi)), - JSON_BUILD_PAIR_CONDITION(osr, "osRelease", JSON_BUILD_VARIANT(osr)))); + JSON_BUILD_PAIR_CONDITION(osr, "osRelease", JSON_BUILD_VARIANT(osr)), + JSON_BUILD_PAIR_CONDITION(exr, "extensionRelease", JSON_BUILD_VARIANT(exr)))); if (r < 0) return log_oom(); } diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index d557b46e90..2799f4f268 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -23,6 +23,7 @@ #include "def.h" #include "device-nodes.h" #include "device-util.h" +#include "discover-image.h" #include "dissect-image.h" #include "dm-util.h" #include "env-file.h" @@ -1230,6 +1231,7 @@ DissectedImage* dissected_image_unref(DissectedImage *m) { free(m->hostname); strv_free(m->machine_info); strv_free(m->os_release); + strv_free(m->extension_release); return mfree(m); } @@ -1400,7 +1402,7 @@ int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift, /* Returns: * * -ENXIO → No root partition found - * -EMEDIUMTYPE → DISSECT_IMAGE_VALIDATE_OS set but no os-release file found + * -EMEDIUMTYPE → DISSECT_IMAGE_VALIDATE_OS set but no os-release/extension-release file found * -EUNATCH → Encrypted partition found for which no dm-crypt was set up yet * -EUCLEAN → fsck for file system failed * -EBUSY → File system already mounted/used elsewhere (kernel) @@ -1430,8 +1432,13 @@ int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift, r = path_is_os_tree(where); if (r < 0) return r; - if (r == 0) - return -EMEDIUMTYPE; + if (r == 0) { + r = path_is_extension_tree(where, m->image_name); + if (r < 0) + return r; + if (r == 0) + return -EMEDIUMTYPE; + } } } @@ -1511,7 +1518,7 @@ int dissected_image_mount_and_warn(DissectedImage *m, const char *where, uid_t u if (r == -ENXIO) return log_error_errno(r, "Not root file system found in image."); if (r == -EMEDIUMTYPE) - return log_error_errno(r, "No suitable os-release file in image found."); + return log_error_errno(r, "No suitable os-release/extension-release file in image found."); if (r == -EUNATCH) return log_error_errno(r, "Encrypted file system discovered, but decryption not requested."); if (r == -EUCLEAN) @@ -2237,18 +2244,20 @@ int dissected_image_acquire_metadata(DissectedImage *m) { META_MACHINE_ID, META_MACHINE_INFO, META_OS_RELEASE, + META_EXTENSION_RELEASE, _META_MAX, }; - static const char *const paths[_META_MAX] = { - [META_HOSTNAME] = "/etc/hostname\0", - [META_MACHINE_ID] = "/etc/machine-id\0", - [META_MACHINE_INFO] = "/etc/machine-info\0", - [META_OS_RELEASE] = ("/etc/os-release\0" - "/usr/lib/os-release\0"), + static const char *paths[_META_MAX] = { + [META_HOSTNAME] = "/etc/hostname\0", + [META_MACHINE_ID] = "/etc/machine-id\0", + [META_MACHINE_INFO] = "/etc/machine-info\0", + [META_OS_RELEASE] = ("/etc/os-release\0" + "/usr/lib/os-release\0"), + [META_EXTENSION_RELEASE] = NULL, }; - _cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL; + _cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL, **extension_release = NULL; _cleanup_close_pair_ int error_pipe[2] = { -1, -1 }; _cleanup_(rmdir_and_freep) char *t = NULL; _cleanup_(sigkill_waitp) pid_t child = 0; @@ -2262,11 +2271,21 @@ int dissected_image_acquire_metadata(DissectedImage *m) { assert(m); - for (; n_meta_initialized < _META_MAX; n_meta_initialized ++) + /* As per the os-release spec, if the image is an extension it will have a file + * named after the image name in extension-release.d/ */ + if (m->image_name) + paths[META_EXTENSION_RELEASE] = strjoina("/usr/lib/extension-release.d/extension-release.", m->image_name); + else + log_debug("No image name available, will skip extension-release metadata"); + + for (; n_meta_initialized < _META_MAX; n_meta_initialized ++) { + if (!paths[n_meta_initialized]) + continue; if (pipe2(fds + 2*n_meta_initialized, O_CLOEXEC) < 0) { r = -errno; goto finish; } + } r = mkdtemp_malloc("/tmp/dissect-XXXXXX", &t); if (r < 0) @@ -2296,6 +2315,9 @@ int dissected_image_acquire_metadata(DissectedImage *m) { _cleanup_close_ int fd = -ENOENT; const char *p; + if (!paths[k]) + continue; + fds[2*k] = safe_close(fds[2*k]); NULSTR_FOREACH(p, paths[k]) { @@ -2326,6 +2348,9 @@ int dissected_image_acquire_metadata(DissectedImage *m) { for (k = 0; k < _META_MAX; k++) { _cleanup_fclose_ FILE *f = NULL; + if (!paths[k]) + continue; + fds[2*k+1] = safe_close(fds[2*k+1]); f = take_fdopen(&fds[2*k], "r"); @@ -2376,6 +2401,13 @@ int dissected_image_acquire_metadata(DissectedImage *m) { log_debug_errno(r, "Failed to read OS release file: %m"); break; + + case META_EXTENSION_RELEASE: + r = load_env_file_pairs(f, "extension-release", &extension_release); + if (r < 0) + log_debug_errno(r, "Failed to read extension release file: %m"); + + break; } } @@ -2399,10 +2431,14 @@ int dissected_image_acquire_metadata(DissectedImage *m) { m->machine_id = machine_id; strv_free_and_replace(m->machine_info, machine_info); strv_free_and_replace(m->os_release, os_release); + strv_free_and_replace(m->extension_release, extension_release); finish: - for (k = 0; k < n_meta_initialized; k++) + for (k = 0; k < n_meta_initialized; k++) { + if (!paths[k]) + continue; safe_close_pair(fds + 2*k); + } return r; } diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index fc120b29b4..8907850000 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -102,6 +102,7 @@ struct DissectedImage { sd_id128_t machine_id; char **machine_info; char **os_release; + char **extension_release; }; struct MountOptions { From bcf94222a5e3492eadff5d1d9ba711402c192f58 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 27 Jan 2021 12:59:45 +0000 Subject: [PATCH 6/9] machine: parse and store extension-release Follow the same pattern as os-release parsing, and store the key-value pairs in a strv if found --- src/shared/discover-image.c | 9 ++++++++- src/shared/discover-image.h | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index 1f5e4c6f86..fbce201e29 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -71,6 +71,7 @@ static Image *image_free(Image *i) { free(i->hostname); strv_free(i->machine_info); strv_free(i->os_release); + strv_free(i->extension_release); return mfree(i); } @@ -1129,7 +1130,7 @@ int image_read_metadata(Image *i) { case IMAGE_SUBVOLUME: case IMAGE_DIRECTORY: { - _cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL; + _cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL, **extension_release = NULL; sd_id128_t machine_id = SD_ID128_NULL; _cleanup_free_ char *hostname = NULL; _cleanup_free_ char *path = NULL; @@ -1176,10 +1177,15 @@ int image_read_metadata(Image *i) { if (r < 0) log_debug_errno(r, "Failed to read os-release in image, ignoring: %m"); + r = load_extension_release_pairs(i->path, i->name, &extension_release); + if (r < 0) + log_debug_errno(r, "Failed to read extension-release in image, ignoring: %m"); + free_and_replace(i->hostname, hostname); i->machine_id = machine_id; strv_free_and_replace(i->machine_info, machine_info); strv_free_and_replace(i->os_release, os_release); + strv_free_and_replace(i->extension_release, extension_release); break; } @@ -1205,6 +1211,7 @@ int image_read_metadata(Image *i) { i->machine_id = m->machine_id; strv_free_and_replace(i->machine_info, m->machine_info); strv_free_and_replace(i->os_release, m->os_release); + strv_free_and_replace(i->extension_release, m->extension_release); break; } diff --git a/src/shared/discover-image.h b/src/shared/discover-image.h index a266d4abdb..af67d167c4 100644 --- a/src/shared/discover-image.h +++ b/src/shared/discover-image.h @@ -50,6 +50,7 @@ typedef struct Image { sd_id128_t machine_id; char **machine_info; char **os_release; + char **extension_release; bool metadata_valid:1; bool discoverable:1; /* true if we know for sure that image_find() would find the image given just the short name */ From 7eda2d7fa5a3e2ffa9b74945e51c564cb4a4bf0f Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 27 Jan 2021 12:54:15 +0000 Subject: [PATCH 7/9] os-util: split extension_release_validate out of sysext --- src/shared/os-util.c | 67 +++++++++++++++++++++++++++++++++ src/shared/os-util.h | 5 +++ src/sysext/sysext.c | 90 ++++++++++++++++++-------------------------- 3 files changed, 109 insertions(+), 53 deletions(-) diff --git a/src/shared/os-util.c b/src/shared/os-util.c index 2916dfeba4..53679c9597 100644 --- a/src/shared/os-util.c +++ b/src/shared/os-util.c @@ -3,6 +3,7 @@ #include "alloc-util.h" #include "discover-image.h" #include "env-file.h" +#include "env-util.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" @@ -192,3 +193,69 @@ int load_extension_release_pairs(const char *root, const char *extension, char * return load_env_file_pairs(f, p, ret); } + +int extension_release_validate( + const char *name, + const char *host_os_release_id, + const char *host_os_release_version_id, + const char *host_os_release_sysext_level, + char **extension_release) { + + const char *extension_release_id = NULL, *extension_release_sysext_level = NULL; + + assert(name); + assert(!isempty(host_os_release_id)); + assert(!isempty(host_os_release_version_id) || !isempty(host_os_release_sysext_level)); + + /* Now that we can look into the extension image, let's see if the OS version is compatible */ + if (strv_isempty(extension_release)) { + log_debug("Extension '%s' carries no extension-release data, ignoring extension.", name); + return 0; + } + + extension_release_id = strv_env_pairs_get(extension_release, "ID"); + if (isempty(extension_release_id)) { + log_debug("Extension '%s' does not contain ID in extension-release but requested to match '%s'", + name, strna(host_os_release_id)); + return 0; + } + + if (!streq_ptr(host_os_release_id, extension_release_id)) { + log_debug("Extension '%s' is for OS '%s', but deployed on top of '%s'.", + name, strna(extension_release_id), strna(host_os_release_id)); + return 0; + } + + /* If the extension has a sysext API level declared, then it must match the host API + * level. Otherwise, compare OS version as a whole */ + extension_release_sysext_level = strv_env_pairs_get(extension_release, "SYSEXT_LEVEL"); + if (!isempty(host_os_release_sysext_level) && !isempty(extension_release_sysext_level)) { + if (!streq_ptr(host_os_release_sysext_level, extension_release_sysext_level)) { + log_debug("Extension '%s' is for sysext API level '%s', but running on sysext API level '%s'", + name, strna(extension_release_sysext_level), strna(host_os_release_sysext_level)); + return 0; + } + } else if (!isempty(host_os_release_version_id)) { + const char *extension_release_version_id; + + extension_release_version_id = strv_env_pairs_get(extension_release, "VERSION_ID"); + if (isempty(extension_release_version_id)) { + log_debug("Extension '%s' does not contain VERSION_ID in extension-release but requested to match '%s'", + name, strna(host_os_release_version_id)); + return 0; + } + + if (!streq_ptr(host_os_release_version_id, extension_release_version_id)) { + log_debug("Extension '%s' is for OS '%s', but deployed on top of '%s'.", + name, strna(extension_release_version_id), strna(host_os_release_version_id)); + return 0; + } + } else if (isempty(host_os_release_version_id) && isempty(host_os_release_sysext_level)) { + /* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */ + log_debug("No version info on the host (rolling release?), but ID in %s matched.", name); + return 1; + } + + log_debug("Version info of extension '%s' matches host.", name); + return 1; +} diff --git a/src/shared/os-util.h b/src/shared/os-util.h index bdb9e6adea..d4bff5e48e 100644 --- a/src/shared/os-util.h +++ b/src/shared/os-util.h @@ -26,3 +26,8 @@ int parse_os_release(const char *root, ...) _sentinel_; int load_extension_release_pairs(const char *root, const char *extension, char ***ret); int load_os_release_pairs(const char *root, char ***ret); int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret); + +/* Given an image name (for logging purposes), a set of os-release values from the host + * and a key-value pair vector of extension-release variables, check that the distro and + * (system extension level or distro version) match and return 1, and 0 otherwise. */ +int extension_release_validate(const char *name, const char *host_os_release_id, const char *host_os_release_version_id, const char *host_os_release_sysext_level, char **extension_release); diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 57a36d478a..141c64993e 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -403,16 +403,15 @@ static int strverscmp_improvedp(char *const* a, char *const* b) { static int validate_version( const char *root, - const char *name, + const Image *img, const char *host_os_release_id, const char *host_os_release_version_id, const char *host_os_release_sysext_level) { - _cleanup_free_ char *extension_release_id = NULL, *extension_release_version_id = NULL, *extension_release_sysext_level = NULL; int r; assert(root); - assert(name); + assert(img); if (arg_force) { log_debug("Force mode enabled, skipping version validation."); @@ -430,45 +429,12 @@ static int validate_version( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extension image contains /usr/lib/os-release file, which is not allowed (it may carry /etc/os-release), refusing."); - /* Now that we can look into the extension image, let's see if the OS version is compatible */ - r = parse_extension_release( - root, - name, - "ID", &extension_release_id, - "VERSION_ID", &extension_release_version_id, - "SYSEXT_LEVEL", &extension_release_sysext_level, - NULL); - if (r == -ENOENT) { - log_notice_errno(r, "Extension '%s' carries no extension-release data, ignoring extension.", name); - return 0; - } - if (r < 0) - return log_error_errno(r, "Failed to acquire 'os-release' data of extension '%s': %m", name); - - if (!streq_ptr(host_os_release_id, extension_release_id)) { - log_notice("Extension '%s' is for OS '%s', but running on '%s', ignoring extension.", - name, strna(extension_release_id), strna(host_os_release_id)); - return 0; - } - - /* If the extension has a sysext API level declared, then it must match the host API - * level. Otherwise, compare OS version as a whole */ - if (extension_release_sysext_level) { - if (!streq_ptr(host_os_release_sysext_level, extension_release_sysext_level)) { - log_notice("Extension '%s' is for sysext API level '%s', but running on sysext API level '%s', ignoring extension.", - name, extension_release_sysext_level, strna(host_os_release_sysext_level)); - return 0; - } - } else { - if (!streq_ptr(host_os_release_version_id, extension_release_version_id)) { - log_notice("Extension '%s' is for OS version '%s', but running on OS version '%s', ignoring extension.", - name, extension_release_version_id, strna(host_os_release_version_id)); - return 0; - } - } - - log_debug("Version info of extension '%s' matches host.", name); - return 1; + return extension_release_validate( + img->name, + host_os_release_id, + host_os_release_version_id, + host_os_release_sysext_level, + img->extension_release); } static int merge_subprocess(Hashmap *images, const char *workspace) { @@ -596,7 +562,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) { r = validate_version( p, - img->name, + img, host_os_release_id, host_os_release_version_id, host_os_release_sysext_level); @@ -742,13 +708,12 @@ static int merge(Hashmap *images) { return r != 123; /* exit code 123 means: didn't do anything */ } -static int verb_merge(int argc, char **argv, void *userdata) { +static int image_discover_and_read_metadata(Hashmap **ret_images) { _cleanup_(hashmap_freep) Hashmap *images = NULL; - char **p; + Image *img; int r; - if (!have_effective_cap(CAP_SYS_ADMIN)) - return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged."); + assert(ret_images); images = hashmap_new(&image_hash_ops); if (!images) @@ -758,6 +723,29 @@ static int verb_merge(int argc, char **argv, void *userdata) { if (r < 0) return log_error_errno(r, "Failed to discover extension images: %m"); + HASHMAP_FOREACH(img, images) { + r = image_read_metadata(img); + if (r < 0) + return log_error_errno(r, "Failed to read metadata for image %s: %m", img->name); + } + + *ret_images = TAKE_PTR(images); + + return 0; +} + +static int verb_merge(int argc, char **argv, void *userdata) { + _cleanup_(hashmap_freep) Hashmap *images = NULL; + char **p; + int r; + + if (!have_effective_cap(CAP_SYS_ADMIN)) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged."); + + r = image_discover_and_read_metadata(&images); + if (r < 0) + return r; + /* 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) { @@ -789,13 +777,9 @@ static int verb_refresh(int argc, char **argv, void *userdata) { if (!have_effective_cap(CAP_SYS_ADMIN)) return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged."); - images = hashmap_new(&image_hash_ops); - if (!images) - return log_oom(); - - r = image_discover(IMAGE_EXTENSION, arg_root, images); + r = image_discover_and_read_metadata(&images); if (r < 0) - return log_error_errno(r, "Failed to discover extension images: %m"); + 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 From d335f4c5833387e5e779d629086defa46818592c Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 12 Feb 2021 15:30:10 +0000 Subject: [PATCH 8/9] os-util: allow missing VERSION_ID on the host Rolling releases, like ArchLinux, do not set VERSION_ID in their os-release files, so allow matching simply on ID if the host does not provide anything. --- src/shared/os-util.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/shared/os-util.c b/src/shared/os-util.c index 53679c9597..9aed3b5df4 100644 --- a/src/shared/os-util.c +++ b/src/shared/os-util.c @@ -205,7 +205,6 @@ int extension_release_validate( assert(name); assert(!isempty(host_os_release_id)); - assert(!isempty(host_os_release_version_id) || !isempty(host_os_release_sysext_level)); /* Now that we can look into the extension image, let's see if the OS version is compatible */ if (strv_isempty(extension_release)) { @@ -226,6 +225,12 @@ int extension_release_validate( return 0; } + /* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */ + if (isempty(host_os_release_version_id) && isempty(host_os_release_sysext_level)) { + log_debug("No version info on the host (rolling release?), but ID in %s matched.", name); + return 1; + } + /* If the extension has a sysext API level declared, then it must match the host API * level. Otherwise, compare OS version as a whole */ extension_release_sysext_level = strv_env_pairs_get(extension_release, "SYSEXT_LEVEL"); From 93547f2812f457312a8b13b640e42f4f671ef244 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 19 Jan 2021 17:11:55 +0000 Subject: [PATCH 9/9] env-util: refactor parsing helper for SYSTEMD_SYSEXT_HIERARCHIES out of sysext --- src/basic/env-util.c | 42 ++++++++++++++++++++++++++++++++++++++ src/basic/env-util.h | 4 ++++ src/sysext/sysext.c | 48 ++++++-------------------------------------- 3 files changed, 52 insertions(+), 42 deletions(-) diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 137d6b1f3c..df24cb935d 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -12,6 +12,7 @@ #include "extract-word.h" #include "macro.h" #include "parse-util.h" +#include "path-util.h" #include "process-util.h" #include "stdio-util.h" #include "string-util.h" @@ -787,3 +788,44 @@ int setenv_systemd_exec_pid(bool update_only) { return 1; } + +int getenv_path_list(const char *name, char ***ret_paths) { + _cleanup_strv_free_ char **l = NULL; + const char *e; + char **p; + int r; + + assert(name); + assert(ret_paths); + + *ret_paths = NULL; + + e = secure_getenv(name); + if (!e) + return 0; + + r = strv_split_full(&l, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return log_debug_errno(r, "Failed to parse $%s: %m", name); + + STRV_FOREACH(p, l) { + if (!path_is_absolute(*p)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Path '%s' is not absolute, refusing.", *p); + + if (!path_is_normalized(*p)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Path '%s' is not normalized, refusing.", *p); + + if (path_equal(*p, "/")) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Path '%s' is the root fs, refusing.", *p); + } + + if (strv_isempty(l)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "No paths specified, refusing."); + + *ret_paths = TAKE_PTR(l); + return 0; +} diff --git a/src/basic/env-util.h b/src/basic/env-util.h index 79307a4a5f..7ba8488af7 100644 --- a/src/basic/env-util.h +++ b/src/basic/env-util.h @@ -59,3 +59,7 @@ int getenv_bool_secure(const char *p); int set_unset_env(const char *name, const char *value, bool overwrite); int setenv_systemd_exec_pid(bool update_only); + +/* Parses and does sanity checks on an environment variable containing + * PATH-like colon-separated absolute paths */ +int getenv_path_list(const char *name, char ***ret_paths); diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 141c64993e..cd824887bf 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -8,6 +8,7 @@ #include "capability-util.h" #include "discover-image.h" #include "dissect-image.h" +#include "env-util.h" #include "escape.h" #include "fd-util.h" #include "fileio.h" @@ -954,46 +955,6 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -static int parse_env(void) { - _cleanup_strv_free_ char **l = NULL; - const char *e; - char **p; - int r; - - e = secure_getenv("SYSTEMD_SYSEXT_HIERARCHIES"); - if (!e) - return 0; - - /* 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 = strv_split_full(&l, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return log_error_errno(r, "Failed to parse $SYSTEMD_SYSEXT_HIERARCHIES: %m"); - - STRV_FOREACH(p, l) { - if (!path_is_absolute(*p)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Hierarchy path '%s' is not absolute, refusing.", *p); - - if (!path_is_normalized(*p)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Hierarchy path '%s' is not normalized, refusing.", *p); - - if (path_equal(*p, "/")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Hierarchy path '%s' is the root fs, refusing.", *p); - } - - if (strv_isempty(l)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "No hierarchies specified, refusing."); - - strv_free_and_replace(arg_hierarchies, l); - return 0; -} - static int sysext_main(int argc, char *argv[]) { static const Verb verbs[] = { @@ -1018,9 +979,12 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - r = parse_env(); + /* 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 = getenv_path_list("SYSTEMD_SYSEXT_HIERARCHIES", &arg_hierarchies); if (r < 0) - return r; + return log_error_errno(r, "Failed to parse $SYSTEMD_SYSEXT_HIERARCHIES environment variable: %m"); if (!arg_hierarchies) { arg_hierarchies = strv_new("/usr", "/opt");