btrfs: add FS_IOC_FSSETXATTR ioctl
[sfrench/cifs-2.6.git] / fs / btrfs / ioctl.c
index c3100f3988e2258f4d4c52dc923cfaae629731ab..28a13b3c750381074472529dc0661d9b2b908307 100644 (file)
@@ -388,6 +388,98 @@ static int btrfs_ioctl_fsgetxattr(struct file *file, void __user *arg)
        return 0;
 }
 
+static int btrfs_ioctl_fssetxattr(struct file *file, void __user *arg)
+{
+       struct inode *inode = file_inode(file);
+       struct btrfs_inode *binode = BTRFS_I(inode);
+       struct btrfs_root *root = binode->root;
+       struct btrfs_trans_handle *trans;
+       struct fsxattr fa;
+       unsigned old_flags;
+       unsigned old_i_flags;
+       int ret = 0;
+
+       if (!inode_owner_or_capable(inode))
+               return -EPERM;
+
+       if (btrfs_root_readonly(root))
+               return -EROFS;
+
+       memset(&fa, 0, sizeof(fa));
+       if (copy_from_user(&fa, arg, sizeof(fa)))
+               return -EFAULT;
+
+       ret = check_xflags(fa.fsx_xflags);
+       if (ret)
+               return ret;
+
+       if (fa.fsx_extsize != 0 || fa.fsx_projid != 0 || fa.fsx_cowextsize != 0)
+               return -EOPNOTSUPP;
+
+       ret = mnt_want_write_file(file);
+       if (ret)
+               return ret;
+
+       inode_lock(inode);
+
+       old_flags = binode->flags;
+       old_i_flags = inode->i_flags;
+
+       /* We need the capabilities to change append-only or immutable inode */
+       if (((old_flags & (BTRFS_INODE_APPEND | BTRFS_INODE_IMMUTABLE)) ||
+            (fa.fsx_xflags & (FS_XFLAG_APPEND | FS_XFLAG_IMMUTABLE))) &&
+           !capable(CAP_LINUX_IMMUTABLE)) {
+               ret = -EPERM;
+               goto out_unlock;
+       }
+
+       if (fa.fsx_xflags & FS_XFLAG_SYNC)
+               binode->flags |= BTRFS_INODE_SYNC;
+       else
+               binode->flags &= ~BTRFS_INODE_SYNC;
+       if (fa.fsx_xflags & FS_XFLAG_IMMUTABLE)
+               binode->flags |= BTRFS_INODE_IMMUTABLE;
+       else
+               binode->flags &= ~BTRFS_INODE_IMMUTABLE;
+       if (fa.fsx_xflags & FS_XFLAG_APPEND)
+               binode->flags |= BTRFS_INODE_APPEND;
+       else
+               binode->flags &= ~BTRFS_INODE_APPEND;
+       if (fa.fsx_xflags & FS_XFLAG_NODUMP)
+               binode->flags |= BTRFS_INODE_NODUMP;
+       else
+               binode->flags &= ~BTRFS_INODE_NODUMP;
+       if (fa.fsx_xflags & FS_XFLAG_NOATIME)
+               binode->flags |= BTRFS_INODE_NOATIME;
+       else
+               binode->flags &= ~BTRFS_INODE_NOATIME;
+
+       /* 1 item for the inode */
+       trans = btrfs_start_transaction(root, 1);
+       if (IS_ERR(trans)) {
+               ret = PTR_ERR(trans);
+               goto out_unlock;
+       }
+
+       btrfs_sync_inode_flags_to_i_flags(inode);
+       inode_inc_iversion(inode);
+       inode->i_ctime = current_time(inode);
+       ret = btrfs_update_inode(trans, root, inode);
+
+       btrfs_end_transaction(trans);
+
+out_unlock:
+       if (ret) {
+               binode->flags = old_flags;
+               inode->i_flags = old_i_flags;
+       }
+
+       inode_unlock(inode);
+       mnt_drop_write_file(file);
+
+       return ret;
+}
+
 static int btrfs_ioctl_getversion(struct file *file, int __user *arg)
 {
        struct inode *inode = file_inode(file);
@@ -5429,6 +5521,8 @@ long btrfs_ioctl(struct file *file, unsigned int
                return btrfs_ioctl_set_features(file, argp);
        case FS_IOC_FSGETXATTR:
                return btrfs_ioctl_fsgetxattr(file, argp);
+       case FS_IOC_FSSETXATTR:
+               return btrfs_ioctl_fssetxattr(file, argp);
        }
 
        return -ENOTTY;