fd-util: introduce XAT_FDROOT fd shortcut for the root dir (#39244)

This commit is contained in:
Daan De Meyer
2026-01-18 11:57:11 +01:00
committed by GitHub
10 changed files with 306 additions and 63 deletions
+50 -6
View File
@@ -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;
@@ -135,7 +136,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 +219,45 @@ 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;
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() */
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;
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. */
@@ -251,7 +290,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)
@@ -514,6 +553,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);
@@ -656,9 +696,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)
+48 -10
View File
@@ -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);
+9 -2
View File
@@ -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]);
@@ -166,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);
+23 -3
View File
@@ -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;
+58 -22
View File
@@ -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) {