blob: 4388b981c9f8d2218a2e537aeb1d6207b429a9e0 [file] [log] [blame]
//
// Copyright (C) 2022 The Android Open Source Project
//
// 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 "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 kDefaultArtifact[] = "dlc.img";
constexpr char kDefaultPackage[] = "package";
constexpr char kDefaultSlotting[] = "dlc-scaled";
} // namespace
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);
// 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;
if (!boot_control->GetPartitionDevice(
partition_name, boot_control->GetCurrentSlot(), &partition)) {
LOG(ERROR) << "Could not retrieve device partition for " << id_;
processor_->ActionComplete(this, ErrorCode::kScaledInstallationError);
return;
}
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 " << id_ << " at "
<< partition;
processor_->ActionComplete(this, ErrorCode::kScaledInstallationError);
return;
}
LOG(INFO) << "Installing to " << partition;
std::string url_to_fetch;
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);
backup_urls_ = {UrlToFetch(kLorryArtifactsMetaUrl)};
} 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);
backup_urls_ = {UrlToFetch(kLorryUrl)};
}
StartInstallation(url_to_fetch);
}
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_;
StartInstallation(backup_urls_[backup_url_index_++]);
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 (" << expected_offset
<< ").";
TerminateInstallation();
return;
}
LOG(INFO) << "Transferred bytes offset (" << expected_offset << ") 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 (" << expected_sha256_str
<< ").";
TerminateInstallation();
return;
}
LOG(INFO) << "Transferred bytes hash (" << expected_sha256_str
<< ") 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) {
LOG(INFO) << "Starting installation using URL=" << url_to_fetch;
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