#!/bin/ksh # version 3.2, 2005-04-18, Michael Wang . # * Add PPID check "kill 0 => $ppid" to avoid Linux/Perl bug. # # version 3.1, 2002-05-12, Michael Wang . # * minor change in documentation. # # version 3.0, 2002-01-13, Michael Wang . # * added perl search order. # # * added "help=y" option. # * use old PATH for external command. # * eliminated "use FileHandle;". # * general code clean up. # # version 2.2, 2000-05-27, Michael Wang . # * fixed a Y2K bug with "$year += 1900;" # * fixed a problem with appending to named pipe in: # + open FH, ">> ./np" # can not open: Illegal seek. # # version 2.1, 1998-10-xx, Michael Wang . # * added -i -e and -l options. # * fixed minor problems with signal handling and exit code. # # version 2.0, 1998-03-xx, Michael Wang . # * rewritten in Perl 5. # * fixed bugs (exit code etc). # * added features (self cleaning log, process group). # # version 1.0, 1993-04-23, Michael Sullivan. OPATH=$PATH PATH=$(PATH=/usr/bin:/bin getconf PATH) typeset -x OPATH PATH i= unset i typeset -l i=$1 [[ $# = 0 || $# = 1 && $i = help=y ]] && { PATH=$OPATH whence perldoc >/dev/null || { print "You need to have perldoc in your PATH." exit 1 } j= unset j j=/tmp/${0##*/}_man.$$ trap "rm -f $j" EXIT INT sed -n "/^##/s:^## \{0,1\}::p" $0 > $j PATH=$OPATH perldoc $j exit 0 } i= unset i for i in /usr/bin/perl \ /usr/local/bin/perl \ /opt/perl/bin/perl \ /apps/perl/bin/perl do [[ -x $i ]] && exec $i -wx $0 "$@" done print "No Perl found!" exit 1 #!perl use File::stat qw(:FIELDS); use strict; use Getopt::Std; use vars qw($opt_i $opt_e $opt_l); getopt('lie'); my $unix_command="@ARGV"; $SIG{'INT'}='cleanup'; $SIG{'QUIT'}='cleanup'; $SIG{'TERM'}='cleanup'; my $OPATH=$ENV{'OPATH'}; my $PATH=$ENV{'PATH'}; my ($LOGDIR, $LOGFILE); my ($log_user, @passwd_ent, $sub_user, $sub_user_home); my $max_log_size=1000000; my $min_log_size=100000; my ($num_read, $buf); my $child_pid; # if mwang switched to root with su -, $log_user = mwang, $sub_user = root. # getlogin get the user from wtmp, and getpwuid gives the real user. @passwd_ent = getpwuid ($<); $sub_user = $passwd_ent[0]; $log_user = getlogin || $sub_user; $sub_user_home = $passwd_ent[7]; if (defined($opt_l)) { $LOGFILE="$opt_l"; } else { if ( $sub_user eq "root" ) { $LOGDIR="/opt/local/log"; ( -d $LOGDIR ) || system("PATH=$PATH mkdir -p $LOGDIR"); } else { $LOGDIR=$sub_user_home; } $LOGFILE="$LOGDIR/submit.log"; } if ( -f $LOGFILE ) { stat($LOGFILE) or die "can not stat $LOGFILE: $!"; if ($st_size > $max_log_size) { if ( open(FH, "< $LOGFILE") ) { seek(FH, -$min_log_size, 2); $num_read = read(FH, $buf, $min_log_size); close(FH); } if ( $num_read == $min_log_size ) { if ( open(FH, "> $LOGFILE") ) { print(FH $buf); close(FH); } } } } if (-p $LOGFILE ) { open(FH, "> $LOGFILE") or die "I can not open $LOGFILE: $!\n"; } else { open(FH, ">> $LOGFILE") or die "I can not open $LOGFILE: $!\n"; } my $old_fh = select(); my $ppid; select(FH); $|=1; select(STDOUT); $|=1; select(STDERR); $|=1; select($old_fh); FORK: { if ( $child_pid = fork() ) { print("$sub_user submitting $unix_command, pid=$child_pid\n"); print("check results in $LOGFILE\n"); if ( defined($opt_i) ) { print("pid saved in $opt_i\n"); } if ( defined($opt_e) ) { print("exit code saved in $opt_e\n"); } open(STDIN, "< /dev/null") or die "can not open STDIN: $!"; open(STDOUT, ">& FH") or die "can not open STDOUT: $!"; open(STDERR, ">& FH") or die "can not open STDERR: $!"; writelog(""); writelog("spawning child process: $child_pid"); close(STDIN); close(STDOUT); close(STDERR); close(FH); if ( defined($opt_i) ) { open(PIDFILE, "> $opt_i") or die "can not open $opt_i: $!"; print(PIDFILE "$child_pid\n"); close(PIDFILE); } exit 0; } elsif ( defined($child_pid) ) { until ( ($ppid = getppid()) == 1 || !(kill 0 => $ppid) ) { sleep 1; } close(STDIN); close(STDOUT); close(STDERR); open(STDIN, "< /dev/null") or die "can not open STDIN: $!"; open(STDOUT, ">& FH") or die "can not open STDOUT: $!"; open(STDERR, ">& FH") or die "can not open STDERR: $!"; } elsif ( $! =~ /No more process/ ) { sleep 5; redo FORK; } else { die "can't fork: $!\n"; } } writelog("$sub_user ($log_user) submitting: $unix_command"); ( defined($opt_i) || defined($opt_e) ) && writelog("with options:"); defined($opt_i) && writelog("-i $opt_i"); defined($opt_e) && writelog("-e $opt_e"); # to make the ps the process group leader, which can be killed # with kill -- -, suggested by reimin@issl.atl.hp.com. setpgrp(); system("PATH=$OPATH $unix_command"); exit_status($?); sub writelog { use Sys::Hostname; my $line=shift; my $host_name=hostname(); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time); $mon++; $year += 1900; printf("%4.4d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d ($host_name:%d)", $year,$mon,$mday,$hour,$min,$sec,$$); print("$line\n"); } sub cleanup { my $sig = shift; writelog("Got SIG$sig, wait for submitted job to terminate."); # the signal is sent to process group, parent ignore the signal # and wait for children to exit. } sub exit_status { my $stat = shift; my $WIFEXITED = ( ($stat&0xFF) == 0 ); my $WEXITSTATUS = ($stat>>8)&0xFF; my $WIFSIGNALED = ( ($stat&0xFF) > 0 && ($stat&0xFF00) == 0 ); my $WTERMSIG = $stat&0x7F; my $WCOREDUMP = $stat&0x80; my $WIFSTOPPED = ( ($stat&0xFF) == 0x7F && ($stat&0xFF00) != 0 ); my $WSTOPSIG = ($stat>>8)&0xFF; my $WIFCONTINUED = ( ($stat&0xFFFF) == 0xFFFF ); if ($WIFEXITED) { if ($WEXITSTATUS) { writelog("run with non-zero exit: $WEXITSTATUS"); } else { writelog("run with normal exit: $WEXITSTATUS"); } if (defined($opt_e)) { open(EXITFILE, "> $opt_e") or die "can not open $opt_e: $!"; print(EXITFILE "$WEXITSTATUS\n"); close(EXITFILE); } } elsif ($WIFSIGNALED) { writelog("terminated abnormally, signal: $WTERMSIG"); if ($WCOREDUMP) { writelog("and coredumped"); } if (defined($opt_e)) { open(EXITFILE, "> $opt_e") or die "can not open $opt_e: $!"; print(EXITFILE "128+$WTERMSIG\n"); close(EXITFILE); } # From Andrew Gierth with editing: # "you can not distinguish between a process that does exit(130) # and one that is killed by signal 2. But this is what you # expect of $? in shell. This is the standard." } elsif ($WIFSTOPPED) { # can not happen, system() has no job control writelog("process stopped, signal: $WSTOPSIG"); } elsif ($WIFCONTINUED) { # can not happen, system() has no job control writelog("process continued"); } } __END__ ## =head1 NAME ## ## submit - submit a unix command ## ## =head1 SYNOPSIS ## ## B ## B<-l> I ## B<-i> I ## B<-e> I ## I ## ## B help=I ## ## kill -- -I ## ## =head1 DESCRIPTION ## ## B allows you to issue a command to the operating system, let it ## run independently, and immediately return the control to the calling ## process. ## ## The calling process is able to check the existence of the process, and ## exiting status, and the log via files specified. ## ## B can be used in following situations: ## ## =over 4 ## ## =item ## ## When you want to issue a long running process and exit from the terminal ## voluntarily, or you fear that the connection to the server you want to ## run the process is not reliable. ## ## =item ## ## When you want to run multiple processes in parallel, and need to check ## the status of the processes (see mrun program). ## ## =back ## ## Submit is similar to nohup(1) and putting a process ## in background (nohup unix_command &). But submit makes it possible, or ## easier to check the existence and status of the process for I, ## especially for programming purpose. ## ## The mechanisms used by submit and nohup are completely different. ## ## Nohup blocks the SIGHUP to the process running I. We need to ## put the process in background in order to return to the calling process. ## ## Submit uses double fock() technique, Submit spawns a child process and ## then immediately returns to the calling program. The child process is then ## adopted by init and thus runs indepedent from the calling program. ## The child spawns another child process (grand child) that runs the the ## I. The child process waits for I to complete ## and reaping the status. The child process is promoted to be the process ## group leader, so we can kill the processes as a group. ## ## =head1 OPTIONS ## ## =over 4 ## ## =item B<-l> I ## ## The stdout and stderr of the I is saved in a self-cleaning ## log file specified by this option. The default is I/submit.log ## for normal users, and /opt/local/log/submit.log for root user. The log file ## is kept for a maximum number of lines. ## ## The process ID and exit code are also saved in I, but they ## can be save seperately for easier processing programatically. ## ## =item B<-i> I ## ## The submit's child process ID is used this file. ## ## =item B<-e> I ## ## The exit code of process for I. ## ## =item I ## ## Specify a Unix command. Shell special characters need to escaped so ## they can be seen by Perl's system() call. ## ## =back ## ## =head1 EXAMPLES ## ## $ submit "make > make.log" ## $ submit make \> make.log ## ## $ submit 'make CC="gcc" CFLAGS="-g -O2"' ## $ submit make CC=\"gcc\" CFLAGS=\"-g -O2\" ## ## $ submit make\; make install ## ## $ rsh machine "submit command" ## ## $ submit sleep 120 ## $ mwang submitting sleep 120, pid=4939 ## $ ptree 4939 ## 4939 /usr/bin/perl -w /usr/local/bin/submit sleep 120 ## 4948 sleep 120 ## $ kill -- -4939 ## ## =head1 SEE ALSO ## ## mrun help=y, bk.oracle. ## ## Advanced Programming in the Unix Environment, by W. Richard Stevens ## (Addison-Wesley, 1992). ## ## =head1 AUTHOR ## ## Michael Wang , 1998-2002, with help from others, ## acknowledged in code comments. ## ## Michael Sullivan, 1993. ## ## This is free software. You may copy or redistribute it under the same ## terms as Perl. However, if you modify it, you need to send the ## modification to the current author via email.