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.

memfs.gno

7.14 Kb · 282 lines
  1// Package memfs is a RAM file server: Plan 9's ramfs, in a realm's heap.
  2//
  3// It is the reference implementation of gno.land/p/nym-moultest123/x/plan9/ninep's File
  4// and Mutable, and the tree a namespace server hands out as a user's private
  5// root. Children live in an avl.Tree, so a directory listing is ordered by
  6// name and therefore identical on every validating node; a map would make
  7// Render a consensus bug.
  8//
  9// Mutable is IN-REALM ONLY. A non-crossing method runs in the caller's frame,
 10// so a foreign realm calling Create or Write here would be mutating objects it
 11// does not own. Reads (the ninep.File half) are safe from anywhere, which is
 12// what makes a memfs tree mountable into somebody else's namespace.
 13//
 14// The caller supplies the clock (a block height) on every mutation rather than
 15// the tree reading chain state itself, so the same tree is exercisable in a
 16// plain unit test.
 17//
 18// NOTICE. Plan 9 from Bell Labs is the work of the Computing Science Research
 19// Center at Bell Labs; the name and the marks are theirs, and the copyright is
 20// held by the Plan 9 Foundation (https://p9f.org). This package is not
 21// affiliated with, endorsed by, or sponsored by them, and contains no Plan 9
 22// code: it borrows the vocabulary so that the design reads without a glossary,
 23// and it is an homage, asking what that ecosystem's spirit looks like on a
 24// chain. Full attribution: NOTICE.md at the root of moul/gno-contracts.
 25package memfs
 26
 27import (
 28	"strings"
 29
 30	"gno.land/p/nt/avl/v0"
 31
 32	ninep "gno.land/p/nym-moultest123/x/plan9/ninep/v0"
 33)
 34
 35// FS is a mutable in-memory file tree.
 36type FS struct {
 37	root *node
 38	next uint64 // qid path allocator, unique for this server's lifetime
 39	uid  string
 40}
 41
 42type node struct {
 43	fs       *FS
 44	name     string
 45	perm     ninep.Perm
 46	qid      ninep.Qid
 47	data     string
 48	children *avl.Tree // name -> *node; nil for a plain file
 49	mtime    int64
 50	uid      string
 51	gid      string
 52	muid     string
 53}
 54
 55// New returns an empty file server owned by uid, with its root created at
 56// block height now.
 57func New(uid string, now int64) *FS {
 58	fs := &FS{uid: uid}
 59	fs.root = fs.newNode("/", ninep.DirPerm, now)
 60	return fs
 61}
 62
 63// Root returns the server's root directory.
 64func (fs *FS) Root() ninep.File { return fs.root }
 65
 66// RootMutable returns the root as a Mutable. Only call it from the realm that
 67// owns this FS.
 68func (fs *FS) RootMutable() ninep.Mutable { return fs.root }
 69
 70// Uid returns the owner this server stamps on new files.
 71func (fs *FS) Uid() string { return fs.uid }
 72
 73func (fs *FS) newNode(name string, perm ninep.Perm, now int64) *node {
 74	fs.next++
 75	qt := ninep.QTFILE
 76	var kids *avl.Tree
 77	if perm.IsDir() {
 78		qt = ninep.QTDIR
 79		kids = avl.NewTree()
 80	}
 81	return &node{
 82		fs:       fs,
 83		name:     name,
 84		perm:     perm,
 85		qid:      ninep.Qid{Type: qt, Version: 0, Path: fs.next},
 86		children: kids,
 87		mtime:    now,
 88		uid:      fs.uid,
 89		gid:      fs.uid,
 90		muid:     fs.uid,
 91	}
 92}
 93
 94// Stat implements ninep.File.
 95func (n *node) Stat() ninep.Stat {
 96	length := int64(len(n.data))
 97	if n.perm.IsDir() {
 98		length = 0 // 9P reports zero for a directory
 99	}
100	return ninep.Stat{
101		Qid:    n.qid,
102		Mode:   n.perm,
103		Mtime:  n.mtime,
104		Length: length,
105		Name:   n.name,
106		Uid:    n.uid,
107		Gid:    n.gid,
108		Muid:   n.muid,
109	}
110}
111
112// Walk implements ninep.File.
113func (n *node) Walk(name string) (ninep.File, error) {
114	if !n.perm.IsDir() {
115		return nil, ninep.ErrNotDir
116	}
117	if !ninep.ValidName(name) {
118		return nil, ninep.ErrBadName
119	}
120	v := n.children.Get(name) // avl.Get returns ONE value in gno
121	if v == nil {
122		return nil, ninep.ErrNotExist
123	}
124	return v.(*node), nil
125}
126
127// Read implements ninep.File.
128func (n *node) Read(off, count int64) (string, error) {
129	if n.perm.IsDir() {
130		return "", ninep.ErrIsDir
131	}
132	return ninep.Slice(n.data, off, count), nil
133}
134
135// ReadDir implements ninep.File.
136func (n *node) ReadDir() ([]ninep.Stat, error) {
137	if !n.perm.IsDir() {
138		return nil, ninep.ErrNotDir
139	}
140	out := []ninep.Stat{}
141	n.children.Iterate("", "", func(_ string, v any) bool {
142		out = append(out, v.(*node).Stat())
143		return false
144	})
145	return out, nil
146}
147
148// Create implements ninep.Mutable.
149func (n *node) Create(name string, perm ninep.Perm, now int64) (ninep.File, error) {
150	if !n.perm.IsDir() {
151		return nil, ninep.ErrNotDir
152	}
153	if !ninep.ValidName(name) {
154		return nil, ninep.ErrBadName
155	}
156	if n.children.Has(name) {
157		return nil, ninep.ErrExist
158	}
159	child := n.fs.newNode(name, perm, now)
160	n.children.Set(name, child)
161	n.touch(now)
162	return child, nil
163}
164
165// Remove implements ninep.Mutable. A non-empty directory is refused, as in
166// Plan 9.
167func (n *node) Remove(name string) error {
168	if !n.perm.IsDir() {
169		return ninep.ErrNotDir
170	}
171	v := n.children.Get(name)
172	if v == nil {
173		return ninep.ErrNotExist
174	}
175	child := v.(*node)
176	if child.perm.IsDir() && child.children.Size() > 0 {
177		return ninep.ErrNotEmpty
178	}
179	n.children.Remove(name)
180	n.touch(n.mtime)
181	return nil
182}
183
184// Write implements ninep.Mutable. Writing past the end extends the file with
185// NUL bytes, as a 9P server does.
186func (n *node) Write(off int64, data string, now int64) (int64, error) {
187	if n.perm.IsDir() {
188		return 0, ninep.ErrIsDir
189	}
190	if off < 0 {
191		off = 0
192	}
193	if n.perm&ninep.DMAPPEND != 0 {
194		off = int64(len(n.data))
195	}
196	cur := n.data
197	if off > int64(len(cur)) {
198		cur += strings.Repeat("\x00", int(off)-len(cur))
199	}
200	end := off + int64(len(data))
201	tail := ""
202	if end < int64(len(cur)) {
203		tail = cur[end:]
204	}
205	n.data = cur[:off] + data + tail
206	n.touch(now)
207	return int64(len(data)), nil
208}
209
210// Truncate implements ninep.Mutable.
211func (n *node) Truncate(size int64, now int64) error {
212	if n.perm.IsDir() {
213		return ninep.ErrIsDir
214	}
215	if size < 0 {
216		size = 0
217	}
218	switch {
219	case size < int64(len(n.data)):
220		n.data = n.data[:size]
221	case size > int64(len(n.data)):
222		n.data += strings.Repeat("\x00", int(size)-len(n.data))
223	}
224	n.touch(now)
225	return nil
226}
227
228// touch bumps the qid version and the mtime. A client that kept a qid can tell
229// the file changed without reading it, which is the whole point of Qid.Version.
230func (n *node) touch(now int64) {
231	n.qid.Version++
232	n.mtime = now
233	n.muid = n.fs.uid
234}
235
236// MkdirAll creates p and every missing parent, and returns the leaf. An
237// existing directory is not an error; an existing plain file on the path is.
238func (fs *FS) MkdirAll(p string, now int64) (ninep.File, error) {
239	cur := fs.root
240	for _, e := range ninep.Elems(p) {
241		v := cur.children.Get(e)
242		if v == nil {
243			f, err := cur.Create(e, ninep.DirPerm, now)
244			if err != nil {
245				return nil, err
246			}
247			cur = f.(*node)
248			continue
249		}
250		cur = v.(*node)
251		if !cur.perm.IsDir() {
252			return nil, ninep.ErrNotDir
253		}
254	}
255	return cur, nil
256}
257
258// WriteFile creates or replaces the file at p, creating parent directories as
259// needed. It is the seeding helper a realm wants at init.
260func (fs *FS) WriteFile(p, data string, now int64) error {
261	dir, err := fs.MkdirAll(ninep.Dir(p), now)
262	if err != nil {
263		return err
264	}
265	name := ninep.Base(p)
266	d := dir.(*node)
267	v := d.children.Get(name)
268	if v == nil {
269		f, err := d.Create(name, ninep.FilePerm, now)
270		if err != nil {
271			return err
272		}
273		_, err = f.(*node).Write(0, data, now)
274		return err
275	}
276	target := v.(*node)
277	if err := target.Truncate(0, now); err != nil {
278		return err
279	}
280	_, err = target.Write(0, data, now)
281	return err
282}