aboutsummaryrefslogtreecommitdiffhomepage
path: root/cmd/mbf/daemon.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/mbf/daemon.go')
-rw-r--r--cmd/mbf/daemon.go151
1 files changed, 151 insertions, 0 deletions
diff --git a/cmd/mbf/daemon.go b/cmd/mbf/daemon.go
new file mode 100644
index 00000000..22eeb72b
--- /dev/null
+++ b/cmd/mbf/daemon.go
@@ -0,0 +1,151 @@
+package main
+
+import (
+ "context"
+ "errors"
+ "io"
+ "log"
+ "net"
+ "os"
+ "sync"
+ "testing"
+ "time"
+
+ "hakurei.app/check"
+ "hakurei.app/internal/pkg"
+)
+
+// daemonTimeout is the maximum amount of time cureFromIR will wait on I/O.
+const daemonTimeout = 30 * time.Second
+
+// daemonDeadline returns the deadline corresponding to daemonTimeout, or the
+// zero value when running in a test.
+func daemonDeadline() time.Time {
+ if testing.Testing() {
+ return time.Time{}
+ }
+ return time.Now().Add(daemonTimeout)
+}
+
+// cureFromIR services an IR curing request.
+func cureFromIR(
+ ctx context.Context,
+ cache *pkg.Cache,
+ conn net.Conn,
+) (pkg.Artifact, error) {
+ go func() {
+ <-ctx.Done()
+ _ = conn.SetDeadline(time.Now())
+ }()
+
+ if err := conn.SetReadDeadline(daemonDeadline()); err != nil {
+ return nil, errors.Join(err, conn.Close())
+ }
+
+ a, decodeErr := cache.NewDecoder(conn).Decode()
+ if decodeErr != nil {
+ _, err := conn.Write([]byte("\x00" + decodeErr.Error()))
+ return nil, errors.Join(decodeErr, err, conn.Close())
+ }
+
+ pathname, _, cureErr := cache.Cure(a)
+ if err := conn.SetWriteDeadline(daemonDeadline()); err != nil {
+ if !testing.Testing() || !errors.Is(err, io.ErrClosedPipe) {
+ return a, errors.Join(err, conn.Close())
+ }
+ }
+ if cureErr != nil {
+ _, err := conn.Write([]byte("\x00" + cureErr.Error()))
+ return a, errors.Join(cureErr, err, conn.Close())
+ }
+ _, err := conn.Write([]byte(pathname.String()))
+ if testing.Testing() && errors.Is(err, io.ErrClosedPipe) {
+ return a, nil
+ }
+ return a, errors.Join(err, conn.Close())
+}
+
+// serve services connections from a [net.UnixListener].
+func serve(ctx context.Context, log *log.Logger, cm *cache, ul *net.UnixListener) error {
+ ul.SetUnlinkOnClose(true)
+ if cm.c == nil {
+ if err := cm.open(); err != nil {
+ return errors.Join(err, ul.Close())
+ }
+ }
+
+ var wg sync.WaitGroup
+ defer wg.Wait()
+
+ wg.Go(func() {
+ for {
+ if ctx.Err() != nil {
+ break
+ }
+
+ conn, err := ul.AcceptUnix()
+ if err != nil {
+ if !errors.Is(err, os.ErrDeadlineExceeded) {
+ log.Println(err)
+ }
+ continue
+ }
+ wg.Go(func() {
+ if a, _err := cureFromIR(ctx, cm.c, conn); _err != nil {
+ log.Println(_err)
+ } else {
+ log.Printf(
+ "fulfilled artifact %s",
+ pkg.Encode(cm.c.Ident(a).Value()),
+ )
+ }
+ })
+ }
+ })
+
+ <-ctx.Done()
+ if err := ul.SetDeadline(time.Now()); err != nil {
+ return errors.Join(err, ul.Close())
+ }
+ wg.Wait()
+ return ul.Close()
+}
+
+// cureRemote cures a [pkg.Artifact] on a daemon.
+func cureRemote(
+ ctx context.Context,
+ addr *net.UnixAddr,
+ a pkg.Artifact,
+) (*check.Absolute, error) {
+ conn, err := net.DialUnix("unix", nil, addr)
+ if err != nil {
+ return nil, err
+ }
+
+ go func() {
+ <-ctx.Done()
+ _ = conn.SetDeadline(time.Now())
+ }()
+
+ if err = pkg.NewIR().EncodeAll(conn, a); err != nil {
+ return nil, errors.Join(err, conn.Close())
+ } else if err = conn.CloseWrite(); err != nil {
+ return nil, errors.Join(err, conn.Close())
+ }
+
+ payload, recvErr := io.ReadAll(conn)
+ if err = errors.Join(recvErr, conn.Close()); err != nil {
+ if errors.Is(err, os.ErrDeadlineExceeded) {
+ err = ctx.Err()
+ }
+ return nil, err
+ }
+
+ if len(payload) > 0 && payload[0] == 0 {
+ return nil, errors.New(string(payload[1:]))
+ }
+
+ var p *check.Absolute
+ p, err = check.NewAbs(string(payload))
+ return p, err
+}