aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/pkg/exec.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-03-26 15:00:25 +0900
committerOphestra <cat@gensokyo.uk>2026-03-26 15:05:04 +0900
commite661260607573009d0f7b0cbcbb8ee561fc08f3f (patch)
tree8a4f1393ed289acc023add65ba42eaf7b701b4f6 /internal/pkg/exec.go
parent044490e0a521036ce10fed0b6813a8ff912237b5 (diff)
internal/pkg: enter exec container
This enables much easier troubleshooting of failing cures. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/pkg/exec.go')
-rw-r--r--internal/pkg/exec.go98
1 files changed, 98 insertions, 0 deletions
diff --git a/internal/pkg/exec.go b/internal/pkg/exec.go
index 244b353b..f1385529 100644
--- a/internal/pkg/exec.go
+++ b/internal/pkg/exec.go
@@ -491,6 +491,104 @@ func (a *execArtifact) makeContainer(
return
}
+var (
+ // ErrExecBusy is returned entering [Cache.EnterExec] while another
+ // goroutine has not yet returned from it.
+ ErrExecBusy = errors.New("scratch directories in use")
+ // ErrNotExec is returned for unsupported implementations of [Artifact]
+ // passed to [Cache.EnterExec].
+ ErrNotExec = errors.New("attempting to run a non-exec artifact")
+)
+
+// EnterExec runs the container of an [Artifact] of [KindExec] or [KindExecNet]
+// with its entry point, argument, and standard streams replaced with values
+// supplied by the caller.
+func (c *Cache) EnterExec(
+ ctx context.Context,
+ a Artifact,
+ retainSession bool,
+ stdin io.Reader,
+ stdout, stderr io.Writer,
+ path *check.Absolute,
+ args ...string,
+) (err error) {
+ if !c.inExec.CompareAndSwap(false, true) {
+ return ErrExecBusy
+ }
+ defer c.inExec.Store(false)
+
+ var hostNet bool
+ var e *execArtifact
+ switch f := a.(type) {
+ case *execArtifact:
+ e = f
+
+ case *execNetArtifact:
+ e = &f.execArtifact
+ hostNet = true
+
+ default:
+ return ErrNotExec
+ }
+
+ deps := Collect(a.Dependencies())
+ if _, _, err = c.Cure(&deps); err == nil {
+ return errors.New("unreachable")
+ } else if !IsCollected(err) {
+ return
+ }
+
+ dm := make(map[Artifact]cureRes)
+ for i, p := range deps {
+ var res cureRes
+ res.pathname, res.checksum, err = c.Cure(p)
+ if err != nil {
+ return
+ }
+ dm[deps[i]] = res
+ }
+
+ scratch := c.base.Append(dirExecScratch)
+ temp, work := scratch.Append("temp"), scratch.Append("work")
+ // work created during makeContainer
+ if err = os.MkdirAll(temp.String(), 0700); err != nil {
+ return
+ }
+ defer func() {
+ if chmodErr, removeErr := removeAll(scratch); chmodErr != nil || removeErr != nil {
+ err = errors.Join(err, chmodErr, removeErr)
+ }
+ }()
+
+ var z *container.Container
+ z, err = e.makeContainer(
+ ctx, c.msg,
+ hostNet,
+ temp, work,
+ func(a Artifact) (*check.Absolute, unique.Handle[Checksum]) {
+ if res, ok := dm[a]; ok {
+ return res.pathname, res.checksum
+ }
+ panic(InvalidLookupError(c.Ident(a).Value()))
+ },
+ c.Ident,
+ )
+ if err != nil {
+ return
+ }
+ z.Stdin, z.Stdout, z.Stderr = stdin, stdout, stderr
+ z.Path, z.Args = path, args
+ z.RetainSession = retainSession
+
+ if err = z.Start(); err != nil {
+ return
+ }
+ if err = z.Serve(); err != nil {
+ return
+ }
+ return z.Wait()
+}
+
// cure is like Cure but allows optional host net namespace.
func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
ctx, cancel := context.WithTimeout(f.Unwrap(), a.timeout)