diff options
Diffstat (limited to 'pkg/p9')
-rw-r--r-- | pkg/p9/BUILD | 47 | ||||
-rw-r--r-- | pkg/p9/buffer.go | 262 | ||||
-rw-r--r-- | pkg/p9/client.go | 301 | ||||
-rw-r--r-- | pkg/p9/client_file.go | 513 | ||||
-rw-r--r-- | pkg/p9/client_test.go | 62 | ||||
-rw-r--r-- | pkg/p9/file.go | 180 | ||||
-rw-r--r-- | pkg/p9/handlers.go | 705 | ||||
-rw-r--r-- | pkg/p9/local_server/BUILD | 14 | ||||
-rw-r--r-- | pkg/p9/local_server/local_server.go | 347 | ||||
-rw-r--r-- | pkg/p9/messages.go | 2271 | ||||
-rw-r--r-- | pkg/p9/messages_test.go | 391 | ||||
-rw-r--r-- | pkg/p9/p9.go | 1023 | ||||
-rw-r--r-- | pkg/p9/p9_test.go | 188 | ||||
-rw-r--r-- | pkg/p9/p9test/BUILD | 28 | ||||
-rw-r--r-- | pkg/p9/p9test/client_test.go | 335 | ||||
-rw-r--r-- | pkg/p9/p9test/mocks.go | 490 | ||||
-rw-r--r-- | pkg/p9/pool.go | 68 | ||||
-rw-r--r-- | pkg/p9/pool_test.go | 64 | ||||
-rw-r--r-- | pkg/p9/server.go | 380 | ||||
-rw-r--r-- | pkg/p9/transport.go | 346 | ||||
-rw-r--r-- | pkg/p9/transport_test.go | 184 | ||||
-rw-r--r-- | pkg/p9/version.go | 145 | ||||
-rw-r--r-- | pkg/p9/version_test.go | 145 |
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") + } +} |