]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
selftest: add tests for O_EMPTYPATH
authorJori Koolstra <jkoolstra@xs4all.nl>
Fri, 24 Apr 2026 11:46:03 +0000 (13:46 +0200)
committerChristian Brauner <brauner@kernel.org>
Thu, 21 May 2026 08:53:37 +0000 (10:53 +0200)
Add tests for the new O_EMPTYPATH flag of openat(2)/openat2(2).

Also, the current openat2 tests include a helper header file that
defines the necessary structs and constants to use openat2(2), such as
struct open_how. This may result in conflicting definitions when the
system header openat2.h is present as well.

So add openat2.h generated by 'make headers' to the uapi header
files in ./tools/include and remove the helper file definitions of
the current openat2 selftests.

Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
Link: https://patch.msgid.link/20260424114611.1678641-3-jkoolstra@xs4all.nl
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
tools/include/uapi/linux/openat2.h [new file with mode: 0644]
tools/testing/selftests/filesystems/openat2/Makefile
tools/testing/selftests/filesystems/openat2/emptypath_test.c [new file with mode: 0644]
tools/testing/selftests/filesystems/openat2/helpers.h

diff --git a/tools/include/uapi/linux/openat2.h b/tools/include/uapi/linux/openat2.h
new file mode 100644 (file)
index 0000000..4759c47
--- /dev/null
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_OPENAT2_H
+#define _LINUX_OPENAT2_H
+
+#include <linux/types.h>
+
+/*
+ * Arguments for how openat2(2) should open the target path. If only @flags and
+ * @mode are non-zero, then openat2(2) operates very similarly to openat(2).
+ *
+ * However, unlike openat(2), unknown or invalid bits in @flags result in
+ * -EINVAL rather than being silently ignored. @mode must be zero unless one of
+ * {O_CREAT, O_TMPFILE} are set.
+ *
+ * @flags: O_* flags.
+ * @mode: O_CREAT/O_TMPFILE file mode.
+ * @resolve: RESOLVE_* flags.
+ */
+struct open_how {
+       __u64 flags;
+       __u64 mode;
+       __u64 resolve;
+};
+
+/* how->resolve flags for openat2(2). */
+#define RESOLVE_NO_XDEV                0x01 /* Block mount-point crossings
+                                       (includes bind-mounts). */
+#define RESOLVE_NO_MAGICLINKS  0x02 /* Block traversal through procfs-style
+                                       "magic-links". */
+#define RESOLVE_NO_SYMLINKS    0x04 /* Block traversal through all symlinks
+                                       (implies OEXT_NO_MAGICLINKS) */
+#define RESOLVE_BENEATH                0x08 /* Block "lexical" trickery like
+                                       "..", symlinks, and absolute
+                                       paths which escape the dirfd. */
+#define RESOLVE_IN_ROOT                0x10 /* Make all jumps to "/" and ".."
+                                       be scoped inside the dirfd
+                                       (similar to chroot(2)). */
+#define RESOLVE_CACHED         0x20 /* Only complete if resolution can be
+                                       completed through cached lookup. May
+                                       return -EAGAIN if that's not
+                                       possible. */
+
+#endif /* _LINUX_OPENAT2_H */
index 7736e37b798663fe244777ec74712151d64de1bd..d848aac96bded5fa77b76df5126893964b6d49d4 100644 (file)
@@ -1,8 +1,8 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 
 CFLAGS += $(KHDR_INCLUDES)
-CFLAGS += -Wall -O2 -g -fsanitize=address -fsanitize=undefined
-TEST_GEN_PROGS := openat2_test resolve_test rename_attack_test
+CFLAGS += -Wall -O2 -g -fsanitize=address -fsanitize=undefined $(TOOLS_INCLUDES)
+TEST_GEN_PROGS := openat2_test resolve_test rename_attack_test emptypath_test
 
 # gcc requires -static-libasan in order to ensure that Address Sanitizer's
 # library is the first one loaded. However, clang already statically links the
