// PicoClaw - Ultra-lightweight personal AI agent // Inspired by and based on nanobot: https://github.com/HKUDS/nanobot // License: MIT // // Copyright (c) 2026 PicoClaw contributors package utils import ( "fmt" "os" "path/filepath" ) // WriteFileAtomic atomically writes data to a file using a temp file + rename pattern. // // This guarantees that the target file is either: // - Completely written with the new data // - Unchanged (if write fails or power loss during write) // // The function: // 1. Creates a temp file in the same directory // 2. Writes data to temp file // 3. Syncs to disk (critical for SD cards/flash storage) // 4. Sets file permissions // 5. Atomically renames temp file to target path // // Parameters: // - path: Target file path // - data: Data to write // - perm: File permission mode (e.g., 0o600 for secure, 0o644 for readable) // // Returns: // - Error if any step fails, nil on success // // Example: // // // Secure config file (owner read/write only) // err := utils.WriteFileAtomic("config.json", data, 0o600) // // // Public readable file // err := utils.WriteFileAtomic("public.txt", data, 0o644) func WriteFileAtomic(path string, data []byte, perm os.FileMode) error { dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0o755); err != nil { return fmt.Errorf("failed to create directory: %w", err) } // Create temp file in the same directory (ensures atomic rename works) tmpFile, err := os.CreateTemp(dir, ".tmp-*.tmp") if err != nil { return fmt.Errorf("failed to create temp file: %w", err) } tmpPath := tmpFile.Name() // Cleanup on error: ensure temp file is removed if anything fails cleanup := true defer func() { if cleanup { _ = os.Remove(tmpPath) } }() // Write data to temp file if _, err := tmpFile.Write(data); err != nil { tmpFile.Close() return fmt.Errorf("failed to write temp file: %w", err) } // CRITICAL: Force sync to storage medium before rename. // This ensures data is physically written to disk, not just cached. // Essential for SD cards, eMMC, and other flash storage on edge devices. if err := tmpFile.Sync(); err != nil { tmpFile.Close() return fmt.Errorf("failed to sync temp file: %w", err) } // Set file permissions if err := tmpFile.Chmod(perm); err != nil { tmpFile.Close() return fmt.Errorf("failed to set permissions: %w", err) } // Close file before rename if err := tmpFile.Close(); err != nil { return fmt.Errorf("failed to close temp file: %w", err) } // Atomic rename: temp file becomes the target if err := os.Rename(tmpPath, path); err != nil { return fmt.Errorf("failed to rename temp file: %w", err) } // Success: skip cleanup cleanup = false return nil }