diff options
| author | Ophestra <cat@gensokyo.uk> | 2026-03-26 14:02:58 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2026-03-26 14:02:58 +0900 |
| commit | d2f30173cd8d635e37623cf72b4867eb0e6ddf1d (patch) | |
| tree | f3467d3ad18fb77cef48ff0ab0b48ff55a75b750 /internal | |
| parent | 5319ea994c1c93617d7814e710d922a7b147932b (diff) | |
internal/pkg: isolate container params
This enables exporting container params for interactive troubleshooting within the cure container.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/pkg/exec.go | 164 | ||||
| -rw-r--r-- | internal/pkg/exec_test.go | 2 |
2 files changed, 98 insertions, 68 deletions
diff --git a/internal/pkg/exec.go b/internal/pkg/exec.go index e0512bac..244b353b 100644 --- a/internal/pkg/exec.go +++ b/internal/pkg/exec.go @@ -40,14 +40,17 @@ type ExecPath struct { W bool } -// SetSchedIdle is whether to set [std.SCHED_IDLE] scheduling priority. +// SetSchedIdle is whether to set [ext.SCHED_IDLE] scheduling priority. var SetSchedIdle bool +// GetArtifactFunc is the function signature of [FContext.GetArtifact]. +type GetArtifactFunc func(Artifact) (*check.Absolute, unique.Handle[Checksum]) + // PromoteLayers returns artifacts with identical-by-content layers promoted to // the highest priority instance, as if mounted via [ExecPath]. func PromoteLayers( artifacts []Artifact, - getArtifact func(Artifact) (*check.Absolute, unique.Handle[Checksum]), + getArtifact GetArtifactFunc, report func(i int, d Artifact), ) []*check.Absolute { layers := make([]*check.Absolute, 0, len(artifacts)) @@ -67,14 +70,14 @@ func PromoteLayers( } // layers returns pathnames collected from A deduplicated via [PromoteLayers]. -func (p *ExecPath) layers(f *FContext) []*check.Absolute { - msg := f.GetMessage() - return PromoteLayers(p.A, f.GetArtifact, func(i int, d Artifact) { +func (p *ExecPath) layers( + msg message.Msg, + getArtifact GetArtifactFunc, + ident func(a Artifact) unique.Handle[ID], +) []*check.Absolute { + return PromoteLayers(p.A, getArtifact, func(i int, d Artifact) { if msg.IsVerbose() { - msg.Verbosef( - "promoted layer %d as %s", - i, reportName(d, f.cache.Ident(d)), - ) + msg.Verbosef("promoted layer %d as %s", i, reportName(d, ident(d))) } }) } @@ -382,17 +385,30 @@ func scanVerbose( } } +var ( + // ErrInvalidPaths is returned for an [Artifact] of [KindExec] or + // [KindExecNet] specified with invalid paths. + ErrInvalidPaths = errors.New("invalid mount point") +) + // SeccompPresets is the [seccomp] presets used by exec artifacts. const SeccompPresets = std.PresetStrict & ^(std.PresetDenyNS | std.PresetDenyDevel) -// cure is like Cure but allows optional host net namespace. This is used for -// the [KnownChecksum] variant where networking is allowed. -func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) { +// makeContainer sets up the specified temp and work directories and returns the +// corresponding [container.Container] that would have run for cure. +func (a *execArtifact) makeContainer( + ctx context.Context, + msg message.Msg, + hostNet bool, + temp, work *check.Absolute, + getArtifact GetArtifactFunc, + ident func(a Artifact) unique.Handle[ID], +) (z *container.Container, err error) { overlayWorkIndex := -1 for i, p := range a.paths { if p.P == nil || len(p.A) == 0 { - return os.ErrInvalid + return nil, ErrInvalidPaths } if p.P.Is(AbsWork) { overlayWorkIndex = i @@ -404,10 +420,7 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) { artifactCount += len(p.A) } - ctx, cancel := context.WithTimeout(f.Unwrap(), a.timeout) - defer cancel() - - z := container.New(ctx, f.GetMessage()) + z = container.New(ctx, msg) z.WaitDelay = execWaitDelay z.SeccompPresets = SeccompPresets z.SeccompFlags |= seccomp.AllowMultiarch @@ -421,53 +434,9 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) { } z.Uid, z.Gid = (1<<10)-1, (1<<10)-1 - var status io.Writer - if status, err = f.GetStatusWriter(); err != nil { - return - } - - if msg := f.GetMessage(); msg.IsVerbose() { - var stdout, stderr io.ReadCloser - if stdout, err = z.StdoutPipe(); err != nil { - return - } - if stderr, err = z.StderrPipe(); err != nil { - _ = stdout.Close() - return - } - defer func() { - if err != nil && !errors.As(err, new(*exec.ExitError)) { - _ = stdout.Close() - _ = stderr.Close() - } - }() - - brStdout, brStderr := f.cache.getReader(stdout), f.cache.getReader(stderr) - stdoutDone, stderrDone := make(chan struct{}), make(chan struct{}) - go scanVerbose( - msg, cancel, stdoutDone, - "("+a.name+":1)", - io.TeeReader(brStdout, status), - ) - go scanVerbose( - msg, cancel, stderrDone, - "("+a.name+":2)", - io.TeeReader(brStderr, status), - ) - defer func() { - <-stdoutDone - <-stderrDone - f.cache.putReader(brStdout) - f.cache.putReader(brStderr) - }() - } else { - z.Stdout, z.Stderr = status, status - } - z.Dir, z.Env, z.Path, z.Args = a.dir, a.env, a.path, a.args z.Grow(len(a.paths) + 4) - temp, work := f.GetTempDir(), f.GetWorkDir() for i, b := range a.paths { if i == overlayWorkIndex { if err = os.MkdirAll(work.String(), 0700); err != nil { @@ -481,7 +450,7 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) { AbsWork, work, tempWork, - b.layers(f)..., + b.layers(msg, getArtifact, ident)..., ) continue } @@ -498,12 +467,12 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) { if err = os.MkdirAll(tempWork.String(), 0700); err != nil { return } - z.Overlay(b.P, tempUpper, tempWork, b.layers(f)...) + z.Overlay(b.P, tempUpper, tempWork, b.layers(msg, getArtifact, ident)...) } else if len(b.A) == 1 { - pathname, _ := f.GetArtifact(b.A[0]) + pathname, _ := getArtifact(b.A[0]) z.Bind(pathname, b.P, 0) } else { - z.OverlayReadonly(b.P, b.layers(f)...) + z.OverlayReadonly(b.P, b.layers(msg, getArtifact, ident)...) } } if overlayWorkIndex < 0 { @@ -514,11 +483,72 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) { ) } z.Bind( - f.GetTempDir(), + temp, fhs.AbsTmp, std.BindWritable|std.BindEnsure, ) z.Proc(fhs.AbsProc).Dev(fhs.AbsDev, true) + return +} + +// 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) + defer cancel() + + msg := f.GetMessage() + var z *container.Container + if z, err = a.makeContainer( + ctx, msg, hostNet, + f.GetTempDir(), f.GetWorkDir(), + f.GetArtifact, + f.cache.Ident, + ); err != nil { + return + } + + var status io.Writer + if status, err = f.GetStatusWriter(); err != nil { + return + } + + if msg.IsVerbose() { + var stdout, stderr io.ReadCloser + if stdout, err = z.StdoutPipe(); err != nil { + return + } + if stderr, err = z.StderrPipe(); err != nil { + _ = stdout.Close() + return + } + defer func() { + if err != nil && !errors.As(err, new(*exec.ExitError)) { + _ = stdout.Close() + _ = stderr.Close() + } + }() + + brStdout, brStderr := f.cache.getReader(stdout), f.cache.getReader(stderr) + stdoutDone, stderrDone := make(chan struct{}), make(chan struct{}) + go scanVerbose( + msg, cancel, stdoutDone, + "("+a.name+":1)", + io.TeeReader(brStdout, status), + ) + go scanVerbose( + msg, cancel, stderrDone, + "("+a.name+":2)", + io.TeeReader(brStderr, status), + ) + defer func() { + <-stdoutDone + <-stderrDone + f.cache.putReader(brStdout) + f.cache.putReader(brStderr) + }() + } else { + z.Stdout, z.Stderr = status, status + } if err = z.Start(); err != nil { return @@ -532,7 +562,7 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) { // do not allow empty directories to succeed for { - err = syscall.Rmdir(work.String()) + err = syscall.Rmdir(f.GetWorkDir().String()) if err != syscall.EINTR { break } diff --git a/internal/pkg/exec_test.go b/internal/pkg/exec_test.go index baf9b8de..b3eaa74e 100644 --- a/internal/pkg/exec_test.go +++ b/internal/pkg/exec_test.go @@ -92,7 +92,7 @@ func TestExec(t *testing.T) { []string{"testtool"}, pkg.ExecPath{}, - ), nil, pkg.Checksum{}, os.ErrInvalid}, + ), nil, pkg.Checksum{}, pkg.ErrInvalidPaths}, }) // check init failure passthrough |
