Skip to contentSkip to conversation

fs: interface for directly reading/writing compressed data

PATCH Superseded Omar Sandoval 6 participants 30 messages Mar 16, 2021 – Mar 20, 2021

Subsystems (11)

v8 of 9 Superseded Latest is v9 →

lore
Share & tools
lore
#0 lore raw

v8 cover fs: interface for directly reading/writing compressed data

From: Omar Sandoval <[email protected]>

This series adds an API for reading compressed data on a filesystem without decompressing it as well as support for writing compressed data directly to the filesystem. As with the previous submissions, I've included a man page patch describing the API. I have test cases (including fsstress support) and example programs which I'll send up [1].

The main use-case is Btrfs send/receive: currently, when sending data from one compressed filesystem to another, the sending side decompresses the data and the receiving side recompresses it before writing it out. This is wasteful and can be avoided if we can just send and write compressed extents. The patches implementing the send/receive support will be sent shortly.

Patches 1-3 add the VFS support and UAPI. Patch 4 is a fix that this series depends on; it can be merged independently. Patches 5-8 are Btrfs prep patches. Patch 9 adds Btrfs encoded read support and patch 10 adds Btrfs encoded write support.

These patches are based on Dave Sterba's Btrfs misc-next branch [2], which is in turn currently based on v5.12-rc3.

Changes since v7 [3]:

- Rebased on current kdave/misc-next (v5.12-rc3).

Omar Sandoval (10):
iov_iter: add copy_struct_from_iter()
fs: add O_ALLOW_ENCODED open flag
fs: add RWF_ENCODED for reading/writing compressed data
btrfs: fix check_data_csum() error message for direct I/O
btrfs: don't advance offset for compressed bios in
btrfs_csum_one_bio()
btrfs: add ram_bytes and offset to btrfs_ordered_extent
btrfs: support different disk extent size for delalloc
btrfs: optionally extend i_size in cow_file_range_inline()
btrfs: implement RWF_ENCODED reads
btrfs: implement RWF_ENCODED writes

1: https://github.com/osandov/xfstests/tree/rwf-encoded
2: https://github.com/kdave/btrfs-devel/tree/misc-next
3: https://lore.kernel.org/linux-btrfs/[email protected]/

 Documentation/filesystems/encoded_io.rst |  74 ++
 Documentation/filesystems/index.rst      |   1 +
 arch/alpha/include/uapi/asm/fcntl.h      |   1 +
 arch/parisc/include/uapi/asm/fcntl.h     |   1 +
 arch/sparc/include/uapi/asm/fcntl.h      |   1 +
 fs/btrfs/compression.c                   |  12 +-
 fs/btrfs/compression.h                   |   6 +-
 fs/btrfs/ctree.h                         |   9 +-
 fs/btrfs/delalloc-space.c                |  18 +-
 fs/btrfs/file-item.c                     |  35 +-
 fs/btrfs/file.c                          |  46 +-
 fs/btrfs/inode.c                         | 936 ++++++++++++++++++++---
 fs/btrfs/ordered-data.c                  |  80 +-
 fs/btrfs/ordered-data.h                  |  18 +-
 fs/btrfs/relocation.c                    |   4 +-
 fs/fcntl.c                               |  10 +-
 fs/namei.c                               |   4 +
 fs/read_write.c                          | 168 +++-
 include/linux/encoded_io.h               |  17 +
 include/linux/fcntl.h                    |   2 +-
 include/linux/fs.h                       |  13 +
 include/linux/uio.h                      |   2 +
 include/uapi/asm-generic/fcntl.h         |   4 +
 include/uapi/linux/encoded_io.h          |  30 +
 include/uapi/linux/fs.h                  |   5 +-
 lib/iov_iter.c                           |  82 ++
 26 files changed, 1378 insertions(+), 201 deletions(-)
 create mode 100644 Documentation/filesystems/encoded_io.rst
 create mode 100644 include/linux/encoded_io.h
 create mode 100644 include/uapi/linux/encoded_io.h
Signature · 1 line
2.30.2
#1 lore raw

PATCH v8 1/1 Document encoded I/O

From: Omar Sandoval <[email protected]>

This adds a new page, encoded_io(7), providing an overview of encoded I/O and updates fcntl(2), open(2), and preadv2(2)/pwritev2(2) to reference it.

---
 man2/fcntl.2      |   8 +
 man2/open.2       |  13 ++
 man2/readv.2      |  69 +++++++++
 man7/encoded_io.7 | 369 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 459 insertions(+)
 create mode 100644 man7/encoded_io.7
man2/fcntl.2 +8 -0
index a15f467ef..1081cb70f 100644
@@ -217,6 +217,7 @@ in
 .I arg
 are ignored.
 On Linux, this command can change only the
+.BR O_ALLOW_ENCODED ,
 .BR O_APPEND ,
 .BR O_ASYNC ,
 .BR O_DIRECT ,
@@ -1815,6 +1816,13 @@ and the soft or hard user pipe limit has been reached; see
 .BR pipe (7).
 .TP
 .B EPERM
+Attempted to set the
+.B O_ALLOW_ENCODED
+flag and the calling process did not have the
+.B CAP_SYS_ADMIN
+capability.
+.TP
+.B EPERM
 Attempted to clear the
 .B O_APPEND
 flag on a file that has the append-only attribute set.
man2/open.2 +13 -0
index 03fff1b65..0456a173b 100644
@@ -180,6 +180,14 @@ for details.
 .PP
 The full list of file creation flags and file status flags is as follows:
 .TP
+.B O_ALLOW_ENCODED
+Open the file with encoded I/O permissions;
+see
+.BR encoded_io (7).
+The caller must have the
+.B CAP_SYS_ADMIN
+capability.
+.TP
 .B O_APPEND
 The file is opened in append mode.
 Before each
@@ -1232,6 +1240,11 @@ did not match the owner of the file and the caller was not privileged.
 The operation was prevented by a file seal; see
 .BR fcntl (2).
 .TP
+.B EPERM
+The
+.B O_ALLOW_ENCODED
+flag was specified, but the caller was not privileged.
+.TP
 .B EROFS
 .I pathname
 refers to a file on a read-only filesystem and write access was
man2/readv.2 +69 -0

87 lines not on this page · raw message

man7/encoded_io.7 new file +369 -0

369 lines not on this page · raw message

Signature · 1 line
2.30.2
#2 lore raw

PATCH v8 1/10 iov_iter: add copy_struct_from_iter()

In reply to Omar Sandoval

From: Omar Sandoval <[email protected]>

This is essentially copy_struct_from_user() but for an iov_iter.

---
 include/linux/uio.h |  2 ++
 lib/iov_iter.c      | 82 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 84 insertions(+)
include/linux/uio.h +2 -0
index 72d88566694e..f4e6ea85a269 100644
@@ -121,6 +121,8 @@ size_t copy_page_to_iter(struct page *page, size_t offset, size_t bytes,
 			 struct iov_iter *i);
 size_t copy_page_from_iter(struct page *page, size_t offset, size_t bytes,
 			 struct iov_iter *i);
+int copy_struct_from_iter(void *dst, size_t ksize, struct iov_iter *i,
+			  size_t usize);
 
 size_t _copy_to_iter(const void *addr, size_t bytes, struct iov_iter *i);
 size_t _copy_from_iter(void *addr, size_t bytes, struct iov_iter *i);
lib/iov_iter.c +82 -0

88 lines not on this page · raw message

Signature · 1 line
2.30.2
#3 lore raw

Re: [PATCH v8 01/10] iov_iter: add copy_struct_from_iter()

Quoted 99 lines · Omar Sandoval

From: Omar Sandoval <[email protected]>

This is essentially copy_struct_from_user() but for an iov_iter.

---
 include/linux/uio.h |  2 ++
 lib/iov_iter.c      | 82 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 84 insertions(+)
include/linux/uio.h +2 -0
index 72d88566694e..f4e6ea85a269 100644
@@ -121,6 +121,8 @@ size_t copy_page_to_iter(struct page *page, size_t offset, size_t bytes,
 			 struct iov_iter *i);
 size_t copy_page_from_iter(struct page *page, size_t offset, size_t bytes,
 			 struct iov_iter *i);
+int copy_struct_from_iter(void *dst, size_t ksize, struct iov_iter *i,
+			  size_t usize);
 
 size_t _copy_to_iter(const void *addr, size_t bytes, struct iov_iter *i);
 size_t _copy_from_iter(void *addr, size_t bytes, struct iov_iter *i);
lib/iov_iter.c +66 -0
index a21e6a5792c5..f45826ed7528 100644
@@ -948,6 +948,88 @@ size_t copy_page_from_iter(struct page *page, size_t offset, size_t bytes,
 }
 EXPORT_SYMBOL(copy_page_from_iter);
 
