/* * First draft of fixed ext2fs_rename. The structure of this is * similar to the fixed tmpfs_rename; eventually it should be * abstracted, perhaps in genfs or in vfs. * * -- Riastradh, 20111213 */ /* * ext2fs_rename: The hairiest vop, with the insanest API. * * Arguments: * * . fdvp (from directory vnode), * . fvp (from vnode), * . fcnp (from component name), * . tdvp (to directory vnode), * . tvp (to vnode, or NULL), and * . tcnp (to component name). * * Any pair of vnode parameters may have the same vnode. * * On entry, * * . fdvp, fvp, tdvp, and tvp are referenced, * . fdvp and fvp are unlocked, and * . tdvp and tvp (if nonnull) are locked. * * On exit, * * . fdvp, fvp, tdvp, and tvp (if nonnull) are unreferenced, and * . tdvp and tvp are unlocked. */ int 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 *fdvp = ap->a_fdvp; struct vnode *fvp = ap->a_fvp; struct componentname *fcnp = ap->a_fcnp; struct vnode *tdvp = ap->a_tdvp; struct vnode *tvp = ap->a_tvp; struct componentname *tcnp = ap->a_tcnp; kauth_cred_t cred; int error; KASSERT(fdvp != NULL); KASSERT(fvp != NULL); KASSERT(fcnp != NULL); KASSERT(fcnp->cn_nameptr != NULL); KASSERT(tdvp != NULL); KASSERT(tcnp != NULL); KASSERT(fcnp->cn_nameptr != NULL); /* KASSERT(VOP_ISLOCKED(fdvp) != LK_EXCLUSIVE); */ /* KASSERT(VOP_ISLOCKED(fvp) != LK_EXCLUSIVE); */ KASSERT(VOP_ISLOCKED(tdvp) == LK_EXCLUSIVE); KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) == LK_EXCLUSIVE)); KASSERT(fdvp->v_type == VDIR); KASSERT(tdvp->v_type == VDIR); cred = fcnp->cn_cred; KASSERT(tcnp->cn_cred == cred); /* * Sanitize our world from the VFS insanity. Unlock the target * directory and node, which are locked. Release the children, * which are referenced. Check for rename("x", "y/."), which * it is our responsibility to reject, not the caller's. (But * the caller does reject rename("x/.", "y"). Go figure.) */ VOP_UNLOCK(tdvp); if ((tvp != NULL) && (tvp != tdvp)) VOP_UNLOCK(tvp); vrele(fvp); if (tvp != NULL) vrele(tvp); if (tvp == tdvp) { error = EINVAL; goto out; } error = ext2fs_sane_rename(fdvp, fcnp, tdvp, tcnp, cred, false); out: /* * All done, whether with success or failure. Release the * directory nodes now, as the caller expects from the VFS * protocol. */ vrele(fdvp); vrele(tdvp); return error; } /* * ext2fs_sane_rename: The hairiest vop, with the saner API. * * Arguments: * * . fdvp (from directory vnode), * . fcnp (from component name), * . tdvp (to directory vnode), and * . tcnp (to component name). * * fdvp and tdvp must be referenced and unlocked. */ static int ext2fs_sane_rename(struct vnode *fdvp, struct componentname *fcnp, struct vnode *tdvp, struct componentname *tcnp, kauth_cred_t cred, bool posixly_correct) { struct mount *mp; struct ufs_lookup_results fulr, tulr; int error; KASSERT(fdvp != NULL); KASSERT(fcnp != NULL); KASSERT(tdvp != NULL); KASSERT(tcnp != NULL); /* KASSERT(VOP_ISLOCKED(fdvp) != LK_EXCLUSIVE); */ /* KASSERT(VOP_ISLOCKED(tdvp) != LK_EXCLUSIVE); */ KASSERT(fdvp->v_type == VDIR); KASSERT(tdvp->v_type == VDIR); KASSERT(fdvp->v_mount == tdvp->v_mount); KASSERT((fcnp->cn_flags & ISDOTDOT) == 0); KASSERT((tcnp->cn_flags & ISDOTDOT) == 0); KASSERT((fcnp->cn_namelen != 1) || (fcnp->cn_nameptr[0] != '.')); KASSERT((tcnp->cn_namelen != 1) || (tcnp->cn_nameptr[0] != '.')); KASSERT((fcnp->cn_namelen != 2) || (fcnp->cn_nameptr[0] != '.') || (fcnp->cn_nameptr[1] != '.')); KASSERT((tcnp->cn_namelen != 2) || (tcnp->cn_nameptr[0] != '.') || (tcnp->cn_nameptr[1] != '.')); mp = fdvp->v_mount; KASSERT(mp != NULL); KASSERT(mp == tdvp->v_mount); /* XXX How can we be sure this stays true? */ KASSERT((mp->mnt_flag & MNT_RDONLY) == 0); #if 0 /* XXX Before or after taking locks? */ fstrans_start(mp, FSTRANS_SHARED); #endif error = ext2fs_rename_enter(mp, cred, fdvp, fcnp, &fulr, &fvp, tdvp, tcnp, &tulr, &tvp); if (error) goto out_unlocked; /* * Check that everything is locked and looks right. */ KASSERT(fvp != NULL); KASSERT(VOP_ISLOCKED(fdvp) == LK_EXCLUSIVE); KASSERT(VOP_ISLOCKED(fvp) == LK_EXCLUSIVE); KASSERT(VOP_ISLOCKED(tdvp) == LK_EXCLUSIVE); KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) == LK_EXCLUSIVE)); /* * If the source and destination are the same object, we need * only at most delete the source entry. */ if (fvp == tvp) { KASSERT(tvp != NULL); if (fvp->v_type == VDIR) { /* XXX How can this possibly happen? */ error = EINVAL; goto out_locked; } /* XXX Does the ulr_offset identify directory entries? */ if (!posixly_correct && (fulr.ulr_offset != tulr.ulr_offset)) { /* * XXX Doesn't work because of locking. * error = VOP_REMOVE(fdvp, fvp, fcnp); */ error = ext2fs_do_remove(fdvp, fcnp, fulr, fvp); /* XXX Skip VN_KNOTE(fvp, NOTE_RENAME)? */ goto out_locked; } goto success; } /* XXX Does the ulr_offset identify directory entries? */ KASSERT(fulr.ulr_offset != tulr.ulr_offset); KASSERT(fvp != tvp); /* * If the target exists, refuse to rename a directory over a * non-directory or vice versa, or to clobber a non-empty * directory. */ if (tvp != NULL) { if (fvp->v_type == VDIR && tvp->v_type == VDIR) error = (ext2fs_dirempty(VTOI(tvp), VTOI(tdvp)->i_number, cred)? ENOTEMPTY : 0); else if (fvp->v_type == VDIR && tvp->v_type != VDIR) error = ENOTDIR; else if (fvp->v_type != VDIR && tvp->v_type == VDIR) error = EISDIR; else error = 0; if (error) goto out_locked; KASSERT((fvp->v_type == VDIR) == (tvp->v_type == VDIR)); } /* * Authorize the rename. */ error = ext2fs_rename_check_possible(fdvp, fvp, tdvp, tvp); if (error) goto out_locked; error = ext2fs_rename_check_permitted(cred, fdvp, fvp, tdvp, tvp); error = kauth_authorize_vnode(cred, KAUTH_VNODE_DELETE, fvp, fdvp, error); error = kauth_authorize_vnode(cred, KAUTH_VNODE_RENAME, tvp, tdvp, error); if (error) goto out_locked; /* * Everything is hunky-dory. Shuffle the directory entries. */ error = ext2fs_rename_attachdetach(mp, cred, fdvp, &fulr, fvp, tdvp, &tulr, tvp); if (error) goto out_locked; success: VN_KNOTE(fvp, NOTE_RENAME); error = 0; out_locked: ext2fs_rename_exit(mp, fdvp, fvp, tdvp, tvp); out_unlocked: /* KASSERT(VOP_ISLOCKED(fdvp) != LK_EXCLUSIVE); */ /* KASSERT(VOP_ISLOCKED(tdvp) != LK_EXCLUSIVE); */ /* KASSERT((fvp == NULL) || (VOP_ISLOCKED(fvp) != LK_EXCLUSIVE)); */ /* KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) != LK_EXCLUSIVE)); */ #if 0 /* XXX */ fstrans_done(mp); #endif return error; } /* * ext2fs_rename_enter: Look up fcnp in fdvp, and store the lookup * results in *fulr_ret and the associated vnode in *fvp_ret; fail if * not found. Look up tcnp in tdvp, and store the lookup results in * *tulr_ret and the associated vnode in *tvp_ret; store null instead * if not found. Fail if anything has been mounted on any of the nodes * involved. * * fdvp and tdvp must be referenced. * * On entry, nothing is locked. * * On success, everything is locked, and *fvp_ret, and *tvp_ret if * nonnull, are referenced. The only pairs of vnodes that may be * identical are {fdvp, tdvp} and {fvp, tvp}. * * On failure, everything remains as was. * * Locking everything including the source and target nodes is * necessary to make sure that, e.g., link count updates are OK. The * locking order is, in general, ancestor-first, matching the order you * need to use to look up a descendant anyway. */ static int ext2fs_rename_enter(struct mount *mp, kauth_cred_t cred, struct vnode *fdvp, struct componentname *fcnp, struct ufs_lookup_results *fulr_ret, struct vnode **fvp_ret, struct vnode *tdvp, struct componentname *tcnp, struct ufs_lookup_results *tulr_ret, struct vnode **tvp_ret) { int error; KASSERT(mp != NULL); KASSERT(fdvp != NULL); KASSERT(fcnp != NULL); KASSERT(fulr_ret != NULL); KASSERT(fvp_ret != NULL); KASSERT(tdvp != NULL); KASSERT(tcnp != NULL); KASSERT(tulr_ret != NULL); KASSERT(tvp_ret != NULL); KASSERT(fdvp->v_type == VDIR); KASSERT(tdvp->v_type == VDIR); /* XXX Paranoia cargo-culted from dh's ufs_rename. */ KASSERT((fcnp->cn_flags & (LOCKPARENT | LOCKLEAF)) == (LOCKPARENT | LOCKLEAF)); KASSERT((tcnp->cn_flags & (LOCKPARENT | LOCKLEAF)) == (LOCKPARENT | LOCKLEAF)); if (fdvp == tdvp) error = ext2fs_rename_enter_common(mp, cred, fdvp, fcnp, fulr_ret, fvp_ret, tcnp, tulr_ret, tvp_ret); else error = ext2fs_rename_enter_separate(mp, cred, fdvp, fcnp, fulr_ret, fvp_ret, tdvp, tcnp, tulr_ret, tvp_ret); if (error) return error; #if 0 /* XXX Before or after taking locks? */ fstrans_start(mp, FSTRANS_SHARED); #endif KASSERT(*fvp_ret != NULL); KASSERT(VOP_ISLOCKED(*fvp_ret) == LK_EXCLUSIVE); KASSERT((*tvp_ret == NULL) || (VOP_ISLOCKED(*tvp_ret) == LK_EXCLUSIVE)); KASSERT(*fvp_ret != fdvp); KASSERT(*fvp_ret != tdvp); KASSERT(*tvp_ret != fdvp); KASSERT(*tvp_ret != tdvp); return 0; } /* * ext2fs_rename_enter_common: Lock and look up with a common * source/target directory. */ static int ext2fs_rename_enter_common(struct mount *mp, kauth_cred_t cred, struct vnode *dvp, struct componentname *fcnp, struct ufs_lookup_results *fulr_ret, struct vnode **fvp_ret, struct componentname *tcnp, struct ufs_lookup_results *tulr_ret, struct vnode **tvp_ret) { struct ufs_lookup_results fulr, tulr; struct vnode *fvp, *tvp; int error; error = ext2fs_rename_lock_directory(dvp); if (error) goto fail0; /* Did we lose a race with mount? */ if (dvp->v_mountedhere != NULL) { error = EBUSY; goto fail1; } error = ext2fs_rename_relookup(dvp, fcnp, &fulr, &fvp); if (error) goto fail1; KASSERT(fvp != NULL); /* We ruled out `.' earlier. */ if (fvp == dvp) { ufs_dirbad(VTOI(dvp), fulr.ulr_offset, "entry other than `.' for self"); error = EIO; /* XXX What? */ goto fail2; } KASSERT(fvp != dvp); error = ext2fs_rename_relookup(dvp, tcnp, &tulr, &tvp); if (error == ENOENT) { tvp = NULL; } else if (error) { goto fail2; } else { KASSERT(tvp != NULL); /* We ruled out `.' earlier. */ if (tvp == dvp) { ufs_dirbad(VTOI(dvp), fulr.ulr_offset, "entry other than `.' for self"); error = EIO; /* XXX What? */ goto fail2; } } KASSERT(tvp != dvp); /* * We've looked up both nodes. Now lock them and check them. */ vn_lock(fvp, LK_EXCLUSIVE | LK_RETRY); KASSERT(fvp->v_mount == mp); /* Refuse to rename a mount point. */ if ((fvp->v_type == VDIR) && (fvp->v_mountedhere != NULL)) { error = EBUSY; goto fail3; } if ((tvp != NULL) && (tvp != fvp)) { vn_lock(tvp, LK_EXCLUSIVE | LK_RETRY); KASSERT(tvp->v_mount == mp); /* Refuse to rename over a mount point. */ if ((tvp->v_type == VDIR) && (tvp->v_mountedhere != NULL)) { error = EBUSY; goto fail4; } } if (fvp != NULL) *fulr_ret = fulr; *fvp_ret = fvp; if (tvp != NULL) *tulr_ret = tulr; *tvp_ret = tvp; return 0; fail4: if ((tvp != NULL) && (tvp != fvp)) VOP_UNLOCK(tvp); fail3: VOP_UNLOCK(fvp); if (tvp != NULL) vrele(tvp); fail2: vrele(fvp); fail1: VOP_UNLOCK(dvp); fail0: return error; } /* * ext2fs_rename_enter_separate: Lock and look up with separate source * and target directories. */ static int ext2fs_rename_enter_separate(struct mount *mp, kauth_cred_t cred, struct vnode *fdvp, struct componentname *fcnp, struct ufs_lookup_results *fulr_ret, struct vnode **fvp_ret, struct vnode *tdvp, struct componentname *tcnp, struct ufs_lookup_results *tulr_ret, struct vnode **tvp_ret) { struct vnode *intermediate_node; struct ufs_lookup_results fulr, tulr; struct vnode *fvp, *tvp; int error; error = ext2fs_rename_genealogy(mp, fdvp, tdvp, cred, &intermediate_node); if (error) return error; /* * intermediate_node == NULL means fdvp is not an ancestor of tdvp. */ if (intermediate_node == NULL) error = ext2fs_rename_lock(mp, cred, ENOTEMPTY, tdvp, tcnp, true, &tulr, &tvp, fdvp, fcnp, false, &fulr, &fvp); else error = ext2fs_rename_lock(mp, cred, EINVAL, fdvp, fcnp, false, &fulr, &fvp, tdvp, tcnp, true, &tulr, &tvp); if (error) return error; KASSERT(fvp != NULL); /* * Reject rename("foo/bar", "foo/bar/baz/quux/zot"). */ if (fvp == intermediate_node) { ext2fs_rename_exit(mp, fdvp, fvp, tdvp, tvp); return EINVAL; } if (fvp != NULL) *fulr_ret = fulr; *fvp_ret = fvp; if (tvp != NULL) *tulr_ret = tulr; *tvp_ret = tvp; return 0; } /* * ext2fs_rename_exit: Unlock everything we locked for rename. * * fdvp and tdvp must be referenced. * * On entry, everything is locked, and fvp and tvp referenced. * * On exit, everything is unlocked, and fvp and tvp are released. */ static int ext2fs_rename_exit(struct mount *mp, struct vnode *fdvp, struct vnode *fvp, struct vnode *tdvp, struct vnode *tvp) { KASSERT(mp != NULL); KASSERT(fdvp != NULL); KASSERT(fvp != NULL); KASSERT(fdvp != fvp); KASSERT(fdvp != tvp); KASSERT(tdvp != tvp); KASSERT(tdvp != fvp); KASSERT(VOP_ISLOCKED(fdvp) == LK_EXCLUSIVE); KASSERT(VOP_ISLOCKED(tdvp) == LK_EXCLUSIVE); KASSERT(VOP_ISLOCKED(fvp) == LK_EXCLUSIVE); KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) == LK_EXCLUSIVE)); if (tvp != NULL) { if (tvp != fvp) vput(tvp); else vrele(tvp); } VOP_UNLOCK(tdvp); vput(fvp); if (fdvp != tdvp) VOP_UNLOCK(fdvp); #if 0 /* XXX */ fstrans_done(mp); #endif } /* * ext2fs_rename_relookup: Relookup cnp in dvp, storing the unlocked * vnode in *vp_ret and the lookup results in *ulr_ret. */ static int ext2fs_rename_relookup(struct vnode *dvp, struct componentname *cnp, struct ulr_lookup_results *ulr_ret, struct vnode **vp_ret) { struct vnode *vp; int error; KASSERT(dvp != NULL); KASSERT(ulr_ret != NULL); KASSERT(vp_ret != NULL); KASSERT(cnp != NULL); KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); error = relookup(dvp, &vp, cnp); if ((error == 0) && (vp == NULL)) error = ENOENT; if (error) return error; /* * Thanks to VFS insanity, relookup locks vp for us, which * screws us in various ways. */ VOP_UNLOCK(vp); *ulr_ret = VTOI(dvp)->i_crap; *vp_ret = vp; return 0; } /* * ext2fs_rename_lock_directory: Lock a directory, but fail if it has * been rmdir'd. * * vp must be referenced. */ static int ext2fs_rename_lock_directory(struct vnode *vp) { KASSERT(vp != NULL); KASSERT(vp->v_type == VDIR); vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); /* XXX Is this the right way to test for rmdir? */ if (VTOI(vp)->i_size == 0) { VOP_UNLOCK(vp); return ENOENT; } return 0; } /* * ext2fs_rename_genealogy: Analyze the genealogy of the source and * target nodes. * * On success, stores in *intermediate_node_ret either the child of * fdvp of which tdvp is a descendant, or null if tdvp is not a * descendant of fdvp at all. * * fdvp and tdvp must be unlocked and referenced. The file system's * rename lock must also be held, to exclude concurrent changes to the * file system's genealogy other than rmdir. * * XXX This causes an extra lock/unlock of tdvp in the case when we're * just about to lock it again before locking anything else. However, * changing that requires reorganizing the code to make it even more * horrifically obscure. */ static int ext2fs_rename_genealogy(struct mount *mp, struct vnode *fdvp, struct vnode *tdvp, struct vnode **intermediate_node_ret) { struct vnode *vp, *dvp; ino_t dotdot_ino; int error; KASSERT(fdvp != NULL); KASSERT(tdvp != NULL); KASSERT(intermediate_node_ret != NULL); KASSERT(fdvp->v_type == VDIR); KASSERT(tdvp->v_type == VDIR); KASSERT(fdvp != tdvp); /* * We need to provisionally lock tdvp to keep rmdir from * deleting it -- or any ancestor -- at an inopportune moment. * XXX This doesn't do the same thing as in tmpfs. What to do? */ error = ext2fs_rename_lock_directory(tdvp); if (error) return error; vp = tdvp; for (;;) { KASSERT(vp->v_type == VDIR); /* Did we hit the root without finding fdvp? */ if (VTOI(vp)->i_number == ROOTINO) { *intermediate_node_ret = NULL; break; } error = ext2fs_read_dotdot(vp, cred, &dotdot_ino); if (error) goto out; /* Did we find that fdvp is an ancestor? */ if (VTOI(fdvp)->i_number == dotdot_ino) { *intermediate_node_ret = vp; break; } /* Neither -- keep ascending. */ /* * XXX Why not vput(vp); VFS_VGET(...)? Cargo-culted * from ufs_parentcheck... */ VOP_UNLOCK(vp); error = VFS_VGET(mp, dotdot_ino, &dvp); vrele(vp); if (error) return error; KASSERT(dvp != NULL); vp = dvp; if (vp->v_type != VDIR) { error = ENOTDIR; goto out; } /* XXX Is this the right way to test for rmdir? */ if (VTOI(vp)->i_size == 0) { error = ENOENT; goto out; } } error = 0; out: VOP_UNLOCK(vp); return error; } 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(vp->v_type == VDIR); KASSERT(ino_ret != NULL); 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] != '.') return ENOTDIR; *ino_ret = fs2h32(dirbuf.dotdot_ino); return 0; } /* * ext2fs_rename_lock: Lock directories a and b, which must be * distinct, and look up and lock nodes a and b. Do a first and then * b. Directory b may not be an ancestor of directory a, although * directory a may be an ancestor of directory b. Fail with * overlap_error if node a is directory b. Neither componentname may * be `.' or `..'. * * a_dvp and b_dvp must be referenced. * * On entry, a_dvp and b_dvp are unlocked. * * On success, * . a_dvp and b_dvp are locked, * . *a_dirent_ret is filled with a directory entry whose node is * locked and referenced, * . *b_vp_ret is filled with the corresponding vnode, * . *b_dirent_ret is filled either with null or with a directory entry * whose node is locked and referenced, * . *b_vp is filled either with null or with the corresponding vnode, * and * . the only pair of vnodes that may be identical is a_vp and b_vp. * * On failure, a_dvp and b_dvp are left unlocked, and *a_dirent_ret, * *a_vp, *b_dirent_ret, and *b_vp are left alone. */ static int ext2fs_rename_lock(struct mount *mount, kauth_cred_t cred, int overlap_error, struct vnode *a_dvp, struct componentname *a_cnp, bool a_missing_ok, struct ufs_lookup_results **a_ulr_ret, struct vnode **a_vp_ret, struct vnode *b_dvp, struct componentname *b_cnp, bool b_missing_ok, struct ufs_lookup_results **b_ulr_ret, struct vnode **b_vp_ret) { struct ufs_lookup_results *a_ulr, *b_ulr; struct vnode *a_vp, *b_vp; int error; KASSERT(a_dvp != NULL); KASSERT(a_cnp != NULL); KASSERT(a_ulr_ret != NULL); KASSERT(a_vp_ret != NULL); KASSERT(b_dvp != NULL); KASSERT(b_cnp != NULL); KASSERT(b_ulr_ret != NULL); KASSERT(b_vp_ret != NULL); KASSERT(a_dvp != b_dvp); KASSERT(a_dvp->v_type == VDIR); KASSERT(b_dvp->v_type == VDIR); KASSERT(a_missing_ok != b_missing_ok); error = ext2fs_lock_directory(a_dvp); if (error) goto fail0; /* Did we lose a race with mount? */ if (a_dvp->v_mountedhere != NULL) { error = EBUSY; goto fail1; } error = ext2fs_rename_relookup(a_dvp, a_cnp, &a_ulr, &a_vp); if (error) { if (a_missing_ok && (error == ENOENT)) a_vp = NULL; else goto fail1; } else { KASSERT(a_vp != NULL); /* We ruled out `.' earlier. */ if (a_vp == a_dvp) { ufs_dirbad(VTOI(a_dvp), fulr.ulr_offset, "entry other than `.' for self"); error = EIO; /* XXX What? */ goto fail2; } if (a_vp == b_dvp) { error = overlap_error; goto fail2; } } KASSERT(a_vp != a_dvp); KASSERT(a_vp != b_dvp); error = ext2fs_rename_lock_directory(b_dvp); if (error) goto fail2; /* Did we lose a race with mount? */ if (b_dvp->v_mountedhere != NULL) { error = EBUSY; goto fail3; } error = ext2fs_rename_relookup(b_dvp, b_cnp, &b_ulr, &b_vp); if (error) { if (b_missing_ok && (errno == ENOENT)) b_vp = NULL; else goto fail3; } else { KASSERT(b_vp != NULL); /* We ruled out `.' earlier. */ if (b_vp == b_dvp) { ufs_dirbad(VTOI(b_dvp), tulr.ulr_offset, "entry other than `.' for self"); error = EIO; /* XXX What? */ goto fail4; } /* b is not an ancestor of a. */ if (b_vp == a_dvp) { ufs_dirbad(VTOI(b_dvp), tulr.ulr_offset, "directory hard link"); error = EIO; /* XXX What? */ goto fail4; } } KASSERT(b_vp != b_dvp); KASSERT(b_vp != a_dvp); /* * We've looked up both nodes. Now lock them and check them. */ if (a_vp != NULL) { vn_lock(a_vp, LK_EXCLUSIVE | LK_RETRY); KASSERT(a_vp->v_mount == mp); /* Refuse to rename (over) a mount point. */ if ((a_vp->v_type == VDIR) && (a_vp->v_mountedhere != NULL)) { error = EBUSY; goto fail5; } } if ((b_vp != NULL) && (b_vp != a_vp)) { vn_lock(b_vp, LK_EXCLUSIVE | LK_RETRY); KASSERT(b_vp->v_mount == mp); /* Refuse to rename (over) a mount point. */ if ((b_vp->v_type == VDIR) && (b_vp->v_mountedhere != NULL)) { error = EBUSY; goto fail6; } } KASSERT(VOP_ISLOCKED(a_dvp) == LK_EXCLUSIVE); KASSERT(VOP_ISLOCKED(b_dvp) == LK_EXCLUSIVE); KASSERT(a_missing_ok || (a_vp != NULL)); KASSERT(b_missing_ok || (b_vp != NULL)); KASSERT((a_vp == NULL) || (VOP_ISLOCKED(a_vp) == LK_EXCLUSIVE)); KASSERT((b_vp == NULL) || (VOP_ISLOCKED(b_vp) == LK_EXCLUSIVE)); if (a_vp != NULL) *a_ulr_ret = a_ulr; *a_vp_ret = a_vp; if (b_vp != NULL) *b_ulr_ret = b_ulr; *b_vp_ret = b_vp; return 0; fail6: if ((b_vp != NULL) && (b_vp != a_vp)) VOP_UNLOCK(b_vp); fail5: if (a_vp != NULL) VOP_UNLOCK(a_vp); fail4: if (b_vp != NULL) vrele(b_vp); fail3: VOP_UNLOCK(b_dvp); fail2: if (a_vp != NULL) vrele(a_vp); fail1: VOP_UNLOCK(a_dvp); fail0: return error; } /* * ext2fs_rename_attachdetach: Shuffle the directory entries to move * fvp from the directory fdvp into the directory tdvp. fulr is fvp's * lookup results in fdvp. If we are overwriting a target node, it is * tvp, and tulr is its lookup results in tdvp. * * fdvp, fvp, tdvp, and tvp must all be locked and referenced. */ static int ext2fs_rename_attachdetach(struct mount *mp, kauth_cred_t cred, struct vnode *fdvp, struct ufs_lookup_results *fulr, struct vnode *fvp, struct vnode *tdvp, struct ufs_lookup_results *tulr, struct vnode *tvp) { bool directory_p, reparent_p; KASSERT(mp != NULL); KASSERT(fdvp != NULL); KASSERT(fulr != NULL); KASSERT(fvp != NULL); KASSERT(tdvp != NULL); KASSERT(tulr != 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)); /* * We will 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(fvp)->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)); /* * 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) { /* * XXX WTF? Why check here and not earlier? Is this * covered by checking v_mount earlier? */ KASSERT(VTOI(tdvp)->i_dev == VTOI(fvp)->i_dev); /* * 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) { 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) { 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; } VOP_KNOTE(tdvp, NOTE_WRITE); } else { /* XXX WTF? Why check here and not earlier? Is this * covered by checking v_mount earlier? */ KASSERT(VTOI(tvp)->i_dev == VTOI(tdvp)->i_dev); KASSERT(VTOI(fvp)->i_dev == VTOI(fdvp)->i_dev); KASSERT(VTOI(tvp)->i_dev == VTOI(fvp)->i_dev); 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); } VTOI(tvp)->i_flag |= IN_CHANGE; VN_KNOTE(tdvp, NOTE_WRITE); VN_KNOTE(tvp, NOTE_DELETE); } /* * 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. */ if (!reparent_p && ulr_overlap(fulr, tulr)) ext2fs_rename_unbotch_ulr_overlap(fdvp, fulr, tulr); /* * 3) Unlink the source. */ /* * 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); } error = ext2fs_dirremove(fdvp, fulr, fcnp); 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; } static int ext2fs_rename_unbotch_ulr_overlap(struct vnode *dvp, struct ufs_lookup_results *fulr, struct ufs_lookup_results *tulr) { /* XXX */ } /* * 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 *fvp, 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_do_remove: Remove the entry for the non-directory vp with * componentname cnp from the directory dvp, using the lookup results * ulr. * * Everything must be locked and referenced. * * XXX Change ext2fs_remove to use this. */ static int ext2fs_do_remove(struct mount *mp, struct vnode *dvp, struct componentname *cnp, struct ufs_lookup_results *ulr, struct vnode *vp, kauth_cred_t cred) { int error; KASSERT(mp != NULL); KASSERT(dvp != NULL); KASSERT(cnp != NULL); KASSERT(ulr != NULL); KASSERT(vp != NULL); KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); error = ext2fs_remove_check_possible(dvp, vp); if (error) return error; error = ext2fs_remove_check_permitted(cred, dvp, vp); error = kauth_authorize_vnode(cred, KAUTH_VNODE_DELETE, vp, dvp, error); if (error) return error; 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; /* XXX What? */ VN_KNOTE(vp, NOTE_DELETE); VN_KNOTE(dvp, NOTE_WRITE); return 0; } /* * ext2fs_rename_check_possible: Check whether a rename is possible * independent of credentials. * * Everything must be locked and referenced. */ static int ext2fs_rename_check_possible( struct vnode *fdvp, struct vnode *fvp, struct vnode *tdvp, struct vnode *tvp) { KASSERT(fdvp != NULL); KASSERT(fvp != NULL); KASSERT(tdvp != NULL); KASSERT(fdvp != fvp); KASSERT(tdvp != tvp); KASSERT(fvp != tvp); 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)); /* * XXX Wait a minute -- in ufs and ext2fs, it looks like the * source directory can be immutable. WTF? Or is that covered * by VOP_ACCESS? */ /* * If fdvp is immutable, we can't write to it. If fdvp is * append-only, the only change we can make is to add entries * to it. If fvp is immutable, we can't change links to it. * If fvp is append-only...well, this is what UFS does. */ if ((VTOI(fdvp)->i_e2fs_flags | (VTOI(fvp)->i_e2fs_flags)) & (EXT2_IMMUTABLE | EXT2_APPEND)) return EPERM; /* * If tdvp is immutable, we can't write to it. If tdvp is * append-only, we can add entries, but we can't change * existing entries. */ if (VTOI(tdvp)->i_e2fs_flags & (EXT2_IMMUTABLE | (tvp? EXT2_APPEND : 0))) return EPERM; /* * If tvp is immutable, we can't replace links to it. If tvp * is append-only...well, this is what UFS does. */ if ((tvp != NULL) && (VTOI(tvp)->i_e2fs_flags & (EXT2_IMMUTABLE | EXT2_APPEND))) return EPERM; return 0; } /* * ext2fs_rename_check_permitted: Check whether a rename is permitted * given our credentials. * * Everything must be locked and referenced. */ static int ext2fs_rename_check_permitted(kauth_cred_t cred, struct vnode *fdvp, struct vnode *fvp, struct vnode *tdvp, struct vnode *tvp) { int error; KASSERT(fdvp != NULL); KASSERT(fvp != NULL); KASSERT(tdvp != NULL); KASSERT(fdvp != fvp); KASSERT(tdvp != tvp); KASSERT(fvp != tvp); 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 need to remove or change an entry in the source directory. */ error = VOP_ACCESS(fdvp, VWRITE, cred); if (error) return error; /* * If we are changing directories, then we need to write to the * target directory to add or change an entry. Also, if fvp is * a directory, we need to write to it to change its `..' * entry. */ if (fdvp != tdvp) { error = VOP_ACCESS(tdvp, VWRITE, cred); if (error) return error; if (fvp->v_type == VDIR) { error = VOP_ACCESS(fvp, VWRITE, cred); if (error) return error; } } error = ext2fs_check_sticky(cred, fdvp, fvp); if (error) return error; error = ext2fs_check_sticky(cred, tdvp, tvp); if (error) return error; return 0; } /* * ext2fs_remove_check_possible: Check whether removing vp's entry in * dvp is possible independent of credentials. * * Everything must be locked and referenced. */ static int ext2fs_remove_check_possible(struct vnode *dvp, struct vnode *vp) { KASSERT(dvp != NULL); KASSERT(vp != NULL); KASSERT(dvp != vp); KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); /* * We want to delete the entry. If dvp is immutable, we can't * write to it to delete the entry. If dvp is append-only, the * only change we can make is to add entries, so we can't * delete entries. If vp is immutable, we can't change the * links to it, so we can't delete the entry. If vp is * append-only...well, this is what UFS does. */ if ((VTOI(dvp)->i_e2fs_flags | VTOI(vp)->i_e2fs_flags) & (EXT2_IMMUTABLE | EXT2_APPEND)) return EPERM; return 0; } /* * ext2fs_remove_check_permitted: Check whether removing vp's entry in * dvp is permitted given our credentials. * * Everything must be locked and referenced. */ static int ext2fs_remove_check_permitted(kauth_cred_t cred, struct vnode *dvp, struct vnode *vp) { int error; KASSERT(dvp != NULL); KASSERT(vp != NULL); KASSERT(dvp != vp); KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); /* * Check whether we are permitted to write to the source * directory in order to delete an entry from it. */ error = VOP_ACCESS(dvp, VWRITE, cred); if (error) return error; error = ext2fs_check_sticky(cred, dvp, vp); if (error) return error; return 0; } /* * ext2fs_check_sticky: Check whether we may change an entry in a * sticky directory. If the directory is sticky, the user must own * either the directory or, if it exists, the node, in order to change * the entry. * * Everything must be locked and referenced. */ static int ext2fs_check_sticky(kauth_cred_t cred, struct vnode *dvp, struct vnode *vp) { KASSERT(dvp != NULL); KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); KASSERT((vp == NULL) || (VOP_ISLOCKED(vp) == LK_EXCLUSIVE)); if (VTOI(dvp)->i_e2fs_mode & S_ISTXT) { uid_t euid = kauth_cred_geteuid(cred); if (euid == VTOI(dvp)->i_uid) return 0; if ((node == NULL) || (euid == VTOI(vp)->i_uid)) return 0; return EPERM; } return 0; }