mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-06 01:53:25 -08:00
* [Servatrice] Exit non-zero when server initialization fails initServer() reported every startup failure through a zero exit code: the database could not be opened, the server printed nothing of its own, and the process looked like a clean shutdown. Log the two refusal reasons at critical level and return 1 so supervisors and the Docker exit status can tell the difference. * [Servatrice] Keep console logging independent of the logfile logMessage() returned before the console echo unless a logfile was open and server/writelog was true, so --log-to-console produced nothing when server/logfile was empty or could not be opened. That is how a refused database connection could end in a silent exit. Print to the console before the logfile gates, drop the duplicate echo from flushBuffer(), and read writelog/logfilters once in main instead of calling the non-thread-safe global QSettings from every logging thread. * [Servatrice] Uninstall the log handler before tearing down globals The handler reaches into the global logger and settingsCache. Leaving it installed while main deletes those objects turns any Qt warning emitted during QCoreApplication/QFactoryLoader teardown into a use-after-free; with QT_DEBUG_PLUGINS=1 that warning exists, and Servatrice died with SIGSEGV in QSettings::value() on the way out. * [Servatrice] Buffer console logs on the logger thread Address review feedback on the startup failure change: - Append console lines to a buffer drained by the logger thread, so a stalled stdout reader cannot block game threads. - Report islServer->errorString() instead of the literal call text. - Let the constructor reuse reloadConfig() instead of parsing the filter list twice. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
243 lines
8.2 KiB
C++
243 lines
8.2 KiB
C++
/***************************************************************************
|
|
* Copyright (C) 2008 by Max-Wilhelm Bruker *
|
|
* brukie@laptop *
|
|
* *
|
|
* 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) 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, write to the *
|
|
* Free Software Foundation, Inc., *
|
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
***************************************************************************/
|
|
|
|
#include "libcockatrice/rng/rng_abstract.h"
|
|
#include "servatrice.h"
|
|
#include "server_logger.h"
|
|
#include "settingscache.h"
|
|
#include "signalhandler.h"
|
|
#include "smtpclient.h"
|
|
#include "version_string.h"
|
|
|
|
#include <QCommandLineOption>
|
|
#include <QCommandLineParser>
|
|
#include <QCoreApplication>
|
|
#include <QDateTime>
|
|
#include <QDebug>
|
|
#include <QFile>
|
|
#include <QList>
|
|
#include <QMetaObject>
|
|
#include <QMetaType>
|
|
#include <QObject>
|
|
#include <QSharedPointer>
|
|
#include <QString>
|
|
#include <QThread>
|
|
#include <QVariant>
|
|
#include <QVector>
|
|
#include <cstdlib>
|
|
#include <google/protobuf/stubs/common.h>
|
|
#include <iostream>
|
|
#include <libcockatrice/rng/rng_sfmt.h>
|
|
#include <libcockatrice/utility/cryptoutil.h>
|
|
#include <libcockatrice/utility/passwordhasher.h>
|
|
#include <qlogging.h>
|
|
#include <qnamespace.h>
|
|
#include <string>
|
|
|
|
RNG_Abstract *rng;
|
|
ServerLogger *logger;
|
|
QThread *loggerThread;
|
|
SettingsCache *settingsCache;
|
|
SignalHandler *signalhandler;
|
|
SmtpClient *smtpClient;
|
|
|
|
/* Prototypes */
|
|
|
|
void testRNG();
|
|
void testHash();
|
|
void myMessageOutput(QtMsgType type, const QMessageLogContext &, const QString &msg);
|
|
void myMessageOutput2(QtMsgType type, const QMessageLogContext &, const QString &msg);
|
|
|
|
/* Implementations */
|
|
|
|
void testRNG()
|
|
{
|
|
const int n = 500000;
|
|
std::cerr << "Testing random number generator (n = " << n << " * bins)..." << std::endl;
|
|
|
|
const int min = 1;
|
|
const int minMax = 2;
|
|
const int maxMax = 10;
|
|
|
|
QVector<QVector<int>> numbers(maxMax - minMax + 1);
|
|
QVector<double> chisq(maxMax - minMax + 1);
|
|
for (int max = minMax; max <= maxMax; ++max) {
|
|
numbers[max - minMax] = rng->makeNumbersVector(n * (max - min + 1), min, max);
|
|
chisq[max - minMax] = rng->testRandom(numbers[max - minMax]);
|
|
}
|
|
for (int i = 0; i <= maxMax - min; ++i) {
|
|
std::cerr << (min + i);
|
|
for (auto &number : numbers) {
|
|
if (i < number.size()) {
|
|
std::cerr << "\t" << number[i];
|
|
} else {
|
|
std::cerr << "\t";
|
|
}
|
|
}
|
|
std::cerr << std::endl;
|
|
}
|
|
std::cerr << std::endl << "Chi^2 =";
|
|
for (double j : chisq) {
|
|
std::cerr << "\t" << QString::number(j, 'f', 3).toStdString();
|
|
}
|
|
std::cerr << std::endl << "k =";
|
|
for (int j = 0; j < chisq.size(); ++j) {
|
|
std::cerr << "\t" << (j - min + minMax);
|
|
}
|
|
std::cerr << std::endl << std::endl;
|
|
}
|
|
|
|
void testHash()
|
|
{
|
|
const int n = 5000;
|
|
std::cerr << "Benchmarking password hash function (n =" << n << ")..." << std::endl;
|
|
QDateTime startTime = QDateTime::currentDateTime();
|
|
for (int i = 0; i < n; ++i) {
|
|
PasswordHasher::computeHash("aaaaaa", "aaaaaaaaaaaaaaaa");
|
|
}
|
|
QDateTime endTime = QDateTime::currentDateTime();
|
|
std::cerr << startTime.secsTo(endTime) << "secs" << std::endl;
|
|
}
|
|
|
|
void myMessageOutput(QtMsgType /*type*/, const QMessageLogContext &, const QString &msg)
|
|
{
|
|
logger->logMessage(msg);
|
|
}
|
|
|
|
void myMessageOutput2(QtMsgType /*type*/, const QMessageLogContext &, const QString &msg)
|
|
{
|
|
logger->logMessage(msg);
|
|
std::cerr << msg.toStdString() << std::endl;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
QCoreApplication app(argc, argv);
|
|
QCoreApplication::setOrganizationName("Cockatrice");
|
|
QCoreApplication::setApplicationName("Servatrice");
|
|
QCoreApplication::setApplicationVersion(VERSION_STRING);
|
|
|
|
QCommandLineParser parser;
|
|
parser.addHelpOption();
|
|
parser.addVersionOption();
|
|
|
|
QCommandLineOption testRandomOpt("test-random", "Test PRNG (chi^2)");
|
|
parser.addOption(testRandomOpt);
|
|
|
|
QCommandLineOption testHashFunctionOpt("test-hash", "Test password hash function");
|
|
parser.addOption(testHashFunctionOpt);
|
|
|
|
QCommandLineOption logToConsoleOpt("log-to-console", "Write server logs to console");
|
|
parser.addOption(logToConsoleOpt);
|
|
|
|
QCommandLineOption configPathOpt("config", "Read server configuration from <file>", "file", "");
|
|
parser.addOption(configPathOpt);
|
|
|
|
parser.process(app);
|
|
|
|
bool testRandom = parser.isSet(testRandomOpt);
|
|
bool testHashFunction = parser.isSet(testHashFunctionOpt);
|
|
bool logToConsole = parser.isSet(logToConsoleOpt);
|
|
QString configPath = parser.value(configPathOpt);
|
|
|
|
qRegisterMetaType<QList<int>>("QList<int>");
|
|
|
|
if (configPath.isEmpty()) {
|
|
configPath = SettingsCache::guessConfigurationPath();
|
|
} else if (!QFile::exists(configPath)) {
|
|
qCritical() << "Could not find configuration file at" << configPath;
|
|
return EXIT_FAILURE;
|
|
}
|
|
qWarning() << "Using configuration file: " << configPath;
|
|
settingsCache = new SettingsCache(configPath);
|
|
|
|
loggerThread = new QThread;
|
|
loggerThread->setObjectName("logger");
|
|
logger = new ServerLogger(logToConsole, settingsCache->value("server/writelog", 1).toBool(),
|
|
settingsCache->value("server/logfilters").toString());
|
|
logger->moveToThread(loggerThread);
|
|
|
|
loggerThread->start();
|
|
QMetaObject::invokeMethod(logger, "startLog", Qt::BlockingQueuedConnection,
|
|
Q_ARG(QString, settingsCache->value("server/logfile", QString("server.log")).toString()));
|
|
|
|
if (logToConsole) {
|
|
qInstallMessageHandler(myMessageOutput);
|
|
} else {
|
|
qInstallMessageHandler(myMessageOutput2);
|
|
}
|
|
|
|
signalhandler = new SignalHandler();
|
|
|
|
rng = new RNG_SFMT(CryptoUtil::randomUInt64());
|
|
|
|
std::cerr << "Servatrice " << VERSION_STRING << " starting." << std::endl;
|
|
std::cerr << "-------------------------" << std::endl;
|
|
|
|
if (testRandom) {
|
|
testRNG();
|
|
}
|
|
if (testHashFunction) {
|
|
testHash();
|
|
}
|
|
if (testRandom || testHashFunction) {
|
|
return 0;
|
|
}
|
|
|
|
smtpClient = new SmtpClient();
|
|
|
|
auto *server = new Servatrice();
|
|
QObject::connect(server, SIGNAL(destroyed()), &app, SLOT(quit()), Qt::QueuedConnection);
|
|
int retval = 0;
|
|
if (server->initServer()) {
|
|
std::cerr << "-------------------------" << std::endl;
|
|
std::cerr << "Server initialized." << std::endl;
|
|
|
|
qInstallMessageHandler(myMessageOutput);
|
|
|
|
retval = QCoreApplication::exec();
|
|
|
|
std::cerr << "Server quit." << std::endl;
|
|
std::cerr << "-------------------------" << std::endl;
|
|
} else {
|
|
// initServer() logged the reason; a failed startup must not look like a clean exit.
|
|
std::cerr << "Server initialization failed, exiting." << std::endl;
|
|
retval = EXIT_FAILURE;
|
|
}
|
|
|
|
// The handler reads the logger and settingsCache globals torn down below; leaving it
|
|
// installed lets Qt's own shutdown warnings dereference freed memory.
|
|
qInstallMessageHandler(nullptr);
|
|
|
|
delete smtpClient;
|
|
delete rng;
|
|
delete signalhandler;
|
|
delete settingsCache;
|
|
|
|
logger->deleteLater();
|
|
loggerThread->wait();
|
|
delete loggerThread;
|
|
|
|
// Delete all global objects allocated by libprotobuf.
|
|
google::protobuf::ShutdownProtobufLibrary();
|
|
|
|
QCoreApplication::quit();
|
|
return retval;
|
|
}
|