#!/usr/bin/perl -w use strict; # $Id: mail-output,v 1.17 2006-12-05 08:01:56-05 roderick Exp $ # # Roderick Schertler # Copyright (C) 2001 Roderick Schertler # # 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. # # For a copy of the GNU General Public License write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA use sigtrap qw(die untrapped normal-signals); # process END in these cases use Proc::SyncExec qw(fork_retry); use Proc::WaitStat qw(waitstat waitstat_die); use RS::Handy qw($Me getopt have_prog safe_tmp rfc822_dt xdie); use String::ShellQuote qw(shell_quote); my @Sendmail = qw(sendmail /usr/sbin/sendmail /usr/lib/sendmail); my $Debug = 0; my $Empty = 0; my $Exit = 0; my $Failure_only = 0; my $Out_file = undef; my $Pid = $$; my @Recip = (); my $Sendmail = undef; my $Subject = undef; my $Version = q$Revision: 1.17 $ =~ /(\d\S+)/ ? $1 : '?'; my @Option_spec = ( 'debug!' => \$Debug, 'empty|e' => \$Empty, 'failure-only|f' => \$Failure_only, 'help!' => sub { usage() }, 'recip|r=s@' => \@Recip, 'sendmail=s' => \$Sendmail, 'subject|s=s' => \$Subject, 'version' => sub { print "$Me version $Version\n"; exit }, ); my $Usage = < set recipient (default you), can be used multiple times --sendmail use to send the mail -s, --subject set message subject --version show the version ($Version) and exit Use \`perldoc $Me\' to see the full documenation. EOF sub xwarn { RS::Handy::xwarn @_; $Exit ||= 1; } sub debug { print STDERR "debug: ", @_, "\n" if $Debug; } sub usage { xwarn @_ if @_; # Use exit() rather than die(), as Getopt::Long does eval(). print STDERR $Usage; exit 1; } sub init { getopt -bundle, @Option_spec or usage if @ARGV; push @Recip, $ENV{LOGNAME} || $ENV{USER} || getlogin || getpwuid($<) || xdie "can't figure out your login name\n" unless @Recip; debug "recip = [", join("] [", @Recip), "]"; if (defined $Sendmail) { have_prog $Sendmail or xdie "--sendmail program $Sendmail doesn't exist\n"; } else { $Sendmail = have_prog @Sendmail; if (!defined $Sendmail) { local $" = ', '; xdie "can't find sendmail program (tried @Sendmail),", " specify it with --sendmail\n"; } } debug "sendmail = $Sendmail"; $SIG{HUP} = 'IGNORE'; if (-t STDIN) { open STDIN, "/dev/null" or xdie "can't open /dev/null for stdin:"; } } sub main { init; @ARGV or usage; my @cmd = @ARGV; my $cmd_name = $cmd[0]; ($Out_file, my $out_fh) = safe_tmp; select $out_fh; $| = 1; select STDOUT; $| = 1; my $recip = join ", ", @Recip; $recip =~ s/\n(^[\040\t])/\n\t$1/g; $Subject = "$Me: " . shell_quote @cmd if !defined $Subject; $Subject =~ s/\n.*//s; print $out_fh "To: $recip\n", "Subject: $Subject\n", "X-Started-At: ", rfc822_dt, "\n", "\n" or xdie "error writing to $Out_file:"; my $body_pos = tell $out_fh; debug "body_pos = $body_pos"; my $do_send = undef; my $pid = fork_retry; if (!$pid) { my $fd = fileno $out_fh; open STDOUT, ">&=$fd" or xdie "child open of STDOUT:"; open STDERR, ">&STDOUT" or xdie "child open of STDERR:"; $^W = 0; # I'll check my own errors, thankyouverymuch. exec @cmd or xdie "can't exec $cmd_name:"; } if (waitpid($pid, 0) == -1) { xwarn "waitpid returned -1?! ($!)\n"; } elsif ($? != 0) { debug "sending enabled due to failure"; $do_send = 1; print $out_fh "$Me: non-zero exit (", waitstat($?), ") from $cmd_name\n" or xwarn "error writing to $Out_file:"; } seek $out_fh, 0, 2 or xdie "error seeking to EOF of $Out_file:"; my $final_pos = tell $out_fh; debug "final_pos = $final_pos"; if ($final_pos == $body_pos && $Empty) { debug "no output"; print $out_fh "$Me: no output generated by $cmd_name\n" or xwarn "error writing to $Out_file:"; $final_pos = tell $out_fh; } if ($final_pos != $body_pos && !$Failure_only) { debug "sending enabled due to non-empty body"; $do_send = 1; } if ($Debug) { debug "message:"; seek $out_fh, 0, 0 or xwarn "error seeking in $Out_file:"; print while <$out_fh>; } if (!$do_send) { debug "not sending message"; } else { debug "sending message"; # This has to be a sysseek() rather than a seek() since it needs # to be done at the kernel level before I fork()/exec() the # $Sendmail program. sysseek $out_fh, 0, 0 or xdie "error seeking in $Out_file:"; open STDIN, "<&" . fileno $out_fh or xdie "can't dup $Out_file to stdin:"; system $Sendmail, @Recip; waitstat_die $?, $Sendmail; } return 0; } END { if ($$ == $Pid && defined $Out_file && !unlink $Out_file) { xwarn "error unlinking $Out_file:"; $? ||= 1; } } $Exit = eval { main } || $Exit; die $@ if $@; exit $Exit; __END__ =head1 NAME mail-output - run a command and mail back the output =head1 SYNOPSIS B [B<--debug>] [B<--empty>|B<-e>] [B<--failure-only>|B<-f>] [B<--help>] [B<--recip>|B<-r> I]... [B<--sendmail> I] [B<--subject>|B<-s> I] [B<--version>] B [I]... =head1 DESCRIPTION I runs the given I and mails you any output it generates. (If there isn't any output no mail is sent.) If the command exits with a non-zero status mail is always generated. I ignores hangup (C) signals, so the program it runs should do the same. If stdin is a terminal, it's re-opened on F instead. I used to hold the output in an unlinked temporary file, but I switched to using a named one so you can look at the it before the command finishes. The file's location and name can vary, but usually it'll be called something like F in C<$TMPDIR> or F. =head1 OPTIONS =over 4 =item B<--debug> Turn debugging on. =item B<--empty> =item B<-e> Send mail even if the command doesn't output anything and returns 0. =item B<--failure-only> =item B<-f> Don't send the message unless the command exits with a non-zero status. =item B<--help> Show the usage message and die. =item B<--recip> I
=item B<-r> I
Send the mail to I
. Multiple instances of this switch can be used to send the mail to more than one person, but only put one I
for each instance of B<--recip>. If you don't use this switch the mail will be sent back to you. If you do use this switch then the mail will only go to the I
s you specify. =item B<--sendmail> I Use I to send the mail. This program should take the message on standard input and the recipients as arguments. If you don't give this switch B searches for F on your $PATH and in the usual locations. =item B<--subject> I =item B<-s> I Set the subject used for the mail. The default subject includes the name of the command run. =item B<--version> Show the version number and exit. =head1 AUTHOR Roderick Schertler =cut