fuse2fs cannot stand in for the loop device, because the loop device is exposing a partition table
scope: generic · severity: finding · confidence: proven · subsystem: build
The question — the sandbox design needs pmbootstrap install to build a
flashable image without a loop device, because a rootless container cannot have
one. fuse2fs mounts ext4 from a plain file inside a user namespace, which
looked like the answer. Is it?
The answer — no, and the reason is structural rather than a missing feature.
fuse2fs mounts a filesystem. The loop device is not being used to mount a
filesystem: it is being used to expose a partitioned disk so the kernel
will create partition block devices from its table.
The chain, in pmbootstrap 3.11.1:
| step | what it needs |
|---|---|
pmb/chroot/mount.py:34 |
binds the loop device into the chroot as /dev/install |
pmb/install/partition.py:58-61 |
runs parted on /dev/install, which makes the KERNEL create /dev/installp1, p2, p3 |
pmb/install/format.py:260-266 |
runs mkfs on /dev/installp1 and /dev/installp2 — partition devices, not files |
pmb/install/partition.py:16-41 |
mounts those partition devices |
There is no point in that chain where a filesystem image is mounted from a
plain file. Every consumer wants a partition block device, and only the
kernel’s partition scanner produces one — which is what the loop device is
there to trigger. fuse2fs cannot produce /dev/installp1, so there is
nothing for it to substitute for.
--split does not help: it creates separate boot and root images and then
calls losetup.mount on each of them anyway.
What this rules out — the “shim” framing. docs/SANDBOX-PROVISIONING.md §4
described replacing losetup with a no-op and mount /dev/loopN with
fuse2fs <img> <mnt>, and said pmbootstrap would keep owning partition layout
and fstab. That is not a change that can be made: the code between those two
points is the part that needs a block device.
What is still true, and is the real opening. Two facts survive:
pmbootstrap install --no-imagenever reaches any of this._install.pyreturns beforeinstall_system_image(), which is the only caller ofblockdevice.create(). Verified by reading, not run.mkfs.ext4does not need a block device. It works on a plain file, andmkfs.ext4 -d <dir>will POPULATE a filesystem from a directory with no mount at all — no loop device, no fuse, no privilege.
So an unprivileged image build is still possible; it is just a different shape.
Build each partition as its own filesystem image with mkfs.ext4 -d, then
assemble the combined image by hand: truncate it, write the table with
sfdisk (which operates happily on a regular file), and dd each filesystem
image to its offset.
The cost of that shape, stated plainly, because it is the reason the shim
was preferred in the first place: porthole would then own the partition layout
and the filesystem UUIDs that boot.img hard-codes in its cmdline. ph-build.sh:510
records a real device failure from exactly that pair disagreeing — a boot.img
wanting 6805a9af-… while the device held 39056921-…, which presents as a
phone dropping to a telnet debug shell. Duplicating that logic is how it drifts.
Not yet established — whether mkfs.ext4 -d reproduces what pmbootstrap’s
own mkfs invocation produces closely enough (it passes -O feature flags and
a label; see pmb/install/format.py:40-49), and whether the assembled image boots. Both
need a real build, which needs the pmbootstrap work directory — so neither was
run while another agent was using it.
