/*
 * Copyright (c) 1997-2011 Erez Zadok
 * Copyright (c) 1990 Jan-Simon Pendry
 * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
 * Copyright (c) 1990 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/libamu/xutil.c
 *
 */

/*
 * Miscellaneous Utilities: Logging, TTY, timers, signals, RPC, memory, etc.
 */

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

/*
 * Logfp is the default logging device, and is initialized to stderr by
 * default in dplog/plog below, and in
 * amd/amfs_program.c:amfs_program_exec().
 */
FILE *logfp = NULL;

static char *am_progname = "unknown";	/* "amd" */
static char am_hostname[MAXHOSTNAMELEN] = "unknown"; /* Hostname */
pid_t am_mypid = -1;		/* process ID */
serv_state amd_state;		/* amd's state */
int foreground = 1;		/* 1 == this is the top-level server */
u_int debug_flags = D_CONTROL;	/* set regardless if compiled with debugging */

#ifdef HAVE_SYSLOG
int syslogging;
#endif /* HAVE_SYSLOG */
static u_int xlog_level = XLOG_DEFAULT;
static u_long amd_program_number = AMQ_PROGRAM;

#ifdef DEBUG_MEM
# if defined(HAVE_MALLINFO) && defined(HAVE_MALLOC_VERIFY)
static int mem_bytes;
static int orig_mem_bytes;
# endif /* not defined(HAVE_MALLINFO) && defined(HAVE_MALLOC_VERIFY) */
#endif /* DEBUG_MEM */

/* forward definitions */
/* for GCC format string auditing */
static void real_plog(int lvl, const char *fmt, va_list vargs)
     __attribute__((__format__(__printf__, 2, 0)));


#ifdef DEBUG
/*
 * List of debug options.
 */
struct opt_tab dbg_opt[] =
{
  {"all", D_ALL},		/* All non-disruptive options */
  {"defaults", D_DEFAULT},	/* Default options */
  {"test", D_TEST},		/* Full debug - no daemon, no fork, no amq, local mtab */
  {"amq", D_AMQ},		/* Register for AMQ program */
  {"daemon", D_DAEMON},		/* Enter daemon mode */
  {"fork", D_FORK},		/* Fork server (hlfsd only) */
  {"full", D_FULL},		/* Program trace */
#ifdef HAVE_CLOCK_GETTIME
  {"hrtime", D_HRTIME},		/* Print high resolution time stamps */
#endif /* HAVE_CLOCK_GETTIME */
  {"info", D_INFO},		/* info service specific debugging (hesiod, nis, etc) */
  {"mem", D_MEM},		/* Trace memory allocations */
  {"mtab", D_MTAB},		/* Use local mtab file */
  {"readdir", D_READDIR},	/* Check on browsable_dirs progress */
  {"str", D_STR},		/* Debug string munging */
  {"trace", D_TRACE},		/* Protocol trace */
  {"xdrtrace", D_XDRTRACE},	/* Trace xdr routines */
  {NULL, 0}
};
#endif /* DEBUG */

/*
 * List of log options
 */
struct opt_tab xlog_opt[] =
{
  {"all", XLOG_ALL},		/* All messages */
  {"defaults", XLOG_DEFAULT},	/* Default messages */
#ifdef DEBUG
  {"debug", XLOG_DEBUG},	/* Debug messages */
#endif /* DEBUG */		/* DEBUG */
  {"error", XLOG_ERROR},	/* Non-fatal system errors */
  {"fatal", XLOG_FATAL},	/* Fatal errors */
  {"info", XLOG_INFO},		/* Information */
  {"map", XLOG_MAP},		/* Map errors */
  {"stats", XLOG_STATS},	/* Additional statistical information */
  {"user", XLOG_USER},		/* Non-fatal user errors */
  {"warn", XLOG_WARNING},	/* Warnings */
  {"warning", XLOG_WARNING},	/* Warnings */
  {NULL, 0}
};


void
am_set_progname(char *pn)
{
  am_progname = pn;
}


