From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.168]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 25BBF372B27 for ; Sat, 4 Jul 2026 16:41:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.168 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783183293; cv=none; b=Z31mw6Bnxrds40OT4PWYMDn/hUXC8OJa/wfI2AGTzril9bgFy+Rz8JLoyD7uo2faoID5wJEXtRfCzMqK3sB7RwcveWf41cKQssj1znAzvY/MHvsAcvjzHBVQALFccJX2kxZPeaXCf81j7UnwPMP+ESbVJikxKwBzlXxy7ppOHRw= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783183293; c=relaxed/simple; bh=7WcYN0QqKL3hZV52Zledf9EXObr6W8P312PPfzKea38=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=W1+iahIKEtULYTUJtBfpUxU1efFrmO9yn+ZWCjFM3MxYIEz1CLYx5y7g8xM9tNxlVz/0WKru1fS0CA/8R7KXd7pvvhm3WcJD1vprSLrAvLGviyiGK/Jb0RR12mNUoONbOAxXXsyqaNjmC64Zu+AeYjeUJuvKrEzq8caja4hVWHo= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=cguoF4tQ; arc=none smtp.client-ip=195.121.94.168 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="cguoF4tQ" X-KPN-MessageId: 3327cb93-77c7-11f1-861c-005056aba152 Received: from smtp.kpnmail.nl (unknown [10.31.155.38]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id 3327cb93-77c7-11f1-861c-005056aba152; Sat, 04 Jul 2026 18:41:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=BYGCMz9KLG4xzelA7NUOrTl5E4m+DuNGsUhuNWvILWI=; b=cguoF4tQFRm9siQJBjQ8ybU9eRZwjAtSLMZ15ns8kAoF4K+m2EDNVsKYbJNUW/CUqnZKc2Z8BskKp iDE5H6ZA9GVsUgjz31qH6SFEUFCeiR1WRRNHuX0UOf9NC8U8fHRXpkh4FutU+7+/Je7GV2hVx0/PLN UsEe8YTMMtyg7UfMKITro5KmfxgCiSyj0Jd0fp442QPaIcHy+vLDym+qGXAIbCbMM9vKdtlgBPlpDa 9MsKfwoXVtRDBxDqo6o1jjYZU9Jei6sU5R4z3tohHdPcHQwu8DMjGlAleSHCZrJB2LWoXl1opND3is 1t21X7RgsOiLeivU1wGmK/hDwKNNDxA== X-KPN-MID: 33|6xrN2a1wQ+R1o42+dSSnwONgkJcDnh1Xtx2AbuQJttQIfsy9Di+LE3z1ZKAtQO2 XgA2aAy4sb/D/iuYjY6vVlH8tUQPycKRSETBz/kWDFxg= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|H0d+/c9q7lfOlh7GZpWTeRdH5Ug05IQsrbK7TwxvkIKQEgSzQZYxNT/OITXI7J8 aVLf7AYdF5QmECwq8djuMSw== Received: from daedalus.home (unknown [178.230.195.42]) by smtp.xs4all.nl (Halon) with ESMTPSA id 32e81b67-77c7-11f1-a998-005056abf0db; Sat, 04 Jul 2026 18:41:26 +0200 (CEST) From: Jori Koolstra To: Jeff Layton , Christian Brauner , Al Viro , Aleksa Sarai , NeilBrown , Amir Goldstein , Jan Kara Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Jori Koolstra Subject: [PATCH v3 14/14] selftest: add tests for open*(O_CREAT|O_DIRECTORY) Date: Sat, 4 Jul 2026 18:41:48 +0200 Message-ID: <20260704164149.3480051-15-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260704164149.3480051-1-jkoolstra@xs4all.nl> References: <20260704164149.3480051-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Add some tests for the new valid O_CREAT|O_DIRECTORY flag combination for open*(2) to test compliance and to showcase its behaviour. Signed-off-by: Jori Koolstra --- .../testing/selftests/filesystems/.gitignore | 1 + tools/testing/selftests/filesystems/Makefile | 2 +- .../filesystems/open_o_creat_o_dir.c | 197 ++++++++++++++++++ 3 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/filesystems/open_o_creat_o_dir.c diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing/selftests/filesystems/.gitignore index a78f894157de..d779a7945cf8 100644 --- a/tools/testing/selftests/filesystems/.gitignore +++ b/tools/testing/selftests/filesystems/.gitignore @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +open_o_creat_o_dir dnotify_test devpts_pts fclog diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/selftests/filesystems/Makefile index a7ec2ba2dd83..b60950f8b15c 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 +TEST_GEN_PROGS := open_o_creat_o_dir devpts_pts file_stressor anon_inode_test kernfs_test fclog TEST_GEN_PROGS += idmapped_tmpfile TEST_GEN_PROGS_EXTENDED := dnotify_test diff --git a/tools/testing/selftests/filesystems/open_o_creat_o_dir.c b/tools/testing/selftests/filesystems/open_o_creat_o_dir.c new file mode 100644 index 000000000000..df3cdbae2c85 --- /dev/null +++ b/tools/testing/selftests/filesystems/open_o_creat_o_dir.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include + +#include "kselftest_harness.h" + +static inline int open_o_creat_o_dir(int dfd, const char *pathname, + mode_t mode, unsigned int flags) +{ + return syscall(__NR_openat, dfd, pathname, + flags | O_DIRECTORY | O_CREAT, mode); +} + +#define open_o_creat_o_dir_checked_flags(dfd, pathname, flags) ({ \ + struct stat __st; \ + int __fd = open_o_creat_o_dir(dfd, pathname, S_IRWXU, flags); \ + ASSERT_GE(__fd, 0); \ + ASSERT_EQ(fstat(__fd, &__st), 0); \ + EXPECT_TRUE(S_ISDIR(__st.st_mode)); \ + __fd; \ +}) + +#define open_o_creat_o_dir_checked(dfd, pathname) \ + open_o_creat_o_dir_checked_flags(dfd, pathname, 0) + +FIXTURE(open_o_creat_o_dir) { + char dirpath[PATH_MAX]; + int dfd; +}; + +FIXTURE_SETUP(open_o_creat_o_dir) +{ + strcpy(self->dirpath, "/tmp/open_o_creat_o_dir_test.XXXXXX"); + ASSERT_NE(mkdtemp(self->dirpath), NULL); + self->dfd = open(self->dirpath, O_DIRECTORY); + ASSERT_GE(self->dfd, 0); +} + +FIXTURE_TEARDOWN(open_o_creat_o_dir) +{ + close(self->dfd); + rmdir(self->dirpath); +} + +/* Does open_o_creat_o_dir return a fd at all? */ +TEST_F(open_o_creat_o_dir, returns_fd) +{ + int fd = open_o_creat_o_dir_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(open_o_creat_o_dir, fd_is_created_dir) +{ + int fd; + struct stat st_via_fd, st_via_path; + char path[PATH_MAX]; + + fd = open_o_creat_o_dir_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(open_o_creat_o_dir, enoent_missing_parent) +{ + EXPECT_EQ(open_o_creat_o_dir(self->dfd, "nonexistent/child", S_IRWXU, 0), -1); + EXPECT_EQ(errno, ENOENT); +} + +/* An invalid dfd must fail with EBADF. */ +TEST_F(open_o_creat_o_dir, ebadf) +{ + EXPECT_EQ(open_o_creat_o_dir(-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(open_o_creat_o_dir, enotdir_dfd) +{ + int file_fd; + + file_fd = openat(self->dfd, "file", + O_CREAT | O_WRONLY, S_IRWXU); + ASSERT_GE(file_fd, 0); + + EXPECT_EQ(open_o_creat_o_dir(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); +} + +/* + * O_EXCL together with O_CREAT|O_DIRECTORY must fail with EEXIST when + * the target directory already exists. + */ +TEST_F(open_o_creat_o_dir, o_excl_eexist) +{ + int fd; + + fd = open_o_creat_o_dir_checked_flags(self->dfd, "excldir", O_EXCL); + EXPECT_EQ(close(fd), 0); + + EXPECT_EQ(open_o_creat_o_dir(self->dfd, "excldir", S_IRWXU, O_EXCL), -1); + EXPECT_EQ(errno, EEXIST); + + EXPECT_EQ(unlinkat(self->dfd, "excldir", AT_REMOVEDIR), 0); +} + +/* + * O_CREAT|O_DIRECTORY on a path that already exists as a regular file + * must fail with ENOTDIR. + */ +TEST_F(open_o_creat_o_dir, existing_file_enotdir) +{ + int file_fd; + + file_fd = openat(self->dfd, "regfile", + O_CREAT | O_WRONLY, S_IRWXU); + ASSERT_GE(file_fd, 0); + EXPECT_EQ(close(file_fd), 0); + + EXPECT_EQ(open_o_creat_o_dir(self->dfd, "regfile", S_IRWXU, 0), -1); + EXPECT_EQ(errno, ENOTDIR); + + EXPECT_EQ(unlinkat(self->dfd, "regfile", 0), 0); +} + +/* + * O_CREAT|O_DIRECTORY combined with a writable access mode must be + * rejected: a directory cannot be opened for writing. + */ +TEST_F(open_o_creat_o_dir, rejects_writable_acc_mode) +{ + EXPECT_EQ(open_o_creat_o_dir(self->dfd, "rdwrdir", S_IRWXU, O_RDWR), -1); + EXPECT_EQ(errno, ENOTDIR); + /* Clean up if the kernel created the directory anyway. */ + unlinkat(self->dfd, "rdwrdir", AT_REMOVEDIR); +} + +/* + * openat(O_CREAT) with a trailing slash but without O_DIRECTORY + * must fail with EISDIR and must not create anything at the path. + */ +TEST_F(open_o_creat_o_dir, trailing_slash_no_o_dir) +{ + int fd; + struct stat st; + + fd = openat(self->dfd, "trailing/", O_CREAT | O_WRONLY, S_IRWXU); + EXPECT_EQ(fd, -1); + EXPECT_EQ(errno, EISDIR); + + EXPECT_EQ(fstatat(self->dfd, "trailing", &st, 0), -1); + EXPECT_EQ(errno, ENOENT); + + /* Best-effort cleanup in case the kernel left a file behind. */ + if (fd >= 0) + close(fd); + unlinkat(self->dfd, "trailing", 0); +} + +/* + * The returned fd must be usable as a dfd for further *at() calls. + */ +TEST_F(open_o_creat_o_dir, fd_usable_as_dfd) +{ + int parent_fd, child_fd; + char path[PATH_MAX]; + + parent_fd = open_o_creat_o_dir_checked(self->dfd, "parent"); + child_fd = open_o_creat_o_dir_checked(parent_fd, "child"); + + EXPECT_EQ(close(child_fd), 0); + EXPECT_EQ(close(parent_fd), 0); + + 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); +} + +TEST_HARNESS_MAIN -- 2.55.0