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