// 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.

/*
 * tools.c - A set of useful functions that don't really fit anywhere
 *           else.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include "defines.h"

namelist_t /*@null@ */  * name_list = NULL;
argtype_list_t /*@null@ */  * argtype_list = NULL;
dyn_depend_t /*@null@ */  * dyndep_list = NULL;
char /*@null@ */ *include_list = NULL;

extern int yyleng;

long int
size2num (char *text)
{
  char *p = (text);
  long int num;

  if (strchr (p, 'l') != NULL)
    {
      num = atol (p) * 1024L;
    }
  else if (strchr (p, 'm') != NULL)
    {
      num = atol (p) * 1024L * 1024L;
    }
  else
    {
      num = atol (p);
    }
  return num;
}

char *
parse_string (char *str)
{
  int index;
  int newindex = 0;
  char *newstr;
  int newlen;

  newlen = 1;
  for (index = 0; index < (int) strlen (str); index++)
    {
      if (str[index] == '\\' && (index + 1) < (int) strlen (str))
	newlen--;
      if (str[index] == '(')
	newlen++;
      newlen++;
    }

  newstr = malloc (newlen * sizeof (char));

  if (!newstr)
    yyfatalerror ("Out of memory");

  for (index = 0; index < (int) strlen (str); index++)
    {
      if (str[index] != '\"')
	{
	  if (str[index] == '\\')
	    {
	      if (index + 1 < (int) strlen (str))	// More string exists
		{
		  index++;
		  switch (str[index])
		    {
		    case 'N':
		    case 'n':
		      newstr[newindex] = '\n';
		      break;
		    case '\"':
		      newstr[newindex] = '\"';
		      break;
		    default:
		      newstr[newindex] = str[index];
		    }
		  newindex++;
		}
	      else
		{
		  newstr[newindex] = '\\';
		  newindex++;
		}
	    }
	  else
	    {
	      if (str[index] == ')')
		{
		  newstr[newindex] = '\\';
		  newstr[newindex + 1] = ')';
		  newindex += 2;
		}
	      else
		{
		  newstr[newindex] = str[index];
		  newindex++;
		}
	    }
	}
    }
  newstr[newindex] = '\0';

  return (newstr);
}

/********************/
/** Name functions **/
/********************/

