/*-
* Copyright (c) 2011 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Taylor R Campbell.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Sanitize our world from the VFS insanity. Unlock the target
* directory and node, which are locked. Release the children,
* which are referenced. Check for rename("x", "y/."), which
* it is our responsibility to reject, not the caller's. (But
* the caller does reject rename("x/.", "y"). Go figure.)
*/
VOP_UNLOCK(tdvp);
if ((tvp != NULL) && (tvp != tdvp))
VOP_UNLOCK(tvp);
out: /*
* All done, whether with success or failure. Release the
* directory nodes now, as the caller expects from the VFS
* protocol.
*/
vrele(fdvp);
vrele(tdvp);
return error;
}
/*
* msdosfs_sane_rename: The hairiest vop, with the saner API.
*
* Arguments:
*
* . fdvp (from directory vnode),
* . fcnp (from component name),
* . tdvp (to directory vnode), and
* . tcnp (to component name).
*
* fdvp and tdvp must be referenced and unlocked.
*/
static int
msdosfs_sane_rename(
struct vnode *fdvp, struct componentname *fcnp,
struct vnode *tdvp, struct componentname *tcnp,
kauth_cred_t cred, bool posixly_correct)
{
struct msdosfs_lookup_results fmlr, tmlr;
/*
* msdosfs_gro_directory_empty_p: Return true if the directory vp is
* empty. dvp is its parent.
*
* vp and dvp must be locked and referenced.
*/
static bool
msdosfs_gro_directory_empty_p(struct mount *mp, kauth_cred_t cred,
struct vnode *vp, struct vnode *dvp)
{
/*
* msdosfs_gro_rename_check_possible: Check whether renaming fvp in fdvp
* to tvp in tdvp is possible independent of credentials.
*/
static int
msdosfs_gro_rename_check_possible(struct mount *mp,
struct vnode *fdvp, struct vnode *fvp,
struct vnode *tdvp, struct vnode *tvp)
{
/*
* We shall need to temporarily bump the reference count, so
* make sure there is room to do so.
*/
if (VTODE(fvp)->de_refcnt >= LONG_MAX)
return EMLINK;
/*
* XXX There is a pile of logic here to handle a voodoo flag
* DE_RENAME. I think this is a vestige of days when the file
* system hackers didn't understand concurrency or race
* conditions; I believe it serves no useful function
* whatsoever.
*/
/*
* XXX Hold it right there -- surely if we crash after
* removede, we'll fail to provide rename's guarantee that
* there will be something at the target pathname?
*/
if (tvp != NULL) {
error = msdosfs_removede(VTODE(tdvp), VTODE(tvp), tmlr);
if (error)
goto out;
}
/*
* Convert the filename in tcnp into a dos filename. We copy this
* into the denode and directory entry for the destination
* file/directory.
*/
error = msdosfs_uniqdosname(VTODE(tdvp), tcnp, toname);
if (error)
goto out;
/*
* First write a new entry in the destination directory and
* mark the entry in the source directory as deleted. Then
* move the denode to the correct hash chain for its new
* location in the filesystem. And, if we moved a directory,
* then update its .. entry to point to the new parent
* directory.
*/
/* Save the old name in case we need to back out. */
memcpy(oldname, VTODE(fvp)->de_Name, 11);
memcpy(VTODE(fvp)->de_Name, toname, 11);
error = msdosfs_createde(VTODE(fvp), VTODE(tdvp), tmlr, 0, tcnp);
if (error) {
/* Directory entry didn't take -- back out the name change. */
memcpy(VTODE(fvp)->de_Name, oldname, 11);
goto out;
}
/*
* createde doesn't increment de_refcnt, but removede
* decrements it. Go figure.
*/
KASSERT(VTODE(fvp)->de_refcnt < LONG_MAX);
VTODE(fvp)->de_refcnt++;
/*
* XXX Yes, createde and removede have arguments swapped. Go figure.
*/
error = msdosfs_removede(VTODE(fdvp), VTODE(fvp), fmlr);
if (error) {
#if 0 /* XXX Back out the new directory entry? Panic? */
(void)msdosfs_removede(VTODE(tdvp), VTODE(fvp), tmlr);
memcpy(VTODE(fvp)->de_Name, oldname, 11);
#endif
goto out;
}
/*
* If we moved a directory to a new parent directory, then we must
* fixup the ".." entry in the moved directory.
*/
if (directory_p && reparent_p) {
error = msdosfs_rename_replace_dotdot(fvp, fdvp, tdvp, cred);
if (error)
goto out;
}
if (directory_p)
VTODE(fvp)->de_flag &=~ DE_RENAME;
return error;
}
/*
* msdosfs_gro_remove: Rename an object over another link to itself,
* effectively removing just the original link.
*/
static int
msdosfs_gro_remove(struct mount *mp, kauth_cred_t cred,
struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp,
nlink_t *tvp_nlinkp)
{
struct msdosfs_lookup_results *mlr = de;
int error;
/*
* msdosfs_rmdired_p: Check whether the directory vp has been rmdired.
*
* vp must be locked and referenced.
*/
static bool
msdosfs_rmdired_p(struct vnode *vp)
{
/*
* We need to provisionally lock tdvp to keep rmdir from
* deleting it -- or any ancestor -- at an inopportune moment.
*/
error = msdosfs_gro_lock_directory(mp, tdvp);
if (error)
return error;
vp = tdvp;
vref(vp);
for (;;) {
KASSERT(vp->v_type == VDIR);
/* Did we hit the root without finding fdvp? */
if ((vp->v_vflag & VV_ROOT) != 0) {
vput(vp);
*intermediate_node_ret = NULL;
return 0;
}
/* Did we find that fdvp is an ancestor? */
if (VTODE(fdvp)->de_StartCluster == dotdot_cn) {
/* Unlock vp, but keep it referenced. */
VOP_UNLOCK(vp);
*intermediate_node_ret = vp;
return 0;
}
if (msdosfs_rmdired_p(vp)) {
vput(vp);
return ENOENT;
}
}
}
/*
* msdosfs_read_dotdot: Store in *cn_ret the cluster number of the
* parent of the directory vp.
*/
static int
msdosfs_read_dotdot(struct vnode *vp, kauth_cred_t cred, unsigned long *cn_ret)
{
struct msdosfsmount *pmp;
unsigned long start_cn, cn;
struct buf *bp;
struct direntry *ep;
int error;
/*
* msdosfs_gro_lock_directory: Lock the directory vp, but fail if it has
* been rmdir'd.
*/
static int
msdosfs_gro_lock_directory(struct mount *mp, struct vnode *vp)
{
int error;
(void)mp;
KASSERT(vp != NULL);
error = vn_lock(vp, LK_EXCLUSIVE);
if (error)
return error;
KASSERT(mp != NULL);
KASSERT(vp->v_mount == mp);
if (msdosfs_rmdired_p(vp)) {
VOP_UNLOCK(vp);
return ENOENT;
}