//go:build linux || dragonfly || solaris
// +build linux dragonfly solaris
package internal
import (
"os"
"syscall"
"golang.org/x/sys/unix"
)
// InfoToStat takes a platform native FileInfo and converts it into a 9P2000.L compatible Stat_t
func InfoToStat(fi os.FileInfo) *Stat_t {
nativeStat := fi.Sys().(*syscall.Stat_t)
return &Stat_t{
Dev: nativeStat.Dev,
Ino: nativeStat.Ino,
Nlink: nativeStat.Nlink,
Mode: nativeStat.Mode,
Uid: nativeStat.Uid,
Gid: nativeStat.Gid,
Rdev: nativeStat.Rdev,
Size: nativeStat.Size,
Blksize: nativeStat.Blksize,
Blocks: nativeStat.Blocks,
Atim: unix.NsecToTimespec(syscall.TimespecToNsec(nativeStat.Atim)),
Mtim: unix.NsecToTimespec(syscall.TimespecToNsec(nativeStat.Mtim)),
Ctim: unix.NsecToTimespec(syscall.TimespecToNsec(nativeStat.Ctim)),
}
}
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package linux
import "fmt"
// Errno is a Linux error number on every GOOS.
type Errno uintptr
func (e Errno) Error() string {
if 0 <= int(e) && int(e) < len(errorTable) {
s := errorTable[e]
if s != "" {
return s
}
}
return fmt.Sprintf("errno %d", int(e))
}
// numbers defined on Linux/amd64.
const (
E2BIG = Errno(0x7)
EACCES = Errno(0xd)
EADDRINUSE = Errno(0x62)
EADDRNOTAVAIL = Errno(0x63)
EADV = Errno(0x44)
EAFNOSUPPORT = Errno(0x61)
EAGAIN = Errno(0xb)
EALREADY = Errno(0x72)
EBADE = Errno(0x34)
EBADF = Errno(0x9)
EBADFD = Errno(0x4d)
EBADMSG = Errno(0x4a)
EBADR = Errno(0x35)
EBADRQC = Errno(0x38)
EBADSLT = Errno(0x39)
EBFONT = Errno(0x3b)
EBUSY = Errno(0x10)
ECANCELED = Errno(0x7d)
ECHILD = Errno(0xa)
ECHRNG = Errno(0x2c)
ECOMM = Errno(0x46)
ECONNABORTED = Errno(0x67)
ECONNREFUSED = Errno(0x6f)
ECONNRESET = Errno(0x68)
EDEADLK = Errno(0x23)
EDEADLOCK = Errno(0x23)
EDESTADDRREQ = Errno(0x59)
EDOM = Errno(0x21)
EDOTDOT = Errno(0x49)
EDQUOT = Errno(0x7a)
EEXIST = Errno(0x11)
EFAULT = Errno(0xe)
EFBIG = Errno(0x1b)
EHOSTDOWN = Errno(0x70)
EHOSTUNREACH = Errno(0x71)
EHWPOISON = Errno(0x85)
EIDRM = Errno(0x2b)
EILSEQ = Errno(0x54)
EINPROGRESS = Errno(0x73)
EINTR = Errno(0x4)
EINVAL = Errno(0x16)
EIO = Errno(0x5)
EISCONN = Errno(0x6a)
EISDIR = Errno(0x15)
EISNAM = Errno(0x78)
EKEYEXPIRED = Errno(0x7f)
EKEYREJECTED = Errno(0x81)
EKEYREVOKED = Errno(0x80)
EL2HLT = Errno(0x33)
EL2NSYNC = Errno(0x2d)
EL3HLT = Errno(0x2e)
EL3RST = Errno(0x2f)
ELIBACC = Errno(0x4f)
ELIBBAD = Errno(0x50)
ELIBEXEC = Errno(0x53)
ELIBMAX = Errno(0x52)
ELIBSCN = Errno(0x51)
ELNRNG = Errno(0x30)
ELOOP = Errno(0x28)
EMEDIUMTYPE = Errno(0x7c)
EMFILE = Errno(0x18)
EMLINK = Errno(0x1f)
EMSGSIZE = Errno(0x5a)
EMULTIHOP = Errno(0x48)
ENAMETOOLONG = Errno(0x24)
ENAVAIL = Errno(0x77)
ENETDOWN = Errno(0x64)
ENETRESET = Errno(0x66)
ENETUNREACH = Errno(0x65)
ENFILE = Errno(0x17)
ENOANO = Errno(0x37)
ENOBUFS = Errno(0x69)
ENOCSI = Errno(0x32)
ENODATA = Errno(0x3d)
ENODEV = Errno(0x13)
ENOENT = Errno(0x2)
ENOEXEC = Errno(0x8)
ENOKEY = Errno(0x7e)
ENOLCK = Errno(0x25)
ENOLINK = Errno(0x43)
ENOMEDIUM = Errno(0x7b)
ENOMEM = Errno(0xc)
ENOMSG = Errno(0x2a)
ENONET = Errno(0x40)
ENOPKG = Errno(0x41)
ENOPROTOOPT = Errno(0x5c)
ENOSPC = Errno(0x1c)
ENOSR = Errno(0x3f)
ENOSTR = Errno(0x3c)
ENOSYS = Errno(0x26)
ENOTBLK = Errno(0xf)
ENOTCONN = Errno(0x6b)
ENOTDIR = Errno(0x14)
ENOTEMPTY = Errno(0x27)
ENOTNAM = Errno(0x76)
ENOTRECOVERABLE = Errno(0x83)
ENOTSOCK = Errno(0x58)
ENOTSUP = Errno(0x5f)
ENOTTY = Errno(0x19)
ENOTUNIQ = Errno(0x4c)
ENXIO = Errno(0x6)
EOPNOTSUPP = Errno(0x5f)
EOVERFLOW = Errno(0x4b)
EOWNERDEAD = Errno(0x82)
EPERM = Errno(0x1)
EPFNOSUPPORT = Errno(0x60)
EPIPE = Errno(0x20)
EPROTO = Errno(0x47)
EPROTONOSUPPORT = Errno(0x5d)
EPROTOTYPE = Errno(0x5b)
ERANGE = Errno(0x22)
EREMCHG = Errno(0x4e)
EREMOTE = Errno(0x42)
EREMOTEIO = Errno(0x79)
ERESTART = Errno(0x55)
ERFKILL = Errno(0x84)
EROFS = Errno(0x1e)
ESHUTDOWN = Errno(0x6c)
ESOCKTNOSUPPORT = Errno(0x5e)
ESPIPE = Errno(0x1d)
ESRCH = Errno(0x3)
ESRMNT = Errno(0x45)
ESTALE = Errno(0x74)
ESTRPIPE = Errno(0x56)
ETIME = Errno(0x3e)
ETIMEDOUT = Errno(0x6e)
ETOOMANYREFS = Errno(0x6d)
ETXTBSY = Errno(0x1a)
EUCLEAN = Errno(0x75)
EUNATCH = Errno(0x31)
EUSERS = Errno(0x57)
EWOULDBLOCK = Errno(0xb)
EXDEV = Errno(0x12)
EXFULL = Errno(0x36)
)
var errorTable = [...]string{
1: "operation not permitted",
2: "no such file or directory",
3: "no such process",
4: "interrupted system call",
5: "input/output error",
6: "no such device or address",
7: "argument list too long",
8: "exec format error",
9: "bad file descriptor",
10: "no child processes",
11: "resource temporarily unavailable",
12: "cannot allocate memory",
13: "permission denied",
14: "bad address",
15: "block device required",
16: "device or resource busy",
17: "file exists",
18: "invalid cross-device link",
19: "no such device",
20: "not a directory",
21: "is a directory",
22: "invalid argument",
23: "too many open files in system",
24: "too many open files",
25: "inappropriate ioctl for device",
26: "text file busy",
27: "file too large",
28: "no space left on device",
29: "illegal seek",
30: "read-only file system",
31: "too many links",
32: "broken pipe",
33: "numerical argument out of domain",
34: "numerical result out of range",
35: "resource deadlock avoided",
36: "file name too long",
37: "no locks available",
38: "function not implemented",
39: "directory not empty",
40: "too many levels of symbolic links",
42: "no message of desired type",
43: "identifier removed",
44: "channel number out of range",
45: "level 2 not synchronized",
46: "level 3 halted",
47: "level 3 reset",
48: "link number out of range",
49: "protocol driver not attached",
50: "no CSI structure available",
51: "level 2 halted",
52: "invalid exchange",
53: "invalid request descriptor",
54: "exchange full",
55: "no anode",
56: "invalid request code",
57: "invalid slot",
59: "bad font file format",
60: "device not a stream",
61: "no data available",
62: "timer expired",
63: "out of streams resources",
64: "machine is not on the network",
65: "package not installed",
66: "object is remote",
67: "link has been severed",
68: "advertise error",
69: "srmount error",
70: "communication error on send",
71: "protocol error",
72: "multihop attempted",
73: "RFS specific error",
74: "bad message",
75: "value too large for defined data type",
76: "name not unique on network",
77: "file descriptor in bad state",
78: "remote address changed",
79: "can not access a needed shared library",
80: "accessing a corrupted shared library",
81: ".lib section in a.out corrupted",
82: "attempting to link in too many shared libraries",
83: "cannot exec a shared library directly",
84: "invalid or incomplete multibyte or wide character",
85: "interrupted system call should be restarted",
86: "streams pipe error",
87: "too many users",
88: "socket operation on non-socket",
89: "destination address required",
90: "message too long",
91: "protocol wrong type for socket",
92: "protocol not available",
93: "protocol not supported",
94: "socket type not supported",
95: "operation not supported",
96: "protocol family not supported",
97: "address family not supported by protocol",
98: "address already in use",
99: "cannot assign requested address",
100: "network is down",
101: "network is unreachable",
102: "network dropped connection on reset",
103: "software caused connection abort",
104: "connection reset by peer",
105: "no buffer space available",
106: "transport endpoint is already connected",
107: "transport endpoint is not connected",
108: "cannot send after transport endpoint shutdown",
109: "too many references: cannot splice",
110: "connection timed out",
111: "connection refused",
112: "host is down",
113: "no route to host",
114: "operation already in progress",
115: "operation now in progress",
116: "stale NFS file handle",
117: "structure needs cleaning",
118: "not a XENIX named type file",
119: "no XENIX semaphores available",
120: "is a named type file",
121: "remote I/O error",
122: "disk quota exceeded",
123: "no medium found",
124: "wrong medium type",
125: "operation canceled",
126: "required key not available",
127: "key has expired",
128: "key has been revoked",
129: "key was rejected by service",
130: "owner died",
131: "state not recoverable",
132: "operation not possible due to RF-kill",
}
package linux
import (
"errors"
"os"
)
// ExtractErrno extracts an [Errno] from an error, best effort.
//
// If the system-specific or Go-specific error cannot be mapped to anything, it
// will be logged and EIO will be returned.
func ExtractErrno(err error) Errno {
for _, pair := range []struct {
error
Errno
}{
{os.ErrNotExist, ENOENT},
{os.ErrExist, EEXIST},
{os.ErrPermission, EACCES},
{os.ErrInvalid, EINVAL},
} {
if errors.Is(err, pair.error) {
return pair.Errno
}
}
var errno Errno
if errors.As(err, &errno) {
return errno
}
if e := sysErrno(err); e != 0 {
return e
}
// Default case.
return EIO
}
//go:build linux
// +build linux
package linux
import (
"errors"
"syscall"
)
func sysErrno(err error) Errno {
var systemErr syscall.Errno
if errors.As(err, &systemErr) {
return Errno(systemErr)
}
return 0
}
// 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())
}
// 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))
}
// 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]))
}
}
// 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"
"sync"
"github.com/hugelgupf/p9/linux"
"github.com/u-root/uio/ulog"
)
// 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 {
// conn is the connected conn.
conn io.ReadWriteCloser
// 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
// log is the logger to write to, if specified.
log ulog.Logger
}
// ClientOpt enables optional client configuration.
type ClientOpt func(*Client) error
// WithMessageSize overrides the default message size.
func WithMessageSize(m uint32) ClientOpt {
return func(c *Client) error {
// Need at least one byte of payload.
if m <= msgDotLRegistry.largestFixedSize {
return &ErrMessageTooLarge{
size: m,
msize: msgDotLRegistry.largestFixedSize,
}
}
c.messageSize = m
return nil
}
}
// WithClientLogger overrides the default logger for the client.
func WithClientLogger(l ulog.Logger) ClientOpt {
return func(c *Client) error {
c.log = l
return nil
}
}
func roundDown(p uint32, align uint32) uint32 {
if p > align && p%align != 0 {
return p - p%align
}
return p
}
// 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 conn for multiple clients.
func NewClient(conn io.ReadWriteCloser, o ...ClientOpt) (*Client, error) {
c := &Client{
conn: conn,
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: DefaultMessageSize,
log: ulog.Null,
// Request a high version by default.
version: highestSupportedVersion,
}
for _, opt := range o {
if err := opt(c); err != nil {
return nil, err
}
}
// Compute a payload size and round to 512 (normal block size)
// if it's larger than a single block.
c.payloadSize = roundDown(c.messageSize-msgDotLRegistry.largestFixedSize, 512)
// Agree upon a version.
requested := c.version
for {
rversion := rversion{}
err := c.sendRecv(&tversion{Version: versionString(version9P2000L, requested), MSize: c.messageSize}, &rversion)
// The server told us to try again with a lower version.
if errors.Is(err, linux.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.
baseVersion, version, ok := parseVersion(rversion.Version)
if !ok {
// The server gave us a bad version. We return a generically worrisome error.
c.log.Printf("server returned bad version string %q", rversion.Version)
return nil, ErrBadVersionString
}
if baseVersion != version9P2000L {
c.log.Printf("server returned unsupported base version %q (version %q)", baseVersion, 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() {
t, r, err := recv(c.log, c.conn, c.messageSize, func(t tag, mt msgType) (message, error) {
c.pendingMu.Lock()
resp := c.pending[t]
c.pendingMu.Unlock()
// Not expecting this message?
if resp == nil {
c.log.Printf("client received unexpected tag %v, ignoring", t)
return nil, ErrUnexpectedTag
}
// Is it an error? We specifically allow this to
// go through, and then we deserialize below.
if mt == msgRlerror {
return &rlerror{}, nil
}
// Does it match expectations?
if mt != resp.r.typ() {
return nil, &ErrBadResponse{Got: mt, Want: resp.r.typ()}
}
// Return the response.
return resp.r, nil
})
if err != nil {
// No tag was extracted (probably a conn 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[t]
delete(c.pending, t)
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(tm message, rm message) error {
t, ok := c.tagPool.Get()
if !ok {
return ErrOutOfTags
}
defer c.tagPool.Put(t)
// 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 = rm
c.pendingMu.Lock()
c.pending[tag(t)] = resp
c.pendingMu.Unlock()
// Send the request over the wire.
c.sendMu.Lock()
err := send(c.log, c.conn, tag(t), tm)
c.sendMu.Unlock()
if err != nil {
return fmt.Errorf("send: %w", err)
}
// Co-ordinate with other receivers.
if err := c.waitAndRecv(resp.done); err != nil {
return fmt.Errorf("wait: %w", 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 linux.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 connection.
func (c *Client) Close() error {
return c.conn.Close()
}
// 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"
"github.com/hugelgupf/p9/linux"
)
// Attach attaches to a server.
//
// Note that authentication is not currently supported.
func (c *Client) Attach(name string) (File, error) {
id, ok := c.fidPool.Get()
if !ok {
return nil, ErrOutOfFIDs
}
rattach := rattach{}
if err := c.sendRecv(&tattach{fid: fid(id), Auth: tauth{AttachName: name, Authenticationfid: noFID, UID: NoUID}}, &rattach); err != nil {
c.fidPool.Put(id)
return nil, err
}
return c.newFile(fid(id)), 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
}
// SetXattr implements p9.File.SetXattr.
func (c *clientFile) SetXattr(attr string, data []byte, flags XattrFlags) error {
return linux.ENOSYS
}
// RemoveXattr implements p9.File.RemoveXattr.
func (c *clientFile) RemoveXattr(attr string) error {
return linux.ENOSYS
}
// GetXattr implements p9.File.GetXattr.
func (c *clientFile) GetXattr(attr string) ([]byte, error) {
return nil, linux.ENOSYS
}
// ListXattrs implements p9.File.ListXattrs.
func (c *clientFile) ListXattrs() ([]string, error) {
return nil, linux.ENOSYS
}
// Walk implements File.Walk.
func (c *clientFile) Walk(names []string) ([]QID, File, error) {
if atomic.LoadUint32(&c.closed) != 0 {
return nil, nil, linux.EBADF
}
id, ok := c.client.fidPool.Get()
if !ok {
return nil, nil, ErrOutOfFIDs
}
rwalk := rwalk{}
if err := c.client.sendRecv(&twalk{fid: c.fid, newFID: fid(id), Names: names}, &rwalk); err != nil {
c.client.fidPool.Put(id)
return nil, nil, err
}
// Return a new client file.
return rwalk.QIDs, c.client.newFile(fid(id)), 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{}, linux.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
}
id, 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(id), Names: components}, &rwalkgetattr); err != nil {
c.client.fidPool.Put(id)
return nil, nil, AttrMask{}, Attr{}, err
}
// Return a new client file.
return rwalkgetattr.QIDs, c.client.newFile(fid(id)), rwalkgetattr.Valid, rwalkgetattr.Attr, nil
}
// StatFS implements File.StatFS.
func (c *clientFile) StatFS() (FSStat, error) {
if atomic.LoadUint32(&c.closed) != 0 {
return FSStat{}, linux.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 linux.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{}, linux.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 linux.EBADF
}
return c.client.sendRecv(&tsetattr{fid: c.fid, Valid: valid, SetAttr: attr}, &rsetattr{})
}
// Lock implements File.Lock
func (c *clientFile) Lock(pid int, locktype LockType, flags LockFlags, start, length uint64, client string) (LockStatus, error) {
if atomic.LoadUint32(&c.closed) != 0 {
return LockStatusError, linux.EBADF
}
r := rlock{}
err := c.client.sendRecv(&tlock{
Type: locktype,
Flags: flags,
Start: start,
Length: length,
PID: int32(pid),
Client: client,
}, &r)
return r.Status, err
}
// 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 linux.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 linux.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.
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) (QID, uint32, error) {
if atomic.LoadUint32(&c.closed) != 0 {
return QID{}, 0, linux.EBADF
}
rlopen := rlopen{}
if err := c.client.sendRecv(&tlopen{fid: c.fid, Flags: flags}, &rlopen); err != nil {
return QID{}, 0, err
}
return rlopen.QID, rlopen.IoUnit, 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, int64) (int, error), p []byte, offset int64) (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 += int64(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 int64) (int, error) {
return chunk(c.client.payloadSize, c.readAt, p, offset)
}
func (c *clientFile) readAt(p []byte, offset int64) (int, error) {
if atomic.LoadUint32(&c.closed) != 0 {
return 0, linux.EBADF
}
rread := rread{Data: p}
if err := c.client.sendRecv(&tread{fid: c.fid, Offset: uint64(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 int64) (int, error) {
return chunk(c.client.payloadSize, c.writeAt, p, offset)
}
func (c *clientFile) writeAt(p []byte, offset int64) (int, error) {
if atomic.LoadUint32(&c.closed) != 0 {
return 0, linux.EBADF
}
rwrite := rwrite{}
if err := c.client.sendRecv(&twrite{fid: c.fid, Offset: uint64(offset), Data: p}, &rwrite); err != nil {
return 0, err
}
return int(rwrite.Count), nil
}
// Rename implements File.Rename.
func (c *clientFile) Rename(dir File, name string) error {
if atomic.LoadUint32(&c.closed) != 0 {
return linux.EBADF
}
clientDir, ok := dir.(*clientFile)
if !ok {
return linux.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) (File, QID, uint32, error) {
if atomic.LoadUint32(&c.closed) != 0 {
return nil, QID{}, 0, linux.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, QID{}, 0, err
}
return c, rucreate.QID, rucreate.IoUnit, nil
}
rlcreate := rlcreate{}
if err := c.client.sendRecv(&msg, &rlcreate); err != nil {
return nil, QID{}, 0, err
}
return 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{}, linux.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{}, linux.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 linux.EBADF
}
targetFile, ok := target.(*clientFile)
if !ok {
return linux.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{}, linux.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 linux.EBADF
}
clientNewDir, ok := newdir.(*clientFile)
if !ok {
return linux.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 linux.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) (Dirents, error) {
if atomic.LoadUint32(&c.closed) != 0 {
return nil, linux.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 "", linux.EBADF
}
rreadlink := rreadlink{}
if err := c.client.sendRecv(&treadlink{fid: c.fid}, &rreadlink); err != nil {
return "", err
}
return rreadlink.Target, nil
}
// Renamed implements File.Renamed.
func (c *clientFile) Renamed(newDir File, newName string) {}
// 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 (
"github.com/hugelgupf/p9/linux"
)
// 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 linux.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
// 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 ReadAt, WriteAt, 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. 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) (QID, uint32, error)
// ReadAt reads from this file. Open must be called first.
//
// This may return io.EOF in addition to linux.Errno values.
//
// On the server, ReadAt has a read concurrency guarantee. See Open for
// additional requirements regarding lazy path resolution.
ReadAt(p []byte, offset int64) (int, error)
// WriteAt writes to this file. Open must be called first.
//
// This may return io.EOF in addition to linux.Errno values.
//
// On the server, WriteAt has a read concurrency guarantee. See Open
// for additional requirements regarding lazy path resolution.
WriteAt(p []byte, offset int64) (int, error)
// SetXattr sets the extended attributes attr=data of the file.
//
// Flags are implementation-specific, but are
// generally Linux setxattr(2) flags.
SetXattr(attr string, data []byte, flags XattrFlags) error
// GetXattr fetches the extended attribute attr of the file.
GetXattr(attr string) ([]byte, error)
// ListXattrs lists the extended attribute names of the file.
ListXattrs() ([]string, error)
// RemoveXattr removes the extended attribute attr from the file.
RemoveXattr(attr string) error
// FSync syncs this node. Open must be called first.
//
// On the server, FSync has a read concurrency guarantee.
FSync() error
// Lock locks the file. The operation as defined in 9P2000.L is fairly
// ambitious, being a near-direct mapping to lockf(2)/fcntl(2)-style
// locking, but most implementations use flock(2).
//
// Arguments are defined by the 9P2000.L standard.
//
// Pid is the PID on the client. Locktype is one of read, write, or
// unlock (resp. 0, 1, or 2). Flags are to block (0), meaning wait; or
// reclaim (1), which is currently "reserved for future use." Start and
// length are the start of the region to use and the size. In many
// implementations, they are ignored and flock(2) is used. Client is an
// arbitrary string, also frequently unused. The Linux v9fs client
// happens to set the client name to the node name.
//
// The Linux v9fs client implements fcntl(F_SETLK) by calling lock
// without any flags set.
//
// The Linux v9fs client implements the fcntl(F_SETLKW) (blocking)
// lock request by calling lock with P9_LOCK_FLAGS_BLOCK set. If the
// response is P9_LOCK_BLOCKED, it retries the lock request in an
// interruptible loop until status is no longer P9_LOCK_BLOCKED.
//
// The Linux v9fs client translates BSD advisory locks (flock) to
// whole-file POSIX record locks. v9fs does not implement mandatory
// locks and will return ENOLCK if use is attempted.
//
// In the return values, a LockStatus corresponds to an Rlock, while
// returning an error corresponds to an Rlerror message. If any non-nil
// error is returned, an Rlerror message will be sent.
//
// The most commonly used return values are success and error (resp. 0
// and 2); blocked (1) and grace (3) are also possible.
Lock(pid int, locktype LockType, flags LockFlags, start, length uint64, client string) (LockStatus, 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.
//
// On the server, Create has a write concurrency guarantee.
Create(name string, flags OpenFlags, permissions FileMode, uid UID, gid GID) (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.
//
// offset is the entry offset, and count the number of entries to
// return.
//
// This may return io.EOF in addition to linux.Errno values.
//
// On the server, Readdir has a read concurrency guarantee.
Readdir(offset uint64, count uint32) (Dirents, error)
// Readlink reads the link target.
//
// On the server, Readlink has a read concurrency guarantee.
Readlink() (string, 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{}, linux.ENOSYS
}
//go:build gofuzz
// +build gofuzz
package p9
import (
"bytes"
"github.com/u-root/uio/ulog"
)
func Fuzz(data []byte) int {
buf := bytes.NewBuffer(data)
tag, msg, err := recv(ulog.Null, buf, DefaultMessageSize, msgDotLRegistry.get)
if err != nil {
if msg != nil {
panic("msg !=nil on error")
}
return 0
}
buf.Reset()
send(ulog.Null, buf, tag, msg)
if err != nil {
panic(err)
}
return 1
}
// 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"
"path"
"strings"
"sync"
"sync/atomic"
"github.com/hugelgupf/p9/linux"
)
// newErr returns a new error message from an error.
func newErr(err error) *rlerror {
return &rlerror{Error: uint32(linux.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 the server does not understand the client's version string, it
// should respond with an Rversion message (not Rerror) with the
// version string the 7 characters "unknown"".
//
// - 9P2000 spec.
//
// Makes sense, since there are two different kinds of errors depending on the version.
unknown := &rversion{
MSize: 0,
Version: "unknown",
}
if t.MSize == 0 {
return unknown
}
msize := t.MSize
if t.MSize > maximumLength {
msize = maximumLength
}
reqBaseVersion, reqVersion, ok := parseVersion(t.Version)
if !ok {
return unknown
}
var baseVersion baseVersion
var version uint32
switch reqBaseVersion {
case version9P2000, version9P2000U:
return unknown
case version9P2000L:
baseVersion = reqBaseVersion
// 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 reqVersion > highestSupportedVersion {
version = highestSupportedVersion
} else {
version = reqVersion
}
}
// 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.messageSize, msize)
atomic.StoreUint32(&cs.version, version)
// This is not thread-safe. We're changing this into sessions anyway,
// so who cares.
cs.baseVersion = baseVersion
// Initial a pool with msize-shaped buffers.
cs.readBufPool = sync.Pool{
New: func() interface{} {
// These buffers are used for decoding without a payload.
// We need to return a pointer to avoid unnecessary allocations
// (see https://staticcheck.io/docs/checks#SA6002).
b := make([]byte, msize)
return &b
},
}
// Buffer of zeros.
cs.pristineZeros = make([]byte, msize)
return &rversion{
MSize: msize,
Version: versionString(baseVersion, 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 linux.EINVAL
}
func clunkHandleXattr(cs *connState, t *tclunk) message {
// Lookup the fid.
ref, ok := cs.LookupFID(t.fid)
if !ok {
return newErr(linux.EBADF)
}
defer ref.DecRef()
if err := ref.safelyRead(func() error {
if ref.pendingXattr.op == xattrCreate {
if len(ref.pendingXattr.buf) != int(ref.pendingXattr.size) {
return linux.EINVAL
}
if ref.pendingXattr.flags == XattrReplace && ref.pendingXattr.size == 0 {
return ref.file.RemoveXattr(ref.pendingXattr.name)
}
return ref.file.SetXattr(ref.pendingXattr.name, ref.pendingXattr.buf, ref.pendingXattr.flags)
}
return nil
}); err != nil {
return newErr(err)
}
return nil
}
// handle implements handler.handle.
func (t *tclunk) handle(cs *connState) message {
cerr := clunkHandleXattr(cs, t)
if err := cs.DeleteFID(t.fid); err != nil {
return newErr(err)
}
if cerr != nil {
return cerr
}
return &rclunk{}
}
// handle implements handler.handle.
func (t *tremove) handle(cs *connState) message {
ref, ok := cs.LookupFID(t.fid)
if !ok {
return newErr(linux.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 linux.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 linux.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 fidErr := cs.DeleteFID(t.fid); fidErr != nil {
return newErr(fidErr)
}
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(linux.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(linux.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(linux.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(linux.EBADF)
}
defer ref.DecRef()
var (
qid QID
ioUnit uint32
)
if err := ref.safelyRead(func() (err error) {
// Has it been deleted already?
if ref.isDeleted() {
return linux.EINVAL
}
// Has it been opened already?
if ref.opened || !CanOpen(ref.mode) {
return linux.EINVAL
}
// Is this an attempt to open a directory as writable? Don't accept.
if ref.mode.IsDir() && t.Flags.Mode() != ReadOnly {
return linux.EISDIR
}
// Do the open.
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}
}
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, linux.EBADF
}
defer ref.DecRef()
var (
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 linux.EINVAL
}
// Not allowed on open directories.
if ref.opened {
return linux.EINVAL
}
// Do the create.
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}}, 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, linux.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 linux.EINVAL
}
// Not allowed on open directories.
if ref.opened {
return linux.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(linux.EBADF)
}
defer ref.DecRef()
// Lookup the other fid.
refTarget, ok := cs.LookupFID(t.Target)
if !ok {
return newErr(linux.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 linux.EINVAL
}
// Not allowed on open directories.
if ref.opened {
return linux.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(linux.EBADF)
}
defer ref.DecRef()
// Lookup the other fid.
refTarget, ok := cs.LookupFID(t.NewDirectory)
if !ok {
return newErr(linux.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 linux.EINVAL
}
// Not allowed on open directories.
if ref.opened {
return linux.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(linux.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 linux.EINVAL
}
// Not allowed on open directories.
if ref.opened {
return linux.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
// unlinkat 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.opMu.Lock()
defer childPathNode.opMu.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(linux.EBADF)
}
defer ref.DecRef()
// Lookup the target.
refTarget, ok := cs.LookupFID(t.Directory)
if !ok {
return newErr(linux.EBADF)
}
defer refTarget.DecRef()
if err := ref.safelyGlobal(func() (err error) {
// Don't allow a root rename.
if ref.isRoot() {
return linux.EINVAL
}
// Don't allow renaming deleting entries, or target non-directories.
if ref.isDeleted() || refTarget.isDeleted() || !refTarget.mode.IsDir() {
return linux.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(linux.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 linux.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(linux.EBADF)
}
defer ref.DecRef()
// Constrain the size of the read buffer.
if int(t.Count) > int(maximumLength) {
return newErr(linux.ENOBUFS)
}
var n int
data := cs.readBufPool.Get().(*[]byte)
// Retain a reference to the full length of the buffer.
dataBuf := (*data)
if err := ref.safelyRead(func() (err error) {
switch ref.pendingXattr.op {
case xattrNone:
// Has it been opened already?
if !ref.opened {
return linux.EINVAL
}
// Can it be read? Check permissions.
if ref.openFlags&OpenFlagsModeMask == WriteOnly {
return linux.EPERM
}
n, err = ref.file.ReadAt(dataBuf[:t.Count], int64(t.Offset))
return err
case xattrWalk:
// Make sure we do not pass an empty buffer to GetXattr or ListXattrs.
// Both of them will return the required buffer length if
// the input buffer has length 0.
// tread means the caller already knows the required buffer length
// and wants to get the attribute value.
if t.Count == 0 {
if ref.pendingXattr.size == 0 {
// the provided buffer has length 0 and
// the attribute value is also empty.
return nil
}
// buffer too small.
return linux.EINVAL
}
if t.Offset+uint64(t.Count) > uint64(len(ref.pendingXattr.buf)) {
return linux.EINVAL
}
n = copy(dataBuf[:t.Count], ref.pendingXattr.buf[t.Offset:])
return nil
default:
return linux.EINVAL
}
}); err != nil && !errors.Is(err, io.EOF) {
return newErr(err)
}
return &rreadServerPayloader{
rread: rread{
Data: dataBuf[:n],
},
cs: cs,
fullBuffer: dataBuf,
}
}
// handle implements handler.handle.
func (t *twrite) handle(cs *connState) message {
// Lookup the fid.
ref, ok := cs.LookupFID(t.fid)
if !ok {
return newErr(linux.EBADF)
}
defer ref.DecRef()
var n int
if err := ref.safelyRead(func() (err error) {
switch ref.pendingXattr.op {
case xattrNone:
// Has it been opened already?
if !ref.opened {
return linux.EINVAL
}
// Can it be written? Check permissions.
if ref.openFlags&OpenFlagsModeMask == ReadOnly {
return linux.EPERM
}
n, err = ref.file.WriteAt(t.Data, int64(t.Offset))
case xattrCreate:
if uint64(len(ref.pendingXattr.buf)) != t.Offset {
return linux.EINVAL
}
if t.Offset+uint64(len(t.Data)) > ref.pendingXattr.size {
return linux.EINVAL
}
ref.pendingXattr.buf = append(ref.pendingXattr.buf, t.Data...)
n = len(t.Data)
default:
return linux.EINVAL
}
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, linux.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 linux.EINVAL
}
// Not allowed on open directories.
if ref.opened {
return linux.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, linux.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 linux.EINVAL
}
// Not allowed on open directories.
if ref.opened {
return linux.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(linux.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(linux.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 linux.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 *txattrwalk) handle(cs *connState) message {
// Lookup the fid.
ref, ok := cs.LookupFID(t.fid)
if !ok {
return newErr(linux.EBADF)
}
defer ref.DecRef()
size := 0
if err := ref.safelyRead(func() error {
if ref.isDeleted() {
return linux.EINVAL
}
var buf []byte
var err error
if len(t.Name) > 0 {
buf, err = ref.file.GetXattr(t.Name)
} else {
var xattrs []string
xattrs, err = ref.file.ListXattrs()
if err == nil {
buf = []byte(strings.Join(xattrs, "\000") + "\000")
}
}
if err != nil || uint32(len(buf)) > maximumLength {
return linux.EINVAL
}
size = len(buf)
newRef := &fidRef{
server: cs.server,
file: ref.file,
pendingXattr: pendingXattr{
op: xattrWalk,
name: t.Name,
size: uint64(size),
buf: buf,
},
pathNode: ref.pathNode,
parent: ref.parent,
}
cs.InsertFID(t.newFID, newRef)
return nil
}); err != nil {
return newErr(err)
}
return &rxattrwalk{Size: uint64(size)}
}
// handle implements handler.handle.
func (t *txattrcreate) handle(cs *connState) message {
// Lookup the fid.
ref, ok := cs.LookupFID(t.fid)
if !ok {
return newErr(linux.EBADF)
}
defer ref.DecRef()
if err := ref.safelyWrite(func() error {
if ref.isDeleted() {
return linux.EINVAL
}
ref.pendingXattr = pendingXattr{
op: xattrCreate,
name: t.Name,
size: t.AttrSize,
flags: XattrFlags(t.Flags),
}
return nil
}); err != nil {
return newErr(err)
}
return &rxattrcreate{}
}
// handle implements handler.handle.
func (t *treaddir) handle(cs *connState) message {
// Lookup the fid.
ref, ok := cs.LookupFID(t.Directory)
if !ok {
return newErr(linux.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 linux.EINVAL
}
// Has it been opened already?
if !ref.opened {
return linux.EINVAL
}
// Read the entries.
entries, err = ref.file.Readdir(t.Offset, t.Count)
if err != nil && !errors.Is(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(linux.EBADF)
}
defer ref.DecRef()
if err := ref.safelyRead(func() (err error) {
// Has it been opened already?
if !ref.opened {
return linux.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(linux.EBADF)
}
defer ref.DecRef()
st, err := ref.file.StatFS()
if err != nil {
return newErr(err)
}
return &rstatfs{st}
}
// handle implements handler.handle.
func (t *tlock) handle(cs *connState) message {
// Lookup the fid.
ref, ok := cs.LookupFID(t.fid)
if !ok {
return newErr(linux.EBADF)
}
defer ref.DecRef()
status, err := ref.file.Lock(int(t.PID), t.Type, t.Flags, t.Start, t.Length, t.Client)
if err != nil {
return newErr(err)
}
return &rlock{Status: status}
}
// 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) {
nwname := len(names)
if nwname > 1 {
// We require exactly zero or one elements.
return nil, nil, AttrMask{}, Attr{}, linux.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 !errors.Is(err, linux.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 nwname == 1 && len(localQIDs) != 1 {
// Expected a single QID.
sf.Close()
return nil, nil, AttrMask{}, Attr{}, linux.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
}
}
// 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,
}
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.
// walk(5) "nwqid will always be less than or equal to nwname"
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{}, linux.EINVAL
}
var sf File // Temporary.
if err := walkRef.safelyRead(func() (err error) {
// It is not safe to walk on a deleted directory. It
// could have been replaced with a malicious symlink.
if walkRef.isDeleted() {
// Fail this operation as the result will not
// be meaningful if walkRef is deleted.
return linux.ENOENT
}
// 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(linux.EBADF)
}
defer ref.DecRef()
if err := ref.safelyRead(func() error {
// Has it been opened already?
//
// That as OK as long as newFID is different. Note this
// violates the spec, but the Linux client does too, so we have
// little choice.
if ref.opened && t.fid == t.newFID {
return linux.EBUSY
}
return nil
}); err != nil {
return newErr(err)
}
// Is this an empty list? Handle specially. We don't actually need to
// 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(linux.EBADF)
}
defer ref.DecRef()
if err := ref.safelyRead(func() error {
// Has it been opened already?
//
// That as OK as long as newFID is different. Note this
// violates the spec, but the Linux client does too, so we have
// little choice.
if ref.opened && t.fid == t.newFID {
return linux.EBUSY
}
return nil
}); err != nil {
return newErr(err)
}
// Is this an empty list? Handle specially. We don't actually need to
// 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}
}
// 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"
)
// 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.
typ() 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)
// PayloadCleanup is called after a payloader message is sent and
// buffers can be reapt.
PayloadCleanup()
}
// 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)
}
// typ implements message.typ.
func (*tversion) typ() 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)
}
// typ implements message.typ.
func (*rversion) typ() 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)
}
// typ implements message.typ.
func (*tflush) typ() 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) {
}
// typ implements message.typ.
func (*rflush) typ() 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)
}
}
// typ implements message.typ.
func (*twalk) typ() 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)
}
}
// typ implements message.typ.
func (*rwalk) typ() 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)
}
// typ implements message.typ.
func (*tclunk) typ() 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) {
}
// typ implements message.typ.
func (*rclunk) typ() msgType {
return msgRclunk
}
// String implements fmt.Stringer.
func (r *rclunk) String() string {
return fmt.Sprintf("Rclunk{}")
}
// tremove is a remove request.
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)
}
// typ implements message.typ.
func (*tremove) typ() 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) {
}
// typ implements message.typ.
func (*rremove) typ() 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)
}
// typ implements message.typ.
func (*rlerror) typ() 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)
}
// typ implements message.typ.
func (*tauth) typ() 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
}
// typ implements message.typ.
func (*rauth) typ() 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)
}
// typ implements message.typ.
func (*tattach) typ() 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
}
// typ implements message.typ.
func (*rattach) typ() 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)
}
// typ implements message.typ.
func (*tlopen) typ() 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
}
// 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)
}
// typ implements message.typ.
func (*rlopen) typ() msgType {
return msgRlopen
}
// String implements fmt.Stringer.
func (r *rlopen) String() string {
return fmt.Sprintf("Rlopen{QID: %s, IoUnit: %d}", r.QID, r.IoUnit)
}
// 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)
}
// typ implements message.typ.
func (*tlcreate) typ() 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
}
// typ implements message.typ.
func (*rlcreate) typ() msgType {
return msgRlcreate
}
// String implements fmt.Stringer.
func (r *rlcreate) String() string {
return fmt.Sprintf("Rlcreate{QID: %s, IoUnit: %d}", r.QID, r.IoUnit)
}
// 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)
}
// typ implements message.typ.
func (*tsymlink) typ() 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)
}
// typ implements message.typ.
func (*rsymlink) typ() 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)
}
// typ implements message.typ.
func (*tlink) typ() 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 {
}
// typ implements message.typ.
func (*rlink) typ() 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)
}
// typ implements message.typ.
func (*trenameat) typ() 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) {
}
// typ implements message.typ.
func (*rrenameat) typ() 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)
}
// typ implements message.typ.
func (*tunlinkat) typ() 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) {
}
// typ implements message.typ.
func (*runlinkat) typ() msgType {
return msgRunlinkat
}
// String implements fmt.Stringer.
func (r *runlinkat) String() string {
return fmt.Sprintf("Runlinkat{}")
}
// trename is a rename request.
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)
}
// typ implements message.typ.
func (*trename) typ() 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) {
}
// typ implements message.typ.
func (*rrename) typ() 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)
}
// typ implements message.typ.
func (*treadlink) typ() 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)
}
// typ implements message.typ.
func (*rreadlink) typ() 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)
}
// typ implements message.typ.
func (*tread) typ() 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)
}
// rreadServerPayloader is the response for a Tread by p9 servers.
//
// rreadServerPayloader exists so the fuzzer can fuzz rread -- however,
// PayloadCleanup causes it to panic, and putting connState in the fuzzer seems
// excessive.
type rreadServerPayloader struct {
rread
fullBuffer []byte
cs *connState
}
// 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)))
}
// typ implements message.typ.
func (*rread) typ() 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
}
func (*rread) PayloadCleanup() {}
// FixedSize implements payloader.FixedSize.
func (*rreadServerPayloader) FixedSize() uint32 {
return 4
}
// Payload implements payloader.Payload.
func (r *rreadServerPayloader) Payload() []byte {
return r.Data
}
// SetPayload implements payloader.SetPayload.
func (r *rreadServerPayloader) SetPayload(p []byte) {
r.Data = p
}
// PayloadCleanup implements payloader.PayloadCleanup.
func (r *rreadServerPayloader) PayloadCleanup() {
// Fill it with zeros to not risk leaking previous files' data.
copy(r.Data, r.cs.pristineZeros)
r.cs.readBufPool.Put(&r.fullBuffer)
}
// 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)))
}
// typ implements message.typ.
func (*twrite) typ() 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
}
func (t *twrite) PayloadCleanup() {}
// 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)
}
// typ implements message.typ.
func (*rwrite) typ() 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)
}
// typ implements message.typ.
func (*tmknod) typ() 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)
}
// typ implements message.typ.
func (*rmknod) typ() 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)
}
// typ implements message.typ.
func (*tmkdir) typ() 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)
}
// typ implements message.typ.
func (*rmkdir) typ() 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)
}
// typ implements message.typ.
func (*tgetattr) typ() 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)
}
// typ implements message.typ.
func (*rgetattr) typ() 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)
}
// typ implements message.typ.
func (*tsetattr) typ() 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) {
}
// typ implements message.typ.
func (*rsetattr) typ() msgType {
return msgRsetattr
}
// String implements fmt.Stringer.
func (r *rsetattr) String() string {
return fmt.Sprintf("Rsetattr{}")
}
// txattrwalk walks extended attributes.
type txattrwalk struct {
// fid is the fid to check for attributes.
fid fid
// newFID is the new fid associated with the attributes.
newFID fid
// Name is the attribute name.
Name string
}
// decode implements encoder.decode.
func (t *txattrwalk) decode(b *buffer) {
t.fid = b.ReadFID()
t.newFID = b.ReadFID()
t.Name = b.ReadString()
}
// encode implements encoder.encode.
func (t *txattrwalk) encode(b *buffer) {
b.WriteFID(t.fid)
b.WriteFID(t.newFID)
b.WriteString(t.Name)
}
// typ implements message.typ.
func (*txattrwalk) typ() 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)
}
// typ implements message.typ.
func (*rxattrwalk) typ() 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)
}
// typ implements message.typ.
func (*txattrcreate) typ() 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) {
}
// typ implements message.typ.
func (*rxattrcreate) typ() 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)
}
// typ implements message.typ.
func (*treaddir) typ() 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{}
payloadSize := 0
for _, d := range r.Entries {
d.encode(&entriesBuf)
if len(entriesBuf.data) > int(r.Count) {
break
}
payloadSize = len(entriesBuf.data)
}
r.Count = uint32(payloadSize)
r.payload = entriesBuf.data[:payloadSize]
b.Write32(r.Count)
}
// typ implements message.typ.
func (*rreaddir) typ() 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
}
func (r *rreaddir) PayloadCleanup() {}
// 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)
}
// typ implements message.typ.
func (*tfsync) typ() 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) {
}
// typ implements message.typ.
func (*rfsync) typ() 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)
}
// typ implements message.typ.
func (*tstatfs) typ() 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)
}
// typ implements message.typ.
func (*rstatfs) typ() msgType {
return msgRstatfs
}
// String implements fmt.Stringer.
func (r *rstatfs) String() string {
return fmt.Sprintf("Rstatfs{FSStat: %v}", r.FSStat)
}
// 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)
}
}
// typ implements message.typ.
func (*twalkgetattr) typ() 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)
}
}
// typ implements message.typ.
func (*rwalkgetattr) typ() 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)
}
// typ implements message.typ.
func (t *tucreate) typ() 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
}
// typ implements message.typ.
func (*rucreate) typ() 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)
}
// typ implements message.typ.
func (t *tumkdir) typ() 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
}
// typ implements message.typ.
func (*rumkdir) typ() 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)
}
// typ implements message.typ.
func (t *tumknod) typ() 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
}
// typ implements message.typ.
func (*rumknod) typ() 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)
}
// typ implements message.typ.
func (t *tusymlink) typ() 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
}
// typ implements message.typ.
func (*rusymlink) typ() msgType {
return msgRusymlink
}
// String implements fmt.Stringer.
func (r *rusymlink) String() string {
return fmt.Sprintf("Rusymlink{%v}", &r.rsymlink)
}
// LockType is lock type for Tlock
type LockType uint8
// These constants define Lock operations: Read, Write, and Un(lock)
// They map to Linux values of F_RDLCK, F_WRLCK, F_UNLCK.
// If that seems a little Linux-centric, recall that the "L"
// in 9P2000.L means "Linux" :-)
const (
ReadLock LockType = iota
WriteLock
Unlock
)
func (l LockType) String() string {
switch l {
case ReadLock:
return "ReadLock"
case WriteLock:
return "WriteLock"
case Unlock:
return "Unlock"
}
return "unknown lock type"
}
// LockFlags are flags for the lock. Currently, and possibly forever, only one
// is really used: LockFlagsBlock
type LockFlags uint32
const (
// LockFlagsBlock indicates a blocking request.
LockFlagsBlock LockFlags = 1
// LockFlagsReclaim is "Reserved for future use."
// It's been some time since 9P2000.L came about,
// I suspect "future" in this case is "never"?
LockFlagsReclaim LockFlags = 2
)
// LockStatus contains lock status result.
type LockStatus uint8
// These are the four current return values for Rlock.
const (
LockStatusOK LockStatus = iota
LockStatusBlocked
LockStatusError
LockStatusGrace
)
func (s LockStatus) String() string {
switch s {
case LockStatusOK:
return "LockStatusOK"
case LockStatusBlocked:
return "LockStatusBlocked"
case LockStatusError:
return "LockStatusError"
case LockStatusGrace:
return "LockStatusGrace"
}
return "unknown lock status"
}
// tlock is a Tlock message
type tlock struct {
// fid is the fid to lock.
fid fid
Type LockType // Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */
Flags LockFlags // flags, not whence, docs are wrong.
Start uint64 // Starting offset for lock
Length uint64 // Number of bytes to lock
PID int32 // PID of process blocking our lock (F_GETLK only)
// "client_id is an additional mechanism for uniquely
// identifying the lock requester and is set to the nodename
// by the Linux v9fs client."
// https://github.com/chaos/diod/blob/master/protocol.md#lock---acquire-or-release-a-posix-record-lock
Client string // Client id -- but technically can be anything.
}
// decode implements encoder.decode.
func (t *tlock) decode(b *buffer) {
t.fid = b.ReadFID()
t.Type = LockType(b.Read8())
t.Flags = LockFlags(b.Read32())
t.Start = b.Read64()
t.Length = b.Read64()
t.PID = int32(b.Read32())
t.Client = b.ReadString()
}
// encode implements encoder.encode.
func (t *tlock) encode(b *buffer) {
b.WriteFID(t.fid)
b.Write8(uint8(t.Type))
b.Write32(uint32(t.Flags))
b.Write64(t.Start)
b.Write64(t.Length)
b.Write32(uint32(t.PID))
b.WriteString(t.Client)
}
// typ implements message.typ.
func (*tlock) typ() msgType {
return msgTlock
}
// String implements fmt.Stringer.
func (t *tlock) String() string {
return fmt.Sprintf("Tlock{Type: %s, Flags: %#x, Start: %d, Length: %d, PID: %d, Client: %s}", t.Type.String(), t.Flags, t.Start, t.Length, t.PID, t.Client)
}
// rlock is a lock response.
type rlock struct {
Status LockStatus
}
// decode implements encoder.decode.
func (r *rlock) decode(b *buffer) {
r.Status = LockStatus(b.Read8())
}
// encode implements encoder.encode.
func (r *rlock) encode(b *buffer) {
b.Write8(uint8(r.Status))
}
// typ implements message.typ.
func (*rlock) typ() msgType {
return msgRlock
}
// String implements fmt.Stringer.
func (r *rlock) String() string {
return fmt.Sprintf("Rlock{Status: %s}", r.Status)
}
// Let's wait until we need this? POSIX locks over a network make 0 sense.
// getlock - test for the existence of a POSIX record lock
// size[4] Tgetlock tag[2] fid[4] type[1] start[8] length[8] proc_id[4] client_id[s]
// size[4] Rgetlock tag[2] type[1] start[8] length[8] proc_id[4] client_id[s]
// getlock tests for the existence of a POSIX record lock and has semantics similar to Linux fcntl(F_GETLK).
// As with lock, type has one of the values defined above, and start,
// length, and proc_id correspond to the analogous fields in struct
// flock passed to Linux fcntl(F_GETLK), and client_Id is an
// additional mechanism for uniquely identifying the lock requester
// and is set to the nodename by the Linux v9fs client. tusymlink is
// a Tsymlink message that includes a UID.
/// END LOCK
const maxCacheSize = 3
// msgFactory is used to reduce allocations by caching messages for reuse.
type msgFactory struct {
create func() message
cache chan message
}
// msgDotLRegistry indexes all 9P2000.L(.Google.N) message factories by type.
var msgDotLRegistry registry
type registry struct {
factories [math.MaxUint8 + 1]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)
}
entry := &r.factories[msg.typ()]
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() {
msgDotLRegistry.register(msgRlerror, func() message { return &rlerror{} })
msgDotLRegistry.register(msgTstatfs, func() message { return &tstatfs{} })
msgDotLRegistry.register(msgRstatfs, func() message { return &rstatfs{} })
msgDotLRegistry.register(msgTlopen, func() message { return &tlopen{} })
msgDotLRegistry.register(msgRlopen, func() message { return &rlopen{} })
msgDotLRegistry.register(msgTlcreate, func() message { return &tlcreate{} })
msgDotLRegistry.register(msgRlcreate, func() message { return &rlcreate{} })
msgDotLRegistry.register(msgTsymlink, func() message { return &tsymlink{} })
msgDotLRegistry.register(msgRsymlink, func() message { return &rsymlink{} })
msgDotLRegistry.register(msgTmknod, func() message { return &tmknod{} })
msgDotLRegistry.register(msgRmknod, func() message { return &rmknod{} })
msgDotLRegistry.register(msgTrename, func() message { return &trename{} })
msgDotLRegistry.register(msgRrename, func() message { return &rrename{} })
msgDotLRegistry.register(msgTreadlink, func() message { return &treadlink{} })
msgDotLRegistry.register(msgRreadlink, func() message { return &rreadlink{} })
msgDotLRegistry.register(msgTgetattr, func() message { return &tgetattr{} })
msgDotLRegistry.register(msgRgetattr, func() message { return &rgetattr{} })
msgDotLRegistry.register(msgTsetattr, func() message { return &tsetattr{} })
msgDotLRegistry.register(msgRsetattr, func() message { return &rsetattr{} })
msgDotLRegistry.register(msgTxattrwalk, func() message { return &txattrwalk{} })
msgDotLRegistry.register(msgRxattrwalk, func() message { return &rxattrwalk{} })
msgDotLRegistry.register(msgTxattrcreate, func() message { return &txattrcreate{} })
msgDotLRegistry.register(msgRxattrcreate, func() message { return &rxattrcreate{} })
msgDotLRegistry.register(msgTreaddir, func() message { return &treaddir{} })
msgDotLRegistry.register(msgRreaddir, func() message { return &rreaddir{} })
msgDotLRegistry.register(msgTfsync, func() message { return &tfsync{} })
msgDotLRegistry.register(msgRfsync, func() message { return &rfsync{} })
msgDotLRegistry.register(msgTlink, func() message { return &tlink{} })
msgDotLRegistry.register(msgRlink, func() message { return &rlink{} })
msgDotLRegistry.register(msgTlock, func() message { return &tlock{} })
msgDotLRegistry.register(msgRlock, func() message { return &rlock{} })
msgDotLRegistry.register(msgTmkdir, func() message { return &tmkdir{} })
msgDotLRegistry.register(msgRmkdir, func() message { return &rmkdir{} })
msgDotLRegistry.register(msgTrenameat, func() message { return &trenameat{} })
msgDotLRegistry.register(msgRrenameat, func() message { return &rrenameat{} })
msgDotLRegistry.register(msgTunlinkat, func() message { return &tunlinkat{} })
msgDotLRegistry.register(msgRunlinkat, func() message { return &runlinkat{} })
msgDotLRegistry.register(msgTversion, func() message { return &tversion{} })
msgDotLRegistry.register(msgRversion, func() message { return &rversion{} })
msgDotLRegistry.register(msgTauth, func() message { return &tauth{} })
msgDotLRegistry.register(msgRauth, func() message { return &rauth{} })
msgDotLRegistry.register(msgTattach, func() message { return &tattach{} })
msgDotLRegistry.register(msgRattach, func() message { return &rattach{} })
msgDotLRegistry.register(msgTflush, func() message { return &tflush{} })
msgDotLRegistry.register(msgRflush, func() message { return &rflush{} })
msgDotLRegistry.register(msgTwalk, func() message { return &twalk{} })
msgDotLRegistry.register(msgRwalk, func() message { return &rwalk{} })
msgDotLRegistry.register(msgTread, func() message { return &tread{} })
msgDotLRegistry.register(msgRread, func() message { return &rread{} })
msgDotLRegistry.register(msgTwrite, func() message { return &twrite{} })
msgDotLRegistry.register(msgRwrite, func() message { return &rwrite{} })
msgDotLRegistry.register(msgTclunk, func() message { return &tclunk{} })
msgDotLRegistry.register(msgRclunk, func() message { return &rclunk{} })
msgDotLRegistry.register(msgTremove, func() message { return &tremove{} })
msgDotLRegistry.register(msgRremove, func() message { return &rremove{} })
msgDotLRegistry.register(msgTwalkgetattr, func() message { return &twalkgetattr{} })
msgDotLRegistry.register(msgRwalkgetattr, func() message { return &rwalkgetattr{} })
msgDotLRegistry.register(msgTucreate, func() message { return &tucreate{} })
msgDotLRegistry.register(msgRucreate, func() message { return &rucreate{} })
msgDotLRegistry.register(msgTumkdir, func() message { return &tumkdir{} })
msgDotLRegistry.register(msgRumkdir, func() message { return &rumkdir{} })
msgDotLRegistry.register(msgTumknod, func() message { return &tumknod{} })
msgDotLRegistry.register(msgRumknod, func() message { return &rumknod{} })
msgDotLRegistry.register(msgTusymlink, func() message { return &tusymlink{} })
msgDotLRegistry.register(msgRusymlink, func() message { return &rusymlink{} })
}
// 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.
//
// Servers implement Attacher and File interfaces.
//
// Clients can use Client.
package p9
import (
"fmt"
"math"
"os"
"strings"
"sync/atomic"
"github.com/hugelgupf/p9/internal"
)
// Debug can be assigned to log.Printf to print messages received and sent.
var Debug = func(fmt string, v ...interface{}) {}
const (
// DefaultMessageSize is a sensible default.
DefaultMessageSize uint32 = 64 << 10
)
// 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
)
// Mode returns only the open mode (read-only, read-write, or write-only).
func (o OpenFlags) Mode() OpenFlags {
return o & OpenFlagsModeMask
}
// OSFlags converts a p9.OpenFlags to an int compatible with open(2).
func (o OpenFlags) OSFlags() int {
return int(o & OpenFlagsModeMask)
}
// String implements fmt.Stringer.
func (o OpenFlags) String() string {
switch o {
case ReadOnly:
return "ReadOnly"
case WriteOnly:
return "WriteOnly"
case ReadWrite:
return "ReadWrite"
case OpenFlagsModeMask:
return "OpenFlagsModeMask"
default:
return fmt.Sprintf("unknown (%#x)", uint32(o))
}
}
// XattrFlags are flags set on a setxattr operation.
type XattrFlags int
const (
// XattrCreate set on setxattr requires a pure create, which fails if
// the named attribute already exists.
XattrCreate XattrFlags = 1
// XattrReplace set on setxattr requires a pure replace, which fails if
// the named attribute does not already exist.
XattrReplace XattrFlags = 2
)
// tag is a message 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 (
msgRlerror msgType = 7
msgTstatfs msgType = 8
msgRstatfs msgType = 9
msgTlopen msgType = 12
msgRlopen msgType = 13
msgTlcreate msgType = 14
msgRlcreate msgType = 15
msgTsymlink msgType = 16
msgRsymlink msgType = 17
msgTmknod msgType = 18
msgRmknod msgType = 19
msgTrename msgType = 20
msgRrename msgType = 21
msgTreadlink msgType = 22
msgRreadlink msgType = 23
msgTgetattr msgType = 24
msgRgetattr msgType = 25
msgTsetattr msgType = 26
msgRsetattr msgType = 27
msgTxattrwalk msgType = 30
msgRxattrwalk msgType = 31
msgTxattrcreate msgType = 32
msgRxattrcreate msgType = 33
msgTreaddir msgType = 40
msgRreaddir msgType = 41
msgTfsync msgType = 50
msgRfsync msgType = 51
msgTlock msgType = 52
msgRlock msgType = 53
msgTgetlock msgType = 54
msgRgetlock msgType = 55
msgTlink msgType = 70
msgRlink msgType = 71
msgTmkdir msgType = 72
msgRmkdir msgType = 73
msgTrenameat msgType = 74
msgRrenameat msgType = 75
msgTunlinkat msgType = 76
msgRunlinkat msgType = 77
msgTversion msgType = 100
msgRversion msgType = 101
msgTauth msgType = 102
msgRauth msgType = 103
msgTattach msgType = 104
msgRattach msgType = 105
msgTflush msgType = 108
msgRflush msgType = 109
msgTwalk msgType = 110
msgRwalk msgType = 111
msgTread msgType = 116
msgRread msgType = 117
msgTwrite msgType = 118
msgRwrite msgType = 119
msgTclunk msgType = 120
msgRclunk msgType = 121
msgTremove msgType = 122
msgRremove msgType = 123
msgTflushf msgType = 124
msgRflushf msgType = 125
msgTwalkgetattr msgType = 126
msgRwalkgetattr msgType = 127
msgTucreate msgType = 128
msgRucreate msgType = 129
msgTumkdir msgType = 130
msgRumkdir msgType = 131
msgTumknod msgType = 132
msgRumknod msgType = 133
msgTusymlink msgType = 134
msgRusymlink msgType = 135
)
// 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
)
var qidTypeString = map[QIDType]string{
TypeDir: "QID Type Directory",
TypeAppendOnly: "QID Type Append Only",
TypeExclusive: "QID Type Exclusive",
TypeMount: "QID Type Mount",
TypeAuth: "QID Type Auth",
TypeTemporary: "QID Type Temporary",
TypeSymlink: "QID Type Symlink",
TypeLink: "QID Type Link",
TypeRegular: "QID Type Regular",
}
func (q QIDType) String() string {
s, ok := qidTypeString[q]
if ok {
return s
}
return fmt.Sprintf("unknown QID type (%#x)", uint8(q))
}
// 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 is an AttrMask with all fields masked.
var AttrMaskAll = 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)
}
// NLink is the number of links to this fs object.
//
// While this type has no utilities, it is useful in order to force linux+amd64
// only developers to cast to NLink for the NLink field, which will make their
// code compatible with other GOARCH and GOOS values.
type NLink uint64
// Dev is the device number of an fs object.
//
// While this type has no utilities, it is useful in order to force linux+amd64
// only developers to cast to Dev for the Dev field, which will make their
// code compatible with other GOARCH and GOOS values.
type Dev uint64
// Attr is a set of attributes for getattr.
type Attr struct {
Mode FileMode
UID UID
GID GID
NLink NLink
RDev Dev
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)
}
// Apply applies this to the given Attr.
func (a Attr) WithMask(mask AttrMask) Attr {
var b Attr
if mask.Mode {
b.Mode = a.Mode
}
if mask.NLink {
b.NLink = a.NLink
}
if mask.UID {
b.UID = a.UID
}
if mask.GID {
b.GID = a.GID
}
if mask.RDev {
b.RDev = a.RDev
}
if mask.ATime {
b.ATimeSeconds = a.ATimeSeconds
b.ATimeNanoSeconds = a.ATimeNanoSeconds
}
if mask.MTime {
b.MTimeSeconds = a.MTimeSeconds
b.MTimeNanoSeconds = a.MTimeNanoSeconds
}
if mask.CTime {
b.CTimeSeconds = a.CTimeSeconds
b.CTimeNanoSeconds = a.CTimeNanoSeconds
}
// Unclear on mask.INo. It corresponds to the inode number, but the
// inode number really is subsumed in the QID's path field normally and
// not accessible via GetAttr anyway.
if mask.Size {
b.Size = a.Size
}
if mask.Blocks {
b.Blocks = a.Blocks
// I don't know if Size or Blocks fills in BlockSize.
b.BlockSize = a.BlockSize
}
if mask.BTime {
b.BTimeSeconds = a.BTimeSeconds
b.BTimeNanoSeconds = a.BTimeNanoSeconds
}
if mask.Gen {
b.Gen = a.Gen
}
if mask.DataVersion {
b.DataVersion = a.DataVersion
}
return b
}
// encode implements encoder.encode.
func (a *Attr) encode(b *buffer) {
b.WriteFileMode(a.Mode)
b.WriteUID(a.UID)
b.WriteGID(a.GID)
b.Write64(uint64(a.NLink))
b.Write64(uint64(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 = NLink(b.Read64())
a.RDev = Dev(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 *internal.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 = NLink(s.Nlink)
}
if req.UID {
attr.UID = UID(s.Uid)
}
if req.GID {
attr.GID = GID(s.Gid)
}
if req.RDev {
attr.RDev = Dev(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
}
}
// Dirents is a collection of directory entries.
type Dirents []Dirent
// Find returns a Dirent with the given name if it exists, or nil.
func (d Dirents) Find(name string) *Dirent {
for _, dir := range d {
if dir.Name == name {
return &dir
}
}
return nil
}
// Dirent represents a directory entry in File.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)
}
// 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.
//
// Lock ordering:
//
// opMu
// childMu
//
// Two different pathNodes may only be locked if Server.renameMu is held for
// write, in which case they can be acquired in any order.
type pathNode struct {
// opMu synchronizes high-level, sematic operations, such as the
// simultaneous creation and deletion of a file.
opMu sync.RWMutex
// 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 is protected by opMu. However, it may be
// changed without opMu if this node is deleted as part of an entire subtree
// on unlink. So deleted must only be accessed/mutated using atomics.
deleted uint32
// childMu protects the fields below.
childMu sync.RWMutex
// childNodes maps child path component names to their pathNode.
childNodes map[string]*pathNode
// childRefs maps child path component names to all of the their
// references.
childRefs map[string]map[*fidRef]struct{}
// childRefNames maps child references back to their path component
// name.
childRefNames map[*fidRef]string
}
func newPathNode() *pathNode {
return &pathNode{
childNodes: make(map[string]*pathNode),
childRefs: make(map[string]map[*fidRef]struct{}),
childRefNames: make(map[*fidRef]string),
}
}
// forEachChildRef calls fn for each child reference.
func (p *pathNode) forEachChildRef(fn func(ref *fidRef, name string)) {
p.childMu.RLock()
defer p.childMu.RUnlock()
for name, m := range p.childRefs {
for ref := range m {
fn(ref, name)
}
}
}
// forEachChildNode calls fn for each child pathNode.
func (p *pathNode) forEachChildNode(fn func(pn *pathNode)) {
p.childMu.RLock()
defer p.childMu.RUnlock()
for _, pn := range p.childNodes {
fn(pn)
}
}
// pathNodeFor returns the path node for the given name, or a new one.
func (p *pathNode) pathNodeFor(name string) *pathNode {
p.childMu.RLock()
// Fast path, node already exists.
if pn, ok := p.childNodes[name]; ok {
p.childMu.RUnlock()
return pn
}
p.childMu.RUnlock()
// Slow path, create a new pathNode for shared use.
p.childMu.Lock()
// Re-check after re-lock.
if pn, ok := p.childNodes[name]; ok {
p.childMu.Unlock()
return pn
}
pn := newPathNode()
p.childNodes[name] = pn
p.childMu.Unlock()
return pn
}
// nameFor returns the name for the given fidRef.
//
// Precondition: addChild is called for ref before nameFor.
func (p *pathNode) nameFor(ref *fidRef) string {
p.childMu.RLock()
n, ok := p.childRefNames[ref]
p.childMu.RUnlock()
if !ok {
// This should not happen, don't proceed.
panic(fmt.Sprintf("expected name for %+v, none found", ref))
}
return n
}
// addChildLocked adds a child reference to p.
//
// Precondition: As addChild, plus childMu is locked for write.
func (p *pathNode) addChildLocked(ref *fidRef, name string) {
if n, ok := p.childRefNames[ref]; ok {
// This should not happen, don't proceed.
panic(fmt.Sprintf("unexpected fidRef %+v with path %q, wanted %q", ref, n, name))
}
p.childRefNames[ref] = name
m, ok := p.childRefs[name]
if !ok {
m = make(map[*fidRef]struct{})
p.childRefs[name] = m
}
m[ref] = struct{}{}
}
// addChild adds a child reference to p.
//
// Precondition: ref may only be added once at a time.
func (p *pathNode) addChild(ref *fidRef, name string) {
p.childMu.Lock()
p.addChildLocked(ref, name)
p.childMu.Unlock()
}
// removeChild removes the given child.
//
// This applies only to an individual fidRef, which is not required to exist.
func (p *pathNode) removeChild(ref *fidRef) {
p.childMu.Lock()
// This ref may not exist anymore. This can occur, e.g., in unlink,
// where a removeWithName removes the ref, and then a DecRef on the ref
// attempts to remove again.
if name, ok := p.childRefNames[ref]; ok {
m, ok := p.childRefs[name]
if !ok {
// This should not happen, don't proceed.
p.childMu.Unlock()
panic(fmt.Sprintf("name %s missing from childfidRefs", name))
}
delete(m, ref)
if len(m) == 0 {
delete(p.childRefs, name)
}
}
delete(p.childRefNames, ref)
p.childMu.Unlock()
}
// addPathNodeFor adds an existing pathNode as the node for name.
//
// Preconditions: newName does not exist.
func (p *pathNode) addPathNodeFor(name string, pn *pathNode) {
p.childMu.Lock()
if opn, ok := p.childNodes[name]; ok {
p.childMu.Unlock()
panic(fmt.Sprintf("unexpected pathNode %+v with path %q", opn, name))
}
p.childNodes[name] = pn
p.childMu.Unlock()
}
// removeWithName removes all references with the given name.
//
// The provided function is executed after reference removal. The only method
// it may (transitively) call on this pathNode is addChildLocked.
//
// If a child pathNode for name exists, it is removed from this pathNode and
// returned by this function. Any operations on the removed tree must use this
// value.
func (p *pathNode) removeWithName(name string, fn func(ref *fidRef)) *pathNode {
p.childMu.Lock()
defer p.childMu.Unlock()
if m, ok := p.childRefs[name]; ok {
for ref := range m {
delete(m, ref)
delete(p.childRefNames, ref)
if fn == nil {
continue
}
// Attempt to hold a reference while calling fn() to
// prevent concurrent destruction of the child, which
// can lead to data races. If the child has already
// been destroyed, then we can skip the callback.
if ref.TryIncRef() {
fn(ref)
ref.DecRef()
}
}
}
// Return the original path node, if it exists.
origPathNode := p.childNodes[name]
delete(p.childNodes, name)
return origPathNode
}
// 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
// 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()
}
// 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 (
"context"
"errors"
"fmt"
"io"
"net"
"runtime/debug"
"strings"
"sync"
"sync/atomic"
"github.com/hugelgupf/p9/linux"
"github.com/u-root/uio/ulog"
)
// 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
// log is a logger to log to, if specified.
log ulog.Logger
}
// ServerOpt is an optional config for a new server.
type ServerOpt func(s *Server)
// WithServerLogger overrides the default logger for the server.
func WithServerLogger(l ulog.Logger) ServerOpt {
return func(s *Server) {
s.log = l
}
}
// NewServer returns a new server.
func NewServer(attacher Attacher, o ...ServerOpt) *Server {
s := &Server{
attacher: attacher,
pathTree: newPathNode(),
log: ulog.Null,
}
for _, opt := range o {
opt(s)
}
return s
}
// connState is the state for a single connection.
type connState struct {
// server is the backing server.
server *Server
// 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
readBufPool sync.Pool
pristineZeros []byte
// baseVersion is the version of 9P protocol.
baseVersion baseVersion
// version is the agreed upon version X of 9P2000.L.Google.X.
// version 0 implies 9P2000.L.
version uint32
// pendingWg counts requests that are still being handled.
pendingWg sync.WaitGroup
// recvMu serializes receiving from t.
recvMu sync.Mutex
// recvIdle is the number of goroutines in handleRequests() attempting to
// lock recvMu so that they can receive from t. recvIdle is accessed
// using atomic memory operations.
recvIdle int32
// If recvShutdown is true, at least one goroutine has observed a
// connection error while receiving from t, and all goroutines in
// handleRequests() should exit immediately. recvShutdown is protected
// by recvMu.
recvShutdown bool
// sendMu serializes sending to r.
sendMu sync.Mutex
// t reads T messages and r write R messages
t io.ReadCloser
r io.WriteCloser
}
// xattrOp is the xattr related operations, walk or create.
type xattrOp int
const (
xattrNone = 0
xattrCreate = 1
xattrWalk = 2
)
type pendingXattr struct {
// the pending xattr-related operation
op xattrOp
// name is the attribute.
name string
// size of the attribute value, represents the
// length of the attribute value that is going to write to or read from a file.
size uint64
// flags associated with a txattrcreate message.
// generally Linux setxattr(2) flags.
flags XattrFlags
// saved up xattr operation value (for reads, listed / gotten buffer --
// ready for chunking; for writes, this is used to accumulate chunked
// values until a Tclunk actuates the operation)
buf []byte
}
// fidRef wraps a node and tracks references.
type fidRef struct {
// server is the associated server.
server *Server
// file is the associated File.
file File
// pendingXattr is the xattr-related operations that are going to be done
// in a tread or twrite request.
pendingXattr pendingXattr
// refs is an active refence count.
//
// The node above will be closed only when refs reaches zero.
refs int64
// opened indicates whether this has been opened already.
//
// This is updated in handlers.go.
//
// opened is protected by pathNode.opMu or renameMu (for write).
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.
//
// opened is protected by pathNode.opMu or renameMu (for write).
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
}
// 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() error {
if atomic.AddInt64(&f.refs, -1) == 0 {
var (
errs []error
err = f.file.Close()
)
if err != nil {
err = fmt.Errorf("file: %w", err)
errs = append(errs, err)
}
// 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, removing this
// ref is a no-op. That's expected.
f.parent.pathNode.removeChild(f)
if pErr := f.parent.DecRef(); pErr != nil {
pErr = fmt.Errorf("parent: %w", pErr)
errs = append(errs, pErr)
}
}
return errors.Join(errs...)
}
return nil
}
// TryIncRef returns true if a new reference is taken on the fid, and false if
// the fid has been destroyed.
func (f *fidRef) TryIncRef() bool {
for {
r := atomic.LoadInt64(&f.refs)
if r <= 0 {
return false
}
if atomic.CompareAndSwapInt64(&f.refs, r, r+1) {
return true
}
}
}
// isDeleted returns true if this fidRef has been deleted.
//
// Precondition: this must be called via safelyRead, safelyWrite or
// safelyGlobal.
func (f *fidRef) isDeleted() bool {
return atomic.LoadUint32(&f.pathNode.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: this must be called via safelyWrite or safelyGlobal.
func notifyDelete(pn *pathNode) {
atomic.StoreUint32(&pn.deleted, 1)
// Call on all subtrees.
pn.forEachChildNode(func(pn *pathNode) {
notifyDelete(pn)
})
}
// 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) {
if origPathNode := f.pathNode.removeWithName(name, nil); origPathNode != nil {
// Mark all children 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: this must be called via safelyGlobal.
func notifyNameChange(pn *pathNode) {
// Call on all local references.
pn.forEachChildRef(func(ref *fidRef, name string) {
ref.file.Renamed(ref.parent.file, name)
})
// Call on all subtrees.
pn.forEachChildNode(func(pn *pathNode) {
notifyNameChange(pn)
})
}
// 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) {
// N.B. DecRef can take f.pathNode's parent's childMu. This is
// allowed because renameMu is held for write via safelyGlobal.
ref.parent.DecRef() // Drop original reference.
ref.parent = target // Change parent.
ref.parent.IncRef() // Acquire new one.
if f.pathNode == target.pathNode {
target.pathNode.addChildLocked(ref, newName)
} else {
target.pathNode.addChild(ref, newName)
}
ref.file.Renamed(target.file, newName)
})
if origPathNode != nil {
// Replace the previous (now deleted) path node.
target.pathNode.addPathNodeFor(newName, origPathNode)
// Call Renamed on all children.
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.opMu.RLock()
defer f.pathNode.opMu.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.opMu.Lock()
defer f.pathNode.opMu.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) error {
cs.fidMu.Lock()
defer cs.fidMu.Unlock()
fidRef, ok := cs.fids[fid]
if !ok {
return linux.EBADF
}
delete(cs.fids, fid)
return fidRef.DecRef()
}
// 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() bool {
cs.pendingWg.Add(1)
defer cs.pendingWg.Done()
// Obtain the right to receive a message from cs.t.
atomic.AddInt32(&cs.recvIdle, 1)
cs.recvMu.Lock()
atomic.AddInt32(&cs.recvIdle, -1)
if cs.recvShutdown {
// Another goroutine already detected a connection problem; exit
// immediately.
cs.recvMu.Unlock()
return false
}
messageSize := atomic.LoadUint32(&cs.messageSize)
if messageSize == 0 {
// Default or not yet negotiated.
messageSize = maximumLength
}
// Receive a message.
tag, m, err := recv(cs.server.log, cs.t, messageSize, msgDotLRegistry.get)
if errSocket, ok := err.(ConnError); ok {
if errSocket.error != io.EOF {
// Connection problem; stop serving.
cs.server.log.Printf("p9.recv: %v", errSocket.error)
}
cs.recvShutdown = true
cs.recvMu.Unlock()
return false
}
// Ensure that another goroutine is available to receive from cs.t.
if atomic.LoadInt32(&cs.recvIdle) == 0 {
go cs.handleRequests() // S/R-SAFE: Irrelevant.
}
cs.recvMu.Unlock()
// 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.server.log, cs.r, tag, newErr(err))
cs.sendMu.Unlock()
if err != nil {
cs.server.log.Printf("p9.send: %v", err)
}
return true
}
// Try to start the tag.
if !cs.StartTag(tag) {
cs.server.log.Printf("no valid tag [%05d]", tag)
// Nothing we can do at this point; client is bogus.
return true
}
// Handle the message.
r := cs.handle(m)
// 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.server.log, cs.r, tag, r)
cs.sendMu.Unlock()
if err != nil {
cs.server.log.Printf("p9.send: %v", err)
}
msgDotLRegistry.put(m)
m = nil // 'm' should not be touched after this point.
return true
}
func (cs *connState) handle(m message) (r message) {
defer func() {
if r == nil {
// Don't allow a panic to propagate.
err := recover()
// Include a useful log message.
cs.server.log.Printf("panic in handler - %v: %s", err, 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(linux.EFAULT)
}
}()
if handler, ok := m.(handler); ok {
// Call the message handler.
r = handler.handle(cs)
} else {
// Produce an ENOSYS error.
r = newErr(linux.ENOSYS)
}
return
}
func (cs *connState) handleRequests() {
for {
if !cs.handleRequest() {
return
}
}
}
func (cs *connState) stop() {
// Wait for completion of all inflight request goroutines.. If a
// request is stuck, something has the opportunity to kill us with
// SIGABRT to get a stack dump of the offending handler.
cs.pendingWg.Wait()
// Ensure the connection is closed.
cs.r.Close()
cs.t.Close()
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()
}
}
// Handle handles a single connection.
func (s *Server) Handle(t io.ReadCloser, r io.WriteCloser) error {
cs := &connState{
server: s,
t: t,
r: r,
fids: make(map[fid]*fidRef),
tags: make(map[tag]chan struct{}),
}
defer cs.stop()
// Serve requests from t in the current goroutine; handleRequests()
// will create more goroutines as needed.
cs.handleRequests()
return nil
}
func isErrClosing(err error) bool {
return strings.Contains(err.Error(), "use of closed network connection")
}
// Serve handles requests from the bound socket.
//
// The passed serverSocket _must_ be created in packet mode.
func (s *Server) Serve(serverSocket net.Listener) error {
return s.ServeContext(nil, serverSocket)
}
var errAlreadyClosed = errors.New("already closed")
// ServeContext handles requests from the bound socket.
//
// The passed serverSocket _must_ be created in packet mode.
//
// When the context is done, the listener is closed and serve returns once
// every request has been handled.
func (s *Server) ServeContext(ctx context.Context, serverSocket net.Listener) error {
var wg sync.WaitGroup
defer wg.Wait()
var cancelCause context.CancelCauseFunc
if ctx != nil {
ctx, cancelCause = context.WithCancelCause(ctx)
wg.Add(1)
go func() {
defer wg.Done()
<-ctx.Done()
// Only close the server socket if it wasn't already closed.
if err := ctx.Err(); errors.Is(err, errAlreadyClosed) {
return
}
serverSocket.Close()
}()
}
for {
conn, err := serverSocket.Accept()
if err != nil {
if cancelCause != nil {
cancelCause(errAlreadyClosed)
}
if isErrClosing(err) {
return nil
}
// Something went wrong.
return err
}
wg.Add(1)
go func(conn net.Conn) { // S/R-SAFE: Irrelevant.
s.Handle(conn, conn)
wg.Done()
}(conn)
}
}
// 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"
"net"
"sync"
"github.com/hugelgupf/p9/vecnet"
"github.com/u-root/uio/ulog"
)
// ConnError is returned in cases of a connection issue.
//
// This may be treated differently than other errors.
type ConnError struct {
// error is the socket error.
error
}
func (e ConnError) Error() string {
return fmt.Sprintf("socket error: %v", e.error)
}
// Is reports whether any error in err's chain matches target.
func (e ConnError) Is(target error) bool { return target == e.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.
// We need to return a pointer to avoid unnecessary allocations
// (see https://staticcheck.io/docs/checks#SA6002).
b := make([]byte, initialBufferLength)
return &b
},
}
// send sends the given message over the socket.
func send(l ulog.Logger, w io.Writer, tag tag, m message) error {
data := dataPool.Get().(*[]byte)
dataBuf := buffer{data: (*data)[:0]}
// Encode the message. The buffer will grow automatically.
m.encode(&dataBuf)
l.Printf("send [w %p] [Tag %06d] %s", w, tag, m)
// Get our vectors to send.
var hdr [headerLength]byte
vecs := make(net.Buffers, 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))
}
defer payloader.PayloadCleanup()
}
// Construct the header.
headerBuf := buffer{data: hdr[:0]}
headerBuf.Write32(totalLength)
headerBuf.WriteMsgType(m.typ())
headerBuf.WriteTag(tag)
if _, err := vecs.WriteTo(w); err != nil {
return ConnError{err}
}
// 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(l ulog.Logger, r io.Reader, msize uint32, lookup lookupTagAndType) (tag, message, error) {
// Read a header.
var hdr [headerLength]byte
if _, err := io.ReadAtLeast(r, hdr[:], int(headerLength)); err != nil {
return noTag, nil, ConnError{err}
}
// 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, ConnError{ErrNoValidMessage}
}
if size > maximumLength || size > msize {
// The message is too big.
return noTag, nil, ConnError{&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.LimitReader(r, int64(remaining)))
}
return tag, nil, err
}
// Not yet initialized.
var dataBuf buffer
var vecs vecnet.Buffers
appendBuffer := func(size int) *[]byte {
// Pull a data buffer from the pool.
datap := dataPool.Get().(*[]byte)
data := *datap
if size > len(data) {
// Create a larger data buffer.
data = make([]byte, size)
datap = &data
} else {
// Limit the data buffer.
data = data[:size]
}
dataBuf = buffer{data: data}
vecs = append(vecs, data)
return datap
}
// 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.
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.LimitReader(r, int64(remaining)))
}
return noTag, nil, ErrNoValidMessage
}
if fixedSize != 0 {
datap := appendBuffer(int(fixedSize))
defer dataPool.Put(datap)
}
// 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 {
datap := appendBuffer(int(remaining))
defer dataPool.Put(datap)
}
if len(vecs) > 0 {
if _, err := vecs.ReadFrom(r); err != nil {
return noTag, nil, ConnError{err}
}
}
// Decode the message data.
m.decode(&dataBuf)
if dataBuf.isOverrun() {
// No need to drain the socket.
return noTag, nil, ErrNoValidMessage
}
l.Printf("recv [r %p] [Tag %06d] %s", r, tag, m)
// All set.
return tag, m, nil
}
// 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
)
type baseVersion string
const (
undetermined baseVersion = ""
version9P2000 baseVersion = "9P2000"
version9P2000U baseVersion = "9P2000.u"
version9P2000L 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(version9P2000L, 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) (baseVersion, uint32, bool) {
switch str {
case "9P2000.L":
return version9P2000L, 0, true
case "9P2000.u":
return version9P2000U, 0, true
case "9P2000":
return version9P2000, 0, true
default:
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 version9P2000L, uint32(version), true
}
}
// versionString formats a p9 version number into a Tversion version string.
func versionString(baseVersion baseVersion, 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 string(baseVersion)
}
return fmt.Sprintf("9P2000.L.Google.%d", version)
}
// 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
}
// VersionSupportsMultiUser returns true if version v supports multi-user fake
// directory permissions and ID values.
func VersionSupportsMultiUser(v uint32) bool {
return v >= 6
}
// 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.
//go:build !386 && !arm && !mips && !mipsle
// +build !386,!arm,!mips,!mipsle
package vecnet
func iovlen(i int) uint64 {
return uint64(i)
}
// 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 vecnet provides access to recvmsg syscalls on net.Conns.
package vecnet
import (
"io"
"net"
"syscall"
)
// Buffers points to zero or more buffers to read into.
//
// On connections that support it, ReadFrom is optimized into the batch read
// operation recvmsg.
type Buffers net.Buffers
// ReadFrom reads into the pre-allocated bufs. Returns bytes read.
//
// ReadFrom keeps reading until all bufs are filled or EOF is received.
//
// The pre-allocatted space used by ReadFrom is based upon slice lengths.
func (bufs Buffers) ReadFrom(r io.Reader) (int64, error) {
if conn, ok := r.(syscall.Conn); ok && readFromBuffers != nil {
return readFromBuffers(bufs, conn)
}
var total int64
for _, buf := range bufs {
for filled := 0; filled < len(buf); {
n, err := r.Read(buf[filled:])
total += int64(n)
filled += n
if (n == 0 && err == nil) || err == io.EOF {
return total, io.EOF
} else if err != nil {
return total, err
}
}
}
return total, nil
}
// 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.
//go:build !386
// +build !386
package vecnet
import (
"io"
"runtime"
"syscall"
"unsafe"
)
var readFromBuffers = readFromBuffersLinux
func readFromBuffersLinux(bufs Buffers, conn syscall.Conn) (int64, error) {
rc, err := conn.SyscallConn()
if err != nil {
return 0, err
}
length := int64(0)
for _, buf := range bufs {
length += int64(len(buf))
}
for n := int64(0); n < length; {
cur, err := recvmsg(bufs, rc)
if err != nil && (cur == 0 || err != io.EOF) {
return n, err
}
n += int64(cur)
// Consume iovecs to retry.
for consumed := 0; consumed < cur; {
if len(bufs[0]) <= cur-consumed {
consumed += len(bufs[0])
bufs = bufs[1:]
} else {
bufs[0] = bufs[0][cur-consumed:]
break
}
}
}
return length, nil
}
// buildIovec builds an iovec slice from the given []byte slice.
//
// iovecs is used as an initial slice, to avoid excessive allocations.
func buildIovec(bufs Buffers, iovecs []syscall.Iovec) ([]syscall.Iovec, int) {
var length int
for _, buf := range bufs {
if l := len(buf); l > 0 {
iovecs = append(iovecs, syscall.Iovec{
Base: &buf[0],
Len: iovlen(l),
})
length += l
}
}
return iovecs, length
}
func recvmsg(bufs Buffers, rc syscall.RawConn) (int, error) {
iovecs, length := buildIovec(bufs, make([]syscall.Iovec, 0, 2))
var msg syscall.Msghdr
if len(iovecs) != 0 {
msg.Iov = &iovecs[0]
msg.Iovlen = iovlen(len(iovecs))
}
// n is the bytes received.
var n uintptr
var e syscall.Errno
err := rc.Read(func(fd uintptr) bool {
n, _, e = syscall.Syscall(syscall.SYS_RECVMSG, fd, uintptr(unsafe.Pointer(&msg)), syscall.MSG_DONTWAIT)
// Return false if EINTR, EAGAIN, or EWOULDBLOCK to retry.
return !(e == syscall.EINTR || e == syscall.EAGAIN || e == syscall.EWOULDBLOCK)
})
runtime.KeepAlive(iovecs)
if err != nil {
return 0, err
}
if e != 0 {
return 0, e
}
// The other end is closed by returning a 0 length read with no error.
if n == 0 {
return 0, io.EOF
}
if int(n) > length {
return length, io.ErrShortBuffer
}
return int(n), nil
}