/* Copyright (C) 1998, 1999 Thorsten Kukuk
   Author: Thorsten Kukuk <kukuk@suse.de>

   This program 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 2, or (at your option)
   any later version.

   This program 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 this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#define _GNU_SOURCE

#include <ctype.h>
#include <errno.h>
#include <locale.h>
#include <libintl.h>
#include <string.h>
#include <rpcsvc/nis.h>
#include <sys/resource.h>

#include "log_msg.h"
#include "nisd.h"

#ifndef _
#define _(String) gettext (String)
#endif

/* This list contains the directorys we are serving */
static char **srvlist = NULL;

/* Operations statistics. These are part of the server state, so they
   are kept here. We keep track of things like how many times our procedures
   are called and their success/failure rates. */

struct stat_info {
  unsigned long call;
  unsigned long fail;
  unsigned long tick_tot;
  unsigned long tick_avg;
  unsigned long tick_cnt;

};

static struct stat_info nis_stat[NIS_UPDKEYS];

#define STATS_ALL 0

void
nis_init_stats (void)
{
  memset (&nis_stat, '\0', sizeof (nis_stat));
}

void
nis_update_stats (unsigned int op, bool_t result, unsigned long ticks)
{
  if (op > NIS_UPDKEYS)
    return;

  nis_stat[op].call++;
  if (nis_stat[op].tick_cnt == 16)
    {
      nis_stat[op].tick_cnt = 0;
      nis_stat[op].tick_tot = 0;
      nis_stat[op].tick_avg = 0;
    }
  nis_stat[op].tick_cnt++;
  nis_stat[op].tick_tot += ticks;
  nis_stat[op].tick_avg = nis_stat[op].tick_tot / nis_stat[op].tick_cnt;
  nis_stat[STATS_ALL].call++;
  if (result == FALSE)
    {
      nis_stat[op].fail++;
      nis_stat[STATS_ALL].fail++;
    }

  return;
}

static char *function_names[] =
{
  "NULLPROC", "LOOKUP", "ADD", "MODIFY", "REMOVE", "LIST", "ADDENTRY",
  "MODENTRY", "REMENTRY", "FIRSTENTRY", "NEXTENTRY", "RESRVD1", "FINDDIR",
  "RESRVD2", "STATUS", "DUMPLOG", "DUMP", "CALLBACK", "CPTIME", "CHECKPOINT",
  "PING", "SERVSTATE", "MKDIR", "RMDIR", "UPDKEYS"};

