/*
 * rstrt - child-process monitor/restart wrapper
 *
 * This program turns itself into a daemon, which in turn spawns
 * a child process as defined.
 *
 * Should anything happen to the child (exit, die, stop, et cetera),
 * the daemon will spawn another child to take its place.
 *
 * All IO is directed to a file for purposes of debugging the parent
 * as well as the children.
 *
 * This and other hacks can be found at: http://oddgeek.info/
 *
 * Copyright (c) 2001 Jason A. Dour
 *
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the authors be held liable for any damages arising from the
 * use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 *     1. The origin of this software must not be misrepresented; you must not
 *        claim that you wrote the original software. If you use this software in
 *        a product, an acknowledgment in the product documentation would be
 *        appreciated but is not required.
 *
 *     2. Altered source versions must be plainly marked as such, and must not
 *        be misrepresented as being the original software.
 *
 *     3. This notice may not be removed or altered from any source
 *        distribution.
 *
 */

/*
 * Version Information
 *
 * 1.0  2009.10.26
 *
 *      Revised to accomodate automatic logfile name generations based on
 *      time/date stamp.  Probably should allow for passing command via
 *      ARGV, but hey...it's a hack.
 *
 * ooze 2001.10.15
 * 
 *      First documented version.  Used to peg an SSH tunnel for database
 *      communications across an unsecured network.
 *
 */



/*
 * USER DEFINED VALUES
 *
 * Define the exec string, are a comma-separated, null-terminated array 
 * of strings.  Also, define the directory in which to put output files.
 *
 */
#define EXECSTRING      "/usr/local/bin/someproc","someproc","--debug",NULL  /* FOR PRODUCTION. */
/* #define EXECSTRING   "/bin/sleep","sleep","600",NULL  /* FOR TESTING. */

#define LOGFILE         "/root"



/* STANDARD INCLUDES */
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/stat.h>

/* CHOOSE PROPER ARGS HEADER BASED ON ANSI OR K&R COMPILER. */
#ifdef ANSIC
#include <stdarg.h>
#define VA_START(a,f)   va_start(a,f)
#elif __linux__
#include <stdarg.h>
#define VA_START(a,f)   va_start(a,f)
#else
#include <varargs.h>
#define VA_START(a,f)   va_start(a)
#endif



/* Program name. */
#define PROGNAME	"rstrt"

/* Child process to be killed. */
pid_t kchld;

/* Filename for logfile. */
char logfile[512]; /* Set at max length of 512 characters...this is a hack, remember? */



/*
 * dtstamp()
 *    Write the date/time stamp to the logfile along with the current
 *    process ID.
 */
int dtstamp () {
   time_t ltvar; /* time placeholder. */
   struct tm *lt; /* exploded time structure. */
   pid_t cpid;

   /* Get local time. */
   time(&ltvar);
   lt = localtime(&ltvar);

   /* Get our process ID. */
   cpid = getpid();

   /* Dump date/time stamp and pid to stdout. */
   printf("[%d-%.2d-%.2d %.2d:%.2d:%.2d]: (%d) ",
      lt->tm_year + 1900, lt->tm_mon + 1,
      lt->tm_mday, lt->tm_hour, lt->tm_min,
      lt->tm_sec, cpid);

   return(0);
}



/*
 * logit(fmt, args)
 *    Take a variable number of arguments, the first being the printf()
 *    format string, followed by any additional arguments.
 */
#ifdef ANSIC
int logit (char *fmt, ...)
#elif __linux__
int logit (char *fmt, ...)
#else
int logit (fmt, va_alist)
   char *fmt;
   va_dcl
#endif
{
   va_list args;

   VA_START(args, fmt);

   dtstamp();
   vprintf(fmt, args);
   fflush(NULL);

   va_end(args);

   return(0);
}



/*
 * cleanup(signo)
 *    Take the trapped signal number as an argument.  Then proceed to
 *    kill the child process and shutdown.
 */
void cleanup (signo)
   int signo;
{
   logit("SIGTERM (%d) received.  Killing child (%d) and shutting down.\n", signo, kchld);

if ( kchld > 0 )
      kill(kchld, SIGKILL);

   logit("%s stopped.\n", PROGNAME);
   exit(15);
}



/*
 * logname()
 *    Build log filename from timestamp.
 *    I would like to include data from EXECSTRING, but maybe next time.
 */
int logname ()
{
   time_t ltvar; /* time placeholder. */
   struct tm *lt; /* exploded time structure. */

   /* Get local time. */
   time(&ltvar);
   lt = localtime(&ltvar);

   sprintf(logfile, "%s/%s.%d%.2d%.2d.%.2d%.2d%.2d.log",
      LOGFILE, PROGNAME, lt->tm_year + 1900, lt->tm_mon + 1,
      lt->tm_mday, lt->tm_hour, lt->tm_min,
      lt->tm_sec);

   return(0);
}



/*
 * daemon_init()
 *    Turn the program into a daemon.
 */
int daemon_init ()
{
   pid_t pid;

   if ( (pid = fork()) < 0 ) {
      logit("Error creating daemon.  EXITING.");
      exit(2);
      }
   else if ( pid != 0 )
      exit(0);  /* Okay I love you crazy parent process, buhbye! */

   /* Child process becomes daemon. */
   setsid();  /* Become session leader. */
   chdir("/");  /* Default working directory to root filesystem. */
   umask(0);  /* Clear creation mask. */

   return(0);
}



/* MAIN */
int main (argc, argv)
   int argc;
   char *argv[];
{
   pid_t chld; /* Child process. */
   int status; /* process status. */
   time_t last, current; /* time placeholder. */

   /* Build initial logfile filename. */
   logname();

   /* Assign OUT and ERR to logfile. */
   freopen(logfile,"a",stdout);
   freopen(logfile,"a",stderr);

   /* Assign null as input. */
   freopen("/dev/null","r",stdin);

   /* Log start. */
   logit("%s started\n", PROGNAME);

   /* Split off the daemon. */
   daemon_init();

   /* Log daemon init. */
   logit("Daemon Initialized\n");

   /* Log start of watch loop. */
   logit("Entering execute/monitor loop.\n");
   while ( 1 ) {
      /* Safety net timestamp.*/
      time(&last);

      /* Fork!  Fork!  Fork! */
      chld = fork();

      /* FORK: Error handling. */
      if ( chld < 0 ) {
         logit("Error: Could not fork!\n");
         exit(1);
         }

      /* FORK: Child processing. */
      if ( chld == 0 ) {
         /* Exec the command. */
         logit("Starting process.\n");
         execl(EXECSTRING);
         /*
          * Still here?  Where's the kaboom?
          * There was supposed to be an earth-shattering kaboom!
          */
         logit("Error: Could not start process!\n");
         exit(127);
         }

      /* FORK: Parent processing. */
      /* Wait for child to do something, then log outcome. */
      if ( chld > 0 ) {
         /* Trap TERMs and do cleanup. */
         signal(SIGTERM, cleanup);
         kchld = chld;
         waitpid(chld, &status, WUNTRACED);
         if (WIFEXITED(status)) {
            logit("Child terminated normally.  (Status = %d)\n", WEXITSTATUS(status));
         } else if (WIFSIGNALED(status)) {
            logit("Child terminated abnormally.  (Status = %d)%s\n", WTERMSIG(status),
#ifdef WCOREDUMP
               WCOREDUMP(status) ? " (core file generated)" : "");
#else
               "");
#endif
         } else if (WIFSTOPPED(status)) {
            /* If chld is STOPped, then it cannot tunnel data.  Kill it. */
            kill(chld, SIGKILL);
            logit("Child terminated after receiving STOP signal.  (Status = %d)\n", WSTOPSIG(status));
            }
         }

      /* Safety net check; keep from crazy respawn. */
      time(&current); /* Get current time */
      /* If the current time is less than 31 seconds from the last time... */
      if ( current - last <= 30 ) {
         /* Bomb out.  Respawning too quickly. */
         logit("Error: Respawning too fast.  Exiting.\n");
         exit(10);
         }
      /* Generate a new logfile name. */
      logname();
      /* Re-attach output to new logfile. */
      freopen(logfile,"a",stdout);
      freopen(logfile,"a",stderr);
      logit("%s restarting child after failure.", PROGNAME);
      }

   /* That's it!  We're done.  Let's go home. */
   exit(0);
}

