Skip to content

Commit

Permalink
sysext,sysupdate: resolve incompatibilities (systemd#36617)
Browse files Browse the repository at this point in the history
Fixes systemd#24562.
Fixes systemd#34445.
Replaces systemd#36311.
  • Loading branch information
poettering authored Mar 6, 2025
2 parents 729620b + de4144c commit bc07635
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 41 deletions.
107 changes: 76 additions & 31 deletions src/sysext/sysext.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "sd-bus.h"
#include "sd-varlink.h"

#include "blockdev-util.h"
#include "build.h"
#include "bus-error.h"
#include "bus-locator.h"
Expand Down Expand Up @@ -1045,7 +1046,7 @@ static int determine_used_extensions(const char *hierarchy, char **paths, char *
continue;
}

r = strv_consume_with_size (&used_paths, &n, TAKE_PTR(resolved));
r = strv_consume_with_size(&used_paths, &n, TAKE_PTR(resolved));
if (r < 0)
return log_oom();
}
Expand Down Expand Up @@ -1143,15 +1144,11 @@ static int determine_top_lower_dirs(OverlayFSPaths *op, const char *meta_path) {
}

static int determine_middle_lower_dirs(OverlayFSPaths *op, char **paths) {
int r;

assert(op);
assert(paths);

/* The paths were already determined in determine_used_extensions, so we just take them as is. */
r = strv_extend_strv(&op->lower_dirs, paths, false);
if (r < 0)
return log_oom ();
if (strv_extend_strv(&op->lower_dirs, paths, /* filter_duplicates= */ false) < 0)
return log_oom();

return 0;
}
Expand Down Expand Up @@ -1202,33 +1199,39 @@ static int hierarchy_as_lower_dir(OverlayFSPaths *op) {
return 0;
}

static int determine_bottom_lower_dirs(OverlayFSPaths *op) {
static int determine_bottom_lower_dirs(OverlayFSPaths *op, dev_t *ret_backing_devnum) {
int r;

assert(op);
assert(ret_backing_devnum);

r = hierarchy_as_lower_dir(op);
if (r < 0)
return r;
if (!r) {
r = strv_extend(&op->lower_dirs, op->resolved_hierarchy);
if (r < 0)
if (strv_extend(&op->lower_dirs, op->resolved_hierarchy) < 0)
return log_oom();
}

r = get_block_device(op->resolved_hierarchy, ret_backing_devnum);
if (r < 0)
return log_error_errno(r, "Failed to get block device of '%s': %m", op->resolved_hierarchy);
} else
*ret_backing_devnum = 0;

return 0;
}

