ollie/cmd/toolsrv/server.go

519 lines
11 KiB
Go

// server.go - 9P filesystem server for toolsrv.
package main
import (
"context"
"fmt"
"io"
"net"
"os"
"sync"
"ollie/fsedsl"
"ollie/toolsrv"
"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
}
// authFid represents an auth file handle for Tauth.
type authFid struct {
uname string
aname string
authenticated bool
token string
}
// connState holds per-connection state.
type connState struct {
mu sync.RWMutex
fids map[uint32]*fid
authFids map[uint32]*authFid
ctx context.Context
uname string
srv *toolsrv.Server9P
noAuth bool // skip authentication (for debugging)
}
// serve9P handles a single 9P connection using the fsedsl tree.
func serve9P(ctx context.Context, conn net.Conn, tree *fsedsl.Tree, srv *toolsrv.Server9P, noAuth bool) {
defer conn.Close()
cs := &connState{
fids: make(map[uint32]*fid),
authFids: make(map[uint32]*authFid),
ctx: ctx,
noAuth: noAuth,
srv: srv,
}
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(cs, 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(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
cs.mu.Lock()
cs.authFids[fc.Afid] = &authFid{
uname: fc.Uname,
aname: fc.Aname,
}
cs.mu.Unlock()
return &plan9.Fcall{
Type: plan9.Rauth,
Tag: fc.Tag,
Aqid: plan9.Qid{Type: plan9.QTAUTH, Vers: 0, Path: uint64(fc.Afid)},
}
}
func handleAttach(cs *connState, fc *plan9.Fcall, tree *fsedsl.Tree) *plan9.Fcall {
// Skip auth check if noAuth is set (for debugging)
if cs.noAuth {
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}}
}
// Check auth - afid must exist and be authenticated
cs.mu.RLock()
af, hasAuth := cs.authFids[fc.Afid]
cs.mu.RUnlock()
// NOFID means no auth required (we require auth)
if fc.Afid == plan9.NOFID {
return errFcall(fc, "authentication required")
}
if !hasAuth {
return errFcall(fc, "invalid afid")
}
if !af.authenticated {
return errFcall(fc, "not authenticated")
}
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}}
}
// handleAuthWrite handles writing the secret to an auth fid.
func handleAuthWrite(cs *connState, fc *plan9.Fcall, af *authFid) *plan9.Fcall {
clientSecret := string(fc.Data)
token, err := cs.srv.Authenticate(clientSecret)
if err != nil {
return errFcall(fc, err.Error())
}
af.authenticated = true
af.token = token
return &plan9.Fcall{Type: plan9.Rwrite, Tag: fc.Tag, Count: uint32(len(fc.Data))}
}
// handleAuthRead handles reading the token from an auth fid.
func handleAuthRead(cs *connState, fc *plan9.Fcall, af *authFid) *plan9.Fcall {
if !af.authenticated {
return errFcall(fc, "write secret first")
}
data := []byte(af.token + "\n")
offset := int(fc.Offset)
if offset >= len(data) {
return &plan9.Fcall{Type: plan9.Rread, Tag: fc.Tag, Data: nil}
}
end := offset + int(fc.Count)
if end > len(data) {
end = len(data)
}
return &plan9.Fcall{Type: plan9.Rread, Tag: fc.Tag, Data: data[offset:end]}
}
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 {
// Check if reading from auth fid
cs.mu.RLock()
af, isAuthFid := cs.authFids[fc.Fid]
f, isFid := cs.fids[fc.Fid]
cs.mu.RUnlock()
if isAuthFid {
return handleAuthRead(cs, fc, af)
}
if !isFid {
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 {
// Check if writing to auth fid
cs.mu.Lock()
af, isAuthFid := cs.authFids[fc.Fid]
f, isFid := cs.fids[fc.Fid]
cs.mu.Unlock()
if isAuthFid {
return handleAuthWrite(cs, fc, af)
}
if !isFid {
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)
delete(cs.authFids, 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
}