-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathprocess.go
106 lines (97 loc) · 2.7 KB
/
process.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package wormhole
import (
"io"
"os"
"os/exec"
"os/signal"
"syscall"
"github.com/sirupsen/logrus"
)
// Process is a wrapper around external program
// It handles execution and shutdown of a program and can notify an optional io.Closer
// when a program is terminated.
type Process struct {
cmd *exec.Cmd
closer io.Closer
logger *logrus.Entry
}
// NewProcess returns the Process to execute a named program
func NewProcess(logger *logrus.Logger, program string, closer io.Closer) *Process {
cs := shellArgs(program)
cmd := exec.Command(cs[0], cs[1:]...)
cmd.Stdin = nil
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = os.Environ()
return &Process{cmd: cmd, closer: closer, logger: logger.WithFields(logrus.Fields{"prefix": "process"})}
}
// Run starts the specified command and returns
// without waiting for the program to complete.
// It sets up a os.Signal listener and passes SIGNINT and SIGTERM to the running program.
// It also closer the Closer if present.
func (p *Process) Run() (err error) {
p.handleOsSignal()
p.logger.Println("Starting program:", p.cmd.Path)
if err = p.cmd.Start(); err != nil {
p.logger.Println("Failed to start program:", err)
if exiterr, ok := err.(*exec.ExitError); ok {
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
p.logger.Printf("Exit Status: %d", status.ExitStatus())
os.Exit(status.ExitStatus())
}
}
}
go wait(p.cmd, p.logger)
return
}
func (p *Process) handleOsSignal() {
signalChan := make(chan os.Signal, 2)
signal.Notify(signalChan)
go func(signalChan <-chan os.Signal) {
for sig := range signalChan {
var exitStatus int
var exited bool
if p.cmd != nil {
exited, exitStatus, _ = signalProcess(p.cmd, sig)
} else {
exitStatus = 0
}
switch sig {
case syscall.SIGINT, syscall.SIGTERM:
p.logger.Println("Cleaning up local agent.")
if p.closer != nil {
p.closer.Close()
}
os.Exit(int(exitStatus))
default:
if p.cmd != nil && exited {
os.Exit(int(exitStatus))
}
}
}
}(signalChan)
}
func signalProcess(cmd *exec.Cmd, sig os.Signal) (exited bool, exitStatus int, err error) {
exited = false
err = cmd.Process.Signal(sig)
if err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
exited = true
status, _ := exiterr.Sys().(syscall.WaitStatus)
exitStatus = status.ExitStatus()
}
}
return
}
func wait(cmd *exec.Cmd, log *logrus.Entry) {
if err := cmd.Wait(); err != nil {
log.Errorln("Program error:", err)
if exiterr, ok := err.(*exec.ExitError); ok {
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
log.Printf("Exit Status: %d", status.ExitStatus())
os.Exit(status.ExitStatus())
}
}
}
log.Println("Terminating program", cmd.Path)
}