commit d9c8ccc2a1b6fbc98ff1d934e98c73662411c036
Author: Matsuda Kenji <info@mtkn.jp>
Date: Sat, 15 Jul 2023 08:55:04 +0900
first commit
examining io/fs
Diffstat:
| A | cmd/disk.go | | | 32 | ++++++++++++++++++++++++++++++++ |
| A | fcall.go | | | 240 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | go.mod | | | 3 | +++ |
| A | note | | | 7 | +++++++ |
| A | parse.go | | | 30 | ++++++++++++++++++++++++++++++ |
| A | server.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
+ }
+ }
+}