const char *
am_get_progname(void)
{
  return am_progname;
}


void
am_set_hostname(char *hn)
{
  xstrlcpy(am_hostname, hn, sizeof(am_hostname));
}


const char *
am_get_hostname(void)
{
  return am_hostname;
}


pid_t
am_set_mypid(void)
{
  am_mypid = getpid();
  return am_mypid;
}


long
get_server_pid()
{
  return (long) (foreground ? am_mypid : getppid());
}


voidp
xmalloc(int len)
{
  voidp p;
  int retries = 600;

  /*
   * Avoid malloc's which return NULL for malloc(0)
   */
  if (len == 0)
    len = 1;

  do {
    p = (voidp) malloc((unsigned) len);
    if (p) {
      if (amuDebug(D_MEM))
	plog(XLOG_DEBUG, "Allocated size %d; block %p", len, p);
      return p;
    }
    if (retries > 0) {
      plog(XLOG_ERROR, "Retrying memory allocation");
      sleep(1);
    }
  } while (--retries);

  plog(XLOG_FATAL, "Out of memory");
  going_down(1);

  abort();

  return 0;
}


/* like xmalloc, but zeros out the bytes */
voidp
xzalloc(int len)
{
  voidp p = xmalloc(len);

  if (p)
    memset(p, 0, len);
  return p;
}


voidp
xrealloc(voidp ptr, int len)
{
  if (amuDebug(D_MEM))
    plog(XLOG_DEBUG, "Reallocated size %d; block %p", len, ptr);

  if (len == 0)
    len = 1;

  if (ptr)
    ptr = (voidp) realloc(ptr, (unsigned) len);
  else
    ptr = (voidp) xmalloc((unsigned) len);

  if (!ptr) {
    plog(XLOG_FATAL, "Out of memory in realloc");
    going_down(1);
    abort();
  }
  return ptr;
}


#ifdef DEBUG_MEM
void
dxfree(char *file, int line, voidp ptr)
{
  if (amuDebug(D_MEM))
    plog(XLOG_DEBUG, "Free in %s:%d: block %p", file, line, ptr);
  /* this is the only place that must NOT use XFREE()!!! */
  free(ptr);
  ptr = NULL;			/* paranoid */
}


# if defined(HAVE_MALLINFO) && defined(HAVE_MALLOC_VERIFY)
static void
checkup_mem(void)
{
  struct mallinfo mi = mallinfo();
  u_long uordbytes = mi.uordblks * 4096;

  if (mem_bytes != uordbytes) {
    if (orig_mem_bytes == 0)
      mem_bytes = orig_mem_bytes = uordbytes;
    else {
      fprintf(logfp, "%s[%ld]: ", am_get_progname(), (long) am_mypid);
      if (mem_bytes < uordbytes) {
	fprintf(logfp, "ALLOC: %ld bytes", uordbytes - mem_bytes);
      } else {
	fprintf(logfp, "FREE: %ld bytes", mem_bytes - uordbytes);
      }
      mem_bytes = uordbytes;
      fprintf(logfp, ", making %d missing\n", mem_bytes - orig_mem_bytes);
    }
  }
  malloc_verify();
}
# endif /* not defined(HAVE_MALLINFO) && defined(HAVE_MALLOC_VERIFY) */
#endif /* DEBUG_MEM */


/*
 * Take a log format string and expand occurrences of %m
 * with the current error code taken from errno.  Make sure
 * 'e' never gets longer than maxlen characters.
 */
static const char *
expand_error(const char *f, char *e, size_t maxlen)
{
  const char *p;
  char *q;
  int error = errno;
  int len = 0;

  for (p = f, q = e; (*q = *p) && (size_t) len < maxlen; len++, q++, p++) {
    if (p[0] == '%' && p[1] == 'm') {
      xstrlcpy(q, strerror(error), maxlen);
      len += strlen(q) - 1;
      q += strlen(q) - 1;
      p++;
    }
  }
  e[maxlen-1] = '\0';		/* null terminate, to be sure */
  return e;
}


