blob: d9ee501909ae6a3d993a3aa0ff8c31a770d51b11 [file] [log] [blame]
// Copyright 2018 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_service_dbus_adaptor.h"
#include <algorithm>
#include <utility>
#include <vector>
#include <base/files/file_enumerator.h>
#include <base/files/file_util.h>
#include <base/message_loop/message_loop.h>
#include <brillo/errors/error.h>
#include <brillo/errors/error_codes.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/dlcservice/dbus-constants.h>
#include <update_engine/dbus-constants.h>
#include "dlcservice/boot_slot.h"
#include "dlcservice/utils.h"
namespace dlcservice {
namespace {
// Permissions for DLC module directories.
constexpr mode_t kDlcModuleDirectoryPerms = 0755;
// Creates a directory with permissions required for DLC modules.
bool CreateDirWithDlcPermissions(const base::FilePath& path) {
base::File::Error file_err;
if (!base::CreateDirectoryAndGetError(path, &file_err)) {
LOG(ERROR) << "Failed to create directory '" << path.value()
<< "': " << base::File::ErrorToString(file_err);
return false;
}
if (!base::SetPosixFilePermissions(path, kDlcModuleDirectoryPerms)) {
LOG(ERROR) << "Failed to set directory permissions for '" << path.value()
<< "'";
return false;
}
return true;
}
// Creates a directory with an empty image file and resizes it to the given
// size.
bool CreateImageFile(const base::FilePath& path, int64_t image_size) {
if (!CreateDirWithDlcPermissions(path.DirName())) {
return false;
}
constexpr uint32_t file_flags =
base::File::FLAG_CREATE | base::File::FLAG_READ | base::File::FLAG_WRITE;
base::File file(path, file_flags);
if (!file.IsValid()) {
LOG(ERROR) << "Failed to create image file '" << path.value()
<< "': " << base::File::ErrorToString(file.error_details());
return false;
}
if (!file.SetLength(image_size)) {
LOG(ERROR) << "Failed to reserve backup file for DLC module image '"
<< path.value() << "'";
return false;
}
return true;
}
// Sets the D-Bus error object and logs the error message.
void LogAndSetError(brillo::ErrorPtr* err, const std::string& msg) {
if (err)
*err = brillo::Error::Create(FROM_HERE, "dlcservice", "INTERNAL", msg);
LOG(ERROR) << msg;
}
} // namespace
DlcServiceDBusAdaptor::DlcServiceDBusAdaptor(
std::unique_ptr<org::chromium::ImageLoaderInterfaceProxyInterface>
image_loader_proxy,
std::unique_ptr<org::chromium::UpdateEngineInterfaceProxyInterface>
update_engine_proxy,
std::unique_ptr<BootSlot> boot_slot,
const base::FilePath& manifest_dir,
const base::FilePath& content_dir)
: org::chromium::DlcServiceInterfaceAdaptor(this),
image_loader_proxy_(std::move(image_loader_proxy)),
update_engine_proxy_(std::move(update_engine_proxy)),
boot_slot_(std::move(boot_slot)),
manifest_dir_(manifest_dir),
content_dir_(content_dir),
weak_ptr_factory_(this) {
// Get current boot slot.
std::string boot_disk_name;
int num_slots = -1;
int current_boot_slot = -1;
if (!boot_slot_->GetCurrentSlot(&boot_disk_name, &num_slots,
&current_boot_slot))
LOG(FATAL) << "Can not get current boot slot.";
current_boot_slot_name_ = current_boot_slot == 0 ? imageloader::kSlotNameA
: imageloader::kSlotNameB;
// Register D-Bus signal callbacks.
update_engine_proxy_->RegisterStatusUpdateSignalHandler(
base::Bind(&DlcServiceDBusAdaptor::OnStatusUpdateSignal,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&DlcServiceDBusAdaptor::OnStatusUpdateSignalConnected,
weak_ptr_factory_.GetWeakPtr()));
// Initalize installed DLC modules.
installed_dlc_modules_ = utils::ScanDirectory(content_dir_);
// Initialize supported DLC modules.
supported_dlc_modules_ = utils::ScanDirectory(manifest_dir_);
}
DlcServiceDBusAdaptor::~DlcServiceDBusAdaptor() {}
void DlcServiceDBusAdaptor::LoadDlcModuleImages() {
// Load all installed DLC modules.
for (const auto& dlc_module_id : installed_dlc_modules_) {
std::string package = ScanDlcModulePackage(dlc_module_id);
auto dlc_module_content_path =
utils::GetDlcModulePackagePath(content_dir_, dlc_module_id, package);
// Mount the installed DLC image.
std::string path;
image_loader_proxy_->LoadDlcImage(dlc_module_id, package,
current_boot_slot_name_, &path, nullptr);
if (path.empty()) {
LOG(ERROR) << "DLC image " << dlc_module_id << "/" << package
<< " is corrupted.";
} else {
LOG(INFO) << "DLC image " << dlc_module_id << "/" << package
<< " is mounted at " << path;
}
}
}
bool DlcServiceDBusAdaptor::Install(brillo::ErrorPtr* err,
const std::string& id_in,
const std::string& omaha_url_in) {
if (supported_dlc_modules_.find(id_in) == supported_dlc_modules_.end()) {
LogAndSetError(err, "The DLC ID provided is not supported.");
return false;
}
std::string package = ScanDlcModulePackage(id_in);
// Create the DLC ID directory with correct permissions.
base::FilePath module_path = utils::GetDlcModulePath(content_dir_, id_in);
if (base::PathExists(module_path)) {
LogAndSetError(err, "The DLC module is installed.");
return false;
}
if (!CreateDirWithDlcPermissions(module_path)) {
LogAndSetError(err, "Failed to create DLC ID directory.");
return false;
}
// Create the DLC package directory with correct permissions.
base::FilePath module_package_path =
utils::GetDlcModulePackagePath(content_dir_, id_in, package);
if (base::PathExists(module_package_path)) {
LogAndSetError(err, "The DLC module is installed.");
return false;
}
if (!CreateDirWithDlcPermissions(module_package_path)) {
LogAndSetError(err, "Failed to create DLC Package directory.");
return false;
}
// Creates DLC module storage.
// TODO(xiaochu): Manifest currently returns a signed integer, which means
// this will likely fail for modules >= 2 GiB in size.
// https://crbug.com/904539
imageloader::Manifest manifest;
if (!dlcservice::utils::GetDlcManifest(manifest_dir_, id_in, package,
&manifest)) {
LogAndSetError(err, "Failed to get DLC module manifest.");
return false;
}
int64_t image_size = manifest.preallocated_size();
if (image_size <= 0) {
LogAndSetError(err, "Preallocated size in manifest is illegal.");
return false;
}
// Creates image A.
base::FilePath image_a_path =
utils::GetDlcModuleImagePath(content_dir_, id_in, package, 0);
if (!CreateImageFile(image_a_path, image_size)) {
LogAndSetError(err, "Failed to create slot A DLC image file");
return false;
}
// Creates image B.
base::FilePath image_b_path =
utils::GetDlcModuleImagePath(content_dir_, id_in, package, 1);
if (!CreateImageFile(image_b_path, image_size)) {
LogAndSetError(err, "Failed to create slot B image file");
return false;
}
if (!CheckForUpdateEngineStatus({update_engine::kUpdateStatusIdle})) {
LogAndSetError(
err, "Update Engine is performing operations or a reboot is pending.");
return false;
}
// Invokes update_engine to install the DLC module.
dlcservice::DlcModuleList dlc_parameters;
dlc_parameters.set_omaha_url(omaha_url_in);
dlcservice::DlcModuleInfo* dlc_info = dlc_parameters.add_dlc_module_infos();
dlc_info->set_dlc_id(id_in);
if (!update_engine_proxy_->AttemptInstall(dlc_parameters, nullptr)) {
LogAndSetError(err, "Update Engine failed to schedule install operations.");
return false;
}
dlc_id_being_installed_ = id_in;
// Note: Do NOT add to installed indication. Let |OnStatusUpdateSignaled()|
// handle since hat's truly when the DLC(s) are installed.
return true;
}
bool DlcServiceDBusAdaptor::Uninstall(brillo::ErrorPtr* err,
const std::string& id_in) {
if (installed_dlc_modules_.find(id_in) == installed_dlc_modules_.end()) {
LogAndSetError(err, "The DLC ID provided is not installed");
return false;
}
std::string package = ScanDlcModulePackage(id_in);
const base::FilePath dlc_module_content_path =
utils::GetDlcModulePackagePath(content_dir_, id_in, package);
if (!base::PathExists(dlc_module_content_path) ||
!base::PathExists(
utils::GetDlcModuleImagePath(content_dir_, id_in, package, 0)) ||
!base::PathExists(
utils::GetDlcModuleImagePath(content_dir_, id_in, package, 1))) {
LogAndSetError(err, "The DLC module is not installed properly.");
return false;
}
// Unmounts the DLC module image.
bool success = false;
if (!image_loader_proxy_->UnloadDlcImage(id_in, package, &success, nullptr)) {
LogAndSetError(err, "Imageloader is not available.");
return false;
}
if (!success) {
LogAndSetError(err, "Imageloader UnloadDlcImage failed.");
return false;
}
if (!CheckForUpdateEngineStatus(
{update_engine::kUpdateStatusIdle,
update_engine::kUpdateStatusUpdatedNeedReboot})) {
LogAndSetError(err, "Update Engine is performing operations.");
return false;
}
// Deletes the DLC module images.
const base::FilePath dlc_module_path =
utils::GetDlcModulePath(content_dir_, id_in);
if (!base::DeleteFile(dlc_module_path, true)) {
LogAndSetError(err, "DLC image folder could not be deleted.");
return false;
}
LOG(INFO) << "Uninstalling DLC id:" << id_in;
installed_dlc_modules_.erase(id_in);
return true;
}
bool DlcServiceDBusAdaptor::GetInstalled(brillo::ErrorPtr* err,
DlcModuleList* dlc_module_list_out) {
const auto InsertIntoDlcModuleListOut =
[dlc_module_list_out](const std::string& dlc_module_id) {
DlcModuleInfo* dlc_module_info =
dlc_module_list_out->add_dlc_module_infos();
dlc_module_info->set_dlc_id(dlc_module_id);
};
for_each(begin(installed_dlc_modules_), end(installed_dlc_modules_),
InsertIntoDlcModuleListOut);
return true;
}
std::string DlcServiceDBusAdaptor::ScanDlcModulePackage(const std::string& id) {
return *(utils::ScanDirectory(manifest_dir_.Append(id)).begin());
}
bool DlcServiceDBusAdaptor::CheckForUpdateEngineStatus(
const std::vector<std::string>& status_list) {
int64_t last_checked_time = 0;
double progress = 0;
std::string current_operation;
std::string new_version;
int64_t new_size = 0;
if (!update_engine_proxy_->GetStatus(&last_checked_time, &progress,
&current_operation, &new_version,
&new_size, nullptr)) {
LOG(ERROR) << "Update Engine is not available.";
return false;
}
if (!std::any_of(status_list.begin(), status_list.end(),
[&current_operation](const std::string& status) {
return current_operation == status;
})) {
return false;
}
return true;
}
void DlcServiceDBusAdaptor::SendOnInstalledSignal(
const InstallResult& install_result) {
org::chromium::DlcServiceInterfaceAdaptor::SendOnInstalledSignal(
install_result);
}
void DlcServiceDBusAdaptor::OnStatusUpdateSignal(
int64_t last_checked_time,
double progress,
const std::string& current_operation,
const std::string& new_version,
int64_t new_size) {
// This signal is for DLC install only when have DLC modules being installed.
if (dlc_id_being_installed_.empty())
return;
// Install is complete when we receive kUpdateStatusIdle signal.
if (current_operation != update_engine::kUpdateStatusIdle)
return;
// At this point, update_engine finished installation of the requested DLC
// module (failure or success).
std::string dlc_id = dlc_id_being_installed_;
dlc_id_being_installed_.clear();
InstallResult install_result;
install_result.set_success(false);
install_result.set_dlc_id(dlc_id);
std::string package = ScanDlcModulePackage(dlc_id);
// Mount the installed DLC module image.
std::string mount_point;
if (!image_loader_proxy_->LoadDlcImage(
dlc_id, package, current_boot_slot_name_, &mount_point, nullptr)) {
LOG(ERROR) << "Imageloader is not available.";
install_result.set_error_code(
static_cast<int>(OnInstalledSignalErrorCode::kImageLoaderReturnsFalse));
SendOnInstalledSignal(install_result);
return;
}
if (mount_point.empty()) {
LOG(ERROR) << "Imageloader LoadDlcImage failed.";
install_result.set_error_code(
static_cast<int>(OnInstalledSignalErrorCode::kMountFailure));
SendOnInstalledSignal(install_result);
return;
}
// Install was a success so keep track.
installed_dlc_modules_.insert(dlc_id);
install_result.set_success(true);
install_result.set_error_code(
static_cast<int>(OnInstalledSignalErrorCode::kNone));
install_result.set_dlc_root(
utils::GetDlcRootInModulePath(base::FilePath(mount_point)).value());
SendOnInstalledSignal(install_result);
}
void DlcServiceDBusAdaptor::OnStatusUpdateSignalConnected(
const std::string& interface_name,
const std::string& signal_name,
bool success) {
if (!success) {
LOG(ERROR) << "Failed to connect to update_engine's StatusUpdate signal.";
}
}
} // namespace dlcservice