From: Daan De Meyer Date: Sat, 20 Dec 2025 20:38:09 +0000 (+0100) Subject: vpick: Fix pick_filter_image_any X-Git-Tag: v260-rc1~393 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3fa3a4fd153c4ff836b3365d48adf02eac8c8221;p=thirdparty%2Fsystemd.git vpick: Fix pick_filter_image_any 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 --- diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index d87392d5383..c8994c83340 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -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) { diff --git a/src/core/namespace.c b/src/core/namespace.c index c35226d6390..fc36d79863f 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -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) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 8883dc6e263..346472ef0d5 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -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) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index bb90cc1c940..b388cbd7ed6 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -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) diff --git a/src/portable/portable.c b/src/portable/portable.c index cc1269de1e3..b7cb9af1ff7 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -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) diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index 0be11229e05..69163f8878f 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -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) { diff --git a/src/shared/vpick.c b/src/shared/vpick.c index 07d9d9ffd8c..d8fae8aa77f 100644 --- a/src/shared/vpick.c +++ b/src/shared/vpick.c @@ -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, + }, }; diff --git a/src/shared/vpick.h b/src/shared/vpick.h index 3c23104ca9a..d08dc58b8fb 100644 --- a/src/shared/vpick.h +++ b/src/shared/vpick.h @@ -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], \ +} diff --git a/src/test/test-vpick.c b/src/test/test-vpick.c index 8b64531b1f0..f550b924644 100644 --- a/src/test/test-vpick.c +++ b/src/test/test-vpick.c @@ -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); diff --git a/src/vpick/vpick-tool.c b/src/vpick/vpick-tool.c index 5f114b168b4..f4251f5c943 100644 --- a/src/vpick/vpick-tool.c +++ b/src/vpick/vpick-tool.c @@ -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)