/* Copyright (c) 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 /* for stpcpy */

#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/resource.h>
#include <rpc/xdr.h>
#include <rpcsvc/nis.h>

#include "nis_db.h"
#include "db_private.h"
#include "nisdb_priv.h"
#include "xdb.h"

static struct {
  xdb_header header;
  xdb_header header_log;
  FILE *fp;
  FILE *fp_log;
  char *path;
  char *path_log;
  table_t *tobj;
} catalog_ptr = {
  {XDB_VER_STRING, XDB_VERSION, FALSE, XDB_FILE, 0},
  {XDB_VER_STRING" (log)", XDB_VERSION, FALSE, XDB_LOG, 0},
  NULL,
  NULL,
  NULL,
  NULL,
  NULL
};

bool_t
db_initialize (const char *pathname)
{
  char *path_log, *cp;
  struct rlimit limit;
  bool_t cold_start = FALSE;

  if (pathname == NULL || pathname[0] == '\0')
    return FALSE;

  catalog_ptr.header_log.header_size =
    xdr_sizeof ((xdrproc_t) xdr_xdb_header, &catalog_ptr.header_log);

  catalog_ptr.header.header_size =
    xdr_sizeof ((xdrproc_t) xdr_xdb_header, &catalog_ptr.header);

  /* Set open file limit from default to maximum. */
  if (getrlimit (RLIMIT_NOFILE, &limit) == -1)
      return FALSE;
  limit.rlim_cur = limit.rlim_max;
  if (setrlimit(RLIMIT_NOFILE, &limit) == -1)
    return FALSE;

  path_log = alloca (strlen (pathname) + 5);
  if (path_log == NULL)
    return FALSE;

  cp = stpcpy (path_log, pathname);
  strcpy (cp, ".log");

  /* See if we have already this database. */
  if (access (pathname, R_OK|W_OK) == -1)
    {
      if (errno == ENOENT)
        {
	  /* Check if we have a log file, or if we have
	     to create one. */
	  if (access (path_log, R_OK|W_OK) == -1)
	    {
	      if (errno == ENOENT)
		cold_start = TRUE;
	      else
		return FALSE;
	    }
	}
      else
	return FALSE;
    }

  if (cold_start) /* We don't have any database, so create a log file */
    {
      XDR xdrs;

      catalog_ptr.fp_log = fopen (path_log, "w+");
      if (catalog_ptr.fp_log == NULL)
	return FALSE;
      __close_on_exit (&catalog_ptr.fp_log);

      catalog_ptr.header.log_present = TRUE;
      catalog_ptr.header_log.log_present = TRUE; /* This could be ignored */

      xdrstdio_create (&xdrs, catalog_ptr.fp_log, XDR_ENCODE);
      if (!xdr_xdb_header (&xdrs, &catalog_ptr.header_log))
	{
	  xdr_destroy (&xdrs);
	  fclose (catalog_ptr.fp_log);
	  catalog_ptr.fp_log = NULL;
	  unlink (path_log);
	  return FALSE;
	}
      xdr_destroy (&xdrs);
    }
  else
    { /* No cold_start, so we have already a data dictionary */
      XDR xdrs;
      table_t *tobj;

      /* If we have a data file, load the header and tables */
      if (access (pathname, R_OK|W_OK) >= 0)
	{
	  catalog_ptr.fp = fopen (pathname, "rb+");
	  if (catalog_ptr.fp == NULL)
	    return FALSE;
	  __close_on_exit (&catalog_ptr.fp);

	  memset (&catalog_ptr.header, 0, sizeof (xdb_header));
	  xdrstdio_create (&xdrs, catalog_ptr.fp, XDR_DECODE);
	  if (!xdr_xdb_header (&xdrs, &catalog_ptr.header))
	    {
	      xdr_destroy (&xdrs);
	      fclose (catalog_ptr.fp);
	      catalog_ptr.fp = NULL;
	      return FALSE;
	    }

	  if (catalog_ptr.header.vers != XDB_VERSION ||
	      strncmp (catalog_ptr.header.identifier, XDB_VER_STRING,
		       strlen (XDB_VER_STRING)) != 0 ||
	      catalog_ptr.header.type != XDB_FILE)
	    return FALSE;

	  while (!feof (catalog_ptr.fp))
	    { /* Read the datafile here */
	      table_t *t_entry;
	      xdb_logentry lentry;

	      memset (&lentry, 0, sizeof (xdb_logentry));
	      if (!xdr_xdb_logentry (&xdrs, &lentry))
		{
		  if (feof (catalog_ptr.fp))
		    break;
		  fclose (catalog_ptr.fp);
		  catalog_ptr.fp = NULL;
		  return FALSE;
		}
	      if (lentry.action != LOG_ADD)
		{
		  fclose (catalog_ptr.fp);
		  catalog_ptr.fp = NULL;
		  return FALSE;
		}

	      t_entry = malloc (sizeof (table_t));
	      if (t_entry == NULL)
		return FALSE;

	      t_entry->object = calloc (1, sizeof (table_obj));
	      if (t_entry->object == NULL)
		return FALSE;

	      if (!__nisdb_xdr_table_obj (&xdrs, t_entry->object))
		{
		  fclose (catalog_ptr.fp);
		  catalog_ptr.fp = NULL;
		  return FALSE;
		}
	      t_entry->hashkey = lentry.hashkey;
	      t_entry->name = strdup (lentry.table_name);
	      t_entry->path = strdup (lentry.table_path);
	      {
		char log [strlen (t_entry->path) + 5];
		cp = stpcpy (log, t_entry->path);
		strcpy (cp, ".log");
		t_entry->path_log = strdup (log);
	      }
	      t_entry->next = catalog_ptr.tobj;
	      t_entry->entry = NULL;
	      catalog_ptr.tobj = t_entry;

	      xdr_free ((xdrproc_t) xdr_xdb_logentry, (caddr_t) &lentry);
	    }

	  xdr_destroy (&xdrs);
	}
      else
	{
	  if (errno != ENOENT)
	    return FALSE; /* Seems we don't have permissions to access it */
	  else
	    catalog_ptr.header.log_present = TRUE; /* We must have an
						      log file */
	}

      if (catalog_ptr.header.log_present)
	{ /* We have a log file flag */
	  xdb_logentry lentry;

	  catalog_ptr.fp_log = fopen (path_log, "rb+");
	  if (catalog_ptr.fp_log == NULL)
	    return FALSE;
	  __close_on_exit (&catalog_ptr.fp_log);

	  /* Read the header of the log file */
	  memset (&catalog_ptr.header_log, 0, sizeof (xdb_header));
	  xdrstdio_create (&xdrs, catalog_ptr.fp_log, XDR_DECODE);
	  if (!xdr_xdb_header (&xdrs, &catalog_ptr.header_log))
	    {
	      xdr_destroy (&xdrs);
	      fclose (catalog_ptr.fp_log);
	      catalog_ptr.fp_log = NULL;
	      return FALSE;
	    }

	  if (catalog_ptr.header_log.vers != XDB_VERSION ||
	      strncmp (catalog_ptr.header_log.identifier, XDB_VER_STRING,
		       strlen (XDB_VER_STRING)) != 0 ||
	      catalog_ptr.header_log.type != XDB_LOG)
	    return FALSE;

	  while (!feof (catalog_ptr.fp_log))
	    { /* Read the logfile here */
	      memset (&lentry, 0, sizeof (xdb_logentry));
	      if (!xdr_xdb_logentry (&xdrs, &lentry))
		{
		  if (feof (catalog_ptr.fp_log))
		      break;
		  fclose (catalog_ptr.fp_log);
		  catalog_ptr.fp_log = NULL;
		  return FALSE;
		}
	      switch (lentry.action)
		{
		case LOG_ADD:
		  {
		    table_t *t_entry = malloc (sizeof (table_t));

		    if (t_entry == NULL)
		      return FALSE;
		    t_entry->object = calloc (1, sizeof (table_obj));
		    if (t_entry->object == NULL)
		      return FALSE;

		    if (!__nisdb_xdr_table_obj (&xdrs, t_entry->object))
		      {
			fclose (catalog_ptr.fp_log);
			catalog_ptr.fp_log = NULL;
			return FALSE;
		      }
		    t_entry->hashkey = lentry.hashkey;
		    t_entry->name = strdup (lentry.table_name);
		    t_entry->path = strdup (lentry.table_path);
		    {
		      char log [strlen (t_entry->path) + 5];
		      cp = stpcpy (log, t_entry->path);
		      strcpy (cp, ".log");
		      t_entry->path_log = strdup (log);
		    }
		    t_entry->next = catalog_ptr.tobj;
		    t_entry->entry = NULL;
		    catalog_ptr.tobj = t_entry;
		  }
		  break;
		case LOG_DELETE:
		  {
		    table_t *t_old = NULL;

		    tobj = catalog_ptr.tobj;

		    while (tobj != NULL)
		      {
			if (tobj->hashkey == lentry.hashkey)
			  if (strcmp (tobj->name, lentry.table_name) == 0)
			    {
			      if (t_old == NULL)
				catalog_ptr.tobj = catalog_ptr.tobj->next;
			      else
				t_old->next = t_old->next->next;

			      free (tobj->name);
			      free (tobj->path);
			      free (tobj->path_log);
			      xdr_free ((xdrproc_t) __nisdb_xdr_table_obj,
					(caddr_t) tobj->object);
			      free (tobj->object);
			      xdr_free ((xdrproc_t) xdr_xdb_header,
					(caddr_t) &tobj->header);
			      free (tobj);

			      break; /* Bail out, we delete the entry */
			    }
			t_old = tobj;
			tobj = tobj->next;
		      }
		  }
		  break;
		case LOG_IGNORE:
		  /* Ignore entry */
		  break;
		default:
		  /* Logfile is broken. Maybe need external tool for
		     repairing it ? */
		  return FALSE;
		}
	      xdr_free ((xdrproc_t) xdr_xdb_logentry, (caddr_t) &lentry);
	    }

	  xdr_destroy (&xdrs);
	}

      tobj = catalog_ptr.tobj;
      while (tobj != NULL)
	{
	  if (!xdb_read_header (tobj->path, &tobj->header))
	    return FALSE;
	  if (__load_entries (tobj) != DB_SUCCESS)
	    return FALSE;
	  tobj = tobj->next;
	}
    }

  catalog_ptr.path = strdup (pathname);
  catalog_ptr.path_log = strdup (path_log);

  return TRUE;
}

