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