| // Copyright 2017 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // +build !windows |
| |
| package gps |
| |
| import ( |
| "bytes" |
| "context" |
| "os" |
| "os/exec" |
| "syscall" |
| "time" |
| |
| "github.com/pkg/errors" |
| "golang.org/x/sys/unix" |
| ) |
| |
| type cmd struct { |
| // ctx is provided by the caller; SIGINT is sent when it is cancelled. |
| ctx context.Context |
| Cmd *exec.Cmd |
| } |
| |
| func commandContext(ctx context.Context, name string, arg ...string) cmd { |
| c := exec.Command(name, arg...) |
| |
| // Force subprocesses into their own process group, rather than being in the |
| // same process group as the dep process. Because Ctrl-C sent from a |
| // terminal will send the signal to the entire currently running process |
| // group, this allows us to directly manage the issuance of signals to |
| // subprocesses. |
| c.SysProcAttr = &syscall.SysProcAttr{ |
| Setpgid: true, |
| Pgid: 0, |
| } |
| |
| return cmd{ctx: ctx, Cmd: c} |
| } |
| |
| // CombinedOutput is like (*os/exec.Cmd).CombinedOutput except that it |
| // terminates subprocesses gently (via os.Interrupt), but resorts to Kill if |
| // the subprocess fails to exit after 1 minute. |
| func (c cmd) CombinedOutput() ([]byte, error) { |
| // Adapted from (*os/exec.Cmd).CombinedOutput |
| if c.Cmd.Stdout != nil { |
| return nil, errors.New("exec: Stdout already set") |
| } |
| if c.Cmd.Stderr != nil { |
| return nil, errors.New("exec: Stderr already set") |
| } |
| var b bytes.Buffer |
| c.Cmd.Stdout = &b |
| c.Cmd.Stderr = &b |
| if err := c.Cmd.Start(); err != nil { |
| return nil, err |
| } |
| |
| // Adapted from (*os/exec.Cmd).Start |
| waitDone := make(chan struct{}) |
| defer close(waitDone) |
| go func() { |
| select { |
| case <-c.ctx.Done(): |
| if err := c.Cmd.Process.Signal(os.Interrupt); err != nil { |
| // If an error comes back from attempting to signal, proceed |
| // immediately to hard kill. |
| _ = unix.Kill(-c.Cmd.Process.Pid, syscall.SIGKILL) |
| } else { |
| defer time.AfterFunc(time.Minute, func() { |
| _ = unix.Kill(-c.Cmd.Process.Pid, syscall.SIGKILL) |
| }).Stop() |
| <-waitDone |
| } |
| case <-waitDone: |
| } |
| }() |
| |
| err := c.Cmd.Wait() |
| return b.Bytes(), err |
| } |