github.com/dmmcquay/sia@v1.3.1-0.20180712220038-9f8d535311b9/persist/persist.go (about)

     1  package persist
     2  
     3  import (
     4  	"encoding/base32"
     5  	"errors"
     6  	"os"
     7  	"path/filepath"
     8  	"sync"
     9  
    10  	"github.com/NebulousLabs/fastrand"
    11  )
    12  
    13  const (
    14  	// persistDir defines the folder that is used for testing the persist
    15  	// package.
    16  	persistDir = "persist"
    17  
    18  	// tempSuffix is the suffix that is applied to the temporary/backup versions
    19  	// of the files being persisted.
    20  	tempSuffix = "_temp"
    21  )
    22  
    23  var (
    24  	// ErrBadFilenameSuffix indicates that SaveJSON or LoadJSON was called using
    25  	// a filename that has a bad suffix. This prevents users from trying to use
    26  	// this package to manage the temp files - this packaage will manage them
    27  	// automatically.
    28  	ErrBadFilenameSuffix = errors.New("filename suffix not allowed")
    29  
    30  	// ErrBadHeader indicates that the file opened is not the file that was
    31  	// expected.
    32  	ErrBadHeader = errors.New("wrong header")
    33  
    34  	// ErrBadVersion indicates that the version number of the file is not
    35  	// compatible with the current codebase.
    36  	ErrBadVersion = errors.New("incompatible version")
    37  
    38  	// ErrFileInUse is returned if SaveJSON or LoadJSON is called on a file
    39  	// that's already being manipulated in another thread by the persist
    40  	// package.
    41  	ErrFileInUse = errors.New("another thread is saving or loading this file")
    42  )
    43  
    44  var (
    45  	// activeFiles is a map tracking which filenames are currently being used
    46  	// for saving and loading. There should never be a situation where the same
    47  	// file is being called twice from different threads, as the persist package
    48  	// has no way to tell what order they were intended to be called.
    49  	activeFiles   = make(map[string]struct{})
    50  	activeFilesMu sync.Mutex
    51  )
    52  
    53  // Metadata contains the header and version of the data being stored.
    54  type Metadata struct {
    55  	Header, Version string
    56  }
    57  
    58  // RandomSuffix returns a 20 character base32 suffix for a filename. There are
    59  // 100 bits of entropy, and a very low probability of colliding with existing
    60  // files unintentionally.
    61  func RandomSuffix() string {
    62  	str := base32.StdEncoding.EncodeToString(fastrand.Bytes(20))
    63  	return str[:20]
    64  }
    65  
    66  // A safeFile is a file that is stored under a temporary filename. When Commit
    67  // is called, the file is renamed to its "final" filename. This allows for
    68  // atomic updating of files; otherwise, an unexpected shutdown could leave a
    69  // valuable file in a corrupted state. Callers must still Close the file handle
    70  // as usual.
    71  type safeFile struct {
    72  	*os.File
    73  	finalName string
    74  }
    75  
    76  // CommitSync syncs the file, closes it, and then renames it to the intended
    77  // final filename. CommitSync should not be called from a defer if the
    78  // function it is being called from can return an error.
    79  func (sf *safeFile) CommitSync() error {
    80  	if err := sf.Sync(); err != nil {
    81  		return err
    82  	}
    83  	if err := sf.Close(); err != nil {
    84  		return err
    85  	}
    86  	return os.Rename(sf.finalName+"_temp", sf.finalName)
    87  }
    88  
    89  // NewSafeFile returns a file that can atomically be written to disk,
    90  // minimizing the risk of corruption.
    91  func NewSafeFile(filename string) (*safeFile, error) {
    92  	file, err := os.Create(filename + tempSuffix)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	// Get the absolute path of the filename so that calling os.Chdir in
    98  	// between calling NewSafeFile and calling safeFile.Commit does not change
    99  	// the final file path.
   100  	absFilename, err := filepath.Abs(filename)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	return &safeFile{file, absFilename}, nil
   106  }
   107  
   108  // RemoveFile removes an atomic file from disk, along with any uncommitted
   109  // or temporary files.
   110  func RemoveFile(filename string) error {
   111  	err := os.RemoveAll(filename)
   112  	if err != nil {
   113  		return err
   114  	}
   115  	err = os.RemoveAll(filename + tempSuffix)
   116  	if err != nil {
   117  		return err
   118  	}
   119  	return nil
   120  }