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 }