PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

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

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v0
Namespace
moul / x / plan9 / memfs
Files
4 (README)(gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/moul/x/plan9/memfs/v0
gno
0.9

Files (4)

  • README.mdmarkdown
  • gnomod.tomltoml
  • memfs.gnogno
  • memfs_test.gnogno
memfs.gnogno
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 File4// and Mutable, and the tree a namespace server hands out as a user's private5// root. Children live in an avl.Tree, so a directory listing is ordered by6// name and therefore identical on every validating node; a map would make7// 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 it11// does not own. Reads (the ninep.File half) are safe from anywhere, which is12// 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 than15// the tree reading chain state itself, so the same tree is exercisable in a16// plain unit test.17//18// NOTICE. Plan 9 from Bell Labs is the work of the Computing Science Research19// Center at Bell Labs; the name and the marks are theirs, and the copyright is20// held by the Plan 9 Foundation (https://p9f.org). This package is not21// affiliated with, endorsed by, or sponsored by them, and contains no Plan 922// 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 a24// chain. Full attribution: NOTICE.md at the root of moul/gno-contracts.25package memfs2627import (28	"strings"2930	"gno.land/p/nt/avl/v0"3132	ninep "gno.land/p/moul/x/plan9/ninep/v0"33)3435// FS is a mutable in-memory file tree.36type FS struct {37	root *node38	next uint64 // qid path allocator, unique for this server's lifetime39	uid  string40}4142type node struct {43	fs       *FS44	name     string45	perm     ninep.Perm46	qid      ninep.Qid47	data     string48	children *avl.Tree // name -> *node; nil for a plain file49	mtime    int6450	uid      string51	gid      string52	muid     string53}5455// New returns an empty file server owned by uid, with its root created at56// 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 fs61}6263// Root returns the server's root directory.64func (fs *FS) Root() ninep.File { return fs.root }6566// RootMutable returns the root as a Mutable. Only call it from the realm that67// owns this FS.68func (fs *FS) RootMutable() ninep.Mutable { return fs.root }6970// Uid returns the owner this server stamps on new files.71func (fs *FS) Uid() string { return fs.uid }7273func (fs *FS) newNode(name string, perm ninep.Perm, now int64) *node {74	fs.next++75	qt := ninep.QTFILE76	var kids *avl.Tree77	if perm.IsDir() {78		qt = ninep.QTDIR79		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}9394// 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 directory99	}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}111112// Walk implements ninep.File.113func (n *node) Walk(name string) (ninep.File, error) {114	if !n.perm.IsDir() {115		return nil, ninep.ErrNotDir116	}117	if !ninep.ValidName(name) {118		return nil, ninep.ErrBadName119	}120	v := n.children.Get(name) // avl.Get returns ONE value in gno121	if v == nil {122		return nil, ninep.ErrNotExist123	}124	return v.(*node), nil125}126127// Read implements ninep.File.128func (n *node) Read(off, count int64) (string, error) {129	if n.perm.IsDir() {130		return "", ninep.ErrIsDir131	}132	return ninep.Slice(n.data, off, count), nil133}134135// ReadDir implements ninep.File.136func (n *node) ReadDir() ([]ninep.Stat, error) {137	if !n.perm.IsDir() {138		return nil, ninep.ErrNotDir139	}140	out := []ninep.Stat{}141	n.children.Iterate("", "", func(_ string, v any) bool {142		out = append(out, v.(*node).Stat())143		return false144	})145	return out, nil146}147148// 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.ErrNotDir152	}153	if !ninep.ValidName(name) {154		return nil, ninep.ErrBadName155	}156	if n.children.Has(name) {157		return nil, ninep.ErrExist158	}159	child := n.fs.newNode(name, perm, now)160	n.children.Set(name, child)161	n.touch(now)162	return child, nil163}164165// Remove implements ninep.Mutable. A non-empty directory is refused, as in166// Plan 9.167func (n *node) Remove(name string) error {168	if !n.perm.IsDir() {169		return ninep.ErrNotDir170	}171	v := n.children.Get(name)172	if v == nil {173		return ninep.ErrNotExist174	}175	child := v.(*node)176	if child.perm.IsDir() && child.children.Size() > 0 {177		return ninep.ErrNotEmpty178	}179	n.children.Remove(name)180	n.touch(n.mtime)181	return nil182}183184// Write implements ninep.Mutable. Writing past the end extends the file with185// 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.ErrIsDir189	}190	if off < 0 {191		off = 0192	}193	if n.perm&ninep.DMAPPEND != 0 {194		off = int64(len(n.data))195	}196	cur := n.data197	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 + tail206	n.touch(now)207	return int64(len(data)), nil208}209210// Truncate implements ninep.Mutable.211func (n *node) Truncate(size int64, now int64) error {212	if n.perm.IsDir() {213		return ninep.ErrIsDir214	}215	if size < 0 {216		size = 0217	}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 nil226}227228// touch bumps the qid version and the mtime. A client that kept a qid can tell229// 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 = now233	n.muid = n.fs.uid234}235236// MkdirAll creates p and every missing parent, and returns the leaf. An237// 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.root240	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, err246			}247			cur = f.(*node)248			continue249		}250		cur = v.(*node)251		if !cur.perm.IsDir() {252			return nil, ninep.ErrNotDir253		}254	}255	return cur, nil256}257258// WriteFile creates or replaces the file at p, creating parent directories as259// 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 err264	}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 err272		}273		_, err = f.(*node).Write(0, data, now)274		return err275	}276	target := v.(*node)277	if err := target.Truncate(0, now); err != nil {278		return err279	}280	_, err = target.Write(0, data, now)281	return err282}283

Functions

not supported for pure packages by the node (vm/qfuncs)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.