github.com/jwhonce/docker@v0.6.7-0.20190327063223-da823cf3a5a3/pkg/tailfile/tailfile.go (about) 1 // Package tailfile provides helper functions to read the nth lines of any 2 // ReadSeeker. 3 package tailfile // import "github.com/docker/docker/pkg/tailfile" 4 5 import ( 6 "bufio" 7 "bytes" 8 "context" 9 "errors" 10 "io" 11 "os" 12 ) 13 14 const blockSize = 1024 15 16 var eol = []byte("\n") 17 18 // ErrNonPositiveLinesNumber is an error returned if the lines number was negative. 19 var ErrNonPositiveLinesNumber = errors.New("The number of lines to extract from the file must be positive") 20 21 //TailFile returns last n lines of the passed in file. 22 func TailFile(f *os.File, n int) ([][]byte, error) { 23 size, err := f.Seek(0, io.SeekEnd) 24 if err != nil { 25 return nil, err 26 } 27 28 rAt := io.NewSectionReader(f, 0, size) 29 r, nLines, err := NewTailReader(context.Background(), rAt, n) 30 if err != nil { 31 return nil, err 32 } 33 34 buf := make([][]byte, 0, nLines) 35 scanner := bufio.NewScanner(r) 36 37 for scanner.Scan() { 38 buf = append(buf, scanner.Bytes()) 39 } 40 return buf, nil 41 } 42 43 // SizeReaderAt is an interface used to get a ReaderAt as well as the size of the underlying reader. 44 // Note that the size of the underlying reader should not change when using this interface. 45 type SizeReaderAt interface { 46 io.ReaderAt 47 Size() int64 48 } 49 50 // NewTailReader scopes the passed in reader to just the last N lines passed in 51 func NewTailReader(ctx context.Context, r SizeReaderAt, reqLines int) (io.Reader, int, error) { 52 return NewTailReaderWithDelimiter(ctx, r, reqLines, eol) 53 } 54 55 // NewTailReaderWithDelimiter scopes the passed in reader to just the last N lines passed in 56 // In this case a "line" is defined by the passed in delimiter. 57 // 58 // Delimiter lengths should be generally small, no more than 12 bytes 59 func NewTailReaderWithDelimiter(ctx context.Context, r SizeReaderAt, reqLines int, delimiter []byte) (io.Reader, int, error) { 60 if reqLines < 1 { 61 return nil, 0, ErrNonPositiveLinesNumber 62 } 63 if len(delimiter) == 0 { 64 return nil, 0, errors.New("must provide a delimiter") 65 } 66 var ( 67 size = r.Size() 68 tailStart int64 69 tailEnd = size 70 found int 71 ) 72 73 if int64(len(delimiter)) >= size { 74 return bytes.NewReader(nil), 0, nil 75 } 76 77 scanner := newScanner(r, delimiter) 78 for scanner.Scan(ctx) { 79 if err := scanner.Err(); err != nil { 80 return nil, 0, scanner.Err() 81 } 82 83 found++ 84 if found == 1 { 85 tailEnd = scanner.End() 86 } 87 if found == reqLines { 88 break 89 } 90 } 91 92 tailStart = scanner.Start(ctx) 93 94 if found == 0 { 95 return bytes.NewReader(nil), 0, nil 96 } 97 98 if found < reqLines && tailStart != 0 { 99 tailStart = 0 100 } 101 return io.NewSectionReader(r, tailStart, tailEnd-tailStart), found, nil 102 } 103 104 func newScanner(r SizeReaderAt, delim []byte) *scanner { 105 size := r.Size() 106 readSize := blockSize 107 if readSize > int(size) { 108 readSize = int(size) 109 } 110 // silly case... 111 if len(delim) >= readSize/2 { 112 readSize = len(delim)*2 + 2 113 } 114 115 return &scanner{ 116 r: r, 117 pos: size, 118 buf: make([]byte, readSize), 119 delim: delim, 120 } 121 } 122 123 type scanner struct { 124 r SizeReaderAt 125 pos int64 126 buf []byte 127 delim []byte 128 err error 129 idx int 130 done bool 131 } 132 133 func (s *scanner) Start(ctx context.Context) int64 { 134 if s.idx > 0 { 135 idx := bytes.LastIndex(s.buf[:s.idx], s.delim) 136 if idx >= 0 { 137 return s.pos + int64(idx) + int64(len(s.delim)) 138 } 139 } 140 141 // slow path 142 buf := make([]byte, len(s.buf)) 143 copy(buf, s.buf) 144 145 readAhead := &scanner{ 146 r: s.r, 147 pos: s.pos, 148 delim: s.delim, 149 idx: s.idx, 150 buf: buf, 151 } 152 153 if !readAhead.Scan(ctx) { 154 return 0 155 } 156 return readAhead.End() 157 } 158 159 func (s *scanner) End() int64 { 160 return s.pos + int64(s.idx) + int64(len(s.delim)) 161 } 162 163 func (s *scanner) Err() error { 164 return s.err 165 } 166 167 func (s *scanner) Scan(ctx context.Context) bool { 168 if s.err != nil { 169 return false 170 } 171 172 for { 173 select { 174 case <-ctx.Done(): 175 s.err = ctx.Err() 176 return false 177 default: 178 } 179 180 idx := s.idx - len(s.delim) 181 if idx < 0 { 182 readSize := int(s.pos) 183 if readSize > len(s.buf) { 184 readSize = len(s.buf) 185 } 186 187 if readSize < len(s.delim) { 188 return false 189 } 190 191 offset := s.pos - int64(readSize) 192 n, err := s.r.ReadAt(s.buf[:readSize], offset) 193 if err != nil && err != io.EOF { 194 s.err = err 195 return false 196 } 197 198 s.pos -= int64(n) 199 idx = n 200 } 201 202 s.idx = bytes.LastIndex(s.buf[:idx], s.delim) 203 if s.idx >= 0 { 204 return true 205 } 206 207 if len(s.delim) > 1 && s.pos > 0 { 208 // in this case, there may be a partial delimiter at the front of the buffer, so set the position forward 209 // up to the maximum size partial that could be there so it can be read again in the next iteration with any 210 // potential remainder. 211 // An example where delimiter is `####`: 212 // [##asdfqwerty] 213 // ^ 214 // This resets the position to where the arrow is pointing. 215 // It could actually check if a partial exists and at the front, but that is pretty similar to the indexing 216 // code above though a bit more complex since each byte has to be checked (`len(delimiter)-1`) factorial). 217 // It's much simpler and cleaner to just re-read `len(delimiter)-1` bytes again. 218 s.pos += int64(len(s.delim)) - 1 219 } 220 221 } 222 }