/* Copyright (C) 1998 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
#define NIS_INTERNAL 1

#include <time.h>
#include <stdio.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#else
#include "lib/compat/getopt.h"
#endif
#include <locale.h>
#include <libintl.h>
#include <rpcsvc/nis.h>
#include "nis_xdr.h"

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

/* Print the version information.  */
static inline void
print_version (void)
{
  fprintf (stdout, "nisping (%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: nisping [-uf] [-H hostname] [-r|directory]\n"), stdout);
  fputs (_("       nisping -C [-a] [-H hostname] [directory]\n"), stdout);
}

static void
print_help (void)
{
  print_usage ();
  fputs (_("nisping - send ping to NIS+ servers\n\n"),
	 stdout);

  fputs (_("  -a             Checkpoint all directories on the server\n"),
	 stdout);
  fputs (_("  -C             Send a request to checkpoint to each server\n"),
	 stdout);
  fputs (_("  -H hostname    Only the host \"hostname\" is used\n"), stdout);
  fputs (_("  -f, --force    Force a ping\n"), stdout);
  fputs (_("  -r             Update or get status about the root object\n"),
	 stdout);
  fputs (_("  -u, --update   Display the time of the last update\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 = "nisping";

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

static struct timeval RPCTIMEOUT = {10, 0};

static nis_error
niscall (const nis_server *server, u_int server_len, u_long prog,
	 xdrproc_t xargs, caddr_t req, xdrproc_t xres, caddr_t resp,
	 u_long flags)
{
  enum clnt_stat result;
  dir_binding dbp;
  nis_error status;

  if (flags & MASTER_ONLY)
    server_len = 1;

  status = __nisbind_create (&dbp, server, server_len, flags);
  if (status != NIS_SUCCESS)
    return status;

  while (__nisbind_connect (&dbp) != NIS_SUCCESS)
    {
      if (__nisbind_next (&dbp) != NIS_SUCCESS)
        return NIS_NAMEUNREACHABLE;
    }

  result = clnt_call (dbp.clnt, prog, xargs, req, xres, resp, RPCTIMEOUT);
  if (result != RPC_SUCCESS)
    status = NIS_RPCERROR;
  else
    status = NIS_SUCCESS;


  __nisbind_destroy (&dbp);

  return status;

}

/*
 * Get the update time from the master.
 */
static unsigned long
find_master_utime (directory_obj *dir, nis_server *master)
{
  nis_name name = dir->do_name;
  unsigned long utime;
  nis_error res;

  fprintf (stdout, _("Master server is %s\n"), master->name);

  res = niscall (&dir->do_servers.do_servers_val[0], 1,
		 NIS_CPTIME, (xdrproc_t) xdr_nis_name,
		 (caddr_t) &name, (xdrproc_t) xdr_u_long,
		 (caddr_t) &utime, NO_AUTHINFO);

  if (res != NIS_SUCCESS)
    {
      fprintf (stderr, "%s: %s\n\n", dir->do_name, nis_sperrno (res));
      return 0;
    }
  if (utime == 0)
    fprintf (stdout,
	     _("\tNo last update time available for directory %s.\n\n"),
	     dir->do_name);
  else
    fprintf (stdout, _("\tLast update occurred at %s\n"), ctime (&utime));

  return utime;
}

static int
ping_replica (nis_name dirname, ping_args *args, const nis_server *srv,
	      int force, int no_ping)
{
  unsigned long utime;
  nis_error res;

  printf(_("Replica server is %s\n"), srv->name);

  res = niscall (srv, 1, NIS_CPTIME, (xdrproc_t) xdr_nis_name,
		 (caddr_t) &dirname, (xdrproc_t) xdr_u_long,
		 (caddr_t) &utime, NO_AUTHINFO);

  if (res != NIS_SUCCESS)
    {
      fputs (_("\tUnavailable.\n\n"), stdout);
      return 1;
    }
  else
    if (utime)
      fprintf (stdout, _("\tLast update seen was %s\n"), ctime (&utime));
    else
      fprintf (stdout, _("\tNo last update available for directory %s.\n\n"),
	       dirname);

  if ((utime < args->stamp || force) && !no_ping)
    {
      fprintf (stdout, _("\tPinging ... %s\n"), srv->name);

      res = niscall (srv, 1, NIS_PING, (xdrproc_t) xdr_ping_args,
		     (caddr_t) args, (xdrproc_t) xdr_void,
		     (caddr_t) NULL, 0);

      if (res == NIS_SUCCESS)
	return 0;
      return 1;
    }

  return 0;
}

static int
do_ping (nis_object *obj, char *host, int force, int no_ping)
{
  unsigned long i;
  u_long failed = 0;
  char hname[NIS_MAXNAMELEN];
  directory_obj *dir = &obj->DI_data;
  nis_server *master = &dir->do_servers.do_servers_val[0];
  nis_server *srv = NULL;
  ping_args p_args;

  if (dir->do_servers.do_servers_len == 1 && !no_ping)
    {
      fprintf (stderr, _("%s: no replicas.\n"), dir->do_name);
      return -1;
    }

  if (host != NULL)
    {
      if (host[strlen(host) - 1] == '.')
	snprintf (hname, sizeof (hname), "%s", host);
      else
	snprintf (hname, sizeof (hname), "%s.%s", host, dir->do_name);

      for (i = 0; i < dir->do_servers.do_servers_len; ++i)
	{
	  if (nis_dir_cmp (dir->do_servers.do_servers_val[i].name, hname)
	      == SAME_NAME)
	    {
	      srv = &dir->do_servers.do_servers_val[i];
	      break;
	    }
	}
      if (srv == NULL)
	{
	  fprintf (stderr, _("Host \"%s\" does not serve directory \"%s\".\n"),
		   hname, dir->do_name);
	  return -1;
	}
      if (i == 0)
	{
	  fprintf (stderr, _("Host \"%s\" is the master server for \"%s\".\n"),
		   hname, dir->do_name);
	  return -1;
	}
    } /* if (host != NULL) */

  if (no_ping)
    fprintf (stdout, _("Last updates for directory %s:\n"), dir->do_name);
  else
    if (host != NULL)
      fprintf (stdout, _("Pinging host serving directory %s :\n"),
	       dir->do_name);
    else
      fprintf (stdout, _("Pinging replicas serving directory %s :\n"),
	       dir->do_name);

  p_args.dir = dir->do_name;
  p_args.stamp = find_master_utime (dir, master);

  /* If there's just the one user-specified host, handle only that. */
  if (host != NULL)
    {
      failed = ping_replica (dir->do_name, &p_args, srv, force, no_ping);
      if (failed == 1)
	return -1;
      else
	return 0;
    }

  for (i = 1; i < dir->do_servers.do_servers_len; ++i)
    {
      srv = &dir->do_servers.do_servers_val[i];
      failed += ping_replica (dir->do_name, &p_args, srv, force, no_ping);
    }
  if (failed == (dir->do_servers.do_servers_len -1))
    return -1;
  else
    if (failed > 0)
      return 1;

  return 0;
}

static long
send_checkpoint (nis_server *srv, nis_name dir)
{
  nis_error res;
  cp_result cpres;

  memset (&cpres, '\0', sizeof (cp_result));
  res = niscall (srv, 1, NIS_CHECKPOINT, (xdrproc_t) xdr_nis_name,
		 (caddr_t) &dir, (xdrproc_t) xdr_cp_result,
		 (caddr_t) &cpres, 0);

  if (res != NIS_SUCCESS)
    {
      fputs (_("\tUnavailable.\n"), stdout);
      return 1;
    }

  if (cpres.cp_status != NIS_SUCCESS)
    {
      fprintf (stdout, _("checkpoint failed : %s\n"),
	       nis_sperrno (cpres.cp_status));
      return 1;
    }
  else
    {
      fprintf (stdout, _("checkpoint scheduled on %s.\n"), srv->name);
      return 0;
    }
}

static int
do_checkpoint (nis_object *obj, char *host, int check_all)
{
  char hname[NIS_MAXNAMELEN];
  directory_obj *dir = &obj->DI_data;
  nis_server  *srv = NULL, *master = &dir->do_servers.do_servers_val[0];
  u_long i, failed = 0;

  /* XXX If check_all is set, get a list of all directorys (nis_stats)
     and send the check to them. */

  if (host != NULL)
    {
      if (host[strlen(host) - 1] == '.')
	snprintf (hname, sizeof (hname), "%s", host);
      else
	snprintf (hname, sizeof (hname), "%s.%s", host, dir->do_name);

      for (i = 0; i < dir->do_servers.do_servers_len; ++i)
	{
	  if (nis_dir_cmp (dir->do_servers.do_servers_val[i].name,
			   hname) == SAME_NAME)
	    {
	      srv = &dir->do_servers.do_servers_val[i];
	      break;
	    }
	}
      if (srv == NULL)
	{
	  fprintf (stderr, _("Host %s does not serve directory %s."),
		   hname, dir->do_name);
	  return -1;
	}
      fprintf (stdout, _("Checkpointing host serving directory %s :\n"),
	       dir->do_name);

      find_master_utime (dir, master);

      fprintf (stdout, "%s %s\n", i ? _("Replica server is") :
	       _("Master server is"), srv->name);
      failed = send_checkpoint(srv, dir->do_name);

      if (failed)
	return -1;
      else
	return 0;
    }
  else
    {
      fprintf (stdout, _("Checkpointing replicas serving directory %s :\n"),
	       dir->do_name);

      find_master_utime (dir, master);

      for (i = 0; i < dir->do_servers.do_servers_len; ++i)
	{
	  srv = &dir->do_servers.do_servers_val[i];
	  fprintf (stdout, "%s %s\n", i ? _("Replica server is") :
		   _("Master server is"), srv->name);
	  failed += send_checkpoint (srv, dir->do_name);
	}

      if (failed == dir->do_servers.do_servers_len)
	return -1;
      else if (failed)
	return 1;
      else
	return 0;
    }
}

int
main (int argc, char **argv)
{
  int no_ping = 0, root = 0, force = 0, checkpoint = 0, check_all = 0;
  int exitval = 0;
  char *hostname = NULL, *dir = NULL;
  nis_result *res;

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

  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'},
	{"force", no_argument, NULL, 'f'},
	{"update", no_argument, NULL, 'u'},
        {NULL, 0, NULL, '\0'}
      };

      c = getopt_long (argc, argv, "aCH:fru", long_options, &option_index);
      if (c == (-1))
        break;
      switch (c)
        {
	case 'a':
	  fputs ("nisping -a will be ignored in the moment!\n", stderr);
	  check_all = 1;
	  break;
	case 'C':
	  if (root)
	    {
	      fputs (_("You could not use -C and -r at the same time!\n"),
		     stderr);
	      print_error ();
	      return 1;
	    }
	  checkpoint = 1;
	  break;
	case 'H':
	  hostname = optarg;
	  break;
	case 'f':
	  force = 1;
	  break;
	case 'r':
	  fputs ("nisping -r will be ignored in the moment!\n", stderr);
	  if (checkpoint)
	    {
	      fputs (_("You could not use -C and -r at the same time!\n"),
		     stderr);
	      print_error ();
	      return 1;
	    }
	  root = 1;
	  break;
	case 'u':
	  no_ping = 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"), "nisping");
      print_error ();
      return 1;
    }
  else if (argc == 1)
    dir = argv[0];
  else
    dir = nis_local_directory ();

  res = nis_lookup (dir, MASTER_ONLY|EXPAND_NAME);

  if (res == NULL)
    {
      fputs (_("nis_lookup failed\n"), stderr);
      return -1;
    }

  if (res->status != NIS_SUCCESS)
    {
      fprintf (stderr, "%s: %s\n", dir, nis_sperrno (res->status));
      nis_freeresult (res);
      return -1;
    }

  if (__type_of (NIS_RES_OBJECT(res)) != DIRECTORY_OBJ)
    {
      fprintf (stderr, _("%s is not a directory.\n"), dir);
      nis_freeresult (res);
      return -1;
    }

  if (!checkpoint)
    exitval = do_ping (NIS_RES_OBJECT(res), hostname, force, no_ping);
  else
    exitval = do_checkpoint (NIS_RES_OBJECT(res), hostname, check_all);

  nis_freeresult (res);

  return exitval;
}