void
nis_getstate (nis_taglist *tags, nis_taglist *res)
{
  unsigned int i, j;
  nis_tag *tag = NULL;
  char buf[1024], tbuf[1024];
  struct rusage rusage;
  unsigned long days = 0, hrs = 0, mins = 0, secs = 0;
  time_t uptime;

  res->tags.tags_val = calloc (tags->tags.tags_len, sizeof (nis_tag));
  res->tags.tags_len = tags->tags.tags_len;

  for (i = 0; i < tags->tags.tags_len; i++)
    {
      tag = &tags->tags.tags_val[i];
      switch (tag->tag_type)
	{
	case TAG_UPTIME:
	  uptime = time (NULL) - boottime;
	  days = uptime / 86400;
	  hrs = (uptime - (days * 86400)) / 3600;
	  mins = (uptime - (days * 86400) - (hrs * 3600)) / 60;
	  secs = uptime - (days * 86400) - (hrs * 3600) - (mins * 60);
	  snprintf (buf, 1024, "up %luD, %02lu:%02lu:%02lu",
		    days, hrs, mins, secs);
	  res->tags.tags_val[i].tag_type = TAG_UPTIME;
	  res->tags.tags_val[i].tag_val = strdup (buf);
	  break;
	case TAG_SECURITY_LEVEL:
	  snprintf (buf, 1024, "%d", securelevel);
	  res->tags.tags_val[i].tag_type = TAG_SECURITY_LEVEL;
	  res->tags.tags_val[i].tag_val = strdup (buf);
	  break;
	case TAG_DNSFORWARDING:
	  res->tags.tags_val[i].tag_type = TAG_DNSFORWARDING;
	  res->tags.tags_val[i].tag_val =
	    strdup ("DNS forwarding not supported");
	  break;
	case TAG_NISCOMPAT:
	  res->tags.tags_val[i].tag_type = TAG_NISCOMPAT;
	  res->tags.tags_val[i].tag_val =
	    strdup ("YP compat mode not supported");
	  break;
	case TAG_S_STORAGE:
	  getrusage (RUSAGE_SELF, &rusage);
	  snprintf (buf, 1024, "%ld", rusage.ru_idrss);
	  res->tags.tags_val[i].tag_type = TAG_S_STORAGE;
	  res->tags.tags_val[i].tag_val = strdup (buf);
	  break;
	case TAG_S_DCACHE:
	  {
	    unsigned long calls, hits, misses;
	    get_dcache_stats (&hits, &misses, &calls);
	    snprintf (buf, 1024, "C=%lu:H=%lu:M=%lu:HR=%lu%%",
		      calls, hits, misses,
		      calls == 0 ? 0 : ((calls - misses) * 100 / calls));
	    res->tags.tags_val[i].tag_type = TAG_S_DCACHE;
	    res->tags.tags_val[i].tag_val = strdup (buf);
	  }
	  break;
	case TAG_S_OCACHE:
	  {
	    unsigned long calls, hits, misses;

	    get_ocache_stats (&hits, &misses, &calls);
	    snprintf (buf, 1024, "C=%lu:H=%lu:M=%lu:HR=%lu%%",
		      calls, hits, misses,
		      calls == 0 ? 0 : ((calls - misses) * 100 / calls));
	    res->tags.tags_val[i].tag_type = TAG_S_OCACHE;
	    res->tags.tags_val[i].tag_val = strdup (buf);
	  }
	  break;
	case TAG_DEBUG:
	  res->tags.tags_val[i].tag_type = TAG_DEBUG;
	  res->tags.tags_val[i].tag_val = strdup (debug_flag ? "ON" : "OFF");
	  break;
	case TAG_DIRLIST:
	  {
	    char **slist = srvlist;
	    /* XXX Rewrite it, it is more then stupid, insecure and slow !! */
	    memset (buf, '\0', sizeof (buf));
	    while (*slist != NULL)
	      {
		strncat (buf, *slist, sizeof (buf) - 1);
		slist++;
		if (*slist != NULL)
		  strncat (buf, " ", sizeof (buf));
	      }
	    res->tags.tags_val[i].tag_type = TAG_DIRLIST;
	    res->tags.tags_val[i].tag_val = strdup (buf);
	  }
	  break;
	case TAG_ROOTSERVER:
	  res->tags.tags_val[i].tag_type = TAG_ROOTSERVER;
	  res->tags.tags_val[i].tag_val = strdup (root_obj != NULL ?
						  "ON" : "OFF");
	  break;
	case TAG_OPSTATS:
	  /* XXX Rewrite it, it is stupid, slow and insecure !! */
	  memset (buf, '\0', sizeof (buf));
	  for (j = 1; j < NIS_UPDKEYS; j++)
	    {
	      if (nis_stat[j].call)
		{
		  snprintf (tbuf, sizeof (tbuf), "OP=%s:C=%lu:E=%lu:T=%lu",
			    function_names[j], nis_stat[j].call,
			    nis_stat[j].fail, nis_stat[j].tick_avg);
		  strncat (buf, "\n", sizeof (buf));
		  strncat (buf, tbuf, sizeof (buf));
		}
	    }
	  if (buf[0] == '\0')
	    strcpy (buf, "\n");
	  res->tags.tags_val[i].tag_type = TAG_OPSTATS;
	  res->tags.tags_val[i].tag_val = strdup (buf);
	  break;
	case TAG_HEAP:
	default:
	  res->tags.tags_val[i].tag_type = tag->tag_type;
	  res->tags.tags_val[i].tag_val =
	    strdup ("unknown");
	  break;
	}
    }
  return;
}

