Add some tests for the new mkdirat2() syscall to test compliance and to showcase its behaviour. Signed-off-by: Jori Koolstra --- tools/include/uapi/asm-generic/unistd.h | 5 +- .../testing/selftests/filesystems/.gitignore | 1 + tools/testing/selftests/filesystems/Makefile | 4 +- .../selftests/filesystems/mkdirat_fd_test.c | 143 ++++++++++++++++++ 4 files changed, 150 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..6efc21779b62 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_mkdirat2 472 +__SYSCALL(__NR_mkdirat2, sys_mkdirat2) + #undef __NR_syscalls -#define __NR_syscalls 472 +#define __NR_syscalls 473 /* * 32 bit systems traditionally used different diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing/selftests/filesystems/.gitignore index 64ac0dfa46b7..84e2175d171f 100644 --- a/tools/testing/selftests/filesystems/.gitignore +++ b/tools/testing/selftests/filesystems/.gitignore @@ -5,3 +5,4 @@ fclog file_stressor anon_inode_test kernfs_test +mkdirat_fd_test 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..a02c0223d63b --- /dev/null +++ b/tools/testing/selftests/filesystems/mkdirat_fd_test.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include + +#include + +#include "kselftest_harness.h" + +#ifndef VALID_MKDIRAT2_FLAGS +#define VALID_MKDIRAT2_FLAGS (AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT) +#endif + +#define mkdirat2_checked_flags(dfd, pathname, flags) ({ \ + struct stat __st; \ + int __fd = sys_mkdirat2(dfd, pathname, S_IRWXU, flags); \ + ASSERT_GE(__fd, 0); \ + EXPECT_EQ(fstat(__fd, &__st), 0); \ + EXPECT_TRUE(S_ISDIR(__st.st_mode)); \ + __fd; \ +}) + +#define mkdirat2_checked(dfd, pathname) \ + mkdirat2_checked_flags(dfd, pathname, 0) + + +static inline int sys_mkdirat2(int dfd, const char *pathname, mode_t mode, + unsigned int flags) +{ + return syscall(__NR_mkdirat2, dfd, pathname, mode, flags); +} + +FIXTURE(mkdirat2) { + char dirpath[PATH_MAX]; + int dfd; +}; + +FIXTURE_SETUP(mkdirat2) +{ + snprintf(self->dirpath, sizeof(self->dirpath), + "/tmp/mkdirat2_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(mkdirat2) +{ + close(self->dfd); + rmdir(self->dirpath); +} + +/* Does mkdirat2 return a fd at all */ +TEST_F(mkdirat2, returns_fd) +{ + int fd = mkdirat2_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(mkdirat2, fd_is_created_dir) +{ + int fd; + struct stat st_via_fd, st_via_path; + char path[PATH_MAX]; + + fd = mkdirat2_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(mkdirat2, enoent_missing_parent) +{ + EXPECT_EQ(sys_mkdirat2(self->dfd, "nonexistent/child", S_IRWXU, 0), -1); + EXPECT_EQ(errno, ENOENT); +} + +/* An invalid dfd must fail with EBADF. */ +TEST_F(mkdirat2, ebadf) +{ + EXPECT_EQ(sys_mkdirat2(-42, "badfdir", S_IRWXU, 0), -1); + EXPECT_EQ(errno, EBADF); +} + +/* A dfd that points to a file (not a directory) must fail with ENOTDIR. */ +TEST_F(mkdirat2, 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_mkdirat2(file_fd, "subdir", S_IRWXU, 0), -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(mkdirat2, fd_usable_as_dfd) +{ + int parent_fd, child_fd; + + parent_fd = mkdirat2_checked(self->dfd, "parent"); + child_fd = mkdirat2_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(mkdirat2, einval_unknown_flags) +{ + EXPECT_EQ(sys_mkdirat2(self->dfd, "flagsdir", S_IRWXU, ~VALID_MKDIRAT2_FLAGS ), -1); + EXPECT_EQ(errno, EINVAL); +} + +TEST_HARNESS_MAIN -- 2.53.0