/*
 * Copyright (C) 2014-2024 CZ.NIC
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations including
 * the two.
 */

#include <cstdlib>
#include <signal.h>
#include <pthread.h>
#include <QThread>

#include "src/datovka_shared/log/log.h"
#include "src/global.h" /* GlobInstcs::req_halt */
#include "src/system/unix_signals.h"

#undef HAVE_PTHREAD_MUTEXATTR_SETROBUST

class SignalCatcherThread : public QThread {
public:
	SignalCatcherThread(void)
	    : QThread()
	{ }

	virtual
	void run(void) Q_DECL_OVERRIDE;
};

static sigset_t setint; /*!< Set of signals to be handled. */
static SignalCatcherThread thread; /*!< Signal handling thread. */

/*!
 * @brief Handles incoming signals.
 *
 * @param[in] sig Signal number.
 */
static
void signal_handler(int sig)
{
	switch (sig) {
	case SIGHUP: /* Reload configuration. */
		//GlobInstcs::req_reload = 1;
		break;
	case SIGINT:
	//case SIGQUIT:
	case SIGUSR1:
	case SIGUSR2:
	//case SIGALRM:
	case SIGTERM: /* Stop. */
		GlobInstcs::req_halt = 1;
		break;
	case SIGPIPE: /* Ignore. */
		break;
	default:
		break;
	}

#ifndef HAVE_PTHREAD_MUTEXATTR_SETROBUST
#endif /* !HAVE_PTHREAD_MUTEXATTR_SETROBUST */
}

#ifndef HAVE_PTHREAD_MUTEXATTR_SETROBUST
/*!
 * @brief Does nothing.
 */
static
void empty_signal_handler(int sig)
{
	Q_UNUSED(sig);
}
#endif /* !HAVE_PTHREAD_MUTEXATTR_SETROBUST */

/*!
 * @brief Handles incoming signals.
 *
 * @note This function is not an actual signal handler as specified and used by
 *     man 2 signal, man 2 sigaction
 *     therefore it can use other than async-signal-safe functions
 *     man 7 signal, man 7 signal-safety .
 *
 * @param[in] data Data passed into the thread.
 */
static
void *signal_handler_thread_function(void *data)
{
	Q_UNUSED(data);
	int sig;

	for (;;) {
		if (sigwait(&setint, &sig) == 0) {
			logInfoNL("Caught signal number %d.", sig);
			signal_handler(sig);
			if (GlobInstcs::req_halt) {
				/* Exit signal handler thread on halt. */
				logInfoNL("%s", "Indicating a requested halt.");
				return NULL;
			}
		} else {
			logErrorNL("%s", "sigwait: error");
		}
	}

	return NULL;
}

void SignalCatcherThread::run(void)
{
	signal_handler_thread_function(NULL);
}

/*!
 * @brief Terminate and/or wait for signal handler thread to join.
 */
static
void terminate_signal_handler_thread(void)
{
	/* QThread::wait() is similar to POSIX pthread_join(). */
	if (Q_UNLIKELY(!thread.wait(500))) {
		thread.terminate();
		thread.wait();
	}
}

int install_signal_handlers(void)
{
	/* Set of signals to be handled by the application. */
	sigemptyset(&setint);
	sigaddset(&setint, SIGHUP);
	sigaddset(&setint, SIGINT);
	sigaddset(&setint, SIGUSR1);
	sigaddset(&setint, SIGUSR2);
	sigaddset(&setint, SIGTERM);
	sigaddset(&setint, SIGPIPE); /*
	                              * Reloading the configuration file
	                              * may cause to receive broken pipe.
	                              */

#ifndef HAVE_PTHREAD_MUTEXATTR_SETROBUST
	sigset_t sset;
	struct sigaction new_action;

	/*
	 * By default are the reader threads set to be cancellable and are
	 * terminated by calling pthread_cancel(). This approach requires the
	 * mutexes in the log facility to be able to recover from a locked
	 * state when the locking thread suddenly terminates. This is achieved
	 * by using robust mutexes.
	 * Regrettably, robust mutexes are not implemented in all systems.
	 * The default fall-back solution is to use a shared variable
	 * to signal a stop condition in combination with sending SIGALRM to
	 * the terminated thread in order to interrupt its blocking I/O.
	 */
	new_action.sa_handler = empty_signal_handler;
	sigemptyset(&new_action.sa_mask);
	new_action.sa_flags = 0;
	if (sigaction(SIGALRM, &new_action, NULL) != 0) {
		return -1;
	}

	sigemptyset(&sset);
	sigaddset(&sset, SIGALRM);
	pthread_sigmask(SIG_UNBLOCK, &sset, NULL);
#endif /* !HAVE_PTHREAD_MUTEXATTR_SETROBUST */

	/* Block the signals. */
	if (pthread_sigmask(SIG_BLOCK, &setint, NULL) != 0) {
		return -1;
	}

	thread.start();

	/* Must terminate and join the signal handler thread. */
	if (atexit(terminate_signal_handler_thread) != 0) {
		return -1;
	}

	return 0;
}
