[alt.sources] timeout command

tchrist@convex.com (Tom Christiansen) (10/02/89)

It is occasionally useful to set a timeout on a command that
would otherwise hang or run forever.  Instead of building a timeout
into each command, the UNIX philosophy would be to build a tool
to do the timeout for you, so here it is.  It's pretty easy
to write, and has surely been done many times before, but I didn't
have one handy when I needed it, so here it is.


#include <stdio.h>
#include <signal.h>
#include <sysexits.h>
#include <sys/wait.h>

int pid,count;
union wait status;
int bang();
char **commands;

main(ac,av) 
    char **av;
{
    if (ac < 3) {
usage:  fprintf (stderr, "usage: %s seconds command\n",*av);
	exit (EX_USAGE);
    } 
    if ((count=atoi(av[1])) < 1) {
	fprintf (stderr, "seconds (%s) malformed or nonpositive\n",av[1]);
	goto usage;
    } 

    commands = &av[2];
    switch (pid=fork()) {
	default: parent(); 
		 break;
	case 0: child();  
		 /* NOTREACHED */
	case -1: perror("fork"); 
		 exit(EX_OSERR); 
		 /* NOTREACHED */
    } 
    printf("exit status was %d\n",status.w_retcode);
    printf("termsig was %d\n",status.w_termsig);
} 

parent() {
    (void) signal(SIGALRM,bang);
    alarm(count);
    while(wait(&status) != pid) 
	  /* VOID */; 
    if (WIFSIGNALED(status)) 
	exit(-status.w_termsig);
    exit(status.w_retcode);

} 


bang() {
    fprintf(stderr,"Timeout!\n");
    (void) signal(SIGALRM,SIG_DFL);
    (void) kill(pid,SIGTERM);
    if (!kill(pid,0)) {
	sleep(3);
	(void) kill(pid,SIGKILL);
    }
    exit(EX_TEMPFAIL);
} 

child() {
    execvp(*commands,commands);
    perror(*commands);
    _exit(EX_DATAERR);
    /* NOTREACHED */
} 

/* lint output:
 *	timeout.c:
 */

    Tom Christiansen                       {uunet,uiucdcs,sun}!convex!tchrist 
    Convex Computer Corporation                            tchrist@convex.COM
		 "EMACS belongs in <sys/errno.h>: Editor too big!"

mfuller@prandtl.nas.nasa.gov (Mike J. Fuller) (10/03/89)

I had to write such a program not too long ago, so here is my two
cents worth.  I'm not posting this because I think that it's any
better, I just figure that if we see it done a bunch of different ways
maybe we can arrive at a much better final product.  Also, after I
went to all the trouble of writing my own version I found a version
someone had written out at Ames that was also different from mine.  If
the author reads this news group, maybe he'd like to post it.  I'd
post it myself, but I'm not willing to take responsibility for my own
code, let alone someone else's :-)

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	Makefile
#	timeout.c
#	timeout.man
# This archive created: Mon Oct  2 16:30:54 1989
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'Makefile'
then
	echo shar: "will not over-write existing file 'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
# Makefile for timeout.

BINDIR = /usr/local/bin
MANEXT = l
MANDIR = /usr/local/man/man$(MANEXT)

CFLAGS = -O
LDFLAGS =
LINTFLAGS = -abch

timeout: timeout.c
	cc $(CFLAGS) -o timeout timeout.c $(LDFLAGS)

lint: timeout.c
	lint $(LINTFLAGS) timeout.c

install: timeout timeout.man
	install -cs timeout $(BINDIR)
	install -c timeout.man $(MANDIR)/timeout.$(MANEXT)

clean:
	rm -f core timeout *~ \#*
SHAR_EOF
fi
if test -f 'timeout.c'
then
	echo shar: "will not over-write existing file 'timeout.c'"
else
cat << \SHAR_EOF > 'timeout.c'
/* timeout.c -- execute a program with specified timeout period. */

