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