fsx

package module
v0.0.0-...-c199c2d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 22, 2026 License: MIT Imports: 11 Imported by: 1

README

fsx

fsx is a Go library that extends the standard io/fs package to support write operations and creates a rich ecosystem of composable filesystem abstractions.

While Go's io/fs provides an excellent abstraction for read-only filesystems, fsx defines standard interfaces for modifying filesystems—including creating files, writing data, removing entries, and changing metadata. It also provides a suite of robust filesystem implementations like UnionFS, EvictFS (cache), and secure OS-backed filesystems.

Features

  • Standard Write Interfaces: Extends fs.FS with WriterFS for Create, OpenFile, and Remove.
  • Context Awareness: The contextual package mirrors filesystem operations with context.Context support for timeout and cancellation.
  • Composable implementations:
    • osfs: A secure, confined filesystem rooted in a specific OS directory (leveraging Go 1.24+ os.Root).
    • unionfs: A Copy-on-Write (CoW) union filesystem merging a read-write layer with multiple read-only layers.
    • evictfs: A self-cleaning filesystem that evicts files based on LRU, total size, or file age (perfect for caches).
    • bindfs: A wrapper that can override file permissions and ownership dynamically.
  • Testable: Includes mockfs generated with mockgen for easy unit testing of filesystem interactions.

Installation

go get github.com/gwangyi/fsx

Usage

Basic Filesystem Operations

Use the top-level fsx helper functions to perform write operations on any compatible filesystem.

package main

import (
	"log"

	"github.com/gwangyi/fsx"
	"github.com/gwangyi/fsx/osfs"
)

func main() {
	// Create a secure filesystem confined to a local directory
	fsys, err := osfs.New("./workspace")
	if err != nil {
		log.Fatal(err)
	}

	// Create a new file
	file, err := fsx.Create(fsys, "example.txt")
	if err != nil {
		log.Fatal(err)
	}
	
	_, err = file.Write([]byte("Hello, fsx!"))
	file.Close()

	if err != nil {
		log.Fatal(err)
	}

	// Remove the file
	if err := fsx.Remove(fsys, "example.txt"); err != nil {
		log.Fatal(err)
	}
}
Union Filesystem (Overlay)

Create a filesystem that merges a read-only base layer with a writable upper layer. Modifications are written to the upper layer (Copy-on-Write), leaving the base intact.

import (
	"github.com/gwangyi/fsx/unionfs"
	"github.com/gwangyi/fsx/osfs"
	"github.com/gwangyi/fsx/contextual"
)

// ...

// Base layer (Read-Only)
base, _ := osfs.New("./base_data")
// Upper layer (Read-Write)
upper, _ := osfs.New("./user_data")

// Create a unified view
// We use contextual.ToContextual to adapt standard fs.FS to contextual.FS
ufs := unionfs.New(contextual.ToContextual(upper), contextual.ToContextual(base))

// Reads check 'upper' then 'base'
// Writes always go to 'upper'
Eviction Filesystem (Cache)

Wrap a filesystem to automatically enforce limits on size or file count.

import (
	"time"
	"github.com/gwangyi/fsx/evictfs"
)

// ...

config := evictfs.Config{
    MaxFiles: 1000,
    MaxSize:  100 * 1024 * 1024, // 100 MB
    MaxAge:   24 * time.Hour,
}

// wrappedFS will now automatically delete old/least-used files 
// to stay within limits
cacheFS, err := evictfs.New(ctx, underlyingFS, config)

Packages

Package Description
fsx Core interfaces (WriterFS, FileSystem) and helper functions.
osfs OS-backed filesystem confined to a root directory.
contextual context.Context-aware interfaces and adapters.
unionfs Union (Overlay) filesystem implementation.
evictfs LRU/Size/Time-based eviction filesystem.
bindfs Bind filesystem for remapping permissions/owners.
mockfs Generated mocks for testing.

Requirements

  • Go 1.25 or higher.

License

MIT License

Documentation

Overview

Package fsx provides extended filesystem interfaces that support write operations. It builds upon the standard library's io/fs package, adding support for creating, writing, and removing files.

The standard io/fs package defines a read-only filesystem interface. fsx extends this to support common write operations, making it suitable for virtual filesystems, overlays, and testing mocks where write capabilities are required.

Index

Constants

View Source
const (
	// O_ACCMODE is the mask for access modes (O_RDONLY, O_WRONLY, O_RDWR).
	O_ACCMODE = 3
)

