Documentation
¶
Overview ¶
Package ioutils provides a comprehensive suite of I/O utilities for Go applications, offering enhanced functionality for file operations, stream management, and data flow control.
Design Philosophy ¶
The ioutils package is built around three core principles:
1. Safety: All operations include proper error handling and resource cleanup 2. Flexibility: Components can be combined to create complex I/O workflows 3. Performance: Optimized for both throughput and memory efficiency
Architecture ¶
The package is organized into a root-level utility and several specialized subpackages:
Root Package (ioutils) ├── PathCheckCreate - File/directory creation with permission management │ ├── aggregator - Thread-safe write aggregator that buffers and serializes concurrent writes to a single writer function ├── bufferReadCloser - io.Closer wrappers for bytes.Buffer and bufio types ├── delim - Buffered reader for reading delimiter-separated data streams ├── fileDescriptor - Cross-platform utilities for managing file descriptor limits ├── ioprogress - Thread-safe I/O progress tracking wrappers for monitoring read and write operations ├── iowrapper - Flexible I/O wrapper enabling customization and interception of I/O operations ├── mapCloser - Thread-safe, context-aware manager for multiple io.Closer instances ├── maxstdio - Standard I/O redirection and capture ├── multi - Thread-safe, adaptive multi-writer extending io.MultiWriter with advanced features └── nopwritecloser - io.WriteCloser wrapper with no-op Close()
Each subpackage is designed to solve specific I/O challenges while maintaining compatibility with standard library interfaces.
Key Features ¶
Path Management:
- Atomic file/directory creation
- Automatic parent directory creation
- Permission validation and correction
- Type checking (file vs directory)
Stream Processing:
- Buffered write aggregation
- Progress tracking
- Delimiter-based parsing
- Multiple source/destination handling
Resource Management:
- Automatic cleanup via defer patterns
- Closer collection management
- File descriptor lifecycle control
Performance Characteristics ¶
The package is optimized for:
- Low memory overhead through buffer pooling
- Minimal allocations in hot paths
- Concurrent-safe operations where needed
- Efficient permission checking
Benchmarks show:
- PathCheckCreate: ~100ns for existing paths with correct permissions
- Aggregator: 10x throughput improvement for concurrent writes
- BufferReadCloser: 50% memory reduction vs bytes.Buffer for large streams
Limitations ¶
1. Platform Dependencies:
- Permission handling varies between Unix and Windows
- File descriptor operations may require OS-specific handling
- Some permissions (e.g., write-only on Windows) have limited support
2. Concurrency:
- PathCheckCreate is not atomic for the same path across goroutines
- Subpackages provide their own concurrency guarantees
3. Error Handling:
- Does not use panic; all errors must be explicitly handled
- Some operations may partially succeed before returning an error
Common Use Cases ¶
Application Initialization:
// Ensure application directories exist
if err := ioutils.PathCheckCreate(false, "/var/app/data", 0644, 0755); err != nil {
return fmt.Errorf("data dir: %w", err)
}
// Create log file with proper permissions
if err := ioutils.PathCheckCreate(true, "/var/log/app.log", 0644, 0755); err != nil {
return fmt.Errorf("log file: %w", err)
}
Configuration Management:
// Create config directory structure
configPaths := []string{
"/etc/app/config",
"/etc/app/config/plugins",
"/etc/app/config/templates",
}
for _, path := range configPaths {
if err := ioutils.PathCheckCreate(false, path, 0644, 0750); err != nil {
return err
}
}
Log File Rotation:
// Ensure log directory exists before rotation
logDir := filepath.Dir(logPath)
if err := ioutils.PathCheckCreate(false, logDir, 0644, 0755); err != nil {
return err
}
// Create new log file
if err := ioutils.PathCheckCreate(true, logPath, 0644, 0755); err != nil {
return err
}
See subpackage documentation for advanced I/O operations including write aggregation, progress tracking, and stream processing.
Thread Safety ¶
The root PathCheckCreate function is safe to call from multiple goroutines for different paths. However, concurrent calls for the same path may result in race conditions. Use external synchronization if needed.
Each subpackage documents its own concurrency guarantees.
Error Handling ¶
All functions return errors that can be inspected using standard error handling patterns:
if err := ioutils.PathCheckCreate(true, path, 0644, 0755); err != nil {
if errors.Is(err, os.ErrPermission) {
// Handle permission error
} else if errors.Is(err, os.ErrNotExist) {
// Handle path error
} else {
// Handle other errors
}
}
Errors are wrapped to provide context about the operation that failed.
Best Practices ¶
1. Always check errors - this package never uses panic 2. Use appropriate permissions - follow least privilege principle 3. Clean up resources - use defer for closers 4. Consider platform differences - test on target OS 5. Validate paths - ensure paths are absolute when needed
Migration Notes ¶
When migrating from os package functions:
// Before
if err := os.MkdirAll(path, 0755); err != nil {
return err
}
// After - also handles permission updates
if err := ioutils.PathCheckCreate(false, path, 0644, 0755); err != nil {
return err
}
The PathCheckCreate function combines mkdir, permission checking, and type validation in a single call.
Related Packages ¶
- os: Standard library file operations
- io: Standard library I/O interfaces
- filepath: Path manipulation utilities
- bufio: Buffered I/O operations
Subpackage Overview ¶
For detailed documentation, see individual subpackage docs:
- aggregator: Thread-safe write aggregator that buffers and serializes concurrent write operations to a single writer function
- bufferReadCloser: io.Closer wrappers for bytes.Buffer and bufio types with cleanup
- delim: Buffered reader for reading delimiter-separated data streams
- fileDescriptor: Cross-platform utilities for managing file descriptor limits in Go applications
- ioprogress: Thread-safe I/O progress tracking wrappers for monitoring read and write operations in real-time through customizable callbacks
- iowrapper: Flexible I/O wrapper that enables customization and interception of read, write, seek, and close operations on any underlying I/O object
- mapCloser: Thread-safe, context-aware manager for multiple io.Closer instances
- maxstdio: Standard I/O stream redirection and capture
- multi: Thread-safe, adaptive multi-writer that extends Go's standard io.MultiWriter with advanced features
- nopwritecloser: No-op Close() wrapper for io.Writer (delegates Write unchanged)
Index ¶
Examples ¶
- PathCheckCreate (ApplicationInit)
- PathCheckCreate (BasicDirectory)
- PathCheckCreate (BasicFile)
- PathCheckCreate (ErrorHandling)
- PathCheckCreate (Idempotent)
- PathCheckCreate (LogRotation)
- PathCheckCreate (MultipleFiles)
- PathCheckCreate (NestedPath)
- PathCheckCreate (PermissionUpdate)
- PathCheckCreate (SecureConfig)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func PathCheckCreate ¶ added in v1.7.0
PathCheckCreate ensures a file or directory exists at the given path with the correct permissions.
This function performs the following operations:
- Checks if the path exists
- Creates the path if it doesn't exist (file or directory based on isFile parameter)
- Creates parent directories if needed
- Validates that existing paths match the expected type (file vs directory)
- Updates permissions if they don't match the specified values
Parameters:
- isFile: true to ensure path is a file, false for a directory
- path: the filesystem path to check/create (should be absolute for predictable behavior)
- permFile: permissions to apply to files (e.g., 0644 for rw-r--r--)
- permDir: permissions to apply to directories (e.g., 0755 for rwxr-xr-x)
Returns:
- error: nil on success, or an error if:
- The path exists but is the wrong type (file when directory expected, or vice versa)
- Parent directory creation fails
- Permission updates fail
- File/directory creation fails
- Path is empty or invalid
Behavior:
- If path exists as expected type: validates and updates permissions if needed
- If path doesn't exist: creates it with specified permissions
- If parent directories don't exist: creates them with permDir permissions
- If path exists as wrong type: returns an error without modification
Implementation Details:
- Uses os.OpenRoot for atomic file creation on supported systems
- Permission comparison checks full mode, not just permission bits
- Recursively creates parent directories as needed
- Attempts to set permissions even if initial mode is close
Example:
// Ensure config directory exists with 0755 permissions err := PathCheckCreate(false, "/etc/app/config", 0644, 0755) // Ensure log file exists with 0644 permissions err := PathCheckCreate(true, "/var/log/app.log", 0644, 0755)
Thread Safety:
This function is safe to call concurrently for different paths, but concurrent calls for the same path may result in race conditions. Use external synchronization if the same path may be accessed concurrently.
Example (ApplicationInit) ¶
ExamplePathCheckCreate_applicationInit demonstrates a realistic use case: initializing application directories during startup.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/nabbar/golib/ioutils"
)
func main() {
tmpDir := os.TempDir()
appRoot := filepath.Join(tmpDir, "myapp")
// Define application directory structure
dirs := []string{
filepath.Join(appRoot, "data"),
filepath.Join(appRoot, "config"),
filepath.Join(appRoot, "logs"),
filepath.Join(appRoot, "cache"),
filepath.Join(appRoot, "tmp"),
}
// Create all directories
for _, dir := range dirs {
if err := ioutils.PathCheckCreate(false, dir, 0644, 0755); err != nil {
log.Fatalf("Failed to create %s: %v", dir, err)
}
}
// Create important files
files := map[string]os.FileMode{
filepath.Join(appRoot, "config", "app.conf"): 0640, // Restrictive config
filepath.Join(appRoot, "logs", "app.log"): 0644, // Standard log
filepath.Join(appRoot, "data", "state.json"): 0644, // Data file
}
for file, perm := range files {
if err := ioutils.PathCheckCreate(true, file, perm, 0755); err != nil {
log.Fatalf("Failed to create %s: %v", file, err)
}
}
// Clean up
defer os.RemoveAll(appRoot)
// Verify structure
allExist := true
for _, dir := range dirs {
if _, err := os.Stat(dir); err != nil {
allExist = false
break
}
}
for file := range files {
if _, err := os.Stat(file); err != nil {
allExist = false
break
}
}
if allExist {
fmt.Println("Application structure initialized")
}
}
Output: Application structure initialized
Example (BasicDirectory) ¶
ExamplePathCheckCreate_basicDirectory demonstrates the simplest use case: creating a single directory with standard permissions.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/nabbar/golib/ioutils"
)
func main() {
tmpDir := os.TempDir()
dirPath := filepath.Join(tmpDir, "example_dir")
// Create a directory with standard permissions
// isFile=false means we want a directory
// 0644 is file permission (not used for directories)
// 0755 is directory permission (rwxr-xr-x)
err := ioutils.PathCheckCreate(false, dirPath, 0644, 0755)
if err != nil {
log.Fatalf("Failed to create directory: %v", err)
}
// Clean up
defer os.RemoveAll(dirPath)
// Verify it exists
if info, err := os.Stat(dirPath); err == nil && info.IsDir() {
fmt.Println("Directory created successfully")
}
}
Output: Directory created successfully
Example (BasicFile) ¶
ExamplePathCheckCreate_basicFile demonstrates creating a single file with appropriate permissions.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/nabbar/golib/ioutils"
)
func main() {
tmpDir := os.TempDir()
filePath := filepath.Join(tmpDir, "example_file.txt")
// Create a file with standard permissions
// isFile=true means we want a file
// 0644 is file permission (rw-r--r--)
// 0755 is directory permission for parent dirs
err := ioutils.PathCheckCreate(true, filePath, 0644, 0755)
if err != nil {
log.Fatalf("Failed to create file: %v", err)
}
// Clean up
defer os.Remove(filePath)
// Verify it exists
if info, err := os.Stat(filePath); err == nil && !info.IsDir() {
fmt.Println("File created successfully")
}
}
Output: File created successfully
Example (ErrorHandling) ¶
ExamplePathCheckCreate_errorHandling demonstrates proper error handling when path conflicts occur.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/nabbar/golib/ioutils"
)
func main() {
tmpDir := os.TempDir()
path := filepath.Join(tmpDir, "example_conflict")
// Create as directory first
if err := ioutils.PathCheckCreate(false, path, 0644, 0755); err != nil {
log.Fatalf("Failed to create directory: %v", err)
}
// Clean up
defer os.RemoveAll(path)
// Try to create as file - should fail
err := ioutils.PathCheckCreate(true, path, 0644, 0755)
if err != nil {
fmt.Println("Expected error: path exists as directory")
}
}
Output: Expected error: path exists as directory
Example (Idempotent) ¶
ExamplePathCheckCreate_idempotent demonstrates that PathCheckCreate can be called multiple times safely.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/nabbar/golib/ioutils"
)
func main() {
tmpDir := os.TempDir()
dirPath := filepath.Join(tmpDir, "example_idempotent")
// Clean up
defer os.RemoveAll(dirPath)
// Call multiple times - should not error
for i := 0; i < 3; i++ {
err := ioutils.PathCheckCreate(false, dirPath, 0644, 0755)
if err != nil {
log.Fatalf("Failed on iteration %d: %v", i, err)
}
}
fmt.Println("Idempotent calls successful")
}
Output: Idempotent calls successful
Example (LogRotation) ¶
ExamplePathCheckCreate_logRotation demonstrates using PathCheckCreate for log file rotation scenarios.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/nabbar/golib/ioutils"
)
func main() {
tmpDir := os.TempDir()
logDir := filepath.Join(tmpDir, "example_logs")
currentLog := filepath.Join(logDir, "app.log")
// Ensure log directory exists
if err := ioutils.PathCheckCreate(false, logDir, 0644, 0755); err != nil {
log.Fatalf("Failed to create log directory: %v", err)
}
// Create or verify current log file
if err := ioutils.PathCheckCreate(true, currentLog, 0644, 0755); err != nil {
log.Fatalf("Failed to create log file: %v", err)
}
// Simulate rotation - archive old log
archivedLog := filepath.Join(logDir, "app.log.1")
if err := os.Rename(currentLog, archivedLog); err == nil {
// Create new log file
if err := ioutils.PathCheckCreate(true, currentLog, 0644, 0755); err != nil {
log.Fatalf("Failed to create new log file: %v", err)
}
}
// Clean up
defer os.RemoveAll(logDir)
// Verify both files exist
if _, err := os.Stat(currentLog); err == nil {
if _, err := os.Stat(archivedLog); err == nil {
fmt.Println("Log rotation successful")
}
}
}
Output: Log rotation successful
Example (MultipleFiles) ¶
ExamplePathCheckCreate_multipleFiles demonstrates creating multiple files efficiently in a batch operation.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/nabbar/golib/ioutils"
)
func main() {
tmpDir := os.TempDir()
baseDir := filepath.Join(tmpDir, "example_batch")
// Create base directory
if err := ioutils.PathCheckCreate(false, baseDir, 0644, 0755); err != nil {
log.Fatalf("Failed to create base directory: %v", err)
}
// Clean up
defer os.RemoveAll(baseDir)
// Create multiple files with different permissions
files := []struct {
path string
perm os.FileMode
}{
{filepath.Join(baseDir, "public.txt"), 0644},
{filepath.Join(baseDir, "private.txt"), 0600},
{filepath.Join(baseDir, "executable.sh"), 0755},
{filepath.Join(baseDir, "data", "nested.dat"), 0644},
}
for _, f := range files {
if err := ioutils.PathCheckCreate(true, f.path, f.perm, 0755); err != nil {
log.Fatalf("Failed to create %s: %v", f.path, err)
}
}
// Verify all created
allCreated := true
for _, f := range files {
if _, err := os.Stat(f.path); err != nil {
allCreated = false
break
}
}
if allCreated {
fmt.Println("Multiple files created successfully")
}
}
Output: Multiple files created successfully
Example (NestedPath) ¶
ExamplePathCheckCreate_nestedPath demonstrates creating a file with automatic parent directory creation.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/nabbar/golib/ioutils"
)
func main() {
tmpDir := os.TempDir()
filePath := filepath.Join(tmpDir, "example_nested", "deep", "path", "file.log")
// Create file in nested directories - parent dirs created automatically
err := ioutils.PathCheckCreate(true, filePath, 0644, 0755)
if err != nil {
log.Fatalf("Failed to create nested file: %v", err)
}
// Clean up
defer os.RemoveAll(filepath.Join(tmpDir, "example_nested"))
// Verify the entire path exists
if _, err := os.Stat(filePath); err == nil {
fmt.Println("Nested file structure created successfully")
}
}
Output: Nested file structure created successfully
Example (PermissionUpdate) ¶
ExamplePathCheckCreate_permissionUpdate demonstrates how PathCheckCreate updates permissions if the path already exists.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/nabbar/golib/ioutils"
)
func main() {
tmpDir := os.TempDir()
filePath := filepath.Join(tmpDir, "example_perm_update.txt")
// Create file with restrictive permissions
err := ioutils.PathCheckCreate(true, filePath, 0600, 0755)
if err != nil {
log.Fatalf("Failed to create file: %v", err)
}
// Clean up
defer os.Remove(filePath)
// Update permissions by calling again with different perms
err = ioutils.PathCheckCreate(true, filePath, 0644, 0755)
if err != nil {
log.Fatalf("Failed to update permissions: %v", err)
}
// Verify new permissions
if info, err := os.Stat(filePath); err == nil {
if info.Mode()&0777 == 0644 {
fmt.Println("Permissions updated successfully")
}
}
}
Output: Permissions updated successfully
Example (SecureConfig) ¶
ExamplePathCheckCreate_secureConfig demonstrates creating configuration files with restrictive permissions for security.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/nabbar/golib/ioutils"
)
func main() {
tmpDir := os.TempDir()
configDir := filepath.Join(tmpDir, "example_secure_config")
secretsFile := filepath.Join(configDir, "secrets.conf")
// Create config directory with standard permissions
if err := ioutils.PathCheckCreate(false, configDir, 0644, 0750); err != nil {
log.Fatalf("Failed to create config directory: %v", err)
}
// Create secrets file with restrictive permissions (owner read/write only)
if err := ioutils.PathCheckCreate(true, secretsFile, 0600, 0750); err != nil {
log.Fatalf("Failed to create secrets file: %v", err)
}
// Clean up
defer os.RemoveAll(configDir)
// Verify restrictive permissions
if info, err := os.Stat(secretsFile); err == nil {
if info.Mode()&0777 == 0600 {
fmt.Println("Secure configuration file created")
}
}
}
Output: Secure configuration file created
Types ¶
This section is empty.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package aggregator provides a thread-safe write aggregator that buffers and serializes concurrent write operations to a single writer function.
|
Package aggregator provides a thread-safe write aggregator that buffers and serializes concurrent write operations to a single writer function. |
|
Package bufferReadCloser provides lightweight wrappers around Go's standard buffered I/O types (bytes.Buffer, bufio.Reader, bufio.Writer, bufio.ReadWriter) that add io.Closer support with automatic resource cleanup and custom close callbacks.
|
Package bufferReadCloser provides lightweight wrappers around Go's standard buffered I/O types (bytes.Buffer, bufio.Reader, bufio.Writer, bufio.ReadWriter) that add io.Closer support with automatic resource cleanup and custom close callbacks. |
|
Package delim provides a buffered reader for reading delimiter-separated data streams.
|
Package delim provides a buffered reader for reading delimiter-separated data streams. |
|
Package fileDescriptor provides cross-platform utilities for managing file descriptor limits in Go applications.
|
Package fileDescriptor provides cross-platform utilities for managing file descriptor limits in Go applications. |
|
Package ioprogress provides thread-safe I/O progress tracking wrappers for monitoring read and write operations in real-time through customizable callbacks.
|
Package ioprogress provides thread-safe I/O progress tracking wrappers for monitoring read and write operations in real-time through customizable callbacks. |
|
Package iowrapper provides a flexible I/O wrapper that enables customization and interception of read, write, seek, and close operations on any underlying I/O object without modifying its implementation.
|
Package iowrapper provides a flexible I/O wrapper that enables customization and interception of read, write, seek, and close operations on any underlying I/O object without modifying its implementation. |
|
Package mapCloser provides a thread-safe, context-aware manager for multiple io.Closer instances.
|
Package mapCloser provides a thread-safe, context-aware manager for multiple io.Closer instances. |
|
Package maxstdio provides Windows-specific functions for managing the maximum number of simultaneously open file handles (stdio streams) in a process through Windows CRT.
|
Package maxstdio provides Windows-specific functions for managing the maximum number of simultaneously open file handles (stdio streams) in a process through Windows CRT. |
|
Package multi provides a thread-safe, adaptive multi-writer that extends Go's standard io.MultiWriter with advanced features including adaptive sequential/parallel execution, latency monitoring, and comprehensive concurrency support.
|
Package multi provides a thread-safe, adaptive multi-writer that extends Go's standard io.MultiWriter with advanced features including adaptive sequential/parallel execution, latency monitoring, and comprehensive concurrency support. |
|
Package nopwritecloser provides a wrapper that implements io.WriteCloser for an io.Writer by adding a no-op Close() method.
|
Package nopwritecloser provides a wrapper that implements io.WriteCloser for an io.Writer by adding a no-op Close() method. |