lib9p

Go 9P library.
Log | Files | Refs

commit 6e160f8c1271735c3023c09bdd433eae1e09e8a0
parent 3b765680088fc16e4fd3dbdcbb33e001cfe8a4db
Author: Matsuda Kenji <info@mtkn.jp>
Date:   Sun, 30 Jul 2023 15:21:06 +0900

change FileInfo and stat

Diffstat:
Mfcall.go | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mfile.go | 227+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mparse.go | 4++++
Mserver.go | 73++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
4 files changed, 364 insertions(+), 65 deletions(-)

diff --git a/fcall.go b/fcall.go @@ -598,8 +598,8 @@ func (msg *TOpen) String() string { } type ROpen struct { - tag uint16 - qid *Qid + tag uint16 + qid *Qid iounit uint32 } @@ -607,9 +607,9 @@ func newROpen(buf []byte) *ROpen { panic("not implemented") } func (msg *ROpen) Size() uint32 { return uint32(4 + 1 + 2 + 13 + 4) } -func (msg *ROpen) Type() MsgType { return Ropen } -func (msg *ROpen) Tag() uint16 { return msg.tag } -func (msg *ROpen) Qid() *Qid { return msg.qid } +func (msg *ROpen) Type() MsgType { return Ropen } +func (msg *ROpen) Tag() uint16 { return msg.tag } +func (msg *ROpen) Qid() *Qid { return msg.qid } func (msg *ROpen) IoUnit() uint32 { return msg.iounit } func (msg *ROpen) conv2M() []byte { cur := 0 @@ -637,6 +637,107 @@ func (msg *ROpen) String() string { msg.Tag(), msg.Qid(), msg.IoUnit()) } +type TRead struct { + size uint32 + tag uint16 + fid uint32 + offset uint64 + count uint32 +} + +func newTRead(buf []byte) *TRead { + cur := 0 + msg := new(TRead) + msg.size = gbit32(buf[cur : cur+4]) + cur += 4 + cur += 1 // type + msg.tag = gbit16(buf[cur : cur+2]) + cur += 2 + msg.fid = gbit32(buf[cur : cur+4]) + cur += 4 + msg.offset = gbit64(buf[cur : cur+8]) + cur += 8 + msg.count = gbit32(buf[cur : cur+4]) + cur += 4 + if cur != len(buf) { + panic("length of buf and cursor position don't match") + } + return msg +} +func (msg *TRead) Size() uint32 { return msg.size } +func (msg *TRead) Type() MsgType { return Tread } +func (msg *TRead) Tag() uint16 { return msg.tag } +func (msg *TRead) Fid() uint32 { return msg.fid } +func (msg *TRead) Offset() uint64 { return msg.offset } +func (msg *TRead) Count() uint32 { return msg.count } +func (msg *TRead) conv2M() []byte { + cur := 0 + buf := make([]byte, msg.Size()) + pbit32(buf[cur:cur+4], msg.Size()) + cur += 4 + buf[cur] = uint8(Tread) + cur += 1 + pbit16(buf[cur:cur+2], msg.Tag()) + cur += 2 + pbit32(buf[cur:cur+4], msg.Fid()) + cur += 4 + pbit64(buf[cur:cur+8], msg.Offset()) + cur += 8 + pbit32(buf[cur:cur+4], msg.Count()) + cur += 4 + if cur != len(buf) { + panic("length of buf and cursor position don't match") + } + return buf +} +func (msg *TRead) String() string { + return fmt.Sprintf("Tread tag %d fid %d offset %d count %d", + msg.Tag(), msg.Fid(), msg.Offset(), msg.Count()) +} + +type RRead struct { + tag uint16 + count uint32 + data []byte +} + +func newRRead(buf []byte) *RRead { panic("not implemented") } +func (msg *RRead) Size() uint32 { + return uint32(4 + 1 + 2 + 4 + msg.Count()) +} +func (msg *RRead) Type() MsgType { return Rread } +func (msg *RRead) Tag() uint16 { return msg.tag } +func (msg *RRead) Count() uint32 { return msg.count } +func (msg *RRead) Data() []byte { return msg.data } +func (msg *RRead) conv2M() []byte { + if uint32(len(msg.Data())) != msg.Count() { + panic(fmt.Errorf("data size %d and count %d don't match", + len(msg.Data()), msg.Count())) + } + cur := 0 + buf := make([]byte, msg.Size()) + pbit32(buf[cur:cur+4], msg.Size()) + cur += 4 + buf[cur] = uint8(msg.Type()) + cur += 1 + pbit16(buf[cur:cur+2], msg.Tag()) + cur += 2 + pbit32(buf[cur:cur+4], msg.Count()) + cur += 4 + data := msg.Data() + for i := 0; i < len(data); i++ { + buf[cur+i] = data[i] + } + cur += len(data) + if cur != len(buf) { + panic("length of buf and cursor position don't match") + } + return buf +} +func (msg *RRead) String() string { + return fmt.Sprintf("Rread tag %d count %d data %v", + msg.Tag(), msg.Count(), msg.Data()) +} type TStat struct { size uint32 @@ -670,11 +771,11 @@ func (msg *TStat) String() string { type RStat struct { tag uint16 - stat *stat + info *FileInfo } func newRStat(buf []byte) *RStat { panic("not implemented") } -func (msg *RStat) Size() uint32 { return uint32(4 + 1 + 2 + 2 + 2 + msg.stat.size()) } // TODO: collect ? +func (msg *RStat) Size() uint32 { return uint32(4 + 1 + 2 + 2 + 2 + msg.info.statSize()) } // TODO: collect ? func (msg *RStat) Type() MsgType { return Rstat } func (msg *RStat) Tag() uint16 { return msg.tag } func (msg *RStat) conv2M() []byte { @@ -682,12 +783,12 @@ func (msg *RStat) conv2M() []byte { 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] + fiBuf := msg.info.conv2M() + pbit16(buf[7:9], uint16(len(fiBuf))) + for i := 0; i < len(fiBuf); i++ { + buf[9+i] = fiBuf[i] } return buf } -func (msg *RStat) String() string { return fmt.Sprintf("Rstat tag %d stat %s", msg.Tag(), msg.stat) } +func (msg *RStat) String() string { return fmt.Sprintf("Rstat tag %d stat %s", msg.Tag(), msg.info) } diff --git a/file.go b/file.go @@ -2,6 +2,7 @@ package lib9p import ( "fmt" + "io" "io/fs" "time" ) @@ -24,19 +25,18 @@ func hasPerm(f *File, uid string, p fs.FileMode) (bool, error) { if err != nil { return false, fmt.Errorf("stat: %v", err) } - st := (*stat)(fi) fp := fi.Mode().Perm() m := fp & 7 // other if (p & m) == p { return true, nil } - if st.uid == uid { + if fi.statUid() == uid { m |= (fp >> 6) & 7 if (p & m) == p { return true, nil } } - if st.gid == uid { + if fi.statGid() == uid { m |= (fp >> 3) & 7 if (p & m) == p { return true, nil @@ -130,65 +130,203 @@ func (s *stat) String() string { s.aTime.Unix(), s.mTime.Unix(), s.length, s.t, s.dev) } -type FileInfo stat +type FileInfo struct { + info fs.FileInfo + qid *Qid + /* + 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 (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 - if smode&DMDIR != 0 { - mode |= fs.ModeDir - } - if smode&DMAPPEND != 0 { - mode |= fs.ModeAppend - } - if smode&DMEXCL != 0 { - mode |= fs.ModeExclusive - } - if smode&DMTMP != 0 { - mode |= fs.ModeTemporary - } - // Permission bits are fixed according to godoc of fs.FileMode - mode |= fs.FileMode(smode & 0777) - return mode +func (fi *FileInfo) Name() string { return fi.info.Name() } +func (fi *FileInfo) Size() int64 { return fi.info.Size() } +func (fi *FileInfo) Mode() fs.FileMode { return fi.info.Mode() } +func (fi *FileInfo) ModTime() time.Time { return fi.info.ModTime() } +func (fi *FileInfo) IsDir() bool { return fi.info.IsDir() } +func (fi *FileInfo) Sys() any { return fi } + +func (fi *FileInfo) statSize() uint16 { + return uint16(2 + 4 + 13 + 4 + 4 + 4 + 8 + + 2 + len(fi.statName()) + 2 + len(fi.statUid()) + + 2 + len(fi.statGid()) + 2 + len(fi.statMuid())) +} +func (fi *FileInfo) statType() uint16 { return 0 } +func (fi *FileInfo) statDev() uint32 { return 0 } +func (fi *FileInfo) statQid() *Qid { return fi.qid } +func (fi *FileInfo) statMode() uint32 { return DMDIR|0777 } +func (fi *FileInfo) statATime() uint32 { return uint32(fi.ModTime().Unix()) } +func (fi *FileInfo) statMTime() uint32 { return uint32(fi.ModTime().Unix()) } +func (fi *FileInfo) statLength() uint64 { + if fi.IsDir() { + return 0 + } else { + return uint64(fi.Size()) + } +} +func (fi *FileInfo) statName() string { return fi.Name() } // TODO: "/" for root +func (fi *FileInfo) statUid() string { return "" } +func (fi *FileInfo) statGid() string { return "" } +func (fi *FileInfo) statMuid() string { return "" } +func (fi *FileInfo) conv2M() []byte { + cur := 0 + buf := make([]byte, 2 + fi.statSize()) + pbit16(buf[cur:cur+2], fi.statSize()) + cur += 2 + pbit16(buf[cur:cur+2], fi.statType()) + cur += 2 + pbit32(buf[cur:cur+4], fi.statDev()) + cur += 4 + qidBuf := fi.statQid().conv2M() + for i := 0; i < len(qidBuf); i++ { + buf[cur+i] = qidBuf[i] + } + cur += len(qidBuf) + pbit32(buf[cur:cur+4], fi.statMode()) + cur += 4 + pbit32(buf[cur:cur+4], fi.statATime()) + cur += 4 + pbit32(buf[cur:cur+4], fi.statMTime()) + cur += 4 + pbit64(buf[cur:cur+8], fi.statLength()) + cur += 8 + name := fi.statName() + pbit16(buf[cur:cur+2], uint16(len(name))) + cur += 2 + for i := 0; i < len(name); i++ { + buf[cur+i] = name[i] + } + cur += len(name) + uid := fi.statUid() + pbit16(buf[cur:cur+2], uint16(len(uid))) + cur += 2 + for i := 0; i < len(uid); i++ { + buf[cur+i] = uid[i] + } + cur += len(uid) + gid := fi.statGid() + pbit16(buf[cur:cur+2], uint16(len(gid))) + cur += 2 + for i := 0; i < len(gid); i++ { + buf[cur+i] = gid[i] + } + cur += len(gid) + muid := fi.statMuid() + pbit16(buf[cur:cur+2], uint16(len(muid))) + cur += 2 + for i := 0; i < len(muid); i++ { + buf[cur+i] = muid[i] + } + cur += len(muid) + if len(buf) != cur { + panic(fmt.Errorf("cursor position %d and buf length %d don't match", + cur, len(buf))) + } + return buf +} +func (fi *FileInfo) String() string { + return fmt.Sprintf("'%s' '%s' '%s' '%s' q %v m %#o at %d mt %d l %d t %d d %d", + fi.statName(), fi.statUid(), fi.statGid(), fi.statMuid(), + fi.statQid(), fi.statMode(), fi.statATime(), fi.statMTime(), + fi.statLength(), fi.statType(), fi.statDev()) } -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 { + fs *FS // file system to which this file belongs file fs.File // underlying file qid *Qid } func (f *File) Stat() (*FileInfo, error) { - fi, err := f.file.Stat() + fsfi, err := f.file.Stat() if err != nil { return nil, fmt.Errorf("stat file: %v, %v", f, err) } - var size int64 - if fi.IsDir() { - size = 0 - } else { - size = fi.Size() - } - st := &stat{ - qid: f.qid, - mode: fi.Mode(), // TODO: convert mode from fs's to 9p - aTime: fi.ModTime(), - mTime: fi.ModTime(), - length: size, - name: fi.Name(), - // TODO: size, t, dev, uid, gid, muid - } - return (*FileInfo)(st), nil + fi := &FileInfo{ + info: fsfi, + qid: f.qid, + // TODO: t, dev, uid, gid, muid + } + return fi, nil } func (f *File) Read(b []byte) (int, error) { return f.file.Read(b) } +func (f *File) ReadAt(p []byte, off int64) (int, error) { + if f, ok := f.file.(io.ReaderAt); ok { + return f.ReadAt(p, off) + } + file := f.file + + buf := make([]byte, 8*1024) // TODO: set appropreate size + var n int64 + for n+int64(len(buf)) < off { + m, err := file.Read(buf) + if err != nil { + return 0, err + } + n += int64(m) + } + m, err := file.Read(buf[:off-n]) + if err != nil { + return 0, err + } + n += int64(m) + if n != off { + panic("wrong offset") + } + return file.Read(p) +} + +func (f *File) ReadDir(n int) ([]*DirEntry, error) { + fi, err := f.file.Stat() + if err != nil { + return nil, fmt.Errorf("stat: %v", err) + } + dir, ok := f.file.(fs.ReadDirFile) + if !ok || !fi.IsDir() { + return nil, fmt.Errorf("not a directory") + } + fsde, err := dir.ReadDir(n) + if err != nil { + return nil, err + } + de := make([]*DirEntry, len(fsde)) + for i := 0; i < len(de); i++ { + fsinfo, err := fsde[i].Info() + if err != nil { + return nil, fmt.Errorf("stat: %v", err) + } + de[i] = &DirEntry{ + dirEnt: fsde[i], + info: &FileInfo{fsinfo, &Qid{path:f.fs.nextQid}}, // TODO: need mutex? + } + f.fs.nextQid++ + } + return de, nil +} + +type DirEntry struct { + dirEnt fs.DirEntry // underlying fs.DirEntry + info *FileInfo +} + +func (e *DirEntry) Name() string { return e.dirEnt.Name() } +func (e *DirEntry) IsDir() bool { return e.dirEnt.IsDir() } +func (e *DirEntry) Type() fs.FileMode { return e.dirEnt.Type() } +func (e *DirEntry) Info() (*FileInfo, error) { return e.info, nil } + func (f *File) Close() error { return f.file.Close() } @@ -231,6 +369,7 @@ func (fsys *FS) Open(name string) (*File, error) { fsys.nextQid++ file := &File{ + fs: fsys, file: f, qid: qid, } diff --git a/parse.go b/parse.go @@ -54,6 +54,10 @@ func unmarshal(buf []byte) (Msg, error) { return newRWalk(buf), nil case Topen: return newTOpen(buf), nil + case Tread: + return newTRead(buf), nil + case Rread: + return newRRead(buf), nil case Tstat: return newTStat(buf), nil case Rstat: diff --git a/server.go b/server.go @@ -230,10 +230,10 @@ func sOpen(s *Server, r *Req) { case OEXEC: p = AEXEC } - if ifcall.Mode() & OTRUNC != 0 { + if ifcall.Mode()&OTRUNC != 0 { p |= AWRITE } - if fidStruct.Qid.t & QTDIR != 0 && p != AREAD { + if fidStruct.Qid.t&QTDIR != 0 && p != AREAD { respond(r, fmt.Errorf("permission denied")) return } @@ -249,19 +249,70 @@ func sOpen(s *Server, r *Req) { return } - if ifcall.Mode() & ORCLOSE != 0 { + if ifcall.Mode()&ORCLOSE != 0 { panic("ORCLOSE not implemented") } fidStruct.OMode = ifcall.Mode() r.ofcall = &ROpen{ - qid: fidStruct.Qid, + qid: fidStruct.Qid, iounit: s.MSize - 23, } respond(r, nil) } func rOpen(r *Req, err error) {} +func sRead(s *Server, r *Req) { + ifcall, ok := r.ifcall.(*TRead) + if !ok { + panic("not TRead") + } + + fid, ok := s.fPool.lookupFid(ifcall.Fid()) + if !ok { + respond(r, fmt.Errorf("unknown fid")) + return + } + if fid.OMode == -1 { + respond(r, fmt.Errorf("not open")) + return + } + /* + if fid.OMode|OREAD == 0 || fid.OMode|ORDWR == 0 { + log.Printf("permission: %o\n", fid.OMode) + respond(r, fmt.Errorf("unko permission denied")) + return + } + */ + data := make([]byte, ifcall.Count()) + var n int + var err error + fi, err := fid.File.Stat() + if err != nil { + log.Printf("stat: %v") + respond(r, fmt.Errorf("internal error")) + return + } + if fi.IsDir() { + + } else { + n, err = fid.File.ReadAt(data, int64(ifcall.Offset())) + if err != nil { + respond(r, err) + return + } + } + + ofcall := &RRead{ + tag: ifcall.Tag(), + count: uint32(n), + data: data[:n], + } + r.ofcall = ofcall + respond(r, nil) +} +func rRead(r *Req, err error) {} + func rError(r *Req, err error) { ofcall := &RError{ tag: r.ifcall.Tag(), @@ -290,7 +341,7 @@ func sStat(s *Server, r *Req) { ofcall := &RStat{ tag: ifcall.Tag(), - stat: (*stat)(fileInfo), + info: fileInfo, } r.ofcall = ofcall @@ -302,13 +353,13 @@ func rStat(r *Req, err error) {} func (s *Server) Serve() { for { r, err := s.getReq() - if r == nil { - log.Printf("getReq returns nil request: %v", err) - break - } if err == io.EOF { log.Printf("getReq: %v\n", err) break + } + if r == nil { + log.Printf("getReq returns nil request: %v", err) + break } else if err != nil { log.Printf("getReq: %v\n", err) respond(r, err) @@ -327,6 +378,8 @@ func (s *Server) Serve() { sWalk(s, r) case *TOpen: sOpen(s, r) + case *TRead: + sRead(s, r) case *TStat: sStat(s, r) } @@ -360,6 +413,8 @@ func respond(r *Req, err error) { rWalk(r, err) case *ROpen: rOpen(r, err) + case *RRead: + rRead(r, err) case *RStat: rStat(r, err) }