Re: [PATCH v2 21/21] docs: fuse: document extended passthrough (FUSE_PASSTHROUGH_INO)

Amir Goldstein <[email protected]> Sat, 16 May 2026 20:23:15 +0200
Newsgroups org.kernel.vger.linux-unionfs,dev.linux.lists.fuse-devel
Message-ID <CAOQ4uxjg4ULPKC7K9PabXPOOT7J5i2EOnzsv5ABNP3R5Y9jdYQ@mail.gmail.com>
On Sat, May 16, 2026 at 2:53 AM Joanne Koong <[email protected]> wrote:
>
> Add section about extended passthrough (FUSE_PASSTHROUGH_INO) mode.
>
> Signed-off-by: Joanne Koong <[email protected]>
> ---
>  .../filesystems/fuse/fuse-passthrough.rst     | 126 ++++++++++++++++++
>  1 file changed, 126 insertions(+)
>
> diff --git a/Documentation/filesystems/fuse/fuse-passthrough.rst b/Documentation/filesystems/fuse/fuse-passthrough.rst
> index 2b0e7c2da54a..751d27c6fc5c 100644
> --- a/Documentation/filesystems/fuse/fuse-passthrough.rst
> +++ b/Documentation/filesystems/fuse/fuse-passthrough.rst
> @@ -25,6 +25,11 @@ operations.
>  Currently, passthrough is supported for operations like ``read(2)``/``write(2)``
>  (via ``read_iter``/``write_iter``), ``splice(2)``, and ``mmap(2)``.
>
> +With the extended ``FUSE_PASSTHROUGH_INO`` mode, passthrough is also supported
> +for inode operations (getattr, setattr) and directory operations (readdir).
> +In this mode, a backing file is attached to a fuse inode for its entire
> +lifetime.
> +
>  Enabling Passthrough
>  ====================
>
> @@ -46,6 +51,127 @@ To use FUSE passthrough:
>       the ``backing_id`` to release the kernel's reference to the backing file
>       when it's no longer needed for passthrough setups.
>
> +Extended Passthrough (FUSE_PASSTHROUGH_INO)
> +============================================
> +
> +``FUSE_PASSTHROUGH_INO`` is a stricter variant of ``FUSE_PASSTHROUGH`` in
> +which the backing file inode number must match the fuse inode number, enforcing
> +a one-to-one mapping. The kernel offers this flag during ``FUSE_INIT`` if
> +``CONFIG_FUSE_PASSTHROUGH`` is enabled and the architecture has 64-bit
> +``ino_t``. The daemon accepts by returning it back in the init reply.

IIUC, ino_t is now always 64-bit.

> +
> +Enabling Extended Passthrough
> +-----------------------------
> +
> +To use extended passthrough:
> +
> +  1. Follow steps 1-2 from `Enabling Passthrough`_ above. The daemon must
> +     also negotiate the ``FUSE_PASSTHROUGH_INO`` capability during
> +     ``FUSE_INIT``.
> +  2. When registering a backing file via ``FUSE_DEV_IOC_BACKING_OPEN``, set
> +     the ``ops_mask`` field in ``struct fuse_backing_map`` to declare which
> +     operations should be passed through. At minimum,
> +     ``FUSE_PASSTHROUGH_OP_GETATTR`` must be set for any inode-level
> +     passthrough.
> +  3. When handling a ``LOOKUP``, ``CREATE``, ``MKNOD``, ``MKDIR``,
> +     ``SYMLINK``, or ``LINK`` request, the daemon responds with a
> +     ``fuse_entry2_out`` (instead of ``fuse_entry_out``). To enable
> +     passthrough on the inode, set ``backing_id`` to the id returned by
> +     ``FUSE_DEV_IOC_BACKING_OPEN``. Set ``backing_id`` to 0 for inodes
> +     that should not use passthrough. The ``nodeid`` in the response must
> +     be the backing file's inode number (``i_ino``). If they don't match,
> +     the kernel rejects the passthrough setup with ``-EIO``.
> +  4. When handling an ``OPEN`` request for a FUSE file, the daemon
> +     replies with the ``FOPEN_PASSTHROUGH`` flag set in
> +     ``fuse_open_out::open_flags`` and provides the corresponding ``backing_id``
> +     in ``fuse_open_out::backing_id`` or leaves ``fuse_open_out::backing_id``
> +     blank. If the daemon would like to opt out of passthrough when the inode
> +     is already in passthrough mode, it may additionally set
> +     ``FOPEN_DIRECT_IO``, which will forward read/write operations directly to
> +     the daemon.
> +  5. The FUSE daemon should eventually call ``FUSE_DEV_IOC_BACKING_CLOSE`` with
> +     the ``backing_id`` to release the kernel's reference to the backing file
> +     when it's no longer needed for passthrough setups.
> +
> +Passthrough Operations Mask
> +---------------------------
> +
> +When registering a backing file via ``FUSE_DEV_IOC_BACKING_OPEN``, the daemon
> +sets ``ops_mask`` in ``struct fuse_backing_map`` to declare which operations
> +should be passed through::
> +
> +    FUSE_PASSTHROUGH_OP_READ
> +    FUSE_PASSTHROUGH_OP_WRITE
> +    FUSE_PASSTHROUGH_OP_READDIR
> +    FUSE_PASSTHROUGH_OP_GETATTR
> +    FUSE_PASSTHROUGH_OP_SETATTR
> +
> +Operations fall into two categories, which can be combined:
> +
> +**Inode operations** (getattr, setattr): Activated on lookup when the daemon
> +returns a ``backing_id`` in the ``fuse_entry2_out`` response. The backing
> +file reference persists for the lifetime of the fuse inode. Getattr is the
> +minimum required inode operation.
> +
> +**File operations** (read, write, readdir): Require the file to be opened with
> +``FOPEN_PASSTHROUGH`` in the daemon's open response. Read and write can be set
> +independently for partial passthrough. If only one direction is set, the other
> +falls back to direct IO and mmap is disabled.
> +
> +Extended Entry Reply
> +--------------------
> +
> +When ``FUSE_PASSTHROUGH_INO`` is negotiated, the kernel uses
> +``fuse_entry2_out`` instead of ``fuse_entry_out`` for entry responses.
> +This struct carries a ``backing_id`` and ``fuse_statx`` attributes instead
> +of ``fuse_attr``.
> +
> +When ``backing_id > 0``, the kernel associates the inode with the backing file
> +for passthrough inode operations. Statx attributes are not cached because
> +passthrough getattr fetches them directly from the backing inode.
> +
> +When ``backing_id == 0`` (no passthrough), the statx attributes from the reply
> +are cached normally.
> +
> +A negative ``backing_id`` is treated as an error. The kernel sends
> +``FUSE_FORGET`` for the returned nodeid and fails the operation.
> +
> +IO Mode State Machine
> +---------------------
> +
> +The ``iocachectr`` field in ``struct fuse_inode`` prevents conflicting access
> +modes on the same inode (page-cache I/O and passthrough I/O cannot coexist)::
> +
> +    iocachectr > 0    Cached mode
> +    iocachectr == 0   Idle. No files open, no passthrough

