Move ufs_wapbl_rename to ufs_vnops.c next to the old ufs_rename.
This commit is contained in:
parent
b4152f8512
commit
ffbed3d146
@ -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.
|
* Copyright (c) 2008 The NetBSD Foundation, Inc.
|
||||||
@ -66,7 +66,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <sys/cdefs.h>
|
#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)
|
#if defined(_KERNEL_OPT)
|
||||||
#include "opt_ffs.h"
|
#include "opt_ffs.h"
|
||||||
@ -1103,6 +1103,872 @@ ufs_whiteout(void *v)
|
|||||||
* once.)
|
* 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
|
int
|
||||||
ufs_rename(void *v)
|
ufs_rename(void *v)
|
||||||
{
|
{
|
||||||
|
@ -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.
|
* Copyright (c) 2003,2006,2008 The NetBSD Foundation, Inc.
|
||||||
@ -66,7 +66,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <sys/cdefs.h>
|
#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/param.h>
|
||||||
#include <sys/systm.h>
|
#include <sys/systm.h>
|
||||||
@ -101,9 +101,6 @@ __KERNEL_RCSID(0, "$NetBSD: ufs_wapbl.c,v 1.20 2011/07/18 01:14:27 dholland Exp
|
|||||||
|
|
||||||
#include <uvm/uvm.h>
|
#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).
|
* A virgin directory (no blushing please).
|
||||||
*/
|
*/
|
||||||
@ -112,869 +109,6 @@ static const struct dirtemplate mastertemplate = {
|
|||||||
0, DIRBLKSIZ - 12, DT_DIR, 2, ".."
|
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
|
#ifdef WAPBL_DEBUG_INODES
|
||||||
#error WAPBL_DEBUG_INODES: not functional before ufs_wapbl.c is updated
|
#error WAPBL_DEBUG_INODES: not functional before ufs_wapbl.c is updated
|
||||||
void
|
void
|
||||||
|
Loading…
Reference in New Issue
Block a user