/*
# Copyright (c) 2009 - OpenSLX Project, Computer Center University of Freiburg
#
# This program is free software distributed under the GPL version 2.
# See http://openslx.org/COPYING
#
# If you have any feedback please consult http://openslx.org/feedback and
# send your suggestions, praise, or complaints to feedback@openslx.org
#
# General information about OpenSLX can be found at http://openslx.org/
# --------------------------------------------------------------------------
# pvsprivinputd.cpp:
# - Handle privileged input events - daemon
# --------------------------------------------------------------------------
*/
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <signal.h>
#include <unistd.h>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <QtDebug>
#include <QFileInfo>
#include <QSettings>
#include <QCoreApplication>
#include <QStringList>
#include <QSocketNotifier>
#include "pvsCheckPrivileges.h"
#include "pvsPrivInputSocket.h"
#include "pvsPrivInputHandler.h"
#include "pvsPrivInputSignalHandler.h"
#include "pvsSyslog.h"
using namespace std;
#ifndef UNIX_PATH_MAX
# define UNIX_PATH_MAX 108 /* according to unix(7) */
#endif
QByteArray socketPath;
int signalFds[2];
QTextStream qout(stdout, QIODevice::WriteOnly);
QTextStream qerr(stderr, QIODevice::WriteOnly);
static void unlinkSocket()
{
if(!socketPath.isNull())
{
unlink(socketPath.constData());
}
}
static void onCaughtSignal(int signum)
{
pvsPrivInputSendMessage(signalFds[0], &signum, sizeof(signum), 0);
}
static void usage()
{
qout << QObject::tr(
"Usage: %1 [--help|-h] [--daemon|-d] [--log=<Logger>|-l<Logger>]\n"
"\n"
"Options:\n"
" --help, -h Show this message\n"
" --daemon, -d Run in background\n"
" --log=<Logger>,\n"
" -l<Logger> Redirect all output to <Logger>\n"
" valid values are:\n"
" - any file name\n"
" - `syslog' to redirect output to the system log\n"
" - `null' to discard output (default)\n"
" (without quotes)\n"
"\n"
"Signals:\n"
" SIGINT, SIGTERM (or press Ctrl+C when run in foreground)\n"
" Quit\n"
" SIGHUP Reload configuration and cached user data\n").arg(qApp->arguments().at(0)) << flush;
}
int main(int argc, char** argv)
{
QCoreApplication app(argc, argv);
app.setApplicationName("pvsprivinputd");
app.setOrganizationName("openslx");
bool no_fork = true;
QString logTarget = "null";
foreach(QString arg, app.arguments().mid(1))
{
if(arg == "--daemon" || arg == "-d")
{
no_fork = false;
}
else if(arg.startsWith("-l"))
{
logTarget = arg.mid(2);
}
else if(arg.startsWith("--log="))
{
logTarget = arg.mid(6);
}
else if(arg == "--help" || arg == "-h")
{
usage();
return EXIT_SUCCESS;
}
else
{
qout << "Unexpected argument: " << arg << endl;
usage();
return EXIT_FAILURE;
}
}
if(logTarget != "null" && logTarget != "syslog")
{
logTarget = QFileInfo(logTarget).absoluteFilePath();
qout << "Writing log to " << logTarget << endl;
}
if(!no_fork)
{
pid_t pid;
pid_t sid;
pid = fork();
if(pid < 0)
{
qCritical("Could not fork: %s", strerror(errno));
exit(EXIT_FAILURE);
}
if(pid > 0)
{
exit(EXIT_SUCCESS);
}
// We are now inside a child process.
// Detach from parent:
sid = setsid();
if(sid < 0)
{
qCritical("Could not detach from parent process: %s", strerror(errno));
exit(EXIT_FAILURE);
}
// Change to some benign directory:
if(chdir("/") < 0)
{
qCritical("Could not change directory: %s", strerror(errno));
exit(EXIT_FAILURE);
}
}
// Okay, we are running as a daemon. Defer reopening standard file descriptors
// so we can report errors.
// Set the file creation mask to allow all processes to connect to
// the socket (access control is handled differently):
umask(0);
// Store the socket path before the signal handler is installed so it does not risk segfaulting
// due to bad timing.
socketPath = pvsPrivInputGetSocketAddress().toLocal8Bit();
// Ignore SIGPIPE. Connection errors are handled internally.
// According to signal(2), the only error is that the signal number
// is invalid. This cannot happen.
signal(SIGPIPE, SIG_IGN);
// Create our socket:
int sock = pvsPrivInputMakeServerSocket();
if(sock < 0)
{
exit(EXIT_FAILURE);
}
// Install our main object
PVSPrivInputHandler* handler = new PVSPrivInputHandler(sock, &app);
// When the handler gets deleted, we want to quit the application
QObject::connect(handler, SIGNAL(destroyed(QObject*)), &app, SLOT(quit()));
// set up signal handling:
if(socketpair(AF_UNIX, SOCK_DGRAM, 0, signalFds) < 0)
{
qCritical("Could not set up signal handling. Giving up.");
exit(EXIT_FAILURE);
}
PVSPrivInputSignalHandler sigHandler;
QSocketNotifier sigintWatcher(signalFds[1], QSocketNotifier::Read);
sigHandler.allowUnauthenticatedKilling(!pvsPrivInputEnableReceiveCredentials(signalFds[1]));
QObject::connect(&sigintWatcher, SIGNAL(activated(int)), &sigHandler, SLOT(signalReceived(int)));
QObject::connect(&sigHandler, SIGNAL(terminate()), &app, SLOT(quit()));
QObject::connect(&sigHandler, SIGNAL(reloadConfiguration()), PVSCheckPrivileges::instance(), SLOT(updateCachedUserDatabase()));
QObject::connect(&sigHandler, SIGNAL(reloadConfiguration()), PVSCheckPrivileges::instance(), SLOT(rereadConfiguration()));
signal(SIGINT, onCaughtSignal);
signal(SIGTERM, onCaughtSignal);
signal(SIGHUP, onCaughtSignal);
PVSLogRedirector* logRedir = 0;
QSocketNotifier* logNotif = 0;
// Our setup is complete.
if(!no_fork)
{
// Reopen standard file descriptors:
freopen("/dev/null", "r", stdin);
if(logTarget == "null")
{
freopen("/dev/null", "w", stdout);
freopen("/dev/null", "w", stderr);
}
else if(logTarget == "syslog")
{
logRedir = new PVSSyslogRedirector();
}
else
{
logRedir = new PVSLogfileRedirector(logTarget);
}
if(logRedir)
{
int logFds[2];
if(socketpair(AF_UNIX, SOCK_STREAM, 0, logFds) < 0)
{
qWarning("Could not open a socket pair: %s", strerror(errno));
return EXIT_FAILURE;
}
logNotif = new QSocketNotifier(logFds[1], QSocketNotifier::Read);
QObject::connect(logNotif, SIGNAL(activated(int)), logRedir, SLOT(inputAvailableOn(int)));
// redirect stdout and stderr:
dup2(logFds[0], 1);
dup2(logFds[0], 2);
}
}
atexit(unlinkSocket);
qout << "PVS Privileged Input Daemon initialization complete. Entering main loop." << endl;
// and run the main loop.
int ret = app.exec();
if(logRedir)
{
delete logNotif;
delete logRedir;
}
PVSCheckPrivileges::deleteInstance();
qout << "PVS Privileged Input Daemon deinitialization complete. Exiting." << endl;
return ret;
}