]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - fs/btrfs/ioctl.c
btrfs: offline dedupe
[karo-tx-linux.git] / fs / btrfs / ioctl.c
index 238a05545ee2230629fc850191f348b94cadd8cf..022d8364e0726a169088f6a8e8b4b4326c93b017 100644 (file)
@@ -43,6 +43,7 @@
 #include <linux/blkdev.h>
 #include <linux/uuid.h>
 #include <linux/btrfs.h>
+#include <linux/uaccess.h>
 #include "compat.h"
 #include "ctree.h"
 #include "disk-io.h"
@@ -57,6 +58,9 @@
 #include "send.h"
 #include "dev-replace.h"
 
+static int btrfs_clone(struct inode *src, struct inode *inode,
+                      u64 off, u64 olen, u64 olen_aligned, u64 destoff);
+
 /* Mask out flags that are inappropriate for the given type of inode. */
 static inline __u32 btrfs_mask_flags(umode_t mode, __u32 flags)
 {
@@ -396,7 +400,7 @@ static noinline int create_subvol(struct inode *dir,
         * of create_snapshot().
         */
        ret = btrfs_subvolume_reserve_metadata(root, &block_rsv,
-                                              7, &qgroup_reserved);
+                                              7, &qgroup_reserved, false);
        if (ret)
                return ret;
 
@@ -436,15 +440,15 @@ static noinline int create_subvol(struct inode *dir,
        memset(&root_item, 0, sizeof(root_item));
 
        inode_item = &root_item.inode;
-       inode_item->generation = cpu_to_le64(1);
-       inode_item->size = cpu_to_le64(3);
-       inode_item->nlink = cpu_to_le32(1);
-       inode_item->nbytes = cpu_to_le64(root->leafsize);
-       inode_item->mode = cpu_to_le32(S_IFDIR | 0755);
+       btrfs_set_stack_inode_generation(inode_item, 1);
+       btrfs_set_stack_inode_size(inode_item, 3);
+       btrfs_set_stack_inode_nlink(inode_item, 1);
+       btrfs_set_stack_inode_nbytes(inode_item, root->leafsize);
+       btrfs_set_stack_inode_mode(inode_item, S_IFDIR | 0755);
 
-       root_item.flags = 0;
-       root_item.byte_limit = 0;
-       inode_item->flags = cpu_to_le64(BTRFS_INODE_ROOT_ITEM_INIT);
+       btrfs_set_root_flags(&root_item, 0);
+       btrfs_set_root_limit(&root_item, 0);
+       btrfs_set_stack_inode_flags(inode_item, BTRFS_INODE_ROOT_ITEM_INIT);
 
        btrfs_set_root_bytenr(&root_item, leaf->start);
        btrfs_set_root_generation(&root_item, trans->transid);
@@ -457,8 +461,8 @@ static noinline int create_subvol(struct inode *dir,
                        btrfs_root_generation(&root_item));
        uuid_le_gen(&new_uuid);
        memcpy(root_item.uuid, new_uuid.b, BTRFS_UUID_SIZE);
-       root_item.otime.sec = cpu_to_le64(cur_time.tv_sec);
-       root_item.otime.nsec = cpu_to_le32(cur_time.tv_nsec);
+       btrfs_set_stack_timespec_sec(&root_item.otime, cur_time.tv_sec);
+       btrfs_set_stack_timespec_nsec(&root_item.otime, cur_time.tv_nsec);
        root_item.ctime = root_item.otime;
        btrfs_set_root_ctransid(&root_item, trans->transid);
        btrfs_set_root_otransid(&root_item, trans->transid);
@@ -576,7 +580,8 @@ static int create_snapshot(struct btrfs_root *root, struct inode *dir,
         */
        ret = btrfs_subvolume_reserve_metadata(BTRFS_I(dir)->root,
                                        &pending_snapshot->block_rsv, 7,
-                                       &pending_snapshot->qgroup_reserved);
+                                       &pending_snapshot->qgroup_reserved,
+                                       false);
        if (ret)
                goto out;
 
@@ -2174,7 +2179,7 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
         * ref/backref.
         */
        err = btrfs_subvolume_reserve_metadata(root, &block_rsv,
-                                              5, &qgroup_reserved);
+                                              5, &qgroup_reserved, true);
        if (err)
                goto out_up_write;
 
@@ -2469,150 +2474,336 @@ out:
        return ret;
 }
 
