git: 8e6e77ec25bc - main - renameat(2): add AT_RENAME_EXCHANGE flag

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Sat, 11 Jul 2026 22:05:22 UTC
The branch main has been updated by kib:

URL: https://cgit.FreeBSD.org/src/commit/?id=8e6e77ec25bc5809500d29bf318a843b40205657

commit 8e6e77ec25bc5809500d29bf318a843b40205657
Author:     Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2026-06-18 18:12:01 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2026-07-11 22:04:43 +0000

    renameat(2): add AT_RENAME_EXCHANGE flag
    
    Currently no filesystems support it.
    
    Reviewed by:    mckusick
    Discussed with: markj
    Tested by:      pho
    Sponsored by:   The FreeBSD Foundation
    MFC after:      1 week
    Differential revision:  https://reviews.freebsd.org/D57658
---
 sys/kern/vfs_syscalls.c | 70 ++++++++++++++++++++++++++++++++++++++++---------
 sys/sys/fcntl.h         |  2 ++
 2 files changed, 59 insertions(+), 13 deletions(-)

diff --git a/sys/kern/vfs_syscalls.c b/sys/kern/vfs_syscalls.c
index 59d0c9c40927..29d503bbb3fa 100644
--- a/sys/kern/vfs_syscalls.c
+++ b/sys/kern/vfs_syscalls.c
@@ -3752,12 +3752,13 @@ sys_renameat2(struct thread *td, struct renameat2_args *uap)
 #ifdef MAC
 static int
 kern_renameat_mac(struct thread *td, int oldfd, const char *old, int newfd,
-    const char *new, enum uio_seg pathseg, struct nameidata *fromnd)
+    const char *new, enum uio_seg pathseg, struct nameidata *fromnd, int op,
+    int ndflags)
 {
 	int error;
 
-	NDINIT_ATRIGHTS(fromnd, DELETE, LOCKPARENT | LOCKLEAF | AUDITVNODE1,
-	    pathseg, old, oldfd, &cap_renameat_source_rights);
+	NDINIT_ATRIGHTS(fromnd, op, LOCKPARENT | LOCKLEAF | AUDITVNODE1 |
+	    ndflags, pathseg, old, oldfd, &cap_renameat_source_rights);
 	if ((error = namei(fromnd)) != 0)
 		return (error);
 	error = mac_vnode_check_rename_from(td->td_ucred, fromnd->ni_dvp,
@@ -3782,23 +3783,35 @@ kern_renameat(struct thread *td, int oldfd, const char *old, int newfd,
 	struct vnode *tvp, *fvp, *tdvp;
 	struct nameidata fromnd, tond;
 	uint64_t tondflags;
-	int error;
+	int error, fndflags, op;
 	short irflag;
+	bool exchange;
 
-	if ((flags & ~(AT_RENAME_NOREPLACE)) != 0)
+	if ((flags & ~(AT_RENAME_NOREPLACE | AT_RENAME_EXCHANGE)) != 0)
+		return (EINVAL);
+	if ((flags & (AT_RENAME_NOREPLACE | AT_RENAME_EXCHANGE)) ==
+	    (AT_RENAME_NOREPLACE | AT_RENAME_EXCHANGE))
 		return (EINVAL);
+	if ((flags & AT_RENAME_EXCHANGE) != 0) {
+		op = RENAME;
+		exchange = true;
+	} else {
+		op = DELETE;
+		exchange = false;
+	}
+	fndflags = 0;
 again:
 	tmp = mp = NULL;
 	bwillwrite();
 #ifdef MAC
 	if (mac_vnode_check_rename_from_enabled()) {
 		error = kern_renameat_mac(td, oldfd, old, newfd, new, pathseg,
-		    &fromnd);
+		    &fromnd, op, fndflags);
 		if (error != 0)
 			return (error);
 	} else {
 #endif
-	NDINIT_ATRIGHTS(&fromnd, DELETE, WANTPARENT | AUDITVNODE1,
+	NDINIT_ATRIGHTS(&fromnd, op, WANTPARENT | AUDITVNODE1 | fndflags,
 	    pathseg, old, oldfd, &cap_renameat_source_rights);
 	if ((error = namei(&fromnd)) != 0)
 		return (error);
@@ -3806,6 +3819,11 @@ again:
 	}
 #endif
 	fvp = fromnd.ni_vp;
+	if (exchange && fvp == NULL) {
+		NDFREE_PNBUF(&fromnd);
+		vrele(fromnd.ni_dvp);
+		return (ENOENT);
+	}
 	tondflags = LOCKPARENT | LOCKLEAF | NOCACHE | AUDITVNODE2;
 	if (fromnd.ni_vp->v_type == VDIR)
 		tondflags |= WILLBEDIR;
@@ -3844,6 +3862,29 @@ again:
 		error = EEXIST;
 		goto out;
 	}
+	if (exchange) {
+		if (tvp == NULL) {
+			error = ENOENT;
+			goto out;
+		}
+		if (tvp->v_type == VDIR && fndflags == 0) {
+			fndflags = WILLBEDIR;
+again2:
+			NDFREE_PNBUF(&fromnd);
+			vrele(fromnd.ni_dvp);
+			vrele(fvp);
+			NDFREE_PNBUF(&tond);
+			VOP_VPUT_PAIR(tdvp, &tvp, true);
+			error = sig_intr();
+			if (error != 0)
+				goto out1;
+			goto again;
+		}
+		if (tvp->v_type != VDIR && fndflags != 0) {
+			fndflags = 0;
+			goto again2;
+		}
+	}
 	error = vn_start_write(fvp, &mp, V_NOWAIT);
 	if (error != 0) {
 again1:
@@ -3886,12 +3927,15 @@ again1:
 		goto out;
 	}
 	if (tvp != NULL) {
-		if (fvp->v_type == VDIR && tvp->v_type != VDIR) {
-			error = ENOTDIR;
-			goto out;
-		} else if (fvp->v_type != VDIR && tvp->v_type == VDIR) {
-			error = EISDIR;
-			goto out;
+		if (!exchange) {
+			if (fvp->v_type == VDIR && tvp->v_type != VDIR) {
+				error = ENOTDIR;
+				goto out;
+			} else if (fvp->v_type != VDIR &&
+			    tvp->v_type == VDIR) {
+				error = EISDIR;
+				goto out;
+			}
 		}
 #ifdef CAPABILITIES
 		if (newfd != AT_FDCWD && (tond.ni_resflags & NIRES_ABS) == 0) {
diff --git a/sys/sys/fcntl.h b/sys/sys/fcntl.h
index 80cbca4ea753..44f43f745762 100644
--- a/sys/sys/fcntl.h
+++ b/sys/sys/fcntl.h
@@ -260,7 +260,9 @@ typedef	__pid_t		pid_t;
 #define	AT_EMPTY_PATH		0x4000	/* Operate on dirfd if path is empty */
 
 #define	AT_RENAME_NOREPLACE	0x0001	/* Fail rename if target exists */
+#define	AT_RENAME_EXCHANGE	0x0002	/* Atomically exchange 'from' and 'to' */
 #define	RENAME_NOREPLACE	AT_RENAME_NOREPLACE
+#define	RENAME_EXCHANGE		AT_RENAME_EXCHANGE
 #endif	/* __BSD_VISIBLE */
 
 /*