1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "apps/app_host/binaries_installer.h" 6 7#include "base/logging.h" 8#include "base/threading/platform_thread.h" 9#include "base/win/scoped_bstr.h" 10#include "base/win/scoped_com_initializer.h" 11#include "base/win/scoped_comptr.h" 12#include "google_update/google_update_idl.h" 13 14 15namespace app_host { 16 17// Helpers -------------------------------------------------------------------- 18 19namespace { 20 21const wchar_t kAppHostAppId[] = L"{FDA71E6F-AC4C-4a00-8B70-9958A68906BF}"; 22const wchar_t kBinariesAppId[] = L"{4DC8B4CA-1BDA-483e-B5FA-D3C12E15B62D}"; 23const int kInstallationPollingIntervalMs = 50; 24 25HRESULT CreateInstalledApp(IAppBundle* app_bundle, 26 const wchar_t* app_guid, 27 IApp** app) { 28 base::win::ScopedComPtr<IDispatch> idispatch; 29 HRESULT hr = app_bundle->createInstalledApp(base::win::ScopedBstr(app_guid), 30 idispatch.Receive()); 31 if (FAILED(hr)) { 32 LOG(ERROR) << "Failed to configure App Bundle: " << hr; 33 return hr; 34 } 35 36 base::win::ScopedComPtr<IApp> temp_app; 37 hr = temp_app.QueryFrom(idispatch); 38 if (FAILED(hr)) { 39 LOG(ERROR) << "Unexpected error querying IApp from " 40 << "IAppBundle->createInstalledApp return value: " << hr; 41 } else { 42 *app = temp_app.Detach(); 43 } 44 return hr; 45} 46 47HRESULT GetAppHostApValue(IGoogleUpdate3* update3, 48 IAppBundle* app_bundle, 49 BSTR* ap_value) { 50 base::win::ScopedComPtr<IApp> app; 51 HRESULT hr = CreateInstalledApp(app_bundle, kAppHostAppId, app.Receive()); 52 if (FAILED(hr)) 53 return hr; 54 55 hr = app->get_ap(ap_value); 56 if (FAILED(hr)) 57 LOG(ERROR) << "Failed to get the App Launcher AP value."; 58 return hr; 59} 60 61HRESULT GetCurrentState(IApp* app, 62 ICurrentState** current_state, 63 CurrentState* state_value) { 64 base::win::ScopedComPtr<IDispatch> idispatch; 65 HRESULT hr = app->get_currentState(idispatch.Receive()); 66 if (FAILED(hr)) { 67 LOG(ERROR) << "Failed to get App Bundle state: " << hr; 68 return hr; 69 } 70 71 base::win::ScopedComPtr<ICurrentState> temp_current_state; 72 hr = temp_current_state.QueryFrom(idispatch); 73 if (FAILED(hr)) { 74 LOG(ERROR) << "Unexpected error querying ICurrentState from " 75 << "IApp::get_currentState return value: " << hr; 76 return hr; 77 } 78 79 LONG long_state_value; 80 hr = temp_current_state->get_stateValue(&long_state_value); 81 if (SUCCEEDED(hr)) { 82 *state_value = static_cast<CurrentState>(long_state_value); 83 *current_state = temp_current_state.Detach(); 84 } else { 85 LOG(ERROR) << "Failed to get App Bundle state value: " << hr; 86 } 87 return hr; 88} 89 90bool CheckIsBusy(IAppBundle* app_bundle, HRESULT* hr) { 91 VARIANT_BOOL variant_is_busy = VARIANT_TRUE; 92 *hr = app_bundle->isBusy(&variant_is_busy); 93 if (FAILED(*hr)) 94 LOG(ERROR) << "Failed to check app_bundle->isBusy: " << *hr; 95 return (variant_is_busy == VARIANT_TRUE); 96} 97 98void OnUpdateAvailable(IAppBundle* app_bundle, HRESULT* hr) { 99 // If the app bundle is busy we will just wait some more. 100 if (CheckIsBusy(app_bundle, hr) || FAILED(*hr)) 101 return; 102 *hr = app_bundle->download(); 103 if (FAILED(*hr)) 104 LOG(ERROR) << "Failed to initiate bundle download: " << *hr; 105} 106 107void OnReadyToInstall(IAppBundle* app_bundle, HRESULT* hr) { 108 // If the app bundle is busy we will just wait some more. 109 if (CheckIsBusy(app_bundle, hr) || FAILED(*hr)) 110 return; 111 *hr = app_bundle->install(); 112 if (FAILED(*hr)) 113 LOG(ERROR) << "Failed to initiate bundle install: " << *hr; 114} 115 116HRESULT OnError(ICurrentState* current_state) { 117 LONG error_code; 118 HRESULT hr = current_state->get_errorCode(&error_code); 119 if (FAILED(hr)) { 120 LOG(ERROR) << "Failed to retrieve bundle error code: " << hr; 121 return hr; 122 } 123 124 base::win::ScopedBstr completion_message; 125 HRESULT completion_message_hr = 126 current_state->get_completionMessage(completion_message.Receive()); 127 if (FAILED(completion_message_hr)) { 128 LOG(ERROR) << "Bundle installation failed with error " << error_code 129 << ". Error message retrieval failed with error: " 130 << completion_message_hr; 131 } else { 132 LOG(ERROR) << "Bundle installation failed with error " << error_code << ": " 133 << completion_message; 134 } 135 return error_code; 136} 137 138HRESULT CreateGoogleUpdate3(IGoogleUpdate3** update3) { 139 base::win::ScopedComPtr<IGoogleUpdate3> temp_update3; 140 HRESULT hr = temp_update3.CreateInstance(CLSID_GoogleUpdate3UserClass); 141 if (SUCCEEDED(hr)) { 142 *update3 = temp_update3.Detach(); 143 } else { 144 // TODO(erikwright): Try in-proc to support running elevated? According 145 // to update3_utils.cc (CreateGoogleUpdate3UserClass): 146 // The primary reason for the LocalServer activation failing on Vista/Win7 147 // is that COM does not look at HKCU registration when the code is running 148 // elevated. We fall back to an in-proc mode. The in-proc mode is limited to 149 // one install at a time, so we use it only as a backup mechanism. 150 LOG(ERROR) << "Failed to instantiate GoogleUpdate3: " << hr; 151 } 152 return hr; 153} 154 155HRESULT CreateAppBundle(IGoogleUpdate3* update3, IAppBundle** app_bundle) { 156 base::win::ScopedComPtr<IDispatch> idispatch; 157 HRESULT hr = update3->createAppBundle(idispatch.Receive()); 158 if (FAILED(hr)) { 159 LOG(ERROR) << "Failed to createAppBundle: " << hr; 160 return hr; 161 } 162 163 base::win::ScopedComPtr<IAppBundle> temp_app_bundle; 164 hr = temp_app_bundle.QueryFrom(idispatch); 165 if (FAILED(hr)) { 166 LOG(ERROR) << "Unexpected error querying IAppBundle from " 167 << "IGoogleUpdate3->createAppBundle return value: " << hr; 168 return hr; 169 } 170 171 hr = temp_app_bundle->initialize(); 172 if (FAILED(hr)) 173 LOG(ERROR) << "Failed to initialize App Bundle: " << hr; 174 else 175 *app_bundle = temp_app_bundle.Detach(); 176 return hr; 177} 178 179HRESULT SelectBinariesApValue(IGoogleUpdate3* update3, 180 BSTR* ap_value) { 181 // TODO(erikwright): Uncomment this when we correctly propagate the AP value 182 // from the system-level binaries when quick-enabling the app host at 183 // user-level (http://crbug.com/178479). 184 // base::win::ScopedComPtr<IAppBundle> app_bundle; 185 // HRESULT hr = CreateAppBundle(update3, app_bundle.Receive()); 186 // if (FAILED(hr)) 187 // return hr; 188 189 // hr = GetAppHostApValue(update3, app_bundle, ap_value); 190 // if (SUCCEEDED(hr)) 191 // return hr; 192 193 // TODO(erikwright): distinguish between AppHost not installed and an 194 // error in GetAppHostApValue. 195 // TODO(erikwright): Use stable by default when App Host support is in 196 // stable. 197 base::win::ScopedBstr temp_ap_value; 198 if (temp_ap_value.Allocate(L"2.0-dev-multi-apphost") == NULL) { 199 LOG(ERROR) << "Unexpected error in ScopedBstr::Allocate."; 200 return E_FAIL; 201 } 202 *ap_value = temp_ap_value.Release(); 203 return S_OK; 204} 205 206HRESULT CreateBinariesIApp(IAppBundle* app_bundle, BSTR ap, IApp** app) { 207 base::win::ScopedComPtr<IDispatch> idispatch; 208 HRESULT hr = app_bundle->createApp(base::win::ScopedBstr(kBinariesAppId), 209 idispatch.Receive()); 210 if (FAILED(hr)) { 211 LOG(ERROR) << "Failed to configure App Bundle: " << hr; 212 return hr; 213 } 214 215 base::win::ScopedComPtr<IApp> temp_app; 216 hr = temp_app.QueryFrom(idispatch); 217 if (FAILED(hr)) { 218 LOG(ERROR) << "Unexpected error querying IApp from " 219 << "IAppBundle->createApp return value: " << hr; 220 return hr; 221 } 222 223 hr = temp_app->put_isEulaAccepted(VARIANT_TRUE); 224 if (FAILED(hr)) { 225 LOG(ERROR) << "Failed to set 'EULA Accepted': " << hr; 226 return hr; 227 } 228 229 hr = temp_app->put_ap(ap); 230 if (FAILED(hr)) 231 LOG(ERROR) << "Failed to set AP value: " << hr; 232 else 233 *app = temp_app.Detach(); 234 return hr; 235} 236 237bool CheckIfDone(IAppBundle* app_bundle, IApp* app, HRESULT* hr) { 238 base::win::ScopedComPtr<ICurrentState> current_state; 239 CurrentState state_value; 240 *hr = GetCurrentState(app, current_state.Receive(), &state_value); 241 if (FAILED(*hr)) 242 return true; 243 244 switch (state_value) { 245 case STATE_WAITING_TO_CHECK_FOR_UPDATE: 246 case STATE_CHECKING_FOR_UPDATE: 247 case STATE_WAITING_TO_DOWNLOAD: 248 case STATE_RETRYING_DOWNLOAD: 249 case STATE_DOWNLOADING: 250 case STATE_WAITING_TO_INSTALL: 251 case STATE_INSTALLING: 252 case STATE_DOWNLOAD_COMPLETE: 253 case STATE_EXTRACTING: 254 case STATE_APPLYING_DIFFERENTIAL_PATCH: 255 // These states will all transition on their own. 256 return false; 257 258 case STATE_UPDATE_AVAILABLE: 259 OnUpdateAvailable(app_bundle, hr); 260 return FAILED(*hr); 261 262 case STATE_READY_TO_INSTALL: 263 OnReadyToInstall(app_bundle, hr); 264 return FAILED(*hr); 265 266 case STATE_NO_UPDATE: 267 LOG(INFO) << "Google Update reports that the binaries are already " 268 << "installed and up-to-date."; 269 return true; 270 271 case STATE_INSTALL_COMPLETE: 272 return true; 273 274 case STATE_ERROR: 275 *hr = OnError(current_state); 276 return FAILED(*hr); 277 278 case STATE_INIT: 279 case STATE_PAUSED: 280 default: 281 LOG(ERROR) << "Unexpected bundle state: " << state_value << "."; 282 *hr = E_FAIL; 283 return true; 284 } 285} 286 287} // namespace 288 289 290// Globals -------------------------------------------------------------------- 291 292HRESULT InstallBinaries() { 293 base::win::ScopedCOMInitializer initialize_com; 294 if (!initialize_com.succeeded()) { 295 LOG(ERROR) << "COM initialization failed"; 296 return E_FAIL; 297 } 298 299 base::win::ScopedComPtr<IGoogleUpdate3> update3; 300 HRESULT hr = CreateGoogleUpdate3(update3.Receive()); 301 if (FAILED(hr)) 302 return hr; 303 304 base::win::ScopedBstr ap_value; 305 hr = SelectBinariesApValue(update3, ap_value.Receive()); 306 if (FAILED(hr)) 307 return hr; 308 309 base::win::ScopedComPtr<IAppBundle> app_bundle; 310 hr = CreateAppBundle(update3, app_bundle.Receive()); 311 if (FAILED(hr)) 312 return hr; 313 314 base::win::ScopedComPtr<IApp> app; 315 hr = CreateBinariesIApp(app_bundle, ap_value, app.Receive()); 316 if (FAILED(hr)) 317 return hr; 318 319 hr = app_bundle->checkForUpdate(); 320 if (FAILED(hr)) { 321 LOG(ERROR) << "Failed to initiate update check: " << hr; 322 return hr; 323 } 324 325 // We rely upon Omaha to eventually time out and transition to a failure 326 // state. 327 while (!CheckIfDone(app_bundle, app, &hr)) { 328 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds( 329 kInstallationPollingIntervalMs)); 330 } 331 return hr; 332} 333 334} // namespace app_host 335