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