/* This file is part of GNU Pies.
   Copyright (C) 2008-2026 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"
#include "parseopt.h"

int preprocess_only;		/* Preprocess config, do nothing more */
int log_to_stderr_only;                /* Use only stderr for logging */
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,
    COM_LIST_SHUTDOWN_SEQUENCE
  };

enum pies_command command;
char *statedir = DEFAULT_STATE_DIR;
char *instance;
char *pidfile;
char *qotdfile;

enum
  {
    MODE_PIES,
    MODE_LINT,
    MODE_INETD,
    MODE_TELINIT
  };
int pies_mode;

char *incpath = DEFAULT_INCLUDE_PATH;
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
};

static envop_t *pies_envop;

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 },
#if PIES_SYSVINIT_ENABLED
  [CONF_INITTAB] = { "inittab", inittab_parse },
#endif
};

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_node_t *node,
	    void *varptr, void *cb_data)
{
  grecs_locus_t *locus = &node->locus;
  grecs_value_t *value = node->v.value;

  enum return_action *pact = varptr;
  static struct tokendef actab[] = {
    {"disable", action_disable},
    {"restart", action_restart},
    {NULL}
  };
  int res;

  if (grecs_assert_node_value_type (cmd, node, 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)
    {
      grecs_list_free (act->cond_list);
      free (act->addr);
      free (act->message);
      free (act->command);

      free (act);
    }
}

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

int
strtosignum (char const *arg)
{
  size_t len;
  int n;

  if (arg == NULL)
    return 0;

  len = strlen (arg);
  if (len > 3 && memcmp (arg, "SIG", 3) == 0)
    {
      if (arg[4] == '+')
	{
	  char *p;
	  unsigned long un;

	  errno = 0;
	  un = strtoul (arg + 4, &p, 0);
	  if (un == 0 || *p || errno || un > INT_MAX)
	    return 0;
	  n = un;
	}
      else if (strtotok_ci (sig_tokendef, arg, &n))
	return 0;
    }
  return n;
}

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;
  struct grecs_list *cond_list;
  struct action *act;

  if (argc == 0 || (argc == 1 && strcmp (getarg (val, 0, locus), "*") == 0))
    cond_list = NULL;
  else
    {
      cond_list = grecs_list_create ();
      cond_list->free_entry = listel_dispose;

      for (i = 0; i < argc; i++)
	{
	  struct status_cond *sc;
	  int n;
	  int neg = 0;
	  const char *arg = getarg (val, i, locus);
	  size_t len = strlen (arg);

	  if (*arg == '!')
	    {
	      neg = 1;
	      arg++;
	      if (*arg == 0)
		{
		  i++;
		  if (i == argc)
		    break;
		  arg = getarg (val, i, locus);
		  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)
	    {
	      int signo;
	      if ((signo = strtosignum (arg)) == 0)
		{
		  grecs_error (locus, 0, _("%s: bad signal specification"),
			       arg);
		  continue;
		}
	      n = STATUS_SIG_BIT | signo;
	    }
	  else if (strtotok_ci (ex_tokendef, arg, (int *) &n))
	    {
	      grecs_error (locus, 0, _("%s: not a return code"), arg);
	      continue;
	    }

	  sc = grecs_malloc (sizeof (sc[0]));
	  sc->status = n;
	  sc->neg = neg;

	  grecs_list_append (cond_list, sc);
	}
    }

  act = grecs_zalloc (sizeof *act);
  act->cond_list = cond_list;
  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 (grecs_assert_value_type (val->v.arg.v[num], GRECS_TYPE_STRING,
				   locus) == 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 (grecs_assert_value_type (elt, GRECS_TYPE_STRING, locus) == 0)
    return elt->v.string;
  return NULL;
}

static int
return_code_section_parser (enum grecs_callback_command cmd,
			    grecs_node_t *node,
			    void *varptr, void *cb_data)
{
  grecs_locus_t *locus = &node->locus;
  grecs_value_t *value = node->v.value;

  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 (grecs_assert_value_type (val->v.arg.v[i], GRECS_TYPE_STRING, locus)
	  == 0)
	argv[j++] = grecs_strdup (val->v.arg.v[i]->v.string);
    }
  argv[j] = NULL;
  if (pargc)
    *pargc = argc;
  return argv;
}

static int
_cb_umask (enum grecs_callback_command cmd,
			    grecs_node_t *node,
			    void *varptr, void *cb_data)
{
  grecs_locus_t *locus = &node->locus;
  grecs_value_t *value = node->v.value;

  mode_t *pmode = varptr;
  char *p;
  unsigned long n;

  if (grecs_assert_node_value_type (cmd, node, 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;
}

void
argv_free (char **argv)
{
  if (argv)
    {
      size_t i;
      for (i = 0; argv[i]; i++)
	free (argv[i]);
      free (argv);
    }
}

static int
parse_legacy_env (char **argv, envop_t **envop)
{
  size_t i = 0;
  int rc;
  char *name;

  if (strcmp (argv[0], "-") == 0)
    {
      rc = envop_entry_add (envop, envop_clear, NULL, NULL);
      if (rc)
	return rc;
      i++;
    }
  for (; (name = argv[i]) != NULL; i++)
    {
      char *name = argv[i];
      size_t len = strcspn (name, "=");
      char *value;
      char *mem = NULL;
      size_t msize = 0;
      enum envop_code code;

      if (name[0] == '-')
	{
	  /* Unset directive */
	  name++;
	  len--;

	  if (name[len])
	    {
	      name[len] = 0;
	      value = name + len + 1;
	    }
	  else
	    value = NULL;

	  code = envop_unset;
	}
      else if (name[len])
	{
	  size_t vlen;

	  if (len == 0)
	    /* Skip erroneous entry */
	    continue;
	  value = name + len + 1;
	  vlen = strlen (value);
	  name[len] = 0;
	  if (name[len-1] == '+')
	    {
	      name[--len] = 0;
	      if (c_ispunct (value[0]))
		{
		  msize = 2*len + 9 + vlen + 1;
		  mem = grecs_malloc (msize);
		  snprintf (mem, msize, "${%s:-}${%s:+%c}%s",
			    name, name, value[0], value + 1);
		}
	      else
		{
		  msize = len + vlen + 6;
		  mem = grecs_malloc (msize);
		  snprintf (mem, msize, "${%s:-}%s", name, value);
		}
	      value = mem;
	    }
	  else if (value[0] == '+')
	    {
	      value++;
	      vlen--;

	      if (vlen > 0 && c_ispunct (value[vlen-1]))
		{
		  int c = value[vlen-1];
		  value[--vlen] = 0;

		  msize = 2*len + 10 + vlen + 1;
		  mem = grecs_malloc (msize);
		  snprintf (mem, msize, "%s${%s:+%c}${%s:-}",
			    value, name, c, name);
		}
	      else
		{
		  msize = len + vlen + 6;
		  mem = grecs_malloc (msize);
		  snprintf (mem, msize, "%s${%s:-}", value, name);
		}
	      value = mem;
	    }
	  code = envop_set;
	}
      else
	{
	  value = NULL;
	  code = envop_keep;
	}
      rc = envop_entry_add (envop, code, name, value);
      free (mem);
      if (rc)
	return rc;
    }
  return 0;
}

static int
_cb_env (envop_t **envop, grecs_value_t *value, grecs_locus_t *locus)
{
  char **argv;
  int rc;

  switch (value->type)
    {
    case GRECS_TYPE_STRING:
      argv = grecs_calloc (2, sizeof (argv[0]));
      argv[0] = grecs_strdup (value->v.string);
      argv[1] = NULL;
      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;
    }

  rc = parse_legacy_env (argv, envop);
  argv_free (argv);
  if (rc)
    {
      grecs_error (locus, errno, _("can't parse legacy env statement"));
      return 1;
    }
  return 0;
}

static int
cb_env_section_parser (enum grecs_callback_command cmd,
		       grecs_node_t *node,
		       void *varptr, void *cb_data)
{
  grecs_locus_t *locus = &node->locus;
  grecs_value_t *value = node->v.value;
  envop_t **envop_ptr = varptr;

  switch (cmd)
    {
    case grecs_callback_section_begin:
      *(envop_t ***) cb_data = envop_ptr;
      break;

    case grecs_callback_section_end:
      break;

    case grecs_callback_set_value:
      return _cb_env (envop_ptr, value, locus);
    }
  return 0;
}

static int
_cb_env_clear (enum grecs_callback_command cmd,
	       grecs_node_t *node,
	       void *varptr, void *cb_data)
{
  grecs_locus_t *locus = &node->locus;
  grecs_value_t *value = node->v.value;
  envop_t **envop_ptr = varptr;

  if (!GRECS_VALUE_EMPTY_P (value))
    {
      grecs_error (&value->locus, 0, "%s", _("unexpected argument"));
      return 1;
    }

  if (envop_entry_add (envop_ptr, envop_clear, NULL, NULL))
    grecs_error (locus, errno, "envop_entry_add");

  return 0;
}

static int
_cb_env_keep (enum grecs_callback_command cmd,
	      grecs_node_t *node,
	      void *varptr, void *cb_data)
{
  grecs_locus_t *locus = &node->locus;
  grecs_value_t *value = node->v.value;
  envop_t **envop_ptr = varptr;
  char *p;

  if (grecs_assert_node_value_type (cmd, node, GRECS_TYPE_STRING))
    return 1;
  p = strchr (value->v.string, '=');
  if (p)
    *p++ = 0;
  if (envop_entry_add (envop_ptr, envop_clear, NULL, NULL))
    grecs_error (locus, errno, "envop_entry_add");
  if (envop_entry_add (envop_ptr, envop_keep, value->v.string, p))
    grecs_error (locus, errno, "envop_entry_add");
  return 0;
}

static int
_cb_env_set (enum grecs_callback_command cmd,
	     grecs_node_t *node,
	     void *varptr, void *cb_data)
{
  grecs_locus_t *locus = &node->locus;
  grecs_value_t *value = node->v.value;
  envop_t **envop_ptr = varptr;
  char *p;

  if (grecs_assert_node_value_type (cmd, node, GRECS_TYPE_STRING))
    return 1;
  p = strchr (value->v.string, '=');
  if (p)
    *p++ = 0;
  if (envop_entry_add (envop_ptr, envop_set, value->v.string, p))
    grecs_error (locus, errno, "envop_entry_add");
  return 0;
}

static int
_cb_env_eval (enum grecs_callback_command cmd,
	      grecs_node_t *node,
	      void *varptr, void *cb_data)
{
  grecs_locus_t *locus = &node->locus;
  grecs_value_t *value = node->v.value;
  envop_t **envop_ptr = varptr;

  if (grecs_assert_node_value_type (cmd, node, GRECS_TYPE_STRING))
    return 1;
  if (envop_entry_add (envop_ptr, envop_set, NULL, value->v.string))
    grecs_error (locus, errno, "envop_entry_add");
  return 0;
}

static int
_cb_env_unset (enum grecs_callback_command cmd,
	       grecs_node_t *node,
	       void *varptr, void *cb_data)
{
  grecs_locus_t *locus = &node->locus;
  grecs_value_t *value = node->v.value;
  envop_t **envop_ptr = varptr;
  char *p;

  if (grecs_assert_node_value_type (cmd, node, GRECS_TYPE_STRING))
    return 1;
  p = strchr (value->v.string, '=');
  if (p)
    *p++ = 0;
  if (envop_entry_add (envop_ptr, envop_unset, value->v.string, p))
    grecs_error (locus, errno, "envop_entry_add");
  return 0;
}

struct grecs_keyword cb_env_keywords[] = {
  { "clear",
    N_("bool"),
    N_("Clear environment."),
    grecs_type_bool, GRECS_DFLT,
    NULL, 0,
    _cb_env_clear },
  { "keep",
    N_("name[=value]"),
    N_("Keep this variable. Unless value is supplied, name can contain wildcards.\n"
       "Implies \"clear\"."),
    grecs_type_string, GRECS_DFLT,
    NULL, 0,
    _cb_env_keep },
  { "set",
    N_("name=value"),
    N_("Set environment variable. Note, that argument must be quoted."),
    grecs_type_string, GRECS_DFLT,
    NULL, 0,
    _cb_env_set },
  { "eval",
    N_("string"),
    N_("Evaluate string. Useful for side-effects, e.g. eval ${X:=2}."),
    grecs_type_string, GRECS_DFLT,
    NULL, 0,
    _cb_env_eval },
  { "unset",
    N_("name"),
    N_("Unset environment variable. Name can contain wildcards."),
    grecs_type_string, GRECS_DFLT,
    NULL, 0,
    _cb_env_unset },
  { NULL }
};


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 len, 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 len == 0
	  ? strtotok_ci (tokdef_fac, key, pres)
	  : strtotok_len_ci (tokdef_fac, key, len, pres);
}

static int
string_to_syslog_fp (const char *str, grecs_locus_t *locus, int *pres)
{
  size_t len = strcspn (str, ".");
  int f = pies_log_facility, p = LOG_ERR;

  if (string_to_syslog_facility (str, len, &f) == 0)
    {
      if (str[len])
	{
	  if (string_to_syslog_priority (str + len + 1, &p))
	    {
	      grecs_error (locus, 0, "%s", _("unknown syslog priority"));
	      return 1;
	    }
	}
    }
  else if (str[len])
    {
      grecs_error (locus, 0, "%s", _("unknown syslog facility"));
      return 1;
    }
  else if (string_to_syslog_priority (str, &p))
    {
      grecs_error (locus, 0, "%s", _("unknown syslog priority"));
      return 1;
    }
  *pres = f | p;
  return 0;
}

static int
cb_syslog_dev (enum grecs_callback_command cmd,
		    grecs_node_t *node,
		    void *varptr, void *cb_data)
{
  grecs_value_t *value = node->v.value;

  if (grecs_assert_node_value_type (cmd, node, GRECS_TYPE_STRING))
    return 1;
  return pies_syslog_set_dev (value->v.string);
}

static int
cb_syslog_facility (enum grecs_callback_command cmd,
		    grecs_node_t *node,
		    void *varptr, void *cb_data)
{
  grecs_locus_t *locus = &node->locus;
  grecs_value_t *value = node->v.value;
  const char *str;

  if (grecs_assert_node_value_type (cmd, node, 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, 0, varptr))
    grecs_error (locus, 0, _("unknown syslog facility %s"), str);
  return 0;
}

static int
_cb_redir (enum grecs_callback_command cmd,
	   grecs_node_t *node,
	   void *varptr, void *cb_data)
{
  grecs_locus_t *locus = &node->locus;
  grecs_value_t *value = node->v.value;
  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_fp (value->v.string, locus, &rp->v.prio))
	return 1;
      break;

    case GRECS_TYPE_ARRAY:
      if (grecs_assert_value_type (value->v.arg.v[0], GRECS_TYPE_STRING,
				   locus))
	return 1;
      if (strtotok (redirtab, value->v.arg.v[0]->v.string, &res))
	grecs_error (locus, 0, _("%s: unrecognized 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 1;
		}
	      if (grecs_assert_value_type (value->v.arg.v[1],
					   GRECS_TYPE_STRING, locus))
		return 1;

	      switch (res)
		{
		case redir_null:
		  break;

		case redir_syslog:
		  if (string_to_syslog_fp (value->v.arg.v[1]->v.string,
					   locus, &rp->v.prio))
		    return 1;
		  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_node_t *node,
		 void *varptr, void *cb_data)
{
  grecs_locus_t *locus = &node->locus;
  grecs_value_t *value = node->v.value;

  if (grecs_assert_node_value_type (cmd, node, 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},
  {"startup", pies_comp_startup},
  {"shutdown", pies_comp_shutdown},
  {"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_node_t *node,
	  void *varptr, void *cb_data)
{
  grecs_locus_t *locus = &node->locus;
  grecs_value_t *value = node->v.value;
  int res;

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

static int
_cb_limits (enum grecs_callback_command cmd,
	    grecs_node_t *node,
	    void *varptr, void *cb_data)
{
  grecs_locus_t *locus = &node->locus;
  grecs_value_t *value = node->v.value;
  limits_record_t *plrec = varptr;
  char *p;

  if (grecs_assert_node_value_type (cmd, node, 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 },
    { "shell", CF_SHELL },
    { "expandenv", CF_EXPANDENV },
    { "deansify", CF_DEANSIFY },
    { 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_node_t *node,
	   void *varptr, void *cb_data)
{
  grecs_locus_t *locus = &node->locus;
  grecs_value_t *value = node->v.value;
  int *flags = varptr;

  switch (value->type)
    {
    case GRECS_TYPE_STRING:
      if (str_to_cf (value->v.string, flags))
	{
	  grecs_error (locus, 0, _("%s: unrecognized 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 (grecs_assert_value_type (vp, GRECS_TYPE_STRING, locus))
	      return 1;
	    if (str_to_cf (vp->v.string, flags))
	      {
		grecs_error (locus, 0, _("%s: unrecognized 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_signal (enum grecs_callback_command cmd,
	    grecs_node_t *node,
	    void *varptr, void *cb_data)
{
  grecs_locus_t *locus = &node->locus;
  grecs_value_t *value = node->v.value;
  int n;

  if (grecs_assert_node_value_type (cmd, node, GRECS_TYPE_STRING))
    return 1;
  if ((n = strtosignum (value->v.string)) == 0)
    {
      grecs_error (locus, 0, _("%s: bad signal specification"),
		   value->v.string);
      return 1;
    }
  *(int*)varptr = n;
  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, offsetof (struct component, command),
   NULL,
  },
  {"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 },
  {"sigterm",
   N_("SIGNAL"),
   N_("Termination signal."),
   grecs_type_string, GRECS_DFLT, NULL, offsetof (struct component, sigterm),
   _cb_signal },
#if PIES_SYSVINIT_ENABLED
  {"runlevels",
   N_("chars"),
   N_("Runlevels to start that component in."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, runlevels),
   cb_runlevels },
#endif
  {"pass-fd-socket",
   N_("name"),
   N_("Pass fd through this socket."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, pass_fd_socket),
   NULL,
   },
  {"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 },
  {"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,
   },
  {"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",
   NULL,
   N_("Modify program environment."),
   grecs_type_section, GRECS_DFLT,
   NULL, offsetof (struct component, envop),
   cb_env_section_parser, NULL, cb_env_keywords
   },
  {"env",
   N_("arg: list"),
   N_("Modify program environment (legacy syntax).\n"
      "Argument is a list of quoted assignments separated by white space."),
   grecs_type_string, GRECS_DFLT,
   NULL, offsetof (struct component, envop),
   cb_env_section_parser, NULL, NULL
   },
  {"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_node_t *node,
			  void *varptr, void *cb_data)
{
  grecs_locus_t *locus = &node->locus;
  grecs_value_t *value = node->v.value;
  struct component *comp;
  void **section_data = cb_data;

  switch (cmd)
    {
    case grecs_callback_section_begin:
      if (GRECS_VALUE_EMPTY_P (value))
	{
	  grecs_error (locus, 0, _("missing tag"));
	  return 1;
	}
      if (grecs_assert_value_type (value, GRECS_TYPE_STRING, locus))
	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[] = {
  {"dev",
   N_("name"),
   N_("Syslog device: either absolute pathname of the socket file, "
      "or an IPv4 address and optional port, separated with a colon"),
   grecs_type_string, GRECS_CONST,
   NULL, 0, cb_syslog_dev },
  {"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,
   &pies_log_facility, 0, cb_syslog_facility},
  {"tag", N_("string"), N_("Tag syslog messages with this string"),
   grecs_type_string, GRECS_DFLT,
   &pies_log_tag},
  {"fallback-file", N_("filename"), N_("Name of the fallback log file"),
   grecs_type_string, GRECS_DFLT,
   &pies_fallback_file},
  {"message-queue-size", N_("arg"), N_("Max. capacity of the log message queue"),
   grecs_type_size, GRECS_DFLT,
   &pies_log_max_queue },
  {"fallback-ignore-failures",
   N_("arg"),
   N_("Ignore errors during writes to the fallback file"),
   grecs_type_bool, GRECS_DFLT,
   &pies_fallback_ignore_failures},
#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_node_t *node,
		   void *varptr, void *cb_data)
{
  grecs_value_t *value = node->v.value;
  if (grecs_assert_node_value_type (cmd, node, GRECS_TYPE_STRING))
    return 1;
  meta1_config_parse (value->v.string);
  return 0;
}

static int
_cb_include_inetd (enum grecs_callback_command cmd,
		   grecs_node_t *node,
		   void *varptr, void *cb_data)
{
  grecs_value_t *value = node->v.value;
  if (grecs_assert_node_value_type (cmd, node, 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},
  {"env",
   NULL,
   N_("Modify program environment."),
   grecs_type_section, GRECS_DFLT,
   &pies_envop, 0,
   cb_env_section_parser, NULL, cb_env_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,
   },
#if PIES_SYSVINIT_ENABLED
  {"initdefault",
   N_("arg: char"),
   N_("Default runlevel"),
   grecs_type_string, GRECS_DFLT,
   NULL, 0, cb_initdefault,
  },
#endif
  {"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_preprocessor = DEFAULT_PREPROCESSOR;

  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 (SYSVINIT_ACTIVE)
    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];

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;
}

static int
children_op_toggle (int op)
{
  int result = children_op & op;
  children_op &= ~op;
  return result;
}

void
sig_handler (int sig)
{
  if (SYSVINIT_ACTIVE && 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
sigchld_early (int sig)
{
  while (waitpid (-1, NULL, WNOHANG) != -1)
    ;
}

void
setsigvhan (void (*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, sigv[i]);

  act.sa_handler = handler;
  for (i = 0; i < sigc; i++)
    {
      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 (void (*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 || pid == getpid ())
    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);
}

#ifdef INIT_EMU
# warning "pies compiled with init emulation code"
static char init_emu_help[] =
  "    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";
#endif

static inline void
init_emu_fifo_setup (void)
{
#ifdef INIT_EMU
  init_fifo = getenv ("INIT_FIFO");
  if (!init_fifo)
    init_fifo = "/tmp/initctl";
#endif
}

static inline int
init_emu (void)
{
#ifdef INIT_EMU
  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_emu_fifo_setup ();

      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, "%s", init_emu_help);
      fprintf (stderr, "%s: End of notice\n", program_name);
    }
#endif
  return 0;
}

static void
set_conf_file_names (const char *base)
{
  if (SYSVINIT_ACTIVE)
    {
      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");
}

static int
is_docker (void)
{
  FILE *fp;

  //                        0         1
  //                        0123456789012345678
  static char alphabet[] = "0123456789docker:/\n";
  static unsigned char transition[][20] = {
    // 0  1  2  3  4  5  6  7  8  9  d  o  c  k  e  r  :  /  \n
    {  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,10,10,10,10,10,10, 1,10, 0, 10 },
    {  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 0,  1 },
    { 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 3, 0, 10 },
    { 10,10,10,10,10,10,10,10,10,10, 4,10,10,10,10,10,10,10, 0, 10 },
    { 10,10,10,10,10,10,10,10,10,10,10, 5,10,10,10,10,10,10, 0, 10 },
    { 10,10,10,10,10,10,10,10,10,10,10,10, 6,10,10,10,10,10, 0, 10 },
    { 10,10,10,10,10,10,10,10,10,10,10,10,10, 7,10,10,10,10, 0, 10 },
    { 10,10,10,10,10,10,10,10,10,10,10,10,10,10, 8,10,10,10, 0, 10 },
    { 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 9,10,10, 0, 10 },
    { 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11, 0, 10 },
    { 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 0, 10 },
  };
  enum { state_init = 0, state_success = 11, state_eof = 12 };
  int state = state_init;

  fp = fopen ("/proc/self/cgroup", "r");
  if (!fp)
    return 0;

  while (state != state_success && state != state_eof)
    {
      int c = fgetc (fp);
      if (c == EOF)
	state = state_eof;
      else
	{
	  char *p = strchr (alphabet, c);
	  int a = p ? p - alphabet : sizeof (alphabet) - 1;
	  state = transition[state][a];
	}
    }
  fclose (fp);
  return state == state_success;
}

/*
 * Check if `--no-init' option is present in argv/argc.  Return 1 if so,
 * 0 otherwise.
 */
static int
no_init_option (int argc, char **argv)
{
  int i;
  for (i = 1; i < argc; i++)
    {
      if (strcmp (argv[i], "--no-init") == 0)
	return 1;
    }
  return 0;
}

/*
 * init_detect - detect if we're run as init process.
 *
 * Pies runs as init process if (1) it has PID 1, (2) is not started as docker
 * entrypoint, and (3) the `--no-init' command line option is not given.
 *
 * The function sets init_process to 1 if pies runs as init process and 0
 * otherwise.
 *
 * No matter the result, if PID is 1 it installs an early SIGCHLD handler,
 * to ensure that the configuration preprocessor (if such is started) will be
 * cleaned up properly on exit.
 */
static void
init_detect (int argc, char **argv)
{
  if (getpid () == 1)
    {
      int s[] = { SIGCHLD };
      setsigvhan (sigchld_early, s, 1);

      init_process = PIES_SYSVINIT_ENABLED;
      if (init_process)
	{
	  if (no_init_option (argc, argv) || is_docker ())
	    init_process = 0;
	}
    }
}

enum
{
  OPT_CONFIG_HELP = 256,
  OPT_FORCE,
  OPT_FOREGROUND,
  OPT_NO_INIT,
  OPT_INSTANCE,
  OPT_RATE,
  OPT_STDERR,
  OPT_SYNTAX,
  OPT_SYSLOG,
  OPT_PREPROCESSOR,
  OPT_NO_PREPROCESSOR,
  OPT_NO_INCLUDE,
  OPT_DUMP_DEPMAP,
  OPT_TRACE_DEPEND,
  OPT_TRACE_PREREQ,
  OPT_LIST_SHUTDOWN_SEQUENCE,
  OPT_SOURCE_INFO
};

static int
optset_config_file (struct parseopt *po, struct optdef *opt, char *arg)
{
  config_file_add (current_syntax, arg);
  return 0;
}

static int
optset_config_help (struct parseopt *po, struct optdef *opt, char *arg)
{
  config_help ();
  exit (0);
}

static int
optset_current_syntax (struct parseopt *po, struct optdef *opt, char *arg)
{
  current_syntax = str_to_config_syntax (arg);
  if (!current_syntax)
    {
      logmsg (LOG_ERR, _("unknown syntax type: %s"), arg);
      exit (EX_USAGE);
    }
  return 0;
}

static int
optset_pp_option (struct parseopt *po, struct optdef *opt, char *arg)
{
  char optbuf[3] = { '-', 0, 0 };
  while (opt->opt_name[1])
    {
      ++opt;
      if (!(opt->opt_flags & OPTFLAG_ALIAS))
	{
	  logmsg (LOG_CRIT, "INTERNAL ERROR at %s:%d, please report",
		  __FILE__, __LINE__);
	  exit (EX_SOFTWARE);
	}
    }
  optbuf[1] = opt->opt_name[0];
  pp_add_option (optbuf, arg);
  return 0;
}

static struct optdef *mode_opt;

static void
set_mode (struct parseopt *po, struct optdef *opt, int mode)
{
  if (mode_opt != NULL)
    {
      po->po_error (po, PO_MSG_ERR,
		    _("conflicting options: %s%s and %s%s"),
		    option_dash (po, mode_opt), mode_opt->opt_name,
		    option_dash (po, opt), opt->opt_name);
      exit (EX_USAGE);
    }
  pies_mode = mode;
}

static int
optset_inetd_mode (struct parseopt *po, struct optdef *opt, char *arg)
{
  if (!instance)
    instance = "inetd";
  current_syntax = &config_syntax_tab[CONF_INETD];
  set_mode (po, opt, MODE_INETD);
  return 0;
}

static int
optset_telinit_mode (struct parseopt *po, struct optdef *opt, char *arg)
{
  set_mode (po, opt, MODE_TELINIT);
  po->po_eopt = 1;
  log_to_stderr_only = 1;
  return 0;
}

static int
optset_lint_mode (struct parseopt *po, struct optdef *opt, char *arg)
{
  set_mode (po, opt, MODE_LINT);
  log_to_stderr_only = 1;
  return 0;
}

static struct optdef options[] = {
  {
    .opt_flags = OPTFLAG_DOC,
    .opt_doc = N_("Operation Mode")
  },

  {
    .opt_name = "config-file",
    .opt_argdoc = N_("FILE"),
    .opt_doc = N_("use FILE instead of the default configuration"),
    .opt_set = optset_config_file
  },
  {
    .opt_name = "c",
    .opt_flags = OPTFLAG_ALIAS
  },

  {
    .opt_name = "config-help",
    .opt_doc = N_("show configuration file summary"),
    .opt_code = OPT_CONFIG_HELP,
    .opt_set = optset_config_help
  },

  {
    .opt_name = "E",
    .opt_doc = N_("preprocess configuration file and exit"),
    .opt_set = optset_true,
    .opt_ptr = &preprocess_only
  },

  {
    .opt_name = "force",
    .opt_doc = N_("force startup even if another instance may be running"),
    .opt_code = OPT_FORCE,
    .opt_set = optset_true,
    .opt_ptr = &force_option
  },

  {
    .opt_name = "foreground",
    .opt_doc = N_("remain in foreground"),
    .opt_code = OPT_FOREGROUND
  },

  {
    .opt_name = "no-init",
    .opt_doc = N_("don't suppose SysV init mode if PID is 1"),
    .opt_code = OPT_NO_INIT
    /* NOTE: handled separately by init_detect */
  },

  {
    .opt_name = "inetd",
    .opt_doc = N_("run in inetd mode"),
    .opt_set = optset_inetd_mode
  },
  {
    .opt_name = "i",
    .opt_flags = OPTFLAG_ALIAS
  },

  {
    .opt_name = "instance",
    .opt_argdoc = N_("NAME"),
    .opt_doc = N_("set instance name"),
    .opt_code = OPT_INSTANCE,
    .opt_set = optset_string_copy,
    .opt_ptr = &instance
  },

  {
    .opt_name = "rate",
    .opt_argdoc = N_("NUMBER"),
    .opt_doc = N_("set default maximum rate for inetd-style components"),
    .opt_code = OPT_RATE,
    .opt_set = optset_ulong,
    .opt_ptr = &default_max_rate
  },

  {
    .opt_name = "stderr",
    .opt_doc = N_("log to stderr"),
    .opt_code = OPT_STDERR,
    .opt_set = optset_true,
    .opt_ptr = &log_to_stderr_only
  },

  {
    .opt_name = "syntax",
    .opt_argdoc = N_("{pies|meta1|inetd|inittab}"),
    .opt_doc = N_("expect configuration files in the given syntax"),
    .opt_code = OPT_SYNTAX,
    .opt_set = optset_current_syntax
  },

  {
    .opt_name = "syslog",
    .opt_doc = N_("log to syslog"),
    .opt_code = OPT_SYSLOG,
    .opt_set = optset_false,
    .opt_ptr = &log_to_stderr_only
  },

  {
    .opt_name = "lint",
    .opt_doc = N_("parse configuration file and exit"),
    .opt_set = optset_lint_mode
  },
  {
    .opt_name = "t",
    .opt_flags = OPTFLAG_ALIAS
  },

  {
    .opt_name = "telinit",
    .opt_doc = N_("telinit command: run \"pies -T --help\" for help"),
    .opt_set = optset_telinit_mode
  },
  {
    .opt_name = "T",
    .opt_flags = OPTFLAG_ALIAS
  },

  {
    .opt_flags = OPTFLAG_DOC,
    .opt_doc = N_("Preprocessor")
  },

  {
    .opt_name = "preprocessor",
    .opt_argdoc = N_("COMMAND"),
    .opt_doc = N_("use COMMAND instead of the default preprocessor"),
    .opt_code = OPT_PREPROCESSOR,
    .opt_set = optset_string_copy,
    .opt_ptr = &grecs_preprocessor
  },

  {
    .opt_name = "no-preprocessor",
    .opt_doc = N_("disable preprocessing"),
    .opt_code = OPT_NO_PREPROCESSOR,
    .opt_set = optset_string_copy, /* This sets grecs_preprocessor to NULL. */
    .opt_ptr = &grecs_preprocessor
  },

  {
    .opt_name = "include-directory",
    .opt_argdoc = N_("DIR"),
    .opt_doc = N_("add include directory"),
    .opt_set = optset_pp_option
  },
  {
    .opt_name = "I",
    .opt_flags = OPTFLAG_ALIAS
  },

  {
    .opt_name = "no-include",
    .opt_doc = N_("clear default preprocessor search path"),
    .opt_code = OPT_NO_INCLUDE,
    .opt_set = optset_string_copy, /* Set incpath to NULL. */
    .opt_ptr = &incpath
  },

  {
    .opt_name = "define",
    .opt_argdoc = N_("NAME[=VALUE]"),
    .opt_doc =
    N_("define a preprocessor symbol NAME as having VALUE or empty"),
    .opt_set = optset_pp_option
  },
  {
    .opt_name = "D",
    .opt_flags = OPTFLAG_ALIAS
  },

  {
    .opt_name = "undefine",
    .opt_argdoc = N_("NAME"),
    .opt_doc = N_("undefine a preprocessor symbol NAME"),
    .opt_set = optset_pp_option
  },
  {
    .opt_name = "U",
    .opt_flags = OPTFLAG_ALIAS
  },

  {
    .opt_flags = OPTFLAG_DOC,
    .opt_doc = N_("Component Management")
  },

  {
    .opt_name = "reload",
    .opt_doc = N_("reload the running instance of pies"),
  },
  {
    .opt_name = "r",
    .opt_flags = OPTFLAG_ALIAS
  },
  {
    .opt_name = "hup",
    .opt_flags = OPTFLAG_ALIAS
  },

  {
    .opt_name = "restart-component",
    .opt_doc = N_("restart components named in the command line"),
  },
  {
    .opt_name = "R",
    .opt_flags = OPTFLAG_ALIAS
  },

  {
    .opt_name = "status",
    .opt_doc = N_("display info about the running instance"),
  },
  {
    .opt_name = "s",
    .opt_flags = OPTFLAG_ALIAS
  },

  {
    .opt_name = "stop",
    .opt_doc = N_("stop the running instance"),
  },
  {
    .opt_name = "S",
    .opt_flags = OPTFLAG_ALIAS
  },

  {
    .opt_flags = OPTFLAG_DOC,
    .opt_doc = N_("Debugging and Additional Diagnostics")
  },

  {
    .opt_name = "dump-depmap",
    .opt_doc = N_("dump dependency map"),
    .opt_code = OPT_DUMP_DEPMAP
  },

  {
    .opt_name = "trace-depend",
    .opt_doc = N_("trace dependencies"),
    .opt_code = OPT_TRACE_DEPEND
  },

  {
    .opt_name = "trace-prereq",
    .opt_doc = N_("trace prerequisites"),
    .opt_code = OPT_TRACE_PREREQ
  },

  {
    .opt_name = "list-shutdown-sequence",
    .opt_doc = N_("list components in shutdown sequence ordering"),
    .opt_code = OPT_LIST_SHUTDOWN_SEQUENCE
  },

  {
    .opt_name = "source-info",
    .opt_doc = N_("show source info with debugging messages"),
    .opt_code = OPT_SOURCE_INFO,
    .opt_set = optset_true,
    .opt_ptr = &source_info_option
  },

  {
    .opt_name = "debug",
    .opt_argdoc = N_("LEVEL"),
    .opt_doc = N_("set debug verbosity level"),
    .opt_set = optset_uint,
    .opt_ptr = &debug_level
  },
  {
    .opt_name = "x",
    .opt_flags = OPTFLAG_ALIAS
  },

  { NULL }
}, *optdef[] = { options, NULL };

static void
setopt (struct parseopt *po, int code, char *arg)
{
  switch (code)
    {
    case OPT_FOREGROUND:
      {
	log_to_stderr_only = 1;
	foreground = 1;
	break;
      }

    case 'r':
      {
	log_to_stderr_only = 1;
	command = COM_RELOAD;
	break;
      }

    case 'R':
      {
	log_to_stderr_only = 1;
	command = COM_RESTART_COMPONENT;
	break;
      }

    case 's':
      {
	log_to_stderr_only = 1;
	command = COM_STATUS;
	break;
      }

    case 'S':
      {
	log_to_stderr_only = 1;
	command = COM_STOP;
	break;
      }

    case OPT_DUMP_DEPMAP:
      {
	log_to_stderr_only = 1;
	command = COM_DUMP_DEPMAP;
	break;
      }

    case OPT_TRACE_DEPEND:
      {
	log_to_stderr_only = 1;
	command = COM_TRACE_DEPEND;
	break;
      }

    case OPT_TRACE_PREREQ:
      {
	log_to_stderr_only = 1;
	command = COM_TRACE_PREREQ;
	break;
      }

    case OPT_LIST_SHUTDOWN_SEQUENCE:
      {
	log_to_stderr_only = 1;
	command = COM_LIST_SHUTDOWN_SEQUENCE;
	break;
      }

    default:
      po->po_error (po, PO_MSG_ERR,
		    "internal error at %s:%d\n", __FILE__, __LINE__);
      abort ();
    }
}

static void
pies_help_hook (WORDWRAP_FILE wf, struct parseopt *po)
{
  if (DEFAULT_PREPROCESSOR)
    {
      wordwrap_printf (wf,
		       _("This pies preprocesses its configuration using %s\n"),
		       DEFAULT_PREPROCESSOR);
      wordwrap_printf (wf,
		       _("Preprocessor include path: %s\n"),
		       DEFAULT_INCLUDE_PATH);
    }
  else
    wordwrap_printf (wf,
		   _("This pies does not use configuration preprocessor.\n"));
#ifdef INIT_EMU
  wordwrap_para (wf);
  wordwrap_puts (wf,
		 "Notice: this program is compiled with init emulation code.");
  wordwrap_para (wf);
  wordwrap_flush (wf);
  fputs (init_emu_help, stdout);
#endif
}

extern void version_hook (WORDWRAP_FILE wf, struct parseopt *po);

static struct parseopt po = {
  .po_descr = N_("process invocation and execution supervisor"),
  .po_optdef = optdef,
  .po_setopt = setopt,
  .po_package_name = PACKAGE_NAME,
  .po_package_url = PACKAGE_URL,
  .po_bugreport_address = PACKAGE_BUGREPORT,
  .po_help_hook = pies_help_hook,
  .po_version_hook = version_hook
};

void
parse_options (int *pargc, char ***pargv)
{
  int argc = *pargc;
  char **argv = *pargv;

  if (SYSVINIT_ACTIVE)
    {
      sysvinit_parse_argv (argc, argv);
      *pargc = 0;
      *pargv = argv + argc;
    }
  else
    {
      if (parseopt_getopt (&po, argc, argv) == OPT_ERR)
	{
	  logmsg (LOG_CRIT, _("parseopt_getopt failed"));
	  exit (EX_SOFTWARE);
	}
      parseopt_argv (&po, pargc, pargv);
      parseopt_free (&po);
      pp_init (incpath);
    }
}

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_detect (argc, argv);

  /* Set default logging */
  if (SYSVINIT_ACTIVE)
    {
      pies_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 PIES_SYSVINIT_ENABLED
  if (pies_mode == MODE_TELINIT)
    {
      init_emu_fifo_setup ();
      exit (telinit (argc, argv));
    }
#endif

  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);
  pies_log_tag = grecs_strdup (instance);

  set_conf_file_names (instance);

  if (SYSVINIT_ACTIVE || !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_preprocess (file->name, 0))
	    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 (pies_envop)
    {
      environ_t *env;

      if ((env = environ_create (environ)) == NULL)
	{
	  logmsg (LOG_CRIT, "environ_create: %s", strerror (errno));
	  exit (EX_OSERR);
	}
      if (envop_exec (pies_envop, env))
	{
	  logmsg (LOG_CRIT, "environ_exec: %s", strerror (errno));
	  exit (EX_OSERR);
	}
      environ = environ_ptr (env);

      if (debug_level >= 4)
	{
	  int i;
	  logmsg_printf (LOG_DEBUG, "environment: ");
	  for (i = 0; environ[i]; i++)
	    logmsg_printf (LOG_DEBUG, "%s ", environ[i]);
	  logmsg_printf (LOG_DEBUG, "\n");
	}
    }

  if (pies_mode == MODE_LINT)
    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);

    case COM_LIST_SHUTDOWN_SEQUENCE:
      components_list_shutdown_sequence ();
      exit (0);

    default:
      pies_priv_setup (&pies_privs);
      if (pies_umask)
	umask (pies_umask);
    }

  if (SYSVINIT_ACTIVE)
    {
      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"), PACKAGE_NAME, PACKAGE_VERSION);

  if (!SYSVINIT_ACTIVE)
    {
      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 ();
  program_init_startup ();
  progman_start ();

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

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

	  //pies_schedule_children (PIES_CHLD_WAKEUP);
	  action = ACTION_CONT;
	  break;

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

	case ACTION_CTRLALTDEL:
	  debug (1, ("ctrl-alt-del"));
	  if (SYSVINIT_ACTIVE)
	    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"));
	  if (SYSVINIT_ACTIVE)
	    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"));
	  if (SYSVINIT_ACTIVE)
	    sysvinit_power ();
	  pies_schedule_children (PIES_CHLD_WAKEUP);
	  action = ACTION_CONT;
	}
      if (action == ACTION_CONT)
	{
	  if (children_op_toggle (PIES_CHLD_GC))
	    progman_gc ();
	  if (children_op_toggle (PIES_CHLD_CLEANUP))
	    progman_cleanup (CLEANUP_DEFAULT);
	  /* The order is important here: progman_wake_sleeping
	     can raise PIES_CHLD_RESCHEDULE_ALARM */
	  if (children_op_toggle (PIES_CHLD_WAKEUP))
	    progman_wake_sleeping (1);
	  if (children_op_toggle (PIES_CHLD_RESCHEDULE_ALARM))
	    progman_recompute_alarm ();
	}
    }
  while (SYSVINIT_ACTIVE || action == ACTION_CONT);

  progman_stop ();
  remove_pidfile (pidfile);

  if (action == ACTION_RESTART)
    {
      pies_close_fds (3);
      signal_setup (SIG_DFL);
      execv (pies_master_argv[0], pies_master_argv);
    }

  logmsg (LOG_INFO, _("%s %s terminated"), PACKAGE_NAME, PACKAGE_VERSION);
  exit (EX_OK);
}

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