A cross-platform FUSE driver and reusable C library for mounting AXFS v2 filesystem images as native drives. Designed for AxisOS disk images from OpenComputers, but works with any AXFS v2 volume.
Transparently handles GZIP-compressed OpenComputers drive images, Amiga-style RDB partition tables with multiple AXFS partitions, and bare superblocks.
Z:\> dir
Volume in drive Z is AXFS [AxisOS]
Directory of Z:\
2025-06-14 12:30 <DIR> bin
2025-06-14 12:30 <DIR> etc
2025-06-14 12:30 <DIR> lib
2025-06-14 12:30 <DIR> var
0 File(s) 0 bytes
4 Dir(s) 1,331,200 bytes free
- Multi-partition auto-mount — scans RDB tables, assigns drive letters (Windows) or
/tmpmount points (Linux) automatically - Read/write — full create, delete, rename, truncate support
- GZIP transparency — detects
.gzOC drive images, decompresses on load, recompresses on save - LRU inode cache — O(1) cached reads for recently accessed inodes (64 entries)
- Path resolution cache — HashMap-backed path→inode cache, cleared on directory mutations
- Extent-based allocation — coalesces consecutive blocks into extents, supports indirect blocks
- Inline small files — files ≤52 bytes stored directly in the inode (zero data blocks)
- Thread-safe — per-volume mutex, safe for concurrent FUSE access
┌──────────────────────────────────────────────────────────┐
│ axfs_fuse.c │
│ FUSE callbacks + mount logic │
│ (WinFsp on Windows, libfuse on Linux) │
├──────────────────────────────────────────────────────────┤
│ axfs_lib.h / .c │
│ Filesystem library (transport-agnostic) │
│ Superblock · Inodes · Dirs · Bitmaps · Path resolve │
│ LRU inode cache · Path HashMap cache │
├──────────────────────────────────────────────────────────┤
│ ctemplates.h v2 │
│ Compile-time generics for C │
│ Vec · HashMap · LRU · Bitset · SmallVec · Optional │
│ Result · FixedString · Pair · CT_AUTOFREE │
└──────────────────────────────────────────────────────────┘
| File | Purpose |
|---|---|
ctemplates.h |
Type-safe generic containers via C preprocessor |
axfs_lib.h |
Public API — types, constants, function declarations |
axfs_lib.c |
Filesystem implementation — all logic lives here |
axfs_fuse.c |
FUSE driver — mounts volumes, bridges OS ↔ library |
Prerequisites:
- Visual Studio 2019+ with C11 support
- WinFsp installed (default path assumed below)
cl /std:c11 /Zc:preprocessor /O2 axfs_fuse.c ^
/I"C:\Program Files (x86)\WinFsp\inc\fuse" ^
/link /LIBPATH:"C:\Program Files (x86)\WinFsp\lib" winfsp-x64.libOr use the included build script:
build_all.batPrerequisites:
- GCC or Clang with C99+ support
- libfuse 2.x development headers (
apt install libfuse-dev)
gcc -std=c99 -O2 -o axfs_mount axfs_fuse.c \
$(pkg-config fuse --cflags --libs) -lpthreadaxfs_mount drive.binScans the image for an RDB partition table, discovers all AXFS v2 partitions, and mounts each one:
- Windows: assigns free drive letters (
Z:,Y:, ...) - Linux: creates mount points under
/tmp/axfs_0_AxisOS/, etc.
# Windows
axfs_mount drive.bin Z:
# Linux
axfs_mount drive.bin /mnt/axfsaxfs_mount drive.bin 42 Z:Press Ctrl+C in the terminal, or:
# Linux
fusermount -u /mnt/axfs
# Windows — Ctrl+C or eject from ExplorerChanges are saved to the image file on unmount. GZIP images are recompressed automatically.
AXFS v2 is a simple extent-based filesystem designed for small virtual disks (64KB–4MB typical in OpenComputers).
Sector 0: Primary superblock ("AXF2" magic, version 2)
Sector 1: Mirror superblock (recovery copy)
Sector 2: Inode bitmap (1 bit per inode)
Sectors 3+: Block bitmap (1 bit per data block)
Inode table (80 bytes per inode)
Data blocks (extent-addressed)
| Parameter | Value |
|---|---|
| Magic | AXF2 (4 bytes) |
| Inode size | 80 bytes |
| Directory entry size | 32 bytes |
| Max filename length | 27 bytes |
| Max inline file size | 52 bytes |
| Max direct extents | 13 per inode |
| Indirect block support | Yes (1 level) |
| Byte order | Big-endian (Amiga heritage) |
| Value | Type |
|---|---|
| 0 | Free |
| 1 | File |
| 2 | Directory |
| 3 | Symlink |
The library includes two caches that mirror the Lua v3 implementation's CLOCK cache and path cache:
- Capacity: 64 entries (configurable via
AXFS_INODE_CACHE_SIZE) - Eviction: least-recently-used
- Hit path: O(1) hash lookup → copy cached inode → return
- Miss path: disk read → parse → insert into cache → return
- Invalidation:
axfs_write_inodedeletes the entry for the written inode
- Type: open-addressing HashMap (from ctemplates.h)
- Key: fixed-length path string (up to 128 chars)
- Value: inode number
- Hit path: O(1) hash probe → return cached inode number
- Invalidation: entire cache cleared on any
dir_addordir_remove
axfs_cache_stats_t stats = axfs_cache_stats(vol);
printf("Inode cache: %zu/%zu entries, %zu hits, %zu misses\n",
stats.inode_entries, stats.inode_capacity,
stats.inode_hits, stats.inode_misses);
printf("Path cache: %zu entries, %zu hits, %zu misses\n",
stats.path_entries, stats.path_hits, stats.path_misses);A standalone header providing type-safe generic containers for C via preprocessor macro expansion. No dependencies beyond the C standard library.
| Macro | Generates |
|---|---|
DEFINE_VEC(T) |
Growable dynamic array (Vec_T) |
DEFINE_HASHMAP(K, V, h, eq) |
Open-addressing hash table |
DEFINE_LRU(K, V, h, eq) |
Fixed-capacity LRU eviction cache |
DEFINE_OPTIONAL(T) |
Nullable value wrapper |
DEFINE_RESULT(T) |
Rust-style Result<T, int> |
DEFINE_PAIR(A, B) |
Typed key-value pair |
DEFINE_BITSET(Name, N) |
Compile-time fixed-size bit array |
DEFINE_SMALLVEC(T, N) |
Stack-first small-buffer-optimized vector |
DEFINE_FIXED_STRING(Name, N) |
Value-semantic fixed-capacity string |
#include "ctemplates.h"
DEFINE_VEC(int)
DEFINE_HASHMAP(int, double, ct_hash_int, ct_eq_int)
int main(void) {
Vec_int v = Vec_int_new();
Vec_int_push(&v, 10);
Vec_int_push(&v, 20);
Vec_int_push(&v, 30);
printf("len = %zu, back = %d\n", v.len, Vec_int_back(&v));
Vec_int_free(&v);
HashMap_int_double m = HashMap_int_double_new();
HashMap_int_double_put(&m, 42, 3.14);
double *val = HashMap_int_double_get(&m, 42);
if (val) printf("42 -> %f\n", *val);
HashMap_int_double_free(&m);
}void process(void) {
CT_AUTOFREE uint8_t *buf = malloc(4096);
CT_AUTO_VEC(int) Vec_int v = Vec_int_new();
Vec_int_push(&v, 1);
if (error_condition)
return; // buf freed, v freed — automatically
// ... more work ...
} // buf freed, v freed — automaticallyAXFS images can contain an Amiga Rigid Disk Block partition table at sector 0. The driver scans this to discover multiple AXFS partitions within a single disk image.
Sector 0: RDSK header (Rigid Disk Block)
Sectors 1-16: PART entries (linked list, one per partition)
Sector N+: Partition data (each contains its own AXFS superblock)
The driver recognizes AXFS partitions by their filesystem type ID:
0x41584632 ("AXF2" in ASCII).
If no RDB is found, the driver falls back to detecting a bare AXFS superblock at sector 0 or 1.
This driver is the host-side tooling for AxisOS, a security-focused
operating system for OpenComputers (Minecraft mod). The Lua-side
implementation (axfs_core.lua) runs inside the game and provides the
same filesystem with additional features:
| Feature | C Library | Lua v3 |
|---|---|---|
| Inode cache | LRU (64 entries) | LRU-ish (48 entries) |
| Path cache | HashMap | Table-based |
| Dir cache | — | Hash table per directory |
| Sector cache | — | CLOCK algorithm (48 entries) |
| Block checksums | — | CRC32 per block |
| Copy-on-Write | — | Optional, crash-safe |
| Batch I/O | — | Multi-sector reads |
The C library focuses on correctness and FUSE integration. The Lua implementation adds caching layers appropriate for the constrained OpenComputers runtime (limited memory, coroutine-based I/O).
The FUSE mount options must include uid=-1,gid=-1,umask=0 to bypass
WinFsp's permission checking. This is already configured in the provided
axfs_fuse.c. If you see permission errors, verify that mount_one()
passes these options.
- Check that WinFsp is installed
- Run the driver from an elevated (Administrator) terminal
- Check
stderroutput for mount errors
The FUSE process crashed or was killed without unmounting:
fusermount -u /mnt/axfsChanges are saved when the last mounted partition is unmounted
(Ctrl+C or fusermount -u). If the process is killed with SIGKILL,
changes are lost. Use SIGINT (Ctrl+C) or SIGTERM for clean shutdown.
i dont give a fuck