diff mbox series

[v2,3/3] ext4: reimplement ext4_empty_dir() using is_dirent_block_empty

Message ID 20200407064616.221459-3-harshadshirwadkar@gmail.com
State New
Headers show
Series [v2,1/3] ext4: return lblk from ext4_find_entry | expand

Commit Message

harshad shirwadkar April 7, 2020, 6:46 a.m. UTC
The new function added in this patchset adds an efficient way to
check if a dirent block is empty. Based on that, reimplement
ext4_empty_dir().

This is a new patch added in V2.

Signed-off-by: Harshad Shirwadkar <harshadshirwadkar@gmail.com>
---
 fs/ext4/namei.c | 39 ++++++++++++++++-----------------------
 1 file changed, 16 insertions(+), 23 deletions(-)

Comments

Andreas Dilger April 7, 2020, 5:21 p.m. UTC | #1
On Apr 7, 2020, at 12:46 AM, Harshad Shirwadkar <harshadshirwadkar@gmail.com> wrote:
> 
> The new function added in this patchset adds an efficient way to
> check if a dirent block is empty. Based on that, reimplement
> ext4_empty_dir().
> 
> This is a new patch added in V2.
> 
> Signed-off-by: Harshad Shirwadkar <harshadshirwadkar@gmail.com>

Again, a very minor style change iff patch is refreshed.

Reviewed-by: Andreas Dilger <adilger@dilger.ca>


While looking at this code, I noticed that ext4_empty_dir() considers a
directory without a "." or ".." entry to be empty.  I see this was changed
in 64d4ce8923 ("ext4: fix ext4_empty_dir() for directories with holes").
I can understand that we want to not die on corrupted leaf blocks, but it
isn't clear to me that it is a good idea to allow deleting an entire
directory tree if the first block has an error (missing "." or ".." as the
first and second entries) but is otherwise valid.  There were definitely
bugs in the past that made "." or ".." not be the first and second entries.

That isn't a problem in this patch, but seems dangerous in general.  It
doesn't seem like the directory shrinking would trigger this case, since it
is checking for individual empty blocks rather than using ext4_empty_dir().

> diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
> @@ -3218,34 +3219,26 @@ bool ext4_empty_dir(struct inode *inode)
> 		brelse(bh);
> 		return true;
> 	}
> +	de = ext4_next_entry(de, sb->s_blocksize);
> +	if (!is_empty_dirent_block(inode, bh, de)) {
> +		brelse(bh);
> +		return false;
> +	}
> +	brelse(bh);
> +
> +	for (lblk = 1; lblk < inode->i_size >> EXT4_BLOCK_SIZE_BITS(sb);
> +			lblk++) {

(style) should be aligned after '(' on previous line

> +		bh = ext4_read_dirblock(inode, lblk, EITHER);
> +		if (bh == NULL)
> 			continue;
> -		}
> -		if (le32_to_cpu(de->inode)) {
> +		if (IS_ERR(bh))
> +			return true;
> +		if (!is_empty_dirent_block(inode, bh, NULL)) {
> 			brelse(bh);
> 			return false;
> 		}
> -		offset += ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize);
> +		brelse(bh);
> 	}
> -	brelse(bh);
> 	return true;
> }

Cheers, Andreas
Jan Kara April 8, 2020, 9:31 a.m. UTC | #2
On Tue 07-04-20 11:21:01, Andreas Dilger wrote:
> While looking at this code, I noticed that ext4_empty_dir() considers a
> directory without a "." or ".." entry to be empty.  I see this was changed
> in 64d4ce8923 ("ext4: fix ext4_empty_dir() for directories with holes").
> I can understand that we want to not die on corrupted leaf blocks, but it
> isn't clear to me that it is a good idea to allow deleting an entire
> directory tree if the first block has an error (missing "." or ".." as the
> first and second entries) but is otherwise valid.  There were definitely
> bugs in the past that made "." or ".." not be the first and second entries.

That's a good question. I'd just say that ext4_empty_dir() generally
returns true when there's some problem with the directory. In commit
64d4ce8923 I just followed that convention. This behavior of ext4_empty_dir()
(and empty_dir() before in ext3) dates back at least to the beginning of
git history... I guess we could err on the safer side and disallow
directory deletion if there is any problem with it but I guess there was
some motivation for this behavior in the past? Maybe somebody remembers?

									Honza
diff mbox series

Patch

diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index 39360c442dad..dae7d15fba5c 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -3179,6 +3179,7 @@  bool ext4_empty_dir(struct inode *inode)
 	struct buffer_head *bh;
 	struct ext4_dir_entry_2 *de;
 	struct super_block *sb;
+	ext4_lblk_t lblk;
 
 	if (ext4_has_inline_data(inode)) {
 		int has_inline_data = 1;
@@ -3218,34 +3219,26 @@  bool ext4_empty_dir(struct inode *inode)
 		brelse(bh);
 		return true;
 	}
-	offset += ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize);
-	while (offset < inode->i_size) {
-		if (!(offset & (sb->s_blocksize - 1))) {
-			unsigned int lblock;
-			brelse(bh);
-			lblock = offset >> EXT4_BLOCK_SIZE_BITS(sb);
-			bh = ext4_read_dirblock(inode, lblock, EITHER);
-			if (bh == NULL) {
-				offset += sb->s_blocksize;
-				continue;
-			}
-			if (IS_ERR(bh))
-				return true;
-		}
-		de = (struct ext4_dir_entry_2 *) (bh->b_data +
-					(offset & (sb->s_blocksize - 1)));
-		if (ext4_check_dir_entry(inode, NULL, de, bh,
-					 bh->b_data, bh->b_size, offset)) {
-			offset = (offset | (sb->s_blocksize - 1)) + 1;
+	de = ext4_next_entry(de, sb->s_blocksize);
+	if (!is_empty_dirent_block(inode, bh, de)) {
+		brelse(bh);
+		return false;
+	}
+	brelse(bh);
+
+	for (lblk = 1; lblk < inode->i_size >> EXT4_BLOCK_SIZE_BITS(sb);
+			lblk++) {
+		bh = ext4_read_dirblock(inode, lblk, EITHER);
+		if (bh == NULL)
 			continue;
-		}
-		if (le32_to_cpu(de->inode)) {
+		if (IS_ERR(bh))
+			return true;
+		if (!is_empty_dirent_block(inode, bh, NULL)) {
 			brelse(bh);
 			return false;
 		}
-		offset += ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize);
+		brelse(bh);
 	}
-	brelse(bh);
 	return true;
 }