blob: 2f00c8acbc8a48b44ef555d3f4a43f329e765fd6 [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 <map>
#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/task/post_task.h"
#include "base/task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/version.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/utils.h"
#include "starboard/configuration_constants.h"
namespace {
using update_client::ComponentState;
// 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};
// The map to translate updater status from enum to readable string.
const std::map<ComponentState, const char*> updater_status_map = {
{ComponentState::kNew, "Will check for update soon"},
{ComponentState::kChecking, "Checking for update"},
{ComponentState::kCanUpdate, "Update is available"},
{ComponentState::kDownloadingDiff, "Downloading delta update"},
{ComponentState::kDownloading, "Downloading update"},
{ComponentState::kDownloaded, "Update is downloaded"},
{ComponentState::kUpdatingDiff, "Installing delta update"},
{ComponentState::kUpdating, "Installing update"},
{ComponentState::kUpdated, "Update installed, pending restart"},
{ComponentState::kUpToDate, "App is up to date"},
{ComponentState::kUpdateError, "Failed to update"},
{ComponentState::kUninstalled, "Update uninstalled"},
{ComponentState::kRun, "Transitioning..."},
// ComponentState::kLastStatus is not meaningful to show to users.
};
void QuitLoop(base::OnceClosure quit_closure) { std::move(quit_closure).Run(); }
} // namespace
namespace cobalt {
namespace updater {
void Observer::OnEvent(Events event, const std::string& id) {
std::string status;
if (update_client_->GetCrxUpdateState(id, &crx_update_item_)) {
auto status_iterator = updater_status_map.find(crx_update_item_.state);
if (status_iterator == updater_status_map.end()) {
status = "Status is unknown.";
} else {
status = std::string(status_iterator->second);
}
if (crx_update_item_.state == ComponentState::kUpdateError) {
status +=
", error code is " + std::to_string(crx_update_item_.error_code);
}
} else {
status = "No status available";
}
updater_configurator_->SetUpdaterStatus(status);
SB_LOG(INFO) << "Updater status is " << status;
}
UpdaterModule::UpdaterModule(network::NetworkModule* network_module)
: updater_thread_("updater"), network_module_(network_module) {
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)));
}
UpdaterModule::~UpdaterModule() {
if (is_updater_running_) {
is_updater_running_ = false;
updater_thread_.task_runner()->PostBlockingTask(
FROM_HERE,
base::Bind(&UpdaterModule::Finalize, base::Unretained(this)));
}
}
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.
updater_thread_.task_runner()->PostTask(
FROM_HERE, base::Bind(&UpdaterModule::Update, base::Unretained(this)));
}
void UpdaterModule::Finalize() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
update_client_->RemoveObserver(updater_observer_.get());
updater_observer_.reset();
update_client_ = nullptr;
updater_configurator_->GetPrefService()->CommitPendingWrite(
base::BindOnce(&QuitLoop, base::Bind(base::DoNothing::Repeatedly())));
updater_configurator_ = nullptr;
}
void UpdaterModule::MarkSuccessful() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
auto installation_manager =
static_cast<const CobaltExtensionInstallationManagerApi*>(
SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
if (!installation_manager) {
SB_LOG(ERROR) << "Updater failed to get installation manager extension.";
return;
}
int index = installation_manager->GetCurrentInstallationIndex();
if (index == IM_EXT_ERROR) {
SB_LOG(ERROR) << "Updater failed to get current installation index.";
return;
}
if (installation_manager->MarkInstallationSuccessful(index) !=
IM_EXT_SUCCESS) {
SB_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()) {
SB_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())));
// Mark the current installation as successful.
updater_thread_.task_runner()->PostTask(
FROM_HERE,
base::Bind(&UpdaterModule::MarkSuccessful, base::Unretained(this)));
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 three methods all called by the main web module thread.
std::string UpdaterModule::GetUpdaterChannel() const {
return updater_configurator_->GetChannel();
}
void UpdaterModule::SetUpdaterChannel(const std::string& updater_channel) {
updater_configurator_->SetChannel(updater_channel);
}
void UpdaterModule::RunUpdateCheck() {
updater_thread_.task_runner()->PostTask(
FROM_HERE, base::Bind(&UpdaterModule::Update, base::Unretained(this)));
}
} // namespace updater
} // namespace cobalt