diff --git a/internal/pkg/exec.go b/internal/pkg/exec.go index 8fd6898..8d27d07 100644 --- a/internal/pkg/exec.go +++ b/internal/pkg/exec.go @@ -1,11 +1,14 @@ package pkg import ( + "bufio" "bytes" "context" "errors" "fmt" + "io" "os" + "os/exec" "path" "slices" "strconv" @@ -16,6 +19,7 @@ import ( "hakurei.app/container/check" "hakurei.app/container/fhs" "hakurei.app/container/std" + "hakurei.app/message" ) // AbsWork is the container pathname [CureContext.GetWorkDir] is mounted on. @@ -57,7 +61,8 @@ const ( // Methods of execArtifact does not modify any struct field or underlying arrays // referred to by slices. type execArtifact struct { - // Caller-supplied user-facing reporting name. + // Caller-supplied user-facing reporting name, guaranteed to be nonzero + // during initialisation. name string // Caller-supplied inner mount points. paths []ExecPath @@ -219,6 +224,23 @@ const ( execWaitDelay = time.Nanosecond ) +// scanVerbose prefixes program output for a verbose [message.Msg]. +func scanVerbose( + msg message.Msg, + done chan<- struct{}, + prefix string, + r io.Reader, +) { + defer close(done) + s := bufio.NewScanner(r) + for s.Scan() { + msg.Verbose(prefix, s.Text()) + } + if err := s.Err(); err != nil && !errors.Is(err, os.ErrClosed) { + msg.Verbose("*"+prefix, err) + } +} + // 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) { @@ -250,8 +272,26 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) { z.Hostname = "cure-net" } z.Uid, z.Gid = (1<<10)-1, (1<<10)-1 - if f.GetMessage().IsVerbose() { - z.Stdout, z.Stderr = os.Stdout, os.Stderr + 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() + } + }() + + stdoutDone, stderrDone := make(chan struct{}), make(chan struct{}) + go scanVerbose(msg, stdoutDone, "("+a.name+":1)", stdout) + go scanVerbose(msg, stderrDone, "("+a.name+":2)", stderr) + defer func() { <-stdoutDone; <-stderrDone }() } z.Dir, z.Env, z.Path, z.Args = a.dir, a.env, a.path, a.args