blob: 34a6dbd59ea33a328f5acede1f5f9de909a002bd [file] [log] [blame] [edit]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "update_engine/cros/install_action.h"
#include <inttypes.h>
#include <string>
#include <utility>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <chromeos/constants/imageloader.h>
#include <crypto/secure_hash.h>
#include <crypto/sha2.h>
#include <libimageloader/manifest.h>
#include "update_engine/common/boot_control.h"
#include "update_engine/common/dlcservice_interface.h"
#include "update_engine/common/system_state.h"
#include "update_engine/common/utils.h"
#include "update_engine/cros/image_properties.h"
namespace chromeos_update_engine {
namespace {
constexpr char kBandaidUrl[] = "https://edgedl.me.gvt1.com/edgedl/dlc";
constexpr char kLorryUrl[] = "https://dl.google.com/dlc";
constexpr char kBandaidArtifactsMetaUrl[] = "https://edgedl.me.gvt1.com/edgedl";
constexpr char kLorryArtifactsMetaUrl[] = "https://dl.google.com";
constexpr char kUrlNameBandaid[] = "<BANDAID>";
constexpr char kUrlNameLorry[] = "<LORRY>";
constexpr char kUrlNameBandaidArtifactsMeta[] = "<BANDAID_ARTIFACTS_META>";
constexpr char kUrlNameLorryArtifactsMeta[] = "<LORRY_ARTIFACTS_META>";
constexpr char kDefaultArtifact[] = "dlc.img";
constexpr char kDefaultPackage[] = "package";
constexpr char kRedactedDlcPartition[] = "<REDACTED_PARTITION>";
} // namespace
const char kDefaultSlotting[] = "dlc-scaled";
const char kForceOTASlotting[] = "dlc";
InstallAction::InstallAction(std::unique_ptr<HttpFetcher> http_fetcher,
const std::string& id,
const std::string& slotting,
const std::string& manifest_dir)
: http_fetcher_(std::move(http_fetcher)), id_(id) {
slotting_ = slotting.empty() ? kDefaultSlotting : slotting;
manifest_dir_ =
manifest_dir.empty() ? imageloader::kDlcManifestRootpath : manifest_dir;
}
InstallAction::~InstallAction() {}
void InstallAction::PerformAction() {
LOG(INFO) << "InstallAction performing action.";
manifest_ = SystemState::Get()->dlc_utils()->GetDlcManifest(
id_, base::FilePath(manifest_dir_));
if (!manifest_) {
LOG(ERROR) << "Could not retrieve manifest for " << id_;
processor_->ActionComplete(this, ErrorCode::kScaledInstallationError);
return;
}
image_props_ = LoadImageProperties();
http_fetcher_->set_delegate(this);
bool user_tied = manifest_->user_tied();
if (user_tied)
http_fetcher_->set_payload_info_visible(false);
// Get the DLC device partition.
auto partition_name =
base::FilePath("dlc").Append(id_).Append(kDefaultPackage).value();
auto* boot_control = SystemState::Get()->boot_control();
std::string partition;
const auto& sanitized_id = manifest_->sanitized_id();
if (!boot_control->GetPartitionDevice(
partition_name, boot_control->GetCurrentSlot(), &partition)) {
LOG(ERROR) << "Could not retrieve device partition for " << sanitized_id;
processor_->ActionComplete(this, ErrorCode::kScaledInstallationError);
return;
}
const auto& sanitized_partition =
user_tied ? kRedactedDlcPartition : partition;
f_.Initialize(base::FilePath(partition), base::File::Flags::FLAG_OPEN |
base::File::Flags::FLAG_READ |
base::File::Flags::FLAG_WRITE);
if (!f_.IsValid()) {
LOG(ERROR) << "Could not open device partition for " << sanitized_id
<< " at " << sanitized_partition;
processor_->ActionComplete(this, ErrorCode::kScaledInstallationError);
return;
}
LOG(INFO) << "Installing to " << sanitized_partition;
std::string url_to_fetch;
std::string sanitized_url;
const auto& artifacts_meta = manifest_->artifacts_meta();
if (artifacts_meta.valid) {
auto UrlToFetch = [artifacts_meta](const std::string& url) -> std::string {
return base::FilePath(url)
.Append(artifacts_meta.uri)
.Append(kDefaultArtifact)
.value();
};
url_to_fetch = UrlToFetch(kBandaidArtifactsMetaUrl);
sanitized_url = user_tied ? kUrlNameBandaidArtifactsMeta : url_to_fetch;
backup_urls_ = {UrlToFetch(kLorryArtifactsMetaUrl)};
backup_urls_sanitized_ =
user_tied ? std::vector<std::string>({kUrlNameLorryArtifactsMeta})
: backup_urls_;
} else {
auto UrlToFetch = [this](const std::string& url) -> std::string {
return base::FilePath(url)
.Append(image_props_.builder_path)
.Append(slotting_)
.Append(id_)
.Append(kDefaultPackage)
.Append(kDefaultArtifact)
.value();
};
url_to_fetch = UrlToFetch(kBandaidUrl);
sanitized_url = user_tied ? kUrlNameBandaid : url_to_fetch;
backup_urls_ = {UrlToFetch(kLorryUrl)};
backup_urls_sanitized_ =
user_tied ? std::vector<std::string>({kUrlNameLorry}) : backup_urls_;
}
StartInstallation(url_to_fetch, sanitized_url);
}
void InstallAction::TerminateProcessing() {
http_fetcher_->TerminateTransfer();
}
bool InstallAction::ReceivedBytes(HttpFetcher* fetcher,
const void* bytes,
size_t length) {
uint64_t new_offset = offset_ + length;
// Overflow upper bound check against manifest.
if (new_offset > manifest_->size()) {
LOG(ERROR) << "Overflow of bytes, terminating.";
http_fetcher_->TerminateTransfer();
return false;
}
if (delegate()) {
delegate_->BytesReceived(new_offset, manifest_->size());
}
hash_->Update(bytes, length);
int64_t total_written_bytes = 0;
do {
int written_bytes =
f_.Write(offset_ + total_written_bytes,
static_cast<const char*>(bytes) + total_written_bytes,
length - total_written_bytes);
if (written_bytes == -1) {
PLOG(ERROR) << "Failed to write bytes.";
http_fetcher_->TerminateTransfer();
return false;
}
total_written_bytes += written_bytes;
} while (total_written_bytes != length);
offset_ = new_offset;
return true;
}
void InstallAction::TransferComplete(HttpFetcher* fetcher, bool successful) {
if (!successful) {
// Continue to use backup URLs.
if (backup_url_index_ < backup_urls_.size()) {
LOG(INFO) << "Using backup url at index=" << backup_url_index_;
const auto& url = backup_urls_[backup_url_index_];
const auto& url_sanitized = backup_urls_sanitized_[backup_url_index_];
++backup_url_index_;
StartInstallation(url, url_sanitized);
return;
}
LOG(ERROR) << "Transfer failed.";
TerminateInstallation();
return;
}
auto expected_offset = manifest_->size();
if (offset_ != expected_offset) {
LOG(ERROR) << "Transferred bytes offset (" << offset_
<< ") don't match the expected offset ("
<< manifest_->sanitized_size() << ").";
TerminateInstallation();
return;
}
LOG(INFO) << "Transferred bytes offset (" << manifest_->sanitized_size()
<< ") is valid.";
std::vector<uint8_t> sha256(crypto::kSHA256Length);
hash_->Finish(sha256.data(), sha256.size());
auto expected_sha256 = manifest_->image_sha256();
auto expected_sha256_str =
base::HexEncode(expected_sha256.data(), expected_sha256.size());
if (sha256 != expected_sha256) {
LOG(ERROR) << "Transferred bytes hash ("
<< base::HexEncode(sha256.data(), sha256.size())
<< ") don't match the expected hash ("
<< manifest_->sanitized_image_sha256() << ").";
TerminateInstallation();
return;
}
LOG(INFO) << "Transferred bytes hash (" << manifest_->sanitized_image_sha256()
<< ") is valid.";
processor_->ActionComplete(this, ErrorCode::kSuccess);
}
void InstallAction::TransferTerminated(HttpFetcher* fetcher) {
LOG(ERROR) << "Failed to complete transfer.";
TerminateInstallation();
}
void InstallAction::StartInstallation(const std::string& url_to_fetch,
const std::string& sanitized_url) {
LOG(INFO) << "Starting installation using URL=" << sanitized_url;
offset_ = 0;
hash_.reset(crypto::SecureHash::Create(crypto::SecureHash::SHA256));
http_fetcher_->SetOffset(0);
http_fetcher_->UnsetLength();
http_fetcher_->BeginTransfer(url_to_fetch);
}
void InstallAction::TerminateInstallation() {
processor_->ActionComplete(this, ErrorCode::kScaledInstallationError);
}
} // namespace chromeos_update_engine