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