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