github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/persist/persist.go (about)

     1  package persist
     2  
     3  import (
     4  	"crypto/rand"
     5  	"encoding/base32"
     6  	"errors"
     7  	"os"
     8  	"path/filepath"
     9  )
    10  
    11  const (
    12  	// persistDir defines the folder that is used for testing the persist
    13  	// package.
    14  	persistDir = "persist"
    15  )
    16  
    17  var (
    18  	// ErrBadVersion indicates that the version number of the file is not
    19  	// compatible with the current codebase.
    20  	ErrBadVersion = errors.New("incompatible version")
    21  
    22  	// ErrBadHeader indicates that the file opened is not the file that was
    23  	// expected.
    24  	ErrBadHeader = errors.New("wrong header")
    25  )
    26  
    27  // Metadata contains the header and version of the data being stored.
    28  type Metadata struct {
    29  	Header, Version string
    30  }
    31  
    32  // RandomSuffix returns a 20 character base32 suffix for a filename. There are
    33  // 100 bits of entropy, and a very low probability of colliding with existing
    34  // files unintentionally.
    35  func RandomSuffix() string {
    36  	randBytes := make([]byte, 20)
    37  	rand.Read(randBytes)
    38  	str := base32.StdEncoding.EncodeToString(randBytes)
    39  	return str[:20]
    40  }
    41  
    42  // A safeFile is a file that is stored under a temporary filename. When Commit
    43  // is called, the file is renamed to its "final" filename. This allows for
    44  // atomic updating of files; otherwise, an unexpected shutdown could leave a
    45  // valuable file in a corrupted state. Callers must still Close the file handle
    46  // as usual.
    47  type safeFile struct {
    48  	*os.File
    49  	finalName string
    50  }
    51  
    52  // Commit closes the file, and then renames it to the intended final filename.
    53  // Commit should not be called from a defer if the function it is being called
    54  // from can return an error.
    55  func (sf *safeFile) Commit() error {
    56  	if err := sf.Close(); err != nil {
    57  		return err
    58  	}
    59  	return os.Rename(sf.finalName+"_temp", sf.finalName)
    60  }
    61  
    62  // CommitSync syncs the file, closes it, and then renames it to the intended
    63  // final filename. CommitSync should not be called from a defer if the
    64  // function it is being called from can return an error.
    65  func (sf *safeFile) CommitSync() error {
    66  	if err := sf.Sync(); err != nil {
    67  		return err
    68  	}
    69  	return sf.Commit()
    70  }
    71  
    72  // NewSafeFile returns a file that can atomically be written to disk,
    73  // minimizing the risk of corruption.
    74  func NewSafeFile(filename string) (*safeFile, error) {
    75  	file, err := os.Create(filename + "_temp")
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  
    80  	// Get the absolute path of the filename so that calling os.Chdir in
    81  	// between calling NewSafeFile and calling safeFile.Commit does not change
    82  	// the final file path.
    83  	absFilename, err := filepath.Abs(filename)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	return &safeFile{file, absFilename}, nil
    89  }