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

memfs.gno

7.12 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/moul/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/moul/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}