Extract code handling PrinterProviderAPI from PrintPreviewHandler
[chromium-blink-merge.git] / chrome / browser / component_updater / recovery_component_installer.cc
blob20898059d1394103ac2c6bdfed1deed3f4059fdb
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.
5 #include "chrome/browser/component_updater/recovery_component_installer.h"
7 #include <stdint.h>
8 #include <string>
10 #include "base/base_paths.h"
11 #include "base/bind.h"
12 #include "base/command_line.h"
13 #include "base/compiler_specific.h"
14 #include "base/files/file_path.h"
15 #include "base/files/file_util.h"
16 #include "base/json/json_file_value_serializer.h"
17 #include "base/logging.h"
18 #include "base/memory/scoped_ptr.h"
19 #include "base/metrics/histogram.h"
20 #include "base/path_service.h"
21 #include "base/prefs/pref_registry_simple.h"
22 #include "base/prefs/pref_service.h"
23 #include "base/process/kill.h"
24 #include "base/process/launch.h"
25 #include "base/process/process.h"
26 #include "base/threading/worker_pool.h"
27 #include "chrome/common/chrome_switches.h"
28 #include "chrome/common/pref_names.h"
29 #include "components/component_updater/component_updater_paths.h"
30 #include "components/component_updater/component_updater_service.h"
31 #include "components/component_updater/pref_names.h"
32 #include "components/update_client/update_client.h"
33 #include "content/public/browser/browser_thread.h"
35 using content::BrowserThread;
37 namespace component_updater {
39 namespace {
41 // CRX hash. The extension id is: npdjjkjlcidkjlamlmmdelcjbcpdjocm.
42 const uint8_t kSha2Hash[] = {0xdf, 0x39, 0x9a, 0x9b, 0x28, 0x3a, 0x9b, 0x0c,
43 0xbc, 0xc3, 0x4b, 0x29, 0x12, 0xf3, 0x9e, 0x2c,
44 0x19, 0x7a, 0x71, 0x4b, 0x0a, 0x7c, 0x80, 0x1c,
45 0xf6, 0x29, 0x7c, 0x0a, 0x5f, 0xea, 0x67, 0xb7};
47 // File name of the recovery binary on different platforms.
48 const base::FilePath::CharType kRecoveryFileName[] =
49 #if defined(OS_WIN)
50 FILE_PATH_LITERAL("ChromeRecovery.exe");
51 #else // OS_LINUX, OS_MACOSX, etc.
52 FILE_PATH_LITERAL("ChromeRecovery");
53 #endif
55 const char kRecoveryManifestName[] = "ChromeRecovery";
57 // ChromeRecovery process exit codes.
58 enum ChromeRecoveryExitCode {
59 EXIT_CODE_RECOVERY_SUCCEEDED = 0,
60 EXIT_CODE_RECOVERY_SKIPPED = 1,
61 EXIT_CODE_ELEVATION_NEEDED = 2,
64 enum RecoveryComponentEvent {
65 RCE_RUNNING_NON_ELEVATED = 0,
66 RCE_ELEVATION_NEEDED = 1,
67 RCE_FAILED = 2,
68 RCE_SUCCEEDED = 3,
69 RCE_SKIPPED = 4,
70 RCE_RUNNING_ELEVATED = 5,
71 RCE_ELEVATED_FAILED = 6,
72 RCE_ELEVATED_SUCCEEDED = 7,
73 RCE_ELEVATED_SKIPPED = 8,
74 RCE_COMPONENT_DOWNLOAD_ERROR = 9,
75 RCE_COUNT
78 void RecordRecoveryComponentUMAEvent(RecoveryComponentEvent event) {
79 UMA_HISTOGRAM_ENUMERATION("RecoveryComponent.Event", event, RCE_COUNT);
82 #if !defined(OS_CHROMEOS)
83 // Checks if elevated recovery simulation switch was present on the command
84 // line. This is for testing purpose.
85 bool SimulatingElevatedRecovery() {
86 return base::CommandLine::ForCurrentProcess()->HasSwitch(
87 switches::kSimulateElevatedRecovery);
89 #endif // !defined(OS_CHROMEOS)
91 #if defined(OS_WIN)
92 scoped_ptr<base::DictionaryValue> ReadManifest(const base::FilePath& manifest) {
93 JSONFileValueSerializer serializer(manifest);
94 std::string error;
95 scoped_ptr<base::Value> root(serializer.Deserialize(NULL, &error));
96 if (root.get() && root->IsType(base::Value::TYPE_DICTIONARY)) {
97 return scoped_ptr<base::DictionaryValue>(
98 static_cast<base::DictionaryValue*>(root.release()));
100 return scoped_ptr<base::DictionaryValue>();
103 void WaitForElevatedInstallToComplete(base::Process process) {
104 int installer_exit_code = 0;
105 const base::TimeDelta kMaxWaitTime = base::TimeDelta::FromSeconds(600);
106 if (process.WaitForExitWithTimeout(kMaxWaitTime, &installer_exit_code)) {
107 if (installer_exit_code == EXIT_CODE_RECOVERY_SUCCEEDED) {
108 RecordRecoveryComponentUMAEvent(RCE_ELEVATED_SUCCEEDED);
109 } else {
110 RecordRecoveryComponentUMAEvent(RCE_ELEVATED_SKIPPED);
112 } else {
113 RecordRecoveryComponentUMAEvent(RCE_ELEVATED_FAILED);
117 void DoElevatedInstallRecoveryComponent(const base::FilePath& path) {
118 const base::FilePath main_file = path.Append(kRecoveryFileName);
119 const base::FilePath manifest_file =
120 path.Append(FILE_PATH_LITERAL("manifest.json"));
121 if (!base::PathExists(main_file) || !base::PathExists(manifest_file))
122 return;
124 scoped_ptr<base::DictionaryValue> manifest(ReadManifest(manifest_file));
125 std::string name;
126 manifest->GetStringASCII("name", &name);
127 if (name != kRecoveryManifestName)
128 return;
129 std::string proposed_version;
130 manifest->GetStringASCII("version", &proposed_version);
131 const Version version(proposed_version.c_str());
132 if (!version.IsValid())
133 return;
135 base::CommandLine cmdline(main_file);
136 std::string arguments;
137 if (manifest->GetStringASCII("x-recovery-args", &arguments))
138 cmdline.AppendArg(arguments);
139 std::string add_version;
140 if (manifest->GetStringASCII("x-recovery-add-version", &add_version) &&
141 add_version == "yes") {
142 cmdline.AppendSwitchASCII("version", version.GetString());
145 RecordRecoveryComponentUMAEvent(RCE_RUNNING_ELEVATED);
147 base::LaunchOptions options;
148 options.start_hidden = true;
149 base::Process process = base::LaunchElevatedProcess(cmdline, options);
151 base::WorkerPool::PostTask(
152 FROM_HERE,
153 base::Bind(&WaitForElevatedInstallToComplete, base::Passed(&process)),
154 true);
157 void ElevatedInstallRecoveryComponent(const base::FilePath& installer_path) {
158 base::WorkerPool::PostTask(
159 FROM_HERE,
160 base::Bind(&DoElevatedInstallRecoveryComponent, installer_path),
161 true);
163 #endif // defined(OS_WIN)
165 } // namespace
167 // Component installer that is responsible to repair the chrome installation
168 // or repair the Google update installation. This is a last resort safety
169 // mechanism.
170 // For user Chrome, recovery component just installs silently. For machine
171 // Chrome, elevation may be needed. If that happens, the installer will set
172 // preference flag prefs::kRecoveryComponentNeedsElevation to request that.
173 // There is a global error service monitors this flag and will pop up
174 // bubble if the flag is set to true.
175 // See chrome/browser/recovery/recovery_install_global_error.cc for details.
176 class RecoveryComponentInstaller : public update_client::ComponentInstaller {
177 public:
178 RecoveryComponentInstaller(const Version& version, PrefService* prefs);
180 // ComponentInstaller implementation:
181 void OnUpdateError(int error) override;
183 bool Install(const base::DictionaryValue& manifest,
184 const base::FilePath& unpack_path) override;
186 bool GetInstalledFile(const std::string& file,
187 base::FilePath* installed_file) override;
189 bool Uninstall() override;
191 private:
192 ~RecoveryComponentInstaller() override {}
194 bool RunInstallCommand(const base::CommandLine& cmdline,
195 const base::FilePath& installer_folder) const;
197 Version current_version_;
198 PrefService* prefs_;
201 void SimulateElevatedRecoveryHelper(PrefService* prefs) {
202 prefs->SetBoolean(prefs::kRecoveryComponentNeedsElevation, true);
205 void RecoveryRegisterHelper(ComponentUpdateService* cus, PrefService* prefs) {
206 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
207 Version version(prefs->GetString(prefs::kRecoveryComponentVersion));
208 if (!version.IsValid()) {
209 NOTREACHED();
210 return;
213 update_client::CrxComponent recovery;
214 recovery.name = "recovery";
215 recovery.installer = new RecoveryComponentInstaller(version, prefs);
216 recovery.version = version;
217 recovery.pk_hash.assign(kSha2Hash, &kSha2Hash[sizeof(kSha2Hash)]);
218 if (cus->RegisterComponent(recovery) != ComponentUpdateService::kOk) {
219 NOTREACHED() << "Recovery component registration failed.";
223 void RecoveryUpdateVersionHelper(const Version& version, PrefService* prefs) {
224 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
225 prefs->SetString(prefs::kRecoveryComponentVersion, version.GetString());
228 void SetPrefsForElevatedRecoveryInstall(const base::FilePath& unpack_path,
229 PrefService* prefs) {
230 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
231 prefs->SetFilePath(prefs::kRecoveryComponentUnpackPath, unpack_path);
232 prefs->SetBoolean(prefs::kRecoveryComponentNeedsElevation, true);
235 RecoveryComponentInstaller::RecoveryComponentInstaller(const Version& version,
236 PrefService* prefs)
237 : current_version_(version), prefs_(prefs) {
238 DCHECK(version.IsValid());
241 void RecoveryComponentInstaller::OnUpdateError(int error) {
242 RecordRecoveryComponentUMAEvent(RCE_COMPONENT_DOWNLOAD_ERROR);
243 NOTREACHED() << "Recovery component update error: " << error;
246 #if defined(OS_WIN)
247 void WaitForInstallToComplete(base::Process process,
248 const base::FilePath& installer_folder,
249 PrefService* prefs) {
250 int installer_exit_code = 0;
251 const base::TimeDelta kMaxWaitTime = base::TimeDelta::FromSeconds(600);
252 if (process.WaitForExitWithTimeout(kMaxWaitTime, &installer_exit_code)) {
253 if (installer_exit_code == EXIT_CODE_ELEVATION_NEEDED) {
254 RecordRecoveryComponentUMAEvent(RCE_ELEVATION_NEEDED);
256 BrowserThread::PostTask(
257 BrowserThread::UI,
258 FROM_HERE,
259 base::Bind(&SetPrefsForElevatedRecoveryInstall,
260 installer_folder,
261 prefs));
262 } else if (installer_exit_code == EXIT_CODE_RECOVERY_SUCCEEDED) {
263 RecordRecoveryComponentUMAEvent(RCE_SUCCEEDED);
264 } else if (installer_exit_code == EXIT_CODE_RECOVERY_SKIPPED) {
265 RecordRecoveryComponentUMAEvent(RCE_SKIPPED);
267 } else {
268 RecordRecoveryComponentUMAEvent(RCE_FAILED);
272 bool RecoveryComponentInstaller::RunInstallCommand(
273 const base::CommandLine& cmdline,
274 const base::FilePath& installer_folder) const {
275 RecordRecoveryComponentUMAEvent(RCE_RUNNING_NON_ELEVATED);
277 base::LaunchOptions options;
278 options.start_hidden = true;
279 base::Process process = base::LaunchProcess(cmdline, options);
280 if (!process.IsValid())
281 return false;
283 // Let worker pool thread wait for us so we don't block Chrome shutdown.
284 base::WorkerPool::PostTask(
285 FROM_HERE,
286 base::Bind(&WaitForInstallToComplete,
287 base::Passed(&process), installer_folder, prefs_),
288 true);
290 // Returns true regardless of install result since from updater service
291 // perspective the install is done, even we may need to do elevated
292 // install later.
293 return true;
295 #else
296 bool RecoveryComponentInstaller::RunInstallCommand(
297 const base::CommandLine& cmdline,
298 const base::FilePath&) const {
299 return base::LaunchProcess(cmdline, base::LaunchOptions()).IsValid();
301 #endif // defined(OS_WIN)
303 bool RecoveryComponentInstaller::Install(const base::DictionaryValue& manifest,
304 const base::FilePath& unpack_path) {
305 std::string name;
306 manifest.GetStringASCII("name", &name);
307 if (name != kRecoveryManifestName)
308 return false;
309 std::string proposed_version;
310 manifest.GetStringASCII("version", &proposed_version);
311 Version version(proposed_version.c_str());
312 if (!version.IsValid())
313 return false;
314 if (current_version_.CompareTo(version) >= 0)
315 return false;
317 // Passed the basic tests. Copy the installation to a permanent directory.
318 base::FilePath path;
319 if (!PathService::Get(DIR_RECOVERY_BASE, &path))
320 return false;
321 if (!base::PathExists(path) && !base::CreateDirectory(path))
322 return false;
323 path = path.AppendASCII(version.GetString());
324 if (base::PathExists(path) && !base::DeleteFile(path, true))
325 return false;
326 if (!base::Move(unpack_path, path)) {
327 DVLOG(1) << "Recovery component move failed.";
328 return false;
331 base::FilePath main_file = path.Append(kRecoveryFileName);
332 if (!base::PathExists(main_file))
333 return false;
334 // Run the recovery component.
335 base::CommandLine cmdline(main_file);
336 std::string arguments;
337 if (manifest.GetStringASCII("x-recovery-args", &arguments))
338 cmdline.AppendArg(arguments);
339 std::string add_version;
340 if (manifest.GetStringASCII("x-recovery-add-version", &add_version) &&
341 add_version == "yes") {
342 cmdline.AppendSwitchASCII("version", current_version_.GetString());
345 if (!RunInstallCommand(cmdline, path)) {
346 return false;
349 current_version_ = version;
350 if (prefs_) {
351 BrowserThread::PostTask(
352 BrowserThread::UI,
353 FROM_HERE,
354 base::Bind(&RecoveryUpdateVersionHelper, version, prefs_));
356 return true;
359 bool RecoveryComponentInstaller::GetInstalledFile(
360 const std::string& file,
361 base::FilePath* installed_file) {
362 return false;
365 bool RecoveryComponentInstaller::Uninstall() {
366 return false;
369 void RegisterRecoveryComponent(ComponentUpdateService* cus,
370 PrefService* prefs) {
371 #if !defined(OS_CHROMEOS)
372 if (SimulatingElevatedRecovery()) {
373 BrowserThread::PostTask(
374 BrowserThread::UI,
375 FROM_HERE,
376 base::Bind(&SimulateElevatedRecoveryHelper, prefs));
379 // We delay execute the registration because we are not required in
380 // the critical path during browser startup.
381 BrowserThread::PostDelayedTask(
382 BrowserThread::UI,
383 FROM_HERE,
384 base::Bind(&RecoveryRegisterHelper, cus, prefs),
385 base::TimeDelta::FromSeconds(6));
386 #endif // !defined(OS_CHROMEOS)
389 void RegisterPrefsForRecoveryComponent(PrefRegistrySimple* registry) {
390 registry->RegisterStringPref(prefs::kRecoveryComponentVersion, "0.0.0.0");
391 registry->RegisterFilePathPref(prefs::kRecoveryComponentUnpackPath,
392 base::FilePath());
393 registry->RegisterBooleanPref(prefs::kRecoveryComponentNeedsElevation, false);
396 void AcceptedElevatedRecoveryInstall(PrefService* prefs) {
397 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
399 #if defined(OS_WIN)
400 ElevatedInstallRecoveryComponent(
401 prefs->GetFilePath(prefs::kRecoveryComponentUnpackPath));
402 #endif // OS_WIN
403 prefs->SetBoolean(prefs::kRecoveryComponentNeedsElevation, false);
406 void DeclinedElevatedRecoveryInstall(PrefService* prefs) {
407 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
408 prefs->SetBoolean(prefs::kRecoveryComponentNeedsElevation, false);
411 } // namespace component_updater