Whamcloud - gitweb
e2fsck: check for consistent encryption policies
[tools/e2fsprogs.git] / e2fsck / pass2.c
index 6f9e717..306373b 100644 (file)
  *     - The inode_used_map bitmap
  *     - The inode_bad_map bitmap
  *     - The inode_dir_map bitmap
+ *     - The encrypted_file_info
  *
  * Pass 2 frees the following data structures
  *     - The inode_bad_map bitmap
  *     - The inode_reg_map bitmap
+ *     - The encrypted_file_info
  */
 
 #define _GNU_SOURCE 1 /* get strnlen() */
@@ -47,7 +49,7 @@
 
 #include "e2fsck.h"
 #include "problem.h"
-#include "dict.h"
+#include "support/dict.h"
 
 #ifdef NO_INLINE_FUNCS
 #define _INLINE_
@@ -61,6 +63,9 @@
  * Keeps track of how many times an inode is referenced.
  */
 static void deallocate_inode(e2fsck_t ctx, ext2_ino_t ino, char* block_buf);
+static int check_dir_block2(ext2_filsys fs,
+                          struct ext2_db_entry2 *dir_blocks_info,
+                          void *priv_data);
 static int check_dir_block(ext2_filsys fs,
                           struct ext2_db_entry2 *dir_blocks_info,
                           void *priv_data);
@@ -77,25 +82,63 @@ struct check_dir_struct {
        struct problem_context  pctx;
        int     count, max;
        e2fsck_t ctx;
+       unsigned long long list_offset;
+       unsigned long long ra_entries;
+       unsigned long long next_ra_off;
 };
 