void
add_name (nt_t type, char *name, unsigned int context)
{
  namelist_t *list;

  /** First, check for duplication among the different types. **/
  list = name_list;
  while (list)
    {
      switch (type)
	{
	case NT_PROCESS:
	  if (list->procs.name)
	    {
	      if (strcasecmp (list->procs.name, name) == 0)
		{
		  yyerror
		    ("Name conflict: process \"%s\" conflicts with proc defined on line %d",
		     name, list->procs.lineno);
		  return;
		}
	    }
	  if (list->nebs.name)
	    {
	      if (strcasecmp (list->nebs.name, name) == 0)
		{
		  yyerror
		    ("Name conflict: process \"%s\" conflicts with nebuloid defined on line %d",
		     name, list->nebs.lineno);
		  return;
		}
	    }
	  if (list->modules.name)
	    {
	      if (strcasecmp (list->modules.name, name) == 0)
		{
		  yyerror
		    ("Name conflict: process \"%s\" conflicts with module of same name",
		     name);
		  return;
		}
	    }
	  if (list->processes.name)
	    {
	      if (strcasecmp (list->processes.name, name) == 0)
		{
		  yyerror
		    ("Process \"%s\" defined twice\nOriginal definition on line %d",
		     name, list->processes.lineno);
		  return;
		}
	    }
	  break;

	case NT_PROC:
	  if (list->procs.name)
	    {
	      if (strcasecmp (list->procs.name, name) == 0)
		{
		  if (context == 0 || list->procs.context == 0
		      || context == list->procs.context)
		    {
		      yyerror
			("Duplicate proc \"%s\"\nOriginally defined line %d",
			 name, list->procs.lineno);
		      return;
		    }
		}
	    }

	  if (list->nebs.name)
	    {
	      if (strcasecmp (list->nebs.name, name) == 0)
		{
		  if (context == 0 || list->nebs.context == 0
		      || context == list->nebs.context)
		    {
		      yyerror
			("Name conflict: proc \"%s\" conflicts with nebuloid defined on line %d",
			 name, list->nebs.lineno);
		      return;
		    }
		}
	    }
	  if (list->modules.name)
	    {
	      if (strcasecmp (list->modules.name, name) == 0)
		{
		  yyerror
		    ("Name conflict: proc \"%s\" conflicts with module of same name",
		     name);
		  return;
		}
	    }

	  if (list->processes.name)
	    {
	      if (strcasecmp (list->processes.name, name) == 0)
		{
		  yyerror
		    ("Name conflict: proc \"%s\" conflicts with process on line %d",
		     name, list->processes.lineno);
		  return;
		}
	    }
	  break;

	case NT_NEBULOID:
	  if (list->procs.name)
	    {
	      if (strcasecmp (list->procs.name, name) == 0)
		{
		  if (context == 0 || list->procs.context == 0
		      || context == list->procs.context)
		    {
		      yyerror
			("Name conflict: nebuloid \"%s\" conflicts with proc defined on line %d",
			 name, list->procs.lineno);
		      return;
		    }
		}
	    }

	  if (list->nebs.name)
	    {
	      if (strcasecmp (list->nebs.name, name) == 0)
		{
		  if (context == 0 || list->nebs.context == 0
		      || context == list->nebs.context)
		    {
		      yyerror
			("Duplicate nebuloid \"%s\"\nOriginally defined line %d",
			 name, list->nebs.lineno);
		      return;
		    }
		}
	    }
	  if (list->modules.name)
	    {
	      if (strcasecmp (list->modules.name, name) == 0)
		{
		  yyerror
		    ("Name conflict: nebuloid \"%s\" conflicts with module of same name",
		     name);
		  return;
		}
	    }

	  if (list->processes.name)
	    {
	      if (strcasecmp (list->processes.name, name) == 0)
		{
		  yyerror
		    ("Name conflict: nebuloid \"%s\" conflicts with process on line %d",
		     name, list->processes.lineno);
		  return;
		}
	    }
	  break;

	case NT_MODULE:
	  if (list->procs.name)
	    {
	      if (strcasecmp (list->procs.name, name) == 0)
		{
		  if (context == 0 || list->procs.context == 0
		      || context == list->procs.context)
		    {
		      yyerror
			("Name conflict: module \"%s\" conflicts with proc defined on line %d",
			 name, list->procs.lineno);
		      return;
		    }
		}
	    }

	  if (list->nebs.name)
	    {
	      if (strcasecmp (list->nebs.name, name) == 0)
		{
		  if (context == 0 || list->nebs.context == 0
		      || context == list->nebs.context)
		    {
		      yyerror
			("Name conflict: module \"%s\" conflicts with nebuloid defined on line %d",
			 name, list->nebs.lineno);
		      return;
		    }
		}
	    }
	  if (list->modules.name)
	    {
	      if (strcasecmp (list->modules.name, name) == 0)
		{
		  // We don't mind duplicate modules; just don't add them.
		  return;
		}
	    }

	  if (list->processes.name)
	    {
	      if (strcasecmp (list->processes.name, name) == 0)
		{
		  yyerror
		    ("Name conflict: module \"%s\" conflicts with process on line %d",
		     name, list->processes.lineno);
		  return;
		}
	    }
	  break;
	}
      list = list->next;
    }

  if (!name_list)
    {
      name_list = calloc (1, sizeof (namelist_t));
      if (!name_list)
	yyerror ("Out of memory");
    }

  list = name_list;

  while (list)
    {
      switch (type)
	{
	case NT_PROCESS:
	  if (!list->processes.name)
	    {
	      list->processes.name = strdup (name);
	      if (!list->processes.name)
		yyfatalerror ("Out of memory");
	      list->processes.context = context;
	      list->processes.lineno = (unsigned int) yylineno;
	      return;
	    }
	  break;
	case NT_PROC:
	  if (!list->procs.name)
	    {
	      list->procs.name = strdup (name);
	      if (!list->procs.name)
		yyfatalerror ("Out of memory");
	      list->procs.context = context;
	      list->procs.lineno = (unsigned int) yylineno;
	      return;
	    }
	  break;
	case NT_NEBULOID:
	  if (!list->nebs.name)
	    {
	      list->nebs.name = strdup (name);
	      if (!list->nebs.name)
		yyfatalerror ("Out of memory");
	      list->nebs.context = context;
	      list->nebs.lineno = (unsigned int) yylineno;
	      return;
	    }
	  break;
	case NT_MODULE:
	  if (!list->modules.name)
	    {
	      list->modules.name = strdup (name);
	      if (!list->modules.name)
		yyfatalerror ("Out of memory");
	      list->modules.context = context;
	      list->modules.lineno = (unsigned int) yylineno;
	      return;
	    }
	  break;
	}
      if (!list->next)
	{
	  list->next = calloc (1, sizeof (namelist_t));
	  if (!list->next)
	    yyfatalerror ("Out of memory");
	}
      list = list->next;
    }
}

