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 #include "config.h"
17 
18 #include "manager.hpp"
19 
20 #include "action.hpp"
21 #include "dbus_paths.hpp"
22 #include "event.hpp"
23 #include "fan.hpp"
24 #include "group.hpp"
25 #include "json_config.hpp"
26 #include "power_state.hpp"
27 #include "profile.hpp"
28 #include "sdbusplus.hpp"
29 #include "utils/flight_recorder.hpp"
30 #include "zone.hpp"
31 
32 #include <systemd/sd-bus.h>
33 
34 #include <nlohmann/json.hpp>
35 #include <sdbusplus/bus.hpp>
36 #include <sdbusplus/server/manager.hpp>
37 #include <sdeventplus/event.hpp>
38 #include <sdeventplus/utility/timer.hpp>
39 
40 #include <algorithm>
41 #include <chrono>
42 #include <filesystem>
43 #include <functional>
44 #include <map>
45 #include <memory>
46 #include <tuple>
47 #include <utility>
48 #include <vector>
49 
50 namespace phosphor::fan::control::json
51 {
52 
53 using json = nlohmann::json;
54 
55 std::vector<std::string> Manager::_activeProfiles;
56 std::map<std::string,
57          std::map<std::string, std::pair<bool, std::vector<std::string>>>>
58     Manager::_servTree;
59 std::map<std::string,
60          std::map<std::string, std::map<std::string, PropertyVariantType>>>
61     Manager::_objects;
62 std::unordered_map<std::string, PropertyVariantType> Manager::_parameters;
63 std::unordered_map<std::string, TriggerActions> Manager::_parameterTriggers;
64 
65 const std::string Manager::dumpFile = "/tmp/fan_control_dump.json";
66 
67 Manager::Manager(const sdeventplus::Event& event) :
68     _bus(util::SDBusPlus::getBus()), _event(event),
69     _mgr(util::SDBusPlus::getBus(), CONTROL_OBJPATH), _loadAllowed(true),
70     _powerState(std::make_unique<PGoodState>(
71         util::SDBusPlus::getBus(),
72         std::bind(std::mem_fn(&Manager::powerStateChanged), this,
73                   std::placeholders::_1)))
74 {}
75 
76 void Manager::sighupHandler(sdeventplus::source::Signal&,
77                             const struct signalfd_siginfo*)
78 {
79     FlightRecorder::instance().log("main", "SIGHUP received");
80     // Save current set of available and active profiles
81     std::map<configKey, std::unique_ptr<Profile>> profiles;
82     profiles.swap(_profiles);
83     std::vector<std::string> activeProfiles;
84     activeProfiles.swap(_activeProfiles);
85 
86     try
87     {
88         _loadAllowed = true;
89         load();
90     }
91     catch (const std::runtime_error& re)
92     {
93         // Restore saved available and active profiles
94         _loadAllowed = false;
95         _profiles.swap(profiles);
96         _activeProfiles.swap(activeProfiles);
97         log<level::ERR>("Error reloading configs, no changes made",
98                         entry("LOAD_ERROR=%s", re.what()));
99         FlightRecorder::instance().log(
100             "main", fmt::format("Error reloading configs, no changes made: {}",
101                                 re.what()));
102     }
103 }
104 
105 void Manager::sigUsr1Handler(sdeventplus::source::Signal&,
106                              const struct signalfd_siginfo*)
107 {
108     debugDumpEventSource = std::make_unique<sdeventplus::source::Defer>(
109         _event, std::bind(std::mem_fn(&Manager::dumpDebugData), this,
110                           std::placeholders::_1));
111 }
112 
113 void Manager::dumpDebugData(sdeventplus::source::EventBase& /*source*/)
114 {
115     json data;
116     FlightRecorder::instance().dump(data);
117     dumpCache(data);
118 
119     std::for_each(_zones.begin(), _zones.end(), [&data](const auto& zone) {
120         data["zones"][zone.second->getName()] = zone.second->dump();
121     });
122 
123     std::ofstream file{Manager::dumpFile};
124     if (!file)
125     {
126         log<level::ERR>("Could not open file for fan dump");
127         return;
128     }
129 
130     file << std::setw(4) << data;
131 
132     debugDumpEventSource.reset();
133 }
134 
135 void Manager::dumpCache(json& data)
136 {
137     auto& objects = data["objects"];
138     for (const auto& [path, interfaces] : _objects)
139     {
140         auto& interfaceJSON = objects[path];
141 
142         for (const auto& [interface, properties] : interfaces)
143         {
144             auto& propertyJSON = interfaceJSON[interface];
145             for (const auto& [propName, propValue] : properties)
146             {
147                 std::visit(
148                     [&obj = propertyJSON[propName]](auto&& val) { obj = val; },
149                     propValue);
150             }
151         }
152     }
153 
154     auto& parameters = data["parameters"];
155     for (const auto& [name, value] : _parameters)
156     {
157         std::visit([&obj = parameters[name]](auto&& val) { obj = val; }, value);
158     }
159 
160     std::for_each(_events.begin(), _events.end(), [&data](const auto& event) {
161         data["events"][event.second->getName()] = event.second->dump();
162     });
163 
164     data["services"] = _servTree;
165 }
166 
167 void Manager::load()
168 {
169     if (_loadAllowed)
170     {
171         // Load the available profiles and which are active
172         setProfiles();
173 
174         // Load the zone configurations
175         auto zones = getConfig<Zone>(false, _event, this);
176         // Load the fan configurations and move each fan into its zone
177         auto fans = getConfig<Fan>(false);
178         for (auto& fan : fans)
179         {
180             configKey fanProfile =
181                 std::make_pair(fan.second->getZone(), fan.first.second);
182             auto itZone = std::find_if(
183                 zones.begin(), zones.end(), [&fanProfile](const auto& zone) {
184                     return Manager::inConfig(fanProfile, zone.first);
185                 });
186             if (itZone != zones.end())
187             {
188                 if (itZone->second->getTarget() != fan.second->getTarget() &&
189                     fan.second->getTarget() != 0)
190                 {
191                     // Update zone target to current target of the fan in the
192                     // zone
193                     itZone->second->setTarget(fan.second->getTarget());
194                 }
195                 itZone->second->addFan(std::move(fan.second));
196             }
197         }
198 
199         // Save all currently available groups, if any, then clear for reloading
200         auto groups = std::move(Event::getAllGroups(false));
201         Event::clearAllGroups();
202 
203         std::map<configKey, std::unique_ptr<Event>> events;
204         try
205         {
206             // Load any events configured, including all the groups
207             events = getConfig<Event>(true, this, zones);
208         }
209         catch (const std::runtime_error& re)
210         {
211             // Restore saved set of all available groups for current events
212             Event::setAllGroups(std::move(groups));
213             throw re;
214         }
215 
216         // Enable zones
217         _zones = std::move(zones);
218         std::for_each(_zones.begin(), _zones.end(),
219                       [](const auto& entry) { entry.second->enable(); });
220 
221         // Clear current timers and signal subscriptions before enabling events
222         // To save reloading services and/or objects into cache, do not clear
223         // cache
224         _timers.clear();
225         _signals.clear();
226 
227         // Enable events
228         _events = std::move(events);
229         std::for_each(_events.begin(), _events.end(),
230                       [](const auto& entry) { entry.second->enable(); });
231 
232         _loadAllowed = false;
233     }
234 }
235 
236 void Manager::powerStateChanged(bool powerStateOn)
237 {
238     if (powerStateOn)
239     {
240         if (_zones.empty())
241         {
242             throw std::runtime_error("No configured zones found at poweron");
243         }
244         std::for_each(_zones.begin(), _zones.end(), [](const auto& entry) {
245             entry.second->setTarget(entry.second->getPoweronTarget());
246         });
247 
248         // Tell events to run their power on triggers
249         std::for_each(_events.begin(), _events.end(),
250                       [](const auto& entry) { entry.second->powerOn(); });
251     }
252     else
253     {
254         // Tell events to run their power off triggers
255         std::for_each(_events.begin(), _events.end(),
256                       [](const auto& entry) { entry.second->powerOff(); });
257     }
258 }
259 
260 const std::vector<std::string>& Manager::getActiveProfiles()
261 {
262     return _activeProfiles;
263 }
264 
265 bool Manager::inConfig(const configKey& input, const configKey& comp)
266 {
267     // Config names dont match, do not include in config
268     if (input.first != comp.first)
269     {
270         return false;
271     }
272     // No profiles specified by input config, can be used in any config
273     if (input.second.empty())
274     {
275         return true;
276     }
277     else
278     {
279         // Profiles must have one match in the other's profiles(and they must be
280         // an active profile) to be used in the config
281         return std::any_of(
282             input.second.begin(), input.second.end(),
283             [&comp](const auto& lProfile) {
284                 return std::any_of(
285                     comp.second.begin(), comp.second.end(),
286                     [&lProfile](const auto& rProfile) {
287                         if (lProfile != rProfile)
288                         {
289                             return false;
290                         }
291                         auto activeProfs = getActiveProfiles();
292                         return std::find(activeProfs.begin(), activeProfs.end(),
293                                          lProfile) != activeProfs.end();
294                     });
295             });
296     }
297 }
298 
299 bool Manager::hasOwner(const std::string& path, const std::string& intf)
300 {
301     auto itServ = _servTree.find(path);
302     if (itServ == _servTree.end())
303     {
304         // Path not found in cache, therefore owner missing
305         return false;
306     }
307     for (const auto& service : itServ->second)
308     {
309         auto itIntf = std::find_if(
310             service.second.second.begin(), service.second.second.end(),
311             [&intf](const auto& interface) { return intf == interface; });
312         if (itIntf != std::end(service.second.second))
313         {
314             // Service found, return owner state
315             return service.second.first;
316         }
317     }
318     // Interface not found in cache, therefore owner missing
319     return false;
320 }
321 
322 void Manager::setOwner(const std::string& serv, bool hasOwner)
323 {
324     // Update owner state on all entries of `serv`
325     for (auto& itPath : _servTree)
326     {
327         auto itServ = itPath.second.find(serv);
328         if (itServ != itPath.second.end())
329         {
330             itServ->second.first = hasOwner;
331 
332             // Remove associated interfaces from object cache when service no
333             // longer has an owner
334             if (!hasOwner && _objects.find(itPath.first) != _objects.end())
335             {
336                 for (auto& intf : itServ->second.second)
337                 {
338                     _objects[itPath.first].erase(intf);
339                 }
340             }
341         }
342     }
343 }
344 
345 void Manager::setOwner(const std::string& path, const std::string& serv,
346                        const std::string& intf, bool isOwned)
347 {
348     // Set owner state for specific object given
349     auto& ownIntf = _servTree[path][serv];
350     ownIntf.first = isOwned;
351     auto itIntf = std::find_if(
352         ownIntf.second.begin(), ownIntf.second.end(),
353         [&intf](const auto& interface) { return intf == interface; });
354     if (itIntf == std::end(ownIntf.second))
355     {
356         ownIntf.second.emplace_back(intf);
357     }
358 
359     // Update owner state on all entries of the same `serv` & `intf`
360     for (auto& itPath : _servTree)
361     {
362         if (itPath.first == path)
363         {
364             // Already set/updated owner on this path for `serv` & `intf`
365             continue;
366         }
367         for (auto& itServ : itPath.second)
368         {
369             if (itServ.first != serv)
370             {
371                 continue;
372             }
373             auto itIntf = std::find_if(
374                 itServ.second.second.begin(), itServ.second.second.end(),
375                 [&intf](const auto& interface) { return intf == interface; });
376             if (itIntf != std::end(itServ.second.second))
377             {
378                 itServ.second.first = isOwned;
379             }
380         }
381     }
382 }
383 
384 const std::string& Manager::findService(const std::string& path,
385                                         const std::string& intf)
386 {
387     static const std::string empty = "";
388 
389     auto itServ = _servTree.find(path);
390     if (itServ != _servTree.end())
391     {
392         for (const auto& service : itServ->second)
393         {
394             auto itIntf = std::find_if(
395                 service.second.second.begin(), service.second.second.end(),
396                 [&intf](const auto& interface) { return intf == interface; });
397             if (itIntf != std::end(service.second.second))
398             {
399                 // Service found, return service name
400                 return service.first;
401             }
402         }
403     }
404 
405     return empty;
406 }
407 
408 void Manager::addServices(const std::string& intf, int32_t depth)
409 {
410     // Get all subtree objects for the given interface
411     auto objects = util::SDBusPlus::getSubTreeRaw(util::SDBusPlus::getBus(),
412                                                   "/", intf, depth);
413     // Add what's returned to the cache of path->services
414     for (auto& itPath : objects)
415     {
416         auto pathIter = _servTree.find(itPath.first);
417         if (pathIter != _servTree.end())
418         {
419             // Path found in cache
420             for (auto& itServ : itPath.second)
421             {
422                 auto servIter = pathIter->second.find(itServ.first);
423                 if (servIter != pathIter->second.end())
424                 {
425                     if (std::find(servIter->second.second.begin(),
426                                   servIter->second.second.end(),
427                                   intf) == servIter->second.second.end())
428                     {
429                         // Add interface to cache
430                         servIter->second.second.emplace_back(intf);
431                     }
432                 }
433                 else
434                 {
435                     // Service not found in cache
436                     auto intfs = {intf};
437                     pathIter->second[itServ.first] =
438                         std::make_pair(true, intfs);
439                 }
440             }
441         }
442         else
443         {
444             // Path not found in cache
445             auto intfs = {intf};
446             for (const auto& [servName, servIntfs] : itPath.second)
447             {
448                 _servTree[itPath.first][servName] = std::make_pair(true, intfs);
449             }
450         }
451     }
452 }
453 
454 const std::string& Manager::getService(const std::string& path,
455                                        const std::string& intf)
456 {
457     // Retrieve service from cache
458     const auto& serviceName = findService(path, intf);
459     if (serviceName.empty())
460     {
461         addServices(intf, 0);
462         return findService(path, intf);
463     }
464 
465     return serviceName;
466 }
467 
468 std::vector<std::string> Manager::findPaths(const std::string& serv,
469                                             const std::string& intf)
470 {
471     std::vector<std::string> paths;
472 
473     for (const auto& path : _servTree)
474     {
475         auto itServ = path.second.find(serv);
476         if (itServ != path.second.end())
477         {
478             if (std::find(itServ->second.second.begin(),
479                           itServ->second.second.end(),
480                           intf) != itServ->second.second.end())
481             {
482                 if (std::find(paths.begin(), paths.end(), path.first) ==
483                     paths.end())
484                 {
485                     paths.push_back(path.first);
486                 }
487             }
488         }
489     }
490 
491     return paths;
492 }
493 
494 std::vector<std::string> Manager::getPaths(const std::string& serv,
495                                            const std::string& intf)
496 {
497     auto paths = findPaths(serv, intf);
498     if (paths.empty())
499     {
500         addServices(intf, 0);
501         return findPaths(serv, intf);
502     }
503 
504     return paths;
505 }
506 
507 void Manager::insertFilteredObjects(ManagedObjects& ref)
508 {
509     // Filter out objects that aren't part of a group
510     const auto& allGroupMembers = Group::getAllMembers();
511     auto it = ref.begin();
512 
513     while (it != ref.end())
514     {
515         if (allGroupMembers.find(it->first) == allGroupMembers.end())
516         {
517             it = ref.erase(it);
518         }
519         else
520         {
521             it++;
522         }
523     }
524 
525     for (auto& [path, pathMap] : ref)
526     {
527         for (auto& [intf, intfMap] : pathMap)
528         {
529             // for each property on this path+interface
530             for (auto& [prop, value] : intfMap)
531             {
532                 setProperty(path, intf, prop, value);
533             }
534         }
535     }
536 }
537 
538 void Manager::addObjects(const std::string& path, const std::string& intf,
539                          const std::string& prop,
540                          const std::string& serviceName)
541 {
542     auto service = serviceName;
543     if (service.empty())
544     {
545         service = getService(path, intf);
546         if (service.empty())
547         {
548             // Log service not found for object
549             log<level::DEBUG>(
550                 fmt::format(
551                     "Unable to get service name for path {}, interface {}",
552                     path, intf)
553                     .c_str());
554             return;
555         }
556     }
557     else
558     {
559         // The service is known, so the service cache can be
560         // populated even if the path itself isn't present.
561         const auto& s = findService(path, intf);
562         if (s.empty())
563         {
564             addServices(intf, 0);
565         }
566     }
567 
568     auto objMgrPaths = getPaths(service, "org.freedesktop.DBus.ObjectManager");
569     if (objMgrPaths.empty())
570     {
571         // No object manager interface provided by service?
572         // Attempt to retrieve property directly
573         try
574         {
575             auto value =
576                 util::SDBusPlus::getPropertyVariant<PropertyVariantType>(
577                     _bus, service, path, intf, prop);
578 
579             setProperty(path, intf, prop, value);
580         }
581         catch (const std::exception& e)
582         {}
583         return;
584     }
585 
586     for (const auto& objMgrPath : objMgrPaths)
587     {
588         // Get all managed objects of service
589         auto objects = util::SDBusPlus::getManagedObjects<PropertyVariantType>(
590             _bus, service, objMgrPath);
591 
592         // insert all objects that are in groups but remove any NaN values
593         insertFilteredObjects(objects);
594     }
595 }
596 
597 const std::optional<PropertyVariantType>
598     Manager::getProperty(const std::string& path, const std::string& intf,
599                          const std::string& prop)
600 {
601     // TODO Objects hosted by fan control (i.e. ThermalMode) are required to
602     // update the cache upon being set/updated
603     auto itPath = _objects.find(path);
604     if (itPath != _objects.end())
605     {
606         auto itIntf = itPath->second.find(intf);
607         if (itIntf != itPath->second.end())
608         {
609             auto itProp = itIntf->second.find(prop);
610             if (itProp != itIntf->second.end())
611             {
612                 return itProp->second;
613             }
614         }
615     }
616 
617     return std::nullopt;
618 }
619 
620 void Manager::setProperty(const std::string& path, const std::string& intf,
621                           const std::string& prop, PropertyVariantType value)
622 {
623     // filter NaNs out of the cache
624     if (PropertyContainsNan(value))
625     {
626         // dont use operator [] if paths dont exist
627         if (_objects.find(path) != _objects.end() &&
628             _objects[path].find(intf) != _objects[path].end())
629         {
630             _objects[path][intf].erase(prop);
631         }
632     }
633     else
634     {
635         _objects[path][intf][prop] = std::move(value);
636     }
637 }
638 
639 void Manager::addTimer(const TimerType type,
640                        const std::chrono::microseconds interval,
641                        std::unique_ptr<TimerPkg> pkg)
642 {
643     auto dataPtr =
644         std::make_unique<TimerData>(std::make_pair(type, std::move(*pkg)));
645     Timer timer(_event,
646                 std::bind(&Manager::timerExpired, this, std::ref(*dataPtr)));
647     if (type == TimerType::repeating)
648     {
649         timer.restart(interval);
650     }
651     else if (type == TimerType::oneshot)
652     {
653         timer.restartOnce(interval);
654     }
655     else
656     {
657         throw std::invalid_argument("Invalid Timer Type");
658     }
659     _timers.emplace_back(std::move(dataPtr), std::move(timer));
660 }
661 
662 void Manager::addGroups(const std::vector<Group>& groups)
663 {
664     std::string lastServ;
665     std::vector<std::string> objMgrPaths;
666     std::set<std::string> services;
667     for (const auto& group : groups)
668     {
669         for (const auto& member : group.getMembers())
670         {
671             try
672             {
673                 auto service = group.getService();
674                 if (service.empty())
675                 {
676                     service = getService(member, group.getInterface());
677                 }
678 
679                 if (!service.empty())
680                 {
681                     if (lastServ != service)
682                     {
683                         objMgrPaths = getPaths(
684                             service, "org.freedesktop.DBus.ObjectManager");
685                         lastServ = service;
686                     }
687 
688                     // Look for the ObjectManager as an ancestor from the
689                     // member.
690                     auto hasObjMgr = std::any_of(
691                         objMgrPaths.begin(), objMgrPaths.end(),
692                         [&member](const auto& path) {
693                             return member.find(path) != std::string::npos;
694                         });
695 
696                     if (!hasObjMgr)
697                     {
698                         // No object manager interface provided for group member
699                         // Attempt to retrieve group member property directly
700                         try
701                         {
702                             auto value = util::SDBusPlus::getPropertyVariant<
703                                 PropertyVariantType>(_bus, service, member,
704                                                      group.getInterface(),
705                                                      group.getProperty());
706                             setProperty(member, group.getInterface(),
707                                         group.getProperty(), value);
708                         }
709                         catch (const std::exception& e)
710                         {}
711                         continue;
712                     }
713 
714                     if (services.find(service) == services.end())
715                     {
716                         services.insert(service);
717                         for (const auto& objMgrPath : objMgrPaths)
718                         {
719                             // Get all managed objects from the service
720                             auto objects = util::SDBusPlus::getManagedObjects<
721                                 PropertyVariantType>(_bus, service, objMgrPath);
722 
723                             // Insert objects into cache
724                             insertFilteredObjects(objects);
725                         }
726                     }
727                 }
728             }
729             catch (const util::DBusError&)
730             {
731                 // No service or property found for group member with the
732                 // group's configured interface
733                 continue;
734             }
735         }
736     }
737 }
738 
739 void Manager::timerExpired(TimerData& data)
740 {
741     if (std::get<bool>(data.second))
742     {
743         addGroups(std::get<const std::vector<Group>&>(data.second));
744     }
745 
746     auto& actions =
747         std::get<std::vector<std::unique_ptr<ActionBase>>&>(data.second);
748     // Perform the actions in the timer data
749     std::for_each(actions.begin(), actions.end(),
750                   [](auto& action) { action->run(); });
751 
752     // Remove oneshot timers after they expired
753     if (data.first == TimerType::oneshot)
754     {
755         auto itTimer = std::find_if(
756             _timers.begin(), _timers.end(), [&data](const auto& timer) {
757                 return (data.first == timer.first->first &&
758                         (std::get<std::string>(data.second) ==
759                          std::get<std::string>(timer.first->second)));
760             });
761         if (itTimer != std::end(_timers))
762         {
763             _timers.erase(itTimer);
764         }
765     }
766 }
767 
768 void Manager::handleSignal(sdbusplus::message_t& msg,
769                            const std::vector<SignalPkg>* pkgs)
770 {
771     for (auto& pkg : *pkgs)
772     {
773         // Handle the signal callback and only run the actions if the handler
774         // updated the cache for the given SignalObject
775         if (std::get<SignalHandler>(pkg)(msg, std::get<SignalObject>(pkg),
776                                          *this))
777         {
778             // Perform the actions in the handler package
779             auto& actions = std::get<TriggerActions>(pkg);
780             std::for_each(actions.begin(), actions.end(), [](auto& action) {
781                 if (action.get())
782                 {
783                     action.get()->run();
784                 }
785             });
786         }
787         // Only rewind message when not last package
788         if (&pkg != &pkgs->back())
789         {
790             sd_bus_message_rewind(msg.get(), true);
791         }
792     }
793 }
794 
795 void Manager::setProfiles()
796 {
797     // Profiles JSON config file is optional
798     auto confFile =
799         fan::JsonConfig::getConfFile(confAppName, Profile::confFileName, true);
800 
801     _profiles.clear();
802     if (!confFile.empty())
803     {
804         for (const auto& entry : fan::JsonConfig::load(confFile))
805         {
806             auto obj = std::make_unique<Profile>(entry);
807             _profiles.emplace(
808                 std::make_pair(obj->getName(), obj->getProfiles()),
809                 std::move(obj));
810         }
811     }
812 
813     // Ensure all configurations use the same set of active profiles
814     // (In case a profile's active state changes during configuration)
815     _activeProfiles.clear();
816     for (const auto& profile : _profiles)
817     {
818         if (profile.second->isActive())
819         {
820             _activeProfiles.emplace_back(profile.first.first);
821         }
822     }
823 }
824 
825 void Manager::addParameterTrigger(
826     const std::string& name, std::vector<std::unique_ptr<ActionBase>>& actions)
827 {
828     auto it = _parameterTriggers.find(name);
829     if (it != _parameterTriggers.end())
830     {
831         std::for_each(actions.begin(), actions.end(),
832                       [&actList = it->second](auto& action) {
833                           actList.emplace_back(std::ref(action));
834                       });
835     }
836     else
837     {
838         TriggerActions triggerActions;
839         std::for_each(actions.begin(), actions.end(),
840                       [&triggerActions](auto& action) {
841                           triggerActions.emplace_back(std::ref(action));
842                       });
843         _parameterTriggers[name] = std::move(triggerActions);
844     }
845 }
846 
847 void Manager::runParameterActions(const std::string& name)
848 {
849     auto it = _parameterTriggers.find(name);
850     if (it != _parameterTriggers.end())
851     {
852         std::for_each(it->second.begin(), it->second.end(),
853                       [](auto& action) { action.get()->run(); });
854     }
855 }
856 
857 } // namespace phosphor::fan::control::json
858