/* This file is part of GNU Pies.
   Copyright (C) 2007-2022 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_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;
}

static inline void
prog_stop_redirectors (struct prog *p)
{
  if (p->v.p.redir[RETR_OUT] != -1)
    {
      deregister_socket (p->v.p.redir[RETR_OUT]);
      p->v.p.redir[RETR_OUT] = -1;
    }
  if (p->v.p.redir[RETR_ERR] != -1)
    {
      deregister_socket (p->v.p.redir[RETR_ERR]);
      p->v.p.redir[RETR_ERR] = -1;
    }
}

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

  unlink_prog (p);
  switch (p->type)
    {
    case TYPE_COMPONENT:
      environ_free (p->v.p.env);
      if (p->v.p.comp->flags & CF_EXPANDENV)
	argv_free (p->v.p.argv);
      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) */
      prog_stop_redirectors (p);
      break;

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

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

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

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));
    }
  /* Position to the end of file */
  if (lseek (fd, 0, SEEK_END) == -1)
    {
      if (errno != ESPIPE)
	logmsg (LOG_ERR, "lseek(%s): %s",
		master->v.p.comp->redir[stream].v.file,
		strerror (errno));
    }
  return fd;
}

struct read_buffer
{
  struct prog *master;
  int stream;
  char text[PIES_LOG_BUF_SIZE];
  size_t len;
  int overflow;
};

static int
redirect_read (int fd, void *data)
{
  struct read_buffer *rb = data;
  int n;
  int prio = rb->master->v.p.comp->redir[rb->stream].v.prio;
  char const *tag = prog_tag (rb->master);
  pid_t pid = rb->master->pid;
  char *p;
  static char *retr_stream_name[] = {
    [RETR_OUT] = "stdout",
    [RETR_ERR] = "stderr"
  };

  n = read (fd, rb->text + rb->len, sizeof (rb->text) - rb->len);
  if (n == -1)
    {
      if (errno != EINTR)
	{
	  logmsg (LOG_INFO, _("%s: %s while reading %s"),
		  tag, strerror (errno), retr_stream_name[rb->stream]);
	  rb->master->v.p.redir[rb->stream] = -1;
	  close (fd);
	  deregister_socket (fd);
	  return -1;
	}
    }
  else if (n == 0)
    {
      logmsg (LOG_INFO, _("%s: EOF on %s"),
	      tag, retr_stream_name[rb->stream]);
      rb->master->v.p.redir[rb->stream] = -1;
      close (fd);
      deregister_socket (fd);
    }
  else
    {
      rb->len += n;

      while (rb->len)
	{
	  p = memchr (rb->text, '\n', rb->len);
	  if (p)
	    {
	      *p++ = 0;
	      if (rb->overflow)
		rb->overflow = 0;
	      else
		pies_syslog_message (prio, rb->text, tag, pid);
	      n = rb->len - (p - rb->text);
	      if (n > 0)
		memmove (rb->text, p, n);
	      rb->len = n;
	    }
	  else if (rb->len == sizeof (rb->text))
	    {
	      rb->text[sizeof (rb->text) - 1] = 0;
	      pies_syslog_message (prio, rb->text, tag, pid);
	      rb->len = 0;
	      rb->overflow = 1;
	    }
	  else
	    break;
	}
    }
  return 0;
}

static void
read_buffer_free (void *p)
{
  free (p);
}

int
redirect_to_syslog (struct prog *master, int stream, int *fd)
{
  struct read_buffer *rb;
  int p[2];
  
  if (pipe (p))
    {
      logmsg (LOG_CRIT, "pipe: %s", strerror (errno));
      return -1;
    }

  rb = grecs_zalloc (sizeof (*rb));
  rb->master = master;
  rb->stream = stream;
  rb->len = 0;
  rb->overflow = 0;
  
  register_socket (p[0], redirect_read, NULL, NULL, rb, read_buffer_free);
  *fd = p[0];
  return p[1];
}

void
free_redirector (struct redirector *rp)
{
  if (rp->type == redir_file)
    free (rp->v.file);
}

int
open_redirector (struct prog *master, int stream, int *fd)
{
  switch (master->v.p.comp->redir[stream].type)
    {
    case redir_null:
      *fd = -1;
      break;
      
    case redir_file:
      *fd = -1;
      return redirect_to_file (master, stream);

    case redir_syslog:
      return redirect_to_syslog (master, stream, fd);
    }
  return -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);
}


#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_var[] = {
  ENV_PROTO,
  ENV_SOCKTYPE,
  ENV_LOCALIP,
  ENV_LOCALPORT,
  ENV_LOCALHOST,
  ENV_REMOTEIP,
  ENV_REMOTEPORT,
  ENV_REMOTEHOST,
  NULL
};

static inline void
debug_environ (struct prog *prog, int l)
{
  if (debug_level >= l)
    {
      int i;
      char **env = environ_ptr (prog->v.p.env);
      logmsg_printf (LOG_DEBUG, "environment: ");
      for (i = 0; env[i]; i++)
	logmsg_printf (LOG_DEBUG, "%s ", env[i]);
      logmsg_printf (LOG_DEBUG, "\n");
    }
}

