/*
 * Copyright (C) 1996-2000,2012 Michael R. Elkins <me@mutt.org>
 *
 *     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 of the License, 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#if HAVE_CONFIG_H
# include "config.h"
#endif

#include "mutt.h"
#include "mutt_curses.h"

#include <signal.h>
#include <string.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>

static sigset_t Sigset;
static sigset_t SigsetSys;
static struct sigaction SysOldInt;
static struct sigaction SysOldQuit;
static int IsEndwin = 0;

static char *Caught_Signal_L10N = NULL;
static char *Exiting_L10N = NULL;


static void exit_print_int_recursive(int n)
{
  char digit;

  if (n > 9)
    exit_print_int_recursive(n / 10);

  digit = '0' + (n % 10);
  write(1, &digit, 1);
}

static void exit_print_int(int n)
{
  if (n < 0)
  {
    write(1, "-", 1);
    n = -n;
  }
  exit_print_int_recursive(n);
}

static void exit_print_string(const char *str)
{
  size_t len = 0;

  if (!str)
    return;

  while (str[len])
    len++;

  if (len > 0)
    write(1, str, len);
}

/* Attempt to catch "ordinary" signals and shut down gracefully.
 * Do not do l10n translations as _() can invoke malloc(), (__MEM_CHECKED__)
 * which may not be re-entrant. */
static void exit_handler(int sig)
{
  curs_set(1);
  endwin(); /* just to be safe */

  exit_print_string(Caught_Signal_L10N ? Caught_Signal_L10N : "Caught signal ");
#if SYS_SIGLIST_DECLARED
  exit_print_string(sys_siglist[sig]);
#else
#if (__sun__ && __svr4__)
  exit_print_string(_sys_siglist[sig]);
#else
#if (__alpha && __osf__)
  exit_print_string(__sys_siglist[sig]);
#else
  exit_print_int(sig);
#endif
#endif
#endif
  exit_print_string(Exiting_L10N ? Exiting_L10N : "...  Exiting.\n");
  exit(0);
}

/* These are gettext() translated in advance because that
 * is not guaranteed reentrant.
 */
static void exit_handler_l10n_init(void)
{
  if (!Caught_Signal_L10N)
  {
    /* L10N:
       This is printed in the exit handler when a signal is caught.
       The whole string is "Caught signal [XXX]...  Exiting\n".
       This is the first part of the string: note with a trailing space.
    */
    Caught_Signal_L10N = safe_strdup(_("Caught signal "));
  }
  if (!Exiting_L10N)
  {
    /* L10N:
       This is printed in the exit handler when a signal is caught.
       The whole string is "Caught signal [XXX]...  Exiting\n".
       This is the second part of the string, printed after the
       signal number or name.
    */
    Exiting_L10N = safe_strdup(_("...  Exiting.\n"));
  }
}

static void exit_handler_l10n_cleanup(void)
{
  FREE(&Caught_Signal_L10N);
  FREE(&Exiting_L10N);
}

static void chld_handler(int sig)
{
  SigChld = 1;
}

static void sighandler(int sig)
{
  int save_errno = errno;

  switch (sig)
  {
    case SIGTSTP: /* user requested a suspend */
      if (!option(OPTSUSPEND))
        break;
      IsEndwin = isendwin();
      curs_set(1);
      if (!IsEndwin)
        endwin();
      kill(0, SIGSTOP);
      /* fall through */

    case SIGCONT:
      if (!IsEndwin)
        refresh();
      mutt_curs_set(-1);
#if defined(USE_SLANG_CURSES) || defined(HAVE_RESIZETERM)
      /* We don't receive SIGWINCH when suspended; however, no harm is done by
       * just assuming we received one, and triggering the 'resize' anyway. */
      SigWinch = 1;
#endif
      break;

#if defined(USE_SLANG_CURSES) || defined(HAVE_RESIZETERM)
    case SIGWINCH:
      SigWinch = 1;
      break;
#endif

    case SIGINT:
      SigInt = 1;
      break;

  }
  errno = save_errno;
}

#ifdef USE_SLANG_CURSES
int mutt_intr_hook(void)
{
  return (-1);
}
#endif /* USE_SLANG_CURSES */

