176 lines
4.2 KiB
Go
176 lines
4.2 KiB
Go
// spec.go - fsedsl-based namespace specification for toolsrv.
|
|
package fs
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"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 <tool>' or 'unload <tool>'"),
|
|
Write(handleCtlWrite),
|
|
),
|
|
FileNode("tools", 0666,
|
|
Doc("Tools: write agent ID, read tool list (rdwr)"),
|
|
Request(handleToolsRequest),
|
|
),
|
|
FileNode("info", 0444,
|
|
Doc("Host info: platform, arch"),
|
|
Read(handleInfoRead),
|
|
),
|
|
DirNode("proc",
|
|
FileNode("new", 0666,
|
|
Doc("Execute tool: write token + tool + args, read result (blocking)"),
|
|
Request(handleProcNew),
|
|
),
|
|
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 <N>' or 'dismiss'"),
|
|
Write(handleProcCtlWrite),
|
|
),
|
|
),
|
|
),
|
|
)
|
|
}
|
|
|
|
// --- 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
|
|
ModeWriteOnly os.FileMode = 0222
|
|
ModeReadWrite os.FileMode = 0666
|
|
)
|