mirror of
https://github.com/WinampDesktop/winamp.git
synced 2024-09-24 15:54:12 +00:00
1213 lines
38 KiB
C++
1213 lines
38 KiB
C++
#ifdef _WIN32
|
|
#undef _WIN32_WINNT
|
|
#define _WIN32_WINNT 0x0601
|
|
#include <winsock2.h>
|
|
#include <conio.h>
|
|
#else
|
|
#include <sys/resource.h>
|
|
#include <termios.h>
|
|
#endif
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string>
|
|
#include "services/stdServiceImpl.h"
|
|
#include "file/fileUtils.h"
|
|
#include "global.h"
|
|
#include "threadedRunner.h"
|
|
#include "protocol_relay.h"
|
|
#include "w3cLog.h"
|
|
#include "yp2.h"
|
|
#include "updater.h"
|
|
#include "auth.h"
|
|
#include "streamData.h"
|
|
#include "adminList.h"
|
|
#include "banList.h"
|
|
#include "ripList.h"
|
|
#include "agentList.h"
|
|
#include "cpucount.h"
|
|
#include "stats.h"
|
|
#include "bandwidth.h"
|
|
#include "cache.h"
|
|
|
|
#ifdef _WIN32
|
|
#define _WSPIAPI_H_
|
|
#endif
|
|
//#include <GeoIP.h>
|
|
|
|
using namespace std;
|
|
using namespace uniString;
|
|
using namespace stringUtil;
|
|
|
|
#define LOGNAME "[MAIN] "
|
|
|
|
#ifdef _WIN32
|
|
#include <Mswsock.h>
|
|
static void win32SocketSetup() throw(runtime_error)
|
|
{
|
|
WSADATA wsaData = {0};
|
|
WORD wVersionRequested = MAKEWORD( 2, 2 );
|
|
int err = WSAStartup(wVersionRequested, &wsaData);
|
|
if (err != 0)
|
|
{
|
|
throw runtime_error("Could not find usable Winsock DLL");
|
|
}
|
|
|
|
/* Confirm that the WinSock DLL supports 2.2.*/
|
|
/* Note that if the DLL supports versions greater */
|
|
/* than 2.2 in addition to 2.2, it will still return */
|
|
/* 2.2 in wVersion since that is the version we */
|
|
/* requested. */
|
|
|
|
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
|
|
{
|
|
/* Tell the user that we could not find a usable */
|
|
/* WinSock DLL. */
|
|
WSACleanup();
|
|
throw runtime_error("Could not find appropriate winsock dll version");
|
|
}
|
|
/* The WinSock DLL is acceptable. Proceed. */
|
|
|
|
// if we can detect WSAPoll(..) then we'll use that over
|
|
// select which means Vista+ should be similar to Linux.
|
|
HINSTANCE ws2_32 = GetModuleHandle(TEXT("WS2_32.DLL"));
|
|
if (ws2_32 != NULL)
|
|
{
|
|
typedef INT (WSAAPI *LPFN_WSAPOLL)(LPWSAPOLLFD fdarray, ULONG nfds, INT timeout);
|
|
extern LPFN_WSAPOLL fnWSAPoll;
|
|
fnWSAPoll = (LPFN_WSAPOLL)GetProcAddress(ws2_32, "WSAPoll");
|
|
}
|
|
}
|
|
|
|
static void win32SocketCleanup() throw()
|
|
{
|
|
WSACleanup();
|
|
}
|
|
#else
|
|
int _kbhit(void)
|
|
{
|
|
struct termios oldt, newt;
|
|
tcgetattr(STDIN_FILENO, &oldt);
|
|
newt = oldt;
|
|
newt.c_lflag &= ~(ICANON | ECHO);
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
|
|
int oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
|
|
fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
|
|
|
|
int ch = getchar();
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
|
|
fcntl(STDIN_FILENO, F_SETFL, oldf);
|
|
|
|
return (ch != EOF);
|
|
}
|
|
#endif
|
|
|
|
void scheduleRelay(const config::streamConfig &info) throw()
|
|
{
|
|
// only attempt to run if not indicated as having moved
|
|
if(gOptions.stream_movedUrl(info.m_streamID).empty())
|
|
{
|
|
threadedRunner::scheduleRunnable(new protocol_relay(info));
|
|
}
|
|
}
|
|
|
|
utf8 getLogFile(utf8 fileName)
|
|
{
|
|
#ifdef _WIN32
|
|
// this will fill in the default log path as required
|
|
wchar_t s_defaultFileName[MAX_PATH] = {0};
|
|
ExpandEnvironmentStringsW(DEFAULT_LOGW, s_defaultFileName, MAX_PATH);
|
|
utf8 m_defaultFilename(utf32(s_defaultFileName).toUtf8()), m_fileName = fileName;
|
|
|
|
HANDLE m_file = ::CreateFileW(m_fileName.toWString().c_str(),GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
|
|
if (m_file == INVALID_HANDLE_VALUE)
|
|
{
|
|
m_file = ::CreateFileW(m_defaultFilename.toWString().c_str(),GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
|
|
if (m_file == INVALID_HANDLE_VALUE)
|
|
{
|
|
uniFile::filenameType fallbackFilename("%temp%\\sc_serv_" + tos(getpid()) + ".log");
|
|
wchar_t s_fallbackFileName[MAX_PATH] = {0};
|
|
ExpandEnvironmentStringsW(fallbackFilename.toWString().c_str(), s_fallbackFileName, MAX_PATH);
|
|
utf8 m_fallbackFilename(utf32(s_fallbackFileName).toUtf8());
|
|
|
|
m_file = ::CreateFileW(m_fallbackFilename.toWString().c_str(),GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
|
|
if (m_file != INVALID_HANDLE_VALUE)
|
|
{
|
|
forgetHandleInvalid(m_file);
|
|
}
|
|
else
|
|
{
|
|
m_fileName = m_fallbackFilename;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_fileName = m_defaultFilename;
|
|
}
|
|
}
|
|
#else
|
|
utf8 m_defaultFilename = gOptions.logFile_Default(), m_fileName = fileName;
|
|
|
|
int m_file = ::open(m_fileName.hideAsString().c_str(),O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
|
|
if (m_file == -1)
|
|
{
|
|
m_file = ::open(m_defaultFilename.hideAsString().c_str(),O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
|
|
if (m_file == -1)
|
|
{
|
|
uniFile::filenameType fallbackFilename("/tmp/sc_serv_" + tos(getpid()) + ".log");
|
|
m_file = ::open(fallbackFilename.hideAsString().c_str(),O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
|
|
if (m_file == -1)
|
|
{
|
|
throw runtime_error("Logger could not open the log file \"" + m_fileName.hideAsString() + "\" for writing [" + errMessage().hideAsString() + "]. Check the directory exists and another instance is not already running.");
|
|
}
|
|
else
|
|
{
|
|
m_fileName = fallbackFilename;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_fileName = m_defaultFilename;
|
|
}
|
|
}
|
|
#endif
|
|
return m_fileName;
|
|
}
|
|
|
|
// event triggers for main loop
|
|
static vector<HANDLE> s_ControlEvents;
|
|
|
|
class sc_serv2_service
|
|
{
|
|
// log messages that have been stuffed in config objects deferred list.
|
|
// this only need be called at startup, to display messages that were created during
|
|
// system initialization, but could not be displayed because the loggers didn't yet exist.
|
|
static void logDeferredMessages()
|
|
{
|
|
const vector<utf8> &deferred_error_log_messages(gOptions.deferredErrorLogMessages());
|
|
for (vector<utf8>::const_iterator i = deferred_error_log_messages.begin(); i != deferred_error_log_messages.end(); ++i)
|
|
{
|
|
ELOG((*i).hideAsString());
|
|
}
|
|
|
|
const vector<utf8> &deferred_warn_log_messages(gOptions.deferredWarnLogMessages());
|
|
for (vector<utf8>::const_iterator i = deferred_warn_log_messages.begin(); i != deferred_warn_log_messages.end(); ++i)
|
|
{
|
|
WLOG((*i).hideAsString());
|
|
}
|
|
|
|
gOptions.clearDeferredErrorLogMessages();
|
|
gOptions.clearDeferredWarnLogMessages();
|
|
}
|
|
|
|
public:
|
|
static void addCustomLogElements() {}
|
|
sc_serv2_service() {}
|
|
|
|
// main application loop
|
|
static int go(stdServiceBase &base) throw()
|
|
{
|
|
g_upTime = ::time(NULL);
|
|
g_userAgent = g_userAgentBase + gOptions.getVersionBuildStrings();
|
|
#ifdef _WIN32
|
|
bool win32_socket_cleanup_required(false);
|
|
#endif
|
|
int mainResult = 0; // result of main loop
|
|
utf8 pidFn;
|
|
vector<threadedRunner*> runners;
|
|
|
|
try
|
|
{
|
|
runserver:
|
|
#ifdef CONFIG_BUILDER
|
|
bool builderMode = (gOptions.confFile() == "builder" || gOptions.confFile() == "-b");
|
|
bool setupMode = (gOptions.confFile() == "setup" || gOptions.confFile() == "-s" || builderMode);
|
|
#else
|
|
bool setupMode = (gOptions.confFile() == "setup" || gOptions.confFile() == "-s");
|
|
#endif
|
|
bool gotConfig = (!gOptions.confFile().empty() && uniFile::fileExists(gOptions.confFile()));
|
|
// look for sc_serv.conf or sc_serv.ini to emulate v1 DNAS behaviour as
|
|
// too many people cannot get on with having to specify a config file :(
|
|
if (gotConfig == false && setupMode == false)
|
|
{
|
|
utf8 currentLogFile = gOptions.logFile();
|
|
|
|
#ifdef _WIN32
|
|
vector<wstring> fileList = fileUtil::directoryFileList(gStartupDirectory.toWString() + L"sc_serv.ini", L"", true, true);
|
|
vector<wstring> fileListConf = fileUtil::directoryFileList(gStartupDirectory.toWString() + L"sc_serv.conf", L"", true, true);
|
|
#else
|
|
vector<string> fileList = fileUtil::directoryFileList(gStartupDirectory.hideAsString() + "sc_serv.ini", "");
|
|
vector<string> fileListConf = fileUtil::directoryFileList(gStartupDirectory.hideAsString() + "sc_serv.conf", "");
|
|
#endif
|
|
|
|
if (!fileList.empty())
|
|
{
|
|
fileList.insert(fileList.end(), fileListConf.begin(),fileListConf.end());
|
|
}
|
|
else
|
|
{
|
|
fileList = fileListConf;
|
|
}
|
|
|
|
if (!fileList.empty())
|
|
{
|
|
#ifdef _WIN32
|
|
utf32 u32file(fileList[0]);
|
|
utf8 u8f(u32file.toUtf8());
|
|
gotConfig = gOptions.load(u8f);
|
|
#else
|
|
gotConfig = gOptions.load(fileList[0]);
|
|
#endif
|
|
}
|
|
|
|
// if these do not match then we need to update the log file being used
|
|
// as otherwise it reports the wrong thing on the admin pages, etc
|
|
if (gOptions.logFile() != currentLogFile)
|
|
{
|
|
base.startNormalLog(false);
|
|
}
|
|
}
|
|
|
|
if (isPostSetup() == false)
|
|
{
|
|
if (gOptions.screenLog())
|
|
{
|
|
base.startScreenLog();
|
|
}
|
|
// during initial startup some messages are produced but cannot be logged because
|
|
// the loggers do not yet exist. These are in the deferred list. At this point they
|
|
// can be safely logged
|
|
logDeferredMessages();
|
|
|
|
gOptions.m_certPath = fileUtil::getFullFilePath(gStartupDirectory + "cacert.pem");
|
|
if (!uniFile::fileExists(gOptions.m_certPath))
|
|
{
|
|
WLOG(LOGNAME "Cannot find `" + gOptions.m_certPath + "'");
|
|
WLOG(LOGNAME "Without `cacert.pem' the DNAS may not be able to contact the Directory");
|
|
WLOG(LOGNAME "The latest can be downloaded from `http://curl.haxx.se/ca/cacert.pem'" + eol());
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
ILOG("*********************" +
|
|
string(!sDaemon ? "***************************" : "<<RUNNING_IN_SERVICE_MODE>>") +
|
|
"*********************");
|
|
#else
|
|
ILOG("*********************" +
|
|
string(!sDaemon ? "***************************" : "<<RUNNING_IN__DAEMON_MODE>>") +
|
|
"*********************");
|
|
#endif
|
|
ILOG("** Shoutcast Distributed Network Audio Server (DNAS) **");
|
|
ILOG("** Copyright (C) 2014-2023 Radionomy SA, All Rights Reserved **");
|
|
if (gotConfig == false && setupMode == false)
|
|
{
|
|
ILOG("** Use \"sc_serv [filename]\" to specify a config file **");
|
|
}
|
|
ILOG("*********************************************************************");
|
|
utf8 version = gOptions.getVersionBuildStrings();
|
|
#if defined(_DEBUG) || defined(DEBUG)
|
|
version += "[DBUG]";
|
|
#endif
|
|
ILOG(LOGNAME "Shoutcast DNAS/" SERV_OSNAME " v" + version + " (" __DATE__ ")");
|
|
ILOG(LOGNAME "PID: " + tos(getpid()));
|
|
}
|
|
if (gotConfig)
|
|
{
|
|
ILOG(LOGNAME "Saving log output to `" + fileUtil::getFullFilePath(gOptions.realLogFile()) + "'");
|
|
ILOG(LOGNAME "Automatic log rotation " + (!gOptions.rotateInterval() ? utf8("disabled") : utf8("interval: ") + timeString(gOptions.rotateInterval())));
|
|
ILOG(LOGNAME "Loaded config from `" + fileUtil::getFullFilePath(gOptions.confFile()) + "'");
|
|
}
|
|
else
|
|
{
|
|
uniFile::filenameType oldLogFile = gOptions.logFile();
|
|
// if we get to this state then to make things easier, we attempt to offer
|
|
// some possible configuration files to attempt to load as the config file
|
|
int mode = (setupMode ? 2 : gOptions.promptConfigFile());
|
|
if (mode <= 0)
|
|
{
|
|
if (mode == -1)
|
|
{
|
|
ELOG(LOGNAME "Aborting as no valid config files could be found.");
|
|
throwEx<runtime_error>(LOGNAME "Try running setup mode to create a valid config file.");
|
|
}
|
|
else if (mode == -2)
|
|
{
|
|
throwEx<runtime_error>(LOGNAME "Aborting at user request.");
|
|
}
|
|
else
|
|
{
|
|
if (!gOptions.confFile().empty() && !uniFile::fileExists(gOptions.confFile()))
|
|
{
|
|
throwEx<runtime_error>(LOGNAME "Passed config file does not exist (check the file path exists)");
|
|
}
|
|
else
|
|
{
|
|
throwEx<runtime_error>(LOGNAME "No config file passed");
|
|
}
|
|
}
|
|
}
|
|
else if (mode == 1)
|
|
{
|
|
// if these do not match then we need to update the log file being used
|
|
utf8 newLogFile = getLogFile(gOptions.logFile());
|
|
if (newLogFile != oldLogFile)
|
|
{
|
|
uniString::utf8 file = newLogFile.substr(0,gOptions.logFile().rfind(fileUtil::getFilePathDelimiter())).c_str();
|
|
if ((file == newLogFile) || fileUtil::directoryExists(fileUtil::onlyPath(newLogFile)))
|
|
{
|
|
#ifdef _WIN32
|
|
// see if we can create the file, if not then just keep using the temp folder for the log file as we know it's ok
|
|
HANDLE m_file = ::CreateFileW(newLogFile.toWString().c_str(),GENERIC_WRITE,
|
|
FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
|
|
if (m_file != INVALID_HANDLE_VALUE)
|
|
{
|
|
gOptions.setOption(utf8("reallogfile"), newLogFile);
|
|
forgetHandleInvalid(m_file);
|
|
base.startNormalLog(true);
|
|
}
|
|
// make sure it's showing the correct log file
|
|
else
|
|
{
|
|
// this will fill in the default log path as required
|
|
wchar_t m_fileName[MAX_PATH] = {0};
|
|
ExpandEnvironmentStringsW(DEFAULT_LOGW, m_fileName, MAX_PATH);
|
|
utf8 log(utf32(m_fileName).toUtf8());
|
|
gOptions.setOption(utf8("logfile"), log);
|
|
gOptions.setOption(utf8("reallogfile"), log);
|
|
ILOG(LOGNAME "Logger keeping log file as `" + fileUtil::getFullFilePath(log) + "'");
|
|
ILOG(LOGNAME "Check you have write permissions for the folder(s) set in the config file.");
|
|
}
|
|
#else
|
|
// see if we can create the file, if not then just keep using the temp folder for the log file as we know it's ok
|
|
int m_file = ::open(newLogFile.hideAsString().c_str(),O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
|
|
if (m_file != -1)
|
|
{
|
|
::close(m_file);
|
|
gOptions.setOption(utf8("reallogfile"), newLogFile);
|
|
base.startNormalLog(true);
|
|
}
|
|
// make sure it's showing the correct log file
|
|
else
|
|
{
|
|
// this will fill in the default log path as required
|
|
utf8 log(DEFAULT_LOG);
|
|
gOptions.setOption(utf8("logfile"), log);
|
|
gOptions.setOption(utf8("reallogfile"), log);
|
|
ILOG(LOGNAME "Logger keeping log file as `" + fileUtil::getFullFilePath(log) + "'");
|
|
ILOG(LOGNAME "Check you have write permissions for the folder(s) set in the config file.");
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
throwEx<runtime_error>(LOGNAME "Log file path does not exist (check the folder path exists)");
|
|
}
|
|
}
|
|
|
|
ILOG(LOGNAME "Saving log output to `" + fileUtil::getFullFilePath(gOptions.realLogFile()) + "'");
|
|
ILOG(LOGNAME "Automatic log rotation " + (!gOptions.rotateInterval() ? utf8("disabled") :
|
|
utf8("interval: ") + timeString(gOptions.rotateInterval())));
|
|
ILOG(LOGNAME "Loaded config from `" + fileUtil::getFullFilePath(gOptions.confFile()) + "'");
|
|
}
|
|
else if (mode == 2)
|
|
{
|
|
if (setupMode)
|
|
{
|
|
#ifdef CONFIG_BUILDER
|
|
if (!builderMode)
|
|
{
|
|
#endif
|
|
ILOG(LOGNAME "Entering setup mode - Open 127.0.0.1:8000/setup in a");
|
|
ILOG(LOGNAME "browser on the same machine the DNAS was started on");
|
|
#ifdef CONFIG_BUILDER
|
|
}
|
|
else
|
|
{
|
|
ILOG(LOGNAME "Entering builder mode - Open 127.0.0.1:8000/builder in");
|
|
ILOG(LOGNAME "a browser on the same machine the DNAS was started on");
|
|
}
|
|
#endif
|
|
ILOG(LOGNAME "if the browser does not automatically open the page.");
|
|
ILOG(LOGNAME "If working remotely then replace 127.0.0.1 with the");
|
|
ILOG(LOGNAME "IP / address of the remote system to use this mode.");
|
|
}
|
|
|
|
// attempt to open the setup / builder page on the system's browser i.e. kiss option
|
|
#ifdef _WIN32
|
|
#ifdef CONFIG_BUILDER
|
|
if (!builderMode)
|
|
{
|
|
#endif
|
|
::system("start http://127.0.0.1:8000/setup");
|
|
#ifdef CONFIG_BUILDER
|
|
}
|
|
else
|
|
{
|
|
::system("start http://127.0.0.1:8000/builder");
|
|
}
|
|
#endif
|
|
#else
|
|
#ifdef CONFIG_BUILDER
|
|
if (!builderMode)
|
|
{
|
|
#endif
|
|
::system("(if(which xdg-open > /dev/null)then(xdg-open http://127.0.0.1:8000/setup)elif(which gnome-open > /dev/null)then(gnome-open http://127.0.0.1:8000/setup)fi)&>/dev/null");
|
|
#ifdef CONFIG_BUILDER
|
|
}
|
|
else
|
|
{
|
|
::system("(if(which xdg-open > /dev/null)then(xdg-open http://127.0.0.1:8000/builder)elif(which gnome-open > /dev/null)then(gnome-open http://127.0.0.1:8000/builder)fi)&>/dev/null");
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
win32SocketSetup();
|
|
win32_socket_cleanup_required = true;
|
|
#endif
|
|
|
|
threadedRunner *tr = new threadedRunner;
|
|
runners.push_back(tr);
|
|
tr->start();
|
|
|
|
threadedRunner::scheduleRunnable(new microServer("", gOptions.portBase(),
|
|
(microServer::AllowableProtocols_t)(P_WEB_SETUP),
|
|
microServer::L_MISC));
|
|
|
|
s_ControlEvents.push_back(serviceMain::sStop);
|
|
while (!iskilled())
|
|
{
|
|
#if defined(_WIN32) && defined(_WIN64)
|
|
if (WaitForMultipleObjects((DWORD)s_ControlEvents.size(), &(s_ControlEvents[0]), 0, 1000) == WAIT_OBJECT_0)
|
|
#else
|
|
if (WaitForMultipleObjects(s_ControlEvents.size(), &(s_ControlEvents[0]), 0, 1000) == WAIT_OBJECT_0)
|
|
#endif
|
|
{
|
|
// stop signal
|
|
setkill(1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (iskilled() == 2)
|
|
{
|
|
ILOG(LOGNAME "Stopping setup mode. Preparing for broadcasting...");
|
|
}
|
|
else
|
|
{
|
|
ILOG(LOGNAME "Stopping setup mode. Shutting down...");
|
|
}
|
|
|
|
for (vector<threadedRunner*>::const_iterator i = runners.begin(); i != runners.end(); ++i)
|
|
{
|
|
(*i)->stop();
|
|
(*i)->join();
|
|
delete (*i);
|
|
}
|
|
runners.clear();
|
|
|
|
#ifdef _WIN32
|
|
if (win32_socket_cleanup_required)
|
|
{
|
|
win32SocketCleanup();
|
|
}
|
|
#endif
|
|
|
|
s_ControlEvents.clear();
|
|
|
|
// if we get here, we effectively restart the server post-setup
|
|
if (iskilled() == 2)
|
|
{
|
|
#ifdef _WIN32
|
|
gOptions.load(gStartupDirectory + "sc_serv.conf");
|
|
#else
|
|
gOptions.load(gStartupDirectory.hideAsString() + "sc_serv.conf");
|
|
#endif
|
|
setPostSetup(true);
|
|
setkill(0);
|
|
goto runserver;
|
|
}
|
|
setkill(0);
|
|
return mainResult;
|
|
}
|
|
}
|
|
|
|
// just make sure that we can do this else we need to abort
|
|
if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK)
|
|
{
|
|
throwEx<runtime_error>(LOGNAME "Unable to load libcurl & / or its dependencies - cannot continue.");
|
|
}
|
|
|
|
config::streams_t streams;
|
|
gOptions.getStreamConfigs(streams);
|
|
// the old sc_serv had two sets of passwords, mimic this
|
|
if (gOptions.adminPassword().empty())
|
|
{
|
|
WLOG(gOptions.logSectionName() + "A dedicated `adminpassword' should be specified in the configuration.");
|
|
WLOG(gOptions.logSectionName() + "Legacy handling has been enabled to map `adminpassword' to `password'.");
|
|
WLOG(gOptions.logSectionName() + "This is not deemed safe and `adminpassword' should be directly set.");
|
|
gOptions.setOption(utf8("adminpassword"), gOptions.password());
|
|
}
|
|
else
|
|
{
|
|
// otherwise if explicitly set as the same then we abort
|
|
if (gOptions.adminPassword() == gOptions.password())
|
|
{
|
|
throwEx<runtime_error>(gOptions.logSectionName() + "You must specify different passwords for `adminpassword' and `password'.");
|
|
}
|
|
}
|
|
|
|
|
|
// abort no matter what as without passwords we can be at risk
|
|
if (gOptions.adminPassword().empty() && gOptions.password().empty())
|
|
{
|
|
throwEx<runtime_error>(gOptions.logSectionName() + "You must specify a password for `adminpassword' and `password'.");
|
|
}
|
|
|
|
if (!streams.empty())
|
|
{
|
|
if (gOptions.setupPasswords(streams))
|
|
{
|
|
// if there was any error on the passwords then we need to abort
|
|
// so it can be fixed. important if using multi-stream hosting!
|
|
throwEx<runtime_error>(gOptions.logSectionName() + "Check the stream configurations above or ensure `adminpassword' and `password' have been set.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if there are no stream configs and no main password
|
|
// then we'll also need to abort as that's not allowed
|
|
if (gOptions.password().empty())
|
|
{
|
|
throwEx<runtime_error>(gOptions.logSectionName() + "You must specify a password for `password'.");
|
|
}
|
|
}
|
|
|
|
|
|
if (!gOptions.cdn().empty())
|
|
{
|
|
ILOG(LOGNAME "CDN " + utf8(gOptions.cdn() == "on" ? "opt-in" : "opt-out") + " mode enabled -> ensure all stream(s) are properly configured");
|
|
}
|
|
|
|
const int cpu_count = gOptions.getCPUCount(); // check options
|
|
ILOG(LOGNAME "Calculated CPU count is " + tos(cpucount()) + " -> " + (cpu_count == cpucount() ? "using " +
|
|
utf8(cpu_count > 1 ? "all" : "the") + " available CPU" + (cpu_count > 1 ? "s" : "") :
|
|
tos(cpu_count) + " CPU" + (cpu_count > 1 ? "s" : "") + " specified to be used"));
|
|
#ifndef _WIN32
|
|
rlimit rlim = {0};
|
|
if(!getrlimit(RLIMIT_NOFILE, &rlim))
|
|
{
|
|
ILOG(LOGNAME "Limited to " + tos(rlim.rlim_cur) + " file descriptors [relates to ulimit -n]");
|
|
}
|
|
#endif
|
|
// calculate defaults for yp
|
|
// if 'publicport' is not the same as 'portbase'
|
|
// then we use it instead of the normal portbase
|
|
g_portForClients = ((gOptions.publicPort() != -1 && (gOptions.publicPort() != gOptions.portBase())) ? gOptions.publicPort() : gOptions.portBase());
|
|
|
|
// before anything else starts up we'll start this as needed
|
|
metrics::metrics_apply(gOptions);
|
|
auth::init();
|
|
|
|
// load any SSL certificates
|
|
threadedRunner::SSL_init();
|
|
|
|
#ifdef _WIN32
|
|
win32SocketSetup();
|
|
win32_socket_cleanup_required = true;
|
|
#endif
|
|
|
|
ILOG(LOGNAME "Starting " + tos(cpu_count) + " network thread" + (cpu_count > 1 ? "s" : ""));
|
|
|
|
for (int x = 0; x < cpu_count; ++x)
|
|
{
|
|
threadedRunner *tr = new threadedRunner;
|
|
runners.push_back(tr);
|
|
tr->start();
|
|
}
|
|
|
|
constructMessageResponses();
|
|
|
|
// w3c logging
|
|
if (gOptions.w3cEnable())
|
|
{
|
|
w3cLog::open(gOptions.w3cLog());
|
|
}
|
|
|
|
// load geoIP database if able to be found
|
|
/*{
|
|
static utf8 dir = gStartupDirectory;
|
|
char path[1024] = {0};
|
|
GeoIP_setup_custom_directory(strncpy(path, (char*)dir.hideAsString().c_str(), sizeof(path)));
|
|
GeoIP * gi = GeoIP_new(GEOIP_MEMORY_CACHE);
|
|
if(gi != NULL)
|
|
{
|
|
ILOG(LOGNAME "Loaded GeoIP database - Ban and Reserve by country is enabled");
|
|
const char* returnedCountry,
|
|
* ips[] = {"58.218.199.227","77.76.181.71","87.104.93.85","195.238.117.56",
|
|
"114.244.60.232","108.52.34.116","91.121.164.186","78.46.75.50",
|
|
"62.75.139.39","172.17.200.143","205.188.215.228","82.30.80.183"
|
|
};
|
|
for (int ip = 0; ip < sizeof(ips)/sizeof(ips[0]); ip++)
|
|
{
|
|
utf8 details;
|
|
returnedCountry = GeoIP_country_code_by_addr(gi, ips[ip]);
|
|
details = (returnedCountry ? returnedCountry : utf8("UNKNOWN"));
|
|
returnedCountry = GeoIP_country_name_by_addr(gi, ips[ip]);
|
|
details += " (" + (returnedCountry ? returnedCountry : utf8("UNKNOWN")) + ")";
|
|
WLOG(details);
|
|
}
|
|
|
|
GeoIP_delete(gi);
|
|
}
|
|
}*/
|
|
|
|
// load up stream branding artwork
|
|
gOptions.m_artworkBody[0] = loadLocalFile(fileUtil::getFullFilePath(gOptions.artworkFile()), LOGNAME, 523872/*32 x (16384 - 6 - 6 - 1)*/);
|
|
|
|
// load up ban file
|
|
g_banList.load(gOptions.banFile(),0);
|
|
|
|
// load up rip file
|
|
g_ripList.load(gOptions.ripFile(),0);
|
|
|
|
// load up admin access file
|
|
g_adminList.load(gOptions.adminFile());
|
|
|
|
// load up agent file
|
|
g_agentList.load(gOptions.agentFile(),0);
|
|
|
|
// per-stream options
|
|
gOptions.getStreamConfigs(streams);
|
|
for (config::streams_t::const_iterator i = streams.begin(); i != streams.end(); ++i)
|
|
{
|
|
// w3c logging
|
|
if (gOptions.read_stream_w3cLog((*i).first))
|
|
{
|
|
w3cLog::open(gOptions.stream_w3cLog((*i).first),(*i).first);
|
|
}
|
|
|
|
// load up ban file
|
|
if (gOptions.read_stream_banFile((*i).first))
|
|
{
|
|
g_banList.load(gOptions.stream_banFile((*i).first),(*i).first);
|
|
}
|
|
|
|
// load up rip file
|
|
if (gOptions.read_stream_ripFile((*i).first))
|
|
{
|
|
g_ripList.load(gOptions.stream_ripFile((*i).first),(*i).first);
|
|
}
|
|
|
|
// load up agent file
|
|
if (gOptions.read_stream_agentFile((*i).first))
|
|
{
|
|
g_agentList.load(gOptions.stream_agentFile((*i).first),(*i).first);
|
|
}
|
|
|
|
// load up stream branding artwork
|
|
if (gOptions.read_stream_artworkFile((*i).first))
|
|
{
|
|
gOptions.m_artworkBody[(*i).first] = loadLocalFile(fileUtil::getFullFilePath(gOptions.stream_artworkFile((*i).first)), LOGNAME, 523872/*32 x (16384 - 6 - 6 - 1)*/);
|
|
}
|
|
}
|
|
|
|
utf8 srcBindAddr = stripHTTPprefix(stripWhitespace(gOptions.srcIP()));
|
|
utf8 destBindAddr = metrics::metrics_verifyDestIP(gOptions, false);
|
|
|
|
if (g_IPAddressForClients.empty())
|
|
{
|
|
char s[MAXHOSTNAMELEN] = {0}; // paranoia
|
|
if (!::gethostname(s, MAXHOSTNAMELEN - 1))
|
|
{
|
|
// changed to not throw (build 43) as this will still run correctly
|
|
// and has caused a number of users to go back to v1 unnecessarily.
|
|
g_IPAddressForClients = socketOps::hostNameToAddress(s, g_portForClients);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
utf8 addr = socketOps::hostNameToAddress(destBindAddr.hideAsString().c_str());
|
|
if (!addr.empty())
|
|
{
|
|
destBindAddr = addr;
|
|
}
|
|
/*hostent *host;
|
|
if ((host = ::gethostbyname((const char *)destBindAddr.c_str())) != NULL){
|
|
destBindAddr = (inet_ntoa(*( (struct in_addr *)host->h_addr)));
|
|
}*/
|
|
}
|
|
|
|
if (!srcBindAddr.empty())
|
|
{
|
|
utf8 addr = socketOps::hostNameToAddress(srcBindAddr.hideAsString().c_str());
|
|
if (!addr.empty())
|
|
{
|
|
srcBindAddr = addr;
|
|
}
|
|
/*hostent *host;
|
|
if ((host = ::gethostbyname((const char *)srcBindAddr.c_str())) != NULL){
|
|
srcBindAddr = (inet_ntoa(*( (struct in_addr *)host->h_addr)));
|
|
}*/
|
|
}
|
|
|
|
// for legacy sources (with optional portbase override / disable)
|
|
g_legacyPort = ((gOptions.portLegacy() != gOptions.portBase() + 1) && (gOptions.portLegacy() != -1) ?
|
|
gOptions.portLegacy() : gOptions.portBase() + 1);
|
|
|
|
// if src and dst are same we configure ourselves a bit differently
|
|
if ((srcBindAddr == destBindAddr) ||
|
|
(inet_addr((const char *)srcBindAddr.c_str()) == inet_addr((const char *)destBindAddr.c_str())))
|
|
{
|
|
// for clients and sources
|
|
threadedRunner::scheduleRunnable(new microServer(srcBindAddr.hideAsString(), gOptions.portBase(),
|
|
(microServer::AllowableProtocols_t)(P_SHOUTCAST1CLIENT | P_SHOUTCAST2CLIENT |
|
|
P_SHOUTCAST1SOURCE | P_SHOUTCAST2SOURCE | P_WEB),
|
|
(microServer::ListenTypes_t)(microServer::L_SOURCE | microServer::L_CLIENT)));
|
|
}
|
|
else
|
|
{
|
|
// changed in b71 as it's possible to specify a destip which does not correctly bind but the
|
|
// value of destip could be correct and able to work in all other areas e.g. via listen.pls
|
|
// so if there is an issue with the destip specific bind, then attempt a destip==srcip bind
|
|
microServer *r = 0;
|
|
try
|
|
{
|
|
// for clients
|
|
// changed in b79
|
|
r = new microServer(destBindAddr.hideAsString(), gOptions.portBase(),
|
|
(microServer::AllowableProtocols_t)(P_SHOUTCAST1CLIENT | P_SHOUTCAST2CLIENT | P_SHOUTCAST1SOURCE | P_WEB),
|
|
(microServer::ListenTypes_t)(microServer::L_SOURCE | microServer::L_CLIENT));
|
|
threadedRunner::scheduleRunnable(r);
|
|
|
|
// for sources
|
|
threadedRunner::scheduleRunnable(new microServer(srcBindAddr.hideAsString(), gOptions.portBase(),
|
|
(microServer::AllowableProtocols_t)(P_SHOUTCAST1SOURCE | P_SHOUTCAST2SOURCE | P_WEB),
|
|
microServer::L_SOURCE));
|
|
}
|
|
catch(const exception &ex)
|
|
{
|
|
if (r == 0)
|
|
{
|
|
WLOG(ex.what());
|
|
}
|
|
|
|
try
|
|
{
|
|
// for clients and sources
|
|
threadedRunner::scheduleRunnable(new microServer(srcBindAddr.hideAsString(), gOptions.portBase(),
|
|
(microServer::AllowableProtocols_t)(P_SHOUTCAST1CLIENT | P_SHOUTCAST2CLIENT |
|
|
P_SHOUTCAST1SOURCE | P_SHOUTCAST2SOURCE | P_WEB),
|
|
(microServer::ListenTypes_t)(microServer::L_SOURCE | microServer::L_CLIENT)));
|
|
}
|
|
catch(const exception &exx)
|
|
{
|
|
// changed in b79
|
|
// if we get to here, if we've been able to bind at least on the destip
|
|
// but the srcip bind fails then attempt to allow v2 sources on destip.
|
|
if (r != 0)
|
|
{
|
|
r->updateProtocols((microServer::AllowableProtocols_t)(P_SHOUTCAST1CLIENT | P_SHOUTCAST2CLIENT |
|
|
P_SHOUTCAST1SOURCE | P_SHOUTCAST2SOURCE | P_WEB),
|
|
(microServer::ListenTypes_t)(microServer::L_SOURCE | microServer::L_SOURCE2), gOptions.portBase());
|
|
}
|
|
else
|
|
{
|
|
throwEx<runtime_error>(exx.what());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (g_legacyPort > 0)
|
|
{
|
|
// for v1 sources
|
|
threadedRunner::scheduleRunnable(new microServer(srcBindAddr.hideAsString(), g_legacyPort,
|
|
(microServer::AllowableProtocols_t)(P_SHOUTCAST1SOURCE),
|
|
microServer::L_SOURCE));
|
|
}
|
|
else
|
|
{
|
|
ILOG("[MICROSERVER] Legacy v1 source support not enabled");
|
|
}
|
|
|
|
// for flash policy file server
|
|
if (gOptions.flashPolicyServerPort() != -1)
|
|
{
|
|
threadedRunner::scheduleRunnable(new microServer(srcBindAddr.hideAsString(), gOptions.flashPolicyServerPort(),
|
|
(microServer::AllowableProtocols_t)(P_FLASHPOLICYFILE), microServer::L_FLASH));
|
|
}
|
|
else
|
|
{
|
|
ILOG("[MICROSERVER] Flash policy file server not enabled");
|
|
}
|
|
|
|
// and finally look at adding any extra client ports e.g. so you can run on 80
|
|
// and 8000 and anything else for clients that cannot connect to the main port
|
|
if (!gOptions.alternatePorts().empty())
|
|
{
|
|
vector<utf8> tokens = tokenizer(gOptions.alternatePorts(), ',');
|
|
for (size_t tok = 0; tok < tokens.size(); tok++)
|
|
{
|
|
u_short port = (u_short)atoi(tokens[tok].hideAsString().c_str());
|
|
// make sure we're only allowing a valid port number and it isn't already been registered
|
|
if (port && (port != gOptions.portBase()) && (port != g_portForClients))
|
|
{
|
|
microServer *r = 0;
|
|
try
|
|
{
|
|
r = new microServer(srcBindAddr.hideAsString(), port,
|
|
(microServer::AllowableProtocols_t)(P_SHOUTCAST1CLIENT | P_SHOUTCAST2CLIENT),
|
|
(microServer::ListenTypes_t)(microServer::L_CLIENT_ALT));
|
|
threadedRunner::scheduleRunnable(r);
|
|
gOptions.m_usedAlternatePorts += "," + tokens[tok];
|
|
}
|
|
catch(const exception &ex)
|
|
{
|
|
if (r == 0)
|
|
{
|
|
WLOG(ex.what());
|
|
WLOG("[MICROSERVER] Alternate client connections on port " + tokens[tok] + " will not work");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (port != gOptions.portBase() && port != g_portForClients)
|
|
{
|
|
WLOG("[MICROSERVER] Skipping `" + tokens[tok] + "' as it is an invalid alternate client port");
|
|
}
|
|
else
|
|
{
|
|
WLOG("[MICROSERVER] Skipping alternate port " + tokens[tok] + " as it is already a used port");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (gOptions.pidFile().empty() || (gOptions.pidFile() == "sc_serv_$.pid"))
|
|
{
|
|
pidFn = gStartupDirectory + "sc_serv_" + tos(gOptions.portBase()) + ".pid";
|
|
}
|
|
else if (!gOptions.pidFile().empty())
|
|
{
|
|
pidFn = gOptions.pidFile();
|
|
}
|
|
|
|
if (!pidFn.empty())
|
|
{
|
|
FILE *f = uniFile::fopen(pidFn, "wb");
|
|
if (f)
|
|
{
|
|
try
|
|
{
|
|
utf8 s(tos(getpid()) + eol());
|
|
if (fwrite(s.c_str(),1,s.size(),f) != s.size())
|
|
{
|
|
ELOG(LOGNAME "I/O error writing PID file `" + fileUtil::getFullFilePath(pidFn) + "'");
|
|
}
|
|
}
|
|
catch(...)
|
|
{
|
|
if (f)
|
|
{
|
|
::fclose(f);
|
|
}
|
|
ELOG(LOGNAME "Error writing to PID file `" + fileUtil::getFullFilePath(pidFn) + "'");
|
|
}
|
|
if (f)
|
|
{
|
|
::fclose(f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ELOG(LOGNAME "Could not open PID file `" + fileUtil::getFullFilePath(pidFn) +
|
|
"' for writing (" + errMessage().hideAsString() + ")");
|
|
}
|
|
}
|
|
|
|
// schedule relays
|
|
if (!gOptions.startInactive())
|
|
{
|
|
vector<config::streamConfig> relayList(gOptions.getRelayList());
|
|
if (!relayList.empty())
|
|
{
|
|
for_each(relayList.begin(), relayList.end(), scheduleRelay);
|
|
}
|
|
}
|
|
|
|
//threadedRunner::scheduleRunnable(new yp2);
|
|
threadedRunner::scheduleRunnable(new updater);
|
|
|
|
s_ControlEvents.push_back(serviceMain::sStop);
|
|
#ifndef _WIN32
|
|
s_ControlEvents.push_back(serviceMain::sWINCH);
|
|
s_ControlEvents.push_back(serviceMain::sHUP);
|
|
s_ControlEvents.push_back(serviceMain::sUSR1);
|
|
s_ControlEvents.push_back(serviceMain::sUSR2);
|
|
#endif
|
|
|
|
#ifdef OPEN_PORT_CHECKER
|
|
//BOOL CheckPortTCP(short int dwPort , char*ipAddressStr)
|
|
{
|
|
struct sockaddr_in client;
|
|
int sock;
|
|
|
|
client.sin_family = AF_INET;
|
|
client.sin_port = htons(gOptions.portBase()-1000);
|
|
//client.sin_addr.s_addr = inet_addr(g_IPAddressForClients/*destBindAddr*/.hideAsString().c_str()/*"127.0.0.1");
|
|
client.sin_addr.s_addr = inet_addr("192.168.2.3"/*"127.0.0.1"*/);
|
|
|
|
sock = (int) socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
ELOG(g_IPAddressForClients + ":" + tos(gOptions.portBase()) + /*destBindAddr*/ + " " + tos(connect(sock, (struct sockaddr *) &client,sizeof(client)) == 0));
|
|
}
|
|
#endif
|
|
|
|
while (!iskilled())
|
|
{
|
|
metrics::metrics_wakeup();
|
|
|
|
#if defined(_WIN32) && defined(_WIN64)
|
|
int waitResult = WaitForMultipleObjects((DWORD)s_ControlEvents.size(),&(s_ControlEvents[0]),0,1000);
|
|
#else
|
|
int waitResult = WaitForMultipleObjects(s_ControlEvents.size(),&(s_ControlEvents[0]),0,1000);
|
|
#endif
|
|
switch (waitResult)
|
|
{
|
|
// stop signal
|
|
case WAIT_OBJECT_0:
|
|
{
|
|
setkill(true);
|
|
// remove the pid file on successful exit
|
|
if (!pidFn.empty() && uniFile::fileExists(pidFn))
|
|
{
|
|
uniFile::unlink(pidFn);
|
|
}
|
|
break;
|
|
}
|
|
#ifdef _WIN32
|
|
case WAIT_OBJECT_0+1: // admin server
|
|
break;
|
|
|
|
case WAIT_OBJECT_0+2: // main msg loop
|
|
break;
|
|
#else
|
|
case WAIT_OBJECT_0+1: // winch
|
|
{
|
|
// reserved, banned and blocked user agent list(s) reload
|
|
reloadBanLists();
|
|
reloadRipLists();
|
|
reloadAdminAccessList();
|
|
reloadAgentLists();
|
|
break;
|
|
}
|
|
case WAIT_OBJECT_0+2: // hup
|
|
{
|
|
ILOG(LOGNAME "Rotating log and W3C files [PID: " + tos(getpid()) + "]");
|
|
ROTATE;
|
|
printUpdateMessage();
|
|
rotatew3cFiles("w3c");
|
|
ILOG(LOGNAME "Rotated log and W3C files [PID: " + tos(getpid()) + "]");
|
|
break;
|
|
}
|
|
case WAIT_OBJECT_0+3: // usr1
|
|
{
|
|
// config reload
|
|
reloadConfig(false);
|
|
break;
|
|
}
|
|
case WAIT_OBJECT_0+4: // usr2
|
|
{
|
|
// forced config reload
|
|
reloadConfig(true);
|
|
break;
|
|
}
|
|
case WAIT_OBJECT_0+5: // admin server
|
|
break;
|
|
|
|
case WAIT_OBJECT_0+6: // main msg loop
|
|
break;
|
|
#endif
|
|
default:
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
ILOG(LOGNAME "Exiting threads...");
|
|
if (gOptions.configRewrite())
|
|
{
|
|
gOptions.rewriteConfigurationFile((gOptions.configRewrite() == 1), true);
|
|
}
|
|
}
|
|
catch(const exception &ex)
|
|
{
|
|
ELOG(ex.what());
|
|
mainResult = -2;
|
|
}
|
|
catch(...)
|
|
{
|
|
ELOG(LOGNAME "Unknown exception caught");
|
|
mainResult = -1;
|
|
}
|
|
|
|
if (stats::getTotalUniqueListeners() > 0)
|
|
{
|
|
ILOG(LOGNAME "Terminating listeners...");
|
|
stats::kickAllClients(0, true);
|
|
}
|
|
|
|
// Stop all sources and wait up to ten seconds for everything to clear out
|
|
const streamData::streamIDs_t streamIds = streamData::getStreamIds(true);
|
|
if (!streamIds.empty())
|
|
{
|
|
ILOG(LOGNAME "Terminating sources...");
|
|
for (streamData::streamIDs_t::const_iterator i = streamIds.begin(); i != streamIds.end(); ++i)
|
|
{
|
|
// kick source off system
|
|
streamData::killStreamSource((*i));
|
|
}
|
|
}
|
|
|
|
if (yp2::requestsInQueue())
|
|
{
|
|
ILOG(LOGNAME "Running Directory cleanup...");
|
|
// now wait for YP to clear out
|
|
for (int x = 0; (x < 100); ++x)
|
|
{
|
|
if (yp2::requestsInQueue())
|
|
{
|
|
safe_sleep(0, 10000);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (vector<threadedRunner*>::const_iterator i = runners.begin(); i != runners.end(); ++i)
|
|
{
|
|
(*i)->stop();
|
|
(*i)->join();
|
|
delete (*i);
|
|
}
|
|
|
|
runners.clear();
|
|
metrics::metrics_wakeup(true);
|
|
metrics::metrics_stop();
|
|
auth::cleanup();
|
|
|
|
// general files to save
|
|
if (gOptions.saveBanListOnExit())
|
|
{
|
|
g_banList.save(gOptions.banFile(),0);
|
|
}
|
|
|
|
if (gOptions.saveRipListOnExit())
|
|
{
|
|
g_ripList.save(gOptions.ripFile(),0);
|
|
}
|
|
|
|
if (gOptions.saveAgentListOnExit())
|
|
{
|
|
g_agentList.save(gOptions.agentFile(),0);
|
|
}
|
|
|
|
if (gOptions.w3cEnable())
|
|
{
|
|
w3cLog::close(0);
|
|
}
|
|
|
|
// per-stream files to save
|
|
config::streams_t streams;
|
|
gOptions.getStreamConfigs(streams);
|
|
for (config::streams_t::const_iterator i = streams.begin(); i != streams.end(); ++i)
|
|
{
|
|
if (gOptions.saveBanListOnExit())
|
|
{
|
|
if (gOptions.read_stream_banFile((*i).first) && !gOptions.stream_banFile((*i).first).empty())
|
|
{
|
|
g_banList.save(gOptions.stream_banFile((*i).first),(*i).first);
|
|
}
|
|
}
|
|
|
|
if (gOptions.saveRipListOnExit())
|
|
{
|
|
if (gOptions.read_stream_ripFile((*i).first) && !gOptions.stream_ripFile((*i).first).empty())
|
|
{
|
|
g_ripList.save(gOptions.stream_ripFile((*i).first),(*i).first);
|
|
}
|
|
}
|
|
|
|
if (gOptions.saveAgentListOnExit())
|
|
{
|
|
if (gOptions.read_stream_agentFile((*i).first) && !gOptions.stream_agentFile((*i).first).empty())
|
|
{
|
|
g_agentList.save(gOptions.stream_agentFile((*i).first),(*i).first);
|
|
}
|
|
}
|
|
|
|
if (gOptions.read_stream_w3cLog((*i).first))
|
|
{
|
|
w3cLog::close((*i).first);
|
|
}
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
if (win32_socket_cleanup_required)
|
|
{
|
|
win32SocketCleanup();
|
|
}
|
|
#endif
|
|
|
|
s_ControlEvents.clear();
|
|
DeleteAllCaches();
|
|
|
|
stats::getFinalStats();
|
|
bandWidth::getFinalAmounts();
|
|
utf8 t = timeString(::time(NULL) - g_upTime);
|
|
ILOG(LOGNAME + (t.empty() ? "Shutdown" : "Shutdown after " + t + " running") + eol());
|
|
|
|
curl_global_cleanup();
|
|
|
|
/*#ifdef _WIN32
|
|
{
|
|
wchar_t buf[MAX_PATH] = L"\"";
|
|
STARTUPINFO si = {sizeof(si), };
|
|
PROCESS_INFORMATION pi;
|
|
GetModuleFileName(NULL, buf + 1, sizeof(buf) - 1);
|
|
wcsncat(buf, L"\"", MAX_PATH);
|
|
CreateProcess(NULL, buf, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
|
|
}
|
|
#else
|
|
#endif*/
|
|
|
|
// if we're in 'user' mode then if there was an exception
|
|
// we'll not immediately abort and instead keep things on
|
|
// display so it's easier for noobs, etc to see the error
|
|
if (!sDaemon && gOptions.screenLog() && (mainResult < 0))
|
|
{
|
|
ILOG(LOGNAME "Press any key to continue . . .");
|
|
while(!_kbhit()) {}
|
|
#ifdef _WIN32
|
|
// do this so we consume the input which
|
|
// as we already have a custom _kbhit()
|
|
// on non-Windows already does it for us
|
|
_getch();
|
|
#endif
|
|
}
|
|
return mainResult;
|
|
}
|
|
};
|
|
|
|
// create the appropriate handler for the app/daemon/service framework
|
|
int sm_main(const vector<utf8> &args) throw()
|
|
{
|
|
#ifdef _WIN32
|
|
stdServiceWin32<sc_serv2_service> s("Shoutcast DNAS");
|
|
#else
|
|
stdServiceUnix<sc_serv2_service> s;
|
|
#endif
|
|
return s.sm_main(args);
|
|
}
|