Variables

View Source
var (
	// ErrBadFileDescriptor is returned when an operation is performed on a file descriptor
	// that is not open for that operation (e.g., writing to a read-only file).
	// It is an alias for syscall.EBADF.
	ErrBadFileDescriptor = syscall.EBADF

	// ErrNotDir is returned when a directory operation is requested on a non-directory file.
	// It is an alias for syscall.ENOTDIR.
	ErrNotDir = syscall.ENOTDIR

	// ErrIsDir is returned when a file operation is requested on a directory.
	// It is an alias for syscall.EISDIR.
	ErrIsDir = syscall.EISDIR
)

Functions

func Chmod

func Chmod(fs fs.FS, name string, mode fs.FileMode) error

Chmod changes the mode of the named file to mode.

It checks if the provided filesystem (fs.FS) implements the ChangeFS interface. If it does, it calls the interface's Chmod method.

Parameters:

fs:   The filesystem interface.
name: The path of the file.
mode: The new file mode bits.

Returns:

error: nil on success, or an error if the operation fails or is unsupported.
       Returns errors.ErrUnsupported if fs does not implement ChangeFS.

func Chown

func Chown(fs fs.FS, name, owner, group string) error

Chown changes the owner and group of the named file.

It checks if the provided filesystem (fs.FS) implements the ChangeFS interface. If it does, it calls the interface's Chown method.

Parameters:

fs:    The filesystem interface.
name:  The path of the file.
owner: The new owner.
group: The new group.

Returns:

error: nil on success, or an error if the operation fails or is unsupported.
       Returns errors.ErrUnsupported if fs does not implement ChangeFS.

func Chtimes

func Chtimes(fs fs.FS, name string, atime, mtime time.Time) error

Chtimes changes the access and modification times of the named file.

It checks if the provided filesystem (fs.FS) implements the ChangeFS interface. If it does, it calls the interface's Chtimes method.

Parameters:

fs:    The filesystem interface.
name:  The path of the file.
atime: The new access time.
mtime: The new modification time.

Returns:

error: nil on success, or an error if the operation fails or is unsupported.
       Returns errors.ErrUnsupported if fs does not implement ChangeFS.

func IsInvalid

func IsInvalid(err error) bool

IsInvalid checks if the provided error represents an invalid operation or path.

func IsUnsupported

func IsUnsupported(err error) bool

IsUnsupported checks if the provided error indicates that an operation is not supported.

func Lchown

func Lchown(fsys fs.FS, name, owner, group string) error

Lchown changes the ownership of the named file. If the file is a symbolic link, it changes the ownership of the link itself. It uses the provided filesystem fsys. If fsys implements LchownFS, it calls fsys.Lchown. Otherwise, it returns an error indicating that the operation is unsupported.

func Mkdir

func Mkdir(fsys fs.FS, name string, perm fs.FileMode) error

Mkdir creates a new directory with the specified name and permission bits (before umask).

If fsys implements DirFS, it calls fsys.Mkdir. Otherwise, it returns errors.ErrUnsupported.

func MkdirAll

func MkdirAll(fsys fs.FS, name string, perm fs.FileMode) error

MkdirAll creates a directory named path, along with any necessary parents, and returns nil, or else returns an error. The permission bits perm (before umask) are used for all directories that MkdirAll creates. If path is already a directory, MkdirAll does nothing and returns nil.

If fsys implements MkdirAllFS, it calls fsys.MkdirAll. If the operation is not supported or not implemented, it falls back to a naive implementation using Mkdir and recursion.

func Remove

func Remove(fsys fs.FS, name string) error

Remove removes the named file or (empty) directory from the filesystem.

If fsys implements fsx.WriterFS, it calls fsys.Remove. Otherwise, it returns errors.ErrUnsupported.

func RemoveAll

func RemoveAll(fsys fs.FS, name string) error

RemoveAll removes path and any children it contains. It removes everything it can but returns the first error it encounters. If the path does not exist, RemoveAll returns nil.

If fsys implements RemoveAllFS, it calls fsys.RemoveAll(name). Otherwise, it falls back to a recursive implementation using ReadDir and Remove.

func Rename

func Rename(fsys fs.FS, oldname, newname string) error

Rename moves oldname to newname.

If fsys implements RenameFS, it calls fsys.Rename. Otherwise, it attempts to simulate the rename operation by copying the source to the destination and then removing the source. This fallback is not atomic.

