Whamcloud - gitweb
e2fsck: clean up xattr checking code
[tools/e2fsprogs.git] / e2fsck / pass1.c
index c565c14..412bbda 100644 (file)
  *     - A bitmap of which inodes are in use.          (inode_used_map)
  *     - A bitmap of which inodes are directories.     (inode_dir_map)
  *     - A bitmap of which inodes are regular files.   (inode_reg_map)
- *     - A bitmap of which inodes have bad fields.     (inode_bad_map)
+ *     - An icount mechanism is used to keep track of
+ *       inodes with bad fields and its badness        (ctx->inode_badness)
  *     - A bitmap of which inodes are in bad blocks.   (inode_bb_map)
  *     - A bitmap of which inodes are imagic inodes.   (inode_imagic_map)
  *     - A bitmap of which inodes are casefolded.      (inode_casefold_map)
+ *     - A bitmap of which inodes need to be expanded  (expand_eisize_map)
  *     - A bitmap of which blocks are in use.          (block_found_map)
  *     - A bitmap of which blocks are in use by two inodes     (block_dup_map)
  *     - The data blocks of the directory inodes.      (dir_map)
@@ -85,7 +87,6 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx,
 static void mark_table_blocks(e2fsck_t ctx);
 static void alloc_bb_map(e2fsck_t ctx);
 static void alloc_imagic_map(e2fsck_t ctx);
-static void mark_inode_bad(e2fsck_t ctx, ino_t ino);
 static void add_casefolded_dir(e2fsck_t ctx, ino_t ino);
 static void handle_fs_bad_blocks(e2fsck_t ctx);
 static EXT2_QSORT_TYPE process_inode_cmp(const void *a, const void *b);
@@ -433,13 +434,13 @@ static void check_ea_in_inode(e2fsck_t ctx, struct problem_context *pctx,
        ea_ibody_quota->inodes = 0;
 
        inode = (struct ext2_inode_large *) pctx->inode;
-       storage_size = EXT2_INODE_SIZE(ctx->fs->super) - EXT2_GOOD_OLD_INODE_SIZE -
-               inode->i_extra_isize;
+       storage_size = EXT2_INODE_SIZE(ctx->fs->super) -
+               EXT2_GOOD_OLD_INODE_SIZE - inode->i_extra_isize;
        header = ((char *) inode) + EXT2_GOOD_OLD_INODE_SIZE +
                 inode->i_extra_isize;
        end = header + storage_size;
-       start = header + sizeof(__u32);
-       entry = (struct ext2_ext_attr_entry *) start;
+       entry = &IHDR(inode)->h_first_entry[0];
+       start = (char *)entry;
 
        /* scan all entry's headers first */
 
@@ -574,7 +575,7 @@ static void check_inode_extra_space(e2fsck_t ctx, struct problem_context *pctx,
        struct ext2_super_block *sb = ctx->fs->super;
        struct ext2_inode_large *inode;
        __u32 *eamagic;
-       int min, max;
+       int min, max, dirty = 0;
 
        ea_ibody_quota->blocks = 0;
        ea_ibody_quota->inodes = 0;
@@ -602,24 +603,38 @@ static void check_inode_extra_space(e2fsck_t ctx, struct problem_context *pctx,
                if (!fix_problem(ctx, PR_1_EXTRA_ISIZE, pctx))
                        return;
                if (inode->i_extra_isize < min || inode->i_extra_isize > max)
-                       inode->i_extra_isize = sb->s_want_extra_isize;
+                       inode->i_extra_isize = ctx->want_extra_isize;
                else
                        inode->i_extra_isize = (inode->i_extra_isize + 3) & ~3;
-               e2fsck_write_inode_full(ctx, pctx->ino, pctx->inode,
-                                       EXT2_INODE_SIZE(sb), "pass1");
+               dirty = 1;
+
+               goto out;
        }
 
        /* check if there is no place for an EA header */
        if (inode->i_extra_isize >= max - sizeof(__u32))
                return;
 
-       eamagic = (__u32 *) (((char *) inode) + EXT2_GOOD_OLD_INODE_SIZE +
-                       inode->i_extra_isize);
-       if (*eamagic == EXT2_EXT_ATTR_MAGIC) {
-               /* it seems inode has an extended attribute(s) in body */
-               check_ea_in_inode(ctx, pctx, ea_ibody_quota);
+       eamagic = &IHDR(inode)->h_magic;
+       if (*eamagic != EXT2_EXT_ATTR_MAGIC &&
+           (ctx->flags & E2F_FLAG_EXPAND_EISIZE) &&
+           (inode->i_extra_isize < ctx->want_extra_isize)) {
+               fix_problem(ctx, PR_1_EXPAND_EISIZE, pctx);
+               memset((char *)inode + EXT2_GOOD_OLD_INODE_SIZE, 0,
+                       EXT2_INODE_SIZE(sb) - EXT2_GOOD_OLD_INODE_SIZE);
+               inode->i_extra_isize = ctx->want_extra_isize;
+               dirty = 1;
+               if (inode->i_extra_isize < ctx->min_extra_isize)
+                       ctx->min_extra_isize = inode->i_extra_isize;
        }
 
+       if (*eamagic == EXT2_EXT_ATTR_MAGIC)
+               check_ea_in_inode(ctx, pctx, ea_ibody_quota);
+
+       if (EXT4_XTIME_FUTURE(ctx, sb, inode->i_crtime, ctx->time_fudge))
+               e2fsck_mark_inode_bad(ctx, pctx, PR_1_CRTIME_BAD);
+       else if (EXT4_XTIME_ANCIENT(ctx, sb, inode->i_crtime, ctx->time_fudge))
+               e2fsck_mark_inode_bad(ctx, pctx, PR_1_CRTIME_BAD);
        /*
         * If the inode's extended atime (ctime, crtime, mtime) is stored in
         * the old, invalid format, repair it.
@@ -632,7 +647,7 @@ static void check_inode_extra_space(e2fsck_t ctx, struct problem_context *pctx,
             CHECK_INODE_EXTRA_NEGATIVE_EPOCH(inode, crtime) ||
             CHECK_INODE_EXTRA_NEGATIVE_EPOCH(inode, mtime))) {
 
-               if (!fix_problem(ctx, PR_1_EA_TIME_OUT_OF_RANGE, pctx))
+               if (!fix_problem_bad(ctx, PR_1_EA_TIME_OUT_OF_RANGE, pctx, 2))
                        return;
 
                if (CHECK_INODE_EXTRA_NEGATIVE_EPOCH(inode, atime))
@@ -643,10 +658,13 @@ static void check_inode_extra_space(e2fsck_t ctx, struct problem_context *pctx,
                        inode->i_crtime_extra &= ~EXT4_EPOCH_MASK;
                if (CHECK_INODE_EXTRA_NEGATIVE_EPOCH(inode, mtime))
                        inode->i_mtime_extra &= ~EXT4_EPOCH_MASK;
-               e2fsck_write_inode_full(ctx, pctx->ino, pctx->inode,
-                                       EXT2_INODE_SIZE(sb), "pass1");
+               dirty = 1;
        }
 
+out:
+       if (dirty)
+               e2fsck_write_inode_full(ctx, pctx->ino, pctx->inode,
+                                       EXT2_INODE_SIZE(sb), "pass1");
 }
 
 static _INLINE_ int is_blocks_used(e2fsck_t ctx, blk64_t block,
@@ -908,6 +926,150 @@ static errcode_t recheck_bad_inode_checksum(ext2_filsys fs, ext2_ino_t ino,
        return retval;
 }
 
+int e2fsck_pass1_delete_attr(e2fsck_t ctx, struct ext2_inode_large *inode,
+                            struct problem_context *pctx, int needed_size)
+{
+       struct ext2_ext_attr_header *header;
+       struct ext2_ext_attr_entry *entry_ino, *entry_blk = NULL, *entry;
+       char *start, name[4096], block_buf[4096];
+       int len, index = EXT2_ATTR_INDEX_USER, entry_size, ea_size;
+       int in_inode = 1, error;
+       unsigned int freed_bytes = inode->i_extra_isize;
+
+       entry_ino = &IHDR(inode)->h_first_entry[0];
+       start = (char *)entry_ino;
+
+       if (inode->i_file_acl) {
+               error = ext2fs_read_ext_attr(ctx->fs, inode->i_file_acl,
+                                            block_buf);
+               /* We have already checked this block, shouldn't happen */
+               if (error) {
+                       fix_problem(ctx, PR_1_EXTATTR_READ_ABORT, pctx);
+                       return 0;
+               }
+               header = BHDR(block_buf);
+               if (header->h_magic != EXT2_EXT_ATTR_MAGIC) {
+                       fix_problem(ctx, PR_1_EXTATTR_READ_ABORT, pctx);
+                       return 0;
+               }
+
+               entry_blk = (struct ext2_ext_attr_entry *)(header+1);
+       }
+       entry = entry_ino;
+       len = sizeof(entry->e_name);
+       entry_size = ext2fs_attr_get_next_attr(entry, index, name, len, 1);
+
+       while (freed_bytes < needed_size) {
+               if (entry_size && name[0] != '\0') {
+                       pctx->str = name;
+                       if (fix_problem(ctx, PR_1_EISIZE_DELETE_EA, pctx)) {
+                               ea_size = EXT2_EXT_ATTR_LEN(entry->e_name_len) +
+                                         EXT2_EXT_ATTR_SIZE(entry->e_value_size);
+                               error = ext2fs_attr_set(ctx->fs, pctx->ino,
+                                                       (struct ext2_inode *)inode,
+                                                       index, name, 0, 0, 0);
+                               if (!error)
+                                       freed_bytes += ea_size;
+                       }
+               }
+               len = sizeof(entry->e_name);
+               entry_size = ext2fs_attr_get_next_attr(entry, index,name,len,0);
+               entry = EXT2_EXT_ATTR_NEXT(entry);
+               if (EXT2_EXT_IS_LAST_ENTRY(entry)) {
+                       if (in_inode) {
+                               entry = entry_blk;
+                               len = sizeof(entry->e_name);
+                               entry_size = ext2fs_attr_get_next_attr(entry,
+                                                       index, name, len, 1);
+                               in_inode = 0;
+                       } else {
+                               index += 1;
+                               in_inode = 1;
+                               if (!entry && index < EXT2_ATTR_INDEX_MAX)
+                                       entry = (struct ext2_ext_attr_entry *)start;
+                               else
+                                       return freed_bytes;
+                       }
+               }
+       }
+
+       return freed_bytes;
+}
+
+int e2fsck_pass1_expand_eisize(e2fsck_t ctx, struct ext2_inode_large *inode,
+                              struct problem_context *pctx)
+{
+       int needed_size = 0, retval, ret = EXT2_EXPAND_EISIZE_UNSAFE;
+       static int message;
+
+retry:
+       retval = ext2fs_expand_extra_isize(ctx->fs, pctx->ino, inode,
+                                          ctx->want_extra_isize, &ret,
+                                          &needed_size);
+       if (ret & EXT2_EXPAND_EISIZE_NEW_BLOCK)
+               goto mark_expand_eisize_map;
+       if (!retval) {
+               e2fsck_write_inode_full(ctx, pctx->ino,
+                                       (struct ext2_inode *)inode,
+                                       EXT2_INODE_SIZE(ctx->fs->super),
+                                       "pass1");
+               return 0;
+       }
+
+       if (ret & EXT2_EXPAND_EISIZE_NOSPC) {
+               if (ctx->options & (E2F_OPT_PREEN | E2F_OPT_YES)) {
+                       fix_problem(ctx, PR_1_EA_BLK_NOSPC, pctx);
+                       ctx->flags |= E2F_FLAG_ABORT;
+                       return -1;
+               }
+
+               if (!message) {
+                       pctx->num = ctx->fs->super->s_min_extra_isize;
+                       fix_problem(ctx, PR_1_EXPAND_EISIZE_WARNING, pctx);
+                       message = 1;
+               }
+delete_EA:
+               retval = e2fsck_pass1_delete_attr(ctx, inode, pctx,
+                                                 needed_size);
+               if (retval >= ctx->want_extra_isize)
+                       goto retry;
+
+               needed_size -= retval;
+
+               /*
+                * We loop here until either the user deletes EA(s) or
+                * EXTRA_ISIZE feature is disabled.
+                */
+               if (fix_problem(ctx, PR_1_CLEAR_EXTRA_ISIZE, pctx)) {
+                       ctx->fs->super->s_feature_ro_compat &=
+                                       ~EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE;
+                       ext2fs_mark_super_dirty(ctx->fs);
+               } else {
+                       goto delete_EA;
+               }
+               ctx->fs_unexpanded_inodes++;
+
+               /* No EA was deleted, inode cannot be expanded */
+               return -1;
+       }
+
+mark_expand_eisize_map:
+       if (!ctx->expand_eisize_map) {
+               pctx->errcode = ext2fs_allocate_inode_bitmap(ctx->fs,
+                                        _("expand extrz isize map"),
+                                        &ctx->expand_eisize_map);
+               if (pctx->errcode) {
+                       fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR,
+                                   pctx);
+                       exit(1);
+               }
+       }
+
+       /* Add this inode to the expand_eisize_map */
+       ext2fs_mark_inode_bitmap2(ctx->expand_eisize_map, pctx->ino);
+       return 0;
+}
+
 static void reserve_block_for_root_repair(e2fsck_t ctx)
 {
        blk64_t         blk = 0;
@@ -969,6 +1131,28 @@ err:
        return retval;
 }
 