db_status
__catalog_checkpoint (void)
{
  table_t *tobj;
  int has_log = 0;
  FILE *fp;
  XDR xdrs;
  char *filename, *cp;

  tobj = catalog_ptr.tobj;

  while (tobj != NULL)
    {
      if (tobj->header.log_present)
	++has_log;
      tobj = tobj->next;
    }

  if (has_log)
    return DB_SUCCESS;

  tobj = catalog_ptr.tobj;

  filename = alloca (strlen (catalog_ptr.path) + 5);
  cp = stpcpy (filename, catalog_ptr.path);
  strcpy (cp, ".tmp");
  unlink (filename);
  fp = fopen (filename, "w");
  if (fp == NULL)
    return DB_INTERNAL_ERROR;
  __close_on_exit (&fp);

  catalog_ptr.header.type = XDB_FILE;
  catalog_ptr.header.log_present = FALSE;

  xdrstdio_create (&xdrs, fp, XDR_ENCODE);
  if (!xdr_xdb_header (&xdrs, &catalog_ptr.header))
    {
      xdr_destroy (&xdrs);
      fclose (fp);
      unlink (filename);
      return DB_INTERNAL_ERROR;
    }

  while (tobj != NULL)
    {
      xdb_logentry lentry;

      /* Add the table to the logfile of the database. */
      lentry.action = LOG_ADD;
      lentry.hashkey = tobj->hashkey;
      lentry.table_name = tobj->name;
      lentry.table_path = tobj->path;

      if (!xdr_xdb_logentry (&xdrs, &lentry))
	{
	  xdr_destroy (&xdrs);
	  fclose (fp);
	  unlink (filename);
	  return DB_INTERNAL_ERROR;
	}

      if (!__nisdb_xdr_table_obj (&xdrs, tobj->object))
	{
	  xdr_destroy (&xdrs);
	  fclose (fp);
	  unlink (filename);
	  return DB_INTERNAL_ERROR;
	}
      tobj = tobj->next;
    }

  xdr_destroy (&xdrs);
  fclose (fp);

  if (rename (filename, catalog_ptr.path) != 0)
    {
      unlink (filename);
      catalog_ptr.header.log_present = TRUE;
      return DB_INTERNAL_ERROR;
    }

  /* If a log file is open, close it */
  if (catalog_ptr.fp_log)
    {
      fclose (catalog_ptr.fp_log);
      catalog_ptr.fp_log = NULL;
    }
  unlink (catalog_ptr.path_log);

  return DB_SUCCESS;
}

