Here's the next iteration.  I'll probably commit it later this weekend
unless someone finds issues with it.

Here we trap errors from `ftp` unless we're using the verbose modes with
progress bars.  We then parse those to guess whether we might be able to
find a different firmware (currently only 404) and save the errors for
later printing.  We then return a different error code indicating
whether we should exit immediately or not.  We can then output those
errors after we've finished outputting the current line.

Comments, testing results, OK?


Index: fw_update.sh
===================================================================
RCS file: /cvs/src/usr.sbin/fw_update/fw_update.sh,v
retrieving revision 1.50
diff -u -p -r1.50 fw_update.sh
--- fw_update.sh        28 Sep 2023 01:18:52 -0000      1.50
+++ fw_update.sh        30 Sep 2023 20:18:44 -0000
@@ -61,6 +61,7 @@ cleanup() {
        if [ -d "$FD_DIR" ]; then
                echo "" >&"$STATUS_FD"
                exec 4>&-
+               exec 5>&-
 
                [ -s "$FD_DIR/status" ] && cat "$FD_DIR/status"
                [ -s "$FD_DIR/warn"   ] && cat "$FD_DIR/warn" >&2
@@ -107,21 +108,24 @@ spin() {
 
 fetch() {
        local _src="${FWURL}/${1##*/}" _dst=$1 _user=_file _exit _error=''
+       local _ftp_errors="$FD_DIR/ftp_errors"
+       rm -f "$_ftp_errors"
 
        # The installer uses a limited doas(1) as a tiny su(1)
        set -o monitor # make sure ftp gets its own process group
        (
        _flags=-vm
        case "$VERBOSE" in
-               0|1) _flags=-VM ;;
+               0|1) _flags=-VM ; exec 2>"$_ftp_errors" ;;
                  2) _flags=-Vm ;;
        esac
+
        if [ -x /usr/bin/su ]; then
                exec /usr/bin/su -s /bin/ksh "$_user" -c \
-                   "/usr/bin/ftp -N '${0##/}' -D 'Get/Verify' $_flags -o- 
'$_src'" > "$_dst"
+                   "/usr/bin/ftp -N error -D 'Get/Verify' $_flags -o- '$_src'" 
> "$_dst"
        else
                exec /usr/bin/doas -u "$_user" \
-                   /usr/bin/ftp -N "${0##/}" -D 'Get/Verify' $_flags -o- 
"$_src" > "$_dst"
+                   /usr/bin/ftp -N error -D 'Get/Verify' $_flags -o- "$_src" > 
"$_dst"
        fi
        ) & FTPPID=$!
        set +o monitor
@@ -151,13 +155,32 @@ fetch() {
 
        unset FTPPID
 
-       if [ "$_exit" -ne 0 ]; then
+       if ((_exit != 0)); then
                rm -f "$_dst"
+
+               # ftp doesn't provide useful exit codes
+               # so we have to grep its STDERR.
+               # _exit=2 means don't keep trying
+               _exit=2
+
+               # If it was 404, we might succeed at another file
+               if [ -s "$_ftp_errors" ] && grep -q "404 Not Found" 
"$_ftp_errors"; then
+                       _exit=1
+                       _error=" (404 Not Found)"
+                       rm -f "$_ftp_errors"
+               fi
+
                warn "Cannot fetch $_src$_error"
-               return 1
        fi
 
-       return 0
+       # If we have ftp errors, print them out,
+       # removing any cntrl characters (like 0x0d),
+       # and any leading blank lines.
+       [ -s "$_ftp_errors" ] &&
+               sed -e 's/[[:cntrl:]]//g' \
+                   -e '/./,$!d' "$_ftp_errors" >&"$WARN_FD"
+
+       return "$_exit"
 }
 
 # If we fail to fetch the CFILE, we don't want to try again
