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