From fa441f91f1dc02f7c72694b5d4f65374b5a18f44 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 10 Aug 2014 18:42:59 -0400 Subject: [PATCH] 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 --- e2fsck/pass1.c | 114 +++++++++++++++++++++++++++++ e2fsck/problem.c | 20 +++++ e2fsck/problem.h | 12 +++ tests/f_idata_and_extents/expect.1 | 35 +++++++++ tests/f_idata_and_extents/expect.2 | 7 ++ tests/f_idata_and_extents/image.gz | Bin 0 -> 3112 bytes tests/f_idata_and_extents/name | 1 + 7 files changed, 189 insertions(+) create mode 100644 tests/f_idata_and_extents/expect.1 create mode 100644 tests/f_idata_and_extents/expect.2 create mode 100644 tests/f_idata_and_extents/image.gz create mode 100644 tests/f_idata_and_extents/name diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c index 2423f281..53ded2e7 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 26ee51bd..4245244d 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 3c5e11ad..22c86c5c 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 00000000..7f7fbf39 --- /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 00000000..307d3f64 --- /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 0000000000000000000000000000000000000000..5187ba14138568a1ccd3540228b092f93186d109 GIT binary patch literal 3112 zcmeIxT})GV7zXg8MnQ}yL5)$cYIMvfWdxZDVk1r*0)wDisaDj2pi(Rf43x4HC)Fun zCtre9kJy#LfMTs+3qtD#fz6NdS)fH4C~c)sTPPMzdwTxE>)m*<%f2^H-sF95e#uM7 zb%u_;tU9uAkwJ0dZ1EWn$fOGjZ1hhItkO8eev|1onDFg&H`d}~&zxz`SJRXE0`dO0 zZ2jh9;)@#2&zl>gZjRL6%%+!K?mFlkay7m)&t=<=%10YdH_P@IF9lwBSpUYaN5#No z<`&%xWh!oLH)ATH`@$5i!kutwQ%yJrDO}ko)TL5Z?HV|8e#s4|<8UbVi@e(n_`RGo z^ON&upQKMRwo;nDJU&?ZwU>oux}<2q$M829LEY26FPSpyfh3jQ8zxLNd|Xa7P>mkU zC!I5sB{o-J>TBuW@UDLQQcmyz{)}R7ul67Pj91wh%3Q~Ja0P;1UyH$sq3YW-yXt|w z0)*HZ)uYa}Zm2%?%t0azbgkmSlUF5HCByI`?5#90-(PHLa3wsR?@IkIbgh~NU1*5SX@2FeU)(ew&=6Sk zDL#Lsef|N~!-E0yjg;o8rOuz<{Ldw5`=k7&Tp+qL-C|0yMmdj|Os6Kg%A<>7DJ%82 zl23H-*dHZcn@AsK3wEB@J_3E`^6u~Dw2Cq>gxd2Pz*SV3$grj`d$y?If_&KTc_lrl zjD~GTZg{M&ysZ}CVy_&(zcl?Yv8F?hd=+YUPeL${K2jWRH4LY_*MXtPzDf`qp*(U9 zM-qHX;EE`u*896SN@ZlD?Mjm^AW*SH5iAnN>?4K;WUNTDEYInT8A>?aJ*)&&nWdXB zFf}(?vRwmD<&u0_cMH=EoTP5-5I$!`1(>;>$2G+?WLNbUT>o`U*<&dg&8m!ORK%WT zOz878bWvchCf8WbP1W6^k(M1cR@H!OSz+zI^5zIiQNElddR8lu{8VAfOk`n){$&4t z>yBJ103zl#a8t>a!NgWf?V5`+I%uO9nLOlP*azx+CgaFFEGzOpG2|XRNg|RbtONv% zk3<*AfNwuf_&zR!g)K7<;h}k@!0rnQRivZ!m{3@?)i4{i&igI`csx$RaC`1*Hpr?D zvGi~F#m&Y}{+2ADX0C`PO=d0R$m5$cCZ^fc>S1()xDCJ ztnn!hQ|q5T6##i!cAMy8=4(MMhaif^QOVspwwq4EZ8HOU^f^r8J&{F!NO{O-?BH;I zsF2l`(T0#oj1(}Ao+OtVP_Unc{05maG%G9U| zH7dV@IG4-K_cN%u<6}tv0&_8ZKcmHi*cOk7E24H%15FotLw#2z> zrQ*LqnG4!3uA60=wQB`npwaKsKRL!B;1F;KI0PI54uSt&K&v3N2~T{0ppDSKYUGCn literal 0 HcmV?d00001 diff --git a/tests/f_idata_and_extents/name b/tests/f_idata_and_extents/name new file mode 100644 index 00000000..362ce0e8 --- /dev/null +++ b/tests/f_idata_and_extents/name @@ -0,0 +1 @@ +conflicting extents and inline_data inode flags