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