Linux block layer
 help / color / mirror / Atom feed
* [PATCH v7 31/43] btrfs: limit encrypted writes to 256 segments
From: Daniel Vacek @ 2026-05-13  8:53 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Josef Bacik <josef@toxicpanda.com>

For the fallback encrypted writes it allocates a bounce buffer to
encrypt, and if the bio is larger than 256 segments it splits the bio we
send down.  This wreaks havoc on us because we need our actual original
bio to be passed into the process_cb callback in order to get at our
ordered extent.  With the split we'll get some cloned bio that has none
of our information.  Handle this by returning the length we need to be
truncated to and then splitting ourselves, which will handle giving us
the correct btrfs_bio that we need.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/1fc07b885453495bccea5a37e13d7d26333bd2af.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/btrfs/bio.c     | 29 ++++++++++++++++++++++++++++-
 fs/btrfs/fscrypt.c | 24 ++++++++++++++++++++++++
 fs/btrfs/fscrypt.h |  6 ++++++
 3 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
index b79fc467445d..3e2ee19aab50 100644
--- a/fs/btrfs/bio.c
+++ b/fs/btrfs/bio.c
@@ -15,6 +15,7 @@
 #include "zoned.h"
 #include "file-item.h"
 #include "raid-stripe-tree.h"
+#include "fscrypt.h"
 
 static struct bio_set btrfs_bioset;
 static struct bio_set btrfs_clone_bioset;
@@ -759,6 +760,7 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num)
 	u64 logical = bio->bi_iter.bi_sector << SECTOR_SHIFT;
 	u64 length = bio->bi_iter.bi_size;
 	u64 map_length = length;
+	u64 max_bio_len = length;
 	struct btrfs_io_context *bioc = NULL;
 	struct btrfs_io_stripe smap;
 	blk_status_t status;
@@ -770,6 +772,31 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num)
 		smap.rst_search_commit_root = false;
 
 	btrfs_bio_counter_inc_blocked(fs_info);
+
+	/*
+	 * The blk-crypto-fallback limits bio sizes to 256 segments, because it
+	 * has no way of controlling the pages it gets for the bounce bio it
+	 * submits.
+	 *
+	 * If we don't pre-split our bio blk-crypto-fallback will do it for us,
+	 * and then call into our checksum callback with a random cloned bio
+	 * that isn't a btrfs_bio.
+	 *
+	 * To account for this we must truncate the bio ourselves, so we need to
+	 * get our length at 256 segments and return that.  We must do this
+	 * before btrfs_map_block because the RAID5/6 code relies on having
+	 * properly stripe aligned things, so we return the truncated length
+	 * here so that btrfs_map_block() does the correct thing.  Further down
+	 * we actually truncate map_length to map_bio_len because map_length
+	 * will be set to whatever the mapping length is for the underlying
+	 * geometry.  This will work properly with RAID5/6 as it will have
+	 * already setup everything for the expected length, and everything else
+	 * can handle with a truncated map_length.
+	 */
+	if (bio_has_crypt_ctx(bio))
+		max_bio_len = btrfs_fscrypt_bio_length(bio, map_length);
+
+	map_length = max_bio_len;
 	ret = btrfs_map_block(fs_info, btrfs_op(bio), logical, &map_length,
 			      &bioc, &smap, &mirror_num);
 	if (ret) {
@@ -788,7 +815,7 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num)
 
 	bbio->can_use_append = btrfs_use_zone_append(bbio);
 
-	map_length = min(map_length, length);
+	map_length = min(map_length, max_bio_len);
 	if (bbio->can_use_append)
 		map_length = btrfs_append_map_length(bbio, map_length);
 
diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index 5d4802fce7eb..5d34a8b94da5 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -295,6 +295,30 @@ ssize_t btrfs_fscrypt_context_for_new_extent(struct btrfs_inode *inode,
 	return ret;
 }
 
