/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2011 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 #include #include #include #include #ifdef WIN32 #include #else #include #include #endif #include "nzbget.h" #include "Util.h" #include "Options.h" #include "Log.h" #include "ServerPool.h" #include "NewsServer.h" #include "MessageBase.h" #include "Scheduler.h" extern ServerPool* g_pServerPool; extern Scheduler* g_pScheduler; #ifdef HAVE_GETOPT_LONG static struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"configfile", required_argument, 0, 'c'}, {"noconfigfile", no_argument, 0, 'n'}, {"printconfig", no_argument, 0, 'p'}, {"server", no_argument, 0, 's' }, {"daemon", no_argument, 0, 'D' }, {"version", no_argument, 0, 'v'}, {"serverversion", no_argument, 0, 'V'}, {"option", required_argument, 0, 'o'}, {"append", no_argument, 0, 'A'}, {"list", no_argument, 0, 'L'}, {"pause", no_argument, 0, 'P'}, {"unpause", no_argument, 0, 'U'}, {"rate", required_argument, 0, 'R'}, {"debug", no_argument, 0, 'B'}, {"log", required_argument, 0, 'G'}, {"top", no_argument, 0, 'T'}, {"edit", required_argument, 0, 'E'}, {"connect", no_argument, 0, 'C'}, {"quit", no_argument, 0, 'Q'}, {"reload", no_argument, 0, 'O'}, {"write", required_argument, 0, 'W'}, {"category", required_argument, 0, 'K'}, {"scan", no_argument, 0, 'S'}, {0, 0, 0, 0} }; #endif static char short_options[] = "c:hno:psvAB:DCE:G:K:LPR:STUQOVW:"; // Program options static const char* OPTION_CONFIGFILE = "ConfigFile"; static const char* OPTION_APPBIN = "AppBin"; static const char* OPTION_APPDIR = "AppDir"; static const char* OPTION_VERSION = "Version"; static const char* OPTION_MAINDIR = "MainDir"; static const char* OPTION_DESTDIR = "DestDir"; static const char* OPTION_TEMPDIR = "TempDir"; static const char* OPTION_QUEUEDIR = "QueueDir"; static const char* OPTION_NZBDIR = "NzbDir"; static const char* OPTION_WEBDIR = "WebDir"; static const char* OPTION_CREATELOG = "CreateLog"; static const char* OPTION_LOGFILE = "LogFile"; static const char* OPTION_APPENDNZBDIR = "AppendNzbDir"; static const char* OPTION_APPENDCATEGORYDIR = "AppendCategoryDir"; static const char* OPTION_LOCKFILE = "LockFile"; static const char* OPTION_DAEMONUSERNAME = "DaemonUserName"; static const char* OPTION_OUTPUTMODE = "OutputMode"; static const char* OPTION_DUPECHECK = "DupeCheck"; static const char* OPTION_DOWNLOADRATE = "DownloadRate"; static const char* OPTION_RENAMEBROKEN = "RenameBroken"; static const char* OPTION_CONTROLIP = "ControlIp"; static const char* OPTION_CONTROLPORT = "ControlPort"; static const char* OPTION_CONTROLPASSWORD = "ControlPassword"; static const char* OPTION_CONNECTIONTIMEOUT = "ConnectionTimeout"; static const char* OPTION_SAVEQUEUE = "SaveQueue"; static const char* OPTION_RELOADQUEUE = "ReloadQueue"; static const char* OPTION_RELOADURLQUEUE = "ReloadUrlQueue"; static const char* OPTION_RELOADPOSTQUEUE = "ReloadPostQueue"; static const char* OPTION_CREATEBROKENLOG = "CreateBrokenLog"; static const char* OPTION_RESETLOG = "ResetLog"; static const char* OPTION_DECODE = "Decode"; static const char* OPTION_RETRIES = "Retries"; static const char* OPTION_RETRYINTERVAL = "RetryInterval"; static const char* OPTION_TERMINATETIMEOUT = "TerminateTimeout"; static const char* OPTION_CONTINUEPARTIAL = "ContinuePartial"; static const char* OPTION_URLCONNECTIONS = "UrlConnections"; static const char* OPTION_LOGBUFFERSIZE = "LogBufferSize"; static const char* OPTION_INFOTARGET = "InfoTarget"; static const char* OPTION_WARNINGTARGET = "WarningTarget"; static const char* OPTION_ERRORTARGET = "ErrorTarget"; static const char* OPTION_DEBUGTARGET = "DebugTarget"; static const char* OPTION_DETAILTARGET = "DetailTarget"; static const char* OPTION_LOADPARS = "LoadPars"; static const char* OPTION_PARCHECK = "ParCheck"; static const char* OPTION_PARREPAIR = "ParRepair"; static const char* OPTION_PARSCAN = "ParScan"; static const char* OPTION_POSTPROCESS = "PostProcess"; static const char* OPTION_POSTCONFIGFILE = "PostConfigFile"; static const char* OPTION_NZBPROCESS = "NZBProcess"; static const char* OPTION_NZBADDEDPROCESS = "NZBAddedProcess"; static const char* OPTION_STRICTPARNAME = "StrictParName"; static const char* OPTION_UMASK = "UMask"; static const char* OPTION_UPDATEINTERVAL = "UpdateInterval"; static const char* OPTION_CURSESNZBNAME = "CursesNzbName"; static const char* OPTION_CURSESTIME = "CursesTime"; static const char* OPTION_CURSESGROUP = "CursesGroup"; static const char* OPTION_CRCCHECK = "CrcCheck"; static const char* OPTION_RETRYONCRCERROR = "RetryOnCrcError"; static const char* OPTION_THREADLIMIT = "ThreadLimit"; static const char* OPTION_DIRECTWRITE = "DirectWrite"; static const char* OPTION_WRITEBUFFERSIZE = "WriteBufferSize"; static const char* OPTION_NZBDIRINTERVAL = "NzbDirInterval"; static const char* OPTION_NZBDIRFILEAGE = "NzbDirFileAge"; static const char* OPTION_PARCLEANUPQUEUE = "ParCleanupQueue"; static const char* OPTION_DISKSPACE = "DiskSpace"; static const char* OPTION_PROCESSLOGKIND = "ProcessLogKind"; static const char* OPTION_ALLOWREPROCESS = "AllowReProcess"; static const char* OPTION_DUMPCORE = "DumpCore"; static const char* OPTION_PARPAUSEQUEUE = "ParPauseQueue"; static const char* OPTION_POSTPAUSEQUEUE = "PostPauseQueue"; static const char* OPTION_NZBCLEANUPDISK = "NzbCleanupDisk"; static const char* OPTION_DELETECLEANUPDISK = "DeleteCleanupDisk"; static const char* OPTION_MERGENZB = "MergeNzb"; static const char* OPTION_PARTIMELIMIT = "ParTimeLimit"; static const char* OPTION_KEEPHISTORY = "KeepHistory"; static const char* OPTION_ACCURATERATE = "AccurateRate"; // obsolete options static const char* OPTION_POSTLOGKIND = "PostLogKind"; static const char* OPTION_NZBLOGKIND = "NZBLogKind"; const char* BoolNames[] = { "yes", "no", "true", "false", "1", "0", "on", "off", "enable", "disable", "enabled", "disabled" }; const int BoolValues[] = { 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 }; const int BoolCount = 12; #ifndef WIN32 const char* PossibleConfigLocations[] = { "~/.nzbget", "/etc/nzbget.conf", "/usr/etc/nzbget.conf", "/usr/local/etc/nzbget.conf", "/opt/etc/nzbget.conf", NULL }; #endif Options::OptEntry::OptEntry() { m_szName = NULL; m_szValue = NULL; m_szDefValue = NULL; m_iLineNo = 0; } Options::OptEntry::OptEntry(const char* szName, const char* szValue) { m_szName = strdup(szName); m_szValue = strdup(szValue); m_szDefValue = NULL; m_iLineNo = 0; } Options::OptEntry::~OptEntry() { if (m_szName) { free(m_szName); } if (m_szValue) { free(m_szValue); } if (m_szDefValue) { free(m_szDefValue); } } void Options::OptEntry::SetName(const char* szName) { if (m_szName) { free(m_szName); } m_szName = strdup(szName); } void Options::OptEntry::SetValue(const char* szValue) { if (m_szValue) { free(m_szValue); } m_szValue = strdup(szValue); if (!m_szDefValue) { m_szDefValue = strdup(szValue); } } Options::OptEntries::~OptEntries() { for (iterator it = begin(); it != end(); it++) { delete *it; } } Options::OptEntry* Options::OptEntries::FindOption(const char* szName) { if (!szName) { return NULL; } for (iterator it = begin(); it != end(); it++) { OptEntry* pOptEntry = *it; if (!strcasecmp(pOptEntry->GetName(), szName)) { return pOptEntry; } } return NULL; } Options::Category::Category(const char* szName, const char* szDestDir) { m_szName = strdup(szName); m_szDestDir = strdup(szDestDir); } Options::Category::~Category() { if (m_szName) { free(m_szName); } if (m_szDestDir) { free(m_szDestDir); } } Options::Categories::~Categories() { for (iterator it = begin(); it != end(); it++) { delete *it; } } Options::Category* Options::Categories::FindCategory(const char* szName) { if (!szName) { return NULL; } for (iterator it = begin(); it != end(); it++) { Category* pCategory = *it; if (!strcasecmp(pCategory->GetName(), szName)) { return pCategory; } } return NULL; } Options::Options(int argc, char* argv[]) { m_bConfigErrors = false; m_iConfigLine = 0; // initialize options with default values m_bConfigInitialized = false; m_szConfigFilename = NULL; m_szDestDir = NULL; m_szTempDir = NULL; m_szQueueDir = NULL; m_szNzbDir = NULL; m_szWebDir = NULL; m_eInfoTarget = mtScreen; m_eWarningTarget = mtScreen; m_eErrorTarget = mtScreen; m_eDebugTarget = mtScreen; m_eDetailTarget = mtScreen; m_bDecode = true; m_bPauseDownload = false; m_bPauseDownload2 = false; m_bPausePostProcess = false; m_bPauseScan = false; m_bCreateBrokenLog = false; m_bResetLog = false; m_fDownloadRate = 0; m_iEditQueueAction = 0; m_pEditQueueIDList = NULL; m_iEditQueueIDCount = 0; m_iEditQueueOffset = 0; m_szEditQueueText = NULL; m_szArgFilename = NULL; m_szLastArg = NULL; m_szAddCategory = NULL; m_iAddPriority = 0; m_szAddNZBFilename = NULL; m_bAddPaused = false; m_iConnectionTimeout = 0; m_iTerminateTimeout = 0; m_bServerMode = false; m_bDaemonMode = false; m_bRemoteClientMode = false; m_bPrintOptions = false; m_bAddTop = false; m_bAppendNZBDir = false; m_bAppendCategoryDir = false; m_bContinuePartial = false; m_bRenameBroken = false; m_bSaveQueue = false; m_bDupeCheck = false; m_iRetries = 0; m_iRetryInterval = 0; m_szControlPort = 0; m_szControlIP = NULL; m_szControlPassword = NULL; m_szLockFile = NULL; m_szDaemonUserName = NULL; m_eOutputMode = omLoggable; m_bReloadQueue = false; m_bReloadUrlQueue = false; m_bReloadPostQueue = false; m_iUrlConnections = 0; m_iLogBufferSize = 0; m_iLogLines = 0; m_iWriteLogKind = 0; m_bCreateLog = false; m_szLogFile = NULL; m_eLoadPars = lpAll; m_bParCheck = false; m_bParRepair = false; m_eParScan = psLimited; m_szPostProcess = NULL; m_szPostConfigFilename = NULL; m_szNZBProcess = NULL; m_szNZBAddedProcess = NULL; m_bStrictParName = false; m_bNoConfig = false; m_iUMask = 0; m_iUpdateInterval = 0; m_bCursesNZBName = false; m_bCursesTime = false; m_bCursesGroup = false; m_bCrcCheck = false; m_bRetryOnCrcError = false; m_bDirectWrite = false; m_iThreadLimit = 0; m_iWriteBufferSize = 0; m_iNzbDirInterval = 0; m_iNzbDirFileAge = 0; m_bParCleanupQueue = false; m_iDiskSpace = 0; m_eProcessLogKind = slNone; m_bAllowReProcess = false; m_bTestBacktrace = false; m_bTLS = false; m_bDumpCore = false; m_bParPauseQueue = false; m_bPostPauseQueue = false; m_bNzbCleanupDisk = false; m_bDeleteCleanupDisk = false; m_bMergeNzb = false; m_iParTimeLimit = 0; m_iKeepHistory = 0; m_bAccurateRate = false; m_EMatchMode = mmID; // Option "ConfigFile" will be initialized later, but we want // to see it at the top of option list, so we add it first SetOption(OPTION_CONFIGFILE, ""); char szFilename[MAX_PATH + 1]; #ifdef WIN32 GetModuleFileName(NULL, szFilename, sizeof(szFilename)); #else Util::ExpandFileName(argv[0], szFilename, sizeof(szFilename)); #endif Util::NormalizePathSeparators(szFilename); SetOption(OPTION_APPBIN, szFilename); char* end = strrchr(szFilename, PATH_SEPARATOR); if (end) *end = '\0'; SetOption(OPTION_APPDIR, szFilename); SetOption(OPTION_VERSION, Util::VersionRevision()); InitDefault(); InitCommandLine(argc, argv); if (argc == 1) { PrintUsage(argv[0]); } if (!m_szConfigFilename && !m_bNoConfig) { if (argc == 1) { printf("\n"); } printf("No configuration-file found\n"); #ifdef WIN32 printf("Please put configuration-file \"nzbget.conf\" into the directory with exe-file\n"); #else printf("Please use option \"-c\" or put configuration-file in one of the following locations:\n"); int p = 0; while (const char* szFilename = PossibleConfigLocations[p++]) { printf("%s\n", szFilename); } #endif exit(-1); } if (argc == 1) { exit(-1); } InitOptions(); if (!m_bPrintOptions) { InitFileArg(argc, argv); } InitServers(); InitCategories(); InitScheduler(); CheckOptions(); if (m_bPrintOptions) { Dump(); exit(-1); } if (m_bConfigErrors && m_eClientOperation == opClientNoOperation) { info("Pausing all activities due to errors in configuration"); m_bPauseDownload = true; m_bPauseDownload2= true; m_bPausePostProcess = true; m_bPauseScan = true; } } Options::~Options() { if (m_szConfigFilename) { free(m_szConfigFilename); } if (m_szDestDir) { free(m_szDestDir); } if (m_szTempDir) { free(m_szTempDir); } if (m_szQueueDir) { free(m_szQueueDir); } if (m_szNzbDir) { free(m_szNzbDir); } if (m_szWebDir) { free(m_szWebDir); } if (m_szArgFilename) { free(m_szArgFilename); } if (m_szAddCategory) { free(m_szAddCategory); } if (m_szEditQueueText) { free(m_szEditQueueText); } if (m_szLastArg) { free(m_szLastArg); } if (m_szControlIP) { free(m_szControlIP); } if (m_szControlPassword) { free(m_szControlPassword); } if (m_szLogFile) { free(m_szLogFile); } if (m_szLockFile) { free(m_szLockFile); } if (m_szDaemonUserName) { free(m_szDaemonUserName); } if (m_szPostProcess) { free(m_szPostProcess); } if (m_szPostConfigFilename) { free(m_szPostConfigFilename); } if (m_szNZBProcess) { free(m_szNZBProcess); } if (m_szNZBAddedProcess) { free(m_szNZBAddedProcess); } if (m_pEditQueueIDList) { free(m_pEditQueueIDList); } if (m_szAddNZBFilename) { free(m_szAddNZBFilename); } for (NameList::iterator it = m_EditQueueNameList.begin(); it != m_EditQueueNameList.end(); it++) { free(*it); } m_EditQueueNameList.clear(); } void Options::Dump() { for (OptEntries::iterator it = m_OptEntries.begin(); it != m_OptEntries.end(); it++) { OptEntry* pOptEntry = *it; printf("%s = \"%s\"\n", pOptEntry->GetName(), pOptEntry->GetValue()); } } void Options::ConfigError(const char* msg, ...) { char tmp2[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp2, 1024, msg, ap); tmp2[1024-1] = '\0'; va_end(ap); printf("%s(%i): %s\n", Util::BaseFileName(m_szConfigFilename), m_iConfigLine, tmp2); error("%s(%i): %s", Util::BaseFileName(m_szConfigFilename), m_iConfigLine, tmp2); m_bConfigErrors = true; } void Options::InitDefault() { #ifdef WIN32 SetOption(OPTION_MAINDIR, "${AppDir}"); #else SetOption(OPTION_MAINDIR, "~/downloads"); #endif SetOption(OPTION_TEMPDIR, "${MainDir}/tmp"); SetOption(OPTION_DESTDIR, "${MainDir}/dst"); SetOption(OPTION_QUEUEDIR, "${MainDir}/queue"); SetOption(OPTION_NZBDIR, "${MainDir}/nzb"); SetOption(OPTION_LOCKFILE, "${MainDir}/nzbget.lock"); SetOption(OPTION_LOGFILE, "${DestDir}/nzbget.log"); SetOption(OPTION_WEBDIR, ""); SetOption(OPTION_CREATELOG, "yes"); SetOption(OPTION_APPENDNZBDIR, "yes"); SetOption(OPTION_APPENDCATEGORYDIR, "yes"); SetOption(OPTION_OUTPUTMODE, "curses"); SetOption(OPTION_DUPECHECK, "yes"); SetOption(OPTION_DOWNLOADRATE, "0"); SetOption(OPTION_RENAMEBROKEN, "no"); SetOption(OPTION_CONTROLIP, "0.0.0.0"); SetOption(OPTION_CONTROLPASSWORD, "tegbzn6789"); SetOption(OPTION_CONTROLPORT, "6789"); SetOption(OPTION_CONNECTIONTIMEOUT, "60"); SetOption(OPTION_SAVEQUEUE, "yes"); SetOption(OPTION_RELOADQUEUE, "yes"); SetOption(OPTION_RELOADURLQUEUE, "yes"); SetOption(OPTION_RELOADPOSTQUEUE, "yes"); SetOption(OPTION_CREATEBROKENLOG, "yes"); SetOption(OPTION_RESETLOG, "no"); SetOption(OPTION_DECODE, "yes"); SetOption(OPTION_RETRIES, "3"); SetOption(OPTION_RETRYINTERVAL, "10"); SetOption(OPTION_TERMINATETIMEOUT, "600"); SetOption(OPTION_CONTINUEPARTIAL, "no"); SetOption(OPTION_URLCONNECTIONS, "4"); SetOption(OPTION_LOGBUFFERSIZE, "1000"); SetOption(OPTION_INFOTARGET, "both"); SetOption(OPTION_WARNINGTARGET, "both"); SetOption(OPTION_ERRORTARGET, "both"); SetOption(OPTION_DEBUGTARGET, "none"); SetOption(OPTION_DETAILTARGET, "both"); SetOption(OPTION_LOADPARS, "one"); SetOption(OPTION_PARCHECK, "no"); SetOption(OPTION_PARREPAIR, "yes"); SetOption(OPTION_PARSCAN, "limited"); SetOption(OPTION_POSTPROCESS, ""); SetOption(OPTION_POSTCONFIGFILE, ""); SetOption(OPTION_NZBPROCESS, ""); SetOption(OPTION_NZBADDEDPROCESS, ""); SetOption(OPTION_STRICTPARNAME, "yes"); SetOption(OPTION_DAEMONUSERNAME, "root"); SetOption(OPTION_UMASK, "1000"); SetOption(OPTION_UPDATEINTERVAL, "200"); SetOption(OPTION_CURSESNZBNAME, "yes"); SetOption(OPTION_CURSESTIME, "no"); SetOption(OPTION_CURSESGROUP, "no"); SetOption(OPTION_CRCCHECK, "yes"); SetOption(OPTION_RETRYONCRCERROR, "no"); SetOption(OPTION_THREADLIMIT, "100"); SetOption(OPTION_DIRECTWRITE, "yes"); SetOption(OPTION_WRITEBUFFERSIZE, "0"); SetOption(OPTION_NZBDIRINTERVAL, "5"); SetOption(OPTION_NZBDIRFILEAGE, "60"); SetOption(OPTION_PARCLEANUPQUEUE, "yes"); SetOption(OPTION_DISKSPACE, "250"); SetOption(OPTION_PROCESSLOGKIND, "detail"); SetOption(OPTION_ALLOWREPROCESS, "no"); SetOption(OPTION_DUMPCORE, "no"); SetOption(OPTION_PARPAUSEQUEUE, "no"); SetOption(OPTION_POSTPAUSEQUEUE, "no"); SetOption(OPTION_NZBCLEANUPDISK, "no"); SetOption(OPTION_DELETECLEANUPDISK, "no"); SetOption(OPTION_MERGENZB, "no"); SetOption(OPTION_PARTIMELIMIT, "0"); SetOption(OPTION_KEEPHISTORY, "7"); SetOption(OPTION_ACCURATERATE, "no"); } void Options::InitOptFile() { if (m_bConfigInitialized) { return; } if (!m_szConfigFilename && !m_bNoConfig) { // search for config file in default locations #ifdef WIN32 char szFilename[MAX_PATH + 1]; GetModuleFileName(NULL, szFilename, MAX_PATH + 1); szFilename[MAX_PATH] = '\0'; Util::NormalizePathSeparators(szFilename); char* end = strrchr(szFilename, PATH_SEPARATOR); if (end) end[1] = '\0'; strcat(szFilename, "nzbget.conf"); if (Util::FileExists(szFilename)) { m_szConfigFilename = strdup(szFilename); } #else int p = 0; while (const char* szFilename = PossibleConfigLocations[p++]) { // substitute HOME-variable char szExpandedFilename[1024]; if (Util::ExpandHomePath(szFilename, szExpandedFilename, sizeof(szExpandedFilename))) { szFilename = szExpandedFilename; } if (Util::FileExists(szFilename)) { m_szConfigFilename = strdup(szFilename); break; } } #endif } if (m_szConfigFilename) { // normalize path in filename char szFilename[MAX_PATH + 1]; Util::ExpandFileName(m_szConfigFilename, szFilename, sizeof(szFilename)); szFilename[MAX_PATH] = '\0'; #ifndef WIN32 // substitute HOME-variable char szExpandedFilename[1024]; if (Util::ExpandHomePath(szFilename, szExpandedFilename, sizeof(szExpandedFilename))) { strncpy(szFilename, szExpandedFilename, sizeof(szFilename)); } #endif free(m_szConfigFilename); m_szConfigFilename = strdup(szFilename); SetOption(OPTION_CONFIGFILE, m_szConfigFilename); LoadConfigFile(); } m_bConfigInitialized = true; } void Options::CheckDir(char** dir, const char* szOptionName, bool bAllowEmpty, bool bCreate) { char* usedir = NULL; const char* tempdir = GetOption(szOptionName); if (tempdir && strlen(tempdir) > 0) { int len = strlen(tempdir); usedir = (char*) malloc(len + 2); strcpy(usedir, tempdir); char ch = usedir[len-1]; if (ch == ALT_PATH_SEPARATOR) { usedir[len-1] = PATH_SEPARATOR; } else if (ch != PATH_SEPARATOR) { usedir[len] = PATH_SEPARATOR; usedir[len + 1] = '\0'; } Util::NormalizePathSeparators(usedir); } else { if (!bAllowEmpty) { ConfigError("Invalid value for option \"%s\": ", szOptionName); } *dir = strdup(""); return; } // Ensure the dir is created if (bCreate && !Util::ForceDirectories(usedir)) { ConfigError("Invalid value for option \"%s\": could not create directory \"%s\"", szOptionName, usedir); } *dir = usedir; } void Options::InitOptions() { CheckDir(&m_szDestDir, OPTION_DESTDIR, false, true); CheckDir(&m_szTempDir, OPTION_TEMPDIR, false, true); CheckDir(&m_szQueueDir, OPTION_QUEUEDIR, false, true); CheckDir(&m_szWebDir, OPTION_WEBDIR, true, false); m_szPostProcess = strdup(GetOption(OPTION_POSTPROCESS)); m_szNZBProcess = strdup(GetOption(OPTION_NZBPROCESS)); m_szNZBAddedProcess = strdup(GetOption(OPTION_NZBADDEDPROCESS)); m_szControlIP = strdup(GetOption(OPTION_CONTROLIP)); m_szControlPassword = strdup(GetOption(OPTION_CONTROLPASSWORD)); m_szLockFile = strdup(GetOption(OPTION_LOCKFILE)); m_szDaemonUserName = strdup(GetOption(OPTION_DAEMONUSERNAME)); m_szLogFile = strdup(GetOption(OPTION_LOGFILE)); m_fDownloadRate = ParseFloatValue(OPTION_DOWNLOADRATE); m_iConnectionTimeout = ParseIntValue(OPTION_CONNECTIONTIMEOUT, 10); m_iTerminateTimeout = ParseIntValue(OPTION_TERMINATETIMEOUT, 10); m_iRetries = ParseIntValue(OPTION_RETRIES, 10); m_iRetryInterval = ParseIntValue(OPTION_RETRYINTERVAL, 10); m_szControlPort = ParseIntValue(OPTION_CONTROLPORT, 10); m_iUrlConnections = ParseIntValue(OPTION_URLCONNECTIONS, 10); m_iLogBufferSize = ParseIntValue(OPTION_LOGBUFFERSIZE, 10); m_iUMask = ParseIntValue(OPTION_UMASK, 8); m_iUpdateInterval = ParseIntValue(OPTION_UPDATEINTERVAL, 10); m_iThreadLimit = ParseIntValue(OPTION_THREADLIMIT, 10); m_iWriteBufferSize = ParseIntValue(OPTION_WRITEBUFFERSIZE, 10); m_iNzbDirInterval = ParseIntValue(OPTION_NZBDIRINTERVAL, 10); m_iNzbDirFileAge = ParseIntValue(OPTION_NZBDIRFILEAGE, 10); m_iDiskSpace = ParseIntValue(OPTION_DISKSPACE, 10); m_iParTimeLimit = ParseIntValue(OPTION_PARTIMELIMIT, 10); m_iKeepHistory = ParseIntValue(OPTION_KEEPHISTORY, 10); CheckDir(&m_szNzbDir, OPTION_NZBDIR, m_iNzbDirInterval == 0, true); m_bCreateBrokenLog = (bool)ParseEnumValue(OPTION_CREATEBROKENLOG, BoolCount, BoolNames, BoolValues); m_bResetLog = (bool)ParseEnumValue(OPTION_RESETLOG, BoolCount, BoolNames, BoolValues); m_bAppendNZBDir = (bool)ParseEnumValue(OPTION_APPENDNZBDIR, BoolCount, BoolNames, BoolValues); m_bAppendCategoryDir = (bool)ParseEnumValue(OPTION_APPENDCATEGORYDIR, BoolCount, BoolNames, BoolValues); m_bContinuePartial = (bool)ParseEnumValue(OPTION_CONTINUEPARTIAL, BoolCount, BoolNames, BoolValues); m_bRenameBroken = (bool)ParseEnumValue(OPTION_RENAMEBROKEN, BoolCount, BoolNames, BoolValues); m_bSaveQueue = (bool)ParseEnumValue(OPTION_SAVEQUEUE, BoolCount, BoolNames, BoolValues); m_bDupeCheck = (bool)ParseEnumValue(OPTION_DUPECHECK, BoolCount, BoolNames, BoolValues); m_bCreateLog = (bool)ParseEnumValue(OPTION_CREATELOG, BoolCount, BoolNames, BoolValues); m_bParCheck = (bool)ParseEnumValue(OPTION_PARCHECK, BoolCount, BoolNames, BoolValues); m_bParRepair = (bool)ParseEnumValue(OPTION_PARREPAIR, BoolCount, BoolNames, BoolValues); m_bStrictParName = (bool)ParseEnumValue(OPTION_STRICTPARNAME, BoolCount, BoolNames, BoolValues); m_bReloadQueue = (bool)ParseEnumValue(OPTION_RELOADQUEUE, BoolCount, BoolNames, BoolValues); m_bReloadUrlQueue = (bool)ParseEnumValue(OPTION_RELOADURLQUEUE, BoolCount, BoolNames, BoolValues); m_bReloadPostQueue = (bool)ParseEnumValue(OPTION_RELOADPOSTQUEUE, BoolCount, BoolNames, BoolValues); m_bCursesNZBName = (bool)ParseEnumValue(OPTION_CURSESNZBNAME, BoolCount, BoolNames, BoolValues); m_bCursesTime = (bool)ParseEnumValue(OPTION_CURSESTIME, BoolCount, BoolNames, BoolValues); m_bCursesGroup = (bool)ParseEnumValue(OPTION_CURSESGROUP, BoolCount, BoolNames, BoolValues); m_bCrcCheck = (bool)ParseEnumValue(OPTION_CRCCHECK, BoolCount, BoolNames, BoolValues); m_bRetryOnCrcError = (bool)ParseEnumValue(OPTION_RETRYONCRCERROR, BoolCount, BoolNames, BoolValues); m_bDirectWrite = (bool)ParseEnumValue(OPTION_DIRECTWRITE, BoolCount, BoolNames, BoolValues); m_bParCleanupQueue = (bool)ParseEnumValue(OPTION_PARCLEANUPQUEUE, BoolCount, BoolNames, BoolValues); m_bDecode = (bool)ParseEnumValue(OPTION_DECODE, BoolCount, BoolNames, BoolValues); m_bAllowReProcess = (bool)ParseEnumValue(OPTION_ALLOWREPROCESS, BoolCount, BoolNames, BoolValues); m_bDumpCore = (bool)ParseEnumValue(OPTION_DUMPCORE, BoolCount, BoolNames, BoolValues); m_bParPauseQueue = (bool)ParseEnumValue(OPTION_PARPAUSEQUEUE, BoolCount, BoolNames, BoolValues); m_bPostPauseQueue = (bool)ParseEnumValue(OPTION_POSTPAUSEQUEUE, BoolCount, BoolNames, BoolValues); m_bNzbCleanupDisk = (bool)ParseEnumValue(OPTION_NZBCLEANUPDISK, BoolCount, BoolNames, BoolValues); m_bDeleteCleanupDisk = (bool)ParseEnumValue(OPTION_DELETECLEANUPDISK, BoolCount, BoolNames, BoolValues); m_bMergeNzb = (bool)ParseEnumValue(OPTION_MERGENZB, BoolCount, BoolNames, BoolValues); m_bAccurateRate = (bool)ParseEnumValue(OPTION_ACCURATERATE, BoolCount, BoolNames, BoolValues); const char* OutputModeNames[] = { "loggable", "logable", "log", "colored", "color", "ncurses", "curses" }; const int OutputModeValues[] = { omLoggable, omLoggable, omLoggable, omColored, omColored, omNCurses, omNCurses }; const int OutputModeCount = 7; m_eOutputMode = (EOutputMode)ParseEnumValue(OPTION_OUTPUTMODE, OutputModeCount, OutputModeNames, OutputModeValues); const char* LoadParsNames[] = { "none", "one", "all", "1", "0" }; const int LoadParsValues[] = { lpNone, lpOne, lpAll, lpOne, lpNone }; const int LoadParsCount = 5; m_eLoadPars = (ELoadPars)ParseEnumValue(OPTION_LOADPARS, LoadParsCount, LoadParsNames, LoadParsValues); const char* ParScanNames[] = { "limited", "full", "auto" }; const int ParScanValues[] = { psLimited, psFull, psAuto }; const int ParScanCount = 3; m_eParScan = (EParScan)ParseEnumValue(OPTION_PARSCAN, ParScanCount, ParScanNames, ParScanValues); const char* TargetNames[] = { "screen", "log", "both", "none" }; const int TargetValues[] = { mtScreen, mtLog, mtBoth, mtNone }; const int TargetCount = 4; m_eInfoTarget = (EMessageTarget)ParseEnumValue(OPTION_INFOTARGET, TargetCount, TargetNames, TargetValues); m_eWarningTarget = (EMessageTarget)ParseEnumValue(OPTION_WARNINGTARGET, TargetCount, TargetNames, TargetValues); m_eErrorTarget = (EMessageTarget)ParseEnumValue(OPTION_ERRORTARGET, TargetCount, TargetNames, TargetValues); m_eDebugTarget = (EMessageTarget)ParseEnumValue(OPTION_DEBUGTARGET, TargetCount, TargetNames, TargetValues); m_eDetailTarget = (EMessageTarget)ParseEnumValue(OPTION_DETAILTARGET, TargetCount, TargetNames, TargetValues); const char* ScriptLogKindNames[] = { "none", "detail", "info", "warning", "error", "debug" }; const int ScriptLogKindValues[] = { slNone, slDetail, slInfo, slWarning, slError, slDebug }; const int ScriptLogKindCount = 6; m_eProcessLogKind = (EScriptLogKind)ParseEnumValue(OPTION_PROCESSLOGKIND, ScriptLogKindCount, ScriptLogKindNames, ScriptLogKindValues); InitPostConfig(); } int Options::ParseEnumValue(const char* OptName, int argc, const char * argn[], const int argv[]) { OptEntry* pOptEntry = FindOption(OptName); if (!pOptEntry) { ConfigError("Undefined value for option \"%s\"", OptName); return argv[0]; //abort("FATAL ERROR: Undefined value for option \"%s\"\n", OptName); } int iDefNum = 0; for (int i = 0; i < argc; i++) { if (!strcasecmp(pOptEntry->GetValue(), argn[i])) { // normalizing option value in option list, for example "NO" -> "no" for (int j = 0; j < argc; j++) { if (argv[j] == argv[i]) { if (strcmp(argn[j], pOptEntry->GetValue())) { pOptEntry->SetValue(argn[j]); } break; } } return argv[i]; } if (!strcasecmp(pOptEntry->GetDefValue(), argn[i])) { iDefNum = i; } } m_iConfigLine = pOptEntry->GetLineNo(); ConfigError("Invalid value for option \"%s\": \"%s\"", OptName, pOptEntry->GetValue()); pOptEntry->SetValue(argn[iDefNum]); return argv[iDefNum]; } int Options::ParseIntValue(const char* OptName, int iBase) { OptEntry* pOptEntry = FindOption(OptName); if (!pOptEntry) { abort("FATAL ERROR: Undefined value for option \"%s\"\n", OptName); } char *endptr; int val = strtol(pOptEntry->GetValue(), &endptr, iBase); if (endptr && *endptr != '\0') { m_iConfigLine = pOptEntry->GetLineNo(); ConfigError("Invalid value for option \"%s\": \"%s\"", OptName, pOptEntry->GetValue()); pOptEntry->SetValue(pOptEntry->GetDefValue()); val = strtol(pOptEntry->GetDefValue(), NULL, iBase); } return val; } float Options::ParseFloatValue(const char* OptName) { OptEntry* pOptEntry = FindOption(OptName); if (!pOptEntry) { abort("FATAL ERROR: Undefined value for option \"%s\"\n", OptName); } char *endptr; float val = (float)strtod(pOptEntry->GetValue(), &endptr); if (endptr && *endptr != '\0') { m_iConfigLine = pOptEntry->GetLineNo(); ConfigError("Invalid value for option \"%s\": \"%s\"", OptName, pOptEntry->GetValue()); pOptEntry->SetValue(pOptEntry->GetDefValue()); val = (float)strtod(pOptEntry->GetDefValue(), NULL); } return val; } void Options::InitPostConfig() { if (!m_szPostProcess || strlen(m_szPostProcess) == 0) { return; } //extract directory of config file name char szConfigFileDir[1024]; strncpy(szConfigFileDir, m_szConfigFilename, 1024); szConfigFileDir[1024-1] = '\0'; char *base = Util::BaseFileName(szConfigFileDir); if (base) { *base = '\0'; } char szPostConfig[1024]; snprintf(szPostConfig, sizeof(szPostConfig) - 1, "%s%s", szConfigFileDir, Util::BaseFileName(m_szPostProcess)); // replace extension with ".conf" char *szExt = strrchr(Util::BaseFileName(szPostConfig), '.'); if (szExt && (szExt + 5 - szPostConfig < sizeof(szPostConfig))) { strncpy(szExt, ".conf\0", 6); } if (Util::FileExists(szPostConfig)) { m_szPostConfigFilename = strdup(szPostConfig); SetOption(OPTION_POSTCONFIGFILE, m_szPostConfigFilename); } } void Options::InitCommandLine(int argc, char* argv[]) { m_eClientOperation = opClientNoOperation; // default // reset getopt optind = 1; while (true) { int c; #ifdef HAVE_GETOPT_LONG int option_index = 0; c = getopt_long(argc, argv, short_options, long_options, &option_index); #else c = getopt(argc, argv, short_options); #endif if (c == -1) break; switch (c) { case 'c': m_szConfigFilename = strdup(optarg); break; case 'n': m_szConfigFilename = NULL; m_bNoConfig = true; break; case 'h': PrintUsage(argv[0]); exit(0); break; case 'v': printf("nzbget version: %s\n", Util::VersionRevision()); exit(1); break; case 'p': m_bPrintOptions = true; break; case 'o': InitOptFile(); if (!SetOptionString(optarg)) { abort("FATAL ERROR: error in option \"%s\"\n", optarg); } break; case 's': m_bServerMode = true; break; case 'D': m_bServerMode = true; m_bDaemonMode = true; break; case 'A': m_eClientOperation = opClientRequestDownload; // default while (true) { optind++; optarg = optind > argc ? NULL : argv[optind-1]; if (optarg && !strcasecmp(optarg, "F")) { m_eClientOperation = opClientRequestDownload; } else if (optarg && !strcasecmp(optarg, "U")) { m_eClientOperation = opClientRequestDownloadUrl; } else if (optarg && !strcasecmp(optarg, "T")) { m_bAddTop = true; } else if (optarg && !strcasecmp(optarg, "P")) { m_bAddPaused = true; } else if (optarg && !strcasecmp(optarg, "I")) { optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'A'\n"); } m_iAddPriority = atoi(argv[optind-1]); } else if (optarg && !strcasecmp(optarg, "C")) { optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'A'\n"); } if (m_szAddCategory) { free(m_szAddCategory); } m_szAddCategory = strdup(argv[optind-1]); } else if (optarg && !strcasecmp(optarg, "N")) { optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'A'\n"); } if (m_szAddNZBFilename) { free(m_szAddNZBFilename); } m_szAddNZBFilename = strdup(argv[optind-1]); } else { optind--; break; } } break; case 'L': optind++; optarg = optind > argc ? NULL : argv[optind-1]; if (!optarg || !strncmp(optarg, "-", 1)) { m_eClientOperation = opClientRequestListFiles; optind--; } else if (!strcasecmp(optarg, "F") || !strcasecmp(optarg, "FR")) { m_eClientOperation = opClientRequestListFiles; } else if (!strcasecmp(optarg, "G") || !strcasecmp(optarg, "GR")) { m_eClientOperation = opClientRequestListGroups; } else if (!strcasecmp(optarg, "O")) { m_eClientOperation = opClientRequestPostQueue; } else if (!strcasecmp(optarg, "S")) { m_eClientOperation = opClientRequestListStatus; } else if (!strcasecmp(optarg, "H")) { m_eClientOperation = opClientRequestHistory; } else if (!strcasecmp(optarg, "U")) { m_eClientOperation = opClientRequestUrlQueue; } else { abort("FATAL ERROR: Could not parse value of option 'L'\n"); } if (optarg && (!strcasecmp(optarg, "FR") || !strcasecmp(optarg, "GR"))) { m_EMatchMode = mmRegEx; optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'L'\n"); } m_szEditQueueText = strdup(argv[optind-1]); } break; case 'P': case 'U': optind++; optarg = optind > argc ? NULL : argv[optind-1]; if (!optarg || !strncmp(optarg, "-", 1)) { m_eClientOperation = c == 'P' ? opClientRequestDownloadPause : opClientRequestDownloadUnpause; optind--; } else if (!strcasecmp(optarg, "D")) { m_eClientOperation = c == 'P' ? opClientRequestDownloadPause : opClientRequestDownloadUnpause; } else if (!strcasecmp(optarg, "D2")) { m_eClientOperation = c == 'P' ? opClientRequestDownload2Pause : opClientRequestDownload2Unpause; } else if (!strcasecmp(optarg, "O")) { m_eClientOperation = c == 'P' ? opClientRequestPostPause : opClientRequestPostUnpause; } else if (!strcasecmp(optarg, "S")) { m_eClientOperation = c == 'P' ? opClientRequestScanPause : opClientRequestScanUnpause; } else { abort("FATAL ERROR: Could not parse value of option '%c'\n", c); } break; case 'R': m_eClientOperation = opClientRequestSetRate; m_fSetRate = (float)atof(optarg); break; case 'B': if (!strcasecmp(optarg, "dump")) { m_eClientOperation = opClientRequestDumpDebug; } else if (!strcasecmp(optarg, "trace")) { m_bTestBacktrace = true; } else { abort("FATAL ERROR: Could not parse value of option 'B'\n"); } break; case 'G': m_eClientOperation = opClientRequestLog; m_iLogLines = atoi(optarg); if (m_iLogLines == 0) { abort("FATAL ERROR: Could not parse value of option 'G'\n"); } break; case 'T': m_bAddTop = true; break; case 'C': m_bRemoteClientMode = true; break; case 'E': { m_eClientOperation = opClientRequestEditQueue; bool bGroup = !strcasecmp(optarg, "G") || !strcasecmp(optarg, "GN") || !strcasecmp(optarg, "GR"); bool bFile = !strcasecmp(optarg, "F") || !strcasecmp(optarg, "FN") || !strcasecmp(optarg, "FR"); if (!strcasecmp(optarg, "GN") || !strcasecmp(optarg, "FN")) { m_EMatchMode = mmName; } else if (!strcasecmp(optarg, "GR") || !strcasecmp(optarg, "FR")) { m_EMatchMode = mmRegEx; } else { m_EMatchMode = mmID; }; bool bPost = !strcasecmp(optarg, "O"); bool bHistory = !strcasecmp(optarg, "H"); if (bGroup || bFile || bPost || bHistory) { optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } optarg = argv[optind-1]; } if (bPost) { // edit-commands for post-processor-queue if (!strcasecmp(optarg, "T")) { m_iEditQueueAction = eRemoteEditActionPostMoveTop; } else if (!strcasecmp(optarg, "B")) { m_iEditQueueAction = eRemoteEditActionPostMoveBottom; } else if (!strcasecmp(optarg, "D")) { m_iEditQueueAction = eRemoteEditActionPostDelete; } else { m_iEditQueueOffset = atoi(optarg); if (m_iEditQueueOffset == 0) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } m_iEditQueueAction = eRemoteEditActionPostMoveOffset; } } else if (bHistory) { // edit-commands for history if (!strcasecmp(optarg, "D")) { m_iEditQueueAction = eRemoteEditActionHistoryDelete; } else if (!strcasecmp(optarg, "R")) { m_iEditQueueAction = eRemoteEditActionHistoryReturn; } else if (!strcasecmp(optarg, "P")) { m_iEditQueueAction = eRemoteEditActionHistoryProcess; } else { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } } else { // edit-commands for download-queue if (!strcasecmp(optarg, "T")) { m_iEditQueueAction = bGroup ? eRemoteEditActionGroupMoveTop : eRemoteEditActionFileMoveTop; } else if (!strcasecmp(optarg, "B")) { m_iEditQueueAction = bGroup ? eRemoteEditActionGroupMoveBottom : eRemoteEditActionFileMoveBottom; } else if (!strcasecmp(optarg, "P")) { m_iEditQueueAction = bGroup ? eRemoteEditActionGroupPause : eRemoteEditActionFilePause; } else if (!strcasecmp(optarg, "A")) { m_iEditQueueAction = bGroup ? eRemoteEditActionGroupPauseAllPars : eRemoteEditActionFilePauseAllPars; } else if (!strcasecmp(optarg, "R")) { m_iEditQueueAction = bGroup ? eRemoteEditActionGroupPauseExtraPars : eRemoteEditActionFilePauseExtraPars; } else if (!strcasecmp(optarg, "U")) { m_iEditQueueAction = bGroup ? eRemoteEditActionGroupResume : eRemoteEditActionFileResume; } else if (!strcasecmp(optarg, "D")) { m_iEditQueueAction = bGroup ? eRemoteEditActionGroupDelete : eRemoteEditActionFileDelete; } else if (!strcasecmp(optarg, "C") || !strcasecmp(optarg, "K")) { // switch "K" is provided for compatibility with v. 0.8.0 and can be removed in future versions if (!bGroup) { abort("FATAL ERROR: Category can be set only for groups\n"); } m_iEditQueueAction = eRemoteEditActionGroupSetCategory; optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } m_szEditQueueText = strdup(argv[optind-1]); } else if (!strcasecmp(optarg, "N")) { if (!bGroup) { abort("FATAL ERROR: Only groups can be renamed\n"); } m_iEditQueueAction = eRemoteEditActionGroupSetName; optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } m_szEditQueueText = strdup(argv[optind-1]); } else if (!strcasecmp(optarg, "M")) { if (!bGroup) { abort("FATAL ERROR: only groups can be merged\n"); } m_iEditQueueAction = eRemoteEditActionGroupMerge; } else if (!strcasecmp(optarg, "O")) { if (!bGroup) { abort("FATAL ERROR: Post-process parameter can be set only for groups\n"); } m_iEditQueueAction = eRemoteEditActionGroupSetParameter; optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } m_szEditQueueText = strdup(argv[optind-1]); if (!strchr(m_szEditQueueText, '=')) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } } else if (!strcasecmp(optarg, "I")) { m_iEditQueueAction = bGroup ? eRemoteEditActionGroupSetPriority : eRemoteEditActionFileSetPriority; optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } m_szEditQueueText = strdup(argv[optind-1]); if (atoi(m_szEditQueueText) == 0 && strcmp("0", m_szEditQueueText)) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } } else { m_iEditQueueOffset = atoi(optarg); if (m_iEditQueueOffset == 0) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } m_iEditQueueAction = bGroup ? eRemoteEditActionGroupMoveOffset : eRemoteEditActionFileMoveOffset; } } break; } case 'Q': m_eClientOperation = opClientRequestShutdown; break; case 'O': m_eClientOperation = opClientRequestReload; break; case 'V': m_eClientOperation = opClientRequestVersion; break; case 'W': m_eClientOperation = opClientRequestWriteLog; if (!strcasecmp(optarg, "I")) { m_iWriteLogKind = (int)Message::mkInfo; } else if (!strcasecmp(optarg, "W")) { m_iWriteLogKind = (int)Message::mkWarning; } else if (!strcasecmp(optarg, "E")) { m_iWriteLogKind = (int)Message::mkError; } else if (!strcasecmp(optarg, "D")) { m_iWriteLogKind = (int)Message::mkDetail; } else if (!strcasecmp(optarg, "G")) { m_iWriteLogKind = (int)Message::mkDebug; } else { abort("FATAL ERROR: Could not parse value of option 'W'\n"); } break; case 'K': // switch "K" is provided for compatibility with v. 0.8.0 and can be removed in future versions if (m_szAddCategory) { free(m_szAddCategory); } m_szAddCategory = strdup(optarg); break; case 'S': optind++; optarg = optind > argc ? NULL : argv[optind-1]; if (!optarg || !strncmp(optarg, "-", 1)) { m_eClientOperation = opClientRequestScanAsync; optind--; } else if (!strcasecmp(optarg, "W")) { m_eClientOperation = opClientRequestScanSync; } else { abort("FATAL ERROR: Could not parse value of option '%c'\n", c); } break; case '?': exit(-1); break; } } if (m_bServerMode && (m_eClientOperation == opClientRequestDownloadPause || m_eClientOperation == opClientRequestDownload2Pause)) { m_bPauseDownload = m_eClientOperation == opClientRequestDownloadPause; m_bPauseDownload2 = m_eClientOperation == opClientRequestDownload2Pause; m_eClientOperation = opClientNoOperation; } InitOptFile(); } void Options::PrintUsage(char* com) { printf("Usage:\n" " %s [switches]\n\n" "Switches:\n" " -h, --help Print this help-message\n" " -v, --version Print version and exit\n" " -c, --configfile Filename of configuration-file\n" " -n, --noconfigfile Prevent loading of configuration-file\n" " (required options must be passed with --option)\n" " -p, --printconfig Print configuration and exit\n" " -o, --option Set or override option in configuration-file\n" " -s, --server Start nzbget as a server in console-mode\n" #ifndef WIN32 " -D, --daemon Start nzbget as a server in daemon-mode\n" #endif " -V, --serverversion Print server's version and exit\n" " -Q, --quit Shutdown server\n" " -O, --reload Reload config and restart all services\n" " -A, --append [F|U] [] Send file/url to server's\n" " download queue\n" " F Send file (default)\n" " U Send url\n" " are (multiple options must be separated with space):\n" " T Add file to the top (beginning) of queue\n" " P Pause added files\n" " C Assign category to nzb-file\n" " N Use this name as nzb-filename (only for URLs)\n" " I Set priority (signed integer)\n" " -C, --connect Attach client to server\n" " -L, --list [F|FR|G|GR|O|U|H|S] [RegEx] Request list of items from server\n" " F List individual files and server status (default)\n" " FR Like \"F\" but apply regular expression filter\n" " G List groups (nzb-files) and server status\n" " GR Like \"G\" but apply regular expression filter\n" " O List post-processor-queue\n" " U List url-queue\n" " H List history\n" " S Print only server status\n" " Regular expression (only with options \"FR\", \"GR\")\n" " using POSIX Extended Regular Expression Syntax\n" " -P, --pause [D|D2|O|S] Pause server\n" " D Pause download queue (default)\n" " D2 Pause download queue via second pause-register\n" " O Pause post-processor queue\n" " S Pause scan of incoming nzb-directory\n" " -U, --unpause [D|D2|O|S] Unpause server\n" " D Unpause download queue (default)\n" " D2 Unpause download queue via second pause-register\n" " O Unpause post-processor queue\n" " S Unpause scan of incoming nzb-directory\n" " -R, --rate Set download rate on server, in KB/s\n" " -G, --log Request last lines from server's screen-log\n" " -W, --write \"Text\" Send text to server's log\n" " -S, --scan [W] Scan incoming nzb-directory on server\n" " W Wait until scan completes (synchronous mode)\n" " -E, --edit [F|FN|FR|G|GN|GR|O|H] Edit items\n" " on server\n" " F Edit individual files (default)\n" " FN Like \"F\" but uses names (as \"group/file\")\n" " instead of IDs\n" " FR Like \"FN\" but with regular expressions\n" " G Edit all files in the group (same nzb-file)\n" " GN Like \"G\" but uses group names instead of IDs\n" " GR Like \"GN\" but with regular expressions\n" " O Edit post-processor-queue\n" " H Edit history\n" " is one of:\n" " <+offset|-offset> Move file(s)/group(s)/post-job in queue relative to\n" " current position, offset is an integer value\n" " T Move file(s)/group(s)/post-job to top of queue\n" " B Move file(s)/group(s)/post-job to bottom of queue\n" " P Pause file(s)/group(s)/\n" " Postprocess history-item(s) again\n" " U Resume (unpause) file(s)/group(s)\n" " A Pause all pars (for groups)\n" " R Pause extra pars (for groups)/\n" " Return history-item(s) back to download queue\n" " D Delete file(s)/group(s)/post-job(s)/history-item(s)\n" " C Set category (for groups)\n" " N Rename (for groups)\n" " M Merge (for groups)\n" " O = Set post-process parameter (for groups)\n" " I Set priority (signed integer) for file(s)/group(s)\n" " Comma-separated list of file-ids or ranges\n" " of file-ids, e. g.: 1-5,3,10-22\n" " List of names (with options \"FN\" and \"GN\"),\n" " e. g.: \"my nzb download%cmyfile.nfo\" \"another nzb\"\n" " List of regular expressions (options \"FR\", \"GR\")\n" " using POSIX Extended Regular Expression Syntax", Util::BaseFileName(com), PATH_SEPARATOR, PATH_SEPARATOR); } void Options::InitFileArg(int argc, char* argv[]) { if (optind >= argc) { // no nzb-file passed if (!m_bServerMode && !m_bRemoteClientMode && (m_eClientOperation == opClientNoOperation || m_eClientOperation == opClientRequestDownload || m_eClientOperation == opClientRequestWriteLog)) { if (m_eClientOperation == opClientRequestWriteLog) { abort("FATAL ERROR: Log-text not specified\n"); } else { abort("FATAL ERROR: Nzb-file not specified\n"); } } } else if (m_eClientOperation == opClientRequestEditQueue) { if (m_EMatchMode == mmID) { ParseFileIDList(argc, argv, optind); } else { ParseFileNameList(argc, argv, optind); } } else { m_szLastArg = strdup(argv[optind]); // Check if the file-name is a relative path or an absolute path // If the path starts with '/' its an absolute, else relative const char* szFileName = argv[optind]; #ifdef WIN32 m_szArgFilename = strdup(szFileName); #else if (szFileName[0] == '/') { m_szArgFilename = strdup(szFileName); } else { // TEST char szFileNameWithPath[1024]; getcwd(szFileNameWithPath, 1024); strcat(szFileNameWithPath, "/"); strcat(szFileNameWithPath, szFileName); m_szArgFilename = strdup(szFileNameWithPath); } #endif if (m_bServerMode || m_bRemoteClientMode || !(m_eClientOperation == opClientNoOperation || m_eClientOperation == opClientRequestDownload || m_eClientOperation == opClientRequestDownloadUrl || m_eClientOperation == opClientRequestWriteLog)) { abort("FATAL ERROR: Too many arguments\n"); } } } void Options::SetOption(const char* optname, const char* value) { OptEntry* pOptEntry = FindOption(optname); if (!pOptEntry) { pOptEntry = new OptEntry(); pOptEntry->SetName(optname); m_OptEntries.push_back(pOptEntry); } char* curvalue = NULL; #ifndef WIN32 if (value && (value[0] == '~') && (value[1] == '/')) { char szExpandedPath[1024]; if (!Util::ExpandHomePath(value, szExpandedPath, sizeof(szExpandedPath))) { ConfigError("Invalid value for option\"%s\": unable to determine home-directory", optname); szExpandedPath[0] = '\0'; } curvalue = strdup(szExpandedPath); } else #endif { curvalue = strdup(value); } pOptEntry->SetLineNo(m_iConfigLine); bool bOK = true; // expand variables while (char* dollar = strstr(curvalue, "${")) { char* end = strchr(dollar, '}'); if (end) { int varlen = (int)(end - dollar - 2); char variable[101]; int maxlen = varlen < 100 ? varlen : 100; strncpy(variable, dollar + 2, maxlen); variable[maxlen] = '\0'; const char* varvalue = GetOption(variable); if (varvalue) { int newlen = strlen(varvalue); char* newvalue = (char*)malloc(strlen(curvalue) - varlen - 3 + newlen + 1); strncpy(newvalue, curvalue, dollar - curvalue); strncpy(newvalue + (dollar - curvalue), varvalue, newlen); strcpy(newvalue + (dollar - curvalue) + newlen, end + 1); free(curvalue); curvalue = newvalue; } else { ConfigError("Invalid value for option \"%s\": variable \"%s\" not found", optname, variable); bOK = false; break; } } else { ConfigError("Invalid value for option \"%s\": syntax error in variable-substitution \"%s\"", optname, curvalue); bOK = false; break; } } if (bOK) { pOptEntry->SetValue(curvalue); } free(curvalue); } Options::OptEntry* Options::FindOption(const char* optname) { OptEntry* pOptEntry = m_OptEntries.FindOption(optname); // normalize option name in option list; for example "server1.joingroup" -> "Server1.JoinGroup" if (pOptEntry && strcmp(pOptEntry->GetName(), optname)) { pOptEntry->SetName(optname); } return pOptEntry; } const char* Options::GetOption(const char* optname) { OptEntry* pOptEntry = FindOption(optname); if (pOptEntry) { if (pOptEntry->GetLineNo() > 0) { m_iConfigLine = pOptEntry->GetLineNo(); } return pOptEntry->GetValue(); } return NULL; } void Options::InitServers() { int n = 1; while (true) { char optname[128]; sprintf(optname, "Server%i.Level", n); const char* nlevel = GetOption(optname); sprintf(optname, "Server%i.Host", n); const char* nhost = GetOption(optname); sprintf(optname, "Server%i.Port", n); const char* nport = GetOption(optname); sprintf(optname, "Server%i.Username", n); const char* nusername = GetOption(optname); sprintf(optname, "Server%i.Password", n); const char* npassword = GetOption(optname); sprintf(optname, "Server%i.JoinGroup", n); const char* njoingroup = GetOption(optname); bool bJoinGroup = true; if (njoingroup) { bJoinGroup = (bool)ParseEnumValue(optname, BoolCount, BoolNames, BoolValues); } sprintf(optname, "Server%i.Encryption", n); const char* ntls = GetOption(optname); bool bTLS = false; if (ntls) { bTLS = (bool)ParseEnumValue(optname, BoolCount, BoolNames, BoolValues); #ifdef DISABLE_TLS if (bTLS) { ConfigError("Invalid value for option \"%s\": program was compiled without TLS/SSL-support", optname); bTLS = false; } #endif m_bTLS |= bTLS; } sprintf(optname, "Server%i.Connections", n); const char* nconnections = GetOption(optname); bool definition = nlevel || nhost || nport || nusername || npassword || nconnections || njoingroup || ntls; bool completed = nlevel && nhost && nport && nconnections; if (!definition) { break; } if (completed) { NewsServer* pNewsServer = new NewsServer(nhost, atoi(nport), nusername, npassword, bJoinGroup, bTLS, atoi((char*)nconnections), atoi((char*)nlevel)); g_pServerPool->AddServer(pNewsServer); } else { ConfigError("Server definition not complete for \"Server%i\"", n); } n++; } g_pServerPool->SetTimeout(GetConnectionTimeout()); } void Options::InitCategories() { int n = 1; while (true) { char optname[128]; sprintf(optname, "Category%i.Name", n); const char* nname = GetOption(optname); sprintf(optname, "Category%i.DestDir", n); const char* ndestdir = GetOption(optname); bool definition = nname || ndestdir; bool completed = nname; if (!definition) { break; } if (completed) { Category* pCategory = new Category(nname, ndestdir); m_Categories.push_back(pCategory); } else { ConfigError("Category definition not complete for \"Category%i\"", n); } n++; } } void Options::InitScheduler() { int n = 1; while (true) { char optname[128]; sprintf(optname, "Task%i.Time", n); const char* szTime = GetOption(optname); sprintf(optname, "Task%i.WeekDays", n); const char* szWeekDays = GetOption(optname); sprintf(optname, "Task%i.Command", n); const char* szCommand = GetOption(optname); sprintf(optname, "Task%i.DownloadRate", n); const char* szDownloadRate = GetOption(optname); sprintf(optname, "Task%i.Process", n); const char* szProcess = GetOption(optname); bool definition = szTime || szWeekDays || szCommand || szDownloadRate || szProcess; bool completed = szTime && szCommand; if (!definition) { break; } bool bOK = true; if (!completed) { ConfigError("Task definition not complete for \"Task%i\"", n); bOK = false; } if (szProcess && strlen(szProcess) > 0 && !Util::SplitCommandLine(szProcess, NULL)) { ConfigError("Invalid value for option \"Task%i.Process\"", n); bOK = false; } snprintf(optname, sizeof(optname), "Task%i.Command", n); optname[sizeof(optname)-1] = '\0'; const char* CommandNames[] = { "pausedownload", "pause", "unpausedownload", "resumedownload", "unpause", "resume", "downloadrate", "setdownloadrate", "rate", "speed", "script", "process", "pausescan", "unpausescan", "resumescan" }; const int CommandValues[] = { Scheduler::scPauseDownload, Scheduler::scPauseDownload, Scheduler::scUnpauseDownload, Scheduler::scUnpauseDownload, Scheduler::scUnpauseDownload, Scheduler::scUnpauseDownload, Scheduler::scDownloadRate, Scheduler::scDownloadRate, Scheduler::scDownloadRate, Scheduler::scDownloadRate, Scheduler::scProcess, Scheduler::scProcess, Scheduler::scPauseScan, Scheduler::scUnpauseScan, Scheduler::scUnpauseScan }; const int CommandCount = 15; Scheduler::ECommand eCommand = (Scheduler::ECommand)ParseEnumValue(optname, CommandCount, CommandNames, CommandValues); int iWeekDays = 0; if (szWeekDays && !ParseWeekDays(szWeekDays, &iWeekDays)) { ConfigError("Invalid value for option \"Task%i.WeekDays\": \"%s\"", n, szWeekDays); bOK = false; } int iDownloadRate = 0; if (eCommand == Scheduler::scDownloadRate) { if (szDownloadRate) { char* szErr; iDownloadRate = strtol(szDownloadRate, &szErr, 10); if (!szErr || *szErr != '\0' || iDownloadRate < 0) { ConfigError("Invalid value for option \"Task%i.DownloadRate\": \"%s\"", n, szDownloadRate); bOK = false; } } else { ConfigError("Task definition not complete for \"Task%i\". Option \"Task%i.DownloadRate\" is missing", n, n); bOK = false; } } if (eCommand == Scheduler::scProcess && (!szProcess || strlen(szProcess) == 0)) { ConfigError("Task definition not complete for \"Task%i\". Option \"Task%i.Process\" is missing", n, n); bOK = false; } int iHours, iMinutes; const char** pTime = &szTime; while (*pTime) { if (!ParseTime(pTime, &iHours, &iMinutes)) { ConfigError("Invalid value for option \"Task%i.Time\": \"%s\"", n, pTime); bOK = false; break; } if (bOK) { if (iHours == -1) { for (int iEveryHour = 0; iEveryHour < 24; iEveryHour++) { Scheduler::Task* pTask = new Scheduler::Task(iEveryHour, iMinutes, iWeekDays, eCommand, iDownloadRate, szProcess); g_pScheduler->AddTask(pTask); } } else { Scheduler::Task* pTask = new Scheduler::Task(iHours, iMinutes, iWeekDays, eCommand, iDownloadRate, szProcess); g_pScheduler->AddTask(pTask); } } } n++; } } /* * Parses Time string and moves current string pointer to the next time token. */ bool Options::ParseTime(const char** pTime, int* pHours, int* pMinutes) { const char* szTime = *pTime; const char* szComma = strchr(szTime, ','); int iColons = 0; const char* p = szTime; while (*p && (!szComma || p != szComma)) { if (!strchr("0123456789: *", *p)) { return false; } if (*p == ':') { iColons++; } p++; } if (iColons != 1) { return false; } const char* szColon = strchr(szTime, ':'); if (!szColon) { return false; } if (szTime[0] == '*') { *pHours = -1; } else { *pHours = atoi(szTime); if (*pHours < 0 || *pHours > 23) { return false; } } if (szColon[1] == '*') { return false; } *pMinutes = atoi(szColon + 1); if (*pMinutes < 0 || *pMinutes > 59) { return false; } if (szComma) { *pTime = szComma + 1; } else { *pTime = NULL; } return true; } bool Options::ParseWeekDays(const char* szWeekDays, int* pWeekDaysBits) { *pWeekDaysBits = 0; const char* p = szWeekDays; int iFirstDay = 0; bool bRange = false; while (*p) { if (strchr("1234567", *p)) { int iDay = *p - '0'; if (bRange) { if (iDay <= iFirstDay || iFirstDay == 0) { return false; } for (int i = iFirstDay; i <= iDay; i++) { *pWeekDaysBits |= 1 << (i - 1); } iFirstDay = 0; } else { *pWeekDaysBits |= 1 << (iDay - 1); iFirstDay = iDay; } bRange = false; } else if (*p == ',') { bRange = false; } else if (*p == '-') { bRange = true; } else if (*p == ' ') { // skip spaces } else { return false; } p++; } return true; } void Options::LoadConfigFile() { FILE* infile = fopen(m_szConfigFilename, "rb"); if (!infile) { abort("FATAL ERROR: Could not open file %s\n", m_szConfigFilename); } m_iConfigLine = 0; int iLine = 0; char buf[1024]; while (fgets(buf, sizeof(buf) - 1, infile)) { m_iConfigLine = ++iLine; if (buf[0] != 0 && buf[strlen(buf)-1] == '\n') { buf[strlen(buf)-1] = 0; // remove traling '\n' } if (buf[0] != 0 && buf[strlen(buf)-1] == '\r') { buf[strlen(buf)-1] = 0; // remove traling '\r' (for windows line endings) } if (buf[0] == 0 || buf[0] == '#' || strspn(buf, " ") == strlen(buf)) { continue; } SetOptionString(buf); } fclose(infile); m_iConfigLine = 0; } bool Options::SetOptionString(const char * option) { const char* eq = strchr(option, '='); if (eq) { char optname[1001]; char optvalue[1001]; int maxlen = (int)(eq - option < 1000 ? eq - option : 1000); strncpy(optname, option, maxlen); optname[maxlen] = '\0'; strncpy(optvalue, eq + 1, 1000); optvalue[1000] = '\0'; if (strlen(optname) > 0) { ConvertOldOptionName(optname, sizeof(optname)); if (!ValidateOptionName(optname)) { ConfigError("Invalid option \"%s\"", optname); return false; } SetOption(optname, optvalue); } return true; } else { ConfigError("Invalid option \"%s\"", option); return false; } } bool Options::ValidateOptionName(const char * optname) { if (!strcasecmp(optname, OPTION_CONFIGFILE) || !strcasecmp(optname, OPTION_APPBIN) || !strcasecmp(optname, OPTION_APPDIR) || !strcasecmp(optname, OPTION_VERSION)) { // read-only options return false; } const char* v = GetOption(optname); if (v) { // it's predefined option, OK return true; } if (!strncasecmp(optname, "server", 6)) { char* p = (char*)optname + 6; while (*p >= '0' && *p <= '9') p++; if (p && (!strcasecmp(p, ".level") || !strcasecmp(p, ".host") || !strcasecmp(p, ".port") || !strcasecmp(p, ".username") || !strcasecmp(p, ".password") || !strcasecmp(p, ".joingroup") || !strcasecmp(p, ".encryption") || !strcasecmp(p, ".connections"))) { return true; } } if (!strncasecmp(optname, "task", 4)) { char* p = (char*)optname + 4; while (*p >= '0' && *p <= '9') p++; if (p && (!strcasecmp(p, ".time") || !strcasecmp(p, ".weekdays") || !strcasecmp(p, ".command") || !strcasecmp(p, ".downloadrate") || !strcasecmp(p, ".process"))) { return true; } } if (!strncasecmp(optname, "category", 8)) { char* p = (char*)optname + 8; while (*p >= '0' && *p <= '9') p++; if (p && (!strcasecmp(p, ".name") || !strcasecmp(p, ".destdir"))) { return true; } } // suppress abort on obsolete options; print a warning message instead if (!strcasecmp(optname, OPTION_POSTLOGKIND) || !strcasecmp(optname, OPTION_NZBLOGKIND)) { ConfigError("Option \"%s\" is obsolete, use \"%s\" instead", optname, OPTION_PROCESSLOGKIND); return true; } return false; } void Options::CheckOptions() { #ifdef DISABLE_PARCHECK if (m_bParCheck) { ConfigError("Invalid value for option \"%s\": program was compiled without parcheck-support", OPTION_PARCHECK); } #endif #ifdef DISABLE_CURSES if (m_eOutputMode == omNCurses) { ConfigError("Invalid value for option \"%s\": program was compiled without curses-support", OPTION_OUTPUTMODE); } #endif if (!m_bDecode) { m_bDirectWrite = false; } } void Options::ParseFileIDList(int argc, char* argv[], int optind) { std::vector IDs; IDs.clear(); while (optind < argc) { char* szWritableFileIDList = strdup(argv[optind++]); char* optarg = strtok(szWritableFileIDList, ", "); while (optarg) { int iEditQueueIDFrom = 0; int iEditQueueIDTo = 0; const char* p = strchr(optarg, '-'); if (p) { char buf[101]; int maxlen = (int)(p - optarg < 100 ? p - optarg : 100); strncpy(buf, optarg, maxlen); buf[maxlen] = '\0'; iEditQueueIDFrom = atoi(buf); iEditQueueIDTo = atoi(p + 1); if (iEditQueueIDFrom <= 0 || iEditQueueIDTo <= 0) { abort("FATAL ERROR: invalid list of file IDs\n"); } } else { iEditQueueIDFrom = atoi(optarg); if (iEditQueueIDFrom <= 0) { abort("FATAL ERROR: invalid list of file IDs\n"); } iEditQueueIDTo = iEditQueueIDFrom; } int iEditQueueIDCount = 0; if (iEditQueueIDTo != 0) { if (iEditQueueIDFrom < iEditQueueIDTo) { iEditQueueIDCount = iEditQueueIDTo - iEditQueueIDFrom + 1; } else { iEditQueueIDCount = iEditQueueIDFrom - iEditQueueIDTo + 1; } } else { iEditQueueIDCount = 1; } for (int i = 0; i < iEditQueueIDCount; i++) { if (iEditQueueIDFrom < iEditQueueIDTo || iEditQueueIDTo == 0) { IDs.push_back(iEditQueueIDFrom + i); } else { IDs.push_back(iEditQueueIDFrom - i); } } optarg = strtok(NULL, ", "); } free(szWritableFileIDList); } m_iEditQueueIDCount = IDs.size(); m_pEditQueueIDList = (int*)malloc(sizeof(int) * m_iEditQueueIDCount); for (int i = 0; i < m_iEditQueueIDCount; i++) { m_pEditQueueIDList[i] = IDs[i]; } } void Options::ParseFileNameList(int argc, char* argv[], int optind) { while (optind < argc) { m_EditQueueNameList.push_back(strdup(argv[optind++])); } } Options::OptEntries* Options::LockOptEntries() { m_mutexOptEntries.Lock(); return &m_OptEntries; } void Options::UnlockOptEntries() { m_mutexOptEntries.Unlock(); } bool Options::LoadConfig(EDomain eDomain, OptEntries* pOptEntries) { const char* szConfigFile = NULL; if (eDomain == dmServer) { szConfigFile = GetConfigFilename(); } else if (eDomain == dmPostProcess) { szConfigFile = GetPostConfigFilename(); if (!szConfigFile) { return true; } } // read config file FILE* infile = fopen(szConfigFile, "rb"); if (!infile) { return false; } char buf[1024]; while (fgets(buf, sizeof(buf) - 1, infile)) { // remove trailing '\n' and '\r' and spaces Util::TrimRight(buf); // skip comments and empty lines if (buf[0] == 0 || buf[0] == '#' || strspn(buf, " ") == strlen(buf)) { continue; } const char* eq = strchr(buf, '='); if (eq) { char optname[1024]; char optvalue[1024]; int len = (int)(eq - buf); strncpy(optname, buf, len); optname[len] = '\0'; strncpy(optvalue, eq + 1, 1024); optvalue[1024-1] = '\0'; if (strlen(optname) > 0) { if (eDomain == dmServer) { ConvertOldOptionName(optname, sizeof(optname)); } OptEntry* pOptEntry = new OptEntry(); pOptEntry->SetName(optname); pOptEntry->SetValue(optvalue); pOptEntries->push_back(pOptEntry); } } } fclose(infile); return true; } bool Options::SaveConfig(EDomain eDomain, OptEntries* pOptEntries) { const char* szConfigFile = NULL; if (eDomain == dmServer) { szConfigFile = GetConfigFilename(); } else if (eDomain == dmPostProcess) { szConfigFile = GetPostConfigFilename(); if (!szConfigFile) { return true; } } // save to config file FILE* infile = fopen(szConfigFile, "r+b"); if (!infile) { return false; } std::vector config; std::set writtenOptions; // read config file into memory array char buf[1024]; char val[1024]; while (fgets(buf, sizeof(buf) - 1, infile)) { config.push_back(strdup(buf)); } // write config file back to disk, replace old vcalues of existing options with new values rewind(infile); for (std::vector::iterator it = config.begin(); it != config.end(); it++) { char* buf = *it; const char* eq = strchr(buf, '='); if (eq && buf[0] != '#') { // remove trailing '\n' and '\r' and spaces Util::TrimRight(buf); char optname[1024]; char optvalue[1024]; int len = (int)(eq - buf); strncpy(optname, buf, len); optname[len] = '\0'; strncpy(optvalue, eq + 1, 1024); optvalue[1024-1] = '\0'; if (strlen(optname) > 0) { if (eDomain == dmServer) { ConvertOldOptionName(optname, sizeof(optname)); } OptEntry *pOptEntry = pOptEntries->FindOption(optname); if (pOptEntry) { snprintf(val, sizeof(val) - 1, "%s=%s\n", pOptEntry->GetName(), pOptEntry->GetValue()); val[sizeof(val) - 1] = '\0'; fputs(val, infile); writtenOptions.insert(pOptEntry); } } } else { fputs(buf, infile); } free(buf); } // write new options for (Options::OptEntries::iterator it = pOptEntries->begin(); it != pOptEntries->end(); it++) { Options::OptEntry* pOptEntry = *it; std::set::iterator fit = writtenOptions.find(pOptEntry); if (fit == writtenOptions.end() && strlen(pOptEntry->GetValue()) > 0) { snprintf(val, sizeof(val) - 1, "%s=%s\n", pOptEntry->GetName(), pOptEntry->GetValue()); val[sizeof(val) - 1] = '\0'; fputs(val, infile); } } // close and truncate the file int pos = ftell(infile); fclose(infile); Util::TruncateFile(szConfigFile, pos); return true; } void Options::ConvertOldOptionName(char *szOption, int iBufLen) { // for compatibility with older versions accept old option names if (!strcasecmp(szOption, "$MAINDIR")) { strncpy(szOption, "MainDir", iBufLen); } if (!strcasecmp(szOption, "ServerIP")) { strncpy(szOption, "ControlIP", iBufLen); } if (!strcasecmp(szOption, "ServerPort")) { strncpy(szOption, "ControlPort", iBufLen); } if (!strcasecmp(szOption, "ServerPassword")) { strncpy(szOption, "ControlPassword", iBufLen); } szOption[iBufLen-1] = '\0'; }