void mutt_signal_init(void)
{
  struct sigaction act;

  exit_handler_l10n_init();

  sigemptyset(&act.sa_mask);
  act.sa_flags = 0;
  act.sa_handler = SIG_IGN;
  sigaction(SIGPIPE, &act, NULL);

  act.sa_handler = exit_handler;
  sigaction(SIGTERM, &act, NULL);
  sigaction(SIGHUP, &act, NULL);
  sigaction(SIGQUIT, &act, NULL);

  /* we want to avoid race conditions */
  sigaddset(&act.sa_mask, SIGTSTP);

  act.sa_handler = sighandler;

  /* we want SIGALRM to abort the current syscall, so we do this before
   * setting the SA_RESTART flag below.  currently this is only used to
   * timeout on a connect() call in a reasonable amount of time.
   */
  sigaction(SIGALRM, &act, NULL);

  /* we also don't want to mess with interrupted system calls */
#ifdef SA_RESTART
  act.sa_flags = SA_RESTART;
#endif

  sigaction(SIGCONT, &act, NULL);
  sigaction(SIGTSTP, &act, NULL);
  sigaction(SIGINT, &act, NULL);
#if defined(USE_SLANG_CURSES) || defined(HAVE_RESIZETERM)
  sigaction(SIGWINCH, &act, NULL);
#endif

  /* Handle SIGCHLD.  Tracked for background editing processes. */
  act.sa_handler = chld_handler;
  /* don't need to block any other signals here */
  sigemptyset(&act.sa_mask);
  /* we don't want to mess with stopped children */
  act.sa_flags |= SA_NOCLDSTOP;
  sigaction(SIGCHLD, &act, NULL);

#ifdef USE_SLANG_CURSES
  /* This bit of code is required because of the implementation of
   * SLcurses_wgetch().  If a signal is received (like SIGWINCH) when we
   * are in blocking mode, SLsys_getkey() will not return an error unless
   * a handler function is defined and it returns -1.  This is needed so
   * that if the user resizes the screen while at a prompt, it will just
   * abort and go back to the main-menu.
   */
  SLang_getkey_intr_hook = mutt_intr_hook;
#endif
}

void mutt_signal_cleanup(void)
{
  exit_handler_l10n_cleanup();
}

/* signals which are important to block while doing critical ops */
void mutt_block_signals(void)
{
  if (!option(OPTSIGNALSBLOCKED))
  {
    sigemptyset(&Sigset);
    sigaddset(&Sigset, SIGTERM);
    sigaddset(&Sigset, SIGHUP);
    sigaddset(&Sigset, SIGTSTP);
    sigaddset(&Sigset, SIGINT);
#if defined(USE_SLANG_CURSES) || defined(HAVE_RESIZETERM)
    sigaddset(&Sigset, SIGWINCH);
#endif
    sigprocmask(SIG_BLOCK, &Sigset, 0);
    set_option(OPTSIGNALSBLOCKED);
  }
}

/* restore the previous signal mask */
void mutt_unblock_signals(void)
{
  if (option(OPTSIGNALSBLOCKED))
  {
    sigprocmask(SIG_UNBLOCK, &Sigset, 0);
    unset_option(OPTSIGNALSBLOCKED);
  }
}

void mutt_block_signals_system(void)
{
  struct sigaction sa;

  if (! option(OPTSYSSIGNALSBLOCKED))
  {
    /* POSIX: ignore SIGINT and SIGQUIT & block SIGCHLD  before exec */
    sa.sa_handler = SIG_IGN;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, &SysOldInt);
    sigaction(SIGQUIT, &sa, &SysOldQuit);

    sigemptyset(&SigsetSys);
    sigaddset(&SigsetSys, SIGCHLD);
    sigprocmask(SIG_BLOCK, &SigsetSys, 0);
    set_option(OPTSYSSIGNALSBLOCKED);
  }
}

void mutt_unblock_signals_system(int catch)
{
  if (option(OPTSYSSIGNALSBLOCKED))
  {
    sigprocmask(SIG_UNBLOCK, &SigsetSys, NULL);
    if (catch)
    {
      sigaction(SIGQUIT, &SysOldQuit, NULL);
      sigaction(SIGINT, &SysOldInt, NULL);
    }
    else
    {
      struct sigaction sa;

      sa.sa_handler = SIG_DFL;
      sigemptyset(&sa.sa_mask);
      sa.sa_flags = 0;
      sigaction(SIGQUIT, &sa, NULL);
      sigaction(SIGINT, &sa, NULL);
    }

    unset_option(OPTSYSSIGNALSBLOCKED);
  }
}

/* Resets ignored signals back to the default.
 *
 * See sigaction(2):
 *   A child created via fork(2) inherits a copy of its parent's
 *   signal dispositions.  During an execve(2), the dispositions of
 *   handled signals are reset to the default; the dispositions of
 *   ignored signals are left unchanged.
 */
void mutt_reset_child_signals(void)
{
  struct sigaction act;

  act.sa_handler = SIG_DFL;
  act.sa_flags = 0;
  sigemptyset(&act.sa_mask);

  /* These signals are set to SIG_IGN and must be reset */
  sigaction(SIGPIPE, &act, NULL);

  /* These technically don't need to be reset, but the code has been
   * doing so for a long time. */
  sigaction(SIGTERM, &act, NULL);
  sigaction(SIGTSTP, &act, NULL);
  sigaction(SIGCONT, &act, NULL);
}

void mutt_allow_interrupt(int disposition)
{
  struct sigaction sa;

  memset(&sa, 0, sizeof sa);
  sa.sa_handler = sighandler;
#ifdef SA_RESTART
  if (disposition == 0)
    sa.sa_flags |= SA_RESTART;
#endif
  sigaction(SIGINT, &sa, NULL);
}
