go-p9p: github.com/docker/go-p9p Index | Files | Directories

package p9p

import "github.com/docker/go-p9p"

Package p9p implements a compliant 9P2000 client and server library for use in modern, production Go services. This package differentiates itself in that is has departed from the plan 9 implementation primitives and better follows idiomatic Go style.

The package revolves around the session type, which is an enumeration of raw 9p message calls. A few calls, such as flush and version, have been elided, deferring their usage to the server implementation. Sessions can be trivially proxied through clients and servers.

Getting Started

The best place to get started is with Serve. Serve can be provided a connection and a handler. A typical implementation will call Serve as part of a listen/accept loop. As each network connection is created, Serve can be called with a handler for the specific connection. The handler can be implemented with a Session via the Dispatch function or can generate sessions for dispatch in response to client messages. (See cmd/9ps for an example)

On the client side, NewSession provides a 9p session from a connection. After a version negotiation, methods can be called on the session, in parallel, and calls will be sent over the connection. Call timeouts can be controlled via the context provided to each method call.


This package has the beginning of a nice client-server framework for working with 9p. Some of the abstractions aren't entirely fleshed out, but most of this can center around the Handler.

Missing from this are a number of tools for implementing 9p servers. The most glaring are directory read and walk helpers. Other, more complex additions might be a system to manage in memory filesystem trees that expose multi-user sessions.


The largest difference between this package and other 9p packages is simplification of the types needed to implement a server. To avoid confusing bugs and odd behavior, the components are separated by each level of the protocol. One example is that requests and responses are separated and they no longer hold mutable state. This means that framing, transport management, encoding, and dispatching are componentized. Little work will be required to swap out encodings, transports or connection implementations.

Context Integration

This package has been wired from top to bottom to support context-based resource management. Everything from startup to shutdown can have timeouts using contexts. Not all close methods are fully in place, but we are very close to having controlled, predictable cleanup for both servers and clients. Timeouts can be very granular or very course, depending on the context of the timeout. For example, it is very easy to set a short timeout for a stat call but a long timeout for reading data.

Multiversion Support

Currently, there is not multiversion support. The hooks and functionality are in place to add multi-version support. Generally, the correct space to do this is in the codec. Types, such as Dir, simply need to be extended to support the possibility of extra fields.

The real question to ask here is what is the role of the version number in the 9p protocol. It really comes down to the level of support required. Do we just need it at the protocol level, or do handlers and sessions need to be have differently based on negotiated versions?


This package has a number of TODOs to make it easier to use. Most of the existing code provides a solid base to work from. Don't be discouraged by the sawdust.

In addition, the testing is embarassingly lacking. With time, we can get full testing going and ensure we have confidence in the implementation.


Package Files

channel.go client.go context.go dispatcher.go doc.go encoding.go errors.go fcall.go messages.go overflow.go readdir.go server.go session.go transport.go types.go version.go


