git: 2ed053cde558 - main - vfs: Add IGNOREWHITEOUT flag and adopt it in UFS/unionfs

From: Jason A. Harmening <jah_at_FreeBSD.org>
Date: Mon, 09 Sep 2024 00:02:14 UTC
The branch main has been updated by jah:

URL: https://cgit.FreeBSD.org/src/commit/?id=2ed053cde55869d3440377d479deb00f42ba1cf8

commit 2ed053cde55869d3440377d479deb00f42ba1cf8
Author:     Jason A. Harmening <jah@FreeBSD.org>
AuthorDate: 2024-08-06 04:12:36 +0000
Commit:     Jason A. Harmening <jah@FreeBSD.org>
CommitDate: 2024-09-08 23:34:14 +0000

    vfs: Add IGNOREWHITEOUT flag and adopt it in UFS/unionfs
    
    This flag is meant to request that the VOP implementation ignore
    whiteout entries when processing directory contents.
    
    Employ this flag (initially) in UFS when determining whether a directory
    is empty for the purpose of deleting it or renaming another directory
    over it.  The previous UFS behavior was to always ignore whiteouts and
    to therefore always allow directories containing only whiteouts to be
    deleted or overwritten.  This makes sense when the directory in question
    is being accessed through a unionfs view in which the whiteouts produce
    a unionfs directory that is logically empty, but it makes less sense
    when directly operating against the UFS directory in which case silently
    discarding the whiteouts may produce unexpected behavior in a current or
    future unionfs view.  IGNOREWHITEOUT is therefore treated as opt-in and
    only specified by unionfs_rmdir() when invoking VOP_RMDIR() against the
    upper filesystem.  IGNOREWHITEOUT is not currently used for unionfs
    rename operations, as the current implementation of unionfs_rename()
    simply forbids renaming over any existing upper filesystem directory in
    the first place.
    
    Differential Revision:  https://reviews.freebsd.org/D45987
    Reviewed by:            olce
    Tested by:              pho
---
 sys/fs/unionfs/union_vnops.c | 2 +-
 sys/sys/namei.h              | 2 +-
 sys/ufs/ufs/ufs_extern.h     | 2 +-
 sys/ufs/ufs/ufs_lookup.c     | 6 ++++--
 sys/ufs/ufs/ufs_vnops.c      | 6 ++++--
 5 files changed, 11 insertions(+), 7 deletions(-)

diff --git a/sys/fs/unionfs/union_vnops.c b/sys/fs/unionfs/union_vnops.c
index 3f39352ea5c0..e1048e4ba7ab 100644
--- a/sys/fs/unionfs/union_vnops.c
+++ b/sys/fs/unionfs/union_vnops.c
@@ -1732,7 +1732,7 @@ unionfs_rmdir(struct vop_rmdir_args *ap)
 		}
 		ump = MOUNTTOUNIONFSMOUNT(ap->a_vp->v_mount);
 		if (ump->um_whitemode == UNIONFS_WHITE_ALWAYS || lvp != NULLVP)
-			cnp->cn_flags |= DOWHITEOUT;
+			cnp->cn_flags |= (DOWHITEOUT | IGNOREWHITEOUT);
 		int udvp_lkflags, uvp_lkflags;
 		unionfs_forward_vop_start_pair(udvp, &udvp_lkflags,
 		    uvp, &uvp_lkflags);
