commit - 97423e8b7909c86abde24bd416798b1172759758
commit + fb37ce2aa1f717002944b5aec393b3318e2e7261
blob - 49f36bf99fe75ae3bdfb40e35d93b4c68c440fac
blob + 30c2205d3c5f55a717ead6a71e823d1445b868fa
--- client.go
+++ client.go
)
type client struct {
+ version string
+ msize uint32
ctx context.Context
transport roundTripper
}
// a context for out of bad messages, such as flushes, that may be sent by the
// session. The session can effectively shutdown with this context.
func NewSession(ctx context.Context, conn net.Conn) (Session, error) {
+ const msize = 64 << 10
+ const vers = "9P2000"
+
+ ch := newChannel(conn, codec9p{}, msize) // sets msize, effectively.
+
+ // negotiate the protocol version
+ smsize, svers, err := version(ctx, ch, msize, vers)
+ if err != nil {
+ return nil, err
+ }
+
+ if svers != vers {
+ // TODO(stevvooe): A stubborn client indeed!
+ return nil, fmt.Errorf("unsupported server version: %v", vers)
+ }
+
+ if smsize > msize {
+ // upgrade msize if server differs.
+ ch.setmsize(int(smsize))
+ }
+
return &client{
+ version: vers,
+ msize: msize,
ctx: ctx,
- transport: newTransport(ctx, conn),
+ transport: newTransport(ctx, ch),
}, nil
}
var _ Session = &client{}
+func (c *client) Version() (uint32, string) {
+ return c.msize, c.version
+}
+
func (c *client) Auth(ctx context.Context, afid Fid, uname, aname string) (Qid, error) {
panic("not implemented")
}
return Qid{}, 0, fmt.Errorf("invalid rpc response for open message: %v", resp)
}
- return respmsg.Qid, respmsg.Msize, nil
+ return respmsg.Qid, respmsg.IOUnit, nil
}
-func (c *client) Create(ctx context.Context, parent Fid, name string, perm uint32, mode uint32) (Qid, error) {
+func (c *client) Create(ctx context.Context, parent Fid, name string, perm uint32, mode uint32) (Qid, uint32, error) {
panic("not implemented")
}
panic("not implemented")
}
-func (c *client) Version(ctx context.Context, msize uint32, version string) (uint32, string, error) {
- msg := MessageTversion{
+func (c *client) flush(ctx context.Context, tag Tag) error {
+ // TODO(stevvooe): We need to fire and forget flush messages when a call
+ // context gets cancelled.
+
+ panic("not implemented")
+}
+
+// version negiotiates the protocol version using channel, blocking until a
+// response is received. This should be called before starting the transport.
+func version(ctx context.Context, ch *channel, msize uint32, version string) (uint32, string, error) {
+ req := newFcall(MessageTversion{
MSize: uint32(msize),
Version: version,
+ })
+
+ if err := ch.writeFcall(ctx, req); err != nil {
+ return 0, "", err
}
- fcall := newFcall(msg)
-
- resp, err := c.transport.send(ctx, fcall)
- if err != nil {
+ resp := new(Fcall)
+ if err := ch.readFcall(ctx, resp); err != nil {
return 0, "", err
}
return 0, "", fmt.Errorf("invalid rpc response for version message: %v", resp)
}
- // TODO(stevvooe): Use this response to set iounit and version on this
- // client instance.
-
return mv.MSize, mv.Version, nil
}
-
-func (c *client) flush(ctx context.Context, tag Tag) error {
- // TODO(stevvooe): We need to fire and forget flush messages when a call
- // context gets cancelled.
-
- panic("not implemented")
-}
blob - /dev/null
blob + 9de03f0d26e4cad75685fe0929ef3cc725232ec9 (mode 644)
--- /dev/null
+++ channel.go
+package p9pnew
+
+import (
+ "bufio"
+ "encoding/binary"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "net"
+
+ "golang.org/x/net/context"
+)
+
+// channel provides bidirectional protocol framing for 9p over net.Conn.
+// Operations are not thread-safe but reads and writes may be carried out
+// concurrently, supporting separate read and write loops.
+type channel struct {
+ conn net.Conn
+ codec Codec
+ brd *bufio.Reader
+ bwr *bufio.Writer
+ closed chan struct{}
+ rdbuf []byte
+ wrbuf []byte
+}
+
+func newChannel(conn net.Conn, codec Codec, msize int) *channel {
+ return &channel{
+ conn: conn,
+ codec: codec,
+ brd: bufio.NewReaderSize(conn, msize), // msize may not be optimal buffer size
+ bwr: bufio.NewWriterSize(conn, msize),
+ closed: make(chan struct{}),
+ rdbuf: make([]byte, msize),
+ wrbuf: make([]byte, msize),
+ }
+}
+
+// setmsize resizes the buffers for use with a separate msize. This call must
+// be protected by a mutex or made before passing to other goroutines.
+func (ch *channel) setmsize(msize int) {
+ // NOTE(stevvooe): We cannot safely resize the buffered reader and writer.
+ // Proceed assuming that original size is sufficient.
+
+ if msize < len(ch.rdbuf) {
+ // just change the cap
+ ch.rdbuf = ch.rdbuf[:msize]
+ ch.wrbuf = ch.wrbuf[:msize]
+ return
+ }
+
+ ch.rdbuf = make([]byte, msize)
+ ch.wrbuf = make([]byte, msize)
+}
+
+// ReadFcall reads the next message from the channel into fcall.
+func (ch *channel) readFcall(ctx context.Context, fcall *Fcall) error {
+ select {
+ case <-ch.closed:
+ return ErrClosed
+ default:
+ }
+ log.Println("channel: readFcall", fcall)
+
+ if deadline, ok := ctx.Deadline(); ok {
+ if err := ch.conn.SetReadDeadline(deadline); err != nil {
+ log.Printf("transport: error setting read deadline on %v: %v", ch.conn.RemoteAddr(), err)
+ }
+ }
+
+ n, err := readmsg(ch.brd, ch.rdbuf)
+ if err != nil {
+ // TODO(stevvooe): There may be more we can do here to detect partial
+ // reads. For now, we just propagate the error untouched.
+ return err
+ }
+
+ if n > len(ch.rdbuf) {
+ // TODO(stevvooe): Make this error detectable and respond with error
+ // message.
+ return fmt.Errorf("message large than buffer:", n)
+ }
+
+ return ch.codec.Unmarshal(ch.rdbuf[:n], fcall)
+}
+
+func (ch *channel) writeFcall(ctx context.Context, fcall *Fcall) error {
+ select {
+ case <-ch.closed:
+ return ErrClosed
+ default:
+ }
+ log.Println("channel: writeFcall", fcall)
+
+ if deadline, ok := ctx.Deadline(); ok {
+ if err := ch.conn.SetWriteDeadline(deadline); err != nil {
+ log.Printf("transport: error setting read deadline on %v: %v", ch.conn.RemoteAddr(), err)
+ }
+ }
+
+ n, err := ch.codec.Marshal(ch.wrbuf, fcall)
+ if err != nil {
+ return err
+ }
+
+ p := ch.wrbuf[:n]
+
+ if err := sendmsg(ch.bwr, p); err != nil {
+ return err
+ }
+
+ return ch.bwr.Flush()
+}
+
+// readmsg reads a 9p message into p from rd, ensuring that all bytes are
+// consumed from the size header. If the size header indicates the message is
+// larger than p, the entire message will be discarded, leaving a truncated
+// portion in p. Any error should be treated as a framing error unless n is
+// zero. The caller must check that n is less than or equal to len(p) to
+// ensure that a valid message has been read.
+func readmsg(rd io.Reader, p []byte) (n int, err error) {
+ var msize uint32
+
+ if err := binary.Read(rd, binary.LittleEndian, &msize); err != nil {
+ return 0, err
+ }
+
+ n += binary.Size(msize)
+ mbody := int(msize) - 4
+
+ if mbody < len(p) {
+ p = p[:mbody]
+ }
+
+ np, err := io.ReadFull(rd, p)
+ if err != nil {
+ return np + n, err
+ }
+ n += np
+
+ if mbody > len(p) {
+ // message has been read up to len(p) but we must consume the entire
+ // message. This is an error condition but is non-fatal if we can
+ // consume msize bytes.
+ nn, err := io.CopyN(ioutil.Discard, rd, int64(mbody-len(p)))
+ n += int(nn)
+ if err != nil {
+ return n, err
+ }
+ }
+
+ return n, nil
+}
+
+// sendmsg writes a message of len(p) to wr with a 9p size header. All errors
+// should be considered terminal.
+func sendmsg(wr io.Writer, p []byte) error {
+ size := uint32(len(p) + 4) // message size plus 4-bytes for size.
+ if err := binary.Write(wr, binary.LittleEndian, size); err != nil {
+ return nil
+ }
+
+ // This assume partial writes to wr aren't possible. Not sure if this
+ // valid. Matters during timeout retries.
+ if n, err := wr.Write(p); err != nil {
+ return err
+ } else if n < len(p) {
+ return io.ErrShortWrite
+ }
+
+ return nil
+}
blob - d003a08067db2beb05f4e2124a29c59f1d0a43b8
blob + 1db7807e7ff9d6b0336ace83a50ede2453e8270a
--- cmd/9pr/main.go
+++ cmd/9pr/main.go
}
commander.readline = rl
+ msize, version := commander.session.Version()
+ if err != nil {
+ log.Fatalln(err)
+ }
+ log.Println("9p version", version, msize)
+
log.Println("attach root")
// attach root
commander.nextfid = 1
}
}
- ctx, _ = context.WithTimeout(commander.ctx, time.Second)
+ ctx, _ = context.WithTimeout(commander.ctx, 5*time.Second)
if err := cmd(ctx, args[1:]...); err != nil {
log.Printf("👹 %s: %v", name, err)
}
}
defer c.session.Clunk(ctx, targetfid)
- if _, _, err := c.session.Open(ctx, targetfid, p9pnew.OREAD); err != nil {
+ _, msize, err := c.session.Open(ctx, targetfid, p9pnew.OREAD)
+ if err != nil {
return err
}
- p := make([]byte, 4<<20)
+ p := make([]byte, msize)
n, err := c.session.Read(ctx, targetfid, p, 0)
if err != nil {
return fi.dir.Qid, 4 << 20, nil
}
-func (s *simpleSession) Create(ctx context.Context, parent p9pnew.Fid, name string, perm uint32, mode uint32) (p9pnew.Qid, error) {
+func (s *simpleSession) Create(ctx context.Context, parent p9pnew.Fid, name string, perm uint32, mode uint32) (p9pnew.Qid, uint32, error) {
panic("not implemented")
}
panic("not implemented")
}
-// TODO(stevvooe): The version message affects a lot of protocol behavior.
-// Consider hiding it behind the implementation, letting the version get
-// negotiated. The API user should still be able to query it.
-func (s *simpleSession) Version(ctx context.Context, msize uint32, version string) (uint32, string, error) {
- return 4096, "9P2000", nil
+func (s *simpleSession) Version() (uint32, string) {
+ return 64 << 10, "9P2000"
}
blob - 200e30422567e89a0b07142ec27544ad2d5364dc
blob + 10d52740dedc0b060356f11de11880aee4f26b6b
--- encoding.go
+++ encoding.go
package p9pnew
import (
+ "bytes"
"encoding/binary"
"fmt"
"io"
func DecodeDir(rd io.Reader, d *Dir) error {
dec := &decoder{rd}
return dec.decode(d)
+}
+
+type Codec interface {
+ Unmarshal(data []byte, v interface{}) error
+ Marshal(data []byte, v interface{}) (n int, err error)
+}
+
+type codec9p struct{}
+
+func (c codec9p) Unmarshal(data []byte, v interface{}) error {
+ dec := &decoder{bytes.NewReader(data)}
+ return dec.decode(v)
+}
+
+func (c codec9p) Marshal(data []byte, v interface{}) (n int, err error) {
+ n = int(size9p(v))
+
+ buf := bytes.NewBuffer(data[:0])
+ enc := &encoder{buf}
+
+ if err := enc.encode(v); err != nil {
+ return buf.Len(), nil
+ }
+
+ if len(data) < buf.Len() {
+ return len(data), io.ErrShortBuffer
+ }
+
+ return buf.Len(), nil
}
// NOTE(stevvooe): This file covers 9p encoding and decoding (despite just
return err
}
case *Fcall:
- if err := e.encode(size9p(v), v.Type, v.Tag, v.Message); err != nil {
+ if err := e.encode(v.Type, v.Tag, v.Message); err != nil {
return err
}
default:
return err
}
case *Fcall:
- var size uint32
- if err := d.decode(&size, &v.Type, &v.Tag); err != nil {
+ if err := d.decode(&v.Type, &v.Tag); err != nil {
return err
}
var err error
v.Message, err = newMessage(v.Type)
if err != nil {
+ log.Printf("unknown message type %#v", v.Type)
return err
}
+ // take the address of v.Message from the struct and encode into
+ // that.
+
if err := d.decode(v.Message); err != nil {
return err
}
s += size9p(elements...)
case Fcall:
- s += size9p(uint32(0), v.Type, v.Tag, v.Message)
+ s += size9p(v.Type, v.Tag, v.Message)
case *Fcall:
s += size9p(*v)
default:
return nil, fmt.Errorf("can't interface: %v", f)
}
- if !f.CanSet() {
- panic("asdf")
- return nil, fmt.Errorf("cannot set %v", f)
- }
-
if f.CanAddr() {
f = f.Addr()
}
blob - 6e11cca7e4518ea4c56ec3c5b7877dce8cd47fb6
blob + 31ff66b1312bc7cae6d6b1ec9f8cc17ece4b5744
--- encoding_test.go
+++ encoding_test.go
},
},
marshaled: []byte{
- 0x13, 0x0, 0x0, 0x0,
0x64, 0xcf, 0x8, 0x0, 0x4, 0x0, 0x0,
0x6, 0x0, 0x39, 0x50, 0x54, 0x45, 0x53, 0x54},
},
},
},
marshaled: []byte{
- 0x13, 0x0, 0x0, 0x0,
0x65, 0xcf, 0x8, 0x0, 0x4, 0x0, 0x0,
0x6, 0x0, 0x39, 0x50, 0x54, 0x45, 0x53, 0x54},
},
},
},
marshaled: []byte{
- 0x1a, 0x0, 0x0, 0x0,
0x6e, 0x22, 0x16, 0xf2, 0x3, 0x0, 0x0, 0xf3, 0x3, 0x0, 0x0,
0x3, 0x0, // len(wnames)
0x1, 0x0, 0x61, // "a"
},
},
marshaled: []byte{
- 0x23, 0x0, 0x0, 0x0,
0x6f, 0xb4, 0x15,
0x2, 0x0,
0x80, 0x68, 0x2b, 0x0, 0x0, 0x57, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
},
},
marshaled: []byte{
- 0x1b, 0x0, 0x0, 0x0,
0x75, 0xb4, 0x15,
0x12, 0x0,
0x61, 0x20, 0x6c, 0x6f, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x62, 0x79, 0x74, 0x65, 0x20, 0x64, 0x61, 0x74, 0x61},
},
},
marshaled: []byte{
- 0x47, 0x0, 0x0, 0x0,
0x7d, 0xb4, 0x15,
// 0x40, 0x0, // TODO(stevvooe): Include Dir size. Not straightforward.
0xff, 0xff, // type
blob - 9e03f7e1d58923ae84789c8cf90fab13bc7c9b6e
blob + 5689477107327c6ad5e089a80ad44d7096a206a3
--- fcall.go
+++ fcall.go
func newFcall(msg Message) *Fcall {
return &Fcall{
Type: msg.Type(),
+ Tag: NOTAG,
Message: msg,
}
}
}
type MessageRopen struct {
- Qid Qid
- Msize uint32
+ Qid Qid
+ IOUnit uint32
}
type MessageTcreate struct {
blob - db1076f7a47a1f6a1475b98a52947c464adaccc5
blob + 325347028d2ac5a31e07eb8ed43af174b5a3beb1
--- server.go
+++ server.go
log.Println("server:", "wait")
fcall := new(Fcall)
+
+ // BUG(stevvooe): The decoder is not reliably consuming all of the
+ // bytes of the wire. Needs to be setup to consume all bytes indicated
+ // in the size portion. Should use msize from the version negotiation.
+
if err := dec.decode(fcall); err != nil {
log.Println("server decoding fcall:", err)
continue
return nil, fmt.Errorf("bad message: %v message=%v", req, req.Message)
}
- qid, msize, err := s.session.Open(ctx, reqmsg.Fid, reqmsg.Mode)
+ qid, iounit, err := s.session.Open(ctx, reqmsg.Fid, reqmsg.Mode)
if err != nil {
return nil, err
}
resp = newFcall(&MessageRopen{
- Qid: qid,
- Msize: msize,
+ Qid: qid,
+ IOUnit: iounit,
})
case Tread:
reqmsg, ok := req.Message.(*MessageTread)
blob - 37779b341b219f43c948e22ba0287ac579e16625
blob + 91e26ec436170573317f400b7da56a6a2971d62b
--- session.go
+++ session.go
Read(ctx context.Context, fid Fid, p []byte, offset int64) (n int, err error)
Write(ctx context.Context, fid Fid, p []byte, offset int64) (n int, err error)
Open(ctx context.Context, fid Fid, mode uint8) (Qid, uint32, error)
- Create(ctx context.Context, parent Fid, name string, perm uint32, mode uint32) (Qid, error)
+ Create(ctx context.Context, parent Fid, name string, perm uint32, mode uint32) (Qid, uint32, error)
Stat(context.Context, Fid) (Dir, error)
WStat(context.Context, Fid, Dir) error
- // TODO(stevvooe): The version message affects a lot of protocol behavior.
- // Consider hiding it behind the implementation, letting the version get
- // negotiated. The API user should still be able to query it.
- Version(ctx context.Context, msize uint32, version string) (uint32, string, error)
+ // Version returns the supported version and msize of the session. This
+ // can be affected by negotiating or the level of support provided by the
+ // session implementation.
+ Version() (msize uint32, version string)
}
func Dial(ctx context.Context, addr string) (Session, error) {
blob - 7c956dd15cc2364349bdac627b8a8af380784de7
blob + 4b25bc02d50aef020b58e0cb0ca06ea43cedede0
--- transport.go
+++ transport.go
package p9pnew
import (
- "bufio"
"fmt"
"log"
"net"
- "time"
"golang.org/x/net/context"
)
type transport struct {
ctx context.Context
- conn net.Conn
+ ch *channel
requests chan *fcallRequest
closed chan struct{}
tags uint16
}
-func newTransport(ctx context.Context, conn net.Conn) roundTripper {
+func newTransport(ctx context.Context, ch *channel) roundTripper {
t := &transport{
ctx: ctx,
- conn: conn,
+ ch: ch,
requests: make(chan *fcallRequest),
closed: make(chan struct{}),
}
case <-ctx.Done():
return nil, ctx.Err()
case resp := <-req.response:
+ log.Println("resp", resp)
if resp.Type == Rerror {
// pack the error into something useful
respmesg, ok := resp.Message.(*MessageRerror)
tags Tag
// outstanding provides a map of tags to outstanding requests.
outstanding = map[Tag]*fcallRequest{}
- brd = bufio.NewReader(t.conn)
- bwr = bufio.NewWriter(t.conn)
- enc = &encoder{bwr}
- dec = &decoder{brd}
)
// loop to read messages off of the connection
go func() {
-
+ defer func() {
+ log.Println("exited read loop")
+ close(t.closed)
+ }()
loop:
for {
- const pump = time.Second
-
- // Continuously set the read dead line pump the loop below. We can
- // probably set a connection dead threshold that can count these.
- // Usually, this would only matter when there are actually
- // outstanding requests.
- deadline, ok := t.ctx.Deadline()
- if !ok {
- deadline = time.Now().Add(pump)
- } else {
- // if the deadline is before
- nd := time.Now().Add(pump)
- if nd.Before(deadline) {
- deadline = nd
- }
- }
-
- if err := t.conn.SetReadDeadline(deadline); err != nil {
- log.Printf("error setting read deadline: %v", err)
- }
-
- fc := new(Fcall)
- if err := dec.decode(fc); err != nil {
+ fcall := new(Fcall)
+ if err := t.ch.readFcall(t.ctx, fcall); err != nil {
switch err := err.(type) {
case net.Error:
if err.Timeout() || err.Temporary() {
- break loop
+ // BUG(stevvooe): There may be partial reads under
+ // timeout errors where this is actually fatal.
+
+ // can only retry if we haven't offset the frame.
+ continue loop
}
}
- panic(fmt.Sprintf("connection read error: %v", err))
+ log.Println("fatal error reading msg:", err)
+ t.Close()
+ return
}
select {
case <-t.ctx.Done():
+ log.Println("ctx done")
return
case <-t.closed:
+ log.Println("transport closed")
return
- case responses <- fc:
+ case responses <- fcall:
}
}
}()
for {
select {
case req := <-t.requests:
- tags++
- req.fcall.Tag = tags
- outstanding[req.fcall.Tag] = req
- // use deadline to set write deadline for this request.
- deadline, ok := req.ctx.Deadline()
- if !ok {
- deadline = time.Now().Add(time.Second)
+ log.Println("send", req.fcall)
+ if req.fcall.Type != Tversion {
+ tags++
+ req.fcall.Tag = tags
+ outstanding[req.fcall.Tag] = req
+ } else {
+ // TODO(stevvooe): Man this protocol is bad. Version messages
+ // have no response tag. Effectively, the client can only have
+ // one version call outstanding at a time. We have to create
+ // an entire special code path to handle it. The client
+ // shouldn't proceed until the version reply is completed.
+ req.fcall.Tag = NOTAG
}
- if err := t.conn.SetWriteDeadline(deadline); err != nil {
- log.Printf("error setting write deadline: %v", err)
- }
-
// TODO(stevvooe): Consider the case of requests that never
// receive a response. We need to remove the fcall context from
// the tag map and dealloc the tag. We may also want to send a
// flush for the tag.
-
- log.Println("send", req.fcall)
- if err := enc.encode(req.fcall); err != nil {
+ if err := t.ch.writeFcall(req.ctx, req.fcall); err != nil {
delete(outstanding, req.fcall.Tag)
req.err <- err
}
- if err := bwr.Flush(); err != nil {
- delete(outstanding, req.fcall.Tag)
- req.err <- err
- }
log.Println("sent", req.fcall)
case b := <-responses:
+ log.Println("recv", b)
req, ok := outstanding[b.Tag]
if !ok {
panic("unknown tag received")
blob - a721ab636b2e82ae5b06a92c70f3c5b17e96a47e
blob + 604d967a1ddcf792eb4bba17b3110e4ac5814534
--- types.go
+++ types.go
package p9pnew
-import "time"
+import (
+ "fmt"
+ "time"
+)
const (
NOFID = ^Fid(0)
DMMOUNT = 0x10000000 // mode bit for mounted channel
DMAUTH = 0x08000000 // mode bit for authentication file
DMTMP = 0x04000000 // mode bit for non-backed-up files
- DMREAD = 0x4 // mode bit for read permission
- DMWRITE = 0x2 // mode bit for write permission
- DMEXEC = 0x1 // mode bit for execute permission
+
+ // 9p2000.u extensions
+ DMSYMLINK = 0x02000000
+ DMDEVICE = 0x00800000
+ DMNAMEDPIPE = 0x00200000
+ DMSOCKET = 0x00100000
+ DMSETUID = 0x00080000
+ DMSETGID = 0x00040000
+
+ DMREAD = 0x4 // mode bit for read permission
+ DMWRITE = 0x2 // mode bit for write permission
+ DMEXEC = 0x1 // mode bit for execute permission
)
const (
- OREAD = 0 // open for read
- OWRITE = 1 // write
- ORDWR = 2 // read and write
- OEXEC = 3 // execute, == read but check execute permission
- OTRUNC = 16 // or'ed in (except for exec), truncate file first
- OCEXEC = 32 // or'ed in, close on exec
- ORCLOSE = 64 // or'ed in, remove on close
- OEXCL = 0x1000 // or'ed in, exclusive use (create only)
+ OREAD = 0x00 // open for read
+ OWRITE = 0x01 // write
+ ORDWR = 0x02 // read and write
+ OEXEC = 0x03 // execute, == read but check execute permission
+
+ // PROPOSAL(stevvooe): Possible protocal extension to allow the create of
+ // symlinks. Initially, the link is created with no value. Read and write
+ // to read and set the link value.
+ OSYMLINK = 0x04
+
+ // or'd in
+ OTRUNC = 0x10 // or'ed in (except for exec), truncate file first
+ OCEXEC = 0x20 // or'ed in, close on exec
+ ORCLOSE = 0x40 // or'ed in, remove on close
)
type QType uint8
QTFILE QType = 0x00 // plain file
)
+func (qt QType) String() string {
+ switch qt {
+ case QTDIR:
+ return "dir"
+ case QTAPPEND:
+ return "append"
+ case QTEXCL:
+ return "excl"
+ case QTMOUNT:
+ return "mount"
+ case QTAUTH:
+ return "auth"
+ case QTTMP:
+ return "tmp"
+ case QTFILE:
+ return "file"
+ }
+
+ return "unknown"
+}
+
type Fid uint32
type Qid struct {
- Type QType
+ Type QType `9p:type,1`
Version uint32
Path uint64
}
+func (qid Qid) String() string {
+ return fmt.Sprintf("Qid(%v, version=%x, path=%x)",
+ qid.Type, qid.Version, qid.Path)
+}
+
type Dir struct {
- Type uint16
- Dev uint32
- Qid Qid
- Mode uint32
+ Type uint16
+ Dev uint32
+ Qid Qid
+ Mode uint32
+
+ // BUG(stevvooe): The Year 2038 is coming soon. 9p wire protocol has these
+ // as 4 byte epoch times. Some possibilities include time dilation fields
+ // or atemporal files. We can also just not use them and set them to zero.
+
AccessTime time.Time
ModTime time.Time
- Length uint64
- Name string
- UID string
- GID string
- MUID string
- // TODO(stevvooe): 9p2000.u/L should go here.
+ Length uint64
+ Name string
+ UID string
+ GID string
+ MUID string
}
//