Merge pull request #18625 from bluca/sysext_refactor

dissect: parse and store extension-release metadata
This commit is contained in:
Luca Boccassi
2021-02-18 10:43:08 +00:00
committed by GitHub
11 changed files with 324 additions and 117 deletions
+8 -1
View File
@@ -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;
}
+1
View File
@@ -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 */
+80 -14
View File
@@ -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;
}
+2
View File
@@ -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 {
+88 -3
View File
@@ -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;
}
+12 -2
View File
@@ -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);