go / atomicfile

I use atomic file writes to prevent partial or corrupt files when a program crashes or is interrupted mid-write.

The problem

A naive file write can leave a file in a corrupt state:

// Dangerous: if the program crashes mid-write,
// the file may be truncated or partially written
err := os.WriteFile("config.json", data, 0644)

Atomic write

Write to a temp file, sync to disk, then rename:

func WriteFile(filename string, data []byte, perm os.FileMode) error {
	dir := filepath.Dir(filename)
	f, err := os.CreateTemp(dir, filepath.Base(filename)+".tmp")
	if err != nil {
		return err
	}
	tmpName := f.Name()

	// Clean up on any error
	defer func() {
		if err != nil {
			f.Close()
			os.Remove(tmpName)
		}
	}()

	if _, err = f.Write(data); err != nil {
		return err
	}
	if err = f.Chmod(perm); err != nil {
		return err
	}
	if err = f.Sync(); err != nil {
		return err
	}
	if err = f.Close(); err != nil {
		return err
	}
	return os.Rename(tmpName, filename)
}

Key points:

Usage

config := Config{Debug: true}
data, _ := json.MarshalIndent(config, "", "  ")
if err := WriteFile("config.json", data, 0644); err != nil {
    log.Fatal(err)
}

When to use

For append-only logs or very large files, other strategies (write-ahead logs, checksums) may be more appropriate.

← All articles