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::vector<SignalPkg> pkgs = {signalPkg}; 53 std::vector<SignalPkg> dataPkgs = 54 std::vector<SignalPkg>(std::move(pkgs)); 55 std::unique_ptr<sdbusplus::server::match::match> ptrMatch = nullptr; 56 // TODO(ibm-openbmc/#3195) - Filter signal subscriptions to objects 57 // owned by fan control? 58 if (!match.empty()) 59 { 60 // Subscribe to signal 61 ptrMatch = std::make_unique<sdbusplus::server::match::match>( 62 mgr->getBus(), match.c_str(), 63 std::bind(std::mem_fn(&Manager::handleSignal), &(*mgr), 64 std::placeholders::_1, dataPkgs)); 65 } 66 signalData.emplace_back(std::move(dataPkgs), std::move(ptrMatch)); 67 } 68 else 69 { 70 // Signal subscription already exists 71 // Only a single signal data entry tied to each match is supported 72 auto& pkgs = std::get<std::vector<SignalPkg>>(signalData.front()); 73 for (auto& pkg : pkgs) 74 { 75 if (isSameSig(pkg)) 76 { 77 // Same signal expected, add action to be run when signal 78 // received 79 auto& pkgActions = std::get<SignalActions>(signalPkg); 80 auto& actions = std::get<SignalActions>(pkg); 81 // Only one action is given on the signal package passed in 82 actions.push_back(pkgActions.front()); 83 } 84 else 85 { 86 // Expected signal differs, add signal package 87 pkgs.emplace_back(std::move(signalPkg)); 88 } 89 } 90 } 91 } 92 93 void propertiesChanged(Manager* mgr, std::unique_ptr<ActionBase>& action, 94 const json&) 95 { 96 // Groups are optional, but a signal triggered event with no groups 97 // will do nothing since signals require a group 98 for (const auto& group : action->getGroups()) 99 { 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 SignalActions({action})}; 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 121 void interfacesAdded(Manager* mgr, std::unique_ptr<ActionBase>& action, 122 const json&) 123 { 124 // Groups are optional, but a signal triggered event with no groups 125 // will do nothing since signals require a group 126 for (const auto& group : action->getGroups()) 127 { 128 for (const auto& member : group.getMembers()) 129 { 130 // Setup interfaces added signal handler on the group member 131 const auto match = rules::interfacesAdded(member); 132 SignalPkg signalPkg = {Handlers::interfacesAdded, 133 SignalObject(std::cref(member), 134 std::cref(group.getInterface()), 135 std::cref(group.getProperty())), 136 SignalActions({action})}; 137 auto isSameSig = [&intf = group.getInterface()](SignalPkg& pkg) { 138 auto& obj = std::get<SignalObject>(pkg); 139 return intf == std::get<Intf>(obj); 140 }; 141 142 subscribe(match, std::move(signalPkg), isSameSig, mgr); 143 } 144 } 145 } 146 147 void interfacesRemoved(Manager* mgr, std::unique_ptr<ActionBase>& action, 148 const json&) 149 { 150 // Groups are optional, but a signal triggered event with no groups 151 // will do nothing since signals require a group 152 for (const auto& group : action->getGroups()) 153 { 154 for (const auto& member : group.getMembers()) 155 { 156 // Setup interfaces added signal handler on the group member 157 const auto match = rules::interfacesRemoved(member); 158 SignalPkg signalPkg = {Handlers::interfacesRemoved, 159 SignalObject(std::cref(member), 160 std::cref(group.getInterface()), 161 std::cref(group.getProperty())), 162 SignalActions({action})}; 163 auto isSameSig = [&intf = group.getInterface()](SignalPkg& pkg) { 164 auto& obj = std::get<SignalObject>(pkg); 165 return intf == std::get<Intf>(obj); 166 }; 167 168 subscribe(match, std::move(signalPkg), isSameSig, mgr); 169 } 170 } 171 } 172 173 void nameOwnerChanged(Manager* mgr, std::unique_ptr<ActionBase>& action, 174 const json&) 175 { 176 // Groups are optional, but a signal triggered event with no groups 177 // will do nothing since signals require a group 178 for (const auto& group : action->getGroups()) 179 { 180 for (const auto& member : group.getMembers()) 181 { 182 auto serv = Manager::getService(member, group.getInterface()); 183 if (!serv.empty()) 184 { 185 // Setup name owner changed signal handler on the group 186 // member's service 187 const auto match = rules::nameOwnerChanged(serv); 188 SignalPkg signalPkg = { 189 Handlers::nameOwnerChanged, 190 SignalObject(std::cref(member), 191 std::cref(group.getInterface()), 192 std::cref(group.getProperty())), 193 SignalActions({action})}; 194 // If signal match already exists, then the service will be the 195 // same so add action to be run 196 auto isSameSig = [](SignalPkg& pkg) { return true; }; 197 198 subscribe(match, std::move(signalPkg), isSameSig, mgr); 199 } 200 else 201 { 202 // Unable to construct nameOwnerChanged match string 203 // Path and/or interface configured does not exist on dbus yet? 204 // TODO How to handle this? Create timer to keep checking for 205 // service to appear? When to stop checking? 206 log<level::ERR>( 207 fmt::format( 208 "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 217 void member(Manager* mgr, std::unique_ptr<ActionBase>& action, 218 const json& jsonObj) 219 { 220 if (!jsonObj.contains("member") || !jsonObj["member"].contains("name") || 221 !jsonObj["member"].contains("path") || 222 !jsonObj["member"].contains("interface")) 223 { 224 log<level::ERR>("Missing required member trigger attributes", 225 entry("JSON=%s", jsonObj.dump().c_str())); 226 throw std::runtime_error("Missing required member trigger attributes"); 227 } 228 const auto match = 229 rules::type::signal() + 230 rules::member(jsonObj["member"]["name"].get<std::string>()) + 231 rules::path(jsonObj["member"]["path"].get<std::string>()) + 232 rules::interface(jsonObj["member"]["interface"].get<std::string>()); 233 // No SignalObject required to associate to this signal 234 SignalPkg signalPkg = {Handlers::member, SignalObject(), 235 SignalActions({action})}; 236 // If signal match already exists, then the member signal will be the 237 // same so add action to be run 238 auto isSameSig = [](SignalPkg& pkg) { return true; }; 239 subscribe(match, std::move(signalPkg), isSameSig, mgr); 240 } 241 242 enableTrigger triggerSignal(const json& jsonObj, const std::string& eventName, 243 std::vector<std::unique_ptr<ActionBase>>& actions) 244 { 245 auto subscriber = signals.end(); 246 if (jsonObj.contains("signal")) 247 { 248 auto signal = jsonObj["signal"].get<std::string>(); 249 std::transform(signal.begin(), signal.end(), signal.begin(), tolower); 250 subscriber = signals.find(signal); 251 } 252 if (subscriber == signals.end()) 253 { 254 // Construct list of available signals 255 auto availSignals = 256 std::accumulate(std::next(signals.begin()), signals.end(), 257 signals.begin()->first, [](auto list, auto signal) { 258 return std::move(list) + ", " + signal.first; 259 }); 260 auto msg = 261 fmt::format("Event '{}' requires a supported signal given to be " 262 "triggered by signal, available signals: {}", 263 eventName, availSignals); 264 log<level::ERR>(msg.c_str()); 265 throw std::runtime_error(msg.c_str()); 266 } 267 268 return [subscriber = std::move(subscriber), 269 jsonObj](const std::string& eventName, Manager* mgr, 270 std::vector<std::unique_ptr<ActionBase>>& actions) { 271 for (auto& action : actions) 272 { 273 // Call signal subscriber for each group in the action 274 subscriber->second(mgr, action, jsonObj); 275 } 276 }; 277 } 278 279 } // namespace phosphor::fan::control::json::trigger::signal 280