vpick: honour the "user.uapi.match_…" extended attributes of entries

systemd-sysupdate copies the "user.uapi.match_…" extended attributes of files
in UAPI.16 manifests onto the files it installs, which restrict the
files to specific systems (hostname, machine ID, machine tags, os-release
image/OS ID and version). Let's honour them when picking entries from
.v/ directories too: entries whose extended attributes don't match the
local system are skipped, as if they didn't exist. The system properties
are only loaded once an entry actually carries one of the attributes.

Entries referenced literally, i.e. without making use of .v/ logic, are
not filtered.

This may be turned off with the new PICK_IGNORE_XATTR_MATCH flag, which
systemd-vpick exposes as --match-xattr=no.
This commit is contained in:
Lennart Poettering committed 2026-11-02 15:57:51 +01:00
1 parent 7e3305960b
commit 8a9b726de0
16 files changed
+261 -32

No files matched your search

+12
View File
@@ -147,6 +147,18 @@
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--match-xattr=</option></term>
<listitem><para>Takes a boolean argument. If true (the default), entries of the
<literal>.v/</literal> directory whose <literal>user.uapi.match_…</literal> extended attributes do
not match the local system are skipped, see
<citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
details. If false, these extended attributes are ignored.</para>
<xi:include href="version-info.xml" xpointer="v263"/></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
+20
View File
@@ -106,6 +106,26 @@
version comparison.</para></listitem>
<listitem><para>The rest of the variable part is the version string.</para></listitem>
<listitem><para>Entries may be restricted to specific systems via the extended attributes
<literal>user.uapi.match_hostname</literal>, <literal>user.uapi.match_machine_id</literal>,
<literal>user.uapi.match_machine_tags</literal>, <literal>user.uapi.match_image_id</literal>,
<literal>user.uapi.match_image_version</literal>, <literal>user.uapi.match_os_id</literal> and
<literal>user.uapi.match_os_version</literal>, which are matched against the hostname, the machine
ID, the machine tags (see <varname>TAGS=</varname> in
<citerefentry><refentrytitle>machine-info</refentrytitle><manvolnum>5</manvolnum></citerefentry>),
and the <varname>IMAGE_ID=</varname>, <varname>IMAGE_VERSION=</varname>, <varname>ID=</varname> and
<varname>VERSION_ID=</varname> fields of
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry> of
the local system. Entries carrying any of these extended attributes that do not match the local
system (or that cannot be parsed) are filtered and not considered for the version comparison. The
syntax of these extended attributes is described in the documentation of the
<varname>MatchXAttr=</varname> setting in
<citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>:
<command>systemd-sysupdate</command> sets them on the files it installs, if they are defined for
them in the UAPI.16 manifest the files are downloaded from. Note that entries that are explicitly
selected by their full path (i.e. without making use of the <literal>.v/</literal> logic) are not
filtered this way.</para></listitem>
</itemizedlist>
<para>Or in other words, the files in the <literal>.v/</literal> directories should follow one of these
+8 -2
View File
@@ -30,17 +30,23 @@ _systemd-vpick() {
local -A OPTS=(
[STANDALONE]='--help -h --version'
[ARG]='-B --baseline -V -A -S --suffix --type -p --print --resolve'
[ARG]='-B --basename -V -A -S --suffix -t --type -p --print --resolve --match-xattr'
)
_init_completion || return
if __contains_word "$prev" ${OPTS[ARG]}; then
case $prev in
-A)
comps="native secondary uname auto $(systemd-analyze --no-legend --no-pager architectures 2>/dev/null | { while read -r a _; do echo " $a"; done; })"
;;
-t|--type)
comps='reg dir lnk chr blk fifo sock'
;;
-p|--print)
comps='filename version type arch tries all'
;;
--resolve)
--resolve|--match-xattr)
comps='yes no'
;;
esac
+22
View File
@@ -0,0 +1,22 @@
#compdef systemd-vpick
# SPDX-License-Identifier: LGPL-2.1-or-later
(( $+functions[_systemd-vpick_architectures] )) ||
_systemd-vpick_architectures() {
local -a _architectures
_architectures=( native secondary uname auto $(systemd-analyze --quiet --no-pager --no-legend architectures 2>/dev/null | { while read -r a _; do echo "$a"; done; }) )
_describe -t architectures 'architecture' _architectures
}
_arguments \
'(- *)'{-h,--help}'[Show help text]' \
'(- *)--version[Show package version]' \
'(-B --basename)'{-B+,--basename=}'[Look for specified basename]:basename' \
'-V+[Look for specified version]:version' \
'-A+[Look for specified architecture]:architecture:_systemd-vpick_architectures' \
'(-S --suffix)'{-S+,--suffix=}'[Look for specified suffix]:suffix' \
'*'{-t+,--type=}'[Look for specified inode type]:type:(reg dir lnk chr blk fifo sock)' \
"--match-xattr=[Skip entries whose user.uapi.match_* xattrs don't match the system]:boolean:(yes no)" \
'(-p --print)'{-p+,--print=}'[Print selected WHAT rather than path]:what:(filename version type arch tries all)' \
'--resolve=[Canonicalize the result path]:boolean:(yes no)' \
'*:path:_files'
+1
View File
@@ -64,6 +64,7 @@ foreach item : [
['_systemd-sysupdate', 'ENABLE_SYSUPDATE'],
['_systemd-tmpfiles', 'ENABLE_TMPFILES'],
['_systemd-vmspawn', 'ENABLE_VMSPAWN'],
['_systemd-vpick', ''],
['_timedatectl', 'ENABLE_TIMEDATED'],
['_udevadm', ''],
['_updatectl', 'ENABLE_SYSUPDATED'],
+2
View File
@@ -1194,6 +1194,7 @@ static int link_context_add_extras(LinkContext *c, int uki_dir_fd, Set **seen) {
de->d_name,
&filter,
/* n_filters= */ 1,
/* match_root= */ c->root,
/* flags= */ 0,
&pick);
if (r == -ENOENT || r == 0) {
@@ -1249,6 +1250,7 @@ static int link_context_find_kernel(LinkContext *c, int uki_dir_fd) {
"kernel.efi.v",
&pick_filter_uki,
/* n_filters= */ 1,
/* match_root= */ c->root,
/* flags= */ 0,
&pick);
if (r == -ENOENT || r == 0)
+3
View File
@@ -3983,6 +3983,7 @@ static int pin_rootfs(
context->root_image,
pick_filter_image_raw,
ELEMENTSOF(pick_filter_image_raw),
/* match_root= */ NULL,
PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
&result);
if (r < 0) {
@@ -4025,6 +4026,7 @@ static int pin_rootfs(
context->root_directory,
pick_filter_image_dir,
ELEMENTSOF(pick_filter_image_dir),
/* match_root= */ NULL,
PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
&result);
if (r < 0) {
@@ -4055,6 +4057,7 @@ static int pin_rootfs(
context->root_mstack,
pick_filter_image_mstack,
/* n_filters= */ 1,
/* match_root= */ NULL,
PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
&result);
if (r < 0) {
+2
View File
@@ -570,6 +570,7 @@ static int append_extensions(
m->source,
pick_filter_image_raw,
ELEMENTSOF(pick_filter_image_raw),
/* match_root= */ NULL,
PICK_ARCHITECTURE|PICK_TRIES,
&result);
if (r == -ENOENT && m->ignore_enoent)
@@ -643,6 +644,7 @@ static int append_extensions(
e,
pick_filter_image_dir,
ELEMENTSOF(pick_filter_image_dir),
/* match_root= */ NULL,
PICK_ARCHITECTURE|PICK_TRIES,
&result);
if (r == -ENOENT && ignore_enoent)
+3
View File
@@ -836,6 +836,7 @@ static int extract_image_and_extensions(
name_or_path,
pick_filter_image_any,
ELEMENTSOF(pick_filter_image_any),
/* match_root= */ NULL,
PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
&result);
if (r < 0)
@@ -876,6 +877,7 @@ static int extract_image_and_extensions(
*p,
pick_filter_image_any,
ELEMENTSOF(pick_filter_image_any),
/* match_root= */ NULL,
PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
&ext_result);
if (r < 0)
@@ -2224,6 +2226,7 @@ static int marker_matches_images(const char *marker, const char *name_or_path, c
*image_name_or_path,
pick_filter_image_any,
ELEMENTSOF(pick_filter_image_any),
/* match_root= */ NULL,
PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
&result);
if (r < 0 && r != -ENOENT)
+2
View File
@@ -962,6 +962,7 @@ int image_find(RuntimeScope scope,
fname_path, /* This has to be the unresolved entry with the .v suffix */
&filter,
/* n_filters= */ 1,
/* match_root= */ root,
PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
&result);
if (r < 0) {
@@ -1184,6 +1185,7 @@ int image_discover(
fname_path, /* This has to be the unresolved entry with the .v suffix */
&filter,
/* n_filters= */ 1,
/* match_root= */ root,
PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
&result);
if (r < 0) {
+1 -1
View File
@@ -139,7 +139,7 @@ static int mstack_load_one(MStack *mstack, const char *dir, int dir_fd, const ch
};
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
r = path_pick(dir, dir_fd, dir_fd, fname, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE, &result);
r = path_pick(dir, dir_fd, dir_fd, fname, &filter, /* n_filters= */ 1, /* match_root= */ NULL, PICK_ARCHITECTURE, &result);
if (r < 0)
return log_debug_errno(r, "Failed to resolve '%s' directory: %m", fname);
if (r == 0)
+62 -2
View File
@@ -6,7 +6,9 @@
#include "architecture.h"
#include "bitfield.h"
#include "chase.h"
#include "errno-util.h"
#include "fd-util.h"
#include "host-properties.h"
#include "log.h"
#include "parse-util.h"
#include "path-util.h"
@@ -14,6 +16,7 @@
#include "stat-util.h"
#include "string-util.h"
#include "vpick.h"
#include "xattr-match.h"
void pick_result_done(PickResult *p) {
assert(p);
@@ -184,6 +187,7 @@ static int pin_choice(
unsigned tries_left,
unsigned tries_done,
const PickFilter *filter,
const XAttrMatchFilter *xattr_filter, /* NULL to ignore "user.uapi.match_…" xattrs */
PickFlags flags,
PickResult *ret) {
@@ -224,6 +228,32 @@ static int pin_choice(
return 0;
}
if (xattr_filter) {
_cleanup_(xattr_match_lists_done) XAttrMatchLists lists = {};
r = xattr_match_lists_read(inode_fd, "", xattr_filter->mask, &lists);
if (ERRNO_IS_NEG_PRIVILEGE(r)) {
/* Unlike fstat() on the O_PATH fd, reading user xattrs requires read access to the
* inode. Don't let a single inaccessible entry fail the whole operation. */
log_debug_errno(r, "Not allowed to read 'user.uapi.match_*' extended attributes of '%s/%s', ignoring entry: %m",
empty_to_root(root_path), skip_leading_slash(inode_path));
*ret = PICK_RESULT_NULL;
return 0;
}
if (r < 0)
return log_debug_errno(r, "Failed to read 'user.uapi.match_*' extended attributes of '%s/%s': %m",
empty_to_root(root_path), skip_leading_slash(inode_path));
r = xattr_match_filter_test_lists(xattr_filter, lists, inode_path);
if (r < 0)
return r;
if (r == 0) {
log_debug("Inode '%s/%s' is not suitable for this system, ignoring.",
empty_to_root(root_path), skip_leading_slash(inode_path));
*ret = PICK_RESULT_NULL;
return 0;
}
}
_cleanup_(pick_result_done) PickResult result = {
.fd = TAKE_FD(inode_fd),
.st = st,
@@ -319,6 +349,7 @@ static int make_choice(
const char *inode_path,
int _inode_fd, /* we always take ownership of the fd, even on failure */
const PickFilter *filter,
const XAttrMatchFilter *xattr_filter,
PickFlags flags,
PickResult *ret) {
@@ -367,6 +398,7 @@ static int make_choice(
/* tries_left= */ UINT_MAX,
/* tries_done= */ UINT_MAX,
filter,
xattr_filter,
flags & ~PICK_RESOLVE,
ret);
@@ -476,6 +508,7 @@ static int make_choice(
.architecture = a,
.suffix = filter->suffix,
},
xattr_filter,
flags,
&found);
if (r == 0)
@@ -512,6 +545,7 @@ static int path_pick_one(
int dir_fd,
const char *path,
const PickFilter *filter,
const XAttrMatchFilter *xattr_filter,
PickFlags flags,
PickResult *ret) {
@@ -551,6 +585,7 @@ static int path_pick_one(
path,
/* inode_fd= */ -EBADF,
filter,
xattr_filter,
flags,
ret);
@@ -644,11 +679,13 @@ static int path_pick_one(
.architecture = filter->architecture,
.suffix = filter_suffix,
},
xattr_filter,
flags,
ret);
bypass:
/* Don't make any choice, but just use the passed path literally */
/* Don't make any choice, but just use the passed path literally. Since the path was explicitly
* specified we also don't check the "user.uapi.match_…" xattrs here. */
return pin_choice(
root_path,
root_fd,
@@ -658,6 +695,7 @@ bypass:
/* tries_left= */ UINT_MAX,
/* tries_done= */ UINT_MAX,
filter,
/* xattr_filter= */ NULL,
flags,
ret);
}
@@ -668,6 +706,7 @@ int path_pick(const char *root_path,
const char *path,
const PickFilter filters[],
size_t n_filters,
const char *match_root,
PickFlags flags,
PickResult *ret) {
@@ -680,11 +719,31 @@ int path_pick(const char *root_path,
assert(filters || n_filters == 0);
assert(ret);
/* Entries in .v/ directories may be restricted to specific systems via "user.uapi.match_…" xattrs
* (as set by systemd-sysupdate, see MatchXAttr= in sysupdate.d(5)). The properties of the system
* are only loaded once an entry actually carries one of them, from match_root (or the host if NULL).
* Note that this is deliberately separate from root_path: the directory we resolve paths in is not
* necessarily the root of the system the entries are going to be used on. */
_cleanup_(host_properties_done) HostProperties host = {};
const XAttrMatchFilter xattr_filter = {
.mask = XATTR_MATCH_MASK_ALL,
.host = &host,
.root = empty_to_null(match_root),
};
/* 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(root_path, root_fd, dir_fd, path, &filters[i], flags, &result);
r = path_pick_one(
root_path,
root_fd,
dir_fd,
path,
&filters[i],
FLAGS_SET(flags, PICK_IGNORE_XATTR_MATCH) ? NULL : &xattr_filter,
flags,
&result);
if (r < 0)
return r;
if (r == 0)
@@ -736,6 +795,7 @@ int path_pick_update_warn(
*path,
filters,
n_filters,
/* match_root= */ NULL,
flags,
&result);
if (r == -ENOENT) {
+5 -3
View File
@@ -7,9 +7,10 @@
#include "forward.h"
typedef enum PickFlags {
PICK_ARCHITECTURE = 1 << 0, /* Look for an architecture suffix */
PICK_TRIES = 1 << 1, /* Look for tries left/tries done counters */
PICK_RESOLVE = 1 << 2, /* Return the fully resolved (chased) path, rather than the path to the entry */
PICK_ARCHITECTURE = 1 << 0, /* Look for an architecture suffix */
PICK_TRIES = 1 << 1, /* Look for tries left/tries done counters */
PICK_RESOLVE = 1 << 2, /* Return the fully resolved (chased) path, rather than the path to the entry */
PICK_IGNORE_XATTR_MATCH = 1 << 3, /* Don't skip entries whose "user.uapi.match_…" xattrs don't match the system */
} PickFlags;
typedef struct PickFilter {
@@ -51,6 +52,7 @@ int path_pick(const char *root_path,
const char *path,
const PickFilter filters[],
size_t n_filters,
const char *match_root,
PickFlags flags,
PickResult *ret);
+94 -24
View File
@@ -2,6 +2,7 @@
#include <unistd.h>
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
@@ -10,6 +11,7 @@
#include "tests.h"
#include "tmpfile-util.h"
#include "vpick.h"
#include "xattr-util.h"
TEST(path_pick) {
_cleanup_(rm_rf_physical_and_freep) char *p = NULL;
@@ -48,7 +50,7 @@ TEST(path_pick) {
};
if (IN_SET(native_architecture(), ARCHITECTURE_X86, ARCHITECTURE_X86_64)) {
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, /* match_root= */ NULL, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_TRUE(S_ISREG(result.st.st_mode));
ASSERT_STREQ(result.version, "99");
ASSERT_EQ(result.architecture, ARCHITECTURE_X86);
@@ -58,7 +60,7 @@ TEST(path_pick) {
}
filter.architecture = ARCHITECTURE_X86_64;
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, /* match_root= */ NULL, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_TRUE(S_ISREG(result.st.st_mode));
ASSERT_STREQ(result.version, "55");
ASSERT_EQ(result.architecture, ARCHITECTURE_X86_64);
@@ -66,7 +68,7 @@ TEST(path_pick) {
pick_result_done(&result);
filter.architecture = ARCHITECTURE_IA64;
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, /* match_root= */ NULL, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_TRUE(S_ISREG(result.st.st_mode));
ASSERT_STREQ(result.version, "5");
ASSERT_EQ(result.architecture, ARCHITECTURE_IA64);
@@ -75,7 +77,7 @@ TEST(path_pick) {
filter.architecture = _ARCHITECTURE_INVALID;
filter.version = "5";
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, /* match_root= */ NULL, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_TRUE(S_ISREG(result.st.st_mode));
ASSERT_STREQ(result.version, "5");
if (native_architecture() != ARCHITECTURE_IA64) {
@@ -85,7 +87,7 @@ TEST(path_pick) {
pick_result_done(&result);
filter.architecture = ARCHITECTURE_IA64;
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, /* match_root= */ NULL, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_TRUE(S_ISREG(result.st.st_mode));
ASSERT_STREQ(result.version, "5");
ASSERT_EQ(result.architecture, ARCHITECTURE_IA64);
@@ -93,7 +95,7 @@ TEST(path_pick) {
pick_result_done(&result);
filter.architecture = ARCHITECTURE_CRIS;
ASSERT_OK_ZERO(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_OK_ZERO(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, /* match_root= */ NULL, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_EQ(result.st.st_mode, MODE_INVALID);
ASSERT_NULL(result.version);
ASSERT_LT(result.architecture, 0);
@@ -104,7 +106,7 @@ TEST(path_pick) {
filter.architecture = _ARCHITECTURE_INVALID;
filter.version = NULL;
if (IN_SET(native_architecture(), ARCHITECTURE_X86_64, ARCHITECTURE_X86)) {
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, /* match_root= */ NULL, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_TRUE(S_ISREG(result.st.st_mode));
ASSERT_STREQ(result.version, "55");
@@ -123,7 +125,7 @@ TEST(path_pick) {
pp = ASSERT_NOT_NULL(path_join(p, "foo.v/foo___.raw"));
if (IN_SET(native_architecture(), ARCHITECTURE_X86, ARCHITECTURE_X86_64)) {
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, /* match_root= */ NULL, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_TRUE(S_ISREG(result.st.st_mode));
ASSERT_STREQ(result.version, "55");
ASSERT_EQ(result.architecture, native_architecture());
@@ -137,10 +139,10 @@ TEST(path_pick) {
pp = ASSERT_NOT_NULL(path_join(p, "foo.v/foo_5.raw"));
filter.type_mask = UINT32_C(1) << DT_DIR;
ASSERT_OK_ZERO(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_OK_ZERO(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, /* match_root= */ NULL, PICK_ARCHITECTURE|PICK_TRIES, &result));
filter.type_mask = UINT32_C(1) << DT_REG;
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, /* match_root= */ NULL, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_TRUE(S_ISREG(result.st.st_mode));
ASSERT_NULL(result.version);
ASSERT_EQ(result.architecture, _ARCHITECTURE_INVALID);
@@ -153,7 +155,7 @@ TEST(path_pick) {
filter.architecture = ARCHITECTURE_S390;
filter.basename = "quux";
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, /* match_root= */ NULL, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_TRUE(S_ISREG(result.st.st_mode));
ASSERT_STREQ(result.version, "2");
ASSERT_EQ(result.tries_left, 4U);
@@ -209,7 +211,7 @@ TEST(pick_filter_image_any) {
_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(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), /* match_root= */ NULL, PICK_ARCHITECTURE, &result));
ASSERT_TRUE(S_ISDIR(result.st.st_mode));
ASSERT_STREQ(result.version, "5");
ASSERT_TRUE(endswith(result.path, "/test_5"));
@@ -219,14 +221,14 @@ TEST(pick_filter_image_any) {
ASSERT_OK(unlinkat(sub_dfd, "test_4", AT_REMOVEDIR));
ASSERT_OK(unlinkat(sub_dfd, "test_5", AT_REMOVEDIR));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), /* match_root= */ NULL, 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(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_raw, ELEMENTSOF(pick_filter_image_raw), PICK_ARCHITECTURE, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_raw, ELEMENTSOF(pick_filter_image_raw), /* match_root= */ NULL, PICK_ARCHITECTURE, &result));
ASSERT_TRUE(S_ISREG(result.st.st_mode));
ASSERT_STREQ(result.version, "3");
ASSERT_TRUE(endswith(result.path, "/test_3.raw"));
@@ -239,12 +241,12 @@ TEST(pick_filter_image_any) {
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(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result));
ASSERT_OK_ZERO(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), /* match_root= */ NULL, 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(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), /* match_root= */ NULL, PICK_ARCHITECTURE, &result));
ASSERT_TRUE(S_ISDIR(result.st.st_mode));
ASSERT_STREQ(result.version, "6");
ASSERT_TRUE(endswith(result.path, "/test_6"));
@@ -263,7 +265,7 @@ TEST(pick_filter_image_any) {
pick_result_done(&result);
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), PICK_ARCHITECTURE, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), /* match_root= */ NULL, PICK_ARCHITECTURE, &result));
ASSERT_TRUE(S_ISDIR(result.st.st_mode));
ASSERT_STREQ(result.version, "2");
ASSERT_TRUE(endswith(result.path, "/myimage_2"));
@@ -273,7 +275,7 @@ TEST(pick_filter_image_any) {
ASSERT_OK(unlinkat(sub_dfd, "myimage_1", AT_REMOVEDIR));
ASSERT_OK(unlinkat(sub_dfd, "myimage_2", AT_REMOVEDIR));
ASSERT_OK_ZERO(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), PICK_ARCHITECTURE, &result));
ASSERT_OK_ZERO(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), /* match_root= */ NULL, PICK_ARCHITECTURE, &result));
}
TEST(path_pick_resolve) {
@@ -294,26 +296,26 @@ TEST(path_pick_resolve) {
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
/* Test without PICK_RESOLVE - should return the symlink path */
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), /* match_root= */ NULL, 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(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE|PICK_RESOLVE, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), /* match_root= */ NULL, 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(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), PICK_ARCHITECTURE, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), /* match_root= */ NULL, 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(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), PICK_ARCHITECTURE|PICK_RESOLVE, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), /* match_root= */ NULL, 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"));
@@ -323,12 +325,12 @@ TEST(path_pick_resolve) {
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(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_raw, ELEMENTSOF(pick_filter_image_raw), PICK_ARCHITECTURE, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_raw, ELEMENTSOF(pick_filter_image_raw), /* match_root= */ NULL, 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(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_raw, ELEMENTSOF(pick_filter_image_raw), PICK_ARCHITECTURE|PICK_RESOLVE, &result));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_raw, ELEMENTSOF(pick_filter_image_raw), /* match_root= */ NULL, 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"));
@@ -404,4 +406,72 @@ TEST(pick_result_compare) {
a.version = b.version = NULL;
}
TEST(path_pick_xattr_match) {
_cleanup_(rm_rf_physical_and_freep) char *p = NULL;
_cleanup_close_ int dfd = -EBADF, sub_dfd = -EBADF;
int r;
dfd = ASSERT_OK(mkdtemp_open(/* template= */ NULL, O_DIRECTORY, &p));
sub_dfd = ASSERT_OK(open_mkdir_at(dfd, "foo.v", /* flags= */ 0, 0777));
ASSERT_OK(write_string_file_at(sub_dfd, "foo_1.raw", "1", WRITE_STRING_FILE_CREATE));
ASSERT_OK(write_string_file_at(sub_dfd, "foo_2.raw", "2", WRITE_STRING_FILE_CREATE));
ASSERT_OK(write_string_file_at(sub_dfd, "foo_3.raw", "3", WRITE_STRING_FILE_CREATE));
/* The newest one is restricted to a machine ID no system has, the second newest to any hostname */
r = xsetxattr_strv(sub_dfd, "foo_3.raw", /* at_flags= */ 0, "user.uapi.match_machine_id", STRV_MAKE("00000000000000000000000000000000"));
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
return (void) log_tests_skipped_errno(r, "User extended attributes not supported");
ASSERT_OK(r);
ASSERT_OK(xsetxattr_strv(sub_dfd, "foo_2.raw", /* at_flags= */ 0, "user.uapi.match_hostname", STRV_MAKE("*")));
_cleanup_free_ char *pp = ASSERT_NOT_NULL(path_join(p, "foo.v"));
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
PickFilter filter = {
.architecture = _ARCHITECTURE_INVALID,
.suffix = ".raw",
};
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, /* match_root= */ NULL, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_STREQ(result.version, "2");
pick_result_done(&result);
/* Also when the version is fully specified */
filter.version = "3";
ASSERT_OK_ZERO(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, /* match_root= */ NULL, /* flags= */ 0, &result));
filter.version = NULL;
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, /* match_root= */ NULL, PICK_ARCHITECTURE|PICK_TRIES|PICK_IGNORE_XATTR_MATCH, &result));
ASSERT_STREQ(result.version, "3");
pick_result_done(&result);
/* An invalid extended attribute disqualifies an entry too */
ASSERT_OK(xsetxattr_strv(sub_dfd, "foo_2.raw", /* at_flags= */ 0, "user.uapi.match_hostname", STRV_MAKE("")));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, /* match_root= */ NULL, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_STREQ(result.version, "1");
pick_result_done(&result);
/* The host properties are loaded from match_root if specified, not from the host */
_cleanup_(rm_rf_physical_and_freep) char *mr = NULL;
_cleanup_close_ int mr_fd = ASSERT_OK(mkdtemp_open(/* template= */ NULL, O_DIRECTORY, &mr));
ASSERT_OK(write_string_file_at(mr_fd, "etc/hostname", "vpick-test-host", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755));
ASSERT_OK(write_string_file_at(sub_dfd, "foo_4.raw", "4", WRITE_STRING_FILE_CREATE));
ASSERT_OK(xsetxattr_strv(sub_dfd, "foo_4.raw", /* at_flags= */ 0, "user.uapi.match_hostname", STRV_MAKE("vpick-test-host")));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, mr, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_STREQ(result.version, "4");
pick_result_done(&result);
/* An empty /etc/hostname in match_root means no hostname, which doesn't match, but isn't an error */
ASSERT_OK(write_string_file_at(mr_fd, "etc/hostname", "# nothing", WRITE_STRING_FILE_TRUNCATE));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, mr, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_STREQ(result.version, "1");
pick_result_done(&result);
/* A path that doesn't make use of .v/ is taken literally */
_cleanup_free_ char *literal = ASSERT_NOT_NULL(path_join(p, "foo.v/foo_3.raw"));
ASSERT_OK_POSITIVE(path_pick(/* root_path= */ NULL, AT_FDCWD, AT_FDCWD, literal, &filter, /* n_filters= */ 1, /* match_root= */ NULL, PICK_ARCHITECTURE|PICK_TRIES, &result));
ASSERT_TRUE(endswith(result.path, "/foo_3.raw"));
}
DEFINE_TEST_MAIN(LOG_DEBUG);
+10
View File
@@ -10,6 +10,7 @@
#include "format-table.h"
#include "log.h"
#include "main-func.h"
#include "parse-argument.h"
#include "parse-util.h"
#include "path-util.h"
#include "stat-util.h"
@@ -140,6 +141,14 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
break;
OPTION_LONG("match-xattr", "BOOL", "Skip entries whose user.uapi.match_* xattrs don't match the system (default: yes)"):
r = parse_boolean_argument("--match-xattr=", opts.arg, /* ret= */ NULL);
if (r < 0)
return r;
SET_FLAG(arg_flags, PICK_IGNORE_XATTR_MATCH, !r);
break;
OPTION_GROUP("Output"): {}
OPTION_COMMON_HELP:
@@ -223,6 +232,7 @@ static int run(int argc, char *argv[]) {
.type_mask = arg_filter_type_mask,
},
/* n_filters= */ 1,
/* match_root= */ NULL,
arg_flags,
&result);
if (r < 0)
+14
View File
@@ -8,6 +8,7 @@ at_exit() {
rm -rf /var/lib/machines/mymachine.raw.v
rm -rf /var/lib/machines/mytree.v
rm -rf /var/lib/machines/testroot.v
rm -rf /var/lib/machines/xattr.raw.v
umount -l /tmp/dotvroot
rmdir /tmp/dotvroot
}
@@ -114,3 +115,16 @@ test "$(systemd-vpick --resolve=yes /var/lib/machines/testroot.v)" = /var/lib/ma
rm -rf /var/lib/machines/testroot.v/testroot_32
(! systemd-vpick /var/lib/machines/testroot.v)
(! systemd-run --wait -p RootDirectory=/var/lib/machines/testroot.v true)
# Entries may be restricted to specific systems via "user.uapi.match_…" extended attributes
mkdir /var/lib/machines/xattr.raw.v
touch /var/lib/machines/xattr.raw.v/xattr_1.raw /var/lib/machines/xattr.raw.v/xattr_2.raw
setfattr -n user.uapi.match_machine_id -v 00000000000000000000000000000000 /var/lib/machines/xattr.raw.v/xattr_2.raw
test "$(systemd-vpick --suffix=.raw /var/lib/machines/xattr.raw.v)" = /var/lib/machines/xattr.raw.v/xattr_1.raw
test "$(systemd-vpick --suffix=.raw --match-xattr=no /var/lib/machines/xattr.raw.v)" = /var/lib/machines/xattr.raw.v/xattr_2.raw
setfattr -n user.uapi.match_machine_id -v "$(</etc/machine-id)" /var/lib/machines/xattr.raw.v/xattr_2.raw
test "$(systemd-vpick --suffix=.raw /var/lib/machines/xattr.raw.v)" = /var/lib/machines/xattr.raw.v/xattr_2.raw
setfattr -n user.uapi.match_hostname -v "not-$(hostname)" /var/lib/machines/xattr.raw.v/xattr_2.raw
test "$(systemd-vpick --suffix=.raw /var/lib/machines/xattr.raw.v)" = /var/lib/machines/xattr.raw.v/xattr_1.raw
# A literally specified path is not filtered
test "$(systemd-vpick /var/lib/machines/xattr.raw.v/xattr_2.raw)" = /var/lib/machines/xattr.raw.v/xattr_2.raw