/* This file is part of GNU Pies.
   Copyright (C) 2007-2019 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 "prog.h"

static struct prog *proghead, *progtail;
static struct grecs_symtab *conn_tab;

struct prog *
progman_locate (const char *name)
{
  struct prog *prog;

  for (prog = proghead; prog; prog = prog->next)
    if (strcmp (prog_tag (prog), name) == 0)
      break;
  return prog;
}

int
progman_foreach (int (*filter) (struct prog *, void *data), void *data)
{
  struct prog *prog;
  int rc = 0;
  for (prog = proghead; prog; )
    {
      struct prog *next = prog->next;
      if ((rc = filter (prog, data)) != 0)
	break;
      prog = next;
    }
  return rc;
}

static struct prog *
prog_lookup_by_pid (pid_t pid)
{
  struct prog *prog;

  for (prog = proghead; prog; prog = prog->next)
    if (prog->pid == pid)
      break;
  return prog;
}

struct component *
progman_lookup_component (const char *tag)
{
  struct prog *prog;
  for (prog = proghead; prog; prog = prog->next)
    if (IS_COMPONENT (prog) && strcmp (prog_tag (prog), tag) == 0)
      return prog->v.p.comp;
  return NULL;
}

struct component *
progman_lookup_tcpmux (const char *service, const char *master)
{
  struct prog *prog;
  for (prog = proghead; prog; prog = prog->next)
    if (IS_COMPONENT (prog)
	&& ISCF_TCPMUX (prog->v.p.comp->flags)
	&& prog->active
	&& prog->v.p.comp->service
	&& strcasecmp (prog->v.p.comp->service, service) == 0
	&& prog->v.p.comp->tcpmux
	&& strcmp (prog->v.p.comp->tcpmux, master) == 0)
      return prog->v.p.comp;
  return NULL;
}

void prog_stop (struct prog *prog, int sig);
static int prog_start_prerequisites (struct prog *prog);

char const *
prog_tag (struct prog const *prog)
{
  switch (prog->type)
     {
     case TYPE_COMPONENT:
       return prog->v.p.comp->tag;

     case TYPE_REDIRECTOR:
       return prog->v.r.tag;

     case TYPE_COMMAND:
       return prog->v.c.tag;
     }
   abort ();
 }

void
link_prog (struct prog *prog, struct prog *ref)
{
  if (!ref)
    {
      prog->prev = NULL;
      prog->next = proghead;
      if (proghead)
	proghead->prev = prog;
      else
	progtail = prog;
      proghead = prog;
    }
  else
    {
      struct prog *x;

      prog->prev = ref;
      prog->next = ref->next;

      if ((x = ref->next))
	x->prev = prog;
      else
	progtail = prog;

      ref->next = prog;
    }
}

void
unlink_prog (struct prog *pp)
{
  struct prog *x;

  if ((x = pp->prev))
    x->next = pp->next;
  else
    proghead = pp->next;
  if ((x = pp->next))
    x->prev = pp->prev;
  else
    progtail = pp->prev;
}

void
destroy_prog (struct prog **pp)
{
  struct prog *p = *pp;

  unlink_prog (p);
  switch (p->type)
    {
    case TYPE_COMPONENT:
      component_ref_decr (p->v.p.comp);
      if (p->v.p.status == status_listener && p->v.p.socket != -1)
	deregister_socket (p->v.p.socket);
	/* FIXME: Remove also all dependent progs (esp. tcpmux) */
      if (p->v.p.redir[RETR_OUT])
	p->v.p.redir[RETR_OUT]->v.r.master = NULL;
      if (p->v.p.redir[RETR_ERR])
	p->v.p.redir[RETR_ERR]->v.r.master = NULL;
      break;

    case TYPE_REDIRECTOR:
      {
	struct prog *master = p->v.r.master;
	component_ref_decr (p->v.r.comp);
	if (master)
	  {
	    if (p == master->v.p.redir[0])
	      master->v.p.redir[0] = NULL;
	    else if (p == master->v.p.redir[1])
	      master->v.p.redir[1] = NULL;
	  }
	/*	else
		logmsg (LOG_NOTICE, _("orphan redirector: %s"), p->tag);*/
	free (p->v.r.tag);
      }
      break;

    case TYPE_COMMAND:
      free (p->v.c.tag);
      free (p->v.c.command);
    }
  free (p);
  *pp = NULL;
}

static char *
redir_tag (struct prog *master, int type)
{
  static char *redirstr[2] = { "stdout", "stderr" };
  char *str = NULL;
  size_t len = 0;
  char const *tag = prog_tag (master);
  if (type < ARRAY_SIZE(redirstr))
    grecs_asprintf (&str, &len, "%s/%s", tag, redirstr[type]);
  else
    grecs_asprintf (&str, &len, "%s/%d", tag, type);
  return str;
}

static struct prog *
register_redir (int type, struct prog *master)
{
  char *tag = redir_tag (master, type);
  struct prog *pp = grecs_zalloc (sizeof (*pp));

  pp->type = TYPE_REDIRECTOR;
  pp->v.r.tag = tag;
  pp->v.r.master = master;
  pp->v.r.comp = master->v.p.comp;
  component_ref_incr (pp->v.r.comp);
  link_prog (pp, NULL);
  return pp;
}

void
update_redir (int type, struct prog *master, pid_t pid)
{
  struct prog *pp;
  if (master->v.p.redir[type])
    {
      pp = master->v.p.redir[type];
      prog_stop (pp, SIGKILL); /* Just in case */
    }

  pp = register_redir (type, master);
  master->v.p.redir[type] = pp;
  pp->pid = pid;
}

/* Given the component COMP find the element of the program list
   after which to link in the new prog associated with that COMP.
   The progs in the resulting list must be arranged exactly in the
   same order as the corresponding components. Points to note:
   1. If comp->prog is already present, follow progs in its next
   chain until the last one that points to the same COMP is found.
   2. Otherwise, if that component is not the first one, step back
   to the previous component and do the same for its comp->prog chain.
   3. Return the prog found.
*/
static struct prog *
find_prog_ref (struct component *comp)
{
  struct prog *prog;

  if (!comp->prog)
    {
      comp = comp->prev;
      if (!comp)
	return NULL; /* FIXME: Skip redirectors? */
    }

  if (comp->prog)
    {
      for (prog = comp->prog;
	   prog->next
	     && IS_COMPONENT (prog->next)
	     && prog->next->v.p.comp == comp;
	   prog = prog->next)
	;
    }
  else
    prog = NULL;
  return prog;
}

static struct prog *
register_prog0 (struct component *comp)
{
  struct prog *newp;

  newp = grecs_zalloc (sizeof (*newp));
  newp->type = TYPE_COMPONENT;
  newp->pid = 0;
  newp->v.p.comp = comp;
  newp->v.p.socket = -1;

  if (comp->mode == pies_comp_inetd)
    newp->v.p.status = status_listener;
  else
    newp->v.p.status = status_stopped;

  if ((comp->flags & CF_DISABLED)
      || comp->mode == pies_comp_ondemand
      || comp->mode == pies_comp_shutdown)
    newp->active = 0;
  else
    newp->active = 1;

  switch (comp->mode)
    {
    case pies_comp_exec:
    case pies_comp_startup:
    case pies_comp_shutdown:
      break;
	  
    default:
      comp->redir[RETR_OUT].type = redir_null;
    }
  
  link_prog (newp, find_prog_ref (comp));
  component_ref_incr (comp);
  return newp;
}

void
register_prog (struct component *comp)
{
  comp->prog = register_prog0 (comp);
}

void
register_command (char *tag, char *command, pid_t pid)
{
  struct prog *newp = grecs_zalloc (sizeof (*newp));
  newp->type = TYPE_COMMAND;
  newp->pid = pid;
  newp->v.c.tag = grecs_strdup (tag);
  newp->v.c.command = command;
  link_prog (newp, progtail);
}

static inline int
progman_startup_phase (void)
{
  struct prog *prog;

  for (prog = proghead; prog; prog = prog->next)
    {
      if (IS_COMPONENT (prog) && prog->v.p.comp->mode == pies_comp_startup
	  && prog->v.p.status == status_running)
	return 1;
    }
  return 0;
}

int
progman_waiting_p (void)
{
  struct prog *prog;

  for (prog = proghead; prog; prog = prog->next)
    {
      if (IS_COMPONENT (prog)
	  && prog->v.p.status == status_running
	  && (prog->wait || prog->v.p.comp->mode == pies_comp_startup))
	{
	  debug (3, ("%s: waiting for %s (%lu)",
		     __FUNCTION__, prog_tag (prog),
		     (unsigned long) prog->pid));
	  return 1;
	}
    }
  return 0;
}

RETSIGTYPE
redir_exit (int sig)
{
  _exit (0);
}

