1 /**
2  * Copyright © 2022 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 "event.hpp"
20 #include "group.hpp"
21 #include "json_config.hpp"
22 #include "power_state.hpp"
23 #include "profile.hpp"
24 #include "sdbusplus.hpp"
25 #include "utils/flight_recorder.hpp"
26 #include "zone.hpp"
27 
28 #include <nlohmann/json.hpp>
29 #include <phosphor-logging/log.hpp>
30 #include <sdbusplus/bus.hpp>
31 #include <sdbusplus/server/manager.hpp>
32 #include <sdeventplus/event.hpp>
33 #include <sdeventplus/source/event.hpp>
34 #include <sdeventplus/utility/timer.hpp>
35 
36 #include <chrono>
37 #include <format>
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 /**
62  * Package of data required when a timer expires
63  * Tuple constructed of:
64  *      std::string = Timer package unique identifier
65  *      std::vector<std::unique_ptr<ActionBase>> = List of pointers to actions
66  * that run when the timer expires
67  *      const std::vector<Group> = List of groups
68  *      bool = If groups should be preloaded before actions are run
69  */
70 using TimerPkg =
71     std::tuple<std::string, std::vector<std::unique_ptr<ActionBase>>&,
72                const std::vector<Group>&, bool>;
73 /**
74  * Data associated with a running timer that's used when it expires
75  * Pair constructed of:
76  *      TimerType = Type of timer to manage expired timer instances
77  *      TimerPkg = Package of data required when the timer expires
78  */
79 using TimerData = std::pair<TimerType, TimerPkg>;
80 /* Dbus event timer */
81 using Timer = sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>;
82 
83 /* Dbus signal object */
84 constexpr auto Path = 0;
85 constexpr auto Intf = 1;
86 constexpr auto Prop = 2;
87 using SignalObject = std::tuple<std::string, std::string, std::string>;
88 /* Dbus signal actions */
89 using TriggerActions =
90     std::vector<std::reference_wrapper<std::unique_ptr<ActionBase>>>;
91 /**
92  * Signal handler function that handles parsing a signal's message for a
93  * particular signal object and stores the results in the manager
94  */
95 using SignalHandler =
96     std::function<bool(sdbusplus::message_t&, const SignalObject&, Manager&)>;
97 /**
98  * Package of data required when a signal is received
99  * Tuple constructed of:
100  *     SignalHandler = Signal handler function
101  *     SignalObject = Dbus signal object
102  *     TriggerActions = List of actions that are run when the signal is received
103  */
104 using SignalPkg = std::tuple<SignalHandler, SignalObject, TriggerActions>;
105 /**
106  * Data associated to a subscribed signal
107  * Tuple constructed of:
108  *     std::unique_ptr<std::vector<SignalPkg>> =
109  *         Pointer to list of the signal's packages
110  *     std::unique_ptr<sdbusplus::bus::match_t> =
111  *         Pointer to match holding the subscription to a signal
112  */
113 using SignalData = std::tuple<std::unique_ptr<std::vector<SignalPkg>>,
114                               std::unique_ptr<sdbusplus::bus::match_t>>;
115 
116 /**
117  * Package of data from a D-Bus call to get managed objects
118  * Tuple constructed of:
119  *     std::map<Path,            // D-Bus Path
120  *       std::map<Intf,          // D-Bus Interface
121  *         std::map<Property,    // D-Bus Property
122  *         std::variant>>>       // Variant value of that property
123  */
124 using Path_v = sdbusplus::message::object_path;
125 using Intf_v = std::string;
126 using Prop_v = std::string;
127 using ManagedObjects =
128     std::map<Path_v, std::map<Intf_v, std::map<Prop_v, PropertyVariantType>>>;
129 
130 /**
131  * Actions to run when a parameter trigger runs.
132  */
133 using ParamTriggerData = std::vector<
134     std::reference_wrapper<const std::vector<std::unique_ptr<ActionBase>>>>;
135 
136 /**
137  * @class Manager - Represents the fan control manager's configuration
138  *
139  * A fan control manager configuration is optional, therefore the "manager.json"
140  * file is also optional. The manager configuration is used to populate
141  * fan control's manager parameters which are used in how the application
142  * operates, not in how the fans are controlled.
143  *
144  * When no manager configuration exists, the fan control application starts,
145  * processes any configured events and then begins controlling fans according
146  * to those events.
147  */
148 class Manager
149 {
150   public:
151     Manager() = delete;
152     Manager(const Manager&) = delete;
153     Manager(Manager&&) = delete;
154     Manager& operator=(const Manager&) = delete;
155     Manager& operator=(Manager&&) = delete;
156     ~Manager() = default;
157 
158     /**
159      * Constructor
160      * Parses and populates the fan control manager attributes from a json file
161      *
162      * @param[in] event - sdeventplus event loop
163      */
164     explicit Manager(const sdeventplus::Event& event);
165 
166     /**
167      * @brief Callback function to handle receiving a HUP signal to reload the
168      * JSON configurations.
169      */
170     void sighupHandler(sdeventplus::source::Signal&,
171                        const struct signalfd_siginfo*);
172 
173     /**
174      * @brief Callback function to handle receiving a USR1 signal to dump
175      * the flight recorder.
176      */
177     void dumpDebugData(sdeventplus::source::Signal&,
178                        const struct signalfd_siginfo*);
179 
180     /**
181      * @brief Get the active profiles of the system where an empty list
182      * represents that only configuration entries without a profile defined will
183      * be loaded.
184      *
185      * @return - The list of active profiles
186      */
187     static const std::vector<std::string>& getActiveProfiles();
188 
189     /**
190      * @brief Load the configuration of a given JSON class object based on the
191      * active profiles
192      *
193      * @param[in] isOptional - JSON configuration file is optional or not
194      * @param[in] args - Arguments to be forwarded to each instance of `T`
195      *   (*Note that a sdbusplus bus object is required as the first argument)
196      *
197      * @return Map of configuration entries
198      *     Map of configuration keys to their corresponding configuration object
199      */
200     template <typename T, typename... Args>
201     static std::map<configKey, std::unique_ptr<T>>
getConfig(bool isOptional,Args &&...args)202         getConfig(bool isOptional, Args&&... args)
203     {
204         std::map<configKey, std::unique_ptr<T>> config;
205 
206         auto confFile = fan::JsonConfig::getConfFile(
207             confAppName, T::confFileName, isOptional);
208 
209         if (!confFile.empty())
210         {
211             FlightRecorder::instance().log(
212                 "main", std::format("Loading configuration from {}",
213                                     confFile.string()));
214             for (const auto& entry : fan::JsonConfig::load(confFile))
215             {
216                 if (entry.contains("profiles"))
217                 {
218                     std::vector<std::string> profiles;
219                     for (const auto& profile : entry["profiles"])
220                     {
221                         profiles.emplace_back(
222                             profile.template get<std::string>());
223                     }
224                     // Do not create the object if its profiles are not in the
225                     // list of active profiles
226                     if (!profiles.empty() &&
227                         !std::any_of(profiles.begin(), profiles.end(),
228                                      [](const auto& name) {
229                                          return std::find(
230                                                     getActiveProfiles().begin(),
231                                                     getActiveProfiles().end(),
232                                                     name) !=
233                                                 getActiveProfiles().end();
234                                      }))
235                     {
236                         continue;
237                     }
238                 }
239                 auto obj =
240                     std::make_unique<T>(entry, std::forward<Args>(args)...);
241                 config.emplace(
242                     std::make_pair(obj->getName(), obj->getProfiles()),
243                     std::move(obj));
244             }
245             log<level::INFO>(
246                 std::format("Configuration({}) loaded successfully",
247                             T::confFileName)
248                     .c_str());
249             FlightRecorder::instance().log(
250                 "main", std::format("Configuration({}) loaded successfully",
251                                     T::confFileName));
252         }
253         return config;
254     }
255 
256     /**
257      * @brief Check if the given input configuration key matches with another
258      * configuration key that it's to be included in
259      *
260      * @param[in] input - Config key to be included in another config object
261      * @param[in] comp - Config key of the config object to compare with
262      *
263      * @return Whether the configuration object should be included
264      */
265     static bool inConfig(const configKey& input, const configKey& comp);
266 
267     /**
268      * @brief Check if the given path and inteface is owned by a dbus service
269      *
270      * @param[in] path - Dbus object path
271      * @param[in] intf - Dbus object interface
272      *
273      * @return - Whether the service has an owner for the given object path and
274      * interface
275      */
276     static bool hasOwner(const std::string& path, const std::string& intf);
277 
278     /**
279      * @brief Sets the dbus service owner state for all entries in the _servTree
280      * cache and removes associated objects from the _objects cache
281      *
282      * @param[in] serv - Dbus service name
283      * @param[in] hasOwner - Dbus service owner state
284      */
285     void setOwner(const std::string& serv, bool hasOwner);
286 
287     /**
288      * @brief Sets the dbus service owner state of a given object
289      *
290      * @param[in] path - Dbus object path
291      * @param[in] serv - Dbus service name
292      * @param[in] intf - Dbus object interface
293      * @param[in] isOwned - Dbus service owner state
294      */
295     void setOwner(const std::string& path, const std::string& serv,
296                   const std::string& intf, bool isOwned);
297 
298     /**
299      * @brief Add a set of services for a path and interface by retrieving all
300      * the path subtrees to the given depth from root for the interface
301      *
302      * @param[in] intf - Interface to add services for
303      * @param[in] depth - Depth of tree traversal from root path
304      *
305      * @throws - DBusMethodError
306      * Throws a DBusMethodError when the `getSubTree` method call fails
307      */
308     static void addServices(const std::string& intf, int32_t depth);
309 
310     /**
311      * @brief Get the service for a given path and interface from cached
312      * dataset and attempt to add all the services for the given path/interface
313      * when it's not found
314      *
315      * @param[in] path - Path to get service for
316      * @param[in] intf - Interface to get service for
317      *
318      * @return - The now cached service name
319      *
320      * @throws - DBusMethodError
321      * Ripples up a DBusMethodError exception from calling addServices
322      */
323     static const std::string& getService(const std::string& path,
324                                          const std::string& intf);
325 
326     /**
327      * @brief Get all the object paths for a given service and interface from
328      * the cached dataset and try to add all the services for the given
329      * interface when no paths are found and then attempt to get all the object
330      * paths again
331      *
332      * @param[in] serv - Service name to get paths for
333      * @param[in] intf - Interface to get paths for
334      *
335      * @return The cached object paths
336      */
337     std::vector<std::string> getPaths(const std::string& serv,
338                                       const std::string& intf);
339 
340     /**
341      * @brief Add objects to the cached dataset by first using
342      * `getManagedObjects` for the same service providing the given path and
343      * interface or just add the single object of the given path, interface, and
344      * property if that fails.
345      *
346      * @param[in] path - Dbus object's path
347      * @param[in] intf - Dbus object's interface
348      * @param[in] prop - Dbus object's property
349      *
350      * @throws - DBusMethodError
351      * Throws a DBusMethodError when the the service is failed to be found or
352      * when the `getManagedObjects` method call fails
353      */
addObjects(const std::string & path,const std::string & intf,const std::string & prop)354     void addObjects(const std::string& path, const std::string& intf,
355                     const std::string& prop)
356     {
357         addObjects(path, intf, prop, std::string{});
358     }
359 
360     /**
361      * @copydoc Manager::addObjects()
362      *
363      * If the service is known, then it can be used to add all objects
364      * in that service with the interface passed in to the cache instead of
365      * having to look it up.  This is done so objects can still be
366      * added even when the D-Bus path passed in doesn't exist so it
367      * can't be used to get a service name.
368      *
369      * @param[in] path - Dbus object's path
370      * @param[in] intf - Dbus object's interface
371      * @param[in] prop - Dbus object's property
372      * @param[in] serviceName - The service of the path/intf/prop if known
373      */
374     void addObjects(const std::string& path, const std::string& intf,
375                     const std::string& prop, const std::string& serviceName);
376 
377     /**
378      * @brief Get an object's property value
379      *
380      * @param[in] path - Dbus object's path
381      * @param[in] intf - Dbus object's interface
382      * @param[in] prop - Dbus object's property
383      */
384     const std::optional<PropertyVariantType>
385         getProperty(const std::string& path, const std::string& intf,
386                     const std::string& prop);
387 
388     /**
389      * @brief Set/update an object's property value
390      *
391      * @param[in] path - Dbus object's path
392      * @param[in] intf - Dbus object's interface
393      * @param[in] prop - Dbus object's property
394      * @param[in] value - Dbus object's property value
395      */
396     void setProperty(const std::string& path, const std::string& intf,
397                      const std::string& prop, PropertyVariantType value);
398 
399     /**
400      * @brief Remove an object's interface
401      *
402      * @param[in] path - Dbus object's path
403      * @param[in] intf - Dbus object's interface
404      */
removeInterface(const std::string & path,const std::string & intf)405     inline void removeInterface(const std::string& path,
406                                 const std::string& intf)
407     {
408         auto itPath = _objects.find(path);
409         if (itPath != std::end(_objects))
410         {
411             _objects[path].erase(intf);
412         }
413     }
414 
415     /**
416      * @brief Get the object's property value as a variant
417      *
418      * @param[in] path - Path of the object containing the property
419      * @param[in] intf - Interface name containing the property
420      * @param[in] prop - Name of property
421      *
422      * @return - The object's property value as a variant
423      */
getObjValueVariant(const std::string & path,const std::string & intf,const std::string & prop)424     static inline auto getObjValueVariant(const std::string& path,
425                                           const std::string& intf,
426                                           const std::string& prop)
427     {
428         return _objects.at(path).at(intf).at(prop);
429     };
430 
431     /**
432      * @brief Add a dbus timer
433      *
434      * @param[in] type - Type of timer
435      * @param[in] interval - Timer interval in microseconds
436      * @param[in] pkg - Packaged data for when timer expires
437      */
438     void addTimer(const TimerType type,
439                   const std::chrono::microseconds interval,
440                   std::unique_ptr<TimerPkg> pkg);
441 
442     /**
443      * @brief Callback when a timer expires
444      *
445      * @param[in] data - Data to be used when the timer expired
446      */
447     void timerExpired(TimerData& data);
448 
449     /**
450      * @brief Get the signal data for a given match string
451      *
452      * @param[in] sigMatch - Signal match string
453      *
454      * @return - Reference to the signal data for the given match string
455      */
getSignal(const std::string & sigMatch)456     std::vector<SignalData>& getSignal(const std::string& sigMatch)
457     {
458         return _signals[sigMatch];
459     }
460 
461     /**
462      * @brief Handle receiving signals
463      *
464      * @param[in] msg - Signal message containing the signal's data
465      * @param[in] pkgs - Signal packages associated to the signal being handled
466      */
467     void handleSignal(sdbusplus::message_t& msg,
468                       const std::vector<SignalPkg>* pkgs);
469 
470     /**
471      * @brief Get the sdbusplus bus object
472      */
getBus()473     inline auto& getBus()
474     {
475         return _bus;
476     }
477 
478     /**
479      * @brief Is the power state on
480      *
481      * @return Current power state of the system
482      */
isPowerOn() const483     inline bool isPowerOn() const
484     {
485         return _powerState->isPowerOn();
486     }
487 
488     /**
489      * @brief Load all the fan control JSON configuration files
490      *
491      * This is where all the fan control JSON configuration files are parsed and
492      * loaded into their associated objects. Anything that needs to be done when
493      * the Manager object is constructed or handling a SIGHUP to reload the
494      * configurations needs to be done here.
495      */
496     void load();
497 
498     /**
499      * @brief Sets a value in the parameter map.
500      *
501      * If it's a std::nullopt, it will be deleted instead.
502      *
503      * @param[in] name - The parameter name
504      * @param[in] value - The parameter value
505      */
setParameter(const std::string & name,const std::optional<PropertyVariantType> & value)506     static void setParameter(const std::string& name,
507                              const std::optional<PropertyVariantType>& value)
508     {
509         if (value)
510         {
511             auto it = _parameters.find(name);
512             auto changed = (it == _parameters.end()) ||
513                            ((it != _parameters.end()) && it->second != *value);
514             _parameters[name] = *value;
515 
516             if (changed)
517             {
518                 runParameterActions(name);
519             }
520         }
521         else
522         {
523             size_t deleted = _parameters.erase(name);
524 
525             if (deleted)
526             {
527                 runParameterActions(name);
528             }
529         }
530     }
531 
532     /**
533      * @brief Returns a value from the parameter map
534      *
535      * @param[in] name - The parameter name
536      *
537      * @return The parameter value, or std::nullopt if not found
538      */
539     static std::optional<PropertyVariantType>
getParameter(const std::string & name)540         getParameter(const std::string& name)
541     {
542         auto it = _parameters.find(name);
543         if (it != _parameters.end())
544         {
545             return it->second;
546         }
547 
548         return std::nullopt;
549     }
550 
551     /**
552      * @brief Runs the actions registered to a parameter
553      *        trigger with this name.
554      *
555      * @param[in] name - The parameter name
556      */
557     static void runParameterActions(const std::string& name);
558 
559     /**
560      * @brief Adds a parameter trigger
561      *
562      * @param[in] name - The parameter name
563      * @param[in] actions - The actions to run on the trigger
564      */
565     static void
566         addParameterTrigger(const std::string& name,
567                             std::vector<std::unique_ptr<ActionBase>>& actions);
568 
569     /* The name of the dump file */
570     static const std::string dumpFile;
571 
572   private:
573     /**
574      * @brief Helper to detect when a property's double contains a NaN
575      * (not-a-number) value.
576      *
577      * @param[in] value - The property to test
578      */
PropertyContainsNan(const PropertyVariantType & value)579     static bool PropertyContainsNan(const PropertyVariantType& value)
580     {
581         return (std::holds_alternative<double>(value) &&
582                 std::isnan(std::get<double>(value)));
583     }
584 
585     /**
586      * @brief Insert managed objects into cache, but filter out properties
587      * containing unwanted NaN (not-a-number) values and properties that
588      * are on D-Bus paths that aren't in an existing Group object.
589      *
590      * @param[in] ref - The map of ManagedObjects to insert into cache
591      */
592     void insertFilteredObjects(ManagedObjects& ref);
593 
594     /* The sdbusplus bus object to use */
595     sdbusplus::bus_t& _bus;
596 
597     /* The sdeventplus even loop to use */
598     sdeventplus::Event _event;
599 
600     /* The sdbusplus manager object to set the ObjectManager interface */
601     sdbusplus::server::manager_t _mgr;
602 
603     /* Whether loading the config files is allowed or not */
604     bool _loadAllowed;
605 
606     /* The system's power state determination object */
607     std::unique_ptr<PowerState> _powerState;
608 
609     /* List of profiles configured */
610     std::map<configKey, std::unique_ptr<Profile>> _profiles;
611 
612     /* List of active profiles */
613     static std::vector<std::string> _activeProfiles;
614 
615     /* Subtree map of paths to services of interfaces(with ownership state) */
616     static std::map<
617         std::string,
618         std::map<std::string, std::pair<bool, std::vector<std::string>>>>
619         _servTree;
620 
621     /* Object map of paths to interfaces of properties and their values */
622     static std::map<
623         std::string,
624         std::map<std::string, std::map<std::string, PropertyVariantType>>>
625         _objects;
626 
627     /* List of timers and their data to be processed when expired */
628     std::vector<std::pair<std::unique_ptr<TimerData>, Timer>> _timers;
629 
630     /* Map of signal match strings to a list of signal handler data */
631     std::unordered_map<std::string, std::vector<SignalData>> _signals;
632 
633     /* List of zones configured */
634     std::map<configKey, std::unique_ptr<Zone>> _zones;
635 
636     /* List of events configured */
637     std::map<configKey, std::unique_ptr<Event>> _events;
638 
639     /**
640      * @brief A map of parameter names and values that are something
641      *        other than just D-Bus property values that other actions
642      *        can set and use.
643      */
644     static std::unordered_map<std::string, PropertyVariantType> _parameters;
645 
646     /**
647      * @brief Map of parameter names to the actions to run when their
648      *        values change.
649      */
650     static std::unordered_map<std::string, TriggerActions> _parameterTriggers;
651 
652     /**
653      * @brief Callback for power state changes
654      *
655      * @param[in] powerStateOn - Whether the power state is on or not
656      *
657      * Callback function bound to the PowerState object instance to handle each
658      * time the power state changes.
659      */
660     void powerStateChanged(bool powerStateOn);
661 
662     /**
663      * @brief Find the service name for a given path and interface from the
664      * cached dataset
665      *
666      * @param[in] path - Path to get service for
667      * @param[in] intf - Interface to get service for
668      *
669      * @return - The cached service name
670      */
671     static const std::string& findService(const std::string& path,
672                                           const std::string& intf);
673 
674     /**
675      * @brief Find all the paths for a given service and interface from the
676      * cached dataset
677      *
678      * @param[in] serv - Service name to get paths for
679      * @param[in] intf - Interface to get paths for
680      *
681      * @return - The cached object paths
682      */
683     std::vector<std::string> findPaths(const std::string& serv,
684                                        const std::string& intf);
685 
686     /**
687      * @brief Parse and set the configured profiles from the profiles JSON file
688      *
689      * Retrieves the optional profiles JSON configuration file, parses it, and
690      * creates a list of configured profiles available to the other
691      * configuration files. These profiles can be used to remove or include
692      * entries within the other configuration files.
693      */
694     void setProfiles();
695 
696     /**
697      * @brief Dump the _objects, _servTree, and _parameters maps to JSON
698      *
699      * @param[out] data - The JSON that will be filled in
700      */
701     void dumpCache(json& data);
702 
703     /**
704      * @brief Add a list of groups to the cache dataset.
705      *
706      * @param[in] groups - The groups to add
707      */
708     void addGroups(const std::vector<Group>& groups);
709 };
710 
711 } // namespace phosphor::fan::control::json
712