]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
discover-image: add support for vpick
authorLennart Poettering <lennart@poettering.net>
Wed, 15 Nov 2023 17:36:24 +0000 (18:36 +0100)
committerLennart Poettering <lennart@poettering.net>
Wed, 3 Jan 2024 17:38:46 +0000 (18:38 +0100)
src/shared/discover-image.c

index 3baa84c8bd52b0523e6b8b5023b796c391c9e9af..e7e3a4d71c68c83754f8d885410ee5691c6e1649 100644 (file)
@@ -45,6 +45,7 @@
 #include "strv.h"
 #include "time-util.h"
 #include "utf8.h"
+#include "vpick.h"
 #include "xattr-util.h"
 
 static const char* const image_search_path[_IMAGE_CLASS_MAX] = {
@@ -215,40 +216,60 @@ static int image_new(
         return 0;
 }
 
-static int extract_pretty(
+static int extract_image_basename(
                 const char *path,
-                const char *class_suffix,
-                const char *format_suffix,
-                char **ret) {
+                const char *class_suffix,  /* e.g. ".sysext" (this is an optional suffix) */
+                char **format_suffixes,    /* e.g. ".raw"    (one of these will be required) */
+                char **ret_basename,
+                char **ret_suffix) {
 
-        _cleanup_free_ char *name = NULL;
+        _cleanup_free_ char *name = NULL, *suffix = NULL;
         int r;
 
         assert(path);
-        assert(ret);
 
         r = path_extract_filename(path, &name);
         if (r < 0)
                 return r;
 
-        if (format_suffix) {
-                char *e = endswith(name, format_suffix);
+        if (format_suffixes) {
+                char *e = strv_endswith(name, format_suffixes);
                 if (!e) /* Format suffix is required */
                         return -EINVAL;
 
+                if (ret_suffix) {
+                        suffix = strdup(e);
+                        if (!suffix)
+                                return -ENOMEM;
+                }
+
                 *e = 0;
         }
 
         if (class_suffix) {
                 char *e = endswith(name, class_suffix);
-                if (e) /* Class suffix is optional */
+                if (e) { /* Class suffix is optional */
+                        if (ret_suffix) {
+                                _cleanup_free_ char *j = strjoin(e, suffix);
+                                if (!j)
+                                        return -ENOMEM;
+
+                                free_and_replace(suffix, j);
+                        }
+
                         *e = 0;
+                }
         }
 
         if (!image_name_is_valid(name))
                 return -EINVAL;
 
-        *ret = TAKE_PTR(name);
+        if (ret_suffix)
+                *ret_suffix = TAKE_PTR(suffix);
+
+        if (ret_basename)
+                *ret_basename = TAKE_PTR(name);
+
         return 0;
 }
 
@@ -303,7 +324,12 @@ static int image_make(
                         return 0;
 
                 if (!pretty) {
-                        r = extract_pretty(filename, image_class_suffix_to_string(c), NULL, &pretty_buffer);
+                        r = extract_image_basename(
+                                        filename,
+                                        image_class_suffix_to_string(c),
+                                        /* format_suffix= */ NULL,
+                                        &pretty_buffer,
+                                        /* ret_suffix= */ NULL);
                         if (r < 0)
                                 return r;
 
@@ -390,7 +416,12 @@ static int image_make(
                 (void) fd_getcrtime_at(dfd, filename, AT_SYMLINK_FOLLOW, &crtime);
 
                 if (!pretty) {
-                        r = extract_pretty(filename, image_class_suffix_to_string(c), ".raw", &pretty_buffer);
+                        r = extract_image_basename(
+                                        filename,
+                                        image_class_suffix_to_string(c),
+                                        STRV_MAKE(".raw"),
+                                        &pretty_buffer,
+                                        /* ret_suffix= */ NULL);
                         if (r < 0)
                                 return r;
 
@@ -424,7 +455,12 @@ static int image_make(
                         return 0;
 
                 if (!pretty) {
-                        r = extract_pretty(filename, NULL, NULL, &pretty_buffer);
+                        r = extract_image_basename(
+                                        filename,
+                                        /* class_suffix= */ NULL,
+                                        /* format_suffix= */ NULL,
+                                        &pretty_buffer,
+                                        /* ret_suffix= */ NULL);
                         if (r < 0)
                                 return r;
 
@@ -488,6 +524,37 @@ static const char *pick_image_search_path(ImageClass class) {
         return in_initrd() && image_search_path_initrd[class] ? image_search_path_initrd[class] : image_search_path[class];
 }
 
+static char **make_possible_filenames(ImageClass class, const char *image_name) {
+        _cleanup_strv_free_ char **l = NULL;
+
+        assert(image_name);
+
+        FOREACH_STRING(v_suffix, "", ".v")
+                FOREACH_STRING(format_suffix, "", ".raw") {
+                        _cleanup_free_ char *j = NULL;
+                        const char *class_suffix;
+
+                        class_suffix = image_class_suffix_to_string(class);
+                        if (class_suffix) {
+                                j = strjoin(image_name, class_suffix, format_suffix, v_suffix);
+                                if (!j)
+                                        return NULL;
+
+                                if (strv_consume(&l, TAKE_PTR(j)) < 0)
+                                        return NULL;
+                        }
+
+                        j = strjoin(image_name, format_suffix, v_suffix);
+                        if (!j)
+                                return NULL;
+
+                        if (strv_consume(&l, TAKE_PTR(j)) < 0)
+                                return NULL;
+                }
+
+        return TAKE_PTR(l);
+}
+
 int image_find(ImageClass class,
                const char *name,
                const char *root,
@@ -503,6 +570,10 @@ int image_find(ImageClass class,
         if (!image_name_is_valid(name))
                 return -ENOENT;
 
+        _cleanup_strv_free_ char **names = make_possible_filenames(class, name);
+        if (!names)
+                return -ENOMEM;
+
         NULSTR_FOREACH(path, pick_image_search_path(class)) {
                 _cleanup_free_ char *resolved = NULL;
                 _cleanup_closedir_ DIR *d = NULL;
@@ -519,43 +590,97 @@ int image_find(ImageClass class,
                  * to symlink block devices into the search path. (For now, we disable that when operating
                  * relative to some root directory.) */
                 flags = root ? AT_SYMLINK_NOFOLLOW : 0;
-                if (fstatat(dirfd(d), name, &st, flags) < 0) {
-                        _cleanup_free_ char *raw = NULL;
 
-                        if (errno != ENOENT)
-                                return -errno;
+                STRV_FOREACH(n, names) {
+                        _cleanup_free_ char *fname_buf = NULL;
+                        const char *fname = *n;
 
-                        raw = strjoin(name, ".raw");
-                        if (!raw)
-                                return -ENOMEM;
+                        if (fstatat(dirfd(d), fname, &st, flags) < 0) {
+                                if (errno != ENOENT)
+                                        return -errno;
 
-                        if (fstatat(dirfd(d), raw, &st, flags) < 0) {
-                                if (errno == ENOENT)
+                                continue; /* Vanished while we were looking at it */
+                        }
+
+                        if (endswith(fname, ".raw")) {
+                                if (!S_ISREG(st.st_mode)) {
+                                        log_debug("Ignoring non-regular file '%s' with .raw suffix.", fname);
                                         continue;
+                                }
 
-                                return -errno;
-                        }
+                        } else if (endswith(fname, ".v")) {
 
-                        if (!S_ISREG(st.st_mode))
-                                continue;
+                                if (!S_ISDIR(st.st_mode)) {
+                                        log_debug("Ignoring non-directory file '%s' with .v suffix.", fname);
+                                        continue;
+                                }
+
+                                _cleanup_free_ char *suffix = NULL;
+                                suffix = strdup(ASSERT_PTR(startswith(fname, name)));
+                                if (!suffix)
+                                        return -ENOMEM;
+
+                                *ASSERT_PTR(endswith(suffix, ".v")) = 0;
+
+                                _cleanup_free_ char *vp = path_join(resolved, fname);
+                                if (!vp)
+                                        return -ENOMEM;
+
+                                PickFilter filter = {
+                                        .type_mask = endswith(suffix, ".raw") ? (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) : (UINT32_C(1) << DT_DIR),
+                                        .basename = name,
+                                        .architecture = _ARCHITECTURE_INVALID,
+                                        .suffix = suffix,
+                                };
+
+                                _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
+                                r = path_pick(root,
+                                              /* toplevel_fd= */ AT_FDCWD,
+                                              vp,
+                                              &filter,
+                                              PICK_ARCHITECTURE|PICK_TRIES,
+                                              &result);
+                                if (r < 0) {
+                                        log_debug_errno(r, "Failed to pick versioned image on '%s', skipping: %m", vp);
+                                        continue;
+                                }
+                                if (!result.path) {
+                                        log_debug("Found versioned directory '%s', without matching entry, skipping: %m", vp);
+                                        continue;
+                                }
+
+                                /* Refresh the stat data for the discovered target */
+                                st = result.st;
+
+                                _cleanup_free_ char *bn = NULL;
+                                r = path_extract_filename(result.path, &bn);
+                                if (r < 0) {
+                                        log_debug_errno(r, "Failed to extract basename of image path '%s', skipping: %m", result.path);
+                                        continue;
+                                }
+
+                                fname_buf = path_join(fname, bn);
+                                if (!fname_buf)
+                                        return log_oom();
 
-                        r = image_make(class, name, dirfd(d), resolved, raw, &st, ret);
+                                fname = fname_buf;
 
-                } else {
-                        if (!S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode))
+                        } else if (!S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode)) {
+                                log_debug("Ignoring non-directory and non-block device file '%s' without suffix.", fname);
                                 continue;
+                        }
 
-                        r = image_make(class, name, dirfd(d), resolved, name, &st, ret);
-                }
-                if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
-                        continue;
-                if (r < 0)
-                        return r;
+                        r = image_make(class, name, dirfd(d), resolved, fname, &st, ret);
+                        if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
+                                continue;
+                        if (r < 0)
+                                return r;
 
-                if (ret)
-                        (*ret)->discoverable = true;
+                        if (ret)
+                                (*ret)->discoverable = true;
 
-                return 1;
+                        return 1;
+                }
         }
 
         if (class == IMAGE_MACHINE && streq(name, ".host")) {
@@ -566,7 +691,7 @@ int image_find(ImageClass class,
                 if (ret)
                         (*ret)->discoverable = true;
 
-                return r;
+                return 1;
         }
 
         return -ENOENT;
@@ -613,43 +738,133 @@ int image_discover(
                         return r;
 
                 FOREACH_DIRENT_ALL(de, d, return -errno) {
+                        _cleanup_free_ char *pretty = NULL, *fname_buf = NULL;
                         _cleanup_(image_unrefp) Image *image = NULL;
-                        _cleanup_free_ char *pretty = NULL;
+                        const char *fname = de->d_name;
                         struct stat st;
                         int flags;
 
-                        if (dot_or_dot_dot(de->d_name))
+                        if (dot_or_dot_dot(fname))
                                 continue;
 
                         /* As mentioned above, we follow symlinks on this fstatat(), because we want to
                          * permit people to symlink block devices into the search path. */
                         flags = root ? AT_SYMLINK_NOFOLLOW : 0;
-                        if (fstatat(dirfd(d), de->d_name, &st, flags) < 0) {
+                        if (fstatat(dirfd(d), fname, &st, flags) < 0) {
                                 if (errno == ENOENT)
                                         continue;
 
                                 return -errno;
                         }
 
-                        if (S_ISREG(st.st_mode))
-                                r = extract_pretty(de->d_name, image_class_suffix_to_string(class), ".raw", &pretty);
-                        else if (S_ISDIR(st.st_mode))
-                                r = extract_pretty(de->d_name, image_class_suffix_to_string(class), NULL, &pretty);
-                        else if (S_ISBLK(st.st_mode))
-                                r = extract_pretty(de->d_name, NULL, NULL, &pretty);
-                        else {
-                                log_debug("Skipping directory entry '%s', which is neither regular file, directory nor block device.", de->d_name);
-                                continue;
-                        }
-                        if (r < 0) {
-                                log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", de->d_name);
+                        if (S_ISREG(st.st_mode)) {
+                                r = extract_image_basename(
+                                                fname,
+                                                image_class_suffix_to_string(class),
+                                                STRV_MAKE(".raw"),
+                                                &pretty,
+                                                /* suffix= */ NULL);
+                                if (r < 0) {
+                                        log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", fname);
+                                        continue;
+                                }
+                        } else if (S_ISDIR(st.st_mode)) {
+                                const char *v;
+
+                                v = endswith(fname, ".v");
+                                if (v) {
+                                        _cleanup_free_ char *suffix = NULL, *nov = NULL;
+
+                                        nov = strndup(fname, v - fname); /* Chop off the .v */
+                                        if (!nov)
+                                                return -ENOMEM;
+
+                                        r = extract_image_basename(
+                                                        nov,
+                                                        image_class_suffix_to_string(class),
+                                                        STRV_MAKE(".raw", ""),
+                                                        &pretty,
+                                                        &suffix);
+                                        if (r < 0) {
+                                                log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like a versioned image.", fname);
+                                                continue;
+                                        }
+
+                                        _cleanup_free_ char *vp = path_join(resolved, fname);
+                                        if (!vp)
+                                                return -ENOMEM;
+
+                                        PickFilter filter = {
+                                                .type_mask = endswith(suffix, ".raw") ? (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) : (UINT32_C(1) << DT_DIR),
+                                                .basename = pretty,
+                                                .architecture = _ARCHITECTURE_INVALID,
+                                                .suffix = suffix,
+                                        };
+
+                                        _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
+                                        r = path_pick(root,
+                                                      /* toplevel_fd= */ AT_FDCWD,
+                                                      vp,
+                                                      &filter,
+                                                      PICK_ARCHITECTURE|PICK_TRIES,
+                                                      &result);
+                                        if (r < 0) {
+                                                log_debug_errno(r, "Failed to pick versioned image on '%s', skipping: %m", vp);
+                                                continue;
+                                        }
+                                        if (!result.path) {
+                                                log_debug("Found versioned directory '%s', without matching entry, skipping: %m", vp);
+                                                continue;
+                                        }
+
+                                        /* Refresh the stat data for the discovered target */
+                                        st = result.st;
+
+                                        _cleanup_free_ char *bn = NULL;
+                                        r = path_extract_filename(result.path, &bn);
+                                        if (r < 0) {
+                                                log_debug_errno(r, "Failed to extract basename of image path '%s', skipping: %m", result.path);
+                                                continue;
+                                        }
+
+                                        fname_buf = path_join(fname, bn);
+                                        if (!fname_buf)
+                                                return log_oom();
+
+                                        fname = fname_buf;
+                                } else {
+                                        r = extract_image_basename(
+                                                        fname,
+                                                        image_class_suffix_to_string(class),
+                                                        /* format_suffix= */ NULL,
+                                                        &pretty,
+                                                        /* ret_suffix= */ NULL);
+                                        if (r < 0) {
+                                                log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", fname);
+                                                continue;
+                                        }
+                                }
+
+                        } else if (S_ISBLK(st.st_mode)) {
+                                r = extract_image_basename(
+                                                fname,
+                                                /* class_suffix= */ NULL,
+                                                /* format_suffix= */ NULL,
+                                                &pretty,
+                                                /* ret_v_suffix= */ NULL);
+                                if (r < 0) {
+                                        log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", fname);
+                                        continue;
+                                }
+                        } else {
+                                log_debug("Skipping directory entry '%s', which is neither regular file, directory nor block device.", fname);
                                 continue;
                         }
 
                         if (hashmap_contains(h, pretty))
                                 continue;
 
-                        r = image_make(class, pretty, dirfd(d), resolved, de->d_name, &st, &image);
+                        r = image_make(class, pretty, dirfd(d), resolved, fname, &st, &image);
                         if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
                                 continue;
                         if (r < 0)