-static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
-                                      u64 off, u64 olen, u64 destoff)
+static struct page *extent_same_get_page(struct inode *inode, u64 off)
+{
+       struct page *page;
+       pgoff_t index;
+       struct extent_io_tree *tree = &BTRFS_I(inode)->io_tree;
+
+       index = off >> PAGE_CACHE_SHIFT;
+
+       page = grab_cache_page(inode->i_mapping, index);
+       if (!page)
+               return NULL;
+
+       if (!PageUptodate(page)) {
+               if (extent_read_full_page_nolock(tree, page, btrfs_get_extent,
+                                                0))
+                       return NULL;
+               lock_page(page);
+               if (!PageUptodate(page)) {
+                       unlock_page(page);
+                       page_cache_release(page);
+                       return NULL;
+               }
+       }
+       unlock_page(page);
+
+       return page;
+}
+
+static inline void lock_extent_range(struct inode *inode, u64 off, u64 len)
+{
+       /* do any pending delalloc/csum calc on src, one way or
+          another, and lock file content */
+       while (1) {
+               struct btrfs_ordered_extent *ordered;
+               lock_extent(&BTRFS_I(inode)->io_tree, off, off + len - 1);
+               ordered = btrfs_lookup_first_ordered_extent(inode,
+                                                           off + len - 1);
+               if (!ordered &&
+                   !test_range_bit(&BTRFS_I(inode)->io_tree, off,
+                                   off + len - 1, EXTENT_DELALLOC, 0, NULL))
+                       break;
+               unlock_extent(&BTRFS_I(inode)->io_tree, off, off + len - 1);
+               if (ordered)
+                       btrfs_put_ordered_extent(ordered);
+               btrfs_wait_ordered_range(inode, off, len);
+       }
+}
+
+static void btrfs_double_unlock(struct inode *inode1, u64 loff1,
+                               struct inode *inode2, u64 loff2, u64 len)
+{
+       unlock_extent(&BTRFS_I(inode1)->io_tree, loff1, loff1 + len - 1);
+       unlock_extent(&BTRFS_I(inode2)->io_tree, loff2, loff2 + len - 1);
+
+       mutex_unlock(&inode1->i_mutex);
+       mutex_unlock(&inode2->i_mutex);
+}
+
+static void btrfs_double_lock(struct inode *inode1, u64 loff1,
+                             struct inode *inode2, u64 loff2, u64 len)
+{
+       if (inode1 < inode2) {
+               swap(inode1, inode2);
+               swap(loff1, loff2);
+       }
+
+       mutex_lock_nested(&inode1->i_mutex, I_MUTEX_PARENT);
+       lock_extent_range(inode1, loff1, len);
+       if (inode1 != inode2) {
+               mutex_lock_nested(&inode2->i_mutex, I_MUTEX_CHILD);
+               lock_extent_range(inode2, loff2, len);
+       }
+}
+
+static int btrfs_cmp_data(struct inode *src, u64 loff, struct inode *dst,
+                         u64 dst_loff, u64 len)
+{
+       int ret = 0;
+       struct page *src_page, *dst_page;
+       unsigned int cmp_len = PAGE_CACHE_SIZE;
+       void *addr, *dst_addr;
+
+       while (len) {
+               if (len < PAGE_CACHE_SIZE)
+                       cmp_len = len;
+
+               src_page = extent_same_get_page(src, loff);
+               if (!src_page)
+                       return -EINVAL;
+               dst_page = extent_same_get_page(dst, dst_loff);
+               if (!dst_page) {
+                       page_cache_release(src_page);
+                       return -EINVAL;
+               }
+               addr = kmap_atomic(src_page);
+               dst_addr = kmap_atomic(dst_page);
+
+               flush_dcache_page(src_page);
+               flush_dcache_page(dst_page);
+
+               if (memcmp(addr, dst_addr, cmp_len))
+                       ret = BTRFS_SAME_DATA_DIFFERS;
+
+               kunmap_atomic(addr);
+               kunmap_atomic(dst_addr);
+               page_cache_release(src_page);
+               page_cache_release(dst_page);
+
+               if (ret)
+                       break;
+
+               loff += cmp_len;
+               dst_loff += cmp_len;
+               len -= cmp_len;
+       }
+
+       return ret;
+}
+
+static int extent_same_check_offsets(struct inode *inode, u64 off, u64 len)
+{
+       u64 bs = BTRFS_I(inode)->root->fs_info->sb->s_blocksize;
+
+       if (off + len > inode->i_size || off + len < off)
+               return -EINVAL;
+       /* Check that we are block aligned - btrfs_clone() requires this */
+       if (!IS_ALIGNED(off, bs) || !IS_ALIGNED(off + len, bs))
+               return -EINVAL;
+
+       return 0;
+}
+
+static int btrfs_extent_same(struct inode *src, u64 loff, u64 len,
+                            struct inode *dst, u64 dst_loff)
 {
-       struct inode *inode = file_inode(file);
-       struct btrfs_root *root = BTRFS_I(inode)->root;
-       struct fd src_file;
-       struct inode *src;
-       struct btrfs_trans_handle *trans;
-       struct btrfs_path *path;
-       struct extent_buffer *leaf;
-       char *buf;
-       struct btrfs_key key;
-       u32 nritems;
-       int slot;
        int ret;
-       u64 len = olen;
-       u64 bs = root->fs_info->sb->s_blocksize;
-       int same_inode = 0;
 
        /*
-        * TODO:
-        * - split compressed inline extents.  annoying: we need to
-        *   decompress into destination's address_space (the file offset
-        *   may change, so source mapping won't do), then recompress (or
-        *   otherwise reinsert) a subrange.
-        * - allow ranges within the same file to be cloned (provided
-        *   they don't overlap)?
+        * btrfs_clone() can't handle extents in the same file
+        * yet. Once that works, we can drop this check and replace it
+        * with a check for the same inode, but overlapping extents.
         */
-
-       /* the destination must be opened for writing */
-       if (!(file->f_mode & FMODE_WRITE) || (file->f_flags & O_APPEND))
+       if (src == dst)
                return -EINVAL;
 
-       if (btrfs_root_readonly(root))
-               return -EROFS;
+       btrfs_double_lock(src, loff, dst, dst_loff, len);
+
+       ret = extent_same_check_offsets(src, loff, len);
+       if (ret)
+               goto out_unlock;
+
+       ret = extent_same_check_offsets(dst, dst_loff, len);
+       if (ret)
+               goto out_unlock;
+
+       /* don't make the dst file partly checksummed */
+       if ((BTRFS_I(src)->flags & BTRFS_INODE_NODATASUM) !=
+           (BTRFS_I(dst)->flags & BTRFS_INODE_NODATASUM)) {
+               ret = -EINVAL;
+               goto out_unlock;
+       }
+
+       ret = btrfs_cmp_data(src, loff, dst, dst_loff, len);
+       if (ret == 0)
+               ret = btrfs_clone(src, dst, loff, len, len, dst_loff);
+
+out_unlock:
+       btrfs_double_unlock(src, loff, dst, dst_loff, len);
+
+       return ret;
+}
+
+#define BTRFS_MAX_DEDUPE_LEN   (16 * 1024 * 1024)
+
+static long btrfs_ioctl_file_extent_same(struct file *file,
+                                        void __user *argp)
+{
+       struct btrfs_ioctl_same_args *args = argp;
+       struct btrfs_ioctl_same_args same;
+       struct btrfs_ioctl_same_extent_info info;
+       struct inode *src = file->f_dentry->d_inode;
+       struct file *dst_file = NULL;
+       struct inode *dst;
+       u64 off;
+       u64 len;
+       int i;
+       int ret;
+       u64 bs = BTRFS_I(src)->root->fs_info->sb->s_blocksize;
+       bool is_admin = capable(CAP_SYS_ADMIN);
+
+       if (!(file->f_mode & FMODE_READ))
+               return -EINVAL;
 
        ret = mnt_want_write_file(file);
        if (ret)
                return ret;
 
-       src_file = fdget(srcfd);
-       if (!src_file.file) {
-               ret = -EBADF;
-               goto out_drop_write;
+       if (copy_from_user(&same,
+                          (struct btrfs_ioctl_same_args __user *)argp,
+                          sizeof(same))) {
+               ret = -EFAULT;
+               goto out;
        }
 
-       ret = -EXDEV;
-       if (src_file.file->f_path.mnt != file->f_path.mnt)
-               goto out_fput;
+       off = same.logical_offset;
+       len = same.length;
 
-       src = file_inode(src_file.file);
+       /*
+        * Limit the total length we will dedupe for each operation.
+        * This is intended to bound the total time spent in this
+        * ioctl to something sane.
+        */
+       if (len > BTRFS_MAX_DEDUPE_LEN)
+               len = BTRFS_MAX_DEDUPE_LEN;
 
-       ret = -EINVAL;
-       if (src == inode)
-               same_inode = 1;
+       if (WARN_ON_ONCE(bs < PAGE_CACHE_SIZE)) {
+               /*
+                * Btrfs does not support blocksize < page_size. As a
+                * result, btrfs_cmp_data() won't correctly handle
+                * this situation without an update.
+                */
+               ret = -EINVAL;
+               goto out;
+       }
 
-       /* the src must be open for reading */
-       if (!(src_file.file->f_mode & FMODE_READ))
-               goto out_fput;
+       ret = -EISDIR;
+       if (S_ISDIR(src->i_mode))
+               goto out;
 
-       /* don't make the dst file partly checksummed */
-       if ((BTRFS_I(src)->flags & BTRFS_INODE_NODATASUM) !=
-           (BTRFS_I(inode)->flags & BTRFS_INODE_NODATASUM))
-               goto out_fput;
+       ret = -EACCES;
+       if (!S_ISREG(src->i_mode))
+               goto out;
 
-       ret = -EISDIR;
-       if (S_ISDIR(src->i_mode) || S_ISDIR(inode->i_mode))
-               goto out_fput;
+       ret = 0;
+       for (i = 0; i < same.dest_count; i++) {
+               if (copy_from_user(&info, &args->info[i], sizeof(info))) {
+                       ret = -EFAULT;
+                       goto out;
+               }
 
-       ret = -EXDEV;
-       if (src->i_sb != inode->i_sb)
-               goto out_fput;
+               info.bytes_deduped = 0;
 
-       ret = -ENOMEM;
-       buf = vmalloc(btrfs_level_size(root, 0));
-       if (!buf)
-               goto out_fput;
+               dst_file = fget(info.fd);
+               if (!dst_file) {
+                       info.status = -EBADF;
+                       goto next;
+               }
 
-       path = btrfs_alloc_path();
-       if (!path) {
-               vfree(buf);
-               goto out_fput;
-       }
-       path->reada = 2;
+               if (!(is_admin || (dst_file->f_mode & FMODE_WRITE))) {
+                       info.status = -EINVAL;
+                       goto next;
+               }
 
-       if (!same_inode) {
-               if (inode < src) {
-                       mutex_lock_nested(&inode->i_mutex, I_MUTEX_PARENT);
-                       mutex_lock_nested(&src->i_mutex, I_MUTEX_CHILD);
-               } else {
-                       mutex_lock_nested(&src->i_mutex, I_MUTEX_PARENT);
-                       mutex_lock_nested(&inode->i_mutex, I_MUTEX_CHILD);
+               info.status = -EXDEV;
+               if (file->f_path.mnt != dst_file->f_path.mnt)
+                       goto next;
+
+               dst = dst_file->f_dentry->d_inode;
+               if (src->i_sb != dst->i_sb)
+                       goto next;
+
+               if (S_ISDIR(dst->i_mode)) {
+                       info.status = -EISDIR;
+                       goto next;
                }
-       } else {
-               mutex_lock(&src->i_mutex);
-       }
 
-       /* determine range to clone */
-       ret = -EINVAL;
-       if (off + len > src->i_size || off + len < off)
-               goto out_unlock;
-       if (len == 0)
-               olen = len = src->i_size - off;
-       /* if we extend to eof, continue to block boundary */
-       if (off + len == src->i_size)
-               len = ALIGN(src->i_size, bs) - off;
+               if (!S_ISREG(dst->i_mode)) {
+                       info.status = -EACCES;
+                       goto next;
+               }
 
-       /* verify the end result is block aligned */
-       if (!IS_ALIGNED(off, bs) || !IS_ALIGNED(off + len, bs) ||
-           !IS_ALIGNED(destoff, bs))
-               goto out_unlock;
+               info.status = btrfs_extent_same(src, off, len, dst,
+                                               info.logical_offset);
+               if (info.status == 0)
+                       info.bytes_deduped += len;
 
-       /* verify if ranges are overlapped within the same file */
-       if (same_inode) {
-               if (destoff + len > off && destoff < off + len)
-                       goto out_unlock;
-       }
+next:
+               if (dst_file)
+                       fput(dst_file);
 
-       if (destoff > inode->i_size) {
-               ret = btrfs_cont_expand(inode, inode->i_size, destoff);
-               if (ret)
-                       goto out_unlock;
+               if (__put_user_unaligned(info.status, &args->info[i].status) ||
+                   __put_user_unaligned(info.bytes_deduped,
+                                        &args->info[i].bytes_deduped)) {
+                       ret = -EFAULT;
+                       goto out;
+               }                                                               
        }
 
-       /* truncate page cache pages from target inode range */
-       truncate_inode_pages_range(&inode->i_data, destoff,
-                                  PAGE_CACHE_ALIGN(destoff + len) - 1);
+out:
+       mnt_drop_write_file(file);
+       return ret;
+}
 
-       /* do any pending delalloc/csum calc on src, one way or
-          another, and lock file content */
-       while (1) {
-               struct btrfs_ordered_extent *ordered;
-               lock_extent(&BTRFS_I(src)->io_tree, off, off + len - 1);
-               ordered = btrfs_lookup_first_ordered_extent(src, off + len - 1);
-               if (!ordered &&
-                   !test_range_bit(&BTRFS_I(src)->io_tree, off, off + len - 1,
-                                   EXTENT_DELALLOC, 0, NULL))
-                       break;
-               unlock_extent(&BTRFS_I(src)->io_tree, off, off + len - 1);
-               if (ordered)
-                       btrfs_put_ordered_extent(ordered);
-               btrfs_wait_ordered_range(src, off, len);
+/**
+ * btrfs_clone() - clone a range from inode file to another
+ *
+ * @src: Inode to clone from
+ * @inode: Inode to clone to
+ * @off: Offset within source to start clone from
+ * @olen: Original length, passed by user, of range to clone
+ * @olen_aligned: Block-aligned value of olen, extent_same uses
+ *               identical values here
+ * @destoff: Offset within @inode to start clone
+ */
+static int btrfs_clone(struct inode *src, struct inode *inode,
+                      u64 off, u64 olen, u64 olen_aligned, u64 destoff)
+{
+       struct btrfs_root *root = BTRFS_I(inode)->root;
+       struct btrfs_path *path = NULL;
+       struct extent_buffer *leaf;
+       struct btrfs_trans_handle *trans;
+       char *buf = NULL;
+       struct btrfs_key key;
+       u32 nritems;
+       int slot;
+       int ret;
+       u64 len = olen_aligned;
+
+       ret = -ENOMEM;
+       buf = vmalloc(btrfs_level_size(root, 0));
+       if (!buf)
+               return ret;
+
+       path = btrfs_alloc_path();
+       if (!path) {
+               vfree(buf);
+               return ret;
        }
 
+       path->reada = 2;
        /* clone data */
        key.objectid = btrfs_ino(src);
        key.type = BTRFS_EXTENT_DATA_KEY;
@@ -2858,15 +3049,132 @@ next:
                key.offset++;
        }
        ret = 0;
+
 out:
        btrfs_release_path(path);
+       btrfs_free_path(path);
+       vfree(buf);
+       return ret;
+}
+
+static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
+                                      u64 off, u64 olen, u64 destoff)
+{
+       struct inode *inode = fdentry(file)->d_inode;
+       struct btrfs_root *root = BTRFS_I(inode)->root;
+       struct fd src_file;
+       struct inode *src;
+       int ret;
+       u64 len = olen;
+       u64 bs = root->fs_info->sb->s_blocksize;
+       int same_inode = 0;
+
+       /*
+        * TODO:
+        * - split compressed inline extents.  annoying: we need to
+        *   decompress into destination's address_space (the file offset
+        *   may change, so source mapping won't do), then recompress (or
+        *   otherwise reinsert) a subrange.
+        * - allow ranges within the same file to be cloned (provided
+        *   they don't overlap)?
+        */
+
+       /* the destination must be opened for writing */
+       if (!(file->f_mode & FMODE_WRITE) || (file->f_flags & O_APPEND))
+               return -EINVAL;
+
+       if (btrfs_root_readonly(root))
+               return -EROFS;
+
+       ret = mnt_want_write_file(file);
+       if (ret)
+               return ret;
+
+       src_file = fdget(srcfd);
+       if (!src_file.file) {
+               ret = -EBADF;
+               goto out_drop_write;
+       }
+
+       ret = -EXDEV;
+       if (src_file.file->f_path.mnt != file->f_path.mnt)
+               goto out_fput;
+
+       src = file_inode(src_file.file);
+
+       ret = -EINVAL;
+       if (src == inode)
+               same_inode = 1;
+
+       /* the src must be open for reading */
+       if (!(src_file.file->f_mode & FMODE_READ))
+               goto out_fput;
+
+       /* don't make the dst file partly checksummed */
+       if ((BTRFS_I(src)->flags & BTRFS_INODE_NODATASUM) !=
+           (BTRFS_I(inode)->flags & BTRFS_INODE_NODATASUM))
+               goto out_fput;
+
+       ret = -EISDIR;
+       if (S_ISDIR(src->i_mode) || S_ISDIR(inode->i_mode))
+               goto out_fput;
+
+       ret = -EXDEV;
+       if (src->i_sb != inode->i_sb)
+               goto out_fput;
+
+       if (!same_inode) {
+               if (inode < src) {
+                       mutex_lock_nested(&inode->i_mutex, I_MUTEX_PARENT);
+                       mutex_lock_nested(&src->i_mutex, I_MUTEX_CHILD);
+               } else {
+                       mutex_lock_nested(&src->i_mutex, I_MUTEX_PARENT);
+                       mutex_lock_nested(&inode->i_mutex, I_MUTEX_CHILD);
+               }
+       } else {
+               mutex_lock(&src->i_mutex);
+       }
+
+       /* determine range to clone */
+       ret = -EINVAL;
+       if (off + len > src->i_size || off + len < off)
+               goto out_unlock;
+       if (len == 0)
+               olen = len = src->i_size - off;
+       /* if we extend to eof, continue to block boundary */
+       if (off + len == src->i_size)
+               len = ALIGN(src->i_size, bs) - off;
+
+       /* verify the end result is block aligned */
+       if (!IS_ALIGNED(off, bs) || !IS_ALIGNED(off + len, bs) ||
+           !IS_ALIGNED(destoff, bs))
+               goto out_unlock;
+
+       /* verify if ranges are overlapped within the same file */
+       if (same_inode) {
+               if (destoff + len > off && destoff < off + len)
+                       goto out_unlock;
+       }
+
+       if (destoff > inode->i_size) {
+               ret = btrfs_cont_expand(inode, inode->i_size, destoff);
+               if (ret)
+                       goto out_unlock;
+       }
+
+       /* truncate page cache pages from target inode range */
+       truncate_inode_pages_range(&inode->i_data, destoff,
+                                  PAGE_CACHE_ALIGN(destoff + len) - 1);
+
+       lock_extent_range(src, off, len);
+
+       ret = btrfs_clone(src, inode, off, olen, len, destoff);
+
        unlock_extent(&BTRFS_I(src)->io_tree, off, off + len - 1);
 out_unlock:
        mutex_unlock(&src->i_mutex);
        if (!same_inode)
                mutex_unlock(&inode->i_mutex);
-       vfree(buf);
-       btrfs_free_path(path);
 out_fput:
        fdput(src_file);
 out_drop_write:
@@ -4010,10 +4318,10 @@ static long btrfs_ioctl_set_received_subvol(struct file *file,
        memcpy(root_item->received_uuid, sa->uuid, BTRFS_UUID_SIZE);
        btrfs_set_root_stransid(root_item, sa->stransid);
        btrfs_set_root_rtransid(root_item, sa->rtransid);
-       root_item->stime.sec = cpu_to_le64(sa->stime.sec);
-       root_item->stime.nsec = cpu_to_le32(sa->stime.nsec);
-       root_item->rtime.sec = cpu_to_le64(sa->rtime.sec);
-       root_item->rtime.nsec = cpu_to_le32(sa->rtime.nsec);
+       btrfs_set_stack_timespec_sec(&root_item->stime, sa->stime.sec);
+       btrfs_set_stack_timespec_nsec(&root_item->stime, sa->stime.nsec);
+       btrfs_set_stack_timespec_sec(&root_item->rtime, sa->rtime.sec);
+       btrfs_set_stack_timespec_nsec(&root_item->rtime, sa->rtime.nsec);
 
        ret = btrfs_update_root(trans, root->fs_info->tree_root,
                                &root->root_key, &root->root_item);
@@ -4041,18 +4349,22 @@ out:
 static int btrfs_ioctl_get_fslabel(struct file *file, void __user *arg)
 {
        struct btrfs_root *root = BTRFS_I(file_inode(file))->root;
-       const char *label = root->fs_info->super_copy->label;
-       size_t len = strnlen(label, BTRFS_LABEL_SIZE);
+       size_t len;
        int ret;
+       char label[BTRFS_LABEL_SIZE];
+
+       spin_lock(&root->fs_info->super_lock);
+       memcpy(label, root->fs_info->super_copy->label, BTRFS_LABEL_SIZE);
+       spin_unlock(&root->fs_info->super_lock);
+
+       len = strnlen(label, BTRFS_LABEL_SIZE);
 
        if (len == BTRFS_LABEL_SIZE) {
                pr_warn("btrfs: label is too long, return the first %zu bytes\n",
                        --len);
        }
 
-       mutex_lock(&root->fs_info->volume_mutex);
        ret = copy_to_user(arg, label, len);
-       mutex_unlock(&root->fs_info->volume_mutex);
 
        return ret ? -EFAULT : 0;
 }
@@ -4081,18 +4393,18 @@ static int btrfs_ioctl_set_fslabel(struct file *file, void __user *arg)
        if (ret)
                return ret;
 
-       mutex_lock(&root->fs_info->volume_mutex);
        trans = btrfs_start_transaction(root, 0);
        if (IS_ERR(trans)) {
                ret = PTR_ERR(trans);
                goto out_unlock;
        }
 
+       spin_lock(&root->fs_info->super_lock);
        strcpy(super_block->label, label);
+       spin_unlock(&root->fs_info->super_lock);
        ret = btrfs_end_transaction(trans, root);
 
 out_unlock:
-       mutex_unlock(&root->fs_info->volume_mutex);
        mnt_drop_write_file(file);
        return ret;
 }
@@ -4207,6 +4519,8 @@ long btrfs_ioctl(struct file *file, unsigned int
                return btrfs_ioctl_get_fslabel(file, argp);
        case BTRFS_IOC_SET_FSLABEL:
                return btrfs_ioctl_set_fslabel(file, argp);
+       case BTRFS_IOC_FILE_EXTENT_SAME:
+               return btrfs_ioctl_file_extent_same(file, argp);
        }
 
        return -ENOTTY;