cmd/toolsrv: implement 9P protocol server
- server.go: Full 9P2000 protocol handling - version, auth, attach, walk, open, read, write, clunk, stat - Uses fsedsl.Tree for all operations - Handles directories and files - Supports rdwr files (RequestRespMode) - main.go: Remove placeholder serve9P, use new implementation The toolsrv binary now serves a working 9P filesystem with the namespace defined in toolsrv/spec9p.go: /register, /ctl, /tools, /proc/*, /info
This commit is contained in:
parent
427cadeb29
commit
75f364dd85
|
|
@ -120,14 +120,3 @@ func main() {
|
|||
go serve9P(runCtx, conn, tree)
|
||||
}
|
||||
}
|
||||
|
||||
// serve9P handles a single 9P connection.
|
||||
// TODO: This needs to implement the 9P protocol using the fsedsl tree.
|
||||
// For now this is a placeholder - the actual implementation should follow
|
||||
// the pattern in cmd/olliesrv/server.go.
|
||||
func serve9P(ctx context.Context, conn net.Conn, tree *fsedsl.Tree) {
|
||||
defer conn.Close()
|
||||
// TODO: Implement 9P protocol handling
|
||||
// This would use plan9.ReadFcall/WriteFcall and dispatch to tree operations
|
||||
fmt.Fprintf(os.Stderr, "toolsrv: new connection (9P serving not yet implemented)\n")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,413 @@
|
|||
// server.go - 9P filesystem server for toolsrv.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"ollie/fsedsl"
|
||||
|
||||
"9fans.net/go/plan9"
|
||||
)
|
||||
|
||||
// fid represents an open file handle.
|
||||
type fid struct {
|
||||
path string
|
||||
qid plan9.Qid
|
||||
mode uint8
|
||||
opened bool
|
||||
entry fsedsl.File
|
||||
readBuf []byte // buffer for read data
|
||||
readDone bool // true if file has been read
|
||||
dirCache []byte
|
||||
}
|
||||
|
||||
// connState holds per-connection state.
|
||||
type connState struct {
|
||||
mu sync.RWMutex
|
||||
fids map[uint32]*fid
|
||||
ctx context.Context
|
||||
uname string
|
||||
}
|
||||
|
||||
// serve9P handles a single 9P connection using the fsedsl tree.
|
||||
func serve9P(ctx context.Context, conn net.Conn, tree *fsedsl.Tree) {
|
||||
defer conn.Close()
|
||||
|
||||
cs := &connState{
|
||||
fids: make(map[uint32]*fid),
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
fc, err := plan9.ReadFcall(conn)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
fmt.Fprintf(os.Stderr, "toolsrv: read error: %v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
resp := handleFcall(cs, fc, tree)
|
||||
if err := plan9.WriteFcall(conn, resp); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "toolsrv: write error: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleFcall(cs *connState, fc *plan9.Fcall, tree *fsedsl.Tree) *plan9.Fcall {
|
||||
switch fc.Type {
|
||||
case plan9.Tversion:
|
||||
return handleVersion(fc)
|
||||
case plan9.Tauth:
|
||||
return handleAuth(fc)
|
||||
case plan9.Tattach:
|
||||
return handleAttach(cs, fc, tree)
|
||||
case plan9.Twalk:
|
||||
return handleWalk(cs, fc, tree)
|
||||
case plan9.Topen:
|
||||
return handleOpen(cs, fc, tree)
|
||||
case plan9.Tread:
|
||||
return handleRead(cs, fc, tree)
|
||||
case plan9.Twrite:
|
||||
return handleWrite(cs, fc, tree)
|
||||
case plan9.Tclunk:
|
||||
return handleClunk(cs, fc)
|
||||
case plan9.Tstat:
|
||||
return handleStat(cs, fc, tree)
|
||||
default:
|
||||
return errFcall(fc, fmt.Sprintf("unsupported: %d", fc.Type))
|
||||
}
|
||||
}
|
||||
|
||||
func errFcall(fc *plan9.Fcall, msg string) *plan9.Fcall {
|
||||
return &plan9.Fcall{Type: plan9.Rerror, Tag: fc.Tag, Ename: msg}
|
||||
}
|
||||
|
||||
func handleVersion(fc *plan9.Fcall) *plan9.Fcall {
|
||||
msize := fc.Msize
|
||||
if msize > 65536 {
|
||||
msize = 65536
|
||||
}
|
||||
return &plan9.Fcall{Type: plan9.Rversion, Tag: fc.Tag, Msize: msize, Version: "9P2000"}
|
||||
}
|
||||
|
||||
func handleAuth(fc *plan9.Fcall) *plan9.Fcall {
|
||||
// Simple auth - accept all for now
|
||||
// TODO: Implement shared secret verification
|
||||
return &plan9.Fcall{Type: plan9.Rauth, Tag: fc.Tag, Aqid: plan9.Qid{Type: plan9.QTAUTH}}
|
||||
}
|
||||
|
||||
func handleAttach(cs *connState, fc *plan9.Fcall, tree *fsedsl.Tree) *plan9.Fcall {
|
||||
cs.mu.Lock()
|
||||
cs.uname = fc.Uname
|
||||
cs.fids[fc.Fid] = &fid{
|
||||
path: "/",
|
||||
qid: plan9.Qid{Type: plan9.QTDIR, Path: 0},
|
||||
}
|
||||
cs.mu.Unlock()
|
||||
return &plan9.Fcall{Type: plan9.Rattach, Tag: fc.Tag, Qid: plan9.Qid{Type: plan9.QTDIR, Path: 0}}
|
||||
}
|
||||
|
||||
func handleWalk(cs *connState, fc *plan9.Fcall, tree *fsedsl.Tree) *plan9.Fcall {
|
||||
cs.mu.RLock()
|
||||
f, ok := cs.fids[fc.Fid]
|
||||
cs.mu.RUnlock()
|
||||
if !ok {
|
||||
return errFcall(fc, "unknown fid")
|
||||
}
|
||||
|
||||
newPath := f.path
|
||||
var qids []plan9.Qid
|
||||
|
||||
for _, name := range fc.Wname {
|
||||
if name == ".." {
|
||||
if newPath != "/" {
|
||||
lastSlash := len(newPath) - 1
|
||||
for lastSlash > 0 && newPath[lastSlash] != '/' {
|
||||
lastSlash--
|
||||
}
|
||||
if lastSlash == 0 {
|
||||
newPath = "/"
|
||||
} else {
|
||||
newPath = newPath[:lastSlash]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if newPath == "/" {
|
||||
newPath = "/" + name
|
||||
} else {
|
||||
newPath = newPath + "/" + name
|
||||
}
|
||||
}
|
||||
|
||||
// Check if path exists in tree
|
||||
treePath := newPath
|
||||
if treePath == "/" {
|
||||
treePath = ""
|
||||
} else {
|
||||
treePath = treePath[1:] // strip leading /
|
||||
}
|
||||
|
||||
info, err := tree.Stat(treePath)
|
||||
if err != nil {
|
||||
if len(qids) == 0 {
|
||||
return errFcall(fc, "not found")
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
var qt uint8 = plan9.QTFILE
|
||||
if info.IsDir() {
|
||||
qt = plan9.QTDIR
|
||||
}
|
||||
qids = append(qids, plan9.Qid{Type: qt, Path: pathHash(newPath)})
|
||||
}
|
||||
|
||||
if len(qids) == len(fc.Wname) && fc.Newfid != fc.Fid {
|
||||
var lastQid plan9.Qid
|
||||
if len(qids) > 0 {
|
||||
lastQid = qids[len(qids)-1]
|
||||
} else {
|
||||
lastQid = f.qid
|
||||
}
|
||||
cs.mu.Lock()
|
||||
cs.fids[fc.Newfid] = &fid{
|
||||
path: newPath,
|
||||
qid: lastQid,
|
||||
}
|
||||
cs.mu.Unlock()
|
||||
} else if len(qids) == len(fc.Wname) && len(qids) > 0 {
|
||||
cs.mu.Lock()
|
||||
f.path = newPath
|
||||
f.qid = qids[len(qids)-1]
|
||||
cs.mu.Unlock()
|
||||
}
|
||||
|
||||
return &plan9.Fcall{Type: plan9.Rwalk, Tag: fc.Tag, Wqid: qids}
|
||||
}
|
||||
|
||||
func handleOpen(cs *connState, fc *plan9.Fcall, tree *fsedsl.Tree) *plan9.Fcall {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
|
||||
f, ok := cs.fids[fc.Fid]
|
||||
if !ok {
|
||||
return errFcall(fc, "unknown fid")
|
||||
}
|
||||
|
||||
// Directories don't need an entry
|
||||
if f.qid.Type&plan9.QTDIR != 0 {
|
||||
f.opened = true
|
||||
f.mode = fc.Mode
|
||||
return &plan9.Fcall{Type: plan9.Ropen, Tag: fc.Tag, Qid: f.qid, Iounit: 8192}
|
||||
}
|
||||
|
||||
path := f.path
|
||||
if path == "/" {
|
||||
path = ""
|
||||
} else {
|
||||
path = path[1:] // strip leading /
|
||||
}
|
||||
|
||||
entry, err := tree.Open(path)
|
||||
if err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
}
|
||||
|
||||
f.entry = entry
|
||||
f.opened = true
|
||||
f.mode = fc.Mode
|
||||
f.readBuf = nil
|
||||
f.readDone = false
|
||||
|
||||
return &plan9.Fcall{Type: plan9.Ropen, Tag: fc.Tag, Qid: f.qid, Iounit: 8192}
|
||||
}
|
||||
|
||||
func handleRead(cs *connState, fc *plan9.Fcall, tree *fsedsl.Tree) *plan9.Fcall {
|
||||
cs.mu.RLock()
|
||||
f, ok := cs.fids[fc.Fid]
|
||||
cs.mu.RUnlock()
|
||||
if !ok {
|
||||
return errFcall(fc, "unknown fid")
|
||||
}
|
||||
if !f.opened {
|
||||
return errFcall(fc, "not open")
|
||||
}
|
||||
|
||||
if f.qid.Type&plan9.QTDIR != 0 {
|
||||
return handleDirRead(cs, f, fc, tree)
|
||||
}
|
||||
|
||||
if f.entry == nil {
|
||||
return errFcall(fc, "no entry")
|
||||
}
|
||||
|
||||
// For rdwr files, check if we've already read
|
||||
if f.entry.RequestRespMode() && f.readDone {
|
||||
return &plan9.Fcall{Type: plan9.Rread, Tag: fc.Tag, Data: nil}
|
||||
}
|
||||
|
||||
// Buffer read data if not already done
|
||||
if f.readBuf == nil {
|
||||
data, err := f.entry.Read()
|
||||
if err != nil && err != io.EOF {
|
||||
return errFcall(fc, err.Error())
|
||||
}
|
||||
f.readBuf = data
|
||||
}
|
||||
|
||||
// Handle offset/count
|
||||
offset := int(fc.Offset)
|
||||
if offset >= len(f.readBuf) {
|
||||
f.readDone = true
|
||||
return &plan9.Fcall{Type: plan9.Rread, Tag: fc.Tag, Data: nil}
|
||||
}
|
||||
|
||||
end := offset + int(fc.Count)
|
||||
if end > len(f.readBuf) {
|
||||
end = len(f.readBuf)
|
||||
}
|
||||
|
||||
return &plan9.Fcall{Type: plan9.Rread, Tag: fc.Tag, Data: f.readBuf[offset:end]}
|
||||
}
|
||||
|
||||
func handleDirRead(cs *connState, f *fid, fc *plan9.Fcall, tree *fsedsl.Tree) *plan9.Fcall {
|
||||
if fc.Offset == 0 || f.dirCache == nil {
|
||||
path := f.path
|
||||
if path == "/" {
|
||||
path = ""
|
||||
} else {
|
||||
path = path[1:]
|
||||
}
|
||||
|
||||
entries, err := tree.Readdir(path)
|
||||
if err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
for _, e := range entries {
|
||||
var qt uint8 = plan9.QTFILE
|
||||
mode := plan9.Perm(0644)
|
||||
if e.IsDir() {
|
||||
qt = plan9.QTDIR
|
||||
mode = plan9.Perm(plan9.DMDIR | 0755)
|
||||
}
|
||||
dir := plan9.Dir{
|
||||
Name: e.Name(),
|
||||
Qid: plan9.Qid{Type: qt, Path: pathHash(f.path + "/" + e.Name())},
|
||||
Mode: mode,
|
||||
Uid: "toolsrv",
|
||||
Gid: "toolsrv",
|
||||
}
|
||||
data, _ := dir.Bytes()
|
||||
buf = append(buf, data...)
|
||||
}
|
||||
f.dirCache = buf
|
||||
}
|
||||
|
||||
offset := int(fc.Offset)
|
||||
if offset >= len(f.dirCache) {
|
||||
return &plan9.Fcall{Type: plan9.Rread, Tag: fc.Tag, Data: nil}
|
||||
}
|
||||
|
||||
end := offset + int(fc.Count)
|
||||
if end > len(f.dirCache) {
|
||||
end = len(f.dirCache)
|
||||
}
|
||||
|
||||
return &plan9.Fcall{Type: plan9.Rread, Tag: fc.Tag, Data: f.dirCache[offset:end]}
|
||||
}
|
||||
|
||||
func handleWrite(cs *connState, fc *plan9.Fcall, tree *fsedsl.Tree) *plan9.Fcall {
|
||||
cs.mu.Lock()
|
||||
f, ok := cs.fids[fc.Fid]
|
||||
cs.mu.Unlock()
|
||||
if !ok {
|
||||
return errFcall(fc, "unknown fid")
|
||||
}
|
||||
if !f.opened {
|
||||
return errFcall(fc, "not open")
|
||||
}
|
||||
if f.entry == nil {
|
||||
return errFcall(fc, "no entry")
|
||||
}
|
||||
|
||||
err := f.entry.Write(fc.Data)
|
||||
if err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
}
|
||||
|
||||
return &plan9.Fcall{Type: plan9.Rwrite, Tag: fc.Tag, Count: uint32(len(fc.Data))}
|
||||
}
|
||||
|
||||
func handleClunk(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
|
||||
cs.mu.Lock()
|
||||
delete(cs.fids, fc.Fid)
|
||||
cs.mu.Unlock()
|
||||
return &plan9.Fcall{Type: plan9.Rclunk, Tag: fc.Tag}
|
||||
}
|
||||
|
||||
func handleStat(cs *connState, fc *plan9.Fcall, tree *fsedsl.Tree) *plan9.Fcall {
|
||||
cs.mu.RLock()
|
||||
f, ok := cs.fids[fc.Fid]
|
||||
cs.mu.RUnlock()
|
||||
if !ok {
|
||||
return errFcall(fc, "unknown fid")
|
||||
}
|
||||
|
||||
path := f.path
|
||||
if path == "/" {
|
||||
path = ""
|
||||
} else {
|
||||
path = path[1:]
|
||||
}
|
||||
|
||||
info, err := tree.Stat(path)
|
||||
if err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
}
|
||||
|
||||
var qt uint8 = plan9.QTFILE
|
||||
mode := plan9.Perm(0644)
|
||||
if info.IsDir() {
|
||||
qt = plan9.QTDIR
|
||||
mode = plan9.Perm(plan9.DMDIR | 0755)
|
||||
}
|
||||
|
||||
name := info.Name()
|
||||
if name == "" {
|
||||
name = "/"
|
||||
}
|
||||
|
||||
dir := plan9.Dir{
|
||||
Name: name,
|
||||
Qid: plan9.Qid{Type: qt, Path: pathHash(f.path)},
|
||||
Mode: mode,
|
||||
Uid: "toolsrv",
|
||||
Gid: "toolsrv",
|
||||
}
|
||||
data, _ := dir.Bytes()
|
||||
|
||||
return &plan9.Fcall{Type: plan9.Rstat, Tag: fc.Tag, Stat: data}
|
||||
}
|
||||
|
||||
func pathHash(p string) uint64 {
|
||||
var h uint64 = 5381
|
||||
for _, c := range p {
|
||||
h = h*33 + uint64(c)
|
||||
}
|
||||
return h
|
||||
}
|
||||
Loading…
Reference in New Issue