| OLD | NEW |
| (Empty) |
| 1 // Copyright 2007-2009 Google Inc. | |
| 2 // | |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
| 4 // you may not use this file except in compliance with the License. | |
| 5 // You may obtain a copy of the License at | |
| 6 // | |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 // | |
| 9 // Unless required by applicable law or agreed to in writing, software | |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, | |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 12 // See the License for the specific language governing permissions and | |
| 13 // limitations under the License. | |
| 14 // ======================================================================== | |
| 15 | |
| 16 // | |
| 17 // Service-related utilities. | |
| 18 // | |
| 19 | |
| 20 #include "omaha/base/service_utils.h" | |
| 21 | |
| 22 #include <windows.h> | |
| 23 #include "omaha/base/constants.h" | |
| 24 #include "omaha/base/debug.h" | |
| 25 #include "omaha/base/error.h" | |
| 26 #include "omaha/base/reg_key.h" | |
| 27 #include "omaha/base/smart_handle.h" | |
| 28 #include "omaha/base/string.h" | |
| 29 #include "omaha/base/timer.h" | |
| 30 #include "omaha/base/utils.h" | |
| 31 | |
| 32 namespace omaha { | |
| 33 | |
| 34 HRESULT ScmDatabase::EnumerateServices( | |
| 35 ScmDatabase::EnumerateServicesCallback callback, | |
| 36 void* callback_context) { | |
| 37 ASSERT1(callback); | |
| 38 if (!callback) | |
| 39 return E_POINTER; | |
| 40 | |
| 41 const wchar_t* kServicesRegKeyFromRoot = | |
| 42 L"SYSTEM\\CurrentControlSet\\Services"; | |
| 43 | |
| 44 HRESULT hr = E_FAIL; | |
| 45 | |
| 46 RegKey services_key; | |
| 47 if (FAILED(hr = services_key.Open(HKEY_LOCAL_MACHINE, | |
| 48 kServicesRegKeyFromRoot, | |
| 49 KEY_ENUMERATE_SUB_KEYS))) { | |
| 50 ASSERT1(false); | |
| 51 REPORT(false, R_ERROR, (L"Couldn't open services subkey, hr=0x%x", hr), | |
| 52 9834572); | |
| 53 return hr; | |
| 54 } | |
| 55 | |
| 56 CString service_name; | |
| 57 int key_index = 0; | |
| 58 while (SUCCEEDED(hr = services_key.GetSubkeyNameAt(key_index++, | |
| 59 &service_name))) { | |
| 60 hr = callback(callback_context, service_name); | |
| 61 if (FAILED(hr) || hr == S_FALSE) { | |
| 62 // Callback asked to terminate enumeration. | |
| 63 return hr; | |
| 64 } | |
| 65 } | |
| 66 | |
| 67 if (hr != HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS)) { | |
| 68 ASSERT1(false); | |
| 69 REPORT(false, R_ERROR, (L"Failed enumerating service subkeys: 0x%x", hr), | |
| 70 1499372); | |
| 71 return hr; | |
| 72 } | |
| 73 | |
| 74 return S_OK; | |
| 75 } | |
| 76 | |
| 77 bool ScmDatabase::IsServiceStateEqual(SC_HANDLE service, DWORD state) { | |
| 78 ASSERT1(service); | |
| 79 | |
| 80 DWORD bytes_needed_ignored = 0; | |
| 81 byte buffer[8 * 1024] = { 0 }; | |
| 82 QUERY_SERVICE_CONFIG* service_config = | |
| 83 reinterpret_cast<QUERY_SERVICE_CONFIG*>(buffer); | |
| 84 if (!::QueryServiceConfig(service, service_config, sizeof(buffer), | |
| 85 &bytes_needed_ignored)) { | |
| 86 ASSERT(false, (L"Failed to query service config, perhaps handle is missing " | |
| 87 L"SERVICE_QUERY_CONFIG rights?")); | |
| 88 return false; | |
| 89 } | |
| 90 | |
| 91 return (service_config[0].dwStartType == state); | |
| 92 } | |
| 93 | |
| 94 bool ScmDatabase::IsServiceMarkedDeleted(SC_HANDLE service) { | |
| 95 ASSERT1(service); | |
| 96 | |
| 97 // Services that have been marked deleted are always in the | |
| 98 // SERVICE_DISABLED state. The converse is not true, and unfortunately | |
| 99 // there is no way to check if a service has been marked deleted except by | |
| 100 // attempting to change one of its configuration parameters, at which | |
| 101 // point you get a specific error indicating it has been marked deleted. | |
| 102 // | |
| 103 // The following call to ChangeServiceConfig does not actually change any | |
| 104 // of the service's configuration, but should hopefully return the | |
| 105 // specific error if the service has been marked deleted. | |
| 106 if (!::ChangeServiceConfig(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, | |
| 107 SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL, | |
| 108 NULL, NULL, NULL) && | |
| 109 ::GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE) { | |
| 110 ASSERT1(IsServiceStateEqual(service, SERVICE_DISABLED)); | |
| 111 return true; | |
| 112 } else { | |
| 113 return false; | |
| 114 } | |
| 115 } | |
| 116 | |
| 117 HRESULT ServiceInstall::UninstallByPrefix(void* context, | |
| 118 const wchar_t* service_name) { | |
| 119 ASSERT1(context != NULL); | |
| 120 if (!context) | |
| 121 return E_POINTER; | |
| 122 | |
| 123 UninstallByPrefixParams* params = | |
| 124 reinterpret_cast<UninstallByPrefixParams*>(context); | |
| 125 | |
| 126 if (String_StartsWith(service_name, params->prefix, true) && | |
| 127 lstrcmpiW(service_name, params->unless_matches) != 0) { | |
| 128 // The service must be stopped before attempting to remove it from the | |
| 129 // database. Otherwise, the SCM database remains dirty and all service | |
| 130 // functions return ERROR_SERVICE_MARKED_FOR_DELETE until the system is | |
| 131 // restarted. | |
| 132 StopService(service_name); | |
| 133 | |
| 134 scoped_service scm(::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS)); | |
| 135 if (!scm) { | |
| 136 HRESULT hr = HRESULTFromLastError(); | |
| 137 ASSERT1(false); | |
| 138 REPORT(false, R_ERROR, (L"Failed to open SCM: 0x%x", hr), 77223399); | |
| 139 return hr; | |
| 140 } | |
| 141 scoped_service service(::OpenService(get(scm), | |
| 142 service_name, | |
| 143 SERVICE_CHANGE_CONFIG | DELETE)); | |
| 144 if (service) { | |
| 145 // The service may not get deleted immediately; if there are handles to | |
| 146 // it open, it won't get deleted until the last one is closed. If the | |
| 147 // service is running, it won't get deleted immediately but rather will be | |
| 148 // marked for deletion (which happens on next reboot). Having to wait for | |
| 149 // a while and even until reboot doesn't matter much to us as our new | |
| 150 // service is installed under a new name and we are just cleaning up old | |
| 151 // ones. | |
| 152 if (!::DeleteService(get(service))) { | |
| 153 // We do not assert but just report so that we know if this happens | |
| 154 // abnormally often. | |
| 155 if (::GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE) { | |
| 156 REPORT(false, R_INFO, | |
| 157 (L"Failed to immediately delete service %s", service_name), | |
| 158 5440098); | |
| 159 } else { | |
| 160 ASSERT(false, (L"Failed to delete service %s, error %d", | |
| 161 service_name, ::GetLastError())); | |
| 162 } | |
| 163 // DO NOT return an error here; we want to keep going through all the | |
| 164 // services. | |
| 165 } else { | |
| 166 SERVICE_LOG(L1, | |
| 167 (L"Deleted old service %s", service_name)); | |
| 168 } | |
| 169 } else { | |
| 170 // Per documentation of the EnumerateServicesCallback interface we can | |
| 171 // expect not to be able to open the service with one of the following two | |
| 172 // error codes, because of discrepancies between the registry and the SCM | |
| 173 // database in memory. | |
| 174 DWORD last_error = ::GetLastError(); | |
| 175 ASSERT(last_error == ERROR_SERVICE_DOES_NOT_EXIST || | |
| 176 last_error == ERROR_INVALID_NAME, | |
| 177 (L"Failed to open service %s, last error %d", service_name, | |
| 178 last_error)); | |
| 179 REPORT(last_error == ERROR_SERVICE_DOES_NOT_EXIST || | |
| 180 last_error == ERROR_INVALID_NAME, R_ERROR, | |
| 181 (L"Failed to open service %s, last error %d", service_name, | |
| 182 last_error), 5576234); | |
| 183 } | |
| 184 } | |
| 185 | |
| 186 return S_OK; | |
| 187 } | |
| 188 | |
| 189 CString ServiceInstall::GenerateServiceName(const TCHAR* service_prefix) { | |
| 190 FILETIME ft = {0}; | |
| 191 ::GetSystemTimeAsFileTime(&ft); | |
| 192 CString versioned_service_name; | |
| 193 versioned_service_name.Format(_T("%s%x%x"), | |
| 194 service_prefix, | |
| 195 ft.dwHighDateTime, | |
| 196 ft.dwLowDateTime); | |
| 197 | |
| 198 ASSERT1(!versioned_service_name.IsEmpty()); | |
| 199 return versioned_service_name; | |
| 200 } | |
| 201 | |
| 202 HRESULT ServiceInstall::UninstallServices(const TCHAR* service_prefix, | |
| 203 const TCHAR* exclude_service) { | |
| 204 SERVICE_LOG(L2, (L"ServiceInstall::UninstallServices")); | |
| 205 | |
| 206 UninstallByPrefixParams params = { | |
| 207 service_prefix, | |
| 208 exclude_service, | |
| 209 }; | |
| 210 | |
| 211 return ScmDatabase::EnumerateServices(UninstallByPrefix, ¶ms); | |
| 212 } | |
| 213 | |
| 214 bool ServiceInstall::CanInstallWithoutReboot() { | |
| 215 scoped_service scm(::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS)); | |
| 216 if (!scm) { | |
| 217 ASSERT1(false); | |
| 218 REPORT(false, R_ERROR, (L"Failed to open SCM: %d", ::GetLastError()), | |
| 219 77224449); | |
| 220 return false; // request reboot just in case | |
| 221 } | |
| 222 | |
| 223 scoped_service service(::OpenService(get(scm), | |
| 224 _T("gupdate"), | |
| 225 SERVICE_QUERY_CONFIG | | |
| 226 SERVICE_CHANGE_CONFIG)); | |
| 227 if (!service) { | |
| 228 DWORD last_error = ::GetLastError(); | |
| 229 if (last_error == ERROR_ACCESS_DENIED || | |
| 230 last_error == ERROR_INVALID_HANDLE) { | |
| 231 // unable to verify the service is fully deleted, so request reboot | |
| 232 ASSERT(false, (L"Expected access and correct handle")); | |
| 233 return false; | |
| 234 } else { | |
| 235 // service does not exist | |
| 236 return true; | |
| 237 } | |
| 238 } | |
| 239 | |
| 240 return !ScmDatabase::IsServiceMarkedDeleted(get(service)); | |
| 241 } | |
| 242 | |
| 243 HRESULT ServiceInstall::StopService(const CString& service_name) { | |
| 244 SERVICE_LOG(L1, (_T("[ServiceInstall::StopService]"))); | |
| 245 | |
| 246 scoped_service scm(::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS)); | |
| 247 if (!scm) { | |
| 248 return HRESULTFromLastError(); | |
| 249 } | |
| 250 scoped_service service(::OpenService(get(scm), | |
| 251 service_name, | |
| 252 SERVICE_QUERY_STATUS | SERVICE_STOP)); | |
| 253 if (!service) { | |
| 254 return HRESULTFromLastError(); | |
| 255 } | |
| 256 | |
| 257 SERVICE_STATUS status = {0}; | |
| 258 if (::QueryServiceStatus(get(service), &status)) { | |
| 259 if (status.dwCurrentState != SERVICE_STOPPED && | |
| 260 status.dwCurrentState != SERVICE_STOP_PENDING) { | |
| 261 // Stop the service. | |
| 262 SetZero(status); | |
| 263 if (!::ControlService(get(service), SERVICE_CONTROL_STOP, &status)) { | |
| 264 return HRESULTFromLastError(); | |
| 265 } | |
| 266 } | |
| 267 } | |
| 268 | |
| 269 if (status.dwCurrentState != SERVICE_STOPPED) { | |
| 270 SERVICE_LOG(L1, (_T("[Service is stopping...]"))); | |
| 271 | |
| 272 const int kWaitForServiceToStopMs = 8000; | |
| 273 LowResTimer t(true); | |
| 274 | |
| 275 while (status.dwCurrentState != SERVICE_STOPPED && | |
| 276 t.GetMilliseconds() < kWaitForServiceToStopMs) { | |
| 277 const int kSleepTimeMs = 50; | |
| 278 ::Sleep(kSleepTimeMs); | |
| 279 SetZero(status); | |
| 280 VERIFY1(::QueryServiceStatus(get(service), &status)); | |
| 281 SERVICE_LOG(L1, (_T("[Waiting for service to stop][time elapsed: %d ms]"), | |
| 282 static_cast<int>(t.GetMilliseconds()))); | |
| 283 } | |
| 284 | |
| 285 if (status.dwCurrentState != SERVICE_STOPPED) { | |
| 286 SERVICE_LOG(LEVEL_WARNING, (_T("[Service did not stop! Not good...]"))); | |
| 287 return HRESULT_FROM_WIN32(ERROR_TIMEOUT); | |
| 288 } | |
| 289 } | |
| 290 | |
| 291 ASSERT1(status.dwCurrentState == SERVICE_STOPPED); | |
| 292 SERVICE_LOG(L1, (_T("[ServiceInstall::StopService - service stopped]"))); | |
| 293 return S_OK; | |
| 294 } | |
| 295 | |
| 296 bool ServiceInstall::IsServiceInstalled(const TCHAR* service_name) { | |
| 297 ASSERT1(service_name); | |
| 298 scoped_service scm(::OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT)); | |
| 299 if (!scm) { | |
| 300 return false; | |
| 301 } | |
| 302 scoped_service service(::OpenService(get(scm), | |
| 303 service_name, | |
| 304 SERVICE_QUERY_CONFIG)); | |
| 305 return valid(service); | |
| 306 } | |
| 307 | |
| 308 // TODO(Omaha): Move all functions under a common ServiceUtils namespace. | |
| 309 bool ServiceUtils::IsServiceRunning(const TCHAR* service_name) { | |
| 310 ASSERT1(service_name); | |
| 311 | |
| 312 scoped_service scm(::OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT)); | |
| 313 if (!scm) { | |
| 314 SERVICE_LOG(LE, (_T("[OpenSCManager fail][0x%x]"), HRESULTFromLastError())); | |
| 315 return false; | |
| 316 } | |
| 317 | |
| 318 scoped_service service(::OpenService(get(scm), | |
| 319 service_name, | |
| 320 SERVICE_QUERY_STATUS)); | |
| 321 if (!service) { | |
| 322 SERVICE_LOG(LE, (_T("[OpenService failed][%s][0x%x]"), | |
| 323 service_name, HRESULTFromLastError())); | |
| 324 return false; | |
| 325 } | |
| 326 | |
| 327 SERVICE_STATUS status = {0}; | |
| 328 if (!::QueryServiceStatus(get(service), &status)) { | |
| 329 SERVICE_LOG(LE, (_T("[QueryServiceStatus failed][%s][0x%x]"), | |
| 330 service_name, HRESULTFromLastError())); | |
| 331 return false; | |
| 332 } | |
| 333 | |
| 334 return status.dwCurrentState == SERVICE_RUNNING || | |
| 335 status.dwCurrentState == SERVICE_START_PENDING; | |
| 336 } | |
| 337 | |
| 338 bool ServiceUtils::IsServiceDisabled(const TCHAR* service_name) { | |
| 339 ASSERT1(service_name); | |
| 340 | |
| 341 scoped_service scm(::OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT)); | |
| 342 if (!scm) { | |
| 343 SERVICE_LOG(LE, (_T("[OpenSCManager fail][0x%x]"), HRESULTFromLastError())); | |
| 344 return false; | |
| 345 } | |
| 346 | |
| 347 scoped_service service(::OpenService(get(scm), | |
| 348 service_name, | |
| 349 SERVICE_QUERY_CONFIG)); | |
| 350 if (!service) { | |
| 351 SERVICE_LOG(LE, (_T("[OpenService failed][%s][0x%x]"), | |
| 352 service_name, HRESULTFromLastError())); | |
| 353 return false; | |
| 354 } | |
| 355 | |
| 356 return ScmDatabase::IsServiceStateEqual(get(service), SERVICE_DISABLED); | |
| 357 } | |
| 358 | |
| 359 } // namespace omaha | |
| 360 | |
| OLD | NEW |