Add f_orphan_flush_abort, which drives e2fsck directly: 1. Build an ext4 image whose inode 12 is an orphan (links_count 0, s_last_orphan = 12). 2. Run e2fsck with a nonexistent bad-blocks file so that orphan cleanup runs first and then read_bad_blocks_file aborts the run via exit() before the normal superblock write-back. 3. Check that s_last_orphan is 0 on disk afterwards. Without the flush after orphan cleanup, s_last_orphan stays stale on disk (still 12) while the orphan inode has already been written back, which the kernel rejects as a "bad orphan inode" on next mount. Signed-off-by: Baokun Li --- tests/f_orphan_flush_abort/expect.1 | 7 ++++ tests/f_orphan_flush_abort/name | 1 + tests/f_orphan_flush_abort/script | 61 +++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 tests/f_orphan_flush_abort/expect.1 create mode 100644 tests/f_orphan_flush_abort/name create mode 100644 tests/f_orphan_flush_abort/script diff --git a/tests/f_orphan_flush_abort/expect.1 b/tests/f_orphan_flush_abort/expect.1 new file mode 100644 index 000000000000..0a6d09f16e36 --- /dev/null +++ b/tests/f_orphan_flush_abort/expect.1 @@ -0,0 +1,7 @@ +Clearing orphaned inode 12 (uid=0, gid=0, mode=0100644, size=8192) +read_bad_blocks_file: No such file or directory while trying to open test.img + +test.img: ***** FILE SYSTEM WAS MODIFIED ***** +Exit status is 9 +Orphan before e2fsck: 12 +Orphan after e2fsck: 0 (cleared) diff --git a/tests/f_orphan_flush_abort/name b/tests/f_orphan_flush_abort/name new file mode 100644 index 000000000000..c9292546c6dd --- /dev/null +++ b/tests/f_orphan_flush_abort/name @@ -0,0 +1 @@ +orphan cleanup superblock flush survives a later abort diff --git a/tests/f_orphan_flush_abort/script b/tests/f_orphan_flush_abort/script new file mode 100644 index 000000000000..9c18011b40db --- /dev/null +++ b/tests/f_orphan_flush_abort/script @@ -0,0 +1,61 @@ +if ! test -x $DEBUGFS_EXE; then + echo "$test_name: $test_description: skipped (no debugfs)" + return 0 +fi + +TEST_DATA="$test_name.tmp" +OUT=$test_name.log + +dd if=/dev/zero of=$TEST_DATA bs=8k count=1 > /dev/null 2>&1 + +$MKE2FS -Fq -t ext4 -o Linux $TMPFILE 4M > /dev/null 2>&1 + +# Create an orphan inode: testfile becomes inode 12 (the first regular inode +# on a fresh fs), drop its link count to 0 and point s_last_orphan at it so +# e2fsck will run orphan cleanup. +$DEBUGFS -w $TMPFILE << EOF > /dev/null 2>&1 +write $TEST_DATA testfile +set_inode_field testfile links_count 0 +set_super_value last_orphan 12 +unlink testfile +quit +EOF + +ORPHAN_BEFORE=$($DUMPE2FS $TMPFILE 2>&1 | grep -E "First orphan inode" | awk '{print $NF}') + +cp /dev/null $OUT + +# Run e2fsck with a nonexistent bad blocks file. Orphan cleanup runs first +# and clears s_last_orphan, then read_bad_blocks_file fails and e2fsck aborts +# via exit() before the normal superblock write-back. The fix flushes the +# superblock right after orphan cleanup, so s_last_orphan is 0 on disk even +# though the run aborted; without it the disk keeps a stale s_last_orphan +# while the orphan inode has already been written back, which the kernel +# rejects as a "bad orphan inode" on next mount. +$FSCK -yf -l /nonexistent/bad_blocks_file $TMPFILE > $OUT.new 2>&1 +status=$? +echo "Exit status is $status" >> $OUT.new +sed -f $cmd_dir/filter.sed -e "s|$TMPFILE|test.img|g" $OUT.new > $OUT +rm -f $OUT.new + +ORPHAN_AFTER=$($DUMPE2FS $TMPFILE 2>&1 | grep -E "First orphan inode" | awk '{print $NF}') + +echo "Orphan before e2fsck: $ORPHAN_BEFORE" >> $OUT +if [ -z "$ORPHAN_AFTER" ]; then + echo "Orphan after e2fsck: 0 (cleared)" >> $OUT +else + echo "Orphan after e2fsck: $ORPHAN_AFTER (BUG: should be 0!)" >> $OUT +fi + +cmp -s $OUT $test_dir/expect.1 +status=$? +if [ "$status" -eq 0 ]; then + echo "$test_name: $test_description: ok" + touch $test_name.ok +else + echo "$test_name: $test_description: failed" + diff $DIFF_OPTS $test_dir/expect.1 $OUT > $test_name.failed +fi + +rm -f $TEST_DATA +unset OUT TEST_DATA -- 2.43.7