diff mbox series

[RFCv1,67/72] sec: support encrypted files handling in pfsck mode

Message ID 77a302b36f3576b9a9f7ef6e42bc1ef939227090.1667822612.git.ritesh.list@gmail.com
State Under Review
Delegated to: Theodore Ts'o
Headers show
Series e2fsprogs: Parallel fsck support | expand

Commit Message

Ritesh Harjani (IBM) Nov. 7, 2022, 12:21 p.m. UTC
From: Sebastien Buisson <sbuisson@ddn.com>

e2fsck needs to be improved in order to support encrypted files
handling in parallel fsck mode. The e2fsck_merge_encrypted_info()
function is added to merge encrypted inodes info collected from
different threads in pass1, so that it can be used in pass2.

Signed-off-by: Sebastien Buisson <sbuisson@ddn.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Li Dongyang <dongyangli@ddn.com>
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
Signed-off-by: Ritesh Harjani (IBM) <ritesh.list@gmail.com>
---
 e2fsck/e2fsck.h          |   2 +
 e2fsck/encrypted_files.c | 139 +++++++++++++++++++++++++++++++++++++++
 e2fsck/pass1.c           |  26 +++++++-
 3 files changed, 164 insertions(+), 3 deletions(-)

Comments

Eric Biggers Nov. 7, 2022, 7:22 p.m. UTC | #1
On Mon, Nov 07, 2022 at 05:51:55PM +0530, Ritesh Harjani (IBM) wrote:

> sec: support encrypted files handling in pfsck mode
>

"sec:" => "e2fsck:".

> +/**
> + * Search policy matching @policy in @info->policies
> + * @ctx: e2fsck context
> + * @info: encrypted_file_info to look into
> + * @policy: the policy we are looking for
> + * @parent: (out) last known parent, useful to insert a new leaf
> + *	    in @info->policies
> + *
> + * Return: id of found policy on success, -1 if no matching policy found.
> + */
> +static inline int search_policy(e2fsck_t ctx, struct encrypted_file_info *info,
> +				union fscrypt_policy policy,
> +				struct rb_node **parent)
> +{
> +	struct rb_node *n = info->policies.rb_node;
> +	struct policy_map_entry *entry;
> +
> +	while (n) {
> +		int res;
> +
> +		*parent = n;
> +		entry = ext2fs_rb_entry(n, struct policy_map_entry, node);
> +		res = cmp_fscrypt_policies(ctx, &policy, &entry->policy);
> +		if (res < 0)
> +			n = n->rb_left;
> +		else if (res > 0)
> +			n = n->rb_right;
> +		else
> +			return entry->policy_id;
> +	}
> +	return -1;
> +}

Can this rbtree search code be reused by get_encryption_policy_id()?

Also, please use the existing constant NO_ENCRYPTION_POLICY instead of -1.

