Code:
#!/rescue/sh
# Run with: /rescue/sh /root/freebsd_update_fix.sh
#
# Examples:
# # Before rebooting into the upgraded kernel, prefetch 15.0 base.txz:
# TARGET_RELEASE=15.0-RELEASE /rescue/sh /root/freebsd_update_fix.sh
#
# # After libsys is broken, recover using an already-downloaded archive:
# BASE_TXZ=/tmp/base.txz /rescue/sh /root/freebsd_update_fix.sh
#
# # After libsys is broken, use base.txz if present; otherwise try cache:
# /rescue/sh /root/freebsd_update_fix.sh
#
# If dynamic userland still works, this script prefetches and verifies base.txz
# for the running kernel release. Set TARGET_RELEASE=15.0-RELEASE to override.
# If /lib/libsys.so.7 is already broken, it restores libsys.so.7 from that
# verified base.txz using /rescue tools only.
# If base.txz is unavailable, it can fall back to the active freebsd-update
# cache, but only after verifying the extracted payload is ELF and matches ABI.
# Normal fetch is only attempted while dynamic userland still works.
set -e
TARGET=/lib/libsys.so.7
BACKUPDIR=/root/freebsd-upgrade-backup
LIBCXXDIR=/usr/include/c++/v1
WORKDIR=/var/db/freebsd-update
TMP=/tmp/libsys.so.7.$$
VALIDTMP=/tmp/libsys.so.7.valid.$$
MAGICTMP=/tmp/libsys.magic.$$
MACHINETMP=/tmp/libsys.machine.$$
# MD5 of the four ELF magic bytes: 0x7f 0x45 0x4c 0x46.
ELF_MAGIC_MD5=d1531b1622de54fe3a0187c3344600e9
CHFLAGS=/rescue/chflags
CHMOD=/rescue/chmod
CHOWN=/rescue/chown
CP=/rescue/cp
DD=/rescue/dd
FETCH=/usr/bin/fetch
GUNZIP=/rescue/gunzip
ID=/rescue/id
LS=/rescue/ls
MD5=/rescue/md5
MKDIR=/rescue/mkdir
MOUNT=/rescue/mount
MV=/rescue/mv
RM=/rescue/rm
SYSCTL=/rescue/sysctl
TAR=/rescue/tar
TEST=/rescue/[
die() {
echo "ERROR: $*"
exit 1
}
cleanup() {
$RM -f "$TMP" "$VALIDTMP" "$MAGICTMP" "$MACHINETMP"
}
trap cleanup 0 1 2 3 15
require_rescue_commands() {
if ! $TEST -x "$TEST" ]; then
echo "ERROR: missing required rescue command: $TEST"
exit 1
fi
missing=0
for cmd in \
"$CHFLAGS" "$CHMOD" "$CHOWN" "$CP" "$DD" "$GUNZIP" "$ID" \
"$LS" "$MD5" "$MKDIR" "$MOUNT" "$MV" "$RM" "$SYSCTL" "$TAR"
do
if ! $TEST -x "$cmd" ]; then
echo "ERROR: missing required rescue command: $cmd"
missing=1
fi
done
if $TEST "$missing" != 0 ]; then
exit 1
fi
}
require_root() {
if $TEST "$($ID -u)" != 0 ]; then
die "this script must be run as root"
fi
}
mount_root_writable() {
if ! $MOUNT -uw /; then
die "could not mount / read-write"
fi
}
detect_release_and_abi() {
OSREL="$($SYSCTL -n kern.osrelease)"
MACHINE="$($SYSCTL -n hw.machine)"
ARCH="$($SYSCTL -n hw.machine_arch)"
if $TEST -n "$TARGET_RELEASE" ]; then
RELEASE="$TARGET_RELEASE"
else
RELEASE="$OSREL"
case "$RELEASE" in
*-RELEASE-p*) RELEASE=${RELEASE%-p*} ;;
esac
fi
case "$RELEASE" in
*-RELEASE) ;;
*) die "running kernel release is not a RELEASE build: $OSREL" ;;
esac
case "$MACHINE:$ARCH" in
amd64:amd64)
RELEASE_PATH=amd64/amd64
ELF_MACHINE_MD5=123e0b3a96980be286441bd30768dde0
;;
arm64:aarch64|arm64:arm64)
RELEASE_PATH=arm64/aarch64
ELF_MACHINE_MD5=e6daa092cbde078bc46a6e2de3043ad8
;;
i386:i386)
RELEASE_PATH=i386/i386
ELF_MACHINE_MD5=598f4fe64aefab8f00bcbea4c9239abf
;;
*)
die "unsupported machine/architecture: $MACHINE/$ARCH"
;;
esac
RELEASE_URL="https://download.freebsd.org/releases/${RELEASE_PATH}/${RELEASE}/base.txz"
DEFAULT_BASE_TXZ="$BACKUPDIR/base-${RELEASE}-${MACHINE}-${ARCH}.txz"
if $TEST -z "$BASE_TXZ" ]; then
if $TEST -f "$DEFAULT_BASE_TXZ" ]; then
BASE_TXZ="$DEFAULT_BASE_TXZ"
elif $TEST -f /tmp/base.txz ]; then
BASE_TXZ=/tmp/base.txz
else
BASE_TXZ="$DEFAULT_BASE_TXZ"
fi
fi
}
libsys_is_broken() {
if /bin/sh -c 'exit 0' >/dev/null 2>&1; then
return 1
fi
return 0
}
fetch_base_txz_if_needed() {
if $TEST -f "$BASE_TXZ" ]; then
echo "Using existing base.txz: $BASE_TXZ"
return 0
fi
if $TEST "$DYNAMIC_USERLAND_OK" != yes ]; then
echo "Dynamic userland is broken; not attempting normal fetch."
return 1
fi
if ! $TEST -x "$FETCH" ]; then
echo "Fetch command is unavailable: $FETCH"
return 1
fi
echo "Fetching: $RELEASE_URL"
echo "Saving: $BASE_TXZ"
$MKDIR -p "$BACKUPDIR"
if ! $FETCH --no-verify-peer --no-verify-hostname -o "${BASE_TXZ}.part" "$RELEASE_URL"; then
$RM -f "${BASE_TXZ}.part"
echo "Fetch failed; will try freebsd-update cache if available."
return 1
fi
$MV "${BASE_TXZ}.part" "$BASE_TXZ"
return 0
}
extract_libsys_from_base() {
$RM -f "$TMP"
if $TAR -xOf "$BASE_TXZ" ./lib/libsys.so.7 > "$TMP"; then
:
elif $TAR -xOf "$BASE_TXZ" lib/libsys.so.7 > "$TMP"; then
:
else
$RM -f "$TMP"
echo "could not extract /lib/libsys.so.7 from $BASE_TXZ"
return 1
fi
if ! $TEST -s "$TMP" ]; then
echo "extracted libsys.so.7 is empty"
return 1
fi
return 0
}
verify_libsys_payload() {
$DD if="$TMP" bs=4 count=1 of="$MAGICTMP"
magic_md5="$($MD5 -q "$MAGICTMP")"
$RM -f "$MAGICTMP"
if $TEST "$magic_md5" != "$ELF_MAGIC_MD5" ]; then
echo "extracted libsys.so.7 is not an ELF file; magic MD5: $magic_md5"
return 1
fi
$DD if="$TMP" bs=1 skip=18 count=2 of="$MACHINETMP"
machine_md5="$($MD5 -q "$MACHINETMP")"
$RM -f "$MACHINETMP"
if $TEST "$machine_md5" != "$ELF_MACHINE_MD5" ]; then
echo "extracted libsys.so.7 is for the wrong ABI; machine MD5: $machine_md5"
return 1
fi
return 0
}
try_update_cache_index() {
idx=$1
found=
while IFS= read -r line; do
case "$line" in
/lib/libsys.so.7\|*)
found="$line"
break
;;
esac
done < "$idx"
if $TEST -z "$found" ]; then
return 1
fi
oldifs=$IFS
IFS='|'
set -- $found
IFS=$oldifs
path=$1
type=$2
hash=$7
link=$8
if $TEST "$path" != "$TARGET" ] || $TEST "$type" != "f" ] || $TEST -n "$link" ]; then
echo "unexpected manifest entry for $TARGET:"
echo "$found"
return 1
fi
payload="$WORKDIR/files/${hash}.gz"
if ! $TEST -f "$payload" ]; then
echo "missing update payload: $payload"
return 1
fi
echo "Checking freebsd-update cache manifest: $WORKDIR/$idx"
echo "Checking freebsd-update cache payload: $payload"
$RM -f "$TMP"
if ! $GUNZIP -c "$payload" > "$TMP"; then
$RM -f "$TMP"
echo "could not decompress update payload: $payload"
return 1
fi
if ! $TEST -s "$TMP" ]; then
$RM -f "$TMP"
echo "extracted cache payload is empty"
return 1
fi
if ! verify_libsys_payload; then
$RM -f "$TMP"
echo "cache payload failed ELF/ABI verification: $payload"
return 1
fi
CACHE_INDEX="$idx"
CACHE_PAYLOAD="$payload"
return 0
}
extract_libsys_from_update_cache() {
valid_count=0
valid_index=
valid_payload=
if ! $TEST -d "$WORKDIR" ]; then
echo "freebsd-update cache directory is missing: $WORKDIR"
return 1
fi
cd "$WORKDIR" || return 1
$RM -f "$VALIDTMP"
for idx in *-install/INDEX-NEW; do
if ! $TEST -f "$idx" ]; then
continue
fi
if try_update_cache_index "$idx"; then
valid_count=$((valid_count + 1))
if $TEST "$valid_count" = 1 ]; then
valid_index="$CACHE_INDEX"
valid_payload="$CACHE_PAYLOAD"
$MV "$TMP" "$VALIDTMP"
else
echo "multiple valid freebsd-update cache candidates found:"
echo " $valid_index"
echo " $CACHE_INDEX"
$RM -f "$TMP" "$VALIDTMP"
return 1
fi
fi
done
if $TEST "$valid_count" != 1 ]; then
echo "could not find exactly one verified /lib/libsys.so.7 payload in $WORKDIR/*-install"
return 1
fi
$MV "$VALIDTMP" "$TMP"
echo "Selected freebsd-update cache manifest: $WORKDIR/$valid_index"
echo "Selected freebsd-update cache payload: $valid_payload"
return 0
}
prepare_libsys_payload() {
if fetch_base_txz_if_needed; then
if extract_libsys_from_base && verify_libsys_payload; then
PAYLOAD_SOURCE="$BASE_TXZ"
return 0
fi
echo "base.txz did not provide a valid libsys.so.7; trying freebsd-update cache."
fi
if extract_libsys_from_update_cache && verify_libsys_payload; then
PAYLOAD_SOURCE="freebsd-update cache"
return 0
fi
die "could not prepare a verified libsys.so.7 from base.txz or freebsd-update cache"
}
install_libsys() {
$MKDIR -p "$BACKUPDIR/lib"
if $TEST -e "$TARGET" ]; then
backup="$BACKUPDIR/lib/libsys.so.7.before-fix"
if $TEST -e "$backup" ]; then
backup="$BACKUPDIR/lib/libsys.so.7.before-fix.$$"
fi
echo "Backing up existing $TARGET to $backup"
$CP -p "$TARGET" "$backup"
$CHFLAGS noschg "$TARGET"
fi
$CHOWN root:wheel "$TMP"
$CHMOD 0444 "$TMP"
$MV "$TMP" "$TARGET"
echo "Restored:"
$LS -l "$TARGET"
}
fix_libcxx_headers() {
$MKDIR -p "$BACKUPDIR/libcxx-header-files"
for name in __tuple __string; do
path="$LIBCXXDIR/$name"
if $TEST -f "$path" ]; then
backup="$BACKUPDIR/libcxx-header-files/$name.old-file"
if $TEST -e "$backup" ]; then
backup="$BACKUPDIR/libcxx-header-files/$name.old-file.$$"
fi
echo "Moving stale libc++ header file $path to $backup"
$MV "$path" "$backup"
fi
$MKDIR -p "$path"
done
echo "libc++ header paths:"
$LS -ld "$LIBCXXDIR/__tuple" "$LIBCXXDIR/__string"
}
require_rescue_commands
require_root
detect_release_and_abi
echo "Kernel release: $OSREL"
echo "Release fetch: $RELEASE"
echo "Architecture: $MACHINE/$ARCH"
echo "Base archive: $BASE_TXZ"
if libsys_is_broken; then
DYNAMIC_USERLAND_OK=no
else
DYNAMIC_USERLAND_OK=yes
fi
if $TEST "$DYNAMIC_USERLAND_OK" = yes ]; then
echo "Dynamic userland test passed; /lib/libsys.so.7 does not appear broken."
mount_root_writable
prepare_libsys_payload
$RM -f "$TMP"
echo "Verified libsys.so.7 recovery payload from: $PAYLOAD_SOURCE"
exit 0
fi
echo "Dynamic userland test failed; attempting /lib/libsys.so.7 recovery."
mount_root_writable
prepare_libsys_payload
echo "Installing libsys.so.7 from: $PAYLOAD_SOURCE"
install_libsys
fix_libcxx_headers
echo "Done. Now test by running: /bin/sh"
echo "If that works, run: freebsd-update install"
Last edited: