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 |