X-Git-Url: https://arthur.barton.de/gitweb/?a=blobdiff_plain;f=mail%2Fwrapper%2Fmail-wrapper;h=fcda76e2ffa89a37664984e4deda79e7e5994fe8;hb=HEAD;hp=7c5a19b98c2b5a358e1c817763f5a0402ad6f4ac;hpb=474ec38cc7084249a3bb70b7a0dac004e9d205b9;p=ax-unix.git diff --git a/mail/wrapper/mail-wrapper b/mail/wrapper/mail-wrapper index 7c5a19b..fcda76e 100755 --- a/mail/wrapper/mail-wrapper +++ b/mail/wrapper/mail-wrapper @@ -1,7 +1,7 @@ #!/usr/bin/env bash # # mail-wrapper -- Report results of a command by email -# Copyright (c)2017 Alexander Barton (alex@barton.de) +# Copyright (c)2017,2018,2023 Alexander Barton (alex@barton.de) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,7 +20,7 @@ for dir in "$HOME/lib" "$HOME/.ax" /usr/local /opt/ax /usr; do [ -r "$ax_common" ] && . "$ax_common" done if [ -z "$ax_common_sourced" ]; then - echo "Error ($(basename "$0")): \"ax-common.sh\" not found, aborting!" + echo "Error ($NAME): \"ax-common.sh\" not found, aborting!" >&2 echo "Please install 'ax-unix', \"Alex' UNIX Tools & Scripts\", and try again." exit 99 fi @@ -34,12 +34,16 @@ usage() { echo " $NAME {parameters} [ [ [<…>]]]" echo echo " -C Use the \"C\" locale, no localized (error) messages." + echo " --dontfail|-n Don't return the error code of the command called." echo " --errors|-e Generate email on errors only." echo " --from|-f Email address of the sender of the email." + echo " --stderr-is-warning|-W Exit code indicates error; stderr is only warning." echo " --subject|-s Subject for the email." + echo " --time|-t Include timing information." echo " --to|-t
Email address to send the email to." echo - echo "When no is given, $NAME reads from standard input." + echo "When no is given, $NAME reads from standard input. The default for" + echo " and for the current user is \"$to\"." echo } >&2 exit "${1:-0}" @@ -50,13 +54,69 @@ syntax_error() { usage 2 } +clean_up() { + if [[ -z "$proc_fd_works" ]]; then + ax_debug "Cleaning temporary files ..." + [[ -n "$buffer_file" ]] && rm -f "$buffer_file" + [[ -n "$error_file" ]] && rm -f "$error_file" + fi +} + +# Convert UNIX time stamp to date text. +time_t_to_date() { + t="$1" + shift + if ! date -d @"$t" "$@" 2>/dev/null; then + # "date -d @" (GNU variant) failed! + date -r "$t" "$@" + fi +} + +time_t_to_duration() { + s="$1" + if [[ "$s" -ge 60 ]]; then + m=$((s / 60)) + s=$((s % 60)) + if [[ "$m" -ge 60 ]]; then + h=$((m / 60)) + m=$((m % 60)) + if [[ "$h" -ge 24 ]]; then + d=$((h / 24)) + h=$((h % 24)) + echo "${d}d${2}${h}h${2}${m}m${2}${s}s" + else + echo "${h}h${2}${m}m${2}${s}s" + fi + else + echo "${m}m${2}${s}s" + fi + else + echo "${s}s" + fi +} + +case "$(uname)" in + "Darwin") + unset proc_fd_works + ;; + *) + proc_fd_works=1 +esac + # Initialize internal state. -unset is_error +buffer_file="" +error_file="" +error_level=0 host=$(hostname -f 2>/dev/null || hostname) +trap clean_up EXIT + # Some defaults (can be adjusted by command line parameters). unset do_errors_only +unset dont_fail +unset stderr_is_warning unset subject +unset time from="${LOGNAME:-root} <${LOGNAME:-root}@$host>" to="$from" @@ -70,6 +130,9 @@ while [[ $# -gt 0 ]]; do "--debug"|"-D") export DEBUG=1 ;; + "--dontfail"|"-n") + dont_fail=1 + ;; "--errors"|"-e") do_errors_only=1 ;; @@ -86,6 +149,16 @@ while [[ $# -gt 0 ]]; do [[ $# -gt 0 ]] || syntax_error subject="$1" ;; + "--stderr-is-warning"|"-W") + stderr_is_warning=1 + ;; + "--time"|"-t") + time=1 + ;; + "--suppress-empty") + # Ignore this switch for compatibility with an other + # "mail-wrapper" script. This is the default anyway! + ;; "--to"|"-t") shift [[ $# -gt 0 ]] || syntax_error @@ -103,7 +176,7 @@ while [[ $# -gt 0 ]]; do done # Initialize the "buffer file" on file handle #3. This file will store all -# output, stdout and stderr combined. The file is immediately unliked so that +# output, stdout and stderr combined. The file is immediately unlinked so that # we can't leak stale files. Afterwards this script accesses the "buffer file" # by its file descriptor only. buffer_file=$(mktemp) \ @@ -111,9 +184,11 @@ buffer_file=$(mktemp) \ ax_debug "buffer_file=\"$buffer_file\"" exec 3>"$buffer_file" \ || ax_abort -l "Failed to redirect FD #3 to buffer file!" -rm "$buffer_file" \ - || ax_error -l "Failed to delete buffer file: \"$buffer_file\"!" -buffer_file="/dev/fd/3" +if [[ -n "$proc_fd_works" ]]; then + rm "$buffer_file" \ + || ax_error -l "Failed to delete buffer file: \"$buffer_file\"!" + buffer_file="/dev/fd/3" +fi if [[ $# -gt 0 ]]; then # Execute command and save output in buffer file. @@ -123,42 +198,62 @@ if [[ $# -gt 0 ]]; then ax_debug "error_file=\"$error_file\"" exec 4>"$error_file" \ || ax_abort -l "Failed to redirect FD #4 to error file!" - rm "$error_file" \ - || ax_error -l "Failed to delete error buffer file: \"$error_file\"!" - error_file="/dev/fd/4" + if [[ -n "$proc_fd_works" ]]; then + rm "$error_file" \ + || ax_error -l "Failed to delete error buffer file: \"$error_file\"!" + error_file="/dev/fd/4" + fi job=$(basename "$1") ax_debug "Running command \"$*\" ..." + start_t=$EPOCHSECONDS exit_code=$( "$@" 2>&1 1>&3 | tee "$error_file" >&3 echo "${PIPESTATUS[0]}" ) + end_t=$EPOCHSECONDS else # Read from stdin and save it to the buffer file. error_file="/dev/null" job="Job" ax_debug "Reading from stdin ..." + start_t=$EPOCHSECONDS while read -r line; do echo "$line" >&3 \ || ax_abort -l "Failed to write to buffer file!" done + end_t=$EPOCHSECONDS exit_code=0 fi ax_debug "exit_code=$exit_code" +declare -i count_all count_err count_all=$(wc -l <"$buffer_file" || ax_abort -l "Failed to count buffer file!") count_err=$(wc -l <"$error_file" || ax_abort -l "Failed to count error file!") # Error or no error -- that's the question! An error is assumed when either the # exit code of the command was non-zero or there was output to stderr. -[[ $count_err -gt 0 || $exit_code -ne 0 ]] && is_error=1 +# But when stderr_is_warning is set, messages on stderr result on a warning only! +if [[ $exit_code -ne 0 ]]; then + error_level=2 +elif [[ $count_err -gt 0 ]]; then + [[ -n $stderr_is_warning ]] && error_level=1 || error_level=2 +else + error_level=0 +fi # Construct email subject ... [[ -z "$subject" ]] && subject="$host: $job report" -[[ -n "$is_error" ]] && subject="$subject - ERROR!" || subject="$subject - success" +if [[ "$error_level" -eq 0 ]]; then + subject="$subject - success" +elif [[ "$error_level" -eq 1 ]]; then + subject="$subject - WARNING!" +else + subject="$subject - ERROR!" +fi ax_debug "from=\"$from\"" ax_debug "to=\"$to\"" @@ -174,19 +269,19 @@ fi ax_debug "count_all=$count_all" ax_debug "count_err=$count_err" -ax_debug "is_error=$is_error" +ax_debug "error_level=$error_level" # No errors detected (exit code & stderr), and email should be sent on errors # only: so exit early! -[[ -z "$is_error" && -n "$do_errors_only" ]] && exit $exit_code +[[ "$error_level" -lt 2 && -n "$do_errors_only" ]] && exit $exit_code # No error detected and no output at all: skip email, exit early: -[[ -z "$is_error" && $count_all -eq 0 ]] && exit $exit_code +[[ "$error_level" -eq 0 && $count_all -eq 0 ]] && exit $exit_code # Build the report mail. # Make sure to ignore all mail(1) configuration files, system wide /etc/mailrc # (by using the "-n" option) as well as ~/.mailrc (by setting the MAILRC -# environment varialbe). +# environment variable). export MAILRC=/dev/null ( echo "$job report:" @@ -194,6 +289,7 @@ export MAILRC=/dev/null echo " - Host: $host" echo " - User: $(id -un)" echo " - Exit code: $exit_code" + [[ -n "$time" ]] && printf " - Duration: %s\n" "$(time_t_to_duration $((end_t - start_t)) ' ')" echo if [[ $# -gt 0 ]]; then # A command name is known (not stdin), show it! @@ -201,6 +297,7 @@ export MAILRC=/dev/null echo "$@" echo fi + [[ -n "$time" ]] && printf "%s - %s:\n\n" "$(time_t_to_date "$start_t")" "$(time_t_to_date "$end_t")" if [[ $count_err -gt 0 ]]; then # Prefix mail with all error messages. echo "Error summary:" @@ -218,4 +315,4 @@ export MAILRC=/dev/null ) | mail -n -a "From: $from" -s "$subject" "$to" \ || ax_abort -l "Failed to send email to \"$to\"!" -exit $exit_code +[[ -n "$dont_fail" ]] && exit 0 || exit $exit_code