Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

v0 source pure

Package ns is a Plan 9 namespace: a private, mutable mount table over gno.land/p/moul/x/plan9/ninep file trees.

Readme View source

gno.land/p/moul/x/plan9/ns/v0

A Plan 9 namespace: a private, mutable mount table over ninep file trees, with bind(2)'s flags and union directories.

1import ns "gno.land/p/moul/x/plan9/ns/v0"
2
3n := ns.New(fs.Root())
4n.Bind("/usr/glenda/bin", "/bin", ns.MAFTER|ns.MCREATE)
5n.ReadDir("/bin", false)   // the concatenation of both directories

This is the idea the rest of the suite exists for. Plan 9's leverage does not come from "everything is a file", it comes from every process owning its own name-to-resource mapping, so a name can be replaced and any service can be composed, shadowed, sandboxed or mocked without the program knowing. Gno has one global tree of realm paths that looks the same to everybody; an Ns is a view of it that belongs to you.

Flags follow bind(2): MREPL replaces, MBEFORE splices the new directory in front of the old one, MAFTER behind it, and MCREATE (OR'd onto any of them) marks the union member that new files are created in.

Four Plan 9 properties are load-bearing and reproduced deliberately:

  • A union is top level only. Binding /a onto /b unions the two directories at /b; /b/c resolves in whichever member won the walk and is not itself a union unless something is bound there too.
  • A bind captures a channel, not a name. The source is resolved once, at bind time, and the file it named is what gets stored. Rebinding the source afterwards does not retroactively move the target.
  • A listing is a concatenation, duplicates and all, so shadowing stays visible. Pass unique to collapse it first-wins instead.
  • Creating in a union needs MCREATE on some member, and is refused otherwise. A plain, unbound directory takes creates with no flag at all.
  • The target of a bind must already exist, exactly as bind(1) requires.

MaxUnion (8), MaxOps (64) and ninep.MaxDepth (32) bound resolution, because each element of each member can cost a cross-realm call.

String() prints the namespace in ns(1) format: one line per binding, in application order, then the working directory.

Live demo: r/moul/x/plan9/ns gives every account one of these. 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:

gno.land/p/moul/x/plan9/ns/v0 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.

Overview

Package ns is a Plan 9 namespace: a private, mutable mount table over gno.land/p/moul/x/plan9/ninep file trees.

This is the idea the rest of the suite exists for. Plan 9's leverage does not come from "everything is a file", it comes from every process owning its own name-to-resource mapping, so a name can be REPLACED and any service can be composed, shadowed, sandboxed or mocked without the program knowing. Gno has one global tree and no way to hold a view of it; an Ns is that view.

Bind flags follow bind(2): MREPL replaces, MBEFORE splices the new directory in front of the old one, MAFTER behind it, and MCREATE (OR'd onto any of them) marks the union member that new files are created in.

Two Plan 9 properties are load-bearing and reproduced deliberately:

  • A union is TOP LEVEL ONLY. Binding /a onto /b unions the two directories at /b; /b/c resolves in whichever member won the walk, and is not itself a union unless something is bound there too.
  • A bind captures a CHANNEL, not a name. The source is resolved once, at bind time, and the resulting file is what is stored. Rebinding the source afterwards does not retroactively move the target.

".." is removed lexically before resolution starts and never reaches a server, per "Lexical File Names in Plan 9, or Getting Dot-Dot Right".

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.

Constants 2

const MaxUnion, MaxOps

1const (
2	MaxUnion = 8 // members in one union
3	MaxOps   = 64
4)
source

Limits. Resolution costs one call per element per union member, and those calls may cross a realm boundary, so both are capped rather than trusted.

const MREPL, MBEFORE, MAFTER, MCREATE

1const (
2	MREPL   Flag = 0x0000 // replace the old file with the new one
3	MBEFORE Flag = 0x0001 // union, new directory first
4	MAFTER  Flag = 0x0002 // union, new directory last
5	MCREATE Flag = 0x0004 // creates in this union go here
6)
source

Mount flags, with Plan 9's values.

Variables 1

var ErrNoRoot, ErrWideUnion, ErrTooManyOps, ErrNotBound

 1var (
 2	// ErrNoRoot means the namespace has nothing bound at "/".
 3	ErrNoRoot = errors.New("no root in namespace")
 4	// ErrWideUnion means a bind would exceed MaxUnion members.
 5	ErrWideUnion = errors.New("union too wide")
 6	// ErrTooManyOps means the namespace has reached MaxOps bindings.
 7	ErrTooManyOps = errors.New("too many namespace operations")
 8	// ErrNotBound means unmount was asked to remove something that is not
 9	// bound at that name.
10	ErrNotBound = errors.New("not bound")
11)
source

Functions 1

func New

1func New(root ninep.File) *Ns
source

New returns a namespace whose root is the given file. The root is the one binding that cannot be expressed as a bind of something else, so it is installed directly.

Types 3

type Flag

ident
1type Flag uint32
source

Flag is a bind(2) mount flag. The low two bits pick the mode; MCREATE is OR'd onto it.

Methods on Flag

func String

method on Flag
1func (f Flag) String() string
source

String renders the flag as the command-line letters bind(1) uses.

type Ns

struct
1type Ns struct {
2	table *avl.Tree // cleaned target path -> *entry
3	ops   []Op
4	cwd   string
5}
source

Ns is one namespace: a mount table, a working directory, and the ordered list of operations that produced them.

Methods on Ns

func Abs

method on Ns
1func (n *Ns) Abs(p string) string
source

Abs resolves p against the working directory.

func Bind

method on Ns
1func (n *Ns) Bind(source, target string, flag Flag) error
source

Bind makes source visible at target, as bind(1) does. Both names are resolved in THIS namespace, and the source is resolved once: what is stored is the file it names today, not the name.

func BindVerb

method on Ns
1func (n *Ns) BindVerb(verb, source, target string, flag Flag) error
source

BindVerb is Bind with the verb that String should print. Plan 9 spells the same operation "bind" or "mount" depending on whether the source is a name or a channel, and ns(1) echoes back whichever was used.

func Cd

method on Ns
1func (n *Ns) Cd(p string) error
source

Cd sets the working directory, which must resolve to a directory.

func CreateTarget

method on Ns
1func (n *Ns) CreateTarget(p string) (ninep.Mutable, error)
source

CreateTarget returns the directory that a create at p should happen in.

For a plain directory that is just the directory. For a union it is the first member carrying MCREATE, and if no member has it, creation is refused, which is bind(2)'s rule.

func Cwd

method on Ns
1func (n *Ns) Cwd() string
source

Cwd returns the working directory.

func Mount

method on Ns
1func (n *Ns) Mount(f ninep.File, source, target string, flag Flag) error
source

Mount grafts a file tree that has no name in this namespace yet, which is how a service posted by another realm gets in. source is a label, used only when printing the namespace.

func Open

method on Ns
1func (n *Ns) Open(p string) (ninep.File, error)
source

Open returns the file a name resolves to: the first member of its union, which is the one a walk through this name would reach.

func Ops

method on Ns
1func (n *Ns) Ops() []Op
source

Ops returns the bindings in application order.

func ReadDir

method on Ns
1func (n *Ns) ReadDir(p string, unique bool) ([]ninep.Stat, error)
source

ReadDir lists p. A union directory is the CONCATENATION of its members' contents, as in Plan 9, so duplicate names can appear and shadowing is visible. Pass unique to collapse them first-wins instead, which is the set of names a walk can actually reach.

func ReadFile

method on Ns
1func (n *Ns) ReadFile(p string) (string, error)
source

ReadFile reads the whole file at p.

func Resolve

method on Ns
1func (n *Ns) Resolve(p string) ([]ninep.File, error)
source

Resolve returns every union member visible at p, in search order.

func Stat

method on Ns
1func (n *Ns) Stat(p string) (ninep.Stat, error)
source

Stat returns the entry for p, with the name replaced by the last element of the path as asked for, so that a union member's own name never leaks.

func String

method on Ns
1func (n *Ns) String() string
source

String prints the namespace the way Plan 9's ns(1) does: one line per binding, in the order they were applied, then the working directory.

func Unmount

method on Ns
1func (n *Ns) Unmount(source, target string) error
source

Unmount undoes bindings at target. With an empty source it removes every binding there, restoring the name to whatever it resolved to originally.

type Op

struct
1type Op struct {
2	Verb   string // "bind" or "mount"
3	Flag   Flag
4	Source string
5	Target string
6}
source

Op records one bind or mount, in application order, so that String can print the namespace the way Plan 9's ns(1) does.

Imports 4

Source Files 4