Files
nzbget/nzbget.cpp

874 lines
19 KiB
C++

/*
* This file is part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2012 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#ifdef WIN32
#include <winsvc.h>
#else
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <sys/resource.h>
#ifdef HAVE_SYS_PRCTL_H
#include <sys/prctl.h>
#endif
#include <signal.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#ifndef DISABLE_PARCHECK
#include <iostream>
#endif
#ifdef HAVE_BACKTRACE
#include <execinfo.h>
#endif
#include "nzbget.h"
#include "ServerPool.h"
#include "Log.h"
#include "NZBFile.h"
#include "Options.h"
#include "Thread.h"
#include "ColoredFrontend.h"
#include "NCursesFrontend.h"
#include "QueueCoordinator.h"
#include "UrlCoordinator.h"
#include "RemoteServer.h"
#include "RemoteClient.h"
#include "MessageBase.h"
#include "DiskState.h"
#include "PrePostProcessor.h"
#include "ParChecker.h"
#include "Scheduler.h"
#include "Util.h"
#ifdef WIN32
#include "NTService.h"
#endif
// Prototypes
void RunMain();
void Run(bool bReload);
void Reload();
void Cleanup();
void ProcessClientRequest();
#ifndef WIN32
void InstallSignalHandlers();
void Daemonize();
void PrintBacktrace();
#ifdef HAVE_SYS_PRCTL_H
void EnableDumpCore();
#endif
#ifdef DEBUG
void MakeSegFault();
#endif
#endif
#ifndef DISABLE_PARCHECK
void DisableCout();
#endif
Thread* g_pFrontend = NULL;
Options* g_pOptions = NULL;
ServerPool* g_pServerPool = NULL;
QueueCoordinator* g_pQueueCoordinator = NULL;
UrlCoordinator* g_pUrlCoordinator = NULL;
RemoteServer* g_pRemoteServer = NULL;
RemoteServer* g_pRemoteSecureServer = NULL;
DownloadSpeedMeter* g_pDownloadSpeedMeter = NULL;
DownloadQueueHolder* g_pDownloadQueueHolder = NULL;
Log* g_pLog = NULL;
PrePostProcessor* g_pPrePostProcessor = NULL;
DiskState* g_pDiskState = NULL;
Scheduler* g_pScheduler = NULL;
int g_iArgumentCount;
char* (*g_szEnvironmentVariables)[] = NULL;
char* (*g_szArguments)[] = NULL;
bool g_bReloading = true;
/*
* Main loop
*/
int main(int argc, char *argv[], char *argp[])
{
#ifdef WIN32
#ifdef _DEBUG
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF
#ifdef DEBUG_CRTMEMLEAKS
| _CRTDBG_CHECK_CRT_DF | _CRTDBG_CHECK_ALWAYS_DF
#endif
);
#endif
#endif
Util::InitVersionRevision();
#ifdef WIN32
InstallUninstallServiceCheck(argc, argv);
#endif
#ifndef DISABLE_PARCHECK
DisableCout();
#endif
g_iArgumentCount = argc;
g_szArguments = (char*(*)[])argv;
g_szEnvironmentVariables = (char*(*)[])argp;
#ifdef WIN32
for (int i=0; i < argc; i++)
{
if (!strcmp(argv[i], "-D"))
{
StartService(RunMain);
return 0;
}
}
#endif
RunMain();
#ifdef WIN32
#ifdef _DEBUG
_CrtDumpMemoryLeaks();
#endif
#endif
return 0;
}
void RunMain()
{
// we need to save and later restore current directory each time
// the program is reloaded (RPC-Method "reload") in order for
// config to properly load in a case relative paths are used
// in command line
char szCurDir[MAX_PATH + 1];
Util::GetCurrentDirectory(szCurDir, sizeof(szCurDir));
bool bReload = false;
while (g_bReloading)
{
g_bReloading = false;
Util::SetCurrentDirectory(szCurDir);
Run(bReload);
bReload = true;
}
}
void Run(bool bReload)
{
g_pLog = new Log();
debug("nzbget %s", Util::VersionRevision());
if (!bReload)
{
Thread::Init();
}
g_pServerPool = new ServerPool();
g_pScheduler = new Scheduler();
debug("Reading options");
g_pOptions = new Options(g_iArgumentCount, *g_szArguments);
#ifndef WIN32
if (g_pOptions->GetUMask() < 01000)
{
/* set newly created file permissions */
umask(g_pOptions->GetUMask());
}
#endif
if (g_pOptions->GetServerMode() && g_pOptions->GetCreateLog() && g_pOptions->GetResetLog())
{
debug("Deleting old log-file");
g_pLog->ResetLog();
}
g_pLog->InitOptions();
if (g_pOptions->GetDaemonMode() && !bReload)
{
#ifdef WIN32
info("nzbget %s service-mode", Util::VersionRevision());
#else
Daemonize();
info("nzbget %s daemon-mode", Util::VersionRevision());
#endif
}
else if (g_pOptions->GetServerMode() && !bReload)
{
info("nzbget %s server-mode", Util::VersionRevision());
}
else if (g_pOptions->GetRemoteClientMode())
{
info("nzbget %s remote-mode", Util::VersionRevision());
}
if (!bReload)
{
Connection::Init();
}
if (!g_pOptions->GetRemoteClientMode())
{
g_pServerPool->InitConnections();
#ifdef DEBUG
g_pServerPool->LogDebugInfo();
#endif
}
#ifndef WIN32
#ifdef HAVE_SYS_PRCTL_H
if (g_pOptions->GetDumpCore())
{
EnableDumpCore();
}
#endif
#endif
#ifndef WIN32
InstallSignalHandlers();
#ifdef DEBUG
if (g_pOptions->GetTestBacktrace())
{
MakeSegFault();
}
#endif
#endif
// client request
if (g_pOptions->GetClientOperation() != Options::opClientNoOperation)
{
ProcessClientRequest();
Cleanup();
return;
}
// Create the queue coordinator
if (!g_pOptions->GetRemoteClientMode())
{
g_pQueueCoordinator = new QueueCoordinator();
g_pDownloadSpeedMeter = g_pQueueCoordinator;
g_pDownloadQueueHolder = g_pQueueCoordinator;
g_pUrlCoordinator = new UrlCoordinator();
}
// Setup the network-server
if (g_pOptions->GetServerMode())
{
g_pRemoteServer = new RemoteServer(false);
g_pRemoteServer->Start();
if (g_pOptions->GetSecureControl())
{
g_pRemoteSecureServer = new RemoteServer(true);
g_pRemoteSecureServer->Start();
}
}
// Creating PrePostProcessor
if (!g_pOptions->GetRemoteClientMode())
{
g_pPrePostProcessor = new PrePostProcessor();
}
// Create the frontend
if (!g_pOptions->GetDaemonMode())
{
switch (g_pOptions->GetOutputMode())
{
case Options::omNCurses:
#ifndef DISABLE_CURSES
g_pFrontend = new NCursesFrontend();
break;
#endif
case Options::omColored:
g_pFrontend = new ColoredFrontend();
break;
case Options::omLoggable:
g_pFrontend = new LoggableFrontend();
break;
}
}
// Starting a thread with the frontend
if (g_pFrontend)
{
g_pFrontend->Start();
}
// Starting QueueCoordinator and PrePostProcessor
if (!g_pOptions->GetRemoteClientMode())
{
// Standalone-mode
if (!g_pOptions->GetServerMode())
{
NZBFile* pNZBFile = NZBFile::CreateFromFile(g_pOptions->GetArgFilename(), g_pOptions->GetAddCategory() ? g_pOptions->GetAddCategory() : "");
if (!pNZBFile)
{
abort("FATAL ERROR: Parsing NZB-document %s failed\n\n", g_pOptions->GetArgFilename() ? g_pOptions->GetArgFilename() : "N/A");
return;
}
g_pQueueCoordinator->AddNZBFileToQueue(pNZBFile, false);
delete pNZBFile;
}
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
g_pDiskState = new DiskState();
}
g_pQueueCoordinator->Start();
g_pUrlCoordinator->Start();
g_pPrePostProcessor->Start();
// enter main program-loop
while (g_pQueueCoordinator->IsRunning() ||
g_pUrlCoordinator->IsRunning() ||
g_pPrePostProcessor->IsRunning())
{
if (!g_pOptions->GetServerMode() &&
!g_pQueueCoordinator->HasMoreJobs() &&
!g_pUrlCoordinator->HasMoreJobs() &&
!g_pPrePostProcessor->HasMoreJobs())
{
// Standalone-mode: download completed
if (!g_pQueueCoordinator->IsStopped())
{
g_pQueueCoordinator->Stop();
}
if (!g_pUrlCoordinator->IsStopped())
{
g_pUrlCoordinator->Stop();
}
if (!g_pPrePostProcessor->IsStopped())
{
g_pPrePostProcessor->Stop();
}
}
usleep(100 * 1000);
}
// main program-loop is terminated
debug("QueueCoordinator stopped");
debug("UrlCoordinator stopped");
debug("PrePostProcessor stopped");
}
// Stop network-server
if (g_pRemoteServer)
{
debug("stopping RemoteServer");
g_pRemoteServer->Stop();
int iMaxWaitMSec = 1000;
while (g_pRemoteServer->IsRunning() && iMaxWaitMSec > 0)
{
usleep(100 * 1000);
iMaxWaitMSec -= 100;
}
if (g_pRemoteServer->IsRunning())
{
debug("Killing RemoteServer");
g_pRemoteServer->Kill();
}
debug("RemoteServer stopped");
}
if (g_pRemoteSecureServer)
{
debug("stopping RemoteSecureServer");
g_pRemoteSecureServer->Stop();
int iMaxWaitMSec = 1000;
while (g_pRemoteSecureServer->IsRunning() && iMaxWaitMSec > 0)
{
usleep(100 * 1000);
iMaxWaitMSec -= 100;
}
if (g_pRemoteSecureServer->IsRunning())
{
debug("Killing RemoteSecureServer");
g_pRemoteSecureServer->Kill();
}
debug("RemoteSecureServer stopped");
}
// Stop Frontend
if (g_pFrontend)
{
if (!g_pOptions->GetRemoteClientMode())
{
debug("Stopping Frontend");
g_pFrontend->Stop();
}
while (g_pFrontend->IsRunning())
{
usleep(50 * 1000);
}
debug("Frontend stopped");
}
Cleanup();
}
void ProcessClientRequest()
{
RemoteClient* Client = new RemoteClient();
switch (g_pOptions->GetClientOperation())
{
case Options::opClientRequestListFiles:
Client->RequestServerList(true, false, g_pOptions->GetMatchMode() == Options::mmRegEx ? g_pOptions->GetEditQueueText() : NULL);
break;
case Options::opClientRequestListGroups:
Client->RequestServerList(false, true, g_pOptions->GetMatchMode() == Options::mmRegEx ? g_pOptions->GetEditQueueText() : NULL);
break;
case Options::opClientRequestListStatus:
Client->RequestServerList(false, false, NULL);
break;
case Options::opClientRequestDownloadPause:
Client->RequestServerPauseUnpause(true, eRemotePauseUnpauseActionDownload);
break;
case Options::opClientRequestDownloadUnpause:
Client->RequestServerPauseUnpause(false, eRemotePauseUnpauseActionDownload);
break;
case Options::opClientRequestDownload2Pause:
Client->RequestServerPauseUnpause(true, eRemotePauseUnpauseActionDownload2);
break;
case Options::opClientRequestDownload2Unpause:
Client->RequestServerPauseUnpause(false, eRemotePauseUnpauseActionDownload2);
break;
case Options::opClientRequestSetRate:
Client->RequestServerSetDownloadRate(g_pOptions->GetSetRate());
break;
case Options::opClientRequestDumpDebug:
Client->RequestServerDumpDebug();
break;
case Options::opClientRequestEditQueue:
Client->RequestServerEditQueue((eRemoteEditAction)g_pOptions->GetEditQueueAction(), g_pOptions->GetEditQueueOffset(),
g_pOptions->GetEditQueueText(), g_pOptions->GetEditQueueIDList(), g_pOptions->GetEditQueueIDCount(),
g_pOptions->GetEditQueueNameList(), (eRemoteMatchMode)g_pOptions->GetMatchMode(), true);
break;
case Options::opClientRequestLog:
Client->RequestServerLog(g_pOptions->GetLogLines());
break;
case Options::opClientRequestShutdown:
Client->RequestServerShutdown();
break;
case Options::opClientRequestReload:
Client->RequestServerReload();
break;
case Options::opClientRequestDownload:
Client->RequestServerDownload(g_pOptions->GetArgFilename(), g_pOptions->GetAddCategory(), g_pOptions->GetAddTop(), g_pOptions->GetAddPaused(), g_pOptions->GetAddPriority());
break;
case Options::opClientRequestVersion:
Client->RequestServerVersion();
break;
case Options::opClientRequestPostQueue:
Client->RequestPostQueue();
break;
case Options::opClientRequestWriteLog:
Client->RequestWriteLog(g_pOptions->GetWriteLogKind(), g_pOptions->GetLastArg());
break;
case Options::opClientRequestScanAsync:
Client->RequestScan(false);
break;
case Options::opClientRequestScanSync:
Client->RequestScan(true);
break;
case Options::opClientRequestPostPause:
Client->RequestServerPauseUnpause(true, eRemotePauseUnpauseActionPostProcess);
break;
case Options::opClientRequestPostUnpause:
Client->RequestServerPauseUnpause(false, eRemotePauseUnpauseActionPostProcess);
break;
case Options::opClientRequestScanPause:
Client->RequestServerPauseUnpause(true, eRemotePauseUnpauseActionScan);
break;
case Options::opClientRequestScanUnpause:
Client->RequestServerPauseUnpause(false, eRemotePauseUnpauseActionScan);
break;
case Options::opClientRequestHistory:
Client->RequestHistory();
break;
case Options::opClientRequestDownloadUrl:
Client->RequestServerDownloadUrl(g_pOptions->GetLastArg(), g_pOptions->GetAddNZBFilename(), g_pOptions->GetAddCategory(), g_pOptions->GetAddTop(), g_pOptions->GetAddPaused(), g_pOptions->GetAddPriority());
break;
case Options::opClientRequestUrlQueue:
Client->RequestUrlQueue();
break;
case Options::opClientNoOperation:
break;
}
delete Client;
}
void ExitProc()
{
if (!g_bReloading)
{
info("Stopping, please wait...");
}
if (g_pOptions->GetRemoteClientMode())
{
if (g_pFrontend)
{
debug("Stopping Frontend");
g_pFrontend->Stop();
}
}
else
{
if (g_pQueueCoordinator)
{
debug("Stopping QueueCoordinator");
g_pQueueCoordinator->Stop();
g_pUrlCoordinator->Stop();
g_pPrePostProcessor->Stop();
}
}
}
void Reload()
{
g_bReloading = true;
info("Reloading...");
ExitProc();
}
#ifndef WIN32
#ifdef DEBUG
typedef void(*sighandler)(int);
std::vector<sighandler> SignalProcList;
#endif
/*
* Signal handler
*/
void SignalProc(int iSignal)
{
switch (iSignal)
{
case SIGINT:
signal(SIGINT, SIG_DFL); // Reset the signal handler
ExitProc();
break;
case SIGTERM:
signal(SIGTERM, SIG_DFL); // Reset the signal handler
ExitProc();
break;
case SIGCHLD:
// ignoring
break;
#ifdef DEBUG
case SIGSEGV:
signal(SIGSEGV, SIG_DFL); // Reset the signal handler
PrintBacktrace();
break;
#endif
}
}
void InstallSignalHandlers()
{
signal(SIGINT, SignalProc);
signal(SIGTERM, SignalProc);
signal(SIGPIPE, SIG_IGN);
#ifdef DEBUG
signal(SIGSEGV, SignalProc);
#endif
#ifdef SIGCHLD_HANDLER
// it could be necessary on some systems to activate a handler for SIGCHLD
// however it make troubles on other systems and is deactivated by default
signal(SIGCHLD, SignalProc);
#endif
}
void PrintBacktrace()
{
#ifdef HAVE_BACKTRACE
printf("Segmentation fault, tracing...\n");
void *array[100];
size_t size;
char **strings;
size_t i;
size = backtrace(array, 100);
strings = backtrace_symbols(array, size);
// first trace to screen
printf("Obtained %zd stack frames\n", size);
for (i = 0; i < size; i++)
{
printf("%s\n", strings[i]);
}
// then trace to log
error("Segmentation fault, tracing...");
error("Obtained %zd stack frames", size);
for (i = 0; i < size; i++)
{
error("%s", strings[i]);
}
free(strings);
#else
error("Segmentation fault");
#endif
}
#ifdef DEBUG
void MakeSegFault()
{
char* N = NULL;
strcpy(N, "");
}
#endif
#ifdef HAVE_SYS_PRCTL_H
/**
* activates the creation of core-files
*/
void EnableDumpCore()
{
rlimit rlim;
rlim.rlim_cur= RLIM_INFINITY;
rlim.rlim_max= RLIM_INFINITY;
setrlimit(RLIMIT_CORE, &rlim);
prctl(PR_SET_DUMPABLE, 1);
}
#endif
#endif
void Cleanup()
{
debug("Cleaning up global objects");
debug("Deleting UrlCoordinator");
if (g_pUrlCoordinator)
{
delete g_pUrlCoordinator;
g_pUrlCoordinator = NULL;
}
debug("UrlCoordinator deleted");
debug("Deleting RemoteServer");
if (g_pRemoteServer)
{
delete g_pRemoteServer;
g_pRemoteServer = NULL;
}
debug("RemoteServer deleted");
debug("Deleting RemoteSecureServer");
if (g_pRemoteSecureServer)
{
delete g_pRemoteSecureServer;
g_pRemoteSecureServer = NULL;
}
debug("RemoteSecureServer deleted");
debug("Deleting PrePostProcessor");
if (g_pPrePostProcessor)
{
delete g_pPrePostProcessor;
g_pPrePostProcessor = NULL;
}
debug("PrePostProcessor deleted");
debug("Deleting Frontend");
if (g_pFrontend)
{
delete g_pFrontend;
g_pFrontend = NULL;
}
debug("Frontend deleted");
debug("Deleting QueueCoordinator");
if (g_pQueueCoordinator)
{
delete g_pQueueCoordinator;
g_pQueueCoordinator = NULL;
}
debug("QueueCoordinator deleted");
debug("Deleting DiskState");
if (g_pDiskState)
{
delete g_pDiskState;
g_pDiskState = NULL;
}
debug("DiskState deleted");
debug("Deleting Options");
if (g_pOptions)
{
if (g_pOptions->GetDaemonMode() && !g_bReloading)
{
info("Deleting lock file");
remove(g_pOptions->GetLockFile());
}
delete g_pOptions;
g_pOptions = NULL;
}
debug("Options deleted");
debug("Deleting ServerPool");
if (g_pServerPool)
{
delete g_pServerPool;
g_pServerPool = NULL;
}
debug("ServerPool deleted");
debug("Deleting Scheduler");
if (g_pScheduler)
{
delete g_pScheduler;
g_pScheduler = NULL;
}
debug("Scheduler deleted");
if (!g_bReloading)
{
Connection::Final();
Thread::Final();
}
debug("Global objects cleaned up");
if (g_pLog)
{
delete g_pLog;
g_pLog = NULL;
}
}
#ifndef WIN32
void Daemonize()
{
int i, lfp;
char str[10];
if (getppid() == 1) return; /* already a daemon */
i = fork();
if (i < 0) exit(1); /* fork error */
if (i > 0) exit(0); /* parent exits */
/* child (daemon) continues */
setsid(); /* obtain a new process group */
for (i = getdtablesize();i >= 0;--i) close(i); /* close all descriptors */
i = open("/dev/null", O_RDWR); dup(i); dup(i); /* handle standart I/O */
chdir(g_pOptions->GetDestDir()); /* change running directory */
lfp = open(g_pOptions->GetLockFile(), O_RDWR | O_CREAT, 0640);
if (lfp < 0) exit(1); /* can not open */
if (lockf(lfp, F_TLOCK, 0) < 0) exit(0); /* can not lock */
/* Drop user if there is one, and we were run as root */
if ( getuid() == 0 || geteuid() == 0 )
{
struct passwd *pw = getpwnam(g_pOptions->GetDaemonUserName());
if (pw)
{
fchown(lfp, pw->pw_uid, pw->pw_gid); /* change owner of lock file */
setgroups( 0, (const gid_t*) 0 ); /* Set aux groups to null. */
setgid(pw->pw_gid); /* Set primary group. */
/* Try setting aux groups correctly - not critical if this fails. */
initgroups( g_pOptions->GetDaemonUserName(),pw->pw_gid);
/* Finally, set uid. */
setuid(pw->pw_uid);
}
}
/* first instance continues */
sprintf(str, "%d\n", getpid());
write(lfp, str, strlen(str)); /* record pid to lockfile */
signal(SIGCHLD, SIG_IGN); /* ignore child */
signal(SIGTSTP, SIG_IGN); /* ignore tty signals */
signal(SIGTTOU, SIG_IGN);
signal(SIGTTIN, SIG_IGN);
}
#endif
#ifndef DISABLE_PARCHECK
class NullStreamBuf : public std::streambuf
{
public:
int sputc ( char c ) { return (int) c; }
} NullStreamBufInstance;
void DisableCout()
{
// libpar2 prints messages to c++ standard output stream (std::cout).
// However we do not want these messages to be printed.
// Since we do not use std::cout in nzbget we just disable it.
std::cout.rdbuf(&NullStreamBufInstance);
}
#endif