| OLD | NEW |
| (Empty) |
| 1 // Copyright 2009-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/install_manager.h" | |
| 17 #include <vector> | |
| 18 #include "omaha/base/debug.h" | |
| 19 #include "omaha/base/error.h" | |
| 20 #include "omaha/base/logging.h" | |
| 21 #include "omaha/base/path.h" | |
| 22 #include "omaha/base/safe_format.h" | |
| 23 #include "omaha/base/scope_guard.h" | |
| 24 #include "omaha/base/synchronized.h" | |
| 25 #include "omaha/base/utils.h" | |
| 26 #include "omaha/common/config_manager.h" | |
| 27 #include "omaha/common/const_cmd_line.h" | |
| 28 #include "omaha/common/install_manifest.h" | |
| 29 #include "omaha/goopdate/app_manager.h" | |
| 30 #include "omaha/goopdate/installer_wrapper.h" | |
| 31 #include "omaha/goopdate/model.h" | |
| 32 #include "omaha/goopdate/server_resource.h" | |
| 33 #include "omaha/goopdate/string_formatter.h" | |
| 34 | |
| 35 namespace omaha { | |
| 36 | |
| 37 namespace { | |
| 38 | |
| 39 // Number of tries when the MSI service is busy. | |
| 40 // Updates are silent so we can wait longer. | |
| 41 const int kNumMsiAlreadyRunningInteractiveMaxTries = 4; // Up to 35 seconds. | |
| 42 const int kNumMsiAlreadyRunningSilentMaxTries = 7; // Up to 6.25 minutes. | |
| 43 | |
| 44 // TODO(omaha): there can be more install actions for each install event. | |
| 45 bool GetInstallActionForEvent( | |
| 46 const std::vector<xml::InstallAction>& install_actions, | |
| 47 xml::InstallAction::InstallEvent install_event, | |
| 48 xml::InstallAction* action) { | |
| 49 ASSERT1(action); | |
| 50 | |
| 51 for (size_t i = 0; i < install_actions.size(); ++i) { | |
| 52 if (install_actions[i].install_event == install_event) { | |
| 53 *action = install_actions[i]; | |
| 54 return true; | |
| 55 } | |
| 56 } | |
| 57 | |
| 58 return false; | |
| 59 } | |
| 60 | |
| 61 } // namespace | |
| 62 | |
| 63 InstallManager::InstallManager(const Lockable* model_lock, bool is_machine) | |
| 64 : model_lock_(model_lock), | |
| 65 is_machine_(is_machine) { | |
| 66 CORE_LOG(L3, (_T("[InstallManager::InstallManager][%d]"), is_machine_)); | |
| 67 | |
| 68 install_working_dir_ = | |
| 69 is_machine ? | |
| 70 ConfigManager::Instance()->GetMachineInstallWorkingDir() : | |
| 71 ConfigManager::Instance()->GetUserInstallWorkingDir(); | |
| 72 CORE_LOG(L3, (_T("[install_working_dir][%s]"), install_working_dir())); | |
| 73 | |
| 74 VERIFY1(SUCCEEDED(DeleteDirectory(install_working_dir_))); | |
| 75 VERIFY1(SUCCEEDED(CreateDir(install_working_dir_, NULL))); | |
| 76 | |
| 77 installer_wrapper_.reset(new InstallerWrapper(is_machine_)); | |
| 78 } | |
| 79 | |
| 80 InstallManager::~InstallManager() { | |
| 81 CORE_LOG(L3, (_T("[InstallManager::~InstallManager]"))); | |
| 82 } | |
| 83 | |
| 84 HRESULT InstallManager::Initialize() { | |
| 85 return installer_wrapper_->Initialize(); | |
| 86 } | |
| 87 | |
| 88 CString InstallManager::install_working_dir() const { | |
| 89 __mutexScope(model_lock_); | |
| 90 return install_working_dir_; | |
| 91 } | |
| 92 | |
| 93 // For each app, set the state to STATE_INSTALLING, install it, and update the | |
| 94 // state of the model after it completes. | |
| 95 void InstallManager::InstallApp(App* app, const CString& dir) { | |
| 96 CORE_LOG(L3, (_T("[InstallManager::InstallApp][0x%p]"), app)); | |
| 97 ASSERT1(app); | |
| 98 | |
| 99 const ConfigManager& cm = *ConfigManager::Instance(); | |
| 100 // TODO(omaha): Since we don't currently have is_manual, check the least | |
| 101 // restrictive case of true. It would be nice if we had is_manual. We'll see. | |
| 102 ASSERT(SUCCEEDED(app->CheckGroupPolicy()), | |
| 103 (_T("Installing/updating app for which this is not allowed."))); | |
| 104 ASSERT(app->is_eula_accepted() || | |
| 105 app->is_install() && app->app_bundle()->is_offline_install(), | |
| 106 (_T("update/online install of app for which EULA is not accepted."))); | |
| 107 | |
| 108 // TODO(omaha3): This needs to be set/passed per app/bundle. | |
| 109 const int priority = app->app_bundle()->priority(); | |
| 110 const int num_tries = (priority < INSTALL_PRIORITY_HIGH) ? | |
| 111 kNumMsiAlreadyRunningSilentMaxTries : | |
| 112 kNumMsiAlreadyRunningInteractiveMaxTries; | |
| 113 installer_wrapper_->set_num_tries_when_msi_busy(num_tries); | |
| 114 | |
| 115 AppVersion* next_version = app->next_version(); | |
| 116 ASSERT1(app->app_bundle()->is_machine() == is_machine_); | |
| 117 | |
| 118 const CString current_version_string = app->current_version()->version(); | |
| 119 | |
| 120 HANDLE primary_token(app->app_bundle()->primary_token()); | |
| 121 | |
| 122 HRESULT hr = InstallApp(is_machine_, | |
| 123 primary_token, | |
| 124 current_version_string, | |
| 125 *model_lock_, | |
| 126 installer_wrapper_.get(), | |
| 127 app, | |
| 128 dir); | |
| 129 if (FAILED(hr)) { | |
| 130 CORE_LOG(LE, (_T("[InstallApp failed][0x%p][0x%08x]"), app, hr)); | |
| 131 } | |
| 132 | |
| 133 app->LogTextAppendFormat(_T("Install result=0x%08x"), hr); | |
| 134 | |
| 135 ASSERT1(FAILED(hr) == (app->state() == STATE_ERROR)); | |
| 136 } | |
| 137 | |
| 138 HRESULT InstallManager::InstallApp(bool is_machine, | |
| 139 HANDLE user_token, | |
| 140 const CString& existing_version, | |
| 141 const Lockable& model_lock, | |
| 142 InstallerWrapper* installer_wrapper, | |
| 143 App* app, | |
| 144 const CString& dir) { | |
| 145 UNREFERENCED_PARAMETER(is_machine); | |
| 146 ASSERT1(installer_wrapper); | |
| 147 ASSERT1(app); | |
| 148 | |
| 149 const bool is_update = app->is_update(); | |
| 150 | |
| 151 CString display_name; | |
| 152 GUID app_guid = {0}; | |
| 153 CString installer_path; | |
| 154 // TODO(omaha3): Consider generating the installerdata file external to the | |
| 155 // InstallerWrapper and just adding the path to the arguments. | |
| 156 CString manifest_arguments; | |
| 157 CString installer_data; | |
| 158 CString expected_version; | |
| 159 | |
| 160 AppManager& app_manager = *AppManager::Instance(); | |
| 161 __mutexScope(app_manager.GetRegistryStableStateLock()); | |
| 162 | |
| 163 // TODO(omaha): If this does not get much simpler, extract method. | |
| 164 AppVersion& next_version = *(app->next_version()); | |
| 165 ASSERT1(app->app_bundle()->is_machine() == is_machine); | |
| 166 | |
| 167 CString language = app->app_bundle()->display_language(); | |
| 168 | |
| 169 // TODO(omaha): review the need for locking below. | |
| 170 __mutexBlock(model_lock) { | |
| 171 app->Installing(); | |
| 172 | |
| 173 app_guid = app->app_guid(); | |
| 174 | |
| 175 // The first package is always the Package Manager for the app. | |
| 176 // TODO(omaha3): Use program_to_run here instead of the installer_path. This | |
| 177 // will introduce complexity such as having to find the full path and | |
| 178 // handling the case where the file is not in the cache. | |
| 179 ASSERT1(next_version.GetNumberOfPackages() > 0); | |
| 180 if (next_version.GetNumberOfPackages() <= 0) { | |
| 181 HRESULT hr = E_FAIL; | |
| 182 const CString message = InstallerWrapper::GetMessageForError(hr, | |
| 183 CString(), | |
| 184 language); | |
| 185 app->Error(ErrorContext(hr), message); | |
| 186 return hr; | |
| 187 } | |
| 188 const Package& package_manager = *(next_version.GetPackage(0)); | |
| 189 installer_path = ConcatenatePath(dir, package_manager.filename()); | |
| 190 | |
| 191 xml::InstallAction action; | |
| 192 const bool is_event_found = GetInstallActionForEvent( | |
| 193 next_version.install_manifest()->install_actions, | |
| 194 is_update ? xml::InstallAction::kUpdate : xml::InstallAction::kInstall, | |
| 195 &action); | |
| 196 ASSERT1(is_event_found); | |
| 197 if (is_event_found) { | |
| 198 manifest_arguments = action.program_arguments; | |
| 199 | |
| 200 // If this is an Omaha self-update, append a switch to the argument list | |
| 201 // to set the session ID. | |
| 202 if (is_update && ::IsEqualGUID(app_guid, kGoopdateGuid)) { | |
| 203 SafeCStringAppendFormat(&manifest_arguments, _T(" /%s \"%s\""), | |
| 204 kCmdLineSessionId, | |
| 205 app->app_bundle()->session_id()); | |
| 206 } | |
| 207 } | |
| 208 | |
| 209 installer_data = app->GetInstallData(); | |
| 210 | |
| 211 expected_version = next_version.install_manifest()->version; | |
| 212 | |
| 213 // TODO(omaha3): All app key registry writes and reads must be protected by | |
| 214 // some lock to prevent race conditions caused by multiple bundles | |
| 215 // installing the same app. This includes while writing the pre-install | |
| 216 // data, while the installer is running, and while checking application | |
| 217 // registration (basically the rest of this method). An app-specific lock | |
| 218 // similar to the app install lock in Omaha 2 seems to be appropriate. | |
| 219 // (Omaha 2 only took that during install - not updates - though.) | |
| 220 // Some app installers may also check the state of other apps (i.e. to check | |
| 221 // for version compatibility). Should we protect this case as well? | |
| 222 // Is it safest to use the installer lock for this? What is the performance | |
| 223 // impact. If we do use the installer lock, it would need to be acquired | |
| 224 // here instead of in the InstallerWrapper::InstallApp path. | |
| 225 if (!is_update) { | |
| 226 HRESULT hr = app_manager.WritePreInstallData(*app); | |
| 227 if (FAILED(hr)) { | |
| 228 CORE_LOG(LE, (_T("[AppManager::WritePreInstallData failed][0x%08x]"))); | |
| 229 const CString message = | |
| 230 InstallerWrapper::GetMessageForError(hr, CString(), language); | |
| 231 app->Error(ErrorContext(hr), message); | |
| 232 return hr; | |
| 233 } | |
| 234 } | |
| 235 } | |
| 236 | |
| 237 OPT_LOG(L1, (_T("[Installing][%s][%s][%s][%s][%s]"), | |
| 238 app->display_name(), | |
| 239 GuidToString(app_guid), | |
| 240 installer_path, | |
| 241 manifest_arguments, | |
| 242 installer_data)); | |
| 243 | |
| 244 InstallerResultInfo result_info; | |
| 245 | |
| 246 HRESULT hr = installer_wrapper->InstallApp(user_token, | |
| 247 app_guid, | |
| 248 installer_path, | |
| 249 manifest_arguments, | |
| 250 installer_data, | |
| 251 language, | |
| 252 &result_info); | |
| 253 | |
| 254 OPT_LOG(L1, (_T("[InstallApp returned][0x%x][%s][type:%d][code: %d][%s][%s]"), | |
| 255 hr, GuidToString(app_guid), result_info.type, result_info.code, | |
| 256 result_info.text, result_info.post_install_launch_command_line)); | |
| 257 | |
| 258 __mutexScope(model_lock); | |
| 259 | |
| 260 if (SUCCEEDED(hr)) { | |
| 261 ASSERT1(result_info.type == INSTALLER_RESULT_SUCCESS); | |
| 262 // Skip checking application registration for Omaha self-updates because the | |
| 263 // installer has not completed. | |
| 264 if (!::IsEqualGUID(kGoopdateGuid, app_guid)) { | |
| 265 hr = app_manager.ReadInstallerRegistrationValues(app); | |
| 266 if (SUCCEEDED(hr)) { | |
| 267 hr = installer_wrapper->CheckApplicationRegistration( | |
| 268 app_guid, | |
| 269 next_version.version(), | |
| 270 expected_version, | |
| 271 existing_version, | |
| 272 is_update); | |
| 273 } | |
| 274 } | |
| 275 } else { | |
| 276 ASSERT1(result_info.type != INSTALLER_RESULT_SUCCESS); | |
| 277 ASSERT1(!result_info.text.IsEmpty() || | |
| 278 result_info.type == INSTALLER_RESULT_UNKNOWN); | |
| 279 } | |
| 280 | |
| 281 if (FAILED(hr)) { | |
| 282 // If we failed the install job and the product wasn't registered, it's safe | |
| 283 // to delete the ClientState key. We need to remove it because it contains | |
| 284 // data like "ap", browsertype, language, etc. that need to be cleaned up in | |
| 285 // case user tries to install again in the future. | |
| 286 if (!is_update && !app_manager.IsAppRegistered(app->app_guid())) { | |
| 287 app_manager.RemoveClientState(app->app_guid()); | |
| 288 } | |
| 289 | |
| 290 if (hr == GOOPDATEINSTALL_E_INSTALLER_FAILED) { | |
| 291 ASSERT1(!result_info.text.IsEmpty()); | |
| 292 app->ReportInstallerComplete(result_info); | |
| 293 } else { | |
| 294 // TODO(omaha3): If we end up having the installer filename above, pass it | |
| 295 // instead of installer_path. | |
| 296 const CString message = InstallerWrapper::GetMessageForError( | |
| 297 hr, installer_path, language); | |
| 298 app->Error(ErrorContext(hr), message); | |
| 299 } | |
| 300 | |
| 301 return hr; | |
| 302 } | |
| 303 | |
| 304 PopulateSuccessfulInstallResultInfo(app, &result_info); | |
| 305 | |
| 306 app->ReportInstallerComplete(result_info); | |
| 307 return S_OK; | |
| 308 } | |
| 309 | |
| 310 // Call this function only when installer succeeded. | |
| 311 // This function sets the post install action and url based on installer result | |
| 312 // info and app manifest. Note that the action may already have been set by | |
| 313 // InstallerWrapper::GetInstallerResultHelper() in some cases. This function | |
| 314 // only sets the action when necessary. | |
| 315 void InstallManager::PopulateSuccessfulInstallResultInfo( | |
| 316 const App* app, | |
| 317 InstallerResultInfo* result_info) { | |
| 318 ASSERT1(result_info); | |
| 319 ASSERT1(result_info->type == INSTALLER_RESULT_SUCCESS); | |
| 320 ASSERT1(app); | |
| 321 ASSERT1(app->next_version()->install_manifest()); | |
| 322 | |
| 323 xml::InstallAction action; | |
| 324 if (GetInstallActionForEvent( | |
| 325 app->next_version()->install_manifest()->install_actions, | |
| 326 xml::InstallAction::kPostInstall, | |
| 327 &action)) { | |
| 328 result_info->post_install_url = action.success_url; | |
| 329 | |
| 330 if (result_info->post_install_launch_command_line.IsEmpty() && | |
| 331 action.success_action == SUCCESS_ACTION_EXIT_SILENTLY_ON_LAUNCH_CMD) { | |
| 332 CORE_LOG(LW, (_T("[Success action specified launchcmd, but cmd empty]"))); | |
| 333 } | |
| 334 | |
| 335 if (result_info->post_install_action == | |
| 336 POST_INSTALL_ACTION_LAUNCH_COMMAND && | |
| 337 action.success_action == SUCCESS_ACTION_EXIT_SILENTLY_ON_LAUNCH_CMD) { | |
| 338 result_info->post_install_action = | |
| 339 POST_INSTALL_ACTION_EXIT_SILENTLY_ON_LAUNCH_COMMAND; | |
| 340 } else if (result_info->post_install_action == | |
| 341 POST_INSTALL_ACTION_DEFAULT) { | |
| 342 if (action.success_action == SUCCESS_ACTION_EXIT_SILENTLY) { | |
| 343 result_info->post_install_action = POST_INSTALL_ACTION_EXIT_SILENTLY; | |
| 344 } else if (!result_info->post_install_url.IsEmpty()) { | |
| 345 result_info->post_install_action = action.terminate_all_browsers ? | |
| 346 POST_INSTALL_ACTION_RESTART_ALL_BROWSERS : | |
| 347 POST_INSTALL_ACTION_RESTART_BROWSER; | |
| 348 } | |
| 349 } | |
| 350 } | |
| 351 | |
| 352 // Load message based on post install action if not overridden. | |
| 353 if (result_info->text.IsEmpty()) { | |
| 354 StringFormatter formatter(app->app_bundle()->display_language()); | |
| 355 VERIFY1(SUCCEEDED(formatter.LoadString( | |
| 356 IDS_APPLICATION_INSTALLED_SUCCESSFULLY, | |
| 357 &result_info->text))); | |
| 358 } | |
| 359 } | |
| 360 | |
| 361 } // namespace omaha | |
| OLD | NEW |