diff --git a/tools/testing/selftests/filesystems/openat2/emptypath_test.c b/tools/testing/selftests/filesystems/openat2/emptypath_test.c
new file mode 100644 (file)
index 0000000..d75b5e9
--- /dev/null
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#define __SANE_USERSPACE_TYPES__
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "kselftest.h"
+
+#ifndef O_EMPTYPATH
+#define O_EMPTYPATH    (1 << 26)
+#endif
+
+int main(void)
+{
+       int opath_fd, reopen_fd;
+       const char *path = "/tmp/emptypath_test";
+
+       ksft_print_header();
+       ksft_set_plan(2);
+
+       opath_fd = open(path, O_CREAT | O_WRONLY, S_IRWXU);
+       if (opath_fd < 0)
+               ksft_exit_fail_msg("create %s: %m\n", path);
+       close(opath_fd);
+
+       opath_fd = open(path, O_PATH);
+       if (opath_fd < 0)
+               ksft_exit_fail_msg("open %s O_PATH: %m\n", path);
+
+       reopen_fd = openat(opath_fd, "", O_RDONLY);
+       if (reopen_fd < 0 && errno == ENOENT)
+               ksft_test_result_pass("empty path without O_EMPTYPATH returns ENOENT\n");
+       else if (reopen_fd >= 0) {
+               ksft_test_result_fail("empty path without O_EMPTYPATH unexpectedly succeeded\n");
+               close(reopen_fd);
+       } else {
+               ksft_test_result_fail("empty path without O_EMPTYPATH: expected ENOENT, got %m\n");
+       }
+
+       reopen_fd = openat(opath_fd, "", O_RDONLY | O_EMPTYPATH);
+
+       if (reopen_fd < 0 && errno == EINVAL)
+               ksft_exit_skip("O_EMPTYPATH not supported\n");
+
+       if (reopen_fd >= 0) {
+               ksft_test_result_pass("O_EMPTYPATH reopens O_PATH fd\n");
+               close(reopen_fd);
+       } else {
+               ksft_test_result_fail("O_EMPTYPATH failed: %m\n");
+       }
+
+       unlink(path);
+       ksft_finished();
+}
index 7ca54c718c45b0904d65ca15dd7aaeef0b918581..3f01fb68c5a6d218697fb7b99cc6c41c1a9159fe 100644 (file)
 #include <limits.h>
 #include <linux/types.h>
 #include <linux/unistd.h>
+#include <linux/openat2.h>
 #include "kselftest_harness.h"
 
 #define BUILD_BUG_ON(e) ((void)(sizeof(struct { int:(-!!(e)); })))
 
-/*
- * Arguments for how openat2(2) should open the target path. If @resolve is
- * zero, then openat2(2) operates very similarly to openat(2).
- *
- * However, unlike openat(2), unknown bits in @flags result in -EINVAL rather
- * than being silently ignored. @mode must be zero unless one of {O_CREAT,
- * O_TMPFILE} are set.
- *
- * @flags: O_* flags.
- * @mode: O_CREAT/O_TMPFILE file mode.
- * @resolve: RESOLVE_* flags.
- */
-struct open_how {
-       __u64 flags;
-       __u64 mode;
-       __u64 resolve;
-};
-
 #define OPEN_HOW_SIZE_VER0     24 /* sizeof first published struct */
 #define OPEN_HOW_SIZE_LATEST   OPEN_HOW_SIZE_VER0
 
-#ifndef RESOLVE_IN_ROOT
-/* how->resolve flags for openat2(2). */
-#define RESOLVE_NO_XDEV                0x01 /* Block mount-point crossings
-                                       (includes bind-mounts). */
-#define RESOLVE_NO_MAGICLINKS  0x02 /* Block traversal through procfs-style
-                                       "magic-links". */
-#define RESOLVE_NO_SYMLINKS    0x04 /* Block traversal through all symlinks
-                                       (implies OEXT_NO_MAGICLINKS) */
-#define RESOLVE_BENEATH                0x08 /* Block "lexical" trickery like
-                                       "..", symlinks, and absolute
-                                       paths which escape the dirfd. */
-#define RESOLVE_IN_ROOT                0x10 /* Make all jumps to "/" and ".."
-                                       be scoped inside the dirfd
-                                       (similar to chroot(2)). */
-#endif /* RESOLVE_IN_ROOT */
-
 __maybe_unused
 static bool needs_openat2(const struct open_how *how)
 {