Files
face-unlock/src/daemon/main.cpp
T

112 lines
4.2 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
//
// face-unlockd: the part that runs as root.
//
// It owns the camera and the face data, and it is the only thing that does.
// Everything else (the lock screen agent, sudo, the setup window, the menu)
// asks it over one socket. systemd starts it on the first connection and it
// exits again after a minute with nothing to do, so most of the time it is not
// running at all.
#include "server.h"
#include "buildconfig.h"
#include <QCommandLineParser>
#include <QCoreApplication>
#include <QSocketNotifier>
#include <csignal>
#include <sys/signalfd.h>
#include <sys/stat.h>
#include <unistd.h>
namespace
{
// sd_listen_fds(), without linking libsystemd for one function.
int systemdSocket()
{
const QByteArray pid = qgetenv("LISTEN_PID");
const QByteArray fds = qgetenv("LISTEN_FDS");
if (pid.isEmpty() || fds.isEmpty() || pid.toLongLong() != getpid() || fds.toInt() < 1) {
return -1;
}
qunsetenv("LISTEN_PID");
qunsetenv("LISTEN_FDS");
qunsetenv("LISTEN_FDNAMES");
return 3;
}
// SIGTERM from systemd is the normal way this ends. The camera thread is
// cancelled and joined on the way out rather than being cut off mid-frame.
void quitOnSignals(QCoreApplication &app)
{
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGTERM);
sigaddset(&mask, SIGINT);
sigprocmask(SIG_BLOCK, &mask, nullptr);
const int fd = signalfd(-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK);
if (fd < 0) {
return;
}
auto *notifier = new QSocketNotifier(fd, QSocketNotifier::Read, &app);
QObject::connect(notifier, &QSocketNotifier::activated, &app, [fd] {
signalfd_siginfo info;
while (read(fd, &info, sizeof(info)) > 0) {
}
QCoreApplication::quit();
});
}
} // namespace
int main(int argc, char **argv)
{
// Face data and state are for root's eyes only, whatever creates them.
umask(077);
QCoreApplication app(argc, argv);
app.setApplicationName(QStringLiteral("face-unlockd"));
app.setApplicationVersion(QStringLiteral(FU_VERSION));
qSetMessagePattern(QStringLiteral("%{if-warning}warning: %{endif}%{if-critical}error: %{endif}%{message}"));
QCommandLineParser parser;
parser.setApplicationDescription(QStringLiteral("Face unlock daemon"));
parser.addHelpOption();
parser.addVersionOption();
const QCommandLineOption socketOpt(QStringLiteral("socket"), QStringLiteral("Listen here instead of the system socket."), QStringLiteral("path"),
QStringLiteral(FU_SOCKET));
const QCommandLineOption stateOpt(QStringLiteral("state-dir"), QStringLiteral("Where face data is kept."), QStringLiteral("dir"),
QStringLiteral(FU_STATEDIR));
const QCommandLineOption configOpt(QStringLiteral("config"), QStringLiteral("The settings file."), QStringLiteral("file"), QStringLiteral(FU_CONFIG));
const QCommandLineOption modelOpt(QStringLiteral("models"), QStringLiteral("Where the networks are."), QStringLiteral("dir"),
QStringLiteral(FU_MODELDIR));
const QCommandLineOption idleOpt(QStringLiteral("idle-exit"), QStringLiteral("Exit after this many idle seconds (0: never)."), QStringLiteral("seconds"));
parser.addOptions({socketOpt, stateOpt, configOpt, modelOpt, idleOpt});
parser.process(app);
ServerOptions options;
options.socketPath = parser.value(socketOpt);
options.stateDir = parser.value(stateOpt);
options.configPath = parser.value(configOpt);
options.modelDir = parser.value(modelOpt);
options.systemdFd = systemdSocket();
// Started by the socket: go away again when there is nothing to do.
// Started by hand (development): stay.
options.idleSeconds = parser.isSet(idleOpt) ? parser.value(idleOpt).toInt() : (options.systemdFd >= 0 ? 60 : 0);
options.askPolkit = geteuid() == 0;
if (!options.askPolkit) {
qInfo("not running as root: face changes are not checked with polkit (development only)");
}
quitOnSignals(app);
Server server(options);
QString error;
if (!server.start(&error)) {
qCritical("cannot listen: %s", qPrintable(error));
return 1;
}
return app.exec();
}