summaryrefslogtreecommitdiffstats
path: root/src/basic/fd-util.c
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2024-10-08 10:01:22 +0200
committerLennart Poettering <lennart@poettering.net>2024-10-08 13:13:49 +0200
commite7f905347526dd17543dd54561f56a047e6ff9f4 (patch)
tree61f574ac97abcbd16d2b0ebe1ac0968ba4531dd4 /src/basic/fd-util.c
parentfd-util: introduce fd_validate() helper (diff)
downloadsystemd-e7f905347526dd17543dd54561f56a047e6ff9f4.tar.xz
systemd-e7f905347526dd17543dd54561f56a047e6ff9f4.zip
fd-util: use F_DUPFD_QUERY for same_fd()
Catch up with the nice little toys the kernel fs developers have added for us. Preferably, let's make use of the new F_DUPFD_QUERY fcntl() call that checks whether two fds are just duplicates of each other (duplicates as in dup(), not as in open() of the same inode, i.e. whether they share a single file offset and so on). This API is much nicer, since it is a core kernel feature, unlike the kcmp() call we so far used, which is part of the (optional) checkpoint/restore stuff. F_DUPFD_QUERY is available since kernel 6.10.
Diffstat (limited to 'src/basic/fd-util.c')
-rw-r--r--src/basic/fd-util.c52
1 files changed, 42 insertions, 10 deletions
diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c
index 3f8c5b92d3..c112f8dbad 100644
--- a/src/basic/fd-util.c
+++ b/src/basic/fd-util.c
@@ -530,25 +530,57 @@ int same_fd(int a, int b) {
assert(b >= 0);
/* Compares two file descriptors. Note that semantics are quite different depending on whether we
- * have kcmp() or we don't. If we have kcmp() this will only return true for dup()ed file
- * descriptors, but not otherwise. If we don't have kcmp() this will also return true for two fds of
- * the same file, created by separate open() calls. Since we use this call mostly for filtering out
- * duplicates in the fd store this difference hopefully doesn't matter too much. */
+ * have F_DUPFD_QUERY/kcmp() or we don't. If we have F_DUPFD_QUERY/kcmp() this will only return true
+ * for dup()ed file descriptors, but not otherwise. If we don't have F_DUPFD_QUERY/kcmp() this will
+ * also return true for two fds of the same file, created by separate open() calls. Since we use this
+ * call mostly for filtering out duplicates in the fd store this difference hopefully doesn't matter
+ * too much.
+ *
+ * Guarantees that if either of the passed fds is not allocated we'll return -EBADF. */
+
+ if (a == b) {
+ /* Let's validate that the fd is valid */
+ r = fd_validate(a);
+ if (r < 0)
+ return r;
- if (a == b)
return true;
+ }
+
+ /* Try to use F_DUPFD_QUERY if we have it first, as it is the nicest API */
+ r = fcntl(a, F_DUPFD_QUERY, b);
+ if (r > 0)
+ return true;
+ if (r == 0) {
+ /* The kernel will return 0 in case the first fd is allocated, but the 2nd is not. (Which is different in the kcmp() case) Explicitly validate it hence. */
+ r = fd_validate(b);
+ if (r < 0)
+ return r;
+
+ return false;
+ }
+ /* On old kernels (< 6.10) that do not support F_DUPFD_QUERY this will return EINVAL for regular fds, and EBADF on O_PATH fds. Confusing. */
+ if (errno == EBADF) {
+ /* EBADF could mean two things: the first fd is not valid, or it is valid and is O_PATH and
+ * F_DUPFD_QUERY is not supported. Let's validate the fd explicitly, to distinguish this
+ * case. */
+ r = fd_validate(a);
+ if (r < 0)
+ return r;
+
+ /* If the fd is valid, but we got EBADF, then let's try kcmp(). */
+ } else if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno) && errno != EINVAL)
+ return -errno;
/* Try to use kcmp() if we have it. */
pid = getpid_cached();
r = kcmp(pid, pid, KCMP_FILE, a, b);
- if (r == 0)
- return true;
- if (r > 0)
- return false;
+ if (r >= 0)
+ return !r;
if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
return -errno;
- /* We don't have kcmp(), use fstat() instead. */
+ /* We have neither F_DUPFD_QUERY nor kcmp(), use fstat() instead. */
if (fstat(a, &sta) < 0)
return -errno;