bool
find_name (nt_t type, char *name, unsigned int context)
{
  namelist_t *list;

  list = name_list;

  while (list)
    {
      switch (type)
	{
	case NT_PROCESS:
	  if (list->processes.name)
	    if (strcasecmp (list->processes.name, name) == 0)
	      return (TRUE);
	  break;
	case NT_NEBULOID:
	  if (list->nebs.name)
	    if (strcasecmp (list->nebs.name, name) == 0
		&& list->nebs.context == context)
	      return (TRUE);
	  break;
	case NT_PROC:
	  if (list->procs.name)
	    if (strcasecmp (list->procs.name, name) == 0
		&& list->procs.context == context)
	      return (TRUE);
	  break;
	case NT_MODULE:
	  if (list->modules.name)
	    if (strcasecmp (list->modules.name, name) == 0)
	      return (TRUE);
	  break;
	}
      list = list->next;
    }

  return (FALSE);
}

/**********************************/
/** System verification function **/
/**********************************/

void
verify_system (system_t * syst)
{
  policy_t *pol;
  policy_t *procpol;
  process_t *proc;
  dependency_t *deps;
  int num_start;
  int num_restart;
  int num_dp;
  int num_isup;
  int num_isalive;
  int num_sigchld;
  int num_sigusr1 = 0;
  int num_sigusr2 = 0;

  /** Make sure all dependencies correspond with processes. **/
  deps = syst->depends;

  while (deps)
    {
      if (!find_name (NT_PROCESS, deps->name, 0))
	{
	  yylineno = deps->lineno;
	  yytokenpos = deps->tokpos;
	  yyleng = strlen (deps->name);
	  yyerror
	    ("System dependency \"%s\" does not correspond to a process.",
	     deps->name);
	}
      deps = deps->next;
    }
  pol = syst->policy;

  while (pol)
    {
      if (pol->type == PT_PROCESS)
	{
	  num_sigchld = num_isup = num_isalive = num_start = num_restart =
	    num_dp = 0;

	  proc = pol->pol_data;
	  procpol = proc->policy;

	  while (procpol)
	    {
	      switch (procpol->type)
		{
		case PT_START:
		  num_start++;
		  break;
		case PT_RESTART:
		  num_restart++;
		  break;
		case PT_DEPENDFAIL:
		  num_dp++;
		  break;
		case PT_ISALIVE:
		  num_isalive++;
		  break;
		case PT_ISUP:
		  num_isup++;
		  break;
		case PT_SIGNAL:
		  num_sigchld++;
		  break;
		default:
		  break;
		}
	      procpol = procpol->next;
	    }

	  // XXX: Set yylineno!

	  if (num_start != 1)
	    yyerror ("Process \"%s\" requires one start policy", proc->name);
	  if (num_restart > 1)
	    yyerror ("Process \"%s\" requires one restart policy",
		     proc->name);
	  if (num_dp > 1)
	    yyerror ("Process \"%s\" has too many DEPENDFAIL policies",
		     proc->name);
	  if (num_isup > 1)
	    yyerror ("Process \"%s\" has too many ISUP policies", proc->name);
	  if (num_isalive > 1)
	    yyerror ("Process \"%s\" has too many ISALIVE policies",
		     proc->name);
	  if (num_sigchld > 1)
	    yyerror ("Process \"%s\" has too many SIGCHLD policies",
		     proc->name);
	  if (num_restart == 0 && num_start == 1)	// No restart policy specified; assume start
	    {
	      procpol = proc->policy;
	      while (procpol)
		{
		  if (procpol->type == PT_START)
		    break;
		  procpol = procpol->next;
		}
	      // I'd build a new restart, but why?  It's the same type.
	      proc->policy = new_policy (PT_RESTART, procpol->pol_data,
					 proc->policy, proc->context);
	    }

      /** If no DEPENDFAIL, add a blank one. **/
	  if (num_dp == 0)
	    proc->policy = new_policy (PT_DEPENDFAIL, new_dependfail (NULL),
				       proc->policy, proc->context);

	  deps = proc->depends;
	  while (deps)
	    {
	      if (!find_name (NT_PROCESS, deps->name, 0))
		{
		  yylineno = deps->lineno;
		  yytokenpos = deps->tokpos;
		  yyleng = strlen (deps->name);
		  yyerror
		    ("Process \"%s\" dependency \"%s\" does not correspond to a process",
		     proc->name, deps->name);
		}
	      deps = deps->next;
	    }

	  // Don't forget dynamic dependencies!
	  deps = find_dyn_depends (proc->context);
	  while (deps)
	    {
	      if (!find_name (NT_PROCESS, deps->name, 0))
		{
		  yylineno = deps->lineno;
		  yytokenpos = deps->tokpos;
		  yyleng = strlen (deps->name);
		  yyerror
		    ("Process \"%s\" dependency \"%s\" does not correspond to a process",
		     proc->name, deps->name);
		}
	      deps = deps->next;
	    }

	}

      if (pol->type == PT_SIGNAL)
	{
	  if (((sighandler_t *) pol->pol_data)->type == SH_SIGUSR1)
	    num_sigusr1++;
	  else
	    num_sigusr2++;
	}
      pol = pol->next;
    }
  if (num_sigusr1 > 1)
    yyerror ("Too many SIGUSR1 policies in system.");
  if (num_sigusr2 > 1)
    yyerror ("Too many SIGUSR2 policies in system.");

}

