// Copyright 2007-2009 Google Inc. | |
// | |
// 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. | |
// ======================================================================== | |
// | |
// Service-related utilities. | |
// | |
#include "omaha/common/service_utils.h" | |
#include <windows.h> | |
#include "omaha/common/constants.h" | |
#include "omaha/common/debug.h" | |
#include "omaha/common/error.h" | |
#include "omaha/common/reg_key.h" | |
#include "omaha/common/smart_handle.h" | |
#include "omaha/common/string.h" | |
#include "omaha/common/timer.h" | |
#include "omaha/common/utils.h" | |
namespace omaha { | |
HRESULT ScmDatabase::EnumerateServices( | |
ScmDatabase::EnumerateServicesCallback callback, | |
void* callback_context) { | |
ASSERT1(callback); | |
if (!callback) | |
return E_POINTER; | |
const wchar_t* kServicesRegKeyFromRoot = | |
L"SYSTEM\\CurrentControlSet\\Services"; | |
HRESULT hr = E_FAIL; | |
RegKey services_key; | |
if (FAILED(hr = services_key.Open(HKEY_LOCAL_MACHINE, | |
kServicesRegKeyFromRoot, | |
KEY_ENUMERATE_SUB_KEYS))) { | |
ASSERT1(false); | |
REPORT(false, R_ERROR, (L"Couldn't open services subkey, hr=0x%x", hr), | |
9834572); | |
return hr; | |
} | |
CString service_name; | |
int key_index = 0; | |
while (SUCCEEDED(hr = services_key.GetSubkeyNameAt(key_index++, | |
&service_name))) { | |
hr = callback(callback_context, service_name); | |
if (FAILED(hr) || hr == S_FALSE) { | |
// Callback asked to terminate enumeration. | |
return hr; | |
} | |
} | |
if (hr != HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS)) { | |
ASSERT1(false); | |
REPORT(false, R_ERROR, (L"Failed enumerating service subkeys: 0x%x", hr), | |
1499372); | |
return hr; | |
} | |
return S_OK; | |
} | |
bool ScmDatabase::IsServiceStateEqual(SC_HANDLE service, DWORD state) { | |
ASSERT1(service); | |
DWORD bytes_needed_ignored = 0; | |
byte buffer[8 * 1024] = { 0 }; | |
QUERY_SERVICE_CONFIG* service_config = | |
reinterpret_cast<QUERY_SERVICE_CONFIG*>(buffer); | |
if (!::QueryServiceConfig(service, service_config, sizeof(buffer), | |
&bytes_needed_ignored)) { | |
ASSERT(false, (L"Failed to query service config, perhaps handle is missing " | |
L"SERVICE_QUERY_CONFIG rights?")); | |
return false; | |
} | |
return (service_config[0].dwStartType == state); | |
} | |
bool ScmDatabase::IsServiceMarkedDeleted(SC_HANDLE service) { | |
ASSERT1(service); | |
// Services that have been marked deleted are always in the | |
// SERVICE_DISABLED state. The converse is not true, and unfortunately | |
// there is no way to check if a service has been marked deleted except by | |
// attempting to change one of its configuration parameters, at which | |
// point you get a specific error indicating it has been marked deleted. | |
// | |
// The following call to ChangeServiceConfig does not actually change any | |
// of the service's configuration, but should hopefully return the | |
// specific error if the service has been marked deleted. | |
if (!::ChangeServiceConfig(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, | |
SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL, | |
NULL, NULL, NULL) && | |
::GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE) { | |
ASSERT1(IsServiceStateEqual(service, SERVICE_DISABLED)); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
HRESULT ServiceInstall::UninstallByPrefix(void* context, | |
const wchar_t* service_name) { | |
ASSERT1(context != NULL); | |
if (!context) | |
return E_POINTER; | |
UninstallByPrefixParams* params = | |
reinterpret_cast<UninstallByPrefixParams*>(context); | |
if (String_StartsWith(service_name, params->prefix, true) && | |
lstrcmpiW(service_name, params->unless_matches) != 0) { | |
// The service must be stopped before attempting to remove it from the | |
// database. Otherwise, the SCM database remains dirty and all service | |
// functions return ERROR_SERVICE_MARKED_FOR_DELETE until the system is | |
// restarted. | |
StopService(service_name); | |
scoped_service scm(::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS)); | |
if (!scm) { | |
HRESULT hr = HRESULTFromLastError(); | |
ASSERT1(false); | |
REPORT(false, R_ERROR, (L"Failed to open SCM: 0x%x", hr), 77223399); | |
return hr; | |
} | |
scoped_service service(::OpenService(get(scm), | |
service_name, | |
SERVICE_CHANGE_CONFIG | DELETE)); | |
if (service) { | |
// The service may not get deleted immediately; if there are handles to | |
// it open, it won't get deleted until the last one is closed. If the | |
// service is running, it won't get deleted immediately but rather will be | |
// marked for deletion (which happens on next reboot). Having to wait for | |
// a while and even until reboot doesn't matter much to us as our new | |
// service is installed under a new name and we are just cleaning up old | |
// ones. | |
if (!::DeleteService(get(service))) { | |
// We do not assert but just report so that we know if this happens | |
// abnormally often. | |
if (::GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE) { | |
REPORT(false, R_INFO, | |
(L"Failed to immediately delete service %s", service_name), | |
5440098); | |
} else { | |
ASSERT(false, (L"Failed to delete service %s, error %d", | |
service_name, ::GetLastError())); | |
} | |
// DO NOT return an error here; we want to keep going through all the | |
// services. | |
} else { | |
SERVICE_LOG(L1, | |
(L"Deleted old service %s", service_name)); | |
} | |
} else { | |
// Per documentation of the EnumerateServicesCallback interface we can | |
// expect not to be able to open the service with one of the following two | |
// error codes, because of discrepancies between the registry and the SCM | |
// database in memory. | |
DWORD last_error = ::GetLastError(); | |
ASSERT(last_error == ERROR_SERVICE_DOES_NOT_EXIST || | |
last_error == ERROR_INVALID_NAME, | |
(L"Failed to open service %s, last error %d", service_name, | |
last_error)); | |
REPORT(last_error == ERROR_SERVICE_DOES_NOT_EXIST || | |
last_error == ERROR_INVALID_NAME, R_ERROR, | |
(L"Failed to open service %s, last error %d", service_name, | |
last_error), 5576234); | |
} | |
} | |
return S_OK; | |
} | |
CString ServiceInstall::GenerateServiceName(const TCHAR* service_prefix) { | |
FILETIME ft = {0}; | |
::GetSystemTimeAsFileTime(&ft); | |
CString versioned_service_name; | |
versioned_service_name.Format(_T("%s%x%x"), | |
service_prefix, | |
ft.dwHighDateTime, | |
ft.dwLowDateTime); | |
ASSERT1(!versioned_service_name.IsEmpty()); | |
return versioned_service_name; | |
} | |
HRESULT ServiceInstall::UninstallServices(const TCHAR* service_prefix, | |
const TCHAR* exclude_service) { | |
SERVICE_LOG(L2, (L"ServiceInstall::UninstallServices")); | |
UninstallByPrefixParams params = { | |
service_prefix, | |
exclude_service, | |
}; | |
return ScmDatabase::EnumerateServices(UninstallByPrefix, ¶ms); | |
} | |
bool ServiceInstall::CanInstallWithoutReboot() { | |
scoped_service scm(::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS)); | |
if (!scm) { | |
ASSERT1(false); | |
REPORT(false, R_ERROR, (L"Failed to open SCM: %d", ::GetLastError()), | |
77224449); | |
return false; // request reboot just in case | |
} | |
scoped_service service(::OpenService(get(scm), | |
_T("gupdate"), | |
SERVICE_QUERY_CONFIG | | |
SERVICE_CHANGE_CONFIG)); | |
if (!service) { | |
DWORD last_error = ::GetLastError(); | |
if (last_error == ERROR_ACCESS_DENIED || | |
last_error == ERROR_INVALID_HANDLE) { | |
// unable to verify the service is fully deleted, so request reboot | |
ASSERT(false, (L"Expected access and correct handle")); | |
return false; | |
} else { | |
// service does not exist | |
return true; | |
} | |
} | |
return !ScmDatabase::IsServiceMarkedDeleted(get(service)); | |
} | |
HRESULT ServiceInstall::StopService(const CString& service_name) { | |
SERVICE_LOG(L1, (_T("[ServiceInstall::StopService]"))); | |
scoped_service scm(::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS)); | |
if (!scm) { | |
return HRESULTFromLastError(); | |
} | |
scoped_service service(::OpenService(get(scm), | |
service_name, | |
SERVICE_QUERY_STATUS | SERVICE_STOP)); | |
if (!service) { | |
return HRESULTFromLastError(); | |
} | |
SERVICE_STATUS status = {0}; | |
if (::QueryServiceStatus(get(service), &status)) { | |
if (status.dwCurrentState != SERVICE_STOPPED && | |
status.dwCurrentState != SERVICE_STOP_PENDING) { | |
// Stop the service. | |
SetZero(status); | |
if (!::ControlService(get(service), SERVICE_CONTROL_STOP, &status)) { | |
return HRESULTFromLastError(); | |
} | |
} | |
} | |
if (status.dwCurrentState != SERVICE_STOPPED) { | |
SERVICE_LOG(L1, (_T("[Service is stopping...]"))); | |
const int kWaitForServiceToStopMs = 8000; | |
LowResTimer t(true); | |
while (status.dwCurrentState != SERVICE_STOPPED && | |
t.GetMilliseconds() < kWaitForServiceToStopMs) { | |
const int kSleepTimeMs = 50; | |
::Sleep(kSleepTimeMs); | |
SetZero(status); | |
VERIFY1(::QueryServiceStatus(get(service), &status)); | |
SERVICE_LOG(L1, (_T("[Waiting for service to stop %d]"), | |
static_cast<int>(t.GetMilliseconds()))); | |
} | |
if (status.dwCurrentState != SERVICE_STOPPED) { | |
SERVICE_LOG(LEVEL_WARNING, (_T("[Service did not stop! Not good...]"))); | |
return HRESULT_FROM_WIN32(ERROR_TIMEOUT); | |
} | |
} | |
ASSERT1(status.dwCurrentState == SERVICE_STOPPED); | |
SERVICE_LOG(L1, (_T("[ServiceInstall::StopService - service stopped]"))); | |
return S_OK; | |
} | |
bool ServiceInstall::IsServiceInstalled(const TCHAR* service_name) { | |
ASSERT1(service_name); | |
scoped_service scm(::OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT)); | |
if (!scm) { | |
return false; | |
} | |
scoped_service service(::OpenService(get(scm), | |
service_name, | |
SERVICE_QUERY_CONFIG)); | |
return valid(service); | |
} | |
// TODO(Omaha): Move all functions under a common ServiceUtils namespace. | |
bool ServiceUtils::IsServiceRunning(const TCHAR* service_name) { | |
ASSERT1(service_name); | |
scoped_service scm(::OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT)); | |
if (!scm) { | |
SERVICE_LOG(LE, (_T("[OpenSCManager fail][0x%x]"), HRESULTFromLastError())); | |
return false; | |
} | |
scoped_service service(::OpenService(get(scm), | |
service_name, | |
SERVICE_QUERY_STATUS)); | |
if (!service) { | |
SERVICE_LOG(LE, (_T("[OpenService failed][%s][0x%x]"), | |
service_name, HRESULTFromLastError())); | |
return false; | |
} | |
SERVICE_STATUS status = {0}; | |
if (!::QueryServiceStatus(get(service), &status)) { | |
SERVICE_LOG(LE, (_T("[QueryServiceStatus failed][%s][0x%x]"), | |
service_name, HRESULTFromLastError())); | |
return false; | |
} | |
return status.dwCurrentState == SERVICE_RUNNING || | |
status.dwCurrentState == SERVICE_START_PENDING; | |
} | |
bool ServiceUtils::IsServiceDisabled(const TCHAR* service_name) { | |
ASSERT1(service_name); | |
scoped_service scm(::OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT)); | |
if (!scm) { | |
SERVICE_LOG(LE, (_T("[OpenSCManager fail][0x%x]"), HRESULTFromLastError())); | |
return false; | |
} | |
scoped_service service(::OpenService(get(scm), | |
service_name, | |
SERVICE_QUERY_CONFIG)); | |
if (!service) { | |
SERVICE_LOG(LE, (_T("[OpenService failed][%s][0x%x]"), | |
service_name, HRESULTFromLastError())); | |
return false; | |
} | |
return ScmDatabase::IsServiceStateEqual(get(service), SERVICE_DISABLED); | |
} | |
} // namespace omaha | |