More accurately, no open files in caching or passthough.
files open with FOPEN_DIRECT_IO may exist and they
do not deny entering either caching or passthrough iomode.

> +    iocachectr < 0    Uncached/passthrough mode
> +
> +Each open file in passthrough mode holds one reference (``iocachectr--``).
> +The inode-level passthrough setup holds one additional long-lived reference
> +if the backing has inode ops (getattr/setattr). This long-lived reference is
> +released on inode eviction.
> +
> +Cached mode and passthrough mode are mutually exclusive. Attempting either
> +while the other is active returns ``-ETXTBSY``.
> +
> +For directories, the same mechanism arbitrates between cached readdir
> +(``FOPEN_CACHE_DIR``) and passthrough readdir. A directory opened without
> +``FOPEN_CACHE_DIR`` and without ``FOPEN_PASSTHROUGH`` is treated as direct I/O
> +and does not affect io mode.
> +
> +Things to note
> +--------------
> +
> +- ``FUSE_PASSTHROUGH_INO`` requires 64-bit ``ino_t``.

Not relevant I think.

> +- Readdirplus does not set up inode passthrough. Inodes created via readdirplus
> +  use normal FUSE operations until a fresh lookup occurs.
> +- An inode's backing association is set once and cannot be changed.
> +- Passthrough and cached I/O cannot coexist on the same inode.
> +- If any inode operations are passed through, this means all opened files need
> +  to set the ``FOPEN_PASSTHROUGH`` flag in the open response, even if reads
> +  and writes are not passed through. If reads and writes are not passed
> +  through, they will go directly to the daemon.

I had not considered this.
Setting OP_GETATTR/SETATTR in LOOKUP without any OP_RW bits
and then requiring that the server open files with FOPEN_PASSTHROUGH
with the backing_id sounds strange -
I understand why it was done this way, but for users this could be perplexing.

The thing is that if the server is only doing GETATTR passthrough there is not
really a reason to deny open file in caching mode (I think?).

The limitation that requires FOPEN_PASSTHROUGH is encoded in
fuse_file_cached_io_open()
    fuse_is_io_cache_wait()
        !fuse_inode_backing()

We have two options.

Either we decide that server passing through GETATTR and opening
files in caching mode is not interesting to support (for now) and then just
maybe we need to better document the reason that server needs to
use FOPEN_PASSTHROUGH despite not doing io passthrough.

Or we relax the limitation w.r.t cached io mode, something like this
(completely untested most likely broken based or earlier review
suggestion regarding fuse_passthrough_op):

Thanks,
Amir.

diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c
index c5f3e2201c6ab..7c33e7a974f6c 100644
--- a/fs/fuse/iomode.c
+++ b/fs/fuse/iomode.c
@@ -20,7 +20,8 @@ static inline bool fuse_is_io_cache_wait(struct inode *inode)
        struct fuse_inode *fi = get_fuse_inode(inode);

        return S_ISREG(inode->i_mode) &&
-               READ_ONCE(fi->iocachectr) < 0 && !fuse_inode_backing(fi);
+               READ_ONCE(fi->iocachectr) < 0 &&
+               !fuse_passthrough_op(inode, FUSE_PASSTHROUGH_RW_OPS);
 }

 /*
@@ -54,7 +55,7 @@ int fuse_file_cached_io_open(struct inode *inode,
struct fuse_file *ff)
         * Check if inode entered passthrough io mode while waiting for parallel
         * dio write completion.
         */
-       if (fuse_inode_backing(fi)) {
+       if (fuse_passthrough_op(inode, FUSE_PASSTHROUGH_RW_OPS)) {
                clear_bit(FUSE_I_CACHE_IO_MODE, &fi->state);
                spin_unlock(&fi->lock);
                return -ETXTBSY;
@@ -285,7 +286,8 @@ int fuse_file_io_open(struct file *file, struct
inode *inode)
         * which is already open for passthrough.
         */
        err = -EINVAL;
-       if (fuse_inode_backing(fi) && !(ff->open_flags & FOPEN_PASSTHROUGH))
+       if (fuse_passthrough_op(inode, FUSE_PASSTHROUGH_RW_OPS) &&
+           !(ff->open_flags & FOPEN_PASSTHROUGH))
                goto fail;

        /*