1.2 Object Model and Protocol
| 中文 | English | Contents |
Chapter 1: FUSE Subsystem Design · Article 2 of 4
Correct FUSE implementations depend on understanding three different object systems at once: VFS objects, kernel FUSE objects, and daemon-owned identities. They overlap, but none is a one-to-one replacement for another.
1. Three layers of objects
1.1 VFS objects
The VFS presents familiar structures:
super_block: a mounted filesystem instance;dentry: a name-to-inode relationship, including negative entries;inode: metadata and operation tables for a filesystem object;file: one open file description;address_space: cached file data and its I/O operations.
Their lifetimes are controlled by VFS references, not by FUSE protocol messages alone.
1.2 Kernel FUSE objects
The kernel adds protocol-specific state such as:
- the connection and mount objects;
- per-inode FUSE metadata;
- per-open-file state and daemon file handles;
- request objects and input/output argument vectors;
- pending, interrupt, and processing queues.
These objects bridge VFS lifetimes to message lifetimes.
1.3 Daemon identities
The daemon returns identifiers that have protocol meaning:
nodeid: identity of a filesystem node within the connection;generation: distinguishes reuse of a node ID;fh: daemon-defined handle returned byOPENorOPENDIR;unique: identifier of one request, used to match its reply.
A nodeid is not a pointer, a Linux inode number, or an open handle. An fh is not a nodeid. A unique lasts for one protocol transaction only.
2. Namespace lifetime and FORGET
A successful LOOKUP gives the kernel a lookup reference on the returned node. The kernel can accumulate multiple references while dentries are cached. When it no longer needs them, it sends FORGET or BATCH_FORGET.
Important consequences:
FORGETis normally one-way and has no ordinary reply;- the daemon must account for the
nlookupdecrement correctly; - forgetting a lookup identity does not imply that every open handle is closed;
- an unlinked but open file may remain reachable through its
fh; - node ID reuse requires correct generation handling.
A daemon that treats FORGET as RELEASE will eventually corrupt its lifetime model.
3. Protocol envelope
Every request begins with struct fuse_in_header, which includes message length, opcode, unique ID, node ID, caller UID/GID/PID, and related context. Most replies begin with struct fuse_out_header, which contains length, error, and the matching unique ID.
request = fuse_in_header + opcode-specific input + optional payload
reply = fuse_out_header + opcode-specific output + optional payload
The protocol is binary. Structure layout, feature negotiation, length validation, and compatibility rules matter as much as the logical operation.
4. Kernel request representation
The kernel does not need to flatten every message immediately. It describes input and output with argument vectors, together with request flags such as whether a reply is expected or output data is variable length. Transport code then copies or maps the described buffers.
This separation allows one logical request representation to support classic device I/O, io_uring delivery, and other channels.
5. Operation classes
The opcode space covers several classes:
- namespace:
LOOKUP,CREATE,MKNOD,MKDIR,UNLINK,RENAME; - metadata:
GETATTR,SETATTR,ACCESS, xattr operations; - open lifetime:
OPEN,OPENDIR,RELEASE,RELEASEDIR,FLUSH; - data:
READ,WRITE, fallocate, copy-file-range; - cache control:
FORGET, invalidation notifications; - connection control:
INIT,DESTROY,INTERRUPT; - specialized paths: mapping operations used by DAX and newer extensions.
Not every request expects a reply. That distinction must be explicit in both kernel and daemon logic.
6. FUSE_INIT: protocol negotiation
The mount is not ready for normal operation until the kernel and daemon exchange FUSE_INIT.
Negotiated information includes:
- protocol major and minor versions;
- supported feature flags;
- maximum write and readahead sizes;
- background queue and congestion limits;
- time granularity and alignment constraints;
- support for writeback cache, parallel directory operations, DAX, passthrough, security context, io_uring transport, and other version-dependent capabilities.
Feature flags are a contract. A daemon must not advertise a capability and then implement only its happy path.
7. Example: LOOKUP reply
A successful LOOKUP returns a fuse_entry_out containing:
nodeidandgeneration;- entry validity duration;
- attribute validity duration;
- the
fuse_attrpayload.
The kernel uses these values to instantiate or refresh both namespace and inode state. A negative lookup can also be cached when the daemon returns ENOENT with a configured validity interval.
8. Example: open identity versus node identity
Suppose two processes open the same FUSE inode. The VFS may use one inode object, while the daemon returns two different fh values. Per-open flags—such as direct I/O or keep-cache behavior—may differ. Later READ, WRITE, FLUSH, and RELEASE operations can carry the relevant fh even though their nodeid is identical.
This distinction is essential for backends with sessions, credentials, remote descriptors, or per-open locks.
9. Protocol invariants
A robust implementation preserves at least these invariants:
- Every reply is matched to exactly one live request by
unique. - The reply opcode payload and length agree with the original request.
- Node identity remains stable for the advertised lifetime.
- Lookup counts and open-handle counts are accounted independently.
- Negotiated limits are honored on both input and output.
- Late replies, interrupted requests, and connection teardown cannot complete the same request twice.
- User-controlled lengths and offsets are validated before copying or mapping.
10. Summary
VFS objects, FUSE kernel state, and daemon identifiers form three related lifetime systems. nodeid, generation, fh, and unique answer different questions. The binary protocol joins those systems, while INIT defines which optional behaviors are legal for the lifetime of the connection.
| Previous: Boundary and Overall Architecture | Next: Caching and Coherency |