package postfix import ( "fmt" "os" "path/filepath" ) // writeFileAtomic writes data to path via a temp file in the same directory // followed by a rename, so a concurrent Postfix reload only ever sees the // complete old or new map, never a partial write (spec 7.6.4). It mirrors the // same primitive used for the OpenDKIM tables. func writeFileAtomic(path string, data []byte, perm os.FileMode) error { dir := filepath.Dir(path) tmp, err := os.CreateTemp(dir, ".tmp-*") if err != nil { return fmt.Errorf("create temp in %s: %w", dir, err) } tmpName := tmp.Name() cleanup := true defer func() { if cleanup { _ = os.Remove(tmpName) } }() if _, err := tmp.Write(data); err != nil { tmp.Close() return fmt.Errorf("write temp %s: %w", tmpName, err) } if err := tmp.Chmod(perm); err != nil { tmp.Close() return fmt.Errorf("chmod temp %s: %w", tmpName, err) } if err := tmp.Sync(); err != nil { tmp.Close() return fmt.Errorf("sync temp %s: %w", tmpName, err) } if err := tmp.Close(); err != nil { return fmt.Errorf("close temp %s: %w", tmpName, err) } if err := os.Rename(tmpName, path); err != nil { return fmt.Errorf("rename %s -> %s: %w", tmpName, path, err) } cleanup = false return nil }