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