Commit dc6982ff authored by Theodore Ts'o's avatar Theodore Ts'o

ext4: refactor code to read directory blocks into ext4_read_dirblock()

The code to read in directory blocks and verify their metadata
checksums was replicated in ten different places across
fs/ext4/namei.c, and the code was buggy in subtle ways in a number of
those replicated sites.  In some cases, ext4_error() was called with a
training newline.  In others, in particularly in empty_dir(), it was
possible to call ext4_dirent_csum_verify() on an index block, which
would trigger false warnings requesting the system adminsitrator to
run e2fsck.

By refactoring the code, we make the code more readable, as well as
shrinking the compiled object file by over 700 bytes and 50 lines of
code.
Signed-off-by: default avatar"Theodore Ts'o" <tytso@mit.edu>
parent 01a523eb
...@@ -83,6 +83,86 @@ static struct buffer_head *ext4_append(handle_t *handle, ...@@ -83,6 +83,86 @@ static struct buffer_head *ext4_append(handle_t *handle,
return bh; return bh;
} }
static int ext4_dx_csum_verify(struct inode *inode,
struct ext4_dir_entry *dirent);
typedef enum {
EITHER, INDEX, DIRENT
} dirblock_type_t;
#define ext4_read_dirblock(inode, block, type) \
__ext4_read_dirblock((inode), (block), (type), __LINE__)
static struct buffer_head *__ext4_read_dirblock(struct inode *inode,
ext4_lblk_t block,
dirblock_type_t type,
unsigned int line)
{
struct buffer_head *bh;
struct ext4_dir_entry *dirent;
int err = 0, is_dx_block = 0;
bh = ext4_bread(NULL, inode, block, 0, &err);
if (!bh) {
if (err == 0) {
ext4_error_inode(inode, __func__, line, block,
"Directory hole found");
return ERR_PTR(-EIO);
}
__ext4_warning(inode->i_sb, __func__, line,
"error reading directory block "
"(ino %lu, block %lu)", inode->i_ino,
(unsigned long) block);
return ERR_PTR(err);
}
dirent = (struct ext4_dir_entry *) bh->b_data;
/* Determine whether or not we have an index block */
if (is_dx(inode)) {
if (block == 0)
is_dx_block = 1;
else if (ext4_rec_len_from_disk(dirent->rec_len,
inode->i_sb->s_blocksize) ==
inode->i_sb->s_blocksize)
is_dx_block = 1;
}
if (!is_dx_block && type == INDEX) {
ext4_error_inode(inode, __func__, line, block,
"directory leaf block found instead of index block");
return ERR_PTR(-EIO);
}
if (!EXT4_HAS_RO_COMPAT_FEATURE(inode->i_sb,
EXT4_FEATURE_RO_COMPAT_METADATA_CSUM) ||
buffer_verified(bh))
return bh;
/*
* An empty leaf block can get mistaken for a index block; for
* this reason, we can only check the index checksum when the
* caller is sure it should be an index block.
*/
if (is_dx_block && type == INDEX) {
if (ext4_dx_csum_verify(inode, dirent))
set_buffer_verified(bh);
else {
ext4_error_inode(inode, __func__, line, block,
"Directory index failed checksum");
brelse(bh);
return ERR_PTR(-EIO);
}
}
if (!is_dx_block) {
if (ext4_dirent_csum_verify(inode, dirent))
set_buffer_verified(bh);
else {
ext4_error_inode(inode, __func__, line, block,
"Directory block failed checksum");
brelse(bh);
return ERR_PTR(-EIO);
}
}
return bh;
}
#ifndef assert #ifndef assert
#define assert(test) J_ASSERT(test) #define assert(test) J_ASSERT(test)
#endif #endif
...@@ -604,9 +684,9 @@ dx_probe(const struct qstr *d_name, struct inode *dir, ...@@ -604,9 +684,9 @@ dx_probe(const struct qstr *d_name, struct inode *dir,
u32 hash; u32 hash;
frame->bh = NULL; frame->bh = NULL;
if (!(bh = ext4_bread(NULL, dir, 0, 0, err))) { bh = ext4_read_dirblock(dir, 0, INDEX);
if (*err == 0) if (IS_ERR(bh)) {
*err = ERR_BAD_DX_DIR; *err = PTR_ERR(bh);
goto fail; goto fail;
} }
root = (struct dx_root *) bh->b_data; root = (struct dx_root *) bh->b_data;
...@@ -643,15 +723,6 @@ dx_probe(const struct qstr *d_name, struct inode *dir, ...@@ -643,15 +723,6 @@ dx_probe(const struct qstr *d_name, struct inode *dir,
goto fail; goto fail;
} }
if (!buffer_verified(bh) &&
!ext4_dx_csum_verify(dir, (struct ext4_dir_entry *)bh->b_data)) {
ext4_warning(dir->i_sb, "Root failed checksum");
brelse(bh);
*err = ERR_BAD_DX_DIR;
goto fail;
}
set_buffer_verified(bh);
entries = (struct dx_entry *) (((char *)&root->info) + entries = (struct dx_entry *) (((char *)&root->info) +
root->info.info_length); root->info.info_length);
...@@ -709,23 +780,13 @@ dx_probe(const struct qstr *d_name, struct inode *dir, ...@@ -709,23 +780,13 @@ dx_probe(const struct qstr *d_name, struct inode *dir,
frame->entries = entries; frame->entries = entries;
frame->at = at; frame->at = at;
if (!indirect--) return frame; if (!indirect--) return frame;
if (!(bh = ext4_bread(NULL, dir, dx_get_block(at), 0, err))) { bh = ext4_read_dirblock(dir, dx_get_block(at), INDEX);
if (!(*err)) if (IS_ERR(bh)) {
*err = ERR_BAD_DX_DIR; *err = PTR_ERR(bh);
goto fail2; goto fail2;
} }
entries = ((struct dx_node *) bh->b_data)->entries; entries = ((struct dx_node *) bh->b_data)->entries;
if (!buffer_verified(bh) &&
!ext4_dx_csum_verify(dir,
(struct ext4_dir_entry *)bh->b_data)) {
ext4_warning(dir->i_sb, "Node failed checksum");
brelse(bh);
*err = ERR_BAD_DX_DIR;
goto fail2;
}
set_buffer_verified(bh);
if (dx_get_limit(entries) != dx_node_limit (dir)) { if (dx_get_limit(entries) != dx_node_limit (dir)) {
ext4_warning(dir->i_sb, ext4_warning(dir->i_sb,
"dx entry: limit != node limit"); "dx entry: limit != node limit");
...@@ -783,7 +844,7 @@ static int ext4_htree_next_block(struct inode *dir, __u32 hash, ...@@ -783,7 +844,7 @@ static int ext4_htree_next_block(struct inode *dir, __u32 hash,
{ {
struct dx_frame *p; struct dx_frame *p;
struct buffer_head *bh; struct buffer_head *bh;
int err, num_frames = 0; int num_frames = 0;
__u32 bhash; __u32 bhash;
p = frame; p = frame;
...@@ -822,26 +883,9 @@ static int ext4_htree_next_block(struct inode *dir, __u32 hash, ...@@ -822,26 +883,9 @@ static int ext4_htree_next_block(struct inode *dir, __u32 hash,
* block so no check is necessary * block so no check is necessary
*/ */
while (num_frames--) { while (num_frames--) {
if (!(bh = ext4_bread(NULL, dir, dx_get_block(p->at), bh = ext4_read_dirblock(dir, dx_get_block(p->at), INDEX);
0, &err))) { if (IS_ERR(bh))
if (!err) { return PTR_ERR(bh);
ext4_error(dir->i_sb,
"Directory hole detected on inode %lu\n",
dir->i_ino);
return -EIO;
}
return err; /* Failure */
}
if (!buffer_verified(bh) &&
!ext4_dx_csum_verify(dir,
(struct ext4_dir_entry *)bh->b_data)) {
ext4_warning(dir->i_sb, "Node failed checksum");
brelse(bh);
return -EIO;
}
set_buffer_verified(bh);
p++; p++;
brelse(p->bh); brelse(p->bh);
p->bh = bh; p->bh = bh;
...@@ -867,23 +911,9 @@ static int htree_dirblock_to_tree(struct file *dir_file, ...@@ -867,23 +911,9 @@ static int htree_dirblock_to_tree(struct file *dir_file,
dxtrace(printk(KERN_INFO "In htree dirblock_to_tree: block %lu\n", dxtrace(printk(KERN_INFO "In htree dirblock_to_tree: block %lu\n",
(unsigned long)block)); (unsigned long)block));
if (!(bh = ext4_bread(NULL, dir, block, 0, &err))) { bh = ext4_read_dirblock(dir, block, DIRENT);
if (!err) { if (IS_ERR(bh))
err = -EIO; return PTR_ERR(bh);
ext4_error(dir->i_sb,
"Directory hole detected on inode %lu\n",
dir->i_ino);
}
return err;
}
if (!buffer_verified(bh) &&
!ext4_dirent_csum_verify(dir,
(struct ext4_dir_entry *)bh->b_data)) {
brelse(bh);
return -EIO;
}
set_buffer_verified(bh);
de = (struct ext4_dir_entry_2 *) bh->b_data; de = (struct ext4_dir_entry_2 *) bh->b_data;
top = (struct ext4_dir_entry_2 *) ((char *) de + top = (struct ext4_dir_entry_2 *) ((char *) de +
...@@ -1337,26 +1367,11 @@ static struct buffer_head * ext4_dx_find_entry(struct inode *dir, const struct q ...@@ -1337,26 +1367,11 @@ static struct buffer_head * ext4_dx_find_entry(struct inode *dir, const struct q
return NULL; return NULL;
do { do {
block = dx_get_block(frame->at); block = dx_get_block(frame->at);
if (!(bh = ext4_bread(NULL, dir, block, 0, err))) { bh = ext4_read_dirblock(dir, block, DIRENT);
if (!(*err)) { if (IS_ERR(bh)) {
*err = -EIO; *err = PTR_ERR(bh);
ext4_error(dir->i_sb,
"Directory hole detected on inode %lu\n",
dir->i_ino);
}
goto errout;
}
if (!buffer_verified(bh) &&
!ext4_dirent_csum_verify(dir,
(struct ext4_dir_entry *)bh->b_data)) {
EXT4_ERROR_INODE(dir, "checksumming directory "
"block %lu", (unsigned long)block);
brelse(bh);
*err = -EIO;
goto errout; goto errout;
} }
set_buffer_verified(bh);
retval = search_dirblock(bh, dir, d_name, retval = search_dirblock(bh, dir, d_name,
block << EXT4_BLOCK_SIZE_BITS(sb), block << EXT4_BLOCK_SIZE_BITS(sb),
res_dir); res_dir);
...@@ -1920,22 +1935,10 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry, ...@@ -1920,22 +1935,10 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
} }
blocks = dir->i_size >> sb->s_blocksize_bits; blocks = dir->i_size >> sb->s_blocksize_bits;
for (block = 0; block < blocks; block++) { for (block = 0; block < blocks; block++) {
if (!(bh = ext4_bread(handle, dir, block, 0, &retval))) { bh = ext4_read_dirblock(dir, block, DIRENT);
if (!retval) { if (IS_ERR(bh))
retval = -EIO; return PTR_ERR(bh);
ext4_error(inode->i_sb,
"Directory hole detected on inode %lu\n",
inode->i_ino);
}
return retval;
}
if (!buffer_verified(bh) &&
!ext4_dirent_csum_verify(dir,
(struct ext4_dir_entry *)bh->b_data)) {
brelse(bh);
return -EIO;
}
set_buffer_verified(bh);
retval = add_dirent_to_buf(handle, dentry, inode, NULL, bh); retval = add_dirent_to_buf(handle, dentry, inode, NULL, bh);
if (retval != -ENOSPC) { if (retval != -ENOSPC) {
brelse(bh); brelse(bh);
...@@ -1986,22 +1989,13 @@ static int ext4_dx_add_entry(handle_t *handle, struct dentry *dentry, ...@@ -1986,22 +1989,13 @@ static int ext4_dx_add_entry(handle_t *handle, struct dentry *dentry,
return err; return err;
entries = frame->entries; entries = frame->entries;
at = frame->at; at = frame->at;
bh = ext4_read_dirblock(dir, dx_get_block(frame->at), DIRENT);
if (!(bh = ext4_bread(handle, dir, dx_get_block(frame->at), 0, &err))) { if (IS_ERR(bh)) {
if (!err) { err = PTR_ERR(bh);
err = -EIO; bh = NULL;
ext4_error(dir->i_sb,
"Directory hole detected on inode %lu\n",
dir->i_ino);
}
goto cleanup; goto cleanup;
} }
if (!buffer_verified(bh) &&
!ext4_dirent_csum_verify(dir, (struct ext4_dir_entry *)bh->b_data))
goto journal_error;
set_buffer_verified(bh);
BUFFER_TRACE(bh, "get_write_access"); BUFFER_TRACE(bh, "get_write_access");
err = ext4_journal_get_write_access(handle, bh); err = ext4_journal_get_write_access(handle, bh);
if (err) if (err)
...@@ -2352,6 +2346,7 @@ static int ext4_init_new_dir(handle_t *handle, struct inode *dir, ...@@ -2352,6 +2346,7 @@ static int ext4_init_new_dir(handle_t *handle, struct inode *dir,
struct buffer_head *dir_block = NULL; struct buffer_head *dir_block = NULL;
struct ext4_dir_entry_2 *de; struct ext4_dir_entry_2 *de;
struct ext4_dir_entry_tail *t; struct ext4_dir_entry_tail *t;
ext4_lblk_t block = 0;
unsigned int blocksize = dir->i_sb->s_blocksize; unsigned int blocksize = dir->i_sb->s_blocksize;
int csum_size = 0; int csum_size = 0;
int err; int err;
...@@ -2368,16 +2363,9 @@ static int ext4_init_new_dir(handle_t *handle, struct inode *dir, ...@@ -2368,16 +2363,9 @@ static int ext4_init_new_dir(handle_t *handle, struct inode *dir,
goto out; goto out;
} }
inode->i_size = EXT4_I(inode)->i_disksize = blocksize; inode->i_size = 0;
if (!(dir_block = ext4_bread(handle, inode, 0, 1, &err))) { if (!(dir_block = ext4_append(handle, inode, &block, &err)))
if (!err) {
err = -EIO;
ext4_error(inode->i_sb,
"Directory hole detected on inode %lu\n",
inode->i_ino);
}
goto out; goto out;
}
BUFFER_TRACE(dir_block, "get_write_access"); BUFFER_TRACE(dir_block, "get_write_access");
err = ext4_journal_get_write_access(handle, dir_block); err = ext4_journal_get_write_access(handle, dir_block);
if (err) if (err)
...@@ -2477,26 +2465,14 @@ static int empty_dir(struct inode *inode) ...@@ -2477,26 +2465,14 @@ static int empty_dir(struct inode *inode)
} }
sb = inode->i_sb; sb = inode->i_sb;
if (inode->i_size < EXT4_DIR_REC_LEN(1) + EXT4_DIR_REC_LEN(2) || if (inode->i_size < EXT4_DIR_REC_LEN(1) + EXT4_DIR_REC_LEN(2)) {
!(bh = ext4_bread(NULL, inode, 0, 0, &err))) { EXT4_ERROR_INODE(inode, "invalid size");
if (err)
EXT4_ERROR_INODE(inode,
"error %d reading directory lblock 0", err);
else
ext4_warning(inode->i_sb,
"bad directory (dir #%lu) - no data block",
inode->i_ino);
return 1; return 1;
} }
if (!buffer_verified(bh) && bh = ext4_read_dirblock(inode, 0, EITHER);
!ext4_dirent_csum_verify(inode, if (IS_ERR(bh))
(struct ext4_dir_entry *)bh->b_data)) { return 1;
EXT4_ERROR_INODE(inode, "checksum error reading directory "
"lblock 0");
brelse(bh);
return -EIO;
}
set_buffer_verified(bh);
de = (struct ext4_dir_entry_2 *) bh->b_data; de = (struct ext4_dir_entry_2 *) bh->b_data;
de1 = ext4_next_entry(de, sb->s_blocksize); de1 = ext4_next_entry(de, sb->s_blocksize);
if (le32_to_cpu(de->inode) != inode->i_ino || if (le32_to_cpu(de->inode) != inode->i_ino ||
...@@ -2519,29 +2495,9 @@ static int empty_dir(struct inode *inode) ...@@ -2519,29 +2495,9 @@ static int empty_dir(struct inode *inode)
err = 0; err = 0;
brelse(bh); brelse(bh);
lblock = offset >> EXT4_BLOCK_SIZE_BITS(sb); lblock = offset >> EXT4_BLOCK_SIZE_BITS(sb);
bh = ext4_bread(NULL, inode, lblock, 0, &err); bh = ext4_read_dirblock(inode, lblock, EITHER);
if (!bh) { if (IS_ERR(bh))
if (err) return 1;
EXT4_ERROR_INODE(inode,
"error %d reading directory "
"lblock %u", err, lblock);
else
ext4_warning(inode->i_sb,
"bad directory (dir #%lu) - no data block",
inode->i_ino);
offset += sb->s_blocksize;
continue;
}
if (!buffer_verified(bh) &&
!ext4_dirent_csum_verify(inode,
(struct ext4_dir_entry *)bh->b_data)) {
EXT4_ERROR_INODE(inode, "checksum error "
"reading directory lblock 0");
brelse(bh);
return -EIO;
}
set_buffer_verified(bh);
de = (struct ext4_dir_entry_2 *) bh->b_data; de = (struct ext4_dir_entry_2 *) bh->b_data;
} }
if (ext4_check_dir_entry(inode, NULL, de, bh, if (ext4_check_dir_entry(inode, NULL, de, bh,
...@@ -3004,13 +2960,9 @@ static struct buffer_head *ext4_get_first_dir_block(handle_t *handle, ...@@ -3004,13 +2960,9 @@ static struct buffer_head *ext4_get_first_dir_block(handle_t *handle,
struct buffer_head *bh; struct buffer_head *bh;
if (!ext4_has_inline_data(inode)) { if (!ext4_has_inline_data(inode)) {
if (!(bh = ext4_bread(handle, inode, 0, 0, retval))) { bh = ext4_read_dirblock(inode, 0, EITHER);
if (!*retval) { if (IS_ERR(bh)) {
*retval = -EIO; *retval = PTR_ERR(bh);
ext4_error(inode->i_sb,
"Directory hole detected on inode %lu\n",
inode->i_ino);
}
return NULL; return NULL;
} }
*parent_de = ext4_next_entry( *parent_de = ext4_next_entry(
...@@ -3089,11 +3041,6 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, ...@@ -3089,11 +3041,6 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
&inlined); &inlined);
if (!dir_bh) if (!dir_bh)
goto end_rename; goto end_rename;
if (!inlined && !buffer_verified(dir_bh) &&
!ext4_dirent_csum_verify(old_inode,
(struct ext4_dir_entry *)dir_bh->b_data))
goto end_rename;
set_buffer_verified(dir_bh);
if (le32_to_cpu(parent_de->inode) != old_dir->i_ino) if (le32_to_cpu(parent_de->inode) != old_dir->i_ino)
goto end_rename; goto end_rename;
retval = -EMLINK; retval = -EMLINK;
......
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