func New
New returns a tree whose root directory is named name, owned by uid, using clock (normally the block height) as every node's mtime. clock may be nil, in which case the mtime is zero.
Package synfs builds a synthetic, read-only ninep file server whose contents are computed by a function at read time.
gno.land/p/moul/x/plan9/synfs/v0A synthetic, read-only file server whose contents are computed at read time. This is the package that makes the rest of the suite adoptable.
1import synfs "gno.land/p/moul/x/plan9/synfs/v0"
2
3t := synfs.New("dev", "sys", func() int64 { return runtime.ChainHeight() })
4t.Root().
5 Add("sysname", func() string { return runtime.ChainID() }).
6 Add("height", func() string { return strconv.FormatInt(runtime.ChainHeight(), 10) })
In Plan 9 a device is not storage, it is code behind a name: reading /dev/time
runs a function. A realm that wants to publish its state as a browsable tree
does the same thing here, in a few lines, and gets ls, cat, stat and
mountability for free.
Read-only by construction. The tree implements ninep.File and not
ninep.Mutable, so every method is free of side effects and the tree is safe to
hand to a namespace owned by somebody else. That is the property the whole
cross-realm mount story rests on.
Two details worth knowing:
Qid.Version at 0 forever. Its contents can change
on every block, so a version would be a lie; a client that needs change
detection should read the file.AddRange serves the 9P read window itself, for a file with no natural
end. /dev/zero uses it: an unbounded read returns nothing rather than an
endless value, which is what stops cat /dev/zero from being a denial of
service.Live demo: r/moul/x/plan9/dev
publishes the chain itself as a device tree. Design and analysis:
moul/gno-contracts#136.
Not affiliated with Plan 9. Plan 9 from Bell Labs is the work of the Computing Science Research Center at Bell Labs; the name and the marks are theirs, and the copyright is held by the Plan 9 Foundation. This package borrows the vocabulary and none of the code: it is an independent homage, asking what that ecosystem's spirit looks like on a chain. Full attribution: NOTICE.
Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.
Dependency graph:

🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.
Package synfs builds a synthetic, read-only ninep file server whose contents are computed by a function at read time.
This is the package that makes the rest of the suite adoptable. Plan 9's device drivers are not storage, they are code behind a name: reading /dev/time runs a function. A realm that wants to publish its state as a browsable tree does the same thing here, in a few lines, and gets ls, cat, stat and mountability for free:
1t := synfs.New("dev", "g1...", func() int64 { return runtime.ChainHeight() })
2t.Root().
3 Add("sysname", func() string { return runtime.ChainID() }).
4 Add("height", func() string { return strconv.FormatInt(runtime.ChainHeight(), 10) })
The tree is READ-ONLY by construction, which is what makes it safe to hand to another realm's namespace: every method is free of side effects, so it can be called from a frame that does not own these objects.
A synthetic file reports Qid.Version 0 forever. Its contents can change on every block, so a version would be a lie; clients that need change detection should read the file.
NOTICE. Plan 9 from Bell Labs is the work of the Computing Science Research Center at Bell Labs; the name and the marks are theirs, and the copyright is held by the Plan 9 Foundation (https://p9f.org). This package is not affiliated with, endorsed by, or sponsored by them, and contains no Plan 9 code: it borrows the vocabulary so that the design reads without a glossary, and it is an homage, asking what that ecosystem's spirit looks like on a chain. Full attribution: NOTICE.md at the root of moul/gno-contracts.
Dir is a synthetic directory. Its children are held in an avl tree, so a listing is ordered by name and identical on every node.
Add attaches a read-only file computed by fn, mode 0444. It returns the directory, so calls chain.
AddDir attaches a subdirectory built with Tree.NewDir.
AddFile attaches any ninep.File under the given name, which is how a synthetic tree splices in a tree served by something else.
AddPerm is Add with an explicit mode.
AddRange attaches a file that serves a read window itself. Use it for a file with no natural end, where materialising the whole thing would be wrong.
Names returns the directory's entries, in order. Useful for a file that wants to describe its own directory, as /dev/drivers does.
Read implements ninep.File.
ReadDir implements ninep.File.
Stat implements ninep.File.
Walk implements ninep.File.
RangeFn serves a 9P read window directly, for files that are cheaper (or only possible) to generate a slice at a time, such as an endless one.
ReadFn computes a whole file.
Tree owns the qid allocator, the clock and the owner name shared by every node in one synthetic server.