/*
 * Output the time of day and hostname to the logfile
 */
static void
show_time_host_and_name(int lvl)
{
  static time_t last_t = 0;
  static char *last_ctime = NULL;
  time_t t;
#if defined(HAVE_CLOCK_GETTIME) && defined(DEBUG)
  struct timespec ts;
#endif /* defined(HAVE_CLOCK_GETTIME) && defined(DEBUG) */
  char nsecs[11];		/* '.' + 9 digits + '\0' */
  char *sev;

  nsecs[0] = '\0';

#if defined(HAVE_CLOCK_GETTIME) && defined(DEBUG)
  /*
   * Some systems (AIX 4.3) seem to implement clock_gettime() as stub
   * returning ENOSYS.
   */
  if (clock_gettime(CLOCK_REALTIME, &ts) == 0) {
    t = ts.tv_sec;
    if (amuDebug(D_HRTIME))
      xsnprintf(nsecs, sizeof(nsecs), ".%09ld", ts.tv_nsec);
  }
  else
#endif /* defined(HAVE_CLOCK_GETTIME) && defined(DEBUG) */
    t = clocktime(NULL);

  if (t != last_t) {
    last_ctime = ctime(&t);
    last_t = t;
  }

  switch (lvl) {
  case XLOG_FATAL:
    sev = "fatal:";
    break;
  case XLOG_ERROR:
    sev = "error:";
    break;
  case XLOG_USER:
    sev = "user: ";
    break;
  case XLOG_WARNING:
    sev = "warn: ";
    break;
  case XLOG_INFO:
    sev = "info: ";
    break;
  case XLOG_DEBUG:
    sev = "debug:";
    break;
  case XLOG_MAP:
    sev = "map:  ";
    break;
  case XLOG_STATS:
    sev = "stats:";
    break;
  default:
    sev = "hmm:  ";
    break;
  }
  fprintf(logfp, "%15.15s%s %s %s[%ld]/%s ",
	  last_ctime + 4, nsecs, am_get_hostname(),
	  am_get_progname(),
	  (long) am_mypid,
	  sev);
}


#ifdef DEBUG
/*
 * Switch on/off debug options
 */
int
debug_option(char *opt)
{
  u_int dl = debug_flags;
  static int initialized_debug_flags = 0;
  int rc = cmdoption(opt, dbg_opt, &dl);

  if (rc)		    /* if got any error, don't update debug flags */
    return EINVAL;

  /*
   * If we already initialized the debugging flags once (via amd.conf), then
   * don't allow "immutable" flags to be changed again (via amq -D), because
   * they could mess Amd's state and only make sense to be set once when Amd
   * starts.
   */
  if (initialized_debug_flags &&
      debug_flags != 0 &&
      (dl & D_IMMUTABLE) != (debug_flags & D_IMMUTABLE)) {
    plog(XLOG_ERROR, "cannot change immutable debug flags");
    /* undo any attempted change to an immutable flag */
    dl = (dl & ~D_IMMUTABLE) | (debug_flags & D_IMMUTABLE);
  }
  initialized_debug_flags = 1;
  debug_flags = dl;

  return rc;
}


void
dplog(const char *fmt, ...)
{
  va_list ap;

  if (!logfp)
    logfp = stderr;		/* initialize before possible first use */

  va_start(ap, fmt);
  real_plog(XLOG_DEBUG, fmt, ap);
  va_end(ap);
}
#endif /* DEBUG */


void
plog(int lvl, const char *fmt, ...)
{
  va_list ap;

  if (!logfp)
    logfp = stderr;		/* initialize before possible first use */

  va_start(ap, fmt);
  real_plog(lvl, fmt, ap);
  va_end(ap);
}


