1#!/bin/ksh 2# $OpenBSD: fw_update.sh,v 1.62 2024/11/24 21:27:04 afresh1 Exp $ 3# 4# Copyright (c) 2021,2023 Andrew Hewus Fresh <afresh1@openbsd.org> 5# 6# Permission to use, copy, modify, and distribute this software for any 7# purpose with or without fee is hereby granted, provided that the above 8# copyright notice and this permission notice appear in all copies. 9# 10# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 18set -o errexit -o pipefail -o nounset -o noclobber -o noglob 19set +o monitor 20export PATH=/usr/bin:/bin:/usr/sbin:/sbin 21 22CFILE=SHA256.sig 23DESTDIR=${DESTDIR:-} 24FWPATTERNS="${DESTDIR}/usr/share/misc/firmware_patterns" 25 26VNAME=${VNAME:-$(sysctl -n kern.osrelease)} 27VERSION=${VERSION:-"${VNAME%.*}${VNAME#*.}"} 28 29HTTP_FWDIR="$VNAME" 30VTYPE=$( sed -n "/^OpenBSD $VNAME\([^ ]*\).*$/s//\1/p" \ 31 /var/run/dmesg.boot | sed '$!d' ) 32[ "$VTYPE" = -current ] && HTTP_FWDIR=snapshots 33 34FWURL=http://firmware.openbsd.org/firmware/${HTTP_FWDIR} 35FWPUB_KEY=${DESTDIR}/etc/signify/openbsd-${VERSION}-fw.pub 36 37DRYRUN=false 38integer VERBOSE=0 39DELETE=false 40DOWNLOAD=true 41INSTALL=true 42LOCALSRC= 43ENABLE_SPINNER=false 44[ -t 1 ] && ENABLE_SPINNER=true 45 46integer STATUS_FD=1 47integer WARN_FD=2 48FD_DIR= 49 50unset FTPPID 51unset LOCKPID 52unset FWPKGTMP 53REMOVE_LOCALSRC=false 54DROP_PRIVS=true 55 56status() { echo -n "$*" >&"$STATUS_FD"; } 57warn() { echo "$*" >&"$WARN_FD"; } 58 59cleanup() { 60 set +o errexit # ignore errors from killing ftp 61 62 if [ -d "$FD_DIR" ]; then 63 echo "" >&"$STATUS_FD" 64 ((STATUS_FD == 3)) && exec 3>&- 65 ((WARN_FD == 4)) && exec 4>&- 66 67 [ -s "$FD_DIR/status" ] && cat "$FD_DIR/status" 68 [ -s "$FD_DIR/warn" ] && cat "$FD_DIR/warn" >&2 69 70 rm -rf "$FD_DIR" 71 fi 72 73 [ "${FTPPID:-}" ] && kill -TERM -"$FTPPID" 2>/dev/null 74 [ "${LOCKPID:-}" ] && kill -TERM -"$LOCKPID" 2>/dev/null 75 [ "${FWPKGTMP:-}" ] && rm -rf "$FWPKGTMP" 76 "$REMOVE_LOCALSRC" && rm -rf "$LOCALSRC" 77 [ -e "$CFILE" ] && [ ! -s "$CFILE" ] && rm -f "$CFILE" 78} 79trap cleanup EXIT 80 81tmpdir() { 82 local _i=1 _dir 83 84 # The installer lacks mktemp(1), do it by hand 85 if [ -x /usr/bin/mktemp ]; then 86 _dir=$( mktemp -d "${1}-XXXXXXXXX" ) 87 else 88 until _dir="${1}.$_i.$RANDOM" && mkdir -- "$_dir" 2>/dev/null; do 89 ((++_i < 10000)) || return 1 90 done 91 fi 92 93 echo "$_dir" 94} 95 96spin() { 97 if ! "$ENABLE_SPINNER"; then 98 sleep 1 99 return 0 100 fi 101 102 { 103 for p in '/' '-' '\\' '|' '/' '-' '\\' '|'; do 104 echo -n "$p"'\b' 105 sleep 0.125 106 done 107 echo -n " "'\b' 108 }>/dev/tty 109} 110 111fetch() { 112 local _src="${FWURL}/${1##*/}" _dst=$1 _user=_file _exit _error='' 113 local _ftp_errors="$FD_DIR/ftp_errors" 114 rm -f "$_ftp_errors" 115 116 # The installer uses a limited doas(1) as a tiny su(1) 117 set -o monitor # make sure ftp gets its own process group 118 ( 119 _flags=-vm 120 case "$VERBOSE" in 121 0|1) _flags=-VM ; exec 2>"$_ftp_errors" ;; 122 2) _flags=-Vm ;; 123 esac 124 125 if ! "$DROP_PRIVS"; then 126 /usr/bin/ftp -N error -D 'Get/Verify' $_flags -o- "$_src" > "$_dst" 127 elif [ -x /usr/bin/su ]; then 128 exec /usr/bin/su -s /bin/ksh "$_user" -c \ 129 "/usr/bin/ftp -N error -D 'Get/Verify' $_flags -o- '$_src'" > "$_dst" 130 else 131 exec /usr/bin/doas -u "$_user" \ 132 /usr/bin/ftp -N error -D 'Get/Verify' $_flags -o- "$_src" > "$_dst" 133 fi 134 ) & FTPPID=$! 135 set +o monitor 136 137 SECONDS=0 138 _last=0 139 while kill -0 -"$FTPPID" 2>/dev/null; do 140 if [[ $SECONDS -gt 12 ]]; then 141 set -- $( ls -ln "$_dst" 2>/dev/null ) 142 if [[ $_last -ne $5 ]]; then 143 _last=$5 144 SECONDS=0 145 spin 146 else 147 kill -INT -"$FTPPID" 2>/dev/null 148 _error=" (timed out)" 149 fi 150 else 151 spin 152 fi 153 done 154 155 set +o errexit 156 wait "$FTPPID" 157 _exit=$? 158 set -o errexit 159 160 unset FTPPID 161 162 if ((_exit != 0)); then 163 rm -f "$_dst" 164 165 # ftp doesn't provide useful exit codes 166 # so we have to grep its STDERR. 167 # _exit=2 means don't keep trying 168 _exit=2 169 170 # If it was 404, we might succeed at another file 171 if [ -s "$_ftp_errors" ] && \ 172 grep -q "404 Not Found" "$_ftp_errors"; then 173 _exit=1 174 _error=" (404 Not Found)" 175 rm -f "$_ftp_errors" 176 fi 177 178 warn "Cannot fetch $_src$_error" 179 fi 180 181 # If we have ftp errors, print them out, 182 # removing any cntrl characters (like 0x0d), 183 # and any leading blank lines. 184 if [ -s "$_ftp_errors" ]; then 185 sed -e 's/[[:cntrl:]]//g' \ 186 -e '/./,$!d' "$_ftp_errors" >&"$WARN_FD" 187 fi 188 189 return "$_exit" 190} 191 192# If we fail to fetch the CFILE, we don't want to try again 193# but we might be doing this in a subshell so write out 194# a blank file indicating failure. 195check_cfile() { 196 if [ -e "$CFILE" ]; then 197 [ -s "$CFILE" ] || return 2 198 return 0 199 fi 200 if ! fetch_cfile; then 201 echo -n > "$CFILE" 202 return 2 203 fi 204 return 0 205} 206 207fetch_cfile() { 208 if "$DOWNLOAD"; then 209 set +o noclobber # we want to get the latest CFILE 210 fetch "$CFILE" || return 1 211 set -o noclobber 212 signify -qVep "$FWPUB_KEY" -x "$CFILE" -m /dev/null \ 213 2>&"$WARN_FD" || { 214 warn "Signature check of SHA256.sig failed" 215 rm -f "$CFILE" 216 return 1 217 } 218 elif [ ! -e "$CFILE" ]; then 219 warn "${0##*/}: $CFILE: No such file or directory" 220 return 1 221 fi 222 223 return 0 224} 225 226verify() { 227 check_cfile || return $? 228 # The installer sha256 lacks -C, do it by hand 229 if ! grep -Fqx "SHA256 (${1##*/}) = $( /bin/sha256 -qb "$1" )" "$CFILE" 230 then 231 ((VERBOSE != 1)) && warn "Checksum test for ${1##*/} failed." 232 return 1 233 fi 234 235 return 0 236} 237 238# When verifying existing files that we are going to re-download 239# if VERBOSE is 0, don't show the checksum failure of an existing file. 240verify_existing() { 241 local _v=$VERBOSE 242 check_cfile || return $? 243 244 ((_v == 0)) && "$DOWNLOAD" && _v=1 245 ( VERBOSE=$_v verify "$@" ) 246} 247 248devices_in_dmesg() { 249 local IFS 250 local _d _m _dmesgtail _last='' _nl=' 251' 252 253 # The dmesg can contain multiple boots, only look in the last one 254 _dmesgtail="$( echo ; sed -n 'H;/^OpenBSD/h;${g;p;}' /var/run/dmesg.boot )" 255 256 grep -v '^[[:space:]]*#' "$FWPATTERNS" | 257 while read -r _d _m; do 258 [ "$_d" = "$_last" ] && continue 259 [ "$_m" ] || _m="${_nl}${_d}[0-9] at " 260 [ "$_m" = "${_m#^}" ] || _m="${_nl}${_m#^}" 261 262 IFS='*' 263 set -- $_m 264 unset IFS 265 266 case $# in 267 1|2|3) [[ $_dmesgtail = *$1*([!$_nl])${2-}*([!$_nl])${3-}* ]] || continue;; 268 *) warn "${0##*/}: Bad pattern '${_m#$_nl}' in $FWPATTERNS"; exit 1 ;; 269 esac 270 271 echo "$_d" 272 _last="$_d" 273 done 274} 275 276firmware_filename() { 277 check_cfile || return $? 278 sed -n "s/.*(\($1-firmware-.*\.tgz\)).*/\1/p" "$CFILE" | sed '$!d' 279} 280 281firmware_devicename() { 282 local _d="${1##*/}" 283 _d="${_d%-firmware-*}" 284 echo "$_d" 285} 286 287lock_db() { 288 local _waited 289 [ "${LOCKPID:-}" ] && return 0 290 291 # The installer doesn't have perl, so we can't lock there 292 [ -e /usr/bin/perl ] || return 0 293 294 set -o monitor 295 perl <<-'EOL' |& 296 no lib ('/usr/local/libdata/perl5/site_perl'); 297 use v5.36; 298 use OpenBSD::PackageInfo qw< lock_db >; 299 300 $|=1; 301 302 $0 = "fw_update: lock_db"; 303 my $waited = 0; 304 package OpenBSD::FwUpdateState { 305 use parent 'OpenBSD::BaseState'; 306 sub errprint ($self, @p) { 307 if ($p[0] && $p[0] =~ /already locked/) { 308 $waited++; 309 $p[0] = " " . $p[0] 310 if !$ENV{VERBOSE}; 311 } 312 $self->SUPER::errprint(@p); 313 } 314 315 } 316 lock_db(0, 'OpenBSD::FwUpdateState'); 317 318 say "$$ $waited"; 319 320 # Wait for STDOUT to be readable, which won't happen 321 # but if our parent exits unexpectedly it will close. 322 my $rin = ''; 323 vec($rin, fileno(STDOUT), 1) = 1; 324 select $rin, '', '', undef; 325EOL 326 set +o monitor 327 328 read -rp LOCKPID _waited 329 330 if ((_waited)); then 331 ! ((VERBOSE)) && status "${0##*/}:" 332 fi 333 334 return 0 335} 336 337available_firmware() { 338 check_cfile || return $? 339 sed -n 's/.*(\(.*\)-firmware.*/\1/p' "$CFILE" 340} 341 342installed_firmware() { 343 local _pre="$1" _match="$2" _post="$3" _firmware _fw 344 set -sA _firmware -- $( 345 set +o noglob 346 grep -Fxl '@option firmware' \ 347 "${DESTDIR}/var/db/pkg/"$_pre"$_match"$_post"/+CONTENTS" \ 348 2>/dev/null || true 349 set -o noglob 350 ) 351 352 [ "${_firmware[*]:-}" ] || return 0 353 for _fw in "${_firmware[@]}"; do 354 _fw="${_fw%/+CONTENTS}" 355 echo "${_fw##*/}" 356 done 357} 358 359detect_firmware() { 360 local _devices _last='' _d 361 362 set -sA _devices -- $( 363 devices_in_dmesg 364 for _d in $( installed_firmware '*' '-firmware-' '*' ); do 365 firmware_devicename "$_d" 366 done 367 ) 368 369 [ "${_devices[*]:-}" ] || return 0 370 for _d in "${_devices[@]}"; do 371 [ "$_last" = "$_d" ] && continue 372 echo "$_d" 373 _last="$_d" 374 done 375} 376 377add_firmware () { 378 local _f="${1##*/}" _m="${2:-Install}" 379 local _pkgdir="${DESTDIR}/var/db/pkg" _pkg 380 FWPKGTMP="$( tmpdir "${DESTDIR}/var/db/pkg/.firmware" )" 381 local _flags=-vm 382 case "$VERBOSE" in 383 0|1) _flags=-VM ;; 384 2|3) _flags=-Vm ;; 385 esac 386 387 ftp -N "${0##/}" -D "$_m" "$_flags" -o- "file:${1}" | 388 tar -s ",^\+,${FWPKGTMP}/+," \ 389 -s ",^firmware,${DESTDIR}/etc/firmware," \ 390 -C / -zxphf - "+*" "firmware/*" 391 392 393 [ -s "${FWPKGTMP}/+CONTENTS" ] && 394 _pkg="$( sed -n '/^@name /{s///p;q;}' "${FWPKGTMP}/+CONTENTS" )" 395 396 if [ ! "${_pkg:-}" ]; then 397 warn "Failed to extract name from $1, partial install" 398 rm -rf "$FWPKGTMP" 399 unset FWPKGTMP 400 return 1 401 fi 402 403 if [ -e "$_pkgdir/$_pkg" ]; then 404 warn "Failed to register: $_pkgdir/$_pkg is not firmware" 405 rm -rf "$FWPKGTMP" 406 unset FWPKGTMP 407 return 1 408 fi 409 410 ed -s "${FWPKGTMP}/+CONTENTS" <<EOL 411/^@comment pkgpath/ -1a 412@option manual-installation 413@option firmware 414@comment install-script 415. 416w 417EOL 418 419 chmod 755 "$FWPKGTMP" 420 mv "$FWPKGTMP" "$_pkgdir/$_pkg" 421 unset FWPKGTMP 422} 423 424remove_files() { 425 local _r 426 # Use rm -f, not removing files/dirs is probably not worth failing over 427 for _r in "$@" ; do 428 if [ -d "$_r" ]; then 429 # The installer lacks rmdir, 430 # but we only want to remove empty directories. 431 set +o noglob 432 [ "$_r/*" = "$( echo "$_r"/* )" ] && rm -rf "$_r" 433 set -o noglob 434 else 435 rm -f "$_r" 436 fi 437 done 438} 439 440delete_firmware() { 441 local _cwd _pkg="$1" _pkgdir="${DESTDIR}/var/db/pkg" 442 443 # TODO: Check hash for files before deleting 444 ((VERBOSE > 2)) && echo -n "Uninstall $_pkg ..." 445 _cwd="${_pkgdir}/$_pkg" 446 447 if [ ! -e "$_cwd/+CONTENTS" ] || 448 ! grep -Fxq '@option firmware' "$_cwd/+CONTENTS"; then 449 warn "${0##*/}: $_pkg does not appear to be firmware" 450 return 2 451 fi 452 453 set -A _remove -- "${_cwd}/+CONTENTS" "${_cwd}" 454 455 while read -r _c _g; do 456 case $_c in 457 @cwd) _cwd="${DESTDIR}$_g" 458 ;; 459 @*) continue 460 ;; 461 *) set -A _remove -- "$_cwd/$_c" "${_remove[@]}" 462 ;; 463 esac 464 done < "${_pkgdir}/${_pkg}/+CONTENTS" 465 466 remove_files "${_remove[@]}" 467 468 ((VERBOSE > 2)) && echo " done." 469 470 return 0 471} 472 473unregister_firmware() { 474 local _d="$1" _pkgdir="${DESTDIR}/var/db/pkg" _fw 475 476 set -A installed -- $( installed_firmware '' "$d-firmware-" '*' ) 477 if [ "${installed:-}" ]; then 478 for _fw in "${installed[@]}"; do 479 ((VERBOSE)) && echo "Unregister $_fw" 480 "$DRYRUN" && continue 481 remove_files \ 482 "$_pkgdir/$_fw/+CONTENTS" \ 483 "$_pkgdir/$_fw/+DESC" \ 484 "$_pkgdir/$_fw/" 485 done 486 return 0 487 fi 488 489 return 1 490} 491 492usage() { 493 echo "usage: ${0##*/} [-adFlnv] [-p path] [driver | file ...]" 494 exit 1 495} 496 497ALL=false 498LIST=false 499while getopts :adFlnp:v name 500do 501 case "$name" in 502 a) ALL=true ;; 503 d) DELETE=true ;; 504 F) INSTALL=false ;; 505 l) LIST=true ;; 506 n) DRYRUN=true ;; 507 p) FWURL="$OPTARG" ;; 508 v) ((++VERBOSE)) ;; 509 :) 510 warn "${0##*/}: option requires an argument -- -$OPTARG" 511 usage 512 ;; 513 ?) 514 warn "${0##*/}: unknown option -- -$OPTARG" 515 usage 516 ;; 517 esac 518done 519shift $((OPTIND - 1)) 520 521# When listing, provide a clean output 522"$LIST" && VERBOSE=1 ENABLE_SPINNER=false 523 524# Progress bars, not spinner When VERBOSE > 1 525((VERBOSE > 1)) && ENABLE_SPINNER=false 526 527if [[ $FWURL != @(ftp|http?(s))://* ]]; then 528 FWURL="${FWURL#file:}" 529 ! [ -d "$FWURL" ] && 530 warn "The path must be a URL or an existing directory" && 531 exit 1 532 DOWNLOAD=false 533 FWURL="file:$FWURL" 534fi 535 536if [ -x /usr/bin/id ] && [ "$(/usr/bin/id -u)" != 0 ]; then 537 if ! "$INSTALL" || "$LIST"; then 538 # When we aren't in the installer, 539 # allow downloading as the current user. 540 DROP_PRIVS=false 541 else 542 warn "need root privileges" 543 exit 1 544 fi 545fi 546 547set -sA devices -- "$@" 548 549FD_DIR="$( tmpdir "${DESTDIR}/tmp/${0##*/}-fd" )" 550# When being verbose, save the status line for the end. 551if ((VERBOSE)); then 552 exec 3>"${FD_DIR}/status" 553 STATUS_FD=3 554fi 555# Control "warning" messages to avoid the middle of a line. 556# Things that we don't expect to send to STDERR 557# still go there so the output, while it may be ugly, isn't lost 558exec 4>"${FD_DIR}/warn" 559WARN_FD=4 560 561status "${0##*/}:" 562 563if "$DELETE"; then 564 ! "$INSTALL" && warn "Cannot use -F and -d" && usage 565 lock_db 566 567 # Show the "Uninstall" message when just deleting not upgrading 568 ((VERBOSE)) && VERBOSE=3 569 570 set -A installed 571 if [ "${devices[*]:-}" ]; then 572 "$ALL" && warn "Cannot use -a and devices/files" && usage 573 574 set -A installed -- $( 575 for d in "${devices[@]}"; do 576 f="${d##*/}" # only care about the name 577 f="${f%.tgz}" # allow specifying the package name 578 [ "$( firmware_devicename "$f" )" = "$f" ] && f="$f-firmware" 579 580 set -A i -- $( installed_firmware '' "$f-" '*' ) 581 582 if [ "${i[*]:-}" ]; then 583 echo "${i[@]}" 584 else 585 warn "No firmware found for '$d'" 586 fi 587 done 588 ) 589 elif "$ALL"; then 590 set -A installed -- $( installed_firmware '*' '-firmware-' '*' ) 591 else 592 set -A installed -- $( 593 set -- $( devices_in_dmesg ) 594 for f in $( installed_firmware '*' -firmware- '*' ); do 595 n="$( firmware_devicename "$f" )" 596 for d; do 597 [ "$d" = "$n" ] && continue 2 598 done 599 echo "$f" 600 done 601 ) 602 fi 603 604 status " delete " 605 606 comma='' 607 if [ "${installed:-}" ]; then 608 for fw in "${installed[@]}"; do 609 status "$comma$( firmware_devicename "$fw" )" 610 comma=, 611 if "$DRYRUN"; then 612 ((VERBOSE)) && echo "Delete $fw" 613 elif "$LIST"; then 614 echo "$fw" 615 else 616 delete_firmware "$fw" || { 617 status " ($fw failed)" 618 continue 619 } 620 fi 621 done 622 fi 623 624 [ "$comma" ] || status none 625 626 # no status when listing 627 "$LIST" && rm -f "$FD_DIR/status" 628 629 exit 630fi 631 632! "$INSTALL" && ! "$LIST" && LOCALSRC="${LOCALSRC:-.}" 633 634if [ ! "$LOCALSRC" ]; then 635 LOCALSRC="$( tmpdir "${DESTDIR}/tmp/${0##*/}" )" 636 REMOVE_LOCALSRC=true 637fi 638 639CFILE="$LOCALSRC/$CFILE" 640 641if [ "${devices[*]:-}" ]; then 642 "$ALL" && warn "Cannot use -a and devices/files" && usage 643elif "$ALL"; then 644 set -sA devices -- $( available_firmware ) 645else 646 ((VERBOSE > 1)) && echo -n "Detect firmware ..." 647 set -sA devices -- $( detect_firmware ) 648 ((VERBOSE > 1)) && 649 { [ "${devices[*]:-}" ] && echo " found." || echo " done." ; } 650fi 651 652 653set -A add '' 654set -A update '' 655kept='' 656unregister='' 657 658"$LIST" && ! "$INSTALL" && 659 echo "$FWURL/${CFILE##*/}" 660 661if [ "${devices[*]:-}" ]; then 662 lock_db 663 for f in "${devices[@]}"; do 664 d="$( firmware_devicename "$f" )" 665 666 if "$LIST" && "$INSTALL"; then 667 echo "$d" 668 continue 669 fi 670 671 verify_existing=true 672 if [ "$f" = "$d" ]; then 673 f=$( firmware_filename "$d" ) || { 674 # Fetching the CFILE here is often the 675 # first attempt to talk to FWURL 676 # If it fails, no point in continuing. 677 if (($? > 1)); then 678 status " failed." 679 exit 1 680 fi 681 682 # otherwise we can try the next firmware 683 continue 684 } 685 if [ ! "$f" ]; then 686 if "$INSTALL" && unregister_firmware "$d"; then 687 unregister="$unregister,$d" 688 else 689 warn "Unable to find firmware for $d" 690 fi 691 continue 692 fi 693 elif ! "$INSTALL" && ! grep -Fq "($f)" "$CFILE" ; then 694 warn "Cannot download local file $f" 695 exit 1 696 else 697 # Don't verify files specified on the command-line 698 verify_existing=false 699 fi 700 701 if "$LIST"; then 702 echo "$FWURL/$f" 703 continue 704 fi 705 706 set -A installed 707 if "$INSTALL"; then 708 set -A installed -- \ 709 $( installed_firmware '' "$d-firmware-" '*' ) 710 711 if [ "${installed[*]:-}" ]; then 712 for i in "${installed[@]}"; do 713 if [ "${f##*/}" = "$i.tgz" ]; then 714 ((VERBOSE > 2)) \ 715 && echo "Keep $i" 716 kept="$kept,$d" 717 continue 2 718 fi 719 done 720 fi 721 fi 722 723 # Fetch an unqualified file into LOCALSRC 724 # if it doesn't exist in the current directory. 725 if [ "$f" = "${f##/}" ] && [ ! -e "$f" ]; then 726 f="$LOCALSRC/$f" 727 fi 728 729 if "$verify_existing" && [ -e "$f" ]; then 730 pending_status=false 731 if ((VERBOSE == 1)); then 732 echo -n "Verify ${f##*/} ..." 733 pending_status=true 734 elif ((VERBOSE > 1)) && ! "$INSTALL"; then 735 echo "Keep/Verify ${f##*/}" 736 fi 737 738 if "$DRYRUN" || verify_existing "$f"; then 739 "$pending_status" && echo " done." 740 if ! "$INSTALL"; then 741 kept="$kept,$d" 742 continue 743 fi 744 elif "$DOWNLOAD"; then 745 "$pending_status" && echo " failed." 746 ((VERBOSE > 1)) && echo "Refetching $f" 747 rm -f "$f" 748 else 749 "$pending_status" && echo " failed." 750 continue 751 fi 752 fi 753 754 if [ "${installed[*]:-}" ]; then 755 set -A update -- "${update[@]}" "$f" 756 else 757 set -A add -- "${add[@]}" "$f" 758 fi 759 760 done 761fi 762 763if "$LIST"; then 764 # No status when listing 765 rm -f "$FD_DIR/status" 766 exit 767fi 768 769if "$INSTALL"; then 770 status " add " 771 action=Install 772else 773 status " download " 774 action=Download 775fi 776 777comma='' 778[ "${add[*]}" ] || status none 779for f in "${add[@]}" _update_ "${update[@]}"; do 780 [ "$f" ] || continue 781 if [ "$f" = _update_ ]; then 782 comma='' 783 "$INSTALL" || continue 784 action=Update 785 status "; update " 786 [ "${update[*]}" ] || status none 787 continue 788 fi 789 d="$( firmware_devicename "$f" )" 790 status "$comma$d" 791 comma=, 792 793 pending_status=false 794 if [ -e "$f" ]; then 795 if "$DRYRUN"; then 796 ((VERBOSE)) && echo "$action ${f##*/}" 797 else 798 if ((VERBOSE == 1)); then 799 echo -n "Install ${f##*/} ..." 800 pending_status=true 801 fi 802 fi 803 elif "$DOWNLOAD"; then 804 if "$DRYRUN"; then 805 ((VERBOSE)) && echo "Get/Verify ${f##*/}" 806 else 807 if ((VERBOSE == 1)); then 808 echo -n "Get/Verify ${f##*/} ..." 809 pending_status=true 810 fi 811 fetch "$f" && 812 verify "$f" || { 813 integer e=$? 814 815 "$pending_status" && echo " failed." 816 status " failed (${f##*/})" 817 818 if ((VERBOSE)) && [ -s "$FD_DIR/warn" ]; then 819 cat "$FD_DIR/warn" >&2 820 rm -f "$FD_DIR/warn" 821 fi 822 823 # Fetch or verify exited > 1 824 # which means we don't keep trying. 825 ((e > 1)) && exit 1 826 827 continue 828 } 829 fi 830 elif "$INSTALL"; then 831 warn "Cannot install ${f##*/}, not found" 832 continue 833 fi 834 835 if ! "$INSTALL"; then 836 "$pending_status" && echo " done." 837 continue 838 fi 839 840 if ! "$DRYRUN"; then 841 if [ "$action" = Update ]; then 842 for i in $( installed_firmware '' "$d-firmware-" '*' ) 843 do 844 delete_firmware "$i" || { 845 "$pending_status" && 846 echo -n " (remove $i failed)" 847 status " (remove $i failed)" 848 849 continue 850 } 851 #status " (removed $i)" 852 done 853 fi 854 855 add_firmware "$f" "$action" || { 856 "$pending_status" && echo " failed." 857 status " failed (${f##*/})" 858 continue 859 } 860 fi 861 862 if "$pending_status"; then 863 if [ "$action" = Install ]; then 864 echo " installed." 865 else 866 echo " updated." 867 fi 868 fi 869done 870 871[ "$unregister" ] && status "; unregister ${unregister:#,}" 872[ "$kept" ] && status "; keep ${kept:#,}" 873 874exit 0 875