hijack.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  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 CHECK !!!
  98. // For some reason this Close call blocks on darwin..
  99. // As the client exists right after, simply discard the close
  100. // until we find a better solution.
  101. if in != nil && runtime.GOOS != "darwin" {
  102. return in.Close()
  103. }
  104. return nil
  105. }