/*
 * Copyright (c) 1997-2011 Erez Zadok
 * Copyright (c) 1989 Jan-Simon Pendry
 * Copyright (c) 1989 Imperial College of Science, Technology & Medicine
 * Copyright (c) 1989 The Regents of the University of California.
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Jan-Simon Pendry at Imperial College, London.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgment:
 *      This product includes software developed by the University of
 *      California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *
 * File: am-utils/hlfsd/hlfsd.c
 *
 * HLFSD was written at Columbia University Computer Science Department, by
 * Erez Zadok <ezk@cs.columbia.edu> and Alexander Dupuy <dupuy@cs.columbia.edu>
 * It is being distributed under the same terms and conditions as amd does.
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */
#include <am_defs.h>
#include <hlfsd.h>

/*
 * STATIC VARIABLES:
 */
static RETSIGTYPE proceed(int);
static RETSIGTYPE reaper(int);
static RETSIGTYPE reload(int);
static char *hlfs_group = DEFAULT_HLFS_GROUP;
static char default_dir_name[] = DEFAULT_DIRNAME;
static char *dir_name = default_dir_name;
static int printpid = 0;
static int stoplight = 0;
static void hlfsd_init(void);
static void usage(void);

static struct itimerval reloadinterval = {
  {DEFAULT_INTERVAL, 0},
  {DEFAULT_INTERVAL, 0}
};

/*
 * default mount options.
 */
static char default_mntopts[] = "ro,noac";

/*
 * GLOBALS:
 */
SVCXPRT *nfsxprt;
char *alt_spooldir = ALT_SPOOLDIR;
char *home_subdir = HOME_SUBDIR;
char *logfile = DEFAULT_LOGFILE;
char *passwdfile = NULL;	/* alternate passwd file to use */
char *slinkname = NULL;
char hostname[MAXHOSTNAMELEN + 1] = "localhost";
u_int cache_interval = DEFAULT_CACHE_INTERVAL;
gid_t hlfs_gid = (gid_t) INVALIDID;
int masterpid = 0;
int noverify = 0;
int orig_umask = 022;
int serverpid = 0;
nfstime startup;
u_short nfs_port;

/* symbol must be available always */
#ifdef MNTTAB_FILE_NAME
char *mnttab_file_name = MNTTAB_FILE_NAME;
#else /* not MNTTAB_FILE_NAME */
char *mnttab_file_name = NULL;
#endif /* not MNTTAB_FILE_NAME */

/* forward declarations */
void hlfsd_going_down(int rc);
void fatalerror(char *str);


static void
usage(void)
{
  fprintf(stderr,
	  "Usage: %s [-Cfhnpv] [-a altdir] [-c cache-interval] [-g group]\n",
	  am_get_progname());
  fprintf(stderr, "\t[-i interval] [-l logfile] [-o mntopts] [-P passwdfile]\n");
  show_opts('x', xlog_opt);
#ifdef DEBUG
  show_opts('D', dbg_opt);
#endif /* DEBUG */
  fprintf(stderr, "\t[dir_name [subdir]]\n");
  exit(2);
}


void
fatalerror(char *str)
{
#define ERRM ": %m"
  size_t l = strlen(str) + sizeof(ERRM) - 1;
  char *tmp = strnsave(str, l);
  xstrlcat(tmp, ERRM, l);
  fatal(tmp);
}


