/*
 * Signal handlers for cHg
 *
 * Copyright 2011, 2018 Yuya Nishihara <yuya@tcha.org>
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2 or any later version.
 */

#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

static pid_t peerpgid = 0;
static pid_t peerpid = 0;

static void forwardsignal(int sig)
{
	assert(peerpid > 0);
	(void)kill(peerpid, sig);
}

static void forwardsignaltogroup(int sig)
{
	/* prefer kill(-pgid, sig), fallback to pid if pgid is invalid */
	pid_t killpid = peerpgid > 1 ? -peerpgid : peerpid;
	(void)kill(killpid, sig);
}

static void handlestopsignal(int sig)
{
	sigset_t unblockset, oldset;
	struct sigaction sa, oldsa;
	if (sigemptyset(&unblockset) < 0) {
		return;
	}
	if (sigaddset(&unblockset, sig) < 0) {
		return;
	}
	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = SIG_DFL;
	sa.sa_flags = SA_RESTART;
	if (sigemptyset(&sa.sa_mask) < 0) {
		return;
	}

	forwardsignal(sig);
	if (raise(sig) < 0) { /* resend to self */
		return;
	}
	if (sigaction(sig, &sa, &oldsa) < 0) {
		return;
	}
	if (sigprocmask(SIG_UNBLOCK, &unblockset, &oldset) < 0) {
		return;
	}
	/* resent signal will be handled before sigprocmask() returns */
	if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0) {
		return;
	}
	if (sigaction(sig, &oldsa, NULL) < 0) {
		return;
	}
}

/*
 * Installs signal handlers.
 *
 * Returns 0 on success, -1 on error and errno is set appropriately.
 * Installed handlers wouldn't be cleaned up on error.
 */
int setupsignalhandler(pid_t pid, pid_t pgid)
{
	if (pid <= 0) {
		errno = EINVAL;
		return -1;
	}
	peerpid = pid;
	peerpgid = (pgid <= 1 ? 0 : pgid);

	struct sigaction sa;
	memset(&sa, 0, sizeof(sa));

	/* deadly signals meant to be sent to a process group:
	 * - SIGHUP: usually generated by the kernel, when termination of a
	 *   process causes that process group to become orphaned
	 * - SIGINT: usually generated by the terminal */
	sa.sa_handler = forwardsignaltogroup;
	sa.sa_flags = SA_RESTART;
	if (sigemptyset(&sa.sa_mask) < 0) {
		return -1;
	}
	if (sigaction(SIGHUP, &sa, NULL) < 0) {
		return -1;
	}
	if (sigaction(SIGINT, &sa, NULL) < 0) {
		return -1;
	}

	/* terminate frontend by double SIGTERM in case of server freeze */
	sa.sa_handler = forwardsignal;
	sa.sa_flags |= SA_RESETHAND;
	if (sigaction(SIGTERM, &sa, NULL) < 0) {
		return -1;
	}

	/* notify the worker about window resize events */
	sa.sa_flags = SA_RESTART;
	if (sigaction(SIGWINCH, &sa, NULL) < 0) {
		return -1;
	}
	/* forward user-defined signals */
	if (sigaction(SIGUSR1, &sa, NULL) < 0) {
		return -1;
	}
	if (sigaction(SIGUSR2, &sa, NULL) < 0) {
		return -1;
	}
	/* propagate job control requests to worker */
	sa.sa_handler = forwardsignal;
	sa.sa_flags = SA_RESTART;
	if (sigaction(SIGCONT, &sa, NULL) < 0) {
		return -1;
	}
	sa.sa_handler = handlestopsignal;
	sa.sa_flags = SA_RESTART;
	if (sigaction(SIGTSTP, &sa, NULL) < 0) {
		return -1;
	}

	return 0;
}

/*
 * Restores signal handlers to the default, and masks SIGINT.
 *
 * Returns 0 on success, -1 on error and errno is set appropriately.
 */
int restoresignalhandler(void)
{
	struct sigaction sa;
	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = SIG_DFL;
	sa.sa_flags = SA_RESTART;
	if (sigemptyset(&sa.sa_mask) < 0) {
		return -1;
	}

	if (sigaction(SIGHUP, &sa, NULL) < 0) {
		return -1;
	}
	if (sigaction(SIGTERM, &sa, NULL) < 0) {
		return -1;
	}
	if (sigaction(SIGWINCH, &sa, NULL) < 0) {
		return -1;
	}
	if (sigaction(SIGCONT, &sa, NULL) < 0) {
		return -1;
	}
	if (sigaction(SIGTSTP, &sa, NULL) < 0) {
		return -1;
	}

	/* ignore Ctrl+C while shutting down to make pager exits cleanly */
	sa.sa_handler = SIG_IGN;
	if (sigaction(SIGINT, &sa, NULL) < 0) {
		return -1;
	}

	peerpid = 0;
	return 0;
}
