Reading and writing
The data path: reads, writes, sparse files, metadata updates and permissions.
read and write move bytes. set_metadata changes attributes. Everything else about
storing those bytes — layout, compression, cache, durability — is yours.
Reading
read(inode, offset, output) fills the caller's buffer and returns how many bytes it wrote:
The rules:
- Write at most
output.len()bytes. Returning a larger count isEINVALeverywhere. - A short read is fine. The kernel calls again for the rest.
- Return
0only at end of file, or whenoffsetis at or past the logical size. - An offset past EOF is a successful zero-length read, not an error.
- Reading a directory returns
EISDIR. Reading an unknown inode returnsENOENTorSTALE.
On Windows, read hydrates projected content. ProjFS keeps calling until the requested range
is full, so a backend that returns a short read and then 0 in the middle of a file produces
ERROR_HANDLE_EOF. Report the true file size from metadata as well.
Writing
write(inode, offset, input) returns the number of bytes consumed, not the new file size:
A partial write is allowed: return fewer bytes than input.len() and the caller sees a short
write and retries the rest. Most backends return everything or an error.
When the write extends the file, update size. A write in the middle does not change size,
but it does change modified_ns and should bump generation. The default write returns
ENOTSUP.
Sparse files
size is the logical length. allocated_size is the space the file really consumes, and it may
be smaller. A 1 MiB file whose bytes are all holes reports size == 1 MiB and an
allocated_size near zero.
Holes read as zeroes. If your storage does not model them, report allocated_size == size; that
is truthful, just less efficient for the volume display. Linux derives the blocks count from
allocated_size as allocated_size.div_ceil(512).
Do not report allocated_size > size unless your storage really over-allocates. Tools use the
pair to estimate compression and thin provisioning.
Attributes and permissions
set_metadata is the only mutation for attributes:
Only Some fields change. Return the complete updated metadata.
mode is permission bits only; the type lives in kind. On Linux the adapter masks mode to
0o7777. On Windows, uid and gid are ignored, and a mode with no write bits sets
FILE_ATTRIBUTE_READ_ONLY.
The Linux adapter mounts with kernel permission checks by default (MountOptions::kernel_permissions),
so mode, uid and gid are enforced by the kernel rather than by your backend. If you disable
that, your backend must check access itself and return EACCES.
Error mapping
Return a positive errno. These are the ones you will use most:
The portable constants are translated per platform; the numeric codes above pass through on
macOS and Linux. On Windows they map to the closest Win32 error, and an untranslated code
becomes ERROR_INVALID_FUNCTION. Error(0) and negative values are never valid success codes;
they surface as EPERM, EIO or ERROR_INVALID_FUNCTION depending on the platform.
A writable file with a lock
One Mutex per file is enough to serialise reads and writes without blocking other files:
Reads take the same lock. DashMap comes from the dashmap crate, as in the memory backend;
one short global mutex around namespace changes, separate from the per-file locks, keeps
cross-directory rename atomic without serialising reads.
Windows and durability
ProjFS is a projection, not a write-back filesystem. It materialises content into a
Windows-managed local cache, and local edits stay there. FSK does not call write, create,
remove or rename on Windows; the provider observes changes through raw notification events
if it asked for them.
That means a portable backend's write path only runs on macOS and Linux. Writes through a Windows mount are visible locally but are not delivered to your storage unless you handle notifications and push the cache contents yourself.
synchronize is volume-wide on every platform. If you buffer writes, flush everything there.
There is no per-file fsync callback, so treat it as a checkpoint.
Next
- Directories and lookup for namespace operations.
- Mounting and lifecycle for read-only mounts and options.
- Raw events for observing native writes on Windows.
