/* * Perform chown operation on inode ip; * inode must be locked prior to call. */ static int ext2fs_chown(struct vnode *vp, uid_t uid, gid_t gid, struct ucred *cred, struct proc *p) { struct inode *ip = VTOI(vp); uid_t ouid; gid_t ogid; int error = 0; if (uid == (uid_t)VNOVAL) uid = ip->i_e2fs_uid; if (gid == (gid_t)VNOVAL) gid = ip->i_e2fs_gid; /* * If we don't own the file, are trying to change the owner * of the file, or are not a member of the target group, * the caller must be superuser or the call fails. */ if ((cred->cr_uid != ip->i_e2fs_uid || uid != ip->i_e2fs_uid || (gid != ip->i_e2fs_gid && !groupmember((gid_t)gid, cred))) && (error = suser_ucred(cred))) return (error); ogid = ip->i_e2fs_gid; ouid = ip->i_e2fs_uid; ip->i_e2fs_gid = gid; ip->i_e2fs_uid = uid; if (ouid != uid || ogid != gid) ip->i_flag |= IN_CHANGE; if (ouid != uid && cred->cr_uid != 0) ip->i_e2fs_mode &= ~ISUID; if (ogid != gid && cred->cr_uid != 0) ip->i_e2fs_mode &= ~ISGID; return (0); }
/* * Change the mode on a file. * Inode must be locked before calling. */ static int ext2fs_chmod(struct vnode *vp, mode_t mode, struct ucred *cred, struct proc *p) { struct inode *ip = VTOI(vp); int error; if (cred->cr_uid != ip->i_e2fs_uid && (error = suser_ucred(cred))) return (error); if (cred->cr_uid) { if (vp->v_type != VDIR && (mode & S_ISTXT)) return (EFTYPE); if (!groupmember(ip->i_e2fs_gid, cred) && (mode & ISGID)) return (EPERM); } ip->i_e2fs_mode &= ~ALLPERMS; ip->i_e2fs_mode |= (mode & ALLPERMS); ip->i_flag |= IN_CHANGE; if ((vp->v_flag & VTEXT) && (ip->i_e2fs_mode & S_ISTXT) == 0) (void) uvm_vnp_uncache(vp); return (0); }
/* * Change the mode on a file. * Inode must be locked before calling. */ int ufs_chmod(struct vnode *vp, int mode, struct ucred *cred, struct proc *p) { struct inode *ip = VTOI(vp); int error; if (cred->cr_uid != DIP(ip, uid) && (error = suser_ucred(cred))) return (error); if (cred->cr_uid) { if (vp->v_type != VDIR && (mode & S_ISTXT)) return (EFTYPE); if (!groupmember(DIP(ip, gid), cred) && (mode & ISGID)) return (EPERM); } DIP_AND(ip, mode, ~ALLPERMS); DIP_OR(ip, mode, mode & ALLPERMS); ip->i_flag |= IN_CHANGE; if ((vp->v_flag & VTEXT) && (DIP(ip, mode) & S_ISTXT) == 0) (void) uvm_vnp_uncache(vp); return (0); }
/* * Perform chown operation on inode ip; * inode must be locked prior to call. */ int ufs_chown(struct vnode *vp, uid_t uid, gid_t gid, struct ucred *cred, struct proc *p) { struct inode *ip = VTOI(vp); uid_t ouid; gid_t ogid; int error = 0; daddr64_t change; enum ufs_quota_flags quota_flags = 0; if (uid == (uid_t)VNOVAL) uid = DIP(ip, uid); if (gid == (gid_t)VNOVAL) gid = DIP(ip, gid); /* * If we don't own the file, are trying to change the owner * of the file, or are not a member of the target group, * the caller must be superuser or the call fails. */ if ((cred->cr_uid != DIP(ip, uid) || uid != DIP(ip, uid) || (gid != DIP(ip, gid) && !groupmember((gid_t)gid, cred))) && (error = suser_ucred(cred))) return (error); ogid = DIP(ip, gid); ouid = DIP(ip, uid); change = DIP(ip, blocks); if (ouid == uid) quota_flags |= UFS_QUOTA_NOUID; if (ogid == gid) quota_flags |= UFS_QUOTA_NOGID; if ((error = getinoquota(ip)) != 0) return (error); (void) ufs_quota_free_blocks2(ip, change, cred, quota_flags); (void) ufs_quota_free_inode2(ip, cred, quota_flags); (void) ufs_quota_delete(ip); DIP_ASSIGN(ip, gid, gid); DIP_ASSIGN(ip, uid, uid); if ((error = getinoquota(ip)) != 0) goto error; if ((error = ufs_quota_alloc_blocks2(ip, change, cred, quota_flags)) != 0) goto error; if ((error = ufs_quota_alloc_inode2(ip, cred , quota_flags)) != 0) { (void)ufs_quota_free_blocks2(ip, change, cred, quota_flags); goto error; } if (getinoquota(ip)) panic("chown: lost quota"); if (ouid != uid || ogid != gid) ip->i_flag |= IN_CHANGE; if (ouid != uid && cred->cr_uid != 0) DIP_AND(ip, mode, ~ISUID); if (ogid != gid && cred->cr_uid != 0) DIP_AND(ip, mode, ~ISGID); return (0); error: (void) ufs_quota_delete(ip); DIP_ASSIGN(ip, gid, ogid); DIP_ASSIGN(ip, uid, ouid); if (getinoquota(ip) == 0) { (void) ufs_quota_alloc_blocks2(ip, change, cred, quota_flags | UFS_QUOTA_FORCE); (void) ufs_quota_alloc_inode2(ip, cred, quota_flags | UFS_QUOTA_FORCE); (void) getinoquota(ip); } return (error); }
/* * Set attribute vnode op. called from several syscalls */ int ufs_setattr(void *v) { struct vop_setattr_args *ap = v; struct vattr *vap = ap->a_vap; struct vnode *vp = ap->a_vp; struct inode *ip = VTOI(vp); struct ucred *cred = ap->a_cred; struct proc *p = curproc; int error; long hint = NOTE_ATTRIB; u_quad_t oldsize; /* * Check for unsettable attributes. */ if ((vap->va_type != VNON) || (vap->va_nlink != VNOVAL) || (vap->va_fsid != VNOVAL) || (vap->va_fileid != VNOVAL) || (vap->va_blocksize != VNOVAL) || (vap->va_rdev != VNOVAL) || ((int)vap->va_bytes != VNOVAL) || (vap->va_gen != VNOVAL)) { return (EINVAL); } if (vap->va_flags != VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) return (EROFS); if (cred->cr_uid != DIP(ip, uid) && (error = suser_ucred(cred))) return (error); if (cred->cr_uid == 0) { if ((DIP(ip, flags) & (SF_IMMUTABLE | SF_APPEND)) && securelevel > 0) return (EPERM); DIP_ASSIGN(ip, flags, vap->va_flags); } else { if (DIP(ip, flags) & (SF_IMMUTABLE | SF_APPEND) || (vap->va_flags & UF_SETTABLE) != vap->va_flags) return (EPERM); DIP_AND(ip, flags, SF_SETTABLE); DIP_OR(ip, flags, vap->va_flags & UF_SETTABLE); } ip->i_flag |= IN_CHANGE; if (vap->va_flags & (IMMUTABLE | APPEND)) return (0); } if (DIP(ip, flags) & (IMMUTABLE | APPEND)) return (EPERM); /* * Go through the fields and update if not VNOVAL. */ if (vap->va_uid != (uid_t)VNOVAL || vap->va_gid != (gid_t)VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) return (EROFS); error = ufs_chown(vp, vap->va_uid, vap->va_gid, cred, p); if (error) return (error); } if (vap->va_size != VNOVAL) { oldsize = DIP(ip, size); /* * Disallow write attempts on read-only file systems; * unless the file is a socket, fifo, or a block or * character device resident on the file system. */ switch (vp->v_type) { case VDIR: return (EISDIR); case VLNK: case VREG: if (vp->v_mount->mnt_flag & MNT_RDONLY) return (EROFS); break; default: break; } if ((error = UFS_TRUNCATE(ip, vap->va_size, 0, cred)) != 0) return (error); if (vap->va_size < oldsize) hint |= NOTE_TRUNCATE; } if (vap->va_atime.tv_sec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) return (EROFS); if (cred->cr_uid != DIP(ip, uid) && (error = suser_ucred(cred)) && ((vap->va_vaflags & VA_UTIMES_NULL) == 0 || (error = VOP_ACCESS(vp, VWRITE, cred)))) return (error); if (vap->va_mtime.tv_sec != VNOVAL) ip->i_flag |= IN_CHANGE | IN_UPDATE; if (vap->va_atime.tv_sec != VNOVAL) { if (!(vp->v_mount->mnt_flag & MNT_NOATIME) || (ip->i_flag & (IN_CHANGE | IN_UPDATE))) ip->i_flag |= IN_ACCESS; } error = UFS_UPDATE2(ip, &vap->va_atime, &vap->va_mtime, 0); if (error) return (error); } error = 0; if (vap->va_mode != (mode_t)VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) return (EROFS); error = ufs_chmod(vp, (int)vap->va_mode, cred, p); } VN_KNOTE(vp, hint); return (error); }
/* * Allocate a new inode. */ int ufs_makeinode(int mode, struct vnode *dvp, struct vnode **vpp, struct componentname *cnp) { struct inode *ip, *pdir; struct direct newdir; struct vnode *tvp; int error; pdir = VTOI(dvp); #ifdef DIAGNOSTIC if ((cnp->cn_flags & HASBUF) == 0) panic("ufs_makeinode: no name"); #endif *vpp = NULL; if ((mode & IFMT) == 0) mode |= IFREG; if ((error = UFS_INODE_ALLOC(pdir, mode, cnp->cn_cred, &tvp)) != 0) { pool_put(&namei_pool, cnp->cn_pnbuf); vput(dvp); return (error); } ip = VTOI(tvp); DIP_ASSIGN(ip, gid, DIP(pdir, gid)); DIP_ASSIGN(ip, uid, cnp->cn_cred->cr_uid); if ((error = getinoquota(ip)) || (error = ufs_quota_alloc_inode(ip, cnp->cn_cred))) { pool_put(&namei_pool, cnp->cn_pnbuf); UFS_INODE_FREE(ip, ip->i_number, mode); vput(tvp); vput(dvp); return (error); } ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE; DIP_ASSIGN(ip, mode, mode); tvp->v_type = IFTOVT(mode); /* Rest init'd in getnewvnode(). */ ip->i_effnlink = 1; DIP_ASSIGN(ip, nlink, 1); if (DOINGSOFTDEP(tvp)) softdep_change_linkcnt(ip, 0); if ((DIP(ip, mode) & ISGID) && !groupmember(DIP(ip, gid), cnp->cn_cred) && suser_ucred(cnp->cn_cred)) DIP_AND(ip, mode, ~ISGID); /* * Make sure inode goes to disk before directory entry. */ if ((error = UFS_UPDATE(ip, !DOINGSOFTDEP(tvp))) != 0) goto bad; ufs_makedirentry(ip, cnp, &newdir); if ((error = ufs_direnter(dvp, tvp, &newdir, cnp, NULL)) != 0) goto bad; if ((cnp->cn_flags & SAVESTART) == 0) pool_put(&namei_pool, 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. */ pool_put(&namei_pool, cnp->cn_pnbuf); vput(dvp); ip->i_effnlink = 0; DIP_ASSIGN(ip, nlink, 0); ip->i_flag |= IN_CHANGE; if (DOINGSOFTDEP(tvp)) softdep_change_linkcnt(ip, 0); tvp->v_type = VNON; vput(tvp); return (error); }
/* * Extended attribute area writing. */ int ffs_ea_write(struct vnode *vp, struct uio *uio, int ioflag, struct ucred *ucred) { struct inode *ip; struct ufs2_dinode *dp; struct fs *fs; struct buf *bp; off_t osize; size_t resid; int error, flags, xfersize; ip = VTOI(vp); fs = ip->i_fs; dp = ip->i_din2; error = 0; /* Don't cross the limit for the extended attribute area length. */ if ((unsigned)uio->uio_offset + uio->uio_resid > NXADDR * fs->fs_bsize) return (EFBIG); /* * Save the original amount of data to be written ('uio->uio_resid') as * well as the extended attribute area size ('dp->di_extsize') in case * we need to truncate in the end due to an unpredicted error. */ resid = uio->uio_resid; osize = dp->di_extsize; /* Prepare flags to be passed to UFS_BUF_ALLOC(). */ flags = IO_EXT | B_CLRBUF; if (!DOINGASYNC(vp)) flags |= B_SYNC; /* * Loop over the amount of data requested by the caller, stopping only * if an error occurs. By default, we always try to write a file system * block worth of bytes per iteration ('xfersize'). Check this value * against what is left to be copied ('uio->uio_resid'). If we are * writing less than a full block, ask the buffer to be cleaned first. */ while (uio->uio_resid > 0) { xfersize = fs->fs_bsize; if (uio->uio_resid < xfersize) { xfersize = uio->uio_resid; flags |= B_CLRBUF; } /* Ask the ffs_balloc.c code for the block. */ error = UFS_BUF_ALLOC(ip, uio->uio_offset, xfersize, ucred, flags, &bp); if (error) break; /* Check if we are growing the extended attributes area. */ if (uio->uio_offset + xfersize > dp->di_extsize) dp->di_extsize = uio->uio_offset + xfersize; error = uiomove(bp->b_data, xfersize, uio); if (error) { clrbuf(bp); /* Get rid of stray contents. */ error = EIO; break; } /* * We use the same criteria for calling bwrite(), bawrite(), or * bdwrite() as the rest of the FFS code does. */ if (ioflag & IO_SYNC) bwrite(bp); else if (xfersize == fs->fs_bsize) bawrite(bp); else bdwrite(bp); if (error || xfersize == 0) break; ip->i_flag |= IN_CHANGE | IN_UPDATE; } /* * If we successfully wrote any data, and we are not the superuser we * clear the setuid and setgid bits as a precaution against tampering. */ if ((DIP(ip, mode) & (ISUID | ISGID)) && resid > uio->uio_resid) if (ucred && suser_ucred(ucred)) DIP(ip, mode) &= ~(ISUID | ISGID); if (error) { /* Release blocks if we failed. */ if (ioflag & IO_UNIT) { ffs_truncate(ip, osize, flags, ucred); uio->uio_offset -= resid - uio->uio_resid; uio->uio_resid = resid; } return (error); } /* If needed, sync the inode now. */ if (resid > uio->uio_resid && (ioflag & IO_SYNC)) return (ffs_update(ip, NULL, NULL, MNT_WAIT)); return (0); }
/* * Set attribute vnode op. called from several syscalls */ int ext2fs_setattr(void *v) { struct vop_setattr_args *ap = v; struct vattr *vap = ap->a_vap; struct vnode *vp = ap->a_vp; struct inode *ip = VTOI(vp); struct ucred *cred = ap->a_cred; struct proc *p = curproc; int error; /* * Check for unsettable attributes. */ if ((vap->va_type != VNON) || (vap->va_nlink != VNOVAL) || (vap->va_fsid != VNOVAL) || (vap->va_fileid != VNOVAL) || (vap->va_blocksize != VNOVAL) || (vap->va_rdev != VNOVAL) || ((int)vap->va_bytes != VNOVAL) || (vap->va_gen != VNOVAL)) { return (EINVAL); } if (vap->va_flags != VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) return (EROFS); if (cred->cr_uid != ip->i_e2fs_uid && (error = suser_ucred(cred))) return (error); #ifdef EXT2FS_SYSTEM_FLAGS if (cred->cr_uid == 0) { if ((ip->i_e2fs_flags & (EXT2_APPEND | EXT2_IMMUTABLE)) && securelevel > 0) return (EPERM); ip->i_e2fs_flags &= ~(EXT2_APPEND | EXT2_IMMUTABLE); ip->i_e2fs_flags |= (vap->va_flags & SF_APPEND) ? EXT2_APPEND : 0 | (vap->va_flags & SF_IMMUTABLE) ? EXT2_IMMUTABLE: 0; } else { return (EPERM); } #else ip->i_e2fs_flags &= ~(EXT2_APPEND | EXT2_IMMUTABLE); ip->i_e2fs_flags |= (vap->va_flags & UF_APPEND) ? EXT2_APPEND : 0 | (vap->va_flags & UF_IMMUTABLE) ? EXT2_IMMUTABLE: 0; #endif ip->i_flag |= IN_CHANGE; if (vap->va_flags & (IMMUTABLE | APPEND)) return (0); } if (ip->i_e2fs_flags & (EXT2_APPEND | EXT2_IMMUTABLE)) return (EPERM); /* * Go through the fields and update iff not VNOVAL. */ if (vap->va_uid != (uid_t)VNOVAL || vap->va_gid != (gid_t)VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) return (EROFS); error = ext2fs_chown(vp, vap->va_uid, vap->va_gid, cred, p); if (error) return (error); } if (vap->va_size != VNOVAL) { /* * Disallow write attempts on read-only file systems; * unless the file is a socket, fifo, or a block or * character device resident on the file system. */ switch (vp->v_type) { case VDIR: return (EISDIR); case VLNK: case VREG: if (vp->v_mount->mnt_flag & MNT_RDONLY) return (EROFS); default: break; } error = ext2fs_truncate(ip, vap->va_size, 0, cred); if (error) return (error); } if (vap->va_atime.tv_sec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) return (EROFS); if (cred->cr_uid != ip->i_e2fs_uid && (error = suser_ucred(cred)) && ((vap->va_vaflags & VA_UTIMES_NULL) == 0 || (error = VOP_ACCESS(vp, VWRITE, cred)))) return (error); if (vap->va_mtime.tv_sec != VNOVAL) ip->i_flag |= IN_CHANGE | IN_UPDATE; if (vap->va_atime.tv_sec != VNOVAL) { if (!(vp->v_mount->mnt_flag & MNT_NOATIME) || (ip->i_flag & (IN_CHANGE | IN_UPDATE))) ip->i_flag |= IN_ACCESS; } error = ext2fs_update(ip, &vap->va_atime, &vap->va_mtime, 1); if (error) return (error); } error = 0; if (vap->va_mode != (mode_t)VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) return (EROFS); error = ext2fs_chmod(vp, (int)vap->va_mode, cred, p); } return (error); }
/* * Allocate a new inode. */ int ext2fs_makeinode(int mode, struct vnode *dvp, struct vnode **vpp, struct componentname *cnp) { struct inode *ip, *pdir; struct vnode *tvp; int error; pdir = VTOI(dvp); #ifdef DIAGNOSTIC if ((cnp->cn_flags & HASBUF) == 0) panic("ext2fs_makeinode: no name"); #endif *vpp = NULL; if ((mode & IFMT) == 0) mode |= IFREG; if ((error = ext2fs_inode_alloc(pdir, mode, cnp->cn_cred, &tvp)) != 0) { pool_put(&namei_pool, cnp->cn_pnbuf); vput(dvp); return (error); } ip = VTOI(tvp); ip->i_e2fs_gid = pdir->i_e2fs_gid; ip->i_e2fs_uid = cnp->cn_cred->cr_uid; ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE; ip->i_e2fs_mode = mode; tvp->v_type = IFTOVT(mode); /* Rest init'd in getnewvnode(). */ ip->i_e2fs_nlink = 1; if ((ip->i_e2fs_mode & ISGID) && !groupmember(ip->i_e2fs_gid, cnp->cn_cred) && suser_ucred(cnp->cn_cred)) ip->i_e2fs_mode &= ~ISGID; /* * Make sure inode goes to disk before directory entry. */ if ((error = ext2fs_update(ip, NULL, NULL, 1)) != 0) goto bad; error = ext2fs_direnter(ip, dvp, cnp); if (error != 0) goto bad; if ((cnp->cn_flags & SAVESTART) == 0) pool_put(&namei_pool, 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. */ pool_put(&namei_pool, cnp->cn_pnbuf); vput(dvp); ip->i_e2fs_nlink = 0; ip->i_flag |= IN_CHANGE; tvp->v_type = VNON; vput(tvp); return (error); }
int msdosfs_setattr(void *v) { struct vop_setattr_args *ap = v; int error = 0; struct denode *dep = VTODE(ap->a_vp); struct vattr *vap = ap->a_vap; struct ucred *cred = ap->a_cred; #ifdef MSDOSFS_DEBUG printf("msdosfs_setattr(): vp %08x, vap %08x, cred %08x, p %08x\n", ap->a_vp, vap, cred, ap->a_p); #endif if ((vap->va_type != VNON) || (vap->va_nlink != VNOVAL) || (vap->va_fsid != VNOVAL) || (vap->va_fileid != VNOVAL) || (vap->va_blocksize != VNOVAL) || (vap->va_rdev != VNOVAL) || (vap->va_bytes != VNOVAL) || (vap->va_gen != VNOVAL) || (vap->va_uid != VNOVAL) || (vap->va_gid != VNOVAL)) { #ifdef MSDOSFS_DEBUG printf("msdosfs_setattr(): returning EINVAL\n"); printf(" va_type %d, va_nlink %x, va_fsid %x, va_fileid %x\n", vap->va_type, vap->va_nlink, vap->va_fsid, vap->va_fileid); printf(" va_blocksize %x, va_rdev %x, va_bytes %x, va_gen %x\n", vap->va_blocksize, vap->va_rdev, vap->va_bytes, vap->va_gen); printf(" va_uid %x, va_gid %x\n", vap->va_uid, vap->va_gid); #endif return (EINVAL); } /* * Directories must not ever get their attributes modified */ if (ap->a_vp->v_type == VDIR) return (0); if (vap->va_size != VNOVAL) { error = detrunc(dep, (uint32_t)vap->va_size, 0, cred, ap->a_p); if (error) return (error); } if (vap->va_atime.tv_sec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL) { if (cred->cr_uid != dep->de_pmp->pm_uid && (error = suser_ucred(cred)) && ((vap->va_vaflags & VA_UTIMES_NULL) == 0 || (error = VOP_ACCESS(ap->a_vp, VWRITE, cred, ap->a_p)))) return (error); if (!(dep->de_pmp->pm_flags & MSDOSFSMNT_NOWIN95) && vap->va_atime.tv_sec != VNOVAL) unix2dostime(&vap->va_atime, &dep->de_ADate, NULL, NULL); if (vap->va_mtime.tv_sec != VNOVAL) unix2dostime(&vap->va_mtime, &dep->de_MDate, &dep->de_MTime, NULL); dep->de_Attributes |= ATTR_ARCHIVE; dep->de_flag |= DE_MODIFIED; } /* * DOS files only have the ability to have their writability * attribute set, so we use the owner write bit to set the readonly * attribute. */ if (vap->va_mode != (mode_t)VNOVAL) { if (cred->cr_uid != dep->de_pmp->pm_uid && (error = suser_ucred(cred))) return (error); /* We ignore the read and execute bits. */ if (vap->va_mode & VWRITE) dep->de_Attributes &= ~ATTR_READONLY; else dep->de_Attributes |= ATTR_READONLY; dep->de_flag |= DE_MODIFIED; } /* * Allow the `archived' bit to be toggled. */ if (vap->va_flags != VNOVAL) { if (cred->cr_uid != dep->de_pmp->pm_uid && (error = suser_ucred(cred))) return (error); if (vap->va_flags & SF_ARCHIVED) dep->de_Attributes &= ~ATTR_ARCHIVE; else dep->de_Attributes |= ATTR_ARCHIVE; dep->de_flag |= DE_MODIFIED; } return (deupdat(dep, 1)); }