+/**
+ * copy_struct_from_iter - copy a struct from an iov_iter
+ * @dst: Destination buffer.
+ * @ksize: Size of @dst struct.
+ * @i: Source iterator.
+ * @usize: (Alleged) size of struct in @i.
+ *
+ * Copies a struct from an iov_iter in a way that guarantees
+ * backwards-compatibility for struct arguments in an iovec (as long as the
+ * rules for copy_struct_from_user() are followed).
+ *
+ * The recommended usage is that @usize be taken from the current segment:
+ *
+ *   int do_foo(struct iov_iter *i)
+ *   {
+ *     size_t usize = iov_iter_single_seg_count(i);
+ *     struct foo karg;
+ *     int err;
+ *
+ *     if (usize > PAGE_SIZE)
+ *       return -E2BIG;
+ *     if (usize < FOO_SIZE_VER0)
+ *       return -EINVAL;
+ *     err = copy_struct_from_iter(&karg, sizeof(karg), i, usize);
+ *     if (err)
+ *       return err;
+ *
+ *     // ...
+ *   }
+ *
+ * Return: 0 on success, -errno on error (see copy_struct_from_user()).
+ *
+ * On success, the iterator is advanced @usize bytes. On error, the iterator is
+ * not advanced.
+ */
+int copy_struct_from_iter(void *dst, size_t ksize, struct iov_iter *i,
+			  size_t usize)
+{
+	if (usize <= ksize) {
+		if (!copy_from_iter_full(dst, usize, i))
+			return -EFAULT;
+		memset(dst + usize, 0, ksize - usize);
+	} else {
+		size_t copied = 0, copy;
+		int ret;
+
+		if (WARN_ON(iov_iter_is_pipe(i)) || unlikely(i->count < usize))
+			return -EFAULT;
+		if (iter_is_iovec(i))
+			might_fault();
+		iterate_all_kinds(i, usize, v, ({
+			copy = min(ksize - copied, v.iov_len);
+			if (copy && copyin(dst + copied, v.iov_base, copy))
+				return -EFAULT;
+			copied += copy;
+			ret = check_zeroed_user(v.iov_base + copy,
+						v.iov_len - copy);
+			if (ret <= 0)
+				return ret ?: -E2BIG;
+			0;}), ({
+			char *addr = kmap_atomic(v.bv_page);
+			copy = min_t(size_t, ksize - copied, v.bv_len);
+			memcpy(dst + copied, addr + v.bv_offset, copy);
+			copied += copy;
+			ret = memchr_inv(addr + v.bv_offset + copy, 0,
+					 v.bv_len - copy) ? -E2BIG : 0;

lib/iov_iter.c size_t copy_page_from_iter(struct page *page, size_t offset, size_t bytes, · line 1026

Quoted 10 lines
lib/iov_iter.c +10 -0
+			kunmap_atomic(addr);
+			if (ret)
+				return ret;
+			}), ({
+			copy = min(ksize - copied, v.iov_len);
+			memcpy(dst + copied, v.iov_base, copy);
+			if (memchr_inv(v.iov_base, 0, v.iov_len))
+				return -E2BIG;
+			})
+		)

Following the semantics of copy_struct_from_user() is certainly a good idea but can this in any way be rewritten to not look like this; at least not as crammed. It's a bit painful to follow here what's going.

#4 lore raw

Re: [PATCH v8 01/10] iov_iter: add copy_struct_from_iter()

Quoted 107 lines · Christian Brauner
Quoted 106 lines · Omar Sandoval

From: Omar Sandoval <[email protected]>

This is essentially copy_struct_from_user() but for an iov_iter.

---
 include/linux/uio.h |  2 ++
 lib/iov_iter.c      | 82 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 84 insertions(+)
include/linux/uio.h +2 -0
index 72d88566694e..f4e6ea85a269 100644
@@ -121,6 +121,8 @@ size_t copy_page_to_iter(struct page *page, size_t offset, size_t bytes,
 			 struct iov_iter *i);
 size_t copy_page_from_iter(struct page *page, size_t offset, size_t bytes,
 			 struct iov_iter *i);
+int copy_struct_from_iter(void *dst, size_t ksize, struct iov_iter *i,
+			  size_t usize);
 
 size_t _copy_to_iter(const void *addr, size_t bytes, struct iov_iter *i);
 size_t _copy_from_iter(void *addr, size_t bytes, struct iov_iter *i);
lib/iov_iter.c +73 -0
index a21e6a5792c5..f45826ed7528 100644
@@ -948,6 +948,88 @@ size_t copy_page_from_iter(struct page *page, size_t offset, size_t bytes,
 }
 EXPORT_SYMBOL(copy_page_from_iter);
 
+/**
+ * copy_struct_from_iter - copy a struct from an iov_iter
+ * @dst: Destination buffer.
+ * @ksize: Size of @dst struct.
+ * @i: Source iterator.
+ * @usize: (Alleged) size of struct in @i.
+ *
+ * Copies a struct from an iov_iter in a way that guarantees
+ * backwards-compatibility for struct arguments in an iovec (as long as the
+ * rules for copy_struct_from_user() are followed).
+ *
+ * The recommended usage is that @usize be taken from the current segment:
+ *
+ *   int do_foo(struct iov_iter *i)
+ *   {
+ *     size_t usize = iov_iter_single_seg_count(i);
+ *     struct foo karg;
+ *     int err;
+ *
+ *     if (usize > PAGE_SIZE)
+ *       return -E2BIG;
+ *     if (usize < FOO_SIZE_VER0)
+ *       return -EINVAL;
+ *     err = copy_struct_from_iter(&karg, sizeof(karg), i, usize);
+ *     if (err)
+ *       return err;
+ *
+ *     // ...
+ *   }
+ *
+ * Return: 0 on success, -errno on error (see copy_struct_from_user()).
+ *
+ * On success, the iterator is advanced @usize bytes. On error, the iterator is
+ * not advanced.
+ */
+int copy_struct_from_iter(void *dst, size_t ksize, struct iov_iter *i,
+			  size_t usize)
+{
+	if (usize <= ksize) {
+		if (!copy_from_iter_full(dst, usize, i))
+			return -EFAULT;
+		memset(dst + usize, 0, ksize - usize);
+	} else {
+		size_t copied = 0, copy;
+		int ret;
+
+		if (WARN_ON(iov_iter_is_pipe(i)) || unlikely(i->count < usize))
+			return -EFAULT;
+		if (iter_is_iovec(i))
+			might_fault();
+		iterate_all_kinds(i, usize, v, ({
+			copy = min(ksize - copied, v.iov_len);
+			if (copy && copyin(dst + copied, v.iov_base, copy))
+				return -EFAULT;
+			copied += copy;
+			ret = check_zeroed_user(v.iov_base + copy,
+						v.iov_len - copy);
+			if (ret <= 0)
+				return ret ?: -E2BIG;
+			0;}), ({
+			char *addr = kmap_atomic(v.bv_page);
+			copy = min_t(size_t, ksize - copied, v.bv_len);
+			memcpy(dst + copied, addr + v.bv_offset, copy);
+			copied += copy;
+			ret = memchr_inv(addr + v.bv_offset + copy, 0,
+					 v.bv_len - copy) ? -E2BIG : 0;
+			kunmap_atomic(addr);
+			if (ret)
+				return ret;
+			}), ({
+			copy = min(ksize - copied, v.iov_len);
+			memcpy(dst + copied, v.iov_base, copy);
+			if (memchr_inv(v.iov_base, 0, v.iov_len))
Quoted 8 lines

lib/iov_iter.c size_t copy_page_from_iter(struct page *page, size_t offset, size_t bytes, · line 1026

Quoted 3 lines
lib/iov_iter.c +3 -0
+				return -E2BIG;
+			})
+		)

Following the semantics of copy_struct_from_user() is certainly a good idea but can this in any way be rewritten to not look like this; at least not as crammed. It's a bit painful to follow here what's going.

I think that's just the nature of the iov_iter code :) I'm just following the rest of this file, which uses some mind-expanding macros. Do you have any suggestions for how to clean this function up?

#5 lore raw

Re: [PATCH v8 01/10] iov_iter: add copy_struct_from_iter()

Quoted 117 lines · Omar Sandoval
Quoted 107 lines · Christian Brauner
Quoted 106 lines · Omar Sandoval

From: Omar Sandoval <[email protected]>

This is essentially copy_struct_from_user() but for an iov_iter.

---
 include/linux/uio.h |  2 ++
 lib/iov_iter.c      | 82 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 84 insertions(+)
include/linux/uio.h +2 -0
index 72d88566694e..f4e6ea85a269 100644
@@ -121,6 +121,8 @@ size_t copy_page_to_iter(struct page *page, size_t offset, size_t bytes,
 			 struct iov_iter *i);
 size_t copy_page_from_iter(struct page *page, size_t offset, size_t bytes,
 			 struct iov_iter *i);
+int copy_struct_from_iter(void *dst, size_t ksize, struct iov_iter *i,
+			  size_t usize);
 
 size_t _copy_to_iter(const void *addr, size_t bytes, struct iov_iter *i);
 size_t _copy_from_iter(void *addr, size_t bytes, struct iov_iter *i);
lib/iov_iter.c +73 -0
index a21e6a5792c5..f45826ed7528 100644
@@ -948,6 +948,88 @@ size_t copy_page_from_iter(struct page *page, size_t offset, size_t bytes,
 }
 EXPORT_SYMBOL(copy_page_from_iter);
 
+/**
+ * copy_struct_from_iter - copy a struct from an iov_iter
+ * @dst: Destination buffer.
+ * @ksize: Size of @dst struct.
+ * @i: Source iterator.
+ * @usize: (Alleged) size of struct in @i.
+ *
+ * Copies a struct from an iov_iter in a way that guarantees
+ * backwards-compatibility for struct arguments in an iovec (as long as the
+ * rules for copy_struct_from_user() are followed).
+ *
+ * The recommended usage is that @usize be taken from the current segment:
+ *
+ *   int do_foo(struct iov_iter *i)
+ *   {
+ *     size_t usize = iov_iter_single_seg_count(i);
+ *     struct foo karg;
+ *     int err;
+ *
+ *     if (usize > PAGE_SIZE)
+ *       return -E2BIG;
+ *     if (usize < FOO_SIZE_VER0)
+ *       return -EINVAL;
+ *     err = copy_struct_from_iter(&karg, sizeof(karg), i, usize);
+ *     if (err)
+ *       return err;
+ *
+ *     // ...
+ *   }
+ *
+ * Return: 0 on success, -errno on error (see copy_struct_from_user()).
+ *
+ * On success, the iterator is advanced @usize bytes. On error, the iterator is
+ * not advanced.
+ */
+int copy_struct_from_iter(void *dst, size_t ksize, struct iov_iter *i,
+			  size_t usize)
+{
+	if (usize <= ksize) {
+		if (!copy_from_iter_full(dst, usize, i))
+			return -EFAULT;
+		memset(dst + usize, 0, ksize - usize);
+	} else {
+		size_t copied = 0, copy;
+		int ret;
+
+		if (WARN_ON(iov_iter_is_pipe(i)) || unlikely(i->count < usize))
+			return -EFAULT;
+		if (iter_is_iovec(i))
+			might_fault();
+		iterate_all_kinds(i, usize, v, ({
+			copy = min(ksize - copied, v.iov_len);
+			if (copy && copyin(dst + copied, v.iov_base, copy))
+				return -EFAULT;
+			copied += copy;
+			ret = check_zeroed_user(v.iov_base + copy,
+						v.iov_len - copy);
+			if (ret <= 0)
+				return ret ?: -E2BIG;
+			0;}), ({
+			char *addr = kmap_atomic(v.bv_page);
+			copy = min_t(size_t, ksize - copied, v.bv_len);
+			memcpy(dst + copied, addr + v.bv_offset, copy);
+			copied += copy;
+			ret = memchr_inv(addr + v.bv_offset + copy, 0,
+					 v.bv_len - copy) ? -E2BIG : 0;
+			kunmap_atomic(addr);
+			if (ret)
+				return ret;
+			}), ({
+			copy = min(ksize - copied, v.iov_len);
+			memcpy(dst + copied, v.iov_base, copy);
+			if (memchr_inv(v.iov_base, 0, v.iov_len))
Quoted 8 lines

lib/iov_iter.c size_t copy_page_from_iter(struct page *page, size_t offset, size_t bytes, · line 1026

Quoted 3 lines
lib/iov_iter.c +3 -0
+				return -E2BIG;
+			})
+		)

Following the semantics of copy_struct_from_user() is certainly a good idea but can this in any way be rewritten to not look like this; at least not as crammed. It's a bit painful to follow here what's going.

Quoted 3 lines

I think that's just the nature of the iov_iter code :) I'm just following the rest of this file, which uses some mind-expanding macros. Do you have any suggestions for how to clean this function up?

I think the follow-up discussion this triggered caused an improvement now. :) Christian

#6 lore raw

PATCH v8 2/10 fs: add O_ALLOW_ENCODED open flag

In reply to Omar Sandoval

From: Omar Sandoval <[email protected]>

The upcoming RWF_ENCODED operation introduces some security concerns:

1. Compressed writes will pass arbitrary data to decompression algorithms in the kernel.
2. Compressed reads can leak truncated/hole punched data.

Therefore, we need to require privilege for RWF_ENCODED. It's not possible to do the permissions checks at the time of the read or write because, e.g., io_uring submits IO from a worker thread. So, add an open flag which requires CAP_SYS_ADMIN. It can also be set and cleared with fcntl(). The flag is not cleared in any way on fork or exec.

Note that the usual issue that unknown open flags are ignored doesn't really matter for O_ALLOW_ENCODED; if the kernel doesn't support O_ALLOW_ENCODED, then it doesn't support RWF_ENCODED, either.

---
 arch/alpha/include/uapi/asm/fcntl.h  |  1 +
 arch/parisc/include/uapi/asm/fcntl.h |  1 +
 arch/sparc/include/uapi/asm/fcntl.h  |  1 +
 fs/fcntl.c                           | 10 ++++++++--
 fs/namei.c                           |  4 ++++
 include/linux/fcntl.h                |  2 +-
 include/uapi/asm-generic/fcntl.h     |  4 ++++
 7 files changed, 20 insertions(+), 3 deletions(-)
arch/alpha/include/uapi/asm/fcntl.h +1 -0
index 50bdc8e8a271..391e0d112e41 100644
@@ -34,6 +34,7 @@
 
 #define O_PATH		040000000
 #define __O_TMPFILE	0100000000
+#define O_ALLOW_ENCODED	0200000000
 
 #define F_GETLK		7
 #define F_SETLK		8
arch/parisc/include/uapi/asm/fcntl.h +1 -0
index 03dee816cb13..72ea9bdf5f04 100644
@@ -19,6 +19,7 @@
 
 #define O_PATH		020000000
 #define __O_TMPFILE	040000000
+#define O_ALLOW_ENCODED	100000000
 
 #define F_GETLK64	8
 #define F_SETLK64	9
arch/sparc/include/uapi/asm/fcntl.h +1 -0
index 67dae75e5274..ac3e8c9cb32c 100644
@@ -37,6 +37,7 @@
 
 #define O_PATH		0x1000000
 #define __O_TMPFILE	0x2000000
+#define O_ALLOW_ENCODED	0x8000000
 
 #define F_GETOWN	5	/*  for sockets. */
 #define F_SETOWN	6	/*  for sockets. */
fs/fcntl.c +8 -2
index 05b36b28f2e8..2c1f0580a497 100644
@@ -30,7 +30,8 @@
 #include <asm/siginfo.h>
 #include <linux/uaccess.h>
 
-#define SETFL_MASK (O_APPEND | O_NONBLOCK | O_NDELAY | O_DIRECT | O_NOATIME)
+#define SETFL_MASK (O_APPEND | O_NONBLOCK | O_NDELAY | O_DIRECT | O_NOATIME | \
+		    O_ALLOW_ENCODED)
 
 static int setfl(int fd, struct file * filp, unsigned long arg)
 {
@@ -49,6 +50,11 @@ static int setfl(int fd, struct file * filp, unsigned long arg)
 		if (!inode_owner_or_capable(inode))
 			return -EPERM;
 
+	/* O_ALLOW_ENCODED can only be set by superuser */
+	if ((arg & O_ALLOW_ENCODED) && !(filp->f_flags & O_ALLOW_ENCODED) &&
+	    !capable(CAP_SYS_ADMIN))
+		return -EPERM;
+
 	/* required for strict SunOS emulation */
 	if (O_NONBLOCK != O_NDELAY)
 	       if (arg & O_NDELAY)
@@ -1035,7 +1041,7 @@ static int __init fcntl_init(void)
 	 * Exceptions: O_NONBLOCK is a two bit define on parisc; O_NDELAY
 	 * is defined as O_NONBLOCK on some platforms and not on others.
 	 */
-	BUILD_BUG_ON(21 - 1 /* for O_RDONLY being 0 */ !=
+	BUILD_BUG_ON(22 - 1 /* for O_RDONLY being 0 */ !=
 		HWEIGHT32(
 			(VALID_OPEN_FLAGS & ~(O_NONBLOCK | O_NDELAY)) |
 			__FMODE_EXEC | __FMODE_NONOTIFY));
fs/namei.c +4 -0
index 78443a85480a..73b925b133a0 100644
@@ -2892,6 +2892,10 @@ static int may_open(const struct path *path, int acc_mode, int flag)
 	if (flag & O_NOATIME && !inode_owner_or_capable(inode))
 		return -EPERM;
 
+	/* O_ALLOW_ENCODED can only be set by superuser */
+	if ((flag & O_ALLOW_ENCODED) && !capable(CAP_SYS_ADMIN))
+		return -EPERM;
+
 	return 0;
 }
 
include/linux/fcntl.h +1 -1
index 921e750843e6..dc66c557b7d0 100644
@@ -10,7 +10,7 @@
 	(O_RDONLY | O_WRONLY | O_RDWR | O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC | \
 	 O_APPEND | O_NDELAY | O_NONBLOCK | __O_SYNC | O_DSYNC | \
 	 FASYNC	| O_DIRECT | O_LARGEFILE | O_DIRECTORY | O_NOFOLLOW | \
-	 O_NOATIME | O_CLOEXEC | O_PATH | __O_TMPFILE)
+	 O_NOATIME | O_CLOEXEC | O_PATH | __O_TMPFILE | O_ALLOW_ENCODED)
 
 /* List of all valid flags for the how->upgrade_mask argument: */
 #define VALID_UPGRADE_FLAGS \
include/uapi/asm-generic/fcntl.h +4 -0
index 9dc0bf0c5a6e..75321c7a66ac 100644
@@ -89,6 +89,10 @@
 #define __O_TMPFILE	020000000
 #endif
 
+#ifndef O_ALLOW_ENCODED
+#define O_ALLOW_ENCODED	040000000
+#endif
+
 /* a horrid kludge trying to make sure that this will fail on old kernels */
 #define O_TMPFILE (__O_TMPFILE | O_DIRECTORY)
 #define O_TMPFILE_MASK (__O_TMPFILE | O_DIRECTORY | O_CREAT)      
Signature · 1 line
2.30.2
#7 lore raw

PATCH v8 3/10 fs: add RWF_ENCODED for reading/writing compressed data

In reply to Omar Sandoval

From: Omar Sandoval <[email protected]>

Btrfs supports transparent compression: data written by the user can be compressed when written to disk and decompressed when read back. However, we'd like to add an interface to write pre-compressed data directly to the filesystem, and the matching interface to read compressed data without decompressing it. This adds support for so-called "encoded I/O" via preadv2() and pwritev2().

A new RWF_ENCODED flags indicates that a read or write is "encoded". If this flag is set, iov[0].iov_base points to a struct encoded_iov which is used for metadata: namely, the compression algorithm, unencoded (i.e., decompressed) length, and what subrange of the unencoded data should be used (needed for truncated or hole-punched extents and when reading in the middle of an extent). For reads, the filesystem returns this information; for writes, the caller provides it to the filesystem. iov[0].iov_len must be set to sizeof(struct encoded_iov), which can be used to extend the interface in the future a la copy_struct_from_user(). The remaining iovecs contain the encoded extent.

This adds the VFS helpers for supporting encoded I/O and documentation for filesystem support.

---
 Documentation/filesystems/encoded_io.rst |  74 ++++++++++
 Documentation/filesystems/index.rst      |   1 +
 fs/read_write.c                          | 168 +++++++++++++++++++++--
 include/linux/encoded_io.h               |  17 +++
 include/linux/fs.h                       |  13 ++
 include/uapi/linux/encoded_io.h          |  30 ++++
 include/uapi/linux/fs.h                  |   5 +-
 7 files changed, 294 insertions(+), 14 deletions(-)
 create mode 100644 Documentation/filesystems/encoded_io.rst
 create mode 100644 include/linux/encoded_io.h
 create mode 100644 include/uapi/linux/encoded_io.h
Documentation/filesystems/encoded_io.rst new file +74 -0
new file mode 100644
index 000000000000..50405276d866
@@ -0,0 +1,74 @@
+===========
+Encoded I/O
+===========
+
+Encoded I/O is a mechanism for reading and writing encoded (e.g., compressed
+and/or encrypted) data directly from/to the filesystem. The userspace interface
+is thoroughly described in the :manpage:`encoded_io(7)` man page; this document
+describes the requirements for filesystem support.
+
+First of all, a filesystem supporting encoded I/O must indicate this by setting
+the ``FMODE_ENCODED_IO`` flag in its ``file_open`` file operation::
+
+    static int foo_file_open(struct inode *inode, struct file *filp)
+    {
+            ...
+            filep->f_mode |= FMODE_ENCODED_IO;
+            ...
+    }
+
+Encoded I/O goes through ``read_iter`` and ``write_iter``, designated by the
+``IOCB_ENCODED`` flag in ``kiocb->ki_flags``.
+
+Reads
+=====
+
+Encoded ``read_iter`` should:
+
+1. Call ``generic_encoded_read_checks()`` to validate the file and buffers
+   provided by userspace.
+2. Initialize the ``encoded_iov`` appropriately.
+3. Copy it to the user with ``copy_encoded_iov_to_iter()``.
+4. Copy the encoded data to the user.
+5. Advance ``kiocb->ki_pos`` by ``encoded_iov->len``.
+6. Return the size of the encoded data read, not including the ``encoded_iov``.
+
+There are a few details to be aware of:
+
+* Encoded ``read_iter`` should support reading unencoded data if the extent is
+  not encoded.
+* If the buffers provided by the user are not large enough to contain an entire
+  encoded extent, then ``read_iter`` should return ``-ENOBUFS``. This is to
+  avoid confusing userspace with truncated data that cannot be properly
+  decoded.
+* Reads in the middle of an encoded extent can be returned by setting
+  ``encoded_iov->unencoded_offset`` to non-zero.
+* Truncated unencoded data (e.g., because the file does not end on a block
+  boundary) may be returned by setting ``encoded_iov->len`` to a value smaller
+  value than ``encoded_iov->unencoded_len - encoded_iov->unencoded_offset``.
+
+Writes
+======
+
+Encoded ``write_iter`` should (in addition to the usual accounting/checks done
+by ``write_iter``):
+
+1. Call ``copy_encoded_iov_from_iter()`` to get and validate the
+   ``encoded_iov``.
+2. Call ``generic_encoded_write_checks()`` instead of
+   ``generic_write_checks()``.
+3. Check that the provided encoding in ``encoded_iov`` is supported.
+4. Advance ``kiocb->ki_pos`` by ``encoded_iov->len``.
+5. Return the size of the encoded data written.
+
+Again, there are a few details:
+
+* Encoded ``write_iter`` doesn't need to support writing unencoded data.
+* ``write_iter`` should either write all of the encoded data or none of it; it
+  must not do partial writes.
+* ``write_iter`` doesn't need to validate the encoded data; a subsequent read
+  may return, e.g., ``-EIO`` if the data is not valid.
+* The user may lie about the unencoded size of the data; a subsequent read
+  should truncate or zero-extend the unencoded data rather than returning an
+  error.
+* Be careful of page cache coherency.
Documentation/filesystems/index.rst +1 -0
index 7be9b46d85d9..258b745ea3d8 100644
@@ -53,6 +53,7 @@ filesystem implementations.
    journalling
    fscrypt
    fsverity
+   encoded_io
 
 Filesystems
 ===========
fs/read_write.c +155 -13

196 lines not on this page · raw message

include/linux/encoded_io.h new file +17 -0
new file mode 100644
index 000000000000..a8cfc0108ba0
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_ENCODED_IO_H
+#define _LINUX_ENCODED_IO_H
+
+#include <uapi/linux/encoded_io.h>
+
+struct encoded_iov;
+struct iov_iter;
+struct kiocb;
+extern int generic_encoded_write_checks(struct kiocb *,
+					const struct encoded_iov *);
+extern int copy_encoded_iov_from_iter(struct encoded_iov *, struct iov_iter *);
+extern ssize_t generic_encoded_read_checks(struct kiocb *, struct iov_iter *);
+extern int copy_encoded_iov_to_iter(const struct encoded_iov *,
+				    struct iov_iter *);
+
+#endif /* _LINUX_ENCODED_IO_H */
include/linux/fs.h +13 -0
index fd47deea7c17..2a0700e4efcc 100644
@@ -178,6 +178,9 @@ typedef int (dio_iodone_t)(struct kiocb *iocb, loff_t offset,
 /* File supports async buffered reads */
 #define FMODE_BUF_RASYNC	((__force fmode_t)0x40000000)
 
+/* File supports encoded IO */
+#define FMODE_ENCODED_IO	((__force fmode_t)0x80000000)
+
 /*
  * Attribute flags.  These should be or-ed together to figure out what
  * has been changed!
@@ -308,6 +311,7 @@ enum rw_hint {
 #define IOCB_SYNC		(__force int) RWF_SYNC
 #define IOCB_NOWAIT		(__force int) RWF_NOWAIT
 #define IOCB_APPEND		(__force int) RWF_APPEND
+#define IOCB_ENCODED		(__force int) RWF_ENCODED
 
 /* non-RWF related bits - start at 16 */
 #define IOCB_EVENTFD		(1 << 16)
@@ -2961,6 +2965,13 @@ extern int generic_file_readonly_mmap(struct file *, struct vm_area_struct *);
 extern ssize_t generic_write_checks(struct kiocb *, struct iov_iter *);
 extern int generic_write_check_limits(struct file *file, loff_t pos,
 		loff_t *count);
+struct encoded_iov;
+extern int generic_encoded_write_checks(struct kiocb *,
+					const struct encoded_iov *);
+extern int copy_encoded_iov_from_iter(struct encoded_iov *, struct iov_iter *);
+extern ssize_t generic_encoded_read_checks(struct kiocb *, struct iov_iter *);
+extern int copy_encoded_iov_to_iter(const struct encoded_iov *,
+				    struct iov_iter *);
 extern int generic_file_rw_checks(struct file *file_in, struct file *file_out);
 extern ssize_t generic_file_buffered_read(struct kiocb *iocb,
 		struct iov_iter *to, ssize_t already_read);
@@ -3267,6 +3278,8 @@ static inline int kiocb_set_rw_flags(struct kiocb *ki, rwf_t flags)
 			return -EOPNOTSUPP;
 		kiocb_flags |= IOCB_NOIO;
 	}
+	if ((flags & RWF_ENCODED) && !(ki->ki_filp->f_mode & FMODE_ENCODED_IO))
+		return -EOPNOTSUPP;
 	kiocb_flags |= (__force int) (flags & RWF_SUPPORTED);
 	if (flags & RWF_SYNC)
 		kiocb_flags |= IOCB_DSYNC;
include/uapi/linux/encoded_io.h new file +30 -0
new file mode 100644
index 000000000000..cf741453dba4
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_LINUX_ENCODED_IO_H
+#define _UAPI_LINUX_ENCODED_IO_H
+
+#include <linux/types.h>
+
+#define ENCODED_IOV_COMPRESSION_NONE 0
+#define ENCODED_IOV_COMPRESSION_BTRFS_ZLIB 1
+#define ENCODED_IOV_COMPRESSION_BTRFS_ZSTD 2
+#define ENCODED_IOV_COMPRESSION_BTRFS_LZO_4K 3
+#define ENCODED_IOV_COMPRESSION_BTRFS_LZO_8K 4
+#define ENCODED_IOV_COMPRESSION_BTRFS_LZO_16K 5
+#define ENCODED_IOV_COMPRESSION_BTRFS_LZO_32K 6
+#define ENCODED_IOV_COMPRESSION_BTRFS_LZO_64K 7
+#define ENCODED_IOV_COMPRESSION_TYPES 8
+
+#define ENCODED_IOV_ENCRYPTION_NONE 0
+#define ENCODED_IOV_ENCRYPTION_TYPES 1
+
+struct encoded_iov {
+	__aligned_u64 len;
+	__aligned_u64 unencoded_len;
+	__aligned_u64 unencoded_offset;
+	__u32 compression;
+	__u32 encryption;
+};
+
+#define ENCODED_IOV_SIZE_VER0 32
+
+#endif /* _UAPI_LINUX_ENCODED_IO_H */
include/uapi/linux/fs.h +4 -1
index f44eb0a04afd..0800b01524e8 100644
@@ -300,8 +300,11 @@ typedef int __bitwise __kernel_rwf_t;
 /* per-IO O_APPEND */
 #define RWF_APPEND	((__force __kernel_rwf_t)0x00000010)
 
+/* encoded (e.g., compressed and/or encrypted) IO */
+#define RWF_ENCODED	((__force __kernel_rwf_t)0x00000020)
+
 /* mask of flags supported by the kernel */
 #define RWF_SUPPORTED	(RWF_HIPRI | RWF_DSYNC | RWF_SYNC | RWF_NOWAIT |\
-			 RWF_APPEND)
+			 RWF_APPEND | RWF_ENCODED)
 
 #endif /* _UAPI_LINUX_FS_H */
Signature · 1 line
2.30.2
#8 lore raw

PATCH v8 4/10 btrfs: fix check_data_csum() error message for direct I/O

In reply to Omar Sandoval

From: Omar Sandoval <[email protected]>

Commit 1dae796aabf6 ("btrfs: inode: sink parameter start and len to check_data_csum()") replaced the start parameter to check_data_csum() with page_offset(), but page_offset() is not meaningful for direct I/O pages. Bring back the start parameter.

Fixes 265d4ac03fdf ("btrfs: sink parameter start and len to check_data_csum")
---
 fs/btrfs/inode.c | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)
fs/btrfs/inode.c +9 -5
index ef6cb7b620d0..d2ece8554416 100644
@@ -2947,11 +2947,13 @@ void btrfs_writepage_endio_finish_ordered(struct page *page, u64 start,
  * @bio_offset:	offset to the beginning of the bio (in bytes)
  * @page:	page where is the data to be verified
  * @pgoff:	offset inside the page
+ * @start:	logical offset in the file
  *
  * The length of such check is always one sector size.
  */
 static int check_data_csum(struct inode *inode, struct btrfs_io_bio *io_bio,
-			   u32 bio_offset, struct page *page, u32 pgoff)
+			   u32 bio_offset, struct page *page, u32 pgoff,
+			   u64 start)
 {
 	struct btrfs_fs_info *fs_info = btrfs_sb(inode->i_sb);
 	SHASH_DESC_ON_STACK(shash, fs_info->csum_shash);
@@ -2978,8 +2980,8 @@ static int check_data_csum(struct inode *inode, struct btrfs_io_bio *io_bio,
 	kunmap_atomic(kaddr);
 	return 0;
 zeroit:
-	btrfs_print_data_csum_error(BTRFS_I(inode), page_offset(page) + pgoff,
-				    csum, csum_expected, io_bio->mirror_num);
+	btrfs_print_data_csum_error(BTRFS_I(inode), start, csum, csum_expected,
+				    io_bio->mirror_num);
 	if (io_bio->device)
 		btrfs_dev_stat_inc_and_print(io_bio->device,
 					     BTRFS_DEV_STAT_CORRUPTION_ERRS);
@@ -3032,7 +3034,8 @@ int btrfs_verify_data_csum(struct btrfs_io_bio *io_bio, u32 bio_offset,
 	     pg_off += sectorsize, bio_offset += sectorsize) {
 		int ret;
 
-		ret = check_data_csum(inode, io_bio, bio_offset, page, pg_off);
+		ret = check_data_csum(inode, io_bio, bio_offset, page, pg_off,
+				      page_offset(page) + pg_off);
 		if (ret < 0)
 			return -EIO;
 	}
@@ -7742,7 +7745,8 @@ static blk_status_t btrfs_check_read_dio_bio(struct inode *inode,
 			ASSERT(pgoff < PAGE_SIZE);
 			if (uptodate &&
 			    (!csum || !check_data_csum(inode, io_bio,
-					bio_offset, bvec.bv_page, pgoff))) {
+						       bio_offset, bvec.bv_page,
+						       pgoff, start))) {
 				clean_io_failure(fs_info, failure_tree, io_tree,
 						 start, bvec.bv_page,
 						 btrfs_ino(BTRFS_I(inode)),
Signature · 1 line
2.30.2
#9 lore raw

Re: [PATCH v8 04/10] btrfs: fix check_data_csum() error message for direct I/O

Quoted 6 lines · Omar Sandoval

From: Omar Sandoval <[email protected]>

Commit 1dae796aabf6 ("btrfs: inode: sink parameter start and len to check_data_csum()") replaced the start parameter to check_data_csum() with page_offset(), but page_offset() is not meaningful for direct I/O pages. Bring back the start parameter.

So direct IO pages doesn't have page::index set at all?

Any reproducer? I'd like to try to reproduce it first.

Quoted 12 lines
Fixes 265d4ac03fdf ("btrfs: sink parameter start and len to check_data_csum")
---
  fs/btrfs/inode.c | 14 +++++++++-----
  1 file changed, 9 insertions(+), 5 deletions(-)
fs/btrfs/inode.c +0 -0
index ef6cb7b620d0..d2ece8554416 100644

fs/btrfs/inode.c void btrfs_writepage_endio_finish_ordered(struct page *page, u64 start, · line 2950

Quoted 4 lines
fs/btrfs/inode.c +1 -0
@@ -2947,11 +2947,13 @@ void btrfs_writepage_endio_finish_ordered(struct page *page, u64 start,
   * @bio_offset:	offset to the beginning of the bio (in bytes)
   * @page:	page where is the data to be verified
   * @pgoff:	offset inside the page
+ * @start:	logical offset in the file

Please add some comment if only for direct IO we need that @start parameter.

Thanks,
Qu

Quoted 42 lines
fs/btrfs/inode.c +8 -5
   *
   * The length of such check is always one sector size.
   */
  static int check_data_csum(struct inode *inode, struct btrfs_io_bio *io_bio,
-			   u32 bio_offset, struct page *page, u32 pgoff)
+			   u32 bio_offset, struct page *page, u32 pgoff,
+			   u64 start)
  {
  	struct btrfs_fs_info *fs_info = btrfs_sb(inode->i_sb);
  	SHASH_DESC_ON_STACK(shash, fs_info->csum_shash);
@@ -2978,8 +2980,8 @@ static int check_data_csum(struct inode *inode, struct btrfs_io_bio *io_bio,
  	kunmap_atomic(kaddr);
  	return 0;
  zeroit:
-	btrfs_print_data_csum_error(BTRFS_I(inode), page_offset(page) + pgoff,
-				    csum, csum_expected, io_bio->mirror_num);
+	btrfs_print_data_csum_error(BTRFS_I(inode), start, csum, csum_expected,
+				    io_bio->mirror_num);
  	if (io_bio->device)
  		btrfs_dev_stat_inc_and_print(io_bio->device,
  					     BTRFS_DEV_STAT_CORRUPTION_ERRS);
@@ -3032,7 +3034,8 @@ int btrfs_verify_data_csum(struct btrfs_io_bio *io_bio, u32 bio_offset,
  	     pg_off += sectorsize, bio_offset += sectorsize) {
  		int ret;
 
-		ret = check_data_csum(inode, io_bio, bio_offset, page, pg_off);
+		ret = check_data_csum(inode, io_bio, bio_offset, page, pg_off,
+				      page_offset(page) + pg_off);
  		if (ret < 0)
  			return -EIO;
  	}
@@ -7742,7 +7745,8 @@ static blk_status_t btrfs_check_read_dio_bio(struct inode *inode,
  			ASSERT(pgoff < PAGE_SIZE);
  			if (uptodate &&
  			    (!csum || !check_data_csum(inode, io_bio,
-					bio_offset, bvec.bv_page, pgoff))) {
+						       bio_offset, bvec.bv_page,
+						       pgoff, start))) {
  				clean_io_failure(fs_info, failure_tree, io_tree,
  						 start, bvec.bv_page,
  						 btrfs_ino(BTRFS_I(inode)),
#10 lore raw

Re: [PATCH v8 04/10] btrfs: fix check_data_csum() error message for direct I/O

Quoted 11 lines · Qu Wenruo
Quoted 6 lines · Omar Sandoval

From: Omar Sandoval <[email protected]>

Commit 1dae796aabf6 ("btrfs: inode: sink parameter start and len to check_data_csum()") replaced the start parameter to check_data_csum() with page_offset(), but page_offset() is not meaningful for direct I/O pages. Bring back the start parameter.

So direct IO pages doesn't have page::index set at all?

No, they don't. Usually you do direct I/O into an anonymous page, but I suppose you could even do direct I/O into a page mmap'd from another file or filesystem. In either case, the index isn't meaningful for the file you're doing direct I/O from.

Quoted 1 line

Any reproducer? I'd like to try to reproduce it first.

The easiest way to see this issue is to apply this patch:

fs/btrfs/inode.c +3 -0
index 2a92211439e8..a962b3026573 100644
@@ -3114,6 +3114,9 @@ static int check_data_csum(struct inode *inode, struct btrfs_io_bio *io_bio,
 	u8 *csum_expected;
 	u8 csum[BTRFS_CSUM_SIZE];
 
+	WARN_ONCE(page_offset(page) + pgoff != start,
+		  "page offset %llu != start %llu\n",
+		  page_offset(page) + pgoff, start);
 	ASSERT(pgoff + len <= PAGE_SIZE);
 
 	offset_sectors = bio_offset >> fs_info->sectorsize_bits;

Run this simple test:

$ dd if=/dev/zero of=foo bs=4k count=1024
1024+0 records in
1024+0 records out
4194304 bytes (4.2 MB, 4.0 MiB) copied, 0.00456495 s, 919 MB/s
$ sync
$ dd if=foo of=/dev/null iflag=direct bs=4k
1024+0 records in
1024+0 records out
4194304 bytes (4.2 MB, 4.0 MiB) copied, 0.163079 s, 25.7 MB/s

And you'll get a warning like:

[   84.896486] ------------[ cut here ]------------
[   84.897370] page offset 94199157981184 != start 0
[   84.898128] WARNING: CPU: 1 PID: 459 at fs/btrfs/inode.c:3119 check_data_csum+0x189/0x260 [btrfs]
[   84.899547] Modules linked in: btrfs blake2b_generic xor pata_acpi ata_piix libata scsi_mod raid6_pq virtio_net net_failover virtio_rng libcrc32c rng_core failover
[   84.901742] CPU: 1 PID: 459 Comm: kworker/u56:2 Not tainted 5.12.0-rc3-00060-ge0cd3910d8cb-dirty #139
[   84.903205] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.14.0-0-g155821a1990b-prebuilt.qemu.org 04/01/2014
[   84.904875] Workqueue: btrfs-endio btrfs_work_helper [btrfs]
[   84.905749] RIP: 0010:check_data_csum+0x189/0x260 [btrfs]
[   84.906576] Code: 57 11 00 00 0f 85 03 ff ff ff 4c 89 ca 48 c7 c7 50 ba 35 c0 4c 89 44 24 10 48 89 44 24 08 c6 05 04 57 11 00 01 e8 22 e0 cf d4 <0f> 0b 4c 8b 44 24 10 48 8b 44 24 08 e9 d2 fe ff ff 41 8b 45 00 4d
[   84.909288] RSP: 0018:ffffb6e9c164bb98 EFLAGS: 00010282
[   84.910061] RAX: 0000000000000000 RBX: ffffe96b84a05f40 RCX: 0000000000000001
[   84.911109] RDX: 0000000080000001 RSI: ffffffff9573d067 RDI: 00000000ffffffff
[   84.912149] RBP: 0000000000000000 R08: 0000000000000000 R09: c0000000ffffdfff
[   84.913197] R10: 0000000000000001 R11: ffffb6e9c164b9c0 R12: 0000000000000000
[   84.914247] R13: ffff9d32a28c8dc0 R14: ffff9d32ac495e10 R15: 0000000000000004
[   84.915304] FS:  0000000000000000(0000) GS:ffff9d399f640000(0000) knlGS:0000000000000000
[   84.916478] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[   84.917340] CR2: 000055ad52f97120 CR3: 00000001292f4002 CR4: 0000000000370ee0
[   84.918435] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[   84.919473] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
[   84.920515] Call Trace:
[   84.920884]  ? find_busiest_group+0x41/0x380
[   84.921518]  ? load_balance+0x176/0xc10
[   84.922082]  ? kvm_sched_clock_read+0x5/0x10
[   84.922711]  ? sched_clock+0x5/0x10
[   84.923236]  btrfs_end_dio_bio+0x2fb/0x310 [btrfs]
[   84.923982]  end_workqueue_fn+0x29/0x40 [btrfs]
[   84.924698]  btrfs_work_helper+0xc1/0x350 [btrfs]
[   84.925435]  process_one_work+0x1c8/0x390
[   84.926025]  ? process_one_work+0x390/0x390
[   84.926650]  worker_thread+0x30/0x370
[   84.927209]  ? process_one_work+0x390/0x390
[   84.927875]  kthread+0x13d/0x160
[   84.928466]  ? kthread_park+0x80/0x80
[   84.929008]  ret_from_fork+0x22/0x30
[   84.929543] ---[ end trace 4f87c4a13fa476d4 ]---
Quoted 7 lines
Quoted 7 lines
Fixes 265d4ac03fdf ("btrfs: sink parameter start and len to check_data_csum")
---
  fs/btrfs/inode.c | 14 +++++++++-----
  1 file changed, 9 insertions(+), 5 deletions(-)
Quoted 11 lines

fs/btrfs/inode.c void btrfs_writepage_endio_finish_ordered(struct page *page, u64 start, · line 2950

Quoted 9 lines
fs/btrfs/inode.c +1 -0
index ef6cb7b620d0..d2ece8554416 100644
@@ -2947,11 +2947,13 @@ void btrfs_writepage_endio_finish_ordered(struct page *page, u64 start,
   * @bio_offset:	offset to the beginning of the bio (in bytes)
   * @page:	page where is the data to be verified
   * @pgoff:	offset inside the page
+ * @start:	logical offset in the file

Please add some comment if only for direct IO we need that @start parameter.

Won't that add more confusion? Someone might read that and assume that they don't need to pass start for a page cache page. In my opinion, having this change in the git log is enough.

Reviewed-by
#11 lore raw

Re: [PATCH v8 04/10] btrfs: fix check_data_csum() error message for direct I/O

Quoted 107 lines · Omar Sandoval
Quoted 11 lines · Qu Wenruo
Quoted 6 lines · Omar Sandoval

From: Omar Sandoval <[email protected]>

Commit 1dae796aabf6 ("btrfs: inode: sink parameter start and len to check_data_csum()") replaced the start parameter to check_data_csum() with page_offset(), but page_offset() is not meaningful for direct I/O pages. Bring back the start parameter.

So direct IO pages doesn't have page::index set at all?

No, they don't. Usually you do direct I/O into an anonymous page, but I suppose you could even do direct I/O into a page mmap'd from another file or filesystem. In either case, the index isn't meaningful for the file you're doing direct I/O from.

Quoted 1 line

Any reproducer? I'd like to try to reproduce it first.

The easiest way to see this issue is to apply this patch:

fs/btrfs/inode.c +3 -0
index 2a92211439e8..a962b3026573 100644
@@ -3114,6 +3114,9 @@ static int check_data_csum(struct inode *inode, struct btrfs_io_bio *io_bio,
  	u8 *csum_expected;
  	u8 csum[BTRFS_CSUM_SIZE];
 
+	WARN_ONCE(page_offset(page) + pgoff != start,
+		  "page offset %llu != start %llu\n",
+		  page_offset(page) + pgoff, start);
  	ASSERT(pgoff + len <= PAGE_SIZE);
 
  	offset_sectors = bio_offset >> fs_info->sectorsize_bits;

Run this simple test:

$ dd if=/dev/zero of=foo bs=4k count=1024
1024+0 records in
1024+0 records out
4194304 bytes (4.2 MB, 4.0 MiB) copied, 0.00456495 s, 919 MB/s
$ sync
$ dd if=foo of=/dev/null iflag=direct bs=4k
1024+0 records in
1024+0 records out
4194304 bytes (4.2 MB, 4.0 MiB) copied, 0.163079 s, 25.7 MB/s

And you'll get a warning like:

[   84.896486] ------------[ cut here ]------------
[   84.897370] page offset 94199157981184 != start 0
[   84.898128] WARNING: CPU: 1 PID: 459 at fs/btrfs/inode.c:3119 check_data_csum+0x189/0x260 [btrfs]
[   84.899547] Modules linked in: btrfs blake2b_generic xor pata_acpi ata_piix libata scsi_mod raid6_pq virtio_net net_failover virtio_rng libcrc32c rng_core failover
[   84.901742] CPU: 1 PID: 459 Comm: kworker/u56:2 Not tainted 5.12.0-rc3-00060-ge0cd3910d8cb-dirty #139
[   84.903205] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.14.0-0-g155821a1990b-prebuilt.qemu.org 04/01/2014
[   84.904875] Workqueue: btrfs-endio btrfs_work_helper [btrfs]
[   84.905749] RIP: 0010:check_data_csum+0x189/0x260 [btrfs]
[   84.906576] Code: 57 11 00 00 0f 85 03 ff ff ff 4c 89 ca 48 c7 c7 50 ba 35 c0 4c 89 44 24 10 48 89 44 24 08 c6 05 04 57 11 00 01 e8 22 e0 cf d4 <0f> 0b 4c 8b 44 24 10 48 8b 44 24 08 e9 d2 fe ff ff 41 8b 45 00 4d
[   84.909288] RSP: 0018:ffffb6e9c164bb98 EFLAGS: 00010282
[   84.910061] RAX: 0000000000000000 RBX: ffffe96b84a05f40 RCX: 0000000000000001
[   84.911109] RDX: 0000000080000001 RSI: ffffffff9573d067 RDI: 00000000ffffffff
[   84.912149] RBP: 0000000000000000 R08: 0000000000000000 R09: c0000000ffffdfff
[   84.913197] R10: 0000000000000001 R11: ffffb6e9c164b9c0 R12: 0000000000000000
[   84.914247] R13: ffff9d32a28c8dc0 R14: ffff9d32ac495e10 R15: 0000000000000004
[   84.915304] FS:  0000000000000000(0000) GS:ffff9d399f640000(0000) knlGS:0000000000000000
[   84.916478] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[   84.917340] CR2: 000055ad52f97120 CR3: 00000001292f4002 CR4: 0000000000370ee0
[   84.918435] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[   84.919473] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
[   84.920515] Call Trace:
[   84.920884]  ? find_busiest_group+0x41/0x380
[   84.921518]  ? load_balance+0x176/0xc10
[   84.922082]  ? kvm_sched_clock_read+0x5/0x10
[   84.922711]  ? sched_clock+0x5/0x10
[   84.923236]  btrfs_end_dio_bio+0x2fb/0x310 [btrfs]
[   84.923982]  end_workqueue_fn+0x29/0x40 [btrfs]
[   84.924698]  btrfs_work_helper+0xc1/0x350 [btrfs]
[   84.925435]  process_one_work+0x1c8/0x390
[   84.926025]  ? process_one_work+0x390/0x390
[   84.926650]  worker_thread+0x30/0x370
[   84.927209]  ? process_one_work+0x390/0x390
[   84.927875]  kthread+0x13d/0x160
[   84.928466]  ? kthread_park+0x80/0x80
[   84.929008]  ret_from_fork+0x22/0x30
[   84.929543] ---[ end trace 4f87c4a13fa476d4 ]---
Quoted 7 lines
Quoted 7 lines
Fixes 265d4ac03fdf ("btrfs: sink parameter start and len to check_data_csum")
---
   fs/btrfs/inode.c | 14 +++++++++-----
   1 file changed, 9 insertions(+), 5 deletions(-)
Quoted 11 lines

fs/btrfs/inode.c void btrfs_writepage_endio_finish_ordered(struct page *page, u64 start, · line 2950

Quoted 9 lines
fs/btrfs/inode.c +1 -0
index ef6cb7b620d0..d2ece8554416 100644
@@ -2947,11 +2947,13 @@ void btrfs_writepage_endio_finish_ordered(struct page *page, u64 start,
    * @bio_offset:	offset to the beginning of the bio (in bytes)
    * @page:	page where is the data to be verified
    * @pgoff:	offset inside the page
+ * @start:	logical offset in the file

Please add some comment if only for direct IO we need that @start parameter.

Quoted 4 lines

Won't that add more confusion? Someone might read that and assume that they don't need to pass start for a page cache page. In my opinion, having this change in the git log is enough.

That's fine.

Then this patch looks fine to me.

Thanks,
Qu

#12 lore raw

Re: [PATCH v8 04/10] btrfs: fix check_data_csum() error message for direct I/O

Quoted 114 lines · Qu Wenruo
Quoted 107 lines · Omar Sandoval
Quoted 11 lines · Qu Wenruo
Quoted 6 lines · Omar Sandoval

From: Omar Sandoval <[email protected]>

Commit 1dae796aabf6 ("btrfs: inode: sink parameter start and len to check_data_csum()") replaced the start parameter to check_data_csum() with page_offset(), but page_offset() is not meaningful for direct I/O pages. Bring back the start parameter.

So direct IO pages doesn't have page::index set at all?

No, they don't. Usually you do direct I/O into an anonymous page, but I suppose you could even do direct I/O into a page mmap'd from another file or filesystem. In either case, the index isn't meaningful for the file you're doing direct I/O from.

Quoted 1 line

Any reproducer? I'd like to try to reproduce it first.

The easiest way to see this issue is to apply this patch:

fs/btrfs/inode.c +3 -0
index 2a92211439e8..a962b3026573 100644
@@ -3114,6 +3114,9 @@ static int check_data_csum(struct inode *inode, struct btrfs_io_bio *io_bio,
  	u8 *csum_expected;
  	u8 csum[BTRFS_CSUM_SIZE];
 
+	WARN_ONCE(page_offset(page) + pgoff != start,
+		  "page offset %llu != start %llu\n",
+		  page_offset(page) + pgoff, start);
  	ASSERT(pgoff + len <= PAGE_SIZE);
 
  	offset_sectors = bio_offset >> fs_info->sectorsize_bits;

Run this simple test:

$ dd if=/dev/zero of=foo bs=4k count=1024
1024+0 records in
1024+0 records out
4194304 bytes (4.2 MB, 4.0 MiB) copied, 0.00456495 s, 919 MB/s
$ sync
$ dd if=foo of=/dev/null iflag=direct bs=4k
1024+0 records in
1024+0 records out
4194304 bytes (4.2 MB, 4.0 MiB) copied, 0.163079 s, 25.7 MB/s

And you'll get a warning like:

[   84.896486] ------------[ cut here ]------------
[   84.897370] page offset 94199157981184 != start 0
[   84.898128] WARNING: CPU: 1 PID: 459 at fs/btrfs/inode.c:3119 check_data_csum+0x189/0x260 [btrfs]
[   84.899547] Modules linked in: btrfs blake2b_generic xor pata_acpi ata_piix libata scsi_mod raid6_pq virtio_net net_failover virtio_rng libcrc32c rng_core failover
[   84.901742] CPU: 1 PID: 459 Comm: kworker/u56:2 Not tainted 5.12.0-rc3-00060-ge0cd3910d8cb-dirty #139
[   84.903205] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.14.0-0-g155821a1990b-prebuilt.qemu.org 04/01/2014
[   84.904875] Workqueue: btrfs-endio btrfs_work_helper [btrfs]
[   84.905749] RIP: 0010:check_data_csum+0x189/0x260 [btrfs]
[   84.906576] Code: 57 11 00 00 0f 85 03 ff ff ff 4c 89 ca 48 c7 c7 50 ba 35 c0 4c 89 44 24 10 48 89 44 24 08 c6 05 04 57 11 00 01 e8 22 e0 cf d4 <0f> 0b 4c 8b 44 24 10 48 8b 44 24 08 e9 d2 fe ff ff 41 8b 45 00 4d
[   84.909288] RSP: 0018:ffffb6e9c164bb98 EFLAGS: 00010282
[   84.910061] RAX: 0000000000000000 RBX: ffffe96b84a05f40 RCX: 0000000000000001
[   84.911109] RDX: 0000000080000001 RSI: ffffffff9573d067 RDI: 00000000ffffffff
[   84.912149] RBP: 0000000000000000 R08: 0000000000000000 R09: c0000000ffffdfff
[   84.913197] R10: 0000000000000001 R11: ffffb6e9c164b9c0 R12: 0000000000000000
[   84.914247] R13: ffff9d32a28c8dc0 R14: ffff9d32ac495e10 R15: 0000000000000004
[   84.915304] FS:  0000000000000000(0000) GS:ffff9d399f640000(0000) knlGS:0000000000000000
[   84.916478] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[   84.917340] CR2: 000055ad52f97120 CR3: 00000001292f4002 CR4: 0000000000370ee0
[   84.918435] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[   84.919473] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
[   84.920515] Call Trace:
[   84.920884]  ? find_busiest_group+0x41/0x380
[   84.921518]  ? load_balance+0x176/0xc10
[   84.922082]  ? kvm_sched_clock_read+0x5/0x10
[   84.922711]  ? sched_clock+0x5/0x10
[   84.923236]  btrfs_end_dio_bio+0x2fb/0x310 [btrfs]
[   84.923982]  end_workqueue_fn+0x29/0x40 [btrfs]
[   84.924698]  btrfs_work_helper+0xc1/0x350 [btrfs]
[   84.925435]  process_one_work+0x1c8/0x390
[   84.926025]  ? process_one_work+0x390/0x390
[   84.926650]  worker_thread+0x30/0x370
[   84.927209]  ? process_one_work+0x390/0x390
[   84.927875]  kthread+0x13d/0x160
[   84.928466]  ? kthread_park+0x80/0x80
[   84.929008]  ret_from_fork+0x22/0x30
[   84.929543] ---[ end trace 4f87c4a13fa476d4 ]---
Quoted 7 lines
Quoted 7 lines
Fixes 265d4ac03fdf ("btrfs: sink parameter start and len to check_data_csum")
---
   fs/btrfs/inode.c | 14 +++++++++-----
   1 file changed, 9 insertions(+), 5 deletions(-)
Quoted 11 lines

fs/btrfs/inode.c void btrfs_writepage_endio_finish_ordered(struct page *page, u64 start, · line 2950

Quoted 9 lines
fs/btrfs/inode.c +1 -0
index ef6cb7b620d0..d2ece8554416 100644
@@ -2947,11 +2947,13 @@ void btrfs_writepage_endio_finish_ordered(struct page *page, u64 start,
    * @bio_offset:	offset to the beginning of the bio (in bytes)
    * @page:	page where is the data to be verified
    * @pgoff:	offset inside the page
+ * @start:	logical offset in the file

Please add some comment if only for direct IO we need that @start parameter.

Quoted 4 lines

Won't that add more confusion? Someone might read that and assume that they don't need to pass start for a page cache page. In my opinion, having this change in the git log is enough.

Quoted 5 lines

That's fine.

Then this patch looks fine to me.

Added to misc-next, thanks.

#13 lore raw

PATCH v8 5/10 btrfs: don't advance offset for compressed bios in btrfs_csum_one_bio()

In reply to Omar Sandoval

From: Omar Sandoval <[email protected]>

btrfs_csum_one_bio() loops over each filesystem block in the bio while keeping a cursor of its current logical position in the file in order to look up the ordered extent to add the checksums to. However, this doesn't make much sense for compressed extents, as a sector on disk does not correspond to a sector of decompressed file data. It happens to work because 1) the compressed bio always covers one ordered extent and 2) the size of the bio is always less than the size of the ordered extent. However, the second point will not always be true for encoded writes.

Let's add a boolean parameter to btrfs_csum_one_bio() to indicate that it can assume that the bio only covers one ordered extent. Since we're already changing the signature, let's get rid of the contig parameter and make it implied by the offset parameter, similar to the change we recently made to btrfs_lookup_bio_sums(). Additionally, let's rename nr_sectors to blockcount to make it clear that it's the number of filesystem blocks, not the number of 512-byte sectors.

---
 fs/btrfs/compression.c |  5 +++--
 fs/btrfs/ctree.h       |  2 +-
 fs/btrfs/file-item.c   | 35 ++++++++++++++++-------------------
 fs/btrfs/inode.c       |  8 ++++----
 4 files changed, 24 insertions(+), 26 deletions(-)
fs/btrfs/compression.c +3 -2
index 5ae3fa0386b7..d9d1d9e38fc5 100644
@@ -436,7 +436,8 @@ blk_status_t btrfs_submit_compressed_write(struct btrfs_inode *inode, u64 start,
 			BUG_ON(ret); /* -ENOMEM */
 
 			if (!skip_sum) {
-				ret = btrfs_csum_one_bio(inode, bio, start, 1);
+				ret = btrfs_csum_one_bio(inode, bio, start,
+							 true);
 				BUG_ON(ret); /* -ENOMEM */
 			}
 
@@ -468,7 +469,7 @@ blk_status_t btrfs_submit_compressed_write(struct btrfs_inode *inode, u64 start,
 	BUG_ON(ret); /* -ENOMEM */
 
 	if (!skip_sum) {
-		ret = btrfs_csum_one_bio(inode, bio, start, 1);
+		ret = btrfs_csum_one_bio(inode, bio, start, true);
 		BUG_ON(ret); /* -ENOMEM */
 	}
 
fs/btrfs/ctree.h +1 -1
index 1bd416e5a731..f1b9a9e42cc6 100644
@@ -3051,7 +3051,7 @@ int btrfs_csum_file_blocks(struct btrfs_trans_handle *trans,
 			   struct btrfs_root *root,
 			   struct btrfs_ordered_sum *sums);
 blk_status_t btrfs_csum_one_bio(struct btrfs_inode *inode, struct bio *bio,
-				u64 file_start, int contig);
+				u64 offset, bool one_ordered);
 int btrfs_lookup_csums_range(struct btrfs_root *root, u64 start, u64 end,
 			     struct list_head *list, int search_commit);
 void btrfs_extent_item_to_extent_map(struct btrfs_inode *inode,
fs/btrfs/file-item.c +16 -19
index 6ccfc019ad90..82d110c8dc03 100644
@@ -608,28 +608,28 @@ int btrfs_lookup_csums_range(struct btrfs_root *root, u64 start, u64 end,
  * btrfs_csum_one_bio - Calculates checksums of the data contained inside a bio
  * @inode:	 Owner of the data inside the bio
  * @bio:	 Contains the data to be checksummed
- * @file_start:  offset in file this bio begins to describe
- * @contig:	 Boolean. If true/1 means all bio vecs in this bio are
- *		 contiguous and they begin at @file_start in the file. False/0
- *		 means this bio can contains potentially discontigous bio vecs
- *		 so the logical offset of each should be calculated separately.
+ * @offset:      If (u64)-1, @bio may contain discontiguous bio vecs, so the
+ *               file offsets are determined from the page offsets in the bio.
+ *               Otherwise, this is the starting file offset of the bio vecs in
+ *               @bio, which must be contiguous.
+ * @one_ordered: If true, @bio only refers to one ordered extent.
  */
 blk_status_t btrfs_csum_one_bio(struct btrfs_inode *inode, struct bio *bio,
-		       u64 file_start, int contig)
+				u64 offset, bool one_ordered)
 {
 	struct btrfs_fs_info *fs_info = inode->root->fs_info;
 	SHASH_DESC_ON_STACK(shash, fs_info->csum_shash);
 	struct btrfs_ordered_sum *sums;
 	struct btrfs_ordered_extent *ordered = NULL;
+	const bool page_offsets = (offset == (u64)-1);
 	char *data;
 	struct bvec_iter iter;
 	struct bio_vec bvec;
 	int index;
-	int nr_sectors;
+	int blockcount;
 	unsigned long total_bytes = 0;
 	unsigned long this_sum_bytes = 0;
 	int i;
-	u64 offset;
 	unsigned nofs_flag;
 
 	nofs_flag = memalloc_nofs_save();
@@ -643,18 +643,13 @@ blk_status_t btrfs_csum_one_bio(struct btrfs_inode *inode, struct bio *bio,
 	sums->len = bio->bi_iter.bi_size;
 	INIT_LIST_HEAD(&sums->list);
 
-	if (contig)
-		offset = file_start;
-	else
-		offset = 0; /* shut up gcc */
-
 	sums->bytenr = bio->bi_iter.bi_sector << 9;
 	index = 0;
 
 	shash->tfm = fs_info->csum_shash;
 
 	bio_for_each_segment(bvec, bio, iter) {
-		if (!contig)
+		if (page_offsets)
 			offset = page_offset(bvec.bv_page) + bvec.bv_offset;
 
 		if (!ordered) {
@@ -662,13 +657,14 @@ blk_status_t btrfs_csum_one_bio(struct btrfs_inode *inode, struct bio *bio,
 			BUG_ON(!ordered); /* Logic error */
 		}
 
-		nr_sectors = BTRFS_BYTES_TO_BLKS(fs_info,
+		blockcount = BTRFS_BYTES_TO_BLKS(fs_info,
 						 bvec.bv_len + fs_info->sectorsize
 						 - 1);
 
-		for (i = 0; i < nr_sectors; i++) {
-			if (offset >= ordered->file_offset + ordered->num_bytes ||
-			    offset < ordered->file_offset) {
+		for (i = 0; i < blockcount; i++) {
+			if (!one_ordered &&
+			    (offset >= ordered->file_offset + ordered->num_bytes ||
+			     offset < ordered->file_offset)) {
 				unsigned long bytes_left;
 
 				sums->len = this_sum_bytes;
@@ -699,7 +695,8 @@ blk_status_t btrfs_csum_one_bio(struct btrfs_inode *inode, struct bio *bio,
 					    sums->sums + index);
 			kunmap_atomic(data);
 			index += fs_info->csum_size;
-			offset += fs_info->sectorsize;
+			if (!one_ordered)
+				offset += fs_info->sectorsize;
 			this_sum_bytes += fs_info->sectorsize;
 			total_bytes += fs_info->sectorsize;
 		}
fs/btrfs/inode.c +4 -4
index d2ece8554416..b92c4a1d6d2f 100644
@@ -2214,7 +2214,7 @@ int btrfs_bio_fits_in_stripe(struct page *page, size_t size, struct bio *bio,
 static blk_status_t btrfs_submit_bio_start(struct inode *inode, struct bio *bio,
 					   u64 dio_file_offset)
 {
-	return btrfs_csum_one_bio(BTRFS_I(inode), bio, 0, 0);
+	return btrfs_csum_one_bio(BTRFS_I(inode), bio, (u64)-1, false);
 }
 
 /*
@@ -2282,7 +2282,7 @@ blk_status_t btrfs_submit_data_bio(struct inode *inode, struct bio *bio,
 					  0, btrfs_submit_bio_start);
 		goto out;
 	} else if (!skip_sum) {
-		ret = btrfs_csum_one_bio(BTRFS_I(inode), bio, 0, 0);
+		ret = btrfs_csum_one_bio(BTRFS_I(inode), bio, (u64)-1, false);
 		if (ret)
 			goto out;
 	}
@@ -7820,7 +7820,7 @@ static blk_status_t btrfs_submit_bio_start_direct_io(struct inode *inode,
 						     struct bio *bio,
 						     u64 dio_file_offset)
 {
-	return btrfs_csum_one_bio(BTRFS_I(inode), bio, dio_file_offset, 1);
+	return btrfs_csum_one_bio(BTRFS_I(inode), bio, dio_file_offset, false);
 }
 
 static void btrfs_end_dio_bio(struct bio *bio)
@@ -7877,7 +7877,7 @@ static inline blk_status_t btrfs_submit_dio_bio(struct bio *bio,
 		 * If we aren't doing async submit, calculate the csum of the
 		 * bio now.
 		 */
-		ret = btrfs_csum_one_bio(BTRFS_I(inode), bio, file_offset, 1);
+		ret = btrfs_csum_one_bio(BTRFS_I(inode), bio, file_offset, false);
 		if (ret)
 			goto err;
 	} else {
Signature · 1 line
2.30.2
#14 lore raw

PATCH v8 6/10 btrfs: add ram_bytes and offset to btrfs_ordered_extent

In reply to Omar Sandoval

From: Omar Sandoval <[email protected]>

Currently, we only create ordered extents when ram_bytes == num_bytes and offset == 0. However, RWF_ENCODED writes may create extents which only refer to a subset of the full unencoded extent, so we need to plumb these fields through the ordered extent infrastructure and pass them down to insert_reserved_file_extent().

Since we're changing the btrfs_add_ordered_extent* signature, let's get rid of the trivial wrappers and add a kernel-doc.

---
 fs/btrfs/inode.c        | 56 ++++++++++++++++++---------------
 fs/btrfs/ordered-data.c | 68 ++++++++++++++++-------------------------
 fs/btrfs/ordered-data.h | 16 ++++------
 3 files changed, 64 insertions(+), 76 deletions(-)
fs/btrfs/inode.c +32 -24
index b92c4a1d6d2f..139d690f171e 100644
@@ -912,13 +912,12 @@ static noinline void submit_compressed_extents(struct async_chunk *async_chunk)
 			goto out_free_reserve;
 		free_extent_map(em);
 
-		ret = btrfs_add_ordered_extent_compress(inode,
-						async_extent->start,
-						ins.objectid,
-						async_extent->ram_size,
-						ins.offset,
-						BTRFS_ORDERED_COMPRESSED,
-						async_extent->compress_type);
+		ret = btrfs_add_ordered_extent(inode, async_extent->start,
+					       async_extent->ram_size,
+					       async_extent->ram_size,
+					       ins.objectid, ins.offset, 0,
+					       1 << BTRFS_ORDERED_COMPRESSED,
+					       async_extent->compress_type);
 		if (ret) {
 			btrfs_drop_extent_cache(inode, async_extent->start,
 						async_extent->start +
@@ -1126,8 +1125,9 @@ static noinline int cow_file_range(struct btrfs_inode *inode,
 		}
 		free_extent_map(em);
 
-		ret = btrfs_add_ordered_extent(inode, start, ins.objectid,
-					       ram_size, cur_alloc_size, 0);
+		ret = btrfs_add_ordered_extent(inode, start, ram_size, ram_size,
+					       ins.objectid, cur_alloc_size, 0,
+					       0, BTRFS_COMPRESS_NONE);
 		if (ret)
 			goto out_drop_extent_cache;
 
@@ -1768,10 +1768,11 @@ static noinline int run_delalloc_nocow(struct btrfs_inode *inode,
 				goto error;
 			}
 			free_extent_map(em);
-			ret = btrfs_add_ordered_extent(inode, cur_offset,
-						       disk_bytenr, num_bytes,
-						       num_bytes,
-						       BTRFS_ORDERED_PREALLOC);
+			ret = btrfs_add_ordered_extent(inode,
+					cur_offset, num_bytes, num_bytes,
+					disk_bytenr, num_bytes, 0,
+					1 << BTRFS_ORDERED_PREALLOC,
+					BTRFS_COMPRESS_NONE);
 			if (ret) {
 				btrfs_drop_extent_cache(inode, cur_offset,
 							cur_offset + num_bytes - 1,
@@ -1780,9 +1781,11 @@ static noinline int run_delalloc_nocow(struct btrfs_inode *inode,
 			}
 		} else {
 			ret = btrfs_add_ordered_extent(inode, cur_offset,
+						       num_bytes, num_bytes,
 						       disk_bytenr, num_bytes,
-						       num_bytes,
-						       BTRFS_ORDERED_NOCOW);
+						       0,
+						       1 << BTRFS_ORDERED_NOCOW,
+						       BTRFS_COMPRESS_NONE);
 			if (ret)
 				goto error;
 		}
@@ -2586,6 +2589,7 @@ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans,
 	struct btrfs_key ins;
 	u64 disk_num_bytes = btrfs_stack_file_extent_disk_num_bytes(stack_fi);
 	u64 disk_bytenr = btrfs_stack_file_extent_disk_bytenr(stack_fi);
+	u64 offset = btrfs_stack_file_extent_offset(stack_fi);
 	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 };
@@ -2660,7 +2664,8 @@ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans,
 		goto out;
 
 	ret = btrfs_alloc_reserved_file_extent(trans, root, btrfs_ino(inode),
-					       file_pos, qgroup_reserved, &ins);
+					       file_pos - offset,
+					       qgroup_reserved, &ins);
 out:
 	btrfs_free_path(path);
 
@@ -2686,20 +2691,20 @@ static int insert_ordered_extent_file_extent(struct btrfs_trans_handle *trans,
 					     struct btrfs_ordered_extent *oe)
 {
 	struct btrfs_file_extent_item stack_fi;
-	u64 logical_len;
 	bool update_inode_bytes;
+	u64 num_bytes = oe->num_bytes;
+	u64 ram_bytes = oe->ram_bytes;
 
 	memset(&stack_fi, 0, sizeof(stack_fi));
 	btrfs_set_stack_file_extent_type(&stack_fi, BTRFS_FILE_EXTENT_REG);
 	btrfs_set_stack_file_extent_disk_bytenr(&stack_fi, oe->disk_bytenr);
 	btrfs_set_stack_file_extent_disk_num_bytes(&stack_fi,
 						   oe->disk_num_bytes);
+	btrfs_set_stack_file_extent_offset(&stack_fi, oe->offset);
 	if (test_bit(BTRFS_ORDERED_TRUNCATED, &oe->flags))
-		logical_len = oe->truncated_len;
-	else
-		logical_len = oe->num_bytes;
-	btrfs_set_stack_file_extent_num_bytes(&stack_fi, logical_len);
-	btrfs_set_stack_file_extent_ram_bytes(&stack_fi, logical_len);
+		num_bytes = ram_bytes = oe->truncated_len;
+	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 */
 
@@ -7053,8 +7058,11 @@ static struct extent_map *btrfs_create_dio_extent(struct btrfs_inode *inode,
 		if (IS_ERR(em))
 			goto out;
 	}
-	ret = btrfs_add_ordered_extent_dio(inode, start, block_start, len,
-					   block_len, type);
+	ret = btrfs_add_ordered_extent(inode, start, len, len, block_start,
+				       block_len, 0,
+				       (1 << type) |
+				       (1 << BTRFS_ORDERED_DIRECT),
+				       BTRFS_COMPRESS_NONE);
 	if (ret) {
 		if (em) {
 			free_extent_map(em);
fs/btrfs/ordered-data.c +26 -42

104 lines not on this page · raw message

fs/btrfs/ordered-data.h +6 -10
index 46194c2c05d4..8c8d6ed7a350 100644
@@ -72,9 +72,11 @@ struct btrfs_ordered_extent {
 	 * These fields directly correspond to the same fields in
 	 * btrfs_file_extent_item.
 	 */
-	u64 disk_bytenr;
 	u64 num_bytes;
+	u64 ram_bytes;
+	u64 disk_bytenr;
 	u64 disk_num_bytes;
+	u64 offset;
 
 	/* number of bytes that still need writing */
 	u64 bytes_left;
@@ -160,15 +162,9 @@ bool btrfs_dec_test_first_ordered_pending(struct btrfs_inode *inode,
 				   u64 *file_offset, u64 io_size,
 				   int uptodate);
 int btrfs_add_ordered_extent(struct btrfs_inode *inode, u64 file_offset,
-			     u64 disk_bytenr, u64 num_bytes, u64 disk_num_bytes,
-			     int type);
-int btrfs_add_ordered_extent_dio(struct btrfs_inode *inode, u64 file_offset,
-				 u64 disk_bytenr, u64 num_bytes,
-				 u64 disk_num_bytes, int type);
-int btrfs_add_ordered_extent_compress(struct btrfs_inode *inode, u64 file_offset,
-				      u64 disk_bytenr, u64 num_bytes,
-				      u64 disk_num_bytes, int type,
-				      int compress_type);
+			     u64 num_bytes, u64 ram_bytes, u64 disk_bytenr,
+			     u64 disk_num_bytes, u64 offset, int flags,
+			     int compress_type);
 void btrfs_add_ordered_sum(struct btrfs_ordered_extent *entry,
 			   struct btrfs_ordered_sum *sum);
 struct btrfs_ordered_extent *btrfs_lookup_ordered_extent(struct btrfs_inode *inode,
Signature · 1 line
2.30.2
#15 lore raw

PATCH v8 7/10 btrfs: support different disk extent size for delalloc

In reply to Omar Sandoval

From: Omar Sandoval <[email protected]>

Currently, we always reserve the same extent size in the file and extent size on disk for delalloc because the former is the worst case for the latter. For RWF_ENCODED writes, we know the exact size of the extent on disk, which may be less than or greater than (for bookends) the size in the file. Add a disk_num_bytes parameter to btrfs_delalloc_reserve_metadata() so that we can reserve the correct amount of csum bytes. No functional change.

---
 fs/btrfs/ctree.h          |  3 ++-
 fs/btrfs/delalloc-space.c | 18 ++++++++++--------
 fs/btrfs/file.c           |  3 ++-
 fs/btrfs/inode.c          |  2 +-
 fs/btrfs/relocation.c     |  4 ++--
 5 files changed, 17 insertions(+), 13 deletions(-)
fs/btrfs/ctree.h +2 -1
index f1b9a9e42cc6..3a0c4fb4c657 100644
@@ -2746,7 +2746,8 @@ void btrfs_subvolume_release_metadata(struct btrfs_root *root,
 				      struct btrfs_block_rsv *rsv);
 void btrfs_delalloc_release_extents(struct btrfs_inode *inode, u64 num_bytes);
 
-int btrfs_delalloc_reserve_metadata(struct btrfs_inode *inode, u64 num_bytes);
+int btrfs_delalloc_reserve_metadata(struct btrfs_inode *inode, u64 num_bytes,
+				    u64 disk_num_bytes);
 u64 btrfs_account_ro_block_groups_free_space(struct btrfs_space_info *sinfo);
 int btrfs_error_unpin_extent_range(struct btrfs_fs_info *fs_info,
 				   u64 start, u64 end);
fs/btrfs/delalloc-space.c +10 -8
index bacee09b7bfd..948b78f03f63 100644
@@ -265,11 +265,11 @@ static void btrfs_calculate_inode_block_rsv_size(struct btrfs_fs_info *fs_info,
 }
 
 static void calc_inode_reservations(struct btrfs_fs_info *fs_info,
-				    u64 num_bytes, u64 *meta_reserve,
-				    u64 *qgroup_reserve)
+				    u64 num_bytes, u64 disk_num_bytes,
+				    u64 *meta_reserve, u64 *qgroup_reserve)
 {
 	u64 nr_extents = count_max_extents(num_bytes);
-	u64 csum_leaves = btrfs_csum_bytes_to_leaves(fs_info, num_bytes);
+	u64 csum_leaves = btrfs_csum_bytes_to_leaves(fs_info, disk_num_bytes);
 	u64 inode_update = btrfs_calc_metadata_size(fs_info, 1);
 
 	*meta_reserve = btrfs_calc_insert_metadata_size(fs_info,
@@ -283,7 +283,8 @@ static void calc_inode_reservations(struct btrfs_fs_info *fs_info,
 	*qgroup_reserve = nr_extents * fs_info->nodesize;
 }
 
-int btrfs_delalloc_reserve_metadata(struct btrfs_inode *inode, u64 num_bytes)
+int btrfs_delalloc_reserve_metadata(struct btrfs_inode *inode, u64 num_bytes,
+				    u64 disk_num_bytes)
 {
 	struct btrfs_root *root = inode->root;
 	struct btrfs_fs_info *fs_info = root->fs_info;
@@ -313,6 +314,7 @@ int btrfs_delalloc_reserve_metadata(struct btrfs_inode *inode, u64 num_bytes)
 	}
 
 	num_bytes = ALIGN(num_bytes, fs_info->sectorsize);
+	disk_num_bytes = ALIGN(disk_num_bytes, fs_info->sectorsize);
 
 	/*
 	 * We always want to do it this way, every other way is wrong and ends
@@ -324,8 +326,8 @@ int btrfs_delalloc_reserve_metadata(struct btrfs_inode *inode, u64 num_bytes)
 	 * everything out and try again, which is bad.  This way we just
 	 * over-reserve slightly, and clean up the mess when we are done.
 	 */
-	calc_inode_reservations(fs_info, num_bytes, &meta_reserve,
-				&qgroup_reserve);
+	calc_inode_reservations(fs_info, num_bytes, disk_num_bytes,
+				&meta_reserve, &qgroup_reserve);
 	ret = btrfs_qgroup_reserve_meta_prealloc(root, qgroup_reserve, true);
 	if (ret)
 		return ret;
@@ -344,7 +346,7 @@ int btrfs_delalloc_reserve_metadata(struct btrfs_inode *inode, u64 num_bytes)
 	spin_lock(&inode->lock);
 	nr_extents = count_max_extents(num_bytes);
 	btrfs_mod_outstanding_extents(inode, nr_extents);
-	inode->csum_bytes += num_bytes;
+	inode->csum_bytes += disk_num_bytes;
 	btrfs_calculate_inode_block_rsv_size(fs_info, inode);
 	spin_unlock(&inode->lock);
 
@@ -448,7 +450,7 @@ int btrfs_delalloc_reserve_space(struct btrfs_inode *inode,
 	ret = btrfs_check_data_free_space(inode, reserved, start, len);
 	if (ret < 0)
 		return ret;
-	ret = btrfs_delalloc_reserve_metadata(inode, len);
+	ret = btrfs_delalloc_reserve_metadata(inode, len, len);
 	if (ret < 0)
 		btrfs_free_reserved_data_space(inode, *reserved, start, len);
 	return ret;
fs/btrfs/file.c +2 -1
index d81ae1f518f2..32578f31cf43 100644
@@ -1733,7 +1733,8 @@ static noinline ssize_t btrfs_buffered_write(struct kiocb *iocb,
 					 fs_info->sectorsize);
 		WARN_ON(reserve_bytes == 0);
 		ret = btrfs_delalloc_reserve_metadata(BTRFS_I(inode),
-				reserve_bytes);
+						      reserve_bytes,
+						      reserve_bytes);
 		if (ret) {
 			if (!only_release_metadata)
 				btrfs_free_reserved_data_space(BTRFS_I(inode),
fs/btrfs/inode.c +1 -1
index 139d690f171e..3c1f1297c9d9 100644
@@ -4712,7 +4712,7 @@ int btrfs_truncate_block(struct btrfs_inode *inode, loff_t from, loff_t len,
 			goto out;
 		}
 	}
-	ret = btrfs_delalloc_reserve_metadata(inode, blocksize);
+	ret = btrfs_delalloc_reserve_metadata(inode, blocksize, blocksize);
 	if (ret < 0) {
 		if (!only_release_metadata)
 			btrfs_free_reserved_data_space(inode, data_reserved,
fs/btrfs/relocation.c +2 -2
index 9f2289bcdde6..85e9e7287bfd 100644
@@ -2660,8 +2660,8 @@ static int relocate_file_extent_cluster(struct inode *inode,
 	index = (cluster->start - offset) >> PAGE_SHIFT;
 	last_index = (cluster->end - offset) >> PAGE_SHIFT;
 	while (index <= last_index) {
-		ret = btrfs_delalloc_reserve_metadata(BTRFS_I(inode),
-				PAGE_SIZE);
+		ret = btrfs_delalloc_reserve_metadata(BTRFS_I(inode), PAGE_SIZE,
+						      PAGE_SIZE);
 		if (ret)
 			goto out;
 
Signature · 1 line
2.30.2
#16 lore raw

PATCH v8 8/10 btrfs: optionally extend i_size in cow_file_range_inline()

In reply to Omar Sandoval

From: Omar Sandoval <[email protected]>

Currently, an inline extent is always created after i_size is extended from btrfs_dirty_pages(). However, for encoded writes, we only want to update i_size after we successfully created the inline extent. Add an update_i_size parameter to cow_file_range_inline() and insert_inline_extent() and pass in the size of the extent rather than determining it from i_size. Since the start parameter is always passed as 0, get rid of it and simplify the logic in these two functions. While we're here, let's document the requirements for creating an inline extent.

---
 fs/btrfs/inode.c | 100 +++++++++++++++++++++++------------------------
 1 file changed, 48 insertions(+), 52 deletions(-)
fs/btrfs/inode.c +48 -52
index 3c1f1297c9d9..b362e73af840 100644
@@ -203,9 +203,10 @@ static int btrfs_init_inode_security(struct btrfs_trans_handle *trans,
 static int insert_inline_extent(struct btrfs_trans_handle *trans,
 				struct btrfs_path *path, bool extent_inserted,
 				struct btrfs_root *root, struct inode *inode,
-				u64 start, size_t size, size_t compressed_size,
+				size_t size, size_t compressed_size,
 				int compress_type,
-				struct page **compressed_pages)
+				struct page **compressed_pages,
+				bool update_i_size)
 {
 	struct extent_buffer *leaf;
 	struct page *page = NULL;
@@ -214,7 +215,7 @@ static int insert_inline_extent(struct btrfs_trans_handle *trans,
 	struct btrfs_file_extent_item *ei;
 	int ret;
 	size_t cur_size = size;
-	unsigned long offset;
+	u64 i_size;
 
 	ASSERT((compressed_size > 0 && compressed_pages) ||
 	       (compressed_size == 0 && !compressed_pages));
@@ -227,7 +228,7 @@ static int insert_inline_extent(struct btrfs_trans_handle *trans,
 		size_t datasize;
 
 		key.objectid = btrfs_ino(BTRFS_I(inode));
-		key.offset = start;
+		key.offset = 0;
 		key.type = BTRFS_EXTENT_DATA_KEY;
 
 		datasize = btrfs_file_extent_calc_inline_size(cur_size);
@@ -265,12 +266,10 @@ static int insert_inline_extent(struct btrfs_trans_handle *trans,
 		btrfs_set_file_extent_compression(leaf, ei,
 						  compress_type);
 	} else {
-		page = find_get_page(inode->i_mapping,
-				     start >> PAGE_SHIFT);
+		page = find_get_page(inode->i_mapping, 0);
 		btrfs_set_file_extent_compression(leaf, ei, 0);
 		kaddr = kmap_atomic(page);
-		offset = offset_in_page(start);
-		write_extent_buffer(leaf, kaddr + offset, ptr, size);
+		write_extent_buffer(leaf, kaddr, ptr, size);
 		kunmap_atomic(kaddr);
 		put_page(page);
 	}
@@ -281,8 +280,8 @@ static int insert_inline_extent(struct btrfs_trans_handle *trans,
 	 * We align size to sectorsize for inline extents just for simplicity
 	 * sake.
 	 */
-	size = ALIGN(size, root->fs_info->sectorsize);
-	ret = btrfs_inode_set_file_extent_range(BTRFS_I(inode), start, size);
+	ret = btrfs_inode_set_file_extent_range(BTRFS_I(inode), 0,
+					ALIGN(size, root->fs_info->sectorsize));
 	if (ret)
 		goto fail;
 
@@ -295,7 +294,13 @@ static int insert_inline_extent(struct btrfs_trans_handle *trans,
 	 * before we unlock the pages.  Otherwise we
 	 * could end up racing with unlink.
 	 */
-	BTRFS_I(inode)->disk_i_size = inode->i_size;
+	i_size = i_size_read(inode);
+	if (update_i_size && size > i_size) {
+		i_size_write(inode, size);
+		i_size = size;
+	}
+	BTRFS_I(inode)->disk_i_size = i_size;
+
 fail:
 	return ret;
 }
@@ -306,35 +311,31 @@ static int insert_inline_extent(struct btrfs_trans_handle *trans,
  * does the checks required to make sure the data is small enough
  * to fit as an inline extent.
  */
-static noinline int cow_file_range_inline(struct btrfs_inode *inode, u64 start,
-					  u64 end, size_t compressed_size,
+static noinline int cow_file_range_inline(struct btrfs_inode *inode, u64 size,
+					  size_t compressed_size,
 					  int compress_type,
-					  struct page **compressed_pages)
+					  struct page **compressed_pages,
+					  bool update_i_size)
 {
 	struct btrfs_drop_extents_args drop_args = { 0 };
 	struct btrfs_root *root = inode->root;
 	struct btrfs_fs_info *fs_info = root->fs_info;
 	struct btrfs_trans_handle *trans;
-	u64 isize = i_size_read(&inode->vfs_inode);
-	u64 actual_end = min(end + 1, isize);
-	u64 inline_len = actual_end - start;
-	u64 aligned_end = ALIGN(end, fs_info->sectorsize);
-	u64 data_len = inline_len;
+	u64 data_len = compressed_size ? compressed_size : size;
 	int ret;
 	struct btrfs_path *path;
 
-	if (compressed_size)
-		data_len = compressed_size;
-
-	if (start > 0 ||
-	    actual_end > fs_info->sectorsize ||
+	/*
+	 * We can create an inline extent if it ends at or beyond the current
+	 * i_size, is no larger than a sector (decompressed), and the (possibly
+	 * compressed) data fits in a leaf and the configured maximum inline
+	 * size.
+	 */
+	if (size < i_size_read(&inode->vfs_inode) ||
+	    size > fs_info->sectorsize ||
 	    data_len > BTRFS_MAX_INLINE_DATA_SIZE(fs_info) ||
-	    (!compressed_size &&
-	    (actual_end & (fs_info->sectorsize - 1)) == 0) ||
-	    end + 1 < isize ||
-	    data_len > fs_info->max_inline) {
+	    data_len > fs_info->max_inline)
 		return 1;
-	}
 
 	path = btrfs_alloc_path();
 	if (!path)
@@ -348,30 +349,21 @@ static noinline int cow_file_range_inline(struct btrfs_inode *inode, u64 start,
 	trans->block_rsv = &inode->block_rsv;
 
 	drop_args.path = path;
-	drop_args.start = start;
-	drop_args.end = aligned_end;
+	drop_args.start = 0;
+	drop_args.end = fs_info->sectorsize;
 	drop_args.drop_cache = true;
 	drop_args.replace_extent = true;
-
-	if (compressed_size && compressed_pages)
-		drop_args.extent_item_size = btrfs_file_extent_calc_inline_size(
-		   compressed_size);
-	else
-		drop_args.extent_item_size = btrfs_file_extent_calc_inline_size(
-		    inline_len);
-
+	drop_args.extent_item_size = btrfs_file_extent_calc_inline_size(data_len);
 	ret = btrfs_drop_extents(trans, root, inode, &drop_args);
 	if (ret) {
 		btrfs_abort_transaction(trans, ret);
 		goto out;
 	}
 
-	if (isize > actual_end)
-		inline_len = min_t(u64, isize, actual_end);
-	ret = insert_inline_extent(trans, path, drop_args.extent_inserted,
-				   root, &inode->vfs_inode, start,
-				   inline_len, compressed_size,
-				   compress_type, compressed_pages);
+	ret = insert_inline_extent(trans, path, drop_args.extent_inserted, root,
+				   &inode->vfs_inode, size, compressed_size,
+				   compress_type, compressed_pages,
+				   update_i_size);
 	if (ret && ret != -ENOSPC) {
 		btrfs_abort_transaction(trans, ret);
 		goto out;
@@ -380,7 +372,7 @@ static noinline int cow_file_range_inline(struct btrfs_inode *inode, u64 start,
 		goto out;
 	}
 
-	btrfs_update_inode_bytes(inode, inline_len, drop_args.bytes_found);
+	btrfs_update_inode_bytes(inode, size, drop_args.bytes_found);
 	ret = btrfs_update_inode(trans, root, inode);
 	if (ret && ret != -ENOSPC) {
 		btrfs_abort_transaction(trans, ret);
@@ -661,14 +653,15 @@ static noinline int compress_file_range(struct async_chunk *async_chunk)
 			/* we didn't compress the entire range, try
 			 * to make an uncompressed inline extent.
 			 */
-			ret = cow_file_range_inline(BTRFS_I(inode), start, end,
+			ret = cow_file_range_inline(BTRFS_I(inode), actual_end,
 						    0, BTRFS_COMPRESS_NONE,
-						    NULL);
+						    NULL, false);
 		} else {
 			/* try making a compressed inline extent */
-			ret = cow_file_range_inline(BTRFS_I(inode), start, end,
+			ret = cow_file_range_inline(BTRFS_I(inode), actual_end,
 						    total_compressed,
-						    compress_type, pages);
+						    compress_type, pages,
+						    false);
 		}
 		if (ret <= 0) {
 			unsigned long clear_flags = EXTENT_DELALLOC |
@@ -1056,9 +1049,12 @@ static noinline int cow_file_range(struct btrfs_inode *inode,
 	inode_should_defrag(inode, start, end, num_bytes, SZ_64K);
 
 	if (start == 0) {
+		u64 actual_end = min_t(u64, i_size_read(&inode->vfs_inode),
+				       end + 1);
+
 		/* lets try to make an inline extent */
-		ret = cow_file_range_inline(inode, start, end, 0,
-					    BTRFS_COMPRESS_NONE, NULL);
+		ret = cow_file_range_inline(inode, actual_end, 0,
+					    BTRFS_COMPRESS_NONE, NULL, false);
 		if (ret == 0) {
 			/*
 			 * We use DO_ACCOUNTING here because we need the
Signature · 1 line
2.30.2
#17 lore raw

PATCH v8 9/10 btrfs: implement RWF_ENCODED reads

In reply to Omar Sandoval

From: Omar Sandoval <[email protected]>

There are 4 main cases:

1. Inline extents: we copy the data straight out of the extent buffer.
2. Hole/preallocated extents: we fill in zeroes.
3. Regular, uncompressed extents: we read the sectors we need directly from disk.
4. Regular, compressed extents: we read the entire compressed extent from disk and indicate what subset of the decompressed extent is in the file.

This initial implementation simplifies a few things that can be improved in the future:

- We hold the inode lock during the operation.
- Cases 1, 3, and 4 allocate temporary memory to read into before copying out to userspace.
- We don't do read repair, because it turns out that read repair is currently broken for compressed data.

---
 fs/btrfs/ctree.h |   2 +
 fs/btrfs/file.c  |   5 +
 fs/btrfs/inode.c | 497 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 504 insertions(+)
fs/btrfs/ctree.h +2 -0
index 3a0c4fb4c657..d093f7a900d7 100644
@@ -3157,6 +3157,8 @@ int btrfs_run_delalloc_range(struct btrfs_inode *inode, struct page *locked_page
 int btrfs_writepage_cow_fixup(struct page *page, u64 start, u64 end);
 void btrfs_writepage_endio_finish_ordered(struct page *page, u64 start,
 					  u64 end, int uptodate);
+ssize_t btrfs_encoded_read(struct kiocb *iocb, struct iov_iter *iter);
+
 extern const struct dentry_operations btrfs_dentry_operations;
 extern const struct iomap_ops btrfs_dio_iomap_ops;
 extern const struct iomap_dio_ops btrfs_dio_ops;
fs/btrfs/file.c +5 -0
index 32578f31cf43..88eb4c5ddd52 100644
@@ -3630,6 +3630,11 @@ static ssize_t btrfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
 {
 	ssize_t ret = 0;
 
+	if (iocb->ki_flags & IOCB_ENCODED) {
+		if (iocb->ki_flags & IOCB_NOWAIT)
+			return -EOPNOTSUPP;
+		return btrfs_encoded_read(iocb, to);
+	}
 	if (iocb->ki_flags & IOCB_DIRECT) {
 		ret = btrfs_direct_read(iocb, to);
 		if (ret < 0 || !iov_iter_count(to) ||
fs/btrfs/inode.c +497 -0

509 lines not on this page · raw message

Signature · 1 line
2.30.2
#18 lore raw

PATCH v8 10/10 btrfs: implement RWF_ENCODED writes

In reply to Omar Sandoval

From: Omar Sandoval <[email protected]>

The implementation resembles direct I/O: we have to flush any ordered extents, invalidate the page cache, and do the io tree/delalloc/extent map/ordered extent dance. From there, we can reuse the compression code with a minor modification to distinguish the write from writeback. This also creates inline extents when possible.

Now that read and write are implemented, this also sets the FMODE_ENCODED_IO flag in btrfs_file_open().

---
 fs/btrfs/compression.c  |   7 +-
 fs/btrfs/compression.h  |   6 +-
 fs/btrfs/ctree.h        |   2 +
 fs/btrfs/file.c         |  38 +++++-
 fs/btrfs/inode.c        | 259 +++++++++++++++++++++++++++++++++++++++-
 fs/btrfs/ordered-data.c |  12 +-
 fs/btrfs/ordered-data.h |   2 +
 7 files changed, 314 insertions(+), 12 deletions(-)
fs/btrfs/compression.c +5 -2
index d9d1d9e38fc5..4cfd03918ffc 100644
@@ -336,7 +336,8 @@ static void end_compressed_bio_write(struct bio *bio)
 			bio->bi_status == BLK_STS_OK);
 	cb->compressed_pages[0]->mapping = NULL;
 
-	end_compressed_writeback(inode, cb);
+	if (cb->writeback)
+		end_compressed_writeback(inode, cb);
 	/* note, our inode could be gone now */
 
 	/*
@@ -372,7 +373,8 @@ blk_status_t btrfs_submit_compressed_write(struct btrfs_inode *inode, u64 start,
 				 struct page **compressed_pages,
 				 unsigned long nr_pages,
 				 unsigned int write_flags,
-				 struct cgroup_subsys_state *blkcg_css)
+				 struct cgroup_subsys_state *blkcg_css,
+				 bool writeback)
 {
 	struct btrfs_fs_info *fs_info = inode->root->fs_info;
 	struct bio *bio = NULL;
@@ -396,6 +398,7 @@ blk_status_t btrfs_submit_compressed_write(struct btrfs_inode *inode, u64 start,
 	cb->mirror_num = 0;
 	cb->compressed_pages = compressed_pages;
 	cb->compressed_len = compressed_len;
+	cb->writeback = writeback;
 	cb->orig_bio = NULL;
 	cb->nr_pages = nr_pages;
 
fs/btrfs/compression.h +5 -1
index 8001b700ea3a..f95cdc16f503 100644
@@ -49,6 +49,9 @@ struct compressed_bio {
 	/* the compression algorithm for this bio */
 	int compress_type;
 
+	/* Whether this is a write for writeback. */
+	bool writeback;
+
 	/* number of compressed pages in the array */
 	unsigned long nr_pages;
 
@@ -96,7 +99,8 @@ blk_status_t btrfs_submit_compressed_write(struct btrfs_inode *inode, u64 start,
 				  struct page **compressed_pages,
 				  unsigned long nr_pages,
 				  unsigned int write_flags,
-				  struct cgroup_subsys_state *blkcg_css);
+				  struct cgroup_subsys_state *blkcg_css,
+				  bool writeback);
 blk_status_t btrfs_submit_compressed_read(struct inode *inode, struct bio *bio,
 				 int mirror_num, unsigned long bio_flags);
 
fs/btrfs/ctree.h +2 -0
index d093f7a900d7..33a08ab5cb0e 100644
@@ -3158,6 +3158,8 @@ int btrfs_writepage_cow_fixup(struct page *page, u64 start, u64 end);
 void btrfs_writepage_endio_finish_ordered(struct page *page, u64 start,
 					  u64 end, int uptodate);
 ssize_t btrfs_encoded_read(struct kiocb *iocb, struct iov_iter *iter);
+ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from,
+			       struct encoded_iov *encoded);
 
 extern const struct dentry_operations btrfs_dentry_operations;
 extern const struct iomap_ops btrfs_dio_iomap_ops;
fs/btrfs/file.c +34 -4
index 88eb4c5ddd52..dd7fec85fea1 100644
@@ -3,6 +3,7 @@
  * Copyright (C) 2007 Oracle.  All rights reserved.
  */
 
+#include <linux/encoded_io.h>
 #include <linux/fs.h>
 #include <linux/pagemap.h>
 #include <linux/time.h>
@@ -1993,6 +1994,32 @@ static ssize_t btrfs_direct_write(struct kiocb *iocb, struct iov_iter *from)
 	return written ? written : err;
 }
 
+static ssize_t btrfs_encoded_write(struct kiocb *iocb, struct iov_iter *from)
+{
+	struct file *file = iocb->ki_filp;
+	struct inode *inode = file_inode(file);
+	struct encoded_iov encoded;
+	ssize_t ret;
+
+	ret = copy_encoded_iov_from_iter(&encoded, from);
+	if (ret)
+		return ret;
+
+	btrfs_inode_lock(inode, 0);
+	ret = generic_encoded_write_checks(iocb, &encoded);
+	if (ret || encoded.len == 0)
+		goto out;
+
+	ret = btrfs_write_check(iocb, from, encoded.len);
+	if (ret < 0)
+		goto out;
+
+	ret = btrfs_do_encoded_write(iocb, from, &encoded);
+out:
+	btrfs_inode_unlock(inode, 0);
+	return ret;
+}
+
 static ssize_t btrfs_file_write_iter(struct kiocb *iocb,
 				    struct iov_iter *from)
 {
@@ -2009,14 +2036,17 @@ static ssize_t btrfs_file_write_iter(struct kiocb *iocb,
 	if (test_bit(BTRFS_FS_STATE_ERROR, &inode->root->fs_info->fs_state))
 		return -EROFS;
 
-	if (!(iocb->ki_flags & IOCB_DIRECT) &&
-	    (iocb->ki_flags & IOCB_NOWAIT))
+	if ((iocb->ki_flags & IOCB_NOWAIT) &&
+	    (!(iocb->ki_flags & IOCB_DIRECT) ||
+	     (iocb->ki_flags & IOCB_ENCODED)))
 		return -EOPNOTSUPP;
 
 	if (sync)
 		atomic_inc(&inode->sync_writers);
 
-	if (iocb->ki_flags & IOCB_DIRECT)
+	if (iocb->ki_flags & IOCB_ENCODED)
+		num_written = btrfs_encoded_write(iocb, from);
+	else if (iocb->ki_flags & IOCB_DIRECT)
 		num_written = btrfs_direct_write(iocb, from);
 	else
 		num_written = btrfs_buffered_write(iocb, from);
@@ -3587,7 +3617,7 @@ static loff_t btrfs_file_llseek(struct file *file, loff_t offset, int whence)
 
 static int btrfs_file_open(struct inode *inode, struct file *filp)
 {
-	filp->f_mode |= FMODE_NOWAIT | FMODE_BUF_RASYNC;
+	filp->f_mode |= FMODE_NOWAIT | FMODE_BUF_RASYNC | FMODE_ENCODED_IO;
 	return generic_file_open(inode, filp);
 }
 
fs/btrfs/inode.c +257 -2

283 lines not on this page · raw message

fs/btrfs/ordered-data.c +9 -3
index e11a517fab10..00329d82337e 100644
@@ -472,9 +472,15 @@ void btrfs_remove_ordered_extent(struct btrfs_inode *btrfs_inode,
 	spin_lock(&btrfs_inode->lock);
 	btrfs_mod_outstanding_extents(btrfs_inode, -1);
 	spin_unlock(&btrfs_inode->lock);
-	if (root != fs_info->tree_root)
-		btrfs_delalloc_release_metadata(btrfs_inode, entry->num_bytes,
-						false);
+	if (root != fs_info->tree_root) {
+		u64 release;
+
+		if (test_bit(BTRFS_ORDERED_ENCODED, &entry->flags))
+			release = entry->disk_num_bytes;
+		else
+			release = entry->num_bytes;
+		btrfs_delalloc_release_metadata(btrfs_inode, release, false);
+	}
 
 	if (test_bit(BTRFS_ORDERED_DIRECT, &entry->flags))
 		percpu_counter_add_batch(&fs_info->dio_bytes, -entry->num_bytes,
fs/btrfs/ordered-data.h +2 -0
index 8c8d6ed7a350..67c72485bd90 100644
@@ -62,6 +62,8 @@ enum {
 	BTRFS_ORDERED_LOGGED_CSUM,
 	/* We wait for this extent to complete in the current transaction */
 	BTRFS_ORDERED_PENDING,
+	/* RWF_ENCODED I/O */
+	BTRFS_ORDERED_ENCODED,
 };
 
 struct btrfs_ordered_extent {
Signature · 1 line
2.30.2
#19 lore raw
In reply to Omar Sandoval
Quoted 16 lines · Omar Sandoval

From: Omar Sandoval <[email protected]>

This series adds an API for reading compressed data on a filesystem without decompressing it as well as support for writing compressed data directly to the filesystem. As with the previous submissions, I've included a man page patch describing the API. I have test cases (including fsstress support) and example programs which I'll send up [1].

The main use-case is Btrfs send/receive: currently, when sending data from one compressed filesystem to another, the sending side decompresses the data and the receiving side recompresses it before writing it out. This is wasteful and can be avoided if we can just send and write compressed extents. The patches implementing the send/receive support will be sent shortly.

Quoted 8 lines

Patches 1-3 add the VFS support and UAPI. Patch 4 is a fix that this series depends on; it can be merged independently. Patches 5-8 are Btrfs prep patches. Patch 9 adds Btrfs encoded read support and patch 10 adds Btrfs encoded write support.

These patches are based on Dave Sterba's Btrfs misc-next branch [2], which is in turn currently based on v5.12-rc3.

Al,

Can we get some movement on this? Omar is sort of spinning his wheels here trying to get this stuff merged, no major changes have been done in a few postings. Thanks,

Josef

#20 lore raw
Quoted 4 lines · Josef Bacik

Can we get some movement on this? Omar is sort of spinning his wheels here trying to get this stuff merged, no major changes have been done in a few postings.

I'm not Al, and I absolutely detest the IOCB_ENCODED thing, and want more explanations of why this should be done that way, and pollute our iov_iter handling EVEN MORE.

Our iov_iter stuff isn't the most legible, and I don't understand why anybody would ever think it's a good idea to spread what is clearly a "struct" inside multiple different iov extents.

Honestly, this sounds way more like an ioctl interface than read/write. We've done that before.

But if it has to be done with an iov_iter, is there *any* reason to not make it be a hard rule that iov[0] should not be the entirely of the struct, and the code shouldn't ever need to iterate?

Also I see references to the man-page, but honestly, that's not how the kernel UAPI should be defined ("just read the man-page"), plus I refuse to live in the 70's, and consider troff to be an atrocious format.

So make the UAPI explanation for this horror be in a legible format that is actually part of the kernel so that I can read what the intent is, instead of having to decode hieroglypics.

Linus

#21 lore raw
Quoted 10 lines · Linus Torvalds
Quoted 4 lines · Josef Bacik

Can we get some movement on this? Omar is sort of spinning his wheels here trying to get this stuff merged, no major changes have been done in a few postings.

I'm not Al, and I absolutely detest the IOCB_ENCODED thing, and want more explanations of why this should be done that way, and pollute our iov_iter handling EVEN MORE.

Quoted 6 lines

Our iov_iter stuff isn't the most legible, and I don't understand why anybody would ever think it's a good idea to spread what is clearly a "struct" inside multiple different iov extents.

Honestly, this sounds way more like an ioctl interface than read/write. We've done that before.

That's actually the way this started

https://lore.kernel.org/linux-fsdevel/8eae56abb90c0fe87c350322485ce8674e135074.1567623877.git.osandov@fb.com/

it was suggested that Omar make it generic by Dave Chinner, hence this is the direction it took. I'll leave the rest of the comments for Omar to respond to himself. Thanks,

Josef

#22 lore raw
In reply to Linus Torvalds
Quoted 10 lines · Linus Torvalds
Quoted 4 lines · Josef Bacik

Can we get some movement on this? Omar is sort of spinning his wheels here trying to get this stuff merged, no major changes have been done in a few postings.

I'm not Al, and I absolutely detest the IOCB_ENCODED thing, and want more explanations of why this should be done that way, and pollute our iov_iter handling EVEN MORE.

Quoted 6 lines

Our iov_iter stuff isn't the most legible, and I don't understand why anybody would ever think it's a good idea to spread what is clearly a "struct" inside multiple different iov extents.

Honestly, this sounds way more like an ioctl interface than read/write. We've done that before.

As Josef just mentioned, I started with an ioctl, and Dave Chinner suggested doing it this way:
https://lore.kernel.org/linux-fsdevel/[email protected]/

The nice thing about it is that it sidesteps all of the issues we had with the dedupe/reflink ioctls over the years where permissions checks and the like were slightly different from read/write.

Quoted 3 lines

But if it has to be done with an iov_iter, is there *any* reason to not make it be a hard rule that iov[0] should not be the entirely of the struct, and the code shouldn't ever need to iterate?

For RWF_ENCODED, iov[0] is always used as the entirety of the struct. I made the helper more generic to support other use cases, but if that's the main objection I can easily make it specifically use iov[0].

Quoted 4 lines

Also I see references to the man-page, but honestly, that's not how the kernel UAPI should be defined ("just read the man-page"), plus I refuse to live in the 70's, and consider troff to be an atrocious format.

No disagreement here, troff is horrible to read.

Quoted 3 lines

So make the UAPI explanation for this horror be in a legible format that is actually part of the kernel so that I can read what the intent is, instead of having to decode hieroglypics.

I didn't want to document the UAPI in two places that would need to be kept in sync, but this is fair enough. I'll add UAPI documentation to the kernel. In the meantime, for anyone else following along, here's the formatted man page:

ENCODED_IO(7) Linux Programmer's Manual ENCODED_IO(7)

NAME
encoded_io - overview of encoded I/O

DESCRIPTION
Several filesystems (e.g., Btrfs) support transparent encoding (e.g., compression, encryption) of data on disk: written data is encoded by the kernel before it is written to disk, and read
data is decoded before being returned to the user. In some
cases, it is useful to skip this encoding step. For example, the user may want to read the compressed contents of a file or write pre-compressed data directly to a file. This is referred to as "encoded I/O".

Encoded I/O API
Encoded I/O is specified with the RWF_ENCODED flag to
preadv2(2) and pwritev2(2). If RWF_ENCODED is specified, then iov[0].iov_base points to an encoded_iov structure, defined in <linux/fs.h> as:

struct encoded_iov {
__aligned_u64 len;
__aligned_u64 unencoded_len;
__aligned_u64 unencoded_offset;
__u32 compression;
__u32 encryption;
};

This may be extended in the future, so iov[0].iov_len must be set to sizeof(struct encoded_iov) for forward/backward compati‐ bility. The remaining buffers contain the encoded data.

compression and encryption are the encoding fields. compres‐ sion is ENCODED_IOV_COMPRESSION_NONE (zero) or a filesystem- specific ENCODED_IOV_COMPRESSION_* constant; see Filesystem support below. encryption is currently always ENCODED_IOV_EN‐ CRYPTION_NONE (zero).

unencoded_len is the length of the unencoded (i.e., decrypted and decompressed) data. unencoded_offset is the offset from the first byte of the unencoded data to the first byte of logi‐ cal data in the file (less than or equal to unencoded_len). len is the length of the data in the file (less than or equal to unencoded_len - unencoded_offset). See Extent layout below for some examples.

If the unencoded data is actually longer than unencoded_len, then it is truncated; if it is shorter, then it is extended with zeroes.

pwritev2(2) uses the metadata specified in iov[0], writes the encoded data from the remaining buffers, and returns the number of encoded bytes written (that is, the sum of iov[n].iov_len for 1 <= n < iovcnt; partial writes will not occur). At least one encoding field must be non-zero. Note that the encoded data is not validated when it is written; if it is not valid (e.g., it cannot be decompressed), then a subsequent read may return an error. If the offset argument to pwritev2(2) is -1, then the file offset is incremented by len. If iov[0].iov_len is less than sizeof(struct encoded_iov) in the kernel, then any fields unknown to user space are treated as if they were zero; if it is greater and any fields unknown to the kernel are non- zero, then this returns -1 and sets errno to E2BIG.

preadv2(2) populates the metadata in iov[0], the encoded data in the remaining buffers, and returns the number of encoded
bytes read. This will only return one extent per call. This
can also read data which is not encoded; all encoding fields
will be zero in that case. If the offset argument to
preadv2(2) is -1, then the file offset is incremented by len. If iov[0].iov_len is less than sizeof(struct encoded_iov) in the kernel and any fields unknown to user space are non-zero, then preadv2(2) returns -1 and sets errno to E2BIG; if it is greater, then any fields unknown to the kernel are returned as
zero. If the provided buffers are not large enough to return
an entire encoded extent, then preadv2(2) returns -1 and sets errno to ENOBUFS.

As the filesystem page cache typically contains decoded data, encoded I/O bypasses the page cache.

Extent layout
By using len, unencoded_len, and unencoded_offset, it is possi‐ ble to refer to a subset of an unencoded extent.

In the simplest case, len is equal to unencoded_len and unen‐ coded_offset is zero. This means that the entire unencoded ex‐ tent is used.

However, suppose we read 50 bytes into a file which contains a single compressed extent. The filesystem must still return the entire compressed extent for us to be able to decompress it, so unencoded_len would be the length of the entire decompressed
extent. However, because the read was at offset 50, the first
50 bytes should be ignored. Therefore, unencoded_offset would be 50, and len would accordingly be unencoded_len - 50.

Additionally, suppose we want to create an encrypted file with length 500, but the file is encrypted with a block cipher using a block size of 4096. The unencoded data would therefore in‐ clude the appropriate padding, and unencoded_len would be 4096. However, to represent the logical size of the file, len would be 500 (and unencoded_offset would be 0).

Similar situations can arise in other cases:

* If the filesystem pads data to the filesystem block size be‐ fore compressing, then compressed files with a size un‐ aligned to the filesystem block size will end with an extent with len < unencoded_len.

* Extents cloned from the middle of a larger encoded extent with FICLONERANGE may have a non-zero unencoded_offset and/or len < unencoded_len.

* If the middle of an encoded extent is overwritten, the filesystem may create extents with a non-zero unencoded_off‐ set and/or len < unencoded_len for the parts that were not overwritten.

Security
Encoded I/O creates the potential for some security issues:

* Encoded writes allow writing arbitrary data which the kernel will decode on a subsequent read. Decompression algorithms are complex and may have bugs which can be exploited by ma‐ liciously crafted data.

* Encoded reads may return data which is not logically present in the file (see the discussion of len vs unencoded_len
above). It may not be intended for this data to be read‐
able.

Therefore, encoded I/O requires privilege. Namely, the RWF_EN‐ CODED flag may only be used if the file description has the O_ALLOW_ENCODED file status flag set, and the O_ALLOW_ENCODED flag may only be set by a thread with the CAP_SYS_ADMIN capa‐ bility. The O_ALLOW_ENCODED flag can be set by open(2) or fc‐
ntl(2). It can also be cleared by fcntl(2); clearing it does
not require CAP_SYS_ADMIN. Note that it is not cleared on fork(2) or execve(2). One may wish to use O_CLOEXEC with O_AL‐ LOW_ENCODED.

Filesystem support
Encoded I/O is supported on the following filesystems:

Btrfs (since Linux 5.13)

Btrfs supports encoded reads and writes of compressed data. The data is encoded as follows:

* If compression is ENCODED_IOV_COMPRESSION_BTRFS_ZLIB, then the encoded data is a single zlib stream.

* If compression is ENCODED_IOV_COMPRESSION_BTRFS_ZSTD, then the encoded data is a single zstd frame com‐ pressed with the windowLog compression parameter set to no more than 17.

* If compression is one of ENCODED_IOV_COMPRES‐
SION_BTRFS_LZO_4K, ENCODED_IOV_COMPRES‐
SION_BTRFS_LZO_8K, ENCODED_IOV_COMPRES‐
SION_BTRFS_LZO_16K, ENCODED_IOV_COMPRES‐
SION_BTRFS_LZO_32K, or ENCODED_IOV_COMPRES‐
SION_BTRFS_LZO_64K, then the encoded data is com‐ pressed page by page (using the page size indicated by the name of the constant) with LZO1X and wrapped in the format documented in the Linux kernel source file fs/btrfs/lzo.c.

Additionally, there are some restrictions on
pwritev2(2):

* offset (or the current file offset if offset is -1) must be aligned to the sector size of the filesystem.

* len must be aligned to the sector size of the filesystem unless the data ends at or beyond the cur‐ rent end of the file.

* unencoded_len and the length of the encoded data must each be no more than 128 KiB. This limit may in‐ crease in the future.

* The length of the encoded data must be less than or equal to unencoded_len.

* If using LZO, the filesystem's page size must match the compression page size.

SEE ALSO

       open(2), preadv2(2)

Linux 2020-11-11 ENCODED_IO(7)

#23 lore raw
Quoted 34 lines · Omar Sandoval
Quoted 10 lines · Linus Torvalds
Quoted 4 lines · Josef Bacik

Can we get some movement on this? Omar is sort of spinning his wheels here trying to get this stuff merged, no major changes have been done in a few postings.

I'm not Al, and I absolutely detest the IOCB_ENCODED thing, and want more explanations of why this should be done that way, and pollute our iov_iter handling EVEN MORE.

Quoted 6 lines

Our iov_iter stuff isn't the most legible, and I don't understand why anybody would ever think it's a good idea to spread what is clearly a "struct" inside multiple different iov extents.

Honestly, this sounds way more like an ioctl interface than read/write. We've done that before.

As Josef just mentioned, I started with an ioctl, and Dave Chinner suggested doing it this way:
https://lore.kernel.org/linux-fsdevel/[email protected]/

The nice thing about it is that it sidesteps all of the issues we had with the dedupe/reflink ioctls over the years where permissions checks and the like were slightly different from read/write.

Quoted 3 lines

But if it has to be done with an iov_iter, is there *any* reason to not make it be a hard rule that iov[0] should not be the entirely of the struct, and the code shouldn't ever need to iterate?

For RWF_ENCODED, iov[0] is always used as the entirety of the struct. I made the helper more generic to support other use cases, but if that's the main objection I can easily make it specifically use iov[0].

Quoted 6 lines
Quoted 4 lines

Also I see references to the man-page, but honestly, that's not how the kernel UAPI should be defined ("just read the man-page"), plus I refuse to live in the 70's, and consider troff to be an atrocious format.

No disagreement here, troff is horrible to read.

Not a fan of troff either: One thing that might help you a little bit is to use pandoc to convert to troff. You can write your manpage in markdown or rst and then convert it into troff via pandoc. Still might require some massaging but it makes it considerably more pleasant to write a manpage.

Christian

#24 lore raw
In reply to Omar Sandoval
Quoted 4 lines · Omar Sandoval

For RWF_ENCODED, iov[0] is always used as the entirety of the struct. I made the helper more generic to support other use cases, but if that's the main objection I can easily make it specifically use iov[0].

Honestly, with new interfaces, I'd prefer to always start off as limited as possible.

And read/write is not very limited (but O_ALLOW_ENCODED and RWF_ENCODED at least helps with the "fool suid program to do it"). But at least we could make sure that the structure then has to be as strict as humanly possible.

So it's not so much a "main objection" as more about trying to make the rules stricter in the hope that that at least makes only one very particular way of doing things valid. I'd hate for user space to start 'streaming" struct data.

Quoted 5 lines
Quoted 4 lines

Also I see references to the man-page, but honestly, that's not how the kernel UAPI should be defined ("just read the man-page"), plus I refuse to live in the 70's, and consider troff to be an atrocious format.

Quoted 8 lines

No disagreement here, troff is horrible to read.

Quoted 3 lines

So make the UAPI explanation for this horror be in a legible format that is actually part of the kernel so that I can read what the intent is, instead of having to decode hieroglypics.

I didn't want to document the UAPI in two places that would need to be kept in sync

Honestly, I would suggest that nobody ever write troff format stuff. I don't think it supports UTF-8 properly, for example, which means that you can't even give credit to people properly etc.

RST isn't perfect, but at least it's human-legible, and it's obviously what we're encouraging for kernel use. You can use rst2man to convert to bad formats (and yes, you might lose something in the translation, eg proper names etc).

Almost anything else(*) is better than troff. But at least I can read the formatted version.

Linus

(*) With the possible exception of "info" files. Now *there* is a truly pointless format maximally designed to make it inconvenient for users.

#25 lore raw
Quoted 14 lines · Linus Torvalds
Quoted 4 lines · Omar Sandoval

For RWF_ENCODED, iov[0] is always used as the entirety of the struct. I made the helper more generic to support other use cases, but if that's the main objection I can easily make it specifically use iov[0].

Honestly, with new interfaces, I'd prefer to always start off as limited as possible.

And read/write is not very limited (but O_ALLOW_ENCODED and RWF_ENCODED at least helps with the "fool suid program to do it"). But at least we could make sure that the structure then has to be as strict as humanly possible.

Quoted 4 lines

So it's not so much a "main objection" as more about trying to make the rules stricter in the hope that that at least makes only one very particular way of doing things valid. I'd hate for user space to start 'streaming" struct data.

After spending a few minutes trying to simplify copy_struct_from_iter(), it's honestly easier to just use the iterate_all_kinds() craziness than open coding it to only operate on iov[0]. But that's an implementation detail, and we can trivially make the interface stricter:

lib/iov_iter.c +2 -2
index 395ca89e5d9b..41b6b0325d18 100644
@@ -969,9 +969,9 @@ EXPORT_SYMBOL(copy_page_from_iter);
  * On success, the iterator is advanced @usize bytes. On error, the iterator is
  * not advanced.
  */
-int copy_struct_from_iter(void *dst, size_t ksize, struct iov_iter *i,
-			  size_t usize)
+int copy_struct_from_iter(void *dst, size_t ksize, struct iov_iter *i)
 {
+	size_t usize = iov_iter_single_seg_count(i);
 	if (usize <= ksize) {
 		if (!copy_from_iter_full(dst, usize, i))
 			return -EFAULT;

I.e., the size of the userspace structure is always the remaining size of the current segment. Maybe we can even throw in a check that we're either at the beginning of the current segment or the very beginning of the whole iter, what do you think?

(Again, this is what RWF_ENCODED already does, it was just easier to write copy_struct_from_iter() more generically).

Quoted 14 lines
Quoted 5 lines
Quoted 4 lines

Also I see references to the man-page, but honestly, that's not how the kernel UAPI should be defined ("just read the man-page"), plus I refuse to live in the 70's, and consider troff to be an atrocious format.

Quoted 8 lines

No disagreement here, troff is horrible to read.

Quoted 3 lines

So make the UAPI explanation for this horror be in a legible format that is actually part of the kernel so that I can read what the intent is, instead of having to decode hieroglypics.

I didn't want to document the UAPI in two places that would need to be kept in sync

Quoted 8 lines

Honestly, I would suggest that nobody ever write troff format stuff. I don't think it supports UTF-8 properly, for example, which means that you can't even give credit to people properly etc.

RST isn't perfect, but at least it's human-legible, and it's obviously what we're encouraging for kernel use. You can use rst2man to convert to bad formats (and yes, you might lose something in the translation, eg proper names etc).

It looks like there's precedent for man pages being generated from the kernel documentation [1], so I'll try that.

1: https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/commit/?id=53666f6c30451cde022f65d35a8d448f5a7132ba

Quoted 8 lines

Almost anything else(*) is better than troff. But at least I can read the formatted version.

Linus

(*) With the possible exception of "info" files. Now *there* is a truly pointless format maximally designed to make it inconvenient for users.

#26 lore raw
Quoted 5 lines · Omar Sandoval

After spending a few minutes trying to simplify copy_struct_from_iter(), it's honestly easier to just use the iterate_all_kinds() craziness than open coding it to only operate on iov[0]. But that's an implementation detail, and we can trivially make the interface stricter:

This is an improvement, but talking about the iterate_all_kinds() craziness, I think your existing code is broken.

That third case (kernel pointer source):

+    copy = min(ksize - copied, v.iov_len);
+    memcpy(dst + copied, v.iov_base, copy);
+    if (memchr_inv(v.iov_base, 0, v.iov_len))

+ return -E2BIG;

can't be right. Aren't you checking that it's *all* zero, even the part you copied?

Our iov_iter stuff is really complicated already, this is part of why I'm not a huge fan of using it.

I still suspect you'd be better off not using the iterate_all_kinds() thing at all, and just explicitly checking ITER_BVEC/ITER_KVEC manually.

Because you can play games like fooling your "copy_struct_from_iter()" to not copy anything at all with ITER_DISCARD, can't you?

Which then sounds like it might end up being useful as a kernel data leak, because it will use some random uninitialized kernel memory for the structure.

Now, I don't think you can actually get that ITER_DISCARD case, so this is not *really* a problem, but it's another example of how that iterate_all_kinds() thing has these subtle cases embedded into it.

The whole point of copy_struct_from_iter() is presumably to be the same kind of "obviously safe" interface as copy_struct_from_user() is meant to be, so these subtle cases just then make me go "Hmm".

I think just open-coding this when you know there is no actual looping going on, and the data has to be at the *beginning*, should be fairly simple. What makes iterate_all_kinds() complicated is that iteration, the fact that there can be empty entries in there, but it's also that "iov_offset" thing etc.

For the case where you just (a) require that iov_offset is zero, and
(b) everything has to fit into the very first iov entry (regardless of what type that iov entry is), I think you actually end up with a much simpler model.

I do realize that I am perhaps concentrating a bit too much on this one part of the patch series, but the iov_iter thing has bitten us before. And it has bitten really core developers and then Al has had to fix up mistakes.

In fact, it wasn't that long ago that I asked Al to verify code I wrote, because I was worried about having missed something subtle. So now when I see these iov_iter users, it just makes me go all nervous.

Linus

#27 lore raw
Quoted 17 lines · Linus Torvalds
Quoted 5 lines · Omar Sandoval

After spending a few minutes trying to simplify copy_struct_from_iter(), it's honestly easier to just use the iterate_all_kinds() craziness than open coding it to only operate on iov[0]. But that's an implementation detail, and we can trivially make the interface stricter:

This is an improvement, but talking about the iterate_all_kinds() craziness, I think your existing code is broken.

That third case (kernel pointer source):

(quoted diff) +4 -0
+    copy = min(ksize - copied, v.iov_len);
+    memcpy(dst + copied, v.iov_base, copy);
+    if (memchr_inv(v.iov_base, 0, v.iov_len))
+        return -E2BIG;
Quoted 2 lines

can't be right. Aren't you checking that it's *all* zero, even the part you copied?

Oops, that should of course be

	if (memchr_inv(v.iov_base + copy, 0, v.iov_len - copy))
		return -E2BIG;

like the other cases. Point taken, though.

Quoted 10 lines

Our iov_iter stuff is really complicated already, this is part of why I'm not a huge fan of using it.

I still suspect you'd be better off not using the iterate_all_kinds() thing at all, and just explicitly checking ITER_BVEC/ITER_KVEC manually.

Because you can play games like fooling your "copy_struct_from_iter()" to not copy anything at all with ITER_DISCARD, can't you?

Quoted 7 lines

Which then sounds like it might end up being useful as a kernel data leak, because it will use some random uninitialized kernel memory for the structure.

Now, I don't think you can actually get that ITER_DISCARD case, so this is not *really* a problem, but it's another example of how that iterate_all_kinds() thing has these subtle cases embedded into it.

Right, that would probably be better off returning EFAULT or something for ITER_DISCARD.

Quoted 15 lines

The whole point of copy_struct_from_iter() is presumably to be the same kind of "obviously safe" interface as copy_struct_from_user() is meant to be, so these subtle cases just then make me go "Hmm".

I think just open-coding this when you know there is no actual looping going on, and the data has to be at the *beginning*, should be fairly simple. What makes iterate_all_kinds() complicated is that iteration, the fact that there can be empty entries in there, but it's also that "iov_offset" thing etc.

For the case where you just (a) require that iov_offset is zero, and
(b) everything has to fit into the very first iov entry (regardless of what type that iov entry is), I think you actually end up with a much simpler model.

Quoted 8 lines

I do realize that I am perhaps concentrating a bit too much on this one part of the patch series, but the iov_iter thing has bitten us before. And it has bitten really core developers and then Al has had to fix up mistakes.

In fact, it wasn't that long ago that I asked Al to verify code I wrote, because I was worried about having missed something subtle. So now when I see these iov_iter users, it just makes me go all nervous.

So here's what it looks like with these restrictions (chances are there's a bug or two in here):

int copy_struct_from_iter(void *dst, size_t ksize, struct iov_iter *i)

{
	size_t usize;
	int ret;

	if (i->iov_offset != 0)
		return -EINVAL;
	if (iter_is_iovec(i)) {
		usize = i->iov->iov_len;
		might_fault();
		if (copyin(dst, i->iov->iov_base, min(ksize, usize)))
			return -EFAULT;
		if (usize > ksize) {
			ret = check_zeroed_user(i->iov->iov_base + ksize,
						usize - ksize);
			if (ret < 0)
				return ret;
			else if (ret == 0)
				return -E2BIG;
		}
	} else if (iov_iter_is_kvec(i)) {
		usize = i->kvec->iov_len;
		memcpy(dst, i->kvec->iov_base, min(ksize, usize));
		if (usize > ksize &&
		    memchr_inv(i->kvec->iov_base + ksize, 0, usize - ksize))
			return -E2BIG;
	} else if (iov_iter_is_bvec(i)) {
		char *p;

		usize = i->bvec->bv_len;
		p = kmap_atomic(i->bvec->bv_page);
		memcpy(dst, p + i->bvec->bv_offset, min(ksize, usize));
		if (usize > ksize &&
		    memchr_inv(p + i->bvec->bv_offset + ksize, 0,
			       usize - ksize)) {
			kunmap_atomic(p);
			return -E2BIG;
		}
		kunmap_atomic(p);
	} else {
		return -EFAULT;
	}
	if (usize < ksize)
		memset(dst + usize, 0, ksize - usize);
	iov_iter_advance(i, usize);
	return 0;
}

Not much shorter, but it is easier to follow.

#28 lore raw
Quoted 2 lines · Omar Sandoval

Not much shorter, but it is easier to follow.

Yeah, that looks about right to me.

You should probably use kmap_local_page() rather than kmap_atomic() these days, but other than that this looks fairly straightforward, and I much prefer the model where we very much force that "must be the first iovec entry".

As you say, maybe not shorter, but a lot more straightforward.

That said, looking through the patch series, I see at least one other issue. Look at parisc:

    +#define O_ALLOW_ENCODED 100000000

yeah, that's completely wrong. I see how it happened, but that's _really_ wrong.

I would want others to take a look in case there's something else. I'm not qualified to comment about (nor do I deeply care) about the btrfs parts, but the generic interface parts should most definitely get more attention.

By Al, if possible, but other fs people too..

Linus

#29 lore raw
Quoted 10 lines · Linus Torvalds
Quoted 2 lines · Omar Sandoval

Not much shorter, but it is easier to follow.

Yeah, that looks about right to me.

You should probably use kmap_local_page() rather than kmap_atomic() these days, but other than that this looks fairly straightforward, and I much prefer the model where we very much force that "must be the first iovec entry".

To be exact, this code only enforces that the iov_iter is at the beginning of the current entry. As far as I can tell, iov_iter doesn't track its position overall, so there's no way to tell whether the current entry is the first one.

Quoted 8 lines

As you say, maybe not shorter, but a lot more straightforward.

That said, looking through the patch series, I see at least one other issue. Look at parisc:

    +#define O_ALLOW_ENCODED 100000000

yeah, that's completely wrong. I see how it happened, but that's _really_ wrong.

Ugh, that's embarrassing. It also happens to add exactly one new bit to VALID_OPEN_FLAGS, so the BUILD_BUG_ON() in fcntl_init() didn't catch it.

Quoted 6 lines

I would want others to take a look in case there's something else. I'm not qualified to comment about (nor do I deeply care) about the btrfs parts, but the generic interface parts should most definitely get more attention.

By Al, if possible, but other fs people too..

That's all I ask. I'll fix your comments and wait a few days to get some more feedback on the fs side before I resend the patch bomb.

Thanks,
Omar