+int e2fsck_fix_bad_inode(e2fsck_t ctx, struct problem_context *pctx)
+{
+       __u16 badness;
+       int rc = 0;
+
+       if (!ctx->inode_badness)
+               return 0;
+
+       if (ext2fs_icount_fetch(ctx->inode_badness, pctx->ino, &badness))
+               return 0;
+
+       if ((badness & ~BADNESS_BAD_MODE) > ctx->inode_badness_threshold) {
+               __u64 pctx_num_sav = pctx->num;
+
+               pctx->num = badness;
+               rc = fix_problem_bad(ctx, PR_1B_INODE_TOOBAD, pctx, 0);
+               pctx->num = pctx_num_sav;
+       }
+
+       return rc;
+}
+
 static void finish_processing_inode(e2fsck_t ctx, ext2_ino_t ino,
                                    struct problem_context *pctx,
                                    int failed_csum)
@@ -988,7 +1172,7 @@ static void finish_processing_inode(e2fsck_t ctx, ext2_ino_t ino,
 #define FINISH_INODE_LOOP(ctx, ino, pctx, failed_csum) \
        do { \
                finish_processing_inode((ctx), (ino), (pctx), (failed_csum)); \
-               if ((ctx)->flags & E2F_FLAG_ABORT) { \
+               if (e2fsck_should_abort(ctx)) { \
                        e2fsck_pass1_check_unlock(ctx); \
                        return; \
                } \
@@ -1262,7 +1446,7 @@ static void e2fsck_pass1_set_thread_num(e2fsck_t ctx)
 {
        unsigned flexbg_size = 1;
        ext2_filsys fs = ctx->fs;
-       int num_threads = ctx->fs_num_threads;
+       int num_threads = ctx->pfs_num_threads;
        int max_threads;
 
        if (num_threads < 1) {
@@ -1282,6 +1466,8 @@ static void e2fsck_pass1_set_thread_num(e2fsck_t ctx)
        max_threads = fs->group_desc_count / flexbg_size;
        if (max_threads == 0)
                max_threads = 1;
+       if (max_threads > E2FSCK_MAX_THREADS)
+               max_threads = E2FSCK_MAX_THREADS;
 
        if (num_threads > max_threads) {
                fprintf(stderr, "Use max possible thread num: %d instead\n",
@@ -1289,7 +1475,7 @@ static void e2fsck_pass1_set_thread_num(e2fsck_t ctx)
                num_threads = max_threads;
        }
 out:
-       ctx->fs_num_threads = num_threads;
+       ctx->pfs_num_threads = num_threads;
        ctx->fs->fs_num_threads = num_threads;
 }
 #endif
@@ -1317,7 +1503,7 @@ static errcode_t e2fsck_pass1_prepare(e2fsck_t ctx)
 
 #ifdef HAVE_PTHREAD
        /* don't use more than 1/10 of memory for threads checking */
-       readahead_kb = get_memory_size() / (10 * ctx->fs_num_threads);
+       readahead_kb = get_memory_size() / (10 * ctx->pfs_num_threads);
        /* maybe better disable RA if this is too small? */
        if (ctx->readahead_kb > readahead_kb)
                ctx->readahead_kb = readahead_kb;
@@ -1375,7 +1561,7 @@ static errcode_t e2fsck_pass1_prepare(e2fsck_t ctx)
 #ifdef HAVE_PTHREAD
        pthread_rwlock_init(&ctx->fs_fix_rwlock, NULL);
        pthread_rwlock_init(&ctx->fs_block_map_rwlock, NULL);
-       if (ctx->fs_num_threads > 1)
+       if (ctx->pfs_num_threads > 1)
                ctx->fs_need_locking = 1;
 #endif
 
@@ -1474,6 +1660,23 @@ static void e2fsck_pass1_post(e2fsck_t ctx)
 }
 
 
+/*
+ * Lustre FS creates special inodes - precreated objects.
+ * They are zero-sized and have special attributes:
+ * mode |= S_ISUID | S_ISGID;
+ * valid |= LA_ATIME | LA_MTIME | LA_CTIME;
+ * atime = 0;
+ * mtime = 0;
+ * ctime = 0;
+ */
+static int precreated_object(struct ext2_inode *inode)
+{
+       if (((inode->i_mode & (S_ISUID | S_ISGID)) == (S_ISUID | S_ISGID)) &&
+            inode->i_ctime == 0)
+               return 1;
+       return 0;
+}
+
 void e2fsck_pass1_run(e2fsck_t ctx)
 {
        int     i;
@@ -1502,6 +1705,7 @@ void e2fsck_pass1_run(e2fsck_t ctx)
        struct process_inode_block *inodes_to_process;
        int             process_inode_count, check_mmp;
        e2fsck_t        global_ctx = ctx->global_ctx ? ctx->global_ctx : ctx;
+       int             inode_exp = 0;
 
        init_resource_track(&rtrack, ctx->fs->io);
        clear_problem_context(&pctx);
@@ -1650,9 +1854,8 @@ void e2fsck_pass1_run(e2fsck_t ctx)
        if (ctx->global_ctx) {
                if (ctx->options & E2F_OPT_DEBUG &&
                    ctx->options & E2F_OPT_MULTITHREAD)
-                       fprintf(stderr, "thread %d jumping to group %d\n",
-                                       ctx->thread_info.et_thread_index,
-                                       ctx->thread_info.et_group_start);
+                       log_out(ctx, "jumping to group %u\n",
+                               ctx->thread_info.et_group_start);
                pctx.errcode = ext2fs_inode_scan_goto_blockgroup(scan,
                                        ctx->thread_info.et_group_start);
                if (pctx.errcode) {
@@ -2207,18 +2410,21 @@ void e2fsck_pass1_run(e2fsck_t ctx)
                        frag = fsize = 0;
                }
 
+               /* Fixed in pass2, e2fsck_process_bad_inode(). */
                if (inode->i_faddr || frag || fsize ||
                    (!ext2fs_has_feature_largedir(fs->super) &&
-                   (LINUX_S_ISDIR(inode->i_mode) && inode->i_size_high)))
-                       mark_inode_bad(ctx, ino);
+                    LINUX_S_ISDIR(inode->i_mode) && inode->i_size_high))
+                       e2fsck_mark_inode_bad(ctx, &pctx,
+                                             PR_2_DIR_SIZE_HIGH_ZERO);
                if ((fs->super->s_creator_os != EXT2_OS_HURD) &&
                    !ext2fs_has_feature_64bit(fs->super) &&
                    inode->osd2.linux2.l_i_file_acl_high != 0)
-                       mark_inode_bad(ctx, ino);
+                       e2fsck_mark_inode_bad(ctx, &pctx,
+                                             PR_2_I_FILE_ACL_HI_ZERO);
                if ((fs->super->s_creator_os != EXT2_OS_HURD) &&
                    !ext2fs_has_feature_huge_file(fs->super) &&
                    (inode->osd2.linux2.l_i_blocks_hi != 0))
-                       mark_inode_bad(ctx, ino);
+                       e2fsck_mark_inode_bad(ctx, &pctx, PR_2_BLOCKS_HI_ZERO);
                if (inode->i_flags & EXT2_IMAGIC_FL) {
                        if (imagic_fs) {
                                if (!ctx->inode_imagic_map)
@@ -2309,8 +2515,37 @@ void e2fsck_pass1_run(e2fsck_t ctx)
                        check_immutable(ctx, &pctx);
                        check_size(ctx, &pctx);
                        ctx->fs_sockets_count++;
-               } else
-                       mark_inode_bad(ctx, ino);
+               } else {
+                       e2fsck_mark_inode_bad(ctx, &pctx, PR_2_BAD_MODE);
+               }
+
+               /* Future atime/mtime may be valid in rare cases, but are more
+                * likely to indicate corruption.  Don't try to fix timestamps,
+                * but take into consideration whether inode is corrupted.  If
+                * no other problems with the inode, probably it is OK. */
+               if (EXT4_XTIME_FUTURE(ctx, sb, inode->i_atime, ctx->time_fudge))
+                       e2fsck_mark_inode_bad(ctx, &pctx, PR_1_INODE_BAD_TIME);
+               if (EXT4_XTIME_FUTURE(ctx, sb, inode->i_mtime, ctx->time_fudge))
+                       e2fsck_mark_inode_bad(ctx, &pctx, PR_1_INODE_BAD_TIME);
+
+               /* Since ctime cannot be set directly from userspace, consider
+                * very old/future values worse than a bad atime/mtime. Same for
+                * crtime, but it is checked in check_inode_extra_space(). */
+               if (EXT4_XTIME_FUTURE(ctx, sb, inode->i_ctime, ctx->time_fudge))
+                       e2fsck_mark_inode_badder(ctx, &pctx,
+                                                PR_1_INODE_BAD_TIME);
+               else if (!precreated_object(inode) &&
+                        EXT4_XTIME_ANCIENT(ctx, sb, inode->i_ctime,
+                                           ctx->time_fudge))
+                       e2fsck_mark_inode_badder(ctx, &pctx,
+                                                PR_1_INODE_BAD_TIME);
+
+               /* no restart if clearing bad inode before block processing */
+               if (e2fsck_fix_bad_inode(ctx, &pctx)) {
+                       e2fsck_clear_inode(ctx, ino, inode, 0, "pass1");
+                       goto next_unlock;
+               }
+
                if (!(inode->i_flags & EXT4_EXTENTS_FL) &&
                    !(inode->i_flags & EXT4_INLINE_DATA_FL)) {
                        if (inode->i_block[EXT2_IND_BLOCK])
@@ -2341,6 +2576,22 @@ void e2fsck_pass1_run(e2fsck_t ctx)
 
                FINISH_INODE_LOOP(ctx, ino, &pctx, failed_csum);
 
+               if (ctx->flags & E2F_FLAG_EXPAND_EISIZE) {
+                       struct ext2_inode_large *inode_l;
+
+                       inode_l = (struct ext2_inode_large *)inode;
+
+                       if (inode_l->i_extra_isize < ctx->want_extra_isize) {
+                               fix_problem(ctx, PR_1_EXPAND_EISIZE, &pctx);
+                               inode_exp = e2fsck_pass1_expand_eisize(ctx,
+                                                                      inode_l,
+                                                                      &pctx);
+                       }
+                       if ((inode_l->i_extra_isize < ctx->min_extra_isize) &&
+                           inode_exp == 0)
+                               ctx->min_extra_isize = inode_l->i_extra_isize;
+               }
+
                if (e2fsck_should_abort(ctx)) {
                        e2fsck_pass1_check_unlock(ctx);
                        goto endit;
@@ -2355,6 +2606,7 @@ void e2fsck_pass1_run(e2fsck_t ctx)
                                goto endit;
                        }
                }
+       next_unlock:
                e2fsck_pass1_check_unlock(ctx);
        }
        process_inodes(ctx, block_buf, inodes_to_process,
@@ -2832,6 +3084,11 @@ static errcode_t e2fsck_pass1_merge_icounts(e2fsck_t global_ctx, e2fsck_t thread
                return ret;
        ret = e2fsck_pass1_merge_icount(&global_ctx->inode_link_info,
                                        &thread_ctx->inode_link_info);
+       if (ret)
+               return ret;
+
+       ret = e2fsck_pass1_merge_icount(&global_ctx->inode_badness,
+                                       &thread_ctx->inode_badness);
 
        return ret;
 }
@@ -3079,11 +3336,6 @@ static errcode_t e2fsck_pass1_merge_context(e2fsck_t global_ctx,
                return retval;
 
        retval = e2fsck_pass1_merge_bitmap(global_fs,
-                               &thread_ctx->inode_bad_map,
-                               &global_ctx->inode_bad_map);
-       if (retval)
-               return retval;
-       retval = e2fsck_pass1_merge_bitmap(global_fs,
                                        &thread_ctx->inode_dir_map,
                                        &global_ctx->inode_dir_map);
        if (retval)
@@ -3163,11 +3415,11 @@ static int e2fsck_pass1_thread_join(e2fsck_t global_ctx, e2fsck_t thread_ctx)
 static int e2fsck_pass1_threads_join(struct e2fsck_thread_info *infos,
                                     e2fsck_t global_ctx)
 {
-       errcode_t                        rc;
-       errcode_t                        ret = 0;
-       int                              i;
-       struct e2fsck_thread_info       *pinfo;
-       int                              num_threads = global_ctx->fs_num_threads;
+       errcode_t rc;
+       errcode_t ret = 0;
+       struct e2fsck_thread_info *pinfo;
+       int num_threads = global_ctx->pfs_num_threads;
+       int i;
 
        /* merge invalid bitmaps will recalculate it */
        global_ctx->invalid_bitmaps = 0;
@@ -3233,7 +3485,7 @@ static void *e2fsck_pass1_thread(void *arg)
 out:
        if (thread_ctx->options & E2F_OPT_MULTITHREAD)
                log_out(thread_ctx,
-                       _("Scanned group range [%lu, %lu), inodes %lu\n"),
+                       _("Scanned group range [%u, %u), inodes %u\n"),
                        thread_ctx->thread_info.et_group_start,
                        thread_ctx->thread_info.et_group_end,
                        thread_ctx->thread_info.et_inode_number);
@@ -3259,7 +3511,7 @@ static int e2fsck_pass1_threads_start(struct e2fsck_thread_info **pinfo,
        int                              i;
        e2fsck_t                         thread_ctx;
        dgrp_t                           average_group;
-       int                              num_threads = global_ctx->fs_num_threads;
+       int num_threads = global_ctx->pfs_num_threads;
 #ifdef DEBUG_THREADS
        struct e2fsck_thread_debug       thread_debug =
                {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0};
@@ -3363,8 +3615,7 @@ void e2fsck_pass1(e2fsck_t ctx)
        if (retval)
                return;
 #ifdef HAVE_PTHREAD
-       if (ctx->fs_num_threads > 1 ||
-           ctx->options & E2F_OPT_MULTITHREAD) {
+       if (ctx->pfs_num_threads > 1 || ctx->options & E2F_OPT_MULTITHREAD) {
                need_single = 0;
                e2fsck_pass1_multithread(ctx);
        }
@@ -3492,27 +3743,44 @@ static EXT2_QSORT_TYPE process_inode_cmp(const void *a, const void *b)
 }
 
 /*
- * Mark an inode as being bad in some what
+ * Mark an inode as being bad and increment its badness counter.
  */
-static void mark_inode_bad(e2fsck_t ctx, ino_t ino)
+void e2fsck_mark_inode_bad_loc(e2fsck_t ctx, struct problem_context *pctx,
+                              __u32 code, int badness, const char *func,
+                              const int line)
 {
-       struct          problem_context pctx;
+       __u16 badness_before, badness_after;
+       __u64 pctx_num_sav = pctx->num;
 
-       if (!ctx->inode_bad_map) {
-               clear_problem_context(&pctx);
+       if (!ctx->inode_badness_threshold)      /* badness is disabled */
+               return;
 
-               pctx.errcode = e2fsck_allocate_inode_bitmap(ctx->fs,
-                               _("bad inode map"), EXT2FS_BMAP64_RBTREE,
-                               "inode_bad_map", &ctx->inode_bad_map);
-               if (pctx.errcode) {
-                       pctx.num = 3;
-                       fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx);
-                       /* Should never get here */
+       if (!ctx->inode_badness) {
+               errcode_t retval;
+
+               retval = ext2fs_create_icount2(ctx->fs, 0, 0, NULL,
+                                              &ctx->inode_badness);
+               if (retval) {
+                       pctx->errcode = retval;
+                       fix_problem(ctx, PR_1_ALLOCATE_ICOUNT, pctx);
                        ctx->flags |= E2F_FLAG_ABORT;
                        return;
                }
        }
-       ext2fs_mark_inode_bitmap2(ctx->inode_bad_map, ino);
+       ext2fs_icount_fetch(ctx->inode_badness, pctx->ino, &badness_before);
+       if (badness + badness_before > BADNESS_MAX)
+               badness_after = BADNESS_MAX;
+       else if (badness < 0 && badness_before < -badness)
+               badness_after = 0;
+       else
+               badness_after = badness_before + badness;
+       ext2fs_icount_store(ctx->inode_badness, pctx->ino, badness_after);
+
+       if (ctx->options & E2F_OPT_DEBUG)
+               log_out(ctx,
+                       "%s:%d: increase inode %lu badness %u to %u for %x\n",
+                       func, line, (unsigned long)pctx->ino, badness_before,
+                       badness_after, code);
 }
 
 static void add_casefolded_dir(e2fsck_t ctx, ino_t ino)
@@ -3656,11 +3924,17 @@ static void adjust_extattr_refcount(e2fsck_t ctx, ext2_refcount_t refcount,
                pctx.blk = blk;
                pctx.errcode = ext2fs_read_ext_attr3(fs, blk, block_buf,
                                                     pctx.ino);
+               /* We already checked this block, shouldn't happen */
                if (pctx.errcode) {
                        fix_problem(ctx, PR_1_EXTATTR_READ_ABORT, &pctx);
                        return;
                }
-               header = (struct ext2_ext_attr_header *) block_buf;
+               header = BHDR(block_buf);
+               if (header->h_magic != EXT2_EXT_ATTR_MAGIC) {
+                       fix_problem(ctx, PR_1_EXTATTR_READ_ABORT, &pctx);
+                       return;
+               }
+
                pctx.blkcount = header->h_refcount;
                should_be = header->h_refcount + adjust_sign * (int)count;
                pctx.num = should_be;
@@ -3713,7 +3987,8 @@ static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx,
        if (!ext2fs_has_feature_xattr(fs->super) ||
            (blk < fs->super->s_first_data_block) ||
            (blk >= ext2fs_blocks_count(fs->super))) {
-               mark_inode_bad(ctx, ino);
+               /* Fixed in pass2, e2fsck_process_bad_inode(). */
+               e2fsck_mark_inode_bad(ctx, pctx, PR_2_FILE_ACL_ZERO);
                return 0;
        }
 
@@ -3806,7 +4081,7 @@ static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx,
                pctx->errcode = 0;
                goto clear_extattr;
        }
-       header = (struct ext2_ext_attr_header *) block_buf;
+       header = BHDR(block_buf);
        pctx->blk = ext2fs_file_acl_block(fs, inode);
        if (((ctx->ext_attr_ver == 1) &&
             (header->h_magic != EXT2_EXT_ATTR_MAGIC_v1)) ||
@@ -3993,8 +4268,10 @@ static int handle_htree(e2fsck_t ctx, struct problem_context *pctx,
        }
 
        retval = io_channel_read_blk64(fs->io, blk, 1, block_buf);
-       if (retval && fix_problem(ctx, PR_1_HTREE_BADROOT, pctx))
-               return 1;
+       if (retval) {
+               if (fix_problem(ctx, PR_1_HTREE_BADROOT, pctx))
+                       return 1;
+       }
 
        /* XXX should check that beginning matches a directory */
        root = (struct ext2_dx_root_info *) (block_buf + 24);
@@ -4076,8 +4353,8 @@ void e2fsck_clear_inode(e2fsck_t ctx, ext2_ino_t ino,
        ext2fs_unmark_inode_bitmap2(ctx->inode_used_map, ino);
        if (ctx->inode_reg_map)
                ext2fs_unmark_inode_bitmap2(ctx->inode_reg_map, ino);
-       if (ctx->inode_bad_map)
-               ext2fs_unmark_inode_bitmap2(ctx->inode_bad_map, ino);
+       if (ctx->inode_badness)
+               ext2fs_icount_store(ctx->inode_badness, ino, 0);
 
        /*
         * If the inode was partially accounted for before processing
@@ -4249,7 +4526,9 @@ static void scan_extent_node(e2fsck_t ctx, struct problem_context *pctx,
 #endif
                if (try_repairs && problem) {
 report_problem:
-                       if (fix_problem(ctx, problem, pctx)) {
+                       /* Record badness only if extent is within inode */
+                       if (fix_problem_bad(ctx, problem, pctx,
+                                           info.curr_level == 0)) {
                                if (ctx->invalid_bitmaps) {
                                        /*
                                         * If fsck knows the bitmaps are bad,
@@ -4318,9 +4597,9 @@ report_problem:
                                                      extent.e_pblk)) {
                                next_try_repairs = 0;
                                pctx->blk = blk;
-                               fix_problem(ctx,
+                               fix_problem_bad(ctx,
                                            PR_1_CRITICAL_METADATA_COLLISION,
-                                           pctx);
+                                           pctx, 2);
                                if ((ctx->options & E2F_OPT_NO) == 0)
                                        ctx->flags |= E2F_FLAG_RESTART_LATER;
                        }
@@ -4797,6 +5076,10 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx,
 
        if (!pb.num_blocks && pb.is_dir &&
            !(inode->i_flags & EXT4_INLINE_DATA_FL)) {
+               /*
+                * The mode might be in-correct. Increasing the badness by
+                * small amount won't hurt much.
+                */
                if (fix_problem(ctx, PR_1_ZERO_LENGTH_DIR, pctx)) {
                        e2fsck_clear_inode(ctx, ino, inode, 0, "check_blocks");
                        ctx->fs_directory_count--;
@@ -4929,7 +5212,11 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx,
                e2fsck_rehash_dir_later(ctx, ino);
 
 out:
-       if (dirty_inode)
+       /* need restart if clearing bad inode after block processing */
+       if (e2fsck_fix_bad_inode(ctx, pctx))
+               e2fsck_clear_inode(ctx, ino, inode, E2F_FLAG_RESTART,
+                                  "check_blocks_bad");
+       else if (dirty_inode)
                e2fsck_write_inode(ctx, ino, inode, "check_blocks");
 }
 
@@ -5081,7 +5368,7 @@ static int process_block(ext2_filsys fs,
            blk < ctx->fs->super->s_blocks_count &&
            ext2fs_test_block_bitmap2(ctx->block_metadata_map, blk)) {
                pctx->blk = blk;
-               fix_problem(ctx, PR_1_CRITICAL_METADATA_COLLISION, pctx);
+               fix_problem_bad(ctx, PR_1_CRITICAL_METADATA_COLLISION, pctx, 2);
                if ((ctx->options & E2F_OPT_NO) == 0)
                        ctx->flags |= E2F_FLAG_RESTART_LATER;
        }