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 = {
192                     Handlers::nameOwnerChanged,
193                     SignalObject(std::cref(member),
194                                  std::cref(group.getInterface()),
195                                  std::cref(group.getProperty())),
196                     actions};
197                 // If signal match already exists, then the service will be the
198                 // same so add action to be run
199                 auto isSameSig = [](SignalPkg& pkg) { return true; };
200 
201                 subscribe(match, std::move(signalPkg), isSameSig, mgr);
202                 grpServices.emplace_back(serv);
203             }
204         }
205         else
206         {
207             // Unable to construct nameOwnerChanged match string
208             // Path and/or interface configured does not exist on dbus yet?
209             // TODO How to handle this? Create timer to keep checking for
210             // service to appear? When to stop checking?
211             log<level::ERR>(
212                 fmt::format("Events will not be triggered by name owner changed"
213                             "signals from service of path {}, interface {}",
214                             member, group.getInterface())
215                     .c_str());
216         }
217     }
218 }
219 
220 void member(Manager* mgr, const Group& group, TriggerActions& actions,
221             const json&)
222 {
223     // No SignalObject required to associate to this signal
224     SignalPkg signalPkg = {Handlers::member, SignalObject(), actions};
225     // If signal match already exists, then the member signal will be the
226     // same so add action to be run
227     auto isSameSig = [](SignalPkg& pkg) { return true; };
228 
229     // Groups are optional, but a signal triggered event with no groups
230     // will do nothing since signals require a group
231     for (const auto& member : group.getMembers())
232     {
233         // Subscribe for signal from each group member
234         const auto match =
235             rules::type::signal() + rules::member(group.getProperty()) +
236             rules::path(member) + rules::interface(group.getInterface());
237 
238         subscribe(match, std::move(signalPkg), isSameSig, mgr);
239     }
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                      const std::vector<Group>& groups,
271                      std::vector<std::unique_ptr<ActionBase>>& actions) {
272         TriggerActions signalActions;
273         std::for_each(actions.begin(), actions.end(),
274                       [&signalActions](auto& action) {
275                           signalActions.emplace_back(std::ref(action));
276                       });
277         for (const auto& group : groups)
278         {
279             // Call signal subscriber for each group
280             subscriber->second(mgr, group, signalActions, jsonObj);
281         }
282     };
283 }
284 
285 } // namespace phosphor::fan::control::json::trigger::signal
286