]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysext: Refactor the merge hierarchy code
authorKrzesimir Nowak <knowak@microsoft.com>
Thu, 15 Feb 2024 14:12:24 +0000 (15:12 +0100)
committerKrzesimir Nowak <knowak@microsoft.com>
Thu, 22 Feb 2024 18:06:22 +0000 (19:06 +0100)
Divide the merge_hierarchy function into code that:

- determines the lower directories for overlayfs

  - determination of lower directories was further split into top, middle and
    bottom directories:

    - bottom - possibly the hierarchy itself

    - middle - hierarchies from extensions

    - top - metadata directory

- mounts the overlayfs using determined directories

- writes information to the metadata directory

- makes the merged hierarchy read-only

src/sysext/sysext.c

index b64209267998549d8609c3651aae5ed43a9f48b0..7581b736c305a389200469751abef18b657b7acf 100644 (file)
@@ -540,129 +540,356 @@ static int mount_overlayfs(
         return 0;
 }
 
-static int merge_hierarchy(
-                ImageClass image_class,
-                const char *hierarchy,
-                int noexec,
-                char **extensions,
-                char **paths,
-                const char *meta_path,
-                const char *overlay_path) {
+typedef struct OverlayFSPaths {
+        char *hierarchy;
+        char *resolved_hierarchy;
 
-        _cleanup_free_ char *resolved_hierarchy = NULL, *f = NULL, *buf = NULL;
-        _cleanup_strv_free_ char **layers = NULL;
-        struct stat st;
+        /* lowest index is top lowerdir, highest index is bottom lowerdir */
+        char **lower_dirs;
+} OverlayFSPaths;
+
+static OverlayFSPaths *overlayfs_paths_free(OverlayFSPaths *op) {
+        if (!op)
+                return NULL;
+
+        free(op->hierarchy);
+        free(op->resolved_hierarchy);
+
+        strv_free(op->lower_dirs);
+
+        free(op);
+        return NULL;
+}
+DEFINE_TRIVIAL_CLEANUP_FUNC(OverlayFSPaths *, overlayfs_paths_free);
+
+static int resolve_hierarchy(const char *hierarchy, char **ret_resolved_hierarchy) {
+        _cleanup_free_ char *resolved_path = NULL;
         int r;
 
         assert(hierarchy);
-        assert(meta_path);
-        assert(overlay_path);
+        assert(ret_resolved_hierarchy);
 
-        /* Resolve the path of the host's version of the hierarchy, i.e. what we want to use as lowest layer
-         * in the overlayfs stack. */
-        r = chase(hierarchy, arg_root, CHASE_PREFIX_ROOT, &resolved_hierarchy, NULL);
-        if (r == -ENOENT)
-                log_debug_errno(r, "Hierarchy '%s' on host doesn't exist, not merging.", hierarchy);
-        else if (r < 0)
-                return log_error_errno(r, "Failed to resolve host hierarchy '%s': %m", hierarchy);
-        else {
-                r = dir_is_empty(resolved_hierarchy, /* ignore_hidden_or_backup= */ false);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to check if host hierarchy '%s' is empty: %m", resolved_hierarchy);
-                if (r > 0) {
-                        log_debug("Host hierarchy '%s' is empty, not merging.", resolved_hierarchy);
-                        resolved_hierarchy = mfree(resolved_hierarchy);
-                }
-        }
+        r = chase(hierarchy, arg_root, CHASE_PREFIX_ROOT, &resolved_path, NULL);
+        if (r < 0 && r != -ENOENT)
+                return log_error_errno(r, "Failed to resolve hierarchy '%s': %m", hierarchy);
 
-        /* Let's generate a metadata file that lists all extensions we took into account for this
-         * hierarchy. We include this in the final fs, to make things nicely discoverable and
-         * recognizable. */
-        f = path_join(meta_path, image_class_info[image_class].dot_directory_name, image_class_info[image_class].short_identifier_plural);
-        if (!f)
-                return log_oom();
+        *ret_resolved_hierarchy = TAKE_PTR(resolved_path);
+        return 0;
+}
 
-        buf = strv_join(extensions, "\n");
-        if (!buf)
+static int overlayfs_paths_new(const char *hierarchy, OverlayFSPaths **ret_op) {
+        _cleanup_free_ char *hierarchy_copy = NULL, *resolved_hierarchy = NULL, *resolved_mutable_directory = NULL;
+        int r;
+
+        assert (hierarchy);
+        assert (ret_op);
+
+        hierarchy_copy = strdup(hierarchy);
+        if (!hierarchy_copy)
                 return log_oom();
 
-        r = write_string_file(f, buf, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755);
+        r = resolve_hierarchy(hierarchy, &resolved_hierarchy);
         if (r < 0)
-                return log_error_errno(r, "Failed to write extension meta file '%s': %m", f);
+                return r;
+
+        OverlayFSPaths *op;
+        op = new(OverlayFSPaths, 1);
+        if (!op)
+                return log_oom();
+
+        *op = (OverlayFSPaths) {
+                .hierarchy = TAKE_PTR(hierarchy_copy),
+                .resolved_hierarchy = TAKE_PTR(resolved_hierarchy),
+        };
+
+        *ret_op = TAKE_PTR(op);
+        return 0;
+}
+
+static int determine_top_lower_dirs(OverlayFSPaths *op, const char *meta_path) {
+        int r;
+
+        assert(op);
+        assert(meta_path);
 
         /* Put the meta path (i.e. our synthesized stuff) at the top of the layer stack */
-        layers = strv_new(meta_path);
-        if (!layers)
+        r = strv_extend(&op->lower_dirs, meta_path);
+        if (r < 0)
                 return log_oom();
 
+        return 0;
+}
+
+static int determine_middle_lower_dirs(OverlayFSPaths *op, char **paths, size_t *ret_extensions_used) {
+        size_t n = 0;
+        int r;
+
+        assert(op);
+        assert(paths);
+        assert(ret_extensions_used);
+
         /* Put the extensions in the middle */
         STRV_FOREACH(p, paths) {
                 _cleanup_free_ char *resolved = NULL;
 
-                r = chase(hierarchy, *p, CHASE_PREFIX_ROOT, &resolved, NULL);
+                r = chase(op->hierarchy, *p, CHASE_PREFIX_ROOT, &resolved, NULL);
                 if (r == -ENOENT) {
-                        log_debug_errno(r, "Hierarchy '%s' in extension '%s' doesn't exist, not merging.", hierarchy, *p);
+                        log_debug_errno(r, "Hierarchy '%s' in extension '%s' doesn't exist, not merging.", op->hierarchy, *p);
                         continue;
                 }
                 if (r < 0)
-                        return log_error_errno(r, "Failed to resolve hierarchy '%s' in extension '%s': %m", hierarchy, *p);
+                        return log_error_errno(r, "Failed to resolve hierarchy '%s' in extension '%s': %m", op->hierarchy, *p);
 
                 r = dir_is_empty(resolved, /* ignore_hidden_or_backup= */ false);
                 if (r < 0)
                         return log_error_errno(r, "Failed to check if hierarchy '%s' in extension '%s' is empty: %m", resolved, *p);
                 if (r > 0) {
-                        log_debug("Hierarchy '%s' in extension '%s' is empty, not merging.", hierarchy, *p);
+                        log_debug("Hierarchy '%s' in extension '%s' is empty, not merging.", op->hierarchy, *p);
                         continue;
                 }
 
-                r = strv_consume(&layers, TAKE_PTR(resolved));
+                r = strv_consume(&op->lower_dirs, TAKE_PTR(resolved));
                 if (r < 0)
                         return log_oom();
+                ++n;
         }
 
-        if (!layers[1]) /* No extension with files in this hierarchy? Then don't do anything. */
-                return 0;
+        *ret_extensions_used = n;
+        return 0;
+}
+
+static int hierarchy_as_lower_dir(OverlayFSPaths *op) {
+        int r;
+
+        /* return 0 if hierarchy should be used as lower dir, >0, if not */
+
+        assert(op);
+
+        if (!op->resolved_hierarchy) {
+                log_debug("Host hierarchy '%s' does not exist, will not be used as lowerdir", op->hierarchy);
+                return 1;
+        }
+
+        r = dir_is_empty(op->resolved_hierarchy, /* ignore_hidden_or_backup= */ false);
+        if (r < 0)
+                return log_error_errno(r, "Failed to check if host hierarchy '%s' is empty: %m", op->resolved_hierarchy);
+        if (r > 0) {
+                log_debug("Host hierarchy '%s' is empty, will not be used as lower dir.", op->resolved_hierarchy);
+                return 1;
+        }
 
-        if (resolved_hierarchy) {
-                /* Add the host hierarchy as last (lowest) layer in the stack */
-                r = strv_consume(&layers, TAKE_PTR(resolved_hierarchy));
+        return 0;
+}
+
+static int determine_bottom_lower_dirs(OverlayFSPaths *op) {
+        int r;
+
+        assert(op);
+
+        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)
-                        return log_oom();
+                        return r;
         }
 
+        return 0;
+}
+
+static int determine_lower_dirs(
+                OverlayFSPaths *op,
+                char **paths,
+                const char *meta_path,
+                size_t *ret_extensions_used) {
+
+        int r;
+
+        assert(op);
+        assert(paths);
+        assert(meta_path);
+        assert(ret_extensions_used);
+
+        r = determine_top_lower_dirs(op, meta_path);
+        if (r < 0)
+                return r;
+
+        r = determine_middle_lower_dirs(op, paths, ret_extensions_used);
+        if (r < 0)
+                return r;
+
+        r = determine_bottom_lower_dirs(op);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int mount_overlayfs_with_op(
+                OverlayFSPaths *op,
+                ImageClass image_class,
+                int noexec,
+                const char *overlay_path,
+                const char *meta_path) {
+
+        int r;
+
+        assert(op);
+        assert(overlay_path);
+
         r = mkdir_p(overlay_path, 0700);
         if (r < 0)
                 return log_error_errno(r, "Failed to make directory '%s': %m", overlay_path);
 
-        r = mount_overlayfs(image_class, noexec, overlay_path, layers);
+        r = mkdir_p(meta_path, 0700);
+        if (r < 0)
+                return log_error_errno(r, "Failed to make directory '%s': %m", meta_path);
+
+        r = mount_overlayfs(image_class, noexec, overlay_path, op->lower_dirs);
         if (r < 0)
                 return r;
 
-        /* The overlayfs superblock is read-only. Let's also mark the bind mount read-only. Extra turbo safety ðŸ˜Ž */
-        r = bind_remount_recursive(overlay_path, MS_RDONLY, MS_RDONLY, NULL);
+        return 0;
+}
+
+static int write_extensions_file(ImageClass image_class, char **extensions, const char *meta_path) {
+        _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
+         * hierarchy. We include this in the final fs, to make things nicely discoverable and
+         * recognizable. */
+        f = path_join(meta_path, image_class_info[image_class].dot_directory_name, image_class_info[image_class].short_identifier_plural);
+        if (!f)
+                return log_oom();
+
+        buf = strv_join(extensions, "\n");
+        if (!buf)
+                return log_oom();
+
+        r = write_string_file(f, buf, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755);
         if (r < 0)
-                return log_error_errno(r, "Failed to make bind mount '%s' read-only: %m", overlay_path);
+                return log_error_errno(r, "Failed to write extension meta file '%s': %m", f);
+
+        return 0;
+}
+
+static int write_dev_file(ImageClass image_class, const char *meta_path, const char *overlay_path) {
+        _cleanup_free_ char *f = NULL;
+        struct stat st;
+        int r;
+
+        assert(meta_path);
+        assert(overlay_path);
 
         /* Now we have mounted the new file system. Let's now figure out its .st_dev field, and make that
          * available in the metadata directory. This is useful to detect whether the metadata dir actually
          * belongs to the fs it is found on: if .st_dev of the top-level mount matches it, it's pretty likely
          * we are looking at a live tree, and not an unpacked tar or so of one. */
         if (stat(overlay_path, &st) < 0)
-                return log_error_errno(r, "Failed to stat mount '%s': %m", overlay_path);
+                return log_error_errno(errno, "Failed to stat mount '%s': %m", overlay_path);
 
-        free(f);
         f = path_join(meta_path, image_class_info[image_class].dot_directory_name, "dev");
         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 */
         r = write_string_file(f, FORMAT_DEVNUM(st.st_dev), WRITE_STRING_FILE_CREATE);
         if (r < 0)
                 return log_error_errno(r, "Failed to write '%s': %m", f);
 
+        return 0;
+}
+
+static int store_info_in_meta(
+                ImageClass image_class,
+                char **extensions,
+                const char *meta_path,
+                const char *overlay_path) {
+
+        int r;
+
+        assert(extensions);
+        assert(meta_path);
+        assert(overlay_path);
+
+        r = write_extensions_file(image_class, extensions, meta_path);
+        if (r < 0)
+                return r;
+
+        r = write_dev_file(image_class, meta_path, overlay_path);
+        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);
 
+        return 0;
+}
+
+static int make_mounts_read_only(ImageClass image_class, const char *overlay_path) {
+        int r;
+
+        assert(overlay_path);
+
+        /* The overlayfs superblock is read-only. Let's also mark the bind mount read-only. Extra turbo
+         * safety ðŸ˜Ž */
+        r = bind_remount_recursive(overlay_path, MS_RDONLY, MS_RDONLY, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to make bind mount '%s' read-only: %m", overlay_path);
+
+        return 0;
+}
+
+static int merge_hierarchy(
+                ImageClass image_class,
+                const char *hierarchy,
+                int noexec,
+                char **extensions,
+                char **paths,
+                const char *meta_path,
+                const char *overlay_path) {
+
+        _cleanup_(overlayfs_paths_freep) OverlayFSPaths *op = NULL;
+        size_t extensions_used = 0;
+        int r;
+
+        assert(hierarchy);
+        assert(extensions);
+        assert(paths);
+        assert(meta_path);
+        assert(overlay_path);
+
+        r = overlayfs_paths_new(hierarchy, &op);
+        if (r < 0)
+                return r;
+
+        r = determine_lower_dirs(op, paths, meta_path, &extensions_used);
+        if (r < 0)
+                return r;
+
+        if (extensions_used == 0) /* No extension with files in this hierarchy? Then don't do anything. */
+                return 0;
+
+        r = mount_overlayfs_with_op(op, image_class, noexec, overlay_path, meta_path);
+        if (r < 0)
+                return r;
+
+        r = store_info_in_meta(image_class, extensions, meta_path, overlay_path);
+        if (r < 0)
+                return r;
+
+        r = make_mounts_read_only(image_class, overlay_path);
+        if (r < 0)
+                return r;
+
         return 1;
 }