int
main(int argc, char *argv[])
{
  char *dot;
  char *mntopts = (char *) NULL;
  char hostpid_fs[MAXHOSTNAMELEN + 1 + 16];	/* room for ":(pid###)" */
  char progpid_fs[PROGNAMESZ + 1 + 11];		/* room for ":pid" */
  char preopts[128];
  char *progname;
  int forcecache = 0;
  int forcefast = 0;
  int genflags = 0;
  int opt, ret;
  int opterrs = 0;
  int retry;
  int soNFS;			/* NFS socket */
  int s = -99;
  mntent_t mnt;
  nfs_args_t nfs_args;
  am_nfs_handle_t anh;
  struct dirent *direntry;
  struct group *grp;
  struct stat stmodes;
  DIR *mountdir;
  MTYPE_TYPE type = MOUNT_TYPE_NFS;

#ifdef HAVE_SIGACTION
  struct sigaction sa;
#endif /* not HAVE_SIGACTION */

#ifndef HAVE_TRANSPORT_TYPE_TLI
  struct sockaddr_in localsocket;
#endif /* not HAVE_TRANSPORT_TYPE_TLI */


  /* get program name and truncate so we don't overflow progpid_fs */

  if ((progname = strrchr(argv[0], '/')) != NULL)
    progname++;
  else
    progname = argv[0];
  if ((int) strlen(progname) > PROGNAMESZ) /* truncate to reasonable size */
    progname[PROGNAMESZ] = '\0';
  am_set_progname(progname);

  while ((opt = getopt(argc, argv, "a:c:CD:fg:hi:l:no:pP:x:v")) != -1)
    switch (opt) {

    case 'a':
      if (!optarg || optarg[0] != '/') {
	printf("%s: invalid directory for -a: %s\n",
	       am_get_progname(), optarg);
	exit(3);
      }
      alt_spooldir = optarg;
      break;

    case 'c':
      if (!atoi(optarg)) {
	printf("%s: invalid interval for -c: %s\n",
	       am_get_progname(), optarg);
	exit(3);
      }
      cache_interval = atoi(optarg);
      break;

    case 'C':
      forcecache++;
      break;

    case 'f':
      forcefast++;
      break;

    case 'g':
      hlfs_group = optarg;
      break;

    case 'i':
      if (!atoi(optarg)) {
	printf("%s: invalid interval for -i: %s\n",
	       am_get_progname(), optarg);
	exit(3);
      }
      reloadinterval.it_interval.tv_sec = atoi(optarg);
      reloadinterval.it_value.tv_sec = atoi(optarg);
      break;

    case 'l':
      logfile = optarg;
      break;

    case 'n':
      noverify++;
      break;

    case 'o':
      mntopts = optarg;
      break;

    case 'p':
      printpid++;
      break;

    case 'P':
      passwdfile = optarg;
      break;

    case 'v':
      fprintf(stderr, "%s\n", HLFSD_VERSION);
      exit(0);

    case 'x':
      opterrs += switch_option(optarg);
      break;

    case 'D':
#ifdef DEBUG
      opterrs += debug_option(optarg);
#else /* not DEBUG */
      fprintf(stderr, "%s: not compiled with DEBUG -- sorry.\n", am_get_progname());
#endif /* not DEBUG */
      break;

    case 'h':
    case '?':
      opterrs++;
    }

  /* need my pid before any dlog/plog */
  am_set_mypid();
#ifdef DEBUG
  switch_option("debug");
#endif /* DEBUG */

/*
 * Terminate if did not ask to forcecache (-C) and hlfsd would not be able
 * to set the minimum cache intervals.
 */
#if !defined(MNT2_NFS_OPT_ACREGMIN) && !defined(MNT2_NFS_OPT_NOAC) && !defined(HAVE_NFS_ARGS_T_ACREGMIN)
  if (!forcecache) {
    fprintf(stderr, "%s: will not be able to turn off attribute caches.\n", am_get_progname());
    exit(1);
  }
#endif /* !defined(MNT2_NFS_OPT_ACREGMIN) && !defined(MNT2_NFS_OPT_NOAC) && !defined(HAVE_NFS_ARGS_T_ACREGMIN) */


  switch (argc - optind) {
  case 2:
    home_subdir = argv[optind + 1];
  case 1:
    dir_name = argv[optind];
  case 0:
    break;
  default:
    opterrs++;
  }

  if (opterrs)
    usage();

  /* ensure that only root can run hlfsd */
  if (geteuid()) {
    fprintf(stderr, "hlfsd can only be run as root\n");
    exit(1);
  }
  setbuf(stdout, (char *) NULL);
  umask(0);

  /* find gid for hlfs_group */
  if ((grp = getgrnam(hlfs_group)) == (struct group *) NULL) {
    fprintf(stderr, "%s: cannot get gid for group \"%s\".\n",
	    am_get_progname(), hlfs_group);
  } else {
    hlfs_gid = grp->gr_gid;
  }

  /* get hostname for logging and open log before we reset umask */
  gethostname(hostname, sizeof(hostname));
  hostname[sizeof(hostname) - 1] = '\0';
  if ((dot = strchr(hostname, '.')) != NULL)
    *dot = '\0';
  orig_umask = umask(0);
  if (logfile)
    switch_to_logfile(logfile, orig_umask, 0);

#ifndef MOUNT_TABLE_ON_FILE
  if (amuDebug(D_MTAB))
    dlog("-D mtab option ignored");
#endif /* not MOUNT_TABLE_ON_FILE */

  /* avoid hanging on other NFS servers if started elsewhere */
  if (chdir("/") < 0)
    fatal("cannot chdir to /: %m");

  if (geteuid() != 0)
    fatal("must be root to mount filesystems");

  /*
   * dir_name must match "^(/.*)/([^/]+)$", and is split at last '/' with
   * slinkname = `basename $dir_name` - requires dir_name be writable
   */

  if (dir_name[0] != '/'
      || ((slinkname = strrchr(dir_name, '/')), *slinkname++ = '\0',
	  (dir_name[0] == '\0' || slinkname[0] == '\0'))) {
    if (slinkname)
      *--slinkname = '/';
    printf("%s: invalid mount directory/link %s\n",
	   am_get_progname(), dir_name);
    exit(3);
  }

  if (!forcefast) {
    /* make sure mount point exists and is at least mode 555 */
    if (stat(dir_name, &stmodes) < 0)
      if (errno != ENOENT || mkdirs(dir_name, 0555) < 0
	  || stat(dir_name, &stmodes) < 0)
	fatalerror(dir_name);

    if ((stmodes.st_mode & 0555) != 0555) {
      fprintf(stderr, "%s: directory %s not read/executable\n",
	      am_get_progname(), dir_name);
      plog(XLOG_WARNING, "directory %s not read/executable",
	   dir_name);
    }

    /* warn if extraneous stuff will be hidden by mount */
    if ((mountdir = opendir(dir_name)) == NULL)
      fatalerror(dir_name);

    while ((direntry = readdir(mountdir)) != NULL) {
      if (!NSTREQ(".", direntry->d_name, NAMLEN(direntry)) &&
	  !NSTREQ("..", direntry->d_name, NAMLEN(direntry)) &&
	  !NSTREQ(slinkname, direntry->d_name, NAMLEN(direntry)))
	break;
    }

    if (direntry != NULL) {
      fprintf(stderr, "%s: %s/%s will be hidden by mount\n",
	      am_get_progname(), dir_name, direntry->d_name);
      plog(XLOG_WARNING, "%s/%s will be hidden by mount\n",
	   dir_name, direntry->d_name);
    }
    closedir(mountdir);

    /* make sure alternate spool dir exists */
    if ((errno = mkdirs(alt_spooldir, OPEN_SPOOLMODE))) {
      fprintf(stderr, "%s: cannot create alternate dir ",
	      am_get_progname());
      perror(alt_spooldir);
      plog(XLOG_ERROR, "cannot create alternate dir %s: %m",
	   alt_spooldir);
    }
    chmod(alt_spooldir, OPEN_SPOOLMODE);

    /* create failsafe link to alternate spool directory */
    *(slinkname-1) = '/';	/* unsplit dir_name to include link */
    if (lstat(dir_name, &stmodes) == 0 &&
	(stmodes.st_mode & S_IFMT) != S_IFLNK) {
      fprintf(stderr, "%s: failsafe %s not a symlink\n",
	      am_get_progname(), dir_name);
      plog(XLOG_WARNING, "failsafe %s not a symlink\n",
	   dir_name);
    } else {
      unlink(dir_name);

      if (symlink(alt_spooldir, dir_name) < 0) {
	fprintf(stderr,
		"%s: cannot create failsafe symlink %s -> ",
		am_get_progname(), dir_name);
	perror(alt_spooldir);
	plog(XLOG_WARNING,
	     "cannot create failsafe symlink %s -> %s: %m",
	     dir_name, alt_spooldir);
      }
    }

    *(slinkname-1) = '\0';	/* resplit dir_name */
  } /* end of "if (!forcefast) {" */

  /*
   * Register hlfsd as an nfs service with the portmapper.
   */
#ifdef HAVE_TRANSPORT_TYPE_TLI
  ret = create_nfs_service(&soNFS, &nfs_port, &nfsxprt, nfs_program_2);
#else /* not HAVE_TRANSPORT_TYPE_TLI */
  ret = create_nfs_service(&soNFS, &nfs_port, &nfsxprt, nfs_program_2);
#endif /* not HAVE_TRANSPORT_TYPE_TLI */
  if (ret != 0)
    fatal("cannot create NFS service");

#ifdef HAVE_SIGACTION
  sa.sa_handler = proceed;
  sa.sa_flags = 0;
  sigemptyset(&(sa.sa_mask));
  sigaddset(&(sa.sa_mask), SIGUSR2);
  sigaction(SIGUSR2, &sa, NULL);
#else /* not HAVE_SIGACTION */
  signal(SIGUSR2, proceed);
#endif /* not HAVE_SIGACTION */

  plog(XLOG_INFO, "Initializing hlfsd...");
  hlfsd_init();			/* start up child (forking) to run svc_run */

#ifdef HAVE_SIGACTION
  sa.sa_handler = reaper;
  sa.sa_flags = 0;
  sigemptyset(&(sa.sa_mask));
  sigaddset(&(sa.sa_mask), SIGCHLD);
  sigaction(SIGCHLD, &sa, NULL);
#else /* not HAVE_SIGACTION */
  signal(SIGCHLD, reaper);
#endif /* not HAVE_SIGACTION */

  /*
   * In the parent, if -D nodaemon, we don't need to
   * set this signal handler.
   */
  if (amuDebug(D_DAEMON)) {
    s = -99;
    while (stoplight != SIGUSR2) {
      plog(XLOG_INFO, "parent waits for child to setup (stoplight=%d)", stoplight);
#ifdef HAVE_SIGSUSPEND
      {
	sigset_t mask;
	sigemptyset(&mask);
	s = sigsuspend(&mask);	/* wait for child to set up */
      }
#else /* not HAVE_SIGSUSPEND */
      s = sigpause(0);		/* wait for child to set up */
#endif /* not HAVE_SIGSUSPEND */
      sleep(1);
    }
  }

  /*
   * setup options to mount table (/etc/{mtab,mnttab}) entry
   */
  xsnprintf(hostpid_fs, sizeof(hostpid_fs),
	    "%s:(pid%d)", hostname, masterpid);
  memset((char *) &mnt, 0, sizeof(mnt));
  mnt.mnt_dir = dir_name;	/* i.e., "/mail" */
  mnt.mnt_fsname = hostpid_fs;
  if (mntopts) {
    mnt.mnt_opts = mntopts;
  } else {
    xstrlcpy(preopts, default_mntopts, sizeof(preopts));
    /*
     * Turn off all kinds of attribute and symlink caches as
     * much as possible.  Also make sure that mount does not
     * show up to df.
     */
#ifdef MNTTAB_OPT_INTR
    xstrlcat(preopts, ",", sizeof(preopts));
    xstrlcat(preopts, MNTTAB_OPT_INTR, sizeof(preopts));
#endif /* MNTTAB_OPT_INTR */
#ifdef MNTTAB_OPT_IGNORE
    xstrlcat(preopts, ",", sizeof(preopts));
    xstrlcat(preopts, MNTTAB_OPT_IGNORE, sizeof(preopts));
#endif /* MNTTAB_OPT_IGNORE */
#ifdef MNT2_GEN_OPT_CACHE
    xstrlcat(preopts, ",nocache", sizeof(preopts));
#endif /* MNT2_GEN_OPT_CACHE */
#ifdef MNT2_NFS_OPT_SYMTTL
    xstrlcat(preopts, ",symttl=0", sizeof(preopts));
#endif /* MNT2_NFS_OPT_SYMTTL */
    mnt.mnt_opts = preopts;
  }

  /*
   * Make sure that amd's top-level NFS mounts are hidden by default
   * from df.
   * If they don't appear to support the either the "ignore" mnttab
   * option entry, or the "auto" one, set the mount type to "nfs".
   */
#ifdef HIDE_MOUNT_TYPE
  mnt.mnt_type = HIDE_MOUNT_TYPE;
#else /* not HIDE_MOUNT_TYPE */
  mnt.mnt_type = "nfs";
#endif /* not HIDE_MOUNT_TYPE */
  /* some systems don't have a mount type, but a mount flag */

#ifndef HAVE_TRANSPORT_TYPE_TLI
  amu_get_myaddress(&localsocket.sin_addr, NULL);
  localsocket.sin_family = AF_INET;
  localsocket.sin_port = htons(nfsxprt->xp_port);
#endif /* not HAVE_TRANSPORT_TYPE_TLI */

  /*
   * Update hostname field.
   * Make some name prog:pid (i.e., hlfsd:174) for hostname
   */
  xsnprintf(progpid_fs, sizeof(progpid_fs),
	    "%s:%d", am_get_progname(), masterpid);

  /* Most kernels have a name length restriction. */
  if ((int) strlen(progpid_fs) >= (int) MAXHOSTNAMELEN)
    xstrlcpy(progpid_fs + MAXHOSTNAMELEN - 3, "..",
	     sizeof(progpid_fs) - MAXHOSTNAMELEN + 3);

  genflags = compute_mount_flags(&mnt);

  retry = hasmntval(&mnt, MNTTAB_OPT_RETRY);
  if (retry <= 0)
    retry = 1;			/* XXX */

  memmove(&anh.v2, root_fhp, sizeof(*root_fhp));
#ifdef HAVE_TRANSPORT_TYPE_TLI
  compute_nfs_args(&nfs_args,
		   &mnt,
		   genflags,
		   nfsncp,
		   NULL,	/* remote host IP addr is set below */
		   NFS_VERSION,	/* version 2 */
		   "udp",	/* XXX: shouldn't this be "udp"? */
		   &anh,
		   progpid_fs,	/* host name for kernel */
		   hostpid_fs); /* filesystem name for kernel */
  /*
   * IMPORTANT: set the correct IP address AFTERWARDS.  It cannot
   * be done using the normal mechanism of compute_nfs_args(), because
   * that one will allocate a new address and use NFS_SA_DREF() to copy
   * parts to it, while assuming that the ip_addr passed is always
   * a "struct sockaddr_in".  That assumption is incorrect on TLI systems,
   * because they define a special macro HOST_SELF which is DIFFERENT
   * than localhost (127.0.0.1)!
   */
  nfs_args.addr = &nfsxprt->xp_ltaddr;
#else /* not HAVE_TRANSPORT_TYPE_TLI */
  compute_nfs_args(&nfs_args,
		   &mnt,
		   genflags,
		   NULL,
		   &localsocket,
		   NFS_VERSION, /* version 2 */
		   "udp",	/* XXX: shouldn't this be "udp"? */
		   &anh,
		   progpid_fs,	/* host name for kernel */
		   hostpid_fs); /* filesystem name for kernel */
#endif /* not HAVE_TRANSPORT_TYPE_TLI */

  /*************************************************************************
   * NOTE: while compute_nfs_args() works ok for regular NFS mounts	   *
   * the toplvl one is not, and so some options must be corrected by hand  *
   * more carefully, *after* compute_nfs_args() runs.			   *
   *************************************************************************/
  compute_automounter_nfs_args(&nfs_args, &mnt);

/*
 * For some reason, this mount may have to be done in the background, if I am
 * using -D daemon.  I suspect that the actual act of mounting requires
 * calling to hlfsd itself to invoke one or more of its nfs calls, to stat
 * /mail.  That means that even if you say -D daemon, at least the mount
 * of hlfsd itself on top of /mail will be done in the background.
 * The other alternative I have is to run svc_run, but set a special
 * signal handler to perform the mount in N seconds via some alarm.
 *      -Erez Zadok.
 */
  if (!amuDebug(D_DAEMON)) {	/* Normal case */
    plog(XLOG_INFO, "normal NFS mounting hlfsd service points");
    if (mount_fs(&mnt, genflags, (caddr_t) &nfs_args, retry, type, 0, NULL, mnttab_file_name, 0) < 0)
      fatal("nfsmount: %m");
  } else {			/* asked for -D daemon */
    if (fork() == 0) {		/* child runs mount */
      am_set_mypid();
      foreground = 0;
      plog(XLOG_INFO, "child NFS mounting hlfsd service points");
      if (mount_fs(&mnt, genflags, (caddr_t) &nfs_args, retry, type, 0, NULL, mnttab_file_name, 0) < 0) {
	fatal("nfsmount: %m");
      }
      exit(0);			/* all went well */
    } else { /* fork failed or parent running */
      plog(XLOG_INFO, "parent waiting 1sec for mount...");
    }
  }

#ifdef HAVE_TRANSPORT_TYPE_TLI
  /*
   * XXX: this free_knetconfig() was not done for hlfsd before,
   * and apparently there was a reason for it, but why? -Erez
   */
  free_knetconfig(nfs_args.knconf);
  /*
   * local automounter mounts do not allocate a special address, so
   * no need to XFREE(nfs_args.addr) under TLI.
   */
#endif /* HAVE_TRANSPORT_TYPE_TLI */

  if (printpid)
    printf("%d\n", masterpid);

  plog(XLOG_INFO, "hlfsd ready to serve");
  /*
   * If asked not to fork a daemon (-D nodaemon), then hlfsd_init()
   * will not run svc_run.  We must start svc_run here.
   */
  if (!amuDebug(D_DAEMON)) {
    plog(XLOG_DEBUG, "starting no-daemon debugging svc_run");
    svc_run();
  }

  cleanup(0);			/* should never happen here */
  return (0);			/* everything went fine? */
}


