diff options
| -rw-r--r-- | fs/ext4/namei.c | 291 | 
1 files changed, 119 insertions, 172 deletions
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 3e1529c39808..0e28c749e273 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -83,6 +83,86 @@ static struct buffer_head *ext4_append(handle_t *handle,  	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  #define assert(test) J_ASSERT(test)  #endif @@ -604,9 +684,9 @@ dx_probe(const struct qstr *d_name, struct inode *dir,  	u32 hash;  	frame->bh = NULL; -	if (!(bh = ext4_bread(NULL, dir, 0, 0, err))) { -		if (*err == 0) -			*err = ERR_BAD_DX_DIR; +	bh = ext4_read_dirblock(dir, 0, INDEX); +	if (IS_ERR(bh)) { +		*err = PTR_ERR(bh);  		goto fail;  	}  	root = (struct dx_root *) bh->b_data; @@ -643,15 +723,6 @@ dx_probe(const struct qstr *d_name, struct inode *dir,  		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) +  				       root->info.info_length); @@ -709,23 +780,13 @@ dx_probe(const struct qstr *d_name, struct inode *dir,  		frame->entries = entries;  		frame->at = at;  		if (!indirect--) return frame; -		if (!(bh = ext4_bread(NULL, dir, dx_get_block(at), 0, err))) { -			if (!(*err)) -				*err = ERR_BAD_DX_DIR; +		bh = ext4_read_dirblock(dir, dx_get_block(at), INDEX); +		if (IS_ERR(bh)) { +			*err = PTR_ERR(bh);  			goto fail2;  		}  		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)) {  			ext4_warning(dir->i_sb,  				     "dx entry: limit != node limit"); @@ -783,7 +844,7 @@ static int ext4_htree_next_block(struct inode *dir, __u32 hash,  {  	struct dx_frame *p;  	struct buffer_head *bh; -	int err, num_frames = 0; +	int num_frames = 0;  	__u32 bhash;  	p = frame; @@ -822,26 +883,9 @@ static int ext4_htree_next_block(struct inode *dir, __u32 hash,  	 * block so no check is necessary  	 */  	while (num_frames--) { -		if (!(bh = ext4_bread(NULL, dir, dx_get_block(p->at), -				      0, &err))) { -			if (!err) { -				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); - +		bh = ext4_read_dirblock(dir, dx_get_block(p->at), INDEX); +		if (IS_ERR(bh)) +			return PTR_ERR(bh);  		p++;  		brelse(p->bh);  		p->bh = bh; @@ -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",  							(unsigned long)block)); -	if (!(bh = ext4_bread(NULL, dir, block, 0, &err))) { -		if (!err) { -			err = -EIO; -			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); +	bh = ext4_read_dirblock(dir, block, DIRENT); +	if (IS_ERR(bh)) +		return PTR_ERR(bh);  	de = (struct ext4_dir_entry_2 *) bh->b_data;  	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  		return NULL;  	do {  		block = dx_get_block(frame->at); -		if (!(bh = ext4_bread(NULL, dir, block, 0, err))) { -			if (!(*err)) { -				*err = -EIO; -				ext4_error(dir->i_sb, -					   "Directory hole detected on inode %lu\n", -					   dir->i_ino); -			} +		bh = ext4_read_dirblock(dir, block, DIRENT); +		if (IS_ERR(bh)) { +			*err = PTR_ERR(bh);  			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; -		} -		set_buffer_verified(bh);  		retval = search_dirblock(bh, dir, d_name,  					 block << EXT4_BLOCK_SIZE_BITS(sb),  					 res_dir); @@ -1920,22 +1935,10 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,  	}  	blocks = dir->i_size >> sb->s_blocksize_bits;  	for (block = 0; block < blocks; block++) { -		if (!(bh = ext4_bread(handle, dir, block, 0, &retval))) { -			if (!retval) { -				retval = -EIO; -				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); +		bh = ext4_read_dirblock(dir, block, DIRENT); +		if (IS_ERR(bh)) +			return PTR_ERR(bh); +  		retval = add_dirent_to_buf(handle, dentry, inode, NULL, bh);  		if (retval != -ENOSPC) {  			brelse(bh); @@ -1986,22 +1989,13 @@ static int ext4_dx_add_entry(handle_t *handle, struct dentry *dentry,  		return err;  	entries = frame->entries;  	at = frame->at; - -	if (!(bh = ext4_bread(handle, dir, dx_get_block(frame->at), 0, &err))) { -		if (!err) { -			err = -EIO; -			ext4_error(dir->i_sb, -				   "Directory hole detected on inode %lu\n", -				   dir->i_ino); -		} +	bh = ext4_read_dirblock(dir, dx_get_block(frame->at), DIRENT); +	if (IS_ERR(bh)) { +		err = PTR_ERR(bh); +		bh = NULL;  		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");  	err = ext4_journal_get_write_access(handle, bh);  	if (err) @@ -2352,6 +2346,7 @@ static int ext4_init_new_dir(handle_t *handle, struct inode *dir,  	struct buffer_head *dir_block = NULL;  	struct ext4_dir_entry_2 *de;  	struct ext4_dir_entry_tail *t; +	ext4_lblk_t block = 0;  	unsigned int blocksize = dir->i_sb->s_blocksize;  	int csum_size = 0;  	int err; @@ -2368,16 +2363,9 @@ static int ext4_init_new_dir(handle_t *handle, struct inode *dir,  			goto out;  	} -	inode->i_size = EXT4_I(inode)->i_disksize = blocksize; -	if (!(dir_block = ext4_bread(handle, inode, 0, 1, &err))) { -		if (!err) { -			err = -EIO; -			ext4_error(inode->i_sb, -				   "Directory hole detected on inode %lu\n", -				   inode->i_ino); -		} +	inode->i_size = 0; +	if (!(dir_block = ext4_append(handle, inode, &block, &err)))  		goto out; -	}  	BUFFER_TRACE(dir_block, "get_write_access");  	err = ext4_journal_get_write_access(handle, dir_block);  	if (err) @@ -2477,26 +2465,14 @@ static int empty_dir(struct inode *inode)  	}  	sb = inode->i_sb; -	if (inode->i_size < EXT4_DIR_REC_LEN(1) + EXT4_DIR_REC_LEN(2) || -	    !(bh = ext4_bread(NULL, inode, 0, 0, &err))) { -		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); +	if (inode->i_size < EXT4_DIR_REC_LEN(1) + EXT4_DIR_REC_LEN(2)) { +		EXT4_ERROR_INODE(inode, "invalid size");  		return 1;  	} -	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); +	bh = ext4_read_dirblock(inode, 0, EITHER); +	if (IS_ERR(bh)) +		return 1; +  	de = (struct ext4_dir_entry_2 *) bh->b_data;  	de1 = ext4_next_entry(de, sb->s_blocksize);  	if (le32_to_cpu(de->inode) != inode->i_ino || @@ -2519,29 +2495,9 @@ static int empty_dir(struct inode *inode)  			err = 0;  			brelse(bh);  			lblock = offset >> EXT4_BLOCK_SIZE_BITS(sb); -			bh = ext4_bread(NULL, inode, lblock, 0, &err); -			if (!bh) { -				if (err) -					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); +			bh = ext4_read_dirblock(inode, lblock, EITHER); +			if (IS_ERR(bh)) +				return 1;  			de = (struct ext4_dir_entry_2 *) bh->b_data;  		}  		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,  	struct buffer_head *bh;  	if (!ext4_has_inline_data(inode)) { -		if (!(bh = ext4_bread(handle, inode, 0, 0, retval))) { -			if (!*retval) { -				*retval = -EIO; -				ext4_error(inode->i_sb, -					   "Directory hole detected on inode %lu\n", -					   inode->i_ino); -			} +		bh = ext4_read_dirblock(inode, 0, EITHER); +		if (IS_ERR(bh)) { +			*retval = PTR_ERR(bh);  			return NULL;  		}  		*parent_de = ext4_next_entry( @@ -3089,11 +3041,6 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,  						  &inlined);  		if (!dir_bh)  			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)  			goto end_rename;  		retval = -EMLINK;  | 
