github.com/golang/dep@v0.5.4/gps/cmd_unix.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // +build !windows
     6  
     7  package gps
     8  
     9  import (
    10  	"bytes"
    11  	"context"
    12  	"os"
    13  	"os/exec"
    14  	"syscall"
    15  	"time"
    16  
    17  	"github.com/pkg/errors"
    18  	"golang.org/x/sys/unix"
    19  )
    20  
    21  type cmd struct {
    22  	// ctx is provided by the caller; SIGINT is sent when it is cancelled.
    23  	ctx context.Context
    24  	Cmd *exec.Cmd
    25  }
    26  
    27  func commandContext(ctx context.Context, name string, arg ...string) cmd {
    28  	c := exec.Command(name, arg...)
    29  
    30  	// Force subprocesses into their own process group, rather than being in the
    31  	// same process group as the dep process. Because Ctrl-C sent from a
    32  	// terminal will send the signal to the entire currently running process
    33  	// group, this allows us to directly manage the issuance of signals to
    34  	// subprocesses.
    35  	c.SysProcAttr = &syscall.SysProcAttr{
    36  		Setpgid: true,
    37  		Pgid:    0,
    38  	}
    39  
    40  	return cmd{ctx: ctx, Cmd: c}
    41  }
    42  
    43  // CombinedOutput is like (*os/exec.Cmd).CombinedOutput except that it
    44  // terminates subprocesses gently (via os.Interrupt), but resorts to Kill if
    45  // the subprocess fails to exit after 1 minute.
    46  func (c cmd) CombinedOutput() ([]byte, error) {
    47  	// Adapted from (*os/exec.Cmd).CombinedOutput
    48  	if c.Cmd.Stdout != nil {
    49  		return nil, errors.New("exec: Stdout already set")
    50  	}
    51  	if c.Cmd.Stderr != nil {
    52  		return nil, errors.New("exec: Stderr already set")
    53  	}
    54  	var b bytes.Buffer
    55  	c.Cmd.Stdout = &b
    56  	c.Cmd.Stderr = &b
    57  	if err := c.Cmd.Start(); err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	// Adapted from (*os/exec.Cmd).Start
    62  	waitDone := make(chan struct{})
    63  	defer close(waitDone)
    64  	go func() {
    65  		select {
    66  		case <-c.ctx.Done():
    67  			if err := c.Cmd.Process.Signal(os.Interrupt); err != nil {
    68  				// If an error comes back from attempting to signal, proceed
    69  				// immediately to hard kill.
    70  				_ = unix.Kill(-c.Cmd.Process.Pid, syscall.SIGKILL)
    71  			} else {
    72  				defer time.AfterFunc(time.Minute, func() {
    73  					_ = unix.Kill(-c.Cmd.Process.Pid, syscall.SIGKILL)
    74  				}).Stop()
    75  				<-waitDone
    76  			}
    77  		case <-waitDone:
    78  		}
    79  	}()
    80  
    81  	err := c.Cmd.Wait()
    82  	return b.Bytes(), err
    83  }