fix(platform): handle terminating POSIX signals

Add PosixSignalNotifier utility class to covert POSIX signals to
Qt signals and allow qTox to quit appropriately on Linux, FreeBSD
and macOS.

Might protect from data corruption bugs when application is being
terminated during I/O.

Fixes: #4470
reviewable/pr4581/r7
Vincas Dargis 2017-08-19 15:35:54 +03:00
parent e606d3cb55
commit 32b97cb927
4 changed files with 201 additions and 0 deletions

View File

@ -450,6 +450,13 @@ elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
)
endif()
if (UNIX)
set(${PROJECT_NAME}_SOURCES ${${PROJECT_NAME}_SOURCES}
src/platform/posixsignalnotifier.cpp
src/platform/posixsignalnotifier.h
)
endif()
if (PLATFORM_EXTENSIONS)
set(${PROJECT_NAME}_SOURCES ${${PROJECT_NAME}_SOURCES}
src/platform/autorun.h

View File

@ -46,6 +46,10 @@
#include "platform/install_osx.h"
#endif
#if defined(Q_OS_UNIX)
#include "platform/posixsignalnotifier.h"
#endif
#ifdef LOG_TO_FILE
static QAtomicPointer<FILE> logFileFile = nullptr;
static QList<QByteArray>* logBuffer =
@ -145,6 +149,17 @@ int main(int argc, char* argv[])
qInstallMessageHandler(logMessageHandler);
QApplication* a = new QApplication(argc, argv);
#if defined(Q_OS_UNIX)
// PosixSignalNotifier is used only for terminating signals,
// so it's connected directly to quit() without any filtering.
QObject::connect(&PosixSignalNotifier::globalInstance(),
&PosixSignalNotifier::activated,
a,
&QApplication::quit);
PosixSignalNotifier::watchCommonTerminatingSignals();
#endif
a->setApplicationName("qTox");
a->setOrganizationName("Tox");
a->setApplicationVersion("\nGit commit: " + QString(GIT_VERSION));

View File

@ -0,0 +1,126 @@
/*
Copyright © 2017 by The qTox Project Contributors
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre 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.
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#include "posixsignalnotifier.h"
#include <QDebug>
#include <QSocketNotifier>
#include <array>
#include <atomic>
#include <errno.h>
#include <signal.h> // sigaction()
#include <sys/socket.h> // socketpair()
#include <sys/types.h> // may be needed for BSD
#include <unistd.h> // close()
/**
* @class PosixSignalNotifier
* @brief Class for converting POSIX signals to Qt signals
*/
namespace detail {
static std::atomic_flag g_signalSocketUsageFlag{ATOMIC_FLAG_INIT};
static std::array<int, 2> g_signalSocketPair;
static void signalHandler(int signum)
{
// DO NOT call any Qt functions directly, only limited amount of so-called async-signal-safe
// functions can be called in signal handlers.
// See https://doc.qt.io/qt-4.8/unix-signals.html
// If test_and_set() returns true, it means it was already in use (only by ~PosixSignalNotifier()),
// so we bail out. Our signal handler is blocking, only one will be called (no race between
// threads), hence simple implementation.
if (g_signalSocketUsageFlag.test_and_set())
return;
::write(g_signalSocketPair[0], &signum, sizeof(signum));
g_signalSocketUsageFlag.clear();
}
} // namespace detail
PosixSignalNotifier::~PosixSignalNotifier()
{
while (detail::g_signalSocketUsageFlag.test_and_set()) {
// spin-loop until we aquire flag (signal handler might be running and have flag in use)
}
// do not leak sockets
::close(detail::g_signalSocketPair[0]);
::close(detail::g_signalSocketPair[1]);
// do not clear the usage flag here, signal handler cannot use socket any more!
}
void PosixSignalNotifier::watchSignal(int signum)
{
sigset_t blockMask;
sigemptyset(&blockMask); // do not prefix with ::, it's a macro on macOS
sigaddset(&blockMask, signum); // do not prefix with ::, it's a macro on macOS
struct sigaction action = {}; // all zeroes by default
action.sa_handler = detail::signalHandler;
action.sa_mask = blockMask; // allow old signal to finish before new is raised
if (::sigaction(signum, &action, 0)) {
qFatal("Failed to setup signal %d, error = %d", signum, errno);
}
}
void PosixSignalNotifier::watchSignals(std::initializer_list<int> signalSet)
{
for (auto s: signalSet) {
watchSignal(s);
}
}
void PosixSignalNotifier::watchCommonTerminatingSignals()
{
watchSignals({SIGHUP, SIGINT, SIGQUIT, SIGTERM});
}
PosixSignalNotifier& PosixSignalNotifier::globalInstance()
{
static PosixSignalNotifier instance;
return instance;
}
void PosixSignalNotifier::onSignalReceived()
{
int signum{0};
::read(detail::g_signalSocketPair[1], &signum, sizeof(signum));
qDebug() << "Signal" << signum << "received";
emit activated(signum);
}
PosixSignalNotifier::PosixSignalNotifier()
{
if (::socketpair(AF_UNIX, SOCK_STREAM, 0, detail::g_signalSocketPair.data())) {
qFatal("Failed to create socket pair, error = %d", errno);
}
notifier = new QSocketNotifier(detail::g_signalSocketPair[1], QSocketNotifier::Read, this);
connect(notifier, &QSocketNotifier::activated, this, &PosixSignalNotifier::onSignalReceived);
}

View File

@ -0,0 +1,53 @@
/*
Copyright © 2017 by The qTox Project Contributors
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre 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.
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef POSIXSIGNALNOTIFIER_H
#define POSIXSIGNALNOTIFIER_H
#include <QObject>
class QSocketNotifier;
class PosixSignalNotifier : public QObject
{
Q_OBJECT
public:
~PosixSignalNotifier();
static void watchSignal(int signum);
static void watchSignals(std::initializer_list<int> signalSet);
static void watchCommonTerminatingSignals();
static PosixSignalNotifier& globalInstance();
signals:
void activated(int signal);
private slots:
void onSignalReceived();
private:
PosixSignalNotifier();
private:
QSocketNotifier* notifier{nullptr};
};
#endif // POSIXSIGNALNOTIFIER_H