blob: 97d45c1ce7b888055cd25c0437c3d3626fea3d32 [file] [log] [blame]
// Copyright 2019 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_manager.h"
#include <cinttypes>
#include <utility>
#include <vector>
#include <base/strings/stringprintf.h>
#include <base/strings/string_util.h>
#include <brillo/message_loops/message_loop.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/dlcservice/dbus-constants.h>
#include "dlcservice/error.h"
#include "dlcservice/system_state.h"
#include "dlcservice/utils.h"
using base::Callback;
using base::FilePath;
using brillo::ErrorPtr;
using std::string;
using std::vector;
namespace dlcservice {
namespace {
// Timeout in ms for DBus method calls into imageloader.
constexpr int kImageLoaderTimeoutMs = 5000;
} // namespace
DlcManager::DlcManager() {
const auto system_state = SystemState::Get();
image_loader_proxy_ = system_state->image_loader();
manifest_dir_ = system_state->manifest_dir();
preloaded_content_dir_ = system_state->preloaded_content_dir();
content_dir_ = system_state->content_dir();
string boot_disk_name;
if (!system_state->boot_slot().GetCurrentSlot(&boot_disk_name,
&current_boot_slot_))
LOG(FATAL) << "Can not get current boot slot.";
// Initialize supported DLC(s).
for (const auto& id : ScanDirectory(manifest_dir_))
supported_[id];
}
DlcManager::~DlcManager() = default;
bool DlcManager::IsSupported(const DlcId& id) {
return supported_.find(id) != supported_.end();
}
bool DlcManager::IsInstalling() {
for (const auto& pr : supported_)
if (pr.second.state.state() == DlcState::INSTALLING)
return true;
return false;
}
DlcInfo DlcManager::GetInfo(const DlcId& id) {
DCHECK(IsSupported(id));
return supported_[id];
}
// Loads the preloadable DLC(s) from |preloaded_content_dir_| by scanning the
// preloaded DLC(s) and verifying the validity to be preloaded before doing
// so.
void DlcManager::PreloadDlcModuleImages() {
// Load all preloaded DLC(s) into |content_dir_| one by one.
for (const auto& id : ScanDirectory(preloaded_content_dir_)) {
if (!IsSupported(id)) {
LOG(ERROR) << "Preloading is not allowed for unsupported DLC=" << id;
continue;
}
if (!IsDlcPreloadAllowed(manifest_dir_, id)) {
LOG(ERROR) << "Preloading is not allowed for DLC=" << id;
continue;
}
ErrorPtr tmp_err;
// Deleting DLC(s) that might already be installed as preloading DLC(s)
// take precedence in order to allow stale DLC(s) 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.
if (!DeleteInternal(id, &tmp_err)) {
PLOG(ERROR) << "Failed to delete prior to preloading DLC=" << id << ", "
<< Error::ToString(tmp_err);
continue;
}
DlcSet dlc_set = {id};
if (!InitInstallInternal(dlc_set, &tmp_err)) {
LOG(ERROR) << "Failed to create preloaded DLC=" << id << ", "
<< Error::ToString(tmp_err);
continue;
}
if (!PreloadedCopier(id)) {
LOG(ERROR) << "Something went wrong during preloading DLC (" << id
<< "), please check for previous errors.";
if (!CancelInstall(&tmp_err))
LOG(WARNING) << Error::ToString(tmp_err);
continue;
}
// 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);
continue;
}
// Delete the preloaded DLC only after both copies into A and B succeed as
// well as mounting.
auto image_preloaded_path = JoinPaths(preloaded_content_dir_, id,
GetDlcPackage(id), kDlcImageFileName);
if (!base::DeleteFile(image_preloaded_path.DirName().DirName(), true)) {
LOG(ERROR) << "Failed to delete image after preloading DLC=" << id;
continue;
}
}
}
bool DlcManager::InitInstallInternal(const DlcSet& requested_install,
ErrorPtr* err) {
DCHECK(!IsInstalling());
ErrorPtr tmp_err;
for (const auto& id : requested_install) {
if (!IsSupported(id)) {
*err = Error::Create(
kErrorInvalidDlc,
base::StringPrintf("Trying to install unsupported DLC=%s",
id.c_str()));
if (!CancelInstall(&tmp_err))
LOG(ERROR) << "Failed during install initialization: "
<< Error::ToString(tmp_err);
return false;
}
switch (GetInfo(id).state.state()) {
case DlcState::NOT_INSTALLED:
if (IsActiveImagePresent(id)) {
if (ValidateInactiveImage(id) && TryMount(id))
break;
if (!DeleteInternal(id, err)) {
if (!CancelInstall(&tmp_err))
LOG(ERROR) << "Failed during install initialization: "
<< Error::ToString(tmp_err);
return false;
}
}
if (!Create(id, err)) {
if (!CancelInstall(&tmp_err))
LOG(ERROR) << "Failed during install initialization: "
<< Error::ToString(tmp_err);
return false;
}
break;
case DlcState::INSTALLED:
if (!ValidateInactiveImage(id))
LOG(ERROR) << "Bad inactive image for DLC=" << id;
if (!TryMount(id))
LOG(ERROR) << "Failed to mount installed DLC=" << id;
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,
&tmp_err)) {
LOG(WARNING) << "Update Engine failed to set DLC to active:" << id
<< (tmp_err ? Error::ToString(tmp_err)
: "Missing error from update engine proxy.");
}
}
return true;
}
bool DlcManager::FinishInstall(ErrorPtr* err) {
bool ret = true;
for (const auto& pr : supported_) {
const auto& id = pr.first;
const auto& info = pr.second;
if (info.state.state() != DlcState::INSTALLING)
continue;
string mount_point;
ErrorPtr tmp_err;
if (!Mount(id, &mount_point, &tmp_err)) {
LOG(ERROR) << "Failed during install finalization: "
<< Error::ToString(tmp_err);
if (!DeleteInternal(id, &tmp_err))
LOG(ERROR) << "Failed during install finalization: "
<< Error::ToString(tmp_err);
ret = false;
continue;
}
SetInstalled(id, GetDlcRoot(FilePath(mount_point)).value());
}
if (!ret)
*err =
Error::Create(kErrorInternal, "Not all DLC(s) successfully mounted.");
return ret;
}
bool DlcManager::CancelInstall(ErrorPtr* err) {
bool ret = true;
if (!IsInstalling()) {
LOG(WARNING) << "No install started to being with, nothing to cancel.";
return ret;
}
for (const auto& pr : supported_) {
const auto& id = pr.first;
const auto& info = pr.second;
if (info.state.state() != DlcState::INSTALLING)
continue;
// Consider as not installed even if delete fails below, correct errors
// will be propagated later and should not block on further installs.
SetNotInstalled(id);
ErrorPtr tmp_err;
if (!DeleteInternal(id, &tmp_err)) {
PLOG(ERROR) << "Failed during install cancellation: "
<< Error::ToString(tmp_err);
ret = false;
}
}
if (!ret)
*err = Error::Create(kErrorInternal,
"Not all installing DLC(s) successfully cancelled.");
return ret;
}
// Deletes all directories related to the given DLC |id|.
bool DlcManager::DeleteInternal(const string& id, ErrorPtr* err) {
vector<string> undeleted_paths;
for (const auto& path : {JoinPaths(content_dir_, 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.");
}
bool ret = undeleted_paths.empty();
if (!ret) {
*err = Error::Create(
kErrorInternal,
base::StringPrintf("DLC directories (%s) could not be deleted.",
base::JoinString(undeleted_paths, ",").c_str()));
}
SetNotInstalled(id);
return ret;
}
bool DlcManager::Mount(const string& id, string* mount_point, ErrorPtr* err) {
if (!image_loader_proxy_->LoadDlcImage(
id, GetDlcPackage(id),
current_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;
}
return true;
}
bool DlcManager::Unmount(const string& id, ErrorPtr* err) {
bool success = false;
if (!image_loader_proxy_->UnloadDlcImage(id, GetDlcPackage(id), &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;
}
return true;
}
string DlcManager::GetDlcPackage(const DlcId& id) {
return *(ScanDirectory(JoinPaths(manifest_dir_, id)).begin());
}
void DlcManager::SetNotInstalled(const DlcId& id) {
DCHECK(IsSupported(id));
supported_[id] = DlcInfo(DlcState::NOT_INSTALLED);
}
void DlcManager::SetInstalling(const DlcId& id) {
DCHECK(IsSupported(id));
supported_[id] = DlcInfo(DlcState::INSTALLING);
}
void DlcManager::SetInstalled(const DlcId& id, const DlcRoot& root) {
DCHECK(IsSupported(id));
supported_[id] = DlcInfo(DlcState::INSTALLED, root);
}
// Returns true if the DLC module has a boolean true for 'preload-allowed'
// attribute in the manifest for the given |id| and |package|.
bool DlcManager::IsDlcPreloadAllowed(const base::FilePath& dlc_manifest_path,
const std::string& id) {
imageloader::Manifest manifest;
if (!GetDlcManifest(dlc_manifest_path, id, GetDlcPackage(id), &manifest)) {
// Failing to read the manifest will be considered a preloading blocker.
return false;
}
return manifest.preload_allowed();
}
// Create the DLC |id| and |package| directories if they don't exist.
bool DlcManager::CreateDlcPackagePath(const string& id,
const string& package,
ErrorPtr* err) {
FilePath content_path_local = JoinPaths(content_dir_, id);
FilePath content_package_path = JoinPaths(content_dir_, id, package);
// Create the DLC ID directory with correct permissions.
if (!CreateDir(content_path_local)) {
*err =
Error::Create(kErrorInternal,
base::StringPrintf(
"Failed to create directory for DLC=%s", id.c_str()));
return false;
}
// Create the DLC package directory with correct permissions.
if (!CreateDir(content_package_path)) {
*err = Error::Create(
kErrorInternal,
base::StringPrintf("Failed to create package directory for DLC=%s",
id.c_str()));
return false;
}
return true;
}
bool DlcManager::Create(const string& id, ErrorPtr* err) {
// Create content directories.
const auto& package = GetDlcPackage(id);
const auto& content_id_path = JoinPaths(content_dir_, id);
for (const auto& path :
{content_id_path, JoinPaths(content_id_path, package)}) {
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;
}
}
imageloader::Manifest manifest;
if (!dlcservice::GetDlcManifest(manifest_dir_, id, package, &manifest)) {
*err = Error::Create(
kErrorInternal,
base::StringPrintf("Cannot read the manifest for DLC=%s", 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.
FilePath image_a_path =
GetDlcImagePath(content_dir_, id, package, BootSlot::Slot::A);
if (!CreateFile(image_a_path, image_size)) {
*err = Error::Create(
kErrorAllocation,
base::StringPrintf("Failed to create slot A image file for DLC=%s",
id.c_str()));
return false;
}
// Creates image B.
FilePath image_b_path =
GetDlcImagePath(content_dir_, id, package, BootSlot::Slot::B);
if (!CreateFile(image_b_path, image_size)) {
*err = Error::Create(
kErrorAllocation,
base::StringPrintf("Failed to create slot B image file for DLC=%s",
id.c_str()));
return false;
}
SetInstalling(id);
return true;
}
// Validate that:
// - If inactive image for DLC is missing, try creating it.
// - If inactive image size is less than size in manifest, increase it.
bool DlcManager::ValidateInactiveImage(const string& id) {
string mount_point;
const auto& package = GetDlcPackage(id);
FilePath inactive_image_path = GetDlcImagePath(
content_dir_, id, package,
current_boot_slot_ == BootSlot::Slot::A ? BootSlot::Slot::B
: BootSlot::Slot::A);
imageloader::Manifest manifest;
if (!GetDlcManifest(manifest_dir_, id, package, &manifest)) {
LOG(ERROR) << "Failed to get manifest for DLC=" << id;
return false;
}
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;
}
// Helper used to load in (copy + cleanup) preloadable files for the |id|.
bool DlcManager::PreloadedCopier(const string& id) {
const auto& package = GetDlcPackage(id);
FilePath image_preloaded_path =
JoinPaths(preloaded_content_dir_, id, package, kDlcImageFileName);
FilePath image_a_path =
GetDlcImagePath(content_dir_, id, package, BootSlot::Slot::A);
FilePath image_b_path =
GetDlcImagePath(content_dir_, id, package, BootSlot::Slot::B);
// Check the size of file to copy is valid.
imageloader::Manifest manifest;
if (!dlcservice::GetDlcManifest(manifest_dir_, id, package, &manifest)) {
LOG(ERROR) << "Failed to get manifest for DLC=" << id;
return false;
}
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 |current_boot_slot_|, copy the preloadable image.
FilePath image_boot_path, image_non_boot_path;
switch (current_boot_slot_) {
case BootSlot::Slot::A:
image_boot_path = image_a_path;
image_non_boot_path = image_b_path;
break;
case BootSlot::Slot::B:
image_boot_path = image_b_path;
image_non_boot_path = image_a_path;
break;
default:
NOTREACHED();
return false;
}
// TODO(kimjae): when preloaded images are place into unencrypted, this
// operation can be a move.
if (!CopyAndResizeFile(image_preloaded_path, image_boot_path,
max_image_size)) {
LOG(ERROR) << "Failed to preload DLC (" << id << ") into boot slot.";
return false;
}
return true;
}
bool DlcManager::TryMount(const DlcId& id) {
const auto info = GetInfo(id);
if (base::PathExists(FilePath(info.root))) {
LOG(INFO) << "Skipping mount as already mounted at " << info.root;
return true;
}
string mount_point;
ErrorPtr tmp_err;
if (!Mount(id, &mount_point, &tmp_err)) {
LOG(ERROR) << "DLC thought to have been installed, but maybe is in a "
<< "bad state. DLC=" << id << ", " << Error::ToString(tmp_err);
return false;
}
SetInstalled(id, GetDlcRoot(FilePath(mount_point)).value());
return true;
}
bool DlcManager::IsActiveImagePresent(const DlcId& id) {
return base::PathExists(
GetDlcImagePath(content_dir_, id, GetDlcPackage(id), current_boot_slot_));
}
DlcModuleList DlcManager::GetInstalled() {
return ToDlcModuleList(supported_, [](const DlcId&, const DlcInfo& info) {
return info.state.state() == DlcState::INSTALLED;
});
}
DlcModuleList DlcManager::GetSupported() {
return ToDlcModuleList(supported_, [](const DlcId&, const DlcInfo&) {
return true;
});
}
bool DlcManager::GetState(const DlcId& id, DlcState* state, ErrorPtr* err) {
DCHECK(state);
DCHECK(err);
if (!IsSupported(id)) {
*err = Error::Create(
kErrorInvalidDlc,
base::StringPrintf("Cannot get state for unsupported DLC=%s",
id.c_str()));
return false;
}
*state = GetInfo(id).state;
return true;
}
void DlcManager::LoadDlcModuleImages() {
PreloadDlcModuleImages();
}
bool DlcManager::InitInstall(const DlcModuleList& dlc_module_list,
ErrorPtr* err) {
DCHECK(err);
const auto dlc_set =
ToDlcSet(dlc_module_list, [](const DlcModuleInfo&) { return true; });
if (dlc_set.empty()) {
*err = Error::Create(kErrorInvalidDlc,
"Must provide at least one DLC to install.");
return false;
}
return InitInstallInternal(dlc_set, err);
}
DlcModuleList DlcManager::GetMissingInstalls() {
// Only return the DLC(s) that aren't already installed.
return ToDlcModuleList(supported_, [](const DlcId&, const DlcInfo& info) {
return info.state.state() == DlcState::INSTALLING;
});
}
bool DlcManager::Delete(const string& id, ErrorPtr* err) {
DCHECK(err);
if (!IsSupported(id)) {
*err = Error::Create(
kErrorInvalidDlc,
base::StringPrintf("Trying to delete unsupported DLC=%s", id.c_str()));
return false;
}
switch (GetInfo(id).state.state()) {
case DlcState::NOT_INSTALLED:
LOG(WARNING) << "Trying to uninstall not installed DLC=" << id;
return DeleteInternal(id, 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(id, err) && DeleteInternal(id, err);
default:
NOTREACHED();
return false;
}
}
} // namespace dlcservice