mirror of
https://github.com/nzbget/nzbget.git
synced 2026-02-18 14:53:58 -05:00
514 lines
11 KiB
C++
514 lines
11 KiB
C++
/*
|
|
* This file if part of nzbget
|
|
*
|
|
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
|
* Copyright (C) 2007 Andrei Prygounkov <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., 675 Mass Ave, Cambridge, MA 02139, 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>
|
|
#endif
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
|
|
#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 "RemoteServer.h"
|
|
#include "RemoteClient.h"
|
|
#include "MessageBase.h"
|
|
#include "PrePostProcessor.h"
|
|
#include "ParChecker.h"
|
|
#ifdef WIN32
|
|
#include "NTService.h"
|
|
#endif
|
|
|
|
// Prototypes
|
|
void Run();
|
|
void Cleanup();
|
|
void ProcessClientRequest();
|
|
#ifndef WIN32
|
|
void InstallSignalHandlers();
|
|
void Daemonize();
|
|
#endif
|
|
|
|
Thread* g_pFrontend = NULL;
|
|
Options* g_pOptions = NULL;
|
|
ServerPool* g_pServerPool = NULL;
|
|
QueueCoordinator* g_pQueueCoordinator = NULL;
|
|
RemoteServer* g_pRemoteServer = NULL;
|
|
DownloadSpeedMeter* g_pDownloadSpeedMeter = NULL;
|
|
Log* g_pLog = NULL;
|
|
PrePostProcessor* g_pPrePostProcessor;
|
|
|
|
/*
|
|
* Main loop
|
|
*/
|
|
int main(int argc, char *argv[])
|
|
{
|
|
#ifdef WIN32
|
|
_set_fmode(_O_BINARY);
|
|
InstallUninstallServiceCheck(argc, argv);
|
|
#endif
|
|
|
|
// Init options & get the name of the .nzb file
|
|
g_pLog = new Log();
|
|
g_pServerPool = new ServerPool();
|
|
debug("Options parsing");
|
|
g_pOptions = new Options(argc, argv);
|
|
|
|
#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();
|
|
}
|
|
|
|
if (g_pOptions->GetDaemonMode())
|
|
{
|
|
#ifdef WIN32
|
|
info("nzbget service-mode");
|
|
StartService(Run);
|
|
return 0;
|
|
#else
|
|
Daemonize();
|
|
info("nzbget daemon-mode");
|
|
#endif
|
|
}
|
|
else if (g_pOptions->GetServerMode())
|
|
{
|
|
info("nzbget server-mode");
|
|
}
|
|
else if (g_pOptions->GetRemoteClientMode())
|
|
{
|
|
info("nzbget remote-mode");
|
|
}
|
|
|
|
Run();
|
|
return 0;
|
|
}
|
|
|
|
void Run()
|
|
{
|
|
#ifndef WIN32
|
|
InstallSignalHandlers();
|
|
#endif
|
|
|
|
Thread::Init();
|
|
Connection::Init();
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Setup the network-server
|
|
if (g_pOptions->GetServerMode())
|
|
{
|
|
g_pRemoteServer = new RemoteServer();
|
|
g_pRemoteServer->Start();
|
|
}
|
|
|
|
// Starting a thread with the PrePostProcessor
|
|
if (!g_pOptions->GetRemoteClientMode())
|
|
{
|
|
g_pPrePostProcessor = new PrePostProcessor();
|
|
g_pPrePostProcessor->Start();
|
|
}
|
|
|
|
// 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
|
|
if (!g_pOptions->GetRemoteClientMode())
|
|
{
|
|
// Standalone-mode
|
|
if (!g_pOptions->GetServerMode() && !g_pQueueCoordinator->AddFileToQueue(g_pOptions->GetArgFilename()))
|
|
{
|
|
abort("FATAL ERROR: Parsing NZB-document %s failed!!\n\n", g_pOptions->GetArgFilename() ? g_pOptions->GetArgFilename() : "N/A");
|
|
return;
|
|
}
|
|
|
|
g_pQueueCoordinator->Start();
|
|
|
|
// enter main program-loop
|
|
while (g_pQueueCoordinator->IsRunning() || g_pPrePostProcessor->IsRunning())
|
|
{
|
|
if (!g_pOptions->GetServerMode() && !g_pQueueCoordinator->HasMoreJobs() && !g_pPrePostProcessor->HasMoreJobs())
|
|
{
|
|
// Standalone-mode: download completed
|
|
if (!g_pQueueCoordinator->IsStopped())
|
|
{
|
|
g_pQueueCoordinator->Stop();
|
|
}
|
|
if (!g_pPrePostProcessor->IsStopped())
|
|
{
|
|
g_pPrePostProcessor->Stop();
|
|
}
|
|
}
|
|
usleep(100 * 1000);
|
|
}
|
|
|
|
// main program-loop is terminated
|
|
debug("QueueCoordinator 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");
|
|
}
|
|
|
|
// 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();
|
|
|
|
if (g_pOptions->GetClientOperation() == Options::opClientRequestList)
|
|
{
|
|
Client->RequestServerList();
|
|
}
|
|
else if (g_pOptions->GetClientOperation() == Options::opClientRequestPause)
|
|
{
|
|
Client->RequestServerPauseUnpause(true);
|
|
}
|
|
else if (g_pOptions->GetClientOperation() == Options::opClientRequestUnpause)
|
|
{
|
|
Client->RequestServerPauseUnpause(false);
|
|
}
|
|
else if (g_pOptions->GetClientOperation() == Options::opClientRequestSetRate)
|
|
{
|
|
Client->RequestServerSetDownloadRate(g_pOptions->GetSetRate());
|
|
}
|
|
else if (g_pOptions->GetClientOperation() == Options::opClientRequestDumpDebug)
|
|
{
|
|
Client->RequestServerDumpDebug();
|
|
}
|
|
else if (g_pOptions->GetClientOperation() == Options::opClientRequestEditQueue)
|
|
{
|
|
Client->RequestServerEditQueue(g_pOptions->GetEditQueueAction(), g_pOptions->GetEditQueueOffset(),
|
|
g_pOptions->GetEditQueueIDList(), g_pOptions->GetEditQueueIDCount(), true);
|
|
}
|
|
else if (g_pOptions->GetClientOperation() == Options::opClientRequestLog)
|
|
{
|
|
Client->RequestServerLog(g_pOptions->GetLogLines());
|
|
}
|
|
else if (g_pOptions->GetClientOperation() == Options::opClientRequestShutdown)
|
|
{
|
|
Client->RequestServerShutdown();
|
|
}
|
|
else if (g_pOptions->GetClientOperation() == Options::opClientRequestDownload)
|
|
{
|
|
Client->RequestServerDownload(g_pOptions->GetArgFilename(), g_pOptions->GetAddTop());
|
|
}
|
|
|
|
delete Client;
|
|
}
|
|
|
|
void ExitProc()
|
|
{
|
|
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_pPrePostProcessor->Stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
#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
|
|
debug("SIGINT received");
|
|
ExitProc();
|
|
break;
|
|
|
|
case SIGTERM:
|
|
signal(SIGTERM, SIG_DFL); // Reset the signal handler
|
|
debug("SIGTERM received");
|
|
ExitProc();
|
|
break;
|
|
|
|
#ifdef DEBUG
|
|
case SIGPIPE:
|
|
debug("SIGPIPE received, ignoring");
|
|
break;
|
|
|
|
case SIGSEGV:
|
|
signal(SIGSEGV, SIG_DFL); // Reset the signal handler
|
|
debug("SIGSEGV received");
|
|
break;
|
|
|
|
default:
|
|
debug("Signal %i received", iSignal);
|
|
if (SignalProcList[iSignal - 1])
|
|
{
|
|
SignalProcList[iSignal - 1](iSignal);
|
|
}
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void InstallSignalHandlers()
|
|
{
|
|
signal(SIGINT, SignalProc);
|
|
signal(SIGTERM, SignalProc);
|
|
signal(SIGPIPE, SIG_IGN);
|
|
#ifdef DEBUG
|
|
SignalProcList.clear();
|
|
for (int i = 1; i <= 32; i++)
|
|
{
|
|
SignalProcList.push_back((sighandler)signal(i, SignalProc));
|
|
}
|
|
signal(SIGWINCH, SIG_DFL);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
void Cleanup()
|
|
{
|
|
debug("Cleaning up global objects");
|
|
|
|
debug("Deleting QueueCoordinator");
|
|
if (g_pQueueCoordinator)
|
|
{
|
|
delete g_pQueueCoordinator;
|
|
g_pQueueCoordinator = NULL;
|
|
}
|
|
debug("QueueCoordinator deleted");
|
|
|
|
debug("Deleting RemoteServer");
|
|
if (g_pRemoteServer)
|
|
{
|
|
delete g_pRemoteServer;
|
|
g_pRemoteServer = NULL;
|
|
}
|
|
debug("RemoteServer 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 Options");
|
|
if (g_pOptions)
|
|
{
|
|
if (g_pOptions->GetDaemonMode())
|
|
{
|
|
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");
|
|
|
|
Thread::Final();
|
|
Connection::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)
|
|
{
|
|
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
|