From 53b4618561e41e7d6cb930bba956330a982f7386 Mon Sep 17 00:00:00 2001 From: Ollie Agent Date: Tue, 11 Aug 2026 16:56:34 +0200 Subject: [PATCH] toolsrv: migrate to closure-capture fsedsl API Spec() now takes *Server and all handlers are closures that capture it. Removed Ctx type, type aliases, and separate handler functions. The tree structure and behavior are declared together in one place. --- cmd/toolsrv/internal/fs/spec.go | 229 +++++++++++-------------------- cmd/toolsrv/internal/fs/state.go | 3 +- cmd/toolsrv/main.go | 5 +- 3 files changed, 86 insertions(+), 151 deletions(-) diff --git a/cmd/toolsrv/internal/fs/spec.go b/cmd/toolsrv/internal/fs/spec.go index c2c1079..d7e3b0b 100644 --- a/cmd/toolsrv/internal/fs/spec.go +++ b/cmd/toolsrv/internal/fs/spec.go @@ -11,162 +11,99 @@ import ( "ollie/fsedsl" ) -// Ctx is the context passed through the fsedsl tree. -type Ctx struct { - Server *Server - Proc *Proc // set when traversing into /proc/{pid} -} - -// Type aliases from fsedsl, specialized to Ctx. -type ( - FsNodeDecl = fsedsl.FsNodeDecl[Ctx] - Binding = fsedsl.Binding[Ctx] -) - -// Specialized fsedsl constructors for Ctx. -var ( - DirNode = fsedsl.DirNode[Ctx] - FileNode = fsedsl.FileNode[Ctx] - Each = fsedsl.Each[Ctx] - Doc = fsedsl.Doc[Ctx] - Read = fsedsl.Read[Ctx] - Write = fsedsl.Write[Ctx] - BlockOnce = fsedsl.BlockOnce[Ctx] - Request = fsedsl.Request[Ctx] -) - // Spec returns the fsedsl specification for the toolsrv namespace. -func Spec() FsNodeDecl { - return DirNode("/", - FileNode("ctl", 0222, - Doc("Control: write 'load ' or 'unload '"), - Write(handleCtlWrite), +// The server is captured by closures — no context threading needed. +func Spec(srv *Server) fsedsl.FsNodeDecl { + return fsedsl.DirNode("/", + fsedsl.FileNode("ctl", 0222, + fsedsl.Doc("Control: write 'load ' or 'unload '"), + fsedsl.Write(func(data []byte) error { + return srv.Fs.HandleCtl(strings.TrimSpace(string(data))) + }), ), - FileNode("tools", 0666, - Doc("Tools: write agent ID, read tool list (rdwr)"), - Request(handleToolsRequest), + fsedsl.FileNode("tools", 0666, + fsedsl.Doc("Tools: write agent ID, read tool list (rdwr)"), + fsedsl.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 + }), ), - FileNode("info", 0444, - Doc("Host info: platform, arch"), - Read(handleInfoRead), + fsedsl.FileNode("info", 0444, + fsedsl.Doc("Host info: platform, arch"), + fsedsl.Read(func() ([]byte, error) { + return []byte(srv.HostInfo()), nil + }), ), - DirNode("proc", - FileNode("new", 0666, - Doc("Execute tool: write token + tool + args, read result (blocking)"), - Request(handleProcNew), + fsedsl.DirNode("proc", + fsedsl.FileNode("new", 0666, + fsedsl.Doc("Execute tool: write token + tool + args, read result (blocking)"), + fsedsl.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 + }), ), - FileNode("new.bg", 0666, - Doc("Execute tool in background: write token + tool + args, read pid"), - Request(handleProcNewBg), - ), - Each("{pid}", procBindings, - FileNode("out", 0444, - Doc("Process output"), - Read(handleProcOutRead), - ), - FileNode("wait", 0444, - Doc("Block until process exits, returns exit code"), - BlockOnce(handleProcWait), - ), - FileNode("stat", 0444, - Doc("Process status: running/exited, runtime, tool"), - Read(handleProcStatRead), - ), - FileNode("ctl", 0222, - Doc("Process control: write 'signal ' or 'dismiss'"), - Write(handleProcCtlWrite), - ), + fsedsl.FileNode("new.bg", 0666, + fsedsl.Doc("Execute tool in background: write token + tool + args, read pid"), + fsedsl.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 + }), ), + fsedsl.Each("{pid}", func() ([]fsedsl.Binding, error) { + pids := srv.Fs.ListProcs() + var bindings []fsedsl.Binding + for _, pid := range pids { + proc := srv.Fs.GetProc(pid) + if proc == nil { + continue + } + p := proc + bindings = append(bindings, fsedsl.Binding{ + Name: strconv.Itoa(pid), + Children: []fsedsl.FsNodeDecl{ + fsedsl.FileNode("out", 0444, + fsedsl.Doc("Process output"), + fsedsl.Read(func() ([]byte, error) { + return []byte(p.Output()), nil + }), + ), + fsedsl.FileNode("wait", 0444, + fsedsl.Doc("Block until process exits, returns exit code"), + fsedsl.BlockOnce(func(_ context.Context, _ string) ([]byte, string, error) { + exitCode := p.Wait() + return []byte(fmt.Sprintf("%d\n", exitCode)), "", nil + }), + ), + fsedsl.FileNode("stat", 0444, + fsedsl.Doc("Process status: running/exited, runtime, tool"), + fsedsl.Read(func() ([]byte, error) { + return []byte(p.Stat()), nil + }), + ), + fsedsl.FileNode("ctl", 0222, + fsedsl.Doc("Process control: write 'signal ' or 'dismiss'"), + fsedsl.Write(func(data []byte) error { + return srv.Fs.HandleProcCtl(p.ID, strings.TrimSpace(string(data))) + }), + ), + }, + }) + } + return bindings, nil + }), ), ) } -// --- Handlers --- - -func handleCtlWrite(ctx Ctx, data []byte) error { - return ctx.Server.Fs.HandleCtl(strings.TrimSpace(string(data))) -} - -func handleToolsRequest(ctx Ctx, data []byte) ([]byte, error) { - agentID := strings.TrimSpace(string(data)) - if agentID == "" { - return nil, fmt.Errorf("agent ID required") - } - return []byte(ctx.Server.Fs.HandleToolsRequest(agentID)), nil -} - -func handleInfoRead(ctx Ctx) ([]byte, error) { - return []byte(ctx.Server.HostInfo()), nil -} - -func handleProcNew(ctx Ctx, data []byte) ([]byte, error) { - result, err := ctx.Server.Fs.HandleProcNew(context.Background(), string(data)) - if err != nil { - return nil, err - } - return []byte(result), nil -} - -func handleProcNewBg(ctx Ctx, data []byte) ([]byte, error) { - result, err := ctx.Server.Fs.HandleProcNewBg(context.Background(), string(data)) - if err != nil { - return nil, err - } - return []byte(result), nil -} - -// procBindings returns bindings for each running process. -func procBindings(ctx Ctx) ([]Binding, error) { - pids := ctx.Server.Fs.ListProcs() - var bindings []Binding - - for _, pid := range pids { - proc := ctx.Server.Fs.GetProc(pid) - if proc == nil { - continue - } - p := proc // capture for closure - bindings = append(bindings, Binding{ - Name: strconv.Itoa(pid), - Applier: func(c Ctx) Ctx { - c.Proc = p - return c - }, - }) - } - - return bindings, nil -} - -func handleProcOutRead(ctx Ctx) ([]byte, error) { - if ctx.Proc == nil { - return nil, fmt.Errorf("no process context") - } - return []byte(ctx.Proc.Output()), nil -} - -func handleProcWait(ctx Ctx, cctx context.Context, base string) ([]byte, string, error) { - if ctx.Proc == nil { - return nil, "", fmt.Errorf("no process context") - } - exitCode := ctx.Proc.Wait() - return []byte(fmt.Sprintf("%d\n", exitCode)), "", nil -} - -func handleProcStatRead(ctx Ctx) ([]byte, error) { - if ctx.Proc == nil { - return nil, fmt.Errorf("no process context") - } - return []byte(ctx.Proc.Stat()), nil -} - -func handleProcCtlWrite(ctx Ctx, data []byte) error { - if ctx.Proc == nil { - return fmt.Errorf("no process context") - } - return ctx.Server.Fs.HandleProcCtl(ctx.Proc.ID, strings.TrimSpace(string(data))) -} - // Mode constants for convenience. const ( ModeReadOnly os.FileMode = 0444 diff --git a/cmd/toolsrv/internal/fs/state.go b/cmd/toolsrv/internal/fs/state.go index b1bae36..31e6736 100644 --- a/cmd/toolsrv/internal/fs/state.go +++ b/cmd/toolsrv/internal/fs/state.go @@ -84,8 +84,7 @@ func (s *Server) HostInfo() string { // BuildTree creates the fsedsl tree for this server. func (s *Server) BuildTree() *fsedsl.Tree { - ctx := Ctx{Server: s} - return fsedsl.BuildTree(Spec(), ctx) + return fsedsl.BuildTree(Spec(s)) } // GenerateSecret generates a random shared secret for toolsrv auth. diff --git a/cmd/toolsrv/main.go b/cmd/toolsrv/main.go index 05ff7d0..16616ad 100644 --- a/cmd/toolsrv/main.go +++ b/cmd/toolsrv/main.go @@ -27,7 +27,7 @@ import ( "ollie/cmd/toolsrv/internal/fs" "ollie/cmd/toolsrv/internal/registry" "ollie/env" - "ollie/fsedsl" + olog "ollie/log" "ollie/paths" ) @@ -88,8 +88,7 @@ func main() { } // Build the filesystem tree - ctx := fs.Ctx{Server: srv} - tree := fsedsl.BuildTree(fs.Spec(), ctx) + tree := srv.BuildTree() if tree == nil { logger.Error("failed to build filesystem tree") os.Exit(1)