summaryrefslogtreecommitdiffhomepage
path: root/pkg/p9
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/p9')
-rw-r--r--pkg/p9/BUILD47
-rw-r--r--pkg/p9/buffer.go262
-rw-r--r--pkg/p9/client.go301
-rw-r--r--pkg/p9/client_file.go513
-rw-r--r--pkg/p9/client_test.go62
-rw-r--r--pkg/p9/file.go180
-rw-r--r--pkg/p9/handlers.go705
-rw-r--r--pkg/p9/local_server/BUILD14
-rw-r--r--pkg/p9/local_server/local_server.go347
-rw-r--r--pkg/p9/messages.go2271
-rw-r--r--pkg/p9/messages_test.go391
-rw-r--r--pkg/p9/p9.go1023
-rw-r--r--pkg/p9/p9_test.go188
-rw-r--r--pkg/p9/p9test/BUILD28
-rw-r--r--pkg/p9/p9test/client_test.go335
-rw-r--r--pkg/p9/p9test/mocks.go490
-rw-r--r--pkg/p9/pool.go68
-rw-r--r--pkg/p9/pool_test.go64
-rw-r--r--pkg/p9/server.go380
-rw-r--r--pkg/p9/transport.go346
-rw-r--r--pkg/p9/transport_test.go184
-rw-r--r--pkg/p9/version.go145
-rw-r--r--pkg/p9/version_test.go145
23 files changed, 8489 insertions, 0 deletions
diff --git a/pkg/p9/BUILD b/pkg/p9/BUILD
new file mode 100644
index 000000000..f348ff2e9
--- /dev/null
+++ b/pkg/p9/BUILD
@@ -0,0 +1,47 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+package(
+ default_visibility = ["//visibility:public"],
+ licenses = ["notice"], # Apache 2.0
+)
+
+go_library(
+ name = "p9",
+ srcs = [
+ "buffer.go",
+ "client.go",
+ "client_file.go",
+ "file.go",
+ "handlers.go",
+ "messages.go",
+ "p9.go",
+ "pool.go",
+ "server.go",
+ "transport.go",
+ "version.go",
+ ],
+ importpath = "gvisor.googlesource.com/gvisor/pkg/p9",
+ deps = [
+ "//pkg/fd",
+ "//pkg/log",
+ "//pkg/unet",
+ ],
+)
+
+go_test(
+ name = "p9_test",
+ size = "small",
+ srcs = [
+ "client_test.go",
+ "messages_test.go",
+ "p9_test.go",
+ "pool_test.go",
+ "transport_test.go",
+ "version_test.go",
+ ],
+ embed = [":p9"],
+ deps = [
+ "//pkg/fd",
+ "//pkg/unet",
+ ],
+)
diff --git a/pkg/p9/buffer.go b/pkg/p9/buffer.go
new file mode 100644
index 000000000..fc65d2c5f
--- /dev/null
+++ b/pkg/p9/buffer.go
@@ -0,0 +1,262 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package p9
+
+import (
+ "encoding/binary"
+)
+
+// encoder is used for messages and 9P primitives.
+type encoder interface {
+ // Decode decodes from the given buffer.
+ //
+ // This may not fail, exhaustion will be recorded in the buffer.
+ Decode(b *buffer)
+
+ // Encode encodes to the given buffer.
+ //
+ // This may not fail.
+ Encode(b *buffer)
+}
+
+// order is the byte order used for encoding.
+var order = binary.LittleEndian
+
+// buffer is a slice that is consumed.
+//
+// This is passed to the encoder methods.
+type buffer struct {
+ // data is the underlying data. This may grow during Encode.
+ data []byte
+
+ // overflow indicates whether an overflow has occurred.
+ overflow bool
+}
+
+// append appends n bytes to the buffer and returns a slice pointing to the
+// newly appended bytes.
+func (b *buffer) append(n int) []byte {
+ b.data = append(b.data, make([]byte, n)...)
+ return b.data[len(b.data)-n:]
+}
+
+// consume consumes n bytes from the buffer.
+func (b *buffer) consume(n int) ([]byte, bool) {
+ if !b.has(n) {
+ b.markOverrun()
+ return nil, false
+ }
+ rval := b.data[:n]
+ b.data = b.data[n:]
+ return rval, true
+}
+
+// has returns true if n bytes are available.
+func (b *buffer) has(n int) bool {
+ return len(b.data) >= n
+}
+
+// markOverrun immediately marks this buffer as overrun.
+//
+// This is used by ReadString, since some invalid data implies the rest of the
+// buffer is no longer valid either.
+func (b *buffer) markOverrun() {
+ b.overflow = true
+}
+
+// isOverrun returns true if this buffer has run past the end.
+func (b *buffer) isOverrun() bool {
+ return b.overflow
+}
+
+// Read8 reads a byte from the buffer.
+func (b *buffer) Read8() uint8 {
+ v, ok := b.consume(1)
+ if !ok {
+ return 0
+ }
+ return uint8(v[0])
+}
+
+// Read16 reads a 16-bit value from the buffer.
+func (b *buffer) Read16() uint16 {
+ v, ok := b.consume(2)
+ if !ok {
+ return 0
+ }
+ return order.Uint16(v)
+}
+
+// Read32 reads a 32-bit value from the buffer.
+func (b *buffer) Read32() uint32 {
+ v, ok := b.consume(4)
+ if !ok {
+ return 0
+ }
+ return order.Uint32(v)
+}
+
+// Read64 reads a 64-bit value from the buffer.
+func (b *buffer) Read64() uint64 {
+ v, ok := b.consume(8)
+ if !ok {
+ return 0
+ }
+ return order.Uint64(v)
+}
+
+// ReadQIDType reads a QIDType value.
+func (b *buffer) ReadQIDType() QIDType {
+ return QIDType(b.Read8())
+}
+
+// ReadTag reads a Tag value.
+func (b *buffer) ReadTag() Tag {
+ return Tag(b.Read16())
+}
+
+// ReadFID reads a FID value.
+func (b *buffer) ReadFID() FID {
+ return FID(b.Read32())
+}
+
+// ReadUID reads a UID value.
+func (b *buffer) ReadUID() UID {
+ return UID(b.Read32())
+}
+
+// ReadGID reads a GID value.
+func (b *buffer) ReadGID() GID {
+ return GID(b.Read32())
+}
+
+// ReadPermissions reads a file mode value and applies the mask for permissions.
+func (b *buffer) ReadPermissions() FileMode {
+ return b.ReadFileMode() & PermissionsMask
+}
+
+// ReadFileMode reads a file mode value.
+func (b *buffer) ReadFileMode() FileMode {
+ return FileMode(b.Read32())
+}
+
+// ReadOpenFlags reads an OpenFlags.
+func (b *buffer) ReadOpenFlags() OpenFlags {
+ return OpenFlags(b.Read32())
+}
+
+// ReadConnectFlags reads a ConnectFlags.
+func (b *buffer) ReadConnectFlags() ConnectFlags {
+ return ConnectFlags(b.Read32())
+}
+
+// ReadMsgType writes a MsgType.
+func (b *buffer) ReadMsgType() MsgType {
+ return MsgType(b.Read8())
+}
+
+// ReadString deserializes a string.
+func (b *buffer) ReadString() string {
+ l := b.Read16()
+ if !b.has(int(l)) {
+ // Mark the buffer as corrupted.
+ b.markOverrun()
+ return ""
+ }
+
+ bs := make([]byte, l)
+ for i := 0; i < int(l); i++ {
+ bs[i] = byte(b.Read8())
+ }
+ return string(bs)
+}
+
+// Write8 writes a byte to the buffer.
+func (b *buffer) Write8(v uint8) {
+ b.append(1)[0] = byte(v)
+}
+
+// Write16 writes a 16-bit value to the buffer.
+func (b *buffer) Write16(v uint16) {
+ order.PutUint16(b.append(2), v)
+}
+
+// Write32 writes a 32-bit value to the buffer.
+func (b *buffer) Write32(v uint32) {
+ order.PutUint32(b.append(4), v)
+}
+
+// Write64 writes a 64-bit value to the buffer.
+func (b *buffer) Write64(v uint64) {
+ order.PutUint64(b.append(8), v)
+}
+
+// WriteQIDType writes a QIDType value.
+func (b *buffer) WriteQIDType(qidType QIDType) {
+ b.Write8(uint8(qidType))
+}
+
+// WriteTag writes a Tag value.
+func (b *buffer) WriteTag(tag Tag) {
+ b.Write16(uint16(tag))
+}
+
+// WriteFID writes a FID value.
+func (b *buffer) WriteFID(fid FID) {
+ b.Write32(uint32(fid))
+}
+
+// WriteUID writes a UID value.
+func (b *buffer) WriteUID(uid UID) {
+ b.Write32(uint32(uid))
+}
+
+// WriteGID writes a GID value.
+func (b *buffer) WriteGID(gid GID) {
+ b.Write32(uint32(gid))
+}
+
+// WritePermissions applies a permissions mask and writes the FileMode.
+func (b *buffer) WritePermissions(perm FileMode) {
+ b.WriteFileMode(perm & PermissionsMask)
+}
+
+// WriteFileMode writes a FileMode.
+func (b *buffer) WriteFileMode(mode FileMode) {
+ b.Write32(uint32(mode))
+}
+
+// WriteOpenFlags writes an OpenFlags.
+func (b *buffer) WriteOpenFlags(flags OpenFlags) {
+ b.Write32(uint32(flags))
+}
+
+// WriteConnectFlags writes a ConnectFlags.
+func (b *buffer) WriteConnectFlags(flags ConnectFlags) {
+ b.Write32(uint32(flags))
+}
+
+// WriteMsgType writes a MsgType.
+func (b *buffer) WriteMsgType(t MsgType) {
+ b.Write8(uint8(t))
+}
+
+// WriteString serializes the given string.
+func (b *buffer) WriteString(s string) {
+ b.Write16(uint16(len(s)))
+ for i := 0; i < len(s); i++ {
+ b.Write8(byte(s[i]))
+ }
+}
diff --git a/pkg/p9/client.go b/pkg/p9/client.go
new file mode 100644
index 000000000..5fa231bc5
--- /dev/null
+++ b/pkg/p9/client.go
@@ -0,0 +1,301 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package p9
+
+import (
+ "errors"
+ "fmt"
+ "sync"
+ "syscall"
+
+ "gvisor.googlesource.com/gvisor/pkg/log"
+ "gvisor.googlesource.com/gvisor/pkg/unet"
+)
+
+// ErrOutOfTags indicates no tags are available.
+var ErrOutOfTags = errors.New("out of tags -- messages lost?")
+
+// ErrOutOfFIDs indicates no more FIDs are available.
+var ErrOutOfFIDs = errors.New("out of FIDs -- messages lost?")
+
+// ErrUnexpectedTag indicates a response with an unexpected tag was received.
+var ErrUnexpectedTag = errors.New("unexpected tag in response")
+
+// ErrVersionsExhausted indicates that all versions to negotiate have been exhausted.
+var ErrVersionsExhausted = errors.New("exhausted all versions to negotiate")
+
+// ErrBadVersionString indicates that the version string is malformed or unsupported.
+var ErrBadVersionString = errors.New("bad version string")
+
+// ErrBadResponse indicates the response didn't match the request.
+type ErrBadResponse struct {
+ Got MsgType
+ Want MsgType
+}
+
+// Error returns a highly descriptive error.
+func (e *ErrBadResponse) Error() string {
+ return fmt.Sprintf("unexpected message type: got %v, want %v", e.Got, e.Want)
+}
+
+// response is the asynchronous return from recv.
+//
+// This is used in the pending map below.
+type response struct {
+ r message
+ done chan error
+}
+
+var responsePool = sync.Pool{
+ New: func() interface{} {
+ return &response{
+ done: make(chan error, 1),
+ }
+ },
+}
+
+// Client is at least a 9P2000.L client.
+type Client struct {
+ // socket is the connected socket.
+ socket *unet.Socket
+
+ // tagPool is the collection of available tags.
+ tagPool pool
+
+ // fidPool is the collection of available fids.
+ fidPool pool
+
+ // pending is the set of pending messages.
+ pending map[Tag]*response
+ pendingMu sync.Mutex
+
+ // sendMu is the lock for sending a request.
+ sendMu sync.Mutex
+
+ // recvr is essentially a mutex for calling recv.
+ //
+ // Whoever writes to this channel is permitted to call recv. When
+ // finished calling recv, this channel should be emptied.
+ recvr chan bool
+
+ // messageSize is the maximum total size of a message.
+ messageSize uint32
+
+ // payloadSize is the maximum payload size of a read or write
+ // request. For large reads and writes this means that the
+ // read or write is broken up into buffer-size/payloadSize
+ // requests.
+ payloadSize uint32
+
+ // version is the agreed upon version X of 9P2000.L.Google.X.
+ // version 0 implies 9P2000.L.
+ version uint32
+}
+
+// NewClient creates a new client. It performs a Tversion exchange with
+// the server to assert that messageSize is ok to use.
+//
+// You should not use the same socket for multiple clients.
+func NewClient(socket *unet.Socket, messageSize uint32, version string) (*Client, error) {
+ // Need at least one byte of payload.
+ if messageSize <= largestFixedSize {
+ return nil, &ErrMessageTooLarge{
+ size: messageSize,
+ msize: largestFixedSize,
+ }
+ }
+ // Compute a payload size and round to 512 (normal block size)
+ // if it's larger than a single block.
+ payloadSize := messageSize - largestFixedSize
+ if payloadSize > 512 && payloadSize%512 != 0 {
+ payloadSize -= (payloadSize % 512)
+ }
+ c := &Client{
+ socket: socket,
+ tagPool: pool{start: 1, limit: uint64(NoTag)},
+ fidPool: pool{start: 1, limit: uint64(NoFID)},
+ pending: make(map[Tag]*response),
+ recvr: make(chan bool, 1),
+ messageSize: messageSize,
+ payloadSize: payloadSize,
+ }
+ // Agree upon a version.
+ requested, ok := parseVersion(version)
+ if !ok {
+ return nil, ErrBadVersionString
+ }
+ for {
+ rversion := Rversion{}
+ err := c.sendRecv(&Tversion{Version: versionString(requested), MSize: messageSize}, &rversion)
+
+ // The server told us to try again with a lower version.
+ if err == syscall.EAGAIN {
+ if requested == lowestSupportedVersion {
+ return nil, ErrVersionsExhausted
+ }
+ requested--
+ continue
+ }
+
+ // We requested an impossible version or our other parameters were bogus.
+ if err != nil {
+ return nil, err
+ }
+
+ // Parse the version.
+ version, ok := parseVersion(rversion.Version)
+ if !ok {
+ // The server gave us a bad version. We return a generically worrisome error.
+ log.Warningf("server returned bad version string %q", rversion.Version)
+ return nil, ErrBadVersionString
+ }
+ c.version = version
+ break
+ }
+ return c, nil
+}
+
+// handleOne handles a single incoming message.
+//
+// This should only be called with the token from recvr. Note that the received
+// tag will automatically be cleared from pending.
+func (c *Client) handleOne() {
+ tag, r, err := recv(c.socket, c.messageSize, func(tag Tag, t MsgType) (message, error) {
+ c.pendingMu.Lock()
+ resp := c.pending[tag]
+ c.pendingMu.Unlock()
+
+ // Not expecting this message?
+ if resp == nil {
+ log.Warningf("client received unexpected tag %v, ignoring", tag)
+ return nil, ErrUnexpectedTag
+ }
+
+ // Is it an error? We specifically allow this to
+ // go through, and then we deserialize below.
+ if t == MsgRlerror {
+ return &Rlerror{}, nil
+ }
+
+ // Does it match expectations?
+ if t != resp.r.Type() {
+ return nil, &ErrBadResponse{Got: t, Want: resp.r.Type()}
+ }
+
+ // Return the response.
+ return resp.r, nil
+ })
+
+ if err != nil {
+ // No tag was extracted (probably a socket error).
+ //
+ // Likely catastrophic. Notify all waiters and clear pending.
+ c.pendingMu.Lock()
+ for _, resp := range c.pending {
+ resp.done <- err
+ }
+ c.pending = make(map[Tag]*response)
+ c.pendingMu.Unlock()
+ } else {
+ // Process the tag.
+ //
+ // We know that is is contained in the map because our lookup function
+ // above must have succeeded (found the tag) to return nil err.
+ c.pendingMu.Lock()
+ resp := c.pending[tag]
+ delete(c.pending, tag)
+ c.pendingMu.Unlock()
+ resp.r = r
+ resp.done <- err
+ }
+}
+
+// waitAndRecv co-ordinates with other receivers to handle responses.
+func (c *Client) waitAndRecv(done chan error) error {
+ for {
+ select {
+ case err := <-done:
+ return err
+ case c.recvr <- true:
+ select {
+ case err := <-done:
+ // It's possible that we got the token, despite
+ // done also being available. Check for that.
+ <-c.recvr
+ return err
+ default:
+ // Handle receiving one tag.
+ c.handleOne()
+
+ // Return the token.
+ <-c.recvr
+ }
+ }
+ }
+}
+
+// sendRecv performs a roundtrip message exchange.
+//
+// This is called by internal functions.
+func (c *Client) sendRecv(t message, r message) error {
+ tag, ok := c.tagPool.Get()
+ if !ok {
+ return ErrOutOfTags
+ }
+ defer c.tagPool.Put(tag)
+
+ // Indicate we're expecting a response.
+ //
+ // Note that the tag will be cleared from pending
+ // automatically (see handleOne for details).
+ resp := responsePool.Get().(*response)
+ defer responsePool.Put(resp)
+ resp.r = r
+ c.pendingMu.Lock()
+ c.pending[Tag(tag)] = resp
+ c.pendingMu.Unlock()
+
+ // Send the request over the wire.
+ c.sendMu.Lock()
+ err := send(c.socket, Tag(tag), t)
+ c.sendMu.Unlock()
+ if err != nil {
+ return err
+ }
+
+ // Co-ordinate with other receivers.
+ if err := c.waitAndRecv(resp.done); err != nil {
+ return err
+ }
+
+ // Is it an error message?
+ //
+ // For convenience, we transform these directly
+ // into errors. Handlers need not handle this case.
+ if rlerr, ok := resp.r.(*Rlerror); ok {
+ return syscall.Errno(rlerr.Error)
+ }
+
+ // At this point, we know it matches.
+ //
+ // Per recv call above, we will only allow a type
+ // match (and give our r) or an instance of Rlerror.
+ return nil
+}
+
+// Version returns the negotiated 9P2000.L.Google version number.
+func (c *Client) Version() uint32 {
+ return c.version
+}
diff --git a/pkg/p9/client_file.go b/pkg/p9/client_file.go
new file mode 100644
index 000000000..0f02980a0
--- /dev/null
+++ b/pkg/p9/client_file.go
@@ -0,0 +1,513 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package p9
+
+import (
+ "fmt"
+ "io"
+ "runtime"
+ "sync/atomic"
+ "syscall"
+
+ "gvisor.googlesource.com/gvisor/pkg/fd"
+ "gvisor.googlesource.com/gvisor/pkg/log"
+)
+
+// Attach attaches to a server.
+//
+// Note that authentication is not currently supported.
+func (c *Client) Attach(name string) (File, error) {
+ fid, ok := c.fidPool.Get()
+ if !ok {
+ return nil, ErrOutOfFIDs
+ }
+
+ rattach := Rattach{}
+ if err := c.sendRecv(&Tattach{FID: FID(fid), Auth: Tauth{AttachName: name, AuthenticationFID: NoFID, UID: NoUID}}, &rattach); err != nil {
+ c.fidPool.Put(fid)
+ return nil, err
+ }
+
+ return c.newFile(FID(fid)), nil
+}
+
+// newFile returns a new client file.
+func (c *Client) newFile(fid FID) *clientFile {
+ cf := &clientFile{
+ client: c,
+ fid: fid,
+ }
+
+ // Make sure the file is closed.
+ runtime.SetFinalizer(cf, (*clientFile).Close)
+
+ return cf
+}
+
+// clientFile is provided to clients.
+//
+// This proxies all of the interfaces found in file.go.
+type clientFile struct {
+ // client is the originating client.
+ client *Client
+
+ // fid is the FID for this file.
+ fid FID
+
+ // closed indicates whether this file has been closed.
+ closed uint32
+}
+
+// Walk implements File.Walk.
+func (c *clientFile) Walk(names []string) ([]QID, File, error) {
+ fid, ok := c.client.fidPool.Get()
+ if !ok {
+ return nil, nil, ErrOutOfFIDs
+ }
+
+ rwalk := Rwalk{}
+ if err := c.client.sendRecv(&Twalk{FID: c.fid, NewFID: FID(fid), Names: names}, &rwalk); err != nil {
+ c.client.fidPool.Put(fid)
+ return nil, nil, err
+ }
+
+ // Return a new client file.
+ return rwalk.QIDs, c.client.newFile(FID(fid)), nil
+}
+
+// WalkGetAttr implements File.WalkGetAttr.
+func (c *clientFile) WalkGetAttr(components []string) ([]QID, File, AttrMask, Attr, error) {
+ if !versionSupportsTwalkgetattr(c.client.version) {
+ qids, file, err := c.Walk(components)
+ if err != nil {
+ return nil, nil, AttrMask{}, Attr{}, err
+ }
+ _, valid, attr, err := file.GetAttr(AttrMaskAll())
+ if err != nil {
+ file.Close()
+ return nil, nil, AttrMask{}, Attr{}, err
+ }
+ return qids, file, valid, attr, nil
+ }
+
+ fid, ok := c.client.fidPool.Get()
+ if !ok {
+ return nil, nil, AttrMask{}, Attr{}, ErrOutOfFIDs
+ }
+
+ rwalkgetattr := Rwalkgetattr{}
+ if err := c.client.sendRecv(&Twalkgetattr{FID: c.fid, NewFID: FID(fid), Names: components}, &rwalkgetattr); err != nil {
+ c.client.fidPool.Put(fid)
+ return nil, nil, AttrMask{}, Attr{}, err
+ }
+
+ // Return a new client file.
+ return rwalkgetattr.QIDs, c.client.newFile(FID(fid)), rwalkgetattr.Valid, rwalkgetattr.Attr, nil
+}
+
+// StatFS implements File.StatFS.
+func (c *clientFile) StatFS() (FSStat, error) {
+ rstatfs := Rstatfs{}
+ if err := c.client.sendRecv(&Tstatfs{FID: c.fid}, &rstatfs); err != nil {
+ return FSStat{}, err
+ }
+
+ return rstatfs.FSStat, nil
+}
+
+// FSync implements File.FSync.
+func (c *clientFile) FSync() error {
+ return c.client.sendRecv(&Tfsync{FID: c.fid}, &Rfsync{})
+}
+
+// GetAttr implements File.GetAttr.
+func (c *clientFile) GetAttr(req AttrMask) (QID, AttrMask, Attr, error) {
+ rgetattr := Rgetattr{}
+ if err := c.client.sendRecv(&Tgetattr{FID: c.fid, AttrMask: req}, &rgetattr); err != nil {
+ return QID{}, AttrMask{}, Attr{}, err
+ }
+
+ return rgetattr.QID, rgetattr.Valid, rgetattr.Attr, nil
+}
+
+// SetAttr implements File.SetAttr.
+func (c *clientFile) SetAttr(valid SetAttrMask, attr SetAttr) error {
+ return c.client.sendRecv(&Tsetattr{FID: c.fid, Valid: valid, SetAttr: attr}, &Rsetattr{})
+}
+
+// Remove implements File.Remove.
+func (c *clientFile) Remove() error {
+ if err := c.client.sendRecv(&Tremove{FID: c.fid}, &Rremove{}); err != nil {
+ log.Warningf("Tremove failed, losing FID %v: %v", c.fid, err)
+ return err
+ }
+
+ // "It is correct to consider remove to be a clunk with the side effect
+ // of removing the file if permissions allow."
+ // https://swtch.com/plan9port/man/man9/remove.html
+ // Return the FID to the pool.
+ c.client.fidPool.Put(uint64(c.fid))
+ return nil
+}
+
+// Close implements File.Close.
+func (c *clientFile) Close() error {
+ // Avoid double close.
+ if !atomic.CompareAndSwapUint32(&c.closed, 0, 1) {
+ return nil
+ }
+
+ // Send the close message.
+ if err := c.client.sendRecv(&Tclunk{FID: c.fid}, &Rclunk{}); err != nil {
+ // If an error occurred, we toss away the FID. This isn't ideal,
+ // but I'm not sure what else makes sense in this context.
+ log.Warningf("Tclunk failed, losing FID %v: %v", c.fid, err)
+ return err
+ }
+
+ // Return the FID to the pool.
+ c.client.fidPool.Put(uint64(c.fid))
+ return nil
+}
+
+// Open implements File.Open.
+func (c *clientFile) Open(flags OpenFlags) (*fd.FD, QID, uint32, error) {
+ rlopen := Rlopen{}
+ if err := c.client.sendRecv(&Tlopen{FID: c.fid, Flags: flags}, &rlopen); err != nil {
+ return nil, QID{}, 0, err
+ }
+
+ return rlopen.File, rlopen.QID, rlopen.IoUnit, nil
+}
+
+// Connect implements File.Connect.
+func (c *clientFile) Connect(flags ConnectFlags) (*fd.FD, error) {
+ if !VersionSupportsConnect(c.client.version) {
+ return nil, syscall.ECONNREFUSED
+ }
+ rlconnect := Rlconnect{}
+ if err := c.client.sendRecv(&Tlconnect{FID: c.fid, Flags: flags}, &rlconnect); err != nil {
+ return nil, err
+ }
+
+ return rlconnect.File, nil
+}
+
+// chunk applies fn to p in chunkSize-sized chunks until fn returns a partial result, p is
+// exhausted, or an error is encountered (which may be io.EOF).
+func chunk(chunkSize uint32, fn func([]byte, uint64) (int, error), p []byte, offset uint64) (int, error) {
+ // Some p9.Clients depend on executing fn on zero-byte buffers. Handle this
+ // as a special case (normally it is fine to short-circuit and return (0, nil)).
+ if len(p) == 0 {
+ return fn(p, offset)
+ }
+
+ // total is the cumulative bytes processed.
+ var total int
+ for {
+ var n int
+ var err error
+
+ // We're done, don't bother trying to do anything more.
+ if total == len(p) {
+ return total, nil
+ }
+
+ // Apply fn to a chunkSize-sized (or less) chunk of p.
+ if len(p) < total+int(chunkSize) {
+ n, err = fn(p[total:], offset)
+ } else {
+ n, err = fn(p[total:total+int(chunkSize)], offset)
+ }
+ total += n
+ offset += uint64(n)
+
+ // Return whatever we have processed if we encounter an error. This error
+ // could be io.EOF.
+ if err != nil {
+ return total, err
+ }
+
+ // Did we get a partial result? If so, return it immediately.
+ if n < int(chunkSize) {
+ return total, nil
+ }
+
+ // If we received more bytes than we ever requested, this is a problem.
+ if total > len(p) {
+ panic(fmt.Sprintf("bytes completed (%d)) > requested (%d)", total, len(p)))
+ }
+ }
+}
+
+// ReadAt proxies File.ReadAt.
+func (c *clientFile) ReadAt(p []byte, offset uint64) (int, error) {
+ return chunk(c.client.payloadSize, c.readAt, p, offset)
+}
+
+func (c *clientFile) readAt(p []byte, offset uint64) (int, error) {
+ rread := Rread{Data: p}
+ if err := c.client.sendRecv(&Tread{FID: c.fid, Offset: offset, Count: uint32(len(p))}, &rread); err != nil {
+ return 0, err
+ }
+
+ // The message may have been truncated, or for some reason a new buffer
+ // allocated. This isn't the common path, but we make sure that if the
+ // payload has changed we copy it. See transport.go for more information.
+ if len(p) > 0 && len(rread.Data) > 0 && &rread.Data[0] != &p[0] {
+ copy(p, rread.Data)
+ }
+
+ // io.EOF is not an error that a p9 server can return. Use POSIX semantics to
+ // return io.EOF manually: zero bytes were returned and a non-zero buffer was used.
+ if len(rread.Data) == 0 && len(p) > 0 {
+ return 0, io.EOF
+ }
+
+ return len(rread.Data), nil
+}
+
+// WriteAt proxies File.WriteAt.
+func (c *clientFile) WriteAt(p []byte, offset uint64) (int, error) {
+ return chunk(c.client.payloadSize, c.writeAt, p, offset)
+}
+
+func (c *clientFile) writeAt(p []byte, offset uint64) (int, error) {
+ rwrite := Rwrite{}
+ if err := c.client.sendRecv(&Twrite{FID: c.fid, Offset: offset, Data: p}, &rwrite); err != nil {
+ return 0, err
+ }
+
+ return int(rwrite.Count), nil
+}
+
+// ReadWriterFile wraps a File and implements io.ReadWriter, io.ReaderAt, and io.WriterAt.
+type ReadWriterFile struct {
+ File File
+ Offset uint64
+}
+
+// Read implements part of the io.ReadWriter interface.
+func (r *ReadWriterFile) Read(p []byte) (int, error) {
+ n, err := r.File.ReadAt(p, r.Offset)
+ r.Offset += uint64(n)
+ if err != nil {
+ return n, err
+ }
+ if n == 0 && len(p) > 0 {
+ return n, io.EOF
+ }
+ return n, nil
+}
+
+// ReadAt implements the io.ReaderAt interface.
+func (r *ReadWriterFile) ReadAt(p []byte, offset int64) (int, error) {
+ n, err := r.File.ReadAt(p, uint64(offset))
+ if err != nil {
+ return 0, err
+ }
+ if n == 0 && len(p) > 0 {
+ return n, io.EOF
+ }
+ return n, nil
+}
+
+// Write implements part of the io.ReadWriter interface.
+func (r *ReadWriterFile) Write(p []byte) (int, error) {
+ n, err := r.File.WriteAt(p, r.Offset)
+ r.Offset += uint64(n)
+ if err != nil {
+ return n, err
+ }
+ if n < len(p) {
+ return n, io.ErrShortWrite
+ }
+ return n, nil
+}
+
+// WriteAt implements the io.WriteAt interface.
+func (r *ReadWriterFile) WriteAt(p []byte, offset int64) (int, error) {
+ n, err := r.File.WriteAt(p, uint64(offset))
+ if err != nil {
+ return n, err
+ }
+ if n < len(p) {
+ return n, io.ErrShortWrite
+ }
+ return n, nil
+}
+
+// Rename implements File.Rename.
+func (c *clientFile) Rename(dir File, name string) error {
+ clientDir, ok := dir.(*clientFile)
+ if !ok {
+ return syscall.EBADF
+ }
+ return c.client.sendRecv(&Trename{FID: c.fid, Directory: clientDir.fid, Name: name}, &Rrename{})
+}
+
+// Create implements File.Create.
+func (c *clientFile) Create(name string, openFlags OpenFlags, permissions FileMode, uid UID, gid GID) (*fd.FD, File, QID, uint32, error) {
+ msg := Tlcreate{
+ FID: c.fid,
+ Name: name,
+ OpenFlags: openFlags,
+ Permissions: permissions,
+ GID: NoGID,
+ }
+
+ if versionSupportsTucreation(c.client.version) {
+ msg.GID = gid
+ rucreate := Rucreate{}
+ if err := c.client.sendRecv(&Tucreate{Tlcreate: msg, UID: uid}, &rucreate); err != nil {
+ return nil, nil, QID{}, 0, err
+ }
+ return rucreate.File, c, rucreate.QID, rucreate.IoUnit, nil
+ }
+
+ rlcreate := Rlcreate{}
+ if err := c.client.sendRecv(&msg, &rlcreate); err != nil {
+ return nil, nil, QID{}, 0, err
+ }
+ return rlcreate.File, c, rlcreate.QID, rlcreate.IoUnit, nil
+}
+
+// Mkdir implements File.Mkdir.
+func (c *clientFile) Mkdir(name string, permissions FileMode, uid UID, gid GID) (QID, error) {
+ msg := Tmkdir{
+ Directory: c.fid,
+ Name: name,
+ Permissions: permissions,
+ GID: NoGID,
+ }
+
+ if versionSupportsTucreation(c.client.version) {
+ msg.GID = gid
+ rumkdir := Rumkdir{}
+ if err := c.client.sendRecv(&Tumkdir{Tmkdir: msg, UID: uid}, &rumkdir); err != nil {
+ return QID{}, err
+ }
+ return rumkdir.QID, nil
+ }
+
+ rmkdir := Rmkdir{}
+ if err := c.client.sendRecv(&msg, &rmkdir); err != nil {
+ return QID{}, err
+ }
+ return rmkdir.QID, nil
+}
+
+// Symlink implements File.Symlink.
+func (c *clientFile) Symlink(oldname string, newname string, uid UID, gid GID) (QID, error) {
+ msg := Tsymlink{
+ Directory: c.fid,
+ Name: newname,
+ Target: oldname,
+ GID: NoGID,
+ }
+
+ if versionSupportsTucreation(c.client.version) {
+ msg.GID = gid
+ rusymlink := Rusymlink{}
+ if err := c.client.sendRecv(&Tusymlink{Tsymlink: msg, UID: uid}, &rusymlink); err != nil {
+ return QID{}, err
+ }
+ return rusymlink.QID, nil
+ }
+
+ rsymlink := Rsymlink{}
+ if err := c.client.sendRecv(&msg, &rsymlink); err != nil {
+ return QID{}, err
+ }
+ return rsymlink.QID, nil
+}
+
+// Link implements File.Link.
+func (c *clientFile) Link(target File, newname string) error {
+ targetFile, ok := target.(*clientFile)
+ if !ok {
+ return syscall.EBADF
+ }
+ return c.client.sendRecv(&Tlink{Directory: c.fid, Name: newname, Target: targetFile.fid}, &Rlink{})
+}
+
+// Mknod implements File.Mknod.
+func (c *clientFile) Mknod(name string, permissions FileMode, major uint32, minor uint32, uid UID, gid GID) (QID, error) {
+ msg := Tmknod{
+ Directory: c.fid,
+ Name: name,
+ Permissions: permissions,
+ Major: major,
+ Minor: minor,
+ GID: NoGID,
+ }
+
+ if versionSupportsTucreation(c.client.version) {
+ msg.GID = gid
+ rumknod := Rumknod{}
+ if err := c.client.sendRecv(&Tumknod{Tmknod: msg, UID: uid}, &rumknod); err != nil {
+ return QID{}, err
+ }
+ return rumknod.QID, nil
+ }
+
+ rmknod := Rmknod{}
+ if err := c.client.sendRecv(&msg, &rmknod); err != nil {
+ return QID{}, err
+ }
+ return rmknod.QID, nil
+}
+
+// RenameAt implements File.RenameAt.
+func (c *clientFile) RenameAt(oldname string, newdir File, newname string) error {
+ clientNewDir, ok := newdir.(*clientFile)
+ if !ok {
+ return syscall.EBADF
+ }
+ return c.client.sendRecv(&Trenameat{OldDirectory: c.fid, OldName: oldname, NewDirectory: clientNewDir.fid, NewName: newname}, &Rrenameat{})
+}
+
+// UnlinkAt implements File.UnlinkAt.
+func (c *clientFile) UnlinkAt(name string, flags uint32) error {
+ return c.client.sendRecv(&Tunlinkat{Directory: c.fid, Name: name, Flags: flags}, &Runlinkat{})
+}
+
+// Readdir implements File.Readdir.
+func (c *clientFile) Readdir(offset uint64, count uint32) ([]Dirent, error) {
+ rreaddir := Rreaddir{}
+ if err := c.client.sendRecv(&Treaddir{Directory: c.fid, Offset: offset, Count: count}, &rreaddir); err != nil {
+ return nil, err
+ }
+
+ return rreaddir.Entries, nil
+}
+
+// Readlink implements File.Readlink.
+func (c *clientFile) Readlink() (string, error) {
+ rreadlink := Rreadlink{}
+ if err := c.client.sendRecv(&Treadlink{FID: c.fid}, &rreadlink); err != nil {
+ return "", err
+ }
+
+ return rreadlink.Target, nil
+}
+
+// Flush implements File.Flush.
+func (c *clientFile) Flush() error {
+ if !VersionSupportsTflushf(c.client.version) {
+ return nil
+ }
+ return c.client.sendRecv(&Tflushf{FID: c.fid}, &Rflushf{})
+}
diff --git a/pkg/p9/client_test.go b/pkg/p9/client_test.go
new file mode 100644
index 000000000..06302a76a
--- /dev/null
+++ b/pkg/p9/client_test.go
@@ -0,0 +1,62 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package p9
+
+import (
+ "syscall"
+ "testing"
+
+ "gvisor.googlesource.com/gvisor/pkg/unet"
+)
+
+// TestVersion tests the version negotiation.
+func TestVersion(t *testing.T) {
+ // First, create a new server and connection.
+ serverSocket, clientSocket, err := unet.SocketPair(false)
+ if err != nil {
+ t.Fatalf("socketpair got err %v expected nil", err)
+ }
+ defer clientSocket.Close()
+
+ // Create a new server and client.
+ s := NewServer(nil)
+ go s.Handle(serverSocket)
+
+ // NewClient does a Tversion exchange, so this is our test for success.
+ c, err := NewClient(clientSocket, 1024*1024 /* 1M message size */, HighestVersionString())
+ if err != nil {
+ t.Fatalf("got %v, expected nil", err)
+ }
+
+ // Check a bogus version string.
+ if err := c.sendRecv(&Tversion{Version: "notokay", MSize: 1024 * 1024}, &Rversion{}); err != syscall.EINVAL {
+ t.Errorf("got %v expected %v", err, syscall.EINVAL)
+ }
+
+ // Check a bogus version number.
+ if err := c.sendRecv(&Tversion{Version: "9P1000.L", MSize: 1024 * 1024}, &Rversion{}); err != syscall.EINVAL {
+ t.Errorf("got %v expected %v", err, syscall.EINVAL)
+ }
+
+ // Check a too high version number.
+ if err := c.sendRecv(&Tversion{Version: versionString(highestSupportedVersion + 1), MSize: 1024 * 1024}, &Rversion{}); err != syscall.EAGAIN {
+ t.Errorf("got %v expected %v", err, syscall.EAGAIN)
+ }
+
+ // Check an invalid MSize.
+ if err := c.sendRecv(&Tversion{Version: versionString(highestSupportedVersion), MSize: 0}, &Rversion{}); err != syscall.EINVAL {
+ t.Errorf("got %v expected %v", err, syscall.EINVAL)
+ }
+}
diff --git a/pkg/p9/file.go b/pkg/p9/file.go
new file mode 100644
index 000000000..ae726f0b9
--- /dev/null
+++ b/pkg/p9/file.go
@@ -0,0 +1,180 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package p9
+
+import (
+ "syscall"
+
+ "gvisor.googlesource.com/gvisor/pkg/fd"
+)
+
+// Attacher is provided by the user.
+type Attacher interface {
+ // Attach returns a new File.
+ Attach(attachName string) (File, error)
+}
+
+// File is a set of operations corresponding to a single node.
+//
+// Functions below MUST return syscall.Errno values.
+// TODO: Enforce that with the type.
+//
+// These must be implemented in all circumstances.
+type File interface {
+ // Walk walks to the path components given in names.
+ //
+ // Walk returns QIDs in the same order that the names were passed in.
+ //
+ // An empty list of arguments should return a copy of the current file.
+ Walk(names []string) ([]QID, File, error)
+
+ // StatFS returns information about the file system associated with
+ // this file.
+ StatFS() (FSStat, error)
+
+ // GetAttr returns attributes of this node.
+ GetAttr(req AttrMask) (QID, AttrMask, Attr, error)
+
+ // SetAttr sets attributes on this node.
+ SetAttr(valid SetAttrMask, attr SetAttr) error
+
+ // Remove removes the file.
+ //
+ // This is deprecated in favor of UnlinkAt below.
+ Remove() error
+
+ // Rename renames the file.
+ Rename(directory File, name string) error
+
+ // Close is called when all references are dropped on the server side,
+ // and Close should be called by the client to drop all references.
+ //
+ // For server-side implementations of Close, the error is ignored.
+ //
+ // Close must be called even when Open has not been called.
+ Close() error
+
+ // Open is called prior to using read/write.
+ //
+ // The *fd.FD may be nil. If an *fd.FD is provided, ownership now
+ // belongs to the caller and the FD must be non-blocking.
+ //
+ // If Open returns a non-nil *fd.FD, it should do so for all possible
+ // OpenFlags. If Open returns a nil *fd.FD, it should similarly return
+ // a nil *fd.FD for all possible OpenFlags.
+ //
+ // This can be assumed to be one-shot only.
+ Open(mode OpenFlags) (*fd.FD, QID, uint32, error)
+
+ // Read reads from this file.
+ //
+ // This may return io.EOF in addition to syscall.Errno values.
+ //
+ // Preconditions: Open has been called and returned success.
+ ReadAt(p []byte, offset uint64) (int, error)
+
+ // Write writes to this file.
+ //
+ // This may return io.EOF in addition to syscall.Errno values.
+ //
+ // Preconditions: Open has been called and returned success.
+ WriteAt(p []byte, offset uint64) (int, error)
+
+ // FSync syncs this node.
+ //
+ // Preconditions: Open has been called and returned success.
+ FSync() error
+
+ // Create creates a new regular file and opens it according to the
+ // flags given.
+ //
+ // See p9.File.Open for a description of *fd.FD.
+ Create(name string, flags OpenFlags, permissions FileMode, uid UID, gid GID) (*fd.FD, File, QID, uint32, error)
+
+ // Mkdir creates a subdirectory.
+ Mkdir(name string, permissions FileMode, uid UID, gid GID) (QID, error)
+
+ // Symlink makes a new symbolic link.
+ Symlink(oldname string, newname string, uid UID, gid GID) (QID, error)
+
+ // Link makes a new hard link.
+ Link(target File, newname string) error
+
+ // Mknod makes a new device node.
+ Mknod(name string, permissions FileMode, major uint32, minor uint32, uid UID, gid GID) (QID, error)
+
+ // RenameAt renames a given file to a new name in a potentially new
+ // directory.
+ //
+ // oldname must be a name relative to this file, which must be a
+ // directory. newname is a name relative to newdir.
+ //
+ // This is deprecated in favor of Rename.
+ RenameAt(oldname string, newdir File, newname string) error
+
+ // UnlinkAt the given named file.
+ //
+ // name must be a file relative to this directory.
+ //
+ // Flags are implementation-specific (e.g. O_DIRECTORY), but are
+ // generally Linux unlinkat(2) flags.
+ UnlinkAt(name string, flags uint32) error
+
+ // Readdir reads directory entries.
+ //
+ // This may return io.EOF in addition to syscall.Errno values.
+ //
+ // Preconditions: Open has been called and returned success.
+ Readdir(offset uint64, count uint32) ([]Dirent, error)
+
+ // Readlink reads the link target.
+ Readlink() (string, error)
+
+ // Flush is called prior to Close.
+ //
+ // Whereas Close drops all references to the file, Flush cleans up the
+ // file state. Behavior is implementation-specific.
+ //
+ // Flush is not related to flush(9p). Flush is an extension to 9P2000.L,
+ // see version.go.
+ Flush() error
+
+ // WalkGetAttr walks to the next file and returns its maximal set of
+ // attributes.
+ //
+ // Server-side p9.Files may return syscall.ENOSYS to indicate that Walk
+ // and GetAttr should be used separately to satisfy this request.
+ WalkGetAttr([]string) ([]QID, File, AttrMask, Attr, error)
+
+ // Connect establishes a new host-socket backed connection with a
+ // socket. A File does not need to be opened before it can be connected
+ // and it can be connected to multiple times resulting in a unique
+ // *fd.FD each time. In addition, the lifetime of the *fd.FD is
+ // independent from the lifetime of the p9.File and must be managed by
+ // the caller.
+ //
+ // The returned FD must be non-blocking.
+ //
+ // flags indicates the requested type of socket.
+ Connect(flags ConnectFlags) (*fd.FD, error)
+}
+
+// DefaultWalkGetAttr implements File.WalkGetAttr to return ENOSYS for server-side Files.
+type DefaultWalkGetAttr struct{}
+
+// WalkGetAttr implements File.WalkGetAttr.
+func (DefaultWalkGetAttr) WalkGetAttr([]string) ([]QID, File, AttrMask, Attr, error) {
+ return nil, nil, AttrMask{}, Attr{}, syscall.ENOSYS
+}
diff --git a/pkg/p9/handlers.go b/pkg/p9/handlers.go
new file mode 100644
index 000000000..83a66d3ae
--- /dev/null
+++ b/pkg/p9/handlers.go
@@ -0,0 +1,705 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package p9
+
+import (
+ "io"
+ "os"
+ "sync/atomic"
+ "syscall"
+
+ "gvisor.googlesource.com/gvisor/pkg/log"
+)
+
+// newErr returns a new error message from an error.
+func newErr(err error) *Rlerror {
+ switch e := err.(type) {
+ case syscall.Errno:
+ return &Rlerror{Error: uint32(e)}
+ case *os.PathError:
+ return newErr(e.Err)
+ case *os.SyscallError:
+ return newErr(e.Err)
+ default:
+ log.Warningf("unknown error: %v", err)
+ return &Rlerror{Error: uint32(syscall.EIO)}
+ }
+}
+
+// handler is implemented for server-handled messages.
+//
+// See server.go for call information.
+type handler interface {
+ // Handle handles the given message.
+ //
+ // This may modify the server state. The handle function must return a
+ // message which will be sent back to the client. It may be useful to
+ // use newErr to automatically extract an error message.
+ handle(cs *connState) message
+}
+
+// handle implements handler.handle.
+func (t *Tversion) handle(cs *connState) message {
+ if t.MSize == 0 {
+ return newErr(syscall.EINVAL)
+ }
+ if t.MSize > maximumLength {
+ return newErr(syscall.EINVAL)
+ }
+ atomic.StoreUint32(&cs.messageSize, t.MSize)
+ requested, ok := parseVersion(t.Version)
+ if !ok {
+ return newErr(syscall.EINVAL)
+ }
+ // The server cannot support newer versions that it doesn't know about. In this
+ // case we return EAGAIN to tell the client to try again with a lower version.
+ if requested > highestSupportedVersion {
+ return newErr(syscall.EAGAIN)
+ }
+ // From Tversion(9P): "The server may respond with the client’s version
+ // string, or a version string identifying an earlier defined protocol version".
+ atomic.StoreUint32(&cs.version, requested)
+ return &Rversion{
+ MSize: t.MSize,
+ Version: t.Version,
+ }
+}
+
+// handle implements handler.handle.
+func (t *Tflush) handle(cs *connState) message {
+ cs.WaitTag(t.OldTag)
+ return &Rflush{}
+}
+
+// handle implements handler.handle.
+func (t *Twalk) handle(cs *connState) message {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.FID)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ // Has it been opened already?
+ if _, opened := ref.OpenFlags(); opened {
+ return newErr(syscall.EBUSY)
+ }
+
+ // Do the walk.
+ qids, sf, err := ref.file.Walk(t.Names)
+ if err != nil {
+ return newErr(err)
+ }
+
+ // Install the new FID.
+ cs.InsertFID(t.NewFID, &fidRef{file: sf})
+
+ return &Rwalk{QIDs: qids}
+}
+
+// handle implements handler.handle.
+func (t *Tclunk) handle(cs *connState) message {
+ if !cs.DeleteFID(t.FID) {
+ return newErr(syscall.EBADF)
+ }
+ return &Rclunk{}
+}
+
+// handle implements handler.handle.
+func (t *Tremove) handle(cs *connState) message {
+ ref, ok := cs.LookupFID(t.FID)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ // "The remove request asks the file server both to remove the file
+ // represented by fid and to clunk the fid, even if the remove fails."
+ //
+ // "It is correct to consider remove to be a clunk with the side effect
+ // of removing the file if permissions allow."
+ // https://swtch.com/plan9port/man/man9/remove.html
+ err := ref.file.Remove()
+
+ // Clunk the FID regardless of Remove error.
+ if !cs.DeleteFID(t.FID) {
+ return newErr(syscall.EBADF)
+ }
+
+ if err != nil {
+ return newErr(err)
+ }
+ return &Rremove{}
+}
+
+// handle implements handler.handle.
+//
+// We don't support authentication, so this just returns ENOSYS.
+func (t *Tauth) handle(cs *connState) message {
+ return newErr(syscall.ENOSYS)
+}
+
+// handle implements handler.handle.
+func (t *Tattach) handle(cs *connState) message {
+ // Ensure no authentication FID is provided.
+ if t.Auth.AuthenticationFID != NoFID {
+ return newErr(syscall.EINVAL)
+ }
+
+ // Do the attach.
+ sf, err := cs.server.attacher.Attach(t.Auth.AttachName)
+ if err != nil {
+ return newErr(err)
+ }
+ cs.InsertFID(t.FID, &fidRef{file: sf})
+
+ // Return an empty QID.
+ return &Rattach{}
+}
+
+// handle implements handler.handle.
+func (t *Tlopen) handle(cs *connState) message {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.FID)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ ref.openedMu.Lock()
+ defer ref.openedMu.Unlock()
+
+ // Has it been opened already?
+ if ref.opened {
+ return newErr(syscall.EINVAL)
+ }
+
+ // Do the open.
+ osFile, qid, ioUnit, err := ref.file.Open(t.Flags)
+ if err != nil {
+ return newErr(err)
+ }
+
+ // Mark file as opened and set open mode.
+ ref.opened = true
+ ref.openFlags = t.Flags
+
+ return &Rlopen{QID: qid, IoUnit: ioUnit, File: osFile}
+}
+
+func (t *Tlcreate) do(cs *connState, uid UID) (*Rlcreate, error) {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.FID)
+ if !ok {
+ return nil, syscall.EBADF
+ }
+ defer ref.DecRef()
+
+ // Do the create.
+ osFile, nsf, qid, ioUnit, err := ref.file.Create(t.Name, t.OpenFlags, t.Permissions, uid, t.GID)
+ if err != nil {
+ return nil, err
+ }
+
+ // Replace the FID reference.
+ //
+ // The new file will be opened already.
+ cs.InsertFID(t.FID, &fidRef{file: nsf, opened: true, openFlags: t.OpenFlags})
+
+ return &Rlcreate{Rlopen: Rlopen{QID: qid, IoUnit: ioUnit, File: osFile}}, nil
+}
+
+// handle implements handler.handle.
+func (t *Tlcreate) handle(cs *connState) message {
+ rlcreate, err := t.do(cs, NoUID)
+ if err != nil {
+ return newErr(err)
+ }
+ return rlcreate
+}
+
+// handle implements handler.handle.
+func (t *Tsymlink) handle(cs *connState) message {
+ rsymlink, err := t.do(cs, NoUID)
+ if err != nil {
+ return newErr(err)
+ }
+ return rsymlink
+}
+
+func (t *Tsymlink) do(cs *connState, uid UID) (*Rsymlink, error) {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.Directory)
+ if !ok {
+ return nil, syscall.EBADF
+ }
+ defer ref.DecRef()
+
+ // Do the symlink.
+ qid, err := ref.file.Symlink(t.Target, t.Name, uid, t.GID)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Rsymlink{QID: qid}, nil
+}
+
+// handle implements handler.handle.
+func (t *Tlink) handle(cs *connState) message {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.Directory)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ // Lookup the other FID.
+ refTarget, ok := cs.LookupFID(t.Target)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer refTarget.DecRef()
+
+ // Do the link.
+ if err := ref.file.Link(refTarget.file, t.Name); err != nil {
+ return newErr(err)
+ }
+
+ return &Rlink{}
+}
+
+// handle implements handler.handle.
+func (t *Trenameat) handle(cs *connState) message {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.OldDirectory)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ // Lookup the other FID.
+ refTarget, ok := cs.LookupFID(t.NewDirectory)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer refTarget.DecRef()
+
+ // Do the rename.
+ if err := ref.file.RenameAt(t.OldName, refTarget.file, t.NewName); err != nil {
+ return newErr(err)
+ }
+
+ return &Rrenameat{}
+}
+
+// handle implements handler.handle.
+func (t *Tunlinkat) handle(cs *connState) message {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.Directory)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ // Do the unlink.
+ if err := ref.file.UnlinkAt(t.Name, t.Flags); err != nil {
+ return newErr(err)
+ }
+
+ return &Runlinkat{}
+}
+
+// handle implements handler.handle.
+func (t *Trename) handle(cs *connState) message {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.FID)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ // Lookup the target.
+ refTarget, ok := cs.LookupFID(t.Directory)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer refTarget.DecRef()
+
+ // Call the rename method.
+ if err := ref.file.Rename(refTarget.file, t.Name); err != nil {
+ return newErr(err)
+ }
+
+ return &Rrename{}
+}
+
+// handle implements handler.handle.
+func (t *Treadlink) handle(cs *connState) message {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.FID)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ // Do the read.
+ target, err := ref.file.Readlink()
+ if err != nil {
+ return newErr(err)
+ }
+
+ return &Rreadlink{target}
+}
+
+// handle implements handler.handle.
+func (t *Tread) handle(cs *connState) message {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.FID)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ // Has it been opened already?
+ openFlags, opened := ref.OpenFlags()
+ if !opened {
+ return newErr(syscall.EINVAL)
+ }
+
+ // Can it be read? Check permissions.
+ if openFlags&OpenFlagsModeMask == WriteOnly {
+ return newErr(syscall.EPERM)
+ }
+
+ // Constrain the size of the read buffer.
+ if int(t.Count) > int(maximumLength) {
+ return newErr(syscall.ENOBUFS)
+ }
+
+ // Do the read.
+ data := make([]byte, t.Count)
+ n, err := ref.file.ReadAt(data, t.Offset)
+ if err != nil && err != io.EOF {
+ return newErr(err)
+ }
+
+ return &Rread{Data: data[:n]}
+}
+
+// handle implements handler.handle.
+func (t *Twrite) handle(cs *connState) message {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.FID)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ // Has it been opened already?
+ openFlags, opened := ref.OpenFlags()
+ if !opened {
+ return newErr(syscall.EINVAL)
+ }
+
+ // Can it be write? Check permissions.
+ if openFlags&OpenFlagsModeMask == ReadOnly {
+ return newErr(syscall.EPERM)
+ }
+
+ // Do the write.
+ n, err := ref.file.WriteAt(t.Data, t.Offset)
+ if err != nil {
+ return newErr(err)
+ }
+
+ return &Rwrite{Count: uint32(n)}
+}
+
+// handle implements handler.handle.
+func (t *Tmknod) handle(cs *connState) message {
+ rmknod, err := t.do(cs, NoUID)
+ if err != nil {
+ return newErr(err)
+ }
+ return rmknod
+}
+
+func (t *Tmknod) do(cs *connState, uid UID) (*Rmknod, error) {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.Directory)
+ if !ok {
+ return nil, syscall.EBADF
+ }
+ defer ref.DecRef()
+
+ // Do the mknod.
+ qid, err := ref.file.Mknod(t.Name, t.Permissions, t.Major, t.Minor, uid, t.GID)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Rmknod{QID: qid}, nil
+}
+
+// handle implements handler.handle.
+func (t *Tmkdir) handle(cs *connState) message {
+ rmkdir, err := t.do(cs, NoUID)
+ if err != nil {
+ return newErr(err)
+ }
+ return rmkdir
+}
+
+func (t *Tmkdir) do(cs *connState, uid UID) (*Rmkdir, error) {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.Directory)
+ if !ok {
+ return nil, syscall.EBADF
+ }
+ defer ref.DecRef()
+
+ // Do the mkdir.
+ qid, err := ref.file.Mkdir(t.Name, t.Permissions, uid, t.GID)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Rmkdir{QID: qid}, nil
+}
+
+// handle implements handler.handle.
+func (t *Tgetattr) handle(cs *connState) message {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.FID)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ // Get attributes.
+ qid, valid, attr, err := ref.file.GetAttr(t.AttrMask)
+ if err != nil {
+ return newErr(err)
+ }
+
+ return &Rgetattr{QID: qid, Valid: valid, Attr: attr}
+}
+
+// handle implements handler.handle.
+func (t *Tsetattr) handle(cs *connState) message {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.FID)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ // Set attributes.
+ if err := ref.file.SetAttr(t.Valid, t.SetAttr); err != nil {
+ return newErr(err)
+ }
+
+ return &Rsetattr{}
+}
+
+// handle implements handler.handle.
+func (t *Txattrwalk) handle(cs *connState) message {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.FID)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ // We don't support extended attributes.
+ return newErr(syscall.ENODATA)
+}
+
+// handle implements handler.handle.
+func (t *Txattrcreate) handle(cs *connState) message {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.FID)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ // We don't support extended attributes.
+ return newErr(syscall.ENOSYS)
+}
+
+// handle implements handler.handle.
+func (t *Treaddir) handle(cs *connState) message {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.Directory)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ // Has it been opened already?
+ if _, opened := ref.OpenFlags(); !opened {
+ return newErr(syscall.EINVAL)
+ }
+
+ // Read the entries.
+ entries, err := ref.file.Readdir(t.Offset, t.Count)
+ if err != nil && err != io.EOF {
+ return newErr(err)
+ }
+
+ return &Rreaddir{Count: t.Count, Entries: entries}
+}
+
+// handle implements handler.handle.
+func (t *Tfsync) handle(cs *connState) message {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.FID)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ // Has it been opened already?
+ if _, opened := ref.OpenFlags(); !opened {
+ return newErr(syscall.EINVAL)
+ }
+
+ err := ref.file.FSync()
+ if err != nil {
+ return newErr(err)
+ }
+
+ return &Rfsync{}
+}
+
+// handle implements handler.handle.
+func (t *Tstatfs) handle(cs *connState) message {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.FID)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ st, err := ref.file.StatFS()
+ if err != nil {
+ return newErr(err)
+ }
+
+ return &Rstatfs{st}
+}
+
+// handle implements handler.handle.
+func (t *Tflushf) handle(cs *connState) message {
+ ref, ok := cs.LookupFID(t.FID)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ if err := ref.file.Flush(); err != nil {
+ return newErr(err)
+ }
+
+ return &Rflushf{}
+}
+
+// handle implements handler.handle.
+func (t *Twalkgetattr) handle(cs *connState) message {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.FID)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ // Has it been opened already?
+ if _, opened := ref.OpenFlags(); opened {
+ return newErr(syscall.EBUSY)
+ }
+
+ // Do the walk.
+ qids, sf, valid, attr, err := ref.file.WalkGetAttr(t.Names)
+ if err == syscall.ENOSYS {
+ qids, sf, err = ref.file.Walk(t.Names)
+ if err != nil {
+ return newErr(err)
+ }
+ _, valid, attr, err = sf.GetAttr(AttrMaskAll())
+ }
+ if err != nil {
+ return newErr(err)
+ }
+
+ // Install the new FID.
+ cs.InsertFID(t.NewFID, &fidRef{file: sf})
+
+ return &Rwalkgetattr{QIDs: qids, Valid: valid, Attr: attr}
+}
+
+// handle implements handler.handle.
+func (t *Tucreate) handle(cs *connState) message {
+ rlcreate, err := t.Tlcreate.do(cs, t.UID)
+ if err != nil {
+ return newErr(err)
+ }
+ return &Rucreate{*rlcreate}
+}
+
+// handle implements handler.handle.
+func (t *Tumkdir) handle(cs *connState) message {
+ rmkdir, err := t.Tmkdir.do(cs, t.UID)
+ if err != nil {
+ return newErr(err)
+ }
+ return &Rumkdir{*rmkdir}
+}
+
+// handle implements handler.handle.
+func (t *Tusymlink) handle(cs *connState) message {
+ rsymlink, err := t.Tsymlink.do(cs, t.UID)
+ if err != nil {
+ return newErr(err)
+ }
+ return &Rusymlink{*rsymlink}
+}
+
+// handle implements handler.handle.
+func (t *Tumknod) handle(cs *connState) message {
+ rmknod, err := t.Tmknod.do(cs, t.UID)
+ if err != nil {
+ return newErr(err)
+ }
+ return &Rumknod{*rmknod}
+}
+
+// handle implements handler.handle.
+func (t *Tlconnect) handle(cs *connState) message {
+ // Lookup the FID.
+ ref, ok := cs.LookupFID(t.FID)
+ if !ok {
+ return newErr(syscall.EBADF)
+ }
+ defer ref.DecRef()
+
+ // Do the connect.
+ osFile, err := ref.file.Connect(t.Flags)
+ if err != nil {
+ return newErr(err)
+ }
+
+ return &Rlconnect{File: osFile}
+}
diff --git a/pkg/p9/local_server/BUILD b/pkg/p9/local_server/BUILD
new file mode 100644
index 000000000..8229e6308
--- /dev/null
+++ b/pkg/p9/local_server/BUILD
@@ -0,0 +1,14 @@
+package(licenses = ["notice"]) # Apache 2.0
+
+load("@io_bazel_rules_go//go:def.bzl", "go_binary")
+
+go_binary(
+ name = "local_server",
+ srcs = ["local_server.go"],
+ deps = [
+ "//pkg/fd",
+ "//pkg/log",
+ "//pkg/p9",
+ "//pkg/unet",
+ ],
+)
diff --git a/pkg/p9/local_server/local_server.go b/pkg/p9/local_server/local_server.go
new file mode 100644
index 000000000..5b1e97711
--- /dev/null
+++ b/pkg/p9/local_server/local_server.go
@@ -0,0 +1,347 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Binary local_server provides a local 9P2000.L server for the p9 package.
+//
+// To use, first start the server:
+// local_server /tmp/my_bind_addr
+//
+// Then, connect using the Linux 9P filesystem:
+// mount -t 9p -o trans=unix,version=9P2000.L /tmp/my_bind_addr /mnt
+//
+// This package also serves as an examplar.
+package main
+
+import (
+ "os"
+ "path"
+ "syscall"
+
+ "gvisor.googlesource.com/gvisor/pkg/fd"
+ "gvisor.googlesource.com/gvisor/pkg/log"
+ "gvisor.googlesource.com/gvisor/pkg/p9"
+ "gvisor.googlesource.com/gvisor/pkg/unet"
+)
+
+// local wraps a local file.
+type local struct {
+ p9.DefaultWalkGetAttr
+
+ path string
+ file *os.File
+}
+
+// info constructs a QID for this file.
+func (l *local) info() (p9.QID, os.FileInfo, error) {
+ var (
+ qid p9.QID
+ fi os.FileInfo
+ err error
+ )
+
+ // Stat the file.
+ if l.file != nil {
+ fi, err = l.file.Stat()
+ } else {
+ fi, err = os.Lstat(l.path)
+ }
+ if err != nil {
+ log.Warningf("error stating %#v: %v", l, err)
+ return qid, nil, err
+ }
+
+ // Construct the QID type.
+ qid.Type = p9.ModeFromOS(fi.Mode()).QIDType()
+
+ // Save the path from the Ino.
+ qid.Path = fi.Sys().(*syscall.Stat_t).Ino
+ return qid, fi, nil
+}
+
+// Attach implements p9.Attacher.Attach.
+func (l *local) Attach(name string) (p9.File, error) {
+ return &local{path: path.Clean(name)}, nil
+}
+
+// Walk implements p9.File.Walk.
+func (l *local) Walk(names []string) ([]p9.QID, p9.File, error) {
+ var qids []p9.QID
+ last := &local{path: l.path}
+ for _, name := range names {
+ c := &local{path: path.Join(last.path, name)}
+ qid, _, err := c.info()
+ if err != nil {
+ return nil, nil, err
+ }
+ qids = append(qids, qid)
+ last = c
+ }
+ return qids, last, nil
+}
+
+// StatFS implements p9.File.StatFS.
+//
+// Not implemented.
+func (l *local) StatFS() (p9.FSStat, error) {
+ return p9.FSStat{}, syscall.ENOSYS
+}
+
+// FSync implements p9.File.FSync.
+func (l *local) FSync() error {
+ return l.file.Sync()
+}
+
+// GetAttr implements p9.File.GetAttr.
+//
+// Not fully implemented.
+func (l *local) GetAttr(req p9.AttrMask) (p9.QID, p9.AttrMask, p9.Attr, error) {
+ qid, fi, err := l.info()
+ if err != nil {
+ return qid, p9.AttrMask{}, p9.Attr{}, err
+ }
+
+ stat := fi.Sys().(*syscall.Stat_t)
+ attr := p9.Attr{
+ Mode: p9.FileMode(stat.Mode),
+ UID: p9.UID(stat.Uid),
+ GID: p9.GID(stat.Gid),
+ NLink: stat.Nlink,
+ RDev: stat.Rdev,
+ Size: uint64(stat.Size),
+ BlockSize: uint64(stat.Blksize),
+ Blocks: uint64(stat.Blocks),
+ ATimeSeconds: uint64(stat.Atim.Sec),
+ ATimeNanoSeconds: uint64(stat.Atim.Nsec),
+ MTimeSeconds: uint64(stat.Mtim.Sec),
+ MTimeNanoSeconds: uint64(stat.Mtim.Nsec),
+ CTimeSeconds: uint64(stat.Ctim.Sec),
+ CTimeNanoSeconds: uint64(stat.Ctim.Nsec),
+ }
+ valid := p9.AttrMask{
+ Mode: true,
+ UID: true,
+ GID: true,
+ NLink: true,
+ RDev: true,
+ Size: true,
+ Blocks: true,
+ ATime: true,
+ MTime: true,
+ CTime: true,
+ }
+
+ return qid, valid, attr, nil
+}
+
+// SetAttr implements p9.File.SetAttr.
+//
+// Not implemented.
+func (l *local) SetAttr(valid p9.SetAttrMask, attr p9.SetAttr) error {
+ return syscall.ENOSYS
+}
+
+// Remove implements p9.File.Remove.
+//
+// Not implemented.
+func (l *local) Remove() error {
+ return syscall.ENOSYS
+}
+
+// Rename implements p9.File.Rename.
+//
+// Not implemented.
+func (l *local) Rename(directory p9.File, name string) error {
+ return syscall.ENOSYS
+}
+
+// Close implements p9.File.Close.
+func (l *local) Close() error {
+ if l.file != nil {
+ return l.file.Close()
+ }
+ return nil
+}
+
+// Open implements p9.File.Open.
+func (l *local) Open(mode p9.OpenFlags) (*fd.FD, p9.QID, uint32, error) {
+ qid, _, err := l.info()
+ if err != nil {
+ return nil, qid, 0, err
+ }
+
+ // Do the actual open.
+ f, err := os.OpenFile(l.path, int(mode), 0)
+ if err != nil {
+ return nil, qid, 0, err
+ }
+ l.file = f
+
+ // Note: we don't send the local file for this server.
+ return nil, qid, 4096, nil
+}
+
+// Read implements p9.File.Read.
+func (l *local) ReadAt(p []byte, offset uint64) (int, error) {
+ return l.file.ReadAt(p, int64(offset))
+}
+
+// Write implements p9.File.Write.
+func (l *local) WriteAt(p []byte, offset uint64) (int, error) {
+ return l.file.WriteAt(p, int64(offset))
+}
+
+// Create implements p9.File.Create.
+func (l *local) Create(name string, mode p9.OpenFlags, permissions p9.FileMode, _ p9.UID, _ p9.GID) (*fd.FD, p9.File, p9.QID, uint32, error) {
+ f, err := os.OpenFile(l.path, int(mode)|syscall.O_CREAT|syscall.O_EXCL, os.FileMode(permissions))
+ if err != nil {
+ return nil, nil, p9.QID{}, 0, err
+ }
+
+ l2 := &local{path: path.Join(l.path, name), file: f}
+ qid, _, err := l2.info()
+ if err != nil {
+ l2.Close()
+ return nil, nil, p9.QID{}, 0, err
+ }
+
+ return nil, l2, qid, 4096, nil
+}
+
+// Mkdir implements p9.File.Mkdir.
+//
+// Not properly implemented.
+func (l *local) Mkdir(name string, permissions p9.FileMode, _ p9.UID, _ p9.GID) (p9.QID, error) {
+ if err := os.Mkdir(path.Join(l.path, name), os.FileMode(permissions)); err != nil {
+ return p9.QID{}, err
+ }
+
+ // Blank QID.
+ return p9.QID{}, nil
+}
+
+// Symlink implements p9.File.Symlink.
+//
+// Not properly implemented.
+func (l *local) Symlink(oldname string, newname string, _ p9.UID, _ p9.GID) (p9.QID, error) {
+ if err := os.Symlink(oldname, path.Join(l.path, newname)); err != nil {
+ return p9.QID{}, err
+ }
+
+ // Blank QID.
+ return p9.QID{}, nil
+}
+
+// Link implements p9.File.Link.
+//
+// Not properly implemented.
+func (l *local) Link(target p9.File, newname string) error {
+ if err := os.Link(target.(*local).path, path.Join(l.path, newname)); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Mknod implements p9.File.Mknod.
+//
+// Not implemented.
+func (l *local) Mknod(name string, permissions p9.FileMode, major uint32, minor uint32, _ p9.UID, _ p9.GID) (p9.QID, error) {
+ return p9.QID{}, syscall.ENOSYS
+}
+
+// RenameAt implements p9.File.RenameAt.
+//
+// Not implemented.
+func (l *local) RenameAt(oldname string, newdir p9.File, newname string) error {
+ return syscall.ENOSYS
+}
+
+// UnlinkAt implements p9.File.UnlinkAt.
+//
+// Not implemented.
+func (l *local) UnlinkAt(name string, flags uint32) error {
+ return syscall.ENOSYS
+}
+
+// Readdir implements p9.File.Readdir.
+func (l *local) Readdir(offset uint64, count uint32) ([]p9.Dirent, error) {
+ // We only do *all* dirents in single shot.
+ const maxDirentBuffer = 1024 * 1024
+ buf := make([]byte, maxDirentBuffer)
+ n, err := syscall.ReadDirent(int(l.file.Fd()), buf)
+ if err != nil {
+ // Return zero entries.
+ return nil, nil
+ }
+
+ // Parse the entries; note that we read up to offset+count here.
+ _, newCount, newNames := syscall.ParseDirent(buf[:n], int(offset)+int(count), nil)
+ var dirents []p9.Dirent
+ for i := int(offset); i >= 0 && i < newCount; i++ {
+ entry := local{path: path.Join(l.path, newNames[i])}
+ qid, _, err := entry.info()
+ if err != nil {
+ continue
+ }
+ dirents = append(dirents, p9.Dirent{
+ QID: qid,
+ Type: qid.Type,
+ Name: newNames[i],
+ Offset: uint64(i + 1),
+ })
+ }
+
+ return dirents, nil
+}
+
+// Readlink implements p9.File.Readlink.
+//
+// Not properly implemented.
+func (l *local) Readlink() (string, error) {
+ return os.Readlink(l.path)
+}
+
+// Flush implements p9.File.Flush.
+func (l *local) Flush() error {
+ return nil
+}
+
+// Connect implements p9.File.Connect.
+func (l *local) Connect(p9.ConnectFlags) (*fd.FD, error) {
+ return nil, syscall.ECONNREFUSED
+}
+
+func main() {
+ log.SetLevel(log.Debug)
+
+ if len(os.Args) != 2 {
+ log.Warningf("usage: %s <bind-addr>", os.Args[0])
+ os.Exit(1)
+ }
+
+ // Bind and listen on the socket.
+ serverSocket, err := unet.BindAndListen(os.Args[1], false)
+ if err != nil {
+ log.Warningf("err binding: %v", err)
+ os.Exit(1)
+ }
+
+ // Run the server.
+ s := p9.NewServer(&local{})
+ s.Serve(serverSocket)
+}
+
+var (
+ _ p9.File = &local{}
+)
diff --git a/pkg/p9/messages.go b/pkg/p9/messages.go
new file mode 100644
index 000000000..b3d76801b
--- /dev/null
+++ b/pkg/p9/messages.go
@@ -0,0 +1,2271 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package p9
+
+import (
+ "fmt"
+ "reflect"
+
+ "gvisor.googlesource.com/gvisor/pkg/fd"
+)
+
+// ErrInvalidMsgType is returned when an unsupported message type is found.
+type ErrInvalidMsgType struct {
+ MsgType
+}
+
+// Error returns a useful string.
+func (e *ErrInvalidMsgType) Error() string {
+ return fmt.Sprintf("invalid message type: %d", e.MsgType)
+}
+
+// message is a generic 9P message.
+type message interface {
+ encoder
+ fmt.Stringer
+
+ // Type returns the message type number.
+ Type() MsgType
+}
+
+// payloader is a special message which may include an inline payload.
+type payloader interface {
+ // FixedSize returns the size of the fixed portion of this message.
+ FixedSize() uint32
+
+ // Payload returns the payload for sending.
+ Payload() []byte
+
+ // SetPayload returns the decoded message.
+ //
+ // This is going to be total message size - FixedSize. But this should
+ // be validated during Decode, which will be called after SetPayload.
+ SetPayload([]byte)
+}
+
+// filer is a message capable of passing a file.
+type filer interface {
+ // FilePayload returns the file payload.
+ FilePayload() *fd.FD
+
+ // SetFilePayload sets the file payload.
+ SetFilePayload(*fd.FD)
+}
+
+// Tversion is a version request.
+type Tversion struct {
+ // MSize is the message size to use.
+ MSize uint32
+
+ // Version is the version string.
+ //
+ // For this implementation, this must be 9P2000.L.
+ Version string
+}
+
+// Decode implements encoder.Decode.
+func (t *Tversion) Decode(b *buffer) {
+ t.MSize = b.Read32()
+ t.Version = b.ReadString()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tversion) Encode(b *buffer) {
+ b.Write32(t.MSize)
+ b.WriteString(t.Version)
+}
+
+// Type implements message.Type.
+func (*Tversion) Type() MsgType {
+ return MsgTversion
+}
+
+// String implements fmt.Stringer.
+func (t *Tversion) String() string {
+ return fmt.Sprintf("Tversion{MSize: %d, Version: %s}", t.MSize, t.Version)
+}
+
+// Rversion is a version response.
+type Rversion struct {
+ // MSize is the negotiated size.
+ MSize uint32
+
+ // Version is the negotiated version.
+ Version string
+}
+
+// Decode implements encoder.Decode.
+func (r *Rversion) Decode(b *buffer) {
+ r.MSize = b.Read32()
+ r.Version = b.ReadString()
+}
+
+// Encode implements encoder.Encode.
+func (r *Rversion) Encode(b *buffer) {
+ b.Write32(r.MSize)
+ b.WriteString(r.Version)
+}
+
+// Type implements message.Type.
+func (*Rversion) Type() MsgType {
+ return MsgRversion
+}
+
+// String implements fmt.Stringer.
+func (r *Rversion) String() string {
+ return fmt.Sprintf("Rversion{MSize: %d, Version: %s}", r.MSize, r.Version)
+}
+
+// Tflush is a flush request.
+type Tflush struct {
+ // OldTag is the tag to wait on.
+ OldTag Tag
+}
+
+// Decode implements encoder.Decode.
+func (t *Tflush) Decode(b *buffer) {
+ t.OldTag = b.ReadTag()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tflush) Encode(b *buffer) {
+ b.WriteTag(t.OldTag)
+}
+
+// Type implements message.Type.
+func (*Tflush) Type() MsgType {
+ return MsgTflush
+}
+
+// String implements fmt.Stringer.
+func (t *Tflush) String() string {
+ return fmt.Sprintf("Tflush{OldTag: %d}", t.OldTag)
+}
+
+// Rflush is a flush response.
+type Rflush struct {
+}
+
+// Decode implements encoder.Decode.
+func (*Rflush) Decode(b *buffer) {
+}
+
+// Encode implements encoder.Encode.
+func (*Rflush) Encode(b *buffer) {
+}
+
+// Type implements message.Type.
+func (*Rflush) Type() MsgType {
+ return MsgRflush
+}
+
+// String implements fmt.Stringer.
+func (r *Rflush) String() string {
+ return fmt.Sprintf("RFlush{}")
+}
+
+// Twalk is a walk request.
+type Twalk struct {
+ // FID is the FID to be walked.
+ FID FID
+
+ // NewFID is the resulting FID.
+ NewFID FID
+
+ // Names are the set of names to be walked.
+ Names []string
+}
+
+// Decode implements encoder.Decode.
+func (t *Twalk) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+ t.NewFID = b.ReadFID()
+ n := b.Read16()
+ for i := 0; i < int(n); i++ {
+ t.Names = append(t.Names, b.ReadString())
+ }
+}
+
+// Encode implements encoder.Encode.
+func (t *Twalk) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+ b.WriteFID(t.NewFID)
+ b.Write16(uint16(len(t.Names)))
+ for _, name := range t.Names {
+ b.WriteString(name)
+ }
+}
+
+// Type implements message.Type.
+func (*Twalk) Type() MsgType {
+ return MsgTwalk
+}
+
+// String implements fmt.Stringer.
+func (t *Twalk) String() string {
+ return fmt.Sprintf("Twalk{FID: %d, NewFID: %d, Names: %v}", t.FID, t.NewFID, t.Names)
+}
+
+// Rwalk is a walk response.
+type Rwalk struct {
+ // QIDs are the set of QIDs returned.
+ QIDs []QID
+}
+
+// Decode implements encoder.Decode.
+func (r *Rwalk) Decode(b *buffer) {
+ n := b.Read16()
+ for i := 0; i < int(n); i++ {
+ var q QID
+ q.Decode(b)
+ r.QIDs = append(r.QIDs, q)
+ }
+}
+
+// Encode implements encoder.Encode.
+func (r *Rwalk) Encode(b *buffer) {
+ b.Write16(uint16(len(r.QIDs)))
+ for _, q := range r.QIDs {
+ q.Encode(b)
+ }
+}
+
+// Type implements message.Type.
+func (*Rwalk) Type() MsgType {
+ return MsgRwalk
+}
+
+// String implements fmt.Stringer.
+func (r *Rwalk) String() string {
+ return fmt.Sprintf("Rwalk{QIDs: %v}", r.QIDs)
+}
+
+// Tclunk is a close request.
+type Tclunk struct {
+ // FID is the FID to be closed.
+ FID FID
+}
+
+// Decode implements encoder.Decode.
+func (t *Tclunk) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tclunk) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+}
+
+// Type implements message.Type.
+func (*Tclunk) Type() MsgType {
+ return MsgTclunk
+}
+
+// String implements fmt.Stringer.
+func (t *Tclunk) String() string {
+ return fmt.Sprintf("Tclunk{FID: %d}", t.FID)
+}
+
+// Rclunk is a close response.
+type Rclunk struct {
+}
+
+// Decode implements encoder.Decode.
+func (*Rclunk) Decode(b *buffer) {
+}
+
+// Encode implements encoder.Encode.
+func (*Rclunk) Encode(b *buffer) {
+}
+
+// Type implements message.Type.
+func (*Rclunk) Type() MsgType {
+ return MsgRclunk
+}
+
+// String implements fmt.Stringer.
+func (r *Rclunk) String() string {
+ return fmt.Sprintf("Rclunk{}")
+}
+
+// Tremove is a remove request.
+//
+// This will eventually be replaced by Tunlinkat.
+type Tremove struct {
+ // FID is the FID to be removed.
+ FID FID
+}
+
+// Decode implements encoder.Decode.
+func (t *Tremove) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tremove) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+}
+
+// Type implements message.Type.
+func (*Tremove) Type() MsgType {
+ return MsgTremove
+}
+
+// String implements fmt.Stringer.
+func (t *Tremove) String() string {
+ return fmt.Sprintf("Tremove{FID: %d}", t.FID)
+}
+
+// Rremove is a remove response.
+type Rremove struct {
+}
+
+// Decode implements encoder.Decode.
+func (*Rremove) Decode(b *buffer) {
+}
+
+// Encode implements encoder.Encode.
+func (*Rremove) Encode(b *buffer) {
+}
+
+// Type implements message.Type.
+func (*Rremove) Type() MsgType {
+ return MsgRremove
+}
+
+// String implements fmt.Stringer.
+func (r *Rremove) String() string {
+ return fmt.Sprintf("Rremove{}")
+}
+
+// Rlerror is an error response.
+//
+// Note that this replaces the error code used in 9p.
+type Rlerror struct {
+ Error uint32
+}
+
+// Decode implements encoder.Decode.
+func (r *Rlerror) Decode(b *buffer) {
+ r.Error = b.Read32()
+}
+
+// Encode implements encoder.Encode.
+func (r *Rlerror) Encode(b *buffer) {
+ b.Write32(r.Error)
+}
+
+// Type implements message.Type.
+func (*Rlerror) Type() MsgType {
+ return MsgRlerror
+}
+
+// String implements fmt.Stringer.
+func (r *Rlerror) String() string {
+ return fmt.Sprintf("Rlerror{Error: %d}", r.Error)
+}
+
+// Tauth is an authentication request.
+type Tauth struct {
+ // AuthenticationFID is the FID to attach the authentication result.
+ AuthenticationFID FID
+
+ // UserName is the user to attach.
+ UserName string
+
+ // AttachName is the attach name.
+ AttachName string
+
+ // UserID is the numeric identifier for UserName.
+ UID UID
+}
+
+// Decode implements encoder.Decode.
+func (t *Tauth) Decode(b *buffer) {
+ t.AuthenticationFID = b.ReadFID()
+ t.UserName = b.ReadString()
+ t.AttachName = b.ReadString()
+ t.UID = b.ReadUID()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tauth) Encode(b *buffer) {
+ b.WriteFID(t.AuthenticationFID)
+ b.WriteString(t.UserName)
+ b.WriteString(t.AttachName)
+ b.WriteUID(t.UID)
+}
+
+// Type implements message.Type.
+func (*Tauth) Type() MsgType {
+ return MsgTauth
+}
+
+// String implements fmt.Stringer.
+func (t *Tauth) String() string {
+ return fmt.Sprintf("Tauth{AuthFID: %d, UserName: %s, AttachName: %s, UID: %d", t.AuthenticationFID, t.UserName, t.AttachName, t.UID)
+}
+
+// Rauth is an authentication response.
+//
+// Encode, Decode and Length are inherited directly from QID.
+type Rauth struct {
+ QID
+}
+
+// Type implements message.Type.
+func (*Rauth) Type() MsgType {
+ return MsgRauth
+}
+
+// String implements fmt.Stringer.
+func (r *Rauth) String() string {
+ return fmt.Sprintf("Rauth{QID: %s}", r.QID)
+}
+
+// Tattach is an attach request.
+type Tattach struct {
+ // FID is the FID to be attached.
+ FID FID
+
+ // Auth is the embedded authentication request.
+ //
+ // See client.Attach for information regarding authentication.
+ Auth Tauth
+}
+
+// Decode implements encoder.Decode.
+func (t *Tattach) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+ t.Auth.Decode(b)
+}
+
+// Encode implements encoder.Encode.
+func (t *Tattach) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+ t.Auth.Encode(b)
+}
+
+// Type implements message.Type.
+func (*Tattach) Type() MsgType {
+ return MsgTattach
+}
+
+// String implements fmt.Stringer.
+func (t *Tattach) String() string {
+ return fmt.Sprintf("Tattach{FID: %d, AuthFID: %d, UserName: %s, AttachName: %s, UID: %d}", t.FID, t.Auth.AuthenticationFID, t.Auth.UserName, t.Auth.AttachName, t.Auth.UID)
+}
+
+// Rattach is an attach response.
+type Rattach struct {
+ QID
+}
+
+// Type implements message.Type.
+func (*Rattach) Type() MsgType {
+ return MsgRattach
+}
+
+// String implements fmt.Stringer.
+func (r *Rattach) String() string {
+ return fmt.Sprintf("Rattach{QID: %s}", r.QID)
+}
+
+// Tlopen is an open request.
+type Tlopen struct {
+ // FID is the FID to be opened.
+ FID FID
+
+ // Flags are the open flags.
+ Flags OpenFlags
+}
+
+// Decode implements encoder.Decode.
+func (t *Tlopen) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+ t.Flags = b.ReadOpenFlags()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tlopen) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+ b.WriteOpenFlags(t.Flags)
+}
+
+// Type implements message.Type.
+func (*Tlopen) Type() MsgType {
+ return MsgTlopen
+}
+
+// String implements fmt.Stringer.
+func (t *Tlopen) String() string {
+ return fmt.Sprintf("Tlopen{FID: %d, Flags: %v}", t.FID, t.Flags)
+}
+
+// Rlopen is a open response.
+type Rlopen struct {
+ // QID is the file's QID.
+ QID QID
+
+ // IoUnit is the recommended I/O unit.
+ IoUnit uint32
+
+ // File may be attached via the socket.
+ //
+ // This is an extension specific to this package.
+ File *fd.FD
+}
+
+// Decode implements encoder.Decode.
+func (r *Rlopen) Decode(b *buffer) {
+ r.QID.Decode(b)
+ r.IoUnit = b.Read32()
+}
+
+// Encode implements encoder.Encode.
+func (r *Rlopen) Encode(b *buffer) {
+ r.QID.Encode(b)
+ b.Write32(r.IoUnit)
+}
+
+// Type implements message.Type.
+func (*Rlopen) Type() MsgType {
+ return MsgRlopen
+}
+
+// FilePayload returns the file payload.
+func (r *Rlopen) FilePayload() *fd.FD {
+ return r.File
+}
+
+// SetFilePayload sets the received file.
+func (r *Rlopen) SetFilePayload(file *fd.FD) {
+ r.File = file
+}
+
+// String implements fmt.Stringer.
+func (r *Rlopen) String() string {
+ return fmt.Sprintf("Rlopen{QID: %s, IoUnit: %d, File: %v}", r.QID, r.IoUnit, r.File)
+}
+
+// Tlcreate is a create request.
+type Tlcreate struct {
+ // FID is the parent FID.
+ //
+ // This becomes the new file.
+ FID FID
+
+ // Name is the file name to create.
+ Name string
+
+ // Mode is the open mode (O_RDWR, etc.).
+ //
+ // Note that flags like O_TRUNC are ignored, as is O_EXCL. All
+ // create operations are exclusive.
+ OpenFlags OpenFlags
+
+ // Permissions is the set of permission bits.
+ Permissions FileMode
+
+ // GID is the group ID to use for creating the file.
+ GID GID
+}
+
+// Decode implements encoder.Decode.
+func (t *Tlcreate) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+ t.Name = b.ReadString()
+ t.OpenFlags = b.ReadOpenFlags()
+ t.Permissions = b.ReadPermissions()
+ t.GID = b.ReadGID()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tlcreate) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+ b.WriteString(t.Name)
+ b.WriteOpenFlags(t.OpenFlags)
+ b.WritePermissions(t.Permissions)
+ b.WriteGID(t.GID)
+}
+
+// Type implements message.Type.
+func (*Tlcreate) Type() MsgType {
+ return MsgTlcreate
+}
+
+// String implements fmt.Stringer.
+func (t *Tlcreate) String() string {
+ return fmt.Sprintf("Tlcreate{FID: %d, Name: %s, OpenFlags: %s, Permissions: 0o%o, GID: %d}", t.FID, t.Name, t.OpenFlags, t.Permissions, t.GID)
+}
+
+// Rlcreate is a create response.
+//
+// The Encode, Decode, etc. methods are inherited from Rlopen.
+type Rlcreate struct {
+ Rlopen
+}
+
+// Type implements message.Type.
+func (*Rlcreate) Type() MsgType {
+ return MsgRlcreate
+}
+
+// String implements fmt.Stringer.
+func (r *Rlcreate) String() string {
+ return fmt.Sprintf("Rlcreate{QID: %s, IoUnit: %d, File: %v}", r.QID, r.IoUnit, r.File)
+}
+
+// Tsymlink is a symlink request.
+type Tsymlink struct {
+ // Directory is the directory FID.
+ Directory FID
+
+ // Name is the new in the directory.
+ Name string
+
+ // Target is the symlink target.
+ Target string
+
+ // GID is the owning group.
+ GID GID
+}
+
+// Decode implements encoder.Decode.
+func (t *Tsymlink) Decode(b *buffer) {
+ t.Directory = b.ReadFID()
+ t.Name = b.ReadString()
+ t.Target = b.ReadString()
+ t.GID = b.ReadGID()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tsymlink) Encode(b *buffer) {
+ b.WriteFID(t.Directory)
+ b.WriteString(t.Name)
+ b.WriteString(t.Target)
+ b.WriteGID(t.GID)
+}
+
+// Type implements message.Type.
+func (*Tsymlink) Type() MsgType {
+ return MsgTsymlink
+}
+
+// String implements fmt.Stringer.
+func (t *Tsymlink) String() string {
+ return fmt.Sprintf("Tsymlink{DirectoryFID: %d, Name: %s, Target: %s, GID: %d}", t.Directory, t.Name, t.Target, t.GID)
+}
+
+// Rsymlink is a symlink response.
+type Rsymlink struct {
+ // QID is the new symlink's QID.
+ QID QID
+}
+
+// Decode implements encoder.Decode.
+func (r *Rsymlink) Decode(b *buffer) {
+ r.QID.Decode(b)
+}
+
+// Encode implements encoder.Encode.
+func (r *Rsymlink) Encode(b *buffer) {
+ r.QID.Encode(b)
+}
+
+// Type implements message.Type.
+func (*Rsymlink) Type() MsgType {
+ return MsgRsymlink
+}
+
+// String implements fmt.Stringer.
+func (r *Rsymlink) String() string {
+ return fmt.Sprintf("Rsymlink{QID: %s}", r.QID)
+}
+
+// Tlink is a link request.
+type Tlink struct {
+ // Directory is the directory to contain the link.
+ Directory FID
+
+ // FID is the target.
+ Target FID
+
+ // Name is the new source name.
+ Name string
+}
+
+// Decode implements encoder.Decode.
+func (t *Tlink) Decode(b *buffer) {
+ t.Directory = b.ReadFID()
+ t.Target = b.ReadFID()
+ t.Name = b.ReadString()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tlink) Encode(b *buffer) {
+ b.WriteFID(t.Directory)
+ b.WriteFID(t.Target)
+ b.WriteString(t.Name)
+}
+
+// Type implements message.Type.
+func (*Tlink) Type() MsgType {
+ return MsgTlink
+}
+
+// String implements fmt.Stringer.
+func (t *Tlink) String() string {
+ return fmt.Sprintf("Tlink{DirectoryFID: %d, TargetFID: %d, Name: %s}", t.Directory, t.Target, t.Name)
+}
+
+// Rlink is a link response.
+type Rlink struct {
+}
+
+// Type implements message.Type.
+func (*Rlink) Type() MsgType {
+ return MsgRlink
+}
+
+// Decode implements encoder.Decode.
+func (*Rlink) Decode(b *buffer) {
+}
+
+// Encode implements encoder.Encode.
+func (*Rlink) Encode(b *buffer) {
+}
+
+// String implements fmt.Stringer.
+func (r *Rlink) String() string {
+ return fmt.Sprintf("Rlink{}")
+}
+
+// Trenameat is a rename request.
+type Trenameat struct {
+ // OldDirectory is the source directory.
+ OldDirectory FID
+
+ // OldName is the source file name.
+ OldName string
+
+ // NewDirectory is the target directory.
+ NewDirectory FID
+
+ // NewName is the new file name.
+ NewName string
+}
+
+// Decode implements encoder.Decode.
+func (t *Trenameat) Decode(b *buffer) {
+ t.OldDirectory = b.ReadFID()
+ t.OldName = b.ReadString()
+ t.NewDirectory = b.ReadFID()
+ t.NewName = b.ReadString()
+}
+
+// Encode implements encoder.Encode.
+func (t *Trenameat) Encode(b *buffer) {
+ b.WriteFID(t.OldDirectory)
+ b.WriteString(t.OldName)
+ b.WriteFID(t.NewDirectory)
+ b.WriteString(t.NewName)
+}
+
+// Type implements message.Type.
+func (*Trenameat) Type() MsgType {
+ return MsgTrenameat
+}
+
+// String implements fmt.Stringer.
+func (t *Trenameat) String() string {
+ return fmt.Sprintf("TrenameAt{OldDirectoryFID: %d, OldName: %s, NewDirectoryFID: %d, NewName: %s}", t.OldDirectory, t.OldName, t.NewDirectory, t.NewName)
+}
+
+// Rrenameat is a rename response.
+type Rrenameat struct {
+}
+
+// Decode implements encoder.Decode.
+func (*Rrenameat) Decode(b *buffer) {
+}
+
+// Encode implements encoder.Encode.
+func (*Rrenameat) Encode(b *buffer) {
+}
+
+// Type implements message.Type.
+func (*Rrenameat) Type() MsgType {
+ return MsgRrenameat
+}
+
+// String implements fmt.Stringer.
+func (r *Rrenameat) String() string {
+ return fmt.Sprintf("Rrenameat{}")
+}
+
+// Tunlinkat is an unlink request.
+type Tunlinkat struct {
+ // Directory is the originating directory.
+ Directory FID
+
+ // Name is the name of the entry to unlink.
+ Name string
+
+ // Flags are extra flags (e.g. O_DIRECTORY). These are not interpreted by p9.
+ Flags uint32
+}
+
+// Decode implements encoder.Decode.
+func (t *Tunlinkat) Decode(b *buffer) {
+ t.Directory = b.ReadFID()
+ t.Name = b.ReadString()
+ t.Flags = b.Read32()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tunlinkat) Encode(b *buffer) {
+ b.WriteFID(t.Directory)
+ b.WriteString(t.Name)
+ b.Write32(t.Flags)
+}
+
+// Type implements message.Type.
+func (*Tunlinkat) Type() MsgType {
+ return MsgTunlinkat
+}
+
+// String implements fmt.Stringer.
+func (t *Tunlinkat) String() string {
+ return fmt.Sprintf("Tunlinkat{DirectoryFID: %d, Name: %s, Flags: 0x%X}", t.Directory, t.Name, t.Flags)
+}
+
+// Runlinkat is an unlink response.
+type Runlinkat struct {
+}
+
+// Decode implements encoder.Decode.
+func (*Runlinkat) Decode(b *buffer) {
+}
+
+// Encode implements encoder.Encode.
+func (*Runlinkat) Encode(b *buffer) {
+}
+
+// Type implements message.Type.
+func (*Runlinkat) Type() MsgType {
+ return MsgRunlinkat
+}
+
+// String implements fmt.Stringer.
+func (r *Runlinkat) String() string {
+ return fmt.Sprintf("Runlinkat{}")
+}
+
+// Trename is a rename request.
+//
+// Note that this generally isn't used anymore, and ideally all rename calls
+// should Trenameat below.
+type Trename struct {
+ // FID is the FID to rename.
+ FID FID
+
+ // Directory is the target directory.
+ Directory FID
+
+ // Name is the new file name.
+ Name string
+}
+
+// Decode implements encoder.Decode.
+func (t *Trename) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+ t.Directory = b.ReadFID()
+ t.Name = b.ReadString()
+}
+
+// Encode implements encoder.Encode.
+func (t *Trename) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+ b.WriteFID(t.Directory)
+ b.WriteString(t.Name)
+}
+
+// Type implements message.Type.
+func (*Trename) Type() MsgType {
+ return MsgTrename
+}
+
+// String implements fmt.Stringer.
+func (t *Trename) String() string {
+ return fmt.Sprintf("Trename{FID: %d, DirectoryFID: %d, Name: %s}", t.FID, t.Directory, t.Name)
+}
+
+// Rrename is a rename response.
+type Rrename struct {
+}
+
+// Decode implements encoder.Decode.
+func (*Rrename) Decode(b *buffer) {
+}
+
+// Encode implements encoder.Encode.
+func (*Rrename) Encode(b *buffer) {
+}
+
+// Type implements message.Type.
+func (*Rrename) Type() MsgType {
+ return MsgRrename
+}
+
+// String implements fmt.Stringer.
+func (r *Rrename) String() string {
+ return fmt.Sprintf("Rrename{}")
+}
+
+// Treadlink is a readlink request.
+type Treadlink struct {
+ // FID is the symlink.
+ FID FID
+}
+
+// Decode implements encoder.Decode.
+func (t *Treadlink) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+}
+
+// Encode implements encoder.Encode.
+func (t *Treadlink) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+}
+
+// Type implements message.Type.
+func (*Treadlink) Type() MsgType {
+ return MsgTreadlink
+}
+
+// String implements fmt.Stringer.
+func (t *Treadlink) String() string {
+ return fmt.Sprintf("Treadlink{FID: %d}", t.FID)
+}
+
+// Rreadlink is a readlink response.
+type Rreadlink struct {
+ // Target is the symlink target.
+ Target string
+}
+
+// Decode implements encoder.Decode.
+func (r *Rreadlink) Decode(b *buffer) {
+ r.Target = b.ReadString()
+}
+
+// Encode implements encoder.Encode.
+func (r *Rreadlink) Encode(b *buffer) {
+ b.WriteString(r.Target)
+}
+
+// Type implements message.Type.
+func (*Rreadlink) Type() MsgType {
+ return MsgRreadlink
+}
+
+// String implements fmt.Stringer.
+func (r *Rreadlink) String() string {
+ return fmt.Sprintf("Rreadlink{Target: %s}", r.Target)
+}
+
+// Tread is a read request.
+type Tread struct {
+ // FID is the FID to read.
+ FID FID
+
+ // Offset indicates the file offset.
+ Offset uint64
+
+ // Count indicates the number of bytes to read.
+ Count uint32
+}
+
+// Decode implements encoder.Decode.
+func (t *Tread) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+ t.Offset = b.Read64()
+ t.Count = b.Read32()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tread) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+ b.Write64(t.Offset)
+ b.Write32(t.Count)
+}
+
+// Type implements message.Type.
+func (*Tread) Type() MsgType {
+ return MsgTread
+}
+
+// String implements fmt.Stringer.
+func (t *Tread) String() string {
+ return fmt.Sprintf("Tread{FID: %d, Offset: %d, Count: %d}", t.FID, t.Offset, t.Count)
+}
+
+// Rread is the response for a Tread.
+type Rread struct {
+ // Data is the resulting data.
+ Data []byte
+}
+
+// Decode implements encoder.Decode.
+//
+// Data is automatically decoded via Payload.
+func (r *Rread) Decode(b *buffer) {
+ count := b.Read32()
+ if count != uint32(len(r.Data)) {
+ b.markOverrun()
+ }
+}
+
+// Encode implements encoder.Encode.
+//
+// Data is automatically encoded via Payload.
+func (r *Rread) Encode(b *buffer) {
+ b.Write32(uint32(len(r.Data)))
+}
+
+// Type implements message.Type.
+func (*Rread) Type() MsgType {
+ return MsgRread
+}
+
+// FixedSize implements payloader.FixedSize.
+func (*Rread) FixedSize() uint32 {
+ return 4
+}
+
+// Payload implements payloader.Payload.
+func (r *Rread) Payload() []byte {
+ return r.Data
+}
+
+// SetPayload implements payloader.SetPayload.
+func (r *Rread) SetPayload(p []byte) {
+ r.Data = p
+}
+
+// String implements fmt.Stringer.
+func (r *Rread) String() string {
+ return fmt.Sprintf("Rread{len(Data): %d}", len(r.Data))
+}
+
+// Twrite is a write request.
+type Twrite struct {
+ // FID is the FID to read.
+ FID FID
+
+ // Offset indicates the file offset.
+ Offset uint64
+
+ // Data is the data to be written.
+ Data []byte
+}
+
+// Decode implements encoder.Decode.
+func (t *Twrite) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+ t.Offset = b.Read64()
+ count := b.Read32()
+ if count != uint32(len(t.Data)) {
+ b.markOverrun()
+ }
+}
+
+// Encode implements encoder.Encode.
+//
+// This uses the buffer payload to avoid a copy.
+func (t *Twrite) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+ b.Write64(t.Offset)
+ b.Write32(uint32(len(t.Data)))
+}
+
+// Type implements message.Type.
+func (*Twrite) Type() MsgType {
+ return MsgTwrite
+}
+
+// FixedSize implements payloader.FixedSize.
+func (*Twrite) FixedSize() uint32 {
+ return 16
+}
+
+// Payload implements payloader.Payload.
+func (t *Twrite) Payload() []byte {
+ return t.Data
+}
+
+// SetPayload implements payloader.SetPayload.
+func (t *Twrite) SetPayload(p []byte) {
+ t.Data = p
+}
+
+// String implements fmt.Stringer.
+func (t *Twrite) String() string {
+ return fmt.Sprintf("Twrite{FID: %v, Offset %d, len(Data): %d}", t.FID, t.Offset, len(t.Data))
+}
+
+// Rwrite is the response for a Twrite.
+type Rwrite struct {
+ // Count indicates the number of bytes successfully written.
+ Count uint32
+}
+
+// Decode implements encoder.Decode.
+func (r *Rwrite) Decode(b *buffer) {
+ r.Count = b.Read32()
+}
+
+// Encode implements encoder.Encode.
+func (r *Rwrite) Encode(b *buffer) {
+ b.Write32(r.Count)
+}
+
+// Type implements message.Type.
+func (*Rwrite) Type() MsgType {
+ return MsgRwrite
+}
+
+// String implements fmt.Stringer.
+func (r *Rwrite) String() string {
+ return fmt.Sprintf("Rwrite{Count: %d}", r.Count)
+}
+
+// Tmknod is a mknod request.
+type Tmknod struct {
+ // Directory is the parent directory.
+ Directory FID
+
+ // Name is the device name.
+ Name string
+
+ // Permissions are the device permissions.
+ Permissions FileMode
+
+ // Major is the device major number.
+ Major uint32
+
+ // Minor is the device minor number.
+ Minor uint32
+
+ // GID is the device GID.
+ GID GID
+}
+
+// Decode implements encoder.Decode.
+func (t *Tmknod) Decode(b *buffer) {
+ t.Directory = b.ReadFID()
+ t.Name = b.ReadString()
+ t.Permissions = b.ReadPermissions()
+ t.Major = b.Read32()
+ t.Minor = b.Read32()
+ t.GID = b.ReadGID()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tmknod) Encode(b *buffer) {
+ b.WriteFID(t.Directory)
+ b.WriteString(t.Name)
+ b.WritePermissions(t.Permissions)
+ b.Write32(t.Major)
+ b.Write32(t.Minor)
+ b.WriteGID(t.GID)
+}
+
+// Type implements message.Type.
+func (*Tmknod) Type() MsgType {
+ return MsgTmknod
+}
+
+// String implements fmt.Stringer.
+func (t *Tmknod) String() string {
+ return fmt.Sprintf("Tmknod{DirectoryFID: %d, Name: %s, Permissions: 0o%o, Major: %d, Minor: %d, GID: %d}", t.Directory, t.Name, t.Permissions, t.Major, t.Minor, t.GID)
+}
+
+// Rmknod is a mknod response.
+type Rmknod struct {
+ // QID is the resulting QID.
+ QID QID
+}
+
+// Decode implements encoder.Decode.
+func (r *Rmknod) Decode(b *buffer) {
+ r.QID.Decode(b)
+}
+
+// Encode implements encoder.Encode.
+func (r *Rmknod) Encode(b *buffer) {
+ r.QID.Encode(b)
+}
+
+// Type implements message.Type.
+func (*Rmknod) Type() MsgType {
+ return MsgRmknod
+}
+
+// String implements fmt.Stringer.
+func (r *Rmknod) String() string {
+ return fmt.Sprintf("Rmknod{QID: %s}", r.QID)
+}
+
+// Tmkdir is a mkdir request.
+type Tmkdir struct {
+ // Directory is the parent directory.
+ Directory FID
+
+ // Name is the new directory name.
+ Name string
+
+ // Permissions is the set of permission bits.
+ Permissions FileMode
+
+ // GID is the owning group.
+ GID GID
+}
+
+// Decode implements encoder.Decode.
+func (t *Tmkdir) Decode(b *buffer) {
+ t.Directory = b.ReadFID()
+ t.Name = b.ReadString()
+ t.Permissions = b.ReadPermissions()
+ t.GID = b.ReadGID()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tmkdir) Encode(b *buffer) {
+ b.WriteFID(t.Directory)
+ b.WriteString(t.Name)
+ b.WritePermissions(t.Permissions)
+ b.WriteGID(t.GID)
+}
+
+// Type implements message.Type.
+func (*Tmkdir) Type() MsgType {
+ return MsgTmkdir
+}
+
+// String implements fmt.Stringer.
+func (t *Tmkdir) String() string {
+ return fmt.Sprintf("Tmkdir{DirectoryFID: %d, Name: %s, Permissions: 0o%o, GID: %d}", t.Directory, t.Name, t.Permissions, t.GID)
+}
+
+// Rmkdir is a mkdir response.
+type Rmkdir struct {
+ // QID is the resulting QID.
+ QID QID
+}
+
+// Decode implements encoder.Decode.
+func (r *Rmkdir) Decode(b *buffer) {
+ r.QID.Decode(b)
+}
+
+// Encode implements encoder.Encode.
+func (r *Rmkdir) Encode(b *buffer) {
+ r.QID.Encode(b)
+}
+
+// Type implements message.Type.
+func (*Rmkdir) Type() MsgType {
+ return MsgRmkdir
+}
+
+// String implements fmt.Stringer.
+func (r *Rmkdir) String() string {
+ return fmt.Sprintf("Rmkdir{QID: %s}", r.QID)
+}
+
+// Tgetattr is a getattr request.
+type Tgetattr struct {
+ // FID is the FID to get attributes for.
+ FID FID
+
+ // AttrMask is the set of attributes to get.
+ AttrMask AttrMask
+}
+
+// Decode implements encoder.Decode.
+func (t *Tgetattr) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+ t.AttrMask.Decode(b)
+}
+
+// Encode implements encoder.Encode.
+func (t *Tgetattr) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+ t.AttrMask.Encode(b)
+}
+
+// Type implements message.Type.
+func (*Tgetattr) Type() MsgType {
+ return MsgTgetattr
+}
+
+// String implements fmt.Stringer.
+func (t *Tgetattr) String() string {
+ return fmt.Sprintf("Tgetattr{FID: %d, AttrMask: %s}", t.FID, t.AttrMask)
+}
+
+// Rgetattr is a getattr response.
+type Rgetattr struct {
+ // Valid indicates which fields are valid.
+ Valid AttrMask
+
+ // QID is the QID for this file.
+ QID
+
+ // Attr is the set of attributes.
+ Attr Attr
+}
+
+// Decode implements encoder.Decode.
+func (r *Rgetattr) Decode(b *buffer) {
+ r.Valid.Decode(b)
+ r.QID.Decode(b)
+ r.Attr.Decode(b)
+}
+
+// Encode implements encoder.Encode.
+func (r *Rgetattr) Encode(b *buffer) {
+ r.Valid.Encode(b)
+ r.QID.Encode(b)
+ r.Attr.Encode(b)
+}
+
+// Type implements message.Type.
+func (*Rgetattr) Type() MsgType {
+ return MsgRgetattr
+}
+
+// String implements fmt.Stringer.
+func (r *Rgetattr) String() string {
+ return fmt.Sprintf("Rgetattr{Valid: %v, QID: %s, Attr: %s}", r.Valid, r.QID, r.Attr)
+}
+
+// Tsetattr is a setattr request.
+type Tsetattr struct {
+ // FID is the FID to change.
+ FID FID
+
+ // Valid is the set of bits which will be used.
+ Valid SetAttrMask
+
+ // SetAttr is the set request.
+ SetAttr SetAttr
+}
+
+// Decode implements encoder.Decode.
+func (t *Tsetattr) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+ t.Valid.Decode(b)
+ t.SetAttr.Decode(b)
+}
+
+// Encode implements encoder.Encode.
+func (t *Tsetattr) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+ t.Valid.Encode(b)
+ t.SetAttr.Encode(b)
+}
+
+// Type implements message.Type.
+func (*Tsetattr) Type() MsgType {
+ return MsgTsetattr
+}
+
+// String implements fmt.Stringer.
+func (t *Tsetattr) String() string {
+ return fmt.Sprintf("Tsetattr{FID: %d, Valid: %v, SetAttr: %s}", t.FID, t.Valid, t.SetAttr)
+}
+
+// Rsetattr is a setattr response.
+type Rsetattr struct {
+}
+
+// Decode implements encoder.Decode.
+func (*Rsetattr) Decode(b *buffer) {
+}
+
+// Encode implements encoder.Encode.
+func (*Rsetattr) Encode(b *buffer) {
+}
+
+// Type implements message.Type.
+func (*Rsetattr) Type() MsgType {
+ return MsgRsetattr
+}
+
+// String implements fmt.Stringer.
+func (r *Rsetattr) String() string {
+ return fmt.Sprintf("Rsetattr{}")
+}
+
+// Txattrwalk walks extended attributes.
+type Txattrwalk struct {
+ // FID is the FID to check for attributes.
+ FID FID
+
+ // NewFID is the new FID associated with the attributes.
+ NewFID FID
+
+ // Name is the attribute name.
+ Name string
+}
+
+// Decode implements encoder.Decode.
+func (t *Txattrwalk) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+ t.NewFID = b.ReadFID()
+ t.Name = b.ReadString()
+}
+
+// Encode implements encoder.Encode.
+func (t *Txattrwalk) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+ b.WriteFID(t.NewFID)
+ b.WriteString(t.Name)
+}
+
+// Type implements message.Type.
+func (*Txattrwalk) Type() MsgType {
+ return MsgTxattrwalk
+}
+
+// String implements fmt.Stringer.
+func (t *Txattrwalk) String() string {
+ return fmt.Sprintf("Txattrwalk{FID: %d, NewFID: %d, Name: %s}", t.FID, t.NewFID, t.Name)
+}
+
+// Rxattrwalk is a xattrwalk response.
+type Rxattrwalk struct {
+ // Size is the size of the extended attribute.
+ Size uint64
+}
+
+// Decode implements encoder.Decode.
+func (r *Rxattrwalk) Decode(b *buffer) {
+ r.Size = b.Read64()
+}
+
+// Encode implements encoder.Encode.
+func (r *Rxattrwalk) Encode(b *buffer) {
+ b.Write64(r.Size)
+}
+
+// Type implements message.Type.
+func (*Rxattrwalk) Type() MsgType {
+ return MsgRxattrwalk
+}
+
+// String implements fmt.Stringer.
+func (r *Rxattrwalk) String() string {
+ return fmt.Sprintf("Rxattrwalk{Size: %d}", r.Size)
+}
+
+// Txattrcreate prepare to set extended attributes.
+type Txattrcreate struct {
+ // FID is input/output parameter, it identifies the file on which
+ // extended attributes will be set but after successful Rxattrcreate
+ // it is used to write the extended attribute value.
+ FID FID
+
+ // Name is the attribute name.
+ Name string
+
+ // Size of the attribute value. When the FID is clunked it has to match
+ // the number of bytes written to the FID.
+ AttrSize uint64
+
+ // Linux setxattr(2) flags.
+ Flags uint32
+}
+
+// Decode implements encoder.Decode.
+func (t *Txattrcreate) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+ t.Name = b.ReadString()
+ t.AttrSize = b.Read64()
+ t.Flags = b.Read32()
+}
+
+// Encode implements encoder.Encode.
+func (t *Txattrcreate) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+ b.WriteString(t.Name)
+ b.Write64(t.AttrSize)
+ b.Write32(t.Flags)
+}
+
+// Type implements message.Type.
+func (*Txattrcreate) Type() MsgType {
+ return MsgTxattrcreate
+}
+
+// String implements fmt.Stringer.
+func (t *Txattrcreate) String() string {
+ return fmt.Sprintf("Txattrcreate{FID: %d, Name: %s, AttrSize: %d, Flags: %d}", t.FID, t.Name, t.AttrSize, t.Flags)
+}
+
+// Rxattrcreate is a xattrcreate response.
+type Rxattrcreate struct {
+}
+
+// Decode implements encoder.Decode.
+func (r *Rxattrcreate) Decode(b *buffer) {
+}
+
+// Encode implements encoder.Encode.
+func (r *Rxattrcreate) Encode(b *buffer) {
+}
+
+// Type implements message.Type.
+func (*Rxattrcreate) Type() MsgType {
+ return MsgRxattrcreate
+}
+
+// String implements fmt.Stringer.
+func (r *Rxattrcreate) String() string {
+ return fmt.Sprintf("Rxattrcreate{}")
+}
+
+// Treaddir is a readdir request.
+type Treaddir struct {
+ // Directory is the directory FID to read.
+ Directory FID
+
+ // Offset is the offset to read at.
+ Offset uint64
+
+ // Count is the number of bytes to read.
+ Count uint32
+}
+
+// Decode implements encoder.Decode.
+func (t *Treaddir) Decode(b *buffer) {
+ t.Directory = b.ReadFID()
+ t.Offset = b.Read64()
+ t.Count = b.Read32()
+}
+
+// Encode implements encoder.Encode.
+func (t *Treaddir) Encode(b *buffer) {
+ b.WriteFID(t.Directory)
+ b.Write64(t.Offset)
+ b.Write32(t.Count)
+}
+
+// Type implements message.Type.
+func (*Treaddir) Type() MsgType {
+ return MsgTreaddir
+}
+
+// String implements fmt.Stringer.
+func (t *Treaddir) String() string {
+ return fmt.Sprintf("Treaddir{DirectoryFID: %d, Offset: %d, Count: %d}", t.Directory, t.Offset, t.Count)
+}
+
+// Rreaddir is a readdir response.
+type Rreaddir struct {
+ // Count is the byte limit.
+ //
+ // This should always be set from the Treaddir request.
+ Count uint32
+
+ // Entries are the resulting entries.
+ //
+ // This may be constructed in decode.
+ Entries []Dirent
+
+ // payload is the encoded payload.
+ //
+ // This is constructed by encode.
+ payload []byte
+}
+
+// Decode implements encoder.Decode.
+func (r *Rreaddir) Decode(b *buffer) {
+ r.Count = b.Read32()
+ entriesBuf := buffer{data: r.payload}
+ for {
+ var d Dirent
+ d.Decode(&entriesBuf)
+ if entriesBuf.isOverrun() {
+ // Couldn't decode a complete entry.
+ break
+ }
+ r.Entries = append(r.Entries, d)
+ }
+}
+
+// Encode implements encoder.Encode.
+func (r *Rreaddir) Encode(b *buffer) {
+ entriesBuf := buffer{}
+ for _, d := range r.Entries {
+ d.Encode(&entriesBuf)
+ if len(entriesBuf.data) >= int(r.Count) {
+ break
+ }
+ }
+ if len(entriesBuf.data) < int(r.Count) {
+ r.Count = uint32(len(entriesBuf.data))
+ r.payload = entriesBuf.data
+ } else {
+ r.payload = entriesBuf.data[:r.Count]
+ }
+ b.Write32(uint32(r.Count))
+}
+
+// Type implements message.Type.
+func (*Rreaddir) Type() MsgType {
+ return MsgRreaddir
+}
+
+// FixedSize implements payloader.FixedSize.
+func (*Rreaddir) FixedSize() uint32 {
+ return 4
+}
+
+// Payload implements payloader.Payload.
+func (r *Rreaddir) Payload() []byte {
+ return r.payload
+}
+
+// SetPayload implements payloader.SetPayload.
+func (r *Rreaddir) SetPayload(p []byte) {
+ r.payload = p
+}
+
+// String implements fmt.Stringer.
+func (r *Rreaddir) String() string {
+ return fmt.Sprintf("Rreaddir{Count: %d, Entries: %s}", r.Count, r.Entries)
+}
+
+// Tfsync is an fsync request.
+type Tfsync struct {
+ // FID is the fid to sync.
+ FID FID
+}
+
+// Decode implements encoder.Decode.
+func (t *Tfsync) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tfsync) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+}
+
+// Type implements message.Type.
+func (*Tfsync) Type() MsgType {
+ return MsgTfsync
+}
+
+// String implements fmt.Stringer.
+func (t *Tfsync) String() string {
+ return fmt.Sprintf("Tfsync{FID: %d}", t.FID)
+}
+
+// Rfsync is an fsync response.
+type Rfsync struct {
+}
+
+// Decode implements encoder.Decode.
+func (*Rfsync) Decode(b *buffer) {
+}
+
+// Encode implements encoder.Encode.
+func (*Rfsync) Encode(b *buffer) {
+}
+
+// Type implements message.Type.
+func (*Rfsync) Type() MsgType {
+ return MsgRfsync
+}
+
+// String implements fmt.Stringer.
+func (r *Rfsync) String() string {
+ return fmt.Sprintf("Rfsync{}")
+}
+
+// Tstatfs is a stat request.
+type Tstatfs struct {
+ // FID is the root.
+ FID FID
+}
+
+// Decode implements encoder.Decode.
+func (t *Tstatfs) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tstatfs) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+}
+
+// Type implements message.Type.
+func (*Tstatfs) Type() MsgType {
+ return MsgTstatfs
+}
+
+// String implements fmt.Stringer.
+func (t *Tstatfs) String() string {
+ return fmt.Sprintf("Tstatfs{FID: %d}", t.FID)
+}
+
+// Rstatfs is the response for a Tstatfs.
+type Rstatfs struct {
+ // FSStat is the stat result.
+ FSStat FSStat
+}
+
+// Decode implements encoder.Decode.
+func (r *Rstatfs) Decode(b *buffer) {
+ r.FSStat.Decode(b)
+}
+
+// Encode implements encoder.Encode.
+func (r *Rstatfs) Encode(b *buffer) {
+ r.FSStat.Encode(b)
+}
+
+// Type implements message.Type.
+func (*Rstatfs) Type() MsgType {
+ return MsgRstatfs
+}
+
+// String implements fmt.Stringer.
+func (r *Rstatfs) String() string {
+ return fmt.Sprintf("Rstatfs{FSStat: %v}", r.FSStat)
+}
+
+// Tflushf is a flush file request, not to be confused with Tflush.
+type Tflushf struct {
+ // FID is the FID to be flushed.
+ FID FID
+}
+
+// Decode implements encoder.Decode.
+func (t *Tflushf) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tflushf) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+}
+
+// Type implements message.Type.
+func (*Tflushf) Type() MsgType {
+ return MsgTflushf
+}
+
+// String implements fmt.Stringer.
+func (t *Tflushf) String() string {
+ return fmt.Sprintf("Tflushf{FID: %d}", t.FID)
+}
+
+// Rflushf is a flush file response.
+type Rflushf struct {
+}
+
+// Decode implements encoder.Decode.
+func (*Rflushf) Decode(b *buffer) {
+}
+
+// Encode implements encoder.Encode.
+func (*Rflushf) Encode(b *buffer) {
+}
+
+// Type implements message.Type.
+func (*Rflushf) Type() MsgType {
+ return MsgRflushf
+}
+
+// String implements fmt.Stringer.
+func (*Rflushf) String() string {
+ return fmt.Sprintf("Rflushf{}")
+}
+
+// Twalkgetattr is a walk request.
+type Twalkgetattr struct {
+ // FID is the FID to be walked.
+ FID FID
+
+ // NewFID is the resulting FID.
+ NewFID FID
+
+ // Names are the set of names to be walked.
+ Names []string
+}
+
+// Decode implements encoder.Decode.
+func (t *Twalkgetattr) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+ t.NewFID = b.ReadFID()
+ n := b.Read16()
+ for i := 0; i < int(n); i++ {
+ t.Names = append(t.Names, b.ReadString())
+ }
+}
+
+// Encode implements encoder.Encode.
+func (t *Twalkgetattr) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+ b.WriteFID(t.NewFID)
+ b.Write16(uint16(len(t.Names)))
+ for _, name := range t.Names {
+ b.WriteString(name)
+ }
+}
+
+// Type implements message.Type.
+func (*Twalkgetattr) Type() MsgType {
+ return MsgTwalkgetattr
+}
+
+// String implements fmt.Stringer.
+func (t *Twalkgetattr) String() string {
+ return fmt.Sprintf("Twalkgetattr{FID: %d, NewFID: %d, Names: %v}", t.FID, t.NewFID, t.Names)
+}
+
+// Rwalkgetattr is a walk response.
+type Rwalkgetattr struct {
+ // Valid indicates which fields are valid in the Attr below.
+ Valid AttrMask
+
+ // Attr is the set of attributes for the last QID (the file walked to).
+ Attr Attr
+
+ // QIDs are the set of QIDs returned.
+ QIDs []QID
+}
+
+// Decode implements encoder.Decode.
+func (r *Rwalkgetattr) Decode(b *buffer) {
+ r.Valid.Decode(b)
+ r.Attr.Decode(b)
+ n := b.Read16()
+ for i := 0; i < int(n); i++ {
+ var q QID
+ q.Decode(b)
+ r.QIDs = append(r.QIDs, q)
+ }
+}
+
+// Encode implements encoder.Encode.
+func (r *Rwalkgetattr) Encode(b *buffer) {
+ r.Valid.Encode(b)
+ r.Attr.Encode(b)
+ b.Write16(uint16(len(r.QIDs)))
+ for _, q := range r.QIDs {
+ q.Encode(b)
+ }
+}
+
+// Type implements message.Type.
+func (*Rwalkgetattr) Type() MsgType {
+ return MsgRwalkgetattr
+}
+
+// String implements fmt.Stringer.
+func (r *Rwalkgetattr) String() string {
+ return fmt.Sprintf("Rwalkgetattr{Valid: %s, Attr: %s, QIDs: %v}", r.Valid, r.Attr, r.QIDs)
+}
+
+// Tucreate is a Tlcreate message that includes a UID.
+type Tucreate struct {
+ Tlcreate
+
+ // UID is the UID to use as the effective UID in creation messages.
+ UID UID
+}
+
+// Decode implements encoder.Decode.
+func (t *Tucreate) Decode(b *buffer) {
+ t.Tlcreate.Decode(b)
+ t.UID = b.ReadUID()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tucreate) Encode(b *buffer) {
+ t.Tlcreate.Encode(b)
+ b.WriteUID(t.UID)
+}
+
+// Type implements message.Type.
+func (t *Tucreate) Type() MsgType {
+ return MsgTucreate
+}
+
+// String implements fmt.Stringer.
+func (t *Tucreate) String() string {
+ return fmt.Sprintf("Tucreate{Tlcreate: %v, UID: %d}", &t.Tlcreate, t.UID)
+}
+
+// Rucreate is a file creation response.
+type Rucreate struct {
+ Rlcreate
+}
+
+// Type implements message.Type.
+func (*Rucreate) Type() MsgType {
+ return MsgRucreate
+}
+
+// String implements fmt.Stringer.
+func (r *Rucreate) String() string {
+ return fmt.Sprintf("Rucreate{%v}", &r.Rlcreate)
+}
+
+// Tumkdir is a Tmkdir message that includes a UID.
+type Tumkdir struct {
+ Tmkdir
+
+ // UID is the UID to use as the effective UID in creation messages.
+ UID UID
+}
+
+// Decode implements encoder.Decode.
+func (t *Tumkdir) Decode(b *buffer) {
+ t.Tmkdir.Decode(b)
+ t.UID = b.ReadUID()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tumkdir) Encode(b *buffer) {
+ t.Tmkdir.Encode(b)
+ b.WriteUID(t.UID)
+}
+
+// Type implements message.Type.
+func (t *Tumkdir) Type() MsgType {
+ return MsgTumkdir
+}
+
+// String implements fmt.Stringer.
+func (t *Tumkdir) String() string {
+ return fmt.Sprintf("Tumkdir{Tmkdir: %v, UID: %d}", &t.Tmkdir, t.UID)
+}
+
+// Rumkdir is a umkdir response.
+type Rumkdir struct {
+ Rmkdir
+}
+
+// Type implements message.Type.
+func (*Rumkdir) Type() MsgType {
+ return MsgRumkdir
+}
+
+// String implements fmt.Stringer.
+func (r *Rumkdir) String() string {
+ return fmt.Sprintf("Rumkdir{%v}", &r.Rmkdir)
+}
+
+// Tumknod is a Tmknod message that includes a UID.
+type Tumknod struct {
+ Tmknod
+
+ // UID is the UID to use as the effective UID in creation messages.
+ UID UID
+}
+
+// Decode implements encoder.Decode.
+func (t *Tumknod) Decode(b *buffer) {
+ t.Tmknod.Decode(b)
+ t.UID = b.ReadUID()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tumknod) Encode(b *buffer) {
+ t.Tmknod.Encode(b)
+ b.WriteUID(t.UID)
+}
+
+// Type implements message.Type.
+func (t *Tumknod) Type() MsgType {
+ return MsgTumknod
+}
+
+// String implements fmt.Stringer.
+func (t *Tumknod) String() string {
+ return fmt.Sprintf("Tumknod{Tmknod: %v, UID: %d}", &t.Tmknod, t.UID)
+}
+
+// Rumknod is a umknod response.
+type Rumknod struct {
+ Rmknod
+}
+
+// Type implements message.Type.
+func (*Rumknod) Type() MsgType {
+ return MsgRumknod
+}
+
+// String implements fmt.Stringer.
+func (r *Rumknod) String() string {
+ return fmt.Sprintf("Rumknod{%v}", &r.Rmknod)
+}
+
+// Tusymlink is a Tsymlink message that includes a UID.
+type Tusymlink struct {
+ Tsymlink
+
+ // UID is the UID to use as the effective UID in creation messages.
+ UID UID
+}
+
+// Decode implements encoder.Decode.
+func (t *Tusymlink) Decode(b *buffer) {
+ t.Tsymlink.Decode(b)
+ t.UID = b.ReadUID()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tusymlink) Encode(b *buffer) {
+ t.Tsymlink.Encode(b)
+ b.WriteUID(t.UID)
+}
+
+// Type implements message.Type.
+func (t *Tusymlink) Type() MsgType {
+ return MsgTusymlink
+}
+
+// String implements fmt.Stringer.
+func (t *Tusymlink) String() string {
+ return fmt.Sprintf("Tusymlink{Tsymlink: %v, UID: %d}", &t.Tsymlink, t.UID)
+}
+
+// Rusymlink is a usymlink response.
+type Rusymlink struct {
+ Rsymlink
+}
+
+// Type implements message.Type.
+func (*Rusymlink) Type() MsgType {
+ return MsgRusymlink
+}
+
+// String implements fmt.Stringer.
+func (r *Rusymlink) String() string {
+ return fmt.Sprintf("Rusymlink{%v}", &r.Rsymlink)
+}
+
+// Tlconnect is a connect request.
+type Tlconnect struct {
+ // FID is the FID to be connected.
+ FID FID
+
+ // Flags are the connect flags.
+ Flags ConnectFlags
+}
+
+// Decode implements encoder.Decode.
+func (t *Tlconnect) Decode(b *buffer) {
+ t.FID = b.ReadFID()
+ t.Flags = b.ReadConnectFlags()
+}
+
+// Encode implements encoder.Encode.
+func (t *Tlconnect) Encode(b *buffer) {
+ b.WriteFID(t.FID)
+ b.WriteConnectFlags(t.Flags)
+}
+
+// Type implements message.Type.
+func (*Tlconnect) Type() MsgType {
+ return MsgTlconnect
+}
+
+// String implements fmt.Stringer.
+func (t *Tlconnect) String() string {
+ return fmt.Sprintf("Tlconnect{FID: %d, Flags: %v}", t.FID, t.Flags)
+}
+
+// Rlconnect is a connect response.
+type Rlconnect struct {
+ // File is a host socket.
+ File *fd.FD
+}
+
+// Decode implements encoder.Decode.
+func (r *Rlconnect) Decode(*buffer) {}
+
+// Encode implements encoder.Encode.
+func (r *Rlconnect) Encode(*buffer) {}
+
+// Type implements message.Type.
+func (*Rlconnect) Type() MsgType {
+ return MsgRlconnect
+}
+
+// FilePayload returns the file payload.
+func (r *Rlconnect) FilePayload() *fd.FD {
+ return r.File
+}
+
+// SetFilePayload sets the received file.
+func (r *Rlconnect) SetFilePayload(file *fd.FD) {
+ r.File = file
+}
+
+// String implements fmt.Stringer.
+func (r *Rlconnect) String() string {
+ return fmt.Sprintf("Rlconnect{File: %v}", r.File)
+}
+
+// messageRegistry indexes all messages by type.
+var messageRegistry = make(map[MsgType]func() message)
+
+// messageByType creates a new message by type.
+//
+// An error is returned in the case of an unknown message.
+//
+// This takes, and ignores, a message tag so that it may be used directly as a
+// lookupTagAndType function for recv (by design).
+func messageByType(_ Tag, t MsgType) (message, error) {
+ fn, ok := messageRegistry[t]
+ if !ok {
+ return nil, &ErrInvalidMsgType{t}
+ }
+ return fn(), nil
+}
+
+// register registers the given message type.
+//
+// This uses reflection and records only the type. This may cause panic on
+// failure and should only be used from init.
+func register(m message) {
+ t := m.Type()
+ if fn, ok := messageRegistry[t]; ok {
+ panic(fmt.Sprintf("duplicate message type %d: first is %#v, second is %#v", t, fn(), m))
+ }
+
+ to := reflect.ValueOf(m).Elem().Type()
+ messageRegistry[t] = func() message {
+ return reflect.New(to).Interface().(message)
+ }
+}
+
+func calculateSize(m message) uint32 {
+ if p, ok := m.(payloader); ok {
+ return p.FixedSize()
+ }
+ var dataBuf buffer
+ m.Encode(&dataBuf)
+ return uint32(len(dataBuf.data))
+}
+
+// largestFixedSize is computed within calculateLargestSize.
+//
+// This is computed so that given some message size M, you can compute
+// the maximum payload size (e.g. for Twrite, Rread) with M-largestFixedSize.
+// You could do this individual on a per-message basis, but it's easier to
+// compute a single maximum safe payload.
+var largestFixedSize uint32
+
+// calculateLargestFixedSize is called from within init.
+func calculateLargestFixedSize() {
+ for _, fn := range messageRegistry {
+ if size := calculateSize(fn()); size > largestFixedSize {
+ largestFixedSize = size
+ }
+ }
+}
+
+func init() {
+ register(&Rlerror{})
+ register(&Tstatfs{})
+ register(&Rstatfs{})
+ register(&Tlopen{})
+ register(&Rlopen{})
+ register(&Tlcreate{})
+ register(&Rlcreate{})
+ register(&Tsymlink{})
+ register(&Rsymlink{})
+ register(&Tmknod{})
+ register(&Rmknod{})
+ register(&Trename{})
+ register(&Rrename{})
+ register(&Treadlink{})
+ register(&Rreadlink{})
+ register(&Tgetattr{})
+ register(&Rgetattr{})
+ register(&Tsetattr{})
+ register(&Rsetattr{})
+ register(&Txattrwalk{})
+ register(&Rxattrwalk{})
+ register(&Txattrcreate{})
+ register(&Rxattrcreate{})
+ register(&Treaddir{})
+ register(&Rreaddir{})
+ register(&Tfsync{})
+ register(&Rfsync{})
+ register(&Tlink{})
+ register(&Rlink{})
+ register(&Tmkdir{})
+ register(&Rmkdir{})
+ register(&Trenameat{})
+ register(&Rrenameat{})
+ register(&Tunlinkat{})
+ register(&Runlinkat{})
+ register(&Tversion{})
+ register(&Rversion{})
+ register(&Tauth{})
+ register(&Rauth{})
+ register(&Tattach{})
+ register(&Rattach{})
+ register(&Tflush{})
+ register(&Rflush{})
+ register(&Twalk{})
+ register(&Rwalk{})
+ register(&Tread{})
+ register(&Rread{})
+ register(&Twrite{})
+ register(&Rwrite{})
+ register(&Tclunk{})
+ register(&Rclunk{})
+ register(&Tremove{})
+ register(&Rremove{})
+ register(&Tflushf{})
+ register(&Rflushf{})
+ register(&Twalkgetattr{})
+ register(&Rwalkgetattr{})
+ register(&Tucreate{})
+ register(&Rucreate{})
+ register(&Tumkdir{})
+ register(&Rumkdir{})
+ register(&Tumknod{})
+ register(&Rumknod{})
+ register(&Tusymlink{})
+ register(&Rusymlink{})
+ register(&Tlconnect{})
+ register(&Rlconnect{})
+
+ calculateLargestFixedSize()
+}
diff --git a/pkg/p9/messages_test.go b/pkg/p9/messages_test.go
new file mode 100644
index 000000000..f353755f1
--- /dev/null
+++ b/pkg/p9/messages_test.go
@@ -0,0 +1,391 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package p9
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestEncodeDecode(t *testing.T) {
+ objs := []encoder{
+ &QID{
+ Type: 1,
+ Version: 2,
+ Path: 3,
+ },
+ &FSStat{
+ Type: 1,
+ BlockSize: 2,
+ Blocks: 3,
+ BlocksFree: 4,
+ BlocksAvailable: 5,
+ Files: 6,
+ FilesFree: 7,
+ FSID: 8,
+ NameLength: 9,
+ },
+ &AttrMask{
+ Mode: true,
+ NLink: true,
+ UID: true,
+ GID: true,
+ RDev: true,
+ ATime: true,
+ MTime: true,
+ CTime: true,
+ INo: true,
+ Size: true,
+ Blocks: true,
+ BTime: true,
+ Gen: true,
+ DataVersion: true,
+ },
+ &Attr{
+ Mode: Exec,
+ UID: 2,
+ GID: 3,
+ NLink: 4,
+ RDev: 5,
+ Size: 6,
+ BlockSize: 7,
+ Blocks: 8,
+ ATimeSeconds: 9,
+ ATimeNanoSeconds: 10,
+ MTimeSeconds: 11,
+ MTimeNanoSeconds: 12,
+ CTimeSeconds: 13,
+ CTimeNanoSeconds: 14,
+ BTimeSeconds: 15,
+ BTimeNanoSeconds: 16,
+ Gen: 17,
+ DataVersion: 18,
+ },
+ &SetAttrMask{
+ Permissions: true,
+ UID: true,
+ GID: true,
+ Size: true,
+ ATime: true,
+ MTime: true,
+ CTime: true,
+ ATimeNotSystemTime: true,
+ MTimeNotSystemTime: true,
+ },
+ &SetAttr{
+ Permissions: 1,
+ UID: 2,
+ GID: 3,
+ Size: 4,
+ ATimeSeconds: 5,
+ ATimeNanoSeconds: 6,
+ MTimeSeconds: 7,
+ MTimeNanoSeconds: 8,
+ },
+ &Dirent{
+ QID: QID{Type: 1},
+ Offset: 2,
+ Type: 3,
+ Name: "a",
+ },
+ &Rlerror{
+ Error: 1,
+ },
+ &Tstatfs{
+ FID: 1,
+ },
+ &Rstatfs{
+ FSStat: FSStat{Type: 1},
+ },
+ &Tlopen{
+ FID: 1,
+ Flags: WriteOnly,
+ },
+ &Rlopen{
+ QID: QID{Type: 1},
+ IoUnit: 2,
+ },
+ &Tlconnect{
+ FID: 1,
+ },
+ &Rlconnect{},
+ &Tlcreate{
+ FID: 1,
+ Name: "a",
+ OpenFlags: 2,
+ Permissions: 3,
+ GID: 4,
+ },
+ &Rlcreate{
+ Rlopen{QID: QID{Type: 1}},
+ },
+ &Tsymlink{
+ Directory: 1,
+ Name: "a",
+ Target: "b",
+ GID: 2,
+ },
+ &Rsymlink{
+ QID: QID{Type: 1},
+ },
+ &Tmknod{
+ Directory: 1,
+ Name: "a",
+ Permissions: 2,
+ Major: 3,
+ Minor: 4,
+ GID: 5,
+ },
+ &Rmknod{
+ QID: QID{Type: 1},
+ },
+ &Trename{
+ FID: 1,
+ Directory: 2,
+ Name: "a",
+ },
+ &Rrename{},
+ &Treadlink{
+ FID: 1,
+ },
+ &Rreadlink{
+ Target: "a",
+ },
+ &Tgetattr{
+ FID: 1,
+ AttrMask: AttrMask{Mode: true},
+ },
+ &Rgetattr{
+ Valid: AttrMask{Mode: true},
+ QID: QID{Type: 1},
+ Attr: Attr{Mode: Write},
+ },
+ &Tsetattr{
+ FID: 1,
+ Valid: SetAttrMask{Permissions: true},
+ SetAttr: SetAttr{Permissions: Write},
+ },
+ &Rsetattr{},
+ &Txattrwalk{
+ FID: 1,
+ NewFID: 2,
+ Name: "a",
+ },
+ &Rxattrwalk{
+ Size: 1,
+ },
+ &Treaddir{
+ Directory: 1,
+ Offset: 2,
+ Count: 3,
+ },
+ &Rreaddir{
+ // Count must be sufficient to encode a dirent.
+ Count: 0x18,
+ Entries: []Dirent{{QID: QID{Type: 2}}},
+ },
+ &Tfsync{
+ FID: 1,
+ },
+ &Rfsync{},
+ &Tlink{
+ Directory: 1,
+ Target: 2,
+ Name: "a",
+ },
+ &Rlink{},
+ &Tmkdir{
+ Directory: 1,
+ Name: "a",
+ Permissions: 2,
+ GID: 3,
+ },
+ &Rmkdir{
+ QID: QID{Type: 1},
+ },
+ &Trenameat{
+ OldDirectory: 1,
+ OldName: "a",
+ NewDirectory: 2,
+ NewName: "b",
+ },
+ &Rrenameat{},
+ &Tunlinkat{
+ Directory: 1,
+ Name: "a",
+ Flags: 2,
+ },
+ &Runlinkat{},
+ &Tversion{
+ MSize: 1,
+ Version: "a",
+ },
+ &Rversion{
+ MSize: 1,
+ Version: "a",
+ },
+ &Tauth{
+ AuthenticationFID: 1,
+ UserName: "a",
+ AttachName: "b",
+ UID: 2,
+ },
+ &Rauth{
+ QID: QID{Type: 1},
+ },
+ &Tattach{
+ FID: 1,
+ Auth: Tauth{AuthenticationFID: 2},
+ },
+ &Rattach{
+ QID: QID{Type: 1},
+ },
+ &Tflush{
+ OldTag: 1,
+ },
+ &Rflush{},
+ &Twalk{
+ FID: 1,
+ NewFID: 2,
+ Names: []string{"a"},
+ },
+ &Rwalk{
+ QIDs: []QID{{Type: 1}},
+ },
+ &Tread{
+ FID: 1,
+ Offset: 2,
+ Count: 3,
+ },
+ &Rread{
+ Data: []byte{'a'},
+ },
+ &Twrite{
+ FID: 1,
+ Offset: 2,
+ Data: []byte{'a'},
+ },
+ &Rwrite{
+ Count: 1,
+ },
+ &Tclunk{
+ FID: 1,
+ },
+ &Rclunk{},
+ &Tremove{
+ FID: 1,
+ },
+ &Rremove{},
+ &Tflushf{
+ FID: 1,
+ },
+ &Rflushf{},
+ &Twalkgetattr{
+ FID: 1,
+ NewFID: 2,
+ Names: []string{"a"},
+ },
+ &Rwalkgetattr{
+ QIDs: []QID{{Type: 1}},
+ Valid: AttrMask{Mode: true},
+ Attr: Attr{Mode: Write},
+ },
+ &Tucreate{
+ Tlcreate: Tlcreate{
+ FID: 1,
+ Name: "a",
+ OpenFlags: 2,
+ Permissions: 3,
+ GID: 4,
+ },
+ UID: 5,
+ },
+ &Rucreate{
+ Rlcreate{Rlopen{QID: QID{Type: 1}}},
+ },
+ &Tumkdir{
+ Tmkdir: Tmkdir{
+ Directory: 1,
+ Name: "a",
+ Permissions: 2,
+ GID: 3,
+ },
+ UID: 4,
+ },
+ &Rumkdir{
+ Rmkdir{QID: QID{Type: 1}},
+ },
+ &Tusymlink{
+ Tsymlink: Tsymlink{
+ Directory: 1,
+ Name: "a",
+ Target: "b",
+ GID: 2,
+ },
+ UID: 3,
+ },
+ &Rusymlink{
+ Rsymlink{QID: QID{Type: 1}},
+ },
+ &Tumknod{
+ Tmknod: Tmknod{
+ Directory: 1,
+ Name: "a",
+ Permissions: 2,
+ Major: 3,
+ Minor: 4,
+ GID: 5,
+ },
+ UID: 6,
+ },
+ &Rumknod{
+ Rmknod{QID: QID{Type: 1}},
+ },
+ }
+
+ for _, enc := range objs {
+ // Encode the original.
+ data := make([]byte, initialBufferLength)
+ buf := buffer{data: data[:0]}
+ enc.Encode(&buf)
+
+ // Create a new object, same as the first.
+ enc2 := reflect.New(reflect.ValueOf(enc).Elem().Type()).Interface().(encoder)
+ buf2 := buffer{data: buf.data}
+
+ // To be fair, we need to add any payloads (directly).
+ if pl, ok := enc.(payloader); ok {
+ enc2.(payloader).SetPayload(pl.Payload())
+ }
+
+ // And any file payloads (directly).
+ if fl, ok := enc.(filer); ok {
+ enc2.(filer).SetFilePayload(fl.FilePayload())
+ }
+
+ // Mark sure it was okay.
+ enc2.Decode(&buf2)
+ if buf2.isOverrun() {
+ t.Errorf("object %#v->%#v got overrun on decode", enc, enc2)
+ continue
+ }
+
+ // Check that they are equal.
+ if !reflect.DeepEqual(enc, enc2) {
+ t.Errorf("object %#v and %#v differ", enc, enc2)
+ continue
+ }
+ }
+}
diff --git a/pkg/p9/p9.go b/pkg/p9/p9.go
new file mode 100644
index 000000000..c6899c3ce
--- /dev/null
+++ b/pkg/p9/p9.go
@@ -0,0 +1,1023 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package p9 is a 9P2000.L implementation.
+package p9
+
+import (
+ "fmt"
+ "math"
+ "os"
+ "strings"
+ "sync/atomic"
+ "syscall"
+)
+
+// OpenFlags is the mode passed to Open and Create operations.
+//
+// These correspond to bits sent over the wire.
+type OpenFlags uint32
+
+const (
+ // ReadOnly is a Topen and Tcreate flag indicating read-only mode.
+ ReadOnly OpenFlags = 0
+
+ // WriteOnly is a Topen and Tcreate flag indicating write-only mode.
+ WriteOnly OpenFlags = 1
+
+ // ReadWrite is a Topen flag indicates read-write mode.
+ ReadWrite OpenFlags = 2
+
+ // OpenFlagsModeMask is a mask of valid OpenFlags mode bits.
+ OpenFlagsModeMask OpenFlags = 3
+)
+
+// ConnectFlags is the mode passed to Connect operations.
+//
+// These correspond to bits sent over the wire.
+type ConnectFlags uint32
+
+const (
+ // StreamSocket is a Tlconnect flag indicating SOCK_STREAM mode.
+ StreamSocket ConnectFlags = 0
+
+ // DgramSocket is a Tlconnect flag indicating SOCK_DGRAM mode.
+ DgramSocket ConnectFlags = 1
+
+ // SeqpacketSocket is a Tlconnect flag indicating SOCK_SEQPACKET mode.
+ SeqpacketSocket ConnectFlags = 2
+
+ // AnonymousSocket is a Tlconnect flag indicating that the mode does not
+ // matter and that the requester will accept any socket type.
+ AnonymousSocket ConnectFlags = 3
+)
+
+// OSFlags converts a p9.OpenFlags to an int compatible with open(2).
+func (o OpenFlags) OSFlags() int {
+ return int(o & OpenFlagsModeMask)
+}
+
+// String implements fmt.Stringer.
+func (o OpenFlags) String() string {
+ switch o {
+ case ReadOnly:
+ return "ReadOnly"
+ case WriteOnly:
+ return "WriteOnly"
+ case ReadWrite:
+ return "ReadWrite"
+ case OpenFlagsModeMask:
+ return "OpenFlagsModeMask"
+ default:
+ return "UNDEFINED"
+ }
+}
+
+// Tag is a messsage tag.
+type Tag uint16
+
+// FID is a file identifier.
+type FID uint64
+
+// FileMode are flags corresponding to file modes.
+//
+// These correspond to bits sent over the wire.
+// These also correspond to mode_t bits.
+type FileMode uint32
+
+const (
+ // FileModeMask is a mask of all the file mode bits of FileMode.
+ FileModeMask FileMode = 0170000
+
+ // ModeSocket is an (unused) mode bit for a socket.
+ ModeSocket FileMode = 0140000
+
+ // ModeSymlink is a mode bit for a symlink.
+ ModeSymlink FileMode = 0120000
+
+ // ModeRegular is a mode bit for regular files.
+ ModeRegular FileMode = 0100000
+
+ // ModeBlockDevice is a mode bit for block devices.
+ ModeBlockDevice FileMode = 060000
+
+ // ModeDirectory is a mode bit for directories.
+ ModeDirectory FileMode = 040000
+
+ // ModeCharacterDevice is a mode bit for a character device.
+ ModeCharacterDevice FileMode = 020000
+
+ // ModeNamedPipe is a mode bit for a named pipe.
+ ModeNamedPipe FileMode = 010000
+
+ // Read is a mode bit indicating read permission.
+ Read FileMode = 04
+
+ // Write is a mode bit indicating write permission.
+ Write FileMode = 02
+
+ // Exec is a mode bit indicating exec permission.
+ Exec FileMode = 01
+
+ // PermissionsMask is the mask to apply to FileModes for permissions.
+ PermissionsMask FileMode = 0777
+)
+
+// QIDType is the most significant byte of the FileMode word, to be used as the
+// Type field of p9.QID.
+func (m FileMode) QIDType() QIDType {
+ switch {
+ case m.IsDir():
+ return TypeDir
+ case m.IsSocket(), m.IsNamedPipe(), m.IsCharacterDevice():
+ // Best approximation.
+ return TypeAppendOnly
+ case m.IsSymlink():
+ return TypeSymlink
+ default:
+ return TypeRegular
+ }
+}
+
+// FileType returns the file mode without the permission bits.
+func (m FileMode) FileType() FileMode {
+ return m & FileModeMask
+}
+
+// Permissions returns just the permission bits of the mode.
+func (m FileMode) Permissions() FileMode {
+ return m & PermissionsMask
+}
+
+// Writable returns the mode with write bits added.
+func (m FileMode) Writable() FileMode {
+ return m | 0222
+}
+
+// IsReadable returns true if m represents a file that can be read.
+func (m FileMode) IsReadable() bool {
+ return m&0444 != 0
+}
+
+// IsWritable returns true if m represents a file that can be written to.
+func (m FileMode) IsWritable() bool {
+ return m&0222 != 0
+}
+
+// IsExecutable returns true if m represents a file that can be executed.
+func (m FileMode) IsExecutable() bool {
+ return m&0111 != 0
+}
+
+// IsRegular returns true if m is a regular file.
+func (m FileMode) IsRegular() bool {
+ return m&FileModeMask == ModeRegular
+}
+
+// IsDir returns true if m represents a directory.
+func (m FileMode) IsDir() bool {
+ return m&FileModeMask == ModeDirectory
+}
+
+// IsNamedPipe returns true if m represents a named pipe.
+func (m FileMode) IsNamedPipe() bool {
+ return m&FileModeMask == ModeNamedPipe
+}
+
+// IsCharacterDevice returns true if m represents a character device.
+func (m FileMode) IsCharacterDevice() bool {
+ return m&FileModeMask == ModeCharacterDevice
+}
+
+// IsBlockDevice returns true if m represents a character device.
+func (m FileMode) IsBlockDevice() bool {
+ return m&FileModeMask == ModeBlockDevice
+}
+
+// IsSocket returns true if m represents a socket.
+func (m FileMode) IsSocket() bool {
+ return m&FileModeMask == ModeSocket
+}
+
+// IsSymlink returns true if m represents a symlink.
+func (m FileMode) IsSymlink() bool {
+ return m&FileModeMask == ModeSymlink
+}
+
+// ModeFromOS returns a FileMode from an os.FileMode.
+func ModeFromOS(mode os.FileMode) FileMode {
+ m := FileMode(mode.Perm())
+ switch {
+ case mode.IsDir():
+ m |= ModeDirectory
+ case mode&os.ModeSymlink != 0:
+ m |= ModeSymlink
+ case mode&os.ModeSocket != 0:
+ m |= ModeSocket
+ case mode&os.ModeNamedPipe != 0:
+ m |= ModeNamedPipe
+ case mode&os.ModeCharDevice != 0:
+ m |= ModeCharacterDevice
+ case mode&os.ModeDevice != 0:
+ m |= ModeBlockDevice
+ default:
+ m |= ModeRegular
+ }
+ return m
+}
+
+// OSMode converts a p9.FileMode to an os.FileMode.
+func (m FileMode) OSMode() os.FileMode {
+ var osMode os.FileMode
+ osMode |= os.FileMode(m.Permissions())
+ switch {
+ case m.IsDir():
+ osMode |= os.ModeDir
+ case m.IsSymlink():
+ osMode |= os.ModeSymlink
+ case m.IsSocket():
+ osMode |= os.ModeSocket
+ case m.IsNamedPipe():
+ osMode |= os.ModeNamedPipe
+ case m.IsCharacterDevice():
+ osMode |= os.ModeCharDevice | os.ModeDevice
+ case m.IsBlockDevice():
+ osMode |= os.ModeDevice
+ }
+ return osMode
+}
+
+// UID represents a user ID.
+type UID uint32
+
+// Ok returns true if uid is not NoUID.
+func (uid UID) Ok() bool {
+ return uid != NoUID
+}
+
+// GID represents a group ID.
+type GID uint32
+
+// Ok returns true if gid is not NoGID.
+func (gid GID) Ok() bool {
+ return gid != NoGID
+}
+
+const (
+ // NoTag is a sentinel used to indicate no valid tag.
+ NoTag Tag = math.MaxUint16
+
+ // NoFID is a sentinel used to indicate no valid FID.
+ NoFID FID = math.MaxUint32
+
+ // NoUID is a sentinel used to indicate no valid UID.
+ NoUID UID = math.MaxUint32
+
+ // NoGID is a sentinel used to indicate no valid GID.
+ NoGID GID = math.MaxUint32
+)
+
+// MsgType is a type identifier.
+type MsgType uint8
+
+// MsgType declarations.
+const (
+ MsgTlerror MsgType = 6
+ MsgRlerror = 7
+ MsgTstatfs = 8
+ MsgRstatfs = 9
+ MsgTlopen = 12
+ MsgRlopen = 13
+ MsgTlcreate = 14
+ MsgRlcreate = 15
+ MsgTsymlink = 16
+ MsgRsymlink = 17
+ MsgTmknod = 18
+ MsgRmknod = 19
+ MsgTrename = 20
+ MsgRrename = 21
+ MsgTreadlink = 22
+ MsgRreadlink = 23
+ MsgTgetattr = 24
+ MsgRgetattr = 25
+ MsgTsetattr = 26
+ MsgRsetattr = 27
+ MsgTxattrwalk = 30
+ MsgRxattrwalk = 31
+ MsgTxattrcreate = 32
+ MsgRxattrcreate = 33
+ MsgTreaddir = 40
+ MsgRreaddir = 41
+ MsgTfsync = 50
+ MsgRfsync = 51
+ MsgTlink = 70
+ MsgRlink = 71
+ MsgTmkdir = 72
+ MsgRmkdir = 73
+ MsgTrenameat = 74
+ MsgRrenameat = 75
+ MsgTunlinkat = 76
+ MsgRunlinkat = 77
+ MsgTversion = 100
+ MsgRversion = 101
+ MsgTauth = 102
+ MsgRauth = 103
+ MsgTattach = 104
+ MsgRattach = 105
+ MsgTflush = 108
+ MsgRflush = 109
+ MsgTwalk = 110
+ MsgRwalk = 111
+ MsgTread = 116
+ MsgRread = 117
+ MsgTwrite = 118
+ MsgRwrite = 119
+ MsgTclunk = 120
+ MsgRclunk = 121
+ MsgTremove = 122
+ MsgRremove = 123
+ MsgTflushf = 124
+ MsgRflushf = 125
+ MsgTwalkgetattr = 126
+ MsgRwalkgetattr = 127
+ MsgTucreate = 128
+ MsgRucreate = 129
+ MsgTumkdir = 130
+ MsgRumkdir = 131
+ MsgTumknod = 132
+ MsgRumknod = 133
+ MsgTusymlink = 134
+ MsgRusymlink = 135
+ MsgTlconnect = 136
+ MsgRlconnect = 137
+)
+
+// QIDType represents the file type for QIDs.
+//
+// QIDType corresponds to the high 8 bits of a Plan 9 file mode.
+type QIDType uint8
+
+const (
+ // TypeDir represents a directory type.
+ TypeDir QIDType = 0x80
+
+ // TypeAppendOnly represents an append only file.
+ TypeAppendOnly QIDType = 0x40
+
+ // TypeExclusive represents an exclusive-use file.
+ TypeExclusive QIDType = 0x20
+
+ // TypeMount represents a mounted channel.
+ TypeMount QIDType = 0x10
+
+ // TypeAuth represents an authentication file.
+ TypeAuth QIDType = 0x08
+
+ // TypeTemporary represents a temporary file.
+ TypeTemporary QIDType = 0x04
+
+ // TypeSymlink represents a symlink.
+ TypeSymlink QIDType = 0x02
+
+ // TypeLink represents a hard link.
+ TypeLink QIDType = 0x01
+
+ // TypeRegular represents a regular file.
+ TypeRegular QIDType = 0x00
+)
+
+// QID is a unique file identifier.
+//
+// This may be embedded in other requests and responses.
+type QID struct {
+ // Type is the highest order byte of the file mode.
+ Type QIDType
+
+ // Version is an arbitrary server version number.
+ Version uint32
+
+ // Path is a unique server identifier for this path (e.g. inode).
+ Path uint64
+}
+
+// String implements fmt.Stringer.
+func (q QID) String() string {
+ return fmt.Sprintf("QID{Type: %d, Version: %d, Path: %d}", q.Type, q.Version, q.Path)
+}
+
+// Decode implements encoder.Decode.
+func (q *QID) Decode(b *buffer) {
+ q.Type = b.ReadQIDType()
+ q.Version = b.Read32()
+ q.Path = b.Read64()
+}
+
+// Encode implements encoder.Encode.
+func (q *QID) Encode(b *buffer) {
+ b.WriteQIDType(q.Type)
+ b.Write32(q.Version)
+ b.Write64(q.Path)
+}
+
+// QIDGenerator is a simple generator for QIDs that atomically increments Path
+// values.
+type QIDGenerator struct {
+ // uids is an ever increasing value that can be atomically incremented
+ // to provide unique Path values for QIDs.
+ uids uint64
+}
+
+// Get returns a new 9P unique ID with a unique Path given a QID type.
+//
+// While the 9P spec allows Version to be incremented every time the file is
+// modified, we currently do not use the Version member for anything. Hence,
+// it is set to 0.
+func (q *QIDGenerator) Get(t QIDType) QID {
+ return QID{
+ Type: t,
+ Version: 0,
+ Path: atomic.AddUint64(&q.uids, 1),
+ }
+}
+
+// FSStat is used by statfs.
+type FSStat struct {
+ // Type is the filesystem type.
+ Type uint32
+
+ // BlockSize is the blocksize.
+ BlockSize uint32
+
+ // Blocks is the number of blocks.
+ Blocks uint64
+
+ // BlocksFree is the number of free blocks.
+ BlocksFree uint64
+
+ // BlocksAvailable is the number of blocks *available*.
+ BlocksAvailable uint64
+
+ // Files is the number of files available.
+ Files uint64
+
+ // FilesFree is the number of free file nodes.
+ FilesFree uint64
+
+ // FSID is the filesystem ID.
+ FSID uint64
+
+ // NameLength is the maximum name length.
+ NameLength uint32
+}
+
+// Decode implements encoder.Decode.
+func (f *FSStat) Decode(b *buffer) {
+ f.Type = b.Read32()
+ f.BlockSize = b.Read32()
+ f.Blocks = b.Read64()
+ f.BlocksFree = b.Read64()
+ f.BlocksAvailable = b.Read64()
+ f.Files = b.Read64()
+ f.FilesFree = b.Read64()
+ f.FSID = b.Read64()
+ f.NameLength = b.Read32()
+}
+
+// Encode implements encoder.Encode.
+func (f *FSStat) Encode(b *buffer) {
+ b.Write32(f.Type)
+ b.Write32(f.BlockSize)
+ b.Write64(f.Blocks)
+ b.Write64(f.BlocksFree)
+ b.Write64(f.BlocksAvailable)
+ b.Write64(f.Files)
+ b.Write64(f.FilesFree)
+ b.Write64(f.FSID)
+ b.Write32(f.NameLength)
+}
+
+// AttrMask is a mask of attributes for getattr.
+type AttrMask struct {
+ Mode bool
+ NLink bool
+ UID bool
+ GID bool
+ RDev bool
+ ATime bool
+ MTime bool
+ CTime bool
+ INo bool
+ Size bool
+ Blocks bool
+ BTime bool
+ Gen bool
+ DataVersion bool
+}
+
+// Contains returns true if a contains all of the attributes masked as b.
+func (a AttrMask) Contains(b AttrMask) bool {
+ if b.Mode && !a.Mode {
+ return false
+ }
+ if b.NLink && !a.NLink {
+ return false
+ }
+ if b.UID && !a.UID {
+ return false
+ }
+ if b.GID && !a.GID {
+ return false
+ }
+ if b.RDev && !a.RDev {
+ return false
+ }
+ if b.ATime && !a.ATime {
+ return false
+ }
+ if b.MTime && !a.MTime {
+ return false
+ }
+ if b.CTime && !a.CTime {
+ return false
+ }
+ if b.INo && !a.INo {
+ return false
+ }
+ if b.Size && !a.Size {
+ return false
+ }
+ if b.Blocks && !a.Blocks {
+ return false
+ }
+ if b.BTime && !a.BTime {
+ return false
+ }
+ if b.Gen && !a.Gen {
+ return false
+ }
+ if b.DataVersion && !a.DataVersion {
+ return false
+ }
+ return true
+}
+
+// Empty returns true if no fields are masked.
+func (a AttrMask) Empty() bool {
+ return !a.Mode && !a.NLink && !a.UID && !a.GID && !a.RDev && !a.ATime && !a.MTime && !a.CTime && !a.INo && !a.Size && !a.Blocks && !a.BTime && !a.Gen && !a.DataVersion
+}
+
+// AttrMaskAll returns an AttrMask with all fields masked.
+func AttrMaskAll() AttrMask {
+ return AttrMask{
+ Mode: true,
+ NLink: true,
+ UID: true,
+ GID: true,
+ RDev: true,
+ ATime: true,
+ MTime: true,
+ CTime: true,
+ INo: true,
+ Size: true,
+ Blocks: true,
+ BTime: true,
+ Gen: true,
+ DataVersion: true,
+ }
+}
+
+// String implements fmt.Stringer.
+func (a AttrMask) String() string {
+ var masks []string
+ if a.Mode {
+ masks = append(masks, "Mode")
+ }
+ if a.NLink {
+ masks = append(masks, "NLink")
+ }
+ if a.UID {
+ masks = append(masks, "UID")
+ }
+ if a.GID {
+ masks = append(masks, "GID")
+ }
+ if a.RDev {
+ masks = append(masks, "RDev")
+ }
+ if a.ATime {
+ masks = append(masks, "ATime")
+ }
+ if a.MTime {
+ masks = append(masks, "MTime")
+ }
+ if a.CTime {
+ masks = append(masks, "CTime")
+ }
+ if a.INo {
+ masks = append(masks, "INo")
+ }
+ if a.Size {
+ masks = append(masks, "Size")
+ }
+ if a.Blocks {
+ masks = append(masks, "Blocks")
+ }
+ if a.BTime {
+ masks = append(masks, "BTime")
+ }
+ if a.Gen {
+ masks = append(masks, "Gen")
+ }
+ if a.DataVersion {
+ masks = append(masks, "DataVersion")
+ }
+ return fmt.Sprintf("AttrMask{with: %s}", strings.Join(masks, " "))
+}
+
+// Decode implements encoder.Decode.
+func (a *AttrMask) Decode(b *buffer) {
+ mask := b.Read64()
+ a.Mode = mask&0x00000001 != 0
+ a.NLink = mask&0x00000002 != 0
+ a.UID = mask&0x00000004 != 0
+ a.GID = mask&0x00000008 != 0
+ a.RDev = mask&0x00000010 != 0
+ a.ATime = mask&0x00000020 != 0
+ a.MTime = mask&0x00000040 != 0
+ a.CTime = mask&0x00000080 != 0
+ a.INo = mask&0x00000100 != 0
+ a.Size = mask&0x00000200 != 0
+ a.Blocks = mask&0x00000400 != 0
+ a.BTime = mask&0x00000800 != 0
+ a.Gen = mask&0x00001000 != 0
+ a.DataVersion = mask&0x00002000 != 0
+}
+
+// Encode implements encoder.Encode.
+func (a *AttrMask) Encode(b *buffer) {
+ var mask uint64
+ if a.Mode {
+ mask |= 0x00000001
+ }
+ if a.NLink {
+ mask |= 0x00000002
+ }
+ if a.UID {
+ mask |= 0x00000004
+ }
+ if a.GID {
+ mask |= 0x00000008
+ }
+ if a.RDev {
+ mask |= 0x00000010
+ }
+ if a.ATime {
+ mask |= 0x00000020
+ }
+ if a.MTime {
+ mask |= 0x00000040
+ }
+ if a.CTime {
+ mask |= 0x00000080
+ }
+ if a.INo {
+ mask |= 0x00000100
+ }
+ if a.Size {
+ mask |= 0x00000200
+ }
+ if a.Blocks {
+ mask |= 0x00000400
+ }
+ if a.BTime {
+ mask |= 0x00000800
+ }
+ if a.Gen {
+ mask |= 0x00001000
+ }
+ if a.DataVersion {
+ mask |= 0x00002000
+ }
+ b.Write64(mask)
+}
+
+// Attr is a set of attributes for getattr.
+type Attr struct {
+ Mode FileMode
+ UID UID
+ GID GID
+ NLink uint64
+ RDev uint64
+ Size uint64
+ BlockSize uint64
+ Blocks uint64
+ ATimeSeconds uint64
+ ATimeNanoSeconds uint64
+ MTimeSeconds uint64
+ MTimeNanoSeconds uint64
+ CTimeSeconds uint64
+ CTimeNanoSeconds uint64
+ BTimeSeconds uint64
+ BTimeNanoSeconds uint64
+ Gen uint64
+ DataVersion uint64
+}
+
+// String implements fmt.Stringer.
+func (a Attr) String() string {
+ return fmt.Sprintf("Attr{Mode: 0o%o, UID: %d, GID: %d, NLink: %d, RDev: %d, Size: %d, BlockSize: %d, Blocks: %d, ATime: {Sec: %d, NanoSec: %d}, MTime: {Sec: %d, NanoSec: %d}, CTime: {Sec: %d, NanoSec: %d}, BTime: {Sec: %d, NanoSec: %d}, Gen: %d, DataVersion: %d}",
+ a.Mode, a.UID, a.GID, a.NLink, a.RDev, a.Size, a.BlockSize, a.Blocks, a.ATimeSeconds, a.ATimeNanoSeconds, a.MTimeSeconds, a.MTimeNanoSeconds, a.CTimeSeconds, a.CTimeNanoSeconds, a.BTimeSeconds, a.BTimeNanoSeconds, a.Gen, a.DataVersion)
+}
+
+// Encode implements encoder.Encode.
+func (a *Attr) Encode(b *buffer) {
+ b.WriteFileMode(a.Mode)
+ b.WriteUID(a.UID)
+ b.WriteGID(a.GID)
+ b.Write64(a.NLink)
+ b.Write64(a.RDev)
+ b.Write64(a.Size)
+ b.Write64(a.BlockSize)
+ b.Write64(a.Blocks)
+ b.Write64(a.ATimeSeconds)
+ b.Write64(a.ATimeNanoSeconds)
+ b.Write64(a.MTimeSeconds)
+ b.Write64(a.MTimeNanoSeconds)
+ b.Write64(a.CTimeSeconds)
+ b.Write64(a.CTimeNanoSeconds)
+ b.Write64(a.BTimeSeconds)
+ b.Write64(a.BTimeNanoSeconds)
+ b.Write64(a.Gen)
+ b.Write64(a.DataVersion)
+}
+
+// Decode implements encoder.Decode.
+func (a *Attr) Decode(b *buffer) {
+ a.Mode = b.ReadFileMode()
+ a.UID = b.ReadUID()
+ a.GID = b.ReadGID()
+ a.NLink = b.Read64()
+ a.RDev = b.Read64()
+ a.Size = b.Read64()
+ a.BlockSize = b.Read64()
+ a.Blocks = b.Read64()
+ a.ATimeSeconds = b.Read64()
+ a.ATimeNanoSeconds = b.Read64()
+ a.MTimeSeconds = b.Read64()
+ a.MTimeNanoSeconds = b.Read64()
+ a.CTimeSeconds = b.Read64()
+ a.CTimeNanoSeconds = b.Read64()
+ a.BTimeSeconds = b.Read64()
+ a.BTimeNanoSeconds = b.Read64()
+ a.Gen = b.Read64()
+ a.DataVersion = b.Read64()
+}
+
+// StatToAttr converts a Linux syscall stat structure to an Attr.
+func StatToAttr(s *syscall.Stat_t, req AttrMask) (Attr, AttrMask) {
+ attr := Attr{
+ UID: NoUID,
+ GID: NoGID,
+ }
+ if req.Mode {
+ // p9.FileMode corresponds to Linux mode_t.
+ attr.Mode = FileMode(s.Mode)
+ }
+ if req.NLink {
+ attr.NLink = s.Nlink
+ }
+ if req.UID {
+ attr.UID = UID(s.Uid)
+ }
+ if req.GID {
+ attr.GID = GID(s.Gid)
+ }
+ if req.RDev {
+ attr.RDev = s.Dev
+ }
+ if req.ATime {
+ attr.ATimeSeconds = uint64(s.Atim.Sec)
+ attr.ATimeNanoSeconds = uint64(s.Atim.Nsec)
+ }
+ if req.MTime {
+ attr.MTimeSeconds = uint64(s.Mtim.Sec)
+ attr.MTimeNanoSeconds = uint64(s.Mtim.Nsec)
+ }
+ if req.CTime {
+ attr.CTimeSeconds = uint64(s.Ctim.Sec)
+ attr.CTimeNanoSeconds = uint64(s.Ctim.Nsec)
+ }
+ if req.Size {
+ attr.Size = uint64(s.Size)
+ }
+ if req.Blocks {
+ attr.BlockSize = uint64(s.Blksize)
+ attr.Blocks = uint64(s.Blocks)
+ }
+
+ // Use the req field because we already have it.
+ req.BTime = false
+ req.Gen = false
+ req.DataVersion = false
+
+ return attr, req
+}
+
+// SetAttrMask specifies a valid mask for setattr.
+type SetAttrMask struct {
+ Permissions bool
+ UID bool
+ GID bool
+ Size bool
+ ATime bool
+ MTime bool
+ CTime bool
+ ATimeNotSystemTime bool
+ MTimeNotSystemTime bool
+}
+
+// IsSubsetOf returns whether s is a subset of m.
+func (s SetAttrMask) IsSubsetOf(m SetAttrMask) bool {
+ sb := s.bitmask()
+ sm := m.bitmask()
+ return sm|sb == sm
+}
+
+// String implements fmt.Stringer.
+func (s SetAttrMask) String() string {
+ var masks []string
+ if s.Permissions {
+ masks = append(masks, "Permissions")
+ }
+ if s.UID {
+ masks = append(masks, "UID")
+ }
+ if s.GID {
+ masks = append(masks, "GID")
+ }
+ if s.Size {
+ masks = append(masks, "Size")
+ }
+ if s.ATime {
+ masks = append(masks, "ATime")
+ }
+ if s.MTime {
+ masks = append(masks, "MTime")
+ }
+ if s.CTime {
+ masks = append(masks, "CTime")
+ }
+ if s.ATimeNotSystemTime {
+ masks = append(masks, "ATimeNotSystemTime")
+ }
+ if s.MTimeNotSystemTime {
+ masks = append(masks, "MTimeNotSystemTime")
+ }
+ return fmt.Sprintf("SetAttrMask{with: %s}", strings.Join(masks, " "))
+}
+
+// Empty returns true if no fields are masked.
+func (s SetAttrMask) Empty() bool {
+ return !s.Permissions && !s.UID && !s.GID && !s.Size && !s.ATime && !s.MTime && !s.CTime && !s.ATimeNotSystemTime && !s.MTimeNotSystemTime
+}
+
+// Decode implements encoder.Decode.
+func (s *SetAttrMask) Decode(b *buffer) {
+ mask := b.Read32()
+ s.Permissions = mask&0x00000001 != 0
+ s.UID = mask&0x00000002 != 0
+ s.GID = mask&0x00000004 != 0
+ s.Size = mask&0x00000008 != 0
+ s.ATime = mask&0x00000010 != 0
+ s.MTime = mask&0x00000020 != 0
+ s.CTime = mask&0x00000040 != 0
+ s.ATimeNotSystemTime = mask&0x00000080 != 0
+ s.MTimeNotSystemTime = mask&0x00000100 != 0
+}
+
+func (s SetAttrMask) bitmask() uint32 {
+ var mask uint32
+ if s.Permissions {
+ mask |= 0x00000001
+ }
+ if s.UID {
+ mask |= 0x00000002
+ }
+ if s.GID {
+ mask |= 0x00000004
+ }
+ if s.Size {
+ mask |= 0x00000008
+ }
+ if s.ATime {
+ mask |= 0x00000010
+ }
+ if s.MTime {
+ mask |= 0x00000020
+ }
+ if s.CTime {
+ mask |= 0x00000040
+ }
+ if s.ATimeNotSystemTime {
+ mask |= 0x00000080
+ }
+ if s.MTimeNotSystemTime {
+ mask |= 0x00000100
+ }
+ return mask
+}
+
+// Encode implements encoder.Encode.
+func (s *SetAttrMask) Encode(b *buffer) {
+ b.Write32(s.bitmask())
+}
+
+// SetAttr specifies a set of attributes for a setattr.
+type SetAttr struct {
+ Permissions FileMode
+ UID UID
+ GID GID
+ Size uint64
+ ATimeSeconds uint64
+ ATimeNanoSeconds uint64
+ MTimeSeconds uint64
+ MTimeNanoSeconds uint64
+}
+
+// String implements fmt.Stringer.
+func (s SetAttr) String() string {
+ return fmt.Sprintf("SetAttr{Permissions: 0o%o, UID: %d, GID: %d, Size: %d, ATime: {Sec: %d, NanoSec: %d}, MTime: {Sec: %d, NanoSec: %d}}", s.Permissions, s.UID, s.GID, s.Size, s.ATimeSeconds, s.ATimeNanoSeconds, s.MTimeSeconds, s.MTimeNanoSeconds)
+}
+
+// Decode implements encoder.Decode.
+func (s *SetAttr) Decode(b *buffer) {
+ s.Permissions = b.ReadPermissions()
+ s.UID = b.ReadUID()
+ s.GID = b.ReadGID()
+ s.Size = b.Read64()
+ s.ATimeSeconds = b.Read64()
+ s.ATimeNanoSeconds = b.Read64()
+ s.MTimeSeconds = b.Read64()
+ s.MTimeNanoSeconds = b.Read64()
+}
+
+// Encode implements encoder.Encode.
+func (s *SetAttr) Encode(b *buffer) {
+ b.WritePermissions(s.Permissions)
+ b.WriteUID(s.UID)
+ b.WriteGID(s.GID)
+ b.Write64(s.Size)
+ b.Write64(s.ATimeSeconds)
+ b.Write64(s.ATimeNanoSeconds)
+ b.Write64(s.MTimeSeconds)
+ b.Write64(s.MTimeNanoSeconds)
+}
+
+// Dirent is used for readdir.
+type Dirent struct {
+ // QID is the entry QID.
+ QID QID
+
+ // Offset is the offset in the directory.
+ //
+ // This will be communicated back the original caller.
+ Offset uint64
+
+ // Type is the 9P type.
+ Type QIDType
+
+ // Name is the name of the entry (i.e. basename).
+ Name string
+}
+
+// String implements fmt.Stringer.
+func (d Dirent) String() string {
+ return fmt.Sprintf("Dirent{QID: %d, Offset: %d, Type: 0x%X, Name: %s}", d.QID, d.Offset, d.Type, d.Name)
+}
+
+// Decode implements encoder.Decode.
+func (d *Dirent) Decode(b *buffer) {
+ d.QID.Decode(b)
+ d.Offset = b.Read64()
+ d.Type = b.ReadQIDType()
+ d.Name = b.ReadString()
+}
+
+// Encode implements encoder.Encode.
+func (d *Dirent) Encode(b *buffer) {
+ d.QID.Encode(b)
+ b.Write64(d.Offset)
+ b.WriteQIDType(d.Type)
+ b.WriteString(d.Name)
+}
diff --git a/pkg/p9/p9_test.go b/pkg/p9/p9_test.go
new file mode 100644
index 000000000..a50ac80a4
--- /dev/null
+++ b/pkg/p9/p9_test.go
@@ -0,0 +1,188 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package p9
+
+import (
+ "os"
+ "testing"
+)
+
+func TestFileModeHelpers(t *testing.T) {
+ fns := map[FileMode]struct {
+ // name identifies the file mode.
+ name string
+
+ // function is the function that should return true given the
+ // right FileMode.
+ function func(m FileMode) bool
+ }{
+ ModeRegular: {
+ name: "regular",
+ function: FileMode.IsRegular,
+ },
+ ModeDirectory: {
+ name: "directory",
+ function: FileMode.IsDir,
+ },
+ ModeNamedPipe: {
+ name: "named pipe",
+ function: FileMode.IsNamedPipe,
+ },
+ ModeCharacterDevice: {
+ name: "character device",
+ function: FileMode.IsCharacterDevice,
+ },
+ ModeBlockDevice: {
+ name: "block device",
+ function: FileMode.IsBlockDevice,
+ },
+ ModeSymlink: {
+ name: "symlink",
+ function: FileMode.IsSymlink,
+ },
+ ModeSocket: {
+ name: "socket",
+ function: FileMode.IsSocket,
+ },
+ }
+ for mode, info := range fns {
+ // Make sure the mode doesn't identify as anything but itself.
+ for testMode, testfns := range fns {
+ if mode != testMode && testfns.function(mode) {
+ t.Errorf("Mode %s returned true when asked if it was mode %s", info.name, testfns.name)
+ }
+ }
+
+ // Make sure mode identifies as itself.
+ if !info.function(mode) {
+ t.Errorf("Mode %s returned false when asked if it was itself", info.name)
+ }
+ }
+}
+
+func TestFileModeToQID(t *testing.T) {
+ for _, test := range []struct {
+ // name identifies the test.
+ name string
+
+ // mode is the FileMode we start out with.
+ mode FileMode
+
+ // want is the corresponding QIDType we expect.
+ want QIDType
+ }{
+ {
+ name: "Directories are of type directory",
+ mode: ModeDirectory,
+ want: TypeDir,
+ },
+ {
+ name: "Sockets are append-only files",
+ mode: ModeSocket,
+ want: TypeAppendOnly,
+ },
+ {
+ name: "Named pipes are append-only files",
+ mode: ModeNamedPipe,
+ want: TypeAppendOnly,
+ },
+ {
+ name: "Character devices are append-only files",
+ mode: ModeCharacterDevice,
+ want: TypeAppendOnly,
+ },
+ {
+ name: "Symlinks are of type symlink",
+ mode: ModeSymlink,
+ want: TypeSymlink,
+ },
+ {
+ name: "Regular files are of type regular",
+ mode: ModeRegular,
+ want: TypeRegular,
+ },
+ {
+ name: "Block devices are regular files",
+ mode: ModeBlockDevice,
+ want: TypeRegular,
+ },
+ } {
+ if qidType := test.mode.QIDType(); qidType != test.want {
+ t.Errorf("ModeToQID test %s failed: got %o, wanted %o", test.name, qidType, test.want)
+ }
+ }
+}
+
+func TestP9ModeConverters(t *testing.T) {
+ for _, m := range []FileMode{
+ ModeRegular,
+ ModeDirectory,
+ ModeCharacterDevice,
+ ModeBlockDevice,
+ ModeSocket,
+ ModeSymlink,
+ ModeNamedPipe,
+ } {
+ if mb := ModeFromOS(m.OSMode()); mb != m {
+ t.Errorf("Converting %o to OS.FileMode gives %o and is converted back as %o", m, m.OSMode(), mb)
+ }
+ }
+}
+
+func TestOSModeConverters(t *testing.T) {
+ // Modes that can be converted back and forth.
+ for _, m := range []os.FileMode{
+ 0, // Regular file.
+ os.ModeDir,
+ os.ModeCharDevice | os.ModeDevice,
+ os.ModeDevice,
+ os.ModeSocket,
+ os.ModeSymlink,
+ os.ModeNamedPipe,
+ } {
+ if mb := ModeFromOS(m).OSMode(); mb != m {
+ t.Errorf("Converting %o to p9.FileMode gives %o and is converted back as %o", m, ModeFromOS(m), mb)
+ }
+ }
+
+ // Modes that will be converted to a regular file since p9 cannot
+ // express these.
+ for _, m := range []os.FileMode{
+ os.ModeAppend,
+ os.ModeExclusive,
+ os.ModeTemporary,
+ } {
+ if p9Mode := ModeFromOS(m); p9Mode != ModeRegular {
+ t.Errorf("Converting %o to p9.FileMode should have given ModeRegular, but yielded %o", m, p9Mode)
+ }
+ }
+}
+
+func TestAttrMaskContains(t *testing.T) {
+ req := AttrMask{Mode: true, Size: true}
+ have := AttrMask{}
+ if have.Contains(req) {
+ t.Fatalf("AttrMask %v should not be a superset of %v", have, req)
+ }
+ have.Mode = true
+ if have.Contains(req) {
+ t.Fatalf("AttrMask %v should not be a superset of %v", have, req)
+ }
+ have.Size = true
+ have.MTime = true
+ if !have.Contains(req) {
+ t.Fatalf("AttrMask %v should be a superset of %v", have, req)
+ }
+}
diff --git a/pkg/p9/p9test/BUILD b/pkg/p9/p9test/BUILD
new file mode 100644
index 000000000..339c86089
--- /dev/null
+++ b/pkg/p9/p9test/BUILD
@@ -0,0 +1,28 @@
+package(licenses = ["notice"]) # Apache 2.0
+
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_test(
+ name = "p9test_test",
+ size = "small",
+ srcs = ["client_test.go"],
+ embed = [":p9test"],
+ deps = [
+ "//pkg/fd",
+ "//pkg/p9",
+ "//pkg/unet",
+ ],
+)
+
+go_library(
+ name = "p9test",
+ srcs = [
+ "mocks.go",
+ ],
+ importpath = "gvisor.googlesource.com/gvisor/pkg/p9/p9test",
+ visibility = ["//:sandbox"],
+ deps = [
+ "//pkg/fd",
+ "//pkg/p9",
+ ],
+)
diff --git a/pkg/p9/p9test/client_test.go b/pkg/p9/p9test/client_test.go
new file mode 100644
index 000000000..8e35d6017
--- /dev/null
+++ b/pkg/p9/p9test/client_test.go
@@ -0,0 +1,335 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package p9test
+
+import (
+ "io/ioutil"
+ "os"
+ "reflect"
+ "syscall"
+ "testing"
+
+ "gvisor.googlesource.com/gvisor/pkg/fd"
+ "gvisor.googlesource.com/gvisor/pkg/p9"
+ "gvisor.googlesource.com/gvisor/pkg/unet"
+)
+
+func TestDonateFD(t *testing.T) {
+ // Temporary file.
+ osFile, err := ioutil.TempFile("", "p9")
+ if err != nil {
+ t.Fatalf("could not create temporary file: %v", err)
+ }
+ os.Remove(osFile.Name())
+
+ hfi, err := osFile.Stat()
+ if err != nil {
+ osFile.Close()
+ t.Fatalf("stat failed: %v", err)
+ }
+ osFileStat := hfi.Sys().(*syscall.Stat_t)
+
+ f, err := fd.NewFromFile(osFile)
+ // osFile should always be closed.
+ osFile.Close()
+ if err != nil {
+ t.Fatalf("unable to create file: %v", err)
+ }
+
+ // Craft attacher to attach to the mocked file which will return our
+ // temporary file.
+ fileMock := &FileMock{OpenMock: OpenMock{File: f}}
+ attacher := &AttachMock{File: fileMock}
+
+ // Make socket pair.
+ serverSocket, clientSocket, err := unet.SocketPair(false)
+ if err != nil {
+ t.Fatalf("socketpair got err %v wanted nil", err)
+ }
+ defer clientSocket.Close()
+ server := p9.NewServer(attacher)
+ go server.Handle(serverSocket)
+ client, err := p9.NewClient(clientSocket, 1024*1024 /* 1M message size */, p9.HighestVersionString())
+ if err != nil {
+ t.Fatalf("new client got %v, expected nil", err)
+ }
+
+ // Attach to the mocked file.
+ cFile, err := client.Attach("")
+ if err != nil {
+ t.Fatalf("attach failed: %v", err)
+ }
+
+ // Try to open the mocked file.
+ clientHostFile, _, _, err := cFile.Open(0)
+ if err != nil {
+ t.Fatalf("open failed: %v", err)
+ }
+ var clientStat syscall.Stat_t
+ if err := syscall.Fstat(clientHostFile.FD(), &clientStat); err != nil {
+ t.Fatalf("stat failed: %v", err)
+ }
+
+ // Compare inode nums to make sure it's the same file.
+ if clientStat.Ino != osFileStat.Ino {
+ t.Errorf("fd donation failed")
+ }
+}
+
+// TestClient is a megatest.
+//
+// This allows us to probe various edge cases, while changing the state of the
+// underlying server in expected ways. The test slowly builds server state and
+// is documented inline.
+//
+// We wind up with the following, after probing edge cases:
+//
+// FID 1: ServerFile (sf).
+// FID 2: Directory (d).
+// FID 3: File (f).
+// FID 4: Symlink (s).
+//
+// Although you should use the FID method on the individual files.
+func TestClient(t *testing.T) {
+ var (
+ // Sentinel error.
+ sentinelErr = syscall.Errno(4383)
+
+ // Backend mocks.
+ a = &AttachMock{}
+ sf = &FileMock{}
+ d = &FileMock{}
+ f = &FileMock{}
+ s = &FileMock{}
+
+ // Client Files for the above.
+ sfFile p9.File
+ )
+
+ testSteps := []struct {
+ name string
+ fn func(*p9.Client) error
+ want error
+ }{
+ {
+ name: "bad-attach",
+ want: sentinelErr,
+ fn: func(c *p9.Client) error {
+ a.File = nil
+ a.Err = sentinelErr
+ _, err := c.Attach("")
+ return err
+ },
+ },
+ {
+ name: "attach",
+ fn: func(c *p9.Client) error {
+ a.Called = false
+ a.File = sf
+ a.Err = nil
+ var err error
+ sfFile, err = c.Attach("foo")
+ if !a.Called {
+ t.Errorf("Attach never Called?")
+ }
+ if a.AttachName != "foo" {
+ // This wasn't carried through?
+ t.Errorf("attachName got %v wanted foo", a.AttachName)
+ }
+ return err
+ },
+ },
+ {
+ name: "bad-walk",
+ want: sentinelErr,
+ fn: func(c *p9.Client) error {
+ sf.WalkMock.File = d
+ sf.WalkMock.Err = sentinelErr
+ _, _, err := sfFile.Walk([]string{"foo", "bar"})
+ return err
+ },
+ },
+ {
+ name: "walk-to-dir",
+ fn: func(c *p9.Client) error {
+ sf.WalkMock.Called = false
+ sf.WalkMock.File = d
+ sf.WalkMock.Err = nil
+ sf.WalkMock.QIDs = []p9.QID{{Type: 1}}
+ var qids []p9.QID
+ var err error
+ qids, _, err = sfFile.Walk([]string{"foo", "bar"})
+ if !sf.WalkMock.Called {
+ t.Errorf("Walk never Called?")
+ }
+ if !reflect.DeepEqual(sf.WalkMock.Names, []string{"foo", "bar"}) {
+ t.Errorf("got names %v wanted []{foo, bar}", sf.WalkMock.Names)
+ }
+ if len(qids) != 1 || qids[0].Type != 1 {
+ t.Errorf("got qids %v wanted []{{Type: 1}}", qids)
+ }
+ return err
+ },
+ },
+ {
+ name: "walkgetattr-to-dir",
+ fn: func(c *p9.Client) error {
+ sf.WalkGetAttrMock.Called = false
+ sf.WalkGetAttrMock.File = d
+ sf.WalkGetAttrMock.Err = nil
+ sf.WalkGetAttrMock.QIDs = []p9.QID{{Type: 1}}
+ sf.WalkGetAttrMock.Attr = p9.Attr{UID: 1}
+ sf.WalkGetAttrMock.Valid = p9.AttrMask{Mode: true}
+ var qids []p9.QID
+ var err error
+ var mask p9.AttrMask
+ var attr p9.Attr
+ qids, _, mask, attr, err = sfFile.WalkGetAttr([]string{"foo", "bar"})
+ if !sf.WalkGetAttrMock.Called {
+ t.Errorf("Walk never Called?")
+ }
+ if !reflect.DeepEqual(sf.WalkGetAttrMock.Names, []string{"foo", "bar"}) {
+ t.Errorf("got names %v wanted []{foo, bar}", sf.WalkGetAttrMock.Names)
+ }
+ if len(qids) != 1 || qids[0].Type != 1 {
+ t.Errorf("got qids %v wanted []{{Type: 1}}", qids)
+ }
+ if !reflect.DeepEqual(attr, sf.WalkGetAttrMock.Attr) {
+ t.Errorf("got attrs %s wanted %s", attr, sf.WalkGetAttrMock.Attr)
+ }
+ if !reflect.DeepEqual(mask, sf.WalkGetAttrMock.Valid) {
+ t.Errorf("got mask %s wanted %s", mask, sf.WalkGetAttrMock.Valid)
+ }
+ return err
+ },
+ },
+ {
+ name: "walk-to-file",
+ fn: func(c *p9.Client) error {
+ // Basic sanity check is done in walk-to-dir.
+ //
+ // Here we just create basic file FIDs to use.
+ sf.WalkMock.File = f
+ sf.WalkMock.Err = nil
+ var err error
+ _, _, err = sfFile.Walk(nil)
+ return err
+ },
+ },
+ {
+ name: "walk-to-symlink",
+ fn: func(c *p9.Client) error {
+ // See note in walk-to-file.
+ sf.WalkMock.File = s
+ sf.WalkMock.Err = nil
+ var err error
+ _, _, err = sfFile.Walk(nil)
+ return err
+ },
+ },
+ {
+ name: "bad-statfs",
+ want: sentinelErr,
+ fn: func(c *p9.Client) error {
+ sf.StatFSMock.Err = sentinelErr
+ _, err := sfFile.StatFS()
+ return err
+ },
+ },
+ {
+ name: "statfs",
+ fn: func(c *p9.Client) error {
+ sf.StatFSMock.Called = false
+ sf.StatFSMock.Stat = p9.FSStat{Type: 1}
+ sf.StatFSMock.Err = nil
+ stat, err := sfFile.StatFS()
+ if !sf.StatFSMock.Called {
+ t.Errorf("StatfS never Called?")
+ }
+ if stat.Type != 1 {
+ t.Errorf("got stat %v wanted {Type: 1}", stat)
+ }
+ return err
+ },
+ },
+ }
+
+ // First, create a new server and connection.
+ serverSocket, clientSocket, err := unet.SocketPair(false)
+ if err != nil {
+ t.Fatalf("socketpair got err %v wanted nil", err)
+ }
+ defer clientSocket.Close()
+ server := p9.NewServer(a)
+ go server.Handle(serverSocket)
+ client, err := p9.NewClient(clientSocket, 1024*1024 /* 1M message size */, p9.HighestVersionString())
+ if err != nil {
+ t.Fatalf("new client got err %v, wanted nil", err)
+ }
+
+ // Now, run through each of the test steps.
+ for _, step := range testSteps {
+ err := step.fn(client)
+ if err != step.want {
+ // Don't fail, just note this one step failed.
+ t.Errorf("step %q got %v wanted %v", step.name, err, step.want)
+ }
+ }
+}
+
+func BenchmarkClient(b *testing.B) {
+ // Backend mock.
+ a := &AttachMock{
+ File: &FileMock{
+ ReadAtMock: ReadAtMock{N: 1},
+ },
+ }
+
+ // First, create a new server and connection.
+ serverSocket, clientSocket, err := unet.SocketPair(false)
+ if err != nil {
+ b.Fatalf("socketpair got err %v wanted nil", err)
+ }
+ defer clientSocket.Close()
+ server := p9.NewServer(a)
+ go server.Handle(serverSocket)
+ client, err := p9.NewClient(clientSocket, 1024*1024 /* 1M message size */, p9.HighestVersionString())
+ if err != nil {
+ b.Fatalf("new client got %v, expected nil", err)
+ }
+
+ // Attach to the server.
+ f, err := client.Attach("")
+ if err != nil {
+ b.Fatalf("error during attach, got %v wanted nil", err)
+ }
+
+ // Open the file.
+ if _, _, _, err := f.Open(p9.ReadOnly); err != nil {
+ b.Fatalf("error during open, got %v wanted nil", err)
+ }
+
+ // Reset the clock.
+ b.ResetTimer()
+
+ // Do N reads.
+ var buf [1]byte
+ for i := 0; i < b.N; i++ {
+ _, err := f.ReadAt(buf[:], 0)
+ if err != nil {
+ b.Fatalf("error during read %d, got %v wanted nil", i, err)
+ }
+ }
+}
diff --git a/pkg/p9/p9test/mocks.go b/pkg/p9/p9test/mocks.go
new file mode 100644
index 000000000..e10f206dd
--- /dev/null
+++ b/pkg/p9/p9test/mocks.go
@@ -0,0 +1,490 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package p9test
+
+import (
+ "gvisor.googlesource.com/gvisor/pkg/fd"
+ "gvisor.googlesource.com/gvisor/pkg/p9"
+)
+
+// StatFSMock mocks p9.File.StatFS.
+type StatFSMock struct {
+ Called bool
+
+ // Return.
+ Stat p9.FSStat
+ Err error
+}
+
+// StatFS implements p9.File.StatFS.
+func (f *StatFSMock) StatFS() (p9.FSStat, error) {
+ f.Called = true
+ return f.Stat, f.Err
+}
+
+// GetAttrMock mocks p9.File.GetAttr.
+type GetAttrMock struct {
+ Called bool
+
+ // Args.
+ Req p9.AttrMask
+
+ // Return.
+ QID p9.QID
+ Valid p9.AttrMask
+ Attr p9.Attr
+ Err error
+}
+
+// GetAttr implements p9.File.GetAttr.
+func (g *GetAttrMock) GetAttr(req p9.AttrMask) (p9.QID, p9.AttrMask, p9.Attr, error) {
+ g.Called, g.Req = true, req
+ return g.QID, g.Valid, g.Attr, g.Err
+}
+
+// WalkGetAttrMock mocks p9.File.WalkGetAttr.
+type WalkGetAttrMock struct {
+ Called bool
+
+ // Args.
+ Names []string
+
+ // Return.
+ QIDs []p9.QID
+ File p9.File
+ Valid p9.AttrMask
+ Attr p9.Attr
+ Err error
+}
+
+// WalkGetAttr implements p9.File.WalkGetAttr.
+func (w *WalkGetAttrMock) WalkGetAttr(names []string) ([]p9.QID, p9.File, p9.AttrMask, p9.Attr, error) {
+ w.Called, w.Names = true, names
+ return w.QIDs, w.File, w.Valid, w.Attr, w.Err
+}
+
+// SetAttrMock mocks p9.File.SetAttr.
+type SetAttrMock struct {
+ Called bool
+
+ // Args.
+ Valid p9.SetAttrMask
+ Attr p9.SetAttr
+
+ // Return.
+ Err error
+}
+
+// SetAttr implements p9.File.SetAttr.
+func (s *SetAttrMock) SetAttr(valid p9.SetAttrMask, attr p9.SetAttr) error {
+ s.Called, s.Valid, s.Attr = true, valid, attr
+ return s.Err
+}
+
+// RemoveMock mocks p9.File.Remove.
+type RemoveMock struct {
+ Called bool
+
+ // Return.
+ Err error
+}
+
+// Remove implements p9.File.Remove.
+func (r *RemoveMock) Remove() error {
+ r.Called = true
+ return r.Err
+}
+
+// OpenMock mocks p9.File.Open.
+type OpenMock struct {
+ Called bool
+
+ // Args.
+ Flags p9.OpenFlags
+
+ // Return.
+ File *fd.FD
+ QID p9.QID
+ IOUnit uint32
+ Err error
+}
+
+// Open implements p9.File.Open.
+func (o *OpenMock) Open(flags p9.OpenFlags) (*fd.FD, p9.QID, uint32, error) {
+ o.Called, o.Flags = true, flags
+ return o.File, o.QID, o.IOUnit, o.Err
+}
+
+// ReadAtMock mocks p9.File.ReadAt.
+type ReadAtMock struct {
+ Called bool
+
+ // Args.
+ P []byte
+ Offset uint64
+
+ // Return.
+ N int
+ Err error
+}
+
+// ReadAt implements p9.File.ReadAt.
+func (r *ReadAtMock) ReadAt(p []byte, offset uint64) (int, error) {
+ r.Called, r.P, r.Offset = true, p, offset
+ return r.N, r.Err
+}
+
+// WriteAtMock mocks p9.File.WriteAt.
+type WriteAtMock struct {
+ Called bool
+
+ // Args.
+ P []byte
+ Offset uint64
+
+ // Return.
+ N int
+ Err error
+}
+
+// WriteAt implements p9.File.WriteAt.
+func (w *WriteAtMock) WriteAt(p []byte, offset uint64) (int, error) {
+ w.Called, w.P, w.Offset = true, p, offset
+ return w.N, w.Err
+}
+
+// FSyncMock mocks p9.File.FSync.
+type FSyncMock struct {
+ Called bool
+
+ // Return.
+ Err error
+}
+
+// FSync implements p9.File.FSync.
+func (f *FSyncMock) FSync() error {
+ f.Called = true
+ return f.Err
+}
+
+// MkdirMock mocks p9.File.Mkdir.
+type MkdirMock struct {
+ Called bool
+
+ // Args.
+ Name string
+ Permissions p9.FileMode
+ UID p9.UID
+ GID p9.GID
+
+ // Return.
+ QID p9.QID
+ Err error
+}
+
+// Mkdir implements p9.File.Mkdir.
+func (s *MkdirMock) Mkdir(name string, permissions p9.FileMode, uid p9.UID, gid p9.GID) (p9.QID, error) {
+ s.Called, s.Name, s.Permissions, s.UID, s.GID = true, name, permissions, uid, gid
+ return s.QID, s.Err
+}
+
+// SymlinkMock mocks p9.File.Symlink.
+type SymlinkMock struct {
+ Called bool
+
+ // Args.
+ Oldname string
+ Newname string
+ UID p9.UID
+ GID p9.GID
+
+ // Return.
+ QID p9.QID
+ Err error
+}
+
+// Symlink implements p9.File.Symlink.
+func (s *SymlinkMock) Symlink(oldname string, newname string, uid p9.UID, gid p9.GID) (p9.QID, error) {
+ s.Called, s.Oldname, s.Newname, s.UID, s.GID = true, oldname, newname, uid, gid
+ return s.QID, s.Err
+}
+
+// MknodMock mocks p9.File.Mknod.
+type MknodMock struct {
+ Called bool
+
+ // Args.
+ Name string
+ Permissions p9.FileMode
+ Major uint32
+ Minor uint32
+ UID p9.UID
+ GID p9.GID
+
+ // Return.
+ QID p9.QID
+ Err error
+}
+
+// Mknod implements p9.File.Mknod.
+func (m *MknodMock) Mknod(name string, permissions p9.FileMode, major uint32, minor uint32, uid p9.UID, gid p9.GID) (p9.QID, error) {
+ m.Called, m.Name, m.Permissions, m.Major, m.Minor, m.UID, m.GID = true, name, permissions, major, minor, uid, gid
+ return m.QID, m.Err
+}
+
+// UnlinkAtMock mocks p9.File.UnlinkAt.
+type UnlinkAtMock struct {
+ Called bool
+
+ // Args.
+ Name string
+ Flags uint32
+
+ // Return.
+ Err error
+}
+
+// UnlinkAt implements p9.File.UnlinkAt.
+func (u *UnlinkAtMock) UnlinkAt(name string, flags uint32) error {
+ u.Called, u.Name, u.Flags = true, name, flags
+ return u.Err
+}
+
+// ReaddirMock mocks p9.File.Readdir.
+type ReaddirMock struct {
+ Called bool
+
+ // Args.
+ Offset uint64
+ Count uint32
+
+ // Return.
+ Dirents []p9.Dirent
+ Err error
+}
+
+// Readdir implements p9.File.Readdir.
+func (r *ReaddirMock) Readdir(offset uint64, count uint32) ([]p9.Dirent, error) {
+ r.Called, r.Offset, r.Count = true, offset, count
+ return r.Dirents, r.Err
+}
+
+// ReadlinkMock mocks p9.File.Readlink.
+type ReadlinkMock struct {
+ Called bool
+
+ // Return.
+ Target string
+ Err error
+}
+
+// Readlink implements p9.File.Readlink.
+func (r *ReadlinkMock) Readlink() (string, error) {
+ r.Called = true
+ return r.Target, r.Err
+}
+
+// AttachMock mocks p9.Attacher.Attach.
+type AttachMock struct {
+ Called bool
+
+ // Args.
+ AttachName string
+
+ // Return.
+ File p9.File
+ Err error
+}
+
+// Attach implements p9.Attacher.Attach.
+func (a *AttachMock) Attach(attachName string) (p9.File, error) {
+ a.Called, a.AttachName = true, attachName
+ return a.File, a.Err
+}
+
+// WalkMock mocks p9.File.Walk.
+type WalkMock struct {
+ Called bool
+
+ // Args.
+ Names []string
+
+ // Return.
+ QIDs []p9.QID
+ File p9.File
+ Err error
+}
+
+// Walk implements p9.File.Walk.
+func (w *WalkMock) Walk(names []string) ([]p9.QID, p9.File, error) {
+ w.Called, w.Names = true, names
+ return w.QIDs, w.File, w.Err
+}
+
+// RenameMock mocks p9.File.Rename.
+type RenameMock struct {
+ Called bool
+
+ // Args.
+ Directory p9.File
+ Name string
+
+ // Return.
+ Err error
+}
+
+// Rename implements p9.File.Rename.
+func (r *RenameMock) Rename(directory p9.File, name string) error {
+ r.Called, r.Directory, r.Name = true, directory, name
+ return r.Err
+}
+
+// CloseMock mocks p9.File.Close.
+type CloseMock struct {
+ Called bool
+
+ // Return.
+ Err error
+}
+
+// Close implements p9.File.Close.
+func (d *CloseMock) Close() error {
+ d.Called = true
+ return d.Err
+}
+
+// CreateMock mocks p9.File.Create.
+type CreateMock struct {
+ Called bool
+
+ // Args.
+ Name string
+ Flags p9.OpenFlags
+ Permissions p9.FileMode
+ UID p9.UID
+ GID p9.GID
+
+ // Return.
+ HostFile *fd.FD
+ File p9.File
+ QID p9.QID
+ IOUnit uint32
+ Err error
+}
+
+// Create implements p9.File.Create.
+func (c *CreateMock) Create(name string, flags p9.OpenFlags, permissions p9.FileMode, uid p9.UID, gid p9.GID) (*fd.FD, p9.File, p9.QID, uint32, error) {
+ c.Called, c.Name, c.Flags, c.Permissions, c.UID, c.GID = true, name, flags, permissions, uid, gid
+ return c.HostFile, c.File, c.QID, c.IOUnit, c.Err
+}
+
+// LinkMock mocks p9.File.Link.
+type LinkMock struct {
+ Called bool
+
+ // Args.
+ Target p9.File
+ Newname string
+
+ // Return.
+ Err error
+}
+
+// Link implements p9.File.Link.
+func (l *LinkMock) Link(target p9.File, newname string) error {
+ l.Called, l.Target, l.Newname = true, target, newname
+ return l.Err
+}
+
+// RenameAtMock mocks p9.File.RenameAt.
+type RenameAtMock struct {
+ Called bool
+
+ // Args.
+ Oldname string
+ Newdir p9.File
+ Newname string
+
+ // Return.
+ Err error
+}
+
+// RenameAt implements p9.File.RenameAt.
+func (r *RenameAtMock) RenameAt(oldname string, newdir p9.File, newname string) error {
+ r.Called, r.Oldname, r.Newdir, r.Newname = true, oldname, newdir, newname
+ return r.Err
+}
+
+// FlushMock mocks p9.File.Flush.
+type FlushMock struct {
+ Called bool
+
+ // Return.
+ Err error
+}
+
+// Flush implements p9.File.Flush.
+func (f *FlushMock) Flush() error {
+ return f.Err
+}
+
+// ConnectMock mocks p9.File.Connect.
+type ConnectMock struct {
+ Called bool
+
+ // Args.
+ Flags p9.ConnectFlags
+
+ // Return.
+ File *fd.FD
+ Err error
+}
+
+// Connect implements p9.File.Connect.
+func (o *ConnectMock) Connect(flags p9.ConnectFlags) (*fd.FD, error) {
+ o.Called, o.Flags = true, flags
+ return o.File, o.Err
+}
+
+// FileMock mocks p9.File.
+type FileMock struct {
+ WalkMock
+ WalkGetAttrMock
+ StatFSMock
+ GetAttrMock
+ SetAttrMock
+ RemoveMock
+ RenameMock
+ CloseMock
+ OpenMock
+ ReadAtMock
+ WriteAtMock
+ FSyncMock
+ CreateMock
+ MkdirMock
+ SymlinkMock
+ LinkMock
+ MknodMock
+ RenameAtMock
+ UnlinkAtMock
+ ReaddirMock
+ ReadlinkMock
+ FlushMock
+ ConnectMock
+}
+
+var (
+ _ p9.File = &FileMock{}
+)
diff --git a/pkg/p9/pool.go b/pkg/p9/pool.go
new file mode 100644
index 000000000..9a508b898
--- /dev/null
+++ b/pkg/p9/pool.go
@@ -0,0 +1,68 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package p9
+
+import (
+ "sync"
+)
+
+// pool is a simple allocator.
+//
+// It is used for both tags and FIDs.
+type pool struct {
+ mu sync.Mutex
+
+ // cache is the set of returned values.
+ cache []uint64
+
+ // start is the starting value (if needed).
+ start uint64
+
+ // max is the current maximum issued.
+ max uint64
+
+ // limit is the upper limit.
+ limit uint64
+}
+
+// Get gets a value from the pool.
+func (p *pool) Get() (uint64, bool) {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+
+ // Anything cached?
+ if len(p.cache) > 0 {
+ v := p.cache[len(p.cache)-1]
+ p.cache = p.cache[:len(p.cache)-1]
+ return v, true
+ }
+
+ // Over the limit?
+ if p.start == p.limit {
+ return 0, false
+ }
+
+ // Generate a new value.
+ v := p.start
+ p.start++
+ return v, true
+}
+
+// Put returns a value to the pool.
+func (p *pool) Put(v uint64) {
+ p.mu.Lock()
+ p.cache = append(p.cache, v)
+ p.mu.Unlock()
+}
diff --git a/pkg/p9/pool_test.go b/pkg/p9/pool_test.go
new file mode 100644
index 000000000..96be2c8bd
--- /dev/null
+++ b/pkg/p9/pool_test.go
@@ -0,0 +1,64 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package p9
+
+import (
+ "testing"
+)
+
+func TestPoolUnique(t *testing.T) {
+ p := pool{start: 1, limit: 3}
+ got := make(map[uint64]bool)
+
+ for {
+ n, ok := p.Get()
+ if !ok {
+ break
+ }
+
+ // Check unique.
+ if _, ok := got[n]; ok {
+ t.Errorf("pool spit out %v multiple times", n)
+ }
+
+ // Record.
+ got[n] = true
+ }
+}
+
+func TestExausted(t *testing.T) {
+ p := pool{start: 1, limit: 500}
+ for i := 0; i < 499; i++ {
+ _, ok := p.Get()
+ if !ok {
+ t.Fatalf("pool exhausted before 499 items")
+ }
+ }
+
+ _, ok := p.Get()
+ if ok {
+ t.Errorf("pool not exhausted when it should be")
+ }
+}
+
+func TestPoolRecycle(t *testing.T) {
+ p := pool{start: 1, limit: 500}
+ n1, _ := p.Get()
+ p.Put(n1)
+ n2, _ := p.Get()
+ if n1 != n2 {
+ t.Errorf("pool not recycling items")
+ }
+}
diff --git a/pkg/p9/server.go b/pkg/p9/server.go
new file mode 100644
index 000000000..2965ae16e
--- /dev/null
+++ b/pkg/p9/server.go
@@ -0,0 +1,380 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package p9
+
+import (
+ "sync"
+ "sync/atomic"
+ "syscall"
+
+ "gvisor.googlesource.com/gvisor/pkg/log"
+ "gvisor.googlesource.com/gvisor/pkg/unet"
+)
+
+// Server is a 9p2000.L server.
+type Server struct {
+ // attacher provides the attach function.
+ attacher Attacher
+}
+
+// NewServer returns a new server.
+//
+func NewServer(attacher Attacher) *Server {
+ return &Server{
+ attacher: attacher,
+ }
+}
+
+// connState is the state for a single connection.
+type connState struct {
+ // server is the backing server.
+ server *Server
+
+ // sendMu is the send lock.
+ sendMu sync.Mutex
+
+ // conn is the connection.
+ conn *unet.Socket
+
+ // fids is the set of active FIDs.
+ //
+ // This is used to find FIDs for files.
+ fidMu sync.Mutex
+ fids map[FID]*fidRef
+
+ // tags is the set of active tags.
+ //
+ // The given channel is closed when the
+ // tag is finished with processing.
+ tagMu sync.Mutex
+ tags map[Tag]chan struct{}
+
+ // messageSize is the maximum message size. The server does not
+ // do automatic splitting of messages.
+ messageSize uint32
+
+ // version is the agreed upon version X of 9P2000.L.Google.X.
+ // version 0 implies 9P2000.L.
+ version uint32
+
+ // recvOkay indicates that a receive may start.
+ recvOkay chan bool
+
+ // recvDone is signalled when a message is received.
+ recvDone chan error
+
+ // sendDone is signalled when a send is finished.
+ sendDone chan error
+}
+
+// fidRef wraps a node and tracks references.
+type fidRef struct {
+ // file is the associated File.
+ file File
+
+ // refs is an active refence count.
+ //
+ // The node above will be closed only when refs reaches zero.
+ refs int64
+
+ // openedMu protects opened and openFlags.
+ openedMu sync.Mutex
+
+ // opened indicates whether this has been opened already.
+ //
+ // This is updated in handlers.go.
+ opened bool
+
+ // openFlags is the mode used in the open.
+ //
+ // This is updated in handlers.go.
+ openFlags OpenFlags
+}
+
+// OpenFlags returns the flags the file was opened with and true iff the fid was opened previously.
+func (f *fidRef) OpenFlags() (OpenFlags, bool) {
+ f.openedMu.Lock()
+ defer f.openedMu.Unlock()
+ return f.openFlags, f.opened
+}
+
+// DecRef should be called when you're finished with a fid.
+func (f *fidRef) DecRef() {
+ if atomic.AddInt64(&f.refs, -1) == 0 {
+ f.file.Close()
+ }
+}
+
+// LookupFID finds the given FID.
+//
+// You should call fid.DecRef when you are finished using the fid.
+func (cs *connState) LookupFID(fid FID) (*fidRef, bool) {
+ cs.fidMu.Lock()
+ defer cs.fidMu.Unlock()
+ fidRef, ok := cs.fids[fid]
+ if ok {
+ atomic.AddInt64(&fidRef.refs, 1)
+ return fidRef, true
+ }
+ return nil, false
+}
+
+// InsertFID installs the given FID.
+//
+// This fid starts with a reference count of one. If a FID exists in
+// the slot already it is closed, per the specification.
+func (cs *connState) InsertFID(fid FID, newRef *fidRef) {
+ cs.fidMu.Lock()
+ defer cs.fidMu.Unlock()
+ origRef, ok := cs.fids[fid]
+ if ok {
+ defer origRef.DecRef()
+ }
+ atomic.AddInt64(&newRef.refs, 1)
+ cs.fids[fid] = newRef
+}
+
+// DeleteFID removes the given FID.
+//
+// This simply removes it from the map and drops a reference.
+func (cs *connState) DeleteFID(fid FID) bool {
+ cs.fidMu.Lock()
+ defer cs.fidMu.Unlock()
+ fidRef, ok := cs.fids[fid]
+ if !ok {
+ return false
+ }
+ delete(cs.fids, fid)
+ fidRef.DecRef()
+ return true
+}
+
+// StartTag starts handling the tag.
+//
+// False is returned if this tag is already active.
+func (cs *connState) StartTag(t Tag) bool {
+ cs.tagMu.Lock()
+ defer cs.tagMu.Unlock()
+ _, ok := cs.tags[t]
+ if ok {
+ return false
+ }
+ cs.tags[t] = make(chan struct{})
+ return true
+}
+
+// ClearTag finishes handling a tag.
+func (cs *connState) ClearTag(t Tag) {
+ cs.tagMu.Lock()
+ defer cs.tagMu.Unlock()
+ ch, ok := cs.tags[t]
+ if !ok {
+ // Should never happen.
+ panic("unused tag cleared")
+ }
+ delete(cs.tags, t)
+
+ // Notify.
+ close(ch)
+}
+
+// WaitTag waits for a tag to finish.
+func (cs *connState) WaitTag(t Tag) {
+ cs.tagMu.Lock()
+ ch, ok := cs.tags[t]
+ cs.tagMu.Unlock()
+ if !ok {
+ return
+ }
+
+ // Wait for close.
+ <-ch
+}
+
+// handleRequest handles a single request.
+//
+// The recvDone channel is signaled when recv is done (with a error if
+// necessary). The sendDone channel is signaled with the result of the send.
+func (cs *connState) handleRequest() {
+ messageSize := atomic.LoadUint32(&cs.messageSize)
+ if messageSize == 0 {
+ // Default or not yet negotiated.
+ messageSize = maximumLength
+ }
+
+ // Receive a message.
+ tag, m, err := recv(cs.conn, messageSize, messageByType)
+ if errSocket, ok := err.(ErrSocket); ok {
+ // Connection problem; stop serving.
+ cs.recvDone <- errSocket.error
+ return
+ }
+
+ // Signal receive is done.
+ cs.recvDone <- nil
+
+ // Deal with other errors.
+ if err != nil {
+ // If it's not a connection error, but some other protocol error,
+ // we can send a response immediately.
+ log.Debugf("err [%05d] %v", tag, err)
+ cs.sendMu.Lock()
+ err := send(cs.conn, tag, newErr(err))
+ cs.sendMu.Unlock()
+ cs.sendDone <- err
+ return
+ }
+
+ // Try to start the tag.
+ if !cs.StartTag(tag) {
+ // Nothing we can do at this point; client is bogus.
+ cs.sendDone <- ErrNoValidMessage
+ return
+ }
+
+ // Handle the message.
+ var r message
+ if handler, ok := m.(handler); ok {
+ // Call the message handler.
+ r = handler.handle(cs)
+ } else {
+ // Produce an ENOSYS error.
+ r = newErr(syscall.ENOSYS)
+ }
+
+ // Clear the tag before sending. That's because as soon
+ // as this hits the wire, the client can legally send
+ // another message with the same tag.
+ cs.ClearTag(tag)
+
+ // Send back the result.
+ cs.sendMu.Lock()
+ err = send(cs.conn, tag, r)
+ cs.sendMu.Unlock()
+ cs.sendDone <- err
+ return
+}
+
+func (cs *connState) handleRequests() {
+ for range cs.recvOkay {
+ cs.handleRequest()
+ }
+}
+
+func (cs *connState) stop() {
+ // Close all channels.
+ close(cs.recvOkay)
+ close(cs.recvDone)
+ close(cs.sendDone)
+
+ for _, fidRef := range cs.fids {
+ // Drop final reference in the FID table. Note this should
+ // always close the file, since we've ensured that there are no
+ // handlers running via the wait for Pending => 0 below.
+ fidRef.DecRef()
+ }
+
+ // Ensure the connection is closed.
+ cs.conn.Close()
+}
+
+// service services requests concurrently.
+func (cs *connState) service() error {
+ // Pending is the number of handlers that have finished receiving but
+ // not finished processing requests. These must be waiting on properly
+ // below. See the next comment for an explanation of the loop.
+ pending := 0
+
+ // Start the first request handler.
+ go cs.handleRequests() // S/R-SAFE: Irrelevant.
+ cs.recvOkay <- true
+
+ // We loop and make sure there's always one goroutine waiting for a new
+ // request. We process all the data for a single request in one
+ // goroutine however, to ensure the best turnaround time possible.
+ for {
+ select {
+ case err := <-cs.recvDone:
+ if err != nil {
+ // Wait for pending handlers.
+ for i := 0; i < pending; i++ {
+ <-cs.sendDone
+ }
+ return err
+ }
+
+ // This handler is now pending.
+ pending++
+
+ // Kick the next receiver, or start a new handler
+ // if no receiver is currently waiting.
+ select {
+ case cs.recvOkay <- true:
+ default:
+ go cs.handleRequests() // S/R-SAFE: Irrelevant.
+ cs.recvOkay <- true
+ }
+
+ case <-cs.sendDone:
+ // This handler is finished.
+ pending--
+
+ // Error sending a response? Nothing can be done.
+ //
+ // We don't terminate on a send error though, since
+ // we still have a pending receive. The error would
+ // have been logged above, we just ignore it here.
+ }
+ }
+}
+
+// Handle handles a single connection.
+func (s *Server) Handle(conn *unet.Socket) error {
+ cs := &connState{
+ server: s,
+ conn: conn,
+ fids: make(map[FID]*fidRef),
+ tags: make(map[Tag]chan struct{}),
+ recvOkay: make(chan bool),
+ recvDone: make(chan error, 10),
+ sendDone: make(chan error, 10),
+ }
+ defer cs.stop()
+ return cs.service()
+}
+
+// Serve handles requests from the bound socket.
+//
+// The passed serverSocket _must_ be created in packet mode.
+func (s *Server) Serve(serverSocket *unet.ServerSocket) error {
+ var wg sync.WaitGroup
+ defer wg.Wait()
+
+ for {
+ conn, err := serverSocket.Accept()
+ if err != nil {
+ // Something went wrong.
+ //
+ // Socket closed?
+ return err
+ }
+
+ wg.Add(1)
+ go func(conn *unet.Socket) { // S/R-SAFE: Irrelevant.
+ s.Handle(conn)
+ wg.Done()
+ }(conn)
+ }
+}
diff --git a/pkg/p9/transport.go b/pkg/p9/transport.go
new file mode 100644
index 000000000..c42b41fcf
--- /dev/null
+++ b/pkg/p9/transport.go
@@ -0,0 +1,346 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package p9
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "sync"
+ "syscall"
+
+ "gvisor.googlesource.com/gvisor/pkg/fd"
+ "gvisor.googlesource.com/gvisor/pkg/log"
+ "gvisor.googlesource.com/gvisor/pkg/unet"
+)
+
+// ErrSocket is returned in cases of a socket issue.
+//
+// This may be treated differently than other errors.
+type ErrSocket struct {
+ // error is the socket error.
+ error
+}
+
+// ErrMessageTooLarge indicates the size was larger than reasonable.
+type ErrMessageTooLarge struct {
+ size uint32
+ msize uint32
+}
+
+// Error returns a sensible error.
+func (e *ErrMessageTooLarge) Error() string {
+ return fmt.Sprintf("message too large for fixed buffer: size is %d, limit is %d", e.size, e.msize)
+}
+
+// ErrNoValidMessage indicates no valid message could be decoded.
+var ErrNoValidMessage = errors.New("buffer contained no valid message")
+
+const (
+ // headerLength is the number of bytes required for a header.
+ headerLength uint32 = 7
+
+ // maximumLength is the largest possible message.
+ maximumLength uint32 = 4 * 1024 * 1024
+
+ // initialBufferLength is the initial data buffer we allocate.
+ initialBufferLength uint32 = 64
+)
+
+var dataPool = sync.Pool{
+ New: func() interface{} {
+ // These buffers are used for decoding without a payload.
+ return make([]byte, initialBufferLength)
+ },
+}
+
+// send sends the given message over the socket.
+func send(s *unet.Socket, tag Tag, m message) error {
+ data := dataPool.Get().([]byte)
+ dataBuf := buffer{data: data[:0]}
+
+ if log.IsLogging(log.Debug) {
+ log.Debugf("send [FD %d] [Tag %06d] %s", s.FD(), tag, m.String())
+ }
+
+ // Encode the message. The buffer will grow automatically.
+ m.Encode(&dataBuf)
+
+ // Get our vectors to send.
+ var hdr [headerLength]byte
+ vecs := make([][]byte, 0, 3)
+ vecs = append(vecs, hdr[:])
+ if len(dataBuf.data) > 0 {
+ vecs = append(vecs, dataBuf.data)
+ }
+ totalLength := headerLength + uint32(len(dataBuf.data))
+
+ // Is there a payload?
+ if payloader, ok := m.(payloader); ok {
+ p := payloader.Payload()
+ if len(p) > 0 {
+ vecs = append(vecs, p)
+ totalLength += uint32(len(p))
+ }
+ }
+
+ // Construct the header.
+ headerBuf := buffer{data: hdr[:0]}
+ headerBuf.Write32(totalLength)
+ headerBuf.WriteMsgType(m.Type())
+ headerBuf.WriteTag(tag)
+
+ // Pack any files if necessary.
+ w := s.Writer(true)
+ if filer, ok := m.(filer); ok {
+ if f := filer.FilePayload(); f != nil {
+ defer f.Close()
+ // Pack the file into the message.
+ w.PackFDs(f.FD())
+ }
+ }
+
+ for n := 0; n < int(totalLength); {
+ cur, err := w.WriteVec(vecs)
+ if err != nil {
+ return ErrSocket{err}
+ }
+ n += cur
+
+ // Consume iovecs.
+ for consumed := 0; consumed < cur; {
+ if len(vecs[0]) <= cur-consumed {
+ consumed += len(vecs[0])
+ vecs = vecs[1:]
+ } else {
+ vecs[0] = vecs[0][cur-consumed:]
+ break
+ }
+ }
+
+ if n > 0 && n < int(totalLength) {
+ // Don't resend any control message.
+ w.UnpackFDs()
+ }
+ }
+
+ // All set.
+ dataPool.Put(dataBuf.data)
+ return nil
+}
+
+// lookupTagAndType looks up an existing message or creates a new one.
+//
+// This is called by recv after decoding the header. Any error returned will be
+// propagating back to the caller. You may use messageByType directly as a
+// lookupTagAndType function (by design).
+type lookupTagAndType func(tag Tag, t MsgType) (message, error)
+
+// recv decodes a message from the socket.
+//
+// This is done in two parts, and is thus not safe for multiple callers.
+//
+// On a socket error, the special error type ErrSocket is returned.
+//
+// The tag value NoTag will always be returned if err is non-nil.
+func recv(s *unet.Socket, msize uint32, lookup lookupTagAndType) (Tag, message, error) {
+ // Read a header.
+ //
+ // Since the send above is atomic, we must always receive control
+ // messages along with the header. This means we need to be careful
+ // about closing FDs during errors to prevent leaks.
+ var hdr [headerLength]byte
+ r := s.Reader(true)
+ r.EnableFDs(1)
+
+ n, err := r.ReadVec([][]byte{hdr[:]})
+ if err != nil {
+ r.CloseFDs()
+ return NoTag, nil, ErrSocket{err}
+ }
+
+ fds, err := r.ExtractFDs()
+ if err != nil {
+ return NoTag, nil, ErrSocket{err}
+ }
+ defer func() {
+ // Close anything left open. The case where
+ // fds are caught and used is handled below,
+ // and the fds variable will be set to nil.
+ for _, fd := range fds {
+ syscall.Close(fd)
+ }
+ }()
+ r.EnableFDs(0)
+
+ // Continuing reading for a short header.
+ for n < int(headerLength) {
+ cur, err := r.ReadVec([][]byte{hdr[n:]})
+ if err != nil {
+ return NoTag, nil, ErrSocket{err}
+ } else if cur == 0 {
+ return NoTag, nil, ErrSocket{io.EOF}
+ }
+ n += cur
+ }
+
+ // Decode the header.
+ headerBuf := buffer{data: hdr[:]}
+ size := headerBuf.Read32()
+ t := headerBuf.ReadMsgType()
+ tag := headerBuf.ReadTag()
+ if size < headerLength {
+ // The message is too small.
+ //
+ // See above: it's probably screwed.
+ return NoTag, nil, ErrNoValidMessage
+ }
+ if size > maximumLength || size > msize {
+ // The message is too big.
+ return NoTag, nil, &ErrMessageTooLarge{size, msize}
+ }
+ remaining := size - headerLength
+
+ // Find our message to decode.
+ m, err := lookup(tag, t)
+ if err != nil {
+ // Throw away the contents of this message.
+ if remaining > 0 {
+ io.Copy(ioutil.Discard, &io.LimitedReader{R: s, N: int64(remaining)})
+ }
+ return tag, nil, err
+ }
+
+ // Not yet initialized.
+ var dataBuf buffer
+
+ // Read the rest of the payload.
+ //
+ // This requires some special care to ensure that the vectors all line
+ // up the way they should. We do this to minimize copying data around.
+ var vecs [][]byte
+ if payloader, ok := m.(payloader); ok {
+ fixedSize := payloader.FixedSize()
+
+ // Do we need more than there is?
+ if fixedSize > remaining {
+ // This is not a valid message.
+ if remaining > 0 {
+ io.Copy(ioutil.Discard, &io.LimitedReader{R: s, N: int64(remaining)})
+ }
+ return NoTag, nil, ErrNoValidMessage
+ }
+
+ if fixedSize != 0 {
+ // Pull a data buffer from the pool.
+ data := dataPool.Get().([]byte)
+ if int(fixedSize) > len(data) {
+ // Create a larger data buffer, ensuring
+ // sufficient capicity for the message.
+ data = make([]byte, fixedSize)
+ defer dataPool.Put(data)
+ dataBuf = buffer{data: data}
+ vecs = append(vecs, data)
+ } else {
+ // Limit the data buffer, and make sure it
+ // gets filled before the payload buffer.
+ defer dataPool.Put(data)
+ dataBuf = buffer{data: data[:fixedSize]}
+ vecs = append(vecs, data[:fixedSize])
+ }
+ }
+
+ // Include the payload.
+ p := payloader.Payload()
+ if p == nil || len(p) != int(remaining-fixedSize) {
+ p = make([]byte, remaining-fixedSize)
+ payloader.SetPayload(p)
+ }
+ if len(p) > 0 {
+ vecs = append(vecs, p)
+ }
+ } else if remaining != 0 {
+ // Pull a data buffer from the pool.
+ data := dataPool.Get().([]byte)
+ if int(remaining) > len(data) {
+ // Create a larger data buffer.
+ data = make([]byte, remaining)
+ defer dataPool.Put(data)
+ dataBuf = buffer{data: data}
+ vecs = append(vecs, data)
+ } else {
+ // Limit the data buffer.
+ defer dataPool.Put(data)
+ dataBuf = buffer{data: data[:remaining]}
+ vecs = append(vecs, data[:remaining])
+ }
+ }
+
+ if len(vecs) > 0 {
+ // Read the rest of the message.
+ //
+ // No need to handle a control message.
+ r := s.Reader(true)
+ for n := 0; n < int(remaining); {
+ cur, err := r.ReadVec(vecs)
+ if err != nil {
+ return NoTag, nil, ErrSocket{err}
+ } else if cur == 0 {
+ return NoTag, nil, ErrSocket{io.EOF}
+ }
+ n += cur
+
+ // Consume iovecs.
+ for consumed := 0; consumed < cur; {
+ if len(vecs[0]) <= cur-consumed {
+ consumed += len(vecs[0])
+ vecs = vecs[1:]
+ } else {
+ vecs[0] = vecs[0][cur-consumed:]
+ break
+ }
+ }
+ }
+ }
+
+ // Decode the message data.
+ m.Decode(&dataBuf)
+ if dataBuf.isOverrun() {
+ // No need to drain the socket.
+ return NoTag, nil, ErrNoValidMessage
+ }
+
+ // Save the file, if any came out.
+ if filer, ok := m.(filer); ok && len(fds) > 0 {
+ // Set the file object.
+ filer.SetFilePayload(fd.New(fds[0]))
+
+ // Close the rest. We support only one.
+ for i := 1; i < len(fds); i++ {
+ syscall.Close(fds[i])
+ }
+
+ // Don't close in the defer.
+ fds = nil
+ }
+
+ if log.IsLogging(log.Debug) {
+ log.Debugf("recv [FD %d] [Tag %06d] %s", s.FD(), tag, m.String())
+ }
+
+ // All set.
+ return tag, m, nil
+}
diff --git a/pkg/p9/transport_test.go b/pkg/p9/transport_test.go
new file mode 100644
index 000000000..e3ee3e9bd
--- /dev/null
+++ b/pkg/p9/transport_test.go
@@ -0,0 +1,184 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package p9
+
+import (
+ "io/ioutil"
+ "os"
+ "testing"
+
+ "gvisor.googlesource.com/gvisor/pkg/fd"
+ "gvisor.googlesource.com/gvisor/pkg/unet"
+)
+
+const (
+ MsgTypeBadEncode = iota + 252
+ MsgTypeBadDecode
+ MsgTypeUnregistered
+)
+
+func TestSendRecv(t *testing.T) {
+ server, client, err := unet.SocketPair(false)
+ if err != nil {
+ t.Fatalf("socketpair got err %v expected nil", err)
+ }
+ defer server.Close()
+ defer client.Close()
+
+ if err := send(client, Tag(1), &Tlopen{}); err != nil {
+ t.Fatalf("send got err %v expected nil", err)
+ }
+
+ tag, m, err := recv(server, maximumLength, messageByType)
+ if err != nil {
+ t.Fatalf("recv got err %v expected nil", err)
+ }
+ if tag != Tag(1) {
+ t.Fatalf("got tag %v expected 1", tag)
+ }
+ if _, ok := m.(*Tlopen); !ok {
+ t.Fatalf("got message %v expected *Tlopen", m)
+ }
+}
+
+// badDecode overruns on decode.
+type badDecode struct{}
+
+func (*badDecode) Decode(b *buffer) { b.markOverrun() }
+func (*badDecode) Encode(b *buffer) {}
+func (*badDecode) Type() MsgType { return MsgTypeBadDecode }
+func (*badDecode) String() string { return "badDecode{}" }
+
+func TestRecvOverrun(t *testing.T) {
+ server, client, err := unet.SocketPair(false)
+ if err != nil {
+ t.Fatalf("socketpair got err %v expected nil", err)
+ }
+ defer server.Close()
+ defer client.Close()
+
+ if err := send(client, Tag(1), &badDecode{}); err != nil {
+ t.Fatalf("send got err %v expected nil", err)
+ }
+
+ if _, _, err := recv(server, maximumLength, messageByType); err != ErrNoValidMessage {
+ t.Fatalf("recv got err %v expected ErrNoValidMessage", err)
+ }
+}
+
+// unregistered is not registered on decode.
+type unregistered struct{}
+
+func (*unregistered) Decode(b *buffer) {}
+func (*unregistered) Encode(b *buffer) {}
+func (*unregistered) Type() MsgType { return MsgTypeUnregistered }
+func (*unregistered) String() string { return "unregistered{}" }
+
+func TestRecvInvalidType(t *testing.T) {
+ server, client, err := unet.SocketPair(false)
+ if err != nil {
+ t.Fatalf("socketpair got err %v expected nil", err)
+ }
+ defer server.Close()
+ defer client.Close()
+
+ if err := send(client, Tag(1), &unregistered{}); err != nil {
+ t.Fatalf("send got err %v expected nil", err)
+ }
+
+ _, _, err = recv(server, maximumLength, messageByType)
+ if _, ok := err.(*ErrInvalidMsgType); !ok {
+ t.Fatalf("recv got err %v expected ErrInvalidMsgType", err)
+ }
+}
+
+func TestSendRecvWithFile(t *testing.T) {
+ server, client, err := unet.SocketPair(false)
+ if err != nil {
+ t.Fatalf("socketpair got err %v expected nil", err)
+ }
+ defer server.Close()
+ defer client.Close()
+
+ // Create a tempfile.
+ osf, err := ioutil.TempFile("", "p9")
+ if err != nil {
+ t.Fatalf("tempfile got err %v expected nil", err)
+ }
+ os.Remove(osf.Name())
+ f, err := fd.NewFromFile(osf)
+ osf.Close()
+ if err != nil {
+ t.Fatalf("unable to create file: %v", err)
+ }
+
+ if err := send(client, Tag(1), &Rlopen{File: f}); err != nil {
+ t.Fatalf("send got err %v expected nil", err)
+ }
+
+ // Enable withFile.
+ tag, m, err := recv(server, maximumLength, messageByType)
+ if err != nil {
+ t.Fatalf("recv got err %v expected nil", err)
+ }
+ if tag != Tag(1) {
+ t.Fatalf("got tag %v expected 1", tag)
+ }
+ rlopen, ok := m.(*Rlopen)
+ if !ok {
+ t.Fatalf("got m %v expected *Rlopen", m)
+ }
+ if rlopen.File == nil {
+ t.Fatalf("got nil file expected non-nil")
+ }
+}
+
+func TestRecvClosed(t *testing.T) {
+ server, client, err := unet.SocketPair(false)
+ if err != nil {
+ t.Fatalf("socketpair got err %v expected nil", err)
+ }
+ defer server.Close()
+ client.Close()
+
+ _, _, err = recv(server, maximumLength, messageByType)
+ if err == nil {
+ t.Fatalf("got err nil expected non-nil")
+ }
+ if _, ok := err.(ErrSocket); !ok {
+ t.Fatalf("got err %v expected ErrSocket", err)
+ }
+}
+
+func TestSendClosed(t *testing.T) {
+ server, client, err := unet.SocketPair(false)
+ if err != nil {
+ t.Fatalf("socketpair got err %v expected nil", err)
+ }
+ server.Close()
+ defer client.Close()
+
+ err = send(client, Tag(1), &Tlopen{})
+ if err == nil {
+ t.Fatalf("send got err nil expected non-nil")
+ }
+ if _, ok := err.(ErrSocket); !ok {
+ t.Fatalf("got err %v expected ErrSocket", err)
+ }
+}
+
+func init() {
+ register(&badDecode{})
+}
diff --git a/pkg/p9/version.go b/pkg/p9/version.go
new file mode 100644
index 000000000..8783eaa7e
--- /dev/null
+++ b/pkg/p9/version.go
@@ -0,0 +1,145 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package p9
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+const (
+ // highestSupportedVersion is the highest supported version X in a
+ // version string of the format 9P2000.L.Google.X.
+ //
+ // Clients are expected to start requesting this version number and
+ // to continuously decrement it until a Tversion request succeeds.
+ highestSupportedVersion uint32 = 6
+
+ // lowestSupportedVersion is the lowest supported version X in a
+ // version string of the format 9P2000.L.Google.X.
+ //
+ // Clients are free to send a Tversion request at a version below this
+ // value but are expected to encounter an Rlerror in response.
+ lowestSupportedVersion uint32 = 0
+
+ // baseVersion is the base version of 9P that this package must always
+ // support. It is equivalent to 9P2000.L.Google.0.
+ baseVersion = "9P2000.L"
+)
+
+// HighestVersionString returns the highest possible version string that a client
+// may request or a server may support.
+func HighestVersionString() string {
+ return versionString(highestSupportedVersion)
+}
+
+// parseVersion parses a Tversion version string into a numeric version number
+// if the version string is supported by p9. Otherwise returns (0, false).
+//
+// From Tversion(9P): "Version strings are defined such that, if the client string
+// contains one or more period characters, the initial substring up to but not
+// including any single period in the version string defines a version of the protocol."
+//
+// p9 intentionally diverges from this and always requires that the version string
+// start with 9P2000.L to express that it is always compatible with 9P2000.L. The
+// only supported versions extensions are of the format 9p2000.L.Google.X where X
+// is an ever increasing version counter.
+//
+// Version 9P2000.L.Google.0 implies 9P2000.L.
+//
+// New versions must always be a strict superset of 9P2000.L. A version increase must
+// define a predicate representing the feature extension introduced by that version. The
+// predicate must be commented and should take the format:
+//
+// // VersionSupportsX returns true if version v supports X and must be checked when ...
+// func VersionSupportsX(v int32) bool {
+// ...
+// )
+func parseVersion(str string) (uint32, bool) {
+ // Special case the base version which lacks the ".Google.X" suffix. This
+ // version always means version 0.
+ if str == baseVersion {
+ return 0, true
+ }
+ substr := strings.Split(str, ".")
+ if len(substr) != 4 {
+ return 0, false
+ }
+ if substr[0] != "9P2000" || substr[1] != "L" || substr[2] != "Google" || len(substr[3]) == 0 {
+ return 0, false
+ }
+ version, err := strconv.ParseUint(substr[3], 10, 32)
+ if err != nil {
+ return 0, false
+ }
+ return uint32(version), true
+}
+
+// versionString formats a p9 version number into a Tversion version string.
+func versionString(version uint32) string {
+ // Special case the base version so that clients expecting this string
+ // instead of the 9P2000.L.Google.0 equivalent get it. This is important
+ // for backwards compatibility with legacy servers that check for exactly
+ // the baseVersion and allow nothing else.
+ if version == 0 {
+ return baseVersion
+ }
+ return fmt.Sprintf("9P2000.L.Google.%d", version)
+}
+
+// VersionSupportsTflushf returns true if version v supports the Tflushf message.
+// This predicate must be checked by clients before attempting to make a Tflushf
+// request. If this predicate returns false, then clients may safely no-op.
+func VersionSupportsTflushf(v uint32) bool {
+ return v >= 1
+}
+
+// versionSupportsTwalkgetattr returns true if version v supports the
+// Twalkgetattr message. This predicate must be checked by clients before
+// attempting to make a Twalkgetattr request.
+func versionSupportsTwalkgetattr(v uint32) bool {
+ return v >= 2
+}
+
+// versionSupportsTucreation returns true if version v supports the Tucreation
+// messages (Tucreate, Tusymlink, Tumkdir, Tumknod). This predicate must be
+// checked by clients before attempting to make a Tucreation request.
+// If Tucreation messages are not supported, their non-UID supporting
+// counterparts (Tlcreate, Tsymlink, Tmkdir, Tmknod) should be used.
+func versionSupportsTucreation(v uint32) bool {
+ return v >= 3
+}
+
+// VersionSupportsConnect returns true if version v supports the Tlconnect
+// message. This predicate must be checked by clients
+// before attempting to make a Tlconnect request. If Tlconnect messages are not
+// supported, Tlopen should be used.
+func VersionSupportsConnect(v uint32) bool {
+ return v >= 4
+}
+
+// VersionSupportsAnonymous returns true if version v supports Tlconnect
+// with the AnonymousSocket mode. This predicate must be checked by clients
+// before attempting to use the AnonymousSocket Tlconnect mode.
+func VersionSupportsAnonymous(v uint32) bool {
+ return v >= 5
+}
+
+// VersionSupportsMultiUser returns true if version v supports multi-user fake
+// directory permissions and ID values.
+func VersionSupportsMultiUser(v uint32) bool {
+ return v >= 6
+}
diff --git a/pkg/p9/version_test.go b/pkg/p9/version_test.go
new file mode 100644
index 000000000..634ac3ca5
--- /dev/null
+++ b/pkg/p9/version_test.go
@@ -0,0 +1,145 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package p9
+
+import (
+ "testing"
+)
+
+func TestVersionNumberEquivalent(t *testing.T) {
+ for i := uint32(0); i < 1024; i++ {
+ str := versionString(i)
+ version, ok := parseVersion(str)
+ if !ok {
+ t.Errorf("#%d: parseVersion(%q) failed, want success", i, str)
+ continue
+ }
+ if i != version {
+ t.Errorf("#%d: got version %d, want %d", i, i, version)
+ }
+ }
+}
+
+func TestVersionStringEquivalent(t *testing.T) {
+ // There is one case where the version is not equivalent on purpose,
+ // that is 9P2000.L.Google.0. It is not equivalent because versionString
+ // must always return the more generic 9P2000.L for legacy servers that
+ // check for it. See net/9p/client.c.
+ str := "9P2000.L.Google.0"
+ version, ok := parseVersion(str)
+ if !ok {
+ t.Errorf("parseVersion(%q) failed, want success", str)
+ }
+ if got := versionString(version); got != "9P2000.L" {
+ t.Errorf("versionString(%d) got %q, want %q", version, got, "9P2000.L")
+ }
+
+ for _, test := range []struct {
+ versionString string
+ }{
+ {
+ versionString: "9P2000.L",
+ },
+ {
+ versionString: "9P2000.L.Google.1",
+ },
+ {
+ versionString: "9P2000.L.Google.347823894",
+ },
+ } {
+ version, ok := parseVersion(test.versionString)
+ if !ok {
+ t.Errorf("parseVersion(%q) failed, want success", test.versionString)
+ continue
+ }
+ if got := versionString(version); got != test.versionString {
+ t.Errorf("versionString(%d) got %q, want %q", version, got, test.versionString)
+ }
+ }
+}
+
+func TestParseVersion(t *testing.T) {
+ for _, test := range []struct {
+ versionString string
+ expectSuccess bool
+ expectedVersion uint32
+ }{
+ {
+ versionString: "9P",
+ expectSuccess: false,
+ },
+ {
+ versionString: "9P.L",
+ expectSuccess: false,
+ },
+ {
+ versionString: "9P200.L",
+ expectSuccess: false,
+ },
+ {
+ versionString: "9P2000",
+ expectSuccess: false,
+ },
+ {
+ versionString: "9P2000.L.Google.-1",
+ expectSuccess: false,
+ },
+ {
+ versionString: "9P2000.L.Google.",
+ expectSuccess: false,
+ },
+ {
+ versionString: "9P2000.L.Google.3546343826724305832",
+ expectSuccess: false,
+ },
+ {
+ versionString: "9P2001.L",
+ expectSuccess: false,
+ },
+ {
+ versionString: "9P2000.L",
+ expectSuccess: true,
+ expectedVersion: 0,
+ },
+ {
+ versionString: "9P2000.L.Google.0",
+ expectSuccess: true,
+ expectedVersion: 0,
+ },
+ {
+ versionString: "9P2000.L.Google.1",
+ expectSuccess: true,
+ expectedVersion: 1,
+ },
+ } {
+ version, ok := parseVersion(test.versionString)
+ if ok != test.expectSuccess {
+ t.Errorf("parseVersion(%q) got (_, %v), want (_, %v)", test.versionString, ok, test.expectSuccess)
+ continue
+ }
+ if !test.expectSuccess {
+ continue
+ }
+ if version != test.expectedVersion {
+ t.Errorf("parseVersion(%q) got (%d, _), want (%d, _)", test.versionString, version, test.expectedVersion)
+ }
+ }
+}
+
+func BenchmarkParseVersion(b *testing.B) {
+ for n := 0; n < b.N; n++ {
+ parseVersion("9P2000.L.Google.1")
+ }
+}