2.2 Namespace and Metadata Operations
| 中文 | English | Contents |
Chapter 2: FUSE Implementation Analysis · Article 2 of 6
This article follows fs/fuse/dir.c through pathname lookup, inode instantiation, attribute refresh, create, unlink, and rename. The central issue is keeping VFS state and daemon state consistent across a message boundary.
1. Begin at the inode operation table
A normal directory maps VFS methods to FUSE entry points such as:
.lookup -> fuse_lookup
.create -> fuse_create
.unlink -> fuse_unlink
.rename -> fuse_rename2
.getattr -> fuse_getattr
.setattr -> fuse_setattr
Different inode types use different tables, while much of the attribute machinery is shared. The table tells you the VFS contract before you inspect protocol details.
2. LOOKUP call chain
VFS path walk
-> fuse_lookup(dir, dentry, flags)
-> fuse_lookup_name(sb, parent_nodeid, name, ...)
-> fuse_lookup_init(args, parent_nodeid, name, out)
-> fuse_simple_request()
-> daemon: FUSE_LOOKUP
-> validate fuse_entry_out
-> fuse_iget() / update attributes
-> d_splice_alias() or negative dentry
The reply establishes identity, a lookup reference, an entry lease, and an attribute lease in one transaction.
3. Reply validation
The kernel treats daemon output as untrusted. It checks, among other conditions:
- valid node ID and special-ID semantics;
- inode type compatibility with an existing identity;
- safely representable mode, size, and attribute fields;
- stable generation and type for reused objects;
- response length compatible with the opcode and version;
- legal alias relationships, especially for directories.
A malformed reply is a protocol error, not a cacheable negative lookup.
4. Positive and negative dentries
A successful lookup instantiates or reuses an inode and adds a lookup reference that the daemon must account for. ENOENT can produce a negative dentry with its own validity duration.
Two leases remain independent:
- entry validity: whether the parent/name relationship is reusable;
- attribute validity: whether metadata of the resolved inode is reusable.
An expired attribute lease can cause GETATTR without repeating name lookup. An expired entry lease may require confirming that the name still identifies the same object.
5. Inode instantiation and attribute updates
FUSE finds or creates a VFS inode from node identity, converts fuse_attr, and updates cached state. File size is especially delicate:
- shrinking must coordinate with page-cache truncation;
- an old daemon reply must not overwrite size advanced by a local write;
- mmap, writeback, and direct I/O can observe size concurrently;
- attribute versions and invalidation state prevent stale response overwrite.
The implementation is consequently much more than assigning reply fields to struct inode.
6. GETATTR
fuse_getattr() first decides whether the attribute cache is still valid. If not, it sends FUSE_GETATTR, possibly including an open fh, then applies the returned attributes before filling the caller’s kstat.
vfs_getattr
-> fuse_getattr
-> valid cache: generic_fillattr
-> otherwise FUSE_GETATTR
-> fuse_change_attributes()
-> generic_fillattr
7. SETATTR and truncate
chmod, chown, timestamp changes, and truncate can converge on fuse_setattr(). It translates VFS iattr valid bits into fuse_setattr_in, sends FUSE_SETATTR, and reconciles the reply.
A truncate sequence must coordinate:
- permission and writer-state checks;
- page cache, writeback, and direct-I/O exclusion;
- backend size modification;
- response attribute adoption and cache truncation;
- a coherent state if the remote operation fails.
8. Combined create and open
fuse_create() can use fuse_create_open() and FUSE_CREATE to receive both a new fuse_entry_out and fuse_open_out. This avoids a second OPEN round trip.
Combining operations complicates rollback. If the backend created the entry and handle but local file initialization later fails, the kernel must release the handle and balance the new lookup reference. Compatibility paths must also preserve O_EXCL and atomic-create semantics.
9. Unlink has multiple lifetimes
fuse_unlink() sends the parent node ID and name. On success, the VFS namespace entry disappears and related caches are updated, but the underlying object may remain alive because:
- another hard link exists;
- an open
fhremains usable; - mmap and dirty pages still hold state;
- lookup references are dropped later through FORGET;
- the backend has its own references.
10. Rename and invalidation
fuse_rename2() uses FUSE_RENAME2 for supported flags and can fall back to the older opcode. One request names old parent/name and new parent/name.
After success, code must reconcile both parent directories, source and target dentries, an overwritten target inode, and cross-directory parent relationships. EXCHANGE, NOREPLACE, and WHITEOUT add distinct cases.
The daemon’s backend operation must itself be atomic. Kernel dentry locks cannot turn a two-step remote implementation into an atomic rename.
11. FORGET accounting
When VFS cache references disappear, FUSE queues FORGET or BATCH_FORGET. These messages normally receive no reply. They reduce daemon lookup counts, but do not close live file handles.
When diagnosing leaks, count separately:
- lookup increments from successful LOOKUP/CREATE replies;
nlookupdecrements in FORGET;- OPEN/RELEASE handles;
- backend references.
12. Debugging method
Record parent node ID, name, opcode, unique, response node ID/generation, entry and attribute lease, and final dentry/inode. A pathname string alone loses identity after rename, aliasing, and unlink.
13. Summary
dir.c converts VFS name operations into protocol transactions and replies into dentry/inode state. LOOKUP establishes identity, lookup accounting, and two leases. Create, unlink, and rename must preserve atomicity and invalidation ordering across kernel and daemon state.
| Previous: Source Map, Mount, and Initialization | Next: Open, Read, Write, and Writeback |