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  }