Subject: Re: Radius (Livingston) on NetBSD
To: None <codewarrior@daemon.org>
From: der Mouse <mouse@Rodents.Montreal.QC.CA>
List: current-users
Date: 07/09/1997 07:45:25
>> loop like
>> 	while true
>> 		do	/etc/.../radiusd ...
>> 	done

> would anyone be interested in adding a cute little respawning daemon
> to the tree?  something that runs a program and then starts it again
> when it dies?

Here's the one I wrote for SunOS; it probably would require little
porting.  If anyone wants to pull it into the NetBSD tree, feel free:

/*
 * keeprunning [-m address] [-o] [-e] [--] command [arg [arg ...]]
 *
 * Runs "command arg arg...".  If/when it exits, it runs it again.  If
 *  -m is given, mails a death report to the given address each time it
 *  dies; -o and -e cause the death report to include stdout and
 *  stderr, which are otherwise thrown away.  (If more than 64K is
 *  produced on stdout or stderr, only the last 64K is included in the
 *  death report.)  If -m is not given, -o and -e are ignored if
 *  present and death reports are not sent anywhere.
 *
 * If the command begins with a - sign, the -- option must be given to
 *  mark the end of the option list.
 *
 * Backoff is implemented to protect against rapid repeated failures.
 *  When a copy is started, a timer is also started.  If the process
 *  dies before the timer expires, keeprunning waits until the timer
 *  expires, then repeats with the timer value multiplied by 1.5.  If
 *  the timer expires first, keeprunning resets it to its initial value
 *  and awaits the death of the process.  If keeprunning receives a
 *  SIGUSR1, it unconditionally resets its timer value, and if it's
 *  sleeping after a death waiting for the timer to expire, causes the
 *  timer to expire immediately (even if the default timer value has
 *  not yet expired).
 *
 * The timer's initial value is 60 seconds.
 */

/* max amount of stdout/stderr to save */
#define IOBUFSIZE 65536

/* default timer, in seconds */
#define TIMER_DEF_SEC 60

/* mail command - put a 0 where the to-address should be inserted */
/* this array is not nil-terminated; its size determines the count */
static const char *mailcmd[] = { "/bin/mail", 0 };

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include <strings.h>
#include <sys/file.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <syscalls.h>
#include <sys/ioctl.h>
#include <signaltype.h>

extern char **argvec;

extern void sleep(unsigned int);

typedef struct obuf OBUF;

struct obuf {
  int fd;
  unsigned int ptr;
  unsigned int fill;
  char buf[IOBUFSIZE];
  } ;

static char *mailto;
static int flag_o;
static int flag_e;
static int flag_f;
static int run_ac;
static const char **run_av;
static OBUF stdo;
static OBUF stde;
static struct timeval def_timer;
static struct timeval cur_timer;
static struct timeval child_start;
static int child_pid;
static int child_stat;
/* we would _like_ to just make these ints.  But there's no sigselect()
   (ie, a syscall combining sigpause() and select()), so we have to turn
   signals into something that will reliably break out of a select() loop.
   There is a possibility of our deadlocking against ourselves - blocking
   in the write() in the signal handler becase the pipe is full - but for
   this to occur, we'd have to be flooded with thousands of signals fast
   enough to keep us from returning to the main line to read from the pipes,
   which is unlikely enough to be ignored. */
static int usr1pipe[2];
static int chldpipe[2];

static SIGNALHANDLERDEF(handle_usr1,sig)
{
 write(usr1pipe[1],"",1);
}

static SIGNALHANDLERDEF(handle_chld,sig)
{
 write(chldpipe[1],"",1);
}

static void ppipe(int *fdv)
{
 while (1)
  { if (pipe(fdv) == 0) break;
    perror("pipe");
    sleep(10);
  }
}

static int pfork(void)
{
 int pid;

 while (1)
  { pid = fork();
    if (pid >= 0) break;
    perror("fork");
    sleep(10);
  }
 return(pid);
}

static void makepipe(int *fdp, OBUF *obp)
{
 int p[2];

 ppipe(&p[0]);
 *fdp = p[1];
 obp->fd = p[0];
 obp->ptr = 0;
 obp->fill = 0;
}