/************************************/
/** Argument list helper functions **/
/************************************/

argtype_t *
new_argtype (at_t type)
{
  // This is really ridiculous; it's a linked-list of BITS.
  argtype_t *newarg;

  newarg = malloc (sizeof (argtype_t));

  if (!newarg)
    yyfatalerror ("Out of memory: new_argtype");
  newarg->type = type;
  newarg->next = NULL;

  return (newarg);
}

void
add_argtype (char *name, argtype_t * stat, argtype_t * dyn)
{
  argtype_list_t *arglist;

  arglist = malloc (sizeof (argtype_list_t));

  if (!arglist)
    yyfatalerror ("Out of memory");

  arglist->name = strdup (name);
  if (!arglist->name)
    yyfatalerror ("Out of memory");

  arglist->statargs = stat;
  arglist->dynargs = dyn;
  arglist->next = argtype_list;

  argtype_list = arglist;
}

argtype_t *
get_argtype (char *name, at_t type)
{
  argtype_list_t *arglist;

  arglist = argtype_list;

  while (arglist)
    {
      if (strcasecmp (arglist->name, name) == 0)
	break;
      arglist = arglist->next;
    }
  if (!arglist)
    {
      yyerror ("Unable to find argument list definition for \"%s\"", name);
      return (NULL);
    }
  if (type == AT_STATIC)
    return (arglist->statargs);

  return (arglist->dynargs);
}

static argtype_stack_t *argstack = NULL;

argtype_t *
pop_argtype (void)
{
  argtype_t *arg;
  argtype_stack_t *node;

  if (argstack)
    {
      arg = argstack->arg;
      node = argstack;
      argstack = node->next;
      free (node);
    }
  else
    {
      yyerror ("Internal error: Attempted to pop past end of argument stack");
      arg = NULL;
    }
  return (arg);
}

void
push_argtype (argtype_t * arg)
{
  argtype_stack_t *node;

  node = malloc (sizeof (argtype_stack_t));
  if (!node)
    yyfatalerror ("Out of memory allocating argument stack node");

  node->arg = arg;
  node->next = argstack;
  argstack = node;
}

void
push_all_args (argtype_t * firstarg)
{
  argtype_t *lastarg = NULL;
  argtype_t *arg;
  char done = 0;

  // Must push argtypes onto the stack in reverse order.
  // lastarg is the last argument we haven't pushed onto the stack yet.
  while (!done)
    {
      push_argtype (lastarg);
      arg = firstarg;

      // lastarg is firstarg, therefore we've pushed them all
      if (arg == lastarg) 
	done = 1;
      else
	{
	  // find the one before lastarg.          
	  // Subtlety: It is impossible for arg to be NULL here, and lastarg
	  //  will always be either NULL or in the list.
	  while (arg->next != lastarg)
	    arg = arg->next;
	  lastarg = arg;
	}
    }
}

/*****************************************/
/** dynamic dependency helper functions **/
/*****************************************/

void
add_dyn_depend (char *name, unsigned int context)
{
  dyn_depend_t *curdyndep;
  dependency_t *curdep;

  curdyndep = dyndep_list;
  while (curdyndep)
    {
      if (context == curdyndep->context)
	break;
      curdyndep = curdyndep->next;
    }

  if (curdyndep == NULL)	// Not found!
    {
      curdyndep = calloc (1, sizeof (dyn_depend_t));
      if (curdyndep == NULL)
	yyfatalerror ("Out of memory");

      curdyndep->context = context;
      curdyndep->next = dyndep_list;
      dyndep_list = curdyndep;
    }
  else				// Does the name already exist?
    {
      curdep = curdyndep->depends;
      while (curdep)
	{
	  if (strcasecmp (name, curdep->name) == 0)
	    return;		// Yep, don't bother adding it.
	  curdep = curdep->next;
	}
    }
  // Name definately doesn't exist.  Add it.
  curdyndep->depends = new_dependency (name, curdyndep->depends);
}