int
redirect_to_file (struct prog *master, int stream)
{
  struct passwd *pw;
  int fd = open (master->v.p.comp->redir[stream].v.file, O_RDWR|O_CREAT,
		 0644 & ~master->v.p.comp->umask);
  if (fd == -1)
    {
      logmsg (LOG_ERR, _("cannot open output file %s: %s"),
		master->v.p.comp->redir[stream].v.file,
		strerror (errno));
      return -1;
    }
  /* Fix file ownership */
  if (master->v.p.comp->privs.user
      && (pw = getpwnam (master->v.p.comp->privs.user)) != NULL)
    {
      if (chown (master->v.p.comp->redir[stream].v.file,
		 pw->pw_uid, pw->pw_gid))
	logmsg (LOG_ERR, "chown %s: %s",
		master->v.p.comp->redir[stream].v.file,
		strerror (errno));
    }
  return fd;
}

static void
close_fds (fd_set *fdset)
{
  int i;

  for (i = FD_SETSIZE-1; i >= 0; i--)
    {
      if (fdset && FD_ISSET (i, fdset))
	continue;
      close (i);
    }
}

void
free_redirector (struct redirector *rp)
{
  if (rp->type == redir_file)
    free (rp->v.file);
}

int
open_redirector (struct prog *master, int stream)
{
  int p[2];
  FILE *fp;
  char *buf = NULL;
  size_t size = 0;
  pid_t pid;
  int prio;
  char *tag;
  fd_set fdset;

  switch (master->v.p.comp->redir[stream].type)
    {
    case redir_null:
      return -1;

    case redir_file:
      return redirect_to_file (master, stream);

    case redir_syslog:
      break;
    }

  if (pipe (p))
    {
      logmsg (LOG_CRIT, "pipe: %s", strerror (errno));
      return -1;
    }

  switch (pid = fork ())
    {
    case 0:
      /* Redirector process */
      tag = redir_tag (master, stream);
      mf_proctitle_format ("%s redirector", tag);
      free (tag);

      FD_ZERO (&fdset);
      FD_SET (p[0], &fdset);
      close_fds (&fdset);

      diag_setup (0);
      signal_setup (redir_exit);

      close (p[1]);
      fp = fdopen (p[0], "r");
      if (fp == NULL)
	_exit (1);
      openlog (prog_tag (master), LOG_PID, master->v.p.comp->facility);
      prio = master->v.p.comp->redir[stream].v.prio;
      while (getline (&buf, &size, fp) > 0)
	syslog (prio, "%s", buf);
      _exit (0);

    case -1:
      logmsg (LOG_CRIT,
		      _("cannot run redirector `%s': fork failed: %s"),
	      prog_tag (master), strerror (errno));
      return -1;

    default:
      debug (1, (_("redirector for %s started, pid=%lu"),
		 prog_tag (master), (unsigned long) pid));
      update_redir (stream, master, pid);
      close (p[0]);
      return p[1];
    }
}


static unsigned
conn_class_hasher (void *data, unsigned long n_buckets)
{
  struct conn_class *pcclass = data;
  unsigned char const *tag = (unsigned char const *)pcclass->tag;
  size_t value = 0;
  unsigned char ch;
  size_t len;

  while ((ch = *tag++))
    value = (value * 31 + ch) % n_buckets;

  for (tag = (const unsigned char *)&pcclass->sa_storage,
	 len = pcclass->sa_len;
       len;
       tag++, len--)
    value = (value * 31 + *tag) % n_buckets;
  return value;
}

/* Compare two strings for equality.  */
static int
conn_class_compare (void const *data1, void const *data2)
{
  struct conn_class const *p1 = data1;
  struct conn_class const *p2 = data2;

  return !(p1->sa_len == p2->sa_len &&
	   memcmp (&p1->sa_storage, &p2->sa_storage, p1->sa_len) == 0 &&
	   strcmp (p1->tag, p2->tag) == 0);
}

static void
conn_class_free (void *data)
{
  free (data);
}

static int
conn_class_copy(void *a, void *b)
{
  memcpy (a, b, sizeof(struct conn_class));
  memset (b, 0, sizeof(struct conn_class));
  return 0;
}

static struct conn_class *
conn_class_lookup (const char *tag,
		  union pies_sockaddr_storage const *sa_storage_ptr,
		  size_t sa_len)
{
  int install = 1;
  struct conn_class *probe, *ret;

  probe = grecs_malloc (sizeof (probe[0]));
  probe->tag = tag;
  probe->sa_storage = *sa_storage_ptr;
  probe->sa_len = sa_len;
  switch (probe->sa_storage.s.sa_family)
    {
    case AF_INET:
      probe->sa_storage.s_in.sin_port = 0;
      break;

    case AF_UNIX:
      break;

    default:
      logmsg (LOG_ERR, _("unexpected address family: %d"),
	      probe->sa_storage.s.sa_family);
      break;
    }

  probe->count = 0;
  if (!conn_tab)
    {
      conn_tab = grecs_symtab_create(sizeof (struct conn_class),
				     conn_class_hasher,
				     conn_class_compare,
				     conn_class_copy,
				     NULL,
				     conn_class_free);
      if (!conn_tab)
	grecs_alloc_die ();
    }

  ret = grecs_symtab_lookup_or_install (conn_tab, probe, &install);
  if (!ret)
    {
      logmsg (LOG_CRIT, "cannot allocate conn_class: %s",
	      strerror (errno));
      abort ();
    }
  free (probe);
  return ret;
}

static void
conn_class_report (int prio, struct conn_class *pcclass)
{
  char *s = sockaddr_to_astr ((struct sockaddr *)&pcclass->sa_storage,
			      pcclass->sa_len);
  logmsg (prio, _("connections in class %s/%s: %lu"),
	  pcclass->tag, s, (unsigned long)pcclass->count);
  free (s);
}

static void
conn_class_remove (struct conn_class *pcclass)
{
  if (!conn_tab)
    {
      logmsg (LOG_CRIT, "attempt to free unexisting conn_class %p", pcclass);
      conn_class_report (LOG_CRIT, pcclass);
      abort ();
    }
  grecs_symtab_remove (conn_tab, pcclass);
}


extern char **environ;    /* Environment */
static size_t envsize;    /* Size of environ. If it is not 0, then we
			     have allocated space for the environ. */

#define ENV_PROTO         "PROTO"
#define ENV_SOCKTYPE      "SOCKTYPE"
#define ENV_LOCALIP       "LOCALIP"
#define ENV_LOCALPORT     "LOCALPORT"
#define ENV_LOCALHOST     "LOCALHOST"
#define ENV_REMOTEIP      "REMOTEIP"
#define ENV_REMOTEPORT    "REMOTEPORT"
#define ENV_REMOTEHOST    "REMOTEHOST"
static char *sockenv_hint[] = {
  "-" ENV_PROTO,
  "-" ENV_SOCKTYPE,
  "-" ENV_LOCALIP,
  "-" ENV_LOCALPORT,
  "-" ENV_LOCALHOST,
  "-" ENV_REMOTEIP,
  "-" ENV_REMOTEPORT,
  "-" ENV_REMOTEHOST,
  NULL
};

