Skip to content

Quick Start

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)
}
}

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” — WriteVerified fails with ErrConcurrentModification if another process creates it first.

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")
}

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)
}
// From raw bytes
fp := 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 file
if fp.IsZero() {
// First run — no file existed
}
// Check if content matches
if fp.Matches(currentData) {
// File hasn't changed
}
  1. Fingerprint — compute an xxhash64 digest when you read the file
  2. Write to unique .tmp — stage new content in a uniquely-named temp file
  3. fsync — flush the temp file to disk for crash durability
  4. Lock + verify — acquire an exclusive file lock, re-read the file, verify the fingerprint
  5. Atomic rename — rename the temp file to the target
  6. fsync directory — sync the directory entry (POSIX)

If the fingerprint doesn’t match, WriteVerified returns ErrConcurrentModification — the caller should re-read and retry.