Module Name: src
Committed By: dholland
Date: Mon Jul 18 06:45:28 UTC 2011
Modified Files:
src/sys/ufs/ufs: ufs_vnops.c ufs_wapbl.c
Log Message:
Move ufs_wapbl_rename to ufs_vnops.c next to the old ufs_rename.
To generate a diff of this commit:
cvs rdiff -u -r1.198 -r1.199 src/sys/ufs/ufs/ufs_vnops.c
cvs rdiff -u -r1.20 -r1.21 src/sys/ufs/ufs/ufs_wapbl.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/sys/ufs/ufs/ufs_vnops.c
diff -u src/sys/ufs/ufs/ufs_vnops.c:1.198 src/sys/ufs/ufs/ufs_vnops.c:1.199
--- src/sys/ufs/ufs/ufs_vnops.c:1.198 Mon Jul 18 02:35:11 2011
+++ src/sys/ufs/ufs/ufs_vnops.c Mon Jul 18 06:45:27 2011
@@ -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 <sys/cdefs.h>
-__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 @@
* 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)
{
Index: src/sys/ufs/ufs/ufs_wapbl.c
diff -u src/sys/ufs/ufs/ufs_wapbl.c:1.20 src/sys/ufs/ufs/ufs_wapbl.c:1.21
--- src/sys/ufs/ufs/ufs_wapbl.c:1.20 Mon Jul 18 01:14:27 2011
+++ src/sys/ufs/ufs/ufs_wapbl.c Mon Jul 18 06:45:28 2011
@@ -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 <sys/cdefs.h>
-__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 <sys/param.h>
#include <sys/systm.h>
@@ -101,9 +101,6 @@
#include <uvm/uvm.h>
-/* 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 @@
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