-
Notifications
You must be signed in to change notification settings - Fork 3.1k
/
command_unix.go
104 lines (82 loc) · 1.91 KB
/
command_unix.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
//go:build linux || darwin
package os_specific
import (
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"syscall"
"time"
"github.com/creack/pty"
"golang.org/x/term"
)
func StartCommand(cmd *exec.Cmd) (func(), error) {
closer := func() {}
if cmd.Stdin == nil {
cmd.Stdin = os.Stdin
}
cmd.SysProcAttr = &syscall.SysProcAttr{}
if !isTerminal(cmd.Stdin) {
// avoid the error "Inappropriate ioctl for device" when
// running in tty
//
// pty.Start uses setsid internally, which makes the process
// the group leader already
Setpgid(cmd.SysProcAttr)
return simpleStart(cmd)
}
stdin, ok := cmd.Stdin.(*os.File)
if !ok {
// should never happen when stdin is a tty
return nil, fmt.Errorf("Cannot convert stdin to os.File, it was %T", cmd.Stdin)
}
stdout := cmd.Stdout
stderr := cmd.Stderr
// pty.Start will not assign these to the pty unless they are nil
cmd.Stdin = nil
cmd.Stdout = nil
cmd.Stderr = nil
ptmx, err := pty.Start(cmd)
if err != nil {
return nil, err
}
// Handle pty size
sigWinchCh := make(chan os.Signal, 1)
signal.Notify(sigWinchCh, syscall.SIGWINCH)
go func() {
for range sigWinchCh {
if err := pty.InheritSize(stdin, ptmx); err != nil {
logger.WithError(err).Warn("Cannot resize pty")
}
}
}()
// Initial resize
sigWinchCh <- syscall.SIGWINCH
oldState, err := term.MakeRaw(int(stdin.Fd()))
if err != nil {
return nil, err
}
go func() { _, _ = io.Copy(ptmx, stdin) }()
go func() { _, _ = io.Copy(stdout, ptmx) }()
go func() { _, _ = io.Copy(stderr, ptmx) }()
origCloser := closer
closer = func() {
signal.Stop(sigWinchCh)
close(sigWinchCh)
_ = term.Restore(int(stdin.Fd()), oldState)
_ = ptmx.Close()
origCloser()
}
return closer, nil
}
func simpleStart(cmd *exec.Cmd) (func(), error) {
if err := cmd.Start(); err != nil {
return nil, err
}
closer := func() {
cmd.WaitDelay = 100 * time.Millisecond
_ = cmd.Wait()
}
return closer, nil
}