From: Luca Boccassi Date: Wed, 27 Jan 2021 12:54:15 +0000 (+0000) Subject: os-util: split extension_release_validate out of sysext X-Git-Tag: v248-rc1~66^2~2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=7eda2d7fa5a3e2ffa9b74945e51c564cb4a4bf0f;p=thirdparty%2Fsystemd.git os-util: split extension_release_validate out of sysext --- diff --git a/src/shared/os-util.c b/src/shared/os-util.c index 2916dfeba4b..53679c9597d 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 bdb9e6adea9..d4bff5e48ef 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 57a36d478a2..141c64993e8 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