static void
hlfsd_init(void)
{
  int child = 0;
#ifdef HAVE_SIGACTION
  struct sigaction sa;
#endif /* HAVE_SIGACTION */

  /*
   * Initialize file handles.
   */
  plog(XLOG_INFO, "initializing hlfsd file handles");
  hlfsd_init_filehandles();

  /*
   * If -D daemon then we must fork.
   */
  if (amuDebug(D_DAEMON))
    child = fork();

  if (child < 0)
    fatal("fork: %m");

  if (child != 0) {		/* parent process - save child pid */
    masterpid = child;
    am_set_mypid();		/* for logging routines */
    return;
  }

  /*
   * CHILD CODE:
   * initialize server
   */

  plog(XLOG_INFO, "initializing home directory database");
  plt_init();			/* initialize database */
  plog(XLOG_INFO, "home directory database initialized");

  masterpid = serverpid = am_set_mypid(); /* for logging routines */

  /*
   * SIGALRM/SIGHUP: reload password database if timer expired
   * or user sent HUP signal.
   */
#ifdef HAVE_SIGACTION
  sa.sa_handler = reload;
  sa.sa_flags = 0;
  sigemptyset(&(sa.sa_mask));
  sigaddset(&(sa.sa_mask), SIGALRM);
  sigaddset(&(sa.sa_mask), SIGHUP);
  sigaction(SIGALRM, &sa, NULL);
  sigaction(SIGHUP, &sa, NULL);
#else /* not HAVE_SIGACTION */
  signal(SIGALRM, reload);
  signal(SIGHUP, reload);
#endif /* not HAVE_SIGACTION */

  /*
   * SIGTERM: cleanup and exit.
   */
#ifdef HAVE_SIGACTION
  sa.sa_handler = cleanup;
  sa.sa_flags = 0;
  sigemptyset(&(sa.sa_mask));
  sigaddset(&(sa.sa_mask), SIGTERM);
  sigaction(SIGTERM, &sa, NULL);
#else /* not HAVE_SIGACTION */
  signal(SIGTERM, cleanup);
#endif /* not HAVE_SIGACTION */

  /*
   * SIGCHLD: interlock synchronization and testing
   */
#ifdef HAVE_SIGACTION
  sa.sa_handler = interlock;
  sa.sa_flags = 0;
  sigemptyset(&(sa.sa_mask));
  sigaddset(&(sa.sa_mask), SIGCHLD);
  sigaction(SIGCHLD, &sa, NULL);
#else /* not HAVE_SIGACTION */
  signal(SIGCHLD, interlock);
#endif /* not HAVE_SIGACTION */

  /*
   * SIGUSR1: dump internal hlfsd maps/cache to file
   */
#ifdef HAVE_SIGACTION
# if defined(DEBUG) || defined(DEBUG_PRINT)
  sa.sa_handler = plt_print;
# else /* not defined(DEBUG) || defined(DEBUG_PRINT) */
  sa.sa_handler = SIG_IGN;
# endif /* not defined(DEBUG) || defined(DEBUG_PRINT) */
  sa.sa_flags = 0;
  sigemptyset(&(sa.sa_mask));
  sigaddset(&(sa.sa_mask), SIGUSR1);
  sigaction(SIGUSR1, &sa, NULL);
#else /* not HAVE_SIGACTION */
# if defined(DEBUG) || defined(DEBUG_PRINT)
  signal(SIGUSR1, plt_print);
# else /* not defined(DEBUG) || defined(DEBUG_PRINT) */
  signal(SIGUSR1, SIG_IGN);
# endif /* not defined(DEBUG) || defined(DEBUG_PRINT) */
#endif /* not HAVE_SIGACTION */

  if (setitimer(ITIMER_REAL, &reloadinterval, (struct itimerval *) NULL) < 0)
    fatal("setitimer: %m");

  clocktime(&startup);

  /*
   * If -D daemon, then start serving here in the child,
   * and the parent will exit.  But if -D nodaemon, then
   * skip this code and make sure svc_run is entered elsewhere.
   */
  if (amuDebug(D_DAEMON)) {
    /*
     * Dissociate from the controlling terminal
     */
    amu_release_controlling_tty();

    /*
     * signal parent we are ready. parent should
     * mount(2) and die.
     */
    if (kill(getppid(), SIGUSR2) < 0)
      fatal("kill: %m");
    plog(XLOG_INFO, "starting svc_run");
    svc_run();
    cleanup(0);		/* should never happen, just in case */
  }

}


