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 = rules::propertiesChanged(member, 105 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 = rules::interfacesAdded() + 129 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 removed signal handler on the group member 152 const auto match = rules::interfacesRemoved() + 153 rules::argNpath(0, member); 154 SignalPkg signalPkg = {Handlers::interfacesRemoved, 155 SignalObject(std::cref(member), 156 std::cref(group.getInterface()), 157 std::cref(group.getProperty())), 158 actions}; 159 auto isSameSig = [&intf = group.getInterface()](SignalPkg& pkg) { 160 auto& obj = std::get<SignalObject>(pkg); 161 return intf == std::get<Intf>(obj); 162 }; 163 164 subscribe(match, std::move(signalPkg), isSameSig, mgr); 165 } 166 } 167 168 void nameOwnerChanged(Manager* mgr, const Group& group, TriggerActions& actions, 169 const json&) 170 { 171 std::vector<std::string> grpServices; 172 // Groups are optional, but a signal triggered event with no groups 173 // will do nothing since signals require a group 174 for (const auto& member : group.getMembers()) 175 { 176 auto serv = group.getService(); 177 if (serv.empty()) 178 { 179 serv = Manager::getService(member, group.getInterface()); 180 } 181 if (!serv.empty()) 182 { 183 // No need to re-subscribe to the same service's nameOwnerChanged 184 // signal when a prior group member provided by the same service 185 // already did the subscription 186 if (std::find(grpServices.begin(), grpServices.end(), serv) == 187 grpServices.end()) 188 { 189 // Setup name owner changed signal handler on the group 190 // member's service 191 const auto match = rules::nameOwnerChanged(serv); 192 SignalPkg signalPkg = {Handlers::nameOwnerChanged, 193 SignalObject(), actions}; 194 // If signal match already exists, then the service will be the 195 // same so add action to be run 196 auto isSameSig = [](SignalPkg&) { return true; }; 197 198 subscribe(match, std::move(signalPkg), isSameSig, mgr); 199 grpServices.emplace_back(serv); 200 } 201 } 202 else 203 { 204 // Unable to construct nameOwnerChanged match string 205 // Path and/or interface configured does not exist on dbus yet? 206 // TODO How to handle this? Create timer to keep checking for 207 // service to appear? When to stop checking? 208 log<level::ERR>( 209 fmt::format("Events will not be triggered by name owner changed" 210 "signals from service of path {}, interface {}", 211 member, group.getInterface()) 212 .c_str()); 213 } 214 } 215 } 216 217 void member(Manager* mgr, const Group& group, TriggerActions& actions, 218 const json&) 219 { 220 // No SignalObject required to associate to this signal 221 SignalPkg signalPkg = {Handlers::member, SignalObject(), actions}; 222 // If signal match already exists, then the member signal will be the 223 // same so add action to be run 224 auto isSameSig = [](SignalPkg&) { return true; }; 225 226 // Groups are optional, but a signal triggered event with no groups 227 // will do nothing since signals require a group 228 for (const auto& member : group.getMembers()) 229 { 230 // Subscribe for signal from each group member 231 const auto match = 232 rules::type::signal() + rules::member(group.getProperty()) + 233 rules::path(member) + rules::interface(group.getInterface()); 234 235 subscribe(match, std::move(signalPkg), isSameSig, mgr); 236 } 237 } 238 239 enableTrigger 240 triggerSignal(const json& jsonObj, const std::string& eventName, 241 std::vector<std::unique_ptr<ActionBase>>& /*actions*/) 242 { 243 auto subscriber = signals.end(); 244 if (jsonObj.contains("signal")) 245 { 246 auto signal = jsonObj["signal"].get<std::string>(); 247 std::transform(signal.begin(), signal.end(), signal.begin(), tolower); 248 subscriber = signals.find(signal); 249 } 250 if (subscriber == signals.end()) 251 { 252 // Construct list of available signals 253 auto availSignals = std::accumulate( 254 std::next(signals.begin()), signals.end(), signals.begin()->first, 255 [](auto list, auto signal) { 256 return std::move(list) + ", " + signal.first; 257 }); 258 auto msg = 259 fmt::format("Event '{}' requires a supported signal given to be " 260 "triggered by signal, available signals: {}", 261 eventName, availSignals); 262 log<level::ERR>(msg.c_str()); 263 throw std::runtime_error(msg.c_str()); 264 } 265 266 return [subscriber = std::move(subscriber), 267 jsonObj](const std::string& /*eventName*/, Manager* mgr, 268 const std::vector<Group>& groups, 269 std::vector<std::unique_ptr<ActionBase>>& actions) { 270 TriggerActions signalActions; 271 std::for_each(actions.begin(), actions.end(), 272 [&signalActions](auto& action) { 273 signalActions.emplace_back(std::ref(action)); 274 }); 275 for (const auto& group : groups) 276 { 277 // Call signal subscriber for each group 278 subscriber->second(mgr, group, signalActions, jsonObj); 279 } 280 }; 281 } 282 283 } // namespace phosphor::fan::control::json::trigger::signal 284