const (
    // DefaultMSize messages size used to establish a session.
    DefaultMSize = 64 << 10

    // DefaultVersion for this package. Currently, the only supported version.
    DefaultVersion = "9P2000"
const (
    DMDIR    = 0x80000000 // mode bit for directories
    DMAPPEND = 0x40000000 // mode bit for append only files
    DMEXCL   = 0x20000000 // mode bit for exclusive use files
    DMMOUNT  = 0x10000000 // mode bit for mounted channel
    DMAUTH   = 0x08000000 // mode bit for authentication file
    DMTMP    = 0x04000000 // mode bit for non-backed-up files

    DMSYMLINK   = 0x02000000
    DMDEVICE    = 0x00800000
    DMNAMEDPIPE = 0x00200000
    DMSOCKET    = 0x00100000
    DMSETUID    = 0x00080000
    DMSETGID    = 0x00040000

    DMREAD  = 0x4 // mode bit for read permission
    DMWRITE = 0x2 // mode bit for write permission
    DMEXEC  = 0x1 // mode bit for execute permission

Mode constants for use Dir.Mode.


var (
    ErrBadattach    = new9pError("unknown specifier in attach")
    ErrBadoffset    = new9pError("bad offset")
    ErrBadcount     = new9pError("bad count")
    ErrBotch        = new9pError("9P protocol botch")
    ErrCreatenondir = new9pError("create in non-directory")
    ErrDupfid       = new9pError("duplicate fid")
    ErrDuptag       = new9pError("duplicate tag")
    ErrIsdir        = new9pError("is a directory")
    ErrNocreate     = new9pError("create prohibited")
    ErrNomem        = new9pError("out of memory")
    ErrNoremove     = new9pError("remove prohibited")
    ErrNostat       = new9pError("stat prohibited")
    ErrNotfound     = new9pError("file not found")
    ErrNowrite      = new9pError("write prohibited")
    ErrNowstat      = new9pError("wstat prohibited")
    ErrPerm         = new9pError("permission denied")
    ErrUnknownfid   = new9pError("unknown fid")
    ErrBaddir       = new9pError("bad directory in wstat")
    ErrWalknodir    = new9pError("walk in non-directory")

    ErrTimeout       = new9pError("fcall timeout") // returned when timing out on the fcall
    ErrUnknownTag    = new9pError("unknown tag")
    ErrUnknownMsg    = new9pError("unknown message")    // returned when encountering unknown message type
    ErrUnexpectedMsg = new9pError("unexpected message") // returned when an unexpected message is encountered
    ErrWalkLimit     = new9pError("too many wnames in walk")
    ErrClosed        = errors.New("closed")

9p wire errors returned by Session interface methods

func DecodeDir Uses

func DecodeDir(codec Codec, rd io.Reader, d *Dir) error

DecodeDir decodes a directory entry from rd using the provided codec.

func EncodeDir Uses

func EncodeDir(codec Codec, wr io.Writer, d *Dir) error

EncodeDir writes the directory to wr.

func GetVersion Uses

func GetVersion(ctx context.Context) string

GetVersion returns the protocol version from the context. If the version is not known, an empty string is returned. This is typically set on the context passed into function calls in a server implementation.

func Overflow Uses

func Overflow(err error) int

Overflow will return a positive number, indicating there was an overflow for the error.

func ServeConn Uses

func ServeConn(ctx context.Context, cn net.Conn, handler Handler) error

ServeConn the 9p handler over the provided network connection.

type Channel Uses

type Channel interface {
    // ReadFcall reads one fcall frame into the provided fcall structure. The
    // Fcall may be cleared whether there is an error or not. If the operation
    // is successful, the contents of the fcall will be populated in the
    // argument. ReadFcall cannot be called concurrently with other calls to
    // ReadFcall. This both to preserve message ordering and to allow lockless
    // buffer reusage.
    ReadFcall(ctx context.Context, fcall *Fcall) error

    // WriteFcall writes the provided fcall to the channel. WriteFcall cannot
    // be called concurrently with other calls to WriteFcall.
    WriteFcall(ctx context.Context, fcall *Fcall) error

    // MSize returns the current msize for the channel.
    MSize() int

    // SetMSize sets the maximum message size for the channel. This must never
    // be called currently with ReadFcall or WriteFcall.
    SetMSize(msize int)

Channel defines the operations necessary to implement a 9p message channel interface. Typically, message channels do no protocol processing except to send and receive message frames.

func NewChannel Uses

func NewChannel(conn net.Conn, msize int) Channel

NewChannel returns a new channel to read and write Fcalls with the provided connection and message size.

type Codec Uses

type Codec interface {
    // Unmarshal from data into the value pointed to by v.
    Unmarshal(data []byte, v interface{}) error

    // Marshal the value v into a byte slice.
    Marshal(v interface{}) ([]byte, error)

    // Size returns the encoded size for the target of v.
    Size(v interface{}) int

Codec defines the interface for encoding and decoding of 9p types. Unsupported types will throw an error.

func NewCodec Uses

func NewCodec() Codec

NewCodec returns a new, standard 9P2000 codec, ready for use.

type Dir Uses

type Dir struct {
    Type uint16
    Dev  uint32
    Qid  Qid
    Mode uint32

    AccessTime time.Time
    ModTime    time.Time

    Length uint64
    Name   string
    UID    string
    GID    string
    MUID   string

Dir defines the structure used for expressing resources in stat/wstat and when reading directories.

func ReaddirAll Uses

func ReaddirAll(session Session, fid Fid) ([]Dir, error)

ReaddirAll reads all the directory entries for the resource fid.

func (Dir) String Uses

func (d Dir) String() string

type Fcall Uses

type Fcall struct {
    Type    FcallType
    Tag     Tag
    Message Message

Fcall defines the fields for sending a 9p formatted message. The type will be introspected from the Message implementation.

func (*Fcall) String Uses

func (fc *Fcall) String() string

type FcallType Uses

type FcallType uint8

FcallType encodes the message type for the target Fcall.

const (
    Tversion FcallType = iota + 100

Definitions for Fcall's used in 9P2000.

func (FcallType) String Uses

func (fct FcallType) String() string

type Fid Uses

type Fid uint32

Fid defines a type to hold Fid values.

const NOFID Fid = ^Fid(0)

NOFID indicates the lack of an Fid.

type Flag Uses

type Flag uint8

Flag defines the flag type for use with open and create

const (
    OREAD  Flag = 0x00 // open for read
    OWRITE Flag = 0x01 // write
    ORDWR  Flag = 0x02 // read and write
    OEXEC  Flag = 0x03 // execute, == read but check execute permission

    OSYMLINK Flag = 0x04

    OTRUNC  Flag = 0x10 // or'ed in (except for exec), truncate file first
    OCEXEC  Flag = 0x20 // or'ed in, close on exec
    ORCLOSE Flag = 0x40 // or'ed in, remove on close

Constants to use when opening files.

type Handler Uses

type Handler interface {
    Handle(ctx context.Context, msg Message) (Message, error)

Handler defines an interface for 9p message handlers. A handler implementation could be used to intercept calls of all types before sending them to the next handler.

func Dispatch Uses

func Dispatch(session Session) Handler

Dispatch returns a handler that dispatches messages to the target session. No concurrency is managed by the returned handler. It simply turns messages into function calls on the session.

type HandlerFunc Uses

type HandlerFunc func(ctx context.Context, msg Message) (Message, error)

HandlerFunc is a convenience type for defining inline handlers.

func (HandlerFunc) Handle Uses

func (fn HandlerFunc) Handle(ctx context.Context, msg Message) (Message, error)

Handle implements the requirements for the Handler interface.

type Message Uses

type Message interface {
    // Type returns the type of call for the target message.
    Type() FcallType

Message represents the target of an fcall.

type MessageRattach Uses

type MessageRattach struct {
    Qid Qid

func (MessageRattach) Type Uses

func (MessageRattach) Type() FcallType

type MessageRauth Uses

type MessageRauth struct {
    Qid Qid

func (MessageRauth) Type Uses

func (MessageRauth) Type() FcallType

type MessageRclunk Uses

type MessageRclunk struct{}

func (MessageRclunk) Type Uses

func (MessageRclunk) Type() FcallType

type MessageRcreate Uses

type MessageRcreate struct {
    Qid    Qid
    IOUnit uint32

func (MessageRcreate) Type Uses

func (MessageRcreate) Type() FcallType

type MessageRerror Uses

type MessageRerror struct {
    Ename string

MessageRerror provides both a Go error type and message type.

func (MessageRerror) Error Uses

func (e MessageRerror) Error() string

func (MessageRerror) Type Uses

func (MessageRerror) Type() FcallType

Type ensures that 9p errors can be transparently used as a 9p message in an Fcall.

type MessageRflush Uses

type MessageRflush struct{}

func (MessageRflush) Type Uses

func (MessageRflush) Type() FcallType

type MessageRopen Uses

type MessageRopen struct {
    Qid    Qid
    IOUnit uint32

func (MessageRopen) Type Uses

func (MessageRopen) Type() FcallType

type MessageRread Uses

type MessageRread struct {
    Data []byte

func (MessageRread) Type Uses

func (MessageRread) Type() FcallType

type MessageRremove Uses

type MessageRremove struct{}

func (MessageRremove) Type Uses

func (MessageRremove) Type() FcallType

type MessageRstat Uses

type MessageRstat struct {
    Stat Dir

func (MessageRstat) Type Uses

func (MessageRstat) Type() FcallType

type MessageRversion Uses

type MessageRversion struct {
    MSize   uint32
    Version string

func (MessageRversion) Type Uses

func (MessageRversion) Type() FcallType

type MessageRwalk Uses

type MessageRwalk struct {
    Qids []Qid

func (MessageRwalk) Type Uses

func (MessageRwalk) Type() FcallType

type MessageRwrite Uses

type MessageRwrite struct {
    Count uint32

func (MessageRwrite) Type Uses

func (MessageRwrite) Type() FcallType

type MessageRwstat Uses

type MessageRwstat struct{}

func (MessageRwstat) Type Uses

func (MessageRwstat) Type() FcallType

type MessageTattach Uses

type MessageTattach struct {
    Fid   Fid
    Afid  Fid
    Uname string
    Aname string

func (MessageTattach) Type Uses

func (MessageTattach) Type() FcallType

type MessageTauth Uses

type MessageTauth struct {
    Afid  Fid
    Uname string
    Aname string

func (MessageTauth) Type Uses

func (MessageTauth) Type() FcallType

type MessageTclunk Uses

type MessageTclunk struct {
    Fid Fid

func (MessageTclunk) Type Uses

func (MessageTclunk) Type() FcallType

type MessageTcreate Uses

type MessageTcreate struct {
    Fid  Fid
    Name string
    Perm uint32
    Mode Flag

func (MessageTcreate) Type Uses

func (MessageTcreate) Type() FcallType

type MessageTflush Uses

type MessageTflush struct {
    Oldtag Tag

func (MessageTflush) Type Uses

func (MessageTflush) Type() FcallType

type MessageTopen Uses

type MessageTopen struct {
    Fid  Fid
    Mode Flag

func (MessageTopen) Type Uses

func (MessageTopen) Type() FcallType

type MessageTread Uses

type MessageTread struct {
    Fid    Fid
    Offset uint64
    Count  uint32

func (MessageTread) Type Uses

func (MessageTread) Type() FcallType

type MessageTremove Uses

type MessageTremove struct {
    Fid Fid

func (MessageTremove) Type Uses

func (MessageTremove) Type() FcallType

type MessageTstat Uses

type MessageTstat struct {
    Fid Fid

func (MessageTstat) Type Uses

func (MessageTstat) Type() FcallType

type MessageTversion Uses

type MessageTversion struct {
    MSize   uint32
    Version string

MessageVersion encodes the message body for Tversion and Rversion RPC calls. The body is identical in both directions.

func (MessageTversion) Type Uses

func (MessageTversion) Type() FcallType

type MessageTwalk Uses

type MessageTwalk struct {
    Fid    Fid
    Newfid Fid
    Wnames []string

func (MessageTwalk) Type Uses

func (MessageTwalk) Type() FcallType

type MessageTwrite Uses

type MessageTwrite struct {
    Fid    Fid
    Offset uint64
    Data   []byte

func (MessageTwrite) Type Uses

func (MessageTwrite) Type() FcallType

type MessageTwstat Uses

type MessageTwstat struct {
    Fid  Fid
    Stat Dir

func (MessageTwstat) Type Uses

func (MessageTwstat) Type() FcallType

type QType Uses

type QType uint8

QType indicates the type of a resource within the Qid.

const (
    QTDIR    QType = 0x80 // type bit for directories
    QTAPPEND QType = 0x40 // type bit for append only files
    QTEXCL   QType = 0x20 // type bit for exclusive use files
    QTMOUNT  QType = 0x10 // type bit for mounted channel
    QTAUTH   QType = 0x08 // type bit for authentication file
    QTTMP    QType = 0x04 // type bit for not-backed-up file
    QTFILE   QType = 0x00 // plain file

Constants for use in Qid to indicate resource type.

func (QType) String Uses

func (qt QType) String() string

type Qid Uses

type Qid struct {
    Type    QType `9p:"type,1"`
    Version uint32
    Path    uint64

Qid indicates the type, path and version of the resource returned by a server. It is only valid for a session.

Typically, a client maintains a mapping of Fid-Qid as Qids are returned by the server.

func (Qid) String Uses

func (qid Qid) String() string

type Readdir Uses

type Readdir struct {
    // contains filtered or unexported fields

Readdir helps one to implement the server-side of Session.Read on directories.

func NewFixedReaddir Uses

func NewFixedReaddir(codec Codec, dir []Dir) *Readdir

NewFixedReaddir returns a Readdir that will returned a fixed set of directory entries.

func NewReaddir Uses

func NewReaddir(codec Codec, next func() (Dir, error)) *Readdir

NewReaddir returns a new Readdir to assist implementing server-side Readdir. The codec will be used to decode messages with Dir entries. The provided function next will be called until io.EOF is returned.

func (*Readdir) Read Uses

func (rd *Readdir) Read(ctx context.Context, p []byte, offset int64) (n int, err error)

type Session Uses

type Session interface {
    Auth(ctx context.Context, afid Fid, uname, aname string) (Qid, error)
    Attach(ctx context.Context, fid, afid Fid, uname, aname string) (Qid, error)
    Clunk(ctx context.Context, fid Fid) error
    Remove(ctx context.Context, fid Fid) error
    Walk(ctx context.Context, fid Fid, newfid Fid, names ...string) ([]Qid, error)

    // Read follows the semantics of io.ReaderAt.ReadAtt method except it takes
    // a contxt and Fid.
    Read(ctx context.Context, fid Fid, p []byte, offset int64) (n int, err error)

    // Write follows the semantics of io.WriterAt.WriteAt except takes a context and an Fid.
    // If n == len(p), no error is returned.
    // If n < len(p), io.ErrShortWrite will be returned.
    Write(ctx context.Context, fid Fid, p []byte, offset int64) (n int, err error)

    Open(ctx context.Context, fid Fid, mode Flag) (Qid, uint32, error)
    Create(ctx context.Context, parent Fid, name string, perm uint32, mode Flag) (Qid, uint32, error)
    Stat(ctx context.Context, fid Fid) (Dir, error)
    WStat(ctx context.Context, fid Fid, dir Dir) error

    // Version returns the supported version and msize of the session. This
    // can be affected by negotiating or the level of support provided by the
    // session implementation.
    Version() (msize int, version string)

Session provides the central abstraction for a 9p connection. Clients implement sessions and servers serve sessions. Sessions can be proxied by serving up a client session.

The interface is also wired up with full context support to manage timeouts and resource clean up.

Session represents the operations covered in section 5 of the plan 9 manual (http://man.cat-v.org/plan_9/5/). Requests are managed internally, so the Flush method is handled by the internal implementation. Consider preceeding these all with context to control request timeout.

func NewSession Uses

func NewSession(ctx context.Context, conn net.Conn) (Session, error)

NewSession returns a session using the connection. The Context ctx provides a context for out of bad messages, such as flushes, that may be sent by the session. The session can effectively shutdown with this context.

type Tag Uses

type Tag uint16

Tag uniquely identifies an outstanding fcall in a 9p session.

const NOTAG Tag = ^Tag(0)

NOTAG is a reserved values for messages sent before establishing a session, such as Tversion.


Y2038 is coming.

The options here are to return 0, panic or make this return an error. Ideally, we make it safe to return 0 and have the rest of the package do the right thing. For now, we do this, but may want to panic until things are stable.

The options here are to return 0, panic or make this return an error. Ideally, we make it safe to return 0 and have the rest of the package do the right thing. For now, we do this, but may want to panic until things are stable.

There may be partial reads under timeout errors where this is actually fatal.

The exact handling of an unknown tag is unclear at this point. These may not necessarily fatal to the session, since they could be messages that the client no longer cares for. When we figure this out, replace this panic with something more sensible.

Must detect duplicate tag and ensure that we are waking up the right caller. If a duplicate is received, the entry should not be deleted.

The Year 2038 is coming soon. 9p wire protocol has these as 4 byte epoch times. Some possibilities include time dilation fields or atemporal files. We can also just not use them and set them to zero.



Package p9p imports 14 packages (graph) and is imported by 7 packages. Updated 2019-11-12. Refresh now. Tools for package owners.