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