Skip to content

Give the broker core mount trees and per-thread file-system contexts - #1837

Draft
Weidong Cui (wdcui) wants to merge 6 commits into
wdcui/ulitebox/guest-pid-nsfrom
wdcui/ulitebox/mount-trees
Draft

Weidong Cui (wdcui) wants to merge 6 commits into
wdcui/ulitebox/guest-pid-nsfrom
wdcui/ulitebox/mount-trees

Conversation

@wdcui

Copy link
Copy Markdown
Member

This PR gives the broker core mount trees and per-thread file-system contexts, the core half of letting guests build containers with mount namespaces, stacked on #1833. A mount namespace is now a tree of mounts, each a backend (or a bound directory or file of one) attached at a path inside its parent mount, with at most one mount per point so stacked mounts form chains as in Linux; the host's composed file system becomes the root mount of the initial namespace, so its behavior is unchanged. The resolver walks this tree from a base location (a mount plus a path inside it, anchored on an open directory handle so lookups don't re-walk its ancestors), following stacked mounts, climbing out of mount roots with .. without escaping the caller's root, restarting absolute symlinks at the root, and checking search permission as before (including, for backends that enforce it themselves, at mount points). Open files keep the mount that opened them through a stateless file-operations object, so they keep working when passed to a process in another namespace or after their mount is detached. Each thread now has a file-system context (mount namespace, root and working directory), shared by the threads that share it and copied for a new process, with a process default for requests that name no thread; queries report a handle's mount and a thread's working directory. Composers can share inode allocators (folded from #1831), so backends made later never reuse a node identity. Mount operations follow in the next PR.

Weidong Cui (wdcui) and others added 6 commits November 10, 2026 08:38
`Composer::builder_with` builds a composer whose devices come from given
allocators, and every composer now draws its virtual directories' device
from its allocators too, so composers built from the same allocators, and
backends made later from them, never share a node identity.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f5406de6-bab4-4f3b-b232-ae238032c2c2
The resolver no longer wraps one backend: it resolves paths in a mount namespace, a copy-on-write tree of mounts that each show a backend, a directory of one or a file of one, so the next steps can add per-thread roots and working directories and mount operations. A walk keeps the existing runs, symlink and permission rules, but its position is a location in a mount that starts lookups from an anchored directory handle without re-checking its ancestors, crosses into mounts it steps onto, climbs out of them on .., stops at its root, and hands its final resolution to the caller instead of walking twice. Resolver::new keeps its signature apart from losing the backend type parameter, and every existing path request still resolves from the namespace root, so hosts and shims behave as before.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f5406de6-bab4-4f3b-b232-ae238032c2c2
An open file now keeps the location it was opened at and a stateless FileOps object that builds and interprets its Platform-typed state, instead of relying on the calling process's file service. Every handle operation goes through the file's own FileOps and mount, so a file passed to a process with another file service, or one whose mount was detached, keeps working, and each open file counts as a user of its mount until its last reference closes. Opens still go through the process's service; mount namespaces hold the FileOps their files get.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f5406de6-bab4-4f3b-b232-ae238032c2c2
A walk stepping onto a mount point checked search permission on the directory only when the resolver enforces permissions, so a backend that enforces them itself, such as a 9P server, never saw the lookup and a denied directory above a mount point could be crossed. The walk now looks the mount point's name up in its directory's backend before entering the mount, alone rather than at the end of a longer walk, because a 9P server reports a denied final lookup of a walk only as a shorter walk.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f5406de6-bab4-4f3b-b232-ae238032c2c2
Each thread now has a file-system context holding its mount namespace, root and working directory, which threads of a process share and a new process copies from the thread that creates it, while a root process starts at the root of its file service's namespace. The process keeps its initial thread's context as a default for requests that name no thread, even after that thread exits, until the process ends. Path operations resolve from a context and, for relative paths, an optional open directory, and open files through the namespace's FileOps; the existing absolute-path functions use the default context, so hosts behave as before. Every context is registered in the core for the mount operations that will move roots and working directories.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f5406de6-bab4-4f3b-b232-ae238032c2c2
Add the queries the Linux shim needs on top of mount trees and contexts. mount_info reports the mount an open file is on, which the file keeps after an unmount or when passed to another process: its identifier, the type, source and options it was made with, whether it is read-only, and whether the file is the mount's root. working_directory reports a thread's working directory as a path from its root, or, when it is not below the root, as Linux's getcwd does with its unreachable marker, its path from the root of its own mount tree. The mount module becomes public for the identifier and information types, as the PID namespace module is.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f5406de6-bab4-4f3b-b232-ae238032c2c2
@github-actions

Copy link
Copy Markdown

ℹ️ Note: This semver check was run against the wdcui/ulitebox/guest-pid-ns branch, not main or ulitebox.

🤖 SemverChecks 🤖 ⚠️ Potential breaking API changes detected ⚠️

Click for details
--- failure auto_trait_impl_removed: auto trait no longer implemented ---

Description:
A public type has stopped implementing one or more auto traits. This can break downstream code that depends on the traits being implemented.
        ref: https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.51.0/src/lints/auto_trait_impl_removed.ron

Failed in:
  type Resolver is no longer UnwindSafe, in /home/runner/work/litebox/litebox/litebox_broker_core/src/fs/resolver.rs:43
  type Resolver is no longer RefUnwindSafe, in /home/runner/work/litebox/litebox/litebox_broker_core/src/fs/resolver.rs:43

--- failure type_allows_fewer_generic_type_params: type now allows fewer generic type parameters ---

Description:
A type now allows fewer generic type parameters than it used to. Uses of this type that supplied all previously-supported generic types will be broken.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#trait-new-parameter-no-default
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.51.0/src/lints/type_allows_fewer_generic_type_params.ron

Failed in:
  Struct ResolverEntry allows 1 -> 0 generic types in /home/runner/work/litebox/litebox/litebox_broker_core/src/fs/resolver.rs:1480
  Struct Resolver allows 2 -> 1 generic types in /home/runner/work/litebox/litebox/litebox_broker_core/src/fs/resolver.rs:43

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant