Patch to make ufs (ffs and lfs alike) and ext2fs use genfs_rename. Index: sys/rump/fs/lib/libext2fs/Makefile =================================================================== RCS file: /cvsroot/src/sys/rump/fs/lib/libext2fs/Makefile,v retrieving revision 1.3 diff -p -u -r1.3 Makefile --- sys/rump/fs/lib/libext2fs/Makefile 16 Feb 2010 20:42:46 -0000 1.3 +++ sys/rump/fs/lib/libext2fs/Makefile 7 Apr 2012 21:19:56 -0000 @@ -6,8 +6,8 @@ LIB= rumpfs_ext2fs SRCS= ext2fs_alloc.c ext2fs_balloc.c ext2fs_bmap.c ext2fs_bswap.c \ - ext2fs_inode.c ext2fs_lookup.c ext2fs_readwrite.c ext2fs_subr.c \ - ext2fs_vfsops.c ext2fs_vnops.c + ext2fs_inode.c ext2fs_lookup.c ext2fs_readwrite.c \ + ext2fs_rename.c ext2fs_subr.c ext2fs_vfsops.c ext2fs_vnops.c .include .include Index: sys/rump/fs/lib/libffs/Makefile =================================================================== RCS file: /cvsroot/src/sys/rump/fs/lib/libffs/Makefile,v retrieving revision 1.13 diff -p -u -r1.13 Makefile --- sys/rump/fs/lib/libffs/Makefile 6 Mar 2011 17:08:37 -0000 1.13 +++ sys/rump/fs/lib/libffs/Makefile 7 Apr 2012 21:19:56 -0000 @@ -10,7 +10,7 @@ SRCS= ffs_alloc.c ffs_appleufs.c ffs_bal ffs_wapbl.c ffs_quota2.c SRCS+= ufs_bmap.c ufs_dirhash.c ufs_extattr.c ufs_ihash.c ufs_inode.c \ - ufs_lookup.c ufs_vfsops.c ufs_vnops.c ufs_wapbl.c \ + ufs_lookup.c ufs_rename.c ufs_vfsops.c ufs_vnops.c ufs_wapbl.c \ ufs_quota.c ufs_quota2.c quota2_subr.c CPPFLAGS+= -DFFS_EI -DUFS_DIRHASH -DWAPBL -DAPPLE_UFS -DUFS_EXTATTR \ Index: sys/ufs/files.ufs =================================================================== RCS file: /cvsroot/src/sys/ufs/files.ufs,v retrieving revision 1.27 diff -p -u -r1.27 files.ufs --- sys/ufs/files.ufs 24 Nov 2011 15:51:31 -0000 1.27 +++ sys/ufs/files.ufs 7 Apr 2012 21:19:57 -0000 @@ -19,6 +19,7 @@ file ufs/ext2fs/ext2fs_bswap.c ext2fs file ufs/ext2fs/ext2fs_inode.c ext2fs file ufs/ext2fs/ext2fs_lookup.c ext2fs file ufs/ext2fs/ext2fs_readwrite.c ext2fs +file ufs/ext2fs/ext2fs_rename.c ext2fs file ufs/ext2fs/ext2fs_subr.c ext2fs file ufs/ext2fs/ext2fs_vfsops.c ext2fs file ufs/ext2fs/ext2fs_vnops.c ext2fs @@ -84,6 +85,7 @@ file ufs/ufs/ufs_quota1.c quota & (ffs file ufs/ufs/ufs_quota2.c quota2 & (ffs | lfs | mfs | ext2fs | chfs) file ufs/ufs/quota1_subr.c file ufs/ufs/quota2_subr.c quota2 & (ffs | lfs | mfs | ext2fs | chfs) +file ufs/ufs/ufs_rename.c ffs | lfs | mfs | chfs file ufs/ufs/ufs_vfsops.c ffs | lfs | mfs | ext2fs | chfs file ufs/ufs/ufs_vnops.c ffs | lfs | mfs | ext2fs | chfs file ufs/ufs/ufs_wapbl.c ffs & wapbl Index: sys/ufs/ext2fs/ext2fs_dir.h =================================================================== RCS file: /cvsroot/src/sys/ufs/ext2fs/ext2fs_dir.h,v retrieving revision 1.18 diff -p -u -r1.18 ext2fs_dir.h --- sys/ufs/ext2fs/ext2fs_dir.h 19 Oct 2009 18:41:17 -0000 1.18 +++ sys/ufs/ext2fs/ext2fs_dir.h 7 Apr 2012 21:19:57 -0000 @@ -67,6 +67,8 @@ #ifndef _UFS_EXT2FS_EXT2FS_DIR_H_ #define _UFS_EXT2FS_EXT2FS_DIR_H_ +#include + /* * Theoretically, directories can be more than 2Gb in length, however, in * practice this seems unlikely. So, we define the type doff_t as a 32-bit Index: sys/ufs/ext2fs/ext2fs_extern.h =================================================================== RCS file: /cvsroot/src/sys/ufs/ext2fs/ext2fs_extern.h,v retrieving revision 1.43 diff -p -u -r1.43 ext2fs_extern.h --- sys/ufs/ext2fs/ext2fs_extern.h 12 Jul 2011 16:59:48 -0000 1.43 +++ sys/ufs/ext2fs/ext2fs_extern.h 7 Apr 2012 21:19:57 -0000 @@ -77,6 +77,7 @@ struct uio; struct vnode; struct mbuf; struct componentname; +struct ufs_lookup_results; extern struct pool ext2fs_inode_pool; /* memory pool for inodes */ extern struct pool ext2fs_dinode_pool; /* memory pool for dinodes */ Index: sys/ufs/ext2fs/ext2fs_rename.c =================================================================== RCS file: sys/ufs/ext2fs/ext2fs_rename.c diff -N sys/ufs/ext2fs/ext2fs_rename.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ sys/ufs/ext2fs/ext2fs_rename.c 8 Apr 2012 23:11:48 -0000 @@ -0,0 +1,1012 @@ +/* $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. + */ + +/* + * Ext2fs Rename + */ + +#include +__KERNEL_RCSID(0, "$NetBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +/* + * Forward declarations + */ +static int ext2fs_sane_rename(struct vnode *, struct componentname *, + struct vnode *, struct componentname *, + kauth_cred_t, bool); +static bool ext2fs_rename_ulr_overlap_p(const struct ufs_lookup_results *, + const struct ufs_lookup_results *); +static int ext2fs_rename_recalculate_fulr(struct vnode *, + struct ufs_lookup_results *, const struct ufs_lookup_results *, + const struct componentname *); +static bool ext2fs_rmdired_p(struct vnode *); +static int ext2fs_read_dotdot(struct vnode *, kauth_cred_t, ino_t *); +static int ext2fs_rename_replace_dotdot(struct vnode *, + struct vnode *, struct vnode *, kauth_cred_t); +static int ext2fs_gro_lock_directory(struct mount *, struct vnode *); + +static const struct genfs_rename_ops ext2fs_genfs_rename_ops; + +/* + * ext2fs_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 +ext2fs_sane_rename( + struct vnode *fdvp, struct componentname *fcnp, + struct vnode *tdvp, struct componentname *tcnp, + kauth_cred_t cred, bool posixly_correct) +{ + struct ufs_lookup_results fulr, tulr; + + return genfs_sane_rename(&ext2fs_genfs_rename_ops, + fdvp, fcnp, &fulr, tdvp, tcnp, &tulr, + cred, posixly_correct); +} + +/* + * ext2fs_rename: The hairiest vop, with the insanest API. Defer to + * genfs_insane_rename immediately. + */ +int +ext2fs_rename(void *v) +{ + + return genfs_insane_rename(v, &ext2fs_sane_rename); +} + +/* + * ext2fs_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 +ext2fs_gro_directory_empty_p(struct mount *mp, kauth_cred_t cred, + struct vnode *vp, struct vnode *dvp) +{ + + (void)mp; + 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 ext2fs_dirempty(VTOI(vp), VTOI(dvp)->i_number, cred); +} + +/* + * ext2fs_gro_rename_check_possible: Check whether a rename is possible + * independent of credentials. + */ +static int +ext2fs_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( + VTOI(fdvp)->i_e2fs_flags, VTOI(fvp)->i_e2fs_flags, + VTOI(tdvp)->i_e2fs_flags, (tvp? VTOI(tvp)->i_e2fs_flags : 0), + (tvp != NULL), + EXT2_IMMUTABLE, EXT2_APPEND); +} + +/* + * ext2fs_gro_rename_check_permitted: Check whether a rename is + * permitted given our credentials. + */ +static int +ext2fs_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, VTOI(fdvp)->i_e2fs_mode, VTOI(fdvp)->i_uid, + fvp, VTOI(fvp)->i_uid, + tdvp, VTOI(tdvp)->i_e2fs_mode, VTOI(tdvp)->i_uid, + tvp, (tvp? VTOI(tvp)->i_uid : 0)); +} + +/* + * ext2fs_gro_remove_check_possible: Check whether a remove is possible + * independent of credentials. + */ +static int +ext2fs_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( + VTOI(dvp)->i_e2fs_flags, VTOI(vp)->i_e2fs_flags, + EXT2_IMMUTABLE, EXT2_APPEND); +} + +/* + * ext2fs_gro_remove_check_permitted: Check whether a remove is + * permitted given our credentials. + */ +static int +ext2fs_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, VTOI(dvp)->i_e2fs_mode, VTOI(dvp)->i_uid, + vp, VTOI(vp)->i_uid); +} + +/* + * ext2fs_gro_rename: Actually perform the rename operation. + */ +static int +ext2fs_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 ufs_lookup_results *fulr = fde; + struct ufs_lookup_results *tulr = tde; + bool directory_p, reparent_p; + int error; + + (void)mp; + KASSERT(mp != NULL); + KASSERT(fdvp != NULL); + KASSERT(fcnp != NULL); + KASSERT(fulr != NULL); + KASSERT(fvp != NULL); + KASSERT(tdvp != NULL); + KASSERT(tcnp != NULL); + KASSERT(tulr != NULL); + KASSERT(fulr != tulr); + 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 link count, so make + * sure there is room to do so. + */ + if ((nlink_t)VTOI(fvp)->i_e2fs_nlink >= LINK_MAX) + return EMLINK; + + /* + * XXX There is a pile of logic here to handle a voodoo flag + * IN_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 == ((VTOI(fvp)->i_e2fs_mode & IFMT) == IFDIR)); + KASSERT((tvp == NULL) || (directory_p == (tvp->v_type == VDIR))); + KASSERT((tvp == NULL) || (directory_p == + ((VTOI(tvp)->i_e2fs_mode & IFMT) == IFDIR))); + if (directory_p) { + if (VTOI(fvp)->i_flag & IN_RENAME) + return EINVAL; + VTOI(fvp)->i_flag |= IN_RENAME; + } + + reparent_p = (fdvp != tdvp); + KASSERT(reparent_p == (VTOI(fdvp)->i_number != VTOI(tdvp)->i_number)); + + /* + * Commence hacking of the data on disk. + */ + + /* + * 1) Bump link count while we're moving stuff + * around. If we crash somewhere before + * completing our work, the link count + * may be wrong, but correctable. + */ + + KASSERT((nlink_t)VTOI(fvp)->i_e2fs_nlink < LINK_MAX); + VTOI(fvp)->i_e2fs_nlink++; + VTOI(fvp)->i_flag |= IN_CHANGE; + error = ext2fs_update(fvp, NULL, NULL, UPDATE_WAIT); + if (error) + goto whymustithurtsomuch; + + /* + * 2) If target doesn't exist, link the target + * to the source and unlink the source. + * Otherwise, rewrite the target directory + * entry to reference the source inode and + * expunge the original entry's existence. + */ + + if (tvp == NULL) { + /* + * Account for ".." in new directory. + * When source and destination have the same + * parent we don't fool with the link count. + */ + if (directory_p && reparent_p) { + if ((nlink_t)VTOI(tdvp)->i_e2fs_nlink >= LINK_MAX) { + error = EMLINK; + goto whymustithurtsomuch; + } + KASSERT((nlink_t)VTOI(tdvp)->i_e2fs_nlink < LINK_MAX); + VTOI(tdvp)->i_e2fs_nlink++; + VTOI(tdvp)->i_flag |= IN_CHANGE; + error = ext2fs_update(tdvp, NULL, NULL, UPDATE_WAIT); + if (error) { + /* + * Link count update didn't take -- + * back out the in-memory link count. + */ + KASSERT(0 < VTOI(tdvp)->i_e2fs_nlink); + VTOI(tdvp)->i_e2fs_nlink--; + VTOI(tdvp)->i_flag |= IN_CHANGE; + goto whymustithurtsomuch; + } + } + + error = ext2fs_direnter(VTOI(fvp), tdvp, tulr, tcnp); + if (error) { + if (directory_p && reparent_p) { + /* + * Directory update didn't take, but + * the link count update did -- back + * out the in-memory link count and the + * on-disk link count. + */ + KASSERT(0 < VTOI(tdvp)->i_e2fs_nlink); + VTOI(tdvp)->i_e2fs_nlink--; + VTOI(tdvp)->i_flag |= IN_CHANGE; + (void)ext2fs_update(tdvp, NULL, NULL, + UPDATE_WAIT); + } + goto whymustithurtsomuch; + } + } else { + if (directory_p) + /* XXX WTF? Why purge here? Why not purge others? */ + cache_purge(tdvp); + + /* + * Make the target directory's entry for tcnp point at + * the source node. + */ + error = ext2fs_dirrewrite(VTOI(tdvp), tulr, VTOI(fvp), tcnp); + if (error) + goto whymustithurtsomuch; + + /* + * If the source and target are directories, and the + * target is in the same directory as the source, + * decrement the link count of the common parent + * directory, since we are removing the target from + * that directory. + */ + if (directory_p && !reparent_p) { + KASSERT(fdvp == tdvp); + /* XXX check, don't kassert */ + KASSERT(0 < VTOI(tdvp)->i_e2fs_nlink); + VTOI(tdvp)->i_e2fs_nlink--; + VTOI(tdvp)->i_flag |= IN_CHANGE; + } + + /* + * Adjust the link count of the target to + * reflect the dirrewrite above. If this is + * a directory it is empty and there are + * no links to it, so we can squash the inode and + * any space associated with it. We disallowed + * renaming over top of a directory with links to + * it above, as the remaining link would point to + * a directory without "." or ".." entries. + */ + /* XXX check, don't kassert */ + KASSERT(0 < VTOI(tvp)->i_e2fs_nlink); + VTOI(tvp)->i_e2fs_nlink--; + if (directory_p) { + /* + * XXX The ext2fs_dirempty call earlier does + * not guarantee anything about nlink. + */ + if (VTOI(tvp)->i_e2fs_nlink != 1) + ufs_dirbad(VTOI(tvp), (doff_t)0, + "hard-linked directory"); + VTOI(tvp)->i_e2fs_nlink = 0; + error = ext2fs_truncate(tvp, (off_t)0, IO_SYNC, cred); +#if 0 /* XXX This branch was not in ext2fs_rename! */ + if (error) + goto whymustithurtsomuch; +#endif + } + /* + * XXX Why is this here, and not above the preceding + * conditional? + */ + VTOI(tvp)->i_flag |= IN_CHANGE; + } + + /* + * If the source is a directory with a new parent, the link + * count of the old parent directory must be decremented and + * ".." set to point to the new parent. + */ + if (directory_p && reparent_p) { + error = ext2fs_rename_replace_dotdot(fvp, fdvp, tdvp, cred); + if (error) + goto whymustithurtsomuch; + + /* XXX WTF? Why purge here? Why not purge others? */ + cache_purge(fdvp); + } + + /* + * 3) Unlink the source. + */ + + /* + * ext2fs_direnter may compact the directory in the process of + * inserting a new entry. That may invalidate fulr, which we + * need in order to remove the old entry. In that case, we + * need to recalculate what fulr should be. + * + * XXX I believe this is necessary only if tvp == NULL as well. + */ + if (!reparent_p && ext2fs_rename_ulr_overlap_p(fulr, tulr)) { + error = ext2fs_rename_recalculate_fulr(fdvp, fulr, tulr, fcnp); +#if 0 /* XXX */ + if (error) /* XXX Try to back out changes? */ + goto whymustithurtsomuch; +#endif + } + + error = ext2fs_dirremove(fdvp, fulr, fcnp); + if (error) + goto whymustithurtsomuch; + + /* + * XXX Perhaps this should go at the top, in case the file + * system is modified but incompletely so because of an + * intermediate error. + */ + genfs_rename_knote(fdvp, fvp, tdvp, tvp, + ((tvp != NULL) && (VTOI(tvp)->i_e2fs_nlink == 0))); +#if 0 /* XXX */ + genfs_rename_cache_purge(fdvp, fvp, tdvp, tvp); +#endif + +whymustithurtsomuch: + KASSERT(0 < VTOI(fvp)->i_e2fs_nlink); + VTOI(fvp)->i_e2fs_nlink--; + VTOI(fvp)->i_flag |= IN_CHANGE; + if (directory_p) + VTOI(fvp)->i_flag &=~ IN_RENAME; + return error; +} + +/* + * ext2fs_rename_ulr_overlap_p: True iff tulr overlaps with fulr so + * that entering a directory entry at tulr may move fulr. + */ +static bool +ext2fs_rename_ulr_overlap_p(const struct ufs_lookup_results *fulr, + const struct ufs_lookup_results *tulr) +{ + doff_t from_prev_start, from_prev_end, to_start, to_end; + + KASSERT(fulr != NULL); + KASSERT(tulr != NULL); + KASSERT(fulr != tulr); + + /* + * fulr is from a DELETE lookup, so fulr->ulr_count is the size + * of the preceding entry (d_reclen). + */ + from_prev_end = fulr->ulr_offset; + KASSERT(fulr->ulr_count <= from_prev_end); + from_prev_start = (from_prev_end - fulr->ulr_count); + + /* + * tulr is from a RENAME lookup, so tulr->ulr_count is the size + * of the free space for an entry that we are about to fill. + */ + to_start = tulr->ulr_offset; + KASSERT(tulr->ulr_count < (EXT2FS_MAXDIRSIZE - to_start)); + to_end = (to_start + tulr->ulr_count); + + return + (((to_start <= from_prev_start) && (from_prev_start < to_end)) || + ((to_start <= from_prev_end) && (from_prev_end < to_end))); +} + +/* + * ext2fs_rename_recalculate_fulr: If we have just entered a directory + * into dvp at tulr, and we were about to remove one at fulr for an + * entry named fcnp, fulr may be invalid. So, if necessary, + * recalculate it. + */ +static int +ext2fs_rename_recalculate_fulr(struct vnode *dvp, + struct ufs_lookup_results *fulr, const struct ufs_lookup_results *tulr, + const struct componentname *fcnp) +{ + struct mount *mp; + struct ufsmount *ump; + /* XXX int is a silly type for this; blame ufsmount::um_dirblksiz. */ + int directory_block_mask; + unsigned long io_block_mask; + doff_t offset; /* Offset of entry we're examining. */ + doff_t search_end; /* Limit to our search. */ + struct buf *bp; /* I/O block we're examining. */ + char *dirbuf; /* Pointer into bp's data. */ + doff_t dirbuf_offset; /* Offset of dirbuf from directory start. */ + struct ext2fs_direct *ep; /* Pointer to the entry we're examining. */ + /* XXX direct::d_reclen is 16-bit; + * ufs_lookup_results::ulr_reclen is 32-bit. Blah. */ + uint32_t reclen; /* Length of the entry we're examining. */ + uint32_t prev_reclen; /* Length of the preceding entry. */ + int error; + + KASSERT(dvp != NULL); + KASSERT(dvp->v_mount != NULL); + KASSERT(VTOI(dvp) != NULL); + KASSERT(fulr != NULL); + KASSERT(tulr != NULL); + KASSERT(fulr != tulr); + KASSERT(ext2fs_rename_ulr_overlap_p(fulr, tulr)); + + mp = dvp->v_mount; + ump = VFSTOUFS(mp); + KASSERT(ump != NULL); + KASSERT(ump == VTOI(dvp)->i_ump); + + KASSERT(0 < ump->um_dirblksiz); + KASSERT((ump->um_dirblksiz & (ump->um_dirblksiz - 1)) == 0); + directory_block_mask = (ump->um_dirblksiz - 1); + + KASSERT(0 < mp->mnt_stat.f_iosize); + KASSERT((mp->mnt_stat.f_iosize & (mp->mnt_stat.f_iosize - 1)) == 0); + io_block_mask = (mp->mnt_stat.f_iosize - 1); + + offset = tulr->ulr_offset; + KASSERT(fulr->ulr_reclen < (EXT2FS_MAXDIRSIZE - fulr->ulr_offset)); + search_end = (fulr->ulr_offset + fulr->ulr_reclen); + + dirbuf = NULL; + bp = NULL; + dirbuf_offset = offset; + error = ext2fs_blkatoff(dvp, (off_t)dirbuf_offset, &dirbuf, &bp); + if (error) + return error; + KASSERT(dirbuf != NULL); + KASSERT(bp != NULL); + + prev_reclen = fulr->ulr_count; + + /* + * Search from offset to search_end for the entry matching + * fcnp, which must be there because we found it before and it + * should only at most have moved earlier. + */ + for (;;) { + KASSERT(offset < search_end); + + /* + * If we are at an I/O block boundary, fetch the next block. + */ + if ((offset & io_block_mask) == 0) { +#ifdef DIAGNOSTIC /* XXX */ + printf("%s: directory block of inode 0x%llx" + " extends across I/O block boundary," + " which shouldn't happen!\n", + mp->mnt_stat.f_mntonname, + (unsigned long long)VTOI(dvp)->i_number); +#endif + brelse(bp, 0); + dirbuf = NULL; + bp = NULL; + dirbuf_offset = offset; + error = ext2fs_blkatoff(dvp, (off_t)dirbuf_offset, + &dirbuf, &bp); + if (error) + return error; + KASSERT(dirbuf != NULL); + KASSERT(bp != NULL); + } + + /* + * Examine the directory entry at offset. + */ + KASSERT(dirbuf_offset <= offset); + ep = (struct ext2fs_direct *) + (dirbuf + (offset - dirbuf_offset)); + reclen = ep->e2d_reclen; + + if (ep->e2d_ino == 0) + goto next; /* Entry is unused. */ + + if (ep->e2d_ino == WINO) + goto next; /* Entry is whiteout. */ + + if (fcnp->cn_namelen != ep->e2d_namlen) + goto next; /* Wrong name length. */ + + if (memcmp(ep->e2d_name, fcnp->cn_nameptr, fcnp->cn_namelen)) + goto next; /* Wrong name. */ + + /* Got it! */ + break; + +next: + if (! ((reclen < search_end) && + (offset < (search_end - reclen)))) { + brelse(bp, 0); + return EIO; /* XXX Panic? What? */ + } + + KASSERT(reclen < search_end); + KASSERT(offset < (search_end - reclen)); + prev_reclen = reclen; + offset += reclen; + } + + /* + * Found the entry. Record where. + */ + fulr->ulr_offset = offset; + fulr->ulr_reclen = reclen; + + /* + * Record the preceding record length, but not if we're at the + * start of a directory block. + */ + fulr->ulr_count = ((offset & directory_block_mask)? prev_reclen : 0); + + brelse(bp, 0); + return 0; +} + +/* + * ext2fs_gro_remove: Rename an object over another link to itself, + * effectively removing just the original link. + */ +static int +ext2fs_gro_remove(struct mount *mp, kauth_cred_t cred, + struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp) +{ + struct ufs_lookup_results *ulr = de; + int error; + + (void)mp; + KASSERT(mp != NULL); + KASSERT(dvp != NULL); + KASSERT(cnp != NULL); + KASSERT(ulr != 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 = ext2fs_dirremove(dvp, ulr, cnp); + if (error) + return error; + + KASSERT(0 < VTOI(vp)->i_e2fs_nlink); + VTOI(vp)->i_e2fs_nlink--; + VTOI(vp)->i_flag |= IN_CHANGE; + + VN_KNOTE(dvp, NOTE_WRITE); + VN_KNOTE(vp, (VTOI(vp)->i_e2fs_nlink? NOTE_LINK : NOTE_DELETE)); + + return 0; +} + +/* + * ext2fs_gro_lookup: Look up and save the lookup results. + */ +static int +ext2fs_gro_lookup(struct mount *mp, struct vnode *dvp, + struct componentname *cnp, void *de_ret, struct vnode **vp_ret) +{ + struct ufs_lookup_results *ulr_ret = de_ret; + struct vnode *vp; + int error; + + (void)mp; + KASSERT(mp != NULL); + KASSERT(dvp != NULL); + KASSERT(cnp != NULL); + KASSERT(ulr_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 /* dummy */); + if ((error == 0) && (vp == NULL)) { + error = ENOENT; + goto out; + } else if (error) { + return error; + } + + /* + * Thanks to VFS insanity, relookup locks vp, which screws us + * in various ways. + */ + KASSERT(vp != NULL); + VOP_UNLOCK(vp); + +out: *ulr_ret = VTOI(dvp)->i_crap; + *vp_ret = vp; + return error; +} + +/* + * ext2fs_rmdired_p: Check whether the directory vp has been rmdired. + * + * vp must be locked and referenced. + */ +static bool +ext2fs_rmdired_p(struct vnode *vp) +{ + + KASSERT(vp != NULL); + KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); + KASSERT(vp->v_type == VDIR); + + /* XXX Is this correct? */ + return (ext2fs_size(VTOI(vp)) == 0); +} + +/* + * ext2fs_gro_genealogy: Analyze the genealogy of the source and target + * directories. + */ +static int +ext2fs_gro_genealogy(struct mount *mp, kauth_cred_t cred, + struct vnode *fdvp, struct vnode *tdvp, + struct vnode **intermediate_node_ret) +{ + struct vnode *vp, *dvp; + ino_t dotdot_ino; + 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); + + /* + * We need to provisionally lock tdvp to keep rmdir from + * deleting it -- or any ancestor -- at an inopportune moment. + */ + error = ext2fs_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(!ext2fs_rmdired_p(vp)); + + /* Did we hit the root without finding fdvp? */ + if (VTOI(vp)->i_number == ROOTINO) { + vput(vp); + *intermediate_node_ret = NULL; + return 0; + } + + error = ext2fs_read_dotdot(vp, cred, &dotdot_ino); + if (error) { + vput(vp); + return error; + } + + /* Did we find that fdvp is an ancestor of tdvp? */ + if (VTOI(fdvp)->i_number == dotdot_ino) { + /* Unlock vp, but keep it referenced. */ + VOP_UNLOCK(vp); + *intermediate_node_ret = vp; + return 0; + } + + /* Neither -- keep ascending the family tree. */ + + /* + * Unlock vp so that we can lock the parent, but keep + * vp referenced until after we have found the parent, + * so that dotdot_ino will not be recycled. + * + * XXX This guarantees that vp's inode number will not + * be recycled, but why can't dotdot_ino be recycled? + */ + VOP_UNLOCK(vp); + error = VFS_VGET(mp, dotdot_ino, &dvp); + vrele(vp); + if (error) + return error; + + KASSERT(dvp != NULL); + KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); + vp = dvp; + + if (vp->v_type != VDIR) { + /* + * XXX Panic? Print a warning? Can this + * happen if we lose the race I suspect to + * exist above, and the `..' inode number has + * been recycled? + */ + vput(vp); + return ENOTDIR; + } + + if (ext2fs_rmdired_p(vp)) { + vput(vp); + return ENOENT; + } + } +} + +/* + * ext2fs_read_dotdot: Store in *ino_ret the inode number of the parent + * of the directory vp. + */ +static int +ext2fs_read_dotdot(struct vnode *vp, kauth_cred_t cred, ino_t *ino_ret) +{ + struct ext2fs_dirtemplate dirbuf; + int error; + + KASSERT(vp != NULL); + KASSERT(ino_ret != NULL); + KASSERT(vp->v_type == VDIR); + + error = vn_rdwr(UIO_READ, vp, &dirbuf, sizeof dirbuf, (off_t)0, + UIO_SYSSPACE, IO_NODELOCKED, cred, NULL, NULL); + if (error) + return error; + + if (dirbuf.dotdot_namlen != 2 || + dirbuf.dotdot_name[0] != '.' || + dirbuf.dotdot_name[1] != '.') + /* XXX Panic? Print warning? */ + return ENOTDIR; + + *ino_ret = fs2h32(dirbuf.dotdot_ino); + return 0; +} + +/* + * ext2fs_rename_replace_dotdot: Change the target of the `..' entry of + * the directory vp from fdvp to tdvp. + */ +static int +ext2fs_rename_replace_dotdot(struct vnode *vp, + struct vnode *fdvp, struct vnode *tdvp, + kauth_cred_t cred) +{ + struct ext2fs_dirtemplate dirbuf; + int error; + + /* XXX Does it make sense to do this before the sanity checks below? */ + KASSERT(0 < VTOI(fdvp)->i_e2fs_nlink); + VTOI(fdvp)->i_e2fs_nlink--; + VTOI(fdvp)->i_flag |= IN_CHANGE; + + error = vn_rdwr(UIO_READ, vp, &dirbuf, sizeof dirbuf, (off_t)0, + UIO_SYSSPACE, IO_NODELOCKED, cred, NULL, NULL); + if (error) + return error; + + if (dirbuf.dotdot_namlen != 2 || + dirbuf.dotdot_name[0] != '.' || + dirbuf.dotdot_name[1] != '.') { + ufs_dirbad(VTOI(vp), (doff_t)12, "bad `..' entry"); + return 0; + } + + if (fs2h32(dirbuf.dotdot_ino) != VTOI(fdvp)->i_number) { + ufs_dirbad(VTOI(vp), (doff_t)12, + "`..' does not point at parent"); + return 0; + } + + dirbuf.dotdot_ino = h2fs32(VTOI(tdvp)->i_number); + /* XXX WTF? Why not check error? */ + (void)vn_rdwr(UIO_WRITE, vp, &dirbuf, sizeof dirbuf, (off_t)0, + UIO_SYSSPACE, (IO_NODELOCKED | IO_SYNC), cred, NULL, NULL); + + return 0; +} + +/* + * ext2fs_gro_lock_directory: Lock the directory vp, but fail if it has + * been rmdir'd. + */ +static int +ext2fs_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 (ext2fs_rmdired_p(vp)) { + VOP_UNLOCK(vp); + return ENOENT; + } + + return 0; +} + +static const struct genfs_rename_ops ext2fs_genfs_rename_ops = { + .gro_directory_empty_p = ext2fs_gro_directory_empty_p, + .gro_rename_check_possible = ext2fs_gro_rename_check_possible, + .gro_rename_check_permitted = ext2fs_gro_rename_check_permitted, + .gro_remove_check_possible = ext2fs_gro_remove_check_possible, + .gro_remove_check_permitted = ext2fs_gro_remove_check_permitted, + .gro_rename = ext2fs_gro_rename, + .gro_remove = ext2fs_gro_remove, + .gro_lookup = ext2fs_gro_lookup, + .gro_genealogy = ext2fs_gro_genealogy, + .gro_lock_directory = ext2fs_gro_lock_directory, +}; Index: sys/ufs/ext2fs/ext2fs_vnops.c =================================================================== RCS file: /cvsroot/src/sys/ufs/ext2fs/ext2fs_vnops.c,v retrieving revision 1.102 diff -p -u -r1.102 ext2fs_vnops.c --- sys/ufs/ext2fs/ext2fs_vnops.c 13 Mar 2012 18:41:04 -0000 1.102 +++ sys/ufs/ext2fs/ext2fs_vnops.c 7 Apr 2012 21:19:57 -0000 @@ -644,426 +644,6 @@ out2: } /* - * Rename system call. - * rename("foo", "bar"); - * is essentially - * unlink("bar"); - * link("foo", "bar"); - * unlink("foo"); - * but ``atomically''. Can't do full commit without saving state in the - * inode on disk which isn't feasible at this time. Best we can do is - * always guarantee the target exists. - * - * Basic algorithm is: - * - * 1) Bump link count on source while we're linking it to the - * target. This also ensure the inode won't be deleted out - * from underneath us while we work (it may be truncated by - * a concurrent `trunc' or `open' for creation). - * 2) Link source to destination. If destination already exists, - * delete it first. - * 3) Unlink source reference to inode if still around. If a - * directory was moved and the parent of the destination - * is different from the source, patch the ".." entry in the - * directory. - */ -int -ext2fs_rename(void *v) -{ - struct vop_rename_args /* { - struct vnode *a_fdvp; - struct vnode *a_fvp; - struct componentname *a_fcnp; - struct vnode *a_tdvp; - struct vnode *a_tvp; - struct componentname *a_tcnp; - } */ *ap = v; - struct vnode *tvp = ap->a_tvp; - struct vnode *tdvp = ap->a_tdvp; - struct vnode *fvp = ap->a_fvp; - struct vnode *fdvp = ap->a_fdvp; - struct componentname *tcnp = ap->a_tcnp; - struct componentname *fcnp = ap->a_fcnp; - struct inode *ip, *xp, *dp; - struct ext2fs_dirtemplate dirbuf; - int doingdirectory = 0, oldparent = 0, newparent = 0; - int error = 0; - u_char namlen; - - /* - * Check for cross-device rename. - */ - if ((fvp->v_mount != tdvp->v_mount) || - (tvp && (fvp->v_mount != tvp->v_mount))) { - error = EXDEV; -abortit: - VOP_ABORTOP(tdvp, tcnp); /* XXX, why not in NFS? */ - if (tdvp == tvp) - vrele(tdvp); - else - vput(tdvp); - if (tvp) - vput(tvp); - VOP_ABORTOP(fdvp, fcnp); /* XXX, why not in NFS? */ - vrele(fdvp); - vrele(fvp); - return (error); - } - - /* - * Check if just deleting a link name. - */ - if (tvp && ((VTOI(tvp)->i_e2fs_flags & (EXT2_IMMUTABLE | EXT2_APPEND)) || - (VTOI(tdvp)->i_e2fs_flags & EXT2_APPEND))) { - error = EPERM; - goto abortit; - } - if (fvp == tvp) { - if (fvp->v_type == VDIR) { - error = EINVAL; - goto abortit; - } - - /* Release destination completely. */ - VOP_ABORTOP(tdvp, tcnp); - vput(tdvp); - vput(tvp); - - /* Delete source. */ - vrele(fvp); - fcnp->cn_flags &= ~(MODMASK); - fcnp->cn_flags |= LOCKPARENT | LOCKLEAF; - fcnp->cn_nameiop = DELETE; - vn_lock(fdvp, LK_EXCLUSIVE | LK_RETRY); - if ((error = relookup(fdvp, &fvp, fcnp, 0))) { - vput(fdvp); - return (error); - } - return (VOP_REMOVE(fdvp, fvp, fcnp)); - } - if ((error = vn_lock(fvp, LK_EXCLUSIVE)) != 0) - goto abortit; - dp = VTOI(fdvp); - ip = VTOI(fvp); - if ((nlink_t) ip->i_e2fs_nlink >= LINK_MAX) { - VOP_UNLOCK(fvp); - error = EMLINK; - goto abortit; - } - if ((ip->i_e2fs_flags & (EXT2_IMMUTABLE | EXT2_APPEND)) || - (dp->i_e2fs_flags & EXT2_APPEND)) { - VOP_UNLOCK(fvp); - error = EPERM; - goto abortit; - } - if ((ip->i_e2fs_mode & IFMT) == IFDIR) { - error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred); - if (!error && tvp) - error = VOP_ACCESS(tvp, VWRITE, tcnp->cn_cred); - if (error) { - VOP_UNLOCK(fvp); - error = EACCES; - goto abortit; - } - /* - * Avoid ".", "..", and aliases of "." for obvious reasons. - */ - if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') || - dp == ip || - (fcnp->cn_flags & ISDOTDOT) || - (tcnp->cn_flags & ISDOTDOT) || - (ip->i_flag & IN_RENAME)) { - VOP_UNLOCK(fvp); - error = EINVAL; - goto abortit; - } - ip->i_flag |= IN_RENAME; - oldparent = dp->i_number; - doingdirectory = 1; - } - VN_KNOTE(fdvp, NOTE_WRITE); /* XXXLUKEM/XXX: right place? */ - - /* - * When the target exists, both the directory - * and target vnodes are returned locked. - */ - dp = VTOI(tdvp); - xp = NULL; - if (tvp) - xp = VTOI(tvp); - - /* - * 1) Bump link count while we're moving stuff - * around. If we crash somewhere before - * completing our work, the link count - * may be wrong, but correctable. - */ - ip->i_e2fs_nlink++; - ip->i_flag |= IN_CHANGE; - if ((error = ext2fs_update(fvp, NULL, NULL, UPDATE_WAIT)) != 0) { - VOP_UNLOCK(fvp); - goto bad; - } - - /* - * If ".." must be changed (ie the directory gets a new - * parent) then the source directory must not be in the - * directory hierarchy above the target, as this would - * orphan everything below the source directory. Also - * the user must have write permission in the source so - * as to be able to change "..". We must repeat the call - * to namei, as the parent directory is unlocked by the - * call to checkpath(). - */ - error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred); - VOP_UNLOCK(fvp); - if (oldparent != dp->i_number) - newparent = dp->i_number; - if (doingdirectory && newparent) { - if (error) /* write access check above */ - goto bad; - if (xp != NULL) - vput(tvp); - vref(tdvp); /* compensate for the ref checkpath loses */ - error = ext2fs_checkpath(ip, dp, tcnp->cn_cred); - if (error != 0) { - vrele(tdvp); - goto out; - } - vn_lock(tdvp, LK_EXCLUSIVE | LK_RETRY); - if ((error = relookup(tdvp, &tvp, tcnp, 0)) != 0) { - vput(tdvp); - goto out; - } - dp = VTOI(tdvp); - xp = NULL; - if (tvp) - xp = VTOI(tvp); - } - /* - * 2) If target doesn't exist, link the target - * to the source and unlink the source. - * Otherwise, rewrite the target directory - * entry to reference the source inode and - * expunge the original entry's existence. - */ - if (xp == NULL) { - if (dp->i_dev != ip->i_dev) - panic("rename: EXDEV"); - /* - * Account for ".." in new directory. - * When source and destination have the same - * parent we don't fool with the link count. - */ - if (doingdirectory && newparent) { - if ((nlink_t)dp->i_e2fs_nlink >= LINK_MAX) { - error = EMLINK; - goto bad; - } - dp->i_e2fs_nlink++; - dp->i_flag |= IN_CHANGE; - if ((error = ext2fs_update(tdvp, NULL, NULL, - UPDATE_WAIT)) != 0) - goto bad; - } - error = ext2fs_direnter(ip, tdvp, &VTOI(tdvp)->i_crap, tcnp); - if (error != 0) { - if (doingdirectory && newparent) { - dp->i_e2fs_nlink--; - dp->i_flag |= IN_CHANGE; - (void)ext2fs_update(tdvp, NULL, NULL, - UPDATE_WAIT); - } - goto bad; - } - VN_KNOTE(tdvp, NOTE_WRITE); - vput(tdvp); - } else { - if (xp->i_dev != dp->i_dev || xp->i_dev != ip->i_dev) - panic("rename: EXDEV"); - /* - * Short circuit rename(foo, foo). - */ - if (xp->i_number == ip->i_number) - panic("rename: same file"); - /* - * If the parent directory is "sticky", then the user must - * own the parent directory, or the destination of the rename, - * otherwise the destination may not be changed (except by - * root). This implements append-only directories. - */ - if (dp->i_e2fs_mode & S_ISTXT) { - error = kauth_authorize_vnode(tcnp->cn_cred, - KAUTH_VNODE_DELETE, tvp, tdvp, - genfs_can_sticky(tcnp->cn_cred, dp->i_uid, - xp->i_uid)); - if (error) { - error = EPERM; - goto bad; - } - } - /* - * Target must be empty if a directory and have no links - * to it. Also, ensure source and target are compatible - * (both directories, or both not directories). - */ - if ((xp->i_e2fs_mode & IFMT) == IFDIR) { - if (!ext2fs_dirempty(xp, dp->i_number, tcnp->cn_cred) || - xp->i_e2fs_nlink > 2) { - error = ENOTEMPTY; - goto bad; - } - if (!doingdirectory) { - error = ENOTDIR; - goto bad; - } - cache_purge(tdvp); - } else if (doingdirectory) { - error = EISDIR; - goto bad; - } - error = ext2fs_dirrewrite(dp, &dp->i_crap, ip, tcnp); - if (error != 0) - goto bad; - /* - * If the target directory is in the same - * directory as the source directory, - * decrement the link count on the parent - * of the target directory. - */ - if (doingdirectory && !newparent) { - dp->i_e2fs_nlink--; - dp->i_flag |= IN_CHANGE; - } - /* - * Adjust the link count of the target to - * reflect the dirrewrite above. If this is - * a directory it is empty and there are - * no links to it, so we can squash the inode and - * any space associated with it. We disallowed - * renaming over top of a directory with links to - * it above, as the remaining link would point to - * a directory without "." or ".." entries. - */ - xp->i_e2fs_nlink--; - if (doingdirectory) { - if (--xp->i_e2fs_nlink != 0) - panic("rename: linked directory"); - error = ext2fs_truncate(tvp, (off_t)0, IO_SYNC, - tcnp->cn_cred); - } - xp->i_flag |= IN_CHANGE; - VN_KNOTE(tdvp, NOTE_WRITE); - vput(tdvp); - VN_KNOTE(tvp, NOTE_DELETE); - vput(tvp); - xp = NULL; - } - - /* - * 3) Unlink the source. - */ - fcnp->cn_flags &= ~(MODMASK); - fcnp->cn_flags |= LOCKPARENT | LOCKLEAF; - vn_lock(fdvp, LK_EXCLUSIVE | LK_RETRY); - if ((error = relookup(fdvp, &fvp, fcnp, 0))) { - vput(fdvp); - vrele(ap->a_fvp); - return (error); - } - if (fvp != NULL) { - xp = VTOI(fvp); - dp = VTOI(fdvp); - } else { - /* - * From name has disappeared. - */ - if (doingdirectory) - panic("ext2fs_rename: lost dir entry"); - vrele(ap->a_fvp); - return (0); - } - /* - * Ensure that the directory entry still exists and has not - * changed while the new name has been entered. If the source is - * a file then the entry may have been unlinked or renamed. In - * either case there is no further work to be done. If the source - * is a directory then it cannot have been rmdir'ed; its link - * count of three would cause a rmdir to fail with ENOTEMPTY. - * The IRENAME flag ensures that it cannot be moved by another - * rename. - */ - if (xp != ip) { - if (doingdirectory) - panic("ext2fs_rename: lost dir entry"); - } else { - /* - * If the source is a directory with a - * new parent, the link count of the old - * parent directory must be decremented - * and ".." set to point to the new parent. - */ - if (doingdirectory && newparent) { - KASSERT(dp != NULL); - dp->i_e2fs_nlink--; - dp->i_flag |= IN_CHANGE; - error = vn_rdwr(UIO_READ, fvp, (void *)&dirbuf, - sizeof (struct ext2fs_dirtemplate), (off_t)0, - UIO_SYSSPACE, IO_NODELOCKED, - tcnp->cn_cred, (size_t *)0, NULL); - if (error == 0) { - namlen = dirbuf.dotdot_namlen; - if (namlen != 2 || - dirbuf.dotdot_name[0] != '.' || - dirbuf.dotdot_name[1] != '.') { - ufs_dirbad(xp, (doff_t)12, - "ext2fs_rename: mangled dir"); - } else { - dirbuf.dotdot_ino = h2fs32(newparent); - (void) vn_rdwr(UIO_WRITE, fvp, - (void *)&dirbuf, - sizeof (struct dirtemplate), - (off_t)0, UIO_SYSSPACE, - IO_NODELOCKED|IO_SYNC, - tcnp->cn_cred, (size_t *)0, - NULL); - cache_purge(fdvp); - } - } - } - error = ext2fs_dirremove(fdvp, &VTOI(fdvp)->i_crap, fcnp); - if (!error) { - xp->i_e2fs_nlink--; - xp->i_flag |= IN_CHANGE; - } - xp->i_flag &= ~IN_RENAME; - } - VN_KNOTE(fvp, NOTE_RENAME); - if (dp) - vput(fdvp); - if (xp) - vput(fvp); - vrele(ap->a_fvp); - return (error); - -bad: - if (xp) - vput(ITOV(xp)); - vput(ITOV(dp)); -out: - if (doingdirectory) - ip->i_flag &= ~IN_RENAME; - if (vn_lock(fvp, LK_EXCLUSIVE) == 0) { - ip->i_e2fs_nlink--; - ip->i_flag |= IN_CHANGE; - vput(fvp); - } else - vrele(fvp); - vrele(fdvp); - return (error); -} - -/* * Mkdir system call */ int Index: sys/ufs/lfs/lfs_vnops.c =================================================================== RCS file: /cvsroot/src/sys/ufs/lfs/lfs_vnops.c,v retrieving revision 1.241 diff -p -u -r1.241 lfs_vnops.c --- sys/ufs/lfs/lfs_vnops.c 13 Mar 2012 18:41:14 -0000 1.241 +++ sys/ufs/lfs/lfs_vnops.c 7 Apr 2012 21:19:57 -0000 @@ -810,872 +810,131 @@ lfs_link(void *v) return (error); } -/* XXX following lifted from ufs_lookup.c */ -#define FSFMT(vp) (((vp)->v_mount->mnt_iflag & IMNT_DTYPE) == 0) +static const struct genfs_rename_ops lfs_genfs_rename_ops; /* - * Check if either entry referred to by FROM_ULR is within the range - * of entries named by TO_ULR. + * lfs_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 -ulr_overlap(const struct ufs_lookup_results *from_ulr, - const struct ufs_lookup_results *to_ulr) -{ - doff_t from_start, from_prevstart; - doff_t to_start, to_end; - - /* - * FROM is a DELETE result; offset points to the entry to - * remove and subtracting count gives the previous entry. - */ - from_start = from_ulr->ulr_offset - from_ulr->ulr_count; - from_prevstart = from_ulr->ulr_offset; - - /* - * TO is a RENAME (thus non-DELETE) result; offset points - * to the beginning of a region to write in, and adding - * count gives the end of the region. - */ - to_start = to_ulr->ulr_offset; - to_end = to_ulr->ulr_offset + to_ulr->ulr_count; - - if (from_prevstart >= to_start && from_prevstart < to_end) { - return 1; - } - if (from_start >= to_start && from_start < to_end) { - return 1; - } - return 0; +lfs_sane_rename( + struct vnode *fdvp, struct componentname *fcnp, + struct vnode *tdvp, struct componentname *tcnp, + kauth_cred_t cred, bool posixly_correct) +{ + struct ufs_lookup_results fulr, tulr; + + /* + * XXX Provisional kludge -- ufs_lookup does not reject rename + * of . or .. (from or to), so we hack it here. This is not + * the right place: it should be caller's responsibility to + * reject this case. + */ + KASSERT(fcnp != NULL); + KASSERT(tcnp != NULL); + KASSERT(fcnp != tcnp); + KASSERT(fcnp->cn_nameptr != NULL); + KASSERT(tcnp->cn_nameptr != NULL); + + if ((fcnp->cn_flags | tcnp->cn_flags) & ISDOTDOT) + return EINVAL; /* XXX EISDIR? */ + if ((fcnp->cn_namelen == 1) && (fcnp->cn_nameptr[0] == '.')) + return EINVAL; + if ((tcnp->cn_namelen == 1) && (tcnp->cn_nameptr[0] == '.')) + return EINVAL; + + return genfs_sane_rename(&lfs_genfs_rename_ops, + fdvp, fcnp, &fulr, tdvp, tcnp, &tulr, + cred, posixly_correct); } /* - * A virgin directory (no blushing please). + * lfs_rename: The hairiest vop, with the insanest API. Defer to + * genfs_insane_rename immediately. */ -static const struct dirtemplate mastertemplate = { - 0, 12, DT_DIR, 1, ".", - 0, DIRBLKSIZ - 12, DT_DIR, 2, ".." -}; - -/* - * Wrapper for relookup that also updates the supplemental results. - */ -static int -do_relookup(struct vnode *dvp, struct ufs_lookup_results *ulr, - struct vnode **vp, struct componentname *cnp) +int +lfs_rename(void *v) { - int error; - error = relookup(dvp, vp, cnp, 0); - if (error) { - return error; - } - /* update the supplemental reasults */ - *ulr = VTOI(dvp)->i_crap; - UFS_CHECK_CRAPCOUNTER(VTOI(dvp)); - return 0; + return genfs_insane_rename(v, &lfs_sane_rename); } /* - * Lock and relookup a sequence of two directories and two children. - * + * lfs_gro_rename: Actually perform the rename operation. Do a little + * LFS bookkeeping and then defer to ufs_gro_rename. */ static int -lock_vnode_sequence(struct vnode *d1, struct ufs_lookup_results *ulr1, - struct vnode **v1_ret, struct componentname *cn1, - int v1_missing_ok, - int overlap_error, - struct vnode *d2, struct ufs_lookup_results *ulr2, - struct vnode **v2_ret, struct componentname *cn2, - int v2_missing_ok) +lfs_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 vnode *v1, *v2; int error; - KASSERT(d1 != d2); - - vn_lock(d1, LK_EXCLUSIVE | LK_RETRY); - if (VTOI(d1)->i_size == 0) { - /* d1 has been rmdir'd */ - VOP_UNLOCK(d1); - return ENOENT; - } - error = do_relookup(d1, ulr1, &v1, cn1); - if (v1_missing_ok) { - if (error == ENOENT) { - /* - * Note: currently if the name doesn't exist, - * relookup succeeds (it intercepts the - * EJUSTRETURN from VOP_LOOKUP) and sets tvp - * to NULL. Therefore, we will never get - * ENOENT and this branch is not needed. - * However, in a saner future the EJUSTRETURN - * garbage will go away, so let's DTRT. - */ - v1 = NULL; - error = 0; - } - } else { - if (error == 0 && v1 == NULL) { - /* This is what relookup sets if v1 disappeared. */ - error = ENOENT; - } - } - if (error) { - VOP_UNLOCK(d1); - return error; - } - if (v1 && v1 == d2) { - VOP_UNLOCK(d1); - VOP_UNLOCK(v1); - vrele(v1); - return overlap_error; - } + KASSERT(mp != NULL); + KASSERT(fdvp != NULL); + KASSERT(fcnp != NULL); + KASSERT(fde != NULL); + KASSERT(fvp != NULL); + KASSERT(tdvp != NULL); + KASSERT(tcnp != NULL); + KASSERT(tde != 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)); - /* - * The right way to do this is to do lookups without locking - * the results, and lock the results afterwards; then at the - * end we can avoid trying to lock v2 if v2 == v1. - * - * However, for the reasons described in the fdvp == tdvp case - * in rename below, we can't do that safely. So, in the case - * where v1 is not a directory, unlock it and lock it again - * afterwards. This is safe in locking order because a - * non-directory can't be above anything else in the tree. If - * v1 *is* a directory, that's not true, but then because d1 - * != d2, v1 != v2. - */ - if (v1 && v1->v_type != VDIR) { - VOP_UNLOCK(v1); - } - vn_lock(d2, LK_EXCLUSIVE | LK_RETRY); - if (VTOI(d2)->i_size == 0) { - /* d2 has been rmdir'd */ - VOP_UNLOCK(d2); - if (v1 && v1->v_type == VDIR) { - VOP_UNLOCK(v1); - } - VOP_UNLOCK(d1); - if (v1) { - vrele(v1); - } - return ENOENT; - } - error = do_relookup(d2, ulr2, &v2, cn2); - if (v2_missing_ok) { - if (error == ENOENT) { - /* as above */ - v2 = NULL; - error = 0; - } - } else { - if (error == 0 && v2 == NULL) { - /* This is what relookup sets if v2 disappeared. */ - error = ENOENT; - } - } - if (error) { - VOP_UNLOCK(d2); - if (v1 && v1->v_type == VDIR) { - VOP_UNLOCK(v1); - } - VOP_UNLOCK(d1); - if (v1) { - vrele(v1); - } + error = SET_DIROP_REMOVE(tdvp, tvp); + if (error != 0) return error; - } - if (v1 && v1->v_type != VDIR && v1 != v2) { - vn_lock(v1, LK_EXCLUSIVE | LK_RETRY); - } - *v1_ret = v1; - *v2_ret = v2; - return 0; -} - -int -lfs_rename(void *v) -{ - struct vop_rename_args /* { - struct vnode *a_fdvp; - struct vnode *a_fvp; - struct componentname *a_fcnp; - struct vnode *a_tdvp; - struct vnode *a_tvp; - struct componentname *a_tcnp; - } */ *ap = v; - struct vnode *tvp, *tdvp, *fvp, *fdvp; - struct componentname *tcnp, *fcnp; - struct inode *ip, *txp, *fxp, *tdp, *fdp; - struct mount *mp; - struct direct *newdir; - int doingdirectory, error, marked; - ino_t oldparent, newparent; - - struct ufs_lookup_results from_ulr, to_ulr; - struct lfs *fs = VTOI(ap->a_fvp)->i_lfs; - tvp = ap->a_tvp; - tdvp = ap->a_tdvp; - fvp = ap->a_fvp; - fdvp = ap->a_fdvp; - tcnp = ap->a_tcnp; - fcnp = ap->a_fcnp; - doingdirectory = error = 0; - oldparent = newparent = 0; - marked = 0; - - /* save the supplemental lookup results as they currently exist */ - from_ulr = VTOI(fdvp)->i_crap; - to_ulr = VTOI(tdvp)->i_crap; - UFS_CHECK_CRAPCOUNTER(VTOI(fdvp)); - UFS_CHECK_CRAPCOUNTER(VTOI(tdvp)); - - /* - * Owing to VFS oddities we are currently called with tdvp/tvp - * locked and not fdvp/fvp. In a sane world we'd be passed - * tdvp and fdvp only, unlocked, and two name strings. Pretend - * we have a sane world and unlock tdvp and tvp. - */ - VOP_UNLOCK(tdvp); - if (tvp && tvp != tdvp) { - VOP_UNLOCK(tvp); - } - - /* Also pretend we have a sane world and vrele fvp/tvp. */ - vrele(fvp); - fvp = NULL; - if (tvp) { - vrele(tvp); - tvp = NULL; - } - - /* - * Check for cross-device rename. - */ - if (fdvp->v_mount != tdvp->v_mount) { - error = EXDEV; - goto abort; - } - - /* - * Reject "." and ".." - */ - if ((fcnp->cn_flags & ISDOTDOT) || (tcnp->cn_flags & ISDOTDOT) || - (fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') || - (tcnp->cn_namelen == 1 && tcnp->cn_nameptr[0] == '.')) { - error = EINVAL; - goto abort; - } - - /* - * Get locks. - */ - - /* paranoia */ - fcnp->cn_flags |= LOCKPARENT|LOCKLEAF; - tcnp->cn_flags |= LOCKPARENT|LOCKLEAF; - - if (fdvp == tdvp) { - /* One directory. Lock it and relookup both children. */ - vn_lock(fdvp, LK_EXCLUSIVE | LK_RETRY); - - if (VTOI(fdvp)->i_size == 0) { - /* directory has been rmdir'd */ - VOP_UNLOCK(fdvp); - error = ENOENT; - goto abort; - } - - error = do_relookup(fdvp, &from_ulr, &fvp, fcnp); - if (error == 0 && fvp == NULL) { - /* relookup may produce this if fvp disappears */ - error = ENOENT; - } - if (error) { - VOP_UNLOCK(fdvp); - goto abort; - } - - /* - * The right way to do this is to look up both children - * without locking either, and then lock both unless they - * turn out to be the same. However, due to deep-seated - * VFS-level issues all lookups lock the child regardless - * of whether LOCKLEAF is set (if LOCKLEAF is not set, - * the child is locked during lookup and then unlocked) - * so it is not safe to look up tvp while fvp is locked. - * - * Unlocking fvp here temporarily is more or less safe, - * because with the directory locked there's not much - * that can happen to it. However, ideally it wouldn't - * be necessary. XXX. - */ - VOP_UNLOCK(fvp); - /* remember fdvp == tdvp so tdvp is locked */ - error = do_relookup(tdvp, &to_ulr, &tvp, tcnp); - if (error && error != ENOENT) { - VOP_UNLOCK(fdvp); - goto abort; - } - if (error == ENOENT) { - /* - * Note: currently if the name doesn't exist, - * relookup succeeds (it intercepts the - * EJUSTRETURN from VOP_LOOKUP) and sets tvp - * to NULL. Therefore, we will never get - * ENOENT and this branch is not needed. - * However, in a saner future the EJUSTRETURN - * garbage will go away, so let's DTRT. - */ - tvp = NULL; - } - - /* tvp is locked; lock fvp if necessary */ - if (!tvp || tvp != fvp) { - vn_lock(fvp, LK_EXCLUSIVE | LK_RETRY); - } - } else { - int found_fdvp; - struct vnode *illegal_fvp; - - /* - * The source must not be above the destination. (If - * it were, the rename would detach a section of the - * tree.) - * - * Look up the tree from tdvp to see if we find fdvp, - * and if so, return the immediate child of fdvp we're - * under; that must not turn out to be the same as - * fvp. - * - * The per-volume rename lock guarantees that the - * result of this check remains true until we finish - * looking up and locking. - */ - error = ufs_parentcheck(fdvp, tdvp, fcnp->cn_cred, - &found_fdvp, &illegal_fvp); - if (error) { - goto abort; - } - - /* Must lock in tree order. */ - - if (found_fdvp) { - /* fdvp -> fvp -> tdvp -> tvp */ - error = lock_vnode_sequence(fdvp, &from_ulr, - &fvp, fcnp, 0, - EINVAL, - tdvp, &to_ulr, - &tvp, tcnp, 1); - } else { - /* tdvp -> tvp -> fdvp -> fvp */ - error = lock_vnode_sequence(tdvp, &to_ulr, - &tvp, tcnp, 1, - ENOTEMPTY, - fdvp, &from_ulr, - &fvp, fcnp, 0); - } - if (error) { - if (illegal_fvp) { - vrele(illegal_fvp); - } - goto abort; - } - KASSERT(fvp != NULL); - - if (illegal_fvp && fvp == illegal_fvp) { - vrele(illegal_fvp); - error = EINVAL; - goto abort_withlocks; - } - - if (illegal_fvp) { - vrele(illegal_fvp); - } - } - - KASSERT(fdvp && VOP_ISLOCKED(fdvp)); - KASSERT(fvp && VOP_ISLOCKED(fvp)); - KASSERT(tdvp && VOP_ISLOCKED(tdvp)); - KASSERT(tvp == NULL || VOP_ISLOCKED(tvp)); - - /* --- everything is now locked --- */ - - if (tvp && ((VTOI(tvp)->i_flags & (IMMUTABLE | APPEND)) || - (VTOI(tdvp)->i_flags & APPEND))) { - error = EPERM; - goto abort_withlocks; - } - - /* - * Check if just deleting a link name. - */ - if (fvp == tvp) { - if (fvp->v_type == VDIR) { - error = EINVAL; - goto abort_withlocks; - } - - /* Release destination completely. Leave fdvp locked. */ - VOP_ABORTOP(tdvp, tcnp); - if (fdvp != tdvp) { - VOP_UNLOCK(tdvp); - } - VOP_UNLOCK(tvp); - vrele(tdvp); - vrele(tvp); - - /* Delete source. */ - /* XXX: do we really need to relookup again? */ - - /* - * fdvp is still locked, but we just unlocked fvp - * (because fvp == tvp) so just decref fvp - */ - vrele(fvp); - fcnp->cn_flags &= ~(MODMASK); - fcnp->cn_flags |= LOCKPARENT | LOCKLEAF; - fcnp->cn_nameiop = DELETE; - if ((error = relookup(fdvp, &fvp, fcnp, 0))) { - vput(fdvp); - return (error); - } - return (VOP_REMOVE(fdvp, fvp, fcnp)); - } - - /* The tiny bit of actual LFS code in this function */ - if ((error = SET_DIROP_REMOVE(tdvp, tvp)) != 0) - goto abort_withlocks; MARK_VNODE(fdvp); MARK_VNODE(fvp); - marked = 1; - - fdp = VTOI(fdvp); - ip = VTOI(fvp); - if ((nlink_t) ip->i_nlink >= LINK_MAX) { - error = EMLINK; - goto abort_withlocks; - } - if ((ip->i_flags & (IMMUTABLE | APPEND)) || - (fdp->i_flags & APPEND)) { - error = EPERM; - goto abort_withlocks; - } - if ((ip->i_mode & IFMT) == IFDIR) { - /* - * Avoid ".", "..", and aliases of "." for obvious reasons. - */ - if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') || - fdp == ip || - (fcnp->cn_flags & ISDOTDOT) || - (tcnp->cn_flags & ISDOTDOT) || - (ip->i_flag & IN_RENAME)) { - error = EINVAL; - goto abort_withlocks; - } - ip->i_flag |= IN_RENAME; - doingdirectory = 1; - } - oldparent = fdp->i_number; - VN_KNOTE(fdvp, NOTE_WRITE); /* XXXLUKEM/XXX: right place? */ - - /* - * Both the directory - * and target vnodes are locked. - */ - tdp = VTOI(tdvp); - txp = NULL; - if (tvp) - txp = VTOI(tvp); - - mp = fdvp->v_mount; - fstrans_start(mp, FSTRANS_SHARED); - - if (oldparent != tdp->i_number) - newparent = tdp->i_number; - - /* - * If ".." must be changed (ie the directory gets a new - * parent) the user must have write permission in the source - * so as to be able to change "..". - */ - if (doingdirectory && newparent) { - error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred); - if (error) - goto out; - } - - KASSERT(fdvp != tvp); - - if (newparent) { - /* Check for the rename("foo/foo", "foo") case. */ - if (fdvp == tvp) { - error = doingdirectory ? ENOTEMPTY : EISDIR; - goto out; - } - } - - fxp = VTOI(fvp); - fdp = VTOI(fdvp); - - error = UFS_WAPBL_BEGIN(fdvp->v_mount); - if (error) - goto out2; - - /* - * 1) Bump link count while we're moving stuff - * around. If we crash somewhere before - * completing our work, the link count - * may be wrong, but correctable. - */ - ip->i_nlink++; - DIP_ASSIGN(ip, nlink, ip->i_nlink); - ip->i_flag |= IN_CHANGE; - if ((error = UFS_UPDATE(fvp, NULL, NULL, UPDATE_DIROP)) != 0) { - goto bad; - } - - /* - * 2) If target doesn't exist, link the target - * to the source and unlink the source. - * Otherwise, rewrite the target directory - * entry to reference the source inode and - * expunge the original entry's existence. - */ - if (txp == NULL) { - if (tdp->i_dev != ip->i_dev) - panic("rename: EXDEV"); - /* - * Account for ".." in new directory. - * When source and destination have the same - * parent we don't fool with the link count. - */ - if (doingdirectory && newparent) { - if ((nlink_t)tdp->i_nlink >= LINK_MAX) { - error = EMLINK; - goto bad; - } - tdp->i_nlink++; - DIP_ASSIGN(tdp, nlink, tdp->i_nlink); - tdp->i_flag |= IN_CHANGE; - if ((error = UFS_UPDATE(tdvp, NULL, NULL, - UPDATE_DIROP)) != 0) { - tdp->i_nlink--; - DIP_ASSIGN(tdp, nlink, tdp->i_nlink); - tdp->i_flag |= IN_CHANGE; - goto bad; - } - } - newdir = pool_cache_get(ufs_direct_cache, PR_WAITOK); - ufs_makedirentry(ip, tcnp, newdir); - error = ufs_direnter(tdvp, &to_ulr, - NULL, newdir, tcnp, NULL); - pool_cache_put(ufs_direct_cache, newdir); - if (error != 0) { - if (doingdirectory && newparent) { - tdp->i_nlink--; - DIP_ASSIGN(tdp, nlink, tdp->i_nlink); - tdp->i_flag |= IN_CHANGE; - (void)UFS_UPDATE(tdvp, NULL, NULL, - UPDATE_WAIT | UPDATE_DIROP); - } - goto bad; - } - VN_KNOTE(tdvp, NOTE_WRITE); - } else { - if (txp->i_dev != tdp->i_dev || txp->i_dev != ip->i_dev) - panic("rename: EXDEV"); - /* - * Short circuit rename(foo, foo). - */ - if (txp->i_number == ip->i_number) - panic("rename: same file"); - /* - * If the parent directory is "sticky", then the user must - * own the parent directory, or the destination of the rename, - * otherwise the destination may not be changed (except by - * root). This implements append-only directories. - */ - if (tdp->i_mode & S_ISTXT) { - error = kauth_authorize_vnode(tcnp->cn_cred, - KAUTH_VNODE_DELETE, tvp, tdvp, - genfs_can_sticky(tcnp->cn_cred, tdp->i_uid, - txp->i_uid)); - if (error) - goto bad; - } - /* - * Target must be empty if a directory and have no links - * to it. Also, ensure source and target are compatible - * (both directories, or both not directories). - */ - if ((txp->i_mode & IFMT) == IFDIR) { - if (txp->i_nlink > 2 || - !ufs_dirempty(txp, tdp->i_number, tcnp->cn_cred)) { - error = ENOTEMPTY; - goto bad; - } - if (!doingdirectory) { - error = ENOTDIR; - goto bad; - } - cache_purge(tdvp); - } else if (doingdirectory) { - error = EISDIR; - goto bad; - } - if ((error = ufs_dirrewrite(tdp, to_ulr.ulr_offset, - txp, ip->i_number, - IFTODT(ip->i_mode), doingdirectory && newparent ? - newparent : doingdirectory, IN_CHANGE | IN_UPDATE)) != 0) - goto bad; - if (doingdirectory) { - /* - * Truncate inode. The only stuff left in the directory - * is "." and "..". The "." reference is inconsequential - * since we are quashing it. We have removed the "." - * reference and the reference in the parent directory, - * but there may be other hard links. - */ - if (!newparent) { - tdp->i_nlink--; - DIP_ASSIGN(tdp, nlink, tdp->i_nlink); - tdp->i_flag |= IN_CHANGE; - UFS_WAPBL_UPDATE(tdvp, NULL, NULL, 0); - } - txp->i_nlink--; - DIP_ASSIGN(txp, nlink, txp->i_nlink); - txp->i_flag |= IN_CHANGE; - if ((error = UFS_TRUNCATE(tvp, (off_t)0, IO_SYNC, - tcnp->cn_cred))) - goto bad; - } - VN_KNOTE(tdvp, NOTE_WRITE); - VN_KNOTE(tvp, NOTE_DELETE); - } - - /* - * Handle case where the directory entry we need to remove, - * which is/was at from_ulr.ulr_offset, or the one before it, - * which is/was at from_ulr.ulr_offset - from_ulr.ulr_count, - * may have been moved when the directory insertion above - * performed compaction. - */ - if (tdp->i_number == fdp->i_number && - ulr_overlap(&from_ulr, &to_ulr)) { - - struct buf *bp; - struct direct *ep; - struct ufsmount *ump = fdp->i_ump; - doff_t curpos; - doff_t endsearch; /* offset to end directory search */ - uint32_t prev_reclen; - int dirblksiz = ump->um_dirblksiz; - const int needswap = UFS_MPNEEDSWAP(ump); - u_long bmask; - int namlen, entryoffsetinblock; - char *dirbuf; - - bmask = fdvp->v_mount->mnt_stat.f_iosize - 1; - - /* - * The fcnp entry will be somewhere between the start of - * compaction (to_ulr.ulr_offset) and the original location - * (from_ulr.ulr_offset). - */ - curpos = to_ulr.ulr_offset; - endsearch = from_ulr.ulr_offset + from_ulr.ulr_reclen; - entryoffsetinblock = 0; - - /* - * Get the directory block containing the start of - * compaction. - */ - error = ufs_blkatoff(fdvp, (off_t)to_ulr.ulr_offset, &dirbuf, - &bp, false); - if (error) - goto bad; - - /* - * Keep existing ulr_count (length of previous record) - * for the case where compaction did not include the - * previous entry but started at the from-entry. - */ - prev_reclen = from_ulr.ulr_count; - - while (curpos < endsearch) { - uint32_t reclen; - - /* - * If necessary, get the next directory block. - * - * dholland 7/13/11 to the best of my understanding - * this should never happen; compaction occurs only - * within single blocks. I think. - */ - if ((curpos & bmask) == 0) { - if (bp != NULL) - brelse(bp, 0); - error = ufs_blkatoff(fdvp, (off_t)curpos, - &dirbuf, &bp, false); - if (error) - goto bad; - entryoffsetinblock = 0; - } - - KASSERT(bp != NULL); - ep = (struct direct *)(dirbuf + entryoffsetinblock); - reclen = ufs_rw16(ep->d_reclen, needswap); - -#if (BYTE_ORDER == LITTLE_ENDIAN) - if (FSFMT(fdvp) && needswap == 0) - namlen = ep->d_type; - else - namlen = ep->d_namlen; -#else - if (FSFMT(fdvp) && needswap != 0) - namlen = ep->d_type; - else - namlen = ep->d_namlen; -#endif - if ((ep->d_ino != 0) && - (ufs_rw32(ep->d_ino, needswap) != WINO) && - (namlen == fcnp->cn_namelen) && - memcmp(ep->d_name, fcnp->cn_nameptr, namlen) == 0) { - from_ulr.ulr_reclen = reclen; - break; - } - curpos += reclen; - entryoffsetinblock += reclen; - prev_reclen = reclen; - } - - from_ulr.ulr_offset = curpos; - from_ulr.ulr_count = prev_reclen; - - KASSERT(curpos <= endsearch); - - /* - * If ulr_offset points to start of a directory block, - * clear ulr_count so ufs_dirremove() doesn't try to - * merge free space over a directory block boundary. - */ - if ((from_ulr.ulr_offset & (dirblksiz - 1)) == 0) - from_ulr.ulr_count = 0; - - brelse(bp, 0); - } - - /* - * 3) Unlink the source. - */ - -#if 0 - /* - * Ensure that the directory entry still exists and has not - * changed while the new name has been entered. If the source is - * a file then the entry may have been unlinked or renamed. In - * either case there is no further work to be done. If the source - * is a directory then it cannot have been rmdir'ed; The IRENAME - * flag ensures that it cannot be moved by another rename or removed - * by a rmdir. - */ -#endif - KASSERT(fxp == ip); - /* - * If the source is a directory with a new parent, the link - * count of the old parent directory must be decremented and - * ".." set to point to the new parent. - */ - if (doingdirectory && newparent) { - KASSERT(fdp != NULL); - ufs_dirrewrite(fxp, mastertemplate.dot_reclen, - fdp, newparent, DT_DIR, 0, IN_CHANGE); - cache_purge(fdvp); - } - error = ufs_dirremove(fdvp, &from_ulr, - fxp, fcnp->cn_flags, 0); - fxp->i_flag &= ~IN_RENAME; - - VN_KNOTE(fvp, NOTE_RENAME); - goto done; - - out: - goto out2; - - /* exit routines from steps 1 & 2 */ - bad: - if (doingdirectory) - ip->i_flag &= ~IN_RENAME; - ip->i_nlink--; - DIP_ASSIGN(ip, nlink, ip->i_nlink); - ip->i_flag |= IN_CHANGE; - ip->i_flag &= ~IN_RENAME; - UFS_WAPBL_UPDATE(fvp, NULL, NULL, 0); - done: - UFS_WAPBL_END(fdvp->v_mount); - out2: - /* - * clear IN_RENAME - some exit paths happen too early to go - * through the cleanup done in the "bad" case above, so we - * always do this mini-cleanup here. - */ - ip->i_flag &= ~IN_RENAME; - - VOP_UNLOCK(fdvp); - if (tdvp != fdvp) { - VOP_UNLOCK(tdvp); - } - VOP_UNLOCK(fvp); - if (tvp && tvp != fvp) { - VOP_UNLOCK(tvp); - } - - vrele(fdvp); - vrele(tdvp); - vrele(fvp); - if (tvp) { - vrele(tvp); - } + error = ufs_gro_rename(mp, cred, + fdvp, fcnp, fde, fvp, + tdvp, tcnp, tde, tvp); - fstrans_done(mp); - if (marked) { UNMARK_VNODE(fdvp); UNMARK_VNODE(fvp); - SET_ENDOP_REMOVE(fs, tdvp, tvp, "rename"); - } - return (error); - - abort_withlocks: - VOP_UNLOCK(fdvp); - if (tdvp != fdvp) { - VOP_UNLOCK(tdvp); - } - VOP_UNLOCK(fvp); - if (tvp && tvp != fvp) { - VOP_UNLOCK(tvp); - } + SET_ENDOP_REMOVE(VFSTOUFS(mp)->um_lfs, tdvp, tvp, "rename"); - abort: - VOP_ABORTOP(fdvp, fcnp); /* XXX, why not in NFS? */ - VOP_ABORTOP(tdvp, tcnp); /* XXX, why not in NFS? */ - vrele(tdvp); - if (tvp) { - vrele(tvp); - } - vrele(fdvp); - if (fvp) { - vrele(fvp); - } - if (marked) { - UNMARK_VNODE(fdvp); - UNMARK_VNODE(fvp); - SET_ENDOP_REMOVE(fs, tdvp, tvp, "rename"); - } - return (error); + return error; } +static const struct genfs_rename_ops lfs_genfs_rename_ops = { + .gro_directory_empty_p = ufs_gro_directory_empty_p, + .gro_rename_check_possible = ufs_gro_rename_check_possible, + .gro_rename_check_permitted = ufs_gro_rename_check_permitted, + .gro_remove_check_possible = ufs_gro_remove_check_possible, + .gro_remove_check_permitted = ufs_gro_remove_check_permitted, + .gro_rename = lfs_gro_rename, + .gro_remove = ufs_gro_remove, + .gro_lookup = ufs_gro_lookup, + .gro_genealogy = ufs_gro_genealogy, + .gro_lock_directory = ufs_gro_lock_directory, +}; + /* XXX hack to avoid calling ITIMES in getattr */ int lfs_getattr(void *v) Index: sys/ufs/ufs/ufs_extern.h =================================================================== RCS file: /cvsroot/src/sys/ufs/ufs/ufs_extern.h,v retrieving revision 1.71 diff -p -u -r1.71 ufs_extern.h --- sys/ufs/ufs/ufs_extern.h 1 Feb 2012 05:34:43 -0000 1.71 +++ sys/ufs/ufs/ufs_extern.h 7 Apr 2012 21:19:57 -0000 @@ -139,6 +139,29 @@ int ufs_parentcheck(struct vnode *, stru int *, struct vnode **); int ufs_blkatoff(struct vnode *, off_t, char **, struct buf **, bool); +/* ufs_rename.c -- for lfs */ +bool ufs_gro_directory_empty_p(struct mount *, kauth_cred_t, + struct vnode *, struct vnode *); +int ufs_gro_rename_check_possible(struct mount *, + struct vnode *, struct vnode *, struct vnode *, struct vnode *); +int ufs_gro_rename_check_permitted(struct mount *, kauth_cred_t, + struct vnode *, struct vnode *, struct vnode *, struct vnode *); +int ufs_gro_remove_check_possible(struct mount *, + struct vnode *, struct vnode *); +int ufs_gro_remove_check_permitted(struct mount *, kauth_cred_t, + struct vnode *, struct vnode *); +int ufs_gro_rename(struct mount *, kauth_cred_t, + struct vnode *, struct componentname *, void *, struct vnode *, + struct vnode *, struct componentname *, void *, struct vnode *); +int ufs_gro_remove(struct mount *, kauth_cred_t, + struct vnode *, struct componentname *, void *, struct vnode *); +int ufs_gro_lookup(struct mount *, struct vnode *, + struct componentname *, void *, struct vnode **); +int ufs_gro_genealogy(struct mount *, kauth_cred_t, + struct vnode *, struct vnode *, struct vnode **); +int ufs_gro_lock_directory(struct mount *, struct vnode *); + + /* ufs_quota.c */ /* * Flags to chkdq() and chkiq() Index: sys/ufs/ufs/ufs_lookup.c =================================================================== RCS file: /cvsroot/src/sys/ufs/ufs/ufs_lookup.c,v retrieving revision 1.113 diff -p -u -r1.113 ufs_lookup.c --- sys/ufs/ufs/ufs_lookup.c 16 Mar 2012 08:39:54 -0000 1.113 +++ sys/ufs/ufs/ufs_lookup.c 7 Apr 2012 21:19:57 -0000 @@ -76,8 +76,6 @@ int dirchk = 1; int dirchk = 0; #endif -#define FSFMT(vp) (((vp)->v_mount->mnt_iflag & IMNT_DTYPE) == 0) - /* * Convert a component of a pathname into a pointer to a locked inode. * This is a very central and rather complicated routine. Index: sys/ufs/ufs/ufs_rename.c =================================================================== RCS file: sys/ufs/ufs/ufs_rename.c diff -N sys/ufs/ufs/ufs_rename.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ sys/ufs/ufs/ufs_rename.c 8 Apr 2012 23:13:13 -0000 @@ -0,0 +1,1080 @@ +/* $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. + */ + +/* + * UFS Rename + */ + +#include +__KERNEL_RCSID(0, "$NetBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +/* + * Forward declarations + */ + +static int ufs_sane_rename(struct vnode *, struct componentname *, + struct vnode *, struct componentname *, + kauth_cred_t, bool); +static bool ufs_rename_ulr_overlap_p(const struct ufs_lookup_results *, + const struct ufs_lookup_results *); +static int ufs_rename_recalculate_fulr(struct vnode *, + struct ufs_lookup_results *, const struct ufs_lookup_results *, + const struct componentname *); +static int ufs_direct_namlen(const struct direct *, const struct vnode *); +static int ufs_read_dotdot(struct vnode *, kauth_cred_t, ino_t *); +static int ufs_dirbuf_dotdot_namlen(const struct dirtemplate *, + const struct vnode *); + +static const struct genfs_rename_ops ufs_genfs_rename_ops; + +/* + * ufs_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 +ufs_sane_rename( + struct vnode *fdvp, struct componentname *fcnp, + struct vnode *tdvp, struct componentname *tcnp, + kauth_cred_t cred, bool posixly_correct) +{ + struct ufs_lookup_results fulr, tulr; + + return genfs_sane_rename(&ufs_genfs_rename_ops, + fdvp, fcnp, &fulr, tdvp, tcnp, &tulr, + cred, posixly_correct); +} + +/* + * ufs_rename: The hairiest vop, with the insanest API. Defer to + * genfs_insane_rename immediately. + */ +int +ufs_rename(void *v) +{ + + return genfs_insane_rename(v, &ufs_sane_rename); +} + +/* + * ufs_gro_directory_empty_p: Return true if the directory vp is + * empty. dvp is its parent. + * + * vp and dvp must be locked and referenced. + */ +bool +ufs_gro_directory_empty_p(struct mount *mp, kauth_cred_t cred, + struct vnode *vp, struct vnode *dvp) +{ + + (void)mp; + 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 ufs_dirempty(VTOI(vp), VTOI(dvp)->i_number, cred); +} + +/* + * ufs_gro_rename_check_possible: Check whether a rename is possible + * independent of credentials. + */ +int +ufs_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( + VTOI(fdvp)->i_flags, VTOI(fvp)->i_flags, + VTOI(tdvp)->i_flags, (tvp? VTOI(tvp)->i_flags : 0), + (tvp != NULL), + IMMUTABLE, APPEND); +} + +/* + * ufs_gro_rename_check_permitted: Check whether a rename is permitted + * given our credentials. + */ +int +ufs_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, VTOI(fdvp)->i_mode, VTOI(fdvp)->i_uid, + fvp, VTOI(fvp)->i_uid, + tdvp, VTOI(tdvp)->i_mode, VTOI(tdvp)->i_uid, + tvp, (tvp? VTOI(tvp)->i_uid : 0)); +} + +/* + * ufs_gro_remove_check_possible: Check whether a remove is possible + * independent of credentials. + */ +int +ufs_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( + VTOI(dvp)->i_flags, VTOI(vp)->i_flags, + IMMUTABLE, APPEND); +} + +/* + * ufs_gro_remove_check_permitted: Check whether a remove is permitted + * given our credentials. + */ +int +ufs_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, VTOI(dvp)->i_mode, VTOI(dvp)->i_uid, vp, VTOI(vp)->i_uid); +} + +/* + * A virgin directory (no blushing please). + * + * XXX Copypasta from ufs_vnops.c. Kill! + */ +static const struct dirtemplate mastertemplate = { + 0, 12, DT_DIR, 1, ".", + 0, DIRBLKSIZ - 12, DT_DIR, 2, ".." +}; + +/* + * ufs_gro_rename: Actually perform the rename operation. + */ +int +ufs_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 ufs_lookup_results *fulr = fde; + struct ufs_lookup_results *tulr = tde; + bool directory_p, reparent_p; + struct direct *newdir; + int error; + + KASSERT(mp != NULL); + KASSERT(fdvp != NULL); + KASSERT(fcnp != NULL); + KASSERT(fulr != NULL); + KASSERT(fvp != NULL); + KASSERT(tdvp != NULL); + KASSERT(tcnp != NULL); + KASSERT(tulr != NULL); + KASSERT(fulr != tulr); + 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 link count, so make + * sure there is room to do so. + */ + if ((nlink_t)VTOI(fvp)->i_nlink >= LINK_MAX) + return EMLINK; + + /* + * XXX There is a pile of logic here to handle a voodoo flag + * IN_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 == ((VTOI(fvp)->i_mode & IFMT) == IFDIR)); + KASSERT((tvp == NULL) || (directory_p == (tvp->v_type == VDIR))); + KASSERT((tvp == NULL) || (directory_p == + ((VTOI(tvp)->i_mode & IFMT) == IFDIR))); + if (directory_p) { + if (VTOI(fvp)->i_flag & IN_RENAME) + return EINVAL; + VTOI(fvp)->i_flag |= IN_RENAME; + } + + reparent_p = (fdvp != tdvp); + KASSERT(reparent_p == (VTOI(fdvp)->i_number != VTOI(tdvp)->i_number)); + + /* + * Commence hacking of the data on disk. + */ + + fstrans_start(mp, FSTRANS_SHARED); + error = UFS_WAPBL_BEGIN(mp); + if (error) + goto ihateyou; + + /* + * 1) Bump link count while we're moving stuff + * around. If we crash somewhere before + * completing our work, the link count + * may be wrong, but correctable. + */ + + KASSERT((nlink_t)VTOI(fvp)->i_nlink < LINK_MAX); + VTOI(fvp)->i_nlink++; + DIP_ASSIGN(VTOI(fvp), nlink, VTOI(fvp)->i_nlink); + VTOI(fvp)->i_flag |= IN_CHANGE; + error = UFS_UPDATE(fvp, NULL, NULL, UPDATE_DIROP); + if (error) + goto whymustithurtsomuch; + + /* + * 2) If target doesn't exist, link the target + * to the source and unlink the source. + * Otherwise, rewrite the target directory + * entry to reference the source inode and + * expunge the original entry's existence. + */ + + if (tvp == NULL) { + /* + * Account for ".." in new directory. + * When source and destination have the same + * parent we don't fool with the link count. + */ + if (directory_p && reparent_p) { + if ((nlink_t)VTOI(tdvp)->i_nlink >= LINK_MAX) { + error = EMLINK; + goto whymustithurtsomuch; + } + KASSERT((nlink_t)VTOI(tdvp)->i_nlink < LINK_MAX); + VTOI(tdvp)->i_nlink++; + DIP_ASSIGN(VTOI(tdvp), nlink, VTOI(tdvp)->i_nlink); + VTOI(tdvp)->i_flag |= IN_CHANGE; + error = UFS_UPDATE(tdvp, NULL, NULL, UPDATE_DIROP); + if (error) { + /* + * Link count update didn't take -- + * back out the in-memory link count. + */ + KASSERT(0 < VTOI(tdvp)->i_nlink); + VTOI(tdvp)->i_nlink--; + DIP_ASSIGN(VTOI(tdvp), nlink, + VTOI(tdvp)->i_nlink); + VTOI(tdvp)->i_flag |= IN_CHANGE; + goto whymustithurtsomuch; + } + } + + newdir = pool_cache_get(ufs_direct_cache, PR_WAITOK); + ufs_makedirentry(VTOI(fvp), tcnp, newdir); + error = ufs_direnter(tdvp, tulr, NULL, newdir, tcnp, NULL); + pool_cache_put(ufs_direct_cache, newdir); + if (error) { + if (directory_p && reparent_p) { + /* + * Directory update didn't take, but + * the link count update did -- back + * out the in-memory link count and the + * on-disk link count. + */ + KASSERT(0 < VTOI(tdvp)->i_nlink); + VTOI(tdvp)->i_nlink--; + DIP_ASSIGN(VTOI(tdvp), nlink, + VTOI(tdvp)->i_nlink); + VTOI(tdvp)->i_flag |= IN_CHANGE; + (void)UFS_UPDATE(tdvp, NULL, NULL, + UPDATE_WAIT | UPDATE_DIROP); + } + goto whymustithurtsomuch; + } + } else { + if (directory_p) + /* XXX WTF? Why purge here? Why not purge others? */ + cache_purge(tdvp); + + /* + * Make the target directory's entry for tcnp point at + * the source node. + * + * XXX ufs_dirrewrite decrements tvp's link count, but + * doesn't touch the link count of the new inode. Go + * figure. + */ + error = ufs_dirrewrite(VTOI(tdvp), tulr->ulr_offset, + VTOI(tvp), VTOI(fvp)->i_number, IFTODT(VTOI(fvp)->i_mode), + ((directory_p && reparent_p) ? reparent_p : directory_p), + IN_CHANGE | IN_UPDATE); + if (error) + goto whymustithurtsomuch; + + /* + * If the source and target are directories, and the + * target is in the same directory as the source, + * decrement the link count of the common parent + * directory, since we are removing the target from + * that directory. + */ + if (directory_p && !reparent_p) { + KASSERT(fdvp == tdvp); + /* XXX check, don't kassert */ + KASSERT(0 < VTOI(tdvp)->i_nlink); + VTOI(tdvp)->i_nlink--; + DIP_ASSIGN(VTOI(tdvp), nlink, VTOI(tdvp)->i_nlink); + VTOI(tdvp)->i_flag |= IN_CHANGE; + UFS_WAPBL_UPDATE(tdvp, NULL, NULL, 0); + } + + if (directory_p) { + /* + * XXX I don't understand the following comment + * from ufs_rename -- in particular, the part + * about `there may be other hard links'. + * + * Truncate inode. The only stuff left in the directory + * is "." and "..". The "." reference is inconsequential + * since we are quashing it. We have removed the "." + * reference and the reference in the parent directory, + * but there may be other hard links. + * + * XXX The ufs_dirempty call earlier does + * not guarantee anything about nlink. + */ + if (VTOI(tvp)->i_nlink != 1) + ufs_dirbad(VTOI(tvp), (doff_t)0, + "hard-linked directory"); + VTOI(tvp)->i_nlink = 0; + DIP_ASSIGN(VTOI(tvp), nlink, 0); + error = UFS_TRUNCATE(tvp, (off_t)0, IO_SYNC, cred); + if (error) + goto whymustithurtsomuch; + } + } + + /* + * If the source is a directory with a new parent, the link + * count of the old parent directory must be decremented and + * ".." set to point to the new parent. + * + * XXX ufs_dirrewrite updates the link count of fdvp, but not + * the link count of fvp or the link count of tdvp. Go figure. + */ + if (directory_p && reparent_p) { + error = ufs_dirrewrite(VTOI(fvp), mastertemplate.dot_reclen, + VTOI(fdvp), VTOI(tdvp)->i_number, DT_DIR, 0, IN_CHANGE); +#if 0 /* XXX This branch was not in ufs_rename! */ + if (error) + goto whymustithurtsomuch; +#endif + + /* XXX WTF? Why purge here? Why not purge others? */ + cache_purge(fdvp); + } + + /* + * 3) Unlink the source. + */ + + /* + * ufs_direnter may compact the directory in the process of + * inserting a new entry. That may invalidate fulr, which we + * need in order to remove the old entry. In that case, we + * need to recalculate what fulr should be. + * + * XXX I believe this is necessary only if tvp == NULL as well. + */ + if (!reparent_p && ufs_rename_ulr_overlap_p(fulr, tulr)) { + error = ufs_rename_recalculate_fulr(fdvp, fulr, tulr, fcnp); +#if 0 /* XXX */ + if (error) /* XXX Try to back out changes? */ + goto whymustithurtsomuch; +#endif + } + + /* + * XXX 0 means !isrmdir. But can't this be an rmdir? + * XXX Well, turns out that argument to ufs_dirremove is ignored... + * XXX And it turns out ufs_dirremove updates the link count of fvp. + * XXX But it doesn't update the link count of fdvp. Go figure. + * XXX fdvp's link count is updated in ufs_dirrewrite instead. + * XXX Actually, sometimes it doesn't update fvp's link count. + * XXX I hate the world. + */ + error = ufs_dirremove(fdvp, fulr, VTOI(fvp), fcnp->cn_flags, 0); + if (error) +#if 0 /* XXX */ + goto whymustithurtsomuch; +#endif + goto arghmybrainhurts; + + /* + * XXX Perhaps this should go at the top, in case the file + * system is modified but incompletely so because of an + * intermediate error. + */ + genfs_rename_knote(fdvp, fvp, tdvp, tvp, + ((tvp != NULL) && (VTOI(tvp)->i_nlink == 0))); +#if 0 /* XXX */ + genfs_rename_cache_purge(fdvp, fvp, tdvp, tvp); +#endif + goto arghmybrainhurts; + +whymustithurtsomuch: + KASSERT(0 < VTOI(fvp)->i_nlink); + VTOI(fvp)->i_nlink--; + DIP_ASSIGN(VTOI(fvp), nlink, VTOI(fvp)->i_nlink); + VTOI(fvp)->i_flag |= IN_CHANGE; + if (directory_p) + VTOI(fvp)->i_flag &=~ IN_RENAME; + UFS_WAPBL_UPDATE(fvp, NULL, NULL, 0); + +arghmybrainhurts: + UFS_WAPBL_END(mp); + +ihateyou: + fstrans_done(mp); + /* XXX UFS kludge -- get rid of me with IN_RENAME! */ + if (directory_p) + VTOI(fvp)->i_flag &=~ IN_RENAME; + return error; +} + +/* + * ufs_rename_ulr_overlap_p: True iff tulr overlaps with fulr so that + * entering a directory entry at tulr may move fulr. + */ +static bool +ufs_rename_ulr_overlap_p(const struct ufs_lookup_results *fulr, + const struct ufs_lookup_results *tulr) +{ + doff_t from_prev_start, from_prev_end, to_start, to_end; + + KASSERT(fulr != NULL); + KASSERT(tulr != NULL); + KASSERT(fulr != tulr); + + /* + * fulr is from a DELETE lookup, so fulr->ulr_count is the size + * of the preceding entry (d_reclen). + */ + from_prev_end = fulr->ulr_offset; + KASSERT(fulr->ulr_count <= from_prev_end); + from_prev_start = (from_prev_end - fulr->ulr_count); + + /* + * tulr is from a RENAME lookup, so tulr->ulr_count is the size + * of the free space for an entry that we are about to fill. + */ + to_start = tulr->ulr_offset; + KASSERT(tulr->ulr_count < (MAXDIRSIZE - to_start)); + to_end = (to_start + tulr->ulr_count); + + return + (((to_start <= from_prev_start) && (from_prev_start < to_end)) || + ((to_start <= from_prev_end) && (from_prev_end < to_end))); +} + +/* + * ufs_rename_recalculate_fulr: If we have just entered a directory into + * dvp at tulr, and we were about to remove one at fulr for an entry + * named fcnp, fulr may be invalid. So, if necessary, recalculate it. + */ +static int +ufs_rename_recalculate_fulr(struct vnode *dvp, + struct ufs_lookup_results *fulr, const struct ufs_lookup_results *tulr, + const struct componentname *fcnp) +{ + struct mount *mp; + struct ufsmount *ump; + int needswap; + /* XXX int is a silly type for this; blame ufsmount::um_dirblksiz. */ + int directory_block_mask; + unsigned long io_block_mask; + doff_t offset; /* Offset of entry we're examining. */ + doff_t search_end; /* Limit to our search. */ + struct buf *bp; /* I/O block we're examining. */ + char *dirbuf; /* Pointer into bp's data. */ + doff_t dirbuf_offset; /* Offset of dirbuf from directory start. */ + struct direct *ep; /* Pointer to the entry we're examining. */ + /* XXX direct::d_reclen is 16-bit; + * ufs_lookup_results::ulr_reclen is 32-bit. Blah. */ + uint32_t reclen; /* Length of the entry we're examining. */ + uint32_t prev_reclen; /* Length of the preceding entry. */ + int error; + + KASSERT(dvp != NULL); + KASSERT(dvp->v_mount != NULL); + KASSERT(VTOI(dvp) != NULL); + KASSERT(fulr != NULL); + KASSERT(tulr != NULL); + KASSERT(fulr != tulr); + KASSERT(ufs_rename_ulr_overlap_p(fulr, tulr)); + + mp = dvp->v_mount; + ump = VFSTOUFS(mp); + KASSERT(ump != NULL); + KASSERT(ump == VTOI(dvp)->i_ump); + + needswap = UFS_MPNEEDSWAP(ump); + + KASSERT(0 < ump->um_dirblksiz); + KASSERT((ump->um_dirblksiz & (ump->um_dirblksiz - 1)) == 0); + directory_block_mask = (ump->um_dirblksiz - 1); + + KASSERT(0 < mp->mnt_stat.f_iosize); + KASSERT((mp->mnt_stat.f_iosize & (mp->mnt_stat.f_iosize - 1)) == 0); + io_block_mask = (mp->mnt_stat.f_iosize - 1); + + offset = tulr->ulr_offset; + KASSERT(fulr->ulr_reclen < (MAXDIRSIZE - fulr->ulr_offset)); + search_end = (fulr->ulr_offset + fulr->ulr_reclen); + + dirbuf = NULL; + bp = NULL; + dirbuf_offset = offset; + error = ufs_blkatoff(dvp, (off_t)dirbuf_offset, &dirbuf, &bp, false); + if (error) + return error; + KASSERT(dirbuf != NULL); + KASSERT(bp != NULL); + + prev_reclen = fulr->ulr_count; + + /* + * Search from offset to search_end for the entry matching + * fcnp, which must be there because we found it before and it + * should only at most have moved earlier. + */ + for (;;) { + KASSERT(offset < search_end); + + /* + * If we are at an I/O block boundary, fetch the next block. + */ + if ((offset & io_block_mask) == 0) { +#ifdef DIAGNOSTIC /* XXX */ + printf("%s: directory block of inode 0x%llx" + " extends across I/O block boundary," + " which shouldn't happen!\n", + mp->mnt_stat.f_mntonname, + (unsigned long long)VTOI(dvp)->i_number); +#endif + brelse(bp, 0); + dirbuf = NULL; + bp = NULL; + dirbuf_offset = offset; + error = ufs_blkatoff(dvp, (off_t)dirbuf_offset, + &dirbuf, &bp, false); + if (error) + return error; + KASSERT(dirbuf != NULL); + KASSERT(bp != NULL); + } + + /* + * Examine the directory entry at offset. + */ + KASSERT(dirbuf_offset <= offset); + ep = (struct direct *)(dirbuf + (offset - dirbuf_offset)); + reclen = ufs_rw16(ep->d_reclen, needswap); + + if (ep->d_ino == 0) + goto next; /* Entry is unused. */ + + if (ufs_rw32(ep->d_ino, needswap) == WINO) + goto next; /* Entry is whiteout. */ + + if (fcnp->cn_namelen != ufs_direct_namlen(ep, dvp)) + goto next; /* Wrong name length. */ + + if (memcmp(ep->d_name, fcnp->cn_nameptr, fcnp->cn_namelen)) + goto next; /* Wrong name. */ + + /* Got it! */ + break; + +next: + if (! ((reclen < search_end) && + (offset < (search_end - reclen)))) { + brelse(bp, 0); + return EIO; /* XXX Panic? What? */ + } + + KASSERT(reclen < search_end); + KASSERT(offset < (search_end - reclen)); + prev_reclen = reclen; + offset += reclen; + } + + /* + * Found the entry. Record where. + */ + fulr->ulr_offset = offset; + fulr->ulr_reclen = reclen; + + /* + * Record the preceding record length, but not if we're at the + * start of a directory block. + */ + fulr->ulr_count = ((offset & directory_block_mask)? prev_reclen : 0); + + brelse(bp, 0); + return 0; +} + +/* + * ufs_direct_namlen: Return the namlen of the directory entry ep from + * the directory vp. + */ +static int /* XXX int? uint8_t? */ +ufs_direct_namlen(const struct direct *ep, const struct vnode *vp) +{ + bool swap; + + KASSERT(ep != NULL); + KASSERT(vp != NULL); + KASSERT(VTOI(vp) != NULL); + KASSERT(VTOI(vp)->i_ump != NULL); + +#if (BYTE_ORDER == LITTLE_ENDIAN) + swap = (UFS_MPNEEDSWAP(VTOI(vp)->i_ump) == 0); +#else + swap = (UFS_MPNEEDSWAP(VTOI(vp)->i_ump) != 0); +#endif + + return ((FSFMT(vp) && swap)? ep->d_type : ep->d_namlen); +} + +/* + * ufs_gro_remove: Rename an object over another link to itself, + * effectively removing just the original link. + */ +int +ufs_gro_remove(struct mount *mp, kauth_cred_t cred, + struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp) +{ + struct ufs_lookup_results *ulr = de; + int error; + + KASSERT(mp != NULL); + KASSERT(dvp != NULL); + KASSERT(cnp != NULL); + KASSERT(ulr != 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); + KASSERT(cnp->cn_nameiop == DELETE); + + fstrans_start(mp, FSTRANS_SHARED); + error = UFS_WAPBL_BEGIN(mp); + if (error) + goto out0; + + /* XXX ufs_dirremove decrements vp's link count for us. */ + error = ufs_dirremove(dvp, ulr, VTOI(vp), cnp->cn_flags, 0); + if (error) + goto out1; + + VN_KNOTE(dvp, NOTE_WRITE); + VN_KNOTE(vp, (VTOI(vp)->i_nlink? NOTE_LINK : NOTE_DELETE)); + +out1: UFS_WAPBL_END(mp); +out0: fstrans_done(mp); + return error; +} + +/* + * ufs_gro_lookup: Look up and save the lookup results. + */ +int +ufs_gro_lookup(struct mount *mp, struct vnode *dvp, + struct componentname *cnp, void *de_ret, struct vnode **vp_ret) +{ + struct ufs_lookup_results *ulr_ret = de_ret; + struct vnode *vp = NULL; + int error; + + (void)mp; + KASSERT(mp != NULL); + KASSERT(dvp != NULL); + KASSERT(cnp != NULL); + KASSERT(ulr_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 /* dummy */); + if ((error == 0) && (vp == NULL)) { + error = ENOENT; + goto out; + } else if (error) { + return error; + } + + /* + * Thanks to VFS insanity, relookup locks vp, which screws us + * in various ways. + */ + KASSERT(vp != NULL); + VOP_UNLOCK(vp); + +out: *ulr_ret = VTOI(dvp)->i_crap; + *vp_ret = vp; + return error; +} + +/* + * ufs_rmdired_p: Check whether the directory vp has been rmdired. + * + * vp must be locked and referenced. + */ +static bool +ufs_rmdired_p(struct vnode *vp) +{ + + KASSERT(vp != NULL); + KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); + KASSERT(vp->v_type == VDIR); + + /* XXX Is this correct? */ + return (VTOI(vp)->i_size == 0); +} + +/* + * ufs_read_dotdot: Store in *ino_ret the inode number of the parent + * of the directory vp. + */ +static int +ufs_read_dotdot(struct vnode *vp, kauth_cred_t cred, ino_t *ino_ret) +{ + struct dirtemplate dirbuf; + int error; + + KASSERT(vp != NULL); + KASSERT(ino_ret != NULL); + KASSERT(vp->v_type == VDIR); + + error = vn_rdwr(UIO_READ, vp, &dirbuf, sizeof dirbuf, (off_t)0, + UIO_SYSSPACE, IO_NODELOCKED, cred, NULL, NULL); + if (error) + return error; + + if (ufs_dirbuf_dotdot_namlen(&dirbuf, vp) != 2 || + dirbuf.dotdot_name[0] != '.' || + dirbuf.dotdot_name[1] != '.') + /* XXX Panic? Print warning? */ + return ENOTDIR; + + *ino_ret = ufs_rw32(dirbuf.dotdot_ino, + UFS_MPNEEDSWAP(VTOI(vp)->i_ump)); + return 0; +} + +/* + * ufs_dirbuf_dotdot_namlen: Return the namlen of the directory buffer + * dirbuf that came from the directory vp. Swap byte order if + * necessary. + */ +static int /* XXX int? uint8_t? */ +ufs_dirbuf_dotdot_namlen(const struct dirtemplate *dirbuf, + const struct vnode *vp) +{ + bool swap; + + KASSERT(dirbuf != NULL); + KASSERT(vp != NULL); + KASSERT(VTOI(vp) != NULL); + KASSERT(VTOI(vp)->i_ump != NULL); + +#if (BYTE_ORDER == LITTLE_ENDIAN) + swap = (UFS_MPNEEDSWAP(VTOI(vp)->i_ump) == 0); +#else + swap = (UFS_MPNEEDSWAP(VTOI(vp)->i_ump) != 0); +#endif + + return ((FSFMT(vp) && swap)? + dirbuf->dotdot_type : dirbuf->dotdot_namlen); +} + +/* + * ufs_gro_genealogy: Analyze the genealogy of the source and target + * directories. + */ +int +ufs_gro_genealogy(struct mount *mp, kauth_cred_t cred, + struct vnode *fdvp, struct vnode *tdvp, + struct vnode **intermediate_node_ret) +{ + struct vnode *vp, *dvp; + ino_t dotdot_ino; + 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); + + /* + * We need to provisionally lock tdvp to keep rmdir from + * deleting it -- or any ancestor -- at an inopportune moment. + */ + error = ufs_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(!ufs_rmdired_p(vp)); + + /* Did we hit the root without finding fdvp? */ + if (VTOI(vp)->i_number == ROOTINO) { + vput(vp); + *intermediate_node_ret = NULL; + return 0; + } + + error = ufs_read_dotdot(vp, cred, &dotdot_ino); + if (error) { + vput(vp); + return error; + } + + /* Did we find that fdvp is an ancestor of tdvp? */ + if (VTOI(fdvp)->i_number == dotdot_ino) { + /* Unlock vp, but keep it referenced. */ + VOP_UNLOCK(vp); + *intermediate_node_ret = vp; + return 0; + } + + /* Neither -- keep ascending the family tree. */ + + /* + * Unlock vp so that we can lock the parent, but keep + * vp referenced until after we have found the parent, + * so that dotdot_ino will not be recycled. + * + * XXX This guarantees that vp's inode number will not + * be recycled, but why can't dotdot_ino be recycled? + */ + VOP_UNLOCK(vp); + error = VFS_VGET(mp, dotdot_ino, &dvp); + vrele(vp); + if (error) + return error; + + KASSERT(dvp != NULL); + KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); + vp = dvp; + + if (vp->v_type != VDIR) { + /* + * XXX Panic? Print a warning? Can this + * happen if we lose the race I suspect to + * exist above, and the `..' inode number has + * been recycled? + */ + vput(vp); + return ENOTDIR; + } + + if (ufs_rmdired_p(vp)) { + vput(vp); + return ENOENT; + } + } +} + +/* + * ufs_gro_lock_directory: Lock the directory vp, but fail if it has + * been rmdir'd. + */ +int +ufs_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 (ufs_rmdired_p(vp)) { + VOP_UNLOCK(vp); + return ENOENT; + } + + return 0; +} + +static const struct genfs_rename_ops ufs_genfs_rename_ops = { + .gro_directory_empty_p = ufs_gro_directory_empty_p, + .gro_rename_check_possible = ufs_gro_rename_check_possible, + .gro_rename_check_permitted = ufs_gro_rename_check_permitted, + .gro_remove_check_possible = ufs_gro_remove_check_possible, + .gro_remove_check_permitted = ufs_gro_remove_check_permitted, + .gro_rename = ufs_gro_rename, + .gro_remove = ufs_gro_remove, + .gro_lookup = ufs_gro_lookup, + .gro_genealogy = ufs_gro_genealogy, + .gro_lock_directory = ufs_gro_lock_directory, +}; Index: sys/ufs/ufs/ufs_vnops.c =================================================================== RCS file: /cvsroot/src/sys/ufs/ufs/ufs_vnops.c,v retrieving revision 1.208 diff -p -u -r1.208 ufs_vnops.c --- sys/ufs/ufs/ufs_vnops.c 13 Mar 2012 18:41:15 -0000 1.208 +++ sys/ufs/ufs/ufs_vnops.c 7 Apr 2012 21:19:57 -0000 @@ -967,1024 +967,6 @@ ufs_whiteout(void *v) return (error); } - -/* - * Rename vnode operation - * rename("foo", "bar"); - * is essentially - * unlink("bar"); - * link("foo", "bar"); - * unlink("foo"); - * but ``atomically''. Can't do full commit without saving state in the - * inode on disk which isn't feasible at this time. Best we can do is - * always guarantee the target exists. - * - * Basic algorithm is: - * - * 1) Bump link count on source while we're linking it to the - * target. This also ensure the inode won't be deleted out - * from underneath us while we work (it may be truncated by - * a concurrent `trunc' or `open' for creation). - * 2) Link source to destination. If destination already exists, - * delete it first. - * 3) Unlink source reference to inode if still around. If a - * directory was moved and the parent of the destination - * is different from the source, patch the ".." entry in the - * directory. - */ - -/* - * Notes on rename locking: - * - * We lock parent vnodes before child vnodes. This means in particular - * that if A is above B in the directory tree then A must be locked - * before B. (This is true regardless of how many steps appear in - * between, because an arbitrary number of other processes could lock - * parent/child in between and establish a lock cycle and deadlock.) - * - * Therefore, if tdvp is above fdvp we must lock tdvp first; if fdvp - * is above tdvp we must lock fdvp first; and if they're - * incommensurate it doesn't matter. (But, we rely on the fact that - * there's a whole-volume rename lock to prevent deadlock among groups - * of renames upon overlapping sets of incommensurate vnodes.) - * - * In addition to establishing lock ordering the parent check also - * serves to rule out cases where someone tries to move a directory - * underneath itself, e.g. rename("a/b", "a/b/c"). If allowed to - * proceed such renames would detach portions of the directory tree - * and make fsck very unhappy. - * - * Note that it is an error for *fvp* to be above tdvp; however, - * *fdvp* can be above tdvp, as in rename("a/b", "a/c/d"). - * - * The parent check searches up the tree from tdvp until it either - * finds fdvp or the root of the volume. It also returns the vnode it - * saw immediately before fdvp, if any. Later on (after looking up - * fvp) we will check to see if this *is* fvp and if so fail. - * - * If the parent check finds fdvp, it means fdvp is above tdvp, so we - * lock fdvp first and then tdvp. Otherwise, either tdvp is above fdvp - * or they're incommensurate and we lock tdvp first. - * - * In either case each of the child vnodes has to be looked up and - * locked immediately after its parent. The cases - * - * fdvp/fvp/[.../]tdvp/tvp - * tdvp/tvp/[.../]fdvp/fvp - * - * can cause deadlock otherwise. Note that both of these are error - * cases; the first fails the parent check and the second fails - * because tvp isn't empty. The parent check case is handled before - * we start locking; however, the nonempty case requires locking tvp - * to find out safely that it's nonempty. - * - * Therefore the procedure is either - * - * lock fdvp - * lookup fvp - * lock fvp - * lock tdvp - * lookup tvp - * lock tvp - * - * or - * - * lock tdvp - * lookup tvp - * lock tvp - * lock fdvp - * lookup fvp - * lock fvp - * - * This could in principle be simplified by always looking up fvp - * last; because of the parent check we know by the time we start - * locking that fvp cannot be directly above tdvp, so (given the - * whole-volume rename lock and other assumptions) it's safe to lock - * tdvp before fvp. This would allow the following scheme: - * - * lock fdvp - * lock tdvp - * or - * lock tdvp - * lock fdvp - * - * then - * lookup tvp - * lock tvp - * lookup fvp - * check if fvp is above of tdvp, fail if so - * lock fvp - * - * which is much, much simpler. - * - * However, current levels of vfs namei/lookup sanity do not permit - * this. It is impossible currently to look up fvp without locking it. - * (It gets locked regardless of whether LOCKLEAF is set; without - * LOCKLEAF it just gets unlocked again, which doesn't help.) - * - * Therefore, because we must look up fvp to know if it's above tdvp, - * which locks fvp, we must, at least in the case where fdvp is above - * tdvp, do that before locking tdvp. The longer scheme does that; the - * simpler scheme is not safe. - * - * Note that for now we aren't doing lookup() but relookup(); however, - * the differences are minor. - * - * On top of all the above, just to make everything more - * exciting, any two of the vnodes might end up being the same. - * - * FROMPARENT == FROMCHILD mv a/. foo is an error. - * FROMPARENT == TOPARENT mv a/b a/c is ok. - * FROMPARENT == TOCHILD mv a/b/c a/b will give ENOTEMPTY. - * FROMCHILD == TOPARENT mv a/b a/b/c fails the parent check. - * FROMCHILD == TOCHILD mv a/b a/b is ok. - * TOPARENT == TOCHILD mv foo a/. is an error. - * - * This introduces more cases in the locking, because each distinct - * vnode must be locked exactly once. - * - * When FROMPARENT == TOPARENT and FROMCHILD != TOCHILD we assume it - * doesn't matter what order the children are locked in, because the - * per-volume rename lock excludes other renames and no other - * operation locks two files in the same directory at once. (Note: if - * it turns out that link() does, link() is wrong.) - * - * Until such time as we can do lookups without the namei and lookup - * machinery "helpfully" locking the result vnode for us, we can't - * avoid tripping on cases where FROMCHILD == TOCHILD. Currently for - * non-directories we unlock the first one we lock while looking up - * the second, then relock it if necessary. This is more or less - * harmless since not much of interest can happen to the objects in - * that window while we have the containing directory locked; but it's - * not desirable and should be cleaned up when that becomes possible. - * The right way to do it is to check after looking the second one up - * and only lock it if it's different. (Note: for directories we don't - * do this dance because the same directory can't appear more than - * once.) - */ - -/* XXX following lifted from ufs_lookup.c */ -#define FSFMT(vp) (((vp)->v_mount->mnt_iflag & IMNT_DTYPE) == 0) - -/* - * Check if either entry referred to by FROM_ULR is within the range - * of entries named by TO_ULR. - */ -static int -ulr_overlap(const struct ufs_lookup_results *from_ulr, - const struct ufs_lookup_results *to_ulr) -{ - doff_t from_start, from_prevstart; - doff_t to_start, to_end; - - /* - * FROM is a DELETE result; offset points to the entry to - * remove and subtracting count gives the previous entry. - */ - from_start = from_ulr->ulr_offset - from_ulr->ulr_count; - from_prevstart = from_ulr->ulr_offset; - - /* - * TO is a RENAME (thus non-DELETE) result; offset points - * to the beginning of a region to write in, and adding - * count gives the end of the region. - */ - to_start = to_ulr->ulr_offset; - to_end = to_ulr->ulr_offset + to_ulr->ulr_count; - - if (from_prevstart >= to_start && from_prevstart < to_end) { - return 1; - } - if (from_start >= to_start && from_start < to_end) { - return 1; - } - return 0; -} - -/* - * Wrapper for relookup that also updates the supplemental results. - */ -static int -do_relookup(struct vnode *dvp, struct ufs_lookup_results *ulr, - struct vnode **vp, struct componentname *cnp) -{ - int error; - - error = relookup(dvp, vp, cnp, 0); - if (error) { - return error; - } - /* update the supplemental reasults */ - *ulr = VTOI(dvp)->i_crap; - UFS_CHECK_CRAPCOUNTER(VTOI(dvp)); - return 0; -} - -/* - * Lock and relookup a sequence of two directories and two children. - * - */ -static int -lock_vnode_sequence(struct vnode *d1, struct ufs_lookup_results *ulr1, - struct vnode **v1_ret, struct componentname *cn1, - int v1_missing_ok, - int overlap_error, - struct vnode *d2, struct ufs_lookup_results *ulr2, - struct vnode **v2_ret, struct componentname *cn2, - int v2_missing_ok) -{ - struct vnode *v1, *v2; - int error; - - KASSERT(d1 != d2); - - vn_lock(d1, LK_EXCLUSIVE | LK_RETRY); - if (VTOI(d1)->i_size == 0) { - /* d1 has been rmdir'd */ - VOP_UNLOCK(d1); - return ENOENT; - } - error = do_relookup(d1, ulr1, &v1, cn1); - if (v1_missing_ok) { - if (error == ENOENT) { - /* - * Note: currently if the name doesn't exist, - * relookup succeeds (it intercepts the - * EJUSTRETURN from VOP_LOOKUP) and sets tvp - * to NULL. Therefore, we will never get - * ENOENT and this branch is not needed. - * However, in a saner future the EJUSTRETURN - * garbage will go away, so let's DTRT. - */ - v1 = NULL; - error = 0; - } - } else { - if (error == 0 && v1 == NULL) { - /* This is what relookup sets if v1 disappeared. */ - error = ENOENT; - } - } - if (error) { - VOP_UNLOCK(d1); - return error; - } - if (v1 && v1 == d2) { - VOP_UNLOCK(d1); - VOP_UNLOCK(v1); - vrele(v1); - return overlap_error; - } - - /* - * The right way to do this is to do lookups without locking - * the results, and lock the results afterwards; then at the - * end we can avoid trying to lock v2 if v2 == v1. - * - * However, for the reasons described in the fdvp == tdvp case - * in rename below, we can't do that safely. So, in the case - * where v1 is not a directory, unlock it and lock it again - * afterwards. This is safe in locking order because a - * non-directory can't be above anything else in the tree. If - * v1 *is* a directory, that's not true, but then because d1 - * != d2, v1 != v2. - */ - if (v1 && v1->v_type != VDIR) { - VOP_UNLOCK(v1); - } - vn_lock(d2, LK_EXCLUSIVE | LK_RETRY); - if (VTOI(d2)->i_size == 0) { - /* d2 has been rmdir'd */ - VOP_UNLOCK(d2); - if (v1 && v1->v_type == VDIR) { - VOP_UNLOCK(v1); - } - VOP_UNLOCK(d1); - if (v1) { - vrele(v1); - } - return ENOENT; - } - error = do_relookup(d2, ulr2, &v2, cn2); - if (v2_missing_ok) { - if (error == ENOENT) { - /* as above */ - v2 = NULL; - error = 0; - } - } else { - if (error == 0 && v2 == NULL) { - /* This is what relookup sets if v2 disappeared. */ - error = ENOENT; - } - } - if (error) { - VOP_UNLOCK(d2); - if (v1 && v1->v_type == VDIR) { - VOP_UNLOCK(v1); - } - VOP_UNLOCK(d1); - if (v1) { - vrele(v1); - } - return error; - } - if (v1 && v1->v_type != VDIR && v1 != v2) { - vn_lock(v1, LK_EXCLUSIVE | LK_RETRY); - } - *v1_ret = v1; - *v2_ret = v2; - return 0; -} - -/* - * Rename vnode operation - * rename("foo", "bar"); - * is essentially - * unlink("bar"); - * link("foo", "bar"); - * unlink("foo"); - * but ``atomically''. Can't do full commit without saving state in the - * inode on disk which isn't feasible at this time. Best we can do is - * always guarantee the target exists. - * - * Basic algorithm is: - * - * 1) Bump link count on source while we're linking it to the - * target. This also ensure the inode won't be deleted out - * from underneath us while we work (it may be truncated by - * a concurrent `trunc' or `open' for creation). - * 2) Link source to destination. If destination already exists, - * delete it first. - * 3) Unlink source reference to inode if still around. If a - * directory was moved and the parent of the destination - * is different from the source, patch the ".." entry in the - * directory. - */ -int -ufs_rename(void *v) -{ - struct vop_rename_args /* { - struct vnode *a_fdvp; - struct vnode *a_fvp; - struct componentname *a_fcnp; - struct vnode *a_tdvp; - struct vnode *a_tvp; - struct componentname *a_tcnp; - } */ *ap = v; - struct vnode *tvp, *tdvp, *fvp, *fdvp; - struct componentname *tcnp, *fcnp; - struct inode *ip, *txp, *fxp, *tdp, *fdp; - struct mount *mp; - struct direct *newdir; - int doingdirectory, error; - ino_t oldparent, newparent; - - struct ufs_lookup_results from_ulr, to_ulr; - - tvp = ap->a_tvp; - tdvp = ap->a_tdvp; - fvp = ap->a_fvp; - fdvp = ap->a_fdvp; - tcnp = ap->a_tcnp; - fcnp = ap->a_fcnp; - doingdirectory = error = 0; - oldparent = newparent = 0; - - /* save the supplemental lookup results as they currently exist */ - from_ulr = VTOI(fdvp)->i_crap; - to_ulr = VTOI(tdvp)->i_crap; - UFS_CHECK_CRAPCOUNTER(VTOI(fdvp)); - UFS_CHECK_CRAPCOUNTER(VTOI(tdvp)); - - /* - * Owing to VFS oddities we are currently called with tdvp/tvp - * locked and not fdvp/fvp. In a sane world we'd be passed - * tdvp and fdvp only, unlocked, and two name strings. Pretend - * we have a sane world and unlock tdvp and tvp. - */ - VOP_UNLOCK(tdvp); - if (tvp && tvp != tdvp) { - VOP_UNLOCK(tvp); - } - - /* Also pretend we have a sane world and vrele fvp/tvp. */ - vrele(fvp); - fvp = NULL; - if (tvp) { - vrele(tvp); - tvp = NULL; - } - - /* - * Check for cross-device rename. - */ - if (fdvp->v_mount != tdvp->v_mount) { - error = EXDEV; - goto abort; - } - - /* - * Reject "." and ".." - */ - if ((fcnp->cn_flags & ISDOTDOT) || (tcnp->cn_flags & ISDOTDOT) || - (fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') || - (tcnp->cn_namelen == 1 && tcnp->cn_nameptr[0] == '.')) { - error = EINVAL; - goto abort; - } - - /* - * Get locks. - */ - - /* paranoia */ - fcnp->cn_flags |= LOCKPARENT|LOCKLEAF; - tcnp->cn_flags |= LOCKPARENT|LOCKLEAF; - - if (fdvp == tdvp) { - /* One directory. Lock it and relookup both children. */ - vn_lock(fdvp, LK_EXCLUSIVE | LK_RETRY); - - if (VTOI(fdvp)->i_size == 0) { - /* directory has been rmdir'd */ - VOP_UNLOCK(fdvp); - error = ENOENT; - goto abort; - } - - error = do_relookup(fdvp, &from_ulr, &fvp, fcnp); - if (error == 0 && fvp == NULL) { - /* relookup may produce this if fvp disappears */ - error = ENOENT; - } - if (error) { - VOP_UNLOCK(fdvp); - goto abort; - } - - /* - * The right way to do this is to look up both children - * without locking either, and then lock both unless they - * turn out to be the same. However, due to deep-seated - * VFS-level issues all lookups lock the child regardless - * of whether LOCKLEAF is set (if LOCKLEAF is not set, - * the child is locked during lookup and then unlocked) - * so it is not safe to look up tvp while fvp is locked. - * - * Unlocking fvp here temporarily is more or less safe, - * because with the directory locked there's not much - * that can happen to it. However, ideally it wouldn't - * be necessary. XXX. - */ - VOP_UNLOCK(fvp); - /* remember fdvp == tdvp so tdvp is locked */ - error = do_relookup(tdvp, &to_ulr, &tvp, tcnp); - if (error && error != ENOENT) { - VOP_UNLOCK(fdvp); - goto abort; - } - if (error == ENOENT) { - /* - * Note: currently if the name doesn't exist, - * relookup succeeds (it intercepts the - * EJUSTRETURN from VOP_LOOKUP) and sets tvp - * to NULL. Therefore, we will never get - * ENOENT and this branch is not needed. - * However, in a saner future the EJUSTRETURN - * garbage will go away, so let's DTRT. - */ - tvp = NULL; - } - - /* tvp is locked; lock fvp if necessary */ - if (!tvp || tvp != fvp) { - vn_lock(fvp, LK_EXCLUSIVE | LK_RETRY); - } - } else { - int found_fdvp; - struct vnode *illegal_fvp; - - /* - * The source must not be above the destination. (If - * it were, the rename would detach a section of the - * tree.) - * - * Look up the tree from tdvp to see if we find fdvp, - * and if so, return the immediate child of fdvp we're - * under; that must not turn out to be the same as - * fvp. - * - * The per-volume rename lock guarantees that the - * result of this check remains true until we finish - * looking up and locking. - */ - error = ufs_parentcheck(fdvp, tdvp, fcnp->cn_cred, - &found_fdvp, &illegal_fvp); - if (error) { - goto abort; - } - - /* Must lock in tree order. */ - - if (found_fdvp) { - /* fdvp -> fvp -> tdvp -> tvp */ - error = lock_vnode_sequence(fdvp, &from_ulr, - &fvp, fcnp, 0, - EINVAL, - tdvp, &to_ulr, - &tvp, tcnp, 1); - } else { - /* tdvp -> tvp -> fdvp -> fvp */ - error = lock_vnode_sequence(tdvp, &to_ulr, - &tvp, tcnp, 1, - ENOTEMPTY, - fdvp, &from_ulr, - &fvp, fcnp, 0); - } - if (error) { - if (illegal_fvp) { - vrele(illegal_fvp); - } - goto abort; - } - KASSERT(fvp != NULL); - - if (illegal_fvp && fvp == illegal_fvp) { - vrele(illegal_fvp); - error = EINVAL; - goto abort_withlocks; - } - - if (illegal_fvp) { - vrele(illegal_fvp); - } - } - - KASSERT(fdvp && VOP_ISLOCKED(fdvp)); - KASSERT(fvp && VOP_ISLOCKED(fvp)); - KASSERT(tdvp && VOP_ISLOCKED(tdvp)); - KASSERT(tvp == NULL || VOP_ISLOCKED(tvp)); - - /* --- everything is now locked --- */ - - if (tvp && ((VTOI(tvp)->i_flags & (IMMUTABLE | APPEND)) || - (VTOI(tdvp)->i_flags & APPEND))) { - error = EPERM; - goto abort_withlocks; - } - - /* - * Check if just deleting a link name. - */ - if (fvp == tvp) { - if (fvp->v_type == VDIR) { - error = EINVAL; - goto abort_withlocks; - } - - /* Release destination completely. Leave fdvp locked. */ - VOP_ABORTOP(tdvp, tcnp); - if (fdvp != tdvp) { - VOP_UNLOCK(tdvp); - } - VOP_UNLOCK(tvp); - vrele(tdvp); - vrele(tvp); - - /* Delete source. */ - /* XXX: do we really need to relookup again? */ - - /* - * fdvp is still locked, but we just unlocked fvp - * (because fvp == tvp) so just decref fvp - */ - vrele(fvp); - fcnp->cn_flags &= ~(MODMASK); - fcnp->cn_flags |= LOCKPARENT | LOCKLEAF; - fcnp->cn_nameiop = DELETE; - if ((error = relookup(fdvp, &fvp, fcnp, 0))) { - vput(fdvp); - return (error); - } - return (VOP_REMOVE(fdvp, fvp, fcnp)); - } - fdp = VTOI(fdvp); - ip = VTOI(fvp); - if ((nlink_t) ip->i_nlink >= LINK_MAX) { - error = EMLINK; - goto abort_withlocks; - } - if ((ip->i_flags & (IMMUTABLE | APPEND)) || - (fdp->i_flags & APPEND)) { - error = EPERM; - goto abort_withlocks; - } - if ((ip->i_mode & IFMT) == IFDIR) { - /* - * Avoid ".", "..", and aliases of "." for obvious reasons. - */ - if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') || - fdp == ip || - (fcnp->cn_flags & ISDOTDOT) || - (tcnp->cn_flags & ISDOTDOT) || - (ip->i_flag & IN_RENAME)) { - error = EINVAL; - goto abort_withlocks; - } - ip->i_flag |= IN_RENAME; - doingdirectory = 1; - } - oldparent = fdp->i_number; - VN_KNOTE(fdvp, NOTE_WRITE); /* XXXLUKEM/XXX: right place? */ - - /* - * Both the directory - * and target vnodes are locked. - */ - tdp = VTOI(tdvp); - txp = NULL; - if (tvp) - txp = VTOI(tvp); - - mp = fdvp->v_mount; - fstrans_start(mp, FSTRANS_SHARED); - - if (oldparent != tdp->i_number) - newparent = tdp->i_number; - - /* - * If ".." must be changed (ie the directory gets a new - * parent) the user must have write permission in the source - * so as to be able to change "..". - */ - if (doingdirectory && newparent) { - error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred); - if (error) - goto out; - } - - KASSERT(fdvp != tvp); - - if (newparent) { - /* Check for the rename("foo/foo", "foo") case. */ - if (fdvp == tvp) { - error = doingdirectory ? ENOTEMPTY : EISDIR; - goto out; - } - } - - fxp = VTOI(fvp); - fdp = VTOI(fdvp); - - error = UFS_WAPBL_BEGIN(fdvp->v_mount); - if (error) - goto out2; - - /* - * 1) Bump link count while we're moving stuff - * around. If we crash somewhere before - * completing our work, the link count - * may be wrong, but correctable. - */ - ip->i_nlink++; - DIP_ASSIGN(ip, nlink, ip->i_nlink); - ip->i_flag |= IN_CHANGE; - if ((error = UFS_UPDATE(fvp, NULL, NULL, UPDATE_DIROP)) != 0) { - goto bad; - } - - /* - * 2) If target doesn't exist, link the target - * to the source and unlink the source. - * Otherwise, rewrite the target directory - * entry to reference the source inode and - * expunge the original entry's existence. - */ - if (txp == NULL) { - if (tdp->i_dev != ip->i_dev) - panic("rename: EXDEV"); - /* - * Account for ".." in new directory. - * When source and destination have the same - * parent we don't fool with the link count. - */ - if (doingdirectory && newparent) { - if ((nlink_t)tdp->i_nlink >= LINK_MAX) { - error = EMLINK; - goto bad; - } - tdp->i_nlink++; - DIP_ASSIGN(tdp, nlink, tdp->i_nlink); - tdp->i_flag |= IN_CHANGE; - if ((error = UFS_UPDATE(tdvp, NULL, NULL, - UPDATE_DIROP)) != 0) { - tdp->i_nlink--; - DIP_ASSIGN(tdp, nlink, tdp->i_nlink); - tdp->i_flag |= IN_CHANGE; - goto bad; - } - } - newdir = pool_cache_get(ufs_direct_cache, PR_WAITOK); - ufs_makedirentry(ip, tcnp, newdir); - error = ufs_direnter(tdvp, &to_ulr, - NULL, newdir, tcnp, NULL); - pool_cache_put(ufs_direct_cache, newdir); - if (error != 0) { - if (doingdirectory && newparent) { - tdp->i_nlink--; - DIP_ASSIGN(tdp, nlink, tdp->i_nlink); - tdp->i_flag |= IN_CHANGE; - (void)UFS_UPDATE(tdvp, NULL, NULL, - UPDATE_WAIT | UPDATE_DIROP); - } - goto bad; - } - VN_KNOTE(tdvp, NOTE_WRITE); - } else { - if (txp->i_dev != tdp->i_dev || txp->i_dev != ip->i_dev) - panic("rename: EXDEV"); - /* - * Short circuit rename(foo, foo). - */ - if (txp->i_number == ip->i_number) - panic("rename: same file"); - /* - * If the parent directory is "sticky", then the user must - * own the parent directory, or the destination of the rename, - * otherwise the destination may not be changed (except by - * root). This implements append-only directories. - */ - if (tdp->i_mode & S_ISTXT) { - /* XXX: figure out a way to combine it all into one... */ - error = kauth_authorize_vnode(tcnp->cn_cred, - KAUTH_VNODE_DELETE, tvp, tdvp, - genfs_can_sticky(tcnp->cn_cred, tdp->i_uid, txp->i_uid)); - if (error) - goto bad; - } - /* - * Target must be empty if a directory and have no links - * to it. Also, ensure source and target are compatible - * (both directories, or both not directories). - */ - if ((txp->i_mode & IFMT) == IFDIR) { - if (txp->i_nlink > 2 || - !ufs_dirempty(txp, tdp->i_number, tcnp->cn_cred)) { - error = ENOTEMPTY; - goto bad; - } - if (!doingdirectory) { - error = ENOTDIR; - goto bad; - } - cache_purge(tdvp); - } else if (doingdirectory) { - error = EISDIR; - goto bad; - } - if ((error = ufs_dirrewrite(tdp, to_ulr.ulr_offset, - txp, ip->i_number, - IFTODT(ip->i_mode), doingdirectory && newparent ? - newparent : doingdirectory, IN_CHANGE | IN_UPDATE)) != 0) - goto bad; - if (doingdirectory) { - /* - * Truncate inode. The only stuff left in the directory - * is "." and "..". The "." reference is inconsequential - * since we are quashing it. We have removed the "." - * reference and the reference in the parent directory, - * but there may be other hard links. - */ - if (!newparent) { - tdp->i_nlink--; - DIP_ASSIGN(tdp, nlink, tdp->i_nlink); - tdp->i_flag |= IN_CHANGE; - UFS_WAPBL_UPDATE(tdvp, NULL, NULL, 0); - } - txp->i_nlink--; - DIP_ASSIGN(txp, nlink, txp->i_nlink); - txp->i_flag |= IN_CHANGE; - if ((error = UFS_TRUNCATE(tvp, (off_t)0, IO_SYNC, - tcnp->cn_cred))) - goto bad; - } - VN_KNOTE(tdvp, NOTE_WRITE); - VN_KNOTE(tvp, NOTE_DELETE); - } - - /* - * Handle case where the directory entry we need to remove, - * which is/was at from_ulr.ulr_offset, or the one before it, - * which is/was at from_ulr.ulr_offset - from_ulr.ulr_count, - * may have been moved when the directory insertion above - * performed compaction. - */ - if (tdp->i_number == fdp->i_number && - ulr_overlap(&from_ulr, &to_ulr)) { - - struct buf *bp; - struct direct *ep; - struct ufsmount *ump = fdp->i_ump; - doff_t curpos; - doff_t endsearch; /* offset to end directory search */ - uint32_t prev_reclen; - int dirblksiz = ump->um_dirblksiz; - const int needswap = UFS_MPNEEDSWAP(ump); - u_long bmask; - int namlen, entryoffsetinblock; - char *dirbuf; - - bmask = fdvp->v_mount->mnt_stat.f_iosize - 1; - - /* - * The fcnp entry will be somewhere between the start of - * compaction (to_ulr.ulr_offset) and the original location - * (from_ulr.ulr_offset). - */ - curpos = to_ulr.ulr_offset; - endsearch = from_ulr.ulr_offset + from_ulr.ulr_reclen; - entryoffsetinblock = 0; - - /* - * Get the directory block containing the start of - * compaction. - */ - error = ufs_blkatoff(fdvp, (off_t)to_ulr.ulr_offset, &dirbuf, - &bp, false); - if (error) - goto bad; - - /* - * Keep existing ulr_count (length of previous record) - * for the case where compaction did not include the - * previous entry but started at the from-entry. - */ - prev_reclen = from_ulr.ulr_count; - - while (curpos < endsearch) { - uint32_t reclen; - - /* - * If necessary, get the next directory block. - * - * dholland 7/13/11 to the best of my understanding - * this should never happen; compaction occurs only - * within single blocks. I think. - */ - if ((curpos & bmask) == 0) { - if (bp != NULL) - brelse(bp, 0); - error = ufs_blkatoff(fdvp, (off_t)curpos, - &dirbuf, &bp, false); - if (error) - goto bad; - entryoffsetinblock = 0; - } - - KASSERT(bp != NULL); - ep = (struct direct *)(dirbuf + entryoffsetinblock); - reclen = ufs_rw16(ep->d_reclen, needswap); - -#if (BYTE_ORDER == LITTLE_ENDIAN) - if (FSFMT(fdvp) && needswap == 0) - namlen = ep->d_type; - else - namlen = ep->d_namlen; -#else - if (FSFMT(fdvp) && needswap != 0) - namlen = ep->d_type; - else - namlen = ep->d_namlen; -#endif - if ((ep->d_ino != 0) && - (ufs_rw32(ep->d_ino, needswap) != WINO) && - (namlen == fcnp->cn_namelen) && - memcmp(ep->d_name, fcnp->cn_nameptr, namlen) == 0) { - from_ulr.ulr_reclen = reclen; - break; - } - curpos += reclen; - entryoffsetinblock += reclen; - prev_reclen = reclen; - } - - from_ulr.ulr_offset = curpos; - from_ulr.ulr_count = prev_reclen; - - KASSERT(curpos <= endsearch); - - /* - * If ulr_offset points to start of a directory block, - * clear ulr_count so ufs_dirremove() doesn't try to - * merge free space over a directory block boundary. - */ - if ((from_ulr.ulr_offset & (dirblksiz - 1)) == 0) - from_ulr.ulr_count = 0; - - brelse(bp, 0); - } - - /* - * 3) Unlink the source. - */ - -#if 0 - /* - * Ensure that the directory entry still exists and has not - * changed while the new name has been entered. If the source is - * a file then the entry may have been unlinked or renamed. In - * either case there is no further work to be done. If the source - * is a directory then it cannot have been rmdir'ed; The IRENAME - * flag ensures that it cannot be moved by another rename or removed - * by a rmdir. - */ -#endif - KASSERT(fxp == ip); - - /* - * If the source is a directory with a new parent, the link - * count of the old parent directory must be decremented and - * ".." set to point to the new parent. - */ - if (doingdirectory && newparent) { - KASSERT(fdp != NULL); - ufs_dirrewrite(fxp, mastertemplate.dot_reclen, - fdp, newparent, DT_DIR, 0, IN_CHANGE); - cache_purge(fdvp); - } - error = ufs_dirremove(fdvp, &from_ulr, - fxp, fcnp->cn_flags, 0); - fxp->i_flag &= ~IN_RENAME; - - VN_KNOTE(fvp, NOTE_RENAME); - goto done; - - out: - goto out2; - - /* exit routines from steps 1 & 2 */ - bad: - if (doingdirectory) - ip->i_flag &= ~IN_RENAME; - ip->i_nlink--; - DIP_ASSIGN(ip, nlink, ip->i_nlink); - ip->i_flag |= IN_CHANGE; - ip->i_flag &= ~IN_RENAME; - UFS_WAPBL_UPDATE(fvp, NULL, NULL, 0); - done: - UFS_WAPBL_END(fdvp->v_mount); - out2: - /* - * clear IN_RENAME - some exit paths happen too early to go - * through the cleanup done in the "bad" case above, so we - * always do this mini-cleanup here. - */ - ip->i_flag &= ~IN_RENAME; - - VOP_UNLOCK(fdvp); - if (tdvp != fdvp) { - VOP_UNLOCK(tdvp); - } - VOP_UNLOCK(fvp); - if (tvp && tvp != fvp) { - VOP_UNLOCK(tvp); - } - - vrele(fdvp); - vrele(tdvp); - vrele(fvp); - if (tvp) { - vrele(tvp); - } - - fstrans_done(mp); - return (error); - - abort_withlocks: - VOP_UNLOCK(fdvp); - if (tdvp != fdvp) { - VOP_UNLOCK(tdvp); - } - VOP_UNLOCK(fvp); - if (tvp && tvp != fvp) { - VOP_UNLOCK(tvp); - } - - abort: - VOP_ABORTOP(fdvp, fcnp); /* XXX, why not in NFS? */ - VOP_ABORTOP(tdvp, tcnp); /* XXX, why not in NFS? */ - vrele(tdvp); - if (tvp) { - vrele(tvp); - } - vrele(fdvp); - if (fvp) { - vrele(fvp); - } - return (error); -} - int ufs_mkdir(void *v) { Index: sys/ufs/ufs/ufsmount.h =================================================================== RCS file: /cvsroot/src/sys/ufs/ufs/ufsmount.h,v retrieving revision 1.37 diff -p -u -r1.37 ufsmount.h --- sys/ufs/ufs/ufsmount.h 24 Nov 2011 15:51:32 -0000 1.37 +++ sys/ufs/ufs/ufsmount.h 7 Apr 2012 21:19:57 -0000 @@ -190,6 +190,12 @@ struct ufs_ops { */ #define MNINDIR(ump) ((ump)->um_nindir) #define blkptrtodb(ump, b) ((b) << (ump)->um_bptrtodb) + +/* + * Predicate for byte-swapping support. + */ +#define FSFMT(vp) (((vp)->v_mount->mnt_iflag & IMNT_DTYPE) == 0) + #endif /* _KERNEL */ #endif /* !_UFS_UFS_UFSMOUNT_H_ */