blob: 91372e45b717d8aa0c91548649c1db5f3c93907b [file] [log] [blame]
/*
// 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 perform_scan.cpp
#include "entity_manager.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 <phosphor-logging/lg2.hpp>
#include <charconv>
/* Hacks from splitting entity_manager.cpp */
// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
extern std::shared_ptr<sdbusplus::asio::connection> systemBus;
extern nlohmann::json lastJson;
extern void
propertiesChangedCallback(nlohmann::json& systemConfiguration,
sdbusplus::asio::object_server& objServer);
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
using GetSubTreeType = std::vector<
std::pair<std::string,
std::vector<std::pair<std::string, std::vector<std::string>>>>>;
constexpr const int32_t maxMapperDepth = 0;
struct DBusInterfaceInstance
{
std::string busName;
std::string path;
std::string interface;
};
void getInterfaces(
const DBusInterfaceInstance& instance,
const std::vector<std::shared_ptr<PerformProbe>>& probeVector,
const std::shared_ptr<PerformScan>& scan, size_t retries = 5)
{
if (retries == 0U)
{
std::cerr << "retries exhausted on " << instance.busName << " "
<< instance.path << " " << instance.interface << "\n";
return;
}
systemBus->async_method_call(
[instance, scan, probeVector,
retries](boost::system::error_code& errc, const DBusInterface& resp) {
if (errc)
{
std::cerr << "error calling getall on " << instance.busName
<< " " << instance.path << " "
<< instance.interface << "\n";
auto timer = std::make_shared<boost::asio::steady_timer>(io);
timer->expires_after(std::chrono::seconds(2));
timer->async_wait([timer, instance, scan, probeVector,
retries](const boost::system::error_code&) {
getInterfaces(instance, probeVector, scan, retries - 1);
});
return;
}
scan->dbusProbeObjects[instance.path][instance.interface] = resp;
},
instance.busName, instance.path, "org.freedesktop.DBus.Properties",
"GetAll", instance.interface);
}
static void registerCallback(nlohmann::json& systemConfiguration,
sdbusplus::asio::object_server& objServer,
const std::string& path)
{
static boost::container::flat_map<std::string, sdbusplus::bus::match_t>
dbusMatches;
auto find = dbusMatches.find(path);
if (find != dbusMatches.end())
{
return;
}
std::function<void(sdbusplus::message_t & message)> eventHandler =
[&](sdbusplus::message_t&) {
propertiesChangedCallback(systemConfiguration, objServer);
};
sdbusplus::bus::match_t match(
static_cast<sdbusplus::bus_t&>(*systemBus),
"type='signal',member='PropertiesChanged',path='" + path + "'",
eventHandler);
dbusMatches.emplace(path, std::move(match));
}
static void
processDbusObjects(std::vector<std::shared_ptr<PerformProbe>>& probeVector,
const std::shared_ptr<PerformScan>& scan,
const GetSubTreeType& interfaceSubtree)
{
for (const auto& [path, object] : interfaceSubtree)
{
// Get a PropertiesChanged callback for all interfaces on this path.
registerCallback(scan->_systemConfiguration, scan->objServer, path);
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"))
{
getInterfaces({busname, path, iface}, probeVector, scan);
}
}
}
}
}
// 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 {
if (ec)
{
if (ec.value() == ENOENT)
{
return; // wasn't found by mapper
}
std::cerr << "Error communicating to mapper.\n";
if (retries == 0U)
{
// if we can't communicate to the mapper something is very
// wrong
std::exit(EXIT_FAILURE);
}
auto 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;
}
processDbusObjects(probeVector, scan, interfaceSubtree);
},
"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", maxMapperDepth,
interfaces);
}
static std::string getRecordName(const DBusInterface& 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 (const auto& devPair : probe)
{
device.push_back(devPair.first);
std::visit([&device](auto&& v) { device.push_back(v); },
devPair.second);
}
// hashes are hard to distinguish, use the non-hashed version if we want
// debug
// return probeName + device.dump();
return std::to_string(std::hash<std::string>{}(probeName + device.dump()));
}
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))
{}
static void pruneRecordExposes(nlohmann::json& record)
{
auto findExposes = record.find("Exposes");
if (findExposes == record.end())
{
return;
}
auto copy = nlohmann::json::array();
for (auto& expose : *findExposes)
{
if (!expose.is_null())
{
copy.emplace_back(expose);
}
}
*findExposes = copy;
}
static void recordDiscoveredIdentifiers(
std::set<nlohmann::json>& usedNames, std::list<size_t>& indexes,
const std::string& probeName, const nlohmann::json& record)
{
size_t indexIdx = probeName.find('$');
if (indexIdx == std::string::npos)
{
return;
}
auto nameIt = record.find("Name");
if (nameIt == record.end())
{
std::cerr << "Last JSON Illegal\n";
return;
}
int index = 0;
auto str = nameIt->get<std::string>().substr(indexIdx);
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
const char* endPtr = str.data() + str.size();
auto [p, ec] = std::from_chars(str.data(), endPtr, index);
if (ec != std::errc())
{
return; // non-numeric replacement
}
usedNames.insert(nameIt.value());
auto usedIt = std::find(indexes.begin(), indexes.end(), index);
if (usedIt != indexes.end())
{
indexes.erase(usedIt);
}
}
static bool extractExposeActionRecordNames(std::vector<std::string>& matches,
nlohmann::json::iterator& keyPair)
{
if (keyPair.value().is_string())
{
matches.emplace_back(keyPair.value());
return true;
}
if (keyPair.value().is_array())
{
for (const auto& value : keyPair.value())
{
if (!value.is_string())
{
std::cerr << "Value is invalid type " << value << "\n";
break;
}
matches.emplace_back(value);
}
return true;
}
std::cerr << "Value is invalid type " << keyPair.key() << "\n";
return false;
}
static std::optional<std::vector<std::string>::iterator> findExposeActionRecord(
std::vector<std::string>& matches, const nlohmann::json& record)
{
const auto& name = (record)["Name"].get_ref<const std::string&>();
auto compare = [&name](const std::string& s) { return s == name; };
auto matchIt = std::find_if(matches.begin(), matches.end(), compare);
if (matchIt == matches.end())
{
return std::nullopt;
}
return matchIt;
}
static void applyBindExposeAction(nlohmann::json& exposedObject,
nlohmann::json& expose,
const std::string& propertyName)
{
if (boost::starts_with(propertyName, "Bind"))
{
std::string bind = propertyName.substr(sizeof("Bind") - 1);
exposedObject["Status"] = "okay";
expose[bind] = exposedObject;
}
}
static void applyDisableExposeAction(nlohmann::json& exposedObject,
const std::string& propertyName)
{
if (propertyName == "DisableNode")
{
exposedObject["Status"] = "disabled";
}
}
static void applyConfigExposeActions(
std::vector<std::string>& matches, nlohmann::json& expose,
const std::string& propertyName, nlohmann::json& configExposes)
{
for (auto& exposedObject : configExposes)
{
auto match = findExposeActionRecord(matches, exposedObject);
if (match)
{
matches.erase(*match);
applyBindExposeAction(exposedObject, expose, propertyName);
applyDisableExposeAction(exposedObject, propertyName);
}
}
}
static void applyExposeActions(
nlohmann::json& systemConfiguration, const std::string& recordName,
nlohmann::json& expose, nlohmann::json::iterator& keyPair)
{
bool isBind = boost::starts_with(keyPair.key(), "Bind");
bool isDisable = keyPair.key() == "DisableNode";
bool isExposeAction = isBind || isDisable;
if (!isExposeAction)
{
return;
}
std::vector<std::string> matches;
if (!extractExposeActionRecordNames(matches, keyPair))
{
return;
}
for (const auto& [configId, config] : systemConfiguration.items())
{
// don't disable ourselves
if (isDisable && configId == recordName)
{
continue;
}
auto configListFind = config.find("Exposes");
if (configListFind == config.end())
{
continue;
}
if (!configListFind->is_array())
{
continue;
}
applyConfigExposeActions(matches, expose, keyPair.key(),
*configListFind);
}
if (!matches.empty())
{
std::cerr << "configuration file dependency error, could not find "
<< keyPair.key() << " " << keyPair.value() << "\n";
}
}
static std::string generateDeviceName(
const std::set<nlohmann::json>& usedNames, const DBusObject& dbusObject,
size_t foundDeviceIdx, const std::string& nameTemplate,
std::optional<std::string>& replaceStr)
{
nlohmann::json copyForName = {{"Name", nameTemplate}};
nlohmann::json::iterator copyIt = copyForName.begin();
std::optional<std::string> replaceVal =
templateCharReplace(copyIt, dbusObject, foundDeviceIdx, replaceStr);
if (!replaceStr && replaceVal)
{
if (usedNames.find(copyIt.value()) != usedNames.end())
{
replaceStr = replaceVal;
copyForName = {{"Name", nameTemplate}};
copyIt = copyForName.begin();
templateCharReplace(copyIt, dbusObject, foundDeviceIdx, replaceStr);
}
}
if (replaceStr)
{
std::cerr << "Duplicates found, replacing " << *replaceStr
<< " with found device index.\n Consider "
"fixing template to not have duplicates\n";
}
return copyIt.value();
}
void PerformScan::updateSystemConfiguration(const nlohmann::json& recordRef,
const std::string& probeName,
FoundDevices& foundDevices)
{
_passed = true;
passedProbes.push_back(probeName);
std::set<nlohmann::json> usedNames;
std::list<size_t> indexes(foundDevices.size());
std::iota(indexes.begin(), indexes.end(), 1);
// 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(itr->interface, probeName);
auto record = _systemConfiguration.find(recordName);
if (record == _systemConfiguration.end())
{
record = lastJson.find(recordName);
if (record == lastJson.end())
{
itr++;
continue;
}
pruneRecordExposes(*record);
_systemConfiguration[recordName] = *record;
}
_missingConfigurations.erase(recordName);
// We've processed the device, remove it and advance the
// iterator
itr = foundDevices.erase(itr);
recordDiscoveredIdentifiers(usedNames, indexes, probeName, *record);
}
std::optional<std::string> replaceStr;
DBusObject emptyObject;
DBusInterface emptyInterface;
emptyObject.emplace(std::string{}, emptyInterface);
for (const auto& [foundDevice, path] : foundDevices)
{
// 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.
auto objectIt = dbusProbeObjects.find(path);
const DBusObject& dbusObject = (objectIt == dbusProbeObjects.end())
? emptyObject
: objectIt->second;
nlohmann::json record = recordRef;
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
}
std::string deviceName = generateDeviceName(
usedNames, dbusObject, foundDeviceIdx, getName.value(), replaceStr);
getName.value() = deviceName;
usedNames.insert(deviceName);
for (auto keyPair = record.begin(); keyPair != record.end(); keyPair++)
{
if (keyPair.key() != "Name")
{
templateCharReplace(keyPair, dbusObject, foundDeviceIdx,
replaceStr);
}
}
// insert into configuration temporarily to be able to
// reference ourselves
_systemConfiguration[recordName] = record;
auto findExpose = record.find("Exposes");
if (findExpose == record.end())
{
continue;
}
for (auto& expose : *findExpose)
{
for (auto keyPair = expose.begin(); keyPair != expose.end();
keyPair++)
{
templateCharReplace(keyPair, dbusObject, foundDeviceIdx,
replaceStr);
applyExposeActions(_systemConfiguration, recordName, expose,
keyPair);
}
}
// overwrite ourselves with cleaned up version
_systemConfiguration[recordName] = record;
_missingConfigurations.erase(recordName);
}
}
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();)
{
// check for poorly formatted fields, probe must be an array
auto findProbe = it->find("Probe");
if (findProbe == it->end())
{
std::cerr << "configuration file missing probe:\n " << *it << "\n";
it = _configurations.erase(it);
continue;
}
auto findName = it->find("Name");
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& recordRef = *it;
nlohmann::json probeCommand;
if ((*findProbe).type() != nlohmann::json::value_t::array)
{
probeCommand = nlohmann::json::array();
probeCommand.push_back(*findProbe);
}
else
{
probeCommand = *findProbe;
}
// 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>(
recordRef, probeCommand, probeName, thisRef);
// 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;
}
if (findProbeType(*probe))
{
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());
}
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();
}
else
{
_callback();
}
}