/*
 * 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 <assert.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h> /* SunOS's losing assert.h needs it */
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "xalloc.h"

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

/* 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, const 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, const 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 ();
}
