Esempio n. 1
0
static int
zfs_replay_remove(void *arg1, char *arg2, boolean_t byteswap)
{
	zfsvfs_t *zfsvfs = (zfsvfs_t *)arg1;
	lr_remove_t *lr = (lr_remove_t *)arg2;
	char *name = (char *)(lr + 1);	/* name follows lr_remove_t */
	znode_t *dzp;
	int error;
	int vflg = 0;

	if (byteswap)
		byteswap_uint64_array(lr, sizeof (*lr));

	if ((error = zfs_zget(zfsvfs, lr->lr_doid, &dzp)) != 0)
		return (error);

	if (lr->lr_common.lrc_txtype & TX_CI)
		vflg |= FIGNORECASE;

	switch ((int)lr->lr_common.lrc_txtype) {
	case TX_REMOVE:
		error = VOP_REMOVE(ZTOV(dzp), name, kcred, NULL, vflg);
		break;
	case TX_RMDIR:
		error = VOP_RMDIR(ZTOV(dzp), name, NULL, kcred, NULL, vflg);
		break;
	default:
		error = ENOTSUP;
	}

	VN_RELE(ZTOV(dzp));

	return (error);
}
Esempio n. 2
0
static int zfsfuse_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
{
	if(strlen(name) >= MAXNAMELEN)
		return ENAMETOOLONG;

	vfs_t *vfs = (vfs_t *) fuse_req_userdata(req);
	zfsvfs_t *zfsvfs = vfs->vfs_data;

	ZFS_ENTER(zfsvfs);

	znode_t *znode;

	int error = zfs_zget(zfsvfs, parent, &znode, B_FALSE);
	if(error) {
		ZFS_EXIT(zfsvfs);
		/* If the inode we are trying to get was recently deleted
		   dnode_hold_impl will return EEXIST instead of ENOENT */
		return error == EEXIST ? ENOENT : error;
	}

	ASSERT(znode != NULL);
	vnode_t *dvp = ZTOV(znode);
	ASSERT(dvp != NULL);

	cred_t cred;
	zfsfuse_getcred(req, &cred);

	error = VOP_REMOVE(dvp, (char *) name, &cred, NULL, 0);

	VN_RELE(dvp);
	ZFS_EXIT(zfsvfs);

	return error;
}
Esempio n. 3
0
/*
 *	union_remove:
 *
 *	Remove the specified cnp.  The dvp and vp are passed to us locked
 *	and must remain locked on return.
 *
 * union_remove(struct vnode *a_dvp, struct vnode *a_vp,
 *		struct componentname *a_cnp)
 */
static int
union_remove(struct vop_old_remove_args *ap)
{
	struct union_node *dun = VTOUNION(ap->a_dvp);
	struct union_node *un = VTOUNION(ap->a_vp);
	struct componentname *cnp = ap->a_cnp;
	struct thread *td = cnp->cn_td;
	struct vnode *uppervp;
	struct vnode *upperdvp;
	int error;

	if ((upperdvp = union_lock_upper(dun, td)) == NULLVP)
		panic("union remove: null upper vnode");

	if ((uppervp = union_lock_upper(un, td)) != NULLVP) {
		if (union_dowhiteout(un, cnp->cn_cred, td))
			cnp->cn_flags |= CNP_DOWHITEOUT;
		error = VOP_REMOVE(upperdvp, uppervp, cnp);
#if 0
		/* XXX */
		if (!error)
			union_removed_upper(un);
#endif
		union_unlock_upper(uppervp, td);
	} else {
		error = union_mkwhiteout(
			    MOUNTTOUNIONMOUNT(ap->a_dvp->v_mount),
			    upperdvp, ap->a_cnp, un->un_path);
	}
	union_unlock_upper(upperdvp, td);
	return (error);
}
Esempio n. 4
0
static int
auto_remove(
	vnode_t *dvp,
	char *nm,
	cred_t *cred,
	caller_context_t *ct,
	int flags)
{
	vnode_t *newvp;
	int error;

	AUTOFS_DPRINT((4, "auto_remove dvp %p nm %s\n", (void *)dvp, nm));

	if (error = auto_trigger_mount(dvp, cred, &newvp))
		goto done;

	if (newvp != NULL) {
		/*
		 * Node is now mounted on.
		 */
		if (vn_is_readonly(newvp))
			error = EROFS;
		else
			error = VOP_REMOVE(newvp, nm, cred, ct, flags);
		VN_RELE(newvp);
	} else
		error = ENOSYS;

done:
	AUTOFS_DPRINT((5, "auto_remove: error=%d\n", error));
	return (error);
}
Esempio n. 5
0
static int
zfs_replay_remove(zfsvfs_t *zfsvfs, void *data, boolean_t byteswap)
{
#ifndef TODO_OSV
	kprintf("TX_REMOVE\n");
	return EOPNOTSUPP;
#else
	lr_remove_t *lr = data;
	char *name = (char *)(lr + 1);	/* name follows lr_remove_t */
	znode_t *dzp;
	struct componentname cn;
	vnode_t *vp;
	int error;
	int vflg = 0;

	if (byteswap)
		byteswap_uint64_array(lr, sizeof (*lr));

	if ((error = zfs_zget(zfsvfs, lr->lr_doid, &dzp)) != 0)
		return (error);

	if (lr->lr_common.lrc_txtype & TX_CI)
		vflg |= FIGNORECASE;
	cn.cn_nameptr = name;
	cn.cn_namelen = strlen(name);
	cn.cn_nameiop = DELETE;
	cn.cn_flags = ISLASTCN | SAVENAME;
	cn.cn_lkflags = LK_EXCLUSIVE | LK_RETRY;
	cn.cn_cred = kcred;
	cn.cn_thread = curthread;
	vn_lock(ZTOV(dzp), LK_EXCLUSIVE | LK_RETRY);
	error = VOP_LOOKUP(ZTOV(dzp), &vp, &cn);
	if (error != 0) {
		VOP_UNLOCK(ZTOV(dzp), 0);
		goto fail;
	}

	switch ((int)lr->lr_common.lrc_txtype) {
	case TX_REMOVE:
		error = VOP_REMOVE(ZTOV(dzp), vp, &cn /*,vflg*/);
		break;
	case TX_RMDIR:
		error = VOP_RMDIR(ZTOV(dzp), vp, &cn /*,vflg*/);
		break;
	default:
		error = ENOTSUP;
	}
	vput(vp);
	VOP_UNLOCK(ZTOV(dzp), 0);

fail:
	VN_RELE(ZTOV(dzp));

	return (error);
#endif
}
Esempio n. 6
0
int
RUMP_VOP_REMOVE(struct vnode *dvp,
    struct vnode *vp,
    struct componentname *cnp)
{
	int error;

