Skip to contentSkip to conversation

resize2fs: fix ENOSPC corruption case

PATCH Superseded Eric Sandeen 2 participants 7 messages May 18, 2009 – May 26, 2009

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 resize2fs: fix ENOSPC corruption case

http://people.redhat.com/esandeen/livecd-creator-imagefile.bz2
contains an image (for now) which, when resized to 578639, corrupts the filesystem.

This is a bit crazy, I guess, because the fs currently has only 1 free block, but still, we should be graceful about the failure. Perhaps it would make sense to check the requested valuea against the minimum value resize2fs would compute for "-P" and fail (at least without a force).

But in any case, this exposed 2 bugs when moving that one block required an extent split, which is what hit the ENOSPC.

For starters, ext2fs_extent_set_bmap() in the "(re/un)mapping last block in extent" case was replacing the old extent before the new one was created; when the new extent creation failed, it left us in an inconsistent state. Simply changing the order of the two should fix this problem.

Next, ext2fs_extent_insert was calling ext2fs_extent_delete() on *any* error, including one caused by failure to allocate a new block to split the node to hold that extent ... the handle was left unchanged, and we deleted the -original- extent.

As a quick fix for this, just don't do the delete if we fail the split, though this may need to be smarter. I don't think we have terribly consistent behavior about where a handle is left on various errors.

---

lib/ext2fs/extent.c +9 -7
Index: e2fsprogs/lib/ext2fs/extent.c
===================================================================
@@ -1068,16 +1068,17 @@ errcode_t ext2fs_extent_insert(ext2_exte
 
 	retval = ext2fs_extent_replace(handle, 0, extent);
 	if (retval)
-		goto errout;
+		goto errout_delete;
 
 	retval = update_path(handle);
 	if (retval)
-		goto errout;
+		goto errout_delete;
 
 	return 0;
 
-errout:
+errout_delete:
 	ext2fs_extent_delete(handle, 0);
+errout:
 	return retval;
 }
 
@@ -1239,16 +1240,17 @@ again:
 #ifdef DEBUG
 		printf("(re/un)mapping last block in extent\n");
 #endif
-		extent.e_len--;
-		retval = ext2fs_extent_replace(handle, 0, &extent);
-		if (retval)
-			goto done;
+		/* Make sure insert works before replacing old extent */
 		if (physical) {
 			retval = ext2fs_extent_insert(handle,
 					EXT2_EXTENT_INSERT_AFTER, &newextent);
 			if (retval)
 				goto done;
 		}
