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