static void
real_plog(int lvl, const char *fmt, va_list vargs)
{
  char msg[1024];
  char efmt[1024];
  char *ptr = msg;
  static char last_msg[1024];
  static int last_count = 0, last_lvl = 0;

  if (!(xlog_level & lvl))
    return;

#ifdef DEBUG_MEM
# if defined(HAVE_MALLINFO) && defined(HAVE_MALLOC_VERIFY)
  checkup_mem();
# endif /* not defined(HAVE_MALLINFO) && defined(HAVE_MALLOC_VERIFY) */
#endif /* DEBUG_MEM */

  /*
   * Note: xvsnprintf() may call plog() if a truncation happened, but the
   * latter has some code to break out of an infinite loop.  See comment in
   * xsnprintf() below.
   */
  xvsnprintf(ptr, 1023, expand_error(fmt, efmt, 1024), vargs);

  ptr += strlen(ptr);
  if (*(ptr-1) == '\n')
    *--ptr = '\0';

#ifdef HAVE_SYSLOG
  if (syslogging) {
    switch (lvl) {		/* from mike <mcooper@usc.edu> */
    case XLOG_FATAL:
      lvl = LOG_CRIT;
      break;
    case XLOG_ERROR:
      lvl = LOG_ERR;
      break;
    case XLOG_USER:
      lvl = LOG_WARNING;
      break;
    case XLOG_WARNING:
      lvl = LOG_WARNING;
      break;
    case XLOG_INFO:
      lvl = LOG_INFO;
      break;
    case XLOG_DEBUG:
      lvl = LOG_DEBUG;
      break;
    case XLOG_MAP:
      lvl = LOG_DEBUG;
      break;
    case XLOG_STATS:
      lvl = LOG_INFO;
      break;
    default:
      lvl = LOG_ERR;
      break;
    }
    syslog(lvl, "%s", msg);
    return;
  }
#endif /* HAVE_SYSLOG */

  *ptr++ = '\n';
  *ptr = '\0';

  /*
   * mimic syslog behavior: only write repeated strings if they differ
   */
  switch (last_count) {
  case 0:			/* never printed at all */
    last_count = 1;
    if (strlcpy(last_msg, msg, sizeof(last_msg)) >= sizeof(last_msg)) /* don't use xstrlcpy here (recursive!) */
      fprintf(stderr, "real_plog: string \"%s\" truncated to \"%s\"\n", last_msg, msg);
    last_lvl = lvl;
    show_time_host_and_name(lvl); /* mimic syslog header */
    __IGNORE(fwrite(msg, ptr - msg, 1, logfp));
    fflush(logfp);
    break;

  case 1:			/* item printed once, if same, don't repeat */
    if (STREQ(last_msg, msg)) {
      last_count++;
    } else {			/* last msg printed once, new one differs */
      /* last_count remains at 1 */
      if (strlcpy(last_msg, msg, sizeof(last_msg)) >= sizeof(last_msg)) /* don't use xstrlcpy here (recursive!) */
	fprintf(stderr, "real_plog: string \"%s\" truncated to \"%s\"\n", last_msg, msg);
      last_lvl = lvl;
      show_time_host_and_name(lvl); /* mimic syslog header */
      __IGNORE(fwrite(msg, ptr - msg, 1, logfp));
      fflush(logfp);
    }
    break;

  case 100:
    /*
     * Don't allow repetitions longer than 100, so you can see when something
     * cycles like crazy.
     */
    show_time_host_and_name(last_lvl);
    xsnprintf(last_msg, sizeof(last_msg),
	      "last message repeated %d times\n", last_count);
    __IGNORE(fwrite(last_msg, strlen(last_msg), 1, logfp));
    fflush(logfp);
    last_count = 0;		/* start from scratch */
    break;

  default:			/* item repeated multiple times */
    if (STREQ(last_msg, msg)) {
      last_count++;
    } else {		/* last msg repeated+skipped, new one differs */
      show_time_host_and_name(last_lvl);
      xsnprintf(last_msg, sizeof(last_msg),
		"last message repeated %d times\n", last_count);
      __IGNORE(fwrite(last_msg, strlen(last_msg), 1, logfp));
      if (strlcpy(last_msg, msg, 1024) >= 1024) /* don't use xstrlcpy here (recursive!) */
	fprintf(stderr, "real_plog: string \"%s\" truncated to \"%s\"\n", last_msg, msg);
      last_count = 1;
      last_lvl = lvl;
      show_time_host_and_name(lvl); /* mimic syslog header */
      __IGNORE(fwrite(msg, ptr - msg, 1, logfp));
      fflush(logfp);
    }
    break;
  }

}


