Whamcloud - gitweb
LU-11545 debugfs: allow <inode> for ncheck
[tools/e2fsprogs.git] / e2fsck / pass2.c
index d3f2101..69a18da 100644 (file)
  * Pass 2 relies on the following information from previous passes:
  *     - The directory information collected in pass 1.
  *     - The inode_used_map bitmap
- *     - The inode_bad_map bitmap
+ *     - The inode_badness bitmap
  *     - The inode_dir_map bitmap
  *     - The encrypted_file_info
+ *     - The inode_casefold_map bitmap
  *
  * Pass 2 frees the following data structures
- *     - The inode_bad_map bitmap
  *     - The inode_reg_map bitmap
  *     - The encrypted_file_info
+ *     - The inode_casefold_map bitmap
+ *     - The inode_badness bitmap
  */
 
 #define _GNU_SOURCE 1 /* get strnlen() */
@@ -73,8 +75,8 @@ static int allocate_dir_block(e2fsck_t ctx,
                              struct ext2_db_entry2 *dir_blocks_info,
                              char *buf, struct problem_context *pctx);
 static void clear_htree(e2fsck_t ctx, ext2_ino_t ino);
-static int htree_depth(struct dx_dir_info *dx_dir,
-                      struct dx_dirblock_info *dx_db);
+static short htree_depth(struct dx_dir_info *dx_dir,
+                        struct dx_dirblock_info *dx_db);
 static EXT2_QSORT_TYPE special_dir_block_cmp(const void *a, const void *b);
 
 struct check_dir_struct {
@@ -90,7 +92,7 @@ struct check_dir_struct {
 static void update_parents(struct dx_dir_info *dx_dir, int type)
 {
        struct dx_dirblock_info *dx_db, *dx_parent, *dx_previous;
-       int b;
+       blk_t b;
 
        for (b = 0, dx_db = dx_dir->dx_block;
             b < dx_dir->numblocks;
@@ -132,8 +134,9 @@ void e2fsck_pass2(e2fsck_t ctx)
        struct check_dir_struct cd;
        struct dx_dir_info      *dx_dir;
        struct dx_dirblock_info *dx_db;
-       int                     b;
-       int                     i, depth;
+       blk_t                   b;
+       ext2_ino_t              i;
+       short                   depth;
        problem_t               code;
        int                     bad_dir;
        int (*check_dir_func)(ext2_filsys fs,
@@ -278,15 +281,23 @@ void e2fsck_pass2(e2fsck_t ctx)
        ext2fs_free_mem(&buf);
        ext2fs_free_dblist(fs->dblist);
 
-       if (ctx->inode_bad_map) {
-               ext2fs_free_inode_bitmap(ctx->inode_bad_map);
-               ctx->inode_bad_map = 0;
-       }
        if (ctx->inode_reg_map) {
                ext2fs_free_inode_bitmap(ctx->inode_reg_map);
                ctx->inode_reg_map = 0;
        }
+       if (ctx->inode_casefold_map) {
+               ext2fs_free_inode_bitmap(ctx->inode_casefold_map);
+               ctx->inode_casefold_map = 0;
+       }
        destroy_encrypted_file_info(ctx);
+       if (ctx->casefolded_dirs) {
+               ext2fs_u32_list_free(ctx->casefolded_dirs);
+               ctx->casefolded_dirs = 0;
+       }
+       if (ctx->inode_badness) {
+               ext2fs_free_icount(ctx->inode_badness);
+               ctx->inode_badness = 0;
+       }
 
        clear_problem_context(&pctx);
        if (ctx->large_files) {
@@ -309,10 +320,10 @@ cleanup:
 }
 
 #define MAX_DEPTH 32000
-static int htree_depth(struct dx_dir_info *dx_dir,
-                      struct dx_dirblock_info *dx_db)
+static short htree_depth(struct dx_dir_info *dx_dir,
+                        struct dx_dirblock_info *dx_db)
 {
-       int     depth = 0;
+       short depth = 0;
 
        while (dx_db->type != DX_DIRBLOCK_ROOT && depth < MAX_DEPTH) {
                dx_db = &dx_dir->dx_block[dx_db->parent];
@@ -321,7 +332,7 @@ static int htree_depth(struct dx_dir_info *dx_dir,
        return depth;
 }
 
-static int dict_de_cmp(const void *a, const void *b)
+static int dict_de_cmp(const void *cmp_ctx, const void *a, const void *b)
 {
        const struct ext2_dir_entry *de_a, *de_b;
        int     a_len, b_len;
@@ -337,6 +348,21 @@ static int dict_de_cmp(const void *a, const void *b)
        return memcmp(de_a->name, de_b->name, a_len);
 }
 
+static int dict_de_cf_cmp(const void *cmp_ctx, const void *a, const void *b)
+{
+       const struct ext2fs_nls_table *tbl = cmp_ctx;
+       const struct ext2_dir_entry *de_a, *de_b;
+       int     a_len, b_len;
+
+       de_a = (const struct ext2_dir_entry *) a;
+       a_len = ext2fs_dirent_name_len(de_a);
+       de_b = (const struct ext2_dir_entry *) b;
+       b_len = ext2fs_dirent_name_len(de_b);
+
+       return ext2fs_casefold_cmp(tbl, (unsigned char *) de_a->name, a_len,
+                                  (unsigned char *) de_b->name, b_len);
+}
+
 /*
  * This is special sort function that makes sure that directory blocks
  * with a dirblock of zero are sorted to the beginning of the list.
@@ -365,13 +391,88 @@ static EXT2_QSORT_TYPE special_dir_block_cmp(const void *a, const void *b)
        return (int) (db_a->blockcnt - db_b->blockcnt);
 }
 
+void ext2_fix_dirent_dirdata(struct ext2_dir_entry *de)
+{
+       __u16 file_type = de->name_len & (EXT2_FT_MASK << 8);
+       __u8 de_flags = (de->name_len >> 8) & ~EXT2_FT_MASK;
+       __u8 name_len = de->name_len & EXT2_NAME_LEN;
+       __u8 new_flag = 0;
+       int i;
+
+       for (i = 0; i < 4; i++) {
+               __u8 flags = new_flag | (1 << i) << 4;
+
+               /* new_flag is accumulating flags that are set in de_flags
+                * and still fit inside rec_len. ext2_get_dirent_dirdata_size()
+                * returns the size of all the dirdata entries in flags, and
+                * chops off any that are beyond rec_len.
+                */
+               if ((de_flags & flags) == flags) {
+                       int dirdatalen = ext2_get_dirdata_field_size(de,
+                                                                     flags);
+                       int rlen = EXT2_DIR_NAME_LEN(name_len + dirdatalen);
+
+                       if (rlen > de->rec_len)
+                               break;
+
+                       new_flag |= flags;
+               }
+       }
+
+       de->name_len = name_len | file_type | (new_flag << 8);
+}
+
+/*
+ * check for dirent data in ext3 dirent.
+ * return 0 if dirent data is ok.
+ * return 1 if dirent data does not exist.
+ * return 2 if dirent was modified due to error.
+ */
+int e2fsck_check_dirent_data(e2fsck_t ctx, struct ext2_dir_entry *de,
+                            unsigned int offset, struct problem_context *pctx)
+{
+       if (!(ctx->fs->super->s_feature_incompat &
+                       EXT4_FEATURE_INCOMPAT_DIRDATA)) {
+               if ((de->name_len >> 8) & ~EXT2_FT_MASK) {
+                       /* clear dirent extra data flags. */
+                       if (fix_problem(ctx, PR_2_CLEAR_DIRDATA, pctx)) {
+                               de->name_len &= (EXT2_FT_MASK << 8) |
+                                               EXT2_NAME_LEN;
+                               return 2;
+                       }
+               }
+               return 1;
+       }
+       if ((de->name_len >> 8) & ~EXT2_FT_MASK) {
+               if (de->rec_len >= EXT2_DIR_REC_LEN(de) ||
+                   de->rec_len + offset == EXT2_BLOCK_SIZE(ctx->fs->super)) {
+                       if (ext2_get_dirdata_field_size(de,
+                                                        EXT2_DIRENT_LUFID) %
+                           EXT2_DIRENT_LUFID_SIZE == 1 /*size*/ + 1 /*NULL*/)
+                               return 0;
+               }
+               /* just clear dirent data flags for now, we should fix FID data
+                * in lustre specific pass.
+                */
+               if (fix_problem(ctx, PR_2_CLEAR_DIRDATA, pctx)) {
+                       ext2_fix_dirent_dirdata(de);
+                       if (ext2_get_dirdata_field_size(de,
+                                                        EXT2_DIRENT_LUFID) !=
+                           EXT2_DIRENT_LUFID_SIZE)
+                               de->name_len &= ~(EXT2_DIRENT_LUFID << 8);
+
+                       return 2;
+               }
+       }
+       return 1;
+}
 
 /*
  * Make sure the first entry in the directory is '.', and that the
  * directory entry is sane.
  */
 static int check_dot(e2fsck_t ctx,
-                    struct ext2_dir_entry *dirent,
+                    struct ext2_dir_entry *dirent, unsigned int offset,
                     ext2_ino_t ino, struct problem_context *pctx)
 {
        struct ext2_dir_entry *nextdir;
@@ -379,6 +480,8 @@ static int check_dot(e2fsck_t ctx,
        int             status = 0;
        int             created = 0;
        problem_t       problem = 0;
+       int             dir_data_error;
+       int             ftype = EXT2_FT_DIR;
 
        if (!dirent->inode)
                problem = PR_2_MISSING_DOT;
@@ -388,14 +491,18 @@ static int check_dot(e2fsck_t ctx,
        else if (dirent->name[1] != '\0')
                problem = PR_2_DOT_NULL_TERM;
 
+       dir_data_error = e2fsck_check_dirent_data(ctx, dirent, offset, pctx);
+
        (void) ext2fs_get_rec_len(ctx->fs, dirent, &rec_len);
        if (problem) {
+               if (!ext2fs_has_feature_filetype(ctx->fs->super))
+                       ftype = EXT2_FT_UNKNOWN;
                if (fix_problem(ctx, problem, pctx)) {
                        if (rec_len < 12)
                                rec_len = dirent->rec_len = 12;
                        dirent->inode = ino;
                        ext2fs_dirent_set_name_len(dirent, 1);
-                       ext2fs_dirent_set_file_type(dirent, EXT2_FT_UNKNOWN);
+                       ext2fs_dirent_set_file_type(dirent, ftype);
                        dirent->name[0] = '.';
                        dirent->name[1] = '\0';
                        status = 1;
@@ -410,18 +517,24 @@ static int check_dot(e2fsck_t ctx,
        }
        if (rec_len > 12) {
                new_len = rec_len - 12;
-               if (new_len > 12) {
+               if (new_len > 12 && dir_data_error) {
                        if (created ||
                            fix_problem(ctx, PR_2_SPLIT_DOT, pctx)) {
                                nextdir = (struct ext2_dir_entry *)
                                        ((char *) dirent + 12);
                                dirent->rec_len = 12;
-                               (void) ext2fs_set_rec_len(ctx->fs, new_len,
-                                                         nextdir);
-                               nextdir->inode = 0;
-                               ext2fs_dirent_set_name_len(nextdir, 0);
-                               ext2fs_dirent_set_file_type(nextdir,
-                                                           EXT2_FT_UNKNOWN);
+                               /* if the next entry looks like "..", leave it
+                                * and let check_dotdot() verify the dirent,
+                                * otherwise zap the following entry. */
+                               if (strncmp(nextdir->name, "..", 3) != 0) {
+                                       (void)ext2fs_set_rec_len(ctx->fs,
+                                                                new_len,
+                                                                nextdir);
+                                       nextdir->inode = 0;
+                                       ext2fs_dirent_set_name_len(nextdir, 0);
+                                       ext2fs_dirent_set_file_type(nextdir,
+                                                                   ftype);
+                               }
                                status = 1;
                        }
                }
@@ -435,11 +548,13 @@ static int check_dot(e2fsck_t ctx,
  * here; this gets done in pass 3.
  */
 static int check_dotdot(e2fsck_t ctx,
-                       struct ext2_dir_entry *dirent,
+                       struct ext2_dir_entry *dirent, unsigned int offset,
                        ext2_ino_t ino, struct problem_context *pctx)
 {
        problem_t       problem = 0;
        unsigned int    rec_len;
+       int             dir_data_error;
+       int             ftype = EXT2_FT_DIR;
 
        if (!dirent->inode)
                problem = PR_2_MISSING_DOT_DOT;
@@ -450,8 +565,12 @@ static int check_dotdot(e2fsck_t ctx,
        else if (dirent->name[2] != '\0')
                problem = PR_2_DOT_DOT_NULL_TERM;
 
+       dir_data_error = e2fsck_check_dirent_data(ctx, dirent, offset, pctx);
+
        (void) ext2fs_get_rec_len(ctx->fs, dirent, &rec_len);
        if (problem) {
+               if (!ext2fs_has_feature_filetype(ctx->fs->super))
+                       ftype = EXT2_FT_UNKNOWN;
                if (fix_problem(ctx, problem, pctx)) {
                        if (rec_len < 12)
                                dirent->rec_len = 12;
@@ -462,7 +581,7 @@ static int check_dotdot(e2fsck_t ctx,
                         */
                        dirent->inode = EXT2_ROOT_INO;
                        ext2fs_dirent_set_name_len(dirent, 2);
-                       ext2fs_dirent_set_file_type(dirent, EXT2_FT_UNKNOWN);
+                       ext2fs_dirent_set_file_type(dirent, ftype);
                        dirent->name[0] = '.';
                        dirent->name[1] = '.';
                        dirent->name[2] = '\0';
@@ -514,6 +633,30 @@ static int encrypted_check_name(e2fsck_t ctx,
        return 0;
 }
 
+static int encoded_check_name(e2fsck_t ctx,
+                             struct ext2_dir_entry *dirent,
+                             struct problem_context *pctx)
+{
+       const struct ext2fs_nls_table *tbl = ctx->fs->encoding;
+       int ret;
+       int len = ext2fs_dirent_name_len(dirent);
+       char *pos, *end;
+
+       ret = ext2fs_check_encoded_name(tbl, dirent->name, len, &pos);
+       if (ret < 0) {
+               fatal_error(ctx, _("NLS is broken."));
+       } else if(ret > 0) {
+               ret = fix_problem(ctx, PR_2_BAD_ENCODED_NAME, pctx);
+               if (ret) {
+                       end = &dirent->name[len];
+                       for (; *pos && pos != end; pos++)
+                               *pos = '.';
+               }
+       }
+
+       return (ret || check_name(ctx, dirent, pctx));
+}
+
 /*
  * Check the directory filetype (if present)
  */
@@ -524,7 +667,14 @@ static _INLINE_ int check_filetype(e2fsck_t ctx,
 {
        int     filetype = ext2fs_dirent_file_type(dirent);
        int     should_be = EXT2_FT_UNKNOWN;
+       __u16   badness = 0;
        struct ext2_inode       inode;
+       __u8    dirdata = 0;
+
+       if (ext2fs_has_feature_dirdata(ctx->fs->super)) {
+               dirdata = filetype & ~EXT2_FT_MASK;
+               filetype = filetype & EXT2_FT_MASK;
+       }
 
        if (!ext2fs_has_feature_filetype(ctx->fs->super)) {
                if (filetype == 0 ||
@@ -534,16 +684,18 @@ static _INLINE_ int check_filetype(e2fsck_t ctx,
                return 1;
        }
 
+       if (ctx->inode_badness)
+               ext2fs_icount_fetch(ctx->inode_badness, dirent->inode,
+                                   &badness);
+
        if (ext2fs_test_inode_bitmap2(ctx->inode_dir_map, dirent->inode)) {
                should_be = EXT2_FT_DIR;
        } else if (ext2fs_test_inode_bitmap2(ctx->inode_reg_map,
                                            dirent->inode)) {
                should_be = EXT2_FT_REG_FILE;
-       } else if (ctx->inode_bad_map &&
-                  ext2fs_test_inode_bitmap2(ctx->inode_bad_map,
-                                           dirent->inode))
+       } else if (badness & BADNESS_BAD_MODE) {
                should_be = 0;
-       else {
+       else {
                e2fsck_read_inode(ctx, dirent->inode, &inode,
                                  "check_filetype");
                should_be = ext2_file_type(inode.i_mode);
@@ -555,8 +707,7 @@ static _INLINE_ int check_filetype(e2fsck_t ctx,
        if (fix_problem(ctx, filetype ? PR_2_BAD_FILETYPE : PR_2_SET_FILETYPE,
                        pctx) == 0)
                return 0;
-
-       ext2fs_dirent_set_file_type(dirent, should_be);
+       ext2fs_dirent_set_file_type(dirent, should_be | dirdata);
        return 1;
 }
 
@@ -566,8 +717,8 @@ static void parse_int_node(ext2_filsys fs,
                           struct dx_dir_info   *dx_dir,
                           char *block_buf, int failed_csum)
 {
-       struct          ext2_dx_root_info  *root;
-       struct          ext2_dx_entry *ent;
+       struct          ext2_dx_root_info  *root;
+       struct          ext2_dx_entry *ent;
        struct          ext2_dx_countlimit *limit;
        struct dx_dirblock_info *dx_db;
        int             i, expect_limit, count;
@@ -578,18 +729,18 @@ static void parse_int_node(ext2_filsys fs,
        int             csum_size = 0;
 
        if (db->blockcnt == 0) {
-               root = (struct ext2_dx_root_info *) (block_buf + 24);
+               root = get_ext2_dx_root_info(fs, block_buf);
 
 #ifdef DX_DEBUG
                printf("Root node dump:\n");
                printf("\t Reserved zero: %u\n", root->reserved_zero);
-               printf("\t Hash Version: %d\n", root->hash_version);
-               printf("\t Info length: %d\n", root->info_length);
-               printf("\t Indirect levels: %d\n", root->indirect_levels);
-               printf("\t Flags: %d\n", root->unused_flags);
+               printf("\t Hash Version: %u\n", root->hash_version);
+               printf("\t Info length: %u\n", root->info_length);
+               printf("\t Indirect levels: %u\n", root->indirect_levels);
+               printf("\t Flags: %x\n", root->unused_flags);
 #endif
-
-               ent = (struct ext2_dx_entry *) (block_buf + 24 + root->info_length);
+               ent = (struct ext2_dx_entry *)((char *)root +
+                                              root->info_length);
 
                if (failed_csum &&
                    (e2fsck_dir_will_be_rehashed(cd->ctx, cd->pctx.ino) ||
@@ -597,7 +748,7 @@ static void parse_int_node(ext2_filsys fs,
                                &cd->pctx)))
                        goto clear_and_exit;
        } else {
-               ent = (struct ext2_dx_entry *) (block_buf+8);
+               ent = (struct ext2_dx_entry *)(block_buf + 8);
 
                if (failed_csum &&
                    (e2fsck_dir_will_be_rehashed(cd->ctx, cd->pctx.ino) ||
@@ -605,8 +756,7 @@ static void parse_int_node(ext2_filsys fs,
                                &cd->pctx)))
                        goto clear_and_exit;
        }
-
-       limit = (struct ext2_dx_countlimit *) ent;
+       limit = (struct ext2_dx_countlimit *)ent;
 
 #ifdef DX_DEBUG
        printf("Number of entries (count): %d\n",
@@ -642,7 +792,7 @@ static void parse_int_node(ext2_filsys fs,
 #endif
                blk = ext2fs_le32_to_cpu(ent[i].block) & EXT4_DX_BLOCK_MASK;
                /* Check to make sure the block is valid */
-               if (blk >= (blk_t) dx_dir->numblocks) {
+               if (blk >= dx_dir->numblocks) {
                        cd->pctx.blk = blk;
                        if (fix_problem(cd->ctx, PR_2_HTREE_BADBLK,
                                        &cd->pctx))
@@ -702,7 +852,8 @@ static void salvage_directory(ext2_filsys fs,
                              struct ext2_dir_entry *dirent,
                              struct ext2_dir_entry *prev,
                              unsigned int *offset,
-                             unsigned int block_len)
+                             unsigned int block_len,
+                             int hash_in_dirent)
 {
        char    *cp = (char *) dirent;
        int left;
@@ -727,7 +878,8 @@ static void salvage_directory(ext2_filsys fs,
         * Special case of directory entry of size 8: copy what's left
         * of the directory block up to cover up the invalid hole.
         */
-       if ((left >= 12) && (rec_len == EXT2_DIR_ENTRY_HEADER_LEN)) {
+       if ((left >= ext2fs_dir_rec_len(1, hash_in_dirent)) &&
+            (rec_len == EXT2_DIR_ENTRY_HEADER_LEN)) {
                memmove(cp, cp+EXT2_DIR_ENTRY_HEADER_LEN, left);
                memset(cp + left, 0, EXT2_DIR_ENTRY_HEADER_LEN);
                return;
@@ -739,7 +891,7 @@ static void salvage_directory(ext2_filsys fs,
         */
        if ((left < 0) &&
            ((int) rec_len + left > EXT2_DIR_ENTRY_HEADER_LEN) &&
-           ((int) name_len + EXT2_DIR_ENTRY_HEADER_LEN <= (int) rec_len + left) &&
+           ((int) ext2fs_dir_rec_len(name_len, hash_in_dirent) <= (int) rec_len + left) &&
            dirent->inode <= fs->super->s_inodes_count &&
            strnlen(dirent->name, name_len) == name_len) {
                (void) ext2fs_set_rec_len(fs, (int) rec_len + left, dirent);
@@ -778,7 +930,6 @@ static void salvage_directory(ext2_filsys fs,
        }
 }
 
-#define NEXT_DIRENT(d) ((void *)((char *)(d) + (d)->rec_len))
 static errcode_t insert_dirent_tail(ext2_filsys fs, void *dirbuf)
 {
        struct ext2_dir_entry *d;
@@ -788,11 +939,11 @@ static errcode_t insert_dirent_tail(ext2_filsys fs, void *dirbuf)
        d = dirbuf;
        top = EXT2_DIRENT_TAIL(dirbuf, fs->blocksize);
 
-       while (d->rec_len && !(d->rec_len & 0x3) && NEXT_DIRENT(d) <= top)
-               d = NEXT_DIRENT(d);
+       while (d->rec_len && !(d->rec_len & 0x3) && EXT2_NEXT_DIRENT(d) <= top)
+               d = EXT2_NEXT_DIRENT(d);
 
        if (d != top) {
-               unsigned int min_size = EXT2_DIR_REC_LEN(
+               unsigned int min_size = EXT2_DIR_NAME_LEN(
                                ext2fs_dirent_name_len(dirbuf));
                if (min_size > (char *)top - (char *)d)
                        return EXT2_ET_DIR_NO_SPACE_FOR_CSUM;
@@ -807,7 +958,6 @@ static errcode_t insert_dirent_tail(ext2_filsys fs, void *dirbuf)
 
        return 0;
 }
-#undef NEXT_DIRENT
 
 static errcode_t fix_inline_dir_size(e2fsck_t ctx, ext2_ino_t ino,
                                     size_t *inline_data_size,
@@ -826,7 +976,7 @@ static errcode_t fix_inline_dir_size(e2fsck_t ctx, ext2_ino_t ino,
         */
        if (old_size > EXT4_MIN_INLINE_DATA_SIZE &&
            old_size < EXT4_MIN_INLINE_DATA_SIZE +
-                      EXT2_DIR_REC_LEN(1)) {
+                      EXT2_DIR_NAME_LEN(1)) {
                old_size = EXT4_MIN_INLINE_DATA_SIZE;
                new_size = old_size;
        } else
@@ -994,14 +1144,24 @@ static int check_dir_block(ext2_filsys fs,
        size_t  inline_data_size = 0;
        int     filetype = 0;
        __u32   dir_encpolicy_id = NO_ENCRYPTION_POLICY;
+       int     hash_in_dirent = 0;
+       int     casefolded = 0;
        size_t  max_block_size;
        int     hash_flags = 0;
        static char *eop_read_dirblock = NULL;
+       int cf_dir = 0;
 
        cd = (struct check_dir_struct *) priv_data;
        ibuf = buf = cd->buf;
        ctx = cd->ctx;
 
+       /* We only want filename encoding verification on strict
+        * mode or if explicitly requested by user. */
+       if (ext2fs_test_inode_bitmap2(ctx->inode_casefold_map, ino) &&
+           ((ctx->fs->super->s_encoding_flags & EXT4_ENC_STRICT_MODE_FL) ||
+            (ctx->options & E2F_OPT_CHECK_ENCODING)))
+               cf_dir = 1;
+
        if (ctx->flags & E2F_FLAG_RUN_RETURN)
                return DIRENT_ABORT;
 
@@ -1103,7 +1263,7 @@ inline_read_fail:
        if (((inline_data_size & 3) ||
             (inline_data_size > EXT4_MIN_INLINE_DATA_SIZE &&
              inline_data_size < EXT4_MIN_INLINE_DATA_SIZE +
-                                EXT2_DIR_REC_LEN(1))) &&
+                                EXT2_DIR_NAME_LEN(1))) &&
            fix_problem(ctx, PR_2_BAD_INLINE_DIR_SIZE, &pctx)) {
                errcode_t err = fix_inline_dir_size(ctx, ino,
                                                    &inline_data_size, &pctx,
@@ -1153,9 +1313,12 @@ inline_read_fail:
                (void) ext2fs_get_rec_len(fs, dirent, &rec_len);
                limit = (struct ext2_dx_countlimit *) (buf+8);
                if (db->blockcnt == 0) {
-                       root = (struct ext2_dx_root_info *) (buf + 24);
+                       root = get_ext2_dx_root_info(fs, buf);
                        dx_db->type = DX_DIRBLOCK_ROOT;
                        dx_db->flags |= DX_FLAG_FIRST | DX_FLAG_LAST;
+
+                       /* large_dir was set in pass1 if large dirs were found,
+                        * so ext2_dir_htree_level() should now be correct */
                        if ((root->reserved_zero ||
                             root->info_length < 8 ||
                             root->indirect_levels >=
@@ -1217,12 +1380,25 @@ skip_checksum:
 
        dir_encpolicy_id = find_encryption_policy(ctx, ino);
 
-       dict_init(&de_dict, DICTCOUNT_T_MAX, dict_de_cmp);
+       if (cf_dir) {
+               dict_init(&de_dict, DICTCOUNT_T_MAX, dict_de_cf_cmp);
+               dict_set_cmp_context(&de_dict, (void *)ctx->fs->encoding);
+       } else {
+               dict_init(&de_dict, DICTCOUNT_T_MAX, dict_de_cmp);
+       }
+       if (ctx->casefolded_dirs)
+               casefolded = ext2fs_u32_list_test(ctx->casefolded_dirs, ino);
+       hash_in_dirent = (casefolded &&
+                         (dir_encpolicy_id != NO_ENCRYPTION_POLICY));
+
        prev = 0;
        do {
                dgrp_t group;
                ext2_ino_t first_unused_inode;
                unsigned int name_len;
+               /* csum entry is not checked here, so don't worry about it */
+               int extended = (dot_state > 1) && hash_in_dirent;
+               int min_dir_len = ext2fs_dir_rec_len(1, extended);
 
                problem = 0;
                if (!inline_data_size || dot_state > 1) {
@@ -1232,15 +1408,16 @@ skip_checksum:
                         * force salvaging this dir.
                         */
                        if (max_block_size - offset < EXT2_DIR_ENTRY_HEADER_LEN)
-                               rec_len = EXT2_DIR_REC_LEN(1);
+                               rec_len = ext2fs_dir_rec_len(1, extended);
                        else
                                (void) ext2fs_get_rec_len(fs, dirent, &rec_len);
                        cd->pctx.dirent = dirent;
                        cd->pctx.num = offset;
                        if ((offset + rec_len > max_block_size) ||
-                           (rec_len < 12) ||
+                           (rec_len < min_dir_len) ||
                            ((rec_len % 4) != 0) ||
-                           (((unsigned) ext2fs_dirent_name_len(dirent) + EXT2_DIR_ENTRY_HEADER_LEN) > rec_len)) {
+                           ((ext2fs_dir_rec_len(ext2fs_dirent_name_len(dirent),
+                                                extended)) > rec_len)) {
                                if (fix_problem(ctx, PR_2_DIR_CORRUPTED,
                                                &cd->pctx)) {
 #ifdef WORDS_BIGENDIAN
@@ -1273,7 +1450,8 @@ skip_checksum:
 #endif
                                        salvage_directory(fs, dirent, prev,
                                                          &offset,
-                                                         max_block_size);
+                                                         max_block_size,
+                                                         hash_in_dirent);
 #ifdef WORDS_BIGENDIAN
                                        if (need_reswab) {
                                                (void) ext2fs_get_rec_len(fs,
@@ -1294,7 +1472,7 @@ skip_checksum:
                                memset(&dot, 0, sizeof(dot));
                                dirent = &dot;
                                dirent->inode = ino;
-                               dirent->rec_len = EXT2_DIR_REC_LEN(1);
+                               dirent->rec_len = EXT2_DIR_NAME_LEN(1);
                                dirent->name_len = 1 | filetype;
                                dirent->name[0] = '.';
                        } else if (dot_state == 1) {
@@ -1302,7 +1480,7 @@ skip_checksum:
                                dirent = &dotdot;
                                dirent->inode =
                                        ((struct ext2_dir_entry *)buf)->inode;
-                               dirent->rec_len = EXT2_DIR_REC_LEN(2);
+                               dirent->rec_len = EXT2_DIR_NAME_LEN(2);
                                dirent->name_len = 2 | filetype;
                                dirent->name[0] = '.';
                                dirent->name[1] = '.';
@@ -1314,10 +1492,10 @@ skip_checksum:
                }
 
                if (dot_state == 0) {
-                       if (check_dot(ctx, dirent, ino, &cd->pctx))
+                       if (check_dot(ctx, dirent, offset, ino, &cd->pctx))
                                dir_modified++;
                } else if (dot_state == 1) {
-                       ret = check_dotdot(ctx, dirent, ino, &cd->pctx);
+                       ret = check_dotdot(ctx, dirent, offset, ino, &cd->pctx);
                        if (ret < 0)
                                goto abort_free_dict;
                        if (ret)
@@ -1333,6 +1511,10 @@ skip_checksum:
                if (!dirent->inode)
                        goto next;
 
+               ret = e2fsck_check_dirent_data(ctx, dirent, offset, &cd->pctx);
+               if (ret == 2)
+                       dir_modified++;
+
                /*
                 * Make sure the inode listed is a legal one.
                 */
@@ -1394,26 +1576,6 @@ skip_checksum:
                        }
                }
 
-               /*
-                * If the inode was marked as having bad fields in
-                * pass1, process it and offer to fix/clear it.
-                * (We wait until now so that we can display the
-                * pathname to the user.)
-                */
-               if (ctx->inode_bad_map &&
-                   ext2fs_test_inode_bitmap2(ctx->inode_bad_map,
-                                            dirent->inode)) {
-                       if (e2fsck_process_bad_inode(ctx, ino,
-                                                    dirent->inode,
-                                                    buf + fs->blocksize)) {
-                               dirent->inode = 0;
-                               dir_modified++;
-                               goto next;
-                       }
-                       if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
-                               return DIRENT_ABORT;
-               }
-
                group = ext2fs_group_of_ino(fs, dirent->inode);
                first_unused_inode = group * fs->super->s_inodes_per_group +
                                        1 + fs->super->s_inodes_per_group -
@@ -1454,6 +1616,25 @@ skip_checksum:
                        }
                }
 
+               /*
+                * If the inode was marked as having bad fields in
+                * pass1, process it and offer to fix/clear it.
+                * (We wait until now so that we can display the
+                * pathname to the user.)
+                */
+               if (!(ctx->flags & E2F_FLAG_RESTART_LATER) &&
+                   ctx->inode_badness &&
+                   ext2fs_icount_is_set(ctx->inode_badness, dirent->inode)) {
+                       if (e2fsck_process_bad_inode(ctx, ino, dirent->inode,
+                                                    buf + fs->blocksize)) {
+                               dirent->inode = 0;
+                               dir_modified++;
+                               goto next;
+                       }
+                       if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+                               return DIRENT_ABORT;
+               }
+
                /* 
                 * Offer to clear unused inodes; if we are going to be
                 * restarting the scan due to bg_itable_unused being
@@ -1468,25 +1649,25 @@ skip_checksum:
                        problem = PR_2_UNUSED_INODE;
 
                if (problem) {
-                       if (fix_problem(ctx, problem, &cd->pctx)) {
+                       int next = 0;
+
+                       if (fix_problem_bad(ctx, problem, &cd->pctx, 0)) {
                                dirent->inode = 0;
                                dir_modified++;
-                               goto next;
+                               next = 1;
                        } else {
                                ext2fs_unmark_valid(fs);
                                if (problem == PR_2_BAD_INO)
-                                       goto next;
+                                       next = 1;
                        }
+                       if (next)
+                               goto next;
                }
 
                if (check_filetype(ctx, dirent, ino, &cd->pctx))
                        dir_modified++;
 
-               if (dir_encpolicy_id == NO_ENCRYPTION_POLICY) {
-                       /* Unencrypted directory */
-                       if (check_name(ctx, dirent, &cd->pctx))
-                               dir_modified++;
-               } else {
+               if (dir_encpolicy_id != NO_ENCRYPTION_POLICY) {
                        /* Encrypted directory */
                        if (dot_state > 1 &&
                            check_encrypted_dirent(ctx, dirent,
@@ -1496,16 +1677,31 @@ skip_checksum:
                                dir_modified++;
                                goto next;
                        }
+               } else if (cf_dir) {
+                       /* Casefolded directory */
+                       if (encoded_check_name(ctx, dirent, &cd->pctx))
+                               dir_modified++;
+               } else {
+                       /* Unencrypted and uncasefolded directory */
+                       if (check_name(ctx, dirent, &cd->pctx))
+                               dir_modified++;
                }
 
                if (dx_db) {
                        if (dx_dir->casefolded_hash)
                                hash_flags = EXT4_CASEFOLD_FL;
 
-                       ext2fs_dirhash2(dx_dir->hashversion, dirent->name,
-                                       ext2fs_dirent_name_len(dirent),
-                                       fs->encoding, hash_flags,
-                                       fs->super->s_hash_seed, &hash, 0);
+                       if (dx_dir->hashversion == EXT2_HASH_SIPHASH) {
+                               if (dot_state > 1)
+                                       hash = EXT2_DIRENT_HASH(dirent);
+                       } else {
+                               ext2fs_dirhash2(dx_dir->hashversion,
+                                               dirent->name,
+                                               ext2fs_dirent_name_len(dirent),
+                                               fs->encoding, hash_flags,
+                                               fs->super->s_hash_seed,
+                                               &hash, 0);
+                       }
                        if (hash < dx_db->min_hash)
                                dx_db->min_hash = hash;
                        if (hash > dx_db->max_hash)
@@ -1730,8 +1926,17 @@ static void deallocate_inode(e2fsck_t ctx, ext2_ino_t ino, char* block_buf)
        struct problem_context  pctx;
        __u32                   count;
        struct del_block        del_block;
+       int extent_fs = 0;
 
        e2fsck_read_inode(ctx, ino, &inode, "deallocate_inode");
+       /* ext2fs_block_iterate2() depends on the extents flags */
+       if (inode.i_flags & EXT4_EXTENTS_FL)
+               extent_fs = 1;
+       e2fsck_clear_inode(ctx, ino, &inode, 0, "deallocate_inode");
+       if (extent_fs) {
+               inode.i_flags |= EXT4_EXTENTS_FL;
+               e2fsck_write_inode(ctx, ino, &inode, "deallocate_inode");
+       }
        clear_problem_context(&pctx);
        pctx.ino = ino;
 
@@ -1760,6 +1965,8 @@ static void deallocate_inode(e2fsck_t ctx, ext2_ino_t ino, char* block_buf)
                        ext2fs_block_alloc_stats2(fs,
                                  ext2fs_file_acl_block(fs, &inode), -1);
                }
+               if (ctx->inode_badness)
+                       ext2fs_icount_store(ctx->inode_badness, ino, 0);
                ext2fs_file_acl_block_set(fs, &inode, 0);
        }
 
@@ -1770,9 +1977,12 @@ static void deallocate_inode(e2fsck_t ctx, ext2_ino_t ino, char* block_buf)
        if (inode.i_flags & EXT4_INLINE_DATA_FL)
                goto clear_inode;
 
-       if (LINUX_S_ISREG(inode.i_mode) &&
-           ext2fs_needs_large_file_feature(EXT2_I_SIZE(&inode)))
-               ctx->large_files--;
+       if (ext2fs_needs_large_file_feature(EXT2_I_SIZE(&inode))) {
+               if (LINUX_S_ISREG(inode.i_mode))
+                   ctx->large_files--;
+               else if (LINUX_S_ISDIR(inode.i_mode))
+                   ctx->large_dirs--;
+       }
 
        del_block.ctx = ctx;
        del_block.num = 0;
@@ -1816,8 +2026,14 @@ int e2fsck_process_bad_inode(e2fsck_t ctx, ext2_ino_t dir,
        unsigned char           *frag, *fsize;
        struct problem_context  pctx;
        problem_t               problem = 0;
+       __u16                   badness = 0;
+       unsigned int            flags = ctx->fs->flags;
 
+       flags = ctx->fs->flags;
+       ctx->fs->flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS;
        e2fsck_read_inode(ctx, ino, &inode, "process_bad_inode");
+       ctx->fs->flags = (flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) |
+                         (ctx->fs->flags & ~EXT2_FLAG_IGNORE_CSUM_ERRORS);
 
        clear_problem_context(&pctx);
        pctx.ino = ino;
@@ -1850,8 +2066,8 @@ int e2fsck_process_bad_inode(e2fsck_t ctx, ext2_ino_t dir,
        else if (LINUX_S_ISSOCK(inode.i_mode)
                 && !e2fsck_pass1_check_device_inode(fs, &inode))
                problem = PR_2_BAD_SOCKET;
-       else if (LINUX_S_ISLNK(inode.i_mode)
-                && !e2fsck_pass1_check_symlink(fs, ino, &inode, buf)) {
+       else if (LINUX_S_ISLNK(inode.i_mode) &&
+                !e2fsck_pass1_check_symlink(ctx, ino, &inode, buf)) {
                problem = PR_2_INVALID_SYMLINK;
        }
 
@@ -1864,6 +2080,12 @@ int e2fsck_process_bad_inode(e2fsck_t ctx, ext2_ino_t dir,
                } else
                        not_fixed++;
                problem = 0;
+               /*
+                * A large value is associated with bad mode in order to detect
+                * that mode was corrupt in check_filetype()
+                */
+               e2fsck_mark_inode_bad_loc(ctx, &pctx, problem, BADNESS_BAD_MODE,
+                                         __func__, __LINE__);
        }
 
        if (inode.i_faddr) {
@@ -1932,6 +2154,7 @@ int e2fsck_process_bad_inode(e2fsck_t ctx, ext2_ino_t dir,
                        not_fixed++;
        }
        if (inode.i_size_high && !ext2fs_has_feature_largedir(fs->super) &&
+           inode.i_blocks < 1ULL << (29 - EXT2_BLOCK_SIZE_BITS(fs->super)) &&
            LINUX_S_ISDIR(inode.i_mode)) {
                if (fix_problem(ctx, PR_2_DIR_SIZE_HIGH_ZERO, &pctx)) {
                        inode.i_size_high = 0;
@@ -1940,10 +2163,18 @@ int e2fsck_process_bad_inode(e2fsck_t ctx, ext2_ino_t dir,
                        not_fixed++;
        }
 
+       /* The high value from BADNESS_BAD_MODE should not delete the inode */
+       if (e2fsck_fix_bad_inode(ctx, &pctx)) {
+               deallocate_inode(ctx, ino, 0);
+               if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+                       return 0;
+               inode_modified = 0;
+       } else {
+               not_fixed++;
+       }
+
        if (inode_modified)
                e2fsck_write_inode(ctx, ino, &inode, "process_bad_inode");
-       if (!not_fixed && ctx->inode_bad_map)
-               ext2fs_unmark_inode_bitmap2(ctx->inode_bad_map, ino);
        return 0;
 }
 
@@ -1963,7 +2194,7 @@ static int allocate_dir_block(e2fsck_t ctx,
        char                    *block;
        struct ext2_inode       inode;
 
-       if (fix_problem(ctx, PR_2_DIRECTORY_HOLE, pctx) == 0)
+       if (fix_problem_bad(ctx, PR_2_DIRECTORY_HOLE, pctx, 0) == 0)
                return 1;
 
        /*