Skip to contentSkip to conversation

s_first_meta_bg treatment incompatibility between kernel and e2fsprogs

DISCUSSION Damien Guibouret 2 participants 5 messages Nov 11, 2009 – Nov 16, 2009 ext4 file system

lore
Share & tools
lore
#0 lore raw

Hello,

I have taken a look at META_BG feature and think there is some incoherency between kernel and e2fsprogs about s_first_meta_bg handling.

When considering initialisation of unitialised block bitmaps for groups before first meta group one:
- kernel considers that descriptors blocks occupy EXT4_SB(sb)->s_gdb_count blocks (see ext4_bg_num_gdb_nometa at balloc.c:764 that is indirectly called from ext4_init_block_bitmap), s_gdb_count being the number of blocks to store all descriptors (computed from super.c).
- e2fsprogs considers that descriptors blocks occupy s_first_meta_bg
(see ext2fs_reserve_super_and_bgd at alloc_sb.c:55-68).

The difference of behaviour is wrong as e2fsck will certainly complain there is a bitmap marked unused when it should be if the bitmap was initialised by kernel and number of descriptors blocks is lower than s_first_meta_bg or reverse if number of descriptors blocks is higher than s_first_meta_bg.

So, the kernel behaviour seems to be wrong in the META_BG case when s_first_meta_bg is not 0: ext4_bg_num_gdb returns either 1 (META_BG feature present and group being one of the meta group) or s_gdb_count (META_BG feature not present or group being one before first meta group). As s_gdb_count is number of blocks for all groups, I think it should returns either 1 (META_BG present and group being one of the meta group) or s_first_meta_bg (META_BG present and group being one before first meta group) or s_gdb_count (META_BG not set).

I see also that the resize2fs does not handle the s_first_meta_bg flag in case a filesystem is shrunk such as number of descriptor blocks goes below s_first_meta_bg. To what I looked, it does not seem to be a problem (apart from the problem in kernel described above), but I did not perform a complete check about that. At least there is some blocks still allocated when there is no more need for that (but e2fsck does not complain as it uses also the s_first_meta_bg value). I do not know if it is desired behaviour. In case the s_first_meta_bg is lowered and blocks freed, it will certainly be better to add a check into e2fsck to check that s_first_meta_bg is coherent with number of descriptor blocks (s_first_meta_bg <= fs->desc_blocks).

If you want to perform some tests on that, I modified tune2fs to allow setting the META_BG flag on a filesystem that does not have it with setting s_first_meta_bg to the current number of blocks for descriptors (it is how I understand META_BG/s_first_meta_group should be used).

Regards,

Damien

*** misc/tune2fs.old 2009-11-11 12:20:33.698192912 +0100
--- misc/tune2fs.c 2009-11-11 11:38:20.265333248 +0100
***************
*** 121,126 ****
--- 121,127 ----

                EXT2_FEATURE_COMPAT_DIR_INDEX,
        /* Incompat */
        EXT2_FEATURE_INCOMPAT_FILETYPE |
+         EXT2_FEATURE_INCOMPAT_META_BG |
                EXT3_FEATURE_INCOMPAT_EXTENTS |
                EXT4_FEATURE_INCOMPAT_FLEX_BG,
        /* R/O compat */
***************
*** 418,423 ****
--- 419,440 ----
                }
        }