static void makenull(int *fdp)
{
 *fdp = open("/dev/null",O_RDWR,0);
}

static void dupit(int old, int new)
{
 if (old != new)
  { dup2(old,new);
    close(old);
  }
}

static void closeabove(int maxopen)
{
 int i;

 for (i=getdtablesize()-1;i>maxopen;i--) close(i);
}

static void fork_child(void)
{
 int fd0;
 int fd1;
 int fd2;

 makenull(&fd0);
 if (flag_o) makepipe(&fd1,&stdo); else makenull(&fd1);
 if (flag_e) makepipe(&fd2,&stde); else makenull(&fd2);
 child_pid = pfork();
 if (! child_pid)
  { dupit(fd0,0);
    dupit(fd1,1);
    dupit(fd2,2);
    closeabove(2);
    execv(run_av[0],run_av);
    _exit(1);
  }
 close(fd0);
 close(fd1);
 close(fd2);
 gettimeofday(&child_start,0);
}

static struct timeval tvadd(struct timeval a, struct timeval b)
{
 struct timeval rv;

 rv.tv_sec = a.tv_sec + b.tv_sec;
 rv.tv_usec = a.tv_usec + b.tv_usec;
 if (rv.tv_usec >= 1000000)
  { rv.tv_usec -= 1000000;
    rv.tv_sec ++;
  }
 return(rv);
}

static struct timeval tvsub0(struct timeval num, struct timeval sub)
{
 struct timeval rv;

 rv.tv_sec = num.tv_sec - sub.tv_sec;
 rv.tv_usec = num.tv_usec - sub.tv_usec;
 if (rv.tv_usec < 0)
  { rv.tv_usec += 1000000;
    rv.tv_sec --;
  }
 if (rv.tv_sec < 0)
  { rv.tv_sec = 0;
    rv.tv_usec = 0;
  }
 return(rv);
}

static void wait_children(void)
{
 int pid;
 int stat;

 while (1)
  { pid = wait3(&stat,WNOHANG,0);
    if (pid <= 0) return;
    if (pid == child_pid)
     { child_stat = stat;
       child_pid = -1;
     }
  }
}

static void drain_signal_pipe(int rfd, int wfd)
{
 int n;
 char c;

 while (1)
  { if (ioctl(rfd,FIONREAD,&n) < 0)
     { fprintf(stderr,"%s: signal pipe FIONREAD: %s\n",argvec[0],strerror(errno));
       exit(1);
     }
    if (n < 1) break;
    n = read(rfd,&c,1);
    if (n < 0)
     { fprintf(stderr,"%s: signal pipe read: %s\n",argvec[0],strerror(errno));
       exit(1);
     }
    if (n == 0)
     { fprintf(stderr,"%s: signal pipe EOF?\n",argvec[0]);
       exit(1);
     }
  }
}

static void read_to_buffer(OBUF *b)
{
 int n;

 n = read(b->fd,&b->buf[b->ptr],IOBUFSIZE-b->ptr);
 if (n <= 0)
  { close(b->fd);
    b->fd = -1;
    return;
  }
 b->ptr += n;
 if (b->ptr >= IOBUFSIZE) b->ptr = 0;
 b->fill += n;
 if (b->fill >= IOBUFSIZE) b->fill = IOBUFSIZE;
}

