Comprehensive File I/O Operations
Wraps common file operations so writes are crash-safe, reads verify state, and temporary files are cleaned up automatically.
main.go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
package main
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
)
// FileManager handles common file operations with robust error handling
type FileManager struct {
baseDir string
}
// NewFileManager creates a new FileManager with a base directory (creates it if missing)
func NewFileManager(baseDir string) (*FileManager, error) {
// Create base directory if it doesn't exist
if err := os.MkdirAll(baseDir, 0755); err != nil {
return nil, fmt.Errorf("failed to create base directory: %w", err)
}
return &FileManager{baseDir: baseDir}, nil
}
// WriteFile writes content to a file with atomic write pattern (prevents partial writes)
func (fm *FileManager) WriteFile(filename string, content []byte) error {
filePath := filepath.Join(fm.baseDir, filename)
// Create temp file in the same directory to avoid cross-filesystem issues
tempFile, err := os.CreateTemp(fm.baseDir, fmt.Sprintf("%s.tmp", filename))
if err != nil {
return fmt.Errorf("failed to create temp file: %w", err)
}
tempFileName := tempFile.Name()
// Ensure temp file is cleaned up if something goes wrong
defer func() {
if err != nil {
os.Remove(tempFileName) // Clean up temp file on error
}
tempFile.Close()
}()
// Write content to temp file
if _, err := tempFile.Write(content); err != nil {
return fmt.Errorf("failed to write to temp file: %w", err)
}
// Sync to disk to ensure data is written
if err := tempFile.Sync(); err != nil {
return fmt.Errorf("failed to sync temp file: %w", err)
}
// Atomically rename temp file to target file (safe write)
if err := os.Rename(tempFileName, filePath); err != nil {
return fmt.Errorf("failed to rename temp file: %w", err)
}
// Set proper file permissions
if err := os.Chmod(filePath, 0644); err != nil {
return fmt.Errorf("failed to set file permissions: %w", err)
}
return nil
}
// ReadFile reads content from a file with existence check
func (fm *FileManager) ReadFile(filename string) ([]byte, error) {
filePath := filepath.Join(fm.baseDir, filename)
// Check if file exists and is a regular file
fileInfo, err := os.Stat(filePath)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return nil, fmt.Errorf("file does not exist: %s", filePath)
}
return nil, fmt.Errorf("failed to stat file: %w", err)
}
if fileInfo.IsDir() {
return nil, fmt.Errorf("path is a directory, not a file: %s", filePath)
}
// Read entire file content
content, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("failed to read file: %w", err)
}
return content, nil
}
// DeleteFile safely deletes a file with existence check
func (fm *FileManager) DeleteFile(filename string) error {
filePath := filepath.Join(fm.baseDir, filename)
err := os.Remove(filePath)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("cannot delete: file does not exist: %s", filePath)
}
return fmt.Errorf("failed to delete file: %w", err)
}
return nil
}
func main() {
// Initialize file manager with a "data" directory
fm, err := NewFileManager("data")
if err != nil {
fmt.Printf("Failed to create FileManager: %v\n", err)
return
}
// Write to file
testContent := []byte("Hello, Go File I/O!\nThis is a test file with multiple lines.")
if err := fm.WriteFile("test.txt", testContent); err != nil {
fmt.Printf("Write failed: %v\n", err)
return
}
fmt.Println("Successfully wrote to test.txt")
// Read from file
content, err := fm.ReadFile("test.txt")
if err != nil {
fmt.Printf("Read failed: %v\n", err)
return
}
fmt.Printf("File content:\n%s\n", content)
// Delete file
if err := fm.DeleteFile("test.txt"); err != nil {
fmt.Printf("Delete failed: %v\n", err)
return
}
fmt.Println("Successfully deleted test.txt")
}How It Works
Wraps common file operations so writes are crash-safe, reads verify state, and temporary files are cleaned up automatically.
Constructor ensures the base directory exists; WriteFile creates a temp file in the same folder, writes bytes, syncs, renames atomically, and sets mode; ReadFile stats the path to reject missing or directory targets before reading; DeleteFile removes files and surfaces fs.ErrNotExist distinctly.
Key Concepts
- 1MkdirAll guarantees the working directory exists before any I/O.
- 2Atomic write path (temp file + Sync + Rename) prevents partial files.
- 3Deferred cleanup removes temp artifacts on error and always closes descriptors.
- 4Error wrapping adds context for each failed operation.
When to Use This Pattern
- Persisting configs or small datasets safely on disk.
- CLI tools that read/write user files with predictable error messages.
- Atomic cache or state snapshots for long-running services.
- Test helpers that need clean temporary storage semantics.
Best Practices
- Keep temp files on the same filesystem as the target to preserve atomic renames.
- Call Sync before rename when durability matters.
- Check for fs.ErrNotExist separately to inform callers correctly.
- Set permissions explicitly instead of relying on defaults.
Go Version1.16+
Difficultyintermediate
Production ReadyYes
Lines of Code139