[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.85, Sat Dec 18 22:03:43 2021 UTC (2 years, 5 months ago) by afresh1
Branch: MAIN
Changes since 1.84: +35 -5 lines

Wrap ftp in a timer

That is, we put ftp in the background and check every second that
it's still running and every 12 seconds that the filesize has
changed.  If it hasn't, we interrupt the download and abort.

#!/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
set +o monitor
export PATH=/usr/bin:/bin:/usr/sbin:/sbin

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

LOCALSRC=

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"
}

fetch() {
	local _src="${FWURL}/${1##*/}" _dst=$1 _user=_file _pid _exit

	# If we're not in the installer,
	# we have su(1) and doas(1) is unlikely to be configured.
	set -o monitor # make sure ftp gets its own process group
	(
	if [ -x /usr/bin/su ]; then
		exec /usr/bin/su -s /bin/ksh "$_user" -c \
		    "/usr/bin/ftp -D 'Get/Verify' -Vm -o- '$_src'" > "$_dst"
	else
		exec /usr/bin/doas -u "$_user" \
		    /usr/bin/ftp -D 'Get/Verify' -Vm -o- "$_src" > "$_dst"
	fi
	) & _pid=$!
	set +o monitor

	trap "kill -TERM '-$_pid'; exit 1" EXIT INT QUIT ABRT TERM

	SECONDS=0
	_last=0
	while kill -0 -"$_pid" 2>/dev/null; do
		if [[ $SECONDS -gt 12 ]]; then
			set -- $( ls -ln "$_dst" 2>/dev/null )
			if [[ $_last -ne $5 ]]; then
				_last=$5
				SECONDS=0
				sleep 1
			else
				kill -INT -"$_pid"
				echo "fetch timed out" >&2
			fi
		else
			sleep 1
		fi
	done

	set +o errexit
	wait "$_pid"
	_exit=$?
	set -o errexit

	trap "" EXIT INT QUIT ABRT TERM

	if [ "$_exit" -ne 0 ]; then
		rm -f "$_dst"
		echo "Cannot fetch $_src" >&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 _line _dmesgtail _last=''

	# When we're not in the installer, the dmesg.boot can
	# contain multiple boots, so only look in the last one
	sed -n 'H;/^OpenBSD/h;${g;p;}' /var/run/dmesg.boot |
	    grep -e "^[a-z][a-z]*[0-9]" -e " not configured " | { \
		_m=0
		set -A _dmesgtail
		while read -r _line; do
			_dmesgtail[$_m]="$_line"
			let _m=$_m+1
		done

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

			if [ "$_m" = "${_m#^}" ]; then
				_m="*$_m"
			else
				_m="${_m#^}"
			fi

			for _line in "${_dmesgtail[@]}"; do
				if [[ $_line = ${_m}* ]]; then
					echo "$_d"
					_last="$_d"
				fi
			done
		    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##*/}"
	local _pkgdir="${DESTDIR}/var/db/pkg/${_f%.tgz}"
	ftp -D "Install" -Vmo- "file:${1}" |
		tar -s ",^\+,${_pkgdir}/+," \
		    -s ",^firmware,${DESTDIR}/etc/firmware," \
		    -C / -zxphf - "+*" "firmware/*"

	# TODO: Should we mark these so real fw_update can -Drepair?
	ed -s "${_pkgdir}/+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 [-DL] [driver | file [...]]"
	exit 2
}

INSTALL=true
DOWNLOAD=true

while getopts DL name
do
       case "$name" in
       # "download only" means local dir and don't install
       D) LOCALSRC=. INSTALL=false ;;
       L) LOCALSRC=. ;;
       ?) usage 2 ;;
       esac
done
shift $((OPTIND - 1))

# If we're installing from a local dir
# we don't want to download anything
[ "$LOCALSRC" ] && "$INSTALL" && DOWNLOAD=false
[ "$LOCALSRC" ] || LOCALSRC="$( tmpdir "${DESTDIR}/tmp/fw_install" )"

CFILE="$LOCALSRC/$CFILE"

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

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

for f in "${devices[@]}"; do
	d="$( firmware_devicename "$f" )"

	if [ "$f" = "$d" ]; then
		f=$( firmware_filename "$d" || true )
		[ "$f" ] || continue
		f="$LOCALSRC/$f"
	elif ! "$INSTALL" && ! grep -Fq "($f)" "$CFILE" ; then
		echo "Cannot download local file $f" >&2
		exit 2
	fi

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

	if "$INSTALL" && [ "${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
		if "$DOWNLOAD"; then
			echo "Verify existing ${f##*/}"
			verify "$f" || continue
		# else assume it was verified when downloaded
		fi
	elif "$DOWNLOAD"; then
		fetch  "$f" || continue
		verify "$f" || continue
	elif "$INSTALL"; then
		echo "Cannot install ${f##*/}, not found" >&2
		continue
	fi

	"$INSTALL" || continue

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

	add_firmware "$f"
done