#define DEBUG_ENVIRON(l)				\
  do							\
    if (debug_level >= (l))				\
      {							\
	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");		\
      }							\
  while (0)

static void
add_env (const char *name, const char *value)
{
  size_t i;
  size_t namelen = strlen (name);
  char *p;

  for (i = 0; environ[i]; i++)
    {
      if (!strncmp (environ[i], name, namelen) && environ[i][namelen] == '=')
	break;
    }

  if (environ[i] == NULL)
    {
      if (envsize == 0)
	{
	  char **new_env;

	  envsize = i + 4;
	  new_env = grecs_calloc (envsize, sizeof new_env[0]);
	  for (i = 0; (new_env[i] = environ[i]); i++)
	    ;
	  environ = new_env;
	}
      else if (i == envsize)
	{
	  envsize += 4;
	  environ = grecs_realloc (environ,
				   envsize * sizeof environ[0]);
	}
      environ[i+1] = NULL;
    }

  if (!value)
    value = "";
  p = grecs_malloc (namelen + 1 + strlen (value) + 1);
  strcpy (p, name);
  p[namelen] = '=';
  strcpy (p + namelen + 1, value);
  environ[i] = p;
}

/* Find variable NAME in environment ENV.
   On success, store the index of the ENV slot in *IDX,
   the offset of the value (position right past '=') in *VALOFF, and
   return 0 (IDX and/or VALOFF can be NULL, if that info is not needed).
   Return -1 if NAME was not found. */
static int
find_env_pos (char **env, char *name, size_t *idx, size_t *valoff)
{
  size_t nlen = strcspn (name, "+=");
  size_t i;

  for (i = 0; env[i]; i++)
    {
      size_t elen = strcspn (env[i], "=");
      if (elen == nlen && memcmp (name, env[i], nlen) == 0)
	{
	  if (idx)
	    *idx = i;
	  if (valoff)
	    *valoff = elen + 1;
	  return 0;
	}
    }
  return -1;
}

/* Find variable NAME in environment ENV.
   On success, return pointer to the variable assignment (if VAL is 0),
   or to the value (if VAL is 1).
   Return NULL if NAME is not present in ENV. */
static char *
find_env_ptr (char **env, char *name, int val)
{
  size_t i, j;
  if (find_env_pos (env, name, &i, &j))
    return NULL;
  return val ? env[i] + j : env[i];
}

/* Return 1 if ENV contains a matching unset statement for variable NAME. */
static int
var_is_unset (char **env, const char *name)
{
  int i;
  int nlen = strcspn (name, "=");

  for (i = 0; env[i]; i++)
    {
      if (env[i][0] == '-')
	{
	  size_t elen = strcspn (env[i] + 1, "=");
	  if (elen == nlen && memcmp (name, env[i] + 1, nlen) == 0)
	    {
	      if (env[i][nlen + 1])
		return strcmp (name + nlen, env[i] + 1 + nlen) == 0;
	      else
		return 1;
	    }
	}
    }
  return 0;
}

static char *
env_concat (const char *name, size_t namelen, const char *a, const char *b)
{
  char *res;
  size_t len;

  if (a && b)
    {
      res = grecs_malloc (namelen + 1 + strlen (a) + strlen (b) + 1);
      strcpy (res + namelen + 1, a);
      strcat (res + namelen + 1, b);
    }
  else if (a)
    {
      len = strlen (a);
      if (c_ispunct (a[len-1]))
	len--;
      res = grecs_malloc (namelen + 1 + len + 1);
      memcpy (res + namelen + 1, a, len);
      res[namelen + 1 + len] = 0;
    }
  else /* if (a == NULL) */
    {
      if (c_ispunct (b[0]))
	b++;
      len = strlen (b);
      res = grecs_malloc (namelen + 1 + len + 1);
      strcpy (res + namelen + 1, b);
    }
  memcpy (res, name, namelen);
  res[namelen] = '=';
  return res;
}

static void
environ_setup (char **hint)
{
  char **old_env = environ;
  char **new_env;
  size_t count, i, j, n;

  if (!hint)
    return;

  if (strcmp (hint[0], "-") == 0)
    {
      old_env = NULL;
      hint++;
    }

  /* Count new environment size */
  count = 0;
  if (old_env)
    for (i = 0; old_env[i]; i++)
      count++;

  for (i = 0; hint[i]; i++)
    count++;

  /* Allocate new environment. */
  new_env = grecs_calloc (count + 1, sizeof new_env[0]);

  /* Populate the environment. */
  n = 0;

  if (old_env)
    for (i = 0; old_env[i]; i++)
      {
	if (!var_is_unset (hint, old_env[i]))
	  new_env[n++] = old_env[i];
      }

  for (i = 0; hint[i]; i++)
    {
      char *p;

      if (hint[i][0] == '-')
	{
	  /* Skip unset directives. */
	  continue;
	}

      /* Find the slot for the variable.  Use next available
	 slot if there's no such variable in new_env */
      if (find_env_pos (new_env, hint[i], &j, NULL))
	j = n;

      if ((p = strchr (hint[i], '=')))
	{
	  if (p == hint[i])
	    continue; /* Ignore erroneous entry */
	  if (p[-1] == '+')
	    new_env[j] = env_concat (hint[i], p - hint[i] - 1,
				     find_env_ptr (environ, hint[i], 1),
				     p + 1);
	  else if (p[1] == '+')
	    new_env[j] = env_concat (hint[i], p - hint[i],
				     p + 2,
				     find_env_ptr (environ, hint[i], 1));
	  else
	    new_env[j] = hint[i];
	}
      else if ((p = find_env_ptr (environ, hint[i], 0)))
	new_env[j] = p;
      else
	continue;

      /* Adjust environment size */
      if (j == n)
	++n;
    }
  new_env[n] = NULL;

  if (envsize)
    free (environ);

  environ = new_env;
  envsize = count + 1;
}

/* Pass socket information in environment variables. */
void
prog_sockenv (struct prog *prog)
{
  char buf[INT_BUFSIZE_BOUND (uintmax_t)];
  char *p;
  struct hostent *host;
  union pies_sockaddr_storage sa_server;
  socklen_t len = sizeof (sa_server);
  union pies_sockaddr_storage *sa_client = &prog->v.p.sa_storage;
  socklen_t cltlen = prog->v.p.sa_len;
  const char *proto = NULL;

  if (!(prog->v.p.comp->flags & CF_SOCKENV))
    return;

  if (socket_type_to_str (prog->v.p.comp->socket_type, &proto))
    proto = umaxtostr (prog->v.p.comp->socket_type, buf);
  add_env (ENV_SOCKTYPE, proto);

  if (prog->v.p.comp->socket_url)
    proto = prog->v.p.comp->socket_url->proto_s;
  else if (prog->v.p.listener)
    proto = prog->v.p.listener->v.p.comp->socket_url->proto_s;
  else if (ISCF_TCPMUX (prog->v.p.comp->flags))
    proto = "TCP";

  add_env (ENV_PROTO, proto);

  if (getsockname (prog->v.p.socket,
		   (struct sockaddr *) &sa_server, &len) < 0)
    logmsg (LOG_WARNING, "getsockname(): %s", strerror (errno));
  else if (sa_server.s.sa_family == AF_INET)
    {
      p = inet_ntoa (sa_server.s_in.sin_addr);
      if (p)
	add_env (ENV_LOCALIP, p);

      p = umaxtostr (ntohs (sa_server.s_in.sin_port), buf);
      add_env (ENV_LOCALPORT, p);

      if (prog->v.p.comp->flags & CF_RESOLVE)
	{
	  if ((host = gethostbyaddr ((char *) &sa_server.s_in.sin_addr,
				     sizeof (sa_server.s_in.sin_addr),
				     AF_INET)) == NULL)
	    logmsg (LOG_WARNING, "gethostbyaddr: %s", strerror (errno));
	  else
	    add_env (ENV_LOCALHOST, host->h_name);
	}
    }

  if (cltlen > 0 && sa_client->s.sa_family == AF_INET)
    {
      p = inet_ntoa (sa_client->s_in.sin_addr);
      if (p)
	add_env (ENV_REMOTEIP, p);

      p = umaxtostr (ntohs (sa_client->s_in.sin_port), buf);
      add_env (ENV_REMOTEPORT, p);

      if (prog->v.p.comp->flags & CF_RESOLVE)
	{
	  if ((host = gethostbyaddr ((char *) &sa_client->s_in.sin_addr,
				     sizeof (sa_client->s_in.sin_addr),
				     AF_INET)) == NULL)
	    logmsg (LOG_WARNING, "gethostbyaddr: %s",
		    strerror (errno));
	  else
	    add_env (ENV_REMOTEHOST, host->h_name);
	}
    }
  /* FIXME: $REMOTEINFO ? */
  DEBUG_ENVIRON (4);
}

static int
check_rate (struct prog *prog, unsigned testtime, size_t max_count)
{
  time_t now;

  time (&now);

  if (prog->v.p.timestamp + testtime > now)
    prog->v.p.failcount++;
  else
    {
      prog->v.p.failcount = 0;
      prog->v.p.timestamp = now;
    }

  if (prog->v.p.failcount > max_count)
    {
      prog->v.p.timestamp = now;
      prog->v.p.status = status_sleeping;
      pies_schedule_children (PIES_CHLD_RESCHEDULE_ALARM);
      return 1;
    }

  return 0;
}

static int
check_spawn_rate (struct prog *prog)
{
  if (check_rate (prog, TESTTIME, MAXSPAWN))
    {
      logmsg (LOG_NOTICE,
	      ngettext ("%s is respawning too fast, disabled for %d minute",
			"%s is respawning too fast, disabled for %d minutes",
			SLEEPTIME / 60),
	      prog_tag (prog), SLEEPTIME / 60);
      return 1;
    }
  return 0;
}

static int
check_connection_rate (struct prog *prog)
{
  size_t max_rate = prog->v.p.comp->max_rate
		       ? prog->v.p.comp->max_rate : default_max_rate;
  if (max_rate && check_rate (prog, 60, max_rate))
    {
      logmsg (LOG_NOTICE,
	      ngettext ("%s is starting too often, disabled for %d minute",
			"%s is starting too often, disabled for %d minutes",
			SLEEPTIME / 60),
	      prog_tag (prog), SLEEPTIME / 60);
      return 1;
    }
  return 0;
}

static int
prog_open_socket (struct prog *prog)
{
  prog->v.p.socket = create_socket (prog->v.p.comp->socket_url,
				    prog->v.p.comp->socket_type,
				    prog->v.p.comp->privs.user,
				    prog->v.p.comp->umask);
  if (prog->v.p.socket == -1)
    {
      prog->v.p.status = status_stopped;
      prog->active = 0;
      return 1;
    }
  if (listen (prog->v.p.socket, 8))
    {
      logmsg (LOG_ERR, "listen: %s", strerror (errno));
      close (prog->v.p.socket);
      prog->v.p.socket = -1;
      prog->v.p.status = status_stopped;
      prog->active = 0;
      return 1;
    }
  return 0;
}

static void
prog_start_prologue (struct prog *prog)
{
  if (prog->v.p.comp->dir)
    {
      debug (1, (_("chdir %s"), prog->v.p.comp->dir));
      if (chdir (prog->v.p.comp->dir))
	logmsg (LOG_ERR, _("%s: cannot change to directory %s: %s"),
		prog_tag (prog), prog->v.p.comp->dir, strerror (errno));
    }

  environ_setup (prog->v.p.comp->env ?
		 prog->v.p.comp->env :
		 ((prog->v.p.comp->flags & CF_SOCKENV) ? sockenv_hint : NULL));
  if (init_process)
    environ_setup (sysvinit_environ_hint);
  DEBUG_ENVIRON (4);

  pies_priv_setup (&prog->v.p.comp->privs);
  if (prog->v.p.comp->umask)
    umask (prog->v.p.comp->umask);

  set_limits (prog_tag (prog),
	      prog->v.p.comp->limits ?
	      prog->v.p.comp->limits : pies_limits);

  if (debug_level >= 1)
    {
      int i;
      struct component *comp = prog->v.p.comp;

      logmsg_printf (LOG_DEBUG, "executing");
      for (i = 0; i < comp->argc; i++)
	logmsg_printf (LOG_DEBUG, " %s", quotearg (comp->argv[i]));
      logmsg_printf (LOG_DEBUG, "\n");
    }
}

static void
prog_execute (struct prog *prog)
{
  if (prog->v.p.comp->builtin)
    {
      mf_proctitle_format ("inetd %s",
			   prog->v.p.comp->builtin->service);
      prog->v.p.comp->builtin->fun (0, prog->v.p.comp);
      _exit (0);
    }

  execvp (prog->v.p.comp->program ?
	  prog->v.p.comp->program : prog->v.p.comp->argv[0],
	  prog->v.p.comp->argv);
  openlog (log_tag, LOG_PID, prog->v.p.comp->facility);
  syslog (LOG_CRIT, _("cannot start `%s': %s"), prog_tag (prog),
	  strerror (errno));
  _exit (EX_SOFTWARE);
}

void
progman_run_comp (struct component *comp, int fd,
		  union pies_sockaddr_storage *sa, socklen_t salen)
{
  struct prog *prog = register_prog0 (comp);
  prog->v.p.socket = fd;
  prog->v.p.sa_storage = *sa;
  prog->v.p.sa_len = salen;
  prog->v.p.cclass = conn_class_lookup (comp->tag, sa, salen);
  prog_start_prologue (prog);
  prog_sockenv (prog);
  prog_execute (prog);
}

static void
prog_start (struct prog *prog)
{
  pid_t pid;
  int redir[2];
  fd_set fdset;

  if (prog->pid > 0 || !IS_COMPONENT (prog))
    return;

  if (is_sysvinit (prog->v.p.comp))
    {
      if (!init_process)
	{
	  if (prog->active)
	    {
	      logmsg (LOG_NOTICE, "disabling sysvinit component %s",
		      prog_tag (prog));
	      prog->v.p.status = status_stopped;
	      prog->active = 0;
	    }
	  return;
	}
    }

  /* This call returns 1 in two cases: Either prog is marked as disabled,
     in which case there's nothing more to do, or one or more of its
     prerequisites are in status_stopping. In the latter case either the
     components in question will exit or a SIGALRM will get delivered. In
     both cases, it will cause further processing of `prog'. */
  if (prog_start_prerequisites (prog))
    return;

  switch (prog->v.p.comp->mode)
    {
    case pies_comp_exec:
      if (check_spawn_rate (prog))
	return;
      break;

    case pies_comp_pass_fd:
      if (check_spawn_rate (prog))
	return;
      debug (1, (_("unlinking %s"), prog->v.p.comp->pass_fd_socket));
      if (unlink (prog->v.p.comp->pass_fd_socket) && errno != ENOENT)
	{
	  logfuncall ("unlink", prog->v.p.comp->pass_fd_socket, errno);
	  return;
	}
      if (prog_open_socket (prog))
	return;
      break;

    case pies_comp_accept:
      if (check_spawn_rate (prog))
	return;
      if (prog_open_socket (prog))
	return;
      break;

    case pies_comp_inetd:
      /* Wait until an incoming connection is requested */
      if (prog->v.p.socket == -1)
	return;

    default:
      break;
    }

  debug (1, (_("starting %s"), prog_tag (prog)));

  if (prog->v.p.comp->rmfile)
    {
      debug (1, (_("unlinking %s"), prog->v.p.comp->rmfile));
      if (unlink (prog->v.p.comp->rmfile) && errno != ENOENT)
	logmsg (LOG_ERR, _("%s: cannot remove file `%s': %s"),
		prog_tag (prog), prog->v.p.comp->rmfile, strerror (errno));
    }

  if (prog->v.p.comp->builtin && prog->v.p.comp->builtin->single_process)
    {
      prog->v.p.comp->builtin->fun (prog->v.p.socket, prog->v.p.comp);
      return;
    }

  redir[RETR_OUT] = open_redirector (prog, RETR_OUT);
  redir[RETR_ERR] = open_redirector (prog, RETR_ERR);

  switch (pid = fork ())
    {
      /* The child branch.  */
    case 0:
      signal_setup (SIG_DFL);
      setsid ();
      prog_start_prologue (prog);
      switch (prog->v.p.comp->mode)
	{
	case pies_comp_accept:
	case pies_comp_inetd:
	  prog_sockenv (prog);

	  dup2 (prog->v.p.socket, 0);
	  dup2 (prog->v.p.socket, 1);
	  close (prog->v.p.socket);
	  prog->v.p.socket = -1;
	  break;

	default:
	  if (init_process)
	    {
	      int fd = console_open (O_RDWR|O_NOCTTY);
	      if (fd < 0)
		{
		  logmsg (LOG_CRIT, "open(%s): %s",
			  console_device, strerror (errno));
		  fd = open ("/dev/null", O_RDWR);
		}
	      if (fd != 0)
		dup2 (fd, 0);
	      if (fd != 1)
		dup2 (fd, 1);
	      if (fd != 2)
		dup2 (fd, 2);
	    }
	  else
	    {
	      if (prog->v.p.comp->flags & CF_NULLINPUT)
		{
		  close(0);
		  if (open("/dev/null", O_RDONLY) != 0)
		    {
		      logmsg (LOG_CRIT, "can't open /dev/null: %s",
			      strerror(errno));
		      _exit(127);
		    }
		}

	      if (redir[RETR_OUT] == -1)
		{
		  close (1);
		  open ("/dev/null", O_WRONLY);
		}
	      else if (redir[RETR_OUT] != 1)
		{
		  dup2 (redir[RETR_OUT], 1);
		}
	    }
	  break;
	}

      if (!init_process)
	{
	  if (redir[RETR_ERR] == -1)
	    {
	      if (!DIAG_OUTPUT (DIAG_TO_STDERR))
		{
		  close (2);
		  open ("/dev/null", O_WRONLY);
		}
	    }
	  else if (redir[RETR_ERR] != 1)
	    {
	      dup2 (redir[RETR_ERR], 2);
	    }
	}

      /* Close unneeded descripitors */
      FD_ZERO (&fdset);
      FD_SET (0, &fdset);
      FD_SET (1, &fdset);
      FD_SET (2, &fdset);
      if (prog->v.p.comp->mode == pies_comp_pass_fd)
	FD_SET (prog->v.p.socket, &fdset);
      close_fds (&fdset);

      prog_execute (prog);


    case -1:
      logmsg (LOG_CRIT,
		      _("cannot run `%s': fork failed: %s"),
	      prog_tag (prog), strerror (errno));
      break;

    default:
      if (prog->v.p.comp->mode == pies_comp_pass_fd)
	{
	  pass_fd (prog->v.p.comp->pass_fd_socket, prog->v.p.socket,
		   prog->v.p.comp->pass_fd_timeout ?
		    prog->v.p.comp->pass_fd_timeout : DEFAULT_PASS_FD_TIMEOUT);
	  /* FIXME: Error code */;
	}
      if (prog->v.p.comp->flags & CF_WAIT)
	{
	  disable_socket (prog->v.p.socket);
	}
      else if (prog->v.p.comp->mode == pies_comp_accept ||
	       prog->v.p.comp->mode == pies_comp_inetd ||
	       prog->v.p.comp->mode == pies_comp_pass_fd)
	close (prog->v.p.socket);
      else if (is_sysvinit (prog->v.p.comp))
	sysvinit_acct (SYSV_ACCT_PROC_START, "", prog_tag (prog), pid, "");

      if (redir[RETR_OUT] != -1)
	close (redir[RETR_OUT]);
      if (redir[RETR_ERR])
	close (redir[RETR_ERR]);
      prog->pid = pid;
      prog->v.p.status = status_running;
      debug (1, (_("%s started, pid=%lu"), prog_tag (prog),
		 (unsigned long) pid));
    }
}

int
check_acl (pies_acl_t acl, struct sockaddr *s, socklen_t salen,
	   pies_identity_t identity)
{
  struct acl_input input;
  int rc;

  if (!acl)
    return 0;

  input.addr = s;
  input.addrlen = salen;
  input.identity = identity;

  rc = pies_acl_check (acl, &input, 1);
  if (rc == 0)
    {
      char *p = sockaddr_to_astr (s, salen);
      logmsg (LOG_ERR, _("access from %s blocked"), p);
      free (p);
      return 1;
    }

  return 0;
}

void
fd_report (int fd, const char *msg)
{
  size_t len;

  if (!msg)
    return;

  for (len = strlen (msg); len; )
    {
      ssize_t rc = write (fd, msg, len);
      if (rc == -1)
	{
	  logmsg (LOG_ERR,
		  _("error writing to socket: %s"),
		  strerror (errno));
	  break;
	}
      else if (rc == 0)
	break;
      len -= rc;
      msg += rc;
    }
}

static int
_prog_accept (struct prog *p)
{
  int fd;
  struct prog *pinst;
  union pies_sockaddr_storage addr;
  socklen_t addrlen = sizeof addr;
  struct conn_class *pcclass;

  fd = accept (p->v.p.socket, (struct sockaddr*) &addr, &addrlen);
  if (fd == -1)
    {
      logfuncall ("accept", NULL, errno);
      return 1;
    }

  if (debug_level >= 1)
    {
      char *s = sockaddr_to_astr ((struct sockaddr *)&addr, addrlen);
      logmsg (LOG_DEBUG, _("%s wants %s"), s, prog_tag (p));
      free (s);
    }

  if (check_acl (p->v.p.comp->acl, (struct sockaddr *)&addr, addrlen, NULL)
      || check_acl (pies_acl, (struct sockaddr *)&addr, addrlen, NULL))
    {
      fd_report (fd, p->v.p.comp->access_denied_message);
      close (fd);
      return 1;
    }

  if (p->v.p.comp->max_instances &&
      p->v.p.num_instances >= p->v.p.comp->max_instances)
    {
      char *s = sockaddr_to_astr ((struct sockaddr *)&addr, addrlen);
      logmsg (LOG_ERR,
	      _("%s: access from %s denied: too many instances running"),
	      prog_tag (p), s);
      free (s);
      fd_report (fd, p->v.p.comp->max_instances_message);
      close (fd);
      return 1;
    }

  pcclass = conn_class_lookup (prog_tag (p), &addr, addrlen);
  if (p->v.p.comp->max_ip_connections &&
      pcclass->count >= p->v.p.comp->max_ip_connections)
    {
      char *s = sockaddr_to_astr ((struct sockaddr *)&addr, addrlen);
      logmsg (LOG_ERR,
	      _("%s: access from %s denied: "
		"too many connections from that ip"),
	      prog_tag (p), s);
      free (s);
      fd_report (fd, p->v.p.comp->max_ip_connections_message);
      close (fd);
      return 1;
    }

  if (check_connection_rate (p))
    {
      disable_socket (p->v.p.socket);
      close (fd);
      return 1;
    }

  pcclass->count++;

  if (debug_level > 1)
    conn_class_report (LOG_DEBUG, pcclass);

  pinst = register_prog0 (p->v.p.comp);
  pinst->v.p.socket = fd;
  pinst->v.p.listener = p;
  pinst->v.p.sa_storage = addr;
  pinst->v.p.sa_len = addrlen;
  pinst->v.p.cclass = pcclass;
  prog_start (pinst);
  close (fd);
  pinst->v.p.socket = -1;

  p->v.p.num_instances++;
  return 0;
}

static int
_prog_wait (struct prog *p)
{
  struct prog *pinst;

  debug (1, (_("someone wants %s"), prog_tag (p)));

  if (p->v.p.comp->max_instances
      && p->v.p.num_instances >= p->v.p.comp->max_instances)
    {
      logmsg (LOG_ERR,
	      _("%s: too many instances running, dropping connection"),
	      prog_tag (p));
      return 1;
    }

  pinst = register_prog0 (p->v.p.comp);
  pinst->v.p.socket = p->v.p.socket;
  pinst->v.p.listener = p;
  prog_start (pinst);

  pinst->v.p.socket = -1;

  p->v.p.num_instances++;
  return 0;
}

int
progman_accept (int socket, void *data)
{
  struct prog *p = data;

  if (p->v.p.comp->socket_type == SOCK_STREAM
      && !(p->v.p.comp->flags & CF_WAIT))
    return _prog_accept (p);

  return _prog_wait (p);
}

static int
prog_create_socket (struct prog *prog, void *data)
{
  if (IS_COMPONENT (prog) && prog->v.p.status == status_listener)
    {
      struct component *comp = prog->v.p.comp;
      if (!ISCF_TCPMUX (comp->flags) && prog->v.p.socket == -1)
	{
	  int fd = create_socket (comp->socket_url,
				  comp->socket_type,
				  comp->privs.user, comp->umask);
	  if (fd == -1)
	    destroy_prog (&prog);
	  else if (register_program_socket (comp->socket_type, fd, prog))
	    {
	      close (fd);
	      destroy_prog (&prog);
	    }
	  else
	    prog->v.p.socket = fd;
	}
    }
  return 0;
}

void
progman_create_sockets (void)
{
  progman_foreach (prog_create_socket, NULL);
}

void
progman_recompute_alarm (void)
{
  struct prog *prog;
  time_t now = time (NULL);
  time_t alarm_time = 0, x;

  debug (2, ("Recomputing alarm settings"));
  for (prog = proghead; prog; prog = prog->next)
    if (IS_COMPONENT (prog))
      {
	switch (prog->v.p.status)
	  {
	  case status_sleeping:
	    x = SLEEPTIME - (now - prog->v.p.timestamp);
	    if (alarm_time == 0 || x < alarm_time)
	      alarm_time = x;
	    break;

	  case status_stopping:
	    x = shutdown_timeout - (now - prog->v.p.timestamp);
	    if (alarm_time == 0 || x < alarm_time)
	      alarm_time = x;
	    break;

	  default:
	    break;
	  }
      }
  debug (2, ("alarm=%lu", (unsigned long)alarm_time));
  if (alarm_time)
    alarm (alarm_time);
}

void
program_init_startup (void)
{
  struct prog *prog;

  for (prog = proghead; prog; prog = prog->next)
    if (IS_COMPONENT (prog) && prog->v.p.comp->mode == pies_comp_startup)
      {
	debug (1, ("running startup components"));
	break;
      }

  for (; prog; prog = prog->next)
    if (IS_COMPONENT (prog) && prog->v.p.comp->mode == pies_comp_startup)
      {
	prog_start (prog);
      }
}

void
progman_start (void)
{
  struct prog *prog;

  if (progman_waiting_p ())
    /* Noting to do if there are processes left in the previous runlevel
       (in sysv-init mode) or startup components running. */
    return;

  debug (1, ("starting components"));
  for (prog = proghead; prog; prog = prog->next)
    if (IS_COMPONENT (prog))
      {
	switch (prog->v.p.status)
	  {
	  case status_stopped:
	  case status_sleeping:
	    prog_start (prog);
	    break;

	  case status_running:
	  case status_stopping:
	  case status_finished:
	    break;

	  case status_listener:
	    if (!prog->active)
	      disable_socket (prog->v.p.socket);
	    else
	      enable_socket (prog->v.p.socket);
	  }
      }
}

static void
check_stopping (struct prog *prog, time_t now)
{
  if (now - prog->v.p.timestamp >= shutdown_timeout)
    {
      if (prog->pid == 0)
	logmsg (LOG_EMERG,
		_("INTERNAL ERROR: attempting to kill unexisting process %s"),
		prog_tag (prog));
      else if (prog->v.p.comp->flags & CF_SIGGROUP)
	kill (-prog->pid, SIGKILL);
      else
	kill (prog->pid, SIGKILL);
    }
  else
    pies_schedule_children (PIES_CHLD_RESCHEDULE_ALARM);
}

void
progman_wake_sleeping (int onalrm)
{
  struct prog *prog;
  time_t now = time (NULL);

  debug (1, (_("checking for components to start")));

  for (prog = proghead; prog; prog = prog->next)
    {
      if (IS_ACTIVE_COMPONENT (prog) && prog->wait)
	{
	  /* The following works on the assumption that prog->wait is
	     set when enabling the component and gets cleared right after
	     it has finished. */
	  if (prog->v.p.status != status_running)
	    prog_start (prog);
	  return;
	}
    }

  for (prog = proghead; prog; prog = prog->next)
    switch (prog->v.p.status)
      {
      case status_sleeping:
	if (IS_ACTIVE_COMPONENT (prog))
	  {
	    if (now - prog->v.p.timestamp >= SLEEPTIME)
	      {
		if (prog->v.p.comp->mode == pies_comp_inetd)
		  {
		    prog->v.p.status = status_listener;
		    enable_socket (prog->v.p.socket);
		  }
		else
		  {
		    prog->v.p.status = status_running;
		    prog->v.p.failcount = 0;
		    prog->v.p.timestamp = 0;
		    prog_start (prog);
		  }
	      }
	    /* If there is no alarm pending, recompute next alarm.
	       This allows to cope with eventual clock inaccuracies. */
	    if (onalrm)
	      pies_schedule_children (PIES_CHLD_RESCHEDULE_ALARM);
	  }
	break;

      case status_stopping:
	check_stopping (prog, now);
	break;

      case status_stopped:
	if (IS_ACTIVE_COMPONENT (prog))
	  prog_start (prog);
	break;

      default:
	break;
      }
}

static int
prog_start_prerequisites (struct prog *prog)
{
  int ret = 0;
  pies_depmap_pos_t pos;
  struct component *comp;
  int warned = 0;

  if (!prog->active)
    return 1;
  for (comp = component_depmap_first (depmap_col, prog->v.p.comp->arridx, &pos);
       comp;
       comp = component_depmap_next (pos))
    {
      struct prog *p;

      if (!comp->prog || comp->flags & CF_PRECIOUS)
	continue;
      if (!warned)
	{
	  debug (1, ("starting prerequisites of %s", prog_tag (prog)));
	  warned = 1;
	}

      if (!prog->active)
	{
	  prog->active = 0;
	  return 1;
	}

      p = comp->prog;
      switch (p->v.p.status)
	{
	case status_running:
	  continue;

	case status_stopped:
	  break;

	case status_listener:
	  continue;

	case status_sleeping:
	  /* FIXME: What to do in this case? */
	  break;

	case status_stopping:
	  check_stopping (p, time (NULL));
	  ret = 1;
	  continue;

	case status_finished:
	  continue;
	}
      prog_start (p);
      if (p->v.p.comp->mode == pies_comp_once
	  || p->v.p.status != status_running)//FIXME
	ret = 1;
    }
  depmap_end (pos);

  return ret;
}

void
prog_stop_redirectors (struct prog *prog)
{
  if (prog->v.p.redir[RETR_OUT])
    prog_stop (prog->v.p.redir[RETR_OUT], SIGTERM);
  if (prog->v.p.redir[RETR_ERR])
    prog_stop (prog->v.p.redir[RETR_ERR], SIGTERM);
}

void
prog_stop_dependents (struct prog *prog)
{
  struct component *comp;
  pies_depmap_pos_t pos;
  int warned = 0;

  prog_stop_redirectors (prog);
  for (comp = component_depmap_first (depmap_row, prog->v.p.comp->arridx, &pos);
       comp;
       comp = component_depmap_next (pos))
    {
      struct prog *dp = comp->prog;
      if (!dp) /* should not happen */
	continue;
      if (!warned && dp->pid)
	{
	  debug (1, ("stopping dependencies of %s", prog_tag (prog)));
	  warned = 1;
	}
      prog_stop (dp, SIGTERM);
    }
  depmap_end (pos);
}

void
prog_stop (struct prog *prog, int sig)
{
  if (prog->pid == 0)
    return;
  if (prog->type == TYPE_COMPONENT)
    {
      if (prog->v.p.status == status_running)
	{
	  prog->v.p.status = status_stopping;
	  prog->v.p.timestamp = time (NULL);
	  pies_schedule_children (PIES_CHLD_RESCHEDULE_ALARM);
	}
    }
  debug (1, ("stopping %s (%lu)", prog_tag (prog), (unsigned long) prog->pid));
  if (prog->type == TYPE_COMPONENT && prog->v.p.comp->flags & CF_SIGGROUP)
    kill (-prog->pid, sig);
  else
    kill (prog->pid, sig);
}

static void
print_status (const char *tag, pid_t pid, int status, int expect_term)
{
  int prio;

  if (init_process)
    {
      if (debug_level <= 1)
	return;
      prio = LOG_DEBUG;
    }
  else
    prio = LOG_ERR;

  if (WIFEXITED (status))
    {
      if (WEXITSTATUS (status) == 0)
	debug (1, (_("%s (%lu) exited successfully"),
		   tag, (unsigned long) pid));
      else
	logmsg (prio, _("%s (%lu) exited with status %d"),
		tag, (unsigned long) pid,
		WEXITSTATUS (status));
    }
  else if (WIFSIGNALED (status))
    {
      char const *coremsg = "";

      if (expect_term && WTERMSIG (status) == SIGTERM)
	prio = LOG_DEBUG;

#ifdef WCOREDUMP
      if (WCOREDUMP (status))
	coremsg = _(" (core dumped)");
#endif
      logmsg (prio, _("%s (%lu) terminated on signal %d%s"),
	      tag, (unsigned long) pid,
	      WTERMSIG (status), coremsg);
    }
  else if (WIFSTOPPED (status))
    logmsg (prio, _("%s (%lu) stopped on signal %d"),
	    tag, (unsigned long) pid,
	    WSTOPSIG (status));
  else
    logmsg (prio, _("%s (%lu) terminated with unrecognized status: %d"),
	    tag, (unsigned long) pid, status);
}

static void propagate_child_exit (pid_t) ATTRIBUTE_NORETURN;

static void
propagate_child_exit (pid_t pid)
{
  int wait_status;

  while (waitpid (pid, &wait_status, 0) == -1)
    if (errno != EINTR)
      {
	logfuncall ("waitpid", NULL, errno);
	exit (EX_OSERR);
      }

  if (WIFSIGNALED (wait_status))
    {
      int sig = WTERMSIG (wait_status);
      signal (sig, SIG_DFL);
      raise (sig);
    }
  else if (WIFEXITED (wait_status))
    exit (WEXITSTATUS (wait_status));
  exit (127);
}

char *
wordsplit_string (struct wordsplit const *ws)
{
  char *ret, *p;
  size_t count = ws->ws_wordc + ws->ws_offs;
  char **argv = grecs_calloc (count, sizeof (argv[0]));
  size_t i;
  size_t len = 0;

  for (i = 0; i < count; i++)
    {
      argv[i] = quotearg_n (i, ws->ws_wordv[i]);
      len += strlen (argv[i]);
    }
  len += count;
  ret = grecs_malloc (len);

  for (i = 0, p = ret; i < count; i++)
    {
      strcpy (p, argv[i]);
      p += strlen (argv[i]);
      *p++ = ' ';
    }
  p[-1] = 0;
  free (argv);
  return ret;
}

void
send_msg (char *rcpts, const char *msg_text)
{
  int i, j, k;
  pid_t child_pid, grand_child_pid;
  struct wordsplit ws;
  int p[2];
  size_t size;

  ws.ws_offs = mailer_argc;
  ws.ws_delim = ",";
  if (wordsplit (rcpts, &ws,
		 WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_DELIM | WRDSF_DOOFFS))
    {
      logmsg (LOG_ERR,
	      _("cannot parse recipient address list (%s)"),
	      rcpts);
      return;
    }

  debug (1, (_("sending notification to %s"), rcpts));

  /* Copy mailer arguments */
  for (i = 0; i < mailer_argc; i++)
    ws.ws_wordv[i] = mailer_argv[i];
  /* j - number of the recipient;
     i - index of the ws_wordv being converted;
     k - index of the ws_wordv to store the converted value to.

     Normally i == k, unless there are some invalid ws_wordv's,
     in which case i > k.
  */
  for (j = 0, k = i; j < ws.ws_wordc; j++, i++)
    {
      char *arg = ws.ws_wordv[i];
      size_t len;

      while (*arg && c_isblank (*arg))
	arg++;
      len = strlen (arg);
      if (len > 0)
	{
	  while (len > 0 && c_isblank (arg[len - 1]))
	    len--;
	}
      if (len == 0)
	continue;
      if (arg[0] == '<' && arg[len-1] == '>')
	{
	  arg++;
	  len -= 2;
	}
      if (len == 0)
	continue;
      memmove (ws.ws_wordv[k], arg, len);
      ws.ws_wordv[k][len] = 0;
      k++;
    }
  ws.ws_wordv[k] = NULL;

  /* Fork a child: */
  child_pid = fork ();

  if (child_pid < 0)
    {
      wordsplit_free (&ws);
      logmsg (LOG_ERR,
	      _("cannot send mail: fork failed: %s"), strerror (errno));
      return;
    }

  if (child_pid)
    {
      char *cmd = wordsplit_string (&ws);
      wordsplit_free (&ws);
      debug (1, (_("started mailer: %s, pid=%lu"),
		   cmd, (unsigned long) child_pid));
      register_command (mailer_program, cmd, child_pid);
      return;
    }

  /* Child process */
  /* ============= */
  signal_setup (SIG_DFL);
  setsid ();
  if (pipe (p))
    {
      logmsg (LOG_ERR, _("cannot send mail: pipe failed: %s"),
	      strerror (errno));
      wordsplit_free (&ws);
      exit (EX_OSERR);
    }

  grand_child_pid = fork ();
  if (grand_child_pid < 0)
    {
      logmsg (LOG_ERR,
	      _("cannot send mail: fork failed: %s"), strerror (errno));
      return;
    }

  if (grand_child_pid == 0)
    {
      /* Grand-child */
      /* =========== */
      if (p[0] != 0 && p[1] != 0)
	close (0);
      close (p[1]);
      dup2 (p[0], 0);
      execv (mailer_program, ws.ws_wordv);
      exit (127);
    }

  /* Child again */
  close (p[0]);

  size = strlen (msg_text);
  while (size)
    {
      ssize_t rc = write (p[1], msg_text, size);
      if (rc <= 0)
	{
	  logmsg (LOG_ERR, _("cannot write to pipe: %s"),
		  rc == 0 ? "EOF" : strerror (errno));
	  break;
	}
      size -= rc;
      msg_text += rc;
    }
  close (p[1]);

  propagate_child_exit (grand_child_pid);
}

static const char default_termination_message[] =
"From: <>\n"
"X-Agent: ${canonical_program_name} (${package} ${version})\n"
"Subject: Component ${component} ${termination} ${retcode}.\n"
"\n";

struct notify_closure
{
  const char *tag;
  int status;
  struct action *act;
};

static int
notify_get_tag (char **ret, void *data)
{
  struct notify_closure const *clos = data;
  char *s = strdup (clos->tag);
  if (!s)
    return WRDSE_NOSPACE;
  *ret = s;
  return WRDSE_OK;
}

static int
notify_get_termination (char **ret, void *data)
{
  struct notify_closure const *clos = data;
  char const *msg;
  char *s;

  if (WIFEXITED (clos->status))
    /* TRANSLATORS: The subject of this statement is 'component' */
    msg = _("exited with code");
  else if (WIFSIGNALED (clos->status))
    /* TRANSLATORS: The subject of this statement is 'component' */
    msg = _("terminated on signal");
  else
    msg = "UNKNOWN";

  s = strdup (msg);
  if (!s)
    return WRDSE_NOSPACE;
  *ret = s;
  return WRDSE_OK;
}

static int
notify_get_retcode (char **ret, void *data)
{
  struct notify_closure const *clos = data;
  char *s = NULL;
  size_t l = 0;

  if (WIFEXITED (clos->status))
    grecs_asprintf (&s, &l, "%d", WEXITSTATUS (clos->status));
  else if (WIFSIGNALED (clos->status))
    grecs_asprintf (&s, &l, "%d", WTERMSIG (clos->status));
  else
    s = strdup ("UNKNOWN");
  if (!s)
    return WRDSE_NOSPACE;
  *ret = s;
  return WRDSE_OK;
}

struct notify_variable
{
  char *name;
  size_t name_length;
  int (*get) (char **ret, void *data);
};

static struct notify_variable notify_vartab[] = {
#define S(s) #s, sizeof(#s)-1
  { S(component),     notify_get_tag },
  { S(termination),   notify_get_termination },
  { S(retcode),       notify_get_retcode },
#undef S
  { NULL }
};

static int
notify_getvar (char **ret, const char *vptr, size_t vlen, void *data)
{
  struct notify_variable *var;
  for (var = notify_vartab; var->name; var++)
    if (var->name_length == vlen && memcmp (var->name, vptr, vlen) == 0)
      return var->get (ret, data);
  return WRDSE_UNDEF;
}

static void
notify (const char *tag, int status, struct action *act)
{
  const char *env[] = {
#define PROGRAM_NAME_IDX 1
    "program_name", NULL,
#define INSTANCE_IDX 3
    "instance", NULL,
    "canonical_program_name", "pies",
    "package", PACKAGE_NAME,
    "version", PACKAGE_VERSION,
    NULL
  };
  struct wordsplit ws;
  struct notify_closure closure;

  closure.tag = tag;
  closure.status = status;
  closure.act = act;

  env[PROGRAM_NAME_IDX] = program_name;
  env[INSTANCE_IDX] = instance;

  ws.ws_env = env;
  ws.ws_getvar = notify_getvar;
  ws.ws_closure = &closure;
  if (wordsplit (act->message ? act->message : default_termination_message,
		 &ws,
		 WRDSF_NOSPLIT | WRDSF_NOCMD | WRDSF_QUOTE |
		 WRDSF_ENV | WRDSF_ENV_KV |
		 WRDSF_GETVAR | WRDSF_CLOSURE))
    {
      logmsg (LOG_ERR, "wordsplit: %s", wordsplit_strerror (&ws));
      return;
    }
  send_msg (act->addr, ws.ws_wordv[0]);
  wordsplit_free (&ws);
}

static int
status_matches_p (struct action *act, unsigned status)
{
  int i;

  if (act->nstat == 0)
    return 1;
  for (i = 0; i < act->nstat; i++)
    if (act->status[i] == status)
      return 1;
  return 0;
}

static void
run_command (struct action *act, struct prog *prog, unsigned retcode,
	     pid_t child_pid)
{
  pid_t pid;
  char *argv[4];
  char buf[INT_BUFSIZE_BOUND (uintmax_t)];

  /* FIXME: optionally set output redirectors for this command? */
  pid = fork ();

  if (pid == (pid_t) -1)
    {
      logmsg (LOG_ERR, "fork: %s", strerror (errno));
      return;
    }

  if (pid == 0)
    {
      debug (1, (_("executing %s"), act->command));
      /* Child */
      setsid ();
      setenv ("PIES_VERSION", PACKAGE_VERSION, 1);
      setenv ("PIES_COMPONENT", prog_tag (prog), 1);
      setenv ("PIES_PID", umaxtostr (child_pid, buf), 1);
      if (retcode & STATUS_SIG_BIT)
	setenv ("PIES_SIGNAL", umaxtostr (STATUS_CODE (retcode), buf), 1);
      else
	setenv ("PIES_STATUS", umaxtostr (STATUS_CODE (retcode), buf), 1);

      close_fds (NULL);

      argv[0] = "/bin/sh";
      argv[1] = "-c";
      argv[2] = act->command;
      argv[3] = NULL;

      execv (argv[0], argv);
      exit (127);
    }

  /* Master */
  debug (1, (_("started command: %s, pid=%lu"),
	     act->command, (unsigned long) pid));
  register_command ((char*) _("[action]"), grecs_strdup (act->command), pid);
}

static void
react (struct prog *prog, int status, pid_t pid)
{
  unsigned retcode;
  struct grecs_list *list = prog->v.p.comp->act_list;

  if (!list)
    list = default_component.act_list;

  if (WIFEXITED (status))
    {
      retcode = WEXITSTATUS (status);
      debug (1, (_("%s: terminated with code %d"), prog_tag (prog), retcode));
    }
  else if (WIFSIGNALED (status))
    {
      retcode = WTERMSIG (status);
      debug (1, (_("%s: terminated on signal %d"), prog_tag (prog), retcode));
      retcode |= STATUS_SIG_BIT;
    }
  else
    {
      debug (1, (_("%s: unrecognized termination status"), prog_tag (prog)));
      /* Enforce default action: */
      list = NULL;
    }

  if (list)
    {
      struct grecs_list_entry *ep;
      for (ep = list->head; ep; ep = ep->next)
	{
	  struct action *act = ep->data;

	  if (status_matches_p (act, retcode))
	    {
	      if (act->command)
		{
		  run_command (act, prog, retcode, pid);
		}

	      switch (act->act)
		{
		case action_restart:
		  if (prog->v.p.comp->mode != pies_comp_inetd)
		    prog_start (prog);
		  break;

		case action_disable:
		  logmsg (LOG_NOTICE, _("disabling component %s"),
			  prog_tag (prog));
		  prog->active = 0;
		  /* FIXME:
		     if (prog->v.p.comp->mode == pies_comp_inetd)
		     disable_socket (prog->v.p.socket);
		  */
		}
	      if (act->addr)
		notify (prog_tag (prog), status, act);
	      break;
	    }
	}
    }

  if (!list && prog->v.p.comp->mode != pies_comp_inetd)
    /* Default action: */
    prog_start (prog);
}

void
progman_cleanup (int expect_term)
{
  pid_t pid;
  int status;

  if (!expect_term)
    expect_term = progman_waiting_p ();
  while ((pid = waitpid (-1, &status, WNOHANG)) > 0)
    {
      struct prog *prog = prog_lookup_by_pid (pid);
      if (!prog)
	{
	  print_status (_("unknown child"), pid, status, expect_term);
	  continue;
	}
      prog->pid = 0;
      switch (prog->type)
	{
	case TYPE_COMPONENT:
	  print_status (prog_tag (prog), pid, status, expect_term);
	  prog->stop = 0;
	  if (prog->v.p.comp->mode == pies_comp_inetd)
	    {
	      struct prog *listener = prog->v.p.listener;

	      listener->v.p.num_instances--;
	      if (prog->v.p.cclass)
		{
		  prog->v.p.cclass->count--;
		  if (debug_level > 1)
		    conn_class_report (LOG_DEBUG, prog->v.p.cclass);
		  if (prog->v.p.cclass->count == 0)
		    {
		      conn_class_remove (prog->v.p.cclass);
		      prog->v.p.cclass = NULL;
		    }
		}

	      prog_stop_redirectors (prog);
	      if (listener->v.p.num_instances == 0
		  && !component_is_active (prog->v.p.comp))
		destroy_prog (&listener);
	      else
		{
		  if (!expect_term)
		    react (listener, status, pid);
		  if (listener->v.p.comp->flags & CF_WAIT)
		    enable_socket (listener->v.p.socket);
		}
	      destroy_prog (&prog);
	    }
	  else
	    {
	      if (prog->v.p.comp->mode >= pies_mark_sysvinit
		  && prog->v.p.comp->mode != pies_comp_ondemand)
		{
		  sysvinit_acct (SYSV_ACCT_PROC_STOP, "", prog_tag (prog),
				 pid, "");
		  prog->v.p.status = status_finished;

		  if (prog->wait)
		    {
		      pies_schedule_children (PIES_CHLD_WAKEUP);
		      prog->wait = 0;
		    }
		}
	      else if (prog->v.p.comp->mode == pies_comp_startup)
		{
		  prog->v.p.status = status_finished;
		  if (!progman_startup_phase ())
		    pies_schedule_children (PIES_CHLD_WAKEUP);
		}
	      else
		{
		  if (is_sysvinit (prog->v.p.comp))
		    sysvinit_acct (SYSV_ACCT_PROC_STOP, "",
				   prog_tag (prog), pid, "");

		  prog->v.p.status = status_stopped;
		  prog_stop_dependents (prog);
		  if (!expect_term)
		    react (prog, status, pid);
		}

	      if (!component_is_active (prog->v.p.comp))
		destroy_prog (&prog);
	    }

	  break;

	case TYPE_REDIRECTOR:
	  /* It was a redirector of an already finished process. */
	  print_status (prog_tag (prog), pid, status,
			expect_term ||
			(prog->v.r.master &&
			 prog->v.r.master->v.p.status == status_stopping));
	  debug (1, (_("removing redirector %s, pid=%lu"),
		     prog_tag (prog), (unsigned long)pid));
	  destroy_prog (&prog);
	  break;

	case TYPE_COMMAND:
	  print_status (prog_tag (prog), pid, status, expect_term);
	  destroy_prog (&prog);
	  break;
	}
    }

  if (!expect_term)
    /* This will also recompute alarm settings, if necessary */
    progman_wake_sleeping (0);
}

void
progman_stop_component (struct prog **progptr)
{
  struct prog *prog = *progptr;
  if (prog && IS_COMPONENT (prog))
    {
      switch (prog->v.p.status)
	{
	case status_running:
	  logmsg (LOG_INFO, _("stopping component %s"), prog_tag (prog));
	  prog_stop (prog, SIGTERM);
	  break;

	case status_listener:
	  prog_deactivate_listener (prog);
	  /* fall through */
	case status_stopped:
	  prog->stop = 0;
	  if (!component_is_active (prog->v.p.comp))
	    destroy_prog (progptr);
	  break;

	case status_sleeping:
	  if (!component_is_active (prog->v.p.comp))
	    destroy_prog (progptr);
	  else if (prog->stop)
	    prog->stop = 0;
	  else
	    {
	      logmsg (LOG_INFO, _("waking up component %s"), prog_tag (prog));
	      prog->v.p.failcount = 0;
	    }
	  break;

	case status_stopping:
	  break;

	case status_finished:
	  prog->stop = 0;
	  if (!component_is_active (prog->v.p.comp))
	    destroy_prog (progptr);
	  else
	    logmsg (LOG_INFO,
		    _("stopping component %s: component not started"),
		    prog_tag (prog));
	}
    }
}

void
prog_deactivate_listener (struct prog *prog)
{
  logmsg (LOG_INFO, _("deactivating listener %s"), prog_tag (prog));
  if (prog->v.p.socket != -1)
    {
      deregister_socket (prog->v.p.socket);
      prog->v.p.socket = -1;
    }
}

int
prog_activate_listener (struct prog *prog)
{
  struct component *comp = prog->v.p.comp;

  logmsg (LOG_INFO, _("activating listener %s"), prog_tag (prog));
  if (comp->mode == pies_comp_inetd && !ISCF_TCPMUX (comp->flags)
      && prog->v.p.socket == -1)
    {
      int fd = create_socket (comp->socket_url,
			      comp->socket_type,
			      comp->privs.user, comp->umask);
      if (fd == -1)
	return -1;
      else if (register_program_socket (comp->socket_type, fd, prog))
	{
	  close (fd);
	  return -1;
	}
      else
	prog->v.p.socket = fd;
    }
  return 0;
}

/* Starting at PROG, find first program component marked for termination. */
static struct prog *
prog_to_stop (struct prog *prog)
{
  for (; prog; prog = prog->next)
    if (IS_COMPONENT (prog) && prog->stop)
      break;
  return prog;
}

#define DIAG_CON \
  (init_process ? (DIAG_TO_STDERR|DIAG_REOPEN_LOG) : diag_output)

/* Stop all program components marked for termination.  Wait at most
   2*shutdown_timeout seconds. */
void
progman_gc (void)
{
  time_t start;
  struct prog *prog, *next;

  /* Find first marked prog */
  prog = prog_to_stop (proghead);
  if (!prog)
    return;

  /* First round: */
  /* Gracefully stop it and all marked programs that follow */
  diagmsg (DIAG_CON, LOG_INFO, "Sending processes the TERM signal");
  start = time (NULL);
  do
    {
      next = prog->next;
      progman_stop_component (&prog);
    }
  while ((prog = prog_to_stop (next)));

  /* Wait for them to terminate */
  while (1)
    {
      progman_cleanup (1);
      prog = prog_to_stop (proghead);
      if (!prog)
	return;
      if (time (NULL) - start < shutdown_timeout)
	sleep (1);
      else
	break;
    }

  /* Second round: */
  /* Kill the remaining programs with SIGKILL */
  diagmsg (DIAG_CON, LOG_INFO, "Sending processes the KILL signal");
  start = time (NULL);
  do
    {
      next = prog->next;
      prog_stop (prog, SIGKILL);
    }
  while ((prog = prog_to_stop (next)));

  /* Wait for them to terminate */
  while (1)
    {
      progman_cleanup (1);
      prog = prog_to_stop (proghead);
      if (!prog)
	return;
      if (time (NULL) - start < shutdown_timeout)
	sleep (1);
      else
	break;
    }

  /* FIXME: Report remaining programs */

}

static int
start_shutdown (struct prog *prog, void *data)
{
  if (IS_COMPONENT (prog) && prog->v.p.comp->mode == pies_comp_shutdown)
    {
      int *p = data;
      if (!*p)
	{
	    diagmsg (DIAG_CON, LOG_INFO, "Starting shutdown components");
	    *p = 1;
	}
      prog->active = 1;
      prog_start (prog);
      prog->stop = 1;
    }
  return 0;
}

void
program_shutdown (void)
{
  time_t start = time (NULL);
  int sd = 0;

  /* Activate shutdown components. */
  progman_foreach (start_shutdown, &sd);
  while (prog_to_stop (proghead))
    {
      progman_cleanup (1);
      if (time (NULL) - start < shutdown_timeout)
	sleep (1);
      else
	{
	  progman_gc ();
	  break;
	}
    }
}

static int
mark_for_stopping (struct prog *prog, void *data)
{
  if (IS_COMPONENT (prog))
    prog->stop = 1;
  return 0;
}

void
progman_stop (void)
{
  progman_foreach (mark_for_stopping, NULL);
  progman_gc ();
  program_shutdown ();
}

/* EOF */