typedef enum { AWAIT_TIMER = 1, AWAIT_DEATH, AWAIT_POSTDEATH } AWAIT_HOW;
static int await(AWAIT_HOW how)
{
 struct timeval timeout_at;
 struct timeval now;
 struct timeval delta;
 fd_set fds;
 int srv;
 int msk;
 int gotusr1;

 gotusr1 = 0;
 switch (how)
  { case AWAIT_TIMER:
       gettimeofday(&now,0);
       timeout_at = tvadd(now,cur_timer);
       break;
    case AWAIT_DEATH:
       break;
    case AWAIT_POSTDEATH:
       gettimeofday(&timeout_at,0);
       timeout_at.tv_sec += 5;
       break;
  }
 while (1)
  { switch (how)
     { case AWAIT_TIMER:
	  if ((child_pid < 0) && gotusr1) return(1);
	  break;
       case AWAIT_DEATH:
	  if (child_pid < 0) return(0);
	  break;
       case AWAIT_POSTDEATH:
	  break;
     }
    FD_ZERO(&fds);
    FD_SET(usr1pipe[0],&fds);
    FD_SET(chldpipe[0],&fds);
    if (flag_o && (stdo.fd >= 0)) FD_SET(stdo.fd,&fds);
    if (flag_e && (stde.fd >= 0)) FD_SET(stde.fd,&fds);
    switch (how)
     { case AWAIT_TIMER:
       case AWAIT_POSTDEATH:
	  gettimeofday(&now,0);
	  delta = tvsub0(timeout_at,now);
	  if ((delta.tv_sec == 0) && (delta.tv_usec == 0)) return(1);
	  srv = select(FD_SETSIZE,&fds,0,0,&delta);
	  break;
       case AWAIT_DEATH:
	  srv = select(FD_SETSIZE,&fds,0,0,0);
	  break;
     }
    if (srv < 0)
     { if (errno == EINTR) continue;
       fprintf(stderr,"%s: select: %s\n",argvec[0],strerror(errno));
       exit(1);
     }
    msk = sigblock(sigmask(SIGUSR1)|sigmask(SIGCHLD)) & ~(sigmask(SIGUSR1)|sigmask(SIGCHLD));
    if (FD_ISSET(usr1pipe[0],&fds))
     { drain_signal_pipe(usr1pipe[0],usr1pipe[1]);
       gotusr1 = 1;
     }
    if (FD_ISSET(chldpipe[0],&fds))
     { drain_signal_pipe(chldpipe[0],chldpipe[1]);
       wait_children();
     }
    if (flag_o && (stdo.fd >= 0) && FD_ISSET(stdo.fd,&fds))
     { read_to_buffer(&stdo);
     }
    if (flag_e && (stde.fd >= 0) && FD_ISSET(stde.fd,&fds))
     { read_to_buffer(&stde);
     }
    sigsetmask(msk);
  }
}

static void dump_obuf(OBUF *b, FILE *to)
{
 if (b->fill > b->ptr)
  { int beg;
    beg = (b->ptr + IOBUFSIZE - b->fill) % IOBUFSIZE;
    fwrite(&b->buf[beg],1,IOBUFSIZE-beg,to);
  }
 if (b->ptr > 0) fwrite(&b->buf[0],1,b->ptr,to);
}

static const char *strsignal(int signo)
{
 static char bad[64];
 extern const char *sys_siglist[];

 if ((signo < 0) || (signo >= NSIG))
  { sprintf(&bad[0],"unknown %d",signo);
    return(&bad[0]);
  }
 else
  { return(sys_siglist[signo]);
  }
}

static void send_death_report(void)
{
 FILE *f;
 int i;

 if (!strcmp(mailto,"-"))
  { f = fdopen(dup(1),"w");
  }
 else
  { int p[2];
    int fd1;
    int mpid;
    const char **mav;
#define NMAIL (sizeof(mailcmd)/sizeof(mailcmd[0]))
    ppipe(&p[0]);
    makenull(&fd1);
    mpid = pfork();
    if (mpid == 0)
     { mav = malloc((NMAIL+1)*sizeof(const char *));
       for (i=0;i<NMAIL;i++)
	{ mav[i] = mailcmd[i] ? mailcmd[i] : mailto;
	}
       mav[NMAIL] = 0;
       dupit(p[0],0);
       dupit(fd1,1);
       dup2(1,2);
       closeabove(2);
       execv(mav[0],mav);
       while (read(0,&i,sizeof(i)) > 0) ;
       _exit(1);
     }
    close(fd1);
    close(p[0]);
    f = fdopen(p[1],"w");
#undef NMAIL
  }
 fprintf(f,"Keeprunning death report\n\n");
  { time_t now;
    time(&now);
    fprintf(f,"Time = %.24s\n",ctime(&now));
  }
 fprintf(f,"Command =");
 for (i=0;i<run_ac;i++) fprintf(f," %s",run_av[i]);
 fprintf(f,"\n");
 fprintf(f,"Death status = ");
 if (WIFEXITED(child_stat))
  { fprintf(f,"exit %d\n",WEXITSTATUS(child_stat));
  }
 else if (WIFSIGNALED(child_stat))
  { fprintf(f,"signal %d [%s]",WTERMSIG(child_stat),strsignal(WTERMSIG(child_stat)));
    if (WCOREDUMP(child_stat)) fprintf(f," (core dumped)");
    fprintf(f,"\n");
  }
 else if (WIFSTOPPED(child_stat))
  { fprintf(f,"stopped %d?\n",WSTOPSIG(child_stat));
  }
 else
  { fprintf(f,"unknown exit status %#x\n",child_stat);
  }
 if (flag_o)
  { fprintf(f,"Standard output:\n");
    dump_obuf(&stdo,f);
    fprintf(f,"\n");
  }
 if (flag_e)
  { fprintf(f,"Standard error:\n");
    dump_obuf(&stde,f);
    fprintf(f,"\n");
  }
 fprintf(f,"----\n");
 fclose(f);
}