> +/*
> + * Merge @src encrypted info into @dest
> + */
> +int e2fsck_merge_encrypted_info(e2fsck_t ctx, struct encrypted_file_info *src,
> +				 struct encrypted_file_info *dest)
> +{
> +	struct rb_root *src_policies = &src->policies;
> +	__u32 *policy_trans;
> +	int i, rc = 0;
> +
> +	if (dest->file_ranges[src->file_ranges_count - 1].last_ino >
> +	    src->file_ranges[0].first_ino) {

There is an off-by-one error here.  How about writing it like the following so
that it looks like the check in append_ino_and_policy_id():

        if (src->file_ranges[0].first_ino <=
            dest->file_ranges[src->file_ranges_count - 1].last_ino) {

> +	/* First, deal with the encryption policy => ID map.

My understanding is that e2fsprogs follows the kernel coding style, where block
comments are formatted like the following:

	/*
	 * text
	 */

> +	 * Compare encryption policies in src with policies already recorded
> +	 * in dest. It can be similar policies, but recorded with a different

"similar" => "the same"

> +	while (!ext2fs_rb_empty_root(src_policies)) {
> +		struct policy_map_entry *entry, *newentry;
> +		struct rb_node *new, *parent = NULL;
> +		int existing_polid;
> +
> +		entry = ext2fs_rb_entry(src_policies->rb_node,
> +					struct policy_map_entry, node);
> +		existing_polid = search_policy(ctx, dest,
> +					       entry->policy, &parent);
> +		if (existing_polid >= 0) {

existing_polid != NO_ENCRYPTION_POLICY

> +			/* The policy in src is already recorded in dest,
> +			 * so just update its id.
> +			 */
> +			policy_trans[entry->policy_id] = existing_polid;
> +		} else {
> +			/* The policy in src is new to dest, so insert it
> +			 * with the next available id (its original id could
> +			 * be already used in dest).
> +			 */
> +			rc = ext2fs_get_mem(sizeof(*newentry), &newentry);
> +			if (rc)
> +				goto out_merge;

Use handle_nomem() for memory allocation failures?

> +                     newentry->policy_id = dest->next_policy_id++;
> +                     newentry->policy = entry->policy;
> +                     ext2fs_rb_link_node(&newentry->node, parent, &new);
> +                     ext2fs_rb_insert_color(&newentry->node,
> +                                            &dest->policies);
> +                     policy_trans[entry->policy_id] = newentry->policy_id;

This code also appears in get_encryption_policy_id().  Should it be refactored
into a helper function?

> +	/* Second, deal with the inode number => encryption policy ID map. */
> +	if (dest->file_ranges_capacity <
> +	    dest->file_ranges_count + src->file_ranges_count) {
> +		/* dest->file_ranges is too short, increase its capacity. */
> +		size_t new_capacity = dest->file_ranges_count +
> +			src->file_ranges_count;
> +
> +		/* Make sure we at least double the capacity. */
> +		if (new_capacity < (dest->file_ranges_capacity * 2))
> +			new_capacity = dest->file_ranges_capacity * 2;
> +
> +		/* We won't need more than the filesystem's inode count. */
> +		if (new_capacity > ctx->fs->super->s_inodes_count)
> +			new_capacity = ctx->fs->super->s_inodes_count;
> +
> +		rc = ext2fs_resize_mem(dest->file_ranges_capacity *
> +				       sizeof(struct encrypted_file_range),
> +				       new_capacity *
> +				       sizeof(struct encrypted_file_range),
> +				       &dest->file_ranges);
> +		if (rc) {
> +			fix_problem(ctx, PR_1_ALLOCATE_ENCRYPTED_INODE_LIST,
> +				    NULL);
> +			/* Should never get here */
> +			ctx->flags |= E2F_FLAG_ABORT;
> +			goto out_merge;
> +		}
> +
> +		dest->file_ranges_capacity = new_capacity;
> +	}

The exact allocation size that is needed is
'dest->file_ranges_count + src->file_ranges_count', so much of the above logic
is unnecessary.  Just reallocate that exact amount.

Also, handle_nomem() should be used.

> +	/* Copy file ranges from src to dest. */
> +	for (i = 0; i < src->file_ranges_count; i++) {
> +		/* Make sure to convert policy ids in src. */
> +		src->file_ranges[i].policy_id =
> +			policy_trans[src->file_ranges[i].policy_id];
> +		dest->file_ranges[dest->file_ranges_count++] =
> +			src->file_ranges[i];
> +	}

This needs to handle UNRECOGNIZED_ENCRYPTION_POLICY as a special case.

Also, shouldn't src->file_ranges be freed after this?

> +
> +out_merge:

I guess the "_merge" in "out_merge:" comes from the name of the containing
function?  It's a bit confusing.  Maybe just use "out:".

> diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c
> index 7345c96d..e7dc017c 100644
> --- a/e2fsck/pass1.c
> +++ b/e2fsck/pass1.c
> @@ -2411,9 +2411,6 @@ void e2fsck_pass1_run(e2fsck_t ctx)
>  		ctx->ea_block_quota_inodes = 0;
>  	}
>  
> -	/* We don't need the encryption policy => ID map any more */
> -	destroy_encryption_policy_map(ctx);

With this change, there are no callers of destroy_encryption_policy_map()
outside encrypted_files.c.  So absent any other changes,
destroy_encryption_policy_map() should be made into a static function and the
'if (info)' check inside it removed.

But, isn't there still some point where pass 1 is fully done and the encryption
policy ID map can be destroyed?  Maybe e2fsck_pass1_merge_encrypted_info()
should be calling destroy_encryption_policy_map() on the global_ctx after doing
the merge?

- Eric
diff mbox series

Patch

diff --git a/e2fsck/e2fsck.h b/e2fsck/e2fsck.h
index 1e82b048..e4fb782a 100644
--- a/e2fsck/e2fsck.h
+++ b/e2fsck/e2fsck.h
@@ -642,6 +642,8 @@  __u32 find_encryption_policy(e2fsck_t ctx, ext2_ino_t ino);
 
 void destroy_encryption_policy_map(e2fsck_t ctx);
 void destroy_encrypted_file_info(e2fsck_t ctx);
+int e2fsck_merge_encrypted_info(e2fsck_t ctx, struct encrypted_file_info *src,
+				struct encrypted_file_info *dest);
 
 /* extents.c */
 errcode_t e2fsck_rebuild_extents_later(e2fsck_t ctx, ext2_ino_t ino);
diff --git a/e2fsck/encrypted_files.c b/e2fsck/encrypted_files.c
index 16be2d6d..53e03a62 100644
--- a/e2fsck/encrypted_files.c
+++ b/e2fsck/encrypted_files.c
@@ -456,3 +456,142 @@  void destroy_encrypted_file_info(e2fsck_t ctx)
 		ctx->encrypted_files = NULL;
 	}
 }
+
+/**
+ * Search policy matching @policy in @info->policies
+ * @ctx: e2fsck context
+ * @info: encrypted_file_info to look into
+ * @policy: the policy we are looking for
+ * @parent: (out) last known parent, useful to insert a new leaf
+ *	    in @info->policies
+ *
+ * Return: id of found policy on success, -1 if no matching policy found.
+ */
+static inline int search_policy(e2fsck_t ctx, struct encrypted_file_info *info,
+				union fscrypt_policy policy,
+				struct rb_node **parent)
+{
+	struct rb_node *n = info->policies.rb_node;
+	struct policy_map_entry *entry;
+
+	while (n) {
+		int res;
+
+		*parent = n;
+		entry = ext2fs_rb_entry(n, struct policy_map_entry, node);
+		res = cmp_fscrypt_policies(ctx, &policy, &entry->policy);
+		if (res < 0)
+			n = n->rb_left;
+		else if (res > 0)
+			n = n->rb_right;
+		else
+			return entry->policy_id;
+	}
+	return -1;
+}
+
+/*
+ * Merge @src encrypted info into @dest
+ */
+int e2fsck_merge_encrypted_info(e2fsck_t ctx, struct encrypted_file_info *src,
+				 struct encrypted_file_info *dest)
+{
+	struct rb_root *src_policies = &src->policies;
+	__u32 *policy_trans;
+	int i, rc = 0;
+
+	if (dest->file_ranges[src->file_ranges_count - 1].last_ino >
+	    src->file_ranges[0].first_ino) {
+		/* Should never get here */
+		fatal_error(ctx, "Encrypted inodes processed out of order");
+	}
+
+	rc = ext2fs_get_array(src->next_policy_id, sizeof(__u32),
+			      &policy_trans);
+	if (rc)
+		return rc;
+
+	/* First, deal with the encryption policy => ID map.
+	 * Compare encryption policies in src with policies already recorded
+	 * in dest. It can be similar policies, but recorded with a different
+	 * id, so policy_trans array converts policy ids in src to ids in dest.
+	 * This loop examines each policy in src->policies rb tree, updates
+	 * policy_trans, and removes the entry from src, so that src->policies
+	 * rb tree is cleaned up at the end of the loop.
+	 */
+	while (!ext2fs_rb_empty_root(src_policies)) {
+		struct policy_map_entry *entry, *newentry;
+		struct rb_node *new, *parent = NULL;
+		int existing_polid;
+
+		entry = ext2fs_rb_entry(src_policies->rb_node,
+					struct policy_map_entry, node);
+		existing_polid = search_policy(ctx, dest,
+					       entry->policy, &parent);
+		if (existing_polid >= 0) {
+			/* The policy in src is already recorded in dest,
+			 * so just update its id.
+			 */
+			policy_trans[entry->policy_id] = existing_polid;
+		} else {
+			/* The policy in src is new to dest, so insert it
+			 * with the next available id (its original id could
+			 * be already used in dest).
+			 */
+			rc = ext2fs_get_mem(sizeof(*newentry), &newentry);
+			if (rc)
+				goto out_merge;
+			newentry->policy_id = dest->next_policy_id++;
+			newentry->policy = entry->policy;
+			ext2fs_rb_link_node(&newentry->node, parent, &new);
+			ext2fs_rb_insert_color(&newentry->node,
+					       &dest->policies);
+			policy_trans[entry->policy_id] = newentry->policy_id;
+		}
+		ext2fs_rb_erase(&entry->node, src_policies);
+		ext2fs_free_mem(&entry);
+	}
+
+	/* Second, deal with the inode number => encryption policy ID map. */
+	if (dest->file_ranges_capacity <
+	    dest->file_ranges_count + src->file_ranges_count) {
+		/* dest->file_ranges is too short, increase its capacity. */
+		size_t new_capacity = dest->file_ranges_count +
+			src->file_ranges_count;
+
+		/* Make sure we at least double the capacity. */
+		if (new_capacity < (dest->file_ranges_capacity * 2))
+			new_capacity = dest->file_ranges_capacity * 2;
+
+		/* We won't need more than the filesystem's inode count. */
+		if (new_capacity > ctx->fs->super->s_inodes_count)
+			new_capacity = ctx->fs->super->s_inodes_count;
+
+		rc = ext2fs_resize_mem(dest->file_ranges_capacity *
+				       sizeof(struct encrypted_file_range),
+				       new_capacity *
+				       sizeof(struct encrypted_file_range),
+				       &dest->file_ranges);
+		if (rc) {
+			fix_problem(ctx, PR_1_ALLOCATE_ENCRYPTED_INODE_LIST,
+				    NULL);
+			/* Should never get here */
+			ctx->flags |= E2F_FLAG_ABORT;
+			goto out_merge;
+		}
+
+		dest->file_ranges_capacity = new_capacity;
+	}
+	/* Copy file ranges from src to dest. */
+	for (i = 0; i < src->file_ranges_count; i++) {
+		/* Make sure to convert policy ids in src. */
+		src->file_ranges[i].policy_id =
+			policy_trans[src->file_ranges[i].policy_id];
+		dest->file_ranges[dest->file_ranges_count++] =
+			src->file_ranges[i];
+	}
+
+out_merge:
+	ext2fs_free_mem(&policy_trans);
+	return rc;
+}
diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c
index 7345c96d..e7dc017c 100644
--- a/e2fsck/pass1.c
+++ b/e2fsck/pass1.c
@@ -2411,9 +2411,6 @@  void e2fsck_pass1_run(e2fsck_t ctx)
 		ctx->ea_block_quota_inodes = 0;
 	}
 
-	/* We don't need the encryption policy => ID map any more */
-	destroy_encryption_policy_map(ctx);
-
 	if (ctx->flags & E2F_FLAG_RESTART) {
 		/*
 		 * Only the master copy of the superblock and block
@@ -2703,6 +2700,23 @@  static void e2fsck_pass1_merge_dx_dir(e2fsck_t global_ctx, e2fsck_t thread_ctx)
 	e2fsck_merge_dx_dir(global_ctx, thread_ctx);
 }
 
+static int e2fsck_pass1_merge_encrypted_info(e2fsck_t global_ctx,
+					      e2fsck_t thread_ctx)
+{
+	if (thread_ctx->encrypted_files == NULL)
+		return 0;
+
+	if (global_ctx->encrypted_files == NULL) {
+		global_ctx->encrypted_files = thread_ctx->encrypted_files;
+		thread_ctx->encrypted_files = NULL;
+		return 0;
+	}
+
+	return e2fsck_merge_encrypted_info(global_ctx,
+					   thread_ctx->encrypted_files,
+					   global_ctx->encrypted_files);
+}
+
 static inline errcode_t
 e2fsck_pass1_merge_icount(ext2_icount_t *dest_icount,
 			  ext2_icount_t *src_icount)
@@ -2963,6 +2977,12 @@  static errcode_t e2fsck_pass1_merge_context(e2fsck_t global_ctx,
 
 	e2fsck_pass1_merge_dir_info(global_ctx, thread_ctx);
 	e2fsck_pass1_merge_dx_dir(global_ctx, thread_ctx);
+	retval = e2fsck_pass1_merge_encrypted_info(global_ctx, thread_ctx);
+	if (retval) {
+		com_err(global_ctx->program_name, 0,
+			_("while merging encrypted info\n"));
+		return retval;
+	}
 
 	retval = ext2fs_merge_fs(&(thread_ctx->fs));
 	if (retval) {