[BACK]Return to fw_install.sh CVS log [TXT][DIR] Up to [local] / openbsd / fw_update

File: [local] / openbsd / fw_update / fw_install.sh (download)

Revision 1.64, Thu Dec 9 02:37:38 2021 UTC (2 years, 6 months ago) by afresh1
Branch: MAIN
Changes since 1.63: +20 -5 lines

Add support for installing from a local dir

That way we can download the firmware while the network works and install it
when it doesn't.

I envision this being used to make it so firmware can be installed
by syspatch even when the install kernel doesn't have working internet,
maybe a connection that doesn't come up automatically or something.

This would also mean that if the bsd.upgrade kernel fails to boot for some
reason, we boot back into the previous system with the previous firmware that
was already working without having installed a firmware that doesn't work with
this kernel.  This would be mostly useful for full version upgrades not snapshots,
but there could be days when it's important.

#!/bin/ksh
#	$OpenBSD$
#
# Copyright (c) 2021 Andrew Hewus Fresh <afresh1@openbsd.org>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

set -o errexit -o pipefail -o nounset

CFILE=SHA256.sig
DESTDIR=${DESTDIR:-}
FWPATTERNS="${DESTDIR}/usr/share/misc/firmware_patterns"

VNAME=${VNAME:-$(sysctl -n kern.osrelease)}
VERSION=${VERSION:-"${VNAME%.*}${VNAME#*.}"}

HTTP_FWDIR="$VNAME"
VTYPE=$( sed -n "/^OpenBSD $VNAME\([^ ]*\).*$/s//\1/p" \
    /var/run/dmesg.boot | sed '$!d' )
[[ $VTYPE == -!(stable) ]] && HTTP_FWDIR=snapshots

FWURL=http://firmware.openbsd.org/firmware/${HTTP_FWDIR}
FWPUB_KEY=${DESTDIR}/etc/signify/openbsd-${VERSION}-fw.pub

tmpdir() {
	local _i=1 _dir

	# If we're not in the installer,
	# we have mktemp and a more hostile environment
	if [ -x /usr/bin/mktemp ]; then
		_dir=$( mktemp -d "${1}-XXXXXXXXX" )
	else
		until _dir="${1}.$_i.$RANDOM" && mkdir -- "$_dir" 2>/dev/null; do
		    ((++_i < 10000)) || return 1
		done
	fi

	echo "$_dir"
}

realpath () {
	if [ -x /usr/bin/realpath ]; then
		/usr/bin/realpath "$1"
	elif [ "$1" = "${1%/*}" ]; then
		echo "${PWD}/$1"
	else
		echo "$( cd "${1%/*}" && pwd )/${1##*/}"
	fi
}

fetch() {
	local _file=$1 _user=_file _exit

	# If we're not in the installer, we have su(1)
	# and doas(1) is unlikely to be configured.
	if [ -x /usr/bin/su ]; then
		/usr/bin/su -s /bin/ksh "$_user" -c \
		    "/usr/bin/ftp -D 'Get/Verify' -Vm \
		        -o- '${FWURL}/${_file}'" > "$_file"
		_exit="$?"
	else
		/usr/bin/doas -u "$_user" \
		    ftp -D 'Get/Verify' -Vm \
		        -o- "${FWURL}/${_file}" > "$_file"
		_exit="$?"
	fi

	if [ "$_exit" -ne 0 ]; then
		rm -f "$_file"
		echo "Cannot fetch $_file" >&2
		return 1
	fi
}

verify() {
	# On the installer we don't get sha256 -C, so fake it.
	if ! fgrep -qx "SHA256 ($1) = $( /bin/sha256 -qb "$1" )" "$CFILE"; then
		echo "Checksum test for $1 failed." >&2
		return 1
	fi
}

devices_needing_firmware() {
	local _d _m _grep _dmesgtail _last=''

	# When we're not in the installer, the dmesg.boot can
	# contain multiple boots, so only look in the last one
	_dmesgtail=$( sed -n 'H;/^OpenBSD/h;${g;p;}' /var/run/dmesg.boot )

	grep -v '^[[:space:]]*#' "$FWPATTERNS" |
	    while read -r _d _m; do
		_grep="grep"
		[ "$_last" = "$_d" ] && continue
		[ "$_m" ] || _m="^${_d}[0-9][0-9]* at "
		[ "$_m" = "${_m#^}" ] && _grep="fgrep"

		echo "$_dmesgtail" | $_grep -q "$_m" || continue
		echo "$_d"
		_last="$_d"
	done
}

firmware_filename() {
	local _f
	_f="$( sed -n "s/.*(\($1-firmware-.*\.tgz\)).*/\1/p" "$CFILE" | sed '$!d' )"
	! [ "$_f" ] && echo "Unable to find firmware for $1" >&2 && return 1
	echo "$_f"
}

firmware_devicename() {
	local _d="${1##*/}"
	_d="${_d%-firmware-*}"
	echo "$_d"
}

installed_firmware() {
	for fw in "${DESTDIR}/var/db/pkg/$1-firmware"*; do
		[ -e "$fw" ] || continue
		echo "${fw##*/}"
	done
}

