Solved FreeBSD upgrade from 13.1 to 15.0-p9 helper script where shared object "libsys.so.7" not found

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:
After using it in anger on a number of machines, have made the script a bit more robust. Putting it here so I can find it in future.
 
After using it in anger on a number of machines, have made the script a bit more robust. Putting it here so I can find it in future.
Hi. I encountered this problem today while upgrading to RELEASE-15.0. Are the comments on top of your shell script the steps on how to execute your script ?

When I execute the following command :
/rescue/sh /root/freebsd_update_fix.sh

It displays : no such file or directory.
 
Back
Top