From cc7b6eda4d87222d8d6b23bc47e3ede0243cdfc1 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Wed, 6 Jul 2016 15:40:01 +0200 Subject: scripts: save-etc: maintain a journal of operations on the partition It is sometimes useful to know whether certain operations succeed or fail, especially when a system is found to boot on an old config, or when flash I/O errors are detected. This patch ensures that any write access to the flash device will be logged to a file called /.journal on the flash. In case of success, there's at least one line indicating the success, the file name and its size. In case of error, maximum information are extracted and emitted with stderr output and status code of faulty commands, allowing subtle issues like a failed "mv" or "chmod" to be detected. The journal format looks like this : $date $time $product $model $version $tty $prog: $step: $message The tty helps detect if the command was issued by hand or via an interface. "none" is printed if no tty is known. The program reported will be "save-etc". The step will try to indicate where things fail. Common ones will be "archive" and "cancel" (which indicates a rollback of a change). Note that for all steps except "cancel" and "archive", only failures are reported, as successes are standard and provide little information. This ensures that at least one line is emitted on each write without polluting the output too much. The message in the case of the "archive" step, the message is composed of the output file name, its size and its date. Example of output : 2016/07/07 00:17:55 aloha alb3100 8.0.3 none save-etc: archive: |gzip: stdout: No space left on device | status: 1 2016/07/07 00:17:55 aloha alb3100 8.0.3 none save-etc: cancel: rm empty config.new: status: 0 2016/07/07 00:35:24 aloha alb3100 8.0.3 pts/0 save-etc: archive: |config.new|25464|2016-07-07+00:35:24 | status: 0 In order to limit the risk of accidents, the file is also marked for append-only using "chattr +a". --- scripts/save-etc | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/scripts/save-etc b/scripts/save-etc index bf8da1a..3d7968e 100755 --- a/scripts/save-etc +++ b/scripts/save-etc @@ -20,6 +20,7 @@ QUIET=0 LIST=0 TOFILE=0 TONET=0 +PROG="${0##*/}" # Mounts the flash reaw/write in $FLASHDIR. # This checks /proc/cmdline for the LAST 'flash=' statement, and @@ -83,6 +84,29 @@ umount_flash() { return 0 } +# Usage: $0 | +write_journal() +{ + local date product model version tty + local step="$1" + local message="$2" + + date=$(date +"%Y/%m/%d %H:%M:%S") + if /bin/tty -s; then + tty=$(/bin/tty 2>/dev/null) + tty=${tty#/dev/} + else + tty="none" + fi + + set -- $(grep "^Product:" /usr/share/factory/version); product="$2" + set -- $(grep "^Model:" /usr/share/factory/version); model="$2" + set -- $(grep "^Version:" /usr/share/factory/version); version="$2" + + echo "$date $product $model $version $tty $PROG: $step: $message" >> "$FLASHDIR/.journal" 2>/dev/null + chattr +a "$FLASHDIR/.journal" >/dev/null 2>&1 +} + # makes an archive from files in /etc which differ from the factory config, # except for directories and the files we don't want to save, then exports # it to stdout. @@ -202,9 +226,26 @@ fi if [ $STDOUT -eq 0 -a $TOFILE -eq 0 -a $TONET -eq 0 ]; then mount_flash_rw || exit 1 - rm -f $FLASHCFG/config.new - touch $FLASHCFG/config.new ; chmod 600 $FLASHCFG/config.new - archive_etc >$FLASHCFG/config.new + msg=$(rm -f $FLASHCFG/config.new 2>&1) + ret=$? + msg=$(echo -n $msg | tr '[\000- ]' ' ') + [ $ret = 0 ] || write_journal "1/rm" "|$msg| status: $ret" + msg=$(touch $FLASHCFG/config.new 2>&1) + ret=$? + msg=$(echo -n $msg | tr '[\000- ]' ' ') + [ $ret = 0 ] || write_journal "2/touch" "|$msg| status: $ret" + msg=$(chmod 600 $FLASHCFG/config.new 2>&1) + ret=$? + msg=$(echo -n $msg | tr '[\000- ]' ' ') + [ $ret = 0 ] || write_journal "3/chmod" "|$msg| status: $ret" + + # below, we get in msg either an error message from stderr in case of error, + # or the file status in case of success. + msg=$(archive_etc 2>&1 >$FLASHCFG/config.new) + ret="$?" + [ $ret = 0 ] && msg=$(find "$FLASHCFG/config.new" -printf "%f|%s|%T+" 2>&1) + msg=$(echo -n $msg | tr '[\000- ]' ' ') + write_journal "archive" "|$msg| status: $ret" elif [ $TOFILE -eq 1 ]; then archive_etc > "$OUTFILE" exit 0 @@ -249,16 +290,28 @@ fi if [ ! -s $FLASHCFG/config.new ]; then rm -f $FLASHCFG/config.new + write_journal "cancel" "4/rm empty config.new: status: $?" umount_flash [ $QUIET -eq 0 ] && echo "Problem saving config to flash." sync exit 1 fi -mv $FLASHCFG/config.{cur,bak} 2>/dev/null -cp $FLASHCFG/config.{new,adm} 2>/dev/null -if ! mv $FLASHCFG/config.{new,cur} 2>/dev/null; then +msg=$(mv $FLASHCFG/config.{cur,bak} 2>&1) +ret=$? +msg=$(echo -n $msg | tr '[\000- ]' ' ') +[ $ret = 0 ] || write_journal "5/mv" "config.cur->config.bak: |$msg| status: $ret" +msg=$(cp $FLASHCFG/config.{new,adm} 2>&1) +ret=$? +msg=$(echo -n $msg | tr '[\000- ]' ' ') +[ $ret = 0 ] || write_journal "6/cp" "config.new->config.adm: |$msg| status: $ret" +msg=$(mv $FLASHCFG/config.{new,cur} 2>&1) +ret=$? +if [ $ret -ne 0 ]; then + msg=$(echo -n $msg | tr '[\000- ]' ' ') + write_journal "7/mv" "config.new->config.cur: |$msg| status: $ret" rm -f $FLASHCFG/config.new + write_journal "8/rm" "rm config.new: status: $?" umount_flash [ $QUIET -eq 0 ] && echo "Problem saving config to flash." sync -- 1.7.12.1