github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/src/internal/traceparser/filebuf/filebuf.go (about)

     1  // Copyright 2018 The Go 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 filebuf implements io.SeekReader for os files.
     6  // This is useful only for very large files with lots of
     7  // seeking. (otherwise use ioutil.ReadFile or bufio)
     8  package filebuf
     9  
    10  import (
    11  	"fmt"
    12  	"io"
    13  	"os"
    14  )
    15  
    16  // Buf is the implemented interface
    17  type Buf interface {
    18  	io.ReadCloser
    19  	io.Seeker
    20  	Size() int64
    21  	Stats() Stat
    22  }
    23  
    24  // Buflen is the size of the internal buffer.
    25  // The code is designed to never need to reread unnecessarily
    26  const Buflen = 1 << 20
    27  
    28  // fbuf is a buffered file with seeking.
    29  // fixed is an internal buffer. buf is the slice fixed[:fixedLen]. bufloc is the file
    30  // location of the beginning of fixed (and buf). The seek pointer is at bufloc+bufpos,
    31  // so the file's contents there start with buf[bufpos:]
    32  type fbuf struct {
    33  	Name     string
    34  	fd       *os.File
    35  	size     int64        // file size
    36  	bufloc   int64        // file loc of beginning of fixed
    37  	bufpos   int32        // seekptr is at bufloc+bufpos. bufpos  <= Buflen, fixedLen
    38  	fixed    [Buflen]byte // backing store for buf
    39  	fixedlen int          // how much of fixed is valid file contents
    40  	buf      []byte       // buf is fixed[0:fixedlen]
    41  	// statistics
    42  	seeks int   // number of calls to fd.Seek
    43  	reads int   // number of calls to fd.Read
    44  	bytes int64 // number of bytes read by fd.Read
    45  }
    46  
    47  // Stat returns the number of underlying seeks and reads, and bytes read
    48  type Stat struct {
    49  	Seeks int
    50  	Reads int
    51  	Bytes int64
    52  }
    53  
    54  // Stats returns the stats so far
    55  func (fb *fbuf) Stats() Stat {
    56  	return Stat{fb.seeks, fb.reads, fb.bytes}
    57  }
    58  
    59  // Size returns the file size
    60  func (fb *fbuf) Size() int64 {
    61  	return fb.size
    62  }
    63  
    64  // New returns an initialized *fbuf or an error
    65  func New(fname string) (Buf, error) {
    66  	fd, err := os.Open(fname)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	fi, err := fd.Stat()
    71  	if err != nil || fi.Mode().IsDir() {
    72  		return nil, fmt.Errorf("not readable: %s", fname)
    73  	}
    74  	return &fbuf{Name: fname, fd: fd, size: fi.Size()}, nil
    75  }
    76  
    77  // Read implements io.Reader. It may return a positive
    78  // number of bytes read with io.EOF
    79  func (fb *fbuf) Read(p []byte) (int, error) {
    80  	// If there are enough valid bytes remaining in buf, just use them
    81  	if len(fb.buf[fb.bufpos:]) >= len(p) {
    82  		copy(p, fb.buf[fb.bufpos:])
    83  		fb.bufpos += int32(len(p))
    84  		return len(p), nil
    85  	}
    86  	done := 0 // done counts how many bytes have been transferred
    87  	// If there are any valid bytes left in buf, use them first
    88  	if len(fb.buf[fb.bufpos:]) > 0 {
    89  		m := copy(p, fb.buf[fb.bufpos:])
    90  		done = m
    91  		fb.bufpos += int32(done) // at end of the valid bytes in buf
    92  	}
    93  	// used up buffered data. logical seek pointer is at bufloc+bufpos.
    94  	// loop until p has been filled up or EOF
    95  	for done < len(p) {
    96  		loc, err := fb.fd.Seek(0, io.SeekCurrent) // make sure of the os's file location
    97  		if loc != fb.bufloc+int64(fb.bufpos) {
    98  			panic(fmt.Sprintf("%v loc=%d bufloc=%d bufpos=%d", err, loc,
    99  				fb.bufloc, fb.bufpos))
   100  		}
   101  		fb.seeks++ // did a file system seek
   102  		if loc >= fb.size {
   103  			// at EOF
   104  			fb.bufpos = int32(len(fb.buf))
   105  			fb.bufloc = loc - int64(fb.fixedlen)
   106  			return done, io.EOF
   107  		}
   108  		n, err := fb.fd.Read(fb.fixed[:])
   109  		if n != 0 {
   110  			fb.fixedlen = n
   111  		}
   112  		fb.reads++ // did a file system read
   113  		m := copy(p[done:], fb.fixed[:n])
   114  		done += m
   115  		if err != nil {
   116  			if err == io.EOF {
   117  				fb.bufpos = int32(len(fb.buf))
   118  				fb.bufloc = loc - int64(fb.fixedlen)
   119  				return done, io.EOF
   120  			}
   121  			return 0, err
   122  		}
   123  		fb.bytes += int64(n)
   124  		fb.bufpos = int32(m) // used m byes of the buffer
   125  		fb.bufloc = loc
   126  		fb.buf = fb.fixed[:n]
   127  	}
   128  	return len(p), nil
   129  }
   130  
   131  // Seek implements io.Seeker. (<unchanged>, io.EOF) is returned for seeks off the end.
   132  func (fb *fbuf) Seek(offset int64, whence int) (int64, error) {
   133  	seekpos := offset
   134  	switch whence {
   135  	case io.SeekCurrent:
   136  		seekpos += fb.bufloc + int64(fb.bufpos)
   137  	case io.SeekEnd:
   138  		seekpos += fb.size
   139  	}
   140  	if seekpos < 0 || seekpos > fb.size {
   141  		return fb.bufloc + int64(fb.bufpos), io.EOF
   142  	}
   143  	// if seekpos is inside fixed, just adjust buf and bufpos
   144  	if seekpos >= fb.bufloc && seekpos <= int64(fb.fixedlen)+fb.bufloc {
   145  		fb.bufpos = int32(seekpos - fb.bufloc)
   146  		return seekpos, nil
   147  	}
   148  	// need to refresh the internal buffer. Seek does no reading, mark buf
   149  	// as empty, set bufpos and bufloc.
   150  	fb.buf, fb.bufpos, fb.bufloc = nil, 0, seekpos
   151  	n, err := fb.fd.Seek(seekpos, io.SeekStart)
   152  	fb.seeks++
   153  	if n != seekpos || err != nil {
   154  		return -1, fmt.Errorf("seek failed (%d!= %d) %v", n, seekpos, err)
   155  	}
   156  	return seekpos, nil
   157  }
   158  
   159  // Close closes the underlying file
   160  func (fb *fbuf) Close() error {
   161  	if fb.fd != nil {
   162  		return fb.fd.Close()
   163  	}
   164  	return nil
   165  }