bool_t
is_in_srvlist (const_nis_name name)
{
  char **slist = srvlist;

  while (*slist != NULL)
    {
      if (strcmp (name, *slist) == 0)
	return TRUE;

      ++slist;
    }

  return FALSE;
}

nis_error
add_to_srvlist (const_nis_name name)
{
  char **new = srvlist;
  int srvcnt = 0;
  bool_t found = FALSE;

  while (*new != NULL)
    {
      if (strcmp (name, *new) == 0)
	found = TRUE;
      ++srvcnt;
      ++new;
    }

  if (found)
    return NIS_NOTUNIQUE;

  new = realloc (srvlist, (srvcnt + 2) * sizeof(char *));
  if (new == NULL)
    return NIS_NOMEMORY;

  new[srvcnt] = strdup (name);
  ++srvcnt;
  new[srvcnt] = NULL;
  srvlist = new;

  return nis_store_srvlist ();
}

nis_error
remove_from_srvlist (const_nis_name name)
{
  char **tofree, **old = srvlist;
  int srvcnt = 0;
  bool_t found = FALSE;

  while (*old != NULL)
    {
      if (strcmp (name, *old) == 0)
	found = TRUE;
      ++srvcnt;
      ++old;
    }

  if (!found)
    return NIS_NOTFOUND;

  --srvcnt;

  old = srvlist;
  tofree = srvlist;
  srvlist = malloc ((srvcnt + 1) * sizeof(char *));
  if (srvlist == NULL)
    {
      srvlist = old;
      return NIS_NOMEMORY;
    }

  srvcnt = 0;
  while (*old != NULL)
    {
      if (strcmp (name, *old) != 0)
	{
	  srvlist[srvcnt] = *old;
	  ++srvcnt;
	}
      else
	free (*old);
      ++old;
    }
  srvlist[srvcnt] = NULL;
  free (tofree);

  return nis_store_srvlist ();
}

nis_error
nis_load_srvlist (void)
{
  FILE *input;
  char **new;
  char *line = NULL;
  size_t len = 0;
  int srvcnt = 0, i = 0;

  srvlist = calloc(1, sizeof(char *));
  if (srvlist == NULL)
    return(NIS_NOMEMORY);

  srvcnt = 0;
  srvlist[srvcnt] = NULL;

  if ((input = fopen(_PATH_NIS_SRVLIST, "r")) == NULL)
    {
      if (errno == ENOENT)
	return NIS_NOTFOUND;
      else
	return NIS_SYSTEMERROR;
    }

  do
    {
      char *cp;
      int n;

      n = getdelim (&line, &len, '\n', input);
      if (n < 0)
        break;
      ++i;
      if (line[n - 1] == '\n')
        line[n - 1] = '\0';
      cp = strchr (line, '#');
      if (cp != NULL)
        *cp = '\0';

      /* If the line is blank it is ignored.  */
      if (line[0] == '\0')
        continue;

      cp = line;
      while (isspace (*cp) && *cp != '\0')
        ++cp;
      if (*cp == '\0')
	continue;

      new = realloc (srvlist, (srvcnt + 2) * sizeof (char *));
      if (new == NULL)
	{
	  fclose (input);
	  return NIS_NOMEMORY;
	}
      new[srvcnt] = strdup (cp);
      srvcnt++;
      new[srvcnt] = NULL;
      srvlist = new;
    }
  while (!feof (input));

  free (line);
  fclose (input);
  return NIS_SUCCESS;
}

nis_error
nis_store_srvlist (void)
{
  FILE *output;
  char **slist = srvlist;

  if ((output = fopen(_PATH_NIS_SRVLIST, "w+")) == NULL)
    {
      log_msg (LOG_ERR, _("failed to dump serving list: %s"),
	       strerror (errno));
      return NIS_SYSTEMERROR;
    }

  while (*slist != NULL)
    {
      fputs (*slist, output);
      fputs ("\n", output);
      ++slist;
    }

  fclose (output);

  return NIS_SUCCESS;
}
