blob: 81344448c9f15a733c200efaf8ae8e18462284ba [file] [log] [blame] [edit]
// Copyright 2018 The ChromiumOS Authors
// 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 <cstdint>
#include <memory>
#include <optional>
#include <unordered_set>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/functional/bind.h>
#include <base/functional/callback_helpers.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include <brillo/dbus/dbus_method_response.h>
#include <brillo/files/file_util.h>
#include <brillo/errors/error.h>
#include <chromeos/dbus/service_constants.h>
#include <constants/imageloader.h>
#include <dbus/dlcservice/dbus-constants.h>
#include <dlcservice/proto_bindings/dlcservice.pb.h>
#include <lvmd/proto_bindings/lvmd.pb.h>
#include "dlcservice/error.h"
#include "dlcservice/installer.h"
#include "dlcservice/prefs.h"
#include "dlcservice/system_state.h"
#include "dlcservice/types.h"
#include "dlcservice/utils.h"
#include "dlcservice/utils/utils_interface.h"
using brillo::ErrorPtr;
using brillo::MessageLoop;
using std::string;
namespace dlcservice {
namespace {
// This value represents the delay in seconds between each idle installation
// status task.
constexpr size_t kPeriodicInstallCheckSecondsDelay = 10;
// This value here is the tolerance cap (allowance) of non-install signals
// broadcasted by `installer`. Keep in mind when changing of it's relation
// with the periodic install check delay as that will also determine the max
// idle period before an installation of a DLC is halted.
constexpr size_t kToleranceCap = 30;
// The time delay for reporting daily metrics.
constexpr base::TimeDelta kPeriodicMetricsReportingDelay = base::Minutes(5);
DlcIdList ToDlcIdList(const DlcMap& dlcs,
const std::function<bool(const DlcType&)>& filter) {
DlcIdList list;
for (const auto& pair : dlcs) {
if (filter(pair.second))
list.push_back(pair.first);
}
return list;
}
} // namespace
DlcService::DlcService(std::unique_ptr<DlcCreatorInterface> dlc_creator,
std::shared_ptr<UtilsInterface> utils)
: periodic_install_check_id_(MessageLoop::kTaskIdNull),
dlc_creator_(std::move(dlc_creator)),
utils_(utils),
periodic_metrics_reporting_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) << AlertLogTag(kCategoryCleanup)
<< "Failed to cancel delayed installer check during cleanup.";
if (periodic_metrics_reporting_id_ != MessageLoop::kTaskIdNull &&
!brillo::MessageLoop::current()->CancelTask(
periodic_metrics_reporting_id_))
LOG(ERROR) << AlertLogTag(kCategoryCleanup)
<< "Failed to cancel delayed metrics reporting 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;
}
system_state->installer()->AddObserver(this);
system_state->installer()->OnReady(base::BindOnce(
&DlcService::OnReadyInstaller, weak_ptr_factory_.GetWeakPtr()));
supported_.clear();
auto initialize_dlc = [this](const DlcId& id) -> void {
auto result = supported_.emplace(id, dlc_creator_->Create(id));
if (!result.first->second->Initialize()) {
LOG(ERROR) << "Failed to initialize DLC " << id;
supported_.erase(id);
}
};
// Get supported DLCs from compressed metadata, and initialize them.
for (const auto& id :
utils_->GetSupportedDlcIds(SystemState::Get()->manifest_dir())) {
initialize_dlc(id);
}
// Initialize supported DLC(s).
for (const auto& id : ScanDirectory(SystemState::Get()->manifest_dir())) {
if (supported_.find(id) == supported_.end())
initialize_dlc(id);
}
CleanupUnsupported();
// Start daily metrics reporting.
ScheduleReportDailyMetrics();
}
void DlcService::CleanupUnsupported() {
auto* system_state = SystemState::Get();
// Delete deprecated DLC(s) in content directory.
for (const auto& id : ScanDirectory(system_state->content_dir())) {
brillo::ErrorPtr tmp_err;
if (GetDlc(id, &tmp_err) != nullptr)
continue;
for (const auto& path : GetPathsToDelete(id))
if (base::PathExists(path)) {
if (!brillo::DeletePathRecursively(path))
PLOG(ERROR) << "Failed to delete path=" << path;
else
LOG(INFO) << "Deleted path=" << path << " for deprecated DLC=" << id;
}
}
#if USE_LVM_STATEFUL_PARTITION
CleanupUnsupportedLvs();
#endif // USE_LVM_STATEFUL_PARTITION
// Delete the unsupported/preload not allowed DLC(s) in the preloaded
// directory.
auto preloaded_content_dir = system_state->preloaded_content_dir();
for (const auto& id : ScanDirectory(preloaded_content_dir)) {
brillo::ErrorPtr tmp_err;
auto* dlc = GetDlc(id, &tmp_err);
if (dlc != nullptr && dlc->IsPreloadAllowed())
continue;
// Preloading is not allowed for this image so it will be deleted.
auto path = JoinPaths(preloaded_content_dir, id);
if (!brillo::DeletePathRecursively(path))
PLOG(ERROR) << "Failed to delete path=" << path;
else
LOG(INFO) << "Deleted path=" << path
<< " for unsupported/preload not allowed DLC=" << id;
}
}
#if USE_LVM_STATEFUL_PARTITION
void DlcService::CleanupUnsupportedLvs() {
lvmd::LogicalVolumeList lvs;
if (!SystemState::Get()->lvmd_wrapper()->ListLogicalVolumes(&lvs)) {
LOG(ERROR) << "Failed to list logical volumes for cleaning.";
} else {
std::vector<string> lv_names;
for (const auto& lv : lvs.logical_volume()) {
const auto& id = utils_->LogicalVolumeNameToId(lv.name());
if (id.empty()) {
continue;
}
brillo::ErrorPtr tmp_err;
if (GetDlc(id, &tmp_err) != nullptr) {
continue;
}
lv_names.push_back(lv.name());
}
// Asynchronously delete all unsupported DLC(s).
if (!lv_names.empty()) {
SystemState::Get()->lvmd_wrapper()->RemoveLogicalVolumesAsync(
lv_names,
base::BindOnce(
[](const decltype(lv_names)& lv_names, bool success) {
if (success) {
LOG(INFO)
<< "Successfully removed all stale logical volumes.";
return;
}
LOG(ERROR) << "Failed to remove stale logical volumes: "
<< base::JoinString(lv_names, ", ");
},
lv_names));
}
}
}
#endif // USE_LVM_STATEFUL_PARTITION
void DlcService::OnReadyInstaller(bool available) {
LOG(INFO) << "Installer service available=" << available;
SystemState::Get()->installer()->StatusSync();
}
void DlcService::Install(
const InstallRequest& install_request,
std::unique_ptr<brillo::dbus_utils::DBusMethodResponse<>> response) {
// TODO(b/220202911): Start parallelizing installations.
// Ash Chrome dlcservice client handled installations in a queue, but
// dlcservice has numerous other DBus clients that can all race to install
// various DLCs. This checks here need to guarantee atomic installation per
// DLC in sequence.
auto ret_func =
[install_request](
const DlcId& dlc_id,
std::unique_ptr<brillo::dbus_utils::DBusMethodResponse<>> response,
brillo::ErrorPtr* err) -> void {
// Only send error metrics in here. Install success metrics is sent in
// |DlcBase|.
LOG(ERROR) << AlertLogTag(kCategoryInstall)
<< "Failed to install DLC=" << dlc_id;
SystemState::Get()->metrics()->SendInstallResultFailure(err);
Error::ConvertToDbusError(err);
std::move(response)->ReplyWithError(err->get());
};
brillo::ErrorPtr err;
auto* dlc = GetDlc(install_request.id(), &err);
if (dlc == nullptr) {
LOG(ERROR) << "Requested to install unsupported DLC="
<< install_request.id();
return ret_func(install_request.id(), std::move(response), &err);
}
const auto& sanitized_id = dlc->GetSanitizedId();
dlc->SetReserve(install_request.reserve());
// Try to install and figure out if install through installer is needed.
bool external_install_needed = false;
// If the DLC is being installed, nothing can be done anymore.
if (!dlc->IsInstalling()) {
// Otherwise proceed to install the DLC.
if (!dlc->Install(&err)) {
Error::AddInternalTo(
&err, FROM_HERE, error::kFailedInternal,
base::StringPrintf("Failed to initialize installation for DLC=%s",
sanitized_id.c_str()));
return ret_func(sanitized_id, std::move(response), &err);
}
// If the DLC is now in installing state, it means it now needs
// installer installation.
external_install_needed = dlc->IsInstalling();
}
// Install through installer only if needed.
if (!external_install_needed) {
std::move(response)->Return();
return;
}
if (installing_dlc_id_ && installing_dlc_id_ != install_request.id()) {
auto err_str = base::StringPrintf(
"Installation already in progress for (%s), can't install %s right "
"now.",
SanitizeId(*installing_dlc_id_).c_str(), sanitized_id.c_str());
LOG(ERROR) << err_str;
err = Error::Create(FROM_HERE, kErrorBusy, err_str);
ErrorPtr tmp_err;
if (dlc->IsInstalling() && !dlc->CancelInstall(err, &tmp_err))
LOG(ERROR) << "Failed to cancel install for DLC=" << sanitized_id;
return ret_func(sanitized_id, std::move(response), &err);
}
InstallViaInstaller(install_request, std::move(response));
}
void DlcService::InstallViaInstaller(
const InstallRequest& install_request,
std::unique_ptr<brillo::dbus_utils::DBusMethodResponse<>> response) {
auto ret_func =
[this, install_request](
const DlcId& dlc_id,
std::unique_ptr<brillo::dbus_utils::DBusMethodResponse<>> response,
brillo::ErrorPtr err) -> void {
LOG(ERROR) << AlertLogTag(kCategoryInstall)
<< "Failed to install DLC=" << dlc_id;
SystemState::Get()->metrics()->SendInstallResultFailure(&err);
Error::ConvertToDbusError(&err);
// dlcservice must cancel the install as installer won't be able to
// install the initialized DLC.
CancelInstall(err);
std::move(response)->ReplyWithError(err.get());
};
// Need to set in order for the cancellation of DLC setup.
installing_dlc_id_ = install_request.id();
brillo::ErrorPtr err;
auto* dlc = GetDlc(install_request.id(), &err);
if (dlc == nullptr) {
return ret_func(install_request.id(), std::move(response), err->Clone());
}
const auto& sanitized_id = dlc->GetSanitizedId();
// If installer needs to handle the installation, wait for the service to
// be up and DBus object exported. Returning busy error will allow Chrome
// client to retry the installation.
if (!SystemState::Get()->installer()->IsReady()) {
string err_str = "Installation called before installer is available.";
return ret_func(sanitized_id, std::move(response),
Error::Create(FROM_HERE, kErrorBusy, err_str));
}
// Check what state installer is in.
if (SystemState::Get()->installer_status().state ==
InstallerInterface::Status::State::BLOCKED) {
return ret_func(
sanitized_id, std::move(response),
Error::Create(FROM_HERE, kErrorNeedReboot,
"Installer is blocked, device needs a reboot."));
}
// 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(INFO) << "Sending request to install DLC=" << sanitized_id;
auto [response_bind1, response_bind2] =
base::SplitOnceCallback(base::BindOnce(
[](std::unique_ptr<brillo::dbus_utils::DBusMethodResponse<>> response,
brillo::ErrorPtr err) -> void {
if (err.get()) {
std::move(response)->ReplyWithError(err.get());
} else {
std::move(response)->Return();
}
},
std::move(response)));
SystemState::Get()->installer()->Install(
Installer::InstallArgs{
.id = install_request.id(),
.url = install_request.omaha_url(),
.scaled = dlc->IsScaled(),
.force_ota = dlc->IsForceOTA(),
},
base::BindOnce(&DlcService::OnInstallSuccess,
weak_ptr_factory_.GetWeakPtr(), std::move(response_bind1)),
base::BindOnce(&DlcService::OnInstallFailure,
weak_ptr_factory_.GetWeakPtr(),
std::move(response_bind2)));
}
void DlcService::OnInstallSuccess(
base::OnceCallback<void(brillo::ErrorPtr)> response_func) {
// By now the installer is installing the DLC, so schedule a periodic
// install checker in case we miss installer signals.
SchedulePeriodicInstallCheck();
std::move(response_func).Run(nullptr);
}
void DlcService::OnInstallFailure(
base::OnceCallback<void(brillo::ErrorPtr)> response_func,
brillo::Error* err) {
// Handled during other signal/response.
if (!installing_dlc_id_)
return;
// Keep this double logging until tagging is removed/updated.
LOG(ERROR) << "Installer failed to install requested DLCs: "
<< (err ? Error::ToString(err->Clone())
: "Missing error from installer.");
LOG(ERROR) << AlertLogTag(kCategoryInstall)
<< "Failed to install DLC=" << SanitizeId(*installing_dlc_id_);
auto ret_err =
Error::Create(FROM_HERE, kErrorBusy,
"Installer failed to schedule install operations.");
// dlcservice must cancel the install as installer won't be able to
// install the initialized DLC.
CancelInstall(ret_err);
SystemState::Get()->metrics()->SendInstallResultFailure(&ret_err);
Error::ConvertToDbusError(&ret_err);
std::move(response_func).Run(ret_err->Clone());
}
bool DlcService::Uninstall(const string& id, brillo::ErrorPtr* err) {
DCHECK(err);
// `GetDlc(...)` should set the error when `nullptr` is returned.
auto* dlc = GetDlc(id, err);
bool result = dlc && dlc->Uninstall(err);
SystemState::Get()->metrics()->SendUninstallResult(err);
if (!result) {
LOG(ERROR) << AlertLogTag(kCategoryUninstall) << "Failed to uninstall DLC="
<< (dlc ? dlc->GetSanitizedId() : id);
Error::ConvertToDbusError(err);
}
return result;
}
bool DlcService::Deploy(const DlcId& id, brillo::ErrorPtr* err) {
DCHECK(err);
auto* dlc = GetDlc(id, err);
return dlc && dlc->Deploy(err);
}
DlcInterface* DlcService::GetDlc(const DlcId& id, brillo::ErrorPtr* err) {
const auto& iter = supported_.find(id);
if (iter == supported_.end()) {
*err = Error::Create(
FROM_HERE, kErrorInvalidDlc,
base::StringPrintf("Passed unsupported DLC=%s", id.c_str()));
return nullptr;
}
return iter->second.get();
}
DlcIdList DlcService::GetInstalled(const ListRequest& list_request) {
std::optional<SelectDlc> select;
if (list_request.has_select()) {
select = list_request.select();
}
if (list_request.check_mount()) {
DlcIdList installed;
MountedDlcsAction(base::FilePath(imageloader::kImageloaderMountBase),
select, [&installed](DlcInterface* dlc) -> bool {
installed.push_back(dlc->GetId());
return true;
});
return installed;
} else {
return ToDlcIdList(supported_, [&select](const DlcType& dlc) {
return dlc->IsInstalled() &&
(!select || (select->user_tied() && dlc->IsUserTied()) ||
(select->scaled() && dlc->IsScaled()));
});
}
}
DlcIdList DlcService::GetExistingDlcs() {
std::unordered_set<DlcId> unique_existing_dlcs;
// This scans the files based DLC(s).
for (const auto& id : ScanDirectory(SystemState::Get()->content_dir())) {
brillo::ErrorPtr tmp_err;
if (GetDlc(id, &tmp_err) != nullptr) {
unique_existing_dlcs.insert(id);
}
}
#if USE_LVM_STATEFUL_PARTITION
// This scans the logical volume based DLC(s).
lvmd::LogicalVolumeList lvs;
if (!SystemState::Get()->lvmd_wrapper()->ListLogicalVolumes(&lvs)) {
LOG(ERROR) << "Failed to list logical volumes.";
} else {
for (const auto& lv : lvs.logical_volume()) {
const auto& id = utils_->LogicalVolumeNameToId(lv.name());
if (id.empty()) {
continue;
}
brillo::ErrorPtr tmp_err;
if (GetDlc(id, &tmp_err) != nullptr) {
unique_existing_dlcs.insert(id);
}
}
}
#endif // USE_LVM_STATEFUL_PARTITION
return {std::begin(unique_existing_dlcs), std::end(unique_existing_dlcs)};
}
DlcIdList DlcService::GetDlcsToUpdate() {
return ToDlcIdList(
supported_, [](const DlcType& dlc) { return dlc->MakeReadyForUpdate(); });
}
bool DlcService::InstallCompleted(const DlcIdList& ids, ErrorPtr* err) {
auto manager_install_completed = [this](const DlcIdList& ids,
brillo::ErrorPtr* err) {
DCHECK(err);
bool ret = true;
for (const auto& id : ids) {
auto* dlc = GetDlc(id, err);
if (dlc == nullptr) {
LOG(WARNING) << "Trying to complete installation for unsupported DLC="
<< id;
ret = false;
} else if (!dlc->InstallCompleted(err)) {
PLOG(WARNING) << "Failed to complete install.";
ret = false;
}
}
// The returned error pertains to the last error happened. We probably don't
// need any accumulation of errors.
return ret;
};
return manager_install_completed(ids, err);
}
bool DlcService::UpdateCompleted(const DlcIdList& ids, ErrorPtr* err) {
auto manager_update_completed = [this](const DlcIdList& ids,
brillo::ErrorPtr* err) {
DCHECK(err);
bool ret = true;
for (const auto& id : ids) {
auto* dlc = GetDlc(id, err);
if (dlc == nullptr) {
LOG(WARNING) << "Trying to complete update for unsupported DLC=" << id;
ret = false;
} else if (!dlc->UpdateCompleted(err)) {
LOG(WARNING) << "Failed to complete update.";
ret = false;
}
}
// The returned error pertains to the last error happened. We probably don't
// need any accumulation of errors.
return ret;
};
return manager_update_completed(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();
auto manager_finish_install = [this](const DlcId& id, ErrorPtr* err) {
DCHECK(err);
auto dlc = GetDlc(id, err);
if (!dlc) {
*err = Error::Create(FROM_HERE, kErrorInvalidDlc,
"Finishing installation for invalid DLC.");
return false;
}
if (!dlc->IsInstalling()) {
*err = Error::Create(
FROM_HERE, kErrorInternal,
"Finishing installation for a DLC that is not being installed.");
return false;
}
return dlc->FinishInstall(/*installed_by_ue=*/true, err);
};
return manager_finish_install(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;
auto* dlc = GetDlc(id, &tmp_err);
if (!dlc || (dlc->IsInstalling() && !dlc->CancelInstall(err_in, &tmp_err)))
LOG(ERROR) << "Failed to cancel install for DLC="
<< (dlc ? dlc->GetSanitizedId() : 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;
constexpr base::TimeDelta kNotSeenStatusDelay =
base::Seconds(kPeriodicInstallCheckSecondsDelay);
auto* system_state = SystemState::Get();
if ((system_state->clock()->Now() -
system_state->installer_status_timestamp()) > kNotSeenStatusDelay) {
system_state->installer()->StatusSync();
}
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::BindOnce(&DlcService::PeriodicInstallCheck,
weak_ptr_factory_.GetWeakPtr()),
kUECheckTimeout);
}
bool DlcService::HandleStatus(brillo::ErrorPtr* err) {
// If we are not installing any DLC(s), no need to even handle status result.
if (!installing_dlc_id_) {
tolerance_count_ = 0;
return true;
}
const auto& status = SystemState::Get()->installer_status();
if (!status.is_install) {
if (++tolerance_count_ <= kToleranceCap) {
LOG(WARNING) << "Signal from installer indicates that it's not for an "
"install, but dlcservice was waiting for an install.";
return true;
}
tolerance_count_ = 0;
*err = Error::CreateInternal(
FROM_HERE, error::kFailedInstallInUpdateEngine,
"Signal from installer indicates that it's not for an install, but "
"dlcservice was waiting for an install.");
CancelInstall(*err);
SystemState::Get()->metrics()->SendInstallResultFailure(err);
return false;
}
// Reset the tolerance if a valid status is handled.
tolerance_count_ = 0;
switch (status.state) {
case InstallerInterface::Status::State::BLOCKED:
*err = Error::Create(FROM_HERE, kErrorNeedReboot,
"Installer is blocked, device needs a reboot.");
break;
case InstallerInterface::Status::State::OK:
LOG(INFO)
<< "Signal from installer, 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 InstallerInterface::Status::State::ERROR:
*err =
Error::CreateInternal(FROM_HERE, error::kFailedInstallInUpdateEngine,
"installer indicates failure.");
break;
// Only when installer's `DOWNLOADING` should the DLC send
// `DlcState::INSTALLING`. Majority of the install process for DLC(s) is
// during `DOWNLOADING`, this also means that only a single growth from
// 0.0 to 1.0 for progress reporting will happen.
case InstallerInterface::Status::State::DOWNLOADING: {
auto manager_change_progress = [this](double progress) {
for (auto& pr : supported_) {
auto& dlc = pr.second;
if (dlc->IsInstalling()) {
dlc->ChangeProgress(progress);
}
}
};
manager_change_progress(status.progress);
[[fallthrough]];
}
default:
return true;
}
CancelInstall(*err);
SystemState::Get()->metrics()->SendInstallResultFailure(err);
return false;
}
void DlcService::OnStatusSync(const InstallerInterface::Status& status) {
SystemState::Get()->set_installer_status(status);
ErrorPtr err;
if (!HandleStatus(&err))
DCHECK(err.get());
}
bool DlcService::Unload(const std::string& id, brillo::ErrorPtr* err) {
auto* dlc = GetDlc(id, err);
return dlc && dlc->Unload(err);
}
int DlcService::MountedDlcsAction(
const base::FilePath& mount_base,
std::optional<SelectDlc> select,
const std::function<bool(DlcInterface*)>& action) {
if (select && !select->user_tied() && !select->scaled()) {
LOG(WARNING) << "DLC selection is empty.";
return {};
}
int failed_count = 0;
const auto& mounted = ScanDirectory(base::FilePath(mount_base));
for (const auto& id : mounted) {
ErrorPtr tmp_err;
auto* dlc = GetDlc(id, &tmp_err);
if (!dlc || (select && !((select->user_tied() && dlc->IsUserTied()) ||
(select->scaled() && dlc->IsScaled())))) {
continue;
}
// Ensure DLC is actually mounted by checking the mount point path.
if (!base::PathExists(JoinPaths(mount_base, id, kPackage)))
continue;
if (!action(dlc))
++failed_count;
}
return failed_count;
}
bool DlcService::Unload(const SelectDlc& select,
const base::FilePath& mount_base,
brillo::ErrorPtr* err) {
DlcIdList sanitized_failed_ids;
int failed_count = MountedDlcsAction(
mount_base, select, [&sanitized_failed_ids](DlcInterface* dlc) -> bool {
ErrorPtr tmp_err;
if (!dlc->Unload(&tmp_err)) {
sanitized_failed_ids.push_back(dlc->GetSanitizedId());
return false;
}
return true;
});
if (failed_count > 0) {
*err =
Error::Create(FROM_HERE, kErrorInternal,
base::StringPrintf(
"Failed to unload (%d) DLCs: %s", failed_count,
base::JoinString(sanitized_failed_ids, ",").c_str()));
return false;
}
return true;
}
std::string DlcService::SanitizeId(DlcId id) {
brillo::ErrorPtr tmp_err;
auto* dlc = GetDlc(id, &tmp_err);
if (dlc)
return dlc->GetSanitizedId();
return id;
}
void DlcService::ReportTotalUsedBytesOnDisk() {
// Report total used bytes on disk.
uint64_t total_size = 0;
for (const auto& id : GetExistingDlcs()) {
brillo::ErrorPtr tmp_err;
if (const auto* dlc = GetDlc(id, &tmp_err)) {
auto used_bytes = dlc->GetUsedBytesOnDisk();
total_size += used_bytes ? *used_bytes : 0;
}
}
SystemState::Get()->metrics()->SendTotalUsedOnDisk(total_size);
}
void DlcService::CheckAndReportDailyMetrics() {
periodic_metrics_reporting_id_ = MessageLoop::kTaskIdNull;
const auto& now = SystemState::Get()->clock()->Now();
auto metrics_pref = Prefs(
JoinPaths(SystemState::Get()->prefs_dir(), metrics::kMetricsPrefsDir));
std::string last_time_str;
int64_t last_time_value;
if (metrics_pref.GetKey(metrics::kMetricsLastReportTimePref,
&last_time_str) &&
base::StringToInt64(last_time_str, &last_time_value)) {
const auto& last_reported_at =
base::Time::FromInternalValue(last_time_value);
auto time_reported_since = now - last_reported_at;
if (time_reported_since.InSeconds() < 0) {
LOG(WARNING) << "Unable to determine the time since last report. "
"Reporting regardless.";
} else if (time_reported_since.InSeconds() < 24 * 60 * 60) {
return;
}
}
LOG(INFO) << "Reporting daily metrics.";
metrics_pref.SetKey(metrics::kMetricsLastReportTimePref,
base::NumberToString(now.ToInternalValue()));
// Report metrics.
ReportTotalUsedBytesOnDisk();
// Schedule next reporting.
ScheduleReportDailyMetrics();
}
void DlcService::ScheduleReportDailyMetrics() {
if (periodic_metrics_reporting_id_ != MessageLoop::kTaskIdNull) {
LOG(INFO) << "Another periodic metrics reporting already scheduled.";
return;
}
periodic_metrics_reporting_id_ =
brillo::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&DlcService::CheckAndReportDailyMetrics,
weak_ptr_factory_.GetWeakPtr()),
kPeriodicMetricsReportingDelay);
}
} // namespace dlcservice