/*
 * Display current debug options
 */
void
show_opts(int ch, struct opt_tab *opts)
{
  int i;
  int s = '{';

  fprintf(stderr, "\t[-%c {no}", ch);
  for (i = 0; opts[i].opt; i++) {
    fprintf(stderr, "%c%s", s, opts[i].opt);
    s = ',';
  }
  fputs("}]\n", stderr);
}


int
cmdoption(char *s, struct opt_tab *optb, u_int *flags)
{
  char *p = s;
  int errs = 0;

  while (p && *p) {
    int neg;
    char *opt;
    struct opt_tab *dp, *dpn = NULL;

    s = p;
    p = strchr(p, ',');
    if (p)
      *p = '\0';

    /* check for "no" prefix to options */
    if (s[0] == 'n' && s[1] == 'o') {
      opt = s + 2;
      neg = 1;
    } else {
      opt = s;
      neg = 0;
    }

    /*
     * Scan the array of debug options to find the
     * corresponding flag value.  If it is found
     * then set (or clear) the flag (depending on
     * whether the option was prefixed with "no").
     */
    for (dp = optb; dp->opt; dp++) {
      if (STREQ(opt, dp->opt))
	break;
      if (opt != s && !dpn && STREQ(s, dp->opt))
	dpn = dp;
    }

    if (dp->opt || dpn) {
      if (!dp->opt) {
	dp = dpn;
	neg = !neg;
      }
      if (neg)
	*flags &= ~dp->flag;
      else
	*flags |= dp->flag;
    } else {
      /*
       * This will log to stderr when parsing the command line
       * since any -l option will not yet have taken effect.
       */
      plog(XLOG_ERROR, "option \"%s\" not recognized", s);
      errs++;
    }

    /*
     * Put the comma back
     */
    if (p)
      *p++ = ',';
  }

  return errs;
}


/*
 * Switch on/off logging options
 */
int
switch_option(char *opt)
{
  u_int xl = xlog_level;
  int rc = cmdoption(opt, xlog_opt, &xl);

  if (rc)			/* if got any error, don't update flags */
    return EINVAL;

  /*
   * Don't allow "mandatory" flags to be turned off, because
   * we must always be able to report on flag re/setting errors.
   */
  if ((xl & XLOG_MANDATORY) != XLOG_MANDATORY) {
    plog(XLOG_ERROR, "cannot turn off mandatory logging options");
    xl |= XLOG_MANDATORY;
  }
  if (xlog_level != xl)
    xlog_level = xl;		/* set new flags */
  return rc;
}


#ifdef LOG_DAEMON
/*
 * get syslog facility to use.
 * logfile can be "syslog", "syslog:daemon", "syslog:local7", etc.
 */