static int determine_lower_dirs(
OverlayFSPaths *op,
char **paths,
const char *meta_path) {
const char *meta_path,
dev_t *ret_backing_devnum) {

int r;

assert(op);
assert(paths);
assert(meta_path);
assert(ret_backing_devnum);

r = determine_top_lower_dirs(op, meta_path);
if (r < 0)
Expand All @@ -1238,7 +1241,7 @@ static int determine_lower_dirs(
if (r < 0)
return r;

r = determine_bottom_lower_dirs(op);
r = determine_bottom_lower_dirs(op, ret_backing_devnum);
if (r < 0)
return r;

Expand Down Expand Up @@ -1363,7 +1366,6 @@ static int write_extensions_file(ImageClass image_class, char **extensions, cons
_cleanup_free_ char *f = NULL, *buf = NULL;
int r;

assert(extensions);
assert(meta_path);

/* Let's generate a metadata file that lists all extensions we took into account for this
Expand All @@ -1373,15 +1375,20 @@ static int write_extensions_file(ImageClass image_class, char **extensions, cons
if (!f)
return log_oom();

buf = strv_join(extensions, "\n");
if (!buf)
return log_oom();
if (!strv_isempty(extensions)) {
buf = strv_join(extensions, "\n");
if (!buf)
return log_oom();

if (!strextend(&buf, "\n")) /* manually append newline since we want to suppress newline if zero extensions are applied */
return log_oom();
}

_cleanup_free_ char *hierarchy_path = path_join(hierarchy, image_class_info[image_class].dot_directory_name, image_class_info[image_class].short_identifier_plural);
if (!hierarchy_path)
return log_oom();

r = write_string_file_full(AT_FDCWD,f, buf, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_LABEL, /* ts= */ NULL, hierarchy_path);
r = write_string_file_full(AT_FDCWD, f, strempty(buf), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_LABEL|WRITE_STRING_FILE_AVOID_NEWLINE, /* ts= */ NULL, hierarchy_path);
if (r < 0)
return log_error_errno(r, "Failed to write extension meta file '%s': %m", f);

Expand Down Expand Up @@ -1421,6 +1428,35 @@ static int write_dev_file(ImageClass image_class, const char *meta_path, const c
return 0;
}

static int write_backing_file(ImageClass image_class, const char *meta_path, const char *overlay_path, const char *hierarchy, dev_t backing) {
int r;

assert(meta_path);
assert(overlay_path);
assert(hierarchy);

if (backing == 0)
return 0;

/* Let's also store away the backing file system dev_t, so that applications can trace back what they are looking at here */
_cleanup_free_ char *f = path_join(meta_path, image_class_info[image_class].dot_directory_name, "backing");
if (!f)
return log_oom();

/* Modifying the underlying layers while the overlayfs is mounted is technically undefined, but at
* least it won't crash or deadlock, as per the kernel docs about overlayfs:
* https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html#changes-to-underlying-filesystems */
_cleanup_free_ char *hierarchy_path = path_join(hierarchy, image_class_info[image_class].dot_directory_name, image_class_info[image_class].short_identifier_plural);
if (!hierarchy_path)
return log_oom();

r = write_string_file_full(AT_FDCWD, f, FORMAT_DEVNUM(backing), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_LABEL, /* ts= */ NULL, hierarchy_path);
if (r < 0)
return log_error_errno(r, "Failed to write '%s': %m", f);

return 0;
}

static int write_work_dir_file(ImageClass image_class, const char *meta_path, const char *work_dir, const char* hierarchy) {
_cleanup_free_ char *escaped_work_dir_in_root = NULL, *f = NULL;
char *work_dir_in_root = NULL;
Expand Down Expand Up @@ -1466,11 +1502,11 @@ static int store_info_in_meta(
const char *meta_path,
const char *overlay_path,
const char *work_dir,
const char *hierarchy) {
const char *hierarchy,
dev_t backing) {

int r;

assert(extensions);
assert(meta_path);
assert(overlay_path);
/* work_dir may be NULL */
Expand Down Expand Up @@ -1503,6 +1539,10 @@ static int store_info_in_meta(
if (r < 0)
return r;

r = write_backing_file(image_class, meta_path, overlay_path, hierarchy, backing);
if (r < 0)
return r;

/* Make sure the top-level dir has an mtime marking the point we established the merge */
if (utimensat(AT_FDCWD, meta_path, NULL, AT_SYMLINK_NOFOLLOW) < 0)
return log_error_errno(r, "Failed fix mtime of '%s': %m", meta_path);
Expand Down Expand Up @@ -1559,7 +1599,6 @@ static int merge_hierarchy(
int r;

assert(hierarchy);
assert(extensions);
assert(paths);
assert(meta_path);
assert(overlay_path);
Expand All @@ -1571,14 +1610,15 @@ static int merge_hierarchy(
if (r < 0)
return r;

if (extensions_used == 0) /* No extension with files in this hierarchy? Then don't do anything. */
if (extensions_used == 0 && arg_mutable == MUTABLE_NO) /* No extension with files in this hierarchy? Then don't do anything. */
return 0;

r = overlayfs_paths_new(hierarchy, workspace_path, &op);
if (r < 0)
return r;

r = determine_lower_dirs(op, used_paths, meta_path);
dev_t backing;
r = determine_lower_dirs(op, used_paths, meta_path, &backing);
if (r < 0)
return r;

Expand All @@ -1594,7 +1634,7 @@ static int merge_hierarchy(
if (r < 0)
return r;

r = store_info_in_meta(image_class, extensions, meta_path, overlay_path, op->work_dir, op->hierarchy);
r = store_info_in_meta(image_class, extensions, meta_path, overlay_path, op->work_dir, op->hierarchy, backing);
if (r < 0)
return r;

Expand Down Expand Up @@ -1644,7 +1684,7 @@ static int merge_subprocess(
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, *filename = NULL;
_cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL, *host_os_release_api_level = NULL, *filename = NULL;
_cleanup_strv_free_ char **extensions = NULL, **extensions_v = NULL, **paths = NULL;
size_t n_extensions = 0;
unsigned n_ignored = 0;
Expand Down Expand Up @@ -1843,7 +1883,7 @@ static int merge_subprocess(
}

/* Nothing left? Then shortcut things */
if (n_extensions == 0) {
if (n_extensions == 0 && arg_mutable == MUTABLE_NO) {
if (n_ignored > 0)
log_info("No suitable extensions found (%u ignored due to incompatible image(s)).", n_ignored);
else
Expand All @@ -1855,11 +1895,16 @@ static int merge_subprocess(
typesafe_qsort(extensions, n_extensions, strverscmp_improvedp);
typesafe_qsort(extensions_v, n_extensions, strverscmp_improvedp);

buf = strv_join(extensions_v, "', '");
if (!buf)
return log_oom();
if (n_extensions == 0) {
assert(arg_mutable != MUTABLE_NO);
log_info("No extensions found, proceeding in mutable mode.");
} else {
_cleanup_free_ char *buf = strv_join(extensions_v, "', '");
if (!buf)
return log_oom();

log_info("Using extensions '%s'.", buf);
log_info("Using extensions '%s'.", buf);
}

/* Build table of extension paths (in reverse order) */
paths = new0(char*, n_extensions + 1);
Expand Down
65 changes: 55 additions & 10 deletions src/sysupdate/sysupdate-resource.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "hexdecoct.h"
#include "import-util.h"
#include "macro.h"
#include "missing_magic.h"
#include "process-util.h"
#include "sort-util.h"
#include "string-table.h"
Expand Down Expand Up @@ -552,6 +553,47 @@ Instance* resource_find_instance(Resource *rr, const char *version) {
return *found;
}

static int get_sysext_overlay_block(const char *p, dev_t *ret) {
int r;

assert(p);
assert(ret);

/* Tries to read the backing device information systemd-sysext puts in the virtual file
* /usr/.systemd-sysext/backing */

_cleanup_free_ char *j = path_join(p, ".systemd-sysext");
if (!j)
return log_oom_debug();

_cleanup_close_ int fd = open(j, O_RDONLY|O_DIRECTORY);
if (fd < 0)
return log_debug_errno(errno, "Failed to open '%s': %m", j);

r = fd_is_fs_type(fd, OVERLAYFS_SUPER_MAGIC);
if (r < 0)
return log_debug_errno(r, "Failed to determine backing file system of '%s': %m", j);
if (r == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTTY), "Backing file system of '%s' is not an overlayfs.", j);

_cleanup_free_ char *buf = NULL;
r = read_one_line_file_at(fd, "backing", &buf);
if (r < 0)
return log_debug_errno(r, "Failed to read contents of '%s/backing': %m", j);

r = parse_devnum(buf, ret);
if (r < 0)
return log_debug_errno(r, "Failed to parse contents of '%s/backing': %m", j);

if (major(*ret) == 0) { /* not a block device? */
*ret = 0;
return 0;
}

(void) block_get_originating(*ret, ret);
return 1;
}

int resource_resolve_path(
Resource *rr,
const char *root,
Expand All @@ -571,8 +613,6 @@ int resource_resolve_path(
path_relative_to_to_string(rr->path_relative_to));

if (rr->path_auto) {
struct stat orig_root_stats;

/* NB: If the root mount has been replaced by some form of volatile file system (overlayfs),
* the original root block device node is symlinked in /run/systemd/volatile-root. Let's
* follow that link here. If that doesn't exist, we check the backing device of "/usr". We
Expand All @@ -596,12 +636,18 @@ int resource_resolve_path(
return log_error_errno(SYNTHETIC_ERRNO(EPERM),
"Block device is not allowed when using --root= mode.");

r = stat("/run/systemd/volatile-root", &orig_root_stats);
struct stat orig_root_stats;
r = RET_NERRNO(stat("/run/systemd/volatile-root", &orig_root_stats));
if (r < 0) {
if (errno == ENOENT) /* volatile-root not found */
r = get_block_device_harder("/usr/", &d);
else
if (r != -ENOENT)
return log_error_errno(r, "Failed to stat /run/systemd/volatile-root: %m");

/* volatile-root not found */
r = get_block_device_harder("/usr/", &d);
if (r == 0) /* Not backed by a block device? Let's see if this is a sysext overlayfs instance */
r = get_sysext_overlay_block("/usr/", &d);
if (r < 0)
return log_error_errno(r, "Failed to determine block device of file system: %m");
} else if (!S_ISBLK(orig_root_stats.st_mode)) /* symlink was present but not block device */
return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "/run/systemd/volatile-root is not linked to a block device.");
else /* symlink was present and a block device */
Expand Down Expand Up @@ -643,7 +689,9 @@ int resource_resolve_path(
if (real_fd < 0)
return log_error_errno(real_fd, "Failed to convert O_PATH file descriptor for %s to regular file descriptor: %m", rr->path);

r = get_block_device_harder_fd(fd, &d);
r = get_block_device_harder_fd(real_fd, &d);
if (r < 0)
return log_error_errno(r, "Failed to determine block device of file system: %m");

} else if (RESOURCE_IS_FILESYSTEM(rr->type)) {
_cleanup_free_ char *resolved = NULL, *relative_to = NULL;
Expand Down Expand Up @@ -682,9 +730,6 @@ int resource_resolve_path(
} else
return 0; /* Otherwise assume there's nothing to resolve */

if (r < 0)
return log_error_errno(r, "Failed to determine block device of file system: %m");

r = block_get_whole_disk(d, &d);
if (r < 0)
return log_error_errno(r, "Failed to find whole disk device for partition backing file system: %m");
Expand Down

0 comments on commit bc07635

Please sign in to comment.