ollie/doc/architecture-virtfs.md

157 lines
6.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.