1 #include "config.h" 2 3 #include "host_state_manager.hpp" 4 5 #include <fmt/format.h> 6 #include <stdio.h> 7 #include <systemd/sd-bus.h> 8 9 #include <cereal/archives/json.hpp> 10 #include <cereal/cereal.hpp> 11 #include <cereal/types/string.hpp> 12 #include <cereal/types/tuple.hpp> 13 #include <cereal/types/vector.hpp> 14 #include <phosphor-logging/elog-errors.hpp> 15 #include <phosphor-logging/log.hpp> 16 #include <sdbusplus/exception.hpp> 17 #include <sdbusplus/server.hpp> 18 #include <xyz/openbmc_project/Common/error.hpp> 19 #include <xyz/openbmc_project/Control/Power/RestorePolicy/server.hpp> 20 21 #include <filesystem> 22 #include <fstream> 23 #include <iostream> 24 #include <map> 25 #include <string> 26 27 // Register class version with Cereal 28 CEREAL_CLASS_VERSION(phosphor::state::manager::Host, CLASS_VERSION) 29 30 namespace phosphor 31 { 32 namespace state 33 { 34 namespace manager 35 { 36 37 // When you see server:: or reboot:: you know we're referencing our base class 38 namespace server = sdbusplus::xyz::openbmc_project::State::server; 39 namespace reboot = sdbusplus::xyz::openbmc_project::Control::Boot::server; 40 namespace bootprogress = sdbusplus::xyz::openbmc_project::State::Boot::server; 41 namespace osstatus = 42 sdbusplus::xyz::openbmc_project::State::OperatingSystem::server; 43 using namespace phosphor::logging; 44 namespace fs = std::experimental::filesystem; 45 using sdbusplus::exception::SdBusError; 46 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 47 48 // host-shutdown notifies host of shutdown and that leads to host-stop being 49 // called so initiate a host shutdown with the -shutdown target and consider the 50 // host shut down when the -stop target is complete 51 constexpr auto HOST_STATE_SOFT_POWEROFF_TGT = "obmc-host-shutdown@0.target"; 52 constexpr auto HOST_STATE_POWEROFF_TGT = "obmc-host-stop@0.target"; 53 constexpr auto HOST_STATE_POWERON_TGT = "obmc-host-start@0.target"; 54 constexpr auto HOST_STATE_POWERON_MIN_TGT = "obmc-host-startmin@0.target"; 55 constexpr auto HOST_STATE_REBOOT_TGT = "obmc-host-reboot@0.target"; 56 constexpr auto HOST_STATE_WARM_REBOOT = "obmc-host-warm-reboot@0.target"; 57 constexpr auto HOST_STATE_FORCE_WARM_REBOOT = 58 "obmc-host-force-warm-reboot@0.target"; 59 constexpr auto HOST_STATE_DIAGNOSTIC_MODE = 60 "obmc-host-diagnostic-mode@0.target"; 61 62 constexpr auto HOST_STATE_QUIESCE_TGT = "obmc-host-quiesce@0.target"; 63 64 constexpr auto ACTIVE_STATE = "active"; 65 constexpr auto ACTIVATING_STATE = "activating"; 66 67 /* Map a transition to it's systemd target */ 68 const std::map<server::Host::Transition, std::string> SYSTEMD_TARGET_TABLE = { 69 {server::Host::Transition::Off, HOST_STATE_SOFT_POWEROFF_TGT}, 70 {server::Host::Transition::On, HOST_STATE_POWERON_TGT}, 71 {server::Host::Transition::Reboot, HOST_STATE_REBOOT_TGT}, 72 // Some systems do not support a warm reboot so just map the reboot 73 // requests to our normal cold reboot in that case 74 #if ENABLE_WARM_REBOOT 75 {server::Host::Transition::GracefulWarmReboot, HOST_STATE_WARM_REBOOT}, 76 {server::Host::Transition::ForceWarmReboot, HOST_STATE_FORCE_WARM_REBOOT}}; 77 #else 78 {server::Host::Transition::GracefulWarmReboot, HOST_STATE_REBOOT_TGT}, 79 {server::Host::Transition::ForceWarmReboot, HOST_STATE_REBOOT_TGT}}; 80 #endif 81 82 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; 83 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1"; 84 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; 85 86 constexpr auto SYSTEMD_PROPERTY_IFACE = "org.freedesktop.DBus.Properties"; 87 constexpr auto SYSTEMD_INTERFACE_UNIT = "org.freedesktop.systemd1.Unit"; 88 89 void Host::subscribeToSystemdSignals() 90 { 91 auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 92 SYSTEMD_INTERFACE, "Subscribe"); 93 try 94 { 95 this->bus.call_noreply(method); 96 } 97 catch (const SdBusError& e) 98 { 99 log<level::ERR>("Failed to subscribe to systemd signals", 100 entry("ERR=%s", e.what())); 101 elog<InternalFailure>(); 102 } 103 return; 104 } 105 106 void Host::determineInitialState() 107 { 108 109 if (stateActive(HOST_STATE_POWERON_MIN_TGT)) 110 { 111 log<level::INFO>("Initial Host State will be Running", 112 entry("CURRENT_HOST_STATE=%s", 113 convertForMessage(HostState::Running).c_str())); 114 server::Host::currentHostState(HostState::Running); 115 server::Host::requestedHostTransition(Transition::On); 116 } 117 else 118 { 119 log<level::INFO>("Initial Host State will be Off", 120 entry("CURRENT_HOST_STATE=%s", 121 convertForMessage(HostState::Off).c_str())); 122 server::Host::currentHostState(HostState::Off); 123 server::Host::requestedHostTransition(Transition::Off); 124 } 125 126 if (!deserialize(HOST_STATE_PERSIST_PATH)) 127 { 128 // set to default value. 129 server::Host::requestedHostTransition(Transition::Off); 130 } 131 132 return; 133 } 134 135 void Host::executeTransition(Transition tranReq) 136 { 137 auto sysdUnit = SYSTEMD_TARGET_TABLE.find(tranReq)->second; 138 139 auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 140 SYSTEMD_INTERFACE, "StartUnit"); 141 142 method.append(sysdUnit); 143 method.append("replace"); 144 145 this->bus.call_noreply(method); 146 147 return; 148 } 149 150 bool Host::stateActive(const std::string& target) 151 { 152 std::variant<std::string> currentState; 153 sdbusplus::message::object_path unitTargetPath; 154 155 auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 156 SYSTEMD_INTERFACE, "GetUnit"); 157 158 method.append(target); 159 160 try 161 { 162 auto result = this->bus.call(method); 163 result.read(unitTargetPath); 164 } 165 catch (const SdBusError& e) 166 { 167 log<level::ERR>("Error in GetUnit call", entry("ERROR=%s", e.what())); 168 return false; 169 } 170 171 method = this->bus.new_method_call( 172 SYSTEMD_SERVICE, 173 static_cast<const std::string&>(unitTargetPath).c_str(), 174 SYSTEMD_PROPERTY_IFACE, "Get"); 175 176 method.append(SYSTEMD_INTERFACE_UNIT, "ActiveState"); 177 178 try 179 { 180 auto result = this->bus.call(method); 181 result.read(currentState); 182 } 183 catch (const SdBusError& e) 184 { 185 log<level::ERR>("Error in ActiveState Get", 186 entry("ERROR=%s", e.what())); 187 return false; 188 } 189 190 const auto& currentStateStr = std::get<std::string>(currentState); 191 return currentStateStr == ACTIVE_STATE || 192 currentStateStr == ACTIVATING_STATE; 193 } 194 195 bool Host::isAutoReboot() 196 { 197 using namespace settings; 198 199 /* The logic here is to first check the one-time AutoReboot setting. 200 * If this property is true (the default) then look at the persistent 201 * user setting in the non one-time object, otherwise honor the one-time 202 * setting and do not auto reboot. 203 */ 204 auto methodOneTime = bus.new_method_call( 205 settings.service(settings.autoReboot, autoRebootIntf).c_str(), 206 settings.autoRebootOneTime.c_str(), SYSTEMD_PROPERTY_IFACE, "Get"); 207 methodOneTime.append(autoRebootIntf, "AutoReboot"); 208 209 auto methodUserSetting = bus.new_method_call( 210 settings.service(settings.autoReboot, autoRebootIntf).c_str(), 211 settings.autoReboot.c_str(), SYSTEMD_PROPERTY_IFACE, "Get"); 212 methodUserSetting.append(autoRebootIntf, "AutoReboot"); 213 214 try 215 { 216 auto reply = bus.call(methodOneTime); 217 std::variant<bool> result; 218 reply.read(result); 219 auto autoReboot = std::get<bool>(result); 220 221 if (!autoReboot) 222 { 223 log<level::INFO>("Auto reboot (one-time) disabled"); 224 return false; 225 } 226 else 227 { 228 // one-time is true so read the user setting 229 reply = bus.call(methodUserSetting); 230 reply.read(result); 231 autoReboot = std::get<bool>(result); 232 } 233 234 auto rebootCounterParam = reboot::RebootAttempts::attemptsLeft(); 235 236 if (autoReboot) 237 { 238 if (rebootCounterParam > 0) 239 { 240 // Reduce BOOTCOUNT by 1 241 log<level::INFO>("Auto reboot enabled, rebooting"); 242 return true; 243 } 244 else if (rebootCounterParam == 0) 245 { 246 // Reset reboot counter and go to quiesce state 247 log<level::INFO>("Auto reboot enabled. " 248 "HOST BOOTCOUNT already set to 0."); 249 attemptsLeft(BOOT_COUNT_MAX_ALLOWED); 250 return false; 251 } 252 else 253 { 254 log<level::INFO>("Auto reboot enabled. " 255 "HOST BOOTCOUNT has an invalid value."); 256 return false; 257 } 258 } 259 else 260 { 261 log<level::INFO>("Auto reboot disabled."); 262 return false; 263 } 264 } 265 catch (const SdBusError& e) 266 { 267 log<level::ERR>("Error in AutoReboot Get", entry("ERROR=%s", e.what())); 268 return false; 269 } 270 } 271 272 void Host::sysStateChangeJobRemoved(sdbusplus::message::message& msg) 273 { 274 uint32_t newStateID{}; 275 sdbusplus::message::object_path newStateObjPath; 276 std::string newStateUnit{}; 277 std::string newStateResult{}; 278 279 // Read the msg and populate each variable 280 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult); 281 282 if ((newStateUnit == HOST_STATE_POWEROFF_TGT) && 283 (newStateResult == "done") && 284 (!stateActive(HOST_STATE_POWERON_MIN_TGT))) 285 { 286 log<level::INFO>("Received signal that host is off"); 287 this->currentHostState(server::Host::HostState::Off); 288 this->bootProgress(bootprogress::Progress::ProgressStages::Unspecified); 289 this->operatingSystemState(osstatus::Status::OSStatus::Inactive); 290 } 291 else if ((newStateUnit == HOST_STATE_POWERON_MIN_TGT) && 292 (newStateResult == "done") && 293 (stateActive(HOST_STATE_POWERON_MIN_TGT))) 294 { 295 log<level::INFO>("Received signal that host is running"); 296 this->currentHostState(server::Host::HostState::Running); 297 298 // Remove temporary file which is utilized for scenarios where the 299 // BMC is rebooted while the host is still up. 300 // This file is used to indicate to host related systemd services 301 // that the host is already running and they should skip running. 302 // Once the host state is back to running we can clear this file. 303 auto size = std::snprintf(nullptr, 0, HOST_RUNNING_FILE, 0); 304 size++; // null 305 std::unique_ptr<char[]> hostFile(new char[size]); 306 std::snprintf(hostFile.get(), size, HOST_RUNNING_FILE, 0); 307 if (std::filesystem::exists(hostFile.get())) 308 { 309 std::filesystem::remove(hostFile.get()); 310 } 311 } 312 else if ((newStateUnit == HOST_STATE_QUIESCE_TGT) && 313 (newStateResult == "done") && 314 (stateActive(HOST_STATE_QUIESCE_TGT))) 315 { 316 if (Host::isAutoReboot()) 317 { 318 log<level::INFO>("Beginning reboot..."); 319 Host::requestedHostTransition(server::Host::Transition::Reboot); 320 } 321 else 322 { 323 log<level::INFO>("Maintaining quiesce"); 324 this->currentHostState(server::Host::HostState::Quiesced); 325 } 326 } 327 } 328 329 void Host::sysStateChangeJobNew(sdbusplus::message::message& msg) 330 { 331 uint32_t newStateID{}; 332 sdbusplus::message::object_path newStateObjPath; 333 std::string newStateUnit{}; 334 335 // Read the msg and populate each variable 336 msg.read(newStateID, newStateObjPath, newStateUnit); 337 338 if (newStateUnit == HOST_STATE_DIAGNOSTIC_MODE) 339 { 340 log<level::INFO>("Received signal that host is in diagnostice mode"); 341 this->currentHostState(server::Host::HostState::DiagnosticMode); 342 } 343 } 344 345 uint32_t Host::decrementRebootCount() 346 { 347 auto rebootCount = reboot::RebootAttempts::attemptsLeft(); 348 if (rebootCount > 0) 349 { 350 return (reboot::RebootAttempts::attemptsLeft(rebootCount - 1)); 351 } 352 return rebootCount; 353 } 354 355 fs::path Host::serialize(const fs::path& dir) 356 { 357 std::ofstream os(dir.c_str(), std::ios::binary); 358 cereal::JSONOutputArchive oarchive(os); 359 oarchive(*this); 360 return dir; 361 } 362 363 bool Host::deserialize(const fs::path& path) 364 { 365 try 366 { 367 if (fs::exists(path)) 368 { 369 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary); 370 cereal::JSONInputArchive iarchive(is); 371 iarchive(*this); 372 return true; 373 } 374 return false; 375 } 376 catch (cereal::Exception& e) 377 { 378 log<level::ERR>(e.what()); 379 fs::remove(path); 380 return false; 381 } 382 } 383 384 Host::Transition Host::requestedHostTransition(Transition value) 385 { 386 log<level::INFO>(fmt::format("Host state transition request of {}", 387 convertForMessage(value)) 388 .c_str()); 389 // If this is not a power off request then we need to 390 // decrement the reboot counter. This code should 391 // never prevent a power on, it should just decrement 392 // the count to 0. The quiesce handling is where the 393 // check of this count will occur 394 if (value != server::Host::Transition::Off) 395 { 396 decrementRebootCount(); 397 } 398 399 executeTransition(value); 400 401 auto retVal = server::Host::requestedHostTransition(value); 402 serialize(); 403 return retVal; 404 } 405 406 Host::ProgressStages Host::bootProgress(ProgressStages value) 407 { 408 auto retVal = bootprogress::Progress::bootProgress(value); 409 serialize(); 410 return retVal; 411 } 412 413 Host::OSStatus Host::operatingSystemState(OSStatus value) 414 { 415 auto retVal = osstatus::Status::operatingSystemState(value); 416 serialize(); 417 return retVal; 418 } 419 420 Host::HostState Host::currentHostState(HostState value) 421 { 422 log<level::INFO>( 423 fmt::format("Change to Host State: {}", convertForMessage(value)) 424 .c_str()); 425 return server::Host::currentHostState(value); 426 } 427 428 } // namespace manager 429 } // namespace state 430 } // namespace phosphor 431