/* Copyright (C) 1998, 1999 Thorsten Kukuk
   Author: Thorsten Kukuk <kukuk@vt.uni-paderborn.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 <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#else
#include "lib/compat/getopt.h"
#endif
#include <locale.h>
#include <libintl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <rpcsvc/nis.h>

#include "nis_xdr.h"
#include "nis_log.h"

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

/* Name and version of program.  */
/* Print the version information.  */
static inline void
print_version (void)
{
  fprintf (stdout, "nislog (%s) %s\n", PACKAGE, VERSION);
  fprintf (stdout, gettext ("\
Copyright (C) %s Thorsten Kukuk.\n\
This is free software; see the source for copying conditions.  There is NO\n\
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
"), "1998");
  /*  fprintf (stdout, _("Written by %s.\n"), "Thorsten Kukuk"); */
}

static inline void
print_usage (void)
{
  fputs (_("Usage: nislog [-h num | -t num] [-v] [directory ...]\n"), stdout);
}

static void
print_help (void)
{
  print_usage ();
  fputs (_("nislog - display the contents of the NIS+ transaction log\n\n"),
	 stdout);

  fputs (_("  -h num         Display num transactions from the head of the log\n"), stdout);
  fputs (_("  -t num         Display num transactions from the tail of the log\n"), stdout);
  fputs (_("  --help         Give this help list\n"), stdout);
  fputs (_("  --usage        Give a short usage message\n"), stdout);
  fputs (_("  --version      Print program version\n"), stdout);
}

static inline void
print_error (void)
{
  const char *program = "nislog";

  fprintf (stderr,
	   _("Try `%s --help' or `%s --usage' for more information.\n"),
	   program, program);
}

static struct log_header *hptr;
static struct log_spacer *sptr;
static caddr_t laddr;
static off_t lsize;

static void
nis_position_log (long int count, const char *dirname)
{
  int cnt = count;
  struct log_spacer *s;
  caddr_t ptr;

  ptr = laddr + (hptr->size - sizeof (struct log_spacer));
  s = (struct log_spacer *) ptr;

  while (cnt)
    {
      if (s->magic != SPACER_MAGIC)
	{
	  fputs (_("log is corrupt!\n"), stderr);
	  exit (1);
	}
      if (!s->prev_offset)
	break;
      ptr = (caddr_t) s;
      ptr -= s->prev_offset + sizeof (struct log_spacer);
      s = (struct log_spacer *) ptr;
      if (dirname != NULL)
	{
	  log_entry ent;
	  XDR xdrs;
	  caddr_t addr;

	  memset (&ent, '\0', sizeof (ent));
	  addr = (caddr_t) sptr + sizeof (struct log_spacer);
	  xdrmem_create (&xdrs, addr, sptr->next_offset, XDR_DECODE);
	  xdr_log_entry (&xdrs, &ent);
	  xdr_destroy (&xdrs);
	  xdr_free ((xdrproc_t) xdr_log_entry, (caddr_t) & ent);

	  if (strcmp (dirname, nis_domain_of (ent.le_name)) == 0)
	    --cnt;
	}
      else
	--cnt;
    }
  sptr = s;

  return;
}

static void
nis_dump_log (long int count, const char *dirname)
{
  log_entry ent;
  XDR xdrs;
  caddr_t addr;
  unsigned long update = 0;
  char *letype[] =
  {"NOP", "ADD Name", "REMOVE Name",
   "MODIFY (Original Value)",
   "MODIFY (New Value)",
   "ADD Entry", "REMOVE Entry",
   "MODIFY Entry",
   "UPDATE time stamp"};
  while (sptr->next_offset)
    {
      caddr_t ptr;

      if (sptr->magic != SPACER_MAGIC)
	{
	  fputs (_("log is corrupt!\n"), stderr);
	  exit (1);
	}
      memset (&ent, '\0', sizeof (ent));
      addr = (caddr_t) sptr + sizeof (struct log_spacer);
      xdrmem_create (&xdrs, addr, sptr->next_offset, XDR_DECODE);
      xdr_log_entry (&xdrs, &ent);
      xdr_destroy (&xdrs);

      if (dirname == NULL ||
	  (dirname != NULL &&
	   strcmp (dirname, nis_domain_of (ent.le_name)) == 0))
	{
	  printf ("@@@@@@@@@@@@@@@@ Transaction @@@@@@@@@@@@@@@@@@\n");
	  printf ("#%05lu, XID : %lu\n", update, sptr->xid);
	  printf ("Time        : %s", ctime ((time_t *)&ent.le_time));
	  printf ("Directory   : %s\n", nis_domain_of (ent.le_name));
	  printf ("Entry type  : %s\n", letype[ent.le_type]);
	  printf ("Enrty timestamp : %s", ctime ((time_t *)&ent.le_time));
	  printf ("Principal       : %s\n", ent.le_princp);
	  printf ("Object name     : %s\n", ent.le_name);
	  printf (".................. Object .....................\n");
	  nis_print_object (&ent.le_object);
	  printf ("...............................................\n");
	}
      xdr_free ((xdrproc_t) xdr_log_entry, (caddr_t) & ent);
      if ((update + 1) == (u_long) count)
	break;
      ptr = (caddr_t) sptr;
      ptr += sptr->next_offset + sizeof (struct log_spacer);
      sptr = (struct log_spacer *) ptr;
      update++;
    }

  return;
}

int
main (int argc, char **argv)
{
  int tail = 0, head = 0, log_fd;
  long int count = 0, i;
  struct stat st;

  setlocale (LC_MESSAGES, "");
  bindtextdomain (PACKAGE, LOCALEDIR);
  textdomain (PACKAGE);

  if (geteuid () != 0)
    {
      fputs (_("nislog must be run as root.\n"), stderr);
      return 1;
    }

  while (1)
    {
      int c;
      int option_index = 0;
      static struct option long_options[] =
      {
	{"version", no_argument, NULL, '\255'},
	{"usage", no_argument, NULL, '\254'},
	{"help", no_argument, NULL, '\253'},
	{NULL, 0, NULL, '\0'}
      };

      c = getopt_long (argc, argv, "h:t:", long_options, &option_index);
      if (c == (-1))
	break;
      switch (c)
	{
	case 'h':
	  if (tail)
	    {
	      print_error ();
	      return 1;
	    }
	  ++head;
	  if (isdigit (optarg[0]))
	    count = atol (optarg);
	  if (!count)
	    count = 1;
	  break;
	case 't':
	  if (head)
	    {
	      print_error ();
	      return 1;
	    }
	  ++tail;
	  if (isdigit (optarg[0]))
	    count = atol (optarg);
	  if (!count)
	    count = 1;
	  break;
	case '\253':
	  print_help ();
	  return 0;
	case '\255':
	  print_version ();
	  return 0;
	case '\254':
	  print_usage ();
	  return 0;
	default:
	  print_error ();
	  return 1;
	}
    }

  argc -= optind;
  argv += optind;

  if (argc > 1)
    {
      fprintf (stderr, _("%s: To many arguments\n"), "nislog");
      print_error ();
      return 1;
    }

  if (stat (_PATH_NIS_LOG, &st) == -1)
    {
      fputs (_("couldn't open log\n"), stderr);
      return 1;
    }
  else
    {
      if ((log_fd = open (_PATH_NIS_LOG, O_RDONLY, 0600)) == -1)
	{
	  fputs (_("couldn't open log\n"), stderr);
	  return 1;
	}
      lsize = st.st_size;
    }

  laddr = mmap (0, lsize, PROT_READ, MAP_PRIVATE, log_fd, 0);
  if ((long) laddr == -1)
    {
      fputs (_("mmap failed\n"), stderr);
      close (log_fd);
      return 1;
    }

  hptr = (struct log_header *) laddr;
  sptr = (struct log_spacer *) (laddr + hptr->offset);
  if (hptr->magic != HEADER_MAGIC ||
      sptr->magic != SPACER_MAGIC)
    {
      fputs (_("transaction log corrupt! aborting!\n"), stderr);
      munmap (laddr, lsize);
      close (log_fd);
      return 1;
    }

  fputs (_("NIS Log printing facility\n"), stdout);
  fputs (_("NIS Log dump :\n"), stdout);
  fputs (_("        NIS Log State : "), stdout);

  switch (hptr->state)
    {
    case LOG_STABLE:
      fputs ("STABLE\n", stdout);
      break;
    case LOG_UPDATING:
      fputs ("UPDATING\n", stdout);
      break;
    case LOG_SYNCING:
      fputs ("SYNCING\n", stdout);
      break;
    default:
      fputs (_("unknown\n"), stdout);
      break;
    }
  printf (_("Number of updates    : %lu\n"), hptr->count);
  printf (_("Current XID          : %lu\n"), hptr->xid);
  printf (_("Size of Log in bytes : %qu\n"), hptr->size);

  printf ("*** UPDATES ***\n");

  if (argc == 0)
    {
      if (tail)
	nis_position_log (count, NULL);
      nis_dump_log (count, NULL);
    }
  else
    for (i = 0; i < argc; ++i)
      {
	if (tail)
	  nis_position_log (count, argv[i]);
	nis_dump_log (count, argv[i]);
      }

  munmap (laddr, lsize);
  close (log_fd);

  return 0;
}