func Symlink(fsys fs.FS, oldname, newname string) error

Symlink creates a symbolic link at newname, pointing to oldname. It uses the provided filesystem fsys. If fsys implements SymlinkFS, it calls fsys.Symlink. Otherwise, it returns an error indicating that the operation is unsupported.

func Truncate

func Truncate(fsys fs.FS, name string, size int64) error

Truncate changes the size of the named file.

If fsys implements TruncateFS, it calls fsys.Truncate. Otherwise, it attempts to open the file with write permissions and call the Truncate method on the returned File object.

func WriteFile

func WriteFile(fsys fs.FS, name string, data []byte, perm fs.FileMode) error

WriteFile writes data to the named file, creating it if necessary. If the file does not exist, WriteFile creates it with permissions perm (before umask); otherwise WriteFile truncates it before writing, without changing permissions.

If the filesystem implements WriteFileFS, its WriteFile method is used. Otherwise, it falls back to using OpenFile (with O_WRONLY|O_CREATE|O_TRUNC) and writing the data to the file. If the filesystem does not implement fsx.WriterFS, it returns errors.ErrUnsupported.

Types

type ChangeFS

type ChangeFS interface {
	WriterFS

	// Chown changes the numeric uid and gid of the named file.
	// It is similar to os.Chown.
	//
	// Parameters:
	//   name:  The name of the file within the filesystem.
	//   owner: The string representation of the owner (e.g., username or numeric ID).
	//   group: The string representation of the group (e.g., groupname or numeric ID).
	//
	// Behavior:
	//   If the file is a symbolic link, it changes the owner and group of the link's target.
	//   If the filesystem does not support this operation, it should return an error.
	Chown(name, owner, group string) error

	// Chmod changes the file mode bits of the named file to the specified mode.
	// It is similar to os.Chmod.
	//
	// Parameters:
	//   name: The name of the file within the filesystem.
	//   mode: The new file mode (permissions).
	//
	// Behavior:
	//   If the file is a symbolic link, it changes the mode of the link's target.
	//   If the filesystem does not support this operation, it should return an error.
	Chmod(name string, mode fs.FileMode) error

	// Chtimes changes the access and modification times of the named file.
	// It is similar to os.Chtimes.
	//
	// Parameters:
	//   name:  The name of the file within the filesystem.
	//   atime: The new access time.
	//   mtime: The new modification time.
	//
	// Behavior:
	//   The underlying filesystem may truncate or round the values to a less precise time unit.
	//   If the filesystem does not support this operation, it should return an error.
	Chtimes(name string, atime, mtime time.Time) error
}

ChangeFS is an interface that extends the basic fs.FS to support modification operations. It groups the methods for changing file ownership (Chown), file modes (Chmod), and file timestamps (Chtimes).

File systems that support these operations should implement this interface to allow clients to modify file metadata in a unified way.

type DirEntry

type DirEntry = fs.DirEntry

DirEntry is a type alias for fs.DirEntry, allowing it to be mocked by mockgen.

type DirFS

type DirFS interface {
	WriterFS
	fs.ReadDirFS

	// Mkdir creates a new directory with the specified name and permission bits.
	// If there is an error, it will be of type *PathError.
	Mkdir(name string, perm fs.FileMode) error
}

DirFS is an interface for filesystems that support creating directories. It extends WriterFS (and thus fs.FS) and fs.ReadDirFS with the Mkdir method.

type File

type File interface {
	fs.File
	io.Writer
	// Truncate changes the size of the file.
	// It returns an error if the file was not opened with write permissions.
	Truncate(size int64) error
}

File is an open file that supports reading, writing, and truncation. It extends fs.File (which only supports read-related operations) with io.Writer and a Truncate method.

Implementations of this interface allow users to modify the file content after opening it.

func Create

func Create(fsys fs.FS, name string) (File, error)

Create creates or truncates the named file in the given filesystem. It acts as a helper function that checks if the filesystem implements fsx.WriterFS.

If fsys implements fsx.WriterFS, it calls fsys.Create. If fsys does not implement fsx.WriterFS, it returns errors.ErrUnsupported.

func OpenFile

func OpenFile(fsys fs.FS, name string, flag int, mode fs.FileMode) (File, error)

OpenFile opens the named file with specified flag and mode in the given filesystem. It provides a generalized open call similar to os.OpenFile.

