blob: 6e4d7bd9a7326864e7c6f696df9b881431553f87 [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 <brillo/errors/error.h>
#include <brillo/errors/error_codes.h>
#include <chromeos/dbus/service_constants.h>
#include <dlcservice/proto_bindings/dlcservice.pb.h>
#include <update_engine/dbus-constants.h>
#include <update_engine/proto_bindings/update_engine.pb.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;
}
// The time interval we check for update_engine's progress.
constexpr base::TimeDelta kCheckInterval = base::TimeDelta::FromSeconds(1);
// The retry times to get update_engine's progress before giving up.
constexpr int kRetryCount = 10;
} // 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,
ShutdownDelegate* shutdown_delegate)
: 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),
shutdown_delegate_(shutdown_delegate) {}
DlcServiceDBusAdaptor::~DlcServiceDBusAdaptor() {}
void DlcServiceDBusAdaptor::LoadDlcModuleImages() {
// Initialize supported DLC module id list.
std::vector<std::string> dlc_module_ids = ScanDlcModules();
std::string boot_disk_name;
int num_slots = -1;
int current_slot = -1;
if (!boot_slot_->GetCurrentSlot(&boot_disk_name, &num_slots, &current_slot)) {
LOG(ERROR) << "Can not get current boot slot.";
return;
}
// Load all installed DLC modules.
for (const auto& dlc_module_id : dlc_module_ids) {
auto dlc_module_content_path =
utils::GetDlcModulePath(content_dir_, dlc_module_id);
if (!base::PathExists(dlc_module_content_path))
continue;
// Mount the installed DLC image.
std::string path;
image_loader_proxy_->LoadDlcImage(
dlc_module_id,
current_slot == 0 ? imageloader::kSlotNameA : imageloader::kSlotNameB,
&path, nullptr);
if (path.empty()) {
LOG(ERROR) << "DLC image " << dlc_module_id << " is corrupted.";
} else {
LOG(INFO) << "DLC image " << dlc_module_id << " is mounted at " << path;
}
}
}
bool DlcServiceDBusAdaptor::Install(brillo::ErrorPtr* err,
const std::string& id_in,
const std::string& omaha_url_in,
std::string* dlc_root_out) {
// TODO(xiaochu): change API to accept a list of DLC module ids.
// https://crbug.com/905075
ScopedShutdown scoped_shutdown(shutdown_delegate_);
// Initialize supported DLC module id list.
std::vector<std::string> dlc_module_ids = ScanDlcModules();
if (std::find(dlc_module_ids.begin(), dlc_module_ids.end(), id_in) ==
dlc_module_ids.end()) {
LogAndSetError(err, "The DLC ID provided is invalid.");
return false;
}
// TODO(xiaochu): may detect corrupted DLC modules and recover.
// https://crbug.com/903432
base::FilePath module_path = utils::GetDlcModulePath(content_dir_, id_in);
if (base::PathExists(module_path)) {
LogAndSetError(err, "The DLC module is installed.");
return false;
}
// Create module directory
if (!CreateDirWithDlcPermissions(module_path)) {
LogAndSetError(err, "Failed to create DLC module 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, &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, 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, 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.
chromeos_update_engine::DlcParameters dlc_parameters;
dlc_parameters.set_omaha_url(omaha_url_in);
chromeos_update_engine::DlcInfo* dlc_info = dlc_parameters.add_dlc_infos();
dlc_info->set_dlc_id(id_in);
std::string dlc_request;
if (!dlc_parameters.SerializeToString(&dlc_request)) {
LogAndSetError(err, "protobuf can not be serialized.");
return false;
}
if (!update_engine_proxy_->AttemptInstall(dlc_request, nullptr)) {
LogAndSetError(err, "Update Engine failed to schedule install operations.");
return false;
}
// TODO(xiaochu): make the API asynchronous.
// https://crbug.com/905071
// Code below this point should be moved to a callback (triggered after
// update_engine finishes install) and the function returns true immediately
// to unblock the caller. Currently, we make the assumption that a DLC install
// completes in 10 seconds which is more than enough for downloading (reading)
// a local file on DUT.
if (!WaitForUpdateEngineIdle()) {
LogAndSetError(err, "Failed waiting for update_engine to become idle");
return false;
}
// Mount the installed DLC module image.
std::string boot_disk_name;
int num_slots = -1;
int current_slot = -1;
if (!boot_slot_->GetCurrentSlot(&boot_disk_name, &num_slots, &current_slot)) {
LogAndSetError(err, "Can not get current boot slot.");
return false;
}
std::string mount_point;
if (!image_loader_proxy_->LoadDlcImage(
id_in,
current_slot == 0 ? imageloader::kSlotNameA : imageloader::kSlotNameB,
&mount_point, nullptr)) {
LogAndSetError(err, "Imageloader is not available.");
return false;
}
if (mount_point.empty()) {
LogAndSetError(err, "Imageloader LoadDlcImage failed.");
return false;
}
*dlc_root_out =
utils::GetDlcRootInModulePath(base::FilePath(mount_point)).value();
return true;
}
bool DlcServiceDBusAdaptor::Uninstall(brillo::ErrorPtr* err,
const std::string& id_in) {
ScopedShutdown scoped_shutdown(shutdown_delegate_);
// Initialize supported DLC module id list.
std::vector<std::string> dlc_module_ids = ScanDlcModules();
if (std::find(dlc_module_ids.begin(), dlc_module_ids.end(), id_in) ==
dlc_module_ids.end()) {
LogAndSetError(err, "The DLC ID provided is invalid.");
return false;
}
const base::FilePath dlc_module_content_path =
utils::GetDlcModulePath(content_dir_, id_in);
if (!base::PathExists(dlc_module_content_path) ||
!base::PathExists(utils::GetDlcModuleImagePath(content_dir_, id_in, 0)) ||
!base::PathExists(utils::GetDlcModuleImagePath(content_dir_, id_in, 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, &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.
if (!base::DeleteFile(dlc_module_content_path, true)) {
LogAndSetError(err, "DLC image folder could not be deleted.");
return false;
}
return true;
}
bool DlcServiceDBusAdaptor::GetInstalled(brillo::ErrorPtr* err,
std::string* dlc_module_list_out) {
ScopedShutdown scoped_shutdown(shutdown_delegate_);
// Initialize supported DLC module id list.
std::vector<std::string> dlc_module_ids = ScanDlcModules();
// Find installed DLC modules.
DlcModuleList dlc_module_list;
for (const auto& dlc_module_id : dlc_module_ids) {
auto dlc_module_content_path =
utils::GetDlcModulePath(content_dir_, dlc_module_id);
if (base::PathExists(dlc_module_content_path)) {
DlcModuleInfo* dlc_module_info = dlc_module_list.add_dlc_module_infos();
dlc_module_info->set_dlc_id(dlc_module_id);
}
}
if (!dlc_module_list.SerializeToString(dlc_module_list_out)) {
LogAndSetError(err, "Failed to serialize the protobuf.");
return false;
}
return true;
}
std::vector<std::string> DlcServiceDBusAdaptor::ScanDlcModules() {
std::vector<std::string> dlc_module_ids;
base::FileEnumerator file_enumerator(manifest_dir_, false,
base::FileEnumerator::DIRECTORIES);
for (base::FilePath dir_path = file_enumerator.Next(); !dir_path.empty();
dir_path = file_enumerator.Next()) {
dlc_module_ids.emplace_back(dir_path.BaseName().value());
}
return dlc_module_ids;
}
bool DlcServiceDBusAdaptor::WaitForUpdateEngineIdle() {
for (int i = 0; i < kRetryCount; i++) {
if (CheckForUpdateEngineStatus({update_engine::kUpdateStatusIdle})) {
return true;
}
base::PlatformThread::Sleep(kCheckInterval);
}
return false;
}
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;
}
} // namespace dlcservice