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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

synfs.gno

6.61 Kb · 231 lines
  1// Package synfs builds a synthetic, read-only ninep file server whose contents
  2// are computed by a function at read time.
  3//
  4// This is the package that makes the rest of the suite adoptable. Plan 9's
  5// device drivers are not storage, they are code behind a name: reading
  6// /dev/time runs a function. A realm that wants to publish its state as a
  7// browsable tree does the same thing here, in a few lines, and gets ls, cat,
  8// stat and mountability for free:
  9//
 10//	t := synfs.New("dev", "g1...", func() int64 { return runtime.ChainHeight() })
 11//	t.Root().
 12//		Add("sysname", func() string { return runtime.ChainID() }).
 13//		Add("height", func() string { return strconv.FormatInt(runtime.ChainHeight(), 10) })
 14//
 15// The tree is READ-ONLY by construction, which is what makes it safe to hand
 16// to another realm's namespace: every method is free of side effects, so it
 17// can be called from a frame that does not own these objects.
 18//
 19// A synthetic file reports Qid.Version 0 forever. Its contents can change on
 20// every block, so a version would be a lie; clients that need change detection
 21// should read the file.
 22//
 23// NOTICE. Plan 9 from Bell Labs is the work of the Computing Science Research
 24// Center at Bell Labs; the name and the marks are theirs, and the copyright is
 25// held by the Plan 9 Foundation (https://p9f.org). This package is not
 26// affiliated with, endorsed by, or sponsored by them, and contains no Plan 9
 27// code: it borrows the vocabulary so that the design reads without a glossary,
 28// and it is an homage, asking what that ecosystem's spirit looks like on a
 29// chain. Full attribution: NOTICE.md at the root of moul/gno-contracts.
 30package synfs
 31
 32import (
 33	"gno.land/p/nt/avl/v0"
 34
 35	ninep "gno.land/p/nym-moultest123/x/plan9/ninep/v0"
 36)
 37
 38// ReadFn computes a whole file.
 39type ReadFn func() string
 40
 41// RangeFn serves a 9P read window directly, for files that are cheaper (or
 42// only possible) to generate a slice at a time, such as an endless one.
 43type RangeFn func(off, count int64) (string, error)
 44
 45// Tree owns the qid allocator, the clock and the owner name shared by every
 46// node in one synthetic server.
 47type Tree struct {
 48	root  *Dir
 49	next  uint64
 50	clock func() int64
 51	uid   string
 52}
 53
 54// New returns a tree whose root directory is named name, owned by uid, using
 55// clock (normally the block height) as every node's mtime. clock may be nil,
 56// in which case the mtime is zero.
 57func New(name, uid string, clock func() int64) *Tree {
 58	t := &Tree{clock: clock, uid: uid}
 59	t.root = t.NewDir(name)
 60	return t
 61}
 62
 63// Root returns the tree's root directory.
 64func (t *Tree) Root() *Dir { return t.root }
 65
 66// Uid returns the owner stamped on every node.
 67func (t *Tree) Uid() string { return t.uid }
 68
 69func (t *Tree) now() int64 {
 70	if t.clock == nil {
 71		return 0
 72	}
 73	return t.clock()
 74}
 75
 76func (t *Tree) qid(dir bool) ninep.Qid {
 77	t.next++
 78	qt := ninep.QTFILE
 79	if dir {
 80		qt = ninep.QTDIR
 81	}
 82	return ninep.Qid{Type: qt, Version: 0, Path: t.next}
 83}
 84
 85// NewDir returns a detached directory belonging to this tree. Attach it with
 86// AddDir.
 87func (t *Tree) NewDir(name string) *Dir {
 88	return &Dir{
 89		tree:     t,
 90		name:     name,
 91		perm:     ninep.DMDIR | 0555,
 92		qid:      t.qid(true),
 93		children: avl.NewTree(),
 94	}
 95}
 96
 97// Dir is a synthetic directory. Its children are held in an avl tree, so a
 98// listing is ordered by name and identical on every node.
 99type Dir struct {
100	tree     *Tree
101	name     string
102	perm     ninep.Perm
103	qid      ninep.Qid
104	children *avl.Tree // name -> ninep.File
105}
106
107// Add attaches a read-only file computed by fn, mode 0444. It returns the
108// directory, so calls chain.
109func (d *Dir) Add(name string, fn ReadFn) *Dir {
110	return d.AddPerm(name, 0444, fn)
111}
112
113// AddPerm is Add with an explicit mode.
114func (d *Dir) AddPerm(name string, perm ninep.Perm, fn ReadFn) *Dir {
115	whole := fn
116	return d.AddRange(name, perm, func(off, count int64) (string, error) {
117		return ninep.Slice(whole(), off, count), nil
118	})
119}
120
121// AddRange attaches a file that serves a read window itself. Use it for a file
122// with no natural end, where materialising the whole thing would be wrong.
123func (d *Dir) AddRange(name string, perm ninep.Perm, fn RangeFn) *Dir {
124	if !ninep.ValidName(name) {
125		panic("synfs: invalid file name: " + name)
126	}
127	d.children.Set(name, &file{
128		tree: d.tree,
129		name: name,
130		perm: perm &^ ninep.DMDIR,
131		qid:  d.tree.qid(false),
132		fn:   fn,
133	})
134	return d
135}
136
137// AddDir attaches a subdirectory built with Tree.NewDir.
138func (d *Dir) AddDir(sub *Dir) *Dir {
139	if !ninep.ValidName(sub.name) {
140		panic("synfs: invalid directory name: " + sub.name)
141	}
142	d.children.Set(sub.name, sub)
143	return d
144}
145
146// AddFile attaches any ninep.File under the given name, which is how a
147// synthetic tree splices in a tree served by something else.
148func (d *Dir) AddFile(name string, f ninep.File) *Dir {
149	if !ninep.ValidName(name) {
150		panic("synfs: invalid file name: " + name)
151	}
152	d.children.Set(name, f)
153	return d
154}
155
156// Names returns the directory's entries, in order. Useful for a file that
157// wants to describe its own directory, as /dev/drivers does.
158func (d *Dir) Names() []string {
159	out := []string{}
160	d.children.Iterate("", "", func(k string, _ any) bool {
161		out = append(out, k)
162		return false
163	})
164	return out
165}
166
167// Stat implements ninep.File.
168func (d *Dir) Stat() ninep.Stat {
169	return ninep.Stat{
170		Qid:   d.qid,
171		Mode:  d.perm,
172		Mtime: d.tree.now(),
173		Name:  d.name,
174		Uid:   d.tree.uid,
175		Gid:   d.tree.uid,
176		Muid:  d.tree.uid,
177	}
178}
179
180// Walk implements ninep.File.
181func (d *Dir) Walk(name string) (ninep.File, error) {
182	if !ninep.ValidName(name) {
183		return nil, ninep.ErrBadName
184	}
185	v := d.children.Get(name)
186	if v == nil {
187		return nil, ninep.ErrNotExist
188	}
189	return v.(ninep.File), nil
190}
191
192// Read implements ninep.File.
193func (d *Dir) Read(off, count int64) (string, error) { return "", ninep.ErrIsDir }
194
195// ReadDir implements ninep.File.
196func (d *Dir) ReadDir() ([]ninep.Stat, error) {
197	out := []ninep.Stat{}
198	d.children.Iterate("", "", func(_ string, v any) bool {
199		out = append(out, v.(ninep.File).Stat())
200		return false
201	})
202	return out, nil
203}
204
205type file struct {
206	tree *Tree
207	name string
208	perm ninep.Perm
209	qid  ninep.Qid
210	fn   RangeFn
211}
212
213func (f *file) Stat() ninep.Stat {
214	// A synthetic file has no stored length. 9P servers for such files report
215	// zero rather than run the generator just to measure it, and so does this.
216	return ninep.Stat{
217		Qid:   f.qid,
218		Mode:  f.perm,
219		Mtime: f.tree.now(),
220		Name:  f.name,
221		Uid:   f.tree.uid,
222		Gid:   f.tree.uid,
223		Muid:  f.tree.uid,
224	}
225}
226
227func (f *file) Walk(name string) (ninep.File, error) { return nil, ninep.ErrNotDir }
228
229func (f *file) Read(off, count int64) (string, error) { return f.fn(off, count) }
230
231func (f *file) ReadDir() ([]ninep.Stat, error) { return nil, ninep.ErrNotDir }