| // 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 |