github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/filesystem/atomic.go (about)

     1  package filesystem
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  )
     8  
     9  const (
    10  	// atomicWriteTemporaryNamePrefix is the file name prefix to use for
    11  	// intermediate temporary files used in atomic writes.
    12  	atomicWriteTemporaryNamePrefix = TemporaryNamePrefix + "atomic-write"
    13  )
    14  
    15  // WriteFileAtomic writes a file to disk in an atomic fashion by using an
    16  // intermediate temporary file that is swapped in place using a rename
    17  // operation.
    18  func WriteFileAtomic(path string, data []byte, permissions os.FileMode) error {
    19  	// Create a temporary file. The os package already uses secure permissions
    20  	// for creating temporary files, so we don't need to change them.
    21  	temporary, err := os.CreateTemp(filepath.Dir(path), atomicWriteTemporaryNamePrefix)
    22  	if err != nil {
    23  		return fmt.Errorf("unable to create temporary file: %w", err)
    24  	}
    25  
    26  	// Write data.
    27  	if _, err = temporary.Write(data); err != nil {
    28  		temporary.Close()
    29  		os.Remove(temporary.Name())
    30  		return fmt.Errorf("unable to write data to temporary file: %w", err)
    31  	}
    32  
    33  	// Close out the file.
    34  	if err = temporary.Close(); err != nil {
    35  		os.Remove(temporary.Name())
    36  		return fmt.Errorf("unable to close temporary file: %w", err)
    37  	}
    38  
    39  	// Set the file's permissions.
    40  	if err = os.Chmod(temporary.Name(), permissions); err != nil {
    41  		os.Remove(temporary.Name())
    42  		return fmt.Errorf("unable to change file permissions: %w", err)
    43  	}
    44  
    45  	// Rename the file.
    46  	if err = Rename(nil, temporary.Name(), nil, path, true); err != nil {
    47  		os.Remove(temporary.Name())
    48  		return fmt.Errorf("unable to rename file: %w", err)
    49  	}
    50  
    51  	// Success.
    52  	return nil
    53  }