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/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";
+ }
+ }
+}