diff --git a/config/Config-images.in b/config/Config-images.in index d683e9512e..6b9901b65d 100644 --- a/config/Config-images.in +++ b/config/Config-images.in @@ -313,6 +313,7 @@ menu "Target Images" bool "Build ONIE installer image (self-extracting .bin)" depends on TARGET_x86 depends on GRUB_EFI_IMAGES + select PACKAGE_grub2-editenv help Build a self-extracting ONIE installer .bin for installing OpenWrt on switches that ship with the Open Network Install diff --git a/target/linux/x86/base-files/lib/upgrade/platform.sh b/target/linux/x86/base-files/lib/upgrade/platform.sh index 5dad7a538a..4d47069914 100644 --- a/target/linux/x86/base-files/lib/upgrade/platform.sh +++ b/target/linux/x86/base-files/lib/upgrade/platform.sh @@ -1,9 +1,101 @@ -RAMFS_COPY_BIN='grub-bios-setup' +RAMFS_COPY_BIN='grub-bios-setup grub-editenv' + +find_partname_dev() { + local partname="$1" + local uevent dev + for uevent in /sys/class/block/*/uevent; do + grep -q "^PARTNAME=$partname\$" "$uevent" 2>/dev/null || continue + dev=$(sed -n 's/^DEVNAME=//p' "$uevent") + [ -n "$dev" ] && { echo "/dev/$dev"; return 0; } + done + return 1 +} + +# True when the boot disk hosts ONIE: both ONIE-BOOT and OPENWRT-ROOT +# GPT-labelled partitions present. +is_onie_install() { + grep -q PARTNAME=ONIE-BOOT /sys/class/block/*/uevent 2>/dev/null || return 1 + grep -q PARTNAME=OPENWRT-ROOT /sys/class/block/*/uevent 2>/dev/null || return 1 + return 0 +} + +# Reinstall via ONIE: append the preserved-config tarball to the installer +# image (installer detects it via size check), drop the combined file on +# OPENWRT-ROOT as onie-installer-x86_64, and flip both grubenv files so the +# next boot chainloads ONIE in install mode. ONIE re-runs our installer, +# which recreates OPENWRT-ROOT, extracts the new rootfs, and stashes +# /sysupgrade.tgz for OpenWrt's preinit to restore. +platform_do_upgrade_onie() { + local image="$1" + local backup="$UPGRADE_BACKUP" + + local root_dev onie_dev + root_dev=$(find_partname_dev OPENWRT-ROOT) || { + v "ONIE upgrade: OPENWRT-ROOT not found"; return 1; + } + onie_dev=$(find_partname_dev ONIE-BOOT) || { + v "ONIE upgrade: ONIE-BOOT not found"; return 1; + } + + local root_mnt=/tmp/upgrade-root + local onie_mnt=/tmp/upgrade-onie + mkdir -p "$root_mnt" "$onie_mnt" + + mount -t ext4 -o rw,noatime "$root_dev" "$root_mnt" || { + v "ONIE upgrade: mount $root_dev failed" + return 1 + } + + local bundle="$root_mnt/onie-installer-x86_64" + v "ONIE upgrade: writing bundle to $bundle" + if [ -s "$backup" ]; then + cat "$image" "$backup" > "$bundle" + else + cp "$image" "$bundle" + fi + chmod +x "$bundle" + + # One-shot: OpenWrt's grub.cfg reads next_entry and chainloads ONIE. + grub-editenv "$root_mnt/boot/grub/grubenv" set next_entry=ONIE || { + v "ONIE upgrade: failed to set next_entry in OPENWRT-ROOT grubenv" + sync; umount "$root_mnt" + return 1 + } + sync + umount "$root_mnt" + + mount -o rw,noatime "$onie_dev" "$onie_mnt" || { + v "ONIE upgrade: mount $onie_dev failed" + return 1 + } + grub-editenv "$onie_mnt/grub/grubenv" set onie_mode=install || { + v "ONIE upgrade: failed to set onie_mode in ONIE-BOOT grubenv" + sync; umount "$onie_mnt" + return 1 + } + sync + umount "$onie_mnt" + + v "ONIE upgrade: ready; stage2 will reboot" + return 0 +} platform_check_image() { local diskdev partdev diff [ "$#" -gt 1 ] && return 1 + if is_onie_install; then + [ "$(get_magic_word "$1")" = "2321" ] || { + v "Invalid image: expected ONIE installer (shell script)" + return 1 + } + head -c 4096 "$1" | grep -q '^PAYLOAD_OFFSET=' || { + v "Invalid image: no PAYLOAD_OFFSET header" + return 1 + } + return 0 + fi + case "$(get_magic_word "$1")" in eb48|eb63) ;; *) @@ -39,6 +131,9 @@ platform_check_image() { platform_copy_config() { local partdev parttype=ext4 + # ONIE upgrade bundles sysupgrade.tgz into the installer itself. + is_onie_install && return 0 + if export_partdevice partdev 1; then part_magic_fat "/dev/$partdev" && parttype=vfat mount -t $parttype -o rw,noatime "/dev/$partdev" /mnt @@ -71,6 +166,11 @@ platform_do_bootloader_upgrade() { platform_do_upgrade() { local diskdev partdev diff + if is_onie_install; then + platform_do_upgrade_onie "$1" + return $? + fi + export_bootdevice && export_partdevice diskdev 0 || { v "Unable to determine upgrade device" return 1 diff --git a/target/linux/x86/image/Makefile b/target/linux/x86/image/Makefile index d99b9e6e35..87256c884d 100644 --- a/target/linux/x86/image/Makefile +++ b/target/linux/x86/image/Makefile @@ -97,7 +97,10 @@ define Build/onie-installer -e 's#@TIMEOUT@#$(GRUB_TIMEOUT)#g' \ ./onie-install.sh.in > $@.onie/installer.sh script_len=$$(wc -c < $@.onie/installer.sh | tr -d ' '); \ - sed -i "s/__PYLOAD__/$$(printf '%010d' $$script_len)/" $@.onie/installer.sh + payload_len=$$(wc -c < $@.onie/payload.tar | tr -d ' '); \ + total_len=$$(( script_len + payload_len )); \ + sed -i "s/__PYLOAD__/$$(printf '%010d' $$script_len)/" $@.onie/installer.sh; \ + sed -i "s/__TOTLEN__/$$(printf '%010d' $$total_len)/" $@.onie/installer.sh cat $@.onie/installer.sh $@.onie/payload.tar > $@ chmod +x $@ endef diff --git a/target/linux/x86/image/onie-install.sh.in b/target/linux/x86/image/onie-install.sh.in index cb22f0de67..d79e7ac5a2 100644 --- a/target/linux/x86/image/onie-install.sh.in +++ b/target/linux/x86/image/onie-install.sh.in @@ -14,9 +14,20 @@ set -e -# 10-char zero-padded byte offset of the tar payload. Length-preserving -# substitution: __PYLOAD__ (10 chars) -> "NNNNNNNNNN" at build time. +# 10-char zero-padded byte offsets, substituted length-preserving at build: +# __PYLOAD__ -> byte offset where payload.tar starts (== script size) +# __TOTLEN__ -> total file size at build time (script + payload) +# Sysupgrade appends the preserved-config tarball to the tail of the file; +# the installer detects it by comparing actual file size to BUILD_SIZE. PAYLOAD_OFFSET=__PYLOAD__ +BUILD_SIZE=__TOTLEN__ +# Strip leading zeros: shells (dash/ash) parse a leading 0 as octal, +# which breaks on digits >=8. Parameter expansion keeps us clear of `expr` +# (which returns exit 1 for "0" and trips `set -e`). +PAYLOAD_OFFSET=${PAYLOAD_OFFSET#${PAYLOAD_OFFSET%%[!0]*}} +BUILD_SIZE=${BUILD_SIZE#${BUILD_SIZE%%[!0]*}} +: "${PAYLOAD_OFFSET:=0}" +: "${BUILD_SIZE:=0}" # Substituted by image.mk: GRUB_CMDLINE='@CMDLINE@' @@ -69,7 +80,17 @@ openwrt_mnt=$(mktemp -d) trap 'cd /; umount "$openwrt_mnt" 2>/dev/null; rm -rf "$payload_dir" "$openwrt_mnt"' EXIT echo "Extracting installer payload..." -dd if="$0" bs="$PAYLOAD_OFFSET" skip=1 2>/dev/null | tar -x -C "$payload_dir" +actual_size=$(wc -c < "$0") +if [ "$actual_size" -gt "$BUILD_SIZE" ]; then + # Sysupgrade has appended sysupgrade.tgz past the build-time tail. + payload_size=$(( BUILD_SIZE - PAYLOAD_OFFSET )) + tail -c "+$(( PAYLOAD_OFFSET + 1 ))" "$0" | head -c "$payload_size" \ + | tar -x -C "$payload_dir" + tail -c "+$(( BUILD_SIZE + 1 ))" "$0" > "$payload_dir/sysupgrade.tgz" + echo "Preserved config : $(wc -c < "$payload_dir/sysupgrade.tgz") bytes" +else + dd if="$0" bs="$PAYLOAD_OFFSET" skip=1 2>/dev/null | tar -x -C "$payload_dir" +fi [ -f "$payload_dir/rootfs.tar.gz" ] || { echo "ERROR: rootfs.tar.gz missing" >&2; exit 1; } [ -f "$payload_dir/vmlinuz" ] || { echo "ERROR: vmlinuz missing" >&2; exit 1; } @@ -103,6 +124,13 @@ echo "Installing kernel..." mkdir -p "$openwrt_mnt/boot" cp "$payload_dir/vmlinuz" "$openwrt_mnt/boot/vmlinuz" +# Stash preserved config at rootfs root; OpenWrt's preinit +# (80_mount_root) extracts it on first boot and /etc/init.d/done +# removes it. +if [ -f "$payload_dir/sysupgrade.tgz" ]; then + cp "$payload_dir/sysupgrade.tgz" "$openwrt_mnt/sysupgrade.tgz" +fi + # ------------------------------------------------ install GRUB to ESP --- if [ "$firmware" = "uefi" ]; then uefi_part=0 @@ -153,12 +181,21 @@ cat > "$openwrt_mnt/boot/grub/grub.cfg" <