Skip to content

lesiw/fs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

73 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

lesiw.io/fs

Go Reference CI License

A filesystem abstraction for Go that extends io/fs with write operations and context support. The same code reads and writes local disks, in-memory filesystems, and remote systems reached over SSH, S3, SMB, or WebDAV.

// Copying between filesystems is io.Copy of standard interfaces.
w, err := fs.Create(ctx, remote, "backup.tar.gz")
if err != nil {
    return err
}
defer w.Close()
r, err := fs.Open(ctx, local, "backup.tar.gz")
if err != nil {
    return err
}
defer r.Close()
_, err = io.Copy(w, r)

API reference · Source

The model

  1. FS is one method. Open(ctx, name) (io.ReadCloser, error). Files are standard io values, never a custom File type.
  2. Every other capability is an optional interface discovered by type assertion, in the manner of io/fs.
  3. Helper functions check capabilities and fall back, so portable code calls one function either way.
  4. The context carries the operation's ambience — cancellation and timeouts, file and directory modes, the working directory.
  5. A trailing slash means a directory, and directories are tar streams.

Feature matrix

Capability io/fs os lesiw.io/fs
Read files
Write files
Create/remove directories
Read metadata (stat)
Write metadata (chmod, chtimes)
Create symbolic links
io interfaces, not File types
Fallback implementations
Context support
Bulk operations (tar)
Virtual directories
Range-over-func iterators

Install

go get lesiw.io/fs

Requires Go 1.24.2 or later.

Quick start

package main

import (
    "context"
    "fmt"
    "log"

    "lesiw.io/fs"
    "lesiw.io/fs/memfs"
)

func main() {
    ctx, fsys := context.Background(), memfs.New()
    defer fs.Close(fsys)

    err := fs.WriteFile(ctx, fsys, "hello.txt", []byte("Hello, world!"))
    if err != nil {
        log.Fatal(err)
    }

    content, err := fs.ReadFile(ctx, fsys, "hello.txt")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(content))
}

Tip

The in-memory filesystem runs in the Go Playground, so the package can be tried without installing anything: run an example.

Capabilities are interfaces

The core interface requires one method.

type FS interface {
    Open(ctx context.Context, name string) (io.ReadCloser, error)
}

Everything else is an optional interface an implementation may add — CreateFS for writing, MkdirFS for directories, StatFS for metadata, two dozen in all. Helper functions probe for the capability and report fs.ErrUnsupported when an operation has no path forward.

w, err := fs.Create(ctx, fsys, "file.txt")
if errors.Is(err, fs.ErrUnsupported) {
    // Filesystem is read-only.
}

Many helpers carry fallbacks, so an implementation supplies the operations it can do natively and inherits the rest. Append falls back to read-and-rewrite, Rename to copy-and-delete, Walk and ReadDir to each other, and directory-as-tar operations to archive/tar over a filesystem walk.

Context values

Cancellation and deadlines work the way they do everywhere else in Go, which matters most when the filesystem is on the far side of a network.

ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
data, err := fs.ReadFile(ctx, remote, "large-file.dat")

File and directory modes travel on the context too, where they apply to every operation in a chain — including parent directories created implicitly along the way.

ctx = fs.WithFileMode(ctx, 0600)
ctx = fs.WithDirMode(ctx, 0700)
err := fs.WriteFile(ctx, fsys, "logs/2026/app.log", data)

That call creates logs/2026/ with mode 0700 when it doesn't exist, then writes the file with mode 0600. Object stores treat the nested path as a key and skip directory creation; traditional filesystems create the parents. The calling code is the same for both.

Directories are tar streams

A trailing slash names a directory, and directories read and write as tar archives with the same verbs as files.

// Read a directory as an archive, like reading a file.
r, err := fs.Open(ctx, fsys, "project/")

// Add files to a directory, like appending to a file.
w, err := fs.Append(ctx, fsys, "project/")

// Empty a directory, like truncating a file.
err = fs.Truncate(ctx, fsys, "project/", 0)

// Replace a directory's contents, like creating a file.
w, err = fs.Create(ctx, fsys, "restore/")

Implementations with native tar support (DirFS, AppendDirFS, TruncateDirFS) move one stream instead of one request per file; without them, the helpers fall back to a filesystem walk with archive/tar.

Directory traversal

Walk and ReadDir are range-over-func iterators, so errors are handled in the loop and early termination costs nothing.

for entry, err := range fs.Walk(ctx, fsys, "project", 3) {
    if err != nil {
        return err
    }
    if entry.Name() == "target.txt" {
        return process(entry)
    }
}

Implementations

Maintained in this module:

  • osfs — the local filesystem, backed by os, implementing most optional interfaces natively
  • memfs — in-memory, Playground-safe, useful in tests and examples

Reference implementations demonstrating the abstraction across diverse backends, with Docker-based tests:

  • HTTP — read-only HTTP filesystem
  • S3 — Amazon S3 via the MinIO SDK
  • SFTP — SSH File Transfer Protocol
  • SMB — SMB/CIFS network shares
  • SSH — remote filesystem over SSH
  • WebDAV — WebDAV protocol

lesiw.io/command builds on this package: its command.FS derives a filesystem for any machine it can run commands on, ssh hosts and containers included.

Testing

The fstest package validates filesystem implementations.

func TestMyFS(t *testing.T) {
    fsys := myfs.New()
    t.Cleanup(func() { _ = fs.Close(fsys) })
    fstest.TestFS(t.Context(), t, fsys)
}

The suite detects capabilities through type assertions and exercises every operation the implementation supports, fallbacks included.

Documentation

License

BSD 3-Clause

About

Filesystem abstraction for Go: io/fs with writes, context support, and remote filesystems

Topics

Resources

License

Stars

3 stars

Watchers

0 watching

Forks

Contributors

Languages