Last Updated: February 24, 2026
BlockFS is VeridianOS's native persistent filesystem. Unlike the default TAR rootfs mode -- which loads an archive into RAM at boot and loses all changes on shutdown -- BlockFS reads and writes directly to a raw disk image backed by virtio-blk. Files created, modified, or deleted during a session survive across reboots.
BlockFS is an ext2-inspired filesystem with a simple, predictable on-disk layout: a superblock, block bitmap, inode table, and data blocks. It supports regular files and directories with standard POSIX-style metadata (permissions, timestamps, link counts). The kernel auto-detects the filesystem format at boot by probing the first sector of the virtio-blk device for the BlockFS magic number.
| Mode | Storage | Persistence | RAM | Use Case |
|---|---|---|---|---|
| TAR rootfs | RAM only | None (volatile) | 256M | Quick testing, CI boots |
| BlockFS | Disk image | Full persistence | 2048M | Development, native compilation, iterative work |
BlockFS uses 4KB blocks throughout. The image is divided into four regions:
+-------------------+ Block 0
| Superblock | 62 bytes serialized, padded to 4096
+-------------------+ Block 1
| Block Bitmap | B blocks (B = ceil(total_blocks / 32768))
| | 1 bit per block: 0 = free, 1 = allocated
+-------------------+ Block 1+B
| Inode Table | I blocks (I = ceil(inode_count * 96 / 4096))
| | 42 inodes per block (96 bytes each)
+-------------------+ Block 1+B+I
| Data Blocks | Remaining blocks for file/directory content
| |
| ... |
+-------------------+
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | magic | 0x424C4B46 ("BLKF") |
| 4 | 4 | block_count | Total blocks in image |
| 8 | 4 | inode_count | Total inodes allocated |
| 12 | 4 | free_blocks | Free block count |
| 16 | 4 | free_inodes | Free inode count |
| 20 | 4 | first_data_block | Index of first data block |
| 24 | 4 | block_size | Always 4096 |
| 28 | 2 | inode_size | Always 96 |
| 30 | 4 | blocks_per_group | Block group size (8192) |
| 34 | 4 | inodes_per_group | Inodes per group (2048) |
| 38 | 8 | mount_time | Last mount timestamp |
| 46 | 8 | write_time | Last write timestamp |
| 54 | 2 | mount_count | Mount counter |
| 56 | 2 | max_mount_count | Max mounts before check (100) |
| 58 | 2 | state | Filesystem state (1 = clean) |
| 60 | 2 | errors | Error behavior flags |
All fields are little-endian.
Each inode stores file metadata and block pointers:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 2 | mode | File type + permissions (e.g., 0x81ED = regular rwxr-xr-x) |
| 2 | 2 | uid | Owner user ID |
| 4 | 4 | size | File size in bytes |
| 8 | 4 | atime | Access time |
| 12 | 4 | ctime | Creation time |
| 16 | 4 | mtime | Modification time |
| 20 | 4 | dtime | Deletion time |
| 24 | 2 | gid | Owner group ID |
| 26 | 2 | links_count | Hard link count |
| 28 | 4 | blocks | Allocated block count |
| 32 | 4 | flags | Inode flags |
| 36 | 48 | direct_blocks[12] | 12 direct block pointers (4 bytes each) |
| 84 | 4 | indirect_block | Single indirect block pointer |
| 88 | 4 | double_indirect_block | Double indirect block pointer |
| 92 | 4 | triple_indirect_block | Triple indirect block pointer |
With 12 direct blocks + single indirect (1024 pointers), a single file can be up to (12 + 1024) * 4096 = 4,243,456 bytes (~4MB) without double indirect blocks.
Directories store variable-length entries in their data blocks:
| Offset | Size | Field |
|---|---|---|
| 0 | 4 | inode number |
| 4 | 2 | record length (aligned to 4 bytes) |
| 6 | 1 | name length |
| 7 | 1 | file type (1=regular, 2=directory) |
| 8 | N | filename (up to 255 bytes) |
Build the mkfs-blockfs host tool (runs on Linux, not on VeridianOS):
cd tools/mkfs-blockfs
cargo build --releaseBuild the cross-compiled BusyBox rootfs (required to populate the image):
./scripts/build-busybox-rootfs.sh allThe simplest way is to use the rootfs build script:
# Create a 256MB BlockFS image populated from the BusyBox rootfs
./scripts/build-busybox-rootfs.sh blockfsThis runs mkfs-blockfs internally with the default 256MB size and populates the image from target/rootfs-busybox/.
For manual control, use mkfs-blockfs directly:
# Create a 256MB image populated from a directory
./tools/mkfs-blockfs/target/x86_64-unknown-linux-gnu/release/mkfs-blockfs \
--output target/rootfs-blockfs.img \
--size 256 \
--populate target/rootfs-busybox/
# Create an empty 64MB image (no pre-populated files)
./tools/mkfs-blockfs/target/x86_64-unknown-linux-gnu/release/mkfs-blockfs \
--output target/empty.img \
--size 64
# Specify a custom inode count
./tools/mkfs-blockfs/target/x86_64-unknown-linux-gnu/release/mkfs-blockfs \
--output target/rootfs-blockfs.img \
--size 256 \
--inodes 8192 \
--populate target/rootfs-busybox/| Use Case | Image Size | RAM | Notes |
|---|---|---|---|
| Quick testing | 64MB | 2048M | Minimal rootfs, limited scratch space |
| Development (default) | 256MB | 2048M | Full rootfs + native compilation output |
| Heavy compilation | 512MB | 2048M | Large build trees, multiple packages |
The 256MB default provides approximately 190MB of usable data space after metadata overhead (superblock, bitmap, inode table). This is sufficient for the full BusyBox rootfs (~58MB unpacked), GCC/binutils/make/ninja toolchain, and substantial scratch space for native compilation.
The --size flag accepts values in megabytes. You can override the default with the BLOCKFS_SIZE environment variable when using the build script:
BLOCKFS_SIZE=512 ./scripts/build-busybox-rootfs.sh blockfs# Build the kernel (if not already built)
./build-kernel.sh x86_64 dev
# Boot with BlockFS (serial only)
./scripts/run-veridian.sh --blockfs
# Boot with BlockFS and framebuffer display
./scripts/run-veridian.sh --blockfs --displayqemu-system-x86_64 -enable-kvm \
-drive if=pflash,format=raw,readonly=on,file=/usr/share/edk2/x64/OVMF.4m.fd \
-drive id=disk0,if=none,format=raw,file=target/x86_64-veridian/debug/veridian-uefi.img \
-device ide-hd,drive=disk0 \
-drive file=target/rootfs-blockfs.img,if=none,id=vd0,format=raw \
-device virtio-blk-pci,drive=vd0 \
-serial stdio -display none -m 2048MKey differences from TAR mode:
- The rootfs drive points to
target/rootfs-blockfs.imginstead oftarget/rootfs-busybox.tar - RAM is 2048M (needed for the 512MB kernel heap plus user-space frame headroom during native compilation)
On boot, the kernel prints one of:
[ROOTFS] BlockFS magic detected -- mounting persistent root
or (for TAR mode):
[ROOTFS] Loading TAR rootfs from virtio-blk...
To confirm that changes persist across reboots:
-
Boot with BlockFS:
./scripts/run-veridian.sh --blockfs
-
Create a test file at the shell prompt:
root@veridian:/# echo "persistence test" > /tmp/test.txt root@veridian:/# sync -
Shut down (Ctrl+C or
poweroff). -
Boot again:
./scripts/run-veridian.sh --blockfs
-
Read the file:
root@veridian:/# cat /tmp/test.txt persistence test
If the file contents survive the reboot, persistence is working correctly.
BlockFS buffers writes in kernel memory and flushes them to disk via the sync and fsync system calls:
-
sync-- Flushes all dirty BlockFS metadata (superblock, bitmap, inode table) and data blocks to the virtio-blk device. Available as thesyncshell command andSYS_SYNCsyscall. -
fsync(fd)-- Flushes all pending writes for a specific open file descriptor. Available as theSYS_FSYNCsyscall.
The kernel also performs a sync during clean shutdown. However, if QEMU is terminated abruptly (e.g., kill -9), unflushed data may be lost. Always run sync before shutting down if you have important unflushed writes.
The kernel determines the rootfs format at boot by probing the first block (sector 0) of the virtio-blk device:
- Read the first 4096 bytes from the virtio-blk device.
- Extract the first 4 bytes as a little-endian
u32. - Compare against
BLOCKFS_MAGIC(0x424C4B46, ASCII "BLKF"). - If the magic matches, mount as BlockFS (persistent root).
- Otherwise, treat the entire device as a TAR archive and extract into RamFS.
This logic lives in kernel/src/bootstrap.rs (load_rootfs_from_virtio()). No user configuration is needed -- the kernel automatically selects the correct handler based on the image content.
The convenience script (run-veridian.sh --blockfs) expects the image at target/rootfs-blockfs.img. Create it with:
./scripts/build-busybox-rootfs.sh all # Build rootfs directory
./scripts/build-busybox-rootfs.sh blockfs # Create BlockFS imageA previous QEMU instance is still holding the disk image lock. Kill it:
pkill -9 -f qemu-system
sleep 2Then retry. The convenience script does this automatically.
Ensure OVMF firmware is installed and the path is correct. The convenience script auto-detects it. For manual QEMU commands, verify the -drive if=pflash,... path matches your distribution:
| Distribution | OVMF Path |
|---|---|
| CachyOS / Arch | /usr/share/edk2/x64/OVMF.4m.fd |
| Ubuntu / Debian | /usr/share/OVMF/OVMF_CODE.fd |
| Fedora | /usr/share/edk2/ovmf/OVMF_CODE.fd |
If the image becomes corrupted (e.g., kernel panic during write, QEMU killed without sync), recreate it:
./scripts/build-busybox-rootfs.sh blockfsThere is currently no fsck-equivalent for BlockFS. Rebuilding the image from the host rootfs directory is the recommended recovery path.
The default configuration provides generous inode counts (1 inode per 16KB of image space). If you run out of inodes with many small files, specify a higher count:
./tools/mkfs-blockfs/target/x86_64-unknown-linux-gnu/release/mkfs-blockfs \
--output target/rootfs-blockfs.img \
--size 256 \
--inodes 16384 \
--populate target/rootfs-busybox/For running out of data blocks, increase the image size with --size.
| File | Purpose |
|---|---|
kernel/src/fs/blockfs.rs |
BlockFS kernel driver (mount, read, write, sync) |
kernel/src/bootstrap.rs |
Auto-detection and rootfs mounting at boot |
kernel/src/syscall/filesystem.rs |
sys_sync() and sys_fsync() syscall implementations |
tools/mkfs-blockfs/src/main.rs |
Host-side image creation tool |
scripts/build-busybox-rootfs.sh |
Build script with blockfs phase |
scripts/run-veridian.sh |
Convenience QEMU launcher with --blockfs flag |
- BUILD-INSTRUCTIONS.md -- Full build workflow including persistent storage
- SELF-HOSTING-STATUS.md -- Self-hosting toolchain tiers
- SOFTWARE-PORTING-GUIDE.md -- Porting software to VeridianOS