EntityManager: Split out PerformScan
Start breaking down the code so regular people can comprehend it.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Change-Id: I82fec105fbe29bd09145feaaa48fbcb875ca930f
diff --git a/include/EntityManager.hpp b/include/EntityManager.hpp
index 084174d..a79e7fe 100644
--- a/include/EntityManager.hpp
+++ b/include/EntityManager.hpp
@@ -40,6 +40,25 @@
using FoundDeviceT = std::vector<std::tuple<
boost::container::flat_map<std::string, BasicVariantType>, std::string>>;
+struct CmpStr
+{
+ bool operator()(const char* a, const char* b) const
+ {
+ return std::strcmp(a, b) < 0;
+ }
+};
+
+// underscore T for collison with dbus c api
+enum class probe_type_codes
+{
+ FALSE_T,
+ TRUE_T,
+ AND,
+ OR,
+ FOUND,
+ MATCH_ONE
+};
+
struct PerformScan : std::enable_shared_from_this<PerformScan>
{
diff --git a/src/EntityManager.cpp b/src/EntityManager.cpp
index 490c0f5..725f2bf 100644
--- a/src/EntityManager.cpp
+++ b/src/EntityManager.cpp
@@ -47,29 +47,9 @@
constexpr const char* lastConfiguration = "/tmp/configuration/last.json";
constexpr const char* currentConfiguration = "/var/configuration/system.json";
constexpr const char* globalSchema = "global.json";
-constexpr const int32_t maxMapperDepth = 0;
-
constexpr const bool debug = false;
-struct CmpStr
-{
- bool operator()(const char* a, const char* b) const
- {
- return std::strcmp(a, b) < 0;
- }
-};
-
-// underscore T for collison with dbus c api
-enum class probe_type_codes
-{
- FALSE_T,
- TRUE_T,
- AND,
- OR,
- FOUND,
- MATCH_ONE
-};
-const static boost::container::flat_map<const char*, probe_type_codes, CmpStr>
+static const boost::container::flat_map<const char*, probe_type_codes, CmpStr>
probeTypes{{{"FALSE", probe_type_codes::FALSE_T},
{"TRUE", probe_type_codes::TRUE_T},
{"AND", probe_type_codes::AND},
@@ -83,9 +63,6 @@
std::variant<std::vector<std::string>, std::vector<double>, std::string,
int64_t, uint64_t, double, int32_t, uint32_t, int16_t,
uint16_t, uint8_t, bool>;
-using GetSubTreeType = std::vector<
- std::pair<std::string,
- std::vector<std::pair<std::string, std::vector<std::string>>>>>;
using ManagedObjectType = boost::container::flat_map<
sdbusplus::message::object_path,
@@ -100,17 +77,13 @@
// todo: pass this through nicer
std::shared_ptr<sdbusplus::asio::connection> systemBus;
-static nlohmann::json lastJson;
+nlohmann::json lastJson;
boost::asio::io_context io;
const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]");
const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]");
-void registerCallback(nlohmann::json& systemConfiguration,
- sdbusplus::asio::object_server& objServer,
- const std::string& path);
-
static std::shared_ptr<sdbusplus::asio::dbus_interface>
createInterface(sdbusplus::asio::object_server& objServer,
const std::string& path, const std::string& interface,
@@ -136,153 +109,6 @@
return ptr;
}
-void getInterfaces(
- const std::tuple<std::string, std::string, std::string>& call,
- const std::vector<std::shared_ptr<PerformProbe>>& probeVector,
- const std::shared_ptr<PerformScan>& scan, size_t retries = 5)
-{
- if (!retries)
- {
- std::cerr << "retries exhausted on " << std::get<0>(call) << " "
- << std::get<1>(call) << " " << std::get<2>(call) << "\n";
- return;
- }
-
- systemBus->async_method_call(
- [call, scan, probeVector, retries](
- boost::system::error_code& errc,
- const boost::container::flat_map<std::string, BasicVariantType>&
- resp) {
- if (errc)
- {
- std::cerr << "error calling getall on " << std::get<0>(call)
- << " " << std::get<1>(call) << " "
- << std::get<2>(call) << "\n";
-
- std::shared_ptr<boost::asio::steady_timer> timer =
- std::make_shared<boost::asio::steady_timer>(io);
- timer->expires_after(std::chrono::seconds(2));
-
- timer->async_wait([timer, call, scan, probeVector,
- retries](const boost::system::error_code&) {
- getInterfaces(call, probeVector, scan, retries - 1);
- });
- return;
- }
-
- scan->dbusProbeObjects[std::get<1>(call)][std::get<2>(call)] = resp;
- },
- std::get<0>(call), std::get<1>(call), "org.freedesktop.DBus.Properties",
- "GetAll", std::get<2>(call));
-
- if constexpr (debug)
- {
- std::cerr << __func__ << " " << __LINE__ << "\n";
- }
-}
-
-// Populates scan->dbusProbeObjects with all interfaces and properties
-// for the paths that own the interfaces passed in.
-void findDbusObjects(std::vector<std::shared_ptr<PerformProbe>>&& probeVector,
- boost::container::flat_set<std::string>&& interfaces,
- const std::shared_ptr<PerformScan>& scan,
- size_t retries = 5)
-{
- // Filter out interfaces already obtained.
- for (const auto& [path, probeInterfaces] : scan->dbusProbeObjects)
- {
- for (const auto& [interface, _] : probeInterfaces)
- {
- interfaces.erase(interface);
- }
- }
- if (interfaces.empty())
- {
- return;
- }
-
- // find all connections in the mapper that expose a specific type
- systemBus->async_method_call(
- [interfaces, probeVector{std::move(probeVector)}, scan,
- retries](boost::system::error_code& ec,
- const GetSubTreeType& interfaceSubtree) mutable {
- boost::container::flat_set<
- std::tuple<std::string, std::string, std::string>>
- interfaceConnections;
- if (ec)
- {
- if (ec.value() == ENOENT)
- {
- return; // wasn't found by mapper
- }
- std::cerr << "Error communicating to mapper.\n";
-
- if (!retries)
- {
- // if we can't communicate to the mapper something is very
- // wrong
- std::exit(EXIT_FAILURE);
- }
- std::shared_ptr<boost::asio::steady_timer> timer =
- std::make_shared<boost::asio::steady_timer>(io);
- timer->expires_after(std::chrono::seconds(10));
-
- timer->async_wait(
- [timer, interfaces{std::move(interfaces)}, scan,
- probeVector{std::move(probeVector)},
- retries](const boost::system::error_code&) mutable {
- findDbusObjects(std::move(probeVector),
- std::move(interfaces), scan,
- retries - 1);
- });
- return;
- }
-
- for (const auto& [path, object] : interfaceSubtree)
- {
- for (const auto& [busname, ifaces] : object)
- {
- for (const std::string& iface : ifaces)
- {
- // The 3 default org.freedeskstop interfaces (Peer,
- // Introspectable, and Properties) are returned by
- // the mapper but don't have properties, so don't bother
- // with the GetAll call to save some cycles.
- if (!boost::algorithm::starts_with(iface,
- "org.freedesktop"))
- {
- interfaceConnections.emplace(busname, path, iface);
- }
- }
- }
-
- // Get a PropertiesChanged callback for all
- // interfaces on this path.
- registerCallback(scan->_systemConfiguration, scan->objServer,
- path);
- }
-
- if (interfaceConnections.empty())
- {
- return;
- }
-
- for (const auto& call : interfaceConnections)
- {
- getInterfaces(call, probeVector, scan);
- }
- },
- "xyz.openbmc_project.ObjectMapper",
- "/xyz/openbmc_project/object_mapper",
- "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", maxMapperDepth,
- interfaces);
-
- if constexpr (debug)
- {
- std::cerr << __func__ << " " << __LINE__ << "\n";
- }
-}
-
// probes dbus interface dictionary for a key with a value that matches a regex
// When an interface passes a probe, also save its D-Bus path with it.
bool probeDbus(const std::string& interface,
@@ -1213,455 +1039,6 @@
return true;
}
-std::string getRecordName(
- const boost::container::flat_map<std::string, BasicVariantType>& probe,
- const std::string& probeName)
-{
- if (probe.empty())
- {
- return probeName;
- }
-
- // use an array so alphabetical order from the
- // flat_map is maintained
- auto device = nlohmann::json::array();
- for (auto& devPair : probe)
- {
- device.push_back(devPair.first);
- std::visit([&device](auto&& v) { device.push_back(v); },
- devPair.second);
- }
- size_t hash = std::hash<std::string>{}(probeName + device.dump());
- // hashes are hard to distinguish, use the
- // non-hashed version if we want debug
- if constexpr (debug)
- {
- return probeName + device.dump();
- }
- else
- {
- return std::to_string(hash);
- }
-}
-
-PerformScan::PerformScan(nlohmann::json& systemConfiguration,
- nlohmann::json& missingConfigurations,
- std::list<nlohmann::json>& configurations,
- sdbusplus::asio::object_server& objServerIn,
- std::function<void()>&& callback) :
- _systemConfiguration(systemConfiguration),
- _missingConfigurations(missingConfigurations),
- _configurations(configurations), objServer(objServerIn),
- _callback(std::move(callback))
-{}
-void PerformScan::run()
-{
- boost::container::flat_set<std::string> dbusProbeInterfaces;
- std::vector<std::shared_ptr<PerformProbe>> dbusProbePointers;
-
- for (auto it = _configurations.begin(); it != _configurations.end();)
- {
- auto findProbe = it->find("Probe");
- auto findName = it->find("Name");
-
- nlohmann::json probeCommand;
- // check for poorly formatted fields, probe must be an array
- if (findProbe == it->end())
- {
- std::cerr << "configuration file missing probe:\n " << *it << "\n";
- it = _configurations.erase(it);
- continue;
- }
- if ((*findProbe).type() != nlohmann::json::value_t::array)
- {
- probeCommand = nlohmann::json::array();
- probeCommand.push_back(*findProbe);
- }
- else
- {
- probeCommand = *findProbe;
- }
-
- if (findName == it->end())
- {
- std::cerr << "configuration file missing name:\n " << *it << "\n";
- it = _configurations.erase(it);
- continue;
- }
- std::string probeName = *findName;
-
- if (std::find(passedProbes.begin(), passedProbes.end(), probeName) !=
- passedProbes.end())
- {
- it = _configurations.erase(it);
- continue;
- }
- nlohmann::json* recordPtr = &(*it);
-
- // store reference to this to children to makes sure we don't get
- // destroyed too early
- auto thisRef = shared_from_this();
- auto probePointer = std::make_shared<PerformProbe>(
- probeCommand, thisRef,
- [&, recordPtr, probeName](FoundDeviceT& foundDevices,
- const DBusProbeObjectT& allInterfaces) {
- _passed = true;
- std::set<nlohmann::json> usedNames;
- passedProbes.push_back(probeName);
- std::list<size_t> indexes(foundDevices.size());
- std::iota(indexes.begin(), indexes.end(), 1);
-
- size_t indexIdx = probeName.find('$');
- bool hasTemplateName = (indexIdx != std::string::npos);
-
- // copy over persisted configurations and make sure we remove
- // indexes that are already used
- for (auto itr = foundDevices.begin();
- itr != foundDevices.end();)
- {
- std::string recordName =
- getRecordName(std::get<0>(*itr), probeName);
-
- auto fromLastJson = lastJson.find(recordName);
- if (fromLastJson != lastJson.end())
- {
- auto findExposes = fromLastJson->find("Exposes");
- // delete nulls from any updates
- if (findExposes != fromLastJson->end())
- {
- auto copy = nlohmann::json::array();
- for (auto& expose : *findExposes)
- {
- if (expose.is_null())
- {
- continue;
- }
- copy.emplace_back(expose);
- }
- *findExposes = copy;
- }
-
- // keep user changes
- _systemConfiguration[recordName] = *fromLastJson;
- _missingConfigurations.erase(recordName);
- itr = foundDevices.erase(itr);
- if (hasTemplateName)
- {
- auto nameIt = fromLastJson->find("Name");
- if (nameIt == fromLastJson->end())
- {
- std::cerr << "Last JSON Illegal\n";
- continue;
- }
- int index = 0;
- auto str =
- nameIt->get<std::string>().substr(indexIdx);
- auto [p, ec] = std::from_chars(
- str.data(), str.data() + str.size(), index);
- if (ec != std::errc())
- {
- continue; // non-numeric replacement
- }
- usedNames.insert(nameIt.value());
- auto usedIt = std::find(indexes.begin(),
- indexes.end(), index);
-
- if (usedIt == indexes.end())
- {
- continue; // less items now
- }
- indexes.erase(usedIt);
- }
-
- continue;
- }
- itr++;
- }
-
- std::optional<std::string> replaceStr;
-
- DBusProbeObjectT::mapped_type emptyInterfaces;
- boost::container::flat_map<std::string, BasicVariantType>
- emptyProps;
- emptyInterfaces.emplace(std::string{}, emptyProps);
-
- for (auto& foundDeviceAndPath : foundDevices)
- {
- const boost::container::flat_map<
- std::string, BasicVariantType>& foundDevice =
- std::get<0>(foundDeviceAndPath);
- const std::string& path = std::get<1>(foundDeviceAndPath);
-
- // Need all interfaces on this path so that template
- // substitutions can be done with any of the contained
- // properties. If the probe that passed didn't use an
- // interface, such as if it was just TRUE, then
- // templateCharReplace will just get passed in an empty
- // map.
- const DBusProbeObjectT::mapped_type* allInterfacesOnPath =
- &emptyInterfaces;
-
- auto ifacesIt = allInterfaces.find(path);
- if (ifacesIt != allInterfaces.end())
- {
- allInterfacesOnPath = &ifacesIt->second;
- }
-
- nlohmann::json record = *recordPtr;
- std::string recordName =
- getRecordName(foundDevice, probeName);
- size_t foundDeviceIdx = indexes.front();
- indexes.pop_front();
-
- // check name first so we have no duplicate names
- auto getName = record.find("Name");
- if (getName == record.end())
- {
- std::cerr << "Record Missing Name! " << record.dump();
- continue; // this should be impossible at this level
- }
-
- nlohmann::json copyForName = {{"Name", getName.value()}};
- nlohmann::json::iterator copyIt = copyForName.begin();
- std::optional<std::string> replaceVal =
- templateCharReplace(copyIt, *allInterfacesOnPath,
- foundDeviceIdx, replaceStr);
-
- if (!replaceStr && replaceVal)
- {
- if (usedNames.find(copyIt.value()) != usedNames.end())
- {
- replaceStr = replaceVal;
- copyForName = {{"Name", getName.value()}};
- copyIt = copyForName.begin();
- templateCharReplace(copyIt, *allInterfacesOnPath,
- foundDeviceIdx, replaceStr);
- }
- }
-
- if (replaceStr)
- {
- std::cerr << "Duplicates found, replacing "
- << *replaceStr
- << " with found device index.\n Consider "
- "fixing template to not have duplicates\n";
- }
-
- for (auto keyPair = record.begin(); keyPair != record.end();
- keyPair++)
- {
- if (keyPair.key() == "Name")
- {
- keyPair.value() = copyIt.value();
- usedNames.insert(copyIt.value());
-
- continue; // already covered above
- }
- templateCharReplace(keyPair, *allInterfacesOnPath,
- foundDeviceIdx, replaceStr);
- }
-
- // insert into configuration temporarily to be able to
- // reference ourselves
-
- _systemConfiguration[recordName] = record;
-
- auto findExpose = record.find("Exposes");
- if (findExpose == record.end())
- {
- _systemConfiguration[recordName] = record;
- continue;
- }
-
- for (auto& expose : *findExpose)
- {
- for (auto keyPair = expose.begin();
- keyPair != expose.end(); keyPair++)
- {
-
- templateCharReplace(keyPair, *allInterfacesOnPath,
- foundDeviceIdx, replaceStr);
-
- bool isBind =
- boost::starts_with(keyPair.key(), "Bind");
- bool isDisable = keyPair.key() == "DisableNode";
-
- // special cases
- if (!(isBind || isDisable))
- {
- continue;
- }
-
- if (keyPair.value().type() !=
- nlohmann::json::value_t::string &&
- keyPair.value().type() !=
- nlohmann::json::value_t::array)
- {
- std::cerr << "Value is invalid type "
- << keyPair.key() << "\n";
- continue;
- }
-
- std::vector<std::string> matches;
- if (keyPair.value().type() ==
- nlohmann::json::value_t::string)
- {
- matches.emplace_back(keyPair.value());
- }
- else
- {
- for (const auto& value : keyPair.value())
- {
- if (value.type() !=
- nlohmann::json::value_t::string)
- {
- std::cerr << "Value is invalid type "
- << value << "\n";
- break;
- }
- matches.emplace_back(value);
- }
- }
-
- std::set<std::string> foundMatches;
- for (auto& configurationPair :
- _systemConfiguration.items())
- {
- if (isDisable)
- {
- // don't disable ourselves
- if (configurationPair.key() == recordName)
- {
- continue;
- }
- }
- auto configListFind =
- configurationPair.value().find("Exposes");
-
- if (configListFind ==
- configurationPair.value().end() ||
- configListFind->type() !=
- nlohmann::json::value_t::array)
- {
- continue;
- }
- for (auto& exposedObject : *configListFind)
- {
- auto matchIt = std::find_if(
- matches.begin(), matches.end(),
- [name = (exposedObject)["Name"]
- .get<std::string>()](
- const std::string& s) {
- return s == name;
- });
- if (matchIt == matches.end())
- {
- continue;
- }
- foundMatches.insert(*matchIt);
-
- if (isBind)
- {
- std::string bind = keyPair.key().substr(
- sizeof("Bind") - 1);
-
- exposedObject["Status"] = "okay";
- expose[bind] = exposedObject;
- }
- else if (isDisable)
- {
- exposedObject["Status"] = "disabled";
- }
- }
- }
- if (foundMatches.size() != matches.size())
- {
- std::cerr << "configuration file "
- "dependency error, "
- "could not find "
- << keyPair.key() << " "
- << keyPair.value() << "\n";
- }
- }
- }
- // overwrite ourselves with cleaned up version
- _systemConfiguration[recordName] = record;
- _missingConfigurations.erase(recordName);
- }
- });
-
- // parse out dbus probes by discarding other probe types, store in a
- // map
- for (const nlohmann::json& probeJson : probeCommand)
- {
- const std::string* probe = probeJson.get_ptr<const std::string*>();
- if (probe == nullptr)
- {
- std::cerr << "Probe statement wasn't a string, can't parse";
- continue;
- }
- bool found = false;
- boost::container::flat_map<const char*, probe_type_codes,
- CmpStr>::const_iterator probeType;
- for (probeType = probeTypes.begin(); probeType != probeTypes.end();
- ++probeType)
- {
- if (probe->find(probeType->first) != std::string::npos)
- {
- found = true;
- break;
- }
- }
- if (found)
- {
- continue;
- }
- // syntax requires probe before first open brace
- auto findStart = probe->find('(');
- std::string interface = probe->substr(0, findStart);
- dbusProbeInterfaces.emplace(interface);
- dbusProbePointers.emplace_back(probePointer);
- }
- it++;
- }
-
- // probe vector stores a shared_ptr to each PerformProbe that cares
- // about a dbus interface
- findDbusObjects(std::move(dbusProbePointers),
- std::move(dbusProbeInterfaces), shared_from_this());
- if constexpr (debug)
- {
- std::cerr << __func__ << " " << __LINE__ << "\n";
- }
-}
-
-PerformScan::~PerformScan()
-{
- if (_passed)
- {
- auto nextScan = std::make_shared<PerformScan>(
- _systemConfiguration, _missingConfigurations, _configurations,
- objServer, std::move(_callback));
- nextScan->passedProbes = std::move(passedProbes);
- nextScan->dbusProbeObjects = std::move(dbusProbeObjects);
- nextScan->run();
-
- if constexpr (debug)
- {
- std::cerr << __func__ << " " << __LINE__ << "\n";
- }
- }
- else
- {
- _callback();
-
- if constexpr (debug)
- {
- std::cerr << __func__ << " " << __LINE__ << "\n";
- }
- }
-}
-
void startRemovedTimer(boost::asio::steady_timer& timer,
nlohmann::json& systemConfiguration)
{
@@ -1869,31 +1246,6 @@
});
}
-void registerCallback(nlohmann::json& systemConfiguration,
- sdbusplus::asio::object_server& objServer,
- const std::string& path)
-{
- static boost::container::flat_map<std::string, sdbusplus::bus::match::match>
- dbusMatches;
-
- auto find = dbusMatches.find(path);
- if (find != dbusMatches.end())
- {
- return;
- }
- std::function<void(sdbusplus::message::message & message)> eventHandler =
-
- [&](sdbusplus::message::message&) {
- propertiesChangedCallback(systemConfiguration, objServer);
- };
-
- sdbusplus::bus::match::match match(
- static_cast<sdbusplus::bus::bus&>(*systemBus),
- "type='signal',member='PropertiesChanged',path='" + path + "'",
- eventHandler);
- dbusMatches.emplace(path, std::move(match));
-}
-
int main()
{
// setup connection to dbus
diff --git a/src/PerformScan.cpp b/src/PerformScan.cpp
new file mode 100644
index 0000000..70a4e47
--- /dev/null
+++ b/src/PerformScan.cpp
@@ -0,0 +1,669 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+/// \file PerformScan.cpp
+#include "EntityManager.hpp"
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/asio/steady_timer.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+
+#include <charconv>
+
+/* Hacks from splitting EntityManager.cpp */
+extern std::shared_ptr<sdbusplus::asio::connection> systemBus;
+extern nlohmann::json lastJson;
+extern void
+ propertiesChangedCallback(nlohmann::json& systemConfiguration,
+ sdbusplus::asio::object_server& objServer);
+
+/* Keep this in sync with EntityManager.cpp */
+static const boost::container::flat_map<const char*, probe_type_codes, CmpStr>
+ probeTypes{{{"FALSE", probe_type_codes::FALSE_T},
+ {"TRUE", probe_type_codes::TRUE_T},
+ {"AND", probe_type_codes::AND},
+ {"OR", probe_type_codes::OR},
+ {"FOUND", probe_type_codes::FOUND},
+ {"MATCH_ONE", probe_type_codes::MATCH_ONE}}};
+
+using GetSubTreeType = std::vector<
+ std::pair<std::string,
+ std::vector<std::pair<std::string, std::vector<std::string>>>>>;
+
+constexpr const int32_t maxMapperDepth = 0;
+
+constexpr const bool debug = false;
+
+void getInterfaces(
+ const std::tuple<std::string, std::string, std::string>& call,
+ const std::vector<std::shared_ptr<PerformProbe>>& probeVector,
+ const std::shared_ptr<PerformScan>& scan, size_t retries = 5)
+{
+ if (!retries)
+ {
+ std::cerr << "retries exhausted on " << std::get<0>(call) << " "
+ << std::get<1>(call) << " " << std::get<2>(call) << "\n";
+ return;
+ }
+
+ systemBus->async_method_call(
+ [call, scan, probeVector, retries](
+ boost::system::error_code& errc,
+ const boost::container::flat_map<std::string, BasicVariantType>&
+ resp) {
+ if (errc)
+ {
+ std::cerr << "error calling getall on " << std::get<0>(call)
+ << " " << std::get<1>(call) << " "
+ << std::get<2>(call) << "\n";
+
+ std::shared_ptr<boost::asio::steady_timer> timer =
+ std::make_shared<boost::asio::steady_timer>(io);
+ timer->expires_after(std::chrono::seconds(2));
+
+ timer->async_wait([timer, call, scan, probeVector,
+ retries](const boost::system::error_code&) {
+ getInterfaces(call, probeVector, scan, retries - 1);
+ });
+ return;
+ }
+
+ scan->dbusProbeObjects[std::get<1>(call)][std::get<2>(call)] = resp;
+ },
+ std::get<0>(call), std::get<1>(call), "org.freedesktop.DBus.Properties",
+ "GetAll", std::get<2>(call));
+
+ if constexpr (debug)
+ {
+ std::cerr << __func__ << " " << __LINE__ << "\n";
+ }
+}
+
+void registerCallback(nlohmann::json& systemConfiguration,
+ sdbusplus::asio::object_server& objServer,
+ const std::string& path)
+{
+ static boost::container::flat_map<std::string, sdbusplus::bus::match::match>
+ dbusMatches;
+
+ auto find = dbusMatches.find(path);
+ if (find != dbusMatches.end())
+ {
+ return;
+ }
+ std::function<void(sdbusplus::message::message & message)> eventHandler =
+
+ [&](sdbusplus::message::message&) {
+ propertiesChangedCallback(systemConfiguration, objServer);
+ };
+
+ sdbusplus::bus::match::match match(
+ static_cast<sdbusplus::bus::bus&>(*systemBus),
+ "type='signal',member='PropertiesChanged',path='" + path + "'",
+ eventHandler);
+ dbusMatches.emplace(path, std::move(match));
+}
+
+// Populates scan->dbusProbeObjects with all interfaces and properties
+// for the paths that own the interfaces passed in.
+void findDbusObjects(std::vector<std::shared_ptr<PerformProbe>>&& probeVector,
+ boost::container::flat_set<std::string>&& interfaces,
+ const std::shared_ptr<PerformScan>& scan,
+ size_t retries = 5)
+{
+ // Filter out interfaces already obtained.
+ for (const auto& [path, probeInterfaces] : scan->dbusProbeObjects)
+ {
+ for (const auto& [interface, _] : probeInterfaces)
+ {
+ interfaces.erase(interface);
+ }
+ }
+ if (interfaces.empty())
+ {
+ return;
+ }
+
+ // find all connections in the mapper that expose a specific type
+ systemBus->async_method_call(
+ [interfaces, probeVector{std::move(probeVector)}, scan,
+ retries](boost::system::error_code& ec,
+ const GetSubTreeType& interfaceSubtree) mutable {
+ boost::container::flat_set<
+ std::tuple<std::string, std::string, std::string>>
+ interfaceConnections;
+ if (ec)
+ {
+ if (ec.value() == ENOENT)
+ {
+ return; // wasn't found by mapper
+ }
+ std::cerr << "Error communicating to mapper.\n";
+
+ if (!retries)
+ {
+ // if we can't communicate to the mapper something is very
+ // wrong
+ std::exit(EXIT_FAILURE);
+ }
+ std::shared_ptr<boost::asio::steady_timer> timer =
+ std::make_shared<boost::asio::steady_timer>(io);
+ timer->expires_after(std::chrono::seconds(10));
+
+ timer->async_wait(
+ [timer, interfaces{std::move(interfaces)}, scan,
+ probeVector{std::move(probeVector)},
+ retries](const boost::system::error_code&) mutable {
+ findDbusObjects(std::move(probeVector),
+ std::move(interfaces), scan,
+ retries - 1);
+ });
+ return;
+ }
+
+ for (const auto& [path, object] : interfaceSubtree)
+ {
+ for (const auto& [busname, ifaces] : object)
+ {
+ for (const std::string& iface : ifaces)
+ {
+ // The 3 default org.freedeskstop interfaces (Peer,
+ // Introspectable, and Properties) are returned by
+ // the mapper but don't have properties, so don't bother
+ // with the GetAll call to save some cycles.
+ if (!boost::algorithm::starts_with(iface,
+ "org.freedesktop"))
+ {
+ interfaceConnections.emplace(busname, path, iface);
+ }
+ }
+ }
+
+ // Get a PropertiesChanged callback for all
+ // interfaces on this path.
+ registerCallback(scan->_systemConfiguration, scan->objServer,
+ path);
+ }
+
+ if (interfaceConnections.empty())
+ {
+ return;
+ }
+
+ for (const auto& call : interfaceConnections)
+ {
+ getInterfaces(call, probeVector, scan);
+ }
+ },
+ "xyz.openbmc_project.ObjectMapper",
+ "/xyz/openbmc_project/object_mapper",
+ "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", maxMapperDepth,
+ interfaces);
+
+ if constexpr (debug)
+ {
+ std::cerr << __func__ << " " << __LINE__ << "\n";
+ }
+}
+
+std::string getRecordName(
+ const boost::container::flat_map<std::string, BasicVariantType>& probe,
+ const std::string& probeName)
+{
+ if (probe.empty())
+ {
+ return probeName;
+ }
+
+ // use an array so alphabetical order from the
+ // flat_map is maintained
+ auto device = nlohmann::json::array();
+ for (auto& devPair : probe)
+ {
+ device.push_back(devPair.first);
+ std::visit([&device](auto&& v) { device.push_back(v); },
+ devPair.second);
+ }
+ size_t hash = std::hash<std::string>{}(probeName + device.dump());
+ // hashes are hard to distinguish, use the
+ // non-hashed version if we want debug
+ if constexpr (debug)
+ {
+ return probeName + device.dump();
+ }
+ else
+ {
+ return std::to_string(hash);
+ }
+}
+
+PerformScan::PerformScan(nlohmann::json& systemConfiguration,
+ nlohmann::json& missingConfigurations,
+ std::list<nlohmann::json>& configurations,
+ sdbusplus::asio::object_server& objServerIn,
+ std::function<void()>&& callback) :
+ _systemConfiguration(systemConfiguration),
+ _missingConfigurations(missingConfigurations),
+ _configurations(configurations), objServer(objServerIn),
+ _callback(std::move(callback))
+{}
+void PerformScan::run()
+{
+ boost::container::flat_set<std::string> dbusProbeInterfaces;
+ std::vector<std::shared_ptr<PerformProbe>> dbusProbePointers;
+
+ for (auto it = _configurations.begin(); it != _configurations.end();)
+ {
+ auto findProbe = it->find("Probe");
+ auto findName = it->find("Name");
+
+ nlohmann::json probeCommand;
+ // check for poorly formatted fields, probe must be an array
+ if (findProbe == it->end())
+ {
+ std::cerr << "configuration file missing probe:\n " << *it << "\n";
+ it = _configurations.erase(it);
+ continue;
+ }
+ if ((*findProbe).type() != nlohmann::json::value_t::array)
+ {
+ probeCommand = nlohmann::json::array();
+ probeCommand.push_back(*findProbe);
+ }
+ else
+ {
+ probeCommand = *findProbe;
+ }
+
+ if (findName == it->end())
+ {
+ std::cerr << "configuration file missing name:\n " << *it << "\n";
+ it = _configurations.erase(it);
+ continue;
+ }
+ std::string probeName = *findName;
+
+ if (std::find(passedProbes.begin(), passedProbes.end(), probeName) !=
+ passedProbes.end())
+ {
+ it = _configurations.erase(it);
+ continue;
+ }
+ nlohmann::json* recordPtr = &(*it);
+
+ // store reference to this to children to makes sure we don't get
+ // destroyed too early
+ auto thisRef = shared_from_this();
+ auto probePointer = std::make_shared<PerformProbe>(
+ probeCommand, thisRef,
+ [&, recordPtr, probeName](FoundDeviceT& foundDevices,
+ const DBusProbeObjectT& allInterfaces) {
+ _passed = true;
+ std::set<nlohmann::json> usedNames;
+ passedProbes.push_back(probeName);
+ std::list<size_t> indexes(foundDevices.size());
+ std::iota(indexes.begin(), indexes.end(), 1);
+
+ size_t indexIdx = probeName.find('$');
+ bool hasTemplateName = (indexIdx != std::string::npos);
+
+ // copy over persisted configurations and make sure we remove
+ // indexes that are already used
+ for (auto itr = foundDevices.begin();
+ itr != foundDevices.end();)
+ {
+ std::string recordName =
+ getRecordName(std::get<0>(*itr), probeName);
+
+ auto fromLastJson = lastJson.find(recordName);
+ if (fromLastJson != lastJson.end())
+ {
+ auto findExposes = fromLastJson->find("Exposes");
+ // delete nulls from any updates
+ if (findExposes != fromLastJson->end())
+ {
+ auto copy = nlohmann::json::array();
+ for (auto& expose : *findExposes)
+ {
+ if (expose.is_null())
+ {
+ continue;
+ }
+ copy.emplace_back(expose);
+ }
+ *findExposes = copy;
+ }
+
+ // keep user changes
+ _systemConfiguration[recordName] = *fromLastJson;
+ _missingConfigurations.erase(recordName);
+ itr = foundDevices.erase(itr);
+ if (hasTemplateName)
+ {
+ auto nameIt = fromLastJson->find("Name");
+ if (nameIt == fromLastJson->end())
+ {
+ std::cerr << "Last JSON Illegal\n";
+ continue;
+ }
+ int index = 0;
+ auto str =
+ nameIt->get<std::string>().substr(indexIdx);
+ auto [p, ec] = std::from_chars(
+ str.data(), str.data() + str.size(), index);
+ if (ec != std::errc())
+ {
+ continue; // non-numeric replacement
+ }
+ usedNames.insert(nameIt.value());
+ auto usedIt = std::find(indexes.begin(),
+ indexes.end(), index);
+
+ if (usedIt == indexes.end())
+ {
+ continue; // less items now
+ }
+ indexes.erase(usedIt);
+ }
+
+ continue;
+ }
+ itr++;
+ }
+
+ std::optional<std::string> replaceStr;
+
+ DBusProbeObjectT::mapped_type emptyInterfaces;
+ boost::container::flat_map<std::string, BasicVariantType>
+ emptyProps;
+ emptyInterfaces.emplace(std::string{}, emptyProps);
+
+ for (auto& foundDeviceAndPath : foundDevices)
+ {
+ const boost::container::flat_map<
+ std::string, BasicVariantType>& foundDevice =
+ std::get<0>(foundDeviceAndPath);
+ const std::string& path = std::get<1>(foundDeviceAndPath);
+
+ // Need all interfaces on this path so that template
+ // substitutions can be done with any of the contained
+ // properties. If the probe that passed didn't use an
+ // interface, such as if it was just TRUE, then
+ // templateCharReplace will just get passed in an empty
+ // map.
+ const DBusProbeObjectT::mapped_type* allInterfacesOnPath =
+ &emptyInterfaces;
+
+ auto ifacesIt = allInterfaces.find(path);
+ if (ifacesIt != allInterfaces.end())
+ {
+ allInterfacesOnPath = &ifacesIt->second;
+ }
+
+ nlohmann::json record = *recordPtr;
+ std::string recordName =
+ getRecordName(foundDevice, probeName);
+ size_t foundDeviceIdx = indexes.front();
+ indexes.pop_front();
+
+ // check name first so we have no duplicate names
+ auto getName = record.find("Name");
+ if (getName == record.end())
+ {
+ std::cerr << "Record Missing Name! " << record.dump();
+ continue; // this should be impossible at this level
+ }
+
+ nlohmann::json copyForName = {{"Name", getName.value()}};
+ nlohmann::json::iterator copyIt = copyForName.begin();
+ std::optional<std::string> replaceVal =
+ templateCharReplace(copyIt, *allInterfacesOnPath,
+ foundDeviceIdx, replaceStr);
+
+ if (!replaceStr && replaceVal)
+ {
+ if (usedNames.find(copyIt.value()) != usedNames.end())
+ {
+ replaceStr = replaceVal;
+ copyForName = {{"Name", getName.value()}};
+ copyIt = copyForName.begin();
+ templateCharReplace(copyIt, *allInterfacesOnPath,
+ foundDeviceIdx, replaceStr);
+ }
+ }
+
+ if (replaceStr)
+ {
+ std::cerr << "Duplicates found, replacing "
+ << *replaceStr
+ << " with found device index.\n Consider "
+ "fixing template to not have duplicates\n";
+ }
+
+ for (auto keyPair = record.begin(); keyPair != record.end();
+ keyPair++)
+ {
+ if (keyPair.key() == "Name")
+ {
+ keyPair.value() = copyIt.value();
+ usedNames.insert(copyIt.value());
+
+ continue; // already covered above
+ }
+ templateCharReplace(keyPair, *allInterfacesOnPath,
+ foundDeviceIdx, replaceStr);
+ }
+
+ // insert into configuration temporarily to be able to
+ // reference ourselves
+
+ _systemConfiguration[recordName] = record;
+
+ auto findExpose = record.find("Exposes");
+ if (findExpose == record.end())
+ {
+ _systemConfiguration[recordName] = record;
+ continue;
+ }
+
+ for (auto& expose : *findExpose)
+ {
+ for (auto keyPair = expose.begin();
+ keyPair != expose.end(); keyPair++)
+ {
+
+ templateCharReplace(keyPair, *allInterfacesOnPath,
+ foundDeviceIdx, replaceStr);
+
+ bool isBind =
+ boost::starts_with(keyPair.key(), "Bind");
+ bool isDisable = keyPair.key() == "DisableNode";
+
+ // special cases
+ if (!(isBind || isDisable))
+ {
+ continue;
+ }
+
+ if (keyPair.value().type() !=
+ nlohmann::json::value_t::string &&
+ keyPair.value().type() !=
+ nlohmann::json::value_t::array)
+ {
+ std::cerr << "Value is invalid type "
+ << keyPair.key() << "\n";
+ continue;
+ }
+
+ std::vector<std::string> matches;
+ if (keyPair.value().type() ==
+ nlohmann::json::value_t::string)
+ {
+ matches.emplace_back(keyPair.value());
+ }
+ else
+ {
+ for (const auto& value : keyPair.value())
+ {
+ if (value.type() !=
+ nlohmann::json::value_t::string)
+ {
+ std::cerr << "Value is invalid type "
+ << value << "\n";
+ break;
+ }
+ matches.emplace_back(value);
+ }
+ }
+
+ std::set<std::string> foundMatches;
+ for (auto& configurationPair :
+ _systemConfiguration.items())
+ {
+ if (isDisable)
+ {
+ // don't disable ourselves
+ if (configurationPair.key() == recordName)
+ {
+ continue;
+ }
+ }
+ auto configListFind =
+ configurationPair.value().find("Exposes");
+
+ if (configListFind ==
+ configurationPair.value().end() ||
+ configListFind->type() !=
+ nlohmann::json::value_t::array)
+ {
+ continue;
+ }
+ for (auto& exposedObject : *configListFind)
+ {
+ auto matchIt = std::find_if(
+ matches.begin(), matches.end(),
+ [name = (exposedObject)["Name"]
+ .get<std::string>()](
+ const std::string& s) {
+ return s == name;
+ });
+ if (matchIt == matches.end())
+ {
+ continue;
+ }
+ foundMatches.insert(*matchIt);
+
+ if (isBind)
+ {
+ std::string bind = keyPair.key().substr(
+ sizeof("Bind") - 1);
+
+ exposedObject["Status"] = "okay";
+ expose[bind] = exposedObject;
+ }
+ else if (isDisable)
+ {
+ exposedObject["Status"] = "disabled";
+ }
+ }
+ }
+ if (foundMatches.size() != matches.size())
+ {
+ std::cerr << "configuration file "
+ "dependency error, "
+ "could not find "
+ << keyPair.key() << " "
+ << keyPair.value() << "\n";
+ }
+ }
+ }
+ // overwrite ourselves with cleaned up version
+ _systemConfiguration[recordName] = record;
+ _missingConfigurations.erase(recordName);
+ }
+ });
+
+ // parse out dbus probes by discarding other probe types, store in a
+ // map
+ for (const nlohmann::json& probeJson : probeCommand)
+ {
+ const std::string* probe = probeJson.get_ptr<const std::string*>();
+ if (probe == nullptr)
+ {
+ std::cerr << "Probe statement wasn't a string, can't parse";
+ continue;
+ }
+ bool found = false;
+ boost::container::flat_map<const char*, probe_type_codes,
+ CmpStr>::const_iterator probeType;
+ for (probeType = probeTypes.begin(); probeType != probeTypes.end();
+ ++probeType)
+ {
+ if (probe->find(probeType->first) != std::string::npos)
+ {
+ found = true;
+ break;
+ }
+ }
+ if (found)
+ {
+ continue;
+ }
+ // syntax requires probe before first open brace
+ auto findStart = probe->find('(');
+ std::string interface = probe->substr(0, findStart);
+ dbusProbeInterfaces.emplace(interface);
+ dbusProbePointers.emplace_back(probePointer);
+ }
+ it++;
+ }
+
+ // probe vector stores a shared_ptr to each PerformProbe that cares
+ // about a dbus interface
+ findDbusObjects(std::move(dbusProbePointers),
+ std::move(dbusProbeInterfaces), shared_from_this());
+ if constexpr (debug)
+ {
+ std::cerr << __func__ << " " << __LINE__ << "\n";
+ }
+}
+
+PerformScan::~PerformScan()
+{
+ if (_passed)
+ {
+ auto nextScan = std::make_shared<PerformScan>(
+ _systemConfiguration, _missingConfigurations, _configurations,
+ objServer, std::move(_callback));
+ nextScan->passedProbes = std::move(passedProbes);
+ nextScan->dbusProbeObjects = std::move(dbusProbeObjects);
+ nextScan->run();
+
+ if constexpr (debug)
+ {
+ std::cerr << __func__ << " " << __LINE__ << "\n";
+ }
+ }
+ else
+ {
+ _callback();
+
+ if constexpr (debug)
+ {
+ std::cerr << __func__ << " " << __LINE__ << "\n";
+ }
+ }
+}
diff --git a/src/meson.build b/src/meson.build
index 8a9811b..bab416c 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -3,6 +3,7 @@
executable(
'entity-manager',
'EntityManager.cpp',
+ 'PerformScan.cpp',
'Overlay.cpp',
'Utils.cpp',
cpp_args: cpp_args + ['-DBOOST_ASIO_DISABLE_THREADS'],