Patch to make tmpfs use genfs_rename. Index: sys/fs/tmpfs/files.tmpfs =================================================================== RCS file: /cvsroot/src/sys/fs/tmpfs/files.tmpfs,v retrieving revision 1.4 diff -p -u -r1.4 files.tmpfs --- sys/fs/tmpfs/files.tmpfs 22 Jun 2010 18:32:07 -0000 1.4 +++ sys/fs/tmpfs/files.tmpfs 7 Apr 2012 21:19:55 -0000 @@ -4,6 +4,7 @@ deffs TMPFS file fs/tmpfs/tmpfs_fifoops.c tmpfs file fs/tmpfs/tmpfs_mem.c tmpfs +file fs/tmpfs/tmpfs_rename.c tmpfs file fs/tmpfs/tmpfs_specops.c tmpfs file fs/tmpfs/tmpfs_subr.c tmpfs file fs/tmpfs/tmpfs_vfsops.c tmpfs Index: sys/fs/tmpfs/tmpfs_rename.c =================================================================== RCS file: sys/fs/tmpfs/tmpfs_rename.c diff -N sys/fs/tmpfs/tmpfs_rename.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ sys/fs/tmpfs/tmpfs_rename.c 7 Apr 2012 21:19:55 -0000 @@ -0,0 +1,589 @@ +/* $NetBSD$ */ + +/*- + * Copyright (c) 2012 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. + */ + +/* + * tmpfs rename + */ + +#include +__KERNEL_RCSID(0, "$NetBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +/* + * Forward declarations + */ + +static int tmpfs_sane_rename(struct vnode *, struct componentname *, + struct vnode *, struct componentname *, + kauth_cred_t, bool); +static bool tmpfs_rmdired_p(struct vnode *); +static int tmpfs_gro_lock_directory(struct mount *, struct vnode *); + +static const struct genfs_rename_ops tmpfs_genfs_rename_ops; + +/* + * tmpfs_sane_rename: The hairiest vop, with the saner API. + * + * Arguments: + * + * . fdvp (from directory vnode), + * . fcnp (from component name), + * . tdvp (to directory vnode), + * . tcnp (to component name), + * . cred (credentials structure), and + * . posixly_correct (flag for behaviour if target & source link same file). + * + * fdvp and tdvp may be the same, and must be referenced and unlocked. + */ +static int +tmpfs_sane_rename( + struct vnode *fdvp, struct componentname *fcnp, + struct vnode *tdvp, struct componentname *tcnp, + kauth_cred_t cred, bool posixly_correct) +{ + struct tmpfs_dirent *fdirent, *tdirent; + + return genfs_sane_rename(&tmpfs_genfs_rename_ops, + fdvp, fcnp, &fdirent, tdvp, tcnp, &tdirent, + cred, posixly_correct); +} + +/* + * tmpfs_rename: The hairiest vop, with the insanest API. Defer to + * genfs_insane_rename immediately. + */ +int +tmpfs_rename(void *v) +{ + + return genfs_insane_rename(v, &tmpfs_sane_rename); +} + +/* + * tmpfs_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 +tmpfs_gro_directory_empty_p(struct mount *mp, kauth_cred_t cred, + struct vnode *vp, struct vnode *dvp) +{ + + (void)mp; + (void)cred; + (void)dvp; + KASSERT(mp != NULL); + KASSERT(vp != NULL); + KASSERT(dvp != NULL); + KASSERT(vp != dvp); + KASSERT(vp->v_mount == mp); + KASSERT(dvp->v_mount == mp); + KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); + KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); + + return (VP_TO_TMPFS_NODE(vp)->tn_size == 0); +} + +/* + * tmpfs_gro_rename_check_possible: Check whether a rename is possible + * independent of credentials. + */ +static int +tmpfs_gro_rename_check_possible(struct mount *mp, + struct vnode *fdvp, struct vnode *fvp, + struct vnode *tdvp, struct vnode *tvp) +{ + + (void)mp; + KASSERT(mp != NULL); + KASSERT(fdvp != NULL); + KASSERT(fvp != NULL); + KASSERT(tdvp != NULL); + KASSERT(fdvp != fvp); + KASSERT(fdvp != tvp); + KASSERT(tdvp != fvp); + KASSERT(tdvp != tvp); + KASSERT(fvp != tvp); + KASSERT(fdvp->v_type == VDIR); + KASSERT(tdvp->v_type == VDIR); + KASSERT(fdvp->v_mount == mp); + KASSERT(fvp->v_mount == mp); + KASSERT(tdvp->v_mount == mp); + KASSERT((tvp == NULL) || (tvp->v_mount == mp)); + KASSERT(VOP_ISLOCKED(fdvp) == LK_EXCLUSIVE); + KASSERT(VOP_ISLOCKED(fvp) == LK_EXCLUSIVE); + KASSERT(VOP_ISLOCKED(tdvp) == LK_EXCLUSIVE); + KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) == LK_EXCLUSIVE)); + + return genfs_ufslike_rename_check_possible( + VP_TO_TMPFS_NODE(fdvp)->tn_flags, VP_TO_TMPFS_NODE(fvp)->tn_flags, + VP_TO_TMPFS_NODE(tdvp)->tn_flags, + (tvp? VP_TO_TMPFS_NODE(tvp)->tn_flags : 0), (tvp != NULL), + IMMUTABLE, APPEND); +} + +/* + * tmpfs_gro_rename_check_permitted: Check whether a rename is + * permitted given our credentials. + */ +static int +tmpfs_gro_rename_check_permitted(struct mount *mp, kauth_cred_t cred, + struct vnode *fdvp, struct vnode *fvp, + struct vnode *tdvp, struct vnode *tvp) +{ + + (void)mp; + KASSERT(mp != NULL); + KASSERT(fdvp != NULL); + KASSERT(fvp != NULL); + KASSERT(tdvp != NULL); + KASSERT(fdvp != fvp); + KASSERT(fdvp != tvp); + KASSERT(tdvp != fvp); + KASSERT(tdvp != tvp); + KASSERT(fvp != tvp); + KASSERT(fdvp->v_type == VDIR); + KASSERT(tdvp->v_type == VDIR); + KASSERT(fdvp->v_mount == mp); + KASSERT(fvp->v_mount == mp); + KASSERT(tdvp->v_mount == mp); + KASSERT((tvp == NULL) || (tvp->v_mount == mp)); + KASSERT(VOP_ISLOCKED(fdvp) == LK_EXCLUSIVE); + KASSERT(VOP_ISLOCKED(fvp) == LK_EXCLUSIVE); + KASSERT(VOP_ISLOCKED(tdvp) == LK_EXCLUSIVE); + KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) == LK_EXCLUSIVE)); + + return genfs_ufslike_rename_check_permitted(cred, + fdvp, VP_TO_TMPFS_NODE(fdvp)->tn_mode, + VP_TO_TMPFS_NODE(fdvp)->tn_uid, + fvp, VP_TO_TMPFS_NODE(fvp)->tn_uid, + tdvp, VP_TO_TMPFS_NODE(tdvp)->tn_mode, + VP_TO_TMPFS_NODE(tdvp)->tn_uid, + tvp, (tvp? VP_TO_TMPFS_NODE(tvp)->tn_uid : 0)); +} + +/* + * tmpfs_gro_remove_check_possible: Check whether a remove is possible + * independent of credentials. + */ +static int +tmpfs_gro_remove_check_possible(struct mount *mp, + struct vnode *dvp, struct vnode *vp) +{ + + (void)mp; + KASSERT(mp != NULL); + KASSERT(dvp != NULL); + KASSERT(vp != NULL); + KASSERT(dvp != vp); + KASSERT(dvp->v_type == VDIR); + KASSERT(vp->v_type != VDIR); + KASSERT(dvp->v_mount == mp); + KASSERT(vp->v_mount == mp); + KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); + KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); + + return genfs_ufslike_remove_check_possible( + VP_TO_TMPFS_NODE(dvp)->tn_flags, VP_TO_TMPFS_NODE(vp)->tn_flags, + IMMUTABLE, APPEND); +} + +/* + * tmpfs_gro_remove_check_permitted: Check whether a remove is + * permitted given our credentials. + */ +static int +tmpfs_gro_remove_check_permitted(struct mount *mp, kauth_cred_t cred, + struct vnode *dvp, struct vnode *vp) +{ + + (void)mp; + KASSERT(mp != NULL); + KASSERT(dvp != NULL); + KASSERT(vp != NULL); + KASSERT(dvp != vp); + KASSERT(dvp->v_type == VDIR); + KASSERT(vp->v_type != VDIR); + KASSERT(dvp->v_mount == mp); + KASSERT(vp->v_mount == mp); + KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); + KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); + + return genfs_ufslike_remove_check_permitted(cred, + dvp, VP_TO_TMPFS_NODE(dvp)->tn_mode, VP_TO_TMPFS_NODE(dvp)->tn_uid, + vp, VP_TO_TMPFS_NODE(vp)->tn_uid); +} + +/* + * tmpfs_gro_rename: Actually perform the rename operation. + */ +static int +tmpfs_gro_rename(struct mount *mp, kauth_cred_t cred, + struct vnode *fdvp, struct componentname *fcnp, + void *fde, struct vnode *fvp, + struct vnode *tdvp, struct componentname *tcnp, + void *tde, struct vnode *tvp) +{ + struct tmpfs_dirent **fdep = fde; + struct tmpfs_dirent **tdep = tde; + char *newname; + + (void)cred; + KASSERT(mp != NULL); + KASSERT(fdvp != NULL); + KASSERT(fcnp != NULL); + KASSERT(fdep != NULL); + KASSERT(fvp != NULL); + KASSERT(tdvp != NULL); + KASSERT(tcnp != NULL); + KASSERT(tdep != NULL); + KASSERT(fdep != tdep); + KASSERT((*fdep) != (*tdep)); + KASSERT((*fdep) != NULL); + KASSERT((*fdep)->td_node == VP_TO_TMPFS_NODE(fvp)); + KASSERT((tvp == NULL) || ((*tdep) != NULL)); + KASSERT((tvp == NULL) || ((*tdep)->td_node == VP_TO_TMPFS_NODE(tvp))); + KASSERT(fdvp != fvp); + KASSERT(fdvp != tvp); + KASSERT(tdvp != fvp); + KASSERT(tdvp != tvp); + KASSERT(fvp != tvp); + KASSERT(fdvp->v_mount == mp); + KASSERT(fvp->v_mount == mp); + KASSERT(tdvp->v_mount == mp); + KASSERT((tvp == NULL) || (tvp->v_mount == mp)); + KASSERT(VOP_ISLOCKED(fdvp) == LK_EXCLUSIVE); + KASSERT(VOP_ISLOCKED(fvp) == LK_EXCLUSIVE); + KASSERT(VOP_ISLOCKED(tdvp) == LK_EXCLUSIVE); + KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) == LK_EXCLUSIVE)); + + if (tmpfs_strname_neqlen(fcnp, tcnp)) { + newname = tmpfs_strname_alloc(VFS_TO_TMPFS(mp), + tcnp->cn_namelen); + if (newname == NULL) + return ENOSPC; + } else { + newname = NULL; + } + + /* + * If we are moving from one directory to another, detach the + * source entry and reattach it to the target directory. + */ + if (fdvp != tdvp) { + tmpfs_dir_detach(fdvp, *fdep); + tmpfs_dir_attach(tdvp, *fdep, VP_TO_TMPFS_NODE(fvp)); + } else if (tvp == NULL) { + /* + * We are changing the directory. tmpfs_dir_attach and + * tmpfs_dir_detach note the events for us, but for + * this case we don't call them, so we must note the + * event explicitly. + */ + VN_KNOTE(fdvp, NOTE_WRITE); + } + + /* + * If we are replacing an existing target entry, delete it. + * + * XXX What if the target is a directory with whiteout entries? + */ + if (tvp != NULL) { + KASSERT((*tdep) != NULL); + KASSERT((*tdep)->td_node == VP_TO_TMPFS_NODE(tvp)); + KASSERT((fvp->v_type == VDIR) == (tvp->v_type == VDIR)); + if (tvp->v_type == VDIR) { + KASSERT(VP_TO_TMPFS_NODE(tvp)->tn_size == 0); + KASSERT(VP_TO_TMPFS_NODE(tvp)->tn_links == 2); + + /* + * Decrement the extra link count for `.' so + * the vnode will be recycled when released. + * + * XXX Why not just release directory vnodes + * when their link count is 1 instead of 0 in + * tmpfs_inactive, since `.' is being treated + * specially anyway? + */ + VP_TO_TMPFS_NODE(tvp)->tn_links--; + } + tmpfs_dir_detach(tdvp, *tdep); + tmpfs_free_dirent(VFS_TO_TMPFS(mp), *tdep); + } + + /* + * Update the directory entry's name if necessary, and flag + * metadata updates. A memory allocation failure here is not + * OK because we've already committed some changes that we + * can't back out at this point, hence the early allocation + * above. + */ + if (newname != NULL) { + KASSERT(tcnp->cn_namelen <= TMPFS_MAXNAMLEN); + + tmpfs_strname_free(VFS_TO_TMPFS(mp), (*fdep)->td_name, + (*fdep)->td_namelen); + (*fdep)->td_namelen = (uint16_t)tcnp->cn_namelen; + (void)memcpy(newname, tcnp->cn_nameptr, tcnp->cn_namelen); + (*fdep)->td_name = newname; + + VP_TO_TMPFS_NODE(fvp)->tn_status |= TMPFS_NODE_CHANGED; + VP_TO_TMPFS_NODE(tdvp)->tn_status |= TMPFS_NODE_MODIFIED; + } + +#if 0 /* XXX */ + genfs_rename_cache_purge(fdvp, fvp, tdvp, tvp); +#endif + + return 0; +} + +/* + * tmpfs_gro_remove: Rename an object over another link to itself, + * effectively removing just the original link. + */ +static int +tmpfs_gro_remove(struct mount *mp, kauth_cred_t cred, + struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp) +{ + struct tmpfs_dirent **dep = de; + + (void)vp; + KASSERT(mp != NULL); + KASSERT(dvp != NULL); + KASSERT(cnp != NULL); + KASSERT(dep != NULL); + KASSERT(vp != NULL); + KASSERT(dvp != vp); + KASSERT(dvp->v_mount == mp); + KASSERT(vp->v_mount == mp); + KASSERT(dvp->v_type == VDIR); + KASSERT(vp->v_type != VDIR); + KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); + KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); + + tmpfs_dir_detach(dvp, *dep); + tmpfs_free_dirent(VFS_TO_TMPFS(mp), *dep); + + return 0; +} + +/* + * tmpfs_gro_lookup: Look up and save the lookup results. + */ +static int +tmpfs_gro_lookup(struct mount *mp, struct vnode *dvp, + struct componentname *cnp, void *de_ret, struct vnode **vp_ret) +{ + struct tmpfs_dirent *dirent, **dep_ret = de_ret; + struct vnode *vp; + int error; + + (void)mp; + KASSERT(mp != NULL); + KASSERT(dvp != NULL); + KASSERT(cnp != NULL); + KASSERT(dep_ret != NULL); + KASSERT(vp_ret != NULL); + KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); + + dirent = tmpfs_dir_lookup(VP_TO_TMPFS_NODE(dvp), cnp); + if (dirent == NULL) + return ENOENT; + + mutex_enter(&dirent->td_node->tn_vlock); + error = tmpfs_vnode_get(mp, dirent->td_node, &vp); + /* Note: tmpfs_vnode_get always releases dirent->td_node->tn_vlock. */ + if (error) + return error; + + KASSERT(vp != NULL); + KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); + + /* + * tmpfs_vnode_get returns this locked for us, which would be + * convenient but for lossage in other file systems. So, for + * hysterical raisins, we have to unlock it here. The caller + * will lock it again, and we don't rely on any interesting + * invariants in the interim, beyond that it won't vanish from + * under us, which it won't because it's referenced. + * + * XXX Once namei is fixed, we can change the genfs_rename + * protocol so that this unlock is not necessary. + */ + VOP_UNLOCK(vp); + + *dep_ret = dirent; + *vp_ret = vp; + return 0; +} + +/* + * tmpfs_rmdired_p: Check whether the directory vp has been rmdired. + * + * vp must be locked and referenced. + */ +static bool +tmpfs_rmdired_p(struct vnode *vp) +{ + + KASSERT(vp != NULL); + KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); + KASSERT(vp->v_type == VDIR); + + return (VP_TO_TMPFS_NODE(vp)->tn_spec.tn_dir.tn_parent == NULL); +} + +/* + * tmpfs_gro_genealogy: Analyze the genealogy of the source and target + * directories. + */ +static int +tmpfs_gro_genealogy(struct mount *mp, kauth_cred_t cred, + struct vnode *fdvp, struct vnode *tdvp, + struct vnode **intermediate_node_ret) +{ + struct vnode *vp; + struct tmpfs_node *dnode; + int error; + + (void)cred; + KASSERT(mp != NULL); + KASSERT(fdvp != NULL); + KASSERT(tdvp != NULL); + KASSERT(fdvp != tdvp); + KASSERT(intermediate_node_ret != NULL); + KASSERT(fdvp->v_mount == mp); + KASSERT(tdvp->v_mount == mp); + KASSERT(fdvp->v_type == VDIR); + KASSERT(tdvp->v_type == VDIR); + + /* + * We need to provisionally lock tdvp to keep rmdir from + * deleting it -- or any ancestor -- at an inopportune moment. + */ + error = tmpfs_gro_lock_directory(mp, tdvp); + if (error) + return error; + + vp = tdvp; + vref(vp); + + for (;;) { + KASSERT(vp != NULL); + KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); + KASSERT(vp->v_mount == mp); + KASSERT(vp->v_type == VDIR); + KASSERT(!tmpfs_rmdired_p(vp)); + + dnode = VP_TO_TMPFS_NODE(vp)->tn_spec.tn_dir.tn_parent; + + /* + * If dnode is null then vp has been rmdir'd, which is + * not supposed to happen because we have it locked. + */ + KASSERT(dnode != NULL); + + /* Did we hit the root without finding fdvp? */ + if (dnode == VP_TO_TMPFS_NODE(vp)) { + vput(vp); + *intermediate_node_ret = NULL; + return 0; + } + + /* Did we find that fdvp is an ancestor of tdvp? */ + if (dnode == VP_TO_TMPFS_NODE(fdvp)) { + KASSERT(dnode->tn_vnode == fdvp); + /* Unlock vp, but keep it referenced. */ + VOP_UNLOCK(vp); + *intermediate_node_ret = vp; + return 0; + } + + /* Neither -- keep ascending the family tree. */ + mutex_enter(&dnode->tn_vlock); + vput(vp); + vp = NULL; /* Just in case, for the kassert above... */ + error = tmpfs_vnode_get(mp, dnode, &vp); + if (error) + return error; + } +} + +/* + * tmpfs_gro_lock_directory: Lock the directory vp, but fail if it has + * been rmdir'd. + */ +static int +tmpfs_gro_lock_directory(struct mount *mp, struct vnode *vp) +{ + + (void)mp; + KASSERT(mp != NULL); + KASSERT(vp != NULL); + KASSERT(vp->v_mount == mp); + + vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); + + if (tmpfs_rmdired_p(vp)) { + VOP_UNLOCK(vp); + return ENOENT; + } + + return 0; +} + +static const struct genfs_rename_ops tmpfs_genfs_rename_ops = { + .gro_directory_empty_p = tmpfs_gro_directory_empty_p, + .gro_rename_check_possible = tmpfs_gro_rename_check_possible, + .gro_rename_check_permitted = tmpfs_gro_rename_check_permitted, + .gro_remove_check_possible = tmpfs_gro_remove_check_possible, + .gro_remove_check_permitted = tmpfs_gro_remove_check_permitted, + .gro_rename = tmpfs_gro_rename, + .gro_remove = tmpfs_gro_remove, + .gro_lookup = tmpfs_gro_lookup, + .gro_genealogy = tmpfs_gro_genealogy, + .gro_lock_directory = tmpfs_gro_lock_directory, +}; Index: sys/fs/tmpfs/tmpfs_vnops.c =================================================================== RCS file: /cvsroot/src/sys/fs/tmpfs/tmpfs_vnops.c,v retrieving revision 1.96 diff -p -u -r1.96 tmpfs_vnops.c --- sys/fs/tmpfs/tmpfs_vnops.c 13 Mar 2012 18:40:50 -0000 1.96 +++ sys/fs/tmpfs/tmpfs_vnops.c 7 Apr 2012 21:19:55 -0000 @@ -775,1274 +775,6 @@ out: return error; } -/* - * tmpfs_rename: rename routine, the hairiest system call, with the - * insane API. - * - * Arguments: fdvp (from-parent vnode), fvp (from-leaf), tdvp (to-parent) - * and tvp (to-leaf), if exists (NULL if not). - * - * => Caller holds a reference on fdvp and fvp, they are unlocked. - * Note: fdvp and fvp can refer to the same object (i.e. when it is root). - * - * => Both tdvp and tvp are referenced and locked. It is our responsibility - * to release the references and unlock them (or destroy). - */ - -/* - * First, some forward declarations of subroutines. - */ - -static int tmpfs_sane_rename(struct vnode *, struct componentname *, - struct vnode *, struct componentname *, kauth_cred_t, bool); -static int tmpfs_rename_enter(struct mount *, struct tmpfs_mount *, - kauth_cred_t, - struct vnode *, struct tmpfs_node *, struct componentname *, - struct tmpfs_dirent **, struct vnode **, - struct vnode *, struct tmpfs_node *, struct componentname *, - struct tmpfs_dirent **, struct vnode **); -static int tmpfs_rename_enter_common(struct mount *, struct tmpfs_mount *, - kauth_cred_t, - struct vnode *, struct tmpfs_node *, - struct componentname *, struct tmpfs_dirent **, struct vnode **, - struct componentname *, struct tmpfs_dirent **, struct vnode **); -static int tmpfs_rename_enter_separate(struct mount *, struct tmpfs_mount *, - kauth_cred_t, - struct vnode *, struct tmpfs_node *, struct componentname *, - struct tmpfs_dirent **, struct vnode **, - struct vnode *, struct tmpfs_node *, struct componentname *, - struct tmpfs_dirent **, struct vnode **); -static void tmpfs_rename_exit(struct tmpfs_mount *, - struct vnode *, struct vnode *, struct vnode *, struct vnode *); -static int tmpfs_rename_lock_directory(struct vnode *, struct tmpfs_node *); -static int tmpfs_rename_genealogy(struct tmpfs_node *, struct tmpfs_node *, - struct tmpfs_node **); -static int tmpfs_rename_lock(struct mount *, kauth_cred_t, int, - struct vnode *, struct tmpfs_node *, struct componentname *, bool, - struct tmpfs_dirent **, struct vnode **, - struct vnode *, struct tmpfs_node *, struct componentname *, bool, - struct tmpfs_dirent **, struct vnode **); -static void tmpfs_rename_attachdetach(struct tmpfs_mount *, - struct vnode *, struct tmpfs_dirent *, struct vnode *, - struct vnode *, struct tmpfs_dirent *, struct vnode *); -static int tmpfs_do_remove(struct tmpfs_mount *, struct vnode *, - struct tmpfs_node *, struct tmpfs_dirent *, struct vnode *, kauth_cred_t); -static int tmpfs_rename_check_possible(struct tmpfs_node *, - struct tmpfs_node *, struct tmpfs_node *, struct tmpfs_node *); -static int tmpfs_rename_check_permitted(kauth_cred_t, - struct tmpfs_node *, struct tmpfs_node *, - struct tmpfs_node *, struct tmpfs_node *); -static int tmpfs_remove_check_possible(struct tmpfs_node *, - struct tmpfs_node *); -static int tmpfs_remove_check_permitted(kauth_cred_t, - struct tmpfs_node *, struct tmpfs_node *); -static int tmpfs_check_sticky(kauth_cred_t, - struct tmpfs_node *, struct tmpfs_node *); - -int -tmpfs_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 *fdvp = ap->a_fdvp; - struct vnode *fvp = ap->a_fvp; - struct componentname *fcnp = ap->a_fcnp; - struct vnode *tdvp = ap->a_tdvp; - struct vnode *tvp = ap->a_tvp; - struct componentname *tcnp = ap->a_tcnp; - kauth_cred_t cred; - int error; - - KASSERT(fdvp != NULL); - KASSERT(fvp != NULL); - KASSERT(fcnp != NULL); - KASSERT(fcnp->cn_nameptr != NULL); - KASSERT(tdvp != NULL); - KASSERT(tcnp != NULL); - KASSERT(fcnp->cn_nameptr != NULL); - /* KASSERT(VOP_ISLOCKED(fdvp) != LK_EXCLUSIVE); */ - /* KASSERT(VOP_ISLOCKED(fvp) != LK_EXCLUSIVE); */ - KASSERT(VOP_ISLOCKED(tdvp) == LK_EXCLUSIVE); - KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) == LK_EXCLUSIVE)); - KASSERT(fdvp->v_type == VDIR); - KASSERT(tdvp->v_type == VDIR); - - cred = fcnp->cn_cred; - KASSERT(tcnp->cn_cred == cred); - - /* - * 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); - - vrele(fvp); - if (tvp != NULL) - vrele(tvp); - - if (tvp == tdvp) { - error = EINVAL; - goto out; - } - - error = tmpfs_sane_rename(fdvp, fcnp, tdvp, tcnp, cred, false); - -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; -} - -/* - * tmpfs_sane_rename: rename routine, the hairiest system call, with - * the sane 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 -tmpfs_sane_rename(struct vnode *fdvp, struct componentname *fcnp, - struct vnode *tdvp, struct componentname *tcnp, kauth_cred_t cred, - bool posixly_correct) -{ - struct mount *mount; - struct tmpfs_mount *tmpfs; - struct tmpfs_node *fdnode, *tdnode; - struct tmpfs_dirent *fde, *tde; - struct vnode *fvp, *tvp; - char *newname; - int error; - - KASSERT(fdvp != NULL); - KASSERT(fcnp != NULL); - KASSERT(tdvp != NULL); - KASSERT(tcnp != NULL); - /* KASSERT(VOP_ISLOCKED(fdvp) != LK_EXCLUSIVE); */ - /* KASSERT(VOP_ISLOCKED(tdvp) != LK_EXCLUSIVE); */ - KASSERT(fdvp->v_type == VDIR); - KASSERT(tdvp->v_type == VDIR); - KASSERT(fdvp->v_mount == tdvp->v_mount); - KASSERT((fcnp->cn_flags & ISDOTDOT) == 0); - KASSERT((tcnp->cn_flags & ISDOTDOT) == 0); - KASSERT((fcnp->cn_namelen != 1) || (fcnp->cn_nameptr[0] != '.')); - KASSERT((tcnp->cn_namelen != 1) || (tcnp->cn_nameptr[0] != '.')); - KASSERT((fcnp->cn_namelen != 2) || (fcnp->cn_nameptr[0] != '.') || - (fcnp->cn_nameptr[1] != '.')); - KASSERT((tcnp->cn_namelen != 2) || (tcnp->cn_nameptr[0] != '.') || - (tcnp->cn_nameptr[1] != '.')); - - /* - * Pull out the tmpfs data structures. - */ - fdnode = VP_TO_TMPFS_NODE(fdvp); - tdnode = VP_TO_TMPFS_NODE(tdvp); - KASSERT(fdnode != NULL); - KASSERT(tdnode != NULL); - KASSERT(fdnode->tn_vnode == fdvp); - KASSERT(tdnode->tn_vnode == tdvp); - KASSERT(fdnode->tn_type == VDIR); - KASSERT(tdnode->tn_type == VDIR); - - mount = fdvp->v_mount; - KASSERT(mount != NULL); - KASSERT(mount == tdvp->v_mount); - /* XXX How can we be sure this stays true? (Not that you're - * likely to mount a tmpfs read-only...) */ - KASSERT((mount->mnt_flag & MNT_RDONLY) == 0); - tmpfs = VFS_TO_TMPFS(mount); - KASSERT(tmpfs != NULL); - - /* - * Decide whether we need a new name, and allocate memory for - * it if so. Do this before locking anything or taking - * destructive actions so that we can back out safely and sleep - * safely. XXX Is sleeping an issue here? Can this just be - * moved into tmpfs_rename_attachdetach? - */ - if (tmpfs_strname_neqlen(fcnp, tcnp)) { - newname = tmpfs_strname_alloc(tmpfs, tcnp->cn_namelen); - if (newname == NULL) { - error = ENOSPC; - goto out_unlocked; - } - } else { - newname = NULL; - } - - /* - * Lock and look up everything. GCC is not very clever. - */ - fde = tde = NULL; - fvp = tvp = NULL; - error = tmpfs_rename_enter(mount, tmpfs, cred, - fdvp, fdnode, fcnp, &fde, &fvp, - tdvp, tdnode, tcnp, &tde, &tvp); - if (error) - goto out_unlocked; - - /* - * Check that everything is locked and looks right. - */ - KASSERT(fde != NULL); - KASSERT(fvp != NULL); - KASSERT(fde->td_node != NULL); - KASSERT(fde->td_node->tn_vnode == fvp); - KASSERT(fde->td_node->tn_type == fvp->v_type); - KASSERT((tde == NULL) == (tvp == NULL)); - KASSERT((tde == NULL) || (tde->td_node != NULL)); - KASSERT((tde == NULL) || (tde->td_node->tn_vnode == tvp)); - KASSERT((tde == NULL) || (tde->td_node->tn_type == tvp->v_type)); - KASSERT(VOP_ISLOCKED(fdvp) == LK_EXCLUSIVE); - KASSERT(VOP_ISLOCKED(tdvp) == LK_EXCLUSIVE); - KASSERT(VOP_ISLOCKED(fvp) == LK_EXCLUSIVE); - KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) == LK_EXCLUSIVE)); - - /* - * If the source and destination are the same object, we need - * only at most delete the source entry. - */ - if (fvp == tvp) { - KASSERT(tvp != NULL); - if (fde->td_node->tn_type == VDIR) { - /* XXX How can this possibly happen? */ - error = EINVAL; - goto out_locked; - } - if (!posixly_correct && (fde != tde)) { - /* XXX Doesn't work because of locking. - * error = VOP_REMOVE(fdvp, fvp); - */ - error = tmpfs_do_remove(tmpfs, fdvp, fdnode, fde, fvp, - cred); - if (error) - goto out_locked; - } - goto success; - } - KASSERT(fde != tde); - KASSERT(fvp != tvp); - - /* - * If the target exists, refuse to rename a directory over a - * non-directory or vice versa, or to clobber a non-empty - * directory. - */ - if (tvp != NULL) { - KASSERT(tde != NULL); - KASSERT(tde->td_node != NULL); - if (fvp->v_type == VDIR && tvp->v_type == VDIR) - error = ((tde->td_node->tn_size > 0)? ENOTEMPTY : 0); - else if (fvp->v_type == VDIR && tvp->v_type != VDIR) - error = ENOTDIR; - else if (fvp->v_type != VDIR && tvp->v_type == VDIR) - error = EISDIR; - else - error = 0; - if (error) - goto out_locked; - KASSERT((fvp->v_type == VDIR) == (tvp->v_type == VDIR)); - } - - /* - * Authorize the rename. - */ - error = tmpfs_rename_check_possible(fdnode, fde->td_node, - tdnode, (tde? tde->td_node : NULL)); - if (error) - goto out_locked; - error = tmpfs_rename_check_permitted(cred, fdnode, fde->td_node, - tdnode, (tde? tde->td_node : NULL)); - error = kauth_authorize_vnode(cred, KAUTH_VNODE_DELETE, fvp, fdvp, - error); - error = kauth_authorize_vnode(cred, KAUTH_VNODE_RENAME, tvp, tdvp, - error); - if (error) - goto out_locked; - - /* - * Everything is hunky-dory. Shuffle the directory entries. - */ - tmpfs_rename_attachdetach(tmpfs, fdvp, fde, fvp, tdvp, tde, tvp); - - /* - * Update the directory entry's name necessary, and flag - * metadata updates. A memory allocation failure here is not - * OK because we've already committed some changes that we - * can't back out at this point, and we have things locked so - * we can't sleep, hence the early allocation above. - */ - if (newname != NULL) { - KASSERT(tcnp->cn_namelen <= TMPFS_MAXNAMLEN); - - tmpfs_strname_free(tmpfs, fde->td_name, fde->td_namelen); - fde->td_namelen = (uint16_t)tcnp->cn_namelen; - (void)memcpy(newname, tcnp->cn_nameptr, tcnp->cn_namelen); - /* Commit newname and don't free it on the way out. */ - fde->td_name = newname; - newname = NULL; - - fde->td_node->tn_status |= TMPFS_NODE_CHANGED; - tdnode->tn_status |= TMPFS_NODE_MODIFIED; - } - -success: - VN_KNOTE(fvp, NOTE_RENAME); - error = 0; - -out_locked: - tmpfs_rename_exit(tmpfs, fdvp, fvp, tdvp, tvp); - -out_unlocked: - /* KASSERT(VOP_ISLOCKED(fdvp) != LK_EXCLUSIVE); */ - /* KASSERT(VOP_ISLOCKED(tdvp) != LK_EXCLUSIVE); */ - /* KASSERT((fvp == NULL) || (VOP_ISLOCKED(fvp) != LK_EXCLUSIVE)); */ - /* KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) != LK_EXCLUSIVE)); */ - - if (newname != NULL) - tmpfs_strname_free(tmpfs, newname, tcnp->cn_namelen); - - return error; -} - -/* - * Look up fcnp in fdnode/fdvp and store its directory entry in fde_ret - * and the associated vnode in fvp_ret; fail if not found. Look up - * tcnp in tdnode/tdvp and store its directory entry in tde_ret and the - * associated vnode in tvp_ret; store null instead if not found. Fail - * if anything has been mounted on any of the nodes involved. - * - * fdvp and tdvp must be referenced. - * - * On entry, nothing is locked. - * - * On success, everything is locked, and *fvp_ret, and *tvp_ret if - * nonnull, are referenced. The only pairs of vnodes that may be - * identical are {fdvp, tdvp} and {fvp, tvp}. - * - * On failure, everything remains as was. - * - * Locking everything including the source and target nodes is - * necessary to make sure that, e.g., link count updates are OK. The - * locking order is, in general, ancestor-first, matching the order you - * need to use to look up a descendant anyway. - */ -static int -tmpfs_rename_enter(struct mount *mount, struct tmpfs_mount *tmpfs, - kauth_cred_t cred, - struct vnode *fdvp, struct tmpfs_node *fdnode, struct componentname *fcnp, - struct tmpfs_dirent **fde_ret, struct vnode **fvp_ret, - struct vnode *tdvp, struct tmpfs_node *tdnode, struct componentname *tcnp, - struct tmpfs_dirent **tde_ret, struct vnode **tvp_ret) -{ - int error; - - KASSERT(mount != NULL); - KASSERT(tmpfs != NULL); - KASSERT(fdvp != NULL); - KASSERT(fdnode != NULL); - KASSERT(fcnp != NULL); - KASSERT(fde_ret != NULL); - KASSERT(fvp_ret != NULL); - KASSERT(tdvp != NULL); - KASSERT(tdnode != NULL); - KASSERT(tcnp != NULL); - KASSERT(tde_ret != NULL); - KASSERT(tvp_ret != NULL); - KASSERT(fdnode->tn_vnode == fdvp); - KASSERT(tdnode->tn_vnode == tdvp); - KASSERT(fdnode->tn_type == VDIR); - KASSERT(tdnode->tn_type == VDIR); - - if (fdvp == tdvp) { - KASSERT(fdnode == tdnode); - error = tmpfs_rename_enter_common(mount, tmpfs, cred, fdvp, - fdnode, fcnp, fde_ret, fvp_ret, tcnp, tde_ret, tvp_ret); - } else { - KASSERT(fdnode != tdnode); - error = tmpfs_rename_enter_separate(mount, tmpfs, cred, - fdvp, fdnode, fcnp, fde_ret, fvp_ret, - tdvp, tdnode, tcnp, tde_ret, tvp_ret); - } - - if (error) - return error; - - KASSERT(*fde_ret != NULL); - KASSERT(*fvp_ret != NULL); - KASSERT((*tde_ret == NULL) == (*tvp_ret == NULL)); - KASSERT((*tde_ret == NULL) || ((*tde_ret)->td_node != NULL)); - KASSERT((*tde_ret == NULL) || - ((*tde_ret)->td_node->tn_vnode == *tvp_ret)); - KASSERT(VOP_ISLOCKED(fdvp) == LK_EXCLUSIVE); - KASSERT(VOP_ISLOCKED(*fvp_ret) == LK_EXCLUSIVE); - KASSERT(VOP_ISLOCKED(tdvp) == LK_EXCLUSIVE); - KASSERT((*tvp_ret == NULL) || - (VOP_ISLOCKED(*tvp_ret) == LK_EXCLUSIVE)); - KASSERT(*fvp_ret != fdvp); - KASSERT(*fvp_ret != tdvp); - KASSERT(*tvp_ret != fdvp); - KASSERT(*tvp_ret != tdvp); - return 0; -} - -/* - * Lock and look up with a common source/target directory. - */ -static int -tmpfs_rename_enter_common(struct mount *mount, struct tmpfs_mount *tmpfs, - kauth_cred_t cred, - struct vnode *dvp, struct tmpfs_node *dnode, - struct componentname *fcnp, - struct tmpfs_dirent **fde_ret, struct vnode **fvp_ret, - struct componentname *tcnp, - struct tmpfs_dirent **tde_ret, struct vnode **tvp_ret) -{ - struct tmpfs_dirent *fde, *tde; - struct vnode *fvp, *tvp; - int error; - - error = tmpfs_rename_lock_directory(dvp, dnode); - if (error) - goto fail0; - - /* Did we lose a race with mount? */ - if (dvp->v_mountedhere != NULL) { - error = EBUSY; - goto fail1; - } - - /* Make sure the caller may read the directory. */ - error = VOP_ACCESS(dvp, VEXEC, cred); - if (error) - goto fail1; - - /* - * The order in which we lock the source and target nodes is - * irrelevant because there can only be one rename on this - * directory in flight at a time, and we have it locked. - */ - - fde = tmpfs_dir_lookup(dnode, fcnp); - if (fde == NULL) { - error = ENOENT; - goto fail1; - } - - KASSERT(fde->td_node != NULL); - /* We ruled out `.' earlier. */ - KASSERT(fde->td_node != dnode); - /* We ruled out `..' earlier. */ - KASSERT(fde->td_node != dnode->tn_spec.tn_dir.tn_parent); - mutex_enter(&fde->td_node->tn_vlock); - error = tmpfs_vnode_get(mount, fde->td_node, &fvp); - if (error) - goto fail1; - KASSERT(fvp != NULL); - KASSERT(VOP_ISLOCKED(fvp) == LK_EXCLUSIVE); - KASSERT(fvp != dvp); - KASSERT(fvp->v_mount == mount); - - /* Refuse to rename a mount point. */ - if ((fvp->v_type == VDIR) && (fvp->v_mountedhere != NULL)) { - error = EBUSY; - goto fail2; - } - - tde = tmpfs_dir_lookup(dnode, tcnp); - if (tde == NULL) { - tvp = NULL; - } else { - KASSERT(tde->td_node != NULL); - /* We ruled out `.' earlier. */ - KASSERT(tde->td_node != dnode); - /* We ruled out `..' earlier. */ - KASSERT(tde->td_node != dnode->tn_spec.tn_dir.tn_parent); - if (tde->td_node != fde->td_node) { - mutex_enter(&tde->td_node->tn_vlock); - error = tmpfs_vnode_get(mount, tde->td_node, &tvp); - if (error) - goto fail2; - KASSERT(tvp->v_mount == mount); - /* Refuse to rename over a mount point. */ - if ((tvp->v_type == VDIR) && - (tvp->v_mountedhere != NULL)) { - error = EBUSY; - goto fail3; - } - } else { - tvp = fvp; - vref(tvp); - } - KASSERT(tvp != NULL); - KASSERT(VOP_ISLOCKED(tvp) == LK_EXCLUSIVE); - } - KASSERT(tvp != dvp); - - *fde_ret = fde; - *fvp_ret = fvp; - *tde_ret = tde; - *tvp_ret = tvp; - return 0; - -fail3: if (tvp != NULL) { - if (tvp != fvp) - vput(tvp); - else - vrele(tvp); - } - -fail2: vput(fvp); -fail1: VOP_UNLOCK(dvp); -fail0: return error; -} - -/* - * Lock and look up with separate source and target directories. - */ -static int -tmpfs_rename_enter_separate(struct mount *mount, struct tmpfs_mount *tmpfs, - kauth_cred_t cred, - struct vnode *fdvp, struct tmpfs_node *fdnode, struct componentname *fcnp, - struct tmpfs_dirent **fde_ret, struct vnode **fvp_ret, - struct vnode *tdvp, struct tmpfs_node *tdnode, struct componentname *tcnp, - struct tmpfs_dirent **tde_ret, struct vnode **tvp_ret) -{ - struct tmpfs_node *intermediate_node; - struct tmpfs_dirent *fde, *tde; - struct vnode *fvp, *tvp; - int error; - - KASSERT(fdvp != tdvp); - KASSERT(fdnode != tdnode); - -#if 0 /* XXX */ - mutex_enter(&tmpfs->tm_rename_lock); -#endif - - error = tmpfs_rename_genealogy(fdnode, tdnode, &intermediate_node); - if (error) - goto fail; - - /* - * intermediate_node == NULL means fdnode is not an ancestor of - * tdnode. - */ - if (intermediate_node == NULL) - error = tmpfs_rename_lock(mount, cred, ENOTEMPTY, - tdvp, tdnode, tcnp, true, &tde, &tvp, - fdvp, fdnode, fcnp, false, &fde, &fvp); - else - error = tmpfs_rename_lock(mount, cred, EINVAL, - fdvp, fdnode, fcnp, false, &fde, &fvp, - tdvp, tdnode, tcnp, true, &tde, &tvp); - if (error) - goto fail; - - KASSERT(fde != NULL); - KASSERT(fde->td_node != NULL); - - /* - * Reject rename("foo/bar", "foo/bar/baz/quux/zot"). - */ - if (fde->td_node == intermediate_node) { - tmpfs_rename_exit(tmpfs, fdvp, fvp, tdvp, tvp); - return EINVAL; - } - - *fde_ret = fde; - *fvp_ret = fvp; - *tde_ret = tde; - *tvp_ret = tvp; - return 0; - -fail: -#if 0 /* XXX */ - mutex_exit(&tmpfs->tm_rename_lock); -#endif - return error; -} - -/* - * Unlock everything we locked for rename. - * - * fdvp and tdvp must be referenced. - * - * On entry, everything is locked, and fvp and tvp referenced. - * - * On exit, everything is unlocked, and fvp and tvp are released. - */ -static void -tmpfs_rename_exit(struct tmpfs_mount *tmpfs, - struct vnode *fdvp, struct vnode *fvp, - struct vnode *tdvp, struct vnode *tvp) -{ - - KASSERT(tmpfs != NULL); - KASSERT(fdvp != NULL); - KASSERT(fvp != NULL); - KASSERT(fdvp != fvp); - KASSERT(fdvp != tvp); - KASSERT(tdvp != tvp); - KASSERT(tdvp != fvp); - KASSERT(VOP_ISLOCKED(fdvp) == LK_EXCLUSIVE); - KASSERT(VOP_ISLOCKED(tdvp) == LK_EXCLUSIVE); - KASSERT(VOP_ISLOCKED(fvp) == LK_EXCLUSIVE); - KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) == LK_EXCLUSIVE)); - - if (tvp != NULL) { - if (tvp != fvp) - vput(tvp); - else - vrele(tvp); - } - VOP_UNLOCK(tdvp); - vput(fvp); - if (fdvp != tdvp) - VOP_UNLOCK(fdvp); - -#if 0 /* XXX */ - if (fdvp != tdvp) - mutex_exit(&tmpfs->tm_rename_lock); -#endif -} - -/* - * Lock a directory, but fail if it has been rmdir'd. - * - * vp must be referenced. - */ -static int -tmpfs_rename_lock_directory(struct vnode *vp, struct tmpfs_node *node) -{ - - KASSERT(vp != NULL); - KASSERT(node != NULL); - KASSERT(node->tn_vnode == vp); - KASSERT(node->tn_type == VDIR); - - vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); - if (node->tn_spec.tn_dir.tn_parent == NULL) { - VOP_UNLOCK(vp); - return ENOENT; - } - - return 0; -} - -/* - * Analyze the genealogy of the source and target nodes. - * - * On success, stores in *intermediate_node_ret either the child of - * fdnode of which tdnode is a descendant, or null if tdnode is not a - * descendant of fdnode at all. - * - * fdnode and tdnode must be unlocked and referenced. The file - * system's rename lock must also be held, to exclude concurrent - * changes to the file system's genealogy other than rmdir. - * - * XXX This causes an extra lock/unlock of tdnode in the case when - * we're just about to lock it again before locking anything else. - * However, changing that requires reorganizing the code to make it - * even more horrifically obscure. - */ -static int -tmpfs_rename_genealogy(struct tmpfs_node *fdnode, struct tmpfs_node *tdnode, - struct tmpfs_node **intermediate_node_ret) -{ - struct tmpfs_node *node = tdnode, *parent; - int error; - - KASSERT(fdnode != NULL); - KASSERT(tdnode != NULL); - KASSERT(fdnode != tdnode); - KASSERT(intermediate_node_ret != NULL); - - KASSERT(fdnode->tn_vnode != NULL); - KASSERT(tdnode->tn_vnode != NULL); - KASSERT(fdnode->tn_type == VDIR); - KASSERT(tdnode->tn_type == VDIR); - - /* - * We need to provisionally lock tdnode->tn_vnode to keep rmdir - * from deleting it -- or any ancestor -- at an inopportune - * moment. - */ - error = tmpfs_rename_lock_directory(tdnode->tn_vnode, tdnode); - if (error) - return error; - - for (;;) { - parent = node->tn_spec.tn_dir.tn_parent; - KASSERT(parent != NULL); - KASSERT(parent->tn_type == VDIR); - - /* Did we hit the root without finding fdnode? */ - if (parent == node) { - *intermediate_node_ret = NULL; - break; - } - - /* Did we find that fdnode is an ancestor? */ - if (parent == fdnode) { - *intermediate_node_ret = node; - break; - } - - /* Neither -- keep ascending the family tree. */ - node = parent; - } - - VOP_UNLOCK(tdnode->tn_vnode); - return 0; -} - -/* - * Lock directories a and b, which must be distinct, and look up and - * lock nodes a and b. Do a first and then b. Directory b may not be - * an ancestor of directory a, although directory a may be an ancestor - * of directory b. Fail with overlap_error if node a is directory b. - * Neither componentname may be `.' or `..'. - * - * a_dvp and b_dvp must be referenced. - * - * On entry, a_dvp and b_dvp are unlocked. - * - * On success, - * . a_dvp and b_dvp are locked, - * . *a_dirent_ret is filled with a directory entry whose node is - * locked and referenced, - * . *b_vp_ret is filled with the corresponding vnode, - * . *b_dirent_ret is filled either with null or with a directory entry - * whose node is locked and referenced, - * . *b_vp is filled either with null or with the corresponding vnode, - * and - * . the only pair of vnodes that may be identical is a_vp and b_vp. - * - * On failure, a_dvp and b_dvp are left unlocked, and *a_dirent_ret, - * *a_vp, *b_dirent_ret, and *b_vp are left alone. - */ -static int -tmpfs_rename_lock(struct mount *mount, kauth_cred_t cred, int overlap_error, - struct vnode *a_dvp, struct tmpfs_node *a_dnode, - struct componentname *a_cnp, bool a_missing_ok, - struct tmpfs_dirent **a_dirent_ret, struct vnode **a_vp_ret, - struct vnode *b_dvp, struct tmpfs_node *b_dnode, - struct componentname *b_cnp, bool b_missing_ok, - struct tmpfs_dirent **b_dirent_ret, struct vnode **b_vp_ret) -{ - struct tmpfs_dirent *a_dirent, *b_dirent; - struct vnode *a_vp, *b_vp; - int error; - - KASSERT(a_dvp != NULL); - KASSERT(a_dnode != NULL); - KASSERT(a_cnp != NULL); - KASSERT(a_dirent_ret != NULL); - KASSERT(a_vp_ret != NULL); - KASSERT(b_dvp != NULL); - KASSERT(b_dnode != NULL); - KASSERT(b_cnp != NULL); - KASSERT(b_dirent_ret != NULL); - KASSERT(b_vp_ret != NULL); - KASSERT(a_dvp != b_dvp); - KASSERT(a_dnode != b_dnode); - KASSERT(a_dnode->tn_vnode == a_dvp); - KASSERT(b_dnode->tn_vnode == b_dvp); - KASSERT(a_dnode->tn_type == VDIR); - KASSERT(b_dnode->tn_type == VDIR); - KASSERT(a_missing_ok != b_missing_ok); - - error = tmpfs_rename_lock_directory(a_dvp, a_dnode); - if (error) - goto fail0; - - /* Did we lose a race with mount? */ - if (a_dvp->v_mountedhere != NULL) { - error = EBUSY; - goto fail1; - } - - /* Make sure the caller may read the directory. */ - error = VOP_ACCESS(a_dvp, VEXEC, cred); - if (error) - goto fail1; - - a_dirent = tmpfs_dir_lookup(a_dnode, a_cnp); - if (a_dirent != NULL) { - KASSERT(a_dirent->td_node != NULL); - /* We ruled out `.' earlier. */ - KASSERT(a_dirent->td_node != a_dnode); - /* We ruled out `..' earlier. */ - KASSERT(a_dirent->td_node != - a_dnode->tn_spec.tn_dir.tn_parent); - if (a_dirent->td_node == b_dnode) { - error = overlap_error; - goto fail1; - } - mutex_enter(&a_dirent->td_node->tn_vlock); - error = tmpfs_vnode_get(mount, a_dirent->td_node, &a_vp); - if (error) - goto fail1; - KASSERT(a_vp->v_mount == mount); - /* Refuse to rename (over) a mount point. */ - if ((a_vp->v_type == VDIR) && (a_vp->v_mountedhere != NULL)) { - error = EBUSY; - goto fail2; - } - } else if (!a_missing_ok) { - error = ENOENT; - goto fail1; - } else { - a_vp = NULL; - } - KASSERT(a_vp != a_dvp); - KASSERT(a_vp != b_dvp); - - error = tmpfs_rename_lock_directory(b_dvp, b_dnode); - if (error) - goto fail2; - - /* Did we lose a race with mount? */ - if (b_dvp->v_mountedhere != NULL) { - error = EBUSY; - goto fail3; - } - - /* Make sure the caller may read the directory. */ - error = VOP_ACCESS(b_dvp, VEXEC, cred); - if (error) - goto fail3; - - b_dirent = tmpfs_dir_lookup(b_dnode, b_cnp); - if (b_dirent != NULL) { - KASSERT(b_dirent->td_node != NULL); - /* We ruled out `.' earlier. */ - KASSERT(b_dirent->td_node != b_dnode); - /* We ruled out `..' earlier. */ - KASSERT(b_dirent->td_node != - b_dnode->tn_spec.tn_dir.tn_parent); - /* b is not an ancestor of a. */ - KASSERT(b_dirent->td_node != a_dnode); - /* But the source and target nodes might be the same. */ - if ((a_dirent == NULL) || - (a_dirent->td_node != b_dirent->td_node)) { - mutex_enter(&b_dirent->td_node->tn_vlock); - error = tmpfs_vnode_get(mount, b_dirent->td_node, - &b_vp); - if (error) - goto fail3; - KASSERT(b_vp->v_mount == mount); - KASSERT(a_vp != b_vp); - /* Refuse to rename (over) a mount point. */ - if ((b_vp->v_type == VDIR) && - (b_vp->v_mountedhere != NULL)) { - error = EBUSY; - goto fail4; - } - } else { - b_vp = a_vp; - vref(b_vp); - } - } else if (!b_missing_ok) { - error = ENOENT; - goto fail3; - } else { - b_vp = NULL; - } - KASSERT(b_vp != a_dvp); - KASSERT(b_vp != b_dvp); - - KASSERT(VOP_ISLOCKED(a_dvp) == LK_EXCLUSIVE); - KASSERT(VOP_ISLOCKED(b_dvp) == LK_EXCLUSIVE); - KASSERT(a_missing_ok || (a_dirent != NULL)); - KASSERT(a_missing_ok || (a_dirent->td_node != NULL)); - KASSERT(b_missing_ok || (b_dirent != NULL)); - KASSERT(b_missing_ok || (b_dirent->td_node != NULL)); - KASSERT((a_dirent == NULL) || (a_dirent->td_node != NULL)); - KASSERT((a_dirent == NULL) || (a_dirent->td_node->tn_vnode == a_vp)); - KASSERT((b_dirent == NULL) || (b_dirent->td_node != NULL)); - KASSERT((b_dirent == NULL) || (b_dirent->td_node->tn_vnode == b_vp)); - KASSERT((a_vp == NULL) || (VOP_ISLOCKED(a_vp) == LK_EXCLUSIVE)); - KASSERT((b_vp == NULL) || (VOP_ISLOCKED(b_vp) == LK_EXCLUSIVE)); - - *a_dirent_ret = a_dirent; - *b_dirent_ret = b_dirent; - *a_vp_ret = a_vp; - *b_vp_ret = b_vp; - return 0; - -fail4: if (b_vp != NULL) { - KASSERT(VOP_ISLOCKED(b_vp) == LK_EXCLUSIVE); - if (b_vp != a_vp) - vput(b_vp); - else - vrele(a_vp); - } - -fail3: KASSERT(VOP_ISLOCKED(b_dvp) == LK_EXCLUSIVE); - VOP_UNLOCK(b_dvp); - -fail2: if (a_vp != NULL) { - KASSERT(VOP_ISLOCKED(a_vp) == LK_EXCLUSIVE); - vput(a_vp); - } - -fail1: KASSERT(VOP_ISLOCKED(a_dvp) == LK_EXCLUSIVE); - VOP_UNLOCK(a_dvp); - -fail0: /* KASSERT(VOP_ISLOCKED(a_dvp) != LK_EXCLUSIVE); */ - /* KASSERT(VOP_ISLOCKED(b_dvp) != LK_EXCLUSIVE); */ - /* KASSERT((a_vp == NULL) || (VOP_ISLOCKED(a_vp) != LK_EXCLUSIVE)); */ - /* KASSERT((b_vp == NULL) || (VOP_ISLOCKED(b_vp) != LK_EXCLUSIVE)); */ - return error; -} - -/* - * Shuffle the directory entries to move fvp from the directory fdvp - * into the directory tdvp. fde is fvp's directory entry in fdvp. If - * we are overwriting a target node, it is tvp, and tde is its - * directory entry in tdvp. - * - * fdvp, fvp, tdvp, and tvp must all be locked and referenced. - */ -static void -tmpfs_rename_attachdetach(struct tmpfs_mount *tmpfs, - struct vnode *fdvp, struct tmpfs_dirent *fde, struct vnode *fvp, - struct vnode *tdvp, struct tmpfs_dirent *tde, struct vnode *tvp) -{ - - KASSERT(tmpfs != NULL); - KASSERT(fdvp != NULL); - KASSERT(fde != NULL); - KASSERT(fvp != NULL); - KASSERT(tdvp != NULL); - KASSERT(fde->td_node != NULL); - KASSERT(fde->td_node->tn_vnode == fvp); - KASSERT((tde == NULL) == (tvp == NULL)); - KASSERT((tde == NULL) || (tde->td_node != NULL)); - KASSERT((tde == NULL) || (tde->td_node->tn_vnode == tvp)); - KASSERT(VOP_ISLOCKED(fdvp) == LK_EXCLUSIVE); - KASSERT(VOP_ISLOCKED(tdvp) == LK_EXCLUSIVE); - KASSERT(VOP_ISLOCKED(fvp) == LK_EXCLUSIVE); - KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) == LK_EXCLUSIVE)); - - /* - * If we are moving from one directory to another, detach the - * source entry and reattach it to the target directory. - */ - if (fdvp != tdvp) { - /* tmpfs_dir_detach clobbers fde->td_node, so save it. */ - struct tmpfs_node *fnode = fde->td_node; - tmpfs_dir_detach(fdvp, fde); - tmpfs_dir_attach(tdvp, fde, fnode); - } else if (tvp == NULL) { - /* - * We are changing the directory. tmpfs_dir_attach and - * tmpfs_dir_detach note the events for us, but for - * this case we don't call them, so we must note the - * event explicitly. - */ - VN_KNOTE(fdvp, NOTE_WRITE); - } - - /* - * If we are replacing an existing target entry, delete it. - */ - if (tde != NULL) { - KASSERT(tvp != NULL); - KASSERT(tde->td_node != NULL); - KASSERT((fvp->v_type == VDIR) == (tvp->v_type == VDIR)); - if (tde->td_node->tn_type == VDIR) { - KASSERT(tde->td_node->tn_size == 0); - KASSERT(tde->td_node->tn_links == 2); - /* Decrement the extra link count for `.' so - * the vnode will be recycled when released. */ - tde->td_node->tn_links--; - } - tmpfs_dir_detach(tdvp, tde); - tmpfs_free_dirent(tmpfs, tde); - } -} - -/* - * Remove the entry de for the non-directory vp from the directory dvp. - * - * Everything must be locked and referenced. - */ -static int -tmpfs_do_remove(struct tmpfs_mount *tmpfs, struct vnode *dvp, - struct tmpfs_node *dnode, struct tmpfs_dirent *de, struct vnode *vp, - kauth_cred_t cred) -{ - int error; - - KASSERT(tmpfs != NULL); - KASSERT(dvp != NULL); - KASSERT(dnode != NULL); - KASSERT(de != NULL); - KASSERT(vp != NULL); - KASSERT(dnode->tn_vnode == dvp); - KASSERT(de->td_node != NULL); - KASSERT(de->td_node->tn_vnode == vp); - KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); - KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); - - error = tmpfs_remove_check_possible(dnode, de->td_node); - if (error) - return error; - - error = tmpfs_remove_check_permitted(cred, dnode, de->td_node); - error = kauth_authorize_vnode(cred, KAUTH_VNODE_DELETE, vp, dvp, - error); - if (error) - return error; - - tmpfs_dir_detach(dvp, de); - tmpfs_free_dirent(tmpfs, de); - - return 0; -} - -/* - * Check whether a rename is possible independent of credentials. - * - * Everything must be locked and referenced. - */ -static int -tmpfs_rename_check_possible( - struct tmpfs_node *fdnode, struct tmpfs_node *fnode, - struct tmpfs_node *tdnode, struct tmpfs_node *tnode) -{ - - KASSERT(fdnode != NULL); - KASSERT(fnode != NULL); - KASSERT(tdnode != NULL); - KASSERT(fdnode != fnode); - KASSERT(tdnode != tnode); - KASSERT(fnode != tnode); - KASSERT(fdnode->tn_vnode != NULL); - KASSERT(fnode->tn_vnode != NULL); - KASSERT(tdnode->tn_vnode != NULL); - KASSERT((tnode == NULL) || (tnode->tn_vnode != NULL)); - KASSERT(VOP_ISLOCKED(fdnode->tn_vnode) == LK_EXCLUSIVE); - KASSERT(VOP_ISLOCKED(fnode->tn_vnode) == LK_EXCLUSIVE); - KASSERT(VOP_ISLOCKED(tdnode->tn_vnode) == LK_EXCLUSIVE); - KASSERT((tnode == NULL) || - (VOP_ISLOCKED(tnode->tn_vnode) == LK_EXCLUSIVE)); - - /* - * If fdnode is immutable, we can't write to it. If fdnode is - * append-only, the only change we can make is to add entries - * to it. If fnode is immutable, we can't change the links to - * it. If fnode is append-only...well, this is what UFS does. - */ - if ((fdnode->tn_flags | fnode->tn_flags) & (IMMUTABLE | APPEND)) - return EPERM; - - /* - * If tdnode is immutable, we can't write to it. If tdnode is - * append-only, we can add entries, but we can't change - * existing entries. - */ - if (tdnode->tn_flags & (IMMUTABLE | (tnode? APPEND : 0))) - return EPERM; - - /* - * If tnode is immutable, we can't replace links to it. If - * tnode is append-only...well, this is what UFS does. - */ - if (tnode != NULL) { - KASSERT(tnode != NULL); - if ((tnode->tn_flags & (IMMUTABLE | APPEND)) != 0) - return EPERM; - } - - return 0; -} - -/* - * Check whether a rename is permitted given our credentials. - * - * Everything must be locked and referenced. - */ -static int -tmpfs_rename_check_permitted(kauth_cred_t cred, - struct tmpfs_node *fdnode, struct tmpfs_node *fnode, - struct tmpfs_node *tdnode, struct tmpfs_node *tnode) -{ - int error; - - KASSERT(fdnode != NULL); - KASSERT(fnode != NULL); - KASSERT(tdnode != NULL); - KASSERT(fdnode != fnode); - KASSERT(tdnode != tnode); - KASSERT(fnode != tnode); - KASSERT(fdnode->tn_vnode != NULL); - KASSERT(fnode->tn_vnode != NULL); - KASSERT(tdnode->tn_vnode != NULL); - KASSERT((tnode == NULL) || (tnode->tn_vnode != NULL)); - KASSERT(VOP_ISLOCKED(fdnode->tn_vnode) == LK_EXCLUSIVE); - KASSERT(VOP_ISLOCKED(fnode->tn_vnode) == LK_EXCLUSIVE); - KASSERT(VOP_ISLOCKED(tdnode->tn_vnode) == LK_EXCLUSIVE); - KASSERT((tnode == NULL) || - (VOP_ISLOCKED(tnode->tn_vnode) == LK_EXCLUSIVE)); - - /* - * We need to remove or change an entry in the source directory. - */ - error = VOP_ACCESS(fdnode->tn_vnode, VWRITE, cred); - if (error) - return error; - - /* - * If we are changing directories, then we need to write to the - * target directory to add or change an entry. Also, if fnode - * is a directory, we need to write to it to change its `..' - * entry. - */ - if (fdnode != tdnode) { - error = VOP_ACCESS(tdnode->tn_vnode, VWRITE, cred); - if (error) - return error; - if (fnode->tn_type == VDIR) { - error = VOP_ACCESS(fnode->tn_vnode, VWRITE, cred); - if (error) - return error; - } - } - - error = tmpfs_check_sticky(cred, fdnode, fnode); - if (error) - return error; - - error = tmpfs_check_sticky(cred, tdnode, tnode); - if (error) - return error; - - return 0; -} - -/* - * Check whether removing node's entry in dnode is possible independent - * of credentials. - * - * Everything must be locked and referenced. - */ -static int -tmpfs_remove_check_possible(struct tmpfs_node *dnode, struct tmpfs_node *node) -{ - - KASSERT(dnode != NULL); - KASSERT(dnode->tn_vnode != NULL); - KASSERT(node != NULL); - KASSERT(dnode != node); - KASSERT(VOP_ISLOCKED(dnode->tn_vnode) == LK_EXCLUSIVE); - KASSERT(VOP_ISLOCKED(node->tn_vnode) == LK_EXCLUSIVE); - - /* - * We want to delete the entry. If dnode is immutable, we - * can't write to it to delete the entry. If dnode is - * append-only, the only change we can make is to add entries, - * so we can't delete entries. If node is immutable, we can't - * change the links to it, so we can't delete the entry. If - * node is append-only...well, this is what UFS does. - */ - if ((dnode->tn_flags | node->tn_flags) & (IMMUTABLE | APPEND)) - return EPERM; - - return 0; -} - -/* - * Check whether removing node's entry in dnode is permitted given our - * credentials. - * - * Everything must be locked and referenced. - */ -static int -tmpfs_remove_check_permitted(kauth_cred_t cred, - struct tmpfs_node *dnode, struct tmpfs_node *node) -{ - int error; - - KASSERT(dnode != NULL); - KASSERT(dnode->tn_vnode != NULL); - KASSERT(node != NULL); - KASSERT(dnode != node); - KASSERT(VOP_ISLOCKED(dnode->tn_vnode) == LK_EXCLUSIVE); - KASSERT(VOP_ISLOCKED(node->tn_vnode) == LK_EXCLUSIVE); - - /* - * Check whether we are permitted to write to the source - * directory in order to delete an entry from it. - */ - error = VOP_ACCESS(dnode->tn_vnode, VWRITE, cred); - if (error) - return error; - - error = tmpfs_check_sticky(cred, dnode, node); - if (error) - return error; - - return 0; -} - -/* - * Check whether we may change an entry in a sticky directory. If the - * directory is sticky, the user must own either the directory or, if - * it exists, the node, in order to change the entry. - * - * Everything must be locked and referenced. - */ -static int -tmpfs_check_sticky(kauth_cred_t cred, - struct tmpfs_node *dnode, struct tmpfs_node *node) -{ - - KASSERT(dnode != NULL); - KASSERT(dnode->tn_vnode != NULL); - KASSERT(VOP_ISLOCKED(dnode->tn_vnode) == LK_EXCLUSIVE); - KASSERT((node == NULL) || (node->tn_vnode != NULL)); - KASSERT((node == NULL) || - (VOP_ISLOCKED(dnode->tn_vnode) == LK_EXCLUSIVE)); - - if (node == NULL) - return 0; - - if (dnode->tn_mode & S_ISTXT) { - if (kauth_authorize_vnode(cred, KAUTH_VNODE_DELETE, - node->tn_vnode, dnode->tn_vnode, genfs_can_sticky(cred, - dnode->tn_uid, node->tn_uid)) != 0) - return EPERM; - } - - return 0; -} - int tmpfs_mkdir(void *v) { Index: sys/rump/fs/lib/libtmpfs/Makefile =================================================================== RCS file: /cvsroot/src/sys/rump/fs/lib/libtmpfs/Makefile,v retrieving revision 1.4 diff -p -u -r1.4 Makefile --- sys/rump/fs/lib/libtmpfs/Makefile 22 Jun 2010 18:32:08 -0000 1.4 +++ sys/rump/fs/lib/libtmpfs/Makefile 7 Apr 2012 21:19:56 -0000 @@ -6,7 +6,7 @@ LIB= rumpfs_tmpfs SRCS= tmpfs_fifoops.c tmpfs_specops.c tmpfs_vfsops.c tmpfs_mem.c \ - tmpfs_subr.c tmpfs_vnops.c + tmpfs_rename.c tmpfs_subr.c tmpfs_vnops.c .include .include