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