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