/* * ulfs_gro_remove: Rename an object over another link to itself, * effectively removing just the original link. */ static int ulfs_gro_remove(struct mount *mp, kauth_cred_t cred, struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp) { struct ulfs_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); /* XXX ulfs_dirremove decrements vp's link count for us. */ error = ulfs_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: return error; }
/* * whiteout vnode call */ int ulfs_whiteout(void *v) { struct vop_whiteout_args /* { struct vnode *a_dvp; struct componentname *a_cnp; int a_flags; } */ *ap = v; struct vnode *dvp = ap->a_dvp; struct componentname *cnp = ap->a_cnp; int error; struct ulfsmount *ump = VFSTOULFS(dvp->v_mount); struct lfs *fs = ump->um_lfs; struct ulfs_lookup_results *ulr; /* XXX should handle this material another way */ ulr = &VTOI(dvp)->i_crap; ULFS_CHECK_CRAPCOUNTER(VTOI(dvp)); error = 0; switch (ap->a_flags) { case LOOKUP: /* 4.4 format directories support whiteout operations */ if (fs->um_maxsymlinklen > 0) return (0); return (EOPNOTSUPP); case CREATE: /* create a new directory whiteout */ fstrans_start(dvp->v_mount, FSTRANS_SHARED); #ifdef DIAGNOSTIC if (fs->um_maxsymlinklen <= 0) panic("ulfs_whiteout: old format filesystem"); #endif error = ulfs_direnter(dvp, ulr, NULL, cnp, ULFS_WINO, LFS_DT_WHT, NULL); break; case DELETE: /* remove an existing directory whiteout */ fstrans_start(dvp->v_mount, FSTRANS_SHARED); #ifdef DIAGNOSTIC if (fs->um_maxsymlinklen <= 0) panic("ulfs_whiteout: old format filesystem"); #endif cnp->cn_flags &= ~DOWHITEOUT; error = ulfs_dirremove(dvp, ulr, NULL, cnp->cn_flags, 0); break; default: panic("ulfs_whiteout: unknown op"); /* NOTREACHED */ } fstrans_done(dvp->v_mount); return (error); }
int ulfs_remove(void *v) { struct vop_remove_args /* { struct vnode *a_dvp; struct vnode *a_vp; struct componentname *a_cnp; } */ *ap = v; struct vnode *vp, *dvp; struct inode *ip; struct mount *mp; int error; struct ulfs_lookup_results *ulr; vp = ap->a_vp; dvp = ap->a_dvp; ip = VTOI(vp); mp = dvp->v_mount; KASSERT(mp == vp->v_mount); /* XXX Not stable without lock. */ /* XXX should handle this material another way */ ulr = &VTOI(dvp)->i_crap; ULFS_CHECK_CRAPCOUNTER(VTOI(dvp)); fstrans_start(mp, FSTRANS_SHARED); if (vp->v_type == VDIR || (ip->i_flags & (IMMUTABLE | APPEND)) || (VTOI(dvp)->i_flags & APPEND)) error = EPERM; else { error = ulfs_dirremove(dvp, ulr, ip, ap->a_cnp->cn_flags, 0); } VN_KNOTE(vp, NOTE_DELETE); VN_KNOTE(dvp, NOTE_WRITE); if (dvp == vp) vrele(vp); else vput(vp); vput(dvp); fstrans_done(mp); return (error); }
int ulfs_rmdir(void *v) { struct vop_rmdir_args /* { struct vnode *a_dvp; struct vnode *a_vp; struct componentname *a_cnp; } */ *ap = v; struct vnode *vp, *dvp; struct componentname *cnp; struct inode *ip, *dp; int error; struct ulfs_lookup_results *ulr; vp = ap->a_vp; dvp = ap->a_dvp; cnp = ap->a_cnp; ip = VTOI(vp); dp = VTOI(dvp); /* XXX should handle this material another way */ ulr = &dp->i_crap; ULFS_CHECK_CRAPCOUNTER(dp); /* * No rmdir "." or of mounted directories please. */ if (dp == ip || vp->v_mountedhere != NULL) { if (dp == ip) vrele(dvp); else vput(dvp); vput(vp); return (EINVAL); } fstrans_start(dvp->v_mount, FSTRANS_SHARED); /* * Do not remove a directory that is in the process of being renamed. * Verify that the directory is empty (and valid). (Rmdir ".." won't * be valid since ".." will contain a reference to the current * directory and thus be non-empty.) */ error = 0; if (ip->i_nlink != 2 || !ulfs_dirempty(ip, dp->i_number, cnp->cn_cred)) { error = ENOTEMPTY; goto out; } if ((dp->i_flags & APPEND) || (ip->i_flags & (IMMUTABLE | APPEND))) { error = EPERM; goto out; } /* * Delete reference to directory before purging * inode. If we crash in between, the directory * will be reattached to lost+found, */ error = ulfs_dirremove(dvp, ulr, ip, cnp->cn_flags, 1); if (error) { goto out; } VN_KNOTE(dvp, NOTE_WRITE | NOTE_LINK); cache_purge(dvp); /* * Truncate inode. The only stuff left in the directory is "." and * "..". The "." reference is inconsequential since we're quashing * it. */ dp->i_nlink--; DIP_ASSIGN(dp, nlink, dp->i_nlink); dp->i_flag |= IN_CHANGE; ip->i_nlink--; DIP_ASSIGN(ip, nlink, ip->i_nlink); ip->i_flag |= IN_CHANGE; error = lfs_truncate(vp, (off_t)0, IO_SYNC, cnp->cn_cred); cache_purge(vp); #ifdef LFS_DIRHASH if (ip->i_dirhash != NULL) ulfsdirhash_free(ip); #endif out: VN_KNOTE(vp, NOTE_DELETE); vput(vp); fstrans_done(dvp->v_mount); vput(dvp); return (error); }
/* * ulfs_gro_rename: Actually perform the rename operation. */ static int ulfs_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 ulfs_lookup_results *fulr = fde; struct ulfs_lookup_results *tulr = tde; bool directory_p, reparent_p; struct lfs_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; directory_p = (fvp->v_type == VDIR); KASSERT(directory_p == ((VTOI(fvp)->i_mode & LFS_IFMT) == LFS_IFDIR)); KASSERT((tvp == NULL) || (directory_p == (tvp->v_type == VDIR))); KASSERT((tvp == NULL) || (directory_p == ((VTOI(tvp)->i_mode & LFS_IFMT) == LFS_IFDIR))); reparent_p = (fdvp != tdvp); KASSERT(reparent_p == (VTOI(fdvp)->i_number != VTOI(tdvp)->i_number)); /* * Commence hacking of the data on disk. */ error = 0; /* * 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 = lfs_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 = lfs_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(ulfs_direct_cache, PR_WAITOK); ulfs_makedirentry(VTOI(fvp), tcnp, newdir); error = ulfs_direnter(tdvp, tulr, NULL, newdir, tcnp, NULL); pool_cache_put(ulfs_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)lfs_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 ulfs_dirrewrite decrements tvp's link count, but * doesn't touch the link count of the new inode. Go * figure. */ error = ulfs_dirrewrite(VTOI(tdvp), tulr->ulr_offset, VTOI(tvp), VTOI(fvp)->i_number, LFS_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; } if (directory_p) { /* * XXX I don't understand the following comment * from ulfs_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 ulfs_dirempty call earlier does * not guarantee anything about nlink. */ if (VTOI(tvp)->i_nlink != 1) ulfs_dirbad(VTOI(tvp), (doff_t)0, "hard-linked directory"); VTOI(tvp)->i_nlink = 0; DIP_ASSIGN(VTOI(tvp), nlink, 0); error = lfs_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 ulfs_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 = ulfs_dirrewrite(VTOI(fvp), mastertemplate.dot_reclen, VTOI(fdvp), VTOI(tdvp)->i_number, LFS_DT_DIR, 0, IN_CHANGE); #if 0 /* XXX This branch was not in ulfs_rename! */ if (error) goto whymustithurtsomuch; #endif /* XXX WTF? Why purge here? Why not purge others? */ cache_purge(fdvp); } /* * 3) Unlink the source. */ /* * ulfs_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 && (tvp == NULL) && ulfs_rename_ulr_overlap_p(fulr, tulr)) { error = ulfs_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 ulfs_dirremove is ignored... * XXX And it turns out ulfs_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 ulfs_dirrewrite instead. * XXX Actually, sometimes it doesn't update fvp's link count. * XXX I hate the world. */ error = ulfs_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; arghmybrainhurts: /*ihateyou:*/ return error; }