62 lines
2.0 KiB
Go
62 lines
2.0 KiB
Go
// Package control exposes a local-only command channel into a running
|
|
// gpu-turnstile service: a named pipe on Windows, a unix socket on Linux.
|
|
// It lets unprivileged local users ask the service to do privileged work
|
|
// that is safe to offer — currently triggering an update check, whose
|
|
// payload is signature-verified regardless of who asks. The channel never
|
|
// accepts data beyond a one-word command, and the server rate-limits
|
|
// triggers, so the worst a local user can cause is a cheap, throttled
|
|
// check and a GPU-idle-gated restart onto a signed binary.
|
|
//
|
|
// Protocol: the client writes one command line, the server answers with
|
|
// one reply line ("OK ..." or "ERR ...") and hangs up.
|
|
package control
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
)
|
|
|
|
// CmdUpdateNow asks the service to check for, stage and (once the GPU is
|
|
// idle) restart onto a signed update immediately.
|
|
const CmdUpdateNow = "update-now"
|
|
|
|
// ErrUnavailable means no running service offers the control channel.
|
|
var ErrUnavailable = errors.New("control channel unavailable")
|
|
|
|
// Handler answers one command; the returned string is sent back as one
|
|
// line. It must start with "OK " or "ERR ".
|
|
type Handler func(cmd string) string
|
|
|
|
// serveConn runs the line protocol on one accepted connection.
|
|
func serveConn(c io.ReadWriteCloser, h Handler) {
|
|
defer c.Close()
|
|
line, err := bufio.NewReader(io.LimitReader(c, 4096)).ReadString('\n')
|
|
cmd := strings.TrimSpace(line)
|
|
if cmd == "" {
|
|
if err != nil {
|
|
return
|
|
}
|
|
fmt.Fprintln(c, "ERR empty command")
|
|
return
|
|
}
|
|
fmt.Fprintln(c, h(cmd))
|
|
}
|
|
|
|
// readReply writes cmd and reads the server's one-line reply.
|
|
func readReply(c io.ReadWriteCloser, cmd string) (string, error) {
|
|
if _, err := fmt.Fprintln(c, cmd); err != nil {
|
|
return "", err
|
|
}
|
|
// The server hangs up after its reply; a broken-pipe error after the
|
|
// last byte still leaves the reply in the buffer.
|
|
data, _ := io.ReadAll(io.LimitReader(c, 4096))
|
|
line := strings.TrimSpace(string(data))
|
|
if line == "" {
|
|
return "", ErrUnavailable
|
|
}
|
|
return line, nil
|
|
}
|