From ee79c9d3091f263c3509b99b9f707468bf17806e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 8 Oct 2025 09:39:17 +0200 Subject: [PATCH 01/11] fd-util: introduce XAT_FDROOT fd shortcut for the root dir This takes inspiration from AT_FDCWD, but always references the root dir rather than the cwd. This allows various convenience uses, as we can now reference the root dir without actually allocating an fd for chaseat(), fd_get_path() and various others. The value of XAT_FDROOT is chosen so that it is not a valid fd (essential, because we do not intend to hook this up everywhere, just where we need it, just like AT_FCWD only works in some syscalls), and we want uses at the wrong places to fail cleanly. It also uses a valud outside of the range where we usually return negative errnos, i.e. < -ERRNO_MAX. --- src/basic/fd-util.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index bb9669a84e..2663ff8aac 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -57,6 +57,13 @@ #define NR_OPEN_MINIMUM ((unsigned) (sizeof(long) * 8)) #define NR_OPEN_MAXIMUM ((unsigned) (CONST_MIN((size_t) INT_MAX, SIZE_MAX / __SIZEOF_POINTER__) & ~(sizeof(long) * 8 - 1))) +/* A special fd that can be passed in various helpers instead of an fd indicating the root dir. Inspired by, + * and an alternative to AT_FDCWD. We use specific negative value that is outside of the negative errno + * range, to avoid any potential ambiguities. */ +#define XAT_FDROOT -8192 +assert_cc(XAT_FDROOT != AT_FDCWD); +assert_cc(XAT_FDROOT < -ERRNO_MAX); + int close_nointr(int fd); int safe_close(int fd); void safe_close_pair(int p[static 2]); From ef582ab201c2942813960ad3c451df3642d85558 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 8 Dec 2025 13:31:56 +0100 Subject: [PATCH 02/11] fs-util: wire up XAT_FDROOT in xopenat_full() and access_fd() --- src/basic/fs-util.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index d668416ff8..d3712622f1 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -713,10 +713,15 @@ char* unlink_and_free(char *p) { } int access_fd(int fd, int mode) { - assert(fd >= 0); - /* Like access() but operates on an already open fd */ + if (fd == AT_FDCWD) + return RET_NERRNO(access(".", mode)); + if (fd == XAT_FDROOT) + return RET_NERRNO(access("/", mode)); + + assert(fd >= 0); + if (faccessat(fd, "", mode, AT_EMPTY_PATH) >= 0) return 0; if (errno != EINVAL) @@ -1160,7 +1165,7 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ bool made_dir = false, made_file = false; int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); /* An inode cannot be both a directory and a regular file at the same time. */ assert(!(FLAGS_SET(open_flags, O_DIRECTORY) && FLAGS_SET(xopen_flags, XO_REGULAR))); @@ -1179,8 +1184,23 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ * • if XO_REGULAR is specified will return an error if inode is not a regular file. * * • If mode is specified as MODE_INVALID, we'll use 0755 for dirs, and 0644 for regular files. + * + * • The dir fd can be passed as XAT_FDROOT, in which case any relative paths will be taken relative to the root fs. */ + _cleanup_close_ int _dir_fd = -EBADF; + if (dir_fd == XAT_FDROOT) { + if (path_is_absolute(path)) + dir_fd = AT_FDCWD; + else { + _dir_fd = open("/", O_CLOEXEC|O_DIRECTORY|O_RDONLY); + if (_dir_fd < 0) + return -errno; + + dir_fd = _dir_fd; + } + } + if (mode == MODE_INVALID) mode = (open_flags & O_DIRECTORY) ? 0755 : 0644; From 5667cbcacff3c415e16079ff29e4bada4a6882ea Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 8 Dec 2025 13:30:41 +0100 Subject: [PATCH 03/11] fd-util: wire up XAT_FDROOT in various really basic fd-util.h calls --- src/basic/fd-util.c | 58 +++++++++++++++++++++++++++++++++++++-------- src/basic/fd-util.h | 4 ++-- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index e08ad7d324..f80ce0ae47 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -572,10 +572,12 @@ bool fdname_is_valid(const char *s) { int fd_get_path(int fd, char **ret) { int r; - assert(fd >= 0 || fd == AT_FDCWD); + assert(fd >= 0 || IN_SET(fd, AT_FDCWD, XAT_FDROOT)); if (fd == AT_FDCWD) return safe_getcwd(ret); + if (fd == XAT_FDROOT) + return strdup_to(ret, "/"); r = readlink_malloc(FORMAT_PROC_FD_PATH(fd), ret); if (r == -ENOENT) @@ -770,7 +772,7 @@ finish: } int fd_reopen(int fd, int flags) { - assert(fd >= 0 || fd == AT_FDCWD); + assert(fd >= 0 || IN_SET(fd, AT_FDCWD, XAT_FDROOT)); assert(!FLAGS_SET(flags, O_CREAT)); /* Reopens the specified fd with new flags. This is useful for convert an O_PATH fd into a regular one, or to @@ -782,6 +784,8 @@ int fd_reopen(int fd, int flags) { * * If AT_FDCWD is specified as file descriptor gets an fd to the current cwd. * + * If XAT_FDROOT is specified as fd get an fd to the root directory. + * * If the specified file descriptor refers to a symlink via O_PATH, then this function cannot be used * to follow that symlink. Because we cannot have non-O_PATH fds to symlinks reopening it without * O_PATH will always result in -ELOOP. Or in other words: if you have an O_PATH fd to a symlink you @@ -795,6 +799,9 @@ int fd_reopen(int fd, int flags) { * the same way as the non-O_DIRECTORY case. */ return -ELOOP; + if (fd == XAT_FDROOT) + return RET_NERRNO(open("/", flags | O_DIRECTORY)); + if (FLAGS_SET(flags, O_DIRECTORY) || fd == AT_FDCWD) /* If we shall reopen the fd as directory we can just go via "." and thus bypass the whole * magic /proc/ directory, and make ourselves independent of that being mounted. */ @@ -1025,19 +1032,36 @@ int fd_get_diskseq(int fd, uint64_t *ret) { return 0; } +static bool is_literal_root(const char *p) { + if (!p) + return false; + + /* Check if string consists of at least one '/', and possibly more, but nothing else */ + size_t n = strspn(p, "/"); + return n >= 1 && p[n] == 0; +} + int path_is_root_at(int dir_fd, const char *path) { - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); + + if (dir_fd == XAT_FDROOT && isempty(path)) + return true; + + if (IN_SET(dir_fd, XAT_FDROOT, AT_FDCWD) && is_literal_root(path)) + return true; _cleanup_close_ int fd = -EBADF; if (!isempty(path)) { - fd = openat(dir_fd, path, O_PATH|O_DIRECTORY|O_CLOEXEC); + fd = xopenat(dir_fd, path, O_PATH|O_DIRECTORY|O_CLOEXEC); + if (fd == -ENOTDIR) + return false; /* the root dir must be a dir */ if (fd < 0) - return errno == ENOTDIR ? false : -errno; + return fd; dir_fd = fd; } - _cleanup_close_ int root_fd = openat(AT_FDCWD, "/", O_PATH|O_DIRECTORY|O_CLOEXEC); + _cleanup_close_ int root_fd = open("/", O_PATH|O_DIRECTORY|O_CLOEXEC); if (root_fd < 0) return -errno; @@ -1055,13 +1079,27 @@ int path_is_root_at(int dir_fd, const char *path) { int fds_are_same_mount(int fd1, int fd2) { struct statx sx1 = {}, sx2 = {}; /* explicitly initialize the struct to make msan silent. */ - assert(fd1 >= 0); - assert(fd2 >= 0); + assert(fd1 >= 0 || IN_SET(fd1, AT_FDCWD, XAT_FDROOT)); + assert(fd2 >= 0 || IN_SET(fd2, AT_FDCWD, XAT_FDROOT)); - if (statx(fd1, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sx1) < 0) + const char *fn1; + if (fd1 == XAT_FDROOT) { + fd1 = AT_FDCWD; + fn1 = "/"; + } else + fn1 = ""; + + if (statx(fd1, fn1, AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sx1) < 0) return -errno; - if (statx(fd2, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sx2) < 0) + const char *fn2; + if (fd2 == XAT_FDROOT) { + fd2 = AT_FDCWD; + fn2 = "/"; + } else + fn2 = ""; + + if (statx(fd2, fn2, AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sx2) < 0) return -errno; return statx_inode_same(&sx1, &sx2) && statx_mount_same(&sx1, &sx2); diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index 2663ff8aac..707445d73d 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -173,10 +173,10 @@ static inline int path_is_root(const char *path) { return path_is_root_at(AT_FDCWD, path); } static inline int dir_fd_is_root(int dir_fd) { - return path_is_root_at(dir_fd, NULL); + return dir_fd == XAT_FDROOT ? true : path_is_root_at(dir_fd, NULL); } static inline int dir_fd_is_root_or_cwd(int dir_fd) { - return dir_fd == AT_FDCWD ? true : path_is_root_at(dir_fd, NULL); + return IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT) ? true : path_is_root_at(dir_fd, NULL); } int fds_are_same_mount(int fd1, int fd2); From 874971b1c0d0674bb46f21bda805287d17c34a49 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 8 Dec 2025 13:32:09 +0100 Subject: [PATCH 04/11] chase: wire up XAT_FDROOT --- src/basic/chase.c | 22 +++++++++++++++++----- src/test/test-chase.c | 11 +++++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/basic/chase.c b/src/basic/chase.c index 0199f5dd69..969f88afd2 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -135,7 +135,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int assert(!FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY|CHASE_MUST_BE_REGULAR)); assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME)); assert(!FLAGS_SET(flags, CHASE_NO_AUTOFS|CHASE_TRIGGER_AUTOFS)); - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); if (FLAGS_SET(flags, CHASE_STEP)) assert(!ret_fd); @@ -218,7 +218,15 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int * the mount point is emitted. CHASE_WARN cannot be used in PID 1. */ - if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) { + _cleanup_close_ int _dir_fd = -EBADF; + if (dir_fd == XAT_FDROOT) { + _dir_fd = open("/", O_DIRECTORY|O_RDONLY|O_CLOEXEC); + if (_dir_fd < 0) + return -errno; + + dir_fd = _dir_fd; + flags &= ~CHASE_AT_RESOLVE_IN_ROOT; + } else if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) { /* If we get AT_FDCWD or dir_fd points to "/", then we always resolve symlinks relative to * the host's root. Hence, CHASE_AT_RESOLVE_IN_ROOT is meaningless. */ @@ -656,9 +664,13 @@ int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.", absolute, root); - fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH); - if (fd < 0) - return -errno; + if (empty_or_root(root)) + fd = XAT_FDROOT; + else { + fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH); + if (fd < 0) + return -errno; + } r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL); if (r < 0) diff --git a/src/test/test-chase.c b/src/test/test-chase.c index 89705b4ffd..2dc554ebd1 100644 --- a/src/test/test-chase.c +++ b/src/test/test-chase.c @@ -499,6 +499,17 @@ TEST(chaseat) { fd = safe_close(fd); + /* Same but with XAT_FDROOT */ + ASSERT_OK(chaseat(XAT_FDROOT, p, 0, &result, NULL)); + ASSERT_STREQ(result, "/usr"); + result = mfree(result); + + ASSERT_OK(chaseat(XAT_FDROOT, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_STREQ(result, "/usr"); + result = mfree(result); + + fd = safe_close(fd); + /* If the file descriptor does not point to the root directory, the result will be relative * unless the result is outside of the specified file descriptor. */ From ded2531764631aefe7846107c6f2fa2e1fd1d62d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 28 Dec 2025 10:50:29 +0100 Subject: [PATCH 05/11] chase: optimize the special case where no root dir specified Now that we can recognize the root dir in chaseat() sanely, let's use it top optimize the very common special case where we have no root dir to consider, and directly call open_tree(). --- src/basic/chase.c | 31 ++++++++++++++++++++++++++++++- src/test/test-chase.c | 25 ++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/basic/chase.c b/src/basic/chase.c index 969f88afd2..323e462375 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -128,6 +128,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */ bool exists = true, append_trail_slash = false; struct stat st; /* stat obtained from fd */ + bool need_absolute = false; /* allocate early to avoid compiler warnings around goto */ const char *todo; int r; @@ -220,6 +221,33 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int _cleanup_close_ int _dir_fd = -EBADF; if (dir_fd == XAT_FDROOT) { + + /* Shortcut the common case where no root dir is specified, and no special flags are given to + * a regular open() */ + if (!ret_path && + (flags & (CHASE_STEP|CHASE_NO_AUTOFS|CHASE_NONEXISTENT|CHASE_SAFE|CHASE_WARN|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) { + _cleanup_free_ char *slash_path = NULL; + + if (!path_is_absolute(path)) { + slash_path = strjoin("/", path); + if (!slash_path) + return -ENOMEM; + } + + /* We use open_tree() rather than regular open() here, because it gives us direct + * control over automount behaviour, and otherwise is equivalent to open() with + * O_PATH */ + fd = open_tree(-EBADF, slash_path ?: path, OPEN_TREE_CLOEXEC|(FLAGS_SET(flags, CHASE_TRIGGER_AUTOFS) ? 0 : AT_NO_AUTOMOUNT)); + if (fd < 0) + return -errno; + + if (fstat(fd, &st) < 0) + return -errno; + + exists = true; + goto success; + } + _dir_fd = open("/", O_DIRECTORY|O_RDONLY|O_CLOEXEC); if (_dir_fd < 0) return -errno; @@ -259,7 +287,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (r < 0) return r; - bool need_absolute = r; + need_absolute = r; if (need_absolute) { done = strdup("/"); if (!done) @@ -522,6 +550,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int close_and_replace(fd, child); } +success: if (exists) { if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) { r = stat_verify_directory(&st); diff --git a/src/test/test-chase.c b/src/test/test-chase.c index 2dc554ebd1..129ea19b72 100644 --- a/src/test/test-chase.c +++ b/src/test/test-chase.c @@ -500,15 +500,34 @@ TEST(chaseat) { fd = safe_close(fd); /* Same but with XAT_FDROOT */ - ASSERT_OK(chaseat(XAT_FDROOT, p, 0, &result, NULL)); + _cleanup_close_ int found_fd1 = -EBADF; + ASSERT_OK(chaseat(XAT_FDROOT, p, 0, &result, &found_fd1)); ASSERT_STREQ(result, "/usr"); result = mfree(result); - ASSERT_OK(chaseat(XAT_FDROOT, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + _cleanup_close_ int found_fd2 = -EBADF; + ASSERT_OK(chaseat(XAT_FDROOT, p, CHASE_AT_RESOLVE_IN_ROOT, &result, &found_fd2)); ASSERT_STREQ(result, "/usr"); result = mfree(result); + assert(fd_inode_same(found_fd1, found_fd2) > 0); - fd = safe_close(fd); + /* Do the same XAT_FDROOT tests again, this time without querying the path, so that the open_tree() + * shortcut can work */ + _cleanup_close_ int found_fd3 = -EBADF; + ASSERT_OK(chaseat(XAT_FDROOT, p, 0, NULL, &found_fd3)); + assert(fd_inode_same(found_fd1, found_fd3) > 0); + assert(fd_inode_same(found_fd2, found_fd3) > 0); + + _cleanup_close_ int found_fd4 = -EBADF; + ASSERT_OK(chaseat(XAT_FDROOT, p, CHASE_AT_RESOLVE_IN_ROOT, NULL, &found_fd4)); + assert(fd_inode_same(found_fd1, found_fd4) > 0); + assert(fd_inode_same(found_fd2, found_fd4) > 0); + assert(fd_inode_same(found_fd3, found_fd4) > 0); + + found_fd1 = safe_close(found_fd1); + found_fd2 = safe_close(found_fd2); + found_fd3 = safe_close(found_fd3); + found_fd4 = safe_close(found_fd4); /* If the file descriptor does not point to the root directory, the result will be relative * unless the result is outside of the specified file descriptor. */ From c67abc9c43d2761af9f58df57eeee96b0f045f98 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 16 Jan 2026 17:55:47 +0100 Subject: [PATCH 06/11] chase: extend coverage of the root fd shortcut Let's use dir_fd_is_root() to check if we are looking at the real root fs. --- src/basic/chase.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/basic/chase.c b/src/basic/chase.c index 323e462375..f360cc26a3 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -220,7 +220,10 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int */ _cleanup_close_ int _dir_fd = -EBADF; - if (dir_fd == XAT_FDROOT) { + r = dir_fd_is_root(dir_fd); + if (r < 0) + return r; + if (r > 0) { /* Shortcut the common case where no root dir is specified, and no special flags are given to * a regular open() */ From 95abe4beff08636681069623ffdf16cfa34c7cf8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 8 Dec 2025 13:32:42 +0100 Subject: [PATCH 07/11] stat-util: write up XAT_FDROOT in stat-util.[ch] --- src/basic/stat-util.c | 80 +++++++++++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 22 deletions(-) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index bb20ba50de..d1c5d7aa37 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -31,10 +31,25 @@ static int verify_stat_at( struct stat st; int r; - assert(fd >= 0 || fd == AT_FDCWD); + assert(fd >= 0 || IN_SET(fd, AT_FDCWD, XAT_FDROOT)); assert(!isempty(path) || !follow); assert(verify_func); + _cleanup_free_ char *p = NULL; + if (fd == XAT_FDROOT) { + fd = AT_FDCWD; + + if (isempty(path)) + path = "/"; + else if (!path_is_absolute(path)) { + p = strjoin("/", path); + if (!p) + return -ENOMEM; + + path = p; + } + } + if (fstatat(fd, strempty(path), &st, (isempty(path) ? AT_EMPTY_PATH : 0) | (follow ? 0 : AT_SYMLINK_NOFOLLOW)) < 0) return -errno; @@ -66,8 +81,10 @@ int verify_regular_at(int fd, const char *path, bool follow) { } int fd_verify_regular(int fd) { - assert(fd >= 0); - return verify_regular_at(fd, NULL, false); + if (IN_SET(fd, AT_FDCWD, XAT_FDROOT)) + return -EISDIR; + + return verify_regular_at(fd, /* path= */ NULL, /* follow= */ false); } int stat_verify_directory(const struct stat *st) { @@ -83,7 +100,9 @@ int stat_verify_directory(const struct stat *st) { } int fd_verify_directory(int fd) { - assert(fd >= 0); + if (IN_SET(fd, AT_FDCWD, XAT_FDROOT)) + return 0; + return verify_stat_at(fd, NULL, false, stat_verify_directory, true); } @@ -127,7 +146,10 @@ int stat_verify_linked(const struct stat *st) { } int fd_verify_linked(int fd) { - assert(fd >= 0); + + if (fd == XAT_FDROOT) + return 0; + return verify_stat_at(fd, NULL, false, stat_verify_linked, true); } @@ -223,23 +245,33 @@ int null_or_empty_path_with_root(const char *fn, const char *root) { return null_or_empty(&st); } -int fd_is_read_only_fs(int fd) { - struct statfs st; +static int xfstatfs(int fd, struct statfs *ret) { + assert(ret); + + if (fd == AT_FDCWD) + return RET_NERRNO(statfs(".", ret)); + if (fd == XAT_FDROOT) + return RET_NERRNO(statfs("/", ret)); assert(fd >= 0); + return RET_NERRNO(fstatfs(fd, ret)); +} - if (fstatfs(fd, &st) < 0) - return -errno; +int fd_is_read_only_fs(int fd) { + int r; + + struct statfs st; + r = xfstatfs(fd, &st); + if (r < 0) + return r; if (st.f_flags & ST_RDONLY) return true; - if (is_network_fs(&st)) { + if (is_network_fs(&st)) /* On NFS, fstatfs() might not reflect whether we can actually write to the remote share. * Let's try again with access(W_OK) which is more reliable, at least sometimes. */ - if (access_fd(fd, W_OK) == -EROFS) - return true; - } + return access_fd(fd, W_OK) == -EROFS; return false; } @@ -364,9 +396,9 @@ bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) { } int is_fs_type_at(int dir_fd, const char *path, statfs_f_type_t magic_value) { - struct statfs s; int r; + struct statfs s; r = xstatfsat(dir_fd, path, &s); if (r < 0) return r; @@ -383,19 +415,23 @@ bool is_network_fs(const struct statfs *s) { } int fd_is_temporary_fs(int fd) { - struct statfs s; + int r; - if (fstatfs(fd, &s) < 0) - return -errno; + struct statfs s; + r = xfstatfs(fd, &s); + if (r < 0) + return r; return is_temporary_fs(&s); } int fd_is_network_fs(int fd) { - struct statfs s; + int r; - if (fstatfs(fd, &s) < 0) - return -errno; + struct statfs s; + r = xfstatfs(fd, &s); + if (r < 0) + return r; return is_network_fs(&s); } @@ -489,7 +525,7 @@ bool statx_mount_same(const struct statx *a, const struct statx *b) { int xstatfsat(int dir_fd, const char *path, struct statfs *ret) { _cleanup_close_ int fd = -EBADF; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); assert(ret); if (!isempty(path)) { @@ -499,7 +535,7 @@ int xstatfsat(int dir_fd, const char *path, struct statfs *ret) { dir_fd = fd; } - return RET_NERRNO(fstatfs(dir_fd, ret)); + return RET_NERRNO(xfstatfs(dir_fd, ret)); } usec_t statx_timestamp_load(const struct statx_timestamp *ts) { From 192f71baed625d576e6588f789d4e6086bdd34da Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 8 Dec 2025 13:32:55 +0100 Subject: [PATCH 08/11] id128-util: pass through XAT_FDROOT --- src/libsystemd/sd-id128/id128-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsystemd/sd-id128/id128-util.c b/src/libsystemd/sd-id128/id128-util.c index 86a2938af4..0cae92b728 100644 --- a/src/libsystemd/sd-id128/id128-util.c +++ b/src/libsystemd/sd-id128/id128-util.c @@ -140,7 +140,7 @@ int id128_read_fd(int fd, Id128Flag f, sd_id128_t *ret) { int id128_read_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t *ret) { _cleanup_close_ int fd = -EBADF; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); assert(path); fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY); From 7e64b58f4c1d37a033f63a6dc4a1bfbcea1eef4f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 8 Dec 2025 13:33:55 +0100 Subject: [PATCH 09/11] kernel-image: pass through XAT_FDROOT --- src/shared/kernel-image.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/shared/kernel-image.c b/src/shared/kernel-image.c index ed8fde1fb2..d3e18d19f4 100644 --- a/src/shared/kernel-image.c +++ b/src/shared/kernel-image.c @@ -3,6 +3,7 @@ #include "alloc-util.h" #include "env-file.h" #include "fd-util.h" +#include "fs-util.h" #include "kernel-image.h" #include "log.h" #include "pe-binary.h" @@ -134,12 +135,12 @@ int inspect_kernel( _cleanup_close_ int fd = -EBADF; int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); assert(filename); - fd = openat(dir_fd, filename, O_RDONLY|O_CLOEXEC); + fd = xopenat(dir_fd, filename, O_RDONLY|O_CLOEXEC); if (fd < 0) - return log_error_errno(errno, "Failed to open kernel image file '%s': %m", filename); + return log_error_errno(fd, "Failed to open kernel image file '%s': %m", filename); r = pe_load_headers(fd, &dos_header, &pe_header); if (r == -EBADMSG) /* not a valid PE file */ From 80499cf1c85326abab59bd5f7920cc27d562730d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 8 Dec 2025 13:33:20 +0100 Subject: [PATCH 10/11] find-esp: make use of XAT_FDROOT --- src/shared/find-esp.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index 8e1737eebb..cf4eddbbfa 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -332,7 +332,7 @@ static int verify_esp( dev_t devid = 0; int r; - assert(rfd >= 0 || rfd == AT_FDCWD); + assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); assert(path); /* This logs about all errors, except: @@ -442,14 +442,14 @@ int find_esp_and_warn_at( VerifyESPFlags flags; int r; + assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); + /* This logs about all errors except: * * -ENOKEY → when we can't find the partition * -EACCESS → when unprivileged_mode is true, and we can't access something */ - assert(rfd >= 0 || rfd == AT_FDCWD); - flags = verify_esp_flags_init(unprivileged_mode, "SYSTEMD_RELAX_ESP_CHECKS"); if (path) @@ -527,9 +527,13 @@ int find_esp_and_warn( dev_t devid; int r; - rfd = open(empty_to_root(root), O_PATH|O_DIRECTORY|O_CLOEXEC); - if (rfd < 0) - return -errno; + if (empty_or_root(root)) + rfd = XAT_FDROOT; + else { + rfd = open(root, O_PATH|O_DIRECTORY|O_CLOEXEC); + if (rfd < 0) + return -errno; + } r = find_esp_and_warn_at( rfd, @@ -737,7 +741,7 @@ static int verify_xbootldr( dev_t devid = 0; int r; - assert(rfd >= 0 || rfd == AT_FDCWD); + assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); assert(path); r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_TRIGGER_AUTOFS, &p, &pfd); @@ -800,7 +804,7 @@ int find_xbootldr_and_warn_at( /* Similar to find_esp_and_warn(), but finds the XBOOTLDR partition. Returns the same errors. */ - assert(rfd >= 0 || rfd == AT_FDCWD); + assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); flags = verify_esp_flags_init(unprivileged_mode, "SYSTEMD_RELAX_XBOOTLDR_CHECKS"); @@ -862,9 +866,13 @@ int find_xbootldr_and_warn( dev_t devid; int r; - rfd = open(empty_to_root(root), O_PATH|O_DIRECTORY|O_CLOEXEC); - if (rfd < 0) - return -errno; + if (empty_or_root(root)) + rfd = XAT_FDROOT; + else { + rfd = open(root, O_PATH|O_DIRECTORY|O_CLOEXEC); + if (rfd < 0) + return -errno; + } r = find_xbootldr_and_warn_at( rfd, From 6527f64600f767dde5b75547f04e742426bca1a8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 8 Dec 2025 13:34:24 +0100 Subject: [PATCH 11/11] test-fs-util: some basic tests for XAT_FDROOT --- src/test/test-fs-util.c | 69 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index 51b7b146fe..a56f8f92ad 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -819,11 +819,6 @@ TEST(linkat_replace) { assert_se(inode_same_at(fd1, NULL, fd2_check, NULL, AT_EMPTY_PATH) > 0); } -static int intro(void) { - arg_test_dir = saved_argv[1]; - return EXIT_SUCCESS; -} - TEST(readlinkat_malloc) { _cleanup_(rm_rf_physical_and_freep) char *t = NULL; _cleanup_close_ int tfd = -EBADF, fd = -EBADF; @@ -859,4 +854,68 @@ TEST(readlinkat_malloc) { q = mfree(q); } +TEST(xat_fdroot) { + _cleanup_free_ char *p = NULL; + ASSERT_OK(fd_get_path(XAT_FDROOT, &p)); + ASSERT_STREQ(p, "/"); + + _cleanup_close_ int fd = -EBADF; + fd = fd_reopen(XAT_FDROOT, O_CLOEXEC); + ASSERT_OK(fd); + + ASSERT_OK_POSITIVE(path_is_root_at(XAT_FDROOT, NULL)); + ASSERT_OK_POSITIVE(path_is_root_at(XAT_FDROOT, ".")); + ASSERT_OK_POSITIVE(path_is_root_at(XAT_FDROOT, "/")); + + ASSERT_OK_POSITIVE(path_is_root_at(fd, NULL)); + ASSERT_OK_POSITIVE(path_is_root_at(fd, ".")); + ASSERT_OK_POSITIVE(path_is_root_at(fd, "/")); + + ASSERT_OK_POSITIVE(fds_are_same_mount(fd, fd)); + ASSERT_OK_POSITIVE(fds_are_same_mount(XAT_FDROOT, XAT_FDROOT)); + ASSERT_OK_POSITIVE(fds_are_same_mount(fd, XAT_FDROOT)); + ASSERT_OK_POSITIVE(fds_are_same_mount(XAT_FDROOT, fd)); + + ASSERT_OK_POSITIVE(dir_fd_is_root(XAT_FDROOT)); + ASSERT_OK_POSITIVE(dir_fd_is_root(fd)); + + ASSERT_OK_POSITIVE(dir_fd_is_root_or_cwd(XAT_FDROOT)); + ASSERT_OK_POSITIVE(dir_fd_is_root_or_cwd(AT_FDCWD)); + ASSERT_OK_POSITIVE(dir_fd_is_root_or_cwd(fd)); + + ASSERT_OK(access_fd(XAT_FDROOT, F_OK)); + ASSERT_OK(access_fd(fd, F_OK)); + + fd = safe_close(fd); + fd = xopenat(XAT_FDROOT, ".", O_RDONLY); + ASSERT_OK(fd); + + fd = safe_close(fd); + fd = xopenat(XAT_FDROOT, "/", O_RDONLY); + ASSERT_OK(fd); + + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK(fd_verify_directory(XAT_FDROOT)); + + ASSERT_OK(fd_verify_linked(fd)); + ASSERT_OK(fd_verify_linked(XAT_FDROOT)); + + int a = fd_is_read_only_fs(fd); + ASSERT_OK(a); + int b = fd_is_read_only_fs(XAT_FDROOT); + ASSERT_OK(b); + ASSERT_EQ(a, b); + + a = fd_is_temporary_fs(fd); + ASSERT_OK(a); + b = fd_is_temporary_fs(XAT_FDROOT); + ASSERT_OK(b); + ASSERT_EQ(a, b); +} + +static int intro(void) { + arg_test_dir = saved_argv[1]; + return EXIT_SUCCESS; +} + DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro);