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/ninep/v0

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v0
Namespace
moul / x / plan9 / ninep
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/ninep/v0
gno
0.9

Files (4)

  • README.mdmarkdown
  • gnomod.tomltoml
  • ninep.gnogno
  • ninep_test.gnogno
ninep.gnogno
1// Package ninep is a Plan 9 shaped file abstraction for gno.2//3// It implements the SEMANTICS of 9P2000, not its wire format: there is no4// socket on a chain, the VM call is the transport. What it keeps is the part5// that made 9P useful, namely that every resource answers the same four6// questions (stat, walk, read, readdir), so a client written today can browse7// a file server deployed tomorrow.8//9// One interface, not two. 9P reads a directory with the same Tread it uses for10// a file, so splitting File from Dir would be less faithful, and a single11// interface means no type assertion across a realm boundary.12//13// Deliberate divergences from 9P2000, all of them forced:14//15//   - Data is a string, not []byte. Every consumer on this chain is text and16//     Render returns a string.17//   - Mtime is a block height, not a wall clock. It is the only clock that18//     every validating node agrees on.19//   - There is no open/clunk pair. Without a session there are no fids, so20//     Walk returns the file itself and nothing has to be released.21//   - File is READ-ONLY. A crossing method would mint the caller's realm frame22//     for the callee, which is a confused-deputy hazard (compare r/gov/dao's23//     Executor, and p/nt/grc20's deliberate refusal to make Teller crossing).24//     Mutation lives in Mutable, which is only safe on a tree your own realm25//     owns.26//27// See gno.land/p/moul/x/plan9/ns for the namespace that binds these trees28// together, and gno.land/p/moul/x/plan9/memfs for the reference server.29//30// NOTICE. Plan 9 from Bell Labs is the work of the Computing Science Research31// Center at Bell Labs; the name and the marks are theirs, and the copyright is32// held by the Plan 9 Foundation (https://p9f.org). This package is not33// affiliated with, endorsed by, or sponsored by them, and contains no Plan 934// code: it borrows the vocabulary so that the design reads without a glossary,35// and it is an homage, asking what that ecosystem's spirit looks like on a36// chain. Full attribution: NOTICE.md at the root of moul/gno-contracts.37package ninep3839import (40	"errors"41	"path"42	"strconv"43	"strings"44)4546// Qid type bits, as in 9P2000.47const (48	QTFILE   uint8 = 0x00 // a plain file49	QTTMP    uint8 = 0x04 // not archived50	QTAUTH   uint8 = 0x08 // authentication file51	QTMOUNT  uint8 = 0x10 // mounted channel52	QTEXCL   uint8 = 0x20 // exclusive use53	QTAPPEND uint8 = 0x40 // append only54	QTDIR    uint8 = 0x80 // a directory55)5657// Perm holds the 9P mode word: the low nine bits are rwx for owner, group and58// other, the high bits are the DM* kind flags.59type Perm uint326061// Mode bits, as in 9P2000.62const (63	DMTMP    Perm = 0x0400000064	DMAUTH   Perm = 0x0800000065	DMMOUNT  Perm = 0x1000000066	DMEXCL   Perm = 0x2000000067	DMAPPEND Perm = 0x4000000068	DMDIR    Perm = 0x800000006970	PermMask Perm = 0777 // the rwxrwxrwx bits7172	// Conventional defaults, matching what Plan 9's ramfs hands out.73	DirPerm  Perm = DMDIR | 075574	FilePerm Perm = 064475)7677// IsDir reports whether the mode marks a directory.78func (p Perm) IsDir() bool { return p&DMDIR != 0 }7980// String renders the mode the way ls -l does: a kind letter then nine rwx81// bits. The kind letter is 'd' for a directory, 'a' for append-only, 'l' for82// exclusive-use, '-' otherwise.83func (p Perm) String() string {84	var b strings.Builder85	switch {86	case p&DMDIR != 0:87		b.WriteByte('d')88	case p&DMAPPEND != 0:89		b.WriteByte('a')90	case p&DMEXCL != 0:91		b.WriteByte('l')92	default:93		b.WriteByte('-')94	}95	const rwx = "rwxrwxrwx"96	for i := 0; i < 9; i++ {97		if p&(1<<uint(8-i)) != 0 {98			b.WriteByte(rwx[i])99		} else {100			b.WriteByte('-')101		}102	}103	return b.String()104}105106// Qid is the server's unique handle for a file. Path identifies the file107// within one server for its whole lifetime; Version increments on every write,108// so a client can tell "same file, changed" from "different file" without109// reading either.110type Qid struct {111	Type    uint8112	Version uint32113	Path    uint64114}115116// IsDir reports whether the qid marks a directory.117func (q Qid) IsDir() bool { return q.Type&QTDIR != 0 }118119// String renders the qid as Plan 9 does, "(path version type)", with the path120// in hex.121func (q Qid) String() string {122	kind := "f"123	if q.IsDir() {124		kind = "d"125	}126	return "(" + strconv.FormatUint(q.Path, 16) + " " +127		strconv.FormatUint(uint64(q.Version), 10) + " " + kind + ")"128}129130// Stat is 9P's directory entry, minus the fields that only mean something on a131// wire (type, dev) or on a host clock (atime).132type Stat struct {133	Qid    Qid134	Mode   Perm135	Mtime  int64 // block height of the last write136	Length int64 // in bytes; zero for a directory, as in 9P137	Name   string138	Uid    string // owner; a bech32 address, or a well-known name139	Gid    string // group140	Muid   string // last writer141}142143// IsDir reports whether the entry is a directory.144func (s Stat) IsDir() bool { return s.Mode.IsDir() }145146// Line renders the entry the way ls -l does.147func (s Stat) Line() string {148	return s.Mode.String() + " " + pad(s.Uid, 12) + " " + pad(s.Gid, 8) + " " +149		lpad(strconv.FormatInt(s.Length, 10), 7) + " " + s.Name150}151152// pad right-pads to n runes. ufmt has no width flags in gno, so padding is by153// hand everywhere in this suite.154func pad(s string, n int) string {155	for len(s) < n {156		s += " "157	}158	return s159}160161func lpad(s string, n int) string {162	for len(s) < n {163		s = " " + s164	}165	return s166}167168// File is a 9P file server's whole read surface. Every method must be free of169// side effects: a File is routinely reached across a realm boundary, where the170// running frame belongs to the CALLER, so mutating anything here would be both171// a VM error and a confused deputy.172type File interface {173	// Stat returns the entry for this file.174	Stat() Stat175	// Walk resolves exactly one path element. It returns ErrNotDir on a176	// plain file and ErrNotExist when the name is absent. It never sees177	// "." or "..": both are removed lexically before resolution starts.178	Walk(name string) (File, error)179	// Read returns at most count bytes starting at off. A negative count180	// means "to the end". It returns ErrIsDir on a directory.181	Read(off, count int64) (string, error)182	// ReadDir returns the directory's entries in a deterministic order. It183	// returns ErrNotDir on a plain file.184	ReadDir() ([]Stat, error)185}186187// Mutable is the write half, kept out of File on purpose.188//189// It is NOT safe across a realm boundary: a non-crossing method runs in the190// caller's frame, so a foreign realm calling these would be trying to mutate191// objects it does not own. Only call Mutable on a tree your own realm created.192// The caller supplies now (a block height) rather than the tree reading the193// chain itself, so the same code is testable off chain.194type Mutable interface {195	File196	Create(name string, perm Perm, now int64) (File, error)197	Remove(name string) error198	Write(off int64, data string, now int64) (int64, error)199	Truncate(size int64, now int64) error200}201202// Plan 9 error strings, kept lowercase and verbatim where they exist.203var (204	ErrNotExist = errors.New("file does not exist")205	ErrNotDir   = errors.New("not a directory")206	ErrIsDir    = errors.New("is a directory")207	ErrExist    = errors.New("file already exists")208	ErrPerm     = errors.New("permission denied")209	ErrNoCreate = errors.New("create prohibited")210	ErrReadOnly = errors.New("read-only file server")211	ErrBadName  = errors.New("bad character in file name")212	ErrNotEmpty = errors.New("directory not empty")213	ErrTooDeep  = errors.New("path too deep")214)215216// MaxDepth bounds a walk. Resolution costs one cross-realm call per element217// per union member, so depth is capped rather than trusted.218const MaxDepth = 32219220// ValidName reports whether name is usable as a single path element. Plan 9221// rejects the empty name, "." and "..", and any name containing a slash.222func ValidName(name string) bool {223	if name == "" || name == "." || name == ".." {224		return false225	}226	return !strings.Contains(name, "/")227}228229// Clean returns p as a cleaned absolute path. "." and ".." are resolved230// lexically, before any server sees them, which is what makes ".." undo the231// name you typed rather than the directory you landed in (see "Lexical File232// Names in Plan 9").233func Clean(p string) string {234	if !strings.HasPrefix(p, "/") {235		p = "/" + p236	}237	return path.Clean(p)238}239240// Abs resolves p against cwd, then cleans it.241func Abs(cwd, p string) string {242	if p == "" {243		return Clean(cwd)244	}245	if strings.HasPrefix(p, "/") {246		return Clean(p)247	}248	if cwd == "" {249		cwd = "/"250	}251	return Clean(cwd + "/" + p)252}253254// Elems splits a cleaned absolute path into its elements. The root yields nil.255func Elems(p string) []string {256	p = Clean(p)257	if p == "/" {258		return nil259	}260	return strings.Split(p[1:], "/")261}262263// Base returns the last element of p, or "/" for the root.264func Base(p string) string { return path.Base(Clean(p)) }265266// Dir returns p's parent.267func Dir(p string) string { return path.Dir(Clean(p)) }268269// Join appends name to dir.270func Join(dir, name string) string { return Clean(dir + "/" + name) }271272// Walk resolves elems from f, one element at a time. It is the plain,273// namespace-free walk: no binds, no unions. Use ns.Ns for those.274func Walk(f File, elems []string) (File, error) {275	if len(elems) > MaxDepth {276		return nil, ErrTooDeep277	}278	for _, e := range elems {279		next, err := f.Walk(e)280		if err != nil {281			return nil, err282		}283		f = next284	}285	return f, nil286}287288// ReadAll reads a whole file.289func ReadAll(f File) (string, error) { return f.Read(0, -1) }290291// Slice applies 9P's read window to s: at most count bytes from off, with a292// negative count meaning "to the end". An offset past the end reads empty,293// which is what makes a read loop terminate rather than fail.294func Slice(s string, off, count int64) string {295	if off < 0 {296		off = 0297	}298	n := int64(len(s))299	if off >= n {300		return ""301	}302	end := n303	if count >= 0 && off+count < n {304		end = off + count305	}306	return s[off:end]307}308

Functions

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

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