Skip to content

Commit 57c4332

Browse files
committed
fix(ungrub): replicate GRUB EFI core to all boot-pool members
install_grub installed the core to only the target device's ESP, while grub-install rebuilt the shared modules under /boot/grub. On a mirror that left every other member's ESP with a stale core that no longer matched the rebuilt modules, so those members failed to boot with "symbol 'grub_memcpy' not found" -- silently destroying boot redundancy. - install_grub now copies the freshly built core to every other member's ESP (no-op for a single-device pool). - new "mkbootable sync <good-device>" op re-distributes the core from a known good member to all others, to repair members that already drifted (eg a device added via a bare "zpool attach"). install_efi_core writes safely: - if the target ESP is already mounted, write through that mount (a raw mtools write under a live mount can be clobbered by the mount's cache flush); otherwise write the raw FAT with mtools, no mount needed. - stage as BOOTX64.EFI.new then rename, so an interrupted write can't leave a torn core. The core uses a dynamic ($root) prefix so the same image is valid on every member. Split into member_esp / install_efi_core / replicate_grub. Both write paths and the mtools round-trip verified byte-faithful and idempotent.
1 parent bccf49e commit 57c4332

1 file changed

Lines changed: 77 additions & 0 deletions

File tree

ungrub/mkbootable

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
# Reconfigure grub.cfg and theme.txt after UUID change
1414
# mkbootable reconfigure
1515

16+
# Copy the EFI core from a known-good member to every other member's ESP
17+
# mkbootable sync <good-device>
18+
1619
#set -euo pipefail
1720
#set -x
1821
source /etc/rc.d/rc.runlog
@@ -82,6 +85,59 @@ resize_partitions() {
8285
udevadm settle
8386
}
8487

88+
# resolve a boot-pool member partition to that disk's EFI System Partition,
89+
# eg /dev/nvme1n1p3 -> /dev/nvme1n1p2, /dev/sdb3 -> /dev/sdb2
90+
member_esp() {
91+
local part="$1" disk sep
92+
disk="$(lsblk -no pkname "$part" 2>/dev/null | head -1)"
93+
[[ -n "$disk" ]] || return 1
94+
[[ $disk == *[0-9] ]] && sep="p" || sep=""
95+
printf '/dev/%s%s2\n' "$disk" "$sep"
96+
}
97+
98+
# write the EFI core ($2) onto a single ESP ($1). Staged as BOOTX64.EFI.new then
99+
# renamed, so an interrupted write can't leave a torn core. If the ESP is already
100+
# mounted we write through that mount -- a raw mtools write under a live mount is
101+
# unsafe (the mount's cache can later flush stale data over it). Only when it is
102+
# not mounted do we write the raw FAT with mtools (no mount needed).
103+
install_efi_core() {
104+
local esp="$1" src="$2" mnt dir
105+
mnt="$(findmnt -nro TARGET -S "$esp" 2>/dev/null | head -1)"
106+
if [[ -n "$mnt" ]]; then
107+
dir="$mnt/EFI/BOOT"
108+
mkdir -p "$dir" || return 1
109+
cp -f "$src" "$dir/BOOTX64.EFI.new" || return 1
110+
mv -f "$dir/BOOTX64.EFI.new" "$dir/BOOTX64.EFI" || return 1
111+
sync -f "$mnt"
112+
else
113+
mmd -i "$esp" ::/EFI 2>/dev/null # ensure dir tree exists (ok if already)
114+
mmd -i "$esp" ::/EFI/BOOT 2>/dev/null
115+
mcopy -D o -i "$esp" "$src" ::/EFI/BOOT/BOOTX64.EFI.new || return 1
116+
mdel -i "$esp" ::/EFI/BOOT/BOOTX64.EFI 2>/dev/null
117+
mmove -i "$esp" ::/EFI/BOOT/BOOTX64.EFI.new ::/EFI/BOOT/BOOTX64.EFI || return 1
118+
fi
119+
}
120+
121+
# Copy a known-good EFI core to the ESP of every boot pool member except the one
122+
# it came from. grub-install rebuilds the shared modules under $BOOT/grub but
123+
# writes the core to only one ESP; a member left with a stale core no longer
124+
# matches those modules and fails to boot with "symbol 'grub_memcpy' not found",
125+
# silently breaking the mirror's boot redundancy. The core uses a dynamic
126+
# ($root) prefix, so the same image is valid on every member.
127+
# $1 = path to the known-good BOOTX64.EFI to distribute (on a mounted ESP)
128+
replicate_grub() {
129+
local src="$1" part esp
130+
for part in $(zpool list -v -H -P "$POOL" | awk '/\/dev\// {print $1}'); do
131+
esp="$(member_esp "$part")" || { log "replicate_grub: cannot resolve ESP for $part, skipping"; continue; }
132+
[[ "$esp" == "$EFI_PART" || ! -b "$esp" ]] && continue
133+
if install_efi_core "$esp" "$src"; then
134+
log "replicate_grub: synced EFI core to $esp"
135+
else
136+
log "replicate_grub: failed to sync EFI core to $esp"
137+
fi
138+
done
139+
}
140+
85141
install_grub() {
86142
# format the EFI System Partition (partition 2) with FAT32 and mount
87143
mkfs.fat -F32 -n EFI "$EFI_PART"
@@ -103,6 +159,11 @@ install_grub() {
103159
--modules="part_gpt zfs zfsinfo search search_fs_uuid configfile normal" \
104160
--no-floppy --no-rs-codes
105161

162+
# replicate the core we just built to every other member's ESP so all mirror
163+
# members stay in lockstep with the shared modules rebuilt above (no-op while
164+
# this is the only member)
165+
replicate_grub "$BOOT/efi/EFI/BOOT/BOOTX64.EFI"
166+
106167
# we don't need this mounted anymore
107168
umount "$BOOT/efi"
108169
}
@@ -162,6 +223,19 @@ remove_device() {
162223
fi
163224
}
164225

226+
# Re-distribute the EFI core from a known-good member to all other members.
227+
# Use this to repair members whose ESP holds a stale core (eg a device added
228+
# with a bare "zpool attach", or a member missed by an earlier grub refresh).
229+
sync_device() {
230+
mkdir -p -m 0700 "$BOOT/efi"
231+
if ! mount -o ro "$EFI_PART" "$BOOT/efi" 2>/dev/null; then
232+
log "sync: cannot mount source ESP $EFI_PART"
233+
exit 1
234+
fi
235+
replicate_grub "$BOOT/efi/EFI/BOOT/BOOTX64.EFI"
236+
umount "$BOOT/efi"
237+
}
238+
165239
case "$OPER" in
166240
'add')
167241
add_device
@@ -175,6 +249,9 @@ case "$OPER" in
175249
'reconfigure')
176250
configure_grub
177251
;;
252+
'sync')
253+
sync_device
254+
;;
178255
*)
179256
log "error 2" # shouldn't happen
180257
exit 2

0 commit comments

Comments
 (0)