Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 79787eaa authored by Jeff Mahoney's avatar Jeff Mahoney Committed by David Sterba
Browse files

btrfs: replace many BUG_ONs with proper error handling



 btrfs currently handles most errors with BUG_ON. This patch is a work-in-
 progress but aims to handle most errors other than internal logic
 errors and ENOMEM more gracefully.

 This iteration prevents most crashes but can run into lockups with
 the page lock on occasion when the timing "works out."

Signed-off-by: default avatarJeff Mahoney <jeffm@suse.com>
parent 49b25e05
Loading
Loading
Loading
Loading
+12 −12
Original line number Diff line number Diff line
@@ -391,16 +391,16 @@ int btrfs_submit_compressed_write(struct inode *inode, u64 start,
			 */
			atomic_inc(&cb->pending_bios);
			ret = btrfs_bio_wq_end_io(root->fs_info, bio, 0);
			BUG_ON(ret);
			BUG_ON(ret); /* -ENOMEM */

			if (!skip_sum) {
				ret = btrfs_csum_one_bio(root, inode, bio,
							 start, 1);
				BUG_ON(ret);
				BUG_ON(ret); /* -ENOMEM */
			}

			ret = btrfs_map_bio(root, WRITE, bio, 0, 1);
			BUG_ON(ret);
			BUG_ON(ret); /* -ENOMEM */

			bio_put(bio);

@@ -420,15 +420,15 @@ int btrfs_submit_compressed_write(struct inode *inode, u64 start,
	bio_get(bio);

	ret = btrfs_bio_wq_end_io(root->fs_info, bio, 0);
	BUG_ON(ret);
	BUG_ON(ret); /* -ENOMEM */

	if (!skip_sum) {
		ret = btrfs_csum_one_bio(root, inode, bio, start, 1);
		BUG_ON(ret);
		BUG_ON(ret); /* -ENOMEM */
	}

	ret = btrfs_map_bio(root, WRITE, bio, 0, 1);
	BUG_ON(ret);
	BUG_ON(ret); /* -ENOMEM */

	bio_put(bio);
	return 0;
@@ -661,7 +661,7 @@ int btrfs_submit_compressed_read(struct inode *inode, struct bio *bio,
			bio_get(comp_bio);

			ret = btrfs_bio_wq_end_io(root->fs_info, comp_bio, 0);
			BUG_ON(ret);
			BUG_ON(ret); /* -ENOMEM */

			/*
			 * inc the count before we submit the bio so
@@ -674,14 +674,14 @@ int btrfs_submit_compressed_read(struct inode *inode, struct bio *bio,
			if (!(BTRFS_I(inode)->flags & BTRFS_INODE_NODATASUM)) {
				ret = btrfs_lookup_bio_sums(root, inode,
							comp_bio, sums);
				BUG_ON(ret);
				BUG_ON(ret); /* -ENOMEM */
			}
			sums += (comp_bio->bi_size + root->sectorsize - 1) /
				root->sectorsize;

			ret = btrfs_map_bio(root, READ, comp_bio,
					    mirror_num, 0);
			BUG_ON(ret);
			BUG_ON(ret); /* -ENOMEM */

			bio_put(comp_bio);

@@ -697,15 +697,15 @@ int btrfs_submit_compressed_read(struct inode *inode, struct bio *bio,
	bio_get(comp_bio);

	ret = btrfs_bio_wq_end_io(root->fs_info, comp_bio, 0);
	BUG_ON(ret);
	BUG_ON(ret); /* -ENOMEM */

	if (!(BTRFS_I(inode)->flags & BTRFS_INODE_NODATASUM)) {
		ret = btrfs_lookup_bio_sums(root, inode, comp_bio, sums);
		BUG_ON(ret);
		BUG_ON(ret); /* -ENOMEM */
	}

	ret = btrfs_map_bio(root, READ, comp_bio, mirror_num, 0);
	BUG_ON(ret);
	BUG_ON(ret); /* -ENOMEM */

	bio_put(comp_bio);
	return 0;
+10 −9
Original line number Diff line number Diff line
@@ -356,14 +356,14 @@ static noinline int update_ref_for_cow(struct btrfs_trans_handle *trans,
		     root->root_key.objectid == BTRFS_TREE_RELOC_OBJECTID) &&
		    !(flags & BTRFS_BLOCK_FLAG_FULL_BACKREF)) {
			ret = btrfs_inc_ref(trans, root, buf, 1, 1);
			BUG_ON(ret);
			BUG_ON(ret); /* -ENOMEM */

			if (root->root_key.objectid ==
			    BTRFS_TREE_RELOC_OBJECTID) {
				ret = btrfs_dec_ref(trans, root, buf, 0, 1);
				BUG_ON(ret);
				BUG_ON(ret); /* -ENOMEM */
				ret = btrfs_inc_ref(trans, root, cow, 1, 1);
				BUG_ON(ret);
				BUG_ON(ret); /* -ENOMEM */
			}
			new_flags |= BTRFS_BLOCK_FLAG_FULL_BACKREF;
		} else {
@@ -373,7 +373,7 @@ static noinline int update_ref_for_cow(struct btrfs_trans_handle *trans,
				ret = btrfs_inc_ref(trans, root, cow, 1, 1);
			else
				ret = btrfs_inc_ref(trans, root, cow, 0, 1);
			BUG_ON(ret);
			BUG_ON(ret); /* -ENOMEM */
		}
		if (new_flags != 0) {
			ret = btrfs_set_disk_extent_flags(trans, root,
@@ -390,9 +390,9 @@ static noinline int update_ref_for_cow(struct btrfs_trans_handle *trans,
				ret = btrfs_inc_ref(trans, root, cow, 1, 1);
			else
				ret = btrfs_inc_ref(trans, root, cow, 0, 1);
			BUG_ON(ret);
			BUG_ON(ret); /* -ENOMEM */
			ret = btrfs_dec_ref(trans, root, buf, 1, 1);
			BUG_ON(ret);
			BUG_ON(ret); /* -ENOMEM */
		}
		clean_tree_block(trans, root, buf);
		*last_ref = 1;
@@ -475,7 +475,7 @@ static noinline int __btrfs_cow_block(struct btrfs_trans_handle *trans,

	ret = update_ref_for_cow(trans, root, buf, cow, &last_ref);
	if (ret) {
		btrfs_std_error(root->fs_info, ret);
		btrfs_abort_transaction(trans, root, ret);
		return ret;
	}

@@ -2713,6 +2713,7 @@ static int push_leaf_left(struct btrfs_trans_handle *trans, struct btrfs_root
			      path->nodes[1], slot - 1, &left);
	if (ret) {
		/* we hit -ENOSPC, but it isn't fatal here */
		if (ret == -ENOSPC)
			ret = 1;
		goto out;
	}
@@ -4017,7 +4018,7 @@ int btrfs_search_forward(struct btrfs_root *root, struct btrfs_key *min_key,
		}
		btrfs_set_path_blocking(path);
		cur = read_node_slot(root, cur, slot);
		BUG_ON(!cur);
		BUG_ON(!cur); /* -ENOMEM */

		btrfs_tree_read_lock(cur);

+20 −7
Original line number Diff line number Diff line
@@ -115,6 +115,7 @@ static struct btrfs_delayed_node *btrfs_get_delayed_node(struct inode *inode)
	return NULL;
}

/* Will return either the node or PTR_ERR(-ENOMEM) */
static struct btrfs_delayed_node *btrfs_get_or_create_delayed_node(
							struct inode *inode)
{
@@ -1106,16 +1107,25 @@ static int btrfs_update_delayed_inode(struct btrfs_trans_handle *trans,
	return 0;
}

/* Called when committing the transaction. */
/*
 * Called when committing the transaction.
 * Returns 0 on success.
 * Returns < 0 on error and returns with an aborted transaction with any
 * outstanding delayed items cleaned up.
 */
int btrfs_run_delayed_items(struct btrfs_trans_handle *trans,
			    struct btrfs_root *root)
{
	struct btrfs_root *curr_root = root;
	struct btrfs_delayed_root *delayed_root;
	struct btrfs_delayed_node *curr_node, *prev_node;
	struct btrfs_path *path;
	struct btrfs_block_rsv *block_rsv;
	int ret = 0;

	if (trans->aborted)
		return -EIO;

	path = btrfs_alloc_path();
	if (!path)
		return -ENOMEM;
@@ -1128,17 +1138,18 @@ int btrfs_run_delayed_items(struct btrfs_trans_handle *trans,

	curr_node = btrfs_first_delayed_node(delayed_root);
	while (curr_node) {
		root = curr_node->root;
		ret = btrfs_insert_delayed_items(trans, path, root,
		curr_root = curr_node->root;
		ret = btrfs_insert_delayed_items(trans, path, curr_root,
						 curr_node);
		if (!ret)
			ret = btrfs_delete_delayed_items(trans, path, root,
							 curr_node);
			ret = btrfs_delete_delayed_items(trans, path,
						curr_root, curr_node);
		if (!ret)
			ret = btrfs_update_delayed_inode(trans, root, path,
							 curr_node);
			ret = btrfs_update_delayed_inode(trans, curr_root,
						path, curr_node);
		if (ret) {
			btrfs_release_delayed_node(curr_node);
			btrfs_abort_transaction(trans, root, ret);
			break;
		}

@@ -1149,6 +1160,7 @@ int btrfs_run_delayed_items(struct btrfs_trans_handle *trans,

	btrfs_free_path(path);
	trans->block_rsv = block_rsv;

	return ret;
}

@@ -1369,6 +1381,7 @@ void btrfs_balance_delayed_items(struct btrfs_root *root)
	btrfs_wq_run_delayed_node(delayed_root, root, 0);
}

/* Will return 0 or -ENOMEM */
int btrfs_insert_delayed_dir_index(struct btrfs_trans_handle *trans,
				   struct btrfs_root *root, const char *name,
				   int name_len, struct inode *dir,
+1 −0
Original line number Diff line number Diff line
@@ -115,6 +115,7 @@ int btrfs_insert_xattr_item(struct btrfs_trans_handle *trans,
 * 'location' is the key to stuff into the directory item, 'type' is the
 * type of the inode we're pointing to, and 'index' is the sequence number
 * to use for the second index (if one is created).
 * Will return 0 or -ENOMEM
 */
int btrfs_insert_dir_item(struct btrfs_trans_handle *trans, struct btrfs_root
			  *root, const char *name, int name_len,
+86 −30
Original line number Diff line number Diff line
@@ -98,6 +98,7 @@ struct async_submit_bio {
	 */
	u64 bio_offset;
	struct btrfs_work work;
	int error;
};

/*
@@ -405,7 +406,7 @@ static int csum_dirty_buffer(struct btrfs_root *root, struct page *page)
	u64 found_start;
	unsigned long len;
	struct extent_buffer *eb;
	int ret;
	int ret = -EIO;

	tree = &BTRFS_I(page->mapping->host)->io_tree;

@@ -423,13 +424,20 @@ static int csum_dirty_buffer(struct btrfs_root *root, struct page *page)
	eb = alloc_extent_buffer(tree, start, len, page);
	if (eb == NULL) {
		WARN_ON(1);
		ret = -ENOMEM;
		goto out;
	}
	ret = btree_read_extent_buffer_pages(root, eb, start + PAGE_CACHE_SIZE,
					     btrfs_header_generation(eb));
	BUG_ON(ret);
	if (ret) {
		btrfs_printk(root->fs_info, KERN_WARNING
			     "Failed to checksum dirty buffer @ %llu[%lu]\n",
			      start, len);
		goto err;
	}
	WARN_ON(!btrfs_header_flag(eb, BTRFS_HEADER_FLAG_WRITTEN));

	ret = -EIO;
	found_start = btrfs_header_bytenr(eb);
	if (found_start != start) {
		WARN_ON(1);
@@ -444,10 +452,11 @@ static int csum_dirty_buffer(struct btrfs_root *root, struct page *page)
		goto err;
	}
	csum_tree_block(root, eb, 0);
	ret = 0;
err:
	free_extent_buffer(eb);
out:
	return 0;
	return ret;
}

static int check_tree_block_fsid(struct btrfs_root *root,
@@ -718,11 +727,14 @@ unsigned long btrfs_async_submit_limit(struct btrfs_fs_info *info)
static void run_one_async_start(struct btrfs_work *work)
{
	struct async_submit_bio *async;
	int ret;

	async = container_of(work, struct  async_submit_bio, work);
	async->submit_bio_start(async->inode, async->rw, async->bio,
	ret = async->submit_bio_start(async->inode, async->rw, async->bio,
				      async->mirror_num, async->bio_flags,
				      async->bio_offset);
	if (ret)
		async->error = ret;
}

static void run_one_async_done(struct btrfs_work *work)
@@ -743,6 +755,12 @@ static void run_one_async_done(struct btrfs_work *work)
	    waitqueue_active(&fs_info->async_submit_wait))
		wake_up(&fs_info->async_submit_wait);

	/* If an error occured we just want to clean up the bio and move on */
	if (async->error) {
		bio_endio(async->bio, async->error);
		return;
	}

	async->submit_bio_done(async->inode, async->rw, async->bio,
			       async->mirror_num, async->bio_flags,
			       async->bio_offset);
@@ -784,6 +802,8 @@ int btrfs_wq_submit_bio(struct btrfs_fs_info *fs_info, struct inode *inode,
	async->bio_flags = bio_flags;
	async->bio_offset = bio_offset;

	async->error = 0;

	atomic_inc(&fs_info->nr_async_submits);

	if (rw & REQ_SYNC)
@@ -805,15 +825,18 @@ static int btree_csum_one_bio(struct bio *bio)
	struct bio_vec *bvec = bio->bi_io_vec;
	int bio_index = 0;
	struct btrfs_root *root;
	int ret = 0;

	WARN_ON(bio->bi_vcnt <= 0);
	while (bio_index < bio->bi_vcnt) {
		root = BTRFS_I(bvec->bv_page->mapping->host)->root;
		csum_dirty_buffer(root, bvec->bv_page);
		ret = csum_dirty_buffer(root, bvec->bv_page);
		if (ret)
			break;
		bio_index++;
		bvec++;
	}
	return 0;
	return ret;
}

static int __btree_submit_bio_start(struct inode *inode, int rw,
@@ -825,8 +848,7 @@ static int __btree_submit_bio_start(struct inode *inode, int rw,
	 * when we're called for a write, we're already in the async
	 * submission context.  Just jump into btrfs_map_bio
	 */
	btree_csum_one_bio(bio);
	return 0;
	return btree_csum_one_bio(bio);
}

static int __btree_submit_bio_done(struct inode *inode, int rw, struct bio *bio,
@@ -1381,7 +1403,7 @@ struct btrfs_root *btrfs_read_fs_root_no_radix(struct btrfs_root *tree_root,
	root->node = read_tree_block(root, btrfs_root_bytenr(&root->root_item),
				     blocksize, generation);
	root->commit_root = btrfs_root_node(root);
	BUG_ON(!root->node);
	BUG_ON(!root->node); /* -ENOMEM */
out:
	if (location->objectid != BTRFS_TREE_LOG_OBJECTID) {
		root->ref_cows = 1;
@@ -1618,7 +1640,6 @@ static int transaction_kthread(void *arg)
	u64 transid;
	unsigned long now;
	unsigned long delay;
	int ret;

	do {
		delay = HZ * 30;
@@ -1642,11 +1663,12 @@ static int transaction_kthread(void *arg)
		transid = cur->transid;
		spin_unlock(&root->fs_info->trans_lock);

		/* If the file system is aborted, this will always fail. */
		trans = btrfs_join_transaction(root);
		BUG_ON(IS_ERR(trans));
		if (IS_ERR(trans))
			goto sleep;
		if (transid == trans->transid) {
			ret = btrfs_commit_transaction(trans, root);
			BUG_ON(ret);
			btrfs_commit_transaction(trans, root);
		} else {
			btrfs_end_transaction(trans, root);
		}
@@ -2289,7 +2311,7 @@ int open_ctree(struct super_block *sb,
	chunk_root->node = read_tree_block(chunk_root,
					   btrfs_super_chunk_root(disk_super),
					   blocksize, generation);
	BUG_ON(!chunk_root->node);
	BUG_ON(!chunk_root->node); /* -ENOMEM */
	if (!test_bit(EXTENT_BUFFER_UPTODATE, &chunk_root->node->bflags)) {
		printk(KERN_WARNING "btrfs: failed to read chunk root on %s\n",
		       sb->s_id);
@@ -2429,21 +2451,31 @@ int open_ctree(struct super_block *sb,
		log_tree_root->node = read_tree_block(tree_root, bytenr,
						      blocksize,
						      generation + 1);
		/* returns with log_tree_root freed on success */
		ret = btrfs_recover_log_trees(log_tree_root);
		BUG_ON(ret);
		if (ret) {
			btrfs_error(tree_root->fs_info, ret,
				    "Failed to recover log tree");
			free_extent_buffer(log_tree_root->node);
			kfree(log_tree_root);
			goto fail_trans_kthread;
		}

		if (sb->s_flags & MS_RDONLY) {
			ret = btrfs_commit_super(tree_root);
			BUG_ON(ret);
			if (ret)
				goto fail_trans_kthread;
		}
	}

	ret = btrfs_find_orphan_roots(tree_root);
	BUG_ON(ret);
	if (ret)
		goto fail_trans_kthread;

	if (!(sb->s_flags & MS_RDONLY)) {
		ret = btrfs_cleanup_fs_roots(fs_info);
		BUG_ON(ret);
		if (ret) {
			}

		ret = btrfs_recover_relocation(tree_root);
		if (ret < 0) {
@@ -2863,6 +2895,8 @@ int write_all_supers(struct btrfs_root *root, int max_mirrors)
	if (total_errors > max_errors) {
		printk(KERN_ERR "btrfs: %d errors while writing supers\n",
		       total_errors);

		/* This shouldn't happen. FUA is masked off if unsupported */
		BUG();
	}

@@ -2879,9 +2913,9 @@ int write_all_supers(struct btrfs_root *root, int max_mirrors)
	}
	mutex_unlock(&root->fs_info->fs_devices->device_list_mutex);
	if (total_errors > max_errors) {
		printk(KERN_ERR "btrfs: %d errors while writing supers\n",
		       total_errors);
		BUG();
		btrfs_error(root->fs_info, -EIO,
			    "%d errors while writing supers", total_errors);
		return -EIO;
	}
	return 0;
}
@@ -3014,14 +3048,21 @@ int btrfs_commit_super(struct btrfs_root *root)
	if (IS_ERR(trans))
		return PTR_ERR(trans);
	ret = btrfs_commit_transaction(trans, root);
	BUG_ON(ret);
	if (ret)
		return ret;
	/* run commit again to drop the original snapshot */
	trans = btrfs_join_transaction(root);
	if (IS_ERR(trans))
		return PTR_ERR(trans);
	btrfs_commit_transaction(trans, root);
	ret = btrfs_commit_transaction(trans, root);
	if (ret)
		return ret;
	ret = btrfs_write_and_wait_transaction(NULL, root);
	BUG_ON(ret);
	if (ret) {
		btrfs_error(root->fs_info, ret,
			    "Failed to sync btree inode to disk.");
		return ret;
	}

	ret = write_ctree_super(NULL, root, 0);
	return ret;
@@ -3366,7 +3407,7 @@ static void btrfs_destroy_ordered_extents(struct btrfs_root *root)
	spin_unlock(&root->fs_info->ordered_extent_lock);
}

static int btrfs_destroy_delayed_refs(struct btrfs_transaction *trans,
int btrfs_destroy_delayed_refs(struct btrfs_transaction *trans,
			       struct btrfs_root *root)
{
	struct rb_node *node;
@@ -3376,6 +3417,7 @@ static int btrfs_destroy_delayed_refs(struct btrfs_transaction *trans,

	delayed_refs = &trans->delayed_refs;

again:
	spin_lock(&delayed_refs->lock);
	if (delayed_refs->num_entries == 0) {
		spin_unlock(&delayed_refs->lock);
@@ -3397,6 +3439,7 @@ static int btrfs_destroy_delayed_refs(struct btrfs_transaction *trans,
			struct btrfs_delayed_ref_head *head;

			head = btrfs_delayed_node_to_head(ref);
			spin_unlock(&delayed_refs->lock);
			mutex_lock(&head->mutex);
			kfree(head->extent_op);
			delayed_refs->num_heads--;
@@ -3404,8 +3447,9 @@ static int btrfs_destroy_delayed_refs(struct btrfs_transaction *trans,
				delayed_refs->num_heads_ready--;
			list_del_init(&head->cluster);
			mutex_unlock(&head->mutex);
			btrfs_put_delayed_ref(ref);
			goto again;
		}

		spin_unlock(&delayed_refs->lock);
		btrfs_put_delayed_ref(ref);

@@ -3649,6 +3693,17 @@ int btrfs_cleanup_transaction(struct btrfs_root *root)
	return 0;
}

static int btree_writepage_io_failed_hook(struct bio *bio, struct page *page,
					  u64 start, u64 end,
					  struct extent_state *state)
{
	struct super_block *sb = page->mapping->host->i_sb;
	struct btrfs_fs_info *fs_info = btrfs_sb(sb);
	btrfs_error(fs_info, -EIO,
		    "Error occured while writing out btree at %llu", start);
	return -EIO;
}

static struct extent_io_ops btree_extent_io_ops = {
	.write_cache_pages_lock_hook = btree_lock_page_hook,
	.readpage_end_io_hook = btree_readpage_end_io_hook,
@@ -3656,4 +3711,5 @@ static struct extent_io_ops btree_extent_io_ops = {
	.submit_bio_hook = btree_submit_bio_hook,
	/* note we're sharing with inode.c for the merge bio hook */
	.merge_bio_hook = btrfs_merge_bio_hook,
	.writepage_io_failed_hook = btree_writepage_io_failed_hook,
};
Loading