github.com/scottcagno/storage@v1.8.0/pkg/_junk/bfile/bfile.go (about)

     1  package bfile
     2  
     3  import (
     4  	"encoding/binary"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"math"
     9  	"os"
    10  	"path/filepath"
    11  )
    12  
    13  const (
    14  	sectorSize = 512            // 512 B
    15  	blockSize  = 8 * sectorSize //   4 KB
    16  	chunkSize  = 8 * blockSize  //  32 KB
    17  	extentSize = 8 * chunkSize  // 256 KB
    18  
    19  	headerSize         = 12
    20  	recordMaxSize      = blockSize * math.MaxUint16
    21  	asciiUnitSeparator = 0x1F
    22  	statusFree         = uint16(asciiUnitSeparator >> 0 << 10)
    23  	statusActive       = uint16(asciiUnitSeparator >> 1 << 10)
    24  	statusDeleted      = uint16(asciiUnitSeparator >> 2 << 10)
    25  )
    26  
    27  var (
    28  	ErrBadOffsetAlignment = errors.New("bad offset; not correctly aligned")
    29  	ErrOffsetOutOfBounds  = errors.New("bad offset; out of bounds")
    30  	ErrDataTooLarge       = errors.New("data exceeds max record size")
    31  	ErrFileClosed         = errors.New("file is closed")
    32  	ErrScannerSkip        = errors.New("skip to next place with scanner/iterator")
    33  )
    34  
    35  // align block aligns the provided size
    36  func align(size int64) int64 {
    37  	if size > 0 {
    38  		return ((size + 2) + blockSize - 1) &^ (blockSize - 1)
    39  	}
    40  	return blockSize
    41  }
    42  
    43  // at takes a block position and converts it to a block offset
    44  func at(n int) int64 {
    45  	return int64(n) * blockSize
    46  }
    47  
    48  // getOffset returns a block aligned offset for the provided position
    49  func getOffset(pos int, max int64) (int64, error) {
    50  	// calculate the
    51  	offset := int64(pos * blockSize)
    52  	// return error if offset is not block aligned
    53  	if offset%blockSize != 0 {
    54  		return -1, ErrBadOffsetAlignment
    55  	}
    56  	// return error if offset is larger than max
    57  	if offset > max {
    58  		return -1, ErrOffsetOutOfBounds
    59  	}
    60  	// otherwise, return offset
    61  	return offset, nil
    62  }
    63  
    64  /*
    65   * To save space, the header could always be adjusted as such:
    66   *
    67   * // header size would be reduced in half (12 down to 6 bytes)
    68   * type header struct {
    69   *     status  uint8 	//
    70   *     blocks  uint8 	// max block count of 255
    71   *     length  uint16 	// max length (aka record size) of 65535 bytes
    72   *     padding uint16	// max padding of 65535 bytes (also, padding could be removed)
    73   * }
    74   *
    75   */
    76  
    77  // header represents a record header
    78  type header struct {
    79  	status  uint16 // status of the record
    80  	blocks  uint16 // blocks used by the record
    81  	length  uint32 // length of the record data
    82  	padding uint32 // padding is the extra unused bytes in the block
    83  }
    84  
    85  // readHeader reads and fills out a header from the provided io.Reader
    86  func (hdr *header) readHeader(r io.Reader) (int, error) {
    87  	// make buffer for record header
    88  	buf := make([]byte, headerSize)
    89  	// read in entire record header
    90  	n, err := r.Read(buf)
    91  	if err != nil {
    92  		return n, err
    93  	}
    94  	// decode status
    95  	hdr.status = binary.LittleEndian.Uint16(buf[0:2])
    96  	// decode blocks
    97  	hdr.blocks = binary.LittleEndian.Uint16(buf[2:4])
    98  	// decode length
    99  	hdr.length = binary.LittleEndian.Uint32(buf[4:8])
   100  	// decode padding
   101  	hdr.padding = binary.LittleEndian.Uint32(buf[8:12])
   102  	// return bytes read and nil
   103  	return n, nil
   104  }
   105  
   106  // readHeaderAt reads and fills out a header from the provided io.ReaderAt
   107  func (hdr *header) readHeaderAt(r io.ReaderAt, off int64) (int, error) {
   108  	// make buffer for record header
   109  	buf := make([]byte, headerSize)
   110  	// read in entire record header
   111  	n, err := r.ReadAt(buf, off)
   112  	if err != nil {
   113  		return n, err
   114  	}
   115  	// decode status
   116  	hdr.status = binary.LittleEndian.Uint16(buf[0:2])
   117  	// decode blocks
   118  	hdr.blocks = binary.LittleEndian.Uint16(buf[2:4])
   119  	// decode length
   120  	hdr.length = binary.LittleEndian.Uint32(buf[4:8])
   121  	// decode padding
   122  	hdr.padding = binary.LittleEndian.Uint32(buf[8:12])
   123  	// return bytes read and nil
   124  	return n, nil
   125  }
   126  
   127  // writeHeader writes a header to the provided io.Writer
   128  func (hdr *header) writeHeader(w io.Writer) (int, error) {
   129  	// make buffer to encode record header into
   130  	buf := make([]byte, headerSize)
   131  	// encode status
   132  	binary.LittleEndian.PutUint16(buf[0:2], hdr.status)
   133  	// encode blocks
   134  	binary.LittleEndian.PutUint16(buf[2:4], hdr.blocks)
   135  	// encode length
   136  	binary.LittleEndian.PutUint32(buf[4:8], hdr.length)
   137  	// encode padding
   138  	binary.LittleEndian.PutUint32(buf[8:12], hdr.padding)
   139  	// write record header
   140  	return w.Write(buf)
   141  }
   142  
   143  // writeHeaderAt writes a header to the provided io.WriterAt
   144  func (hdr *header) writeHeaderAt(w io.WriterAt, offset int64) (int, error) {
   145  	// make buffer to encode record header into
   146  	buf := make([]byte, headerSize)
   147  	// encode status
   148  	binary.LittleEndian.PutUint16(buf[0:2], hdr.status)
   149  	// encode blocks
   150  	binary.LittleEndian.PutUint16(buf[2:4], hdr.blocks)
   151  	// encode length
   152  	binary.LittleEndian.PutUint32(buf[4:8], hdr.length)
   153  	// encode padding
   154  	binary.LittleEndian.PutUint32(buf[8:12], hdr.padding)
   155  	// write record header at
   156  	return w.WriteAt(buf, offset)
   157  }
   158  
   159  // record represents a data record on disk
   160  type record struct {
   161  	*header        // record header
   162  	data    []byte // record data
   163  }
   164  
   165  // makeRecord creates and returns a new record with the provided data
   166  func makeRecord(d []byte) (*record, error) {
   167  	// calc "overhead"
   168  	overhead := headerSize + int64(len(d))
   169  	// get aligned size
   170  	size := align(overhead)
   171  	// error check
   172  	if size > recordMaxSize {
   173  		return nil, ErrDataTooLarge
   174  	}
   175  	// create record
   176  	rd := &record{
   177  		// create and fill record header
   178  		header: &header{
   179  			status:  statusActive,
   180  			blocks:  uint16(size / blockSize),
   181  			length:  uint32(len(d)),
   182  			padding: uint32(size - overhead),
   183  		},
   184  		data: d,
   185  	}
   186  	// return record
   187  	return rd, nil
   188  }
   189  
   190  // String is a stringer method for a record
   191  func (rec *record) String() string {
   192  	s := fmt.Sprintf("record:\n")
   193  	s += fmt.Sprintf("\theader:\n")
   194  	s += fmt.Sprintf("\t\tstatus: %d\n", rec.header.status)
   195  	s += fmt.Sprintf("\t\tblocks: %d\n", rec.header.blocks)
   196  	s += fmt.Sprintf("\t\tlength: %d\n", rec.header.length)
   197  	s += fmt.Sprintf("\t\tpadding: %d\n", rec.header.padding)
   198  	s += fmt.Sprintf("\tdata: %s\n", rec.data)
   199  	return s
   200  }
   201  
   202  // readRecord reads and fills out a record from the provided io.Reader
   203  func (rec *record) readRecord(r io.Reader) error {
   204  	// create record header
   205  	hdr := new(header)
   206  	// read the record header
   207  	_, err := hdr.readHeader(r)
   208  	if err != nil {
   209  		return err
   210  	}
   211  	// fill out the record and allocate space to read in data
   212  	rec.header = hdr
   213  	rec.data = make([]byte, hdr.length)
   214  	// read data into the record
   215  	_, err = r.Read(rec.data)
   216  	if err != nil {
   217  		return err
   218  	}
   219  	return nil
   220  }
   221  
   222  // readRecordAt reads and fills out a record from the provided io.ReaderAt
   223  func (rec *record) readRecordAt(r io.ReaderAt, offset int64) error {
   224  	// create record header
   225  	hdr := new(header)
   226  	// read the record header
   227  	n, err := hdr.readHeaderAt(r, offset)
   228  	if err != nil {
   229  		return err
   230  	}
   231  	// fill out the record and allocate space to read in data
   232  	rec.header = hdr
   233  	rec.data = make([]byte, hdr.length)
   234  	// read data into the record
   235  	_, err = r.ReadAt(rec.data, offset+int64(n))
   236  	if err != nil {
   237  		return err
   238  	}
   239  	return nil
   240  }
   241  
   242  // writeRecord writes a record to the provided io.Writer
   243  func (rec *record) writeRecord(w io.Writer) error {
   244  	// write the record header
   245  	_, err := rec.header.writeHeader(w)
   246  	if err != nil {
   247  		return err
   248  	}
   249  	// write the record data
   250  	_, err = w.Write(rec.data)
   251  	if err != nil {
   252  		return err
   253  	}
   254  	// return nil error
   255  	return nil
   256  }
   257  
   258  // writeRecordAt writes a record to the provided io.WriterAt
   259  func (rec *record) writeRecordAt(w io.WriterAt, offset int64) error {
   260  	// write the record header
   261  	n, err := rec.header.writeHeaderAt(w, offset)
   262  	if err != nil {
   263  		return err
   264  	}
   265  	// write the record data
   266  	_, err = w.WriteAt(rec.data, offset+int64(n))
   267  	if err != nil {
   268  		return err
   269  	}
   270  	// return nil error
   271  	return nil
   272  }
   273  
   274  // bfile represents a block file
   275  type bfile struct {
   276  	path  string  // path is the path in which the file pointer is pointing to
   277  	size  int64   // size reports the current file size
   278  	open  bool    // open reports true if the file is open
   279  	index []int64 // index is the record offset index
   280  	fp    *os.File
   281  }
   282  
   283  // openBFile opens and returns a new or existing bfile
   284  func openBFile(name string) (*bfile, error) {
   285  	// sanitize base path
   286  	path, err := filepath.Abs(name)
   287  	if err != nil {
   288  		return nil, err
   289  	}
   290  	// sanitize any path separators
   291  	path = filepath.ToSlash(path)
   292  	// get dir
   293  	dir, _ := filepath.Split(path)
   294  	// create any directories if they are not there
   295  	err = os.MkdirAll(dir, os.ModeDir)
   296  	if err != nil {
   297  		return nil, err
   298  	}
   299  	// open file
   300  	f, err := os.OpenFile(path, os.O_CREATE, 0666)
   301  	if err != nil {
   302  		return nil, err
   303  	}
   304  	// get file size
   305  	fi, err := f.Stat()
   306  	if err != nil {
   307  		return nil, err
   308  	}
   309  	// setup new blockFile
   310  	bf := &bfile{
   311  		path:  path,
   312  		size:  fi.Size(),
   313  		open:  true,
   314  		index: make([]int64, 0),
   315  		fp:    f,
   316  	}
   317  	// call init
   318  	err = bf.init()
   319  	if err != nil {
   320  		return nil, err
   321  	}
   322  	// return blockFile
   323  	return bf, nil
   324  }
   325  
   326  // init initializes the bfile
   327  func (bf *bfile) init() error {
   328  	// init offset
   329  	var offset int64
   330  	// iterate and fill out
   331  	for {
   332  		// create record header
   333  		hdr := new(header)
   334  		// read the record header
   335  		n, err := hdr.readHeaderAt(bf.fp, offset)
   336  		if err != nil {
   337  			if err == io.EOF {
   338  				break
   339  			}
   340  			return err
   341  		}
   342  		// add record offset to index
   343  		bf.index = append(bf.index, offset)
   344  		// increment offset
   345  		offset += int64(uint32(n) + hdr.length + hdr.padding)
   346  		// keep on iterating till we reach the end of the file
   347  	}
   348  	// no errors to return
   349  	return nil
   350  }
   351  
   352  // check performs basic error checking before a call to read, readAt, write or writeAt
   353  func (bf *bfile) check(off int64) error {
   354  	// check offset alignment
   355  	if off != -1 && off%blockSize != 0 {
   356  		return ErrBadOffsetAlignment
   357  	}
   358  	// check offset bounds
   359  	if off != -1 && off > bf.size {
   360  		return ErrOffsetOutOfBounds
   361  	}
   362  	// check to make sure file is open
   363  	if !bf.open {
   364  		return ErrFileClosed
   365  	}
   366  	return nil
   367  }
   368  
   369  // read attempts to read and return the data from the next record in the file.
   370  // After a successful call to read, the file pointer is advanced ahead to the
   371  // offset of the start of the next record. It returns an error if the call fails
   372  // for any reason.
   373  func (bf *bfile) read() ([]byte, error) {
   374  	// error check
   375  	err := bf.check(-1)
   376  	if err != nil {
   377  		return nil, err
   378  	}
   379  	// allocate new record to read into
   380  	rec := new(record)
   381  	// read record
   382  	err = rec.readRecord(bf.fp)
   383  	if err != nil {
   384  		return nil, err
   385  	}
   386  	// skip to the next record offset
   387  	_, err = bf.fp.Seek(int64(rec.padding), io.SeekCurrent)
   388  	if err != nil {
   389  		return nil, err
   390  	}
   391  	// return record data
   392  	return rec.data, nil
   393  }
   394  
   395  // readRaw attempts to read and return the actual record located in the file.
   396  // After a successful call to read, the file pointer is advanced ahead to the
   397  // offset of the start of the next record. It returns an error if the call fails
   398  // for any reason.
   399  func (bf *bfile) readRaw() (*record, error) {
   400  	// error check
   401  	err := bf.check(-1)
   402  	if err != nil {
   403  		return nil, err
   404  	}
   405  	// allocate new record to read into
   406  	rec := new(record)
   407  	// read record
   408  	err = rec.readRecord(bf.fp)
   409  	if err != nil {
   410  		return nil, err
   411  	}
   412  	// skip to the next record offset
   413  	_, err = bf.fp.Seek(int64(rec.padding), io.SeekCurrent)
   414  	if err != nil {
   415  		return nil, err
   416  	}
   417  	// return record data
   418  	return rec, nil
   419  }
   420  
   421  // readAt attempts to read and return the data from the next record in the file
   422  // located at the provided offset. The file pointer is not advanced in a readAt
   423  // call. It returns an error if the call fails for any reason.
   424  func (bf *bfile) readAt(off int64) ([]byte, error) {
   425  	// error check
   426  	err := bf.check(off)
   427  	if err != nil {
   428  		return nil, err
   429  	}
   430  	// allocate new record to read into
   431  	rec := new(record)
   432  	// read record
   433  	err = rec.readRecordAt(bf.fp, off)
   434  	if err != nil {
   435  		return nil, err
   436  	}
   437  	// return record data
   438  	return rec.data, nil
   439  }
   440  
   441  // readAtIndex attempts to read and return the data from the next record in the file
   442  // located at the provided index key. The file pointer is not advanced in a readAt
   443  // call. It returns an error if the call fails for any reason.
   444  func (bf *bfile) readAtIndex(i int) ([]byte, error) {
   445  	// get offset from index
   446  	off := bf.index[i]
   447  	// error check
   448  	err := bf.check(off)
   449  	if err != nil {
   450  		return nil, err
   451  	}
   452  	// allocate new record to read into
   453  	rec := new(record)
   454  	// read record
   455  	err = rec.readRecordAt(bf.fp, off)
   456  	if err != nil {
   457  		return nil, err
   458  	}
   459  	// return record data
   460  	return rec.data, nil
   461  }
   462  
   463  // write takes the provided data and creates a new record. It then attempts to
   464  // write the record to disk. After a successful write the file pointer is advanced
   465  // ahead to the offset of the start of the next record; it will also return the
   466  // offset in the underlying file where the beginning of the record was written.
   467  // It returns an error if the call fails for any reason.
   468  func (bf *bfile) write(data []byte) (int64, error) {
   469  	// error check
   470  	err := bf.check(-1)
   471  	if err != nil {
   472  		return -1, err
   473  	}
   474  	// create record from provided data
   475  	rec, err := makeRecord(data)
   476  	if err != nil {
   477  		return -1, err
   478  	}
   479  	// get offset to return later
   480  	off, err := bf.fp.Seek(0, io.SeekCurrent)
   481  	if err != nil {
   482  		return -1, err
   483  	}
   484  	// write the record to disk
   485  	err = rec.writeRecord(bf.fp)
   486  	if err != nil {
   487  		return -1, err
   488  	}
   489  	// advance the file pointer to the next alignment offset
   490  	_, err = bf.fp.Seek(int64(rec.padding), io.SeekCurrent)
   491  	if err != nil {
   492  		return -1, err
   493  	}
   494  	// add write to size
   495  	bf.size += int64(rec.blocks) * blockSize
   496  	// return offset where record was written
   497  	return off, nil
   498  }
   499  
   500  // writeAt takes the provided data and creates a new record. It then attempts to
   501  // write the record to disk using the provided offset. The function will not advance
   502  // the file pointer, and it will not return the offset of where the record was
   503  // written to in the underlying file (because it was provided.) It returns an error
   504  // if the call fails for any reason.
   505  func (bf *bfile) writeAt(data []byte, off int64) error {
   506  	// error check
   507  	err := bf.check(off)
   508  	if err != nil {
   509  		return err
   510  	}
   511  	// create record from provided data
   512  	rec, err := makeRecord(data)
   513  	if err != nil {
   514  		return err
   515  	}
   516  	// write the record to disk
   517  	err = rec.writeRecordAt(bf.fp, off)
   518  	if err != nil {
   519  		return err
   520  	}
   521  	return nil
   522  }
   523  
   524  // rewind moves the file pointer back to the beginning
   525  func (bf *bfile) rewind() error {
   526  	if !bf.open {
   527  		return ErrFileClosed
   528  	}
   529  	_, err := bf.fp.Seek(0, io.SeekStart)
   530  	if err != nil {
   531  		return err
   532  	}
   533  	return nil
   534  }
   535  
   536  // seek calls the seek operation of the underlying file pointer
   537  func (bf *bfile) seek(offset int64, whence int) error {
   538  	if !bf.open {
   539  		return ErrFileClosed
   540  	}
   541  	_, err := bf.fp.Seek(offset, whence)
   542  	if err != nil {
   543  		return err
   544  	}
   545  	return nil
   546  }
   547  
   548  // count returns the offset index count (which should always match the record count
   549  func (bf *bfile) count() int {
   550  	return len(bf.index)
   551  }
   552  
   553  // sync calls Sync on the underlying *os.File
   554  func (bf *bfile) sync() error {
   555  	if !bf.open {
   556  		return ErrFileClosed
   557  	}
   558  	err := bf.fp.Sync()
   559  	if err != nil {
   560  		return err
   561  	}
   562  	return nil
   563  }
   564  
   565  // close calls Sync and then Close on the underlying *os.File
   566  func (bf *bfile) close() error {
   567  	if !bf.open {
   568  		return ErrFileClosed
   569  	}
   570  	err := bf.fp.Sync()
   571  	if err != nil {
   572  		return err
   573  	}
   574  	err = bf.fp.Close()
   575  	if err != nil {
   576  		return err
   577  	}
   578  	return nil
   579  }