Fix Interactive container hangs when redirecting stdout
| ... | ... |
@@ -22,10 +22,16 @@ type DockerCli struct {
|
| 22 | 22 |
in io.ReadCloser |
| 23 | 23 |
out io.Writer |
| 24 | 24 |
err io.Writer |
| 25 |
- isTerminal bool |
|
| 26 |
- terminalFd uintptr |
|
| 27 | 25 |
tlsConfig *tls.Config |
| 28 | 26 |
scheme string |
| 27 |
+ // inFd holds file descriptor of the client's STDIN, if it's a valid file |
|
| 28 |
+ inFd uintptr |
|
| 29 |
+ // outFd holds file descriptor of the client's STDOUT, if it's a valid file |
|
| 30 |
+ outFd uintptr |
|
| 31 |
+ // isTerminalIn describes if client's STDIN is a TTY |
|
| 32 |
+ isTerminalIn bool |
|
| 33 |
+ // isTerminalOut describes if client's STDOUT is a TTY |
|
| 34 |
+ isTerminalOut bool |
|
| 29 | 35 |
} |
| 30 | 36 |
|
| 31 | 37 |
var funcMap = template.FuncMap{
|
| ... | ... |
@@ -94,9 +100,11 @@ func (cli *DockerCli) LoadConfigFile() (err error) {
|
| 94 | 94 |
|
| 95 | 95 |
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string, tlsConfig *tls.Config) *DockerCli {
|
| 96 | 96 |
var ( |
| 97 |
- isTerminal = false |
|
| 98 |
- terminalFd uintptr |
|
| 99 |
- scheme = "http" |
|
| 97 |
+ inFd uintptr |
|
| 98 |
+ outFd uintptr |
|
| 99 |
+ isTerminalIn = false |
|
| 100 |
+ isTerminalOut = false |
|
| 101 |
+ scheme = "http" |
|
| 100 | 102 |
) |
| 101 | 103 |
|
| 102 | 104 |
if tlsConfig != nil {
|
| ... | ... |
@@ -104,24 +112,34 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string, tlsC |
| 104 | 104 |
} |
| 105 | 105 |
|
| 106 | 106 |
if in != nil {
|
| 107 |
+ if file, ok := in.(*os.File); ok {
|
|
| 108 |
+ inFd = file.Fd() |
|
| 109 |
+ isTerminalIn = term.IsTerminal(inFd) |
|
| 110 |
+ } |
|
| 111 |
+ } |
|
| 112 |
+ |
|
| 113 |
+ if out != nil {
|
|
| 107 | 114 |
if file, ok := out.(*os.File); ok {
|
| 108 |
- terminalFd = file.Fd() |
|
| 109 |
- isTerminal = term.IsTerminal(terminalFd) |
|
| 115 |
+ outFd = file.Fd() |
|
| 116 |
+ isTerminalOut = term.IsTerminal(outFd) |
|
| 110 | 117 |
} |
| 111 | 118 |
} |
| 112 | 119 |
|
| 113 | 120 |
if err == nil {
|
| 114 | 121 |
err = out |
| 115 | 122 |
} |
| 123 |
+ |
|
| 116 | 124 |
return &DockerCli{
|
| 117 |
- proto: proto, |
|
| 118 |
- addr: addr, |
|
| 119 |
- in: in, |
|
| 120 |
- out: out, |
|
| 121 |
- err: err, |
|
| 122 |
- isTerminal: isTerminal, |
|
| 123 |
- terminalFd: terminalFd, |
|
| 124 |
- tlsConfig: tlsConfig, |
|
| 125 |
- scheme: scheme, |
|
| 125 |
+ proto: proto, |
|
| 126 |
+ addr: addr, |
|
| 127 |
+ in: in, |
|
| 128 |
+ out: out, |
|
| 129 |
+ err: err, |
|
| 130 |
+ inFd: inFd, |
|
| 131 |
+ outFd: outFd, |
|
| 132 |
+ isTerminalIn: isTerminalIn, |
|
| 133 |
+ isTerminalOut: isTerminalOut, |
|
| 134 |
+ tlsConfig: tlsConfig, |
|
| 135 |
+ scheme: scheme, |
|
| 126 | 136 |
} |
| 127 | 137 |
} |
| ... | ... |
@@ -277,14 +277,14 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
| 277 | 277 |
// the password or email from the config file, so prompt them |
| 278 | 278 |
if username != authconfig.Username {
|
| 279 | 279 |
if password == "" {
|
| 280 |
- oldState, _ := term.SaveState(cli.terminalFd) |
|
| 280 |
+ oldState, _ := term.SaveState(cli.inFd) |
|
| 281 | 281 |
fmt.Fprintf(cli.out, "Password: ") |
| 282 |
- term.DisableEcho(cli.terminalFd, oldState) |
|
| 282 |
+ term.DisableEcho(cli.inFd, oldState) |
|
| 283 | 283 |
|
| 284 | 284 |
password = readInput(cli.in, cli.out) |
| 285 | 285 |
fmt.Fprint(cli.out, "\n") |
| 286 | 286 |
|
| 287 |
- term.RestoreTerminal(cli.terminalFd, oldState) |
|
| 287 |
+ term.RestoreTerminal(cli.inFd, oldState) |
|
| 288 | 288 |
if password == "" {
|
| 289 | 289 |
return fmt.Errorf("Error : Password Required")
|
| 290 | 290 |
} |
| ... | ... |
@@ -669,7 +669,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
| 669 | 669 |
} |
| 670 | 670 |
|
| 671 | 671 |
if *openStdin || *attach {
|
| 672 |
- if tty && cli.isTerminal {
|
|
| 672 |
+ if tty && cli.isTerminalOut {
|
|
| 673 | 673 |
if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
|
| 674 | 674 |
log.Errorf("Error monitoring TTY size: %s", err)
|
| 675 | 675 |
} |
| ... | ... |
@@ -1821,7 +1821,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
| 1821 | 1821 |
tty = config.GetBool("Tty")
|
| 1822 | 1822 |
) |
| 1823 | 1823 |
|
| 1824 |
- if tty && cli.isTerminal {
|
|
| 1824 |
+ if tty && cli.isTerminalOut {
|
|
| 1825 | 1825 |
if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
|
| 1826 | 1826 |
log.Debugf("Error monitoring TTY size: %s", err)
|
| 1827 | 1827 |
} |
| ... | ... |
@@ -2241,7 +2241,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
| 2241 | 2241 |
return err |
| 2242 | 2242 |
} |
| 2243 | 2243 |
|
| 2244 |
- if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminal {
|
|
| 2244 |
+ if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut {
|
|
| 2245 | 2245 |
if err := cli.monitorTtySize(runResult.Get("Id"), false); err != nil {
|
| 2246 | 2246 |
log.Errorf("Error monitoring TTY size: %s", err)
|
| 2247 | 2247 |
} |
| ... | ... |
@@ -2490,7 +2490,7 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
| 2490 | 2490 |
} |
| 2491 | 2491 |
} |
| 2492 | 2492 |
|
| 2493 |
- if execConfig.Tty && cli.isTerminal {
|
|
| 2493 |
+ if execConfig.Tty && cli.isTerminalIn {
|
|
| 2494 | 2494 |
if err := cli.monitorTtySize(execID, true); err != nil {
|
| 2495 | 2495 |
log.Errorf("Error monitoring TTY size: %s", err)
|
| 2496 | 2496 |
} |
| ... | ... |
@@ -69,20 +69,20 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea |
| 69 | 69 |
|
| 70 | 70 |
var oldState *term.State |
| 71 | 71 |
|
| 72 |
- if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" {
|
|
| 73 |
- oldState, err = term.SetRawTerminal(cli.terminalFd) |
|
| 72 |
+ if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" {
|
|
| 73 |
+ oldState, err = term.SetRawTerminal(cli.inFd) |
|
| 74 | 74 |
if err != nil {
|
| 75 | 75 |
return err |
| 76 | 76 |
} |
| 77 |
- defer term.RestoreTerminal(cli.terminalFd, oldState) |
|
| 77 |
+ defer term.RestoreTerminal(cli.inFd, oldState) |
|
| 78 | 78 |
} |
| 79 | 79 |
|
| 80 | 80 |
if stdout != nil || stderr != nil {
|
| 81 | 81 |
receiveStdout = utils.Go(func() (err error) {
|
| 82 | 82 |
defer func() {
|
| 83 | 83 |
if in != nil {
|
| 84 |
- if setRawTerminal && cli.isTerminal {
|
|
| 85 |
- term.RestoreTerminal(cli.terminalFd, oldState) |
|
| 84 |
+ if setRawTerminal && cli.isTerminalIn {
|
|
| 85 |
+ term.RestoreTerminal(cli.inFd, oldState) |
|
| 86 | 86 |
} |
| 87 | 87 |
// For some reason this Close call blocks on darwin.. |
| 88 | 88 |
// As the client exists right after, simply discard the close |
| ... | ... |
@@ -129,7 +129,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea |
| 129 | 129 |
} |
| 130 | 130 |
} |
| 131 | 131 |
|
| 132 |
- if !cli.isTerminal {
|
|
| 132 |
+ if !cli.isTerminalIn {
|
|
| 133 | 133 |
if err := <-sendStdin; err != nil {
|
| 134 | 134 |
log.Debugf("Error sendStdin: %s", err)
|
| 135 | 135 |
return err |
| ... | ... |
@@ -168,7 +168,7 @@ func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in |
| 168 | 168 |
} |
| 169 | 169 |
|
| 170 | 170 |
if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") || api.MatchesContentType(resp.Header.Get("Content-Type"), "application/x-json-stream") {
|
| 171 |
- return utils.DisplayJSONMessagesStream(resp.Body, stdout, cli.terminalFd, cli.isTerminal) |
|
| 171 |
+ return utils.DisplayJSONMessagesStream(resp.Body, stdout, cli.outFd, cli.isTerminalOut) |
|
| 172 | 172 |
} |
| 173 | 173 |
if stdout != nil || stderr != nil {
|
| 174 | 174 |
// When TTY is ON, use regular copy |
| ... | ... |
@@ -252,10 +252,10 @@ func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
|
| 252 | 252 |
} |
| 253 | 253 |
|
| 254 | 254 |
func (cli *DockerCli) getTtySize() (int, int) {
|
| 255 |
- if !cli.isTerminal {
|
|
| 255 |
+ if !cli.isTerminalOut {
|
|
| 256 | 256 |
return 0, 0 |
| 257 | 257 |
} |
| 258 |
- ws, err := term.GetWinsize(cli.terminalFd) |
|
| 258 |
+ ws, err := term.GetWinsize(cli.outFd) |
|
| 259 | 259 |
if err != nil {
|
| 260 | 260 |
log.Debugf("Error getting size: %s", err)
|
| 261 | 261 |
if ws == nil {
|
| ... | ... |
@@ -1,12 +1,18 @@ |
| 1 | 1 |
package main |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "bufio" |
|
| 4 | 5 |
"fmt" |
| 6 |
+ "io/ioutil" |
|
| 7 |
+ "os" |
|
| 5 | 8 |
"os/exec" |
| 6 | 9 |
"strconv" |
| 7 | 10 |
"strings" |
| 8 | 11 |
"testing" |
| 9 | 12 |
"time" |
| 13 |
+ "unicode" |
|
| 14 |
+ |
|
| 15 |
+ "github.com/kr/pty" |
|
| 10 | 16 |
) |
| 11 | 17 |
|
| 12 | 18 |
func TestEventsUntag(t *testing.T) {
|
| ... | ... |
@@ -166,3 +172,46 @@ func TestEventsImageUntagDelete(t *testing.T) {
|
| 166 | 166 |
} |
| 167 | 167 |
logDone("events - image untag, delete is logged")
|
| 168 | 168 |
} |
| 169 |
+ |
|
| 170 |
+// #5979 |
|
| 171 |
+func TestEventsRedirectStdout(t *testing.T) {
|
|
| 172 |
+ |
|
| 173 |
+ since := time.Now().Unix() |
|
| 174 |
+ |
|
| 175 |
+ cmd(t, "run", "busybox", "true") |
|
| 176 |
+ |
|
| 177 |
+ defer deleteAllContainers() |
|
| 178 |
+ |
|
| 179 |
+ file, err := ioutil.TempFile("", "")
|
|
| 180 |
+ if err != nil {
|
|
| 181 |
+ t.Fatalf("could not create temp file: %v", err)
|
|
| 182 |
+ } |
|
| 183 |
+ defer os.Remove(file.Name()) |
|
| 184 |
+ |
|
| 185 |
+ command := fmt.Sprintf("%s events --since=%d --until=%d > %s", dockerBinary, since, time.Now().Unix(), file.Name())
|
|
| 186 |
+ _, tty, err := pty.Open() |
|
| 187 |
+ if err != nil {
|
|
| 188 |
+ t.Fatalf("Could not open pty: %v", err)
|
|
| 189 |
+ } |
|
| 190 |
+ cmd := exec.Command("sh", "-c", command)
|
|
| 191 |
+ cmd.Stdin = tty |
|
| 192 |
+ cmd.Stdout = tty |
|
| 193 |
+ cmd.Stderr = tty |
|
| 194 |
+ if err := cmd.Run(); err != nil {
|
|
| 195 |
+ t.Fatalf("run err for command %q: %v", command, err)
|
|
| 196 |
+ } |
|
| 197 |
+ |
|
| 198 |
+ scanner := bufio.NewScanner(file) |
|
| 199 |
+ for scanner.Scan() {
|
|
| 200 |
+ for _, c := range scanner.Text() {
|
|
| 201 |
+ if unicode.IsControl(c) {
|
|
| 202 |
+ t.Fatalf("found control character %v", []byte(string(c)))
|
|
| 203 |
+ } |
|
| 204 |
+ } |
|
| 205 |
+ } |
|
| 206 |
+ if err := scanner.Err(); err != nil {
|
|
| 207 |
+ t.Fatalf("Scan err for command %q: %v", command, err)
|
|
| 208 |
+ } |
|
| 209 |
+ |
|
| 210 |
+ logDone("events - redirect stdout")
|
|
| 211 |
+} |
| ... | ... |
@@ -19,6 +19,7 @@ import ( |
| 19 | 19 |
|
| 20 | 20 |
"github.com/docker/docker/pkg/mount" |
| 21 | 21 |
"github.com/docker/docker/pkg/networkfs/resolvconf" |
| 22 |
+ "github.com/kr/pty" |
|
| 22 | 23 |
) |
| 23 | 24 |
|
| 24 | 25 |
// "test123" should be printed by docker run |
| ... | ... |
@@ -2182,3 +2183,41 @@ func TestRunExecDir(t *testing.T) {
|
| 2182 | 2182 |
|
| 2183 | 2183 |
logDone("run - check execdriver dir behavior")
|
| 2184 | 2184 |
} |
| 2185 |
+ |
|
| 2186 |
+// #6509 |
|
| 2187 |
+func TestRunRedirectStdout(t *testing.T) {
|
|
| 2188 |
+ |
|
| 2189 |
+ defer deleteAllContainers() |
|
| 2190 |
+ |
|
| 2191 |
+ checkRedirect := func(command string) {
|
|
| 2192 |
+ _, tty, err := pty.Open() |
|
| 2193 |
+ if err != nil {
|
|
| 2194 |
+ t.Fatalf("Could not open pty: %v", err)
|
|
| 2195 |
+ } |
|
| 2196 |
+ cmd := exec.Command("sh", "-c", command)
|
|
| 2197 |
+ cmd.Stdin = tty |
|
| 2198 |
+ cmd.Stdout = tty |
|
| 2199 |
+ cmd.Stderr = tty |
|
| 2200 |
+ ch := make(chan struct{})
|
|
| 2201 |
+ if err := cmd.Start(); err != nil {
|
|
| 2202 |
+ t.Fatalf("start err: %v", err)
|
|
| 2203 |
+ } |
|
| 2204 |
+ go func() {
|
|
| 2205 |
+ if err := cmd.Wait(); err != nil {
|
|
| 2206 |
+ t.Fatalf("wait err=%v", err)
|
|
| 2207 |
+ } |
|
| 2208 |
+ close(ch) |
|
| 2209 |
+ }() |
|
| 2210 |
+ |
|
| 2211 |
+ select {
|
|
| 2212 |
+ case <-time.After(time.Second): |
|
| 2213 |
+ t.Fatal("command timeout")
|
|
| 2214 |
+ case <-ch: |
|
| 2215 |
+ } |
|
| 2216 |
+ } |
|
| 2217 |
+ |
|
| 2218 |
+ checkRedirect(dockerBinary + " run -i busybox cat /etc/passwd | grep -q root") |
|
| 2219 |
+ checkRedirect(dockerBinary + " run busybox cat /etc/passwd | grep -q root") |
|
| 2220 |
+ |
|
| 2221 |
+ logDone("run - redirect stdout")
|
|
| 2222 |
+} |