diff options
Diffstat (limited to 'cpp/src/qpid/sys/windows/SCM.cpp')
-rw-r--r-- | cpp/src/qpid/sys/windows/SCM.cpp | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/cpp/src/qpid/sys/windows/SCM.cpp b/cpp/src/qpid/sys/windows/SCM.cpp new file mode 100644 index 0000000000..4d2c74d4b9 --- /dev/null +++ b/cpp/src/qpid/sys/windows/SCM.cpp @@ -0,0 +1,332 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 "qpid/log/Statement.h" +#include "qpid/sys/windows/check.h" +#include "SCM.h" + +#pragma comment(lib, "advapi32.lib") + +namespace { + +// Container that will close a SC_HANDLE upon destruction. +class AutoServiceHandle { +public: + AutoServiceHandle(SC_HANDLE h_ = NULL) : h(h_) {} + ~AutoServiceHandle() { if (h != NULL) ::CloseServiceHandle(h); } + void release() { h = NULL; } + void reset(SC_HANDLE newHandle) + { + if (h != NULL) + ::CloseServiceHandle(h); + h = newHandle; + } + operator SC_HANDLE() const { return h; } + +private: + SC_HANDLE h; +}; + +} + +namespace qpid { +namespace windows { + +SCM::SCM() : scmHandle(NULL) +{ +} + +SCM::~SCM() +{ + if (NULL != scmHandle) + ::CloseServiceHandle(scmHandle); +} + +/** + * Install this executable as a service + */ +void SCM::install(const string& serviceName, + const string& serviceDesc, + const string& args, + DWORD startType, + const string& account, + const string& password, + const string& depends) +{ + // Handle dependent service name list; Windows wants a set of nul-separated + // names ending with a double nul. + string depends2 = depends; + if (!depends2.empty()) { + // CDL to null delimiter w/ trailing double null + size_t p = 0; + while ((p = depends2.find_first_of( ',', p)) != string::npos) + depends2.replace(p, 1, 1, '\0'); + depends2.push_back('\0'); + depends2.push_back('\0'); + } + +#if 0 + // I'm nervous about adding a user/password check here. Is this a + // potential attack vector, letting users check passwords without + // control? -Steve Huston, Feb 24, 2011 + + // Validate account, password + HANDLE hToken = NULL; + bool logStatus = false; + if (!account.empty() && !password.empty() && + !(logStatus = ::LogonUserA(account.c_str(), + "", + password.c_str(), + LOGON32_LOGON_NETWORK, + LOGON32_PROVIDER_DEFAULT, + &hToken ) != 0)) + std::cout << "warning: supplied account & password failed with LogonUser." << std::endl; + if (logStatus) + ::CloseHandle(hToken); +#endif + + // Get fully qualified .exe name + char myPath[MAX_PATH]; + DWORD myPathLength = ::GetModuleFileName(NULL, myPath, MAX_PATH); + QPID_WINDOWS_CHECK_NOT(myPathLength, 0); + string imagePath(myPath, myPathLength); + if (!args.empty()) + imagePath += " " + args; + + // Ensure there's a handle to the SCM database. + openSvcManager(); + + // Create the service + SC_HANDLE svcHandle; + svcHandle = ::CreateService(scmHandle, // SCM database + serviceName.c_str(), // name of service + serviceDesc.c_str(), // name to display + SERVICE_ALL_ACCESS, // desired access + SERVICE_WIN32_OWN_PROCESS, // service type + startType, // start type + SERVICE_ERROR_NORMAL, // error cntrl type + imagePath.c_str(), // path to service's binary w/ optional arguments + NULL, // no load ordering group + NULL, // no tag identifier + depends2.empty() ? NULL : depends2.c_str(), + account.empty() ? NULL : account.c_str(), // account name, or NULL for LocalSystem + password.empty() ? NULL : password.c_str()); // password, or NULL for none + QPID_WINDOWS_CHECK_NULL(svcHandle); + ::CloseServiceHandle(svcHandle); + QPID_LOG(info, "Service installed successfully"); +} + +/** + * + */ +void SCM::uninstall(const string& serviceName) +{ + // Ensure there's a handle to the SCM database. + openSvcManager(); + AutoServiceHandle svc(::OpenService(scmHandle, + serviceName.c_str(), + DELETE)); + QPID_WINDOWS_CHECK_NULL((SC_HANDLE)svc); + QPID_WINDOWS_CHECK_NOT(::DeleteService(svc), 0); + QPID_LOG(info, "Service deleted successfully."); +} + +/** + * Attempt to start the service. + */ +void SCM::start(const string& serviceName) +{ + // Ensure we have a handle to the SCM database. + openSvcManager(); + + // Get a handle to the service. + AutoServiceHandle svc(::OpenService(scmHandle, + serviceName.c_str(), + SERVICE_ALL_ACCESS)); + QPID_WINDOWS_CHECK_NULL(svc); + + // Check the status in case the service is not stopped. + DWORD state = waitForStateChangeFrom(svc, SERVICE_STOP_PENDING); + if (state == SERVICE_STOP_PENDING) + throw qpid::Exception("Timed out waiting for running service to stop."); + + // Attempt to start the service. + QPID_WINDOWS_CHECK_NOT(::StartService(svc, 0, NULL), 0); + + QPID_LOG(info, "Service start pending..."); + + // Check the status until the service is no longer start pending. + state = waitForStateChangeFrom(svc, SERVICE_START_PENDING); + // Determine whether the service is running. + if (state == SERVICE_RUNNING) { + QPID_LOG(info, "Service started successfully"); + } + else { + throw qpid::Exception(QPID_MSG("Service not yet running; state now " << state)); + } +} + +/** + * + */ +void SCM::stop(const string& serviceName) +{ + // Ensure a handle to the SCM database. + openSvcManager(); + + // Get a handle to the service. + AutoServiceHandle svc(::OpenService(scmHandle, + serviceName.c_str(), + SERVICE_STOP | SERVICE_QUERY_STATUS | + SERVICE_ENUMERATE_DEPENDENTS)); + QPID_WINDOWS_CHECK_NULL(svc); + + // Make sure the service is not already stopped; if it's stop-pending, + // wait for it to finalize. + DWORD state = waitForStateChangeFrom(svc, SERVICE_STOP_PENDING); + if (state == SERVICE_STOPPED) { + QPID_LOG(info, "Service is already stopped"); + return; + } + + // If the service is running, dependencies must be stopped first. + std::auto_ptr<ENUM_SERVICE_STATUS> deps; + DWORD numDeps = getDependentServices(svc, deps); + for (DWORD i = 0; i < numDeps; i++) + stop(deps.get()[i].lpServiceName); + + // Dependents stopped; send a stop code to the service. + SERVICE_STATUS_PROCESS ssp; + if (!::ControlService(svc, SERVICE_CONTROL_STOP, (LPSERVICE_STATUS)&ssp)) + throw qpid::Exception(QPID_MSG("Stopping " << serviceName << ": " << + qpid::sys::strError(::GetLastError()))); + + // Wait for the service to stop. + state = waitForStateChangeFrom(svc, SERVICE_STOP_PENDING); + if (state == SERVICE_STOPPED) + QPID_LOG(info, QPID_MSG("Service " << serviceName << + " stopped successfully.")); +} + +/** + * + */ +void SCM::openSvcManager() +{ + if (NULL != scmHandle) + return; + + scmHandle = ::OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_ALL_ACCESS); // Rights + QPID_WINDOWS_CHECK_NULL(scmHandle); +} + +DWORD SCM::waitForStateChangeFrom(SC_HANDLE svc, DWORD originalState) +{ + SERVICE_STATUS_PROCESS ssStatus; + DWORD bytesNeeded; + DWORD waitTime; + if (!::QueryServiceStatusEx(svc, // handle to service + SC_STATUS_PROCESS_INFO, // information level + (LPBYTE)&ssStatus, // address of structure + sizeof(ssStatus), // size of structure + &bytesNeeded)) // size needed if buffer is too small + throw QPID_WINDOWS_ERROR(::GetLastError()); + + // Save the tick count and initial checkpoint. + DWORD startTickCount = ::GetTickCount(); + DWORD oldCheckPoint = ssStatus.dwCheckPoint; + + // Wait for the service to change out of the noted state. + while (ssStatus.dwCurrentState == originalState) { + // Do not wait longer than the wait hint. A good interval is + // one-tenth of the wait hint but not less than 1 second + // and not more than 10 seconds. + waitTime = ssStatus.dwWaitHint / 10; + if (waitTime < 1000) + waitTime = 1000; + else if (waitTime > 10000) + waitTime = 10000; + + ::Sleep(waitTime); + + // Check the status until the service is no longer stop pending. + if (!::QueryServiceStatusEx(svc, + SC_STATUS_PROCESS_INFO, + (LPBYTE) &ssStatus, + sizeof(ssStatus), + &bytesNeeded)) + throw QPID_WINDOWS_ERROR(::GetLastError()); + + if (ssStatus.dwCheckPoint > oldCheckPoint) { + // Continue to wait and check. + startTickCount = ::GetTickCount(); + oldCheckPoint = ssStatus.dwCheckPoint; + } else { + if ((::GetTickCount() - startTickCount) > ssStatus.dwWaitHint) + break; + } + } + return ssStatus.dwCurrentState; +} + +/** + * Get the services that depend on @arg svc. All dependent service info + * is returned in an array of ENUM_SERVICE_STATUS structures via @arg deps. + * + * @retval The number of dependent services. + */ +DWORD SCM::getDependentServices(SC_HANDLE svc, + std::auto_ptr<ENUM_SERVICE_STATUS>& deps) +{ + DWORD bytesNeeded; + DWORD numEntries; + + // Pass a zero-length buffer to get the required buffer size. + if (::EnumDependentServices(svc, + SERVICE_ACTIVE, + 0, + 0, + &bytesNeeded, + &numEntries)) { + // If the Enum call succeeds, then there are no dependent + // services, so do nothing. + return 0; + } + + if (::GetLastError() != ERROR_MORE_DATA) + throw QPID_WINDOWS_ERROR((::GetLastError())); + + // Allocate a buffer for the dependencies. + deps.reset((LPENUM_SERVICE_STATUS)(new char[bytesNeeded])); + // Enumerate the dependencies. + if (!::EnumDependentServices(svc, + SERVICE_ACTIVE, + deps.get(), + bytesNeeded, + &bytesNeeded, + &numEntries)) + throw QPID_WINDOWS_ERROR((::GetLastError())); + return numEntries; +} + +} } // namespace qpid::windows |