blob: dfbb14bece18207a21ea7a5e6f88628e07d78288 [file] [log] [blame]
// Copyright 2019 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/updater/updater_module.h"
#include <utility>
#include <vector>
#include "base/bind_helpers.h"
#include "base/callback_forward.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/optional.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/post_task.h"
#include "base/task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/version.h"
#include "cobalt/browser/switches.h"
#include "cobalt/extension/installation_manager.h"
#include "cobalt/updater/crash_client.h"
#include "cobalt/updater/crash_reporter.h"
#include "cobalt/updater/utils.h"
#include "components/crx_file/crx_verifier.h"
#include "components/update_client/cobalt_slot_management.h"
#include "components/update_client/utils.h"
#include "starboard/common/file.h"
#include "starboard/configuration_constants.h"
namespace {
using update_client::ComponentState;
using update_client::CobaltSlotManagement;
// The SHA256 hash of the "cobalt_evergreen_public" key.
constexpr uint8_t kCobaltPublicKeyHash[] = {
0x51, 0xa8, 0xc0, 0x90, 0xf8, 0x1a, 0x14, 0xb0, 0xda, 0x7a, 0xfb,
0x9e, 0x8b, 0x2d, 0x22, 0x65, 0x19, 0xb1, 0xfa, 0xba, 0x02, 0x04,
0x3a, 0xb2, 0x7a, 0xf6, 0xfe, 0xd5, 0x35, 0xa1, 0x19, 0xd9};
void QuitLoop(base::OnceClosure quit_closure) { std::move(quit_closure).Run(); }
CobaltExtensionUpdaterNotificationState
ComponentStateToCobaltExtensionUpdaterNotificationState(
ComponentState component_state) {
switch (component_state) {
case ComponentState::kChecking:
return kCobaltExtensionUpdaterNotificationStateChecking;
case ComponentState::kCanUpdate:
return kCobaltExtensionUpdaterNotificationStateUpdateAvailable;
case ComponentState::kDownloading:
return kCobaltExtensionUpdaterNotificationStateDownloading;
case ComponentState::kDownloaded:
return kCobaltExtensionUpdaterNotificationStateDownloaded;
case ComponentState::kUpdating:
return kCobaltExtensionUpdaterNotificationStateInstalling;
#if SB_API_VERSION > 13
case ComponentState::kUpdated:
return kCobaltExtensionUpdaterNotificationStateUpdated;
case ComponentState::kUpToDate:
return kCobaltExtensionUpdaterNotificationStateUpToDate;
case ComponentState::kUpdateError:
return kCobaltExtensionUpdaterNotificationStateUpdateFailed;
#else
case ComponentState::kUpdated:
return kCobaltExtensionUpdaterNotificationStatekUpdated;
case ComponentState::kUpToDate:
return kCobaltExtensionUpdaterNotificationStatekUpToDate;
case ComponentState::kUpdateError:
return kCobaltExtensionUpdaterNotificationStatekUpdateFailed;
#endif
default:
return kCobaltExtensionUpdaterNotificationStateNone;
}
}
} // namespace
namespace cobalt {
namespace updater {
// The delay in seconds before the first update check.
const uint64_t kDefaultUpdateCheckDelaySeconds = 15;
void Observer::OnEvent(Events event, const std::string& id) {
LOG(INFO) << "Observer::OnEvent id=" << id;
std::string status;
if (update_client_->GetCrxUpdateState(id, &crx_update_item_)) {
auto status_iterator =
component_to_updater_status_map.find(crx_update_item_.state);
if (status_iterator == component_to_updater_status_map.end()) {
status = "Status is unknown.";
} else if (crx_update_item_.state == ComponentState::kUpToDate &&
updater_configurator_->GetPreviousUpdaterStatus().compare(
updater_status_string_map.find(UpdaterStatus::kUpdated)
->second) == 0) {
status = std::string(
updater_status_string_map.find(UpdaterStatus::kUpdated)->second);
} else {
status = std::string(
updater_status_string_map.find(status_iterator->second)->second);
}
if (crx_update_item_.state == ComponentState::kUpdateError) {
status +=
", error code is " + std::to_string(crx_update_item_.error_code);
}
if (updater_notification_ext_ != nullptr) {
updater_notification_ext_->UpdaterState(
ComponentStateToCobaltExtensionUpdaterNotificationState(
crx_update_item_.state),
GetCurrentEvergreenVersion().c_str());
}
} else {
status = "No status available";
}
updater_configurator_->SetUpdaterStatus(status);
LOG(INFO) << "Updater status is " << status;
}
UpdaterModule::UpdaterModule(network::NetworkModule* network_module,
uint64_t update_check_delay_sec)
: network_module_(network_module),
update_check_delay_sec_(update_check_delay_sec) {
LOG(INFO) << "UpdaterModule::UpdaterModule";
updater_thread_.reset(new base::Thread("Updater"));
updater_thread_->StartWithOptions(
base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
DETACH_FROM_THREAD(thread_checker_);
// Initialize the underlying update client.
is_updater_running_ = true;
updater_thread_->task_runner()->PostTask(
FROM_HERE,
base::Bind(&UpdaterModule::Initialize, base::Unretained(this)));
// Mark the current installation as successful.
updater_thread_->task_runner()->PostTask(
FROM_HERE,
base::Bind(&UpdaterModule::MarkSuccessful, base::Unretained(this)));
}
UpdaterModule::~UpdaterModule() {
LOG(INFO) << "UpdaterModule::~UpdaterModule";
if (is_updater_running_) {
is_updater_running_ = false;
updater_thread_->task_runner()->PostBlockingTask(
FROM_HERE,
base::Bind(&UpdaterModule::Finalize, base::Unretained(this)));
}
// Upon destruction the thread will allow all queued tasks to complete before
// the thread is terminated. The thread is destroyed before returning from
// this destructor to prevent one of the thread's tasks from accessing member
// fields after they are destroyed.
updater_thread_.reset();
}
void UpdaterModule::Suspend() {
if (is_updater_running_) {
is_updater_running_ = false;
updater_thread_->task_runner()->PostBlockingTask(
FROM_HERE,
base::Bind(&UpdaterModule::Finalize, base::Unretained(this)));
}
}
void UpdaterModule::Resume() {
if (!is_updater_running_) {
is_updater_running_ = true;
updater_thread_->task_runner()->PostTask(
FROM_HERE,
base::Bind(&UpdaterModule::Initialize, base::Unretained(this)));
}
}
void UpdaterModule::Initialize() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
updater_configurator_ = base::MakeRefCounted<Configurator>(network_module_);
update_client_ = update_client::UpdateClientFactory(updater_configurator_);
updater_observer_.reset(new Observer(update_client_, updater_configurator_));
update_client_->AddObserver(updater_observer_.get());
// Schedule the first update check.
LOG(INFO) << "Scheduling UpdateCheck with delay " << update_check_delay_sec_
<< " seconds";
updater_thread_->task_runner()->PostDelayedTask(
FROM_HERE, base::Bind(&UpdaterModule::Update, base::Unretained(this)),
base::TimeDelta::FromSeconds(update_check_delay_sec_));
}
void UpdaterModule::Finalize() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
LOG(INFO) << "UpdaterModule::Finalize begin";
update_client_->RemoveObserver(updater_observer_.get());
updater_observer_.reset();
update_client_->Stop();
update_client_ = nullptr;
if (updater_configurator_ != nullptr) {
auto pref_service = updater_configurator_->GetPrefService();
if (pref_service != nullptr) {
pref_service->CommitPendingWrite(
base::BindOnce(&QuitLoop, base::Bind(base::DoNothing::Repeatedly())));
}
}
updater_configurator_ = nullptr;
// Cleanup drain files
const auto installation_manager =
static_cast<const CobaltExtensionInstallationManagerApi*>(
SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
if (installation_manager) {
CobaltSlotManagement cobalt_slot_management;
if (cobalt_slot_management.Init(installation_manager)) {
cobalt_slot_management.CleanupAllDrainFiles();
}
}
LOG(INFO) << "UpdaterModule::Finalize end";
}
void UpdaterModule::MarkSuccessful() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
LOG(INFO) << "UpdaterModule::MarkSuccessful";
auto installation_manager =
static_cast<const CobaltExtensionInstallationManagerApi*>(
SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
if (!installation_manager) {
LOG(ERROR) << "Updater failed to get installation manager extension.";
return;
}
int index = installation_manager->GetCurrentInstallationIndex();
if (index == IM_EXT_ERROR) {
LOG(ERROR) << "Updater failed to get current installation index.";
return;
}
if (installation_manager->MarkInstallationSuccessful(index) !=
IM_EXT_SUCCESS) {
LOG(ERROR) << "Updater failed to mark the current installation successful";
}
}
void UpdaterModule::Update() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// If updater_configurator_ is nullptr, the updater is suspended.
if (updater_configurator_ == nullptr) {
return;
}
const std::vector<std::string> app_ids = {
updater_configurator_->GetAppGuid()};
const base::Version manifest_version(GetCurrentEvergreenVersion());
if (!manifest_version.IsValid()) {
LOG(ERROR) << "Updater failed to get the current update version.";
return;
}
update_client_->Update(
app_ids,
base::BindOnce(
[](base::Version manifest_version,
const std::vector<std::string>& ids)
-> std::vector<base::Optional<update_client::CrxComponent>> {
update_client::CrxComponent component;
component.name = "cobalt";
component.app_id = ids[0];
component.version = manifest_version;
component.pk_hash.assign(std::begin(kCobaltPublicKeyHash),
std::end(kCobaltPublicKeyHash));
component.requires_network_encryption = true;
component.crx_format_requirement = crx_file::VerifierFormat::CRX3;
return {component};
},
manifest_version),
false,
base::BindOnce(
[](base::OnceClosure closure, update_client::Error error) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&QuitLoop, std::move(closure)));
},
base::Bind(base::DoNothing::Repeatedly())));
IncrementUpdateCheckCount();
int kNextUpdateCheckHours = 0;
if (GetUpdateCheckCount() == 1) {
// Update check ran once. The next check will be scheduled at a randomized
// time between 1 and 24 hours.
kNextUpdateCheckHours = base::RandInt(1, 24);
} else {
// Update check ran at least twice. The next check will be scheduled in 24
// hours.
kNextUpdateCheckHours = 24;
}
updater_thread_->task_runner()->PostDelayedTask(
FROM_HERE, base::Bind(&UpdaterModule::Update, base::Unretained(this)),
base::TimeDelta::FromHours(kNextUpdateCheckHours));
}
// The following methods are called by other threads than the updater_thread_.
void UpdaterModule::CompareAndSwapChannelChanged(int old_value, int new_value) {
auto config = updater_configurator_;
if (config) config->CompareAndSwapChannelChanged(old_value, new_value);
}
std::string UpdaterModule::GetUpdaterChannel() const {
LOG(INFO) << "UpdaterModule::GetUpdaterChannel";
auto config = updater_configurator_;
if (!config) {
LOG(ERROR) << "UpdaterModule::GetUpdaterChannel: missing config";
return "";
}
std::string channel = config->GetChannel();
LOG(INFO) << "UpdaterModule::GetUpdaterChannel channel=" << channel;
return channel;
}
void UpdaterModule::SetUpdaterChannel(const std::string& updater_channel) {
LOG(INFO) << "UpdaterModule::SetUpdaterChannel updater_channel="
<< updater_channel;
auto config = updater_configurator_;
if (config) config->SetChannel(updater_channel);
}
std::string UpdaterModule::GetUpdaterStatus() const {
LOG(INFO) << "UpdaterModule::GetUpdaterStatus";
auto config = updater_configurator_;
if (!config) {
LOG(ERROR) << "UpdaterModule::GetUpdaterStatus: missing configuration";
return "";
}
std::string updater_status = config->GetUpdaterStatus();
LOG(INFO) << "UpdaterModule::GetUpdaterStatus updater_status="
<< updater_status;
return updater_status;
}
void UpdaterModule::RunUpdateCheck() {
updater_thread_->task_runner()->PostTask(
FROM_HERE, base::Bind(&UpdaterModule::Update, base::Unretained(this)));
}
void UpdaterModule::ResetInstallations() {
auto installation_manager =
static_cast<const CobaltExtensionInstallationManagerApi*>(
SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
if (!installation_manager) {
LOG(ERROR) << "Updater failed to get installation manager extension.";
return;
}
if (installation_manager->Reset() == IM_EXT_ERROR) {
LOG(ERROR) << "Updater failed to reset installations.";
return;
}
base::FilePath product_data_dir;
if (!GetProductDirectoryPath(&product_data_dir)) {
LOG(ERROR) << "Updater failed to get product directory path.";
return;
}
if (!starboard::SbFileDeleteRecursive(product_data_dir.value().c_str(),
true)) {
LOG(ERROR) << "Updater failed to clean the product directory.";
return;
}
}
int UpdaterModule::GetInstallationIndex() const {
auto installation_manager =
static_cast<const CobaltExtensionInstallationManagerApi*>(
SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
if (!installation_manager) {
LOG(ERROR) << "Updater failed to get installation manager extension.";
return -1;
}
int index = installation_manager->GetCurrentInstallationIndex();
if (index == IM_EXT_ERROR) {
LOG(ERROR) << "Updater failed to get current installation index.";
return -1;
}
return index;
}
void UpdaterModule::SetMinFreeSpaceBytes(uint64_t bytes) {
LOG(INFO) << "UpdaterModule::SetMinFreeSpaceBytes bytes=" << bytes;
if (updater_configurator_) {
updater_configurator_->SetMinFreeSpaceBytes(bytes);
}
}
} // namespace updater
} // namespace cobalt