#include "args.h"
#include "config.h"
/*
 * Copyright (c) 1986, 2014 by The Trustees of Columbia University in
 * the City of New York.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  + Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *  + 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.
 *
 *  + Neither the name of Columbia University nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 */

#ifndef lint
static const char *rcsid = "$Header: /usr/local/src/mm/mm-0.94/mm/RCS/signals.c,v 1.5 2005/05/31 14:43:55 beebe Exp beebe $";
#endif

/*
 * Signal handling support for MM.
 */

#include "mm.h"

#if defined(HAVE_VOIDSIG)
#define SIG void
#else
#define SIG int
#endif

#if defined(__sgi) && defined(_LANGUAGE_C_PLUS_PLUS)
	/* SGI C++ <sys/signal.h> has irregular signal() arguments */
typedef SIG (*Sigval) ARGS((_sigargs));
#else
typedef SIG (*Sigval) ARGS((int));
#endif

#ifndef sigmask
#define sigmask(m) (1 << ((m) - 1))
#endif

#ifndef NSIG
#define NSIG 32
#endif

static long signal_mask = 0, deferred_signals = 0;

static SIG signal_handler ARGS((int sig));
static void handle_signal ARGS((int sig));
static void run_deferred_signals ARGS((void));

#ifdef HAVE_BSD_SIGNALS
#if defined(__hpux)
  /* HPUX defines sigblock() and sigsetmask() with long arguments and long
     return values */
#else
extern int sigblock ARGS((int mask));
extern int sigsetmask ARGS((int mask));
#endif
#endif /* HAVE_BSD_SIGNALS */

/*
 * Set up signal handlers
 */

void
#if HAVE_STDC
init_signals (void)
#else /* K&R style */
init_signals (VOID)
#endif /* HAVE_STDC */
{
    signal_mask = ~0;
    deferred_signals = 0;

    (void) signal (SIGHUP, signal_handler);
    (void) signal (SIGALRM, signal_handler);
    (void) signal (SIGINT, signal_handler);
#ifdef SIGPIPE
    (void) signal (SIGPIPE, signal_handler);
#endif
#ifdef SIGTSTP
    (void) signal (SIGTSTP, signal_handler);
#endif
#ifdef SIGXCPU
    (void) signal (SIGXCPU, signal_handler);
#endif
#ifdef SIGCHLD
    (void) signal (SIGCHLD, signal_handler);
#endif
    release_signals (0L);
}

/*
 * Block signals we want to lock out.  On BSD systems this
 * just calls sigblock(); on others, we maintain our own signal mask:
 * the signal_handler routine will catch signals and remember they
 * happened, so their handlers can be invoked by release_signals.
 */

long
#if HAVE_STDC
block_signals (void)
#else /* K&R style */
block_signals (VOID)
#endif /* HAVE_STDC */
{
#ifndef HAVE_BSD_SIGNALS
    long m = signal_mask;
#endif
    signal_mask |= sigmask (SIGHUP) | sigmask (SIGINT) |
#ifdef SIGPIPE
        sigmask (SIGPIPE) |
#endif
#ifdef SIGTSTP
	sigmask (SIGTSTP) |
#endif
#ifdef SIGCONT
	sigmask (SIGCONT) |
#endif
#ifdef SIGCHLD
	sigmask (SIGCHLD) |
#endif
#ifdef SIGXCPU
	sigmask (SIGXCPU) |
#endif
	sigmask (SIGALRM);
#ifdef HAVE_BSD_SIGNALS
    return sigblock (signal_mask);
#else
    return m;
#endif
}

/*
 * Restore the signal mask, and invoke handlers for any pending signals
 */
void
#if HAVE_STDC
release_signals (long int m)
#else /* K&R style */
release_signals (m)
long m;
#endif /* HAVE_STDC */
{
#ifdef HAVE_BSD_SIGNALS
    (void) sigsetmask (m);
#endif
    signal_mask = m;
    if (deferred_signals)
	run_deferred_signals ();
}

void
#if HAVE_STDC
queue_signal (int sig)
#else /* K&R style */
queue_signal (sig)
int sig;
#endif /* HAVE_STDC */
{
    deferred_signals |= sigmask (sig);
}

static void
#if HAVE_STDC
run_deferred_signals (void)
#else /* K&R style */
run_deferred_signals (VOID)
#endif /* HAVE_STDC */
{
    int i;
    long m = deferred_signals & ~signal_mask;

    for (i = 0; (i < NSIG) && m; i++)
	if (m & sigmask (i)) {
	    m &= ~sigmask(i);
	    handle_signal (i);
	}
}

/*
 * This routine is the only one we install as a signal handler using
 * signal(2).  On BSD systems it just calls handle_signal(), while on
 * other systems it may defer invocation of the handler if the signal
 * has been disabled with block_signals.
 */

static SIG
#if HAVE_STDC
signal_handler (int sig)
#else /* K&R style */
signal_handler (sig)
int sig;
#endif /* HAVE_STDC */
{
#ifdef HAVE_BSD_SIGNALS
    handle_signal (sig);
#else
    unsigned long b = sigmask (sig);

    if (signal_mask & b) {
	queue_signal (sig);
	if (sig != SIGCHLD)
	    signal (sig, signal_handler);
	return signalhandler_retval;
    }

    handle_signal (sig);
    signal (sig, signal_handler);
#endif
    return signalhandler_retval;
}

/*
 * Dispatch to handle signal SIG.
 */

