Files
picoclaw/pkg/utils/file.go
T

98 lines
2.7 KiB
Go

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