/* This file is part of GNU Pies.
   Copyright (C) 2016 Sergey Poznyakoff

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

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

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

#include "pies.h"
#include "prog.h"
#include <assert.h>

struct complist
{
  struct component *head;
  struct component *tail;
};

static struct complist comp_list[2];
static int cur;

static struct component **comp_array;
static size_t comp_count;

static pies_depmap_t depmap;

static int
next_index (void)
{
  return (cur + 1) % ARRAY_SIZE (comp_list);
}

static int
prev_index (void)
{
  return (cur + ARRAY_SIZE (comp_list) - 1) % ARRAY_SIZE (comp_list);
}

void
component_link (struct component *comp, struct component *ref)
{
  if (!ref)
    {
      struct complist *list = &comp_list[comp->listidx];
  
      comp->prev = NULL;
      comp->next = list->head;
      if (list->head)
	list->head->prev = comp;
      else
	list->tail = comp;
      list->head = comp;
    }
  else
    {
      struct complist *list = &comp_list[comp->listidx];
      struct component *x;

      assert (comp->listidx == ref->listidx);

      comp->prev = ref;
      comp->next = ref->next;
      
      if ((x = ref->next))
	x->prev = comp;
      else
	list->tail = comp;

      ref->next = comp;
    }
}

void
component_append (struct component *comp)
{
  component_link (comp, comp_list[comp->listidx].tail);
}

void
component_unlink (struct component *comp)
{
  struct complist *list = &comp_list[comp->listidx];
  struct component *x;
  
  if ((x = comp->prev))
    x->next = comp->next;
  else
    list->head = comp->next;
  if ((x = comp->next))
    x->prev = comp->prev;
  else
    list->tail = comp->prev;
}  

int
component_list_is_empty (void)
{
  return !comp_list[cur].head;
}

struct component *
component_lookup_tag (int idx, const char *tag)
{
  struct complist *list = &comp_list[idx];
  struct component *comp;

  for (comp = list->head; comp; comp = comp->next)
    if (strcmp (comp->tag, tag) == 0)
      break;

  return comp;
}

ssize_t
component_lookup_index (const char *tag)
{
  size_t i;

  for (i = 0; i < comp_count; i++)
    if (comp_array[i] && strcmp (comp_array[i]->tag, tag) == 0)
      return i;
  return -1;
}
  
struct component *
component_create (const char *name)
{
  struct component *comp = component_lookup_tag (cur, name);
  if (!comp)
    {
      comp = grecs_zalloc (sizeof (*comp));
      comp->listidx = cur;
      comp->facility = log_facility;
      comp->redir[RETR_OUT].type = comp->redir[RETR_ERR].type = redir_null;
      comp->tag = grecs_strdup (name);
      comp->socket_type = SOCK_STREAM;
      component_append (comp);
    }
  return comp;
}

void
component_free (struct component *comp)
{
  size_t i;

  component_unlink (comp);
  free (comp->tag);
  free (comp->program);
  if (comp->argv)
    {
      for (i = 0; i < comp->argc; i++)
	free (comp->argv[i]);
      free (comp->argv);
    }
  if (comp->env)
    {
      for (i = 0; comp->env[i]; i++)
	free (comp->env[i]);
      free (comp->env);
    }
  free (comp->dir);
  grecs_list_free (comp->prereq);
  grecs_list_free (comp->depend);
  free (comp->rmfile);
  pies_privs_free (&comp->privs);
  free (comp->runlevels);
  free_limits (comp->limits);
  free (comp->service);
  pies_url_destroy (&comp->socket_url);
  free (comp->pass_fd_socket);
  free (comp->tcpmux);
  free (comp->access_denied_message);
  free (comp->max_instances_message);
  free (comp->max_ip_connections_message);
  free_redirector (&comp->redir[0]);
  free_redirector (&comp->redir[1]);
  grecs_list_free (comp->act_list);
  pies_acl_free (comp->acl);
  pies_acl_free (comp->list_acl);
  pies_acl_free (comp->adm_acl);
  free (comp);
}

void
component_ref_incr (struct component *comp)
{
  ++comp->ref_count;
}

void
component_ref_decr (struct component *comp)
{
  assert (comp->ref_count > 0);
  if (--comp->ref_count == 0)
    component_free (comp);
}

static int
argvcmp (char **a, char **b)
{
  size_t i;
  
  if (!a != !b)
    return 1;
  
  for (i = 0; a[i]; i++)
    if (!b[i] || strcmp (b[i], a[i]))
      return 1;
  return !!b[i];
}

static int
urlcmp (struct pies_url *a, struct pies_url *b)
{
  if (!a)
    return !!b;
  else if (!b)
    return 1;
  return safe_strcmp (a->string, b->string);
}

static int
redirector_cmp (struct redirector const *a, struct redirector const *b)
{
  if (a->type != b->type)
    return 1;
  switch (a->type)
    {
    case redir_null:
      break;

    case redir_syslog:
      if (a->v.prio != b->v.prio)
	return 1;
      break;

    case redir_file:
      if (safe_strcmp (a->v.file, b->v.file))
	return 1;
    }

  return 0;
}
      
static int
component_match (struct component *comp, struct component *ref)
{
#define MATCH(cond) do if (cond) return 1; while (0)
#define EQ(memb) MATCH (comp->memb != ref->memb)
#define FN(memb,fun) MATCH (fun (comp->memb, ref->memb))
#define FNP(memb,fun) MATCH (fun (&comp->memb, &ref->memb))
  
  EQ (mode);
  FN (tag, safe_strcmp);
  FN (program, safe_strcmp);
  EQ (argc);
  FN (argv, argvcmp);
  FN (dir, safe_strcmp);
  FN (prereq, grecs_list_compare);
  FN (depend, grecs_list_compare);
  EQ (flags);
  EQ (max_instances);
  FN (rmfile, safe_strcmp);
  FNP (privs, pies_privs_cmp);
  FN (limits, limits_cmp);
  FN (runlevels, safe_strcmp);
  EQ (max_rate);
  EQ (max_ip_connections);
  EQ (socket_type);
  EQ (builtin);
  FN (service, safe_strcmp);
  FN (socket_url, urlcmp);
  FN (pass_fd_socket, safe_strcmp);
  EQ (pass_fd_timeout);
  FN (acl, pies_acl_cmp);
  FN (tcpmux, safe_strcmp);
  EQ (facility);
  FNP (redir[0], redirector_cmp);
  FNP (redir[1], redirector_cmp);
#undef MATCH
#undef EQ
#undef FN
#undef FNP

  return 0;
}

static struct component *
complist_find_match (int idx, struct component *ref)
{
  struct complist *list = &comp_list[idx];
  struct component *comp;
  
  for (comp = list->head; comp && component_match (comp, ref);
       comp = comp->next)
    ;
  return comp;
}

static void
strasgn (char **dst, char **src)
{
  free (*dst);
  *dst = *src;
  *src = NULL;
}

void
component_merge (struct component *comp, struct component *ref)
{
  strasgn (&comp->tag, &ref->tag);

  strasgn (&comp->access_denied_message, &ref->access_denied_message);
  strasgn (&comp->max_instances_message, &ref->max_instances_message);
  strasgn (&comp->max_ip_connections_message,
	   &ref->max_ip_connections_message);

  grecs_list_free (comp->act_list);
  comp->act_list = ref->act_list;
  ref->act_list = NULL;

  pies_acl_free (comp->list_acl);
  comp->list_acl = ref->list_acl;
  ref->list_acl = NULL;
  
  pies_acl_free (comp->adm_acl);
  comp->adm_acl = ref->adm_acl;
  ref->adm_acl = NULL;
}

int
component_is_active (struct component *comp)
{
  return comp->listidx == cur;
}

void
component_config_begin (void)
{
  cur = next_index ();
}

void
component_config_rollback (void)
{
  struct complist *list = &comp_list[cur];
  while (list->head)
    component_free (list->head);
  cur = prev_index ();
}

/* Return true if PROG is a leftover from previous configuration */
static int
prog_is_leftover (struct prog *prog)
{
  return IS_COMPONENT (prog) && !component_is_active (prog->v.p.comp);
}

