mirror of
https://github.com/znc/znc.git
synced 2026-03-28 17:42:41 +01:00
fcntl() is more portable than flock() so this makes znc run on more systems (everyone smile and say hi to solaris). The downside is that fcntl() locks are lost if *any* fd referring to that file is closed (luckily we don't do that). The big downside is that the child process after fork() does not inherit the lock. To work around this, when znc forks into the background, the child process immediately blocks and tries to get the lock on the config file. Once the parent releases the lock by exiting, the child will get it. This shouldn't cause races with other ZNCs, because in every other place we don't block waiting for a lock but instead abort immediately if the file is already locked. Thanks to LeftWing aka Joshua M. Clulow for making znc work on solaris (and automatically fixing some issues with NFS in the process). git-svn-id: https://znc.svn.sourceforge.net/svnroot/znc/trunk@2065 726aef4b-f618-498e-8847-2d620e286838
345 lines
8.9 KiB
C++
345 lines
8.9 KiB
C++
/*
|
|
* Copyright (C) 2004-2010 See the AUTHORS file for details.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 as published
|
|
* by the Free Software Foundation.
|
|
*/
|
|
|
|
#include "znc.h"
|
|
#include <getopt.h>
|
|
#include <sys/wait.h>
|
|
|
|
static const struct option g_LongOpts[] = {
|
|
{ "help", no_argument, 0, 'h' },
|
|
{ "version", no_argument, 0, 'v' },
|
|
{ "debug", no_argument, 0, 'D' },
|
|
{ "foreground", no_argument, 0, 'f' },
|
|
{ "no-color", no_argument, 0, 'n' },
|
|
{ "allow-root", no_argument, 0, 'r' },
|
|
{ "makeconf", no_argument, 0, 'c' },
|
|
{ "makepass", no_argument, 0, 's' },
|
|
#ifdef HAVE_LIBSSL
|
|
{ "makepem", no_argument, 0, 'p' },
|
|
#endif /* HAVE_LIBSSL */
|
|
{ "datadir", required_argument, 0, 'd' },
|
|
{ 0, 0, 0, 0 }
|
|
};
|
|
|
|
static void GenerateHelp(const char *appname) {
|
|
CUtils::PrintMessage("USAGE: " + CString(appname) + " [options]");
|
|
CUtils::PrintMessage("Options are:");
|
|
CUtils::PrintMessage("\t-h, --help List available command line options (this page)");
|
|
CUtils::PrintMessage("\t-v, --version Output version information and exit");
|
|
CUtils::PrintMessage("\t-f, --foreground Don't fork into the background");
|
|
CUtils::PrintMessage("\t-D, --debug Output debugging information (Implies -f)");
|
|
CUtils::PrintMessage("\t-n, --no-color Don't use escape sequences in the output");
|
|
CUtils::PrintMessage("\t-r, --allow-root Don't complain if ZNC is run as root");
|
|
CUtils::PrintMessage("\t-c, --makeconf Interactively create a new config");
|
|
CUtils::PrintMessage("\t-s, --makepass Generates a password for use in config");
|
|
#ifdef HAVE_LIBSSL
|
|
CUtils::PrintMessage("\t-p, --makepem Generates a pemfile for use with SSL");
|
|
#endif /* HAVE_LIBSSL */
|
|
CUtils::PrintMessage("\t-d, --datadir Set a different znc repository (default is ~/.znc)");
|
|
}
|
|
|
|
static void die(int sig) {
|
|
signal(SIGPIPE, SIG_DFL);
|
|
|
|
CUtils::PrintMessage("Exiting on SIG [" + CString(sig) + "]");
|
|
|
|
delete &CZNC::Get();
|
|
exit(sig);
|
|
}
|
|
|
|
static void signalHandler(int sig) {
|
|
switch (sig) {
|
|
case SIGHUP:
|
|
CUtils::PrintMessage("Caught SIGHUP");
|
|
CZNC::Get().SetConfigState(CZNC::ECONFIG_NEED_REHASH);
|
|
break;
|
|
case SIGUSR1:
|
|
CUtils::PrintMessage("Caught SIGUSR1");
|
|
CZNC::Get().SetConfigState(CZNC::ECONFIG_NEED_WRITE);
|
|
break;
|
|
default:
|
|
CUtils::PrintMessage("WTF? Signal handler called for a signal it doesn't know?");
|
|
}
|
|
}
|
|
|
|
static bool isRoot() {
|
|
// User root? If one of these were root, we could switch the others to root, too
|
|
if (geteuid() == 0 || getuid() == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static void seedPRNG() {
|
|
struct timeval tv;
|
|
unsigned int seed;
|
|
|
|
// Try to find a seed which can't be as easily guessed as only time()
|
|
|
|
if (gettimeofday(&tv, NULL) == 0) {
|
|
seed = tv.tv_sec;
|
|
|
|
// This is in [0:1e6], which means that roughly 20 bits are
|
|
// actually used, let's try to shuffle the high bits.
|
|
seed ^= (tv.tv_usec << 10) | tv.tv_usec;
|
|
} else
|
|
seed = time(NULL);
|
|
|
|
seed ^= rand();
|
|
seed ^= getpid();
|
|
|
|
srand(seed);
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
CString sConfig;
|
|
CString sDataDir = "";
|
|
|
|
seedPRNG();
|
|
CUtils::SetStdoutIsTTY(isatty(1));
|
|
|
|
int iArg, iOptIndex = -1;
|
|
bool bMakeConf = false;
|
|
bool bMakePass = false;
|
|
bool bAllowRoot = false;
|
|
bool bForeground = false;
|
|
#ifdef _DEBUG
|
|
bForeground = true;
|
|
#endif
|
|
#ifdef HAVE_LIBSSL
|
|
bool bMakePem = false;
|
|
|
|
while ((iArg = getopt_long(argc, argv, "hvnrcspd:Df", g_LongOpts, &iOptIndex)) != -1) {
|
|
#else
|
|
while ((iArg = getopt_long(argc, argv, "hvnrcsd:Df", g_LongOpts, &iOptIndex)) != -1) {
|
|
#endif /* HAVE_LIBSSL */
|
|
switch (iArg) {
|
|
case 'h':
|
|
GenerateHelp(argv[0]);
|
|
return 0;
|
|
case 'v':
|
|
cout << CZNC::GetTag() << endl;
|
|
return 0;
|
|
case 'n':
|
|
CUtils::SetStdoutIsTTY(false);
|
|
break;
|
|
case 'r':
|
|
bAllowRoot = true;
|
|
break;
|
|
case 'c':
|
|
bMakeConf = true;
|
|
break;
|
|
case 's':
|
|
bMakePass = true;
|
|
break;
|
|
#ifdef HAVE_LIBSSL
|
|
case 'p':
|
|
bMakePem = true;
|
|
break;
|
|
#endif /* HAVE_LIBSSL */
|
|
case 'd':
|
|
sDataDir = CString(optarg);
|
|
break;
|
|
case 'f':
|
|
bForeground = true;
|
|
break;
|
|
case 'D':
|
|
bForeground = true;
|
|
CUtils::SetDebug(true);
|
|
break;
|
|
case '?':
|
|
default:
|
|
GenerateHelp(argv[0]);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (optind < argc) {
|
|
CUtils::PrintError("Specifying a config file as an argument isn't supported anymore.");
|
|
CUtils::PrintError("Use --datadir instead.");
|
|
return 1;
|
|
}
|
|
|
|
CZNC* pZNC = &CZNC::Get();
|
|
pZNC->InitDirs(((argc) ? argv[0] : ""), sDataDir);
|
|
|
|
#ifdef HAVE_LIBSSL
|
|
if (bMakePem) {
|
|
pZNC->WritePemFile();
|
|
|
|
delete pZNC;
|
|
return 0;
|
|
}
|
|
#endif /* HAVE_LIBSSL */
|
|
|
|
if (bMakePass) {
|
|
CString sSalt;
|
|
CString sHash = CUtils::GetSaltedHashPass(sSalt);
|
|
CUtils::PrintMessage("Use this in the <User> section of your config:");
|
|
CUtils::PrintMessage("Pass = " + CUtils::sDefaultHash + "#" + sHash + "#" + sSalt + "#");
|
|
|
|
delete pZNC;
|
|
return 0;
|
|
}
|
|
|
|
#ifndef RUN_FROM_SOURCE
|
|
if (CFile::Exists(pZNC->GetCurPath() + "/znc-uninstalled.pc")) {
|
|
CUtils::PrintError("It looks like you are running znc without installing it first.");
|
|
CUtils::PrintError("Recompile with --enable-run-from-source if you intend to do that.");
|
|
}
|
|
#endif
|
|
|
|
if (isRoot()) {
|
|
CUtils::PrintError("You are running ZNC as root! Don't do that! There are not many valid");
|
|
CUtils::PrintError("reasons for this and it can, in theory, cause great damage!");
|
|
if (!bAllowRoot) {
|
|
delete pZNC;
|
|
return 1;
|
|
}
|
|
CUtils::PrintError("You have been warned.");
|
|
CUtils::PrintError("Hit CTRL+C now if you don't want to run ZNC as root.");
|
|
CUtils::PrintError("ZNC will start in 30 seconds.");
|
|
sleep(30);
|
|
}
|
|
|
|
if (bMakeConf) {
|
|
if (!pZNC->WriteNewConfig(sConfig)) {
|
|
delete pZNC;
|
|
return 0;
|
|
}
|
|
/* Fall through to normal bootup */
|
|
}
|
|
|
|
if (!pZNC->ParseConfig(sConfig)) {
|
|
CUtils::PrintError("Unrecoverable config error.");
|
|
delete pZNC;
|
|
return 1;
|
|
}
|
|
|
|
if (!pZNC->OnBoot()) {
|
|
CUtils::PrintError("Exiting due to module boot errors.");
|
|
delete pZNC;
|
|
return 1;
|
|
}
|
|
|
|
if (bForeground) {
|
|
int iPid = getpid();
|
|
CUtils::PrintMessage("Staying open for debugging [pid: " + CString(iPid) + "]");
|
|
|
|
pZNC->WritePidFile(iPid);
|
|
CUtils::PrintMessage(CZNC::GetTag());
|
|
} else {
|
|
CUtils::PrintAction("Forking into the background");
|
|
|
|
int iPid = fork();
|
|
|
|
if (iPid == -1) {
|
|
CUtils::PrintStatus(false, strerror(errno));
|
|
delete pZNC;
|
|
return 1;
|
|
}
|
|
|
|
if (iPid > 0) {
|
|
// We are the parent. We are done and will go to bed.
|
|
CUtils::PrintStatus(true, "[pid: " + CString(iPid) + "]");
|
|
|
|
pZNC->WritePidFile(iPid);
|
|
CUtils::PrintMessage(CZNC::GetTag());
|
|
/* Don't destroy pZNC here or it will delete the pid file. */
|
|
return 0;
|
|
}
|
|
|
|
/* fcntl() locks don't necessarily propagate to forked()
|
|
* children. Reacquire the lock here. Use the blocking
|
|
* call to avoid race condition with parent exiting.
|
|
*/
|
|
if (!pZNC->WaitForChildLock()) {
|
|
CUtils::PrintError("Child was unable to obtain lock on config file.");
|
|
delete pZNC;
|
|
return 1;
|
|
}
|
|
|
|
// Redirect std in/out/err to /dev/null
|
|
close(0); open("/dev/null", O_RDONLY);
|
|
close(1); open("/dev/null", O_WRONLY);
|
|
close(2); open("/dev/null", O_WRONLY);
|
|
|
|
CUtils::SetStdoutIsTTY(false);
|
|
|
|
// We are the child. There is no way we can be a process group
|
|
// leader, thus setsid() must succeed.
|
|
setsid();
|
|
// Now we are in our own process group and session (no
|
|
// controlling terminal). We are independent!
|
|
}
|
|
|
|
struct sigaction sa;
|
|
sa.sa_flags = 0;
|
|
sigemptyset(&sa.sa_mask);
|
|
|
|
sa.sa_handler = SIG_IGN;
|
|
sigaction(SIGPIPE, &sa, (struct sigaction*) NULL);
|
|
|
|
sa.sa_handler = signalHandler;
|
|
sigaction(SIGHUP, &sa, (struct sigaction*) NULL);
|
|
sigaction(SIGUSR1, &sa, (struct sigaction*) NULL);
|
|
|
|
// Once this signal is caught, the signal handler is reset
|
|
// to SIG_DFL. This avoids endless loop with signals.
|
|
sa.sa_flags = SA_RESETHAND;
|
|
sa.sa_handler = die;
|
|
sigaction(SIGINT, &sa, (struct sigaction*) NULL);
|
|
sigaction(SIGQUIT, &sa, (struct sigaction*) NULL);
|
|
sigaction(SIGTERM, &sa, (struct sigaction*) NULL);
|
|
|
|
int iRet = 0;
|
|
|
|
try {
|
|
pZNC->Loop();
|
|
} catch (CException e) {
|
|
switch (e.GetType()) {
|
|
case CException::EX_Shutdown:
|
|
iRet = 0;
|
|
break;
|
|
case CException::EX_Restart: {
|
|
// strdup() because GCC is stupid
|
|
char *args[] = {
|
|
strdup(argv[0]),
|
|
strdup("--datadir"),
|
|
strdup(pZNC->GetZNCPath().c_str()),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
};
|
|
int pos = 3;
|
|
if (CUtils::Debug())
|
|
args[pos++] = strdup("--debug");
|
|
else if (bForeground)
|
|
args[pos++] = strdup("--foreground");
|
|
if (!CUtils::StdoutIsTTY())
|
|
args[pos++] = strdup("--no-color");
|
|
if (bAllowRoot)
|
|
args[pos++] = strdup("--allow-root");
|
|
// The above code adds 4 entries to args tops
|
|
// which means the array should be big enough
|
|
|
|
delete pZNC;
|
|
execvp(args[0], args);
|
|
CUtils::PrintError("Unable to restart znc [" + CString(strerror(errno)) + "]");
|
|
} /* Fall through */
|
|
default:
|
|
iRet = 1;
|
|
}
|
|
}
|
|
|
|
delete pZNC;
|
|
|
|
return iRet;
|
|
}
|