mirror of
https://github.com/systemd/systemd.git
synced 2024-10-31 16:21:26 +03:00
Merge pull request #18625 from bluca/sysext_refactor
dissect: parse and store extension-release metadata
This commit is contained in:
commit
905348da28
@ -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"
|
||||
@ -455,6 +456,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;
|
||||
@ -775,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;
|
||||
}
|
||||
|
@ -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);
|
||||
@ -58,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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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"
|
||||
@ -34,6 +35,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 +474,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 +581,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,9 +1227,11 @@ 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);
|
||||
strv_free(m->extension_release);
|
||||
|
||||
return mfree(m);
|
||||
}
|
||||
@ -1370,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)
|
||||
@ -1400,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1481,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)
|
||||
@ -2207,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;
|
||||
@ -2232,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)
|
||||
@ -2266,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]) {
|
||||
@ -2296,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");
|
||||
@ -2346,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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2369,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;
|
||||
}
|
||||
|
@ -97,10 +97,12 @@ struct DissectedImage {
|
||||
|
||||
DissectedPartition partitions[_PARTITION_DESIGNATOR_MAX];
|
||||
|
||||
char *image_name;
|
||||
char *hostname;
|
||||
sd_id128_t machine_id;
|
||||
char **machine_info;
|
||||
char **os_release;
|
||||
char **extension_release;
|
||||
};
|
||||
|
||||
struct MountOptions {
|
||||
|
@ -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"
|
||||
@ -11,7 +12,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 +23,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)
|
||||
@ -179,3 +181,86 @@ 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);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* 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");
|
||||
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;
|
||||
}
|
||||
|
@ -3,10 +3,14 @@
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
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);
|
||||
@ -19,5 +23,11 @@ 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);
|
||||
|
||||
/* 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);
|
||||
|
@ -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"
|
||||
@ -403,16 +404,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 +430,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 +563,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 +709,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 +724,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 +778,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
|
||||
@ -970,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[] = {
|
||||
@ -1034,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");
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user