Skip to content

ARCHITECTURE · 03

The VFS model.

TFS is a virtual filesystem with pluggable backends, spoken through one small C ABI. That ABI is the seam of the whole architecture: patched Ruby above it, backends below it, and either a C++ or a Rust implementation behind it.

The seam: tebako_fs_*.

SHIPPED · libtfs v0.13.0

Since libtfs v0.13.0 the surface is modern-only: the legacy tebako C/C++ API (a ~16k-line libdwarfs-wr lineage) was removed, leaving exactly 24 exports, declared in include/tebako/fs/c_api.h. Because the contract is a C ABI, consumers don't care which implementation — or which repository — ships it. Two implementations now exist: the C++ one below, and the Rust tfs crate in tebako-rs — a drop-in libtfs.so/.dylib/.dll/.a exporting exactly 33 tebako_* symbols, nm-verified (the 24-function contract surface plus tebako_fs_abi_version and eight compat/introspection helpers — nothing else leaks).

LIFECYCLE — SINGLE-MOUNT COMPAT

tebako_fs_inittebako_fs_init_from_filetebako_fs_init_from_file_attebako_fs_unmount

The original entry points, kept byte-compatible: init* stays single-mount (second init → EALREADY); unmount() tears down everything.

LIFECYCLE — MULTI-MOUNT

tebako_fs_mount_from_filetebako_fs_mount_from_file_attebako_fs_mount_from_memorytebako_fs_unmount_handle

The mount-table API: each mount returns an opaque handle and attaches at its own mount point — an app image, data images, and more, concurrently.

FILE I/O

tebako_fs_opentebako_fs_readtebako_fs_preadtebako_fs_lseektebako_fs_closetebako_fs_stattebako_fs_fstat

pread never moves the fd position (lseek+read emulation was rejected as racy).

DIRECTORY I/O

tebako_fs_opendirtebako_fs_readdirtebako_fs_closedirtebako_fs_rewinddirtebako_fs_telldirtebako_fs_seekdirtebako_fs_dir_is_embedded

Index-based telldir/seekdir cookies; dir_is_embedded is the registry test the patched runtime uses to dispatch between memfs and host.

UTILITIES

tebako_fs_extract_alltebako_fs_dlmap2file

extract_all powers --tebako-extract; dlmap2file extracts a memfs file to a host path so native extensions can be dlopen’ed.

The full behavior contract is executable: the c_api test suite registers 493 tests under ctest on the C++ side — the parity oracle. The Rust workspace runs the ported cases through its own C ABI (164 today, the full sweep following the same pattern; the audit is in tebako-rs's docs/parity.md).

Multi-mount.

SHIPPED · v0.13.0

One process can attach many TFS images at once — the app image plus data images plus anything else the package carries. A mount table maps opaque handles to (mount point, backend instance); path resolution is longest-prefix-wins on mount-point boundaries, and every fd/dir handle records its owning mount, so unmounting one image force-closes only its own handles and leaves the rest fully usable.

open("/tebako/fs/app/config/boot.rb", …)
        │
        ▼
┌───────────────────────────────────────────────────────┐
│  path dispatch — longest mount-point prefix wins      │
│                                                       │
│   mount 0   /tebako/fs/app    → dwarfs backend        │
│   mount 1   /tebako/fs/data   → zip backend           │
│   (nested mount points allowed; duplicates → EEXIST)  │
└───────────────────────────────────────────────────────┘
        │ path inside a mount              │ path outside every mount
        ▼                                  ▼
  tebako_fs_open()                    host open()
  read-only image, owning mount       cwd, writes, mkdir — untouched

The backends are instance-based — dwarfs (via dwarfs-t), squashfs, zip — with no global mount state, which is exactly what makes N concurrent mounts safe. Images can be mounted from a package slot (file + offset + length), from a standalone file, or from memory.

Read-only memfs, host cwd and writes.

The packaged filesystem is read-only by design. Paths inside a mount are served from the image; everything else — the process working directory, every write, mkdir, unlink — passes through to the host filesystem unchanged. There is deliberately no tebako_fs_chdir and no write surface in the ABI: a packaged app can corrupt neither its own image nor another package's mount. The one sanctioned escape hatch is tebako_fs_dlmap2file, which extracts a file to the host so the OS loader can map a native extension.

IO routing in patched Ruby.

SHIPPED

The patches published by tamatebako/ruby reroute Ruby's file calls to the ABI — and only to the ABI. A stat-based "is this path inside a mount?" check decides routing; non-mount paths fall through to the host unchanged.

RUBY / LIBC CALLROUTES TO
open / read / lseek / closetebako_fs_open / read / lseek / close
stat / fstat / lstat / access-as-existstebako_fs_stat / fstat (+ stat-as-exists)
opendir / readdir / closedirtebako_fs_opendir / readdir / closedir
Dir#tell / Dir#seek / Dir#eachtebako_fs_telldir / seekdir / rewinddir
IO#preadtebako_fs_pread (fd position untouched)
fd / dir introspectiontebako_fs_dir_is_embedded and the fd registry
writes, cwd, mkdir, unlinknever routed — they go to the host FS (read-only memfs)
native extension load (dlopen)tebako_fs_dlmap2file → host path → host dlopen