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