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

Amir Goldstein <[email protected]> Sat, 16 May 2026 22:59:30 +0200
Newsgroups org.kernel.vger.linux-unionfs,dev.linux.lists.fuse-devel
Message-ID <CAOQ4uxg4+6s094=ka7xG+Rjz2A6amyrSOr6C0QpYQ_iyrR4f+Q@mail.gmail.com>
On Sat, May 16, 2026 at 8:23 PM Amir Goldstein <[email protected]> wrote:
>
> 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);
>  }

Obviously, this was naive and not close to enough, because
fuse_inode_set_passthrough() still takes the negative iocachectr
refcount.

Please ignore this. Supporting caching iomode and GETATTR
passthrough is left for a future exercise, if anyone is ever interested.

Thanks,
Amir.