Loading fs/xfs/xfs_ioctl.c +231 −220 Original line number Original line Diff line number Diff line Loading @@ -987,210 +987,245 @@ xfs_diflags_to_linux( inode->i_flags &= ~S_NOATIME; inode->i_flags &= ~S_NOATIME; } } #define FSX_PROJID 1 static int #define FSX_EXTSIZE 2 xfs_ioctl_setattr_xflags( #define FSX_XFLAGS 4 struct xfs_trans *tp, #define FSX_NONBLOCK 8 struct xfs_inode *ip, struct fsxattr *fa) STATIC int xfs_ioctl_setattr( xfs_inode_t *ip, struct fsxattr *fa, int mask) { { struct xfs_mount *mp = ip->i_mount; struct xfs_mount *mp = ip->i_mount; struct xfs_trans *tp; unsigned int lock_flags = 0; struct xfs_dquot *udqp = NULL; struct xfs_dquot *pdqp = NULL; struct xfs_dquot *olddquot = NULL; int code; trace_xfs_ioctl_setattr(ip); if (mp->m_flags & XFS_MOUNT_RDONLY) /* Can't change realtime flag if any extents are allocated. */ return -EROFS; if ((ip->i_d.di_nextents || ip->i_delayed_blks) && if (XFS_FORCED_SHUTDOWN(mp)) XFS_IS_REALTIME_INODE(ip) != (fa->fsx_xflags & XFS_XFLAG_REALTIME)) return -EIO; return -EINVAL; /* /* If realtime flag is set then must have realtime device */ * Disallow 32bit project ids when projid32bit feature is not enabled. if (fa->fsx_xflags & XFS_XFLAG_REALTIME) { */ if (mp->m_sb.sb_rblocks == 0 || mp->m_sb.sb_rextsize == 0 || if ((mask & FSX_PROJID) && (fa->fsx_projid > (__uint16_t)-1) && (ip->i_d.di_extsize % mp->m_sb.sb_rextsize)) !xfs_sb_version_hasprojid32bit(&ip->i_mount->m_sb)) return -EINVAL; return -EINVAL; } /* /* * If disk quotas is on, we make sure that the dquots do exist on disk, * Can't modify an immutable/append-only file unless * before we start any other transactions. Trying to do this later * we have appropriate permission. * is messy. We don't care to take a readlock to look at the ids * in inode here, because we can't hold it across the trans_reserve. * If the IDs do change before we take the ilock, we're covered * because the i_*dquot fields will get updated anyway. */ */ if (XFS_IS_QUOTA_ON(mp) && (mask & FSX_PROJID)) { if (((ip->i_d.di_flags & (XFS_DIFLAG_IMMUTABLE | XFS_DIFLAG_APPEND)) || code = xfs_qm_vop_dqalloc(ip, ip->i_d.di_uid, (fa->fsx_xflags & (XFS_XFLAG_IMMUTABLE | XFS_XFLAG_APPEND))) && ip->i_d.di_gid, fa->fsx_projid, !capable(CAP_LINUX_IMMUTABLE)) XFS_QMOPT_PQUOTA, &udqp, NULL, &pdqp); return -EPERM; if (code) return code; xfs_set_diflags(ip, fa->fsx_xflags); xfs_diflags_to_linux(ip); xfs_trans_ichgtime(tp, ip, XFS_ICHGTIME_CHG); xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); XFS_STATS_INC(xs_ig_attrchg); return 0; } } /* /* * For the other attributes, we acquire the inode lock and * Set up the transaction structure for the setattr operation, checking that we * first do an error checking pass. * have permission to do so. On success, return a clean transaction and the * inode locked exclusively ready for further operation specific checks. On * failure, return an error without modifying or locking the inode. */ */ static struct xfs_trans * xfs_ioctl_setattr_get_trans( struct xfs_inode *ip) { struct xfs_mount *mp = ip->i_mount; struct xfs_trans *tp; int error; if (mp->m_flags & XFS_MOUNT_RDONLY) return ERR_PTR(-EROFS); if (XFS_FORCED_SHUTDOWN(mp)) return ERR_PTR(-EIO); tp = xfs_trans_alloc(mp, XFS_TRANS_SETATTR_NOT_SIZE); tp = xfs_trans_alloc(mp, XFS_TRANS_SETATTR_NOT_SIZE); code = xfs_trans_reserve(tp, &M_RES(mp)->tr_ichange, 0, 0); error = xfs_trans_reserve(tp, &M_RES(mp)->tr_ichange, 0, 0); if (code) if (error) goto error_return; goto out_cancel; lock_flags = XFS_ILOCK_EXCL; xfs_ilock(ip, XFS_ILOCK_EXCL); xfs_ilock(ip, lock_flags); xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL); /* /* * CAP_FOWNER overrides the following restrictions: * CAP_FOWNER overrides the following restrictions: * * * The user ID of the calling process must be equal * The user ID of the calling process must be equal to the file owner * to the file owner ID, except in cases where the * ID, except in cases where the CAP_FSETID capability is applicable. * CAP_FSETID capability is applicable. */ */ if (!inode_owner_or_capable(VFS_I(ip))) { if (!inode_owner_or_capable(VFS_I(ip))) { code = -EPERM; error = -EPERM; goto error_return; goto out_cancel; } } /* if (mp->m_flags & XFS_MOUNT_WSYNC) * Do a quota reservation only if projid is actually going to change. xfs_trans_set_sync(tp); * Only allow changing of projid from init_user_ns since it is a * non user namespace aware identifier. */ if (mask & FSX_PROJID) { if (current_user_ns() != &init_user_ns) { code = -EINVAL; goto error_return; } if (XFS_IS_QUOTA_RUNNING(mp) && return tp; XFS_IS_PQUOTA_ON(mp) && xfs_get_projid(ip) != fa->fsx_projid) { ASSERT(tp); code = xfs_qm_vop_chown_reserve(tp, ip, udqp, NULL, pdqp, capable(CAP_FOWNER) ? XFS_QMOPT_FORCE_RES : 0); if (code) /* out of quota */ goto error_return; } } if (mask & FSX_EXTSIZE) { out_cancel: /* xfs_trans_cancel(tp, 0); * Can't change extent size if any extents are allocated. return ERR_PTR(error); */ if (ip->i_d.di_nextents && ((ip->i_d.di_extsize << mp->m_sb.sb_blocklog) != fa->fsx_extsize)) { code = -EINVAL; /* EFBIG? */ goto error_return; } } /* /* * Extent size must be a multiple of the appropriate block * extent size hint validation is somewhat cumbersome. Rules are: * size, if set at all. It must also be smaller than the * maximum extent size supported by the filesystem. * * * Also, for non-realtime files, limit the extent size hint to * 1. extent size hint is only valid for directories and regular files * half the size of the AGs in the filesystem so alignment * 2. XFS_XFLAG_EXTSIZE is only valid for regular files * doesn't result in extents larger than an AG. * 3. XFS_XFLAG_EXTSZINHERIT is only valid for directories. * 4. can only be changed on regular files if no extents are allocated * 5. can be changed on directories at any time * 6. extsize hint of 0 turns off hints, clears inode flags. * 7. Extent size must be a multiple of the appropriate block size. * 8. for non-realtime files, the extent size hint must be limited * to half the AG size to avoid alignment extending the extent beyond the * limits of the AG. */ */ int xfs_ioctl_setattr_check_extsize( struct xfs_inode *ip, struct fsxattr *fa) { struct xfs_mount *mp = ip->i_mount; if ((fa->fsx_xflags & XFS_XFLAG_EXTSIZE) && !S_ISREG(ip->i_d.di_mode)) return -EINVAL; if ((fa->fsx_xflags & XFS_XFLAG_EXTSZINHERIT) && !S_ISDIR(ip->i_d.di_mode)) return -EINVAL; if (S_ISREG(ip->i_d.di_mode) && ip->i_d.di_nextents && ((ip->i_d.di_extsize << mp->m_sb.sb_blocklog) != fa->fsx_extsize)) return -EINVAL; if (fa->fsx_extsize != 0) { if (fa->fsx_extsize != 0) { xfs_extlen_t size; xfs_extlen_t size; xfs_fsblock_t extsize_fsb; xfs_fsblock_t extsize_fsb; extsize_fsb = XFS_B_TO_FSB(mp, fa->fsx_extsize); extsize_fsb = XFS_B_TO_FSB(mp, fa->fsx_extsize); if (extsize_fsb > MAXEXTLEN) { if (extsize_fsb > MAXEXTLEN) code = -EINVAL; return -EINVAL; goto error_return; } if (XFS_IS_REALTIME_INODE(ip) || if (XFS_IS_REALTIME_INODE(ip) || ((mask & FSX_XFLAGS) && (fa->fsx_xflags & XFS_XFLAG_REALTIME)) { (fa->fsx_xflags & XFS_XFLAG_REALTIME))) { size = mp->m_sb.sb_rextsize << mp->m_sb.sb_blocklog; size = mp->m_sb.sb_rextsize << mp->m_sb.sb_blocklog; } else { } else { size = mp->m_sb.sb_blocksize; size = mp->m_sb.sb_blocksize; if (extsize_fsb > mp->m_sb.sb_agblocks / 2) { if (extsize_fsb > mp->m_sb.sb_agblocks / 2) code = -EINVAL; return -EINVAL; goto error_return; } } } if (fa->fsx_extsize % size) { if (fa->fsx_extsize % size) code = -EINVAL; return -EINVAL; goto error_return; } else } fa->fsx_xflags &= ~(XFS_XFLAG_EXTSIZE | XFS_XFLAG_EXTSZINHERIT); } return 0; } } int xfs_ioctl_setattr_check_projid( struct xfs_inode *ip, struct fsxattr *fa) { /* Disallow 32bit project ids if projid32bit feature is not enabled. */ if (fa->fsx_projid > (__uint16_t)-1 && !xfs_sb_version_hasprojid32bit(&ip->i_mount->m_sb)) return -EINVAL; if (mask & FSX_XFLAGS) { /* /* * Can't change realtime flag if any extents are allocated. * Project Quota ID state is only allowed to change from within the init * namespace. Enforce that restriction only if we are trying to change * the quota ID state. Everything else is allowed in user namespaces. */ */ if ((ip->i_d.di_nextents || ip->i_delayed_blks) && if (current_user_ns() == &init_user_ns) (XFS_IS_REALTIME_INODE(ip)) != return 0; (fa->fsx_xflags & XFS_XFLAG_REALTIME)) { code = -EINVAL; /* EFBIG? */ if (xfs_get_projid(ip) != fa->fsx_projid) goto error_return; return -EINVAL; if ((fa->fsx_xflags & XFS_XFLAG_PROJINHERIT) != (ip->i_d.di_flags & XFS_DIFLAG_PROJINHERIT)) return -EINVAL; return 0; } } STATIC int xfs_ioctl_setattr( xfs_inode_t *ip, struct fsxattr *fa) { struct xfs_mount *mp = ip->i_mount; struct xfs_trans *tp; struct xfs_dquot *udqp = NULL; struct xfs_dquot *pdqp = NULL; struct xfs_dquot *olddquot = NULL; int code; trace_xfs_ioctl_setattr(ip); code = xfs_ioctl_setattr_check_projid(ip, fa); if (code) return code; /* /* * If realtime flag is set then must have realtime data. * If disk quotas is on, we make sure that the dquots do exist on disk, * before we start any other transactions. Trying to do this later * is messy. We don't care to take a readlock to look at the ids * in inode here, because we can't hold it across the trans_reserve. * If the IDs do change before we take the ilock, we're covered * because the i_*dquot fields will get updated anyway. */ */ if ((fa->fsx_xflags & XFS_XFLAG_REALTIME)) { if (XFS_IS_QUOTA_ON(mp)) { if ((mp->m_sb.sb_rblocks == 0) || code = xfs_qm_vop_dqalloc(ip, ip->i_d.di_uid, (mp->m_sb.sb_rextsize == 0) || ip->i_d.di_gid, fa->fsx_projid, (ip->i_d.di_extsize % mp->m_sb.sb_rextsize)) { XFS_QMOPT_PQUOTA, &udqp, NULL, &pdqp); code = -EINVAL; if (code) goto error_return; return code; } } } /* tp = xfs_ioctl_setattr_get_trans(ip); * Can't modify an immutable/append-only file unless if (IS_ERR(tp)) { * we have appropriate permission. code = PTR_ERR(tp); */ goto error_free_dquots; if ((ip->i_d.di_flags & (XFS_DIFLAG_IMMUTABLE|XFS_DIFLAG_APPEND) || (fa->fsx_xflags & (XFS_XFLAG_IMMUTABLE | XFS_XFLAG_APPEND))) && !capable(CAP_LINUX_IMMUTABLE)) { code = -EPERM; goto error_return; } } if (XFS_IS_QUOTA_RUNNING(mp) && XFS_IS_PQUOTA_ON(mp) && xfs_get_projid(ip) != fa->fsx_projid) { code = xfs_qm_vop_chown_reserve(tp, ip, udqp, NULL, pdqp, capable(CAP_FOWNER) ? XFS_QMOPT_FORCE_RES : 0); if (code) /* out of quota */ goto error_trans_cancel; } } xfs_trans_ijoin(tp, ip, 0); code = xfs_ioctl_setattr_check_extsize(ip, fa); if (code) goto error_trans_cancel; code = xfs_ioctl_setattr_xflags(tp, ip, fa); if (code) goto error_trans_cancel; /* /* * Change file ownership. Must be the owner or privileged. * Change file ownership. Must be the owner or privileged. CAP_FSETID */ * overrides the following restrictions: if (mask & FSX_PROJID) { /* * CAP_FSETID overrides the following restrictions: * * * The set-user-ID and set-group-ID bits of a file will be * The set-user-ID and set-group-ID bits of a file will be cleared upon * cleared upon successful return from chown() * successful return from chown() */ */ if ((ip->i_d.di_mode & (S_ISUID|S_ISGID)) && if ((ip->i_d.di_mode & (S_ISUID|S_ISGID)) && !capable_wrt_inode_uidgid(VFS_I(ip), CAP_FSETID)) !capable_wrt_inode_uidgid(VFS_I(ip), CAP_FSETID)) ip->i_d.di_mode &= ~(S_ISUID|S_ISGID); ip->i_d.di_mode &= ~(S_ISUID|S_ISGID); /* /* Change the ownerships and register project quota modifications */ * Change the ownerships and register quota modifications * in the transaction. */ if (xfs_get_projid(ip) != fa->fsx_projid) { if (xfs_get_projid(ip) != fa->fsx_projid) { if (XFS_IS_QUOTA_RUNNING(mp) && XFS_IS_PQUOTA_ON(mp)) { if (XFS_IS_QUOTA_RUNNING(mp) && XFS_IS_PQUOTA_ON(mp)) { olddquot = xfs_qm_vop_chown(tp, ip, olddquot = xfs_qm_vop_chown(tp, ip, Loading @@ -1200,46 +1235,17 @@ xfs_ioctl_setattr( xfs_set_projid(ip, fa->fsx_projid); xfs_set_projid(ip, fa->fsx_projid); } } } if (mask & FSX_XFLAGS) { xfs_set_diflags(ip, fa->fsx_xflags); xfs_diflags_to_linux(ip); } /* /* * Only set the extent size hint if we've already determined that the * Only set the extent size hint if we've already determined that the * extent size hint should be set on the inode. If no extent size flags * extent size hint should be set on the inode. If no extent size flags * are set on the inode then unconditionally clear the extent size hint. * are set on the inode then unconditionally clear the extent size hint. */ */ if (mask & FSX_EXTSIZE) { if (ip->i_d.di_flags & (XFS_DIFLAG_EXTSIZE | XFS_DIFLAG_EXTSZINHERIT)) int extsize = 0; ip->i_d.di_extsize = fa->fsx_extsize >> mp->m_sb.sb_blocklog; else if (ip->i_d.di_flags & ip->i_d.di_extsize = 0; (XFS_DIFLAG_EXTSIZE | XFS_DIFLAG_EXTSZINHERIT)) extsize = fa->fsx_extsize >> mp->m_sb.sb_blocklog; ip->i_d.di_extsize = extsize; } xfs_trans_ichgtime(tp, ip, XFS_ICHGTIME_CHG); xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); XFS_STATS_INC(xs_ig_attrchg); /* * If this is a synchronous mount, make sure that the * transaction goes to disk before returning to the user. * This is slightly sub-optimal in that truncates require * two sync transactions instead of one for wsync filesystems. * One for the truncate and one for the timestamps since we * don't want to change the timestamps unless we're sure the * truncate worked. Truncates are less than 1% of the laddis * mix so this probably isn't worth the trouble to optimize. */ if (mp->m_flags & XFS_MOUNT_WSYNC) xfs_trans_set_sync(tp); code = xfs_trans_commit(tp, 0); code = xfs_trans_commit(tp, 0); xfs_iunlock(ip, lock_flags); /* /* * Release any dquot(s) the inode had kept before chown. * Release any dquot(s) the inode had kept before chown. Loading @@ -1250,12 +1256,11 @@ xfs_ioctl_setattr( return code; return code; error_return: error_trans_cancel: xfs_trans_cancel(tp, 0); error_free_dquots: xfs_qm_dqrele(udqp); xfs_qm_dqrele(udqp); xfs_qm_dqrele(pdqp); xfs_qm_dqrele(pdqp); xfs_trans_cancel(tp, 0); if (lock_flags) xfs_iunlock(ip, lock_flags); return code; return code; } } Loading @@ -1266,20 +1271,15 @@ xfs_ioc_fssetxattr( void __user *arg) void __user *arg) { { struct fsxattr fa; struct fsxattr fa; unsigned int mask; int error; int error; if (copy_from_user(&fa, arg, sizeof(fa))) if (copy_from_user(&fa, arg, sizeof(fa))) return -EFAULT; return -EFAULT; mask = FSX_XFLAGS | FSX_EXTSIZE | FSX_PROJID; if (filp->f_flags & (O_NDELAY|O_NONBLOCK)) mask |= FSX_NONBLOCK; error = mnt_want_write_file(filp); error = mnt_want_write_file(filp); if (error) if (error) return error; return error; error = xfs_ioctl_setattr(ip, &fa, mask); error = xfs_ioctl_setattr(ip, &fa); mnt_drop_write_file(filp); mnt_drop_write_file(filp); return error; return error; } } Loading @@ -1299,13 +1299,13 @@ xfs_ioc_getxflags( STATIC int STATIC int xfs_ioc_setxflags( xfs_ioc_setxflags( xfs_inode_t *ip, struct xfs_inode *ip, struct file *filp, struct file *filp, void __user *arg) void __user *arg) { { struct xfs_trans *tp; struct fsxattr fa; struct fsxattr fa; unsigned int flags; unsigned int flags; unsigned int mask; int error; int error; if (copy_from_user(&flags, arg, sizeof(flags))) if (copy_from_user(&flags, arg, sizeof(flags))) Loading @@ -1316,15 +1316,26 @@ xfs_ioc_setxflags( FS_SYNC_FL)) FS_SYNC_FL)) return -EOPNOTSUPP; return -EOPNOTSUPP; mask = FSX_XFLAGS; if (filp->f_flags & (O_NDELAY|O_NONBLOCK)) mask |= FSX_NONBLOCK; fa.fsx_xflags = xfs_merge_ioc_xflags(flags, xfs_ip2xflags(ip)); fa.fsx_xflags = xfs_merge_ioc_xflags(flags, xfs_ip2xflags(ip)); error = mnt_want_write_file(filp); error = mnt_want_write_file(filp); if (error) if (error) return error; return error; error = xfs_ioctl_setattr(ip, &fa, mask); tp = xfs_ioctl_setattr_get_trans(ip); if (IS_ERR(tp)) { error = PTR_ERR(tp); goto out_drop_write; } error = xfs_ioctl_setattr_xflags(tp, ip, &fa); if (error) { xfs_trans_cancel(tp, 0); goto out_drop_write; } error = xfs_trans_commit(tp, 0); out_drop_write: mnt_drop_write_file(filp); mnt_drop_write_file(filp); return error; return error; } } Loading Loading
fs/xfs/xfs_ioctl.c +231 −220 Original line number Original line Diff line number Diff line Loading @@ -987,210 +987,245 @@ xfs_diflags_to_linux( inode->i_flags &= ~S_NOATIME; inode->i_flags &= ~S_NOATIME; } } #define FSX_PROJID 1 static int #define FSX_EXTSIZE 2 xfs_ioctl_setattr_xflags( #define FSX_XFLAGS 4 struct xfs_trans *tp, #define FSX_NONBLOCK 8 struct xfs_inode *ip, struct fsxattr *fa) STATIC int xfs_ioctl_setattr( xfs_inode_t *ip, struct fsxattr *fa, int mask) { { struct xfs_mount *mp = ip->i_mount; struct xfs_mount *mp = ip->i_mount; struct xfs_trans *tp; unsigned int lock_flags = 0; struct xfs_dquot *udqp = NULL; struct xfs_dquot *pdqp = NULL; struct xfs_dquot *olddquot = NULL; int code; trace_xfs_ioctl_setattr(ip); if (mp->m_flags & XFS_MOUNT_RDONLY) /* Can't change realtime flag if any extents are allocated. */ return -EROFS; if ((ip->i_d.di_nextents || ip->i_delayed_blks) && if (XFS_FORCED_SHUTDOWN(mp)) XFS_IS_REALTIME_INODE(ip) != (fa->fsx_xflags & XFS_XFLAG_REALTIME)) return -EIO; return -EINVAL; /* /* If realtime flag is set then must have realtime device */ * Disallow 32bit project ids when projid32bit feature is not enabled. if (fa->fsx_xflags & XFS_XFLAG_REALTIME) { */ if (mp->m_sb.sb_rblocks == 0 || mp->m_sb.sb_rextsize == 0 || if ((mask & FSX_PROJID) && (fa->fsx_projid > (__uint16_t)-1) && (ip->i_d.di_extsize % mp->m_sb.sb_rextsize)) !xfs_sb_version_hasprojid32bit(&ip->i_mount->m_sb)) return -EINVAL; return -EINVAL; } /* /* * If disk quotas is on, we make sure that the dquots do exist on disk, * Can't modify an immutable/append-only file unless * before we start any other transactions. Trying to do this later * we have appropriate permission. * is messy. We don't care to take a readlock to look at the ids * in inode here, because we can't hold it across the trans_reserve. * If the IDs do change before we take the ilock, we're covered * because the i_*dquot fields will get updated anyway. */ */ if (XFS_IS_QUOTA_ON(mp) && (mask & FSX_PROJID)) { if (((ip->i_d.di_flags & (XFS_DIFLAG_IMMUTABLE | XFS_DIFLAG_APPEND)) || code = xfs_qm_vop_dqalloc(ip, ip->i_d.di_uid, (fa->fsx_xflags & (XFS_XFLAG_IMMUTABLE | XFS_XFLAG_APPEND))) && ip->i_d.di_gid, fa->fsx_projid, !capable(CAP_LINUX_IMMUTABLE)) XFS_QMOPT_PQUOTA, &udqp, NULL, &pdqp); return -EPERM; if (code) return code; xfs_set_diflags(ip, fa->fsx_xflags); xfs_diflags_to_linux(ip); xfs_trans_ichgtime(tp, ip, XFS_ICHGTIME_CHG); xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); XFS_STATS_INC(xs_ig_attrchg); return 0; } } /* /* * For the other attributes, we acquire the inode lock and * Set up the transaction structure for the setattr operation, checking that we * first do an error checking pass. * have permission to do so. On success, return a clean transaction and the * inode locked exclusively ready for further operation specific checks. On * failure, return an error without modifying or locking the inode. */ */ static struct xfs_trans * xfs_ioctl_setattr_get_trans( struct xfs_inode *ip) { struct xfs_mount *mp = ip->i_mount; struct xfs_trans *tp; int error; if (mp->m_flags & XFS_MOUNT_RDONLY) return ERR_PTR(-EROFS); if (XFS_FORCED_SHUTDOWN(mp)) return ERR_PTR(-EIO); tp = xfs_trans_alloc(mp, XFS_TRANS_SETATTR_NOT_SIZE); tp = xfs_trans_alloc(mp, XFS_TRANS_SETATTR_NOT_SIZE); code = xfs_trans_reserve(tp, &M_RES(mp)->tr_ichange, 0, 0); error = xfs_trans_reserve(tp, &M_RES(mp)->tr_ichange, 0, 0); if (code) if (error) goto error_return; goto out_cancel; lock_flags = XFS_ILOCK_EXCL; xfs_ilock(ip, XFS_ILOCK_EXCL); xfs_ilock(ip, lock_flags); xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL); /* /* * CAP_FOWNER overrides the following restrictions: * CAP_FOWNER overrides the following restrictions: * * * The user ID of the calling process must be equal * The user ID of the calling process must be equal to the file owner * to the file owner ID, except in cases where the * ID, except in cases where the CAP_FSETID capability is applicable. * CAP_FSETID capability is applicable. */ */ if (!inode_owner_or_capable(VFS_I(ip))) { if (!inode_owner_or_capable(VFS_I(ip))) { code = -EPERM; error = -EPERM; goto error_return; goto out_cancel; } } /* if (mp->m_flags & XFS_MOUNT_WSYNC) * Do a quota reservation only if projid is actually going to change. xfs_trans_set_sync(tp); * Only allow changing of projid from init_user_ns since it is a * non user namespace aware identifier. */ if (mask & FSX_PROJID) { if (current_user_ns() != &init_user_ns) { code = -EINVAL; goto error_return; } if (XFS_IS_QUOTA_RUNNING(mp) && return tp; XFS_IS_PQUOTA_ON(mp) && xfs_get_projid(ip) != fa->fsx_projid) { ASSERT(tp); code = xfs_qm_vop_chown_reserve(tp, ip, udqp, NULL, pdqp, capable(CAP_FOWNER) ? XFS_QMOPT_FORCE_RES : 0); if (code) /* out of quota */ goto error_return; } } if (mask & FSX_EXTSIZE) { out_cancel: /* xfs_trans_cancel(tp, 0); * Can't change extent size if any extents are allocated. return ERR_PTR(error); */ if (ip->i_d.di_nextents && ((ip->i_d.di_extsize << mp->m_sb.sb_blocklog) != fa->fsx_extsize)) { code = -EINVAL; /* EFBIG? */ goto error_return; } } /* /* * Extent size must be a multiple of the appropriate block * extent size hint validation is somewhat cumbersome. Rules are: * size, if set at all. It must also be smaller than the * maximum extent size supported by the filesystem. * * * Also, for non-realtime files, limit the extent size hint to * 1. extent size hint is only valid for directories and regular files * half the size of the AGs in the filesystem so alignment * 2. XFS_XFLAG_EXTSIZE is only valid for regular files * doesn't result in extents larger than an AG. * 3. XFS_XFLAG_EXTSZINHERIT is only valid for directories. * 4. can only be changed on regular files if no extents are allocated * 5. can be changed on directories at any time * 6. extsize hint of 0 turns off hints, clears inode flags. * 7. Extent size must be a multiple of the appropriate block size. * 8. for non-realtime files, the extent size hint must be limited * to half the AG size to avoid alignment extending the extent beyond the * limits of the AG. */ */ int xfs_ioctl_setattr_check_extsize( struct xfs_inode *ip, struct fsxattr *fa) { struct xfs_mount *mp = ip->i_mount; if ((fa->fsx_xflags & XFS_XFLAG_EXTSIZE) && !S_ISREG(ip->i_d.di_mode)) return -EINVAL; if ((fa->fsx_xflags & XFS_XFLAG_EXTSZINHERIT) && !S_ISDIR(ip->i_d.di_mode)) return -EINVAL; if (S_ISREG(ip->i_d.di_mode) && ip->i_d.di_nextents && ((ip->i_d.di_extsize << mp->m_sb.sb_blocklog) != fa->fsx_extsize)) return -EINVAL; if (fa->fsx_extsize != 0) { if (fa->fsx_extsize != 0) { xfs_extlen_t size; xfs_extlen_t size; xfs_fsblock_t extsize_fsb; xfs_fsblock_t extsize_fsb; extsize_fsb = XFS_B_TO_FSB(mp, fa->fsx_extsize); extsize_fsb = XFS_B_TO_FSB(mp, fa->fsx_extsize); if (extsize_fsb > MAXEXTLEN) { if (extsize_fsb > MAXEXTLEN) code = -EINVAL; return -EINVAL; goto error_return; } if (XFS_IS_REALTIME_INODE(ip) || if (XFS_IS_REALTIME_INODE(ip) || ((mask & FSX_XFLAGS) && (fa->fsx_xflags & XFS_XFLAG_REALTIME)) { (fa->fsx_xflags & XFS_XFLAG_REALTIME))) { size = mp->m_sb.sb_rextsize << mp->m_sb.sb_blocklog; size = mp->m_sb.sb_rextsize << mp->m_sb.sb_blocklog; } else { } else { size = mp->m_sb.sb_blocksize; size = mp->m_sb.sb_blocksize; if (extsize_fsb > mp->m_sb.sb_agblocks / 2) { if (extsize_fsb > mp->m_sb.sb_agblocks / 2) code = -EINVAL; return -EINVAL; goto error_return; } } } if (fa->fsx_extsize % size) { if (fa->fsx_extsize % size) code = -EINVAL; return -EINVAL; goto error_return; } else } fa->fsx_xflags &= ~(XFS_XFLAG_EXTSIZE | XFS_XFLAG_EXTSZINHERIT); } return 0; } } int xfs_ioctl_setattr_check_projid( struct xfs_inode *ip, struct fsxattr *fa) { /* Disallow 32bit project ids if projid32bit feature is not enabled. */ if (fa->fsx_projid > (__uint16_t)-1 && !xfs_sb_version_hasprojid32bit(&ip->i_mount->m_sb)) return -EINVAL; if (mask & FSX_XFLAGS) { /* /* * Can't change realtime flag if any extents are allocated. * Project Quota ID state is only allowed to change from within the init * namespace. Enforce that restriction only if we are trying to change * the quota ID state. Everything else is allowed in user namespaces. */ */ if ((ip->i_d.di_nextents || ip->i_delayed_blks) && if (current_user_ns() == &init_user_ns) (XFS_IS_REALTIME_INODE(ip)) != return 0; (fa->fsx_xflags & XFS_XFLAG_REALTIME)) { code = -EINVAL; /* EFBIG? */ if (xfs_get_projid(ip) != fa->fsx_projid) goto error_return; return -EINVAL; if ((fa->fsx_xflags & XFS_XFLAG_PROJINHERIT) != (ip->i_d.di_flags & XFS_DIFLAG_PROJINHERIT)) return -EINVAL; return 0; } } STATIC int xfs_ioctl_setattr( xfs_inode_t *ip, struct fsxattr *fa) { struct xfs_mount *mp = ip->i_mount; struct xfs_trans *tp; struct xfs_dquot *udqp = NULL; struct xfs_dquot *pdqp = NULL; struct xfs_dquot *olddquot = NULL; int code; trace_xfs_ioctl_setattr(ip); code = xfs_ioctl_setattr_check_projid(ip, fa); if (code) return code; /* /* * If realtime flag is set then must have realtime data. * If disk quotas is on, we make sure that the dquots do exist on disk, * before we start any other transactions. Trying to do this later * is messy. We don't care to take a readlock to look at the ids * in inode here, because we can't hold it across the trans_reserve. * If the IDs do change before we take the ilock, we're covered * because the i_*dquot fields will get updated anyway. */ */ if ((fa->fsx_xflags & XFS_XFLAG_REALTIME)) { if (XFS_IS_QUOTA_ON(mp)) { if ((mp->m_sb.sb_rblocks == 0) || code = xfs_qm_vop_dqalloc(ip, ip->i_d.di_uid, (mp->m_sb.sb_rextsize == 0) || ip->i_d.di_gid, fa->fsx_projid, (ip->i_d.di_extsize % mp->m_sb.sb_rextsize)) { XFS_QMOPT_PQUOTA, &udqp, NULL, &pdqp); code = -EINVAL; if (code) goto error_return; return code; } } } /* tp = xfs_ioctl_setattr_get_trans(ip); * Can't modify an immutable/append-only file unless if (IS_ERR(tp)) { * we have appropriate permission. code = PTR_ERR(tp); */ goto error_free_dquots; if ((ip->i_d.di_flags & (XFS_DIFLAG_IMMUTABLE|XFS_DIFLAG_APPEND) || (fa->fsx_xflags & (XFS_XFLAG_IMMUTABLE | XFS_XFLAG_APPEND))) && !capable(CAP_LINUX_IMMUTABLE)) { code = -EPERM; goto error_return; } } if (XFS_IS_QUOTA_RUNNING(mp) && XFS_IS_PQUOTA_ON(mp) && xfs_get_projid(ip) != fa->fsx_projid) { code = xfs_qm_vop_chown_reserve(tp, ip, udqp, NULL, pdqp, capable(CAP_FOWNER) ? XFS_QMOPT_FORCE_RES : 0); if (code) /* out of quota */ goto error_trans_cancel; } } xfs_trans_ijoin(tp, ip, 0); code = xfs_ioctl_setattr_check_extsize(ip, fa); if (code) goto error_trans_cancel; code = xfs_ioctl_setattr_xflags(tp, ip, fa); if (code) goto error_trans_cancel; /* /* * Change file ownership. Must be the owner or privileged. * Change file ownership. Must be the owner or privileged. CAP_FSETID */ * overrides the following restrictions: if (mask & FSX_PROJID) { /* * CAP_FSETID overrides the following restrictions: * * * The set-user-ID and set-group-ID bits of a file will be * The set-user-ID and set-group-ID bits of a file will be cleared upon * cleared upon successful return from chown() * successful return from chown() */ */ if ((ip->i_d.di_mode & (S_ISUID|S_ISGID)) && if ((ip->i_d.di_mode & (S_ISUID|S_ISGID)) && !capable_wrt_inode_uidgid(VFS_I(ip), CAP_FSETID)) !capable_wrt_inode_uidgid(VFS_I(ip), CAP_FSETID)) ip->i_d.di_mode &= ~(S_ISUID|S_ISGID); ip->i_d.di_mode &= ~(S_ISUID|S_ISGID); /* /* Change the ownerships and register project quota modifications */ * Change the ownerships and register quota modifications * in the transaction. */ if (xfs_get_projid(ip) != fa->fsx_projid) { if (xfs_get_projid(ip) != fa->fsx_projid) { if (XFS_IS_QUOTA_RUNNING(mp) && XFS_IS_PQUOTA_ON(mp)) { if (XFS_IS_QUOTA_RUNNING(mp) && XFS_IS_PQUOTA_ON(mp)) { olddquot = xfs_qm_vop_chown(tp, ip, olddquot = xfs_qm_vop_chown(tp, ip, Loading @@ -1200,46 +1235,17 @@ xfs_ioctl_setattr( xfs_set_projid(ip, fa->fsx_projid); xfs_set_projid(ip, fa->fsx_projid); } } } if (mask & FSX_XFLAGS) { xfs_set_diflags(ip, fa->fsx_xflags); xfs_diflags_to_linux(ip); } /* /* * Only set the extent size hint if we've already determined that the * Only set the extent size hint if we've already determined that the * extent size hint should be set on the inode. If no extent size flags * extent size hint should be set on the inode. If no extent size flags * are set on the inode then unconditionally clear the extent size hint. * are set on the inode then unconditionally clear the extent size hint. */ */ if (mask & FSX_EXTSIZE) { if (ip->i_d.di_flags & (XFS_DIFLAG_EXTSIZE | XFS_DIFLAG_EXTSZINHERIT)) int extsize = 0; ip->i_d.di_extsize = fa->fsx_extsize >> mp->m_sb.sb_blocklog; else if (ip->i_d.di_flags & ip->i_d.di_extsize = 0; (XFS_DIFLAG_EXTSIZE | XFS_DIFLAG_EXTSZINHERIT)) extsize = fa->fsx_extsize >> mp->m_sb.sb_blocklog; ip->i_d.di_extsize = extsize; } xfs_trans_ichgtime(tp, ip, XFS_ICHGTIME_CHG); xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); XFS_STATS_INC(xs_ig_attrchg); /* * If this is a synchronous mount, make sure that the * transaction goes to disk before returning to the user. * This is slightly sub-optimal in that truncates require * two sync transactions instead of one for wsync filesystems. * One for the truncate and one for the timestamps since we * don't want to change the timestamps unless we're sure the * truncate worked. Truncates are less than 1% of the laddis * mix so this probably isn't worth the trouble to optimize. */ if (mp->m_flags & XFS_MOUNT_WSYNC) xfs_trans_set_sync(tp); code = xfs_trans_commit(tp, 0); code = xfs_trans_commit(tp, 0); xfs_iunlock(ip, lock_flags); /* /* * Release any dquot(s) the inode had kept before chown. * Release any dquot(s) the inode had kept before chown. Loading @@ -1250,12 +1256,11 @@ xfs_ioctl_setattr( return code; return code; error_return: error_trans_cancel: xfs_trans_cancel(tp, 0); error_free_dquots: xfs_qm_dqrele(udqp); xfs_qm_dqrele(udqp); xfs_qm_dqrele(pdqp); xfs_qm_dqrele(pdqp); xfs_trans_cancel(tp, 0); if (lock_flags) xfs_iunlock(ip, lock_flags); return code; return code; } } Loading @@ -1266,20 +1271,15 @@ xfs_ioc_fssetxattr( void __user *arg) void __user *arg) { { struct fsxattr fa; struct fsxattr fa; unsigned int mask; int error; int error; if (copy_from_user(&fa, arg, sizeof(fa))) if (copy_from_user(&fa, arg, sizeof(fa))) return -EFAULT; return -EFAULT; mask = FSX_XFLAGS | FSX_EXTSIZE | FSX_PROJID; if (filp->f_flags & (O_NDELAY|O_NONBLOCK)) mask |= FSX_NONBLOCK; error = mnt_want_write_file(filp); error = mnt_want_write_file(filp); if (error) if (error) return error; return error; error = xfs_ioctl_setattr(ip, &fa, mask); error = xfs_ioctl_setattr(ip, &fa); mnt_drop_write_file(filp); mnt_drop_write_file(filp); return error; return error; } } Loading @@ -1299,13 +1299,13 @@ xfs_ioc_getxflags( STATIC int STATIC int xfs_ioc_setxflags( xfs_ioc_setxflags( xfs_inode_t *ip, struct xfs_inode *ip, struct file *filp, struct file *filp, void __user *arg) void __user *arg) { { struct xfs_trans *tp; struct fsxattr fa; struct fsxattr fa; unsigned int flags; unsigned int flags; unsigned int mask; int error; int error; if (copy_from_user(&flags, arg, sizeof(flags))) if (copy_from_user(&flags, arg, sizeof(flags))) Loading @@ -1316,15 +1316,26 @@ xfs_ioc_setxflags( FS_SYNC_FL)) FS_SYNC_FL)) return -EOPNOTSUPP; return -EOPNOTSUPP; mask = FSX_XFLAGS; if (filp->f_flags & (O_NDELAY|O_NONBLOCK)) mask |= FSX_NONBLOCK; fa.fsx_xflags = xfs_merge_ioc_xflags(flags, xfs_ip2xflags(ip)); fa.fsx_xflags = xfs_merge_ioc_xflags(flags, xfs_ip2xflags(ip)); error = mnt_want_write_file(filp); error = mnt_want_write_file(filp); if (error) if (error) return error; return error; error = xfs_ioctl_setattr(ip, &fa, mask); tp = xfs_ioctl_setattr_get_trans(ip); if (IS_ERR(tp)) { error = PTR_ERR(tp); goto out_drop_write; } error = xfs_ioctl_setattr_xflags(tp, ip, &fa); if (error) { xfs_trans_cancel(tp, 0); goto out_drop_write; } error = xfs_trans_commit(tp, 0); out_drop_write: mnt_drop_write_file(filp); mnt_drop_write_file(filp); return error; return error; } } Loading