Add ICU message format support
[chromium-blink-merge.git] / chrome / browser / component_updater / recovery_component_installer.cc
blob3657d6458dc7cc6f3c6ec177e04c57e3825b0800
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 base::CommandLine GetRecoveryInstallCommandLine(
92 const base::FilePath& command,
93 const base::DictionaryValue& manifest,
94 bool is_deferred_run,
95 const Version& version) {
96 base::CommandLine command_line(command);
98 // Add a flag to for re-attempted install with elevated privilege so that the
99 // recovery executable can report back accordingly.
100 if (is_deferred_run)
101 command_line.AppendArg("/deferredrun");
103 std::string arguments;
104 if (manifest.GetStringASCII("x-recovery-args", &arguments))
105 command_line.AppendArg(arguments);
106 std::string add_version;
107 if (manifest.GetStringASCII("x-recovery-add-version", &add_version) &&
108 add_version == "yes") {
109 std::string version_string = "/version ";
110 version_string += version.GetString();
111 command_line.AppendArg(version_string);
114 return command_line;
117 #if defined(OS_WIN)
118 scoped_ptr<base::DictionaryValue> ReadManifest(const base::FilePath& manifest) {
119 JSONFileValueDeserializer deserializer(manifest);
120 std::string error;
121 scoped_ptr<base::Value> root(deserializer.Deserialize(NULL, &error));
122 if (root.get() && root->IsType(base::Value::TYPE_DICTIONARY)) {
123 return scoped_ptr<base::DictionaryValue>(
124 static_cast<base::DictionaryValue*>(root.release()));
126 return scoped_ptr<base::DictionaryValue>();
129 void WaitForElevatedInstallToComplete(base::Process process) {
130 int installer_exit_code = 0;
131 const base::TimeDelta kMaxWaitTime = base::TimeDelta::FromSeconds(600);
132 if (process.WaitForExitWithTimeout(kMaxWaitTime, &installer_exit_code)) {
133 if (installer_exit_code == EXIT_CODE_RECOVERY_SUCCEEDED) {
134 RecordRecoveryComponentUMAEvent(RCE_ELEVATED_SUCCEEDED);
135 } else {
136 RecordRecoveryComponentUMAEvent(RCE_ELEVATED_SKIPPED);
138 } else {
139 RecordRecoveryComponentUMAEvent(RCE_ELEVATED_FAILED);
143 void DoElevatedInstallRecoveryComponent(const base::FilePath& path) {
144 const base::FilePath main_file = path.Append(kRecoveryFileName);
145 const base::FilePath manifest_file =
146 path.Append(FILE_PATH_LITERAL("manifest.json"));
147 if (!base::PathExists(main_file) || !base::PathExists(manifest_file))
148 return;
150 scoped_ptr<base::DictionaryValue> manifest(ReadManifest(manifest_file));
151 std::string name;
152 manifest->GetStringASCII("name", &name);
153 if (name != kRecoveryManifestName)
154 return;
155 std::string proposed_version;
156 manifest->GetStringASCII("version", &proposed_version);
157 const Version version(proposed_version.c_str());
158 if (!version.IsValid())
159 return;
161 const bool is_deferred_run = true;
162 const auto cmdline = GetRecoveryInstallCommandLine(
163 main_file, *manifest, is_deferred_run, version);
165 RecordRecoveryComponentUMAEvent(RCE_RUNNING_ELEVATED);
167 base::LaunchOptions options;
168 options.start_hidden = true;
169 base::Process process = base::LaunchElevatedProcess(cmdline, options);
171 base::WorkerPool::PostTask(
172 FROM_HERE,
173 base::Bind(&WaitForElevatedInstallToComplete, base::Passed(&process)),
174 true);
177 void ElevatedInstallRecoveryComponent(const base::FilePath& installer_path) {
178 base::WorkerPool::PostTask(
179 FROM_HERE,
180 base::Bind(&DoElevatedInstallRecoveryComponent, installer_path),
181 true);
183 #endif // defined(OS_WIN)
185 } // namespace
187 // Component installer that is responsible to repair the chrome installation
188 // or repair the Google update installation. This is a last resort safety
189 // mechanism.
190 // For user Chrome, recovery component just installs silently. For machine
191 // Chrome, elevation may be needed. If that happens, the installer will set
192 // preference flag prefs::kRecoveryComponentNeedsElevation to request that.
193 // There is a global error service monitors this flag and will pop up
194 // bubble if the flag is set to true.
195 // See chrome/browser/recovery/recovery_install_global_error.cc for details.
196 class RecoveryComponentInstaller : public update_client::CrxInstaller {
197 public:
198 RecoveryComponentInstaller(const Version& version, PrefService* prefs);
200 // ComponentInstaller implementation:
201 void OnUpdateError(int error) override;
203 bool Install(const base::DictionaryValue& manifest,
204 const base::FilePath& unpack_path) override;
206 bool GetInstalledFile(const std::string& file,
207 base::FilePath* installed_file) override;
209 bool Uninstall() override;
211 private:
212 ~RecoveryComponentInstaller() override {}
214 bool RunInstallCommand(const base::CommandLine& cmdline,
215 const base::FilePath& installer_folder) const;
217 Version current_version_;
218 PrefService* prefs_;
221 void SimulateElevatedRecoveryHelper(PrefService* prefs) {
222 prefs->SetBoolean(prefs::kRecoveryComponentNeedsElevation, true);
225 void RecoveryRegisterHelper(ComponentUpdateService* cus, PrefService* prefs) {
226 DCHECK_CURRENTLY_ON(BrowserThread::UI);
227 Version version(prefs->GetString(prefs::kRecoveryComponentVersion));
228 if (!version.IsValid()) {
229 NOTREACHED();
230 return;
233 update_client::CrxComponent recovery;
234 recovery.name = "recovery";
235 recovery.installer = new RecoveryComponentInstaller(version, prefs);
236 recovery.version = version;
237 recovery.pk_hash.assign(kSha2Hash, &kSha2Hash[sizeof(kSha2Hash)]);
238 if (!cus->RegisterComponent(recovery)) {
239 NOTREACHED() << "Recovery component registration failed.";
243 void RecoveryUpdateVersionHelper(const Version& version, PrefService* prefs) {
244 DCHECK_CURRENTLY_ON(BrowserThread::UI);
245 prefs->SetString(prefs::kRecoveryComponentVersion, version.GetString());
248 void SetPrefsForElevatedRecoveryInstall(const base::FilePath& unpack_path,
249 PrefService* prefs) {
250 DCHECK_CURRENTLY_ON(BrowserThread::UI);
251 prefs->SetFilePath(prefs::kRecoveryComponentUnpackPath, unpack_path);
252 prefs->SetBoolean(prefs::kRecoveryComponentNeedsElevation, true);
255 RecoveryComponentInstaller::RecoveryComponentInstaller(const Version& version,
256 PrefService* prefs)
257 : current_version_(version), prefs_(prefs) {
258 DCHECK(version.IsValid());
261 void RecoveryComponentInstaller::OnUpdateError(int error) {
262 RecordRecoveryComponentUMAEvent(RCE_COMPONENT_DOWNLOAD_ERROR);
263 NOTREACHED() << "Recovery component update error: " << error;
266 #if defined(OS_WIN)
267 void WaitForInstallToComplete(base::Process process,
268 const base::FilePath& installer_folder,
269 PrefService* prefs) {
270 int installer_exit_code = 0;
271 const base::TimeDelta kMaxWaitTime = base::TimeDelta::FromSeconds(600);
272 if (process.WaitForExitWithTimeout(kMaxWaitTime, &installer_exit_code)) {
273 if (installer_exit_code == EXIT_CODE_ELEVATION_NEEDED) {
274 RecordRecoveryComponentUMAEvent(RCE_ELEVATION_NEEDED);
276 BrowserThread::PostTask(
277 BrowserThread::UI,
278 FROM_HERE,
279 base::Bind(&SetPrefsForElevatedRecoveryInstall,
280 installer_folder,
281 prefs));
282 } else if (installer_exit_code == EXIT_CODE_RECOVERY_SUCCEEDED) {
283 RecordRecoveryComponentUMAEvent(RCE_SUCCEEDED);
284 } else if (installer_exit_code == EXIT_CODE_RECOVERY_SKIPPED) {
285 RecordRecoveryComponentUMAEvent(RCE_SKIPPED);
287 } else {
288 RecordRecoveryComponentUMAEvent(RCE_FAILED);
292 bool RecoveryComponentInstaller::RunInstallCommand(
293 const base::CommandLine& cmdline,
294 const base::FilePath& installer_folder) const {
295 RecordRecoveryComponentUMAEvent(RCE_RUNNING_NON_ELEVATED);
297 base::LaunchOptions options;
298 options.start_hidden = true;
299 base::Process process = base::LaunchProcess(cmdline, options);
300 if (!process.IsValid())
301 return false;
303 // Let worker pool thread wait for us so we don't block Chrome shutdown.
304 base::WorkerPool::PostTask(
305 FROM_HERE,
306 base::Bind(&WaitForInstallToComplete,
307 base::Passed(&process), installer_folder, prefs_),
308 true);
310 // Returns true regardless of install result since from updater service
311 // perspective the install is done, even we may need to do elevated
312 // install later.
313 return true;
315 #else
316 bool RecoveryComponentInstaller::RunInstallCommand(
317 const base::CommandLine& cmdline,
318 const base::FilePath&) const {
319 return base::LaunchProcess(cmdline, base::LaunchOptions()).IsValid();
321 #endif // defined(OS_WIN)
323 bool RecoveryComponentInstaller::Install(const base::DictionaryValue& manifest,
324 const base::FilePath& unpack_path) {
325 std::string name;
326 manifest.GetStringASCII("name", &name);
327 if (name != kRecoveryManifestName)
328 return false;
329 std::string proposed_version;
330 manifest.GetStringASCII("version", &proposed_version);
331 Version version(proposed_version.c_str());
332 if (!version.IsValid())
333 return false;
334 if (current_version_.CompareTo(version) >= 0)
335 return false;
337 // Passed the basic tests. Copy the installation to a permanent directory.
338 base::FilePath path;
339 if (!PathService::Get(DIR_RECOVERY_BASE, &path))
340 return false;
341 if (!base::PathExists(path) && !base::CreateDirectory(path))
342 return false;
343 path = path.AppendASCII(version.GetString());
344 if (base::PathExists(path) && !base::DeleteFile(path, true))
345 return false;
346 if (!base::Move(unpack_path, path)) {
347 DVLOG(1) << "Recovery component move failed.";
348 return false;
351 base::FilePath main_file = path.Append(kRecoveryFileName);
352 if (!base::PathExists(main_file))
353 return false;
355 // Run the recovery component.
356 const bool is_deferred_run = false;
357 const auto cmdline = GetRecoveryInstallCommandLine(
358 main_file, manifest, is_deferred_run, current_version_);
360 if (!RunInstallCommand(cmdline, path)) {
361 return false;
364 current_version_ = version;
365 if (prefs_) {
366 BrowserThread::PostTask(
367 BrowserThread::UI,
368 FROM_HERE,
369 base::Bind(&RecoveryUpdateVersionHelper, version, prefs_));
371 return true;
374 bool RecoveryComponentInstaller::GetInstalledFile(
375 const std::string& file,
376 base::FilePath* installed_file) {
377 return false;
380 bool RecoveryComponentInstaller::Uninstall() {
381 return false;
384 void RegisterRecoveryComponent(ComponentUpdateService* cus,
385 PrefService* prefs) {
386 #if !defined(OS_CHROMEOS)
387 if (SimulatingElevatedRecovery()) {
388 BrowserThread::PostTask(
389 BrowserThread::UI,
390 FROM_HERE,
391 base::Bind(&SimulateElevatedRecoveryHelper, prefs));
394 // We delay execute the registration because we are not required in
395 // the critical path during browser startup.
396 BrowserThread::PostDelayedTask(
397 BrowserThread::UI,
398 FROM_HERE,
399 base::Bind(&RecoveryRegisterHelper, cus, prefs),
400 base::TimeDelta::FromSeconds(6));
401 #endif // !defined(OS_CHROMEOS)
404 void RegisterPrefsForRecoveryComponent(PrefRegistrySimple* registry) {
405 registry->RegisterStringPref(prefs::kRecoveryComponentVersion, "0.0.0.0");
406 registry->RegisterFilePathPref(prefs::kRecoveryComponentUnpackPath,
407 base::FilePath());
408 registry->RegisterBooleanPref(prefs::kRecoveryComponentNeedsElevation, false);
411 void AcceptedElevatedRecoveryInstall(PrefService* prefs) {
412 DCHECK_CURRENTLY_ON(BrowserThread::UI);
414 #if defined(OS_WIN)
415 ElevatedInstallRecoveryComponent(
416 prefs->GetFilePath(prefs::kRecoveryComponentUnpackPath));
417 #endif // OS_WIN
418 prefs->SetBoolean(prefs::kRecoveryComponentNeedsElevation, false);
421 void DeclinedElevatedRecoveryInstall(PrefService* prefs) {
422 DCHECK_CURRENTLY_ON(BrowserThread::UI);
423 prefs->SetBoolean(prefs::kRecoveryComponentNeedsElevation, false);
426 } // namespace component_updater