erofs #
EROFS images written and read in pure OCaml.
I wanted read-only root filesystems whose updates stay small: change one file,
ship roughly that file. EROFS, the read-only filesystem in the Linux kernel,
can do it if the image is laid out with care, and this library writes such
images. Erofs.write gives you two strings. meta holds the superblock, the
inodes and the directories, plus any small file, which sits right after its
inode. data holds the 4 KiB pages of the remaining regular files. Glue them
together with Erofs.device and the kernel mounts the result as one block
device (mount -t erofs, no device= option needed).
Layout #
Each large file is stored chunk-based. Its inode is followed by an 8-byte index per page, and each index points into the data device, which the device table maps just past the metadata. Page addresses come from the names and the sizes in the tree. File contents never move anything, so if you rewrite some bytes of a file in place, only those data pages change and the metadata image is byte-for-byte the same.
Size changes are handled by ~previous. Give write the metadata image of
the release you are replacing and it keeps each inode and each page where it
was, as long as it still fits. When one file grows or shrinks, the new image
differs from the old one in four places: that file's pages, its inode, the
directory entry naming it, and block 0 (the superblock). How many other files
the tree has makes no difference to the size of the update.
Small files skip the data device. A compact inode (struct erofs_inode_compact) takes 32 bytes, and the kernel will not read inline data
that runs across a 4096-byte block boundary. An inode placed at the start of a
block has 4096 - 32 = 4064 bytes after it, which is Erofs.inline_max. A file
up to that size costs its own length in the metadata, where a chunk-based file
would cost a full page.
Nothing else goes into the output: the same tree, timestamp and previous image
always give the same bytes. Erofs.read parses these images and uncompressed
images made by mkfs.erofs, and bounds-checks every offset it follows.
Installation #
Install with opam:
$ opam install erofs
If opam cannot find the package, it may not yet be released in the public
opam-repository. Add the overlay repository, then install it:
$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam update
$ opam install erofs
Usage #
let image =
Erofs.write
(Erofs.dir
[ ("hello", Erofs.file "hello, world\n");
("bin", Erofs.dir [ ("sh", Erofs.symlink "/bin/busybox") ]) ])
let () =
match Erofs.read (Erofs.device image) with
| Ok _ -> ()
| Error (`Msg e) -> failwith e
Testing against Linux #
test/linux_check.sh runs fsck.erofs on written images, then mounts them
and walks the mount, checking each entry against the tree it was built from.
References #
- Linux,
fs/erofs/erofs_fs.handDocumentation/filesystems/erofs.rst. - erofs-utils,
mkfs.erofs --chunksize --blobdev.