github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/cli/command/system/dial_stdio.go (about) 1 package system 2 3 import ( 4 "context" 5 "io" 6 "os" 7 8 "github.com/khulnasoft/cli/cli" 9 "github.com/khulnasoft/cli/cli/command" 10 "github.com/khulnasoft/cli/cli/command/completion" 11 "github.com/pkg/errors" 12 "github.com/sirupsen/logrus" 13 "github.com/spf13/cobra" 14 ) 15 16 // newDialStdioCommand creates a new cobra.Command for `docker system dial-stdio` 17 func newDialStdioCommand(dockerCli command.Cli) *cobra.Command { 18 cmd := &cobra.Command{ 19 Use: "dial-stdio", 20 Short: "Proxy the stdio stream to the daemon connection. Should not be invoked manually.", 21 Args: cli.NoArgs, 22 Hidden: true, 23 RunE: func(cmd *cobra.Command, args []string) error { 24 return runDialStdio(cmd.Context(), dockerCli) 25 }, 26 ValidArgsFunction: completion.NoComplete, 27 } 28 return cmd 29 } 30 31 func runDialStdio(ctx context.Context, dockerCli command.Cli) error { 32 ctx, cancel := context.WithCancel(ctx) 33 defer cancel() 34 35 dialer := dockerCli.Client().Dialer() 36 conn, err := dialer(ctx) 37 if err != nil { 38 return errors.Wrap(err, "failed to open the raw stream connection") 39 } 40 defer conn.Close() 41 42 var connHalfCloser halfCloser 43 switch t := conn.(type) { 44 case halfCloser: 45 connHalfCloser = t 46 case halfReadWriteCloser: 47 connHalfCloser = &nopCloseReader{t} 48 default: 49 return errors.New("the raw stream connection does not implement halfCloser") 50 } 51 52 stdin2conn := make(chan error, 1) 53 conn2stdout := make(chan error, 1) 54 go func() { 55 stdin2conn <- copier(connHalfCloser, &halfReadCloserWrapper{os.Stdin}, "stdin to stream") 56 }() 57 go func() { 58 conn2stdout <- copier(&halfWriteCloserWrapper{os.Stdout}, connHalfCloser, "stream to stdout") 59 }() 60 select { 61 case err = <-stdin2conn: 62 if err != nil { 63 return err 64 } 65 // wait for stdout 66 err = <-conn2stdout 67 case err = <-conn2stdout: 68 // return immediately without waiting for stdin to be closed. 69 // (stdin is never closed when tty) 70 } 71 return err 72 } 73 74 func copier(to halfWriteCloser, from halfReadCloser, debugDescription string) error { 75 defer func() { 76 if err := from.CloseRead(); err != nil { 77 logrus.Errorf("error while CloseRead (%s): %v", debugDescription, err) 78 } 79 if err := to.CloseWrite(); err != nil { 80 logrus.Errorf("error while CloseWrite (%s): %v", debugDescription, err) 81 } 82 }() 83 if _, err := io.Copy(to, from); err != nil { 84 return errors.Wrapf(err, "error while Copy (%s)", debugDescription) 85 } 86 return nil 87 } 88 89 type halfReadCloser interface { 90 io.Reader 91 CloseRead() error 92 } 93 94 type halfWriteCloser interface { 95 io.Writer 96 CloseWrite() error 97 } 98 99 type halfCloser interface { 100 halfReadCloser 101 halfWriteCloser 102 } 103 104 type halfReadWriteCloser interface { 105 io.Reader 106 halfWriteCloser 107 } 108 109 type nopCloseReader struct { 110 halfReadWriteCloser 111 } 112 113 func (x *nopCloseReader) CloseRead() error { 114 return nil 115 } 116 117 type halfReadCloserWrapper struct { 118 io.ReadCloser 119 } 120 121 func (x *halfReadCloserWrapper) CloseRead() error { 122 return x.Close() 123 } 124 125 type halfWriteCloserWrapper struct { 126 io.WriteCloser 127 } 128 129 func (x *halfWriteCloserWrapper) CloseWrite() error { 130 return x.Close() 131 }