blob: 58e1526142d0b410804f5ad2c4ccdde088d6ce65 [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 <base/files/file_enumerator.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/strings/stringprintf.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/ref_count.h"
#include "dlcservice/utils.h"
using base::Callback;
using brillo::ErrorPtr;
using brillo::MessageLoop;
using std::string;
using update_engine::Operation;
using update_engine::StatusResult;
namespace dlcservice {
DlcService::DlcService()
: periodic_install_check_id_(MessageLoop::kTaskIdNull),
weak_ptr_factory_(this) {}
DlcService::~DlcService() {
if (periodic_install_check_id_ != MessageLoop::kTaskIdNull &&
!brillo::MessageLoop::current()->CancelTask(periodic_install_check_id_))
LOG(ERROR)
<< "Failed to cancel delayed update_engine check during cleanup.";
}
void DlcService::Initialize() {
auto* system_state = SystemState::Get();
const auto prefs_dir = system_state->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.
system_state->update_engine()->RegisterStatusUpdateAdvancedSignalHandler(
base::Bind(&DlcService::OnStatusUpdateAdvancedSignal,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&DlcService::OnStatusUpdateAdvancedSignalConnected,
weak_ptr_factory_.GetWeakPtr()));
system_state->session_manager()->RegisterSessionStateChangedSignalHandler(
base::Bind(&DlcService::OnSessionStateChangedSignal,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&DlcService::OnSessionStateChangedSignalConnected,
weak_ptr_factory_.GetWeakPtr()));
dlc_manager_->Initialize();
}
bool DlcService::Install(const DlcId& id,
const string& omaha_url,
ErrorPtr* err) {
bool result = InstallInternal(id, omaha_url, err);
// Only send error metrics in here. Install success metrics is sent in
// |DlcBase|.
if (!result) {
SystemState::Get()->metrics()->SendInstallResultFailure(err);
Error::ConvertToDbusError(err);
}
return result;
}
bool DlcService::InstallInternal(const DlcId& id,
const string& omaha_url,
ErrorPtr* err) {
// TODO(ahassani): Currently, we create the DLC images even if later we find
// out the update_engine is busy and we have to delete the images. It would be
// better to know the update_engine status beforehand so we can tell the DLC
// to not create the images, just load them if it can. We can do this more
// reliably by caching the last status we saw from update_engine, rather than
// pulling for it on every install request. That would also allows us to
// properly queue the incoming install requests.
// Try to install and figure out if install through update_engine is needed.
bool external_install_needed = false;
if (!dlc_manager_->Install(id, &external_install_needed, err)) {
LOG(ERROR) << "Failed to install DLC=" << id;
return false;
}
// Install through update_engine only if needed.
if (!external_install_needed)
return true;
if (!InstallWithUpdateEngine(id, omaha_url, err)) {
// dlcservice must cancel the install as update_engine won't be able to
// install the initialized DLC.
CancelInstall(*err);
return false;
}
// By now the update_engine is installing the DLC, so schedule a periodic
// install checker in case we miss update_engine signals.
SchedulePeriodicInstallCheck();
return true;
}
bool DlcService::InstallWithUpdateEngine(const DlcId& id,
const string& omaha_url,
ErrorPtr* err) {
// Check what state update_engine is in.
if (SystemState::Get()->update_engine_status().current_operation() ==
update_engine::UPDATED_NEED_REBOOT) {
*err =
Error::Create(FROM_HERE, kErrorNeedReboot,
"Update Engine applied update, device needs a reboot.");
return false;
}
LOG(INFO) << "Sending request to update_engine to install DLC=" << id;
// Invokes update_engine to install the DLC.
ErrorPtr tmp_err;
installing_dlc_id_ = id;
if (!SystemState::Get()->update_engine()->AttemptInstall(omaha_url, {id},
&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(FROM_HERE, kErrorBusy,
"Update Engine failed to schedule install operations.");
return false;
}
return true;
}
bool DlcService::Uninstall(const string& id, brillo::ErrorPtr* err) {
bool result = dlc_manager_->Uninstall(id, err);
SystemState::Get()->metrics()->SendUninstallResult(err);
if (!result)
Error::ConvertToDbusError(err);
return result;
}
bool DlcService::Purge(const string& id, brillo::ErrorPtr* err) {
return dlc_manager_->Purge(id, err);
}
const DlcBase* DlcService::GetDlc(const DlcId& id, brillo::ErrorPtr* err) {
return dlc_manager_->GetDlc(id, err);
}
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, ErrorPtr* err) {
return dlc_manager_->InstallCompleted(ids, err);
}
bool DlcService::UpdateCompleted(const DlcIdList& ids, ErrorPtr* err) {
return dlc_manager_->UpdateCompleted(ids, err);
}
bool DlcService::FinishInstall(ErrorPtr* err) {
if (!installing_dlc_id_) {
LOG(ERROR) << "No DLC installation to finish.";
return false;
}
auto id = installing_dlc_id_.value();
installing_dlc_id_.reset();
return dlc_manager_->FinishInstall(id, err);
}
void DlcService::CancelInstall(const ErrorPtr& err_in) {
if (!installing_dlc_id_) {
LOG(ERROR) << "No DLC installation to cancel.";
return;
}
auto id = installing_dlc_id_.value();
installing_dlc_id_.reset();
ErrorPtr tmp_err;
if (!dlc_manager_->CancelInstall(id, err_in, &tmp_err))
LOG(ERROR) << "Failed to cancel install for DLC=" << id;
}
void DlcService::PeriodicInstallCheck() {
periodic_install_check_id_ = MessageLoop::kTaskIdNull;
// If we're not installing anything anymore, no need to schedule again.
if (!installing_dlc_id_)
return;
const int kNotSeenStatusDelay = 10;
auto* system_state = SystemState::Get();
if ((system_state->clock()->Now() -
system_state->update_engine_status_timestamp()) >
base::TimeDelta::FromSeconds(kNotSeenStatusDelay)) {
if (GetUpdateEngineStatus()) {
ErrorPtr tmp_error;
if (!HandleStatusResult(&tmp_error)) {
return;
}
}
}
SchedulePeriodicInstallCheck();
}
void DlcService::SchedulePeriodicInstallCheck() {
if (periodic_install_check_id_ != MessageLoop::kTaskIdNull) {
LOG(INFO) << "Another periodic install check already scheduled.";
return;
}
periodic_install_check_id_ = brillo::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&DlcService::PeriodicInstallCheck,
weak_ptr_factory_.GetWeakPtr()),
base::TimeDelta::FromSeconds(kUECheckTimeout));
}
bool DlcService::HandleStatusResult(brillo::ErrorPtr* err) {
// If we are not installing any DLC(s), no need to even handle status result.
if (!installing_dlc_id_)
return true;
const StatusResult& status = SystemState::Get()->update_engine_status();
if (!status.is_install()) {
*err = Error::CreateInternal(
FROM_HERE, error::kFailedInstallInUpdateEngine,
"Signal from update_engine indicates that it's not for an install, but "
"dlcservice was waiting for an install.");
CancelInstall(*err);
SystemState::Get()->metrics()->SendInstallResultFailure(err);
return false;
}
switch (status.current_operation()) {
case update_engine::UPDATED_NEED_REBOOT:
*err =
Error::Create(FROM_HERE, kErrorNeedReboot,
"Update Engine applied update, device needs a reboot.");
break;
case Operation::IDLE:
LOG(INFO)
<< "Signal from update_engine, proceeding to complete installation.";
// Send metrics in |DlcBase::FinishInstall| and not here since we might
// be executing this call for multiple DLCs.
if (!FinishInstall(err)) {
LOG(ERROR) << "Failed to finish install.";
return false;
}
return true;
case Operation::REPORTING_ERROR_EVENT:
*err =
Error::CreateInternal(FROM_HERE, error::kFailedInstallInUpdateEngine,
"update_engine indicates reporting failure.");
break;
// Only when update_engine's |Operation::DOWNLOADING| should the DLC send
// |DlcState::INSTALLING|. 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:
// TODO(ahassani): Add unittest for this.
dlc_manager_->ChangeProgress(status.progress());
FALLTHROUGH;
default:
return true;
}
CancelInstall(*err);
SystemState::Get()->metrics()->SendInstallResultFailure(err);
return false;
}
bool DlcService::GetUpdateEngineStatus() {
StatusResult status_result;
if (!SystemState::Get()->update_engine()->GetStatusAdvanced(&status_result,
nullptr)) {
LOG(ERROR) << "Failed to get update_engine status, will try again later.";
return false;
}
SystemState::Get()->set_update_engine_status(status_result);
LOG(INFO) << "Got update_engine status: "
<< status_result.current_operation();
return true;
}
void DlcService::OnStatusUpdateAdvancedSignal(
const StatusResult& status_result) {
// Always set the status.
SystemState::Get()->set_update_engine_status(status_result);
ErrorPtr err;
if (!HandleStatusResult(&err))
DCHECK(err.get());
}
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.";
}
if (!GetUpdateEngineStatus()) {
// As a last resort, if we couldn't get the status, just set the status to
// IDLE, so things can move forward. This is mostly the case because when
// update_engine comes up its first status is IDLE and it will stay that way
// for quite a while.
StatusResult status;
status.set_current_operation(Operation::IDLE);
status.set_is_install(false);
}
}
void DlcService::OnSessionStateChangedSignalConnected(
const string& interface_name, const string& signal_name, bool success) {
if (!success) {
LOG(ERROR) << "Failed to connect to session_manager's SessionStateChanged "
<< "signal.";
}
}
void DlcService::OnSessionStateChangedSignal(const std::string& state) {
UserRefCount::SessionChanged(state);
}
} // namespace dlcservice