+static void update_parents(struct dx_dir_info *dx_dir, int type)
+{
+       struct dx_dirblock_info *dx_db, *dx_parent, *dx_previous;
+       int b;
+
+       for (b = 0, dx_db = dx_dir->dx_block;
+            b < dx_dir->numblocks;
+            b++, dx_db++) {
+               dx_parent = &dx_dir->dx_block[dx_db->parent];
+               if (dx_db->type != type)
+                       continue;
+
+               /*
+                * XXX Make sure dx_parent->min_hash > dx_db->min_hash
+               */
+               if (dx_db->flags & DX_FLAG_FIRST) {
+                       dx_parent->min_hash = dx_db->min_hash;
+                       if (dx_parent->previous) {
+                               dx_previous =
+                                       &dx_dir->dx_block[dx_parent->previous];
+                               dx_previous->node_max_hash =
+                                       dx_parent->min_hash;
+                       }
+               }
+               /*
+                * XXX Make sure dx_parent->max_hash < dx_db->max_hash
+                */
+               if (dx_db->flags & DX_FLAG_LAST) {
+                       dx_parent->max_hash = dx_db->max_hash;
+               }
+       }
+}
+
 void e2fsck_pass2(e2fsck_t ctx)
 {
        struct ext2_super_block *sb = ctx->fs->super;
        struct problem_context  pctx;
        ext2_filsys             fs = ctx->fs;
-       char                    *buf;
+       char                    *buf = NULL;
 #ifdef RESOURCE_TRACK
        struct resource_track   rtrack;
 #endif
        struct check_dir_struct cd;
        struct dx_dir_info      *dx_dir;
-       struct dx_dirblock_info *dx_db, *dx_parent;
-       unsigned int            save_type;
+       struct dx_dirblock_info *dx_db;
        int                     b;
        int                     i, depth;
        problem_t               code;
        int                     bad_dir;
+       int (*check_dir_func)(ext2_filsys fs,
+                             struct ext2_db_entry2 *dir_blocks_info,
+                             void *priv_data);
 
        init_resource_track(&rtrack, ctx->fs->io);
        clear_problem_context(&cd.pctx);
@@ -107,23 +150,13 @@ void e2fsck_pass2(e2fsck_t ctx)
        if (!(ctx->options & E2F_OPT_PREEN))
                fix_problem(ctx, PR_2_PASS_HEADER, &cd.pctx);
 
-       e2fsck_setup_tdb_icount(ctx, EXT2_ICOUNT_OPT_INCREMENT,
-                               &ctx->inode_count);
-       if (ctx->inode_count)
-               cd.pctx.errcode = 0;
-       else {
-               e2fsck_set_bitmap_type(fs, EXT2FS_BMAP64_RBTREE,
-                                      "inode_count", &save_type);
-               cd.pctx.errcode = ext2fs_create_icount2(fs,
-                                               EXT2_ICOUNT_OPT_INCREMENT,
-                                               0, ctx->inode_link_info,
-                                               &ctx->inode_count);
-               fs->default_bitmap_type = save_type;
-       }
+       cd.pctx.errcode = e2fsck_setup_icount(ctx, "inode_count",
+                               EXT2_ICOUNT_OPT_INCREMENT,
+                               ctx->inode_link_info, &ctx->inode_count);
        if (cd.pctx.errcode) {
                fix_problem(ctx, PR_2_ALLOCATE_ICOUNT, &cd.pctx);
                ctx->flags |= E2F_FLAG_ABORT;
-               return;
+               goto cleanup;
        }
        buf = (char *) e2fsck_allocate_memory(ctx, 2*fs->blocksize,
                                              "directory scan buffer");
@@ -139,33 +172,36 @@ void e2fsck_pass2(e2fsck_t ctx)
        cd.ctx = ctx;
        cd.count = 1;
        cd.max = ext2fs_dblist_count2(fs->dblist);
+       cd.list_offset = 0;
+       cd.ra_entries = ctx->readahead_kb * 1024 / ctx->fs->blocksize;
+       cd.next_ra_off = 0;
 
        if (ctx->progress)
                (void) (ctx->progress)(ctx, 2, 0, cd.max);
 
-       if (fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX)
+       if (ext2fs_has_feature_dir_index(fs->super))
                ext2fs_dblist_sort2(fs->dblist, special_dir_block_cmp);
 
-       cd.pctx.errcode = ext2fs_dblist_iterate2(fs->dblist, check_dir_block,
+       check_dir_func = cd.ra_entries ? check_dir_block2 : check_dir_block;
+       cd.pctx.errcode = ext2fs_dblist_iterate2(fs->dblist, check_dir_func,
                                                 &cd);
-       if (ctx->flags & E2F_FLAG_SIGNAL_MASK || ctx->flags & E2F_FLAG_RESTART)
-               return;
-
        if (ctx->flags & E2F_FLAG_RESTART_LATER) {
                ctx->flags |= E2F_FLAG_RESTART;
-               return;
+               ctx->flags &= ~E2F_FLAG_RESTART_LATER;
        }
 
+       if (ctx->flags & E2F_FLAG_RUN_RETURN)
+               goto cleanup;
+
        if (cd.pctx.errcode) {
                fix_problem(ctx, PR_2_DBLIST_ITERATE, &cd.pctx);
                ctx->flags |= E2F_FLAG_ABORT;
-               return;
+               goto cleanup;
        }
 
-#ifdef ENABLE_HTREE
        for (i=0; (dx_dir = e2fsck_dx_dir_info_iter(ctx, &i)) != 0;) {
                if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
-                       return;
+                       goto cleanup;
                if (e2fsck_dir_will_be_rehashed(ctx, dx_dir->ino) ||
                    dx_dir->numblocks == 0)
                        continue;
@@ -181,24 +217,11 @@ void e2fsck_pass2(e2fsck_t ctx)
                 * Find all of the first and last leaf blocks, and
                 * update their parent's min and max hash values
                 */
-               for (b=0, dx_db = dx_dir->dx_block;
-                    b < dx_dir->numblocks;
-                    b++, dx_db++) {
-                       if ((dx_db->type != DX_DIRBLOCK_LEAF) ||
-                           !(dx_db->flags & (DX_FLAG_FIRST | DX_FLAG_LAST)))
-                               continue;
-                       dx_parent = &dx_dir->dx_block[dx_db->parent];
-                       /*
-                        * XXX Make sure dx_parent->min_hash > dx_db->min_hash
-                        */
-                       if (dx_db->flags & DX_FLAG_FIRST)
-                               dx_parent->min_hash = dx_db->min_hash;
-                       /*
-                        * XXX Make sure dx_parent->max_hash < dx_db->max_hash
-                        */
-                       if (dx_db->flags & DX_FLAG_LAST)
-                               dx_parent->max_hash = dx_db->max_hash;
-               }
+               update_parents(dx_dir, DX_DIRBLOCK_LEAF);
+
+               /* for 3 level htree: update 2 level parent's min
+                * and max hash values */
+               update_parents(dx_dir, DX_DIRBLOCK_NODE);
 
                for (b=0, dx_db = dx_dir->dx_block;
                     b < dx_dir->numblocks;
@@ -251,7 +274,7 @@ void e2fsck_pass2(e2fsck_t ctx)
                }
        }
        e2fsck_free_dx_dir_info(ctx);
-#endif
+
        ext2fs_free_mem(&buf);
        ext2fs_free_dblist(fs->dblist);
 
@@ -263,14 +286,13 @@ void e2fsck_pass2(e2fsck_t ctx)
                ext2fs_free_inode_bitmap(ctx->inode_reg_map);
                ctx->inode_reg_map = 0;
        }
+       destroy_encrypted_file_info(ctx);
 
        clear_problem_context(&pctx);
        if (ctx->large_files) {
-               if (!(sb->s_feature_ro_compat &
-                     EXT2_FEATURE_RO_COMPAT_LARGE_FILE) &&
+               if (!ext2fs_has_feature_large_file(sb) &&
                    fix_problem(ctx, PR_2_FEATURE_LARGE_FILES, &pctx)) {
-                       sb->s_feature_ro_compat |=
-                               EXT2_FEATURE_RO_COMPAT_LARGE_FILE;
+                       ext2fs_set_feature_large_file(sb);
                        fs->flags &= ~EXT2_FLAG_MASTER_SB_ONLY;
                        ext2fs_mark_super_dirty(fs);
                }
@@ -282,6 +304,8 @@ void e2fsck_pass2(e2fsck_t ctx)
        }
 
        print_resource_track(ctx, _("Pass 2"), &rtrack, fs->io);
+cleanup:
+       ext2fs_free_mem(&buf);
 }
 
 #define MAX_DEPTH 32000
@@ -310,7 +334,7 @@ static int dict_de_cmp(const void *a, const void *b)
        if (a_len != b_len)
                return (a_len - b_len);
 
-       return strncmp(de_a->name, de_b->name, a_len);
+       return memcmp(de_a->name, de_b->name, a_len);
 }
 
 /*
@@ -459,7 +483,6 @@ static int check_dotdot(e2fsck_t ctx,
  */
 static int check_name(e2fsck_t ctx,
                      struct ext2_dir_entry *dirent,
-                     ext2_ino_t dir_ino EXT2FS_ATTR((unused)),
                      struct problem_context *pctx)
 {
        int     i;
@@ -467,19 +490,30 @@ static int check_name(e2fsck_t ctx,
        int     ret = 0;
 
        for ( i = 0; i < ext2fs_dirent_name_len(dirent); i++) {
-               if (dirent->name[i] == '/' || dirent->name[i] == '\0') {
-                       if (fixup < 0) {
-                               fixup = fix_problem(ctx, PR_2_BAD_NAME, pctx);
-                       }
-                       if (fixup) {
-                               dirent->name[i] = '.';
-                               ret = 1;
-                       }
-               }
+               if (dirent->name[i] != '/' && dirent->name[i] != '\0')
+                       continue;
+               if (fixup < 0)
+                       fixup = fix_problem(ctx, PR_2_BAD_NAME, pctx);
+               if (fixup == 0)
+                       return 0;
+               dirent->name[i] = '.';
+               ret = 1;
        }
        return ret;
 }
 
+static int encrypted_check_name(e2fsck_t ctx,
+                               const struct ext2_dir_entry *dirent,
+                               struct problem_context *pctx)
+{
+       if (ext2fs_dirent_name_len(dirent) < EXT4_CRYPTO_BLOCK_SIZE) {
+               if (fix_problem(ctx, PR_2_BAD_ENCRYPTED_NAME, pctx))
+                       return 1;
+               ext2fs_unmark_valid(ctx->fs);
+       }
+       return 0;
+}
+
 /*
  * Check the directory filetype (if present)
  */
@@ -492,8 +526,7 @@ static _INLINE_ int check_filetype(e2fsck_t ctx,
        int     should_be = EXT2_FT_UNKNOWN;
        struct ext2_inode       inode;
 
-       if (!(ctx->fs->super->s_feature_incompat &
-             EXT2_FEATURE_INCOMPAT_FILETYPE)) {
+       if (!ext2fs_has_feature_filetype(ctx->fs->super)) {
                if (filetype == 0 ||
                    !fix_problem(ctx, PR_2_CLEAR_FILETYPE, pctx))
                        return 0;
@@ -527,7 +560,6 @@ static _INLINE_ int check_filetype(e2fsck_t ctx,
        return 1;
 }
 
-#ifdef ENABLE_HTREE
 static void parse_int_node(ext2_filsys fs,
                           struct ext2_db_entry2 *db,
                           struct check_dir_struct *cd,
@@ -584,8 +616,7 @@ static void parse_int_node(ext2_filsys fs,
 #endif
 
        count = ext2fs_le16_to_cpu(limit->count);
-       if (EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
-                                      EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
+       if (ext2fs_has_feature_metadata_csum(fs->super))
                csum_size = sizeof(struct ext2_dx_tail);
        expect_limit = (fs->blocksize -
                        (csum_size + ((char *) ent - block_buf))) /
@@ -609,7 +640,7 @@ static void parse_int_node(ext2_filsys fs,
                printf("Entry #%d: Hash 0x%08x, block %u\n", i,
                       hash, ext2fs_le32_to_cpu(ent[i].block));
 #endif
-               blk = ext2fs_le32_to_cpu(ent[i].block) & 0x0ffffff;
+               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) {
                        cd->pctx.blk = blk;
@@ -628,6 +659,11 @@ static void parse_int_node(ext2_filsys fs,
                        dx_db->flags |= DX_FLAG_REFERENCED;
                        dx_db->parent = db->blockcnt;
                }
+
+               dx_db->previous =
+                       i ? (ext2fs_le32_to_cpu(ent[i-1].block) &
+                            EXT4_DX_BLOCK_MASK) : 0;
+
                if (hash < min_hash)
                        min_hash = hash;
                if (hash > max_hash)
@@ -657,7 +693,6 @@ clear_and_exit:
        dx_dir->numblocks = 0;
        e2fsck_rehash_dir_later(cd->ctx, cd->pctx.ino);
 }
-#endif /* ENABLE_HTREE */
 
 /*
  * Given a busted directory, try to salvage it somehow.
@@ -757,11 +792,11 @@ static errcode_t insert_dirent_tail(ext2_filsys fs, void *dirbuf)
                d = NEXT_DIRENT(d);
 
        if (d != top) {
-               size_t min_size = EXT2_DIR_REC_LEN(
+               unsigned int min_size = EXT2_DIR_REC_LEN(
                                ext2fs_dirent_name_len(dirbuf));
-               if (min_size > top - (void *)d)
+               if (min_size > (char *)top - (char *)d)
                        return EXT2_ET_DIR_NO_SPACE_FOR_CSUM;
-               d->rec_len = top - (void *)d;
+               d->rec_len = (char *)top - (char *)d;
        }
 
        t = (struct ext2_dir_entry_tail *)top;
@@ -839,14 +874,100 @@ err:
        return retval;
 }
 
+/* Return true if this type of file needs encryption */
+static int needs_encryption(e2fsck_t ctx, const struct ext2_dir_entry *dirent)
+{
+       int filetype = ext2fs_dirent_file_type(dirent);
+       ext2_ino_t ino = dirent->inode;
+       struct ext2_inode inode;
+
+       if (filetype != EXT2_FT_UNKNOWN)
+               return filetype == EXT2_FT_REG_FILE ||
+                      filetype == EXT2_FT_DIR ||
+                      filetype == EXT2_FT_SYMLINK;
+
+       if (ext2fs_test_inode_bitmap2(ctx->inode_reg_map, ino) ||
+           ext2fs_test_inode_bitmap2(ctx->inode_dir_map, ino))
+               return 1;
+
+       e2fsck_read_inode(ctx, ino, &inode, "check_encryption_policy");
+       return LINUX_S_ISREG(inode.i_mode) ||
+              LINUX_S_ISDIR(inode.i_mode) ||
+              LINUX_S_ISLNK(inode.i_mode);
+}
+
+/*
+ * All regular files, directories, and symlinks in encrypted directories must be
+ * encrypted using the same encryption policy as their directory.
+ *
+ * Returns 1 if the dirent should be cleared, otherwise 0.
+ */
+static int check_encryption_policy(e2fsck_t ctx,
+                                  const struct ext2_dir_entry *dirent,
+                                  __u32 dir_encpolicy_id,
+                                  struct problem_context *pctx)
+{
+       __u32 file_encpolicy_id = find_encryption_policy(ctx, dirent->inode);
+
+       /* Same policy or both UNRECOGNIZED_ENCRYPTION_POLICY? */
+       if (file_encpolicy_id == dir_encpolicy_id)
+               return 0;
+
+       if (file_encpolicy_id == NO_ENCRYPTION_POLICY) {
+               if (!needs_encryption(ctx, dirent))
+                       return 0;
+               return fix_problem(ctx, PR_2_UNENCRYPTED_FILE, pctx);
+       }
+
+       return fix_problem(ctx, PR_2_INCONSISTENT_ENCRYPTION_POLICY, pctx);
+}
+
+/*
+ * Check an encrypted directory entry.
+ *
+ * Returns 1 if the dirent should be cleared, otherwise 0.
+ */
+static int check_encrypted_dirent(e2fsck_t ctx,
+                                 const struct ext2_dir_entry *dirent,
+                                 __u32 dir_encpolicy_id,
+                                 struct problem_context *pctx)
+{
+       if (encrypted_check_name(ctx, dirent, pctx))
+               return 1;
+       if (check_encryption_policy(ctx, dirent, dir_encpolicy_id, pctx))
+               return 1;
+       return 0;
+}
+
+static int check_dir_block2(ext2_filsys fs,
+                          struct ext2_db_entry2 *db,
+                          void *priv_data)
+{
+       int err;
+       struct check_dir_struct *cd = priv_data;
+
+       if (cd->ra_entries && cd->list_offset >= cd->next_ra_off) {
+               err = e2fsck_readahead_dblist(fs,
+                                       E2FSCK_RA_DBLIST_IGNORE_BLOCKCNT,
+                                       fs->dblist,
+                                       cd->list_offset + cd->ra_entries / 8,
+                                       cd->ra_entries);
+               if (err)
+                       cd->ra_entries = 0;
+               cd->next_ra_off = cd->list_offset + (cd->ra_entries * 7 / 8);
+       }
+
+       err = check_dir_block(fs, db, priv_data);
+       cd->list_offset++;
+       return err;
+}
+
 static int check_dir_block(ext2_filsys fs,
                           struct ext2_db_entry2 *db,
                           void *priv_data)
 {
        struct dx_dir_info      *dx_dir;
-#ifdef ENABLE_HTREE
        struct dx_dirblock_info *dx_db = 0;
-#endif /* ENABLE_HTREE */
        struct ext2_dir_entry   *dirent, *prev, dot, dotdot;
        ext2_dirhash_t          hash;
        unsigned int            offset = 0;
@@ -872,26 +993,26 @@ static int check_dir_block(ext2_filsys fs,
        int     is_leaf = 1;
        size_t  inline_data_size = 0;
        int     filetype = 0;
+       __u32   dir_encpolicy_id = NO_ENCRYPTION_POLICY;
        size_t  max_block_size;
+       int     hash_flags = 0;
 
        cd = (struct check_dir_struct *) priv_data;
        ibuf = buf = cd->buf;
        ctx = cd->ctx;
 
-       if (ctx->flags & E2F_FLAG_SIGNAL_MASK || ctx->flags & E2F_FLAG_RESTART)
+       if (ctx->flags & E2F_FLAG_RUN_RETURN)
                return DIRENT_ABORT;
 
        if (ctx->progress && (ctx->progress)(ctx, 2, cd->count++, cd->max))
                return DIRENT_ABORT;
 
-       if (EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
-                                      EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)) {
+       if (ext2fs_has_feature_metadata_csum(fs->super)) {
                dx_csum_size = sizeof(struct ext2_dx_tail);
                de_csum_size = sizeof(struct ext2_dir_entry_tail);
        }
 
-       if (EXT2_HAS_INCOMPAT_FEATURE(fs->super,
-                                     EXT2_FEATURE_INCOMPAT_FILETYPE))
+       if (ext2fs_has_feature_filetype(fs->super))
                filetype = EXT2_FT_DIR << 8;
 
        /*
@@ -908,8 +1029,7 @@ static int check_dir_block(ext2_filsys fs,
        cd->pctx.dirent = 0;
        cd->pctx.num = 0;
 
-       if (EXT2_HAS_INCOMPAT_FEATURE(fs->super,
-                                     EXT4_FEATURE_INCOMPAT_INLINE_DATA)) {
+       if (ext2fs_has_feature_inline_data(fs->super)) {
                errcode_t ec;
 
                ec = ext2fs_inline_data_size(fs, ino, &inline_data_size);
@@ -917,6 +1037,15 @@ static int check_dir_block(ext2_filsys fs,
                        return DIRENT_ABORT;
        }
 
+       /* This will allow (at some point in the future) to punch out empty
+        * directory blocks and reduce the space used by a directory that grows
+        * very large and then the files are deleted. For now, all that is
+        * needed is to avoid e2fsck filling in these holes as part of
+        * feature flag. */
+       if (db->blk == 0 && ext2fs_has_feature_largedir(fs->super) &&
+           !ext2fs_has_feature_inline_data(fs->super))
+               return 0;
+
        if (db->blk == 0 && !inline_data_size) {
                if (allocate_dir_block(ctx, db, buf, &cd->pctx))
                        return 0;
@@ -998,7 +1127,6 @@ inline_read_fail:
                memcpy(buf, buf2, fs->blocksize);
                ext2fs_free_mem(&buf2);
        }
-#ifdef ENABLE_HTREE
        dx_dir = e2fsck_get_dx_dir_info(ctx, ino);
        if (dx_dir && dx_dir->numblocks) {
                if (db->blockcnt >= dx_dir->numblocks) {
@@ -1027,11 +1155,12 @@ inline_read_fail:
                        dx_db->flags |= DX_FLAG_FIRST | DX_FLAG_LAST;
                        if ((root->reserved_zero ||
                             root->info_length < 8 ||
-                            root->indirect_levels > 1) &&
+                            root->indirect_levels >=
+                            ext2_dir_htree_level(fs)) &&
                            fix_problem(ctx, PR_2_HTREE_BAD_ROOT, &cd->pctx)) {
                                clear_htree(ctx, ino);
                                dx_dir->numblocks = 0;
-                               dx_db = 0;
+                               dx_db = NULL;
                        }
                        dx_dir->hashversion = root->hash_version;
                        if ((dx_dir->hashversion <= EXT2_HASH_TEA) &&
@@ -1043,12 +1172,12 @@ inline_read_fail:
                           (ext2fs_dirent_name_len(dirent) == 0) &&
                           (ext2fs_le16_to_cpu(limit->limit) ==
                            ((fs->blocksize - (8 + dx_csum_size)) /
-                            sizeof(struct ext2_dx_entry))))
+                            sizeof(struct ext2_dx_entry)))) {
                        dx_db->type = DX_DIRBLOCK_NODE;
-               is_leaf = 0;
+               }
+               is_leaf = dx_db ? (dx_db->type == DX_DIRBLOCK_LEAF) : 0;
        }
 out_htree:
-#endif /* ENABLE_HTREE */
 
        /* Leaf node with no space for csum?  Rebuild dirs in pass 3A. */
        if (is_leaf && !inline_data_size && failed_csum &&
@@ -1083,6 +1212,8 @@ skip_checksum:
        } else
                max_block_size = fs->blocksize - de_csum_size;
 
+       dir_encpolicy_id = find_encryption_policy(ctx, ino);
+
        dict_init(&de_dict, DICTCOUNT_T_MAX, dict_de_cmp);
        prev = 0;
        do {
@@ -1106,7 +1237,7 @@ skip_checksum:
                        if ((offset + rec_len > max_block_size) ||
                            (rec_len < 12) ||
                            ((rec_len % 4) != 0) ||
-                           ((ext2fs_dirent_name_len(dirent) + EXT2_DIR_ENTRY_HEADER_LEN) > rec_len)) {
+                           (((unsigned) ext2fs_dirent_name_len(dirent) + EXT2_DIR_ENTRY_HEADER_LEN) > rec_len)) {
                                if (fix_problem(ctx, PR_2_DIR_CORRUPTED,
                                                &cd->pctx)) {
 #ifdef WORDS_BIGENDIAN
@@ -1345,23 +1476,38 @@ skip_checksum:
                        }
                }
 
-               if (check_name(ctx, dirent, ino, &cd->pctx))
-                       dir_modified++;
-
                if (check_filetype(ctx, dirent, ino, &cd->pctx))
                        dir_modified++;
 
-#ifdef ENABLE_HTREE
+               if (dir_encpolicy_id == NO_ENCRYPTION_POLICY) {
+                       /* Unencrypted directory */
+                       if (check_name(ctx, dirent, &cd->pctx))
+                               dir_modified++;
+               } else {
+                       /* Encrypted directory */
+                       if (dot_state > 1 &&
+                           check_encrypted_dirent(ctx, dirent,
+                                                  dir_encpolicy_id,
+                                                  &cd->pctx)) {
+                               dirent->inode = 0;
+                               dir_modified++;
+                               goto next;
+                       }
+               }
+
                if (dx_db) {
-                       ext2fs_dirhash(dx_dir->hashversion, dirent->name,
-                                      ext2fs_dirent_name_len(dirent),
-                                      fs->super->s_hash_seed, &hash, 0);
+                       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 (hash < dx_db->min_hash)
                                dx_db->min_hash = hash;
                        if (hash > dx_db->max_hash)
                                dx_db->max_hash = hash;
                }
-#endif
 
                /*
                 * If this is a directory, then mark its parent in its
@@ -1437,7 +1583,6 @@ skip_checksum:
 #if 0
        printf("\n");
 #endif
-#ifdef ENABLE_HTREE
        if (dx_db) {
 #ifdef DX_DEBUG
                printf("db_block %d, type %d, min_hash 0x%0x, max_hash 0x%0x\n",
@@ -1449,7 +1594,6 @@ skip_checksum:
                    (dx_db->type == DX_DIRBLOCK_NODE))
                        parse_int_node(fs, db, cd, dx_dir, buf, failed_csum);
        }
-#endif /* ENABLE_HTREE */
 
        if (offset != max_block_size) {
                cd->pctx.num = rec_len + offset - max_block_size;
@@ -1461,8 +1605,7 @@ skip_checksum:
        if (dir_modified) {
                int     flags, will_rehash;
                /* leaf block with no tail?  Rehash dirs later. */
-               if (EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
-                               EXT4_FEATURE_RO_COMPAT_METADATA_CSUM) &&
+               if (ext2fs_has_feature_metadata_csum(fs->super) &&
                    is_leaf &&
                    !inline_data_size &&
                    !ext2fs_dirent_has_tail(fs, (struct ext2_dir_entry *)buf)) {
@@ -1541,6 +1684,7 @@ abort_free_dict:
 struct del_block {
        e2fsck_t        ctx;
        e2_blkcnt_t     num;
+       blk64_t last_cluster;
 };
 
 /*
@@ -1555,20 +1699,26 @@ static int deallocate_inode_block(ext2_filsys fs,
                                  void *priv_data)
 {
        struct del_block *p = priv_data;
+       blk64_t cluster = EXT2FS_B2C(fs, *block_nr);
+
+       if (*block_nr == 0)
+               return 0;
 
-       if (HOLE_BLKADDR(*block_nr))
+       if (cluster == p->last_cluster)
                return 0;
+
+       p->last_cluster = cluster;
        if ((*block_nr < fs->super->s_first_data_block) ||
            (*block_nr >= ext2fs_blocks_count(fs->super)))
                return 0;
-       if ((*block_nr % EXT2FS_CLUSTER_RATIO(fs)) == 0)
-               ext2fs_block_alloc_stats2(fs, *block_nr, -1);
+
+        ext2fs_block_alloc_stats2(fs, *block_nr, -1);
        p->num++;
        return 0;
 }
 
 /*
- * This fuction deallocates an inode
+ * This function deallocates an inode
  */
 static void deallocate_inode(e2fsck_t ctx, ext2_ino_t ino, char* block_buf)
 {
@@ -1589,7 +1739,7 @@ static void deallocate_inode(e2fsck_t ctx, ext2_ino_t ino, char* block_buf)
        ext2fs_inode_alloc_stats2(fs, ino, -1, LINUX_S_ISDIR(inode.i_mode));
 
        if (ext2fs_file_acl_block(fs, &inode) &&
-           (fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_EXT_ATTR)) {
+           ext2fs_has_feature_xattr(fs->super)) {
                pctx.errcode = ext2fs_adjust_ea_refcount3(fs,
                                ext2fs_file_acl_block(fs, &inode),
                                block_buf, -1, &count, ino);
@@ -1623,6 +1773,7 @@ static void deallocate_inode(e2fsck_t ctx, ext2_ino_t ino, char* block_buf)
 
        del_block.ctx = ctx;
        del_block.num = 0;
+       del_block.last_cluster = 0;
        pctx.errcode = ext2fs_block_iterate3(fs, ino, 0, block_buf,
                                             deallocate_inode_block,
                                             &del_block);
@@ -1638,7 +1789,7 @@ clear_inode:
 }
 
 /*
- * This fuction clears the htree flag on an inode
+ * This function clears the htree flag on an inode
  */
 static void clear_htree(e2fsck_t ctx, ext2_ino_t ino)
 {
@@ -1671,7 +1822,7 @@ int e2fsck_process_bad_inode(e2fsck_t ctx, ext2_ino_t dir,
        pctx.inode = &inode;
 
        if (ext2fs_file_acl_block(fs, &inode) &&
-           !(fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_EXT_ATTR)) {
+           !ext2fs_has_feature_xattr(fs->super)) {
                if (fix_problem(ctx, PR_2_FILE_ACL_ZERO, &pctx)) {
                        ext2fs_file_acl_block_set(fs, &inode, 0);
                        inode_modified++;
@@ -1748,8 +1899,7 @@ int e2fsck_process_bad_inode(e2fsck_t ctx, ext2_ino_t dir,
        }
 
        if ((fs->super->s_creator_os == EXT2_OS_LINUX) &&
-           !(fs->super->s_feature_ro_compat &
-             EXT4_FEATURE_RO_COMPAT_HUGE_FILE) &&
+           !ext2fs_has_feature_huge_file(fs->super) &&
            (inode.osd2.linux2.l_i_blocks_hi != 0)) {
                pctx.num = inode.osd2.linux2.l_i_blocks_hi;
                if (fix_problem(ctx, PR_2_BLOCKS_HI_ZERO, &pctx)) {
@@ -1759,8 +1909,7 @@ int e2fsck_process_bad_inode(e2fsck_t ctx, ext2_ino_t dir,
        }
 
        if ((fs->super->s_creator_os == EXT2_OS_LINUX) &&
-           !(fs->super->s_feature_incompat &
-            EXT4_FEATURE_INCOMPAT_64BIT) &&
+           !ext2fs_has_feature_64bit(fs->super) &&
            inode.osd2.linux2.l_i_file_acl_high != 0) {
                pctx.num = inode.osd2.linux2.l_i_file_acl_high;
                if (fix_problem(ctx, PR_2_I_FILE_ACL_HI_ZERO, &pctx)) {
@@ -1779,10 +1928,10 @@ int e2fsck_process_bad_inode(e2fsck_t ctx, ext2_ino_t dir,
                } else
                        not_fixed++;
        }
-       if (inode.i_dir_acl &&
+       if (inode.i_size_high && !ext2fs_has_feature_largedir(fs->super) &&
            LINUX_S_ISDIR(inode.i_mode)) {
-               if (fix_problem(ctx, PR_2_DIR_ACL_ZERO, &pctx)) {
-                       inode.i_dir_acl = 0;
+               if (fix_problem(ctx, PR_2_DIR_SIZE_HIGH_ZERO, &pctx)) {
+                       inode.i_size_high = 0;
                        inode_modified++;
                } else
                        not_fixed++;
@@ -1867,7 +2016,7 @@ static int allocate_dir_block(e2fsck_t ctx,
         * Update the inode block count
         */
        ext2fs_iblk_add_blocks(fs, &inode, 1);
-       if (EXT2_I_SIZE(&inode) < (db->blockcnt+1) * fs->blocksize) {
+       if (EXT2_I_SIZE(&inode) < ((__u64) db->blockcnt+1) * fs->blocksize) {
                pctx->errcode = ext2fs_inode_size_set(fs, &inode,
                                        (db->blockcnt+1) * fs->blocksize);
                if (pctx->errcode) {