/* * FDBService.cpp * * This source file is part of the FoundationDB open source project * * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ServiceBase.h" #include "ThreadPool.h" #include #include #include #include #include #include "..\flow\SimpleOpt.h" #include "..\fdbmonitor\SimpleIni.h" #include "fdbclient/versions.h" // For PathFileExists #include "Shlwapi.h" #pragma comment(lib, "Shlwapi.lib") // For SHGetFolderPath #include #pragma comment(lib, "Shell32.lib") // To be used for logging, naming of Mutex, etc. #ifdef __FDB_DOC_MONITOR__ #define SERVICE_NAME "fdbdocmonitor" #define CONFIG_NAME "document\\document.conf" #else // __FDB_KVS_MONITOR__ #define SERVICE_NAME "fdbmonitor" #define CONFIG_NAME "foundationdb.conf" #endif // Set up the options parsing. We have only one option now, but this could change! enum { OPT_CONFFILE }; CSimpleOpt::SOption g_rgOptions[] = { { OPT_CONFFILE, "--conffile", SO_REQ_SEP }, SO_END_OF_OPTIONS }; // For logging debugging messages bool logging = false; std::ofstream logFile; std::string format( const char* form, ... ) { char buf[200]; va_list args; va_start(args, form); int size = vsnprintf(buf, sizeof(buf), form, args); va_end(args); if(size >= 0 && size < sizeof(buf)) { return std::string(buf, size); } #ifdef _WIN32 // Microsoft's non-standard vsnprintf doesn't return a correct size, but just an error, so determine the necessary size va_start(args, form); size = _vscprintf(form, args); va_end(args); #endif if (size < 0) throw std::exception("Error in format"); std::string s; s.resize(size + 1); va_start(args, form); size = vsnprintf(&s[0], s.size(), form, args); va_end(args); if (size < 0 || size >= s.size()) throw std::exception("Error in format"); s.resize(size); return s; } void writeLogLine(std::string message) { if( logging && logFile.good() ) { char datebuf[128], timebuf[128]; _strdate_s( datebuf, 128 ); _strtime_s( timebuf, 128 ); logFile << datebuf << " " << timebuf << " - " << message << "\n"; logFile.flush(); } } class FDBService : public CServiceBase { public: FDBService(bool fCanStop = true, bool fCanShutdown = true, bool fCanPauseContinue = false) : CServiceBase(SERVICE_NAME, fCanStop, fCanShutdown, fCanPauseContinue), serviceStopping( false ) { // Create a manual-reset event to signal the end of the service. stoppedEvent = CreateEvent(NULL, true, false, NULL); if (stoppedEvent == NULL) { throw GetLastError(); } // Create a manual-reset event to signal the end of the service. stoppingEvent = CreateEvent(NULL, true, false, NULL); if (stoppingEvent == NULL) { throw GetLastError(); } // Initialize child job member childJob = NULL; } virtual ~FDBService(void) { if (stoppedEvent) { CloseHandle(stoppedEvent); stoppedEvent = NULL; } if (stoppingEvent) { CloseHandle(stoppingEvent); stoppingEvent = NULL; } if(childJob) { CloseHandle(childJob); childJob = NULL; } logFile.close(); }; // The following method will allow the object to run // within the foreground virtual bool Run(void) { int status = 0; // Start the service try { // Define the configuration file confFile = GetDefaultConfigFilePath(); // Run the worker thread ServiceWorkerThread(); } catch (DWORD dwError) { // Log the error. WriteErrorLogEntry("Service Start", dwError); status++; } catch (...) { // Log the error. WriteEventLogEntry("Service failed to start.", EVENTLOG_ERROR_TYPE); status++; } return (status == 0); } protected: // The following function will return the default configuration // file path std::string GetDefaultConfigFilePath() { TCHAR programData[MAX_PATH]; if (SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, NULL, 0, programData) != S_OK) { errorExit("resolving CSIDL_COMMON_APPDATA"); } return std::string(programData) + "\\foundationdb\\" + CONFIG_NAME; } struct Command { std::string binary; std::string args; std::string section; std::string ssection; int restartDelay; boolean quiet; boolean valid; Command() : valid( false ), quiet( false ), restartDelay( 5 ) {} bool operator != ( const Command& rhs ) { return binary != rhs.binary || args != rhs.args; } }; struct Subprocess { // Represents a single subprocess which is in the configuration file. It is either running // or waiting (on a timer) to restart. // Interface to event loop Subprocess( FDBService* svc, int id, Command const& cmd ) : svc(svc), id(id), command(cmd), process_or_timer(INVALID_HANDLE_VALUE), isProcess(true) { createProcess(); } ~Subprocess() { if (isProcess) { // FIXME: should this be other than 0? Should we care if this fails? if( !TerminateProcess( process_or_timer, 0 ) ) { svc->errorExit("Terminate fdbmonitor process"); } // TerminateProcess is asynchronous, wait for the process to die if( !command.quiet ) svc->LogEvent( EVENTLOG_INFORMATION_TYPE, format( "Waiting for process %d to terminate", id ) ); DWORD signal = WaitForSingleObject( process_or_timer, INFINITE ); if( signal != WAIT_OBJECT_0 ) { svc->errorExit("Termination wait for process"); } else if( !command.quiet ) { svc->LogEvent( EVENTLOG_INFORMATION_TYPE, format( "Process %d has terminated", id ) ); } } CloseHandle( process_or_timer ); } HANDLE getHandleToWaitOn() { return process_or_timer; } void onHandleSignaled() { if (isProcess) { DWORD exitCode; if( !GetExitCodeProcess(process_or_timer, &exitCode) ) { svc->logLastError("process get exit code"); exitCode = 2181; // FIXME: WHAT? } if( !command.quiet ) svc->LogEvent( EVENTLOG_INFORMATION_TYPE, format( "Child process %d exited with %d, restarting in %d seconds", id, exitCode, command.restartDelay ) ); CloseHandle( process_or_timer ); startTimer(); } else { CloseHandle( process_or_timer ); createProcess(); } } // Intrusive indices for external data structures int id; size_t subprocess_index; // Configuration and state FDBService* svc; Command command; HANDLE process_or_timer; bool isProcess; // otherwise, is waiting to restart // Implementation void startTimer() { isProcess = false; process_or_timer = CreateWaitableTimer(NULL, TRUE, NULL); if( !process_or_timer ) svc->errorExit( format( "Error in startTimer(): CreateWaitableTimer (%d)", GetLastError() ).c_str() ); svc->startTimer(process_or_timer, command.restartDelay); } void createProcess() { isProcess = true; STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); ZeroMemory( &pi, sizeof(pi) ); if( !command.quiet ) svc->LogEvent( EVENTLOG_INFORMATION_TYPE, format( "Starting child job (%s)", command.args.c_str() ) ); // Start the child process. if (!CreateProcess(command.binary.c_str(), // Command's binary (char *)command.args.c_str(), // Command's args NULL, // Process handle not inheritable NULL, // Thread handle not inheritable FALSE, // Set handle inheritance to FALSE CREATE_NEW_PROCESS_GROUP, // No flags for creation NULL, // Use parent's environment block NULL, // Use parent's starting directory &si, // Pointer to STARTUPINFO structure &pi ) // Pointer to PROCESS_INFORMATION structure ) { svc->logLastError( format("Failed to create process, restarting in %d seconds (%s)", command.restartDelay, command.args.c_str()).c_str() ); // If there was an error, we set a timer right away for the restart. startTimer(); } else { CloseHandle(pi.hThread); process_or_timer = pi.hProcess; if( !command.quiet ) svc->LogEvent( EVENTLOG_INFORMATION_TYPE, format( "Child %d started with PID %d", id, pi.dwProcessId ) ); } } void update( Command cmd ) { command.quiet = cmd.quiet; command.restartDelay = cmd.restartDelay; } }; void startTimer(HANDLE h, double delaySeconds) { LARGE_INTEGER liDueTime; // negative numbers are relative times, times are in 100s of ns -- see SetWaitableTimer MSDN docs liDueTime.QuadPart = (LONGLONG)(delaySeconds * -10000000LL); if (!SetWaitableTimer(h, &liDueTime, 0, NULL, NULL, 0)) errorExit( format( "Error in startTimer(): SetWaitableTimer (%d)", GetLastError() ).c_str() ); } virtual void FDBService::OnStart(DWORD argc, LPSTR *argv) { LogEvent( EVENTLOG_INFORMATION_TYPE, SERVICE_NAME " starting (" FDB_VT_VERSION ")"); std::string _confpath(GetDefaultConfigFilePath()); LogEvent( EVENTLOG_INFORMATION_TYPE, format("Default config file at %s", _confpath.c_str() ) ); // Parse "command line" options CSimpleOpt args(argc, argv, g_rgOptions, SO_O_NOERR); while (args.Next()) { if (args.LastError() == SO_SUCCESS) { switch (args.OptionId()) { case OPT_CONFFILE: _confpath = args.OptionArg(); break; } } else { // FIXME: log error throw (DWORD)1; } } confFile = _confpath; LogEvent( EVENTLOG_INFORMATION_TYPE, format("Using config file %s", confFile.c_str() ) ); // Aquire the mutex for this service. There will be one mutex per config file. // If the user installes the executable as more than one service (not likely) // we allow that both can operate so long as they are pointed at different // config files. // For now we will not do this check...there is question about whether we need this /* std::size_t pos = 0; // mutexes cannot have a backslash in their name std::string escaped = confFile; while( ( pos = escaped.find( "\"", pos ) ) != escaped.npos ) escaped.replace( pos, 1, "/" ); HANDLE hMutex = CreateMutex( NULL, // default security descriptor TRUE, // attempt to create with ownership format( "Global\\" SERVICE_NAME ".%s", escaped.c_str() ).c_str() ); // object name if (hMutex == NULL) errorExit( format( "Could not create/aquire global mutex (%s)", escaped.c_str() ).c_str() ); else // In this case ownership on creation flag is ignored. (see MSDN docs) if ( GetLastError() == ERROR_ALREADY_EXISTS ) errorExit( "mutex created without ownership"); LogEvent( EVENTLOG_INFORMATION_TYPE, "instance uniqueness established" ); */ // Queue the main service function for execution in a worker thread. CThreadPool::QueueUserWorkItem(&FDBService::ServiceWorkerThread, this); } virtual void FDBService::OnStop() { LogEvent(EVENTLOG_INFORMATION_TYPE, SERVICE_NAME " shutting down"); serviceStopping = true; SetEvent(stoppingEvent); if (WaitForSingleObject(stoppedEvent, INFINITE) != WAIT_OBJECT_0) { // We're exiting, so this will be logged, but an error will not be thrown logLastError("OnStop final wait"); } // Log a service stop message to the Application log. LogEvent(EVENTLOG_INFORMATION_TYPE, SERVICE_NAME " stop complete"); } virtual void ServiceWorkerThread(void) { try { char confFileDirectory[2048]; char *fileNameStart; if( !GetFullPathName( confFile.c_str(), 2048, confFileDirectory, &fileNameStart ) ) { errorExit( format( "get path of conf file (%s)", confFile ).c_str() ); } if( !fileNameStart ) { errorExit( format( "file name not present (%s)", confFile ).c_str() ); } // Test file existence if( !PathFileExists( confFileDirectory ) ) { errorExit( format( "conf file (%s) does not exist", confFileDirectory ).c_str() ); } // null terminate the absolute path string after the directory *fileNameStart = 0; // confFileDirectory now contains only the parent dir of the conf file // FALSE here as "manualReset" means that the timer will go to the unsignaled state once // signalled from a "wait" call. HANDLE fileErrorReloadHandle = CreateWaitableTimer(NULL, FALSE, NULL); if( fileErrorReloadHandle == INVALID_HANDLE_VALUE ) { errorExit( format( "Error creating waitable timer (%d)", GetLastError() ).c_str() ); } HANDLE fileChangeHandle = FindFirstChangeNotification( confFileDirectory, FALSE, // do not watch the subtree FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_FILE_NAME ); if( fileChangeHandle == INVALID_HANDLE_VALUE ) { errorExit( format( "FindFirstChangeNotification (%s)", confFileDirectory ).c_str() ); } else { LogEvent( EVENTLOG_INFORMATION_TYPE, format( "watching directory %s", confFileDirectory ) ); } loadConf( confFile.c_str() ); const int subprocessWaitIndexBase = 3; while( !serviceStopping ) { // The events to be waited on will have this sequence... // The signals and timers are checked in the subprocesses[] map // 0 - the shutdown signal // 1 - the file change notification // 2 - the file load error timer // [n] - signals for subprocesses (process termination or restart timer) int eventCount = subprocessWaitIndexBase + (int)subprocesses.size(); HANDLE *events = new HANDLE[eventCount]; events[0] = stoppingEvent; events[1] = fileChangeHandle; events[2] = fileErrorReloadHandle; for(int i=0; igetHandleToWaitOn(); // FIXME: Check that eventCount < MAXIMUM_WAIT_OBJECTS, here and/or in loadConf // FIXME: Is 'fileChangeHandle' signalling each time around the loop? // Wait for any of the events to be signalled DWORD signalled = WaitForMultipleObjects( eventCount, events, FALSE, INFINITE ); delete [] events; // Check for error cases (anything other than one of the objects being signalled) if( signalled < WAIT_OBJECT_0 || signalled >= WAIT_OBJECT_0 + eventCount ) { WriteEventLogEntry( SERVICE_NAME " wait failed", EVENTLOG_ERROR_TYPE ); // prevent fast spin Sleep(2000); } else if( signalled == WAIT_OBJECT_0 + 0 ) { WriteEventLogEntry( SERVICE_NAME " service shutdown signalled", EVENTLOG_INFORMATION_TYPE ); } else if( signalled == WAIT_OBJECT_0 + 1 ) { if( !loadConf( confFile.c_str() ) ) { // An error was encountered in loading the conf file...set a timer to force a reload double retrySeconds = 0.1; startTimer( fileErrorReloadHandle, retrySeconds ); WriteEventLogEntry( format( SERVICE_NAME " scheduling reload in %f seconds", retrySeconds ).c_str(), EVENTLOG_INFORMATION_TYPE ); } else { // The load was a success but we may still have an error-caused reload scheduled. // Cancel any outstanding timers to avoid an uneeded reload of the file. CancelWaitableTimer( fileErrorReloadHandle ); } // Reset the signal handling. if( !FindNextChangeNotification( fileChangeHandle ) ) { errorExit("FindNextChangeNotification"); } } else if( signalled == WAIT_OBJECT_0 + 2 ) { WriteEventLogEntry( SERVICE_NAME " attempting configuration reload after error", EVENTLOG_INFORMATION_TYPE ); if( !loadConf( confFile.c_str() ) ) { // An error was encountered in reloading the file after another error // occurred, this should have a longer delay to prevent a spin loop startTimer( fileErrorReloadHandle, 1.0 ); } } else { Subprocess* sp = subprocesses[ signalled - WAIT_OBJECT_0 - subprocessWaitIndexBase ]; sp->onHandleSignaled(); } } for(auto s = subprocesses.begin(); s != subprocesses.end(); ++s) delete *s; subprocesses.clear(); id_subprocess.clear(); } catch(...) { LogEvent( EVENTLOG_ERROR_TYPE, SERVICE_NAME " unexpected exception thrown" ); } try { LogEvent( EVENTLOG_INFORMATION_TYPE, SERVICE_NAME " signalling stopped" ); // Signal the stopped event to allow "onStop" to complete SetEvent( stoppedEvent ); } catch(...) { LogEvent( EVENTLOG_ERROR_TYPE, SERVICE_NAME " unexpected exception thrown while stopping" ); } } public: // The following method stops the service when ctrl-break is pressed void Break() { LogEvent(EVENTLOG_INFORMATION_TYPE, SERVICE_NAME " shutting down"); serviceStopping = true; SetEvent(stoppingEvent); if (WaitForSingleObject(stoppedEvent, INFINITE) != WAIT_OBJECT_0) { // We're exiting, so this will be logged, but an error will not be thrown logLastError("OnStop final wait"); } // Log a service stop message to the Application log. LogEvent(EVENTLOG_INFORMATION_TYPE, SERVICE_NAME " stop complete"); return; } private: void findRemovedOrChangedSubprocesses(const CSimpleIni& ini, std::vector const& subprocesses, std::vector & stop_processes, std::vector> & start_ids) { for( auto it = subprocesses.begin(); it != subprocesses.end(); ++it ) { Subprocess* sp = *it; if( ini.GetSectionSize( sp->command.ssection.c_str() ) == -1) { /* Server on this id no longer configured; kill it */ LogEvent( EVENTLOG_INFORMATION_TYPE, format( "Deconfigured process (ID %d)", sp->id ) ); stop_processes.push_back( sp ); } else { /* Server still configured; let's check the config */ Command cmd = makeCommand( ini, sp->command.section, sp->id ); /* On error we just stop the old process but do not start a new one */ if( !cmd.valid ) { stop_processes.push_back( sp ); } /* The inequality operation only compares the binary path and arguments */ else if( cmd != sp->command ) { /* Config changed; kill the old process, start a new one */ /* Log only if one of the two are non-quiet */ if( !cmd.quiet || !sp->command.quiet ) LogEvent( EVENTLOG_INFORMATION_TYPE, format( "Found new configuration for process (ID %d)", sp->id ) ); stop_processes.push_back( sp ); start_ids.push_back( std::make_pair( sp->id, cmd ) ); } else if( cmd.quiet != sp->command.quiet || cmd.restartDelay != sp->command.restartDelay ) { // Update restartDelay and quiet but do not restart running processes if( !cmd.quiet || !sp->command.quiet ) LogEvent( EVENTLOG_INFORMATION_TYPE, format( "Updating process (ID %d)", sp->id ) ); sp->update( cmd ); } } } } void findAddedSubprocesses(const CSimpleIni& ini, std::unordered_map const& id_subprocess, std::vector> & start_ids) { CSimpleIniA::TNamesDepend sections; ini.GetAllSections( sections ); for( auto it = sections.begin(); it != sections.end(); it++ ) { if( auto dot = strrchr( it->pItem, '.') ) { char* strtol_end; int id = strtoul( dot + 1, &strtol_end, 10 ); if( *strtol_end != '\0' || !(id > 0) ) { LogEvent( EVENTLOG_ERROR_TYPE, format( "Found bogus id in %s", it->pItem ) ); } else { if( !id_subprocess.count(id) ) { /* Found something we haven't yet started */ LogEvent( EVENTLOG_INFORMATION_TYPE, format( "Found new process (ID %d)", id ) ); std::string section( it->pItem, dot - it->pItem ); Command cmd = makeCommand( ini, section, id ); if( cmd.valid ) { start_ids.push_back( std::make_pair( id, cmd ) ); } else { LogEvent( EVENTLOG_ERROR_TYPE, format( "New process (ID %d) does not have a valid specification and will not be started", id ) ); } } } } } } // Returns 'true' on successful load, 'false' if an error is encountered bool loadConf(const char* confpath) { LogEvent( EVENTLOG_INFORMATION_TYPE, format( "Loading configuration %s", confpath ) ); CSimpleIniA ini; ini.SetUnicode(); // Set to a negative code on error. SI_Error err = ini.LoadFile( confpath ); if( err < 0 ) { // If the error was file not found, we assume that future changes to the file will // come in as change notifications. This means that we will not poll, we just leave // processes running as-is waiting for the file to reappear. if( err == SI_FILE && GetLastError() == ERROR_FILE_NOT_FOUND ) { LogEvent( EVENTLOG_ERROR_TYPE, format( "Configuration file `%s' not found on load, waiting for next change", confpath ) ); return true; } LogEvent( EVENTLOG_ERROR_TYPE, format( "Configuration file (`%s') load error: %d, %d", confpath, err, GetLastError() ) ); return false; } std::vector stop_processes; std::vector> start_ids; /* First check all processes (running or waiting to restart) to be sure that current config matches saved file state */ findRemovedOrChangedSubprocesses( ini, subprocesses, stop_processes, start_ids ); /* We've handled deconfigured servers and changed configs, but now we need to look for newly configured servers */ findAddedSubprocesses( ini, id_subprocess, start_ids ); for(auto it = stop_processes.begin(); it != stop_processes.end(); ++it ) { removeSubprocess(*it); delete *it; // Destructor terminates and waits for termination of actual process (if any) } for( auto it = start_ids.begin(); it != start_ids.end(); it++ ) addSubprocess( new Subprocess(this, it->first, it->second) ); return true; } const char* getValueMulti(const CSimpleIni& ini, const char* name, ...) { const char* ret = NULL; const char* section = NULL; va_list ap; va_start(ap, name); while( !ret && (section = va_arg(ap, const char *) ) ) { ret = ini.GetValue( section, name, NULL ); } va_end(ap); return ret; } Command makeCommand(const CSimpleIni& ini, std::string section, uint16_t id) { std::string ssection = format("%s.%d", section.c_str(), id); Command result; CSimpleIniA::TNamesDepend keys, skeys, gkeys; ini.GetAllKeys(section.c_str(), keys); ini.GetAllKeys(ssection.c_str(), skeys); ini.GetAllKeys("general", gkeys); keys.splice(keys.end(), skeys, skeys.begin(), skeys.end()); keys.splice(keys.end(), gkeys, gkeys.begin(), gkeys.end()); keys.sort(CSimpleIniA::Entry::KeyOrder()); keys.unique( [](const CSimpleIniA::Entry& lhs, const CSimpleIniA::Entry& rhs) -> bool { return !CSimpleIniA::Entry::KeyOrder()(lhs, rhs); } ); const char* rd = getValueMulti(ini, "restart_delay", ssection.c_str(), section.c_str(), "general", "fdbmonitor", NULL); if(!rd) { LogEvent( EVENTLOG_ERROR_TYPE, format( "Unable to resolve restart delay for %s\n", ssection.c_str() ) ); return result; } char* endptr; result.restartDelay = strtoul(rd, &endptr, 10); if (*endptr != '\0') { LogEvent( EVENTLOG_ERROR_TYPE, format( "Unable to parse restart delay for %s\n", ssection.c_str() ) ); return result; } const char* q = getValueMulti(ini, "disable_lifecycle_logging", ssection.c_str(), section.c_str(), "general", NULL); if( q && !strcmp(q, "true") ) result.quiet = true; const char* binary = getValueMulti(ini, "command", ssection.c_str(), section.c_str(), "general", NULL); if( !binary ) { LogEvent( EVENTLOG_ERROR_TYPE, format( "Unable to resolve command for %s.%d\n", section.c_str(), id ) ); return result; } result.binary = binary; result.args = quote(binary); const char* id_s = ssection.c_str() + strlen( section.c_str() ) + 1; for( auto i : keys ) { if( !strcmp(i.pItem, "command") || !strcmp(i.pItem, "restart_delay") || !strcmp(i.pItem, "disable_lifecycle_logging")) { continue; } std::string opt = getValueMulti( ini, i.pItem, ssection.c_str(), section.c_str(), "general", NULL ); std::size_t pos = 0; while( (pos = opt.find("$ID", pos)) != opt.npos ) opt.replace( pos, 3, id_s, strlen(id_s) ); pos = 0; std::string pid_s = format("%d", GetCurrentProcessId()); while( (pos = opt.find("$PID", pos)) != opt.npos ) opt.replace( pos, 4, pid_s.c_str(), pid_s.size() ); result.args += std::string(" --") + i.pItem + "=" + quote(opt); } result.section = section; result.ssection = ssection; result.valid = true; return result; } static std::string quote( std::string const& s ) { std::string q; /* Replace every backslash whose next non-backslash character is either the end of the string or a double-quote by two backslashes */ for( int i = 0; i < s.size(); i++ ) { q.append( 1, s[i] ); if( s[i] == '\\' ) { int add = true; for( int j = i + 1; j < s.size(); j++ ) { // If any character other than backslash is encountered, do not duplicate. // Start with "true" so hitting the end of the string keep "add" true. if( s[j] != '\\' ) { add = false; break; } } if( add ) q.append( 1, '\\' ); } } /* Replace every double-quote in the string by backslash double-quote */ std::size_t pos = 0; while( ( pos = q.find( "\"", pos ) ) != q.npos ) q.replace( pos, 1, "\\\"" ); return "\"" + q + "\""; } /////////////// // Error handling, etc. /////////////// void errorExit(const char *context) { logLastError(context); ExitProcess(GetLastError()); } void LogEvent(int wType, std::string message) { WriteEventLogEntry( message.c_str(), wType ); writeLogLine( message ); } void logLastError(const char *context) { LogEvent( EVENTLOG_ERROR_TYPE, format( "%s failed (%d)", context, GetLastError() ) ); } std::string confFile; bool serviceStopping; HANDLE stoppingEvent; HANDLE stoppedEvent; HANDLE childJob; std::unordered_map id_subprocess; std::vector subprocesses; void addSubprocess( Subprocess* newsp ) { newsp->subprocess_index = subprocesses.size(); subprocesses.push_back(newsp); id_subprocess[ newsp->id ] = newsp; } void removeSubprocess( Subprocess* sp ) { subprocesses.back()->subprocess_index = sp->subprocess_index; subprocesses[sp->subprocess_index] = subprocesses.back(); subprocesses.pop_back(); id_subprocess.erase( sp->id ); } }; static FDBService* staticService = NULL; bool consoleHandler(int signal) { // Break from the program if ctrl-c is pressed if ((signal == CTRL_C_EVENT) && (staticService) ) { staticService->Break(); } return true; } void print_usage(const char *name) { printf( "FoundationDB Process Monitor " SERVICE_NAME " (v" FDB_VT_VERSION ")\n" "Usage: %s [OPTIONS]\n" "\n" " -f --foreground Run the process in the foreground and not as a service\n" " -l --logging Enable logging\n" " -h, --help Display this help and exit.\n", name); } int main(DWORD argc, LPCSTR *argv) { _set_FMA3_enable(0); // Workaround for VS 2013 code generation bug. See https://connect.microsoft.com/VisualStudio/feedback/details/811093/visual-studio-2013-rtm-c-x64-code-generation-bug-for-avx2-instructions int status = 0; bool bBackground, bRun; // Initialize variables bBackground = true; bRun = true; // Check if argument is specified to run in foreground for (DWORD loop = 1; loop < argc; loop++) { // Ignore undefined or non-options if ((argv[loop] == NULL) || (argv[loop][0] == '\0')) { } // Check, if foreground option else if ((!_strnicmp("-f", argv[loop], 2)) || (!_strnicmp("--f", argv[loop], 3))) { bBackground = false; } // Check, if logging is enabled else if ((!strnicmp("-l", argv[loop], 2)) || (!_strnicmp("--l", argv[loop], 3))) { logging = true; } // Check, if help is requested else if ((!strnicmp("-h", argv[loop], 2)) || (!_strnicmp("--h", argv[loop], 3))) { print_usage(argv[0]); // Disable execution, if displaying help bRun = false; } } // Only run, if run is still enabled if (bRun) try { // the "start arguments" to the service are passed to the OnStart call, not to here if( logging ) { // Determine the default conf path char programData[2048]; if( !GetEnvironmentVariable( "ALLUSERSPROFILE", programData, 2048 ) ) { throw GetLastError(); } if( !CreateDirectory( format( "%s\\foundationdb", programData ).c_str(), NULL ) ) { if( GetLastError() != ERROR_ALREADY_EXISTS ) throw GetLastError(); } logFile.open(programData + std::string("\\foundationdb\\servicelog.txt")); } FDBService service(SERVICE_NAME); // If not background, run the service in the foreground if (!bBackground) { // Store the service class staticService = &service; // Enable the ctrl handler SetConsoleCtrlHandler((PHANDLER_ROUTINE)consoleHandler, TRUE); // Run the service if (!service.Run()) { fprintf(stderr, "Service failed to run w/err 0x%08lx\n", GetLastError()); status++; } } // Otherwise, run the service within the background using the service manager else if (!CServiceBase::Run(service)) { fprintf(stderr, "Service failed to run w/err 0x%08lx\n", GetLastError()); status++; } else if( logging ) { } } catch( DWORD errno ) { writeLogLine( format( "Service threw exception 0x%08lx\n", errno ) ); fprintf(stderr, "Service failed to run w/err 0x%08lx\n", errno); status++; } catch (...) { writeLogLine( format( "Service failed with unexpected error (last error: %d)\n", GetLastError() ) ); fprintf(stderr, "Service failed with unexpected error (last error: %d)\n", GetLastError()); status++; } return status; }