p9

package module
v0.7.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 30, 2026 License: MIT Imports: 5 Imported by: 10

README

p9

CI Release Go Reference

Go Plan 9 9P2000 Filesystem Network Protocol

A pure-Go implementation of the Plan 9 filesystem protocol, 9P2000 (the original 1992 spec — not the .u/.L Linux extensions), with no dependencies beyond the standard library.

go get github.com/sandgorgon/9p

Packages

Package What it is
p9 Wire encoding: message types, Marshal/Unmarshal, Qid, Stat, Mode. No I/O policy.
p9/client A 9P2000 client: dial or wrap a connection, Attach, Walk, Open, and a File implementing io.Reader/Writer/ReaderAt/WriterAt/Seeker/Closer.
p9/server A 9P2000 server: given a small FileSystem/File backend interface, handles wire encoding, fid bookkeeping, walk batching, and Tflush cancellation.
p9/examples/memfs An in-memory server.FileSystem — a demo backend and the server package's own test fixture.
p9/examples/dirfs A server.FileSystem that exports a real local directory tree, with every path validated to stay inside the configured root.
p9/cmd/9ps Serves a directory (or an empty in-memory filesystem with -mem) over TCP.
p9/cmd/9pc A bare CLI client: ls, cat, get, put.

Quick start

Serve a directory:

fs, err := dirfs.New("/path/to/export")
srv := &server.Server{FS: fs}
l, _ := net.Listen("tcp", ":5640")
srv.Serve(l)

or from the command line:

go run ./cmd/9ps -addr :5640 -root /path/to/export
go run ./cmd/9pc -addr localhost:5640 ls /
go run ./cmd/9pc -addr localhost:5640 cat /some/file
go run ./cmd/9pc -addr localhost:5640 get /some/file ./local-copy
go run ./cmd/9pc -addr localhost:5640 put ./local-file /remote-name

Talk to a server as a client:

c, err := client.Dial("tcp", "localhost:5640")
defer c.Close()

if _, err := c.Attach("glenda", ""); err != nil {
    log.Fatal(err)
}

f, err := c.Open("/some/file", p9.OREAD)
defer f.Close()
io.Copy(os.Stdout, f)

Writing a server backend

A backend only implements two small interfaces — server.FileSystem (one method, Attach) and server.File (walk to a child, open, read/write at an offset, stat, remove). The server package handles everything wire-related: message framing, fid allocation and lifecycle, splitting long walks into ≤16-element Twalk batches, and running each request in its own goroutine so a Tflush can cancel one that's still in flight. See examples/memfs for the smallest complete implementation, or examples/dirfs for one backed by real files.