	rump_schedule();
	error = VOP_REMOVE(dvp, vp, cnp);
	rump_unschedule();

	return error;
}
Esempio n. 7
0
extern int
vnode_iop_unlink(
    INODE_T *dir,
    DENT_T *dent
)
{
    int err = 0;
    VNODE_T *obj;
    struct unlink_ctx ctx;
    CALL_DATA_T cd;
    struct dentry *peer;

    ASSERT_I_SEM_MINE(dent->d_inode);
    ASSERT_I_SEM_MINE(dir);
    ASSERT(MDKI_INOISMVFS(dir));

    if (!S_ISDIR(dir->i_mode)) {
        /* bogus */
        return -ENOTDIR;
    }

    peer = vnlayer_dentry_peer(dent);

    mdki_linux_init_call_data(&cd);
    ctx.dentry = dent;
    ctx.done = FALSE;
    if (dent->d_inode && MDKI_INOISMVFS(dent->d_inode))
        obj = ITOV(dent->d_inode);      /* no extra reference (be careful) */
    else
        obj = NULL;
    err = VOP_REMOVE(ITOV(dir), obj, (char *)dent->d_name.name, &cd, &ctx);
    /* XXX pullup attributes on removed object, if it's not gone yet? */
    err = mdki_errno_unix_to_linux(err);
    mdki_linux_destroy_call_data(&cd);
    /* XXX Don't d_delete(dentry), our caller will do that */

    if (peer != NULL) {
        /*
         * unhash peer name in other mode.  Don't d_delete in case
         * we're racing
         */
        if (err == 0)
            d_drop(peer);
        VNODE_DPUT(peer);
    }

    return err;
}
Esempio n. 8
0
static int
xattr_dir_remove(vnode_t *dvp, char *name, cred_t *cr, caller_context_t *ct,
    int flags)
{
	vnode_t *pvp;
	int error;

	if (is_sattr_name(name)) {
		return (EACCES);
	}

	error = xattr_dir_realdir(dvp, &pvp, LOOKUP_XATTR, cr, ct);
	if (error == 0) {
		error = VOP_REMOVE(pvp, name, cr, ct, flags);
	}
	return (error);
}
Esempio n. 9
0
/* Does most of the work for remove(). */
int
vfs_remove(char *path)
{
	struct vnode *dir;
	char name[NAME_MAX+1];
	int result;
	
	result = vfs_lookparent(path, &dir, name, sizeof(name));
	if (result) {
		return result;
	}

	result = VOP_REMOVE(dir, name);
	VOP_DECREF(dir);

	return result;
}
Esempio n. 10
0
int
smb_vop_remove(vnode_t *dvp, char *name, int flags, cred_t *cr)
{
	int error;
	int option_flags = 0;
	char *np = name;
	char namebuf[MAXNAMELEN];

	if (flags & SMB_IGNORE_CASE)
		option_flags = FIGNORECASE;

	if (flags & SMB_CATIA)
		np = smb_vop_catia_v5tov4(name, namebuf, sizeof (namebuf));

	error = VOP_REMOVE(dvp, np, cr, &smb_ct, option_flags);

	return (error);
}
Esempio n. 11
0
STATIC int
linvfs_unlink(
	struct inode	*dir,
	struct dentry	*dentry)
{
	struct inode	*inode;
	vnode_t		*dvp;	/* directory containing name to remove */
	int		error;

	inode = dentry->d_inode;
	dvp = LINVFS_GET_VP(dir);

	VOP_REMOVE(dvp, dentry, NULL, error);
	if (!error) {
		validate_fields(dir);	/* For size only */
		validate_fields(inode);
	}

