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