Btrfs: fix checks in BTRFS_IOC_CLONE_RANGE
[sfrench/cifs-2.6.git] / fs / btrfs / ioctl.c
index e84ef60ffe35bc13830e78754c6698a135e7683c..9254b3d58dbef22974af3c7c61dbc9a4af7a33b6 100644 (file)
@@ -239,23 +239,19 @@ static noinline int create_subvol(struct btrfs_root *root,
        u64 new_dirid = BTRFS_FIRST_FREE_OBJECTID;
        u64 index = 0;
 
+       ret = btrfs_find_free_objectid(NULL, root->fs_info->tree_root,
+                                      0, &objectid);
+       if (ret)
+               return ret;
        /*
         * 1 - inode item
         * 2 - refs
         * 1 - root item
         * 2 - dir items
         */
-       ret = btrfs_reserve_metadata_space(root, 6);
-       if (ret)
-               return ret;
-
-       trans = btrfs_start_transaction(root, 1);
-       BUG_ON(!trans);
-
-       ret = btrfs_find_free_objectid(trans, root->fs_info->tree_root,
-                                      0, &objectid);
-       if (ret)
-               goto fail;
+       trans = btrfs_start_transaction(root, 6);
+       if (IS_ERR(trans))
+               return PTR_ERR(trans);
 
        leaf = btrfs_alloc_free_block(trans, root, root->leafsize,
                                      0, objectid, NULL, 0, 0, 0);
@@ -345,13 +341,10 @@ fail:
        err = btrfs_commit_transaction(trans, root);
        if (err && !ret)
                ret = err;
-
-       btrfs_unreserve_metadata_space(root, 6);
        return ret;
 }
 
