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