ollie/cmd/toolsrv/internal/fs/spec.go

113 lines
3.3 KiB
Go

// spec.go - virtfs-based namespace specification for toolsrv.
package fs
import (
"context"
"fmt"
"os"
"strconv"
"strings"
"ollie/virtfs"
)
// Spec returns the virtfs specification for the toolsrv namespace.
// The server is captured by closures — no context threading needed.
func Spec(srv *Server) virtfs.FsNodeDecl {
return virtfs.DirNode("/",
virtfs.FileNode("ctl", 0222,
virtfs.Doc("Control: write 'load <agentID> <tool>' or 'unload <agentID> <tool>'"),
virtfs.Write(func(data []byte) error {
return srv.Fs.HandleCtl(strings.TrimSpace(string(data)))
}),
),
virtfs.FileNode("tools", 0666,
virtfs.Doc("Tools: write agent ID, read tool list (rdwr)"),
virtfs.Request(func(data []byte) ([]byte, error) {
agentID := strings.TrimSpace(string(data))
if agentID == "" {
return nil, fmt.Errorf("agent ID required")
}
return []byte(srv.Fs.HandleToolsRequest(agentID)), nil
}),
),
virtfs.FileNode("info", 0444,
virtfs.Doc("Host info: platform, arch"),
virtfs.Read(func() ([]byte, error) {
return []byte(srv.HostInfo()), nil
}),
),
virtfs.DirNode("proc",
virtfs.FileNode("new", 0666,
virtfs.Doc("Execute tool: write token + tool + args, read result (blocking)"),
virtfs.Request(func(data []byte) ([]byte, error) {
result, err := srv.Fs.HandleProcNew(context.Background(), string(data))
if err != nil {
return nil, err
}
return []byte(result), nil
}),
),
virtfs.FileNode("new.bg", 0666,
virtfs.Doc("Execute tool in background: write token + tool + args, read pid"),
virtfs.Request(func(data []byte) ([]byte, error) {
result, err := srv.Fs.HandleProcNewBg(context.Background(), string(data))
if err != nil {
return nil, err
}
return []byte(result), nil
}),
),
virtfs.Each("{pid}", func() ([]virtfs.Binding, error) {
pids := srv.Fs.ListProcs()
var bindings []virtfs.Binding
for _, pid := range pids {
proc := srv.Fs.GetProc(pid)
if proc == nil {
continue
}
p := proc
bindings = append(bindings, virtfs.Binding{
Name: strconv.Itoa(pid),
Children: []virtfs.FsNodeDecl{
virtfs.FileNode("out", 0444,
virtfs.Doc("Process output"),
virtfs.Read(func() ([]byte, error) {
return []byte(p.Output()), nil
}),
),
virtfs.FileNode("wait", 0444,
virtfs.Doc("Block until process exits, returns exit code"),
virtfs.BlockOnce(func(_ context.Context, _ string) ([]byte, string, error) {
exitCode := p.Wait()
return []byte(fmt.Sprintf("%d\n", exitCode)), "", nil
}),
),
virtfs.FileNode("stat", 0444,
virtfs.Doc("Process status: running/exited, runtime, tool"),
virtfs.Read(func() ([]byte, error) {
return []byte(p.Stat()), nil
}),
),
virtfs.FileNode("ctl", 0222,
virtfs.Doc("Process control: write 'signal <N>' or 'dismiss'"),
virtfs.Write(func(data []byte) error {
return srv.Fs.HandleProcCtl(p.ID, strings.TrimSpace(string(data)))
}),
),
},
})
}
return bindings, nil
}),
),
)
}
// Mode constants for convenience.
const (
ModeReadOnly os.FileMode = 0444
ModeWriteOnly os.FileMode = 0222
ModeReadWrite os.FileMode = 0666
)