diff options
-rw-r--r-- | sys/gnu/ext2fs/ext2_vnops.c | 1020 | ||||
-rw-r--r-- | sys/gnu/fs/ext2fs/ext2_vnops.c | 1020 |
2 files changed, 2040 insertions, 0 deletions
diff --git a/sys/gnu/ext2fs/ext2_vnops.c b/sys/gnu/ext2fs/ext2_vnops.c index 8a372d5..d610ad4 100644 --- a/sys/gnu/ext2fs/ext2_vnops.c +++ b/sys/gnu/ext2fs/ext2_vnops.c @@ -47,6 +47,7 @@ #include <sys/param.h> #include <sys/systm.h> #include <sys/resourcevar.h> +#include <sys/malloc.h> #include <sys/kernel.h> #include <sys/stat.h> #include <sys/buf.h> @@ -54,6 +55,7 @@ #include <sys/mount.h> #include <sys/time.h> #include <sys/vnode.h> +#include <sys/namei.h> #include <vm/vm.h> #include <vm/vm_extern.h> @@ -63,6 +65,7 @@ #else #include <sys/signalvar.h> #endif +#include <ufs/ufs/dir.h> #include <ufs/ufs/quota.h> #include <ufs/ufs/inode.h> #include <ufs/ufs/ufsmount.h> @@ -72,9 +75,27 @@ #include <gnu/ext2fs/fs.h> #include <gnu/ext2fs/ext2_extern.h> +static int ext2_makeinode __P((int mode, struct vnode *, struct vnode **, struct componentname *)); + static int ext2_fsync __P((struct vop_fsync_args *)); static int ext2_read __P((struct vop_read_args *)); static int ext2_write __P((struct vop_write_args *)); +static int ext2_remove __P((struct vop_remove_args *)); +static int ext2_link __P((struct vop_link_args *)); +static int ext2_rename __P((struct vop_rename_args *)); +static int ext2_mkdir __P((struct vop_mkdir_args *)); +static int ext2_rmdir __P((struct vop_rmdir_args *)); +static int ext2_create __P((struct vop_create_args *)); +static int ext2_mknod __P((struct vop_mknod_args *)); +static int ext2_symlink __P((struct vop_symlink_args *)); + +/* + * A virgin directory (no blushing please). + */ +static struct odirtemplate omastertemplate = { + 0, 12, 1, { '.', 0 }, + 0, DIRBLKSIZ - 12, 2, { '.', '.', 0 } +}; /* Global vfs data structures for ufs. */ vop_t **ext2_vnodeop_p; @@ -88,6 +109,14 @@ static struct vnodeopv_entry_desc ext2_vnodeop_entries[] = { { &vop_readdir_desc, (vop_t *) ext2_readdir }, { &vop_reallocblks_desc, (vop_t *) ext2_reallocblks }, { &vop_write_desc, (vop_t *) ext2_write }, + { &vop_remove_desc, (vop_t *) ext2_remove }, + { &vop_link_desc, (vop_t *) ext2_link }, + { &vop_rename_desc, (vop_t *) ext2_rename }, + { &vop_mkdir_desc, (vop_t *) ext2_mkdir }, + { &vop_rmdir_desc, (vop_t *) ext2_rmdir }, + { &vop_create_desc, (vop_t *) ext2_create }, + { &vop_mknod_desc, (vop_t *) ext2_mknod }, + { &vop_symlink_desc, (vop_t *) ext2_symlink }, { NULL, NULL } }; static struct vnodeopv_desc ext2fs_vnodeop_opv_desc = @@ -121,6 +150,997 @@ static struct vnodeopv_desc ext2fs_fifoop_opv_desc = #include <gnu/ext2fs/ext2_readwrite.c> + +/* + * Allocate a new inode. + */ +int +ext2_makeinode(mode, dvp, vpp, cnp) + int mode; + struct vnode *dvp; + struct vnode **vpp; + struct componentname *cnp; +{ + register struct inode *ip, *pdir; + struct timeval tv; + struct vnode *tvp; + int error; + + pdir = VTOI(dvp); +#ifdef DIAGNOSTIC + if ((cnp->cn_flags & HASBUF) == 0) + panic("ext2_makeinode: no name"); +#endif + *vpp = NULL; + if ((mode & IFMT) == 0) + mode |= IFREG; + + error = UFS_VALLOC(dvp, mode, cnp->cn_cred, &tvp); + if (error) { + zfree(namei_zone, cnp->cn_pnbuf); + vput(dvp); + return (error); + } + ip = VTOI(tvp); + ip->i_gid = pdir->i_gid; +#ifdef SUIDDIR + { +#ifdef QUOTA + struct ucred ucred, *ucp; + ucp = cnp->cn_cred; +#endif I + /* + * if we are + * not the owner of the directory, + * and we are hacking owners here, (only do this where told to) + * and we are not giving it TOO root, (would subvert quotas) + * then go ahead and give it to the other user. + * Note that this drops off the execute bits for security. + */ + if ( (dvp->v_mount->mnt_flag & MNT_SUIDDIR) && + (pdir->i_mode & ISUID) && + (pdir->i_uid != cnp->cn_cred->cr_uid) && pdir->i_uid) { + ip->i_uid = pdir->i_uid; + mode &= ~07111; +#ifdef QUOTA + /* + * make sure the correct user gets charged + * for the space. + * Quickly knock up a dummy credential for the victim. + * XXX This seems to never be accessed out of our + * context so a stack variable is ok. + */ + ucred.cr_ref = 1; + ucred.cr_uid = ip->i_uid; + ucred.cr_ngroups = 1; + ucred.cr_groups[0] = pdir->i_gid; + ucp = *ucred; +#endif I + } else { + ip->i_uid = cnp->cn_cred->cr_uid; + } + +#ifdef QUOTA + if ((error = getinoquota(ip)) || + (error = chkiq(ip, 1, ucp, 0))) { + free(cnp->cn_pnbuf, M_NAMEI); + VOP_VFREE(tvp, ip->i_number, mode); + vput(tvp); + vput(dvp); + return (error); + } +#endif + } +#else + ip->i_uid = cnp->cn_cred->cr_uid; +#ifdef QUOTA + if ((error = getinoquota(ip)) || + (error = chkiq(ip, 1, cnp->cn_cred, 0))) { + zfree(namei_zone, cnp->cn_pnbuf); + UFS_VFREE(tvp, ip->i_number, mode); + vput(tvp); + vput(dvp); + return (error); + } +#endif +#endif + ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE; + ip->i_mode = mode; + tvp->v_type = IFTOVT(mode); /* Rest init'd in getnewvnode(). */ + ip->i_nlink = 1; + if ((ip->i_mode & ISGID) && !groupmember(ip->i_gid, cnp->cn_cred) && + suser(cnp->cn_cred, NULL)) + ip->i_mode &= ~ISGID; + + if (cnp->cn_flags & ISWHITEOUT) + ip->i_flags |= UF_OPAQUE; + + /* + * Make sure inode goes to disk before directory entry. + */ + gettime(&tv); + error = UFS_UPDATE(tvp, &tv, &tv, 1); + if (error) + goto bad; + error = ext2_direnter(ip, dvp, cnp); + if (error) + goto bad; + + if ((cnp->cn_flags & SAVESTART) == 0) + zfree(namei_zone, cnp->cn_pnbuf); + vput(dvp); + *vpp = tvp; + return (0); + +bad: + /* + * Write error occurred trying to update the inode + * or the directory so must deallocate the inode. + */ + zfree(namei_zone, cnp->cn_pnbuf); + vput(dvp); + ip->i_nlink = 0; + ip->i_flag |= IN_CHANGE; + vput(tvp); + return (error); +} + +/* + * Create a regular file + */ +static int +ext2_create(ap) + struct vop_create_args /* { + struct vnode *a_dvp; + struct vnode **a_vpp; + struct componentname *a_cnp; + struct vattr *a_vap; + } */ *ap; +{ + int error; + + error = + ext2_makeinode(MAKEIMODE(ap->a_vap->va_type, ap->a_vap->va_mode), + ap->a_dvp, ap->a_vpp, ap->a_cnp); + if (error) + return (error); + return (0); +} + +/* + * Mknod vnode call + */ +/* ARGSUSED */ +static int +ext2_mknod(ap) + struct vop_mknod_args /* { + struct vnode *a_dvp; + struct vnode **a_vpp; + struct componentname *a_cnp; + struct vattr *a_vap; + } */ *ap; +{ + struct vattr *vap = ap->a_vap; + struct vnode **vpp = ap->a_vpp; + struct inode *ip; + int error; + + error = ext2_makeinode(MAKEIMODE(vap->va_type, vap->va_mode), + ap->a_dvp, vpp, ap->a_cnp); + if (error) + return (error); + ip = VTOI(*vpp); + ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE; + if (vap->va_rdev != VNOVAL) { + /* + * Want to be able to use this to make badblock + * inodes, so don't truncate the dev number. + */ + ip->i_rdev = vap->va_rdev; + } + /* + * Remove inode so that it will be reloaded by VFS_VGET and + * checked to see if it is an alias of an existing entry in + * the inode cache. + */ + vput(*vpp); + (*vpp)->v_type = VNON; + vgone(*vpp); + *vpp = 0; + return (0); +} + + +/* + * Mkdir system call + */ +static int +ext2_mkdir(ap) + struct vop_mkdir_args /* { + struct vnode *a_dvp; + struct vnode **a_vpp; + struct componentname *a_cnp; + struct vattr *a_vap; + } */ *ap; +{ + register struct vnode *dvp = ap->a_dvp; + register struct vattr *vap = ap->a_vap; + register struct componentname *cnp = ap->a_cnp; + register struct inode *ip, *dp; + struct vnode *tvp; + struct dirtemplate dirtemplate, *dtp; + struct timeval tv; + int error, dmode; + +#ifdef DIAGNOSTIC + if ((cnp->cn_flags & HASBUF) == 0) + panic("ufs_mkdir: no name"); +#endif + dp = VTOI(dvp); + if ((nlink_t)dp->i_nlink >= LINK_MAX) { + error = EMLINK; + goto out; + } + dmode = vap->va_mode & 0777; + dmode |= IFDIR; + /* + * Must simulate part of ext2_makeinode here to acquire the inode, + * but not have it entered in the parent directory. The entry is + * made later after writing "." and ".." entries. + */ + error = UFS_VALLOC(dvp, dmode, cnp->cn_cred, &tvp); + if (error) + goto out; + ip = VTOI(tvp); + ip->i_gid = dp->i_gid; +#ifdef SUIDDIR + { +#ifdef QUOTA + struct ucred ucred, *ucp; + ucp = cnp->cn_cred; +#endif I + /* + * if we are hacking owners here, (only do this where told to) + * and we are not giving it TOO root, (would subvert quotas) + * then go ahead and give it to the other user. + * The new directory also inherits the SUID bit. + * If user's UID an ddir UID are the same, + * 'give it away' so that the SUID is still forced on. + */ + if ( (dvp->v_mount->mnt_flag & MNT_SUIDDIR) && + (dp->i_mode & ISUID) && dp->i_uid) { + dmode |= ISUID; + ip->i_uid = dp->i_uid; +#ifdef QUOTA + if (pdir->i_uid != cnp->cn_cred->cr_uid) { + /* + * make sure the correct user gets charged + * for the space. + * Make a dummy credential for the victim. + * XXX This seems to never be accessed out of + * our context so a stack variable is ok. + */ + ucred.cr_ref = 1; + ucred.cr_uid = ip->i_uid; + ucred.cr_ngroups = 1; + ucred.cr_groups[0] = dp->i_gid; + ucp = *ucred; + } +#endif I + } else { + ip->i_uid = cnp->cn_cred->cr_uid; + } +#ifdef QUOTA + if ((error = getinoquota(ip)) || + (error = chkiq(ip, 1, ucp, 0))) { + free(cnp->cn_pnbuf, M_NAMEI); + VOP_VFREE(tvp, ip->i_number, dmode); + vput(tvp); + vput(dvp); + return (error); + } +#endif + } +#else + ip->i_uid = cnp->cn_cred->cr_uid; +#ifdef QUOTA + if ((error = getinoquota(ip)) || + (error = chkiq(ip, 1, cnp->cn_cred, 0))) { + zfree(namei_zone, cnp->cn_pnbuf); + UFS_VFREE(tvp, ip->i_number, dmode); + vput(tvp); + vput(dvp); + return (error); + } +#endif +#endif + ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE; + ip->i_mode = dmode; + tvp->v_type = VDIR; /* Rest init'd in getnewvnode(). */ + ip->i_nlink = 2; + if (cnp->cn_flags & ISWHITEOUT) + ip->i_flags |= UF_OPAQUE; + gettime(&tv); + error = UFS_UPDATE(tvp, &tv, &tv, 1); + + /* + * Bump link count in parent directory + * to reflect work done below. Should + * be done before reference is created + * so reparation is possible if we crash. + */ + dp->i_nlink++; + dp->i_flag |= IN_CHANGE; + error = UFS_UPDATE(dvp, &tv, &tv, 1); + if (error) + goto bad; + + /* Initialize directory with "." and ".." from static template. */ + dtp = (struct dirtemplate *)&omastertemplate; + dirtemplate = *dtp; + dirtemplate.dot_ino = ip->i_number; + dirtemplate.dotdot_ino = dp->i_number; + /* note that in ext2 DIRBLKSIZ == blocksize, not DEV_BSIZE + * so let's just redefine it - for this function only + */ +#undef DIRBLKSIZ +#define DIRBLKSIZ VTOI(dvp)->i_e2fs->s_blocksize + dirtemplate.dotdot_reclen = DIRBLKSIZ - 12; + error = vn_rdwr(UIO_WRITE, tvp, (caddr_t)&dirtemplate, + sizeof (dirtemplate), (off_t)0, UIO_SYSSPACE, + IO_NODELOCKED|IO_SYNC, cnp->cn_cred, (int *)0, (struct proc *)0); + if (error) { + dp->i_nlink--; + dp->i_flag |= IN_CHANGE; + goto bad; + } + if (DIRBLKSIZ > VFSTOUFS(dvp->v_mount)->um_mountp->mnt_stat.f_bsize) + panic("ufs_mkdir: blksize"); /* XXX should grow with balloc() */ + else { + ip->i_size = DIRBLKSIZ; + ip->i_flag |= IN_CHANGE; + } + + /* Directory set up, now install it's entry in the parent directory. */ + error = ext2_direnter(ip, dvp, cnp); + if (error) { + dp->i_nlink--; + dp->i_flag |= IN_CHANGE; + } +bad: + /* + * No need to do an explicit VOP_TRUNCATE here, vrele will do this + * for us because we set the link count to 0. + */ + if (error) { + ip->i_nlink = 0; + ip->i_flag |= IN_CHANGE; + vput(tvp); + } else + *ap->a_vpp = tvp; +out: + zfree(namei_zone, cnp->cn_pnbuf); + vput(dvp); + return (error); +#undef DIRBLKSIZ +#define DIRBLKSIZ DEV_BSIZE +} + +/* + * Rmdir system call. + */ +static int +ext2_rmdir(ap) + struct vop_rmdir_args /* { + struct vnode *a_dvp; + struct vnode *a_vp; + struct componentname *a_cnp; + } */ *ap; +{ + struct vnode *vp = ap->a_vp; + struct vnode *dvp = ap->a_dvp; + struct componentname *cnp = ap->a_cnp; + struct inode *ip, *dp; + int error; + + ip = VTOI(vp); + dp = VTOI(dvp); + + /* + * Verify 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 || !ext2_dirempty(ip, dp->i_number, cnp->cn_cred)) { + error = ENOTEMPTY; + goto out; + } + if ((dp->i_flags & APPEND) + || (ip->i_flags & (NOUNLINK | 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 = ext2_dirremove(dvp, cnp); + if (error) + goto out; + dp->i_nlink--; + dp->i_flag |= IN_CHANGE; + cache_purge(dvp); + vput(dvp); + dvp = NULL; + /* + * Truncate inode. The only stuff left + * in the directory is "." and "..". The + * "." reference is inconsequential since + * we're quashing it. The ".." reference + * has already been adjusted above. We've + * removed the "." reference and the reference + * in the parent directory, but there may be + * other hard links so decrement by 2 and + * worry about them later. + */ + ip->i_nlink -= 2; + error = UFS_TRUNCATE(vp, (off_t)0, IO_SYNC, cnp->cn_cred, + cnp->cn_proc); + cache_purge(ITOV(ip)); +out: + if (dvp) + vput(dvp); + vput(vp); + return (error); +} + +/* + * symlink -- make a symbolic link + */ +static int +ext2_symlink(ap) + struct vop_symlink_args /* { + struct vnode *a_dvp; + struct vnode **a_vpp; + struct componentname *a_cnp; + struct vattr *a_vap; + char *a_target; + } */ *ap; +{ + register struct vnode *vp, **vpp = ap->a_vpp; + register struct inode *ip; + int len, error; + + error = ext2_makeinode(IFLNK | ap->a_vap->va_mode, ap->a_dvp, + vpp, ap->a_cnp); + if (error) + return (error); + vp = *vpp; + len = strlen(ap->a_target); + if (len < vp->v_mount->mnt_maxsymlinklen) { + ip = VTOI(vp); + bcopy(ap->a_target, (char *)ip->i_shortlink, len); + ip->i_size = len; + ip->i_flag |= IN_CHANGE | IN_UPDATE; + } else + error = vn_rdwr(UIO_WRITE, vp, ap->a_target, len, (off_t)0, + UIO_SYSSPACE, IO_NODELOCKED, ap->a_cnp->cn_cred, (int *)0, + (struct proc *)0); + vput(vp); + return (error); +} + + +/* + * Rename system call. + * See comments in sys/ufs/ufs/ufs_vnops.c + */ +static int +ext2_rename(ap) + 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; +{ + struct vnode *tvp = ap->a_tvp; + register 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 proc *p = fcnp->cn_proc; + struct inode *ip, *xp, *dp; + struct dirtemplate dirbuf; + struct timeval tv; + int doingdirectory = 0, oldparent = 0, newparent = 0; + int error = 0; + u_char namlen; + +#ifdef DIAGNOSTIC + if ((tcnp->cn_flags & HASBUF) == 0 || + (fcnp->cn_flags & HASBUF) == 0) + panic("ufs_rename: no name"); +#endif + /* + * 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); + } + + if (tvp && ((VTOI(tvp)->i_flags & (NOUNLINK | IMMUTABLE | APPEND)) || + (VTOI(tdvp)->i_flags & APPEND))) { + error = EPERM; + goto abortit; + } + + /* + * Check if just deleting a link name or if we've lost a race. + * If another process completes the same rename after we've looked + * up the source and have blocked looking up the target, then the + * source and target inodes may be identical now although the + * names were never linked. + */ + if (fvp == tvp) { + if (fvp->v_type == VDIR) { + /* + * Linked directories are impossible, so we must + * have lost the race. Pretend that the rename + * completed before the lookup. + */ +#ifdef UFS_RENAME_DEBUG + printf("ufs_rename: fvp == tvp for directories\n"); +#endif + error = ENOENT; + goto abortit; + } + + /* Release destination completely. */ + VOP_ABORTOP(tdvp, tcnp); + vput(tdvp); + vput(tvp); + + /* + * Delete source. There is another race now that everything + * is unlocked, but this doesn't cause any new complications. + * Relookup() may find a file that is unrelated to the + * original one, or it may fail. Too bad. + */ + vrele(fdvp); + vrele(fvp); + fcnp->cn_flags &= ~MODMASK; + fcnp->cn_flags |= LOCKPARENT | LOCKLEAF; + if ((fcnp->cn_flags & SAVESTART) == 0) + panic("ufs_rename: lost from startdir"); + fcnp->cn_nameiop = DELETE; + VREF(fdvp); + error = relookup(fdvp, &fvp, fcnp); + if (error == 0) + vrele(fdvp); + if (fvp == NULL) { +#ifdef UFS_RENAME_DEBUG + printf("ufs_rename: from name disappeared\n"); +#endif + return (ENOENT); + } + return (VOP_REMOVE(fdvp, fvp, fcnp)); + } + if (error = vn_lock(fvp, LK_EXCLUSIVE, p)) + goto abortit; + dp = VTOI(fdvp); + ip = VTOI(fvp); + if ((ip->i_flags & (NOUNLINK | IMMUTABLE | APPEND)) + || (dp->i_flags & APPEND)) { + VOP_UNLOCK(fvp, 0, p); + error = EPERM; + goto abortit; + } + if ((ip->i_mode & IFMT) == IFDIR) { + /* + * Avoid ".", "..", and aliases of "." for obvious reasons. + */ + if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') || + dp == ip || (fcnp->cn_flags | tcnp->cn_flags) & ISDOTDOT || + (ip->i_flag & IN_RENAME)) { + VOP_UNLOCK(fvp, 0, p); + error = EINVAL; + goto abortit; + } + ip->i_flag |= IN_RENAME; + oldparent = dp->i_number; + doingdirectory++; + } + vrele(fdvp); + + /* + * 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_nlink++; + ip->i_flag |= IN_CHANGE; + gettime(&tv); + if (error = UFS_UPDATE(fvp, &tv, &tv, 1)) { + VOP_UNLOCK(fvp, 0, p); + goto bad; + } + + /* + * If ".." must be changed (ie the directory gets a new + * parent) then the source directory must not be in the + * directory heirarchy 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, tcnp->cn_proc); + VOP_UNLOCK(fvp, 0, p); + 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); + error = ext2_checkpath(ip, dp, tcnp->cn_cred); + if (error) + goto out; + if ((tcnp->cn_flags & SAVESTART) == 0) + panic("ufs_rename: lost to startdir"); + VREF(tdvp); + error = relookup(tdvp, &tvp, tcnp); + if (error) + goto out; + vrele(tdvp); + 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("ufs_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_nlink >= LINK_MAX) { + error = EMLINK; + goto bad; + } + dp->i_nlink++; + dp->i_flag |= IN_CHANGE; + error = UFS_UPDATE(tdvp, &tv, &tv, 1); + if (error) + goto bad; + } + error = ext2_direnter(ip, tdvp, tcnp); + if (error) { + if (doingdirectory && newparent) { + dp->i_nlink--; + dp->i_flag |= IN_CHANGE; + (void)UFS_UPDATE(tdvp, &tv, &tv, 1); + } + goto bad; + } + vput(tdvp); + } else { + if (xp->i_dev != dp->i_dev || xp->i_dev != ip->i_dev) + panic("ufs_rename: EXDEV"); + /* + * Short circuit rename(foo, foo). + */ + if (xp->i_number == ip->i_number) + panic("ufs_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_mode & S_ISTXT) && tcnp->cn_cred->cr_uid != 0 && + tcnp->cn_cred->cr_uid != dp->i_uid && + xp->i_uid != tcnp->cn_cred->cr_uid) { + 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_mode&IFMT) == IFDIR) { + if (! ext2_dirempty(xp, dp->i_number, tcnp->cn_cred) || + xp->i_nlink > 2) { + error = ENOTEMPTY; + goto bad; + } + if (!doingdirectory) { + error = ENOTDIR; + goto bad; + } + cache_purge(tdvp); + } else if (doingdirectory) { + error = EISDIR; + goto bad; + } + error = ext2_dirrewrite(dp, ip, tcnp); + if (error) + 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_nlink--; + dp->i_flag |= IN_CHANGE; + } + vput(tdvp); + /* + * 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_nlink--; + if (doingdirectory) { + if (--xp->i_nlink != 0) + panic("ufs_rename: linked directory"); + error = UFS_TRUNCATE(tvp, (off_t)0, IO_SYNC, + tcnp->cn_cred, tcnp->cn_proc); + } + xp->i_flag |= IN_CHANGE; + vput(tvp); + xp = NULL; + } + + /* + * 3) Unlink the source. + */ + fcnp->cn_flags &= ~MODMASK; + fcnp->cn_flags |= LOCKPARENT | LOCKLEAF; + if ((fcnp->cn_flags & SAVESTART) == 0) + panic("ufs_rename: lost from startdir"); + VREF(fdvp); + error = relookup(fdvp, &fvp, fcnp); + if (error == 0) + vrele(fdvp); + if (fvp != NULL) { + xp = VTOI(fvp); + dp = VTOI(fdvp); + } else { + /* + * From name has disappeared. + */ + if (doingdirectory) + panic("ufs_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 IN_RENAME flag ensures that it cannot be moved by another + * rename. + */ + if (xp != ip) { + if (doingdirectory) + panic("ufs_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) { + dp->i_nlink--; + dp->i_flag |= IN_CHANGE; + error = vn_rdwr(UIO_READ, fvp, (caddr_t)&dirbuf, + sizeof (struct dirtemplate), (off_t)0, + UIO_SYSSPACE, IO_NODELOCKED, + tcnp->cn_cred, (int *)0, (struct proc *)0); + if (error == 0) { + namlen = ((struct odirtemplate *) + &dirbuf)->dotdot_namlen; + if (namlen != 2 || + dirbuf.dotdot_name[0] != '.' || + dirbuf.dotdot_name[1] != '.') { + ufs_dirbad(xp, (doff_t)12, + "rename: mangled dir"); + } else { + dirbuf.dotdot_ino = newparent; + (void) vn_rdwr(UIO_WRITE, fvp, + (caddr_t)&dirbuf, + sizeof (struct dirtemplate), + (off_t)0, UIO_SYSSPACE, + IO_NODELOCKED|IO_SYNC, + tcnp->cn_cred, (int *)0, + (struct proc *)0); + cache_purge(fdvp); + } + } + } + error = ext2_dirremove(fdvp, fcnp); + if (!error) { + xp->i_nlink--; + xp->i_flag |= IN_CHANGE; + } + xp->i_flag &= ~IN_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, p) == 0) { + ip->i_nlink--; + ip->i_flag |= IN_CHANGE; + ip->i_flag &= ~IN_RENAME; + vput(fvp); + } else + vrele(fvp); + return (error); +} + +/* + * link vnode call + */ +static int +ext2_link(ap) + struct vop_link_args /* { + struct vnode *a_tdvp; + struct vnode *a_vp; + struct componentname *a_cnp; + } */ *ap; +{ + struct vnode *vp = ap->a_vp; + struct vnode *tdvp = ap->a_tdvp; + struct componentname *cnp = ap->a_cnp; + struct proc *p = cnp->cn_proc; + struct inode *ip; + struct timeval tv; + int error; + +#ifdef DIAGNOSTIC + if ((cnp->cn_flags & HASBUF) == 0) + panic("ufs_link: no name"); +#endif + if (tdvp->v_mount != vp->v_mount) { + VOP_ABORTOP(tdvp, cnp); + error = EXDEV; + goto out2; + } + if (tdvp != vp && (error = vn_lock(vp, LK_EXCLUSIVE, p))) { + VOP_ABORTOP(tdvp, cnp); + goto out2; + } + ip = VTOI(vp); + if ((nlink_t)ip->i_nlink >= LINK_MAX) { + VOP_ABORTOP(tdvp, cnp); + error = EMLINK; + goto out1; + } + if (ip->i_flags & (IMMUTABLE | APPEND)) { + VOP_ABORTOP(tdvp, cnp); + error = EPERM; + goto out1; + } + ip->i_nlink++; + ip->i_flag |= IN_CHANGE; + gettime(&tv); + error = UFS_UPDATE(vp, &tv, &tv, 1); + if (!error) { + error = ext2_direnter(ip, tdvp, cnp); + } + + if (error) { + ip->i_nlink--; + ip->i_flag |= IN_CHANGE; + } + zfree(namei_zone, cnp->cn_pnbuf); +out1: + if (tdvp != vp) + VOP_UNLOCK(vp, 0, p); +out2: + vput(tdvp); + return (error); +} + +static int +ext2_remove(ap) + struct vop_remove_args /* { + struct vnode *a_dvp; + struct vnode *a_vp; + struct componentname *a_cnp; + } */ *ap; +{ + struct inode *ip; + struct vnode *vp = ap->a_vp; + struct vnode *dvp = ap->a_dvp; + int error; + + ip = VTOI(vp); + if ((ip->i_flags & (NOUNLINK | IMMUTABLE | APPEND)) || + (VTOI(dvp)->i_flags & APPEND)) { + error = EPERM; + goto out; + } + error = ext2_dirremove(dvp, ap->a_cnp); + if (error == 0) { + ip->i_nlink--; + ip->i_flag |= IN_CHANGE; + } +out: + if (dvp == vp) + vrele(vp); + else + vput(vp); + vput(dvp); + return (error); +} + /* * Synch an open file. */ diff --git a/sys/gnu/fs/ext2fs/ext2_vnops.c b/sys/gnu/fs/ext2fs/ext2_vnops.c index 8a372d5..d610ad4 100644 --- a/sys/gnu/fs/ext2fs/ext2_vnops.c +++ b/sys/gnu/fs/ext2fs/ext2_vnops.c @@ -47,6 +47,7 @@ #include <sys/param.h> #include <sys/systm.h> #include <sys/resourcevar.h> +#include <sys/malloc.h> #include <sys/kernel.h> #include <sys/stat.h> #include <sys/buf.h> @@ -54,6 +55,7 @@ #include <sys/mount.h> #include <sys/time.h> #include <sys/vnode.h> +#include <sys/namei.h> #include <vm/vm.h> #include <vm/vm_extern.h> @@ -63,6 +65,7 @@ #else #include <sys/signalvar.h> #endif +#include <ufs/ufs/dir.h> #include <ufs/ufs/quota.h> #include <ufs/ufs/inode.h> #include <ufs/ufs/ufsmount.h> @@ -72,9 +75,27 @@ #include <gnu/ext2fs/fs.h> #include <gnu/ext2fs/ext2_extern.h> +static int ext2_makeinode __P((int mode, struct vnode *, struct vnode **, struct componentname *)); + static int ext2_fsync __P((struct vop_fsync_args *)); static int ext2_read __P((struct vop_read_args *)); static int ext2_write __P((struct vop_write_args *)); +static int ext2_remove __P((struct vop_remove_args *)); +static int ext2_link __P((struct vop_link_args *)); +static int ext2_rename __P((struct vop_rename_args *)); +static int ext2_mkdir __P((struct vop_mkdir_args *)); +static int ext2_rmdir __P((struct vop_rmdir_args *)); +static int ext2_create __P((struct vop_create_args *)); +static int ext2_mknod __P((struct vop_mknod_args *)); +static int ext2_symlink __P((struct vop_symlink_args *)); + +/* + * A virgin directory (no blushing please). + */ +static struct odirtemplate omastertemplate = { + 0, 12, 1, { '.', 0 }, + 0, DIRBLKSIZ - 12, 2, { '.', '.', 0 } +}; /* Global vfs data structures for ufs. */ vop_t **ext2_vnodeop_p; @@ -88,6 +109,14 @@ static struct vnodeopv_entry_desc ext2_vnodeop_entries[] = { { &vop_readdir_desc, (vop_t *) ext2_readdir }, { &vop_reallocblks_desc, (vop_t *) ext2_reallocblks }, { &vop_write_desc, (vop_t *) ext2_write }, + { &vop_remove_desc, (vop_t *) ext2_remove }, + { &vop_link_desc, (vop_t *) ext2_link }, + { &vop_rename_desc, (vop_t *) ext2_rename }, + { &vop_mkdir_desc, (vop_t *) ext2_mkdir }, + { &vop_rmdir_desc, (vop_t *) ext2_rmdir }, + { &vop_create_desc, (vop_t *) ext2_create }, + { &vop_mknod_desc, (vop_t *) ext2_mknod }, + { &vop_symlink_desc, (vop_t *) ext2_symlink }, { NULL, NULL } }; static struct vnodeopv_desc ext2fs_vnodeop_opv_desc = @@ -121,6 +150,997 @@ static struct vnodeopv_desc ext2fs_fifoop_opv_desc = #include <gnu/ext2fs/ext2_readwrite.c> + +/* + * Allocate a new inode. + */ +int +ext2_makeinode(mode, dvp, vpp, cnp) + int mode; + struct vnode *dvp; + struct vnode **vpp; + struct componentname *cnp; +{ + register struct inode *ip, *pdir; + struct timeval tv; + struct vnode *tvp; + int error; + + pdir = VTOI(dvp); +#ifdef DIAGNOSTIC + if ((cnp->cn_flags & HASBUF) == 0) + panic("ext2_makeinode: no name"); +#endif + *vpp = NULL; + if ((mode & IFMT) == 0) + mode |= IFREG; + + error = UFS_VALLOC(dvp, mode, cnp->cn_cred, &tvp); + if (error) { + zfree(namei_zone, cnp->cn_pnbuf); + vput(dvp); + return (error); + } + ip = VTOI(tvp); + ip->i_gid = pdir->i_gid; +#ifdef SUIDDIR + { +#ifdef QUOTA + struct ucred ucred, *ucp; + ucp = cnp->cn_cred; +#endif I + /* + * if we are + * not the owner of the directory, + * and we are hacking owners here, (only do this where told to) + * and we are not giving it TOO root, (would subvert quotas) + * then go ahead and give it to the other user. + * Note that this drops off the execute bits for security. + */ + if ( (dvp->v_mount->mnt_flag & MNT_SUIDDIR) && + (pdir->i_mode & ISUID) && + (pdir->i_uid != cnp->cn_cred->cr_uid) && pdir->i_uid) { + ip->i_uid = pdir->i_uid; + mode &= ~07111; +#ifdef QUOTA + /* + * make sure the correct user gets charged + * for the space. + * Quickly knock up a dummy credential for the victim. + * XXX This seems to never be accessed out of our + * context so a stack variable is ok. + */ + ucred.cr_ref = 1; + ucred.cr_uid = ip->i_uid; + ucred.cr_ngroups = 1; + ucred.cr_groups[0] = pdir->i_gid; + ucp = *ucred; +#endif I + } else { + ip->i_uid = cnp->cn_cred->cr_uid; + } + +#ifdef QUOTA + if ((error = getinoquota(ip)) || + (error = chkiq(ip, 1, ucp, 0))) { + free(cnp->cn_pnbuf, M_NAMEI); + VOP_VFREE(tvp, ip->i_number, mode); + vput(tvp); + vput(dvp); + return (error); + } +#endif + } +#else + ip->i_uid = cnp->cn_cred->cr_uid; +#ifdef QUOTA + if ((error = getinoquota(ip)) || + (error = chkiq(ip, 1, cnp->cn_cred, 0))) { + zfree(namei_zone, cnp->cn_pnbuf); + UFS_VFREE(tvp, ip->i_number, mode); + vput(tvp); + vput(dvp); + return (error); + } +#endif +#endif + ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE; + ip->i_mode = mode; + tvp->v_type = IFTOVT(mode); /* Rest init'd in getnewvnode(). */ + ip->i_nlink = 1; + if ((ip->i_mode & ISGID) && !groupmember(ip->i_gid, cnp->cn_cred) && + suser(cnp->cn_cred, NULL)) + ip->i_mode &= ~ISGID; + + if (cnp->cn_flags & ISWHITEOUT) + ip->i_flags |= UF_OPAQUE; + + /* + * Make sure inode goes to disk before directory entry. + */ + gettime(&tv); + error = UFS_UPDATE(tvp, &tv, &tv, 1); + if (error) + goto bad; + error = ext2_direnter(ip, dvp, cnp); + if (error) + goto bad; + + if ((cnp->cn_flags & SAVESTART) == 0) + zfree(namei_zone, cnp->cn_pnbuf); + vput(dvp); + *vpp = tvp; + return (0); + +bad: + /* + * Write error occurred trying to update the inode + * or the directory so must deallocate the inode. + */ + zfree(namei_zone, cnp->cn_pnbuf); + vput(dvp); + ip->i_nlink = 0; + ip->i_flag |= IN_CHANGE; + vput(tvp); + return (error); +} + +/* + * Create a regular file + */ +static int +ext2_create(ap) + struct vop_create_args /* { + struct vnode *a_dvp; + struct vnode **a_vpp; + struct componentname *a_cnp; + struct vattr *a_vap; + } */ *ap; +{ + int error; + + error = + ext2_makeinode(MAKEIMODE(ap->a_vap->va_type, ap->a_vap->va_mode), + ap->a_dvp, ap->a_vpp, ap->a_cnp); + if (error) + return (error); + return (0); +} + +/* + * Mknod vnode call + */ +/* ARGSUSED */ +static int +ext2_mknod(ap) + struct vop_mknod_args /* { + struct vnode *a_dvp; + struct vnode **a_vpp; + struct componentname *a_cnp; + struct vattr *a_vap; + } */ *ap; +{ + struct vattr *vap = ap->a_vap; + struct vnode **vpp = ap->a_vpp; + struct inode *ip; + int error; + + error = ext2_makeinode(MAKEIMODE(vap->va_type, vap->va_mode), + ap->a_dvp, vpp, ap->a_cnp); + if (error) + return (error); + ip = VTOI(*vpp); + ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE; + if (vap->va_rdev != VNOVAL) { + /* + * Want to be able to use this to make badblock + * inodes, so don't truncate the dev number. + */ + ip->i_rdev = vap->va_rdev; + } + /* + * Remove inode so that it will be reloaded by VFS_VGET and + * checked to see if it is an alias of an existing entry in + * the inode cache. + */ + vput(*vpp); + (*vpp)->v_type = VNON; + vgone(*vpp); + *vpp = 0; + return (0); +} + + +/* + * Mkdir system call + */ +static int +ext2_mkdir(ap) + struct vop_mkdir_args /* { + struct vnode *a_dvp; + struct vnode **a_vpp; + struct componentname *a_cnp; + struct vattr *a_vap; + } */ *ap; +{ + register struct vnode *dvp = ap->a_dvp; + register struct vattr *vap = ap->a_vap; + register struct componentname *cnp = ap->a_cnp; + register struct inode *ip, *dp; + struct vnode *tvp; + struct dirtemplate dirtemplate, *dtp; + struct timeval tv; + int error, dmode; + +#ifdef DIAGNOSTIC + if ((cnp->cn_flags & HASBUF) == 0) + panic("ufs_mkdir: no name"); +#endif + dp = VTOI(dvp); + if ((nlink_t)dp->i_nlink >= LINK_MAX) { + error = EMLINK; + goto out; + } + dmode = vap->va_mode & 0777; + dmode |= IFDIR; + /* + * Must simulate part of ext2_makeinode here to acquire the inode, + * but not have it entered in the parent directory. The entry is + * made later after writing "." and ".." entries. + */ + error = UFS_VALLOC(dvp, dmode, cnp->cn_cred, &tvp); + if (error) + goto out; + ip = VTOI(tvp); + ip->i_gid = dp->i_gid; +#ifdef SUIDDIR + { +#ifdef QUOTA + struct ucred ucred, *ucp; + ucp = cnp->cn_cred; +#endif I + /* + * if we are hacking owners here, (only do this where told to) + * and we are not giving it TOO root, (would subvert quotas) + * then go ahead and give it to the other user. + * The new directory also inherits the SUID bit. + * If user's UID an ddir UID are the same, + * 'give it away' so that the SUID is still forced on. + */ + if ( (dvp->v_mount->mnt_flag & MNT_SUIDDIR) && + (dp->i_mode & ISUID) && dp->i_uid) { + dmode |= ISUID; + ip->i_uid = dp->i_uid; +#ifdef QUOTA + if (pdir->i_uid != cnp->cn_cred->cr_uid) { + /* + * make sure the correct user gets charged + * for the space. + * Make a dummy credential for the victim. + * XXX This seems to never be accessed out of + * our context so a stack variable is ok. + */ + ucred.cr_ref = 1; + ucred.cr_uid = ip->i_uid; + ucred.cr_ngroups = 1; + ucred.cr_groups[0] = dp->i_gid; + ucp = *ucred; + } +#endif I + } else { + ip->i_uid = cnp->cn_cred->cr_uid; + } +#ifdef QUOTA + if ((error = getinoquota(ip)) || + (error = chkiq(ip, 1, ucp, 0))) { + free(cnp->cn_pnbuf, M_NAMEI); + VOP_VFREE(tvp, ip->i_number, dmode); + vput(tvp); + vput(dvp); + return (error); + } +#endif + } +#else + ip->i_uid = cnp->cn_cred->cr_uid; +#ifdef QUOTA + if ((error = getinoquota(ip)) || + (error = chkiq(ip, 1, cnp->cn_cred, 0))) { + zfree(namei_zone, cnp->cn_pnbuf); + UFS_VFREE(tvp, ip->i_number, dmode); + vput(tvp); + vput(dvp); + return (error); + } +#endif +#endif + ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE; + ip->i_mode = dmode; + tvp->v_type = VDIR; /* Rest init'd in getnewvnode(). */ + ip->i_nlink = 2; + if (cnp->cn_flags & ISWHITEOUT) + ip->i_flags |= UF_OPAQUE; + gettime(&tv); + error = UFS_UPDATE(tvp, &tv, &tv, 1); + + /* + * Bump link count in parent directory + * to reflect work done below. Should + * be done before reference is created + * so reparation is possible if we crash. + */ + dp->i_nlink++; + dp->i_flag |= IN_CHANGE; + error = UFS_UPDATE(dvp, &tv, &tv, 1); + if (error) + goto bad; + + /* Initialize directory with "." and ".." from static template. */ + dtp = (struct dirtemplate *)&omastertemplate; + dirtemplate = *dtp; + dirtemplate.dot_ino = ip->i_number; + dirtemplate.dotdot_ino = dp->i_number; + /* note that in ext2 DIRBLKSIZ == blocksize, not DEV_BSIZE + * so let's just redefine it - for this function only + */ +#undef DIRBLKSIZ +#define DIRBLKSIZ VTOI(dvp)->i_e2fs->s_blocksize + dirtemplate.dotdot_reclen = DIRBLKSIZ - 12; + error = vn_rdwr(UIO_WRITE, tvp, (caddr_t)&dirtemplate, + sizeof (dirtemplate), (off_t)0, UIO_SYSSPACE, + IO_NODELOCKED|IO_SYNC, cnp->cn_cred, (int *)0, (struct proc *)0); + if (error) { + dp->i_nlink--; + dp->i_flag |= IN_CHANGE; + goto bad; + } + if (DIRBLKSIZ > VFSTOUFS(dvp->v_mount)->um_mountp->mnt_stat.f_bsize) + panic("ufs_mkdir: blksize"); /* XXX should grow with balloc() */ + else { + ip->i_size = DIRBLKSIZ; + ip->i_flag |= IN_CHANGE; + } + + /* Directory set up, now install it's entry in the parent directory. */ + error = ext2_direnter(ip, dvp, cnp); + if (error) { + dp->i_nlink--; + dp->i_flag |= IN_CHANGE; + } +bad: + /* + * No need to do an explicit VOP_TRUNCATE here, vrele will do this + * for us because we set the link count to 0. + */ + if (error) { + ip->i_nlink = 0; + ip->i_flag |= IN_CHANGE; + vput(tvp); + } else + *ap->a_vpp = tvp; +out: + zfree(namei_zone, cnp->cn_pnbuf); + vput(dvp); + return (error); +#undef DIRBLKSIZ +#define DIRBLKSIZ DEV_BSIZE +} + +/* + * Rmdir system call. + */ +static int +ext2_rmdir(ap) + struct vop_rmdir_args /* { + struct vnode *a_dvp; + struct vnode *a_vp; + struct componentname *a_cnp; + } */ *ap; +{ + struct vnode *vp = ap->a_vp; + struct vnode *dvp = ap->a_dvp; + struct componentname *cnp = ap->a_cnp; + struct inode *ip, *dp; + int error; + + ip = VTOI(vp); + dp = VTOI(dvp); + + /* + * Verify 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 || !ext2_dirempty(ip, dp->i_number, cnp->cn_cred)) { + error = ENOTEMPTY; + goto out; + } + if ((dp->i_flags & APPEND) + || (ip->i_flags & (NOUNLINK | 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 = ext2_dirremove(dvp, cnp); + if (error) + goto out; + dp->i_nlink--; + dp->i_flag |= IN_CHANGE; + cache_purge(dvp); + vput(dvp); + dvp = NULL; + /* + * Truncate inode. The only stuff left + * in the directory is "." and "..". The + * "." reference is inconsequential since + * we're quashing it. The ".." reference + * has already been adjusted above. We've + * removed the "." reference and the reference + * in the parent directory, but there may be + * other hard links so decrement by 2 and + * worry about them later. + */ + ip->i_nlink -= 2; + error = UFS_TRUNCATE(vp, (off_t)0, IO_SYNC, cnp->cn_cred, + cnp->cn_proc); + cache_purge(ITOV(ip)); +out: + if (dvp) + vput(dvp); + vput(vp); + return (error); +} + +/* + * symlink -- make a symbolic link + */ +static int +ext2_symlink(ap) + struct vop_symlink_args /* { + struct vnode *a_dvp; + struct vnode **a_vpp; + struct componentname *a_cnp; + struct vattr *a_vap; + char *a_target; + } */ *ap; +{ + register struct vnode *vp, **vpp = ap->a_vpp; + register struct inode *ip; + int len, error; + + error = ext2_makeinode(IFLNK | ap->a_vap->va_mode, ap->a_dvp, + vpp, ap->a_cnp); + if (error) + return (error); + vp = *vpp; + len = strlen(ap->a_target); + if (len < vp->v_mount->mnt_maxsymlinklen) { + ip = VTOI(vp); + bcopy(ap->a_target, (char *)ip->i_shortlink, len); + ip->i_size = len; + ip->i_flag |= IN_CHANGE | IN_UPDATE; + } else + error = vn_rdwr(UIO_WRITE, vp, ap->a_target, len, (off_t)0, + UIO_SYSSPACE, IO_NODELOCKED, ap->a_cnp->cn_cred, (int *)0, + (struct proc *)0); + vput(vp); + return (error); +} + + +/* + * Rename system call. + * See comments in sys/ufs/ufs/ufs_vnops.c + */ +static int +ext2_rename(ap) + 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; +{ + struct vnode *tvp = ap->a_tvp; + register 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 proc *p = fcnp->cn_proc; + struct inode *ip, *xp, *dp; + struct dirtemplate dirbuf; + struct timeval tv; + int doingdirectory = 0, oldparent = 0, newparent = 0; + int error = 0; + u_char namlen; + +#ifdef DIAGNOSTIC + if ((tcnp->cn_flags & HASBUF) == 0 || + (fcnp->cn_flags & HASBUF) == 0) + panic("ufs_rename: no name"); +#endif + /* + * 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); + } + + if (tvp && ((VTOI(tvp)->i_flags & (NOUNLINK | IMMUTABLE | APPEND)) || + (VTOI(tdvp)->i_flags & APPEND))) { + error = EPERM; + goto abortit; + } + + /* + * Check if just deleting a link name or if we've lost a race. + * If another process completes the same rename after we've looked + * up the source and have blocked looking up the target, then the + * source and target inodes may be identical now although the + * names were never linked. + */ + if (fvp == tvp) { + if (fvp->v_type == VDIR) { + /* + * Linked directories are impossible, so we must + * have lost the race. Pretend that the rename + * completed before the lookup. + */ +#ifdef UFS_RENAME_DEBUG + printf("ufs_rename: fvp == tvp for directories\n"); +#endif + error = ENOENT; + goto abortit; + } + + /* Release destination completely. */ + VOP_ABORTOP(tdvp, tcnp); + vput(tdvp); + vput(tvp); + + /* + * Delete source. There is another race now that everything + * is unlocked, but this doesn't cause any new complications. + * Relookup() may find a file that is unrelated to the + * original one, or it may fail. Too bad. + */ + vrele(fdvp); + vrele(fvp); + fcnp->cn_flags &= ~MODMASK; + fcnp->cn_flags |= LOCKPARENT | LOCKLEAF; + if ((fcnp->cn_flags & SAVESTART) == 0) + panic("ufs_rename: lost from startdir"); + fcnp->cn_nameiop = DELETE; + VREF(fdvp); + error = relookup(fdvp, &fvp, fcnp); + if (error == 0) + vrele(fdvp); + if (fvp == NULL) { +#ifdef UFS_RENAME_DEBUG + printf("ufs_rename: from name disappeared\n"); +#endif + return (ENOENT); + } + return (VOP_REMOVE(fdvp, fvp, fcnp)); + } + if (error = vn_lock(fvp, LK_EXCLUSIVE, p)) + goto abortit; + dp = VTOI(fdvp); + ip = VTOI(fvp); + if ((ip->i_flags & (NOUNLINK | IMMUTABLE | APPEND)) + || (dp->i_flags & APPEND)) { + VOP_UNLOCK(fvp, 0, p); + error = EPERM; + goto abortit; + } + if ((ip->i_mode & IFMT) == IFDIR) { + /* + * Avoid ".", "..", and aliases of "." for obvious reasons. + */ + if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') || + dp == ip || (fcnp->cn_flags | tcnp->cn_flags) & ISDOTDOT || + (ip->i_flag & IN_RENAME)) { + VOP_UNLOCK(fvp, 0, p); + error = EINVAL; + goto abortit; + } + ip->i_flag |= IN_RENAME; + oldparent = dp->i_number; + doingdirectory++; + } + vrele(fdvp); + + /* + * 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_nlink++; + ip->i_flag |= IN_CHANGE; + gettime(&tv); + if (error = UFS_UPDATE(fvp, &tv, &tv, 1)) { + VOP_UNLOCK(fvp, 0, p); + goto bad; + } + + /* + * If ".." must be changed (ie the directory gets a new + * parent) then the source directory must not be in the + * directory heirarchy 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, tcnp->cn_proc); + VOP_UNLOCK(fvp, 0, p); + 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); + error = ext2_checkpath(ip, dp, tcnp->cn_cred); + if (error) + goto out; + if ((tcnp->cn_flags & SAVESTART) == 0) + panic("ufs_rename: lost to startdir"); + VREF(tdvp); + error = relookup(tdvp, &tvp, tcnp); + if (error) + goto out; + vrele(tdvp); + 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("ufs_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_nlink >= LINK_MAX) { + error = EMLINK; + goto bad; + } + dp->i_nlink++; + dp->i_flag |= IN_CHANGE; + error = UFS_UPDATE(tdvp, &tv, &tv, 1); + if (error) + goto bad; + } + error = ext2_direnter(ip, tdvp, tcnp); + if (error) { + if (doingdirectory && newparent) { + dp->i_nlink--; + dp->i_flag |= IN_CHANGE; + (void)UFS_UPDATE(tdvp, &tv, &tv, 1); + } + goto bad; + } + vput(tdvp); + } else { + if (xp->i_dev != dp->i_dev || xp->i_dev != ip->i_dev) + panic("ufs_rename: EXDEV"); + /* + * Short circuit rename(foo, foo). + */ + if (xp->i_number == ip->i_number) + panic("ufs_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_mode & S_ISTXT) && tcnp->cn_cred->cr_uid != 0 && + tcnp->cn_cred->cr_uid != dp->i_uid && + xp->i_uid != tcnp->cn_cred->cr_uid) { + 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_mode&IFMT) == IFDIR) { + if (! ext2_dirempty(xp, dp->i_number, tcnp->cn_cred) || + xp->i_nlink > 2) { + error = ENOTEMPTY; + goto bad; + } + if (!doingdirectory) { + error = ENOTDIR; + goto bad; + } + cache_purge(tdvp); + } else if (doingdirectory) { + error = EISDIR; + goto bad; + } + error = ext2_dirrewrite(dp, ip, tcnp); + if (error) + 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_nlink--; + dp->i_flag |= IN_CHANGE; + } + vput(tdvp); + /* + * 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_nlink--; + if (doingdirectory) { + if (--xp->i_nlink != 0) + panic("ufs_rename: linked directory"); + error = UFS_TRUNCATE(tvp, (off_t)0, IO_SYNC, + tcnp->cn_cred, tcnp->cn_proc); + } + xp->i_flag |= IN_CHANGE; + vput(tvp); + xp = NULL; + } + + /* + * 3) Unlink the source. + */ + fcnp->cn_flags &= ~MODMASK; + fcnp->cn_flags |= LOCKPARENT | LOCKLEAF; + if ((fcnp->cn_flags & SAVESTART) == 0) + panic("ufs_rename: lost from startdir"); + VREF(fdvp); + error = relookup(fdvp, &fvp, fcnp); + if (error == 0) + vrele(fdvp); + if (fvp != NULL) { + xp = VTOI(fvp); + dp = VTOI(fdvp); + } else { + /* + * From name has disappeared. + */ + if (doingdirectory) + panic("ufs_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 IN_RENAME flag ensures that it cannot be moved by another + * rename. + */ + if (xp != ip) { + if (doingdirectory) + panic("ufs_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) { + dp->i_nlink--; + dp->i_flag |= IN_CHANGE; + error = vn_rdwr(UIO_READ, fvp, (caddr_t)&dirbuf, + sizeof (struct dirtemplate), (off_t)0, + UIO_SYSSPACE, IO_NODELOCKED, + tcnp->cn_cred, (int *)0, (struct proc *)0); + if (error == 0) { + namlen = ((struct odirtemplate *) + &dirbuf)->dotdot_namlen; + if (namlen != 2 || + dirbuf.dotdot_name[0] != '.' || + dirbuf.dotdot_name[1] != '.') { + ufs_dirbad(xp, (doff_t)12, + "rename: mangled dir"); + } else { + dirbuf.dotdot_ino = newparent; + (void) vn_rdwr(UIO_WRITE, fvp, + (caddr_t)&dirbuf, + sizeof (struct dirtemplate), + (off_t)0, UIO_SYSSPACE, + IO_NODELOCKED|IO_SYNC, + tcnp->cn_cred, (int *)0, + (struct proc *)0); + cache_purge(fdvp); + } + } + } + error = ext2_dirremove(fdvp, fcnp); + if (!error) { + xp->i_nlink--; + xp->i_flag |= IN_CHANGE; + } + xp->i_flag &= ~IN_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, p) == 0) { + ip->i_nlink--; + ip->i_flag |= IN_CHANGE; + ip->i_flag &= ~IN_RENAME; + vput(fvp); + } else + vrele(fvp); + return (error); +} + +/* + * link vnode call + */ +static int +ext2_link(ap) + struct vop_link_args /* { + struct vnode *a_tdvp; + struct vnode *a_vp; + struct componentname *a_cnp; + } */ *ap; +{ + struct vnode *vp = ap->a_vp; + struct vnode *tdvp = ap->a_tdvp; + struct componentname *cnp = ap->a_cnp; + struct proc *p = cnp->cn_proc; + struct inode *ip; + struct timeval tv; + int error; + +#ifdef DIAGNOSTIC + if ((cnp->cn_flags & HASBUF) == 0) + panic("ufs_link: no name"); +#endif + if (tdvp->v_mount != vp->v_mount) { + VOP_ABORTOP(tdvp, cnp); + error = EXDEV; + goto out2; + } + if (tdvp != vp && (error = vn_lock(vp, LK_EXCLUSIVE, p))) { + VOP_ABORTOP(tdvp, cnp); + goto out2; + } + ip = VTOI(vp); + if ((nlink_t)ip->i_nlink >= LINK_MAX) { + VOP_ABORTOP(tdvp, cnp); + error = EMLINK; + goto out1; + } + if (ip->i_flags & (IMMUTABLE | APPEND)) { + VOP_ABORTOP(tdvp, cnp); + error = EPERM; + goto out1; + } + ip->i_nlink++; + ip->i_flag |= IN_CHANGE; + gettime(&tv); + error = UFS_UPDATE(vp, &tv, &tv, 1); + if (!error) { + error = ext2_direnter(ip, tdvp, cnp); + } + + if (error) { + ip->i_nlink--; + ip->i_flag |= IN_CHANGE; + } + zfree(namei_zone, cnp->cn_pnbuf); +out1: + if (tdvp != vp) + VOP_UNLOCK(vp, 0, p); +out2: + vput(tdvp); + return (error); +} + +static int +ext2_remove(ap) + struct vop_remove_args /* { + struct vnode *a_dvp; + struct vnode *a_vp; + struct componentname *a_cnp; + } */ *ap; +{ + struct inode *ip; + struct vnode *vp = ap->a_vp; + struct vnode *dvp = ap->a_dvp; + int error; + + ip = VTOI(vp); + if ((ip->i_flags & (NOUNLINK | IMMUTABLE | APPEND)) || + (VTOI(dvp)->i_flags & APPEND)) { + error = EPERM; + goto out; + } + error = ext2_dirremove(dvp, ap->a_cnp); + if (error == 0) { + ip->i_nlink--; + ip->i_flag |= IN_CHANGE; + } +out: + if (dvp == vp) + vrele(vp); + else + vput(vp); + vput(dvp); + return (error); +} + /* * Synch an open file. */ |