| // 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 <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/files/file_enumerator.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_temp_dir.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" |
| |
| using std::string; |
| using std::vector; |
| |
| 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 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. |
| string boot_disk_name; |
| int num_slots = -1; |
| int current_boot_slot = -1; |
| if (!boot_slot_->GetCurrentSlot(&boot_disk_name, &num_slots, |
| ¤t_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. |
| 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 DlcModuleList& dlc_module_list_in) { |
| const auto& dlc_modules = dlc_module_list_in.dlc_module_infos(); |
| if (dlc_modules.empty()) { |
| LogAndSetError(err, "Must provide at least one DLC to install"); |
| return false; |
| } |
| |
| // Note: this holds the list of directories that were created and need to be |
| // freed in case an error happens. |
| vector<std::unique_ptr<base::ScopedTempDir>> scoped_paths; |
| |
| for (const DlcModuleInfo& dlc_module : dlc_modules) { |
| base::FilePath path; |
| const string& id = dlc_module.dlc_id(); |
| auto scoped_path = std::make_unique<base::ScopedTempDir>(); |
| |
| if (!CreateDlc(err, id, &path)) { |
| return false; |
| } |
| if (!scoped_path->Set(path)) { |
| LOG(ERROR) << "Failed when scoping path during install: " << path.value(); |
| return false; |
| } |
| |
| scoped_paths.emplace_back(std::move(scoped_path)); |
| } |
| |
| 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. |
| if (!update_engine_proxy_->AttemptInstall(dlc_module_list_in, nullptr)) { |
| LogAndSetError(err, "Update Engine failed to schedule install operations."); |
| return false; |
| } |
| |
| dlc_modules_being_installed_ = dlc_module_list_in; |
| // Note: Do NOT add to installed indication. Let |OnStatusUpdateSignaled()| |
| // handle since hat's truly when the DLC(s) are installed. |
| |
| // Safely take ownership of scoped paths for them not to be freed. |
| for (const auto& scoped_path : scoped_paths) |
| scoped_path->Take(); |
| |
| return true; |
| } |
| |
| // TODO(crbug/986391): Need to take a protobuf as argument and not a single DLC. |
| bool DlcServiceDBusAdaptor::Uninstall(brillo::ErrorPtr* err, |
| const 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; |
| } |
| 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. |
| if (!UnmountDlcImage(err, id_in, package)) { |
| 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 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; |
| } |
| |
| // TODO(crbug.com/987019): the signal from update engine needs to indicate this. |
| // It is very dangerous for |DlcService| to be determining this. Can be removed |
| // once update engine provides this. Do not use this function anywhere. |
| bool DlcServiceDBusAdaptor::IsInstalling() { |
| return !dlc_modules_being_installed_.dlc_module_infos().empty(); |
| } |
| |
| bool DlcServiceDBusAdaptor::CreateDlc(brillo::ErrorPtr* err, |
| const string& id, |
| base::FilePath* path) { |
| path->clear(); |
| if (supported_dlc_modules_.find(id) == supported_dlc_modules_.end()) { |
| LogAndSetError(err, "The DLC ID provided is not supported."); |
| return false; |
| } |
| |
| const string& package = ScanDlcModulePackage(id); |
| base::FilePath module_path = utils::GetDlcModulePath(content_dir_, id); |
| base::FilePath module_package_path = |
| utils::GetDlcModulePackagePath(content_dir_, id, package); |
| |
| if (base::PathExists(module_path)) { |
| LogAndSetError(err, "The DLC module is installed or duplicate."); |
| return false; |
| } |
| // Create the DLC ID directory with correct permissions. |
| if (!CreateDirWithDlcPermissions(module_path)) { |
| LogAndSetError(err, "Failed to create DLC ID directory"); |
| return false; |
| } |
| // Create the DLC package directory with correct permissions. |
| if (!CreateDirWithDlcPermissions(module_package_path)) { |
| LogAndSetError(err, "Failed to create DLC ID package directory"); |
| return false; |
| } |
| |
| // Creates DLC module storage. |
| // TODO(xiaochu): Manifest currently returns a signed integer, which means |
| // will likely fail for modules >= 2 GiB in size. https://crbug.com/904539 |
| imageloader::Manifest manifest; |
| if (!dlcservice::utils::GetDlcManifest(manifest_dir_, id, 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, 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, package, 1); |
| if (!CreateImageFile(image_b_path, image_size)) { |
| LogAndSetError(err, "Failed to create slot B image file"); |
| return false; |
| } |
| |
| *path = module_path; |
| return true; |
| } |
| |
| bool DlcServiceDBusAdaptor::UnmountDlcImage(brillo::ErrorPtr* err, |
| const std::string& id, |
| const std::string& package) { |
| bool success = false; |
| if (!image_loader_proxy_->UnloadDlcImage(id, package, &success, nullptr)) { |
| LogAndSetError(err, "Imageloader is not available."); |
| return false; |
| } |
| if (!success) { |
| LogAndSetError(err, "Imageloader UnloadDlcImage failed."); |
| return false; |
| } |
| return true; |
| } |
| |
| string DlcServiceDBusAdaptor::ScanDlcModulePackage(const string& id) { |
| return *(utils::ScanDirectory(manifest_dir_.Append(id)).begin()); |
| } |
| |
| bool DlcServiceDBusAdaptor::CheckForUpdateEngineStatus( |
| const vector<string>& status_list) { |
| int64_t last_checked_time = 0; |
| double progress = 0; |
| string current_operation; |
| string new_version; |
| int64_t new_size = 0; |
| if (!update_engine_proxy_->GetStatus(&last_checked_time, &progress, |
| ¤t_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(), |
| [¤t_operation](const 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 string& current_operation, |
| const string& new_version, |
| int64_t new_size) { |
| if (!IsInstalling()) |
| 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 |
| // modules (failure or success). |
| DlcModuleList dlc_module_list = dlc_modules_being_installed_; |
| dlc_modules_being_installed_.clear_dlc_module_infos(); |
| |
| InstallResult install_result; |
| install_result.set_success(false); |
| install_result.mutable_dlc_module_list()->CopyFrom(dlc_module_list); |
| |
| // Keep track of the cleanups for DLC images. |
| utils::ScopedCleanups<base::Callback<void()>> scoped_cleanups; |
| for (const DlcModuleInfo& dlc_module : dlc_module_list.dlc_module_infos()) { |
| const string& dlc_id = dlc_module.dlc_id(); |
| string package = ScanDlcModulePackage(dlc_id); |
| auto cleanup = base::Bind( |
| [](base::Callback<bool()> unmounter, base::Callback<bool()> deleter) { |
| unmounter.Run(); |
| deleter.Run(); |
| }, |
| base::Bind(&DlcServiceDBusAdaptor::UnmountDlcImage, |
| base::Unretained(this), nullptr, dlc_id, package), |
| base::Bind(&base::DeleteFile, |
| utils::GetDlcModulePath(content_dir_, dlc_id), true)); |
| scoped_cleanups.Insert(cleanup); |
| } |
| |
| // Mount the installed DLC module images. |
| for (auto& dlc_module : |
| *install_result.mutable_dlc_module_list()->mutable_dlc_module_infos()) { |
| const string& dlc_id = dlc_module.dlc_id(); |
| string package = ScanDlcModulePackage(dlc_id); |
| 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; |
| } |
| dlc_module.set_dlc_root( |
| utils::GetDlcRootInModulePath(base::FilePath(mount_point)).value()); |
| } |
| |
| // Don't unmount+delete the images+directories as all successfully installed. |
| scoped_cleanups.Cancel(); |
| |
| // Install was a success so keep track. |
| for (const DlcModuleInfo& dlc_module : dlc_module_list.dlc_module_infos()) |
| installed_dlc_modules_.insert(dlc_module.dlc_id()); |
| |
| install_result.set_success(true); |
| install_result.set_error_code( |
| static_cast<int>(OnInstalledSignalErrorCode::kNone)); |
| SendOnInstalledSignal(install_result); |
| } |
| |
| void DlcServiceDBusAdaptor::OnStatusUpdateSignalConnected( |
| const string& interface_name, const string& signal_name, bool success) { |
| if (!success) { |
| LOG(ERROR) << "Failed to connect to update_engine's StatusUpdate signal."; |
| } |
| } |
| |
| } // namespace dlcservice |