#include "serviceMain.h" #include #include #include #include "expat.h" #include "stl/stringUtils.h" #include "services/stdServiceImpl.h" #include "../../versions.h" #include "../../global.h" #ifdef _WIN32 #include "win32/rezFuncs.h" #include #include #else #ifdef PLATFORM_LINUX #include "../stacktrace/StackTrace.h" #endif #include #include "unixversion.h" #include #include #include #include #include #include #include #include #ifdef __APPLE_CC__ #include #include "file/fileUtils.h" #endif #endif using namespace std; using namespace uniString; using namespace stringUtil; #ifndef _WIN32 #define TRUE true #define FALSE false #endif // are we running as a daemon or service bool sDaemon = false; event serviceMain::sStop(TRUE); #ifndef _WIN32 event serviceMain::sWINCH(FALSE); event serviceMain::sHUP(FALSE); event serviceMain::sUSR1(FALSE); event serviceMain::sUSR2(FALSE); #endif #ifdef _WIN32 static SERVICE_STATUS_HANDLE ssh=NULL; // does not have to be closed BOOL WINAPI _console_handler(DWORD fdwCtrlType) { switch (fdwCtrlType) { // Handle the CTRL+C signal. case CTRL_CLOSE_EVENT: case CTRL_BREAK_EVENT: case CTRL_C_EVENT: { _SetEvent(serviceMain::sStop); return TRUE; } default: { return FALSE; } } } #else #ifdef PLATFORM_LINUX static void custom_signal_handler(int signum) { signal(signum, SIG_DFL); // http://sourceforge.net/p/stacktrace/code/HEAD/tree/ // ensure we building with -rdynamic so this works ok static char buf[256] = {0}; static time_t last_ttt; struct tm ttm; time_t ttt = ::time(NULL); if (ttt != last_ttt) { last_ttt = ttt; ::strftime(buf, 255, "%Y-%m-%d %H:%M:%S", ::localtime_r(&ttt, &ttm)); } FILE *fp = freopen(("/tmp/sc_serv_segfault_" + tos(getpid()) + ".log").c_str(), "w", stderr); fprintf(stderr, "Shoutcast DNAS/" SERV_OSNAME " v%s (" __DATE__ ")\n" "An unrecoverable error (%d) has occurred @ %s.\n\n", gOptions.getVersionBuildStrings().hideAsString().c_str(), signum, buf); stacktrace::displayCurrentStackTrace(/*-1U, 0*/); fflush(fp); kill(getpid(), signum); } #endif unsigned sigWatcher() { while (::WaitForSingleObject(serviceMain::sStop,0) != WAIT_OBJECT_0) { sigset_t catchset; sigemptyset(&catchset); sigaddset(&catchset,SIGPIPE); sigaddset(&catchset,SIGTERM); sigaddset(&catchset,SIGHUP); sigaddset(&catchset,SIGINT); sigaddset(&catchset,SIGQUIT); sigaddset(&catchset,SIGTSTP); // ^Z allow this sigaddset(&catchset,SIGCHLD); sigaddset(&catchset,SIGWINCH); sigaddset(&catchset,SIGUSR1); sigaddset(&catchset,SIGUSR2); //struct timespec ts; //ts.tv_sec = 1; //ts.tv_nsec = 0; //apple is missing sigtimed wait. int err = 0; if (::sigwait(&catchset,&err)) { err = SIGTERM; } switch (err) { case SIGTERM: case SIGINT: case SIGQUIT: case SIGTSTP: { _SetEvent(serviceMain::sStop); break; } case SIGHUP: { _SetEvent(serviceMain::sHUP); break; } case SIGWINCH: { _SetEvent(serviceMain::sWINCH); break; } case SIGUSR1: { _SetEvent(serviceMain::sUSR1); break; } case SIGUSR2: { _SetEvent(serviceMain::sUSR2); break; } } } return 0; } // We need access to this for Apple, because we don't have sigtimedwait(). This means there // circumstances when we need access to this thread so we can signal it and force sigwait() to exit Tthread gSigWatcherThread(sigWatcher); #endif #ifdef _WIN32 int run_install(const vector &args) throw(); int run_uninstall(const vector &args) throw(); int run_run(const vector &args) throw(std::exception); int run_daemon(const vector &args) throw(std::exception); #else int run_run(const vector &args, const vector &arg) throw(std::exception); int run_daemon(const vector &args, const vector &arg) throw(std::exception); int blockSignals() throw() { return thread_CORE::standard_signal_block(); } #endif uniString::utf8 getVersionBuildStrings() throw() { #ifdef _WIN32 static utf8 version = ""; if (version.empty()) { getVersionInfo(version); } #else static utf8 version = ""; if (version.empty()) { for (int x = 0; x < VENT; ++x) { if (x) version += "."; version += tos(PRODUCTVERSION[x]); } } #endif #ifdef LICENCE_FREE version += " no-licence-check"; #endif return version; } #ifdef _WIN32 int do__main(vector &args) throw(std::exception) #else int do__main(vector &args, vector &arg) throw(std::exception) #endif { if (!args.empty()) { utf8 s = toLower(args.front()); if (s == "--version" || s == "-v") { XML_Expat_Version expat = XML_ExpatVersionInfo(); printf("%s", utf8("Shoutcast DNAS/" SERV_OSNAME " v" + getVersionBuildStrings() + " (" __DATE__ ") " + utf8(curl_version()) + " expat/" + tos(expat.major) + "." + tos(expat.minor) + "." + tos(expat.micro) + //#ifdef _WIN32 // " pthread-win32/" PTW32_VERSION_STR "-mod" + //#endif eol()).toANSI().c_str()); return 0; } if (s == "--help" || s == "/?") { #ifdef _WIN32 #define SC_SERV_END ".exe" #else #define SC_SERV_END "" #endif printf("%s", utf8("*********************************************************************" + eol()).toANSI().c_str()); printf("%s", utf8("** Shoutcast Distributed Network Audio Server (DNAS) **" + eol()).toANSI().c_str()); printf("%s", utf8("** Copyright (C) 2014-2023 Radionomy SA, All Rights Reserved **" + eol()).toANSI().c_str()); printf("%s", utf8("*********************************************************************" + eol()).toANSI().c_str()); printf("%s", utf8(eol() + "Usage: sc_serv" SC_SERV_END " [OPTION] [PARAMETERS]... [conf]" + eol()).toANSI().c_str()); printf("%s", utf8(eol() + "\t[conf] - File path to the configuration file (this can be" + eol() + "\t\t relative or absolute) [optional]" + eol()).toANSI().c_str()); printf("%s", utf8(eol() + "\t\t If not specified then sc_serv.conf / sc_serv.ini" + eol() + "\t\t in the same folder will be automatically loaded." + eol()).toANSI().c_str()); printf("%s", utf8(eol() + "Options:").toANSI().c_str()); printf("%s", utf8(eol() + "\t-s, setup\t\tRun the DNAS in setup mode for" + eol() + "\t\t\t\tcreating a basic configuration" + eol()).toANSI().c_str()); #ifdef CONFIG_BUILDER printf("%s", utf8(eol() + "\t-b, builder\t\tRun the DNAS in builder mode for" + eol() + "\t\t\t\tcreating an advanced configuration" + eol()).toANSI().c_str()); #endif printf("%s", utf8(eol() + "\t-v, --version\t\tDisplay version information" + eol()).toANSI().c_str()); printf("%s", utf8(eol() + "\t/?, --help\t\tDisplay this information" + eol()).toANSI().c_str()); #ifdef _WIN32 printf("%s", utf8(eol() + eol() + "Service Options:" + eol()).toANSI().c_str()); printf("%s", utf8(eol() + "\tinstall [servicename] [username] [password] [conf]" + eol()).toANSI().c_str()); printf("%s", utf8(eol() + "\t\tservicename - Unique name for the service install" + eol() + "\t\t\t if the default is not appropriate or" + eol() + "\t\t\t needing multiple services [optional]" + eol() + "\t\t\t Default is: \"Shoutcast DNAS Service\"" + eol()).toANSI().c_str()); printf("%s", utf8(eol() + "\t\tusername - User under which to run the service as" + eol() + "\t\t\t or '0' for the local system [optional]" + eol()).toANSI().c_str()); printf("%s", utf8(eol() + "\t\tpassword - Password for user or '0' for the local" + eol() + "\t\t\t system or with no password [optional]" + eol()).toANSI().c_str()); printf("%s", utf8(eol() + "\t\tconf - File path to the configuration file either" + eol() + "\t\t as a full or relative path [optional]" + eol()).toANSI().c_str()); printf("%s", utf8(eol() + "\texample:" + eol() + "\t\tsc_serv.exe install" + eol() + "\t\tor" + eol() + "\t\tsc_serv.exe install sc_serv" + eol() + "\t\tor" + eol() + "\t\tsc_serv.exe install sc_serv 0 0 sc_serv.conf" + eol()).toANSI().c_str()); printf("%s", utf8(eol() + eol() + "\tuninstall [servicename]" + eol()).toANSI().c_str()); printf("%s", utf8(eol() + "\t\tservicename - Name used to install the service or" + eol() + "\t\t\t leave blank for default [optional]" + eol()).toANSI().c_str()); printf("%s", utf8(eol() + "\texample:" + eol() + "\t\tsc_serv.exe uninstall" + eol() + "\t\tor" + eol() + "\t\tsc_serv.exe uninstall sc_serv" + eol() + eol()).toANSI().c_str()); #else #define SC_SERV_FILE "sc_serv" printf("%s", utf8(eol() + "Daemon Options:" + eol()).toANSI().c_str()); printf("%s", utf8(eol() + "\tdaemon [conf]\t\tRun the DNAS in daemon mode " + eol()).toANSI().c_str()); #endif XML_Expat_Version expat = XML_ExpatVersionInfo(); printf("%s", utf8(eol() + eol() + "Built with: " + utf8(curl_version()) + " expat/" + tos(expat.major) + "." + tos(expat.minor) + "." + tos(expat.micro) + eol()).toANSI().c_str()); return 0; } if (s == "install") { #ifdef _WIN32 args.erase(args.begin()); return run_install(args); #else cerr << "install is not supported on this platform" << endl; return -1; #endif } if (s == "uninstall") { #ifdef _WIN32 args.erase(args.begin()); return run_uninstall(args); #else cerr << "uninstall is not supported on this platform" << endl; return -1; #endif } if (s == "daemon") { args.erase(args.begin()); #ifdef _WIN32 return run_daemon(args); #else return run_daemon(args, arg); #endif } } #ifdef _WIN32 return run_run(args); #else return run_run(args, arg); #endif } int main(int argc, char* argv[]) throw(std::exception) { int result = 0; try { vector args; // convert args to vector of strings for (int x = 1; x < argc; ++x) { args.push_back(argv[x]); } // grab the calling program param as // will be needed to ensure relative // path handling will work correctly #ifdef _WIN32 result = do__main(args); #else vector arg; arg.push_back(argv[0]); result = do__main(args, arg); #endif } catch(const std::exception &err) { printf("%s", (std::string(LIBRARY_LOG_TAG) + "Exception in main: " + err.what()).c_str()); } return result; } ///////////////////////////////////////////////////// // surround by quotes #ifdef _WIN32 static wstring quote(const wstring &s) throw() { return wstring(L"\"") + s + wstring(L"\""); } int run_install(const vector &args) throw() { int result = -1; SC_HANDLE schSCManager = 0; SC_HANDLE schService = 0; try { wstring serviceName = (!args.empty() ? args[0].toWString().c_str() : L"Shoutcast DNAS Service"); wstring account = (args.size() > 2 ? (args[1] == "0" ? L"" : args[1].toWString()) : L""); wstring password = (args.size() > 3 ? (args[2] == "0" ? L"" : args[2].toWString()) : L""); const size_t SIZ(2048); wchar_t nameBuffer[SIZ+2] = {0}; ::GetModuleFileNameW(0,nameBuffer,SIZ); schSCManager = ::OpenSCManagerW(NULL, // local machine NULL, // ServicesActive database SC_MANAGER_ALL_ACCESS); // full access rights if (schSCManager == NULL) { if (GetLastError() == ERROR_ACCESS_DENIED) { throw runtime_error("Aborting service install due to a lack of required permissions.\n\n" "Ensure you are using an Administrator Command Prompt or that you have administrator access to be able to install a service."); } else { static char error[512]; snprintf(error, sizeof(error), "Aborting service install due to OpenSCManager(..) failure.\n\nError code: %d\n[%s]", GetLastError(), errMessage().c_str()); throw runtime_error(error); } } wstring cmdString = quote(nameBuffer) + L" " + quote(L"daemon") + L" " + quote(serviceName); if (args.size() > 4) cmdString += L" " + quote(args[3].toWString()); schService = ::CreateServiceW( schSCManager, // SCManager database serviceName.c_str(), // name of service serviceName.c_str(), // service name to display SERVICE_ALL_ACCESS, // desired access SERVICE_WIN32_OWN_PROCESS, // service type SERVICE_DEMAND_START, // start type SERVICE_ERROR_NORMAL, // error control type cmdString.c_str(), NULL, // no load ordering group NULL, // no tag identifier NULL, // no dependencies (account == L"" ? NULL : account.c_str()), // LocalSystem account (password == L"" ? NULL : password.c_str())); // no password if (schService == NULL) { int err = GetLastError(); static char error[512]; snprintf(error, sizeof(error), "Aborting service install due to CreateService(..) failure.\n\nError code: %d\n[%s]%s", err, errMessage().c_str(), (err == ERROR_SERVICE_EXISTS ? "\n\nCheck the service name has not already been used." : (err == ERROR_SERVICE_MARKED_FOR_DELETE ? "\n\nCheck the previous service instance has been completely stopped." : ""))); throw runtime_error(error); } else { SERVICE_DESCRIPTION schServiceDesc; schServiceDesc.lpDescription = L"Shoutcast DNAS Server (sc_serv) v2"; ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &schServiceDesc); } ::CloseServiceHandle(schService); ::CloseServiceHandle(schSCManager); result = 0; } catch(const exception &ex) { ::MessageBox(0,tows(ex.what()).c_str(),L"Shoutcast DNAS Error",MB_OK|MB_ICONEXCLAMATION); if (schService) ::CloseServiceHandle(schService); if (schSCManager) ::CloseServiceHandle(schSCManager); } catch(...) { ::MessageBox(0,L"Unknown exception",L"Shoutcast DNAS Error",MB_OK); if (schService) ::CloseServiceHandle(schService); if (schSCManager) ::CloseServiceHandle(schSCManager); } return result; } ///////////////////////////////////////////////////// int run_uninstall(const vector &args) throw() { int result = -1; SC_HANDLE schSCManager = 0; SC_HANDLE service = 0; try { schSCManager = ::OpenSCManagerW(NULL, // local machine NULL, // ServicesActive database SC_MANAGER_ALL_ACCESS); // full access rights if (schSCManager == NULL) { if (GetLastError() == ERROR_ACCESS_DENIED) { throw runtime_error("Aborting service uninstall due to a lack of required permissions.\n\n" "Ensure you are using an Administrator Command Prompt or that you have administrator access to be able to uninstall a service."); } else { static char error[512]; snprintf(error, sizeof(error), "Aborting service uninstall due to OpenSCManager(..) failure.\n\nError code: %d\n[%s]", GetLastError(), errMessage().c_str()); throw runtime_error(error); } } service = ::OpenServiceW(schSCManager, (!args.empty() ? args[0].toWString().c_str() : L"Shoutcast DNAS Service"), DELETE); if (!service) { int err = GetLastError(); static char error[512]; snprintf(error, sizeof(error), "Aborting service uninstall due to OpenService(..) failure.\n\nError code: %d\n[%s]%s", err, errMessage().c_str(), (err == ERROR_SERVICE_DOES_NOT_EXIST ? "\n\nCheck the service has not already been uninstalled and the\n" "service name matches what was used to register the service." : "")); throw runtime_error(error); } if (!::DeleteService(service)) { int err = GetLastError(); static char error[512]; snprintf(error, sizeof(error), "Aborting service uninstall due to DeleteService(..) failure.\n\nError code: %d\n[%s]%s", err, errMessage().c_str(), (err == ERROR_SERVICE_DOES_NOT_EXIST ? "\n\nCheck the service has not already been removed and that the\n" "service name matches what was used to register the service." : (err == ERROR_SERVICE_MARKED_FOR_DELETE ? "\n\nCheck the previous service instance has been completely stopped." : ""))); throw runtime_error(error); } ::CloseServiceHandle(service); ::CloseServiceHandle(schSCManager); result = 0; } catch(const exception &ex) { ::MessageBox(0,tows(ex.what()).c_str(),L"Shoutcast DNAS Error",MB_OK|MB_ICONEXCLAMATION); if (service) { ::CloseServiceHandle(service); } if (schSCManager) { ::CloseServiceHandle(schSCManager); } } catch(...) { if (service) ::CloseServiceHandle(service); if (schSCManager) ::CloseServiceHandle(schSCManager); } return result; } #endif ///////////////////////////////////////////////////// static vector gServiceArgs; #ifdef _WIN32 // Wraps SetServiceStatus. void SetTheServiceStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwCheckPoint, DWORD dwWaitHint) { SERVICE_STATUS ss; // Current status of the service. // Disable control requests until the service is started. if (dwCurrentState == SERVICE_START_PENDING) ss.dwControlsAccepted = 0; else ss.dwControlsAccepted = SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN; // Other flags include SERVICE_ACCEPT_PAUSE_CONTINUE // and SERVICE_ACCEPT_SHUTDOWN. // Initialize ss structure. ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS; ss.dwServiceSpecificExitCode = 0; ss.dwCurrentState = dwCurrentState; ss.dwWin32ExitCode = dwWin32ExitCode; ss.dwCheckPoint = dwCheckPoint; ss.dwWaitHint = dwWaitHint; // Send status of the service to the Service Controller. ::SetServiceStatus(ssh, &ss); } static void WINAPI service_ctrl(DWORD dwCtrlCode) { DWORD dwState = SERVICE_RUNNING; switch (dwCtrlCode) { case SERVICE_CONTROL_STOP: dwState = SERVICE_STOP_PENDING; break; case SERVICE_CONTROL_SHUTDOWN: dwState = SERVICE_STOP_PENDING; break; case SERVICE_CONTROL_INTERROGATE: break; default: break; } // Set the status of the service. SetTheServiceStatus(dwState, NO_ERROR, 0, 0); // Tell service_main thread to stop. if ((dwCtrlCode == SERVICE_CONTROL_STOP) || (dwCtrlCode == SERVICE_CONTROL_SHUTDOWN)) { _SetEvent(serviceMain::sStop); } } static void WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv) { try { // Register the service ctrl handler. //if (dwArgc == 0) if (gServiceArgs.empty()) { throw runtime_error("service_main - no name"); } ssh = ::RegisterServiceCtrlHandler(lpszArgv[0],(LPHANDLER_FUNCTION)service_ctrl); if (!ssh) { throw runtime_error("RegisterServiceCtrlHandler returned NULL"); } // The service has started. SetTheServiceStatus(SERVICE_RUNNING, 0, 0, 0); vector args(gServiceArgs.begin() + 1,gServiceArgs.end()); #ifdef _WIN32 ::SetConsoleCtrlHandler(_console_handler,TRUE); #else gSigWatcherThread.start(); #endif sm_main(args); #ifndef _WIN32 ::pthread_kill(gSigWatcherThread,SIGTERM); gSigWatcherThread.join(); #endif // Stop the service. OutputDebugString(TEXT("SetTheServiceStatus, SERVICE_STOPPED\n")); SetTheServiceStatus(SERVICE_STOPPED, NO_ERROR, 0, 0); } catch(...) { SetTheServiceStatus(SERVICE_STOPPED, ::GetLastError(), 0, 0); } } #endif #ifdef _WIN32 int run_daemon(const vector &args) throw(std::exception) #else int run_daemon(const vector &args, const vector &arg) throw(std::exception) #endif { sDaemon = true; gServiceArgs = args; _ResetEvent(serviceMain::sStop); #ifdef _WIN32 SERVICE_TABLE_ENTRY ste[] = {{TEXT("Shoutcast DNAS"),(LPSERVICE_MAIN_FUNCTION)service_main}, {NULL, NULL}}; gStartupDirectory = utf32(getfwd()).toUtf8(); if (!::StartServiceCtrlDispatcher(ste)) { static TCHAR error[512]; _snwprintf(error, sizeof(error), TEXT("Error code for StartServiceCtrlDispatcher: %u [%hs].\n"), GetLastError(), errMessage().c_str()); MessageBox (NULL, error, NULL, MB_SERVICE_NOTIFICATION); return -1; } return 0; #else pid_t pid; if ((pid = fork()) < 0) { return -1; } else if (pid != 0) { cout << "sc_serv2 going daemon with PID [" << tos(pid) << "]" << endl; exit(0); // parent goes away } // child continues setsid(); gStartupDirectory = getfwd((const char*)&(arg[0])); blockSignals(); gSigWatcherThread.start(); int result = sm_main(args); ::pthread_kill(gSigWatcherThread,SIGTERM); gSigWatcherThread.join(); return result; #endif } ////////////////////////////////////////////////////// #ifdef _WIN32 wchar_t* getfwd() throw() { // determine the actual location of ourselves and use as needed static wchar_t fwd[MAX_PATH]; if (!fwd[0]) { GetModuleFileNameW(NULL, fwd, ARRAYSIZE(fwd)); // this is needed for the service mode // so simpler to set it here than later gStartupPath = utf32(fwd).toUtf8(); PathRemoveFileSpecW(fwd); PathAddBackslashW(fwd); // this mirrors existing Windows handling despite // other 2.4.2 changes needing to be made for it. SetCurrentDirectory(fwd); } return fwd; } #else char* getfwd(const char* argv) throw() { // determine the actual location of ourselves and use as needed static char fwd[MAXPATHLEN + 1]; if (!fwd[0]) { // first attempt to use readlink(..) as per the platform build #if (defined PLATFORM_LINUX || defined PLATFORM_ARMv6 || defined PLATFORM_ARMv7) if(readlink("/proc/self/exe", fwd, sizeof(fwd) - 1) == -1) #endif #ifdef PLATFORM_BSD if(readlink("/proc/curproc/file", fwd, sizeof(fwd) - 1) == -1) #endif #ifdef __APPLE_CC__ uint32_t fwdSize = sizeof(fwd); if(!_NSGetExecutablePath(fwd, &fwdSize)) #endif { #ifdef __APPLE_CC__ // for this, we get the full program path which can include symlinks // so this will adjust it all so as to get a clean path and then to // strip off the program file name (also included) so we match all of // the other OS versions of this method so it will work consistently. strncpy(fwd, fileUtil::onlyPath(fileUtil::getFullFilePath(string(fwd))).hideAsString().c_str(), sizeof(fwd) - 1); #endif // now look at argv for a / in it if (strchr(argv, '/')) { // if it starts with a / it's absolute so just use if (argv[0] == '/') { strncpy(fwd, argv, sizeof(fwd) - 1); } // otherwise attempt to append to the cwd // only risk is if the cwd changed onload else { if (getcwd(fwd, sizeof(fwd) - 1)) { int len = sizeof(fwd) - strlen(fwd); strncat(fwd, argv, min(len - 1, (int)sizeof(fwd) - 1)); } // and if that doesn't work then set // it as / and behave like older builds else { strncpy(fwd, "/", sizeof(fwd) - 1); } } } } else { char tmp[MAXPATHLEN + 1] = {0}; strncpy(fwd, strncpy(tmp, dirname(fwd), sizeof(tmp) - 1), sizeof(fwd) - 1); } // must be slash terminated size_t fwd_len = strlen(fwd); if (fwd_len && (fwd_len < (sizeof(fwd) - 1)) && fwd[fwd_len - 1] != '/') { strncat(fwd, "/", sizeof(fwd) - 1); } } return fwd; } #endif #ifdef _WIN32 int run_run(const vector &args) throw(std::exception) #else int run_run(const vector &args, const vector &arg) throw(std::exception) #endif { #ifdef _WIN32 ::SetConsoleCtrlHandler(_console_handler,TRUE); gStartupDirectory = utf32(getfwd()).toUtf8(); #else if (blockSignals()) { cerr << "pthread_sigmask failed in run_run()" << endl; exit(-1); } gSigWatcherThread.start(); #ifdef PLATFORM_LINUX struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sa.sa_handler = custom_signal_handler; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGABRT, &sa, NULL); sigaction(SIGFPE, &sa, NULL); #endif gStartupDirectory = getfwd((const char*)&(arg[0])); #endif int result = 0; try { result = sm_main(args); } catch(const std::runtime_error &err) { printf("%s", (std::string(LIBRARY_LOG_TAG) + "Exception in main: " + err.what()).c_str()); } catch(const std::exception &err) { printf("%s", (std::string(LIBRARY_LOG_TAG) + "Exception in main: " + err.what()).c_str()); } catch(...) { printf("%s", (std::string(LIBRARY_LOG_TAG) + "Unknown exception in main()").c_str()); } #ifndef _WIN32 ::pthread_kill(gSigWatcherThread,SIGTERM); gSigWatcherThread.join(); #endif return result; }