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