Commit 907c2d3c authored by Johannes Schwab's avatar Johannes Schwab

server: Get rid of D-Bus for now and use the more basic SIGUSR1 approach

parent 22c437e5
......@@ -10,7 +10,7 @@ Other features are:
The client currently runs at Android, Linux and Windows.
The server only runs on Linux. It needs a mysql compatilbe database and a D-Bus daemon and is configured with an ini style file like this:
The server only runs on Linux. It needs a mysql compatible database and is configured with an ini style file like this:
[general]
;number of jobs to run in the background (>=1) (optional, default: 2)
......@@ -34,7 +34,7 @@ port=3306
You also need to apply the latest db schema from server/dbSchema to the database you intend to use.
While the server is running, you can get some information with the --info command line argument.
While the server is running, he will print out some status information when receiving SIGUSR1.
To build from source you will need:
- Qt >= 5.10
......
TEMPLATE = app
CONFIG += console
TARGET = openrecipesserver
QT += core network sql dbus
QT += core network sql
QT -= gui
include( ../linking.pri )
include( ../common.pri )
......@@ -9,7 +9,6 @@ include( ../common.pri )
SOURCES += \
src/Async.cpp \
src/BackendWorker.cpp \
src/DBusAdaptor.cpp \
src/Request.cpp \
src/RequestParser.cpp \
src/SecureServerConnection.cpp \
......@@ -22,7 +21,6 @@ SOURCES += \
HEADERS += \
src/Async.h \
src/BackendWorker.h \
src/DBusAdaptor.h \
src/DbItem.h \
src/HelperDefs.h \
src/Request.h \
......
......@@ -20,7 +20,7 @@
#include "TcpServer.h"
struct Config {
bool printInfo, runInForeground;
bool runInForeground;
QString configFile;
unsigned int jobs;
SqlBackend::Config sql;
......
/*
* OpenRecipes
* Copyright (C) 2018 Johannes Schwab
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "DBusAdaptor.h"
#include "Config.h"
DBusAdaptor::DBusAdaptor(QObject *parent, const Config &conf)
:
QDBusAbstractAdaptor(parent),
port(conf.server.port)
{}
QString DBusAdaptor::getPublicKey() const {
qDebug() << "DBusAdapter::getPublicKey()";
const QByteArray pubKey = SqlBackend::getSyncPublicKey();
return QString::fromLatin1(pubKey.toHex()).toUpper();
}
ushort DBusAdaptor::getPort() const {
qDebug() << "DBusAdapter::getPort()";
return port;
}
constexpr char DBusAdaptor::serviceName[];
/*
* OpenRecipes
* Copyright (C) 2018 Johannes Schwab
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef D_BUS_ADAPTOR_H
#define D_BUS_ADAPTOR_H
#include <QtDBus>
struct Config;
class DBusAdaptor : public QDBusAbstractAdaptor {
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.jschwab.openrecipesserver.info");
Q_PROPERTY(QString publicKey READ getPublicKey)
Q_PROPERTY(ushort port READ getPort)
private:
const quint16 port;
public:
DBusAdaptor(QObject *parent, const Config &conf);
QString getPublicKey() const;
ushort getPort() const;
static constexpr char serviceName[] = "org.jschwab.openrecipesserver";
};
#endif //D_BUS_ADAPTOR_H
......@@ -204,5 +204,14 @@ void SqlBackend::addSyncKey(const QByteArray &id, const QByteArray &cipher, cons
tryExec(query);
}
unsigned long SqlBackend::getNumberOfTodaysUsers() {
QSqlQuery query = getQuery();
query.prepare("SELECT COUNT(*) FROM users "
"WHERE lastLogin = DATE(NOW());");
tryExec(query);
if (!query.next()) throw SqlNoResult();
return query.value(0).value<unsigned long>();
}
std::unique_ptr<QTimer> SqlBackend::cleanupTimer;
SqlBackend::Config SqlBackend::conf;
......@@ -54,6 +54,7 @@ class SqlBackend : public SqlBackendBase {
static std::vector<DbItem> getChanged(quint64 timestamp, quint64 userId);
static std::pair<QByteArray, QByteArray> getAndDelSyncKey(const QByteArray &id);
static void addSyncKey(const QByteArray &id, const QByteArray &cipher, const QByteArray &nonce);
static unsigned long getNumberOfTodaysUsers();
};
#endif //SQL_BACKEND
......@@ -268,4 +268,8 @@ void TcpConnection::onGotSyncKey(const GetSyncKeyRes &res) {
connection->write(res.second.second);
}
size_t TcpConnection::getNumberOfLoggedInUsers() {
return loggedInUsers.size();
}
std::unordered_set<quint64> TcpConnection::loggedInUsers;
......@@ -49,6 +49,7 @@ class TcpConnection : public QObject {
public:
TcpConnection(QObject *parent, QTcpSocket *socket, quint64 startupTime);
~TcpConnection();
static size_t getNumberOfLoggedInUsers();
public slots:
void onUserLoggedIn(quint64 userId);
......
......@@ -29,6 +29,7 @@ TcpServer::TcpServer(const TcpServer::Config &conf)
startupTime(std::time(nullptr)),
closingGently(false)
{
if (startupTime == -1) throw IERROR("Can't get the time");
const quint16 port = conf.port;
tcpServer->listen(QHostAddress::Any, port);
connect(tcpServer, &QTcpServer::newConnection, this, &TcpServer::onNewConnection);
......@@ -36,6 +37,7 @@ TcpServer::TcpServer(const TcpServer::Config &conf)
}
void TcpServer::onNewConnection() {
static_assert(sizeof(quint64) >= sizeof(time_t));
TcpConnection *con = new TcpConnection(this, tcpServer->nextPendingConnection(), startupTime);
connect(con, &TcpConnection::closed, this, &TcpServer::onTcpConnectionClosed, Qt::QueuedConnection);
}
......@@ -55,3 +57,22 @@ void TcpServer::onTcpConnectionClosed() {
}
}
}
void TcpServer::onPrintInfo() {
QString publicKey;
unsigned long numberOfTodaysUsers;
try {
publicKey = QString::fromLatin1(SqlBackend::getSyncPublicKey().toHex()).toUpper();
numberOfTodaysUsers = SqlBackend::getNumberOfTodaysUsers();
} catch (const SqlNoResult &e) {
qWarning() << "Error while gathering infos";
return;
}
qInfo() << "OpenRecipesServer state:\n"
"\t-Running since" << asctime(localtime(&startupTime)) << "\n"
"\t-Listening on port" << tcpServer->serverPort() << "\n"
"\t-Public key is" << publicKey << "\n"
"\t-At the moment" << TcpConnection::getNumberOfLoggedInUsers() << "users are logged in\n"
"\t-Today" << numberOfTodaysUsers << "users have been active\n";
}
......@@ -33,7 +33,7 @@ class TcpServer : public QObject {
private:
QTcpServer *tcpServer;
const quint64 startupTime;
const time_t startupTime;
bool closingGently;
private slots:
......@@ -45,6 +45,7 @@ class TcpServer : public QObject {
public slots:
void onCloseGently();
void onPrintInfo();
signals:
void closed();
......
......@@ -24,16 +24,30 @@
#include <unistd.h>
#include <csignal>
#include <cstring>
#include <cassert>
UnixSignalHandler::UnixSignalHandler() {
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sigFd) != 0) throw IERROR("Can't create sockets");
QSocketNotifier *sigNot = new QSocketNotifier(sigFd[1], QSocketNotifier::Read, this);
connect(sigNot, &QSocketNotifier::activated, this, [this, sigNot](int fd) {
sigNot->setEnabled(false);
int tmp;
read(fd, &tmp, sizeof(tmp));
qDebug() << "Handling signal:" << strsignal(tmp);
emit stopRequested();
int sig;
if (read(fd, &sig, sizeof(sig)) != sizeof(sig)) {
qWarning() << "Can't read signal from socket";
return;
}
qInfo() << "Received signal" << strsignal(sig);
switch (sig) {
case SIGTERM:
case SIGINT:
emit stopRequested();
break;
case SIGUSR1:
emit printInfo();
break;
default:
assert(false);
}
sigNot->setEnabled(true);
});
......@@ -43,11 +57,12 @@ UnixSignalHandler::UnixSignalHandler() {
siga.sa_flags |= SA_RESTART;
if (sigaction(SIGTERM, &siga, nullptr) != 0) throw IERROR("Can't set signal hanlder");
if (sigaction(SIGINT, &siga, nullptr) != 0) throw IERROR("Can't set signal hanlder");
if (sigaction(SIGUSR1, &siga, nullptr) != 0) throw IERROR("Can't set signal hanlder");
signal(SIGHUP, SIG_IGN);
}
void UnixSignalHandler::sigHandler(int sig) {
qInfo() << "Got signal" << strsignal(sig) << ", stopping...";
write(sigFd[0], &sig, sizeof(sig));
if (write(sigFd[0], &sig, sizeof(sig)) != sizeof(sig)) qWarning() << "Can't write signal" << strsignal(sig) << "to socket";
}
int UnixSignalHandler::sigFd[2];
......@@ -34,6 +34,7 @@ class UnixSignalHandler : public QObject {
signals:
void stopRequested();
void printInfo();
};
#endif //UNIX_SIGNAL_HANDLER_H
......@@ -22,7 +22,6 @@
#include "TcpServer.h"
#include "UnixSignalHandler.h"
#include "Config.h"
#include "DBusAdaptor.h"
#include <InternalError.h>
#include <config.h>
......@@ -54,7 +53,7 @@ void (*argp_program_version_hook) (FILE*, struct argp_state*) = printVersion;
const char *argp_program_bug_address = "<[email protected]>";
static error_t parseCmdLineHelper(int key, char *arg, struct argp_state *state) {
static error_t parseCmdLine(int key, char *arg, struct argp_state *state) {
Config *opts = reinterpret_cast<Config*>(state->input);
switch (key) {
case 'f':
......@@ -63,9 +62,6 @@ static error_t parseCmdLineHelper(int key, char *arg, struct argp_state *state)
case 'c':
opts->configFile = arg;
break;
case 'i':
opts->printInfo = true;
break;
default:
return ARGP_ERR_UNKNOWN;
}
......@@ -84,7 +80,7 @@ static void daemonize() {
pid = fork();
if (pid < 0) exit(EXIT_FAILURE);
if (pid != 0) exit(EXIT_SUCCESS);
chdir("/");
if (chdir("/") != 0) exit(EXIT_FAILURE);
umask(0);
for (size_t i = sysconf(_SC_OPEN_MAX); i > 0; --i) close(i);
}
......@@ -112,24 +108,18 @@ static void syslogMessageHandler(QtMsgType type, const QMessageLogContext &conte
syslog(pri, "%s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
}
static Config parseCmdLine(int argc, char *argv[]) {
static Config getConfig(int argc, char *argv[]) {
Config conf;
struct argp_option options[] = {
{"foreground", 'f', nullptr, 0, "Don't daemonize", 0},
{"config", 'c', "FILE", 0, "Config file (default: /etc/conf.d/openrecipesserver)", 0},
{"info", 'i', nullptr, 0, "Print info about a running server", 0},
{"config", 'c', "FILE", 0, "Config file (default: /etc/openrecipesserver)", 0},
{nullptr, 0, nullptr, 0, nullptr, 0}
};
struct argp argp = {options, parseCmdLineHelper, nullptr, nullptr, nullptr, nullptr, nullptr};
struct argp argp = {options, parseCmdLine, nullptr, nullptr, nullptr, nullptr, nullptr};
conf.runInForeground = false;
conf.configFile = "/etc/conf.d/openrecipesserver";
conf.printInfo = false;
conf.configFile = "/etc/openrecipesserver";
argp_parse(&argp, argc, argv, 0, 0, &conf);
return conf;
}
static void parseConfigFile(Config &conf) {
qDebug() << "Using config file" << conf.configFile;
if (access(conf.configFile.toStdString().c_str(), F_OK) == -1) {
std::cerr << "Can't access config file " << conf.configFile.toStdString() << "\n";
......@@ -170,31 +160,13 @@ static void parseConfigFile(Config &conf) {
std::cerr << "Invalid number for general/jobs. Must be >= 1\n";
exit(EXIT_FAILURE);
}
}
static int printInfo(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
if (!QDBusConnection::sessionBus().isConnected()) {
qCritical() << "Can't connect to D-Bus";
exit(EXIT_FAILURE);
}
QDBusInterface *iface = new QDBusInterface(DBusAdaptor::serviceName, "/", "org.jschwab.openrecipesserver.info", QDBusConnection::sessionBus());
if (!iface->isValid()) {
qCritical() << "Can't open D-Bus interface:" << QDBusConnection::sessionBus().lastError().message();
qCritical() << "Maybe the server isn't running?";
delete iface;
exit(EXIT_FAILURE);
}
QTimer::singleShot(0, &app, [&app, &iface]() {
qInfo() << "OpenRecipesServer is listening on port" << iface->property("port").value<ushort>();
qInfo() << "The public key is" << iface->property("publicKey").toString();
delete iface;
QTimer::singleShot(0, &app, &QCoreApplication::quit);
});
return app.exec();
return conf;
}
int runServer(int argc, char *argv[], const Config &conf) {
int main(int argc, char *argv[]) {
Config conf = getConfig(argc, argv);
qInfo() << "Starting OpenRecipesServer with config file" << conf.configFile;
if (conf.runInForeground) {
qInfo() << "Staying in foreground";
......@@ -244,6 +216,7 @@ int runServer(int argc, char *argv[], const Config &conf) {
UnixSignalHandler sigHandler; //TODO this may throw
TcpServer server(conf.server);
QObject::connect(&sigHandler, &UnixSignalHandler::stopRequested, &server, &TcpServer::onCloseGently);
QObject::connect(&sigHandler, &UnixSignalHandler::printInfo, &server, &TcpServer::onPrintInfo);
for (auto &w : bw) {
QObject::connect(&server, &TcpServer::closed, w.get(), &BackendWorker::onClose);
QObject::connect(
......@@ -260,20 +233,6 @@ int runServer(int argc, char *argv[], const Config &conf) {
);
}
new DBusAdaptor(&server, conf);
QDBusConnection::sessionBus().registerObject("/", &server);
if (!QDBusConnection::sessionBus().registerService(DBusAdaptor::serviceName)) {
qCritical() << "Can't register to D-Bus:" << QDBusConnection::sessionBus().lastError().message();
exit(EXIT_FAILURE);
}
qDebug() << "Starting event loop...";
return app.exec();
}
int main(int argc, char *argv[]) {
Config conf = parseCmdLine(argc, argv);
if (conf.printInfo) return printInfo(argc, argv);
parseConfigFile(conf);
return runServer(argc, argv, conf);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment