/*
 * cleanup.c -- simple dynamic cleanup function management
 * Copyright (C) 1995 Markus Armbruster.
 * Copyright (C) 2007 Colin Watson.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; see the file docs/COPYING.LIB.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
 * Floor, Boston, MA  02110-1301  USA.
*/

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif /* HAVE_CONFIG_H */

#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>		/* SunOS's loosing assert.h needs it */
#include <assert.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>

#include "xalloc.h"

#include "manconfig.h"		/* for FATAL */
#include "cleanup.h"



/* Dealing with signals */


/* saved signal actions */
#ifdef SIGHUP
static struct sigaction saved_hup_action;
#endif /* SIGHUP */
static struct sigaction saved_int_action;
static struct sigaction saved_term_action;


/* Run cleanups, then reraise signal with default handler. */
static _Noreturn void
sighandler (int signo)
{
  struct sigaction act;
  sigset_t set;

  do_cleanups_sigsafe (true);

  /* set default signal action */
  memset (&act, 0, sizeof act);
  act.sa_handler = SIG_DFL;
  sigemptyset (&act.sa_mask);
  act.sa_flags = 0;
  if (sigaction (signo, &act, NULL)) {
    /* should not happen */
    _exit (FATAL);		/* exit() is taboo from signal handlers! */
  }

  /* unmask signo */
  if (   sigemptyset (&set)
      || sigaddset (&set, signo)
      || sigprocmask (SIG_UNBLOCK, &set, NULL)) {
    /* shouldn't happen */
    _exit (FATAL);		/* exit() is taboo from signal handlers! */
  }

  /* signal has now default action and is unmasked,
     reraise it to terminate program abnormally */
  raise (signo);
  abort();
}


/* Save signo's current action to oldact, if its handler is SIG_DFL
   install sighandler, return 0 on success, -1 on failure. */
static int
trap_signal (int signo, struct sigaction *oldact)
{
  if (sigaction (signo, NULL, oldact)) {
    return -1;
  }

  if (oldact->sa_handler == SIG_DFL) {
    struct sigaction act;

    memset (&act, 0, sizeof act);
    act.sa_handler = sighandler;
    sigemptyset (&act.sa_mask);
    act.sa_flags = 0;
    return sigaction (signo, &act, oldact);
  }

  return 0;
}


/* Trap some abnormal exits to call do_cleanups(). */
static int
trap_abnormal_exits (void)
{
#ifdef SIGHUP
  if (trap_signal (SIGHUP, &saved_hup_action))
    return -1;
#endif /* SIGHUP */
  if (trap_signal (SIGINT, &saved_int_action))
    return -1;
  if (trap_signal (SIGTERM, &saved_term_action))
    return -1;
  return 0;
}


/* Restore signo's action from oldact if its current handler is
   sighandler, return 0 on success, -1 on failure. */
static int
untrap_signal (int signo, struct sigaction *oldact)
{
  struct sigaction act;
  if (sigaction (signo, NULL, &act)) {
    return -1;
  }

  if (act.sa_handler == sighandler) {
    return sigaction (signo, oldact, NULL);
  }

  return 0;
}


/* Undo a previous trap_abnormal_exits(). */
static int
untrap_abnormal_exits (void)
{
#ifdef SIGHUP
  if (untrap_signal (SIGHUP, &saved_hup_action))
    return -1;
#endif /* SIGHUP */
  if (untrap_signal (SIGINT, &saved_int_action))
    return -1;
  if (untrap_signal (SIGTERM, &saved_term_action))
    return -1;
  return 0;
}



typedef struct {
  cleanup_fun fun;
  void *arg;
  int sigsafe;
} slot;

static slot *stack = NULL;	/* stack of cleanup functions */
static unsigned nslots = 0;	/* #slots in stack */
static unsigned tos = 0;	/* top of stack, 0 <= tos <= nslots */

/* Call cleanup functions in stack from from top to bottom,
 * Automatically called on program termination via exit(3) or default
 * action for SIGHUP, SIGINT or SIGTERM.
 * Since this may be called from a signal handler, do not use free().
 * If in_sighandler is true, cleanup functions with sigsafe=0 will not be
 * called.
 */
void
do_cleanups_sigsafe (bool in_sighandler)
{
  unsigned i;

  assert (tos <= nslots);
  for (i = tos; i > 0; --i)
    if (!in_sighandler || stack[i-1].sigsafe)
      stack[i-1].fun (stack[i-1].arg);
}

/* Call cleanup functions in stack from from top to bottom,
 * Automatically called on program termination via exit(3).
 */
void
do_cleanups (void)
{
  do_cleanups_sigsafe (false);
  tos = 0;
  nslots = 0;
  free (stack);
  stack = NULL;
}


/* Push a cleanup function on the cleanup stack,
 * return 0 on success, -1 on failure.
 * Caution: the cleanup function may be called from signal handlers if
 * sigsafe=1. If you just want a convenient atexit() wrapper, pass
 * sigsafe=0.
 */
int
push_cleanup (cleanup_fun fun, void *arg, int sigsafe)
{
  static bool handler_installed = false;

  assert (tos <= nslots);

  if (!handler_installed) {
    if (atexit (do_cleanups))
      return -1;
    handler_installed = true;
  }

  if (tos == nslots) {
    /* stack is full, allocate another slot */
    /* stack is not expected to grow much, otherwise we would double it */
    slot *new_stack;

    if (stack) {
      new_stack = xnrealloc (stack, nslots+1, sizeof (slot));
    } else {
      new_stack = xnmalloc (nslots+1, sizeof (slot));
    }

    if (!new_stack) return -1;
    stack = new_stack;
    ++nslots;
  }

  assert (tos < nslots);
  stack[tos].fun = fun;
  stack[tos].arg = arg;
  stack[tos].sigsafe = sigsafe;
  ++tos;


  trap_abnormal_exits();

  return 0;
}


/* Remove topmost cleanup function from the cleanup stack that matches the
 * given values.
 */
void
pop_cleanup (cleanup_fun fun, void *arg)
{
  unsigned i, j;

  assert (tos > 0);

  for (i = tos; i > 0; --i) {
    if (stack[i-1].fun == fun && stack[i-1].arg == arg) {
      for (j = i; j < tos; ++j)
        stack[j-1] = stack[j];
      --tos;
      break;
    }
  }

  if (tos == 0) untrap_abnormal_exits();
}


/* Pop all cleanup functions from the cleanup stack. */
void
pop_all_cleanups (void)
{
  tos = 0;
  untrap_abnormal_exits();
}
