#include "config.h" #include "host_state_manager.hpp" #include "host_check.hpp" #include "utils.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Register class version with Cereal CEREAL_CLASS_VERSION(phosphor::state::manager::Host, CLASS_VERSION) namespace phosphor { namespace state { namespace manager { PHOSPHOR_LOG2_USING; // When you see server:: or reboot:: you know we're referencing our base class namespace server = sdbusplus::server::xyz::openbmc_project::state; namespace reboot = sdbusplus::server::xyz::openbmc_project::control::boot; namespace bootprogress = sdbusplus::server::xyz::openbmc_project::state::boot; namespace osstatus = sdbusplus::server::xyz::openbmc_project::state::operating_system; using namespace phosphor::logging; namespace fs = std::filesystem; using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; constexpr auto ACTIVE_STATE = "active"; constexpr auto ACTIVATING_STATE = "activating"; constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1"; constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; constexpr auto SYSTEMD_PROPERTY_IFACE = "org.freedesktop.DBus.Properties"; constexpr auto SYSTEMD_INTERFACE_UNIT = "org.freedesktop.systemd1.Unit"; void Host::determineInitialState() { if (stateActive(getTarget(server::Host::HostState::Running)) || isHostRunning(id)) { info("Initial Host State will be Running"); server::Host::currentHostState(HostState::Running, true); server::Host::requestedHostTransition(Transition::On, true); } else { info("Initial Host State will be Off"); server::Host::currentHostState(HostState::Off, true); server::Host::requestedHostTransition(Transition::Off, true); } if (!deserialize()) { // set to default value. server::Host::requestedHostTransition(Transition::Off, true); } return; } void Host::setupSupportedTransitions() { std::set supportedTransitions = { Transition::On, Transition::Off, Transition::Reboot, Transition::GracefulWarmReboot, #if ENABLE_FORCE_WARM_REBOOT Transition::ForceWarmReboot, #endif }; server::Host::allowedHostTransitions(supportedTransitions); } void Host::createSystemdTargetMaps() { stateTargetTable = { {HostState::Off, std::format("obmc-host-stop@{}.target", id)}, {HostState::Running, std::format("obmc-host-startmin@{}.target", id)}, {HostState::Quiesced, std::format("obmc-host-quiesce@{}.target", id)}, {HostState::DiagnosticMode, std::format("obmc-host-diagnostic-mode@{}.target", id)}}; transitionTargetTable = { {Transition::Off, std::format("obmc-host-shutdown@{}.target", id)}, {Transition::On, std::format("obmc-host-start@{}.target", id)}, {Transition::Reboot, std::format("obmc-host-reboot@{}.target", id)}, // Some systems do not support a warm reboot so just map the reboot // requests to our normal cold reboot in that case #if ENABLE_WARM_REBOOT {Transition::GracefulWarmReboot, std::format("obmc-host-warm-reboot@{}.target", id)}, {Transition::ForceWarmReboot, std::format("obmc-host-force-warm-reboot@{}.target", id)}}; #else {Transition::GracefulWarmReboot, std::format("obmc-host-reboot@{}.target", id)}, {Transition::ForceWarmReboot, std::format("obmc-host-reboot@{}.target", id)}}; #endif hostCrashTarget = std::format("obmc-host-crash@{}.target", id); } const std::string& Host::getTarget(HostState state) { return stateTargetTable[state]; }; const std::string& Host::getTarget(Transition tranReq) { return transitionTargetTable[tranReq]; }; void Host::executeTransition(Transition tranReq) { const auto& sysdUnit = getTarget(tranReq); auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "StartUnit"); method.append(sysdUnit); method.append("replace"); this->bus.call_noreply(method); return; } bool Host::stateActive(const std::string& target) { std::variant currentState; sdbusplus::message::object_path unitTargetPath; auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "GetUnit"); method.append(target); try { auto result = this->bus.call(method); result.read(unitTargetPath); } catch (const sdbusplus::exception_t& e) { error("Error in GetUnit call: {ERROR}", "ERROR", e); return false; } method = this->bus.new_method_call( SYSTEMD_SERVICE, static_cast(unitTargetPath).c_str(), SYSTEMD_PROPERTY_IFACE, "Get"); method.append(SYSTEMD_INTERFACE_UNIT, "ActiveState"); try { auto result = this->bus.call(method); result.read(currentState); } catch (const sdbusplus::exception_t& e) { error("Error in ActiveState Get: {ERROR}", "ERROR", e); return false; } const auto& currentStateStr = std::get(currentState); return currentStateStr == ACTIVE_STATE || currentStateStr == ACTIVATING_STATE; } bool Host::isAutoReboot() { using namespace settings; /* The logic here is to first check the one-time AutoReboot setting. * If this property is true (the default) then look at the persistent * user setting in the non one-time object, otherwise honor the one-time * setting and do not auto reboot. */ auto methodOneTime = bus.new_method_call( settings.service(settings.autoReboot, autoRebootIntf).c_str(), settings.autoRebootOneTime.c_str(), SYSTEMD_PROPERTY_IFACE, "Get"); methodOneTime.append(autoRebootIntf, "AutoReboot"); auto methodUserSetting = bus.new_method_call( settings.service(settings.autoReboot, autoRebootIntf).c_str(), settings.autoReboot.c_str(), SYSTEMD_PROPERTY_IFACE, "Get"); methodUserSetting.append(autoRebootIntf, "AutoReboot"); try { auto reply = bus.call(methodOneTime); std::variant result; reply.read(result); auto autoReboot = std::get(result); if (!autoReboot) { info("Auto reboot (one-time) disabled"); return false; } else { // one-time is true so read the user setting reply = bus.call(methodUserSetting); reply.read(result); autoReboot = std::get(result); } auto rebootCounterParam = reboot::RebootAttempts::attemptsLeft(); if (autoReboot) { if (rebootCounterParam > 0) { // Reduce BOOTCOUNT by 1 info( "Auto reboot enabled and boot count at {BOOTCOUNT}, rebooting", "BOOTCOUNT", rebootCounterParam); return true; } else { // We are at 0 so reset reboot counter and go to quiesce state info("Auto reboot enabled but HOST BOOTCOUNT already set to 0"); attemptsLeft(reboot::RebootAttempts::retryAttempts()); // Generate log since we will now be sitting in Quiesce const std::string errorMsg = "xyz.openbmc_project.State.Error.HostQuiesce"; utils::createError(this->bus, errorMsg, sdbusplus::xyz::openbmc_project::Logging:: server::Entry::Level::Critical); // Generate BMC dump to assist with debug utils::createBmcDump(this->bus); return false; } } else { info("Auto reboot disabled."); return false; } } catch (const sdbusplus::exception_t& e) { error("Error in AutoReboot Get, {ERROR}", "ERROR", e); return false; } } void Host::sysStateChangeJobRemoved(sdbusplus::message_t& msg) { uint32_t newStateID{}; sdbusplus::message::object_path newStateObjPath; std::string newStateUnit{}; std::string newStateResult{}; // Read the msg and populate each variable msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult); if ((newStateUnit == getTarget(server::Host::HostState::Off)) && (newStateResult == "done") && (!stateActive(getTarget(server::Host::HostState::Running)))) { info("Received signal that host is off"); this->currentHostState(server::Host::HostState::Off); this->bootProgress(bootprogress::Progress::ProgressStages::Unspecified); this->operatingSystemState(osstatus::Status::OSStatus::Inactive); } else if ((newStateUnit == getTarget(server::Host::HostState::Running)) && (newStateResult == "done") && (stateActive(getTarget(server::Host::HostState::Running)))) { info("Received signal that host is running"); this->currentHostState(server::Host::HostState::Running); // Remove temporary file which is utilized for scenarios where the // BMC is rebooted while the host is still up. // This file is used to indicate to host related systemd services // that the host is already running and they should skip running. // Once the host state is back to running we can clear this file. std::string hostFile = std::format(HOST_RUNNING_FILE, 0); if (std::filesystem::exists(hostFile)) { std::filesystem::remove(hostFile); } } else if ((newStateUnit == getTarget(server::Host::HostState::Quiesced)) && (newStateResult == "done") && (stateActive(getTarget(server::Host::HostState::Quiesced)))) { if (Host::isAutoReboot()) { info("Beginning reboot..."); Host::requestedHostTransition(server::Host::Transition::Reboot); } else { info("Maintaining quiesce"); this->currentHostState(server::Host::HostState::Quiesced); } } } void Host::sysStateChangeJobNew(sdbusplus::message_t& msg) { uint32_t newStateID{}; sdbusplus::message::object_path newStateObjPath; std::string newStateUnit{}; // Read the msg and populate each variable msg.read(newStateID, newStateObjPath, newStateUnit); if (newStateUnit == getTarget(server::Host::HostState::DiagnosticMode)) { info("Received signal that host is in diagnostice mode"); this->currentHostState(server::Host::HostState::DiagnosticMode); } else if ((newStateUnit == hostCrashTarget) && (server::Host::currentHostState() == server::Host::HostState::Running)) { // Only decrease the boot count if host was running when the host crash // target was started. Systemd will sometimes trigger multiple // JobNew events for the same target. This seems to be related to // how OpenBMC utilizes the targets in the reboot scenario info("Received signal that host has crashed, decrement reboot count"); // A host crash can cause a reboot of the host so decrement the reboot // count decrementRebootCount(); } } uint32_t Host::decrementRebootCount() { auto rebootCount = reboot::RebootAttempts::attemptsLeft(); if (rebootCount > 0) { return (reboot::RebootAttempts::attemptsLeft(rebootCount - 1)); } return rebootCount; } fs::path Host::serialize() { fs::path path{std::format(HOST_STATE_PERSIST_PATH, id)}; std::ofstream os(path.c_str(), std::ios::binary); cereal::JSONOutputArchive oarchive(os); oarchive(*this); return path; } bool Host::deserialize() { fs::path path{std::format(HOST_STATE_PERSIST_PATH, id)}; try { if (fs::exists(path)) { std::ifstream is(path.c_str(), std::ios::in | std::ios::binary); cereal::JSONInputArchive iarchive(is); iarchive(*this); return true; } return false; } catch (const cereal::Exception& e) { error("deserialize exception: {ERROR}", "ERROR", e); fs::remove(path); return false; } } Host::Transition Host::requestedHostTransition(Transition value) { info("Host state transition request of {REQ}", "REQ", value); #if ONLY_ALLOW_BOOT_WHEN_BMC_READY if ((value != Transition::Off) && (!utils::isBmcReady(this->bus))) { info("BMC State is not Ready so no host on operations allowed"); throw sdbusplus::xyz::openbmc_project::State::Host::Error:: BMCNotReady(); } #endif // If this is not a power off request then we need to // decrement the reboot counter. This code should // never prevent a power on, it should just decrement // the count to 0. The quiesce handling is where the // check of this count will occur if (value != server::Host::Transition::Off) { decrementRebootCount(); } executeTransition(value); auto retVal = server::Host::requestedHostTransition(value); serialize(); return retVal; } Host::ProgressStages Host::bootProgress(ProgressStages value) { auto retVal = bootprogress::Progress::bootProgress(value); serialize(); return retVal; } Host::OSStatus Host::operatingSystemState(OSStatus value) { auto retVal = osstatus::Status::operatingSystemState(value); serialize(); return retVal; } Host::HostState Host::currentHostState(HostState value) { info("Change to Host State: {STATE}", "STATE", value); return server::Host::currentHostState(value); } } // namespace manager } // namespace state } // namespace phosphor