mirror of
https://github.com/apple/foundationdb.git
synced 2026-01-25 04:18:18 +00:00
Remove unused fdbservice directory (#12623)
The fdbservice directory contained Windows-specific service code that is no longer maintained and does not look like it is used elsewhere. This removes the directory and its corresponding CMake configuration.
This commit is contained in:
committed by
GitHub
parent
8c1a69ba60
commit
0c67384ac5
@@ -293,8 +293,6 @@ if(NOT WIN32)
|
||||
# x-compiling.
|
||||
add_subdirectory(fdbmonitor)
|
||||
endif()
|
||||
else()
|
||||
add_subdirectory(fdbservice)
|
||||
endif()
|
||||
add_subdirectory(fdbbackup)
|
||||
add_subdirectory(tests)
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
set(FDBSERVICE_SRCS FDBService.cpp ServiceBase.cpp)
|
||||
add_executable(fdbmonitor ${FDBSERVICE_SRCS})
|
||||
|
||||
get_target_property(fdbclient_target_includes fdbclient INCLUDE_DIRECTORIES)
|
||||
target_link_libraries(fdbmonitor PUBLIC SimpleOpt)
|
||||
target_include_directories(fdbmonitor PUBLIC "${fdbclient_target_includes}")
|
||||
@@ -1,942 +0,0 @@
|
||||
/*
|
||||
* FDBService.cpp
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2024 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 <iostream>
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "SimpleOpt/SimpleOpt.h"
|
||||
#include "fdbclient/SimpleIni.h"
|
||||
#include "fdbclient/versions.h"
|
||||
|
||||
// For PathFileExists
|
||||
#include "Shlwapi.h"
|
||||
#pragma comment(lib, "Shlwapi.lib")
|
||||
// For SHGetFolderPath
|
||||
#include <ShlObj.h>
|
||||
#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()));
|
||||
|
||||
// Acquire the mutex for this service. There will be one mutex per config file.
|
||||
// If the user installs 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/acquire 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 unneeded 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<Subprocess*> const& subprocesses,
|
||||
std::vector<Subprocess*>& stop_processes,
|
||||
std::vector<std::pair<uint16_t, Command>>& 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<uint16_t, Subprocess*> const& id_subprocess,
|
||||
std::vector<std::pair<uint16_t, Command>>& 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<Subprocess*> stop_processes;
|
||||
std::vector<std::pair<uint16_t, Command>> 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<uint16_t, Subprocess*> id_subprocess;
|
||||
std::vector<Subprocess*> 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;
|
||||
}
|
||||
@@ -1,360 +0,0 @@
|
||||
/****************************** Module Header ******************************\
|
||||
* Module Name: ServiceBase.cpp
|
||||
* Project: CppWindowsService
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Provides a base class for a service that will exist as part of a service
|
||||
* application. CServiceBase must be derived from when creating a new service
|
||||
* class.
|
||||
*
|
||||
* This source is subject to the Microsoft Public License.
|
||||
* See http://www.microsoft.com/en-us/openness/resources/licenses.aspx#MPL.
|
||||
* All other rights reserved.
|
||||
*
|
||||
* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||
* EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||
\***************************************************************************/
|
||||
|
||||
#pragma region Includes
|
||||
#include "ServiceBase.h"
|
||||
#include <assert.h>
|
||||
#include <strsafe.h>
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Static Members
|
||||
|
||||
// Initialize the singleton service instance.
|
||||
CServiceBase* CServiceBase::s_service = nullptr;
|
||||
|
||||
bool CServiceBase::Run(CServiceBase& service) {
|
||||
s_service = &service;
|
||||
|
||||
SERVICE_TABLE_ENTRY serviceTable[] = { { service.m_name, ServiceMain }, { nullptr, nullptr } };
|
||||
|
||||
// Connects the main thread of a service process to the service control
|
||||
// manager, which causes the thread to be the service control dispatcher
|
||||
// thread for the calling process. This call returns when the service has
|
||||
// stopped. The process should simply terminate when the call returns.
|
||||
return StartServiceCtrlDispatcher(serviceTable) == TRUE;
|
||||
}
|
||||
|
||||
void WINAPI CServiceBase::ServiceMain(DWORD argc, LPSTR* argv) {
|
||||
assert(s_service != nullptr);
|
||||
|
||||
// Register the handler function for the service
|
||||
s_service->m_statusHandle = RegisterServiceCtrlHandler(s_service->m_name, ServiceCtrlHandler);
|
||||
if (s_service->m_statusHandle == nullptr) {
|
||||
throw GetLastError();
|
||||
}
|
||||
|
||||
// Start the service.
|
||||
s_service->Start(argc, argv);
|
||||
}
|
||||
|
||||
// the control code can be one of the following values:
|
||||
//
|
||||
// SERVICE_CONTROL_CONTINUE
|
||||
// SERVICE_CONTROL_INTERROGATE
|
||||
// SERVICE_CONTROL_NETBINDADD
|
||||
// SERVICE_CONTROL_NETBINDDISABLE
|
||||
// SERVICE_CONTROL_NETBINDREMOVE
|
||||
// SERVICE_CONTROL_PARAMCHANGE
|
||||
// SERVICE_CONTROL_PAUSE
|
||||
// SERVICE_CONTROL_SHUTDOWN
|
||||
// SERVICE_CONTROL_STOP
|
||||
//
|
||||
// This parameter can also be a user-defined control code ranges from 128
|
||||
// to 255.
|
||||
//
|
||||
void WINAPI CServiceBase::ServiceCtrlHandler(DWORD dwCtrl) {
|
||||
switch (dwCtrl) {
|
||||
case SERVICE_CONTROL_STOP:
|
||||
s_service->Stop();
|
||||
break;
|
||||
case SERVICE_CONTROL_PAUSE:
|
||||
s_service->Pause();
|
||||
break;
|
||||
case SERVICE_CONTROL_CONTINUE:
|
||||
s_service->Continue();
|
||||
break;
|
||||
case SERVICE_CONTROL_SHUTDOWN:
|
||||
s_service->Shutdown();
|
||||
break;
|
||||
case SERVICE_CONTROL_INTERROGATE:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Service Constructor and Destructor
|
||||
|
||||
//
|
||||
// FUNCTION: CServiceBase::CServiceBase(PWSTR, BOOL, BOOL, BOOL)
|
||||
//
|
||||
// PURPOSE: The constructor of CServiceBase. It initializes a new instance
|
||||
// of the CServiceBase class. The optional parameters (fCanStop,
|
||||
/// fCanShutdown and fCanPauseContinue) allow you to specify whether the
|
||||
// service can be stopped, paused and continued, or be notified when system
|
||||
// shutdown occurs.
|
||||
//
|
||||
// PARAMETERS:
|
||||
// * pszServiceName - the name of the service
|
||||
// * fCanStop - the service can be stopped
|
||||
// * fCanShutdown - the service is notified when system shutdown occurs
|
||||
// * fCanPauseContinue - the service can be paused and continued
|
||||
//
|
||||
CServiceBase::CServiceBase(char* serviceName, bool fCanStop, bool fCanShutdown, bool fCanPauseContinue) {
|
||||
// Service name must be a valid string and cannot be nullptr.
|
||||
m_name = (serviceName == nullptr) ? const_cast<char*>("") : serviceName;
|
||||
|
||||
m_statusHandle = nullptr;
|
||||
|
||||
// The service runs in its own process.
|
||||
m_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
||||
|
||||
// The service is starting.
|
||||
m_status.dwCurrentState = SERVICE_START_PENDING;
|
||||
|
||||
// The accepted commands of the service.
|
||||
DWORD dwControlsAccepted = 0;
|
||||
if (fCanStop)
|
||||
dwControlsAccepted |= SERVICE_ACCEPT_STOP;
|
||||
if (fCanShutdown)
|
||||
dwControlsAccepted |= SERVICE_ACCEPT_SHUTDOWN;
|
||||
if (fCanPauseContinue)
|
||||
dwControlsAccepted |= SERVICE_ACCEPT_PAUSE_CONTINUE;
|
||||
m_status.dwControlsAccepted = dwControlsAccepted;
|
||||
|
||||
m_status.dwWin32ExitCode = NO_ERROR;
|
||||
m_status.dwServiceSpecificExitCode = 0;
|
||||
m_status.dwCheckPoint = 0;
|
||||
m_status.dwWaitHint = 0;
|
||||
}
|
||||
|
||||
//
|
||||
// FUNCTION: CServiceBase::~CServiceBase()
|
||||
//
|
||||
// PURPOSE: The virtual destructor of CServiceBase.
|
||||
//
|
||||
CServiceBase::~CServiceBase(void) {}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Service Start, Stop, Pause, Continue, and Shutdown
|
||||
|
||||
void CServiceBase::Start(DWORD dwArgc, LPSTR* lpszArgv) {
|
||||
try {
|
||||
// Tell SCM that the service is starting.
|
||||
SetServiceStatus(SERVICE_START_PENDING);
|
||||
|
||||
// Perform service-specific initialization.
|
||||
OnStart(dwArgc, lpszArgv);
|
||||
|
||||
// Tell SCM that the service is started.
|
||||
SetServiceStatus(SERVICE_RUNNING);
|
||||
} catch (DWORD dwError) {
|
||||
// Log the error.
|
||||
WriteErrorLogEntry("Service Start", dwError);
|
||||
|
||||
// Set the service status to be stopped.
|
||||
SetServiceStatus(SERVICE_STOPPED, dwError);
|
||||
} catch (...) {
|
||||
// Log the error.
|
||||
WriteEventLogEntry("Service failed to start.", EVENTLOG_ERROR_TYPE);
|
||||
|
||||
// Set the service status to be stopped.
|
||||
SetServiceStatus(SERVICE_STOPPED);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// FUNCTION: CServiceBase::Stop()
|
||||
//
|
||||
// PURPOSE: The function stops the service. It calls the OnStop virtual
|
||||
// function in which you can specify the actions to take when the service
|
||||
// stops. If an error occurs, the error will be logged in the Application
|
||||
// event log, and the service will be restored to the original state.
|
||||
//
|
||||
void CServiceBase::Stop() {
|
||||
DWORD dwOriginalState = m_status.dwCurrentState;
|
||||
try {
|
||||
// Tell SCM that the service is stopping.
|
||||
SetServiceStatus(SERVICE_STOP_PENDING);
|
||||
|
||||
// Perform service-specific stop operations.
|
||||
OnStop();
|
||||
|
||||
// Tell SCM that the service is stopped.
|
||||
SetServiceStatus(SERVICE_STOPPED);
|
||||
} catch (DWORD dwError) {
|
||||
// Log the error.
|
||||
WriteErrorLogEntry("Service Stop", dwError);
|
||||
|
||||
// Set the original service status.
|
||||
SetServiceStatus(dwOriginalState);
|
||||
} catch (...) {
|
||||
// Log the error.
|
||||
WriteEventLogEntry("Service failed to stop.", EVENTLOG_ERROR_TYPE);
|
||||
|
||||
// Set the original service status.
|
||||
SetServiceStatus(dwOriginalState);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// FUNCTION: CServiceBase::Pause()
|
||||
//
|
||||
// PURPOSE: The function pauses the service if the service supports pause
|
||||
// and continue. It calls the OnPause virtual function in which you can
|
||||
// specify the actions to take when the service pauses. If an error occurs,
|
||||
// the error will be logged in the Application event log, and the service
|
||||
// will become running.
|
||||
//
|
||||
void CServiceBase::Pause() {
|
||||
try {
|
||||
// Tell SCM that the service is pausing.
|
||||
SetServiceStatus(SERVICE_PAUSE_PENDING);
|
||||
|
||||
// Perform service-specific pause operations.
|
||||
OnPause();
|
||||
|
||||
// Tell SCM that the service is paused.
|
||||
SetServiceStatus(SERVICE_PAUSED);
|
||||
} catch (DWORD dwError) {
|
||||
// Log the error.
|
||||
WriteErrorLogEntry("Service Pause", dwError);
|
||||
|
||||
// Tell SCM that the service is still running.
|
||||
SetServiceStatus(SERVICE_RUNNING);
|
||||
} catch (...) {
|
||||
// Log the error.
|
||||
WriteEventLogEntry("Service failed to pause.", EVENTLOG_ERROR_TYPE);
|
||||
|
||||
// Tell SCM that the service is still running.
|
||||
SetServiceStatus(SERVICE_RUNNING);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// FUNCTION: CServiceBase::Continue()
|
||||
//
|
||||
// PURPOSE: The function resumes normal functioning after being paused if
|
||||
// the service supports pause and continue. It calls the OnContinue virtual
|
||||
// function in which you can specify the actions to take when the service
|
||||
// continues. If an error occurs, the error will be logged in the
|
||||
// Application event log, and the service will still be paused.
|
||||
//
|
||||
void CServiceBase::Continue() {
|
||||
try {
|
||||
// Tell SCM that the service is resuming.
|
||||
SetServiceStatus(SERVICE_CONTINUE_PENDING);
|
||||
|
||||
// Perform service-specific continue operations.
|
||||
OnContinue();
|
||||
|
||||
// Tell SCM that the service is running.
|
||||
SetServiceStatus(SERVICE_RUNNING);
|
||||
} catch (DWORD dwError) {
|
||||
// Log the error.
|
||||
WriteErrorLogEntry("Service Continue", dwError);
|
||||
|
||||
// Tell SCM that the service is still paused.
|
||||
SetServiceStatus(SERVICE_PAUSED);
|
||||
} catch (...) {
|
||||
// Log the error.
|
||||
WriteEventLogEntry("Service failed to resume.", EVENTLOG_ERROR_TYPE);
|
||||
|
||||
// Tell SCM that the service is still paused.
|
||||
SetServiceStatus(SERVICE_PAUSED);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// FUNCTION: CServiceBase::Shutdown()
|
||||
//
|
||||
// PURPOSE: The function executes when the system is shutting down. It
|
||||
// calls the OnShutdown virtual function in which you can specify what
|
||||
// should occur immediately prior to the system shutting down. If an error
|
||||
// occurs, the error will be logged in the Application event log.
|
||||
//
|
||||
void CServiceBase::Shutdown() {
|
||||
try {
|
||||
// Perform service-specific shutdown operations.
|
||||
OnShutdown();
|
||||
|
||||
// Tell SCM that the service is stopped.
|
||||
SetServiceStatus(SERVICE_STOPPED);
|
||||
} catch (DWORD dwError) {
|
||||
// Log the error.
|
||||
WriteErrorLogEntry("Service Shutdown", dwError);
|
||||
} catch (...) {
|
||||
// Log the error.
|
||||
WriteEventLogEntry("Service failed to shut down.", EVENTLOG_ERROR_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma region Helper Functions
|
||||
|
||||
// * dwCurrentState - the state of the service
|
||||
// * dwWin32ExitCode - error code to report
|
||||
// * dwWaitHint - estimated time for pending operation, in milliseconds
|
||||
void CServiceBase::SetServiceStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint) {
|
||||
static DWORD dwCheckPoint = 1;
|
||||
|
||||
// Fill in the SERVICE_STATUS structure of the service.
|
||||
|
||||
m_status.dwCurrentState = dwCurrentState;
|
||||
m_status.dwWin32ExitCode = dwWin32ExitCode;
|
||||
m_status.dwWaitHint = dwWaitHint;
|
||||
|
||||
m_status.dwCheckPoint =
|
||||
((dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED)) ? 0 : dwCheckPoint++;
|
||||
|
||||
// Report the status of the service to the SCM.
|
||||
::SetServiceStatus(m_statusHandle, &m_status);
|
||||
}
|
||||
|
||||
// the type of event to be logged can be one of the following values:
|
||||
//
|
||||
// EVENTLOG_SUCCESS
|
||||
// EVENTLOG_AUDIT_FAILURE
|
||||
// EVENTLOG_AUDIT_SUCCESS
|
||||
// EVENTLOG_ERROR_TYPE
|
||||
// EVENTLOG_INFORMATION_TYPE
|
||||
// EVENTLOG_WARNING_TYPE
|
||||
//
|
||||
void CServiceBase::WriteEventLogEntry(const char* message, int wType) {
|
||||
HANDLE hEventSource = nullptr;
|
||||
LPCSTR lpszStrings[2] = { nullptr, nullptr };
|
||||
|
||||
hEventSource = RegisterEventSource(nullptr, m_name);
|
||||
if (hEventSource) {
|
||||
lpszStrings[0] = m_name;
|
||||
lpszStrings[1] = message;
|
||||
|
||||
ReportEvent(hEventSource, // Event log handle
|
||||
wType, // Event type
|
||||
0, // Event category
|
||||
0, // Event identifier
|
||||
nullptr, // No security identifier
|
||||
2, // Size of lpszStrings array
|
||||
0, // No binary data
|
||||
lpszStrings, // Array of strings
|
||||
nullptr // No binary data
|
||||
);
|
||||
|
||||
DeregisterEventSource(hEventSource);
|
||||
}
|
||||
}
|
||||
|
||||
void CServiceBase::WriteErrorLogEntry(const char* function, int error) {
|
||||
char message[260];
|
||||
StringCchPrintf(message, ARRAYSIZE(message), "%s failed w/err 0x%08lx", function, error);
|
||||
WriteEventLogEntry(message, EVENTLOG_ERROR_TYPE);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
@@ -1,112 +0,0 @@
|
||||
/****************************** Module Header ******************************\
|
||||
* Module Name: ServiceBase.h
|
||||
* Project: CppWindowsService
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Provides a base class for a service that will exist as part of a service
|
||||
* application. CServiceBase must be derived from when creating a new service
|
||||
* class.
|
||||
*
|
||||
* This source is subject to the Microsoft Public License.
|
||||
* See http://www.microsoft.com/en-us/openness/resources/licenses.aspx#MPL.
|
||||
* All other rights reserved.
|
||||
*
|
||||
* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||
* EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||
\***************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
|
||||
class CServiceBase {
|
||||
public:
|
||||
// Register the executable for a service with the Service Control Manager
|
||||
// (SCM). After you call Run(ServiceBase), the SCM issues a Start command,
|
||||
// which results in a call to the OnStart method in the service. This
|
||||
// method blocks until the service has stopped.
|
||||
static bool Run(CServiceBase& service);
|
||||
|
||||
// Service object constructor. The optional parameters (fCanStop,
|
||||
// fCanShutdown and fCanPauseContinue) allow you to specify whether the
|
||||
// service can be stopped, paused and continued, or be notified when
|
||||
// system shutdown occurs.
|
||||
CServiceBase(char* pszServiceName, bool fCanStop = true, bool fCanShutdown = true, bool fCanPauseContinue = false);
|
||||
|
||||
// Service object destructor.
|
||||
virtual ~CServiceBase(void);
|
||||
|
||||
// Stop the service.
|
||||
void Stop();
|
||||
|
||||
protected:
|
||||
// When implemented in a derived class, executes when a Start command is
|
||||
// sent to the service by the SCM or when the operating system starts
|
||||
// (for a service that starts automatically). Specifies actions to take
|
||||
// when the service starts.
|
||||
virtual void OnStart(DWORD dwArgc, LPSTR* lpszArgv) {}
|
||||
|
||||
// When implemented in a derived class, executes when a Stop command is
|
||||
// sent to the service by the SCM. Specifies actions to take when a
|
||||
// service stops running.
|
||||
virtual void OnStop() {}
|
||||
|
||||
// When implemented in a derived class, executes when a Pause command is
|
||||
// sent to the service by the SCM. Specifies actions to take when a
|
||||
// service pauses.
|
||||
virtual void OnPause() {}
|
||||
|
||||
// When implemented in a derived class, OnContinue runs when a Continue
|
||||
// command is sent to the service by the SCM. Specifies actions to take
|
||||
// when a service resumes normal functioning after being paused.
|
||||
virtual void OnContinue() {}
|
||||
|
||||
// When implemented in a derived class, executes when the system is
|
||||
// shutting down. Specifies what should occur immediately prior to the
|
||||
// system shutting down.
|
||||
virtual void OnShutdown() {}
|
||||
|
||||
// Set the service status and report the status to the SCM.
|
||||
void SetServiceStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode = NO_ERROR, DWORD dwWaitHint = 0);
|
||||
|
||||
// Log a message to the Application event log.
|
||||
void WriteEventLogEntry(const char* message, int wType);
|
||||
|
||||
// Log an error message to the Application event log.
|
||||
void WriteErrorLogEntry(const char* message, int dwError = GetLastError());
|
||||
|
||||
private:
|
||||
// Entry point for the service. It registers the handler function for the
|
||||
// service and starts the service.
|
||||
static void WINAPI ServiceMain(DWORD argc, LPSTR* argv);
|
||||
|
||||
// The function is called by the SCM whenever a control code is sent to
|
||||
// the service.
|
||||
static void WINAPI ServiceCtrlHandler(DWORD code);
|
||||
|
||||
// Start the service.
|
||||
void Start(DWORD argc, LPSTR* argv);
|
||||
|
||||
// Pause the service.
|
||||
void Pause();
|
||||
|
||||
// Resume the service after being paused.
|
||||
void Continue();
|
||||
|
||||
// Execute when the system is shutting down.
|
||||
void Shutdown();
|
||||
|
||||
// The singleton service instance.
|
||||
static CServiceBase* s_service;
|
||||
|
||||
// The name of the service
|
||||
char* m_name;
|
||||
|
||||
// The status of the service
|
||||
SERVICE_STATUS m_status;
|
||||
|
||||
// The service status handle
|
||||
SERVICE_STATUS_HANDLE m_statusHandle;
|
||||
};
|
||||
@@ -1,70 +0,0 @@
|
||||
/****************************** Module Header ******************************\
|
||||
* Module Name: ThreadPool.h
|
||||
* Project: CppWindowsService
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* The class was designed by Kenny Kerr. It provides the ability to queue
|
||||
* simple member functions of a class to the Windows thread pool.
|
||||
*
|
||||
* Using the thread pool is simple and feels natural in C++.
|
||||
*
|
||||
* class FDBService
|
||||
* {
|
||||
* public:
|
||||
*
|
||||
* void AsyncRun()
|
||||
* {
|
||||
* CThreadPool::QueueUserWorkItem(&Service::Run, this);
|
||||
* }
|
||||
*
|
||||
* void Run()
|
||||
* {
|
||||
* // Some lengthy operation
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* Kenny Kerr spends most of his time designing and building distributed
|
||||
* applications for the Microsoft Windows platform. He also has a particular
|
||||
* passion for C++ and security programming. Reach Kenny at
|
||||
* http://weblogs.asp.net/kennykerr/ or visit his Web site:
|
||||
* http://www.kennyandkarin.com/Kenny/.
|
||||
*
|
||||
* This source is subject to the Microsoft Public License.
|
||||
* See http://www.microsoft.com/en-us/openness/resources/licenses.aspx#MPL.
|
||||
* All other rights reserved.
|
||||
*
|
||||
* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||
* EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||
\***************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
class CThreadPool {
|
||||
public:
|
||||
template <typename T>
|
||||
static void QueueUserWorkItem(void (T::*function)(void), T* object, ULONG flags = WT_EXECUTELONGFUNCTION) {
|
||||
typedef std::pair<void (T::*)(), T*> CallbackType;
|
||||
auto p = std::make_unique<CallbackType>(function, object);
|
||||
|
||||
if (::QueueUserWorkItem(ThreadProc<T>, p.get(), flags)) {
|
||||
// The ThreadProc now has the responsibility of deleting the pair.
|
||||
p.release();
|
||||
} else {
|
||||
throw GetLastError();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
static DWORD WINAPI ThreadProc(PVOID context) {
|
||||
typedef std::pair<void (T::*)(), T*> CallbackType;
|
||||
|
||||
std::unique_ptr<CallbackType> p(static_cast<CallbackType*>(context));
|
||||
|
||||
(p->second->*p->first)();
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user