github.com/decred/dcrlnd@v0.7.6/chanbackup/backupfile.go (about) 1 package chanbackup 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 9 "github.com/decred/dcrlnd/keychain" 10 ) 11 12 const ( 13 // DefaultBackupFileName is the default name of the auto updated static 14 // channel backup fie. 15 DefaultBackupFileName = "channel.backup" 16 17 // DefaultTempBackupFileName is the default name of the temporary SCB 18 // file that we'll use to atomically update the primary back up file 19 // when new channel are detected. 20 DefaultTempBackupFileName = "temp-dont-use.backup" 21 ) 22 23 var ( 24 // ErrNoBackupFileExists is returned if caller attempts to call 25 // UpdateAndSwap with the file name not set. 26 ErrNoBackupFileExists = fmt.Errorf("back up file name not set") 27 28 // ErrNoTempBackupFile is returned if caller attempts to call 29 // UpdateAndSwap with the temp back up file name not set. 30 ErrNoTempBackupFile = fmt.Errorf("temp backup file not set") 31 ) 32 33 // MultiFile represents a file on disk that a caller can use to read the packed 34 // multi backup into an unpacked one, and also atomically update the contents 35 // on disk once new channels have been opened, and old ones closed. This struct 36 // relies on an atomic file rename property which most widely use file systems 37 // have. 38 type MultiFile struct { 39 // fileName is the file name of the main back up file. 40 fileName string 41 42 // tempFileName is the name of the file that we'll use to stage a new 43 // packed multi-chan backup, and the rename to the main back up file. 44 tempFileName string 45 46 // tempFile is an open handle to the temp back up file. 47 tempFile *os.File 48 } 49 50 // NewMultiFile create a new multi-file instance at the target location on the 51 // file system. 52 func NewMultiFile(fileName string) *MultiFile { 53 54 // We'll our temporary backup file in the very same directory as the 55 // main backup file. 56 backupFileDir := filepath.Dir(fileName) 57 tempFileName := filepath.Join( 58 backupFileDir, DefaultTempBackupFileName, 59 ) 60 61 return &MultiFile{ 62 fileName: fileName, 63 tempFileName: tempFileName, 64 } 65 } 66 67 // UpdateAndSwap will attempt write a new temporary backup file to disk with 68 // the newBackup encoded, then atomically swap (via rename) the old file for 69 // the new file by updating the name of the new file to the old. 70 func (b *MultiFile) UpdateAndSwap(newBackup PackedMulti) error { 71 // If the main backup file isn't set, then we can't proceed. 72 if b.fileName == "" { 73 return ErrNoBackupFileExists 74 } 75 76 log.Infof("Updating backup file at %v", b.fileName) 77 78 // If the old back up file still exists, then we'll delete it before 79 // proceeding. 80 if _, err := os.Stat(b.tempFileName); err == nil { 81 log.Infof("Found old temp backup @ %v, removing before swap", 82 b.tempFileName) 83 84 err = os.Remove(b.tempFileName) 85 if err != nil { 86 return fmt.Errorf("unable to remove temp "+ 87 "backup file: %v", err) 88 } 89 } 90 91 // Now that we know the staging area is clear, we'll create the new 92 // temporary back up file. 93 var err error 94 b.tempFile, err = os.Create(b.tempFileName) 95 if err != nil { 96 return fmt.Errorf("unable to create temp file: %v", err) 97 } 98 99 // With the file created, we'll write the new packed multi backup and 100 // remove the temporary file all together once this method exits. 101 _, err = b.tempFile.Write([]byte(newBackup)) 102 if err != nil { 103 return fmt.Errorf("unable to write backup to temp file: %v", err) 104 } 105 if err := b.tempFile.Sync(); err != nil { 106 return fmt.Errorf("unable to sync temp file: %v", err) 107 } 108 defer os.Remove(b.tempFileName) 109 110 log.Infof("Swapping old multi backup file from %v to %v", 111 b.tempFileName, b.fileName) 112 113 // Before we rename the swap (atomic name swap), we'll make 114 // sure to close the current file as some OSes don't support 115 // renaming a file that's already open (Windows). 116 if err := b.tempFile.Close(); err != nil { 117 return fmt.Errorf("unable to close file: %v", err) 118 } 119 120 // Finally, we'll attempt to atomically rename the temporary file to 121 // the main back up file. If this succeeds, then we'll only have a 122 // single file on disk once this method exits. 123 return os.Rename(b.tempFileName, b.fileName) 124 } 125 126 // ExtractMulti attempts to extract the packed multi backup we currently point 127 // to into an unpacked version. This method will fail if no backup file 128 // currently exists as the specified location. 129 func (b *MultiFile) ExtractMulti(keyChain keychain.KeyRing) (*Multi, error) { 130 var err error 131 132 // We'll return an error if the main file isn't currently set. 133 if b.fileName == "" { 134 return nil, ErrNoBackupFileExists 135 } 136 137 // Now that we've confirmed the target file is populated, we'll read 138 // all the contents of the file. This function ensures that file is 139 // always closed, even if we can't read the contents. 140 multiBytes, err := ioutil.ReadFile(b.fileName) 141 if err != nil { 142 return nil, err 143 } 144 145 // Finally, we'll attempt to unpack the file and return the unpack 146 // version to the caller. 147 packedMulti := PackedMulti(multiBytes) 148 return packedMulti.Unpack(keyChain) 149 }