/***************************************************************************
 *   Copyright (C) 2005-2019 by the Quassel Project                        *
 *   devel@quassel-irc.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) version 3.                                           *
 *                                                                         *
 *   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.         *
 ***************************************************************************/

#include "posixsignalwatcher.h"

#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>

#include <cerrno>
#include <csignal>

#include <QDebug>
#include <QSocketNotifier>

#include "logmessage.h"

int PosixSignalWatcher::_sockpair[2];

PosixSignalWatcher::PosixSignalWatcher(QObject *parent)
    : AbstractSignalWatcher{parent}
{
    if (::socketpair(AF_UNIX, SOCK_STREAM, 0, _sockpair)) {
        qWarning() << "Could not setup POSIX signal watcher:" << ::strerror(errno);
        return;
    }

    _notifier = new QSocketNotifier(_sockpair[1], QSocketNotifier::Read, this);
    connect(_notifier, SIGNAL(activated(int)), this, SLOT(onNotify(int)));
    _notifier->setEnabled(true);

    registerSignal(SIGINT);
    registerSignal(SIGTERM);
    registerSignal(SIGHUP);

#ifdef HAVE_EXECINFO
    // we only handle crashes ourselves if coredumps are disabled
    struct rlimit *limit = (rlimit *)malloc(sizeof(struct rlimit));
    int rc = getrlimit(RLIMIT_CORE, limit);
    if (rc == -1 || !((long)limit->rlim_cur > 0 || limit->rlim_cur == RLIM_INFINITY)) {
        registerSignal(SIGABRT);
        registerSignal(SIGSEGV);
        registerSignal(SIGBUS);
    }
    free(limit);
#endif
}

void PosixSignalWatcher::registerSignal(int signal)
{
    struct sigaction sigact;
    sigact.sa_handler = PosixSignalWatcher::signalHandler;
    sigact.sa_flags = 0;
    sigemptyset(&sigact.sa_mask);
    sigact.sa_flags |= SA_RESTART;
    if (sigaction(signal, &sigact, nullptr)) {
        qWarning() << "Could not register handler for POSIX signal:" << ::strerror(errno);
    }
}

void PosixSignalWatcher::signalHandler(int signal)
{
    auto bytes = ::write(_sockpair[0], &signal, sizeof(signal));
    Q_UNUSED(bytes)
}

void PosixSignalWatcher::onNotify(int sockfd)
{
    int signal;
    auto bytes = ::read(sockfd, &signal, sizeof(signal));
    Q_UNUSED(bytes)
    quInfo() << "Caught signal" << signal;

    switch (signal) {
    case SIGHUP:
        emit handleSignal(Action::Reload);
        break;
    case SIGINT:
    case SIGTERM:
        emit handleSignal(Action::Terminate);
        break;
    case SIGABRT:
    case SIGSEGV:
    case SIGBUS:
        emit handleSignal(Action::HandleCrash);
        break;
    default:
        ;
    }
}
