diff --git a/sys/fs/msdosfs/denode.h b/sys/fs/msdosfs/denode.h --- a/sys/fs/msdosfs/denode.h +++ b/sys/fs/msdosfs/denode.h @@ -173,7 +173,6 @@ #define DE_CREATE 0x0008 /* Creation time update */ #define DE_ACCESS 0x0010 /* Access time update */ #define DE_MODIFIED 0x0020 /* Denode has been modified */ -#define DE_RENAME 0x0040 /* Denode is in the process of being renamed */ /* Maximum size of a file on a FAT filesystem */ #define MSDOSFS_FILESIZE_MAX 0xFFFFFFFFLL @@ -267,13 +266,15 @@ int msdosfs_lookup(struct vop_cachedlookup_args *); int msdosfs_inactive(struct vop_inactive_args *); int msdosfs_reclaim(struct vop_reclaim_args *); +int msdosfs_lookup_ino(struct vnode *vdp, struct vnode **vpp, + struct componentname *cnp, daddr_t *scnp, u_long *blkoffp); #endif /* * Internal service routine prototypes. */ struct componentname; -int deget(struct msdosfsmount *, u_long, u_long, struct denode **); +int deget(struct msdosfsmount *, u_long, u_long, int, struct denode **); int uniqdosname(struct denode *, struct componentname *, u_char *); int readep(struct msdosfsmount *pmp, u_long dirclu, u_long dirofs, struct buf **bpp, struct direntry **epp); @@ -286,6 +287,7 @@ int deupdat(struct denode *dep, int waitfor); int removede(struct denode *pdep, struct denode *dep); int detrunc(struct denode *dep, u_long length, int flags, struct ucred *cred); -int doscheckpath( struct denode *source, struct denode *target); +int doscheckpath( struct denode *source, struct denode *target, + daddr_t *wait_scn); #endif /* _KERNEL || MAKEFS */ #endif /* !_FS_MSDOSFS_DENODE_H_ */ diff --git a/sys/fs/msdosfs/msdosfs_denode.c b/sys/fs/msdosfs/msdosfs_denode.c --- a/sys/fs/msdosfs/msdosfs_denode.c +++ b/sys/fs/msdosfs/msdosfs_denode.c @@ -92,11 +92,12 @@ * diroffset is relative to the beginning of the root directory, * otherwise it is cluster relative. * diroffset - offset past begin of cluster of denode we want + * lkflags - locking flags (LK_NOWAIT) * depp - returns the address of the gotten denode. */ int deget(struct msdosfsmount *pmp, u_long dirclust, u_long diroffset, - struct denode **depp) + int lkflags, struct denode **depp) { int error; uint64_t inode; @@ -107,9 +108,11 @@ struct buf *bp; #ifdef MSDOSFS_DEBUG - printf("deget(pmp %p, dirclust %lu, diroffset %lx, depp %p)\n", - pmp, dirclust, diroffset, depp); + printf("deget(pmp %p, dirclust %lu, diroffset %lx, flags %#x, " + "depp %p)\n", + pmp, dirclust, diroffset, flags, depp); #endif + MPASS((lkflags & LK_TYPE_MASK) == LK_EXCLUSIVE); /* * On FAT32 filesystems, root is a (more or less) normal @@ -133,7 +136,7 @@ */ inode = (uint64_t)pmp->pm_bpcluster * dirclust + diroffset; - error = vfs_hash_get(mntp, inode, LK_EXCLUSIVE, curthread, &nvp, + error = vfs_hash_get(mntp, inode, lkflags, curthread, &nvp, de_vncmpf, &inode); if (error) return (error); @@ -164,6 +167,7 @@ ldep->de_inode = inode; cluster_init_vn(&ldep->de_clusterw); lockmgr(nvp->v_vnlock, LK_EXCLUSIVE, NULL); + VN_LOCK_AREC(nvp); /* for doscheckpath */ fc_purge(ldep, 0); /* init the FAT cache for this denode */ error = insmntque(nvp, mntp); if (error != 0) { @@ -171,7 +175,7 @@ *depp = NULL; return (error); } - error = vfs_hash_insert(nvp, inode, LK_EXCLUSIVE, curthread, &xvp, + error = vfs_hash_insert(nvp, inode, lkflags, curthread, &xvp, de_vncmpf, &inode); if (error) { *depp = NULL; diff --git a/sys/fs/msdosfs/msdosfs_lookup.c b/sys/fs/msdosfs/msdosfs_lookup.c --- a/sys/fs/msdosfs/msdosfs_lookup.c +++ b/sys/fs/msdosfs/msdosfs_lookup.c @@ -63,14 +63,12 @@ #include #include -static int msdosfs_lookup_(struct vnode *vdp, struct vnode **vpp, - struct componentname *cnp, uint64_t *inum); - int msdosfs_lookup(struct vop_cachedlookup_args *ap) { - return (msdosfs_lookup_(ap->a_dvp, ap->a_vpp, ap->a_cnp, NULL)); + return (msdosfs_lookup_ino(ap->a_dvp, ap->a_vpp, ap->a_cnp, NULL, + NULL)); } struct deget_dotdot { @@ -89,7 +87,8 @@ pmp = VFSTOMSDOSFS(mp); dd_arg = arg; - error = deget(pmp, dd_arg->cluster, dd_arg->blkoff, &rdp); + error = deget(pmp, dd_arg->cluster, dd_arg->blkoff, + LK_EXCLUSIVE, &rdp); if (error == 0) *rvp = DETOV(rdp); return (error); @@ -110,9 +109,9 @@ * out to disk. This way disk blocks containing directory entries and in * memory denode's will be in synch. */ -static int -msdosfs_lookup_(struct vnode *vdp, struct vnode **vpp, - struct componentname *cnp, uint64_t *dd_inum) +int +msdosfs_lookup_ino(struct vnode *vdp, struct vnode **vpp, struct componentname + *cnp, daddr_t *scnp, u_long *blkoffp) { struct mbnambuf nb; daddr_t bn; @@ -121,11 +120,11 @@ int slotoffset = 0; int frcn; u_long cluster; - int blkoff; + u_long blkoff; int diroff; int blsize; int isadir; /* ~0 if found direntry is a directory */ - u_long scn; /* starting cluster number */ + daddr_t scn; /* starting cluster number */ struct vnode *pdp; struct denode *dp; struct denode *tdp; @@ -466,8 +465,9 @@ if (FAT32(pmp) && scn == MSDOSFSROOT) scn = pmp->pm_rootdirblk; - if (dd_inum != NULL) { - *dd_inum = (uint64_t)pmp->pm_bpcluster * scn + blkoff; + if (scnp != NULL) { + *scnp = cluster; + *blkoffp = blkoff; return (0); } @@ -498,7 +498,7 @@ *vpp = vdp; return (0); } - error = deget(pmp, cluster, blkoff, &tdp); + error = deget(pmp, cluster, blkoff, LK_EXCLUSIVE, &tdp); if (error) return (error); *vpp = DETOV(tdp); @@ -526,7 +526,8 @@ if (dp->de_StartCluster == scn && isadir) return (EISDIR); - if ((error = deget(pmp, cluster, blkoff, &tdp)) != 0) + if ((error = deget(pmp, cluster, blkoff, LK_EXCLUSIVE, + &tdp)) != 0) return (error); *vpp = DETOV(tdp); cnp->cn_flags |= SAVENAME; @@ -558,12 +559,13 @@ * Recheck that ".." still points to the inode we * looked up before pdp lock was dropped. */ - error = msdosfs_lookup_(pdp, NULL, cnp, &inode1); + error = msdosfs_lookup_ino(pdp, NULL, cnp, &scn, &blkoff); if (error) { vput(*vpp); *vpp = NULL; return (error); } + inode1 = scn * pmp->pm_bpcluster + blkoff; if (VTODE(*vpp)->de_inode != inode1) { vput(*vpp); goto restart; @@ -572,7 +574,8 @@ VREF(vdp); /* we want ourself, ie "." */ *vpp = vdp; } else { - if ((error = deget(pmp, cluster, blkoff, &tdp)) != 0) + if ((error = deget(pmp, cluster, blkoff, LK_EXCLUSIVE, + &tdp)) != 0) return (error); *vpp = DETOV(tdp); } @@ -711,7 +714,7 @@ else diroffset = 0; } - return deget(pmp, dirclust, diroffset, depp); + return (deget(pmp, dirclust, diroffset, LK_EXCLUSIVE, depp)); } return 0; @@ -794,10 +797,9 @@ * * Returns 0 if target is NOT a subdirectory of source. * Otherwise returns a non-zero error number. - * The target inode is always unlocked on return. */ int -doscheckpath(struct denode *source, struct denode *target) +doscheckpath(struct denode *source, struct denode *target, daddr_t *wait_scn) { daddr_t scn; struct msdosfsmount *pmp; @@ -806,7 +808,12 @@ struct buf *bp = NULL; int error = 0; + *wait_scn = 0; + dep = target; + pmp = dep->de_pmp; + lockmgr_assert(&pmp->pm_checkpath_lock, KA_XLOCKED); + if ((target->de_Attributes & ATTR_DIRECTORY) == 0 || (source->de_Attributes & ATTR_DIRECTORY) == 0) { error = ENOTDIR; @@ -818,7 +825,6 @@ } if (dep->de_StartCluster == MSDOSFSROOT) goto out; - pmp = dep->de_pmp; #ifdef DIAGNOSTIC if (pmp != source->de_pmp) panic("doscheckpath: source and target on different filesystems"); @@ -833,11 +839,11 @@ } scn = dep->de_StartCluster; error = bread(pmp->pm_devvp, cntobn(pmp, scn), - pmp->pm_bpcluster, NOCRED, &bp); + pmp->pm_bpcluster, NOCRED, &bp); if (error) break; - ep = (struct direntry *) bp->b_data + 1; + ep = (struct direntry *)bp->b_data + 1; if ((ep->deAttributes & ATTR_DIRECTORY) == 0 || bcmp(ep->deName, ".. ", 11) != 0) { error = ENOTDIR; @@ -861,21 +867,26 @@ break; } - vput(DETOV(dep)); brelse(bp); bp = NULL; + if (dep != target) + vput(DETOV(dep)); + dep = NULL; /* NOTE: deget() clears dep on error */ - if ((error = deget(pmp, scn, 0, &dep)) != 0) + error = deget(pmp, scn, 0, LK_EXCLUSIVE | LK_NOWAIT, &dep); + if (error != 0) { + *wait_scn = scn; break; + } } out:; - if (bp) + if (bp != NULL) brelse(bp); #ifdef MSDOSFS_DEBUG if (error == ENOTDIR) printf("doscheckpath(): .. not a directory?\n"); #endif - if (dep != NULL) + if (dep != NULL && dep != target) vput(DETOV(dep)); return (error); } diff --git a/sys/fs/msdosfs/msdosfs_vfsops.c b/sys/fs/msdosfs/msdosfs_vfsops.c --- a/sys/fs/msdosfs/msdosfs_vfsops.c +++ b/sys/fs/msdosfs/msdosfs_vfsops.c @@ -469,6 +469,7 @@ pmp->pm_bo = bo; lockinit(&pmp->pm_fatlock, 0, msdosfs_lock_msg, 0, 0); + lockinit(&pmp->pm_checkpath_lock, 0, "msdoscp", 0, 0); /* * Initialize ownerships and permissions, since nothing else will @@ -740,6 +741,7 @@ } if (pmp) { lockdestroy(&pmp->pm_fatlock); + lockdestroy(&pmp->pm_checkpath_lock); free(pmp->pm_inusemap, M_MSDOSFSFAT); free(pmp, M_MSDOSFSMNT); mp->mnt_data = NULL; @@ -829,6 +831,7 @@ dev_rel(pmp->pm_dev); free(pmp->pm_inusemap, M_MSDOSFSFAT); lockdestroy(&pmp->pm_fatlock); + lockdestroy(&pmp->pm_checkpath_lock); free(pmp, M_MSDOSFSMNT); mp->mnt_data = NULL; MNT_ILOCK(mp); @@ -847,7 +850,7 @@ #ifdef MSDOSFS_DEBUG printf("msdosfs_root(); mp %p, pmp %p\n", mp, pmp); #endif - error = deget(pmp, MSDOSFSROOT, MSDOSFSROOT_OFS, &ndep); + error = deget(pmp, MSDOSFSROOT, MSDOSFSROOT_OFS, LK_EXCLUSIVE, &ndep); if (error) return (error); *vpp = DETOV(ndep); @@ -988,7 +991,8 @@ struct denode *dep; int error; - error = deget(pmp, defhp->defid_dirclust, defhp->defid_dirofs, &dep); + error = deget(pmp, defhp->defid_dirclust, defhp->defid_dirofs, + LK_EXCLUSIVE, &dep); if (error) { *vpp = NULLVP; return (error); diff --git a/sys/fs/msdosfs/msdosfs_vnops.c b/sys/fs/msdosfs/msdosfs_vnops.c --- a/sys/fs/msdosfs/msdosfs_vnops.c +++ b/sys/fs/msdosfs/msdosfs_vnops.c @@ -943,15 +943,16 @@ struct vnode *tvp = ap->a_tvp; struct componentname *tcnp = ap->a_tcnp; struct componentname *fcnp = ap->a_fcnp; - struct denode *ip, *xp, *dp, *zp; + struct denode *fdip, *fip, *tdip, *tip, *nip; u_char toname[12], oldname[11]; u_long from_diroffset, to_diroffset; + bool checkpath_locked, doingdirectory, newparent; u_char to_count; - int doingdirectory = 0, newparent = 0; int error; - u_long cn, pcl; - daddr_t bn; + u_long cn, pcl, blkoff; + daddr_t bn, wait_scn, scn; struct msdosfsmount *pmp; + struct mount *mp; struct direntry *dotdotp; struct buf *bp; @@ -965,19 +966,11 @@ /* * Check for cross-device rename. */ + mp = fvp->v_mount; if (fvp->v_mount != tdvp->v_mount || - (tvp && fvp->v_mount != tvp->v_mount)) { + (tvp != NULL && fvp->v_mount != tvp->v_mount)) { error = EXDEV; -abortit: - if (tdvp == tvp) - vrele(tdvp); - else - vput(tdvp); - if (tvp) - vput(tvp); - vrele(fdvp); - vrele(fvp); - return (error); + goto abortit; } /* @@ -988,11 +981,91 @@ goto abortit; } - error = vn_lock(fvp, LK_EXCLUSIVE); - if (error) - goto abortit; - dp = VTODE(fdvp); - ip = VTODE(fvp); + /* + * When the target exists, both the directory + * and target vnodes are passed locked. + */ + VOP_UNLOCK(tdvp); + if (tvp != NULL && tvp != tdvp) + VOP_UNLOCK(tvp); + + checkpath_locked = false; + +relock: + doingdirectory = newparent = false; + + error = vn_lock(fdvp, LK_EXCLUSIVE); + if (error != 0) + goto releout; + if (vn_lock(tdvp, LK_EXCLUSIVE | LK_NOWAIT) != 0) { + VOP_UNLOCK(fdvp); + error = vn_lock(tdvp, LK_EXCLUSIVE); + if (error != 0) + goto releout; + VOP_UNLOCK(tdvp); + goto relock; + } + + error = msdosfs_lookup_ino(fdvp, NULL, fcnp, &scn, &blkoff); + if (error != 0) { + VOP_UNLOCK(fdvp); + VOP_UNLOCK(tdvp); + goto releout; + } + error = deget(pmp, scn, blkoff, LK_EXCLUSIVE | LK_NOWAIT, &nip); + if (error != 0) { + VOP_UNLOCK(fdvp); + VOP_UNLOCK(tdvp); + if (error != EBUSY) + goto releout; + error = deget(pmp, scn, blkoff, LK_EXCLUSIVE, &nip); + if (error != 0) + goto releout; + vrele(fvp); + fvp = DETOV(nip); + VOP_UNLOCK(fvp); + goto relock; + } + vrele(fvp); + fvp = DETOV(nip); + + error = msdosfs_lookup_ino(tdvp, NULL, tcnp, &scn, &blkoff); + if (error != 0 && error != EJUSTRETURN) { + VOP_UNLOCK(fdvp); + VOP_UNLOCK(tdvp); + VOP_UNLOCK(fvp); + goto releout; + } + if (error == EJUSTRETURN && tvp != NULL) { + vrele(tvp); + tvp = NULL; + } + if (error == 0) { + nip = NULL; + error = deget(pmp, scn, blkoff, LK_EXCLUSIVE | LK_NOWAIT, + &nip); + if (tvp != NULL) { + vrele(tvp); + tvp = NULL; + } + if (error != 0) { + VOP_UNLOCK(fdvp); + VOP_UNLOCK(tdvp); + VOP_UNLOCK(fvp); + if (error != EBUSY) + goto releout; + error = deget(pmp, scn, blkoff, LK_EXCLUSIVE, + &nip); + if (error != 0) + goto releout; + vput(DETOV(nip)); + goto relock; + } + tvp = DETOV(nip); + } + + fdip = VTODE(fdvp); + fip = VTODE(fvp); /* * Be sure we are not renaming ".", "..", or an alias of ".". This @@ -1000,34 +1073,28 @@ * "ls" or "pwd" with the "." directory entry missing, and "cd .." * doesn't work if the ".." entry is missing. */ - if (ip->de_Attributes & ATTR_DIRECTORY) { + if ((fip->de_Attributes & ATTR_DIRECTORY) != 0) { /* * Avoid ".", "..", and aliases of "." for obvious reasons. */ if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') || - dp == ip || - (fcnp->cn_flags & ISDOTDOT) || - (tcnp->cn_flags & ISDOTDOT) || - (ip->de_flag & DE_RENAME)) { - VOP_UNLOCK(fvp); + fdip == fip || + (fcnp->cn_flags & ISDOTDOT) != 0 || + (tcnp->cn_flags & ISDOTDOT) != 0) { error = EINVAL; - goto abortit; + goto unlock; } - ip->de_flag |= DE_RENAME; - doingdirectory++; + doingdirectory = true; } - /* - * When the target exists, both the directory - * and target vnodes are returned locked. - */ - dp = VTODE(tdvp); - xp = tvp ? VTODE(tvp) : NULL; + tdip = VTODE(tdvp); + tip = tvp != NULL ? VTODE(tvp) : NULL; + /* * Remember direntry place to use for destination */ - to_diroffset = dp->de_fndoffset; - to_count = dp->de_fndcnt; + to_diroffset = tdip->de_fndoffset; + to_count = tdip->de_fndcnt; /* * If ".." must be changed (ie the directory gets a new @@ -1040,55 +1107,60 @@ * call to doscheckpath(). */ error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred, tcnp->cn_thread); - VOP_UNLOCK(fvp); - if (VTODE(fdvp)->de_StartCluster != VTODE(tdvp)->de_StartCluster) - newparent = 1; + if (fdip->de_StartCluster != tdip->de_StartCluster) + newparent = true; if (doingdirectory && newparent) { - if (error) /* write access check above */ - goto bad; - if (xp != NULL) - vput(tvp); - /* - * doscheckpath() vput()'s dp, - * so we have to do a relookup afterwards - */ - error = doscheckpath(ip, dp); - if (error) - goto out; + if (error != 0) /* write access check above */ + goto unlock; + lockmgr(&pmp->pm_checkpath_lock, LK_EXCLUSIVE, NULL); + checkpath_locked = true; + error = doscheckpath(fip, tdip, &wait_scn); + if (wait_scn != 0) { + lockmgr(&pmp->pm_checkpath_lock, LK_RELEASE, NULL); + checkpath_locked = false; + VOP_UNLOCK(fdvp); + VOP_UNLOCK(tdvp); + VOP_UNLOCK(fvp); + if (tvp != NULL && tvp != tdvp) + VOP_UNLOCK(tvp); + error = deget(pmp, wait_scn, 0, LK_EXCLUSIVE, + &nip); + if (error == 0) { + vput(DETOV(nip)); + goto relock; + } + } + if (error != 0) + goto unlock; if ((tcnp->cn_flags & SAVESTART) == 0) panic("msdosfs_rename: lost to startdir"); - error = relookup(tdvp, &tvp, tcnp); - if (error) - goto out; - dp = VTODE(tdvp); - xp = tvp ? VTODE(tvp) : NULL; } - if (xp != NULL) { + if (tip != NULL) { /* * Target must be empty if a directory and have no links * to it. Also, ensure source and target are compatible * (both directories, or both not directories). */ - if (xp->de_Attributes & ATTR_DIRECTORY) { - if (!dosdirempty(xp)) { + if ((tip->de_Attributes & ATTR_DIRECTORY) != 0) { + if (!dosdirempty(tip)) { error = ENOTEMPTY; - goto bad; + goto unlock; } if (!doingdirectory) { error = ENOTDIR; - goto bad; + goto unlock; } cache_purge(tdvp); } else if (doingdirectory) { error = EISDIR; - goto bad; + goto unlock; } - error = removede(dp, xp); - if (error) - goto bad; + error = removede(tdip, tip); + if (error != 0) + goto unlock; vput(tvp); - xp = NULL; + tip = NULL; } /* @@ -1096,146 +1168,84 @@ * into the denode and directory entry for the destination * file/directory. */ - error = uniqdosname(VTODE(tdvp), tcnp, toname); - if (error) - goto abortit; + error = uniqdosname(tdip, tcnp, toname); + if (error != 0) + goto unlock; + + from_diroffset = fdip->de_fndoffset; /* - * Since from wasn't locked at various places above, - * have to do a relookup here. + * First write a new entry in the destination + * directory and mark the entry in the source directory + * as deleted. Then move the denode to the correct hash + * chain for its new location in the filesystem. And, if + * we moved a directory, then update its .. entry to point + * to the new parent directory. */ - fcnp->cn_flags &= ~MODMASK; - fcnp->cn_flags |= LOCKPARENT | LOCKLEAF; - if ((fcnp->cn_flags & SAVESTART) == 0) - panic("msdosfs_rename: lost from startdir"); - if (!newparent) - VOP_UNLOCK(tdvp); - if (relookup(fdvp, &fvp, fcnp) == 0) - vrele(fdvp); - if (fvp == NULL) { - /* - * From name has disappeared. - */ - if (doingdirectory) - panic("rename: lost dir entry"); - if (newparent) - VOP_UNLOCK(tdvp); - vrele(tdvp); - vrele(ap->a_fvp); - /* - * fdvp may be locked and has a reference. We need to - * release the lock and reference, unless to and from - * directories are the same. In that case it is already - * unlocked. - */ - if (tdvp != fdvp) - vput(fdvp); - return 0; + memcpy(oldname, fip->de_Name, 11); + memcpy(fip->de_Name, toname, 11); /* update denode */ + tdip->de_fndoffset = to_diroffset; + tdip->de_fndcnt = to_count; + error = createde(fip, tdip, NULL, tcnp); + if (error != 0) { + memcpy(fip->de_Name, oldname, 11); + goto unlock; } - xp = VTODE(fvp); - zp = VTODE(fdvp); - from_diroffset = zp->de_fndoffset; /* - * Ensure that the directory entry still exists and has not - * changed till now. If the source is a file 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 or renamed; this is - * prohibited by the DE_RENAME flag. + * If fip is for a directory, then its name should always + * be "." since it is for the directory entry in the + * directory itself (msdosfs_lookup() always translates + * to the "." entry so as to get a unique denode, except + * for the root directory there are different + * complications). However, we just corrupted its name + * to pass the correct name to createde(). Undo this. */ - if (xp != ip) { - if (doingdirectory) - panic("rename: lost dir entry"); - if (newparent) - VOP_UNLOCK(fdvp); - vrele(ap->a_fvp); - xp = NULL; - } else { - vrele(fvp); - xp = NULL; - - /* - * First write a new entry in the destination - * directory and mark the entry in the source directory - * as deleted. Then move the denode to the correct hash - * chain for its new location in the filesystem. And, if - * we moved a directory, then update its .. entry to point - * to the new parent directory. - */ - memcpy(oldname, ip->de_Name, 11); - memcpy(ip->de_Name, toname, 11); /* update denode */ - dp->de_fndoffset = to_diroffset; - dp->de_fndcnt = to_count; - error = createde(ip, dp, (struct denode **)0, tcnp); - if (error) { - memcpy(ip->de_Name, oldname, 11); - if (newparent) - VOP_UNLOCK(fdvp); - VOP_UNLOCK(fvp); - goto bad; - } - /* - * If ip is for a directory, then its name should always - * be "." since it is for the directory entry in the - * directory itself (msdosfs_lookup() always translates - * to the "." entry so as to get a unique denode, except - * for the root directory there are different - * complications). However, we just corrupted its name - * to pass the correct name to createde(). Undo this. - */ - if ((ip->de_Attributes & ATTR_DIRECTORY) != 0) - memcpy(ip->de_Name, oldname, 11); - ip->de_refcnt++; - zp->de_fndoffset = from_diroffset; - error = removede(zp, ip); - if (error) { - /* XXX should downgrade to ro here, fs is corrupt */ - if (newparent) - VOP_UNLOCK(fdvp); - VOP_UNLOCK(fvp); - goto bad; - } - if (!doingdirectory) { - error = pcbmap(dp, de_cluster(pmp, to_diroffset), 0, - &ip->de_dirclust, 0); - if (error) { - /* XXX should downgrade to ro here, fs is corrupt */ - if (newparent) - VOP_UNLOCK(fdvp); - VOP_UNLOCK(fvp); - goto bad; - } - if (ip->de_dirclust == MSDOSFSROOT) - ip->de_diroffset = to_diroffset; - else - ip->de_diroffset = to_diroffset & pmp->pm_crbomask; + if ((fip->de_Attributes & ATTR_DIRECTORY) != 0) + memcpy(fip->de_Name, oldname, 11); + fip->de_refcnt++; + fdip->de_fndoffset = from_diroffset; + error = removede(fdip, fip); + if (error != 0) { + /* XXX should downgrade to ro here, fs is corrupt */ + goto unlock; + } + if (!doingdirectory) { + error = pcbmap(tdip, de_cluster(pmp, to_diroffset), 0, + &fip->de_dirclust, 0); + if (error != 0) { + /* + * XXX should downgrade to ro here, + * fs is corrupt + */ + goto unlock; } - reinsert(ip); - if (newparent) - VOP_UNLOCK(fdvp); + if (fip->de_dirclust == MSDOSFSROOT) + fip->de_diroffset = to_diroffset; + else + fip->de_diroffset = to_diroffset & pmp->pm_crbomask; } + reinsert(fip); /* * If we moved a directory to a new parent directory, then we must * fixup the ".." entry in the moved directory. */ if (doingdirectory && newparent) { - cn = ip->de_StartCluster; + cn = fip->de_StartCluster; if (cn == MSDOSFSROOT) { /* this should never happen */ panic("msdosfs_rename(): updating .. in root directory?"); } else bn = cntobn(pmp, cn); error = bread(pmp->pm_devvp, bn, pmp->pm_bpcluster, - NOCRED, &bp); - if (error) { + NOCRED, &bp); + if (error != 0) { /* XXX should downgrade to ro here, fs is corrupt */ - VOP_UNLOCK(fvp); - goto bad; + goto unlock; } dotdotp = (struct direntry *)bp->b_data + 1; - pcl = dp->de_StartCluster; + pcl = tdip->de_StartCluster; if (FAT32(pmp) && pcl == pmp->pm_rootdirblk) pcl = MSDOSFSROOT; putushort(dotdotp->deStartCluster, pcl); @@ -1245,8 +1255,7 @@ bdwrite(bp); else if ((error = bwrite(bp)) != 0) { /* XXX should downgrade to ro here, fs is corrupt */ - VOP_UNLOCK(fvp); - goto bad; + goto unlock; } } @@ -1258,17 +1267,38 @@ * namecache entries that were installed for this direntry. */ cache_purge(fvp); - VOP_UNLOCK(fvp); -bad: - if (xp) - vput(tvp); + +unlock: + if (checkpath_locked) + lockmgr(&pmp->pm_checkpath_lock, LK_RELEASE, NULL); + vput(fdvp); + vput(fvp); + if (tvp != NULL) { + if (tvp != tdvp) + vput(tvp); + else + vrele(tvp); + } vput(tdvp); -out: - ip->de_flag &= ~DE_RENAME; + return (error); +releout: + MPASS(!checkpath_locked); + vrele(tdvp); + if (tvp != NULL) + vrele(tvp); + vrele(fdvp); + vrele(fvp); + return (error); +abortit: + if (tdvp == tvp) + vrele(tdvp); + else + vput(tdvp); + if (tvp != NULL) + vput(tvp); vrele(fdvp); vrele(fvp); return (error); - } static struct { @@ -1428,7 +1458,7 @@ * non-empty.) */ error = 0; - if (!dosdirempty(ip) || ip->de_flag & DE_RENAME) { + if (!dosdirempty(ip)) { error = ENOTEMPTY; goto out; } diff --git a/sys/fs/msdosfs/msdosfsmount.h b/sys/fs/msdosfs/msdosfsmount.h --- a/sys/fs/msdosfs/msdosfsmount.h +++ b/sys/fs/msdosfs/msdosfsmount.h @@ -114,6 +114,7 @@ void *pm_d2u; /* DOS->Local iconv handle */ #ifndef MAKEFS struct lock pm_fatlock; /* lockmgr protecting allocations */ + struct lock pm_checkpath_lock; /* protects doscheckpath result */ #endif }; diff --git a/sys/ufs/ffs/ffs_vfsops.c b/sys/ufs/ffs/ffs_vfsops.c --- a/sys/ufs/ffs/ffs_vfsops.c +++ b/sys/ufs/ffs/ffs_vfsops.c @@ -1151,6 +1151,7 @@ else ump->um_check_blkno = NULL; mtx_init(UFS_MTX(ump), "FFS", "FFS Lock", MTX_DEF); + sx_init(&ump->um_checkpath_lock, "uchpth"); ffs_oldfscompat_read(fs, ump, fs->fs_sblockloc); fs->fs_ronly = ronly; fs->fs_active = NULL; @@ -1318,8 +1319,9 @@ g_vfs_close(cp); g_topology_unlock(); } - if (ump) { + if (ump != NULL) { mtx_destroy(UFS_MTX(ump)); + sx_destroy(&ump->um_checkpath_lock); if (mp->mnt_gjprovider != NULL) { free(mp->mnt_gjprovider, M_UFSMNT); mp->mnt_gjprovider = NULL; @@ -1545,6 +1547,7 @@ vrele(ump->um_odevvp); dev_rel(ump->um_dev); mtx_destroy(UFS_MTX(ump)); + sx_destroy(&ump->um_checkpath_lock); if (mp->mnt_gjprovider != NULL) { free(mp->mnt_gjprovider, M_UFSMNT); mp->mnt_gjprovider = NULL; diff --git a/sys/ufs/ufs/ufs_lookup.c b/sys/ufs/ufs/ufs_lookup.c --- a/sys/ufs/ufs/ufs_lookup.c +++ b/sys/ufs/ufs/ufs_lookup.c @@ -1439,7 +1439,8 @@ * Check if source directory is in the path of the target directory. */ int -ufs_checkpath(ino_t source_ino, ino_t parent_ino, struct inode *target, struct ucred *cred, ino_t *wait_ino) +ufs_checkpath(ino_t source_ino, ino_t parent_ino, struct inode *target, + struct ucred *cred, ino_t *wait_ino) { struct mount *mp; struct vnode *tvp, *vp, *vp1; @@ -1449,6 +1450,8 @@ vp = tvp = ITOV(target); mp = vp->v_mount; *wait_ino = 0; + sx_assert(&VFSTOUFS(mp)->um_checkpath_lock, SA_XLOCKED); + if (target->i_number == source_ino) return (EEXIST); if (target->i_number == parent_ino) diff --git a/sys/ufs/ufs/ufs_vnops.c b/sys/ufs/ufs/ufs_vnops.c --- a/sys/ufs/ufs/ufs_vnops.c +++ b/sys/ufs/ufs/ufs_vnops.c @@ -1249,9 +1249,9 @@ struct mount *mp; ino_t ino; seqc_t fdvp_s, fvp_s, tdvp_s, tvp_s; - bool want_seqc_end; + bool checkpath_locked, want_seqc_end; - want_seqc_end = false; + checkpath_locked = want_seqc_end = false; #ifdef INVARIANTS if ((tcnp->cn_flags & HASBUF) == 0 || @@ -1453,6 +1453,9 @@ error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred, tcnp->cn_thread); if (error) goto unlockout; + + sx_xlock(&VFSTOUFS(mp)->um_checkpath_lock); + checkpath_locked = true; error = ufs_checkpath(ino, fdp->i_number, tdp, tcnp->cn_cred, &ino); /* @@ -1460,6 +1463,8 @@ * everything else and VGET before restarting. */ if (ino) { + sx_xunlock(&VFSTOUFS(mp)->um_checkpath_lock); + checkpath_locked = false; VOP_UNLOCK(fdvp); VOP_UNLOCK(fvp); VOP_UNLOCK(tdvp); @@ -1690,6 +1695,9 @@ vn_seqc_write_end(fdvp); } + if (checkpath_locked) + sx_xunlock(&VFSTOUFS(mp)->um_checkpath_lock); + vput(fdvp); vput(fvp); diff --git a/sys/ufs/ufs/ufsmount.h b/sys/ufs/ufs/ufsmount.h --- a/sys/ufs/ufs/ufsmount.h +++ b/sys/ufs/ufs/ufsmount.h @@ -70,6 +70,7 @@ #include #include +#include /* * This structure describes the UFS specific mount structure data. @@ -99,6 +100,8 @@ uint64_t um_maxsymlinklen; /* (c) max size of short symlink */ struct mtx um_lock; /* (c) Protects ufsmount & fs */ + struct sx um_checkpath_lock; /* (c) Protects ufs_checkpath() + result */ pid_t um_fsckpid; /* (u) PID can do fsck sysctl */ struct mount_softdeps *um_softdep; /* (c) softdep mgmt structure */ struct vnode *um_quotas[MAXQUOTAS]; /* (q) pointer to quota files */