/* This file is part of GNU Pies.
   Copyright (C) 2008-2011, 2013-2016 Sergey Poznyakoff

   GNU Pies is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   GNU Pies is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with GNU Pies.  If not, see <http://www.gnu.org/licenses/>. */

#include "pies.h"
#include <locale.h>
#include <configmake.h>
#include "meta1parse.h"
#include "grecsasrt.h"

int preprocess_only;		/* Preprocess config, do nothing more */
int lint_mode;			/* Test configuration syntax and exit */
int log_to_stderr_only;                /* Use only stderr for logging */
int log_facility = LOG_USER;
char *log_tag;
struct pies_privs pies_privs;
int foreground;
int init_process;

enum pies_command
  {
    COM_START,
    COM_RESTART_COMPONENT,
    COM_RELOAD,
    COM_STATUS,
    COM_STOP,
    COM_DUMP_DEPMAP,
    COM_TRACE_PREREQ,
    COM_TRACE_DEPEND
  };

enum pies_command command;
char *statedir = DEFAULT_STATE_DIR;
char *instance;
char *pidfile;
char *qotdfile;
int inetd_mode;
mode_t pies_umask = 0;
unsigned long shutdown_timeout = 5;
size_t default_max_rate;
pies_acl_t pies_acl;
limits_record_t pies_limits;
int force_option;
char *mailer_program = "/usr/sbin/sendmail";
char *mailer_command_line = "/usr/sbin/sendmail -oi -t";
int mailer_argc;
char **mailer_argv;

char *default_control_url[2] = {
  DEFAULT_PIES_CONTROL_URL,
  DEFAULT_INIT_CONTROL_URL
};

struct config_syntax
{
  const char *name;
  int (*parser) (char const *);
};

static int pies_config_parse (char const *);

static struct config_syntax config_syntax_tab[] = {
  [CONF_PIES]    = { "pies" , pies_config_parse },
  [CONF_META1]   = { "meta1", meta1_config_parse },
  [CONF_INETD]   = { "inetd", inetd_config_parse },
  [CONF_INITTAB] = { "inittab", inittab_parse },
};

struct config_file
{
  struct config_syntax *syntax;
  char *name;
};

struct grecs_list *config_list;

struct config_syntax *
str_to_config_syntax (const char *str)
{
  int i;

  for (i = 0; i < ARRAY_SIZE (config_syntax_tab); i++)
    if (strcmp (config_syntax_tab[i].name, str) == 0)
      return &config_syntax_tab[i];
  return NULL;
}

static void
config_file_free (void *ptr)
{
  if (ptr)
    {
      struct config_file *file = ptr;
      grecs_free (file->name);
      grecs_free (file);
    }
}

void
config_file_add (struct config_syntax *syntax, const char *name)
{
  struct config_file *file = grecs_malloc (sizeof (file[0]));
  file->syntax = syntax;
  file->name = grecs_strdup (name);
  if (!config_list)
    {
      config_list = grecs_list_create ();
      config_list->free_entry = config_file_free;
    }
  grecs_list_append (config_list, file);
}

void
config_file_add_type (enum config_syntax_type syntax, const char *name)
{
  config_file_add (&config_syntax_tab[syntax], name);
}

int
config_file_remove (const char *name)
{
  struct grecs_list_entry *ep;
  
  for (ep = config_list->head; ep; ep = ep->next)
    {
      struct config_file *file = ep->data;
      if (strcmp (file->name, name) == 0)
	{
	  grecs_list_remove_entry (config_list, ep);
	  config_file_free (file);
	  return 0;
	}
    }
  return 1;
}

void
config_file_remove_all (void)
{
  grecs_list_clear (config_list);
}

void
config_file_list_serialize (struct json_value *ar)
{
  struct grecs_list_entry *ep;
  
  for (ep = config_list->head; ep; ep = ep->next)
    {
      struct config_file *file = ep->data;
      struct json_value *obj = json_new_object ();
      json_object_set (obj, "syntax", json_new_string (file->syntax->name));
      json_object_set (obj, "file", json_new_string (file->name));
      json_array_append (ar, obj);
    }
}

/* Logging */
static int
stderr_closed_p (void)
{
  int fd = dup (0);
  if (fd < 0)
    return 1;
  close (fd);
  return fd <= 2;
}