+ if (FEATURE_ON(E2P_FEATURE_INCOMPAT, EXT2_FEATURE_INCOMPAT_META_BG)) {
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ fputs(_("The meta_bg feature may only be "
+ "set when the filesystem is\n"
+ "unmounted.\n"), stderr);
+ exit(1);
+ }
+ if (sb->s_feature_compat & EXT2_FEATURE_COMPAT_RESIZE_INODE) {
+ fputs(_("The meta_bg feature cannot be "
+ "set when the resize_inode is\n"
+ "set.\n"), stderr);
+ exit(1);
+ }
+ sb->s_first_meta_bg = fs->desc_blocks;
+ }
+

        if (FEATURE_OFF(E2P_FEATURE_RO_INCOMPAT,
                            EXT4_FEATURE_RO_COMPAT_HUGE_FILE)) {
                if ((mount_flags & EXT2_MF_MOUNTED) &&
maintainer
#1 lore raw
Quoted 5 lines · Damien Guibouret

Hello,

I have taken a look at META_BG feature and think there is some incoherency between kernel and e2fsprogs about s_first_meta_bg handling.

Quoted 9 lines

When considering initialisation of unitialised block bitmaps for groups before first meta group one:
- kernel considers that descriptors blocks occupy EXT4_SB(sb)->s_gdb_count blocks (see ext4_bg_num_gdb_nometa at balloc.c:764 that is indirectly called from ext4_init_block_bitmap), s_gdb_count being the number of blocks to store all descriptors (computed from super.c).
- e2fsprogs considers that descriptors blocks occupy s_first_meta_bg (see ext2fs_reserve_super_and_bgd at alloc_sb.c:55-68).

Yup, you're right. Ouch. That is a kernel bug, and it means that if we resize a filesystem to the point where we need to use meta_bg (because we've run out of blocks to reserve), if there are uninitialized block bitmaps, kernels that don't have a fix will misbehave by reserving too many file system metadata blocks. This will waste bit of disk space, which fsck will fix.

(s_first_meta_bg by definition is always less than or equal to s_gdb_count.)

I think this patch should fix things up.

- Ted

commit b33c339814f97fc48a843f45f6068f84bc735141

Author: Theodore Ts'o <[email protected]>
Date:   Sat Nov 14 23:20:30 2009 -0500

ext4: Fix uninit block bitmap initialization when s_meta_first_bg is non-zero

The number of old-style block group descriptor blocks is s_meta_first_bg when the meta_bg feature flag is set.

fs/ext4/balloc.c +7 -1
index 1d04189..f3032c9 100644
@@ -761,7 +761,13 @@ static unsigned long ext4_bg_num_gdb_meta(struct super_block *sb,
 static unsigned long ext4_bg_num_gdb_nometa(struct super_block *sb,
 					ext4_group_t group)
 {
-	return ext4_bg_has_super(sb, group) ? EXT4_SB(sb)->s_gdb_count : 0;
+	if (!ext4_bg_has_super(sb, group))
+		return 0;
+
+	if (EXT4_HAS_INCOMPAT_FEATURE(sb,EXT4_FEATURE_INCOMPAT_META_BG))
+		return le32_to_cpu(EXT4_SB(sb)->s_es->s_first_meta_bg);
+	else
+		return EXT4_SB(sb)->s_gdb_count;
 }
 
 /**
#2 lore raw
Quoted 38 lines · Theodore Tso

On Wed, Nov 11, 2009 at 01:09:00PM +0100, Damien Guibouret wrote:

Yup, you're right. Ouch. That is a kernel bug, and it means that if we resize a filesystem to the point where we need to use meta_bg (because we've run out of blocks to reserve), if there are uninitialized block bitmaps, kernels that don't have a fix will misbehave by reserving too many file system metadata blocks. This will waste bit of disk space, which fsck will fix.

(s_first_meta_bg by definition is always less than or equal to s_gdb_count.)

I think this patch should fix things up.

- Ted

commit b33c339814f97fc48a843f45f6068f84bc735141

Author: Theodore Ts'o <[email protected]>
Date:   Sat Nov 14 23:20:30 2009 -0500

ext4: Fix uninit block bitmap initialization when s_meta_first_bg is non-zero

The number of old-style block group descriptor blocks is s_meta_first_bg when the meta_bg feature flag is set.

fs/ext4/balloc.c +0 -1
index 1d04189..f3032c9 100644
@@ -761,7 +761,13 @@ static unsigned long ext4_bg_num_gdb_meta(struct super_block *sb,
 static unsigned long ext4_bg_num_gdb_nometa(struct super_block *sb,
 					ext4_group_t group)
 {
-	return ext4_bg_has_super(sb, group) ? EXT4_SB(sb)->s_gdb_count : 0;

fs/ext4/balloc.c static unsigned long ext4_bg_num_gdb_meta(struct super_block *sb, · line 773

Quoted 10 lines
fs/ext4/balloc.c +7 -0
+	if (!ext4_bg_has_super(sb, group))
+		return 0;
+
+	if (EXT4_HAS_INCOMPAT_FEATURE(sb,EXT4_FEATURE_INCOMPAT_META_BG))
+		return le32_to_cpu(EXT4_SB(sb)->s_es->s_first_meta_bg);
+	else
+		return EXT4_SB(sb)->s_gdb_count;
 }
 
 /**

Hello,

I've open a kernel bug since: http://bugzilla.kernel.org/show_bug.cgi?id=14601
with a proposed patch (little different from yours but it is matter of taste :)
And I think there is some other places where kernel should be fixed when it uses s_gdb_count (but here my knowledge of the sources are not deep enough to be sure on what shall be performed).

Regards,

Damien

maintainer
#3 lore raw
Quoted 4 lines · Damien Guibouret

I've open a kernel bug since:
http://bugzilla.kernel.org/show_bug.cgi?id=14601
with a proposed patch (little different from yours but it is matter of taste :)

For future references, patches are less likely to slip through the cracks if they are sent to the linux-ext4 mailing list as opposed to having a BZ bug opened. (Yeah, I know, that's unusual). The reason for that is that patches are tracked via patchwork, here:

http://patchwork.ozlabs.org/project/linux-ext4

Basically, anything that looks like a patch which is sent to linux-ext4 gets snagged by patchwork, and it's a good place to look for stuff that hasn't yet been merged. In some cases there are good reasons why a patch has been kept out, and in other cases patches have been merged or definitely rejected and I don't get to getting that status updated in patchwork, but overall I've found it to work very well.

As far as the matter of taste issue is concerned, I think we already have too many static functions with a single caller, and it actually makes the code harder to understand. So adding yet another simple static function I think is a bad thing, not a good thing.

Quoted 3 lines

And I think there is some other places where kernel should be fixed when it uses s_gdb_count (but here my knowledge of the sources are not deep enough to be sure on what shall be performed).

I've looked through the other areas, and the one place where I see a problem is in the block validity checks in ext4_iget() for the extended attribute block and in block_validity.c. The former can and should be fixed to use the latter.

Here's the fix that I plan to be using. Comments, anyone?

- Ted

ext4: fix block validity checks so they work correctly with meta_bg

The block validity checks used by ext4_data_block_valid() wasn't correctly written to check file systems with the meta_bg feature. Fix this.

---
 fs/ext4/block_validity.c |    2 +-
 fs/ext4/inode.c          |    5 +----
 2 files changed, 2 insertions(+), 5 deletions(-)
fs/ext4/block_validity.c +1 -1
index 50784ef..dc79b75 100644
@@ -160,7 +160,7 @@ int ext4_setup_system_zone(struct super_block *sb)
 		if (ext4_bg_has_super(sb, i) &&
 		    ((i < 5) || ((i % flex_size) == 0)))
 			add_system_zone(sbi, ext4_group_first_block_no(sb, i),
-					sbi->s_gdb_count + 1);
+					ext4_bg_num_gdb(sb, i) + 1);
 		gdp = ext4_get_group_desc(sb, i, NULL);
 		ret = add_system_zone(sbi, ext4_block_bitmap(sb, gdp), 1);
 		if (ret)
fs/ext4/inode.c +1 -4
index b5cdb88..c62ca93 100644
@@ -4886,10 +4886,7 @@ struct inode *ext4_iget(struct super_block *sb, unsigned long ino)
 
 	ret = 0;
 	if (ei->i_file_acl &&
-	    ((ei->i_file_acl <
-	      (le32_to_cpu(EXT4_SB(sb)->s_es->s_first_data_block) +
-	       EXT4_SB(sb)->s_gdb_count)) ||
-	     (ei->i_file_acl >= ext4_blocks_count(EXT4_SB(sb)->s_es)))) {
+	    !ext4_data_block_valid(EXT4_SB(sb), ei->i_file_acl, 1)) {
 		ext4_error(sb, __func__,
 			   "bad extended attribute block %llu in inode #%lu",
 			   ei->i_file_acl, inode->i_ino);
#4 lore raw

Hello,

Quoted 1 line · Theodore Tso

On Sun, Nov 15, 2009 at 11:28:13AM +0100, Damien Guibouret wrote:

[...]

Quoted 5 lines

As far as the matter of taste issue is concerned, I think we already have too many static functions with a single caller, and it actually makes the code harder to understand. So adding yet another simple static function I think is a bad thing, not a good thing.

It was just to mimic the existing function, but I agree with you. The other difference is that it shall be applied on ext3 also.

Quoted 11 lines
Quoted 2 lines

And I think there is some other places where kernel should be fixed when it uses s_gdb_count (but here my knowledge of the sources are not deep enough to be sure on what shall be performed).

I've looked through the other areas, and the one place where I see a problem is in the block validity checks in ext4_iget() for the extended attribute block and in block_validity.c. The former can and should be fixed to use the latter.

Here's the fix that I plan to be using. Comments, anyone?

For the first one (on block_validity.c), as far as I understand, presence of superblock and descriptors blocks in a group are no more related in case of meta_bg group, so shouldn't be the code divided into 2 distincts part: one to treat super block, second to treat descriptor blocks (I do not understand the ((i < 5) || ((i % flex_size) == 0) part into the test, so add it if it is trully needed), something as:

		ext4_fsblk_t firstSystemBlock = ext4_group_first_block_no(sb, i);
		unsigned long nbDescBlocks;
                if (ext4_bg_has_super(sb, i)) {
                        add_system_zone(sbi, firstSystemBlock,
                                        1);
			firstSystemBlock++;
		}
		nbDescBlocks = ext4_bg_num_gdb(sb, i);
		if (nbDescBlocks != 0)
			add_system_zone(sbi, firstSystemBlock,
                                        nbDescBlocks);

Regards,

Damien

Quoted 45 lines

- Ted

ext4: fix block validity checks so they work correctly with meta_bg

The block validity checks used by ext4_data_block_valid() wasn't correctly written to check file systems with the meta_bg feature. Fix this.

---
 fs/ext4/block_validity.c |    2 +-
 fs/ext4/inode.c          |    5 +----
 2 files changed, 2 insertions(+), 5 deletions(-)
fs/ext4/block_validity.c +1 -1
index 50784ef..dc79b75 100644
@@ -160,7 +160,7 @@ int ext4_setup_system_zone(struct super_block *sb)
 		if (ext4_bg_has_super(sb, i) &&
 		    ((i < 5) || ((i % flex_size) == 0)))
 			add_system_zone(sbi, ext4_group_first_block_no(sb, i),
-					sbi->s_gdb_count + 1);
+					ext4_bg_num_gdb(sb, i) + 1);
 		gdp = ext4_get_group_desc(sb, i, NULL);
 		ret = add_system_zone(sbi, ext4_block_bitmap(sb, gdp), 1);
 		if (ret)
fs/ext4/inode.c +1 -4
index b5cdb88..c62ca93 100644
@@ -4886,10 +4886,7 @@ struct inode *ext4_iget(struct super_block *sb, unsigned long ino)
 
 	ret = 0;
 	if (ei->i_file_acl &&
-	    ((ei->i_file_acl <
-	      (le32_to_cpu(EXT4_SB(sb)->s_es->s_first_data_block) +
-	       EXT4_SB(sb)->s_gdb_count)) ||
-	     (ei->i_file_acl >= ext4_blocks_count(EXT4_SB(sb)->s_es)))) {
+	    !ext4_data_block_valid(EXT4_SB(sb), ei->i_file_acl, 1)) {
 		ext4_error(sb, __func__,
 			   "bad extended attribute block %llu in inode #%lu",
 			   ei->i_file_acl, inode->i_ino);