static int
get_syslog_facility(const char *logfile)
{
  char *facstr;

  /* parse facility string */
  facstr = strchr(logfile, ':');
  if (!facstr)			/* log file was "syslog" */
    return LOG_DAEMON;
  facstr++;
  if (!facstr || facstr[0] == '\0') { /* log file was "syslog:" */
    plog(XLOG_WARNING, "null syslog facility, using LOG_DAEMON");
    return LOG_DAEMON;
  }

#ifdef LOG_KERN
  if (STREQ(facstr, "kern"))
      return LOG_KERN;
#endif /* not LOG_KERN */
#ifdef LOG_USER
  if (STREQ(facstr, "user"))
      return LOG_USER;
#endif /* not LOG_USER */
#ifdef LOG_MAIL
  if (STREQ(facstr, "mail"))
      return LOG_MAIL;
#endif /* not LOG_MAIL */

  if (STREQ(facstr, "daemon"))
      return LOG_DAEMON;

#ifdef LOG_AUTH
  if (STREQ(facstr, "auth"))
      return LOG_AUTH;
#endif /* not LOG_AUTH */
#ifdef LOG_SYSLOG
  if (STREQ(facstr, "syslog"))
      return LOG_SYSLOG;
#endif /* not LOG_SYSLOG */
#ifdef LOG_LPR
  if (STREQ(facstr, "lpr"))
      return LOG_LPR;
#endif /* not LOG_LPR */
#ifdef LOG_NEWS
  if (STREQ(facstr, "news"))
      return LOG_NEWS;
#endif /* not LOG_NEWS */
#ifdef LOG_UUCP
  if (STREQ(facstr, "uucp"))
      return LOG_UUCP;
#endif /* not LOG_UUCP */
#ifdef LOG_CRON
  if (STREQ(facstr, "cron"))
      return LOG_CRON;
#endif /* not LOG_CRON */
#ifdef LOG_LOCAL0
  if (STREQ(facstr, "local0"))
      return LOG_LOCAL0;
#endif /* not LOG_LOCAL0 */
#ifdef LOG_LOCAL1
  if (STREQ(facstr, "local1"))
      return LOG_LOCAL1;
#endif /* not LOG_LOCAL1 */
#ifdef LOG_LOCAL2
  if (STREQ(facstr, "local2"))
      return LOG_LOCAL2;
#endif /* not LOG_LOCAL2 */
#ifdef LOG_LOCAL3
  if (STREQ(facstr, "local3"))
      return LOG_LOCAL3;
#endif /* not LOG_LOCAL3 */
#ifdef LOG_LOCAL4
  if (STREQ(facstr, "local4"))
      return LOG_LOCAL4;
#endif /* not LOG_LOCAL4 */
#ifdef LOG_LOCAL5
  if (STREQ(facstr, "local5"))
      return LOG_LOCAL5;
#endif /* not LOG_LOCAL5 */
#ifdef LOG_LOCAL6
  if (STREQ(facstr, "local6"))
      return LOG_LOCAL6;
#endif /* not LOG_LOCAL6 */
#ifdef LOG_LOCAL7
  if (STREQ(facstr, "local7"))
      return LOG_LOCAL7;
#endif /* not LOG_LOCAL7 */

  /* didn't match anything else */
  plog(XLOG_WARNING, "unknown syslog facility \"%s\", using LOG_DAEMON", facstr);
  return LOG_DAEMON;
}
#endif /* not LOG_DAEMON */


/*
 * Change current logfile
 */
int
switch_to_logfile(char *logfile, int old_umask, int truncate_log)
{
  FILE *new_logfp = stderr;

  if (logfile) {
#ifdef HAVE_SYSLOG
    syslogging = 0;
#endif /* HAVE_SYSLOG */

    if (STREQ(logfile, "/dev/stderr"))
      new_logfp = stderr;
    else if (NSTREQ(logfile, "syslog", strlen("syslog"))) {

#ifdef HAVE_SYSLOG
      syslogging = 1;
      new_logfp = stderr;
      openlog(am_get_progname(),
	      LOG_PID
# ifdef LOG_NOWAIT
	      | LOG_NOWAIT
# endif /* LOG_NOWAIT */
# ifdef LOG_DAEMON
	      , get_syslog_facility(logfile)
# endif /* LOG_DAEMON */
	      );
#else /* not HAVE_SYSLOG */
      plog(XLOG_WARNING, "syslog option not supported, logging unchanged");
#endif /* not HAVE_SYSLOG */

    } else {			/* regular log file */
      (void) umask(old_umask);
      if (truncate_log)
	__IGNORE(truncate(logfile, 0));
      new_logfp = fopen(logfile, "a");
      umask(0);
    }
  }

  /*
   * If we couldn't open a new file, then continue using the old.
   */
  if (!new_logfp && logfile) {
    plog(XLOG_USER, "%s: Can't open logfile: %m", logfile);
    return 1;
  }

  /*
   * Close the previous file
   */
  if (logfp && logfp != stderr)
    (void) fclose(logfp);
  logfp = new_logfp;

  if (logfile)
    plog(XLOG_INFO, "switched to logfile \"%s\"", logfile);
  else
    plog(XLOG_INFO, "no logfile defined; using stderr");

  return 0;
}


