lib9p

Go 9P library.
Log | Files | Refs

commit d9c8ccc2a1b6fbc98ff1d934e98c73662411c036
Author: Matsuda Kenji <info@mtkn.jp>
Date:   Sat, 15 Jul 2023 08:55:04 +0900

first commit
examining io/fs

Diffstat:
Acmd/disk.go | 32++++++++++++++++++++++++++++++++
Afcall.go | 240+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ago.mod | 3+++
Anote | 7+++++++
Aparse.go | 30++++++++++++++++++++++++++++++
Aserver.go | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 371 insertions(+), 0 deletions(-)

diff --git a/cmd/disk.go b/cmd/disk.go @@ -0,0 +1,31 @@ +// Disk exports the file system on the disk. +package main + +import ( + "fmt" + "io/fs" + "os" + "strings" + // "lib9p" +) + +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "usage: %s root\n", os.Args[0]) + os.Exit(1) + } + disk := os.DirFS(os.Args[1]) + + fs.WalkDir(disk, ".", walkTo("cmd/disk.go")) +} + +func walkTo(wpath string) fs.WalkDirFunc { + return func(path string, d fs.DirEntry, err error) error { + fmt.Println("walk:", path) + if path == "." || strings.HasPrefix(wpath, path) { + return nil + } else { + return fs.SkipDir + } + } +} +\ No newline at end of file diff --git a/fcall.go b/fcall.go @@ -0,0 +1,240 @@ +package lib9p + +import ( + "encoding/binary" + "fmt" +) + +type MsgType uint8 + +const ( + Tversion MsgType = 100 + Rversion = 101 + Tauth = 102 + Rauth = 103 + Tattach = 104 + Rattach = 105 + Terror = 106 /* illegal */ + Rerror = 107 + Tflush = 108 + Rflush = 109 + Twalk = 110 + Rwalk = 111 + Topen = 112 + Ropen = 113 + Tcreate = 114 + Rcreate = 115 + Tread = 116 + Rread = 117 + Twrite = 118 + Rwrite = 119 + Tclunk = 120 + Rclunk = 121 + Tremove = 122 + Rremove = 123 + Tstat = 124 + Rstat = 125 + Twstat = 126 + Rwstat = 127 + Tmax = 128 +) + +// QidType represents the type of Qid. the 'QT' prefix may be redundant. +type QidType uint8 + +const ( + QTDIR QidType = 0x80 /* type bit for directories */ + QTAPPEND = 0x40 /* type bit for append only files */ + QTEXCL = 0x20 /* type bit for exclusive use files */ + QTMOUNT = 0x10 /* type bit for mounted channel */ + QTAUTH = 0x08 /* type bit for authentication file */ + QTTMP = 0x04 /* type bit for non-backed-up file */ + QTSYMLINK = 0x02 /* type bit for symbolic link */ + QTFILE = 0x00 /* type bits for plain file */ +) + +// Gbit16 reads b as 2-byte long little endian unsigned integer and +// returns the result +func gbit16(b []byte) uint16 { return binary.LittleEndian.Uint16(b[0:2]) } + +// Gbit32 reads b as 4-byte long little endian unsigned integer and +// returns the result +func gbit32(b []byte) uint32 { return binary.LittleEndian.Uint32(b[0:4]) } + +// Gbit64 reads b as 8-byte long little endian unsigned integer and +// returns the result +func gbit64(b []byte) uint64 { return binary.LittleEndian.Uint64(b[0:8]) } + +// Pbit16 puts into b an 2-byte long little endian unsigned integer n. +func pbit16(b []byte, n uint16) { binary.LittleEndian.PutUint16(b, n) } + +// Pbit32 puts into b an 4-byte long little endian unsigned integer n. +func pbit32(b []byte, n uint32) { binary.LittleEndian.PutUint32(b, n) } + +// Pbit64 puts into b an 8-byte long little endian unsigned integer n. +func pbit64(b []byte, n uint64) { binary.LittleEndian.PutUint64(b, n) } + +const ( + Topenfd = 98 + Ropenfd = 99 +) + +type Req struct { + srv *Srv + ifcall Msg + ofcall Msg +} + +type Fid []byte + +func (f Fid) String() string { + return fmt.Sprintf("%d", gbit32(f)) +} + +type Qid []byte + +func (q Qid) Type() QidType { return QidType(q[0]) } +func (q Qid) SetType(t QidType) { q[0] = uint8(t) } +func (q Qid) Vers() uint32 { return gbit32(q[1:5]) } +func (q Qid) SetVers(v uint32) { pbit32(q[1:5], v) } +func (q Qid) Path() uint64 { return gbit64(q[5:13]) } +func (q Qid) SetPath(p uint64) { pbit64(q[5:13], p) } +func (q Qid) String() string { + return fmt.Sprintf("(%016d %d %s)", q.Path(), q.Vers(), q.typeStr()) +} + +// TypeStr returns the q.Type() as string for debugging information. +func (q Qid) typeStr() string { + var s string + t := q.Type() + if t&QTDIR != 0 { + s += "d" + } + if t&QTAPPEND != 0 { + s += "a" + } + if t&QTEXCL != 0 { + s += "l" + } + if t&QTAUTH != 0 { + s += "A" + } + return s +} + +// Msg represents any kind of message of 9P. +// It defines methods for common fields. +type Msg interface { + // Size returns the size field of msg. + Size() uint32 + // Type returns the type field of msg. + Type() MsgType + // Tag returns the tag of msg. + Tag() uint16 + + // Conv2M convert Msg to byte array to be transmitted. + // This name is derived from plan9port's lib9p: convS2M. + conv2M() []byte + String() string +} + +// bufMsg is Msg with just an array of bytes. +type bufMsg []byte + +func (msg bufMsg) Size() uint32 { return gbit32(msg[0:4]) } +func (msg bufMsg) Type() MsgType { return MsgType(msg[4]) } +func (msg bufMsg) Tag() uint16 { return gbit16(msg[5:7]) } +func (msg bufMsg) conv2M() []byte { return []byte(msg)[:msg.Size()] } +func (msg bufMsg) String() string { return fmt.Sprint([]byte(msg[:msg.Size()])) } + +// TVersion represents Tversion message of 9P. +type TVersion []byte + +func (msg TVersion) Size() uint32 { return gbit32(msg[0:4]) } +func (msg TVersion) Type() MsgType { return MsgType(msg[4]) } +func (msg TVersion) Tag() uint16 { return gbit16(msg[5:7]) } +func (msg TVersion) MSize() uint32 { return gbit32(msg[7:11]) } +func (msg TVersion) Version() string { return string(msg[13:msg.Size()]) } +func (msg TVersion) conv2M() []byte { return []byte(msg)[:msg.Size()] } +func (msg TVersion) String() string { + return fmt.Sprintf("Tversion tag %d msize %d version '%s'", + msg.Tag(), msg.MSize(), msg.Version()) +} + +type RVersion []byte + +func (msg RVersion) Size() uint32 { return gbit32(msg[0:4]) } +func (msg RVersion) SetSize(s uint32) { pbit32(msg[0:4], s) } +func (msg RVersion) Type() MsgType { return MsgType(msg[4]) } +func (msg RVersion) SetType(t MsgType) { msg[4] = byte(t) } +func (msg RVersion) Tag() uint16 { return gbit16(msg[5:7]) } +func (msg RVersion) SetTag(t uint16) { pbit16(msg[5:7], t) } +func (msg RVersion) MSize() uint32 { return gbit32(msg[7:11]) } +func (msg RVersion) SetMSize(s uint32) { pbit32(msg[7:11], s) } +func (msg RVersion) Version() string { + vsize := gbit16(msg[11:13]) + return string(msg[13 : 13+vsize]) +} +func (msg RVersion) SetVersion(v string) { + pbit16(msg[4+1+2+4:4+1+2+4+2], uint16(len(v))) + for i := 0; i < len(v); i++ { + msg[4+1+2+4+2+i] = byte(v[i]) + } +} +func (msg RVersion) conv2M() []byte { return []byte(msg)[:msg.Size()] } +func (msg RVersion) String() string { + return fmt.Sprintf("Rversion tag %d msize %d version '%s'", + msg.Tag(), msg.MSize(), msg.Version()) +} + +type TAuth []byte +type RAuth []byte +type RError []byte + +type TAttach []byte + +func (msg TAttach) Size() uint32 { return gbit32(msg[0:4]) } +func (msg TAttach) Type() MsgType { return MsgType(msg[4]) } +func (msg TAttach) Tag() uint16 { return gbit16(msg[5:7]) } +func (msg TAttach) Fid() Fid { return Fid(msg[7:11]) } +func (msg TAttach) AFid() uint32 { return gbit32(msg[11:15]) } +func (msg TAttach) UName() string { + usize := gbit16(msg[15:17]) + return string(msg[17 : 17+usize]) +} +func (msg TAttach) AName() string { + usize := gbit16(msg[15:17]) + asize := gbit16(msg[17+usize : 17+usize+2]) + return string(msg[17+usize+2 : 17+usize+2+asize]) +} +func (msg TAttach) conv2M() []byte { return []byte(msg)[:msg.Size()] } +func (msg TAttach) String() string { + var afid int64 + if msg.AFid() == ^uint32(0) { + afid = -1 + } else { + afid = int64(msg.AFid()) + } + return fmt.Sprintf("Tattach tag %d fid %v afid %d uname %s aname %s", + msg.Tag(), msg.Fid(), afid, msg.UName(), msg.AName()) +} + +type RAttach []byte + +func (msg RAttach) Size() uint32 { return gbit32(msg[0:4]) } +func (msg RAttach) SetSize(s uint32) { pbit32(msg[0:4], s) } +func (msg RAttach) Type() MsgType { return MsgType(msg[4]) } +func (msg RAttach) SetType(t MsgType) { msg[4] = byte(t) } +func (msg RAttach) Tag() uint16 { return gbit16(msg[5:7]) } +func (msg RAttach) SetTag(t uint16) { pbit16(msg[5:7], t) } +func (msg RAttach) Qid() Qid { return Qid(msg[7:20]) } +func (msg RAttach) SetQid(q Qid) { + msg[7] = uint8(q.Type()) + pbit32(msg[8:12], q.Vers()) + pbit64(msg[12:20], q.Path()) +} +func (msg RAttach) conv2M() []byte { return []byte(msg)[:msg.Size()] } +func (msg RAttach) String() string { + return fmt.Sprintf("Rattach tag %d qid %v", + msg.Tag(), msg.Qid()) +} diff --git a/go.mod b/go.mod @@ -0,0 +1,3 @@ +module lib9p + +go 1.20 diff --git a/note b/note @@ -0,0 +1,7 @@ +Implement 9P protocol using io/fs. + +This library just exports fs.FS. +Client can make a file server by implementing fs.FS +optionally with additional methods like Write and Create. +Without these additional method, the file system offered +is read only. diff --git a/parse.go b/parse.go @@ -0,0 +1,30 @@ +package lib9p + +import ( + "fmt" + "io" +) + +// Read9PMsg reads 9P message from r and saves into a byte slice. +// It returns the byte slice read and error if any. +func read9PMsg(r io.Reader) ([]byte, error) { + buf := make([]byte, 4) + read, err := r.Read(buf) + if err != nil { + return nil, fmt.Errorf("read size: %v", err) + } + if read != len(buf) { + return buf, fmt.Errorf("read size: invalid message.") + } + size := bufMsg(buf).Size() + mbuf := make([]byte, size-4) + read, err = r.Read(mbuf) + buf = append(buf, mbuf...) + if err != nil { + return buf, fmt.Errorf("read body: %v", err) + } + if uint32(read+4) != size { + return buf, fmt.Errorf("read body: size mismatch") + } + return buf, nil +} diff --git a/server.go b/server.go @@ -0,0 +1,59 @@ +package lib9p + +import ( + "fmt" + "io" + "io/fs" + "log" + "os" +) + +var chatty9P = false + +type Srv struct { + FS fs.FS + MsgSize uint32 + io.Reader + io.Writer +} + +func getReq(s *Srv) (*Req, error) { + var r Req + + buf, err := read9PMsg(s) + if err != nil { + return nil, fmt.Errorf("read message: %v", err) + } + + switch t := bufMsg(buf).Type(); t { + default: + if chatty9P { + fmt.Fprintf(os.Stderr, "<-- %v\n", bufMsg(buf)) + } + return nil, fmt.Errorf("unknown message type %d", t) + case Tversion: + r.ifcall = TVersion(buf) + case Tattach: + r.ifcall = TAttach(buf) + } + r.srv = s + if chatty9P { + fmt.Fprintf(os.Stderr, "<-- %v\n", r.ifcall) + } + return &r, nil +} + +func srv(s *Srv) { + for { + r, err := getReq(s) + if err != nil { + log.Printf("get req: %v\n", err) + break + } + switch r.ifcall.Type() { + default: + log.Print("unknown message type") + continue + } + } +}