Whamcloud - gitweb
e2fsck: check for consistent encryption policies
[tools/e2fsprogs.git] / e2fsck / pass2.c
index 11c19e8..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() */
@@ -85,18 +87,51 @@ struct check_dir_struct {
        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;
+       struct dx_dirblock_info *dx_db;
        int                     b;
        int                     i, depth;
        problem_t               code;
@@ -182,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;
@@ -264,10 +286,7 @@ void e2fsck_pass2(e2fsck_t ctx)
                ext2fs_free_inode_bitmap(ctx->inode_reg_map);
                ctx->inode_reg_map = 0;
        }
-       if (ctx->encrypted_dirs) {
-               ext2fs_u32_list_free(ctx->encrypted_dirs);
-               ctx->encrypted_dirs = 0;
-       }
+       destroy_encrypted_file_info(ctx);
 
        clear_problem_context(&pctx);
        if (ctx->large_files) {
@@ -484,14 +503,12 @@ static int check_name(e2fsck_t ctx,
 }
 
 static int encrypted_check_name(e2fsck_t ctx,
-                               struct ext2_dir_entry *dirent,
+                               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)) {
-                       dirent->inode = 0;
+               if (fix_problem(ctx, PR_2_BAD_ENCRYPTED_NAME, pctx))
                        return 1;
-               }
                ext2fs_unmark_valid(ctx->fs);
        }
        return 0;
@@ -623,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;
@@ -642,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)
@@ -852,6 +874,71 @@ 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)
@@ -906,8 +993,9 @@ static int check_dir_block(ext2_filsys fs,
        int     is_leaf = 1;
        size_t  inline_data_size = 0;
        int     filetype = 0;
-       int     encrypted = 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;
@@ -949,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;
@@ -1058,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) &&
@@ -1074,9 +1172,10 @@ 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 = (dx_db->type == DX_DIRBLOCK_LEAF);
+               }
+               is_leaf = dx_db ? (dx_db->type == DX_DIRBLOCK_LEAF) : 0;
        }
 out_htree:
 
@@ -1113,8 +1212,7 @@ skip_checksum:
        } else
                max_block_size = fs->blocksize - de_csum_size;
 
-       if (ctx->encrypted_dirs)
-               encrypted = ext2fs_u32_list_test(ctx->encrypted_dirs, ino);
+       dir_encpolicy_id = find_encryption_policy(ctx, ino);
 
        dict_init(&de_dict, DICTCOUNT_T_MAX, dict_de_cmp);
        prev = 0;
@@ -1378,22 +1476,33 @@ skip_checksum:
                        }
                }
 
-               if (!encrypted && check_name(ctx, dirent, &cd->pctx))
+               if (check_filetype(ctx, dirent, ino, &cd->pctx))
                        dir_modified++;
 
-               if (encrypted && (dot_state) > 1 &&
-                   encrypted_check_name(ctx, dirent, &cd->pctx)) {
-                       dir_modified++;
-                       goto next;
+               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 (check_filetype(ctx, dirent, ino, &cd->pctx))
-                       dir_modified++;
-
                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)
@@ -1575,6 +1684,7 @@ abort_free_dict:
 struct del_block {
        e2fsck_t        ctx;
        e2_blkcnt_t     num;
+       blk64_t last_cluster;
 };
 
 /*
@@ -1589,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 (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)
 {
@@ -1657,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);
@@ -1672,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)
 {
@@ -1811,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++;