+		extent.e_len--;
+		retval = ext2fs_extent_replace(handle, 0, &extent);
+		if (retval)
+			goto done;
 	} else if (logical == extent.e_lblk) {
 #ifdef DEBUG
 		printf("(re/un)mapping first block in extent\n");
#1 lore raw
Quoted 9 lines · Eric Sandeen

http://people.redhat.com/esandeen/livecd-creator-imagefile.bz2
contains an image (for now) which, when resized to 578639, corrupts the filesystem.

This is a bit crazy, I guess, because the fs currently has only 1 free block, but still, we should be graceful about the failure. Perhaps it would make sense to check the requested valuea against the minimum value resize2fs would compute for "-P" and fail (at least without a force).

heh, well, maybe not:

[root@inode e2fsprogs]# dumpe2fs -h /mnt/test/livecd-creator-imagefile | grep "Block count"
dumpe2fs 1.41.5 (23-Apr-2009)
Block count: 578640
[root@inode e2fsprogs]# resize/resize2fs.static -P /mnt/test/livecd-creator-imagefile
resize2fs 1.41.5 (23-Apr-2009)
Estimated minimum size of the filesystem: 9171138

The estimated minimum size is 16x the current size.

So much for that plan, at least without a bit more work.

-Eric

#2 lore raw
In reply to Eric Sandeen
Quoted 15 lines · Eric Sandeen

Index: e2fsprogs/lib/ext2fs/extent.c
===================================================================

lib/ext2fs/extent.c +1 -2
@@ -1068,16 +1068,17 @@ errcode_t ext2fs_extent_insert(ext2_exte
 
 	retval = ext2fs_extent_replace(handle, 0, extent);
 	if (retval)
-		goto errout;
+		goto errout_delete;
 
 	retval = update_path(handle);
 	if (retval)
-		goto errout;

lib/ext2fs/extent.c errcode_t ext2fs_extent_insert(ext2_exte · line 1083

Quoted 10 lines
lib/ext2fs/extent.c +3 -1
+		goto errout_delete;
 
 	return 0;
 
-errout:
+errout_delete:
 	ext2fs_extent_delete(handle, 0);
+errout:
 	return retval;
 }

Instead adding an errout_delete and changing what errout means, why not just change:

			retval = extent_node_split(handle);
			if (retval)
-				goto errout;
+				return retval;
			path = handle->path + handle->level;

I also took a quick scan of extent_node_split(), and I'm not 100% convinced it handles all of its error cases sanely. But that should be the focus of a different patch....

Quoted 10 lines
lib/ext2fs/extent.c +1 -4
@@ -1239,16 +1240,17 @@ again:
 #ifdef DEBUG
 		printf("(re/un)mapping last block in extent\n");
 #endif
-		extent.e_len--;
-		retval = ext2fs_extent_replace(handle, 0, &extent);
-		if (retval)
-			goto done;
+		/* Make sure insert works before replacing old extent */
 		if (physical) {

lib/ext2fs/extent.c again: · line 1254

Quoted 10 lines
lib/ext2fs/extent.c +4 -0
 			retval = ext2fs_extent_insert(handle,
 					EXT2_EXTENT_INSERT_AFTER, &newextent);
 			if (retval)
 				goto done;
 		}
+		extent.e_len--;
+		retval = ext2fs_extent_replace(handle, 0, &extent);
+		if (retval)
+			goto done;
 	} else if (logical == extent.e_lblk) {

Have you tested this by hand, using the tst_extents test progam? Code inspection says that ext2fs_extent_insert leaves extent handle pointing at the same place, but I admit the semantics need to be better documented and tested to make sure they are correct in all situations. The extent code is new enough and tricky enough that I'm always cautious about changes to it.

Looks good, though.

- Ted

#3 lore raw
Quoted 34 lines · Theodore Tso
Quoted 14 lines · Eric Sandeen

Index: e2fsprogs/lib/ext2fs/extent.c
===================================================================

lib/ext2fs/extent.c +1 -2
@@ -1068,16 +1068,17 @@ errcode_t ext2fs_extent_insert(ext2_exte
 
 	retval = ext2fs_extent_replace(handle, 0, extent);
 	if (retval)
-		goto errout;
+		goto errout_delete;
 
 	retval = update_path(handle);
 	if (retval)
-		goto errout;

lib/ext2fs/extent.c errcode_t ext2fs_extent_insert(ext2_exte · line 1083

Quoted 10 lines
lib/ext2fs/extent.c +3 -1
+		goto errout_delete;
 
 	return 0;
 
-errout:
+errout_delete:
 	ext2fs_extent_delete(handle, 0);
+errout:
 	return retval;
 }

Instead adding an errout_delete and changing what errout means, why not just change:

			retval = extent_node_split(handle);
			if (retval)
(quoted diff) +1 -1
-				goto errout;
+				return retval;
Quoted 1 line
			path = handle->path + handle->level;

I guess there are other earlier direct returns, so sure, that'd be fine.

Quoted 3 lines

I also took a quick scan of extent_node_split(), and I'm not 100% convinced it handles all of its error cases sanely. But that should be the focus of a different patch....

yeah, it probably needs more careful inspection. Who wrote that thing anyway ... ;)

Quoted 15 lines
Quoted 15 lines
lib/ext2fs/extent.c +1 -4
@@ -1239,16 +1240,17 @@ again:
 #ifdef DEBUG
 		printf("(re/un)mapping last block in extent\n");
 #endif
-		extent.e_len--;
-		retval = ext2fs_extent_replace(handle, 0, &extent);
-		if (retval)
-			goto done;
+		/* Make sure insert works before replacing old extent */
 		if (physical) {
 			retval = ext2fs_extent_insert(handle,
 					EXT2_EXTENT_INSERT_AFTER, &newextent);
 			if (retval)
 				goto done;
Quoted 8 lines

lib/ext2fs/extent.c again: · line 1254

Quoted 6 lines
lib/ext2fs/extent.c +4 -0
 		}
+		extent.e_len--;
+		retval = ext2fs_extent_replace(handle, 0, &extent);
+		if (retval)
+			goto done;
 	} else if (logical == extent.e_lblk) {

Have you tested this by hand, using the tst_extents test progam?

No, but I should.

Quoted 6 lines

Code
inspection says that ext2fs_extent_insert leaves extent handle pointing at the same place, but I admit the semantics need to be better documented and tested to make sure they are correct in all situations. The extent code is new enough and tricky enough that I'm always cautious about changes to it.

yep, this likely needs more work to document the semantics, make sure they're consistent, and check all those error cases.

For F11 I will likely put in the later 2 patches, to fix up the minimum size reporting and then enforce that as the minimum size, so we shouldn't(tm) get into these error paths. Seems like the safer last-minute change.

-Eric

Quoted 3 lines

Looks good, though.

- Ted

#4 lore raw
Quoted 4 lines · Eric Sandeen

For F11 I will likely put in the later 2 patches, to fix up the minimum size reporting and then enforce that as the minimum size, so we shouldn't(tm) get into these error paths. Seems like the safer last-minute change.

The first half of this patch I think is quite safe, and after stepping through tst_etent, I'm pretty sure the second half of this patch would be pretty easy to validate. We probably don't want to enter any of these error paths for now, though, since it's not clear we've fied all of them, particularly in the split_node code.

- Ted

#5 lore raw

PATCH v2 1/1 resize2fs: fix ENOSPC corruption case

In reply to Theodore Tso

http://people.redhat.com/esandeen/livecd-creator-imagefile.bz2
contains an image (for now) which, when resized to 578639, corrupts the filesystem.

This is a bit crazy, I guess, because the fs currently has only 1 free block, but still, we should be graceful about the failure. Perhaps it would make sense to check the requested valuea against the minimum value resize2fs would compute for "-P" and fail (at least without a force).

But in any case, this exposed 2 bugs when moving that one block required an extent split, which is what hit the ENOSPC.

For starters, ext2fs_extent_set_bmap() in the "(re/un)mapping last block in extent" case was replacing the old extent before the new one was created; when the new extent creation failed, it left us in an inconsistent state. Simply changing the order of the two should fix this problem.

Next, ext2fs_extent_insert was calling ext2fs_extent_delete() on *any* error, including one caused by failure to allocate a new block to split the node to hold that extent ... the handle was left unchanged, and we deleted the -original- extent.

As a quick fix for this, just don't do the delete if we fail the split, though this may need to be smarter. I don't think we have terribly consistent behavior about where a handle is left on various errors.

v2: lose the goto change complexity

---

lib/ext2fs/extent.c +6 -5
index 143929e..ddb2d2a 100644
@@ -1040,7 +1040,7 @@ errcode_t ext2fs_extent_insert(ext2_extent_handle_t handle, int flags,
 #endif
 			retval = extent_node_split(handle);
 			if (retval)
-				goto errout;
+				return retval;
 			path = handle->path + handle->level;
 		}
 	}
@@ -1239,16 +1239,17 @@ again:
 #ifdef DEBUG
 		printf("(re/un)mapping last block in extent\n");
 #endif
-		extent.e_len--;
-		retval = ext2fs_extent_replace(handle, 0, &extent);
-		if (retval)
-			goto done;
+		/* Make sure insert works before replacing old extent */
 		if (physical) {
 			retval = ext2fs_extent_insert(handle,
 					EXT2_EXTENT_INSERT_AFTER, &newextent);
 			if (retval)
 				goto done;
 		}
+		extent.e_len--;
+		retval = ext2fs_extent_replace(handle, 0, &extent);
+		if (retval)
+			goto done;
 	} else if (logical == extent.e_lblk) {
 #ifdef DEBUG
 		printf("(re/un)mapping first block in extent\n");