/* If PROG is a leftover, mark it for termination.  If it is a listener,
   terminate it immediately.  This ensures that all decomissioned sockets
   are closed before the subsequent call to progman_create_sockets, which
   might need to reopen some of them.
*/
static int
mark_prog (struct prog *prog, void *data)
{
  if (prog_is_leftover (prog))
    {
      prog->stop = 1;
      if (prog->v.p.status == status_listener)
	progman_stop_component (&prog);
    }
  return 0;
}

static int
list_str_cmp (const void *a, const void *b)
{
  return safe_strcmp (a, b);
}

static void
component_log_dep (size_t idx)
{
  pies_depmap_pos_t pos;
  size_t n;
  
  logmsg_printf (LOG_NOTICE, "%s -> ", comp_array[idx]->tag);
  for (n = depmap_first (depmap, depmap_col, idx, &pos);
       n != (size_t)-1;
       n = depmap_next (depmap, pos))
    {
      logmsg_printf (LOG_NOTICE, "%s -> ", comp_array[n]->tag);
    }
  depmap_end (pos);
  logmsg_printf (LOG_NOTICE, "%s\n", comp_array[idx]->tag);
}  

void
component_build_depmap (void)
{
  size_t i;
  pies_depmap_t dp;
  
  free (depmap);
  depmap = depmap_alloc (comp_count);
  for (i = 0; i < comp_count; i++)
    {
      struct component *comp = comp_array[i];
      struct grecs_list_entry *ep;

      if (comp->prereq)
	for (ep = comp->prereq->head; ep; ep = ep->next)
	  {
	    char const *tag = ep->data;
	    ssize_t tgt = component_lookup_index (tag);
	    if (tgt < 0)
	      {
		logmsg (LOG_ERR,
			_("component %s depends on %s, "
			  "which is not declared"),
			comp->tag, tag);
		component_free (comp);
		comp_array[i] = NULL;
		depmap_clear_all (depmap, depmap_row, i);
		depmap_clear_all (depmap, depmap_col, i);
		continue;
	      }
	    depmap_set (depmap, i, tgt);
	  }
      
      if (comp->depend)
	for (ep = comp->depend->head; ep; ep = ep->next)
	  {
	    char const *tag = ep->data;
	    ssize_t tgt = component_lookup_index (tag);
	    if (tgt < 0)
	      {
		logmsg (LOG_ERR,
			_("undefined component %s depends on %s"),
			tag, comp->tag);
		continue;
	      }
	    depmap_set (depmap, tgt, i);
	  }
    }

  dp = depmap_copy (depmap);
  depmap_tc (dp);
  for (i = 0; i < comp_count; i++)
    if (depmap_isset (dp, i, i))
      {
	logmsg (LOG_ERR, _("component %s depends on itself"),
		comp_array[i]->tag);
	component_log_dep (i);
	component_free (comp_array[i]);
	comp_array[i] = NULL;
	depmap_clear_all (depmap, depmap_row, i);
	depmap_clear_all (depmap, depmap_col, i);
	continue;
      }
  free (dp);
}

