max / alloy
1 file changed,
+69 insertions,
-4 deletions
| @@ -262,11 +262,15 @@ | |||
| 262 | 262 | if command -v mcopy >/dev/null; then | |
| 263 | 263 | mcopy -i "$WORK/efiboot.img" -s "$WORK/iso/EFI/BOOT/"* ::/EFI/BOOT/ 2>/dev/null || true | |
| 264 | 264 | fi | |
| 265 | - | cp "$WORK/efiboot.img" "$WORK/iso/efiboot.img" | |
| 265 | + | # Deliberately not copied into $WORK/iso. It is appended to the medium as a | |
| 266 | + | # real partition below, and El Torito is pointed at that partition rather | |
| 267 | + | # than at a file in the tree, so a copy inside the ISO filesystem would be | |
| 268 | + | # a second copy of these same bytes that nothing reads. | |
| 266 | 269 | ||
| 267 | 270 | # --------------------------------------------------------------------- | |
| 268 | 271 | # 5. Assemble. UEFI only, matching the images bootc produces: they have no | |
| 269 | - | # legacy BIOS path, so a hybrid MBR would advertise a boot that fails. | |
| 272 | + | # legacy BIOS path, so the MBR must be protective and must not advertise | |
| 273 | + | # a BIOS boot that would fail. | |
| 270 | 274 | # --------------------------------------------------------------------- | |
| 271 | 275 | say "xorriso" | |
| 272 | 276 | # -rock is not decoration here. An OCI layout stores every blob under a | |
| @@ -275,17 +279,78 @@ | |||
| 275 | 279 | # install fails looking like a corrupt image rather than a naming problem. | |
| 276 | 280 | # The squashfs and the boot files have short names and never needed it, | |
| 277 | 281 | # which is why it was absent until the install source became a directory. | |
| 282 | + | # | |
| 283 | + | # The partition table is written by -append_partition + -appended_part_as_gpt, | |
| 284 | + | # not by -isohybrid-gpt-basdat, which is what this call used to pass. That | |
| 285 | + | # option is a no-op in this shape: it describes an El Torito image that was | |
| 286 | + | # opened with -eltorito-alt-boot and there is no primary entry for it to be | |
| 287 | + | # an alternative to, so xorriso emitted no table and reported no error. The | |
| 288 | + | # result was an ISO whose first 4096 bytes were zero, i.e. no MBR, no GPT | |
| 289 | + | # and no discoverable ESP, which left booting a USB stick up to how lenient | |
| 290 | + | # the firmware felt. It survived because every QEMU test attached the ISO | |
| 291 | + | # with -cdrom, and optical emulation boots El Torito by design and never | |
| 292 | + | # looks for a partition table. | |
| 293 | + | # | |
| 294 | + | # Appending the ESP instead gives the firmware the removable-media path it | |
| 295 | + | # actually specifies: a protective 0xEE MBR, a GPT, and the ESP as a typed | |
| 296 | + | # partition. `-e --interval:appended_partition_2:all::` keeps El Torito as | |
| 297 | + | # well, pointed at those same bytes, so the optical path is unchanged. | |
| 298 | + | # | |
| 299 | + | # fdisk reports three partitions, not two. The third is the 300 KiB of | |
| 300 | + | # padding xorriso appends by default to work around kernels that read past | |
| 301 | + | # the end of the medium; -appended_part_as_gpt describes every region of | |
| 302 | + | # the image, padding included. It is expected, not a stray partition. | |
| 278 | 303 | xorriso -as mkisofs \ | |
| 279 | 304 | -iso-level 3 \ | |
| 280 | 305 | -rock \ | |
| 281 | 306 | -volid "$VOLID" \ | |
| 282 | 307 | -appid "Alloy Installer" \ | |
| 308 | + | -append_partition 2 C12A7328-F81F-11D2-BA4B-00A0C93EC93B "$WORK/efiboot.img" \ | |
| 309 | + | -appended_part_as_gpt \ | |
| 283 | 310 | -eltorito-alt-boot \ | |
| 284 | - | -e efiboot.img \ | |
| 311 | + | -e --interval:appended_partition_2:all:: \ | |
| 285 | 312 | -no-emul-boot \ | |
| 286 | - | -isohybrid-gpt-basdat \ | |
| 287 | 313 | -output "$OUTPUT/install.iso" \ | |
| 288 | 314 | "$WORK/iso" | |
| 289 | 315 | ||
| 316 | + | # --------------------------------------------------------------------- | |
| 317 | + | # 6. Read the boot structures back. | |
| 318 | + | # | |
| 319 | + | # The bug above shipped because the build trusted a flag that exited 0. | |
| 320 | + | # These checks read the bytes the firmware reads, so a table that silently | |
| 321 | + | # fails to appear fails the build instead of the stick. | |
| 322 | + | # --------------------------------------------------------------------- | |
| 323 | + | say "checking the partition table" | |
| 324 | + | at() { dd if="$OUTPUT/install.iso" bs=1 skip="$1" count="$2" 2>/dev/null | od -An -tx1 | tr -d ' \n'; } | |
| 325 | + | ||
| 326 | + | [ "$(at 510 2)" = "55aa" ] \ | |
| 327 | + | || { echo "no MBR signature at offset 510; the ISO has no partition table" >&2; exit 1; } | |
| 328 | + | [ "$(at 450 1)" = "ee" ] \ | |
| 329 | + | || { echo "MBR partition 1 is not type 0xEE; the MBR is not protective" >&2; exit 1; } | |
| 330 | + | [ "$(at 446 1)" = "00" ] \ | |
| 331 | + | || { echo "MBR partition 1 is marked bootable; this image has no BIOS path" >&2; exit 1; } | |
| 332 | + | [ "$(at 462 16)" = "$(printf '00%.0s' $(seq 16))" ] \ | |
| 333 | + | || { echo "MBR partition 2 is populated; only a protective entry belongs here" >&2; exit 1; } | |
| 334 | + | [ "$(dd if="$OUTPUT/install.iso" bs=512 skip=1 count=1 2>/dev/null | head -c 8)" = "EFI PART" ] \ | |
| 335 | + | || { echo "no GPT header at LBA 1" >&2; exit 1; } | |
| 336 | + | ||
| 337 | + | # Every check above passes on a GPT that describes no ESP at all, which is the | |
| 338 | + | # one thing this shape exists to add, so read the entry array and find it. The | |
| 339 | + | # type GUID is stored mixed-endian: the first three fields little-endian, the | |
| 340 | + | # last two as written, so C12A7328-F81F-11D2-BA4B-00A0C93EC93B lands on disk as | |
| 341 | + | # the bytes below. A 16-byte substring match is not entry-aligned, but a GUID | |
| 342 | + | # occurring by chance anywhere else in the array is not a thing that happens. | |
| 343 | + | le() { local h=$1 out="" i; for ((i = ${#h} / 2 - 1; i >= 0; i--)); do out="$out${h:$((i * 2)):2}"; done; printf '%d\n' "0x$out"; } | |
| 344 | + | ENTRY_LBA=$(le "$(at $((512 + 72)) 8)") | |
| 345 | + | ENTRY_COUNT=$(le "$(at $((512 + 80)) 4)") | |
| 346 | + | ENTRY_SIZE=$(le "$(at $((512 + 84)) 4)") | |
| 347 | + | ARRAY=$(dd if="$OUTPUT/install.iso" bs=512 skip="$ENTRY_LBA" \ | |
| 348 | + | count=$(( (ENTRY_COUNT * ENTRY_SIZE + 511) / 512 )) 2>/dev/null | od -An -tx1 | tr -d ' \n') | |
| 349 | + | case "$ARRAY" in | |
| 350 | + | *28732ac11ff8d211ba4b00a0c93ec93b*) ;; | |
| 351 | + | *) echo "the GPT describes no EFI system partition" >&2; exit 1 ;; | |
| 352 | + | esac | |
| 353 | + | say "protective MBR, GPT at LBA 1, ESP appended as partition 2" | |
| 354 | + | ||
| 290 | 355 | chmod 0644 "$OUTPUT/install.iso" | |
| 291 | 356 | say "built $(du -h "$OUTPUT/install.iso" | cut -f1) at $OUTPUT/install.iso" |