| /** |
| * Copyright © 2022 IBM Corporation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include "config.h" |
| |
| #include "manager.hpp" |
| |
| #include "action.hpp" |
| #include "dbus_paths.hpp" |
| #include "event.hpp" |
| #include "fan.hpp" |
| #include "group.hpp" |
| #include "json_config.hpp" |
| #include "power_state.hpp" |
| #include "profile.hpp" |
| #include "sdbusplus.hpp" |
| #include "utils/flight_recorder.hpp" |
| #include "zone.hpp" |
| |
| #include <systemd/sd-bus.h> |
| |
| #include <nlohmann/json.hpp> |
| #include <sdbusplus/bus.hpp> |
| #include <sdbusplus/server/manager.hpp> |
| #include <sdeventplus/event.hpp> |
| #include <sdeventplus/utility/timer.hpp> |
| |
| #include <algorithm> |
| #include <chrono> |
| #include <filesystem> |
| #include <functional> |
| #include <map> |
| #include <memory> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| namespace phosphor::fan::control::json |
| { |
| |
| using json = nlohmann::json; |
| |
| std::vector<std::string> Manager::_activeProfiles; |
| std::map<std::string, |
| std::map<std::string, std::pair<bool, std::vector<std::string>>>> |
| Manager::_servTree; |
| std::map<std::string, |
| std::map<std::string, std::map<std::string, PropertyVariantType>>> |
| Manager::_objects; |
| std::unordered_map<std::string, PropertyVariantType> Manager::_parameters; |
| std::unordered_map<std::string, TriggerActions> Manager::_parameterTriggers; |
| |
| const std::string Manager::dumpFile = "/tmp/fan_control_dump.json"; |
| |
| Manager::Manager(const sdeventplus::Event& event) : |
| _bus(util::SDBusPlus::getBus()), _event(event), |
| _mgr(util::SDBusPlus::getBus(), CONTROL_OBJPATH), _loadAllowed(true), |
| _powerState(std::make_unique<PGoodState>( |
| util::SDBusPlus::getBus(), |
| std::bind(std::mem_fn(&Manager::powerStateChanged), this, |
| std::placeholders::_1))) |
| {} |
| |
| void Manager::sighupHandler(sdeventplus::source::Signal&, |
| const struct signalfd_siginfo*) |
| { |
| FlightRecorder::instance().log("main", "SIGHUP received"); |
| // Save current set of available and active profiles |
| std::map<configKey, std::unique_ptr<Profile>> profiles; |
| profiles.swap(_profiles); |
| std::vector<std::string> activeProfiles; |
| activeProfiles.swap(_activeProfiles); |
| |
| try |
| { |
| _loadAllowed = true; |
| load(); |
| } |
| catch (const std::runtime_error& re) |
| { |
| // Restore saved available and active profiles |
| _loadAllowed = false; |
| _profiles.swap(profiles); |
| _activeProfiles.swap(activeProfiles); |
| log<level::ERR>("Error reloading configs, no changes made", |
| entry("LOAD_ERROR=%s", re.what())); |
| FlightRecorder::instance().log( |
| "main", std::format("Error reloading configs, no changes made: {}", |
| re.what())); |
| } |
| } |
| |
| void Manager::dumpDebugData(sdeventplus::source::Signal&, |
| const struct signalfd_siginfo*) |
| { |
| json data; |
| FlightRecorder::instance().dump(data); |
| dumpCache(data); |
| |
| std::for_each(_zones.begin(), _zones.end(), [&data](const auto& zone) { |
| data["zones"][zone.second->getName()] = zone.second->dump(); |
| }); |
| |
| std::ofstream file{Manager::dumpFile}; |
| if (!file) |
| { |
| log<level::ERR>("Could not open file for fan dump"); |
| return; |
| } |
| |
| file << std::setw(4) << data; |
| } |
| |
| void Manager::dumpCache(json& data) |
| { |
| auto& objects = data["objects"]; |
| for (const auto& [path, interfaces] : _objects) |
| { |
| auto& interfaceJSON = objects[path]; |
| |
| for (const auto& [interface, properties] : interfaces) |
| { |
| auto& propertyJSON = interfaceJSON[interface]; |
| for (const auto& [propName, propValue] : properties) |
| { |
| std::visit( |
| [&obj = propertyJSON[propName]](auto&& val) { obj = val; }, |
| propValue); |
| } |
| } |
| } |
| |
| auto& parameters = data["parameters"]; |
| for (const auto& [name, value] : _parameters) |
| { |
| std::visit([&obj = parameters[name]](auto&& val) { obj = val; }, value); |
| } |
| |
| std::for_each(_events.begin(), _events.end(), [&data](const auto& event) { |
| data["events"][event.second->getName()] = event.second->dump(); |
| }); |
| |
| data["services"] = _servTree; |
| } |
| |
| void Manager::load() |
| { |
| if (_loadAllowed) |
| { |
| // Load the available profiles and which are active |
| setProfiles(); |
| |
| // Load the zone configurations |
| auto zones = getConfig<Zone>(false, _event, this); |
| // Load the fan configurations and move each fan into its zone |
| auto fans = getConfig<Fan>(false); |
| for (auto& fan : fans) |
| { |
| configKey fanProfile = |
| std::make_pair(fan.second->getZone(), fan.first.second); |
| auto itZone = std::find_if( |
| zones.begin(), zones.end(), [&fanProfile](const auto& zone) { |
| return Manager::inConfig(fanProfile, zone.first); |
| }); |
| if (itZone != zones.end()) |
| { |
| if (itZone->second->getTarget() != fan.second->getTarget() && |
| fan.second->getTarget() != 0) |
| { |
| // Update zone target to current target of the fan in the |
| // zone |
| itZone->second->setTarget(fan.second->getTarget()); |
| } |
| itZone->second->addFan(std::move(fan.second)); |
| } |
| } |
| |
| // Save all currently available groups, if any, then clear for reloading |
| auto groups = std::move(Event::getAllGroups(false)); |
| Event::clearAllGroups(); |
| |
| std::map<configKey, std::unique_ptr<Event>> events; |
| try |
| { |
| // Load any events configured, including all the groups |
| events = getConfig<Event>(true, this, zones); |
| } |
| catch (const std::runtime_error& re) |
| { |
| // Restore saved set of all available groups for current events |
| Event::setAllGroups(std::move(groups)); |
| throw re; |
| } |
| |
| // Enable zones |
| _zones = std::move(zones); |
| std::for_each(_zones.begin(), _zones.end(), |
| [](const auto& entry) { entry.second->enable(); }); |
| |
| // Clear current timers and signal subscriptions before enabling events |
| // To save reloading services and/or objects into cache, do not clear |
| // cache |
| _timers.clear(); |
| _signals.clear(); |
| |
| // Enable events |
| _events = std::move(events); |
| FlightRecorder::instance().log("main", "Enabling events"); |
| std::for_each(_events.begin(), _events.end(), |
| [](const auto& entry) { entry.second->enable(); }); |
| FlightRecorder::instance().log("main", "Done enabling events"); |
| |
| _loadAllowed = false; |
| } |
| } |
| |
| void Manager::powerStateChanged(bool powerStateOn) |
| { |
| if (powerStateOn) |
| { |
| FlightRecorder::instance().log("power", "Power on"); |
| if (_zones.empty()) |
| { |
| throw std::runtime_error("No configured zones found at poweron"); |
| } |
| std::for_each(_zones.begin(), _zones.end(), [](const auto& entry) { |
| entry.second->setTarget(entry.second->getPoweronTarget()); |
| }); |
| |
| // Tell events to run their power on triggers |
| std::for_each(_events.begin(), _events.end(), |
| [](const auto& entry) { entry.second->powerOn(); }); |
| } |
| else |
| { |
| FlightRecorder::instance().log("power", "Power off"); |
| // Tell events to run their power off triggers |
| std::for_each(_events.begin(), _events.end(), |
| [](const auto& entry) { entry.second->powerOff(); }); |
| } |
| } |
| |
| const std::vector<std::string>& Manager::getActiveProfiles() |
| { |
| return _activeProfiles; |
| } |
| |
| bool Manager::inConfig(const configKey& input, const configKey& comp) |
| { |
| // Config names dont match, do not include in config |
| if (input.first != comp.first) |
| { |
| return false; |
| } |
| // No profiles specified by input config, can be used in any config |
| if (input.second.empty()) |
| { |
| return true; |
| } |
| else |
| { |
| // Profiles must have one match in the other's profiles(and they must be |
| // an active profile) to be used in the config |
| return std::any_of( |
| input.second.begin(), input.second.end(), |
| [&comp](const auto& lProfile) { |
| return std::any_of( |
| comp.second.begin(), comp.second.end(), |
| [&lProfile](const auto& rProfile) { |
| if (lProfile != rProfile) |
| { |
| return false; |
| } |
| auto activeProfs = getActiveProfiles(); |
| return std::find(activeProfs.begin(), activeProfs.end(), |
| lProfile) != activeProfs.end(); |
| }); |
| }); |
| } |
| } |
| |
| bool Manager::hasOwner(const std::string& path, const std::string& intf) |
| { |
| auto itServ = _servTree.find(path); |
| if (itServ == _servTree.end()) |
| { |
| // Path not found in cache, therefore owner missing |
| return false; |
| } |
| for (const auto& service : itServ->second) |
| { |
| auto itIntf = std::find_if( |
| service.second.second.begin(), service.second.second.end(), |
| [&intf](const auto& interface) { return intf == interface; }); |
| if (itIntf != std::end(service.second.second)) |
| { |
| // Service found, return owner state |
| return service.second.first; |
| } |
| } |
| // Interface not found in cache, therefore owner missing |
| return false; |
| } |
| |
| void Manager::setOwner(const std::string& serv, bool hasOwner) |
| { |
| // Update owner state on all entries of `serv` |
| for (auto& itPath : _servTree) |
| { |
| auto itServ = itPath.second.find(serv); |
| if (itServ != itPath.second.end()) |
| { |
| itServ->second.first = hasOwner; |
| |
| // Remove associated interfaces from object cache when service no |
| // longer has an owner |
| if (!hasOwner && _objects.find(itPath.first) != _objects.end()) |
| { |
| for (auto& intf : itServ->second.second) |
| { |
| _objects[itPath.first].erase(intf); |
| } |
| } |
| } |
| } |
| } |
| |
| void Manager::setOwner(const std::string& path, const std::string& serv, |
| const std::string& intf, bool isOwned) |
| { |
| // Set owner state for specific object given |
| auto& ownIntf = _servTree[path][serv]; |
| ownIntf.first = isOwned; |
| auto itIntf = std::find_if( |
| ownIntf.second.begin(), ownIntf.second.end(), |
| [&intf](const auto& interface) { return intf == interface; }); |
| if (itIntf == std::end(ownIntf.second)) |
| { |
| ownIntf.second.emplace_back(intf); |
| } |
| |
| // Update owner state on all entries of the same `serv` & `intf` |
| for (auto& itPath : _servTree) |
| { |
| if (itPath.first == path) |
| { |
| // Already set/updated owner on this path for `serv` & `intf` |
| continue; |
| } |
| for (auto& itServ : itPath.second) |
| { |
| if (itServ.first != serv) |
| { |
| continue; |
| } |
| auto itIntf = std::find_if( |
| itServ.second.second.begin(), itServ.second.second.end(), |
| [&intf](const auto& interface) { return intf == interface; }); |
| if (itIntf != std::end(itServ.second.second)) |
| { |
| itServ.second.first = isOwned; |
| } |
| } |
| } |
| } |
| |
| const std::string& Manager::findService(const std::string& path, |
| const std::string& intf) |
| { |
| static const std::string empty = ""; |
| |
| auto itServ = _servTree.find(path); |
| if (itServ != _servTree.end()) |
| { |
| for (const auto& service : itServ->second) |
| { |
| auto itIntf = std::find_if( |
| service.second.second.begin(), service.second.second.end(), |
| [&intf](const auto& interface) { return intf == interface; }); |
| if (itIntf != std::end(service.second.second)) |
| { |
| // Service found, return service name |
| return service.first; |
| } |
| } |
| } |
| |
| return empty; |
| } |
| |
| void Manager::addServices(const std::string& intf, int32_t depth) |
| { |
| // Get all subtree objects for the given interface |
| auto objects = util::SDBusPlus::getSubTreeRaw(util::SDBusPlus::getBus(), |
| "/", intf, depth); |
| // Add what's returned to the cache of path->services |
| for (auto& itPath : objects) |
| { |
| auto pathIter = _servTree.find(itPath.first); |
| if (pathIter != _servTree.end()) |
| { |
| // Path found in cache |
| for (auto& itServ : itPath.second) |
| { |
| auto servIter = pathIter->second.find(itServ.first); |
| if (servIter != pathIter->second.end()) |
| { |
| if (std::find(servIter->second.second.begin(), |
| servIter->second.second.end(), intf) == |
| servIter->second.second.end()) |
| { |
| // Add interface to cache |
| servIter->second.second.emplace_back(intf); |
| } |
| } |
| else |
| { |
| // Service not found in cache |
| auto intfs = {intf}; |
| pathIter->second[itServ.first] = |
| std::make_pair(true, intfs); |
| } |
| } |
| } |
| else |
| { |
| // Path not found in cache |
| auto intfs = {intf}; |
| for (const auto& [servName, servIntfs] : itPath.second) |
| { |
| _servTree[itPath.first][servName] = std::make_pair(true, intfs); |
| } |
| } |
| } |
| } |
| |
| const std::string& Manager::getService(const std::string& path, |
| const std::string& intf) |
| { |
| // Retrieve service from cache |
| const auto& serviceName = findService(path, intf); |
| if (serviceName.empty()) |
| { |
| addServices(intf, 0); |
| return findService(path, intf); |
| } |
| |
| return serviceName; |
| } |
| |
| std::vector<std::string> |
| Manager::findPaths(const std::string& serv, const std::string& intf) |
| { |
| std::vector<std::string> paths; |
| |
| for (const auto& path : _servTree) |
| { |
| auto itServ = path.second.find(serv); |
| if (itServ != path.second.end()) |
| { |
| if (std::find(itServ->second.second.begin(), |
| itServ->second.second.end(), intf) != |
| itServ->second.second.end()) |
| { |
| if (std::find(paths.begin(), paths.end(), path.first) == |
| paths.end()) |
| { |
| paths.push_back(path.first); |
| } |
| } |
| } |
| } |
| |
| return paths; |
| } |
| |
| std::vector<std::string> Manager::getPaths(const std::string& serv, |
| const std::string& intf) |
| { |
| auto paths = findPaths(serv, intf); |
| if (paths.empty()) |
| { |
| addServices(intf, 0); |
| return findPaths(serv, intf); |
| } |
| |
| return paths; |
| } |
| |
| void Manager::insertFilteredObjects(ManagedObjects& ref) |
| { |
| // Filter out objects that aren't part of a group |
| const auto& allGroupMembers = Group::getAllMembers(); |
| auto it = ref.begin(); |
| |
| while (it != ref.end()) |
| { |
| if (allGroupMembers.find(it->first) == allGroupMembers.end()) |
| { |
| it = ref.erase(it); |
| } |
| else |
| { |
| it++; |
| } |
| } |
| |
| for (auto& [path, pathMap] : ref) |
| { |
| for (auto& [intf, intfMap] : pathMap) |
| { |
| // for each property on this path+interface |
| for (auto& [prop, value] : intfMap) |
| { |
| setProperty(path, intf, prop, value); |
| } |
| } |
| } |
| } |
| |
| void Manager::addObjects(const std::string& path, const std::string& intf, |
| const std::string& prop, |
| const std::string& serviceName) |
| { |
| auto service = serviceName; |
| if (service.empty()) |
| { |
| service = getService(path, intf); |
| if (service.empty()) |
| { |
| // Log service not found for object |
| log<level::DEBUG>( |
| std::format( |
| "Unable to get service name for path {}, interface {}", |
| path, intf) |
| .c_str()); |
| return; |
| } |
| } |
| else |
| { |
| // The service is known, so the service cache can be |
| // populated even if the path itself isn't present. |
| const auto& s = findService(path, intf); |
| if (s.empty()) |
| { |
| addServices(intf, 0); |
| } |
| } |
| |
| auto objMgrPaths = getPaths(service, "org.freedesktop.DBus.ObjectManager"); |
| |
| bool useManagedObj = false; |
| |
| if (!objMgrPaths.empty()) |
| { |
| for (const auto& objMgrPath : objMgrPaths) |
| { |
| // Get all managed objects of service |
| auto objects = |
| util::SDBusPlus::getManagedObjects<PropertyVariantType>( |
| _bus, service, objMgrPath); |
| if (objects.size() > 0) |
| { |
| useManagedObj = true; |
| } |
| // insert all objects that are in groups but remove any NaN values |
| insertFilteredObjects(objects); |
| } |
| } |
| |
| if (!useManagedObj) |
| { |
| // No object manager interface provided by service? |
| // Or no object is managed? |
| // Attempt to retrieve property directly |
| try |
| { |
| auto value = |
| util::SDBusPlus::getPropertyVariant<PropertyVariantType>( |
| _bus, service, path, intf, prop); |
| |
| setProperty(path, intf, prop, value); |
| } |
| catch (const std::exception& e) |
| {} |
| return; |
| } |
| } |
| |
| const std::optional<PropertyVariantType> Manager::getProperty( |
| const std::string& path, const std::string& intf, const std::string& prop) |
| { |
| // TODO Objects hosted by fan control (i.e. ThermalMode) are required to |
| // update the cache upon being set/updated |
| auto itPath = _objects.find(path); |
| if (itPath != _objects.end()) |
| { |
| auto itIntf = itPath->second.find(intf); |
| if (itIntf != itPath->second.end()) |
| { |
| auto itProp = itIntf->second.find(prop); |
| if (itProp != itIntf->second.end()) |
| { |
| return itProp->second; |
| } |
| } |
| } |
| |
| return std::nullopt; |
| } |
| |
| void Manager::setProperty(const std::string& path, const std::string& intf, |
| const std::string& prop, PropertyVariantType value) |
| { |
| // filter NaNs out of the cache |
| if (PropertyContainsNan(value)) |
| { |
| // dont use operator [] if paths dont exist |
| if (_objects.find(path) != _objects.end() && |
| _objects[path].find(intf) != _objects[path].end()) |
| { |
| _objects[path][intf].erase(prop); |
| } |
| } |
| else |
| { |
| _objects[path][intf][prop] = std::move(value); |
| } |
| } |
| |
| void Manager::addTimer(const TimerType type, |
| const std::chrono::microseconds interval, |
| std::unique_ptr<TimerPkg> pkg) |
| { |
| auto dataPtr = |
| std::make_unique<TimerData>(std::make_pair(type, std::move(*pkg))); |
| Timer timer(_event, |
| std::bind(&Manager::timerExpired, this, std::ref(*dataPtr))); |
| if (type == TimerType::repeating) |
| { |
| timer.restart(interval); |
| } |
| else if (type == TimerType::oneshot) |
| { |
| timer.restartOnce(interval); |
| } |
| else |
| { |
| throw std::invalid_argument("Invalid Timer Type"); |
| } |
| _timers.emplace_back(std::move(dataPtr), std::move(timer)); |
| } |
| |
| void Manager::addGroups(const std::vector<Group>& groups) |
| { |
| std::string lastServ; |
| std::vector<std::string> objMgrPaths; |
| std::set<std::string> services; |
| for (const auto& group : groups) |
| { |
| for (const auto& member : group.getMembers()) |
| { |
| try |
| { |
| auto service = group.getService(); |
| if (service.empty()) |
| { |
| service = getService(member, group.getInterface()); |
| } |
| |
| if (!service.empty()) |
| { |
| if (lastServ != service) |
| { |
| objMgrPaths = getPaths( |
| service, "org.freedesktop.DBus.ObjectManager"); |
| lastServ = service; |
| } |
| |
| // Look for the ObjectManager as an ancestor from the |
| // member. |
| auto hasObjMgr = std::any_of( |
| objMgrPaths.begin(), objMgrPaths.end(), |
| [&member](const auto& path) { |
| return member.find(path) != std::string::npos; |
| }); |
| |
| if (!hasObjMgr) |
| { |
| // No object manager interface provided for group member |
| // Attempt to retrieve group member property directly |
| try |
| { |
| auto value = util::SDBusPlus::getPropertyVariant< |
| PropertyVariantType>(_bus, service, member, |
| group.getInterface(), |
| group.getProperty()); |
| setProperty(member, group.getInterface(), |
| group.getProperty(), value); |
| } |
| catch (const std::exception& e) |
| {} |
| continue; |
| } |
| |
| if (services.find(service) == services.end()) |
| { |
| services.insert(service); |
| for (const auto& objMgrPath : objMgrPaths) |
| { |
| // Get all managed objects from the service |
| auto objects = util::SDBusPlus::getManagedObjects< |
| PropertyVariantType>(_bus, service, objMgrPath); |
| |
| // Insert objects into cache |
| insertFilteredObjects(objects); |
| } |
| } |
| } |
| } |
| catch (const util::DBusError&) |
| { |
| // No service or property found for group member with the |
| // group's configured interface |
| continue; |
| } |
| } |
| } |
| } |
| |
| void Manager::timerExpired(TimerData& data) |
| { |
| if (std::get<bool>(data.second)) |
| { |
| addGroups(std::get<const std::vector<Group>&>(data.second)); |
| } |
| |
| auto& actions = |
| std::get<std::vector<std::unique_ptr<ActionBase>>&>(data.second); |
| // Perform the actions in the timer data |
| std::for_each(actions.begin(), actions.end(), |
| [](auto& action) { action->run(); }); |
| |
| // Remove oneshot timers after they expired |
| if (data.first == TimerType::oneshot) |
| { |
| auto itTimer = std::find_if( |
| _timers.begin(), _timers.end(), [&data](const auto& timer) { |
| return (data.first == timer.first->first && |
| (std::get<std::string>(data.second) == |
| std::get<std::string>(timer.first->second))); |
| }); |
| if (itTimer != std::end(_timers)) |
| { |
| _timers.erase(itTimer); |
| } |
| } |
| } |
| |
| void Manager::handleSignal(sdbusplus::message_t& msg, |
| const std::vector<SignalPkg>* pkgs) |
| { |
| for (auto& pkg : *pkgs) |
| { |
| // Handle the signal callback and only run the actions if the handler |
| // updated the cache for the given SignalObject |
| if (std::get<SignalHandler>( |
| pkg)(msg, std::get<SignalObject>(pkg), *this)) |
| { |
| // Perform the actions in the handler package |
| auto& actions = std::get<TriggerActions>(pkg); |
| std::for_each(actions.begin(), actions.end(), [](auto& action) { |
| if (action.get()) |
| { |
| action.get()->run(); |
| } |
| }); |
| } |
| // Only rewind message when not last package |
| if (&pkg != &pkgs->back()) |
| { |
| sd_bus_message_rewind(msg.get(), true); |
| } |
| } |
| } |
| |
| void Manager::setProfiles() |
| { |
| // Profiles JSON config file is optional |
| auto confFile = |
| fan::JsonConfig::getConfFile(confAppName, Profile::confFileName, true); |
| |
| _profiles.clear(); |
| if (!confFile.empty()) |
| { |
| for (const auto& entry : fan::JsonConfig::load(confFile)) |
| { |
| auto obj = std::make_unique<Profile>(entry); |
| _profiles.emplace( |
| std::make_pair(obj->getName(), obj->getProfiles()), |
| std::move(obj)); |
| } |
| } |
| |
| // Ensure all configurations use the same set of active profiles |
| // (In case a profile's active state changes during configuration) |
| _activeProfiles.clear(); |
| for (const auto& profile : _profiles) |
| { |
| if (profile.second->isActive()) |
| { |
| _activeProfiles.emplace_back(profile.first.first); |
| } |
| } |
| } |
| |
| void Manager::addParameterTrigger( |
| const std::string& name, std::vector<std::unique_ptr<ActionBase>>& actions) |
| { |
| auto it = _parameterTriggers.find(name); |
| if (it != _parameterTriggers.end()) |
| { |
| std::for_each(actions.begin(), actions.end(), |
| [&actList = it->second](auto& action) { |
| actList.emplace_back(std::ref(action)); |
| }); |
| } |
| else |
| { |
| TriggerActions triggerActions; |
| std::for_each(actions.begin(), actions.end(), |
| [&triggerActions](auto& action) { |
| triggerActions.emplace_back(std::ref(action)); |
| }); |
| _parameterTriggers[name] = std::move(triggerActions); |
| } |
| } |
| |
| void Manager::runParameterActions(const std::string& name) |
| { |
| auto it = _parameterTriggers.find(name); |
| if (it != _parameterTriggers.end()) |
| { |
| std::for_each(it->second.begin(), it->second.end(), |
| [](auto& action) { action.get()->run(); }); |
| } |
| } |
| |
| } // namespace phosphor::fan::control::json |