If fsys implements fsx.WriterFS, it calls fsys.OpenFile. If the operation is not supported by the filesystem implementation (returns ErrUnsupported) or if fsys is not an fsx.WriterFS, it attempts a fallback for read-only access: if the flag requests read-only access (O_RDONLY), it falls back to fsys.Open. Otherwise, it returns errors.ErrUnsupported.

type FileInfo

type FileInfo interface {
	fs.FileInfo

	// Owner returns the user name of the owner of the file.
	// If resolution fails, it may return the numeric UID as a string.
	Owner() string

	// Group returns the group name of the group of the file.
	// If resolution fails, it may return the numeric GID as a string.
	Group() string

	// AccessTime returns the last access time of the file (atime).
	AccessTime() time.Time

	// ChangeTime returns the last status change time of the file (ctime).
	ChangeTime() time.Time
}

FileInfo extends the standard fs.FileInfo interface with additional metadata commonly supported by Unix-like filesystems (and partially by others).

It allows access to extended attributes like ownership (Owner/Group), Inode numbers, and timestamp details (AccessTime, ChangeTime).

func ExtendFileInfo

func ExtendFileInfo(fi fs.FileInfo) FileInfo

ExtendFileInfo returns a FileInfo that wraps the provided fs.FileInfo.

It attempts to extract extended system-specific information from the underlying Sys() method of the input fs.FileInfo (e.g., from syscall.Stat_t on Linux). If the information is not available, it populates the fields with best-effort defaults (e.g., ModTime is used for AccessTime and ChangeTime if they are missing).

Parameters:

fi: The standard fs.FileInfo to extend.

Returns:

FileInfo: The extended file info. If fi already implements FileInfo, it is returned directly.

func Lstat

func Lstat(fsys fs.FS, name string) (FileInfo, error)

Lstat returns a FileInfo describing the named file. If the file is a symbolic link, the returned FileInfo describes the symbolic link. Lstat makes no attempt to follow the link.

It wraps the standard fs.Lstat function (introduced in Go 1.25) but returns an fsx.FileInfo, which provides extended file attributes (like owner, group, inode, etc.) if available from the underlying system.

Parameters:

fsys: The filesystem interface.
name: The path of the file.

Returns:

FileInfo: An extended FileInfo interface containing standard and extended attributes.
error:    nil on success, or an error if the operation fails.

func Stat

func Stat(fsys fs.FS, name string) (FileInfo, error)

Stat returns a FileInfo describing the named file. It wraps the standard fs.Stat function but returns an fsx.FileInfo, which provides extended file attributes (like owner, group, inode, etc.) if available from the underlying system.

If the filesystem does not support extended attributes, default or zero values are returned for the extended fields.

Parameters:

fsys: The filesystem interface.
name: The path of the file.

Returns:

FileInfo: An extended FileInfo interface containing standard and extended attributes.
error:    nil on success, or an error if the operation fails.

type LchownFS

type LchownFS interface {
	SymlinkFS

	// Lchown changes the ownership of the named file.
	// If the file is a symbolic link, it changes the ownership of the link itself.
	// It accepts owner and group as strings, which implies that the underlying
	// filesystem may support user/group names or numeric IDs.
	// If the file does not exist, it returns an error.
	Lchown(name, owner, group string) error
}

LchownFS is an interface for filesystems that support changing the ownership of a symbolic link itself, rather than the file it points to.

type MkdirAllFS

type MkdirAllFS interface {
	DirFS

	// MkdirAll creates a directory named path, along with any necessary parents,
	// and returns nil, or else returns an error.
	// The permission bits perm (before umask) are used for all directories that
	// MkdirAll creates. If path is already a directory, MkdirAll does nothing
	// and returns nil.
	MkdirAll(name string, perm fs.FileMode) error
}

MkdirAllFS is an interface for filesystems that support creating a directory along with any necessary parents (mkdir -p).

type ReadDirFile

type ReadDirFile = fs.ReadDirFile

ReadDirFile is a file that supports reading directory entries.

type ReadOnlyFile

type ReadOnlyFile struct {
	fs.File
}

ReadOnlyFile wraps an fs.File to implement the File interface, explicitly returning errors for any write-related operations.

func (ReadOnlyFile) ReadAt

func (r ReadOnlyFile) ReadAt(p []byte, off int64) (n int, err error)

ReadAt implements io.ReaderAt if the underlying file supports it.

func (ReadOnlyFile) ReadDir

func (r ReadOnlyFile) ReadDir(size int) ([]fs.DirEntry, error)

ReadDir implements fs.ReadDirFile if the underlying file supports it.

