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