static void
#if HAVE_STDC
handle_signal (int sig)
#else /* K&R style */
handle_signal (sig)
int sig;
#endif /* HAVE_STDC */
{
    deferred_signals &= ~sigmask (sig);

    switch (sig) {
      case SIGALRM:
	new_mail (true);
	break;
      case SIGINT:
	sigint_handler ();
	break;
      case SIGHUP:
	sighup_handler ();
	break;
#ifdef SIGPIPE
      case SIGPIPE:			/* ignore */
	break;
#endif
#ifdef SIGCHLD
      case SIGCHLD:
	collect_process (0);
	break;
#endif
#ifdef SIGXCPU
      case SIGXCPU:
	sigxcpu_handler ();
	break;
#endif
#ifdef SIGTSTP
      case SIGTSTP:
	write (1, "\r\n", 2);		/* move to new line */
	suspend (0);			/* suspend ourself */
	break;
#endif
    }
}

/*
 * This routine should be called before and after any synchronous fork/wait
 * sequences; arg should be "true" on the first call and "false" on the
 * second.
 *
 * This routine serves to disable the SIGCHLD handler for callers who want
 * to wait for children to exit, and saves/restores the tty process group
 * for ill-behaved children who may change the tty process group and not
 * change it back before suspending themselves or exiting (e.g. ksh).
 *
 */

void
#if HAVE_STDC
fix_signals_for_fork (int before_the_fork)
#else /* K&R style */
fix_signals_for_fork (before_the_fork)
int before_the_fork;
#endif /* HAVE_STDC */
{
#ifdef SIGCHLD
    static Sigval old_chld_handler = (Sigval)NULL;
#endif
#ifdef TIOCGPGRP
    Sigval tmp = (Sigval)NULL;
    static int ttypgrp = -1;
#endif
    static int nesting = 0;
    if (before_the_fork) {
	if (nesting++ != 0)
	    return;
#ifdef TIOCGPGRP
	if (isatty(2)) {
	    tmp = signal (SIGTTIN, SIG_IGN);
	    (void) ioctl (0, TIOCGPGRP, &ttypgrp);
	    if (tmp != SIG_IGN)
		signal (SIGTTIN, tmp);
	}
#endif
#ifdef SIGCHLD
	old_chld_handler = signal (SIGCHLD, SIG_DFL);
#endif
    }
    else {
	if (--nesting != 0)		/* this code used to be inside the TIOCGPGRP */
	    return;			/* conditional, but that seems WRONG!!! */
	/* Oddly, the old code worked on SunOS 4.1.3 and DEC ULTRIX 4.x and
	   either HP HP-UX 9.x or SGI IRIX 4.x, but failed on Solaris 2.x
	   and one of the two latter */

#ifdef TIOCGPGRP
	if (isatty(2) && ttypgrp > 0) {
	    tmp = signal (SIGTTOU, SIG_IGN);
	    (void) ioctl (2, TIOCSPGRP, &ttypgrp);
	    if (tmp != SIG_IGN)
		signal (SIGTTOU, tmp);
	}
	ttypgrp = -1;
#endif
#ifdef SIGCHLD
	(void) signal (SIGCHLD, old_chld_handler);
#endif
    }
}

/*
 * SIGINT and SIGQUIT must be ignored around blocking wait calls.
 * SIGTSTP should use the default signal handler.
 */

void
#if HAVE_STDC
fix_signals_for_wait (int before_the_wait)
#else /* K&R style */
fix_signals_for_wait (before_the_wait)
int before_the_wait;
#endif /* HAVE_STDC */
{
    static int nesting = 0;
    static Sigval old_handlers[3];

    if (before_the_wait) {
	if (++nesting == 1) {
	    old_handlers[0] = signal (SIGINT, SIG_IGN);
	    old_handlers[1] = signal (SIGQUIT, SIG_IGN);
#ifdef SIGTSTP
	    old_handlers[2] = signal (SIGTSTP, SIG_DFL);
#endif
	}
    }
    else {
	if (--nesting == 0) {
	    (void) signal (SIGINT, old_handlers[0]);
	    (void) signal (SIGQUIT, old_handlers[1]);
#ifdef SIGTSTP
	    (void) signal (SIGTSTP, old_handlers[2]);
#endif
	}
    }
}

/*
 * mm_popen and mm_pclose:

 * Since MM traps SIGCHLD (to catch whatever sendmails it sent into
 * the background), it will wait() on the pipe process.  In fact, when
 * any process finishes, the SIGCHLD handler catches it.  So, when
 * pclose() does a wait, it never gets any processes.  The wait (in
 * pclose()) cannot return until all processes have sent their
 * SIGCHLDs and been waited on by the SIGCHLD handler.  This means
 * that pclose() must wait() until all those really SLOW sendmail
 * processes are done, which is pretty slow.
 *
 * Therefore, in order to guarantee that the pipe process is still
 * around for pclose() to wait() on, we block or ignore all SIGCHLD
 * signals before we do the popen(), and turn our SIGCHLD handler back
 * on after the pclose() is done.  (Note that this may allow pclose()
 * to catch some of our sendmail processes, if they end before the
 * pipe process, but we check for that in maybe_wait_for_process().
 */

static Sigval old_handler;

FILE *
#if HAVE_STDC
mm_popen (const char *command, const char *type)
#else /* K&R style */
mm_popen (command, type)
const char *command, *type;
#endif /* HAVE_STDC */
{
    FILE *stream;

#ifdef SIGCHLD
    old_handler = signal (SIGCHLD, SIG_IGN);
#endif
    if ((stream = popen(command, type)) == NULL)
#ifdef SIGCHLD
	signal (SIGCHLD, old_handler);
#endif
    return (stream);
}

int
#if HAVE_STDC
mm_pclose (FILE *stream)
#else /* K&R style */
mm_pclose (stream)
FILE *stream;
#endif /* HAVE_STDC */
{
    int ret;

    ret = pclose (stream);
#ifdef SIGCHLD
    signal (SIGCHLD, old_handler);
#endif
    return (ret);
}
