# virtfs Architecture `virtfs` is the embedded Go library that defines Ollie’s synthetic filesystem. It separates namespace declaration from protocol transport: `cmd/olliesrv/internal/fs/spec.go` declares the Ollie namespace, `virtfs.BuildTree` materializes it as an in-memory tree, and the 9P server exposes that tree to clients. The library is installed as part of the normal Go build. It is not a separately installed runtime service or configuration directory. The repository package at `virtfs/` is compiled into `olliesrv` by the `ninep` and `core` build targets in `Makefile`. ## Design Declarations are built with constructors and options. Handlers are plain closures that capture their target state; there is no handler-context object or later wiring pass. Dynamic directories use `Each`, whose callback returns fully bound child declarations. The resulting `*virtfs.Tree` supports the filesystem operations needed by the 9P adapter and can also be used directly in tests. ## Core Type ```go type FsNodeDecl struct { Name string Aliases []string // alternate lookup names UID string // "" = inherit from parent GID string // "" = inherit from parent Mode os.FileMode // 0 = inherit parent default Desc string // help description Stat func() os.FileInfo Children []FsNodeDecl Bindings func() ([]FsNodeDecl, error) Read func() ([]byte, error) Write func([]byte) error BlockOnce func(context.Context, string) ([]byte, string, error) Stream func(context.Context, string) ([]byte, string, error) Rdwr func(context.Context, []byte) ([]byte, error) Remove func() error Rename func(string) error } ``` A declaration is a directory when it has `Children` or `Bindings`; otherwise it is a file. `BlockOnce`, `Stream`, and `Rdwr` are mutually exclusive. Handlers capture their target state in closures. ### `DirNode(name, children_or_options...)` Declares a static directory. Arguments may be child declarations, slices of declarations, or `NodeOption` functions: ```go virtfs.DirNode("session", virtfs.FileNode("new", 0666, virtfs.Rdwr(requestSessionNew)), virtfs.FileNode("idx", 0444, virtfs.Read(readSessionIdx)), virtfs.GID("agent"), ) ``` ### `Each(name, bindingsFn, options...)` Declares a dynamic directory. The callback returns the currently available child declarations. Each returned child is already bound to its target through closures: ```go virtfs.Each("{sname}", listSessions) ``` ### `FileNode(name, mode, options...)` Declares a file node. Options provide handlers, metadata, ownership, aliases, and help text: ```go virtfs.FileNode("chat", 0444, virtfs.Doc("Agent chat log"), virtfs.Stream(streamAgentChat, agentSignal), ) ``` ## Node Options | Option | Signature | Purpose | |---|---|---| | `Read(fn)` | `func() ([]byte, error)` | Non-blocking read | | `Write(fn)` | `func([]byte) error` | Write handler | | `Stream(fn, signal)` | read callback plus signal channel | Blocking streaming read | | `StreamRaw(fn)` | `func(context.Context, string) ...` | Custom streaming read | | `BlockOnce(fn, signal)` | read callback plus signal channel | Return one changed value per open | | `BlockOnceRaw(fn)` | `func(context.Context, string) ...` | Custom blocking read | | `Rdwr(fn)` | `func(context.Context, []byte) ([]byte, error)` | Atomic write-then-read | | `RemoveNode(fn)` | `func() error` | Remove handler | | `RenameNode(fn)` | `func(string) error` | Rename handler | | `StatOverride(fn)` | `func() os.FileInfo` | Custom stat | | `UID(uid)` / `GID(gid)` | `string` | Ownership | | `Alias(names...)` | `...string` | Alternate lookup names | | `Doc(desc)` | `string` | Help description | ## Dynamic Declarations The Ollie namespace uses `Each` for runtime collections such as sessions and agents. The callback builds declarations for the current objects and captures each object in its handlers. It does not receive a context or return a generic template that is wired later. ```go virtfs.Each("{aname}", func() ([]virtfs.FsNodeDecl, error) { var out []virtfs.FsNodeDecl for _, a := range agents { agent := a out = append(out, virtfs.DirNode(agent.Name(), virtfs.Alias(agent.ID()), virtfs.FileNode("id", 0444, virtfs.Read(func() ([]byte, error) { return []byte(agent.ID() + "\n"), nil })), )) } return out, nil }) ``` ## Building and Installation `cmd/olliesrv/internal/fs.NewRoot` initializes session state, calls `buildTreeSpec`, and passes the resulting declaration to `virtfs.BuildTree`. The returned tree is mounted into the 9P server. `virtfs` is therefore a linked package, not a separately installed daemon. The repository build uses: ```sh make build # includes core and the 9P server make install # installs the resulting runtime and data files ``` The install phase copies runtime data under `~/.config/ollie` and `~/.local/share/ollie`; it does not copy the `virtfs` package or install a separate virtfs directory. ## Tree API ```go tree := virtfs.BuildTree(spec) tree.List() tree.Stat(path) tree.Open(path) tree.Create(path) tree.Delete(path) tree.Rename(oldPath, newPath) tree.Readdir(path) tree.MkdirAll(path, mode) ``` `virtfs.GenerateHelp(spec)` renders the `Doc` annotations used by the server’s help file. ## Rules 1. Directories use `Children` or `Bindings`; file handlers belong on leaf declarations. 2. `BlockOnce`, `Stream`, and `Rdwr` are mutually exclusive. 3. A template-style name such as `{sname}` must have a `Bindings` callback. 4. Ownership inherits through empty `UID` and `GID` values. 5. A zero mode inherits from the parent. Root directories default to `0755`; root files default to `0444`. ## Aliases A node may declare alternate names with `Alias(...)`. During path walks, if no child matches `Name`, the tree checks `Aliases`. This lets clients address a node by immutable ID while listings use a mutable display name. ```go virtfs.DirNode(session.Name(), virtfs.Alias(session.ID()), // ... ) ``` Aliases are invisible in directory listings. Session and agent declarations use IDs as stable aliases where needed. Clients that maintain long-lived paths should use the ID form.