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) } fingerprint := atomicwrite.FingerprintFromBytes(data)
// 2. Modify newData := []byte(`{"updated": true}`)
// 3. Write with TOCTOU protection err = atomicwrite.WriteVerified(path, newData, fingerprint) if err != nil { panic(err) }}First Write (No Existing File)
Section titled “First Write (No Existing File)”Use plain Write when there is no prior content to protect — it is crash-durable but
skips fingerprint verification:
err := atomicwrite.Write(path, data)For verified writes, a zero-value
Fingerprint{}means “the file must NOT already exist” —WriteVerifiedfails withErrConcurrentModificationif another process creates it first.
Skip No-Op Rewrites
Section titled “Skip No-Op Rewrites”WriteIfChanged writes only when content differs from disk. Perfect for config files
and code generators that must not produce spurious diffs on re-runs:
changed, err := atomicwrite.WriteIfChanged(path, newData)if changed { fmt.Println("file updated")}Detecting Concurrent Modification
Section titled “Detecting Concurrent Modification”If the file changed between your read and write, WriteVerified returns
ErrConcurrentModification:
err := atomicwrite.WriteVerified(path, newData, fingerprint)if errors.Is(err, atomicwrite.ErrConcurrentModification) { // Re-read the file, merge changes, and retry data, err := os.ReadFile(path) if err != nil { panic(err) } fingerprint, err = atomicwrite.FingerprintFile(path) if err != nil { panic(err) } err = atomicwrite.WriteVerified(path, mergedData, fingerprint)}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, WriteVerified returns ErrConcurrentModification —
the caller should re-read and retry.