From ffbed3d1467f5f8484584f95ef3dff228446901e Mon Sep 17 00:00:00 2001 From: dholland Date: Mon, 18 Jul 2011 06:45:27 +0000 Subject: [PATCH] Move ufs_wapbl_rename to ufs_vnops.c next to the old ufs_rename. --- sys/ufs/ufs/ufs_vnops.c | 870 +++++++++++++++++++++++++++++++++++++++- sys/ufs/ufs/ufs_wapbl.c | 870 +--------------------------------------- 2 files changed, 870 insertions(+), 870 deletions(-) diff --git a/sys/ufs/ufs/ufs_vnops.c b/sys/ufs/ufs/ufs_vnops.c index 32c8d2c85f58..ae11850dca43 100644 --- a/sys/ufs/ufs/ufs_vnops.c +++ b/sys/ufs/ufs/ufs_vnops.c @@ -1,4 +1,4 @@ -/* $NetBSD: ufs_vnops.c,v 1.198 2011/07/18 02:35:11 dholland Exp $ */ +/* $NetBSD: ufs_vnops.c,v 1.199 2011/07/18 06:45:27 dholland Exp $ */ /*- * Copyright (c) 2008 The NetBSD Foundation, Inc. @@ -66,7 +66,7 @@ */ #include -__KERNEL_RCSID(0, "$NetBSD: ufs_vnops.c,v 1.198 2011/07/18 02:35:11 dholland Exp $"); +__KERNEL_RCSID(0, "$NetBSD: ufs_vnops.c,v 1.199 2011/07/18 06:45:27 dholland Exp $"); #if defined(_KERNEL_OPT) #include "opt_ffs.h" @@ -1103,6 +1103,872 @@ ufs_whiteout(void *v) * once.) */ +/* XXX following lifted from ufs_lookup.c */ +#define FSFMT(vp) (((vp)->v_mount->mnt_iflag & IMNT_DTYPE) == 0) + +/* + * Check if either entry referred to by FROM_ULR is within the range + * of entries named by TO_ULR. + */ +static int +ulr_overlap(const struct ufs_lookup_results *from_ulr, + const struct ufs_lookup_results *to_ulr) +{ + doff_t from_start, from_prevstart; + doff_t to_start, to_end; + + /* + * FROM is a DELETE result; offset points to the entry to + * remove and subtracting count gives the previous entry. + */ + from_start = from_ulr->ulr_offset - from_ulr->ulr_count; + from_prevstart = from_ulr->ulr_offset; + + /* + * TO is a RENAME (thus non-DELETE) result; offset points + * to the beginning of a region to write in, and adding + * count gives the end of the region. + */ + to_start = to_ulr->ulr_offset; + to_end = to_ulr->ulr_offset + to_ulr->ulr_count; + + if (from_prevstart >= to_start && from_prevstart < to_end) { + return 1; + } + if (from_start >= to_start && from_start < to_end) { + return 1; + } + return 0; +} + +/* + * Wrapper for relookup that also updates the supplemental results. + */ +static int +do_relookup(struct vnode *dvp, struct ufs_lookup_results *ulr, + struct vnode **vp, struct componentname *cnp) +{ + int error; + + error = relookup(dvp, vp, cnp, 0); + if (error) { + return error; + } + /* update the supplemental reasults */ + *ulr = VTOI(dvp)->i_crap; + UFS_CHECK_CRAPCOUNTER(VTOI(dvp)); + return 0; +} + +/* + * Lock and relookup a sequence of two directories and two children. + * + */ +static int +lock_vnode_sequence(struct vnode *d1, struct ufs_lookup_results *ulr1, + struct vnode **v1_ret, struct componentname *cn1, + int v1_missing_ok, + int overlap_error, + struct vnode *d2, struct ufs_lookup_results *ulr2, + struct vnode **v2_ret, struct componentname *cn2, + int v2_missing_ok) +{ + struct vnode *v1, *v2; + int error; + + KASSERT(d1 != d2); + + vn_lock(d1, LK_EXCLUSIVE | LK_RETRY); + if (VTOI(d1)->i_size == 0) { + /* d1 has been rmdir'd */ + VOP_UNLOCK(d1); + return ENOENT; + } + error = do_relookup(d1, ulr1, &v1, cn1); + if (v1_missing_ok) { + if (error == ENOENT) { + /* + * Note: currently if the name doesn't exist, + * relookup succeeds (it intercepts the + * EJUSTRETURN from VOP_LOOKUP) and sets tvp + * to NULL. Therefore, we will never get + * ENOENT and this branch is not needed. + * However, in a saner future the EJUSTRETURN + * garbage will go away, so let's DTRT. + */ + v1 = NULL; + error = 0; + } + } else { + if (error == 0 && v1 == NULL) { + /* This is what relookup sets if v1 disappeared. */ + error = ENOENT; + } + } + if (error) { + VOP_UNLOCK(d1); + return error; + } + if (v1 && v1 == d2) { + VOP_UNLOCK(d1); + VOP_UNLOCK(v1); + vrele(v1); + return overlap_error; + } + + /* + * The right way to do this is to do lookups without locking + * the results, and lock the results afterwards; then at the + * end we can avoid trying to lock v2 if v2 == v1. + * + * However, for the reasons described in the fdvp == tdvp case + * in rename below, we can't do that safely. So, in the case + * where v1 is not a directory, unlock it and lock it again + * afterwards. This is safe in locking order because a + * non-directory can't be above anything else in the tree. If + * v1 *is* a directory, that's not true, but then because d1 + * != d2, v1 != v2. + */ + if (v1 && v1->v_type != VDIR) { + VOP_UNLOCK(v1); + } + vn_lock(d2, LK_EXCLUSIVE | LK_RETRY); + if (VTOI(d2)->i_size == 0) { + /* d2 has been rmdir'd */ + VOP_UNLOCK(d2); + if (v1 && v1->v_type == VDIR) { + VOP_UNLOCK(v1); + } + VOP_UNLOCK(d1); + if (v1) { + vrele(v1); + } + return ENOENT; + } + error = do_relookup(d2, ulr2, &v2, cn2); + if (v2_missing_ok) { + if (error == ENOENT) { + /* as above */ + v2 = NULL; + error = 0; + } + } else { + if (error == 0 && v2 == NULL) { + /* This is what relookup sets if v2 disappeared. */ + error = ENOENT; + } + } + if (error) { + VOP_UNLOCK(d2); + if (v1 && v1->v_type == VDIR) { + VOP_UNLOCK(v1); + } + VOP_UNLOCK(d1); + if (v1) { + vrele(v1); + } + return error; + } + if (v1 && v1->v_type != VDIR && v1 != v2) { + vn_lock(v1, LK_EXCLUSIVE | LK_RETRY); + } + *v1_ret = v1; + *v2_ret = v2; + return 0; +} + +/* + * Rename vnode operation + * rename("foo", "bar"); + * is essentially + * unlink("bar"); + * link("foo", "bar"); + * unlink("foo"); + * but ``atomically''. Can't do full commit without saving state in the + * inode on disk which isn't feasible at this time. Best we can do is + * always guarantee the target exists. + * + * Basic algorithm is: + * + * 1) Bump link count on source while we're linking it to the + * target. This also ensure the inode won't be deleted out + * from underneath us while we work (it may be truncated by + * a concurrent `trunc' or `open' for creation). + * 2) Link source to destination. If destination already exists, + * delete it first. + * 3) Unlink source reference to inode if still around. If a + * directory was moved and the parent of the destination + * is different from the source, patch the ".." entry in the + * directory. + * + * WAPBL NOTE: wapbl_ufs_rename derived from ufs_rename in ufs_vnops.c + * ufs_vnops.c netbsd cvs revision 1.108 + * which has the berkeley copyright above + * changes introduced to ufs_rename since netbsd cvs revision 1.164 + * will need to be ported into wapbl_ufs_rename + */ +int +wapbl_ufs_rename(void *v) +{ + struct vop_rename_args /* { + struct vnode *a_fdvp; + struct vnode *a_fvp; + struct componentname *a_fcnp; + struct vnode *a_tdvp; + struct vnode *a_tvp; + struct componentname *a_tcnp; + } */ *ap = v; + struct vnode *tvp, *tdvp, *fvp, *fdvp; + struct componentname *tcnp, *fcnp; + struct inode *ip, *txp, *fxp, *tdp, *fdp; + struct mount *mp; + struct direct *newdir; + int doingdirectory, oldparent, newparent, error; + + struct ufs_lookup_results from_ulr, to_ulr; + + tvp = ap->a_tvp; + tdvp = ap->a_tdvp; + fvp = ap->a_fvp; + fdvp = ap->a_fdvp; + tcnp = ap->a_tcnp; + fcnp = ap->a_fcnp; + doingdirectory = oldparent = newparent = error = 0; + + /* save the supplemental lookup results as they currently exist */ + from_ulr = VTOI(fdvp)->i_crap; + to_ulr = VTOI(tdvp)->i_crap; + UFS_CHECK_CRAPCOUNTER(VTOI(fdvp)); + UFS_CHECK_CRAPCOUNTER(VTOI(tdvp)); + + /* + * Owing to VFS oddities we are currently called with tdvp/tvp + * locked and not fdvp/fvp. In a sane world we'd be passed + * tdvp and fdvp only, unlocked, and two name strings. Pretend + * we have a sane world and unlock tdvp and tvp. + */ + VOP_UNLOCK(tdvp); + if (tvp && tvp != tdvp) { + VOP_UNLOCK(tvp); + } + + /* Also pretend we have a sane world and vrele fvp/tvp. */ + vrele(fvp); + fvp = NULL; + if (tvp) { + vrele(tvp); + tvp = NULL; + } + + /* + * Check for cross-device rename. + */ + if (fdvp->v_mount != tdvp->v_mount) { + error = EXDEV; + goto abort; + } + + /* + * Reject "." and ".." + */ + if ((fcnp->cn_flags & ISDOTDOT) || (tcnp->cn_flags & ISDOTDOT) || + (fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') || + (tcnp->cn_namelen == 1 && tcnp->cn_nameptr[0] == '.')) { + error = EINVAL; + goto abort; + } + + /* + * Get locks. + */ + + /* paranoia */ + fcnp->cn_flags |= LOCKPARENT|LOCKLEAF; + tcnp->cn_flags |= LOCKPARENT|LOCKLEAF; + + if (fdvp == tdvp) { + /* One directory. Lock it and relookup both children. */ + vn_lock(fdvp, LK_EXCLUSIVE | LK_RETRY); + + if (VTOI(fdvp)->i_size == 0) { + /* directory has been rmdir'd */ + VOP_UNLOCK(fdvp); + error = ENOENT; + goto abort; + } + + error = do_relookup(fdvp, &from_ulr, &fvp, fcnp); + if (error == 0 && fvp == NULL) { + /* relookup may produce this if fvp disappears */ + error = ENOENT; + } + if (error) { + VOP_UNLOCK(fdvp); + goto abort; + } + + /* + * The right way to do this is to look up both children + * without locking either, and then lock both unless they + * turn out to be the same. However, due to deep-seated + * VFS-level issues all lookups lock the child regardless + * of whether LOCKLEAF is set (if LOCKLEAF is not set, + * the child is locked during lookup and then unlocked) + * so it is not safe to look up tvp while fvp is locked. + * + * Unlocking fvp here temporarily is more or less safe, + * because with the directory locked there's not much + * that can happen to it. However, ideally it wouldn't + * be necessary. XXX. + */ + VOP_UNLOCK(fvp); + /* remember fdvp == tdvp so tdvp is locked */ + error = do_relookup(tdvp, &to_ulr, &tvp, tcnp); + if (error && error != ENOENT) { + VOP_UNLOCK(fdvp); + goto abort; + } + if (error == ENOENT) { + /* + * Note: currently if the name doesn't exist, + * relookup succeeds (it intercepts the + * EJUSTRETURN from VOP_LOOKUP) and sets tvp + * to NULL. Therefore, we will never get + * ENOENT and this branch is not needed. + * However, in a saner future the EJUSTRETURN + * garbage will go away, so let's DTRT. + */ + tvp = NULL; + } + + /* tvp is locked; lock fvp if necessary */ + if (!tvp || tvp != fvp) { + vn_lock(fvp, LK_EXCLUSIVE | LK_RETRY); + } + } else { + int found_fdvp; + struct vnode *illegal_fvp; + + /* + * The source must not be above the destination. (If + * it were, the rename would detach a section of the + * tree.) + * + * Look up the tree from tdvp to see if we find fdvp, + * and if so, return the immediate child of fdvp we're + * under; that must not turn out to be the same as + * fvp. + * + * The per-volume rename lock guarantees that the + * result of this check remains true until we finish + * looking up and locking. + */ + error = ufs_parentcheck(fdvp, tdvp, fcnp->cn_cred, + &found_fdvp, &illegal_fvp); + if (error) { + goto abort; + } + + /* Must lock in tree order. */ + + if (found_fdvp) { + /* fdvp -> fvp -> tdvp -> tvp */ + error = lock_vnode_sequence(fdvp, &from_ulr, + &fvp, fcnp, 0, + EINVAL, + tdvp, &to_ulr, + &tvp, tcnp, 1); + } else { + /* tdvp -> tvp -> fdvp -> fvp */ + error = lock_vnode_sequence(tdvp, &to_ulr, + &tvp, tcnp, 1, + ENOTEMPTY, + fdvp, &from_ulr, + &fvp, fcnp, 0); + } + if (error) { + if (illegal_fvp) { + vrele(illegal_fvp); + } + goto abort; + } + KASSERT(fvp != NULL); + + if (illegal_fvp && fvp == illegal_fvp) { + vrele(illegal_fvp); + error = EINVAL; + goto abort_withlocks; + } + + if (illegal_fvp) { + vrele(illegal_fvp); + } + } + + KASSERT(fdvp && VOP_ISLOCKED(fdvp)); + KASSERT(fvp && VOP_ISLOCKED(fvp)); + KASSERT(tdvp && VOP_ISLOCKED(tdvp)); + KASSERT(tvp == NULL || VOP_ISLOCKED(tvp)); + + /* --- everything is now locked --- */ + + if (tvp && ((VTOI(tvp)->i_flags & (IMMUTABLE | APPEND)) || + (VTOI(tdvp)->i_flags & APPEND))) { + error = EPERM; + goto abort_withlocks; + } + + /* + * Check if just deleting a link name. + */ + if (fvp == tvp) { + if (fvp->v_type == VDIR) { + error = EINVAL; + goto abort_withlocks; + } + + /* Release destination completely. Leave fdvp locked. */ + VOP_ABORTOP(tdvp, tcnp); + if (fdvp != tdvp) { + VOP_UNLOCK(tdvp); + } + VOP_UNLOCK(tvp); + vrele(tdvp); + vrele(tvp); + + /* Delete source. */ + /* XXX: do we really need to relookup again? */ + + /* + * fdvp is still locked, but we just unlocked fvp + * (because fvp == tvp) so just decref fvp + */ + vrele(fvp); + fcnp->cn_flags &= ~(MODMASK); + fcnp->cn_flags |= LOCKPARENT | LOCKLEAF; + fcnp->cn_nameiop = DELETE; + if ((error = relookup(fdvp, &fvp, fcnp, 0))) { + vput(fdvp); + return (error); + } + return (VOP_REMOVE(fdvp, fvp, fcnp)); + } + fdp = VTOI(fdvp); + ip = VTOI(fvp); + if ((nlink_t) ip->i_nlink >= LINK_MAX) { + error = EMLINK; + goto abort_withlocks; + } + if ((ip->i_flags & (IMMUTABLE | APPEND)) || + (fdp->i_flags & APPEND)) { + error = EPERM; + goto abort_withlocks; + } + if ((ip->i_mode & IFMT) == IFDIR) { + /* + * Avoid ".", "..", and aliases of "." for obvious reasons. + */ + if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') || + fdp == ip || + (fcnp->cn_flags & ISDOTDOT) || + (tcnp->cn_flags & ISDOTDOT) || + (ip->i_flag & IN_RENAME)) { + error = EINVAL; + goto abort_withlocks; + } + ip->i_flag |= IN_RENAME; + doingdirectory = 1; + } + oldparent = fdp->i_number; + VN_KNOTE(fdvp, NOTE_WRITE); /* XXXLUKEM/XXX: right place? */ + + /* + * Both the directory + * and target vnodes are locked. + */ + tdp = VTOI(tdvp); + txp = NULL; + if (tvp) + txp = VTOI(tvp); + + mp = fdvp->v_mount; + fstrans_start(mp, FSTRANS_SHARED); + + if (oldparent != tdp->i_number) + newparent = tdp->i_number; + + /* + * If ".." must be changed (ie the directory gets a new + * parent) the user must have write permission in the source + * so as to be able to change "..". + */ + if (doingdirectory && newparent) { + error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred); + if (error) + goto out; + } + + KASSERT(fdvp != tvp); + + if (newparent) { + /* Check for the rename("foo/foo", "foo") case. */ + if (fdvp == tvp) { + error = doingdirectory ? ENOTEMPTY : EISDIR; + goto out; + } + } + + fxp = VTOI(fvp); + fdp = VTOI(fdvp); + + error = UFS_WAPBL_BEGIN(fdvp->v_mount); + if (error) + goto out2; + + /* + * 1) Bump link count while we're moving stuff + * around. If we crash somewhere before + * completing our work, the link count + * may be wrong, but correctable. + */ + ip->i_nlink++; + DIP_ASSIGN(ip, nlink, ip->i_nlink); + ip->i_flag |= IN_CHANGE; + if ((error = UFS_UPDATE(fvp, NULL, NULL, UPDATE_DIROP)) != 0) { + goto bad; + } + + /* + * 2) If target doesn't exist, link the target + * to the source and unlink the source. + * Otherwise, rewrite the target directory + * entry to reference the source inode and + * expunge the original entry's existence. + */ + if (txp == NULL) { + if (tdp->i_dev != ip->i_dev) + panic("rename: EXDEV"); + /* + * Account for ".." in new directory. + * When source and destination have the same + * parent we don't fool with the link count. + */ + if (doingdirectory && newparent) { + if ((nlink_t)tdp->i_nlink >= LINK_MAX) { + error = EMLINK; + goto bad; + } + tdp->i_nlink++; + DIP_ASSIGN(tdp, nlink, tdp->i_nlink); + tdp->i_flag |= IN_CHANGE; + if ((error = UFS_UPDATE(tdvp, NULL, NULL, + UPDATE_DIROP)) != 0) { + tdp->i_nlink--; + DIP_ASSIGN(tdp, nlink, tdp->i_nlink); + tdp->i_flag |= IN_CHANGE; + goto bad; + } + } + newdir = pool_cache_get(ufs_direct_cache, PR_WAITOK); + ufs_makedirentry(ip, tcnp, newdir); + error = ufs_direnter(tdvp, &to_ulr, + NULL, newdir, tcnp, NULL); + pool_cache_put(ufs_direct_cache, newdir); + if (error != 0) { + if (doingdirectory && newparent) { + tdp->i_nlink--; + DIP_ASSIGN(tdp, nlink, tdp->i_nlink); + tdp->i_flag |= IN_CHANGE; + (void)UFS_UPDATE(tdvp, NULL, NULL, + UPDATE_WAIT | UPDATE_DIROP); + } + goto bad; + } + VN_KNOTE(tdvp, NOTE_WRITE); + } else { + if (txp->i_dev != tdp->i_dev || txp->i_dev != ip->i_dev) + panic("rename: EXDEV"); + /* + * Short circuit rename(foo, foo). + */ + if (txp->i_number == ip->i_number) + panic("rename: same file"); + /* + * If the parent directory is "sticky", then the user must + * own the parent directory, or the destination of the rename, + * otherwise the destination may not be changed (except by + * root). This implements append-only directories. + */ + if ((tdp->i_mode & S_ISTXT) && + kauth_authorize_generic(tcnp->cn_cred, + KAUTH_GENERIC_ISSUSER, NULL) != 0 && + kauth_cred_geteuid(tcnp->cn_cred) != tdp->i_uid && + txp->i_uid != kauth_cred_geteuid(tcnp->cn_cred)) { + error = EPERM; + goto bad; + } + /* + * 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 ((txp->i_mode & IFMT) == IFDIR) { + if (txp->i_nlink > 2 || + !ufs_dirempty(txp, tdp->i_number, tcnp->cn_cred)) { + error = ENOTEMPTY; + goto bad; + } + if (!doingdirectory) { + error = ENOTDIR; + goto bad; + } + cache_purge(tdvp); + } else if (doingdirectory) { + error = EISDIR; + goto bad; + } + if ((error = ufs_dirrewrite(tdp, to_ulr.ulr_offset, + txp, ip->i_number, + IFTODT(ip->i_mode), doingdirectory && newparent ? + newparent : doingdirectory, IN_CHANGE | IN_UPDATE)) != 0) + goto bad; + if (doingdirectory) { + /* + * Truncate inode. The only stuff left in the directory + * is "." and "..". The "." reference is inconsequential + * since we are quashing it. We have removed the "." + * reference and the reference in the parent directory, + * but there may be other hard links. + */ + if (!newparent) { + tdp->i_nlink--; + DIP_ASSIGN(tdp, nlink, tdp->i_nlink); + tdp->i_flag |= IN_CHANGE; + UFS_WAPBL_UPDATE(tdvp, NULL, NULL, 0); + } + txp->i_nlink--; + DIP_ASSIGN(txp, nlink, txp->i_nlink); + txp->i_flag |= IN_CHANGE; + if ((error = UFS_TRUNCATE(tvp, (off_t)0, IO_SYNC, + tcnp->cn_cred))) + goto bad; + } + VN_KNOTE(tdvp, NOTE_WRITE); + VN_KNOTE(tvp, NOTE_DELETE); + } + + /* + * Handle case where the directory entry we need to remove, + * which is/was at from_ulr.ulr_offset, or the one before it, + * which is/was at from_ulr.ulr_offset - from_ulr.ulr_count, + * may have been moved when the directory insertion above + * performed compaction. + */ + if (tdp->i_number == fdp->i_number && + ulr_overlap(&from_ulr, &to_ulr)) { + + struct buf *bp; + struct direct *ep; + struct ufsmount *ump = fdp->i_ump; + doff_t curpos; + doff_t endsearch; /* offset to end directory search */ + uint32_t prev_reclen; + int dirblksiz = ump->um_dirblksiz; + const int needswap = UFS_MPNEEDSWAP(ump); + u_long bmask; + int namlen, entryoffsetinblock; + char *dirbuf; + + bmask = fdvp->v_mount->mnt_stat.f_iosize - 1; + + /* + * The fcnp entry will be somewhere between the start of + * compaction (to_ulr.ulr_offset) and the original location + * (from_ulr.ulr_offset). + */ + curpos = to_ulr.ulr_offset; + endsearch = from_ulr.ulr_offset + from_ulr.ulr_reclen; + entryoffsetinblock = 0; + + /* + * Get the directory block containing the start of + * compaction. + */ + error = ufs_blkatoff(fdvp, (off_t)to_ulr.ulr_offset, &dirbuf, + &bp, false); + if (error) + goto bad; + + /* + * Keep existing ulr_count (length of previous record) + * for the case where compaction did not include the + * previous entry but started at the from-entry. + */ + prev_reclen = from_ulr.ulr_count; + + while (curpos < endsearch) { + uint32_t reclen; + + /* + * If necessary, get the next directory block. + * + * dholland 7/13/11 to the best of my understanding + * this should never happen; compaction occurs only + * within single blocks. I think. + */ + if ((curpos & bmask) == 0) { + if (bp != NULL) + brelse(bp, 0); + error = ufs_blkatoff(fdvp, (off_t)curpos, + &dirbuf, &bp, false); + if (error) + goto bad; + entryoffsetinblock = 0; + } + + KASSERT(bp != NULL); + ep = (struct direct *)(dirbuf + entryoffsetinblock); + reclen = ufs_rw16(ep->d_reclen, needswap); + +#if (BYTE_ORDER == LITTLE_ENDIAN) + if (FSFMT(fdvp) && needswap == 0) + namlen = ep->d_type; + else + namlen = ep->d_namlen; +#else + if (FSFMT(fdvp) && needswap != 0) + namlen = ep->d_type; + else + namlen = ep->d_namlen; +#endif + if ((ep->d_ino != 0) && + (ufs_rw32(ep->d_ino, needswap) != WINO) && + (namlen == fcnp->cn_namelen) && + memcmp(ep->d_name, fcnp->cn_nameptr, namlen) == 0) { + from_ulr.ulr_reclen = reclen; + break; + } + curpos += reclen; + entryoffsetinblock += reclen; + prev_reclen = reclen; + } + + from_ulr.ulr_offset = curpos; + from_ulr.ulr_count = prev_reclen; + + KASSERT(curpos <= endsearch); + + /* + * If ulr_offset points to start of a directory block, + * clear ulr_count so ufs_dirremove() doesn't try to + * merge free space over a directory block boundary. + */ + if ((from_ulr.ulr_offset & (dirblksiz - 1)) == 0) + from_ulr.ulr_count = 0; + + brelse(bp, 0); + } + + /* + * 3) Unlink the source. + */ + +#if 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 IRENAME + * flag ensures that it cannot be moved by another rename or removed + * by a rmdir. + */ +#endif + KASSERT(fxp == ip); + + /* + * If the source is a directory with a new parent, the link + * count of the old parent directory must be decremented and + * ".." set to point to the new parent. + */ + if (doingdirectory && newparent) { + KASSERT(fdp != NULL); + ufs_dirrewrite(fxp, mastertemplate.dot_reclen, + fdp, newparent, DT_DIR, 0, IN_CHANGE); + cache_purge(fdvp); + } + error = ufs_dirremove(fdvp, &from_ulr, + fxp, fcnp->cn_flags, 0); + fxp->i_flag &= ~IN_RENAME; + + VN_KNOTE(fvp, NOTE_RENAME); + goto done; + + out: + goto out2; + + /* exit routines from steps 1 & 2 */ + bad: + if (doingdirectory) + ip->i_flag &= ~IN_RENAME; + ip->i_nlink--; + DIP_ASSIGN(ip, nlink, ip->i_nlink); + ip->i_flag |= IN_CHANGE; + ip->i_flag &= ~IN_RENAME; + UFS_WAPBL_UPDATE(fvp, NULL, NULL, 0); + done: + UFS_WAPBL_END(fdvp->v_mount); + out2: + /* + * clear IN_RENAME - some exit paths happen too early to go + * through the cleanup done in the "bad" case above, so we + * always do this mini-cleanup here. + */ + ip->i_flag &= ~IN_RENAME; + + VOP_UNLOCK(fdvp); + if (tdvp != fdvp) { + VOP_UNLOCK(tdvp); + } + VOP_UNLOCK(fvp); + if (tvp && tvp != fvp) { + VOP_UNLOCK(tvp); + } + + vrele(fdvp); + vrele(tdvp); + vrele(fvp); + if (tvp) { + vrele(tvp); + } + + fstrans_done(mp); + return (error); + + abort_withlocks: + VOP_UNLOCK(fdvp); + if (tdvp != fdvp) { + VOP_UNLOCK(tdvp); + } + VOP_UNLOCK(fvp); + if (tvp && tvp != fvp) { + VOP_UNLOCK(tvp); + } + + abort: + VOP_ABORTOP(fdvp, fcnp); /* XXX, why not in NFS? */ + VOP_ABORTOP(tdvp, tcnp); /* XXX, why not in NFS? */ + vrele(tdvp); + if (tvp) { + vrele(tvp); + } + vrele(fdvp); + if (fvp) { + vrele(fvp); + } + return (error); +} + int ufs_rename(void *v) { diff --git a/sys/ufs/ufs/ufs_wapbl.c b/sys/ufs/ufs/ufs_wapbl.c index ac3886a24bb9..aecdf82cfe39 100644 --- a/sys/ufs/ufs/ufs_wapbl.c +++ b/sys/ufs/ufs/ufs_wapbl.c @@ -1,4 +1,4 @@ -/* $NetBSD: ufs_wapbl.c,v 1.20 2011/07/18 01:14:27 dholland Exp $ */ +/* $NetBSD: ufs_wapbl.c,v 1.21 2011/07/18 06:45:28 dholland Exp $ */ /*- * Copyright (c) 2003,2006,2008 The NetBSD Foundation, Inc. @@ -66,7 +66,7 @@ */ #include -__KERNEL_RCSID(0, "$NetBSD: ufs_wapbl.c,v 1.20 2011/07/18 01:14:27 dholland Exp $"); +__KERNEL_RCSID(0, "$NetBSD: ufs_wapbl.c,v 1.21 2011/07/18 06:45:28 dholland Exp $"); #include #include @@ -101,9 +101,6 @@ __KERNEL_RCSID(0, "$NetBSD: ufs_wapbl.c,v 1.20 2011/07/18 01:14:27 dholland Exp #include -/* XXX following lifted from ufs_lookup.c */ -#define FSFMT(vp) (((vp)->v_mount->mnt_iflag & IMNT_DTYPE) == 0) - /* * A virgin directory (no blushing please). */ @@ -112,869 +109,6 @@ static const struct dirtemplate mastertemplate = { 0, DIRBLKSIZ - 12, DT_DIR, 2, ".." }; -/* - * Check if either entry referred to by FROM_ULR is within the range - * of entries named by TO_ULR. - */ -static int -ulr_overlap(const struct ufs_lookup_results *from_ulr, - const struct ufs_lookup_results *to_ulr) -{ - doff_t from_start, from_prevstart; - doff_t to_start, to_end; - - /* - * FROM is a DELETE result; offset points to the entry to - * remove and subtracting count gives the previous entry. - */ - from_start = from_ulr->ulr_offset - from_ulr->ulr_count; - from_prevstart = from_ulr->ulr_offset; - - /* - * TO is a RENAME (thus non-DELETE) result; offset points - * to the beginning of a region to write in, and adding - * count gives the end of the region. - */ - to_start = to_ulr->ulr_offset; - to_end = to_ulr->ulr_offset + to_ulr->ulr_count; - - if (from_prevstart >= to_start && from_prevstart < to_end) { - return 1; - } - if (from_start >= to_start && from_start < to_end) { - return 1; - } - return 0; -} - -/* - * Wrapper for relookup that also updates the supplemental results. - */ -static int -do_relookup(struct vnode *dvp, struct ufs_lookup_results *ulr, - struct vnode **vp, struct componentname *cnp) -{ - int error; - - error = relookup(dvp, vp, cnp, 0); - if (error) { - return error; - } - /* update the supplemental reasults */ - *ulr = VTOI(dvp)->i_crap; - UFS_CHECK_CRAPCOUNTER(VTOI(dvp)); - return 0; -} - -/* - * Lock and relookup a sequence of two directories and two children. - * - */ -static int -lock_vnode_sequence(struct vnode *d1, struct ufs_lookup_results *ulr1, - struct vnode **v1_ret, struct componentname *cn1, - int v1_missing_ok, - int overlap_error, - struct vnode *d2, struct ufs_lookup_results *ulr2, - struct vnode **v2_ret, struct componentname *cn2, - int v2_missing_ok) -{ - struct vnode *v1, *v2; - int error; - - KASSERT(d1 != d2); - - vn_lock(d1, LK_EXCLUSIVE | LK_RETRY); - if (VTOI(d1)->i_size == 0) { - /* d1 has been rmdir'd */ - VOP_UNLOCK(d1); - return ENOENT; - } - error = do_relookup(d1, ulr1, &v1, cn1); - if (v1_missing_ok) { - if (error == ENOENT) { - /* - * Note: currently if the name doesn't exist, - * relookup succeeds (it intercepts the - * EJUSTRETURN from VOP_LOOKUP) and sets tvp - * to NULL. Therefore, we will never get - * ENOENT and this branch is not needed. - * However, in a saner future the EJUSTRETURN - * garbage will go away, so let's DTRT. - */ - v1 = NULL; - error = 0; - } - } else { - if (error == 0 && v1 == NULL) { - /* This is what relookup sets if v1 disappeared. */ - error = ENOENT; - } - } - if (error) { - VOP_UNLOCK(d1); - return error; - } - if (v1 && v1 == d2) { - VOP_UNLOCK(d1); - VOP_UNLOCK(v1); - vrele(v1); - return overlap_error; - } - - /* - * The right way to do this is to do lookups without locking - * the results, and lock the results afterwards; then at the - * end we can avoid trying to lock v2 if v2 == v1. - * - * However, for the reasons described in the fdvp == tdvp case - * in rename below, we can't do that safely. So, in the case - * where v1 is not a directory, unlock it and lock it again - * afterwards. This is safe in locking order because a - * non-directory can't be above anything else in the tree. If - * v1 *is* a directory, that's not true, but then because d1 - * != d2, v1 != v2. - */ - if (v1 && v1->v_type != VDIR) { - VOP_UNLOCK(v1); - } - vn_lock(d2, LK_EXCLUSIVE | LK_RETRY); - if (VTOI(d2)->i_size == 0) { - /* d2 has been rmdir'd */ - VOP_UNLOCK(d2); - if (v1 && v1->v_type == VDIR) { - VOP_UNLOCK(v1); - } - VOP_UNLOCK(d1); - if (v1) { - vrele(v1); - } - return ENOENT; - } - error = do_relookup(d2, ulr2, &v2, cn2); - if (v2_missing_ok) { - if (error == ENOENT) { - /* as above */ - v2 = NULL; - error = 0; - } - } else { - if (error == 0 && v2 == NULL) { - /* This is what relookup sets if v2 disappeared. */ - error = ENOENT; - } - } - if (error) { - VOP_UNLOCK(d2); - if (v1 && v1->v_type == VDIR) { - VOP_UNLOCK(v1); - } - VOP_UNLOCK(d1); - if (v1) { - vrele(v1); - } - return error; - } - if (v1 && v1->v_type != VDIR && v1 != v2) { - vn_lock(v1, LK_EXCLUSIVE | LK_RETRY); - } - *v1_ret = v1; - *v2_ret = v2; - return 0; -} - -/* - * Rename vnode operation - * rename("foo", "bar"); - * is essentially - * unlink("bar"); - * link("foo", "bar"); - * unlink("foo"); - * but ``atomically''. Can't do full commit without saving state in the - * inode on disk which isn't feasible at this time. Best we can do is - * always guarantee the target exists. - * - * Basic algorithm is: - * - * 1) Bump link count on source while we're linking it to the - * target. This also ensure the inode won't be deleted out - * from underneath us while we work (it may be truncated by - * a concurrent `trunc' or `open' for creation). - * 2) Link source to destination. If destination already exists, - * delete it first. - * 3) Unlink source reference to inode if still around. If a - * directory was moved and the parent of the destination - * is different from the source, patch the ".." entry in the - * directory. - * - * WAPBL NOTE: wapbl_ufs_rename derived from ufs_rename in ufs_vnops.c - * ufs_vnops.c netbsd cvs revision 1.108 - * which has the berkeley copyright above - * changes introduced to ufs_rename since netbsd cvs revision 1.164 - * will need to be ported into wapbl_ufs_rename - */ -int -wapbl_ufs_rename(void *v) -{ - struct vop_rename_args /* { - struct vnode *a_fdvp; - struct vnode *a_fvp; - struct componentname *a_fcnp; - struct vnode *a_tdvp; - struct vnode *a_tvp; - struct componentname *a_tcnp; - } */ *ap = v; - struct vnode *tvp, *tdvp, *fvp, *fdvp; - struct componentname *tcnp, *fcnp; - struct inode *ip, *txp, *fxp, *tdp, *fdp; - struct mount *mp; - struct direct *newdir; - int doingdirectory, oldparent, newparent, error; - - struct ufs_lookup_results from_ulr, to_ulr; - - tvp = ap->a_tvp; - tdvp = ap->a_tdvp; - fvp = ap->a_fvp; - fdvp = ap->a_fdvp; - tcnp = ap->a_tcnp; - fcnp = ap->a_fcnp; - doingdirectory = oldparent = newparent = error = 0; - - /* save the supplemental lookup results as they currently exist */ - from_ulr = VTOI(fdvp)->i_crap; - to_ulr = VTOI(tdvp)->i_crap; - UFS_CHECK_CRAPCOUNTER(VTOI(fdvp)); - UFS_CHECK_CRAPCOUNTER(VTOI(tdvp)); - - /* - * Owing to VFS oddities we are currently called with tdvp/tvp - * locked and not fdvp/fvp. In a sane world we'd be passed - * tdvp and fdvp only, unlocked, and two name strings. Pretend - * we have a sane world and unlock tdvp and tvp. - */ - VOP_UNLOCK(tdvp); - if (tvp && tvp != tdvp) { - VOP_UNLOCK(tvp); - } - - /* Also pretend we have a sane world and vrele fvp/tvp. */ - vrele(fvp); - fvp = NULL; - if (tvp) { - vrele(tvp); - tvp = NULL; - } - - /* - * Check for cross-device rename. - */ - if (fdvp->v_mount != tdvp->v_mount) { - error = EXDEV; - goto abort; - } - - /* - * Reject "." and ".." - */ - if ((fcnp->cn_flags & ISDOTDOT) || (tcnp->cn_flags & ISDOTDOT) || - (fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') || - (tcnp->cn_namelen == 1 && tcnp->cn_nameptr[0] == '.')) { - error = EINVAL; - goto abort; - } - - /* - * Get locks. - */ - - /* paranoia */ - fcnp->cn_flags |= LOCKPARENT|LOCKLEAF; - tcnp->cn_flags |= LOCKPARENT|LOCKLEAF; - - if (fdvp == tdvp) { - /* One directory. Lock it and relookup both children. */ - vn_lock(fdvp, LK_EXCLUSIVE | LK_RETRY); - - if (VTOI(fdvp)->i_size == 0) { - /* directory has been rmdir'd */ - VOP_UNLOCK(fdvp); - error = ENOENT; - goto abort; - } - - error = do_relookup(fdvp, &from_ulr, &fvp, fcnp); - if (error == 0 && fvp == NULL) { - /* relookup may produce this if fvp disappears */ - error = ENOENT; - } - if (error) { - VOP_UNLOCK(fdvp); - goto abort; - } - - /* - * The right way to do this is to look up both children - * without locking either, and then lock both unless they - * turn out to be the same. However, due to deep-seated - * VFS-level issues all lookups lock the child regardless - * of whether LOCKLEAF is set (if LOCKLEAF is not set, - * the child is locked during lookup and then unlocked) - * so it is not safe to look up tvp while fvp is locked. - * - * Unlocking fvp here temporarily is more or less safe, - * because with the directory locked there's not much - * that can happen to it. However, ideally it wouldn't - * be necessary. XXX. - */ - VOP_UNLOCK(fvp); - /* remember fdvp == tdvp so tdvp is locked */ - error = do_relookup(tdvp, &to_ulr, &tvp, tcnp); - if (error && error != ENOENT) { - VOP_UNLOCK(fdvp); - goto abort; - } - if (error == ENOENT) { - /* - * Note: currently if the name doesn't exist, - * relookup succeeds (it intercepts the - * EJUSTRETURN from VOP_LOOKUP) and sets tvp - * to NULL. Therefore, we will never get - * ENOENT and this branch is not needed. - * However, in a saner future the EJUSTRETURN - * garbage will go away, so let's DTRT. - */ - tvp = NULL; - } - - /* tvp is locked; lock fvp if necessary */ - if (!tvp || tvp != fvp) { - vn_lock(fvp, LK_EXCLUSIVE | LK_RETRY); - } - } else { - int found_fdvp; - struct vnode *illegal_fvp; - - /* - * The source must not be above the destination. (If - * it were, the rename would detach a section of the - * tree.) - * - * Look up the tree from tdvp to see if we find fdvp, - * and if so, return the immediate child of fdvp we're - * under; that must not turn out to be the same as - * fvp. - * - * The per-volume rename lock guarantees that the - * result of this check remains true until we finish - * looking up and locking. - */ - error = ufs_parentcheck(fdvp, tdvp, fcnp->cn_cred, - &found_fdvp, &illegal_fvp); - if (error) { - goto abort; - } - - /* Must lock in tree order. */ - - if (found_fdvp) { - /* fdvp -> fvp -> tdvp -> tvp */ - error = lock_vnode_sequence(fdvp, &from_ulr, - &fvp, fcnp, 0, - EINVAL, - tdvp, &to_ulr, - &tvp, tcnp, 1); - } else { - /* tdvp -> tvp -> fdvp -> fvp */ - error = lock_vnode_sequence(tdvp, &to_ulr, - &tvp, tcnp, 1, - ENOTEMPTY, - fdvp, &from_ulr, - &fvp, fcnp, 0); - } - if (error) { - if (illegal_fvp) { - vrele(illegal_fvp); - } - goto abort; - } - KASSERT(fvp != NULL); - - if (illegal_fvp && fvp == illegal_fvp) { - vrele(illegal_fvp); - error = EINVAL; - goto abort_withlocks; - } - - if (illegal_fvp) { - vrele(illegal_fvp); - } - } - - KASSERT(fdvp && VOP_ISLOCKED(fdvp)); - KASSERT(fvp && VOP_ISLOCKED(fvp)); - KASSERT(tdvp && VOP_ISLOCKED(tdvp)); - KASSERT(tvp == NULL || VOP_ISLOCKED(tvp)); - - /* --- everything is now locked --- */ - - if (tvp && ((VTOI(tvp)->i_flags & (IMMUTABLE | APPEND)) || - (VTOI(tdvp)->i_flags & APPEND))) { - error = EPERM; - goto abort_withlocks; - } - - /* - * Check if just deleting a link name. - */ - if (fvp == tvp) { - if (fvp->v_type == VDIR) { - error = EINVAL; - goto abort_withlocks; - } - - /* Release destination completely. Leave fdvp locked. */ - VOP_ABORTOP(tdvp, tcnp); - if (fdvp != tdvp) { - VOP_UNLOCK(tdvp); - } - VOP_UNLOCK(tvp); - vrele(tdvp); - vrele(tvp); - - /* Delete source. */ - /* XXX: do we really need to relookup again? */ - - /* - * fdvp is still locked, but we just unlocked fvp - * (because fvp == tvp) so just decref fvp - */ - vrele(fvp); - fcnp->cn_flags &= ~(MODMASK); - fcnp->cn_flags |= LOCKPARENT | LOCKLEAF; - fcnp->cn_nameiop = DELETE; - if ((error = relookup(fdvp, &fvp, fcnp, 0))) { - vput(fdvp); - return (error); - } - return (VOP_REMOVE(fdvp, fvp, fcnp)); - } - fdp = VTOI(fdvp); - ip = VTOI(fvp); - if ((nlink_t) ip->i_nlink >= LINK_MAX) { - error = EMLINK; - goto abort_withlocks; - } - if ((ip->i_flags & (IMMUTABLE | APPEND)) || - (fdp->i_flags & APPEND)) { - error = EPERM; - goto abort_withlocks; - } - if ((ip->i_mode & IFMT) == IFDIR) { - /* - * Avoid ".", "..", and aliases of "." for obvious reasons. - */ - if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') || - fdp == ip || - (fcnp->cn_flags & ISDOTDOT) || - (tcnp->cn_flags & ISDOTDOT) || - (ip->i_flag & IN_RENAME)) { - error = EINVAL; - goto abort_withlocks; - } - ip->i_flag |= IN_RENAME; - doingdirectory = 1; - } - oldparent = fdp->i_number; - VN_KNOTE(fdvp, NOTE_WRITE); /* XXXLUKEM/XXX: right place? */ - - /* - * Both the directory - * and target vnodes are locked. - */ - tdp = VTOI(tdvp); - txp = NULL; - if (tvp) - txp = VTOI(tvp); - - mp = fdvp->v_mount; - fstrans_start(mp, FSTRANS_SHARED); - - if (oldparent != tdp->i_number) - newparent = tdp->i_number; - - /* - * If ".." must be changed (ie the directory gets a new - * parent) the user must have write permission in the source - * so as to be able to change "..". - */ - if (doingdirectory && newparent) { - error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred); - if (error) - goto out; - } - - KASSERT(fdvp != tvp); - - if (newparent) { - /* Check for the rename("foo/foo", "foo") case. */ - if (fdvp == tvp) { - error = doingdirectory ? ENOTEMPTY : EISDIR; - goto out; - } - } - - fxp = VTOI(fvp); - fdp = VTOI(fdvp); - - error = UFS_WAPBL_BEGIN(fdvp->v_mount); - if (error) - goto out2; - - /* - * 1) Bump link count while we're moving stuff - * around. If we crash somewhere before - * completing our work, the link count - * may be wrong, but correctable. - */ - ip->i_nlink++; - DIP_ASSIGN(ip, nlink, ip->i_nlink); - ip->i_flag |= IN_CHANGE; - if ((error = UFS_UPDATE(fvp, NULL, NULL, UPDATE_DIROP)) != 0) { - goto bad; - } - - /* - * 2) If target doesn't exist, link the target - * to the source and unlink the source. - * Otherwise, rewrite the target directory - * entry to reference the source inode and - * expunge the original entry's existence. - */ - if (txp == NULL) { - if (tdp->i_dev != ip->i_dev) - panic("rename: EXDEV"); - /* - * Account for ".." in new directory. - * When source and destination have the same - * parent we don't fool with the link count. - */ - if (doingdirectory && newparent) { - if ((nlink_t)tdp->i_nlink >= LINK_MAX) { - error = EMLINK; - goto bad; - } - tdp->i_nlink++; - DIP_ASSIGN(tdp, nlink, tdp->i_nlink); - tdp->i_flag |= IN_CHANGE; - if ((error = UFS_UPDATE(tdvp, NULL, NULL, - UPDATE_DIROP)) != 0) { - tdp->i_nlink--; - DIP_ASSIGN(tdp, nlink, tdp->i_nlink); - tdp->i_flag |= IN_CHANGE; - goto bad; - } - } - newdir = pool_cache_get(ufs_direct_cache, PR_WAITOK); - ufs_makedirentry(ip, tcnp, newdir); - error = ufs_direnter(tdvp, &to_ulr, - NULL, newdir, tcnp, NULL); - pool_cache_put(ufs_direct_cache, newdir); - if (error != 0) { - if (doingdirectory && newparent) { - tdp->i_nlink--; - DIP_ASSIGN(tdp, nlink, tdp->i_nlink); - tdp->i_flag |= IN_CHANGE; - (void)UFS_UPDATE(tdvp, NULL, NULL, - UPDATE_WAIT | UPDATE_DIROP); - } - goto bad; - } - VN_KNOTE(tdvp, NOTE_WRITE); - } else { - if (txp->i_dev != tdp->i_dev || txp->i_dev != ip->i_dev) - panic("rename: EXDEV"); - /* - * Short circuit rename(foo, foo). - */ - if (txp->i_number == ip->i_number) - panic("rename: same file"); - /* - * If the parent directory is "sticky", then the user must - * own the parent directory, or the destination of the rename, - * otherwise the destination may not be changed (except by - * root). This implements append-only directories. - */ - if ((tdp->i_mode & S_ISTXT) && - kauth_authorize_generic(tcnp->cn_cred, - KAUTH_GENERIC_ISSUSER, NULL) != 0 && - kauth_cred_geteuid(tcnp->cn_cred) != tdp->i_uid && - txp->i_uid != kauth_cred_geteuid(tcnp->cn_cred)) { - error = EPERM; - goto bad; - } - /* - * 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 ((txp->i_mode & IFMT) == IFDIR) { - if (txp->i_nlink > 2 || - !ufs_dirempty(txp, tdp->i_number, tcnp->cn_cred)) { - error = ENOTEMPTY; - goto bad; - } - if (!doingdirectory) { - error = ENOTDIR; - goto bad; - } - cache_purge(tdvp); - } else if (doingdirectory) { - error = EISDIR; - goto bad; - } - if ((error = ufs_dirrewrite(tdp, to_ulr.ulr_offset, - txp, ip->i_number, - IFTODT(ip->i_mode), doingdirectory && newparent ? - newparent : doingdirectory, IN_CHANGE | IN_UPDATE)) != 0) - goto bad; - if (doingdirectory) { - /* - * Truncate inode. The only stuff left in the directory - * is "." and "..". The "." reference is inconsequential - * since we are quashing it. We have removed the "." - * reference and the reference in the parent directory, - * but there may be other hard links. - */ - if (!newparent) { - tdp->i_nlink--; - DIP_ASSIGN(tdp, nlink, tdp->i_nlink); - tdp->i_flag |= IN_CHANGE; - UFS_WAPBL_UPDATE(tdvp, NULL, NULL, 0); - } - txp->i_nlink--; - DIP_ASSIGN(txp, nlink, txp->i_nlink); - txp->i_flag |= IN_CHANGE; - if ((error = UFS_TRUNCATE(tvp, (off_t)0, IO_SYNC, - tcnp->cn_cred))) - goto bad; - } - VN_KNOTE(tdvp, NOTE_WRITE); - VN_KNOTE(tvp, NOTE_DELETE); - } - - /* - * Handle case where the directory entry we need to remove, - * which is/was at from_ulr.ulr_offset, or the one before it, - * which is/was at from_ulr.ulr_offset - from_ulr.ulr_count, - * may have been moved when the directory insertion above - * performed compaction. - */ - if (tdp->i_number == fdp->i_number && - ulr_overlap(&from_ulr, &to_ulr)) { - - struct buf *bp; - struct direct *ep; - struct ufsmount *ump = fdp->i_ump; - doff_t curpos; - doff_t endsearch; /* offset to end directory search */ - uint32_t prev_reclen; - int dirblksiz = ump->um_dirblksiz; - const int needswap = UFS_MPNEEDSWAP(ump); - u_long bmask; - int namlen, entryoffsetinblock; - char *dirbuf; - - bmask = fdvp->v_mount->mnt_stat.f_iosize - 1; - - /* - * The fcnp entry will be somewhere between the start of - * compaction (to_ulr.ulr_offset) and the original location - * (from_ulr.ulr_offset). - */ - curpos = to_ulr.ulr_offset; - endsearch = from_ulr.ulr_offset + from_ulr.ulr_reclen; - entryoffsetinblock = 0; - - /* - * Get the directory block containing the start of - * compaction. - */ - error = ufs_blkatoff(fdvp, (off_t)to_ulr.ulr_offset, &dirbuf, - &bp, false); - if (error) - goto bad; - - /* - * Keep existing ulr_count (length of previous record) - * for the case where compaction did not include the - * previous entry but started at the from-entry. - */ - prev_reclen = from_ulr.ulr_count; - - while (curpos < endsearch) { - uint32_t reclen; - - /* - * If necessary, get the next directory block. - * - * dholland 7/13/11 to the best of my understanding - * this should never happen; compaction occurs only - * within single blocks. I think. - */ - if ((curpos & bmask) == 0) { - if (bp != NULL) - brelse(bp, 0); - error = ufs_blkatoff(fdvp, (off_t)curpos, - &dirbuf, &bp, false); - if (error) - goto bad; - entryoffsetinblock = 0; - } - - KASSERT(bp != NULL); - ep = (struct direct *)(dirbuf + entryoffsetinblock); - reclen = ufs_rw16(ep->d_reclen, needswap); - -#if (BYTE_ORDER == LITTLE_ENDIAN) - if (FSFMT(fdvp) && needswap == 0) - namlen = ep->d_type; - else - namlen = ep->d_namlen; -#else - if (FSFMT(fdvp) && needswap != 0) - namlen = ep->d_type; - else - namlen = ep->d_namlen; -#endif - if ((ep->d_ino != 0) && - (ufs_rw32(ep->d_ino, needswap) != WINO) && - (namlen == fcnp->cn_namelen) && - memcmp(ep->d_name, fcnp->cn_nameptr, namlen) == 0) { - from_ulr.ulr_reclen = reclen; - break; - } - curpos += reclen; - entryoffsetinblock += reclen; - prev_reclen = reclen; - } - - from_ulr.ulr_offset = curpos; - from_ulr.ulr_count = prev_reclen; - - KASSERT(curpos <= endsearch); - - /* - * If ulr_offset points to start of a directory block, - * clear ulr_count so ufs_dirremove() doesn't try to - * merge free space over a directory block boundary. - */ - if ((from_ulr.ulr_offset & (dirblksiz - 1)) == 0) - from_ulr.ulr_count = 0; - - brelse(bp, 0); - } - - /* - * 3) Unlink the source. - */ - -#if 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 IRENAME - * flag ensures that it cannot be moved by another rename or removed - * by a rmdir. - */ -#endif - KASSERT(fxp == ip); - - /* - * If the source is a directory with a new parent, the link - * count of the old parent directory must be decremented and - * ".." set to point to the new parent. - */ - if (doingdirectory && newparent) { - KASSERT(fdp != NULL); - ufs_dirrewrite(fxp, mastertemplate.dot_reclen, - fdp, newparent, DT_DIR, 0, IN_CHANGE); - cache_purge(fdvp); - } - error = ufs_dirremove(fdvp, &from_ulr, - fxp, fcnp->cn_flags, 0); - fxp->i_flag &= ~IN_RENAME; - - VN_KNOTE(fvp, NOTE_RENAME); - goto done; - - out: - goto out2; - - /* exit routines from steps 1 & 2 */ - bad: - if (doingdirectory) - ip->i_flag &= ~IN_RENAME; - ip->i_nlink--; - DIP_ASSIGN(ip, nlink, ip->i_nlink); - ip->i_flag |= IN_CHANGE; - ip->i_flag &= ~IN_RENAME; - UFS_WAPBL_UPDATE(fvp, NULL, NULL, 0); - done: - UFS_WAPBL_END(fdvp->v_mount); - out2: - /* - * clear IN_RENAME - some exit paths happen too early to go - * through the cleanup done in the "bad" case above, so we - * always do this mini-cleanup here. - */ - ip->i_flag &= ~IN_RENAME; - - VOP_UNLOCK(fdvp); - if (tdvp != fdvp) { - VOP_UNLOCK(tdvp); - } - VOP_UNLOCK(fvp); - if (tvp && tvp != fvp) { - VOP_UNLOCK(tvp); - } - - vrele(fdvp); - vrele(tdvp); - vrele(fvp); - if (tvp) { - vrele(tvp); - } - - fstrans_done(mp); - return (error); - - abort_withlocks: - VOP_UNLOCK(fdvp); - if (tdvp != fdvp) { - VOP_UNLOCK(tdvp); - } - VOP_UNLOCK(fvp); - if (tvp && tvp != fvp) { - VOP_UNLOCK(tvp); - } - - abort: - VOP_ABORTOP(fdvp, fcnp); /* XXX, why not in NFS? */ - VOP_ABORTOP(tdvp, tcnp); /* XXX, why not in NFS? */ - vrele(tdvp); - if (tvp) { - vrele(tvp); - } - vrele(fdvp); - if (fvp) { - vrele(fvp); - } - return (error); -} - #ifdef WAPBL_DEBUG_INODES #error WAPBL_DEBUG_INODES: not functional before ufs_wapbl.c is updated void