Commit 9c52057c authored by Chris Mason's avatar Chris Mason

Btrfs: fix hash overflow handling

The handling for directory crc hash overflows was fairly obscure,
split_leaf returns EOVERFLOW when we try to extend the item and that is
supposed to bubble up to userland.  For a while it did so, but along the
way we added better handling of errors and forced the FS readonly if we
hit IO errors during the directory insertion.

Along the way, we started testing only for EEXIST and the EOVERFLOW case
was dropped.  The end result is that we may force the FS readonly if we
catch a directory hash bucket overflow.

This fixes a few problem spots.  First I add tests for EOVERFLOW in the
places where we can safely just return the error up the chain.

btrfs_rename is harder though, because it tries to insert the new
directory item only after it has already unlinked anything the rename
was going to overwrite.  Rather than adding very complex logic, I added
a helper to test for the hash overflow case early while it is still safe
to bail out.

Snapshot and subvolume creation had a similar problem, so they are using
the new helper now too.
Signed-off-by: default avatarChris Mason <chris.mason@fusionio.com>
Reported-by: default avatarPascal Junod <pascal@junod.info>
parent c64c2bd8
...@@ -3283,6 +3283,8 @@ void btrfs_update_root_times(struct btrfs_trans_handle *trans, ...@@ -3283,6 +3283,8 @@ void btrfs_update_root_times(struct btrfs_trans_handle *trans,
struct btrfs_root *root); struct btrfs_root *root);
/* dir-item.c */ /* dir-item.c */
int btrfs_check_dir_item_collision(struct btrfs_root *root, u64 dir,
const char *name, int name_len);
int btrfs_insert_dir_item(struct btrfs_trans_handle *trans, int btrfs_insert_dir_item(struct btrfs_trans_handle *trans,
struct btrfs_root *root, const char *name, struct btrfs_root *root, const char *name,
int name_len, struct inode *dir, int name_len, struct inode *dir,
......
...@@ -213,6 +213,65 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans, ...@@ -213,6 +213,65 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans,
return btrfs_match_dir_item_name(root, path, name, name_len); return btrfs_match_dir_item_name(root, path, name, name_len);
} }
int btrfs_check_dir_item_collision(struct btrfs_root *root, u64 dir,
const char *name, int name_len)
{
int ret;
struct btrfs_key key;
struct btrfs_dir_item *di;
int data_size;
struct extent_buffer *leaf;
int slot;
struct btrfs_path *path;
path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
key.objectid = dir;
btrfs_set_key_type(&key, BTRFS_DIR_ITEM_KEY);
key.offset = btrfs_name_hash(name, name_len);
ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
/* return back any errors */
if (ret < 0)
goto out;
/* nothing found, we're safe */
if (ret > 0) {
ret = 0;
goto out;
}
/* we found an item, look for our name in the item */
di = btrfs_match_dir_item_name(root, path, name, name_len);
if (di) {
/* our exact name was found */
ret = -EEXIST;
goto out;
}
/*
* see if there is room in the item to insert this
* name
*/
data_size = sizeof(*di) + name_len + sizeof(struct btrfs_item);
leaf = path->nodes[0];
slot = path->slots[0];
if (data_size + btrfs_item_size_nr(leaf, slot) +
sizeof(struct btrfs_item) > BTRFS_LEAF_DATA_SIZE(root)) {
ret = -EOVERFLOW;
} else {
/* plenty of insertion room */
ret = 0;
}
out:
btrfs_free_path(path);
return ret;
}
/* /*
* lookup a directory item based on index. 'dir' is the objectid * lookup a directory item based on index. 'dir' is the objectid
* we're searching in, and 'mod' tells us if you plan on deleting the * we're searching in, and 'mod' tells us if you plan on deleting the
......
...@@ -4885,7 +4885,7 @@ int btrfs_add_link(struct btrfs_trans_handle *trans, ...@@ -4885,7 +4885,7 @@ int btrfs_add_link(struct btrfs_trans_handle *trans,
ret = btrfs_insert_dir_item(trans, root, name, name_len, ret = btrfs_insert_dir_item(trans, root, name, name_len,
parent_inode, &key, parent_inode, &key,
btrfs_inode_type(inode), index); btrfs_inode_type(inode), index);
if (ret == -EEXIST) if (ret == -EEXIST || ret == -EOVERFLOW)
goto fail_dir_item; goto fail_dir_item;
else if (ret) { else if (ret) {
btrfs_abort_transaction(trans, root, ret); btrfs_abort_transaction(trans, root, ret);
...@@ -7336,6 +7336,28 @@ static int btrfs_rename(struct inode *old_dir, struct dentry *old_dentry, ...@@ -7336,6 +7336,28 @@ static int btrfs_rename(struct inode *old_dir, struct dentry *old_dentry,
if (S_ISDIR(old_inode->i_mode) && new_inode && if (S_ISDIR(old_inode->i_mode) && new_inode &&
new_inode->i_size > BTRFS_EMPTY_DIR_SIZE) new_inode->i_size > BTRFS_EMPTY_DIR_SIZE)
return -ENOTEMPTY; return -ENOTEMPTY;
/* check for collisions, even if the name isn't there */
ret = btrfs_check_dir_item_collision(root, new_dir->i_ino,
new_dentry->d_name.name,
new_dentry->d_name.len);
if (ret) {
if (ret == -EEXIST) {
/* we shouldn't get
* eexist without a new_inode */
if (!new_inode) {
WARN_ON(1);
return ret;
}
} else {
/* maybe -EOVERFLOW */
return ret;
}
}
ret = 0;
/* /*
* we're using rename to replace one file with another. * we're using rename to replace one file with another.
* and the replacement file is large. Start IO on it now so * and the replacement file is large. Start IO on it now so
......
...@@ -710,6 +710,16 @@ static noinline int btrfs_mksubvol(struct path *parent, ...@@ -710,6 +710,16 @@ static noinline int btrfs_mksubvol(struct path *parent,
if (error) if (error)
goto out_dput; goto out_dput;
/*
* even if this name doesn't exist, we may get hash collisions.
* check for them now when we can safely fail
*/
error = btrfs_check_dir_item_collision(BTRFS_I(dir)->root,
dir->i_ino, name,
namelen);
if (error)
goto out_dput;
down_read(&BTRFS_I(dir)->root->fs_info->subvol_sem); down_read(&BTRFS_I(dir)->root->fs_info->subvol_sem);
if (btrfs_root_refs(&BTRFS_I(dir)->root->root_item) == 0) if (btrfs_root_refs(&BTRFS_I(dir)->root->root_item) == 0)
......
...@@ -1190,7 +1190,7 @@ static noinline int create_pending_snapshot(struct btrfs_trans_handle *trans, ...@@ -1190,7 +1190,7 @@ static noinline int create_pending_snapshot(struct btrfs_trans_handle *trans,
parent_inode, &key, parent_inode, &key,
BTRFS_FT_DIR, index); BTRFS_FT_DIR, index);
/* We have check then name at the beginning, so it is impossible. */ /* We have check then name at the beginning, so it is impossible. */
BUG_ON(ret == -EEXIST); BUG_ON(ret == -EEXIST || ret == -EOVERFLOW);
if (ret) { if (ret) {
btrfs_abort_transaction(trans, root, ret); btrfs_abort_transaction(trans, root, ret);
goto fail; goto fail;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment