Important
The latest branch contains v6 — a full rewrite of the allocation engine tuned for HDD, Flash, and NAND storage. v6 images are not compatible with v5 images (different magic number VLX6). Re-format any existing images. v6 veloxfs_fuse.c covers a little less than the main branch version but works for now. Enjoy! The README below reflects v5(main branch)
v6 Recommended Instead - Click Here
.
Important
The latest branch also contains a Linux Driver (concept) that works!
A single-header C filesystem library, following the
single-file library convention.
Listed on:
The entire filesystem implementation lives in veloxfs.h. The optional veloxfs_fuse.c adapter lets you mount a veloxfs image as a real filesystem on Linux via FUSE. It is not required to use the library. Other examples can be found in
here for different use case examples, for now there is only one.
Ensure you read the issues tab.
———
How it works
veloxfs stores data in a flat binary image (a file, a block device, a memory buffer, or anything you can read and write by offset). The layout is:
[ superblock | FAT | journal | inodes | directory | data blocks ] Block allocation uses a FAT-style singly-linked list. Each inode stores the index of its first block; the FAT maps each block to the next, terminating with a sentinel value. This means files can grow without relocation and fragmentation does not cause data corruption.
Directories are implicit. There is no directory inode — a directory exists if at least one file path begins with that prefix. The FUSE adapter anchors empty directories with a hidden .veloxfs_dir marker file so they survive remount.
———
What it supports
Create, read, write, delete, rename files
Create, delete, rename directories (arbitrarily nested)
File data persists across mount/unmount
Unix-style permissions (uid, gid, mode bits)
Optional write-ahead journal (64-entry circular log)
O(1) directory lookups via an in-memory FNV-1a hash table
fsck to detect and free orphaned blocks
Allocation statistics
What it does not support
Hard links or symbolic links
Extended attributes
File locking
Access control lists
Sparse files
Timestamps with sub-second precision
Concurrent access without external locking (the FUSE adapter adds a mutex; the library itself is single-threaded)
———
Usage
Library only (no FUSE)
In one C file, define the implementation before including the header:
#defineveloxfs_IMPLEMENTATION#include"veloxfs.h"All other files that need the API include it without the define:
#include"veloxfs.h"Minimal example
#defineveloxfs_IMPLEMENTATION#include"veloxfs.h"#include<stdio.h>// Provide two callbacks: read and write at a byte offset.// The backing store can be anything.staticuint8_tstorage[32*1024*1024]; // 32 MB RAM diskstaticintmem_read(void*user, uint64_toff, void*buf, uint32_tn) { memcpy(buf, (uint8_t*)user+off, n); return0; } staticintmem_write(void*user, uint64_toff, constvoid*buf, uint32_tn) { memcpy((uint8_t*)user+off, buf, n); return0; } intmain(void) { veloxfs_ioio= { mem_read, mem_write, storage }; uint64_tblocks=sizeof(storage) / veloxfs_BLOCK_SIZE; veloxfs_format(io, blocks, 0/* journaling off */); veloxfs_handlefs; veloxfs_mount(&fs, io); veloxfs_create(&fs, "/hello.txt", 0644); veloxfs_write_file(&fs, "/hello.txt", "hello", 5); charbuf[16]; uint64_tgot; veloxfs_read_file(&fs, "/hello.txt", buf, sizeof(buf), &got); buf[got] ='\0'; printf("%s\n", buf); // helloveloxfs_unmount(&fs); return0; }Mounting as a real filesystem (Linux, requires libfuse)
# Build gcc -Wall -O2 -pthread veloxfs_fuse.c -o veloxfs_fuse \ `pkg-config fuse --cflags --libs`# Create and format an image dd if=/dev/zero of=veloxfs.img bs=1M count=512 ./veloxfs_fuse --format veloxfs.img # Mount mkdir /tmp/mnt ./veloxfs_fuse veloxfs.img /tmp/mnt -o big_writes,max_write=131072 # Use it normally — cp, mv, mkdir, rm, etc. cp largefile.bin /tmp/mnt/ mkdir /tmp/mnt/docs mv /tmp/mnt/docs /tmp/mnt/documents # Unmount — data persists fusermount -u /tmp/mnt # Remount and verify ./veloxfs_fuse veloxfs.img /tmp/mnt ls /tmp/mnt
———
API reference
Filesystem lifecycle
intveloxfs_format(veloxfs_ioio, uint64_tblock_count, intenable_journal); intveloxfs_mount(veloxfs_handle*fs, veloxfs_ioio); intveloxfs_unmount(veloxfs_handle*fs); intveloxfs_sync(veloxfs_handle*fs); intveloxfs_fsck(veloxfs_handle*fs);File operations
intveloxfs_create(veloxfs_handle*fs, constchar*path, uint32_tmode); intveloxfs_delete(veloxfs_handle*fs, constchar*path); intveloxfs_rename(veloxfs_handle*fs, constchar*old_path, constchar*new_path); intveloxfs_write_file(veloxfs_handle*fs, constchar*path, constvoid*data, uint64_tsize); intveloxfs_read_file(veloxfs_handle*fs, constchar*path, void*out, uint64_tmax, uint64_t*out_size);File handle operations (streaming I/O)
intveloxfs_open(veloxfs_handle*fs, constchar*path, intflags, veloxfs_file*file); intveloxfs_close(veloxfs_file*file); intveloxfs_read(veloxfs_file*file, void*buf, uint64_tcount, uint64_t*bytes_read); intveloxfs_write(veloxfs_file*file, constvoid*buf, uint64_tcount); intveloxfs_seek(veloxfs_file*file, int64_toffset, intwhence); uint64_tveloxfs_tell(veloxfs_file*file); intveloxfs_truncate_handle(veloxfs_file*file, uint64_tnew_size);Metadata and statistics
intveloxfs_stat(veloxfs_handle*fs, constchar*path, veloxfs_stat_t*stat); intveloxfs_statfs(veloxfs_handle*fs, uint64_t*total, uint64_t*used, uint64_t*free); intveloxfs_chmod(veloxfs_handle*fs, constchar*path, uint32_tmode); intveloxfs_chown(veloxfs_handle*fs, constchar*path, uint32_tuid, uint32_tgid); intveloxfs_mkdir(veloxfs_handle*fs, constchar*path, uint32_tmode); intveloxfs_list(veloxfs_handle*fs, constchar*path, veloxfs_list_callbackcb, void*user);Error codes
CodeValueMeaningveloxfs_OK0Successveloxfs_ERR_IO-1I/O callback returned an errorveloxfs_ERR_CORRUPT-2On-disk structure is inconsistentveloxfs_ERR_NOT_FOUND-3Path does not existveloxfs_ERR_EXISTS-4Path already existsveloxfs_ERR_NO_SPACE-5No free blocksveloxfs_ERR_INVALID-6Invalid argumentveloxfs_ERR_TOO_LARGE-7Operation exceeds limitsveloxfs_ERR_TOO_MANY_FILES-8Inode or directory table fullveloxfs_ERR_PERMISSION-9Permission denied
———
On-disk layout
RegionSizeSuperblock1 blockFATceil(block_count / 512) blocksJournal64 blocks (optional)Inode tableblock_count / 50 blocksDirectory tableblock_count / 100 blocksDataremainderBlock size is fixed at 4096 bytes. The FAT stores one uint64_t per block — 0 means free, 0xFFFFFFFFFFFFFFFF means end of chain, any other value is the index of the next block in the file.
———
Portability
The library requires C99 and the following standard headers: stdint.h, stddef.h, string.h, stdlib.h, stdio.h.
time.h is included automatically for the default timestamp implementation, but it is not required if you override the timestamp source (see below).
The FUSE adapter requires Linux and libfuse 2.x (FUSE_USE_VERSION 26).
There are no other dependencies.
Systems without a real-time clock
All timestamp calls go through a single overridable macro. If your platform has no RTC or no time() function (bare-metal, RTOS, WASM, etc.), define veloxfs_TIME() before including the header:
// No clock — store 0 for all timestamps#defineveloxfs_TIME() 0 // Custom clock source#defineveloxfs_TIME() my_rtc_get_unix_seconds()If veloxfs_TIME is not defined, it defaults to time(NULL) and time.h is included automatically.
———
Multi-platform Support (Needs One Minor Fix ⚠️) : Should be fixed now!
License
Public domain or MIT, your choice.