1
0
mirror of https://github.com/systemd/systemd synced 2026-03-17 18:44:46 +01:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Daan De Meyer
3fa3a4fd15 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
2026-01-16 13:07:47 +01:00
Michael Vogt
3f958812fd sd-boot: allow setting the timeout via SMBIOS 11
Allow configuring the menu timeout used by sd-boot by setting
`io.systemd.boot.timeout=<value>` as SMBIOS type 11 string.

This takes precedence over the setting from the config file
and the EFI var.

Useful for e.g. integration tests that want to control that
a generic image boots faster.
2026-01-16 10:26:48 +01:00
12 changed files with 573 additions and 241 deletions

View File

@ -94,6 +94,16 @@
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>io.systemd.boot.timeout=</varname><replaceable>TIMEOUT</replaceable></term>
<listitem><para>This allows configuration of the menu timeout in seconds, and is read by <command>systemd-boot</command>.
For details see
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
</variablelist>
</refsect1>

View File

@ -128,7 +128,8 @@ typedef struct {
size_t n_entries;
size_t idx_default;
size_t idx_default_efivar;
uint64_t timeout_sec; /* Actual timeout used (efi_main() override > efivar > config). */
uint64_t timeout_sec; /* Actual timeout used (efi_main() override > smbios > efivar > config). */
uint64_t timeout_sec_smbios;
uint64_t timeout_sec_config;
uint64_t timeout_sec_efivar;
char16_t *entry_default_config;
@ -323,6 +324,7 @@ static void print_status(Config *config, char16_t *loaded_image_path) {
print_timeout_status(" timeout (config)", config->timeout_sec_config);
print_timeout_status(" timeout (EFI var)", config->timeout_sec_efivar);
print_timeout_status(" timeout (smbios)", config->timeout_sec_smbios);
if (config->entry_default_config)
printf(" default (config): %ls\n", config->entry_default_config);
@ -1006,6 +1008,41 @@ static BootEntry* boot_entry_free(BootEntry *entry) {
DEFINE_TRIVIAL_CLEANUP_FUNC(BootEntry *, boot_entry_free);
static EFI_STATUS config_timeout_sec_from_string(const char *value, uint64_t *dst) {
if (streq8(value, "menu-disabled"))
*dst = TIMEOUT_MENU_DISABLED;
else if (streq8(value, "menu-force"))
*dst = TIMEOUT_MENU_DISABLED;
else if (streq8(value, "menu-hidden"))
*dst = TIMEOUT_MENU_DISABLED;
else {
uint64_t u;
if (!parse_number8(value, &u, NULL) || u > TIMEOUT_TYPE_MAX)
return EFI_INVALID_PARAMETER;
*dst = u;
}
return EFI_SUCCESS;
}
static void config_timeout_load_from_smbios(Config *config) {
EFI_STATUS err;
if (is_confidential_vm())
return; /* Don't consume SMBIOS in Confidential Computing contexts */
const char *value = smbios_find_oem_string("io.systemd.boot.timeout=", /* after= */ NULL);
if (!value)
return;
err = config_timeout_sec_from_string(value, &config->timeout_sec_smbios);
if (err != EFI_SUCCESS) {
log_warning_status(err, "Error parsing 'timeout' smbios option, ignoring: %s",
value);
return;
}
config->timeout_sec = config->timeout_sec_smbios;
}
static void config_defaults_load_from_file(Config *config, char *content) {
char *line;
size_t pos = 0;
@ -1018,21 +1055,12 @@ static void config_defaults_load_from_file(Config *config, char *content) {
* shared/bootspec.c@boot_loader_read_conf() to make parsing by bootctl/logind/etc. work. */
while ((line = line_get_key_value(content, " \t", &pos, &key, &value)))
if (streq8(key, "timeout")) {
if (streq8(value, "menu-disabled"))
config->timeout_sec_config = TIMEOUT_MENU_DISABLED;
else if (streq8(value, "menu-force"))
config->timeout_sec_config = TIMEOUT_MENU_FORCE;
else if (streq8(value, "menu-hidden"))
config->timeout_sec_config = TIMEOUT_MENU_HIDDEN;
else {
uint64_t u;
if (!parse_number8(value, &u, NULL) || u > TIMEOUT_TYPE_MAX) {
log_error("Error parsing 'timeout' config option, ignoring: %s",
EFI_STATUS err = config_timeout_sec_from_string(value, &config->timeout_sec_config);
if (err != EFI_SUCCESS) {
log_warning_status(err, "Error parsing 'timeout' config option, ignoring: %s",
value);
continue;
}
config->timeout_sec_config = u;
}
config->timeout_sec = config->timeout_sec_config;
} else if (streq8(key, "default")) {
@ -1496,6 +1524,7 @@ static void config_load_defaults(Config *config, EFI_FILE *root_dir) {
.console_mode_efivar = CONSOLE_MODE_KEEP,
.timeout_sec_config = TIMEOUT_UNSET,
.timeout_sec_efivar = TIMEOUT_UNSET,
.timeout_sec_smbios = TIMEOUT_UNSET,
};
err = file_read(root_dir, u"\\loader\\loader.conf", 0, 0, &content, &content_size);
@ -1520,6 +1549,7 @@ static void config_load_defaults(Config *config, EFI_FILE *root_dir) {
config->timeout_sec = config->timeout_sec_efivar;
else if (err != EFI_NOT_FOUND)
log_warning_status(err, "Error reading LoaderConfigTimeout EFI variable, ignoring: %m");
config_timeout_load_from_smbios(config);
err = efivar_get_timeout(u"LoaderConfigTimeoutOneShot", &config->timeout_sec);
if (err == EFI_SUCCESS) {

View File

@ -3669,7 +3669,8 @@ static int pick_versions(
r = path_pick(/* toplevel_path= */ NULL,
/* toplevel_fd= */ AT_FDCWD,
context->root_image,
&pick_filter_image_raw,
pick_filter_image_raw,
ELEMENTSOF(pick_filter_image_raw),
PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
&result);
if (r < 0) {
@ -3693,7 +3694,8 @@ static int pick_versions(
r = path_pick(/* toplevel_path= */ NULL,
/* toplevel_fd= */ AT_FDCWD,
context->root_directory,
&pick_filter_image_dir,
pick_filter_image_dir,
ELEMENTSOF(pick_filter_image_dir),
PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
&result);
if (r < 0) {

View File

@ -566,7 +566,8 @@ static int append_extensions(
r = path_pick(/* toplevel_path= */ NULL,
/* toplevel_fd= */ AT_FDCWD,
m->source,
&pick_filter_image_raw,
pick_filter_image_raw,
ELEMENTSOF(pick_filter_image_raw),
PICK_ARCHITECTURE|PICK_TRIES,
&result);
if (r == -ENOENT && m->ignore_enoent)
@ -637,7 +638,8 @@ static int append_extensions(
r = path_pick(/* toplevel_path= */ NULL,
/* toplevel_fd= */ AT_FDCWD,
e,
&pick_filter_image_dir,
pick_filter_image_dir,
ELEMENTSOF(pick_filter_image_dir),
PICK_ARCHITECTURE|PICK_TRIES,
&result);
if (r == -ENOENT && ignore_enoent)

View File

@ -2041,7 +2041,8 @@ static int run(int argc, char *argv[]) {
if (arg_image) {
r = path_pick_update_warn(
&arg_image,
&pick_filter_image_raw,
pick_filter_image_raw,
ELEMENTSOF(pick_filter_image_raw),
PICK_ARCHITECTURE|PICK_TRIES,
/* ret_result= */ NULL);
if (r < 0)
@ -2051,7 +2052,8 @@ static int run(int argc, char *argv[]) {
if (arg_root) {
r = path_pick_update_warn(
&arg_root,
&pick_filter_image_dir,
pick_filter_image_dir,
ELEMENTSOF(pick_filter_image_dir),
PICK_ARCHITECTURE|PICK_TRIES,
/* ret_result= */ NULL);
if (r < 0)

View File

@ -3013,13 +3013,14 @@ static int pick_paths(void) {
if (arg_directory) {
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
PickFilter filter = pick_filter_image_dir;
PickFilter filter = *pick_filter_image_dir;
filter.architecture = arg_architecture;
r = path_pick_update_warn(
&arg_directory,
&filter,
/* n_filters= */ 1,
PICK_ARCHITECTURE|PICK_TRIES,
&result);
if (r < 0) {
@ -3032,13 +3033,14 @@ static int pick_paths(void) {
if (arg_image) {
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
PickFilter filter = pick_filter_image_raw;
PickFilter filter = *pick_filter_image_raw;
filter.architecture = arg_architecture;
r = path_pick_update_warn(
&arg_image,
&filter,
/* n_filters= */ 1,
PICK_ARCHITECTURE|PICK_TRIES,
&result);
if (r < 0)
@ -3049,13 +3051,14 @@ static int pick_paths(void) {
if (arg_template) {
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
PickFilter filter = pick_filter_image_dir;
PickFilter filter = *pick_filter_image_dir;
filter.architecture = arg_architecture;
r = path_pick_update_warn(
&arg_template,
&filter,
/* n_filters= */ 1,
PICK_ARCHITECTURE,
&result);
if (r < 0)

View File

@ -595,7 +595,8 @@ static int extract_image_and_extensions(
r = path_pick(/* toplevel_path= */ NULL,
/* toplevel_fd= */ AT_FDCWD,
name_or_path,
&pick_filter_image_any,
pick_filter_image_any,
ELEMENTSOF(pick_filter_image_any),
PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
&result);
if (r < 0)
@ -633,7 +634,8 @@ static int extract_image_and_extensions(
r = path_pick(/* toplevel_path= */ NULL,
/* toplevel_fd= */ AT_FDCWD,
*p,
&pick_filter_image_any,
pick_filter_image_any,
ELEMENTSOF(pick_filter_image_any),
PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
&ext_result);
if (r < 0)
@ -1782,7 +1784,8 @@ static bool marker_matches_images(const char *marker, const char *name_or_path,
r = path_pick(/* toplevel_path= */ NULL,
/* toplevel_fd= */ AT_FDCWD,
*image_name_or_path,
&pick_filter_image_any,
pick_filter_image_any,
ELEMENTSOF(pick_filter_image_any),
PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
&result);
if (r < 0)

View File

@ -821,6 +821,7 @@ int image_find(RuntimeScope scope,
/* toplevel_fd= */ AT_FDCWD,
vp,
&filter,
/* n_filters= */ 1,
PICK_ARCHITECTURE|PICK_TRIES,
&result);
if (r < 0) {
@ -1033,6 +1034,7 @@ int image_discover(
/* toplevel_fd= */ AT_FDCWD,
vp,
&filter,
/* n_filters= */ 1,
PICK_ARCHITECTURE|PICK_TRIES,
&result);
if (r < 0) {

View File

@ -26,6 +26,53 @@ void pick_result_done(PickResult *p) {
*p = PICK_RESULT_NULL;
}
int pick_result_compare(const PickResult *a, const PickResult *b, PickFlags flags) {
int d;
assert(a);
assert(b);
/* Returns > 0 if 'a' is the better pick, < 0 if 'b' is the better pick, 0 if they are equal. */
/* Prefer entries with tries left over those without */
if (FLAGS_SET(flags, PICK_TRIES))
d = CMP(a->tries_left != 0, b->tries_left != 0);
else
d = 0;
/* Prefer newer versions */
if (d == 0)
d = strverscmp_improved(a->version, b->version);
if (FLAGS_SET(flags, PICK_ARCHITECTURE)) {
/* Prefer native architectures over non-native architectures */
if (d == 0)
d = CMP(a->architecture == native_architecture(), b->architecture == native_architecture());
/* Prefer secondary architectures over other architectures */
#ifdef ARCHITECTURE_SECONDARY
if (d == 0)
d = CMP(a->architecture == ARCHITECTURE_SECONDARY, b->architecture == ARCHITECTURE_SECONDARY);
#endif
}
/* Prefer entries with more tries left */
if (FLAGS_SET(flags, PICK_TRIES)) {
if (d == 0)
d = CMP(a->tries_left, b->tries_left);
/* Prefer entries with fewer attempts done so far */
if (d == 0)
d = -CMP(a->tries_done, b->tries_done);
}
/* Finally, just compare the filenames as strings */
if (d == 0)
d = path_compare_filename(a->path, b->path);
return d;
}
static int format_fname(
const PickFilter *filter,
PickFlags flags,
@ -169,13 +216,13 @@ static int pin_choice(
return log_debug_errno(errno, "Failed to stat discovered inode '%s%s': %m",
empty_to_root(toplevel_path), skip_leading_slash(inode_path));
if (filter->type_mask != 0 &&
!BIT_SET(filter->type_mask, IFTODT(st.st_mode)))
return log_debug_errno(
SYNTHETIC_ERRNO(errno_from_mode(filter->type_mask, st.st_mode)),
"Inode '%s/%s' has wrong type, found '%s'.",
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);
if (!best.path) {
best = TAKE_PICK_RESULT(found);
continue;
}
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_version, e);
if (r < 0)
return r;
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 = {
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 = {
const PickFilter pick_filter_image_dir[1] = {
{
.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", ""),
},
};

View File

@ -43,22 +43,29 @@ typedef struct PickResult {
void pick_result_done(PickResult *p);
int path_pick(
const char *toplevel_path,
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 *filter,
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], \
}

View File

@ -15,34 +15,30 @@ TEST(path_pick) {
_cleanup_(rm_rf_physical_and_freep) char *p = NULL;
_cleanup_close_ int dfd = -EBADF, sub_dfd = -EBADF;
dfd = mkdtemp_open(NULL, O_DIRECTORY|O_CLOEXEC, &p);
assert(dfd >= 0);
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);

View File

@ -248,6 +248,7 @@ static int run(int argc, char *argv[]) {
.suffix = STRV_MAKE(arg_filter_suffix),
.type_mask = arg_filter_type_mask,
},
/* n_filters= */ 1,
arg_flags,
&result);
if (r < 0)