Building a modern, writable, tiny live ISO for FreeBSD

Squashfs in the FreeBSD kernel — what exists, what's missing, what to actually build.

TL;DR

1. State of squashfs on FreeBSD

1.1 In-tree: nothing

A grep for squashfs across sys/, sbin/, usr.sbin/, contrib/ in the current source tree returns zero hits. There is no module skeleton, no GEOM provider, no VFS implementation. There is no mount_squashfs mount helper, and makefs(8) doesn't know about the format.

1.2 GSoC 2023 — the closest existing kernel work stalled

1.3 squashfuse (FUSE) — works, but not for early boot limited

2. Your four real architecture options

A. Use mkuzip instead of squashfs recommended

Block-compressed read-only image (zlib/lzma/zstd) with random-access TOC. Mounted via geom_uzip(4); you put a UFS on top.

Pros: in-tree, well-tested, used by every FreeBSD live distro, no kernel work needed, fast.
Cons: not squashfs (no Linux interop), no built-in dedup, slightly worse ratios on metadata-heavy trees.

Closest thing to squashfs that already works.

B. Out-of-tree squashfs kernel module heavy lift

Revive the GSoC 2023 branch, package as a port that installs to /boot/modules/squashfs.ko, ship a mount_squashfs binary.

Pros: real squashfs, Linux interop, image portability.
Cons: weeks of kernel work, you become the maintainer, KBI churn at every major release.

C. squashfuse + scaffolding complex

Boot a minimal mfs_root with userland, run squashfuse to mount the image, reboot -r into it.

Pros: uses existing port, no kernel code.
Cons: FUSE in the boot path is fragile; performance hit; more moving parts than (A).

D. tarfs + zstd interesting

Recently merged in-tree (sys/fs/tarfs/): mount a zstd-compressed tar archive read-only as a filesystem. Originally built for FreeBSD container images.

Pros: in-tree, modern, zstd, simpler than uzip+ufs (single archive).
Cons: less battle-tested for live media, no random-access dedup like squashfs, hardlinks/xattr support varies.

3. In-tree vs. out-of-tree

3.1 An out-of-tree filesystem module is fully supported

FreeBSD's loader searches both /boot/kernel and /boot/modules by default — see sys/kern/kern_linker.c and stand/common/module.c:

static char linker_path[MAXPATHLEN] = "/boot/kernel;/boot/modules";
TUNABLE_STR("module_path", linker_path, sizeof(linker_path));

/boot/modules is the conventional drop-zone for third-party modules — it's exactly what nvidia, vendor wifi drivers, and various ports use.

3.2 A filesystem is a regular kernel module

The macro VFS_SET in sys/sys/mount.h is the registration hook, and it's part of the public KBI shipped in /usr/include/sys/. Every in-tree FS uses the same pattern; cd9660, fusefs, autofs, ext2fs, tarfs all build as both static and loadable.

VFS_SET(squashfs_vfsops, squashfs, VFCF_READONLY);
MODULE_VERSION(squashfs, 1);

3.3 Out-of-tree skeleton (the nvidia pattern)

An out-of-tree port-ready Makefile looks like this, leveraging /usr/share/mk/bsd.kmod.mk:

# Makefile in your repo
KMOD=    squashfs
SRCS=    squashfs_vfsops.c squashfs_vnops.c squashfs_io.c \
         squashfs_decompress.c \
         vnode_if.h opt_kstack_pages.h
KMODDIR= /boot/modules        # default is /boot/kernel; set to modules

.include <bsd.kmod.mk>

Build it against installed headers (no source tree required) by setting KERNBUILDDIR=/usr/include/sys. The ports framework (USES=kmod) handles all of this for you.

3.4 KBI caveat

FreeBSD's KBI is stable within a STABLE branch (e.g. 14.x), but not across major versions. You'd ship one binary per major release, or a source port that rebuilds locally. This is the standard third-party module trade-off.

3.5 Verdict

Out-of-tree is the right answer for an experiment, a third-party project, or a "live distro toolkit". You only need to upstream into sys/fs/squashfs if you want it loaded by the loader as part of a release build, or if you want it in /usr/include/sys for other consumers. Even then, you can develop entirely as a port and merge later.