Known limitations

  • No authentication: Tauth always fails with "authentication not required," and the client always attaches with NOFID. Servers that require an auth handshake before attach aren't supported.
  • Only the base 9P2000 spec — no .u (numeric uids, symlinks, special files) or .L (Linux-specific) extensions.
  • Tflush cancellation reaches a backend's context.Context parameters, but memfs/dirfs don't check them mid-operation (their operations are fast enough that it wouldn't matter); backends fronting something slower can.
  • dirfs's Qid.Path is a hash of the absolute file path, not a real inode number, so it isn't stable across a rename performed outside the server.

Testing

go build ./...
go vet ./...
go test ./...
go test -race ./...
go test -run=none -fuzz=FuzzUnmarshal -fuzztime=30s .

FuzzUnmarshal (in the root package) is the one place this matters most: it's the function parsing untrusted bytes off the wire, and it must never panic regardless of how malformed the input is.

Documentation

Overview

Package p9 implements the wire encoding for the Plan 9 filesystem protocol, 9P2000, as specified in the Plan 9 manual pages intro(5) and fcall(3). It provides only the message types and their marshaling/unmarshaling to and from the wire format; connection handling and RPC dispatch live in the client and server packages.

Index

Constants

View Source
const (
	// Version is the protocol version string sent in Tversion.
	Version = "9P2000"

	// VersionUnknown is the version string a server returns when it
	// does not support the version requested in Tversion.
	VersionUnknown = "unknown"

	// DefaultMsize is a reasonable default maximum message size for
	// a client or server that has no other preference.
	DefaultMsize = 8192

	// MaxWElem is the maximum number of path elements permitted in a
	// single Twalk message.
	MaxWElem = 16
)

Variables

View Source
var (
	ErrTruncated     = errors.New("p9: message truncated")
	ErrTrailingBytes = errors.New("p9: trailing bytes after message")
	ErrMsgTooLarge   = errors.New("p9: message exceeds msize")
	ErrUnknownType   = errors.New("p9: unknown message type")
	ErrTooManyWElem  = errors.New("p9: too many walk elements")
)

Errors returned while framing or decoding a 9P2000 message. These indicate a malformed message on the wire — protocol-level failures (a file not found, permission denied, and so on) are reported by the peer as an RerrorFcall instead.

Functions

func Marshal

func Marshal(tag Tag, m Message) []byte

Marshal encodes tag and m into a complete wire message, including the leading 4-byte size prefix.

func ReadMessage

func ReadMessage(r io.Reader, msize uint32) ([]byte, error)

ReadMessage reads one complete 9P2000 message from r: a 4-byte little-endian size prefix followed by size-4 bytes of body. The returned slice includes the size prefix, as Unmarshal expects. If msize is non-zero, messages larger than msize are rejected without being read into memory, which bounds how much a misbehaving peer can force a reader to allocate.

func Unmarshal

func Unmarshal(raw []byte) (Tag, Message, error)

Unmarshal parses a complete wire message, as produced by ReadMessage, into its tag and typed Message.

func WriteMessage

func WriteMessage(w io.Writer, raw []byte) error

WriteMessage writes raw, as produced by Marshal, to w.

Types

type FcallType

type FcallType uint8

FcallType is the one-byte message type that begins every 9P2000 message on the wire, immediately after the 4-byte size.

const (
	Tversion FcallType = 100
	Rversion FcallType = 101
	Tauth    FcallType = 102
	Rauth    FcallType = 103
	Tattach  FcallType = 104
	Rattach  FcallType = 105
	// 106 (Terror) is illegal and never sent on the wire.
	Rerror  FcallType = 107
	Tflush  FcallType = 108
	Rflush  FcallType = 109
	Twalk   FcallType = 110
	Rwalk   FcallType = 111
	Topen   FcallType = 112
	Ropen   FcallType = 113
	Tcreate FcallType = 114
	Rcreate FcallType = 115
	Tread   FcallType = 116
	Rread   FcallType = 117
	Twrite  FcallType = 118
	Rwrite  FcallType = 119
	Tclunk  FcallType = 120
	Rclunk  FcallType = 121
	Tremove FcallType = 122
	Rremove FcallType = 123
	Tstat   FcallType = 124
	Rstat   FcallType = 125
	Twstat  FcallType = 126
	Rwstat  FcallType = 127
)

func (FcallType) String

func (t FcallType) String() string

type Fid

type Fid uint32

Fid is a client-chosen handle identifying a file on the server, analogous to a file descriptor.

const NoFid Fid = 0xFFFFFFFF

NoFid marks the absence of an authentication fid in Tattach.

type Message

type Message interface {
	MsgType() FcallType
	// contains filtered or unexported methods
}

Message is implemented by every 9P2000 message body (everything after the tag). Marshal and Unmarshal combine a Message with a Tag to produce or parse a complete wire message.

type Mode

type Mode uint32

Mode holds either an open mode (Topen.Mode, Tcreate.Mode — one byte on the wire) or a permission mode (Stat.Mode, Tcreate.Perm — four bytes on the wire). The two share no bits in practice, so one type serves both roles; callers marshal it as one or four bytes depending on which field it appears in.

const (
	OREAD   Mode = 0
	OWRITE  Mode = 1
	ORDWR   Mode = 2
	OEXEC   Mode = 3
	OTRUNC  Mode = 0x10
	ORCLOSE Mode = 0x40
)

Open-mode bits: the low two bits select the access mode, the rest are flags.

const (
	DMDIR    Mode = 0x80000000
	DMAPPEND Mode = 0x40000000
	DMEXCL   Mode = 0x20000000
	DMTMP    Mode = 0x04000000
	DMPerm   Mode = 0777
)

Permission-mode bits: the top bits mark directories and other special files, the low 9 bits are rwxrwxrwx permissions.

func (Mode) IsDir

func (m Mode) IsDir() bool

IsDir reports whether the permission Mode marks a directory.

type Qid

type Qid struct {
	Type    QidType
	Version uint32
	Path    uint64
}

Qid is the server's identification for a file: its type, a version number that changes whenever the file's contents change, and a path that is unique among all files served on a connection.

func (Qid) IsDir

func (q Qid) IsDir() bool

IsDir reports whether the Qid identifies a directory.

func (Qid) String

func (q Qid) String() string

type QidType

type QidType uint8

QidType identifies the type of a file. It occupies the type byte of a Qid and mirrors the high bits of the file's permission Mode.

const (
	QTDIR    QidType = 0x80
	QTAPPEND QidType = 0x40
	QTEXCL   QidType = 0x20
	QTMOUNT  QidType = 0x10
	QTAUTH   QidType = 0x08
	QTTMP    QidType = 0x04
	QTFILE   QidType = 0x00
)

func (QidType) String

func (t QidType) String() string

type RattachFcall

type RattachFcall struct {
	Qid Qid
}

func (*RattachFcall) MsgType

func (m *RattachFcall) MsgType() FcallType

type RauthFcall

type RauthFcall struct {
	Aqid Qid
}

func (*RauthFcall) MsgType

func (m *RauthFcall) MsgType() FcallType

type RclunkFcall

type RclunkFcall struct{}

func (*RclunkFcall) MsgType

func (m *RclunkFcall) MsgType() FcallType

type RcreateFcall

type RcreateFcall struct {
	Qid    Qid
	Iounit uint32
}

func (*RcreateFcall) MsgType

func (m *RcreateFcall) MsgType() FcallType

type RerrorFcall

type RerrorFcall struct {
	Ename string
}

RerrorFcall is the error reply to any T-message. It implements the error interface so it can be returned directly from client RPC calls.

func (*RerrorFcall) Error

func (m *RerrorFcall) Error() string

func (*RerrorFcall) MsgType

func (m *RerrorFcall) MsgType() FcallType

type RflushFcall

type RflushFcall struct{}

func (*RflushFcall) MsgType

func (m *RflushFcall) MsgType() FcallType

type RopenFcall

type RopenFcall struct {
	Qid    Qid
	Iounit uint32
}

func (*RopenFcall) MsgType

func (m *RopenFcall) MsgType() FcallType

type RreadFcall

type RreadFcall struct {
	Data []byte
}

func (*RreadFcall) MsgType

func (m *RreadFcall) MsgType() FcallType

type RremoveFcall

type RremoveFcall struct{}

func (*RremoveFcall) MsgType

func (m *RremoveFcall) MsgType() FcallType

type RstatFcall

type RstatFcall struct {
	Stat Stat
}

func (*RstatFcall) MsgType

func (m *RstatFcall) MsgType() FcallType

type RversionFcall

type RversionFcall struct {
	Msize   uint32
	Version string
}

func (*RversionFcall) MsgType

func (m *RversionFcall) MsgType() FcallType

type RwalkFcall

type RwalkFcall struct {
	Wqid []Qid
}

func (*RwalkFcall) MsgType

func (m *RwalkFcall) MsgType() FcallType

type RwriteFcall

type RwriteFcall struct {
	Count uint32
}

func (*RwriteFcall) MsgType

func (m *RwriteFcall) MsgType() FcallType

type RwstatFcall

type RwstatFcall struct{}

func (*RwstatFcall) MsgType

func (m *RwstatFcall) MsgType() FcallType

type Stat

type Stat struct {
	Type   uint16 // kernel use; servers should leave this zero
	Dev    uint32 // kernel use; servers should leave this zero
	Qid    Qid
	Mode   Mode
	Atime  uint32
	Mtime  uint32
	Length uint64
	Name   string
	Uid    string
	Gid    string
	Muid   string
}

Stat describes a file's metadata, as used in Tstat/Rstat, Twstat, and directory contents (a directory Read returns a concatenation of Stat blobs, one per entry).

func UnmarshalStat

func UnmarshalStat(b []byte) (Stat, error)

UnmarshalStat decodes a single Stat blob, including its leading size prefix. It returns ErrTrailingBytes if b holds more than one Stat's worth of data.

func (Stat) Marshal

func (s Stat) Marshal() []byte

Marshal encodes s as it appears on the wire: a 2-byte size prefix (the length of everything that follows) followed by the fixed and string fields.

type Tag

type Tag uint16

Tag identifies an in-flight request so a reply can be matched to it. NoTag marks a message with no associated tag.

const NoTag Tag = 0xFFFF

NoTag marks a message with no associated tag. It is only ever used on the first Tversion of a connection.

type TattachFcall

type TattachFcall struct {
	Fid   Fid
	Afid  Fid
	Uname string
	Aname string
}

func (*TattachFcall) MsgType

func (m *TattachFcall) MsgType() FcallType

type TauthFcall

type TauthFcall struct {
	Afid  Fid
	Uname string
	Aname string
}

func (*TauthFcall) MsgType

func (m *TauthFcall) MsgType() FcallType

type TclunkFcall

type TclunkFcall struct {
	Fid Fid
}

func (*TclunkFcall) MsgType

func (m *TclunkFcall) MsgType() FcallType

type TcreateFcall

type TcreateFcall struct {
	Fid  Fid
	Name string
	Perm Mode
	Mode Mode
}

func (*TcreateFcall) MsgType

func (m *TcreateFcall) MsgType() FcallType

type TflushFcall

type TflushFcall struct {
	Oldtag Tag
}

func (*TflushFcall) MsgType

func (m *TflushFcall) MsgType() FcallType

type TopenFcall

type TopenFcall struct {
	Fid  Fid
	Mode Mode
}

func (*TopenFcall) MsgType

func (m *TopenFcall) MsgType() FcallType

type TreadFcall

type TreadFcall struct {
	Fid    Fid
	Offset uint64
	Count  uint32
}

func (*TreadFcall) MsgType

func (m *TreadFcall) MsgType() FcallType

type TremoveFcall

type TremoveFcall struct {
	Fid Fid
}

func (*TremoveFcall) MsgType

func (m *TremoveFcall) MsgType() FcallType

type TstatFcall

type TstatFcall struct {
	Fid Fid
}

func (*TstatFcall) MsgType

func (m *TstatFcall) MsgType() FcallType

type TversionFcall

type TversionFcall struct {
	Msize   uint32
	Version string
}

func (*TversionFcall) MsgType

func (m *TversionFcall) MsgType() FcallType

type TwalkFcall

type TwalkFcall struct {
	Fid    Fid
	Newfid Fid
	Wname  []string
}

func (*TwalkFcall) MsgType

func (m *TwalkFcall) MsgType() FcallType

type TwriteFcall

type TwriteFcall struct {
	Fid    Fid
	Offset uint64
	Data   []byte
}

func (*TwriteFcall) MsgType

func (m *TwriteFcall) MsgType() FcallType

type TwstatFcall

type TwstatFcall struct {
	Fid  Fid
	Stat Stat
}

func (*TwstatFcall) MsgType

func (m *TwstatFcall) MsgType() FcallType

Directories

Path Synopsis
Package client implements a 9P2000 client: dial or wrap a connection, attach to a server's exported tree, and walk, open, read, write, and stat files on it.
Package client implements a 9P2000 client: dial or wrap a connection, attach to a server's exported tree, and walk, open, read, write, and stat files on it.
cmd
9pc command
Command 9pc is a bare 9P2000 client: ls, cat, get, and put against a running 9P server, for manual testing and quick scripting.
Command 9pc is a bare 9P2000 client: ls, cat, get, and put against a running 9P server, for manual testing and quick scripting.
9ps command
Command 9ps serves a filesystem over 9P2000 on a TCP address: either a real directory (the default) or an in-memory scratch filesystem (-mem).
Command 9ps serves a filesystem over 9P2000 on a TCP address: either a real directory (the default) or an in-memory scratch filesystem (-mem).
examples
dirfs
Package dirfs is a server.FileSystem backend that exports a real directory tree from the local filesystem, using only the standard library.
Package dirfs is a server.FileSystem backend that exports a real directory tree from the local filesystem, using only the standard library.
memfs
Package memfs is an in-memory server.FileSystem backend: a tree of files and directories held entirely in memory, useful both as a demo server and as a test fixture for the server package.
Package memfs is an in-memory server.FileSystem backend: a tree of files and directories held entirely in memory, useful both as a demo server and as a test fixture for the server package.
Package server implements a 9P2000 server: given a FileSystem backend, it handles wire encoding, fid bookkeeping, multi-element walk batching, and Tflush cancellation, and exposes the backend to any 9P2000 client over a net.Listener or a single connection.
Package server implements a 9P2000 server: given a FileSystem backend, it handles wire encoding, fid bookkeeping, multi-element walk batching, and Tflush cancellation, and exposes the backend to any 9P2000 client over a net.Listener or a single connection.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL