//Copyright, 2001-2003, Astra Network Inc.  All Rights Reserved

//This source code has been published by Astra Network Inc. However, any
//use, reproduction, modification, distribution or transfer of this
//software, or any software which includes or is based upon any of this
//code, is only permitted if expressly authorized by a written license
//agreement from Astra. Contact your Astra representative directly for
//more information.

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#ifdef QNX4
# include <process.h>
#endif
#include "read_pcode.h"
#include "module_load.h"
#include "loop.h"
#include "global_bits.h"
#include "olmod.h"
#include "version.h"

#ifndef LOG_PERROR		// in Solaris for example
#define LOG_PERROR LOG_CONS
#endif

static void sighup_handler (int signum);
/* initiate reload */
static void sigterm_handler (int signum);
/* exit gracefully at next convinience */
static void sigint_handler (int signum);
/* parent done exit */

pid_t child = 0;
static char **nargv;

int verbose = 0;
int quiet = 0;
static char *defaultfname = "p.out";

static volatile int cont = 0;
static volatile int quitting = 0;

int
main (int argc, char *argv[])
{
  int i;
  char *fname = defaultfname;
  char *dldirs[16];
  unsigned int dldircount = 0;
  int filenameset = 0;
  int endofopts = 0;
  int restarting = 0;
  sigset_t mysigset;

  /* Ignore SIGHUP for the moment */
  (void) signal (SIGHUP, SIG_IGN);
  /* clear out signal mask */
  (void) sigemptyset (&mysigset);
  (void) sigprocmask (SIG_SETMASK, &mysigset, NULL);

  for (i = 1; i < argc; i++)
    {
      if (endofopts == 0 && argv[i][0] == '-')
	{
	  switch (argv[i][1])
	    {
	    case 'd':
	      i++;
	      if (dldircount >= 15)
		{
		  fatal
		    ("Too many module directories on the commandline (max 15): -d %s",
		     argv[i]);
		}
	      if (argv[i][0] == '/')
		{
		  dldirs[dldircount] = malloc (strlen (argv[i] + 1));
		  if (dldirs[dldircount] == NULL)
		    {
		      fatal ("Unable to allocate memory for dldir entry");
		    }
		  strcpy (dldirs[dldircount], argv[i]);
		}
	      else
		{
		  char *pwd = getenv ("PWD");

		  if (pwd == NULL)
		    {
		      fatal ("Unable to getenv(\"PWD\") for dldir entry");
		    }
		  dldirs[dldircount] =
		    malloc (strlen (argv[i]) + strlen (pwd) + 2);
		  if (dldirs[dldircount] == NULL)
		    {
		      fatal ("Unable to allocate memory for dldir entry");
		    }
		  sprintf (dldirs[dldircount], "%s/%s", pwd, argv[i]);
		}
	      dldircount++;
	      break;
	    case 'v':
	      verbose = 1;
	      break;
	    case 'q':
	      quiet = 1;
	      break;
	    case 'R':
	      restarting = 1;
	      break;
	    case '-':
	      endofopts = 1;
	      break;
	    default:
	      printf ("Unknown option: %s\n", argv[i]);
	    }
	}
      else
	{
	  if (filenameset != 0)
	    {
	      fatal ("Too many filenames on the commandline: %s", argv[i]);
	    }
	  fname = argv[i];
	  filenameset = 1;
	}
    }
  /* NULL terminate */
  dldirs[dldircount] = NULL;

  /* Display header */
  if (quiet == 0)
    {
#ifdef BETA
      fprintf (stderr,
	       "HAFTA Overlord RunTime v%d.%02d %s (Build %s)\n",
	       VERSION / 100, VERSION % 100, BETA, BUILD);
      fprintf (stderr, "This is a beta version. It is not for use in"
	       " production evironments.\n");
#else
      fprintf (stderr,
	       "HAFTA Overlord RunTime v%d.%02d (Build: %s)\n",
	       VERSION / 100, VERSION % 100, BUILD);
#endif /* #ifdef BETA */
      fprintf (stderr,
	       "Copyright 2002-2003, Astra Network Inc., "
	       "All rights reserved.\n");
    }

  /* set up logging */
  openlog ("olrt",
	   LOG_NDELAY | (verbose != 0 ? LOG_PERROR : 0),
	   LOG_USER /* for now */ );

  /* Make a new copy of argv for respawning */
  nargv = malloc ((argc + 2) * sizeof (char *));
  if (nargv == NULL)
    {
      fatal ("Out of memory copying argv");
    }
  nargv[1] = NULL;
  if (restarting != 0)
    {
      /* copy old arguments */
      for (i = 0; i < argc; i++)
	nargv[i] = argv[i];
      /* and terminate */
      nargv[argc] = NULL;
    }
  else
    {
      /* copy old arguments */
      for (i = 0; i < argc; i++)
	nargv[i + 1] = argv[i];
      /* insert restart flag */
      nargv[0] = nargv[1];
      nargv[1] = "-R";
      /* and terminate */
      nargv[argc + 1] = NULL;
    }
  (void) signal (SIGHUP, sighup_handler);

  /* Read the pcode, load the modules */
  read_pcode (fname);
  load_modules (dldirs);

  /* clean up */
  for (i = 0; dldirs[i] != NULL; i++)
    free (dldirs[i]);

  /* Block SIGINT */
  (void) sigemptyset (&mysigset);
  (void) sigaddset (&mysigset, SIGINT);
  (void) sigprocmask (SIG_BLOCK, &mysigset, NULL);

  (void) sigemptyset (&mysigset);
  if (restarting != 0)
    {
      /* install the sigint handler */
      (void) signal (SIGINT, sigint_handler);

      /* notify the parent we're ready to start up */
      (void) kill (getppid (), SIGTERM);
      while (cont != 0)
	{
	  (void) sigsuspend (&mysigset);
	}
      (void) signal (SIGINT, SIG_DFL);
    }
  /* clear signal mask, and mask SIGTERM */
  (void) sigaddset (&mysigset, SIGTERM);
  (void) sigprocmask (SIG_SETMASK, &mysigset, NULL);

  /* init modules */
  initmodules ();
  /* deinit modules later */
  (void) atexit (deinitmodules);

  /* SIGTERM deinits modules, so it can't be called until the 
   *  modules are inited 
   */
  (void) signal (SIGTERM, sigterm_handler);

  /* clear the signal mask, block SIGUSR1 and SIGUSR2 */
  (void) sigemptyset (&mysigset);
  (void) sigaddset (&mysigset, SIGUSR1);
  (void) sigaddset (&mysigset, SIGUSR2);
  (void) sigprocmask (SIG_SETMASK, &mysigset, NULL);

  /* execute pcode */
  do_pcode ();

  return 0;
}

void
exit_if_quitting ()
{
  if (quitting == 0)
    return;

  deinitmodules ();
  if (child != 0)
    (void) kill (child, SIGINT);
  if (verbose != 0)
    printf ("Normal overlord shutdown. Goodnight.\n");
  exit (EXIT_SUCCESS);
}

static void
sighup_handler (int /*@unused@ */ signum)
{
  child = fork ();
  if (child != 0)
    {
      (void) signal (SIGHUP, sighup_handler);
      return;
    }
  /* re-exec ourselves to clear everything out */
  (void) execv (nargv[0], nargv);
  fatal ("execv failed");	/* hopefully never reached */
}

static void
sigint_handler (int /*@unused@ */ signum)
{
  cont = 1;
}

static void
sigterm_handler (int /*@unused@ */ signum)
{
  quitting = 1;
  (void) signal (SIGTERM, SIG_IGN);
}

void
fatal (const char *format, ...)
{
  va_list args;

  printf ("olrt: ");
  va_start (args, format);
  vprintf (format, args);
  va_end (args);
  printf ("\n");
  exit (EXIT_FAILURE);
}
