/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2012 Andrey Prygunkov * * 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 #include #ifdef WIN32 #include #else #include #include #include #include #ifdef HAVE_SYS_PRCTL_H #include #endif #include #endif #include #include #include #include #ifndef DISABLE_PARCHECK #include #endif #ifdef HAVE_BACKTRACE #include #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 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