#ifndef lint
static char sccsid[] = "@(#)timeout.c 1.0";
#endif

#include <ctype.h>
#include <signal.h>
#include <stdio.h>

/* SysV with no BSD extensions at all? */
#ifndef SIGCHLD
#define SIGCHLD SIGCLD
#endif

/* Function to call when SIGCHLD is received. */
void normal_exit()
{
    exit(0);
}

/* Main program. */
main(argc,argv)
int argc;
char *argv[];
{
    int child;
    char *c;

    /* Check arg count. */
    if(argc < 3) {
        fprintf(stderr,"Usage: %s time program [program args]...\n",argv[0]);
        exit(2);
    }

    /* Make sure time is a number. */
    for(c = argv[1]; *c; ++c)
        if(! isdigit(*c)) {
            fprintf(stderr,"Usage: %s time program [program args]...\n",argv[0]);
            exit(2);
        }

    /* Fork in the road. */
    if((child = fork()) < 0) {
        perror("fork");
        exit(2);
    }

    /* Child -- run program. */
    if(child == 0) {
        execvp(argv[2],argv+2);

        /* We will only be here if the exec fails. */
        fprintf(stderr,"%s: fatal error trying to exec %s\n",argv[0],argv[2]);
    }

    /* Parent -- wait for child to finish, kill child if it takes too long. */
    else {
        /* Exit normally if child finishes in time. */
        signal(SIGCHLD,normal_exit);

        /* Take a nap. */
        sleep(atoi(argv[1]));

        /* Child didn't finish it time.  Ignore SIGCHLD now, else we'll exit
           with 0 immediately upon killing the child. */
        signal(SIGCHLD,SIG_IGN);

        /* Pedocide. */
        if(kill(child,SIGKILL)) {
            perror("kill");
            exit(2);
        }

        /* Exit with error code since child didn't finish in time. */
        exit(1);
    }
}
SHAR_EOF
fi
if test -f 'timeout.man'
then
	echo shar: "will not over-write existing file 'timeout.man'"
else
cat << \SHAR_EOF > 'timeout.man'
.TH TIMEOUT L "7 August 1989"
.SH NAME
.B timeout
\- execute a program with specified timeout period
.SH SYNOPSIS
.B timeout
.I time program
[
.B program args...
]
.SH DESCRIPTION
.LP
.B timeout
executes a program with a specified timeout period of
.I time
seconds.  If the program finishes before the specified timeout period,
.B timeout
exits normally.  If the program doesn't finish in the specified amount
of time, it is killed and
.B timeout
exits with an exit status of 1.
.SH AUTHOR
Mike Fuller
.br
NASA Lewis Research Center
.br
mikef@sarah.lerc.nasa.gov
.SH BUGS
.B timeout
exits with an exit status of 0 if the program isn't found.
SHAR_EOF
fi
exit 0
#	End of shell archive

/-----------------------------------------------------------------------------\
| Mike J. Fuller |Internet: mikef@sarah.lerc.nasa.gov     |You'd be paranoid, |
|----------------|          mikef@zippysun.math.uakron.edu|too, if everyone   |
|/\/\/\/\/\/\/\/\|Bitnet:   r3mjf1@akronvm                |was out to get you!|
\-----------------------------------------------------------------------------/

stuart@amc-gw.UUCP (Stuart Poulin) (10/05/89)

How about an option (-r) that would kill any children of the process
that timeout kills?

tchrist@convex.COM (Tom Christiansen) (10/05/89)

In article <942@amc-gw.UUCP> stuart@amc-gw.UUCP (Stuart Poulin) writes:
>How about an option (-r) that would kill any children of the process
>that timeout kills?

Use killpg() instead of kill().

--tom

    Tom Christiansen                       {uunet,uiucdcs,sun}!convex!tchrist 
    Convex Computer Corporation                            tchrist@convex.COM
		 "EMACS belongs in <sys/errno.h>: Editor too big!"