forked from superfly/flyctl
-
Notifications
You must be signed in to change notification settings - Fork 0
/
io.go
132 lines (112 loc) · 2.31 KB
/
io.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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package ssh
import (
"context"
"errors"
"io"
"runtime"
"sync"
"golang.org/x/crypto/ssh"
"golang.org/x/term"
)
const (
DefaultHeight = 40
DefaultWidth = 80
)
var modes = ssh.TerminalModes{
ssh.ECHO: 0, // disable echoing
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
// FdReader is an io.Reader with an Fd function
type FdReader interface {
io.Reader
Fd() uintptr
}
type SessionIO struct {
Stdin io.Reader
Stdout io.WriteCloser
Stderr io.WriteCloser
AllocPTY bool
TermEnv string
}
func getFd(reader io.Reader) (fd int, ok bool) {
fdthing, ok := reader.(FdReader)
if !ok {
return 0, false
}
fd = int(fdthing.Fd())
return fd, term.IsTerminal(fd)
}
func (s *SessionIO) attach(ctx context.Context, sess *ssh.Session, cmd string) error {
if s.AllocPTY {
width, height := DefaultWidth, DefaultHeight
if fd, ok := getFd(s.Stdin); ok {
state, err := term.MakeRaw(fd)
if err != nil {
return err
}
defer term.Restore(fd, state)
// BUG(tqbf): this is a temporary hack to work around a windows
// terminal handling problem that is probably trivial to fix, but
// winch isn't handled yet there anyways
if runtime.GOOS != "windows" {
width, height, err = term.GetSize(fd)
if err != nil {
return err
}
go watchWindowSize(ctx, fd, sess)
}
}
if err := sess.RequestPty(s.TermEnv, height, width, modes); err != nil {
return err
}
}
var closeStdin sync.Once
stdin, err := sess.StdinPipe()
if err != nil {
return err
}
defer closeStdin.Do(func() {
stdin.Close()
})
stdout, err := sess.StdoutPipe()
if err != nil {
return err
}
stderr, err := sess.StderrPipe()
if err != nil {
return err
}
go func() {
defer closeStdin.Do(func() {
stdin.Close()
})
if s.Stdin != nil {
io.Copy(stdin, s.Stdin)
}
}()
if s.Stdout != nil {
go io.Copy(s.Stdout, stdout)
}
if s.Stderr != nil {
go io.Copy(s.Stderr, stderr)
}
cmdC := make(chan error, 1)
go func() {
defer close(cmdC)
if cmd == "" {
err = sess.Shell()
} else {
err = sess.Run(cmd)
}
if err != nil && err != io.EOF {
cmdC <- err
}
}()
select {
case err := <-cmdC:
return err
case <-ctx.Done():
return errors.New("session forcibly closed; the remote process may still be running")
}
}