Documentation
¶
Overview ¶
Package unionfs provides a layered filesystem implementation for Go with Docker-style overlay capabilities and copy-on-write support.
Overview ¶
UnionFS enables the composition of multiple filesystem layers into a single unified view. This is similar to how Docker and other container systems build images through layering, where each layer can add, modify, or delete files from lower layers.
Key Features ¶
- Multiple filesystem layer composition
- Copy-on-write (CoW) semantics for modifications
- Whiteout support for deletions across layers
- Read-only base layers with writable overlay
- Efficient file lookup through layer precedence
- Full afero.Fs interface compatibility
Architecture ¶
The UnionFS maintains a stack of layers, ordered from top (highest precedence) to bottom. When a file is accessed, layers are searched from top to bottom, and the first match wins. Write operations always go to the topmost writable layer.
Basic Usage ¶
package main
import (
"github.com/absfs/unionfs"
"github.com/spf13/afero"
)
func main() {
// Create base layer (read-only)
baseLayer := afero.NewOsFs()
// Create overlay layer (writable)
overlayLayer := afero.NewMemMapFs()
// Create union filesystem
ufs := unionfs.New(
unionfs.WithWritableLayer(overlayLayer),
unionfs.WithReadOnlyLayer(baseLayer),
)
// Reads fall through to base layer if not in overlay
data, err := afero.ReadFile(ufs, "/etc/config.yml")
// Writes go to overlay layer
err = afero.WriteFile(ufs, "/etc/custom.yml", []byte("key: value"), 0644)
// Modifications trigger copy-on-write
file, err := ufs.OpenFile("/etc/config.yml", os.O_RDWR, 0)
file.Write([]byte("modified")) // Copies to overlay first
}
Multiple Layers ¶
You can stack multiple read-only layers with a single writable layer on top:
ufs := unionfs.New(
unionfs.WithWritableLayer(overlayLayer), // Top: writable
unionfs.WithReadOnlyLayer(configLayer), // Middle: configs
unionfs.WithReadOnlyLayer(appLayer), // Middle: app files
unionfs.WithReadOnlyLayer(systemLayer), // Bottom: system files
)
Layers are searched in order, so files in higher layers take precedence over lower layers.
Copy-on-Write ¶
When you modify a file that exists in a read-only lower layer, the file is automatically copied to the writable layer before modification. This ensures the original file in the lower layer remains unchanged:
// File exists in baseLayer
afero.WriteFile(baseLayer, "/config.txt", []byte("original"), 0644)
// Create union with overlay
ufs := unionfs.New(
unionfs.WithWritableLayer(overlay),
unionfs.WithReadOnlyLayer(baseLayer),
)
// Modify file - triggers copy-on-write
afero.WriteFile(ufs, "/config.txt", []byte("modified"), 0644)
// Read from union gets modified version
data, _ := afero.ReadFile(ufs, "/config.txt") // "modified"
// Base layer still has original
data, _ = afero.ReadFile(baseLayer, "/config.txt") // "original"
Whiteout Files ¶
When you delete a file that exists in a lower layer, a whiteout marker is created in the writable layer. This is a special file with the prefix ".wh." that marks the file as deleted:
// File exists in base layer
afero.WriteFile(baseLayer, "/file.txt", []byte("content"), 0644)
// Delete through union
ufs.Remove("/file.txt")
// Creates whiteout marker at /.wh.file.txt in overlay
// File no longer visible through union
_, err := ufs.Stat("/file.txt") // os.ErrNotExist
// But still exists in base layer
_, err = baseLayer.Stat("/file.txt") // nil error
Whiteout files follow the AUFS/Docker convention using the ".wh." prefix.
Directory Merging ¶
When reading a directory, UnionFS merges the contents from all layers:
// Layer 0 has /dir/file1.txt // Layer 1 has /dir/file2.txt // Layer 2 has /dir/file3.txt entries, _ := afero.ReadDir(ufs, "/dir") // Returns all three files: file1.txt, file2.txt, file3.txt
Whiteouts are respected during directory merging, so deleted files don't appear in listings.
Use Cases ¶
Configuration Management:
// Base configuration + environment-specific overrides
ufs := unionfs.New(
unionfs.WithWritableLayer(runtimeConfig),
unionfs.WithReadOnlyLayer(envConfig),
unionfs.WithReadOnlyLayer(defaultConfig),
)
Testing with Fixtures:
// Immutable test fixtures + test-specific modifications
ufs := unionfs.New(
unionfs.WithWritableLayer(testOverlay),
unionfs.WithReadOnlyLayer(fixtures),
)
Container Filesystems:
// Layer base OS, dependencies, app code, and runtime changes
ufs := unionfs.New(
unionfs.WithWritableLayer(runtime),
unionfs.WithReadOnlyLayer(appCode),
unionfs.WithReadOnlyLayer(dependencies),
unionfs.WithReadOnlyLayer(baseOS),
)
Performance Considerations ¶
- File lookups traverse layers from top to bottom, so fewer layers = better performance
- Copy-on-write operations involve copying the entire file, which can be expensive for large files
- Directory merging may be slower for directories with many files across multiple layers
- Consider using stat caching for frequently accessed files (see WithStatCache option)
Thread Safety ¶
UnionFS uses read-write locks to ensure thread-safe access to the layer stack. Multiple goroutines can safely read from the filesystem concurrently, while write operations are properly synchronized.
absfs Ecosystem Integration ¶
UnionFS is part of the absfs ecosystem and implements both afero.Fs and absfs.Filer interfaces, enabling seamless composition with other absfs packages.
Get an absfs.FileSystem view:
ufs := unionfs.New(
unionfs.WithWritableLayer(overlay),
unionfs.WithReadOnlyLayer(base),
)
// Get absfs.FileSystem with working directory support
fs := ufs.FileSystem()
fs.Chdir("/app")
file, err := fs.Open("config.yml") // Relative to /app
Compose with other absfs packages:
// UnionFS provides: multi-layer composition + copy-on-write union := unionfs.New(...).FileSystem() // Wrap with other absfs packages for additional functionality: // - cachefs: Add caching layer (performance) // - rofs: Make read-only (safety) // - metricsfs: Add Prometheus metrics (observability) // - retryfs: Add retry logic (reliability) // - permfs: Add access control (security)
This demonstrates the absfs philosophy: each package has a single responsibility, and complex behaviors emerge from simple composition.
Compatibility ¶
UnionFS implements both:
- afero.Fs interface - for compatibility with afero-based code
- absfs.Filer interface - for integration with the absfs ecosystem
Use ExtendFiler or FileSystem() to get the full absfs.FileSystem interface with convenience methods and working directory support.
Limitations ¶
- Only one writable layer is supported (topmost layer)
- Hard links are not supported across layers
- Symlink resolution is currently basic (advanced cross-layer symlinks not yet implemented)
- File locking behavior across layers is filesystem-dependent
Package unionfs provides a layered filesystem implementation with Docker-style overlay capabilities and copy-on-write support.
Index ¶
- Constants
- Variables
- type Cache
- type CacheStats
- type Layer
- type Option
- type UnionFS
- func (ufs *UnionFS) AsAbsFS() absfs.FileSystemdeprecated
- func (ufs *UnionFS) CacheStats() CacheStats
- func (ufs *UnionFS) Chmod(name string, mode os.FileMode) error
- func (ufs *UnionFS) Chown(name string, uid, gid int) error
- func (ufs *UnionFS) Chtimes(name string, atime, mtime time.Time) error
- func (ufs *UnionFS) ClearCache()
- func (ufs *UnionFS) Create(name string) (absfs.File, error)
- func (ufs *UnionFS) FileSystem() absfs.FileSystem
- func (ufs *UnionFS) InvalidateCache(path string)
- func (ufs *UnionFS) InvalidateCacheTree(pathPrefix string)
- func (ufs *UnionFS) Lchown(name string, uid, gid int) error
- func (ufs *UnionFS) Lstat(name string) (os.FileInfo, error)
- func (ufs *UnionFS) LstatIfPossible(name string) (os.FileInfo, bool, error)
- func (ufs *UnionFS) Mkdir(name string, perm os.FileMode) error
- func (ufs *UnionFS) MkdirAll(name string, perm os.FileMode) error
- func (ufs *UnionFS) Name() string
- func (ufs *UnionFS) Open(name string) (absfs.File, error)
- func (ufs *UnionFS) OpenFile(name string, flag int, perm os.FileMode) (absfs.File, error)
- func (ufs *UnionFS) ReadDir(name string) ([]fs.DirEntry, error)
- func (ufs *UnionFS) ReadFile(name string) ([]byte, error)
- func (ufs *UnionFS) Readlink(name string) (string, error)
- func (ufs *UnionFS) ReadlinkIfPossible(name string) (string, error)
- func (ufs *UnionFS) Remove(name string) error
- func (ufs *UnionFS) RemoveAll(name string) error
- func (ufs *UnionFS) Rename(oldname, newname string) error
- func (ufs *UnionFS) Stat(name string) (os.FileInfo, error)
- func (ufs *UnionFS) Sub(dir string) (fs.FS, error)
- func (ufs *UnionFS) Symlink(oldname, newname string) error
- func (ufs *UnionFS) SymlinkFileSystem() absfs.SymlinkFileSystem
- func (ufs *UnionFS) SymlinkIfPossible(oldname, newname string) error
Constants ¶
const ( // WhiteoutPrefix is the prefix for whiteout files (AUFS/Docker style) WhiteoutPrefix = ".wh." // OpaqueWhiteout marks a directory as opaque (hides all lower layer contents) OpaqueWhiteout = ".wh.__dir_opaque" )
Variables ¶
var ( // ErrNoWritableLayer is returned when a write operation is attempted but no writable layer exists ErrNoWritableLayer = errors.New("no writable layer configured") // ErrReadOnlyLayer is returned when attempting to write to a read-only layer ErrReadOnlyLayer = errors.New("layer is read-only") )
Functions ¶
This section is empty.
Types ¶
type Cache ¶
type Cache struct {
// contains filtered or unexported fields
}
Cache provides caching capabilities for filesystem operations
type CacheStats ¶
type CacheStats struct {
Enabled bool
StatCacheSize int
NegativeCacheSize int
MaxEntries int
StatTTL time.Duration
NegativeTTL time.Duration
}
CacheStats contains cache statistics
type Layer ¶
type Layer struct {
// contains filtered or unexported fields
}
Layer represents a single filesystem layer with metadata
type Option ¶
type Option func(*UnionFS)
Option is a functional option for configuring UnionFS
func WithCacheConfig ¶
WithCacheConfig enables caching with custom configuration
func WithCopyBufferSize ¶
WithCopyBufferSize sets the buffer size for copy-on-write operations
func WithReadOnlyLayer ¶
func WithReadOnlyLayer(fs absfs.FileSystem) Option
WithReadOnlyLayer adds a read-only layer to the layer stack Read-only layers are added in order after the writable layer
func WithStatCache ¶
WithStatCache enables stat caching with the specified TTL
func WithWritableLayer ¶
func WithWritableLayer(fs absfs.FileSystem) Option
WithWritableLayer adds a writable layer at the top of the layer stack
type UnionFS ¶
type UnionFS struct {
// contains filtered or unexported fields
}
UnionFS implements a union filesystem with multiple layers
func (*UnionFS) AsAbsFS
deprecated
func (ufs *UnionFS) AsAbsFS() absfs.FileSystem
AsAbsFS returns an absfs.FileSystem adapter for this UnionFS. This is an alias for FileSystem() provided for clarity.
Deprecated: Use FileSystem() instead.
func (*UnionFS) CacheStats ¶
func (ufs *UnionFS) CacheStats() CacheStats
CacheStats returns cache statistics
func (*UnionFS) FileSystem ¶
func (ufs *UnionFS) FileSystem() absfs.FileSystem
FileSystem returns an absfs.FileSystem view of this UnionFS. The returned FileSystem maintains its own working directory state and provides the full absfs.FileSystem interface including convenience methods like Open, Create, MkdirAll, RemoveAll, and Truncate.
This enables seamless integration with the absfs ecosystem.
Example:
ufs := unionfs.New(
unionfs.WithWritableLayer(overlay),
unionfs.WithReadOnlyLayer(base),
)
// Use as absfs.FileSystem
fs := ufs.FileSystem()
fs.Chdir("/app")
file, err := fs.Open("config.yml") // Uses current working directory
func (*UnionFS) InvalidateCache ¶
InvalidateCache removes a path from the cache
func (*UnionFS) InvalidateCacheTree ¶
InvalidateCacheTree removes all cache entries under a path prefix
func (*UnionFS) LstatIfPossible ¶
LstatIfPossible returns file info without following symlinks if the filesystem supports it
func (*UnionFS) ReadDir ¶
ReadDir reads the named directory and returns all its directory entries, merging results from all layers. Entries from upper layers take precedence, and whiteouts are respected.
func (*UnionFS) ReadFile ¶
ReadFile reads the named file and returns its contents. It reads from the first layer (highest precedence) that contains the file.
func (*UnionFS) ReadlinkIfPossible ¶
ReadlinkIfPossible returns the destination of a symlink if supported
func (*UnionFS) Sub ¶
Sub returns a filesystem rooted at the given directory, creating a sub-view across all layers.
func (*UnionFS) SymlinkFileSystem ¶
func (ufs *UnionFS) SymlinkFileSystem() absfs.SymlinkFileSystem
SymlinkFileSystem returns an absfs.SymlinkFileSystem view of this UnionFS. The returned SymlinkFileSystem maintains its own working directory state and provides the full absfs.SymlinkFileSystem interface including symlink operations (Symlink, Readlink, Lstat, Lchown).
This enables seamless integration with the absfs ecosystem when symlink support is required.
func (*UnionFS) SymlinkIfPossible ¶
SymlinkIfPossible creates a symbolic link if supported
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
absfs-composition
command
Package main demonstrates composing UnionFS with other absfs ecosystem packages to build complex, layered filesystem behaviors through simple composition.
|
Package main demonstrates composing UnionFS with other absfs ecosystem packages to build complex, layered filesystem behaviors through simple composition. |