*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,
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),
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,
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);
}
/* 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;
/* 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);
}
}
+ 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;
}
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,
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) {
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) {
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,
+ },
};
_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;
};
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) {
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);