/* 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

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#else
#include "lib/compat/getopt.h"
#endif
#include <locale.h>
#include <libintl.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#ifdef HAVE_PATHS_H
#include <paths.h>
#endif
#include <pthread.h>
#include <rpc/pmap_clnt.h>
#include <rpcsvc/nis.h>
#include <sys/fcntl.h>
#include <sys/stat.h>

#include "nis_cache.h"
#include "nis_cachemgr.h"
#include "log_msg.h"

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

#ifndef _PATH_NIS
#define _PATH_NIS "/var/nis"
#endif

/* For pidfile */
static int lock_fd;
#ifdef _PATH_VARRUN
#define _CACHE_PIDFILE _PATH_VARRUN"nis_cachemgr.pid"
#else
#define _CACHE_PIDFILE "/etc/nis_cachemgr.pid"
#endif

/* Print the version information.  */
static inline void
print_version (void)
{
  fprintf (stdout, "nis_cachemgr (%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: nis_cachemgr [-i] [-v]\n"), stdout);
}

static void
print_help (void)
{
  print_usage ();
  fputs (_("nis_cachemgr - NIS+ utility to maintain shared cache file\n\n"),
	 stdout);

  fputs (_("  -i, --ignore   Ignore the previous cache file\n"), stdout);
  fputs (_("  -v, --verbose  Verbose mode\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 = "nis_cachemgr";

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

/* Create a pidfile on startup */
static void
create_pidfile (void)
{
  struct flock lock;
  int left, written;
  pid_t pid;
  char pbuf[10], *ptr;
  int flags;

  lock_fd = open (_CACHE_PIDFILE, O_CREAT | O_RDWR,
		  S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  if (lock_fd < 0)
    log_msg (LOG_ERR, _("cannot create pidfile %s"), _CACHE_PIDFILE);

  /* Make sure file gets correctly closed when process finished.  */
  flags = fcntl (lock_fd, F_GETFD, 0);
  if (flags == -1)
    {
      /* Cannot get file flags.  */
      close (lock_fd);
      return;
    }
  flags |= FD_CLOEXEC;          /* Close on exit.  */
  if (fcntl (lock_fd, F_SETFD, flags) < 0)
    {
      /* Cannot set new flags.  */
      close (lock_fd);
      return;
    }

  lock.l_type = F_WRLCK;
  lock.l_start = 0;
  lock.l_whence = SEEK_SET;
  lock.l_len = 0;

  /* Is the pidfile locked by another ypserv ? */
  if (fcntl (lock_fd, F_GETLK, &lock) < 0)
    log_msg (LOG_ERR, _("fcntl error"));

  if (lock.l_type == F_UNLCK)
    pid = 0;               /* false, region is not locked by another proc */
  else
    pid = lock.l_pid;      /* true, return pid of lock owner */

  if (0 != pid)
    {
      log_msg (LOG_ERR, _("nis_cachemgr already running (pid %d) - exiting"),
               pid);
      exit (1);
    }

  /* write lock */
  lock.l_type = F_WRLCK;
  lock.l_start = 0;
  lock.l_whence = SEEK_SET;
  lock.l_len = 0;
  if (fcntl (lock_fd, F_SETLK, &lock) != 0)
    log_msg (LOG_ERR, _("cannot lock pidfile"));
  sprintf (pbuf, "%ld\n", (long) getpid ());
  left = strlen (pbuf);
  ptr = pbuf;
  while (left > 0)
    {
      if ((written = write (lock_fd, ptr, left)) <= 0)
        return;                 /* error */
      left -= written;
      ptr += written;
    }

  return;
}

/* Thread for handling signals */
static void *
sig_handler (void *v_param __attribute__ ((unused)))
{
  struct flock lock;
  sigset_t sigs_to_catch;
  int caught;

  sigemptyset (&sigs_to_catch);
  sigaddset (&sigs_to_catch, SIGCHLD);
  sigaddset (&sigs_to_catch, SIGTERM);
  sigaddset (&sigs_to_catch, SIGINT);
  sigaddset (&sigs_to_catch, SIGQUIT);
  sigaddset (&sigs_to_catch, SIGSEGV);
  sigaddset (&sigs_to_catch, SIGHUP);

  while (1)
    {
      sigwait (&sigs_to_catch, &caught);
      switch (caught)
        {
        case SIGCHLD:
          log_msg (LOG_ERR, _("SIGCHLD arrived, what should I do ?"));
          break;
        case SIGTERM:
        case SIGINT:
        case SIGQUIT:
        case SIGSEGV:
          /* Clean up if we quit the program. */
          if (debug_flag)
            log_msg (LOG_DEBUG, _("Signal (%d) for quitting program arrived."),
                     caught);
          pmap_unset (NIS_PROG, NIS_VERSION);
          /* unlock pidfile */
          lock.l_type = F_UNLCK;
          lock.l_start = 0;
          lock.l_whence = SEEK_SET;
          lock.l_len = 0;
          if (fcntl (lock_fd, F_SETLK, &lock) != 0)
            log_msg (LOG_ERR, _("cannot unlock pidfile"));
          close (lock_fd);
          unlink (_CACHE_PIDFILE);
          exit (0);
          break;
        case SIGHUP:
          /* Reload config file */
          if (debug_flag)
            log_msg (LOG_DEBUG, _("SIGHUP arrived, reloading config file."));
          break;
        default:
          log_msg (LOG_ERR, _("Unknown signal: %d"), caught);
          break;
        }
    }
}

static void
cacheprog_1(struct svc_req *rqstp, register SVCXPRT *transp)
{
  union {
    int fill;
  } argument;
  union {
  } result;
  bool_t retval;
  xdrproc_t _xdr_argument, _xdr_result;
  bool_t (*local)(char *, void *, struct svc_req *);

  switch (rqstp->rq_proc)
    {
    case NULLPROC:
      svc_sendreply (transp, (xdrproc_t) xdr_void, (char *)NULL);
      return;

    case NIS_CACHE_READ_COLDSTART:
      _xdr_argument = (xdrproc_t) xdr_void;
      _xdr_result = (xdrproc_t) xdr_void;
      local = (bool_t (*) (char *, void *,  struct svc_req *))nis_cache_read_coldstart_1_svc;
      break;

    default:
      svcerr_noproc (transp);
      return;
    }
  memset ((char *)&argument, 0, sizeof (argument));
  if (!svc_getargs (transp, _xdr_argument, (caddr_t) &argument))
    {
      svcerr_decode (transp);
      return;
    }
  retval = (bool_t) (*local)((char *)&argument, (void *)&result, rqstp);
  if (retval > 0 && !svc_sendreply(transp, _xdr_result, (char *)&result))
    svcerr_systemerr (transp);
  if (!svc_freeargs (transp, _xdr_argument, (caddr_t) &argument))
    {
      fprintf (stderr, "unable to free arguments");
      exit (1);
    }

  return;
}

int
main (int argc, char **argv)
{
  SVCXPRT *transp;
  sigset_t sigs_to_block;
  pthread_t sig_thread;
  int sock = RPC_ANYSOCK;

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

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

      c = getopt_long (argc, argv, "iv?", long_options,
		       &option_index);
      if (c == (-1))
        break;
      switch (c)
        {
	case 'i':
	  break;
	case 'v':
	  debug_flag = 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 != 0)
    {
      fprintf (stderr, _("%s: To many arguments\n"), "nis_cachemgr");
      print_error ();
      return 1;
    }

  if (getuid() != 0)
    {
      fputs (_("nis_cachemgr must be run as root\n"), stderr);
      exit (1);
    }

  if (debug_flag)
    log_msg (LOG_DEBUG, "[Welcome to nis_cachemgr, version %s]\n", VERSION);
  else
    {
      int i;

      if (fork ())
	exit (0);

      for (i = 0; i < getdtablesize (); i++)
	close (i);

      if (fork ())
	exit (0);

      openlog ("nis_cachemgr", LOG_PID, LOG_DAEMON);

      create_pidfile ();

      /* Ignore job control signals */
      signal (SIGTTOU, SIG_IGN);
      signal (SIGTTIN, SIG_IGN);
      signal (SIGTSTP, SIG_IGN);
    }

  unlink (CACHEPROGSOCK);

  sigemptyset (&sigs_to_block);
  sigaddset (&sigs_to_block, SIGCHLD);
  sigaddset (&sigs_to_block, SIGTERM);
  sigaddset (&sigs_to_block, SIGINT);
  sigaddset (&sigs_to_block, SIGQUIT);
  sigaddset (&sigs_to_block, SIGSEGV);
  sigaddset (&sigs_to_block, SIGHUP);
  if (pthread_sigmask (SIG_BLOCK, &sigs_to_block, NULL) != 0)
    {
      log_msg (LOG_ERR, _("Could not block signals."));
      exit (1);
    }

  pthread_create (&sig_thread, NULL, &sig_handler, NULL);

  /* Change current directory to _PATH_NIS */
  if (chdir (_PATH_NIS) < 0)
    {
      log_msg (LOG_ERR, _("Cannot change directory to %s"), _PATH_NIS);
      exit (1);
    }

  /* Set umask */
  umask (077);

  if (nis_coldcache_init () != NIS_SUCCESS)
    {
      log_msg (LOG_ERR, _("Cannot open %s"), COLD_START_FILE);
      exit (1);
    }

  transp = svcunix_create (sock, 0, 0, CACHEPROGSOCK);
  chmod (CACHEPROGSOCK, 0666);
  if (transp == NULL)
    log_msg (LOG_INFO, _("cannot create AF_UNIX service."));
  else if (!svc_register (transp, CACHEPROG, CACHE_VER_1, cacheprog_1, 0))
    log_msg (LOG_INFO, _("unable to register (CACHEPROG, CACHE_VER_1, unix)"));

  svc_run ();
  log_msg (LOG_ERR, _("svc_run returned"));
  unlink (CACHEPROGSOCK);
  unlink (_CACHE_PIDFILE);
  return 1;
  /* NOTREACHED */
}