void
component_config_commit (void)
{
  struct complist *list = &comp_list[cur];
  struct component *comp, *match;
  int prev = prev_index ();
  size_t i;

  /* Count available components and allocate array for them */
  for (comp = list->head, i = 0; comp; comp = comp->next, i++)
    /* nothing */;

  if (i == 0)
    {
      free (comp_array);
      comp_array = NULL;
    }
  else
    comp_array = grecs_realloc (comp_array, i * sizeof (comp_array[0]));
  comp_count = i;

  /* Rearrange components, registering prog entries for the new ones */
  for (comp = list->head, i = 0; comp; comp = comp->next, i++)
    {
      match = complist_find_match (prev, comp);
      if (match)
	{
	  component_merge (match, comp);
	  component_unlink (match);
	  match->listidx = cur;
	  component_link (match, comp->prev);
	  component_free (comp);
	  comp = match;
	}
      comp_array[i] = comp;
      comp->arridx = i;
    }

  /* Mark orphaned progs for termination */
  list = &comp_list[prev];
  if (list->head)
    {
      progman_foreach (mark_prog, NULL);
      pies_schedule_children (PIES_CHLD_GC);
    }
  
  /* Build dependency map */
  component_build_depmap ();

  /* Register new progs */
  for (comp = comp_list[cur].head; comp; comp = comp->next)
    if (!comp->prog)
      register_prog (comp);
}

