hijack.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. package container
  2. import (
  3. "io"
  4. "runtime"
  5. "sync"
  6. "github.com/Sirupsen/logrus"
  7. "github.com/docker/docker/api/types"
  8. "github.com/docker/docker/cli/command"
  9. "github.com/docker/docker/pkg/stdcopy"
  10. "golang.org/x/net/context"
  11. )
  12. // holdHijackedConnection handles copying input to and output from streams to the
  13. // connection
  14. func holdHijackedConnection(ctx context.Context, streams command.Streams, tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
  15. var (
  16. err error
  17. restoreOnce sync.Once
  18. )
  19. if inputStream != nil && tty {
  20. if err := setRawTerminal(streams); err != nil {
  21. return err
  22. }
  23. defer func() {
  24. restoreOnce.Do(func() {
  25. restoreTerminal(streams, inputStream)
  26. })
  27. }()
  28. }
  29. receiveStdout := make(chan error, 1)
  30. if outputStream != nil || errorStream != nil {
  31. go func() {
  32. // When TTY is ON, use regular copy
  33. if tty && outputStream != nil {
  34. _, err = io.Copy(outputStream, resp.Reader)
  35. // we should restore the terminal as soon as possible once connection end
  36. // so any following print messages will be in normal type.
  37. if inputStream != nil {
  38. restoreOnce.Do(func() {
  39. restoreTerminal(streams, inputStream)
  40. })
  41. }
  42. } else {
  43. _, err = stdcopy.StdCopy(outputStream, errorStream, resp.Reader)
  44. }
  45. logrus.Debug("[hijack] End of stdout")
  46. receiveStdout <- err
  47. }()
  48. }
  49. stdinDone := make(chan struct{})
  50. go func() {
  51. if inputStream != nil {
  52. io.Copy(resp.Conn, inputStream)
  53. // we should restore the terminal as soon as possible once connection end
  54. // so any following print messages will be in normal type.
  55. if tty {
  56. restoreOnce.Do(func() {
  57. restoreTerminal(streams, inputStream)
  58. })
  59. }
  60. logrus.Debug("[hijack] End of stdin")
  61. }
  62. if err := resp.CloseWrite(); err != nil {
  63. logrus.Debugf("Couldn't send EOF: %s", err)
  64. }
  65. close(stdinDone)
  66. }()
  67. select {
  68. case err := <-receiveStdout:
  69. if err != nil {
  70. logrus.Debugf("Error receiveStdout: %s", err)
  71. return err
  72. }
  73. case <-stdinDone:
  74. if outputStream != nil || errorStream != nil {
  75. select {
  76. case err := <-receiveStdout:
  77. if err != nil {
  78. logrus.Debugf("Error receiveStdout: %s", err)
  79. return err
  80. }
  81. case <-ctx.Done():
  82. }
  83. }
  84. case <-ctx.Done():
  85. }
  86. return nil
  87. }
  88. func setRawTerminal(streams command.Streams) error {
  89. if err := streams.In().SetRawTerminal(); err != nil {
  90. return err
  91. }
  92. return streams.Out().SetRawTerminal()
  93. }
  94. func restoreTerminal(streams command.Streams, in io.Closer) error {
  95. streams.In().RestoreTerminal()
  96. streams.Out().RestoreTerminal()
  97. // WARNING: DO NOT REMOVE THE OS CHECKS !!!
  98. // For some reason this Close call blocks on darwin..
  99. // As the client exits right after, simply discard the close
  100. // until we find a better solution.
  101. //
  102. // This can also cause the client on Windows to get stuck in Win32 CloseHandle()
  103. // in some cases. See https://github.com/docker/docker/issues/28267#issuecomment-288237442
  104. // Tracked internally at Microsoft by VSO #11352156. In the
  105. // Windows case, you hit this if you are using the native/v2 console,
  106. // not the "legacy" console, and you start the client in a new window. eg
  107. // `start docker run --rm -it microsoft/nanoserver cmd /s /c echo foobar`
  108. // will hang. Remove start, and it won't repro.
  109. if in != nil && runtime.GOOS != "darwin" && runtime.GOOS != "windows" {
  110. return in.Close()
  111. }
  112. return nil
  113. }