Whamcloud - gitweb
e2fsck: fix ".." more gracefully if possible
[tools/e2fsprogs.git] / e2fsck / pass2.c
index 0bcc6f0..94f92c8 100644 (file)
@@ -294,6 +294,10 @@ void e2fsck_pass2(e2fsck_t ctx)
                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;
+       }
 
        clear_problem_context(&pctx);
        if (ctx->large_files) {
@@ -328,7 +332,7 @@ static short 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;
@@ -344,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.
@@ -386,6 +405,7 @@ static int check_dot(e2fsck_t ctx,
        int             status = 0;
        int             created = 0;
        problem_t       problem = 0;
+       int             ftype = EXT2_FT_DIR;
 
        if (!dirent->inode)
                problem = PR_2_MISSING_DOT;
@@ -397,12 +417,14 @@ static int check_dot(e2fsck_t ctx,
 
        (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;
@@ -423,12 +445,18 @@ static int check_dot(e2fsck_t ctx,
                                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;
                        }
                }
@@ -447,6 +475,7 @@ static int check_dotdot(e2fsck_t ctx,
 {
        problem_t       problem = 0;
        unsigned int    rec_len;
+       int             ftype = EXT2_FT_DIR;
 
        if (!dirent->inode)
                problem = PR_2_MISSING_DOT_DOT;
@@ -459,6 +488,8 @@ static int check_dotdot(e2fsck_t ctx,
 
        (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;
@@ -469,7 +500,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';
@@ -733,7 +764,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;
@@ -758,7 +790,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;
@@ -770,7 +803,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);
@@ -1025,6 +1058,8 @@ 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;
@@ -1035,9 +1070,10 @@ static int check_dir_block(ext2_filsys fs,
        ctx = cd->ctx;
 
        /* We only want filename encoding verification on strict
-        * mode. */
+        * 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->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)
@@ -1194,6 +1230,9 @@ inline_read_fail:
                        root = (struct ext2_dx_root_info *) (buf + 24);
                        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 >=
@@ -1255,12 +1294,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) {
@@ -1270,15 +1322,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
@@ -1311,7 +1364,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,
@@ -1544,10 +1598,17 @@ skip_checksum:
                        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)
@@ -1812,9 +1873,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;
@@ -1974,6 +2038,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;