| 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 |