Btrfs: create a pinned em when writing to a prealloc range in DIO
authorJosef Bacik <jbacik@fusionio.com>
Tue, 11 Sep 2012 19:40:07 +0000 (15:40 -0400)
committerChris Mason <chris.mason@fusionio.com>
Mon, 1 Oct 2012 19:19:20 +0000 (15:19 -0400)
Wade Cline reported a problem where he was getting garbage and warnings when
writing to a preallocated range via O_DIRECT.  This is because we weren't
creating our normal pinned extent_map for the range we were writing to,
which was causing all sorts of issues.  This patch fixes the problem and
makes his testcase much happier.  Thanks,

Reported-by: Wade Cline <clinew@linux.vnet.ibm.com>
Signed-off-by: Josef Bacik <jbacik@fusionio.com>
fs/btrfs/inode.c

index 9e9754adf7a4cf275185b91cdf8f8d68b48b2482..406666cb6156189d5623246d4ae2271a96116e83 100644 (file)
@@ -5898,6 +5898,48 @@ static int lock_extent_direct(struct inode *inode, u64 lockstart, u64 lockend,
        return ret;
 }
 
+static struct extent_map *create_pinned_em(struct inode *inode, u64 start,
+                                          u64 len, u64 orig_start,
+                                          u64 block_start, u64 block_len,
+                                          int type)
+{
+       struct extent_map_tree *em_tree;
+       struct extent_map *em;
+       struct btrfs_root *root = BTRFS_I(inode)->root;
+       int ret;
+
+       em_tree = &BTRFS_I(inode)->extent_tree;
+       em = alloc_extent_map();
+       if (!em)
+               return ERR_PTR(-ENOMEM);
+
+       em->start = start;
+       em->orig_start = orig_start;
+       em->len = len;
+       em->block_len = block_len;
+       em->block_start = block_start;
+       em->bdev = root->fs_info->fs_devices->latest_bdev;
+       set_bit(EXTENT_FLAG_PINNED, &em->flags);
+       if (type == BTRFS_ORDERED_PREALLOC)
+               set_bit(EXTENT_FLAG_PREALLOC, &em->flags);
+
+       do {
+               btrfs_drop_extent_cache(inode, em->start,
+                               em->start + em->len - 1, 0);
+               write_lock(&em_tree->lock);
+               ret = add_extent_mapping(em_tree, em);
+               write_unlock(&em_tree->lock);
+       } while (ret == -EEXIST);
+
+       if (ret) {
+               free_extent_map(em);
+               return ERR_PTR(ret);
+       }
+
+       return em;
+}
+
+
 static int btrfs_get_blocks_direct(struct inode *inode, sector_t iblock,
                                   struct buffer_head *bh_result, int create)
 {
@@ -6012,6 +6054,19 @@ static int btrfs_get_blocks_direct(struct inode *inode, sector_t iblock,
                        goto must_cow;
 
                if (can_nocow_odirect(trans, inode, start, len) == 1) {
+                       u64 orig_start = em->start;
+
+                       if (type == BTRFS_ORDERED_PREALLOC) {
+                               free_extent_map(em);
+                               em = create_pinned_em(inode, start, len,
+                                                      orig_start,
+                                                      block_start, len, type);
+                               if (IS_ERR(em)) {
+                                       btrfs_end_transaction(trans, root);
+                                       goto unlock_err;
+                               }
+                       }
+
                        ret = btrfs_add_ordered_extent_dio(inode, start,
                                           block_start, len, len, type);
                        btrfs_end_transaction(trans, root);