func (ReadOnlyFile) Seek

func (r ReadOnlyFile) Seek(offset int64, whence int) (int64, error)

Seek implements io.Seeker if the underlying file supports it.

func (ReadOnlyFile) Truncate

func (ReadOnlyFile) Truncate(size int64) error

Truncate returns ErrBadFileDescriptor as ReadOnlyFile does not support truncation.

func (ReadOnlyFile) Write

func (ReadOnlyFile) Write(d []byte) (int, error)

Write returns ErrBadFileDescriptor as ReadOnlyFile does not support writing.

type RemoveAllFS

type RemoveAllFS interface {
	WriterFS

	// RemoveAll removes path and any children it contains.
	// It removes everything it can but returns the first error
	// it encounters. If the path does not exist, RemoveAll returns nil.
	RemoveAll(name string) error
}

RemoveAllFS is the interface implemented by a file system that supports an optimized RemoveAll method.

Implementations of this interface can provide a more efficient way to remove a directory tree compared to the default recursive implementation.

type RenameFS

type RenameFS interface {
	WriterFS

	// Rename moves oldname to newname.
	// If newname already exists and is not a directory, Rename replaces it.
	// OS-specific restrictions may apply when oldname and newname are in different directories.
	Rename(oldname, newname string) error
}

RenameFS is the interface implemented by a file system that supports renaming files.

type SymlinkFS

type SymlinkFS interface {
	WriterFS
	fs.ReadLinkFS

	// Symlink creates a symbolic link at newname, pointing to oldname.
	// If newname already exists, Symlink should return an error.
	Symlink(oldname, newname string) error
}

SymlinkFS is an interface for filesystems that support creating symbolic links. It extends fs.ReadLinkFS to include the Symlink method.

type TruncateFS

type TruncateFS interface {
	WriterFS
	// Truncate changes the size of the named file.
	// If the file is a symbolic link, it changes the size of the link's target.
	Truncate(name string, size int64) error
}

TruncateFS is the interface implemented by a file system that supports truncating files by name.

type WriteFileFS

type WriteFileFS interface {
	WriterFS
	// WriteFile writes data to the named file, creating it if necessary.
	// It is similar to os.WriteFile.
	WriteFile(name string, data []byte, perm fs.FileMode) error
}

WriteFileFS is the interface implemented by a filesystem that provides an optimized WriteFile method.

type WriterFS

type WriterFS interface {
	fs.FS

	// Create creates or truncates the named file. If the file already exists,
	// it is truncated. If the file does not exist, it is created with mode 0666
	// (before umask). If successful, methods on the returned File can
	// be used for I/O; the associated file descriptor has mode O_RDWR.
	// If there is an error, it will be of type *PathError.
	Create(name string) (File, error)

	// OpenFile is the generalized open call; most users will use Open
	// or Create instead. It opens the named file with specified flag
	// (O_RDONLY etc.) and perm (0666 etc.) if applicable. If successful,
	// methods on the returned File can be used for I/O.
	// If there is an error, it will be of type *PathError.
	OpenFile(name string, flag int, mode fs.FileMode) (File, error)

	// Remove removes the named file or (empty) directory.
	// If there is an error, it will be of type *PathError.
	Remove(name string) error
}

WriterFS is a filesystem interface that extends fs.FS to support creating, opening with flags, and removing files.

While fs.FS is read-only, fsx.WriterFS adds the necessary methods to modify the filesystem structure and file contents.

Directories

Path Synopsis
Package contextualfs provides extended filesystem interfaces that support write operations.
Package contextualfs provides extended filesystem interfaces that support write operations.
Package evictfs provides a contextual filesystem wrapper that automatically evicts files based on configurable limits such as maximum file count or total size.
Package evictfs provides a contextual filesystem wrapper that automatically evicts files based on configurable limits such as maximum file count or total size.
Package mockfs is a generated GoMock package.
Package mockfs is a generated GoMock package.
contextual
Package cmockfs is a generated GoMock package.
Package cmockfs is a generated GoMock package.
Package osfs provides a robust and secure implementation of the fsx.WriterFS filesystem interface by leveraging the host's operating system files.
Package osfs provides a robust and secure implementation of the fsx.WriterFS filesystem interface by leveraging the host's operating system files.
Package unionfs provides a union filesystem implementation that merges multiple filesystems into a single view.
Package unionfs provides a union filesystem implementation that merges multiple filesystems into a single view.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL