]> git.ipfire.org Git - thirdparty/e2fsprogs.git/commitdiff
e2fsck: offer to clear inode table blocks that are insane
authorDarrick J. Wong <darrick.wong@oracle.com>
Sun, 3 Aug 2014 02:46:16 +0000 (22:46 -0400)
committerTheodore Ts'o <tytso@mit.edu>
Sun, 3 Aug 2014 02:46:16 +0000 (22:46 -0400)
Add a new behavior flag to the inode scan functions; when specified,
this flag will do some simple sanity checking of entire inode table
blocks.  If all the checksums are ok, we can skip checksum
verification on individual inodes later on.  If more than half of the
inodes look "insane" (bad extent tree root or checksum failure) then
ext2fs_get_next_inode_full() can return a special status code
indicating that what's in the buffer is probably garbage.

When e2fsck' inode scan encounters the 'inode is garbage' return code
it'll offer to zap the inode straightaway instead of trying to recover
anything.  This replaces the previous behavior of asking to zap
anything with a checksum error (strict_csum).

Signed-off-by: Darrick J. Wong <darrick.wong@orale.com>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
e2fsck/pass1.c
e2fsck/problem.c
e2fsck/problem.h
lib/ext2fs/ext2_err.et.in
lib/ext2fs/ext2fs.h
lib/ext2fs/inode.c

index 283b0b1e8675ed38c3ddf864d99bb0fc1e70da08..a7792c40071eaa6fb021e123c1303fedc28f1113 100644 (file)
@@ -809,7 +809,8 @@ void e2fsck_pass1(e2fsck_t ctx)
                ctx->flags |= E2F_FLAG_ABORT;
                goto endit;
        }
-       ext2fs_inode_scan_flags(scan, EXT2_SF_SKIP_MISSING_ITABLE, 0);
+       ext2fs_inode_scan_flags(scan, EXT2_SF_SKIP_MISSING_ITABLE |
+                                     EXT2_SF_WARN_GARBAGE_INODES, 0);
        ctx->stashed_inode = inode;
        scan_struct.ctx = ctx;
        scan_struct.block_buf = block_buf;