+/*
+ * The block crypto stuff allocates bounce buffers for encryption, so splits at
+ * BIO_MAX_VECS worth of segments.  If we are larger than that number of
+ * segments then we need to limit the size to the size that BIO_MAX_VECS covers.
+ */
+int btrfs_fscrypt_bio_length(struct bio *bio, u64 map_length)
+{
+	unsigned int i = 0;
+	struct bio_vec bv;
+	struct bvec_iter iter;
+	u64 segments_length = 0;
+
+	if (bio_op(bio) != REQ_OP_WRITE)
+		return map_length;
+
+	bio_for_each_segment(bv, bio, iter) {
+		segments_length += bv.bv_len;
+		if (++i == BIO_MAX_VECS)
+			return segments_length;
+	}
+
+	return map_length;
+}
+
 const struct fscrypt_operations btrfs_fscrypt_ops = {
 	.inode_info_offs = (int)offsetof(struct btrfs_inode, i_crypt_info) -
 			   (int)offsetof(struct btrfs_inode, vfs_inode),
diff --git a/fs/btrfs/fscrypt.h b/fs/btrfs/fscrypt.h
index 68eab4606935..f7ce2b2e6639 100644
--- a/fs/btrfs/fscrypt.h
+++ b/fs/btrfs/fscrypt.h
@@ -24,6 +24,7 @@ void btrfs_fscrypt_save_extent_info(struct btrfs_path *path, u8 *ctx, unsigned l
 ssize_t btrfs_fscrypt_context_for_new_extent(struct btrfs_inode *inode,
 					     struct fscrypt_extent_info *info,
 					     u8 *ctx);
+int btrfs_fscrypt_bio_length(struct bio *bio, u64 map_length);
 
 #else
 static inline void btrfs_fscrypt_save_extent_info(struct btrfs_path *path,
@@ -63,6 +64,11 @@ static inline ssize_t btrfs_fscrypt_context_for_new_extent(struct btrfs_inode *i
 	return -EINVAL;
 }
 
+static inline u64 btrfs_fscrypt_bio_length(struct bio *bio, u64 map_length)
+{
+	return map_length;
+}
+
 #endif /* CONFIG_FS_ENCRYPTION */
 
 extern const struct fscrypt_operations btrfs_fscrypt_ops;
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 35/43] btrfs: make btrfs_ref_to_path handle encrypted filenames
From: Daniel Vacek @ 2026-05-13  8:53 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Josef Bacik <josef@toxicpanda.com>

We use this helper for inode-resolve and path resolution in send, so
update this helper to properly decrypt any encrypted names it finds.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v7 changes:
 * Fix eb leak in failure case as suggested by Chris' AI review.
v6 changes:
 * Adapted to btrfs_iget() now returning binode instead of vfs inode
   as before.
 * Adapted to crypt info being moved from vfs inode to FS specific inode.
v5: https://lore.kernel.org/linux-btrfs/365d4f820f70b7cf69b1b9cae9b949a15c3350b0.1706116485.git.josef@toxicpanda.com/
---
 fs/btrfs/backref.c | 43 ++++++++++++++++++++++++++++++++++++++----
 fs/btrfs/fscrypt.c | 47 ++++++++++++++++++++++++++++++++++++++++++++++
 fs/btrfs/fscrypt.h | 10 ++++++++++
 3 files changed, 96 insertions(+), 4 deletions(-)

diff --git a/fs/btrfs/backref.c b/fs/btrfs/backref.c
index 273924ca912c..33d5df99be8e 100644
--- a/fs/btrfs/backref.c
+++ b/fs/btrfs/backref.c
@@ -20,6 +20,7 @@
 #include "extent-tree.h"
 #include "relocation.h"
 #include "tree-checker.h"
+#include "fscrypt.h"
 
 /* Just arbitrary numbers so we can be sure one of these happened. */
 #define BACKREF_FOUND_SHARED     6
@@ -2104,6 +2105,39 @@ int btrfs_find_one_extref(struct btrfs_root *root, u64 inode_objectid,
 	return ret;
 }
 
+static int copy_resolved_iref_to_buf(struct btrfs_root *fs_root,
+				     struct extent_buffer *eb, char *dest,
+				     u64 parent, unsigned long name_off,
+				     u32 name_len, s64 *bytes_left)
+{
+	struct btrfs_fs_info *fs_info = fs_root->fs_info;
+	struct fscrypt_str fname = FSTR_INIT(NULL, 0);
+	int ret;
+
+	/* No encryption, just copy the name in. */
+	if (!btrfs_fs_incompat(fs_info, ENCRYPT)) {
+		*bytes_left -= name_len;
+		if (*bytes_left >= 0)
+			read_extent_buffer(eb, dest + *bytes_left, name_off, name_len);
+		return 0;
+	}
+
+	ret = fscrypt_fname_alloc_buffer(BTRFS_NAME_LEN, &fname);
+	if (ret)
+		return ret;
+
+	ret = btrfs_decrypt_name(fs_root, eb, name_off, name_len, parent, &fname);
+	if (ret)
+		goto out;
+
+	*bytes_left -= fname.len;
+	if (*bytes_left >= 0)
+		memcpy(dest + *bytes_left, fname.name, fname.len);
+out:
+	fscrypt_fname_free_buffer(&fname);
+	return ret;
+}
+
 /*
  * this iterates to turn a name (from iref/extref) into a full filesystem path.
  * Elements of the path are separated by '/' and the path is guaranteed to be
@@ -2135,15 +2169,16 @@ char *btrfs_ref_to_path(struct btrfs_root *fs_root, struct btrfs_path *path,
 		dest[bytes_left] = '\0';
 
 	while (1) {
-		bytes_left -= name_len;
-		if (bytes_left >= 0)
-			read_extent_buffer(eb, dest + bytes_left,
-					   name_off, name_len);
+		ret = copy_resolved_iref_to_buf(fs_root, eb, dest, parent,
+						name_off, name_len, &bytes_left);
 		if (eb != eb_in) {
 			if (!path->skip_locking)
 				btrfs_tree_read_unlock(eb);
 			free_extent_buffer(eb);
 		}
+		if (ret)
+			break;
+
 		ret = btrfs_find_item(fs_root, path, parent, 0,
 				BTRFS_INODE_REF_KEY, &found_key);
 		if (ret > 0)
diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index 111ca92a3450..a972c8eadfef 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -352,6 +352,53 @@ int btrfs_fscrypt_bio_length(struct bio *bio, u64 map_length)
 	return map_length;
 }
 
+int btrfs_decrypt_name(struct btrfs_root *root, struct extent_buffer *eb,
+		       unsigned long name_off, u32 name_len,
+		       u64 parent_ino, struct fscrypt_str *name)
+{
+	struct btrfs_inode *inode;
+	struct inode *dir;
+	struct fscrypt_str iname = FSTR_INIT(NULL, 0);
+	int ret;
+
+	ASSERT(name_len <= BTRFS_NAME_LEN);
+
+	ret = fscrypt_fname_alloc_buffer(name_len, &iname);
+	if (ret)
+		return ret;
+
+	inode = btrfs_iget(parent_ino, root);
+	if (IS_ERR(inode)) {
+		ret = PTR_ERR(inode);
+		goto out;
+	}
+	dir = &inode->vfs_inode;
+
+	/*
+	 * Directory isn't encrypted, the name isn't encrypted, we can just copy
+	 * it into the buffer.
+	 */
+	if (!IS_ENCRYPTED(dir)) {
+		read_extent_buffer(eb, name->name, name_off, name_len);
+		name->len = name_len;
+		goto out_inode;
+	}
+
+	read_extent_buffer(eb, iname.name, name_off, name_len);
+
+	ret = fscrypt_prepare_readdir(dir);
+	if (ret)
+		goto out_inode;
+
+	ASSERT(inode->i_crypt_info);
+	ret = fscrypt_fname_disk_to_usr(dir, 0, 0, &iname, name);
+out_inode:
+	iput(dir);
+out:
+	fscrypt_fname_free_buffer(&iname);
+	return ret;
+}
+
 const struct fscrypt_operations btrfs_fscrypt_ops = {
 	.inode_info_offs = (int)offsetof(struct btrfs_inode, i_crypt_info) -
 			   (int)offsetof(struct btrfs_inode, vfs_inode),
diff --git a/fs/btrfs/fscrypt.h b/fs/btrfs/fscrypt.h
index f7ce2b2e6639..4a1daed90d06 100644
--- a/fs/btrfs/fscrypt.h
+++ b/fs/btrfs/fscrypt.h
@@ -25,6 +25,9 @@ ssize_t btrfs_fscrypt_context_for_new_extent(struct btrfs_inode *inode,
 					     struct fscrypt_extent_info *info,
 					     u8 *ctx);
 int btrfs_fscrypt_bio_length(struct bio *bio, u64 map_length);
+int btrfs_decrypt_name(struct btrfs_root *root, struct extent_buffer *eb,
+		       unsigned long name_off, u32 name_len,
+		       u64 parent_ino, struct fscrypt_str *name);
 
 #else
 static inline void btrfs_fscrypt_save_extent_info(struct btrfs_path *path,
@@ -69,6 +72,13 @@ static inline u64 btrfs_fscrypt_bio_length(struct bio *bio, u64 map_length)
 	return map_length;
 }
 
+static inline int btrfs_decrypt_name(struct btrfs_root *root, struct extent_buffer *eb,
+				     unsigned long name_off, u32 name_len,
+				     u64 parent_ino, struct fscrypt_str *name)
+{
+	return -EINVAL;
+}
+
 #endif /* CONFIG_FS_ENCRYPTION */
 
 extern const struct fscrypt_operations btrfs_fscrypt_ops;
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 27/43] btrfs: setup fscrypt_extent_info for new extents
From: Daniel Vacek @ 2026-05-13  8:53 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Josef Bacik <josef@toxicpanda.com>

New extents for encrypted inodes must have a fscrypt_extent_info, which
has the necessary keys and does all the registration at the block layer
for them.  This is passed through all of the infrastructure we've
previously added to make sure the context gets saved properly with the
file extents.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/d8ab016d25f70c9365f508af1d8e0b9ab7c09d76.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/btrfs/inode.c | 30 +++++++++++++++++++++++++++++-
 1 file changed, 29 insertions(+), 1 deletion(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index a78a7206a291..e864006296dd 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -7704,6 +7704,16 @@ struct extent_map *btrfs_create_io_em(struct btrfs_inode *inode, u64 start,
 	if (file_extent->fscrypt_info) {
 		btrfs_extent_map_set_encryption(em, BTRFS_ENCRYPTION_FSCRYPT);
 		em->fscrypt_info = fscrypt_get_extent_info(file_extent->fscrypt_info);
+	} else if (IS_ENCRYPTED(&inode->vfs_inode)) {
+		struct fscrypt_extent_info *fscrypt_info;
+
+		btrfs_extent_map_set_encryption(em, BTRFS_ENCRYPTION_FSCRYPT);
+		fscrypt_info = fscrypt_prepare_new_extent(&inode->vfs_inode);
+		if (IS_ERR(fscrypt_info)) {
+			btrfs_free_extent_map(em);
+			return ERR_CAST(fscrypt_info);
+		}
+		em->fscrypt_info = fscrypt_info;
 	} else {
 		btrfs_extent_map_set_encryption(em, BTRFS_ENCRYPTION_NONE);
 	}
@@ -9423,6 +9433,9 @@ static int __btrfs_prealloc_file_range(struct inode *inode, int mode,
 	if (trans)
 		own_trans = false;
 	while (num_bytes > 0) {
+		struct fscrypt_extent_info *fscrypt_info = NULL;
+		int encryption_type = BTRFS_ENCRYPTION_NONE;
+
 		cur_bytes = min_t(u64, num_bytes, SZ_256M);
 		cur_bytes = max(cur_bytes, min_size);
 		/*
@@ -9437,6 +9450,17 @@ static int __btrfs_prealloc_file_range(struct inode *inode, int mode,
 		if (ret)
 			break;
 
+		if (IS_ENCRYPTED(inode)) {
+			fscrypt_info = fscrypt_prepare_new_extent(inode);
+			if (IS_ERR(fscrypt_info)) {
+				btrfs_dec_block_group_reservations(fs_info, ins.objectid);
+				btrfs_free_reserved_extent(fs_info, ins.objectid, ins.offset, 0);
+				ret = PTR_ERR(fscrypt_info);
+				break;
+			}
+			encryption_type = BTRFS_ENCRYPTION_FSCRYPT;
+		}
+
 		/*
 		 * We've reserved this space, and thus converted it from
 		 * ->bytes_may_use to ->bytes_reserved.  Any error that happens
@@ -9448,7 +9472,7 @@ static int __btrfs_prealloc_file_range(struct inode *inode, int mode,
 
 		last_alloc = ins.offset;
 		trans = insert_prealloc_file_extent(trans, BTRFS_I(inode),
-						    &ins, NULL, cur_offset);
+						    &ins, fscrypt_info, cur_offset);
 		/*
 		 * Now that we inserted the prealloc extent we can finally
 		 * decrement the number of reservations in the block group.
@@ -9458,6 +9482,7 @@ static int __btrfs_prealloc_file_range(struct inode *inode, int mode,
 		btrfs_dec_block_group_reservations(fs_info, ins.objectid);
 		if (IS_ERR(trans)) {
 			ret = PTR_ERR(trans);
+			fscrypt_put_extent_info(fscrypt_info);
 			btrfs_free_reserved_extent(fs_info, ins.objectid,
 						   ins.offset, false);
 			break;
@@ -9465,6 +9490,7 @@ static int __btrfs_prealloc_file_range(struct inode *inode, int mode,
 
 		em = btrfs_alloc_extent_map();
 		if (!em) {
+			fscrypt_put_extent_info(fscrypt_info);
 			btrfs_drop_extent_map_range(BTRFS_I(inode), cur_offset,
 					    cur_offset + ins.offset - 1, false);
 			btrfs_set_inode_full_sync(BTRFS_I(inode));
@@ -9479,6 +9505,8 @@ static int __btrfs_prealloc_file_range(struct inode *inode, int mode,
 		em->ram_bytes = ins.offset;
 		em->flags |= EXTENT_FLAG_PREALLOC;
 		em->generation = trans->transid;
+		em->fscrypt_info = fscrypt_info;
+		btrfs_extent_map_set_encryption(em, encryption_type);
 
 		ret = btrfs_replace_extent_map_range(BTRFS_I(inode), em, true);
 		btrfs_free_extent_map(em);
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 34/43] btrfs: add test_dummy_encryption support
From: Daniel Vacek @ 2026-05-13  8:53 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Josef Bacik <josef@toxicpanda.com>

In order to enable more thorough testing of fscrypt enable the
test_dummy_encryption mount option.  This is used by fscrypt users to
easily enable fscrypt on the file system for testing without needing to
do the key setup and everything.

The only deviation from other file systems we make is we only support
the fsparam_flag version of this mount option, as it defaults to v2.  We
don't want to have to bother with rejecting v1 related mount options.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/77449ee5a882db2945429946c74ea7e796122328.1706116485.git.josef@toxicpanda.com/
 * No changes since.  Just re-wrapping.  We now accept longer lines.
---
 fs/btrfs/disk-io.c |  1 +
 fs/btrfs/fs.h      |  3 +++
 fs/btrfs/fscrypt.c |  6 +++++
 fs/btrfs/super.c   | 58 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 68 insertions(+)

diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index 8a11be02eeb9..1f0fe29dd549 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -1230,6 +1230,7 @@ void btrfs_free_fs_info(struct btrfs_fs_info *fs_info)
 	btrfs_extent_buffer_leak_debug_check(fs_info);
 	kfree(fs_info->super_copy);
 	kfree(fs_info->super_for_commit);
+	fscrypt_free_dummy_policy(&fs_info->dummy_enc_policy);
 	kvfree(fs_info);
 }
 
diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h
index dbdb73722c14..b21909d37b56 100644
--- a/fs/btrfs/fs.h
+++ b/fs/btrfs/fs.h
@@ -28,6 +28,7 @@
 #include <linux/rbtree.h>
 #include <linux/xxhash.h>
 #include <linux/fserror.h>
+#include <linux/fscrypt.h>
 #include <uapi/linux/btrfs.h>
 #include <uapi/linux/btrfs_tree.h>
 #include "extent-io-tree.h"
@@ -270,6 +271,7 @@ enum {
 	BTRFS_MOUNT_IGNOREMETACSUMS		= (1ULL << 31),
 	BTRFS_MOUNT_IGNORESUPERFLAGS		= (1ULL << 32),
 	BTRFS_MOUNT_REF_TRACKER			= (1ULL << 33),
+	BTRFS_MOUNT_TEST_DUMMY_ENCRYPTION	= (1ULL << 34),
 };
 
 /* These mount options require a full read-only fs, no new transaction is allowed. */
@@ -958,6 +960,7 @@ struct btrfs_fs_info {
 	spinlock_t eb_leak_lock;
 	struct list_head allocated_ebs;
 #endif
+	struct fscrypt_dummy_policy dummy_enc_policy;
 };
 
 #define folio_to_inode(_folio)	(BTRFS_I(_Generic((_folio),			\
diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index 924ee3df7f32..111ca92a3450 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -240,6 +240,11 @@ static blk_status_t btrfs_process_encrypted_bio(struct bio *orig_bio,
 	return btrfs_csum_one_bio(bbio, enc_bio, false);
 }
 
+static const union fscrypt_policy *btrfs_get_dummy_policy(struct super_block *sb)
+{
+	return btrfs_sb(sb)->dummy_enc_policy.policy;
+}
+
 int btrfs_fscrypt_load_extent_info(struct btrfs_inode *inode,
 				   struct btrfs_path *path,
 				   struct btrfs_key *key,
@@ -356,4 +361,5 @@ const struct fscrypt_operations btrfs_fscrypt_ops = {
 	.empty_dir = btrfs_fscrypt_empty_dir,
 	.get_devices = btrfs_fscrypt_get_devices,
 	.process_bio = btrfs_process_encrypted_bio,
+	.get_dummy_policy = btrfs_get_dummy_policy,
 };
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index 84df97363611..2c033c68f052 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -87,6 +87,7 @@ struct btrfs_fs_context {
 	unsigned long compress_type:4;
 	int compress_level;
 	refcount_t refs;
+	struct fscrypt_dummy_policy dummy_enc_policy;
 };
 
 static void btrfs_emit_options(struct btrfs_fs_info *info,
@@ -125,6 +126,7 @@ enum {
 	Opt_treelog,
 	Opt_user_subvol_rm_allowed,
 	Opt_norecovery,
+	Opt_test_dummy_encryption,
 
 	/* Rescue options */
 	Opt_rescue,
@@ -259,6 +261,8 @@ static const struct fs_parameter_spec btrfs_fs_parameters[] = {
 	fsparam_enum("fragment", Opt_fragment, btrfs_parameter_fragment),
 	fsparam_flag("ref_tracker", Opt_ref_tracker),
 	fsparam_flag("ref_verify", Opt_ref_verify),
+	fsparam_flag("test_dummy_encryption", Opt_test_dummy_encryption),
+	fsparam_string("test_dummy_encryption", Opt_test_dummy_encryption),
 #endif
 	{}
 };
@@ -651,6 +655,23 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
 	case Opt_ref_tracker:
 		btrfs_set_opt(ctx->mount_opt, REF_TRACKER);
 		break;
+	case Opt_test_dummy_encryption:
+		int ret;
+
+		/*
+		 * We only support v2, so reject any v1 policies.
+		 */
+		if (param->type == fs_value_is_string && *param->string &&
+		    !strcmp(param->string, "v1")) {
+			btrfs_info(NULL, "v1 encryption isn't supported");
+			return -EINVAL;
+		}
+
+		btrfs_set_opt(ctx->mount_opt, TEST_DUMMY_ENCRYPTION);
+		ret = fscrypt_parse_test_dummy_encryption(param, &ctx->dummy_enc_policy);
+		if (ret)
+			return ret;
+		break;
 #endif
 	default:
 		btrfs_err(NULL, "unrecognized mount option '%s'", param->key);
@@ -986,6 +1007,9 @@ static int btrfs_fill_super(struct super_block *sb,
 		return ret;
 	}
 
+	if (fscrypt_is_dummy_policy_set(&fs_info->dummy_enc_policy))
+		btrfs_set_fs_incompat(fs_info, ENCRYPT);
+
 	btrfs_emit_options(fs_info, NULL);
 
 	inode = btrfs_iget(BTRFS_FIRST_FREE_OBJECTID, fs_info->fs_root);
@@ -1150,6 +1174,8 @@ static int btrfs_show_options(struct seq_file *seq, struct dentry *dentry)
 		seq_puts(seq, ",ref_verify");
 	if (btrfs_test_opt(info, REF_TRACKER))
 		seq_puts(seq, ",ref_tracker");
+	if (btrfs_test_opt(info, TEST_DUMMY_ENCRYPTION))
+		fscrypt_show_test_dummy_encryption(seq, ',', dentry->d_sb);
 	seq_printf(seq, ",subvolid=%llu", btrfs_root_id(BTRFS_I(d_inode(dentry))->root));
 	subvol_name = btrfs_get_subvol_name_from_objectid(info,
 			btrfs_root_id(BTRFS_I(d_inode(dentry))->root));
@@ -1415,6 +1441,18 @@ static void btrfs_ctx_to_info(struct btrfs_fs_info *fs_info, struct btrfs_fs_con
 	fs_info->mount_opt = ctx->mount_opt;
 	fs_info->compress_type = ctx->compress_type;
 	fs_info->compress_level = ctx->compress_level;
+
+	/*
+	 * If there's nothing set, or if the fs_info already has one set, don't
+	 * do anything.  If the fs_info is set we'll free the dummy one when we
+	 * free the ctx.
+	 */
+	if (!fscrypt_is_dummy_policy_set(&ctx->dummy_enc_policy) ||
+	    fscrypt_is_dummy_policy_set(&fs_info->dummy_enc_policy))
+		return;
+
+	fs_info->dummy_enc_policy = ctx->dummy_enc_policy;
+	memset(&ctx->dummy_enc_policy, 0, sizeof(ctx->dummy_enc_policy));
 }
 
 static void btrfs_info_to_ctx(struct btrfs_fs_info *fs_info, struct btrfs_fs_context *ctx)
@@ -1468,6 +1506,7 @@ static void btrfs_emit_options(struct btrfs_fs_info *info,
 	btrfs_info_if_set(info, old, IGNOREDATACSUMS, "ignoring data csums");
 	btrfs_info_if_set(info, old, IGNOREMETACSUMS, "ignoring meta csums");
 	btrfs_info_if_set(info, old, IGNORESUPERFLAGS, "ignoring unknown super block flags");
+	btrfs_info_if_set(info, old, TEST_DUMMY_ENCRYPTION, "test dummy encryption mode enabled");
 
 	btrfs_info_if_unset(info, old, NODATASUM, "setting datasum");
 	btrfs_info_if_unset(info, old, NODATACOW, "setting datacow");
@@ -1498,6 +1537,21 @@ static void btrfs_emit_options(struct btrfs_fs_info *info,
 		btrfs_info(info, "max_inline set to %llu", info->max_inline);
 }
 
+static bool btrfs_check_test_dummy_encryption(struct fs_context *fc)
+{
+	struct btrfs_fs_context *ctx = fc->fs_private;
+	struct btrfs_fs_info *fs_info = btrfs_sb(fc->root->d_sb);
+
+	if (!fscrypt_is_dummy_policy_set(&ctx->dummy_enc_policy))
+		return true;
+
+	if (fscrypt_dummy_policies_equal(&fs_info->dummy_enc_policy, &ctx->dummy_enc_policy))
+		return true;
+
+	btrfs_warn(fs_info, "Can't set or change test_dummy_encryption on remount");
+	return false;
+}
+
 static int btrfs_reconfigure(struct fs_context *fc)
 {
 	struct super_block *sb = fc->root->d_sb;
@@ -1523,6 +1577,9 @@ static int btrfs_reconfigure(struct fs_context *fc)
 	if (!btrfs_check_options(fs_info, &ctx->mount_opt, fc->sb_flags))
 		return -EINVAL;
 
+	if (!mount_reconfigure && !btrfs_check_test_dummy_encryption(fc))
+		return -EINVAL;
+
 	ret = btrfs_check_features(fs_info, !(fc->sb_flags & SB_RDONLY));
 	if (ret < 0)
 		return ret;
@@ -2139,6 +2196,7 @@ static void btrfs_free_fs_context(struct fs_context *fc)
 		btrfs_free_fs_info(fs_info);
 
 	if (ctx && refcount_dec_and_test(&ctx->refs)) {
+		fscrypt_free_dummy_policy(&ctx->dummy_enc_policy);
 		kfree(ctx->subvol_name);
 		kfree(ctx);
 	}
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 28/43] btrfs: populate ordered_extent with the orig offset
From: Daniel Vacek @ 2026-05-13  8:53 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Josef Bacik <josef@toxicpanda.com>

For extent encryption we have to use a logical block nr as input for the
IV.  For btrfs we're using the offset into the extent we're operating
on.  For most ordered extents this is the same as the file_offset,
however for prealloc and NOCOW we have to use the original offset.

Add this as an argument and plumb it through everywhere, this will be
used when setting up the bio.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

No changes in v7.
v6 changes:
 * Splitted the dio-related hunks from inode.c to direct-io.c as upstream
   refactored in the meantime.
 * Open-code orig_start to start - offset.
v5: https://lore.kernel.org/linux-btrfs/fe06053fe2973c424dd539fecfee8cc171bdd22d.1706116485.git.josef@toxicpanda.com/
---
 fs/btrfs/direct-io.c    |  1 +
 fs/btrfs/inode.c        |  5 +++++
 fs/btrfs/ordered-data.c | 21 ++++++++++++++++++---
 fs/btrfs/ordered-data.h |  7 +++++++
 4 files changed, 31 insertions(+), 3 deletions(-)

diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index f87656db6157..e8b1ce4a3a11 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -155,6 +155,7 @@ static struct extent_map *btrfs_create_dio_extent(struct btrfs_inode *inode,
 		file_extent->fscrypt_info = em->fscrypt_info;
 	}
 
+	file_extent->orig_offset = start - file_extent->offset;
 	ordered = btrfs_alloc_ordered_extent(inode, start, file_extent,
 					     (1U << type) |
 					     (1U << BTRFS_ORDERED_DIRECT));
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index e864006296dd..8a9f80c8364e 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -1123,6 +1123,7 @@ static void submit_one_async_extent(struct async_chunk *async_chunk,
 	}
 
 	file_extent.fscrypt_info = em->fscrypt_info;
+	file_extent.orig_offset = start;
 	ordered = btrfs_alloc_ordered_extent(inode, start, &file_extent,
 					     1U << BTRFS_ORDERED_COMPRESSED);
 	btrfs_free_extent_map(em);
@@ -1266,6 +1267,7 @@ static int cow_one_range(struct btrfs_inode *inode, struct folio *locked_folio,
 	}
 
 	file_extent.fscrypt_info = em->fscrypt_info;
+	file_extent.orig_offset = file_offset;
 	ordered = btrfs_alloc_ordered_extent(inode, file_offset, &file_extent,
 					     1U << BTRFS_ORDERED_REGULAR);
 	btrfs_free_extent_map(em);
@@ -2208,6 +2210,8 @@ static noinline int run_delalloc_nocow(struct btrfs_inode *inode,
 			cow_start = (u64)-1;
 		}
 
+		nocow_args.file_extent.orig_offset =
+			found_key.offset - nocow_args.file_extent.offset;
 		ret = nocow_one_range(inode, locked_folio, &cached_state,
 				      &nocow_args, cur_offset,
 				      extent_type == BTRFS_FILE_EXTENT_PREALLOC);
@@ -10302,6 +10306,7 @@ ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from,
 	}
 
 	file_extent.fscrypt_info = em->fscrypt_info;
+	file_extent.orig_offset = start - encoded->unencoded_offset;
 	ordered = btrfs_alloc_ordered_extent(inode, start, &file_extent,
 				       (1U << BTRFS_ORDERED_ENCODED) |
 				       (1U << BTRFS_ORDERED_COMPRESSED));
diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
index 5a9bb0e8a18c..f796dd7ea925 100644
--- a/fs/btrfs/ordered-data.c
+++ b/fs/btrfs/ordered-data.c
@@ -146,7 +146,8 @@ static inline struct rb_node *ordered_tree_search(struct btrfs_inode *inode,
 }
 
 static struct btrfs_ordered_extent *alloc_ordered_extent(
-			struct btrfs_inode *inode, u64 file_offset, u64 num_bytes,
+			struct btrfs_inode *inode,
+			u64 file_offset, u64 orig_offset, u64 num_bytes,
 			u64 ram_bytes, u64 disk_bytenr, u64 disk_num_bytes,
 			u64 offset, unsigned long flags, int compress_type,
 			struct fscrypt_extent_info *fscrypt_info)
@@ -193,6 +194,7 @@ static struct btrfs_ordered_extent *alloc_ordered_extent(
 	}
 
 	entry->file_offset = file_offset;
+	entry->orig_offset = orig_offset;
 	entry->num_bytes = num_bytes;
 	entry->ram_bytes = ram_bytes;
 	entry->disk_bytenr = disk_bytenr;
@@ -286,6 +288,7 @@ static void insert_ordered_extent(struct btrfs_ordered_extent *entry)
  *
  * @inode:           Inode that this extent is for.
  * @file_offset:     Logical offset in file where the extent starts.
+ * @orig_offset:     Logical offset of the original extent (PREALLOC or NOCOW)
  * @num_bytes:       Logical length of extent in file.
  * @ram_bytes:       Full length of unencoded data.
  * @disk_bytenr:     Offset of extent on disk.
@@ -323,6 +326,7 @@ struct btrfs_ordered_extent *btrfs_alloc_ordered_extent(
 	 */
 	if (flags & ((1U << BTRFS_ORDERED_NOCOW) | (1U << BTRFS_ORDERED_PREALLOC)))
 		entry = alloc_ordered_extent(inode, file_offset,
+					     file_extent->orig_offset,
 					     file_extent->num_bytes,
 					     file_extent->num_bytes,
 					     file_extent->disk_bytenr + file_extent->offset,
@@ -331,6 +335,7 @@ struct btrfs_ordered_extent *btrfs_alloc_ordered_extent(
 					     file_extent->fscrypt_info);
 	else
 		entry = alloc_ordered_extent(inode, file_offset,
+					     file_extent->orig_offset,
 					     file_extent->num_bytes,
 					     file_extent->ram_bytes,
 					     file_extent->disk_bytenr,
@@ -1276,8 +1281,8 @@ struct btrfs_ordered_extent *btrfs_split_ordered_extent(
 	if (WARN_ON_ONCE(ordered->disk_num_bytes != ordered->num_bytes))
 		return ERR_PTR(-EINVAL);
 
-	new = alloc_ordered_extent(inode, file_offset, len, len, disk_bytenr, len, 0,
-				   flags, ordered->compress_type, ordered->fscrypt_info);
+	new = alloc_ordered_extent(inode, file_offset, ordered->orig_offset, len, len, disk_bytenr,
+				   len, 0, flags, ordered->compress_type, ordered->fscrypt_info);
 	if (IS_ERR(new))
 		return new;
 
@@ -1314,6 +1319,16 @@ struct btrfs_ordered_extent *btrfs_split_ordered_extent(
 	ordered->disk_num_bytes -= len;
 	ordered->ram_bytes -= len;
 
+	/*
+	 * ->orig_offset is the original offset of the original extent, which
+	 * for PREALLOC or NOCOW stays the same, but if we're a regular extent
+	 * that means this is a new extent and thus ->orig_offset must equal
+	 * ->file_offset.  This is only important for encryption as we only use
+	 * it for setting the offset for the bio encryption context.
+	 */
+	if (test_bit(BTRFS_ORDERED_REGULAR, &ordered->flags))
+		ordered->orig_offset = ordered->file_offset;
+
 	if (test_bit(BTRFS_ORDERED_IO_DONE, &ordered->flags)) {
 		ASSERT(ordered->bytes_left == 0);
 		new->bytes_left = 0;
diff --git a/fs/btrfs/ordered-data.h b/fs/btrfs/ordered-data.h
index a3514a4d9ea1..f4ad5ff1a1e2 100644
--- a/fs/btrfs/ordered-data.h
+++ b/fs/btrfs/ordered-data.h
@@ -106,6 +106,12 @@ struct btrfs_ordered_extent {
 	/* logical offset in the file */
 	u64 file_offset;
 
+	/*
+	 * The original logical offset of the extent, this is for NOCOW and
+	 * PREALLOC extents, otherwise it'll be the same as file_offset.
+	 */
+	u64 orig_offset;
+
 	/*
 	 * These fields directly correspond to the same fields in
 	 * btrfs_file_extent_item.
@@ -192,6 +198,7 @@ struct btrfs_file_extent {
 	u64 num_bytes;
 	u64 ram_bytes;
 	u64 offset;
+	u64 orig_offset;
 	struct fscrypt_extent_info *fscrypt_info;
 	u8 compression;
 };
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 26/43] btrfs: implement the fscrypt extent encryption hooks
From: Daniel Vacek @ 2026-05-13  8:53 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Josef Bacik <josef@toxicpanda.com>

This patch implements the necessary hooks from fscrypt to support
per-extent encryption.  There's two main entry points

btrfs_fscrypt_load_extent_info
btrfs_fscrypt_save_extent_info

btrfs_fscrypt_load_extent_info gets called when we create the extent
maps from the file extent item at btrfs_get_extent() time.  We read the
extent context, and pass it into fscrypt to create the appropriate
fscrypt_extent_info structure.  This is then used on the bio's to make
sure the encryption is done properly.

btrfs_fscrypt_save_extent_info is used to generate the fscrypt context
from fscrypt and save it into the tree item when we create a new
file extent item.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

No changes in v7.
v6 changes:
 * Significantly reworked due to the changes in previous commit [24/43].
v5: https://lore.kernel.org/linux-btrfs/30eaad31964c88c3497a0c5bc8f2c727c1dc763a.1706116485.git.josef@toxicpanda.com/
---
 fs/btrfs/ctree.h    |  3 ++
 fs/btrfs/defrag.c   |  6 ++++
 fs/btrfs/file.c     | 11 ++++++
 fs/btrfs/fscrypt.c  | 84 +++++++++++++++++++++++++++++++++++++++++++++
 fs/btrfs/fscrypt.h  | 29 ++++++++++++++++
 fs/btrfs/inode.c    | 36 +++++++++++++++++++
 fs/btrfs/reflink.c  | 43 ++++++++++++++++++++++-
 fs/btrfs/tree-log.c | 19 ++++++++++
 8 files changed, 230 insertions(+), 1 deletion(-)

diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h
index 6de7ad191e04..89d3c3137786 100644
--- a/fs/btrfs/ctree.h
+++ b/fs/btrfs/ctree.h
@@ -400,6 +400,9 @@ struct btrfs_replace_extent_info {
 	u64 file_offset;
 	/* Pointer to a file extent item of type regular or prealloc. */
 	char *extent_buf;
+	/* The fscrypt_extent_info for a new extent. */
+	u8 *fscrypt_ctx;
+	u32 fscrypt_context_size;
 	/*
 	 * Set to true when attempting to replace a file range with a new extent
 	 * described by this structure, set to false when attempting to clone an
diff --git a/fs/btrfs/defrag.c b/fs/btrfs/defrag.c
index 7e2db5d3a4d4..a828ec45bb5f 100644
--- a/fs/btrfs/defrag.c
+++ b/fs/btrfs/defrag.c
@@ -16,6 +16,7 @@
 #include "file-item.h"
 #include "super.h"
 #include "compression.h"
+#include "fscrypt.h"
 
 static struct kmem_cache *btrfs_inode_defrag_cachep;
 
@@ -720,6 +721,11 @@ static struct extent_map *defrag_get_extent(struct btrfs_inode *inode,
 		if (ret > 0)
 			goto not_found;
 	}
+	btrfs_release_path(&path);
+
+	ret = btrfs_fscrypt_load_extent_info(inode, &path, &key, em);
+	if (ret)
+		goto err;
 	return em;
 
 not_found:
diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index fe7baecbf83b..b845556d2100 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -38,6 +38,7 @@
 #include "file.h"
 #include "super.h"
 #include "print-tree.h"
+#include "fscrypt.h"
 
 /*
  * Unlock folio after btrfs_file_write() is done with it.
@@ -2406,6 +2407,16 @@ static int btrfs_insert_replace_extent(struct btrfs_trans_handle *trans,
 	if (extent_info->is_new_extent)
 		btrfs_set_file_extent_generation(leaf, extent, trans->transid);
 	btrfs_release_path(path);
+	if (extent_info->fscrypt_context_size) {
+		key.type = BTRFS_FSCRYPT_CTX_KEY;
+		ret = btrfs_insert_empty_item(trans, root, path, &key,
+					      extent_info->fscrypt_context_size);
+		if (ret)
+			return ret;
+		btrfs_fscrypt_save_extent_info(path,
+					       extent_info->fscrypt_ctx,
+					       extent_info->fscrypt_context_size);
+	}
 
 	ret = btrfs_inode_set_file_extent_range(inode, extent_info->file_offset,
 						replace_len);
diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index 1d29cfa7037c..5d4802fce7eb 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -212,9 +212,93 @@ static struct block_device **btrfs_fscrypt_get_devices(struct super_block *sb,
 	return devs;
 }
 
+int btrfs_fscrypt_load_extent_info(struct btrfs_inode *inode,
+				   struct btrfs_path *path,
+				   struct btrfs_key *key,
+				   struct extent_map *em)
+{
+	struct extent_buffer *leaf;
+	int slot;
+	unsigned long offset;
+	u8 ctx[BTRFS_MAX_EXTENT_CTX_SIZE];
+	unsigned long size;
+	struct fscrypt_extent_info *info;
+	unsigned long nofs_flag;
+	int ret;
+
+	if (em->disk_bytenr == EXTENT_MAP_HOLE)
+		return 0;
+	if (btrfs_extent_map_encryption(em) != BTRFS_ENCRYPTION_FSCRYPT)
+		return 0;
+
+	key->type = BTRFS_FSCRYPT_CTX_KEY;
+	ret = btrfs_search_slot(NULL, inode->root, key, path, 0, 0);
+	leaf = path->nodes[0];
+	slot = path->slots[0];
+	if (ret) {
+		btrfs_err(leaf->fs_info,
+	"missing or error searching encryption context item in leaf: root=%llu block=%llu slot=%d ino=%llu file_offset=%llu, err %i",
+			  btrfs_header_owner(leaf), btrfs_header_bytenr(leaf), slot,
+			  key->objectid, key->offset, ret);
+		btrfs_release_path(path);
+		return ret;
+	}
+
+	size = btrfs_item_size(leaf, slot);
+	if (size > FSCRYPT_SET_CONTEXT_MAX_SIZE) {
+		btrfs_err(leaf->fs_info,
+	"unexpected or corrupted encryption context size in leaf: root=%llu block=%llu slot=%d ino=%llu file_offset=%llu, size %lu (too big)",
+			  btrfs_header_owner(leaf), btrfs_header_bytenr(leaf), slot,
+			  key->objectid, key->offset, size);
+		btrfs_release_path(path);
+		return -EIO;
+	}
+
+	offset = btrfs_item_ptr_offset(leaf, slot),
+	read_extent_buffer(leaf, ctx, offset, size);
+	btrfs_release_path(path);
+
+	nofs_flag = memalloc_nofs_save();
+	info = fscrypt_load_extent_info(&inode->vfs_inode, ctx, size);
+	memalloc_nofs_restore(nofs_flag);
+	if (IS_ERR(info))
+		return PTR_ERR(info);
+	em->fscrypt_info = info;
+	return 0;
+}
+
+void btrfs_fscrypt_save_extent_info(struct btrfs_path *path, u8 *ctx, unsigned long size)
+{
+	struct extent_buffer *leaf = path->nodes[0];
+	unsigned long offset = btrfs_item_ptr_offset(leaf, path->slots[0]);
+
+	ASSERT(size <= FSCRYPT_SET_CONTEXT_MAX_SIZE);
+
+	write_extent_buffer(leaf, ctx, offset, size);
+	btrfs_release_path(path);
+}
+
+ssize_t btrfs_fscrypt_context_for_new_extent(struct btrfs_inode *inode,
+					     struct fscrypt_extent_info *info,
+					     u8 *ctx)
+{
+	ssize_t ret;
+
+	if (!info)
+		return 0;
+
+	ret = fscrypt_context_for_new_extent(&inode->vfs_inode, info, ctx);
+	if (ret < 0) {
+		btrfs_err_rl(inode->root->fs_info, "invalid encrypt context");
+		return ret;
+	}
+	return ret;
+}
+
 const struct fscrypt_operations btrfs_fscrypt_ops = {
 	.inode_info_offs = (int)offsetof(struct btrfs_inode, i_crypt_info) -
 			   (int)offsetof(struct btrfs_inode, vfs_inode),
+	.has_per_extent_encryption = 1,
 	.get_context = btrfs_fscrypt_get_context,
 	.set_context = btrfs_fscrypt_set_context,
 	.empty_dir = btrfs_fscrypt_empty_dir,
diff --git a/fs/btrfs/fscrypt.h b/fs/btrfs/fscrypt.h
index c5cdc27f943c..68eab4606935 100644
--- a/fs/btrfs/fscrypt.h
+++ b/fs/btrfs/fscrypt.h
@@ -16,8 +16,27 @@ int btrfs_fscrypt_get_disk_name(struct extent_buffer *leaf,
 bool btrfs_fscrypt_match_name(struct fscrypt_name *fname,
 			      struct extent_buffer *leaf,
 			      unsigned long de_name, u32 de_name_len);
+int btrfs_fscrypt_load_extent_info(struct btrfs_inode *inode,
+				   struct btrfs_path *path,
+				   struct btrfs_key *key,
+				   struct extent_map *em);
+void btrfs_fscrypt_save_extent_info(struct btrfs_path *path, u8 *ctx, unsigned long size);
+ssize_t btrfs_fscrypt_context_for_new_extent(struct btrfs_inode *inode,
+					     struct fscrypt_extent_info *info,
+					     u8 *ctx);
 
 #else
+static inline void btrfs_fscrypt_save_extent_info(struct btrfs_path *path,
+						  u8 *ctx, unsigned long size) { }
+
+static inline int btrfs_fscrypt_load_extent_info(struct btrfs_inode *inode,
+						 struct btrfs_path *path,
+						 struct btrfs_key *key,
+						 struct extent_map *em)
+{
+	return 0;
+}
+
 static inline int btrfs_fscrypt_get_disk_name(struct extent_buffer *leaf,
 					      struct btrfs_dir_item *di,
 					      struct fscrypt_str *qstr)
@@ -34,6 +53,16 @@ static inline bool btrfs_fscrypt_match_name(struct fscrypt_name *fname,
 		return false;
 	return !memcmp_extent_buffer(leaf, fname->disk_name.name, de_name, de_name_len);
 }
+
+static inline ssize_t btrfs_fscrypt_context_for_new_extent(struct btrfs_inode *inode,
+							   struct fscrypt_extent_info *info,
+							   u8 *ctx)
+{
+	if (!info)
+		return 0;
+	return -EINVAL;
+}
+
 #endif /* CONFIG_FS_ENCRYPTION */
 
 extern const struct fscrypt_operations btrfs_fscrypt_ops;
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index cdd2d7bc50cc..a78a7206a291 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -3074,8 +3074,16 @@ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans,
 	u64 num_bytes = btrfs_stack_file_extent_num_bytes(stack_fi);
 	u64 ram_bytes = btrfs_stack_file_extent_ram_bytes(stack_fi);
 	struct btrfs_drop_extents_args drop_args = { 0 };
+	u8 fscrypt_ctx[FSCRYPT_SET_CONTEXT_MAX_SIZE];
+	ssize_t fscrypt_context_size;
 	int ret;
 
+	fscrypt_context_size = btrfs_fscrypt_context_for_new_extent(inode,
+								    fscrypt_info,
+								    fscrypt_ctx);
+	if (fscrypt_context_size < 0)
+		return (int)fscrypt_context_size;
+
 	path = btrfs_alloc_path();
 	if (!path)
 		return -ENOMEM;
@@ -3116,6 +3124,16 @@ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans,
 
 	btrfs_release_path(path);
 
+	if (fscrypt_context_size) {
+		ins.objectid = btrfs_ino(inode);
+		ins.type = BTRFS_FSCRYPT_CTX_KEY;
+		ins.offset = file_pos;
+		ret = btrfs_insert_empty_item(trans, root, path, &ins, fscrypt_context_size);
+		if (ret)
+			return ret;
+		btrfs_fscrypt_save_extent_info(path, fscrypt_ctx, fscrypt_context_size);
+	}
+
 	/*
 	 * If we dropped an inline extent here, we know the range where it is
 	 * was not marked with the EXTENT_DELALLOC_NEW bit, so we update the
@@ -7481,6 +7499,12 @@ struct extent_map *btrfs_get_extent(struct btrfs_inode *inode,
 		goto out;
 	}
 
+	ret = btrfs_fscrypt_load_extent_info(inode, path, &found_key, em);
+	if (ret > 0)
+		ret = -EIO;
+	if (ret < 0)
+		goto out;
+
 	write_lock(&em_tree->lock);
 	ret = btrfs_add_extent_mapping(inode, &em, start, len);
 	write_unlock(&em_tree->lock);
@@ -9294,6 +9318,8 @@ static struct btrfs_trans_handle *insert_prealloc_file_extent(
 				       u64 file_offset)
 {
 	struct btrfs_file_extent_item stack_fi;
+	u8 fscrypt_ctx[FSCRYPT_SET_CONTEXT_MAX_SIZE];
+	ssize_t fscrypt_context_size;
 	struct btrfs_replace_extent_info extent_info;
 	struct btrfs_trans_handle *trans = trans_in;
 	struct btrfs_path *path;
@@ -9327,6 +9353,14 @@ static struct btrfs_trans_handle *insert_prealloc_file_extent(
 		return trans;
 	}
 
+	fscrypt_context_size = btrfs_fscrypt_context_for_new_extent(inode,
+								    fscrypt_info,
+								    fscrypt_ctx);
+	if (fscrypt_context_size < 0) {
+		ret = (int)fscrypt_context_size;
+		goto free_qgroup;
+	}
+
 	extent_info.disk_offset = start;
 	extent_info.disk_len = len;
 	extent_info.data_offset = 0;
@@ -9337,6 +9371,8 @@ static struct btrfs_trans_handle *insert_prealloc_file_extent(
 	extent_info.update_times = true;
 	extent_info.qgroup_reserved = qgroup_released;
 	extent_info.insertions = 0;
+	extent_info.fscrypt_ctx = fscrypt_ctx;
+	extent_info.fscrypt_context_size = fscrypt_context_size;
 
 	path = btrfs_alloc_path();
 	if (!path) {
diff --git a/fs/btrfs/reflink.c b/fs/btrfs/reflink.c
index 49865a463780..ff431c05cb38 100644
--- a/fs/btrfs/reflink.c
+++ b/fs/btrfs/reflink.c
@@ -421,7 +421,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode,
 		struct btrfs_key new_key;
 		u64 disko = 0, diskl = 0;
 		u64 datao = 0, datal = 0;
-		u8 comp;
+		u8 comp, encryption;
 		u64 drop_start;
 
 		/* Note the key will change type as we walk through the tree */
@@ -464,6 +464,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode,
 		extent = btrfs_item_ptr(leaf, slot,
 					struct btrfs_file_extent_item);
 		extent_gen = btrfs_file_extent_generation(leaf, extent);
+		encryption = btrfs_file_extent_encryption(leaf, extent);
 		comp = btrfs_file_extent_compression(leaf, extent);
 		type = btrfs_file_extent_type(leaf, extent);
 		if (type == BTRFS_FILE_EXTENT_REG ||
@@ -523,6 +524,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode,
 		if (type == BTRFS_FILE_EXTENT_REG ||
 		    type == BTRFS_FILE_EXTENT_PREALLOC) {
 			struct btrfs_replace_extent_info clone_info;
+			u8 fscrypt_ctx[FSCRYPT_SET_CONTEXT_MAX_SIZE];
 
 			/*
 			 *    a  | --- range to clone ---|  b
@@ -539,6 +541,43 @@ static int btrfs_clone(struct inode *src, struct inode *inode,
 				datal -= off - key.offset;
 			}
 
+			if (encryption == BTRFS_ENCRYPTION_FSCRYPT) {
+				unsigned long offset;
+
+				key.type = BTRFS_FSCRYPT_CTX_KEY;
+				ret = btrfs_search_slot(NULL, BTRFS_I(src)->root, &key, path, 0, 0);
+				if (ret < 0)
+					goto out;
+				leaf = path->nodes[0];
+				slot = path->slots[0];
+				if (ret) {
+					btrfs_err(leaf->fs_info,
+	"missing or error searching encryption context item in leaf: root=%llu block=%llu slot=%d ino=%llu file_offset=%llu, err %i",
+						btrfs_header_owner(leaf),
+						btrfs_header_bytenr(leaf), slot,
+						key.objectid, key.offset, ret);
+					goto out;
+				}
+
+				size = btrfs_item_size(leaf, slot);
+				if (size > FSCRYPT_SET_CONTEXT_MAX_SIZE) {
+					btrfs_err(leaf->fs_info,
+	"unexpected or corrupted encryption context size in leaf: root=%llu block=%llu slot=%d ino=%llu file_offset=%llu, size %u (too big)",
+						btrfs_header_owner(leaf),
+						btrfs_header_bytenr(leaf), slot,
+						key.objectid, key.offset, size);
+					ret = -EIO;
+					goto out;
+				}
+
+				offset = btrfs_item_ptr_offset(leaf, slot),
+				read_extent_buffer(leaf, fscrypt_ctx, offset, size);
+				btrfs_release_path(path);
+				key.type = BTRFS_EXTENT_DATA_KEY;
+			} else {
+				size = 0;
+			}
+
 			clone_info.disk_offset = disko;
 			clone_info.disk_len = diskl;
 			clone_info.data_offset = datao;
@@ -547,6 +586,8 @@ static int btrfs_clone(struct inode *src, struct inode *inode,
 			clone_info.extent_buf = buf;
 			clone_info.is_new_extent = false;
 			clone_info.update_times = !no_time_update;
+			clone_info.fscrypt_ctx = fscrypt_ctx;
+			clone_info.fscrypt_context_size = size;
 			ret = btrfs_replace_file_extents(BTRFS_I(inode), path,
 					drop_start, new_key.offset + datal - 1,
 					&clone_info, &trans);
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index 60aa763d2fa4..f3b839950855 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -30,6 +30,7 @@
 #include "print-tree.h"
 #include "tree-checker.h"
 #include "delayed-inode.h"
+#include "fscrypt.h"
 
 #define MAX_CONFLICT_INODES 10
 
@@ -5145,6 +5146,8 @@ static int log_one_extent(struct btrfs_trans_handle *trans,
 	struct btrfs_file_extent_item fi = { 0 };
 	struct extent_buffer *leaf;
 	struct btrfs_key key;
+	u8 fscrypt_ctx[FSCRYPT_SET_CONTEXT_MAX_SIZE];
+	ssize_t fscrypt_context_size;
 	enum btrfs_compression_type compress_type;
 	u64 extent_offset = em->offset;
 	u64 block_start = btrfs_extent_map_block_start(em);
@@ -5152,6 +5155,12 @@ static int log_one_extent(struct btrfs_trans_handle *trans,
 	int ret;
 	u8 encryption = btrfs_extent_map_encryption(em);
 
+	fscrypt_context_size = btrfs_fscrypt_context_for_new_extent(inode,
+								    em->fscrypt_info,
+								    fscrypt_ctx);
+	if (fscrypt_context_size < 0)
+		return (int)fscrypt_context_size;
+
 	btrfs_set_stack_file_extent_generation(&fi, trans->transid);
 	if (em->flags & EXTENT_FLAG_PREALLOC)
 		btrfs_set_stack_file_extent_type(&fi, BTRFS_FILE_EXTENT_PREALLOC);
@@ -5215,6 +5224,16 @@ static int log_one_extent(struct btrfs_trans_handle *trans,
 
 	btrfs_release_path(path);
 
+	if (fscrypt_context_size) {
+		key.objectid = btrfs_ino(inode);
+		key.type = BTRFS_FSCRYPT_CTX_KEY;
+		key.offset = em->start;
+		ret = btrfs_insert_empty_item(trans, log, path, &key, fscrypt_context_size);
+		if (ret)
+			return ret;
+		btrfs_fscrypt_save_extent_info(path, fscrypt_ctx, fscrypt_context_size);
+	}
+
 	return ret;
 }
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 30/43] btrfs: add a bio argument to btrfs_csum_one_bio
From: Daniel Vacek @ 2026-05-13  8:53 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Josef Bacik <josef@toxicpanda.com>

We only ever needed the bbio in btrfs_csum_one_bio, since that has the
bio embedded in it.  However with encryption we'll have a different bio
with the encrypted data in it, and the original bbio.  Update
btrfs_csum_one_bio to take the bio we're going to csum as an argument,
which will allow us to csum the encrypted bio and stuff the csums into
the corresponding bbio to be used later when the IO completes.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

No changes in v7.
v6 changes:
 * Adapted for async csums.  For writes we have to stick with sync
   csums.  Otherwise fscrypt would have to hand us the bounced bio
   instead of releasing it.  Though that is not implemented yet.
v5: https://lore.kernel.org/linux-btrfs/595a2e46dae3d18bceb5dfa60ac3ae23760fc800.1706116485.git.josef@toxicpanda.com/
---
 fs/btrfs/bio.c       |  4 ++--
 fs/btrfs/bio.h       |  1 +
 fs/btrfs/file-item.c | 13 ++++++-------
 fs/btrfs/file-item.h |  2 +-
 4 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
index 689cc41409e5..b79fc467445d 100644
--- a/fs/btrfs/bio.c
+++ b/fs/btrfs/bio.c
@@ -600,9 +600,9 @@ static int btrfs_bio_csum(struct btrfs_bio *bbio)
 	if (bbio->bio.bi_opf & REQ_META)
 		return btree_csum_one_bio(bbio);
 #ifdef CONFIG_BTRFS_EXPERIMENTAL
-	return btrfs_csum_one_bio(bbio, true);
+	return btrfs_csum_one_bio(bbio, &bbio->bio, true);
 #else
-	return btrfs_csum_one_bio(bbio, false);
+	return btrfs_csum_one_bio(bbio, &bbio->bio, false);
 #endif
 }
 
diff --git a/fs/btrfs/bio.h b/fs/btrfs/bio.h
index 303ed6c7103d..43f7544029ac 100644
--- a/fs/btrfs/bio.h
+++ b/fs/btrfs/bio.h
@@ -59,6 +59,7 @@ struct btrfs_bio {
 			struct btrfs_ordered_sum *sums;
 			struct work_struct csum_work;
 			struct completion csum_done;
+			struct bio *csum_bio;
 			struct bvec_iter csum_saved_iter;
 			u64 orig_physical;
 			u64 orig_logical;
diff --git a/fs/btrfs/file-item.c b/fs/btrfs/file-item.c
index 020d30709770..986914078708 100644
--- a/fs/btrfs/file-item.c
+++ b/fs/btrfs/file-item.c
@@ -771,11 +771,10 @@ int btrfs_lookup_csums_bitmap(struct btrfs_root *root, struct btrfs_path *path,
 	return ret;
 }
 
-static void csum_one_bio(struct btrfs_bio *bbio, struct bvec_iter *src)
+static void csum_one_bio(struct btrfs_bio *bbio, struct bio *bio, struct bvec_iter *src)
 {
 	struct btrfs_inode *inode = bbio->inode;
 	struct btrfs_fs_info *fs_info = inode->root->fs_info;
-	struct bio *bio = &bbio->bio;
 	struct btrfs_ordered_sum *sums = bbio->sums;
 	struct bvec_iter iter = *src;
 	phys_addr_t paddr;
@@ -803,19 +802,18 @@ static void csum_one_bio_work(struct work_struct *work)
 
 	ASSERT(btrfs_op(&bbio->bio) == BTRFS_MAP_WRITE);
 	ASSERT(bbio->async_csum == true);
-	csum_one_bio(bbio, &bbio->csum_saved_iter);
+	csum_one_bio(bbio, bbio->csum_bio, &bbio->csum_saved_iter);
 	complete(&bbio->csum_done);
 }
 
 /*
  * Calculate checksums of the data contained inside a bio.
  */
-int btrfs_csum_one_bio(struct btrfs_bio *bbio, bool async)
+int btrfs_csum_one_bio(struct btrfs_bio *bbio, struct bio *bio, bool async)
 {
 	struct btrfs_ordered_extent *ordered = bbio->ordered;
 	struct btrfs_inode *inode = bbio->inode;
 	struct btrfs_fs_info *fs_info = inode->root->fs_info;
-	struct bio *bio = &bbio->bio;
 	struct btrfs_ordered_sum *sums;
 	unsigned nofs_flag;
 
@@ -834,12 +832,13 @@ int btrfs_csum_one_bio(struct btrfs_bio *bbio, bool async)
 	btrfs_add_ordered_sum(ordered, sums);
 
 	if (!async) {
-		csum_one_bio(bbio, &bbio->bio.bi_iter);
+		csum_one_bio(bbio, bio, &bio->bi_iter);
 		return 0;
 	}
 	init_completion(&bbio->csum_done);
 	bbio->async_csum = true;
-	bbio->csum_saved_iter = bbio->bio.bi_iter;
+	bbio->csum_bio = bio;
+	bbio->csum_saved_iter = bio->bi_iter;
 	INIT_WORK(&bbio->csum_work, csum_one_bio_work);
 	schedule_work(&bbio->csum_work);
 	return 0;
diff --git a/fs/btrfs/file-item.h b/fs/btrfs/file-item.h
index 6c678787c770..e170bac310ac 100644
--- a/fs/btrfs/file-item.h
+++ b/fs/btrfs/file-item.h
@@ -64,7 +64,7 @@ int btrfs_lookup_file_extent(struct btrfs_trans_handle *trans,
 int btrfs_insert_data_csums(struct btrfs_trans_handle *trans,
 			    struct btrfs_root *root,
 			    struct btrfs_ordered_sum *sums);
-int btrfs_csum_one_bio(struct btrfs_bio *bbio, bool async);
+int btrfs_csum_one_bio(struct btrfs_bio *bbio, struct bio *bio, bool async);
 int btrfs_alloc_dummy_sum(struct btrfs_bio *bbio);
 int btrfs_lookup_csums_range(struct btrfs_root *root, u64 start, u64 end,
 			     struct list_head *list, int search_commit,
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 29/43] btrfs: set the bio fscrypt context when applicable
From: Daniel Vacek @ 2026-05-13  8:53 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Josef Bacik <josef@toxicpanda.com>

Now that we have the fscrypt_info plumbed through everywhere, add the
code to setup the bio encryption context from the extent context.

We use the per-extent fscrypt_extent_info for encryption/decryption.
We use the offset into the extent as the lblk for fscrypt.  So the start
of the extent has the lblk of 0, 4k into the extent has the lblk of 4k,
etc.  This is done to allow things like relocation to continue to work
properly.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v7 changes:
 * s/\<submit_bio(/blk_crypto_\0/ in btrfs_submit_dev_bio() due to commit
   bb8e2019ad613 ("blk-crypto: handle the fallback above the block layer")
 * Follow upstream fscrypt cleanup and use byte offsets instead of
   logical block numbers in the new api.  As a result remove the
   btrfs_set_bio_crypt_ctx_from_extent() and btrfs_mergeable_encrypted_bio()
   helpers and directly use fscrypt_set_bio_crypt_ctx_from_extent()
   and fscrypt_mergeable_extent_bio().
v6 changes:
 * The dio-related changes in inode.c were moved to direct-io.c
   as upstream refactored in the meantime.
 * Open-code orig_start to start - offset.
 * Get the inode from bbio instead of from page mapping
   as we no longer have the page in btrfs_bio_is_contig().
 * Directly use the file_offset passed to btrfs_bio_is_contig(), no need
   to compute it locally.  We even no longer have the page and offset.
v5: https://lore.kernel.org/linux-btrfs/9ca883acc746b5aba980d1d19317168a7491aba6.1706116485.git.josef@toxicpanda.com/
---
 fs/btrfs/bio.c         |  2 +-
 fs/btrfs/compression.c |  4 +++
 fs/btrfs/direct-io.c   | 10 ++++++
 fs/btrfs/extent_io.c   | 72 +++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 86 insertions(+), 2 deletions(-)

diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
index cc0bd03048ba..689cc41409e5 100644
--- a/fs/btrfs/bio.c
+++ b/fs/btrfs/bio.c
@@ -535,7 +535,7 @@ static void btrfs_submit_dev_bio(struct btrfs_device *dev, struct bio *bio)
 	if (bio->bi_opf & REQ_BTRFS_CGROUP_PUNT)
 		blkcg_punt_bio_submit(bio);
 	else
-		submit_bio(bio);
+		blk_crypto_submit_bio(bio);
 }
 
 static void btrfs_submit_mirrored_bio(struct btrfs_io_context *bioc, int dev_nr)
diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c
index b2393a48a8fe..e235a2ac5838 100644
--- a/fs/btrfs/compression.c
+++ b/fs/btrfs/compression.c
@@ -33,6 +33,7 @@
 #include "subpage.h"
 #include "messages.h"
 #include "super.h"
+#include "fscrypt.h"
 
 static struct bio_set btrfs_compressed_bioset;
 
@@ -333,6 +334,8 @@ void btrfs_submit_compressed_write(struct btrfs_ordered_extent *ordered,
 	cb->bbio.bio.bi_iter.bi_sector = ordered->disk_bytenr >> SECTOR_SHIFT;
 	cb->bbio.ordered = ordered;
 
+	fscrypt_set_bio_crypt_ctx_from_extent(&cb->bbio.bio, ordered->fscrypt_info, 0, GFP_NOFS);
+
 	btrfs_submit_bbio(&cb->bbio, 0);
 }
 
@@ -590,6 +593,7 @@ void btrfs_submit_compressed_read(struct btrfs_bio *bbio)
 	cb->orig_bbio = bbio;
 	cb->bbio.csum_search_commit_root = bbio->csum_search_commit_root;
 
+	fscrypt_set_bio_crypt_ctx_from_extent(&cb->bbio.bio, em->fscrypt_info, 0, GFP_NOFS);
 	btrfs_free_extent_map(em);
 
 	for (int i = 0; i * min_folio_size < compressed_len; i++) {
diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index e8b1ce4a3a11..4891bf233536 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -12,6 +12,7 @@
 #include "volumes.h"
 #include "bio.h"
 #include "ordered-data.h"
+#include "fscrypt.h"
 
 struct btrfs_dio_data {
 	ssize_t submitted;
@@ -727,6 +728,8 @@ static void btrfs_dio_submit_io(const struct iomap_iter *iter, struct bio *bio,
 	struct btrfs_dio_private *dip =
 		container_of(bbio, struct btrfs_dio_private, bbio);
 	struct btrfs_dio_data *dio_data = iter->private;
+	struct fscrypt_extent_info *fscrypt_info = NULL;
+	u64 offset = 0;
 
 	btrfs_bio_init(bbio, BTRFS_I(iter->inode), file_offset,
 		       btrfs_dio_end_io, bio->bi_private);
@@ -746,6 +749,9 @@ static void btrfs_dio_submit_io(const struct iomap_iter *iter, struct bio *bio,
 	if (iter->flags & IOMAP_WRITE) {
 		int ret;
 
+		fscrypt_info = dio_data->ordered->fscrypt_info;
+		offset = file_offset - dio_data->ordered->orig_offset;
+
 		ret = btrfs_extract_ordered_extent(bbio, dio_data->ordered);
 		if (ret) {
 			btrfs_finish_ordered_extent(dio_data->ordered,
@@ -755,8 +761,12 @@ static void btrfs_dio_submit_io(const struct iomap_iter *iter, struct bio *bio,
 			iomap_dio_bio_end_io(bio);
 			return;
 		}
+	} else {
+		fscrypt_info = dio_data->fscrypt_info;
+		offset = file_offset - dio_data->orig_start;
 	}
 
+	fscrypt_set_bio_crypt_ctx_from_extent(&bbio->bio, fscrypt_info, offset, GFP_NOFS);
 	btrfs_submit_bbio(bbio, 0);
 }
 
diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index 2819989f895f..5e9cdcdc996a 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -35,6 +35,7 @@
 #include "dev-replace.h"
 #include "super.h"
 #include "transaction.h"
+#include "fscrypt.h"
 
 static struct kmem_cache *extent_buffer_cache;
 
@@ -150,6 +151,10 @@ struct btrfs_bio_ctrl {
 	 * inside the same inode.
 	 */
 	u64 last_em_start;
+
+	/* This is set for reads and we have encryption. */
+	struct fscrypt_extent_info *fscrypt_info;
+	u64 orig_start;
 };
 
 /*
@@ -710,9 +715,28 @@ static int alloc_eb_folio_array(struct extent_buffer *eb, bool nofail)
 static bool btrfs_bio_is_contig(struct btrfs_bio_ctrl *bio_ctrl,
 				u64 disk_bytenr, loff_t file_offset)
 {
-	struct bio *bio = &bio_ctrl->bbio->bio;
+	struct btrfs_bio *bbio = bio_ctrl->bbio;
+	struct bio *bio = &bbio->bio;
+	struct inode *inode = &bbio->inode->vfs_inode;
 	const sector_t sector = disk_bytenr >> SECTOR_SHIFT;
 
+	if (IS_ENCRYPTED(inode)) {
+		u64 offset = 0;
+		struct fscrypt_extent_info *fscrypt_info = NULL;
+
+		/* bio_ctrl->fscrypt_info is only set in the READ case. */
+		if (bio_ctrl->fscrypt_info) {
+			fscrypt_info = bio_ctrl->fscrypt_info;
+			offset = file_offset - bio_ctrl->orig_start;
+		} else if (bbio->ordered) {
+			fscrypt_info = bbio->ordered->fscrypt_info;
+			offset = file_offset - bbio->ordered->orig_offset;
+		}
+
+		if (!fscrypt_mergeable_extent_bio(bio, fscrypt_info, offset))
+			return false;
+	}
+
 	if (bio_ctrl->compress_type != BTRFS_COMPRESS_NONE) {
 		/*
 		 * For compression, all IO should have its logical bytenr set
@@ -735,6 +759,8 @@ static void alloc_new_bio(struct btrfs_inode *inode,
 {
 	struct btrfs_fs_info *fs_info = inode->root->fs_info;
 	struct btrfs_bio *bbio;
+	struct fscrypt_extent_info *fscrypt_info = NULL;
+	u64 offset = 0;
 
 	bbio = btrfs_bio_alloc(BIO_MAX_VECS, bio_ctrl->opf, inode,
 			       file_offset, bio_ctrl->end_io_func, NULL);
@@ -754,6 +780,8 @@ static void alloc_new_bio(struct btrfs_inode *inode,
 					ordered->file_offset +
 					ordered->disk_num_bytes - file_offset);
 			bbio->ordered = ordered;
+			fscrypt_info = ordered->fscrypt_info;
+			offset = file_offset - ordered->orig_offset;
 		}
 
 		/*
@@ -764,7 +792,12 @@ static void alloc_new_bio(struct btrfs_inode *inode,
 		 */
 		bio_set_dev(&bbio->bio, fs_info->fs_devices->latest_dev->bdev);
 		wbc_init_bio(bio_ctrl->wbc, &bbio->bio);
+	} else {
+		fscrypt_info = bio_ctrl->fscrypt_info;
+		offset = file_offset - bio_ctrl->orig_start;
 	}
+
+	fscrypt_set_bio_crypt_ctx_from_extent(&bbio->bio, fscrypt_info, offset, GFP_NOFS);
 }
 
 /*
@@ -810,6 +843,19 @@ static void submit_extent_folio(struct btrfs_bio_ctrl *bio_ctrl,
 			len = bio_ctrl->len_to_oe_boundary;
 		}
 
+		/*
+		 * Encryption has to allocate bounce buffers to encrypt the bio,
+		 * and we need to make sure that it doesn't split the bio so we
+		 * retain all of our special info in the btrfs_bio, so submit
+		 * any bio that gets up to BIO_MAX_VECS worth of segments.
+		 */
+		if (IS_ENCRYPTED(&inode->vfs_inode) &&
+		    bio_data_dir(&bio_ctrl->bbio->bio) == WRITE &&
+		    bio_segments(&bio_ctrl->bbio->bio) == BIO_MAX_VECS) {
+			submit_one_bio(bio_ctrl);
+			continue;
+		}
+
 		if (!bio_add_folio(&bio_ctrl->bbio->bio, folio, len, pg_offset)) {
 			/* bio full: move on to a new one */
 			submit_one_bio(bio_ctrl);
@@ -1031,6 +1077,8 @@ static int btrfs_do_readpage(struct folio *folio, struct extent_map **em_cached,
 		u64 block_start;
 		u64 em_gen;
 
+		bio_ctrl->fscrypt_info = NULL;
+
 		ASSERT(IS_ALIGNED(cur, fs_info->sectorsize));
 		if (cur >= last_byte) {
 			folio_zero_range(folio, pg_offset, end - cur + 1);
@@ -1120,6 +1168,21 @@ static int btrfs_do_readpage(struct folio *folio, struct extent_map **em_cached,
 
 		bio_ctrl->last_em_start = em->start;
 
+		/*
+		 * We use the extent offset for the IV when decrypting the page,
+		 * so we have to set the extent_offset based on the orig_start
+		 * for this extent.  Also save the fscrypt_info so the bio ctx
+		 * can be set properly.  If this inode isn't encrypted this
+		 * won't do anything.
+		 *
+		 * If we're compressed we'll handle all of this in
+		 * btrfs_submit_compressed_read.
+		 */
+		if (compress_type == BTRFS_COMPRESS_NONE) {
+			bio_ctrl->orig_start = em->start - em->offset;
+			bio_ctrl->fscrypt_info = fscrypt_get_extent_info(em->fscrypt_info);
+		}
+
 		em_gen = em->generation;
 		btrfs_free_extent_map(em);
 		em = NULL;
@@ -1128,11 +1191,17 @@ static int btrfs_do_readpage(struct folio *folio, struct extent_map **em_cached,
 		if (block_start == EXTENT_MAP_HOLE) {
 			folio_zero_range(folio, pg_offset, blocksize);
 			end_folio_read(vi, folio, true, cur, blocksize);
+
+			/* This shouldn't be set, but clear it just in case. */
+			fscrypt_put_extent_info(bio_ctrl->fscrypt_info);
 			continue;
 		}
 		/* the get_extent function already copied into the folio */
 		if (block_start == EXTENT_MAP_INLINE) {
 			end_folio_read(vi, folio, true, cur, blocksize);
+
+			/* This shouldn't be set, but clear it just in case. */
+			fscrypt_put_extent_info(bio_ctrl->fscrypt_info);
 			continue;
 		}
 
@@ -1145,6 +1214,7 @@ static int btrfs_do_readpage(struct folio *folio, struct extent_map **em_cached,
 			submit_one_bio(bio_ctrl);
 		submit_extent_folio(bio_ctrl, disk_bytenr, folio, blocksize,
 				    pg_offset, em_gen);
+		fscrypt_put_extent_info(bio_ctrl->fscrypt_info);
 	}
 	return 0;
 }
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 25/43] btrfs: pass through fscrypt_extent_info to the file extent helpers
From: Daniel Vacek @ 2026-05-13  8:52 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Josef Bacik <josef@toxicpanda.com>

Now that we have the fscrypt_extnet_info in all of the supporting
structures, pass this through and set the file extent encryption bit
accordingly from the supporting structures.  In subsequent patches code
will be added to populate these appropriately.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/a8b9dd312a4504f209e861cca5289a528b30ff95.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/btrfs/inode.c    | 16 ++++++++++------
 fs/btrfs/tree-log.c |  2 +-
 2 files changed, 11 insertions(+), 7 deletions(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index eab7950372ae..cdd2d7bc50cc 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -3056,7 +3056,9 @@ int btrfs_writepage_cow_fixup(struct folio *folio)
 }
 
 static int insert_reserved_file_extent(struct btrfs_trans_handle *trans,
-				       struct btrfs_inode *inode, u64 file_pos,
+				       struct btrfs_inode *inode,
+				       struct fscrypt_extent_info *fscrypt_info,
+				       u64 file_pos,
 				       struct btrfs_file_extent_item *stack_fi,
 				       const bool update_inode_bytes,
 				       u64 qgroup_reserved)
@@ -3180,7 +3182,7 @@ static int insert_ordered_extent_file_extent(struct btrfs_trans_handle *trans,
 	btrfs_set_stack_file_extent_num_bytes(&stack_fi, num_bytes);
 	btrfs_set_stack_file_extent_ram_bytes(&stack_fi, ram_bytes);
 	btrfs_set_stack_file_extent_compression(&stack_fi, oe->compress_type);
-	btrfs_set_stack_file_extent_encryption(&stack_fi, BTRFS_ENCRYPTION_NONE);
+	btrfs_set_stack_file_extent_encryption(&stack_fi, oe->encryption_type);
 	/* Other encoding is reserved and always 0 */
 
 	/*
@@ -3193,7 +3195,7 @@ static int insert_ordered_extent_file_extent(struct btrfs_trans_handle *trans,
 			     test_bit(BTRFS_ORDERED_ENCODED, &oe->flags) ||
 			     test_bit(BTRFS_ORDERED_TRUNCATED, &oe->flags);
 
-	return insert_reserved_file_extent(trans, oe->inode,
+	return insert_reserved_file_extent(trans, oe->inode, oe->fscrypt_info,
 					   oe->file_offset, &stack_fi,
 					   update_inode_bytes, oe->qgroup_rsv);
 }
@@ -9288,6 +9290,7 @@ static struct btrfs_trans_handle *insert_prealloc_file_extent(
 				       struct btrfs_trans_handle *trans_in,
 				       struct btrfs_inode *inode,
 				       struct btrfs_key *ins,
+				       struct fscrypt_extent_info *fscrypt_info,
 				       u64 file_offset)
 {
 	struct btrfs_file_extent_item stack_fi;
@@ -9307,7 +9310,8 @@ static struct btrfs_trans_handle *insert_prealloc_file_extent(
 	btrfs_set_stack_file_extent_num_bytes(&stack_fi, len);
 	btrfs_set_stack_file_extent_ram_bytes(&stack_fi, len);
 	btrfs_set_stack_file_extent_compression(&stack_fi, BTRFS_COMPRESS_NONE);
-	btrfs_set_stack_file_extent_encryption(&stack_fi, BTRFS_ENCRYPTION_NONE);
+	btrfs_set_stack_file_extent_encryption(&stack_fi, fscrypt_info? BTRFS_ENCRYPTION_FSCRYPT:
+									BTRFS_ENCRYPTION_NONE);
 	/* Other encoding is reserved and always 0 */
 
 	ret = btrfs_qgroup_release_data(inode, file_offset, len, &qgroup_released);
@@ -9315,7 +9319,7 @@ static struct btrfs_trans_handle *insert_prealloc_file_extent(
 		return ERR_PTR(ret);
 
 	if (trans) {
-		ret = insert_reserved_file_extent(trans, inode,
+		ret = insert_reserved_file_extent(trans, inode, fscrypt_info,
 						  file_offset, &stack_fi,
 						  true, qgroup_released);
 		if (ret)
@@ -9408,7 +9412,7 @@ static int __btrfs_prealloc_file_range(struct inode *inode, int mode,
 
 		last_alloc = ins.offset;
 		trans = insert_prealloc_file_extent(trans, BTRFS_I(inode),
-						    &ins, cur_offset);
+						    &ins, NULL, cur_offset);
 		/*
 		 * Now that we inserted the prealloc extent we can finally
 		 * decrement the number of reservations in the block group.
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index 3229a60bffb7..60aa763d2fa4 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -5150,7 +5150,7 @@ static int log_one_extent(struct btrfs_trans_handle *trans,
 	u64 block_start = btrfs_extent_map_block_start(em);
 	u64 block_len;
 	int ret;
-	u8 encryption = BTRFS_ENCRYPTION_NONE;
+	u8 encryption = btrfs_extent_map_encryption(em);
 
 	btrfs_set_stack_file_extent_generation(&fi, trans->transid);
 	if (em->flags & EXTENT_FLAG_PREALLOC)
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 24/43] btrfs: add extent encryption context tree item type
From: Daniel Vacek @ 2026-05-13  8:52 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Josef Bacik <josef@toxicpanda.com>

The fscrypt encryption context will be stored as a new tree item type.
This gives us flexibility to include different things in the future.

Also update the tree-checker to validate the new item type.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v7 changes:
 * Keep the original error if any as suggested by Chris' AI review.
 * Fix a comment in check_fscrypt_context() to match the new design
   as suggested by Chris' AI review.
v6 changes:
 * Not much left from the original commit.
   - This was reworked so that the encryption context is now a separate
     tree item with it's unique key.
   - It is tightly related to the file extent item but still optional and
     only used for encrypted extents.
   - The content (and hence the size as well) comes from the fscrrypt
     subsystem and it is not touched by btrfs at all.
   - It's handled as a raw binary data (u8 *).
   - This patch makes sure it is correctly removed when the related extent
     is dropped.
 * As a result, the following patch https://lore.kernel.org/linux-btrfs/f0d9b2d3a40b7a963a977d3dfb62793ff7b065d1.1706116485.git.josef@toxicpanda.com/
   ("btrfs: explicitly track file extent length for replace and drop")
   was dropped as not applicable.  There's no need to track the size
   anymore as it just matches the size of the stored item.

   [RFC]: Should I have kept the structure with __u8 type? Like:

   |  struct btrfs_encryption_info {
   |        __u8 context[0];
   |  };

   I did remove it as it was only used to extend the file extent item
   structure and hence no longer needed.
v5: https://lore.kernel.org/linux-btrfs/7ee9171262857336011bf0e121846617c5181fa4.1706116485.git.josef@toxicpanda.com/
    ("btrfs: add an optional encryption context to the end of file extents")
---
 fs/btrfs/file.c                 | 65 +++++++++++++++++++++++++++++++++
 fs/btrfs/tree-checker.c         | 56 ++++++++++++++++++++++++++--
 include/uapi/linux/btrfs_tree.h |  8 ++++
 3 files changed, 125 insertions(+), 4 deletions(-)

diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index 8b0ebdeca9dd..fe7baecbf83b 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -151,6 +151,7 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans,
 	u64 extent_offset = 0;
 	u64 extent_end = 0;
 	u64 last_end = args->start;
+	u64 first_ctx = 1, last_ctx = 0;
 	int del_nr = 0;
 	int del_slot = 0;
 	int extent_type;
@@ -408,6 +409,12 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans,
 				del_nr++;
 			}
 
+			if (btrfs_file_extent_encryption(leaf, fi) == BTRFS_ENCRYPTION_FSCRYPT) {
+				if (first_ctx > last_ctx)
+					first_ctx = key.offset;
+				last_ctx = key.offset;
+			}
+
 			if (update_refs &&
 			    extent_type == BTRFS_FILE_EXTENT_INLINE) {
 				args->bytes_found += extent_end - key.offset;
@@ -497,6 +504,64 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans,
 		args->extent_inserted = true;
 	}
 
+	if (first_ctx <= last_ctx) {
+		int slot, nritems;
+
+		btrfs_release_path(path);
+
+		key.objectid = ino;
+		key.type = BTRFS_FSCRYPT_CTX_KEY;
+		key.offset = first_ctx;
+
+		ret = btrfs_search_slot(trans, root, &key, path, modify_tree, !!modify_tree);
+		if (ret < 0)
+			goto out_ctx;
+next_leaf:
+		leaf = path->nodes[0];
+		slot = path->slots[0];
+
+		del_slot = slot;
+		del_nr = 0;
+		nritems = btrfs_header_nritems(leaf);
+		while (slot < nritems) {
+			btrfs_item_key_to_cpu(leaf, &key, slot);
+			if (key.objectid > ino ||
+			    key.type > BTRFS_FSCRYPT_CTX_KEY ||
+			    key.offset > last_ctx)
+				break;
+			del_nr++;
+			slot++;
+		}
+		if (del_nr) {
+			ret = btrfs_del_items(trans, root, path, del_slot, del_nr);
+			if (unlikely(ret)) {
+				btrfs_abort_transaction(trans, ret);
+				goto out_ctx;
+			}
+
+			if (slot == nritems) {
+				ret = btrfs_next_leaf(root, path);
+				if (!ret)
+					goto next_leaf;
+				if (ret > 0)
+					ret = 0;
+			}
+		}
+out_ctx:
+		if (args->path && args->extent_inserted) {
+			int err;
+
+			btrfs_release_path(path);
+
+			key.objectid = ino;
+			key.type = BTRFS_EXTENT_DATA_KEY;
+			key.offset = args->start;
+			err = btrfs_search_slot(trans, root, &key, path, 0, 0);
+			if (err && ret >= 0)
+				ret = err;
+		}
+	}
+
 	if (!args->path)
 		btrfs_free_path(path);
 	else if (!args->extent_inserted)
diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
index 61987353b138..fa20bdf9f1d1 100644
--- a/fs/btrfs/tree-checker.c
+++ b/fs/btrfs/tree-checker.c
@@ -186,6 +186,7 @@ static bool check_prev_ino(struct extent_buffer *leaf,
 	       key->type == BTRFS_INODE_EXTREF_KEY ||
 	       key->type == BTRFS_DIR_INDEX_KEY ||
 	       key->type == BTRFS_DIR_ITEM_KEY ||
+	       key->type == BTRFS_FSCRYPT_CTX_KEY ||
 	       key->type == BTRFS_EXTENT_DATA_KEY, "key->type=%u", key->type);
 
 	/*
@@ -204,6 +205,39 @@ static bool check_prev_ino(struct extent_buffer *leaf,
 		prev_key->objectid, key->objectid);
 	return false;
 }
+
+static int check_fscrypt_context(struct extent_buffer *leaf,
+				 struct btrfs_key *key, int slot,
+				 struct btrfs_key *prev_key)
+{
+	u32 sectorsize = leaf->fs_info->sectorsize;
+	u32 item_size = btrfs_item_size(leaf, slot);
+
+	if (unlikely(!IS_ALIGNED(key->offset, sectorsize))) {
+		file_extent_err(leaf, slot,
+"unaligned file_offset for encryption context, have %llu should be aligned to %u",
+			key->offset, sectorsize);
+		return -EUCLEAN;
+	}
+
+	/*
+	 * Previous key must have the same key->objectid (ino).  It can be
+	 * XATTR_ITEM, INODE_ITEM, FSCRYPT_INODE_CTX_KEY, or another FSCRYPT_CTX_KEY.
+	 * But if objectids mismatch, it means we have a missing INODE_ITEM.
+	 */
+	if (unlikely(!check_prev_ino(leaf, key, slot, prev_key)))
+		return -EUCLEAN;
+
+	if (unlikely(item_size > BTRFS_MAX_EXTENT_CTX_SIZE)) {
+		file_extent_err(leaf, slot,
+	"invalid encryption context size, have %u expect a maximum of %u",
+				item_size, BTRFS_MAX_EXTENT_CTX_SIZE);
+		return -EUCLEAN;
+	}
+
+	return 0;
+}
+
 static int check_extent_data_item(struct extent_buffer *leaf,
 				  struct btrfs_key *key, int slot,
 				  struct btrfs_key *prev_key)
@@ -214,6 +248,7 @@ static int check_extent_data_item(struct extent_buffer *leaf,
 	u32 item_size = btrfs_item_size(leaf, slot);
 	u64 extent_end;
 	u8 policy;
+	u8 fe_type;
 
 	if (unlikely(!IS_ALIGNED(key->offset, sectorsize))) {
 		file_extent_err(leaf, slot,
@@ -244,12 +279,12 @@ static int check_extent_data_item(struct extent_buffer *leaf,
 				SZ_4K);
 		return -EUCLEAN;
 	}
-	if (unlikely(btrfs_file_extent_type(leaf, fi) >=
-		     BTRFS_NR_FILE_EXTENT_TYPES)) {
+
+	fe_type = btrfs_file_extent_type(leaf, fi);
+	if (unlikely(fe_type >= BTRFS_NR_FILE_EXTENT_TYPES)) {
 		file_extent_err(leaf, slot,
 		"invalid type for file extent, have %u expect range [0, %u]",
-			btrfs_file_extent_type(leaf, fi),
-			BTRFS_NR_FILE_EXTENT_TYPES - 1);
+			fe_type, BTRFS_NR_FILE_EXTENT_TYPES - 1);
 		return -EUCLEAN;
 	}
 
@@ -298,6 +333,16 @@ static int check_extent_data_item(struct extent_buffer *leaf,
 		return 0;
 	}
 
+	if (policy == BTRFS_ENCRYPTION_FSCRYPT) {
+		/* Only regular and prealloc extents should have an encryption context */
+		if (unlikely(fe_type != BTRFS_FILE_EXTENT_REG &&
+			     fe_type != BTRFS_FILE_EXTENT_PREALLOC)) {
+			file_extent_err(leaf, slot,
+		"invalid type for encrypted file extent, have %u", fe_type);
+			return -EUCLEAN;
+		}
+	}
+
 	/* Regular or preallocated extent has fixed item size */
 	if (unlikely(item_size != sizeof(*fi))) {
 		file_extent_err(leaf, slot,
@@ -2198,6 +2243,9 @@ static enum btrfs_tree_block_status check_leaf_item(struct extent_buffer *leaf,
 	case BTRFS_EXTENT_CSUM_KEY:
 		ret = check_csum_item(leaf, key, slot, prev_key);
 		break;
+	case BTRFS_FSCRYPT_CTX_KEY:
+		ret = check_fscrypt_context(leaf, key, slot, prev_key);
+		break;
 	case BTRFS_DIR_ITEM_KEY:
 	case BTRFS_DIR_INDEX_KEY:
 	case BTRFS_XATTR_ITEM_KEY:
diff --git a/include/uapi/linux/btrfs_tree.h b/include/uapi/linux/btrfs_tree.h
index 7b1922c3265a..26b793396132 100644
--- a/include/uapi/linux/btrfs_tree.h
+++ b/include/uapi/linux/btrfs_tree.h
@@ -168,6 +168,7 @@
 #define BTRFS_VERITY_MERKLE_ITEM_KEY	37
 
 #define BTRFS_FSCRYPT_INODE_CTX_KEY	41
+#define BTRFS_FSCRYPT_CTX_KEY		42
 
 #define BTRFS_ORPHAN_ITEM_KEY		48
 /* reserve 2-15 close to the inode for later flexibility */
@@ -1079,6 +1080,13 @@ enum {
 	BTRFS_NR_FILE_EXTENT_TYPES = 3,
 };
 
+/*
+ * Currently just the FSCRYPT_SET_CONTEXT_MAX_SIZE, which is larger than the
+ * current extent context size from fscrypt, so this should give us plenty of
+ * breathing room for expansion later.
+ */
+#define BTRFS_MAX_EXTENT_CTX_SIZE 40
+
 enum btrfs_encryption_type {
 	BTRFS_ENCRYPTION_NONE,
 	BTRFS_ENCRYPTION_FSCRYPT,
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 23/43] btrfs: keep track of fscrypt info and orig_start for dio reads
From: Daniel Vacek @ 2026-05-13  8:52 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Josef Bacik <josef@toxicpanda.com>

We keep track of this information in the ordered extent for writes, but
we need it for reads as well.  Add fscrypt_extent_info and orig_start to
the dio_data so we can populate this on reads.  This will be used later
when we attach the fscrypt context to the bios.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

No changes in v7.
v6 changes:
 * Open-code orig_start to start - offset.
v5: https://lore.kernel.org/linux-btrfs/acf3d79bcd72a4447b2993e78d9742fa5a05397f.1706116485.git.josef@toxicpanda.com/
---
 fs/btrfs/direct-io.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index e46bca590f35..f87656db6157 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -17,6 +17,8 @@ struct btrfs_dio_data {
 	ssize_t submitted;
 	struct extent_changeset *data_reserved;
 	struct btrfs_ordered_extent *ordered;
+	struct fscrypt_extent_info *fscrypt_info;
+	u64 orig_start;
 	bool data_space_reserved;
 	bool nocow_done;
 };
@@ -550,6 +552,9 @@ static int btrfs_dio_iomap_begin(struct inode *inode, loff_t start,
 							       release_offset,
 							       release_len);
 		}
+	} else {
+		dio_data->fscrypt_info = fscrypt_get_extent_info(em->fscrypt_info);
+		dio_data->orig_start = em->start - em->offset;
 	}
 
 	/*
@@ -640,6 +645,11 @@ static int btrfs_dio_iomap_end(struct inode *inode, loff_t pos, loff_t length,
 		dio_data->ordered = NULL;
 	}
 
+	if (dio_data->fscrypt_info) {
+		fscrypt_put_extent_info(dio_data->fscrypt_info);
+		dio_data->fscrypt_info = NULL;
+	}
+
 	if (write)
 		extent_changeset_free(dio_data->data_reserved);
 	return ret;
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 22/43] btrfs: populate the ordered_extent with the fscrypt context
From: Daniel Vacek @ 2026-05-13  8:52 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Josef Bacik <josef@toxicpanda.com>

The fscrypt_extent_info will be tied to the extent_map lifetime, so it
will be created when we create the IO em, or it'll already exist in the
NOCOW case. Use this fscrypt_info when creating the ordered extent to
make sure everything is passed through properly.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

No changes in v7.
v6 changes:
 * Splitted the dio-related hunks from inode.c to direct-io.c as upstream
   refactored in the meantime.
 * Pass fscrypt_info using the file_extent structure to follow the
   upstream cleanup.
v5: https://lore.kernel.org/linux-btrfs/33e650f3e91ed0318211301beb27fa613382f28e.1706116485.git.josef@toxicpanda.com/
---
 fs/btrfs/direct-io.c |  4 +++-
 fs/btrfs/inode.c     | 36 ++++++++++++++++++++++++++++++------
 2 files changed, 33 insertions(+), 7 deletions(-)

diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index 5a53739e9445..e46bca590f35 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -140,7 +140,7 @@ static int lock_extent_direct(struct inode *inode, u64 lockstart, u64 lockend,
 static struct extent_map *btrfs_create_dio_extent(struct btrfs_inode *inode,
 						  struct btrfs_dio_data *dio_data,
 						  const u64 start,
-						  const struct btrfs_file_extent *file_extent,
+						  struct btrfs_file_extent *file_extent,
 						  const int type)
 {
 	struct extent_map *em = NULL;
@@ -150,6 +150,7 @@ static struct extent_map *btrfs_create_dio_extent(struct btrfs_inode *inode,
 		em = btrfs_create_io_em(inode, start, file_extent, type);
 		if (IS_ERR(em))
 			goto out;
+		file_extent->fscrypt_info = em->fscrypt_info;
 	}
 
 	ordered = btrfs_alloc_ordered_extent(inode, start, file_extent,
@@ -276,6 +277,7 @@ static int btrfs_get_blocks_direct_write(struct extent_map **map,
 		}
 		space_reserved = true;
 
+		file_extent.fscrypt_info = em->fscrypt_info;
 		em2 = btrfs_create_dio_extent(BTRFS_I(inode), dio_data, start,
 					      &file_extent, type);
 		btrfs_dec_nocow_writers(bg);
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index c6c8ae05e471..eab7950372ae 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -1121,10 +1121,11 @@ static void submit_one_async_extent(struct async_chunk *async_chunk,
 		ret = PTR_ERR(em);
 		goto out_free_reserve;
 	}
-	btrfs_free_extent_map(em);
 
+	file_extent.fscrypt_info = em->fscrypt_info;
 	ordered = btrfs_alloc_ordered_extent(inode, start, &file_extent,
 					     1U << BTRFS_ORDERED_COMPRESSED);
+	btrfs_free_extent_map(em);
 	if (IS_ERR(ordered)) {
 		btrfs_drop_extent_map_range(inode, start, end, false);
 		ret = PTR_ERR(ordered);
@@ -1263,10 +1264,11 @@ static int cow_one_range(struct btrfs_inode *inode, struct folio *locked_folio,
 		ret = PTR_ERR(em);
 		goto free_reserved;
 	}
-	btrfs_free_extent_map(em);
 
+	file_extent.fscrypt_info = em->fscrypt_info;
 	ordered = btrfs_alloc_ordered_extent(inode, file_offset, &file_extent,
 					     1U << BTRFS_ORDERED_REGULAR);
+	btrfs_free_extent_map(em);
 	if (IS_ERR(ordered)) {
 		btrfs_drop_extent_map_range(inode, file_offset, cur_end, false);
 		ret = PTR_ERR(ordered);
@@ -1933,18 +1935,32 @@ static int nocow_one_range(struct btrfs_inode *inode, struct folio *locked_folio
 			   u64 file_pos, bool is_prealloc)
 {
 	struct btrfs_ordered_extent *ordered;
+	struct extent_map *em;
 	const u64 len = nocow_args->file_extent.num_bytes;
 	const u64 end = file_pos + len - 1;
 	int ret = 0;
 
 	btrfs_lock_extent(&inode->io_tree, file_pos, end, cached);
 
-	if (is_prealloc) {
-		struct extent_map *em;
+	/*
+	 * We only want to do this lookup if we're encrypted, otherwise
+	 * fsrypt_info will be null and we can avoid this lookup.
+	 */
+	if (IS_ENCRYPTED(&inode->vfs_inode)) {
+		em = btrfs_get_extent(inode, NULL, file_pos, len);
+		if (IS_ERR(em)) {
+			btrfs_unlock_extent(&inode->io_tree, file_pos, end, cached);
+			return PTR_ERR(em);
+		}
+		nocow_args->file_extent.fscrypt_info = fscrypt_get_extent_info(em->fscrypt_info);
+		btrfs_free_extent_map(em);
+	}
 
+	if (is_prealloc) {
 		em = btrfs_create_io_em(inode, file_pos, &nocow_args->file_extent,
 					BTRFS_ORDERED_PREALLOC);
 		if (IS_ERR(em)) {
+			fscrypt_put_extent_info(nocow_args->file_extent.fscrypt_info);
 			ret = PTR_ERR(em);
 			goto error;
 		}
@@ -1955,6 +1971,7 @@ static int nocow_one_range(struct btrfs_inode *inode, struct folio *locked_folio
 					     is_prealloc
 					     ? (1U << BTRFS_ORDERED_PREALLOC)
 					     : (1U << BTRFS_ORDERED_NOCOW));
+	fscrypt_put_extent_info(nocow_args->file_extent.fscrypt_info);
 	if (IS_ERR(ordered)) {
 		if (is_prealloc)
 			btrfs_drop_extent_map_range(inode, file_pos, end, false);
@@ -7657,7 +7674,13 @@ struct extent_map *btrfs_create_io_em(struct btrfs_inode *inode, u64 start,
 	em->flags |= EXTENT_FLAG_PINNED;
 	if (type == BTRFS_ORDERED_COMPRESSED)
 		btrfs_extent_map_set_compression(em, file_extent->compression);
-	btrfs_extent_map_set_encryption(em, BTRFS_ENCRYPTION_NONE);
+
+	if (file_extent->fscrypt_info) {
+		btrfs_extent_map_set_encryption(em, BTRFS_ENCRYPTION_FSCRYPT);
+		em->fscrypt_info = fscrypt_get_extent_info(file_extent->fscrypt_info);
+	} else {
+		btrfs_extent_map_set_encryption(em, BTRFS_ENCRYPTION_NONE);
+	}
 
 	ret = btrfs_replace_extent_map_range(inode, em, true);
 	if (ret) {
@@ -10209,11 +10232,12 @@ ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from,
 		ret = PTR_ERR(em);
 		goto out_free_reserved;
 	}
-	btrfs_free_extent_map(em);
 
+	file_extent.fscrypt_info = em->fscrypt_info;
 	ordered = btrfs_alloc_ordered_extent(inode, start, &file_extent,
 				       (1U << BTRFS_ORDERED_ENCODED) |
 				       (1U << BTRFS_ORDERED_COMPRESSED));
+	btrfs_free_extent_map(em);
 	if (IS_ERR(ordered)) {
 		btrfs_drop_extent_map_range(inode, start, end, false);
 		ret = PTR_ERR(ordered);
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 20/43] btrfs: add fscrypt_info and encryption_type to ordered_extent
From: Daniel Vacek @ 2026-05-13  8:52 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Josef Bacik <josef@toxicpanda.com>

We're going to need these to update the file extent items once the
writes are complete.  Add them and add the pieces necessary to assign
them and free everything.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

No changes in v7.
v6 changes:
 * Changed encryption_type member to u8 as suggested by Dave.
v5: https://lore.kernel.org/linux-btrfs/de9ff13d1dc042b764c224d039fbb2a08946e004.1706116485.git.josef@toxicpanda.com/
---
 fs/btrfs/ordered-data.c | 2 ++
 fs/btrfs/ordered-data.h | 6 ++++++
 2 files changed, 8 insertions(+)

diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
index e5a24b3ff95e..ac302fb7484b 100644
--- a/fs/btrfs/ordered-data.c
+++ b/fs/btrfs/ordered-data.c
@@ -205,6 +205,7 @@ static struct btrfs_ordered_extent *alloc_ordered_extent(
 	}
 	entry->inode = inode;
 	entry->compress_type = compress_type;
+	entry->encryption_type = BTRFS_ENCRYPTION_NONE;
 	entry->truncated_len = (u64)-1;
 	entry->qgroup_rsv = qgroup_rsv;
 	entry->flags = flags;
@@ -629,6 +630,7 @@ void btrfs_put_ordered_extent(struct btrfs_ordered_extent *entry)
 		btrfs_add_delayed_iput(entry->inode);
 		list_for_each_entry_safe(sum, tmp, &entry->csum_list, list)
 			kvfree(sum);
+		fscrypt_put_extent_info(entry->fscrypt_info);
 		kmem_cache_free(btrfs_ordered_extent_cache, entry);
 	}
 }
diff --git a/fs/btrfs/ordered-data.h b/fs/btrfs/ordered-data.h
index 03e12380a2fd..9b288c3908f9 100644
--- a/fs/btrfs/ordered-data.h
+++ b/fs/btrfs/ordered-data.h
@@ -131,6 +131,9 @@ struct btrfs_ordered_extent {
 	/* compression algorithm */
 	int compress_type;
 
+	/* encryption mode */
+	u8 encryption_type;
+
 	/* Qgroup reserved space */
 	int qgroup_rsv;
 
@@ -140,6 +143,9 @@ struct btrfs_ordered_extent {
 	/* the inode we belong to */
 	struct btrfs_inode *inode;
 
+	/* the fscrypt_info for this extent, if necessary */
+	struct fscrypt_extent_info *fscrypt_info;
+
 	/* list of checksums for insertion when the extent io is done */
 	struct list_head csum_list;
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 21/43] btrfs: plumb through setting the fscrypt_info for ordered extents
From: Daniel Vacek @ 2026-05-13  8:52 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Josef Bacik <josef@toxicpanda.com>

We're going to be getting fscrypt_info from the extent maps.  Pass the
fscrypt_info to helpers using the file_extent structure and use that
to set the encryption type on the ordered extent.

For prealloc extents we create an em, since we already have the context
loaded from the original prealloc extent creation we need to pre-
populate the extent map fscrypt info so it can be read properly later
if the pages are evicted.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

No changes in v7.
v6 changes:
 * Added fscrypt_info to file_extent structure to clean up and simplify
   the code following upstream changes since.
 * Squashed in https://lore.kernel.org/linux-btrfs/f2b402eac1963296b6b8db3cb59cdf24a8121b97.1706116485.git.josef@toxicpanda.com/
   ("btrfs: plumb the fscrypt extent context through create_io_em").
 * Moved fscrypt_info to be the last argument of alloc_ordered_extent().
v5: https://lore.kernel.org/linux-btrfs/80c5dabfe190b84e31a95160021da64ebcf7ecf7.1706116485.git.josef@toxicpanda.com/
---
 fs/btrfs/direct-io.c    |  1 +
 fs/btrfs/inode.c        |  3 +++
 fs/btrfs/ordered-data.c | 18 ++++++++++++------
 fs/btrfs/ordered-data.h |  1 +
 4 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index 57167d56dc72..5a53739e9445 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -202,6 +202,7 @@ static struct extent_map *btrfs_new_extent_direct(struct btrfs_inode *inode,
 	file_extent.ram_bytes = ins.offset;
 	file_extent.offset = 0;
 	file_extent.compression = BTRFS_COMPRESS_NONE;
+	file_extent.fscrypt_info = NULL;
 	em = btrfs_create_dio_extent(inode, dio_data, start, &file_extent,
 				     BTRFS_ORDERED_REGULAR);
 	btrfs_dec_block_group_reservations(fs_info, ins.objectid);
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 17e2c77c584a..c6c8ae05e471 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -1112,6 +1112,7 @@ static void submit_one_async_extent(struct async_chunk *async_chunk,
 	file_extent.num_bytes = async_extent->ram_size;
 	file_extent.offset = 0;
 	file_extent.compression = async_extent->cb->compress_type;
+	file_extent.fscrypt_info = NULL;
 
 	async_extent->cb->bbio.bio.bi_iter.bi_sector = ins.objectid >> SECTOR_SHIFT;
 
@@ -1249,6 +1250,7 @@ static int cow_one_range(struct btrfs_inode *inode, struct folio *locked_folio,
 	file_extent.ram_bytes = ins->offset;
 	file_extent.offset = 0;
 	file_extent.compression = BTRFS_COMPRESS_NONE;
+	file_extent.fscrypt_info = NULL;
 
 	/*
 	 * Locked range will be released either during error clean up (inside
@@ -10201,6 +10203,7 @@ ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from,
 	file_extent.ram_bytes = ram_bytes;
 	file_extent.offset = encoded->unencoded_offset;
 	file_extent.compression = compression;
+	file_extent.fscrypt_info = NULL;
 	em = btrfs_create_io_em(inode, start, &file_extent, BTRFS_ORDERED_COMPRESSED);
 	if (IS_ERR(em)) {
 		ret = PTR_ERR(em);
diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
index ac302fb7484b..5a9bb0e8a18c 100644
--- a/fs/btrfs/ordered-data.c
+++ b/fs/btrfs/ordered-data.c
@@ -148,7 +148,8 @@ static inline struct rb_node *ordered_tree_search(struct btrfs_inode *inode,
 static struct btrfs_ordered_extent *alloc_ordered_extent(
 			struct btrfs_inode *inode, u64 file_offset, u64 num_bytes,
 			u64 ram_bytes, u64 disk_bytenr, u64 disk_num_bytes,
-			u64 offset, unsigned long flags, int compress_type)
+			u64 offset, unsigned long flags, int compress_type,
+			struct fscrypt_extent_info *fscrypt_info)
 {
 	struct btrfs_ordered_extent *entry;
 	int ret;
@@ -205,10 +206,12 @@ static struct btrfs_ordered_extent *alloc_ordered_extent(
 	}
 	entry->inode = inode;
 	entry->compress_type = compress_type;
-	entry->encryption_type = BTRFS_ENCRYPTION_NONE;
 	entry->truncated_len = (u64)-1;
 	entry->qgroup_rsv = qgroup_rsv;
 	entry->flags = flags;
+	entry->fscrypt_info = fscrypt_get_extent_info(fscrypt_info);
+	entry->encryption_type = entry->fscrypt_info ?
+		BTRFS_ENCRYPTION_FSCRYPT : BTRFS_ENCRYPTION_NONE;
 	refcount_set(&entry->refs, 1);
 	init_waitqueue_head(&entry->wait);
 	INIT_LIST_HEAD(&entry->csum_list);
@@ -290,6 +293,7 @@ static void insert_ordered_extent(struct btrfs_ordered_extent *entry)
  * @offset:          Offset into unencoded data where file data starts.
  * @flags:           Flags specifying type of extent (1U << BTRFS_ORDERED_*).
  * @compress_type:   Compression algorithm used for data.
+ * @fscrypt_info:    The fscrypt_extent_info for this extent, if necessary.
  *
  * Most of these parameters correspond to &struct btrfs_file_extent_item. The
  * tree is given a single reference on the ordered extent that was inserted, and
@@ -323,7 +327,8 @@ struct btrfs_ordered_extent *btrfs_alloc_ordered_extent(
 					     file_extent->num_bytes,
 					     file_extent->disk_bytenr + file_extent->offset,
 					     file_extent->num_bytes, 0, flags,
-					     file_extent->compression);
+					     file_extent->compression,
+					     file_extent->fscrypt_info);
 	else
 		entry = alloc_ordered_extent(inode, file_offset,
 					     file_extent->num_bytes,
@@ -331,7 +336,8 @@ struct btrfs_ordered_extent *btrfs_alloc_ordered_extent(
 					     file_extent->disk_bytenr,
 					     file_extent->disk_num_bytes,
 					     file_extent->offset, flags,
-					     file_extent->compression);
+					     file_extent->compression,
+					     file_extent->fscrypt_info);
 	if (!IS_ERR(entry))
 		insert_ordered_extent(entry);
 	return entry;
@@ -1270,8 +1276,8 @@ struct btrfs_ordered_extent *btrfs_split_ordered_extent(
 	if (WARN_ON_ONCE(ordered->disk_num_bytes != ordered->num_bytes))
 		return ERR_PTR(-EINVAL);
 
-	new = alloc_ordered_extent(inode, file_offset, len, len, disk_bytenr,
-				   len, 0, flags, ordered->compress_type);
+	new = alloc_ordered_extent(inode, file_offset, len, len, disk_bytenr, len, 0,
+				   flags, ordered->compress_type, ordered->fscrypt_info);
 	if (IS_ERR(new))
 		return new;
 
diff --git a/fs/btrfs/ordered-data.h b/fs/btrfs/ordered-data.h
index 9b288c3908f9..a3514a4d9ea1 100644
--- a/fs/btrfs/ordered-data.h
+++ b/fs/btrfs/ordered-data.h
@@ -192,6 +192,7 @@ struct btrfs_file_extent {
 	u64 num_bytes;
 	u64 ram_bytes;
 	u64 offset;
+	struct fscrypt_extent_info *fscrypt_info;
 	u8 compression;
 };
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 18/43] btrfs: set file extent encryption excplicitly
From: Daniel Vacek @ 2026-05-13  8:52 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Sweet Tea Dorminy
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>

This puts the long-preserved 1-byte encryption field to work, storing
whether the extent is encrypted.  Update the tree-checker to allow for
the encryption bit to be set to our valid types.

Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/1bf1cb768bc8bc54b3855e271412077c60f23c50.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/btrfs/accessors.h            | 2 ++
 fs/btrfs/inode.c                | 6 ++++--
 fs/btrfs/tree-checker.c         | 8 +++++---
 fs/btrfs/tree-log.c             | 2 ++
 include/uapi/linux/btrfs_tree.h | 8 +++++++-
 5 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/fs/btrfs/accessors.h b/fs/btrfs/accessors.h
index 8938357fcb40..09ed68fa2041 100644
--- a/fs/btrfs/accessors.h
+++ b/fs/btrfs/accessors.h
@@ -907,6 +907,8 @@ BTRFS_SETGET_STACK_FUNCS(stack_file_extent_disk_num_bytes,
 			 struct btrfs_file_extent_item, disk_num_bytes, 64);
 BTRFS_SETGET_STACK_FUNCS(stack_file_extent_compression,
 			 struct btrfs_file_extent_item, compression, 8);
+BTRFS_SETGET_STACK_FUNCS(stack_file_extent_encryption,
+			 struct btrfs_file_extent_item, encryption, 8);
 
 
 BTRFS_SETGET_FUNCS(file_extent_type, struct btrfs_file_extent_item, type, 8);
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 0bae9179eb12..bc2460292e43 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -3161,7 +3161,8 @@ static int insert_ordered_extent_file_extent(struct btrfs_trans_handle *trans,
 	btrfs_set_stack_file_extent_num_bytes(&stack_fi, num_bytes);
 	btrfs_set_stack_file_extent_ram_bytes(&stack_fi, ram_bytes);
 	btrfs_set_stack_file_extent_compression(&stack_fi, oe->compress_type);
-	/* Encryption and other encoding is reserved and all 0 */
+	btrfs_set_stack_file_extent_encryption(&stack_fi, BTRFS_ENCRYPTION_NONE);
+	/* Other encoding is reserved and always 0 */
 
 	/*
 	 * For delalloc, when completing an ordered extent we update the inode's
@@ -9280,7 +9281,8 @@ static struct btrfs_trans_handle *insert_prealloc_file_extent(
 	btrfs_set_stack_file_extent_num_bytes(&stack_fi, len);
 	btrfs_set_stack_file_extent_ram_bytes(&stack_fi, len);
 	btrfs_set_stack_file_extent_compression(&stack_fi, BTRFS_COMPRESS_NONE);
-	/* Encryption and other encoding is reserved and all 0 */
+	btrfs_set_stack_file_extent_encryption(&stack_fi, BTRFS_ENCRYPTION_NONE);
+	/* Other encoding is reserved and always 0 */
 
 	ret = btrfs_qgroup_release_data(inode, file_offset, len, &qgroup_released);
 	if (ret < 0)
diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
index 1f15d0793a9c..61987353b138 100644
--- a/fs/btrfs/tree-checker.c
+++ b/fs/btrfs/tree-checker.c
@@ -213,6 +213,7 @@ static int check_extent_data_item(struct extent_buffer *leaf,
 	u32 sectorsize = fs_info->sectorsize;
 	u32 item_size = btrfs_item_size(leaf, slot);
 	u64 extent_end;
+	u8 policy;
 
 	if (unlikely(!IS_ALIGNED(key->offset, sectorsize))) {
 		file_extent_err(leaf, slot,
@@ -264,10 +265,11 @@ static int check_extent_data_item(struct extent_buffer *leaf,
 			BTRFS_NR_COMPRESS_TYPES - 1);
 		return -EUCLEAN;
 	}
-	if (unlikely(btrfs_file_extent_encryption(leaf, fi))) {
+	policy = btrfs_file_extent_encryption(leaf, fi);
+	if (unlikely(policy >= BTRFS_NR_ENCRYPTION_TYPES)) {
 		file_extent_err(leaf, slot,
-			"invalid encryption for file extent, have %u expect 0",
-			btrfs_file_extent_encryption(leaf, fi));
+			"invalid encryption for file extent, have %u expect range [0, %u]",
+			policy, BTRFS_NR_ENCRYPTION_TYPES - 1);
 		return -EUCLEAN;
 	}
 	if (btrfs_file_extent_type(leaf, fi) == BTRFS_FILE_EXTENT_INLINE) {
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index 81f0990d638b..3229a60bffb7 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -5150,6 +5150,7 @@ static int log_one_extent(struct btrfs_trans_handle *trans,
 	u64 block_start = btrfs_extent_map_block_start(em);
 	u64 block_len;
 	int ret;
+	u8 encryption = BTRFS_ENCRYPTION_NONE;
 
 	btrfs_set_stack_file_extent_generation(&fi, trans->transid);
 	if (em->flags & EXTENT_FLAG_PREALLOC)
@@ -5171,6 +5172,7 @@ static int log_one_extent(struct btrfs_trans_handle *trans,
 	btrfs_set_stack_file_extent_num_bytes(&fi, em->len);
 	btrfs_set_stack_file_extent_ram_bytes(&fi, em->ram_bytes);
 	btrfs_set_stack_file_extent_compression(&fi, compress_type);
+	btrfs_set_stack_file_extent_encryption(&fi, encryption);
 
 	ret = log_extent_csums(trans, inode, log, em, ctx);
 	if (ret)
diff --git a/include/uapi/linux/btrfs_tree.h b/include/uapi/linux/btrfs_tree.h
index 3cec245268e7..7b1922c3265a 100644
--- a/include/uapi/linux/btrfs_tree.h
+++ b/include/uapi/linux/btrfs_tree.h
@@ -1107,8 +1107,14 @@ struct btrfs_file_extent_item {
 	 * but not for stat.
 	 */
 	__u8 compression;
+
+	/*
+	 * Type of encryption in use. Unencrypted value is 0.
+	 */
 	__u8 encryption;
-	__le16 other_encoding; /* spare for later use */
+
+	/* spare for later use */
+	__le16 other_encoding;
 
 	/* are we inline data or a real extent? */
 	__u8 type;
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 19/43] btrfs: add fscrypt_info and encryption_type to extent_map
From: Daniel Vacek @ 2026-05-13  8:52 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Sweet Tea Dorminy
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>

Each extent_map will end up with a pointer to its associated
fscrypt_info if any, which should have the same lifetime as the
extent_map.  We are also going to need to track the encryption_type
for the file extent items.  Add the fscrypt_info to the extent_map and
the subsequent code for transferring it in the split and merge cases
as well as the code necessary to free them.  A future patch will add
the code to load them as appropriate.

Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/9818cd4134d048d2d641b9b8ae10be6c6af51956.1706116485.git.josef@toxicpanda.com/
 * No significant changes since. Just add some btrfs_ prefixes even
   to static functions in extent map header file to match the style.
---
 fs/btrfs/extent_map.c | 32 +++++++++++++++++++++++++++++---
 fs/btrfs/extent_map.h | 16 ++++++++++++++++
 fs/btrfs/file-item.c  |  1 +
 fs/btrfs/inode.c      |  1 +
 4 files changed, 47 insertions(+), 3 deletions(-)

diff --git a/fs/btrfs/extent_map.c b/fs/btrfs/extent_map.c
index 61fe262667ee..d13fb42dafab 100644
--- a/fs/btrfs/extent_map.c
+++ b/fs/btrfs/extent_map.c
@@ -59,6 +59,7 @@ struct extent_map *btrfs_alloc_extent_map(void)
 
 static void free_extent_map(struct extent_map *em)
 {
+	fscrypt_put_extent_info(em->fscrypt_info);
 	kmem_cache_free(extent_map_cache, em);
 }
 
@@ -99,12 +100,24 @@ void btrfs_free_extent_map_safe(struct extent_map_tree *tree,
 	if (!em)
 		return;
 
-	if (refcount_dec_and_test(&em->refs)) {
-		WARN_ON(btrfs_extent_map_in_tree(em));
-		WARN_ON(!list_empty(&em->list));
+	if (!refcount_dec_and_test(&em->refs))
+		return;
+
+	WARN_ON(btrfs_extent_map_in_tree(em));
+	WARN_ON(!list_empty(&em->list));
+
+	/*
+	 * We could take a lock freeing the fscrypt_info, so add this to the
+	 * list of freed_extents to be freed later.
+	 */
+	if (em->fscrypt_info) {
 		list_add_tail(&em->free_list, &tree->freed_extents);
 		set_bit(EXTENT_MAP_TREE_PENDING_FREES, &tree->flags);
+		return;
 	}
+
+	/* Nothing scary here, just free the object. */
+	free_extent_map(em);
 }
 
 /*
@@ -291,6 +304,10 @@ static bool can_merge_extent_map(const struct extent_map *em)
 	if (!list_empty(&em->list))
 		return false;
 
+	/* We can't merge encrypted extents. */
+	if (em->fscrypt_info)
+		return false;
+
 	return true;
 }
 
@@ -311,6 +328,10 @@ static bool mergeable_maps(const struct extent_map *prev, const struct extent_ma
 	if (next->disk_bytenr < EXTENT_MAP_LAST_BYTE - 1)
 		return btrfs_extent_map_block_start(next) == extent_map_block_end(prev);
 
+	/* Don't merge adjacent encrypted maps. */
+	if (prev->fscrypt_info || next->fscrypt_info)
+		return false;
+
 	/* HOLES and INLINE extents. */
 	return next->disk_bytenr == prev->disk_bytenr;
 }
@@ -977,6 +998,7 @@ void btrfs_drop_extent_map_range(struct btrfs_inode *inode, u64 start, u64 end,
 
 			split->generation = gen;
 			split->flags = flags;
+			split->fscrypt_info = fscrypt_get_extent_info(em->fscrypt_info);
 			replace_extent_mapping(inode, em, split, modified);
 			btrfs_free_extent_map(split);
 			split = split2;
@@ -1005,6 +1027,7 @@ void btrfs_drop_extent_map_range(struct btrfs_inode *inode, u64 start, u64 end,
 				split->ram_bytes = split->len;
 			}
 
+			split->fscrypt_info = fscrypt_get_extent_info(em->fscrypt_info);
 			if (btrfs_extent_map_in_tree(em)) {
 				replace_extent_mapping(inode, em, split, modified);
 			} else {
@@ -1163,6 +1186,7 @@ int btrfs_split_extent_map(struct btrfs_inode *inode, u64 start, u64 len, u64 pr
 	split_pre->ram_bytes = split_pre->len;
 	split_pre->flags = flags;
 	split_pre->generation = em->generation;
+	split_pre->fscrypt_info = fscrypt_get_extent_info(em->fscrypt_info);
 
 	replace_extent_mapping(inode, em, split_pre, true);
 
@@ -1180,6 +1204,8 @@ int btrfs_split_extent_map(struct btrfs_inode *inode, u64 start, u64 len, u64 pr
 	split_mid->ram_bytes = split_mid->len;
 	split_mid->flags = flags;
 	split_mid->generation = em->generation;
+	split_mid->fscrypt_info = fscrypt_get_extent_info(em->fscrypt_info);
+
 	add_extent_mapping(inode, split_mid, true);
 
 	/* Once for us */
diff --git a/fs/btrfs/extent_map.h b/fs/btrfs/extent_map.h
index a962012be1c3..a0d5be758e7e 100644
--- a/fs/btrfs/extent_map.h
+++ b/fs/btrfs/extent_map.h
@@ -24,6 +24,7 @@ enum {
 	ENUM_BIT(EXTENT_FLAG_COMPRESS_ZLIB),
 	ENUM_BIT(EXTENT_FLAG_COMPRESS_LZO),
 	ENUM_BIT(EXTENT_FLAG_COMPRESS_ZSTD),
+	ENUM_BIT(EXTENT_FLAG_ENCRYPT_FSCRYPT),
 	/* pre-allocated extent */
 	ENUM_BIT(EXTENT_FLAG_PREALLOC),
 	/* Logging this extent */
@@ -96,6 +97,7 @@ struct extent_map {
 	u64 generation;
 	u32 flags;
 	refcount_t refs;
+	struct fscrypt_extent_info *fscrypt_info;
 	struct list_head list;
 	struct list_head free_list;
 };
@@ -114,6 +116,20 @@ struct extent_map_tree {
 
 struct btrfs_inode;
 
+static inline void btrfs_extent_map_set_encryption(struct extent_map *em,
+					     enum btrfs_encryption_type type)
+{
+	if (type == BTRFS_ENCRYPTION_FSCRYPT)
+		em->flags |= EXTENT_FLAG_ENCRYPT_FSCRYPT;
+}
+
+static inline enum btrfs_encryption_type btrfs_extent_map_encryption(const struct extent_map *em)
+{
+	if (em->flags & EXTENT_FLAG_ENCRYPT_FSCRYPT)
+		return BTRFS_ENCRYPTION_FSCRYPT;
+	return BTRFS_ENCRYPTION_NONE;
+}
+
 static inline void btrfs_extent_map_set_compression(struct extent_map *em,
 						    enum btrfs_compression_type type)
 {
diff --git a/fs/btrfs/file-item.c b/fs/btrfs/file-item.c
index d72249390030..020d30709770 100644
--- a/fs/btrfs/file-item.c
+++ b/fs/btrfs/file-item.c
@@ -1364,6 +1364,7 @@ void btrfs_extent_item_to_extent_map(struct btrfs_inode *inode,
 			if (type == BTRFS_FILE_EXTENT_PREALLOC)
 				em->flags |= EXTENT_FLAG_PREALLOC;
 		}
+		btrfs_extent_map_set_encryption(em, btrfs_file_extent_encryption(leaf, fi));
 	} else if (type == BTRFS_FILE_EXTENT_INLINE) {
 		/* Tree-checker has ensured this. */
 		ASSERT(extent_start == 0);
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index bc2460292e43..17e2c77c584a 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -7655,6 +7655,7 @@ struct extent_map *btrfs_create_io_em(struct btrfs_inode *inode, u64 start,
 	em->flags |= EXTENT_FLAG_PINNED;
 	if (type == BTRFS_ORDERED_COMPRESSED)
 		btrfs_extent_map_set_compression(em, file_extent->compression);
+	btrfs_extent_map_set_encryption(em, BTRFS_ENCRYPTION_NONE);
 
 	ret = btrfs_replace_extent_map_range(inode, em, true);
 	if (ret) {
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 16/43] btrfs: select encryption dependencies if FS_ENCRYPTION
From: Daniel Vacek @ 2026-05-13  8:52 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Josef Bacik <josef@toxicpanda.com>

We need this to make sure the appropriate encryption algorithms are
turned on in our config if we have FS_ENCRYPTION enabled, and
additionally we only support inline encryption with the fallback block
crypto, so we need to make sure we pull in those dependencies.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v7 changes:
 * Alsos depend on BLK_INLINE_ENCRYPTION as suggested by Chris' AI review.
No changes in v6.
v5: https://lore.kernel.org/linux-btrfs/396f5067b2551a9f2de4b439509e1a285985d358.1706116485.git.josef@toxicpanda.com/
---
 fs/btrfs/Kconfig | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/fs/btrfs/Kconfig b/fs/btrfs/Kconfig
index 5e75438e0b73..3a41392135da 100644
--- a/fs/btrfs/Kconfig
+++ b/fs/btrfs/Kconfig
@@ -16,6 +16,10 @@ config BTRFS_FS
 	select RAID6_PQ
 	select XOR_BLOCKS
 	select XXHASH
+	select FS_ENCRYPTION_ALGS if FS_ENCRYPTION
+	select FS_ENCRYPTION_INLINE_CRYPT if FS_ENCRYPTION
+	select BLK_INLINE_ENCRYPTION if FS_ENCRYPTION
+	select BLK_INLINE_ENCRYPTION_FALLBACK if FS_ENCRYPTION
 	depends on PAGE_SIZE_LESS_THAN_256KB
 
 	help
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 17/43] btrfs: add get_devices hook for fscrypt
From: Daniel Vacek @ 2026-05-13  8:52 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Sweet Tea Dorminy
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>

Since extent encryption requires inline encryption, even though we
expect to use the inlinecrypt software fallback most of the time, we
need to enumerate all the devices in use by btrfs.

Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/47ff044fae65cb2b06a6bb6ffc91027511c4d1f4.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/btrfs/fscrypt.c | 36 ++++++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index 2fa33e69083d..1d29cfa7037c 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -12,7 +12,9 @@
 #include "ioctl.h"
 #include "messages.h"
 #include "root-tree.h"
+#include "super.h"
 #include "transaction.h"
+#include "volumes.h"
 #include "xattr.h"
 
 /*
@@ -177,10 +179,44 @@ static bool btrfs_fscrypt_empty_dir(struct inode *inode)
 	return inode->i_size == BTRFS_EMPTY_DIR_SIZE;
 }
 
+static struct block_device **btrfs_fscrypt_get_devices(struct super_block *sb,
+						       unsigned int *num_devs)
+{
+	struct btrfs_fs_info *fs_info = btrfs_sb(sb);
+	struct btrfs_fs_devices *fs_devices = fs_info->fs_devices;
+	int nr_devices = fs_devices->open_devices;
+	struct block_device **devs;
+	struct btrfs_device *device;
+	int i = 0;
+
+	devs = kmalloc_array(nr_devices, sizeof(*devs), GFP_NOFS | GFP_NOWAIT);
+	if (!devs)
+		return ERR_PTR(-ENOMEM);
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(device, &fs_devices->devices, dev_list) {
+		if (!test_bit(BTRFS_DEV_STATE_IN_FS_METADATA, &device->dev_state) ||
+		    !device->bdev ||
+		    test_bit(BTRFS_DEV_STATE_REPLACE_TGT, &device->dev_state))
+			continue;
+
+		devs[i++] = device->bdev;
+
+		if (i >= nr_devices)
+			break;
+
+	}
+	rcu_read_unlock();
+
+	*num_devs = i;
+	return devs;
+}
+
 const struct fscrypt_operations btrfs_fscrypt_ops = {
 	.inode_info_offs = (int)offsetof(struct btrfs_inode, i_crypt_info) -
 			   (int)offsetof(struct btrfs_inode, vfs_inode),
 	.get_context = btrfs_fscrypt_get_context,
 	.set_context = btrfs_fscrypt_set_context,
 	.empty_dir = btrfs_fscrypt_empty_dir,
+	.get_devices = btrfs_fscrypt_get_devices,
 };
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 15/43] btrfs: implement fscrypt ioctls
From: Daniel Vacek @ 2026-05-13  8:52 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Omar Sandoval, Sweet Tea Dorminy
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Omar Sandoval <osandov@osandov.com>

These ioctls allow encryption to actually be used.

The set_encryption_policy ioctl is the thing which actually turns on
encryption, and therefore sets the ENCRYPT flag in the superblock.
This prevents the filesystem from being loaded on older kernels.

fscrypt provides CONFIG_FS_ENCRYPTION-disabled versions of all these
functions which just return -EOPNOTSUPP, so the ioctls don't need
to be compiled out if CONFIG_FS_ENCRYPTION isn't enabled.

We could instead gate this ioctl on the superblock having the flag set,
if we wanted to require mkfs with the encrypt flag in order to have a
filesystem with any encryption.

Since this is a disk format change and is currently in development,
gate the fscrypt ioctls for btrfs behind BTRFS_EXPERIMENTAL.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

No changes in v7.
v6 changes:
 * Squash in https://lore.kernel.org/linux-btrfs/5134ab713b080f78b90eae46671596f72bbb1815.1706116485.git.josef@toxicpanda.com/
   ("btrfs: gate encryption behind BTRFS_DEBUG") and change it to
   BTRFS_EXPERIMENTAL as suggested by Dave.
v5: https://lore.kernel.org/linux-btrfs/6182b77868d0c9f9447641f869658ef017c1f467.1706116485.git.josef@toxicpanda.com/
---
 fs/btrfs/ioctl.c | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index 6a37dd3cc5ee..2e0b79f41197 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -5159,6 +5159,35 @@ long btrfs_ioctl(struct file *file, unsigned int
 		return btrfs_ioctl_get_fslabel(fs_info, argp);
 	case FS_IOC_SETFSLABEL:
 		return btrfs_ioctl_set_fslabel(file, argp);
+#ifdef CONFIG_BTRFS_EXPERIMENTAL
+	case FS_IOC_SET_ENCRYPTION_POLICY: {
+		if (!IS_ENABLED(CONFIG_FS_ENCRYPTION))
+			return -EOPNOTSUPP;
+		if (sb_rdonly(fs_info->sb))
+			return -EROFS;
+		/*
+		 *  If we crash before we commit, nothing encrypted could have
+		 * been written so it doesn't matter whether the encrypted
+		 * state persists.
+		 */
+		btrfs_set_fs_incompat(fs_info, ENCRYPT);
+		return fscrypt_ioctl_set_policy(file, (const void __user *)arg);
+	}
+	case FS_IOC_GET_ENCRYPTION_POLICY:
+		return fscrypt_ioctl_get_policy(file, (void __user *)arg);
+	case FS_IOC_GET_ENCRYPTION_POLICY_EX:
+		return fscrypt_ioctl_get_policy_ex(file, (void __user *)arg);
+	case FS_IOC_ADD_ENCRYPTION_KEY:
+		return fscrypt_ioctl_add_key(file, (void __user *)arg);
+	case FS_IOC_REMOVE_ENCRYPTION_KEY:
+		return fscrypt_ioctl_remove_key(file, (void __user *)arg);
+	case FS_IOC_REMOVE_ENCRYPTION_KEY_ALL_USERS:
+		return fscrypt_ioctl_remove_key_all_users(file, (void __user *)arg);
+	case FS_IOC_GET_ENCRYPTION_KEY_STATUS:
+		return fscrypt_ioctl_get_key_status(file, (void __user *)arg);
+	case FS_IOC_GET_ENCRYPTION_NONCE:
+		return fscrypt_ioctl_get_nonce(file, (void __user *)arg);
+#endif /* CONFIG_BTRFS_EXPERIMENTAL */
 	case FITRIM:
 		return btrfs_ioctl_fitrim(fs_info, argp);
 	case BTRFS_IOC_SNAP_CREATE:
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 12/43] btrfs: add new FEATURE_INCOMPAT_ENCRYPT flag
From: Daniel Vacek @ 2026-05-13  8:52 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Omar Sandoval, Sweet Tea Dorminy
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Omar Sandoval <osandov@osandov.com>

As encrypted files will be incompatible with older filesystem versions,
new filesystems should be created with an incompat flag for fscrypt,
which will gate access to the encryption ioctls.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/ccbea52046c1dadbbef926bfc878cc23af952729.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/btrfs/fs.h              | 3 ++-
 fs/btrfs/super.c           | 5 +++++
 fs/btrfs/sysfs.c           | 6 ++++++
 include/uapi/linux/btrfs.h | 1 +
 4 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h
index a4758d94b32e..dbdb73722c14 100644
--- a/fs/btrfs/fs.h
+++ b/fs/btrfs/fs.h
@@ -322,7 +322,8 @@ enum {
 	(BTRFS_FEATURE_INCOMPAT_SUPP_STABLE |	\
 	 BTRFS_FEATURE_INCOMPAT_RAID_STRIPE_TREE | \
 	 BTRFS_FEATURE_INCOMPAT_EXTENT_TREE_V2 | \
-	 BTRFS_FEATURE_INCOMPAT_REMAP_TREE)
+	 BTRFS_FEATURE_INCOMPAT_REMAP_TREE |	\
+	 BTRFS_FEATURE_INCOMPAT_ENCRYPT)
 
 #else
 
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index efaa0788c1fc..84df97363611 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -2563,6 +2563,11 @@ static int __init btrfs_print_mod_info(void)
 			", fsverity=yes"
 #else
 			", fsverity=no"
+#endif
+#ifdef CONFIG_FS_ENCRYPTION
+			", fscrypt=yes"
+#else
+			", fscrypt=no"
 #endif
 			;
 
diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c
index 0d14570c8bc2..3fe57843f902 100644
--- a/fs/btrfs/sysfs.c
+++ b/fs/btrfs/sysfs.c
@@ -305,6 +305,9 @@ BTRFS_FEAT_ATTR_INCOMPAT(remap_tree, REMAP_TREE);
 #ifdef CONFIG_FS_VERITY
 BTRFS_FEAT_ATTR_COMPAT_RO(verity, VERITY);
 #endif
+#ifdef CONFIG_FS_ENCRYPTION
+BTRFS_FEAT_ATTR_INCOMPAT(encryption, ENCRYPT);
+#endif /* CONFIG_FS_ENCRYPTION */
 
 /*
  * Features which depend on feature bits and may differ between each fs.
@@ -338,6 +341,9 @@ static struct attribute *btrfs_supported_feature_attrs[] = {
 #ifdef CONFIG_FS_VERITY
 	BTRFS_FEAT_ATTR_PTR(verity),
 #endif
+#ifdef CONFIG_FS_ENCRYPTION
+	BTRFS_FEAT_ATTR_PTR(encryption),
+#endif /* CONFIG_FS_ENCRYPTION */
 	NULL
 };
 
diff --git a/include/uapi/linux/btrfs.h b/include/uapi/linux/btrfs.h
index 9165154a274d..2f6a46e5f4ce 100644
--- a/include/uapi/linux/btrfs.h
+++ b/include/uapi/linux/btrfs.h
@@ -335,6 +335,7 @@ struct btrfs_ioctl_fs_info_args {
 #define BTRFS_FEATURE_INCOMPAT_ZONED		(1ULL << 12)
 #define BTRFS_FEATURE_INCOMPAT_EXTENT_TREE_V2	(1ULL << 13)
 #define BTRFS_FEATURE_INCOMPAT_RAID_STRIPE_TREE	(1ULL << 14)
+#define BTRFS_FEATURE_INCOMPAT_ENCRYPT		(1ULL << 15)
 #define BTRFS_FEATURE_INCOMPAT_SIMPLE_QUOTA	(1ULL << 16)
 #define BTRFS_FEATURE_INCOMPAT_REMAP_TREE	(1ULL << 17)
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 14/43] btrfs: handle nokey names
From: Daniel Vacek @ 2026-05-13  8:52 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Sweet Tea Dorminy
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>

For encrypted or unencrypted names, we calculate the offset for the dir
item by hashing the name for the dir item. However, this doesn't work
for a long nokey name, where we do not have the complete ciphertext.
Instead, fscrypt stores the filesystem-provided hash in the nokey name,
and we can extract it from the fscrypt_name structure in such a case.

Additionally, for nokey names, if we find the nokey name on disk we can
update the fscrypt_name with the disk name, so add that to searching for
diritems.

Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v7 changes:
 * Fix a memory leak as found by Chris' AI review.
No changes in v6.
v5: https://lore.kernel.org/linux-btrfs/a7e3aec8ec03814b4baa1d929ae8fb77f13c17d2.1706116485.git.josef@toxicpanda.com/
---
 fs/btrfs/dir-item.c | 43 +++++++++++++++++++++++++++++++++++++++++--
 fs/btrfs/fscrypt.c  | 27 +++++++++++++++++++++++++++
 fs/btrfs/fscrypt.h  | 11 +++++++++++
 3 files changed, 79 insertions(+), 2 deletions(-)

diff --git a/fs/btrfs/dir-item.c b/fs/btrfs/dir-item.c
index 1525b945646b..09ce1b85af74 100644
--- a/fs/btrfs/dir-item.c
+++ b/fs/btrfs/dir-item.c
@@ -228,6 +228,35 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans,
 	return di;
 }
 
+/*
+ * If appropriate, populate the disk name for a fscrypt_name looked up without
+ * a key.
+ *
+ * @path:	The path to the extent buffer in which the name was found.
+ * @di:		The dir item corresponding.
+ * @fname:	The fscrypt_name to perhaps populate.
+ *
+ * Returns: 0 if the name is already populated or the dir item doesn't exist
+ * or the name was successfully populated, else an error code.
+ */
+static int ensure_disk_name_from_dir_item(struct btrfs_path *path,
+					  struct btrfs_dir_item *di,
+					  struct fscrypt_name *name)
+{
+	int ret;
+
+	if (name->disk_name.name || !di)
+		return 0;
+
+	ret = btrfs_fscrypt_get_disk_name(path->nodes[0], di, &name->disk_name);
+	if (ret)
+		return ret;
+
+	name->crypto_buf.name = name->disk_name.name;
+	name->crypto_buf.len  = name->disk_name.len;
+	return 0;
+}
+
 /*
  * Lookup for a directory item by fscrypt_name.
  *
@@ -254,8 +283,11 @@ struct btrfs_dir_item *btrfs_lookup_dir_item_fname(struct btrfs_trans_handle *tr
 
 	key.objectid = dir;
 	key.type = BTRFS_DIR_ITEM_KEY;
-	key.offset = btrfs_name_hash(name->disk_name.name, name->disk_name.len);
-	/* XXX get the right hash for no-key names */
+
+	if (!name->disk_name.name)
+		key.offset = name->hash | ((u64)name->minor_hash << 32);
+	else
+		key.offset = btrfs_name_hash(name->disk_name.name, name->disk_name.len);
 
 	ret = btrfs_search_slot(trans, root, &key, path, mod, -mod);
 	if (ret == 0)
@@ -263,6 +295,8 @@ struct btrfs_dir_item *btrfs_lookup_dir_item_fname(struct btrfs_trans_handle *tr
 
 	if (ret == -ENOENT || (di && IS_ERR(di) && PTR_ERR(di) == -ENOENT))
 		return NULL;
+	if (ret == 0)
+		ret = ensure_disk_name_from_dir_item(path, di, name);
 	if (ret < 0)
 		di = ERR_PTR(ret);
 
@@ -371,7 +405,12 @@ btrfs_search_dir_index_item(struct btrfs_root *root, struct btrfs_path *path,
 	btrfs_for_each_slot(root, &key, &key, path, ret) {
 		if (key.objectid != dirid || key.type != BTRFS_DIR_INDEX_KEY)
 			break;
+
 		di = btrfs_match_dir_item_fname(path, name);
+		if (di)
+			ret = ensure_disk_name_from_dir_item(path, di, name);
+		if (ret)
+			break;
 		if (di)
 			return di;
 	}
diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index ae4f15762842..2fa33e69083d 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -15,6 +15,33 @@
 #include "transaction.h"
 #include "xattr.h"
 
+/*
+ * From a given location in a leaf, read a name into a qstr (usually a
+ * fscrypt_name's disk_name), allocating the required buffer. Used for
+ * nokey names.
+ */
+int btrfs_fscrypt_get_disk_name(struct extent_buffer *leaf,
+				struct btrfs_dir_item *dir_item,
+				struct fscrypt_str *name)
+{
+	unsigned long de_name_len = btrfs_dir_name_len(leaf, dir_item);
+	unsigned long de_name = (unsigned long)(dir_item + 1);
+	/*
+	 * For no-key names, we use this opportunity to find the disk
+	 * name, so future searches don't need to deal with nokey names
+	 * and we know what the encrypted size is.
+	 */
+	name->name = kmalloc(de_name_len, GFP_NOFS);
+
+	if (!name->name)
+		return -ENOMEM;
+
+	read_extent_buffer(leaf, name->name, de_name, de_name_len);
+
+	name->len = de_name_len;
+	return 0;
+}
+
 /*
  * This function is extremely similar to fscrypt_match_name() but uses an
  * extent_buffer.
diff --git a/fs/btrfs/fscrypt.h b/fs/btrfs/fscrypt.h
index 4e9c87290158..c5cdc27f943c 100644
--- a/fs/btrfs/fscrypt.h
+++ b/fs/btrfs/fscrypt.h
@@ -9,11 +9,22 @@
 #include "fs.h"
 
 #ifdef CONFIG_FS_ENCRYPTION
+int btrfs_fscrypt_get_disk_name(struct extent_buffer *leaf,
+				struct btrfs_dir_item *di,
+				struct fscrypt_str *qstr);
+
 bool btrfs_fscrypt_match_name(struct fscrypt_name *fname,
 			      struct extent_buffer *leaf,
 			      unsigned long de_name, u32 de_name_len);
 
 #else
+static inline int btrfs_fscrypt_get_disk_name(struct extent_buffer *leaf,
+					      struct btrfs_dir_item *di,
+					      struct fscrypt_str *qstr)
+{
+	return 0;
+}
+
 static inline bool btrfs_fscrypt_match_name(struct fscrypt_name *fname,
 					    struct extent_buffer *leaf,
 					    unsigned long de_name,
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 10/43] btrfs: start using fscrypt hooks
From: Daniel Vacek @ 2026-05-13  8:52 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Omar Sandoval, Sweet Tea Dorminy
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Omar Sandoval <osandov@osandov.com>

In order to appropriately encrypt, create, open, rename, and various
symlink operations must call fscrypt hooks. These determine whether the
inode should be encrypted and do other preparatory actions. The
superblock must have fscrypt operations registered, so implement the
minimal set also, and introduce the new fscrypt.[ch] files to hold the
fscrypt-specific functionality.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v7 changes:
 * Fix rebase conflicts.
 * Fix a memory leak as suggested by Chris' AI review.
v6 changes:
 * FSCrypt info moved from VFS inode to FS specific inode structure.
 * Trivial renames.
v5: https://lore.kernel.org/linux-btrfs/333de15efb2d7ec220293c4b5e3782fe85634c06.1706116485.git.josef@toxicpanda.com/
---
 fs/btrfs/Makefile      |   1 +
 fs/btrfs/btrfs_inode.h |   5 ++
 fs/btrfs/file.c        |   3 ++
 fs/btrfs/fscrypt.c     |  10 ++++
 fs/btrfs/fscrypt.h     |  10 ++++
 fs/btrfs/inode.c       | 105 ++++++++++++++++++++++++++++++++++-------
 fs/btrfs/super.c       |   2 +
 7 files changed, 119 insertions(+), 17 deletions(-)
 create mode 100644 fs/btrfs/fscrypt.c
 create mode 100644 fs/btrfs/fscrypt.h

diff --git a/fs/btrfs/Makefile b/fs/btrfs/Makefile
index b15122aa26f9..7397db45eb36 100644
--- a/fs/btrfs/Makefile
+++ b/fs/btrfs/Makefile
@@ -38,6 +38,7 @@ btrfs-$(CONFIG_BTRFS_FS_POSIX_ACL) += acl.o
 btrfs-$(CONFIG_BTRFS_DEBUG) += ref-verify.o
 btrfs-$(CONFIG_BLK_DEV_ZONED) += zoned.o
 btrfs-$(CONFIG_FS_VERITY) += verity.o
+btrfs-$(CONFIG_FS_ENCRYPTION) += fscrypt.o
 
 btrfs-$(CONFIG_BTRFS_FS_RUN_SANITY_TESTS) += tests/free-space-tests.o \
 	tests/extent-buffer-tests.o tests/btrfs-tests.o \
diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h
index 55c272fe5d92..81940b27b037 100644
--- a/fs/btrfs/btrfs_inode.h
+++ b/fs/btrfs/btrfs_inode.h
@@ -339,6 +339,10 @@ struct btrfs_inode {
 
 	struct rw_semaphore i_mmap_lock;
 
+#ifdef CONFIG_FS_ENCRYPTION
+	struct fscrypt_inode_info *i_crypt_info;
+#endif
+
 	struct inode vfs_inode;
 };
 
@@ -582,6 +586,7 @@ struct btrfs_new_inode_args {
 	struct posix_acl *default_acl;
 	struct posix_acl *acl;
 	struct fscrypt_name fname;
+	bool encrypt;
 };
 
 int btrfs_new_inode_prepare(struct btrfs_new_inode_args *args,
diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index cf1cb5c4db75..8b0ebdeca9dd 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -3813,6 +3813,9 @@ static int btrfs_file_open(struct inode *inode, struct file *filp)
 		return -EIO;
 
 	filp->f_mode |= FMODE_NOWAIT | FMODE_CAN_ODIRECT;
+	ret = fscrypt_file_open(inode, filp);
+	if (ret)
+		return ret;
 
 	ret = fsverity_file_open(inode, filp);
 	if (ret)
diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
new file mode 100644
index 000000000000..6cfba7d94e72
--- /dev/null
+++ b/fs/btrfs/fscrypt.c
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "ctree.h"
+#include "btrfs_inode.h"
+#include "fscrypt.h"
+
+const struct fscrypt_operations btrfs_fscrypt_ops = {
+	.inode_info_offs = (int)offsetof(struct btrfs_inode, i_crypt_info) -
+			   (int)offsetof(struct btrfs_inode, vfs_inode),
+};
diff --git a/fs/btrfs/fscrypt.h b/fs/btrfs/fscrypt.h
new file mode 100644
index 000000000000..7f4e6888bd43
--- /dev/null
+++ b/fs/btrfs/fscrypt.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef BTRFS_FSCRYPT_H
+#define BTRFS_FSCRYPT_H
+
+#include <linux/fscrypt.h>
+
+extern const struct fscrypt_operations btrfs_fscrypt_ops;
+
+#endif /* BTRFS_FSCRYPT_H */
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 906d5c21ebc4..8f89e44b2d53 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -5490,6 +5490,10 @@ static int btrfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
 	if (ret)
 		return ret;
 
+	ret = fscrypt_prepare_setattr(dentry, attr);
+	if (ret)
+		return ret;
+
 	if (S_ISREG(inode->i_mode) && (attr->ia_valid & ATTR_SIZE)) {
 		ret = btrfs_setsize(inode, attr);
 		if (ret)
@@ -5746,6 +5750,7 @@ void btrfs_evict_inode(struct inode *inode)
 	 */
 	btrfs_remove_delayed_node(BTRFS_I(inode));
 clear_inode:
+	fscrypt_put_encryption_info(inode);
 	clear_inode(inode);
 }
 
@@ -6537,6 +6542,12 @@ int btrfs_new_inode_prepare(struct btrfs_new_inode_args *args,
 		return ret;
 	}
 
+	ret = fscrypt_prepare_new_inode(dir, inode, &args->encrypt);
+	if (ret) {
+		btrfs_new_inode_args_destroy(args);
+		return ret;
+	}
+
 	/* 1 to add inode item */
 	*trans_num_items = 1;
 	/* 1 to add compression property */
@@ -7048,9 +7059,13 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
 	if (inode->i_nlink >= BTRFS_LINK_MAX)
 		return -EMLINK;
 
+	ret = fscrypt_prepare_link(old_dentry, dir, dentry);
+	if (ret)
+		return ret;
+
 	ret = fscrypt_setup_filename(dir, &dentry->d_name, 0, &fname);
 	if (ret)
-		goto fail;
+		return ret;
 
 	ret = btrfs_set_inode_index(BTRFS_I(dir), &index);
 	if (ret)
@@ -8109,6 +8124,9 @@ struct inode *btrfs_alloc_inode(struct super_block *sb)
 	INIT_LIST_HEAD(&ei->delayed_iput);
 	init_rwsem(&ei->i_mmap_lock);
 
+#ifdef CONFIG_FS_ENCRYPTION
+	ei->i_crypt_info = NULL;
+#endif
 	return inode;
 }
 
@@ -8124,6 +8142,7 @@ void btrfs_test_destroy_inode(struct inode *inode)
 void btrfs_free_inode(struct inode *inode)
 {
 	kfree(BTRFS_I(inode)->file_extent_tree);
+	fscrypt_free_inode(inode);
 	kmem_cache_free(btrfs_inode_cachep, BTRFS_I(inode));
 }
 
@@ -8195,8 +8214,7 @@ int btrfs_drop_inode(struct inode *inode)
 	/* the snap/subvol tree is on deleting */
 	if (btrfs_root_refs(&root->root_item) == 0)
 		return 1;
-	else
-		return inode_generic_drop(inode);
+	return inode_generic_drop(inode) || fscrypt_drop_inode(inode);
 }
 
 static void init_once(void *foo)
@@ -8847,6 +8865,10 @@ static int btrfs_rename2(struct mnt_idmap *idmap, struct inode *old_dir,
 	if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE | RENAME_WHITEOUT))
 		return -EINVAL;
 
+	ret = fscrypt_prepare_rename(old_dir, old_dentry, new_dir, new_dentry, flags);
+	if (ret)
+		return ret;
+
 	if (flags & RENAME_EXCHANGE)
 		ret = btrfs_rename_exchange(old_dir, old_dentry, new_dir,
 					    new_dentry);
@@ -9041,20 +9063,28 @@ static int btrfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
 	};
 	unsigned int trans_num_items;
 	int ret;
-	int name_len;
 	int datasize;
 	unsigned long ptr;
 	struct btrfs_file_extent_item *ei;
 	struct extent_buffer *leaf;
+	struct fscrypt_str disk_link;
+	size_t max_len;
+	u32 name_len = strlen(symname);
+
+	/*
+	 * BTRFS_MAX_INLINE_DATA_SIZE() isn't actually telling the truth, we actually
+	 * limit inline data extents to min(BTRFS_MAX_INLINE_DATA_SIZE(), sectorsize),
+	 * so adjust max_len given this wonderful bit of inconsistency.
+	 */
+	max_len = min_t(size_t, BTRFS_MAX_INLINE_DATA_SIZE(fs_info), fs_info->sectorsize);
 
-	name_len = strlen(symname);
 	/*
-	 * Symlinks utilize uncompressed inline extent data, which should not
-	 * reach block size.
+	 * fscrypt sets disk_link.len to be len + 1, including a NUL terminator,
+	 * but we don't store that '\0' character.
 	 */
-	if (name_len > BTRFS_MAX_INLINE_DATA_SIZE(fs_info) ||
-	    name_len >= fs_info->sectorsize)
-		return -ENAMETOOLONG;
+	ret = fscrypt_prepare_symlink(dir, symname, name_len, max_len + 1, &disk_link);
+	if (ret)
+		return ret;
 
 	inode = new_inode(dir->i_sb);
 	if (!inode)
@@ -9063,8 +9093,8 @@ static int btrfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
 	inode->i_op = &btrfs_symlink_inode_operations;
 	inode_nohighmem(inode);
 	inode->i_mapping->a_ops = &btrfs_aops;
-	btrfs_i_size_write(BTRFS_I(inode), name_len);
-	inode_set_bytes(inode, name_len);
+	btrfs_i_size_write(BTRFS_I(inode), disk_link.len - 1);
+	inode_set_bytes(inode, disk_link.len - 1);
 
 	new_inode_args.inode = inode;
 	ret = btrfs_new_inode_prepare(&new_inode_args, &trans_num_items);
@@ -9091,17 +9121,29 @@ static int btrfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
 		inode = NULL;
 		goto out;
 	}
+
+	if (IS_ENCRYPTED(inode)) {
+		ret = fscrypt_encrypt_symlink(inode, symname, name_len, &disk_link);
+		if (ret) {
+			btrfs_abort_transaction(trans, ret);
+			btrfs_free_path(path);
+			discard_new_inode(inode);
+			inode = NULL;
+			goto out;
+		}
+	}
+
 	key.objectid = btrfs_ino(BTRFS_I(inode));
 	key.type = BTRFS_EXTENT_DATA_KEY;
 	key.offset = 0;
-	datasize = btrfs_file_extent_calc_inline_size(name_len);
+	datasize = btrfs_file_extent_calc_inline_size(disk_link.len - 1);
 	ret = btrfs_insert_empty_item(trans, root, path, &key, datasize);
 	if (unlikely(ret)) {
 		btrfs_abort_transaction(trans, ret);
 		btrfs_free_path(path);
 		discard_new_inode(inode);
 		inode = NULL;
-		goto out;
+		goto free_name;
 	}
 	leaf = path->nodes[0];
 	ei = btrfs_item_ptr(leaf, path->slots[0],
@@ -9112,14 +9154,17 @@ static int btrfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
 	btrfs_set_file_extent_encryption(leaf, ei, 0);
 	btrfs_set_file_extent_compression(leaf, ei, 0);
 	btrfs_set_file_extent_other_encoding(leaf, ei, 0);
-	btrfs_set_file_extent_ram_bytes(leaf, ei, name_len);
+	btrfs_set_file_extent_ram_bytes(leaf, ei, disk_link.len - 1);
 
 	ptr = btrfs_file_extent_inline_start(ei);
-	write_extent_buffer(leaf, symname, ptr, name_len);
+	write_extent_buffer(leaf, disk_link.name, ptr, disk_link.len - 1);
 	btrfs_free_path(path);
 
 	d_instantiate_new(dentry, inode);
 	ret = 0;
+free_name:
+	if (disk_link.name != (unsigned char *)symname)
+		kfree(disk_link.name);
 out:
 	btrfs_end_transaction(trans);
 	btrfs_btree_balance_dirty(fs_info);
@@ -9131,6 +9176,29 @@ static int btrfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
 	return ret;
 }
 
+static const char *btrfs_get_link(struct dentry *dentry, struct inode *inode,
+				  struct delayed_call *done)
+{
+	struct page *cpage;
+	const char *paddr;
+	struct btrfs_fs_info *fs_info = btrfs_sb(inode->i_sb);
+
+	if (!IS_ENCRYPTED(inode))
+		return page_get_link(dentry, inode, done);
+
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
+	cpage = read_mapping_page(inode->i_mapping, 0, NULL);
+	if (IS_ERR(cpage))
+		return ERR_CAST(cpage);
+
+	paddr = fscrypt_get_symlink(inode, page_address(cpage),
+				    BTRFS_MAX_INLINE_DATA_SIZE(fs_info), done);
+	put_page(cpage);
+	return paddr;
+}
+
 static struct btrfs_trans_handle *insert_prealloc_file_extent(
 				       struct btrfs_trans_handle *trans_in,
 				       struct btrfs_inode *inode,
@@ -10762,7 +10830,7 @@ static const struct inode_operations btrfs_special_inode_operations = {
 	.update_time	= btrfs_update_time,
 };
 static const struct inode_operations btrfs_symlink_inode_operations = {
-	.get_link	= page_get_link,
+	.get_link	= btrfs_get_link,
 	.getattr	= btrfs_getattr,
 	.setattr	= btrfs_setattr,
 	.permission	= btrfs_permission,
@@ -10772,4 +10840,7 @@ static const struct inode_operations btrfs_symlink_inode_operations = {
 
 const struct dentry_operations btrfs_dentry_operations = {
 	.d_delete	= btrfs_dentry_delete,
+#ifdef CONFIG_FS_ENCRYPTION
+	.d_revalidate	= fscrypt_d_revalidate,
+#endif
 };
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index b26aa9169e83..efaa0788c1fc 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -49,6 +49,7 @@
 #include "tests/btrfs-tests.h"
 #include "block-group.h"
 #include "discard.h"
+#include "fscrypt.h"
 #include "qgroup.h"
 #include "raid56.h"
 #include "fs.h"
@@ -969,6 +970,7 @@ static int btrfs_fill_super(struct super_block *sb,
 	sb->s_vop = &btrfs_verityops;
 #endif
 	sb->s_xattr = btrfs_xattr_handlers;
+	fscrypt_set_ops(sb, &btrfs_fscrypt_ops);
 	sb->s_time_gran = 1;
 	sb->s_iflags |= SB_I_CGROUPWB | SB_I_ALLOW_HSM;
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 13/43] btrfs: adapt readdir for encrypted and nokey names
From: Daniel Vacek @ 2026-05-13  8:52 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Omar Sandoval, Sweet Tea Dorminy
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Omar Sandoval <osandov@osandov.com>

Deleting an encrypted file must always be permitted, even if the user
does not have the appropriate key. Therefore, for listing an encrypted
directory, so-called 'nokey' names are provided, and these nokey names
must be sufficient to look up and delete the appropriate encrypted
files. See 'struct fscrypt_nokey_name' for more information on the
format of these names.

The first part of supporting nokey names is allowing lookups by nokey
name. Only a few entry points need to support these: deleting a
directory, file, or subvolume -- each of these call
fscrypt_setup_filename() with a '1' argument, indicating that the key is
not required and therefore a nokey name may be provided. If a nokey name
is provided, the fscrypt_name returned by fscrypt_setup_filename() will
not have its disk_name field populated, but will have various other
fields set.

This change alters the relevant codepaths to pass a complete
fscrypt_name anywhere that it might contain a nokey name. When it does
contain a nokey name, the first time the name is successfully matched
to a stored name populates the disk name field of the fscrypt_name,
allowing the caller to use the normal disk name codepaths afterward.
Otherwise, the matching functionality is in close analogue to the
function fscrypt_match_name().

Functions where most callers are providing a fscrypt_str are duplicated
and adapted for a fscrypt_name, and functions where most callers are
providing a fscrypt_name are changed to so require at all callsites.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v7 changes:
 * Return true instead of error as suggested by Chris' AI review.
v6 changes:
 * Adapt to changed prototypes.
 * Return error directly instead of goto out and remove the label in
   btrfs_inode_by_name().
v5: https://lore.kernel.org/linux-btrfs/52b31cd6e5c86f8fe3d08ce65bca5f40c3ce5c5a.1706116485.git.josef@toxicpanda.com/
---
 fs/btrfs/btrfs_inode.h   |   2 +-
 fs/btrfs/delayed-inode.c |  25 ++++++-
 fs/btrfs/delayed-inode.h |   5 +-
 fs/btrfs/dir-item.c      |  73 +++++++++++++++---
 fs/btrfs/dir-item.h      |  10 ++-
 fs/btrfs/extent_io.c     |  41 +++++++++++
 fs/btrfs/extent_io.h     |   3 +
 fs/btrfs/fscrypt.c       |  33 +++++++++
 fs/btrfs/fscrypt.h       |  18 +++++
 fs/btrfs/inode.c         | 155 ++++++++++++++++++++++++---------------
 fs/btrfs/root-tree.c     |   9 ++-
 fs/btrfs/root-tree.h     |   2 +-
 fs/btrfs/tree-log.c      |   3 +-
 13 files changed, 301 insertions(+), 78 deletions(-)

diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h
index 81940b27b037..9499e15609b3 100644
--- a/fs/btrfs/btrfs_inode.h
+++ b/fs/btrfs/btrfs_inode.h
@@ -560,7 +560,7 @@ struct inode *btrfs_lookup_dentry(struct inode *dir, struct dentry *dentry);
 int btrfs_set_inode_index(struct btrfs_inode *dir, u64 *index);
 int btrfs_unlink_inode(struct btrfs_trans_handle *trans,
 		       struct btrfs_inode *dir, struct btrfs_inode *inode,
-		       const struct fscrypt_str *name);
+		       struct fscrypt_name *name);
 int btrfs_add_link(struct btrfs_trans_handle *trans,
 		   struct btrfs_inode *parent_inode, struct btrfs_inode *inode,
 		   const struct fscrypt_str *name, bool add_backref, u64 index);
diff --git a/fs/btrfs/delayed-inode.c b/fs/btrfs/delayed-inode.c
index 09795439b9fb..097fe6ec60ba 100644
--- a/fs/btrfs/delayed-inode.c
+++ b/fs/btrfs/delayed-inode.c
@@ -1784,7 +1784,9 @@ bool btrfs_should_delete_dir_index(const struct list_head *del_list, u64 index)
 /*
  * Read dir info stored in the delayed tree.
  */
-bool btrfs_readdir_delayed_dir_index(struct dir_context *ctx,
+bool btrfs_readdir_delayed_dir_index(const struct inode *inode,
+				     struct fscrypt_str *fstr,
+				     struct dir_context *ctx,
 				     const struct list_head *ins_list)
 {
 	struct btrfs_dir_item *di;
@@ -1793,6 +1795,7 @@ bool btrfs_readdir_delayed_dir_index(struct dir_context *ctx,
 	char *name;
 	int name_len;
 	unsigned char d_type;
+	size_t fstr_len = fstr->len;
 
 	/*
 	 * Changing the data of the delayed item is impossible. So
@@ -1819,7 +1822,25 @@ bool btrfs_readdir_delayed_dir_index(struct dir_context *ctx,
 		d_type = fs_ftype_to_dtype(btrfs_dir_flags_to_ftype(di->type));
 		btrfs_disk_key_to_cpu(&location, &di->location);
 
-		over = !dir_emit(ctx, name, name_len, location.objectid, d_type);
+		if (di->type & BTRFS_FT_ENCRYPTED) {
+			int ret;
+			struct fscrypt_str iname = FSTR_INIT(name, name_len);
+
+			fstr->len = fstr_len;
+			/*
+			 * The hash is only used when the encryption key is not
+			 * available. But if we have delayed insertions, then we
+			 * must have the encryption key available or we wouldn't
+			 * have been able to create entries in the directory.
+			 * So, we don't calculate the hash.
+			 */
+			ret = fscrypt_fname_disk_to_usr(inode, 0, 0, &iname, fstr);
+			if (ret)
+				return true;
+			over = !dir_emit(ctx, fstr->name, fstr->len, location.objectid, d_type);
+		} else {
+			over = !dir_emit(ctx, name, name_len, location.objectid, d_type);
+		}
 
 		if (refcount_dec_and_test(&curr->refs))
 			kfree(curr);
diff --git a/fs/btrfs/delayed-inode.h b/fs/btrfs/delayed-inode.h
index fc752863f89b..9bff11478632 100644
--- a/fs/btrfs/delayed-inode.h
+++ b/fs/btrfs/delayed-inode.h
@@ -19,6 +19,7 @@
 #include <linux/ref_tracker.h>
 #include "ctree.h"
 
+struct fscrypt_str;
 struct btrfs_disk_key;
 struct btrfs_fs_info;
 struct btrfs_inode;
@@ -159,7 +160,9 @@ void btrfs_readdir_put_delayed_items(struct btrfs_inode *inode,
 				     struct list_head *ins_list,
 				     struct list_head *del_list);
 bool btrfs_should_delete_dir_index(const struct list_head *del_list, u64 index);
-bool btrfs_readdir_delayed_dir_index(struct dir_context *ctx,
+bool btrfs_readdir_delayed_dir_index(const struct inode *inode,
+				     struct fscrypt_str *fstr,
+				     struct dir_context *ctx,
 				     const struct list_head *ins_list);
 
 /* Used during directory logging. */
diff --git a/fs/btrfs/dir-item.c b/fs/btrfs/dir-item.c
index 84f1c64423d3..1525b945646b 100644
--- a/fs/btrfs/dir-item.c
+++ b/fs/btrfs/dir-item.c
@@ -6,6 +6,7 @@
 #include "messages.h"
 #include "ctree.h"
 #include "disk-io.h"
+#include "fscrypt.h"
 #include "transaction.h"
 #include "accessors.h"
 #include "dir-item.h"
@@ -227,6 +228,47 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans,
 	return di;
 }
 
+/*
+ * Lookup for a directory item by fscrypt_name.
+ *
+ * @trans:	The transaction handle to use.
+ * @root:	The root of the target tree.
+ * @path:	Path to use for the search.
+ * @dir:	The inode number (objectid) of the directory.
+ * @name:	The fscrypt_name associated to the directory entry
+ * @mod:	Used to indicate if the tree search is meant for a read only
+ *		lookup or for a deletion lookup, so its value should be 0 or
+ *		-1, respectively.
+ *
+ * Returns: NULL if the dir item does not exists, an error pointer if an error
+ * happened, or a pointer to a dir item if a dir item exists for the given name.
+ */
+struct btrfs_dir_item *btrfs_lookup_dir_item_fname(struct btrfs_trans_handle *trans,
+						   struct btrfs_root *root,
+						   struct btrfs_path *path, u64 dir,
+						   struct fscrypt_name *name, int mod)
+{
+	struct btrfs_key key;
+	struct btrfs_dir_item *di = NULL;
+	int ret = 0;
+
+	key.objectid = dir;
+	key.type = BTRFS_DIR_ITEM_KEY;
+	key.offset = btrfs_name_hash(name->disk_name.name, name->disk_name.len);
+	/* XXX get the right hash for no-key names */
+
+	ret = btrfs_search_slot(trans, root, &key, path, mod, -mod);
+	if (ret == 0)
+		di = btrfs_match_dir_item_fname(path, name);
+
+	if (ret == -ENOENT || (di && IS_ERR(di) && PTR_ERR(di) == -ENOENT))
+		return NULL;
+	if (ret < 0)
+		di = ERR_PTR(ret);
+
+	return di;
+}
+
 int btrfs_check_dir_item_collision(struct btrfs_root *root, u64 dir_ino,
 				   const struct fscrypt_str *name)
 {
@@ -276,9 +318,9 @@ int btrfs_check_dir_item_collision(struct btrfs_root *root, u64 dir_ino,
 }
 
 /*
- * Lookup for a directory index item by name and index number.
+ * Lookup for a directory index item by fscrypt_name and index number.
  *
- * @trans:	The transaction handle to use. Can be NULL if @mod is 0.
+ * @trans:	The transaction handle to use.
  * @root:	The root of the target tree.
  * @path:	Path to use for the search.
  * @dir:	The inode number (objectid) of the directory.
@@ -316,7 +358,7 @@ btrfs_lookup_dir_index_item(struct btrfs_trans_handle *trans,
 
 struct btrfs_dir_item *
 btrfs_search_dir_index_item(struct btrfs_root *root, struct btrfs_path *path,
-			    u64 dirid, const struct fscrypt_str *name)
+			    u64 dirid, struct fscrypt_name *name)
 {
 	struct btrfs_dir_item *di;
 	struct btrfs_key key;
@@ -329,8 +371,7 @@ btrfs_search_dir_index_item(struct btrfs_root *root, struct btrfs_path *path,
 	btrfs_for_each_slot(root, &key, &key, path, ret) {
 		if (key.objectid != dirid || key.type != BTRFS_DIR_INDEX_KEY)
 			break;
-
-		di = btrfs_match_dir_item_name(path, name->name, name->len);
+		di = btrfs_match_dir_item_fname(path, name);
 		if (di)
 			return di;
 	}
@@ -366,8 +407,8 @@ struct btrfs_dir_item *btrfs_lookup_xattr(struct btrfs_trans_handle *trans,
  * this walks through all the entries in a dir item and finds one
  * for a specific name.
  */
-struct btrfs_dir_item *btrfs_match_dir_item_name(const struct btrfs_path *path,
-						 const char *name, int name_len)
+struct btrfs_dir_item *btrfs_match_dir_item_fname(const struct btrfs_path *path,
+						  struct fscrypt_name *name)
 {
 	struct btrfs_dir_item *dir_item;
 	unsigned long name_ptr;
@@ -386,8 +427,8 @@ struct btrfs_dir_item *btrfs_match_dir_item_name(const struct btrfs_path *path,
 			btrfs_dir_data_len(leaf, dir_item);
 		name_ptr = (unsigned long)(dir_item + 1);
 
-		if (btrfs_dir_name_len(leaf, dir_item) == name_len &&
-		    memcmp_extent_buffer(leaf, name, name_ptr, name_len) == 0)
+		if (btrfs_fscrypt_match_name(name, leaf, name_ptr,
+					     btrfs_dir_name_len(leaf, dir_item)))
 			return dir_item;
 
 		cur += this_len;
@@ -397,6 +438,20 @@ struct btrfs_dir_item *btrfs_match_dir_item_name(const struct btrfs_path *path,
 	return NULL;
 }
 
+/*
+ * helper function to look at the directory item pointed to by 'path'
+ * this walks through all the entries in a dir item and finds one
+ * for a specific name.
+ */
+struct btrfs_dir_item *btrfs_match_dir_item_name(const struct btrfs_path *path,
+						 const char *name, int name_len)
+{
+	struct fscrypt_name fname = {
+		.disk_name = FSTR_INIT((char *) name, name_len)
+	};
+	return btrfs_match_dir_item_fname(path, &fname);
+}
+
 /*
  * given a pointer into a directory item, delete it.  This
  * handles items that have more than one entry in them.
diff --git a/fs/btrfs/dir-item.h b/fs/btrfs/dir-item.h
index e52174a8baf9..f89d12ee28a7 100644
--- a/fs/btrfs/dir-item.h
+++ b/fs/btrfs/dir-item.h
@@ -7,6 +7,7 @@
 #include <linux/crc32c.h>
 
 struct fscrypt_str;
+struct fscrypt_name;
 struct btrfs_fs_info;
 struct btrfs_key;
 struct btrfs_path;
@@ -23,6 +24,11 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans,
 					     struct btrfs_root *root,
 					     struct btrfs_path *path, u64 dir,
 					     const struct fscrypt_str *name, int mod);
+struct btrfs_dir_item *btrfs_lookup_dir_item_fname(
+			struct btrfs_trans_handle *trans,
+			struct btrfs_root *root,
+			struct btrfs_path *path, u64 dir,
+			struct fscrypt_name *name, int mod);
 struct btrfs_dir_item *btrfs_lookup_dir_index_item(
 			struct btrfs_trans_handle *trans,
 			struct btrfs_root *root,
@@ -30,7 +36,7 @@ struct btrfs_dir_item *btrfs_lookup_dir_index_item(
 			u64 index, const struct fscrypt_str *name, int mod);
 struct btrfs_dir_item *btrfs_search_dir_index_item(struct btrfs_root *root,
 			    struct btrfs_path *path, u64 dirid,
-			    const struct fscrypt_str *name);
+			    struct fscrypt_name *name);
 int btrfs_delete_one_dir_name(struct btrfs_trans_handle *trans,
 			      struct btrfs_root *root,
 			      struct btrfs_path *path,
@@ -48,6 +54,8 @@ struct btrfs_dir_item *btrfs_lookup_xattr(struct btrfs_trans_handle *trans,
 struct btrfs_dir_item *btrfs_match_dir_item_name(const struct btrfs_path *path,
 						 const char *name,
 						 int name_len);
+struct btrfs_dir_item *btrfs_match_dir_item_fname(const struct btrfs_path *path,
+						  struct fscrypt_name *name);
 
 static inline u64 btrfs_name_hash(const char *name, int len)
 {
diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index 2275189b7860..2819989f895f 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -4160,6 +4160,47 @@ static void assert_eb_folio_uptodate(const struct extent_buffer *eb, int i)
 	}
 }
 
+/* Take a sha256 of a portion of an extent buffer. */
+void extent_buffer_sha256(const struct extent_buffer *eb,
+			  unsigned long start,
+			  unsigned long len, u8 *out)
+{
+	size_t cur;
+	size_t offset;
+	char *kaddr;
+	unsigned long i = get_eb_folio_index(eb, start);
+	struct sha256_ctx sctx;
+
+	if (check_eb_range(eb, start, len))
+		return;
+
+	if (eb->addr) {
+		sha256(eb->addr + start, len, out);
+		return;
+	}
+
+	offset = get_eb_offset_in_folio(eb, start);
+
+	/*
+	 * TODO: This should maybe be using the crypto API, not the fallback,
+	 * but fscrypt uses the fallback and this is only used in emulation of
+	 * fscrypt's buffer sha256 method.
+	 */
+	sha256_init(&sctx);
+	while (len > 0) {
+		assert_eb_folio_uptodate(eb, i);
+
+		cur = min(len, PAGE_SIZE - offset);
+		kaddr = folio_address(eb->folios[i]);
+		sha256_update(&sctx, (u8 *)(kaddr + offset), cur);
+
+		len -= cur;
+		offset = 0;
+		i++;
+	}
+	sha256_final(&sctx, out);
+}
+
 static void __write_extent_buffer(const struct extent_buffer *eb,
 				  const void *srcv, unsigned long start,
 				  unsigned long len, bool use_memmove)
diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h
index b310a5145cf6..8c7da2962cd4 100644
--- a/fs/btrfs/extent_io.h
+++ b/fs/btrfs/extent_io.h
@@ -329,6 +329,9 @@ static inline bool extent_buffer_uptodate(const struct extent_buffer *eb)
 
 int memcmp_extent_buffer(const struct extent_buffer *eb, const void *ptrv,
 			 unsigned long start, unsigned long len);
+void extent_buffer_sha256(const struct extent_buffer *eb,
+			  unsigned long start,
+			  unsigned long len, u8 *out);
 void read_extent_buffer(const struct extent_buffer *eb, void *dst,
 			unsigned long start,
 			unsigned long len);
diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index c503f817cbe7..ae4f15762842 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -1,17 +1,50 @@
 // SPDX-License-Identifier: GPL-2.0
 
 #include <linux/iversion.h>
+#include <crypto/sha2.h>
 #include "ctree.h"
 #include "accessors.h"
 #include "btrfs_inode.h"
 #include "disk-io.h"
+#include "ioctl.h"
 #include "fs.h"
 #include "fscrypt.h"
 #include "ioctl.h"
 #include "messages.h"
+#include "root-tree.h"
 #include "transaction.h"
 #include "xattr.h"
 
+/*
+ * This function is extremely similar to fscrypt_match_name() but uses an
+ * extent_buffer.
+ */
+bool btrfs_fscrypt_match_name(struct fscrypt_name *fname,
+			      struct extent_buffer *leaf, unsigned long de_name,
+			      u32 de_name_len)
+{
+	const struct fscrypt_nokey_name *nokey_name =
+		(const struct fscrypt_nokey_name *)fname->crypto_buf.name;
+	u8 digest[SHA256_DIGEST_SIZE];
+
+	if (likely(fname->disk_name.name)) {
+		if (de_name_len != fname->disk_name.len)
+			return false;
+		return !memcmp_extent_buffer(leaf, fname->disk_name.name, de_name, de_name_len);
+	}
+
+	if (de_name_len <= sizeof(nokey_name->bytes))
+		return false;
+
+	if (memcmp_extent_buffer(leaf, nokey_name->bytes, de_name, sizeof(nokey_name->bytes)))
+		return false;
+
+	extent_buffer_sha256(leaf, de_name + sizeof(nokey_name->bytes),
+			     de_name_len - sizeof(nokey_name->bytes), digest);
+
+	return !memcmp(digest, nokey_name->sha256, sizeof(digest));
+}
+
 static int btrfs_fscrypt_get_context(struct inode *inode, void *ctx, size_t len)
 {
 	struct btrfs_key key = {
diff --git a/fs/btrfs/fscrypt.h b/fs/btrfs/fscrypt.h
index 80adb7e56826..4e9c87290158 100644
--- a/fs/btrfs/fscrypt.h
+++ b/fs/btrfs/fscrypt.h
@@ -4,9 +4,27 @@
 #define BTRFS_FSCRYPT_H
 
 #include <linux/fscrypt.h>
+#include "extent_map.h"
 
 #include "fs.h"
 
+#ifdef CONFIG_FS_ENCRYPTION
+bool btrfs_fscrypt_match_name(struct fscrypt_name *fname,
+			      struct extent_buffer *leaf,
+			      unsigned long de_name, u32 de_name_len);
+
+#else
+static inline bool btrfs_fscrypt_match_name(struct fscrypt_name *fname,
+					    struct extent_buffer *leaf,
+					    unsigned long de_name,
+					    u32 de_name_len)
+{
+	if (de_name_len != fname_len(fname))
+		return false;
+	return !memcmp_extent_buffer(leaf, fname->disk_name.name, de_name, de_name_len);
+}
+#endif /* CONFIG_FS_ENCRYPTION */
+
 extern const struct fscrypt_operations btrfs_fscrypt_ops;
 
 #endif /* BTRFS_FSCRYPT_H */
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 14bad4cdb900..0bae9179eb12 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -4404,7 +4404,7 @@ static void update_time_after_link_or_unlink(struct btrfs_inode *dir)
 static int __btrfs_unlink_inode(struct btrfs_trans_handle *trans,
 				struct btrfs_inode *dir,
 				struct btrfs_inode *inode,
-				const struct fscrypt_str *name,
+				struct fscrypt_name *name,
 				struct btrfs_rename_ctx *rename_ctx)
 {
 	struct btrfs_root *root = dir->root;
@@ -4420,7 +4420,7 @@ static int __btrfs_unlink_inode(struct btrfs_trans_handle *trans,
 	if (!path)
 		return -ENOMEM;
 
-	di = btrfs_lookup_dir_item(trans, root, path, dir_ino, name, -1);
+	di = btrfs_lookup_dir_item_fname(trans, root, path, dir_ino, name, -1);
 	if (IS_ERR_OR_NULL(di)) {
 		btrfs_free_path(path);
 		return di ? PTR_ERR(di) : -ENOENT;
@@ -4452,11 +4452,13 @@ static int __btrfs_unlink_inode(struct btrfs_trans_handle *trans,
 		}
 	}
 
-	ret = btrfs_del_inode_ref(trans, root, name, ino, dir_ino, &index);
+	ret = btrfs_del_inode_ref(trans, root, &name->disk_name, ino, dir_ino, &index);
 	if (unlikely(ret)) {
+		/* This should print a base-64 encoded name if relevant? */
 		btrfs_crit(fs_info,
 	   "failed to delete reference to %.*s, root %llu inode %llu parent %llu",
-			   name->len, name->name, btrfs_root_id(root), ino, dir_ino);
+			   name->disk_name.len, name->disk_name.name,
+			   btrfs_root_id(root), ino, dir_ino);
 		btrfs_abort_transaction(trans, ret);
 		return ret;
 	}
@@ -4477,8 +4479,8 @@ static int __btrfs_unlink_inode(struct btrfs_trans_handle *trans,
 	 * operations on the log tree, increasing latency for applications.
 	 */
 	if (!rename_ctx) {
-		btrfs_del_inode_ref_in_log(trans, name, inode, dir);
-		btrfs_del_dir_entries_in_log(trans, name, dir, index);
+		btrfs_del_inode_ref_in_log(trans, &name->disk_name, inode, dir);
+		btrfs_del_dir_entries_in_log(trans, &name->disk_name, dir, index);
 	}
 
 	/*
@@ -4492,7 +4494,7 @@ static int __btrfs_unlink_inode(struct btrfs_trans_handle *trans,
 	 */
 	btrfs_run_delayed_iput(fs_info, inode);
 
-	btrfs_i_size_write(dir, dir->vfs_inode.i_size - name->len * 2);
+	btrfs_i_size_write(dir, dir->vfs_inode.i_size - name->disk_name.len * 2);
 	inode_inc_iversion(&inode->vfs_inode);
 	inode_set_ctime_current(&inode->vfs_inode);
 	inode_inc_iversion(&dir->vfs_inode);
@@ -4503,7 +4505,7 @@ static int __btrfs_unlink_inode(struct btrfs_trans_handle *trans,
 
 int btrfs_unlink_inode(struct btrfs_trans_handle *trans,
 		       struct btrfs_inode *dir, struct btrfs_inode *inode,
-		       const struct fscrypt_str *name)
+		       struct fscrypt_name *name)
 {
 	int ret;
 
@@ -4550,11 +4552,9 @@ static int btrfs_unlink(struct inode *dir, struct dentry *dentry)
 		goto fscrypt_free;
 	}
 
-	btrfs_record_unlink_dir(trans, BTRFS_I(dir), BTRFS_I(d_inode(dentry)),
-				false);
+	btrfs_record_unlink_dir(trans, BTRFS_I(dir), BTRFS_I(d_inode(dentry)), false);
 
-	ret = btrfs_unlink_inode(trans, BTRFS_I(dir), BTRFS_I(d_inode(dentry)),
-				 &fname.disk_name);
+	ret = btrfs_unlink_inode(trans, BTRFS_I(dir), BTRFS_I(d_inode(dentry)), &fname);
 	if (ret)
 		goto end_trans;
 
@@ -4591,8 +4591,6 @@ static int btrfs_unlink_subvol(struct btrfs_trans_handle *trans,
 	if (ret)
 		return ret;
 
-	/* This needs to handle no-key deletions later on */
-
 	if (btrfs_ino(inode) == BTRFS_FIRST_FREE_OBJECTID) {
 		objectid = btrfs_root_id(inode->root);
 	} else if (btrfs_ino(inode) == BTRFS_EMPTY_SUBVOL_DIR_OBJECTID) {
@@ -4609,8 +4607,7 @@ static int btrfs_unlink_subvol(struct btrfs_trans_handle *trans,
 		goto out;
 	}
 
-	di = btrfs_lookup_dir_item(trans, root, path, dir_ino,
-				   &fname.disk_name, -1);
+	di = btrfs_lookup_dir_item_fname(trans, root, path, dir_ino, &fname, -1);
 	if (IS_ERR_OR_NULL(di)) {
 		ret = di ? PTR_ERR(di) : -ENOENT;
 		goto out;
@@ -4636,7 +4633,7 @@ static int btrfs_unlink_subvol(struct btrfs_trans_handle *trans,
 	 * call btrfs_del_root_ref, and it _shouldn't_ fail.
 	 */
 	if (btrfs_ino(inode) == BTRFS_EMPTY_SUBVOL_DIR_OBJECTID) {
-		di = btrfs_search_dir_index_item(root, path, dir_ino, &fname.disk_name);
+		di = btrfs_search_dir_index_item(root, path, dir_ino, &fname);
 		if (IS_ERR(di)) {
 			ret = PTR_ERR(di);
 			btrfs_abort_transaction(trans, ret);
@@ -4650,7 +4647,7 @@ static int btrfs_unlink_subvol(struct btrfs_trans_handle *trans,
 	} else {
 		ret = btrfs_del_root_ref(trans, objectid,
 					 btrfs_root_id(root), dir_ino,
-					 &index, &fname.disk_name);
+					 &index, &fname);
 		if (unlikely(ret)) {
 			btrfs_abort_transaction(trans, ret);
 			goto out;
@@ -4963,7 +4960,7 @@ static int btrfs_rmdir(struct inode *vfs_dir, struct dentry *dentry)
 	btrfs_record_unlink_dir(trans, dir, inode, false);
 
 	/* now the directory is empty */
-	ret = btrfs_unlink_inode(trans, dir, inode, &fname.disk_name);
+	ret = btrfs_unlink_inode(trans, dir, inode, &fname);
 	if (!ret)
 		btrfs_i_size_write(inode, 0);
 out:
@@ -5762,36 +5759,23 @@ void btrfs_evict_inode(struct inode *inode)
  * If no dir entries were found, returns -ENOENT.
  * If found a corrupted location in dir entry, returns -EUCLEAN.
  */
-static int btrfs_inode_by_name(struct btrfs_inode *dir, struct dentry *dentry,
+static int btrfs_inode_by_name(struct btrfs_inode *dir, struct fscrypt_name *fname,
 			       struct btrfs_key *location, u8 *type)
 {
 	struct btrfs_dir_item *di;
 	BTRFS_PATH_AUTO_FREE(path);
 	struct btrfs_root *root = dir->root;
 	int ret = 0;
-	struct fscrypt_name fname;
 
 	path = btrfs_alloc_path();
 	if (!path)
 		return -ENOMEM;
 
-	ret = fscrypt_setup_filename(&dir->vfs_inode, &dentry->d_name, 1, &fname);
-	if (ret < 0)
-		return ret;
-	/*
-	 * fscrypt_setup_filename() should never return a positive value, but
-	 * gcc on sparc/parisc thinks it can, so assert that doesn't happen.
-	 */
-	ASSERT(ret == 0);
-
 	/* This needs to handle no-key deletions later on */
 
-	di = btrfs_lookup_dir_item(NULL, root, path, btrfs_ino(dir),
-				   &fname.disk_name, 0);
-	if (IS_ERR_OR_NULL(di)) {
-		ret = di ? PTR_ERR(di) : -ENOENT;
-		goto out;
-	}
+	di = btrfs_lookup_dir_item_fname(NULL, root, path, btrfs_ino(dir), fname, 0);
+	if (IS_ERR_OR_NULL(di))
+		return di ? PTR_ERR(di) : -ENOENT;
 
 	btrfs_dir_item_key_to_cpu(path->nodes[0], di, location);
 	if (unlikely(location->type != BTRFS_INODE_ITEM_KEY &&
@@ -5799,13 +5783,11 @@ static int btrfs_inode_by_name(struct btrfs_inode *dir, struct dentry *dentry,
 		ret = -EUCLEAN;
 		btrfs_warn(root->fs_info,
 "%s gets something invalid in DIR_ITEM (name %s, directory ino %llu, location " BTRFS_KEY_FMT ")",
-			   __func__, fname.disk_name.name, btrfs_ino(dir),
+			   __func__, fname->usr_fname->name, btrfs_ino(dir),
 			   BTRFS_KEY_FMT_VALUE(location));
 	}
 	if (!ret)
 		*type = btrfs_dir_ftype(path->nodes[0], di);
-out:
-	fscrypt_free_filename(&fname);
 	return ret;
 }
 
@@ -6072,20 +6054,27 @@ struct inode *btrfs_lookup_dentry(struct inode *dir, struct dentry *dentry)
 	struct btrfs_root *root = BTRFS_I(dir)->root;
 	struct btrfs_root *sub_root = root;
 	struct btrfs_key location = { 0 };
+	struct fscrypt_name fname;
 	u8 di_type = 0;
 	int ret = 0;
 
 	if (dentry->d_name.len > BTRFS_NAME_LEN)
 		return ERR_PTR(-ENAMETOOLONG);
 
-	ret = btrfs_inode_by_name(BTRFS_I(dir), dentry, &location, &di_type);
-	if (ret < 0)
+	ret = fscrypt_prepare_lookup(dir, dentry, &fname);
+	if (ret)
 		return ERR_PTR(ret);
 
+	ret = btrfs_inode_by_name(BTRFS_I(dir), &fname, &location, &di_type);
+	if (ret < 0) {
+		inode = ERR_PTR(ret);
+		goto cleanup;
+	}
+
 	if (location.type == BTRFS_INODE_ITEM_KEY) {
 		inode = btrfs_iget(location.objectid, root);
 		if (IS_ERR(inode))
-			return ERR_CAST(inode);
+			goto cleanup;
 
 		/* Do extra check against inode mode with di_type */
 		if (unlikely(btrfs_inode_type(inode) != di_type)) {
@@ -6094,9 +6083,10 @@ struct inode *btrfs_lookup_dentry(struct inode *dir, struct dentry *dentry)
 				  inode->vfs_inode.i_mode, btrfs_inode_type(inode),
 				  di_type);
 			iput(&inode->vfs_inode);
-			return ERR_PTR(-EUCLEAN);
+			inode = ERR_PTR(-EUCLEAN);
+			goto cleanup;
 		}
-		return &inode->vfs_inode;
+		goto cleanup;
 	}
 
 	ret = fixup_tree_root_location(fs_info, BTRFS_I(dir), dentry,
@@ -6111,7 +6101,7 @@ struct inode *btrfs_lookup_dentry(struct inode *dir, struct dentry *dentry)
 		btrfs_put_root(sub_root);
 
 		if (IS_ERR(inode))
-			return ERR_CAST(inode);
+			goto cleanup;
 
 		down_read(&fs_info->cleanup_work_sem);
 		if (!sb_rdonly(inode->vfs_inode.i_sb))
@@ -6123,6 +6113,9 @@ struct inode *btrfs_lookup_dentry(struct inode *dir, struct dentry *dentry)
 		}
 	}
 
+cleanup:
+	fscrypt_free_filename(&fname);
+
 	if (IS_ERR(inode))
 		return ERR_CAST(inode);
 
@@ -6321,18 +6314,32 @@ static int btrfs_real_readdir(struct file *file, struct dir_context *ctx)
 	LIST_HEAD(del_list);
 	int ret;
 	char *name_ptr;
-	int name_len;
+	u32 name_len;
 	int entries = 0;
 	int total_len = 0;
 	bool put = false;
 	struct btrfs_key location;
+	struct fscrypt_str fstr = FSTR_INIT(NULL, 0);
+	u32 fstr_len = 0;
 
 	if (!dir_emit_dots(file, ctx))
 		return 0;
 
+	if (BTRFS_I(inode)->flags & BTRFS_INODE_ENCRYPT) {
+		ret = fscrypt_prepare_readdir(inode);
+		if (ret)
+			return ret;
+		ret = fscrypt_fname_alloc_buffer(BTRFS_NAME_LEN, &fstr);
+		if (ret)
+			return ret;
+		fstr_len = fstr.len;
+	}
+
 	path = btrfs_alloc_path();
-	if (!path)
-		return -ENOMEM;
+	if (!path) {
+		ret = -ENOMEM;
+		goto err_fstr;
+	}
 
 	addr = private->filldir_buf;
 	path->reada = READA_FORWARD;
@@ -6349,6 +6356,7 @@ static int btrfs_real_readdir(struct file *file, struct dir_context *ctx)
 		struct dir_entry *entry;
 		struct extent_buffer *leaf = path->nodes[0];
 		u8 ftype;
+		u32 nokey_len;
 
 		if (found_key.objectid != key.objectid)
 			break;
@@ -6362,8 +6370,12 @@ static int btrfs_real_readdir(struct file *file, struct dir_context *ctx)
 			continue;
 		di = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_dir_item);
 		name_len = btrfs_dir_name_len(leaf, di);
-		if ((total_len + sizeof(struct dir_entry) + name_len) >=
-		    PAGE_SIZE) {
+		nokey_len = DIV_ROUND_UP(name_len * 4, 3);
+		/*
+		 * If name is encrypted, and we don't have the key, we could
+		 * need up to 4/3rds the bytes to print it.
+		 */
+		if ((total_len + sizeof(struct dir_entry) + nokey_len) >= PAGE_SIZE) {
 			btrfs_release_path(path);
 			ret = btrfs_filldir(private->filldir_buf, entries, ctx);
 			if (ret)
@@ -6377,8 +6389,31 @@ static int btrfs_real_readdir(struct file *file, struct dir_context *ctx)
 		ftype = btrfs_dir_flags_to_ftype(btrfs_dir_flags(leaf, di));
 		entry = addr;
 		name_ptr = (char *)(entry + 1);
-		read_extent_buffer(leaf, name_ptr,
-				   (unsigned long)(di + 1), name_len);
+		if (btrfs_dir_flags(leaf, di) & BTRFS_FT_ENCRYPTED) {
+			struct fscrypt_str oname = FSTR_INIT(name_ptr, nokey_len);
+			u32 hash = 0, minor_hash = 0;
+
+			read_extent_buffer(leaf, fstr.name, (unsigned long)(di + 1), name_len);
+			fstr.len = name_len;
+			/*
+			 * We're iterating through DIR_INDEX items, so we don't
+			 * have the DIR_ITEM hash handy. Only compute it if
+			 * we'll need it -- the nokey name stores it, so that
+			 * we can look up the appropriate item by nokey name
+			 * later on.
+			 */
+			if (!fscrypt_has_encryption_key(inode)) {
+				u64 name_hash = btrfs_name_hash(fstr.name, fstr.len);
+				hash = name_hash;
+				minor_hash = name_hash >> 32;
+			}
+			ret = fscrypt_fname_disk_to_usr(inode, hash, minor_hash, &fstr, &oname);
+			if (ret)
+				goto err;
+			name_len = oname.len;
+		} else {
+			read_extent_buffer(leaf, name_ptr, (unsigned long)(di + 1), name_len);
+		}
 		put_unaligned(name_len, &entry->name_len);
 		put_unaligned(fs_ftype_to_dtype(ftype), &entry->type);
 		btrfs_dir_item_key_to_cpu(leaf, di, &location);
@@ -6398,7 +6433,8 @@ static int btrfs_real_readdir(struct file *file, struct dir_context *ctx)
 	if (ret)
 		goto nopos;
 
-	if (btrfs_readdir_delayed_dir_index(ctx, &ins_list))
+	fstr.len = fstr_len;
+	if (btrfs_readdir_delayed_dir_index(inode, &fstr, ctx, &ins_list))
 		goto nopos;
 
 	/*
@@ -6427,6 +6463,8 @@ static int btrfs_real_readdir(struct file *file, struct dir_context *ctx)
 err:
 	if (put)
 		btrfs_readdir_put_delayed_items(BTRFS_I(inode), &ins_list, &del_list);
+err_fstr:
+	fscrypt_fname_free_buffer(&fstr);
 	return ret;
 }
 
@@ -6928,6 +6966,7 @@ int btrfs_add_link(struct btrfs_trans_handle *trans,
 	struct btrfs_root *root = parent_inode->root;
 	u64 ino = btrfs_ino(inode);
 	u64 parent_ino = btrfs_ino(parent_inode);
+	struct fscrypt_name fname = { .disk_name = *name };
 
 	if (unlikely(ino == BTRFS_FIRST_FREE_OBJECTID)) {
 		memcpy(&key, &inode->root->root_key, sizeof(key));
@@ -6975,7 +7014,7 @@ int btrfs_add_link(struct btrfs_trans_handle *trans,
 		int ret2;
 
 		ret2 = btrfs_del_root_ref(trans, key.objectid, btrfs_root_id(root),
-					  parent_ino, &local_index, name);
+					  parent_ino, &local_index, &fname);
 		if (ret2)
 			btrfs_abort_transaction(trans, ret2);
 	} else if (add_backref) {
@@ -8497,7 +8536,7 @@ static int btrfs_rename_exchange(struct inode *old_dir,
 	} else { /* src is an inode */
 		ret = __btrfs_unlink_inode(trans, BTRFS_I(old_dir),
 					   BTRFS_I(old_dentry->d_inode),
-					   old_name, &old_rename_ctx);
+					   &old_fname, &old_rename_ctx);
 		if (unlikely(ret)) {
 			btrfs_abort_transaction(trans, ret);
 			goto out_fail;
@@ -8519,7 +8558,7 @@ static int btrfs_rename_exchange(struct inode *old_dir,
 	} else { /* dest is an inode */
 		ret = __btrfs_unlink_inode(trans, BTRFS_I(new_dir),
 					   BTRFS_I(new_dentry->d_inode),
-					   new_name, &new_rename_ctx);
+					   &new_fname, &new_rename_ctx);
 		if (unlikely(ret)) {
 			btrfs_abort_transaction(trans, ret);
 			goto out_fail;
@@ -8788,7 +8827,7 @@ static int btrfs_rename(struct mnt_idmap *idmap,
 	} else {
 		ret = __btrfs_unlink_inode(trans, BTRFS_I(old_dir),
 					   BTRFS_I(d_inode(old_dentry)),
-					   &old_fname.disk_name, &rename_ctx);
+					   &old_fname, &rename_ctx);
 		if (unlikely(ret)) {
 			btrfs_abort_transaction(trans, ret);
 			goto out_fail;
@@ -8813,7 +8852,7 @@ static int btrfs_rename(struct mnt_idmap *idmap,
 		} else {
 			ret = btrfs_unlink_inode(trans, BTRFS_I(new_dir),
 						 BTRFS_I(d_inode(new_dentry)),
-						 &new_fname.disk_name);
+						 &new_fname);
 			if (unlikely(ret)) {
 				btrfs_abort_transaction(trans, ret);
 				goto out_fail;
diff --git a/fs/btrfs/root-tree.c b/fs/btrfs/root-tree.c
index d85a09ae1733..9f0f73200889 100644
--- a/fs/btrfs/root-tree.c
+++ b/fs/btrfs/root-tree.c
@@ -10,6 +10,7 @@
 #include "messages.h"
 #include "transaction.h"
 #include "disk-io.h"
+#include "fscrypt.h"
 #include "qgroup.h"
 #include "space-info.h"
 #include "accessors.h"
@@ -328,7 +329,7 @@ int btrfs_del_root(struct btrfs_trans_handle *trans,
 
 int btrfs_del_root_ref(struct btrfs_trans_handle *trans, u64 root_id,
 		       u64 ref_id, u64 dirid, u64 *sequence,
-		       const struct fscrypt_str *name)
+		       struct fscrypt_name *name)
 {
 	struct btrfs_root *tree_root = trans->fs_info->tree_root;
 	BTRFS_PATH_AUTO_FREE(path);
@@ -350,15 +351,15 @@ int btrfs_del_root_ref(struct btrfs_trans_handle *trans, u64 root_id,
 	if (ret < 0) {
 		return ret;
 	} else if (ret == 0) {
+		u32 name_len;
 		leaf = path->nodes[0];
 		ref = btrfs_item_ptr(leaf, path->slots[0],
 				     struct btrfs_root_ref);
 		ptr = (unsigned long)(ref + 1);
+		name_len = btrfs_root_ref_name_len(leaf, ref);
 		if ((btrfs_root_ref_dirid(leaf, ref) != dirid) ||
-		    (btrfs_root_ref_name_len(leaf, ref) != name->len) ||
-		    memcmp_extent_buffer(leaf, name->name, ptr, name->len))
+		    !btrfs_fscrypt_match_name(name, leaf, ptr, name_len))
 			return -ENOENT;
-
 		*sequence = btrfs_root_ref_sequence(leaf, ref);
 
 		ret = btrfs_del_item(trans, tree_root, path);
diff --git a/fs/btrfs/root-tree.h b/fs/btrfs/root-tree.h
index 8f5739e732b9..e85623dba952 100644
--- a/fs/btrfs/root-tree.h
+++ b/fs/btrfs/root-tree.h
@@ -23,7 +23,7 @@ int btrfs_add_root_ref(struct btrfs_trans_handle *trans, u64 root_id,
 		       const struct fscrypt_str *name);
 int btrfs_del_root_ref(struct btrfs_trans_handle *trans, u64 root_id,
 		       u64 ref_id, u64 dirid, u64 *sequence,
-		       const struct fscrypt_str *name);
+		       struct fscrypt_name *name);
 int btrfs_del_root(struct btrfs_trans_handle *trans, const struct btrfs_key *key);
 int btrfs_insert_root(struct btrfs_trans_handle *trans, struct btrfs_root *root,
 		      const struct btrfs_key *key,
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index e6cd475057db..81f0990d638b 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -1045,9 +1045,10 @@ static int unlink_inode_for_log_replay(struct walk_control *wc,
 				       const struct fscrypt_str *name)
 {
 	struct btrfs_trans_handle *trans = wc->trans;
+	struct fscrypt_name fname = { .disk_name = *name, };
 	int ret;
 
-	ret = btrfs_unlink_inode(trans, dir, inode, name);
+	ret = btrfs_unlink_inode(trans, dir, inode, &fname);
 	if (ret) {
 		btrfs_abort_log_replay(wc, ret,
 	       "failed to unlink inode %llu parent dir %llu name %.*s root %llu",
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 09/43] btrfs: add infrastructure for safe em freeing
From: Daniel Vacek @ 2026-05-13  8:52 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Boris Burkov
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Josef Bacik <josef@toxicpanda.com>

When we add fscrypt support we're going to have fscrypt objects hanging
off of extent_maps.  This includes a block key, which if we're the last
one freeing the key we may have to unregister it from the block layer.
This requires taking a semaphore in the block layer, which means we
can't free em's under the extent map tree lock.

Thankfully we only do this in two places, one where we're dropping a
range of extent maps, and when we're freeing logged extents.  Add a
free_extent_map_safe() which will add the em to a list in the em_tree if
we free'd the object.  Currently this is unconditional but will be
changed to conditional on the fscrypt object we will add in a later
patch.

To process these delayed objects add a free_pending_extent_maps() that
is called after the lock has been dropped on the em_tree.  This will
process the extent maps on the freed list and do the appropriate freeing
work in a safe manner.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/6cf44f7860e94de68df242e69f4c5250bd061cff.1706116485.git.josef@toxicpanda.com/
 * No changes since (other than simple function renames).
---
 fs/btrfs/extent_map.c | 76 +++++++++++++++++++++++++++++++++++++++++--
 fs/btrfs/extent_map.h | 10 ++++++
 fs/btrfs/tree-log.c   |  6 ++--
 3 files changed, 87 insertions(+), 5 deletions(-)

diff --git a/fs/btrfs/extent_map.c b/fs/btrfs/extent_map.c
index 9284c0a81bef..61fe262667ee 100644
--- a/fs/btrfs/extent_map.c
+++ b/fs/btrfs/extent_map.c
@@ -34,7 +34,9 @@ void __cold btrfs_extent_map_exit(void)
 void btrfs_extent_map_tree_init(struct extent_map_tree *tree)
 {
 	tree->root = RB_ROOT;
+	tree->flags = 0;
 	INIT_LIST_HEAD(&tree->modified_extents);
+	INIT_LIST_HEAD(&tree->freed_extents);
 	rwlock_init(&tree->lock);
 }
 
@@ -51,9 +53,15 @@ struct extent_map *btrfs_alloc_extent_map(void)
 	RB_CLEAR_NODE(&em->rb_node);
 	refcount_set(&em->refs, 1);
 	INIT_LIST_HEAD(&em->list);
+	INIT_LIST_HEAD(&em->free_list);
 	return em;
 }
 
+static void free_extent_map(struct extent_map *em)
+{
+	kmem_cache_free(extent_map_cache, em);
+}
+
 /*
  * Drop the reference out on @em by one and free the structure if the reference
  * count hits zero.
@@ -65,10 +73,69 @@ void btrfs_free_extent_map(struct extent_map *em)
 	if (refcount_dec_and_test(&em->refs)) {
 		WARN_ON(btrfs_extent_map_in_tree(em));
 		WARN_ON(!list_empty(&em->list));
-		kmem_cache_free(extent_map_cache, em);
+		free_extent_map(em);
+	}
+}
+
+/*
+ * Drop a ref for the extent map in the given tree.
+ *
+ * @tree:	tree that the em is a part of.
+ * @em:		the em to drop the reference to.
+ *
+ * Drop the reference count on @em by one, if the reference count hits 0 and
+ * there is an object on the em that can't be safely freed in the current
+ * context (if we are holding the extent_map_tree->lock for example), then add
+ * it to the freed_extents list on the extent_map_tree for later processing.
+ *
+ * This must be followed by a btrfs_free_pending_extent_maps() to clear
+ * the pending frees.
+ */
+void btrfs_free_extent_map_safe(struct extent_map_tree *tree,
+				struct extent_map *em)
+{
+	lockdep_assert_held_write(&tree->lock);
+
+	if (!em)
+		return;
+
+	if (refcount_dec_and_test(&em->refs)) {
+		WARN_ON(btrfs_extent_map_in_tree(em));
+		WARN_ON(!list_empty(&em->list));
+		list_add_tail(&em->free_list, &tree->freed_extents);
+		set_bit(EXTENT_MAP_TREE_PENDING_FREES, &tree->flags);
 	}
 }
 
+/*
+ * Free the em objects that exist on the em tree
+ *
+ * @tree:	the tree to free the objects from.
+ *
+ * If there are any objects on the em->freed_extents list go ahead and
+ * free them here in a safe way.  This is to be coupled with any uses of
+ * btrfs_free_extent_map_safe().
+ */
+void btrfs_free_pending_extent_maps(struct extent_map_tree *tree)
+{
+	struct extent_map *em;
+
+	/* Avoid taking the write lock if we don't have any pending frees. */
+	if (!test_and_clear_bit(EXTENT_MAP_TREE_PENDING_FREES, &tree->flags))
+		return;
+
+	write_lock(&tree->lock);
+	while ((em = list_first_entry_or_null(&tree->freed_extents,
+					      struct extent_map, free_list))) {
+		list_del_init(&em->free_list);
+		write_unlock(&tree->lock);
+		free_extent_map(em);
+		cond_resched();
+		write_lock(&tree->lock);
+	}
+	write_unlock(&tree->lock);
+}
+
 /* Do the math around the end of an extent, handling wrapping. */
 static u64 range_end(u64 start, u64 len)
 {
@@ -784,7 +851,7 @@ static void drop_all_extent_maps_fast(struct btrfs_inode *inode)
 		em = rb_entry(node, struct extent_map, rb_node);
 		em->flags &= ~(EXTENT_FLAG_PINNED | EXTENT_FLAG_LOGGING);
 		btrfs_remove_extent_mapping(inode, em);
-		btrfs_free_extent_map(em);
+		btrfs_free_extent_map_safe(tree, em);
 
 		if (cond_resched_rwlock_write(&tree->lock))
 			node = rb_first(&tree->root);
@@ -792,6 +859,8 @@ static void drop_all_extent_maps_fast(struct btrfs_inode *inode)
 			node = next;
 	}
 	write_unlock(&tree->lock);
+
+	btrfs_free_pending_extent_maps(tree);
 }
 
 /*
@@ -986,13 +1055,14 @@ void btrfs_drop_extent_map_range(struct btrfs_inode *inode, u64 start, u64 end,
 		btrfs_free_extent_map(em);
 next:
 		/* Once for us (for our lookup reference). */
-		btrfs_free_extent_map(em);
+		btrfs_free_extent_map_safe(em_tree, em);
 
 		em = next_em;
 	}
 
 	write_unlock(&em_tree->lock);
 
+	btrfs_free_pending_extent_maps(em_tree);
 	btrfs_free_extent_map(split);
 	btrfs_free_extent_map(split2);
 }
diff --git a/fs/btrfs/extent_map.h b/fs/btrfs/extent_map.h
index 6f685f3c9327..a962012be1c3 100644
--- a/fs/btrfs/extent_map.h
+++ b/fs/btrfs/extent_map.h
@@ -97,11 +97,18 @@ struct extent_map {
 	u32 flags;
 	refcount_t refs;
 	struct list_head list;
+	struct list_head free_list;
+};
+
+enum extent_map_flags {
+	EXTENT_MAP_TREE_PENDING_FREES,
 };
 
 struct extent_map_tree {
 	struct rb_root root;
+	unsigned long flags;
 	struct list_head modified_extents;
+	struct list_head freed_extents;
 	rwlock_t lock;
 };
 
@@ -175,6 +182,9 @@ int btrfs_split_extent_map(struct btrfs_inode *inode, u64 start, u64 len, u64 pr
 
 struct extent_map *btrfs_alloc_extent_map(void);
 void btrfs_free_extent_map(struct extent_map *em);
+void btrfs_free_extent_map_safe(struct extent_map_tree *tree,
+				struct extent_map *em);
+void btrfs_free_pending_extent_maps(struct extent_map_tree *tree);
 int __init btrfs_extent_map_init(void);
 void __cold btrfs_extent_map_exit(void);
 int btrfs_unpin_extent_cache(struct btrfs_inode *inode, u64 start, u64 len, u64 gen);
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index 9123adafa0d1..e6cd475057db 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -5410,7 +5410,7 @@ static int btrfs_log_changed_extents(struct btrfs_trans_handle *trans,
 		 */
 		if (ret) {
 			btrfs_clear_em_logging(inode, em);
-			btrfs_free_extent_map(em);
+			btrfs_free_extent_map_safe(tree, em);
 			continue;
 		}
 
@@ -5419,11 +5419,13 @@ static int btrfs_log_changed_extents(struct btrfs_trans_handle *trans,
 		ret = log_one_extent(trans, inode, em, path, ctx);
 		write_lock(&tree->lock);
 		btrfs_clear_em_logging(inode, em);
-		btrfs_free_extent_map(em);
+		btrfs_free_extent_map_safe(tree, em);
 	}
 	WARN_ON(!list_empty(&extents));
 	write_unlock(&tree->lock);
 
+	btrfs_free_pending_extent_maps(tree);
+
 	if (!ret)
 		ret = btrfs_log_prealloc_extents(trans, inode, path, ctx);
 	if (ret)
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 11/43] btrfs: add inode encryption contexts
From: Daniel Vacek @ 2026-05-13  8:52 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Omar Sandoval, Sweet Tea Dorminy
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>

From: Omar Sandoval <osandov@osandov.com>

fscrypt stores a context item with encrypted inodes that contains the
related encryption information.  fscrypt provides an arbitrary blob for
the filesystem to store, and it does not clearly fit into an existing
structure, so this goes in a new item type.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v7 changes:
 * Fix a path leak as found by Chri's AI review.
v6 changes:
 * Shorten the inode context key macro name to BTRFS_FSCRYPT_INODE_CTX_KEY.
v5: https://lore.kernel.org/linux-btrfs/5a88efb484b0874a7430b83bc6e5f6b9aa5858d5.1706116485.git.josef@toxicpanda.com/
---
 fs/btrfs/fscrypt.c              | 116 ++++++++++++++++++++++++++++++++
 fs/btrfs/fscrypt.h              |   2 +
 fs/btrfs/inode.c                |  19 ++++++
 fs/btrfs/ioctl.c                |   8 ++-
 include/uapi/linux/btrfs_tree.h |  10 +++
 5 files changed, 153 insertions(+), 2 deletions(-)

diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index 6cfba7d94e72..c503f817cbe7 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -1,10 +1,126 @@
 // SPDX-License-Identifier: GPL-2.0
 
+#include <linux/iversion.h>
 #include "ctree.h"
+#include "accessors.h"
 #include "btrfs_inode.h"
+#include "disk-io.h"
+#include "fs.h"
 #include "fscrypt.h"
+#include "ioctl.h"
+#include "messages.h"
+#include "transaction.h"
+#include "xattr.h"
+
+static int btrfs_fscrypt_get_context(struct inode *inode, void *ctx, size_t len)
+{
+	struct btrfs_key key = {
+		.objectid = btrfs_ino(BTRFS_I(inode)),
+		.type = BTRFS_FSCRYPT_INODE_CTX_KEY,
+		.offset = 0,
+	};
+	struct btrfs_path *path;
+	struct extent_buffer *leaf;
+	unsigned long ptr;
+	int ret;
+
+
+	path = btrfs_alloc_path();
+	if (!path)
+		return -ENOMEM;
+
+	ret = btrfs_search_slot(NULL, BTRFS_I(inode)->root, &key, path, 0, 0);
+	if (ret) {
+		len = -ENOENT;
+		goto out;
+	}
+
+	leaf = path->nodes[0];
+	ptr = btrfs_item_ptr_offset(leaf, path->slots[0]);
+	/* fscrypt provides max context length, but it could be less */
+	len = min_t(size_t, len, btrfs_item_size(leaf, path->slots[0]));
+	read_extent_buffer(leaf, ctx, ptr, len);
+
+out:
+	btrfs_free_path(path);
+	return len;
+}
+
+static int btrfs_fscrypt_set_context(struct inode *inode, const void *ctx,
+				     size_t len, void *fs_data)
+{
+	struct btrfs_trans_handle *trans = fs_data;
+	struct btrfs_key key = {
+		.objectid = btrfs_ino(BTRFS_I(inode)),
+		.type = BTRFS_FSCRYPT_INODE_CTX_KEY,
+		.offset = 0,
+	};
+	struct btrfs_path *path = NULL;
+	struct extent_buffer *leaf;
+	unsigned long ptr;
+	int ret;
+
+	if (!trans)
+		trans = btrfs_start_transaction(BTRFS_I(inode)->root, 2);
+	if (IS_ERR(trans))
+		return PTR_ERR(trans);
+
+	path = btrfs_alloc_path();
+	if (!path) {
+		ret = -ENOMEM;
+		goto out_err;
+	}
+
+	ret = btrfs_search_slot(trans, BTRFS_I(inode)->root, &key, path, 0, 1);
+	if (ret < 0)
+		goto out_err;
+
+	if (ret > 0) {
+		btrfs_release_path(path);
+		ret = btrfs_insert_empty_item(trans, BTRFS_I(inode)->root, path, &key, len);
+		if (ret)
+			goto out_err;
+	}
+
+	leaf = path->nodes[0];
+	ptr = btrfs_item_ptr_offset(leaf, path->slots[0]);
+
+	len = min_t(size_t, len, btrfs_item_size(leaf, path->slots[0]));
+	write_extent_buffer(leaf, ctx, ptr, len);
+	btrfs_mark_buffer_dirty(trans, leaf);
+	btrfs_release_path(path);
+
+	if (fs_data)
+		goto out_err;
+
+	BTRFS_I(inode)->flags |= BTRFS_INODE_ENCRYPT;
+	btrfs_sync_inode_flags_to_i_flags(BTRFS_I(inode));
+	inode_inc_iversion(inode);
+	inode_set_ctime_current(inode);
+	ret = btrfs_update_inode(trans, BTRFS_I(inode));
+	if (ret)
+		goto out_abort;
+	btrfs_free_path(path);
+	btrfs_end_transaction(trans);
+	return 0;
+out_abort:
+	btrfs_abort_transaction(trans, ret);
+out_err:
+	if (!fs_data)
+		btrfs_end_transaction(trans);
+	btrfs_free_path(path);
+	return ret;
+}
+
+static bool btrfs_fscrypt_empty_dir(struct inode *inode)
+{
+	return inode->i_size == BTRFS_EMPTY_DIR_SIZE;
+}
 
 const struct fscrypt_operations btrfs_fscrypt_ops = {
 	.inode_info_offs = (int)offsetof(struct btrfs_inode, i_crypt_info) -
 			   (int)offsetof(struct btrfs_inode, vfs_inode),
+	.get_context = btrfs_fscrypt_get_context,
+	.set_context = btrfs_fscrypt_set_context,
+	.empty_dir = btrfs_fscrypt_empty_dir,
 };
diff --git a/fs/btrfs/fscrypt.h b/fs/btrfs/fscrypt.h
index 7f4e6888bd43..80adb7e56826 100644
--- a/fs/btrfs/fscrypt.h
+++ b/fs/btrfs/fscrypt.h
@@ -5,6 +5,8 @@
 
 #include <linux/fscrypt.h>
 
+#include "fs.h"
+
 extern const struct fscrypt_operations btrfs_fscrypt_ops;
 
 #endif /* BTRFS_FSCRYPT_H */
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 8f89e44b2d53..14bad4cdb900 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -60,6 +60,7 @@
 #include "defrag.h"
 #include "dir-item.h"
 #include "file-item.h"
+#include "fscrypt.h"
 #include "uuid-tree.h"
 #include "ioctl.h"
 #include "file.h"
@@ -6529,6 +6530,9 @@ int btrfs_new_inode_prepare(struct btrfs_new_inode_args *args,
 	struct inode *inode = args->inode;
 	int ret;
 
+	if (fscrypt_is_nokey_name(args->dentry))
+		return -ENOKEY;
+
 	if (!args->orphan) {
 		ret = fscrypt_setup_filename(dir, &args->dentry->d_name, 0,
 					     &args->fname);
@@ -6564,6 +6568,9 @@ int btrfs_new_inode_prepare(struct btrfs_new_inode_args *args,
 	if (dir->i_security)
 		(*trans_num_items)++;
 #endif
+	/* 1 to add fscrypt item */
+	if (args->encrypt)
+		(*trans_num_items)++;
 	if (args->orphan) {
 		/* 1 to add orphan item */
 		(*trans_num_items)++;
@@ -6776,6 +6783,11 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans,
 	BTRFS_I(inode)->i_otime_sec = ts.tv_sec;
 	BTRFS_I(inode)->i_otime_nsec = ts.tv_nsec;
 
+	if (args->encrypt) {
+		BTRFS_I(inode)->flags |= BTRFS_INODE_ENCRYPT;
+		btrfs_sync_inode_flags_to_i_flags(BTRFS_I(inode));
+	}
+
 	/*
 	 * We're going to fill the inode item now, so at this point the inode
 	 * must be fully initialized.
@@ -6849,6 +6861,13 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans,
 			goto discard;
 		}
 	}
+	if (args->encrypt) {
+		ret = fscrypt_set_context(inode, trans);
+		if (ret) {
+			btrfs_abort_transaction(trans, ret);
+			goto discard;
+		}
+	}
 
 	ret = btrfs_add_inode_to_root(BTRFS_I(inode), false);
 	if (WARN_ON(ret)) {
diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index a39460bf68a7..6a37dd3cc5ee 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -153,6 +153,8 @@ static unsigned int btrfs_inode_flags_to_fsflags(const struct btrfs_inode *inode
 		iflags |= FS_DIRSYNC_FL;
 	if (flags & BTRFS_INODE_NODATACOW)
 		iflags |= FS_NOCOW_FL;
+	if (flags & BTRFS_INODE_ENCRYPT)
+		iflags |= FS_ENCRYPT_FL;
 	if (ro_flags & BTRFS_INODE_RO_VERITY)
 		iflags |= FS_VERITY_FL;
 
@@ -181,12 +183,14 @@ void btrfs_sync_inode_flags_to_i_flags(struct btrfs_inode *inode)
 		new_fl |= S_NOATIME;
 	if (inode->flags & BTRFS_INODE_DIRSYNC)
 		new_fl |= S_DIRSYNC;
+	if (inode->flags & BTRFS_INODE_ENCRYPT)
+		new_fl |= S_ENCRYPTED;
 	if (inode->ro_flags & BTRFS_INODE_RO_VERITY)
 		new_fl |= S_VERITY;
 
 	set_mask_bits(&inode->vfs_inode.i_flags,
 		      S_SYNC | S_APPEND | S_IMMUTABLE | S_NOATIME | S_DIRSYNC |
-		      S_VERITY, new_fl);
+		      S_VERITY | S_ENCRYPTED, new_fl);
 }
 
 /*
@@ -199,7 +203,7 @@ static int check_fsflags(unsigned int old_flags, unsigned int flags)
 		      FS_NOATIME_FL | FS_NODUMP_FL | \
 		      FS_SYNC_FL | FS_DIRSYNC_FL | \
 		      FS_NOCOMP_FL | FS_COMPR_FL |
-		      FS_NOCOW_FL))
+		      FS_NOCOW_FL | FS_ENCRYPT_FL))
 		return -EOPNOTSUPP;
 
 	/* COMPR and NOCOMP on new/old are valid */
diff --git a/include/uapi/linux/btrfs_tree.h b/include/uapi/linux/btrfs_tree.h
index cc3b9f7dccaf..3cec245268e7 100644
--- a/include/uapi/linux/btrfs_tree.h
+++ b/include/uapi/linux/btrfs_tree.h
@@ -167,6 +167,8 @@
 #define BTRFS_VERITY_DESC_ITEM_KEY	36
 #define BTRFS_VERITY_MERKLE_ITEM_KEY	37
 
+#define BTRFS_FSCRYPT_INODE_CTX_KEY	41
+
 #define BTRFS_ORPHAN_ITEM_KEY		48
 /* reserve 2-15 close to the inode for later flexibility */
 
@@ -431,6 +433,7 @@ static inline __u8 btrfs_dir_flags_to_ftype(__u8 flags)
 #define BTRFS_INODE_NOATIME		(1U << 9)
 #define BTRFS_INODE_DIRSYNC		(1U << 10)
 #define BTRFS_INODE_COMPRESS		(1U << 11)
+#define BTRFS_INODE_ENCRYPT		(1U << 12)
 
 #define BTRFS_INODE_ROOT_ITEM_INIT	(1U << 31)
 
@@ -447,6 +450,7 @@ static inline __u8 btrfs_dir_flags_to_ftype(__u8 flags)
 	 BTRFS_INODE_NOATIME |						\
 	 BTRFS_INODE_DIRSYNC |						\
 	 BTRFS_INODE_COMPRESS |						\
+	 BTRFS_INODE_ENCRYPT |						\
 	 BTRFS_INODE_ROOT_ITEM_INIT)
 
 #define BTRFS_INODE_RO_VERITY		(1U << 0)
@@ -1075,6 +1079,12 @@ enum {
 	BTRFS_NR_FILE_EXTENT_TYPES = 3,
 };
 
+enum btrfs_encryption_type {
+	BTRFS_ENCRYPTION_NONE,
+	BTRFS_ENCRYPTION_FSCRYPT,
+	BTRFS_NR_ENCRYPTION_TYPES,
+};
+
 struct btrfs_file_extent_item {
 	/*
 	 * transaction id that created this extent
-- 
2.53.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox