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