static int
component_verify (struct component *comp, grecs_locus_t *locus)
{
  int header = 0;
  int i;
#define COMPERR(func, fmt, arg)						\
  do									\
    {									\
      if (!header)							\
	{								\
	  grecs_warning (locus, 0, _("in component %s:"), comp->tag);	\
	  header = 1;							\
	}								\
      func (locus, 0, fmt, arg);					\
    }									\
  while (0)

  if (comp->flags & CF_INTERNAL)
    {
      comp->mode = pies_comp_inetd;
      if (!comp->service)
	/* TRANSLATORS: do not translate quoted words, they are keywords. */
	COMPERR (grecs_error,
		 "%s", _("\"internal\" used without \"service\""));
      else
	{
	  comp->builtin = inetd_builtin_lookup (comp->service,
						comp->socket_type);
	  if (!comp->builtin)
	    COMPERR (grecs_error,
		     "%s", _("unknown internal service"));
	  if (comp->argv)
	    /* TRANSLATORS: do not translate quoted words, they are
	       keywords. */
	    COMPERR (grecs_error,
		     "%s", _("\"internal\" used with \"command\""));
	}
    }
  else if (!comp->argv)
    COMPERR (grecs_error,
	     "%s", _("missing command line"));
  
  if (ISCF_TCPMUX (comp->flags))
    {
      comp->mode = pies_comp_inetd;
      if ((comp->flags & (CF_TCPMUX | CF_TCPMUXPLUS))
	  == (CF_TCPMUX | CF_TCPMUXPLUS))
	COMPERR (grecs_error,
		 "%s", _("both \"tcpmux\" and \"tcpmuxplus\" used"));
      else if (!comp->service)
	/* TRANSLATORS: do not translate quoted words, they are keywords. */
	COMPERR (grecs_error,
		 "%s", _("\"internal\" used without \"service\""));
    }
  
  if (comp->pass_fd_socket && comp->mode != pies_comp_pass_fd)
    COMPERR (grecs_error,
	     "%s", _("pass-fd-socket ignored: wrong mode"));
  switch (comp->mode)
    {
    case pies_comp_exec:
      if (comp->socket_url)
	COMPERR (grecs_error,
		 "%s", _("socket ignored: wrong mode"));
      break;
      
    case pies_comp_pass_fd:
      if (!comp->pass_fd_socket)
	COMPERR (grecs_error,
		 "%s", _("must supply pass-fd-socket in this mode"));
      else if (comp->pass_fd_socket[0] != '/')
	{
	  if (comp->dir)
	    {
	      char *p = mkfilename (comp->dir, comp->pass_fd_socket, NULL);
	      /*free (comp->pass_fd_socket);*/
	      comp->pass_fd_socket = p;
	    }
	  else
	    COMPERR (grecs_error,
		     "%s", _("pass-fd-socket must be an absolute "
			     "file name or chdir must be specified"));
	}
      /* Fall through */
      
    case pies_comp_accept:
      if (!comp->socket_url)
	{
	  COMPERR (grecs_error,
		   "%s", _("socket must be specified in this mode"));
	  return 1;
	}
      break;
      
    case pies_comp_inetd:
      if (ISCF_TCPMUX (comp->flags))
	{
	  pies_url_destroy (&comp->socket_url);
	  if (!comp->tcpmux)
	    {
	      COMPERR (grecs_warning,
		       "%s",
		       _("TCPMUX master not specified, assuming \"tcpmux\""));
	      comp->tcpmux = grecs_strdup ("tcpmux");
	    }
	}
      else if (comp->tcpmux)
	{
	  comp->flags |= CF_TCPMUX;
	  pies_url_destroy (&comp->socket_url);
	}
      else if (!comp->socket_url)
	{
	  COMPERR (grecs_error,
		   "%s", _("socket must be specified in this mode"));
	  return 1;
	}
    default:
      /* FIXME: more checks perhaps */
      break;
    }

  if (comp->mode == pies_comp_inetd)
    {
      if ((comp->flags & CF_WAIT) && comp->socket_type == SOCK_STREAM)
	{
	  if (comp->max_instances)
	    COMPERR (grecs_error, "%s", _("max-instances ignored"));
	  else
	    comp->max_instances = 1;
	}
    }
  else if (comp->flags & CF_WAIT)
    {
      /* TRANSLATORS: `wait' is a keywords, do not translate. */
      COMPERR (grecs_error, "%s", _("wait is useless in this mode"));
      comp->flags &= ~CF_WAIT;
    }

  switch (comp->mode)
    {
    case pies_comp_accept:
    case pies_comp_inetd:
      if (comp->redir[RETR_OUT].type != redir_null)
	{
	  COMPERR (grecs_error,
		   "%s", _("stdout redirection invalid in this mode"));
	  comp->redir[RETR_OUT].type = redir_null;
	}
    default:
      break;
    }
  
  for (i = RETR_OUT; i <= RETR_ERR; i++)
    {
      if (comp->redir[i].type == redir_file
	  && comp->redir[i].v.file[0] != '/')
	{
	  if (comp->dir)
	    {
	      char *p = mkfilename (comp->dir, comp->redir[i].v.file, NULL);
	      free (comp->redir[i].v.file);
	      comp->redir[i].v.file = p;
	    }
	  else
	    COMPERR (grecs_error,
		     _("%s: must be an absolute "
		       "file name or chdir must be specified"),
		     comp->redir[i].v.file);
	}
    }
  
  return header;
#undef COMPERR
}