dependency_t *
find_dyn_depends (unsigned int context)
{
  dyn_depend_t *curdep;

  curdep = dyndep_list;
  while (curdep)
    {
      if (curdep->context == context)
	return (curdep->depends);
      curdep = curdep->next;
    }
  return (NULL);
}

/****************************/
/** include file functions **/
/****************************/

void
add_include_dir (char *dirname)
{
  size_t len;

  if (!dirname)
    return;

  if (include_list)
    {
      len = strlen (dirname) + strlen (include_list) + 2;

      include_list = realloc (include_list, sizeof (char) * len);

      strcat (include_list, ":");
    }
  else
    {
      include_list = calloc (1, sizeof (char) * (strlen (dirname) + 1));
    }

  strcat (include_list, dirname);
}

/** pathfind() isn't posix, so we write a replacement. **/

FILE *
open_include_file (char *fn)
{
  char *list;
  char *token;
  char buffer[PATH_MAX];
  FILE *fp = NULL;

  list = strdup (include_list);	// We'll use strtok, which modifies the string.

  token = strtok (list, ":");
  while (token)
    {
      if (strlen (fn) + strlen (token) + 2 < PATH_MAX)
	{
	  strcpy (buffer, token);
	  if (buffer[strlen (buffer) - 1] != '/')
	    strcat (buffer, "/");
	  strcat (buffer, fn);

	  fp = fopen (buffer, "rt");
	  if (fp)
	    break;
	}
      else
	{
	  yywarning ("Include path \"%s\" too long, ignored", token);
	}
      token = strtok (NULL, ":");
    }

  free (list);

  return (fp);
}

/***************************/
/** find_policy functions **/
/***************************/

void *
find_policy_bytype (policy_t * pol, pt_t type)
{
  while (pol)
    {
      if (pol->type == type)
	return (pol->pol_data);
      pol = pol->next;
    }
  return (NULL);
}

void *
find_policy_byname (policy_t * pol, char *name)
{
  char *polname;

  while (pol)
    {
      switch (pol->type)
	{
	case PT_PROCESS:
	  polname = ((process_t *) pol->pol_data)->name;
	  break;
	case PT_PROC:
	  polname = ((proc_t *) pol->pol_data)->name;
	  break;
	case PT_NEBULOID:
	  polname = ((nebuloid_t *) pol->pol_data)->name;
	  break;
	default:
	  pol = pol->next;
	  continue;
	}
      if (strcasecmp (polname, name) == 0)
	return (pol->pol_data);
      pol = pol->next;
    }
  return (NULL);
}

/********************************************/
/** Process starting order stack functions **/
/********************************************/

typedef struct start_stack
{
  process_t *proc;

  struct start_stack *next;
}
start_stack_t;

static start_stack_t *start_head;

void
push_start_dependencies (dependency_t * dep, system_t * syst)
{
  process_t *proc;

  while (dep)
    {
      proc = find_policy_byname (syst->policy, dep->name);
      push_start_process (proc);
      push_start_dependencies (proc->depends, syst);
      dep = dep->next;
    }
}
void
push_start_process (process_t * proc)
{
  start_stack_t *node;
  start_stack_t *prevnode;

  /** First, check for duplicates **/
  node = start_head;
  prevnode = NULL;
  while (node)
    {
      if (node->proc == proc)
			    /** Is a duplicate **/
	{		    /** Move to top of stack **/
	  if (prevnode)
	    prevnode->next = node->next;
	  node->next = start_head;
	  start_head = node;
	  return;
	}
      prevnode = node;
      node = node->next;
    }

  /** Not found?  Push it. **/
  node = malloc (sizeof (start_stack_t));
  if (!node)
    yyfatalerror ("Out of memory");

  node->proc = proc;
  node->next = start_head;
  start_head = node;
}

process_t *
pop_start_process (void)
{
  start_stack_t *node;
  process_t *proc;

  if (!start_head)
    return (NULL);

  node = start_head;
  start_head = start_head->next;
  proc = node->proc;
  free (node);

  return (proc);
}