@@ -165,12 +188,12 @@ fetch() {
 # a blank file indicating failure.
 check_cfile() {
        if [ -e "$CFILE" ]; then
-               [ -s "$CFILE" ] || return 1
+               [ -s "$CFILE" ] || return 2
                return 0
        fi
        if ! fetch_cfile; then
                echo -n > "$CFILE"
-               return 1
+               return 2
        fi
        return 0
 }
@@ -192,7 +215,7 @@ fetch_cfile() {
 }
 
 verify() {
-       check_cfile || return 1
+       check_cfile || return $?
        # The installer sha256 lacks -C, do it by hand
        if ! grep -Fqx "SHA256 (${1##*/}) = $( /bin/sha256 -qb "$1" )" "$CFILE"
        then
@@ -207,7 +230,7 @@ verify() {
 # if VERBOSE is 0, don't show the checksum failure of an existing file.
 verify_existing() {
        local _v=$VERBOSE
-       check_cfile || return 1
+       check_cfile || return $?
 
        ((_v == 0)) && "$DOWNLOAD" && _v=1
        ( VERBOSE=$_v verify "$@" )
@@ -242,7 +265,7 @@ firmware_in_dmesg() {
 }
 
 firmware_filename() {
-       check_cfile || return 1
+       check_cfile || return $?
        sed -n "s/.*(\($1-firmware-.*\.tgz\)).*/\1/p" "$CFILE" | sed '$!d'
 }
 
@@ -334,8 +357,11 @@ add_firmware () {
                    -s ",^firmware,${DESTDIR}/etc/firmware," \
                    -C / -zxphf - "+*" "firmware/*"
 
-       _pkg="$( sed -n '/^@name /{s///p;q;}' "${FWPKGTMP}/+CONTENTS" )"
-       if [ ! "$_pkg" ]; then
+
+       [ -s "${FWPKGTMP}/+CONTENTS" ] &&
+           _pkg="$( sed -n '/^@name /{s///p;q;}' "${FWPKGTMP}/+CONTENTS" )"
+
+       if [ ! "${_pkg:-}" ]; then
                warn "Failed to extract name from $1, partial install"
                rm -rf "$FWPKGTMP"
                unset FWPKGTMP
@@ -500,23 +526,17 @@ fi
 
 set -sA devices -- "$@"
 
-# In the normal case, we output the status line piecemeal
-# so we save warnings to output at the end to not disrupt
-# the single line status.
-# Actual errors from things like ftp will stil interrupt,
-# but it's impossible to know if it's a message people need
-# to see now or something that can wait.
-# In the verbose case, we instead print out single lines
-# or progress bars for each thing we are doing,
-# so instead we save up the final status line for the end.
 FD_DIR="$( tmpdir "${DESTDIR}/tmp/${0##*/}-fd" )"
+# When being verbose, save the status line for the end.
 if ((VERBOSE)); then
-       exec 4>"${FD_DIR}/status"
-       STATUS_FD=4
-else
-       exec 4>"${FD_DIR}/warn"
-       WARN_FD=4
+       exec 3>"${FD_DIR}/status"
+       STATUS_FD=3
 fi
+# Control "warning" messages to avoid the middle of a line.
+# Things that we don't expect to send to STDERR
+# still go there so the output, while it may be ugly, isn't lost
+exec 4>"${FD_DIR}/warn"
+WARN_FD=4
 
 status "${0##*/}:"
 
@@ -560,7 +580,10 @@ if "$DELETE"; then
                        if "$DRYRUN"; then
                                ((VERBOSE)) && echo "Delete $fw"
                        else
-                               delete_firmware "$fw" || continue
+                               delete_firmware "$fw" || {
+                                       status " ($fw failed)"
+                                       continue
+                               }
                        fi
                done
        fi
@@ -599,7 +622,18 @@ if [ "${devices[*]:-}" ]; then
 
                verify_existing=true
                if [ "$f" = "$d" ]; then
-                       f=$( firmware_filename "$d" ) || continue
+                       f=$( firmware_filename "$d" ) || {
+                               # Fetching the CFILE here is often the
+                               # first attempt to talk to FWURL
+                               # If it fails, no point in continuing.
+                               if (($? > 1)); then
+                                       status " failed."
+                                       exit 1
+                               fi
+
+                               # otherwise we can try the next firmware
+                               continue
+                       }
                        if [ ! "$f" ]; then
                                if "$INSTALL" && unregister_firmware "$d"; then
                                        unregister="$unregister,$d"
@@ -717,11 +751,20 @@ for f in "${add[@]}" _update_ "${update[
                        fi
                        fetch  "$f" &&
                        verify "$f" || {
-                               if "$pending_status"; then
-                                       echo " failed."
-                               elif ! ((VERBOSE)); then
-                                       status " failed (${f##*/})"
+                               integer e=$?
+
+                               "$pending_status" && echo " failed."
+                               status " failed (${f##*/})"
+
+                               if ((VERBOSE)) && [ -s "$FD_DIR/warn" ]; then
+                                       cat "$FD_DIR/warn" >&2
+                                       rm -f "$FD_DIR/warn"
                                fi
+
+                               # Fetch or verify exited > 1
+                               # which means we don't keep trying.
+                               ((e > 1)) && exit 1
+
                                continue
                        }
                fi
@@ -740,22 +783,19 @@ for f in "${add[@]}" _update_ "${update[
                        for i in $( installed_firmware '' "$d-firmware-" '*' )
                        do
                                delete_firmware "$i" || {
-                                       if "$pending_status"; then
-                                               echo " failed."
-                                       elif ! ((VERBOSE)); then
-                                               status " failed ($i)"
-                                       fi
+                                       "$pending_status" &&
+                                           echo -n " (remove $i failed)"
+                                       status " (remove $i failed)"
+
                                        continue
                                }
+                               #status " (removed $i)"
                        done
                fi
 
                add_firmware "$f" "$action" || {
-                       if "$pending_status"; then
-                               echo " failed."
-                       elif ! ((VERBOSE)); then
-                               status " failed (${f##*/})"
-                       fi
+                       "$pending_status" && echo " failed."
+                       status " failed (${f##*/})"
                        continue
                }
        fi

Reply via email to