blob: 733d2e79eb6eddfaa8540671cd5b0974b3f6c88a [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/component_updater/cros_component_installer_chromeos.h"
#include <map>
#include <utility>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/component_updater/component_installer_errors.h"
#include "chrome/browser/component_updater/metadata_table_chromeos.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/image_loader_client.h"
#include "components/component_updater/component_updater_paths.h"
#include "components/crx_file/id_util.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/sha2.h"
namespace component_updater {
namespace {
// Root path where all components are stored.
constexpr char kComponentsRootPath[] = "cros-components";
// All downloadable Chrome OS components.
const ComponentConfig kConfigs[] = {
{"epson-inkjet-printer-escpr", "3.0",
"1913a5e0a6cad30b6f03e176177e0d7ed62c5d6700a9c66da556d7c3f5d6a47e"},
{"cros-termina", "800.1",
"e9d960f84f628e1f42d05de4046bb5b3154b6f1f65c08412c6af57a29aecaffb"},
{"rtanalytics-light", "14.0",
"69f09d33c439c2ab55bbbe24b47ab55cb3f6c0bd1f1ef46eefea3216ec925038"},
{"rtanalytics-full", "14.0",
"c93c3e1013c52100a20038b405ac854d69fa889f6dc4fa6f188267051e05e444"},
{"star-cups-driver", "1.1",
"6d24de30f671da5aee6d463d9e446cafe9ddac672800a9defe86877dcde6c466"},
{"cros-cellular", "1.0",
"5714811c04f0a63aac96b39096faa759ace4c04e9b68291e7c9716128f5a2722"},
{"demo-mode-resources", "1.0",
"93c093ebac788581389015e9c59c5af111d2fa5174d206eb795042e6376cbd10"},
};
const ComponentConfig* FindConfig(const std::string& name) {
const ComponentConfig* config = std::find_if(
std::begin(kConfigs), std::end(kConfigs),
[&name](const ComponentConfig& config) { return config.name == name; });
if (config == std::end(kConfigs))
return nullptr;
return config;
}
// TODO(xiaochu): add metrics for component usage (https://crbug.com/793052).
void LogCustomUninstall(base::Optional<bool> result) {}
void FinishCustomUninstallOnUIThread(const std::string& name) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
chromeos::DBusThreadManager::Get()->GetImageLoaderClient()->UnmountComponent(
name, base::BindOnce(&LogCustomUninstall));
}
std::string GenerateId(const std::string& sha2hashstr) {
// kIdSize is the count of a pair of hex in the sha2hash array.
// In string representation of sha2hash, size is doubled since each hex is
// represented by a single char.
return crx_file::id_util::GenerateIdFromHex(
sha2hashstr.substr(0, crx_file::id_util::kIdSize * 2));
}
// Returns all installed components.
std::vector<ComponentConfig> GetInstalled() {
std::vector<ComponentConfig> configs;
base::FilePath root;
if (!base::PathService::Get(DIR_COMPONENT_USER, &root))
return configs;
root = root.Append(kComponentsRootPath);
for (const ComponentConfig& config : kConfigs) {
base::FilePath component_path = root.Append(config.name);
if (base::PathExists(component_path))
configs.push_back(config);
}
return configs;
}
// Report Error code.
CrOSComponentManager::Error ReportError(CrOSComponentManager::Error error) {
UMA_HISTOGRAM_ENUMERATION("ComponentUpdater.ChromeOS.InstallResult", error,
CrOSComponentManager::Error::ERROR_MAX);
return error;
}
} // namespace
CrOSComponentInstallerPolicy::CrOSComponentInstallerPolicy(
const ComponentConfig& config,
CrOSComponentInstaller* cros_component_installer)
: cros_component_installer_(cros_component_installer),
name_(config.name),
env_version_(config.env_version) {
if (strlen(config.sha2hash) != crypto::kSHA256Length * 2)
return;
bool converted = base::HexStringToBytes(config.sha2hash, &sha2_hash_);
DCHECK(converted);
DCHECK_EQ(crypto::kSHA256Length, sha2_hash_.size());
}
CrOSComponentInstallerPolicy::~CrOSComponentInstallerPolicy() = default;
bool CrOSComponentInstallerPolicy::SupportsGroupPolicyEnabledComponentUpdates()
const {
return true;
}
bool CrOSComponentInstallerPolicy::RequiresNetworkEncryption() const {
return true;
}
update_client::CrxInstaller::Result
CrOSComponentInstallerPolicy::OnCustomInstall(
const base::DictionaryValue& manifest,
const base::FilePath& install_dir) {
cros_component_installer_->EmitInstalledSignal(GetName());
return update_client::CrxInstaller::Result(update_client::InstallError::NONE);
}
void CrOSComponentInstallerPolicy::OnCustomUninstall() {
cros_component_installer_->UnregisterCompatiblePath(name_);
base::PostTask(FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(&FinishCustomUninstallOnUIThread, name_));
}
void CrOSComponentInstallerPolicy::ComponentReady(
const base::Version& version,
const base::FilePath& path,
std::unique_ptr<base::DictionaryValue> manifest) {
std::string min_env_version;
if (!manifest || !manifest->GetString("min_env_version", &min_env_version))
return;
if (!IsCompatible(env_version_, min_env_version))
return;
cros_component_installer_->RegisterCompatiblePath(GetName(), path);
}
bool CrOSComponentInstallerPolicy::VerifyInstallation(
const base::DictionaryValue& manifest,
const base::FilePath& install_dir) const {
return true;
}
base::FilePath CrOSComponentInstallerPolicy::GetRelativeInstallDir() const {
base::FilePath path = base::FilePath(kComponentsRootPath);
return path.Append(name_);
}
void CrOSComponentInstallerPolicy::GetHash(std::vector<uint8_t>* hash) const {
*hash = sha2_hash_;
}
std::string CrOSComponentInstallerPolicy::GetName() const {
return name_;
}
update_client::InstallerAttributes
CrOSComponentInstallerPolicy::GetInstallerAttributes() const {
update_client::InstallerAttributes attrs;
attrs["_env_version"] = env_version_;
return attrs;
}
std::vector<std::string> CrOSComponentInstallerPolicy::GetMimeTypes() const {
std::vector<std::string> mime_types;
return mime_types;
}
bool CrOSComponentInstallerPolicy::IsCompatible(
const std::string& env_version_str,
const std::string& min_env_version_str) {
base::Version env_version(env_version_str);
base::Version min_env_version(min_env_version_str);
return env_version.IsValid() && min_env_version.IsValid() &&
env_version.components()[0] == min_env_version.components()[0] &&
env_version >= min_env_version;
}
CrOSComponentInstaller::CrOSComponentInstaller(
std::unique_ptr<MetadataTable> metadata_table,
ComponentUpdateService* component_updater)
: metadata_table_(std::move(metadata_table)),
component_updater_(component_updater) {}
CrOSComponentInstaller::~CrOSComponentInstaller() {}
void CrOSComponentInstaller::SetDelegate(Delegate* delegate) {
delegate_ = delegate;
}
void CrOSComponentInstaller::Load(const std::string& name,
MountPolicy mount_policy,
UpdatePolicy update_policy,
LoadCallback load_callback) {
if (!IsCompatible(name) || update_policy == UpdatePolicy::kForce) {
// A compatible component is not installed, or forced update is requested.
// Start registration and installation/update process.
Install(name, update_policy, mount_policy, std::move(load_callback));
} else if (mount_policy == MountPolicy::kMount) {
// A compatible component is installed, load it.
LoadInternal(name, std::move(load_callback));
} else {
// A compatible component is installed, do not load it.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(load_callback),
ReportError(Error::NONE), base::FilePath()));
}
}
bool CrOSComponentInstaller::Unload(const std::string& name) {
const ComponentConfig* config = FindConfig(name);
if (!config) {
// Component |name| does not exist.
return false;
}
const std::string id = GenerateId(config->sha2hash);
metadata_table_->DeleteComponentForCurrentUser(name);
return metadata_table_->HasComponentForAnyUser(name) ||
component_updater_->UnregisterComponent(id);
}
void CrOSComponentInstaller::RegisterInstalled() {
base::PostTaskAndReplyWithResult(
FROM_HERE, {base::ThreadPool(), base::MayBlock()},
base::BindOnce(GetInstalled),
base::BindOnce(&CrOSComponentInstaller::RegisterN,
base::Unretained(this)));
}
void CrOSComponentInstaller::RegisterCompatiblePath(
const std::string& name,
const base::FilePath& path) {
compatible_components_[name] = path;
}
void CrOSComponentInstaller::UnregisterCompatiblePath(const std::string& name) {
compatible_components_.erase(name);
}
base::FilePath CrOSComponentInstaller::GetCompatiblePath(
const std::string& name) const {
const auto it = compatible_components_.find(name);
return it == compatible_components_.end() ? base::FilePath() : it->second;
}
void CrOSComponentInstaller::EmitInstalledSignal(const std::string& component) {
if (delegate_)
delegate_->EmitInstalledSignal(component);
}
bool CrOSComponentInstaller::IsRegistered(const std::string& name) const {
base::FilePath root;
if (!base::PathService::Get(DIR_COMPONENT_USER, &root))
return false;
return base::PathExists(root.Append(kComponentsRootPath).Append(name));
}
void CrOSComponentInstaller::Register(const ComponentConfig& config,
base::OnceClosure register_callback) {
auto installer = base::MakeRefCounted<ComponentInstaller>(
std::make_unique<CrOSComponentInstallerPolicy>(config, this));
installer->Register(component_updater_, std::move(register_callback));
}
void CrOSComponentInstaller::Install(const std::string& name,
UpdatePolicy update_policy,
MountPolicy mount_policy,
LoadCallback load_callback) {
const ComponentConfig* config = FindConfig(name);
if (!config) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(load_callback),
ReportError(Error::UNKNOWN_COMPONENT),
base::FilePath()));
return;
}
Register(*config,
base::BindOnce(
&CrOSComponentInstaller::StartInstall, base::Unretained(this),
name, GenerateId(config->sha2hash), update_policy,
base::BindOnce(&CrOSComponentInstaller::FinishInstall,
base::Unretained(this), name, mount_policy,
update_policy, std::move(load_callback))));
}
void CrOSComponentInstaller::StartInstall(
const std::string& name,
const std::string& id,
UpdatePolicy update_policy,
update_client::Callback install_callback) {
// Check whether an installed component was found during registration, and
// determine whether OnDemandUpdater should be started accordingly.
const bool is_compatible = IsCompatible(name);
if (update_policy == UpdatePolicy::kSkip ||
(is_compatible && update_policy != UpdatePolicy::kForce)) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(install_callback),
update_client::Error::NONE));
return;
}
const component_updater::OnDemandUpdater::Priority priority =
is_compatible ? component_updater::OnDemandUpdater::Priority::BACKGROUND
: component_updater::OnDemandUpdater::Priority::FOREGROUND;
component_updater_->GetOnDemandUpdater().OnDemandUpdate(
id, priority, std::move(install_callback));
}
void CrOSComponentInstaller::FinishInstall(const std::string& name,
MountPolicy mount_policy,
UpdatePolicy update_policy,
LoadCallback load_callback,
update_client::Error error) {
if (error != update_client::Error::NONE) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(load_callback),
ReportError(Error::INSTALL_FAILURE), base::FilePath()));
} else if (!IsCompatible(name)) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(load_callback),
ReportError(update_policy == UpdatePolicy::kSkip
? Error::NOT_FOUND
: Error::COMPATIBILITY_CHECK_FAILED),
base::FilePath()));
} else if (mount_policy == MountPolicy::kMount) {
LoadInternal(name, std::move(load_callback));
} else {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(load_callback),
ReportError(Error::NONE), base::FilePath()));
}
}
void CrOSComponentInstaller::LoadInternal(const std::string& name,
LoadCallback load_callback) {
const base::FilePath path = GetCompatiblePath(name);
DCHECK(!path.empty());
chromeos::DBusThreadManager::Get()
->GetImageLoaderClient()
->LoadComponentAtPath(
name, path,
base::BindOnce(&CrOSComponentInstaller::FinishLoad,
base::Unretained(this), std::move(load_callback),
base::TimeTicks::Now(), name));
}
void CrOSComponentInstaller::FinishLoad(LoadCallback load_callback,
const base::TimeTicks start_time,
const std::string& name,
base::Optional<base::FilePath> result) {
// Report component image mount time.
UMA_HISTOGRAM_LONG_TIMES("ComponentUpdater.ChromeOS.MountTime",
base::TimeTicks::Now() - start_time);
if (!result.has_value()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(load_callback),
ReportError(Error::MOUNT_FAILURE), base::FilePath()));
} else {
metadata_table_->AddComponentForCurrentUser(name);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(load_callback),
ReportError(Error::NONE), result.value()));
}
}
void CrOSComponentInstaller::RegisterN(
const std::vector<ComponentConfig>& configs) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
for (const auto& config : configs) {
Register(config, base::OnceClosure());
}
}
bool CrOSComponentInstaller::IsCompatible(const std::string& name) const {
return compatible_components_.count(name) > 0;
}
} // namespace component_updater