xref: /openbsd-src/usr.sbin/fw_update/fw_update.sh (revision 78b9b1e61a9f066f9c220d5f51d9dbbcbc06c879)
181b30a31Safresh1#!/bin/ksh
2*78b9b1e6Safresh1#	$OpenBSD: fw_update.sh,v 1.62 2024/11/24 21:27:04 afresh1 Exp $
3f07f38fcSafresh1#
4d60efbf6Safresh1# Copyright (c) 2021,2023 Andrew Hewus Fresh <afresh1@openbsd.org>
5f07f38fcSafresh1#
6f07f38fcSafresh1# Permission to use, copy, modify, and distribute this software for any
7f07f38fcSafresh1# purpose with or without fee is hereby granted, provided that the above
8f07f38fcSafresh1# copyright notice and this permission notice appear in all copies.
9f07f38fcSafresh1#
10f07f38fcSafresh1# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11f07f38fcSafresh1# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12f07f38fcSafresh1# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13f07f38fcSafresh1# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14f07f38fcSafresh1# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15f07f38fcSafresh1# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16f07f38fcSafresh1# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17f07f38fcSafresh1
1881b30a31Safresh1set -o errexit -o pipefail -o nounset -o noclobber -o noglob
1981b30a31Safresh1set +o monitor
2081b30a31Safresh1export PATH=/usr/bin:/bin:/usr/sbin:/sbin
21f07f38fcSafresh1
2281b30a31Safresh1CFILE=SHA256.sig
2381b30a31Safresh1DESTDIR=${DESTDIR:-}
2481b30a31Safresh1FWPATTERNS="${DESTDIR}/usr/share/misc/firmware_patterns"
2581b30a31Safresh1
2681b30a31Safresh1VNAME=${VNAME:-$(sysctl -n kern.osrelease)}
2781b30a31Safresh1VERSION=${VERSION:-"${VNAME%.*}${VNAME#*.}"}
2881b30a31Safresh1
2981b30a31Safresh1HTTP_FWDIR="$VNAME"
3081b30a31Safresh1VTYPE=$( sed -n "/^OpenBSD $VNAME\([^ ]*\).*$/s//\1/p" \
3181b30a31Safresh1    /var/run/dmesg.boot | sed '$!d' )
320bdd0b6fSafresh1[ "$VTYPE" = -current ] && HTTP_FWDIR=snapshots
3381b30a31Safresh1
3481b30a31Safresh1FWURL=http://firmware.openbsd.org/firmware/${HTTP_FWDIR}
3581b30a31Safresh1FWPUB_KEY=${DESTDIR}/etc/signify/openbsd-${VERSION}-fw.pub
3681b30a31Safresh1
3781b30a31Safresh1DRYRUN=false
38c4d555d4Safresh1integer VERBOSE=0
3981b30a31Safresh1DELETE=false
4081b30a31Safresh1DOWNLOAD=true
4181b30a31Safresh1INSTALL=true
4281b30a31Safresh1LOCALSRC=
43d60efbf6Safresh1ENABLE_SPINNER=false
44d60efbf6Safresh1[ -t 1 ] && ENABLE_SPINNER=true
45d60efbf6Safresh1
46d60efbf6Safresh1integer STATUS_FD=1
47d60efbf6Safresh1integer WARN_FD=2
48d60efbf6Safresh1FD_DIR=
4981b30a31Safresh1
5081b30a31Safresh1unset FTPPID
5153baf8a7Safresh1unset LOCKPID
5281b30a31Safresh1unset FWPKGTMP
5381b30a31Safresh1REMOVE_LOCALSRC=false
548eda69bdSafresh1DROP_PRIVS=true
55d60efbf6Safresh1
56d60efbf6Safresh1status() { echo -n "$*" >&"$STATUS_FD"; }
57d60efbf6Safresh1warn()   { echo    "$*" >&"$WARN_FD"; }
58d60efbf6Safresh1
5981b30a31Safresh1cleanup() {
6081b30a31Safresh1	set +o errexit # ignore errors from killing ftp
61d60efbf6Safresh1
62d60efbf6Safresh1	if [ -d "$FD_DIR" ]; then
63d60efbf6Safresh1		echo "" >&"$STATUS_FD"
648e9ff1e6Safresh1		((STATUS_FD == 3)) && exec 3>&-
658e9ff1e6Safresh1		((WARN_FD   == 4)) && exec 4>&-
66d60efbf6Safresh1
67d60efbf6Safresh1		[ -s "$FD_DIR/status" ] && cat "$FD_DIR/status"
68d60efbf6Safresh1		[ -s "$FD_DIR/warn"   ] && cat "$FD_DIR/warn" >&2
69d60efbf6Safresh1
70d60efbf6Safresh1		rm -rf "$FD_DIR"
71d60efbf6Safresh1	fi
72d60efbf6Safresh1
7381b30a31Safresh1	[ "${FTPPID:-}" ] && kill -TERM -"$FTPPID" 2>/dev/null
7453baf8a7Safresh1	[ "${LOCKPID:-}" ] && kill -TERM -"$LOCKPID" 2>/dev/null
7581b30a31Safresh1	[ "${FWPKGTMP:-}" ] && rm -rf "$FWPKGTMP"
7681b30a31Safresh1	"$REMOVE_LOCALSRC" && rm -rf "$LOCALSRC"
77d60efbf6Safresh1	[ -e "$CFILE" ] && [ ! -s "$CFILE" ] && rm -f "$CFILE"
78f07f38fcSafresh1}
7981b30a31Safresh1trap cleanup EXIT
8081b30a31Safresh1
8181b30a31Safresh1tmpdir() {
8281b30a31Safresh1	local _i=1 _dir
8381b30a31Safresh1
841d1249f3Safresh1	# The installer lacks mktemp(1), do it by hand
8581b30a31Safresh1	if [ -x /usr/bin/mktemp ]; then
8681b30a31Safresh1		_dir=$( mktemp -d "${1}-XXXXXXXXX" )
8781b30a31Safresh1	else
8881b30a31Safresh1		until _dir="${1}.$_i.$RANDOM" && mkdir -- "$_dir" 2>/dev/null; do
8981b30a31Safresh1		    ((++_i < 10000)) || return 1
9081b30a31Safresh1		done
9181b30a31Safresh1	fi
9281b30a31Safresh1
9381b30a31Safresh1	echo "$_dir"
94f07f38fcSafresh1}
95f07f38fcSafresh1
96d60efbf6Safresh1spin() {
97d60efbf6Safresh1	if ! "$ENABLE_SPINNER"; then
98d60efbf6Safresh1		sleep 1
99d60efbf6Safresh1		return 0
100d60efbf6Safresh1	fi
101d60efbf6Safresh1
102d60efbf6Safresh1	{
103d60efbf6Safresh1		for p in '/' '-' '\\' '|' '/' '-' '\\' '|'; do
1040e3a0897Safresh1			echo -n "$p"'\b'
105d60efbf6Safresh1			sleep 0.125
106d60efbf6Safresh1		done
1070e3a0897Safresh1		echo -n " "'\b'
108d60efbf6Safresh1	}>/dev/tty
109d60efbf6Safresh1}
110d60efbf6Safresh1
11181b30a31Safresh1fetch() {
11281b30a31Safresh1	local _src="${FWURL}/${1##*/}" _dst=$1 _user=_file _exit _error=''
113eaf1d082Safresh1	local _ftp_errors="$FD_DIR/ftp_errors"
114eaf1d082Safresh1	rm -f "$_ftp_errors"
115f07f38fcSafresh1
1161d1249f3Safresh1	# The installer uses a limited doas(1) as a tiny su(1)
11781b30a31Safresh1	set -o monitor # make sure ftp gets its own process group
11881b30a31Safresh1	(
119c4d555d4Safresh1	_flags=-vm
120c4d555d4Safresh1	case "$VERBOSE" in
121eaf1d082Safresh1		0|1) _flags=-VM ; exec 2>"$_ftp_errors" ;;
122c4d555d4Safresh1		  2) _flags=-Vm ;;
123c4d555d4Safresh1	esac
124eaf1d082Safresh1
1258eda69bdSafresh1	if ! "$DROP_PRIVS"; then
1268eda69bdSafresh1		/usr/bin/ftp -N error -D 'Get/Verify' $_flags -o- "$_src" > "$_dst"
1278eda69bdSafresh1	elif [ -x /usr/bin/su ]; then
12881b30a31Safresh1		exec /usr/bin/su -s /bin/ksh "$_user" -c \
129eaf1d082Safresh1		    "/usr/bin/ftp -N error -D 'Get/Verify' $_flags -o- '$_src'" > "$_dst"
13081b30a31Safresh1	else
13181b30a31Safresh1		exec /usr/bin/doas -u "$_user" \
132eaf1d082Safresh1		    /usr/bin/ftp -N error -D 'Get/Verify' $_flags -o- "$_src" > "$_dst"
13381b30a31Safresh1	fi
13481b30a31Safresh1	) & FTPPID=$!
13581b30a31Safresh1	set +o monitor
136f07f38fcSafresh1
13781b30a31Safresh1	SECONDS=0
13881b30a31Safresh1	_last=0
13981b30a31Safresh1	while kill -0 -"$FTPPID" 2>/dev/null; do
14081b30a31Safresh1		if [[ $SECONDS -gt 12 ]]; then
14181b30a31Safresh1			set -- $( ls -ln "$_dst" 2>/dev/null )
14281b30a31Safresh1			if [[ $_last -ne $5 ]]; then
14381b30a31Safresh1				_last=$5
14481b30a31Safresh1				SECONDS=0
145d60efbf6Safresh1				spin
14681b30a31Safresh1			else
1478fccd469Safresh1				kill -INT -"$FTPPID" 2>/dev/null
14881b30a31Safresh1				_error=" (timed out)"
14981b30a31Safresh1			fi
15081b30a31Safresh1		else
151d60efbf6Safresh1			spin
15281b30a31Safresh1		fi
15381b30a31Safresh1	done
15481b30a31Safresh1
15581b30a31Safresh1	set +o errexit
15681b30a31Safresh1	wait "$FTPPID"
15781b30a31Safresh1	_exit=$?
15881b30a31Safresh1	set -o errexit
15981b30a31Safresh1
16081b30a31Safresh1	unset FTPPID
16181b30a31Safresh1
162eaf1d082Safresh1	if ((_exit != 0)); then
16381b30a31Safresh1		rm -f "$_dst"
164eaf1d082Safresh1
165eaf1d082Safresh1		# ftp doesn't provide useful exit codes
166eaf1d082Safresh1		# so we have to grep its STDERR.
167eaf1d082Safresh1		# _exit=2 means don't keep trying
168eaf1d082Safresh1		_exit=2
169eaf1d082Safresh1
170eaf1d082Safresh1		# If it was 404, we might succeed at another file
171eaf1d082Safresh1		if [ -s "$_ftp_errors" ] && \
172eaf1d082Safresh1		    grep -q "404 Not Found" "$_ftp_errors"; then
173eaf1d082Safresh1			_exit=1
174eaf1d082Safresh1			_error=" (404 Not Found)"
175eaf1d082Safresh1			rm -f "$_ftp_errors"
17681b30a31Safresh1		fi
177d83d3c9fSafresh1
178eaf1d082Safresh1		warn "Cannot fetch $_src$_error"
179eaf1d082Safresh1	fi
180eaf1d082Safresh1
181eaf1d082Safresh1	# If we have ftp errors, print them out,
182eaf1d082Safresh1	# removing any cntrl characters (like 0x0d),
183eaf1d082Safresh1	# and any leading blank lines.
184eaf1d082Safresh1	if [ -s "$_ftp_errors" ]; then
185eaf1d082Safresh1		sed -e 's/[[:cntrl:]]//g' \
186eaf1d082Safresh1		    -e '/./,$!d' "$_ftp_errors" >&"$WARN_FD"
187eaf1d082Safresh1	fi
188eaf1d082Safresh1
189eaf1d082Safresh1	return "$_exit"
190d83d3c9fSafresh1}
191d83d3c9fSafresh1
1924e4477fdSafresh1# If we fail to fetch the CFILE, we don't want to try again
1934e4477fdSafresh1# but we might be doing this in a subshell so write out
1944e4477fdSafresh1# a blank file indicating failure.
1954e4477fdSafresh1check_cfile() {
1964e4477fdSafresh1	if [ -e "$CFILE" ]; then
197eaf1d082Safresh1		[ -s "$CFILE" ] || return 2
1984e4477fdSafresh1		return 0
1994e4477fdSafresh1	fi
200d60efbf6Safresh1	if ! fetch_cfile; then
2014e4477fdSafresh1		echo -n > "$CFILE"
202eaf1d082Safresh1		return 2
2034e4477fdSafresh1	fi
2044e4477fdSafresh1	return 0
2054e4477fdSafresh1}
2064e4477fdSafresh1
207d83d3c9fSafresh1fetch_cfile() {
208d83d3c9fSafresh1	if "$DOWNLOAD"; then
209d83d3c9fSafresh1		set +o noclobber # we want to get the latest CFILE
210d83d3c9fSafresh1		fetch "$CFILE" || return 1
211d83d3c9fSafresh1		set -o noclobber
2129ba7c5ccSafresh1		signify -qVep "$FWPUB_KEY" -x "$CFILE" -m /dev/null \
2139ba7c5ccSafresh1		    2>&"$WARN_FD" || {
2149ba7c5ccSafresh1		        warn "Signature check of SHA256.sig failed"
2159ba7c5ccSafresh1		        rm -f "$CFILE"
2169ba7c5ccSafresh1			return 1
2179ba7c5ccSafresh1		    }
218d83d3c9fSafresh1	elif [ ! -e "$CFILE" ]; then
219d60efbf6Safresh1		warn "${0##*/}: $CFILE: No such file or directory"
2204e4477fdSafresh1		return 1
221d83d3c9fSafresh1	fi
222d83d3c9fSafresh1
223d83d3c9fSafresh1	return 0
224f07f38fcSafresh1}
225f07f38fcSafresh1
22681b30a31Safresh1verify() {
227eaf1d082Safresh1	check_cfile || return $?
2281d1249f3Safresh1	# The installer sha256 lacks -C, do it by hand
229d60efbf6Safresh1	if ! grep -Fqx "SHA256 (${1##*/}) = $( /bin/sha256 -qb "$1" )" "$CFILE"
230d60efbf6Safresh1	then
231d60efbf6Safresh1		((VERBOSE != 1)) && warn "Checksum test for ${1##*/} failed."
23281b30a31Safresh1		return 1
23381b30a31Safresh1	fi
234d83d3c9fSafresh1
235d83d3c9fSafresh1	return 0
23681b30a31Safresh1}
23781b30a31Safresh1
238d60efbf6Safresh1# When verifying existing files that we are going to re-download
239d60efbf6Safresh1# if VERBOSE is 0, don't show the checksum failure of an existing file.
240d60efbf6Safresh1verify_existing() {
241d60efbf6Safresh1	local _v=$VERBOSE
242eaf1d082Safresh1	check_cfile || return $?
243d60efbf6Safresh1
244d60efbf6Safresh1	((_v == 0)) && "$DOWNLOAD" && _v=1
245d60efbf6Safresh1	( VERBOSE=$_v verify "$@" )
246d60efbf6Safresh1}
247d60efbf6Safresh1
248*78b9b1e6Safresh1devices_in_dmesg() {
2491c702c4aSafresh1	local IFS
2501c702c4aSafresh1	local _d _m _dmesgtail _last='' _nl='
2511c702c4aSafresh1'
25281b30a31Safresh1
2531d1249f3Safresh1	# The dmesg can contain multiple boots, only look in the last one
25481b30a31Safresh1	_dmesgtail="$( echo ; sed -n 'H;/^OpenBSD/h;${g;p;}' /var/run/dmesg.boot )"
25581b30a31Safresh1
25681b30a31Safresh1	grep -v '^[[:space:]]*#' "$FWPATTERNS" |
25781b30a31Safresh1	    while read -r _d _m; do
25881b30a31Safresh1		[ "$_d" = "$_last" ]  && continue
25981b30a31Safresh1		[ "$_m" ]             || _m="${_nl}${_d}[0-9] at "
26081b30a31Safresh1		[ "$_m" = "${_m#^}" ] || _m="${_nl}${_m#^}"
26181b30a31Safresh1
2621c702c4aSafresh1		IFS='*'
2631c702c4aSafresh1		set -- $_m
2641c702c4aSafresh1		unset IFS
2651c702c4aSafresh1
2661c702c4aSafresh1		case $# in
2671c702c4aSafresh1		    1|2|3) [[ $_dmesgtail = *$1*([!$_nl])${2-}*([!$_nl])${3-}* ]] || continue;;
268d60efbf6Safresh1		    *) warn "${0##*/}: Bad pattern '${_m#$_nl}' in $FWPATTERNS"; exit 1 ;;
2691c702c4aSafresh1		esac
2701c702c4aSafresh1
27181b30a31Safresh1		echo "$_d"
27281b30a31Safresh1		_last="$_d"
27381b30a31Safresh1	    done
27481b30a31Safresh1}
27581b30a31Safresh1
27681b30a31Safresh1firmware_filename() {
277eaf1d082Safresh1	check_cfile || return $?
2781e63d4eeSafresh1	sed -n "s/.*(\($1-firmware-.*\.tgz\)).*/\1/p" "$CFILE" | sed '$!d'
27981b30a31Safresh1}
28081b30a31Safresh1
28181b30a31Safresh1firmware_devicename() {
28281b30a31Safresh1	local _d="${1##*/}"
28381b30a31Safresh1	_d="${_d%-firmware-*}"
28481b30a31Safresh1	echo "$_d"
28581b30a31Safresh1}
28681b30a31Safresh1
28753baf8a7Safresh1lock_db() {
28897f1001cSafresh1	local _waited
28953baf8a7Safresh1	[ "${LOCKPID:-}" ] && return 0
29053baf8a7Safresh1
29153baf8a7Safresh1	# The installer doesn't have perl, so we can't lock there
29253baf8a7Safresh1	[ -e /usr/bin/perl ] || return 0
29353baf8a7Safresh1
29453baf8a7Safresh1	set -o monitor
29564882388Safresh1	perl <<-'EOL' |&
29632367434Safresh1		no lib ('/usr/local/libdata/perl5/site_perl');
29764882388Safresh1		use v5.36;
298ef057aebSespie		use OpenBSD::PackageInfo qw< lock_db >;
29953baf8a7Safresh1
30053baf8a7Safresh1		$|=1;
30153baf8a7Safresh1
30264882388Safresh1		$0 = "fw_update: lock_db";
30397f1001cSafresh1		my $waited = 0;
30497f1001cSafresh1		package OpenBSD::FwUpdateState {
30597f1001cSafresh1			use parent 'OpenBSD::BaseState';
30697f1001cSafresh1			sub errprint ($self, @p) {
30797f1001cSafresh1				if ($p[0] && $p[0] =~ /already locked/) {
30897f1001cSafresh1					$waited++;
30997f1001cSafresh1					$p[0] = " " . $p[0]
31097f1001cSafresh1					    if !$ENV{VERBOSE};
31197f1001cSafresh1				}
31297f1001cSafresh1				$self->SUPER::errprint(@p);
31397f1001cSafresh1			}
31453baf8a7Safresh1
31597f1001cSafresh1		}
31697f1001cSafresh1		lock_db(0, 'OpenBSD::FwUpdateState');
31797f1001cSafresh1
31897f1001cSafresh1		say "$$ $waited";
31964882388Safresh1
32064882388Safresh1		# Wait for STDOUT to be readable, which won't happen
32164882388Safresh1		# but if our parent exits unexpectedly it will close.
32264882388Safresh1		my $rin = '';
32364882388Safresh1		vec($rin, fileno(STDOUT), 1) = 1;
32464882388Safresh1		select $rin, '', '', undef;
32553baf8a7Safresh1EOL
32653baf8a7Safresh1	set +o monitor
32753baf8a7Safresh1
32897f1001cSafresh1	read -rp LOCKPID _waited
32997f1001cSafresh1
33097f1001cSafresh1	if ((_waited)); then
33197f1001cSafresh1		! ((VERBOSE)) && status "${0##*/}:"
33297f1001cSafresh1	fi
33353baf8a7Safresh1
33453baf8a7Safresh1	return 0
33553baf8a7Safresh1}
33653baf8a7Safresh1
337fe7b3534Safresh1available_firmware() {
338fe7b3534Safresh1	check_cfile || return $?
339fe7b3534Safresh1	sed -n 's/.*(\(.*\)-firmware.*/\1/p' "$CFILE"
340fe7b3534Safresh1}
341fe7b3534Safresh1
34281b30a31Safresh1installed_firmware() {
34357365b05Safresh1	local _pre="$1" _match="$2" _post="$3" _firmware _fw
34481b30a31Safresh1	set -sA _firmware -- $(
34581b30a31Safresh1	    set +o noglob
34681b30a31Safresh1	    grep -Fxl '@option firmware' \
34781b30a31Safresh1		"${DESTDIR}/var/db/pkg/"$_pre"$_match"$_post"/+CONTENTS" \
34881b30a31Safresh1		2>/dev/null || true
34981b30a31Safresh1	    set -o noglob
35081b30a31Safresh1	)
35181b30a31Safresh1
35281b30a31Safresh1	[ "${_firmware[*]:-}" ] || return 0
35357365b05Safresh1	for _fw in "${_firmware[@]}"; do
35457365b05Safresh1		_fw="${_fw%/+CONTENTS}"
35557365b05Safresh1		echo "${_fw##*/}"
35681b30a31Safresh1	done
35781b30a31Safresh1}
35881b30a31Safresh1
35981b30a31Safresh1detect_firmware() {
36081b30a31Safresh1	local _devices _last='' _d
36181b30a31Safresh1
36281b30a31Safresh1	set -sA _devices -- $(
363*78b9b1e6Safresh1	    devices_in_dmesg
36481b30a31Safresh1	    for _d in $( installed_firmware '*' '-firmware-' '*' ); do
3658fccd469Safresh1		firmware_devicename "$_d"
36681b30a31Safresh1	    done
36781b30a31Safresh1	)
36881b30a31Safresh1
36981b30a31Safresh1	[ "${_devices[*]:-}" ] || return 0
37081b30a31Safresh1	for _d in "${_devices[@]}"; do
3718fccd469Safresh1		[ "$_last" = "$_d" ] && continue
3728fccd469Safresh1		echo "$_d"
37381b30a31Safresh1		_last="$_d"
37481b30a31Safresh1	done
37581b30a31Safresh1}
37681b30a31Safresh1
37781b30a31Safresh1add_firmware () {
3783d362716Safresh1	local _f="${1##*/}" _m="${2:-Install}"
3793d362716Safresh1	local _pkgdir="${DESTDIR}/var/db/pkg" _pkg
38081b30a31Safresh1	FWPKGTMP="$( tmpdir "${DESTDIR}/var/db/pkg/.firmware" )"
381c4d555d4Safresh1	local _flags=-vm
382c4d555d4Safresh1	case "$VERBOSE" in
383c4d555d4Safresh1		0|1) _flags=-VM ;;
384c4d555d4Safresh1		2|3) _flags=-Vm ;;
385c4d555d4Safresh1	esac
386c4d555d4Safresh1
387c4d555d4Safresh1	ftp -N "${0##/}" -D "$_m" "$_flags" -o- "file:${1}" |
38881b30a31Safresh1		tar -s ",^\+,${FWPKGTMP}/+," \
38981b30a31Safresh1		    -s ",^firmware,${DESTDIR}/etc/firmware," \
39081b30a31Safresh1		    -C / -zxphf - "+*" "firmware/*"
39181b30a31Safresh1
3928e9ff1e6Safresh1
3938e9ff1e6Safresh1	[ -s "${FWPKGTMP}/+CONTENTS" ] &&
3943d362716Safresh1	    _pkg="$( sed -n '/^@name /{s///p;q;}' "${FWPKGTMP}/+CONTENTS" )"
3958e9ff1e6Safresh1
3968e9ff1e6Safresh1	if [ ! "${_pkg:-}" ]; then
3973d362716Safresh1		warn "Failed to extract name from $1, partial install"
3983d362716Safresh1		rm -rf "$FWPKGTMP"
3993d362716Safresh1		unset FWPKGTMP
4003d362716Safresh1		return 1
4013d362716Safresh1	fi
4023d362716Safresh1
4033d362716Safresh1	if [ -e "$_pkgdir/$_pkg" ]; then
4043d362716Safresh1		warn "Failed to register: $_pkgdir/$_pkg is not firmware"
40581b30a31Safresh1		rm -rf "$FWPKGTMP"
40681b30a31Safresh1		unset FWPKGTMP
40781b30a31Safresh1		return 1
40881b30a31Safresh1	fi
40981b30a31Safresh1
41081b30a31Safresh1	ed -s "${FWPKGTMP}/+CONTENTS" <<EOL
41181b30a31Safresh1/^@comment pkgpath/ -1a
41281b30a31Safresh1@option manual-installation
41381b30a31Safresh1@option firmware
41481b30a31Safresh1@comment install-script
41581b30a31Safresh1.
41681b30a31Safresh1w
41781b30a31Safresh1EOL
41881b30a31Safresh1
41981b30a31Safresh1	chmod 755 "$FWPKGTMP"
4203d362716Safresh1	mv "$FWPKGTMP" "$_pkgdir/$_pkg"
42181b30a31Safresh1	unset FWPKGTMP
42281b30a31Safresh1}
42381b30a31Safresh1
4241e63d4eeSafresh1remove_files() {
42557365b05Safresh1	local _r
4261e63d4eeSafresh1	# Use rm -f, not removing files/dirs is probably not worth failing over
4271e63d4eeSafresh1	for _r in "$@" ; do
4281e63d4eeSafresh1		if [ -d "$_r" ]; then
4291e63d4eeSafresh1			# The installer lacks rmdir,
4301e63d4eeSafresh1			# but we only want to remove empty directories.
4311e63d4eeSafresh1			set +o noglob
4321e63d4eeSafresh1			[ "$_r/*" = "$( echo "$_r"/* )" ] && rm -rf "$_r"
4331e63d4eeSafresh1			set -o noglob
4341e63d4eeSafresh1		else
4351e63d4eeSafresh1			rm -f "$_r"
4361e63d4eeSafresh1		fi
4371e63d4eeSafresh1	done
4381e63d4eeSafresh1}
4391e63d4eeSafresh1
44081b30a31Safresh1delete_firmware() {
44181b30a31Safresh1	local _cwd _pkg="$1" _pkgdir="${DESTDIR}/var/db/pkg"
44281b30a31Safresh1
44381b30a31Safresh1	# TODO: Check hash for files before deleting
444c4d555d4Safresh1	((VERBOSE > 2)) && echo -n "Uninstall $_pkg ..."
44581b30a31Safresh1	_cwd="${_pkgdir}/$_pkg"
44681b30a31Safresh1
44781b30a31Safresh1	if [ ! -e "$_cwd/+CONTENTS" ] ||
44881b30a31Safresh1	    ! grep -Fxq '@option firmware' "$_cwd/+CONTENTS"; then
449d60efbf6Safresh1		warn "${0##*/}: $_pkg does not appear to be firmware"
45081b30a31Safresh1		return 2
45181b30a31Safresh1	fi
45281b30a31Safresh1
45381b30a31Safresh1	set -A _remove -- "${_cwd}/+CONTENTS" "${_cwd}"
45481b30a31Safresh1
45557365b05Safresh1	while read -r _c _g; do
45657365b05Safresh1		case $_c in
45757365b05Safresh1		@cwd) _cwd="${DESTDIR}$_g"
45881b30a31Safresh1		  ;;
45981b30a31Safresh1		@*) continue
46081b30a31Safresh1		  ;;
46157365b05Safresh1		*) set -A _remove -- "$_cwd/$_c" "${_remove[@]}"
46281b30a31Safresh1		  ;;
46381b30a31Safresh1		esac
46481b30a31Safresh1	done < "${_pkgdir}/${_pkg}/+CONTENTS"
46581b30a31Safresh1
4661e63d4eeSafresh1	remove_files "${_remove[@]}"
467c4d555d4Safresh1
468c4d555d4Safresh1	((VERBOSE > 2)) && echo " done."
469c4d555d4Safresh1
470c4d555d4Safresh1	return 0
47181b30a31Safresh1}
47281b30a31Safresh1
4731e63d4eeSafresh1unregister_firmware() {
47457365b05Safresh1	local _d="$1" _pkgdir="${DESTDIR}/var/db/pkg" _fw
4751e63d4eeSafresh1
4761e63d4eeSafresh1	set -A installed -- $( installed_firmware '' "$d-firmware-" '*' )
4771e63d4eeSafresh1	if [ "${installed:-}" ]; then
47857365b05Safresh1		for _fw in "${installed[@]}"; do
47957365b05Safresh1			((VERBOSE)) && echo "Unregister $_fw"
4801e63d4eeSafresh1			"$DRYRUN" && continue
4811e63d4eeSafresh1			remove_files \
48257365b05Safresh1			    "$_pkgdir/$_fw/+CONTENTS" \
48357365b05Safresh1			    "$_pkgdir/$_fw/+DESC" \
48457365b05Safresh1			    "$_pkgdir/$_fw/"
4851e63d4eeSafresh1		done
4861e63d4eeSafresh1		return 0
4871e63d4eeSafresh1	fi
4881e63d4eeSafresh1
4891e63d4eeSafresh1	return 1
4901e63d4eeSafresh1}
4911e63d4eeSafresh1
49281b30a31Safresh1usage() {
49314dcac1eSafresh1	echo "usage: ${0##*/} [-adFlnv] [-p path] [driver | file ...]"
4948b422bc7Safresh1	exit 1
49581b30a31Safresh1}
49681b30a31Safresh1
49781b30a31Safresh1ALL=false
49814dcac1eSafresh1LIST=false
49914dcac1eSafresh1while getopts :adFlnp:v name
50081b30a31Safresh1do
50181b30a31Safresh1	case "$name" in
50281b30a31Safresh1	a) ALL=true ;;
50381b30a31Safresh1	d) DELETE=true ;;
5043df64845Safresh1	F) INSTALL=false ;;
50514dcac1eSafresh1	l) LIST=true ;;
50681b30a31Safresh1	n) DRYRUN=true ;;
507fcfe10d6Safresh1	p) FWURL="$OPTARG" ;;
508c4d555d4Safresh1	v) ((++VERBOSE)) ;;
50981b30a31Safresh1	:)
510d60efbf6Safresh1	    warn "${0##*/}: option requires an argument -- -$OPTARG"
5118b422bc7Safresh1	    usage
51281b30a31Safresh1	    ;;
51381b30a31Safresh1	?)
514d60efbf6Safresh1	    warn "${0##*/}: unknown option -- -$OPTARG"
5158b422bc7Safresh1	    usage
51681b30a31Safresh1	    ;;
51781b30a31Safresh1	esac
51881b30a31Safresh1done
51981b30a31Safresh1shift $((OPTIND - 1))
52081b30a31Safresh1
52114dcac1eSafresh1# When listing, provide a clean output
52214dcac1eSafresh1"$LIST" && VERBOSE=1 ENABLE_SPINNER=false
52314dcac1eSafresh1
524d60efbf6Safresh1# Progress bars, not spinner When VERBOSE > 1
525d60efbf6Safresh1((VERBOSE > 1)) && ENABLE_SPINNER=false
526d60efbf6Safresh1
527fcfe10d6Safresh1if [[ $FWURL != @(ftp|http?(s))://* ]]; then
528fcfe10d6Safresh1	FWURL="${FWURL#file:}"
529fcfe10d6Safresh1	! [ -d "$FWURL" ] &&
530d60efbf6Safresh1	    warn "The path must be a URL or an existing directory" &&
5318b422bc7Safresh1	    exit 1
5323df64845Safresh1	DOWNLOAD=false
533fcfe10d6Safresh1	FWURL="file:$FWURL"
53481b30a31Safresh1fi
53581b30a31Safresh1
53681b30a31Safresh1if [ -x /usr/bin/id ] && [ "$(/usr/bin/id -u)" != 0 ]; then
5373df64845Safresh1	if ! "$INSTALL" || "$LIST"; then
5388eda69bdSafresh1		# When we aren't in the installer,
5398eda69bdSafresh1		# allow downloading as the current user.
5408eda69bdSafresh1		DROP_PRIVS=false
5418eda69bdSafresh1	else
542d60efbf6Safresh1		warn "need root privileges"
54381b30a31Safresh1		exit 1
54481b30a31Safresh1	fi
5458eda69bdSafresh1fi
54681b30a31Safresh1
54781b30a31Safresh1set -sA devices -- "$@"
54881b30a31Safresh1
549d60efbf6Safresh1FD_DIR="$( tmpdir "${DESTDIR}/tmp/${0##*/}-fd" )"
5508e9ff1e6Safresh1# When being verbose, save the status line for the end.
551d60efbf6Safresh1if ((VERBOSE)); then
5528e9ff1e6Safresh1	exec 3>"${FD_DIR}/status"
5538e9ff1e6Safresh1	STATUS_FD=3
5548e9ff1e6Safresh1fi
5558e9ff1e6Safresh1# Control "warning" messages to avoid the middle of a line.
5568e9ff1e6Safresh1# Things that we don't expect to send to STDERR
5578e9ff1e6Safresh1# still go there so the output, while it may be ugly, isn't lost
558d60efbf6Safresh1exec 4>"${FD_DIR}/warn"
559d60efbf6Safresh1WARN_FD=4
560d60efbf6Safresh1
561d60efbf6Safresh1status "${0##*/}:"
562d60efbf6Safresh1
56381b30a31Safresh1if "$DELETE"; then
5643df64845Safresh1	! "$INSTALL" && warn "Cannot use -F and -d" && usage
56553baf8a7Safresh1	lock_db
56681b30a31Safresh1
567c4d555d4Safresh1	# Show the "Uninstall" message when just deleting not upgrading
568c4d555d4Safresh1	((VERBOSE)) && VERBOSE=3
569c4d555d4Safresh1
57081b30a31Safresh1	set -A installed
57181b30a31Safresh1	if [ "${devices[*]:-}" ]; then
572d60efbf6Safresh1		"$ALL" && warn "Cannot use -a and devices/files" && usage
57381b30a31Safresh1
57481b30a31Safresh1		set -A installed -- $(
57581b30a31Safresh1		    for d in "${devices[@]}"; do
57681b30a31Safresh1			f="${d##*/}"  # only care about the name
57781b30a31Safresh1			f="${f%.tgz}" # allow specifying the package name
57881b30a31Safresh1			[ "$( firmware_devicename "$f" )" = "$f" ] && f="$f-firmware"
57981b30a31Safresh1
58081b30a31Safresh1			set -A i -- $( installed_firmware '' "$f-" '*' )
58181b30a31Safresh1
58281b30a31Safresh1			if [ "${i[*]:-}" ]; then
58381b30a31Safresh1				echo "${i[@]}"
58481b30a31Safresh1			else
585d60efbf6Safresh1				warn "No firmware found for '$d'"
58681b30a31Safresh1			fi
58781b30a31Safresh1		    done
58881b30a31Safresh1		)
58981b30a31Safresh1	elif "$ALL"; then
59081b30a31Safresh1		set -A installed -- $( installed_firmware '*' '-firmware-' '*' )
591*78b9b1e6Safresh1	else
592*78b9b1e6Safresh1		set -A installed -- $(
593*78b9b1e6Safresh1		    set -- $( devices_in_dmesg )
594*78b9b1e6Safresh1		    for f in $( installed_firmware '*' -firmware- '*' ); do
595*78b9b1e6Safresh1		        n="$( firmware_devicename "$f" )"
596*78b9b1e6Safresh1		        for d; do
597*78b9b1e6Safresh1		            [ "$d" = "$n" ] && continue 2
598*78b9b1e6Safresh1		        done
599*78b9b1e6Safresh1		        echo "$f"
600*78b9b1e6Safresh1		    done
601*78b9b1e6Safresh1		)
60281b30a31Safresh1	fi
60381b30a31Safresh1
604d60efbf6Safresh1	status " delete "
605d60efbf6Safresh1
606d60efbf6Safresh1	comma=''
60781b30a31Safresh1	if [ "${installed:-}" ]; then
60881b30a31Safresh1		for fw in "${installed[@]}"; do
609d60efbf6Safresh1			status "$comma$( firmware_devicename "$fw" )"
610d60efbf6Safresh1			comma=,
61181b30a31Safresh1			if "$DRYRUN"; then
612c4d555d4Safresh1				((VERBOSE)) && echo "Delete $fw"
61314dcac1eSafresh1			elif "$LIST"; then
61414dcac1eSafresh1				echo "$fw"
61581b30a31Safresh1			else
6168e9ff1e6Safresh1				delete_firmware "$fw" || {
6178e9ff1e6Safresh1					status " ($fw failed)"
6188e9ff1e6Safresh1					continue
6198e9ff1e6Safresh1				}
62081b30a31Safresh1			fi
62181b30a31Safresh1		done
62281b30a31Safresh1	fi
62381b30a31Safresh1
624d60efbf6Safresh1	[ "$comma" ] || status none
62581b30a31Safresh1
62614dcac1eSafresh1	# no status when listing
62714dcac1eSafresh1	"$LIST" && rm -f "$FD_DIR/status"
62814dcac1eSafresh1
62981b30a31Safresh1	exit
63081b30a31Safresh1fi
63181b30a31Safresh1
6323df64845Safresh1! "$INSTALL" && ! "$LIST" && LOCALSRC="${LOCALSRC:-.}"
6333df64845Safresh1
63481b30a31Safresh1if [ ! "$LOCALSRC" ]; then
63581b30a31Safresh1	LOCALSRC="$( tmpdir "${DESTDIR}/tmp/${0##*/}" )"
63681b30a31Safresh1	REMOVE_LOCALSRC=true
63781b30a31Safresh1fi
63881b30a31Safresh1
63981b30a31Safresh1CFILE="$LOCALSRC/$CFILE"
64081b30a31Safresh1
64181b30a31Safresh1if [ "${devices[*]:-}" ]; then
642d60efbf6Safresh1	"$ALL" && warn "Cannot use -a and devices/files" && usage
643fe7b3534Safresh1elif "$ALL"; then
644fe7b3534Safresh1	set -sA devices -- $( available_firmware )
64581b30a31Safresh1else
646c4d555d4Safresh1	((VERBOSE > 1)) && echo -n "Detect firmware ..."
64781b30a31Safresh1	set -sA devices -- $( detect_firmware )
648c4d555d4Safresh1	((VERBOSE > 1)) &&
64981b30a31Safresh1	    { [ "${devices[*]:-}" ] && echo " found." || echo " done." ; }
65081b30a31Safresh1fi
65181b30a31Safresh1
65253baf8a7Safresh1
653d60efbf6Safresh1set -A add ''
654d60efbf6Safresh1set -A update ''
65581b30a31Safresh1kept=''
6561e63d4eeSafresh1unregister=''
657d60efbf6Safresh1
6583df64845Safresh1"$LIST" && ! "$INSTALL" &&
65914dcac1eSafresh1    echo "$FWURL/${CFILE##*/}"
66014dcac1eSafresh1
6610553a602Safresh1if [ "${devices[*]:-}" ]; then
6620553a602Safresh1	lock_db
66381b30a31Safresh1	for f in "${devices[@]}"; do
66481b30a31Safresh1		d="$( firmware_devicename "$f" )"
66581b30a31Safresh1
6663df64845Safresh1		if "$LIST" && "$INSTALL"; then
66714dcac1eSafresh1			echo "$d"
66814dcac1eSafresh1			continue
66914dcac1eSafresh1		fi
67014dcac1eSafresh1
6717f22cd74Safresh1		verify_existing=true
67281b30a31Safresh1		if [ "$f" = "$d" ]; then
673eaf1d082Safresh1			f=$( firmware_filename "$d" ) || {
674eaf1d082Safresh1				# Fetching the CFILE here is often the
675eaf1d082Safresh1				# first attempt to talk to FWURL
676eaf1d082Safresh1				# If it fails, no point in continuing.
677eaf1d082Safresh1				if (($? > 1)); then
678eaf1d082Safresh1					status " failed."
679eaf1d082Safresh1					exit 1
680eaf1d082Safresh1				fi
681eaf1d082Safresh1
682eaf1d082Safresh1				# otherwise we can try the next firmware
683eaf1d082Safresh1				continue
684eaf1d082Safresh1			}
6851e63d4eeSafresh1			if [ ! "$f" ]; then
6861e63d4eeSafresh1				if "$INSTALL" && unregister_firmware "$d"; then
6871e63d4eeSafresh1					unregister="$unregister,$d"
6881e63d4eeSafresh1				else
689d60efbf6Safresh1					warn "Unable to find firmware for $d"
6901e63d4eeSafresh1				fi
6911e63d4eeSafresh1				continue
6921e63d4eeSafresh1			fi
69381b30a31Safresh1		elif ! "$INSTALL" && ! grep -Fq "($f)" "$CFILE" ; then
694d60efbf6Safresh1			warn "Cannot download local file $f"
6958b422bc7Safresh1			exit 1
696d83d3c9fSafresh1		else
6971d1249f3Safresh1			# Don't verify files specified on the command-line
698d83d3c9fSafresh1			verify_existing=false
69981b30a31Safresh1		fi
70081b30a31Safresh1
70114dcac1eSafresh1		if "$LIST"; then
70214dcac1eSafresh1			echo "$FWURL/$f"
70314dcac1eSafresh1			continue
70414dcac1eSafresh1		fi
70514dcac1eSafresh1
706d60efbf6Safresh1		set -A installed
707d60efbf6Safresh1		if "$INSTALL"; then
708d60efbf6Safresh1			set -A installed -- \
709d60efbf6Safresh1			    $( installed_firmware '' "$d-firmware-" '*' )
71081b30a31Safresh1
711d60efbf6Safresh1			if [ "${installed[*]:-}" ]; then
71281b30a31Safresh1				for i in "${installed[@]}"; do
71381b30a31Safresh1					if [ "${f##*/}" = "$i.tgz" ]; then
714d60efbf6Safresh1						((VERBOSE > 2)) \
715d60efbf6Safresh1						    && echo "Keep $i"
71681b30a31Safresh1						kept="$kept,$d"
71781b30a31Safresh1						continue 2
71881b30a31Safresh1					fi
71981b30a31Safresh1				done
72081b30a31Safresh1			fi
721d60efbf6Safresh1		fi
72281b30a31Safresh1
7238ffd1deaSafresh1		# Fetch an unqualified file into LOCALSRC
7248ffd1deaSafresh1		# if it doesn't exist in the current directory.
7258ffd1deaSafresh1		if [ "$f" = "${f##/}" ] && [ ! -e "$f" ]; then
7268ffd1deaSafresh1			f="$LOCALSRC/$f"
7278ffd1deaSafresh1		fi
7288ffd1deaSafresh1
7297f22cd74Safresh1		if "$verify_existing" && [ -e "$f" ]; then
730d60efbf6Safresh1			pending_status=false
731c4d555d4Safresh1			if ((VERBOSE == 1)); then
732c4d555d4Safresh1				echo -n "Verify ${f##*/} ..."
733c4d555d4Safresh1				pending_status=true
7347f22cd74Safresh1			elif ((VERBOSE > 1)) && ! "$INSTALL"; then
73581b30a31Safresh1				echo "Keep/Verify ${f##*/}"
7367f22cd74Safresh1			fi
7377f22cd74Safresh1
738d60efbf6Safresh1			if "$DRYRUN" || verify_existing "$f"; then
739d60efbf6Safresh1				"$pending_status" && echo " done."
740d60efbf6Safresh1				if ! "$INSTALL"; then
741d60efbf6Safresh1					kept="$kept,$d"
742d60efbf6Safresh1					continue
743d60efbf6Safresh1				fi
7447f22cd74Safresh1			elif "$DOWNLOAD"; then
745d60efbf6Safresh1				"$pending_status" && echo " failed."
7467f22cd74Safresh1				((VERBOSE > 1)) && echo "Refetching $f"
7474e4477fdSafresh1				rm -f "$f"
7487f22cd74Safresh1			else
749c4d555d4Safresh1				"$pending_status" && echo " failed."
750c4d555d4Safresh1				continue
751d83d3c9fSafresh1			fi
7527f22cd74Safresh1		fi
7537f22cd74Safresh1
754d60efbf6Safresh1		if [ "${installed[*]:-}" ]; then
755d60efbf6Safresh1			set -A update -- "${update[@]}" "$f"
756d60efbf6Safresh1		else
757d60efbf6Safresh1			set -A add -- "${add[@]}" "$f"
758d60efbf6Safresh1		fi
759d60efbf6Safresh1
760d60efbf6Safresh1	done
761d60efbf6Safresh1fi
762d60efbf6Safresh1
76314dcac1eSafresh1if "$LIST"; then
76414dcac1eSafresh1	# No status when listing
76514dcac1eSafresh1	rm -f "$FD_DIR/status"
76614dcac1eSafresh1	exit
76714dcac1eSafresh1fi
76814dcac1eSafresh1
769d60efbf6Safresh1if "$INSTALL"; then
770d60efbf6Safresh1	status " add "
771d60efbf6Safresh1	action=Install
772d60efbf6Safresh1else
773d60efbf6Safresh1	status " download "
774d60efbf6Safresh1	action=Download
775d60efbf6Safresh1fi
776d60efbf6Safresh1
777d60efbf6Safresh1comma=''
778d60efbf6Safresh1[ "${add[*]}" ] || status none
779d60efbf6Safresh1for f in "${add[@]}" _update_ "${update[@]}"; do
780d60efbf6Safresh1	[ "$f" ] || continue
781d60efbf6Safresh1	if [ "$f" = _update_ ]; then
782d60efbf6Safresh1		comma=''
783d60efbf6Safresh1		"$INSTALL" || continue
784d60efbf6Safresh1		action=Update
785d60efbf6Safresh1		status "; update "
786d60efbf6Safresh1		[ "${update[*]}" ] || status none
787d60efbf6Safresh1		continue
788d60efbf6Safresh1	fi
789d60efbf6Safresh1	d="$( firmware_devicename "$f" )"
790d60efbf6Safresh1	status "$comma$d"
791d60efbf6Safresh1	comma=,
792d60efbf6Safresh1
793d60efbf6Safresh1	pending_status=false
7947f22cd74Safresh1	if [ -e "$f" ]; then
795d60efbf6Safresh1		if "$DRYRUN"; then
796d60efbf6Safresh1			((VERBOSE)) && echo "$action ${f##*/}"
797d60efbf6Safresh1		else
798d60efbf6Safresh1			if ((VERBOSE == 1)); then
799d60efbf6Safresh1				echo -n "Install ${f##*/} ..."
800d60efbf6Safresh1				pending_status=true
801d60efbf6Safresh1			fi
802d60efbf6Safresh1		fi
80381b30a31Safresh1	elif "$DOWNLOAD"; then
80481b30a31Safresh1		if "$DRYRUN"; then
805c4d555d4Safresh1			((VERBOSE)) && echo "Get/Verify ${f##*/}"
80681b30a31Safresh1		else
807c4d555d4Safresh1			if ((VERBOSE == 1)); then
808c4d555d4Safresh1				echo -n "Get/Verify ${f##*/} ..."
809c4d555d4Safresh1				pending_status=true
810c4d555d4Safresh1			fi
811c4d555d4Safresh1			fetch  "$f" &&
812c4d555d4Safresh1			verify "$f" || {
813eaf1d082Safresh1				integer e=$?
814eaf1d082Safresh1
8158e9ff1e6Safresh1				"$pending_status" && echo " failed."
816d60efbf6Safresh1				status " failed (${f##*/})"
8178e9ff1e6Safresh1
8188e9ff1e6Safresh1				if ((VERBOSE)) && [ -s "$FD_DIR/warn" ]; then
8198e9ff1e6Safresh1					cat "$FD_DIR/warn" >&2
8208e9ff1e6Safresh1					rm -f "$FD_DIR/warn"
821d60efbf6Safresh1				fi
822eaf1d082Safresh1
823eaf1d082Safresh1				# Fetch or verify exited > 1
824eaf1d082Safresh1				# which means we don't keep trying.
825eaf1d082Safresh1				((e > 1)) && exit 1
826eaf1d082Safresh1
827c4d555d4Safresh1				continue
828c4d555d4Safresh1			}
82981b30a31Safresh1		fi
83081b30a31Safresh1	elif "$INSTALL"; then
831d60efbf6Safresh1		warn "Cannot install ${f##*/}, not found"
83281b30a31Safresh1		continue
83381b30a31Safresh1	fi
83481b30a31Safresh1
835d60efbf6Safresh1	if ! "$INSTALL"; then
836d60efbf6Safresh1		"$pending_status" && echo " done."
837d60efbf6Safresh1		continue
838d60efbf6Safresh1	fi
83981b30a31Safresh1
840d60efbf6Safresh1	if ! "$DRYRUN"; then
841d60efbf6Safresh1		if [ "$action" = Update ]; then
842d60efbf6Safresh1			for i in $( installed_firmware '' "$d-firmware-" '*' )
843d60efbf6Safresh1			do
844d60efbf6Safresh1				delete_firmware "$i" || {
8458e9ff1e6Safresh1					"$pending_status" &&
8468e9ff1e6Safresh1					    echo -n " (remove $i failed)"
8478e9ff1e6Safresh1					status " (remove $i failed)"
8488e9ff1e6Safresh1
849d60efbf6Safresh1					continue
850d60efbf6Safresh1				}
8518e9ff1e6Safresh1				#status " (removed $i)"
85281b30a31Safresh1			done
85381b30a31Safresh1		fi
85481b30a31Safresh1
855d60efbf6Safresh1		add_firmware "$f" "$action" || {
8568e9ff1e6Safresh1			"$pending_status" && echo " failed."
857d60efbf6Safresh1			status " failed (${f##*/})"
858d60efbf6Safresh1			continue
859d60efbf6Safresh1		}
860c4d555d4Safresh1	fi
86181b30a31Safresh1
862d60efbf6Safresh1	if "$pending_status"; then
863d60efbf6Safresh1		if [ "$action" = Install ]; then
864d60efbf6Safresh1			echo " installed."
865c4d555d4Safresh1		else
866d60efbf6Safresh1			echo " updated."
867d60efbf6Safresh1		fi
86881b30a31Safresh1	fi
86981b30a31Safresh1done
87081b30a31Safresh1
871d60efbf6Safresh1[ "$unregister" ] && status "; unregister ${unregister:#,}"
872d60efbf6Safresh1[ "$kept"       ] && status "; keep ${kept:#,}"
8735b80eddbSafresh1
8745b80eddbSafresh1exit 0
875