blob: 2eeb143427eeac3448503d5eeeed46afb398a384 [file] [log] [blame]
/*
*
* 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 qpid {
namespace windows {
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;
};
}
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