static RETSIGTYPE
proceed(int signum)
{
  stoplight = signum;
}


static RETSIGTYPE
reload(int signum)
{
  int child;
  int status;

  if (getpid() != masterpid)
    return;

  /*
   * If received a SIGHUP, close and reopen the log file (so that it
   * can be rotated)
   */
  if (signum == SIGHUP && logfile)
    switch_to_logfile(logfile, orig_umask, 0);

  /*
   * parent performs the reload, while the child continues to serve
   * clients accessing the home dir link.
   */
  if ((child = fork()) > 0) {
    serverpid = child;		/* parent runs here */
    am_set_mypid();

    plt_init();

    if (kill(child, SIGKILL) < 0) {
      plog(XLOG_ERROR, "kill child: %m");
    } else {			/* wait for child to die before continue */
      if (wait(&status) != child) {
	/*
	 * I took out this line because it generates annoying output.  It
	 * indicates a very small bug in hlfsd which is totally harmless.
	 * It causes hlfsd to work a bit harder than it should.
	 * Nevertheless, I intend on fixing it in a future release.
	 * -Erez Zadok <ezk@cs.columbia.edu>
	 */
	/* plog(XLOG_ERROR, "unknown child"); */
      }
    }
    serverpid = masterpid;
  } else if (child < 0) {
    plog(XLOG_ERROR, "unable to fork: %m");
  } else {
    /* let child handle requests while we reload */
    serverpid = getpid();
    am_set_mypid();
  }
}


