On Sun, Aug 13, 2023 at 04:42:49PM -0600, Theo de Raadt wrote: > Stuart Henderson <s...@spacehopper.org> wrote: > > > On 2023/08/13 11:44, Andrew Hewus Fresh wrote: > > > My laptop doesn't have the fastest wifi and sysupgrade already uses a > > > progress bar to show what it's doing, so I'd really like to provide more > > > feedback on what it is doing: > > > > Does a single -v give enough feedback? > > As shown below, -vv is ridiculously verbose. > > I think -v is also too verbose.
That makes sense to me. Here's a fairly intrusive patch that in the normal case prints the status line "as we go" so you can see that it is doing something. Errors from ftp are not super friendly, although I'm not entirely sure what it should do yet, so right now I do nothing. In the normal and single -v case, it also adds a spinner while it is downloading if there STDOUT is a tty. We now go in two phases for install/update where we first detect what needs to be done and then do it so we can separate install from download. In any case, I think this is ready for wider testing. Comments, suggestions, OK? Index: fw_update.sh =================================================================== RCS file: /cvs/src/usr.sbin/fw_update/fw_update.sh,v retrieving revision 1.44 diff -u -p -r1.44 fw_update.sh --- fw_update.sh 12 Dec 2022 02:30:51 -0000 1.44 +++ fw_update.sh 19 Aug 2023 21:49:39 -0000 @@ -40,18 +40,39 @@ DELETE=false DOWNLOAD=true INSTALL=true LOCALSRC= +ENABLE_SPINNER=false +[ -t 1 ] && ENABLE_SPINNER=true + +integer STATUS_FD=1 +integer WARN_FD=2 +FD_DIR= unset FTPPID unset LOCKPID unset FWPKGTMP REMOVE_LOCALSRC=false + +status() { echo -n "$*" >&"$STATUS_FD"; } +warn() { echo "$*" >&"$WARN_FD"; } + cleanup() { set +o errexit # ignore errors from killing ftp + + if [ -d "$FD_DIR" ]; then + echo "" >&"$STATUS_FD" + exec 4>&- + + [ -s "$FD_DIR/status" ] && cat "$FD_DIR/status" + [ -s "$FD_DIR/warn" ] && cat "$FD_DIR/warn" >&2 + + rm -rf "$FD_DIR" + fi + [ "${FTPPID:-}" ] && kill -TERM -"$FTPPID" 2>/dev/null [ "${LOCKPID:-}" ] && kill -TERM -"$LOCKPID" 2>/dev/null [ "${FWPKGTMP:-}" ] && rm -rf "$FWPKGTMP" "$REMOVE_LOCALSRC" && rm -rf "$LOCALSRC" - [ -e "${CFILE}" ] && [ ! -s "$CFILE" ] && rm -f "$CFILE" + [ -e "$CFILE" ] && [ ! -s "$CFILE" ] && rm -f "$CFILE" } trap cleanup EXIT @@ -70,6 +91,22 @@ tmpdir() { echo "$_dir" } +spin() { + if ! "$ENABLE_SPINNER"; then + sleep 1 + return 0 + fi + + { + echo -n ' ' + for p in '/' '-' '\\' '|'; do + echo -n '\010'"$p" + sleep 0.25 + done + echo -n "\010 \010\010" + }>/dev/tty +} + fetch() { local _src="${FWURL}/${1##*/}" _dst=$1 _user=_file _exit _error='' @@ -99,13 +136,13 @@ fetch() { if [[ $_last -ne $5 ]]; then _last=$5 SECONDS=0 - sleep 1 + spin else kill -INT -"$FTPPID" 2>/dev/null _error=" (timed out)" fi else - sleep 1 + spin fi done @@ -118,7 +155,7 @@ fetch() { if [ "$_exit" -ne 0 ]; then rm -f "$_dst" - echo "Cannot fetch $_src$_error" >&2 + warn "Cannot fetch $_src$_error" return 1 fi @@ -133,7 +170,7 @@ check_cfile() { [ -s "$CFILE" ] || return 1 return 0 fi - if ! fetch_cfile "$@"; then + if ! fetch_cfile; then echo -n > "$CFILE" return 1 fi @@ -146,10 +183,10 @@ fetch_cfile() { fetch "$CFILE" || return 1 set -o noclobber ! signify -qVep "$FWPUB_KEY" -x "$CFILE" -m "$CFILE" && - echo "Signature check of SHA256.sig failed" >&2 && + warn "Signature check of SHA256.sig failed" && rm -f "$CFILE" && return 1 elif [ ! -e "$CFILE" ]; then - echo "${0##*/}: $CFILE: No such file or directory" >&2 + warn "${0##*/}: $CFILE: No such file or directory" return 1 fi @@ -159,14 +196,25 @@ fetch_cfile() { verify() { check_cfile || return 1 # The installer sha256 lacks -C, do it by hand - if ! fgrep -qx "SHA256 (${1##*/}) = $( /bin/sha256 -qb "$1" )" "$CFILE"; then - ((VERBOSE != 1)) && echo "Checksum test for ${1##*/} failed." >&2 + if ! grep -Fqx "SHA256 (${1##*/}) = $( /bin/sha256 -qb "$1" )" "$CFILE" + then + ((VERBOSE != 1)) && warn "Checksum test for ${1##*/} failed." return 1 fi return 0 } +# When verifying existing files that we are going to re-download +# if VERBOSE is 0, don't show the checksum failure of an existing file. +verify_existing() { + local _v=$VERBOSE + check_cfile || return 1 + + ((_v == 0)) && "$DOWNLOAD" && _v=1 + ( VERBOSE=$_v verify "$@" ) +} + firmware_in_dmesg() { local IFS local _d _m _dmesgtail _last='' _nl=' @@ -187,7 +235,7 @@ firmware_in_dmesg() { case $# in 1|2|3) [[ $_dmesgtail = *$1*([!$_nl])${2-}*([!$_nl])${3-}* ]] || continue;; - *) echo "${0##*/}: Bad pattern '${_m#$_nl}' in $FWPATTERNS" >&2; exit 1 ;; + *) warn "${0##*/}: Bad pattern '${_m#$_nl}' in $FWPATTERNS"; exit 1 ;; esac echo "$_d" @@ -222,7 +270,7 @@ lock_db() { $|=1; lock_db(0); - + say $$; sleep; EOL @@ -329,7 +377,7 @@ delete_firmware() { if [ ! -e "$_cwd/+CONTENTS" ] || ! grep -Fxq '@option firmware' "$_cwd/+CONTENTS"; then - echo "${0##*/}: $_pkg does not appear to be firmware" >&2 + warn "${0##*/}: $_pkg does not appear to be firmware" return 2 fi @@ -389,17 +437,20 @@ do p) LOCALSRC="$OPTARG" ;; v) ((++VERBOSE)) ;; :) - echo "${0##*/}: option requires an argument -- -$OPTARG" >&2 + warn "${0##*/}: option requires an argument -- -$OPTARG" usage ;; ?) - echo "${0##*/}: unknown option -- -$OPTARG" >&2 + warn "${0##*/}: unknown option -- -$OPTARG" usage ;; esac done shift $((OPTIND - 1)) +# Progress bars, not spinner When VERBOSE > 1 +((VERBOSE > 1)) && ENABLE_SPINNER=false + if [ "$LOCALSRC" ]; then if [[ $LOCALSRC = @(ftp|http?(s))://* ]]; then FWURL="${LOCALSRC}" @@ -407,7 +458,7 @@ if [ "$LOCALSRC" ]; then else LOCALSRC="${LOCALSRC#file:}" ! [ -d "$LOCALSRC" ] && - echo "The path must be a URL or an existing directory" >&2 && + warn "The path must be a URL or an existing directory" && exit 1 fi fi @@ -424,7 +475,7 @@ if [ "$OPT_F" ]; then rm -f "$LOCALSRC/$CFILE-OLD" else mv "$LOCALSRC/$CFILE-OLD" "$LOCALSRC/$CFILE" - echo "Using existing $CFILE" >&2 + warn "Using existing $CFILE" fi fi elif [ "$LOCALSRC" ]; then @@ -432,14 +483,34 @@ elif [ "$LOCALSRC" ]; then fi if [ -x /usr/bin/id ] && [ "$(/usr/bin/id -u)" != 0 ]; then - echo "need root privileges" >&2 + warn "need root privileges" exit 1 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" )" +if ((VERBOSE)); then + exec 4>"${FD_DIR}/status" + STATUS_FD=4 +else + exec 4>"${FD_DIR}/warn" + WARN_FD=4 +fi + +status "${0##*/}:" + if "$DELETE"; then - [ "$OPT_F" ] && echo "Cannot use -F and -d" >&2 && usage + [ "$OPT_F" ] && warn "Cannot use -F and -d" && usage lock_db # Show the "Uninstall" message when just deleting not upgrading @@ -447,7 +518,7 @@ if "$DELETE"; then set -A installed if [ "${devices[*]:-}" ]; then - "$ALL" && echo "Cannot use -a and devices/files" >&2 && usage + "$ALL" && warn "Cannot use -a and devices/files" && usage set -A installed -- $( for d in "${devices[@]}"; do @@ -460,7 +531,7 @@ if "$DELETE"; then if [ "${i[*]:-}" ]; then echo "${i[@]}" else - echo "No firmware found for '$d'" >&2 + warn "No firmware found for '$d'" fi done ) @@ -468,20 +539,22 @@ if "$DELETE"; then set -A installed -- $( installed_firmware '*' '-firmware-' '*' ) fi - deleted='' + status " delete " + + comma='' if [ "${installed:-}" ]; then for fw in "${installed[@]}"; do + status "$comma$( firmware_devicename "$fw" )" + comma=, if "$DRYRUN"; then ((VERBOSE)) && echo "Delete $fw" else delete_firmware "$fw" || continue fi - deleted="$deleted,$( firmware_devicename "$fw" )" done fi - deleted="${deleted#,}" - echo "${0:##*/}: deleted ${deleted:-none}"; + [ "$comma" ] || status none exit fi @@ -494,7 +567,7 @@ fi CFILE="$LOCALSRC/$CFILE" if [ "${devices[*]:-}" ]; then - "$ALL" && echo "Cannot use -a and devices/files" >&2 && usage + "$ALL" && warn "Cannot use -a and devices/files" && usage else ((VERBOSE > 1)) && echo -n "Detect firmware ..." set -sA devices -- $( detect_firmware ) @@ -503,10 +576,11 @@ else fi -added='' -updated='' +set -A add '' +set -A update '' kept='' unregister='' + if [ "${devices[*]:-}" ]; then lock_db for f in "${devices[@]}"; do @@ -519,44 +593,53 @@ if [ "${devices[*]:-}" ]; then if "$INSTALL" && unregister_firmware "$d"; then unregister="$unregister,$d" else - echo "Unable to find firmware for $d" >&2 + warn "Unable to find firmware for $d" fi continue fi f="$LOCALSRC/$f" elif ! "$INSTALL" && ! grep -Fq "($f)" "$CFILE" ; then - echo "Cannot download local file $f" >&2 + warn "Cannot download local file $f" exit 1 else # Don't verify files specified on the command-line verify_existing=false fi - set -A installed -- $( installed_firmware '' "$d-firmware-" '*' ) - - if "$INSTALL" && [ "${installed[*]:-}" ]; then - for i in "${installed[@]}"; do - if [ "${f##*/}" = "$i.tgz" ]; then - ((VERBOSE > 2)) && echo "Keep $i" - kept="$kept,$d" - continue 2 - fi - done + set -A installed + if "$INSTALL"; then + set -A installed -- \ + $( installed_firmware '' "$d-firmware-" '*' ) + + if [ "${installed[*]:-}" ]; then + for i in "${installed[@]}"; do + if [ "${f##*/}" = "$i.tgz" ]; then + ((VERBOSE > 2)) \ + && echo "Keep $i" + kept="$kept,$d" + continue 2 + fi + done + fi fi - pending_status=false if "$verify_existing" && [ -e "$f" ]; then + pending_status=false if ((VERBOSE == 1)); then echo -n "Verify ${f##*/} ..." pending_status=true elif ((VERBOSE > 1)) && ! "$INSTALL"; then - echo "Keep/Verify ${f##*/}" + echo "Keep/Verify ${f##*/}" fi - if "$DRYRUN" || verify "$f"; then - "$INSTALL" || kept="$kept,$d" + if "$DRYRUN" || verify_existing "$f"; then + "$pending_status" && echo " done." + if ! "$INSTALL"; then + kept="$kept,$d" + continue + fi elif "$DOWNLOAD"; then - ((VERBOSE == 1)) && echo " failed." + "$pending_status" && echo " failed." ((VERBOSE > 1)) && echo "Refetching $f" rm -f "$f" else @@ -565,67 +648,91 @@ if [ "${devices[*]:-}" ]; then fi fi - if [ -e "$f" ]; then - "$pending_status" && ! "$INSTALL" && echo " done." - elif "$DOWNLOAD"; then - if "$DRYRUN"; then - ((VERBOSE)) && echo "Get/Verify ${f##*/}" - else - if ((VERBOSE == 1)); then - echo -n "Get/Verify ${f##*/} ..." - pending_status=true - fi - fetch "$f" && - verify "$f" || { - "$pending_status" && echo " failed." - continue - } - "$pending_status" && ! "$INSTALL" && echo " done." - fi - "$INSTALL" || added="$added,$d" - elif "$INSTALL"; then - echo "Cannot install ${f##*/}, not found" >&2 - continue + if [ "${installed[*]:-}" ]; then + set -A update -- "${update[@]}" "$f" + else + set -A add -- "${add[@]}" "$f" fi - "$INSTALL" || continue + done +fi - update="Install" - if [ "${installed[*]:-}" ]; then - update="Update" - for i in "${installed[@]}"; do - "$DRYRUN" || delete_firmware "$i" - done - fi +if "$INSTALL"; then + status " add " +else + status " download " +fi +action=Install +comma='' +[ "${add[*]}" ] || status none +for f in "${add[@]}" _update_ "${update[@]}"; do + [ "$f" ] || continue + if [ "$f" = _update_ ]; then + comma='' + "$INSTALL" || continue + action=Update + status "; update " + [ "${update[*]}" ] || status none + continue + fi + d="$( firmware_devicename "$f" )" + status "$comma$d" + comma=, + + pending_status=false + if [ -e "$f" ]; then if "$DRYRUN"; then - ((VERBOSE)) && echo "$update $f" + ((VERBOSE)) && echo "$action ${f##*/}" else - if ((VERBOSE == 1)) && ! "$pending_status"; then + if ((VERBOSE == 1)); then echo -n "Install ${f##*/} ..." pending_status=true fi - add_firmware "$f" "$update" + fi + elif "$DOWNLOAD"; then + if "$DRYRUN"; then + ((VERBOSE)) && echo "Get/Verify ${f##*/}" + else + if ((VERBOSE == 1)); then + echo -n "Get/Verify ${f##*/} ..." + pending_status=true + fi + fetch "$f" && + verify "$f" || { + "$pending_status" && echo " failed." + continue + } + fi + elif "$INSTALL"; then + warn "Cannot install ${f##*/}, not found" + continue + fi + + if ! "$INSTALL"; then + "$pending_status" && echo " done." + continue + fi + + if ! "$DRYRUN"; then + if [ "$action" = Update ]; then + for i in $( installed_firmware '' "$d-firmware-" '*' ) + do + delete_firmware "$i" + done fi - f="${f##*/}" - f="${f%.tgz}" - if [ "$update" = Install ]; then - "$pending_status" && echo " installed." - added="$added,$d" + add_firmware "$f" "$action" + fi + + if "$pending_status"; then + if [ "$action" = Install ]; then + echo " installed." else - "$pending_status" && echo " updated." - updated="$updated,$d" + echo " updated." fi - done -fi + fi +done -added="${added:#,}" -updated="${updated:#,}" -kept="${kept:#,}" -[ "${unregister:-}" ] && unregister="; unregistered ${unregister:#,}" -if "$INSTALL"; then - echo "${0##*/}: added ${added:-none}; updated ${updated:-none}; kept ${kept:-none}${unregister}" -else - echo "${0##*/}: downloaded ${added:-none}; kept ${kept:-none}${unregister}" -fi +[ "${unregister:-}" ] && status "; unregister ${unregister:#,}" +[ "${kept}" ] && status "; keep ${kept:#,}"