Module Name: src Committed By: hannken Date: Sat Oct 23 07:45:03 UTC 2021
Modified Files: src/sys/fs/msdosfs: denode.h msdosfs_rename.c Log Message: Convert msdosfs_rename() to use genfs_sane_rename(). Based on work by Taylor R Campbell. To generate a diff of this commit: cvs rdiff -u -r1.27 -r1.28 src/sys/fs/msdosfs/denode.h cvs rdiff -u -r1.1 -r1.2 src/sys/fs/msdosfs/msdosfs_rename.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/fs/msdosfs/denode.h diff -u src/sys/fs/msdosfs/denode.h:1.27 src/sys/fs/msdosfs/denode.h:1.28 --- src/sys/fs/msdosfs/denode.h:1.27 Sat Oct 23 07:38:33 2021 +++ src/sys/fs/msdosfs/denode.h Sat Oct 23 07:45:03 2021 @@ -1,4 +1,4 @@ -/* $NetBSD: denode.h,v 1.27 2021/10/23 07:38:33 hannken Exp $ */ +/* $NetBSD: denode.h,v 1.28 2021/10/23 07:45:03 hannken Exp $ */ /*- * Copyright (C) 1994, 1995, 1997 Wolfgang Solfrank. @@ -318,7 +318,6 @@ int deget(struct msdosfsmount *, u_long, #endif int detrunc(struct denode *, u_long, int, struct kauth_cred *); int deupdat(struct denode *, int); -int doscheckpath(struct denode *, struct denode *); int dosdirempty(struct denode *); int readde(struct denode *, struct buf **, struct direntry **); int readep(struct msdosfsmount *, u_long, u_long, Index: src/sys/fs/msdosfs/msdosfs_rename.c diff -u src/sys/fs/msdosfs/msdosfs_rename.c:1.1 src/sys/fs/msdosfs/msdosfs_rename.c:1.2 --- src/sys/fs/msdosfs/msdosfs_rename.c:1.1 Sat Oct 23 07:41:37 2021 +++ src/sys/fs/msdosfs/msdosfs_rename.c Sat Oct 23 07:45:03 2021 @@ -1,10 +1,11 @@ -/* $NetBSD: msdosfs_rename.c,v 1.1 2021/10/23 07:41:37 hannken Exp $ */ +/* $NetBSD: msdosfs_rename.c,v 1.2 2021/10/23 07:45:03 hannken Exp $ */ /*- - * Copyright (C) 1994, 1995, 1997 Wolfgang Solfrank. - * Copyright (C) 1994, 1995, 1997 TooLs GmbH. + * Copyright (c) 2011 The NetBSD Foundation, Inc. * All rights reserved. - * Original code by Paul Popelka (pa...@uts.amdahl.com) (see below). + * + * 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 @@ -14,60 +15,36 @@ * 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. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by TooLs GmbH. - * 4. The name of TooLs GmbH may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``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 TOOLS GMBH 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. - */ -/* - * Written by Paul Popelka (pa...@uts.amdahl.com) - * - * You can do anything you want with this software, just don't say you wrote - * it, and don't remove this notice. - * - * This software is provided "as is". - * - * The author supplies this software to be publicly redistributed on the - * understanding that the author is not responsible for the correct - * functioning of this software in any circumstances and is not liable for - * any damages caused by this software. * - * October 1992 + * 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. */ +/* + * MS-DOS FS Rename + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: msdosfs_rename.c,v 1.2 2021/10/23 07:45:03 hannken Exp $"); + #include <sys/param.h> -#include <sys/systm.h> -#include <sys/namei.h> -#include <sys/resourcevar.h> /* defines plimit structure in proc struct */ -#include <sys/kernel.h> -#include <sys/file.h> /* define FWRITE ... */ -#include <sys/stat.h> #include <sys/buf.h> -#include <sys/proc.h> -#include <sys/mount.h> -#include <sys/vnode.h> -#include <sys/signalvar.h> -#include <sys/malloc.h> -#include <sys/dirent.h> -#include <sys/lockf.h> +#include <sys/errno.h> #include <sys/kauth.h> +#include <sys/namei.h> +#include <sys/vnode.h> +#include <sys/vnode_if.h> #include <miscfs/genfs/genfs.h> -#include <miscfs/specfs/specdev.h> /* XXX */ /* defines v_rdev */ - -#include <uvm/uvm_extern.h> #include <fs/msdosfs/bpb.h> #include <fs/msdosfs/direntry.h> @@ -75,10 +52,50 @@ #include <fs/msdosfs/msdosfsmount.h> #include <fs/msdosfs/fat.h> +/* + * Forward declarations + */ + +static int msdosfs_sane_rename(struct vnode *, struct componentname *, + struct vnode *, struct componentname *, + kauth_cred_t, bool); +static bool msdosfs_rmdired_p(struct vnode *); +static int msdosfs_read_dotdot(struct vnode *, kauth_cred_t, unsigned long *); +static int msdosfs_rename_replace_dotdot(struct vnode *, + struct vnode *, struct vnode *, kauth_cred_t); +static int msdosfs_gro_lock_directory(struct mount *, struct vnode *); + +static const struct genfs_rename_ops msdosfs_genfs_rename_ops; + +/* + * msdosfs_rename: The hairiest vop, with the insanest API. + * + * Arguments: + * + * . fdvp (from directory vnode), + * . fvp (from vnode), + * . fcnp (from component name), + * . tdvp (to directory vnode), + * . tvp (to vnode, or NULL), and + * . tcnp (to component name). + * + * Any pair of vnode parameters may have the same vnode. + * + * On entry, + * + * . fdvp, fvp, tdvp, and tvp are referenced, + * . fdvp and fvp are unlocked, and + * . tdvp and tvp (if nonnull) are locked. + * + * On exit, + * + * . fdvp, fvp, tdvp, and tvp (if nonnull) are unreferenced, and + * . tdvp and tvp are unlocked. + */ int msdosfs_rename(void *v) { - struct vop_rename_args /* { + struct vop_rename_args /* { struct vnode *a_fdvp; struct vnode *a_fvp; struct componentname *a_fcnp; @@ -86,428 +103,740 @@ msdosfs_rename(void *v) struct vnode *a_tvp; struct componentname *a_tcnp; } */ *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 vnode *fvp = ap->a_fvp; struct componentname *fcnp = ap->a_fcnp; - struct denode *ip, *xp, *dp, *zp; - u_char toname[12], oldname[12]; - u_long from_diroffset, to_diroffset; - u_char to_count; - int doingdirectory = 0, newparent = 0; + struct vnode *tdvp = ap->a_tdvp; + struct vnode *tvp = ap->a_tvp; + struct componentname *tcnp = ap->a_tcnp; + kauth_cred_t cred; int error; - u_long cn; - daddr_t bn; + + 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 = msdosfs_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; +} + +/* + * 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; + + return genfs_sane_rename(&msdosfs_genfs_rename_ops, + fdvp, fcnp, &fmlr, tdvp, tcnp, &tmlr, + cred, posixly_correct); +} + +/* + * 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) +{ + + (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 dosdirempty(VTODE(vp)); +} + +/* + * Return a UFS-like mode for vp. + */ +static mode_t +msdosfs_vnode_mode(struct vnode *vp) +{ struct msdosfsmount *pmp; - struct direntry *dotdotp; - struct buf *bp; + mode_t mode, mask; - pmp = VFSTOMSDOSFS(fdvp->v_mount); + KASSERT(vp != NULL); - /* - * Check for cross-device rename. + pmp = VTODE(vp)->de_pmp; + KASSERT(pmp != NULL); + + if (VTODE(vp)->de_Attributes & ATTR_READONLY) + mode = S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH; + else + mode = S_IRWXU|S_IRWXG|S_IRWXO; + + if (vp->v_type == VDIR) + mask = pmp->pm_dirmask; + else + mask = pmp->pm_mask; + + return (mode & mask); +} + +/* + * 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) +{ + + (void)mp; + (void)fdvp; + (void)fvp; + (void)tdvp; + (void)tvp; + 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_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)); + + /* It's always possible: no error. */ + return 0; +} + +/* + * msdosfs_gro_rename_check_permitted: ... + */ +static int +msdosfs_gro_rename_check_permitted(struct mount *mp, kauth_cred_t cred, + struct vnode *fdvp, struct vnode *fvp, + struct vnode *tdvp, struct vnode *tvp) +{ + struct msdosfsmount *pmp; + + 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_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)); + + pmp = VFSTOMSDOSFS(mp); + KASSERT(pmp != NULL); + + return genfs_ufslike_rename_check_permitted(cred, + fdvp, msdosfs_vnode_mode(fdvp), pmp->pm_uid, + fvp, pmp->pm_uid, + tdvp, msdosfs_vnode_mode(tdvp), pmp->pm_uid, + tvp, (tvp? pmp->pm_uid : 0)); +} + +/* + * msdosfs_gro_remove_check_possible: ... + */ +static int +msdosfs_gro_remove_check_possible(struct mount *mp, + struct vnode *dvp, struct vnode *vp) +{ + + KASSERT(mp != NULL); + KASSERT(dvp != NULL); + KASSERT(vp != NULL); + KASSERT(dvp != vp); + KASSERT(dvp->v_mount == mp); + KASSERT(vp->v_mount == mp); + KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); + KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); + + /* It's always possible: no error. */ + return 0; +} + +/* + * msdosfs_gro_remove_check_permitted: ... + */ +static int +msdosfs_gro_remove_check_permitted(struct mount *mp, kauth_cred_t cred, + struct vnode *dvp, struct vnode *vp) +{ + struct msdosfsmount *pmp; + + KASSERT(mp != NULL); + KASSERT(dvp != NULL); + KASSERT(vp != NULL); + KASSERT(dvp != vp); + KASSERT(dvp->v_mount == mp); + KASSERT(vp->v_mount == mp); + KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); + KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); + + pmp = VFSTOMSDOSFS(mp); + KASSERT(pmp != NULL); + + return genfs_ufslike_remove_check_permitted(cred, + dvp, msdosfs_vnode_mode(dvp), pmp->pm_uid, vp, pmp->pm_uid); +} + +/* + * msdosfs_gro_rename: Actually perform the rename operation. + */ +static int +msdosfs_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, nlink_t *tvp_nlinkp) +{ + struct msdosfs_lookup_results *fmlr = fde; + struct msdosfs_lookup_results *tmlr = tde; + struct msdosfsmount *pmp; + bool directory_p, reparent_p; + unsigned char toname[12], oldname[12]; + int error; + + KASSERT(mp != NULL); + KASSERT(fdvp != NULL); + KASSERT(fcnp != NULL); + KASSERT(fmlr != NULL); + KASSERT(fvp != NULL); + KASSERT(tdvp != NULL); + KASSERT(tcnp != NULL); + KASSERT(tmlr != NULL); + KASSERT(fmlr != tmlr); + 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)); + + /* + * 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. + */ + + directory_p = (fvp->v_type == VDIR); + KASSERT(directory_p == + ((VTODE(fvp)->de_Attributes & ATTR_DIRECTORY) != 0)); + KASSERT((tvp == NULL) || (directory_p == (tvp->v_type == VDIR))); + KASSERT((tvp == NULL) || (directory_p == + ((VTODE(fvp)->de_Attributes & ATTR_DIRECTORY) != 0))); + if (directory_p) { + if (VTODE(fvp)->de_flag & DE_RENAME) + return EINVAL; + VTODE(fvp)->de_flag |= DE_RENAME; + } + + reparent_p = (fdvp != tdvp); + KASSERT(reparent_p == (VTODE(fdvp)->de_StartCluster != + VTODE(tdvp)->de_StartCluster)); + + /* + * 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 ((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 != NULL) { + error = removede(VTODE(tdvp), VTODE(tvp), tmlr); + if (error) + goto out; } /* - * If source and dest are the same, do nothing. + * Convert the filename in tcnp into a dos filename. We copy this + * into the denode and directory entry for the destination + * file/directory. */ - if (tvp == fvp) { - error = 0; - goto abortit; + error = 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 = 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; } /* - * XXX: This can deadlock since we hold tdvp/tvp locked. - * But I'm not going to fix it now. + * createde doesn't increment de_refcnt, but removede + * decrements it. Go figure. */ - if ((error = vn_lock(fvp, LK_EXCLUSIVE)) != 0) - goto abortit; - dp = VTODE(fdvp); - ip = VTODE(fvp); - - /* - * Be sure we are not renaming ".", "..", or an alias of ".". This - * leads to a crippled directory tree. It's pretty tough to do a - * "ls" or "pwd" with the "." directory entry missing, and "cd .." - * doesn't work if the ".." entry is missing. - */ - if (ip->de_Attributes & ATTR_DIRECTORY) { - /* - * 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->de_flag & DE_RENAME)) { - VOP_UNLOCK(fvp); - error = EINVAL; - goto abortit; - } - ip->de_flag |= DE_RENAME; - doingdirectory++; - } - VN_KNOTE(fdvp, NOTE_WRITE); /* XXXLUKEM/XXX: right place? */ + KASSERT(VTODE(fvp)->de_refcnt < LONG_MAX); + VTODE(fvp)->de_refcnt++; /* - * When the target exists, both the directory - * and target vnodes are returned locked. + * XXX Yes, createde and removede have arguments swapped. Go figure. */ - dp = VTODE(tdvp); - xp = tvp ? VTODE(tvp) : NULL; - /* - * Remember direntry place to use for destination - */ - to_diroffset = dp->de_crap.mlr_fndoffset; - to_count = dp->de_crap.mlr_fndcnt; - - /* - * 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 doscheckpath(). - */ - error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred); - VOP_UNLOCK(fvp); - if (VTODE(fdvp)->de_StartCluster != VTODE(tdvp)->de_StartCluster) - newparent = 1; - - if (doingdirectory && newparent) { - if (error) /* write access check above */ - goto tdvpbad; - if (xp != NULL) - vput(tvp); - tvp = NULL; - /* - * doscheckpath() vput()'s tdvp (dp == VTODE(tdvp)), - * so we have to get an extra ref to it first, and - * because it's been unlocked we need to do a relookup - * afterwards in case tvp has changed. - */ - vref(tdvp); - if ((error = doscheckpath(ip, dp)) != 0) - goto bad; - vn_lock(tdvp, LK_EXCLUSIVE | LK_RETRY); - if ((error = relookup(tdvp, &tvp, tcnp, 0)) != 0) { - VOP_UNLOCK(tdvp); - goto bad; - } - dp = VTODE(tdvp); - xp = tvp ? VTODE(tvp) : NULL; + error = removede(VTODE(fdvp), VTODE(fvp), fmlr); + if (error) { +#if 0 /* XXX Back out the new directory entry? Panic? */ + (void)removede(VTODE(tdvp), VTODE(fvp), tmlr); + memcpy(VTODE(fvp)->de_Name, oldname, 11); +#endif + goto out; } - if (xp != NULL) { - /* - * 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->de_Attributes & ATTR_DIRECTORY) { - if (!dosdirempty(xp)) { - error = ENOTEMPTY; - goto tdvpbad; - } - if (!doingdirectory) { - error = ENOTDIR; - goto tdvpbad; - } - } else if (doingdirectory) { - error = EISDIR; - goto tdvpbad; - } - if ((error = removede(dp, xp, &dp->de_crap)) != 0) - goto tdvpbad; - VN_KNOTE(tdvp, NOTE_WRITE); - VN_KNOTE(tvp, NOTE_DELETE); - cache_purge(tvp); - vput(tvp); - tvp = NULL; - xp = NULL; + pmp = VFSTOMSDOSFS(mp); + + if (!directory_p) { + struct denode_key old_key = VTODE(fvp)->de_key; + struct denode_key new_key = VTODE(fvp)->de_key; + + error = pcbmap(VTODE(tdvp), + de_cluster(pmp, tmlr->mlr_fndoffset), NULL, + &new_key.dk_dirclust, NULL); + if (error) /* XXX Back everything out? Panic? */ + goto out; + new_key.dk_diroffset = tmlr->mlr_fndoffset; + if (new_key.dk_dirclust != MSDOSFSROOT) + new_key.dk_diroffset &= pmp->pm_crbomask; + vcache_rekey_enter(pmp->pm_mountp, fvp, &old_key, + sizeof(old_key), &new_key, sizeof(new_key)); + VTODE(fvp)->de_key = new_key; + vcache_rekey_exit(pmp->pm_mountp, fvp, &old_key, + sizeof(old_key), &VTODE(fvp)->de_key, + sizeof(VTODE(fvp)->de_key)); } /* - * Convert the filename in tcnp into a dos filename. We copy this - * into the denode and directory entry for the destination - * file/directory. + * If we moved a directory to a new parent directory, then we must + * fixup the ".." entry in the moved directory. */ - if ((error = uniqdosname(VTODE(tdvp), tcnp, toname)) != 0) { - goto abortit; + if (directory_p && reparent_p) { + error = msdosfs_rename_replace_dotdot(fvp, fdvp, tdvp, cred); + if (error) + goto out; } - /* - * Since from wasn't locked at various places above, - * have to do a relookup here. - */ - fcnp->cn_flags &= ~MODMASK; - fcnp->cn_flags |= LOCKPARENT | LOCKLEAF; - VOP_UNLOCK(tdvp); - vn_lock(fdvp, LK_EXCLUSIVE | LK_RETRY); - if ((error = relookup(fdvp, &fvp, fcnp, 0))) { - VOP_UNLOCK(fdvp); - vrele(ap->a_fvp); - vrele(tdvp); - return (error); - } - if (fvp == NULL) { - /* - * From name has disappeared. - */ - if (doingdirectory) - panic("rename: lost dir entry"); - vput(fdvp); - vrele(ap->a_fvp); - vrele(tdvp); - return 0; - } - VOP_UNLOCK(fdvp); - xp = VTODE(fvp); - zp = VTODE(fdvp); - from_diroffset = zp->de_crap.mlr_fndoffset; - - /* - * Ensure that the directory entry still exists and has not - * changed till now. If the source is a file 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 or renamed; this is - * prohibited by the DE_RENAME flag. - */ - if (xp != ip) { - if (doingdirectory) - panic("rename: lost dir entry"); - vrele(ap->a_fvp); - xp = NULL; - } else { - vrele(fvp); - xp = NULL; +out:; + if (tvp != NULL) + *tvp_nlinkp = (error ? 1 : 0); - /* - * 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. - */ - memcpy(oldname, ip->de_Name, 11); - memcpy(ip->de_Name, toname, 11); /* update denode */ - dp->de_crap.mlr_fndoffset = to_diroffset; - dp->de_crap.mlr_fndcnt = to_count; - error = createde(ip, dp, &dp->de_crap, (struct denode **)0, - tcnp); - if (error) { - memcpy(ip->de_Name, oldname, 11); - VOP_UNLOCK(fvp); - goto bad; - } - ip->de_refcnt++; - zp->de_crap.mlr_fndoffset = from_diroffset; - if ((error = removede(zp, ip, &zp->de_crap)) != 0) { - /* XXX should really panic here, fs is corrupt */ - VOP_UNLOCK(fvp); - goto bad; - } - cache_purge(fvp); - if (!doingdirectory) { - struct denode_key old_key = ip->de_key; - struct denode_key new_key = ip->de_key; - - error = pcbmap(dp, de_cluster(pmp, to_diroffset), 0, - &new_key.dk_dirclust, 0); - if (error) { - /* XXX should really panic here, fs is corrupt */ - VOP_UNLOCK(fvp); - goto bad; - } - new_key.dk_diroffset = to_diroffset; - if (new_key.dk_dirclust != MSDOSFSROOT) - new_key.dk_diroffset &= pmp->pm_crbomask; - vcache_rekey_enter(pmp->pm_mountp, fvp, &old_key, - sizeof(old_key), &new_key, sizeof(new_key)); - ip->de_key = new_key; - vcache_rekey_exit(pmp->pm_mountp, fvp, &old_key, - sizeof(old_key), &ip->de_key, sizeof(ip->de_key)); - } + genfs_rename_cache_purge(fdvp, fvp, tdvp, tvp); + + 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; + + KASSERT(mp != NULL); + KASSERT(dvp != NULL); + KASSERT(cnp != NULL); + KASSERT(mlr != 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); + + error = removede(VTODE(dvp), VTODE(vp), mlr); + + *tvp_nlinkp = (error ? 1 : 0); + + return error; +} + +/* + * msdosfs_gro_lookup: Look up and save the lookup results. + */ +static int +msdosfs_gro_lookup(struct mount *mp, struct vnode *dvp, + struct componentname *cnp, void *de_ret, struct vnode **vp_ret) +{ + struct msdosfs_lookup_results *mlr_ret = de_ret; + struct vnode *vp; + int error; + + (void)mp; + KASSERT(mp != NULL); + KASSERT(dvp != NULL); + KASSERT(cnp != NULL); + KASSERT(mlr_ret != NULL); + KASSERT(vp_ret != NULL); + KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); + + /* Kludge cargo-culted from dholland's ufs_rename. */ + cnp->cn_flags &=~ MODMASK; + cnp->cn_flags |= (LOCKPARENT | LOCKLEAF); + + error = relookup(dvp, &vp, cnp, 0); + if ((error == 0) && (vp == NULL)) { + error = ENOENT; + goto out; } + if (error) + return error; /* - * If we moved a directory to a new parent directory, then we must - * fixup the ".." entry in the moved directory. + * Thanks to VFS insanity, relookup locks vp, which screws us + * in various ways. */ - if (doingdirectory && newparent) { - cn = ip->de_StartCluster; - if (cn == MSDOSFSROOT) { - /* this should never happen */ - panic("msdosfs_rename: updating .. in root directory?"); - } else - bn = cntobn(pmp, cn); - error = bread(pmp->pm_devvp, de_bn2kb(pmp, bn), - pmp->pm_bpcluster, B_MODIFY, &bp); + VOP_UNLOCK(vp); + +out: + *mlr_ret = VTODE(dvp)->de_crap; + *vp_ret = vp; + return 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) +{ + + KASSERT(vp != NULL); + KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); + KASSERT(vp->v_type == VDIR); + + return (VTODE(vp)->de_FileSize == 0); +} + +/* + * msdosfs_gro_genealogy: Analyze the genealogy of the source and target + * directories. + */ +static int +msdosfs_gro_genealogy(struct mount *mp, kauth_cred_t cred, + struct vnode *fdvp, struct vnode *tdvp, + struct vnode **intermediate_node_ret) +{ + struct msdosfsmount *pmp; + struct vnode *vp, *dvp; + unsigned long dotdot_cn; + int error; + + 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); + + pmp = VFSTOMSDOSFS(mp); + KASSERT(pmp != NULL); + + /* + * 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; + } + + error = msdosfs_read_dotdot(vp, cred, &dotdot_cn); if (error) { - /* XXX should really panic here, fs is corrupt */ - VOP_UNLOCK(fvp); - goto bad; + vput(vp); + return error; } - dotdotp = (struct direntry *)bp->b_data + 1; - putushort(dotdotp->deStartCluster, dp->de_StartCluster); - if (FAT32(pmp)) { - putushort(dotdotp->deHighClust, - dp->de_StartCluster >> 16); - } else { - putushort(dotdotp->deHighClust, 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 ((error = bwrite(bp)) != 0) { - /* XXX should really panic here, fs is corrupt */ - VOP_UNLOCK(fvp); - goto bad; + + /* Neither -- keep ascending. */ + + error = deget(pmp, dotdot_cn, (dotdot_cn ? 0 : MSDOSFSROOT_OFS), &dvp); + vput(vp); + if (error) + return error; + error = vn_lock(dvp, LK_EXCLUSIVE); + if (error) { + vrele(dvp); + return error; } - } - VN_KNOTE(fvp, NOTE_RENAME); - VOP_UNLOCK(fvp); -bad: - if (tvp) - vput(tvp); - vrele(tdvp); - ip->de_flag &= ~DE_RENAME; - vrele(fdvp); - vrele(fvp); - return (error); + KASSERT(dvp != NULL); + KASSERT(dvp->v_type == VDIR); - /* XXX: uuuh */ -tdvpbad: - VOP_UNLOCK(tdvp); - goto bad; + vp = dvp; + + if (msdosfs_rmdired_p(vp)) { + vput(vp); + return ENOENT; + } + } } /* - * Check to see if the directory described by target is in some - * subdirectory of source. This prevents something like the following from - * succeeding and leaving a bunch or files and directories orphaned. mv - * /a/b/c /a/b/c/d/e/f Where c and f are directories. - * - * source - the inode for /a/b/c - * target - the inode for /a/b/c/d/e/f - * - * Returns 0 if target is NOT a subdirectory of source. - * Otherwise returns a non-zero error number. - * The target inode is always unlocked on return. + * msdosfs_read_dotdot: Store in *cn_ret the cluster number of the + * parent of the directory vp. */ -int -doscheckpath(struct denode *source, struct denode *target) +static int +msdosfs_read_dotdot(struct vnode *vp, kauth_cred_t cred, unsigned long *cn_ret) { - u_long scn; struct msdosfsmount *pmp; + unsigned long start_cn, cn; + struct buf *bp; struct direntry *ep; - struct denode *dep; - struct buf *bp = NULL; - int error = 0; - - dep = target; - if ((target->de_Attributes & ATTR_DIRECTORY) == 0 || - (source->de_Attributes & ATTR_DIRECTORY) == 0) { + int error; + + KASSERT(vp != NULL); + KASSERT(cn_ret != NULL); + KASSERT(vp->v_type == VDIR); + KASSERT(VTODE(vp) != NULL); + + pmp = VTODE(vp)->de_pmp; + KASSERT(pmp != NULL); + + start_cn = VTODE(vp)->de_StartCluster; + error = bread(pmp->pm_devvp, de_bn2kb(pmp, cntobn(pmp, start_cn)), + pmp->pm_bpcluster, 0, &bp); + if (error) + return error; + + ep = (struct direntry *)bp->b_data + 1; + if (((ep->deAttributes & ATTR_DIRECTORY) == ATTR_DIRECTORY) && + (memcmp(ep->deName, ".. ", 11) == 0)) { + cn = getushort(ep->deStartCluster); + if (FAT32(pmp)) + cn |= getushort(ep->deHighClust) << 16; + *cn_ret = cn; + error = 0; + } else { error = ENOTDIR; - goto out; } - if (dep->de_StartCluster == source->de_StartCluster) { - error = EEXIST; - goto out; + + brelse(bp, 0); + + return error; +} + +/* + * msdosfs_rename_replace_dotdot: Change the target of the `..' entry of + * the directory vp from fdvp to tdvp. + */ +static int +msdosfs_rename_replace_dotdot(struct vnode *vp, + struct vnode *fdvp, struct vnode *tdvp, + kauth_cred_t cred) +{ + struct msdosfsmount *pmp; + struct direntry *dotdotp; + struct buf *bp; + daddr_t bn; + u_long cn; + int error; + + pmp = VFSTOMSDOSFS(fdvp->v_mount); + + cn = VTODE(vp)->de_StartCluster; + if (cn == MSDOSFSROOT) { + /* this should never happen */ + panic("msdosfs_rename: updating .. in root directory?"); + } else + bn = cntobn(pmp, cn); + + error = bread(pmp->pm_devvp, de_bn2kb(pmp, bn), + pmp->pm_bpcluster, B_MODIFY, &bp); + if (error) + return error; + + dotdotp = (struct direntry *)bp->b_data + 1; + putushort(dotdotp->deStartCluster, VTODE(tdvp)->de_StartCluster); + if (FAT32(pmp)) { + putushort(dotdotp->deHighClust, + VTODE(tdvp)->de_StartCluster >> 16); + } else { + putushort(dotdotp->deHighClust, 0); } - if (dep->de_StartCluster == MSDOSFSROOT) - goto out; - pmp = dep->de_pmp; -#ifdef DIAGNOSTIC - if (pmp != source->de_pmp) - panic("doscheckpath: source and target on different filesystems"); -#endif - if (FAT32(pmp) && dep->de_StartCluster == pmp->pm_rootdirblk) - goto out; - for (;;) { - if ((dep->de_Attributes & ATTR_DIRECTORY) == 0) { - error = ENOTDIR; - break; - } - scn = dep->de_StartCluster; - error = bread(pmp->pm_devvp, de_bn2kb(pmp, cntobn(pmp, scn)), - pmp->pm_bpcluster, 0, &bp); - if (error) - break; + error = bwrite(bp); - ep = (struct direntry *) bp->b_data + 1; - if ((ep->deAttributes & ATTR_DIRECTORY) == 0 || - memcmp(ep->deName, ".. ", 11) != 0) { - error = ENOTDIR; - break; - } - scn = getushort(ep->deStartCluster); - if (FAT32(pmp)) - scn |= getushort(ep->deHighClust) << 16; + return error; +} - if (scn == source->de_StartCluster) { - error = EINVAL; - break; - } - if (scn == MSDOSFSROOT) - break; - if (FAT32(pmp) && scn == pmp->pm_rootdirblk) { - /* - * scn should be 0 in this case, - * but we silently ignore the error. - */ - break; - } +/* + * 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; - vput(DETOV(dep)); - brelse(bp, 0); - bp = NULL; -#ifdef MAKEFS - /* NOTE: deget() clears dep on error */ - if ((error = deget(pmp, scn, 0, &dep)) != 0) - break; -#else - struct vnode *vp; + (void)mp; + KASSERT(vp != NULL); - dep = NULL; - error = deget(pmp, scn, 0, &vp); - if (error) - break; - error = vn_lock(vp, LK_EXCLUSIVE); - if (error) { - vrele(vp); - break; - } - dep = VTODE(vp); -#endif + 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; } -out: - if (bp) - brelse(bp, 0); - if (error == ENOTDIR) - printf("doscheckpath(): .. not a directory?\n"); - if (dep != NULL) - vput(DETOV(dep)); - return (error); + + return 0; } + +static const struct genfs_rename_ops msdosfs_genfs_rename_ops = { + .gro_directory_empty_p = msdosfs_gro_directory_empty_p, + .gro_rename_check_possible = msdosfs_gro_rename_check_possible, + .gro_rename_check_permitted = msdosfs_gro_rename_check_permitted, + .gro_remove_check_possible = msdosfs_gro_remove_check_possible, + .gro_remove_check_permitted = msdosfs_gro_remove_check_permitted, + .gro_rename = msdosfs_gro_rename, + .gro_remove = msdosfs_gro_remove, + .gro_lookup = msdosfs_gro_lookup, + .gro_genealogy = msdosfs_gro_genealogy, + .gro_lock_directory = msdosfs_gro_lock_directory, +};