xref: /openbmc/entity-manager/src/entity_manager/perform_scan.cpp (revision cf6a75bd2bbfff92092388efeb855ecf25ef2256)
1fc9e7fdaSChristopher Meis /*
2fc9e7fdaSChristopher Meis // Copyright (c) 2018 Intel Corporation
3fc9e7fdaSChristopher Meis //
4fc9e7fdaSChristopher Meis // Licensed under the Apache License, Version 2.0 (the "License");
5fc9e7fdaSChristopher Meis // you may not use this file except in compliance with the License.
6fc9e7fdaSChristopher Meis // You may obtain a copy of the License at
7fc9e7fdaSChristopher Meis //
8fc9e7fdaSChristopher Meis //      http://www.apache.org/licenses/LICENSE-2.0
9fc9e7fdaSChristopher Meis //
10fc9e7fdaSChristopher Meis // Unless required by applicable law or agreed to in writing, software
11fc9e7fdaSChristopher Meis // distributed under the License is distributed on an "AS IS" BASIS,
12fc9e7fdaSChristopher Meis // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13fc9e7fdaSChristopher Meis // See the License for the specific language governing permissions and
14fc9e7fdaSChristopher Meis // limitations under the License.
15fc9e7fdaSChristopher Meis */
16fc9e7fdaSChristopher Meis /// \file perform_scan.cpp
17fc9e7fdaSChristopher Meis #include "perform_scan.hpp"
18fc9e7fdaSChristopher Meis 
19fc9e7fdaSChristopher Meis #include "perform_probe.hpp"
2059ef1e72SChristopher Meis #include "utils.hpp"
21fc9e7fdaSChristopher Meis 
22fc9e7fdaSChristopher Meis #include <boost/algorithm/string/predicate.hpp>
23fc9e7fdaSChristopher Meis #include <boost/asio/steady_timer.hpp>
24fc9e7fdaSChristopher Meis #include <boost/container/flat_map.hpp>
25fc9e7fdaSChristopher Meis #include <boost/container/flat_set.hpp>
26fc9e7fdaSChristopher Meis #include <phosphor-logging/lg2.hpp>
27fc9e7fdaSChristopher Meis 
28fc9e7fdaSChristopher Meis #include <charconv>
2959ef1e72SChristopher Meis #include <iostream>
30fc9e7fdaSChristopher Meis 
31fc9e7fdaSChristopher Meis using GetSubTreeType = std::vector<
32fc9e7fdaSChristopher Meis     std::pair<std::string,
33fc9e7fdaSChristopher Meis               std::vector<std::pair<std::string, std::vector<std::string>>>>>;
34fc9e7fdaSChristopher Meis 
35fc9e7fdaSChristopher Meis constexpr const int32_t maxMapperDepth = 0;
36fc9e7fdaSChristopher Meis 
37fc9e7fdaSChristopher Meis struct DBusInterfaceInstance
38fc9e7fdaSChristopher Meis {
39fc9e7fdaSChristopher Meis     std::string busName;
40fc9e7fdaSChristopher Meis     std::string path;
41fc9e7fdaSChristopher Meis     std::string interface;
42fc9e7fdaSChristopher Meis };
43fc9e7fdaSChristopher Meis 
44fc9e7fdaSChristopher Meis void getInterfaces(
45fc9e7fdaSChristopher Meis     const DBusInterfaceInstance& instance,
46fc9e7fdaSChristopher Meis     const std::vector<std::shared_ptr<probe::PerformProbe>>& probeVector,
47fc9e7fdaSChristopher Meis     const std::shared_ptr<scan::PerformScan>& scan, size_t retries = 5)
48fc9e7fdaSChristopher Meis {
49fc9e7fdaSChristopher Meis     if (retries == 0U)
50fc9e7fdaSChristopher Meis     {
51fc9e7fdaSChristopher Meis         std::cerr << "retries exhausted on " << instance.busName << " "
52fc9e7fdaSChristopher Meis                   << instance.path << " " << instance.interface << "\n";
getInterfaces(const DBusInterfaceInstance & instance,const std::vector<std::shared_ptr<probe::PerformProbe>> & probeVector,const std::shared_ptr<scan::PerformScan> & scan,size_t retries=5)53fc9e7fdaSChristopher Meis         return;
54fc9e7fdaSChristopher Meis     }
55fc9e7fdaSChristopher Meis 
56*cf6a75bdSChristopher Meis     scan->_em.systemBus->async_method_call(
57fc9e7fdaSChristopher Meis         [instance, scan, probeVector,
58fc9e7fdaSChristopher Meis          retries](boost::system::error_code& errc, const DBusInterface& resp) {
59fc9e7fdaSChristopher Meis             if (errc)
60fc9e7fdaSChristopher Meis             {
61fc9e7fdaSChristopher Meis                 std::cerr << "error calling getall on  " << instance.busName
62fc9e7fdaSChristopher Meis                           << " " << instance.path << " "
63fc9e7fdaSChristopher Meis                           << instance.interface << "\n";
64fc9e7fdaSChristopher Meis 
65fc9e7fdaSChristopher Meis                 auto timer = std::make_shared<boost::asio::steady_timer>(io);
66fc9e7fdaSChristopher Meis                 timer->expires_after(std::chrono::seconds(2));
__anonb841ee130102(boost::system::error_code& errc, const DBusInterface& resp) 67fc9e7fdaSChristopher Meis 
68fc9e7fdaSChristopher Meis                 timer->async_wait([timer, instance, scan, probeVector,
69fc9e7fdaSChristopher Meis                                    retries](const boost::system::error_code&) {
70fc9e7fdaSChristopher Meis                     getInterfaces(instance, probeVector, scan, retries - 1);
71fc9e7fdaSChristopher Meis                 });
72fc9e7fdaSChristopher Meis                 return;
73fc9e7fdaSChristopher Meis             }
74fc9e7fdaSChristopher Meis 
75fc9e7fdaSChristopher Meis             scan->dbusProbeObjects[instance.path][instance.interface] = resp;
76fc9e7fdaSChristopher Meis         },
77fc9e7fdaSChristopher Meis         instance.busName, instance.path, "org.freedesktop.DBus.Properties",
__anonb841ee130202(const boost::system::error_code&) 78fc9e7fdaSChristopher Meis         "GetAll", instance.interface);
79fc9e7fdaSChristopher Meis }
80fc9e7fdaSChristopher Meis 
81fc9e7fdaSChristopher Meis static void processDbusObjects(
82fc9e7fdaSChristopher Meis     std::vector<std::shared_ptr<probe::PerformProbe>>& probeVector,
83fc9e7fdaSChristopher Meis     const std::shared_ptr<scan::PerformScan>& scan,
84fc9e7fdaSChristopher Meis     const GetSubTreeType& interfaceSubtree)
85fc9e7fdaSChristopher Meis {
86fc9e7fdaSChristopher Meis     for (const auto& [path, object] : interfaceSubtree)
87fc9e7fdaSChristopher Meis     {
88fc9e7fdaSChristopher Meis         // Get a PropertiesChanged callback for all interfaces on this path.
89*cf6a75bdSChristopher Meis         scan->_em.registerCallback(path);
registerCallback(nlohmann::json & systemConfiguration,sdbusplus::asio::object_server & objServer,const std::string & path)90fc9e7fdaSChristopher Meis 
91fc9e7fdaSChristopher Meis         for (const auto& [busname, ifaces] : object)
92fc9e7fdaSChristopher Meis         {
93fc9e7fdaSChristopher Meis             for (const std::string& iface : ifaces)
94fc9e7fdaSChristopher Meis             {
95fc9e7fdaSChristopher Meis                 // The 3 default org.freedeskstop interfaces (Peer,
96fc9e7fdaSChristopher Meis                 // Introspectable, and Properties) are returned by
97fc9e7fdaSChristopher Meis                 // the mapper but don't have properties, so don't bother
98fc9e7fdaSChristopher Meis                 // with the GetAll call to save some cycles.
99fc9e7fdaSChristopher Meis                 if (!boost::algorithm::starts_with(iface, "org.freedesktop"))
100fc9e7fdaSChristopher Meis                 {
101fc9e7fdaSChristopher Meis                     getInterfaces({busname, path, iface}, probeVector, scan);
102fc9e7fdaSChristopher Meis                 }
103fc9e7fdaSChristopher Meis             }
104fc9e7fdaSChristopher Meis         }
105fc9e7fdaSChristopher Meis     }
106fc9e7fdaSChristopher Meis }
107fc9e7fdaSChristopher Meis 
108fc9e7fdaSChristopher Meis // Populates scan->dbusProbeObjects with all interfaces and properties
109fc9e7fdaSChristopher Meis // for the paths that own the interfaces passed in.
110fc9e7fdaSChristopher Meis void findDbusObjects(
111fc9e7fdaSChristopher Meis     std::vector<std::shared_ptr<probe::PerformProbe>>&& probeVector,
112fc9e7fdaSChristopher Meis     boost::container::flat_set<std::string>&& interfaces,
113fc9e7fdaSChristopher Meis     const std::shared_ptr<scan::PerformScan>& scan, size_t retries = 5)
114fc9e7fdaSChristopher Meis {
processDbusObjects(std::vector<std::shared_ptr<probe::PerformProbe>> & probeVector,const std::shared_ptr<scan::PerformScan> & scan,const GetSubTreeType & interfaceSubtree)115fc9e7fdaSChristopher Meis     // Filter out interfaces already obtained.
116fc9e7fdaSChristopher Meis     for (const auto& [path, probeInterfaces] : scan->dbusProbeObjects)
117fc9e7fdaSChristopher Meis     {
118fc9e7fdaSChristopher Meis         for (const auto& [interface, _] : probeInterfaces)
119fc9e7fdaSChristopher Meis         {
120fc9e7fdaSChristopher Meis             interfaces.erase(interface);
121fc9e7fdaSChristopher Meis         }
122fc9e7fdaSChristopher Meis     }
123fc9e7fdaSChristopher Meis     if (interfaces.empty())
124fc9e7fdaSChristopher Meis     {
125fc9e7fdaSChristopher Meis         return;
126fc9e7fdaSChristopher Meis     }
127fc9e7fdaSChristopher Meis 
128fc9e7fdaSChristopher Meis     // find all connections in the mapper that expose a specific type
129*cf6a75bdSChristopher Meis     scan->_em.systemBus->async_method_call(
130fc9e7fdaSChristopher Meis         [interfaces, probeVector{std::move(probeVector)}, scan,
131fc9e7fdaSChristopher Meis          retries](boost::system::error_code& ec,
132fc9e7fdaSChristopher Meis                   const GetSubTreeType& interfaceSubtree) mutable {
133fc9e7fdaSChristopher Meis             if (ec)
134fc9e7fdaSChristopher Meis             {
135fc9e7fdaSChristopher Meis                 if (ec.value() == ENOENT)
136fc9e7fdaSChristopher Meis                 {
137fc9e7fdaSChristopher Meis                     return; // wasn't found by mapper
138fc9e7fdaSChristopher Meis                 }
139fc9e7fdaSChristopher Meis                 std::cerr << "Error communicating to mapper.\n";
140fc9e7fdaSChristopher Meis 
141fc9e7fdaSChristopher Meis                 if (retries == 0U)
142fc9e7fdaSChristopher Meis                 {
143fc9e7fdaSChristopher Meis                     // if we can't communicate to the mapper something is very
findDbusObjects(std::vector<std::shared_ptr<probe::PerformProbe>> && probeVector,boost::container::flat_set<std::string> && interfaces,const std::shared_ptr<scan::PerformScan> & scan,size_t retries=5)144fc9e7fdaSChristopher Meis                     // wrong
145fc9e7fdaSChristopher Meis                     std::exit(EXIT_FAILURE);
146fc9e7fdaSChristopher Meis                 }
147fc9e7fdaSChristopher Meis 
148fc9e7fdaSChristopher Meis                 auto timer = std::make_shared<boost::asio::steady_timer>(io);
149fc9e7fdaSChristopher Meis                 timer->expires_after(std::chrono::seconds(10));
150fc9e7fdaSChristopher Meis 
151fc9e7fdaSChristopher Meis                 timer->async_wait(
152fc9e7fdaSChristopher Meis                     [timer, interfaces{std::move(interfaces)}, scan,
153fc9e7fdaSChristopher Meis                      probeVector{std::move(probeVector)},
154fc9e7fdaSChristopher Meis                      retries](const boost::system::error_code&) mutable {
155fc9e7fdaSChristopher Meis                         findDbusObjects(std::move(probeVector),
156fc9e7fdaSChristopher Meis                                         std::move(interfaces), scan,
157fc9e7fdaSChristopher Meis                                         retries - 1);
158fc9e7fdaSChristopher Meis                     });
159fc9e7fdaSChristopher Meis                 return;
160fc9e7fdaSChristopher Meis             }
161fc9e7fdaSChristopher Meis 
162fc9e7fdaSChristopher Meis             processDbusObjects(probeVector, scan, interfaceSubtree);
163fc9e7fdaSChristopher Meis         },
164fc9e7fdaSChristopher Meis         "xyz.openbmc_project.ObjectMapper",
165fc9e7fdaSChristopher Meis         "/xyz/openbmc_project/object_mapper",
__anonb841ee130402(boost::system::error_code& ec, const GetSubTreeType& interfaceSubtree) 166fc9e7fdaSChristopher Meis         "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", maxMapperDepth,
167fc9e7fdaSChristopher Meis         interfaces);
168fc9e7fdaSChristopher Meis }
169fc9e7fdaSChristopher Meis 
170fc9e7fdaSChristopher Meis static std::string getRecordName(const DBusInterface& probe,
171fc9e7fdaSChristopher Meis                                  const std::string& probeName)
172fc9e7fdaSChristopher Meis {
173fc9e7fdaSChristopher Meis     if (probe.empty())
174fc9e7fdaSChristopher Meis     {
175fc9e7fdaSChristopher Meis         return probeName;
176fc9e7fdaSChristopher Meis     }
177fc9e7fdaSChristopher Meis 
178fc9e7fdaSChristopher Meis     // use an array so alphabetical order from the flat_map is maintained
179fc9e7fdaSChristopher Meis     auto device = nlohmann::json::array();
180fc9e7fdaSChristopher Meis     for (const auto& devPair : probe)
181fc9e7fdaSChristopher Meis     {
182fc9e7fdaSChristopher Meis         device.push_back(devPair.first);
183fc9e7fdaSChristopher Meis         std::visit([&device](auto&& v) { device.push_back(v); },
184fc9e7fdaSChristopher Meis                    devPair.second);
185fc9e7fdaSChristopher Meis     }
186fc9e7fdaSChristopher Meis 
187fc9e7fdaSChristopher Meis     // hashes are hard to distinguish, use the non-hashed version if we want
__anonb841ee130502(const boost::system::error_code&) 188fc9e7fdaSChristopher Meis     // debug
189fc9e7fdaSChristopher Meis     // return probeName + device.dump();
190fc9e7fdaSChristopher Meis 
191fc9e7fdaSChristopher Meis     return std::to_string(std::hash<std::string>{}(probeName + device.dump()));
192fc9e7fdaSChristopher Meis }
193fc9e7fdaSChristopher Meis 
194*cf6a75bdSChristopher Meis scan::PerformScan::PerformScan(EntityManager& em,
195fc9e7fdaSChristopher Meis                                nlohmann::json& missingConfigurations,
196fc9e7fdaSChristopher Meis                                std::list<nlohmann::json>& configurations,
197fc9e7fdaSChristopher Meis                                std::function<void()>&& callback) :
198*cf6a75bdSChristopher Meis     _em(em), _missingConfigurations(missingConfigurations),
199*cf6a75bdSChristopher Meis     _configurations(configurations), _callback(std::move(callback))
200fc9e7fdaSChristopher Meis {}
201fc9e7fdaSChristopher Meis 
202fc9e7fdaSChristopher Meis static void pruneRecordExposes(nlohmann::json& record)
203fc9e7fdaSChristopher Meis {
getRecordName(const DBusInterface & probe,const std::string & probeName)204fc9e7fdaSChristopher Meis     auto findExposes = record.find("Exposes");
205fc9e7fdaSChristopher Meis     if (findExposes == record.end())
206fc9e7fdaSChristopher Meis     {
207fc9e7fdaSChristopher Meis         return;
208fc9e7fdaSChristopher Meis     }
209fc9e7fdaSChristopher Meis 
210fc9e7fdaSChristopher Meis     auto copy = nlohmann::json::array();
211fc9e7fdaSChristopher Meis     for (auto& expose : *findExposes)
212fc9e7fdaSChristopher Meis     {
213fc9e7fdaSChristopher Meis         if (!expose.is_null())
214fc9e7fdaSChristopher Meis         {
215fc9e7fdaSChristopher Meis             copy.emplace_back(expose);
216fc9e7fdaSChristopher Meis         }
__anonb841ee130602(auto&& v) 217fc9e7fdaSChristopher Meis     }
218fc9e7fdaSChristopher Meis     *findExposes = copy;
219fc9e7fdaSChristopher Meis }
220fc9e7fdaSChristopher Meis 
221fc9e7fdaSChristopher Meis static void recordDiscoveredIdentifiers(
222fc9e7fdaSChristopher Meis     std::set<nlohmann::json>& usedNames, std::list<size_t>& indexes,
223fc9e7fdaSChristopher Meis     const std::string& probeName, const nlohmann::json& record)
224fc9e7fdaSChristopher Meis {
225fc9e7fdaSChristopher Meis     size_t indexIdx = probeName.find('$');
226fc9e7fdaSChristopher Meis     if (indexIdx == std::string::npos)
227fc9e7fdaSChristopher Meis     {
PerformScan(nlohmann::json & systemConfiguration,nlohmann::json & missingConfigurations,std::list<nlohmann::json> & configurations,sdbusplus::asio::object_server & objServerIn,std::function<void ()> && callback)228fc9e7fdaSChristopher Meis         return;
229fc9e7fdaSChristopher Meis     }
230fc9e7fdaSChristopher Meis 
231fc9e7fdaSChristopher Meis     auto nameIt = record.find("Name");
232fc9e7fdaSChristopher Meis     if (nameIt == record.end())
233fc9e7fdaSChristopher Meis     {
234fc9e7fdaSChristopher Meis         std::cerr << "Last JSON Illegal\n";
235fc9e7fdaSChristopher Meis         return;
236fc9e7fdaSChristopher Meis     }
237fc9e7fdaSChristopher Meis 
238fc9e7fdaSChristopher Meis     int index = 0;
pruneRecordExposes(nlohmann::json & record)239fc9e7fdaSChristopher Meis     auto str = nameIt->get<std::string>().substr(indexIdx);
240fc9e7fdaSChristopher Meis     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
241fc9e7fdaSChristopher Meis     const char* endPtr = str.data() + str.size();
242fc9e7fdaSChristopher Meis     auto [p, ec] = std::from_chars(str.data(), endPtr, index);
243fc9e7fdaSChristopher Meis     if (ec != std::errc())
244fc9e7fdaSChristopher Meis     {
245fc9e7fdaSChristopher Meis         return; // non-numeric replacement
246fc9e7fdaSChristopher Meis     }
247fc9e7fdaSChristopher Meis 
248fc9e7fdaSChristopher Meis     usedNames.insert(nameIt.value());
249fc9e7fdaSChristopher Meis 
250fc9e7fdaSChristopher Meis     auto usedIt = std::find(indexes.begin(), indexes.end(), index);
251fc9e7fdaSChristopher Meis     if (usedIt != indexes.end())
252fc9e7fdaSChristopher Meis     {
253fc9e7fdaSChristopher Meis         indexes.erase(usedIt);
254fc9e7fdaSChristopher Meis     }
255fc9e7fdaSChristopher Meis }
256fc9e7fdaSChristopher Meis 
257fc9e7fdaSChristopher Meis static bool extractExposeActionRecordNames(std::vector<std::string>& matches,
recordDiscoveredIdentifiers(std::set<nlohmann::json> & usedNames,std::list<size_t> & indexes,const std::string & probeName,const nlohmann::json & record)258fc9e7fdaSChristopher Meis                                            nlohmann::json::iterator& keyPair)
259fc9e7fdaSChristopher Meis {
260fc9e7fdaSChristopher Meis     if (keyPair.value().is_string())
261fc9e7fdaSChristopher Meis     {
262fc9e7fdaSChristopher Meis         matches.emplace_back(keyPair.value());
263fc9e7fdaSChristopher Meis         return true;
264fc9e7fdaSChristopher Meis     }
265fc9e7fdaSChristopher Meis 
266fc9e7fdaSChristopher Meis     if (keyPair.value().is_array())
267fc9e7fdaSChristopher Meis     {
268fc9e7fdaSChristopher Meis         for (const auto& value : keyPair.value())
269fc9e7fdaSChristopher Meis         {
270fc9e7fdaSChristopher Meis             if (!value.is_string())
271fc9e7fdaSChristopher Meis             {
272fc9e7fdaSChristopher Meis                 std::cerr << "Value is invalid type " << value << "\n";
273fc9e7fdaSChristopher Meis                 break;
274fc9e7fdaSChristopher Meis             }
275fc9e7fdaSChristopher Meis             matches.emplace_back(value);
276fc9e7fdaSChristopher Meis         }
277fc9e7fdaSChristopher Meis 
278fc9e7fdaSChristopher Meis         return true;
279fc9e7fdaSChristopher Meis     }
280fc9e7fdaSChristopher Meis 
281fc9e7fdaSChristopher Meis     std::cerr << "Value is invalid type " << keyPair.key() << "\n";
282fc9e7fdaSChristopher Meis 
283fc9e7fdaSChristopher Meis     return false;
284fc9e7fdaSChristopher Meis }
285fc9e7fdaSChristopher Meis 
286fc9e7fdaSChristopher Meis static std::optional<std::vector<std::string>::iterator> findExposeActionRecord(
287fc9e7fdaSChristopher Meis     std::vector<std::string>& matches, const nlohmann::json& record)
288fc9e7fdaSChristopher Meis {
289fc9e7fdaSChristopher Meis     const auto& name = (record)["Name"].get_ref<const std::string&>();
290fc9e7fdaSChristopher Meis     auto compare = [&name](const std::string& s) { return s == name; };
291fc9e7fdaSChristopher Meis     auto matchIt = std::find_if(matches.begin(), matches.end(), compare);
292fc9e7fdaSChristopher Meis 
293fc9e7fdaSChristopher Meis     if (matchIt == matches.end())
extractExposeActionRecordNames(std::vector<std::string> & matches,nlohmann::json::iterator & keyPair)294fc9e7fdaSChristopher Meis     {
295fc9e7fdaSChristopher Meis         return std::nullopt;
296fc9e7fdaSChristopher Meis     }
297fc9e7fdaSChristopher Meis 
298fc9e7fdaSChristopher Meis     return matchIt;
299fc9e7fdaSChristopher Meis }
300fc9e7fdaSChristopher Meis 
301fc9e7fdaSChristopher Meis static void applyBindExposeAction(nlohmann::json& exposedObject,
302fc9e7fdaSChristopher Meis                                   nlohmann::json& expose,
303fc9e7fdaSChristopher Meis                                   const std::string& propertyName)
304fc9e7fdaSChristopher Meis {
305fc9e7fdaSChristopher Meis     if (boost::starts_with(propertyName, "Bind"))
306fc9e7fdaSChristopher Meis     {
307fc9e7fdaSChristopher Meis         std::string bind = propertyName.substr(sizeof("Bind") - 1);
308fc9e7fdaSChristopher Meis         exposedObject["Status"] = "okay";
309fc9e7fdaSChristopher Meis         expose[bind] = exposedObject;
310fc9e7fdaSChristopher Meis     }
311fc9e7fdaSChristopher Meis }
312fc9e7fdaSChristopher Meis 
313fc9e7fdaSChristopher Meis static void applyDisableExposeAction(nlohmann::json& exposedObject,
314fc9e7fdaSChristopher Meis                                      const std::string& propertyName)
315fc9e7fdaSChristopher Meis {
316fc9e7fdaSChristopher Meis     if (propertyName == "DisableNode")
317fc9e7fdaSChristopher Meis     {
318fc9e7fdaSChristopher Meis         exposedObject["Status"] = "disabled";
319fc9e7fdaSChristopher Meis     }
320fc9e7fdaSChristopher Meis }
321fc9e7fdaSChristopher Meis 
322fc9e7fdaSChristopher Meis static void applyConfigExposeActions(
findExposeActionRecord(std::vector<std::string> & matches,const nlohmann::json & record)323fc9e7fdaSChristopher Meis     std::vector<std::string>& matches, nlohmann::json& expose,
324fc9e7fdaSChristopher Meis     const std::string& propertyName, nlohmann::json& configExposes)
325fc9e7fdaSChristopher Meis {
326fc9e7fdaSChristopher Meis     for (auto& exposedObject : configExposes)
327fc9e7fdaSChristopher Meis     {
328fc9e7fdaSChristopher Meis         auto match = findExposeActionRecord(matches, exposedObject);
329fc9e7fdaSChristopher Meis         if (match)
330fc9e7fdaSChristopher Meis         {
331fc9e7fdaSChristopher Meis             matches.erase(*match);
332fc9e7fdaSChristopher Meis             applyBindExposeAction(exposedObject, expose, propertyName);
333fc9e7fdaSChristopher Meis             applyDisableExposeAction(exposedObject, propertyName);
334fc9e7fdaSChristopher Meis         }
335fc9e7fdaSChristopher Meis     }
336fc9e7fdaSChristopher Meis }
337fc9e7fdaSChristopher Meis 
applyBindExposeAction(nlohmann::json & exposedObject,nlohmann::json & expose,const std::string & propertyName)338fc9e7fdaSChristopher Meis static void applyExposeActions(
339fc9e7fdaSChristopher Meis     nlohmann::json& systemConfiguration, const std::string& recordName,
340fc9e7fdaSChristopher Meis     nlohmann::json& expose, nlohmann::json::iterator& keyPair)
341fc9e7fdaSChristopher Meis {
342fc9e7fdaSChristopher Meis     bool isBind = boost::starts_with(keyPair.key(), "Bind");
343fc9e7fdaSChristopher Meis     bool isDisable = keyPair.key() == "DisableNode";
344fc9e7fdaSChristopher Meis     bool isExposeAction = isBind || isDisable;
345fc9e7fdaSChristopher Meis 
346fc9e7fdaSChristopher Meis     if (!isExposeAction)
347fc9e7fdaSChristopher Meis     {
348fc9e7fdaSChristopher Meis         return;
349fc9e7fdaSChristopher Meis     }
350fc9e7fdaSChristopher Meis 
351fc9e7fdaSChristopher Meis     std::vector<std::string> matches;
352fc9e7fdaSChristopher Meis 
353fc9e7fdaSChristopher Meis     if (!extractExposeActionRecordNames(matches, keyPair))
354fc9e7fdaSChristopher Meis     {
355fc9e7fdaSChristopher Meis         return;
356fc9e7fdaSChristopher Meis     }
357fc9e7fdaSChristopher Meis 
358fc9e7fdaSChristopher Meis     for (const auto& [configId, config] : systemConfiguration.items())
359fc9e7fdaSChristopher Meis     {
360fc9e7fdaSChristopher Meis         // don't disable ourselves
361fc9e7fdaSChristopher Meis         if (isDisable && configId == recordName)
362fc9e7fdaSChristopher Meis         {
363fc9e7fdaSChristopher Meis             continue;
364fc9e7fdaSChristopher Meis         }
365fc9e7fdaSChristopher Meis 
366fc9e7fdaSChristopher Meis         auto configListFind = config.find("Exposes");
367fc9e7fdaSChristopher Meis         if (configListFind == config.end())
368fc9e7fdaSChristopher Meis         {
369fc9e7fdaSChristopher Meis             continue;
370fc9e7fdaSChristopher Meis         }
371fc9e7fdaSChristopher Meis 
372fc9e7fdaSChristopher Meis         if (!configListFind->is_array())
373fc9e7fdaSChristopher Meis         {
374fc9e7fdaSChristopher Meis             continue;
375fc9e7fdaSChristopher Meis         }
376fc9e7fdaSChristopher Meis 
377fc9e7fdaSChristopher Meis         applyConfigExposeActions(matches, expose, keyPair.key(),
378fc9e7fdaSChristopher Meis                                  *configListFind);
379fc9e7fdaSChristopher Meis     }
380fc9e7fdaSChristopher Meis 
381fc9e7fdaSChristopher Meis     if (!matches.empty())
382fc9e7fdaSChristopher Meis     {
383fc9e7fdaSChristopher Meis         std::cerr << "configuration file dependency error, could not find "
384fc9e7fdaSChristopher Meis                   << keyPair.key() << " " << keyPair.value() << "\n";
385fc9e7fdaSChristopher Meis     }
386fc9e7fdaSChristopher Meis }
387fc9e7fdaSChristopher Meis 
388fc9e7fdaSChristopher Meis static std::string generateDeviceName(
389fc9e7fdaSChristopher Meis     const std::set<nlohmann::json>& usedNames, const DBusObject& dbusObject,
390fc9e7fdaSChristopher Meis     size_t foundDeviceIdx, const std::string& nameTemplate,
391fc9e7fdaSChristopher Meis     std::optional<std::string>& replaceStr)
392fc9e7fdaSChristopher Meis {
393fc9e7fdaSChristopher Meis     nlohmann::json copyForName = {{"Name", nameTemplate}};
394fc9e7fdaSChristopher Meis     nlohmann::json::iterator copyIt = copyForName.begin();
39559ef1e72SChristopher Meis     std::optional<std::string> replaceVal = em_utils::templateCharReplace(
39659ef1e72SChristopher Meis         copyIt, dbusObject, foundDeviceIdx, replaceStr);
397fc9e7fdaSChristopher Meis 
398fc9e7fdaSChristopher Meis     if (!replaceStr && replaceVal)
399fc9e7fdaSChristopher Meis     {
4006459993cSPatrick Williams         if (usedNames.contains(copyIt.value()))
401fc9e7fdaSChristopher Meis         {
402fc9e7fdaSChristopher Meis             replaceStr = replaceVal;
403fc9e7fdaSChristopher Meis             copyForName = {{"Name", nameTemplate}};
404fc9e7fdaSChristopher Meis             copyIt = copyForName.begin();
40559ef1e72SChristopher Meis             em_utils::templateCharReplace(copyIt, dbusObject, foundDeviceIdx,
40659ef1e72SChristopher Meis                                           replaceStr);
407fc9e7fdaSChristopher Meis         }
408fc9e7fdaSChristopher Meis     }
409fc9e7fdaSChristopher Meis 
410fc9e7fdaSChristopher Meis     if (replaceStr)
411fc9e7fdaSChristopher Meis     {
412fc9e7fdaSChristopher Meis         std::cerr << "Duplicates found, replacing " << *replaceStr
413fc9e7fdaSChristopher Meis                   << " with found device index.\n Consider "
414fc9e7fdaSChristopher Meis                      "fixing template to not have duplicates\n";
415fc9e7fdaSChristopher Meis     }
416fc9e7fdaSChristopher Meis 
417fc9e7fdaSChristopher Meis     return copyIt.value();
418fc9e7fdaSChristopher Meis }
419fc9e7fdaSChristopher Meis 
420fc9e7fdaSChristopher Meis void scan::PerformScan::updateSystemConfiguration(
421fc9e7fdaSChristopher Meis     const nlohmann::json& recordRef, const std::string& probeName,
422fc9e7fdaSChristopher Meis     FoundDevices& foundDevices)
423fc9e7fdaSChristopher Meis {
424fc9e7fdaSChristopher Meis     _passed = true;
generateDeviceName(const std::set<nlohmann::json> & usedNames,const DBusObject & dbusObject,size_t foundDeviceIdx,const std::string & nameTemplate,std::optional<std::string> & replaceStr)425fc9e7fdaSChristopher Meis     passedProbes.push_back(probeName);
426fc9e7fdaSChristopher Meis 
427fc9e7fdaSChristopher Meis     std::set<nlohmann::json> usedNames;
428fc9e7fdaSChristopher Meis     std::list<size_t> indexes(foundDevices.size());
429fc9e7fdaSChristopher Meis     std::iota(indexes.begin(), indexes.end(), 1);
430fc9e7fdaSChristopher Meis 
431fc9e7fdaSChristopher Meis     // copy over persisted configurations and make sure we remove
432fc9e7fdaSChristopher Meis     // indexes that are already used
433fc9e7fdaSChristopher Meis     for (auto itr = foundDevices.begin(); itr != foundDevices.end();)
434fc9e7fdaSChristopher Meis     {
435fc9e7fdaSChristopher Meis         std::string recordName = getRecordName(itr->interface, probeName);
436fc9e7fdaSChristopher Meis 
437*cf6a75bdSChristopher Meis         auto record = _em.systemConfiguration.find(recordName);
438*cf6a75bdSChristopher Meis         if (record == _em.systemConfiguration.end())
439fc9e7fdaSChristopher Meis         {
440*cf6a75bdSChristopher Meis             record = _em.lastJson.find(recordName);
441*cf6a75bdSChristopher Meis             if (record == _em.lastJson.end())
442fc9e7fdaSChristopher Meis             {
443fc9e7fdaSChristopher Meis                 itr++;
444fc9e7fdaSChristopher Meis                 continue;
445fc9e7fdaSChristopher Meis             }
446fc9e7fdaSChristopher Meis 
447fc9e7fdaSChristopher Meis             pruneRecordExposes(*record);
448fc9e7fdaSChristopher Meis 
449*cf6a75bdSChristopher Meis             _em.systemConfiguration[recordName] = *record;
450fc9e7fdaSChristopher Meis         }
451fc9e7fdaSChristopher Meis         _missingConfigurations.erase(recordName);
452fc9e7fdaSChristopher Meis 
453fc9e7fdaSChristopher Meis         // We've processed the device, remove it and advance the
454fc9e7fdaSChristopher Meis         // iterator
455fc9e7fdaSChristopher Meis         itr = foundDevices.erase(itr);
456fc9e7fdaSChristopher Meis         recordDiscoveredIdentifiers(usedNames, indexes, probeName, *record);
updateSystemConfiguration(const nlohmann::json & recordRef,const std::string & probeName,FoundDevices & foundDevices)457fc9e7fdaSChristopher Meis     }
458fc9e7fdaSChristopher Meis 
459fc9e7fdaSChristopher Meis     std::optional<std::string> replaceStr;
460fc9e7fdaSChristopher Meis 
461fc9e7fdaSChristopher Meis     DBusObject emptyObject;
462fc9e7fdaSChristopher Meis     DBusInterface emptyInterface;
463fc9e7fdaSChristopher Meis     emptyObject.emplace(std::string{}, emptyInterface);
464fc9e7fdaSChristopher Meis 
465fc9e7fdaSChristopher Meis     for (const auto& [foundDevice, path] : foundDevices)
466fc9e7fdaSChristopher Meis     {
467fc9e7fdaSChristopher Meis         // Need all interfaces on this path so that template
468fc9e7fdaSChristopher Meis         // substitutions can be done with any of the contained
469fc9e7fdaSChristopher Meis         // properties.  If the probe that passed didn't use an
470fc9e7fdaSChristopher Meis         // interface, such as if it was just TRUE, then
471fc9e7fdaSChristopher Meis         // templateCharReplace will just get passed in an empty
472fc9e7fdaSChristopher Meis         // map.
473fc9e7fdaSChristopher Meis         auto objectIt = dbusProbeObjects.find(path);
474fc9e7fdaSChristopher Meis         const DBusObject& dbusObject = (objectIt == dbusProbeObjects.end())
475fc9e7fdaSChristopher Meis                                            ? emptyObject
476fc9e7fdaSChristopher Meis                                            : objectIt->second;
477fc9e7fdaSChristopher Meis 
478fc9e7fdaSChristopher Meis         nlohmann::json record = recordRef;
479fc9e7fdaSChristopher Meis         std::string recordName = getRecordName(foundDevice, probeName);
480fc9e7fdaSChristopher Meis         size_t foundDeviceIdx = indexes.front();
481fc9e7fdaSChristopher Meis         indexes.pop_front();
482fc9e7fdaSChristopher Meis 
483fc9e7fdaSChristopher Meis         // check name first so we have no duplicate names
484fc9e7fdaSChristopher Meis         auto getName = record.find("Name");
485fc9e7fdaSChristopher Meis         if (getName == record.end())
486fc9e7fdaSChristopher Meis         {
487fc9e7fdaSChristopher Meis             std::cerr << "Record Missing Name! " << record.dump();
488fc9e7fdaSChristopher Meis             continue; // this should be impossible at this level
489fc9e7fdaSChristopher Meis         }
490fc9e7fdaSChristopher Meis 
491fc9e7fdaSChristopher Meis         std::string deviceName = generateDeviceName(
492fc9e7fdaSChristopher Meis             usedNames, dbusObject, foundDeviceIdx, getName.value(), replaceStr);
493fc9e7fdaSChristopher Meis         getName.value() = deviceName;
494fc9e7fdaSChristopher Meis         usedNames.insert(deviceName);
495fc9e7fdaSChristopher Meis 
496fc9e7fdaSChristopher Meis         for (auto keyPair = record.begin(); keyPair != record.end(); keyPair++)
497fc9e7fdaSChristopher Meis         {
498fc9e7fdaSChristopher Meis             if (keyPair.key() != "Name")
499fc9e7fdaSChristopher Meis             {
50059ef1e72SChristopher Meis                 em_utils::templateCharReplace(keyPair, dbusObject,
50159ef1e72SChristopher Meis                                               foundDeviceIdx, replaceStr);
502fc9e7fdaSChristopher Meis             }
503fc9e7fdaSChristopher Meis         }
504fc9e7fdaSChristopher Meis 
505fc9e7fdaSChristopher Meis         // insert into configuration temporarily to be able to
506fc9e7fdaSChristopher Meis         // reference ourselves
507fc9e7fdaSChristopher Meis 
508*cf6a75bdSChristopher Meis         _em.systemConfiguration[recordName] = record;
509fc9e7fdaSChristopher Meis 
510fc9e7fdaSChristopher Meis         auto findExpose = record.find("Exposes");
511fc9e7fdaSChristopher Meis         if (findExpose == record.end())
512fc9e7fdaSChristopher Meis         {
513fc9e7fdaSChristopher Meis             continue;
514fc9e7fdaSChristopher Meis         }
515fc9e7fdaSChristopher Meis 
516fc9e7fdaSChristopher Meis         for (auto& expose : *findExpose)
517fc9e7fdaSChristopher Meis         {
518fc9e7fdaSChristopher Meis             for (auto keyPair = expose.begin(); keyPair != expose.end();
519fc9e7fdaSChristopher Meis                  keyPair++)
520fc9e7fdaSChristopher Meis             {
52159ef1e72SChristopher Meis                 em_utils::templateCharReplace(keyPair, dbusObject,
52259ef1e72SChristopher Meis                                               foundDeviceIdx, replaceStr);
523fc9e7fdaSChristopher Meis 
524*cf6a75bdSChristopher Meis                 applyExposeActions(_em.systemConfiguration, recordName, expose,
525fc9e7fdaSChristopher Meis                                    keyPair);
526fc9e7fdaSChristopher Meis             }
527fc9e7fdaSChristopher Meis         }
528fc9e7fdaSChristopher Meis 
529fc9e7fdaSChristopher Meis         // overwrite ourselves with cleaned up version
530*cf6a75bdSChristopher Meis         _em.systemConfiguration[recordName] = record;
531fc9e7fdaSChristopher Meis         _missingConfigurations.erase(recordName);
532fc9e7fdaSChristopher Meis     }
533fc9e7fdaSChristopher Meis }
534fc9e7fdaSChristopher Meis 
535fc9e7fdaSChristopher Meis void scan::PerformScan::run()
536fc9e7fdaSChristopher Meis {
537fc9e7fdaSChristopher Meis     boost::container::flat_set<std::string> dbusProbeInterfaces;
538fc9e7fdaSChristopher Meis     std::vector<std::shared_ptr<probe::PerformProbe>> dbusProbePointers;
539fc9e7fdaSChristopher Meis 
540fc9e7fdaSChristopher Meis     for (auto it = _configurations.begin(); it != _configurations.end();)
541fc9e7fdaSChristopher Meis     {
542fc9e7fdaSChristopher Meis         // check for poorly formatted fields, probe must be an array
543fc9e7fdaSChristopher Meis         auto findProbe = it->find("Probe");
544fc9e7fdaSChristopher Meis         if (findProbe == it->end())
545fc9e7fdaSChristopher Meis         {
546fc9e7fdaSChristopher Meis             std::cerr << "configuration file missing probe:\n " << *it << "\n";
547fc9e7fdaSChristopher Meis             it = _configurations.erase(it);
548fc9e7fdaSChristopher Meis             continue;
549fc9e7fdaSChristopher Meis         }
550fc9e7fdaSChristopher Meis 
551fc9e7fdaSChristopher Meis         auto findName = it->find("Name");
552fc9e7fdaSChristopher Meis         if (findName == it->end())
553fc9e7fdaSChristopher Meis         {
554fc9e7fdaSChristopher Meis             std::cerr << "configuration file missing name:\n " << *it << "\n";
555fc9e7fdaSChristopher Meis             it = _configurations.erase(it);
556fc9e7fdaSChristopher Meis             continue;
557fc9e7fdaSChristopher Meis         }
558fc9e7fdaSChristopher Meis         std::string probeName = *findName;
559fc9e7fdaSChristopher Meis 
560fc9e7fdaSChristopher Meis         if (std::find(passedProbes.begin(), passedProbes.end(), probeName) !=
561fc9e7fdaSChristopher Meis             passedProbes.end())
562fc9e7fdaSChristopher Meis         {
563fc9e7fdaSChristopher Meis             it = _configurations.erase(it);
564fc9e7fdaSChristopher Meis             continue;
565fc9e7fdaSChristopher Meis         }
566fc9e7fdaSChristopher Meis 
567fc9e7fdaSChristopher Meis         nlohmann::json& recordRef = *it;
568fc9e7fdaSChristopher Meis         nlohmann::json probeCommand;
569fc9e7fdaSChristopher Meis         if ((*findProbe).type() != nlohmann::json::value_t::array)
570fc9e7fdaSChristopher Meis         {
571fc9e7fdaSChristopher Meis             probeCommand = nlohmann::json::array();
run()572fc9e7fdaSChristopher Meis             probeCommand.push_back(*findProbe);
573fc9e7fdaSChristopher Meis         }
574fc9e7fdaSChristopher Meis         else
575fc9e7fdaSChristopher Meis         {
576fc9e7fdaSChristopher Meis             probeCommand = *findProbe;
577fc9e7fdaSChristopher Meis         }
578fc9e7fdaSChristopher Meis 
579fc9e7fdaSChristopher Meis         // store reference to this to children to makes sure we don't get
580fc9e7fdaSChristopher Meis         // destroyed too early
581fc9e7fdaSChristopher Meis         auto thisRef = shared_from_this();
582fc9e7fdaSChristopher Meis         auto probePointer = std::make_shared<probe::PerformProbe>(
583fc9e7fdaSChristopher Meis             recordRef, probeCommand, probeName, thisRef);
584fc9e7fdaSChristopher Meis 
585fc9e7fdaSChristopher Meis         // parse out dbus probes by discarding other probe types, store in a
586fc9e7fdaSChristopher Meis         // map
587fc9e7fdaSChristopher Meis         for (const nlohmann::json& probeJson : probeCommand)
588fc9e7fdaSChristopher Meis         {
589fc9e7fdaSChristopher Meis             const std::string* probe = probeJson.get_ptr<const std::string*>();
590fc9e7fdaSChristopher Meis             if (probe == nullptr)
591fc9e7fdaSChristopher Meis             {
592fc9e7fdaSChristopher Meis                 std::cerr << "Probe statement wasn't a string, can't parse";
593fc9e7fdaSChristopher Meis                 continue;
594fc9e7fdaSChristopher Meis             }
595fc9e7fdaSChristopher Meis             if (probe::findProbeType(*probe))
596fc9e7fdaSChristopher Meis             {
597fc9e7fdaSChristopher Meis                 continue;
598fc9e7fdaSChristopher Meis             }
599fc9e7fdaSChristopher Meis             // syntax requires probe before first open brace
600fc9e7fdaSChristopher Meis             auto findStart = probe->find('(');
601fc9e7fdaSChristopher Meis             std::string interface = probe->substr(0, findStart);
602fc9e7fdaSChristopher Meis             dbusProbeInterfaces.emplace(interface);
603fc9e7fdaSChristopher Meis             dbusProbePointers.emplace_back(probePointer);
604fc9e7fdaSChristopher Meis         }
605fc9e7fdaSChristopher Meis         it++;
606fc9e7fdaSChristopher Meis     }
607fc9e7fdaSChristopher Meis 
608fc9e7fdaSChristopher Meis     // probe vector stores a shared_ptr to each PerformProbe that cares
609fc9e7fdaSChristopher Meis     // about a dbus interface
610fc9e7fdaSChristopher Meis     findDbusObjects(std::move(dbusProbePointers),
611fc9e7fdaSChristopher Meis                     std::move(dbusProbeInterfaces), shared_from_this());
612fc9e7fdaSChristopher Meis }
613fc9e7fdaSChristopher Meis 
614fc9e7fdaSChristopher Meis scan::PerformScan::~PerformScan()
615fc9e7fdaSChristopher Meis {
616fc9e7fdaSChristopher Meis     if (_passed)
617fc9e7fdaSChristopher Meis     {
618fc9e7fdaSChristopher Meis         auto nextScan = std::make_shared<PerformScan>(
619*cf6a75bdSChristopher Meis             _em, _missingConfigurations, _configurations, std::move(_callback));
620fc9e7fdaSChristopher Meis         nextScan->passedProbes = std::move(passedProbes);
621fc9e7fdaSChristopher Meis         nextScan->dbusProbeObjects = std::move(dbusProbeObjects);
622fc9e7fdaSChristopher Meis         nextScan->run();
623fc9e7fdaSChristopher Meis     }
624fc9e7fdaSChristopher Meis     else
625fc9e7fdaSChristopher Meis     {
626fc9e7fdaSChristopher Meis         _callback();
627fc9e7fdaSChristopher Meis     }
628fc9e7fdaSChristopher Meis }
629