github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/persist/persist.go (about) 1 package persist 2 3 import ( 4 "encoding/base32" 5 "errors" 6 "os" 7 "path/filepath" 8 "sync" 9 10 "gitlab.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 }