/* * 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(nullptr, true, false, nullptr); if (stoppedEvent == nullptr) { throw GetLastError(); } // Create a manual-reset event to signal the end of the service. stoppingEvent = CreateEvent(nullptr, true, false, nullptr); if (stoppingEvent == nullptr) { throw GetLastError(); } // Initialize child job member childJob = nullptr; } virtual ~FDBService(void) { if (stoppedEvent) { CloseHandle(stoppedEvent); stoppedEvent = nullptr; } if (stoppingEvent) { CloseHandle(stoppingEvent); stoppingEvent = nullptr; } if (childJob) { CloseHandle(childJob); childJob = nullptr; } 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(nullptr, CSIDL_COMMON_APPDATA, nullptr, 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(nullptr, TRUE, nullptr); 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 nullptr, // Process handle not inheritable nullptr, // Thread handle not inheritable FALSE, // Set handle inheritance to FALSE CREATE_NEW_PROCESS_GROUP, // No flags for creation nullptr, // Use parent's environment block nullptr, // 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, nullptr, nullptr, 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 | SO_O_HYPHEN_TO_UNDERSCORE); 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( nullptr, // default security descriptor TRUE, // attempt to create with ownership format( "Global\\" SERVICE_NAME ".%s", escaped.c_str() ).c_str() ); // object name if (hMutex == nullptr) 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()).c_str()); } if (!fileNameStart) { errorExit(format("file name not present (%s)", confFile.c_str()).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(nullptr, FALSE, nullptr); 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; i < subprocesses.size(); i++) events[subprocessWaitIndexBase + i] = subprocesses[i]->getHandleToWaitOn(); // 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.emplace_back(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.emplace_back(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 = nullptr; const char* section = nullptr; std::string nameWithUnderscores(name); for (int i = nameWithUnderscores.size() - 1; i >= 0; --i) { if (nameWithUnderscores[i] == '-') { nameWithUnderscores.at(i) = '_'; } } va_list ap; va_start(ap, name); while (!ret && (section = va_arg(ap, const char*))) { ret = ini.GetValue(section, name, nullptr); if (!ret) { ret = ini.GetValue(section, nameWithUnderscores.c_str(), nullptr); } } va_end(ap); return ret; } bool isParameterNameEqual(const char* str, const char* target) { if (!str || !target) { return false; } while (*str && *target) { char curStr = *str, curTarget = *target; if (curStr == '-') { curStr = '_'; } if (curTarget == '-') { curTarget = '_'; } if (curStr != curTarget) { return false; } str++; target++; } return !(*str || *target); } 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", nullptr); 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", nullptr); if (q && !strcmp(q, "true")) result.quiet = true; const char* binary = getValueMulti(ini, "command", ssection.c_str(), section.c_str(), "general", nullptr); 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 (isParameterNameEqual(i.pItem, "command") || isParameterNameEqual(i.pItem, "restart-delay") || isParameterNameEqual(i.pItem, "disable-lifecycle-logging")) { continue; } std::string opt = getValueMulti(ini, i.pItem, ssection.c_str(), section.c_str(), "general", nullptr); 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 = nullptr; 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(int 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] == nullptr) || (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(), nullptr)) { 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; }