@@ -1038,6 +1038,41 @@ xfs_ioctl_setattr_flush(
return filemap_write_and_wait(inode->i_mapping);
}
+/*
+ * If immutable is set and we are not clearing it, we're not allowed to change
+ * anything else in the inode. Don't error out if we're only trying to set
+ * immutable on an immutable file.
+ */
+static int
+xfs_ioctl_setattr_immutable(
+ struct xfs_inode *ip,
+ struct fsxattr *fa,
+ uint16_t di_flags,
+ uint64_t di_flags2)
+{
+ struct xfs_mount *mp = ip->i_mount;
+
+ if (!(ip->i_d.di_flags & XFS_DIFLAG_IMMUTABLE) ||
+ !(di_flags & XFS_DIFLAG_IMMUTABLE))
+ return 0;
+
+ if ((ip->i_d.di_flags & ~XFS_DIFLAG_IMMUTABLE) !=
+ (di_flags & ~XFS_DIFLAG_IMMUTABLE))
+ return -EPERM;
+ if (ip->i_d.di_version >= 3 && ip->i_d.di_flags2 != di_flags2)
+ return -EPERM;
+ if (xfs_get_projid(ip) != fa->fsx_projid)
+ return -EPERM;
+ if ((di_flags & (XFS_DIFLAG_EXTSIZE | XFS_DIFLAG_EXTSZINHERIT)) &&
+ ip->i_d.di_extsize != fa->fsx_extsize >> mp->m_sb.sb_blocklog)
+ return -EPERM;
+ if (ip->i_d.di_version >= 3 && (di_flags2 & XFS_DIFLAG2_COWEXTSIZE) &&
+ ip->i_d.di_cowextsize != fa->fsx_cowextsize >> mp->m_sb.sb_blocklog)
+ return -EPERM;
+
+ return 0;
+}
+
static int
xfs_ioctl_setattr_xflags(
struct xfs_trans *tp,
@@ -1045,7 +1080,9 @@ xfs_ioctl_setattr_xflags(
struct fsxattr *fa)
{
struct xfs_mount *mp = ip->i_mount;
+ uint16_t di_flags;
uint64_t di_flags2;
+ int error;
/* Can't change realtime flag if any extents are allocated. */
if ((ip->i_d.di_nextents || ip->i_delayed_blks) &&
@@ -1076,12 +1113,18 @@ xfs_ioctl_setattr_xflags(
!capable(CAP_LINUX_IMMUTABLE))
return -EPERM;
- /* diflags2 only valid for v3 inodes. */
+ /* Don't allow changes to an immutable inode. */
+ di_flags = xfs_flags2diflags(ip, fa->fsx_xflags);
di_flags2 = xfs_flags2diflags2(ip, fa->fsx_xflags);
+ error = xfs_ioctl_setattr_immutable(ip, fa, di_flags, di_flags2);
+ if (error)
+ return error;
+
+ /* diflags2 only valid for v3 inodes. */
if (di_flags2 && ip->i_d.di_version < 3)
return -EINVAL;
- ip->i_d.di_flags = xfs_flags2diflags(ip, fa->fsx_xflags);
+ ip->i_d.di_flags = di_flags;
ip->i_d.di_flags2 = di_flags2;
xfs_diflags_to_linux(ip);