diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index d87392d538..c8994c8334 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 c35226d639..fc36d79863 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 8883dc6e26..346472ef0d 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 bb90cc1c94..b388cbd7ed 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 cc1269de1e..b7cb9af1ff 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 0be11229e0..69163f8878 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 07d9d9ffd8..d8fae8aa77 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; + if (underscore) + a = architecture_from_string(underscore + 1); - for (size_t i = 0; i < n_architectures; i++) - if (architectures[i] == a) { - found_architecture_index = i; - break; - } - - 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; + _cleanup_free_ char *p = path_join(inode_path, (*entry)->d_name); + if (!p) + return log_oom_debug(); - /* 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); + _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; - /* 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; - } + if (!best.path) { + best = TAKE_PICK_RESULT(found); + continue; } - r = free_and_strdup_warn(&best_version, e); - if (r < 0) - return r; + 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; + } - r = free_and_strdup_warn(&best_filename, (*entry)->d_name); - if (r < 0) - return r; - - best_architecture_index = found_architecture_index; - best_tries_left = found_tries_left; - best_tries_done = found_tries_done; + 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_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_dir = { - .type_mask = UINT32_C(1) << DT_DIR, - .architecture = _ARCHITECTURE_INVALID, -}; - -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 3c23104ca9..d08dc58b8f 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 8b64531b1f..f550b92464 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); + dfd = ASSERT_OK(mkdtemp_open(NULL, O_DIRECTORY|O_CLOEXEC, &p)); + sub_dfd = ASSERT_OK(open_mkdir_at(dfd, "foo.v", O_CLOEXEC, 0777)); - 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); + 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 5f114b168b..f4251f5c94 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)