Re: [PATCH v5 3/9] fs: add RWF_ENCODED for reading/writing compressed data

Amir Goldstein
2020-08-21T08:48:08Z

On Fri, Aug 21, 2020 at 10:38 AM Omar Sandoval <osandov@osandov.com> wrote:
>
> From: Omar Sandoval <osandov@fb.com>
>
> 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.
>
> Signed-off-by: Omar Sandoval <osandov@fb.com>
> ---
> Documentation/filesystems/encoded_io.rst | 74 ++++++++++
> Documentation/filesystems/index.rst | 1 +
> include/linux/fs.h | 16 +++
> include/uapi/linux/fs.h | 33 ++++-
> mm/filemap.c | 166 +++++++++++++++++++++--
> 5 files changed, 276 insertions(+), 14 deletions(-)
> create mode 100644 Documentation/filesystems/encoded_io.rst
>
> diff --git a/Documentation/filesystems/encoded_io.rst b/Documentation/filesystems/encoded_io.rst
> new file mode 100644
> index 000000000000..50405276d866
> --- /dev/null
> +++ b/Documentation/filesystems/encoded_io.rst
> @@ -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.

Haha that rings in my head like the "Smoking kills!" warnings...

I find it a bit odd that you mix page cache at all when reading
unencoded extents.
Feels like a file with FMODE_ENCODED_IO should stick to direct IO in all cases.
I don't know how btrfs deals with mixing direct IO and page cache IO normally,
but surely the rules could be made even stricter for an inode accessed with this
new API?

Is there something I am misunderstanding?

Thanks,
Amir.