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