Skip to content

arcboxlabs/ext4-rs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

arcbox-ext4

Pure-Rust ext4 filesystem formatter and reader.
No kernel mount. No FUSE. No C dependencies.

crates.io docs.rs License


arcbox-ext4 creates and reads ext4 filesystem images entirely in userspace. It is designed for one job: converting OCI container image layers into mountable ext4 block devices on macOS and Linux, without needing mkfs.ext4, libext2fs, or any Linux tools on the host.

This is the first pure-Rust ext4 mkfs implementation.

Why

Container runtimes on macOS need to build ext4 root filesystems from OCI image layers. The standard approach requires either shelling out to Linux mkfs.ext4 (not available on macOS) or linking against C libraries like lwext4. This crate does it in pure Rust.

Inspired by Apple's ContainerizationEXT4 (the Swift ext4 implementation in Apple's open-source container runtime), then audited line-by-line against it and the ext4 spec.

Features

Formatter Create ext4 images from scratch -- superblock, group descriptors, inode table, bitmaps, extent trees
Reader Open existing ext4 images -- path resolution, symlink following, file reading
OCI Unpack Stream tar layers directly into ext4 with full OCI whiteout support
Extended Attributes Inline (in-inode) and block-level xattrs with name compression
Hard Links Correct reference counting with deferred block reclamation
Symlinks Fast symlinks (inline, < 60 bytes) and slow symlinks (data blocks)

Quick Start

[dependencies]
arcbox-ext4 = "0.1"

Create an ext4 image

use std::path::Path;
use arcbox_ext4::{Formatter, constants::{make_mode, file_mode}};

let mut fmt = Formatter::new(Path::new("rootfs.ext4"), 4096, 64 * 1024 * 1024)?;

// Create directories and files.
fmt.create("/etc", make_mode(file_mode::S_IFDIR, 0o755),
    None, None, None, None, None, None)?;
fmt.create("/etc/hostname", make_mode(file_mode::S_IFREG, 0o644),
    None, None, Some(&mut b"arcbox\n".as_slice()), None, None, None)?;

// Create a symlink.
fmt.create("/etc/localtime", make_mode(file_mode::S_IFLNK, 0o777),
    Some("/usr/share/zoneinfo/UTC"), None, None, None, None, None)?;

// Finalize -- writes superblock, group descriptors, bitmaps, inode table.
fmt.close()?;

Read an ext4 image

use arcbox_ext4::Reader;

let mut reader = Reader::new(std::path::Path::new("rootfs.ext4"))?;

// Check existence, list directories, read files.
assert!(reader.exists("/etc/hostname"));
let entries = reader.list_dir("/etc")?;
let data = reader.read_file("/etc/hostname", 0, None)?;
assert_eq!(&data, b"arcbox\n");

Unpack OCI layers

use arcbox_ext4::Formatter;

let mut fmt = Formatter::new(path, 4096, 512 * 1024 * 1024)?;

// Apply layers in order. Whiteouts (.wh.* and .wh..wh..opq) are handled.
fmt.unpack_tar(layer1_reader)?;
fmt.unpack_tar(layer2_reader)?;

fmt.close()?;

Architecture

                    ┌─────────────┐
  OCI tar layers ──▶│  unpack.rs  │
                    └──────┬──────┘
                           ▼
                    ┌─────────────┐         ┌─────────────┐
    user code ────▶ │ formatter.rs│────────▶│   .ext4     │
                    └─────────────┘  close()│   image     │
                                            └──────┬──────┘
                                                   ▼
                                            ┌─────────────┐
                    user code ────────────▶ │  reader.rs  │
                                            └─────────────┘

Internally, the formatter writes data sequentially (files, then directories, then metadata) and computes the final layout at close() time:

  1. File/symlink data blocks are appended as create() is called
  2. Directory entries are committed in BFS order (sorted for e2fsck)
  3. Block group layout is optimized to minimize group count
  4. Inode table, block/inode bitmaps, group descriptors, and superblock are written last

ext4 Feature Flags

Flag Status
extents Enabled (extent trees, not legacy block maps)
filetype Enabled (directory entries store file type)
flex_bg Enabled (flexible block groups)
sparse_super2 Enabled
huge_file Enabled (block-unit i_blocks counting)
extra_isize Enabled (256-byte inodes with inline xattrs)
ext_attr Enabled
has_journal Not supported (not needed for container rootfs)
metadata_csum Not supported
64bit Not supported (32-bit block addresses, max 16 TiB)

Limitations

  • Block size is fixed at 4096 bytes
  • Maximum file size: 128 GiB
  • Extent tree depth limited to 1 (sufficient for 128 GiB)
  • No journal -- images are meant to be built once and mounted read-only (or read-write without crash recovery)

Testing

133 tests covering:

  • Struct serialization round-trips for all on-disk types
  • Formatter + Reader end-to-end (files, dirs, symlinks, hardlinks, xattrs)
  • OCI two-layer rootfs simulation (Alpine-like)
  • Low-level struct validation (superblock fields, group descriptors, bitmaps, inode table)
  • Error paths, symlink loops, boundary conditions
  • Bug regression tests (hardlink reclaim, symlink classification, block counting)
cargo test

Acknowledgments

Architecture inspired by Apple's ContainerizationEXT4 Swift implementation, then audited line-by-line against it and the ext4 disk layout specification.

License

Licensed under either of

at your option.

About

Pure-Rust ext4 filesystem formatter and reader. No kernel mount, no FUSE, no C dependencies.

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages