lib9p

Go 9P library.
Log | Files | Refs

commit a095dbd85e5794b0d0ed508312aeaaf4fc7cc4af
parent 7464c340f8010cecdeafff2b5aa1fe44d5233f57
Author: Matsuda Kenji <info@mtkn.jp>
Date:   Thu, 27 Jul 2023 17:54:37 +0900

add stat message

Diffstat:
Mcmd/disk.go | 10++--------
Mfcall.go | 107++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mfid.go | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mfile.go | 201++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mparse.go | 3+++
Mserver.go | 94++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
6 files changed, 383 insertions(+), 101 deletions(-)

diff --git a/cmd/disk.go b/cmd/disk.go @@ -42,12 +42,6 @@ func main() { } func handle(conn net.Conn, disk fs.FS) { - srv := &lib9p.Srv{ - FS: disk, - MSize: 8 * 1024, - Reader: conn, - Writer: conn, - } - - lib9p.Serve(srv) + srv := lib9p.NewServer(disk, 8*1024, conn, conn) + srv.Serve() } diff --git a/fcall.go b/fcall.go @@ -77,7 +77,7 @@ type Msg interface { } type Req struct { - srv *Srv + srv *Server ifcall Msg ofcall Msg } @@ -92,15 +92,41 @@ 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 { +type TVersion struct { + size uint32 + tag uint16 + mSize uint32 + version string +} + +func newTVersion(buf []byte) *TVersion { + msg := new(TVersion) + msg.size = gbit32(buf[0:4]) + msg.tag = gbit16(buf[5:7]) + msg.mSize = gbit32(buf[7:11]) + vs := gbit16(buf[11:13]) + msg.version = string(buf[13 : 13+vs]) + return msg +} +func (msg *TVersion) Size() uint32 { return msg.size } +func (msg *TVersion) Type() MsgType { return Tversion } +func (msg *TVersion) Tag() uint16 { return msg.tag } +func (msg *TVersion) MSize() uint32 { return msg.mSize } +func (msg *TVersion) Version() string { return msg.version } +func (msg *TVersion) conv2M() []byte { + m := make([]byte, msg.Size()) + pbit32(m[0:4], msg.Size()) + m[4] = uint8(Tversion) + pbit16(m[5:7], msg.Tag()) + pbit32(m[7:11], msg.MSize()) + v := msg.Version() + pbit16(m[11:13], uint16(len(v))) + for i := 0; i < len(v); i++ { + m[13+i] = v[i] + } + return m +} +func (msg *TVersion) String() string { return fmt.Sprintf("Tversion tag %d msize %d version '%s'", msg.Tag(), msg.MSize(), msg.Version()) } @@ -161,7 +187,7 @@ type RAuth []byte func (msg RAuth) Size() uint32 { return gbit32(msg[0:4]) } func (msg RAuth) Type() MsgType { return MsgType(msg[4]) } func (msg RAuth) Tag() uint16 { return gbit16(msg[5:7]) } -func (msg RAuth) AQid() Qid { return Qid(msg[7:20]) } +func (msg RAuth) AQid() *Qid { return newQid(msg[7:20]) } func (msg RAuth) conv2M() []byte { return []byte(msg)[:msg.Size()] } func (msg RAuth) String() string { return fmt.Sprintf("Tauth tag %d aqid %v", @@ -173,7 +199,7 @@ 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() uint32 { return gbit32(msg[7:11]) } +func (msg TAttach) Fid() uint32 { return gbit32(msg[7:11]) } func (msg TAttach) AFid() uint32 { return gbit32(msg[11:15]) } func (msg TAttach) UName() string { usize := gbit16(msg[15:17]) @@ -206,8 +232,8 @@ 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) { +func (msg RAttach) Qid() *Qid { return newQid(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()) @@ -245,3 +271,56 @@ func (msg RError) SetEName(err error) { func (msg RError) String() string { return fmt.Sprintf("Rerror tag %d ename %v", msg.Tag(), msg.EName()) } + +type TStat struct { + size uint32 + tag uint16 + fid uint32 +} + +func newTStat(buf []byte) *TStat { + msg := new(TStat) + msg.size = gbit32(buf[0:4]) + msg.tag = gbit16(buf[5:7]) + msg.fid = gbit32(buf[7:11]) + return msg +} +func (msg *TStat) Size() uint32 { return msg.size } +func (msg *TStat) Type() MsgType { return Tstat } +func (msg *TStat) Tag() uint16 { return msg.tag } +func (msg *TStat) Fid() uint32 { return msg.fid } +func (msg *TStat) conv2M() []byte { + m := make([]byte, msg.Size()) + pbit32(m[0:4], msg.Size()) + m[4] = uint8(Tstat) + pbit16(m[5:7], msg.Tag()) + pbit32(m[7:11], msg.Fid()) + return m +} +func (msg *TStat) String() string { + return fmt.Sprintf("Tstat tag %d fid %d", + msg.Tag(), msg.Fid()) +} + +type RStat struct { + tag uint16 + stat *stat +} + +func (msg *RStat) Size() uint32 { return uint32(4 + 1 + 2 + 2 + 2 + msg.stat.size()) } // TODO: collect ? +func (msg *RStat) Type() MsgType { return Rstat } +func (msg *RStat) Tag() uint16 { return msg.tag } +func (msg *RStat) conv2M() []byte { + buf := make([]byte, msg.Size()) + pbit32(buf[0:4], msg.Size()) + buf[4] = uint8(Rstat) + pbit16(buf[5:7], msg.Tag()) + statBuf := msg.stat.conv2M() + pbit16(buf[7:9], uint16(len(statBuf))) + for i := 0; i < len(statBuf); i++ { + buf[9+i] = statBuf[i] + } + return buf +} + +func (msg *RStat) String() string { return fmt.Sprintf("Rstat tag %d %s", msg.Tag(), msg.stat) } diff --git a/fid.go b/fid.go @@ -2,7 +2,6 @@ package lib9p import ( "fmt" - "io/fs" ) // QidType represents the type of Qid. the 'QT' prefix may be redundant. @@ -19,12 +18,26 @@ const ( QTFILE = 0x00 /* type bits for plain file */ ) +type OpenMode int32 + +const ( + OREAD OpenMode = 0 + OWRITE = 1 + ORDWR = 2 + OEXEC = 3 + + OTRUNC = 16 + OCEXEC = 32 + ORCLOSE = 64 + ORDIRECT = 128 +) + type Fid struct { Fid uint32 - OMode int32 /* -1 = not open */ - File fs.File + OMode OpenMode /* -1 = not open */ + File *File Uid string - Qid Qid + Qid *Qid } const NOFID = ^uint32(0) @@ -44,20 +57,31 @@ func (f *Fid) String() string { return fmt.Sprintf("%d", fid) } -type Qid []byte +type Qid struct { + t QidType + vers uint32 + path uint64 +} -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 { +func newQid(b []byte) *Qid { + return &Qid{ + t: QidType(b[0]), + vers: gbit32(b[1:5]), + path: gbit64(b[5:13]), + } +} +func (q *Qid) Type() QidType { return q.t } +func (q *Qid) SetType(t QidType) { q.t = t } +func (q *Qid) Vers() uint32 { return q.vers } +func (q *Qid) SetVers(v uint32) { q.vers = v } +func (q *Qid) Path() uint64 { return q.path } +func (q *Qid) SetPath(p uint64) { q.path = 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 { +func (q *Qid) typeStr() string { var s string t := q.Type() if t&QTDIR != 0 { @@ -85,8 +109,9 @@ func allocFidPool() *FidPool { return f } -func (pool *FidPool) lookupFid(fid uint32) *Fid { - return pool.m[fid] +func (pool *FidPool) lookupFid(fid uint32) (*Fid, bool) { + f, ok := pool.m[fid] + return f, ok } func (pool *FidPool) allocFid(fid uint32) (*Fid, error) { @@ -99,7 +124,19 @@ func (pool *FidPool) allocFid(fid uint32) (*Fid, error) { } func (pool *FidPool) removeFid(fid uint32) *Fid { - f := pool.lookupFid(fid) + f, ok := pool.lookupFid(fid) + if !ok { + return nil + } delete(pool.m, fid) return f } + +func (pool *FidPool) String() string { + s := "{" + for fnum, fstruct := range pool.m { + s += fmt.Sprintf(" [%d]%v", fnum, fstruct) + } + s += "}" + return s +} diff --git a/file.go b/file.go @@ -1,6 +1,7 @@ package lib9p import ( + "fmt" "io/fs" "time" ) @@ -12,46 +13,77 @@ const ( DMTMP = 0x04000000 ) -type Stat []byte - -func (s Stat) Size() uint16 { return gbit16(s[0:2]) } -func (s Stat) Type() uint16 { return gbit16(s[2:4]) } -func (s Stat) Dev() uint32 { return gbit32(s[4:8]) } -func (s Stat) Qid() Qid { return Qid(s[8:21]) } -func (s Stat) Mode() uint32 { return gbit32(s[21:25]) } -func (s Stat) ATime() uint32 { return gbit32(s[25:29]) } -func (s Stat) MTime() uint32 { return gbit32(s[29:33]) } -func (s Stat) Length() uint64 { return gbit64(s[33:41]) } -func (s Stat) Name() string { - nlen := gbit16(s[41:43]) - return string(s[43 : 43+nlen]) +type stat struct { + // size uint16 + t uint16 + dev uint32 + qid *Qid + mode fs.FileMode + aTime time.Time + mTime time.Time + length int64 + name string + uid string + gid string + muid string } -func (s Stat) Uid() string { - nlen := gbit16(s[41:43]) - ulen := gbit16(s[43+nlen : 43+nlen+2]) - return string(s[43+nlen+2 : 43+nlen+2+ulen]) -} -func (s Stat) Gid() string { - nlen := gbit16(s[41:43]) - ulen := gbit16(s[43+nlen : 43+nlen+2]) - glen := gbit16(s[43+nlen+2+ulen : 43+nlen+2+ulen+2]) - return string(s[43+nlen+2+ulen+2 : 43+nlen+2+ulen+2+glen]) + +func (s *stat) size() uint16 { + return uint16(2 + 4 + 13 + 4 + 4 + 4 + 8 + + // type + dev + qid + mode + atime + mtime + length + 2 + len(s.name) + + 2 + len(s.uid) + + 2 + len(s.gid) + + 2 + len(s.muid)) } -func (s Stat) MUid() string { - nlen := gbit16(s[41:43]) - ulen := gbit16(s[43+nlen : 43+nlen+2]) - glen := gbit16(s[43+nlen+2+ulen : 43+nlen+2+ulen+2]) - mlen := gbit16(s[43+nlen+2+ulen+2+glen : 43+nlen+2+ulen+2+glen+2]) - return string(s[43+nlen+2+ulen+2+glen+2 : 43+nlen+2+ulen+2+glen+2+mlen]) + +func (s *stat) conv2M() []byte { + size := s.size() + msg := make([]byte, 2+size) + pbit16(msg[:2], size) + pbit16(msg[2:4], s.t) + pbit32(msg[4:8], s.dev) + msg[8] = uint8(s.qid.Type()) + pbit32(msg[9:13], s.qid.Vers()) + pbit64(msg[13:21], s.qid.Path()) + pbit32(msg[21:25], uint32(s.mode)) + pbit32(msg[25:29], uint32(s.aTime.Unix())) + pbit32(msg[29:33], uint32(s.mTime.Unix())) + pbit64(msg[33:41], uint64(s.length)) + pbit16(msg[41:43], uint16(len(s.name))) + for i := 0; i < len(s.name); i++ { + msg[43+i] = s.name[i] + } + uidOffset := 43 + len(s.name) + pbit16(msg[uidOffset:uidOffset+2], uint16(len(s.uid))) + for i := 0; i < len(s.uid); i++ { + msg[uidOffset+2+i] = s.uid[i] + } + gidOffset := uidOffset + 2 + len(s.uid) + pbit16(msg[gidOffset:gidOffset+2], uint16(len(s.gid))) + for i := 0; i < len(s.gid); i++ { + msg[gidOffset+2+i] = s.gid[i] + } + muidOffset := gidOffset + 2 + len(s.gid) + pbit16(msg[muidOffset:muidOffset+2], uint16(len(s.muid))) + for i := 0; i < len(s.muid); i++ { + msg[muidOffset+2+i] = s.muid[i] + } + + if len(msg) != muidOffset+2+len(s.muid) { + panic(fmt.Errorf("stat offset %d and msg length %d not match", + muidOffset+2+len(s.muid), len(msg))) + } + return msg } -type FileInfo Stat +type FileInfo stat -func (fi FileInfo) Name() string { return Stat(fi).Name() } -func (fi FileInfo) Size() int64 { return int64(Stat(fi).Length()) } -func (fi FileInfo) Mode() fs.FileMode { +func (fi *FileInfo) Name() string { return (*stat)(fi).name } +func (fi *FileInfo) Size() int64 { return int64((*stat)(fi).length) } +func (fi *FileInfo) Mode() fs.FileMode { var mode fs.FileMode - smode := Stat(fi).Mode() + smode := (*stat)(fi).mode if smode&DMDIR != 0 { mode |= fs.ModeDir } @@ -65,9 +97,102 @@ func (fi FileInfo) Mode() fs.FileMode { mode |= fs.ModeTemporary } // Permission bits are fixed according to godoc of fs.FileMode - mode |= fs.FileMode(smode&0777) + mode |= fs.FileMode(smode & 0777) return mode } -func (fi FileInfo) ModTime() time.Time { return time.Unix(int64(Stat(fi).MTime()), 0) } -func (fi FileInfo) IsDir() bool { return Stat(fi).Mode()&DMDIR != 0 } -func (fi FileInfo) Sys() any { return Stat(fi) } +func (fi *FileInfo) ModTime() time.Time { return (*stat)(fi).mTime } +func (fi *FileInfo) IsDir() bool { return (*stat)(fi).mode&DMDIR != 0 } +func (fi *FileInfo) Sys() any { return (*stat)(fi) } + +type File struct { + file fs.File // underlying file + qid *Qid +} + +func (f *File) Stat() (*FileInfo, error) { + fi, err := f.file.Stat() + if err != nil { + return nil, fmt.Errorf("stat file: %v, %v", f, err) + } + st := &stat{ + qid: f.qid, + mode: fi.Mode(), // TODO: convert mode from fs to 9p + aTime: fi.ModTime(), + mTime: fi.ModTime(), + length: fi.Size(), + name: fi.Name(), + // TODO: size, t, dev, uid, gid, muid + } + return (*FileInfo)(st), nil +} + +func (f *File) Read(b []byte) (int, error) { + return f.file.Read(b) +} + +func (f *File) Close() error { + return f.file.Close() +} + +type FS struct { + nextQid uint64 // next qid to be used + fs fs.FS // underlying file system +} + +func (fsys *FS) Open(name string) (*File, error) { + f, err := fsys.fs.Open(name) // fs.File + if err != nil { + return nil, fmt.Errorf("open file %s: %v", name, err) + } + qid := newQid(make([]byte, 13)) + qid.SetPath(fsys.nextQid) + fi, err := f.Stat() + if err != nil { + return nil, fmt.Errorf("stat file %v: %v", f, err) + } + mode := fi.Mode() + var qtype QidType + if mode&fs.ModeDir != 0 { + qtype |= QTDIR + } + if mode&fs.ModeAppend != 0 { + qtype |= QTAPPEND + } + if mode&fs.ModeExclusive != 0 { + qtype |= QTEXCL + } + if mode&fs.ModeTemporary != 0 { + qtype |= QTTMP + } + if mode&fs.ModeSymlink != 0 { + qtype |= QTSYMLINK + } + // TODO: QTAUTH + qid.SetType(qtype) + fsys.nextQid++ + + file := &File{ + file: f, + qid: qid, + } + + return file, nil +} + +/* +func (fs *FS) walkFile1(d fs.DirEntry, wname string) (*Qid, error) { + +} + +func (fs *FS) walkFile(d fs.DirEntry, wnames []string) ([]*Qid, error) { + wqids := make([]*Qid, len(wnames)) + for i, wname := range wnames { + wqids[i] = fs.walkFile1(wname) + } +} + +func walkDirFunc(path string, d fs.DirEntry, err error) error { + +} + +*/ diff --git a/parse.go b/parse.go @@ -11,6 +11,9 @@ func read9PMsg(r io.Reader) ([]byte, error) { buf := make([]byte, 4) read, err := r.Read(buf) if err != nil { + if err == io.EOF { + return nil, err + } return nil, fmt.Errorf("read size: %v", err) } if read != len(buf) { diff --git a/server.go b/server.go @@ -15,20 +15,33 @@ func Chatty() { chatty9P = true } -type Srv struct { - FS fs.FS +type Server struct { + FS *FS MSize uint32 fPool *FidPool Reader io.Reader Writer io.Writer } -func getReq(s *Srv) (*Req, error) { +func NewServer(fs fs.FS, mSize uint32, r io.Reader, w io.Writer) *Server { + s := new(Server) + s.FS = &FS{fs: fs} + s.MSize = mSize + s.fPool = allocFidPool() + s.Reader = r + s.Writer = w + return s +} + +func (s *Server) getReq() (*Req, error) { var r Req r.srv = s buf, err := read9PMsg(s.Reader) if err != nil { + if err == io.EOF { + return &r, err + } return &r, fmt.Errorf("read message: %v", err) } @@ -39,11 +52,13 @@ func getReq(s *Srv) (*Req, error) { } return &r, fmt.Errorf("unknown message type %d", t) case Tversion: - r.ifcall = TVersion(buf) + r.ifcall = newTVersion(buf) case Tattach: r.ifcall = TAttach(buf) case Tauth: r.ifcall = TAuth(buf) + case Tstat: + r.ifcall = newTStat(buf) } if chatty9P { fmt.Fprintf(os.Stderr, "<-- %v\n", r.ifcall) @@ -51,8 +66,8 @@ func getReq(s *Srv) (*Req, error) { return &r, nil } -func sVersion(s *Srv, r *Req) { - ifcall, ok := r.ifcall.(TVersion) +func sVersion(s *Server, r *Req) { + ifcall, ok := r.ifcall.(*TVersion) if !ok { panic("not TVersion") } @@ -83,7 +98,7 @@ func sVersion(s *Srv, r *Req) { func rVersion(r *Req, err error) {} -func sAuth(s *Srv, r *Req) { +func sAuth(s *Server, r *Req) { respond(r, fmt.Errorf("authentication not implemented")) } @@ -98,14 +113,11 @@ func rAuth(r *Req, err error) { } } -func sAttach(s *Srv, r *Req) { +func sAttach(s *Server, r *Req) { ifcall, ok := r.ifcall.(TAttach) if !ok { panic("not TAttach") } - if s.fPool == nil { - s.fPool = allocFidPool() - } fid, err := s.fPool.allocFid(ifcall.Fid()) if err != nil { respond(r, fmt.Errorf("duplicate fid")) @@ -119,26 +131,50 @@ func sAttach(s *Srv, r *Req) { } fid.Uid = ifcall.UName() - - qid := Qid(make([]byte, 13)) - qid.SetType(QTDIR) - qid.SetVers(0) - qid.SetPath(0) - fid.Qid = qid + fid.OMode = OREAD + fid.Qid = fid.File.qid ofcall := RAttach(make([]byte, 4+1+2+13)) ofcall.SetSize(uint32(len(ofcall))) ofcall.SetType(Rattach) ofcall.SetTag(ifcall.Tag()) - ofcall.SetQid(qid) + ofcall.SetQid(fid.Qid) r.ofcall = ofcall respond(r, nil) } -func rAttach(r *Req, err error) { +func rAttach(r *Req, err error) {} + +func sStat(s *Server, r *Req) { + ifcall, ok := r.ifcall.(*TStat) + if !ok { + panic("not TStat") + } + fidNum := ifcall.Fid() + fidStruct, ok := s.fPool.lookupFid(fidNum) + if !ok { + respond(r, fmt.Errorf("unknown fid %d", fidNum)) + return + } + fileInfo, err := fidStruct.File.Stat() + if err != nil { + log.Printf("stat file %v: %v", fidStruct.File, err) + respond(r, fmt.Errorf("internal error")) + return + } + + ofcall := &RStat{ + tag: ifcall.Tag(), + stat: (*stat)(fileInfo), + } + + r.ofcall = ofcall + respond(r, nil) } +func rStat(r *Req, err error) {} + func rError(r *Req, err error) { size := uint32(4 + 1 + 2 + 2 + len(err.Error())) ofcall := RError(make([]byte, size)) @@ -148,23 +184,29 @@ func rError(r *Req, err error) { r.ofcall = ofcall } -func Serve(s *Srv) { +func (s *Server) Serve() { for { - r, err := getReq(s) - if err != nil { - log.Printf("get req: %v\n", err) + r, err := s.getReq() + // TODO: check tag duplicate + if err == io.EOF { + log.Printf("getReq: %v\n", err) + break + } else if err != nil { + log.Printf("getReq: %v\n", err) respond(r, fmt.Errorf("internal error")) continue } switch r.ifcall.(type) { default: respond(r, fmt.Errorf("unknown message type: %d", r.ifcall.Type())) - case TVersion: + case *TVersion: sVersion(s, r) case TAuth: sAuth(s, r) case TAttach: sAttach(s, r) + case *TStat: + sStat(s, r) } } } @@ -186,10 +228,12 @@ func respond(r *Req, err error) { rAuth(r, err) case RAttach: rAttach(r, err) + case *RStat: + rStat(r, err) } - if chatty9P { fmt.Fprintf(os.Stderr, "--> %s\n", r.ofcall) } r.srv.Writer.Write(r.ofcall.conv2M()) + // TODO: free tag. }