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