blob: bff3eb0504c75e0f0d8390effecccd5f6ccf70ab [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.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/strings/string_util.h>
#include <brillo/errors/error.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/dlcservice/dbus-constants.h>
#include "dlcservice/dlc.h"
#include "dlcservice/error.h"
#include "dlcservice/utils.h"
using base::Callback;
using brillo::ErrorPtr;
using brillo::MessageLoop;
using std::string;
using std::vector;
using update_engine::Operation;
using update_engine::StatusResult;
namespace dlcservice {
DlcService::DlcService()
: scheduled_period_ue_check_id_(MessageLoop::kTaskIdNull),
weak_ptr_factory_(this) {
const auto prefs_dir = SystemState::Get()->dlc_prefs_dir();
if (!base::PathExists(prefs_dir)) {
CHECK(CreateDir(prefs_dir))
<< "Failed to create dlc prefs directory: " << prefs_dir;
}
dlc_manager_ = std::make_unique<DlcManager>();
// Register D-Bus signal callbacks.
update_engine_proxy_ = SystemState::Get()->update_engine();
update_engine_proxy_->RegisterStatusUpdateAdvancedSignalHandler(
base::Bind(&DlcService::OnStatusUpdateAdvancedSignal,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&DlcService::OnStatusUpdateAdvancedSignalConnected,
weak_ptr_factory_.GetWeakPtr()));
}
DlcService::~DlcService() {
if (scheduled_period_ue_check_id_ != MessageLoop::kTaskIdNull &&
!brillo::MessageLoop::current()->CancelTask(
scheduled_period_ue_check_id_))
LOG(ERROR)
<< "Failed to cancel delayed update_engine check during cleanup.";
}
void DlcService::Initialize() {
dlc_manager_->Initialize();
}
const DlcBase* DlcService::GetDlc(const DlcId& id) {
return dlc_manager_->GetDlc(id);
}
bool DlcService::Install(const DlcIdList& dlcs,
const string& omaha_url,
ErrorPtr* err) {
// If an install is already in progress, dlcservice is busy.
if (dlc_manager_->IsInstalling()) {
*err = Error::Create(kErrorBusy, "Another install is already in progress.");
LOG(ERROR) << Error::ToString(*err);
return false;
}
// Check what state update_engine is in.
Operation update_engine_op;
if (!GetUpdateEngineStatus(&update_engine_op)) {
*err = Error::Create(kErrorInternal,
"Failed to get the status of Update Engine.");
LOG(ERROR) << Error::ToString(*err);
return false;
}
switch (update_engine_op) {
case update_engine::UPDATED_NEED_REBOOT:
*err =
Error::Create(kErrorNeedReboot,
"Update Engine applied update, device needs a reboot.");
LOG(ERROR) << Error::ToString(*err);
return false;
case update_engine::IDLE:
break;
default:
*err =
Error::Create(kErrorBusy, "Update Engine is performing operations.");
LOG(ERROR) << Error::ToString(*err);
return false;
}
if (!dlc_manager_->InitInstall(dlcs, err)) {
LOG(ERROR) << Error::ToString(*err);
return false;
}
// This is the unique DLC(s) that actually need to be installed.
DlcIdList unique_dlcs_to_install = dlc_manager_->GetMissingInstalls();
// Check if there is nothing to install.
if (unique_dlcs_to_install.size() == 0) {
SendOnInstallStatusSignal(Status::COMPLETED, kErrorNone,
dlc_manager_->GetSupported(), 1.);
return true;
}
LOG(INFO) << "Sending request to update_engine to install DLCs="
<< base::JoinString(unique_dlcs_to_install, ",");
// Invokes update_engine to install the DLC.
ErrorPtr tmp_err;
if (!update_engine_proxy_->AttemptInstall(omaha_url, unique_dlcs_to_install,
&tmp_err)) {
// TODO(kimjae): need update engine to propagate correct error message by
// passing in |ErrorPtr| and being set within update engine, current default
// is to indicate that update engine is updating because there is no way an
// install should have taken place if not through dlcservice. (could also be
// the case that an update applied between the time of the last status check
// above, but just return |kErrorBusy| because the next time around if an
// update has been applied and is in a reboot needed state, it will indicate
// correctly then).
LOG(ERROR) << "Update Engine failed to install requested DLCs: "
<< (tmp_err ? Error::ToString(tmp_err)
: "Missing error from update engine proxy.");
*err = Error::Create(
kErrorBusy, "Update Engine failed to schedule install operations.");
LOG(ERROR) << Error::ToString(*err);
// dlcservice must cancel the install by communicating to dlc_manager who
// manages the DLC(s), as update_engine won't be able to install the
// initialized DLC(s) for installation.
if (!dlc_manager_->CancelInstall(&tmp_err))
LOG(ERROR) << Error::ToString(tmp_err);
return false;
}
SchedulePeriodicInstallCheck(true);
return true;
}
bool DlcService::Uninstall(const string& id_in, brillo::ErrorPtr* err) {
// TODO(crbug.com/1069162): Uninstall should remove based on ref-counting
// logic.
return Purge(id_in, err);
}
bool DlcService::Purge(const string& id_in, brillo::ErrorPtr* err) {
// Check that an update isn't in progress.
if (!dlc_manager_->IsInstalling()) {
Operation op;
if (!GetUpdateEngineStatus(&op)) {
*err = Error::Create(kErrorInternal,
"Failed to get the status of Update Engine");
LOG(ERROR) << Error::ToString(*err);
return false;
}
switch (op) {
case update_engine::IDLE:
case update_engine::UPDATED_NEED_REBOOT:
break;
default:
*err = Error::Create(kErrorBusy, "Install or update is in progress.");
LOG(ERROR) << Error::ToString(*err);
return false;
}
}
bool ret = dlc_manager_->Delete(id_in, err);
if (!ret)
LOG(ERROR) << Error::ToString(*err);
return ret;
}
bool DlcService::GetDlcState(const std::string& id_in,
DlcState* dlc_state,
ErrorPtr* err) {
bool ret = dlc_manager_->GetDlcState(id_in, dlc_state, err);
if (!ret)
LOG(ERROR) << Error::ToString(*err);
return ret;
}
DlcIdList DlcService::GetInstalled() {
return dlc_manager_->GetInstalled();
}
DlcIdList DlcService::GetExistingDlcs() {
return dlc_manager_->GetExistingDlcs();
}
DlcIdList DlcService::GetDlcsToUpdate() {
return dlc_manager_->GetDlcsToUpdate();
}
bool DlcService::InstallCompleted(const DlcIdList& ids_in, ErrorPtr* err) {
bool ret = dlc_manager_->InstallCompleted(ids_in, err);
if (!ret)
LOG(ERROR) << Error::ToString(*err);
return ret;
}
bool DlcService::UpdateCompleted(const DlcIdList& ids_in, ErrorPtr* err) {
bool ret = dlc_manager_->UpdateCompleted(ids_in, err);
if (!ret)
LOG(ERROR) << Error::ToString(*err);
return ret;
}
void DlcService::SendFailedSignalAndCleanup() {
ErrorPtr tmp_err;
if (!dlc_manager_->CancelInstall(&tmp_err))
LOG(ERROR) << Error::ToString(tmp_err);
SendOnInstallStatusSignal(Status::FAILED, kErrorInternal,
dlc_manager_->GetSupported(), 0.);
}
void DlcService::PeriodicInstallCheck() {
if (scheduled_period_ue_check_id_ == MessageLoop::kTaskIdNull) {
LOG(ERROR) << "Should not have been called unless scheduled.";
return;
}
scheduled_period_ue_check_id_ = MessageLoop::kTaskIdNull;
if (!dlc_manager_->IsInstalling()) {
LOG(ERROR) << "Should not have to check update_engine status while not "
"performing an install.";
return;
}
Operation update_engine_op;
if (!GetUpdateEngineStatus(&update_engine_op)) {
LOG(ERROR)
<< "Failed to get the status of update_engine, it is most likely down.";
SendFailedSignalAndCleanup();
return;
}
switch (update_engine_op) {
case update_engine::UPDATED_NEED_REBOOT:
LOG(ERROR) << "Thought to be installing DLC(s), but update_engine is not "
"installing and actually performed an update.";
SendFailedSignalAndCleanup();
break;
case update_engine::IDLE:
if (scheduled_period_ue_check_retry_) {
LOG(INFO) << "Going to retry periodic check to check install signal.";
SchedulePeriodicInstallCheck(false);
return;
}
SendFailedSignalAndCleanup();
break;
default:
SchedulePeriodicInstallCheck(true);
}
}
void DlcService::SchedulePeriodicInstallCheck(bool retry) {
if (scheduled_period_ue_check_id_ != MessageLoop::kTaskIdNull) {
LOG(ERROR) << "Scheduling logic is internally not handled correctly, this "
<< "requires a scheduling logic update.";
if (!brillo::MessageLoop::current()->CancelTask(
scheduled_period_ue_check_id_)) {
LOG(ERROR) << "Failed to cancel previous delayed update_engine check "
<< "when scheduling.";
}
}
scheduled_period_ue_check_id_ =
brillo::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&DlcService::PeriodicInstallCheck,
weak_ptr_factory_.GetWeakPtr()),
base::TimeDelta::FromSeconds(kUECheckTimeout));
scheduled_period_ue_check_retry_ = retry;
}
bool DlcService::HandleStatusResult(const StatusResult& status_result) {
// If we are not installing any DLC(s), no need to even handle status result.
if (!dlc_manager_->IsInstalling())
return false;
// When a signal is received from update_engine, it is more efficient to
// cancel the periodic check that's scheduled by re-posting a delayed task
// after cancelling the currently set periodic check. If the cancelling of the
// periodic check fails, let it run as it will be rescheduled correctly within
// the periodic check itself again.
if (!brillo::MessageLoop::current()->CancelTask(
scheduled_period_ue_check_id_)) {
LOG(ERROR) << "Failed to cancel delayed update_engine check when signal "
"was received from update_engine, so letting it run.";
} else {
scheduled_period_ue_check_id_ = MessageLoop::kTaskIdNull;
}
if (!status_result.is_install()) {
LOG(ERROR) << "Signal from update_engine indicates that it's not for an "
"install, but dlcservice was waiting for an install.";
SendFailedSignalAndCleanup();
return false;
}
switch (status_result.current_operation()) {
case Operation::IDLE:
LOG(INFO)
<< "Signal from update_engine, proceeding to complete installation.";
return true;
case Operation::REPORTING_ERROR_EVENT:
LOG(ERROR) << "Signal from update_engine indicates reporting failure.";
SendFailedSignalAndCleanup();
return false;
// Only when update_engine's |Operation::DOWNLOADING| should dlcservice send
// a signal out for |InstallStatus| for |Status::RUNNING|. Majority of the
// install process for DLC(s) is during |Operation::DOWNLOADING|, this also
// means that only a single growth from 0.0 to 1.0 for progress reporting
// will happen.
case Operation::DOWNLOADING:
SendOnInstallStatusSignal(Status::RUNNING, kErrorNone,
dlc_manager_->GetSupported(),
status_result.progress());
FALLTHROUGH;
default:
SchedulePeriodicInstallCheck(true);
return false;
}
}
bool DlcService::GetUpdateEngineStatus(Operation* operation) {
StatusResult status_result;
if (!update_engine_proxy_->GetStatusAdvanced(&status_result, nullptr)) {
return false;
}
*operation = status_result.current_operation();
return true;
}
void DlcService::AddObserver(DlcService::Observer* observer) {
observers_.push_back(observer);
}
void DlcService::SendOnInstallStatusSignal(const dlcservice::Status& status,
const std::string& error_code,
const DlcIdList& ids,
double progress) {
InstallStatus install_status;
install_status.set_status(status);
switch (status) {
case COMPLETED:
case FAILED:
install_status.set_state(InstallStatus::IDLE);
break;
case RUNNING:
install_status.set_state(InstallStatus::INSTALLING);
break;
default:
NOTREACHED();
}
install_status.set_error_code(error_code);
DlcModuleList* dlc_list = install_status.mutable_dlc_module_list();
for (const auto& id : ids) {
const auto* dlc = dlc_manager_->GetDlc(id);
dlc_list->add_dlc_module_infos()->set_dlc_id(id);
dlc_list->add_dlc_module_infos()->set_dlc_root(dlc->GetRoot().value());
}
install_status.set_progress(progress);
for (const auto& observer : observers_) {
observer->SendInstallStatus(install_status);
}
}
void DlcService::OnStatusUpdateAdvancedSignal(
const StatusResult& status_result) {
if (!HandleStatusResult(status_result))
return;
ErrorPtr tmp_err;
if (!dlc_manager_->FinishInstall(&tmp_err)) {
LOG(ERROR) << Error::ToString(tmp_err);
SendOnInstallStatusSignal(Status::FAILED, kErrorInternal,
dlc_manager_->GetSupported(), 0.);
return;
}
SendOnInstallStatusSignal(Status::COMPLETED, kErrorNone,
dlc_manager_->GetSupported(), 1.);
}
void DlcService::OnStatusUpdateAdvancedSignalConnected(
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