	return -error;
}
Esempio n. 12
0
static int
unionfs_remove(void *v)
{
	struct vop_remove_args *ap = v;
	int		error;
	struct unionfs_node *dunp;
	struct unionfs_node *unp;
	struct unionfs_mount *ump;
	struct vnode   *udvp;
	struct vnode   *uvp;
	struct vnode   *lvp;
	struct componentname *cnp;

	UNIONFS_INTERNAL_DEBUG("unionfs_remove: enter\n");

	error = 0;
	dunp = VTOUNIONFS(ap->a_dvp);
	unp = VTOUNIONFS(ap->a_vp);
	udvp = dunp->un_uppervp;
	uvp = unp->un_uppervp;
	lvp = unp->un_lowervp;
	cnp = ap->a_cnp;

	if (udvp == NULLVP)
		return (EROFS);

	if (uvp != NULLVP) {
		ump = MOUNTTOUNIONFSMOUNT(ap->a_vp->v_mount);
		if (ump->um_whitemode == UNIONFS_WHITE_ALWAYS || lvp != NULLVP)
			cnp->cn_flags |= DOWHITEOUT;
		error = VOP_REMOVE(udvp, uvp, cnp);
	} else if (lvp != NULLVP)
		error = unionfs_mkwhiteout(udvp, cnp, unp->un_path);

	UNIONFS_INTERNAL_DEBUG("unionfs_remove: leave (%d)\n", error);

	return (error);
}
STATIC inline void
cleanup_inode(
	vnode_t		*dvp,
	vnode_t		*vp,
	struct dentry	*dentry,	
	int		mode)
{
	struct dentry   teardown = {};
	int             err2;

	/* Oh, the horror.
	 * If we can't add the ACL or we fail in 
	 * linvfs_init_security we must back out.
	 * ENOSPC can hit here, among other things.
	 */
	teardown.d_inode = LINVFS_GET_IP(vp);
	teardown.d_name = dentry->d_name;

	if (S_ISDIR(mode))
	  	VOP_RMDIR(dvp, &teardown, NULL, err2);
	else
		VOP_REMOVE(dvp, &teardown, NULL, err2);
	VN_RELE(vp);
}
Esempio n. 14
0
STATIC int
linvfs_mknod(
	struct inode	*dir,
	struct dentry	*dentry,
	int		mode,
	int		rdev)
{
	struct inode	*ip;
	vattr_t		va;
	vnode_t		*vp = NULL, *dvp = LINVFS_GET_VP(dir);
	xfs_acl_t	*default_acl = NULL;
	attrexists_t	test_default_acl = _ACL_DEFAULT_EXISTS;
	int		error;

	if (test_default_acl && test_default_acl(dvp)) {
		if (!_ACL_ALLOC(default_acl))
			return -ENOMEM;
		if (!_ACL_GET_DEFAULT(dvp, default_acl)) {
			_ACL_FREE(default_acl);
			default_acl = NULL;
		}
	}

#ifdef CONFIG_XFS_POSIX_ACL
	/*
	 * Conditionally compiled so that the ACL base kernel changes can be
	 * split out into separate patches - remove this once MS_POSIXACL is
	 * accepted, or some other way to implement this exists.
	 */
	if (IS_POSIXACL(dir) && !default_acl && has_fs_struct(current))
		mode &= ~current->fs->umask;
#endif

	memset(&va, 0, sizeof(va));
	va.va_mask = XFS_AT_TYPE|XFS_AT_MODE;
	va.va_type = IFTOVT(mode);
	va.va_mode = mode;

	switch (mode & S_IFMT) {
	case S_IFCHR: case S_IFBLK: case S_IFIFO: case S_IFSOCK:
		va.va_rdev = XFS_MKDEV(MAJOR(rdev), MINOR(rdev));
		va.va_mask |= XFS_AT_RDEV;
		/*FALLTHROUGH*/
	case S_IFREG:
		VOP_CREATE(dvp, dentry, &va, &vp, NULL, error);
		break;
	case S_IFDIR:
		VOP_MKDIR(dvp, dentry, &va, &vp, NULL, error);
		break;
	default:
		error = EINVAL;
		break;
	}

	if (default_acl) {
		if (!error) {
			error = _ACL_INHERIT(vp, &va, default_acl);
			if (!error) {
				VMODIFY(vp);
			} else {
				struct dentry	teardown = {};
				int		err2;

				/* Oh, the horror.
				 * If we can't add the ACL we must back out.
				 * ENOSPC can hit here, among other things.
				 */
				teardown.d_inode = ip = LINVFS_GET_IP(vp);
				teardown.d_name = dentry->d_name;

				vn_mark_bad(vp);

				if (S_ISDIR(mode))
					VOP_RMDIR(dvp, &teardown, NULL, err2);
				else
					VOP_REMOVE(dvp, &teardown, NULL, err2);
				VN_RELE(vp);
			}
		}
		_ACL_FREE(default_acl);
	}

	if (!error) {
		ASSERT(vp);
		ip = LINVFS_GET_IP(vp);

		if (S_ISCHR(mode) || S_ISBLK(mode))
			ip->i_rdev = to_kdev_t(rdev);
		else if (S_ISDIR(mode))
			validate_fields(ip);
		d_instantiate(dentry, ip);
		validate_fields(dir);
	}
	return -error;
}
Esempio n. 15
0
/*
 * Rename system call.
 * 	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.
 */
