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 {server::Host::Transition::GracefulWarmReboot, HOST_STATE_WARM_REBOOT}, 70 {server::Host::Transition::ForceWarmReboot, HOST_STATE_FORCE_WARM_REBOOT}}; 71 72 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; 73 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1"; 74 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; 75 76 constexpr auto SYSTEMD_PROPERTY_IFACE = "org.freedesktop.DBus.Properties"; 77 constexpr auto SYSTEMD_INTERFACE_UNIT = "org.freedesktop.systemd1.Unit"; 78 79 void Host::subscribeToSystemdSignals() 80 { 81 auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 82 SYSTEMD_INTERFACE, "Subscribe"); 83 try 84 { 85 this->bus.call_noreply(method); 86 } 87 catch (const SdBusError& e) 88 { 89 log<level::ERR>("Failed to subscribe to systemd signals", 90 entry("ERR=%s", e.what())); 91 elog<InternalFailure>(); 92 } 93 return; 94 } 95 96 void Host::determineInitialState() 97 { 98 99 if (stateActive(HOST_STATE_POWERON_MIN_TGT)) 100 { 101 log<level::INFO>("Initial Host State will be Running", 102 entry("CURRENT_HOST_STATE=%s", 103 convertForMessage(HostState::Running).c_str())); 104 server::Host::currentHostState(HostState::Running); 105 server::Host::requestedHostTransition(Transition::On); 106 } 107 else 108 { 109 log<level::INFO>("Initial Host State will be Off", 110 entry("CURRENT_HOST_STATE=%s", 111 convertForMessage(HostState::Off).c_str())); 112 server::Host::currentHostState(HostState::Off); 113 server::Host::requestedHostTransition(Transition::Off); 114 } 115 116 if (!deserialize(HOST_STATE_PERSIST_PATH)) 117 { 118 // set to default value. 119 server::Host::requestedHostTransition(Transition::Off); 120 } 121 122 return; 123 } 124 125 void Host::executeTransition(Transition tranReq) 126 { 127 auto sysdUnit = SYSTEMD_TARGET_TABLE.find(tranReq)->second; 128 129 auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 130 SYSTEMD_INTERFACE, "StartUnit"); 131 132 method.append(sysdUnit); 133 method.append("replace"); 134 135 this->bus.call_noreply(method); 136 137 return; 138 } 139 140 bool Host::stateActive(const std::string& target) 141 { 142 std::variant<std::string> currentState; 143 sdbusplus::message::object_path unitTargetPath; 144 145 auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 146 SYSTEMD_INTERFACE, "GetUnit"); 147 148 method.append(target); 149 150 try 151 { 152 auto result = this->bus.call(method); 153 result.read(unitTargetPath); 154 } 155 catch (const SdBusError& e) 156 { 157 log<level::ERR>("Error in GetUnit call", entry("ERROR=%s", e.what())); 158 return false; 159 } 160 161 method = this->bus.new_method_call( 162 SYSTEMD_SERVICE, 163 static_cast<const std::string&>(unitTargetPath).c_str(), 164 SYSTEMD_PROPERTY_IFACE, "Get"); 165 166 method.append(SYSTEMD_INTERFACE_UNIT, "ActiveState"); 167 168 try 169 { 170 auto result = this->bus.call(method); 171 result.read(currentState); 172 } 173 catch (const SdBusError& e) 174 { 175 log<level::ERR>("Error in ActiveState Get", 176 entry("ERROR=%s", e.what())); 177 return false; 178 } 179 180 const auto& currentStateStr = std::get<std::string>(currentState); 181 return currentStateStr == ACTIVE_STATE || 182 currentStateStr == ACTIVATING_STATE; 183 } 184 185 bool Host::isAutoReboot() 186 { 187 using namespace settings; 188 189 auto method = bus.new_method_call( 190 settings.service(settings.autoReboot, autoRebootIntf).c_str(), 191 settings.autoReboot.c_str(), "org.freedesktop.DBus.Properties", "Get"); 192 method.append(autoRebootIntf, "AutoReboot"); 193 194 try 195 { 196 auto reply = bus.call(method); 197 198 std::variant<bool> result; 199 reply.read(result); 200 auto autoReboot = std::get<bool>(result); 201 auto rebootCounterParam = reboot::RebootAttempts::attemptsLeft(); 202 203 if (autoReboot) 204 { 205 if (rebootCounterParam > 0) 206 { 207 // Reduce BOOTCOUNT by 1 208 log<level::INFO>("Auto reboot enabled, rebooting"); 209 return true; 210 } 211 else if (rebootCounterParam == 0) 212 { 213 // Reset reboot counter and go to quiesce state 214 log<level::INFO>("Auto reboot enabled. " 215 "HOST BOOTCOUNT already set to 0."); 216 attemptsLeft(BOOT_COUNT_MAX_ALLOWED); 217 return false; 218 } 219 else 220 { 221 log<level::INFO>("Auto reboot enabled. " 222 "HOST BOOTCOUNT has an invalid value."); 223 return false; 224 } 225 } 226 else 227 { 228 log<level::INFO>("Auto reboot disabled."); 229 return false; 230 } 231 } 232 catch (const SdBusError& e) 233 { 234 log<level::ERR>("Error in AutoReboot Get", entry("ERROR=%s", e.what())); 235 return false; 236 } 237 } 238 239 void Host::sysStateChangeJobRemoved(sdbusplus::message::message& msg) 240 { 241 uint32_t newStateID{}; 242 sdbusplus::message::object_path newStateObjPath; 243 std::string newStateUnit{}; 244 std::string newStateResult{}; 245 246 // Read the msg and populate each variable 247 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult); 248 249 if ((newStateUnit == HOST_STATE_POWEROFF_TGT) && 250 (newStateResult == "done") && 251 (!stateActive(HOST_STATE_POWERON_MIN_TGT))) 252 { 253 log<level::INFO>("Received signal that host is off"); 254 this->currentHostState(server::Host::HostState::Off); 255 this->bootProgress(bootprogress::Progress::ProgressStages::Unspecified); 256 this->operatingSystemState(osstatus::Status::OSStatus::Inactive); 257 } 258 else if ((newStateUnit == HOST_STATE_POWERON_MIN_TGT) && 259 (newStateResult == "done") && 260 (stateActive(HOST_STATE_POWERON_MIN_TGT))) 261 { 262 log<level::INFO>("Received signal that host is running"); 263 this->currentHostState(server::Host::HostState::Running); 264 } 265 else if ((newStateUnit == HOST_STATE_QUIESCE_TGT) && 266 (newStateResult == "done") && 267 (stateActive(HOST_STATE_QUIESCE_TGT))) 268 { 269 if (Host::isAutoReboot()) 270 { 271 log<level::INFO>("Beginning reboot..."); 272 Host::requestedHostTransition(server::Host::Transition::Reboot); 273 } 274 else 275 { 276 log<level::INFO>("Maintaining quiesce"); 277 this->currentHostState(server::Host::HostState::Quiesced); 278 } 279 } 280 } 281 282 void Host::sysStateChangeJobNew(sdbusplus::message::message& msg) 283 { 284 uint32_t newStateID{}; 285 sdbusplus::message::object_path newStateObjPath; 286 std::string newStateUnit{}; 287 288 // Read the msg and populate each variable 289 msg.read(newStateID, newStateObjPath, newStateUnit); 290 291 if (newStateUnit == HOST_STATE_DIAGNOSTIC_MODE) 292 { 293 log<level::INFO>("Received signal that host is in diagnostice mode"); 294 this->currentHostState(server::Host::HostState::DiagnosticMode); 295 } 296 } 297 298 uint32_t Host::decrementRebootCount() 299 { 300 auto rebootCount = reboot::RebootAttempts::attemptsLeft(); 301 if (rebootCount > 0) 302 { 303 return (reboot::RebootAttempts::attemptsLeft(rebootCount - 1)); 304 } 305 return rebootCount; 306 } 307 308 fs::path Host::serialize(const fs::path& dir) 309 { 310 std::ofstream os(dir.c_str(), std::ios::binary); 311 cereal::JSONOutputArchive oarchive(os); 312 oarchive(*this); 313 return dir; 314 } 315 316 bool Host::deserialize(const fs::path& path) 317 { 318 try 319 { 320 if (fs::exists(path)) 321 { 322 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary); 323 cereal::JSONInputArchive iarchive(is); 324 iarchive(*this); 325 return true; 326 } 327 return false; 328 } 329 catch (cereal::Exception& e) 330 { 331 log<level::ERR>(e.what()); 332 fs::remove(path); 333 return false; 334 } 335 } 336 337 Host::Transition Host::requestedHostTransition(Transition value) 338 { 339 log<level::INFO>("Host State transaction request", 340 entry("REQUESTED_HOST_TRANSITION=%s", 341 convertForMessage(value).c_str())); 342 343 // If this is not a power off request then we need to 344 // decrement the reboot counter. This code should 345 // never prevent a power on, it should just decrement 346 // the count to 0. The quiesce handling is where the 347 // check of this count will occur 348 if (value != server::Host::Transition::Off) 349 { 350 decrementRebootCount(); 351 } 352 353 executeTransition(value); 354 355 auto retVal = server::Host::requestedHostTransition(value); 356 serialize(); 357 return retVal; 358 } 359 360 Host::ProgressStages Host::bootProgress(ProgressStages value) 361 { 362 auto retVal = bootprogress::Progress::bootProgress(value); 363 serialize(); 364 return retVal; 365 } 366 367 Host::OSStatus Host::operatingSystemState(OSStatus value) 368 { 369 auto retVal = osstatus::Status::operatingSystemState(value); 370 serialize(); 371 return retVal; 372 } 373 374 Host::HostState Host::currentHostState(HostState value) 375 { 376 log<level::INFO>( 377 "Change to Host State", 378 entry("CURRENT_HOST_STATE=%s", convertForMessage(value).c_str())); 379 return server::Host::currentHostState(value); 380 } 381 382 } // namespace manager 383 } // namespace state 384 } // namespace phosphor 385