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