Here is the latest patch I have. It appears to completely solve the problem. I have shims in unionfs and nfs for the moment.
The patch is against -stable. * Implements SOFTLOCKLEAF namei() option * Implements EAGAIN error & appropriate tsleep/retry code * Universal for rename() & rmdir(). Final patch will also probably implement it on unlink() to solve (pre-existing) unlink/rename regular file race. * Tested very well w/ UFS, should be compatible with and work for direct access to other filesystems that still use IN_RENAME. * Tested for collision probability. Answer: Very low even when one tries on purpose. There is no need to implement a more sophisticated fine-grained tsleep. -Matt Index: kern/vfs_lookup.c =================================================================== RCS file: /home/ncvs/src/sys/kern/vfs_lookup.c,v retrieving revision 1.38.2.3 diff -u -r1.38.2.3 vfs_lookup.c --- kern/vfs_lookup.c 2001/08/31 19:36:49 1.38.2.3 +++ kern/vfs_lookup.c 2001/10/02 20:06:33 @@ -372,6 +372,11 @@ error = EISDIR; goto bad; } + if (cnp->cn_flags & SOFTLOCKLEAF) { + if ((error = vsetsoftlock(dp)) != 0) + goto bad; + VREF(dp); + } if (wantparent) { ndp->ni_dvp = dp; VREF(dp); @@ -565,13 +570,17 @@ error = EROFS; goto bad2; } + if (cnp->cn_flags & SOFTLOCKLEAF) { + if ((error = vsetsoftlock(dp)) != 0) + goto bad2; + VREF(dp); + } if (cnp->cn_flags & SAVESTART) { ndp->ni_startdir = ndp->ni_dvp; VREF(ndp->ni_startdir); } if (!wantparent) vrele(ndp->ni_dvp); - if ((cnp->cn_flags & LOCKLEAF) == 0) VOP_UNLOCK(dp, 0, p); return (0); @@ -654,6 +663,11 @@ error = ENOTDIR; goto bad; } + if (cnp->cn_flags & SOFTLOCKLEAF) { + if ((error = vsetsoftlock(dp)) != 0) + goto bad; + VREF(dp); + } if (!(cnp->cn_flags & LOCKLEAF)) VOP_UNLOCK(dp, 0, p); *vpp = dp; @@ -707,6 +721,11 @@ error = EROFS; goto bad2; } + if (cnp->cn_flags & SOFTLOCKLEAF) { + if ((error = vsetsoftlock(dp)) != 0) + goto bad2; + VREF(dp); + } /* ASSERT(dvp == ndp->ni_startdir) */ if (cnp->cn_flags & SAVESTART) VREF(dvp); @@ -715,8 +734,9 @@ vrele(dvp); if (vn_canvmio(dp) == TRUE && - ((cnp->cn_flags & (NOOBJ|LOCKLEAF)) == LOCKLEAF)) + ((cnp->cn_flags & (NOOBJ|LOCKLEAF)) == LOCKLEAF)) { vfs_object_create(dp, cnp->cn_proc, cnp->cn_cred); + } if ((cnp->cn_flags & LOCKLEAF) == 0) VOP_UNLOCK(dp, 0, p); Index: kern/vfs_subr.c =================================================================== RCS file: /home/ncvs/src/sys/kern/vfs_subr.c,v retrieving revision 1.249.2.11 diff -u -r1.249.2.11 vfs_subr.c --- kern/vfs_subr.c 2001/09/11 09:49:53 1.249.2.11 +++ kern/vfs_subr.c 2001/10/02 22:55:38 @@ -130,6 +132,8 @@ #endif struct nfs_public nfs_pub; /* publicly exported FS */ static vm_zone_t vnode_zone; +static int vagain_count = 1; +static int vagain_waiting = 0; /* * The workitem queue. @@ -2927,6 +2963,13 @@ struct nameidata *ndp; const uint flags; { + if (!(flags & NDF_NO_FREE_SOFTLOCKLEAF) && + (ndp->ni_cnd.cn_flags & SOFTLOCKLEAF) && + ndp->ni_vp) { + vclearsoftlock(ndp->ni_vp); + ndp->ni_cnd.cn_flags &= ~SOFTLOCKLEAF; + vrele(ndp->ni_vp); + } if (!(flags & NDF_NO_FREE_PNBUF) && (ndp->ni_cnd.cn_flags & HASBUF)) { zfree(namei_zone, ndp->ni_cnd.cn_pnbuf); @@ -2955,3 +2998,55 @@ ndp->ni_startdir = NULL; } } + +/* + * vsetsoftlock() - set the VSOFTLOCK flag on the vnode, return + * EAGAIN if it has already been set by someone else. + * + * note: we could further refine the collision by setting a VSOFTLOCKCOLL + * flag and then only waking up waiters when the colliding vnode is + * released. However, this sort of collision does not happen often + * enough for such an addition to yield any improvement in performance. + */ + +int +vsetsoftlock(struct vnode *vp) +{ + int s; + int error = 0; + + s = splbio(); + if (vp->v_flag & VSOFTLOCK) + error = EAGAIN; + else + vp->v_flag |= VSOFTLOCK; + splx(s); + return(error); +} + +void +vclearsoftlock(struct vnode *vp) +{ + int s; + + s = splbio(); + vp->v_flag &= ~VSOFTLOCK; + splx(s); + if (++vagain_count == 0) + vagain_count = 1; + if (vagain_waiting) { + vagain_waiting = 0; + wakeup(&vagain_waiting); + } +} + +void +vagain(int *gc) +{ + while (*gc && *gc == vagain_count) { + vagain_waiting = 1; + tsleep(&vagain_waiting, PVFS, "vagain", 0); + } + *gc = vagain_count; +} + Index: kern/vfs_syscalls.c =================================================================== RCS file: /home/ncvs/src/sys/kern/vfs_syscalls.c,v retrieving revision 1.151.2.9 diff -u -r1.151.2.9 vfs_syscalls.c --- kern/vfs_syscalls.c 2001/08/12 10:48:00 1.151.2.9 +++ kern/vfs_syscalls.c 2001/10/02 22:19:22 @@ -2631,12 +2631,18 @@ register struct vnode *tvp, *fvp, *tdvp; struct nameidata fromnd, tond; int error; + int gc = 0; +again: + vagain(&gc); bwillwrite(); - NDINIT(&fromnd, DELETE, WANTPARENT | SAVESTART, UIO_USERSPACE, - SCARG(uap, from), p); - if ((error = namei(&fromnd)) != 0) + NDINIT(&fromnd, DELETE, WANTPARENT | SAVESTART | SOFTLOCKLEAF, + UIO_USERSPACE, SCARG(uap, from), p); + if ((error = namei(&fromnd)) != 0) { + if (error == EAGAIN) + goto again; return (error); + } fvp = fromnd.ni_vp; NDINIT(&tond, RENAME, LOCKPARENT | LOCKLEAF | NOCACHE | SAVESTART | NOOBJ, UIO_USERSPACE, SCARG(uap, to), p); @@ -2646,7 +2652,7 @@ /* Translate error code for rename("dir1", "dir2/."). */ if (error == EISDIR && fvp->v_type == VDIR) error = EINVAL; - NDFREE(&fromnd, NDF_ONLY_PNBUF); + NDFREE(&fromnd, NDF_ONLY_PNBUF & NDF_ONLY_SOFTLOCKLEAF); vrele(fromnd.ni_dvp); vrele(fvp); goto out1; @@ -2683,12 +2689,14 @@ if (tvp) { VOP_LEASE(tvp, p, p->p_ucred, LEASE_WRITE); } + fromnd.ni_cnd.cn_flags &= ~SOFTLOCKLEAF; /* XXX hack */ error = VOP_RENAME(fromnd.ni_dvp, fromnd.ni_vp, &fromnd.ni_cnd, tond.ni_dvp, tond.ni_vp, &tond.ni_cnd); - NDFREE(&fromnd, NDF_ONLY_PNBUF); + fromnd.ni_cnd.cn_flags |= SOFTLOCKLEAF; + NDFREE(&fromnd, NDF_ONLY_PNBUF & NDF_ONLY_SOFTLOCKLEAF); NDFREE(&tond, NDF_ONLY_PNBUF); } else { - NDFREE(&fromnd, NDF_ONLY_PNBUF); + NDFREE(&fromnd, NDF_ONLY_PNBUF & NDF_ONLY_SOFTLOCKLEAF); NDFREE(&tond, NDF_ONLY_PNBUF); if (tdvp == tvp) vrele(tdvp); @@ -2782,13 +2790,19 @@ { register struct vnode *vp; int error; + int gc = 0; struct nameidata nd; +again: + vagain(&gc); bwillwrite(); - NDINIT(&nd, DELETE, LOCKPARENT | LOCKLEAF, UIO_USERSPACE, - SCARG(uap, path), p); - if ((error = namei(&nd)) != 0) + NDINIT(&nd, DELETE, LOCKPARENT | LOCKLEAF | SOFTLOCKLEAF, + UIO_USERSPACE, SCARG(uap, path), p); + if ((error = namei(&nd)) != 0) { + if (error == EAGAIN) + goto again; return (error); + } vp = nd.ni_vp; if (vp->v_type != VDIR) { error = ENOTDIR; @@ -2812,7 +2826,7 @@ error = VOP_RMDIR(nd.ni_dvp, nd.ni_vp, &nd.ni_cnd); } out: - NDFREE(&nd, NDF_ONLY_PNBUF); + NDFREE(&nd, NDF_ONLY_PNBUF & NDF_ONLY_SOFTLOCKLEAF); if (nd.ni_dvp == vp) vrele(nd.ni_dvp); else Index: miscfs/union/union_vnops.c =================================================================== RCS file: /home/ncvs/src/sys/miscfs/union/Attic/union_vnops.c,v retrieving revision 1.72 diff -u -r1.72 union_vnops.c --- miscfs/union/union_vnops.c 1999/12/15 23:02:14 1.72 +++ miscfs/union/union_vnops.c 2001/10/02 22:48:40 @@ -1380,6 +1380,7 @@ struct vnode *fvp = ap->a_fvp; struct vnode *tdvp = ap->a_tdvp; struct vnode *tvp = ap->a_tvp; + int setsoftlock = 0; /* * Figure out what fdvp to pass to our upper or lower vnode. If we @@ -1456,6 +1457,16 @@ fvp = un->un_uppervp; VREF(fvp); vrele(ap->a_fvp); + if ((error = vsetsoftlock(fvp)) != 0) { + /* + * XXX for now do not support a retry request + */ + if (error == EAGAIN) + error = EINVAL; + goto bad; + } + setsoftlock = 1; + VREF(fvp); /* for softlock */ } /* @@ -1505,11 +1516,15 @@ } /* - * VOP_RENAME releases/vputs prior to returning, so we have no - * cleanup to do. + * VOP_RENAME releases/vputs prior to returning, except for the + * SOFTLOCK, so we have minimal cleanup to do. */ - - return (VOP_RENAME(fdvp, fvp, ap->a_fcnp, tdvp, tvp, ap->a_tcnp)); + error = VOP_RENAME(fdvp, fvp, ap->a_fcnp, tdvp, tvp, ap->a_tcnp); + if (setsoftlock) { + vclearsoftlock(fvp); + vrele(fvp); + } + return(error); /* * Error. We still have to release / vput the various elements. @@ -1517,8 +1532,13 @@ bad: vrele(fdvp); - if (fvp) + if (fvp) { + if (setsoftlock) { + vclearsoftlock(fvp); + vrele(fvp); + } vrele(fvp); + } vput(tdvp); if (tvp != NULLVP) { if (tvp != tdvp) @@ -1583,7 +1603,16 @@ if ((uppervp = union_lock_upper(un, p)) != NULLVP) { if (union_dowhiteout(un, cnp->cn_cred, p)) cnp->cn_flags |= DOWHITEOUT; - error = VOP_RMDIR(upperdvp, uppervp, ap->a_cnp); + if ((error = vsetsoftlock(upperdvp)) != 0) { + /* + * XXX for now do not support a retry request + */ + if (error == EAGAIN) + error = EINVAL; + + } else { + error = VOP_RMDIR(upperdvp, uppervp, ap->a_cnp); + } union_unlock_upper(uppervp, p); } else { error = union_mkwhiteout( Index: nfs/nfs_serv.c =================================================================== RCS file: /home/ncvs/src/sys/nfs/Attic/nfs_serv.c,v retrieving revision 1.93 diff -u -r1.93 nfs_serv.c --- nfs/nfs_serv.c 1999/12/18 19:20:05 1.93 +++ nfs/nfs_serv.c 2001/10/02 23:03:34 @@ -2194,7 +2194,7 @@ saved_uid = cred->cr_uid; fromnd.ni_cnd.cn_cred = cred; fromnd.ni_cnd.cn_nameiop = DELETE; - fromnd.ni_cnd.cn_flags = WANTPARENT | SAVESTART; + fromnd.ni_cnd.cn_flags = WANTPARENT | SAVESTART | SOFTLOCKLEAF; error = nfs_namei(&fromnd, ffhp, len, slp, nam, &md, &dpos, &fdirp, procp, (nfsd->nd_flag & ND_KERBAUTH), FALSE); if (fdirp) { @@ -2207,6 +2207,8 @@ } } if (error) { + if (error == EAGAIN) /* XXX EAGAIN not yet supported */ + error = EINVAL; nfsm_reply(2 * NFSX_WCCDATA(v3)); nfsm_srvwcc_data(fdirfor_ret, &fdirfor, fdiraft_ret, &fdiraft); nfsm_srvwcc_data(tdirfor_ret, &tdirfor, tdiraft_ret, &tdiraft); @@ -2300,13 +2302,17 @@ if (tvp) { nqsrv_getl(tvp, ND_WRITE); } + fromnd.ni_cnd.cn_flags &= ~SOFTLOCKLEAF; /* XXX hack */ error = VOP_RENAME(fromnd.ni_dvp, fromnd.ni_vp, &fromnd.ni_cnd, tond.ni_dvp, tond.ni_vp, &tond.ni_cnd); - fromnd.ni_dvp = NULL; - fromnd.ni_vp = NULL; - tond.ni_dvp = NULL; - tond.ni_vp = NULL; - if (error) { + /* XXX this is a @!#$!@#$@#$ mess */ + vclearsoftlock(fromnd.ni_vp); /* cleanup after SOFTLOCKLEAF */ + vrele(fromnd.ni_vp); /* cleanup after SOFTLOCKLEAF */ + fromnd.ni_dvp = NULL; /* clear for fall through */ + fromnd.ni_vp = NULL; /* clear for fall through */ + tond.ni_dvp = NULL; /* clear for fall through */ + tond.ni_vp = NULL; /* clear for fall through */ + if (error) { /* clear for fall through */ fromnd.ni_cnd.cn_flags &= ~HASBUF; tond.ni_cnd.cn_flags &= ~HASBUF; } @@ -2354,7 +2360,7 @@ vrele(fdirp); if (fromnd.ni_startdir) vrele(fromnd.ni_startdir); - NDFREE(&fromnd, NDF_ONLY_PNBUF); + NDFREE(&fromnd, NDF_ONLY_PNBUF & NDF_ONLY_SOFTLOCKLEAF); if (fromnd.ni_dvp) vrele(fromnd.ni_dvp); if (fromnd.ni_vp) @@ -2826,7 +2832,7 @@ nfsm_srvnamesiz(len); nd.ni_cnd.cn_cred = cred; nd.ni_cnd.cn_nameiop = DELETE; - nd.ni_cnd.cn_flags = LOCKPARENT | LOCKLEAF; + nd.ni_cnd.cn_flags = LOCKPARENT | LOCKLEAF | SOFTLOCKLEAF; error = nfs_namei(&nd, fhp, len, slp, nam, &md, &dpos, &dirp, procp, (nfsd->nd_flag & ND_KERBAUTH), FALSE); if (dirp) { @@ -2839,6 +2845,8 @@ } } if (error) { + if (error == EAGAIN) /* XXX EAGAIN not yet supported */ + error = EINVAL; nfsm_reply(NFSX_WCCDATA(v3)); nfsm_srvwcc_data(dirfor_ret, &dirfor, diraft_ret, &diraft); error = 0; @@ -2871,7 +2879,7 @@ nqsrv_getl(vp, ND_WRITE); error = VOP_RMDIR(nd.ni_dvp, nd.ni_vp, &nd.ni_cnd); } - NDFREE(&nd, NDF_ONLY_PNBUF); + NDFREE(&nd, NDF_ONLY_PNBUF & NDF_ONLY_SOFTLOCKLEAF); if (dirp) diraft_ret = VOP_GETATTR(dirp, &diraft, cred, procp); @@ -2883,7 +2891,7 @@ /* fall through */ nfsmout: - NDFREE(&nd, NDF_ONLY_PNBUF); + NDFREE(&nd, NDF_ONLY_PNBUF & NDF_ONLY_SOFTLOCKLEAF); if (dirp) vrele(dirp); if (nd.ni_dvp) { Index: nfs/nfs_subs.c =================================================================== RCS file: /home/ncvs/src/sys/nfs/Attic/nfs_subs.c,v retrieving revision 1.90.2.1 diff -u -r1.90.2.1 nfs_subs.c --- nfs/nfs_subs.c 2000/10/28 16:27:27 1.90.2.1 +++ nfs/nfs_subs.c 2001/10/02 23:01:03 @@ -1729,6 +1729,10 @@ out: if (error) { zfree(namei_zone, cnp->cn_pnbuf); + if ((cnp->cn_flags & SOFTLOCKLEAF) && ndp->ni_vp) { + vclearsoftlock(ndp->ni_vp); + vrele(ndp->ni_vp); + } ndp->ni_vp = NULL; ndp->ni_dvp = NULL; ndp->ni_startdir = NULL; Index: sys/namei.h =================================================================== RCS file: /home/ncvs/src/sys/sys/namei.h,v retrieving revision 1.29.2.2 diff -u -r1.29.2.2 namei.h --- sys/namei.h 2001/09/30 21:12:54 1.29.2.2 +++ sys/namei.h 2001/10/02 18:43:34 @@ -114,7 +114,7 @@ #define FOLLOW 0x0040 /* follow symbolic links */ #define NOOBJ 0x0080 /* don't create object */ #define NOFOLLOW 0x0000 /* do not follow symbolic links (pseudo) */ -#define MODMASK 0x00fc /* mask of operational modifiers */ +#define MODMASK (0x00fc|SOFTLOCKLEAF) /* * Namei parameter descriptors. * @@ -143,7 +143,7 @@ #define WILLBEDIR 0x080000 /* new files will be dirs; allow trailing / */ #define ISUNICODE 0x100000 /* current component name is unicode*/ #define PDIRUNLOCK 0x200000 /* file system lookup() unlocked parent dir */ -#define PARAMASK 0x1fff00 /* mask of parameter descriptors */ +#define SOFTLOCKLEAF 0x400000 /* set VSOFTLOCK in vnode */ /* * Initialization of an nameidata structure. */ @@ -180,7 +180,9 @@ #define NDF_NO_VP_PUT 0x0000000c #define NDF_NO_STARTDIR_RELE 0x00000010 #define NDF_NO_FREE_PNBUF 0x00000020 +#define NDF_NO_FREE_SOFTLOCKLEAF 0x00000040 #define NDF_ONLY_PNBUF (~NDF_NO_FREE_PNBUF) +#define NDF_ONLY_SOFTLOCKLEAF (~NDF_NO_FREE_SOFTLOCKLEAF) void NDFREE __P((struct nameidata *, const uint)); Index: sys/vnode.h =================================================================== RCS file: /home/ncvs/src/sys/sys/vnode.h,v retrieving revision 1.111.2.12 diff -u -r1.111.2.12 vnode.h --- sys/vnode.h 2001/09/22 09:21:48 1.111.2.12 +++ sys/vnode.h 2001/10/02 22:54:37 @@ -169,6 +169,7 @@ #define VTBFREE 0x100000 /* This vnode is on the to-be-freelist */ #define VONWORKLST 0x200000 /* On syncer work-list */ #define VMOUNT 0x400000 /* Mount in progress */ +#define VSOFTLOCK 0x800000 /* Soft lock on vnode (directory entry) */ /* * Vnode attributes. A field value of VNOVAL represents a field whose value @@ -630,6 +632,9 @@ void vrele __P((struct vnode *vp)); void vref __P((struct vnode *vp)); void vbusy __P((struct vnode *vp)); +int vsetsoftlock __P((struct vnode *vp)); +void vclearsoftlock __P((struct vnode *vp)); +void vagain __P((int *gc)); extern vop_t **default_vnodeop_p; extern vop_t **spec_vnodeop_p; Index: ufs/ufs/ufs_vnops.c =================================================================== RCS file: /home/ncvs/src/sys/ufs/ufs/ufs_vnops.c,v retrieving revision 1.131.2.4 diff -u -r1.131.2.4 ufs_vnops.c --- ufs/ufs/ufs_vnops.c 2001/08/28 17:28:49 1.131.2.4 +++ ufs/ufs/ufs_vnops.c 2001/10/02 19:16:40 @@ -997,13 +997,11 @@ * Avoid ".", "..", and aliases of "." for obvious reasons. */ if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') || - dp == ip || (fcnp->cn_flags | tcnp->cn_flags) & ISDOTDOT || - (ip->i_flag & IN_RENAME)) { + dp == ip || (fcnp->cn_flags | tcnp->cn_flags) & ISDOTDOT) { VOP_UNLOCK(fvp, 0, p); error = EINVAL; goto abortit; } - ip->i_flag |= IN_RENAME; oldparent = dp->i_number; doingdirectory = 1; } @@ -1224,13 +1222,12 @@ return (0); } /* - * Ensure that the directory entry still exists and has not - * changed while the new name has been entered. If the source is - * a file then the entry may have been unlinked or renamed. In - * either case there is no further work to be done. If the source - * is a directory then it cannot have been rmdir'ed; the IN_RENAME - * flag ensures that it cannot be moved by another rename or removed - * by a rmdir. + * The source has been VSOFTLOCKd by the caller, preventing rename + * or rmdir, but not prevent unlink. At the moment we allow the unlink + * race and so do not panic if relookup fails or the inodes do not + * match, because files can be hardlinked and our flag is on the vnode + * rather then the directory entry in the namei cache. But source + * directories must still exist. */ if (xp != ip) { if (doingdirectory) @@ -1248,7 +1245,6 @@ cache_purge(fdvp); } error = ufs_dirremove(fdvp, xp, fcnp->cn_flags, 0); - xp->i_flag &= ~IN_RENAME; } VN_KNOTE(fvp, NOTE_RENAME); if (dp) @@ -1263,13 +1259,10 @@ vput(ITOV(xp)); vput(ITOV(dp)); out: - if (doingdirectory) - ip->i_flag &= ~IN_RENAME; if (vn_lock(fvp, LK_EXCLUSIVE, p) == 0) { ip->i_effnlink--; ip->i_nlink--; ip->i_flag |= IN_CHANGE; - ip->i_flag &= ~IN_RENAME; if (DOINGSOFTDEP(fvp)) softdep_change_linkcnt(ip); vput(fvp); @@ -1503,18 +1496,16 @@ dp = VTOI(dvp); /* - * Do not remove a directory that is in the process of being renamed. * Verify the directory is empty (and valid). Rmdir ".." will not be * valid since ".." will contain a reference to the current directory * and thus be non-empty. Do not allow the removal of mounted on * directories (this can happen when an NFS exported filesystem * tries to remove a locally mounted on directory). + * + * The syscall wrapper already protects us from attempting to remove + * a directory that is undergoing a rename. */ error = 0; - if (ip->i_flag & IN_RENAME) { - error = EINVAL; - goto out; - } if (ip->i_effnlink != 2 || !ufs_dirempty(ip, dp->i_number, cnp->cn_cred)) { error = ENOTEMPTY; To Unsubscribe: send mail to [EMAIL PROTECTED] with "unsubscribe freebsd-hackers" in the body of the message