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  }