github.com/oweisse/u-root@v0.0.0-20181109060735-d005ad25fef1/pkg/lineio/lineio.go (about) 1 // Copyright 2018 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package lineio 6 7 import ( 8 "io" 9 "regexp" 10 11 "github.com/u-root/u-root/pkg/sortedmap" 12 ) 13 14 type LineReader struct { 15 src io.ReaderAt 16 17 // offsetCache remembers the offset of various lines in src. 18 // At minimum, line 1 must be prepopulated. 19 offsetCache sortedmap.Map 20 } 21 22 // scanForLine reads from curOffset (which is on curLine), looking for line, 23 // returning the offset of line. If err == io.EOF, offset is the offset of 24 // the last valid byte. 25 func (l *LineReader) scanForLine(line, curLine, curOffset int64) (offset int64, err error) { 26 lastGoodOffset := int64(-1) 27 28 for { 29 buf := make([]byte, 128) 30 31 n, err := l.src.ReadAt(buf, curOffset) 32 // Keep looking as long as *something* is returned 33 if n == 0 && err != nil { 34 // In the event of EOF, callers want to know the last 35 // byte read, to find the last byte in the last line. 36 return lastGoodOffset, err 37 } 38 39 buf = buf[:n] 40 41 for i, b := range buf { 42 if b != '\n' { 43 continue 44 } 45 46 offset := curOffset + int64(i) + 1 47 48 // We haven't read this offset yet; it may not exist. 49 // Read-ahead by a byte to double-check this offset 50 // exists before adding a new line. 51 // This is a common case for files ending in a newline. 52 if offset >= curOffset+int64(len(buf)) { 53 t := make([]byte, 1) 54 _, err = l.src.ReadAt(t, offset) 55 if err != nil { 56 continue 57 } 58 } 59 60 curLine++ 61 62 l.offsetCache.Insert(curLine, offset) 63 64 if curLine == line { 65 return offset, nil 66 } 67 } 68 69 curOffset += int64(len(buf)) 70 // The last byte in the buffer must have been good if we read it. 71 lastGoodOffset = curOffset - 1 72 } 73 } 74 75 // Populate scans the file, populating the offsetCache, so that future 76 // lookups will be faster. 77 func (l *LineReader) Populate() { 78 // Scan from the start of the file to the maximum possible line, 79 // populating the offsetCache along the way. 80 l.scanForLine(int64(0x7fffffffffffffff), 1, 0) 81 } 82 83 // findLine returns the offset of start of line. 84 func (l *LineReader) findLine(line int64) (offset int64, err error) { 85 nearest, offset, err := l.offsetCache.NearestLessEqual(line) 86 if err != nil { 87 return 0, err 88 } 89 90 // Is this the line we want? 91 if nearest == line { 92 return offset, nil 93 } 94 95 return l.scanForLine(line, nearest, offset) 96 } 97 98 // findLineRange returns the offset of the first and last bytes in line. 99 func (l *LineReader) findLineRange(line int64) (start, end int64, err error) { 100 start, err = l.findLine(line) 101 if err != nil { 102 return 0, 0, err 103 } 104 105 end, err = l.findLine(line + 1) 106 // EOF means there is no next line. End is the last byte in the file, 107 // if it is positive. 108 if err == io.EOF && end >= 0 { 109 return start, end, nil 110 } else if err != nil { 111 return 0, 0, err 112 } 113 114 // The caller expects end to be the last character in the line, 115 // but findLine returns the start of the next line. Subtract 116 // first character in next line and newline at end of previous line. 117 end -= 2 118 119 return start, end, nil 120 } 121 122 // LineExists returns true if the given line is in the file. 123 func (l *LineReader) LineExists(line int64) bool { 124 _, err := l.findLine(line) 125 return err == nil 126 } 127 128 // ReadLine reads up to len(p) bytes from line number line from the source. 129 // It returns the numbers of bytes written and any error encountered. 130 // If n < len(p), err is set to a non-nil value explaining why. 131 // See io.ReaderAt for full description of return values. 132 func (l *LineReader) ReadLine(p []byte, line int64) (n int, err error) { 133 start, end, err := l.findLineRange(line) 134 if err != nil { 135 return 0, err 136 } 137 138 var shrunk bool 139 // Only read one line worth of data. 140 size := end - start + 1 141 if size < int64(len(p)) { 142 p = p[:size] 143 shrunk = true 144 } 145 146 n, err = l.src.ReadAt(p, start) 147 // We used less than len(p), we must return EOF. 148 if err == nil && shrunk { 149 err = io.EOF 150 } 151 152 return n, err 153 } 154 155 // SearchLine runs Regexp.FindAllIndex on the given line, providing the same 156 // return value. 157 func (l *LineReader) SearchLine(r *regexp.Regexp, line int64) ([][]int, error) { 158 start, end, err := l.findLineRange(line) 159 if err != nil { 160 return nil, err 161 } 162 163 size := end - start + 1 164 buf := make([]byte, size) 165 166 _, err = l.src.ReadAt(buf, start) 167 // TODO(prattmic): support partial reads 168 if err != nil { 169 return nil, err 170 } 171 172 return r.FindAllIndex(buf, -1), nil 173 } 174 175 func NewLineReader(src io.ReaderAt) LineReader { 176 l := LineReader{ 177 src: src, 178 offsetCache: sortedmap.NewMap(), 179 } 180 181 // Line 1 starts at the beginning of the file! 182 l.offsetCache.Insert(1, 0) 183 184 return l 185 }