4.5.4 Removal

stratafs has no whiteouts and cannot record that a name should be absent. Removal can therefore only remove an entry that is actually there, in a stratum it may write to.

4.5.4.1 Unlinking #

To remove a non-directory name from a merged directory: if the provider accepts modification (§4.5.1), the entry is removed from the provider; otherwise the operation fails with EROFS. The parent is resolved in the provider's stratum, and the unlink is performed there.

The provider need not be the create stratum. Any stratum that accepts modification may have an entry removed from it, by the same rule that allows an object it provides to be modified in place.

Where a lower-precedence stratum also holds the name, that entry becomes the provider once the higher one is removed, and the name remains visible, now resolving to the lower stratum's object. That is not an error and is not reported as one: the removal succeeded, and the entry it removed is gone. The dentry is dropped on success, so the next lookup finds the lower entry.

Removing an object that a lower stratum also provides is how a modification is undone. Where a file was copied up in order to be edited, removing it through the mount discards the edit and restores the original, which remained untouched in its own stratum throughout. Callers expecting POSIX removal will find the name still present afterwards; §4.8 records this as an intended divergence.

Refusal because the provider does not accept modification is EROFS, with one exception: where the provider's inode is immutable it is EPERM. The outer inode carries the provider's inode flags, so the VFS refuses the removal before stratafs's own EROFS test is reached — and EPERM is what every filesystem returns for an immutable file, so the distinction is the useful one rather than an accident worth papering over. The ro flag and a read-only provider mount both still produce EROFS.

4.5.4.2 Removing directories #

The same rule applies, with one additional condition: the merged directory must be empty. A directory is empty for this purpose only if no participating stratum holds any entry within it, so a directory that is empty in the provider but not in another participant fails with ENOTEMPTY.

Because determining this reads the contents of every participating stratum, the caller must hold traverse and list rights on each of them. That check completes over all participants before any of them is enumerated, so a refusal yields EACCES without disclosing whether the directory was empty. Without that ordering the emptiness test would be a disclosure channel: a caller who may not enumerate a protected participant could learn whether it contains anything by attempting rmdir and distinguishing ENOTEMPTY from success.

The emptiness scan filters staging entries, as enumeration does, so an in-flight copy-up in the create stratum does not make rmdir fail on a directory that looks empty through the mount.

Because it filters them, a directory holding nothing but orphaned staging entries reports empty — and the removal would then fail at the provider, on entries the caller cannot see and has no way to remove. So a scan that finds the directory empty but saw staging entries runs orphan recovery on the create stratum's copy of it before returning (§4.5.2). Only entries belonging to no live mount are removed; one owned by a live mount survives, and the provider's rmdir then fails ENOTEMPTY, which is the right answer while another mount is copying up there.

Where the directory is removed and lower strata hold the same name as a directory, the name remains visible as a merged directory of the remaining strata — which, by the emptiness condition, is empty.

4.5.4.3 What removal never does #

Removal touches exactly one stratum, the provider's. The other strata are opened read-only for the emptiness scan and nothing else. No entry, marker, or object is created in any stratum to suppress a lower entry; no whiteout machinery exists anywhere in the filesystem.

Success is never reported for a name whose provider entry was not removed. The one place zero is returned without a removal is the deferred path of §4.5.3, where the entry the descriptor named is already gone and the deletion is genuinely complete.

Edit this page