1 #pragma once
2 
3 #include "config.h"
4 
5 #include "settings.hpp"
6 #include "xyz/openbmc_project/State/Host/server.hpp"
7 
8 #include <cereal/access.hpp>
9 #include <cereal/cereal.hpp>
10 #include <phosphor-logging/lg2.hpp>
11 #include <sdbusplus/bus.hpp>
12 #include <xyz/openbmc_project/Control/Boot/RebootAttempts/server.hpp>
13 #include <xyz/openbmc_project/State/Boot/Progress/server.hpp>
14 #include <xyz/openbmc_project/State/OperatingSystem/Status/server.hpp>
15 
16 #include <filesystem>
17 #include <string>
18 
19 namespace phosphor
20 {
21 namespace state
22 {
23 namespace manager
24 {
25 
26 using HostInherit = sdbusplus::server::object_t<
27     sdbusplus::xyz::openbmc_project::State::server::Host,
28     sdbusplus::xyz::openbmc_project::State::Boot::server::Progress,
29     sdbusplus::xyz::openbmc_project::Control::Boot::server::RebootAttempts,
30     sdbusplus::xyz::openbmc_project::State::OperatingSystem::server::Status>;
31 
32 PHOSPHOR_LOG2_USING;
33 
34 namespace sdbusRule = sdbusplus::bus::match::rules;
35 namespace fs = std::filesystem;
36 
37 /** @class Host
38  *  @brief OpenBMC host state management implementation.
39  *  @details A concrete implementation for xyz.openbmc_project.State.Host
40  *  DBus API.
41  */
42 class Host : public HostInherit
43 {
44   public:
45     /** @brief Constructs Host State Manager
46      *
47      * @note This constructor passes 'true' to the base class in order to
48      *       defer dbus object registration until we can run
49      *       determineInitialState() and set our properties
50      *
51      * @param[in] bus       - The Dbus bus object
52      * @param[in] objPath   - The Dbus object path
53      * @param[in] id        - The Host id
54      */
55     Host(sdbusplus::bus_t& bus, const char* objPath, size_t id) :
56         HostInherit(bus, objPath, HostInherit::action::defer_emit), bus(bus),
57         systemdSignalJobRemoved(
58             bus,
59             sdbusRule::type::signal() + sdbusRule::member("JobRemoved") +
60                 sdbusRule::path("/org/freedesktop/systemd1") +
61                 sdbusRule::interface("org.freedesktop.systemd1.Manager"),
62             [this](sdbusplus::message_t& m) { sysStateChangeJobRemoved(m); }),
63         systemdSignalJobNew(
64             bus,
65             sdbusRule::type::signal() + sdbusRule::member("JobNew") +
66                 sdbusRule::path("/org/freedesktop/systemd1") +
67                 sdbusRule::interface("org.freedesktop.systemd1.Manager"),
68             [this](sdbusplus::message_t& m) { sysStateChangeJobNew(m); }),
69         settings(bus, id), id(id)
70     {
71         // Enable systemd signals
72         subscribeToSystemdSignals();
73 
74         // create map of target name base on host id
75         createSystemdTargetMaps();
76 
77         // Will throw exception on fail
78         determineInitialState();
79 
80         // Sets auto-reboot attempts to max-allowed
81         attemptsLeft(sdbusplus::xyz::openbmc_project::Control::Boot::server::
82                          RebootAttempts::retryAttempts());
83 
84         // We deferred this until we could get our property correct
85         this->emit_object_added();
86     }
87 
88     /** @brief Set value of HostTransition */
89     Transition requestedHostTransition(Transition value) override;
90 
91     /** @brief Set Value for boot progress */
92     ProgressStages bootProgress(ProgressStages value) override;
93 
94     /** @brief Set Value for Operating System Status */
95     OSStatus operatingSystemState(OSStatus value) override;
96 
97     /** @brief Set value of CurrentHostState */
98     HostState currentHostState(HostState value) override;
99 
100     /**
101      * @brief Set value for allowable auto-reboot count
102      *
103      * This override is responsible for ensuring that when external users
104      * set the number of automatic retry attempts that the number of
105      * automatic reboot attempts left will update accordingly.
106      *
107      * @param[in] value - desired Reboot count value
108      *
109      * @return number of reboot attempts allowed.
110      */
111     uint32_t retryAttempts(uint32_t value) override
112     {
113         if (sdbusplus::xyz::openbmc_project::Control::Boot::server::
114                 RebootAttempts::attemptsLeft() != value)
115         {
116             info("Automatic reboot retry attempts set to: {VALUE} ", "VALUE",
117                  value);
118             sdbusplus::xyz::openbmc_project::Control::Boot::server::
119                 RebootAttempts::attemptsLeft(value);
120         }
121 
122         return (sdbusplus::xyz::openbmc_project::Control::Boot::server::
123                     RebootAttempts::retryAttempts(value));
124     }
125 
126     /**
127      * @brief Set host reboot count to default
128      *
129      * OpenBMC software controls the number of allowed reboot attempts so
130      * any external set request of this property will be overridden by
131      * this function and set to the number of the allowed auto-reboot
132      * retry attempts found on the system.
133      *
134      * The only code responsible for decrementing the boot count resides
135      * within this process and that will use the sub class interface
136      * directly
137      *
138      * @param[in] value  - Reboot count value
139      *
140      * @return number of reboot attempts left(allowed by retry attempts
141      * property)
142      */
143     uint32_t attemptsLeft(uint32_t value) override
144     {
145         debug("External request to reset reboot count");
146         auto retryAttempts = sdbusplus::xyz::openbmc_project::Control::Boot::
147             server::RebootAttempts::retryAttempts();
148         return (
149             sdbusplus::xyz::openbmc_project::Control::Boot::server::
150                 RebootAttempts::attemptsLeft(std::min(value, retryAttempts)));
151     }
152 
153   private:
154     /**
155      * @brief subscribe to the systemd signals
156      *
157      * This object needs to capture when it's systemd targets complete
158      * so it can keep it's state updated
159      *
160      **/
161     void subscribeToSystemdSignals();
162 
163     /**
164      * @brief Determine initial host state and set internally
165      *
166      * @return Will throw exceptions on failure
167      **/
168     void determineInitialState();
169 
170     /**
171      * create systemd target instance names and mapping table
172      **/
173     void createSystemdTargetMaps();
174 
175     /** @brief Execute the transition request
176      *
177      * This function assumes the state has been validated and the host
178      * is in an appropriate state for the transition to be started.
179      *
180      * @param[in] tranReq    - Transition requested
181      */
182     void executeTransition(Transition tranReq);
183 
184     /**
185      * @brief Determine if target is active
186      *
187      * This function determines if the target is active and
188      * helps prevent misleading log recorded states.
189      *
190      * @param[in] target - Target string to check on
191      *
192      * @return boolean corresponding to state active
193      **/
194     bool stateActive(const std::string& target);
195 
196     /**
197      * @brief Determine if auto reboot flag is set
198      *
199      * @return boolean corresponding to current auto_reboot setting
200      **/
201     bool isAutoReboot();
202 
203     /** @brief Check if systemd state change is relevant to this object
204      *
205      * Instance specific interface to handle the detected systemd state
206      * change
207      *
208      * @param[in]  msg       - Data associated with subscribed signal
209      *
210      */
211     void sysStateChangeJobRemoved(sdbusplus::message_t& msg);
212 
213     /** @brief Check if JobNew systemd signal is relevant to this object
214      *
215      * In certain instances phosphor-state-manager needs to monitor for the
216      * entry into a systemd target. This function will be used for these cases.
217      *
218      * Instance specific interface to handle the detected systemd state
219      * change
220      *
221      * @param[in]  msg       - Data associated with subscribed signal
222      *
223      */
224     void sysStateChangeJobNew(sdbusplus::message_t& msg);
225 
226     /** @brief Decrement reboot count
227      *
228      * This is used internally to this application to decrement the boot
229      * count on each boot attempt. The host will use the external
230      * attemptsLeft() interface to reset the count when a boot is successful
231      *
232      * @return number of reboot count attempts left
233      */
234     uint32_t decrementRebootCount();
235 
236     // Allow cereal class access to allow these next two function to be
237     // private
238     friend class cereal::access;
239 
240     /** @brief Function required by Cereal to perform serialization.
241      *
242      *  @tparam Archive - Cereal archive type (binary in our case).
243      *  @param[in] archive - reference to Cereal archive.
244      *  @param[in] version - Class version that enables handling
245      *                       a serialized data across code levels
246      */
247     template <class Archive>
248     void save(Archive& archive, const std::uint32_t version) const
249     {
250         // version is not used currently
251         (void)(version);
252         archive(sdbusplus::xyz::openbmc_project::Control::Boot::server::
253                     RebootAttempts::retryAttempts(),
254                 convertForMessage(sdbusplus::xyz::openbmc_project::State::
255                                       server::Host::requestedHostTransition()),
256                 convertForMessage(sdbusplus::xyz::openbmc_project::State::Boot::
257                                       server::Progress::bootProgress()),
258                 convertForMessage(
259                     sdbusplus::xyz::openbmc_project::State::OperatingSystem::
260                         server::Status::operatingSystemState()));
261     }
262 
263     /** @brief Function required by Cereal to perform deserialization.
264      *
265      *  @tparam Archive - Cereal archive type (binary in our case).
266      *  @param[in] archive - reference to Cereal archive.
267      *  @param[in] version - Class version that enables handling
268      *                       a serialized data across code levels
269      */
270     template <class Archive>
271     void load(Archive& archive, const std::uint32_t version)
272     {
273         std::string reqTranState;
274         std::string bootProgress;
275         std::string osState;
276         // Older cereal archive without RetryAttempt may be implemented
277         // just set to (BOOT_COUNT_MAX_ALLOWED)
278         uint32_t retryAttempts = BOOT_COUNT_MAX_ALLOWED;
279         switch (version)
280         {
281             case 2:
282                 archive(retryAttempts);
283                 [[fallthrough]];
284             case 1:
285                 archive(reqTranState, bootProgress, osState);
286                 break;
287         }
288         auto reqTran = Host::convertTransitionFromString(reqTranState);
289         // When restoring, set the requested state with persistent value
290         // but don't call the override which would execute it
291         sdbusplus::xyz::openbmc_project::State::server::Host::
292             requestedHostTransition(reqTran);
293         sdbusplus::xyz::openbmc_project::State::Boot::server::Progress::
294             bootProgress(Host::convertProgressStagesFromString(bootProgress));
295         sdbusplus::xyz::openbmc_project::State::OperatingSystem::server::
296             Status::operatingSystemState(
297                 Host::convertOSStatusFromString(osState));
298         sdbusplus::xyz::openbmc_project::Control::Boot::server::RebootAttempts::
299             retryAttempts(retryAttempts);
300     }
301 
302     /** @brief Serialize and persist requested host state
303      *
304      *  @return fs::path - pathname of persisted requested host state.
305      */
306     fs::path serialize();
307 
308     /** @brief Deserialze a persisted requested host state.
309      *
310      *  @return bool - true if the deserialization was successful, false
311      *                 otherwise.
312      */
313     bool deserialize();
314 
315     /**
316      * @brief Get target name of a HostState
317      *
318      * @param[in] state      -  The state of the host
319      *
320      * @return string - systemd target name of the state
321      */
322     const std::string& getTarget(HostState state);
323 
324     /**
325      * @brief Get target name of a TransitionRequest
326      *
327      * @param[in] tranReq      -  Transition requested
328      *
329      * @return string - systemd target name of Requested transition
330      */
331     const std::string& getTarget(Transition tranReq);
332 
333     /** @brief Persistent sdbusplus DBus bus connection. */
334     sdbusplus::bus_t& bus;
335 
336     /** @brief Used to subscribe to dbus systemd JobRemoved signal **/
337     sdbusplus::bus::match_t systemdSignalJobRemoved;
338 
339     /** @brief Used to subscribe to dbus systemd JobNew signal **/
340     sdbusplus::bus::match_t systemdSignalJobNew;
341 
342     // Settings host objects of interest
343     settings::HostObjects settings;
344 
345     /** @brief Host id. **/
346     const size_t id = 0;
347 
348     /** @brief HostState to systemd target mapping table. **/
349     std::map<HostState, std::string> stateTargetTable;
350 
351     /** @brief Requested Transition to systemd target mapping table. **/
352     std::map<Transition, std::string> transitionTargetTable;
353 
354     /** @brief Target called when a host crash occurs **/
355     std::string hostCrashTarget;
356 };
357 
358 } // namespace manager
359 } // namespace state
360 } // namespace phosphor
361