1 /**
2  * Copyright © 2020 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #pragma once
17 
18 #include "action.hpp"
19 #include "config_base.hpp"
20 #include "event.hpp"
21 #include "group.hpp"
22 #include "json_config.hpp"
23 #include "power_state.hpp"
24 #include "profile.hpp"
25 #include "sdbusplus.hpp"
26 #include "zone.hpp"
27 
28 #include <fmt/format.h>
29 
30 #include <nlohmann/json.hpp>
31 #include <phosphor-logging/log.hpp>
32 #include <sdbusplus/bus.hpp>
33 #include <sdbusplus/server/manager.hpp>
34 #include <sdeventplus/event.hpp>
35 #include <sdeventplus/utility/timer.hpp>
36 
37 #include <chrono>
38 #include <map>
39 #include <memory>
40 #include <optional>
41 #include <tuple>
42 #include <utility>
43 #include <vector>
44 
45 namespace phosphor::fan::control::json
46 {
47 
48 using json = nlohmann::json;
49 using namespace phosphor::logging;
50 
51 /* Application name to be appended to the path for loading a JSON config file */
52 constexpr auto confAppName = "control";
53 
54 /* Type of timers supported */
55 enum class TimerType
56 {
57     oneshot,
58     repeating,
59 };
60 /**
61  * Package of data required when a timer expires
62  * Tuple constructed of:
63  *      std::string = Timer package unique identifier
64  *      std::vector<std::unique_ptr<ActionBase>> = List of pointers to actions
65  * that run when the timer expires
66  */
67 using TimerPkg =
68     std::tuple<std::string, std::vector<std::unique_ptr<ActionBase>>&>;
69 /**
70  * Data associated with a running timer that's used when it expires
71  * Pair constructed of:
72  *      TimerType = Type of timer to manage expired timer instances
73  *      TimerPkg = Package of data required when the timer expires
74  */
75 using TimerData = std::pair<TimerType, TimerPkg>;
76 /* Dbus event timer */
77 using Timer = sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>;
78 
79 /* Dbus signal object */
80 constexpr auto Path = 0;
81 constexpr auto Intf = 1;
82 constexpr auto Prop = 2;
83 using SignalObject = std::tuple<std::string, std::string, std::string>;
84 /* Dbus signal actions */
85 using SignalActions =
86     std::vector<std::reference_wrapper<std::unique_ptr<ActionBase>>>;
87 /**
88  * Signal handler function that handles parsing a signal's message for a
89  * particular signal object and stores the results in the manager
90  */
91 using SignalHandler = std::function<bool(sdbusplus::message::message&,
92                                          const SignalObject&, Manager&)>;
93 /**
94  * Package of data required when a signal is received
95  * Tuple constructed of:
96  *     SignalHandler = Signal handler function
97  *     SignalObject = Dbus signal object
98  *     SignalActions = List of actions that are run when the signal is received
99  */
100 using SignalPkg = std::tuple<SignalHandler, SignalObject, SignalActions>;
101 /**
102  * Data associated to a subscribed signal
103  * Tuple constructed of:
104  *     std::vector<SignalPkg> = List of the signal's packages
105  *     std::unique_ptr<sdbusplus::server::match::match> =
106  *         Pointer to match holding the subscription to a signal
107  */
108 using SignalData = std::tuple<std::vector<SignalPkg>,
109                               std::unique_ptr<sdbusplus::server::match::match>>;
110 
111 /**
112  * @class Manager - Represents the fan control manager's configuration
113  *
114  * A fan control manager configuration is optional, therefore the "manager.json"
115  * file is also optional. The manager configuration is used to populate
116  * fan control's manager parameters which are used in how the application
117  * operates, not in how the fans are controlled.
118  *
119  * When no manager configuration exists, the fan control application starts,
120  * processes any configured events and then begins controlling fans according
121  * to those events.
122  */
123 class Manager
124 {
125   public:
126     Manager() = delete;
127     Manager(const Manager&) = delete;
128     Manager(Manager&&) = delete;
129     Manager& operator=(const Manager&) = delete;
130     Manager& operator=(Manager&&) = delete;
131     ~Manager() = default;
132 
133     /**
134      * Constructor
135      * Parses and populates the fan control manager attributes from a json file
136      *
137      * @param[in] event - sdeventplus event loop
138      */
139     Manager(const sdeventplus::Event& event);
140 
141     /**
142      * @brief Callback function to handle receiving a HUP signal to reload the
143      * JSON configurations.
144      */
145     void sighupHandler(sdeventplus::source::Signal&,
146                        const struct signalfd_siginfo*);
147 
148     /**
149      * @brief Get the active profiles of the system where an empty list
150      * represents that only configuration entries without a profile defined will
151      * be loaded.
152      *
153      * @return - The list of active profiles
154      */
155     static const std::vector<std::string>& getActiveProfiles();
156 
157     /**
158      * @brief Load the configuration of a given JSON class object based on the
159      * active profiles
160      *
161      * @param[in] isOptional - JSON configuration file is optional or not
162      * @param[in] args - Arguments to be forwarded to each instance of `T`
163      *   (*Note that a sdbusplus bus object is required as the first argument)
164      *
165      * @return Map of configuration entries
166      *     Map of configuration keys to their corresponding configuration object
167      */
168     template <typename T, typename... Args>
169     static std::map<configKey, std::unique_ptr<T>> getConfig(bool isOptional,
170                                                              Args&&... args)
171     {
172         std::map<configKey, std::unique_ptr<T>> config;
173 
174         auto confFile =
175             fan::JsonConfig::getConfFile(util::SDBusPlus::getBus(), confAppName,
176                                          T::confFileName, isOptional);
177         if (!confFile.empty())
178         {
179             for (const auto& entry : fan::JsonConfig::load(confFile))
180             {
181                 if (entry.contains("profiles"))
182                 {
183                     std::vector<std::string> profiles;
184                     for (const auto& profile : entry["profiles"])
185                     {
186                         profiles.emplace_back(
187                             profile.template get<std::string>());
188                     }
189                     // Do not create the object if its profiles are not in the
190                     // list of active profiles
191                     if (!profiles.empty() &&
192                         !std::any_of(profiles.begin(), profiles.end(),
193                                      [](const auto& name) {
194                                          return std::find(
195                                                     getActiveProfiles().begin(),
196                                                     getActiveProfiles().end(),
197                                                     name) !=
198                                                 getActiveProfiles().end();
199                                      }))
200                     {
201                         continue;
202                     }
203                 }
204                 auto obj =
205                     std::make_unique<T>(entry, std::forward<Args>(args)...);
206                 config.emplace(
207                     std::make_pair(obj->getName(), obj->getProfiles()),
208                     std::move(obj));
209             }
210             log<level::INFO>(
211                 fmt::format("Configuration({}) loaded successfully",
212                             T::confFileName)
213                     .c_str());
214         }
215         return config;
216     }
217 
218     /**
219      * @brief Check if the given input configuration key matches with another
220      * configuration key that it's to be included in
221      *
222      * @param[in] input - Config key to be included in another config object
223      * @param[in] comp - Config key of the config object to compare with
224      *
225      * @return Whether the configuration object should be included
226      */
227     static bool inConfig(const configKey& input, const configKey& comp);
228 
229     /**
230      * @brief Check if the given path and inteface is owned by a dbus service
231      *
232      * @param[in] path - Dbus object path
233      * @param[in] intf - Dbus object interface
234      *
235      * @return - Whether the service has an owner for the given object path and
236      * interface
237      */
238     static bool hasOwner(const std::string& path, const std::string& intf);
239 
240     /**
241      * @brief Sets the dbus service owner state of a given object
242      *
243      * @param[in] path - Dbus object path
244      * @param[in] serv - Dbus service name
245      * @param[in] intf - Dbus object interface
246      * @param[in] isOwned - Dbus service owner state
247      */
248     void setOwner(const std::string& path, const std::string& serv,
249                   const std::string& intf, bool isOwned);
250 
251     /**
252      * @brief Add a set of services for a path and interface by retrieving all
253      * the path subtrees to the given depth from root for the interface
254      *
255      * @param[in] intf - Interface to add services for
256      * @param[in] depth - Depth of tree traversal from root path
257      *
258      * @throws - DBusMethodError
259      * Throws a DBusMethodError when the `getSubTree` method call fails
260      */
261     static void addServices(const std::string& intf, int32_t depth);
262 
263     /**
264      * @brief Get the service for a given path and interface from cached
265      * dataset and attempt to add all the services for the given path/interface
266      * when it's not found
267      *
268      * @param[in] path - Path to get service for
269      * @param[in] intf - Interface to get service for
270      *
271      * @return - The now cached service name
272      *
273      * @throws - DBusMethodError
274      * Ripples up a DBusMethodError exception from calling addServices
275      */
276     static const std::string& getService(const std::string& path,
277                                          const std::string& intf);
278 
279     /**
280      * @brief Get all the object paths for a given service and interface from
281      * the cached dataset and try to add all the services for the given
282      * interface when no paths are found and then attempt to get all the object
283      * paths again
284      *
285      * @param[in] serv - Service name to get paths for
286      * @param[in] intf - Interface to get paths for
287      *
288      * @return The cached object paths
289      */
290     std::vector<std::string> getPaths(const std::string& serv,
291                                       const std::string& intf);
292 
293     /**
294      * @brief Add objects to the cached dataset by first using
295      * `getManagedObjects` for the same service providing the given path and
296      * interface or just add the single object of the given path, interface, and
297      * property if that fails.
298      *
299      * @param[in] path - Dbus object's path
300      * @param[in] intf - Dbus object's interface
301      * @param[in] prop - Dbus object's property
302      *
303      * @throws - DBusMethodError
304      * Throws a DBusMethodError when the the service is failed to be found or
305      * when the `getManagedObjects` method call fails
306      */
307     void addObjects(const std::string& path, const std::string& intf,
308                     const std::string& prop);
309 
310     /**
311      * @brief Get an object's property value
312      *
313      * @param[in] path - Dbus object's path
314      * @param[in] intf - Dbus object's interface
315      * @param[in] prop - Dbus object's property
316      */
317     const std::optional<PropertyVariantType>
318         getProperty(const std::string& path, const std::string& intf,
319                     const std::string& prop);
320 
321     /**
322      * @brief Set/update an object's property value
323      *
324      * @param[in] path - Dbus object's path
325      * @param[in] intf - Dbus object's interface
326      * @param[in] prop - Dbus object's property
327      * @param[in] value - Dbus object's property value
328      */
329     void setProperty(const std::string& path, const std::string& intf,
330                      const std::string& prop, PropertyVariantType value)
331     {
332         _objects[path][intf][prop] = std::move(value);
333     }
334 
335     /**
336      * @brief Remove an object's interface
337      *
338      * @param[in] path - Dbus object's path
339      * @param[in] intf - Dbus object's interface
340      */
341     inline void removeInterface(const std::string& path,
342                                 const std::string& intf)
343     {
344         auto itPath = _objects.find(path);
345         if (itPath != std::end(_objects))
346         {
347             _objects[path].erase(intf);
348         }
349     }
350 
351     /**
352      * @brief Get the object's property value as a variant
353      *
354      * @param[in] path - Path of the object containing the property
355      * @param[in] intf - Interface name containing the property
356      * @param[in] prop - Name of property
357      *
358      * @return - The object's property value as a variant
359      */
360     static inline auto getObjValueVariant(const std::string& path,
361                                           const std::string& intf,
362                                           const std::string& prop)
363     {
364         return _objects.at(path).at(intf).at(prop);
365     };
366 
367     /**
368      * @brief Add a dbus timer
369      *
370      * @param[in] type - Type of timer
371      * @param[in] interval - Timer interval in microseconds
372      * @param[in] pkg - Packaged data for when timer expires
373      */
374     void addTimer(const TimerType type,
375                   const std::chrono::microseconds interval,
376                   std::unique_ptr<TimerPkg> pkg);
377 
378     /**
379      * @brief Callback when a timer expires
380      *
381      * @param[in] data - Data to be used when the timer expired
382      */
383     void timerExpired(TimerData& data);
384 
385     /**
386      * @brief Get the signal data for a given match string
387      *
388      * @param[in] sigMatch - Signal match string
389      *
390      * @return - Reference to the signal data for the given match string
391      */
392     std::vector<SignalData>& getSignal(const std::string& sigMatch)
393     {
394         return _signals[sigMatch];
395     }
396 
397     /**
398      * @brief Handle receiving signals
399      *
400      * @param[in] msg - Signal message containing the signal's data
401      * @param[in] pkgs - Signal packages associated to the signal being handled
402      */
403     void handleSignal(sdbusplus::message::message& msg,
404                       const std::vector<SignalPkg>& pkgs);
405 
406     /**
407      * @brief Get the sdbusplus bus object
408      */
409     inline auto& getBus()
410     {
411         return _bus;
412     }
413 
414     /**
415      * @brief Is the power state on
416      *
417      * @return Current power state of the system
418      */
419     inline bool isPowerOn() const
420     {
421         return _powerState->isPowerOn();
422     }
423 
424     /**
425      * @brief Load all the fan control JSON configuration files
426      *
427      * This is where all the fan control JSON configuration files are parsed and
428      * loaded into their associated objects. Anything that needs to be done when
429      * the Manager object is constructed or handling a SIGHUP to reload the
430      * configurations needs to be done here.
431      */
432     void load();
433 
434   private:
435     /* The sdbusplus bus object to use */
436     sdbusplus::bus::bus& _bus;
437 
438     /* The sdeventplus even loop to use */
439     sdeventplus::Event _event;
440 
441     /* The sdbusplus manager object to set the ObjectManager interface */
442     sdbusplus::server::manager::manager _mgr;
443 
444     /* Whether loading the config files is allowed or not */
445     bool _loadAllowed;
446 
447     /* The system's power state determination object */
448     std::unique_ptr<PowerState> _powerState;
449 
450     /* List of profiles configured */
451     std::map<configKey, std::unique_ptr<Profile>> _profiles;
452 
453     /* List of active profiles */
454     static std::vector<std::string> _activeProfiles;
455 
456     /* Subtree map of paths to services of interfaces(with ownership state) */
457     static std::map<
458         std::string,
459         std::map<std::string, std::pair<bool, std::vector<std::string>>>>
460         _servTree;
461 
462     /* Object map of paths to interfaces of properties and their values */
463     static std::map<
464         std::string,
465         std::map<std::string, std::map<std::string, PropertyVariantType>>>
466         _objects;
467 
468     /* List of timers and their data to be processed when expired */
469     std::vector<std::pair<std::unique_ptr<TimerData>, Timer>> _timers;
470 
471     /* Map of signal match strings to a list of signal handler data */
472     std::unordered_map<std::string, std::vector<SignalData>> _signals;
473 
474     /* List of zones configured */
475     std::map<configKey, std::unique_ptr<Zone>> _zones;
476 
477     /* List of events configured */
478     std::map<configKey, std::unique_ptr<Event>> _events;
479 
480     /**
481      * @brief Callback for power state changes
482      *
483      * @param[in] powerStateOn - Whether the power state is on or not
484      *
485      * Callback function bound to the PowerState object instance to handle each
486      * time the power state changes.
487      */
488     void powerStateChanged(bool powerStateOn);
489 
490     /**
491      * @brief Find the service name for a given path and interface from the
492      * cached dataset
493      *
494      * @param[in] path - Path to get service for
495      * @param[in] intf - Interface to get service for
496      *
497      * @return - The cached service name
498      */
499     static const std::string& findService(const std::string& path,
500                                           const std::string& intf);
501 
502     /**
503      * @brief Find all the paths for a given service and interface from the
504      * cached dataset
505      *
506      * @param[in] serv - Service name to get paths for
507      * @param[in] intf - Interface to get paths for
508      *
509      * @return - The cached object paths
510      */
511     std::vector<std::string> findPaths(const std::string& serv,
512                                        const std::string& intf);
513 
514     /**
515      * @brief Parse and set the configured profiles from the profiles JSON file
516      *
517      * Retrieves the optional profiles JSON configuration file, parses it, and
518      * creates a list of configured profiles available to the other
519      * configuration files. These profiles can be used to remove or include
520      * entries within the other configuration files.
521      */
522     void setProfiles();
523 };
524 
525 } // namespace phosphor::fan::control::json
526