-
Notifications
You must be signed in to change notification settings - Fork 20
/
shell.go
118 lines (105 loc) · 3.81 KB
/
shell.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
107
108
109
110
111
112
113
114
115
116
117
118
package main
import (
"bytes"
"fmt"
"os"
"os/exec"
"syscall"
"unsafe"
)
type ShellServiceInterface interface {
// Returns: exitStatus int and signaled bool.
// Set separatePGroup to true in order to ignore signals. Then, you should never
// process the signaled return value.
RunInteractive(cmdString string, separatePGroup bool) (int, bool)
// Returns: stdout string, stderr string, exitStatus int and signaled bool.
// Set separatePGroup to true in order to ignore signals. Then, you should never
// process the signaled return value.
RunGetOutput(cmdString string, separatePGroup bool) (string, string, int, bool)
CheckIfInteractive() bool
// set environment variables, override any existing variables
SetEnvironment(variables []string)
}
func NewBashShellService(logger *Logger) *BashShellService {
return &BashShellService{
Logger: logger,
Environment: make([]string, 0),
}
}
type BashShellService struct {
Logger *Logger
// collection of environment variables,
// which are supposed to be preserved when running
// a command with this struct
Environment []string
}
func (bs *BashShellService) SetEnvironment(variables []string) {
bs.Environment = make([]string, 0)
for _, value := range variables {
bs.Environment = append(bs.Environment, value)
}
}
func (bs BashShellService) RunInteractive(cmdString string, separatePGroup bool) (int, bool) {
cmd := exec.Command("bash", "-c", cmdString)
if separatePGroup {
cmd.SysProcAttr = &syscall.SysProcAttr{
// Run in a separate process group, so that signals are not preserved. In theory
// Setpgid: true should work, but it does not. Maybe this is because we run in "bash -c" ?
// https://stackoverflow.com/questions/43364958/start-command-with-new-process-group-id-golang
Setsid: true,
}
}
cmd.Stdout = os.Stdout
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
cmd.Env = bs.Environment
err := cmd.Run()
status := cmd.ProcessState.Sys().(syscall.WaitStatus)
exitStatus := status.ExitStatus()
signaled := status.Signaled()
signal := status.Signal()
if err != nil && exitStatus == 0 {
panic(fmt.Sprintf("unexpected: err not nil, exitStatus was 0, while running: %s", cmdString))
}
if signaled {
bs.Logger.Log("debug", fmt.Sprintf("Signal: %v, while running: %s", signal, cmdString))
}
return exitStatus, signaled
}
func (bs BashShellService) RunGetOutput(cmdString string, separatePGroup bool) (string, string, int, bool) {
cmd := exec.Command("bash", "-c", cmdString)
if separatePGroup {
cmd.SysProcAttr = &syscall.SysProcAttr{
// Run in a separate process group, so that signals are not preserved. In theory
// Setpgid: true should work, but it does not. Maybe this is because we run in "bash -c" ?
// https://stackoverflow.com/questions/43364958/start-command-with-new-process-group-id-golang
Setsid: true,
}
}
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.Env = bs.Environment
err := cmd.Run()
status := cmd.ProcessState.Sys().(syscall.WaitStatus)
exitStatus := status.ExitStatus()
signaled := status.Signaled()
signal := status.Signal()
if err != nil && exitStatus == 0 {
panic(fmt.Sprintf("unexpected: err not nil, exitStatus was 0, while running: %s", cmdString))
}
if signaled {
bs.Logger.Log("debug", fmt.Sprintf("Signal: %v, while running: %s", signal, cmdString))
}
return stdout.String(), stderr.String(), exitStatus, signaled
}
func (bs BashShellService) CheckIfInteractive() bool {
// stolen from: https://github.com/mattn/go-isatty/blob/master/isatty_linux.go
fd := os.Stdout.Fd()
var termios syscall.Termios
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, uintptr(ioctlReadTermios), uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
interactive := (err == 0)
bs.Logger.Log("debug", fmt.Sprintf("Current shell is interactive: %v", interactive))
return interactive
}