1002 lines
23 KiB
C++
1002 lines
23 KiB
C++
/*
|
|
* synergy -- mouse and keyboard sharing utility
|
|
* Copyright (C) 2002 Chris Schoeneman
|
|
*
|
|
* This package is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* found in the file COPYING that should have accompanied this file.
|
|
*
|
|
* This package is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "CWin32Platform.h"
|
|
#include "CLock.h"
|
|
#include "CThread.h"
|
|
#include "CLog.h"
|
|
#include "TMethodJob.h"
|
|
#include "stdvector.h"
|
|
#include <cstring>
|
|
#include <shlobj.h>
|
|
#include <tchar.h>
|
|
|
|
//
|
|
// CWin32Platform
|
|
//
|
|
|
|
HANDLE CWin32Platform::s_eventLog = NULL;
|
|
CWin32Platform* CWin32Platform::s_daemonPlatform = NULL;
|
|
|
|
CWin32Platform::CWin32Platform()
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
CWin32Platform::~CWin32Platform()
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
bool
|
|
CWin32Platform::isWindows95Family()
|
|
{
|
|
OSVERSIONINFO version;
|
|
version.dwOSVersionInfoSize = sizeof(version);
|
|
if (GetVersionEx(&version) == 0) {
|
|
LOG((CLOG_WARN "cannot determine OS: %d", GetLastError()));
|
|
return true;
|
|
}
|
|
return (version.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS);
|
|
}
|
|
|
|
void
|
|
CWin32Platform::setStatus(SERVICE_STATUS_HANDLE handle, DWORD state)
|
|
{
|
|
setStatus(handle, state, 0, 0);
|
|
}
|
|
|
|
void
|
|
CWin32Platform::setStatus(SERVICE_STATUS_HANDLE handle,
|
|
DWORD state, DWORD step, DWORD waitHint)
|
|
{
|
|
SERVICE_STATUS status;
|
|
status.dwServiceType = SERVICE_WIN32_OWN_PROCESS |
|
|
SERVICE_INTERACTIVE_PROCESS;
|
|
status.dwCurrentState = state;
|
|
status.dwControlsAccepted = SERVICE_ACCEPT_STOP |
|
|
SERVICE_ACCEPT_PAUSE_CONTINUE |
|
|
SERVICE_ACCEPT_SHUTDOWN;
|
|
status.dwWin32ExitCode = NO_ERROR;
|
|
status.dwServiceSpecificExitCode = 0;
|
|
status.dwCheckPoint = step;
|
|
status.dwWaitHint = waitHint;
|
|
SetServiceStatus(handle, &status);
|
|
}
|
|
|
|
void
|
|
CWin32Platform::setStatusError(SERVICE_STATUS_HANDLE handle, DWORD error)
|
|
{
|
|
SERVICE_STATUS status;
|
|
status.dwServiceType = SERVICE_WIN32_OWN_PROCESS |
|
|
SERVICE_INTERACTIVE_PROCESS;
|
|
status.dwCurrentState = SERVICE_STOPPED;
|
|
status.dwControlsAccepted = SERVICE_ACCEPT_STOP |
|
|
SERVICE_ACCEPT_PAUSE_CONTINUE |
|
|
SERVICE_ACCEPT_SHUTDOWN;
|
|
status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
|
|
status.dwServiceSpecificExitCode = error;
|
|
status.dwCheckPoint = 0;
|
|
status.dwWaitHint = 0;
|
|
SetServiceStatus(handle, &status);
|
|
}
|
|
|
|
bool
|
|
CWin32Platform::installDaemon(const char* name, const char* description,
|
|
const char* pathname, const char* commandLine, bool allUsers)
|
|
{
|
|
// if not for all users then use the user's autostart registry.
|
|
// key. if windows 95 family then use windows 95 services key.
|
|
if (!allUsers || isWindows95Family()) {
|
|
// open registry
|
|
HKEY key = isWindows95Family() ? open95ServicesKey() :
|
|
openUserStartupKey();
|
|
if (key == NULL) {
|
|
LOG((CLOG_ERR "cannot open registry key", GetLastError()));
|
|
return false;
|
|
}
|
|
|
|
// construct entry
|
|
CString value;
|
|
value += "\"";
|
|
value += pathname;
|
|
value += "\" ";
|
|
value += commandLine;
|
|
|
|
// install entry
|
|
setValue(key, name, value);
|
|
|
|
// clean up
|
|
closeKey(key);
|
|
|
|
return true;
|
|
}
|
|
|
|
// windows NT family services
|
|
else {
|
|
// open service manager
|
|
SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE);
|
|
if (mgr == NULL) {
|
|
LOG((CLOG_ERR "OpenSCManager failed with %d", GetLastError()));
|
|
return false;
|
|
}
|
|
|
|
// create the servie
|
|
SC_HANDLE service = CreateService(mgr,
|
|
name,
|
|
name,
|
|
0,
|
|
SERVICE_WIN32_OWN_PROCESS |
|
|
SERVICE_INTERACTIVE_PROCESS,
|
|
SERVICE_AUTO_START,
|
|
SERVICE_ERROR_NORMAL,
|
|
pathname,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
// done with service and manager
|
|
if (service != NULL) {
|
|
CloseServiceHandle(service);
|
|
CloseServiceHandle(mgr);
|
|
}
|
|
else {
|
|
// FIXME -- handle ERROR_SERVICE_EXISTS
|
|
|
|
LOG((CLOG_ERR "CreateService failed with %d", GetLastError()));
|
|
CloseServiceHandle(mgr);
|
|
return false;
|
|
}
|
|
|
|
// open the registry key for this service
|
|
HKEY key = openNTServicesKey();
|
|
key = openKey(key, name);
|
|
if (key == NULL) {
|
|
// can't open key
|
|
uninstallDaemon(name, allUsers);
|
|
return false;
|
|
}
|
|
|
|
// set the description
|
|
setValue(key, "Description", description);
|
|
|
|
// set command line
|
|
key = openKey(key, "Parameters");
|
|
if (key == NULL) {
|
|
// can't open key
|
|
uninstallDaemon(name, allUsers);
|
|
return false;
|
|
}
|
|
setValue(key, "CommandLine", commandLine);
|
|
|
|
// done with registry
|
|
closeKey(key);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
IPlatform::EResult
|
|
CWin32Platform::uninstallDaemon(const char* name, bool allUsers)
|
|
{
|
|
// if not for all users then use the user's autostart registry.
|
|
// key. if windows 95 family then use windows 95 services key.
|
|
if (!allUsers || isWindows95Family()) {
|
|
// open registry
|
|
HKEY key = isWindows95Family() ? open95ServicesKey() :
|
|
openUserStartupKey();
|
|
if (key == NULL) {
|
|
LOG((CLOG_ERR "cannot open registry key", GetLastError()));
|
|
return kAlready;
|
|
}
|
|
|
|
// remove entry
|
|
deleteValue(key, name);
|
|
|
|
// clean up
|
|
closeKey(key);
|
|
|
|
return kSuccess;
|
|
}
|
|
|
|
// windows NT family services
|
|
else {
|
|
// remove parameters for this service
|
|
HKEY key = openNTServicesKey();
|
|
key = openKey(key, name);
|
|
if (key != NULL) {
|
|
deleteKey(key, "Parameters");
|
|
closeKey(key);
|
|
}
|
|
|
|
// open service manager
|
|
SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE);
|
|
if (mgr == NULL) {
|
|
LOG((CLOG_ERR "OpenSCManager failed with %d", GetLastError()));
|
|
return kFailed;
|
|
}
|
|
|
|
// open the service. oddly, you must open a service to delete it.
|
|
EResult result;
|
|
SC_HANDLE service = OpenService(mgr, name, DELETE);
|
|
if (service == NULL) {
|
|
const DWORD e = GetLastError();
|
|
LOG((CLOG_ERR "OpenService failed with %d", e));
|
|
result = (e == ERROR_SERVICE_DOES_NOT_EXIST) ? kAlready : kFailed;
|
|
}
|
|
|
|
else {
|
|
if (DeleteService(service) != 0) {
|
|
result = kSuccess;
|
|
}
|
|
else {
|
|
const DWORD e = GetLastError();
|
|
switch (e) {
|
|
case ERROR_SERVICE_MARKED_FOR_DELETE:
|
|
result = kAlready;
|
|
break;
|
|
|
|
default:
|
|
result = kFailed;
|
|
break;
|
|
}
|
|
}
|
|
CloseServiceHandle(service);
|
|
}
|
|
|
|
// close the manager
|
|
CloseServiceHandle(mgr);
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
int
|
|
CWin32Platform::daemonize(const char* name, DaemonFunc func)
|
|
{
|
|
assert(name != NULL);
|
|
assert(func != NULL);
|
|
|
|
// windows 95 family services
|
|
if (isWindows95Family()) {
|
|
typedef DWORD (WINAPI *RegisterServiceProcessT)(DWORD, DWORD);
|
|
|
|
// mark this process as a service so it's not killed when the
|
|
// user logs off.
|
|
HINSTANCE kernel = LoadLibrary("kernel32.dll");
|
|
if (kernel == NULL) {
|
|
LOG((CLOG_ERR "LoadLibrary failed with %d", GetLastError()));
|
|
return -1;
|
|
}
|
|
RegisterServiceProcessT RegisterServiceProcess =
|
|
reinterpret_cast<RegisterServiceProcessT>(
|
|
GetProcAddress(kernel,
|
|
_T("RegisterServiceProcess")));
|
|
if (RegisterServiceProcess == NULL) {
|
|
LOG((CLOG_ERR "can't lookup RegisterServiceProcess: %d", GetLastError()));
|
|
FreeLibrary(kernel);
|
|
return -1;
|
|
}
|
|
if (RegisterServiceProcess(NULL, 1) == 0) {
|
|
LOG((CLOG_ERR "RegisterServiceProcess failed with %d", GetLastError()));
|
|
FreeLibrary(kernel);
|
|
return -1;
|
|
}
|
|
FreeLibrary(kernel);
|
|
|
|
// now simply call the daemon function
|
|
return func(this, 1, &name);
|
|
}
|
|
|
|
// windows NT family services
|
|
else {
|
|
// save daemon function
|
|
m_daemonFunc = func;
|
|
|
|
// construct the service entry
|
|
SERVICE_TABLE_ENTRY entry[2];
|
|
entry[0].lpServiceName = const_cast<char*>(name);
|
|
entry[0].lpServiceProc = &CWin32Platform::serviceMainEntry;
|
|
entry[1].lpServiceName = NULL;
|
|
entry[1].lpServiceProc = NULL;
|
|
|
|
// hook us up to the service control manager. this won't return
|
|
// (if successful) until the processes have terminated.
|
|
s_daemonPlatform = this;
|
|
if (StartServiceCtrlDispatcher(entry)) {
|
|
s_daemonPlatform = NULL;
|
|
return m_daemonResult;
|
|
}
|
|
LOG((CLOG_ERR "StartServiceCtrlDispatcher failed with %d", GetLastError()));
|
|
s_daemonPlatform = NULL;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
void
|
|
CWin32Platform::installDaemonLogger(const char* name)
|
|
{
|
|
if (!CWin32Platform::isWindows95Family()) {
|
|
// open event log and direct log messages to it
|
|
if (s_eventLog == NULL) {
|
|
s_eventLog = RegisterEventSource(NULL, name);
|
|
if (s_eventLog != NULL) {
|
|
CLog::setOutputter(&CWin32Platform::serviceLogger);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
CWin32Platform::canInstallDaemon(const char* name, bool allUsers) const
|
|
{
|
|
// if not for all users then use the user's autostart registry.
|
|
// key. if windows 95 family then use windows 95 services key.
|
|
if (!allUsers || isWindows95Family()) {
|
|
// check if we can open the registry key
|
|
HKEY key = isWindows95Family() ? open95ServicesKey() :
|
|
openUserStartupKey();
|
|
closeKey(key);
|
|
return (key != NULL);
|
|
}
|
|
|
|
// windows NT family services
|
|
else {
|
|
// check if we can open service manager for write
|
|
SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE);
|
|
if (mgr == NULL) {
|
|
return false;
|
|
}
|
|
CloseServiceHandle(mgr);
|
|
|
|
// check if we can open the registry key for this service
|
|
HKEY key = openNTServicesKey();
|
|
key = openKey(key, name);
|
|
key = openKey(key, "Parameters");
|
|
closeKey(key);
|
|
|
|
return (key != NULL);
|
|
}
|
|
}
|
|
|
|
bool
|
|
CWin32Platform::isDaemonInstalled(const char* name, bool allUsers) const
|
|
{
|
|
// if not for all users then use the user's autostart registry.
|
|
// key. if windows 95 family then use windows 95 services key.
|
|
if (!allUsers || isWindows95Family()) {
|
|
// check if we can open the registry key
|
|
HKEY key = isWindows95Family() ? open95ServicesKey() :
|
|
openUserStartupKey();
|
|
if (key == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// check for entry
|
|
const bool installed = !readValueString(key, name).empty();
|
|
|
|
// clean up
|
|
closeKey(key);
|
|
|
|
return installed;
|
|
}
|
|
|
|
// windows NT family services
|
|
else {
|
|
// check parameters for this service
|
|
HKEY key = openNTServicesKey();
|
|
key = openKey(key, name);
|
|
key = openKey(key, "Parameters");
|
|
if (key != NULL) {
|
|
const bool installed = !readValueString(key, "CommandLine").empty();
|
|
closeKey(key);
|
|
if (!installed) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// open service manager
|
|
SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_READ);
|
|
if (mgr == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// open the service
|
|
SC_HANDLE service = OpenService(mgr, name, GENERIC_READ);
|
|
|
|
// clean up
|
|
CloseServiceHandle(service);
|
|
CloseServiceHandle(mgr);
|
|
|
|
return (service != NULL);
|
|
}
|
|
}
|
|
|
|
const char*
|
|
CWin32Platform::getBasename(const char* pathname) const
|
|
{
|
|
if (pathname == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
// check for last /
|
|
const char* basename = strrchr(pathname, '/');
|
|
if (basename != NULL) {
|
|
++basename;
|
|
}
|
|
else {
|
|
basename = pathname;
|
|
}
|
|
|
|
// check for last backslash
|
|
const char* basename2 = strrchr(pathname, '\\');
|
|
if (basename2 != NULL && basename2 > basename) {
|
|
basename = basename2 + 1;
|
|
}
|
|
|
|
return basename;
|
|
}
|
|
|
|
CString
|
|
CWin32Platform::getUserDirectory() const
|
|
{
|
|
// try %HOMEPATH%
|
|
TCHAR dir[MAX_PATH];
|
|
DWORD size = sizeof(dir) / sizeof(TCHAR);
|
|
DWORD result = GetEnvironmentVariable(_T("HOMEPATH"), dir, size);
|
|
if (result != 0 && result <= size) {
|
|
// sanity check -- if dir doesn't appear to start with a
|
|
// drive letter and isn't a UNC name then don't use it
|
|
// FIXME -- allow UNC names
|
|
if (dir[0] != '\0' && (dir[1] == ':' ||
|
|
((dir[0] == '\\' || dir[0] == '/') &&
|
|
(dir[1] == '\\' || dir[1] == '/')))) {
|
|
return dir;
|
|
}
|
|
}
|
|
|
|
// get the location of the personal files. that's as close to
|
|
// a home directory as we're likely to find.
|
|
ITEMIDLIST* idl;
|
|
if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_PERSONAL, &idl))) {
|
|
TCHAR* path = NULL;
|
|
if (SHGetPathFromIDList(idl, dir)) {
|
|
DWORD attr = GetFileAttributes(dir);
|
|
if (attr != 0xffffffff && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
|
|
path = dir;
|
|
}
|
|
|
|
IMalloc* shalloc;
|
|
if (SUCCEEDED(SHGetMalloc(&shalloc))) {
|
|
shalloc->Free(idl);
|
|
shalloc->Release();
|
|
}
|
|
|
|
if (path != NULL) {
|
|
return path;
|
|
}
|
|
}
|
|
|
|
// use root of C drive as a default
|
|
return "C:";
|
|
}
|
|
|
|
CString
|
|
CWin32Platform::getSystemDirectory() const
|
|
{
|
|
// get windows directory
|
|
char dir[MAX_PATH];
|
|
if (GetWindowsDirectory(dir, sizeof(dir)) != 0) {
|
|
return dir;
|
|
}
|
|
else {
|
|
// can't get it. use C:\ as a default.
|
|
return "C:";
|
|
}
|
|
}
|
|
|
|
CString
|
|
CWin32Platform::addPathComponent(const CString& prefix,
|
|
const CString& suffix) const
|
|
{
|
|
CString path;
|
|
path.reserve(prefix.size() + 1 + suffix.size());
|
|
path += prefix;
|
|
if (path.size() == 0 ||
|
|
(path[path.size() - 1] != '\\' &&
|
|
path[path.size() - 1] != '/')) {
|
|
path += '\\';
|
|
}
|
|
path += suffix;
|
|
return path;
|
|
}
|
|
|
|
HKEY
|
|
CWin32Platform::openKey(HKEY key, const char* keyName)
|
|
{
|
|
// ignore if parent is NULL
|
|
if (key == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
// open next key
|
|
HKEY newKey;
|
|
LONG result = RegOpenKeyEx(key, keyName, 0,
|
|
KEY_WRITE | KEY_QUERY_VALUE, &newKey);
|
|
if (result != ERROR_SUCCESS) {
|
|
DWORD disp;
|
|
result = RegCreateKeyEx(key, keyName, 0, _T(""),
|
|
0, KEY_WRITE | KEY_QUERY_VALUE,
|
|
NULL, &newKey, &disp);
|
|
}
|
|
if (result != ERROR_SUCCESS) {
|
|
RegCloseKey(key);
|
|
return NULL;
|
|
}
|
|
|
|
// switch to new key
|
|
RegCloseKey(key);
|
|
return newKey;
|
|
}
|
|
|
|
HKEY
|
|
CWin32Platform::openKey(HKEY key, const char** keyNames)
|
|
{
|
|
for (UInt32 i = 0; key != NULL && keyNames[i] != NULL; ++i) {
|
|
// open next key
|
|
key = openKey(key, keyNames[i]);
|
|
}
|
|
return key;
|
|
}
|
|
|
|
void
|
|
CWin32Platform::closeKey(HKEY key)
|
|
{
|
|
assert(key != NULL);
|
|
RegCloseKey(key);
|
|
}
|
|
|
|
void
|
|
CWin32Platform::deleteKey(HKEY key, const char* name)
|
|
{
|
|
assert(key != NULL);
|
|
assert(name != NULL);
|
|
RegDeleteKey(key, name);
|
|
}
|
|
|
|
void
|
|
CWin32Platform::deleteValue(HKEY key, const char* name)
|
|
{
|
|
assert(key != NULL);
|
|
assert(name != NULL);
|
|
RegDeleteValue(key, name);
|
|
}
|
|
|
|
void
|
|
CWin32Platform::setValue(HKEY key, const char* name, const CString& value)
|
|
{
|
|
assert(key != NULL);
|
|
assert(name != NULL);
|
|
RegSetValueEx(key, name, 0, REG_SZ,
|
|
reinterpret_cast<const BYTE*>(value.c_str()),
|
|
value.size() + 1);
|
|
}
|
|
|
|
CString
|
|
CWin32Platform::readValueString(HKEY key, const char* name)
|
|
{
|
|
// get the size of the string
|
|
DWORD type;
|
|
DWORD size = 0;
|
|
LONG result = RegQueryValueEx(key, name, 0, &type, NULL, &size);
|
|
if (result != ERROR_SUCCESS || type != REG_SZ) {
|
|
return CString();
|
|
}
|
|
|
|
// allocate space
|
|
char* buffer = new char[size];
|
|
|
|
// read it
|
|
result = RegQueryValueEx(key, name, 0, &type,
|
|
reinterpret_cast<BYTE*>(buffer), &size);
|
|
if (result != ERROR_SUCCESS || type != REG_SZ) {
|
|
delete[] buffer;
|
|
return CString();
|
|
}
|
|
|
|
// clean up and return value
|
|
CString value(buffer);
|
|
delete[] buffer;
|
|
return value;
|
|
}
|
|
|
|
HKEY
|
|
CWin32Platform::openNTServicesKey()
|
|
{
|
|
static const char* s_keyNames[] = {
|
|
_T("SYSTEM"),
|
|
_T("CurrentControlSet"),
|
|
_T("Services"),
|
|
NULL
|
|
};
|
|
|
|
return openKey(HKEY_LOCAL_MACHINE, s_keyNames);
|
|
}
|
|
|
|
HKEY
|
|
CWin32Platform::open95ServicesKey()
|
|
{
|
|
static const char* s_keyNames[] = {
|
|
_T("Software"),
|
|
_T("Microsoft"),
|
|
_T("Windows"),
|
|
_T("CurrentVersion"),
|
|
_T("RunServices"),
|
|
NULL
|
|
};
|
|
|
|
return openKey(HKEY_LOCAL_MACHINE, s_keyNames);
|
|
}
|
|
|
|
HKEY
|
|
CWin32Platform::openUserStartupKey()
|
|
{
|
|
static const char* s_keyNames[] = {
|
|
_T("Software"),
|
|
_T("Microsoft"),
|
|
_T("Windows"),
|
|
_T("CurrentVersion"),
|
|
_T("Run"),
|
|
NULL
|
|
};
|
|
|
|
return openKey(HKEY_CURRENT_USER, s_keyNames);
|
|
}
|
|
|
|
int
|
|
CWin32Platform::runDaemon(RunFunc run, StopFunc stop)
|
|
{
|
|
// should only be called from DaemonFunc
|
|
assert(m_serviceMutex != NULL);
|
|
|
|
CLock lock(m_serviceMutex);
|
|
try {
|
|
int result;
|
|
m_stop = stop;
|
|
m_serviceHandlerWaiting = false;
|
|
m_serviceRunning = false;
|
|
for (;;) {
|
|
// mark server as running
|
|
setStatus(m_statusHandle, SERVICE_RUNNING);
|
|
|
|
// run callback in another thread
|
|
m_serviceRunning = true;
|
|
{
|
|
CThread thread(new TMethodJob<CWin32Platform>(this,
|
|
&CWin32Platform::runDaemonThread, run));
|
|
result = reinterpret_cast<int>(thread.getResult());
|
|
}
|
|
m_serviceRunning = false;
|
|
|
|
// notify handler that the server stopped. if handler
|
|
// isn't waiting then we stopped unexpectedly and we
|
|
// quit.
|
|
if (m_serviceHandlerWaiting) {
|
|
m_serviceHandlerWaiting = false;
|
|
m_serviceState->broadcast();
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
|
|
// wait until we're told what to do next
|
|
while (*m_serviceState != SERVICE_RUNNING &&
|
|
*m_serviceState != SERVICE_STOPPED) {
|
|
m_serviceState->wait();
|
|
}
|
|
|
|
// exit loop if we've been told to stop
|
|
if (*m_serviceState == SERVICE_STOPPED) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// prevent daemonHandler from changing state
|
|
*m_serviceState = SERVICE_STOPPED;
|
|
|
|
// tell service control that the service is stopped.
|
|
// FIXME -- hopefully this will ensure that our handler won't
|
|
// be called again but i can't find documentation that
|
|
// verifies that. if it does it'll crash on the mutex that
|
|
// we're about to destroy.
|
|
setStatus(m_statusHandle, *m_serviceState);
|
|
|
|
// clean up
|
|
m_stop = NULL;
|
|
|
|
return result;
|
|
}
|
|
catch (...) {
|
|
// FIXME -- report error
|
|
|
|
// prevent serviceHandler from changing state
|
|
*m_serviceState = SERVICE_STOPPED;
|
|
|
|
// set status
|
|
setStatusError(m_statusHandle, 0);
|
|
|
|
// wake up serviceHandler if it's waiting then wait for it
|
|
if (m_serviceHandlerWaiting) {
|
|
m_serviceHandlerWaiting = false;
|
|
m_serviceState->broadcast();
|
|
m_serviceState->wait();
|
|
// serviceHandler has exited by now
|
|
}
|
|
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void
|
|
CWin32Platform::runDaemonThread(void* vrun)
|
|
{
|
|
RunFunc run = reinterpret_cast<RunFunc>(vrun);
|
|
CThread::exit(reinterpret_cast<void*>(run(m_serviceMutex)));
|
|
}
|
|
|
|
void
|
|
CWin32Platform::serviceMain(DWORD argc, LPTSTR* argvIn)
|
|
{
|
|
typedef std::vector<LPCTSTR> ArgList;
|
|
typedef std::vector<CString> Arguments;
|
|
const char** argv = const_cast<const char**>(argvIn);
|
|
|
|
// open event log and direct log messages to it
|
|
installDaemonLogger(argv[0]);
|
|
|
|
// create synchronization objects
|
|
CThread::init();
|
|
m_serviceMutex = new CMutex;
|
|
m_serviceState = new CCondVar<DWORD>(m_serviceMutex, SERVICE_RUNNING);
|
|
|
|
// register our service handler functiom
|
|
m_statusHandle = RegisterServiceCtrlHandler(argv[0],
|
|
&CWin32Platform::serviceHandlerEntry);
|
|
if (m_statusHandle == NULL) {
|
|
// cannot start as service
|
|
m_daemonResult = -1;
|
|
delete m_serviceState;
|
|
delete m_serviceMutex;
|
|
return;
|
|
}
|
|
|
|
// tell service control manager that we're starting
|
|
setStatus(m_statusHandle, SERVICE_START_PENDING, 0, 1000);
|
|
|
|
// if no arguments supplied then try getting them from the registry.
|
|
// the first argument doesn't count because it's the service name.
|
|
Arguments args;
|
|
ArgList myArgv;
|
|
if (argc <= 1) {
|
|
// read command line
|
|
CString commandLine;
|
|
HKEY key = openNTServicesKey();
|
|
key = openKey(key, argv[0]);
|
|
key = openKey(key, "Parameters");
|
|
if (key != NULL) {
|
|
commandLine = readValueString(key, "CommandLine");
|
|
}
|
|
|
|
// if the command line isn't empty then parse and use it
|
|
if (!commandLine.empty()) {
|
|
// parse, honoring double quoted substrings
|
|
CString::size_type i = commandLine.find_first_not_of(" \t");
|
|
while (i != CString::npos && i != commandLine.size()) {
|
|
// find end of string
|
|
CString::size_type e;
|
|
if (commandLine[i] == '\"') {
|
|
// quoted. find closing quote.
|
|
++i;
|
|
e = commandLine.find("\"", i);
|
|
|
|
// whitespace must follow closing quote
|
|
if (e == CString::npos ||
|
|
(e + 1 != commandLine.size() &&
|
|
commandLine[e + 1] != ' ' &&
|
|
commandLine[e + 1] != '\t')) {
|
|
args.clear();
|
|
break;
|
|
}
|
|
|
|
// extract
|
|
args.push_back(commandLine.substr(i, e - i));
|
|
i = e + 1;
|
|
}
|
|
else {
|
|
// unquoted. find next whitespace.
|
|
e = commandLine.find_first_of(" \t", i);
|
|
if (e == CString::npos) {
|
|
e = commandLine.size();
|
|
}
|
|
|
|
// extract
|
|
args.push_back(commandLine.substr(i, e - i));
|
|
i = e + 1;
|
|
}
|
|
|
|
// next argument
|
|
i = commandLine.find_first_not_of(" \t", i);
|
|
}
|
|
|
|
// service name goes first
|
|
myArgv.push_back(argv[0]);
|
|
|
|
// get pointers
|
|
for (UInt32 i = 0; i < args.size(); ++i) {
|
|
myArgv.push_back(args[i].c_str());
|
|
}
|
|
|
|
// adjust argc/argv
|
|
argc = myArgv.size();
|
|
argv = &myArgv[0];
|
|
}
|
|
}
|
|
|
|
try {
|
|
// invoke daemon function
|
|
m_daemonResult = m_daemonFunc(this, static_cast<int>(argc), argv);
|
|
}
|
|
catch (CDaemonFailed& e) {
|
|
setStatusError(m_statusHandle, e.m_result);
|
|
m_daemonResult = -1;
|
|
}
|
|
catch (...) {
|
|
setStatusError(m_statusHandle, 1);
|
|
m_daemonResult = -1;
|
|
}
|
|
|
|
// clean up
|
|
delete m_serviceState;
|
|
delete m_serviceMutex;
|
|
|
|
// FIXME -- close event log?
|
|
}
|
|
|
|
void WINAPI
|
|
CWin32Platform::serviceMainEntry(DWORD argc, LPTSTR* argv)
|
|
{
|
|
s_daemonPlatform->serviceMain(argc, argv);
|
|
}
|
|
|
|
void
|
|
CWin32Platform::serviceHandler(DWORD ctrl)
|
|
{
|
|
assert(m_serviceMutex != NULL);
|
|
assert(m_serviceState != NULL);
|
|
|
|
CLock lock(m_serviceMutex);
|
|
|
|
// ignore request if service is already stopped
|
|
if (*m_serviceState == SERVICE_STOPPED) {
|
|
setStatus(m_statusHandle, *m_serviceState);
|
|
return;
|
|
}
|
|
|
|
switch (ctrl) {
|
|
case SERVICE_CONTROL_PAUSE:
|
|
// update state
|
|
*m_serviceState = SERVICE_PAUSE_PENDING;
|
|
setStatus(m_statusHandle, *m_serviceState, 0, 1000);
|
|
|
|
// stop run callback if running and wait for it to finish
|
|
if (m_serviceRunning) {
|
|
m_serviceHandlerWaiting = true;
|
|
m_stop();
|
|
m_serviceState->wait();
|
|
}
|
|
|
|
// update state if service hasn't stopped while we were waiting
|
|
if (*m_serviceState != SERVICE_STOPPED) {
|
|
*m_serviceState = SERVICE_PAUSED;
|
|
}
|
|
m_serviceState->broadcast();
|
|
break;
|
|
|
|
case SERVICE_CONTROL_CONTINUE:
|
|
// required status update
|
|
setStatus(m_statusHandle, *m_serviceState);
|
|
|
|
// update state but let main loop send RUNNING notification
|
|
*m_serviceState = SERVICE_RUNNING;
|
|
m_serviceState->broadcast();
|
|
return;
|
|
|
|
case SERVICE_CONTROL_STOP:
|
|
case SERVICE_CONTROL_SHUTDOWN:
|
|
// update state
|
|
*m_serviceState = SERVICE_STOP_PENDING;
|
|
setStatus(m_statusHandle, *m_serviceState, 0, 1000);
|
|
|
|
// stop run callback if running and wait for it to finish
|
|
if (m_serviceRunning) {
|
|
m_serviceHandlerWaiting = true;
|
|
m_stop();
|
|
m_serviceState->wait();
|
|
}
|
|
|
|
// update state
|
|
*m_serviceState = SERVICE_STOPPED;
|
|
m_serviceState->broadcast();
|
|
break;
|
|
|
|
default:
|
|
LOG((CLOG_WARN "unknown service command: %d", ctrl));
|
|
// fall through
|
|
|
|
case SERVICE_CONTROL_INTERROGATE:
|
|
break;
|
|
}
|
|
|
|
// send update
|
|
setStatus(m_statusHandle, *m_serviceState);
|
|
}
|
|
|
|
void WINAPI
|
|
CWin32Platform::serviceHandlerEntry(DWORD ctrl)
|
|
{
|
|
s_daemonPlatform->serviceHandler(ctrl);
|
|
}
|
|
|
|
bool
|
|
CWin32Platform::serviceLogger(int priority, const char* msg)
|
|
{
|
|
if (s_eventLog == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// convert priority
|
|
WORD type;
|
|
switch (priority) {
|
|
case CLog::kFATAL:
|
|
case CLog::kERROR:
|
|
type = EVENTLOG_ERROR_TYPE;
|
|
break;
|
|
|
|
case CLog::kWARNING:
|
|
type = EVENTLOG_WARNING_TYPE;
|
|
break;
|
|
|
|
default:
|
|
type = EVENTLOG_INFORMATION_TYPE;
|
|
break;
|
|
}
|
|
|
|
// log it
|
|
// FIXME -- win32 wants to use a message table to look up event
|
|
// strings. log messages aren't organized that way so we'll
|
|
// just dump our string into the raw data section of the event
|
|
// so users can at least see the message. note that we use our
|
|
// priority as the event category.
|
|
ReportEvent(s_eventLog, type, static_cast<WORD>(priority),
|
|
0, // event ID
|
|
NULL,
|
|
0,
|
|
strlen(msg + 1), // raw data size
|
|
NULL,
|
|
const_cast<char*>(msg));// raw data
|
|
return true;
|
|
}
|