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 }