main.go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
package main

import (
	"encoding/json"
	"fmt"
	"io/fs"
	"log"
	"os"
	"path/filepath"
)

func writeFileAtomic(path string, data []byte, perm fs.FileMode) error {
	dir := filepath.Dir(path)
	if err := os.MkdirAll(dir, 0o755); err != nil {
		return fmt.Errorf("mkdir: %w", err)
	}

	tmp, err := os.CreateTemp(dir, ".tmp-*")
	if err != nil {
		return fmt.Errorf("create temp: %w", err)
	}
	tmpName := tmp.Name()

	cleanup := func() {
		tmp.Close()
		os.Remove(tmpName)
	}

	if _, err := tmp.Write(data); err != nil {
		cleanup()
		return fmt.Errorf("write temp: %w", err)
	}
	if err := tmp.Sync(); err != nil {
		cleanup()
		return fmt.Errorf("sync temp: %w", err)
	}
	if err := tmp.Close(); err != nil {
		os.Remove(tmpName)
		return fmt.Errorf("close temp: %w", err)
	}
	if err := os.Chmod(tmpName, perm); err != nil {
		os.Remove(tmpName)
		return fmt.Errorf("chmod temp: %w", err)
	}

	if err := os.Rename(tmpName, path); err != nil {
		os.Remove(tmpName)
		return fmt.Errorf("rename: %w", err)
	}

	if d, err := os.Open(dir); err == nil {
		_ = d.Sync()
		_ = d.Close()
	}
	return nil
}

func main() {
	cfg := map[string]interface{}{
		"port":        8080,
		"environment": "dev",
		"features":    []string{"metrics", "tracing"},
	}

	b, err := json.MarshalIndent(cfg, "", "  ")
	if err != nil {
		log.Fatalf("marshal: %v", err)
	}

	path := filepath.Join("config", "app.json")
	if err := writeFileAtomic(path, b, 0o644); err != nil {
		log.Fatalf("write atomic: %v", err)
	}
	log.Printf("wrote %s", path)
}

How It Works

Crash-safe JSON writer that uses temp files, fsync, and atomic rename so configuration files are never left half-written.

Marshals configuration to JSON, writes to a temp file, syncs it, fsyncs the directory when supported, then renames over the target; includes rollback cleanup and optional timestamped backups.

Key Concepts

  • 1Temp-file plus rename avoids partially written configs.
  • 2fsync on the file and parent directory increases durability.
  • 3Detailed errors help diagnose filesystem failures.

When to Use This Pattern

  • Writing service configuration or state snapshots safely.
  • CLI tools that must not corrupt user files on crash.
  • Embedded devices where power loss is common.

Best Practices

  • Keep temp files on the same filesystem as the target path.
  • Validate JSON before writing to avoid bad states.
  • Handle permission errors explicitly to guide users.
Go Version1.16+
Difficultyintermediate
Production ReadyYes
Lines of Code75