gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/proto/refcounter.go (about)

     1  package proto
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"math"
     7  	"os"
     8  	"sync"
     9  
    10  	siasync "go.sia.tech/siad/sync"
    11  
    12  	"gitlab.com/SkynetLabs/skyd/skymodules"
    13  
    14  	"gitlab.com/NebulousLabs/writeaheadlog"
    15  	"go.sia.tech/siad/modules"
    16  
    17  	"gitlab.com/NebulousLabs/errors"
    18  )
    19  
    20  var (
    21  	// ErrInvalidHeaderData is returned when we try to deserialize the header from
    22  	// a []byte with incorrect data
    23  	ErrInvalidHeaderData = errors.New("invalid header data")
    24  
    25  	// ErrInvalidSectorNumber is returned when the requested sector doesnt' exist
    26  	ErrInvalidSectorNumber = errors.New("invalid sector given - it does not exist")
    27  
    28  	// ErrInvalidVersion is returned when the version of the file we are trying to
    29  	// read does not match the current refCounterHeaderSize
    30  	ErrInvalidVersion = errors.New("invalid file version")
    31  
    32  	// ErrInvalidUpdateInstruction is returned when trying to parse a WAL update
    33  	// instruction that is too short to possibly contain all the required data.
    34  	ErrInvalidUpdateInstruction = errors.New("instructions slice is too short to contain the required data")
    35  
    36  	// ErrRefCounterNotExist is returned when there is no refcounter file with
    37  	// the given path
    38  	ErrRefCounterNotExist = errors.New("refcounter does not exist")
    39  
    40  	// ErrUpdateWithoutUpdateSession is returned when an update operation is
    41  	// called without an open update session
    42  	ErrUpdateWithoutUpdateSession = errors.New("an update operation was called without an open update session")
    43  
    44  	// ErrUpdateAfterDelete is returned when an update operation is attempted to
    45  	// be created after a delete
    46  	ErrUpdateAfterDelete = errors.New("updates cannot be created after a deletion")
    47  
    48  	// refCounterVersion defines the latest version of the refCounter
    49  	refCounterVersion = [8]byte{1}
    50  
    51  	// updateNameRCDelete is the name of an idempotent update that deletes a file
    52  	// from the disk.
    53  	updateNameRCDelete = "RC_DELETE"
    54  
    55  	// updateNameRCTruncate is the name of an idempotent update that truncates a
    56  	// refcounter file by a number of sectors.
    57  	updateNameRCTruncate = "RC_TRUNCATE"
    58  
    59  	// updateNameRCWriteAt is the name of an idempotent update that writes a
    60  	// value to a position in the file.
    61  	updateNameRCWriteAt = "RC_WRITE_AT"
    62  )
    63  
    64  const (
    65  	// refCounterHeaderSize is the size of the header in bytes
    66  	refCounterHeaderSize = 8
    67  )
    68  
    69  type (
    70  	// refCounter keeps track of how many references to each sector exist.
    71  	//
    72  	// Once the number of references drops to zero we consider the sector as
    73  	// garbage. We move the sector to end of the data and set the
    74  	// GarbageCollectionOffset to point to it. We can either reuse it to store
    75  	// new data or drop it from the contract at the end of the current period
    76  	// and before the contract renewal.
    77  	refCounter struct {
    78  		refCounterHeader
    79  
    80  		filepath   string // where the refcounter is persisted on disk
    81  		numSectors uint64 // used for sanity checks before we attempt mutation operations
    82  		staticWal  *writeaheadlog.WAL
    83  		mu         sync.Mutex
    84  
    85  		// utility fields
    86  		staticDeps modules.Dependencies
    87  
    88  		refCounterUpdateControl
    89  	}
    90  
    91  	// refCounterHeader contains metadata about the reference counter file
    92  	refCounterHeader struct {
    93  		Version [8]byte
    94  	}
    95  
    96  	// refCounterUpdateControl is a helper struct that holds fields pertaining
    97  	// to the process of updating the refcounter
    98  	refCounterUpdateControl struct {
    99  		// isDeleted marks when a refcounter has been deleted and therefore
   100  		// cannot accept further updates
   101  		isDeleted bool
   102  		// isUpdateInProgress marks when an update session is open and updates
   103  		// are allowed to be created and applied
   104  		isUpdateInProgress bool
   105  		// newSectorCounts holds the new values of sector counters during an
   106  		// update session, so we can use them even before they are stored on
   107  		// disk
   108  		newSectorCounts map[uint64]uint16
   109  
   110  		// muUpdate serializes updates to the refcounter. It is acquired by
   111  		// callStartUpdate and released by callUpdateApplied.
   112  		muUpdate siasync.TryMutex
   113  	}
   114  
   115  	// u16 is a utility type for ser/des of uint16 values
   116  	u16 [2]byte
   117  )
   118  
   119  // loadRefCounter loads a refcounter from disk
   120  func loadRefCounter(path string, wal *writeaheadlog.WAL) (_ *refCounter, err error) {
   121  	// Open the file and start loading the data.
   122  	f, err := os.Open(path)
   123  	if err != nil {
   124  		return nil, ErrRefCounterNotExist
   125  	}
   126  	defer func() {
   127  		err = errors.Compose(err, f.Close())
   128  	}()
   129  
   130  	var header refCounterHeader
   131  	headerBytes := make([]byte, refCounterHeaderSize)
   132  	if _, err = f.ReadAt(headerBytes, 0); err != nil {
   133  		return nil, errors.AddContext(err, "unable to read from file")
   134  	}
   135  	if err = deserializeHeader(headerBytes, &header); err != nil {
   136  		return nil, errors.AddContext(err, "unable to load refcounter header")
   137  	}
   138  	if header.Version != refCounterVersion {
   139  		return nil, errors.AddContext(ErrInvalidVersion, fmt.Sprintf("expected version %d, got version %d", refCounterVersion, header.Version))
   140  	}
   141  	fi, err := os.Stat(path)
   142  	if err != nil {
   143  		return nil, errors.AddContext(err, "failed to read file stats")
   144  	}
   145  	numSectors := uint64((fi.Size() - refCounterHeaderSize) / 2)
   146  	return &refCounter{
   147  		refCounterHeader: header,
   148  		filepath:         path,
   149  		numSectors:       numSectors,
   150  		staticWal:        wal,
   151  		staticDeps:       modules.ProdDependencies,
   152  		refCounterUpdateControl: refCounterUpdateControl{
   153  			newSectorCounts: make(map[uint64]uint16),
   154  		},
   155  	}, nil
   156  }
   157  
   158  // newCustomRefCounter creates a new sector reference counter file to accompany
   159  // a contract file and allows setting custom dependencies
   160  func newCustomRefCounter(path string, numSec uint64, wal *writeaheadlog.WAL, deps modules.Dependencies) (*refCounter, error) {
   161  	h := refCounterHeader{
   162  		Version: refCounterVersion,
   163  	}
   164  	updateHeader := writeaheadlog.WriteAtUpdate(path, 0, serializeHeader(h))
   165  
   166  	b := make([]byte, numSec*2)
   167  	for i := uint64(0); i < numSec; i++ {
   168  		binary.LittleEndian.PutUint16(b[i*2:i*2+2], 1)
   169  	}
   170  	updateCounters := writeaheadlog.WriteAtUpdate(path, refCounterHeaderSize, b)
   171  
   172  	err := wal.CreateAndApplyTransaction(writeaheadlog.ApplyUpdates, updateHeader, updateCounters)
   173  	return &refCounter{
   174  		refCounterHeader: h,
   175  		filepath:         path,
   176  		numSectors:       numSec,
   177  		staticWal:        wal,
   178  		staticDeps:       deps,
   179  		refCounterUpdateControl: refCounterUpdateControl{
   180  			newSectorCounts: make(map[uint64]uint16),
   181  		},
   182  	}, err
   183  }
   184  
   185  // newRefCounter creates a new sector reference counter file to accompany
   186  // a contract file
   187  func newRefCounter(path string, numSec uint64, wal *writeaheadlog.WAL) (*refCounter, error) {
   188  	return newCustomRefCounter(path, numSec, wal, modules.ProdDependencies)
   189  }
   190  
   191  // callAppend appends one counter to the end of the refcounter file and
   192  // initializes it with `1`
   193  func (rc *refCounter) callAppend() (writeaheadlog.Update, error) {
   194  	rc.mu.Lock()
   195  	defer rc.mu.Unlock()
   196  	if !rc.isUpdateInProgress {
   197  		return writeaheadlog.Update{}, ErrUpdateWithoutUpdateSession
   198  	}
   199  	if rc.isDeleted {
   200  		return writeaheadlog.Update{}, ErrUpdateAfterDelete
   201  	}
   202  	rc.numSectors++
   203  	rc.newSectorCounts[rc.numSectors-1] = 1
   204  	return createWriteAtUpdate(rc.filepath, rc.numSectors-1, 1), nil
   205  }
   206  
   207  // callCount returns the number of references to the given sector
   208  func (rc *refCounter) callCount(secIdx uint64) (uint16, error) {
   209  	rc.mu.Lock()
   210  	defer rc.mu.Unlock()
   211  	return rc.readCount(secIdx)
   212  }
   213  
   214  // callCreateAndApplyTransaction is a helper method that creates a writeaheadlog
   215  // transaction and applies it.
   216  func (rc *refCounter) callCreateAndApplyTransaction(updates ...writeaheadlog.Update) error {
   217  	rc.mu.Lock()
   218  	defer rc.mu.Unlock()
   219  	// We allow the creation of the file here because of the case where we got
   220  	// interrupted during the creation of the refcounter after writing the
   221  	// header update to the Wal but before applying it.
   222  	f, err := rc.staticDeps.OpenFile(rc.filepath, os.O_CREATE|os.O_RDWR, skymodules.DefaultFilePerm)
   223  	if err != nil {
   224  		return errors.AddContext(err, "failed to open refcounter file in order to apply updates")
   225  	}
   226  	defer func() {
   227  		err = errors.Compose(err, f.Close())
   228  	}()
   229  	if !rc.isUpdateInProgress {
   230  		return ErrUpdateWithoutUpdateSession
   231  	}
   232  	// Create the writeaheadlog transaction.
   233  	txn, err := rc.staticWal.NewTransaction(updates)
   234  	if err != nil {
   235  		return errors.AddContext(err, "failed to create wal txn")
   236  	}
   237  	// No extra setup is required. Signal that it is done.
   238  	if err := <-txn.SignalSetupComplete(); err != nil {
   239  		return errors.AddContext(err, "failed to signal setup completion")
   240  	}
   241  	// Starting at this point, the changes to be made are written to the disk.
   242  	// This means that we need to panic in case applying the updates fails in
   243  	// order to avoid data corruption.
   244  	defer func() {
   245  		if err != nil {
   246  			panic(err)
   247  		}
   248  	}()
   249  	// Apply the updates.
   250  	if err = applyUpdates(f, updates...); err != nil {
   251  		return errors.AddContext(err, "failed to apply updates")
   252  	}
   253  	// Updates are applied. Let the writeaheadlog know.
   254  	if err = txn.SignalUpdatesApplied(); err != nil {
   255  		return errors.AddContext(err, "failed to signal that updates are applied")
   256  	}
   257  	// If the refcounter got deleted then we're done.
   258  	if rc.isDeleted {
   259  		return nil
   260  	}
   261  	// Update the in-memory helper fields.
   262  	fi, err := os.Stat(rc.filepath)
   263  	if err != nil {
   264  		return errors.AddContext(err, "failed to read from disk after updates")
   265  	}
   266  	rc.numSectors = uint64((fi.Size() - refCounterHeaderSize) / 2)
   267  	return nil
   268  }
   269  
   270  // callDecrement decrements the reference counter of a given sector. The sector
   271  // is specified by its sequential number (secIdx).
   272  // Returns the updated number of references or an error.
   273  func (rc *refCounter) callDecrement(secIdx uint64) (writeaheadlog.Update, error) {
   274  	rc.mu.Lock()
   275  	defer rc.mu.Unlock()
   276  	if !rc.isUpdateInProgress {
   277  		return writeaheadlog.Update{}, ErrUpdateWithoutUpdateSession
   278  	}
   279  	if rc.isDeleted {
   280  		return writeaheadlog.Update{}, ErrUpdateAfterDelete
   281  	}
   282  	if secIdx >= rc.numSectors {
   283  		return writeaheadlog.Update{}, errors.AddContext(ErrInvalidSectorNumber, "failed to decrement")
   284  	}
   285  	count, err := rc.readCount(secIdx)
   286  	if err != nil {
   287  		return writeaheadlog.Update{}, errors.AddContext(err, "failed to read count from decrement")
   288  	}
   289  	if count == 0 {
   290  		return writeaheadlog.Update{}, errors.New("sector count underflow")
   291  	}
   292  	count--
   293  	rc.newSectorCounts[secIdx] = count
   294  	return createWriteAtUpdate(rc.filepath, secIdx, count), nil
   295  }
   296  
   297  // callDeleteRefCounter deletes the counter's file from disk
   298  func (rc *refCounter) callDeleteRefCounter() (writeaheadlog.Update, error) {
   299  	rc.mu.Lock()
   300  	defer rc.mu.Unlock()
   301  	if !rc.isUpdateInProgress {
   302  		return writeaheadlog.Update{}, ErrUpdateWithoutUpdateSession
   303  	}
   304  	if rc.isDeleted {
   305  		return writeaheadlog.Update{}, ErrUpdateAfterDelete
   306  	}
   307  	// mark the refcounter as deleted and don't allow any further updates to be created
   308  	rc.isDeleted = true
   309  	return createDeleteUpdate(rc.filepath), nil
   310  }
   311  
   312  // callDropSectors removes the last numSec sector counts from the refcounter file
   313  func (rc *refCounter) callDropSectors(numSec uint64) (writeaheadlog.Update, error) {
   314  	rc.mu.Lock()
   315  	defer rc.mu.Unlock()
   316  	if !rc.isUpdateInProgress {
   317  		return writeaheadlog.Update{}, ErrUpdateWithoutUpdateSession
   318  	}
   319  	if rc.isDeleted {
   320  		return writeaheadlog.Update{}, ErrUpdateAfterDelete
   321  	}
   322  	if numSec > rc.numSectors {
   323  		return writeaheadlog.Update{}, errors.AddContext(ErrInvalidSectorNumber, "failed to drop sectors")
   324  	}
   325  	rc.numSectors -= numSec
   326  	return createTruncateUpdate(rc.filepath, rc.numSectors), nil
   327  }
   328  
   329  // callIncrement increments the reference counter of a given sector. The sector
   330  // is specified by its sequential number (secIdx).
   331  // Returns the updated number of references or an error.
   332  func (rc *refCounter) callIncrement(secIdx uint64) (writeaheadlog.Update, error) {
   333  	rc.mu.Lock()
   334  	defer rc.mu.Unlock()
   335  	if !rc.isUpdateInProgress {
   336  		return writeaheadlog.Update{}, ErrUpdateWithoutUpdateSession
   337  	}
   338  	if rc.isDeleted {
   339  		return writeaheadlog.Update{}, ErrUpdateAfterDelete
   340  	}
   341  	if secIdx >= rc.numSectors {
   342  		return writeaheadlog.Update{}, errors.AddContext(ErrInvalidSectorNumber, "failed to increment")
   343  	}
   344  	count, err := rc.readCount(secIdx)
   345  	if err != nil {
   346  		return writeaheadlog.Update{}, errors.AddContext(err, "failed to read count from increment")
   347  	}
   348  	if count == math.MaxUint16 {
   349  		return writeaheadlog.Update{}, errors.New("sector count overflow")
   350  	}
   351  	count++
   352  	rc.newSectorCounts[secIdx] = count
   353  	return createWriteAtUpdate(rc.filepath, secIdx, count), nil
   354  }
   355  
   356  // callSetCount sets the value of the reference counter of a given sector. The
   357  // sector is specified by its sequential number (secIdx).
   358  func (rc *refCounter) callSetCount(secIdx uint64, c uint16) (writeaheadlog.Update, error) {
   359  	rc.mu.Lock()
   360  	defer rc.mu.Unlock()
   361  	if !rc.isUpdateInProgress {
   362  		return writeaheadlog.Update{}, ErrUpdateWithoutUpdateSession
   363  	}
   364  	if rc.isDeleted {
   365  		return writeaheadlog.Update{}, ErrUpdateAfterDelete
   366  	}
   367  	// this allows the client to set multiple new counts in random order
   368  	if secIdx >= rc.numSectors {
   369  		rc.numSectors = secIdx + 1
   370  	}
   371  	rc.newSectorCounts[secIdx] = c
   372  	return createWriteAtUpdate(rc.filepath, secIdx, c), nil
   373  }
   374  
   375  // callStartUpdate acquires a lock, ensuring the caller is the only one currently
   376  // allowed to perform updates on this refcounter file. This lock is released by
   377  // calling callUpdateApplied after calling callCreateAndApplyTransaction in
   378  // order to apply the updates.
   379  func (rc *refCounter) callStartUpdate() error {
   380  	rc.muUpdate.Lock()
   381  	return rc.managedStartUpdate()
   382  }
   383  
   384  // callSwap swaps the two sectors at the given indices
   385  func (rc *refCounter) callSwap(firstIdx, secondIdx uint64) ([]writeaheadlog.Update, error) {
   386  	rc.mu.Lock()
   387  	defer rc.mu.Unlock()
   388  	if !rc.isUpdateInProgress {
   389  		return []writeaheadlog.Update{}, ErrUpdateWithoutUpdateSession
   390  	}
   391  	if rc.isDeleted {
   392  		return []writeaheadlog.Update{}, ErrUpdateAfterDelete
   393  	}
   394  	if firstIdx >= rc.numSectors || secondIdx >= rc.numSectors {
   395  		return []writeaheadlog.Update{}, errors.AddContext(ErrInvalidSectorNumber, "failed to swap sectors")
   396  	}
   397  	firstVal, err := rc.readCount(firstIdx)
   398  	if err != nil {
   399  		return []writeaheadlog.Update{}, errors.AddContext(err, "failed to read count from swap")
   400  	}
   401  	secondVal, err := rc.readCount(secondIdx)
   402  	if err != nil {
   403  		return []writeaheadlog.Update{}, errors.AddContext(err, "failed to read count from swap")
   404  	}
   405  	rc.newSectorCounts[firstIdx] = secondVal
   406  	rc.newSectorCounts[secondIdx] = firstVal
   407  	return []writeaheadlog.Update{
   408  		createWriteAtUpdate(rc.filepath, firstIdx, secondVal),
   409  		createWriteAtUpdate(rc.filepath, secondIdx, firstVal),
   410  	}, nil
   411  }
   412  
   413  // callUpdateApplied cleans up temporary data and releases the update lock, thus
   414  // allowing other actors to acquire it in order to update the refcounter.
   415  func (rc *refCounter) callUpdateApplied() error {
   416  	rc.mu.Lock()
   417  	defer rc.mu.Unlock()
   418  
   419  	// this method cannot be called if there is no active update session
   420  	if !rc.isUpdateInProgress {
   421  		return ErrUpdateWithoutUpdateSession
   422  	}
   423  
   424  	// clean up the temp counts
   425  	rc.newSectorCounts = make(map[uint64]uint16)
   426  	// close the update session
   427  	rc.isUpdateInProgress = false
   428  	// release the update lock
   429  	rc.muUpdate.Unlock()
   430  	return nil
   431  }
   432  
   433  // managedStartUpdate does everything callStartUpdate needs, aside from acquiring a
   434  // lock
   435  func (rc *refCounter) managedStartUpdate() error {
   436  	rc.mu.Lock()
   437  	defer rc.mu.Unlock()
   438  	if rc.isDeleted {
   439  		return ErrUpdateAfterDelete
   440  	}
   441  	// open an update session
   442  	rc.isUpdateInProgress = true
   443  	return nil
   444  }
   445  
   446  // readCount reads the given sector count either from disk (if there are no
   447  // pending updates) or from the in-memory cache (if there are).
   448  func (rc *refCounter) readCount(secIdx uint64) (_ uint16, err error) {
   449  	// check if the secIdx is a valid sector index based on the number of
   450  	// sectors in the file
   451  	if secIdx >= rc.numSectors {
   452  		return 0, errors.AddContext(ErrInvalidSectorNumber, "failed to read count")
   453  	}
   454  	// check if the value is being changed by a pending update
   455  	if count, ok := rc.newSectorCounts[secIdx]; ok {
   456  		return count, nil
   457  	}
   458  	// read the value from disk
   459  	f, err := rc.staticDeps.Open(rc.filepath)
   460  	if err != nil {
   461  		return 0, errors.AddContext(err, "failed to open the refcounter file")
   462  	}
   463  	defer func() {
   464  		err = errors.Compose(err, f.Close())
   465  	}()
   466  
   467  	var b u16
   468  	if _, err = f.ReadAt(b[:], int64(offset(secIdx))); err != nil {
   469  		return 0, errors.AddContext(err, "failed to read from refcounter file")
   470  	}
   471  	return binary.LittleEndian.Uint16(b[:]), nil
   472  }
   473  
   474  // applyUpdates takes a list of WAL updates and applies them.
   475  func applyUpdates(f modules.File, updates ...writeaheadlog.Update) (err error) {
   476  	for _, update := range updates {
   477  		switch update.Name {
   478  		case updateNameRCDelete:
   479  			err = applyDeleteUpdate(update)
   480  		case updateNameRCTruncate:
   481  			err = applyTruncateUpdate(f, update)
   482  		case updateNameRCWriteAt:
   483  			err = applyWriteAtUpdate(f, update)
   484  		default:
   485  			err = fmt.Errorf("unknown update type: %v", update.Name)
   486  		}
   487  		if err != nil {
   488  			return err
   489  		}
   490  	}
   491  	return f.Sync()
   492  }
   493  
   494  // createDeleteUpdate is a helper function which creates a writeaheadlog update
   495  // for deleting a given refcounter file.
   496  func createDeleteUpdate(path string) writeaheadlog.Update {
   497  	return writeaheadlog.Update{
   498  		Name:         updateNameRCDelete,
   499  		Instructions: []byte(path),
   500  	}
   501  }
   502  
   503  // applyDeleteUpdate parses and applies a Delete update.
   504  func applyDeleteUpdate(update writeaheadlog.Update) error {
   505  	if update.Name != updateNameRCDelete {
   506  		return fmt.Errorf("applyDeleteUpdate called on update of type %v", update.Name)
   507  	}
   508  	// Remove the file and ignore the NotExist error
   509  	if err := os.Remove(string(update.Instructions)); !os.IsNotExist(err) {
   510  		return err
   511  	}
   512  	return nil
   513  }
   514  
   515  // createTruncateUpdate is a helper function which creates a writeaheadlog
   516  // update for truncating a number of sectors from the end of the file.
   517  func createTruncateUpdate(path string, newNumSec uint64) writeaheadlog.Update {
   518  	b := make([]byte, 8+len(path))
   519  	binary.LittleEndian.PutUint64(b[:8], newNumSec)
   520  	copy(b[8:8+len(path)], path)
   521  	return writeaheadlog.Update{
   522  		Name:         updateNameRCTruncate,
   523  		Instructions: b,
   524  	}
   525  }
   526  
   527  // applyTruncateUpdate parses and applies a Truncate update.
   528  func applyTruncateUpdate(f modules.File, u writeaheadlog.Update) error {
   529  	if u.Name != updateNameRCTruncate {
   530  		return fmt.Errorf("applyAppendTruncate called on update of type %v", u.Name)
   531  	}
   532  	// Decode update.
   533  	_, newNumSec, err := readTruncateUpdate(u)
   534  	if err != nil {
   535  		return err
   536  	}
   537  	// Truncate the file to the needed size.
   538  	return f.Truncate(refCounterHeaderSize + int64(newNumSec)*2)
   539  }
   540  
   541  // createWriteAtUpdate is a helper function which creates a writeaheadlog
   542  // update for swapping the values of two positions in the file.
   543  func createWriteAtUpdate(path string, secIdx uint64, value uint16) writeaheadlog.Update {
   544  	b := make([]byte, 8+2+len(path))
   545  	binary.LittleEndian.PutUint64(b[:8], secIdx)
   546  	binary.LittleEndian.PutUint16(b[8:10], value)
   547  	copy(b[10:10+len(path)], path)
   548  	return writeaheadlog.Update{
   549  		Name:         updateNameRCWriteAt,
   550  		Instructions: b,
   551  	}
   552  }
   553  
   554  // applyWriteAtUpdate parses and applies a WriteAt update.
   555  func applyWriteAtUpdate(f modules.File, u writeaheadlog.Update) error {
   556  	if u.Name != updateNameRCWriteAt {
   557  		return fmt.Errorf("applyAppendWriteAt called on update of type %v", u.Name)
   558  	}
   559  	// Decode update.
   560  	_, secIdx, value, err := readWriteAtUpdate(u)
   561  	if err != nil {
   562  		return err
   563  	}
   564  
   565  	// Write the value to disk.
   566  	var b u16
   567  	binary.LittleEndian.PutUint16(b[:], value)
   568  	_, err = f.WriteAt(b[:], int64(offset(secIdx)))
   569  	return err
   570  }
   571  
   572  // deserializeHeader deserializes a header from []byte
   573  func deserializeHeader(b []byte, h *refCounterHeader) error {
   574  	if uint64(len(b)) < refCounterHeaderSize {
   575  		return ErrInvalidHeaderData
   576  	}
   577  	copy(h.Version[:], b[:8])
   578  	return nil
   579  }
   580  
   581  // offset calculates the byte offset of the sector counter in the file on disk
   582  func offset(secIdx uint64) uint64 {
   583  	return refCounterHeaderSize + secIdx*2
   584  }
   585  
   586  // readTruncateUpdate decodes a Truncate update
   587  func readTruncateUpdate(u writeaheadlog.Update) (path string, newNumSec uint64, err error) {
   588  	if len(u.Instructions) < 8 {
   589  		err = ErrInvalidUpdateInstruction
   590  		return
   591  	}
   592  	newNumSec = binary.LittleEndian.Uint64(u.Instructions[:8])
   593  	path = string(u.Instructions[8:])
   594  	return
   595  }
   596  
   597  // readWriteAtUpdate decodes a WriteAt update
   598  func readWriteAtUpdate(u writeaheadlog.Update) (path string, secIdx uint64, value uint16, err error) {
   599  	if len(u.Instructions) < 10 {
   600  		err = ErrInvalidUpdateInstruction
   601  		return
   602  	}
   603  	secIdx = binary.LittleEndian.Uint64(u.Instructions[:8])
   604  	value = binary.LittleEndian.Uint16(u.Instructions[8:10])
   605  	path = string(u.Instructions[10:])
   606  	return
   607  }
   608  
   609  // serializeHeader serializes a header to []byte
   610  func serializeHeader(h refCounterHeader) []byte {
   611  	b := make([]byte, refCounterHeaderSize)
   612  	copy(b[:8], h.Version[:])
   613  	return b
   614  }