Files
Deshab-OS/CODE/linux/pack_system_image.sh
LinehuandCursor 8554a82677 feat(panic): FUCK [panic] mode=strict|fallback + realtest image variant
- UTSM panic_full returns in fallback mode (logs [PANIC:FALLBACK] + code + hit
  count); panic_halt keeps always-halt sites (CPU exception, VMM-E11 LA57,
  VMM-E15 DSK jump, UTSM-E42 test hook). Call sites given safe continuations.
- Mode passed to DSK/FirstInit/login/desktop via DSK_BOOT_FLAG_PANIC_FALLBACK;
  DKM kernel_api.panic / kern.log.panic return to driver; ext4 fatal aborts the
  op via setjmp/longjmp and DSK falls back to FAT32 ESP.
- build.ps1/Makefile: dev=strict, release=fallback, new realtest (release
  options + strict); default -Variant all; serialized iso packing under -j.
- Fix '*/' inside comments (dkm.h, kernel_api.h, bus.c) that broke the build.
- Includes pending work: dkm_bus service bus, mt7920/wlan drivers, desktop
  files/startmenu/prank, pe-tools, diskinfo, docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-12 23:57:19 +08:00

320 lines
12 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
#
# Pack SYSTEM/ into a GPT disk:
# p1 FAT32 ESP (EF00) /EFI /limine /boot /driver /system
# Limine 10 cannot read ext4; every boot(): module must live here.
# p2 ext4 (8300) the rest of SYSTEM/ (and a copy of driver/system)
#
# Usage:
# sudo bash CODE/linux/pack_system_image.sh <SYSTEM_DIR> <OUTPUT_IMG>
#
# FAT32 still cannot hold a single file >= 4GiB. Keep linux-extra-rootfs.img
# under that limit while it lives in SYSTEM/boot.
set -Eeuo pipefail
if [[ $# -ne 2 ]]; then
echo "Usage: $0 <SYSTEM_DIR> <OUTPUT_IMG>" >&2
exit 2
fi
SYSTEM="$(cd "$1" && pwd)"
OUT="$2"
OUT_DIR="$(cd "$(dirname "$OUT")" && pwd)"
OUT="$OUT_DIR/$(basename "$OUT")"
# Must match SYSTEM/limine/limine.conf uuid(...) paths.
EXT4_UUID="64657368-6162-4000-8000-000000000002"
DISK_GUID="64657368-6162-4000-8000-000000000001"
ESP_PARTGUID="64657368-6162-4000-8000-0000000000b1"
EXT4_PARTGUID="64657368-6162-4000-8000-0000000000e2"
for tool in sfdisk mkfs.vfat mkfs.ext4 rsync losetup du; do
if ! command -v "$tool" >/dev/null 2>&1; then
echo "[pack] ERROR: missing tool: $tool" >&2
exit 2
fi
done
if [[ ! -d "$SYSTEM/boot" || ! -d "$SYSTEM/EFI" || ! -f "$SYSTEM/limine/limine.conf" ]]; then
echo "[pack] ERROR: SYSTEM/ must contain boot/, EFI/, and limine/limine.conf" >&2
exit 2
fi
bytes_of() {
local p="$1"
if [[ -e "$p" ]]; then
du -sb --apparent-size "$p" | awk '{print $1}'
else
echo 0
fi
}
align_up() {
local val="$1"
local align="$2"
echo $(( (val + align - 1) / align * align ))
}
# Sum each SYSTEM child so 9p/du-on-parent cannot drop boot/.
boot_bytes="$(bytes_of "$SYSTEM/boot")"
efi_bytes="$(bytes_of "$SYSTEM/EFI")"
limine_bytes="$(bytes_of "$SYSTEM/limine")"
driver_bytes="$(bytes_of "$SYSTEM/driver")"
sys_bytes="$(bytes_of "$SYSTEM/system")"
# M3: 桌面/开始菜单经 DSK FAT32 只读 ESP —— PE 应用目录与配置必须落 ESP。
bin_bytes="$(bytes_of "$SYSTEM/bin")"
program_bytes="$(bytes_of "$SYSTEM/program")"
lib_bytes="$(bytes_of "$SYSTEM/lib")"
esp_cnf_bytes=0
for cnf in PEAPPS.CNF LINUXAPP.CNF STARTMENU.CNF; do
if [[ -f "$SYSTEM/$cnf" ]]; then
esp_cnf_bytes=$((esp_cnf_bytes + $(bytes_of "$SYSTEM/$cnf")))
fi
done
esp_content=$((boot_bytes + efi_bytes + limine_bytes + driver_bytes + sys_bytes \
+ bin_bytes + program_bytes + lib_bytes + esp_cnf_bytes))
rest_bytes=0
shopt -s dotglob nullglob
for child in "$SYSTEM"/*; do
name="$(basename "$child")"
case "$name" in
boot|EFI|limine) continue ;;
esac
rest_bytes=$((rest_bytes + $(bytes_of "$child")))
done
shopt -u dotglob nullglob
# FAT32/ext4 metadata slack: 15% + 64MiB, 1MiB aligned.
esp_need=$((esp_content + esp_content / 7 + 64 * 1024 * 1024))
ext4_need=$((rest_bytes + rest_bytes / 7 + 64 * 1024 * 1024))
[[ "$esp_need" -lt $((256 * 1024 * 1024)) ]] && esp_need=$((256 * 1024 * 1024))
# User-facing data partition: keep at least 2GiB even when SYSTEM/ is small.
[[ "$ext4_need" -lt $((2048 * 1024 * 1024)) ]] && ext4_need=$((2048 * 1024 * 1024))
MIB=$((1024 * 1024))
SECTOR=512
esp_bytes="$(align_up "$esp_need" "$MIB")"
ext4_bytes="$(align_up "$ext4_need" "$MIB")"
esp_sectors=$((esp_bytes / SECTOR))
ext4_sectors=$((ext4_bytes / SECTOR))
# 1MiB protective gap + backup GPT
gpt_front=2048
gpt_back=2048
total_sectors=$((gpt_front + esp_sectors + ext4_sectors + gpt_back))
total_bytes=$((total_sectors * SECTOR))
extra_bytes="$(bytes_of "$SYSTEM/boot/linux-extra-rootfs.img")"
echo "[pack] GPT dual partition (NOT a single FAT32 disk):"
echo "[pack] p1 FAT32 ESP = Limine + /boot modules (Limine 10 cannot read ext4)"
echo "[pack] p2 ext4 = rest of SYSTEM/ uuid=$EXT4_UUID"
echo "[pack] SYSTEM=$SYSTEM"
echo "[pack] ESP content=$((esp_content / 1024 / 1024)) MB partition=$((esp_bytes / 1024 / 1024)) MB"
echo "[pack] linux-extra-rootfs.img=$((extra_bytes / 1024 / 1024)) MB (must stay on p1 FAT32)"
echo "[pack] ext4 content=$((rest_bytes / 1024 / 1024)) MB partition=$((ext4_bytes / 1024 / 1024)) MB"
echo "[pack] disk=$((total_bytes / 1024 / 1024)) MB -> $OUT"
WORK="$(mktemp -d /var/tmp/deshab_pack.XXXXXX)"
IMG="$WORK/disk.img"
MNT_ESP="$WORK/mnt_esp"
MNT_EXT="$WORK/mnt_ext"
LOOP=""
cleanup() {
set +e
if [[ -n "${MNT_ESP:-}" ]]; then
umount "$MNT_ESP" 2>/dev/null
fi
if [[ -n "${MNT_EXT:-}" ]]; then
umount "$MNT_EXT" 2>/dev/null
fi
if [[ -n "${LOOP:-}" ]]; then
losetup -d "$LOOP" 2>/dev/null
fi
rm -rf "$WORK"
}
trap cleanup EXIT
mkdir -p "$MNT_ESP" "$MNT_EXT"
# Prefer a sparse image so the 9p copy back to Windows is smaller when possible.
truncate -s "$total_bytes" "$IMG"
esp_start="$gpt_front"
ext4_start=$((esp_start + esp_sectors))
sfdisk --no-reread "$IMG" <<EOF
label: gpt
label-id: ${DISK_GUID}
first-lba: ${gpt_front}
sector-size: 512
start=${esp_start}, size=${esp_sectors}, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=${ESP_PARTGUID}, name=DESHABBOOT
start=${ext4_start}, size=${ext4_sectors}, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=${EXT4_PARTGUID}, name=DESHAB
EOF
LOOP="$(losetup --find --show --partscan "$IMG")"
echo "[pack] loop=$LOOP"
echo "[pack] partition table:"
sfdisk -l "$IMG" || true
p1=""
p2=""
for _ in $(seq 1 25); do
if [[ -b "${LOOP}p1" && -b "${LOOP}p2" ]]; then
p1="${LOOP}p1"
p2="${LOOP}p2"
break
fi
# Some kernels expose /dev/loopXp1 only after partx.
if command -v partx >/dev/null 2>&1; then
partx --update "$LOOP" 2>/dev/null || true
fi
sleep 0.2
done
if [[ -z "$p1" || -z "$p2" ]]; then
echo "[pack] ERROR: loop partitions did not appear under $LOOP" >&2
ls -l "$LOOP"* 2>/dev/null || true
exit 1
fi
# ESP 的簇大小必须显式指定为 4 个扇区(2KiB),不能交给 mkfs 自动选。
#
# 历史坑一(OVMF 挂不上):曾经写死 -s 8(4KiB 簇)。256MiB 的 ESP 在该几何下
# 恰好得到 65535 个簇 —— 正是 FAT32 的 0xFFF5 边界 —— OVMF(EDK2 FatPkg) 拒挂
# 该卷,现象是 BdsDxe "Not Found",Shell 里分区只有 BLKn 没有 FS。
#
# 历史坑二(根目录写满):修坑一时改成不指定 -s 交给 mkfs 自动选,它给出
# spc=1(512B/簇)。根目录每簇只能放 16 个目录项,而打包进 ESP 根目录的内容
# (DESHABBOOT/EFI/LIMINE/BOOT/DRIVER/SYSTEM/USER/NVVARS + 它们的 LFN 项)
# 正好 16 项,根目录链又只有单簇 —— 于是 DSK 首次启动写 USER.CONF 时必然
# 拿到 "no free dir entry"(-4)→ FS-E02 panic,系统进不去。
#
# 256MiB ESP 各档簇大小的安全性(每档都算过):
# spc=1 16 项/簇 524288 簇 根目录一簇就满(坑二)
# spc=4 64 项/簇 131072 簇 ✅ 远离 FAT32 下限 65525 与 0xFFF5 边界
# spc=8 128 项/簇 65536 簇 ❌ 落在 65535 边界(坑一)
# spc=16 256 项/簇 32768 簇 ❌ 低于 FAT32 下限 65525
# 所以固定 -s 4:既让根目录有 64 项余量,又不碰任何边界。
mkfs.vfat -F 32 -s 4 -n DESHABBOOT "$p1" >/dev/null
# DSK 内嵌 ext4 读写驱动支持范围:4K block / 256B inode / extent 树,
# 不支持 journal、metadata_csum、64bit(遇到即 panic FS-E28)——
# 因此这里必须显式关闭这三个 feature。
mkfs.ext4 -F -q -L DESHAB -U "$EXT4_UUID" -m 1 \
-b 4096 -I 256 -O ^has_journal,^metadata_csum,^64bit,^resize_inode "$p2"
echo "[pack] filesystems:"
blkid "$p1" "$p2" || true
mount "$p1" "$MNT_ESP"
mount "$p2" "$MNT_EXT"
mkdir -p "$MNT_ESP/EFI" "$MNT_ESP/limine" "$MNT_ESP/boot" "$MNT_ESP/driver" "$MNT_ESP/system"
echo "[pack] copying p1 FAT32 ESP (EFI/ limine/ boot/ driver/ system/)"
# vfat has no Unix owners/perms; -a would fail with rsync code 23.
rsync -rltD --info=progress2 "$SYSTEM/EFI/" "$MNT_ESP/EFI/"
rsync -rltD --info=progress2 "$SYSTEM/limine/" "$MNT_ESP/limine/"
# limine.conf 的 Linux 兼容层 module_path 段必须与 SYSTEM/boot/ 的实际产物一致。
# CODE/linux/build.sh 建完 Linux 内核会自动取消那几行注释,但没有任何地方会关回去;
# 而 055c521 之后 .drv / Linux 产物都不入库,于是「注释开着但文件不存在」会让
# Limine 直接 panic(Failed to open module ...),系统根本进不去。
# 这里按实际存在的文件过滤后再写进 ESP —— 源文件 SYSTEM/limine/limine.conf 保持不动。
#
# 注意:Limine 10 优先读 ESP 上的 /limine/limine.conf(目录形式),其次才是
# 根目录 /limine.conf。上面 rsync 已经把未过滤的原版拷进 /limine/ 了,所以
# 两处都必须写过滤后的副本,否则改根目录那份不起作用。
filter_limine_conf() {
local dst="$1"
python3 - "$SYSTEM/limine/limine.conf" "$SYSTEM/boot" "$dst" <<'PYEOF'
import os, re, sys
src, bootdir, dst = sys.argv[1], sys.argv[2], sys.argv[3]
# Linux/guest 内核段的 module_path(其它 module_path 靠路径前缀区分)
GUEST_PREFIXES = ('/boot/linux-', '/boot/xj380.krl')
lines = open(src, 'r', encoding='utf-8').read().splitlines(keepends=True)
out, dropped, kept = [], [], []
for line in lines:
m = re.match(r'^(\s*)module_path:\s*boot\(\):(\S+)\s*$', line)
if m and m.group(2).startswith(GUEST_PREFIXES):
host = os.path.join(bootdir, m.group(2).split('/boot/', 1)[1])
if os.path.exists(host):
kept.append(m.group(2))
out.append(line)
else:
# 产物不存在 → 注释掉,避免 Limine 加载失败 panic
out.append('%s# [pack] module missing on disk, disabled: %s' % (m.group(1), line))
dropped.append(m.group(2))
else:
out.append(line)
open(dst, 'w', encoding='utf-8').write(''.join(out))
for p in kept:
print('[pack] limine module kept: %s' % p)
for p in dropped:
print('[pack] limine module DISABLED (file absent): %s' % p)
PYEOF
}
mkdir -p "$MNT_ESP/limine"
filter_limine_conf "$MNT_ESP/limine.conf" # 根目录副本
filter_limine_conf "$MNT_ESP/limine/limine.conf" # Limine 10 实际优先读的那份
rsync -rltD --info=progress2 "$SYSTEM/boot/" "$MNT_ESP/boot/"
rsync -rltD --info=progress2 "$SYSTEM/driver/" "$MNT_ESP/driver/"
rsync -rltD --info=progress2 "$SYSTEM/system/" "$MNT_ESP/system/"
if [[ -d "$SYSTEM/user" ]]; then
echo "[pack] copying user/ onto FAT32 ESP for native desktop icons"
rsync -rltD --info=progress2 "$SYSTEM/user/" "$MNT_ESP/user/"
fi
# M3: PE 应用/系统工具与注册表配置 —— DSK FAT32 路径只看得见 ESP:
# bin/ CLI exe(cmd.elf PATH 二级)+ ELF 工具(开始菜单 bin/xxx.elf)
# program/ GUI exe(开始菜单 program/xxx.exe,run_windowed 嵌入)
# lib/ PE 真实 DLL(pe_dll_manager 经 fat32_read_path "lib/..." 加载)
# *.CNF 桌面注册表(PEAPPS/LINUXAPP/STARTMENU)
# 没有这几份拷贝,开始菜单/PE 应用在 GPT 镜像里全部静默失效。
if [[ -d "$SYSTEM/bin" ]]; then
echo "[pack] copying bin/ onto FAT32 ESP (CLI exes + ELF tools)"
rsync -rltD --info=progress2 "$SYSTEM/bin/" "$MNT_ESP/bin/"
fi
if [[ -d "$SYSTEM/program" ]]; then
echo "[pack] copying program/ onto FAT32 ESP (GUI PE apps)"
rsync -rltD --info=progress2 "$SYSTEM/program/" "$MNT_ESP/program/"
fi
if [[ -d "$SYSTEM/lib" ]]; then
echo "[pack] copying lib/ onto FAT32 ESP (PE DLLs)"
rsync -rltD --info=progress2 "$SYSTEM/lib/" "$MNT_ESP/lib/"
fi
for cnf in PEAPPS.CNF LINUXAPP.CNF STARTMENU.CNF; do
if [[ -f "$SYSTEM/$cnf" ]]; then
cp -f "$SYSTEM/$cnf" "$MNT_ESP/$cnf"
fi
done
echo "[pack] copying p2 ext4 data partition"
shopt -s dotglob nullglob
for child in "$SYSTEM"/*; do
name="$(basename "$child")"
case "$name" in
boot|EFI|limine) continue ;;
esac
rsync -a --info=progress2 "$child" "$MNT_EXT/"
done
shopt -u dotglob nullglob
sync
umount "$MNT_ESP"
umount "$MNT_EXT"
losetup -d "$LOOP"
LOOP=""
echo "[pack] writing $OUT"
mkdir -p "$OUT_DIR"
# Sparse copy keeps the Windows-side file smaller when the host supports it.
cp --sparse=always "$IMG" "$OUT"
sync
echo "[pack] done: $OUT"
echo "[pack] GPT p1 FAT32 ESP (Limine/boot) + p2 ext4 uuid=$EXT4_UUID"