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 <fmt/format.h> 25 26 #include <nlohmann/json.hpp> 27 #include <phosphor-logging/log.hpp> 28 #include <sdbusplus/bus/match.hpp> 29 30 #include <algorithm> 31 #include <functional> 32 #include <iterator> 33 #include <memory> 34 #include <numeric> 35 #include <utility> 36 #include <vector> 37 38 namespace phosphor::fan::control::json::trigger::signal 39 { 40 41 using json = nlohmann::json; 42 using namespace phosphor::logging; 43 using namespace sdbusplus::bus::match; 44 45 void subscribe(const std::string& match, SignalPkg&& signalPkg, 46 std::function<bool(SignalPkg&)> isSameSig, Manager* mgr) 47 { 48 auto& signalData = mgr->getSignal(match); 49 if (signalData.empty()) 50 { 51 // Signal subscription doesnt exist, add signal package and subscribe 52 std::unique_ptr<std::vector<SignalPkg>> pkgs = 53 std::make_unique<std::vector<SignalPkg>>(); 54 pkgs->emplace_back(std::move(signalPkg)); 55 std::unique_ptr<sdbusplus::bus::match_t> ptrMatch = nullptr; 56 if (!match.empty()) 57 { 58 // Subscribe to signal 59 ptrMatch = std::make_unique<sdbusplus::bus::match_t>( 60 mgr->getBus(), match.c_str(), 61 std::bind(std::mem_fn(&Manager::handleSignal), &(*mgr), 62 std::placeholders::_1, pkgs.get())); 63 } 64 signalData.emplace_back(std::move(pkgs), std::move(ptrMatch)); 65 } 66 else 67 { 68 // Signal subscription already exists 69 // Only a single signal data entry tied to each match is supported 70 auto& pkgs = std::get<std::unique_ptr<std::vector<SignalPkg>>>( 71 signalData.front()); 72 auto sameSignal = false; 73 for (auto& pkg : *pkgs) 74 { 75 if (isSameSig(pkg)) 76 { 77 // Same SignalObject signal to trigger event actions, 78 // add actions to be run when signal for SignalObject received 79 auto& pkgActions = std::get<TriggerActions>(signalPkg); 80 auto& actions = std::get<TriggerActions>(pkg); 81 actions.insert(actions.end(), pkgActions.begin(), 82 pkgActions.end()); 83 sameSignal = true; 84 break; 85 } 86 } 87 if (!sameSignal) 88 { 89 // Expected signal differs, add signal package 90 pkgs->emplace_back(std::move(signalPkg)); 91 } 92 } 93 } 94 95 void propertiesChanged(Manager* mgr, const Group& group, 96 TriggerActions& actions, const json&) 97 { 98 // Groups are optional, but a signal triggered event with no groups 99 // will do nothing since signals require a group 100 for (const auto& member : group.getMembers()) 101 { 102 // Setup property changed signal handler on the group member's 103 // property 104 const auto match = 105 rules::propertiesChanged(member, group.getInterface()); 106 SignalPkg signalPkg = {Handlers::propertiesChanged, 107 SignalObject(std::cref(member), 108 std::cref(group.getInterface()), 109 std::cref(group.getProperty())), 110 actions}; 111 auto isSameSig = [&prop = group.getProperty()](SignalPkg& pkg) { 112 auto& obj = std::get<SignalObject>(pkg); 113 return prop == std::get<Prop>(obj); 114 }; 115 116 subscribe(match, std::move(signalPkg), isSameSig, mgr); 117 } 118 } 119 120 void interfacesAdded(Manager* mgr, const Group& group, TriggerActions& actions, 121 const json&) 122 { 123 // Groups are optional, but a signal triggered event with no groups 124 // will do nothing since signals require a group 125 for (const auto& member : group.getMembers()) 126 { 127 // Setup interfaces added signal handler on the group member 128 const auto match = 129 rules::interfacesAdded() + rules::argNpath(0, member); 130 SignalPkg signalPkg = {Handlers::interfacesAdded, 131 SignalObject(std::cref(member), 132 std::cref(group.getInterface()), 133 std::cref(group.getProperty())), 134 actions}; 135 auto isSameSig = [&intf = group.getInterface()](SignalPkg& pkg) { 136 auto& obj = std::get<SignalObject>(pkg); 137 return intf == std::get<Intf>(obj); 138 }; 139 140 subscribe(match, std::move(signalPkg), isSameSig, mgr); 141 } 142 } 143 144 void interfacesRemoved(Manager* mgr, const Group& group, 145 TriggerActions& actions, const json&) 146 { 147 // Groups are optional, but a signal triggered event with no groups 148 // will do nothing since signals require a group 149 for (const auto& member : group.getMembers()) 150 { 151 // Setup interfaces added signal handler on the group member 152 const auto match = rules::interfacesRemoved(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& pkg) { 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 fmt::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& pkg) { 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 triggerSignal(const json& jsonObj, const std::string& eventName, 239 std::vector<std::unique_ptr<ActionBase>>& actions) 240 { 241 auto subscriber = signals.end(); 242 if (jsonObj.contains("signal")) 243 { 244 auto signal = jsonObj["signal"].get<std::string>(); 245 std::transform(signal.begin(), signal.end(), signal.begin(), tolower); 246 subscriber = signals.find(signal); 247 } 248 if (subscriber == signals.end()) 249 { 250 // Construct list of available signals 251 auto availSignals = 252 std::accumulate(std::next(signals.begin()), signals.end(), 253 signals.begin()->first, [](auto list, auto signal) { 254 return std::move(list) + ", " + signal.first; 255 }); 256 auto msg = 257 fmt::format("Event '{}' requires a supported signal given to be " 258 "triggered by signal, available signals: {}", 259 eventName, availSignals); 260 log<level::ERR>(msg.c_str()); 261 throw std::runtime_error(msg.c_str()); 262 } 263 264 return [subscriber = std::move(subscriber), 265 jsonObj](const std::string& eventName, Manager* mgr, 266 const std::vector<Group>& groups, 267 std::vector<std::unique_ptr<ActionBase>>& actions) { 268 TriggerActions signalActions; 269 std::for_each(actions.begin(), actions.end(), 270 [&signalActions](auto& action) { 271 signalActions.emplace_back(std::ref(action)); 272 }); 273 for (const auto& group : groups) 274 { 275 // Call signal subscriber for each group 276 subscriber->second(mgr, group, signalActions, jsonObj); 277 } 278 }; 279 } 280 281 } // namespace phosphor::fan::control::json::trigger::signal 282