hijack.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  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. type streams interface {
  13. In() *command.InStream
  14. Out() *command.OutStream
  15. }
  16. // holdHijackedConnection handles copying input to and output from streams to the
  17. // connection
  18. func holdHijackedConnection(ctx context.Context, streams streams, tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
  19. var (
  20. err error
  21. restoreOnce sync.Once
  22. )
  23. if inputStream != nil && tty {
  24. if err := setRawTerminal(streams); err != nil {
  25. return err
  26. }
  27. defer func() {
  28. restoreOnce.Do(func() {
  29. restoreTerminal(streams, inputStream)
  30. })
  31. }()
  32. }
  33. receiveStdout := make(chan error, 1)
  34. if outputStream != nil || errorStream != nil {
  35. go func() {
  36. // When TTY is ON, use regular copy
  37. if tty && outputStream != nil {
  38. _, err = io.Copy(outputStream, resp.Reader)
  39. // we should restore the terminal as soon as possible once connection end
  40. // so any following print messages will be in normal type.
  41. if inputStream != nil {
  42. restoreOnce.Do(func() {
  43. restoreTerminal(streams, inputStream)
  44. })
  45. }
  46. } else {
  47. _, err = stdcopy.StdCopy(outputStream, errorStream, resp.Reader)
  48. }
  49. logrus.Debug("[hijack] End of stdout")
  50. receiveStdout <- err
  51. }()
  52. }
  53. stdinDone := make(chan struct{})
  54. go func() {
  55. if inputStream != nil {
  56. io.Copy(resp.Conn, inputStream)
  57. // we should restore the terminal as soon as possible once connection end
  58. // so any following print messages will be in normal type.
  59. if tty {
  60. restoreOnce.Do(func() {
  61. restoreTerminal(streams, inputStream)
  62. })
  63. }
  64. logrus.Debug("[hijack] End of stdin")
  65. }
  66. if err := resp.CloseWrite(); err != nil {
  67. logrus.Debugf("Couldn't send EOF: %s", err)
  68. }
  69. close(stdinDone)
  70. }()
  71. select {
  72. case err := <-receiveStdout:
  73. if err != nil {
  74. logrus.Debugf("Error receiveStdout: %s", err)
  75. return err
  76. }
  77. case <-stdinDone:
  78. if outputStream != nil || errorStream != nil {
  79. select {
  80. case err := <-receiveStdout:
  81. if err != nil {
  82. logrus.Debugf("Error receiveStdout: %s", err)
  83. return err
  84. }
  85. case <-ctx.Done():
  86. }
  87. }
  88. case <-ctx.Done():
  89. }
  90. return nil
  91. }
  92. func setRawTerminal(streams streams) error {
  93. if err := streams.In().SetRawTerminal(); err != nil {
  94. return err
  95. }
  96. return streams.Out().SetRawTerminal()
  97. }
  98. func restoreTerminal(streams streams, in io.Closer) error {
  99. streams.In().RestoreTerminal()
  100. streams.Out().RestoreTerminal()
  101. // WARNING: DO NOT REMOVE THE OS CHECK !!!
  102. // For some reason this Close call blocks on darwin..
  103. // As the client exists right after, simply discard the close
  104. // until we find a better solution.
  105. if in != nil && runtime.GOOS != "darwin" {
  106. return in.Close()
  107. }
  108. return nil
  109. }