From nobody Wed Apr 1 08:15:17 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.183]) (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 EA561345CA5 for ; Tue, 31 Mar 2026 17:21:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.183 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774977689; cv=none; b=Wdh2AS97MxdoNkodA9QNlydPvxCrzBp4Lk3yZsljGH3pN6UcwW5/DkrFiJmfgWQ9fWzrGHpPxhLL2rnzo+p7JYuDXBdQHd4F/p8Im5fXJrp1gkk+fbKOJ7qzHTYlRfvxZVRVHc6DqVFKLT6/eaE4EqyeyRs131qX+iVeG+s/vt8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774977689; c=relaxed/simple; bh=yWQgdgRhZbv2l7+Ro7qNBtP2xSE0vb5n5/JP2mfl/+I=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=K6mh1ZuR8QiqKonOirXfJx6P14UXVxrOgimfr1jxwLKBRLrLw86JYc9nKs7rrr+MVv4QCB6o4RoHkgF1qtCC4EaNqLvHWCCDNatEWSQjdqQjqZb+eJpVWh1kXsFcuYUMEhj0zcPUicNJ8oBYHR80u42ZkDNzOEWFl3cNTz4mnV4= 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=oylyme5g; arc=none smtp.client-ip=195.121.94.183 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="oylyme5g" X-KPN-MessageId: e41bc105-2d25-11f1-bea5-005056992ed3 Received: from smtp.kpnmail.nl (unknown [10.31.155.7]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id e41bc105-2d25-11f1-bea5-005056992ed3; Tue, 31 Mar 2026 19:20:18 +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=R8axV3zrGCR8LJlu0OosxCPVJsIb2s3wxLdTHN9HNfs=; b=oylyme5gUdOycP1i4/B3srC+vbVwh/GgRfJ5is0ImO70jvMbB9EmnAHUalR5tDlHBuVGaTRwBoGYF IfMjtyhvIiSDU/6305uGLevty4qBBnCyPup6MEN28pGKFSoWgYDkqWr9eZBceWN5O3UXsxyKYi+B3Q IM0yCpJk36Yp3AeS7ePQnMpJy9EQv7U4LTQgKqau4feqLgP/ILmi/l33fZ/TEgXPaUBajy42mPw5Fk YINjhjnosgm5yvM4pItsURKfeiwaIgZebvYtI2wqx6paXPkePXhzt3kHJm4FaTW4ZcUGPT1FDE5amS +yY66969218gVp+2eruq7fjvfV40NKg== X-KPN-MID: 33|E+oMIFqNqCiE4l2kkzmIREhn0jNvfbd5HtdsBoKJlJtYl6UMqHxfksVtxQu3h4a S1l/vKYh5kODQ0fVPu6NrAqTCqTdBWcGeVQFguoybViY= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|oK00dgZQze3kacwmaEU7YrVnVXYLTTTiqkBii5XgtPLNjONTvUlor+wDZ5NSIjp HloufzgZAlY5Cchja9QZN0g== Received: from daedalus.home (unknown [178.227.25.158]) by smtp.xs4all.nl (Halon) with ESMTPSA id e3a32562-2d25-11f1-86d5-005056998788; Tue, 31 Mar 2026 19:20:18 +0200 (CEST) From: Jori Koolstra To: Andy Lutomirski , Thomas Gleixner , Ingo Molnar , Borislav Petkov , Dave Hansen , x86@kernel.org, Alexander Viro , Christian Brauner , Jeff Layton , Chuck Lever , Arnd Bergmann , Shuah Khan , Greg Kroah-Hartman , "H. Peter Anvin" , Jan Kara , Alexander Aring Cc: Peter Zijlstra , Oleg Nesterov , Andrey Albershteyn , Jiri Olsa , Mathieu Desnoyers , =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= , Namhyung Kim , Arnaldo Carvalho de Melo , Aleksa Sarai , linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-api@vger.kernel.org, linux-arch@vger.kernel.org, linux-kselftest@vger.kernel.org, cmirabil@redhat.com, Jori Koolstra , "Masami Hiramatsu (Google)" Subject: [RFC PATCH 1/2] vfs: syscalls: add mkdirat_fd() Date: Tue, 31 Mar 2026 19:19:58 +0200 Message-ID: <20260331172011.3512876-2-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260331172011.3512876-1-jkoolstra@xs4all.nl> References: <20260331172011.3512876-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: quoted-printable Content-Type: text/plain; charset="utf-8" Currently there is no way to race-freely create and open a directory. For regular files we have open(O_CREAT) for creating a new file inode, and returning a pinning fd to it. The lack of such functionality for directories means that when populating a directory tree there's always a race involved: the inodes first need to be created, and then opened to adjust their permissions/ownership/labels/timestamps/acls/xattrs/..., but in the time window between the creation and the opening they might be replaced by something else. Addressing this race without proper APIs is possible (by immediately fstat()ing what was opened, to verify that it has the right inode type), but difficult to get right. Hence, mkdirat_fd() that creates a directory and returns an O_DIRECTORY fd is useful. This feature idea (and description) is taken from the UAPI group: https://github.com/uapi-group/kernel-features?tab=3Dreadme-ov-file#race-fre= e-creation-and-opening-of-non-file-inodes Signed-off-by: Jori Koolstra --- arch/x86/entry/syscalls/syscall_64.tbl | 1 + fs/internal.h | 1 + fs/namei.c | 26 ++++++++++++++++++++++++-- include/linux/fcntl.h | 2 ++ include/linux/syscalls.h | 2 ++ include/uapi/asm-generic/fcntl.h | 3 +++ include/uapi/asm-generic/unistd.h | 5 ++++- scripts/syscall.tbl | 1 + 8 files changed, 38 insertions(+), 3 deletions(-) diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscal= ls/syscall_64.tbl index 524155d655da..dda920c26941 100644 --- a/arch/x86/entry/syscalls/syscall_64.tbl +++ b/arch/x86/entry/syscalls/syscall_64.tbl @@ -396,6 +396,7 @@ 469 common file_setattr sys_file_setattr 470 common listns sys_listns 471 common rseq_slice_yield sys_rseq_slice_yield +472 common mkdirat_fd sys_mkdirat_fd =20 # # Due to a historical design error, certain syscalls are numbered differen= tly diff --git a/fs/internal.h b/fs/internal.h index cbc384a1aa09..2885a3e4ebdd 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -58,6 +58,7 @@ int filename_unlinkat(int dfd, struct filename *name); int may_linkat(struct mnt_idmap *idmap, const struct path *link); int filename_renameat2(int olddfd, struct filename *oldname, int newdfd, struct filename *newname, unsigned int flags); +int filename_mkdirat_fd(int dfd, struct filename *name, umode_t mode, unsi= gned int flags); int filename_mkdirat(int dfd, struct filename *name, umode_t mode); int filename_mknodat(int dfd, struct filename *name, umode_t mode, unsigne= d int dev); int filename_symlinkat(struct filename *from, int newdfd, struct filename = *to); diff --git a/fs/namei.c b/fs/namei.c index 1eb9db055292..93252937983e 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -5256,6 +5256,11 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, st= ruct inode *dir, EXPORT_SYMBOL(vfs_mkdir); =20 int filename_mkdirat(int dfd, struct filename *name, umode_t mode) +{ + return filename_mkdirat_fd(dfd, name, mode, 0); +} + +int filename_mkdirat_fd(int dfd, struct filename *name, umode_t mode, unsi= gned int flags) { struct dentry *dentry; struct path path; @@ -5263,7 +5268,7 @@ int filename_mkdirat(int dfd, struct filename *name, = umode_t mode) unsigned int lookup_flags =3D LOOKUP_DIRECTORY; struct delegated_inode delegated_inode =3D { }; =20 -retry: +start: dentry =3D filename_create(dfd, name, &path, lookup_flags); if (IS_ERR(dentry)) return PTR_ERR(dentry); @@ -5276,7 +5281,6 @@ int filename_mkdirat(int dfd, struct filename *name, = umode_t mode) if (IS_ERR(dentry)) error =3D PTR_ERR(dentry); } - end_creating_path(&path, dentry); if (is_delegated(&delegated_inode)) { error =3D break_deleg_wait(&delegated_inode); if (!error) @@ -5286,7 +5290,25 @@ int filename_mkdirat(int dfd, struct filename *name,= umode_t mode) lookup_flags |=3D LOOKUP_REVAL; goto retry; } + + if (!error && (flags & MKDIRAT_FD_NEED_FD)) { + struct path new_path =3D { .mnt =3D path.mnt, .dentry =3D dentry }; + error =3D FD_ADD(0, dentry_open(&new_path, O_DIRECTORY, current_cred())); + } + end_creating_path(&path, dentry); return error; +retry: + end_creating_path(&path, dentry); + goto start; +} + +SYSCALL_DEFINE4(mkdirat_fd, int, dfd, const char __user *, pathname, umode= _t, mode, + unsigned int, flags) +{ + CLASS(filename, name)(pathname); + if (flags & ~VALID_MKDIRAT_FD_FLAGS) + return -EINVAL; + return filename_mkdirat_fd(dfd, name, mode, flags | MKDIRAT_FD_NEED_FD); } =20 SYSCALL_DEFINE3(mkdirat, int, dfd, const char __user *, pathname, umode_t,= mode) diff --git a/include/linux/fcntl.h b/include/linux/fcntl.h index a332e79b3207..d2f0fdb82847 100644 --- a/include/linux/fcntl.h +++ b/include/linux/fcntl.h @@ -25,6 +25,8 @@ #define force_o_largefile() (!IS_ENABLED(CONFIG_ARCH_32BIT_OFF_T)) #endif =20 +#define VALID_MKDIRAT_FD_FLAGS (MKDIRAT_FD_NEED_FD) + #if BITS_PER_LONG =3D=3D 32 #define IS_GETLK32(cmd) ((cmd) =3D=3D F_GETLK) #define IS_SETLK32(cmd) ((cmd) =3D=3D F_SETLK) diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h index 02bd6ddb6278..52e7f09d5525 100644 --- a/include/linux/syscalls.h +++ b/include/linux/syscalls.h @@ -999,6 +999,8 @@ asmlinkage long sys_lsm_get_self_attr(unsigned int attr= , struct lsm_ctx __user * asmlinkage long sys_lsm_set_self_attr(unsigned int attr, struct lsm_ctx __= user *ctx, u32 size, u32 flags); asmlinkage long sys_lsm_list_modules(u64 __user *ids, u32 __user *size, u3= 2 flags); +asmlinkage long sys_mkdirat_fd(int dfd, const char __user *pathname, umode= _t mode, + unsigned int flags) =20 /* * Architecture-specific system calls diff --git a/include/uapi/asm-generic/fcntl.h b/include/uapi/asm-generic/fc= ntl.h index 613475285643..621458bf1fbf 100644 --- a/include/uapi/asm-generic/fcntl.h +++ b/include/uapi/asm-generic/fcntl.h @@ -95,6 +95,9 @@ #define O_NDELAY O_NONBLOCK #endif =20 +/* Flags for mkdirat_fd */ +#define MKDIRAT_FD_NEED_FD 0x01 + #define F_DUPFD 0 /* dup */ #define F_GETFD 1 /* get close_on_exec */ #define F_SETFD 2 /* set/clear close_on_exec */ diff --git a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/u= nistd.h index a627acc8fb5f..5bae1029f5d9 100644 --- a/include/uapi/asm-generic/unistd.h +++ b/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) =20 +#define __NR_mkdirat_fd 472 +__SYSCALL(__NR_mkdirat_fd, sys_mkdirat_fd) + #undef __NR_syscalls -#define __NR_syscalls 472 +#define __NR_syscalls 473 =20 /* * 32 bit systems traditionally used different diff --git a/scripts/syscall.tbl b/scripts/syscall.tbl index 7a42b32b6577..db3bd97d4a1a 100644 --- a/scripts/syscall.tbl +++ b/scripts/syscall.tbl @@ -412,3 +412,4 @@ 469 common file_setattr sys_file_setattr 470 common listns sys_listns 471 common rseq_slice_yield sys_rseq_slice_yield +472 common mkdirat_fd sys_mkdirat_fd --=20 2.53.0 From nobody Wed Apr 1 08:15:17 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.186]) (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 50793345740 for ; Tue, 31 Mar 2026 17:20:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.186 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774977639; cv=none; b=dlgUm4GF7AKy7e7vGe49JVGzevJ41XCIZB2lYtFOX+f3HlCAMbVr7gvxlnFSx8hsXd9uHzbHMNZJ8Uy8wX36nCrD2nf1ouYQqratWEN9vICZjiYh01LZeBm0gEnIzDzR4iFTCE2of0eHSxN9qREJobrD9CdfB3SAVhgNoKDUyHY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774977639; c=relaxed/simple; bh=LXGIe2EbKEgpeH0Y3XR7VUj1ALQtEFbVCyxTICzraSw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=XfNonmuaRAVI4DJuMnkz/WUtwU1osIXZlYV7ZQRuAVtwQHDZVZ6qJ7aAst8ZoXqCKvImeEi4Qz5H5dOmQ91TSupi20IiRaxE59ph2Fnl5BTGL0YAAA+CElDIVXu+VY5+AOixk+AjgDEwKOpsDQFTtS0HKPwAxmCbyuWe+2G9LgA= 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=Z9/cUuft; arc=none smtp.client-ip=195.121.94.186 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="Z9/cUuft" X-KPN-MessageId: eb083a41-2d25-11f1-89dd-00505699b430 Received: from smtp.kpnmail.nl (unknown [10.31.155.7]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id eb083a41-2d25-11f1-89dd-00505699b430; Tue, 31 Mar 2026 19:20:30 +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=zDP/iu2bYslX1MKM5Ro/UsGWemjCD4by8j49Ok9YqUI=; b=Z9/cUuftDVDCQkdTQLqdCiSeMZMVXZPTADgtNig+MEIP2mPBTjDplgPtxG+ceEmet8u+RdoEg3nbs UMvRPZ5ucT8tZAiss8WadzlDev5e76yOfcqTCXFrCMZZLaNtBjSBz8qOqwUnSe0Zgr688HgBvwdQ5K SlWcxFZ1rVEvkLF/k5ZbYYH/RodIHl+ApLBm0WvYxl9gjtz+2BmIlMc2kyFrpREQNG7J998rFtcXzK iBn9wrjyJdxQKxnntNmUo/XBsZzBnlHwVx9PvpTiq3XK0wNHGeaMy1RLKBSoX/xP/fZOXEbGN4pO22 xM2xlX+JALZkWtZiDwqsWpSOz2CZIoQ== X-KPN-MID: 33|sikx9ZcHd/fInEMSfVbSNHhMDyIs4oGsjHeL/W8Z7mI2Bm3I565SPcupuicIDP3 rdLtexMWR+4Zl7NdQamsboA== X-KPN-VerifiedSender: Yes X-CMASSUN: 33|nr5woH1BD8GZbCzHqoBN7y6lJ5aJDQ+LMqUCyp20FGKwuVwbmxrUdUBNnk670m1 YseKZw3G8i96sRps4/UBWfA== Received: from daedalus.home (unknown [178.227.25.158]) by smtp.xs4all.nl (Halon) with ESMTPSA id ea964dfe-2d25-11f1-86d5-005056998788; Tue, 31 Mar 2026 19:20:30 +0200 (CEST) From: Jori Koolstra To: Andy Lutomirski , Thomas Gleixner , Ingo Molnar , Borislav Petkov , Dave Hansen , x86@kernel.org, Alexander Viro , Christian Brauner , Jeff Layton , Chuck Lever , Arnd Bergmann , Shuah Khan , Greg Kroah-Hartman Cc: "H . Peter Anvin" , Jan Kara , Alexander Aring , Peter Zijlstra , Oleg Nesterov , Andrey Albershteyn , Jiri Olsa , Mathieu Desnoyers , =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= , Namhyung Kim , Arnaldo Carvalho de Melo , Aleksa Sarai , linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-api@vger.kernel.org, linux-arch@vger.kernel.org, linux-kselftest@vger.kernel.org, cmirabil@redhat.com, Jori Koolstra , Ingo Molnar Subject: [RFC PATCH 2/2] selftest: add tests for mkdirat_fd() Date: Tue, 31 Mar 2026 19:19:59 +0200 Message-ID: <20260331172011.3512876-3-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260331172011.3512876-1-jkoolstra@xs4all.nl> References: <20260331172011.3512876-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: quoted-printable Content-Type: text/plain; charset="utf-8" Add some tests for the new mkdirat_fd() syscall to test compliance and to showcase its behaviour. Signed-off-by: Jori Koolstra --- 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/a= sm-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) =20 +#define __NR_mkdirat_fd 472 +__SYSCALL(__NR_mkdirat_fd, sys_mkdirat_fd) + #undef __NR_syscalls -#define __NR_syscalls 472 +#define __NR_syscalls 473 =20 /* * 32 bit systems traditionally used different diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/s= elftests/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 =20 -CFLAGS +=3D $(KHDR_INCLUDES) -TEST_GEN_PROGS :=3D devpts_pts file_stressor anon_inode_test kernfs_test f= clog +CFLAGS +=3D $(KHDR_INCLUDES) $(TOOLS_INCLUDES) +TEST_GEN_PROGS :=3D devpts_pts file_stressor anon_inode_test kernfs_test f= clog mkdirat_fd_test TEST_GEN_PROGS_EXTENDED :=3D dnotify_test =20 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 +#include +#include +#include + +#include + +#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 =3D 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 mod= e, + 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 =3D 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 =3D 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 =3D 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 =3D 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 =3D mkdirat_fd_checked(self->dfd, "parent"); + child_fd =3D 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 --=20 2.53.0