Add some tests for the new mkdirat_fd() syscall to test compliance and
to showcase its behaviour.

Signed-off-by: Jori Koolstra <[email protected]>
---
 tools/include/uapi/asm-generic/unistd.h       |   5 +-
 tools/testing/selftests/filesystems/Makefile  |   4 +-
 .../selftests/filesystems/mkdirat_fd_test.c   | 139 ++++++++++++++++++
 3 files changed, 145 insertions(+), 3 deletions(-)
 create mode 100644 tools/testing/selftests/filesystems/mkdirat_fd_test.c

diff --git a/tools/include/uapi/asm-generic/unistd.h 
b/tools/include/uapi/asm-generic/unistd.h
index a627acc8fb5f..5bae1029f5d9 100644
--- a/tools/include/uapi/asm-generic/unistd.h
+++ b/tools/include/uapi/asm-generic/unistd.h
@@ -863,8 +863,11 @@ __SYSCALL(__NR_listns, sys_listns)
 #define __NR_rseq_slice_yield 471
 __SYSCALL(__NR_rseq_slice_yield, sys_rseq_slice_yield)
 
+#define __NR_mkdirat_fd 472
+__SYSCALL(__NR_mkdirat_fd, sys_mkdirat_fd)
+
 #undef __NR_syscalls
