github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/port.go (about) 1 package eval 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "sync" 10 11 "github.com/markusbkk/elvish/pkg/eval/errs" 12 "github.com/markusbkk/elvish/pkg/eval/vals" 13 "github.com/markusbkk/elvish/pkg/strutil" 14 ) 15 16 // Port conveys data stream. It always consists of a byte band and a channel band. 17 type Port struct { 18 File *os.File 19 Chan chan interface{} 20 closeFile bool 21 closeChan bool 22 23 // The following two fields are populated as an additional control 24 // mechanism for output ports. When no more value should be send on Chan, 25 // chanSendError is populated and chanSendStop is closed. This is used for 26 // both detection of reader termination (see readerGone below) and closed 27 // ports. 28 sendStop chan struct{} 29 sendError *error 30 31 // Only populated in output ports writing to another command in a pipeline. 32 // When the reading end of the pipe exits, it stores 1 in readerGone. This 33 // is used to check if an external command killed by SIGPIPE is caused by 34 // the termination of the reader of the pipe. 35 readerGone *int32 36 } 37 38 // ErrNoValueOutput is thrown when writing to a pipe without a value output 39 // component. 40 var ErrNoValueOutput = errors.New("port has no value output") 41 42 // A closed channel, suitable as a value for Port.sendStop when there is no 43 // reader to start with. 44 var closedSendStop = make(chan struct{}) 45 46 func init() { close(closedSendStop) } 47 48 // Returns a copy of the Port with the Close* flags unset. 49 func (p *Port) fork() *Port { 50 return &Port{p.File, p.Chan, false, false, p.sendStop, p.sendError, p.readerGone} 51 } 52 53 // Closes a Port. 54 func (p *Port) close() { 55 if p == nil { 56 return 57 } 58 if p.closeFile { 59 p.File.Close() 60 } 61 if p.closeChan { 62 close(p.Chan) 63 } 64 } 65 66 var ( 67 // ClosedChan is a closed channel, suitable as a placeholder input channel. 68 ClosedChan = getClosedChan() 69 // BlackholeChan is a channel that absorbs all values written to it, 70 // suitable as a placeholder output channel. 71 BlackholeChan = getBlackholeChan() 72 // DevNull is /dev/null, suitable as a placeholder file for either input or 73 // output. 74 DevNull = getDevNull() 75 76 // DummyInputPort is a port made up from DevNull and ClosedChan, suitable as 77 // a placeholder input port. 78 DummyInputPort = &Port{File: DevNull, Chan: ClosedChan} 79 // DummyOutputPort is a port made up from DevNull and BlackholeChan, 80 // suitable as a placeholder output port. 81 DummyOutputPort = &Port{File: DevNull, Chan: BlackholeChan} 82 83 // DummyPorts contains 3 dummy ports, suitable as stdin, stdout and stderr. 84 DummyPorts = []*Port{DummyInputPort, DummyOutputPort, DummyOutputPort} 85 ) 86 87 func getClosedChan() chan interface{} { 88 ch := make(chan interface{}) 89 close(ch) 90 return ch 91 } 92 93 func getBlackholeChan() chan interface{} { 94 ch := make(chan interface{}) 95 go func() { 96 for range ch { 97 } 98 }() 99 return ch 100 } 101 102 func getDevNull() *os.File { 103 f, err := os.Open(os.DevNull) 104 if err != nil { 105 fmt.Fprintf(os.Stderr, 106 "cannot open %s, shell might not function normally\n", os.DevNull) 107 } 108 return f 109 } 110 111 // PipePort returns an output *Port whose value and byte components are both 112 // piped. The supplied functions are called on a separate goroutine with the 113 // read ends of the value and byte components of the port. It also returns a 114 // function to clean up the port and wait for the callbacks to finish. 115 func PipePort(vCb func(<-chan interface{}), bCb func(*os.File)) (*Port, func(), error) { 116 r, w, err := os.Pipe() 117 if err != nil { 118 return nil, nil, err 119 } 120 ch := make(chan interface{}, outputCaptureBufferSize) 121 122 var wg sync.WaitGroup 123 wg.Add(2) 124 go func() { 125 defer wg.Done() 126 vCb(ch) 127 }() 128 go func() { 129 defer wg.Done() 130 defer r.Close() 131 bCb(r) 132 }() 133 134 port := &Port{Chan: ch, closeChan: true, File: w, closeFile: true} 135 done := func() { 136 port.close() 137 wg.Wait() 138 } 139 return port, done, nil 140 } 141 142 // CapturePort returns an output *Port whose value and byte components are 143 // both connected to an internal pipe that saves the output. It also returns a 144 // function to call to obtain the captured output. 145 func CapturePort() (*Port, func() []interface{}, error) { 146 vs := []interface{}{} 147 var m sync.Mutex 148 port, done, err := PipePort( 149 func(ch <-chan interface{}) { 150 for v := range ch { 151 m.Lock() 152 vs = append(vs, v) 153 m.Unlock() 154 } 155 }, 156 func(r *os.File) { 157 buffered := bufio.NewReader(r) 158 for { 159 line, err := buffered.ReadString('\n') 160 if line != "" { 161 v := strutil.ChopLineEnding(line) 162 m.Lock() 163 vs = append(vs, v) 164 m.Unlock() 165 } 166 if err != nil { 167 if err != io.EOF { 168 logger.Println("error on reading:", err) 169 } 170 break 171 } 172 } 173 }) 174 if err != nil { 175 return nil, nil, err 176 } 177 return port, func() []interface{} { 178 done() 179 return vs 180 }, nil 181 } 182 183 // StringCapturePort is like CapturePort, but processes value outputs by 184 // stringifying them and prepending an output marker. 185 func StringCapturePort() (*Port, func() []string, error) { 186 var lines []string 187 var mu sync.Mutex 188 addLine := func(line string) { 189 mu.Lock() 190 defer mu.Unlock() 191 lines = append(lines, line) 192 } 193 port, done, err := PipePort( 194 func(ch <-chan interface{}) { 195 for v := range ch { 196 addLine("▶ " + vals.ToString(v)) 197 } 198 }, 199 func(r *os.File) { 200 bufr := bufio.NewReader(r) 201 for { 202 line, err := bufr.ReadString('\n') 203 if err != nil { 204 if err != io.EOF { 205 addLine("i/o error: " + err.Error()) 206 } 207 break 208 } 209 addLine(strutil.ChopLineEnding(line)) 210 } 211 }) 212 if err != nil { 213 return nil, nil, err 214 } 215 return port, func() []string { 216 done() 217 return lines 218 }, nil 219 } 220 221 // Buffer size for the channel to use in FilePort. The value has been chosen 222 // arbitrarily. 223 const filePortChanSize = 32 224 225 // FilePort returns an output *Port where the byte component is the file itself, 226 // and the value component is converted to an internal channel that writes 227 // each value to the file, prepending with a prefix. It also returns a cleanup 228 // function, which should be called when the *Port is no longer needed. 229 func FilePort(f *os.File, valuePrefix string) (*Port, func()) { 230 ch := make(chan interface{}, filePortChanSize) 231 relayDone := make(chan struct{}) 232 go func() { 233 for v := range ch { 234 f.WriteString(valuePrefix) 235 f.WriteString(vals.ReprPlain(v)) 236 f.WriteString("\n") 237 } 238 close(relayDone) 239 }() 240 return &Port{File: f, Chan: ch}, func() { 241 close(ch) 242 <-relayDone 243 } 244 } 245 246 // PortsFromStdFiles is a shorthand for calling PortsFromFiles with os.Stdin, 247 // os.Stdout and os.Stderr. 248 func PortsFromStdFiles(prefix string) ([]*Port, func()) { 249 return PortsFromFiles([3]*os.File{os.Stdin, os.Stdout, os.Stderr}, prefix) 250 } 251 252 // PortsFromFiles builds 3 ports from 3 files. It also returns a function that 253 // should be called when the ports are no longer needed. 254 func PortsFromFiles(files [3]*os.File, prefix string) ([]*Port, func()) { 255 port1, cleanup1 := FilePort(files[1], prefix) 256 port2, cleanup2 := FilePort(files[2], prefix) 257 return []*Port{{File: files[0], Chan: ClosedChan}, port1, port2}, func() { 258 cleanup1() 259 cleanup2() 260 } 261 } 262 263 // ValueOutput defines the interface through which builtin commands access the 264 // value output. 265 // 266 // The value output is backed by two channels, one for writing output, another 267 // for the back-chanel signal that the reader of the channel has gone. 268 type ValueOutput interface { 269 // Outputs a value. Returns errs.ReaderGone if the reader is gone. 270 Put(v interface{}) error 271 } 272 273 type valueOutput struct { 274 data chan<- interface{} 275 sendStop <-chan struct{} 276 sendError *error 277 } 278 279 func (vo valueOutput) Put(v interface{}) error { 280 select { 281 case vo.data <- v: 282 return nil 283 case <-vo.sendStop: 284 return *vo.sendError 285 } 286 } 287 288 // ByteOutput defines the interface through which builtin commands access the 289 // byte output. 290 // 291 // It is a thin wrapper around the underlying *os.File value, only exposing 292 // the necessary methods for writing bytes and strings, and converting any 293 // syscall.EPIPE errors to errs.ReaderGone. 294 type ByteOutput interface { 295 io.Writer 296 io.StringWriter 297 } 298 299 type byteOutput struct { 300 f *os.File 301 } 302 303 func (bo byteOutput) Write(p []byte) (int, error) { 304 n, err := bo.f.Write(p) 305 return n, convertReaderGone(err) 306 } 307 308 func (bo byteOutput) WriteString(s string) (int, error) { 309 n, err := bo.f.WriteString(s) 310 return n, convertReaderGone(err) 311 } 312 313 func convertReaderGone(err error) error { 314 if pathErr, ok := err.(*os.PathError); ok { 315 if pathErr.Err == epipe { 316 return errs.ReaderGone{} 317 } 318 } 319 return err 320 }