github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/cli/term/file_reader_unix.go (about) 1 //go:build !windows && !plan9 2 // +build !windows,!plan9 3 4 package term 5 6 import ( 7 "io" 8 "os" 9 "sync" 10 "syscall" 11 "time" 12 13 "github.com/markusbkk/elvish/pkg/sys/eunix" 14 ) 15 16 // A helper for reading from a file. 17 type fileReader interface { 18 byteReaderWithTimeout 19 // Stop stops any outstanding read call. It blocks until the read returns. 20 Stop() error 21 // Close releases new resources allocated for the fileReader. It does not 22 // close the underlying file. 23 Close() 24 } 25 26 func newFileReader(file *os.File) (fileReader, error) { 27 rStop, wStop, err := os.Pipe() 28 if err != nil { 29 return nil, err 30 } 31 return &bReader{file: file, rStop: rStop, wStop: wStop}, nil 32 } 33 34 type bReader struct { 35 file *os.File 36 rStop *os.File 37 wStop *os.File 38 // A mutex that is held when Read is in process. 39 mutex sync.Mutex 40 } 41 42 func (r *bReader) ReadByteWithTimeout(timeout time.Duration) (byte, error) { 43 r.mutex.Lock() 44 defer r.mutex.Unlock() 45 for { 46 ready, err := eunix.WaitForRead(timeout, r.file, r.rStop) 47 if err != nil { 48 if err == syscall.EINTR { 49 continue 50 } 51 return 0, err 52 } 53 if ready[1] { 54 var b [1]byte 55 r.rStop.Read(b[:]) 56 return 0, ErrStopped 57 } 58 if !ready[0] { 59 return 0, errTimeout 60 } 61 var b [1]byte 62 nr, err := r.file.Read(b[:]) 63 if err != nil { 64 return 0, err 65 } 66 if nr != 1 { 67 return 0, io.ErrNoProgress 68 } 69 return b[0], nil 70 } 71 } 72 73 func (r *bReader) Stop() error { 74 _, err := r.wStop.Write([]byte{'q'}) 75 r.mutex.Lock() 76 //lint:ignore SA2001 We only lock the mutex to make sure that 77 // ReadByteWithTimeout has exited, so we unlock it immediately. 78 r.mutex.Unlock() 79 return err 80 } 81 82 func (r *bReader) Close() { 83 r.rStop.Close() 84 r.wStop.Close() 85 }