diff --git a/sys/sys/namei.h b/sys/sys/namei.h
index 2ea4f502e8fd..1416ff983f32 100644
--- a/sys/sys/namei.h
+++ b/sys/sys/namei.h
@@ -159,7 +159,7 @@ int	cache_fplookup(struct nameidata *ndp, enum cache_fpl_status *status,
  */
 #define	RDONLY		0x00000200 /* lookup with read-only semantics */
 #define	ISRESTARTED	0x00000400 /* restarted namei */
-/* UNUSED		0x00000800 */
+#define	IGNOREWHITEOUT	0x00000800 /* ignore whiteouts, e.g. when checking if a dir is empty */
 #define	ISWHITEOUT	0x00001000 /* found whiteout */
 #define	DOWHITEOUT	0x00002000 /* do whiteouts */
 #define	WILLBEDIR	0x00004000 /* new files will be dirs; allow trailing / */
diff --git a/sys/ufs/ufs/ufs_extern.h b/sys/ufs/ufs/ufs_extern.h
index b1d55ed1f180..ccd9046a5fa8 100644
--- a/sys/ufs/ufs/ufs_extern.h
+++ b/sys/ufs/ufs/ufs_extern.h
@@ -59,7 +59,7 @@ int	 ufs_bmap_seekdata(struct vnode *, off_t *);
 int	 ufs_checkpath(ino_t, ino_t, struct inode *, struct ucred *, ino_t *);
 void	 ufs_dirbad(struct inode *, doff_t, char *);
 int	 ufs_dirbadentry(struct vnode *, struct direct *, int);
-int	 ufs_dirempty(struct inode *, ino_t, struct ucred *);
+int	 ufs_dirempty(struct inode *, ino_t, struct ucred *, int);
 int	 ufs_extread(struct vop_read_args *);
 int	 ufs_extwrite(struct vop_write_args *);
 void	 ufs_makedirentry(struct inode *, struct componentname *,
diff --git a/sys/ufs/ufs/ufs_lookup.c b/sys/ufs/ufs/ufs_lookup.c
index 2d6c79970c96..eaf37c58756b 100644
--- a/sys/ufs/ufs/ufs_lookup.c
+++ b/sys/ufs/ufs/ufs_lookup.c
@@ -1298,7 +1298,8 @@ ufs_dirrewrite(struct inode *dp, struct inode *oip, ino_t newinum, int newtype,
  * NB: does not handle corrupted directories.
  */
 int
-ufs_dirempty(struct inode *ip, ino_t parentino, struct ucred *cred)
+ufs_dirempty(struct inode *ip, ino_t parentino, struct ucred *cred,
+    int skipwhiteout)
 {
 	doff_t off;
 	struct dirtemplate dbuf;
@@ -1321,7 +1322,8 @@ ufs_dirempty(struct inode *ip, ino_t parentino, struct ucred *cred)
 		if (dp->d_reclen == 0)
 			return (0);
 		/* skip empty entries */
-		if (dp->d_ino == 0 || dp->d_ino == UFS_WINO)
+		if (dp->d_ino == 0 ||
+		    (skipwhiteout != 0 && dp->d_ino == UFS_WINO))
 			continue;
 		/* accept only "." and ".." */
 #		if (BYTE_ORDER == LITTLE_ENDIAN)
diff --git a/sys/ufs/ufs/ufs_vnops.c b/sys/ufs/ufs/ufs_vnops.c
index c62583afaab6..0bca40199071 100644
--- a/sys/ufs/ufs/ufs_vnops.c
+++ b/sys/ufs/ufs/ufs_vnops.c
@@ -1625,7 +1625,8 @@ relock:
 		 */
 		if ((tip->i_mode & IFMT) == IFDIR) {
 			if ((tip->i_effnlink > 2) ||
-			    !ufs_dirempty(tip, tdp->i_number, tcnp->cn_cred)) {
+			    !ufs_dirempty(tip, tdp->i_number, tcnp->cn_cred,
+			    (tcnp->cn_flags & IGNOREWHITEOUT) != 0)) {
 				error = ENOTEMPTY;
 				goto bad;
 			}
@@ -2281,7 +2282,8 @@ ufs_rmdir(
 		error = EINVAL;
 		goto out;
 	}
-	if (!ufs_dirempty(ip, dp->i_number, cnp->cn_cred)) {
+	if (!ufs_dirempty(ip, dp->i_number, cnp->cn_cred,
+	    (cnp->cn_flags & IGNOREWHITEOUT) != 0)) {
 		error = ENOTEMPTY;
 		goto out;
 	}