void
component_finish (struct component *comp, grecs_locus_t *locus)
{
  if (comp->prereq)
    comp->prereq->cmp = list_str_cmp;
  if (comp->depend)
    comp->depend->cmp = list_str_cmp;
  if (comp->privs.groups)
    comp->privs.groups->cmp = list_str_cmp;
  
  if (component_verify (comp, locus))
    {
      component_free (comp);
    }
  else
    {
      size_t n = grecs_list_size (comp->prereq);
      if (n == 1)
	{
	  const char *item = grecs_list_index (comp->prereq, 0);
	  if (strcmp (item, "all") == 0)
	    {
	      struct component *p;

	      grecs_list_clear (comp->prereq);
	      for (p = comp->prev; p; p = p->prev)
		grecs_list_push (comp->prereq, grecs_strdup (comp->tag));
	    }
	  else if (strcmp (item, "none") == 0)
	    {
	      grecs_list_free (comp->prereq);
	      comp->prereq = NULL;
	    }
	}
    }
}

struct component *
component_get (size_t n)
{
  if (n >= comp_count)
    return NULL;
  return comp_array[n];
}

void
components_dump_depmap (void)
{
  size_t i, j;

  printf ("%s:\n", _("Dependency map"));
  printf ("  ");
  for (i = 0; i < comp_count; i++)
    printf (" %2lu", (unsigned long)i);
  printf ("\n");
  for (i = 0; i < comp_count; i++)
    {
      printf ("%2lu ", (unsigned long)i);
      for (j = 0; j < comp_count; j++)
	printf (" %c ", depmap_isset (depmap, i, j) ? 'X' : ' ');
      printf ("\n");
    }
  printf ("\n%s:\n", _("Legend"));
  for (i = 0; i < comp_count; i++)
    printf ("%2lu: %s\n", (unsigned long)i, comp_array[i]->tag);
}

void
component_trace (size_t idx, enum pies_depmap_direction dir)
{
  pies_depmap_pos_t pos;
  size_t n;
  int delim = ':';
    
  logmsg_printf (LOG_NOTICE, "%s", comp_array[idx]->tag);
  for (n = depmap_first (depmap, dir, idx, &pos);
       n != (size_t)-1;
       n = depmap_next (depmap, pos))
    {
      logmsg_printf (LOG_NOTICE, "%c %s", delim, comp_array[n]->tag);
      delim = ',';
    }
  depmap_end (pos);
  logmsg_printf (LOG_NOTICE, "\n");
}

void
components_trace (char **argv, enum pies_depmap_direction dir)
{
  if (*argv)
    for (; *argv; ++argv)
      {
	ssize_t idx = component_lookup_index (*argv);
	if (idx < 0)
	  logmsg (LOG_ERR, "%s: no such component", *argv);
	else
	  component_trace (idx, dir);
      }
  else
    {
      size_t i;

      for (i = 0; i < comp_count; i++)
	component_trace (i, dir);
    }
}

struct component *
component_depmap_first (enum pies_depmap_direction dir,	size_t idx,
			pies_depmap_pos_t *ppos)
{
  size_t n = depmap_first (depmap, dir, idx, ppos);
  if (n == (size_t)-1)
    return NULL;
  return comp_array[n];
}

struct component *
component_depmap_next (pies_depmap_pos_t pos)
{
  size_t n = depmap_next (depmap, pos);
  if (n == (size_t)-1)
    return NULL;
  return comp_array[n];
}

int
component_foreach (int (*filter) (struct component *, void *), void *data)
{
  struct component *comp;
  int rc = 0;
  struct complist *list = &comp_list[cur];

  for (comp = list->head; comp; )
    {
      struct component *next = comp->next;
      if ((rc = filter (comp, data)) != 0)
	break;
      comp = next;
    }
  return rc;
}





