blob: d0e9568f56764645fbd38e027881ebe18f99e8ff [file] [log] [blame]
// Copyright 2020 The Chromium OS 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 "dlcservice/dlc.h"
#include <algorithm>
#include <cinttypes>
#include <memory>
#include <utility>
#include <vector>
#include <base/strings/stringprintf.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include <brillo/errors/error.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/dlcservice/dbus-constants.h>
#include "dlcservice/error.h"
#include "dlcservice/prefs.h"
#include "dlcservice/system_state.h"
#include "dlcservice/utils.h"
using base::FilePath;
using brillo::ErrorPtr;
using std::string;
using std::vector;
namespace dlcservice {
// static
vector<FilePath> DlcBase::GetPathsToDelete(const DlcId& id) {
const auto* system_state = SystemState::Get();
return {JoinPaths(system_state->content_dir(), id),
JoinPaths(system_state->dlc_prefs_dir(), id)};
}
// TODO(ahassani): Instead of initialize function, create a factory method so
// we can develop different types of DLC classes.
bool DlcBase::Initialize() {
const auto* system_state = SystemState::Get();
const auto& manifest_dir = system_state->manifest_dir();
package_ = *ScanDirectory(manifest_dir.Append(id_)).begin();
if (!GetDlcManifest(system_state->manifest_dir(), id_, package_,
&manifest_)) {
// Failing to read the manifest will be considered a blocker.
LOG(ERROR) << "Failed to read the manifest of DLC " << id_;
return false;
}
const auto& content_dir = system_state->content_dir();
content_id_path_ = JoinPaths(content_dir, id_);
content_package_path_ = JoinPaths(content_id_path_, package_);
prefs_path_ = JoinPaths(system_state->dlc_prefs_dir(), id_);
prefs_package_path_ = JoinPaths(prefs_path_, package_);
preloaded_image_path_ = JoinPaths(system_state->preloaded_content_dir(), id_,
package_, kDlcImageFileName);
ref_count_ = RefCountInterface::Create(manifest_.used_by(), prefs_path_);
state_.set_state(DlcState::NOT_INSTALLED);
state_.set_id(id_);
state_.set_progress(0);
state_.set_last_error_code(kErrorNone);
if (manifest_.mount_file_required()) {
if (!Prefs(prefs_package_path_).Delete(kDlcRootMount))
LOG(ERROR)
<< "Failed to delete indirect root mount file during Initialization: "
<< JoinPaths(prefs_package_path_, kDlcRootMount);
}
state_.set_is_verified(
Prefs(*this, system_state->active_boot_slot()).Exists(kDlcPrefVerified));
return true;
}
const DlcId& DlcBase::GetId() const {
return id_;
}
const std::string& DlcBase::GetName() const {
return manifest_.name();
}
const std::string& DlcBase::GetDescription() const {
return manifest_.description();
}
DlcState DlcBase::GetState() const {
return state_;
}
bool DlcBase::IsInstalling() const {
return state_.state() == DlcState::INSTALLING;
}
bool DlcBase::IsInstalled() const {
return state_.state() == DlcState::INSTALLED;
}
bool DlcBase::IsVerified() const {
return state_.is_verified();
}
bool DlcBase::HasContent() const {
for (const auto& path :
{GetImagePath(BootSlot::Slot::A), GetImagePath(BootSlot::Slot::B)}) {
if (base::PathExists(path))
return true;
}
return false;
}
uint64_t DlcBase::GetUsedBytesOnDisk() const {
uint64_t total_size = 0;
for (const auto& path :
{GetImagePath(BootSlot::Slot::A), GetImagePath(BootSlot::Slot::B)}) {
if (!base::PathExists(path))
continue;
int64_t size = 0;
if (!base::GetFileSize(path, &size)) {
LOG(WARNING) << "Failed to get file size for path: " << path.value();
}
total_size += size;
}
return total_size;
}
bool DlcBase::IsPreloadAllowed() const {
return manifest_.preload_allowed() &&
!SystemState::Get()->system_properties()->IsOfficialBuild();
}
base::FilePath DlcBase::GetRoot() const {
if (mount_point_.empty())
return {};
return JoinPaths(mount_point_, kRootDirectoryInsideDlcModule);
}
bool DlcBase::InstallCompleted(ErrorPtr* err) {
if (!MarkVerified()) {
state_.set_last_error_code(kErrorInternal);
*err = Error::Create(
FROM_HERE, state_.last_error_code(),
base::StringPrintf("Failed to mark active DLC=%s as verified.",
id_.c_str()));
return false;
}
return true;
}
bool DlcBase::UpdateCompleted(ErrorPtr* err) {
if (!Prefs(*this, SystemState::Get()->inactive_boot_slot())
.Create(kDlcPrefVerified)) {
*err = Error::Create(
FROM_HERE, kErrorInternal,
base::StringPrintf("Failed to mark inactive DLC=%s as verified.",
id_.c_str()));
return false;
}
return true;
}
FilePath DlcBase::GetImagePath(BootSlot::Slot slot) const {
return JoinPaths(content_package_path_, BootSlot::ToString(slot),
kDlcImageFileName);
}
bool DlcBase::CreateDlc(ErrorPtr* err) {
// Create content directories.
for (const auto& path :
{content_id_path_, content_package_path_, prefs_path_}) {
if (!CreateDir(path)) {
*err = Error::CreateInternal(
FROM_HERE, error::kFailedToCreateDirectory,
base::StringPrintf("Failed to create directory %s for DLC=%s",
path.value().c_str(), id_.c_str()));
state_.set_last_error_code(Error::GetDbusErrorCode(*err));
return false;
}
}
// Creates image A and B.
for (const auto& slot : {BootSlot::Slot::A, BootSlot::Slot::B}) {
FilePath image_path = GetImagePath(slot);
if (!CreateFile(image_path, manifest_.preallocated_size())) {
state_.set_last_error_code(kErrorAllocation);
*err = Error::Create(
FROM_HERE, state_.last_error_code(),
base::StringPrintf("Failed to create image file %s for DLC=%s",
image_path.value().c_str(), id_.c_str()));
return false;
}
}
return true;
}
bool DlcBase::MakeReadyForUpdate() const {
// Deleting the inactive verified pref should always happen before anything
// else here otherwise if we failed to delete, on a reboot after an update, we
// might assume the image is verified, which is not.
if (!Prefs(*this, SystemState::Get()->inactive_boot_slot())
.Delete(kDlcPrefVerified)) {
PLOG(ERROR) << "Failed to mark inactive DLC=" << id_ << " as not-verified.";
return false;
}
if (!IsVerified()) {
return false;
}
const FilePath& inactive_image_path =
GetImagePath(SystemState::Get()->inactive_boot_slot());
if (!CreateFile(inactive_image_path, manifest_.preallocated_size())) {
LOG(ERROR) << "Failed to create inactive image "
<< inactive_image_path.value() << " when making DLC=" << id_
<< " ready for update.";
return false;
}
return true;
}
bool DlcBase::MarkVerified() {
state_.set_is_verified(true);
return Prefs(*this, SystemState::Get()->active_boot_slot())
.Create(kDlcPrefVerified);
}
bool DlcBase::MarkUnverified() {
state_.set_is_verified(false);
return Prefs(*this, SystemState::Get()->active_boot_slot())
.Delete(kDlcPrefVerified);
}
bool DlcBase::Verify() {
auto image_path = GetImagePath(SystemState::Get()->active_boot_slot());
vector<uint8_t> image_sha256;
if (!HashFile(image_path, manifest_.size(), &image_sha256)) {
LOG(ERROR) << "Failed to hash image file: " << image_path.value();
return false;
}
const auto& manifest_image_sha256 = manifest_.image_sha256();
if (image_sha256 != manifest_image_sha256) {
LOG(WARNING) << "Verification failed for image file: " << image_path.value()
<< ". Expected: "
<< base::HexEncode(manifest_image_sha256.data(),
manifest_image_sha256.size())
<< " Found: "
<< base::HexEncode(image_sha256.data(), image_sha256.size());
return false;
}
if (!MarkVerified()) {
LOG(WARNING) << "Failed to mark the image as verified, but temporarily"
<< " we assume the image is verified.";
}
return true;
}
bool DlcBase::PreloadedCopier(ErrorPtr* err) {
int64_t preloaded_image_size;
if (!base::GetFileSize(preloaded_image_path_, &preloaded_image_size)) {
auto err_str = base::StringPrintf("Failed to get preloaded DLC (%s) size.",
id_.c_str());
*err = Error::Create(FROM_HERE, kErrorInternal, err_str);
return false;
}
if (preloaded_image_size != manifest_.size()) {
auto err_str = base::StringPrintf(
"Preloaded DLC (%s) is (%" PRId64 ") different than the size (%" PRId64
") in the manifest.",
id_.c_str(), preloaded_image_size, manifest_.size());
*err = Error::Create(FROM_HERE, kErrorInternal, err_str);
return false;
}
// Before touching the image, we need to mark it as unverified.
MarkUnverified();
// TODO(kimjae): When preloaded images are place into unencrypted, this
// operation can be a move.
FilePath image_path = GetImagePath(SystemState::Get()->active_boot_slot());
vector<uint8_t> image_sha256;
if (!CopyAndHashFile(preloaded_image_path_, image_path, manifest_.size(),
&image_sha256)) {
auto err_str =
base::StringPrintf("Failed to copy preload DLC (%s) into path %s",
id_.c_str(), image_path.value().c_str());
*err = Error::Create(FROM_HERE, kErrorInternal, err_str);
return false;
}
auto manifest_image_sha256 = manifest_.image_sha256();
if (image_sha256 != manifest_image_sha256) {
auto err_str = base::StringPrintf(
"Image is corrupted or modified for DLC=%s. Expected: %s Found: %s",
id_.c_str(),
base::HexEncode(manifest_image_sha256.data(),
manifest_image_sha256.size())
.c_str(),
base::HexEncode(image_sha256.data(), image_sha256.size()).c_str());
*err = Error::Create(FROM_HERE, kErrorInternal, err_str);
return false;
}
if (!MarkVerified())
LOG(ERROR) << "Failed to mark the image verified for DLC=" << id_;
return true;
}
bool DlcBase::Install(ErrorPtr* err) {
switch (state_.state()) {
case DlcState::NOT_INSTALLED: {
bool active_image_existed = IsActiveImagePresent();
// Always try to create the DLC files and directories to make sure they
// all exist before we start the install.
if (!CreateDlc(err)) {
ErrorPtr tmp_err;
if (!CancelInstall(*err, &tmp_err))
LOG(ERROR) << "Failed to cancel the install correctly.";
return false;
}
// Only set the DLC installing after creation is successful to have finer
// control of state changes.
ChangeState(DlcState::INSTALLING);
// Finish the installation for verified images so they can be mounted.
if (IsVerified()) {
LOG(INFO) << "Installing already verified DLC=" << id_;
break;
}
// Try verifying images that already existed before creation. If verified,
// finish the installation so they can be mounted.
if (active_image_existed && Verify()) {
LOG(INFO) << "Verified existing, but previously not verified DLC="
<< id_;
break;
}
// Preload the DLC if possible.
if (IsPreloadAllowed() && base::PathExists(preloaded_image_path_)) {
if (!PreloadedCopier(err)) {
LOG(ERROR)
<< "Preloading failed, so assuming installation failed for DLC="
<< id_;
ErrorPtr tmp_err;
if (!CancelInstall(*err, &tmp_err))
LOG(ERROR) << "Failed to cancel the install from preloading.";
return false;
}
LOG(INFO) << "Preloading DLC=" << id_;
break;
}
// By now the image is not verified, so it needs to be installed
// through update_engine. So don't go any further.
return true;
}
case DlcState::INSTALLING:
// If the image is already in this state, nothing need to be done. It is
// already being installed.
// Skip reporting this scenario to the metrics, since the Install call
// might be from the same client, and reporting this is not useful.
return true;
case DlcState::INSTALLED:
// If the image is already installed, we need to finish the install so it
// gets mounted in case it has been unmounted externally.
break;
default:
NOTREACHED();
return false;
}
// Let's try to finish the installation.
if (!FinishInstall(/*installed_by_ue=*/false, err)) {
return false;
}
// Note: Don't remove preloaded DLC images. F20 transition to provision DLC
// images will allow for preloading to be deprecated.
return true;
}
bool DlcBase::FinishInstall(bool installed_by_ue, ErrorPtr* err) {
DCHECK(err);
DCHECK(err->get() == NULL); // Check there is no error set.
switch (state_.state()) {
case DlcState::INSTALLED:
case DlcState::INSTALLING:
if (!IsVerified()) {
// If the image is not verified, try to verify it. This is to combat
// update_engine failing to call into |InstallCompleted()| even after a
// successful DLC installation.
if (Verify()) {
LOG(WARNING) << "Missing verification mark for DLC=" << id_
<< ", but verified to be a valid image.";
}
}
if (IsVerified()) {
if (Mount(err))
break;
// Do not |CancelInstall| on mount failure.
state_.set_last_error_code(Error::GetDbusErrorCode(*err));
ChangeState(DlcState::NOT_INSTALLED);
MarkUnverified();
SystemState::Get()->metrics()->SendInstallResultFailure(err);
LOG(ERROR) << "Mount failed during install finalization for DLC="
<< id_;
return false;
} else {
// The error is empty since verification was not successful.
*err = Error::CreateInternal(
FROM_HERE, error::kFailedToVerifyImage,
base::StringPrintf("Cannot verify image for DLC=%s", id_.c_str()));
SystemState::Get()->metrics()->SendInstallResultFailure(err);
ErrorPtr tmp_err;
if (!CancelInstall(*err, &tmp_err))
LOG(ERROR) << "Failed during install finalization for DLC=" << id_;
return false;
}
case DlcState::NOT_INSTALLED:
// Should not try to finish install on a not-installed DLC.
default:
NOTREACHED();
return false;
}
// Increase the ref count.
ref_count_->InstalledDlc();
// Now that we are sure the image is installed, we can go ahead and set it as
// active. Failure to set the metadata flags should not fail the install.
SetActiveValue(true);
SystemState::Get()->metrics()->SendInstallResultSuccess(installed_by_ue);
return true;
}
bool DlcBase::CancelInstall(const ErrorPtr& err_in, ErrorPtr* err) {
state_.set_last_error_code(Error::GetDbusErrorCode(err_in));
ChangeState(DlcState::NOT_INSTALLED);
// Consider as not installed even if delete fails below, correct errors
// will be propagated later and should not block on further installs.
if (!DeleteInternal(err)) {
LOG(ERROR) << "Failed during install cancellation for DLC=" << id_;
return false;
}
return true;
}
bool DlcBase::Mount(ErrorPtr* err) {
string mount_point;
if (!SystemState::Get()->image_loader()->LoadDlcImage(
id_, package_,
SystemState::Get()->active_boot_slot() == BootSlot::Slot::A
? imageloader::kSlotNameA
: imageloader::kSlotNameB,
&mount_point, nullptr)) {
*err =
Error::CreateInternal(FROM_HERE, error::kFailedToMountImage,
"Imageloader is unavailable for LoadDlcImage().");
state_.set_last_error_code(Error::GetDbusErrorCode(*err));
return false;
}
if (mount_point.empty()) {
*err = Error::CreateInternal(FROM_HERE, error::kFailedToMountImage,
"Imageloader LoadDlcImage() call failed.");
state_.set_last_error_code(Error::GetDbusErrorCode(*err));
return false;
}
mount_point_ = FilePath(mount_point);
// Creates a file which holds the root mount path, allowing for indirect
// access for processes/scripts which can't access DBus.
if (manifest_.mount_file_required() &&
!Prefs(prefs_package_path_).SetKey(kDlcRootMount, GetRoot().value())) {
// TODO(kimjae): Test this by injecting |Prefs| class.
LOG(ERROR) << "Failed to create indirect root mount file: "
<< JoinPaths(prefs_package_path_, kDlcRootMount);
ErrorPtr tmp_err;
Unmount(&tmp_err);
return false;
}
ChangeState(DlcState::INSTALLED);
return true;
}
bool DlcBase::Unmount(ErrorPtr* err) {
bool success = false;
if (!SystemState::Get()->image_loader()->UnloadDlcImage(id_, package_,
&success, nullptr)) {
state_.set_last_error_code(kErrorInternal);
*err = Error::Create(FROM_HERE, state_.last_error_code(),
"Imageloader is unavailable for UnloadDlcImage().");
return false;
}
if (!success) {
state_.set_last_error_code(kErrorInternal);
*err = Error::Create(FROM_HERE, state_.last_error_code(),
"Imageloader UnloadDlcImage() call failed.");
return false;
}
if (manifest_.mount_file_required()) {
if (!Prefs(prefs_package_path_).Delete(kDlcRootMount))
LOG(ERROR) << "Failed to delete indirect root mount file: "
<< JoinPaths(prefs_package_path_, kDlcRootMount);
}
mount_point_.clear();
return true;
}
bool DlcBase::IsActiveImagePresent() const {
return base::PathExists(GetImagePath(SystemState::Get()->active_boot_slot()));
}
// Deletes all directories related to this DLC.
bool DlcBase::DeleteInternal(ErrorPtr* err) {
// If we're deleting the image, we need to set it as unverified.
MarkUnverified();
vector<string> undeleted_paths;
for (const auto& path : GetPathsToDelete(id_)) {
if (base::PathExists(path)) {
if (!base::DeletePathRecursively(path)) {
PLOG(ERROR) << "Failed to delete path=" << path;
undeleted_paths.push_back(path.value());
} else {
LOG(INFO) << "Deleted path=" << path;
}
}
}
if (!undeleted_paths.empty()) {
state_.set_last_error_code(kErrorInternal);
*err = Error::Create(
FROM_HERE, state_.last_error_code(),
base::StringPrintf("DLC directories (%s) could not be deleted.",
base::JoinString(undeleted_paths, ",").c_str()));
return false;
}
return true;
}
bool DlcBase::Uninstall(ErrorPtr* err) {
switch (state_.state()) {
case DlcState::NOT_INSTALLED:
// We still have to uninstall the DLC, in case we never mounted in this
// session.
LOG(WARNING) << "Trying to uninstall not installed DLC=" << id_;
FALLTHROUGH;
case DlcState::INSTALLED: {
ref_count_->UninstalledDlc();
ErrorPtr tmp_err;
Unmount(&tmp_err);
ChangeState(DlcState::NOT_INSTALLED);
break;
}
case DlcState::INSTALLING:
// We cannot uninstall the image while it is being installed by the
// update_engine.
state_.set_last_error_code(kErrorBusy);
*err = Error::Create(
FROM_HERE, state_.last_error_code(),
base::StringPrintf("Trying to uninstall an installing DLC=%s",
id_.c_str()));
return false;
default:
NOTREACHED();
return false;
}
return true;
}
bool DlcBase::Purge(ErrorPtr* err) {
// If the DLC is not verified, its not being updated, so there is no danger
// purging it.
auto ue_operation =
SystemState::Get()->update_engine_status().current_operation();
bool ue_is_busy = ue_operation != update_engine::IDLE &&
ue_operation != update_engine::UPDATED_NEED_REBOOT;
if (IsVerified() && ue_is_busy) {
*err = Error::Create(FROM_HERE, kErrorBusy,
"Install or update is in progress.");
return false;
}
if (!Uninstall(err))
return false;
SetActiveValue(false);
return DeleteInternal(err);
}
bool DlcBase::ShouldPurge() {
// We can only automatically purge a DLC that is not installed.
return state_.state() == DlcState::NOT_INSTALLED &&
ref_count_->ShouldPurgeDlc();
}
void DlcBase::SetActiveValue(bool active) {
ErrorPtr tmp_err;
if (!SystemState::Get()->update_engine()->SetDlcActiveValue(active, id_,
&tmp_err))
LOG(WARNING) << "Failed to set DLC=" << id_ << (active ? " " : " in")
<< "active."
<< (tmp_err ? Error::ToString(tmp_err)
: "Missing error from update engine proxy.");
}
void DlcBase::ChangeState(DlcState::State state) {
switch (state) {
case DlcState::NOT_INSTALLED:
state_.set_state(state);
state_.set_progress(0);
state_.clear_root_path();
break;
case DlcState::INSTALLING:
state_.set_state(state);
state_.set_progress(0);
state_.set_last_error_code(kErrorNone);
break;
case DlcState::INSTALLED:
state_.set_state(state);
state_.set_progress(1.0);
state_.set_root_path(GetRoot().value());
break;
default:
NOTREACHED();
}
LOG(INFO) << "Changing DLC=" << id_ << " state to " << state_.state();
SystemState::Get()->state_change_reporter()->DlcStateChanged(state_);
}
void DlcBase::ChangeProgress(double progress) {
if (state_.state() != DlcState::INSTALLING) {
LOG(WARNING) << "Cannot change the progress if DLC is not being installed.";
return;
}
// Make sure the progress is not decreased.
if (state_.progress() < progress) {
state_.set_progress(std::min(progress, 1.0));
SystemState::Get()->state_change_reporter()->DlcStateChanged(state_);
}
}
} // namespace dlcservice