static void detach(void)
{
 int i;

 i = pfork();
 if (i) exit(0);
 i = open("/",O_RDONLY,0);
 dupit(i,0);
 dup2(0,1);
 dup2(1,2);
 closeabove(2);
 i = open("/dev/tty",O_RDWR,0);
 if (i >= 0)
  { ioctl(i,TIOCNOTTY,0);
    close(i);
  }
}

void main(int, char **);
void main(int ac, char **av)
{
 flag_o = 0;
 flag_e = 0;
 mailto = 0;
 for (ac--,av++;ac;ac--,av++)
  { if (**av != '-') break;
    if (!strcmp(*av,"-m"))
     { if (ac > 1)
	{ ac --;
	  av ++;
	  mailto = *av;
	}
       else
	{ fprintf(stderr,"%s: -m needs an argument\n",argvec[0]);
	  exit(1);
	}
     }
    else if (!strcmp(*av,"-f"))
     { flag_f = 1;
     }
    else if (!strcmp(*av,"-o"))
     { flag_o = 1;
     }
    else if (!strcmp(*av,"-e"))
     { flag_e = 1;
     }
    else if (!strcmp(*av,"--"))
     { ac --;
       av ++;
       break;
     }
    else
     { fprintf(stderr,"%s: unknown flag %s\n",argvec[0],*av);
       exit(1);
     }
  }
 run_ac = ac;
 run_av = (const char **) av;
 if (run_ac < 1)
  { fprintf(stderr,"%s: need a command to run!\n",argvec[0]);
    exit(1);
  }
 if (flag_f) detach();
 def_timer.tv_sec = TIMER_DEF_SEC;
 def_timer.tv_usec = 0;
 cur_timer = def_timer;
 ppipe(&usr1pipe[0]);
 ppipe(&chldpipe[0]);
 signal(SIGUSR1,handle_usr1);
 signal(SIGCHLD,handle_chld);
 while (1)
  { fork_child();
    if (await(AWAIT_TIMER))
     { cur_timer = def_timer;
     }
    else
     { /* this mess is to multiply the timer value by 1.5.  I suppose it would
	  probably be good enough to just multiply the seconds value and
	  ignore underflow into the usec value, but why not do it right... */
       cur_timer.tv_usec += cur_timer.tv_usec / 2;
       if (cur_timer.tv_sec & 1) cur_timer.tv_usec += 500000;
       cur_timer.tv_sec += cur_timer.tv_sec / 2;
       while (cur_timer.tv_usec >= 1000000) /* don't bother with / and %; */
	{ cur_timer.tv_usec -= 1000000;     /* it loops twice, max */
	  cur_timer.tv_sec ++;
	}
     }
    if (child_pid >= 0) await(AWAIT_DEATH);
    await(AWAIT_POSTDEATH);
    if (mailto) send_death_report();
    if (flag_o && (stdo.fd >= 0)) close(stdo.fd);
    if (flag_e && (stde.fd >= 0)) close(stde.fd);
  }
}