Quick Start
The Read-Modify-Write Pattern
Section titled “The Read-Modify-Write Pattern”Every safe file write follows three steps: read, fingerprint, write.
package main
import ( "errors" "fmt" "os"
atomicwrite "github.com/larsartmann/go-atomic-write")
func main() { path := "/path/to/config.json"
// 1. Read + fingerprint data, err := os.ReadFile(path) if err != nil && !os.IsNotExist(err) { panic(err) } fp := atomicwrite.FingerprintFromBytes(data)
// 2. Modify newData := []byte(`{"updated": true}`)
// 3. Write with TOCTOU protection err = atomicwrite.Write(path, newData, fp) if err != nil { panic(err) }}First Write (No Existing File)
Section titled “First Write (No Existing File)”Pass a zero-value Fingerprint to skip verification:
err := atomicwrite.Write(path, data, atomicwrite.Fingerprint{})Detecting Concurrent Modification
Section titled “Detecting Concurrent Modification”If the file changed between your read and write, Write returns ErrConcurrentModification:
err := atomicwrite.Write(path, newData, fp)if errors.Is(err, atomicwrite.ErrConcurrentModification) { // Re-read the file, merge changes, and retry data, err := os.ReadFile(path) if err != nil { panic(err) } fp = atomicwrite.FingerprintFromBytes(data) err = atomicwrite.Write(path, mergedData, fp)}Fingerprinting Helpers
Section titled “Fingerprinting Helpers”// From raw bytesfp := atomicwrite.FingerprintFromBytes([]byte("content"))
// From a file path (returns zero Fingerprint if file doesn't exist)fp, err := atomicwrite.FingerprintFile(path)
// Check if it represents no prior fileif fp.IsZero() { // First run — no file existed}
// Check if content matchesif fp.Matches(currentData) { // File hasn't changed}How It Works
Section titled “How It Works”- Fingerprint — compute an xxhash64 digest when you read the file
- Write to unique
.tmp— stage new content in a uniquely-named temp file - fsync — flush the temp file to disk for crash durability
- Lock + verify — acquire an exclusive file lock, re-read the file, verify the fingerprint
- Atomic rename — rename the temp file to the target
- fsync directory — sync the directory entry (POSIX)
If the fingerprint doesn’t match, Write returns ErrConcurrentModification — the caller should re-read and retry.