4. Rescue and userland tooling

4.1 Mount helper

Each in-kernel FS has a corresponding sbin/mount_* helper that just calls nmount(2) with the right fstype. Look at sbin/mount_cd9660/ as the template — it's ~250 lines. Your mount_squashfs would be similar.

4.2 Rescue integration

rescue/rescue/Makefile already statically links mount_cd9660, mount_msdosfs, mount_nullfs, mount_unionfs, mount_udf, mount_nfs. Adding mount_squashfs is one line, but only matters if squashfs is in tree. For an out-of-tree port, ship the binary in /usr/local/sbin/.

4.3 mksquashfs / unsquashfs

Already packaged: sysutils/squashfs-tools. You don't have to build these yourself — your live-ISO build script depends on the port.

4.4 makefs(8) extension optional

If you want makefs -t squashfs to work natively (so the release build can produce squashfs images without external tools), add a backend in usr.sbin/makefs/ alongside cd9660.c. This is purely cosmetic — mksquashfs from the port already works.

5. Modern live ISO architecture

5.1 What's wrong with the existing FreeBSD installer ISO?

The release ISO (release/amd64/mkisoimages.sh) is a plain cd9660 image with /etc/fstab set to mount root ro. rc.initdiskless creates tmpfs overlays for /etc, /var, /tmp from cpio templates in /conf/. It works, but it's not compressed and the "writable" parts are limited to a few directories. There's no whole-rootfs writability.

5.2 The Linux pattern, translated to FreeBSD

