/* * Construct a new directory entry after a call to namei, using the * name in the componentname argument cnp. The argument ip is the * inode to which the new directory entry will refer. */ void ulfs_makedirentry(struct inode *ip, struct componentname *cnp, struct lfs_direct *newdirp) { newdirp->d_ino = ip->i_number; newdirp->d_namlen = cnp->cn_namelen; memcpy(newdirp->d_name, cnp->cn_nameptr, (size_t)cnp->cn_namelen); newdirp->d_name[cnp->cn_namelen] = '\0'; if (FSFMT(ITOV(ip))) newdirp->d_type = 0; else newdirp->d_type = LFS_IFTODT(ip->i_mode); }
/* * 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; }
/* * ulfs_link: create hard link. */ int ulfs_link(void *v) { struct vop_link_v2_args /* { struct vnode *a_dvp; struct vnode *a_vp; struct componentname *a_cnp; } */ *ap = v; struct vnode *dvp = ap->a_dvp; struct vnode *vp = ap->a_vp; struct componentname *cnp = ap->a_cnp; struct mount *mp = dvp->v_mount; struct inode *ip; int error; struct ulfs_lookup_results *ulr; KASSERT(dvp != vp); KASSERT(vp->v_type != VDIR); 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); error = vn_lock(vp, LK_EXCLUSIVE); if (error) { VOP_ABORTOP(dvp, cnp); goto out2; } ip = VTOI(vp); if ((nlink_t)ip->i_nlink >= LINK_MAX) { VOP_ABORTOP(dvp, cnp); error = EMLINK; goto out1; } if (ip->i_flags & (IMMUTABLE | APPEND)) { VOP_ABORTOP(dvp, cnp); error = EPERM; goto out1; } ip->i_nlink++; DIP_ASSIGN(ip, nlink, ip->i_nlink); ip->i_flag |= IN_CHANGE; error = lfs_update(vp, NULL, NULL, UPDATE_DIROP); if (!error) { error = ulfs_direnter(dvp, ulr, vp, cnp, ip->i_number, LFS_IFTODT(ip->i_mode), NULL); } if (error) { ip->i_nlink--; DIP_ASSIGN(ip, nlink, ip->i_nlink); ip->i_flag |= IN_CHANGE; } out1: VOP_UNLOCK(vp); out2: VN_KNOTE(vp, NOTE_LINK); VN_KNOTE(dvp, NOTE_WRITE); fstrans_done(mp); return (error); }
/* * Allocate a new inode. */ int ulfs_makeinode(struct vattr *vap, struct vnode *dvp, const struct ulfs_lookup_results *ulr, struct vnode **vpp, struct componentname *cnp) { struct inode *ip; struct vnode *tvp; int error; error = vcache_new(dvp->v_mount, dvp, vap, cnp->cn_cred, &tvp); if (error) return error; error = vn_lock(tvp, LK_EXCLUSIVE); if (error) { vrele(tvp); return error; } lfs_mark_vnode(tvp); *vpp = tvp; ip = VTOI(tvp); ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE; ip->i_nlink = 1; DIP_ASSIGN(ip, nlink, 1); /* Authorize setting SGID if needed. */ if (ip->i_mode & ISGID) { error = kauth_authorize_vnode(cnp->cn_cred, KAUTH_VNODE_WRITE_SECURITY, tvp, NULL, genfs_can_chmod(tvp->v_type, cnp->cn_cred, ip->i_uid, ip->i_gid, MAKEIMODE(vap->va_type, vap->va_mode))); if (error) { ip->i_mode &= ~ISGID; DIP_ASSIGN(ip, mode, ip->i_mode); } } if (cnp->cn_flags & ISWHITEOUT) { ip->i_flags |= UF_OPAQUE; DIP_ASSIGN(ip, flags, ip->i_flags); } /* * Make sure inode goes to disk before directory entry. */ if ((error = lfs_update(tvp, NULL, NULL, UPDATE_DIROP)) != 0) goto bad; error = ulfs_direnter(dvp, ulr, tvp, cnp, ip->i_number, LFS_IFTODT(ip->i_mode), NULL); if (error) goto bad; *vpp = tvp; return (0); bad: /* * Write error occurred trying to update the inode * or the directory so must deallocate the inode. */ ip->i_nlink = 0; DIP_ASSIGN(ip, nlink, 0); ip->i_flag |= IN_CHANGE; /* If IN_ADIROP, account for it */ lfs_unmark_vnode(tvp); vput(tvp); return (error); }