[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.39, Wed Dec 1 02:19:00 2021 UTC (2 years, 6 months ago) by afresh1
Branch: MAIN
Changes since 1.38: +1 -1 lines

Assume the last matching firmware is the one we want

Because it's possible there will be more in the SHA256.sig file, but we will
put the onus of sorting to have the preferred one last to whoever creates that
file, since we are running in a very limited case.

#!/bin/ksh
#	$OpenBSD$
set -e

# 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.

# Fake up some things from install.sub that we don't need to actually do
prefetcharea_fs_list() {
	echo "${DESTDIR}/tmp"
}

# tmpdir, do_as, unpriv, and unpriv2 are from install.sub

# Create a temporary directory based on the supplied directory name prefix.
tmpdir() {
	local _i=1 _dir

	until _dir="${1?}.$_i.$RANDOM" && mkdir -- "$_dir" 2>/dev/null; do
		((++_i < 10000)) || return 1
	done
	echo "$_dir"
}

# Run a command ($2+) as unprivileged user ($1).
# Take extra care that after "cmd" no "user" processes exist.
#
# Optionally:
#	- create "file" and chown it to "user"
#	- after "cmd", chown "file" back to root
#
# Usage: do_as user [-f file] cmd
do_as() {
	(( $# >= 2 )) || return

	local _file _rc _user=$1
	shift

	if [[ $1 == -f ]]; then
		_file=$2
		shift 2
	fi

	if [[ -n $_file ]]; then
		>$_file
		chown "$_user" "$_file"
	fi

	doas -u "$_user" "$@"
	_rc=$?

	while doas -u "$_user" kill -9 -1 2>/dev/null; do
		echo "Processes still running for user $_user after: $@"
		sleep 1
	done

	[[ -n $_file ]] && chown root "$_file"

	return $_rc
}

unpriv() {
	do_as _sndio "$@"
}

unpriv2() {
	do_as _file "$@"
}

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

# TODO: We need the firmware for the system we just installed
#       not the one we booted from.  For example:
#       * booting from a snapshot bsd.rd that thinks it is the 7.0 release
#         will install the firmware from the 7.0 directory instead of
#         from the snapshots dir.
#       If they're using sysupgrade, then the installer kernel will be correct.
#       If we're doing this in the installer we can check what they picked
#       for downloading sets and use that value.
#       Otherwise, the fw_update after first boot will fix it up for us.

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

FWURL=http://firmware.openbsd.org/firmware/${HTTP_FWDIR}
FWPUB_KEY=${DESTDIR}/etc/signify/openbsd-${VERSION}-fw.pub
FWPATTERNS="${DESTDIR}/usr/share/misc/firmware_patterns"

# TODO: support srclocal installation of firmware somehow
fw_install() {
	local _src=$1 _tmpfs_list _tmpfs _tmpsrc \
		_t=Get _cfile="/tmp/SHA256" _pkgdir=${DESTDIR}/var/db/pkg \
		_f _r _remove _i _installed
	local _srclocal=false _unpriv=unpriv

	echo "Let's $MODE firmware!"
	local _d _drivers=$(
		last=''
		while read _d _m; do
			grep=grep
			[ "$last" = "$_d" ] && continue
			[ "$_m" ] || _m="^$_d[0-9][0-9]* at "
			[ "$_m" = "${_m#^}" ] && grep=fgrep
			$grep -q "$_m" /var/run/dmesg.boot || continue
			echo $_d
			last=$_d
		done < $FWPATTERNS
	)

	if [ -z "$_drivers" ]; then
		echo "No devices found which need firmware files to be downloaded."
		return
	fi

	! _tmpfs_list=$(prefetcharea_fs_list) &&
		echo "Cannot determine prefetch area" >&2 && return

	for _tmpfs in $_tmpfs_list; do
		# Try to clean up from previous runs, assuming
		# the _tmpfs selection yields the same mount
		# point.
		for _tmpsrc in $_tmpfs/firmware.+([0-9]).+([0-9]); do
			[[ -d $_tmpsrc ]] && rm -r $_tmpsrc
		done

		# Create a download directory for the firmware and
		# check that the _sndio user can read files from
		# it. Otherwise cleanup and skip the filesystem.
		if _tmpsrc=$(tmpdir "$_tmpfs/firmware"); then
			(
			>$_tmpsrc/t &&
			$_unpriv cat $_tmpsrc/t
			) >/dev/null 2>&1 && break ||
				rm -r $_tmpsrc
		fi
	done

	[[ ! -d $_tmpsrc ]] &&
		echo "Cannot create prefetch area" >&2 && return 1

	# Cleanup from previous runs.
	rm -f $_cfile $_cfile.sig

	_t=Get/Verify

	! $_unpriv ftp -D "$_t" -Vmo - "$_src/SHA256.sig" >"$_cfile.sig" &&
	    echo "Cannot fetch SHA256.sig" >&2 && return 1

	# Verify signature file with public keys.
	! $_unpriv -f "$_cfile" \
	    signify -Vep $FWPUB_KEY -x "$_cfile.sig" -m "$_cfile" &&
	    echo "Signature check of SHA256.sig failed" >&2 && return 1

	for _d in $_drivers; do
		_f=$( sed -n "s/.*(\($_d-firmware-.*\.tgz\)).*/\1/p" "$_cfile" | tail -1 )
		_installed=$(
	        for fw in "${_pkgdir}/$_d-firmware"*; do
		        [ -e "$fw" ] || continue
		        echo ${fw##*/}
	        done
		)

		for _i in $_installed; do
			if [ "$_f" = "$_i.tgz" ]; then
				echo "$_i already installed"
				continue 2
			fi
		done

		rm -f /tmp/h /tmp/fail

		# Fetch firmware file and create a checksum by piping through
		# sha256. Create a flag file in case ftp failed. Firmware
		# from net is written to the prefetch area.
		( $_unpriv ftp -D "$_t" -Vmo - "$_src/$_f" || >/tmp/fail ) |
		( $_srclocal && unpriv2 sha256 -b >/tmp/h ||
		    unpriv2 -f /tmp/h sha256 -bph /tmp/h >"$_tmpsrc/$_f" )

		# Handle failed transfer.
		if [[ -f /tmp/fail ]]; then
			rm -f "$_tmpsrc/$_f"
			echo "Fetching of $_f failed!" >&2
			continue
		fi

		# Verify firmware by comparing its checksum with SHA256.
		if ! fgrep -qx "SHA256 ($_f) = $(</tmp/h)" "$_cfile"; then
			[[ -d "$_tmpsrc" ]] && rm -rf "$_tmpsrc"
			echo "Checksum test for $_f failed." >&2
			continue
		fi

		# TODO: Check hash for files before deleting
		if [ "$_installed" ] && [ -e "${_pkgdir}/$_installed/+CONTENTS" ]; then
			echo "Uninstalling $_installed"
			cwd=${_pkgdir}/$_installed

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

			while read c g; do
				case $c in
				@cwd) cwd="${DESTDIR}/$g"
				  ;;
				@*) continue
				  ;;
				*)  set -A _remove -- "$cwd/$c" "${_remove[@]}"
				  ;;
				esac
			done < "${_pkgdir}/$_installed/+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
		fi

		# TODO: Should we mark these so real fw_update can -Drepair?
		ftp -D "Install" -Vmo- "file:$_tmpsrc/$_f" |
			tar -s ",^\+,${_pkgdir}/${_f%.tgz}/+," \
			-s ",^firmware,${DESTDIR}/etc/firmware," \
			-C / -zxphf - "+*" "firmware/*"

		ed -s "${_pkgdir}/${_f%.tgz}/+CONTENTS" <<EOL
/^@comment pkgpath/ -1a
@option manual-installation
@option firmware
@comment install-script
.
w
EOL
	done
}

fw_install "$FWURL"