Implementing a filesystem
The required methods, the defaulted mutations, and the contracts behind them.
Filesystem has five required methods and ten defaulted ones. A read-only backend implements
the first group and stops; a writable backend overrides methods from the second group one at a
time.
The required five
These signatures are the whole read-only surface:
statfs
Report capacity and inode counts for the volume. The values are advisory and only used for
display and free-space decisions. Return plausible numbers rather than zeroes; a zero
block_size is clamped to 1 on Linux, and some tools divide by it.
Windows never calls this method, so a Windows mount has no capacity display. Report file size
through Metadata::size instead.
metadata
Return the attributes of one inode. The kernel can ask for any inode it has observed, with no
preceding lookup, so this must not depend on a "current directory listing" cache. Return
Error::STALE when the inode is no longer valid, and Error(2) (ENOENT) only when it never
existed.
Every mutation must leave metadata consistent: new size, new modified_ns, bumped
generation if the object changed identity, and an updated parent directory.
lookup
Resolve one name inside one directory and return its inode and kind. parent must be a
directory; return ENOTDIR if it is not. The kind you return is cached by the kernel, so it
must match metadata(inode).kind.
Names are never . or .., and never contain a separator. You do not need to normalise them.
read_directory
Stream entries into the sink, starting at cookie. cookie == 0 starts an enumeration. Stop
when push returns false, and return the cookie of the last entry that fitted as
next_cookie. Set eof only when there is nothing left.
Directories and lookup covers cookies, verifiers and pagination with
a worked example.
read
Fill output from offset and return the number of bytes written. Return at most
output.len() bytes; a short read is allowed, and 0 means end of file. Reporting more than
the buffer holds is rejected with EINVAL by every adapter.
Offset is a u64, so clamp it before converting: usize::try_from(offset) fails on 32-bit
targets for large offsets, and an offset past the end of the file is a zero-length read, not an
error.
Mutations are opt-in
Every other method has a default. The default is usually Error::NOT_SUPPORTED, which the
adapters translate to ENOTSUP on the platform.
Implementing a method that only half works is worse than leaving it out. The kernel assumes a successful return means the operation took effect.
Partial metadata updates
set_metadata receives only the fields the caller wants to change:
Apply every Some field, leave the rest alone, and return the complete updated Metadata.
size is a truncate: grow with zeroes or shrink the data, and update both size and
allocated_size. mode is already masked to permission bits on some platforms, but masking
with & 0o7777 yourself is harmless.
There is no created_ns field. Creation time is not settable.
Creating and removing entries
create(parent, name, kind, mode) handles regular files and directories. Return the new inode,
and fail with Error(17) (EEXIST) if the name is taken. kind is File or Directory;
requests for Symlink go through create_symlink instead.
remove(parent, name, inode) receives the inode the caller last observed. Use it to check that
the name still refers to the same object before deleting: if dir.entries.get(name) is not
Some(&inode), return ENOENT. Check for a non-empty directory before removing anything, and
update the parent's modified_ns and directory verifier on success.
A directory with entries must fail with DIRECTORY_NOT_EMPTY. Removing the last name of a file
may free the inode, but only once the kernel can no longer reference it.
Rename and replacement
replaced_inode is Some(inode) when the kernel expects an atomic replacement of an existing
entry, and None when the destination must not exist. Check both cases: return EEXIST for
None plus an existing name, and remove the replaced object for Some.
Replacing a non-empty directory must fail. A rename that changes the parent must be visible
through lookup from both directories and must update both parents' modified_ns and
directory verifiers.
Linux only forwards RENAME_NOREPLACE; RENAME_EXCHANGE and whiteout flags are rejected with
EINVAL before your backend sees them.
Symlinks
create_symlink(parent, name, target, mode) stores target verbatim; it is not resolved.
read_symlink(inode, output) copies the stored bytes into output and returns their length.
Do not append a NUL terminator.
If the buffer is too small, return an error rather than a truncated target. The memory backend
uses ERANGE; Error::INVALID also works if you do not want to depend on a platform code.
Synchronisation
synchronize is volume-wide. Adapters call it for fsync, fsyncdir and sync. The default
no-op is correct when a write is durable before write returns.
If you buffer, flush everything and report the failure. There is no per-inode fsync callback and no way to know which file the caller intended, so treat it as a checkpoint for the whole backend.
A writable backend skeleton
The shape below is enough for files and directories. It keeps all state behind one RwLock;
the memory backend shards the map and locks each node so independent files do not
contend.
Locking the whole map is fine for small trees. Before you copy it, decide how create,
rename and set_metadata update: the inode's own metadata, the parent's modified_ns, and
the directory's verifier.
Checklist before mounting
ROOT_INODEresolves, and itsparentis itself.- Every inode returned by
lookuphas matchingmetadata, includingkind. readnever writes pastoutput.len()and returns0at EOF.read_directoryadvancesnext_cookieon every page and setseofonly when done.- Directory verifiers change on every mutation the backend performs.
- Errors are positive; unsupported operations keep the default.
Next
- Directories and lookup for cookies, verifiers and pagination.
- Reading and writing for data paths and permissions.
- Network filesystems for lazy remote trees.
- Examples for the full writable implementation.
