blob: 59c1aab60f9bf2d2c5b89eade8c101cc3f4d54f2 [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 <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 initlialize 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_ = content_dir.Append(id_);
content_package_path_ = content_id_path_.Append(package_);
prefs_path_ = system_state->dlc_prefs_dir().Append(id_);
state_.set_state(DlcState::NOT_INSTALLED);
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 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();
}
base::FilePath DlcBase::GetRoot() const {
if (mount_point_.empty())
return {};
return JoinPaths(mount_point_, kRootDirectoryInsideDlcModule);
}
bool DlcBase::InstallCompleted(ErrorPtr* err) {
if (!Prefs(*this, SystemState::Get()->active_boot_slot())
.Create(kDlcPrefVerified)) {
*err = Error::Create(
kErrorInternal,
base::StringPrintf("Failed to mark active DLC=%s as verified.",
id_.c_str()));
return false;
}
is_verified_ = true;
return true;
}
bool DlcBase::UpdateCompleted(ErrorPtr* err) const {
if (!Prefs(*this, SystemState::Get()->inactive_boot_slot())
.Create(kDlcPrefVerified)) {
*err = Error::Create(
kErrorInternal,
base::StringPrintf("Failed to mark inactive DLC=%s as verified.",
id_.c_str()));
return false;
}
return true;
}
bool DlcBase::MakeReadyForUpdate(ErrorPtr* err) const {
if (!Prefs(*this, SystemState::Get()->inactive_boot_slot())
.Delete(kDlcPrefVerified)) {
*err = Error::Create(
kErrorInternal,
base::StringPrintf("Failed to mark inactive DLC=%s as not-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::Create(ErrorPtr* err) {
// Create content directories.
for (const auto& path : {content_id_path_, content_package_path_}) {
if (!CreateDir(path)) {
*err = Error::Create(
kErrorInternal,
base::StringPrintf("Failed to create directory %s for DLC=%s",
path.value().c_str(), id_.c_str()));
return false;
}
}
const int64_t image_size = manifest_.preallocated_size();
if (image_size <= 0) {
*err = Error::Create(
kErrorInternal, base::StringPrintf("Preallocated size=%" PRId64
" in manifest is illegal for DLC=%s",
image_size, id_.c_str()));
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, image_size)) {
*err = Error::Create(
kErrorAllocation,
base::StringPrintf("Failed to create image file %s for DLC=%s",
image_path.value().c_str(), id_.c_str()));
return false;
}
}
state_.set_state(DlcState::INSTALLING);
return true;
}
bool DlcBase::ValidateInactiveImage() const {
const FilePath& inactive_image_path =
GetImagePath(SystemState::Get()->inactive_boot_slot());
const int64_t& max_image_size = manifest_.preallocated_size();
if (!base::PathExists(inactive_image_path)) {
LOG(WARNING) << "The DLC image " << inactive_image_path.value()
<< " does not exist.";
if (!CreateFile(inactive_image_path, max_image_size)) {
LOG(ERROR) << "Failed to create inactive image "
<< inactive_image_path.value()
<< " during validation for DLC=" << id_;
return false;
}
}
// Different scenarios possible to hit this flow:
// - Inactive and manifest size are the same -> Do nothing.
//
// TODO(crbug.com/943780): This requires further design updates to both
// dlcservice and upate_engine in order to fully handle. Solution pending.
// - Update applied and not rebooted -> Do nothing. A lot more corner cases
// than just always keeping active and inactive image sizes the same.
//
// - Update applied and rebooted -> Try fixing up inactive image.
int64_t inactive_image_size;
if (!base::GetFileSize(inactive_image_path, &inactive_image_size)) {
LOG(ERROR) << "Failed to get inactive image size DLC=" << id_;
} else {
// When |inactive_image_size| is less than the size permitted in the
// manifest, this means that we rebooted into an update.
if (inactive_image_size < max_image_size) {
// Only increasing size, the inactive DLC is still usable in case of
// reverts.
if (!ResizeFile(inactive_image_path, max_image_size)) {
LOG(ERROR) << "Failed to increase inactive image, update_engine may "
"face problems in updating when stateful is full later.";
return false;
}
}
}
return true;
}
bool DlcBase::PreloadedCopier() {
FilePath image_preloaded_path =
JoinPaths(SystemState::Get()->preloaded_content_dir(), id_, package_,
kDlcImageFileName);
int64_t max_image_size = manifest_.preallocated_size();
// Scope the |image_preloaded| file so it always closes before deleting.
{
int64_t image_preloaded_size;
if (!base::GetFileSize(image_preloaded_path, &image_preloaded_size)) {
LOG(ERROR) << "Failed to get preloaded DLC (" << id_ << ") size.";
return false;
}
if (image_preloaded_size > max_image_size) {
LOG(ERROR) << "Preloaded DLC (" << id_ << ") is (" << image_preloaded_size
<< ") larger than the preallocated size (" << max_image_size
<< ") in manifest.";
return false;
}
}
// Based on the current boot slot, copy the preloadable image.
FilePath image_boot_path, image_non_boot_path;
if (SystemState::Get()->active_boot_slot() == BootSlot::Slot::A) {
image_boot_path = GetImagePath(BootSlot::Slot::A);
image_non_boot_path = GetImagePath(BootSlot::Slot::B);
} else {
image_boot_path = GetImagePath(BootSlot::Slot::B);
image_non_boot_path = GetImagePath(BootSlot::Slot::A);
}
// TODO(kimjae): when preloaded images are place into unencrypted, this
// operation can be a move.
vector<uint8_t> image_sha256;
if (!CopyAndHashFile(image_preloaded_path, image_boot_path, &image_sha256)) {
LOG(ERROR) << "Failed to preload DLC (" << id_ << ") into boot slot path ("
<< image_boot_path << ")";
return false;
}
if (image_sha256 != manifest_.image_sha256()) {
LOG(ERROR) << "Image is corrupted or modified for DLC=" << id_ << ". "
<< "Expected: "
<< base::HexEncode(manifest_.image_sha256().data(),
manifest_.image_sha256().size())
<< " Found: "
<< base::HexEncode(image_sha256.data(), image_sha256.size());
return false;
}
if (!ResizeFile(image_boot_path, max_image_size)) {
LOG(ERROR) << "Image failed to resize for DLC=" << id_
<< ", Path=" << image_boot_path << " ,Size=" << max_image_size;
return false;
}
ErrorPtr tmp_err;
if (!InstallCompleted(&tmp_err)) {
LOG(ERROR) << Error::ToString(tmp_err);
return false;
}
return true;
}
void DlcBase::PreloadImage() {
// Deleting DLC(s) that might already be installed as preloading DLC
// take precedence in order to allow stale DLC in cache to be cleared.
// Loading should be run prior to preloading, to enforce this strict
// precedence.
// TODO(crbug.com/1059445): Verify before deleting that image to preload
// has the correct hash.
ErrorPtr tmp_err;
if (!DeleteInternal(&tmp_err)) {
PLOG(ERROR) << "Failed to delete prior to preloading DLC=" << id_ << ", "
<< Error::ToString(tmp_err);
return;
}
if (!InitInstall(&tmp_err)) {
LOG(ERROR) << "Failed to create preloaded DLC=" << id_ << ", "
<< Error::ToString(tmp_err);
return;
}
if (!PreloadedCopier()) {
LOG(ERROR) << "Something went wrong during preloading DLC (" << id_
<< "), please check for previous errors.";
if (!CancelInstall(&tmp_err))
LOG(WARNING) << Error::ToString(tmp_err);
return;
}
// When the copying is successful, go ahead and finish installation.
if (!FinishInstall(&tmp_err)) {
LOG(ERROR) << "Failed to finish installation for preloaded DLC=" << id_
<< ", " << Error::ToString(tmp_err);
return;
}
// Don't remove preloaded DLC images when booted from removable device,
// otherwise chromeos-install script will not be able to install stateful
// partition correctly with preloaded DLC images.
if (!SystemState::Get()->IsDeviceRemovable()) {
// Delete the preloaded DLC only after both copies into A and B succeed as
// well as mounting.
const auto path = SystemState::Get()->preloaded_content_dir().Append(id_);
if (!base::DeleteFile(path, true)) {
LOG(ERROR) << "Failed to delete preloaded DLC=" << id_;
}
}
}
bool DlcBase::InitInstall(ErrorPtr* err) {
if (!base::PathExists(prefs_path_)) {
if (!CreateDir(prefs_path_)) {
*err = Error::Create(kErrorInternal, "Failed to create prefs directory.");
return false;
}
}
switch (state_.state()) {
case DlcState::NOT_INSTALLED:
if (IsVerified() && (ValidateInactiveImage() || true) && !TryMount(err)) {
return false;
}
if (IsActiveImagePresent()) {
if (!DeleteInternal(err)) {
if (!CancelInstall(err))
LOG(ERROR) << "Failed during install initialization: "
<< Error::ToString(*err);
return false;
}
}
if (!Create(err)) {
if (!CancelInstall(err))
LOG(ERROR) << "Failed during install initialization: "
<< Error::ToString(*err);
return false;
}
break;
case DlcState::INSTALLED:
if (!ValidateInactiveImage())
LOG(ERROR) << "Bad inactive image for DLC=" << id_;
// Tests that run at times will unmount all loopback devices, hence it's
// required that even installed DLC images need to be mounted again.
if (!TryMount(err)) {
LOG(ERROR) << Error::ToString(*err);
state_.set_state(DlcState::NOT_INSTALLED);
return false;
}
break;
case DlcState::INSTALLING:
default:
NOTREACHED();
return false;
}
// Failure to set the metadata flags should not fail the install.
if (!SystemState::Get()->update_engine()->SetDlcActiveValue(true, id_, err)) {
LOG(WARNING) << "Update Engine failed to set DLC to active:" << id_
<< (*err ? Error::ToString(*err)
: "Missing error from update engine proxy.");
}
return true;
}
bool DlcBase::FinishInstall(ErrorPtr* err) {
switch (state_.state()) {
case DlcState::NOT_INSTALLED:
case DlcState::INSTALLED:
return true;
case DlcState::INSTALLING: {
bool ret = true;
if (!Prefs(*this, SystemState::Get()->active_boot_slot())
.Exists(kDlcPrefVerified)) {
*err =
Error::Create(kErrorInternal,
base::StringPrintf("Cannot mount image which is not "
"marked as verified for DLC=%s",
id_.c_str()));
LOG(ERROR) << "Failed during install finalization: "
<< Error::ToString(*err);
ret = false;
} else if (!Mount(err)) {
LOG(ERROR) << "Failed during install finalization: "
<< Error::ToString(*err) << " for DLC=" << id_;
ret = false;
}
if (!ret) {
ErrorPtr tmp_err;
if (!DeleteInternal(&tmp_err))
LOG(ERROR) << "Failed during install finalization: "
<< Error::ToString(tmp_err) << " for DLC=" << id_;
}
return ret;
}
default:
NOTREACHED();
return false;
}
}
bool DlcBase::CancelInstall(ErrorPtr* err) {
if (!IsInstalling()) {
return true;
}
// 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)) {
PLOG(ERROR) << "Failed during install cancellation: "
<< Error::ToString(*err) << " 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, kImageLoaderTimeoutMs)) {
*err = Error::Create(kErrorInternal,
"Imageloader is unavailable for LoadDlcImage().");
return false;
}
if (mount_point.empty()) {
*err = Error::Create(kErrorInternal,
"Imageloader LoadDlcImage() call failed.");
return false;
}
mount_point_ = FilePath(mount_point);
state_.set_state(DlcState::INSTALLED);
return true;
}
bool DlcBase::Unmount(ErrorPtr* err) {
bool success = false;
if (!SystemState::Get()->image_loader()->UnloadDlcImage(
id_, package_, &success, nullptr, kImageLoaderTimeoutMs)) {
*err = Error::Create(kErrorInternal,
"Imageloader is unavailable for UnloadDlcImage().");
return false;
}
if (!success) {
*err = Error::Create(kErrorInternal,
"Imageloader UnloadDlcImage() call failed.");
return false;
}
state_.set_state(DlcState::NOT_INSTALLED);
return true;
}
bool DlcBase::TryMount(ErrorPtr* err) {
if (!mount_point_.empty() && base::PathExists(GetRoot())) {
LOG(INFO) << "Skipping mount as already mounted at " << GetRoot();
state_.set_state(DlcState::INSTALLED);
return true;
}
if (!Mount(err)) {
LOG(ERROR) << "DLC thought to have been installed, but maybe is in a "
<< "bad state. DLC=" << id_ << ", " << Error::ToString(*err);
return false;
}
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) {
vector<string> undeleted_paths;
for (const auto& path : GetPathsToDelete(id_)) {
if (!base::DeleteFile(path, true)) {
PLOG(ERROR) << "Failed to delete path=" << path;
undeleted_paths.push_back(path.value());
}
}
// Failure to set DLC to inactive should not fail uninstall.
ErrorPtr tmp_err;
if (!SystemState::Get()->update_engine()->SetDlcActiveValue(false, id_,
&tmp_err))
LOG(WARNING) << "Failed to set DLC(" << id_ << ") to inactive."
<< (tmp_err ? Error::ToString(tmp_err)
: "Missing error from update engine proxy.");
state_.set_state(DlcState::NOT_INSTALLED);
is_verified_ = false;
if (!undeleted_paths.empty()) {
*err = Error::Create(
kErrorInternal,
base::StringPrintf("DLC directories (%s) could not be deleted.",
base::JoinString(undeleted_paths, ",").c_str()));
return false;
}
return true;
}
bool DlcBase::Delete(ErrorPtr* err) {
switch (state_.state()) {
case DlcState::NOT_INSTALLED:
LOG(WARNING) << "Trying to uninstall not installed DLC=" << id_;
return DeleteInternal(err);
case DlcState::INSTALLING:
*err = Error::Create(
kErrorBusy,
base::StringPrintf("Trying to delete a currently installing DLC=%s",
id_.c_str()));
return false;
case DlcState::INSTALLED:
return Unmount(err) && DeleteInternal(err);
default:
NOTREACHED();
return false;
}
}
} // namespace dlcservice