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