summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/btrfs/ioctl.c2
-rw-r--r--fs/ext4/ioctl.c2
-rw-r--r--fs/f2fs/file.c2
-rw-r--r--fs/inode.c31
-rw-r--r--fs/xfs/xfs_ioctl.c71
-rw-r--r--include/linux/fs.h3
6 files changed, 90 insertions, 21 deletions
diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index 3cd66efdb99d..aeffe3fd99c4 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -420,7 +420,7 @@ static int btrfs_ioctl_fssetxattr(struct file *file, void __user *arg)
simple_fill_fsxattr(&old_fa,
btrfs_inode_flags_to_xflags(binode->flags));
- ret = vfs_ioc_fssetxattr_check(inode, &old_fa, &fa);
+ ret = vfs_ioc_fssetxattr_prepare(inode, &old_fa, &fa);
if (ret)
goto out_unlock;
diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c
index 566dfac28b3f..69810e59f89a 100644
--- a/fs/ext4/ioctl.c
+++ b/fs/ext4/ioctl.c
@@ -1109,7 +1109,7 @@ resizefs_out:
inode_lock(inode);
ext4_fill_fsxattr(inode, &old_fa);
- err = vfs_ioc_fssetxattr_check(inode, &old_fa, &fa);
+ err = vfs_ioc_fssetxattr_prepare(inode, &old_fa, &fa);
if (err)
goto out;
flags = (ei->i_flags & ~EXT4_FL_XFLAG_VISIBLE) |
diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index 8799468724f9..b47f22eb483e 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -2825,7 +2825,7 @@ static int f2fs_ioc_fssetxattr(struct file *filp, unsigned long arg)
inode_lock(inode);
f2fs_fill_fsxattr(inode, &old_fa);
- err = vfs_ioc_fssetxattr_check(inode, &old_fa, &fa);
+ err = vfs_ioc_fssetxattr_prepare(inode, &old_fa, &fa);
if (err)
goto out;
flags = (fi->i_flags & ~F2FS_FL_XFLAG_VISIBLE) |
diff --git a/fs/inode.c b/fs/inode.c
index 65a412af3ffb..cf07378e5731 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -2293,3 +2293,34 @@ int vfs_ioc_fssetxattr_check(struct inode *inode, const struct fsxattr *old_fa,
return 0;
}
EXPORT_SYMBOL(vfs_ioc_fssetxattr_check);
+
+/*
+ * Generic function to check FS_IOC_FSSETXATTR values and reject any invalid
+ * configurations. If none are found, flush all pending IO and dirty mappings
+ * before setting S_IMMUTABLE on an inode. If the flush fails we'll clear the
+ * flag before returning error.
+ *
+ * Note: the caller must hold whatever locks are necessary to block any other
+ * threads from starting a write to the file.
+ */
+int vfs_ioc_fssetxattr_prepare(struct inode *inode,
+ const struct fsxattr *old_fa,
+ struct fsxattr *fa)
+{
+ int ret;
+
+ ret = vfs_ioc_fssetxattr_check(inode, old_fa, fa);
+ if (ret)
+ return ret;
+
+ if (!S_ISREG(inode->i_mode) || IS_IMMUTABLE(inode) ||
+ !(fa->fsx_xflags & FS_XFLAG_IMMUTABLE))
+ return 0;
+
+ inode_set_flags(inode, S_IMMUTABLE, S_IMMUTABLE);
+ ret = inode_drain_writes(inode);
+ if (ret)
+ inode_set_flags(inode, 0, S_IMMUTABLE);
+ return ret;
+}
+EXPORT_SYMBOL(vfs_ioc_fssetxattr_prepare);
diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
index fe29aa61293c..552f18554c48 100644
--- a/fs/xfs/xfs_ioctl.c
+++ b/fs/xfs/xfs_ioctl.c
@@ -1058,12 +1058,39 @@ xfs_ioctl_setattr_xflags(
}
/*
+ * If we're setting immutable on a regular file, we need to prevent new writes.
+ * Once we've done that, we must wait for all the other writes to complete.
+ *
+ * The caller must use @join_flags to release the locks which are held on @ip
+ * regardless of return value.
+ */
+static int
+xfs_ioctl_setattr_drain_writes(
+ struct xfs_inode *ip,
+ const struct fsxattr *fa,
+ int *join_flags)
+{
+ struct inode *inode = VFS_I(ip);
+
+ if (!S_ISREG(inode->i_mode) || !(fa->fsx_xflags & FS_XFLAG_IMMUTABLE))
+ return 0;
+
+ *join_flags = XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL;
+ xfs_ilock(ip, *join_flags);
+
+ return inode_drain_writes(inode);
+}
+
+/*
* If we are changing DAX flags, we have to ensure the file is clean and any
* cached objects in the address space are invalidated and removed. This
* requires us to lock out other IO and page faults similar to a truncate
* operation. The locks need to be held until the transaction has been committed
* so that the cache invalidation is atomic with respect to the DAX flag
* manipulation.
+ *
+ * The caller must use @join_flags to release the locks which are held on @ip
+ * regardless of return value.
*/
static int
xfs_ioctl_setattr_dax_invalidate(
@@ -1075,8 +1102,6 @@ xfs_ioctl_setattr_dax_invalidate(
struct super_block *sb = inode->i_sb;
int error;
- *join_flags = 0;
-
/*
* It is only valid to set the DAX flag on regular files and
* directories on filesystems where the block size is equal to the page
@@ -1102,21 +1127,15 @@ xfs_ioctl_setattr_dax_invalidate(
return 0;
/* lock, flush and invalidate mapping in preparation for flag change */
- xfs_ilock(ip, XFS_MMAPLOCK_EXCL | XFS_IOLOCK_EXCL);
- error = filemap_write_and_wait(inode->i_mapping);
- if (error)
- goto out_unlock;
- error = invalidate_inode_pages2(inode->i_mapping);
- if (error)
- goto out_unlock;
-
- *join_flags = XFS_MMAPLOCK_EXCL | XFS_IOLOCK_EXCL;
- return 0;
-
-out_unlock:
- xfs_iunlock(ip, XFS_MMAPLOCK_EXCL | XFS_IOLOCK_EXCL);
- return error;
+ if (*join_flags == 0) {
+ *join_flags = XFS_MMAPLOCK_EXCL | XFS_IOLOCK_EXCL;
+ xfs_ilock(ip, *join_flags);
+ error = filemap_write_and_wait(inode->i_mapping);
+ if (error)
+ return error;
+ }
+ return invalidate_inode_pages2(inode->i_mapping);
}
/*
@@ -1325,6 +1344,12 @@ xfs_ioctl_setattr(
return code;
}
+ code = xfs_ioctl_setattr_drain_writes(ip, fa, &join_flags);
+ if (code) {
+ xfs_iunlock(ip, join_flags);
+ goto error_free_dquots;
+ }
+
/*
* Changing DAX config may require inode locking for mapping
* invalidation. These need to be held all the way to transaction commit
@@ -1333,8 +1358,10 @@ xfs_ioctl_setattr(
* appropriately.
*/
code = xfs_ioctl_setattr_dax_invalidate(ip, fa, &join_flags);
- if (code)
+ if (code) {
+ xfs_iunlock(ip, join_flags);
goto error_free_dquots;
+ }
tp = xfs_ioctl_setattr_get_trans(ip, join_flags);
if (IS_ERR(tp)) {
@@ -1484,6 +1511,12 @@ xfs_ioc_setxflags(
if (error)
return error;
+ error = xfs_ioctl_setattr_drain_writes(ip, &fa, &join_flags);
+ if (error) {
+ xfs_iunlock(ip, join_flags);
+ goto out_drop_write;
+ }
+
/*
* Changing DAX config may require inode locking for mapping
* invalidation. These need to be held all the way to transaction commit
@@ -1492,8 +1525,10 @@ xfs_ioc_setxflags(
* appropriately.
*/
error = xfs_ioctl_setattr_dax_invalidate(ip, &fa, &join_flags);
- if (error)
+ if (error) {
+ xfs_iunlock(ip, join_flags);
goto out_drop_write;
+ }
tp = xfs_ioctl_setattr_get_trans(ip, join_flags);
if (IS_ERR(tp)) {
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 0efe749de577..73a8bd789e36 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -3560,6 +3560,9 @@ int vfs_ioc_setflags_prepare(struct inode *inode, unsigned int oldflags,
int vfs_ioc_fssetxattr_check(struct inode *inode, const struct fsxattr *old_fa,
struct fsxattr *fa);
+int vfs_ioc_fssetxattr_prepare(struct inode *inode,
+ const struct fsxattr *old_fa,
+ struct fsxattr *fa);
static inline void simple_fill_fsxattr(struct fsxattr *fa, __u32 xflags)
{