RETSIGTYPE
cleanup(int signum)
{
  struct stat stbuf;
  int umount_result;

  if (amuDebug(D_DAEMON)) {
    if (getpid() != masterpid)
      return;

    if (fork() != 0) {
      masterpid = 0;
      am_set_mypid();
      return;
    }
  }
  am_set_mypid();

  for (;;) {
    while ((umount_result = UMOUNT_FS(dir_name, mnttab_file_name, 0)) == EBUSY) {
      dlog("cleanup(): umount delaying for 10 seconds");
      sleep(10);
    }
    if (stat(dir_name, &stbuf) == 0 && stbuf.st_ino == ROOTID) {
      plog(XLOG_ERROR, "unable to unmount %s", dir_name);
      plog(XLOG_ERROR, "suspending, unmount before terminating");
      kill(am_mypid, SIGSTOP);
      continue;			/* retry unmount */
    }
    break;
  }

  if (amuDebug(D_DAEMON)) {
    plog(XLOG_INFO, "cleanup(): killing processes and terminating");
    kill(masterpid, SIGKILL);
    kill(serverpid, SIGKILL);
  }

  plog(XLOG_INFO, "hlfsd terminating with status 0\n");
  _exit(0);
}


static RETSIGTYPE
reaper(int signum)
{
  int result;

  if (wait(&result) == masterpid) {
    _exit(4);
  }
}


void
hlfsd_going_down(int rc)
{
  int mypid = getpid();		/* XXX: should this be the global am_mypid */

  if (mypid == masterpid)
    cleanup(0);
  else if (mypid == serverpid)
    kill(masterpid, SIGTERM);

  exit(rc);
}


void
fatal(char *mess)
{
  if (logfile && !STREQ(logfile, "stderr")) {
    char lessmess[128];
    int messlen;

    messlen = strlen(mess);

    if (!STREQ(&mess[messlen + 1 - sizeof(ERRM)], ERRM))
      fprintf(stderr, "%s: %s\n", am_get_progname(), mess);
    else {
      xstrlcpy(lessmess, mess, sizeof(lessmess));
      lessmess[messlen - 4] = '\0';

      fprintf(stderr, "%s: %s: %s\n",
	      am_get_progname(), lessmess, strerror(errno));
    }
  }
  plog(XLOG_FATAL, "%s", mess);

  hlfsd_going_down(1);
}
