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
subscribe(const std::string & match,SignalPkg && signalPkg,std::function<bool (SignalPkg &)> isSameSig,Manager * mgr)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
propertiesChanged(Manager * mgr,const Group & group,TriggerActions & actions,const json &)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 =
104 rules::propertiesChanged(member, group.getInterface());
105 SignalPkg signalPkg = {
106 Handlers::propertiesChanged,
107 SignalObject(std::cref(member), 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
interfacesAdded(Manager * mgr,const Group & group,TriggerActions & actions,const json &)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 =
128 rules::interfacesAdded() + rules::argNpath(0, member);
129 SignalPkg signalPkg = {
130 Handlers::interfacesAdded,
131 SignalObject(std::cref(member), 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
interfacesRemoved(Manager * mgr,const Group & group,TriggerActions & actions,const json &)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 =
152 rules::interfacesRemoved() + rules::argNpath(0, member);
153 SignalPkg signalPkg = {
154 Handlers::interfacesRemoved,
155 SignalObject(std::cref(member), 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
nameOwnerChanged(Manager * mgr,const Group & group,TriggerActions & actions,const json &)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
member(Manager * mgr,const Group & group,TriggerActions & actions,const json &)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
triggerSignal(const json & jsonObj,const std::string & eventName,std::vector<std::unique_ptr<ActionBase>> &)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