/
hijack.go
192 lines (175 loc) · 4.46 KB
/
hijack.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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package docker
import (
"context"
"io"
"io/ioutil"
"runtime"
"sync"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stdcopy"
"github.com/pkg/errors"
)
// redirectResponseToOutputStream redirect the response stream to stdout and stderr. When tty is true, all stream will
// only be redirected to stdout.
func redirectResponseToOutputStream(tty bool, outputStream, errorStream io.Writer, resp io.Reader) error {
if outputStream == nil {
outputStream = ioutil.Discard
}
if errorStream == nil {
errorStream = ioutil.Discard
}
var err error
if tty {
_, err = io.Copy(outputStream, resp)
} else {
_, err = stdcopy.StdCopy(outputStream, errorStream, resp)
}
return err
}
func holdHijackedConnection0(ctx context.Context, streams Streams, tty bool,
inputStream io.ReadCloser, outputStream, errorStream io.Writer,
resp types.HijackedResponse) error {
var (
restoreOnce sync.Once
)
if inputStream != nil && tty {
if err := setRawTerminal(streams); err != nil {
return err
}
defer func() {
restoreOnce.Do(func() {
restoreTerminal(streams, inputStream)
})
}()
}
receiveStdout := make(chan error)
if outputStream != nil || errorStream != nil {
go func() {
receiveStdout <- redirectResponseToOutputStream(tty, outputStream, errorStream, resp.Reader)
}()
}
stdinDone := make(chan struct{})
go func() {
if inputStream != nil {
io.Copy(resp.Conn, inputStream)
}
restoreOnce.Do(func() {
restoreTerminal(streams, inputStream)
})
resp.CloseWrite()
close(stdinDone)
}()
select {
case err := <-receiveStdout:
if err != nil {
log.Debugf("Error receiveStdout: %s", err)
return errors.Wrap(err, "while hijacking stdout")
}
return err
case <-stdinDone:
if outputStream != nil || errorStream != nil {
return <-receiveStdout
}
case <-ctx.Done():
break
}
return nil
}
// holdHijackedConnection handles copying input to and output from streams to the
// connection
func holdHijackedConnection(ctx context.Context, streams Streams, tty bool,
inputStream io.ReadCloser, outputStream, errorStream io.Writer,
resp types.HijackedResponse) error {
var (
restoreOnce sync.Once
)
if inputStream != nil && tty {
if err := setRawTerminal(streams); err != nil {
return err
}
defer func() {
restoreOnce.Do(func() {
restoreTerminal(streams, inputStream)
})
}()
}
receiveStdout := make(chan error, 1)
if outputStream != nil || errorStream != nil {
go func() {
var err error
// When TTY is ON, use regular copy
if tty && outputStream != nil {
_, err = io.Copy(outputStream, resp.Reader)
// we should restore the terminal as soon as possible once connection end
// so any following print messages will be in normal type.
if inputStream != nil {
restoreOnce.Do(func() {
restoreTerminal(streams, inputStream)
})
}
} else {
_, err = stdcopy.StdCopy(outputStream, errorStream, resp.Reader)
}
receiveStdout <- err
}()
}
stdinDone := make(chan struct{})
go func() {
if inputStream != nil {
io.Copy(resp.Conn, inputStream)
// we should restore the terminal as soon as possible once connection end
// so any following print messages will be in normal type.
if tty {
restoreOnce.Do(func() {
restoreTerminal(streams, inputStream)
})
}
}
if err := resp.CloseWrite(); err != nil {
log.Debugf("Couldn't send EOF: %s", err)
}
close(stdinDone)
}()
select {
case err := <-receiveStdout:
if err != nil {
log.Debugf("Error receiveStdout: %s", err)
return errors.Wrap(err, "while hijacking stdout")
}
return nil
case <-stdinDone:
if outputStream != nil || errorStream != nil {
select {
case err := <-receiveStdout:
if err != nil {
log.Debugf("Error receiveStdout: %s", err)
return errors.Wrap(err, "stdin done while hijacking stdout")
}
return nil
case <-ctx.Done():
break
}
}
case <-ctx.Done():
break
}
return nil
}
func setRawTerminal(streams Streams) error {
if err := streams.In().SetRawTerminal(); err != nil {
return err
}
return streams.Out().SetRawTerminal()
}
func restoreTerminal(streams Streams, in io.Closer) error {
streams.In().RestoreTerminal()
streams.Out().RestoreTerminal()
// WARNING: DO NOT REMOVE THE OS CHECK !!!
// For some reason this Close call blocks on darwin..
// As the client exists right after, simply discard the close
// until we find a better solution.
if in != nil && runtime.GOOS != "darwin" {
return in.Close()
}
return nil
}