void
unregister_amq(void)
{

  if (amuDebug(D_AMQ)) {
    /* find which instance of amd to unregister */
    u_long amd_prognum = get_amd_program_number();

    if (pmap_unset(amd_prognum, AMQ_VERSION) != 1)
      dlog("failed to de-register Amd program %lu, version %lu",
	   amd_prognum, AMQ_VERSION);
  }
}


void
going_down(int rc)
{
  if (foreground) {
    if (amd_state != Start) {
      if (amd_state != Done)
	return;
      unregister_amq();
    }
  }

#ifdef MOUNT_TABLE_ON_FILE
  /*
   * Call unlock_mntlist to free any important resources such as an on-disk
   * lock file (/etc/mtab~).
   */
  unlock_mntlist();
#endif /* MOUNT_TABLE_ON_FILE */

  if (foreground) {
    plog(XLOG_INFO, "Finishing with status %d", rc);
  } else {
    dlog("background process exiting with status %d", rc);
  }
  /* bye bye... */
  exit(rc);
}


/* return the rpc program number under which amd was used */
u_long
get_amd_program_number(void)
{
  return amd_program_number;
}


/* set the rpc program number used for amd */
void
set_amd_program_number(u_long program)
{
  amd_program_number = program;
}


/*
 * Release the controlling tty of the process pid.
 *
 * Algorithm: try these in order, if available, until one of them
 * succeeds: setsid(), ioctl(fd, TIOCNOTTY, 0).
 * Do not use setpgid(): on some OSs it may release the controlling tty,
 * even if the man page does not mention it, but on other OSs it does not.
 * Also avoid setpgrp(): it works on some systems, and on others it is
 * identical to setpgid().
 */
void
amu_release_controlling_tty(void)
{
  int fd;

  /*
   * In daemon mode, leaving open file descriptors to terminals or pipes
   * can be a really bad idea.
   * Case in point: the redhat startup script calls us through their 'initlog'
   * program, which exits as soon as the original amd process exits. If,
   * at some point, a misbehaved library function decides to print something
   * to the screen, we get a SIGPIPE and die.
   * And guess what: NIS glibc functions will attempt to print to stderr
   * "YPBINDPROC_DOMAIN: Domain not bound" if ypbind is running but can't find
   * a ypserver.
   *
   * So we close all of our "terminal" filedescriptors, i.e. 0, 1 and 2, then
   * reopen them as /dev/null.
   *
   * XXX We should also probably set the SIGPIPE handler to SIG_IGN.
   */
  fd = open("/dev/null", O_RDWR);
  if (fd < 0) {
    plog(XLOG_WARNING, "Could not open /dev/null for rw: %m");
  } else {
    fflush(stdin);  close(0); dup2(fd, 0);
    fflush(stdout); close(1); dup2(fd, 1);
    fflush(stderr); close(2); dup2(fd, 2);
    close(fd);
  }

#ifdef HAVE_SETSID
  /* XXX: one day maybe use vhangup(2) */
  if (setsid() < 0) {
    plog(XLOG_WARNING, "Could not release controlling tty using setsid(): %m");
  } else {
    plog(XLOG_INFO, "released controlling tty using setsid()");
    return;
  }
#endif /* HAVE_SETSID */

#ifdef TIOCNOTTY
  fd = open("/dev/tty", O_RDWR);
  if (fd < 0) {
    /* not an error if already no controlling tty */
    if (errno != ENXIO)
      plog(XLOG_WARNING, "Could not open controlling tty: %m");
  } else {
    if (ioctl(fd, TIOCNOTTY, 0) < 0 && errno != ENOTTY)
      plog(XLOG_WARNING, "Could not disassociate tty (TIOCNOTTY): %m");
    else
      plog(XLOG_INFO, "released controlling tty using ioctl(TIOCNOTTY)");
    close(fd);
  }
  return;
#endif /* not TIOCNOTTY */

  plog(XLOG_ERROR, "unable to release controlling tty");
}


