github.com/olljanat/moby@v1.13.1/cli/command/container/hijack.go (about)

     1  package container
     2  
     3  import (
     4  	"io"
     5  	"runtime"
     6  	"sync"
     7  
     8  	"github.com/Sirupsen/logrus"
     9  	"github.com/docker/docker/api/types"
    10  	"github.com/docker/docker/cli/command"
    11  	"github.com/docker/docker/pkg/stdcopy"
    12  	"golang.org/x/net/context"
    13  )
    14  
    15  // holdHijackedConnection handles copying input to and output from streams to the
    16  // connection
    17  func holdHijackedConnection(ctx context.Context, streams command.Streams, tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
    18  	var (
    19  		err         error
    20  		restoreOnce sync.Once
    21  	)
    22  	if inputStream != nil && tty {
    23  		if err := setRawTerminal(streams); err != nil {
    24  			return err
    25  		}
    26  		defer func() {
    27  			restoreOnce.Do(func() {
    28  				restoreTerminal(streams, inputStream)
    29  			})
    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  
    50  			logrus.Debug("[hijack] End of stdout")
    51  			receiveStdout <- err
    52  		}()
    53  	}
    54  
    55  	stdinDone := make(chan struct{})
    56  	go func() {
    57  		if inputStream != nil {
    58  			io.Copy(resp.Conn, inputStream)
    59  			// we should restore the terminal as soon as possible once connection end
    60  			// so any following print messages will be in normal type.
    61  			if tty {
    62  				restoreOnce.Do(func() {
    63  					restoreTerminal(streams, inputStream)
    64  				})
    65  			}
    66  			logrus.Debug("[hijack] End of stdin")
    67  		}
    68  
    69  		if err := resp.CloseWrite(); err != nil {
    70  			logrus.Debugf("Couldn't send EOF: %s", err)
    71  		}
    72  		close(stdinDone)
    73  	}()
    74  
    75  	select {
    76  	case err := <-receiveStdout:
    77  		if err != nil {
    78  			logrus.Debugf("Error receiveStdout: %s", err)
    79  			return err
    80  		}
    81  	case <-stdinDone:
    82  		if outputStream != nil || errorStream != nil {
    83  			select {
    84  			case err := <-receiveStdout:
    85  				if err != nil {
    86  					logrus.Debugf("Error receiveStdout: %s", err)
    87  					return err
    88  				}
    89  			case <-ctx.Done():
    90  			}
    91  		}
    92  	case <-ctx.Done():
    93  	}
    94  
    95  	return nil
    96  }
    97  
    98  func setRawTerminal(streams command.Streams) error {
    99  	if err := streams.In().SetRawTerminal(); err != nil {
   100  		return err
   101  	}
   102  	return streams.Out().SetRawTerminal()
   103  }
   104  
   105  func restoreTerminal(streams command.Streams, in io.Closer) error {
   106  	streams.In().RestoreTerminal()
   107  	streams.Out().RestoreTerminal()
   108  	// WARNING: DO NOT REMOVE THE OS CHECK !!!
   109  	// For some reason this Close call blocks on darwin..
   110  	// As the client exists right after, simply discard the close
   111  	// until we find a better solution.
   112  	if in != nil && runtime.GOOS != "darwin" {
   113  		return in.Close()
   114  	}
   115  	return nil
   116  }