From: Mark Wooding Date: Wed, 14 Dec 2011 01:47:10 +0000 (+0000) Subject: hush: New handy program. X-Git-Tag: 1.2.6~1 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/misc/commitdiff_plain/c818aceddb8a734c7b3fe0e35f442bbd70e0b0ab hush: New handy program. --- diff --git a/Makefile.am b/Makefile.am index 66f70a1..64eeabf 100644 --- a/Makefile.am +++ b/Makefile.am @@ -155,7 +155,8 @@ SUBSTITUTIONS = \ PYTHON=$(PYTHON) \ PERL=$(PERL) \ TCLSH=$(TCLSH) \ - BASH=$(BASH) + BASH=$(BASH) \ + logdir=$(logdir) EXTRA_DIST += config/confsubst @@ -172,6 +173,19 @@ dist_man_MANS += create.1 dist_bin_SCRIPTS += z dist_man_MANS += z.1 +bin_SCRIPTS += hush +man_MANS += hush.1 +CLEANFILES += hush hush.1 +EXTRA_DIST += hush.in hush.1.in + +hush: hush.in Makefile + $(SUBST) $(srcdir)/hush.in >$@.new $(SUBSTITUTIONS) && \ + chmod +x $@.new && mv $@.new $@ + +hush.1: hush.1.in Makefile + $(SUBST) $(srcdir)/hush.1.in >$@.new $(SUBSTITUTIONS) && \ + mv $@.new $@ + ## bash scripts. if HAVE_BASH diff --git a/configure.ac b/configure.ac index 5726489..b171850 100644 --- a/configure.ac +++ b/configure.ac @@ -31,6 +31,16 @@ AC_CONFIG_AUX_DIR([config]) AM_INIT_AUTOMAKE([foreign]) mdw_SILENT_RULES +AC_ARG_WITH([logdir], + AS_HELP_STRING([--with-logdir=DIR], + [Write log files here.]), + [logdir=$withval], + [logdir=/var/log + for i in /var/log /var/adm; do + if test -d $i; then logdir=$i; break; fi + done]) +AC_SUBST(logdir) + AC_CANONICAL_HOST dnl-------------------------------------------------------------------------- diff --git a/debian/control b/debian/control index c5d505e..09a461e 100644 --- a/debian/control +++ b/debian/control @@ -28,7 +28,8 @@ Depends: inplace, stamp, space, - getpass + getpass, + hush Description: Dummy package for convenience. Package: mdwopt-perl @@ -155,3 +156,12 @@ Architecture: any-i386 any-amd64 Section: utils Description: Shows basic model information about x86 processors. The cpuid program is probably better for most people. + +Package: hush +Architecture: all +Section: utils +Description: Run a command, hiding its output in a logfile unless it fails + The hush program is useful for running noisy programs from cron or similar, + where you get spammed with uninteresting success reports. hush runs a + command, logging its output, but, unless the command actually fails, it + produces no output of its own. diff --git a/debian/inst b/debian/inst index 1f01de4..25007bf 100644 --- a/debian/inst +++ b/debian/inst @@ -50,3 +50,5 @@ z zz /usr/bin z.1 zz /usr/share/man/man1 x86-model x86-model /usr/bin x86-model.1 x86-model /usr/share/man/man1 +hush hush /usr/bin +hush.1 hush /usr/share/man/man1 diff --git a/hush.1.in b/hush.1.in new file mode 100644 index 0000000..e5c2bc7 --- /dev/null +++ b/hush.1.in @@ -0,0 +1,169 @@ +.TH hush 1 "14 December 2011" "Edgeware tools" +.SH NAME +hush \- run a program, quietly unless it fails +.SH SYNOPSIS +.B hush +.RB [ \-d +.IR directory ] +.RB [ \-m +.IR email-address ] +.RB [ \-n +.IR maxlog ] +.RB [ \-u +.IR owner ][\fB: group ] +.br + \c +.I tag +.I command +.IR arguments ... +.SH DESCRIPTION +The +.B hush +program runs a command. The command's output (i.e., what it writes to +its standard output and standard error file descriptors) is always +logged to a file. If the command succeeds, +.B hush +itself outputs nothing; if it fails, then +.B hush +either writes the command's output to its own stdout, or sends it via +email. It is intended to be used when running noisy programs via +.BR cron (8), +to reduce the amount of uninteresting mail (`cronspam') produced by an +essentially working system. +.PP +The following command-line options are recognized. +.TP +.B \-h +Write a help message describing +.BR hush 's +command line options and usage to standard output, and exit. +.TP +.B \-v +Write +.BR hush 's +version number to standard output, and exit. +.TP +.BI "\-d " directory +Write log files to +.I directory +rather than the default, +.BR "@logdir@" . +.TP +.BI "\-m " email-address +Rather than writing its output to stdout if the command fails, send the +command's output to +.IR email-address . +and exit with status 0. (This is perhaps a surprising choice, but it +prevents the caller from taking additional action to report a problem +which has already been escalated to a human.) +.TP +.BI "\-n " maxlog +If necessary, delete old logfiles so that no more than +.I maxlog +log files are left. +.TP +.BI "\-p " mode +Set the permissions on the logfile to +.I mode , +a mode specification acceptable to +.BR chmod (1), though relative permissions will be applied to mode +.B 600 +(i.e., +.BR u=rw,og= ). +.TP +.BI "\-u \fR[" user\fR][ : group\fR] +Set the ownership and/or group of the logfile. If the +.I user +is specified, then the file's owner is set; if the +.I group +is specified, the file's group is set. (Some care is taken to ensure +that the file is never available to members of the wrong group.) +.SS Operation +The given +.I command +is executed with the +given +.IR arguments , +with stdin redirected from +.BR /dev/null , +and stdout and stderr redirected to separate pipes. If it is available, +.BR stdbuf (1) +is used to ensure that the +.IR command 's +stdout is line-buffered. +.PP +The +.IR command 's +output is collected in a log file named +.IB logdir / tag . date # seq +where +.TP +.I logdir +is the argument of the +.B \-d +option, or +.B @logdir@ +by default; +.TP +.I tag +is the +.I tag +string given to +.B hush +as a command-line argument; +.TP +.I date +is the current date, in ISO8601 form (in local time); and +.TP +.I seq +is a sequence number, chosen to ensure that log file names are distinct +and sort in chronological order. +.PP +The log file begins with a header giving the exact start time (in local +time, with an offset from UTC) and a brief summary of the log format; it +ends with another timestamp and the +.IR command 's +exit status. In between is the command's output. Lines written to +stdout begin with +.RB ` | '; +lines to stderr begin with +.RB ` * '. +The two are interleaved in an attempt to help the reader identify how +much progress the +.I command +had made when it encountered an error; however, because the streams are +read asynchronously, this isn't perfect, and lines may appear earlier or +later than they ought to. +.PP +If the +.I command +succeeds, as mentioned, +.B hush +exits without printing anything. If it fails, and the +.B \-m +option was given, the log file is mailed to the appropriate +.I email-address +with a subject line +.IP +.IB tag : +.I command +.B failed (status = +.IB rc ) +.PP +where +.I rc +is the +.IR command 's +exit status. If no +.B \-m +option was given, this log is simply written to standard output. +.SH BUGS +The stream interleaving isn't quite right, but it's hard to see how to +improve it. +.PP +Capturing the command's output involves making a fairly large number of +auxiliary processes and file descriptors. This is a bit ugly. +.SH AUTHOR +Mark Wooding, +.SH SEE ALSO +.BR cron (8). diff --git a/hush.in b/hush.in new file mode 100755 index 0000000..0ecc48e --- /dev/null +++ b/hush.in @@ -0,0 +1,208 @@ +#! /bin/sh +### +### Run a program, but stash its output unless it fails +### +### (c) 2011 Mark Wooding +### + +###----- Licensing notice --------------------------------------------------- +### +### 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 +### the Free Software Foundation; either version 2 of the License, or +### (at your option) any later version. +### +### This program is distributed in the hope that it will be useful, +### but WITHOUT ANY WARRANTY; without even the implied warranty of +### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +### GNU General Public License for more details. +### +### You should have received a copy of the GNU General Public License +### along with this program; if not, write to the Free Software +### Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +set -e + +quis=${0##*/} +usage="usage: $quis [-d DIR] [-m EMAIL] [-n NLOG] TAG COMMAND [ARGS ...]" +ver="@VERSION@" +version () { echo "$quis, @PACKAGE@ version $ver"; } + +###-------------------------------------------------------------------------- +### Parse the command line. + +## Initialize variables for storing command-line option values. +logdir="@logdir@" +maxlog=16 +unset mail +unset owner +unset mode + +## Scan the options. +while getopts "hvd:m:n:p:u:" opt; do + case "$opt" in + h) + version + cat <&2 "$usage"; exit 1 ;; + esac +done +shift $(( OPTIND - 1 )) + +## Check the arguments. +case $# in 0 | 1) echo >&2 "$usage"; exit 1 ;; esac +tag=$1 cmd=$2; shift 2 + +###-------------------------------------------------------------------------- +### Check out the environment. + +## Force a command to line-buffer its output. How does one do this on BSD, +## for example? +if stdbuf --version >/dev/null 2>&1; then + lbuf="stdbuf -oL --" +else + lbuf="" +fi + +###-------------------------------------------------------------------------- +### Set up the log file. + +## Find a name for the log file. In unusual circumstances, we may have +## deleted old logs from today, so just checking for an unused sequence +## number is insufficient. Instead, check all of the logfiles for today, and +## use a sequence number that's larger than any of them. +date=$(date +%Y-%m-%d) seq=1 +for i in "$logdir/$tag.$date#"*; do + tail=${i##*#} + case "$tail" in [!1-9]* | *[!0-9]*) continue ;; esac + if [ -f "$i" -a $tail -ge $seq ]; then seq=$(( tail + 1 )); fi +done +log="$logdir/$tag.$date#$seq" + +## Create the file. Make sure we create it with restrictive permissions +## and then slacken them off if necessary. This means that we don't (for +## example) end up giving the wrong group write permission to the file for a +## little bit. +umask=$(umask) +case ${mode+t} in t) ;; *) mode=$(printf %o $(( 0666 & ~umask ))) ;; esac +umask 077; exec 3>"$log"; umask $umask +case ${owner+t} in t) chown "$owner" "$log" ;; esac +chmod $mode "$log" + +###-------------------------------------------------------------------------- +### Run the program. + +## Write a log header. +cat >&3 <&5; } | + while read line; do echo "| $line"; done >&4; } 2>&1 | + while read line; do echo "* $line"; done >&4; } 4>&1 | + cat >&3; } 5>&1 &3 <&- + +###-------------------------------------------------------------------------- +### Delete old log files if there are too many. + +## Count up the logfiles. +nlog=0 +for i in "$logdir/$tag".*; do + if [ ! -f "$i" ]; then continue; fi + nlog=$(( nlog + 1 )) +done + +## If there are too many, go through and delete some early ones. +if [ $nlog -gt $maxlog ]; then + n=$(( nlog - maxlog )) + for i in "$logdir/$tag".*; do + if [ ! -f "$i" ]; then continue; fi + rm -f "$i" + n=$(( n - 1 )) + if [ $n -eq 0 ]; then break; fi + done +fi + +###-------------------------------------------------------------------------- +### Do something useful with the result. + +case $rc,${mail+t} in + 0,*) + ## Everything worked. Leave the results in the log file in case someone + ## cares. + ;; + *,t) + ## Failed, and we have an email address. Send mail and appear to + ## succeed: we've done our job and reported the situation. The idea is + ## to prevent something else (e.g., cron) from producing another report + ## for the same problem, but without the useful content. + mail -s "$tag: $cmd failed (status = $rc)" "$mail" <"$log" + rc=0 + ;; + *) + ## Failed, and no email address. Write the accumulated stuff. + cat "$log" + ;; +esac + +## Exit with an appropriate status. +exit $rc + +###----- That's all, folks --------------------------------------------------