-static int create_snapshot(struct btrfs_root *root, struct dentry *dentry,
-                          char *name, int namelen)
+static int create_snapshot(struct btrfs_root *root, struct dentry *dentry)
 {
        struct inode *inode;
        struct btrfs_pending_snapshot *pending_snapshot;
@@ -361,40 +354,33 @@ static int create_snapshot(struct btrfs_root *root, struct dentry *dentry,
        if (!root->ref_cows)
                return -EINVAL;
 
-       /*
-        * 1 - inode item
-        * 2 - refs
-        * 1 - root item
-        * 2 - dir items
-        */
-       ret = btrfs_reserve_metadata_space(root, 6);
-       if (ret)
-               goto fail;
-
        pending_snapshot = kzalloc(sizeof(*pending_snapshot), GFP_NOFS);
-       if (!pending_snapshot) {
-               ret = -ENOMEM;
-               btrfs_unreserve_metadata_space(root, 6);
-               goto fail;
-       }
-       pending_snapshot->name = kmalloc(namelen + 1, GFP_NOFS);
-       if (!pending_snapshot->name) {
-               ret = -ENOMEM;
-               kfree(pending_snapshot);
-               btrfs_unreserve_metadata_space(root, 6);
-               goto fail;
-       }
-       memcpy(pending_snapshot->name, name, namelen);
-       pending_snapshot->name[namelen] = '\0';
+       if (!pending_snapshot)
+               return -ENOMEM;
+
+       btrfs_init_block_rsv(&pending_snapshot->block_rsv);
        pending_snapshot->dentry = dentry;
-       trans = btrfs_start_transaction(root, 1);
-       BUG_ON(!trans);
        pending_snapshot->root = root;
+
+       trans = btrfs_start_transaction(root->fs_info->extent_root, 5);
+       if (IS_ERR(trans)) {
+               ret = PTR_ERR(trans);
+               goto fail;
+       }
+
+       ret = btrfs_snap_reserve_metadata(trans, pending_snapshot);
+       BUG_ON(ret);
+
        list_add(&pending_snapshot->list,
                 &trans->transaction->pending_snapshots);
-       ret = btrfs_commit_transaction(trans, root);
+       ret = btrfs_commit_transaction(trans, root->fs_info->extent_root);
        BUG_ON(ret);
-       btrfs_unreserve_metadata_space(root, 6);
+
+       ret = pending_snapshot->error;
+       if (ret)
+               goto fail;
+
+       btrfs_orphan_cleanup(pending_snapshot->snap);
 
        inode = btrfs_lookup_dentry(dentry->d_parent->d_inode, dentry);
        if (IS_ERR(inode)) {
@@ -405,6 +391,7 @@ static int create_snapshot(struct btrfs_root *root, struct dentry *dentry,
        d_instantiate(dentry, inode);
        ret = 0;
 fail:
+       kfree(pending_snapshot);
        return ret;
 }
 
@@ -456,8 +443,7 @@ static noinline int btrfs_mksubvol(struct path *parent,
                goto out_up_read;
 
        if (snap_src) {
-               error = create_snapshot(snap_src, dentry,
-                                       name, namelen);
+               error = create_snapshot(snap_src, dentry);
        } else {
                error = create_subvol(BTRFS_I(dir)->root, dentry,
                                      name, namelen);
@@ -601,19 +587,9 @@ static int btrfs_defrag_file(struct file *file,
                if (range->flags & BTRFS_DEFRAG_RANGE_COMPRESS)
                        BTRFS_I(inode)->force_compress = 1;
 
-               ret = btrfs_check_data_free_space(root, inode, PAGE_CACHE_SIZE);
-               if (ret) {
-                       ret = -ENOSPC;
-                       break;
-               }
-
-               ret = btrfs_reserve_metadata_for_delalloc(root, inode, 1);
-               if (ret) {
-                       btrfs_free_reserved_data_space(root, inode,
-                                                      PAGE_CACHE_SIZE);
-                       ret = -ENOSPC;
-                       break;
-               }
+               ret  = btrfs_delalloc_reserve_space(inode, PAGE_CACHE_SIZE);
+               if (ret)
+                       goto err_unlock;
 again:
                if (inode->i_size == 0 ||
                    i > ((inode->i_size - 1) >> PAGE_CACHE_SHIFT)) {
@@ -622,8 +598,10 @@ again:
                }
 
                page = grab_cache_page(inode->i_mapping, i);
-               if (!page)
+               if (!page) {
+                       ret = -ENOMEM;
                        goto err_reservations;
+               }
 
                if (!PageUptodate(page)) {
                        btrfs_readpage(NULL, page);
@@ -631,6 +609,7 @@ again:
                        if (!PageUptodate(page)) {
                                unlock_page(page);
                                page_cache_release(page);
+                               ret = -EIO;
                                goto err_reservations;
                        }
                }
@@ -644,8 +623,7 @@ again:
                wait_on_page_writeback(page);
 
                if (PageDirty(page)) {
-                       btrfs_free_reserved_data_space(root, inode,
-                                                      PAGE_CACHE_SIZE);
+                       btrfs_delalloc_release_space(inode, PAGE_CACHE_SIZE);
                        goto loop_unlock;
                }
 
@@ -683,7 +661,6 @@ loop_unlock:
                page_cache_release(page);
                mutex_unlock(&inode->i_mutex);
 
-               btrfs_unreserve_metadata_for_delalloc(root, inode, 1);
                balance_dirty_pages_ratelimited_nr(inode->i_mapping, 1);
                i++;
        }
@@ -713,9 +690,9 @@ loop_unlock:
        return 0;
 
 err_reservations:
+       btrfs_delalloc_release_space(inode, PAGE_CACHE_SIZE);
+err_unlock:
        mutex_unlock(&inode->i_mutex);
-       btrfs_free_reserved_data_space(root, inode, PAGE_CACHE_SIZE);
-       btrfs_unreserve_metadata_for_delalloc(root, inode, 1);
        return ret;
 }
 
@@ -811,7 +788,7 @@ static noinline int btrfs_ioctl_resize(struct btrfs_root *root,
                device->name, (unsigned long long)new_size);
 
        if (new_size > old_size) {
-               trans = btrfs_start_transaction(root, 1);
+               trans = btrfs_start_transaction(root, 0);
                ret = btrfs_grow_device(trans, device, new_size);
                btrfs_commit_transaction(trans, root);
        } else {
@@ -1300,7 +1277,13 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
        if (err)
                goto out_up_write;
 
-       trans = btrfs_start_transaction(root, 1);
+       trans = btrfs_start_transaction(root, 0);
+       if (IS_ERR(trans)) {
+               err = PTR_ERR(trans);
+               goto out_up_write;
+       }
+       trans->block_rsv = &root->fs_info->global_block_rsv;
+
        ret = btrfs_unlink_subvol(trans, root, dir,
                                dest->root_key.objectid,
                                dentry->d_name.name,
@@ -1314,10 +1297,12 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
        dest->root_item.drop_level = 0;
        btrfs_set_root_refs(&dest->root_item, 0);
 
-       ret = btrfs_insert_orphan_item(trans,
-                               root->fs_info->tree_root,
-                               dest->root_key.objectid);
-       BUG_ON(ret);
+       if (!xchg(&dest->orphan_item_inserted, 1)) {
+               ret = btrfs_insert_orphan_item(trans,
+                                       root->fs_info->tree_root,
+                                       dest->root_key.objectid);
+               BUG_ON(ret);
+       }
 
        ret = btrfs_commit_transaction(trans, root);
        BUG_ON(ret);
@@ -1358,8 +1343,10 @@ static int btrfs_ioctl_defrag(struct file *file, void __user *argp)
                        ret = -EPERM;
                        goto out;
                }
-               btrfs_defrag_root(root, 0);
-               btrfs_defrag_root(root->fs_info->extent_root, 0);
+               ret = btrfs_defrag_root(root, 0);
+               if (ret)
+                       goto out;
+               ret = btrfs_defrag_root(root->fs_info->extent_root, 0);
                break;
        case S_IFREG:
                if (!(file->f_mode & FMODE_WRITE)) {
@@ -1389,9 +1376,11 @@ static int btrfs_ioctl_defrag(struct file *file, void __user *argp)
                        /* the rest are all set to zero by kzalloc */
                        range->len = (u64)-1;
                }
-               btrfs_defrag_file(file, range);
+               ret = btrfs_defrag_file(file, range);
                kfree(range);
                break;
+       default:
+               ret = -EINVAL;
        }
 out:
        mnt_drop_write(file->f_path.mnt);
@@ -1469,7 +1458,7 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
         */
 
        /* the destination must be opened for writing */
-       if (!(file->f_mode & FMODE_WRITE))
+       if (!(file->f_mode & FMODE_WRITE) || (file->f_flags & O_APPEND))
                return -EINVAL;
 
        ret = mnt_want_write(file->f_path.mnt);
@@ -1481,12 +1470,17 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
                ret = -EBADF;
                goto out_drop_write;
        }
+
        src = src_file->f_dentry->d_inode;
 
        ret = -EINVAL;
        if (src == inode)
                goto out_fput;
 
+       /* the src must be open for reading */
+       if (!(src_file->f_mode & FMODE_READ))
+               goto out_fput;
+
        ret = -EISDIR;
        if (S_ISDIR(src->i_mode) || S_ISDIR(inode->i_mode))
                goto out_fput;
@@ -1517,7 +1511,7 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
 
        /* determine range to clone */
        ret = -EINVAL;
-       if (off >= src->i_size || off + len > src->i_size)
+       if (off + len > src->i_size || off + len < off)
                goto out_unlock;
        if (len == 0)
                olen = len = src->i_size - off;
@@ -1545,12 +1539,6 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
                btrfs_wait_ordered_range(src, off, off+len);
        }
 
-       trans = btrfs_start_transaction(root, 1);
-       BUG_ON(!trans);
-
-       /* punch hole in destination first */
-       btrfs_drop_extents(trans, inode, off, off + len, &hint_byte, 1);
-
        /* clone data */
        key.objectid = src->i_ino;
        key.type = BTRFS_EXTENT_DATA_KEY;
@@ -1561,7 +1549,7 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
                 * note the key will change type as we walk through the
                 * tree.
                 */
-               ret = btrfs_search_slot(trans, root, &key, path, 0, 0);
+               ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
                if (ret < 0)
                        goto out;
 
@@ -1590,6 +1578,7 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
                        u64 disko = 0, diskl = 0;
                        u64 datao = 0, datal = 0;
                        u8 comp;
+                       u64 endoff;
 
                        size = btrfs_item_size_nr(leaf, slot);
                        read_extent_buffer(leaf, buf,
@@ -1624,12 +1613,31 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
                        new_key.objectid = inode->i_ino;
                        new_key.offset = key.offset + destoff - off;
 
+                       trans = btrfs_start_transaction(root, 1);
+                       if (IS_ERR(trans)) {
+                               ret = PTR_ERR(trans);
+                               goto out;
+                       }
+
                        if (type == BTRFS_FILE_EXTENT_REG ||
                            type == BTRFS_FILE_EXTENT_PREALLOC) {
+                               if (off > key.offset) {
+                                       datao += off - key.offset;
+                                       datal -= off - key.offset;
+                               }
+
+                               if (key.offset + datal > off + len)
+                                       datal = off + len - key.offset;
+
+                               ret = btrfs_drop_extents(trans, inode,
+                                                        new_key.offset,
+                                                        new_key.offset + datal,
+                                                        &hint_byte, 1);
+                               BUG_ON(ret);
+
                                ret = btrfs_insert_empty_item(trans, root, path,
                                                              &new_key, size);
-                               if (ret)
-                                       goto out;
+                               BUG_ON(ret);
 
                                leaf = path->nodes[0];
                                slot = path->slots[0];
@@ -1640,14 +1648,6 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
                                extent = btrfs_item_ptr(leaf, slot,
                                                struct btrfs_file_extent_item);
 
-                               if (off > key.offset) {
-                                       datao += off - key.offset;
-                                       datal -= off - key.offset;
-                               }
-
-                               if (key.offset + datal > off + len)
-                                       datal = off + len - key.offset;
-
                                /* disko == 0 means it's a hole */
                                if (!disko)
                                        datao = 0;
@@ -1678,14 +1678,21 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
 
                                if (comp && (skip || trim)) {
                                        ret = -EINVAL;
+                                       btrfs_end_transaction(trans, root);
                                        goto out;
                                }
                                size -= skip + trim;
                                datal -= skip + trim;
+
+                               ret = btrfs_drop_extents(trans, inode,
+                                                        new_key.offset,
+                                                        new_key.offset + datal,
+                                                        &hint_byte, 1);
+                               BUG_ON(ret);
+
                                ret = btrfs_insert_empty_item(trans, root, path,
                                                              &new_key, size);
-                               if (ret)
-                                       goto out;
+                               BUG_ON(ret);
 
                                if (skip) {
                                        u32 start =
@@ -1703,8 +1710,26 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
                        }
 
                        btrfs_mark_buffer_dirty(leaf);
-               }
+                       btrfs_release_path(root, path);
+
+                       inode->i_mtime = inode->i_ctime = CURRENT_TIME;
 
+                       /*
+                        * we round up to the block size at eof when
+                        * determining which extents to clone above,
+                        * but shouldn't round up the file size
+                        */
+                       endoff = new_key.offset + datal;
+                       if (endoff > off+olen)
+                               endoff = off+olen;
+                       if (endoff > inode->i_size)
+                               btrfs_i_size_write(inode, endoff);
+
+                       BTRFS_I(inode)->flags = BTRFS_I(src)->flags;
+                       ret = btrfs_update_inode(trans, root, inode);
+                       BUG_ON(ret);
+                       btrfs_end_transaction(trans, root);
+               }
 next:
                btrfs_release_path(root, path);
                key.offset++;
@@ -1712,17 +1737,7 @@ next:
        ret = 0;
 out:
        btrfs_release_path(root, path);
-       if (ret == 0) {
-               inode->i_mtime = inode->i_ctime = CURRENT_TIME;
-               if (destoff + olen > inode->i_size)
-                       btrfs_i_size_write(inode, destoff + olen);
-               BTRFS_I(inode)->flags = BTRFS_I(src)->flags;
-               ret = btrfs_update_inode(trans, root, inode);
-       }
-       btrfs_end_transaction(trans, root);
        unlock_extent(&BTRFS_I(src)->io_tree, off, off+len, GFP_NOFS);
-       if (ret)
-               vmtruncate(inode, 0);
 out_unlock:
        mutex_unlock(&src->i_mutex);
        mutex_unlock(&inode->i_mutex);
@@ -1840,7 +1855,7 @@ static long btrfs_ioctl_default_subvol(struct file *file, void __user *argp)
        dir_id = btrfs_super_root_dir(&root->fs_info->super_copy);
        di = btrfs_lookup_dir_item(trans, root->fs_info->tree_root, path,
                                   dir_id, "default", 7, 1);
-       if (!di) {
+       if (IS_ERR_OR_NULL(di)) {
                btrfs_free_path(path);
                btrfs_end_transaction(trans, root);
                printk(KERN_ERR "Umm, you don't have the default dir item, "