@@ -851,7 +852,8 @@ void e2fsck_pass1(e2fsck_t ctx)
                        continue;
                }
                if (pctx.errcode &&
-                   pctx.errcode != EXT2_ET_INODE_CSUM_INVALID) {
+                   pctx.errcode != EXT2_ET_INODE_CSUM_INVALID &&
+                   pctx.errcode != EXT2_ET_INODE_IS_GARBAGE) {
                        fix_problem(ctx, PR_1_ISCAN_ERROR, &pctx);
                        ctx->flags |= E2F_FLAG_ABORT;
                        goto endit;
@@ -862,12 +864,14 @@ void e2fsck_pass1(e2fsck_t ctx)
                pctx.inode = inode;
                ctx->stashed_ino = ino;
 
-               /* Clear corrupt inode? */
-               if (pctx.errcode == EXT2_ET_INODE_CSUM_INVALID) {
-                       if (fix_problem(ctx, PR_1_INODE_CSUM_INVALID, &pctx))
-                               goto clear_inode;
-                       failed_csum = 1;
+               /* Clear trashed inode? */
+               if (pctx.errcode == EXT2_ET_INODE_IS_GARBAGE &&
+                   inode->i_links_count > 0 &&
+                   fix_problem(ctx, PR_1_INODE_IS_GARBAGE, &pctx)) {
+                       pctx.errcode = 0;
+                       e2fsck_clear_inode(ctx, ino, inode, 0, "pass1");
                }
+               failed_csum = pctx.errcode != 0;
 
                if (inode->i_links_count) {
                        pctx.errcode = ext2fs_icount_store(ctx->inode_link_info,
index 80ca3a081d974dff8a0b96e22b0d0963f9b1014a..452137ac810257940016b97e0f7a6a3200ac51d7 100644 (file)
@@ -967,10 +967,10 @@ static struct e2fsck_problem problem_table[] = {
          N_("@i %i has zero length extent\n\t(@n logical @b %c, physical @b %b)\n"),
          PROMPT_CLEAR, 0 },
 
-       /* inode checksum does not match inode */
-       { PR_1_INODE_CSUM_INVALID,
-         N_("@i %i checksum does not match @i.  "),
-         PROMPT_CLEAR, PR_PREEN_OK },
+       /* inode seems to contain garbage */
+       { PR_1_INODE_IS_GARBAGE,
+         N_("@i %i seems to contain garbage.  "),
+         PROMPT_CLEAR, 0 },
 
        /* inode passes checks, but checksum does not match inode */
        { PR_1_INODE_ONLY_CSUM_INVALID,
index 4813fc604d4623c4c6b205be4fb22e17e6b6bf26..72cfc4d5b5e8e5f0fb35bbccd0945a1caca4abe5 100644 (file)
@@ -571,8 +571,8 @@ struct problem_context {
 /* Extent has zero length */
 #define PR_1_EXTENT_LENGTH_ZERO                0x010066
 
-/* inode checksum does not match inode */
-#define PR_1_INODE_CSUM_INVALID                0x010067
+/* inode seems to contain garbage */
+#define PR_1_INODE_IS_GARBAGE          0x010067
 
 /* inode passes checks, but checksum does not match inode */
 #define PR_1_INODE_ONLY_CSUM_INVALID   0x010068
index 51c88d033cb7c6ddd4ef78a177f2122fcafc962f..2194a1870a421b030000313ac6440d157c4d5044 100644 (file)
@@ -515,4 +515,7 @@ ec  EXT2_ET_INLINE_DATA_NO_SPACE,
 ec     EXT2_ET_MAGIC_EA_HANDLE,
        "Wrong magic number for extended attribute structure"
 
+ec     EXT2_ET_INODE_IS_GARBAGE,
+       "Inode seems to contain garbage"
+
        end
index 7812956af2a358eb5aaee0acbd702949f2f8453c..b4a9f84b7b51e0b43fb51a8239d74e38e88e230a 100644 (file)
@@ -462,6 +462,7 @@ typedef struct ext2_struct_inode_scan *ext2_inode_scan;
 #define EXT2_SF_BAD_EXTRA_BYTES        0x0004
 #define EXT2_SF_SKIP_MISSING_ITABLE    0x0008
 #define EXT2_SF_DO_LAZY                0x0010
+#define EXT2_SF_WARN_GARBAGE_INODES    0x0020
 
 /*
  * ext2fs_check_if_mounted flags
index 0cea9f0215f1c3d6b05737698f02caadbb1cc474..08024cbd5acdfeed7b45204d0956b7f984dccd2b 100644 (file)
 #include "ext2fsP.h"
 #include "e2image.h"
 
+#define IBLOCK_STATUS_CSUMS_OK 1
+#define IBLOCK_STATUS_INSANE   2
+#define SCAN_BLOCK_STATUS(scan)        ((scan)->temp_buffer + (scan)->inode_size)
+
 struct ext2_struct_inode_scan {
        errcode_t               magic;
        ext2_filsys             fs;
@@ -193,12 +197,14 @@ errcode_t ext2fs_open_inode_scan(ext2_filsys fs, int buffer_blocks,
                ext2fs_free_mem(&scan);
                return retval;
        }
-       retval = ext2fs_get_mem(scan->inode_size, &scan->temp_buffer);
+       retval = ext2fs_get_mem(scan->inode_size + scan->inode_buffer_blocks,
+                               &scan->temp_buffer);
        if (retval) {
                ext2fs_free_mem(&scan->inode_buffer);
                ext2fs_free_mem(&scan);
                return retval;
        }
+       memset(SCAN_BLOCK_STATUS(scan), 0, scan->inode_buffer_blocks);
        if (scan->fs->badblocks && scan->fs->badblocks->num)
                scan->scan_flags |= EXT2_SF_CHK_BADBLOCKS;
        if (ext2fs_has_group_desc_csum(fs))
@@ -348,6 +354,114 @@ static errcode_t check_for_inode_bad_blocks(ext2_inode_scan scan,
        return 0;
 }
 
+static int block_map_looks_insane(ext2_filsys fs,
+                                 struct ext2_inode_large *inode)
+{
+       unsigned int i, bad;
+
+       /* We're only interested in block mapped files, dirs, and symlinks */
+       if ((inode->i_flags & EXT4_INLINE_DATA_FL) ||
+           (inode->i_flags & EXT4_EXTENTS_FL))
+               return 0;
+       if (!LINUX_S_ISREG(inode->i_mode) &&
+           !LINUX_S_ISLNK(inode->i_mode) &&
+           !LINUX_S_ISDIR(inode->i_mode))
+               return 0;
+       if (LINUX_S_ISLNK(inode->i_mode) &&
+           EXT2_I_SIZE(inode) <= sizeof(inode->i_block))
+               return 0;
+
+       /* Unused inodes probably aren't insane */
+       if (inode->i_links_count == 0)
+               return 0;
+
+       /* See if more than half the block maps are insane */
+       for (i = 0, bad = 0; i < EXT2_N_BLOCKS; i++)
+               if (inode->i_block[i] != 0 &&
+                   (inode->i_block[i] < fs->super->s_first_data_block ||
+                    inode->i_block[i] >= ext2fs_blocks_count(fs->super)))
+                       bad++;
+       return bad > EXT2_N_BLOCKS / 2;
+}
+
+static int extent_head_looks_insane(struct ext2_inode_large *inode)
+{
+       if (!(inode->i_flags & EXT4_EXTENTS_FL) ||
+           ext2fs_extent_header_verify(inode->i_block,
+                                       sizeof(inode->i_block)) == 0)
+               return 0;
+       return 1;
+}
+
+/*
+ * Check all the inodes that we just read into the buffer.  Record what we
+ * find here -- currently, we can observe that all checksums are ok; more
+ * than half the inodes are insane; or no conclusions at all.
+ */
+static void check_inode_block_sanity(ext2_inode_scan scan, blk64_t num_blocks)
+{
+       ext2_ino_t      ino, inodes_to_scan;
+       unsigned int    badness, checksum_failures;
+       unsigned int    inodes_in_buf, inodes_per_block;
+       void            *p;
+       struct ext2_inode_large *inode;
+       char            *block_status;
+       unsigned int    blk;
+
+       if (!(scan->scan_flags & EXT2_SF_WARN_GARBAGE_INODES))
+               return;
+
+       inodes_to_scan = scan->inodes_left;
+       inodes_in_buf = num_blocks * scan->fs->blocksize / scan->inode_size;
+       if (inodes_to_scan > inodes_in_buf)
+               inodes_to_scan = inodes_in_buf;
+
+       p = scan->inode_buffer;
+       ino = scan->current_inode + 1;
+       checksum_failures = badness = 0;
+       block_status = SCAN_BLOCK_STATUS(scan);
+       memset(block_status, 0, scan->inode_buffer_blocks);
+       inodes_per_block = EXT2_INODES_PER_BLOCK(scan->fs->super);
+
+       while (inodes_to_scan > 0) {
+               blk = (p - (void *)scan->inode_buffer) / scan->fs->blocksize;
+               inode = p;
+
+               /* Is this inode insane? */
+               if (!ext2fs_inode_csum_verify(scan->fs, ino, inode)) {
+                       checksum_failures++;
+                       badness++;
+               } else if (extent_head_looks_insane(inode) ||
+                          block_map_looks_insane(scan->fs, inode))
+                       badness++;
+
+               /* If more than half are insane, declare the whole block bad */
+               if (badness > inodes_per_block / 2) {
+                       unsigned int ino_adj;
+
+                       block_status[blk] |= IBLOCK_STATUS_INSANE;
+                       ino_adj = inodes_per_block -
+                                               ((ino - 1) % inodes_per_block);
+                       if (ino_adj > inodes_to_scan)
+                               ino_adj = inodes_to_scan;
+                       inodes_to_scan -= ino_adj;
+                       p += scan->inode_size * ino_adj;
+                       ino += ino_adj;
+                       checksum_failures = badness = 0;
+                       continue;
+               }
+
+               if ((ino % inodes_per_block) == 0) {
+                       if (checksum_failures == 0)
+                               block_status[blk] |= IBLOCK_STATUS_CSUMS_OK;
+                       checksum_failures = badness = 0;
+               }
+               inodes_to_scan--;
+               p += scan->inode_size;
+               ino++;
+       };
+}
+
 /*
  * This function is called by ext2fs_get_next_inode when it needs to
  * read in more blocks from the current blockgroup's inode table.
@@ -397,12 +511,15 @@ static errcode_t get_next_blocks(ext2_inode_scan scan)
                if (retval)
                        return EXT2_ET_NEXT_INODE_READ;
        }
+       check_inode_block_sanity(scan, num_blocks);
+
        scan->ptr = scan->inode_buffer;
        scan->bytes_left = num_blocks * scan->fs->blocksize;
 
        scan->blocks_left -= num_blocks;
        if (scan->current_block)
                scan->current_block += num_blocks;
+
        return 0;
 }
 
@@ -432,10 +549,14 @@ errcode_t ext2fs_get_next_inode_full(ext2_inode_scan scan, ext2_ino_t *ino,
 {
        errcode_t       retval;
        int             extra_bytes = 0;
-       const int       length = EXT2_INODE_SIZE(scan->fs->super);
+       int             length;
        struct ext2_inode_large *iptr = (struct ext2_inode_large *)inode;
+       char            *iblock_status;
+       unsigned int    iblk;
 
        EXT2_CHECK_MAGIC(scan, EXT2_ET_MAGIC_INODE_SCAN);
+       length = EXT2_INODE_SIZE(scan->fs->super);
+       iblock_status = SCAN_BLOCK_STATUS(scan);
 
        /*
         * Do we need to start reading a new block group?
@@ -503,6 +624,9 @@ errcode_t ext2fs_get_next_inode_full(ext2_inode_scan scan, ext2_ino_t *ino,
        }
 
        retval = 0;
+       iblk = scan->current_inode % EXT2_INODES_PER_GROUP(scan->fs->super) /
+                               EXT2_INODES_PER_BLOCK(scan->fs->super) %
+                               scan->inode_buffer_blocks;
        if (extra_bytes) {
                memcpy(scan->temp_buffer+extra_bytes, scan->ptr,
                       scan->inode_size - extra_bytes);
@@ -510,7 +634,8 @@ errcode_t ext2fs_get_next_inode_full(ext2_inode_scan scan, ext2_ino_t *ino,
                scan->bytes_left -= scan->inode_size - extra_bytes;
 
                /* Verify the inode checksum. */
-               if (!(scan->fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) &&
+               if (!(iblock_status[iblk] & IBLOCK_STATUS_CSUMS_OK) &&
+                   !(scan->fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) &&
                    !ext2fs_inode_csum_verify(scan->fs, scan->current_inode + 1,
                                (struct ext2_inode_large *)scan->temp_buffer))
                        retval = EXT2_ET_INODE_CSUM_INVALID;
@@ -529,7 +654,8 @@ errcode_t ext2fs_get_next_inode_full(ext2_inode_scan scan, ext2_ino_t *ino,
                scan->scan_flags &= ~EXT2_SF_BAD_EXTRA_BYTES;
        } else {
                /* Verify the inode checksum. */
-               if (!(scan->fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) &&
+               if (!(iblock_status[iblk] & IBLOCK_STATUS_CSUMS_OK) &&
+                   !(scan->fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) &&
                    !ext2fs_inode_csum_verify(scan->fs, scan->current_inode + 1,
                                (struct ext2_inode_large *)scan->ptr))
                        retval = EXT2_ET_INODE_CSUM_INVALID;
@@ -548,6 +674,9 @@ errcode_t ext2fs_get_next_inode_full(ext2_inode_scan scan, ext2_ino_t *ino,
                if (scan->scan_flags & EXT2_SF_BAD_INODE_BLK)
                        retval = EXT2_ET_BAD_BLOCK_IN_INODE_TABLE;
        }
+       if ((iblock_status[iblk] & IBLOCK_STATUS_INSANE) &&
+           (retval == 0 || retval == EXT2_ET_INODE_CSUM_INVALID))
+               retval = EXT2_ET_INODE_IS_GARBAGE;
 
        scan->inodes_left--;
        scan->current_inode++;