Btrfs: fix NULL pointer dereference in log_dir_items
[sfrench/cifs-2.6.git] / fs / btrfs / tree-log.c
index bbd8a40b200631d966c338c2056245591a4e12ea..c6bfa86e2f228ef27965e78faabd2d44151425b9 100644 (file)
@@ -286,7 +286,7 @@ struct walk_control {
         * inside it
         */
        int (*process_func)(struct btrfs_root *log, struct extent_buffer *eb,
-                           struct walk_control *wc, u64 gen);
+                           struct walk_control *wc, u64 gen, int level);
 };
 
 /*
@@ -294,7 +294,7 @@ struct walk_control {
  */
 static int process_one_buffer(struct btrfs_root *log,
                              struct extent_buffer *eb,
-                             struct walk_control *wc, u64 gen)
+                             struct walk_control *wc, u64 gen, int level)
 {
        struct btrfs_fs_info *fs_info = log->fs_info;
        int ret = 0;
@@ -304,7 +304,7 @@ static int process_one_buffer(struct btrfs_root *log,
         * pin down any logged extents, so we have to read the block.
         */
        if (btrfs_fs_incompat(fs_info, MIXED_GROUPS)) {
-               ret = btrfs_read_buffer(eb, gen);
+               ret = btrfs_read_buffer(eb, gen, level, NULL);
                if (ret)
                        return ret;
        }
@@ -2406,17 +2406,16 @@ out:
  * back refs).
  */
 static int replay_one_buffer(struct btrfs_root *log, struct extent_buffer *eb,
-                            struct walk_control *wc, u64 gen)
+                            struct walk_control *wc, u64 gen, int level)
 {
        int nritems;
        struct btrfs_path *path;
        struct btrfs_root *root = wc->replay_dest;
        struct btrfs_key key;
-       int level;
        int i;
        int ret;
 
-       ret = btrfs_read_buffer(eb, gen);
+       ret = btrfs_read_buffer(eb, gen, level, NULL);
        if (ret)
                return ret;
 
@@ -2533,6 +2532,8 @@ static noinline int walk_down_log_tree(struct btrfs_trans_handle *trans,
        WARN_ON(*level >= BTRFS_MAX_LEVEL);
 
        while (*level > 0) {
+               struct btrfs_key first_key;
+
                WARN_ON(*level < 0);
                WARN_ON(*level >= BTRFS_MAX_LEVEL);
                cur = path->nodes[*level];
@@ -2545,6 +2546,7 @@ static noinline int walk_down_log_tree(struct btrfs_trans_handle *trans,
 
                bytenr = btrfs_node_blockptr(cur, path->slots[*level]);
                ptr_gen = btrfs_node_ptr_generation(cur, path->slots[*level]);
+               btrfs_node_key_to_cpu(cur, &first_key, path->slots[*level]);
                blocksize = fs_info->nodesize;
 
                parent = path->nodes[*level];
@@ -2555,7 +2557,8 @@ static noinline int walk_down_log_tree(struct btrfs_trans_handle *trans,
                        return PTR_ERR(next);
 
                if (*level == 1) {
-                       ret = wc->process_func(root, next, wc, ptr_gen);
+                       ret = wc->process_func(root, next, wc, ptr_gen,
+                                              *level - 1);
                        if (ret) {
                                free_extent_buffer(next);
                                return ret;
@@ -2563,7 +2566,8 @@ static noinline int walk_down_log_tree(struct btrfs_trans_handle *trans,
 
                        path->slots[*level]++;
                        if (wc->free) {
-                               ret = btrfs_read_buffer(next, ptr_gen);
+                               ret = btrfs_read_buffer(next, ptr_gen,
+                                                       *level - 1, &first_key);
                                if (ret) {
                                        free_extent_buffer(next);
                                        return ret;
@@ -2593,7 +2597,7 @@ static noinline int walk_down_log_tree(struct btrfs_trans_handle *trans,
                        free_extent_buffer(next);
                        continue;
                }
-               ret = btrfs_read_buffer(next, ptr_gen);
+               ret = btrfs_read_buffer(next, ptr_gen, *level - 1, &first_key);
                if (ret) {
                        free_extent_buffer(next);
                        return ret;
@@ -2643,7 +2647,8 @@ static noinline int walk_up_log_tree(struct btrfs_trans_handle *trans,
 
                        root_owner = btrfs_header_owner(parent);
                        ret = wc->process_func(root, path->nodes[*level], wc,
-                                btrfs_header_generation(path->nodes[*level]));
+                                btrfs_header_generation(path->nodes[*level]),
+                                *level);
                        if (ret)
                                return ret;
 
@@ -2725,7 +2730,8 @@ static int walk_log_tree(struct btrfs_trans_handle *trans,
        /* was the root node processed? if not, catch it here */
        if (path->nodes[orig_level]) {
                ret = wc->process_func(log, path->nodes[orig_level], wc,
-                        btrfs_header_generation(path->nodes[orig_level]));
+                        btrfs_header_generation(path->nodes[orig_level]),
+                        orig_level);
                if (ret)
                        goto out;
                if (wc->free) {
@@ -3514,8 +3520,11 @@ static noinline int log_dir_items(struct btrfs_trans_handle *trans,
                 * from this directory and from this transaction
                 */
                ret = btrfs_next_leaf(root, path);
-               if (ret == 1) {
-                       last_offset = (u64)-1;
+               if (ret) {
+                       if (ret == 1)
+                               last_offset = (u64)-1;
+                       else
+                               err = ret;
                        goto done;
                }
                btrfs_item_key_to_cpu(path->nodes[0], &tmp, path->slots[0]);
@@ -3968,6 +3977,7 @@ fill_holes:
                        ASSERT(ret == 0);
                        src = src_path->nodes[0];
                        i = 0;
+                       need_find_last_extent = true;
                }
 
                btrfs_item_key_to_cpu(src, &key, i);
@@ -4002,6 +4012,36 @@ fill_holes:
                        break;
                *last_extent = extent_end;
        }
+
+       /*
+        * Check if there is a hole between the last extent found in our leaf
+        * and the first extent in the next leaf. If there is one, we need to
+        * log an explicit hole so that at replay time we can punch the hole.
+        */
+       if (ret == 0 &&
+           key.objectid == btrfs_ino(inode) &&
+           key.type == BTRFS_EXTENT_DATA_KEY &&
+           i == btrfs_header_nritems(src_path->nodes[0])) {
+               ret = btrfs_next_leaf(inode->root, src_path);
+               need_find_last_extent = true;
+               if (ret > 0) {
+                       ret = 0;
+               } else if (ret == 0) {
+                       btrfs_item_key_to_cpu(src_path->nodes[0], &key,
+                                             src_path->slots[0]);
+                       if (key.objectid == btrfs_ino(inode) &&
+                           key.type == BTRFS_EXTENT_DATA_KEY &&
+                           *last_extent < key.offset) {
+                               const u64 len = key.offset - *last_extent;
+
+                               ret = btrfs_insert_file_extent(trans, log,
+                                                              btrfs_ino(inode),
+                                                              *last_extent, 0,
+                                                              0, len, 0, len,
+                                                              0, 0, 0);
+                       }
+               }
+       }
        /*
         * Need to let the callers know we dropped the path so they should
         * re-search.
@@ -5513,7 +5553,6 @@ out:
  * the last committed transaction
  */
 static int btrfs_log_inode_parent(struct btrfs_trans_handle *trans,
-                                 struct btrfs_root *root,
                                  struct btrfs_inode *inode,
                                  struct dentry *parent,
                                  const loff_t start,
@@ -5521,6 +5560,7 @@ static int btrfs_log_inode_parent(struct btrfs_trans_handle *trans,
                                  int inode_only,
                                  struct btrfs_log_ctx *ctx)
 {
+       struct btrfs_root *root = inode->root;
        struct btrfs_fs_info *fs_info = root->fs_info;
        struct super_block *sb;
        struct dentry *old_parent = NULL;
@@ -5546,7 +5586,7 @@ static int btrfs_log_inode_parent(struct btrfs_trans_handle *trans,
                goto end_no_trans;
        }
 
-       if (root != inode->root || btrfs_root_refs(&root->root_item) == 0) {
+       if (btrfs_root_refs(&root->root_item) == 0) {
                ret = 1;
                goto end_no_trans;
        }
@@ -5678,7 +5718,7 @@ end_no_trans:
  * data on disk.
  */
 int btrfs_log_dentry_safe(struct btrfs_trans_handle *trans,
-                         struct btrfs_root *root, struct dentry *dentry,
+                         struct dentry *dentry,
                          const loff_t start,
                          const loff_t end,
                          struct btrfs_log_ctx *ctx)
@@ -5686,8 +5726,8 @@ int btrfs_log_dentry_safe(struct btrfs_trans_handle *trans,
        struct dentry *parent = dget_parent(dentry);
        int ret;
 
-       ret = btrfs_log_inode_parent(trans, root, BTRFS_I(d_inode(dentry)),
-                       parent, start, end, LOG_INODE_ALL, ctx);
+       ret = btrfs_log_inode_parent(trans, BTRFS_I(d_inode(dentry)), parent,
+                                    start, end, LOG_INODE_ALL, ctx);
        dput(parent);
 
        return ret;
@@ -5949,7 +5989,6 @@ int btrfs_log_new_name(struct btrfs_trans_handle *trans,
                        struct dentry *parent)
 {
        struct btrfs_fs_info *fs_info = btrfs_sb(inode->vfs_inode.i_sb);
-       struct btrfs_root *root = inode->root;
 
        /*
         * this will force the logging code to walk the dentry chain
@@ -5966,7 +6005,7 @@ int btrfs_log_new_name(struct btrfs_trans_handle *trans,
            (!old_dir || old_dir->logged_trans <= fs_info->last_trans_committed))
                return 0;
 
-       return btrfs_log_inode_parent(trans, root, inode, parent, 0,
-                                     LLONG_MAX, LOG_INODE_EXISTS, NULL);
+       return btrfs_log_inode_parent(trans, inode, parent, 0, LLONG_MAX,
+                                     LOG_INODE_EXISTS, NULL);
 }