From: Darrick J. Wong Date: Sun, 10 Aug 2014 22:42:59 +0000 (-0400) Subject: e2fsck: fix conflicting extents|inlinedata inode flags X-Git-Tag: v1.43-WIP-2015-05-18~231 X-Git-Url: https://git.whamcloud.com/?a=commitdiff_plain;h=fa441f91f1dc02f7c72694b5d4f65374b5a18f44;p=tools%2Fe2fsprogs.git e2fsck: fix conflicting extents|inlinedata inode flags If we come across an inode with the inline data and extents inode flag set, try to figure out the correct flag settings from the contents of i_block and i_size. If i_blocks looks like an extent tree head, we'll make it an extent inode; if it's small enough for inline data, set it to that. This leaves the weird gray area where there's no extent tree but it's too big for the inode -- if /could/ be a block map, change it to that; otherwise, just clear the inode. Signed-off-by: Darrick J. Wong Signed-off-by: Theodore Ts'o --- diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c index 2423f28..53ded2e 100644 --- a/e2fsck/pass1.c +++ b/e2fsck/pass1.c @@ -759,6 +759,108 @@ static void finish_processing_inode(e2fsck_t ctx, ext2_ino_t ino, return; \ } while (0) +static int could_be_block_map(ext2_filsys fs, struct ext2_inode *inode) +{ + __u32 x; + int i; + + for (i = 0; i < EXT2_N_BLOCKS; i++) { + x = inode->i_block[i]; +#ifdef WORDS_BIGENDIAN + x = ext2fs_swab32(x); +#endif + if (x >= ext2fs_blocks_count(fs->super)) + return 0; + } + + return 1; +} + +/* + * Figure out what to do with an inode that has both extents and inline data + * inode flags set. Returns -1 if we decide to erase the inode, 0 otherwise. + */ +static int fix_inline_data_extents_file(e2fsck_t ctx, + ext2_ino_t ino, + struct ext2_inode *inode, + int inode_size, + struct problem_context *pctx) +{ + size_t max_inline_ea_size; + ext2_filsys fs = ctx->fs; + int dirty = 0; + + /* Both feature flags not set? Just run the regular checks */ + if (!EXT2_HAS_INCOMPAT_FEATURE(fs->super, + EXT3_FEATURE_INCOMPAT_EXTENTS) && + !EXT2_HAS_INCOMPAT_FEATURE(fs->super, + EXT4_FEATURE_INCOMPAT_INLINE_DATA)) + return 0; + + /* Clear both flags if it's a special file */ + if (LINUX_S_ISCHR(inode->i_mode) || + LINUX_S_ISBLK(inode->i_mode) || + LINUX_S_ISFIFO(inode->i_mode) || + LINUX_S_ISSOCK(inode->i_mode)) { + check_extents_inlinedata(ctx, pctx); + return 0; + } + + /* If it looks like an extent tree, try to clear inlinedata */ + if (ext2fs_extent_header_verify(inode->i_block, + sizeof(inode->i_block)) == 0 && + fix_problem(ctx, PR_1_CLEAR_INLINE_DATA_FOR_EXTENT, pctx)) { + inode->i_flags &= ~EXT4_INLINE_DATA_FL; + dirty = 1; + goto out; + } + + /* If it looks short enough to be inline data, try to clear extents */ + if (EXT2_INODE_SIZE(fs->super) > EXT2_GOOD_OLD_INODE_SIZE) + max_inline_ea_size = EXT2_INODE_SIZE(fs->super) - + (EXT2_GOOD_OLD_INODE_SIZE + + ((struct ext2_inode_large *)inode)->i_extra_isize); + else + max_inline_ea_size = 0; + if (EXT2_I_SIZE(inode) < + EXT4_MIN_INLINE_DATA_SIZE + max_inline_ea_size && + fix_problem(ctx, PR_1_CLEAR_EXTENT_FOR_INLINE_DATA, pctx)) { + inode->i_flags &= ~EXT4_EXTENTS_FL; + dirty = 1; + goto out; + } + + /* + * Too big for inline data, but no evidence of extent tree - + * maybe it's a block map file? If the mappings all look valid? + */ + if (could_be_block_map(fs, inode) && + fix_problem(ctx, PR_1_CLEAR_EXTENT_INLINE_DATA_FLAGS, pctx)) { +#ifdef WORDS_BIGENDIAN + int i; + + for (i = 0; i < EXT2_N_BLOCKS; i++) + inode->i_block[i] = ext2fs_swab32(inode->i_block[i]); +#endif + + inode->i_flags &= ~(EXT4_EXTENTS_FL | EXT4_INLINE_DATA_FL); + dirty = 1; + goto out; + } + + /* Oh well, just clear the busted inode. */ + if (fix_problem(ctx, PR_1_CLEAR_EXTENT_INLINE_DATA_INODE, pctx)) { + e2fsck_clear_inode(ctx, ino, inode, 0, "pass1"); + return -1; + } + +out: + if (dirty) + e2fsck_write_inode(ctx, ino, inode, "pass1"); + + return 0; +} + void e2fsck_pass1(e2fsck_t ctx) { int i; @@ -1007,6 +1109,18 @@ void e2fsck_pass1(e2fsck_t ctx) } } + /* Conflicting inlinedata/extents inode flags? */ + if ((inode->i_flags & EXT4_INLINE_DATA_FL) && + (inode->i_flags & EXT4_EXTENTS_FL)) { + int res = fix_inline_data_extents_file(ctx, ino, inode, + inode_size, + &pctx); + if (res < 0) { + /* skip FINISH_INODE_LOOP */ + continue; + } + } + /* Test for incorrect inline_data flags settings. */ if ((inode->i_flags & EXT4_INLINE_DATA_FL) && !inlinedata_fs && (ino >= EXT2_FIRST_INODE(fs->super))) { diff --git a/e2fsck/problem.c b/e2fsck/problem.c index 26ee51b..4245244 100644 --- a/e2fsck/problem.c +++ b/e2fsck/problem.c @@ -1056,6 +1056,26 @@ static struct e2fsck_problem problem_table[] = { "or inline-data flag set. "), PROMPT_CLEAR, PR_PREEN_OK | PR_PREEN_NO | PR_NO_OK }, + /* Inode has extent header but inline data flag is set */ + { PR_1_CLEAR_INLINE_DATA_FOR_EXTENT, + N_("@i %i has @x header but inline data flag is set.\n"), + PROMPT_FIX, 0 }, + + /* Inode seems to have inline data but extent flag is set */ + { PR_1_CLEAR_EXTENT_FOR_INLINE_DATA, + N_("@i %i seems to have inline data but @x flag is set.\n"), + PROMPT_FIX, 0 }, + + /* Inode seems to have block map but inline data and extent flags set */ + { PR_1_CLEAR_EXTENT_INLINE_DATA_FLAGS, + N_("@i %i seems to have @b map but inline data and @x flags set.\n"), + PROMPT_FIX, 0 }, + + /* Inode has inline data and extent flags but i_block contains junk */ + { PR_1_CLEAR_EXTENT_INLINE_DATA_INODE, + N_("@i %i has inline data and @x flags set but i_block contains junk.\n"), + PROMPT_CLEAR_INODE, 0 }, + /* Pass 1b errors */ /* Pass 1B: Rescan for duplicate/bad blocks */ diff --git a/e2fsck/problem.h b/e2fsck/problem.h index 3c5e11a..22c86c5 100644 --- a/e2fsck/problem.h +++ b/e2fsck/problem.h @@ -615,6 +615,18 @@ struct problem_context { /* extents/inlinedata set on fifo/socket/device */ #define PR_1_SPECIAL_EXTENTS_IDATA 0x010076 +/* idata/extent flag set and extent header found, clear idata flag */ +#define PR_1_CLEAR_INLINE_DATA_FOR_EXTENT 0x010077 + +/* inlinedata/extent set and no extent header found, clear extent flag */ +#define PR_1_CLEAR_EXTENT_FOR_INLINE_DATA 0x010078 + +/* inlinedata/extent set, clear both flags */ +#define PR_1_CLEAR_EXTENT_INLINE_DATA_FLAGS 0x010079 + +/* inlinedata/extent set, clear inode */ +#define PR_1_CLEAR_EXTENT_INLINE_DATA_INODE 0x01007A + /* * Pass 1b errors */ diff --git a/tests/f_idata_and_extents/expect.1 b/tests/f_idata_and_extents/expect.1 new file mode 100644 index 0000000..7f7fbf3 --- /dev/null +++ b/tests/f_idata_and_extents/expect.1 @@ -0,0 +1,35 @@ +Pass 1: Checking inodes, blocks, and sizes +Special (device/socket/fifo) file (inode 19) has extents +or inline-data flag set. Clear? yes + +Inode 20 has extent header but inline data flag is set. +Fix? yes + +Inode 21 has inline data and extent flags set but i_block contains junk. +Clear inode? yes + +Inode 22 seems to have block map but inline data and extent flags set. +Fix? yes + +Inode 23 seems to have inline data but extent flag is set. +Fix? yes + +Pass 2: Checking directory structure +Entry 'garbage' in /bad (18) has deleted/unused inode 21. Clear? yes + +Pass 3: Checking directory connectivity +Pass 4: Checking reference counts +Pass 5: Checking group summary information +Inode bitmap differences: -21 +Fix? yes + +Free inodes count wrong for group #0 (105, counted=106). +Fix? yes + +Free inodes count wrong (105, counted=106). +Fix? yes + + +test_filesys: ***** FILE SYSTEM WAS MODIFIED ***** +test_filesys: 22/128 files (0.0% non-contiguous), 21/512 blocks +Exit status is 1 diff --git a/tests/f_idata_and_extents/expect.2 b/tests/f_idata_and_extents/expect.2 new file mode 100644 index 0000000..307d3f6 --- /dev/null +++ b/tests/f_idata_and_extents/expect.2 @@ -0,0 +1,7 @@ +Pass 1: Checking inodes, blocks, and sizes +Pass 2: Checking directory structure +Pass 3: Checking directory connectivity +Pass 4: Checking reference counts +Pass 5: Checking group summary information +test_filesys: 22/128 files (0.0% non-contiguous), 21/512 blocks +Exit status is 0 diff --git a/tests/f_idata_and_extents/image.gz b/tests/f_idata_and_extents/image.gz new file mode 100644 index 0000000..5187ba1 Binary files /dev/null and b/tests/f_idata_and_extents/image.gz differ diff --git a/tests/f_idata_and_extents/name b/tests/f_idata_and_extents/name new file mode 100644 index 0000000..362ce0e --- /dev/null +++ b/tests/f_idata_and_extents/name @@ -0,0 +1 @@ +conflicting extents and inline_data inode flags