/* 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);
  environ_set (prog->v.p.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";

  environ_set (prog->v.p.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)
	environ_set (prog->v.p.env, ENV_LOCALIP, p);

      p = umaxtostr (ntohs (sa_server.s_in.sin_port), buf);
      environ_set (prog->v.p.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
	    environ_set (prog->v.p.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)
	environ_set (prog->v.p.env, ENV_REMOTEIP, p);

      p = umaxtostr (ntohs (sa_client->s_in.sin_port), buf);
      environ_set (prog->v.p.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
	    environ_set (prog->v.p.env, ENV_REMOTEHOST, host->h_name);
	}
    }
  /* FIXME: $REMOTEINFO ? */
  debug_environ (prog, 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
progman_ws_error (const char *fmt, ...)
{
  va_list ap;

  va_start (ap, fmt);
  vlogmsg (LOG_ERR, fmt, ap);
  va_end (ap);
}

/* 
 * Initialization of the environment and argc/argv members of
 * struct prog.  For regular components this is done in the main
 * process, so that argv can be reported correctly via the ctl
 * interface.  For tcpmux components, which are run from the
 * listener child, this is not so.  That does little harm as
 * these components are not visible from the ctl interface anyway.
 */
static int
prog_init (struct prog *prog)
{
  if ((prog->v.p.env = environ_create (environ)) == NULL)
    {
      logmsg (LOG_CRIT, "environ_create: %s", strerror (errno));
      return -1;
    }
  
  if (prog->v.p.comp->flags & CF_SOCKENV)
    {
      size_t i;
      for (i = 0; sockenv_var[i]; i++)
	environ_unset (prog->v.p.env, sockenv_var[i], NULL);
    }

  if (envop_exec (prog->v.p.comp->envop, prog->v.p.env))
    {
      logmsg (LOG_CRIT, "environ_exec: %s", strerror (errno));
      return -1;
    }
  
  if (SYSVINIT_ACTIVE)
    {
      size_t i;
      for (i = 0; sysvinit_environ_hint[i]; i++)
	{
	  if (environ_add (prog->v.p.env, sysvinit_environ_hint[i]))
	    {
	      logmsg (LOG_CRIT, "environ_add: %s", strerror (errno));
	      return -1;
	    }
	}	      
    }
  debug_environ (prog, 4);  

  if (prog->v.p.comp->flags & CF_EXPANDENV)
    {
      struct wordsplit ws;

      ws.ws_env = (const char **) environ_ptr (prog->v.p.env);
      ws.ws_error = progman_ws_error;
      if (wordsplit (prog->v.p.comp->command, &ws,
		     WRDSF_QUOTE | WRDSF_SQUEEZE_DELIMS | WRDSF_NOCMD | WRDSF_ENV | WRDSF_ERROR))
	{
	  logmsg (LOG_ERR, _("%s: can't split command line: %s"),
		  prog_tag (prog), wordsplit_strerror (&ws));
	  return -1;
	}
      wordsplit_get_words (&ws, &prog->v.p.argc, &prog->v.p.argv);
      wordsplit_free (&ws);      
    }
  else
    {
      prog->v.p.argc = prog->v.p.comp->argc;
      prog->v.p.argv = prog->v.p.comp->argv;
    }

  if (debug_level >= 1 && prog->v.p.argv)
    {
      int i;
      logmsg_printf (LOG_DEBUG, "executing");
      for (i = 0; i < prog->v.p.argc; i++)
	logmsg_printf (LOG_DEBUG, " %s", quotearg (prog->v.p.argv[i]));
      logmsg_printf (LOG_DEBUG, "\n");
    }
  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));
    }

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

static void
prog_execute (struct prog *prog)
{
  environ = environ_ptr (prog->v.p.env);
  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.argv[0],
	  prog->v.p.argv);
  //FIXME: pies_syslog?  
  syslog (LOG_CRIT, _("cannot start `%s': %s"), prog_tag (prog),
	  strerror (errno));
  _exit (EX_SOFTWARE);
}

/*
 * Run a TCPMUX component.  This function is invoked from a child
 * process.
 */
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);
  if (prog_init (prog))
    _exit (127);
  prog_start_prologue (prog);
  prog_sockenv (prog);
  prog_execute (prog);
}

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

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

  if (is_sysvinit (prog->v.p.comp))
    {
      if (!SYSVINIT_ACTIVE)
	{
	  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;
    }

  if (prog_init (prog))
    return; //FIXME
  
  redir[RETR_OUT] = open_redirector (prog, RETR_OUT,
				     &prog->v.p.redir[RETR_OUT]);
  redir[RETR_ERR] = open_redirector (prog, RETR_ERR,
				     &prog->v.p.redir[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 (SYSVINIT_ACTIVE)
	    {
	      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 (!SYSVINIT_ACTIVE)
	{
	  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 */
      pies_close_fds ((prog->v.p.comp->mode == pies_comp_pass_fd
	               ? prog->v.p.socket : 2) + 1);

      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, NULL))
	    {
	      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 nonexistent 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)
	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_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 (SYSVINIT_ACTIVE)
    {
      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, master_pid;
  char *argv[4];
  char buf[INT_BUFSIZE_BOUND (uintmax_t)];

  master_pid = getpid ();

  /* 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_MASTER_PID", umaxtostr (master_pid, buf), 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);

      pies_close_fds (3);

      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 (PIES_SYSVINIT_ENABLED
		  && 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_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, NULL))
	{
	  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 \
  (SYSVINIT_ACTIVE ? (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 */
