Skip to content

API reference

The package exports three symbols. This page is the authoritative description of each: signature, default, and what happens when you get it wrong. The generated Go documentation is on pkg.go.dev.

Import path:

import "gitlab.com/phpboyscout/go/aferobilly"

New — build an afero.Fs from a billy filesystem

func New(bfs billy.Filesystem, opts ...Option) afero.Fs

Returns an afero.Fs backed by bfs. The concrete type is unexported; the only way to reach it is through the afero.Fs interface and the optional interfaces below.

Parameter bfs Any billy.Filesystem. This is the full billy interface (Basic + TempFile + Dir + Symlink + Chroot), not billy.Basic — a Basic-only value will not compile.
Returns afero.Fs. Never nil, and never an error — construction cannot fail.
Default locker A no-op. Without WithLocker the adapter adds no synchronisation of its own.

What New does at construction time

Exactly one thing: it calls bfs.Root() once and stores the result as the value Name() will return. Nothing is opened, stat-ed or validated. A filesystem pointing at a path that does not exist constructs happily and fails at the first operation.

What happens if you pass nil

New(nil) panics immediately with runtime error: invalid memory address or nil pointer dereference, because the constructor calls bfs.Root(). There is no nil check and no error return: a nil filesystem is a programming error, not a supported input.

Option — the functional-option type

type Option func(*billyFs)

Option is exported but its parameter type is not, so you cannot write your own Option outside this package. It exists so the constructor signature can grow without a breaking change. WithLocker is currently the only implementation.

WithLocker — serialise every operation through a lock

func WithLocker(l sync.Locker) Option

Acquires l around every Fs operation and every File operation, then releases it before returning.

Parameter l Any sync.Locker — a *sync.Mutex, a *sync.RWMutex, or your own type.
nil argument Ignored. The no-op default is kept and no error or panic is raised, so WithLocker(nil) silently produces an unlocked filesystem.
Applied at Construction. The locker cannot be changed afterwards.
Scope Per handle. Two New calls over the same billy filesystem with different lockers do not serialise against each other.

Which mutex to pass

Pass the mutex that guards whatever produced the filesystem — a repository handle, a pool, a cache — not a fresh mutex created for the adapter. A fresh mutex only protects the adapter's own callers from each other; the producer can still touch the underlying filesystem concurrently. Sharing the producer's mutex is what makes an escaped handle safe. See make a handle concurrency-safe.

The locking is per operation, not per sequence

Each call takes and releases the lock. Two calls in a row are two critical sections with a gap between them, so a read-modify-write through the handle is not atomic. Holding the lock yourself across the sequence is the fix — and doing so with a handle configured with that same lock deadlocks, because Go mutexes are not reentrant. Both limits are worked through in make a handle concurrency-safe.

Optional interfaces the returned Fs satisfies

Type-assert the returned value to reach these. The package asserts afero.Fs, afero.Lstater and afero.Symlinker at compile time, and afero.Symlinker is defined as Lstater + Linker + LinkReader — so none of these assertions can fail for a value that came from New.

Interface Methods Notes
afero.Lstater LstatIfPossible The returned bool is always truebilly.Filesystem always declares Lstat.
afero.Linker SymlinkIfPossible Argument order is afero's (oldname, newname), mapped to billy's Symlink(target, link).
afero.LinkReader ReadlinkIfPossible
afero.Symlinker all of the above afero.Symlinker is defined as Lstater + Linker + LinkReader.
fs := aferobilly.New(bfs)

if sl, ok := fs.(afero.Symlinker); ok {
    _ = sl.SymlinkIfPossible("/target", "/link")
}

There is no accessor for the wrapped billy filesystem

Deliberately. Neither the Fs nor its files expose the underlying billy.Filesystem or billy.File, so there is no way to perform an operation that skips the locker. If you need the billy value, keep your own reference to what you passed to New.