github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/varlinkapi/virtwriter/virtwriter.go (about)

     1  package virtwriter
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/binary"
     6  	"encoding/json"
     7  	"io"
     8  	"time"
     9  
    10  	"github.com/pkg/errors"
    11  	"k8s.io/client-go/tools/remotecommand"
    12  )
    13  
    14  // SocketDest is the "key" to where IO should go on the varlink
    15  // multiplexed socket
    16  type SocketDest int
    17  
    18  const (
    19  	// ToStdout indicates traffic should go stdout
    20  	ToStdout SocketDest = iota
    21  	// ToStdin indicates traffic came from stdin
    22  	ToStdin SocketDest = iota
    23  	// ToStderr indicates traffuc should go to stderr
    24  	ToStderr SocketDest = iota
    25  	// TerminalResize indicates a terminal resize event has occurred
    26  	// and data should be passed to resizer
    27  	TerminalResize SocketDest = iota
    28  	// Quit and detach
    29  	Quit SocketDest = iota
    30  	// HangUpFromClient hangs up from the client
    31  	HangUpFromClient SocketDest = iota
    32  )
    33  
    34  // ErrClientHangup signifies that the client wants to drop its connection from
    35  // the server.
    36  var ErrClientHangup = errors.New("client hangup")
    37  
    38  // IntToSocketDest returns a socketdest based on integer input
    39  func IntToSocketDest(i int) SocketDest {
    40  	switch i {
    41  	case ToStdout.Int():
    42  		return ToStdout
    43  	case ToStderr.Int():
    44  		return ToStderr
    45  	case ToStdin.Int():
    46  		return ToStdin
    47  	case TerminalResize.Int():
    48  		return TerminalResize
    49  	case Quit.Int():
    50  		return Quit
    51  	case HangUpFromClient.Int():
    52  		return HangUpFromClient
    53  	default:
    54  		return ToStderr
    55  	}
    56  }
    57  
    58  // Int returns the integer representation of the socket dest
    59  func (sd SocketDest) Int() int {
    60  	return int(sd)
    61  }
    62  
    63  // VirtWriteCloser are writers for attach which include the dest
    64  // of the data
    65  type VirtWriteCloser struct {
    66  	writer *bufio.Writer
    67  	dest   SocketDest
    68  }
    69  
    70  // NewVirtWriteCloser is a constructor
    71  func NewVirtWriteCloser(w *bufio.Writer, dest SocketDest) VirtWriteCloser {
    72  	return VirtWriteCloser{w, dest}
    73  }
    74  
    75  // Close is a required method for a writecloser
    76  func (v VirtWriteCloser) Close() error {
    77  	return v.writer.Flush()
    78  }
    79  
    80  // Write prepends a header to the input message.  The header is
    81  // 8bytes.  Position one contains the destination.  Positions
    82  // 5,6,7,8 are a big-endian encoded uint32 for len of the message.
    83  func (v VirtWriteCloser) Write(input []byte) (int, error) {
    84  	header := []byte{byte(v.dest), 0, 0, 0}
    85  	// Go makes us define the byte for big endian
    86  	mlen := make([]byte, 4)
    87  	binary.BigEndian.PutUint32(mlen, uint32(len(input)))
    88  	// append the message len to the header
    89  	msg := append(header, mlen...)
    90  	// append the message to the header
    91  	msg = append(msg, input...)
    92  	_, err := v.writer.Write(msg)
    93  	if err != nil {
    94  		return 0, err
    95  	}
    96  	err = v.writer.Flush()
    97  	return len(input), err
    98  }
    99  
   100  // Reader decodes the content that comes over the wire and directs it to the proper destination.
   101  func Reader(r *bufio.Reader, output, errput, input io.Writer, resize chan remotecommand.TerminalSize, execEcChan chan int) error {
   102  	var messageSize int64
   103  	headerBytes := make([]byte, 8)
   104  
   105  	if r == nil {
   106  		return errors.Errorf("Reader must not be nil")
   107  	}
   108  	for {
   109  		n, err := io.ReadFull(r, headerBytes)
   110  		if err != nil {
   111  			return errors.Wrapf(err, "Virtual Read failed, %d", n)
   112  		}
   113  		if n < 8 {
   114  			return errors.New("short read and no full header read")
   115  		}
   116  
   117  		messageSize = int64(binary.BigEndian.Uint32(headerBytes[4:8]))
   118  		switch IntToSocketDest(int(headerBytes[0])) {
   119  		case ToStdout:
   120  			if output != nil {
   121  				_, err := io.CopyN(output, r, messageSize)
   122  				if err != nil {
   123  					return err
   124  				}
   125  			}
   126  		case ToStderr:
   127  			if errput != nil {
   128  				_, err := io.CopyN(errput, r, messageSize)
   129  				if err != nil {
   130  					return err
   131  				}
   132  			}
   133  		case ToStdin:
   134  			if input != nil {
   135  				_, err := io.CopyN(input, r, messageSize)
   136  				if err != nil {
   137  					return err
   138  				}
   139  			}
   140  		case TerminalResize:
   141  			if resize != nil {
   142  				out := make([]byte, messageSize)
   143  				if messageSize > 0 {
   144  					_, err = io.ReadFull(r, out)
   145  
   146  					if err != nil {
   147  						return err
   148  					}
   149  				}
   150  				// Resize events come over in bytes, need to be reserialized
   151  				resizeEvent := remotecommand.TerminalSize{}
   152  				if err := json.Unmarshal(out, &resizeEvent); err != nil {
   153  					return err
   154  				}
   155  				resize <- resizeEvent
   156  			}
   157  		case Quit:
   158  			out := make([]byte, messageSize)
   159  			if messageSize > 0 {
   160  				_, err = io.ReadFull(r, out)
   161  
   162  				if err != nil {
   163  					return err
   164  				}
   165  			}
   166  			if execEcChan != nil {
   167  				ecInt := binary.BigEndian.Uint32(out)
   168  				execEcChan <- int(ecInt)
   169  			}
   170  			return nil
   171  		case HangUpFromClient:
   172  			// This sleep allows the pipes to flush themselves before tearing everything down.
   173  			// It makes me sick to do it but after a full day I cannot put my finger on the race
   174  			// that occurs when closing things up.  It would require a significant rewrite of code
   175  			// to make the pipes close down properly.  Given that we are currently discussing a
   176  			// rewrite of all things remote, this hardly seems worth resolving.
   177  			//
   178  			// reproducer: echo hello | (podman-remote run -i alpine cat)
   179  			time.Sleep(1 * time.Second)
   180  			return ErrClientHangup
   181  		default:
   182  			// Something really went wrong
   183  			return errors.New("unknown multiplex destination")
   184  		}
   185  	}
   186  }
   187  
   188  // HangUp sends message to peer to close connection
   189  func HangUp(writer *bufio.Writer, ec uint32) (err error) {
   190  	n := 0
   191  	msg := make([]byte, 4)
   192  
   193  	binary.BigEndian.PutUint32(msg, ec)
   194  
   195  	writeQuit := NewVirtWriteCloser(writer, Quit)
   196  	if n, err = writeQuit.Write(msg); err != nil {
   197  		return
   198  	}
   199  
   200  	if n != len(msg) {
   201  		return errors.Errorf("Failed to send complete %s message", string(msg))
   202  	}
   203  	return
   204  }