// Package ninep is a Plan 9 shaped file abstraction for gno. // // It implements the SEMANTICS of 9P2000, not its wire format: there is no // socket on a chain, the VM call is the transport. What it keeps is the part // that made 9P useful, namely that every resource answers the same four // questions (stat, walk, read, readdir), so a client written today can browse // a file server deployed tomorrow. // // One interface, not two. 9P reads a directory with the same Tread it uses for // a file, so splitting File from Dir would be less faithful, and a single // interface means no type assertion across a realm boundary. // // Deliberate divergences from 9P2000, all of them forced: // // - Data is a string, not []byte. Every consumer on this chain is text and // Render returns a string. // - Mtime is a block height, not a wall clock. It is the only clock that // every validating node agrees on. // - There is no open/clunk pair. Without a session there are no fids, so // Walk returns the file itself and nothing has to be released. // - File is READ-ONLY. A crossing method would mint the caller's realm frame // for the callee, which is a confused-deputy hazard (compare r/gov/dao's // Executor, and p/nt/grc20's deliberate refusal to make Teller crossing). // Mutation lives in Mutable, which is only safe on a tree your own realm // owns. // // See gno.land/p/moul/x/plan9/ns for the namespace that binds these trees // together, and gno.land/p/moul/x/plan9/memfs for the reference server. // // NOTICE. Plan 9 from Bell Labs is the work of the Computing Science Research // Center at Bell Labs; the name and the marks are theirs, and the copyright is // held by the Plan 9 Foundation (https://p9f.org). This package is not // affiliated with, endorsed by, or sponsored by them, and contains no Plan 9 // code: it borrows the vocabulary so that the design reads without a glossary, // and it is an homage, asking what that ecosystem's spirit looks like on a // chain. Full attribution: NOTICE.md at the root of moul/gno-contracts. package ninep import ( "errors" "path" "strconv" "strings" ) // Qid type bits, as in 9P2000. const ( QTFILE uint8 = 0x00 // a plain file QTTMP uint8 = 0x04 // not archived QTAUTH uint8 = 0x08 // authentication file QTMOUNT uint8 = 0x10 // mounted channel QTEXCL uint8 = 0x20 // exclusive use QTAPPEND uint8 = 0x40 // append only QTDIR uint8 = 0x80 // a directory ) // Perm holds the 9P mode word: the low nine bits are rwx for owner, group and // other, the high bits are the DM* kind flags. type Perm uint32 // Mode bits, as in 9P2000. const ( DMTMP Perm = 0x04000000 DMAUTH Perm = 0x08000000 DMMOUNT Perm = 0x10000000 DMEXCL Perm = 0x20000000 DMAPPEND Perm = 0x40000000 DMDIR Perm = 0x80000000 PermMask Perm = 0777 // the rwxrwxrwx bits // Conventional defaults, matching what Plan 9's ramfs hands out. DirPerm Perm = DMDIR | 0755 FilePerm Perm = 0644 ) // IsDir reports whether the mode marks a directory. func (p Perm) IsDir() bool { return p&DMDIR != 0 } // String renders the mode the way ls -l does: a kind letter then nine rwx // bits. The kind letter is 'd' for a directory, 'a' for append-only, 'l' for // exclusive-use, '-' otherwise. func (p Perm) String() string { var b strings.Builder switch { case p&DMDIR != 0: b.WriteByte('d') case p&DMAPPEND != 0: b.WriteByte('a') case p&DMEXCL != 0: b.WriteByte('l') default: b.WriteByte('-') } const rwx = "rwxrwxrwx" for i := 0; i < 9; i++ { if p&(1< MaxDepth { return nil, ErrTooDeep } for _, e := range elems { next, err := f.Walk(e) if err != nil { return nil, err } f = next } return f, nil } // ReadAll reads a whole file. func ReadAll(f File) (string, error) { return f.Read(0, -1) } // Slice applies 9P's read window to s: at most count bytes from off, with a // negative count meaning "to the end". An offset past the end reads empty, // which is what makes a read loop terminate rather than fail. func Slice(s string, off, count int64) string { if off < 0 { off = 0 } n := int64(len(s)) if off >= n { return "" } end := n if count >= 0 && off+count < n { end = off + count } return s[off:end] }