diff --git a/common/os.go b/common/os.go index 9dc81c579..ae2ed087d 100644 --- a/common/os.go +++ b/common/os.go @@ -93,9 +93,8 @@ func MustWriteFile(filePath string, contents []byte, mode os.FileMode) { } } -// Writes to newBytes to filePath. -// Guaranteed not to lose *both* oldBytes and newBytes, -// (assuming that the OS is perfect) +// WriteFileAtomic writes newBytes to temp and atomically moves to filePath +// when everything else succeeds. func WriteFileAtomic(filePath string, newBytes []byte, mode os.FileMode) error { // If a file already exists there, copy to filePath+".bak" (overwrite anything) if _, err := os.Stat(filePath); !os.IsNotExist(err) { @@ -108,13 +107,27 @@ func WriteFileAtomic(filePath string, newBytes []byte, mode os.FileMode) error { return fmt.Errorf("Could not write file %v. %v", filePath+".bak", err) } } - // Write newBytes to filePath.new - err := ioutil.WriteFile(filePath+".new", newBytes, mode) + f, err := ioutil.TempFile("", "") if err != nil { - return fmt.Errorf("Could not write file %v. %v", filePath+".new", err) + return err + } + _, err = f.Write(newBytes) + if err == nil { + err = f.Sync() + } + if closeErr := f.Close(); err == nil { + err = closeErr + } + if permErr := os.Chmod(f.Name(), mode); err == nil { + err = permErr + } + if err == nil { + err = os.Rename(f.Name(), filePath) + } + // any err should result in full cleanup + if err != nil { + os.Remove(f.Name()) } - // Move filePath.new to filePath - err = os.Rename(filePath+".new", filePath) return err }