1 /** 2 * Copyright © 2021 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 "signal.hpp" 17 18 #include "../manager.hpp" 19 #include "action.hpp" 20 #include "group.hpp" 21 #include "handlers.hpp" 22 #include "trigger_aliases.hpp" 23 24 #include <nlohmann/json.hpp> 25 #include <phosphor-logging/log.hpp> 26 #include <sdbusplus/bus/match.hpp> 27 28 #include <algorithm> 29 #include <format> 30 #include <functional> 31 #include <iterator> 32 #include <memory> 33 #include <numeric> 34 #include <utility> 35 #include <vector> 36 37 namespace phosphor::fan::control::json::trigger::signal 38 { 39 40 using json = nlohmann::json; 41 using namespace phosphor::logging; 42 using namespace sdbusplus::bus::match; 43 44 void subscribe(const std::string& match, SignalPkg&& signalPkg, 45 std::function<bool(SignalPkg&)> isSameSig, Manager* mgr) 46 { 47 auto& signalData = mgr->getSignal(match); 48 if (signalData.empty()) 49 { 50 // Signal subscription doesnt exist, add signal package and subscribe 51 std::unique_ptr<std::vector<SignalPkg>> pkgs = 52 std::make_unique<std::vector<SignalPkg>>(); 53 pkgs->emplace_back(std::move(signalPkg)); 54 std::unique_ptr<sdbusplus::bus::match_t> ptrMatch = nullptr; 55 if (!match.empty()) 56 { 57 // Subscribe to signal 58 ptrMatch = std::make_unique<sdbusplus::bus::match_t>( 59 mgr->getBus(), match.c_str(), 60 std::bind(std::mem_fn(&Manager::handleSignal), &(*mgr), 61 std::placeholders::_1, pkgs.get())); 62 } 63 signalData.emplace_back(std::move(pkgs), std::move(ptrMatch)); 64 } 65 else 66 { 67 // Signal subscription already exists 68 // Only a single signal data entry tied to each match is supported 69 auto& pkgs = std::get<std::unique_ptr<std::vector<SignalPkg>>>( 70 signalData.front()); 71 auto sameSignal = false; 72 for (auto& pkg : *pkgs) 73 { 74 if (isSameSig(pkg)) 75 { 76 // Same SignalObject signal to trigger event actions, 77 // add actions to be run when signal for SignalObject received 78 auto& pkgActions = std::get<TriggerActions>(signalPkg); 79 auto& actions = std::get<TriggerActions>(pkg); 80 actions.insert(actions.end(), pkgActions.begin(), 81 pkgActions.end()); 82 sameSignal = true; 83 break; 84 } 85 } 86 if (!sameSignal) 87 { 88 // Expected signal differs, add signal package 89 pkgs->emplace_back(std::move(signalPkg)); 90 } 91 } 92 } 93 94 void propertiesChanged(Manager* mgr, const Group& group, 95 TriggerActions& actions, const json&) 96 { 97 // Groups are optional, but a signal triggered event with no groups 98 // will do nothing since signals require a group 99 for (const auto& member : group.getMembers()) 100 { 101 // Setup property changed signal handler on the group member's 102 // property 103 const auto match = rules::propertiesChanged(member, 104 group.getInterface()); 105 SignalPkg signalPkg = {Handlers::propertiesChanged, 106 SignalObject(std::cref(member), 107 std::cref(group.getInterface()), 108 std::cref(group.getProperty())), 109 actions}; 110 auto isSameSig = [&prop = group.getProperty()](SignalPkg& pkg) { 111 auto& obj = std::get<SignalObject>(pkg); 112 return prop == std::get<Prop>(obj); 113 }; 114 115 subscribe(match, std::move(signalPkg), isSameSig, mgr); 116 } 117 } 118 119 void interfacesAdded(Manager* mgr, const Group& group, TriggerActions& actions, 120 const json&) 121 { 122 // Groups are optional, but a signal triggered event with no groups 123 // will do nothing since signals require a group 124 for (const auto& member : group.getMembers()) 125 { 126 // Setup interfaces added signal handler on the group member 127 const auto match = rules::interfacesAdded() + 128 rules::argNpath(0, member); 129 SignalPkg signalPkg = {Handlers::interfacesAdded, 130 SignalObject(std::cref(member), 131 std::cref(group.getInterface()), 132 std::cref(group.getProperty())), 133 actions}; 134 auto isSameSig = [&intf = group.getInterface()](SignalPkg& pkg) { 135 auto& obj = std::get<SignalObject>(pkg); 136 return intf == std::get<Intf>(obj); 137 }; 138 139 subscribe(match, std::move(signalPkg), isSameSig, mgr); 140 } 141 } 142 143 void interfacesRemoved(Manager* mgr, const Group& group, 144 TriggerActions& actions, const json&) 145 { 146 // Groups are optional, but a signal triggered event with no groups 147 // will do nothing since signals require a group 148 for (const auto& member : group.getMembers()) 149 { 150 // Setup interfaces removed signal handler on the group member 151 const auto match = rules::interfacesRemoved() + 152 rules::argNpath(0, member); 153 SignalPkg signalPkg = {Handlers::interfacesRemoved, 154 SignalObject(std::cref(member), 155 std::cref(group.getInterface()), 156 std::cref(group.getProperty())), 157 actions}; 158 auto isSameSig = [&intf = group.getInterface()](SignalPkg& pkg) { 159 auto& obj = std::get<SignalObject>(pkg); 160 return intf == std::get<Intf>(obj); 161 }; 162 163 subscribe(match, std::move(signalPkg), isSameSig, mgr); 164 } 165 } 166 167 void nameOwnerChanged(Manager* mgr, const Group& group, TriggerActions& actions, 168 const json&) 169 { 170 std::vector<std::string> grpServices; 171 // Groups are optional, but a signal triggered event with no groups 172 // will do nothing since signals require a group 173 for (const auto& member : group.getMembers()) 174 { 175 auto serv = group.getService(); 176 if (serv.empty()) 177 { 178 serv = Manager::getService(member, group.getInterface()); 179 } 180 if (!serv.empty()) 181 { 182 // No need to re-subscribe to the same service's nameOwnerChanged 183 // signal when a prior group member provided by the same service 184 // already did the subscription 185 if (std::find(grpServices.begin(), grpServices.end(), serv) == 186 grpServices.end()) 187 { 188 // Setup name owner changed signal handler on the group 189 // member's service 190 const auto match = rules::nameOwnerChanged(serv); 191 SignalPkg signalPkg = {Handlers::nameOwnerChanged, 192 SignalObject(), actions}; 193 // If signal match already exists, then the service will be the 194 // same so add action to be run 195 auto isSameSig = [](SignalPkg&) { return true; }; 196 197 subscribe(match, std::move(signalPkg), isSameSig, mgr); 198 grpServices.emplace_back(serv); 199 } 200 } 201 else 202 { 203 // Unable to construct nameOwnerChanged match string 204 // Path and/or interface configured does not exist on dbus yet? 205 // TODO How to handle this? Create timer to keep checking for 206 // service to appear? When to stop checking? 207 log<level::ERR>( 208 std::format("Events will not be triggered by name owner changed" 209 "signals from service of path {}, interface {}", 210 member, group.getInterface()) 211 .c_str()); 212 } 213 } 214 } 215 216 void member(Manager* mgr, const Group& group, TriggerActions& actions, 217 const json&) 218 { 219 // No SignalObject required to associate to this signal 220 SignalPkg signalPkg = {Handlers::member, SignalObject(), actions}; 221 // If signal match already exists, then the member signal will be the 222 // same so add action to be run 223 auto isSameSig = [](SignalPkg&) { return true; }; 224 225 // Groups are optional, but a signal triggered event with no groups 226 // will do nothing since signals require a group 227 for (const auto& member : group.getMembers()) 228 { 229 // Subscribe for signal from each group member 230 const auto match = 231 rules::type::signal() + rules::member(group.getProperty()) + 232 rules::path(member) + rules::interface(group.getInterface()); 233 234 subscribe(match, std::move(signalPkg), isSameSig, mgr); 235 } 236 } 237 238 enableTrigger 239 triggerSignal(const json& jsonObj, const std::string& eventName, 240 std::vector<std::unique_ptr<ActionBase>>& /*actions*/) 241 { 242 auto subscriber = signals.end(); 243 if (jsonObj.contains("signal")) 244 { 245 auto signal = jsonObj["signal"].get<std::string>(); 246 std::transform(signal.begin(), signal.end(), signal.begin(), tolower); 247 subscriber = signals.find(signal); 248 } 249 if (subscriber == signals.end()) 250 { 251 // Construct list of available signals 252 auto availSignals = 253 std::accumulate(std::next(signals.begin()), signals.end(), 254 signals.begin()->first, [](auto list, auto signal) { 255 return std::move(list) + ", " + signal.first; 256 }); 257 auto msg = 258 std::format("Event '{}' requires a supported signal given to be " 259 "triggered by signal, available signals: {}", 260 eventName, availSignals); 261 log<level::ERR>(msg.c_str()); 262 throw std::runtime_error(msg.c_str()); 263 } 264 265 return [subscriber = std::move(subscriber), 266 jsonObj](const std::string& /*eventName*/, Manager* mgr, 267 const std::vector<Group>& groups, 268 std::vector<std::unique_ptr<ActionBase>>& actions) { 269 TriggerActions signalActions; 270 std::for_each(actions.begin(), actions.end(), 271 [&signalActions](auto& action) { 272 signalActions.emplace_back(std::ref(action)); 273 }); 274 for (const auto& group : groups) 275 { 276 // Call signal subscriber for each group 277 subscriber->second(mgr, group, signalActions, jsonObj); 278 } 279 }; 280 } 281 282 } // namespace phosphor::fan::control::json::trigger::signal 283