static int
_cb_action (enum grecs_callback_command cmd,
	     grecs_locus_t *locus,
	     void *varptr, grecs_value_t *value, void *cb_data)
{
  enum return_action *pact = varptr;
  static struct tokendef actab[] = {
    {"disable", action_disable},
    {"restart", action_restart},
    {NULL}
  };
  int res;

  if (assert_scalar_stmt (locus, cmd)
      || assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
    return 1;
  if (strtotok (actab, value->v.string, &res))
    {
      grecs_error (locus, 0, _("unknown action code: %s"), value->v.string);
      return 1;
    }
  *pact = res;
  return 0;
}

struct grecs_keyword return_code_keywords[] = {
  {"action",
   /* TRANSLATORS: disable and restart are keywords, do not translate them. */
   N_("arg: {disable | restart}"),
   N_("Specifies action to take when a component finishes with this "
      "return code."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct action, act),
   _cb_action,
   },
  {"notify",
   N_("arg: emails"),
   N_("Notify this address when a component terminates."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct action, addr)
  },
  {"message",
   NULL,
   N_("Notification message text (with headers)."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct action, message),
   NULL},
  {"exec",
   NULL,
   N_("Execute this command."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct action, command),
   NULL,
   },
  {NULL}
};

#define S(s) { #s, s }
static struct tokendef ex_tokendef[] = {
  S (EX_OK),
  S (EX_USAGE),
  S (EX_DATAERR),
  S (EX_NOINPUT),
  S (EX_NOUSER),
  S (EX_NOHOST),
  S (EX_UNAVAILABLE),
  S (EX_SOFTWARE),
  S (EX_OSERR),
  S (EX_OSFILE),
  S (EX_CANTCREAT),
  S (EX_IOERR),
  S (EX_TEMPFAIL),
  S (EX_PROTOCOL),
  S (EX_NOPERM),
  S (EX_CONFIG),
  {NULL}
};

static struct tokendef sig_tokendef[] = {
  S (SIGHUP),
  S (SIGINT),
  S (SIGQUIT),
  S (SIGILL),
  S (SIGTRAP),
  S (SIGABRT),
  S (SIGIOT),
  S (SIGBUS),
  S (SIGFPE),
  S (SIGKILL),
  S (SIGUSR1),
  S (SIGSEGV),
  S (SIGUSR2),
  S (SIGPIPE),
  S (SIGALRM),
  S (SIGTERM),
#ifdef SIGSTKFLT
  S (SIGSTKFLT),
#endif
  S (SIGCHLD),
  S (SIGCONT),
  S (SIGSTOP),
  S (SIGTSTP),
  S (SIGTTIN),
  S (SIGTTOU),
#ifdef SIGURG
  S (SIGURG),
#endif
#ifdef SIGXCPU
  S (SIGXCPU),
#endif
#ifdef SIGXFSZ
  S (SIGXFSZ),
#endif
#ifdef SIGVTALRM
  S (SIGVTALRM),
#endif
#ifdef SIGPROF
  S (SIGPROF),
#endif
#ifdef SIGWINCH
  S (SIGWINCH),
#endif
#ifdef SIGPOLL
  S (SIGPOLL),
#endif
#ifdef SIGIO
  S (SIGIO),
#endif
#ifdef SIGPWR
  S (SIGPWR),
#endif
#ifdef SIGSYS
  S (SIGSYS),
#endif
  {NULL}
};

#undef S

void
action_free (struct action *act)
{
  if (!act)
    return;
  if (act->nstat > 0)
    free (act->status);
  free (act->addr);
  free (act->message);
  free (act->command);
  
  free (act);
}

static void
free_entry_action (void *act)
{
  action_free (act);
}

static struct action *
create_action (struct component *comp,
	       grecs_locus_t *locus,
	       grecs_value_t *val, int argc,
	       const char *(*getarg) (grecs_value_t *, int, grecs_locus_t *))
{
  int i;
  unsigned *retv;
  int retc = 0;
  int allflag = 0;
  struct action *act;

  retv = grecs_calloc (argc, sizeof *retv);
  if (argc == 0 || (argc == 1 && strcmp (getarg (val, 0, locus), "*") == 0))
    allflag = 1;
  else
    {
      for (i = 0; i < argc; i++)
	{
	  unsigned n;
	  const char *arg = getarg (val, i, locus);
	  size_t len = strlen (arg);
	  
	  if (isdigit (arg[0]))
	    {
	      char *p;
	      n = strtoul (arg, &p, 0);
	      if (*p)
		{
		  grecs_error (locus, 0, _("%s: not a number"), p);
		  continue;
		}
	    }
	  else if (len > 3 && memcmp (arg, "SIG", 3) == 0)
	    {
	      if (arg[4] == '+')
		{
		  char *p;
		  n = strtoul (arg + 4, &p, 0);
		  if (*p)
		    {
		      grecs_error (locus, 0, _("%s: not a number"), p);
		      continue;
		    }
		}
	      else if (strtotok_ci (sig_tokendef, arg, (int*) &n))
		{
		  grecs_error (locus, 0, _("%s: not a signal code"), arg);
		  continue;
		}
	      n |= STATUS_SIG_BIT;
	    }
	  else if (strtotok_ci (ex_tokendef, arg, (int *) &n))
	    {
	      grecs_error (locus, 0, _("%s: not a return code"), arg);
	      continue;
	    }
	  
	  /* Alles in ordnung */
	  retv[retc++] = n;
	}
    }
  
  if (retc == 0 && !allflag)
    {
      free (retv);
      return NULL;
    }

  act = grecs_zalloc (sizeof *act);
  if (!allflag)
    {
      act->nstat = retc;
      act->status = retv;
    }
  if (!comp->act_list)
    {
      comp->act_list = grecs_list_create ();
      comp->act_list->free_entry = free_entry_action;
    }
  grecs_list_append (comp->act_list, act);
  return act;
}

const char *
_get_string_arg (grecs_value_t *val, int num, grecs_locus_t *locus)
{
  if (num != 0)
    return NULL;
  return val->v.string;
}

const char *
_get_array_arg (grecs_value_t *val, int num, grecs_locus_t *locus)
{
  if (num < val->v.arg.c)
    {
      if (assert_grecs_value_type (locus, val->v.arg.v[num],
				   GRECS_TYPE_STRING) == 0)
	return val->v.arg.v[num]->v.string;
    }
  return NULL;
}

const char *
_get_list_arg (grecs_value_t *val, int num, grecs_locus_t *locus)
{
  grecs_value_t *elt = (grecs_value_t *) grecs_list_index (val->v.list, num);
  if (!elt)
    {
      grecs_error (locus, 0, _("cannot get list item"));
    }
  else if (assert_grecs_value_type (locus, elt, GRECS_TYPE_STRING) == 0)
    return elt->v.string;
  return NULL;
}

static int
return_code_section_parser (enum grecs_callback_command cmd,
			     grecs_locus_t *locus,
			     void *varptr,
			     grecs_value_t *value, void *cb_data)
{
  struct component *comp = varptr;
  size_t count;
  struct action *act;

  switch (cmd)
    {
    case grecs_callback_section_begin:
      if (GRECS_VALUE_EMPTY_P (value))
	{
	  grecs_error (locus, 0, _("missing tag"));
	  return 1;
	}
      
      switch (value->type)
	{
	case GRECS_TYPE_STRING:
	  act = create_action (comp, locus, value, 1, _get_string_arg);
	  break;
	  
	case GRECS_TYPE_ARRAY:
	  act = create_action (comp, locus, value,
			       value->v.arg.c, _get_array_arg);
	  break;
	  
	case GRECS_TYPE_LIST:
	  count = grecs_list_size (value->v.list);
	  act = create_action (comp, locus, value, count, _get_list_arg);
	}
      if (!act)
	return 1;
      *(struct action **) cb_data = act;
      break;
      
    case grecs_callback_section_end:
      break;

    case grecs_callback_set_value:
      grecs_error (locus, 0, _("invalid use of block statement"));
    }
  return 0;
}

static char **
config_array_to_argv (grecs_value_t *val, grecs_locus_t *locus, size_t *pargc)
{
  int i, j;
  int argc;
  char **argv;

  argc = val->v.arg.c;
  argv = grecs_calloc (argc + 1, sizeof (argv[0]));
  for (i = j = 0; i < argc; i++)
    {
      if (assert_grecs_value_type (locus, val->v.arg.v[i], GRECS_TYPE_STRING)
	  == 0)
	argv[j++] = grecs_strdup (val->v.arg.v[i]->v.string);
    }
  argv[j] = NULL;
  if (pargc)
    *pargc = argc;
  return argv;
}

static int
_cb_command (enum grecs_callback_command cmd,
	      grecs_locus_t *locus,
	      void *varptr, grecs_value_t *value, void *cb_data)
{
  struct component *comp = varptr;
  struct wordsplit ws;

  switch (value->type)
    {
    case GRECS_TYPE_STRING:
      if (wordsplit (value->v.string, &ws, WRDSF_DEFFLAGS))
	{
	  grecs_error (locus, 0, "wordsplit: %s", strerror (errno));
	  return 1;
	}
      wordsplit_getwords (&ws, &comp->argc, &comp->argv);
      wordsplit_free (&ws);
      break;
      
    case GRECS_TYPE_ARRAY:
      comp->argv = config_array_to_argv (value, locus, &comp->argc);
      break;

    case GRECS_TYPE_LIST:
      grecs_error (locus, 0, _("unexpected list"));
      return 1;
    }
  return 0;
}

static int
_cb_umask (enum grecs_callback_command cmd,
	   grecs_locus_t *locus,
	   void *varptr, grecs_value_t *value, void *cb_data)
{
  mode_t *pmode = varptr;
  char *p;
  unsigned long n;

  if (assert_scalar_stmt (locus, cmd)
      || assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
    return 1;
  n = strtoul (value->v.string, &p, 8);
  if (*p)
    {
      grecs_error (locus, 0, _("invalid octal number"));
      return 1;
    }
  *pmode = n;
  return 0;
}

static int
_cb_env (enum grecs_callback_command cmd,
	  grecs_locus_t *locus,
	  void *varptr, grecs_value_t *value, void *cb_data)
{
  size_t argc;
  char **argv;
  char ***penv = varptr;
  struct wordsplit ws;

  switch (value->type)
    {
    case GRECS_TYPE_STRING:
      if (wordsplit (value->v.string, &ws, WRDSF_DEFFLAGS))
	{
	  grecs_error (locus, 0, "wordsplit: %s", strerror (errno));
	  return 1;
	}
      wordsplit_getwords (&ws, &argc, &argv);
      wordsplit_free (&ws);
      break;

    case GRECS_TYPE_ARRAY:
      argv = config_array_to_argv (value, locus, NULL);
      break;

    case GRECS_TYPE_LIST:
      grecs_error (locus, 0, _("unexpected list"));
      return 1;
    }

  *penv = argv;
  return 0;
}


int
string_to_syslog_priority (const char *key, int *pres)
{
  static struct tokendef tokdef_prio[] = {
    {"EMERG", LOG_EMERG},
    {"ALERT", LOG_ALERT},
    {"CRIT", LOG_CRIT},
    {"ERR", LOG_ERR},
    {"WARNING", LOG_WARNING},
    {"NOTICE", LOG_NOTICE},
    {"INFO", LOG_INFO},
    {"DEBUG", LOG_DEBUG},
    {NULL}
  };

  return strtotok_ci (tokdef_prio, key, pres);
}

int
string_to_syslog_facility (const char *key, int *pres)
{
  static struct tokendef tokdef_fac[] = {
    {"auth", LOG_AUTH},
#ifdef LOG_AUTHPRIV
    {"authpriv", LOG_AUTHPRIV},
#endif
    {"cron", LOG_CRON},
    {"daemon", LOG_DAEMON},
#ifdef LOG_FTP
    {"ftp", LOG_FTP},
#endif
    {"kern", LOG_KERN},
    {"lpr", LOG_LPR},
    {"mail", LOG_MAIL},
    {"news", LOG_NEWS},
    {"syslog", LOG_SYSLOG},
    {"user", LOG_USER},
    {"uucp", LOG_UUCP},
    {"local0", LOG_LOCAL0},
    {"local1", LOG_LOCAL1},
    {"local2", LOG_LOCAL2},
    {"local3", LOG_LOCAL3},
    {"local4", LOG_LOCAL4},
    {"local5", LOG_LOCAL5},
    {"local6", LOG_LOCAL6},
    {"local7", LOG_LOCAL7},
    {NULL}
  };

  return strtotok_ci (tokdef_fac, key, pres);
}

static int
cb_syslog_facility (enum grecs_callback_command cmd,
		     grecs_locus_t *locus,
		     void *varptr, grecs_value_t *value, void *cb_data)
{
  const char *str;

  if (assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
    return 1;
  str = value->v.string;
  if (c_isdigit (str[0]))
    {
      char *p;
      int n = strtoul (str, &p, 10);
      if (*p)
	grecs_error (locus, 0,
		     _("expected facility number or symbolic name"));
      else
	*(int *) varptr = n;
    }
  else if (string_to_syslog_facility (str, varptr))
    grecs_error (locus, 0, _("unknown syslog facility %s"), str);
  return 0;
}

static int
_cb_redir (enum grecs_callback_command cmd,
	    grecs_locus_t *locus,
	    void *varptr, grecs_value_t *value, void *cb_data)
{
  struct redirector *rp = varptr;
  static struct tokendef redirtab[] = {
    {"null", redir_null},
    {"syslog", redir_syslog},
    {"file", redir_file},
    {NULL}
  };
  int res;
  
  switch (value->type)
    {
    case GRECS_TYPE_STRING:
      if (strcmp (value->v.string, "null") == 0)
	{
	  rp->type = redir_null;
	  break;
	}
      rp->type = redir_syslog;
      if (string_to_syslog_priority (value->v.string, &rp->v.prio))
	{
	  grecs_error (locus, 0, _("unknown syslog priority %s"),
		       value->v.string);
	  return 0;
	}
      break;
      
    case GRECS_TYPE_ARRAY:
      if (assert_grecs_value_type (locus, value->v.arg.v[0],
				   GRECS_TYPE_STRING))
	return 0;
      if (strtotok (redirtab, value->v.arg.v[0]->v.string, &res))
	grecs_error (locus, 0, _("%s: unrecognised redirector type"),
		     value->v.arg.v[0]->v.string);
      else
	{
	  if (res != redir_null)
	    {
	      if (value->v.arg.c != 2)
		{
		  grecs_error (locus, 0, _("wrong number of arguments"));
		  return 0;
		}
	      if (assert_grecs_value_type (locus, value->v.arg.v[1],
					   GRECS_TYPE_STRING))
		return 0;
	      
	      switch (res)
		{
		case redir_null:
		  break;
		  
		case redir_syslog:
		  if (string_to_syslog_priority (value->v.arg.v[1]->v.string,
						 &rp->v.prio))
		    {
		      grecs_error (locus, 0,
				   _("unknown syslog priority %s"),
				   value->v.arg.v[1]->v.string);
		      return 0;
		    }
		  break;
		  
		case redir_file:
		  rp->v.file = grecs_strdup (value->v.arg.v[1]->v.string);
		  break;
		}
	    }
	  rp->type = res;
	}
      break;
      
    default:
      grecs_error (locus, 0, _("unexpected list"));
    }
  
  return 0;
}

static struct tokendef socktype_xtab[] = {
  { "stream",     SOCK_STREAM },   
  { "dgram",      SOCK_DGRAM },    
  { "seqpacket",  SOCK_SEQPACKET },
  { "raw",        SOCK_RAW },
  { "rdm",        SOCK_RDM },
#ifdef SOCK_PACKET
  { "packet",     SOCK_PACKET },
#endif
  { NULL }
};

int
str_to_socket_type (const char *str, int *pres)
{
  return strtotok (socktype_xtab, str, pres);
}

int
socket_type_to_str (int socket_type, const char **pres)
{
  return toktostr (socktype_xtab, socket_type, pres);
}

static int
_cb_socket_type (enum grecs_callback_command cmd,
		  grecs_locus_t *locus,
		  void *varptr, grecs_value_t *value, void *cb_data)
{
  if (assert_scalar_stmt (locus, cmd)
      || assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
    return 1;

  if (str_to_socket_type (value->v.string, varptr))
    grecs_error (locus, 0, _("bad socket type"));
  return 0;
}

static struct tokendef modetab[] = {
  {"exec", pies_comp_exec},
  {"respawn", pies_comp_exec},
  {"wait", pies_comp_wait},
  {"once", pies_comp_once},
  {"accept", pies_comp_accept},
  {"inetd", pies_comp_inetd},
  {"nostartaccept", pies_comp_inetd},
  {"pass-fd", pies_comp_pass_fd},
  {"pass", pies_comp_pass_fd},
  {"boot", pies_comp_boot},
  {"bootwait", pies_comp_boot},
  {"powerfail", pies_comp_powerfail},
  {"powerwait", pies_comp_powerwait},
  {"powerokwait", pies_comp_powerokwait},
  {"ctrlaltdel", pies_comp_ctrlaltdel},	
  {"ondemand", pies_comp_ondemand},
  {"sysinit", pies_comp_sysinit},
  {"powerfailnow", pies_comp_powerfailnow},
  {"kbrequest", pies_comp_kbrequest},

  {NULL}
};

static int
_cb_mode (enum grecs_callback_command cmd,
	   grecs_locus_t *locus,
	   void *varptr, grecs_value_t *value, void *cb_data)
{
  int res;

  if (assert_scalar_stmt (locus, cmd)
      || assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
    return 1;
  if (strtotok (modetab, value->v.string, &res))
    grecs_error (locus, 0, _("%s: unrecognised mode"), value->v.string);
  else
    *(enum pies_comp_mode *) varptr = res;
  return 0;
}

static int
_cb_limits (enum grecs_callback_command cmd,
	     grecs_locus_t *locus,
	     void *varptr, grecs_value_t *value, void *cb_data)
{
  limits_record_t *plrec = varptr;
  char *p;

  if (assert_scalar_stmt (locus, cmd)
      || assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
    return 1;
  if (parse_limits (plrec, (char *) value->v.string, &p))
    grecs_error (locus, 0, _("invalid limit string (near %s)"), p);
  return 0;
}

int
str_to_cf (const char *string, int *flags)
{
  size_t len = strlen (string);
  int neg = 0;
  int mask;

  static struct tokendef cf_tab[] = {
    { "disable", CF_DISABLED },
    { "precious", CF_PRECIOUS },
    { "wait", CF_WAIT },
    { "tcpmux", CF_TCPMUX },
    { "tcpmuxplus", CF_TCPMUXPLUS },
    { "internal", CF_INTERNAL },
    { "sockenv", CF_SOCKENV },
    { "resolve", CF_RESOLVE },
    { "siggroup", CF_SIGGROUP },
    { "nullinput", CF_NULLINPUT },
    { NULL }
  };

  if (len > 2 && memcmp (string, "no", 2) == 0)
    {
      neg++;
      string += 2;
    }

  if (strtotok (cf_tab, string, &mask))
    return 1;

  if (neg)
    *flags &= ~mask;
  else
    *flags |= mask;
  return 0;
}

static int
_cb_flags (enum grecs_callback_command cmd,
	    grecs_locus_t *locus,
	    void *varptr, grecs_value_t *value, void *cb_data)
{
  int *flags = varptr;

  switch (value->type)
    {
    case GRECS_TYPE_STRING:
      if (str_to_cf (value->v.string, flags))
	{
	  grecs_error (locus, 0, _("%s: unrecognised flag"), value->v.string);
	  return 1;
	}
      break;
      
    case GRECS_TYPE_LIST:
      {
	struct grecs_list_entry *ep;
	
	for (ep = value->v.list->head; ep; ep = ep->next)
	  {
	    const grecs_value_t *vp = ep->data;
	    if (assert_grecs_value_type (locus, vp, GRECS_TYPE_STRING))
	      return 1;
	    if (str_to_cf (vp->v.string, flags))
	      {
		grecs_error (locus, 0, _("%s: unrecognised flag"),
			     vp->v.string);
		return 1;
	      }
	  }
      }
      break;
      
    case GRECS_TYPE_ARRAY:
      grecs_error (locus, 0, _("too many arguments"));
      return 1;
    }
  return 0;
}

static int
_cb_initdefault (enum grecs_callback_command cmd,
		  grecs_locus_t *locus,
		  void *varptr, grecs_value_t *value, void *cb_data)
{
  int *val = varptr;

  if (assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
    return 1;
  if (strlen (value->v.string) != 1)
    {
      grecs_error (locus, 0, _("argument must be a single character"));
      return 1;
    }
  if (!is_valid_runlevel (value->v.string[0]))
    {
      grecs_error (locus, 0, _("not a valid runlevel"));
      return 1;
    }
  *val = toupper (value->v.string[0]);
  return 0;
}

static int
_cb_runlevels (enum grecs_callback_command cmd,
	       grecs_locus_t *locus,
	       void *varptr, grecs_value_t *value, void *cb_data)
{
  char **sptr = varptr, *p;

  if (assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
    return 1;
  for (p = value->v.string; *p; p++)
    {
      if (!is_valid_runlevel (*p))
	{
	  grecs_error (locus, 0, _("not a valid runlevel: %c"), *p);
	  return 1;
	}
    }
  *sptr = grecs_strdup (value->v.string);
  for (p = *sptr; *p; p++)
    *p = toupper (*p);
  return 0;
}

struct grecs_keyword component_keywords[] = {
  {"mode",
   N_("mode"),
   N_("Component execution mode."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, mode),
   _cb_mode,
   },
  {"program",
   NULL,
   N_("Full name of the program."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, program),
   NULL,
   },
  {"command",
   NULL,
   N_("Command line."),
   grecs_type_string, GRECS_DFLT,
   NULL, 0,
   _cb_command,
   },
  {"prerequisites",
   N_("list"),
   N_("List of prerequisites."),
   grecs_type_string, GRECS_LIST, NULL, offsetof (struct component, prereq),
   NULL,
   },
  {"dependents",
   N_("list"),
   N_("List of components for which this one is a prerequisite."),
   grecs_type_string, GRECS_LIST, NULL, offsetof (struct component, depend),
   NULL,
   },
  {"flags",
   N_("list"),
   N_("List of flags."),
   grecs_type_string, GRECS_LIST, NULL, offsetof (struct component, flags),
   _cb_flags },
  {"runlevels",
   N_("chars"),
   N_("Runlevels to start that component in."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, runlevels),
   _cb_runlevels },
  {"pass-fd-timeout",
   NULL,
   N_("Time to wait for pass-fd socket to become available."),
   grecs_type_uint, GRECS_DFLT,
   NULL, offsetof (struct component, pass_fd_timeout),
   NULL,
   },
  {"max-instances",
   NULL,
   N_("Maximum number of running instances."),
   grecs_type_size, GRECS_DFLT,
   NULL, offsetof (struct component, max_instances),
   NULL },
  {"max-instances-message",
   NULL,
   N_("Text to send back if max-instances is reached (inetd-components only)."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, max_instances_message),
   NULL },
  {"max-ip-connections",
   NULL,
   N_("Maximum number of simultaneous connections per IP address (inetd only)."),
   grecs_type_size, GRECS_DFLT,
   NULL, offsetof (struct component, max_ip_connections),
   NULL },
  {"max-ip-connections-message",
   NULL,
   N_("Text to send back if max-ip-connections is reached (inetd only)."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, max_ip_connections_message),
   NULL },

  {"max-rate",
   NULL,
   N_("Maximum number of times an inetd component can be invoked in one minute."),
   grecs_type_size, GRECS_DFLT,
   NULL, offsetof (struct component, max_rate),
   NULL },
  {"socket",
   N_("url: string"),
   N_("Listen on the given url."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, socket_url),
   conf_callback_url,
   },
  {"socket-type",
   /* TRANSLATORS: words after `type:' are keywords. */
   N_("type: {stream | dgram | raw | rdm | seqpacket}"),
   N_("Set socket type."),
   grecs_type_int, GRECS_DFLT,
   NULL, offsetof (struct component, socket_type),
   _cb_socket_type },
  {"pass-fd-socket",
   N_("name"),
   N_("Pass fd through this socket."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, pass_fd_socket),
   NULL,
   },
  {"acl",
   NULL,
   N_("Define connection ACL."),
   grecs_type_section, GRECS_DFLT,
   NULL, offsetof (struct component, acl),
   acl_section_parser, NULL, acl_keywords},
  {"list-acl",
   NULL,
   N_("Define who can list this component."),
   grecs_type_section, GRECS_DFLT,
   NULL, offsetof (struct component, list_acl),
   acl_section_parser, NULL, acl_keywords},
  {"admin-acl",
   NULL,
   N_("Define who can change this component."),
   grecs_type_section, GRECS_DFLT,
   NULL, offsetof (struct component, adm_acl),
   acl_section_parser, NULL, acl_keywords},
  {"access-denied-message",
   NULL,
   N_("Text to send back if access is denied (inetd-components only)."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, access_denied_message),
   NULL },
  {"remove-file",
   N_("file"),
   N_("Remove file before starting the component."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, rmfile),
   NULL,
   },
  {"facility",
   N_("arg"),
   N_("Override default syslog facility for this component."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, facility),
   cb_syslog_facility,
   },
  {"stdout",
   /* TRANSLATORS: file and syslog are keywords. Do not translate them. */
   N_("type: {file | syslog}> <channel: string"),
   N_("Redirect program's standard output to the given file or "
      "syslog priority."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, redir[RETR_OUT]),
   _cb_redir,
   },
  {"stderr",
   /* TRANSLATORS: file and syslog are keywords. Do not translate them. */
   N_("type: {file | syslog}> <channel: string"),
   N_("Redirect program's standard error to the given file or "
      "syslog priority."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, redir[RETR_ERR]),
   _cb_redir,
   },
  {"user",
   NULL,
   N_("Run with this user privileges."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, privs.user),
   NULL,
   },
  {"group",
   NULL,
   N_("Retain supplementary group."),
   grecs_type_string, GRECS_LIST,
   NULL, offsetof (struct component, privs.groups),
   NULL,
   },
  {"allgroups",
   NULL,
   N_("Retain all supplementary groups of which user is a member."),
   grecs_type_bool, GRECS_DFLT,
   NULL, offsetof (struct component, privs.allgroups),
   NULL,
   },
  {"umask",
   N_("arg: number"),
   N_("Force this umask."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, umask),
   _cb_umask,
   },
  {"limits",
   NULL,
   N_("Set system limits"),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, limits),
   _cb_limits,
   },
  {"env",
   N_("arg: list"),
   N_("Set program environment.  Argument is a list of assignments "
      "separated by white space."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, env),
   _cb_env,
   },
  {"chdir",
   N_("dir"),
   N_("Change to this directory before executing the component."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, dir),
   NULL,
   },
  {"return-code",
   N_("tag: exit-code-list"),
   N_("Define what to do when the component finishes."),
   grecs_type_section, GRECS_DFLT,
   NULL, 0,
   return_code_section_parser, NULL, return_code_keywords},
  {"service",
   N_("name"),
   N_("Service name for inetd component."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, service),
   NULL },
  {"tcpmux-master",
   N_("tag"),
   N_("Tag of master TCPMUX component."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, tcpmux),
   NULL },
  {NULL}
};

struct grecs_keyword *
find_component_keyword (const char *ident)
{
  struct grecs_keyword *kwp;
  
  for (kwp = component_keywords; kwp->ident; kwp++)
    if (strcmp (kwp->ident, ident) == 0)
      return kwp;
  return NULL;
}

static int
component_section_parser (enum grecs_callback_command cmd,
			  grecs_locus_t *locus,
			  void *varptr, grecs_value_t *value, void *cb_data)
{
  struct component *comp;
  void **section_data = cb_data;

  switch (cmd)
    {
    case grecs_callback_section_begin:
      if (assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
	return 1;
      comp = component_create (value->v.string);
      *section_data = comp;
      break;

    case grecs_callback_section_end:
      comp = *(struct component **) section_data;
      component_finish (comp, locus);
      break;
      
    case grecs_callback_set_value:
      grecs_error (locus, 0, _("expected block statement"));
    }
  return 0;
}

struct grecs_keyword control_keywords[] = {
  {"socket",
   N_("url: string"),
   N_("Listen on the given url."),
   grecs_type_string, GRECS_DFLT,
   &control.url, 0, conf_callback_url},
  {"acl",
   NULL,
   N_("Set connection ACL."),
   grecs_type_section, GRECS_DFLT,
   &control.conn_acl, 0,
   acl_section_parser, NULL, acl_keywords},
  {"admin-acl",
   NULL,
   N_("Administrative access"),
   grecs_type_section, GRECS_DFLT,
   &control.adm_acl, 0,
   acl_section_parser, NULL, acl_keywords},
  {"user-acl",
   NULL,
   N_("User access"),
   grecs_type_section, GRECS_DFLT,
   &control.usr_acl, 0,
   acl_section_parser, NULL, acl_keywords},
  {"idle-timeout",
   "n",
   N_("Disconnect after <n> seconds of inaction (not implemented)."),
   grecs_type_uint, GRECS_DFLT,
   &control.idle_timeout, 0,
   NULL,
   },
  {"realm",
   N_("name"),
   N_("Authentication realm name"),
   grecs_type_string, GRECS_CONST,
   &control.realm, 0,
   NULL,
  },
  { NULL }
};

/* syslog */
static struct grecs_keyword syslog_kw[] = {
  {"facility",
   N_("name"),
   N_("Set syslog facility. Arg is one of the following: user, daemon, "
      "auth, authpriv, mail, cron, local0 through local7 (case-insensitive), "
      "or a facility number."),
   grecs_type_string, GRECS_DFLT,
   &log_facility, 0, cb_syslog_facility},
  {"tag", N_("string"), N_("Tag syslog messages with this string"),
   grecs_type_string, GRECS_DFLT,
   &log_tag},
#if 0
  /* This is reserved for future use */
  {
    "print-priority", N_("arg"), N_("Prefix each message with its priority"),
    grecs_type_bool, GRECS_DFLT,
    &syslog_include_prio},
#endif
  {NULL},
};


struct component default_component;

static int
_cb_include_meta1 (enum grecs_callback_command cmd,
		   grecs_locus_t *locus,
		   void *varptr, grecs_value_t *value, void *cb_data)
{
  if (assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
    return 1;
  meta1_config_parse (value->v.string);
  return 0;
}

static int
_cb_include_inetd (enum grecs_callback_command cmd,
		   grecs_locus_t *locus,
		   void *varptr, grecs_value_t *value, void *cb_data)
{
  if (assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
    return 1;
  return inetd_config_parse (value->v.string);
}

struct grecs_keyword pies_keywords[] = {
  {"component",
   N_("tag: string"),
   N_("Define a component"),
   grecs_type_section, GRECS_DFLT,
   NULL, 0,
   component_section_parser, NULL, component_keywords},
  {"control",
   NULL,
   N_("Define control socket"),
   grecs_type_section, GRECS_DFLT,
   NULL, 0,
   NULL, NULL, control_keywords },
  {"syslog",
   NULL,
   N_("Configure syslog logging"),
   grecs_type_section, GRECS_DFLT,
   NULL, 0, NULL, NULL, syslog_kw},
  {"debug",
   NULL,
   N_("Set debug verbosity level."),
   grecs_type_uint, GRECS_DFLT,
   &debug_level, 0, NULL},
  {"source-info",
   NULL,
   N_("Show source info with debugging messages."),
   grecs_type_bool, GRECS_DFLT,
   &source_info_option, 0, NULL},
  {"state-directory",
   NULL,
   N_("Full file name of the program state directory."),
   grecs_type_string, GRECS_CONST,
   &statedir, 0, NULL},
  {"pidfile",
   NULL,
   N_("Write PID to this file."),
   grecs_type_string, GRECS_CONST,
   &pidfile, 0,
   NULL,
   },
  {"control-file",
   NULL,
   N_("Ignored for compatibility with version 1.2."),
   grecs_type_string, GRECS_DFLT|GRECS_INAC,
   NULL, 0,
   NULL,
   },
  {"stat-file",
   NULL,
   N_("Ignored for compatibility with version 1.2."),
   grecs_type_string, GRECS_DFLT|GRECS_INAC,
   NULL, 0,
   NULL,
   },
  {"qotd-file",
   NULL,
   N_("Set location of the QOTD file."),
   grecs_type_string, GRECS_CONST,
   &qotdfile, 0,
   NULL },
  {"user",
   NULL,
   N_("Run with this user privileges."),
   grecs_type_string, GRECS_CONST,
   &pies_privs.user, 0,
   NULL,
   },
  {"group",
   NULL,
   N_("Retain supplementary group."),
   grecs_type_string, GRECS_LIST,
   &pies_privs.groups, 0,
   NULL,
   },
  {"allgroups",
   NULL,
   N_("Retain all supplementary groups of which user is a member."),
   grecs_type_bool, GRECS_DFLT,
   &pies_privs.allgroups, 0,
   NULL,
   },
  {"umask",
   N_("arg: number"),
   N_("Force this umask."),
   grecs_type_string, GRECS_DFLT,
   &pies_umask, 0,
   _cb_umask,
   },
  {"limits",
   NULL,
   N_("Set global system limits."),
   grecs_type_string, GRECS_DFLT,
   &pies_limits, 0, _cb_limits,
   },
  {"initdefault",
   N_("arg: char"),
   N_("Default runlevel"),
   grecs_type_string, GRECS_DFLT,
   &initdefault, 0, _cb_initdefault,
  },
  {"shutdown-timeout",
   "n",
   N_("Wait <n> seconds for all components to shut down."),
   grecs_type_uint, GRECS_DFLT,
   &shutdown_timeout, 0,
   NULL,
   },
  {"return-code",
   N_("tag: exit-code-list"),
   N_("Define what to do when the component finishes."),
   grecs_type_section, GRECS_DFLT,
   &default_component, 0,
   return_code_section_parser, NULL, return_code_keywords},
  {"acl",
   NULL,
   N_("Set global ACL."),
   grecs_type_section, GRECS_DFLT,
   &pies_acl, 0,
   acl_section_parser, NULL, acl_keywords},
  {"defacl",
   N_("name: string"),
   N_("Define an ACL."),
   grecs_type_section, GRECS_DFLT,
   NULL, 0,
   defacl_section_parser, NULL, acl_keywords},
  {"include-inetd",
   N_("file-or-dir: string"),
   N_("Include inetd configuration file or directory"),
   grecs_type_string, GRECS_DFLT,
   NULL, 0,
   _cb_include_inetd },
  {"include-meta1",
   N_("file: string"),
   N_("Include components from the specified MeTA1 configuration file."),
   grecs_type_string, GRECS_DFLT,
   NULL, 0,
   _cb_include_meta1,
   },
  {"meta1-queue-dir",
   NULL,
   N_("Set the name of MeTA1 queue directory (default /var/spool/meta1)."),
   grecs_type_string, GRECS_CONST,
   &meta1_queue_dir, 0,
   NULL,
   },
  {"mailer-program",
   NULL,
   N_("Full path to the mailer binary."),
   grecs_type_string, GRECS_CONST,
   &mailer_program, 0,
   NULL
  },
  {"mailer-command-line",
   NULL,
   N_("Mailer command line (without recipient addresses)."),
   grecs_type_string, GRECS_CONST,
   &mailer_command_line, 0,
   NULL
  },
  { "identity-provider", "name: string", "Configure identity provider",
    grecs_type_section, GRECS_INAC | GRECS_HIDDEN },
  {NULL}
};

void
config_init (void)
{
  grecs_include_path_setup (DEFAULT_VERSION_INCLUDE_DIR,
			    DEFAULT_INCLUDE_DIR, NULL);
  grecs_log_to_stderr = log_to_stderr_only;
  pies_identity_mechanism_register (&system_identity_mechanism);
#ifdef WITH_PAM
  pies_identity_mechanism_register (&pam_identity_mechanism);
#endif
}

int
pies_config_parse (char const *name)
{
  struct grecs_node *node;
  struct grecs_node *tree = grecs_parse (name);

  if (!tree)
    return 1;
  
  for (node = tree; node; node = node->next)
    {
      node = grecs_find_node (node, "identity-provider");
      if (!node)
	break;
      pies_config_provider (node);
    }

  if (grecs_tree_process (tree, pies_keywords))
    return 1;

  grecs_tree_free (tree);

  if (grecs_error_count)
    return 1;
  
  return 0;
}

void
config_help (void)
{
  static char docstring[] =
    /* TRANSLATORS: do not translate words in quotes */
    N_("Configuration file structure for pies.\n"
       "For more information, use command \"info pies configuration\".");
  grecs_print_docstring (docstring, 0, stdout);
  grecs_print_statement_array (pies_keywords, 1, 0, stdout);
  pies_config_identity_mechanisms_help ();
}

int
pies_read_config (void)
{
  struct grecs_list_entry *ep;
  int err = 0;

  component_config_begin ();
  
  for (ep = config_list->head; ep; ep = ep->next)
    {
      struct config_file *file = ep->data;
      if (file->syntax->parser (file->name))
	++err;
    }

  if (init_process)
    err = 0;
  
  if (err)
    component_config_rollback ();
  
  return err;
}

int
pies_reread_config (void)
{
  logmsg (LOG_INFO, _("reading configuration"));
  return pies_read_config ();
}

static struct config_syntax *current_syntax = &config_syntax_tab[CONF_PIES];

#include "cmdline.h"


static int action = ACTION_CONT;
static int children_op = PIES_CHLD_NONE;

void
pies_schedule_action (int act)
{
  action = act;
}

void
pies_schedule_children (int op)
{
  children_op |= op;
}

RETSIGTYPE
sig_handler (int sig)
{
  if (init_process && sysvinit_sigtrans (sig, &action))
    return;
  switch (sig)
    {
    case SIGCHLD:
      pies_schedule_children (PIES_CHLD_CLEANUP);
      break;

    case SIGINT:
    case SIGTERM:
    case SIGQUIT:
      logmsg (LOG_NOTICE, "received signal %d", sig);
      pies_schedule_action (ACTION_STOP);
      break;

    case SIGHUP:
      logmsg (LOG_NOTICE, "received signal %d", sig);
      pies_schedule_action (ACTION_RELOAD);
      break;

    case SIGUSR1:
      logmsg (LOG_NOTICE, "received signal %d", sig);
      pies_schedule_action (ACTION_RESTART);
      break;

    case SIGALRM:
      pies_schedule_children (PIES_CHLD_WAKEUP);
      break;
    }
}

void
setsigvhan (RETSIGTYPE (*handler) (int signo), int *sigv, int sigc)
{
  int i;
  struct sigaction act;
  
  act.sa_flags = 0;
  sigemptyset (&act.sa_mask);
  for (i = 0; i < sigc; i++)
    sigaddset (&act.sa_mask, i);

  for (i = 0; i < sigc; i++)
    {
      act.sa_handler = handler;
      sigaction (sigv[i], &act, NULL);
    }
}

#define PIES_MAXSIG 16
static int default_sigv[] = {
  SIGCHLD,
  SIGTERM,
  SIGQUIT,
  SIGINT,
  SIGHUP,
  SIGUSR1,
  SIGALRM,
  SIGPIPE
};

static int extra_sigv[PIES_MAXSIG];
static int extra_sigc;

void
add_extra_sigv (int *sigv, int sigc)
{
  while (sigc--)
    {
      if (extra_sigc == ARRAY_SIZE(extra_sigv))
	abort ();
      extra_sigv[extra_sigc++] = *sigv++;
    }
}

void
signal_setup (RETSIGTYPE (*sf) (int))
{
  setsigvhan (sf, default_sigv, ARRAY_SIZE (default_sigv));
  if (extra_sigc)
    setsigvhan (sf, extra_sigv, extra_sigc);
}


pid_t
pidfile_read (int must_exist)
{
  int c;
  pid_t n = 0;
  FILE *fp = fopen (pidfile, "r");
  if (!fp)
    {
      if (must_exist && errno != ENOENT)
	logmsg (LOG_ERR, _("cannot open file %s: %s"),
		pidfile, strerror (errno));
      return -1;
    }

  while ((c = fgetc (fp)) != EOF)
    {
      if (isdigit (c))
	n = n * 10 + c - '0';
      else if (c == '\n')
	break;
      else
	{
	  logmsg (LOG_ERR,
		  _("unexpected character %#03o in pidfile %s"),
		  c, pidfile);
	  return -1;
	}
    }
  fclose (fp);
  if (n && kill (n, 0))
    {
      if (errno != ESRCH)
	logmsg (LOG_ERR,
		_("cannot signal master process %lu: %s"),
		(unsigned long) n, strerror (errno));
      if (errno == EPERM)
	return n;		/* be on the safe side */
      return -1;
    }
  return n;
}


enum pies_status
{
  pies_status_ctr,		/* clear to run */
  pies_status_stale,
  pies_status_noresp,
  pies_status_running
};

//FIXME: If telinit?
enum pies_status
pies_check_status (pid_t *ppid)
{
  pid_t pid = pidfile_read (0);

  if (pid <= 0)
    return pies_status_ctr;

  *ppid = pid;

  if (kill (pid, 0))
    return pies_status_stale;

  return pies_status_running;
}

#define pies_control_url() control.url->string

int
request_restart_components (size_t cc, char **cv)
{
  char **argv;
  size_t i, j;
  
  argv = grecs_calloc (5 + 3 * cc - 1, sizeof (*argv));
  argv[0] = "piesctl";
  argv[1] = "--url";
  argv[2] = (char*) pies_control_url ();
  argv[3] = "restart";
  j = 4;
  for (i = 0; i < cc; i++)
    {
      if (i > 0)
	argv[j++] = "or";
      argv[j++] = "component";
      argv[j++] = cv[i];
    }
  argv[j] = NULL;
  execvp (argv[0], argv);
  logmsg (LOG_ERR, "can't run piesctl: %s", strerror (errno));
  return EX_OSFILE;
}

void
list_components (void)
{
  char *argv[5];
  
  argv[0] = "piesctl";
  argv[1] = "--url";
  argv[2] = (char*) pies_control_url ();
  argv[3] = "list";
  argv[4] = NULL;
  execvp (argv[0], argv);
  logmsg (LOG_ERR, "can't run piesctl: %s", strerror (errno));
  exit (EX_OSFILE);
}


int
request_reload (void)
{
  pid_t pid = pidfile_read (1);

  if (pid == -1)
    {
      logmsg (LOG_CRIT, _("pies is not running"));
      return 1;
    }

  logmsg (LOG_INFO, _("reloading pies at PID %lu"), (unsigned long) pid);
  return kill (pid, SIGHUP) ? EX_SOFTWARE : 0;
}

int
request_status (void)
{
  pid_t pid;

  switch (pies_check_status (&pid))
    {
    case pies_status_ctr:
      logmsg (LOG_INFO, _("pies is not running"));
      break;

    case pies_status_stale:
      logmsg (LOG_INFO,
	      _("pies is not running, but a pidfile "
		"is found (pid %lu)"), (unsigned long) pid);
      return 1;

    case pies_status_noresp:
      logmsg (LOG_INFO,
	      _("pies seems to run with pid %lu, but is not responding"),
	      (unsigned long) pid);
      return 2;

    case pies_status_running:
      list_components ();
      break;
    }
  return 0;
}

int
request_stop (void)
{
  pid_t pid = pidfile_read (1);

  if (pid == -1)
    {
      logmsg (LOG_CRIT, _("pies is not running"));
      return EX_USAGE;
    }

  logmsg (LOG_INFO, _("stopping pies at PID %lu"), (unsigned long) pid);
  return kill (pid, SIGTERM) ? EX_SOFTWARE : 0;
}


/* Pidfile */
/* Check whether pidfile NAME exists and if so, whether its PID is still
   active. Exit if it is. */
void
check_pidfile (char *name)
{
  unsigned long pid;
  FILE *fp = fopen (name, "r");
  if (!fp)
    {
      if (errno == ENOENT)
	return;
      logmsg (LOG_ERR, _("cannot open file %s: %s"),
	      name, strerror (errno));
      exit (EX_TEMPFAIL);
    }
  if (fscanf (fp, "%lu", &pid) != 1)
    {
      logmsg (LOG_ERR, ("cannot get pid from pidfile `%s'"), name);
    }
  else
    {
      if (kill (pid, 0) == 0)
	{
	  logmsg (LOG_ERR,
		  _
		  ("%s appears to run with pid %lu. If it does not, remove %s and retry."),
		  program_name, pid, name);
	  exit (EX_USAGE);
	}
    }
  fclose (fp);
  if (unlink (pidfile))
    {
      logfuncall ("unlink", name, errno);
      exit (EX_USAGE);
    }
}

void
create_pidfile (char *name)
{
  FILE *fp = fopen (name, "w");
  if (!fp)
    {
      logmsg (LOG_ERR, _("cannot create pidfile `%s': %s"),
	      name, strerror (errno));
      exit (EX_TEMPFAIL);
    }
  fprintf (fp, "%lu", (unsigned long) getpid ());
  fclose (fp);
}

void
remove_pidfile (char *name)
{
  if (unlink (name))
    logfuncall ("unlink", name, errno);
}


static void
set_mailer_argcv (void)
{
  int i;
  struct wordsplit ws;
  
  if (wordsplit (mailer_command_line, &ws, WRDSF_DEFFLAGS))
    {
      logmsg (LOG_CRIT, _("cannot parse mailer command line: %s"),
	      strerror (errno));
      exit (EX_CONFIG);
    }
  mailer_argc = ws.ws_wordc;
  mailer_argv = grecs_calloc (mailer_argc + 1, sizeof (mailer_argv[0]));
  for (i = 0; i < mailer_argc; i++)
    mailer_argv[i] = grecs_strdup (ws.ws_wordv[i]);
  mailer_argv[i] = NULL;
  wordsplit_free (&ws);
}

static inline int
init_emu (void)
{
#ifdef INIT_EMU
# warning "pies compiled with init emulation code"
  char *emu = getenv ("INIT_EMU");
  if (emu)
    {
      char *inittab  = strtok (emu, ":");
      char *piesinit = strtok (NULL, ":");

      config_file_add_type (CONF_INITTAB, inittab);
      config_file_add_type (CONF_PIES,
			    piesinit ? piesinit : "/etc/pies.init");

      init_fifo = getenv ("INIT_FIFO");
      if (!init_fifo)
	init_fifo = "/tmp/initctl";

      init_process = 1;
      fprintf (stderr, "%s: running in init emulation mode\n", program_name);
      return 1;
    }
  else
    {
      fprintf (stderr, "%s: Notice:\n", program_name);
      fprintf (stderr,
	       "    To enable init emulation code, define environment variable\n"
	       "         INIT_EMU=<inittab>[:<config>]\n"
	       "    where <inittab> and <config> are names of the inittab and\n"
	       "    Pies configuration files, correspondingly.\n"
	       "\n"
	       "    To override the default FIFO name, define:\n"
	       "         INIT_FIFO=<pathname>\n");
      fprintf (stderr, "%s: End of notice\n", program_name);
    }
#endif
  return 0;
}

static void
set_conf_file_names (const char *base)
{
  if (init_process)
    {
      config_file_add_type (CONF_INITTAB, "/etc/inittab");
      config_file_add_type (CONF_PIES, "/etc/pies.init");
    }
  else if (!config_list && !init_emu ())
    {
      char *name = mkfilename (SYSCONFDIR, base, ".conf");
      config_file_add (current_syntax, name);
      free (name);
    }
}

static void
set_state_file_names (const char *base)
{
  if (!pidfile)
    pidfile = mkfilename (statedir, base, ".pid");
  if (!qotdfile)
    qotdfile = mkfilename (statedir, base, ".qotd");
}

size_t pies_master_argc;
char **pies_master_argv;

int
main (int argc, char **argv)
{
  pid_t pid;
  extern char **environ;
  struct grecs_list_entry *ep;
  int diag_flags;
  
  set_program_name (argv[0]);
#ifdef ENABLE_NLS
  setlocale (LC_ALL, "");
  bindtextdomain (PACKAGE, LOCALEDIR);
  textdomain (PACKAGE);
#endif
  mf_proctitle_init (argc, argv, environ);

  grecs_print_diag_fun = pies_diag_printer;
  
  pies_master_argc = argc;
  pies_master_argv = argv;
  
  set_quoting_style (NULL, shell_quoting_style);

  init_process = getpid () == 1;
  
  /* Set default logging */
  if (init_process)
    {
      log_facility = LOG_DAEMON;
      diag_flags = DIAG_TO_STDERR | DIAG_REOPEN_LOG;
    }
  else
    diag_flags = DIAG_TO_SYSLOG | (stderr_closed_p () ? 0 : DIAG_TO_STDERR);
				   
  diag_setup (diag_flags);
  
  config_init ();

  parse_options (&argc, &argv);
      
  if (argc && !(command == COM_RESTART_COMPONENT
		|| command == COM_TRACE_DEPEND
		|| command == COM_TRACE_PREREQ))
    {
      logmsg (LOG_ERR, "extra command line arguments");
      exit (EX_USAGE);
    }
  
  if (!instance)
    {
      instance = strrchr (program_name, '/');
      if (!instance)
	instance = (char*) program_name;
      else
	instance++;
    }
  setenv ("PIES_INSTANCE", instance, 1);
  log_tag = grecs_strdup (instance);

  set_conf_file_names (instance);

  if (init_process || !DEFAULT_PREPROCESSOR)
    grecs_preprocessor = NULL;
  else
    grecs_preprocessor = pp_command_line ();

  if (preprocess_only)
    {
      for (ep = config_list->head; ep; ep = ep->next)
	{
	  struct config_file *file = ep->data;

	  if (file->syntax == &config_syntax_tab[CONF_PIES]
	      && grecs_preproc_run (file->name, grecs_preprocessor))
	    exit (EX_CONFIG);
	}
      exit (0);
    }
  else if (pies_read_config ())
    exit (EX_CONFIG);

  component_config_commit ();

  set_state_file_names (instance);
  set_mailer_argcv ();
  
  if (lint_mode)
    exit (0);

  /* Re-setup logging: it might have been reset in the config file */
  diag_setup (log_to_stderr_only ? DIAG_TO_STDERR : 0);
  
  if (!control.url)
    {
      char const *str = default_control_url[init_process];
      if (pies_url_create (&control.url, str))
	{
	  logmsg (LOG_CRIT, _("%s: cannot create control URL: %s"),
		  str, strerror (errno));
	  if (!init_process)
	    exit (EX_OSERR);
	}
    }
  
  switch (command)
    {
    case COM_RESTART_COMPONENT:
      pies_priv_setup (&pies_privs);
      if (pies_umask)
	umask (pies_umask);
      exit (request_restart_components (argc, argv));
      
    case COM_RELOAD:
      exit (request_reload ());

    case COM_STATUS:
      exit (request_status ());

    case COM_STOP:
      exit (request_stop ());

    case COM_DUMP_DEPMAP:
      components_dump_depmap ();
      exit (0);

    case COM_TRACE_DEPEND:
      components_trace (argv, depmap_row);
      exit (0);
      
    case COM_TRACE_PREREQ: 
      components_trace (argv, depmap_col);
      exit (0);
      
    default:
      pies_priv_setup (&pies_privs);
      if (pies_umask)
	umask (pies_umask);
    }

  if (init_process)
    {
      foreground = 1;
      sysvinit_begin ();
    }
  else
    switch (pies_check_status (&pid))
      {
      case pies_status_ctr:
	break;
      case pies_status_stale:
      case pies_status_noresp:
	if (!force_option)
	  {
	    logmsg (LOG_ERR,
		    _("another pies instance may be running (pid %lu), "
		      "use --force to override"), (unsigned long) pid);
	    exit (EX_USAGE);
	  }
	break;
	
      case pies_status_running:
	logmsg (LOG_ERR, _("another pies instance already running (pid %lu)"),
		(unsigned long) pid);
	exit (EX_USAGE);
      }
  
  if (!foreground)
    {
      check_pidfile (pidfile);
      if (daemon (0, 0) == -1)
	{
	  logfuncall ("daemon", NULL, errno);
	  exit (EX_SOFTWARE);
	}
      diag_setup (DIAG_TO_SYSLOG);
    }

  logmsg (LOG_INFO, _("%s %s starting"), proginfo.package, proginfo.version);
  
  if (!init_process)
    {
      if (ctl_open ())
	exit (EX_UNAVAILABLE);
      create_pidfile (pidfile);
    }
  
  if (pies_master_argv[0][0] != '/')
    logmsg (LOG_NOTICE,
	    _("not started as an absolute pathname; "
	      "restart will not work"));

  signal_setup (sig_handler);

  progman_create_sockets ();
  progman_start ();

  do
    {
      if (children_op == PIES_CHLD_NONE)
	pies_pause ();
      switch (action)
	{
	case ACTION_RESTART:
	  if (pies_master_argv[0][0] != '/' || init_process)
	    {
	      logmsg (LOG_INFO, _("restart command ignored"));
	      action = ACTION_CONT;
	    }
	  break;

	case ACTION_RELOAD:
	  if (pies_reread_config ())
	    {
	      action = ACTION_CONT;
	      break;
	    }
	  /* fall through */
	case ACTION_COMMIT:
	  component_config_commit ();
	  if (init_process)
	    sysvinit_runlevel_setup (PIES_COMP_DEFAULT);
	  progman_create_sockets ();
	  progman_start ();

	  pies_schedule_children (PIES_CHLD_WAKEUP);
	  action = ACTION_CONT;
	  break;
	  
	case ACTION_STOP:
	  if (init_process)
	    {
	      debug (1, ("ignoring stop/restart"));
	      action = ACTION_CONT;
	    }
	  break;

	case ACTION_CTRLALTDEL:
	  debug (1, ("ctrl-alt-del"));
	  sysvinit_runlevel_setup (PIES_COMP_MASK (pies_comp_ctrlaltdel));
	  pies_schedule_children (PIES_CHLD_WAKEUP);
	  action = ACTION_CONT;
	  break;
	  
	case ACTION_KBREQUEST:
	  debug (1, ("kbrequest"));
	  sysvinit_runlevel_setup (PIES_COMP_MASK (pies_comp_kbrequest));
	  pies_schedule_children (PIES_CHLD_WAKEUP);
	  action = ACTION_CONT;
	  break;

	case ACTION_POWER:
	  debug (1, ("SIGPWR"));
	  sysvinit_power ();
	  pies_schedule_children (PIES_CHLD_WAKEUP);
	  action = ACTION_CONT;
	}
      if (action == ACTION_CONT)
	{
	  if (children_op & PIES_CHLD_RESCHEDULE_ALARM)
	    progman_recompute_alarm ();
	  if (children_op & PIES_CHLD_GC)
	    progman_gc ();
	  if (children_op & PIES_CHLD_CLEANUP)
	    progman_cleanup (0);
	  if (children_op & PIES_CHLD_WAKEUP)
	    progman_wake_sleeping (1);
	  children_op = PIES_CHLD_NONE;
	}
    }
  while (init_process || action == ACTION_CONT);

  progman_stop ();
  remove_pidfile (pidfile);

  if (action == ACTION_RESTART)
    {
      int minfd = DIAG_OUTPUT (DIAG_TO_STDERR) ? 2 : 0;
      int i;

      for (i = getmaxfd (); i > minfd; i--)
	close (i);

      signal_setup (SIG_DFL);

      execv (pies_master_argv[0], pies_master_argv);
    }

  logmsg (LOG_INFO, _("%s %s terminated"), proginfo.package, proginfo.version);
  exit (EX_OK);
}

void
xalloc_die (void)
{
  logmsg (LOG_CRIT, _("not enough memory"));
  abort ();
}
/* EOF */