-#define __NR_syscalls 472
+#define __NR_syscalls 473
 
 /*
  * 32 bit systems traditionally used different
diff --git a/tools/testing/selftests/filesystems/Makefile 
b/tools/testing/selftests/filesystems/Makefile
index 85427d7f19b9..7357769db57a 100644
--- a/tools/testing/selftests/filesystems/Makefile
+++ b/tools/testing/selftests/filesystems/Makefile
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
-CFLAGS += $(KHDR_INCLUDES)
-TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog
+CFLAGS += $(KHDR_INCLUDES) $(TOOLS_INCLUDES)
+TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog 
mkdirat_fd_test
 TEST_GEN_PROGS_EXTENDED := dnotify_test
 
 include ../lib.mk
diff --git a/tools/testing/selftests/filesystems/mkdirat_fd_test.c 
b/tools/testing/selftests/filesystems/mkdirat_fd_test.c
new file mode 100644
index 000000000000..9058be49dc7b
--- /dev/null
+++ b/tools/testing/selftests/filesystems/mkdirat_fd_test.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/stat.h>
+
+#include <asm-generic/unistd.h>
+
+#include "kselftest_harness.h"
+
+#ifndef MKDIRAT_FD_NEED_FD
+#define MKDIRAT_FD_NEED_FD 0x01
+#endif
+
+#define mkdirat_fd_checked(dfd, pathname) ({                                   
\
+       struct stat __st;                                                       
\
+       int __fd = sys_mkdirat_fd(dfd, pathname, S_IRWXU, MKDIRAT_FD_NEED_FD);  
\
+       ASSERT_GE(__fd, 0);                                                     
\
+       EXPECT_EQ(fstat(__fd, &__st), 0);                                       
\
+       EXPECT_TRUE(S_ISDIR(__st.st_mode));                                     
\
+       __fd;                                                                   
\
+})
+
+static inline int sys_mkdirat_fd(int dfd, const char *pathname, mode_t mode,
+                                unsigned int flags)
+{
+       return syscall(__NR_mkdirat_fd, dfd, pathname, mode, flags);
+}
+
+FIXTURE(mkdirat_fd) {
+       char dirpath[PATH_MAX];
+       int dfd;
+};
+
+FIXTURE_SETUP(mkdirat_fd)
+{
+       snprintf(self->dirpath, sizeof(self->dirpath),
+                "/tmp/mkdirat_fd_test.%d", getpid());
+       ASSERT_EQ(mkdir(self->dirpath, S_IRWXU), 0);
+
+       self->dfd = open(self->dirpath, O_DIRECTORY);
+       ASSERT_GE(self->dfd, 0);
+}
+
+FIXTURE_TEARDOWN(mkdirat_fd)
+{
+       close(self->dfd);
+       rmdir(self->dirpath);
+}
+
+/* Does mkdirat_fd return a fd at all */
+TEST_F(mkdirat_fd, returns_fd)
+{
+       int fd = mkdirat_fd_checked(self->dfd, "newdir");
+       EXPECT_EQ(close(fd), 0)
+       EXPECT_EQ(unlinkat(self->dfd, "newdir", AT_REMOVEDIR), 0);
+}
+
+/* The fd must refer to the directory that was just created. */
+TEST_F(mkdirat_fd, fd_is_created_dir)
+{
+       int fd;
+       struct stat st_via_fd, st_via_path;
+       char path[PATH_MAX];
+
+       fd = mkdirat_fd_checked(self->dfd, "checkdir");
+
+       ASSERT_EQ(fstat(fd, &st_via_fd), 0);
+
+       snprintf(path, sizeof(path), "%s/checkdir", self->dirpath);
+       ASSERT_EQ(stat(path, &st_via_path), 0);
+
+       EXPECT_EQ(st_via_fd.st_ino, st_via_path.st_ino);
+       EXPECT_EQ(st_via_fd.st_dev, st_via_path.st_dev);
+
+       EXPECT_EQ(close(fd), 0)
+       EXPECT_EQ(rmdir(path), 0);
+}
+
+
+/* Missing parent component must fail with ENOENT. */
+TEST_F(mkdirat_fd, enoent_missing_parent)
+{
+       EXPECT_EQ(sys_mkdirat_fd(self->dfd, "nonexistent/child", S_IRWXU, 
MKDIRAT_FD_NEED_FD), -1);
+       EXPECT_EQ(errno, ENOENT);
+}
+
+/* An invalid dfd must fail with EBADF. */
+TEST_F(mkdirat_fd, ebadf)
+{
+       EXPECT_EQ(sys_mkdirat_fd(-42, "badfdir", S_IRWXU, MKDIRAT_FD_NEED_FD), 
-1);
+       EXPECT_EQ(errno, EBADF);
+}
+
+/* A dfd that points to a file (not a directory) must fail with ENOTDIR. */
+TEST_F(mkdirat_fd, enotdir_dfd)
+{
+       int file_fd;
+
+       file_fd = openat(self->dfd, "file",
+                        O_CREAT | O_WRONLY, S_IRWXU);
+       ASSERT_GE(file_fd, 0);
+
+       EXPECT_EQ(sys_mkdirat_fd(file_fd, "subdir", S_IRWXU, 
MKDIRAT_FD_NEED_FD), -1);
+       EXPECT_EQ(errno, ENOTDIR);
+
+       EXPECT_EQ(close(file_fd), 0);
+       EXPECT_EQ(unlinkat(self->dfd, "file", 0), 0);
+}
+
+/*
+ * The returned fd must be usable as a dfd for further *at() calls.
+ */
+TEST_F(mkdirat_fd, fd_usable_as_dfd)
+{
+       int parent_fd, child_fd;
+
+       parent_fd = mkdirat_fd_checked(self->dfd, "parent");
+       child_fd = mkdirat_fd_checked(parent_fd, "child");
+
+       EXPECT_EQ(close(child_fd), 0);
+       EXPECT_EQ(close(parent_fd), 0);
+
+       char path[PATH_MAX];
+       snprintf(path, sizeof(path), "%s/parent/child", self->dirpath);
+       EXPECT_EQ(rmdir(path), 0);
+       snprintf(path, sizeof(path), "%s/parent", self->dirpath);
+       EXPECT_EQ(rmdir(path), 0);
+}
+
+/* Unknown flags must be rejected with EINVAL. */
+TEST_F(mkdirat_fd, einval_unknown_flags)
+{
+       EXPECT_EQ(sys_mkdirat_fd(self->dfd, "flagsdir", S_IRWXU, 
~MKDIRAT_FD_NEED_FD), -1);
+       EXPECT_EQ(errno, EINVAL);
+}
+
+TEST_HARNESS_MAIN
-- 
2.53.0


Reply via email to