diff options
author | gVisor bot <gvisor-bot@google.com> | 2019-06-02 06:44:55 +0000 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2019-06-02 06:44:55 +0000 |
commit | ceb0d792f328d1fc0692197d8856a43c3936a571 (patch) | |
tree | 83155f302eff44a78bcc30a3a08f4efe59a79379 /pkg/p9 | |
parent | deb7ecf1e46862d54f4b102f2d163cfbcfc37f3b (diff) | |
parent | 216da0b733dbed9aad9b2ab92ac75bcb906fd7ee (diff) |
Merge 216da0b7 (automated)
Diffstat (limited to 'pkg/p9')
-rw-r--r-- | pkg/p9/buffer.go | 263 | ||||
-rw-r--r-- | pkg/p9/client.go | 307 | ||||
-rw-r--r-- | pkg/p9/client_file.go | 632 | ||||
-rw-r--r-- | pkg/p9/file.go | 256 | ||||
-rw-r--r-- | pkg/p9/handlers.go | 1291 | ||||
-rw-r--r-- | pkg/p9/messages.go | 2359 | ||||
-rw-r--r-- | pkg/p9/p9.go | 1141 | ||||
-rwxr-xr-x | pkg/p9/p9_state_autogen.go | 4 | ||||
-rw-r--r-- | pkg/p9/path_tree.go | 109 | ||||
-rw-r--r-- | pkg/p9/pool.go | 68 | ||||
-rw-r--r-- | pkg/p9/server.go | 575 | ||||
-rw-r--r-- | pkg/p9/transport.go | 342 | ||||
-rw-r--r-- | pkg/p9/version.go | 150 |
13 files changed, 7497 insertions, 0 deletions
diff --git a/pkg/p9/buffer.go b/pkg/p9/buffer.go new file mode 100644 index 000000000..249536d8a --- /dev/null +++ b/pkg/p9/buffer.go @@ -0,0 +1,263 @@ +// Copyright 2018 The gVisor Authors. +// +// 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. Decode may be called more than once + // to reuse the instance. It must clear any previous state. + // + // 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..56587e2cf --- /dev/null +++ b/pkg/p9/client.go @@ -0,0 +1,307 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 <= msgRegistry.largestFixedSize { + return nil, &ErrMessageTooLarge{ + size: messageSize, + msize: msgRegistry.largestFixedSize, + } + } + + // Compute a payload size and round to 512 (normal block size) + // if it's larger than a single block. + payloadSize := messageSize - msgRegistry.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 +} + +// Close closes the underlying socket. +func (c *Client) Close() error { + return c.socket.Close() +} diff --git a/pkg/p9/client_file.go b/pkg/p9/client_file.go new file mode 100644 index 000000000..258080f67 --- /dev/null +++ b/pkg/p9/client_file.go @@ -0,0 +1,632 @@ +// Copyright 2018 The gVisor Authors. +// +// 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) { + if atomic.LoadUint32(&c.closed) != 0 { + return nil, nil, syscall.EBADF + } + + 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 atomic.LoadUint32(&c.closed) != 0 { + return nil, nil, AttrMask{}, Attr{}, syscall.EBADF + } + + 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) { + if atomic.LoadUint32(&c.closed) != 0 { + return FSStat{}, syscall.EBADF + } + + 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 { + if atomic.LoadUint32(&c.closed) != 0 { + return syscall.EBADF + } + + return c.client.sendRecv(&Tfsync{FID: c.fid}, &Rfsync{}) +} + +// GetAttr implements File.GetAttr. +func (c *clientFile) GetAttr(req AttrMask) (QID, AttrMask, Attr, error) { + if atomic.LoadUint32(&c.closed) != 0 { + return QID{}, AttrMask{}, Attr{}, syscall.EBADF + } + + 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 { + if atomic.LoadUint32(&c.closed) != 0 { + return syscall.EBADF + } + + return c.client.sendRecv(&Tsetattr{FID: c.fid, Valid: valid, SetAttr: attr}, &Rsetattr{}) +} + +// Allocate implements File.Allocate. +func (c *clientFile) Allocate(mode AllocateMode, offset, length uint64) error { + if atomic.LoadUint32(&c.closed) != 0 { + return syscall.EBADF + } + if !versionSupportsTallocate(c.client.version) { + return syscall.EOPNOTSUPP + } + + return c.client.sendRecv(&Tallocate{FID: c.fid, Mode: mode, Offset: offset, Length: length}, &Rallocate{}) +} + +// Remove implements File.Remove. +// +// N.B. This method is no longer part of the file interface and should be +// considered deprecated. +func (c *clientFile) Remove() error { + // Avoid double close. + if !atomic.CompareAndSwapUint32(&c.closed, 0, 1) { + return syscall.EBADF + } + runtime.SetFinalizer(c, nil) + + // Send the remove message. + if err := c.client.sendRecv(&Tremove{FID: c.fid}, &Rremove{}); err != nil { + 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 syscall.EBADF + } + runtime.SetFinalizer(c, 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) { + if atomic.LoadUint32(&c.closed) != 0 { + return nil, QID{}, 0, syscall.EBADF + } + + 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 atomic.LoadUint32(&c.closed) != 0 { + return nil, syscall.EBADF + } + + 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) { + if atomic.LoadUint32(&c.closed) != 0 { + return 0, syscall.EBADF + } + + 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) { + if atomic.LoadUint32(&c.closed) != 0 { + return 0, syscall.EBADF + } + + 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 { + if atomic.LoadUint32(&c.closed) != 0 { + return syscall.EBADF + } + + 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) { + if atomic.LoadUint32(&c.closed) != 0 { + return nil, nil, QID{}, 0, syscall.EBADF + } + + 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) { + if atomic.LoadUint32(&c.closed) != 0 { + return QID{}, syscall.EBADF + } + + 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) { + if atomic.LoadUint32(&c.closed) != 0 { + return QID{}, syscall.EBADF + } + + 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 { + if atomic.LoadUint32(&c.closed) != 0 { + return syscall.EBADF + } + + 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, mode FileMode, major uint32, minor uint32, uid UID, gid GID) (QID, error) { + if atomic.LoadUint32(&c.closed) != 0 { + return QID{}, syscall.EBADF + } + + msg := Tmknod{ + Directory: c.fid, + Name: name, + Mode: mode, + 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 { + if atomic.LoadUint32(&c.closed) != 0 { + return syscall.EBADF + } + + 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 { + if atomic.LoadUint32(&c.closed) != 0 { + return syscall.EBADF + } + + 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) { + if atomic.LoadUint32(&c.closed) != 0 { + return nil, syscall.EBADF + } + + 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) { + if atomic.LoadUint32(&c.closed) != 0 { + return "", syscall.EBADF + } + + 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 atomic.LoadUint32(&c.closed) != 0 { + return syscall.EBADF + } + + if !VersionSupportsTflushf(c.client.version) { + return nil + } + + return c.client.sendRecv(&Tflushf{FID: c.fid}, &Rflushf{}) +} + +// Renamed implements File.Renamed. +func (c *clientFile) Renamed(newDir File, newName string) {} diff --git a/pkg/p9/file.go b/pkg/p9/file.go new file mode 100644 index 000000000..a456e8b3d --- /dev/null +++ b/pkg/p9/file.go @@ -0,0 +1,256 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 server. +type Attacher interface { + // Attach returns a new File. + // + // The client-side attach will be translate to a series of walks from + // the file returned by this Attach call. + Attach() (File, error) +} + +// File is a set of operations corresponding to a single node. +// +// Note that on the server side, the server logic places constraints on +// concurrent operations to make things easier. This may reduce the need for +// complex, error-prone locking and logic in the backend. These are documented +// for each method. +// +// There are three different types of guarantees provided: +// +// none: There is no concurrency guarantee. The method may be invoked +// concurrently with any other method on any other file. +// +// read: The method is guaranteed to be exclusive of any write or global +// operation that is mutating the state of the directory tree starting at this +// node. For example, this means creating new files, symlinks, directories or +// renaming a directory entry (or renaming in to this target), but the method +// may be called concurrently with other read methods. +// +// write: The method is guaranteed to be exclusive of any read, write or global +// operation that is mutating the state of the directory tree starting at this +// node, as described in read above. There may however, be other write +// operations executing concurrently on other components in the directory tree. +// +// global: The method is guaranteed to be exclusive of any read, write or +// global operation. +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. + // + // On the server, Walk has a read concurrency guarantee. + Walk(names []string) ([]QID, File, 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. + // + // On the server, WalkGetAttr has a read concurrency guarantee. + WalkGetAttr([]string) ([]QID, File, AttrMask, Attr, error) + + // StatFS returns information about the file system associated with + // this file. + // + // On the server, StatFS has no concurrency guarantee. + StatFS() (FSStat, error) + + // GetAttr returns attributes of this node. + // + // On the server, GetAttr has a read concurrency guarantee. + GetAttr(req AttrMask) (QID, AttrMask, Attr, error) + + // SetAttr sets attributes on this node. + // + // On the server, SetAttr has a write concurrency guarantee. + SetAttr(valid SetAttrMask, attr SetAttr) error + + // Allocate allows the caller to directly manipulate the allocated disk space + // for the file. See fallocate(2) for more details. + Allocate(mode AllocateMode, offset, length uint64) 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. + // + // On the server, Close has no concurrency guarantee. + Close() error + + // Open must be called prior to using Read, Write or Readdir. Once Open + // is called, some operations, such as Walk, will no longer work. + // + // On the client, Open should be called only once. The fd return is + // optional, and may be nil. + // + // On the server, Open has a read concurrency guarantee. If an *fd.FD + // is provided, ownership now belongs to the caller. Open is guaranteed + // to be called only once. + // + // N.B. The server must resolve any lazy paths when open is called. + // After this point, read and write may be called on files with no + // deletion check, so resolving in the data path is not viable. + Open(mode OpenFlags) (*fd.FD, QID, uint32, error) + + // Read reads from this file. Open must be called first. + // + // This may return io.EOF in addition to syscall.Errno values. + // + // On the server, ReadAt has a read concurrency guarantee. See Open for + // additional requirements regarding lazy path resolution. + ReadAt(p []byte, offset uint64) (int, error) + + // Write writes to this file. Open must be called first. + // + // This may return io.EOF in addition to syscall.Errno values. + // + // On the server, WriteAt has a read concurrency guarantee. See Open + // for additional requirements regarding lazy path resolution. + WriteAt(p []byte, offset uint64) (int, error) + + // FSync syncs this node. Open must be called first. + // + // On the server, FSync has a read concurrency guarantee. + FSync() error + + // Create creates a new regular file and opens it according to the + // flags given. This file is already Open. + // + // N.B. On the client, the returned file is a reference to the current + // file, which now represents the created file. This is not the case on + // the server. These semantics are very subtle and can easily lead to + // bugs, but are a consequence of the 9P create operation. + // + // See p9.File.Open for a description of *fd.FD. + // + // On the server, Create has a write concurrency guarantee. + Create(name string, flags OpenFlags, permissions FileMode, uid UID, gid GID) (*fd.FD, File, QID, uint32, error) + + // Mkdir creates a subdirectory. + // + // On the server, Mkdir has a write concurrency guarantee. + Mkdir(name string, permissions FileMode, uid UID, gid GID) (QID, error) + + // Symlink makes a new symbolic link. + // + // On the server, Symlink has a write concurrency guarantee. + Symlink(oldName string, newName string, uid UID, gid GID) (QID, error) + + // Link makes a new hard link. + // + // On the server, Link has a write concurrency guarantee. + Link(target File, newName string) error + + // Mknod makes a new device node. + // + // On the server, Mknod has a write concurrency guarantee. + Mknod(name string, mode FileMode, major uint32, minor uint32, uid UID, gid GID) (QID, error) + + // Rename renames the file. + // + // Rename will never be called on the server, and RenameAt will always + // be used instead. + Rename(newDir File, newName string) 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. + // + // On the server, RenameAt has a global concurrency guarantee. + 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. + // + // On the server, UnlinkAt has a write concurrency guarantee. + UnlinkAt(name string, flags uint32) error + + // Readdir reads directory entries. + // + // This may return io.EOF in addition to syscall.Errno values. + // + // On the server, Readdir has a read concurrency guarantee. + Readdir(offset uint64, count uint32) ([]Dirent, error) + + // Readlink reads the link target. + // + // On the server, Readlink has a read concurrency guarantee. + 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. + // + // On the server, Flush has a read concurrency guarantee. + Flush() 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. + // + // On the server, Connect has a read concurrency guarantee. + Connect(flags ConnectFlags) (*fd.FD, error) + + // Renamed is called when this node is renamed. + // + // This may not fail. The file will hold a reference to its parent + // within the p9 package, and is therefore safe to use for the lifetime + // of this File (until Close is called). + // + // This method should not be called by clients, who should use the + // relevant Rename methods. (Although the method will be a no-op.) + // + // On the server, Renamed has a global concurrency guarantee. + Renamed(newDir File, newName string) +} + +// 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..f32368763 --- /dev/null +++ b/pkg/p9/handlers.go @@ -0,0 +1,1291 @@ +// Copyright 2018 The gVisor Authors. +// +// 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" + "os" + "path" + "strings" + "sync/atomic" + "syscall" + + "gvisor.googlesource.com/gvisor/pkg/fd" + "gvisor.googlesource.com/gvisor/pkg/log" +) + +// ExtractErrno extracts a syscall.Errno from a error, best effort. +func ExtractErrno(err error) syscall.Errno { + switch err { + case os.ErrNotExist: + return syscall.ENOENT + case os.ErrExist: + return syscall.EEXIST + case os.ErrPermission: + return syscall.EACCES + case os.ErrInvalid: + return syscall.EINVAL + } + + // Attempt to unwrap. + switch e := err.(type) { + case syscall.Errno: + return e + case *os.PathError: + return ExtractErrno(e.Err) + case *os.SyscallError: + return ExtractErrno(e.Err) + } + + // Default case. + log.Warningf("unknown error: %v", err) + return syscall.EIO +} + +// newErr returns a new error message from an error. +func newErr(err error) *Rlerror { + return &Rlerror{Error: uint32(ExtractErrno(err))} +} + +// 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{} +} + +// checkSafeName validates the name and returns nil or returns an error. +func checkSafeName(name string) error { + if name != "" && !strings.Contains(name, "/") && name != "." && name != ".." { + return nil + } + return syscall.EINVAL +} + +// 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() + + // Frustratingly, because we can't be guaranteed that a rename is not + // occurring simultaneously with this removal, we need to acquire the + // global rename lock for this kind of remove operation to ensure that + // ref.parent does not change out from underneath us. + // + // This is why Tremove is a bad idea, and clients should generally use + // Tunlinkat. All p9 clients will use Tunlinkat. + err := ref.safelyGlobal(func() error { + // Is this a root? Can't remove that. + if ref.isRoot() { + return syscall.EINVAL + } + + // N.B. this remove operation is permitted, even if the file is open. + // See also rename below for reasoning. + + // Is this file already deleted? + if ref.isDeleted() { + return syscall.EINVAL + } + + // Retrieve the file's proper name. + name := ref.parent.pathNode.nameFor(ref) + + // Attempt the removal. + if err := ref.parent.file.UnlinkAt(name, 0); err != nil { + return err + } + + // Mark all relevant fids as deleted. We don't need to lock any + // individual nodes because we already hold the global lock. + ref.parent.markChildDeleted(name) + return nil + }) + + // "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 + 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) + } + + // Must provide an absolute path. + if path.IsAbs(t.Auth.AttachName) { + // Trim off the leading / if the path is absolute. We always + // treat attach paths as absolute and call attach with the root + // argument on the server file for clarity. + t.Auth.AttachName = t.Auth.AttachName[1:] + } + + // Do the attach on the root. + sf, err := cs.server.attacher.Attach() + if err != nil { + return newErr(err) + } + qid, valid, attr, err := sf.GetAttr(AttrMaskAll()) + if err != nil { + sf.Close() // Drop file. + return newErr(err) + } + if !valid.Mode { + sf.Close() // Drop file. + return newErr(syscall.EINVAL) + } + + // Build a transient reference. + root := &fidRef{ + server: cs.server, + parent: nil, + file: sf, + refs: 1, + mode: attr.Mode.FileType(), + pathNode: &cs.server.pathTree, + } + defer root.DecRef() + + // Attach the root? + if len(t.Auth.AttachName) == 0 { + cs.InsertFID(t.FID, root) + return &Rattach{QID: qid} + } + + // We want the same traversal checks to apply on attach, so always + // attach at the root and use the regular walk paths. + names := strings.Split(t.Auth.AttachName, "/") + _, newRef, _, _, err := doWalk(cs, root, names, false) + if err != nil { + return newErr(err) + } + defer newRef.DecRef() + + // Insert the FID. + cs.InsertFID(t.FID, newRef) + return &Rattach{QID: qid} +} + +// CanOpen returns whether this file open can be opened, read and written to. +// +// This includes everything except symlinks and sockets. +func CanOpen(mode FileMode) bool { + return mode.IsRegular() || mode.IsDir() || mode.IsNamedPipe() || mode.IsBlockDevice() || mode.IsCharacterDevice() +} + +// 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 || !CanOpen(ref.mode) { + return newErr(syscall.EINVAL) + } + + // Are flags valid? + flags := t.Flags &^ OpenFlagsIgnoreMask + if flags&^OpenFlagsModeMask != 0 { + return newErr(syscall.EINVAL) + } + + // Is this an attempt to open a directory as writable? Don't accept. + if ref.mode.IsDir() && flags != ReadOnly { + return newErr(syscall.EINVAL) + } + + var ( + qid QID + ioUnit uint32 + osFile *fd.FD + ) + if err := ref.safelyRead(func() (err error) { + // Has it been deleted already? + if ref.isDeleted() { + return syscall.EINVAL + } + + // Do the open. + osFile, qid, ioUnit, err = ref.file.Open(t.Flags) + return err + }); 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) { + // Don't allow complex names. + if err := checkSafeName(t.Name); err != nil { + return nil, err + } + + // Lookup the FID. + ref, ok := cs.LookupFID(t.FID) + if !ok { + return nil, syscall.EBADF + } + defer ref.DecRef() + + var ( + osFile *fd.FD + nsf File + qid QID + ioUnit uint32 + newRef *fidRef + ) + if err := ref.safelyWrite(func() (err error) { + // Don't allow creation from non-directories or deleted directories. + if ref.isDeleted() || !ref.mode.IsDir() { + return syscall.EINVAL + } + + // Not allowed on open directories. + if _, opened := ref.OpenFlags(); opened { + return syscall.EINVAL + } + + // Do the create. + osFile, nsf, qid, ioUnit, err = ref.file.Create(t.Name, t.OpenFlags, t.Permissions, uid, t.GID) + if err != nil { + return err + } + + newRef = &fidRef{ + server: cs.server, + parent: ref, + file: nsf, + opened: true, + openFlags: t.OpenFlags, + mode: ModeRegular, + pathNode: ref.pathNode.pathNodeFor(t.Name), + } + ref.pathNode.addChild(newRef, t.Name) + ref.IncRef() // Acquire parent reference. + return nil + }); err != nil { + return nil, err + } + + // Replace the FID reference. + cs.InsertFID(t.FID, newRef) + + 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) { + // Don't allow complex names. + if err := checkSafeName(t.Name); err != nil { + return nil, err + } + + // Lookup the FID. + ref, ok := cs.LookupFID(t.Directory) + if !ok { + return nil, syscall.EBADF + } + defer ref.DecRef() + + var qid QID + if err := ref.safelyWrite(func() (err error) { + // Don't allow symlinks from non-directories or deleted directories. + if ref.isDeleted() || !ref.mode.IsDir() { + return syscall.EINVAL + } + + // Not allowed on open directories. + if _, opened := ref.OpenFlags(); opened { + return syscall.EINVAL + } + + // Do the symlink. + qid, err = ref.file.Symlink(t.Target, t.Name, uid, t.GID) + return err + }); err != nil { + return nil, err + } + + return &Rsymlink{QID: qid}, nil +} + +// handle implements handler.handle. +func (t *Tlink) handle(cs *connState) message { + // Don't allow complex names. + if err := checkSafeName(t.Name); err != nil { + return newErr(err) + } + + // 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() + + if err := ref.safelyWrite(func() (err error) { + // Don't allow create links from non-directories or deleted directories. + if ref.isDeleted() || !ref.mode.IsDir() { + return syscall.EINVAL + } + + // Not allowed on open directories. + if _, opened := ref.OpenFlags(); opened { + return syscall.EINVAL + } + + // Do the link. + return 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 { + // Don't allow complex names. + if err := checkSafeName(t.OldName); err != nil { + return newErr(err) + } + if err := checkSafeName(t.NewName); err != nil { + return newErr(err) + } + + // 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() + + // Perform the rename holding the global lock. + if err := ref.safelyGlobal(func() (err error) { + // Don't allow renaming across deleted directories. + if ref.isDeleted() || !ref.mode.IsDir() || refTarget.isDeleted() || !refTarget.mode.IsDir() { + return syscall.EINVAL + } + + // Not allowed on open directories. + if _, opened := ref.OpenFlags(); opened { + return syscall.EINVAL + } + + // Is this the same file? If yes, short-circuit and return success. + if ref.pathNode == refTarget.pathNode && t.OldName == t.NewName { + return nil + } + + // Attempt the actual rename. + if err := ref.file.RenameAt(t.OldName, refTarget.file, t.NewName); err != nil { + return err + } + + // Update the path tree. + ref.renameChildTo(t.OldName, refTarget, t.NewName) + return nil + }); err != nil { + return newErr(err) + } + + return &Rrenameat{} +} + +// handle implements handler.handle. +func (t *Tunlinkat) handle(cs *connState) message { + // Don't allow complex names. + if err := checkSafeName(t.Name); err != nil { + return newErr(err) + } + + // Lookup the FID. + ref, ok := cs.LookupFID(t.Directory) + if !ok { + return newErr(syscall.EBADF) + } + defer ref.DecRef() + + if err := ref.safelyWrite(func() (err error) { + // Don't allow deletion from non-directories or deleted directories. + if ref.isDeleted() || !ref.mode.IsDir() { + return syscall.EINVAL + } + + // Not allowed on open directories. + if _, opened := ref.OpenFlags(); opened { + return syscall.EINVAL + } + + // Before we do the unlink itself, we need to ensure that there + // are no operations in flight on associated path node. The + // child's path node lock must be held to ensure that the + // unlink at marking the child deleted below is atomic with + // respect to any other read or write operations. + // + // This is one case where we have a lock ordering issue, but + // since we always acquire deeper in the hierarchy, we know + // that we are free of lock cycles. + childPathNode := ref.pathNode.pathNodeFor(t.Name) + childPathNode.mu.Lock() + defer childPathNode.mu.Unlock() + + // Do the unlink. + err = ref.file.UnlinkAt(t.Name, t.Flags) + if err != nil { + return err + } + + // Mark the path as deleted. + ref.markChildDeleted(t.Name) + return nil + }); err != nil { + return newErr(err) + } + + return &Runlinkat{} +} + +// handle implements handler.handle. +func (t *Trename) handle(cs *connState) message { + // Don't allow complex names. + if err := checkSafeName(t.Name); err != nil { + return newErr(err) + } + + // 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() + + if err := ref.safelyGlobal(func() (err error) { + // Don't allow a root rename. + if ref.isRoot() { + return syscall.EINVAL + } + + // Don't allow renaming deleting entries, or target non-directories. + if ref.isDeleted() || refTarget.isDeleted() || !refTarget.mode.IsDir() { + return syscall.EINVAL + } + + // If the parent is deleted, but we not, something is seriously wrong. + // It's fail to die at this point with an assertion failure. + if ref.parent.isDeleted() { + panic(fmt.Sprintf("parent %+v deleted, child %+v is not", ref.parent, ref)) + } + + // N.B. The rename operation is allowed to proceed on open files. It + // does impact the state of its parent, but this is merely a sanity + // check in any case, and the operation is safe. There may be other + // files corresponding to the same path that are renamed anyways. + + // Check for the exact same file and short-circuit. + oldName := ref.parent.pathNode.nameFor(ref) + if ref.parent.pathNode == refTarget.pathNode && oldName == t.Name { + return nil + } + + // Call the rename method on the parent. + if err := ref.parent.file.RenameAt(oldName, refTarget.file, t.Name); err != nil { + return err + } + + // Update the path tree. + ref.parent.renameChildTo(oldName, refTarget, t.Name) + return nil + }); 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() + + var target string + if err := ref.safelyRead(func() (err error) { + // Don't allow readlink on deleted files. There is no need to + // check if this file is opened because symlinks cannot be + // opened. + if ref.isDeleted() || !ref.mode.IsSymlink() { + return syscall.EINVAL + } + + // Do the read. + target, err = ref.file.Readlink() + return err + }); 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() + + // Constrain the size of the read buffer. + if int(t.Count) > int(maximumLength) { + return newErr(syscall.ENOBUFS) + } + + var ( + data = make([]byte, t.Count) + n int + ) + if err := ref.safelyRead(func() (err error) { + // Has it been opened already? + openFlags, opened := ref.OpenFlags() + if !opened { + return syscall.EINVAL + } + + // Can it be read? Check permissions. + if openFlags&OpenFlagsModeMask == WriteOnly { + return syscall.EPERM + } + + n, err = ref.file.ReadAt(data, t.Offset) + return err + }); 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() + + var n int + if err := ref.safelyRead(func() (err error) { + // Has it been opened already? + openFlags, opened := ref.OpenFlags() + if !opened { + return syscall.EINVAL + } + + // Can it be written? Check permissions. + if openFlags&OpenFlagsModeMask == ReadOnly { + return syscall.EPERM + } + + n, err = ref.file.WriteAt(t.Data, t.Offset) + return err + }); 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) { + // Don't allow complex names. + if err := checkSafeName(t.Name); err != nil { + return nil, err + } + + // Lookup the FID. + ref, ok := cs.LookupFID(t.Directory) + if !ok { + return nil, syscall.EBADF + } + defer ref.DecRef() + + var qid QID + if err := ref.safelyWrite(func() (err error) { + // Don't allow mknod on deleted files. + if ref.isDeleted() || !ref.mode.IsDir() { + return syscall.EINVAL + } + + // Not allowed on open directories. + if _, opened := ref.OpenFlags(); opened { + return syscall.EINVAL + } + + // Do the mknod. + qid, err = ref.file.Mknod(t.Name, t.Mode, t.Major, t.Minor, uid, t.GID) + return err + }); 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) { + // Don't allow complex names. + if err := checkSafeName(t.Name); err != nil { + return nil, err + } + + // Lookup the FID. + ref, ok := cs.LookupFID(t.Directory) + if !ok { + return nil, syscall.EBADF + } + defer ref.DecRef() + + var qid QID + if err := ref.safelyWrite(func() (err error) { + // Don't allow mkdir on deleted files. + if ref.isDeleted() || !ref.mode.IsDir() { + return syscall.EINVAL + } + + // Not allowed on open directories. + if _, opened := ref.OpenFlags(); opened { + return syscall.EINVAL + } + + // Do the mkdir. + qid, err = ref.file.Mkdir(t.Name, t.Permissions, uid, t.GID) + return err + }); 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() + + // We allow getattr on deleted files. Depending on the backing + // implementation, it's possible that races exist that might allow + // fetching attributes of other files. But we need to generally allow + // refreshing attributes and this is a minor leak, if at all. + + var ( + qid QID + valid AttrMask + attr Attr + ) + if err := ref.safelyRead(func() (err error) { + qid, valid, attr, err = ref.file.GetAttr(t.AttrMask) + return err + }); 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() + + if err := ref.safelyWrite(func() error { + // We don't allow setattr on files that have been deleted. + // This might be technically incorrect, as it's possible that + // there were multiple links and you can still change the + // corresponding inode information. + if ref.isDeleted() { + return syscall.EINVAL + } + + // Set the attributes. + return ref.file.SetAttr(t.Valid, t.SetAttr) + }); err != nil { + return newErr(err) + } + + return &Rsetattr{} +} + +// handle implements handler.handle. +func (t *Tallocate) handle(cs *connState) message { + // Lookup the FID. + ref, ok := cs.LookupFID(t.FID) + if !ok { + return newErr(syscall.EBADF) + } + defer ref.DecRef() + + if err := ref.safelyWrite(func() error { + // Has it been opened already? + openFlags, opened := ref.OpenFlags() + if !opened { + return syscall.EINVAL + } + + // Can it be written? Check permissions. + if openFlags&OpenFlagsModeMask == ReadOnly { + return syscall.EBADF + } + + // We don't allow allocate on files that have been deleted. + if ref.isDeleted() { + return syscall.EINVAL + } + + return ref.file.Allocate(t.Mode, t.Offset, t.Length) + }); err != nil { + return newErr(err) + } + + return &Rallocate{} +} + +// 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() + + var entries []Dirent + if err := ref.safelyRead(func() (err error) { + // Don't allow reading deleted directories. + if ref.isDeleted() || !ref.mode.IsDir() { + return syscall.EINVAL + } + + // Has it been opened already? + if _, opened := ref.OpenFlags(); !opened { + return syscall.EINVAL + } + + // Read the entries. + entries, err = ref.file.Readdir(t.Offset, t.Count) + if err != nil && err != io.EOF { + return err + } + return nil + }); err != nil { + 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() + + if err := ref.safelyRead(func() (err error) { + // Has it been opened already? + if _, opened := ref.OpenFlags(); !opened { + return syscall.EINVAL + } + + // Perform the sync. + return ref.file.FSync() + }); 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.safelyRead(ref.file.Flush); err != nil { + return newErr(err) + } + + return &Rflushf{} +} + +// walkOne walks zero or one path elements. +// +// The slice passed as qids is append and returned. +func walkOne(qids []QID, from File, names []string, getattr bool) ([]QID, File, AttrMask, Attr, error) { + if len(names) > 1 { + // We require exactly zero or one elements. + return nil, nil, AttrMask{}, Attr{}, syscall.EINVAL + } + var ( + localQIDs []QID + sf File + valid AttrMask + attr Attr + err error + ) + switch { + case getattr: + localQIDs, sf, valid, attr, err = from.WalkGetAttr(names) + // Can't put fallthrough in the if because Go. + if err != syscall.ENOSYS { + break + } + fallthrough + default: + localQIDs, sf, err = from.Walk(names) + if err != nil { + // No way to walk this element. + break + } + if getattr { + _, valid, attr, err = sf.GetAttr(AttrMaskAll()) + if err != nil { + // Don't leak the file. + sf.Close() + } + } + } + if err != nil { + // Error walking, don't return anything. + return nil, nil, AttrMask{}, Attr{}, err + } + if len(localQIDs) != 1 { + // Expected a single QID. + sf.Close() + return nil, nil, AttrMask{}, Attr{}, syscall.EINVAL + } + return append(qids, localQIDs...), sf, valid, attr, nil +} + +// doWalk walks from a given fidRef. +// +// This enforces that all intermediate nodes are walkable (directories). The +// fidRef returned (newRef) has a reference associated with it that is now +// owned by the caller and must be handled appropriately. +func doWalk(cs *connState, ref *fidRef, names []string, getattr bool) (qids []QID, newRef *fidRef, valid AttrMask, attr Attr, err error) { + // Check the names. + for _, name := range names { + err = checkSafeName(name) + if err != nil { + return + } + } + + // Has it been opened already? + if _, opened := ref.OpenFlags(); opened { + err = syscall.EBUSY + return + } + + // Is this an empty list? Handle specially. We don't actually need to + // validate anything since this is always permitted. + if len(names) == 0 { + var sf File // Temporary. + if err := ref.maybeParent().safelyRead(func() (err error) { + // Clone the single element. + qids, sf, valid, attr, err = walkOne(nil, ref.file, nil, getattr) + if err != nil { + return err + } + + newRef = &fidRef{ + server: cs.server, + parent: ref.parent, + file: sf, + mode: ref.mode, + pathNode: ref.pathNode, + + // For the clone case, the cloned fid must + // preserve the deleted property of the + // original FID. + deleted: ref.deleted, + } + if !ref.isRoot() { + if !newRef.isDeleted() { + // Add only if a non-root node; the same node. + ref.parent.pathNode.addChild(newRef, ref.parent.pathNode.nameFor(ref)) + } + ref.parent.IncRef() // Acquire parent reference. + } + // doWalk returns a reference. + newRef.IncRef() + return nil + }); err != nil { + return nil, nil, AttrMask{}, Attr{}, err + } + // Do not return the new QID. + return nil, newRef, valid, attr, nil + } + + // Do the walk, one element at a time. + walkRef := ref + walkRef.IncRef() + for i := 0; i < len(names); i++ { + // We won't allow beyond past symlinks; stop here if this isn't + // a proper directory and we have additional paths to walk. + if !walkRef.mode.IsDir() { + walkRef.DecRef() // Drop walk reference; no lock required. + return nil, nil, AttrMask{}, Attr{}, syscall.EINVAL + } + + var sf File // Temporary. + if err := walkRef.safelyRead(func() (err error) { + // Pass getattr = true to walkOne since we need the file type for + // newRef. + qids, sf, valid, attr, err = walkOne(qids, walkRef.file, names[i:i+1], true) + if err != nil { + return err + } + + // Note that we don't need to acquire a lock on any of + // these individual instances. That's because they are + // not actually addressable via a FID. They are + // anonymous. They exist in the tree for tracking + // purposes. + newRef := &fidRef{ + server: cs.server, + parent: walkRef, + file: sf, + mode: attr.Mode.FileType(), + pathNode: walkRef.pathNode.pathNodeFor(names[i]), + } + walkRef.pathNode.addChild(newRef, names[i]) + // We allow our walk reference to become the new parent + // reference here and so we don't IncRef. Instead, just + // set walkRef to the newRef above and acquire a new + // walk reference. + walkRef = newRef + walkRef.IncRef() + return nil + }); err != nil { + walkRef.DecRef() // Drop the old walkRef. + return nil, nil, AttrMask{}, Attr{}, err + } + } + + // Success. + return qids, walkRef, valid, attr, nil +} + +// 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() + + // Do the walk. + qids, newRef, _, _, err := doWalk(cs, ref, t.Names, false) + if err != nil { + return newErr(err) + } + defer newRef.DecRef() + + // Install the new FID. + cs.InsertFID(t.NewFID, newRef) + return &Rwalk{QIDs: qids} +} + +// 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() + + // Do the walk. + qids, newRef, valid, attr, err := doWalk(cs, ref, t.Names, true) + if err != nil { + return newErr(err) + } + defer newRef.DecRef() + + // Install the new FID. + cs.InsertFID(t.NewFID, newRef) + 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() + + var osFile *fd.FD + if err := ref.safelyRead(func() (err error) { + // Don't allow connecting to deleted files. + if ref.isDeleted() || !ref.mode.IsSocket() { + return syscall.EINVAL + } + + // Do the connect. + osFile, err = ref.file.Connect(t.Flags) + return err + }); err != nil { + return newErr(err) + } + + return &Rlconnect{File: osFile} +} diff --git a/pkg/p9/messages.go b/pkg/p9/messages.go new file mode 100644 index 000000000..75d6bc832 --- /dev/null +++ b/pkg/p9/messages.go @@ -0,0 +1,2359 @@ +// Copyright 2018 The gVisor Authors. +// +// 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" + "math" + + "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() + t.Names = t.Names[:0] + 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() + r.QIDs = r.QIDs[:0] + 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 + + // Mode is the device mode and permissions. + Mode 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.Mode = b.ReadFileMode() + 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.WriteFileMode(t.Mode) + 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, Mode: 0o%o, Major: %d, Minor: %d, GID: %d}", t.Directory, t.Name, t.Mode, 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{}") +} + +// Tallocate is an allocate request. This is an extension to 9P protocol, not +// present in the 9P2000.L standard. +type Tallocate struct { + FID FID + Mode AllocateMode + Offset uint64 + Length uint64 +} + +// Decode implements encoder.Decode. +func (t *Tallocate) Decode(b *buffer) { + t.FID = b.ReadFID() + t.Mode.Decode(b) + t.Offset = b.Read64() + t.Length = b.Read64() +} + +// Encode implements encoder.Encode. +func (t *Tallocate) Encode(b *buffer) { + b.WriteFID(t.FID) + t.Mode.Encode(b) + b.Write64(t.Offset) + b.Write64(t.Length) +} + +// Type implements message.Type. +func (*Tallocate) Type() MsgType { + return MsgTallocate +} + +// String implements fmt.Stringer. +func (t *Tallocate) String() string { + return fmt.Sprintf("Tallocate{FID: %d, Offset: %d, Length: %d}", t.FID, t.Offset, t.Length) +} + +// Rallocate is an allocate response. +type Rallocate struct { +} + +// Decode implements encoder.Decode. +func (*Rallocate) Decode(b *buffer) { +} + +// Encode implements encoder.Encode. +func (*Rallocate) Encode(b *buffer) { +} + +// Type implements message.Type. +func (*Rallocate) Type() MsgType { + return MsgRallocate +} + +// String implements fmt.Stringer. +func (r *Rallocate) String() string { + return fmt.Sprintf("Rallocate{}") +} + +// 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} + r.Entries = r.Entries[:0] + 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() + t.Names = t.Names[:0] + 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() + r.QIDs = r.QIDs[:0] + 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) +} + +const maxCacheSize = 3 + +// msgFactory is used to reduce allocations by caching messages for reuse. +type msgFactory struct { + create func() message + cache chan message +} + +// msgRegistry indexes all message factories by type. +var msgRegistry registry + +type registry struct { + factories [math.MaxUint8]msgFactory + + // largestFixedSize 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. + largestFixedSize uint32 +} + +// get returns 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 (r *registry) get(_ Tag, t MsgType) (message, error) { + entry := &r.factories[t] + if entry.create == nil { + return nil, &ErrInvalidMsgType{t} + } + + select { + case msg := <-entry.cache: + return msg, nil + default: + return entry.create(), nil + } +} + +func (r *registry) put(msg message) { + if p, ok := msg.(payloader); ok { + p.SetPayload(nil) + } + if f, ok := msg.(filer); ok { + f.SetFilePayload(nil) + } + + entry := &r.factories[msg.Type()] + select { + case entry.cache <- msg: + default: + } +} + +// register registers the given message type. +// +// This may cause panic on failure and should only be used from init. +func (r *registry) register(t MsgType, fn func() message) { + if int(t) >= len(r.factories) { + panic(fmt.Sprintf("message type %d is too large. It must be smaller than %d", t, len(r.factories))) + } + if r.factories[t].create != nil { + panic(fmt.Sprintf("duplicate message type %d: first is %T, second is %T", t, r.factories[t].create(), fn())) + } + r.factories[t] = msgFactory{ + create: fn, + cache: make(chan message, maxCacheSize), + } + + if size := calculateSize(fn()); size > r.largestFixedSize { + r.largestFixedSize = size + } +} + +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)) +} + +func init() { + msgRegistry.register(MsgRlerror, func() message { return &Rlerror{} }) + msgRegistry.register(MsgTstatfs, func() message { return &Tstatfs{} }) + msgRegistry.register(MsgRstatfs, func() message { return &Rstatfs{} }) + msgRegistry.register(MsgTlopen, func() message { return &Tlopen{} }) + msgRegistry.register(MsgRlopen, func() message { return &Rlopen{} }) + msgRegistry.register(MsgTlcreate, func() message { return &Tlcreate{} }) + msgRegistry.register(MsgRlcreate, func() message { return &Rlcreate{} }) + msgRegistry.register(MsgTsymlink, func() message { return &Tsymlink{} }) + msgRegistry.register(MsgRsymlink, func() message { return &Rsymlink{} }) + msgRegistry.register(MsgTmknod, func() message { return &Tmknod{} }) + msgRegistry.register(MsgRmknod, func() message { return &Rmknod{} }) + msgRegistry.register(MsgTrename, func() message { return &Trename{} }) + msgRegistry.register(MsgRrename, func() message { return &Rrename{} }) + msgRegistry.register(MsgTreadlink, func() message { return &Treadlink{} }) + msgRegistry.register(MsgRreadlink, func() message { return &Rreadlink{} }) + msgRegistry.register(MsgTgetattr, func() message { return &Tgetattr{} }) + msgRegistry.register(MsgRgetattr, func() message { return &Rgetattr{} }) + msgRegistry.register(MsgTsetattr, func() message { return &Tsetattr{} }) + msgRegistry.register(MsgRsetattr, func() message { return &Rsetattr{} }) + msgRegistry.register(MsgTxattrwalk, func() message { return &Txattrwalk{} }) + msgRegistry.register(MsgRxattrwalk, func() message { return &Rxattrwalk{} }) + msgRegistry.register(MsgTxattrcreate, func() message { return &Txattrcreate{} }) + msgRegistry.register(MsgRxattrcreate, func() message { return &Rxattrcreate{} }) + msgRegistry.register(MsgTreaddir, func() message { return &Treaddir{} }) + msgRegistry.register(MsgRreaddir, func() message { return &Rreaddir{} }) + msgRegistry.register(MsgTfsync, func() message { return &Tfsync{} }) + msgRegistry.register(MsgRfsync, func() message { return &Rfsync{} }) + msgRegistry.register(MsgTlink, func() message { return &Tlink{} }) + msgRegistry.register(MsgRlink, func() message { return &Rlink{} }) + msgRegistry.register(MsgTmkdir, func() message { return &Tmkdir{} }) + msgRegistry.register(MsgRmkdir, func() message { return &Rmkdir{} }) + msgRegistry.register(MsgTrenameat, func() message { return &Trenameat{} }) + msgRegistry.register(MsgRrenameat, func() message { return &Rrenameat{} }) + msgRegistry.register(MsgTunlinkat, func() message { return &Tunlinkat{} }) + msgRegistry.register(MsgRunlinkat, func() message { return &Runlinkat{} }) + msgRegistry.register(MsgTversion, func() message { return &Tversion{} }) + msgRegistry.register(MsgRversion, func() message { return &Rversion{} }) + msgRegistry.register(MsgTauth, func() message { return &Tauth{} }) + msgRegistry.register(MsgRauth, func() message { return &Rauth{} }) + msgRegistry.register(MsgTattach, func() message { return &Tattach{} }) + msgRegistry.register(MsgRattach, func() message { return &Rattach{} }) + msgRegistry.register(MsgTflush, func() message { return &Tflush{} }) + msgRegistry.register(MsgRflush, func() message { return &Rflush{} }) + msgRegistry.register(MsgTwalk, func() message { return &Twalk{} }) + msgRegistry.register(MsgRwalk, func() message { return &Rwalk{} }) + msgRegistry.register(MsgTread, func() message { return &Tread{} }) + msgRegistry.register(MsgRread, func() message { return &Rread{} }) + msgRegistry.register(MsgTwrite, func() message { return &Twrite{} }) + msgRegistry.register(MsgRwrite, func() message { return &Rwrite{} }) + msgRegistry.register(MsgTclunk, func() message { return &Tclunk{} }) + msgRegistry.register(MsgRclunk, func() message { return &Rclunk{} }) + msgRegistry.register(MsgTremove, func() message { return &Tremove{} }) + msgRegistry.register(MsgRremove, func() message { return &Rremove{} }) + msgRegistry.register(MsgTflushf, func() message { return &Tflushf{} }) + msgRegistry.register(MsgRflushf, func() message { return &Rflushf{} }) + msgRegistry.register(MsgTwalkgetattr, func() message { return &Twalkgetattr{} }) + msgRegistry.register(MsgRwalkgetattr, func() message { return &Rwalkgetattr{} }) + msgRegistry.register(MsgTucreate, func() message { return &Tucreate{} }) + msgRegistry.register(MsgRucreate, func() message { return &Rucreate{} }) + msgRegistry.register(MsgTumkdir, func() message { return &Tumkdir{} }) + msgRegistry.register(MsgRumkdir, func() message { return &Rumkdir{} }) + msgRegistry.register(MsgTumknod, func() message { return &Tumknod{} }) + msgRegistry.register(MsgRumknod, func() message { return &Rumknod{} }) + msgRegistry.register(MsgTusymlink, func() message { return &Tusymlink{} }) + msgRegistry.register(MsgRusymlink, func() message { return &Rusymlink{} }) + msgRegistry.register(MsgTlconnect, func() message { return &Tlconnect{} }) + msgRegistry.register(MsgRlconnect, func() message { return &Rlconnect{} }) + msgRegistry.register(MsgTallocate, func() message { return &Tallocate{} }) + msgRegistry.register(MsgRallocate, func() message { return &Rallocate{} }) +} diff --git a/pkg/p9/p9.go b/pkg/p9/p9.go new file mode 100644 index 000000000..4039862e6 --- /dev/null +++ b/pkg/p9/p9.go @@ -0,0 +1,1141 @@ +// Copyright 2018 The gVisor Authors. +// +// 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" + + "golang.org/x/sys/unix" +) + +// 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 + + // OpenFlagsIgnoreMask is a list of OpenFlags mode bits that are ignored for Tlopen. + // Note that syscall.O_LARGEFILE is set to zero, use value from Linux fcntl.h. + OpenFlagsIgnoreMask OpenFlags = syscall.O_DIRECTORY | syscall.O_NOATIME | 0100000 +) + +// 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" + case OpenFlagsIgnoreMask: + return "OpenFlagsIgnoreMask" + 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 + + // AllPermissions is a mask with rwx bits set for user, group and others. + AllPermissions FileMode = 0777 + + // Sticky is a mode bit indicating sticky directories. + Sticky FileMode = 01000 + + // permissionsMask is the mask to apply to FileModes for permissions. It + // includes rwx bits for user, group and others, and sticky bit. + permissionsMask FileMode = 01777 +) + +// 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 + MsgTallocate = 138 + MsgRallocate = 139 +) + +// 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) +} + +// Apply applies this to the given Attr. +func (a *Attr) Apply(mask SetAttrMask, attr SetAttr) { + if mask.Permissions { + a.Mode = a.Mode&^permissionsMask | (attr.Permissions & permissionsMask) + } + if mask.UID { + a.UID = attr.UID + } + if mask.GID { + a.GID = attr.GID + } + if mask.Size { + a.Size = attr.Size + } + if mask.ATime { + a.ATimeSeconds = attr.ATimeSeconds + a.ATimeNanoSeconds = attr.ATimeNanoSeconds + } + if mask.MTime { + a.MTimeSeconds = attr.MTimeSeconds + a.MTimeNanoSeconds = attr.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) +} + +// AllocateMode are possible modes to p9.File.Allocate(). +type AllocateMode struct { + KeepSize bool + PunchHole bool + NoHideStale bool + CollapseRange bool + ZeroRange bool + InsertRange bool + Unshare bool +} + +// ToLinux converts to a value compatible with fallocate(2)'s mode. +func (a *AllocateMode) ToLinux() uint32 { + rv := uint32(0) + if a.KeepSize { + rv |= unix.FALLOC_FL_KEEP_SIZE + } + if a.PunchHole { + rv |= unix.FALLOC_FL_PUNCH_HOLE + } + if a.NoHideStale { + rv |= unix.FALLOC_FL_NO_HIDE_STALE + } + if a.CollapseRange { + rv |= unix.FALLOC_FL_COLLAPSE_RANGE + } + if a.ZeroRange { + rv |= unix.FALLOC_FL_ZERO_RANGE + } + if a.InsertRange { + rv |= unix.FALLOC_FL_INSERT_RANGE + } + if a.Unshare { + rv |= unix.FALLOC_FL_UNSHARE_RANGE + } + return rv +} + +// Decode implements encoder.Decode. +func (a *AllocateMode) Decode(b *buffer) { + mask := b.Read32() + a.KeepSize = mask&0x01 != 0 + a.PunchHole = mask&0x02 != 0 + a.NoHideStale = mask&0x04 != 0 + a.CollapseRange = mask&0x08 != 0 + a.ZeroRange = mask&0x10 != 0 + a.InsertRange = mask&0x20 != 0 + a.Unshare = mask&0x40 != 0 +} + +// Encode implements encoder.Encode. +func (a *AllocateMode) Encode(b *buffer) { + mask := uint32(0) + if a.KeepSize { + mask |= 0x01 + } + if a.PunchHole { + mask |= 0x02 + } + if a.NoHideStale { + mask |= 0x04 + } + if a.CollapseRange { + mask |= 0x08 + } + if a.ZeroRange { + mask |= 0x10 + } + if a.InsertRange { + mask |= 0x20 + } + if a.Unshare { + mask |= 0x40 + } + b.Write32(mask) +} diff --git a/pkg/p9/p9_state_autogen.go b/pkg/p9/p9_state_autogen.go new file mode 100755 index 000000000..0b9556862 --- /dev/null +++ b/pkg/p9/p9_state_autogen.go @@ -0,0 +1,4 @@ +// automatically generated by stateify. + +package p9 + diff --git a/pkg/p9/path_tree.go b/pkg/p9/path_tree.go new file mode 100644 index 000000000..f37ad4ab2 --- /dev/null +++ b/pkg/p9/path_tree.go @@ -0,0 +1,109 @@ +// Copyright 2018 The gVisor Authors. +// +// 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" + "sync" +) + +// pathNode is a single node in a path traversal. +// +// These are shared by all fidRefs that point to the same path. +// +// These are not synchronized because we allow certain operations (file walk) +// to proceed without having to acquire a write lock. The lock in this +// structure exists to synchronize high-level, semantic operations, such as the +// simultaneous creation and deletion of a file. +// +// (+) below is the path component string. +type pathNode struct { + mu sync.RWMutex // See above. + fidRefs sync.Map // => map[*fidRef]string(+) + children sync.Map // => map[string(+)]*pathNode + count int64 +} + +// pathNodeFor returns the path node for the given name, or a new one. +// +// Precondition: mu must be held in a readable fashion. +func (p *pathNode) pathNodeFor(name string) *pathNode { + // Load the existing path node. + if pn, ok := p.children.Load(name); ok { + return pn.(*pathNode) + } + + // Create a new pathNode for shared use. + pn, _ := p.children.LoadOrStore(name, new(pathNode)) + return pn.(*pathNode) +} + +// nameFor returns the name for the given fidRef. +// +// Precondition: mu must be held in a readable fashion. +func (p *pathNode) nameFor(ref *fidRef) string { + if s, ok := p.fidRefs.Load(ref); ok { + return s.(string) + } + + // This should not happen, don't proceed. + panic(fmt.Sprintf("expected name for %+v, none found", ref)) +} + +// addChild adds a child to the given pathNode. +// +// This applies only to an individual fidRef. +// +// Precondition: mu must be held in a writable fashion. +func (p *pathNode) addChild(ref *fidRef, name string) { + if s, ok := p.fidRefs.Load(ref); ok { + // This should not happen, don't proceed. + panic(fmt.Sprintf("unexpected fidRef %+v with path %q, wanted %q", ref, s, name)) + } + + p.fidRefs.Store(ref, name) +} + +// removeChild removes the given child. +// +// This applies only to an individual fidRef. +// +// Precondition: mu must be held in a writable fashion. +func (p *pathNode) removeChild(ref *fidRef) { + p.fidRefs.Delete(ref) +} + +// removeWithName removes all references with the given name. +// +// The original pathNode is returned by this function, and removed from this +// pathNode. Any operations on the removed tree must use this value. +// +// The provided function is executed after removal. +// +// Precondition: mu must be held in a writable fashion. +func (p *pathNode) removeWithName(name string, fn func(ref *fidRef)) *pathNode { + p.fidRefs.Range(func(key, value interface{}) bool { + if value.(string) == name { + p.fidRefs.Delete(key) + fn(key.(*fidRef)) + } + return true + }) + + // Return the original path node. + origPathNode := p.pathNodeFor(name) + p.children.Delete(name) + return origPathNode +} diff --git a/pkg/p9/pool.go b/pkg/p9/pool.go new file mode 100644 index 000000000..52de889e1 --- /dev/null +++ b/pkg/p9/pool.go @@ -0,0 +1,68 @@ +// Copyright 2018 The gVisor Authors. +// +// 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/server.go b/pkg/p9/server.go new file mode 100644 index 000000000..f377a6557 --- /dev/null +++ b/pkg/p9/server.go @@ -0,0 +1,575 @@ +// Copyright 2018 The gVisor Authors. +// +// 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" + "runtime/debug" + "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 + + // pathTree is the full set of paths opened on this server. + // + // These may be across different connections, but rename operations + // must be serialized globally for safely. There is a single pathTree + // for the entire server, and not per connection. + pathTree pathNode + + // renameMu is a global lock protecting rename operations. With this + // lock, we can be certain that any given rename operation can safely + // acquire two path nodes in any order, as all other concurrent + // operations acquire at most a single node. + renameMu sync.RWMutex +} + +// 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 { + // server is the associated server. + server *Server + + // 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 + + // mode is the fidRef's mode from the walk. Only the type bits are + // valid, the permissions may change. This is used to sanity check + // operations on this element, and prevent walks across + // non-directories. + mode FileMode + + // openFlags is the mode used in the open. + // + // This is updated in handlers.go. + openFlags OpenFlags + + // pathNode is the current pathNode for this FID. + pathNode *pathNode + + // parent is the parent fidRef. We hold on to a parent reference to + // ensure that hooks, such as Renamed, can be executed safely by the + // server code. + // + // Note that parent cannot be changed without holding both the global + // rename lock and a writable lock on the associated pathNode for this + // fidRef. Holding either of these locks is sufficient to examine + // parent safely. + // + // The parent will be nil for root fidRefs, and non-nil otherwise. The + // method maybeParent can be used to return a cyclical reference, and + // isRoot should be used to check for root over looking at parent + // directly. + parent *fidRef + + // deleted indicates that the backing file has been deleted. We stop + // many operations at the API level if they are incompatible with a + // file that has already been unlinked. + deleted uint32 +} + +// 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 +} + +// IncRef increases the references on a fid. +func (f *fidRef) IncRef() { + atomic.AddInt64(&f.refs, 1) +} + +// 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() + + // Drop the parent reference. + // + // Since this fidRef is guaranteed to be non-discoverable when + // the references reach zero, we don't need to worry about + // clearing the parent. + if f.parent != nil { + // If we've been previously deleted, this removing this + // ref is a no-op. That's expected. + f.parent.pathNode.removeChild(f) + f.parent.DecRef() + } + } +} + +// isDeleted returns true if this fidRef has been deleted. +func (f *fidRef) isDeleted() bool { + return atomic.LoadUint32(&f.deleted) != 0 +} + +// isRoot indicates whether this is a root fid. +func (f *fidRef) isRoot() bool { + return f.parent == nil +} + +// maybeParent returns a cyclic reference for roots, and the parent otherwise. +func (f *fidRef) maybeParent() *fidRef { + if f.parent != nil { + return f.parent + } + return f // Root has itself. +} + +// notifyDelete marks all fidRefs as deleted. +// +// Precondition: the write lock must be held on the given pathNode. +func notifyDelete(pn *pathNode) { + // Call on all local references. + pn.fidRefs.Range(func(key, _ interface{}) bool { + ref := key.(*fidRef) + atomic.StoreUint32(&ref.deleted, 1) + return true + }) + + // Call on all subtrees. + pn.children.Range(func(_, value interface{}) bool { + notifyDelete(value.(*pathNode)) + return true + }) +} + +// markChildDeleted marks all children below the given name as deleted. +// +// Precondition: this must be called via safelyWrite or safelyGlobal. +func (f *fidRef) markChildDeleted(name string) { + origPathNode := f.pathNode.removeWithName(name, func(ref *fidRef) { + atomic.StoreUint32(&ref.deleted, 1) + }) + + // Mark everything below as deleted. + notifyDelete(origPathNode) +} + +// notifyNameChange calls the relevant Renamed method on all nodes in the path, +// recursively. Note that this applies only for subtrees, as these +// notifications do not apply to the actual file whose name has changed. +// +// Precondition: the write lock must be held on the given pathNode. +func notifyNameChange(pn *pathNode) { + // Call on all local references. + pn.fidRefs.Range(func(key, value interface{}) bool { + ref := key.(*fidRef) + name := value.(string) + ref.file.Renamed(ref.parent.file, name) + return true + }) + + // Call on all subtrees. + pn.children.Range(func(_, value interface{}) bool { + notifyNameChange(value.(*pathNode)) + return true + }) +} + +// renameChildTo renames the given child to the target. +// +// Precondition: this must be called via safelyGlobal. +func (f *fidRef) renameChildTo(oldName string, target *fidRef, newName string) { + target.markChildDeleted(newName) + origPathNode := f.pathNode.removeWithName(oldName, func(ref *fidRef) { + ref.parent.DecRef() // Drop original reference. + ref.parent = target // Change parent. + ref.parent.IncRef() // Acquire new one. + target.pathNode.addChild(ref, newName) + ref.file.Renamed(target.file, newName) + }) + + // Replace the previous (now deleted) path node. + f.pathNode.children.Store(newName, origPathNode) + + // Call Renamed on everything above. + notifyNameChange(origPathNode) +} + +// safelyRead executes the given operation with the local path node locked. +// This implies that paths will not change during the operation. +func (f *fidRef) safelyRead(fn func() error) (err error) { + f.server.renameMu.RLock() + defer f.server.renameMu.RUnlock() + f.pathNode.mu.RLock() + defer f.pathNode.mu.RUnlock() + return fn() +} + +// safelyWrite executes the given operation with the local path node locked in +// a writable fashion. This implies some paths may change. +func (f *fidRef) safelyWrite(fn func() error) (err error) { + f.server.renameMu.RLock() + defer f.server.renameMu.RUnlock() + f.pathNode.mu.Lock() + defer f.pathNode.mu.Unlock() + return fn() +} + +// safelyGlobal executes the given operation with the global path lock held. +func (f *fidRef) safelyGlobal(fn func() error) (err error) { + f.server.renameMu.Lock() + defer f.server.renameMu.Unlock() + return fn() +} + +// 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 { + fidRef.IncRef() + 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() + } + newRef.IncRef() + 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, msgRegistry.get) + 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 && err != io.EOF { + // If it's not a connection error, but some other protocol error, + // we can send a response immediately. + 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. + log.Debugf("no valid tag [%05d]", tag) + cs.sendDone <- ErrNoValidMessage + return + } + + // Handle the message. + var r message // r is the response. + defer func() { + if r == nil { + // Don't allow a panic to propagate. + recover() + + // Include a useful log message. + log.Warningf("panic in handler: %s", debug.Stack()) + + // Wrap in an EFAULT error; we don't really have a + // better way to describe this kind of error. It will + // usually manifest as a result of the test framework. + r = newErr(syscall.EFAULT) + } + + // 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 + }() + if handler, ok := m.(handler); ok { + // Call the message handler. + r = handler.handle(cs) + } else { + // Produce an ENOSYS error. + r = newErr(syscall.ENOSYS) + } + msgRegistry.put(m) + m = nil // 'm' should not be touched after this point. +} + +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..ef59077ff --- /dev/null +++ b/pkg/p9/transport.go @@ -0,0 +1,342 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 && (n == 0 || err != io.EOF) { + 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 && (cur == 0 || err != io.EOF) { + return NoTag, nil, ErrSocket{err} + } + 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, ErrSocket{ErrNoValidMessage} + } + if size > maximumLength || size > msize { + // The message is too big. + return NoTag, nil, ErrSocket{&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 && (cur == 0 || err != io.EOF) { + return NoTag, nil, 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 + } + } + } + } + + // 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/version.go b/pkg/p9/version.go new file mode 100644 index 000000000..c2a2885ae --- /dev/null +++ b/pkg/p9/version.go @@ -0,0 +1,150 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 = 7 + + // 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 +} + +// versionSupportsTallocate returns true if version v supports Allocate(). +func versionSupportsTallocate(v uint32) bool { + return v >= 7 +} |