]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
vpick: Fix pick_filter_image_any
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Sat, 20 Dec 2025 20:38:09 +0000 (21:38 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 16 Jan 2026 12:07:47 +0000 (13:07 +0100)
Currently, pick_filter_image_any matches any image
with any suffix, which is way more than it should
be doing. It should only be matching images with
the .raw suffix.

Let's address this shortcoming by allowing to pass
multiple filters to path_pick(), and define
pick_filter_image_any as the combination of the
raw and directory image filters.

Fixes #40083

src/core/exec-invoke.c
src/core/namespace.c
src/dissect/dissect.c
src/nspawn/nspawn.c
src/portable/portable.c
src/shared/discover-image.c
src/shared/vpick.c
src/shared/vpick.h
src/test/test-vpick.c
src/vpick/vpick-tool.c

index d87392d53838702d769ccf876d0f726ea0e9f4ed..c8994c83340983ea1e873eae009b7445996e5a1b 100644 (file)
@@ -3669,7 +3669,8 @@ static int pick_versions(
                 r = path_pick(/* toplevel_path= */ NULL,
                               /* toplevel_fd= */ AT_FDCWD,
                               context->root_image,
-                              &pick_filter_image_raw,
+                              pick_filter_image_raw,
+                              ELEMENTSOF(pick_filter_image_raw),
                               PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
                               &result);
                 if (r < 0) {
@@ -3693,7 +3694,8 @@ static int pick_versions(
                 r = path_pick(/* toplevel_path= */ NULL,
                               /* toplevel_fd= */ AT_FDCWD,
                               context->root_directory,
-                              &pick_filter_image_dir,
+                              pick_filter_image_dir,
+                              ELEMENTSOF(pick_filter_image_dir),
                               PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
                               &result);
                 if (r < 0) {
index c35226d63902be87b5e9caa6936c38ea368b4ad8..fc36d79863fe0c93b8c0cdb6fc973e43764941c7 100644 (file)
@@ -566,7 +566,8 @@ static int append_extensions(
                 r = path_pick(/* toplevel_path= */ NULL,
                               /* toplevel_fd= */ AT_FDCWD,
                               m->source,
-                              &pick_filter_image_raw,
+                              pick_filter_image_raw,
+                              ELEMENTSOF(pick_filter_image_raw),
                               PICK_ARCHITECTURE|PICK_TRIES,
                               &result);
                 if (r == -ENOENT && m->ignore_enoent)
@@ -637,7 +638,8 @@ static int append_extensions(
                 r = path_pick(/* toplevel_path= */ NULL,
                               /* toplevel_fd= */ AT_FDCWD,
                               e,
-                              &pick_filter_image_dir,
+                              pick_filter_image_dir,
+                              ELEMENTSOF(pick_filter_image_dir),
                               PICK_ARCHITECTURE|PICK_TRIES,
                               &result);
                 if (r == -ENOENT && ignore_enoent)
index 8883dc6e263362638b10637ee4c0195b0c140c40..346472ef0d5592d422b32b0e9a42e3a30d200a96 100644 (file)
@@ -2041,7 +2041,8 @@ static int run(int argc, char *argv[]) {
         if (arg_image) {
                 r = path_pick_update_warn(
                                 &arg_image,
-                                &pick_filter_image_raw,
+                                pick_filter_image_raw,
+                                ELEMENTSOF(pick_filter_image_raw),
                                 PICK_ARCHITECTURE|PICK_TRIES,
                                 /* ret_result= */ NULL);
                 if (r < 0)
@@ -2051,7 +2052,8 @@ static int run(int argc, char *argv[]) {
         if (arg_root) {
                 r = path_pick_update_warn(
                                 &arg_root,
-                                &pick_filter_image_dir,
+                                pick_filter_image_dir,
+                                ELEMENTSOF(pick_filter_image_dir),
                                 PICK_ARCHITECTURE|PICK_TRIES,
                                 /* ret_result= */ NULL);
                 if (r < 0)
index bb90cc1c9404d79e944e8b6fdfccd3878516f01c..b388cbd7ed601a4df84d0e786a36ec87b6d926e1 100644 (file)
@@ -3013,13 +3013,14 @@ static int pick_paths(void) {
 
         if (arg_directory) {
                 _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
-                PickFilter filter = pick_filter_image_dir;
+                PickFilter filter = *pick_filter_image_dir;
 
                 filter.architecture = arg_architecture;
 
                 r = path_pick_update_warn(
                                 &arg_directory,
                                 &filter,
+                                /* n_filters= */ 1,
                                 PICK_ARCHITECTURE|PICK_TRIES,
                                 &result);
                 if (r < 0) {
@@ -3032,13 +3033,14 @@ static int pick_paths(void) {
 
         if (arg_image) {
                 _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
-                PickFilter filter = pick_filter_image_raw;
+                PickFilter filter = *pick_filter_image_raw;
 
                 filter.architecture = arg_architecture;
 
                 r = path_pick_update_warn(
                                 &arg_image,
                                 &filter,
+                                /* n_filters= */ 1,
                                 PICK_ARCHITECTURE|PICK_TRIES,
                                 &result);
                 if (r < 0)
@@ -3049,13 +3051,14 @@ static int pick_paths(void) {
 
         if (arg_template) {
                 _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
-                PickFilter filter = pick_filter_image_dir;
+                PickFilter filter = *pick_filter_image_dir;
 
                 filter.architecture = arg_architecture;
 
                 r = path_pick_update_warn(
                                 &arg_template,
                                 &filter,
+                                /* n_filters= */ 1,
                                 PICK_ARCHITECTURE,
                                 &result);
                 if (r < 0)
index cc1269de1e3c9c3064d65b600dd6c5e371d07356..b7cb9af1ff7cffcb26b78059e2c5b247fd245b7f 100644 (file)
@@ -595,7 +595,8 @@ static int extract_image_and_extensions(
                 r = path_pick(/* toplevel_path= */ NULL,
                               /* toplevel_fd= */ AT_FDCWD,
                               name_or_path,
-                              &pick_filter_image_any,
+                              pick_filter_image_any,
+                              ELEMENTSOF(pick_filter_image_any),
                               PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
                               &result);
                 if (r < 0)
@@ -633,7 +634,8 @@ static int extract_image_and_extensions(
                                 r = path_pick(/* toplevel_path= */ NULL,
                                               /* toplevel_fd= */ AT_FDCWD,
                                               *p,
-                                              &pick_filter_image_any,
+                                              pick_filter_image_any,
+                                              ELEMENTSOF(pick_filter_image_any),
                                               PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
                                               &ext_result);
                                 if (r < 0)
@@ -1782,7 +1784,8 @@ static bool marker_matches_images(const char *marker, const char *name_or_path,
                         r = path_pick(/* toplevel_path= */ NULL,
                                       /* toplevel_fd= */ AT_FDCWD,
                                       *image_name_or_path,
-                                      &pick_filter_image_any,
+                                      pick_filter_image_any,
+                                      ELEMENTSOF(pick_filter_image_any),
                                       PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
                                       &result);
                         if (r < 0)
index 0be11229e05c2a995af62c889e2c197e31c4c9bc..69163f8878fa90e5c7c5f7f0f8176c26232bf6ed 100644 (file)
@@ -821,6 +821,7 @@ int image_find(RuntimeScope scope,
                                               /* toplevel_fd= */ AT_FDCWD,
                                               vp,
                                               &filter,
+                                              /* n_filters= */ 1,
                                               PICK_ARCHITECTURE|PICK_TRIES,
                                               &result);
                                 if (r < 0) {
@@ -1033,6 +1034,7 @@ int image_discover(
                                                       /* toplevel_fd= */ AT_FDCWD,
                                                       vp,
                                                       &filter,
+                                                      /* n_filters= */ 1,
                                                       PICK_ARCHITECTURE|PICK_TRIES,
                                                       &result);
                                         if (r < 0) {
index 07d9d9ffd8cb0ea28c35aabdaf60770de5cc836a..d8fae8aa77ff6298bbd1793c93a0dd6b8d487531 100644 (file)
@@ -26,6 +26,53 @@ void pick_result_done(PickResult *p) {
         *p = PICK_RESULT_NULL;
 }
 
+int pick_result_compare(const PickResult *a, const PickResult *b, PickFlags flags) {
+        int d;
+
+        assert(a);
+        assert(b);
+
+        /* Returns > 0 if 'a' is the better pick, < 0 if 'b' is the better pick, 0 if they are equal. */
+
+        /* Prefer entries with tries left over those without */
+        if (FLAGS_SET(flags, PICK_TRIES))
+                d = CMP(a->tries_left != 0, b->tries_left != 0);
+        else
+                d = 0;
+
+        /* Prefer newer versions */
+        if (d == 0)
+                d = strverscmp_improved(a->version, b->version);
+
+        if (FLAGS_SET(flags, PICK_ARCHITECTURE)) {
+                /* Prefer native architectures over non-native architectures */
+                if (d == 0)
+                        d = CMP(a->architecture == native_architecture(), b->architecture == native_architecture());
+
+                /* Prefer secondary architectures over other architectures */
+#ifdef ARCHITECTURE_SECONDARY
+                if (d == 0)
+                        d = CMP(a->architecture == ARCHITECTURE_SECONDARY, b->architecture == ARCHITECTURE_SECONDARY);
+#endif
+        }
+
+        /* Prefer entries with more tries left */
+        if (FLAGS_SET(flags, PICK_TRIES)) {
+                if (d == 0)
+                        d = CMP(a->tries_left, b->tries_left);
+
+                /* Prefer entries with fewer attempts done so far */
+                if (d == 0)
+                        d = -CMP(a->tries_done, b->tries_done);
+        }
+
+        /* Finally, just compare the filenames as strings */
+        if (d == 0)
+                d = path_compare_filename(a->path, b->path);
+
+        return d;
+}
+
 static int format_fname(
                 const PickFilter *filter,
                 PickFlags flags,
@@ -169,13 +216,13 @@ static int pin_choice(
                 return log_debug_errno(errno, "Failed to stat discovered inode '%s%s': %m",
                                        empty_to_root(toplevel_path), skip_leading_slash(inode_path));
 
-        if (filter->type_mask != 0 &&
-            !BIT_SET(filter->type_mask, IFTODT(st.st_mode)))
-                return log_debug_errno(
-                                SYNTHETIC_ERRNO(errno_from_mode(filter->type_mask, st.st_mode)),
-                                "Inode '%s/%s' has wrong type, found '%s'.",
-                                empty_to_root(toplevel_path), skip_leading_slash(inode_path),
-                                inode_type_to_string(st.st_mode));
+        if (filter->type_mask != 0 && !BIT_SET(filter->type_mask, IFTODT(st.st_mode))) {
+                log_debug("Inode '%s/%s' has wrong type, found '%s'.",
+                          empty_to_root(toplevel_path), skip_leading_slash(inode_path),
+                          inode_type_to_string(st.st_mode));
+                *ret = PICK_RESULT_NULL;
+                return 0;
+        }
 
         _cleanup_(pick_result_done) PickResult result = {
                 .fd = TAKE_FD(inode_fd),
@@ -248,6 +295,23 @@ nomatch:
         return 0;
 }
 
+static bool architecture_matches(const PickFilter *filter, Architecture a) {
+        assert(filter);
+
+        if (filter->architecture >= 0)
+                return a == filter->architecture;
+
+        if (a == native_architecture())
+                return true;
+
+#ifdef ARCHITECTURE_SECONDARY
+        if (a == ARCHITECTURE_SECONDARY)
+                return true;
+#endif
+
+        return a == _ARCHITECTURE_INVALID;
+}
+
 static int make_choice(
                 const char *toplevel_path,
                 int toplevel_fd,
@@ -257,21 +321,7 @@ static int make_choice(
                 PickFlags flags,
                 PickResult *ret) {
 
-        static const Architecture local_architectures[] = {
-                /* In order of preference */
-                native_architecture(),
-#ifdef ARCHITECTURE_SECONDARY
-                ARCHITECTURE_SECONDARY,
-#endif
-                _ARCHITECTURE_INVALID, /* accept any arch, as last resort */
-        };
-
-        _cleanup_free_ DirectoryEntries *de = NULL;
-        _cleanup_free_ char *best_version = NULL, *best_filename = NULL, *p = NULL, *j = NULL;
-        _cleanup_close_ int dir_fd = -EBADF, object_fd = -EBADF, inode_fd = TAKE_FD(_inode_fd);
-        const Architecture *architectures;
-        unsigned best_tries_left = UINT_MAX, best_tries_done = UINT_MAX;
-        size_t n_architectures, best_architecture_index = SIZE_MAX;
+        _cleanup_close_ int inode_fd = TAKE_FD(_inode_fd);
         int r;
 
         assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD);
@@ -286,15 +336,17 @@ static int make_choice(
         }
 
         /* Maybe the filter is fully specified? Then we can generate the file name directly */
+        _cleanup_free_ char *j = NULL;
         r = format_fname(filter, flags, &j);
         if (r >= 0) {
                 _cleanup_free_ char *object_path = NULL;
 
                 /* Yay! This worked! */
-                p = path_join(inode_path, j);
+                _cleanup_free_ char *p = path_join(inode_path, j);
                 if (!p)
                         return log_oom_debug();
 
+                _cleanup_close_ int object_fd = -EBADF;
                 r = chaseat(toplevel_fd, p, CHASE_AT_RESOLVE_IN_ROOT, &object_path, &object_fd);
                 if (r == -ENOENT) {
                         *ret = PICK_RESULT_NULL;
@@ -321,28 +373,22 @@ static int make_choice(
         /* Underspecified, so we do our enumeration dance */
 
         /* Convert O_PATH to a regular directory fd */
-        dir_fd = fd_reopen(inode_fd, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
+        _cleanup_close_ int dir_fd = fd_reopen(inode_fd, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
         if (dir_fd < 0)
                 return log_debug_errno(dir_fd, "Failed to reopen '%s/%s' as directory: %m",
                                        empty_to_root(toplevel_path), skip_leading_slash(inode_path));
 
+        _cleanup_free_ DirectoryEntries *de = NULL;
         r = readdir_all(dir_fd, 0, &de);
         if (r < 0)
                 return log_debug_errno(r, "Failed to read directory '%s/%s': %m",
                                        empty_to_root(toplevel_path), skip_leading_slash(inode_path));
 
-        if (filter->architecture < 0) {
-                architectures = local_architectures;
-                n_architectures = ELEMENTSOF(local_architectures);
-        } else {
-                architectures = &filter->architecture;
-                n_architectures = 1;
-        }
+        _cleanup_(pick_result_done) PickResult best = PICK_RESULT_NULL;
 
         FOREACH_ARRAY(entry, de->entries, de->n_entries) {
                 unsigned found_tries_done = UINT_MAX, found_tries_left = UINT_MAX;
                 _cleanup_free_ char *dname = NULL;
-                size_t found_architecture_index = SIZE_MAX;
                 char *e;
 
                 dname = strdup((*entry)->d_name);
@@ -380,20 +426,16 @@ static int make_choice(
                         }
                 }
 
+                Architecture a = _ARCHITECTURE_INVALID;
                 if (FLAGS_SET(flags, PICK_ARCHITECTURE)) {
                         char *underscore = strrchr(e, '_');
-                        Architecture a;
-
-                        a = underscore ? architecture_from_string(underscore + 1) : _ARCHITECTURE_INVALID;
 
-                        for (size_t i = 0; i < n_architectures; i++)
-                                if (architectures[i] == a) {
-                                        found_architecture_index = i;
-                                        break;
-                                }
+                        if (underscore)
+                                a = architecture_from_string(underscore + 1);
 
-                        if (found_architecture_index == SIZE_MAX) { /* No matching arch found */
-                                log_debug("Found entry with architecture '%s' which is not what we are looking for, ignoring entry.", a < 0 ? "any" : architecture_to_string(a));
+                        if (!architecture_matches(filter, a)) {
+                                log_debug("Found entry with architecture '%s' which is not what we are looking for, ignoring entry.",
+                                          a < 0 ? "any" : architecture_to_string(a));
                                 continue;
                         }
 
@@ -412,89 +454,55 @@ static int make_choice(
                         continue;
                 }
 
-                if (best_filename) { /* Already found one matching entry? Then figure out the better one */
-                        int d = 0;
-
-                        /* First, prefer entries with tries left over those without */
-                        if (FLAGS_SET(flags, PICK_TRIES))
-                                d = CMP(found_tries_left != 0, best_tries_left != 0);
-
-                        /* Second, prefer newer versions */
-                        if (d == 0)
-                                d = strverscmp_improved(e, best_version);
-
-                        /* Third, prefer native architectures over secondary architectures */
-                        if (d == 0 &&
-                            FLAGS_SET(flags, PICK_ARCHITECTURE) &&
-                            found_architecture_index != SIZE_MAX && best_architecture_index != SIZE_MAX)
-                                d = -CMP(found_architecture_index, best_architecture_index);
-
-                        /* Fourth, prefer entries with more tries left */
-                        if (FLAGS_SET(flags, PICK_TRIES)) {
-                                if (d == 0)
-                                        d = CMP(found_tries_left, best_tries_left);
-
-                                /* Fifth, prefer entries with fewer attempts done so far */
-                                if (d == 0)
-                                        d = -CMP(found_tries_done, best_tries_done);
-                        }
-
-                        /* Last, just compare the filenames as strings */
-                        if (d == 0)
-                                d = strcmp((*entry)->d_name, best_filename);
-
-                        if (d < 0) {
-                                log_debug("Found entry '%s' but previously found entry '%s' matches better, hence skipping entry.", (*entry)->d_name, best_filename);
-                                continue;
-                        }
-                }
+                _cleanup_free_ char *p = path_join(inode_path, (*entry)->d_name);
+                if (!p)
+                        return log_oom_debug();
 
-                r = free_and_strdup_warn(&best_version, e);
+                _cleanup_(pick_result_done) PickResult found = PICK_RESULT_NULL;
+                r = pin_choice(toplevel_path,
+                               toplevel_fd,
+                               p,
+                               /* _inode_fd= */ -EBADF,
+                               found_tries_left,
+                               found_tries_done,
+                               &(const PickFilter) {
+                                       .type_mask = filter->type_mask,
+                                       .basename = filter->basename,
+                                       .version = empty_to_null(e),
+                                       .architecture = a,
+                                       .suffix = filter->suffix,
+                               },
+                               flags,
+                               &found);
+                if (r == 0)
+                        continue;
                 if (r < 0)
                         return r;
 
-                r = free_and_strdup_warn(&best_filename, (*entry)->d_name);
-                if (r < 0)
-                        return r;
+                if (!best.path) {
+                        best = TAKE_PICK_RESULT(found);
+                        continue;
+                }
 
-                best_architecture_index = found_architecture_index;
-                best_tries_left = found_tries_left;
-                best_tries_done = found_tries_done;
+                if (pick_result_compare(&found, &best, flags) <= 0) {
+                        log_debug("Found entry '%s' but previously found entry '%s' matches better, hence skipping entry.", found.path, best.path);
+                        continue;
+                }
+
+                pick_result_done(&best);
+                best = TAKE_PICK_RESULT(found);
         }
 
-        if (!best_filename) { /* Everything was good, but we didn't find any suitable entry */
+        if (!best.path) { /* Everything was good, but we didn't find any suitable entry */
                 *ret = PICK_RESULT_NULL;
                 return 0;
         }
 
-        p = path_join(inode_path, best_filename);
-        if (!p)
-                return log_oom_debug();
-
-        object_fd = openat(dir_fd, best_filename, O_CLOEXEC|O_PATH);
-        if (object_fd < 0)
-                return log_debug_errno(errno, "Failed to open '%s/%s': %m",
-                                       empty_to_root(toplevel_path), skip_leading_slash(inode_path));
-
-        return pin_choice(
-                        toplevel_path,
-                        toplevel_fd,
-                        p,
-                        TAKE_FD(object_fd),
-                        best_tries_left,
-                        best_tries_done,
-                        &(const PickFilter) {
-                                .type_mask = filter->type_mask,
-                                .basename = filter->basename,
-                                .version = empty_to_null(best_version),
-                                .architecture = best_architecture_index != SIZE_MAX ? architectures[best_architecture_index] : _ARCHITECTURE_INVALID,
-                                .suffix = filter->suffix,
-                        },
-                        flags,
-                        ret);
+        *ret = TAKE_PICK_RESULT(best);
+        return 1;
 }
 
-int path_pick(
+static int path_pick_one(
                 const char *toplevel_path,
                 int toplevel_fd,
                 const char *path,
@@ -644,9 +652,60 @@ bypass:
                         ret);
 }
 
+int path_pick(const char *toplevel_path,
+              int toplevel_fd,
+              const char *path,
+              const PickFilter filters[],
+              size_t n_filters,
+              PickFlags flags,
+              PickResult *ret) {
+
+        _cleanup_(pick_result_done) PickResult best = PICK_RESULT_NULL;
+        int r;
+
+        assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD);
+        assert(path);
+        assert(filters || n_filters == 0);
+        assert(ret);
+
+        /* Iterate through all filters and pick the best result */
+        for (size_t i = 0; i < n_filters; i++) {
+                _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
+
+                r = path_pick_one(toplevel_path, toplevel_fd, path, &filters[i], flags, &result);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        continue;
+
+                if (!best.path) {
+                        best = TAKE_PICK_RESULT(result);
+                        continue;
+                }
+
+                if (pick_result_compare(&result, &best, flags) <= 0) {
+                        log_debug("Found entry '%s' but previously found entry '%s' matches better, hence skipping entry.",
+                                  result.path, best.path);
+                        continue;
+                }
+
+                pick_result_done(&best);
+                best = TAKE_PICK_RESULT(result);
+        }
+
+        if (!best.path) {
+                *ret = PICK_RESULT_NULL;
+                return 0;
+        }
+
+        *ret = TAKE_PICK_RESULT(best);
+        return 1;
+}
+
 int path_pick_update_warn(
                 char **path,
-                const PickFilter *filter,
+                const PickFilter filters[],
+                size_t n_filters,
                 PickFlags flags,
                 PickResult *ret_result) {
 
@@ -655,14 +714,15 @@ int path_pick_update_warn(
 
         assert(path);
         assert(*path);
-        assert(filter);
+        assert(filters || n_filters == 0);
 
         /* This updates the first argument if needed! */
 
         r = path_pick(/* toplevel_path= */ NULL,
                       /* toplevel_fd= */ AT_FDCWD,
                       *path,
-                      filter,
+                      filters,
+                      n_filters,
                       flags,
                       &result);
         if (r == -ENOENT) {
@@ -726,19 +786,17 @@ int path_uses_vpick(const char *path) {
         return !!endswith(parent, ".v");
 }
 
-const PickFilter pick_filter_image_raw = {
-        .type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK),
-        .architecture = _ARCHITECTURE_INVALID,
-        .suffix = STRV_MAKE(".raw"),
-};
-
-const PickFilter pick_filter_image_dir = {
-        .type_mask = UINT32_C(1) << DT_DIR,
-        .architecture = _ARCHITECTURE_INVALID,
+const PickFilter pick_filter_image_raw[1] = {
+        {
+                .type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK),
+                .architecture = _ARCHITECTURE_INVALID,
+                .suffix = STRV_MAKE(".raw"),
+        },
 };
 
-const PickFilter pick_filter_image_any = {
-        .type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) | (UINT32_C(1) << DT_DIR),
-        .architecture = _ARCHITECTURE_INVALID,
-        .suffix = STRV_MAKE(".raw", ""),
+const PickFilter pick_filter_image_dir[1] = {
+        {
+                .type_mask = UINT32_C(1) << DT_DIR,
+                .architecture = _ARCHITECTURE_INVALID,
+        },
 };
index 3c23104ca9aec8a082dffd3c058ace71b281afc4..d08dc58b8fb52a3adb0d57761e9b20b14998420d 100644 (file)
@@ -43,22 +43,29 @@ typedef struct PickResult {
 
 void pick_result_done(PickResult *p);
 
-int path_pick(
-                const char *toplevel_path,
-                int toplevel_fd,
-                const char *path,
-                const PickFilter *filter,
-                PickFlags flags,
-                PickResult *ret);
+int pick_result_compare(const PickResult *a, const PickResult *b, PickFlags flags);
+
+int path_pick(const char *toplevel_path,
+              int toplevel_fd,
+              const char *path,
+              const PickFilter filters[],
+              size_t n_filters,
+              PickFlags flags,
+              PickResult *ret);
 
 int path_pick_update_warn(
                 char **path,
-                const PickFilter *filter,
+                const PickFilter filters[],
+                size_t n_filters,
                 PickFlags flags,
                 PickResult *ret_result);
 
 int path_uses_vpick(const char *path);
 
-extern const PickFilter pick_filter_image_raw;
-extern const PickFilter pick_filter_image_dir;
-extern const PickFilter pick_filter_image_any;
+extern const PickFilter pick_filter_image_raw[1];
+extern const PickFilter pick_filter_image_dir[1];
+
+#define pick_filter_image_any (const PickFilter[]) {    \
+        pick_filter_image_raw[0],                       \
+        pick_filter_image_dir[0],                       \
+}
index 8b64531b1f06e6e7bd60a1b0db8a8f6e6ced70bd..f550b92464496a94e32d5be9579cfb368cfbe4e9 100644 (file)
@@ -15,34 +15,30 @@ TEST(path_pick) {
         _cleanup_(rm_rf_physical_and_freep) char *p = NULL;
         _cleanup_close_ int dfd = -EBADF, sub_dfd = -EBADF;
 
-        dfd = mkdtemp_open(NULL, O_DIRECTORY|O_CLOEXEC, &p);
-        assert(dfd >= 0);
-
-        sub_dfd = open_mkdir_at(dfd, "foo.v", O_CLOEXEC, 0777);
-        assert(sub_dfd >= 0);
-
-        assert_se(write_string_file_at(sub_dfd, "foo_5.5.raw", "5.5", WRITE_STRING_FILE_CREATE) >= 0);
-        assert_se(write_string_file_at(sub_dfd, "foo_55.raw", "55", WRITE_STRING_FILE_CREATE) >= 0);
-        assert_se(write_string_file_at(sub_dfd, "foo_5.raw", "5", WRITE_STRING_FILE_CREATE) >= 0);
-        assert_se(write_string_file_at(sub_dfd, "foo_5_ia64.raw", "5", WRITE_STRING_FILE_CREATE) >= 0);
-        assert_se(write_string_file_at(sub_dfd, "foo_7.raw", "7", WRITE_STRING_FILE_CREATE) >= 0);
-        assert_se(write_string_file_at(sub_dfd, "foo_7_x86-64.raw", "7 64bit", WRITE_STRING_FILE_CREATE) >= 0);
-        assert_se(write_string_file_at(sub_dfd, "foo_55_x86-64.raw", "55 64bit", WRITE_STRING_FILE_CREATE) >= 0);
-        assert_se(write_string_file_at(sub_dfd, "foo_55_x86.raw", "55 32bit", WRITE_STRING_FILE_CREATE) >= 0);
-        assert_se(write_string_file_at(sub_dfd, "foo_99_x86.raw", "99 32bit", WRITE_STRING_FILE_CREATE) >= 0);
+        dfd = ASSERT_OK(mkdtemp_open(NULL, O_DIRECTORY|O_CLOEXEC, &p));
+        sub_dfd = ASSERT_OK(open_mkdir_at(dfd, "foo.v", O_CLOEXEC, 0777));
+
+        ASSERT_OK(write_string_file_at(sub_dfd, "foo_5.5.raw", "5.5", WRITE_STRING_FILE_CREATE));
+        ASSERT_OK(write_string_file_at(sub_dfd, "foo_55.raw", "55", WRITE_STRING_FILE_CREATE));
+        ASSERT_OK(write_string_file_at(sub_dfd, "foo_5.raw", "5", WRITE_STRING_FILE_CREATE));
+        ASSERT_OK(write_string_file_at(sub_dfd, "foo_5_ia64.raw", "5", WRITE_STRING_FILE_CREATE));
+        ASSERT_OK(write_string_file_at(sub_dfd, "foo_7.raw", "7", WRITE_STRING_FILE_CREATE));
+        ASSERT_OK(write_string_file_at(sub_dfd, "foo_7_x86-64.raw", "7 64bit", WRITE_STRING_FILE_CREATE));
+        ASSERT_OK(write_string_file_at(sub_dfd, "foo_55_x86-64.raw", "55 64bit", WRITE_STRING_FILE_CREATE));
+        ASSERT_OK(write_string_file_at(sub_dfd, "foo_55_x86.raw", "55 32bit", WRITE_STRING_FILE_CREATE));
+        ASSERT_OK(write_string_file_at(sub_dfd, "foo_99_x86.raw", "99 32bit", WRITE_STRING_FILE_CREATE));
 
         /* Let's add an entry for sparc (which is a valid arch, but almost certainly not what we test
          * on). This entry should hence always be ignored */
         if (native_architecture() != ARCHITECTURE_SPARC)
-                assert_se(write_string_file_at(sub_dfd, "foo_100_sparc.raw", "100 sparc", WRITE_STRING_FILE_CREATE) >= 0);
+                ASSERT_OK(write_string_file_at(sub_dfd, "foo_100_sparc.raw", "100 sparc", WRITE_STRING_FILE_CREATE));
 
-        assert_se(write_string_file_at(sub_dfd, "quux_1_s390.raw", "waldo1", WRITE_STRING_FILE_CREATE) >= 0);
-        assert_se(write_string_file_at(sub_dfd, "quux_2_s390+4-6.raw", "waldo2", WRITE_STRING_FILE_CREATE) >= 0);
-        assert_se(write_string_file_at(sub_dfd, "quux_3_s390+0-10.raw", "waldo3", WRITE_STRING_FILE_CREATE) >= 0);
+        ASSERT_OK(write_string_file_at(sub_dfd, "quux_1_s390.raw", "waldo1", WRITE_STRING_FILE_CREATE));
+        ASSERT_OK(write_string_file_at(sub_dfd, "quux_2_s390+4-6.raw", "waldo2", WRITE_STRING_FILE_CREATE));
+        ASSERT_OK(write_string_file_at(sub_dfd, "quux_3_s390+0-10.raw", "waldo3", WRITE_STRING_FILE_CREATE));
 
         _cleanup_free_ char *pp = NULL;
-        pp = path_join(p, "foo.v");
-        assert_se(pp);
+        pp = ASSERT_NOT_NULL(path_join(p, "foo.v"));
 
         _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
 
@@ -52,121 +48,118 @@ TEST(path_pick) {
         };
 
         if (IN_SET(native_architecture(), ARCHITECTURE_X86, ARCHITECTURE_X86_64)) {
-                assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
-                assert_se(S_ISREG(result.st.st_mode));
+                ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
+                ASSERT_TRUE(S_ISREG(result.st.st_mode));
                 ASSERT_STREQ(result.version, "99");
-                assert_se(result.architecture == ARCHITECTURE_X86);
-                assert_se(endswith(result.path, "/foo_99_x86.raw"));
+                ASSERT_EQ(result.architecture, ARCHITECTURE_X86);
+                ASSERT_TRUE(endswith(result.path, "/foo_99_x86.raw"));
 
                 pick_result_done(&result);
         }
 
         filter.architecture = ARCHITECTURE_X86_64;
-        assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
-        assert_se(S_ISREG(result.st.st_mode));
+        ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
+        ASSERT_TRUE(S_ISREG(result.st.st_mode));
         ASSERT_STREQ(result.version, "55");
-        assert_se(result.architecture == ARCHITECTURE_X86_64);
-        assert_se(endswith(result.path, "/foo_55_x86-64.raw"));
+        ASSERT_EQ(result.architecture, ARCHITECTURE_X86_64);
+        ASSERT_TRUE(endswith(result.path, "/foo_55_x86-64.raw"));
         pick_result_done(&result);
 
         filter.architecture = ARCHITECTURE_IA64;
-        assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
-        assert_se(S_ISREG(result.st.st_mode));
+        ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
+        ASSERT_TRUE(S_ISREG(result.st.st_mode));
         ASSERT_STREQ(result.version, "5");
-        assert_se(result.architecture == ARCHITECTURE_IA64);
-        assert_se(endswith(result.path, "/foo_5_ia64.raw"));
+        ASSERT_EQ(result.architecture, ARCHITECTURE_IA64);
+        ASSERT_TRUE(endswith(result.path, "/foo_5_ia64.raw"));
         pick_result_done(&result);
 
         filter.architecture = _ARCHITECTURE_INVALID;
         filter.version = "5";
-        assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
-        assert_se(S_ISREG(result.st.st_mode));
+        ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
+        ASSERT_TRUE(S_ISREG(result.st.st_mode));
         ASSERT_STREQ(result.version, "5");
         if (native_architecture() != ARCHITECTURE_IA64) {
-                assert_se(result.architecture == _ARCHITECTURE_INVALID);
-                assert_se(endswith(result.path, "/foo_5.raw"));
+                ASSERT_EQ(result.architecture, _ARCHITECTURE_INVALID);
+                ASSERT_TRUE(endswith(result.path, "/foo_5.raw"));
         }
         pick_result_done(&result);
 
         filter.architecture = ARCHITECTURE_IA64;
-        assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
-        assert_se(S_ISREG(result.st.st_mode));
+        ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
+        ASSERT_TRUE(S_ISREG(result.st.st_mode));
         ASSERT_STREQ(result.version, "5");
-        assert_se(result.architecture == ARCHITECTURE_IA64);
-        assert_se(endswith(result.path, "/foo_5_ia64.raw"));
+        ASSERT_EQ(result.architecture, ARCHITECTURE_IA64);
+        ASSERT_TRUE(endswith(result.path, "/foo_5_ia64.raw"));
         pick_result_done(&result);
 
         filter.architecture = ARCHITECTURE_CRIS;
-        assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) == 0);
-        assert_se(result.st.st_mode == MODE_INVALID);
-        assert_se(!result.version);
-        assert_se(result.architecture < 0);
-        assert_se(!result.path);
+        ASSERT_OK_ZERO(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
+        ASSERT_EQ(result.st.st_mode, MODE_INVALID);
+        ASSERT_NULL(result.version);
+        ASSERT_LT(result.architecture, 0);
+        ASSERT_NULL(result.path);
 
-        assert_se(unlinkat(sub_dfd, "foo_99_x86.raw", 0) >= 0);
+        ASSERT_OK_ERRNO(unlinkat(sub_dfd, "foo_99_x86.raw", 0));
 
         filter.architecture = _ARCHITECTURE_INVALID;
         filter.version = NULL;
         if (IN_SET(native_architecture(), ARCHITECTURE_X86_64, ARCHITECTURE_X86)) {
-                assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
-                assert_se(S_ISREG(result.st.st_mode));
+                ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
+                ASSERT_TRUE(S_ISREG(result.st.st_mode));
                 ASSERT_STREQ(result.version, "55");
 
                 if (native_architecture() == ARCHITECTURE_X86_64) {
-                        assert_se(result.architecture == ARCHITECTURE_X86_64);
-                        assert_se(endswith(result.path, "/foo_55_x86-64.raw"));
+                        ASSERT_EQ(result.architecture, ARCHITECTURE_X86_64);
+                        ASSERT_TRUE(endswith(result.path, "/foo_55_x86-64.raw"));
                 } else {
-                        assert_se(result.architecture == ARCHITECTURE_X86);
-                        assert_se(endswith(result.path, "/foo_55_x86.raw"));
+                        ASSERT_EQ(result.architecture, ARCHITECTURE_X86);
+                        ASSERT_TRUE(endswith(result.path, "/foo_55_x86.raw"));
                 }
                 pick_result_done(&result);
         }
 
         /* Test explicit patterns in last component of path not being .v */
         free(pp);
-        pp = path_join(p, "foo.v/foo___.raw");
-        assert_se(pp);
+        pp = ASSERT_NOT_NULL(path_join(p, "foo.v/foo___.raw"));
 
         if (IN_SET(native_architecture(), ARCHITECTURE_X86, ARCHITECTURE_X86_64)) {
-                assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
-                assert_se(S_ISREG(result.st.st_mode));
+                ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
+                ASSERT_TRUE(S_ISREG(result.st.st_mode));
                 ASSERT_STREQ(result.version, "55");
-                assert_se(result.architecture == native_architecture());
-                assert_se(endswith(result.path, ".raw"));
-                assert_se(strrstr(result.path, "/foo_55_x86"));
+                ASSERT_EQ(result.architecture, native_architecture());
+                ASSERT_TRUE(endswith(result.path, ".raw"));
+                ASSERT_TRUE(!!strrstr(result.path, "/foo_55_x86"));
                 pick_result_done(&result);
         }
 
         /* Specify an explicit path */
         free(pp);
-        pp = path_join(p, "foo.v/foo_5.raw");
-        assert_se(pp);
+        pp = ASSERT_NOT_NULL(path_join(p, "foo.v/foo_5.raw"));
 
         filter.type_mask = UINT32_C(1) << DT_DIR;
-        assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) == -ENOTDIR);
+        ASSERT_OK_ZERO(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
 
         filter.type_mask = UINT32_C(1) << DT_REG;
-        assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
-        assert_se(S_ISREG(result.st.st_mode));
-        assert_se(!result.version);
-        assert_se(result.architecture == _ARCHITECTURE_INVALID);
-        assert_se(path_equal(result.path, pp));
+        ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
+        ASSERT_TRUE(S_ISREG(result.st.st_mode));
+        ASSERT_NULL(result.version);
+        ASSERT_EQ(result.architecture, _ARCHITECTURE_INVALID);
+        ASSERT_TRUE(path_equal(result.path, pp));
         pick_result_done(&result);
 
         free(pp);
-        pp = path_join(p, "foo.v");
-        assert_se(pp);
+        pp = ASSERT_NOT_NULL(path_join(p, "foo.v"));
 
         filter.architecture = ARCHITECTURE_S390;
         filter.basename = "quux";
 
-        assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
-        assert_se(S_ISREG(result.st.st_mode));
+        ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
+        ASSERT_TRUE(S_ISREG(result.st.st_mode));
         ASSERT_STREQ(result.version, "2");
-        assert_se(result.tries_left == 4);
-        assert_se(result.tries_done == 6);
-        assert_se(endswith(result.path, "quux_2_s390+4-6.raw"));
-        assert_se(result.architecture == ARCHITECTURE_S390);
+        ASSERT_EQ(result.tries_left, 4U);
+        ASSERT_EQ(result.tries_done, 6U);
+        ASSERT_TRUE(endswith(result.path, "quux_2_s390+4-6.raw"));
+        ASSERT_EQ(result.architecture, ARCHITECTURE_S390);
 }
 
 TEST(path_uses_vpick) {
@@ -192,4 +185,223 @@ TEST(path_uses_vpick) {
         ASSERT_ERROR(path_uses_vpick(""), EINVAL);
 }
 
+TEST(pick_filter_image_any) {
+        _cleanup_(rm_rf_physical_and_freep) char *p = NULL;
+
+        _cleanup_close_ int dfd = ASSERT_OK(mkdtemp_open(NULL, O_DIRECTORY|O_CLOEXEC, &p));
+        _cleanup_close_ int sub_dfd = ASSERT_OK(open_mkdir_at(dfd, "test.v", O_CLOEXEC, 0777));
+
+        /* Create .raw files (should match with pick_filter_image_raw and pick_filter_image_any) */
+        ASSERT_OK(write_string_file_at(sub_dfd, "test_1.raw", "version 1 raw", WRITE_STRING_FILE_CREATE));
+        ASSERT_OK(write_string_file_at(sub_dfd, "test_2.raw", "version 2 raw", WRITE_STRING_FILE_CREATE));
+        ASSERT_OK(write_string_file_at(sub_dfd, "test_3.raw", "version 3 raw", WRITE_STRING_FILE_CREATE));
+
+        /* Create directories (should match with pick_filter_image_dir and pick_filter_image_any) */
+        ASSERT_OK(mkdirat(sub_dfd, "test_4", 0755));
+        ASSERT_OK(mkdirat(sub_dfd, "test_5", 0755));
+
+        /* Create files without .raw suffix (should NOT match any of the pick_filter_image_* filters) */
+        ASSERT_OK(write_string_file_at(sub_dfd, "test_10.txt", "version 10 txt", WRITE_STRING_FILE_CREATE));
+        ASSERT_OK(write_string_file_at(sub_dfd, "test_11.img", "version 11 img", WRITE_STRING_FILE_CREATE));
+        ASSERT_OK(write_string_file_at(sub_dfd, "test_12", "version 12", WRITE_STRING_FILE_CREATE));
+
+        _cleanup_free_ char *pp = ASSERT_NOT_NULL(path_join(p, "test.v"));
+        _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
+
+        /* Test pick_filter_image_any: should pick the highest version, which is the directory test_5 */
+        ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result));
+        ASSERT_TRUE(S_ISDIR(result.st.st_mode));
+        ASSERT_STREQ(result.version, "5");
+        ASSERT_TRUE(endswith(result.path, "/test_5"));
+        pick_result_done(&result);
+
+        /* Remove directories, now it should pick the highest .raw file (test_3.raw) */
+        ASSERT_OK(unlinkat(sub_dfd, "test_4", AT_REMOVEDIR));
+        ASSERT_OK(unlinkat(sub_dfd, "test_5", AT_REMOVEDIR));
+
+        ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result));
+        ASSERT_TRUE(S_ISREG(result.st.st_mode));
+        ASSERT_STREQ(result.version, "3");
+        ASSERT_TRUE(endswith(result.path, "/test_3.raw"));
+        pick_result_done(&result);
+
+        /* Verify that pick_filter_image_raw only matches .raw files */
+        ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_raw, ELEMENTSOF(pick_filter_image_raw), PICK_ARCHITECTURE, &result));
+        ASSERT_TRUE(S_ISREG(result.st.st_mode));
+        ASSERT_STREQ(result.version, "3");
+        ASSERT_TRUE(endswith(result.path, "/test_3.raw"));
+        pick_result_done(&result);
+
+        /* Verify that files without .raw suffix are never picked by pick_filter_image_any */
+        /* Remove all .raw files */
+        ASSERT_OK(unlinkat(sub_dfd, "test_1.raw", 0));
+        ASSERT_OK(unlinkat(sub_dfd, "test_2.raw", 0));
+        ASSERT_OK(unlinkat(sub_dfd, "test_3.raw", 0));
+
+        /* Now only test_10.txt, test_11.img, and test_12 remain - none should match */
+        ASSERT_OK_ZERO(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result));
+
+        /* But if we add a directory, it should be picked */
+        ASSERT_OK(mkdirat(sub_dfd, "test_6", 0755));
+
+        ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result));
+        ASSERT_TRUE(S_ISDIR(result.st.st_mode));
+        ASSERT_STREQ(result.version, "6");
+        ASSERT_TRUE(endswith(result.path, "/test_6"));
+        pick_result_done(&result);
+
+        /* Now test pick_filter_image_dir with a separate directory structure */
+        safe_close(sub_dfd);
+        sub_dfd = ASSERT_OK(open_mkdir_at(dfd, "myimage.v", O_CLOEXEC, 0777));
+
+        /* Create directories that pick_filter_image_dir should find */
+        ASSERT_OK(mkdirat(sub_dfd, "myimage_1", 0755));
+        ASSERT_OK(mkdirat(sub_dfd, "myimage_2", 0755));
+
+        free(pp);
+        pp = ASSERT_NOT_NULL(path_join(p, "myimage.v"));
+
+        pick_result_done(&result);
+
+        ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), PICK_ARCHITECTURE, &result));
+        ASSERT_TRUE(S_ISDIR(result.st.st_mode));
+        ASSERT_STREQ(result.version, "2");
+        ASSERT_TRUE(endswith(result.path, "/myimage_2"));
+        pick_result_done(&result);
+
+        /* With no directories, pick_filter_image_dir should return nothing */
+        ASSERT_OK(unlinkat(sub_dfd, "myimage_1", AT_REMOVEDIR));
+        ASSERT_OK(unlinkat(sub_dfd, "myimage_2", AT_REMOVEDIR));
+
+        ASSERT_OK_ZERO(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), PICK_ARCHITECTURE, &result));
+}
+
+TEST(path_pick_resolve) {
+        _cleanup_(rm_rf_physical_and_freep) char *p = NULL;
+
+        _cleanup_close_ int dfd = ASSERT_OK(mkdtemp_open(NULL, O_DIRECTORY|O_CLOEXEC, &p));
+        _cleanup_close_ int sub_dfd = ASSERT_OK(open_mkdir_at(dfd, "resolve.v", O_CLOEXEC, 0777));
+
+        /* Create a target directory and file for symlinks */
+        ASSERT_OK(mkdirat(dfd, "target_dir", 0755));
+        ASSERT_OK(write_string_file_at(dfd, "target_file.raw", "target content", WRITE_STRING_FILE_CREATE));
+
+        /* Create symlinks inside the .v directory pointing to targets outside */
+        ASSERT_OK(symlinkat("../target_dir", sub_dfd, "resolve_1"));
+        ASSERT_OK(symlinkat("../target_file.raw", sub_dfd, "resolve_2.raw"));
+
+        _cleanup_free_ char *pp = ASSERT_NOT_NULL(path_join(p, "resolve.v"));
+        _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
+
+        /* Test without PICK_RESOLVE - should return the symlink path */
+        ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result));
+        ASSERT_STREQ(result.version, "2");
+        ASSERT_TRUE(endswith(result.path, "/resolve_2.raw"));
+        pick_result_done(&result);
+
+        /* Test with PICK_RESOLVE - should return the resolved (target) path */
+        ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE|PICK_RESOLVE, &result));
+        ASSERT_STREQ(result.version, "2");
+        ASSERT_TRUE(endswith(result.path, "/target_file.raw"));
+        pick_result_done(&result);
+
+        /* Test pick_filter_image_dir without PICK_RESOLVE */
+        ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), PICK_ARCHITECTURE, &result));
+        ASSERT_TRUE(S_ISDIR(result.st.st_mode));
+        ASSERT_STREQ(result.version, "1");
+        ASSERT_TRUE(endswith(result.path, "/resolve_1"));
+        pick_result_done(&result);
+
+        /* Test pick_filter_image_dir with PICK_RESOLVE */
+        ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), PICK_ARCHITECTURE|PICK_RESOLVE, &result));
+        ASSERT_TRUE(S_ISDIR(result.st.st_mode));
+        ASSERT_STREQ(result.version, "1");
+        ASSERT_TRUE(endswith(result.path, "/target_dir"));
+        pick_result_done(&result);
+
+        /* Test with a chain of symlinks */
+        ASSERT_OK(symlinkat("target_file.raw", dfd, "intermediate_link.raw"));
+        ASSERT_OK(symlinkat("../intermediate_link.raw", sub_dfd, "resolve_3.raw"));
+
+        ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_raw, ELEMENTSOF(pick_filter_image_raw), PICK_ARCHITECTURE, &result));
+        ASSERT_STREQ(result.version, "3");
+        ASSERT_TRUE(endswith(result.path, "/resolve_3.raw"));
+        pick_result_done(&result);
+
+        ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_raw, ELEMENTSOF(pick_filter_image_raw), PICK_ARCHITECTURE|PICK_RESOLVE, &result));
+        ASSERT_STREQ(result.version, "3");
+        /* The chain should be fully resolved to target_file.raw */
+        ASSERT_TRUE(endswith(result.path, "/target_file.raw"));
+        pick_result_done(&result);
+}
+
+TEST(pick_result_compare) {
+        PickResult a = PICK_RESULT_NULL, b = PICK_RESULT_NULL;
+
+        /* When everything is equal, compare paths */
+        a.path = (char*) "/a";
+        b.path = (char*) "/b";
+        ASSERT_LT(pick_result_compare(&a, &b, 0), 0);
+        ASSERT_GT(pick_result_compare(&b, &a, 0), 0);
+        ASSERT_EQ(pick_result_compare(&a, &a, 0), 0);
+
+        /* Prefer newer versions */
+        a.version = (char*) "1";
+        b.version = (char*) "2";
+        ASSERT_LT(pick_result_compare(&a, &b, 0), 0);
+        ASSERT_GT(pick_result_compare(&b, &a, 0), 0);
+        a.version = b.version = NULL;
+
+        /* Prefer entries with tries left over those without (only with PICK_TRIES) */
+        a.tries_left = 0;
+        b.tries_left = 1;
+        ASSERT_LT(pick_result_compare(&a, &b, 0), 0); /* Without PICK_TRIES, paths are compared */
+        ASSERT_LT(pick_result_compare(&a, &b, PICK_TRIES), 0);
+        ASSERT_GT(pick_result_compare(&b, &a, PICK_TRIES), 0);
+
+        /* Prefer entries with more tries left */
+        a.tries_left = 1;
+        b.tries_left = 5;
+        ASSERT_LT(pick_result_compare(&a, &b, PICK_TRIES), 0);
+        ASSERT_GT(pick_result_compare(&b, &a, PICK_TRIES), 0);
+
+        /* Prefer entries with fewer attempts done */
+        a.tries_left = b.tries_left = 3;
+        a.tries_done = 5;
+        b.tries_done = 1;
+        ASSERT_LT(pick_result_compare(&a, &b, PICK_TRIES), 0);
+        ASSERT_GT(pick_result_compare(&b, &a, PICK_TRIES), 0);
+        a.tries_left = b.tries_left = UINT_MAX;
+        a.tries_done = b.tries_done = UINT_MAX;
+
+        /* Prefer native architecture (only with PICK_ARCHITECTURE) */
+        a.architecture = native_architecture();
+        b.architecture = ARCHITECTURE_ALPHA; /* Unlikely to be native */
+        if (native_architecture() != ARCHITECTURE_ALPHA) {
+                ASSERT_LT(pick_result_compare(&a, &b, 0), 0); /* Without PICK_ARCHITECTURE, paths are compared */
+                ASSERT_GT(pick_result_compare(&a, &b, PICK_ARCHITECTURE), 0);
+                ASSERT_LT(pick_result_compare(&b, &a, PICK_ARCHITECTURE), 0);
+        }
+        a.architecture = b.architecture = _ARCHITECTURE_INVALID;
+
+        /* Version takes precedence over architecture */
+        a.version = (char*) "1";
+        b.version = (char*) "2";
+        a.architecture = native_architecture();
+        b.architecture = ARCHITECTURE_ALPHA;
+        if (native_architecture() != ARCHITECTURE_ALPHA)
+                ASSERT_LT(pick_result_compare(&a, &b, PICK_ARCHITECTURE), 0); /* b wins due to higher version */
+        a.version = b.version = NULL;
+        a.architecture = b.architecture = _ARCHITECTURE_INVALID;
+
+        /* Tries left takes precedence over version */
+        a.tries_left = 0;
+        b.tries_left = 1;
+        a.version = (char*) "2";
+        b.version = (char*) "1";
+        ASSERT_LT(pick_result_compare(&a, &b, PICK_TRIES), 0); /* b wins due to tries left */
+        a.tries_left = b.tries_left = UINT_MAX;
+        a.version = b.version = NULL;
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);
index 5f114b168b436c6d7a0dc4236c188465c700f5c6..f4251f5c943669b1adb84c99af38c5a9ad632788 100644 (file)
@@ -248,6 +248,7 @@ static int run(int argc, char *argv[]) {
                                       .suffix = STRV_MAKE(arg_filter_suffix),
                                       .type_mask = arg_filter_type_mask,
                               },
+                              /* n_filters= */ 1,
                               arg_flags,
                               &result);
                 if (r < 0)