LayerLinuxFreeBSD equivalent
Compressed read-only basesquashfs (zstd)mkuzip on UFS, OR squashfs (out-of-tree)
Writable overlayOverlayFS (file-level)gunion(8) (block-level, 14.0+) — or unionfs-fuse
Writable backingtmpfs upperswap-backed md(4) via mdconfig -t swap
Pivotswitch_root from initramfsreboot -r (since FreeBSD 11)
Boot media FScd9660 + initramfscd9660 (loader can't read uzip directly; ramdisk image lives inside cd9660)

5.3 Concrete boot flow

  1. Loader (BIOS or EFI) reads cd9660; loads kernel + mfs_root image (small UFS, ~5–10 MB).
  2. Kernel boots with mfs_root as /. Init runs a tiny /sbin/init shell script.
  3. Script: locates the compressed image on the optical/USB media (or in cd9660), attaches it via mdconfig -t vnode -f /cdrom/rootfs.uzip, mounts the resulting /dev/mdN.uzip read-only at /newroot.
  4. Script: creates a swap-backed memdisk for the writable upper (mdconfig -t swap -s 2g), then either
  5. Set vfs.root.mountfrom to the new device, reboot -r.
  6. Kernel re-mounts root from the union device, re-execs /sbin/init from the live system. You're now running with a fully writable rootfs.

5.4 The unionfs trap

From mount_unionfs(8) in the current tree:
"THIS FILE SYSTEM TYPE IS NOT YET FULLY SUPPORTED (READ: IT DOESN'T WORK) AND USING IT MAY, IN FACT, DESTROY DATA ON YOUR SYSTEM."

Don't use it. NomadBSD switched to unionfs-fuse after deadlocks during reboot -r. Your two real overlay choices are gunion(8) (block-level, in-tree as of 14) or unionfs-fuse (file-level, port).

5.5 What existing FreeBSD-derived live distros do

DistroRead-only baseWritable layerPivot
helloSystemmkuzip (UFS inside)Copy into swap-backed memdisk (no overlay; just duplicates)reboot -r
NomadBSDmkuzipunionfs-fusedirect mount
FuryBSDmkuzipsame as helloSystemreboot -r
GhostBSDcd9660full RAM copy (4 GB+)n/a
mfsBSDtar.gz into MFSeverything in RAMn/a

Nobody uses squashfs. Every active project uses mkuzip.

6. reboot -r (reroot)

The FreeBSD analog of Linux's switch_root. Implemented in sbin/init/init.c (the userland half) and sys/kern/kern_shutdown.c (kern_reroot()). Restricted to PID 1.

What it does, mechanically:

  1. Init copies itself into a tmpfs at /dev/reroot and re-execs from there.
  2. The temp init issues reboot(RB_REROOT).
  3. Kernel kills all other processes, unmounts everything except /dev, calls vfs_mountroot() with the new vfs.root.mountfrom.
  4. Kernel re-execs /sbin/init from the new root.

What this means for you:

7. Recommended path forward

7.1 If your goal is a tiny modern writable live ISO ASAP do this

Don't write a kernel module. Build with what's already in the tree:

  1. Stage your rootfs as UFS (use makefs -t ffs).
  2. Compress with mkuzip -j16 -d -s 65536 rootfs.ufs (zstd, 64K blocks). Expect ~40-50% of original size for a typical install.
  3. Build a tiny mfs_root (~5 MB UFS) containing /sbin/init, mdconfig, mount, gunion, your pivot script, and a bare /dev.
  4. Pack everything into a cd9660 with makefs -t cd9660 -o rockridge,bootimage=....
  5. Pivot script: attach uzip → create gunion with swap-backed upper → put UFS on union → reboot -r.
  6. For installation, your installer reroots into the on-disk target.

Reference implementations to read first: helloSystem ISO build, NomadBSD build script.

7.2 If you specifically need squashfs format heavier

  1. Fork the GSoC 2023 branch (Mashijams/freebsd-src).
  2. Rebase onto current main; convert to a standalone repo with the layout squashfs/{Makefile, squashfs_vfsops.c, squashfs_vnops.c, ...}.
  3. Add a mount_squashfs userland binary under squashfs/mount_squashfs/.
  4. Add a ports skeleton (filesystems/squashfs-kmod + USES=kmod) that builds against installed headers and installs to /boot/modules/squashfs.ko.
  5. Write a small test harness — at minimum, mount and read a few reference squashfs images produced by mksquashfs at different compression levels.
  6. Then plug the resulting kmod into the live ISO build above, replacing mkuzip+mdconfig with mksquashfs+mount_squashfs.

You can do this entirely outside the FreeBSD tree. Upstream later if it's healthy.

7.3 If you want squashfs now without writing kernel code workable

Use fusefs-squashfuse from ports as the read-only layer. Boot scaffolding looks like (1) tiny mfs_root with fusefs.ko preloaded, squashfuse in /sbin, (2) script attaches the squashfs image, squashfuse rootfs.sqsh /newroot, (3) gunion-style overlay on top (note: gunion is block-level so it sits under, not above, FUSE — for FUSE you'd need unionfs-fuse instead), (4) reboot -r. Slower than uzip and an extra moving part, but no kernel code.

8. What gets touched, where

ComponentPath (if in tree) or repo (if out)Notes
kernel module sourcesys/fs/squashfs/ or your-repo/squashfs/Out-of-tree fine
module Makefilesys/modules/squashfs/ or repo top-levelKMODDIR=/boot/modules
VFS registrationin your squashfs_vfsops.cVFS_SET(... , VFCF_READONLY)
mount_squashfssbin/mount_squashfs/ or portModel on sbin/mount_cd9660/
mksquashfsnot yoursAlready in sysutils/squashfs-tools
rescue inclusionrescue/rescue/MakefileOnly matters if upstreamed
makefs -t squashfsusr.sbin/makefs/squashfs.cOptional polish
release/livefs buildrelease/amd64/mkisoimages.sh + your build scriptMost realistic to keep entirely out-of-tree as a separate "freebsd-livecd" project repo
boot loader squashfs readerstand/libsa/Only needed if you want to boot directly from squashfs without an mfs_root shim. Significant work — skip it.

9. References

FreeBSD source (this tree)

External — squashfs-on-FreeBSD

External — FreeBSD live-ISO building

External — manuals & reviews


Generated 2026-05-04. Research synthesized from a deep search of /Users/jmaloney/freebsd-src (current main, last commit 045a9ef829fa) plus external research across FreeBSD status reports, GSoC archives, and the active FreeBSD-derived live-distro projects.