Chromium Code Reviews| Index: chrome/browser/component_updater/recovery_component_installer.cc |
| diff --git a/chrome/browser/component_updater/recovery_component_installer.cc b/chrome/browser/component_updater/recovery_component_installer.cc |
| index 9aadd24c0c7364422689bdcdcfbee828ce331041..f7b51ca53d2d64d8f3cc13eeb6e3422d13db95ff 100644 |
| --- a/chrome/browser/component_updater/recovery_component_installer.cc |
| +++ b/chrome/browser/component_updater/recovery_component_installer.cc |
| @@ -13,13 +13,17 @@ |
| #include "base/compiler_specific.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| +#include "base/json/json_file_value_serializer.h" |
| #include "base/logging.h" |
| +#include "base/memory/scoped_ptr.h" |
| #include "base/path_service.h" |
| #include "base/prefs/pref_registry_simple.h" |
| #include "base/prefs/pref_service.h" |
| +#include "base/process/kill.h" |
| #include "base/process/launch.h" |
| -#include "base/strings/string_util.h" |
| -#include "base/values.h" |
| +#include "base/threading/worker_pool.h" |
| +#include "chrome/common/chrome_switches.h" |
| +#include "chrome/common/pref_names.h" |
| #include "components/component_updater/component_updater_paths.h" |
| #include "components/component_updater/component_updater_service.h" |
| #include "components/component_updater/pref_names.h" |
| @@ -47,13 +51,87 @@ const base::FilePath::CharType kRecoveryFileName[] = |
| const char kRecoveryManifestName[] = "ChromeRecovery"; |
| +// ChromeRecovery process exit codes. |
| +enum ChromeRecoveryExitCode { |
| + EXIT_CODE_RECOVERY_SUCCEEDED = 0, |
| + EXIT_CODE_RECOVERY_SKIPPED = 1, |
| + EXIT_CODE_ELEVATION_NEEDED = 2, |
| +}; |
| + |
| +// Checks if elevated recovery simulation switch was present on the command |
| +// line. This is for testing purpose. |
| +bool SimulatingElevatedRecovery() { |
| + return CommandLine::ForCurrentProcess()-> |
| + HasSwitch(switches::kSimulateElevatedRecovery); |
| +} |
| + |
| +#if defined(OS_WIN) |
| +scoped_ptr<base::DictionaryValue> ReadManifest(const base::FilePath& manifest) { |
| + JSONFileValueSerializer serializer(manifest); |
| + std::string error; |
| + scoped_ptr<base::Value> root(serializer.Deserialize(NULL, &error)); |
| + if (root.get() && root->IsType(base::Value::TYPE_DICTIONARY)) { |
| + return scoped_ptr<base::DictionaryValue>( |
| + static_cast<base::DictionaryValue*>(root.release())); |
| + } |
| + return scoped_ptr<base::DictionaryValue>(); |
| +} |
| + |
| +void DoElevatedInstallRecoveryComponent(const base::FilePath& path) { |
| + const base::FilePath main_file = path.Append(kRecoveryFileName); |
| + const base::FilePath manifest_file = |
| + path.Append(FILE_PATH_LITERAL("manifest.json")); |
| + if (!base::PathExists(main_file) || !base::PathExists(manifest_file)) |
| + return; |
| + |
| + scoped_ptr<base::DictionaryValue> manifest(ReadManifest(manifest_file)); |
| + std::string name; |
| + manifest->GetStringASCII("name", &name); |
| + if (name != kRecoveryManifestName) |
| + return; |
| + std::string proposed_version; |
| + manifest->GetStringASCII("version", &proposed_version); |
| + const Version version(proposed_version.c_str()); |
| + if (!version.IsValid()) |
| + return; |
| + |
| + CommandLine cmdline(main_file); |
| + std::string arguments; |
| + if (manifest->GetStringASCII("x-recovery-args", &arguments)) |
| + cmdline.AppendArg(arguments); |
| + std::string add_version; |
| + if (manifest->GetStringASCII("x-recovery-add-version", &add_version) && |
| + add_version == "yes") { |
| + cmdline.AppendSwitchASCII("version", version.GetString()); |
| + } |
| + |
| + base::LaunchOptions options; |
| + options.start_hidden = true; |
| + base::LaunchElevatedProcess(cmdline, options); |
| +} |
| + |
| +void ElevatedInstallRecoveryComponent(const base::FilePath& installer_path) { |
| + base::WorkerPool::PostTask( |
|
waffles
2014/12/06 01:20:35
I think this is OK.
Threads from the worker pool
xiaoling
2014/12/06 02:21:38
Acknowledged.
|
| + FROM_HERE, |
| + base::Bind(&DoElevatedInstallRecoveryComponent, installer_path), |
| + true); |
| +} |
| +#endif |
| + |
| } // namespace |
| +// Component installer that is responsible to repair the chrome installation |
| +// or repair the Google update installation. This is a last resort safety |
| +// mechanism. |
| +// For user Chrome, recovery component just installs silently. For machine |
| +// Chrome, elevation may be needed. If that happens, the installer will set |
| +// preference flag prefs::kRecoveryComponentNeedsElevation to request that. |
| +// There is a global error service monitors this flag and will pop up |
| +// bubble if the flag is set to true. |
| +// See chrome/browser/recovery/recovery_install_global_error.cc for details. |
| class RecoveryComponentInstaller : public ComponentInstaller { |
| public: |
| - explicit RecoveryComponentInstaller(const Version& version, |
| - PrefService* prefs); |
| - |
| + RecoveryComponentInstaller(const Version& version, PrefService* prefs); |
| ~RecoveryComponentInstaller() override {} |
| void OnUpdateError(int error) override; |
| @@ -65,10 +143,17 @@ class RecoveryComponentInstaller : public ComponentInstaller { |
| base::FilePath* installed_file) override; |
| private: |
| + bool RunInstallCommand(const CommandLine& cmdline, |
| + const base::FilePath& installer_path) const; |
| + |
| Version current_version_; |
| PrefService* prefs_; |
| }; |
| +void SimulateElevatedRecoveryHelper(PrefService* prefs) { |
| + prefs->SetBoolean(prefs::kRecoveryComponentNeedsElevation, true); |
| +} |
| + |
| void RecoveryRegisterHelper(ComponentUpdateService* cus, PrefService* prefs) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| Version version(prefs->GetString(prefs::kRecoveryComponentVersion)); |
| @@ -92,6 +177,13 @@ void RecoveryUpdateVersionHelper(const Version& version, PrefService* prefs) { |
| prefs->SetString(prefs::kRecoveryComponentVersion, version.GetString()); |
| } |
| +void SetPrefsForElevatedRecoveryInstall(const base::FilePath& unpack_path, |
| + PrefService* prefs) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + prefs->SetFilePath(prefs::kRecoveryComponentUnpackPath, unpack_path); |
| + prefs->SetBoolean(prefs::kRecoveryComponentNeedsElevation, true); |
| +} |
| + |
| RecoveryComponentInstaller::RecoveryComponentInstaller(const Version& version, |
| PrefService* prefs) |
| : current_version_(version), prefs_(prefs) { |
| @@ -102,6 +194,41 @@ void RecoveryComponentInstaller::OnUpdateError(int error) { |
| NOTREACHED() << "Recovery component update error: " << error; |
| } |
| +#if defined(OS_WIN) |
| +bool RecoveryComponentInstaller::RunInstallCommand( |
| + const CommandLine& cmdline, const base::FilePath& installer_folder) const { |
| + base::ProcessHandle process_handle; |
| + base::LaunchOptions options; |
| + options.start_hidden = true; |
| + if (!base::LaunchProcess(cmdline, options, &process_handle)) |
| + return false; |
| + |
| + int installer_exit_code = 0; |
| + const base::TimeDelta kMaxWaitTime = base::TimeDelta::FromSeconds(600); |
|
waffles
2014/12/06 01:20:35
In the worst case, this can block Chrome shutdown
xiaoling
2014/12/06 02:21:38
Done.
|
| + if (base::WaitForExitCodeWithTimeout(process_handle, |
| + &installer_exit_code, |
| + kMaxWaitTime) && |
| + installer_exit_code == EXIT_CODE_ELEVATION_NEEDED) { |
| + BrowserThread::PostTask( |
| + BrowserThread::UI, |
| + FROM_HERE, |
| + base::Bind(&SetPrefsForElevatedRecoveryInstall, |
| + installer_folder, |
| + prefs_)); |
| + } |
| + |
| + // Returns true regardless of exit code since from updater service |
| + // perspective the install is done, even we may need to do elevated |
| + // install later. |
| + return true; |
| +} |
| +#else |
| +bool RecoveryComponentInstaller::RunInstallCommand( |
| + const CommandLine& cmdline, const base::FilePath&) const { |
| + return base::LaunchProcess(cmdline, base::LaunchOptions(), NULL); |
| +} |
| +#endif |
| + |
| bool RecoveryComponentInstaller::Install(const base::DictionaryValue& manifest, |
| const base::FilePath& unpack_path) { |
| std::string name; |
| @@ -120,11 +247,8 @@ bool RecoveryComponentInstaller::Install(const base::DictionaryValue& manifest, |
| base::FilePath path; |
| if (!PathService::Get(DIR_RECOVERY_BASE, &path)) |
| return false; |
| - if (!base::PathExists(path)) { |
| - if (!base::CreateDirectory(path)) { |
| + if (!base::PathExists(path) && !base::CreateDirectory(path)) |
| return false; |
| - } |
| - } |
| path = path.AppendASCII(version.GetString()); |
| if (base::PathExists(path) && !base::DeleteFile(path, true)) |
| return false; |
| @@ -142,10 +266,15 @@ bool RecoveryComponentInstaller::Install(const base::DictionaryValue& manifest, |
| if (manifest.GetStringASCII("x-recovery-args", &arguments)) |
| cmdline.AppendArg(arguments); |
| std::string add_version; |
| - if (manifest.GetStringASCII("x-recovery-add-version", &add_version)) { |
| - if (add_version == "yes") |
| - cmdline.AppendSwitchASCII("version", current_version_.GetString()); |
| + if (manifest.GetStringASCII("x-recovery-add-version", &add_version) && |
| + add_version == "yes") { |
| + cmdline.AppendSwitchASCII("version", current_version_.GetString()); |
| + } |
| + |
| + if (!RunInstallCommand(cmdline, path)) { |
| + return false; |
| } |
| + |
| current_version_ = version; |
| if (prefs_) { |
| BrowserThread::PostTask( |
| @@ -153,7 +282,7 @@ bool RecoveryComponentInstaller::Install(const base::DictionaryValue& manifest, |
| FROM_HERE, |
| base::Bind(&RecoveryUpdateVersionHelper, version, prefs_)); |
| } |
| - return base::LaunchProcess(cmdline, base::LaunchOptions(), NULL); |
| + return true; |
| } |
| bool RecoveryComponentInstaller::GetInstalledFile( |
| @@ -165,6 +294,13 @@ bool RecoveryComponentInstaller::GetInstalledFile( |
| void RegisterRecoveryComponent(ComponentUpdateService* cus, |
| PrefService* prefs) { |
| #if !defined(OS_CHROMEOS) |
| + if (SimulatingElevatedRecovery()) { |
| + BrowserThread::PostTask( |
| + BrowserThread::UI, |
| + FROM_HERE, |
| + base::Bind(&SimulateElevatedRecoveryHelper, prefs)); |
| + } |
| + |
| // We delay execute the registration because we are not required in |
| // the critical path during browser startup. |
| BrowserThread::PostDelayedTask( |
| @@ -177,14 +313,24 @@ void RegisterRecoveryComponent(ComponentUpdateService* cus, |
| void RegisterPrefsForRecoveryComponent(PrefRegistrySimple* registry) { |
| registry->RegisterStringPref(prefs::kRecoveryComponentVersion, "0.0.0.0"); |
| + registry->RegisterFilePathPref(prefs::kRecoveryComponentUnpackPath, |
| + base::FilePath()); |
| + registry->RegisterBooleanPref(prefs::kRecoveryComponentNeedsElevation, false); |
| } |
| void AcceptedElevatedRecoveryInstall(PrefService* prefs) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + |
| +#if defined(OS_WIN) |
| + ElevatedInstallRecoveryComponent( |
| + prefs->GetFilePath(prefs::kRecoveryComponentUnpackPath)); |
| +#endif |
| + prefs->SetBoolean(prefs::kRecoveryComponentNeedsElevation, false); |
| } |
| void DeclinedElevatedRecoveryInstall(PrefService* prefs) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + prefs->SetBoolean(prefs::kRecoveryComponentNeedsElevation, false); |
| } |
| } // namespace component_updater |