table_t *
__get_table (const char *name)
{
  table_t *t_entry;
  uint32_t hkey;

  hkey = __nis_hash (name, strlen (name));

  t_entry = catalog_ptr.tobj;

  while (t_entry != NULL)
    {
      if (t_entry->hashkey == hkey)
	if (strcmp (t_entry->name, name) == 0)
	  return t_entry;
      t_entry = t_entry->next;
    }

  return NULL;
}


db_status
__add_table (table_t *t_entry)
{
  xdb_logentry lentry;
  XDR xdrs;
  long int save_pos;

  if (t_entry->path_log == NULL)
    {
      char path_log [strlen (t_entry->name) + 5], *cp;

      cp = stpcpy (path_log, t_entry->name);
      strcpy (cp, ".log");
      t_entry->path_log = strdup (path_log);
    }

  /* Create the new table file, ignore old files. */
  unlink (t_entry->path_log);
  unlink (t_entry->name);
  t_entry->fp_log = fopen (t_entry->path_log, "w+");
  if (t_entry->fp_log == NULL)
    return DB_INTERNAL_ERROR;
  __close_on_exit (&t_entry->fp_log);

  /* Fill in the header */
  t_entry->header.identifier = XDB_VER_STRING;
  t_entry->header.vers = XDB_VERSION;
  t_entry->header.log_present = TRUE;
  t_entry->header.type = XDB_LOG;
  t_entry->header.header_size = xdr_sizeof ((xdrproc_t) xdr_xdb_header,
					    &t_entry->header);

  xdrstdio_create (&xdrs, t_entry->fp_log, XDR_ENCODE);
  if (!xdr_xdb_header (&xdrs, &t_entry->header))
    {
      xdr_destroy (&xdrs);
      fclose (t_entry->fp_log);
      t_entry->fp_log = NULL;
      unlink (t_entry->path_log);
      return DB_INTERNAL_ERROR;
    }
  xdr_destroy (&xdrs);


  /* Add the table to the logfile of the database. */
  lentry.action = LOG_ADD;
  lentry.hashkey = t_entry->hashkey;
  lentry.table_name = t_entry->name;
  lentry.table_path = t_entry->path;

  if (catalog_ptr.fp_log == NULL)
    {
      catalog_ptr.fp_log = fopen (catalog_ptr.path_log, "w+");
      if (catalog_ptr.fp_log == NULL)
	return DB_INTERNAL_ERROR;
      __close_on_exit (&catalog_ptr.fp_log);

      xdrstdio_create (&xdrs, catalog_ptr.fp_log, XDR_ENCODE);
      if (!xdr_xdb_header (&xdrs, &catalog_ptr.header_log))
	{
	  xdr_destroy (&xdrs);
	  fclose (catalog_ptr.fp_log);
	  catalog_ptr.fp_log = NULL;
	  unlink (catalog_ptr.path_log);
	  return DB_INTERNAL_ERROR;
	}
      xdr_destroy (&xdrs);
      if (catalog_ptr.fp != NULL)
	xdb_set_log_present (catalog_ptr.fp, &catalog_ptr.header);
    }

  fseek (catalog_ptr.fp_log, 0, SEEK_END);
  save_pos = ftell (catalog_ptr.fp_log);
  xdrstdio_create (&xdrs, catalog_ptr.fp_log, XDR_ENCODE);
  if (!xdr_xdb_logentry (&xdrs, &lentry))
    {
    try_to_restore:
      xdr_destroy (&xdrs);
      fclose (catalog_ptr.fp_log);
      catalog_ptr.fp_log = NULL;
      return DB_INTERNAL_ERROR;
   }
  if (!__nisdb_xdr_table_obj (&xdrs, t_entry->object))
    {
      /* Remove last log entry: */
      fseek (catalog_ptr.fp_log, save_pos, SEEK_SET);
      lentry.action = LOG_IGNORE;
      xdr_xdb_logentry (&xdrs, &lentry);
      goto try_to_restore;
    }
  xdr_destroy (&xdrs);

  t_entry->next = catalog_ptr.tobj;
  catalog_ptr.tobj = t_entry;

  return DB_SUCCESS;
}


