4.3.1 Lookup
Resolution is defined for one name in one directory. Every path operation is a sequence of such resolutions, each independent of the last.
4.3.1.1 The provider #
To resolve a name in a stratafs directory, the corresponding directory
of each stratum is examined in precedence order, and the first stratum
holding an entry of that name is its provider. If no stratum holds
it, the resolution produces a negative dentry and the VFS reports
ENOENT.
Mechanically, each stratum's path string is joined with the relative
path of the name and walked in full, once per stratum. A walk that
succeeds sets that stratum's bit in a presence bitmap; a walk that
fails with ENOENT or ENOTDIR leaves it clear and the stratum is
skipped. Selecting the provider is then the trailing-zero count of the
bitmap, computed in stratafs-core.
Any other error from a stratum's walk — EACCES, EIO, ELOOP,
ENAMETOOLONG, ESTALE — is not treated as absence. It fails the
whole resolution, so a stratum that is unreadable for a reason other
than not being there masks the name entirely rather than being passed
over.
A joined stratum path, or a child relative path, that would exceed
PATH_MAX fails with ENAMETOOLONG.
4.3.1.2 Ancestors #
Resolution does not consult a parent's provider to find a child's. A
name's provider is chosen afresh across all strata, so if /a is
provided by stratum 2, /a/b may still be provided by stratum 1 —
provided stratum 1 also holds /a as a directory and the two therefore
merge (§4.3.2).
What resolution does consult is whether any ancestor of the path is
masked. Before resolving the final component, every proper prefix of
the relative path is resolved across all strata and its merged provider
computed; a prefix whose provider is not a directory aborts the whole
resolution with ENOTDIR. That is what makes masking total (§4.3.3),
and it is why a lookup costs one full walk per stratum for the name
itself plus one merged resolution per path component above it.
4.3.1.3 Independence from the caller #
Resolution runs under the credentials captured at mount, against the root captured at mount, and takes no operation argument. It does not depend on the calling token, on what the caller is trying to do, or on whether the operation will ultimately be permitted.
A name whose provider the caller may not access therefore resolves normally and is then refused. It does not fall through to a lower stratum — which would let a caller's rights change which file they read, a considerably worse property than a denial.
4.3.1.4 Reaching the object #
Once a name resolves to a non-directory provider, the object is the
provider's object and stratafs does not interpose on its contents. The
outer inode takes the provider's mode and, from it, the operations
tables for a regular file, symlink or special file; reads, writes,
mappings, splices, locks and ioctls are forwarded to a backing file
opened on the provider.
Symbolic links are forwarded rather than followed. The outer inode's
get_link calls the provider inode's own and returns the raw target
verbatim; stratafs deliberately does not use vfs_get_link, which
would demand a read right the caller need not hold to traverse a link.
The VFS then interprets the target in the caller's own namespace, so an
absolute target resolves from the process's root and may re-enter this
mount, another stratafs mount, or none.
Where a mount is established at a path within a stratum, stratafs
follows it: the stratum walk is an ordinary filename_lookup with no
flag restricting it to one filesystem, and everything downstream
operates on the inner mount it returns.
4.3.1.5 Staging entries #
One class of name is invisible to resolution. While a copy-up is in flight, its staged object may exist under a name in the create stratum; that name is dropped from resolution for the mount that owns it, so an incomplete copy is never reachable through the merged view (§4.5.2). The suppression applies only to the create stratum, and only within the owning mount — a second stratafs mount sharing that directory, and any direct reader of it, sees an ordinary entry.
Staged names begin with .stratafs-stage-, and a lookup of any name
with that prefix triggers a recovery scan of the create-stratum parent
before resolving. A resolution can therefore have the side effect of
removing orphaned staging entries from the create stratum.
4.3.1.6 Recursion #
A task that is already resolving inside a superblock and re-enters the
same superblock fails immediately with ELOOP. The guard is a global
list of task-and-superblock pairs, not a depth counter, so a cycle
formed after mount — a stratafs mount established inside one of its own
strata, or bind-mounted into one — terminates the moment resolution
returns to a mount it is already inside.