/* setup a single signal handler */
void
setup_sighandler(int signum, void (*handler)(int))
{
#ifdef HAVE_SIGACTION
  struct sigaction sa;
  memset(&sa, 0, sizeof(sa));
  sa.sa_flags = 0;		/* unnecessary */
  sa.sa_handler = handler;
  sigemptyset(&(sa.sa_mask));	/* probably unnecessary too */
  sigaddset(&(sa.sa_mask), signum);
  sigaction(signum, &sa, NULL);
#else /* not HAVE_SIGACTION */
  (void) signal(signum, handler);
#endif /* not HAVE_SIGACTION */
}


/*
 * Return current time in seconds.  If passed a non-null argyument, then
 * fill it in with the current time in seconds and microseconds (useful
 * for mtime updates).
 */
time_t
clocktime(nfstime *nt)
{
  static struct timeval now;	/* keep last time, as default */

  if (gettimeofday(&now, NULL) < 0) {
    plog(XLOG_ERROR, "clocktime: gettimeofday: %m");
    /* hack: force time to have incremented by at least 1 second */
    now.tv_sec++;
  }
  /* copy seconds and microseconds. may demote a long to an int */
  if (nt) {
    nt->nt_seconds = (u_int) now.tv_sec;
    nt->nt_useconds = (u_int) now.tv_usec;
  }
  return (time_t) now.tv_sec;
}


/*
 * Make all the directories in the path.
 */
int
mkdirs(char *path, int mode)
{
  /*
   * take a copy in case path is in readonly store
   */
  char *p2 = strdup(path);
  char *sp = p2;
  struct stat stb;
  int error_so_far = 0;

  /*
   * Skip through the string make the directories.
   * Mostly ignore errors - the result is tested at the end.
   *
   * This assumes we are root so that we can do mkdir in a
   * mode 555 directory...
   */
  while ((sp = strchr(sp + 1, '/'))) {
    *sp = '\0';
    if (mkdir(p2, mode) < 0) {
      error_so_far = errno;
    } else {
      dlog("mkdir(%s)", p2);
    }
    *sp = '/';
  }

  if (mkdir(p2, mode) < 0) {
    error_so_far = errno;
  } else {
    dlog("mkdir(%s)", p2);
  }

  XFREE(p2);

  return stat(path, &stb) == 0 &&
    (stb.st_mode & S_IFMT) == S_IFDIR ? 0 : error_so_far;
}


/*
 * Remove as many directories in the path as possible.
 * Give up if the directory doesn't appear to have
 * been created by Amd (not mode dr-x) or an rmdir
 * fails for any reason.
 */
void
rmdirs(char *dir)
{
  char *xdp = strdup(dir);
  char *dp;

  do {
    struct stat stb;
    /*
     * Try to find out whether this was
     * created by amd.  Do this by checking
     * for owner write permission.
     */
    if (stat(xdp, &stb) == 0 && (stb.st_mode & 0200) == 0) {
      if (rmdir(xdp) < 0) {
	if (errno != ENOTEMPTY &&
	    errno != EBUSY &&
	    errno != EEXIST &&
	    errno != EROFS &&
	    errno != EINVAL)
	  plog(XLOG_ERROR, "rmdir(%s): %m", xdp);
	break;
      } else {
	dlog("rmdir(%s)", xdp);
      }
    } else {
      break;
    }

    dp = strrchr(xdp, '/');
    if (dp)
      *dp = '\0';
  } while (dp && dp > xdp);

  XFREE(xdp);
}