table_t *
__destroy_table (const char *name)
{
  table_t *t_entry, *t_old;
  uint32_t hkey;

  hkey = __nis_hash (name, strlen (name));

  t_entry = catalog_ptr.tobj;
  t_old = NULL;

  while (t_entry != NULL)
    {
      if (t_entry->hashkey == hkey)
	if (strcmp (t_entry->name, name) == 0)
	  {
	    xdb_logentry lentry;
	    XDR xdrs;

	    if (t_old == NULL)
	      catalog_ptr.tobj = catalog_ptr.tobj->next;
	    else
	      t_old->next = t_old->next->next;

	    /* Add the table to the logfile of the database. */
	    lentry.action = LOG_DELETE;
	    lentry.hashkey = t_entry->hashkey;
	    lentry.table_name = t_entry->name;
	    lentry.table_path = t_entry->path;

	    /* If we don't have a log file, create one.
	       catalog_ptr couldn't be in standby mode. */
	    if (catalog_ptr.fp_log == NULL)
	      {
		catalog_ptr.fp_log = fopen (catalog_ptr.path_log, "w+");
		if (catalog_ptr.fp_log == NULL)
		  return NULL;
		__close_on_exit (&catalog_ptr.fp_log);

		xdrstdio_create (&xdrs, catalog_ptr.fp_log, XDR_ENCODE);
		if (!xdr_xdb_header (&xdrs, &catalog_ptr.header_log))
		  {
		    xdr_destroy (&xdrs);
		    fclose (catalog_ptr.fp_log);
		    catalog_ptr.fp_log = NULL;
		    unlink (catalog_ptr.path_log);
		    return NULL;
		  }
		xdr_destroy (&xdrs);
		if (catalog_ptr.fp != NULL)
		  xdb_set_log_present (catalog_ptr.fp, &catalog_ptr.header);
	      }

	    fseek (catalog_ptr.fp_log, 0, SEEK_END);
	    xdrstdio_create (&xdrs, catalog_ptr.fp_log, XDR_ENCODE);
	    if (!xdr_xdb_logentry (&xdrs, &lentry))
	      {
		xdr_destroy (&xdrs);
		fclose (catalog_ptr.fp_log);
		catalog_ptr.fp_log = NULL;
		return NULL;
	      }
	    xdr_destroy (&xdrs);

	    return t_entry;
	  }
      t_old = t_entry;
      t_entry = t_entry->next;
    }

  return NULL;
}