add_firmware () {
	local _f="${1##*/}" _pkgdir="${DESTDIR}/var/db/pkg"
	ftp -D "Install" -Vmo- "file:${1}" |
		tar -s ",^\+,${_pkgdir}/${_f%.tgz}/+," \
		-s ",^firmware,${DESTDIR}/etc/firmware," \
		-C / -zxphf - "+*" "firmware/*"

	# TODO: Should we mark these so real fw_update can -Drepair?
	ed -s "${_pkgdir}/${_f%.tgz}/+CONTENTS" <<EOL
/^@comment pkgpath/ -1a
@option manual-installation
@option firmware
@comment install-script
.
w
EOL
}

delete_firmware() {
	local _cwd _pkg="$1" _pkgdir="${DESTDIR}/var/db/pkg"

	# TODO: Check hash for files before deleting
	echo "Uninstalling $_pkg"
	_cwd="${_pkgdir}/$_pkg"

	set -A _remove -- "${_cwd}/+CONTENTS" "${_cwd}"

	while read -r c g; do
		case $c in
		@cwd) _cwd="${DESTDIR}$g"
		  ;;
		@*) continue
		  ;;
		*) set -A _remove -- "$_cwd/$c" "${_remove[@]}"
		  ;;
		esac
	done < "${_pkgdir}/${_pkg}/+CONTENTS"

	# We specifically rm -f here because not removing files/dirs
	# is probably not worth failing over.
	for _r in "${_remove[@]}" ; do
		if [ -d "$_r" ]; then
			# Try hard not to actually remove recursively
			# without rmdir on the install media.
			[ "$_r/*" = "$( echo "$_r"/* )" ] && rm -rf "$_r"
		else
			rm -f "$_r"
		fi
	done
}

usage() {
	echo "Usage: fw_install [ -d dir | -L dir ] [ driver | file [ ... ] ]"
	exit 2
}

DOWNLOADDIR=
LOCALDIR=
while getopts d:L: name
do
       case "$name" in
       d) DOWNLOADDIR=$OPTARG ;;
       L) LOCALDIR=$OPTARG    ;;
       ?) usage 2 ;;
       esac
done
shift $((OPTIND - 1))

if [[ -n "${DOWNLOADDIR:-}" && -n "${LOCALDIR:-}" ]]; then
	echo "Cannot use -d and -L" >&2
	usage 2
fi

set -A devices -- "$@"

[ "${devices[*]:-}" ] ||
    set -A devices -- $( devices_needing_firmware )

if [ ! "${devices:-}" ]; then
	echo "No devices found which need firmware files to be downloaded."
	exit
fi

# Have to find the full path to firmware files
# so we can cd and still find them later.
i=0
while (( i < "${#devices[@]}" )); do
	f="${devices[$i]}"
	d=$( firmware_devicename "$f" )
	[ "$f" = "$d" ] && f="$( echo "$f"-firmware-*.tgz | sed '$!d' )"
	if [ -e "$f" ]; then
		if [ "${DOWNLOADDIR:-}" ]; then
			echo "Cannot download local file $f" >&2
			exit 2
		fi
		devices[$i]="$d:$( realpath "$f" )"
	fi
	i=$((i + 1))
done

if [ "$DOWNLOADDIR" ]; then
	if ! cd "$DOWNLOADDIR"; then
		echo "Unable to use $DOWNLOADDIR, make sure it is a directory"
		exit 2
	fi
elif [ "$LOCALDIR" ]; then
	if ! cd "$LOCALDIR"; then
		echo "Unable to use $LOCALDIR, make sure it is a directory"
		exit 2
	fi
else
	TMPDIR=$( tmpdir "${DESTDIR}/tmp/fw_install" )
	cd "$TMPDIR"
fi

if ! [[ -n "$LOCALDIR" && -e "$CFILE" ]]; then
	fetch "$CFILE"
	! signify -qVep "$FWPUB_KEY" -x "$CFILE" -m "$CFILE" &&
	    echo "Signature check of SHA256.sig failed" >&2 && exit 1
fi

for d in "${devices[@]}"; do
	f="${d##*:}"
	if [ "$f" = "$d" ]; then
		f=$( firmware_filename "$d" || true )
		[ "$f" ] || continue
	else
		d="${d%:*}"
	fi

	set -A installed -- $( installed_firmware "$d" )

	if [ ! "${DOWNLOADDIR:-}" ] && [ "${installed:-}" ]; then
		for i in "${installed[@]:-}"; do
			if [ "${f##*/}" = "$i.tgz" ]; then
				echo "$i already installed"
				continue 2
			fi
		done
	fi

	if [ ! -e "$f" ]; then
		[ "$LOCALDIR" ] && echo "Cannot install $f, not found" >&2 && continue
		fetch  "$f" || continue
		verify "$f" || continue
	elif [ "${DOWNLOADDIR:-}" ]; then
		echo "Already have $f"
		verify "$f" || continue
	fi

	[ "${DOWNLOADDIR:-}" ] && continue

	if [ "${installed:-}" ]; then
		for i in "${installed[@]}"; do
			delete_firmware "$i"
		done
	fi

	add_firmware "$f"
done