int
ext2fs_rename(void *v)
{
	struct vop_rename_args  *ap = v;
	struct vnode *tvp = ap->a_tvp;
	struct vnode *tdvp = ap->a_tdvp;
	struct vnode *fvp = ap->a_fvp;
	struct vnode *fdvp = ap->a_fdvp;
	struct componentname *tcnp = ap->a_tcnp;
	struct componentname *fcnp = ap->a_fcnp;
	struct inode *ip, *xp, *dp;
	struct proc *p = fcnp->cn_proc;
	struct ext2fs_dirtemplate dirbuf;
	/* struct timespec ts; */
	int doingdirectory = 0, oldparent = 0, newparent = 0;
	int error = 0;
	u_char namlen;

#ifdef DIAGNOSTIC
	if ((tcnp->cn_flags & HASBUF) == 0 ||
	    (fcnp->cn_flags & HASBUF) == 0)
		panic("ext2fs_rename: no name");
#endif
	/*
	 * Check for cross-device rename.
	 */
	if ((fvp->v_mount != tdvp->v_mount) ||
	    (tvp && (fvp->v_mount != tvp->v_mount))) {
		error = EXDEV;
abortit:
		VOP_ABORTOP(tdvp, tcnp); /* XXX, why not in NFS? */
		if (tdvp == tvp)
			vrele(tdvp);
		else
			vput(tdvp);
		if (tvp)
			vput(tvp);
		VOP_ABORTOP(fdvp, fcnp); /* XXX, why not in NFS? */
		vrele(fdvp);
		vrele(fvp);
		return (error);
	}

	/*
	 * Check if just deleting a link name.
	 */
	if (tvp && ((VTOI(tvp)->i_e2fs_flags & (EXT2_IMMUTABLE | EXT2_APPEND)) ||
	    (VTOI(tdvp)->i_e2fs_flags & EXT2_APPEND))) {
		error = EPERM;
		goto abortit;
	}
	if (fvp == tvp) {
		if (fvp->v_type == VDIR) {
			error = EINVAL;
			goto abortit;
		}

		/* Release destination completely. */
		VOP_ABORTOP(tdvp, tcnp);
		vput(tdvp);
		vput(tvp);

		/* Delete source. */
		vrele(fdvp);
		vrele(fvp);
		fcnp->cn_flags &= ~MODMASK;
		fcnp->cn_flags |= LOCKPARENT | LOCKLEAF;
		if ((fcnp->cn_flags & SAVESTART) == 0)
			panic("ext2fs_rename: lost from startdir");
		fcnp->cn_nameiop = DELETE;
		(void) vfs_relookup(fdvp, &fvp, fcnp);
		return (VOP_REMOVE(fdvp, fvp, fcnp));
	}
	if ((error = vn_lock(fvp, LK_EXCLUSIVE, p)) != 0)
		goto abortit;
	dp = VTOI(fdvp);
	ip = VTOI(fvp);
	if ((nlink_t)ip->i_e2fs_nlink >= LINK_MAX) {
		VOP_UNLOCK(fvp, 0);
		error = EMLINK;
		goto abortit;
	}
	if ((ip->i_e2fs_flags & (EXT2_IMMUTABLE | EXT2_APPEND)) ||
		(dp->i_e2fs_flags & EXT2_APPEND)) {
		VOP_UNLOCK(fvp, 0);
		error = EPERM;
		goto abortit;
	}
	if ((ip->i_e2fs_mode & IFMT) == IFDIR) {
		error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred);
		if (!error && tvp)
			error = VOP_ACCESS(tvp, VWRITE, tcnp->cn_cred);
		if (error) {
			VOP_UNLOCK(fvp, 0);
			error = EACCES;
			goto abortit;
		}
		/*
		 * Avoid ".", "..", and aliases of "." for obvious reasons.
		 */
		if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') ||
		    dp == ip ||
			(fcnp->cn_flags&ISDOTDOT) ||
			(tcnp->cn_flags & ISDOTDOT) ||
		    (ip->i_flag & IN_RENAME)) {
			VOP_UNLOCK(fvp, 0);
			error = EINVAL;
			goto abortit;
		}
		ip->i_flag |= IN_RENAME;
		oldparent = dp->i_number;
		doingdirectory++;
	}
	vrele(fdvp);

	/*
	 * When the target exists, both the directory
	 * and target vnodes are returned locked.
	 */
	dp = VTOI(tdvp);
	xp = NULL;
	if (tvp)
		xp = VTOI(tvp);

	/*
	 * 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_e2fs_nlink++;
	ip->i_flag |= IN_CHANGE;
	if ((error = ext2fs_update(ip, NULL, NULL, 1)) != 0) {
		VOP_UNLOCK(fvp, 0);
		goto bad;
	}

	/*
	 * If ".." must be changed (ie the directory gets a new
	 * parent) then the source directory must not be in the
	 * directory hierarchy above the target, as this would
	 * orphan everything below the source directory. Also
	 * the user must have write permission in the source so
	 * as to be able to change "..". We must repeat the call 
	 * to namei, as the parent directory is unlocked by the
	 * call to checkpath().
	 */
	error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred);
	VOP_UNLOCK(fvp, 0);
	if (oldparent != dp->i_number)
		newparent = dp->i_number;
	if (doingdirectory && newparent) {
		if (error)	/* write access check above */
			goto bad;
		if (xp != NULL)
			vput(tvp);
		error = ext2fs_checkpath(ip, dp, tcnp->cn_cred);
		if (error != 0)
			goto out;
		if ((tcnp->cn_flags & SAVESTART) == 0)
			panic("ext2fs_rename: lost to startdir");
		if ((error = vfs_relookup(tdvp, &tvp, tcnp)) != 0)
			goto out;
		dp = VTOI(tdvp);
		xp = NULL;
		if (tvp)
			xp = VTOI(tvp);
	}
	/*
	 * 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 (xp == NULL) {
		if (dp->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)dp->i_e2fs_nlink >= LINK_MAX) {
				error = EMLINK;
				goto bad;
			}
			dp->i_e2fs_nlink++;
			dp->i_flag |= IN_CHANGE;
			if ((error = ext2fs_update(dp, NULL, NULL, 1)) != 0)
				goto bad;
		}
		error = ext2fs_direnter(ip, tdvp, tcnp);
		if (error != 0) {
			if (doingdirectory && newparent) {
				dp->i_e2fs_nlink--;
				dp->i_flag |= IN_CHANGE;
				(void)ext2fs_update(dp, NULL, NULL, 1);
			}
			goto bad;
		}
		vput(tdvp);
	} else {
		if (xp->i_dev != dp->i_dev || xp->i_dev != ip->i_dev)
			panic("rename: EXDEV");
		/*
		 * Short circuit rename(foo, foo).
		 */
		if (xp->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 ((dp->i_e2fs_mode & S_ISTXT) && tcnp->cn_cred->cr_uid != 0 &&
		    tcnp->cn_cred->cr_uid != dp->i_e2fs_uid &&
		    xp->i_e2fs_uid != tcnp->cn_cred->cr_uid) {
			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 ((xp->i_e2fs_mode & IFMT) == IFDIR) {
			if (!ext2fs_dirempty(xp, dp->i_number, tcnp->cn_cred) ||
				xp->i_e2fs_nlink > 2) {
				error = ENOTEMPTY;
				goto bad;
			}
			if (!doingdirectory) {
				error = ENOTDIR;
				goto bad;
			}
			cache_purge(tdvp);
		} else if (doingdirectory) {
			error = EISDIR;
			goto bad;
		}
		error = ext2fs_dirrewrite(dp, ip, tcnp);
		if (error != 0)
			goto bad;
		/*
		 * If the target directory is in the same
		 * directory as the source directory,
		 * decrement the link count on the parent
		 * of the target directory.
		 */
		 if (doingdirectory && !newparent) {
			dp->i_e2fs_nlink--;
			dp->i_flag |= IN_CHANGE;
		}
		vput(tdvp);
		/*
		 * Adjust the link count of the target to
		 * reflect the dirrewrite above.  If this is
		 * a directory it is empty and there are
		 * no links to it, so we can squash the inode and
		 * any space associated with it.  We disallowed
		 * renaming over top of a directory with links to
		 * it above, as the remaining link would point to
		 * a directory without "." or ".." entries.
		 */
		xp->i_e2fs_nlink--;
		if (doingdirectory) {
			if (--xp->i_e2fs_nlink != 0)
				panic("rename: linked directory");
			error = ext2fs_truncate(xp, (off_t)0, IO_SYNC,
			    tcnp->cn_cred);
		}
		xp->i_flag |= IN_CHANGE;
		vput(tvp);
		xp = NULL;
	}

	/*
	 * 3) Unlink the source.
	 */
	fcnp->cn_flags &= ~MODMASK;
	fcnp->cn_flags |= LOCKPARENT | LOCKLEAF;
	if ((fcnp->cn_flags & SAVESTART) == 0)
		panic("ext2fs_rename: lost from startdir");
	(void) vfs_relookup(fdvp, &fvp, fcnp);
	if (fvp != NULL) {
		xp = VTOI(fvp);
		dp = VTOI(fdvp);
	} else {
		/*
		 * From name has disappeared.
		 */
		if (doingdirectory)
			panic("ext2fs_rename: lost dir entry");
		vrele(ap->a_fvp);
		return (0);
	}
	/*
	 * Ensure that the directory entry still exists and has not
	 * changed while the new name has been entered. If the source is
	 * a file then the entry may have been unlinked or renamed. In
	 * either case there is no further work to be done. If the source
	 * is a directory then it cannot have been rmdir'ed; its link
	 * count of three would cause a rmdir to fail with ENOTEMPTY.
	 * The IRENAME flag ensures that it cannot be moved by another
	 * rename.
	 */
	if (xp != ip) {
		if (doingdirectory)
			panic("ext2fs_rename: lost dir entry");
	} else {
		/*
		 * 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) {
			dp->i_e2fs_nlink--;
			dp->i_flag |= IN_CHANGE;
			error = vn_rdwr(UIO_READ, fvp, (caddr_t)&dirbuf,
				sizeof (struct ext2fs_dirtemplate), (off_t)0,
				UIO_SYSSPACE, IO_NODELOCKED, 
				tcnp->cn_cred, NULL, curproc);
			if (error == 0) {
					namlen = dirbuf.dotdot_namlen;
				if (namlen != 2 ||
				    dirbuf.dotdot_name[0] != '.' ||
				    dirbuf.dotdot_name[1] != '.') {
					ufs_dirbad(xp, (doff_t)12,
					    "ext2fs_rename: mangled dir");
				} else {
					dirbuf.dotdot_ino = h2fs32(newparent);
					(void) vn_rdwr(UIO_WRITE, fvp,
					    (caddr_t)&dirbuf,
					    sizeof (struct dirtemplate),
					    (off_t)0, UIO_SYSSPACE,
					    IO_NODELOCKED|IO_SYNC,
					    tcnp->cn_cred, NULL, curproc);
					cache_purge(fdvp);
				}
			}
		}
		error = ext2fs_dirremove(fdvp, fcnp);
		if (!error) {
			xp->i_e2fs_nlink--;
			xp->i_flag |= IN_CHANGE;
		}
		xp->i_flag &= ~IN_RENAME;
	}
	if (dp)
		vput(fdvp);
	if (xp)
		vput(fvp);
	vrele(ap->a_fvp);
	return (error);

bad:
	if (xp)
		vput(ITOV(xp));
	vput(ITOV(dp));
out:
	if (doingdirectory)
		ip->i_flag &= ~IN_RENAME;
	if (vn_lock(fvp, LK_EXCLUSIVE, p) == 0) {
		ip->i_e2fs_nlink--;
		ip->i_flag |= IN_CHANGE;
		vput(fvp);
	} else
		vrele(fvp);
	return (error);
}
Esempio n. 16
0
/*ARGSUSED4*/
static int
devfs_setattr(
	struct vnode *vp,
	struct vattr *vap,
	int flags,
	struct cred *cr,
	caller_context_t *ct)
{
	struct dv_node	*dv = VTODV(vp);
	struct dv_node	*ddv;
	struct vnode	*dvp;
	struct vattr	*map;
	long int	mask;
	int		error = 0;
	struct vattr	*free_vattr = NULL;
	struct vattr	*vattrp = NULL;
	mperm_t		mp;
	int		persist;

	/*
	 * Message goes to console only. Otherwise, the message
	 * causes devfs_getattr to be invoked again... infinite loop
	 */
	dcmn_err2(("?devfs_setattr %s\n", dv->dv_name));
	ASSERT(dv->dv_attr || dv->dv_attrvp);

	if (!(vp->v_type == VDIR || vp->v_type == VCHR || vp->v_type == VBLK)) {
		cmn_err(CE_WARN,	/* panic ? */
		    "?%s: getattr on vnode type %d", dvnm, vp->v_type);
		return (ENOENT);
	}

	if (vap->va_mask & AT_NOSET)
		return (EINVAL);

	/*
	 * If we are changing something we don't care about
	 * the persistence of, return success.
	 */
	if ((vap->va_mask &
	    (AT_MODE|AT_UID|AT_GID|AT_ATIME|AT_MTIME)) == 0)
		return (0);

	/*
	 * If driver overrides fs perm, disallow chmod
	 * and do not create attribute nodes.
	 */
	if (dv->dv_flags & DV_NO_FSPERM) {
		ASSERT(dv->dv_attr);
		if (vap->va_mask & (AT_MODE | AT_UID | AT_GID))
			return (EPERM);
		if ((vap->va_mask & (AT_ATIME|AT_MTIME)) == 0)
			return (0);
		rw_enter(&dv->dv_contents, RW_WRITER);
		if (vap->va_mask & AT_ATIME)
			dv->dv_attr->va_atime = vap->va_atime;
		if (vap->va_mask & AT_MTIME)
			dv->dv_attr->va_mtime = vap->va_mtime;
		rw_exit(&dv->dv_contents);
		return (0);
	}

	/*
	 * Directories are always created but device nodes are
	 * only used to persist non-default permissions.
	 */
	if (vp->v_type == VDIR) {
		ASSERT(dv->dv_attr || dv->dv_attrvp);
		return (devfs_setattr_dir(dv, vp, vap, flags, cr));
	}

	/*
	 * Allocate now before we take any locks
	 */
	vattrp = kmem_zalloc(sizeof (*vattrp), KM_SLEEP);

	/* to ensure consistency, single thread setting of attributes */
	rw_enter(&dv->dv_contents, RW_WRITER);

	/*
	 * We don't need to create an attribute node
	 * to persist access or modification times.
	 */
	persist = (vap->va_mask & (AT_MODE | AT_UID | AT_GID));

	/*
	 * If persisting something, get the default permissions
	 * for this minor to compare against what the attributes
	 * are now being set to.  Default ordering is:
	 *	- minor_perm match for this minor
	 *	- mode supplied by ddi_create_priv_minor_node
	 *	- devfs defaults
	 */
	if (persist) {
		if (dev_minorperm(dv->dv_devi, dv->dv_name, &mp) != 0) {
			mp.mp_uid = dv_vattr_file.va_uid;
			mp.mp_gid = dv_vattr_file.va_gid;
			mp.mp_mode = dv_vattr_file.va_mode;
			if (dv->dv_flags & DV_DFLT_MODE) {
				ASSERT((dv->dv_dflt_mode & ~S_IAMB) == 0);
				mp.mp_mode &= ~S_IAMB;
				mp.mp_mode |= dv->dv_dflt_mode;
				dcmn_err5(("%s: setattr priv default 0%o\n",
				    dv->dv_name, mp.mp_mode));
			} else {
				dcmn_err5(("%s: setattr devfs default 0%o\n",
				    dv->dv_name, mp.mp_mode));
			}
		} else {
			dcmn_err5(("%s: setattr minor perm default 0%o\n",
			    dv->dv_name, mp.mp_mode));
		}
	}

	/*
	 * If we don't have a vattr for this node, construct one.
	 */
	if (dv->dv_attr) {
		free_vattr = vattrp;
		vattrp = NULL;
	} else {
		ASSERT(dv->dv_attrvp);
		ASSERT(vp->v_type != VDIR);
		*vattrp = dv_vattr_file;
		error = VOP_GETATTR(dv->dv_attrvp, vattrp, 0, cr);
		dsysdebug(error, ("vop_getattr %s %d\n",
			dv->dv_name, error));
		if (error)
			goto out;
		dv->dv_attr = vattrp;
		dv_vattr_merge(dv, dv->dv_attr);
		vattrp = NULL;
	}

	error = secpolicy_vnode_setattr(cr, vp, vap, dv->dv_attr,
					flags, devfs_unlocked_access, dv);
	if (error) {
		dsysdebug(error, ("devfs_setattr %s secpolicy error %d\n",
			dv->dv_name, error));
		goto out;
	}

	/*
	 * Apply changes to the memory based attribute. This code
	 * is modeled after the tmpfs implementation of memory
	 * based vnodes
	 */
	map = dv->dv_attr;
	mask = vap->va_mask;

	/* Change file access modes. */
	if (mask & AT_MODE) {
		map->va_mode &= S_IFMT;
		map->va_mode |= vap->va_mode & ~S_IFMT;
	}
	if (mask & AT_UID)
		map->va_uid = vap->va_uid;
	if (mask & AT_GID)
		map->va_gid = vap->va_gid;
	if (mask & AT_ATIME)
		map->va_atime = vap->va_atime;
	if (mask & AT_MTIME)
		map->va_mtime = vap->va_mtime;

	if (mask & (AT_MODE | AT_UID | AT_GID | AT_MTIME)) {
		gethrestime(&map->va_ctime);
	}

	/*
	 * A setattr to defaults means we no longer need the
	 * shadow node as a persistent store, unless there
	 * are ACLs.  Otherwise create a shadow node if one
	 * doesn't exist yet.
	 */
	if (persist) {
		if ((dv_setattr_cmp(map, &mp) == 0) &&
		    ((dv->dv_flags & DV_ACL) == 0)) {

			if (dv->dv_attrvp) {
				ddv = dv->dv_dotdot;
				ASSERT(ddv->dv_attrvp);
				error = VOP_REMOVE(ddv->dv_attrvp,
				    dv->dv_name, cr);
				dsysdebug(error,
				    ("vop_remove %s %s %d\n",
				    ddv->dv_name, dv->dv_name, error));

				if (error == EROFS)
					error = 0;
				VN_RELE(dv->dv_attrvp);
				dv->dv_attrvp = NULL;
			}
			ASSERT(dv->dv_attr);
		} else {
			if (mask & AT_MODE)
				dcmn_err5(("%s persisting mode 0%o\n",
					dv->dv_name, vap->va_mode));
			if (mask & AT_UID)
				dcmn_err5(("%s persisting uid %d\n",
					dv->dv_name, vap->va_uid));
			if (mask & AT_GID)
				dcmn_err5(("%s persisting gid %d\n",
					dv->dv_name, vap->va_gid));

			if (dv->dv_attrvp == NULL) {
				dvp = DVTOV(dv->dv_dotdot);
				dv_shadow_node(dvp, dv->dv_name, vp,
				    NULL, NULLVP, cr,
				    DV_SHADOW_CREATE | DV_SHADOW_WRITE_HELD);
			}
			if (dv->dv_attrvp) {
				error = VOP_SETATTR(dv->dv_attrvp,
				    vap, flags, cr, NULL);
				dsysdebug(error, ("vop_setattr %s %d\n",
				    dv->dv_name, error));
			}
			/*
			 * Some file systems may return EROFS for a setattr
			 * on a readonly file system.  In this case save
			 * as our own memory based attribute.
			 * NOTE: ufs is NOT one of these (see ufs_iupdat).
			 */
			if (dv->dv_attr && dv->dv_attrvp && error == 0) {
				vattrp = dv->dv_attr;
				dv->dv_attr = NULL;
			} else if (error == EROFS)
				error = 0;
		}
	}

out:
	rw_exit(&dv->dv_contents);

	if (vattrp)
		kmem_free(vattrp, sizeof (*vattrp));
	if (free_vattr)
		kmem_free(free_vattr, sizeof (*free_vattr));
	return (error);
}
Esempio n. 17
0
/*
 * Rename system call.
 * 	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.
 */
int
ufs_rename(void *v)
{
	struct vop_rename_args *ap = v;
	struct vnode *tvp = ap->a_tvp;
	struct vnode *tdvp = ap->a_tdvp;
	struct vnode *fvp = ap->a_fvp;
	struct vnode *fdvp = ap->a_fdvp;
	struct componentname *tcnp = ap->a_tcnp;
	struct componentname *fcnp = ap->a_fcnp;
	struct proc *p = fcnp->cn_proc;
	struct inode *ip, *xp, *dp;
	struct direct newdir;
	int doingdirectory = 0, oldparent = 0, newparent = 0;
	int error = 0;

#ifdef DIAGNOSTIC
	if ((tcnp->cn_flags & HASBUF) == 0 ||
	    (fcnp->cn_flags & HASBUF) == 0)
		panic("ufs_rename: no name");
#endif
	/*
	 * Check for cross-device rename.
	 */
	if ((fvp->v_mount != tdvp->v_mount) ||
	    (tvp && (fvp->v_mount != tvp->v_mount))) {
		error = EXDEV;
abortit:
		VOP_ABORTOP(tdvp, tcnp);
		if (tdvp == tvp)
			vrele(tdvp);
		else
			vput(tdvp);
		if (tvp)
			vput(tvp);
		VOP_ABORTOP(fdvp, fcnp);
		vrele(fdvp);
		vrele(fvp);
		return (error);
	}

	if (tvp && ((DIP(VTOI(tvp), flags) & (IMMUTABLE | APPEND)) ||
	    (DIP(VTOI(tdvp), flags) & APPEND))) {
		error = EPERM;
		goto abortit;
	}

	/*
	 * Check if just deleting a link name or if we've lost a race.
	 * If another process completes the same rename after we've looked
	 * up the source and have blocked looking up the target, then the
	 * source and target inodes may be identical now although the
	 * names were never linked.
	 */
	if (fvp == tvp) {
		if (fvp->v_type == VDIR) {
			/*
			 * Linked directories are impossible, so we must
			 * have lost the race.  Pretend that the rename
			 * completed before the lookup.
			 */
			error = ENOENT;
			goto abortit;
		}

		/* Release destination completely. */
		VOP_ABORTOP(tdvp, tcnp);
		vput(tdvp);
		vput(tvp);

		/*
		 * Delete source.  There is another race now that everything
		 * is unlocked, but this doesn't cause any new complications.
		 * relookup() may find a file that is unrelated to the
		 * original one, or it may fail.  Too bad.
		 */
		vrele(fvp);
		fcnp->cn_flags &= ~MODMASK;
		fcnp->cn_flags |= LOCKPARENT | LOCKLEAF;
		if ((fcnp->cn_flags & SAVESTART) == 0)
			panic("ufs_rename: lost from startdir");
		fcnp->cn_nameiop = DELETE;
		if ((error = vfs_relookup(fdvp, &fvp, fcnp)) != 0)
			return (error);		/* relookup did vrele() */
		vrele(fdvp);
		return (VOP_REMOVE(fdvp, fvp, fcnp));
	}

	if ((error = vn_lock(fvp, LK_EXCLUSIVE, p)) != 0)
		goto abortit;

	/* fvp, tdvp, tvp now locked */
	dp = VTOI(fdvp);
	ip = VTOI(fvp);
	if ((nlink_t) DIP(ip, nlink) >= LINK_MAX) {
		VOP_UNLOCK(fvp, 0);
		error = EMLINK;
		goto abortit;
	}
	if ((DIP(ip, flags) & (IMMUTABLE | APPEND)) ||
	    (DIP(dp, flags) & APPEND)) {
		VOP_UNLOCK(fvp, 0);
		error = EPERM;
		goto abortit;
	}
	if ((DIP(ip, mode) & IFMT) == IFDIR) {
		error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred);
		if (!error && tvp)
			error = VOP_ACCESS(tvp, VWRITE, tcnp->cn_cred);
		if (error) {
			VOP_UNLOCK(fvp, 0);
			error = EACCES;
			goto abortit;
		}
		/*
		 * Avoid ".", "..", and aliases of "." for obvious reasons.
		 */
		if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') ||
		    dp == ip ||
		    (fcnp->cn_flags & ISDOTDOT) ||
		    (tcnp->cn_flags & ISDOTDOT) ||
		    (ip->i_flag & IN_RENAME)) {
			VOP_UNLOCK(fvp, 0);
			error = EINVAL;
			goto abortit;
		}
		ip->i_flag |= IN_RENAME;
		oldparent = dp->i_number;
		doingdirectory = 1;
	}
	VN_KNOTE(fdvp, NOTE_WRITE);		/* XXX right place? */

	/*
	 * When the target exists, both the directory
	 * and target vnodes are returned locked.
	 */
	dp = VTOI(tdvp);
	xp = NULL;
	if (tvp)
		xp = VTOI(tvp);

	/*
	 * 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_effnlink++;
	DIP_ADD(ip, nlink, 1);
	ip->i_flag |= IN_CHANGE;
	if (DOINGSOFTDEP(fvp))
		softdep_change_linkcnt(ip, 0);
	if ((error = UFS_UPDATE(ip, !DOINGSOFTDEP(fvp))) != 0) {
		VOP_UNLOCK(fvp, 0);
		goto bad;
	}

	/*
	 * If ".." must be changed (ie the directory gets a new
	 * parent) then the source directory must not be in the
	 * directory hierarchy above the target, as this would
	 * orphan everything below the source directory. Also
	 * the user must have write permission in the source so
	 * as to be able to change "..". We must repeat the call 
	 * to namei, as the parent directory is unlocked by the
	 * call to checkpath().
	 */
	error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred);
	VOP_UNLOCK(fvp, 0);

	/* tdvp and tvp locked */
	if (oldparent != dp->i_number)
		newparent = dp->i_number;
	if (doingdirectory && newparent) {
		if (error)	/* write access check above */
			goto bad;
		if (xp != NULL)
			vput(tvp);
		/*
		 * Compensate for the reference ufs_checkpath() loses.
		 */
		vref(tdvp);
		/* Only tdvp is locked */
		if ((error = ufs_checkpath(ip, dp, tcnp->cn_cred)) != 0) {
			vrele(tdvp);
			goto out;
		}
		if ((tcnp->cn_flags & SAVESTART) == 0)
			panic("ufs_rename: lost to startdir");
		if ((error = vfs_relookup(tdvp, &tvp, tcnp)) != 0)
			goto out;
		vrele(tdvp); /* relookup() acquired a reference */
		dp = VTOI(tdvp);
		xp = NULL;
		if (tvp)
			xp = VTOI(tvp);
	}
	/*
	 * 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 (xp == NULL) {
		if (dp->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) DIP(dp, nlink) >= LINK_MAX) {
				error = EMLINK;
				goto bad;
			}
			dp->i_effnlink++;
			DIP_ADD(dp, nlink, 1);
			dp->i_flag |= IN_CHANGE;
			if (DOINGSOFTDEP(tdvp))
                               softdep_change_linkcnt(dp, 0);
			if ((error = UFS_UPDATE(dp, !DOINGSOFTDEP(tdvp))) 
			    != 0) {
				dp->i_effnlink--;
				DIP_ADD(dp, nlink, -1);
				dp->i_flag |= IN_CHANGE;
				if (DOINGSOFTDEP(tdvp))
					softdep_change_linkcnt(dp, 0);
				goto bad;
			}
		}
		ufs_makedirentry(ip, tcnp, &newdir);
		if ((error = ufs_direnter(tdvp, NULL, &newdir, tcnp, NULL)) != 0) {
			if (doingdirectory && newparent) {
				dp->i_effnlink--;
				DIP_ADD(dp, nlink, -1);
				dp->i_flag |= IN_CHANGE;
				if (DOINGSOFTDEP(tdvp))
					softdep_change_linkcnt(dp, 0);
				(void)UFS_UPDATE(dp, 1);
			}
			goto bad;
		}
		VN_KNOTE(tdvp, NOTE_WRITE);
		vput(tdvp);
	} else {
		if (xp->i_dev != dp->i_dev || xp->i_dev != ip->i_dev)
			panic("rename: EXDEV");
		/*
		 * Short circuit rename(foo, foo).
		 */
		if (xp->i_number == ip->i_number)
			panic("ufs_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 ((DIP(dp, mode) & S_ISTXT) && tcnp->cn_cred->cr_uid != 0 &&
		    tcnp->cn_cred->cr_uid != DIP(dp, uid) &&
		    DIP(xp, uid )!= tcnp->cn_cred->cr_uid) {
			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 ((DIP(xp, mode) & IFMT) == IFDIR) {
			if (xp->i_effnlink > 2 ||
			    !ufs_dirempty(xp, dp->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(dp, xp, ip->i_number,
                   IFTODT(DIP(ip, mode)), (doingdirectory && newparent) ?
		   newparent : doingdirectory)) != 0)
                        goto bad;
		if (doingdirectory) {
			if (!newparent) {
				dp->i_effnlink--;
				if (DOINGSOFTDEP(tdvp))
					softdep_change_linkcnt(dp, 0);
			}
			xp->i_effnlink--;
			if (DOINGSOFTDEP(tvp))
				softdep_change_linkcnt(xp, 0);
		}
		if (doingdirectory && !DOINGSOFTDEP(tvp)) {
		       /*
			* 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. The soft
                        * dependency code will arrange to do these operations
                        * after the parent directory entry has been deleted on
                        * disk, so when running with that code we avoid doing
                        * them now.
                        */
			if (!newparent) {
				DIP_ADD(dp, nlink, -1);
				dp->i_flag |= IN_CHANGE;
			}

			DIP_ADD(xp, nlink, -1);
			xp->i_flag |= IN_CHANGE;
			if ((error = UFS_TRUNCATE(VTOI(tvp), (off_t)0, IO_SYNC,
			        tcnp->cn_cred)) != 0)
				goto bad;
                }
		VN_KNOTE(tdvp, NOTE_WRITE);
	        vput(tdvp);
		VN_KNOTE(tvp, NOTE_DELETE);
		vput(tvp);
		xp = NULL;
	}

	/*
	 * 3) Unlink the source.
	 */
	fcnp->cn_flags &= ~MODMASK;
	fcnp->cn_flags |= LOCKPARENT | LOCKLEAF;
	if ((fcnp->cn_flags & SAVESTART) == 0)
		panic("ufs_rename: lost from startdir");
	if ((error = vfs_relookup(fdvp, &fvp, fcnp)) != 0) {
		vrele(ap->a_fvp);
		return (error);
	}
	vrele(fdvp);
	if (fvp == NULL) {
		/*
		 * From name has disappeared.
		 */
		if (doingdirectory)
			panic("ufs_rename: lost dir entry");
		vrele(ap->a_fvp);
		return (0);
	}

	xp = VTOI(fvp);
	dp = VTOI(fdvp);

	/*
	 * Ensure that the directory entry still exists and has not
	 * changed while the new name has been entered. If the source is
	 * a file then the entry may have been unlinked or renamed. In
	 * either case there is no further work to be done. If the source
	 * is a directory then it cannot have been rmdir'ed; the IN_RENAME 
	 * flag ensures that it cannot be moved by another rename or removed
	 * by a rmdir.
	 */
	if (xp != ip) {
		if (doingdirectory)
			panic("ufs_rename: lost dir entry");
	} else {
		/*
		 * 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) {
			xp->i_offset = mastertemplate.dot_reclen;
			ufs_dirrewrite(xp, dp, newparent, DT_DIR, 0);
			cache_purge(fdvp);
		}
		error = ufs_dirremove(fdvp, xp, fcnp->cn_flags, 0);
		xp->i_flag &= ~IN_RENAME;
	}
	VN_KNOTE(fvp, NOTE_RENAME);
	if (dp)
		vput(fdvp);
	if (xp)
		vput(fvp);
	vrele(ap->a_fvp);
	return (error);

bad:
	if (xp)
		vput(ITOV(xp));
	vput(ITOV(dp));
out:
	vrele(fdvp);
	if (doingdirectory)
		ip->i_flag &= ~IN_RENAME;
	if (vn_lock(fvp, LK_EXCLUSIVE, p) == 0) {
		ip->i_effnlink--;
		DIP_ADD(ip, nlink, -1);
		ip->i_flag |= IN_CHANGE;
		ip->i_flag &= ~IN_RENAME;
		if (DOINGSOFTDEP(fvp))
			softdep_change_linkcnt(ip, 0);
		vput(fvp);
	} else
		vrele(fvp);
	return (error);
}