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