blob: e94a7b8dabcfa6308a9cc840571f4e3155ac362c [file] [log] [blame] [edit]
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "heartd/daemon/action_runner.h"
#include <unistd.h>
#include <vector>
#include <base/check.h>
#include <base/logging.h>
#include <base/time/time.h>
#include <brillo/process/process.h>
#include <power_manager/dbus-proxies.h>
#include "heartd/daemon/boot_record.h"
#include "heartd/mojom/heartd.mojom.h"
namespace heartd {
namespace {
namespace mojom = ::ash::heartd::mojom;
void SyncData() {
brillo::ProcessImpl sync_process;
sync_process.AddArg("/bin/sync");
// We don't call `Wait()` because it may block the following flow.
// Instead, we sleep 10 seconds for sync.
sync_process.Start();
sleep(10);
}
} // namespace
ActionRunner::ActionRunner(DbusConnector* dbus_connector)
: dbus_connector_(dbus_connector) {
CHECK(dbus_connector_) << "DbusConnector object is nullptr.";
}
ActionRunner::~ActionRunner() {
if (sysrq_fd_ != -1) {
close(sysrq_fd_);
}
}
void ActionRunner::SetupSysrq(int sysrq_fd) {
sysrq_fd_ = sysrq_fd;
}
mojom::HeartbeatResponse ActionRunner::DryRun(mojom::ServiceName name,
mojom::ActionType action) {
switch (action) {
case mojom::ActionType::kUnmappedEnumField:
return mojom::HeartbeatResponse::kNotAllowed;
case mojom::ActionType::kNoOperation:
return mojom::HeartbeatResponse::kSuccess;
case mojom::ActionType::kNormalReboot:
if (!allow_normal_reboot_) {
LOG(WARNING) << "Heartd is not allowed to normal reboot the device.";
return mojom::HeartbeatResponse::kNotAllowed;
}
if (IsNormalRebootTooManyTimes()) {
return mojom::HeartbeatResponse::kRateLimit;
}
break;
case mojom::ActionType::kForceReboot:
if (!allow_force_reboot_) {
LOG(WARNING) << "Heartd is not allowed to force reboot the device.";
return mojom::HeartbeatResponse::kNotAllowed;
}
if (IsForceRebootTooManyTimes()) {
return mojom::HeartbeatResponse::kRateLimit;
}
break;
case mojom::ActionType::kSyncData:
return mojom::HeartbeatResponse::kSuccess;
}
return mojom::HeartbeatResponse::kSuccess;
}
void ActionRunner::Run(mojom::ServiceName name, mojom::ActionType action) {
if (DryRun(name, action) == mojom::HeartbeatResponse::kSuccess) {
switch (action) {
case mojom::ActionType::kUnmappedEnumField:
case mojom::ActionType::kNoOperation:
break;
case mojom::ActionType::kNormalReboot:
LOG(WARNING) << "Heartd starts to reboot the device.";
// There is nothing to do for heartd when it's success or error. When
// failure, power manager should understand why it fails. We just need
// to check the log.
dbus_connector_->power_manager_proxy()->RequestRestartAsync(
/*in_reason = REQUEST_RESTART_HEARTD */ 6,
/*in_description = */ "heartd reset",
/*success_callback = */ base::DoNothing(),
/*error_callback = */ base::DoNothing());
break;
case mojom::ActionType::kForceReboot:
LOG(WARNING) << "Heartd starts to force reboot the device.";
if (sync_flag_) {
SyncData();
}
if (!write(sysrq_fd_, "c", 1)) {
LOG(ERROR) << "Heartd failed to force reboot the device";
}
break;
case mojom::ActionType::kSyncData:
if (sync_flag_) {
SyncData();
}
break;
}
}
}
void ActionRunner::EnableNormalRebootAction() {
allow_normal_reboot_ = true;
}
void ActionRunner::EnableForceRebootAction() {
allow_force_reboot_ = true;
}
void ActionRunner::DisableNormalRebootAction() {
allow_normal_reboot_ = false;
}
void ActionRunner::DisableForceRebootAction() {
allow_force_reboot_ = false;
}
void ActionRunner::CacheBootRecord(
const std::vector<BootRecord>& boot_records) {
boot_records_ = boot_records;
}
int ActionRunner::GetNormalRebootCount(const base::Time& time) {
int count = 0;
for (int i = boot_records_.size() - 1; i >= 0; --i) {
if (boot_records_[i].time < time) {
break;
}
if (boot_records_[i].id.starts_with("shutdown.")) {
++count;
}
}
return count;
}
int ActionRunner::GetForceRebootCount(const base::Time& time) {
int count = 0;
for (int i = boot_records_.size() - 2; i >= 0; --i) {
if (boot_records_[i + 1].time < time) {
break;
}
// Two consecutive boot IDs mean boot_records_[i+1] is a force reboot.
if (!boot_records_[i].id.starts_with("shutdown.") &&
!boot_records_[i + 1].id.starts_with("shutdown.")) {
++count;
}
}
return count;
}
bool ActionRunner::IsNormalRebootTooManyTimes() {
// Rules:
// 1. At most 3 reboots in 12 hours window.
// 2. At most 10 reboots in the 7 days window.
if (GetNormalRebootCount(base::Time().Now() - base::Hours(12)) >= 3) {
LOG(INFO) << "There are at least 3 normal reboot in 12 hours window";
return true;
}
if (GetNormalRebootCount(base::Time().Now() - base::Days(7)) >= 10) {
LOG(INFO) << "There are at least 10 normal reboot in 7 days window";
return true;
}
return false;
}
bool ActionRunner::IsForceRebootTooManyTimes() {
// Rules:
// 1. At most 3 reboots in 12 hours window.
// 2. At most 10 reboots in the 7 days window.
if (GetForceRebootCount(base::Time().Now() - base::Hours(12)) >= 3) {
LOG(INFO) << "There are at least 3 force reboot in 12 hours window";
return true;
}
if (GetForceRebootCount(base::Time().Now() - base::Days(7)) >= 10) {
LOG(INFO) << "There are at least 10 force reboot in 7 days window";
return true;
}
return false;
}
void ActionRunner::SetSyncFlag(bool sync_flag) {
sync_flag_ = sync_flag;
}
} // namespace heartd