OLD | NEW |
| (Empty) |
1 // Copyright 2007-2010 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 #include "omaha/goopdate/installer_wrapper.h" | |
17 #include "omaha/base/const_object_names.h" | |
18 #include "omaha/base/const_utils.h" | |
19 #include "omaha/base/debug.h" | |
20 #include "omaha/base/error.h" | |
21 #include "omaha/base/logging.h" | |
22 #include "omaha/base/path.h" | |
23 #include "omaha/base/process.h" | |
24 #include "omaha/base/safe_format.h" | |
25 #include "omaha/base/scope_guard.h" | |
26 #include "omaha/base/scoped_ptr_address.h" | |
27 #include "omaha/base/string.h" | |
28 #include "omaha/base/synchronized.h" | |
29 #include "omaha/base/system_info.h" | |
30 #include "omaha/base/utils.h" | |
31 #include "omaha/common/const_goopdate.h" | |
32 #include "omaha/common/goopdate_utils.h" | |
33 #include "omaha/goopdate/app_manager.h" | |
34 #include "omaha/goopdate/server_resource.h" | |
35 #include "omaha/goopdate/string_formatter.h" | |
36 #include "omaha/goopdate/worker_metrics.h" | |
37 | |
38 namespace omaha { | |
39 | |
40 namespace { | |
41 | |
42 CString BuildMsiCommandLine(const CString& arguments, | |
43 const CString& msi_file_path, | |
44 const CString& enclosed_installer_data_file_path) { | |
45 CORE_LOG(L3, (_T("[CreateMsiCommandLine]"))); | |
46 | |
47 CString command_line; | |
48 // Suppressing reboots can lead to an inconsistent state until the user | |
49 // reboots, but automatically rebooting is unacceptable. The user will be | |
50 // informed by the string for ERROR_SUCCESS_REBOOT_REQUIRED that a reboot is | |
51 // necessary. See http://b/1184091 for details. | |
52 | |
53 if (!enclosed_installer_data_file_path.IsEmpty()) { | |
54 SafeCStringFormat(&command_line, _T("INSTALLERDATA=%s "), | |
55 enclosed_installer_data_file_path); | |
56 } | |
57 | |
58 SafeCStringAppendFormat(&command_line, _T("%s %s /qn /i \"%s\""), | |
59 arguments, | |
60 kMsiSuppressAllRebootsCmdLine, | |
61 msi_file_path); | |
62 | |
63 // The msiexec version in XP SP2 (V 3.01) and higher supports the /log switch. | |
64 if (SystemInfo::OSWinXPSP2OrLater()) { | |
65 CString logfile(msi_file_path); | |
66 logfile.Append(_T(".log")); | |
67 | |
68 SafeCStringAppendFormat(&command_line, _T(" /log \"%s\""), logfile); | |
69 } | |
70 | |
71 CORE_LOG(L2, (_T("[msiexec command line][%s]"), command_line)); | |
72 return command_line; | |
73 } | |
74 | |
75 // Gets the installer exit code. | |
76 HRESULT GetInstallerExitCode(const Process& p, uint32* exit_code) { | |
77 ASSERT1(exit_code); | |
78 | |
79 if (p.Running()) { | |
80 ASSERT(false, | |
81 (_T("GetInstallerExitCode called while the process is running."))); | |
82 return GOOPDATEINSTALL_E_INSTALLER_INTERNAL_ERROR; | |
83 } | |
84 | |
85 if (!p.GetExitCode(exit_code)) { | |
86 ASSERT(false, | |
87 (_T("[Failed to get the installer exit code for some reason.]"))); | |
88 return GOOPDATEINSTALL_E_INSTALLER_INTERNAL_ERROR; | |
89 } | |
90 | |
91 CORE_LOG(L2, (_T("[Installer exit code][%u]"), *exit_code)); | |
92 | |
93 return S_OK; | |
94 } | |
95 | |
96 // Gets the errors string for the specified system error. | |
97 // Assumes error_code represents a system error. | |
98 void GetSystemErrorString(uint32 error_code, | |
99 const CString& language, | |
100 CString* error_string) { | |
101 ASSERT1(error_string); | |
102 ASSERT1(ERROR_SUCCESS != error_code); | |
103 | |
104 const CString error_code_string = FormatErrorCode(error_code); | |
105 | |
106 StringFormatter formatter(language); | |
107 | |
108 const CString error_message(GetMessageForSystemErrorCode(error_code)); | |
109 if (!error_message.IsEmpty()) { | |
110 VERIFY1(SUCCEEDED(formatter.FormatMessage(error_string, | |
111 IDS_INSTALLER_FAILED_WITH_MESSAGE, | |
112 error_code_string, | |
113 error_message))); | |
114 } else { | |
115 VERIFY1(SUCCEEDED(formatter.FormatMessage(error_string, | |
116 IDS_INSTALLER_FAILED_NO_MESSAGE, | |
117 error_code_string))); | |
118 } | |
119 | |
120 OPT_LOG(LEVEL_ERROR, (_T("[installer system error][%u][%s]"), | |
121 error_code, *error_string)); | |
122 ASSERT1(!error_string->IsEmpty()); | |
123 } | |
124 | |
125 } // namespace | |
126 | |
127 InstallerWrapper::InstallerWrapper(bool is_machine) | |
128 : is_machine_(is_machine), | |
129 num_tries_when_msi_busy_(1) { | |
130 CORE_LOG(L3, (_T("[InstallerWrapper::InstallerWrapper]"))); | |
131 } | |
132 | |
133 InstallerWrapper::~InstallerWrapper() { | |
134 CORE_LOG(L3, (_T("[InstallerWrapper::~InstallerWrapper]"))); | |
135 } | |
136 | |
137 HRESULT InstallerWrapper::Initialize() { | |
138 NamedObjectAttributes lock_attr; | |
139 // TODO(omaha3): We might want to move this lock to the InstallManager. | |
140 GetNamedObjectAttributes(kInstallManagerSerializer, is_machine_, &lock_attr); | |
141 if (!installer_lock_.InitializeWithSecAttr(lock_attr.name, &lock_attr.sa)) { | |
142 OPT_LOG(LEVEL_ERROR, (_T("[Could not init Install Manager lock]"))); | |
143 return GOOPDATEINSTALL_E_FAILED_INIT_INSTALLER_LOCK; | |
144 } | |
145 | |
146 return S_OK; | |
147 } | |
148 | |
149 // result_* will be populated if the installer ran and exited, regardless of the | |
150 // return value. | |
151 // Assumes the call is protected by some mechanism providing exclusive access | |
152 // to the app's registry keys in Clients and ClientState. | |
153 HRESULT InstallerWrapper::InstallApp(HANDLE user_token, | |
154 const GUID& app_guid, | |
155 const CString& installer_path, | |
156 const CString& arguments, | |
157 const CString& installer_data, | |
158 const CString& language, | |
159 InstallerResultInfo* result_info) { | |
160 ASSERT1(result_info); | |
161 | |
162 HRESULT hr = DoInstallApp(user_token, | |
163 app_guid, | |
164 installer_path, | |
165 arguments, | |
166 installer_data, | |
167 language, | |
168 result_info); | |
169 | |
170 ASSERT1((SUCCEEDED(hr) && result_info->type == INSTALLER_RESULT_SUCCESS) || | |
171 (GOOPDATEINSTALL_E_INSTALLER_FAILED == hr && | |
172 (result_info->type == INSTALLER_RESULT_ERROR_MSI || | |
173 result_info->type == INSTALLER_RESULT_ERROR_SYSTEM || | |
174 result_info->type == INSTALLER_RESULT_ERROR_OTHER)) || | |
175 (GOOPDATEINSTALL_E_MSI_INSTALL_ALREADY_RUNNING == hr && | |
176 (result_info->type == INSTALLER_RESULT_ERROR_MSI || | |
177 result_info->type == INSTALLER_RESULT_ERROR_SYSTEM)) || | |
178 (FAILED(hr) && result_info->type == INSTALLER_RESULT_UNKNOWN)); | |
179 ASSERT1(!result_info->text.IsEmpty() == | |
180 (GOOPDATEINSTALL_E_INSTALLER_FAILED == hr || | |
181 GOOPDATEINSTALL_E_MSI_INSTALL_ALREADY_RUNNING == hr) || | |
182 SUCCEEDED(hr)); // Successes may or may not have messages. | |
183 | |
184 CORE_LOG(L3, (_T("[InstallApp result][0x%x][%s][type: %d][code: %d][%s][%s]"), | |
185 hr, GuidToString(app_guid), result_info->type, | |
186 result_info->code, result_info->text, | |
187 result_info->post_install_launch_command_line)); | |
188 return hr; | |
189 } | |
190 | |
191 CString InstallerWrapper::GetMessageForError(HRESULT error_code, | |
192 const CString& installer_filename, | |
193 const CString& language) { | |
194 CString message; | |
195 StringFormatter formatter(language); | |
196 | |
197 switch (error_code) { | |
198 case GOOPDATEINSTALL_E_FILENAME_INVALID: | |
199 VERIFY1(SUCCEEDED(formatter.FormatMessage(&message, | |
200 IDS_INVALID_INSTALLER_FILENAME, | |
201 installer_filename))); | |
202 break; | |
203 case GOOPDATEINSTALL_E_INSTALLER_FAILED_START: | |
204 VERIFY1(SUCCEEDED(formatter.LoadString(IDS_INSTALLER_FAILED_TO_START, | |
205 &message))); | |
206 break; | |
207 case GOOPDATEINSTALL_E_INSTALLER_TIMED_OUT: | |
208 VERIFY1(SUCCEEDED(formatter.LoadString(IDS_INSTALLER_TIMED_OUT, | |
209 &message))); | |
210 break; | |
211 case GOOPDATEINSTALL_E_INSTALLER_DID_NOT_WRITE_CLIENTS_KEY: | |
212 case GOOPDATEINSTALL_E_INSTALLER_DID_NOT_CHANGE_VERSION: | |
213 case GOOPDATEINSTALL_E_INSTALLER_VERSION_MISMATCH: | |
214 VERIFY1(SUCCEEDED(formatter.LoadString(IDS_INSTALL_FAILED, &message))); | |
215 break; | |
216 case GOOPDATEINSTALL_E_MSI_INSTALL_ALREADY_RUNNING: | |
217 VERIFY1(SUCCEEDED(formatter.LoadString(IDS_MSI_INSTALL_ALREADY_RUNNING, | |
218 &message))); | |
219 break; | |
220 case GOOPDATEINSTALL_E_INSTALLER_FAILED: | |
221 ASSERT(false, | |
222 (_T("[GetOmahaErrorTextToReport]") | |
223 _T("GOOPDATEINSTALL_E_INSTALLER_FAILED should never be reported ") | |
224 _T("directly. The installer error string should be reported."))); | |
225 VERIFY1(SUCCEEDED(formatter.LoadString(IDS_INSTALL_FAILED, &message))); | |
226 break; | |
227 case GOOPDATEINSTALL_E_INSTALLER_INTERNAL_ERROR: | |
228 default: | |
229 ASSERT(false, (_T("[GetOmahaErrorTextToReport]") | |
230 _T("[An Omaha error occurred that this method does not ") | |
231 _T("know how to report.][0x%08x]"), error_code)); | |
232 VERIFY1(SUCCEEDED(formatter.LoadString(IDS_INSTALL_FAILED, &message))); | |
233 break; | |
234 } | |
235 | |
236 ASSERT1(!message.IsEmpty()); | |
237 return message; | |
238 } | |
239 | |
240 void InstallerWrapper::set_num_tries_when_msi_busy( | |
241 int num_tries_when_msi_busy) { | |
242 ASSERT1(num_tries_when_msi_busy >= 1); | |
243 num_tries_when_msi_busy_ = num_tries_when_msi_busy; | |
244 } | |
245 | |
246 HRESULT InstallerWrapper::BuildCommandLineFromFilename( | |
247 const CString& file_path, | |
248 const CString& arguments, | |
249 const CString& installer_data, | |
250 CString* executable_path, | |
251 CString* command_line, | |
252 InstallerType* installer_type) { | |
253 CORE_LOG(L3, (_T("[BuildCommandLineFromFilename]"))); | |
254 | |
255 ASSERT1(executable_path); | |
256 ASSERT1(command_line); | |
257 ASSERT1(installer_type); | |
258 | |
259 *executable_path = _T(""); | |
260 *command_line = _T(""); | |
261 *installer_type = UNKNOWN_INSTALLER; | |
262 | |
263 // The app's installer owns the lifetime of installer data file if it has been | |
264 // created, so Omaha does not delete it. | |
265 CString enclosed_installer_data_file_path; | |
266 | |
267 VERIFY1(SUCCEEDED(goopdate_utils::WriteInstallerDataToTempFile( | |
268 installer_data, | |
269 &enclosed_installer_data_file_path))); | |
270 if (!enclosed_installer_data_file_path.IsEmpty()) { | |
271 EnclosePath(&enclosed_installer_data_file_path); | |
272 } | |
273 | |
274 // PathFindExtension returns the address of the trailing NUL character if an | |
275 // extension is not found. It does not return NULL. | |
276 const TCHAR* ext = ::PathFindExtension(file_path); | |
277 ASSERT1(ext); | |
278 if (*ext != _T('\0')) { | |
279 ext++; // Skip the period. | |
280 if (0 == lstrcmpi(ext, _T("exe"))) { | |
281 *executable_path = file_path; | |
282 if (enclosed_installer_data_file_path.IsEmpty()) { | |
283 *command_line = arguments; | |
284 } else { | |
285 SafeCStringFormat(command_line, _T("%s /installerdata=%s"), | |
286 arguments, | |
287 enclosed_installer_data_file_path); | |
288 } | |
289 *installer_type = CUSTOM_INSTALLER; | |
290 | |
291 CORE_LOG(L2, (_T("[BuildCommandLineFromFilename][exe][%s][%s]"), | |
292 *executable_path, *command_line)); | |
293 } else if (0 == lstrcmpi(ext, _T("msi"))) { | |
294 *executable_path = _T("msiexec"); | |
295 *command_line = BuildMsiCommandLine(arguments, | |
296 file_path, | |
297 enclosed_installer_data_file_path); | |
298 *installer_type = MSI_INSTALLER; | |
299 | |
300 CORE_LOG(L2, (_T("[BuildCommandLineFromFilename][msi][%s]"), | |
301 *command_line)); | |
302 } else { | |
303 *executable_path = _T(""); | |
304 *command_line = _T(""); | |
305 *installer_type = UNKNOWN_INSTALLER; | |
306 | |
307 OPT_LOG(LE, (_T("[Unsupported extension '%s' in %s]"), ext, file_path)); | |
308 return GOOPDATEINSTALL_E_FILENAME_INVALID; | |
309 } | |
310 } else { | |
311 OPT_LOG(LE, (_T("[No extension found in %s]"), file_path)); | |
312 return GOOPDATEINSTALL_E_FILENAME_INVALID; | |
313 } | |
314 | |
315 return S_OK; | |
316 } | |
317 | |
318 // Calls DoExecuteAndWaitForInstaller to do the work. If an MSI installer | |
319 // returns, ERROR_INSTALL_ALREADY_RUNNING waits and retries several times or | |
320 // until the installation succeeds. | |
321 HRESULT InstallerWrapper::ExecuteAndWaitForInstaller( | |
322 HANDLE user_token, | |
323 const GUID& app_guid, | |
324 const CString& executable_path, | |
325 const CString& command_line, | |
326 InstallerType installer_type, | |
327 const CString& language, | |
328 InstallerResultInfo* result_info) { | |
329 CORE_LOG(L3, (_T("[InstallerWrapper::ExecuteAndWaitForInstaller]"))); | |
330 ASSERT1(result_info); | |
331 ASSERT1(num_tries_when_msi_busy_ >= 1); | |
332 | |
333 ++metric_worker_install_execute_total; | |
334 if (MSI_INSTALLER == installer_type) { | |
335 ++metric_worker_install_execute_msi_total; | |
336 } | |
337 | |
338 // Run the installer, retrying if necessary. | |
339 int retry_delay = kMsiAlreadyRunningRetryDelayBaseMs; | |
340 int num_tries(0); | |
341 HRESULT hr = GOOPDATEINSTALL_E_MSI_INSTALL_ALREADY_RUNNING; | |
342 for (num_tries = 0; | |
343 hr == GOOPDATEINSTALL_E_MSI_INSTALL_ALREADY_RUNNING && | |
344 num_tries < num_tries_when_msi_busy_; | |
345 ++num_tries) { | |
346 // Reset the result info - it contains the previous error when retrying. | |
347 *result_info = InstallerResultInfo(); | |
348 | |
349 if (0 < num_tries) { | |
350 // Retrying - wait between attempts. | |
351 CORE_LOG(L1, (_T("[Retrying][%d]"), num_tries)); | |
352 ::Sleep(retry_delay); | |
353 retry_delay *= 2; // Double the retry delay next time. | |
354 } | |
355 | |
356 hr = DoExecuteAndWaitForInstaller(user_token, | |
357 app_guid, | |
358 executable_path, | |
359 command_line, | |
360 installer_type, | |
361 language, | |
362 result_info); | |
363 if (FAILED(hr)) { | |
364 CORE_LOG(LE, (_T("[DoExecuteAndWaitForInstaller failed][0x%08x]"), hr)); | |
365 return hr; | |
366 } | |
367 CORE_LOG(L1, (_T("[Installer result][%d][%d][%s]"), | |
368 result_info->type, result_info->code, result_info->text)); | |
369 ASSERT1(result_info->type != INSTALLER_RESULT_UNKNOWN); | |
370 | |
371 if ((INSTALLER_RESULT_ERROR_MSI == result_info->type || | |
372 INSTALLER_RESULT_ERROR_SYSTEM == result_info->type) && | |
373 ERROR_INSTALL_ALREADY_RUNNING == result_info->code) { | |
374 hr = GOOPDATEINSTALL_E_MSI_INSTALL_ALREADY_RUNNING; | |
375 } | |
376 } | |
377 | |
378 if (1 < num_tries) { | |
379 // Record metrics about the ERROR_INSTALL_ALREADY_RUNNING retries. | |
380 // TODO(omaha3): If we're willing to have a single metric for installs and | |
381 // updates, we can avoid knowing is_update. | |
382 #if 0 | |
383 if (!app_version_->is_update()) { | |
384 #endif | |
385 ++metric_worker_install_msi_in_progress_detected_install; | |
386 if (result_info->type == INSTALLER_RESULT_SUCCESS) { | |
387 ++metric_worker_install_msi_in_progress_retry_succeeded_install; | |
388 metric_worker_install_msi_in_progress_retry_succeeded_tries_install | |
389 = num_tries; | |
390 } | |
391 #if 0 | |
392 } else { | |
393 ++metric_worker_install_msi_in_progress_detected_update; | |
394 if (result_info->type == INSTALLER_RESULT_SUCCESS) { | |
395 ++metric_worker_install_msi_in_progress_retry_succeeded_update; | |
396 metric_worker_install_msi_in_progress_retry_succeeded_tries_update | |
397 = num_tries; | |
398 } | |
399 } | |
400 #endif | |
401 } | |
402 | |
403 return hr; | |
404 } | |
405 | |
406 HRESULT InstallerWrapper::DoExecuteAndWaitForInstaller( | |
407 HANDLE user_token, | |
408 const GUID& app_guid, | |
409 const CString& executable_path, | |
410 const CString& command_line, | |
411 InstallerType installer_type, | |
412 const CString& language, | |
413 InstallerResultInfo* result_info) { | |
414 OPT_LOG(L1, (_T("[Running installer][%s][%s][%s]"), | |
415 executable_path, command_line, GuidToString(app_guid))); | |
416 ASSERT1(result_info); | |
417 | |
418 AppManager::Instance()->ClearInstallerResultApiValues(app_guid); | |
419 | |
420 Process p(executable_path, NULL); | |
421 HRESULT hr = p.Start(command_line, user_token); | |
422 if (FAILED(hr)) { | |
423 OPT_LOG(LE, (_T("[p.Start fail][hr][%s][%s]"), | |
424 hr, executable_path, command_line)); | |
425 set_error_extra_code1(static_cast<int>(hr)); | |
426 return GOOPDATEINSTALL_E_INSTALLER_FAILED_START; | |
427 } | |
428 | |
429 // TODO(omaha): InstallerWrapper should not special case Omaha. It is better | |
430 // to have an abstraction such as waiting or not for the installer to | |
431 // exit and let the App state machine special case Omaha. It's too low level | |
432 // to make a decision like this in the InstallerWrapper. Same for all | |
433 // kinds of tests on the call stack above this call that the app_guid is | |
434 // Omaha's guid. | |
435 if (::IsEqualGUID(app_guid, kGoopdateGuid)) { | |
436 // Do not wait for the installer when installing Omaha. | |
437 result_info->type = INSTALLER_RESULT_SUCCESS; | |
438 return S_OK; | |
439 } | |
440 | |
441 if (!p.WaitUntilDead(kInstallerCompleteIntervalMs)) { | |
442 OPT_LOG(LEVEL_WARNING, (_T("[Installer has timed out]") | |
443 _T("[%s][%s]"), executable_path, command_line)); | |
444 return GOOPDATEINSTALL_E_INSTALLER_TIMED_OUT; | |
445 } | |
446 | |
447 hr = GetInstallerResult(app_guid, | |
448 installer_type, | |
449 p, | |
450 language, | |
451 result_info); | |
452 if (FAILED(hr)) { | |
453 CORE_LOG(LEVEL_ERROR, (_T("[GetInstallerResult failed][0x%08x]"), hr)); | |
454 return hr; | |
455 } | |
456 | |
457 if (result_info->type != INSTALLER_RESULT_SUCCESS) { | |
458 OPT_LOG(LE, (_T("[Installer failed][%s][%s][%u]"), | |
459 executable_path, command_line, result_info->code)); | |
460 } | |
461 | |
462 return S_OK; | |
463 } | |
464 | |
465 HRESULT InstallerWrapper::GetInstallerResult(const GUID& app_guid, | |
466 InstallerType installer_type, | |
467 const Process& p, | |
468 const CString& language, | |
469 InstallerResultInfo* result_info) { | |
470 CORE_LOG(L3, (_T("[InstallerWrapper::GetInstallerResult]"))); | |
471 ASSERT1(result_info); | |
472 | |
473 uint32 exit_code = 0; | |
474 HRESULT hr = GetInstallerExitCode(p, &exit_code); | |
475 if (FAILED(hr)) { | |
476 CORE_LOG(LEVEL_ERROR, (_T("[GetInstallerExitCode failed][0x%08x]"), hr)); | |
477 return hr; | |
478 } | |
479 | |
480 GetInstallerResultHelper(app_guid, | |
481 installer_type, | |
482 exit_code, | |
483 language, | |
484 result_info); | |
485 return S_OK; | |
486 } | |
487 | |
488 // The default InstallerResult behavior can be overridden in the registry. | |
489 // By default, error_code is the exit code. For some InstallerResults, it | |
490 // can be overridden by InstallerError in the registry. | |
491 // The success string cannot be overridden. | |
492 void InstallerWrapper::GetInstallerResultHelper( | |
493 const GUID& app_guid, | |
494 InstallerType installer_type, | |
495 uint32 exit_code, | |
496 const CString& language, | |
497 InstallerResultInfo* result_info) { | |
498 ASSERT1(result_info); | |
499 | |
500 AppManager::InstallerResult installer_result = | |
501 AppManager::INSTALLER_RESULT_DEFAULT; | |
502 InstallerResultInfo result; | |
503 | |
504 result.code = exit_code; | |
505 | |
506 AppManager& app_manager = *AppManager::Instance(); | |
507 app_manager.ReadInstallerResultApiValues( | |
508 app_guid, | |
509 &installer_result, | |
510 &result.code, | |
511 &result.extra_code1, | |
512 &result.text, | |
513 &result.post_install_launch_command_line); | |
514 OPT_LOG(L1, (_T("[InstallerResult][%s][%u]"), | |
515 GuidToString(app_guid), installer_result)); | |
516 | |
517 switch (installer_result) { | |
518 case AppManager::INSTALLER_RESULT_SUCCESS: | |
519 result.type = INSTALLER_RESULT_SUCCESS; | |
520 // TODO(omaha3): Support custom success messages. | |
521 break; | |
522 case AppManager::INSTALLER_RESULT_FAILED_CUSTOM_ERROR: | |
523 result.type = INSTALLER_RESULT_ERROR_OTHER; | |
524 break; | |
525 case AppManager::INSTALLER_RESULT_FAILED_MSI_ERROR: | |
526 result.type = INSTALLER_RESULT_ERROR_MSI; | |
527 break; | |
528 case AppManager::INSTALLER_RESULT_FAILED_SYSTEM_ERROR: | |
529 result.type = INSTALLER_RESULT_ERROR_SYSTEM; | |
530 break; | |
531 case AppManager::INSTALLER_RESULT_EXIT_CODE: | |
532 ASSERT(result.code == exit_code, (_T("InstallerError overridden"))); | |
533 if (0 == exit_code) { | |
534 result.type = INSTALLER_RESULT_SUCCESS; | |
535 result.code = 0; | |
536 } else { | |
537 switch (installer_type) { | |
538 case MSI_INSTALLER: | |
539 result.type = INSTALLER_RESULT_ERROR_MSI; | |
540 break; | |
541 case UNKNOWN_INSTALLER: | |
542 case CUSTOM_INSTALLER: | |
543 case MAX_INSTALLER: | |
544 default: | |
545 result.type = INSTALLER_RESULT_ERROR_OTHER; | |
546 break; | |
547 } | |
548 } | |
549 break; | |
550 case AppManager::INSTALLER_RESULT_MAX: | |
551 default: | |
552 ASSERT1(false); | |
553 break; | |
554 } | |
555 | |
556 // Handle the reboot required case. | |
557 if ((INSTALLER_RESULT_ERROR_MSI == result.type || | |
558 INSTALLER_RESULT_ERROR_SYSTEM == result.type) && | |
559 (ERROR_SUCCESS_REBOOT_REQUIRED == result.code)) { | |
560 // Reboot takes precedence over other actions. | |
561 result.type = INSTALLER_RESULT_SUCCESS; | |
562 result.code = 0; | |
563 result.post_install_action = POST_INSTALL_ACTION_REBOOT; | |
564 } else if (!result.post_install_launch_command_line.IsEmpty()) { | |
565 result.post_install_action = POST_INSTALL_ACTION_LAUNCH_COMMAND; | |
566 } | |
567 | |
568 // InstallerResultInfo status has been finalized. Make sure all errors | |
569 // have error strings. | |
570 switch (result.type) { | |
571 case INSTALLER_RESULT_SUCCESS: | |
572 break; | |
573 | |
574 case INSTALLER_RESULT_ERROR_MSI: | |
575 case INSTALLER_RESULT_ERROR_SYSTEM: | |
576 GetSystemErrorString(result.code, language, &result.text); | |
577 break; | |
578 | |
579 case INSTALLER_RESULT_ERROR_OTHER: | |
580 if (result.text.IsEmpty()) { | |
581 result.text.FormatMessage( | |
582 IDS_INSTALLER_FAILED_NO_MESSAGE, | |
583 FormatErrorCode(result.code)); | |
584 } | |
585 break; | |
586 | |
587 case INSTALLER_RESULT_UNKNOWN: | |
588 default: | |
589 ASSERT1(false); | |
590 } | |
591 | |
592 // TODO(omaha3): Serialize InstallerResultInfo. | |
593 // OPT_LOG(L1, (_T("[%s]"), result_info->ToString())); | |
594 *result_info = result; | |
595 } | |
596 | |
597 // TODO(omaha3): Consider moving this method out of this class, maybe into | |
598 // InstallManager. | |
599 HRESULT InstallerWrapper::CheckApplicationRegistration( | |
600 const GUID& app_guid, | |
601 const CString& registered_version, | |
602 const CString& expected_version, | |
603 const CString& previous_version, | |
604 bool is_update) const { | |
605 const CString app_guid_string = GuidToString(app_guid); | |
606 CORE_LOG(L2, (_T("[InstallerWrapper::CheckApplicationRegistration][%s]"), | |
607 app_guid_string)); | |
608 ASSERT(!::IsEqualGUID(kGoopdateGuid, app_guid), | |
609 (_T("Probably do not want to call this method for Omaha"))); | |
610 | |
611 if (registered_version.IsEmpty()) { | |
612 return GOOPDATEINSTALL_E_INSTALLER_DID_NOT_WRITE_CLIENTS_KEY; | |
613 } | |
614 | |
615 CORE_LOG(L2, (_T("[CheckApplicationRegistration]") | |
616 _T("[guid=%s][registered=%s][expected=%s][previous=%s]"), | |
617 app_guid_string, registered_version, expected_version, | |
618 previous_version)); | |
619 | |
620 if (!expected_version.IsEmpty() && registered_version != expected_version) { | |
621 OPT_LOG(LE, (_T("[Registered version does not match expected][%s][%s][%s]"), | |
622 app_guid_string, registered_version, expected_version)); | |
623 // This is expected if a newer version is already installed. Do not fail | |
624 // here in that case. This only works for four-element version strings. | |
625 // If the version format is not recognized, VersionFromString() returns 0. | |
626 | |
627 ULONGLONG registered_version_number = VersionFromString(registered_version); | |
628 ULONGLONG expected_version_number = VersionFromString(expected_version); | |
629 | |
630 // Check that the version did not change, the registered version is newer, | |
631 // and neither VersionFromString() call failed. | |
632 if (is_update && registered_version != previous_version || | |
633 registered_version_number < expected_version_number || | |
634 !registered_version_number || | |
635 !expected_version_number) { | |
636 return GOOPDATEINSTALL_E_INSTALLER_VERSION_MISMATCH; | |
637 } | |
638 | |
639 CORE_LOG(L1, (_T("[Newer version already registered]"))); | |
640 } | |
641 | |
642 if (is_update && previous_version == registered_version) { | |
643 ASSERT1(!previous_version.IsEmpty()); | |
644 ASSERT(expected_version.IsEmpty() || | |
645 VersionFromString(expected_version) > | |
646 VersionFromString(previous_version) || | |
647 VersionFromString(expected_version) == 0 || | |
648 VersionFromString(previous_version) == 0, | |
649 (_T("expected_version should be > previous_version when ") | |
650 _T("is_update - possibly a bad update rule."))); | |
651 | |
652 OPT_LOG(LE, (_T("[Installer did not change version][%s][%s]"), | |
653 app_guid_string, previous_version)); | |
654 return GOOPDATEINSTALL_E_INSTALLER_DID_NOT_CHANGE_VERSION; | |
655 } | |
656 | |
657 return S_OK; | |
658 } | |
659 | |
660 // Assumes installer_lock_ has been initialized. | |
661 HRESULT InstallerWrapper::DoInstallApp(HANDLE user_token, | |
662 const GUID& app_guid, | |
663 const CString& installer_path, | |
664 const CString& arguments, | |
665 const CString& installer_data, | |
666 const CString& language, | |
667 InstallerResultInfo* result_info) { | |
668 CORE_LOG(L1, (_T("[InstallerWrapper::DoInstallApp][%s][%s][%s]"), | |
669 GuidToString(app_guid), installer_path, arguments)); | |
670 ASSERT1(result_info); | |
671 | |
672 CString executable_path; | |
673 CString command_line; | |
674 InstallerType installer_type = UNKNOWN_INSTALLER; | |
675 | |
676 // TODO(omaha): Remove when http://b/1443404 is addressed. | |
677 const TCHAR* const kChromeGuid = _T("{8A69D345-D564-463C-AFF1-A69D9E530F96}"); | |
678 const TCHAR* const kChromePerMachineArg = _T("--system-level"); | |
679 CString modified_arguments = arguments; | |
680 if (kChromeGuid == GuidToString(app_guid) && is_machine_) { | |
681 modified_arguments.AppendFormat(_T(" %s"), kChromePerMachineArg); | |
682 } | |
683 | |
684 HRESULT hr = BuildCommandLineFromFilename(installer_path, | |
685 modified_arguments, | |
686 installer_data, | |
687 &executable_path, | |
688 &command_line, | |
689 &installer_type); | |
690 | |
691 if (FAILED(hr)) { | |
692 CORE_LOG(LW, (_T("[BuildCommandLineFromFilename failed][0x%08x]"), hr)); | |
693 ASSERT1(GOOPDATEINSTALL_E_FILENAME_INVALID == hr); | |
694 return hr; | |
695 } | |
696 | |
697 // Acquire the global lock here. This will ensure that we are the only | |
698 // installer running of the multiple goopdates. | |
699 __mutexBlock(installer_lock_) { | |
700 hr = ExecuteAndWaitForInstaller(user_token, | |
701 app_guid, | |
702 executable_path, | |
703 command_line, | |
704 installer_type, | |
705 language, | |
706 result_info); | |
707 } | |
708 | |
709 if (FAILED(hr)) { | |
710 CORE_LOG(LE, (_T("[ExecuteAndWaitForInstaller failed][0x%08x][%s]"), | |
711 hr, GuidToString(app_guid))); | |
712 return hr; | |
713 } | |
714 | |
715 if (result_info->type != INSTALLER_RESULT_SUCCESS) { | |
716 CORE_LOG(LE, (_T("[Installer failed][%d]"), result_info->type)); | |
717 return GOOPDATEINSTALL_E_INSTALLER_FAILED; | |
718 } | |
719 | |
720 return S_OK; | |
721 } | |
722 | |
723 } // namespace omaha | |
OLD | NEW |