blob: 242903b94c67b7786e3374b0c4562f35b8d9abc2 [file] [log] [blame]
#include "associations.hpp"
#include "handler.hpp"
#include "processing.hpp"
#include "types.hpp"
#include <tinyxml2.h>
#include <boost/asio/io_context.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/container/flat_map.hpp>
#include <sdbusplus/asio/connection.hpp>
#include <sdbusplus/asio/object_server.hpp>
#include <xyz/openbmc_project/Common/error.hpp>
#include <atomic>
#include <chrono>
#include <exception>
#include <iomanip>
#include <iostream>
#include <string>
#include <string_view>
#include <utility>
AssociationMaps associationMaps;
void updateOwners(sdbusplus::asio::connection* conn,
boost::container::flat_map<std::string, std::string>& owners,
const std::string& newObject)
{
if (newObject.starts_with(":"))
{
return;
}
conn->async_method_call(
[&, newObject](const boost::system::error_code ec,
const std::string& nameOwner) {
if (ec)
{
std::cerr << "Error getting owner of " << newObject << " : " << ec
<< "\n";
return;
}
owners[nameOwner] = newObject;
},
"org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetNameOwner",
newObject);
}
void sendIntrospectionCompleteSignal(sdbusplus::asio::connection* systemBus,
const std::string& processName)
{
// TODO(ed) This signal doesn't get exposed properly in the
// introspect right now. Find out how to register signals in
// sdbusplus
sdbusplus::message_t m = systemBus->new_signal(
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper.Private", "IntrospectionComplete");
m.append(processName);
m.signal_send();
}
struct InProgressIntrospect
{
InProgressIntrospect() = delete;
InProgressIntrospect(const InProgressIntrospect&) = delete;
InProgressIntrospect(InProgressIntrospect&&) = delete;
InProgressIntrospect& operator=(const InProgressIntrospect&) = delete;
InProgressIntrospect& operator=(InProgressIntrospect&&) = delete;
InProgressIntrospect(
sdbusplus::asio::connection* systemBus, boost::asio::io_context& io,
const std::string& processName, AssociationMaps& am
#ifdef MAPPER_ENABLE_DEBUG
,
std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
globalStartTime
#endif
) :
systemBus(systemBus),
io(io), processName(processName), assocMaps(am)
#ifdef MAPPER_ENABLE_DEBUG
,
globalStartTime(std::move(globalStartTime)),
processStartTime(std::chrono::steady_clock::now())
#endif
{}
~InProgressIntrospect()
{
try
{
sendIntrospectionCompleteSignal(systemBus, processName);
#ifdef MAPPER_ENABLE_DEBUG
std::chrono::duration<float> diff =
std::chrono::steady_clock::now() - processStartTime;
std::cout << std::setw(50) << processName << " scan took "
<< diff.count() << " seconds\n";
// If we're the last outstanding caller globally, calculate the
// time it took
if (globalStartTime != nullptr && globalStartTime.use_count() == 1)
{
diff = std::chrono::steady_clock::now() - *globalStartTime;
std::cout << "Total scan took " << diff.count()
<< " seconds to complete\n";
}
#endif
}
catch (const std::exception& e)
{
std::cerr
<< "Terminating, unhandled exception while introspecting: "
<< e.what() << "\n";
std::terminate();
}
catch (...)
{
std::cerr
<< "Terminating, unhandled exception while introspecting\n";
std::terminate();
}
}
sdbusplus::asio::connection* systemBus;
boost::asio::io_context& io;
std::string processName;
AssociationMaps& assocMaps;
#ifdef MAPPER_ENABLE_DEBUG
std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
globalStartTime;
std::chrono::time_point<std::chrono::steady_clock> processStartTime;
#endif
};
void doAssociations(boost::asio::io_context& io,
sdbusplus::asio::connection* systemBus,
InterfaceMapType& interfaceMap,
sdbusplus::asio::object_server& objectServer,
const std::string& processName, const std::string& path,
int timeoutRetries = 0)
{
constexpr int maxTimeoutRetries = 3;
systemBus->async_method_call(
[&io, &objectServer, path, processName, &interfaceMap, systemBus,
timeoutRetries](
const boost::system::error_code ec,
const std::variant<std::vector<Association>>& variantAssociations) {
if (ec)
{
if (ec.value() == boost::system::errc::timed_out &&
timeoutRetries < maxTimeoutRetries)
{
doAssociations(io, systemBus, interfaceMap, objectServer,
processName, path, timeoutRetries + 1);
return;
}
std::cerr << "Error getting associations from " << path << "\n";
}
std::vector<Association> associations =
std::get<std::vector<Association>>(variantAssociations);
associationChanged(io, objectServer, associations, path, processName,
interfaceMap, associationMaps);
},
processName, path, "org.freedesktop.DBus.Properties", "Get",
assocDefsInterface, assocDefsProperty);
}
void doIntrospect(boost::asio::io_context& io,
sdbusplus::asio::connection* systemBus,
const std::shared_ptr<InProgressIntrospect>& transaction,
InterfaceMapType& interfaceMap,
sdbusplus::asio::object_server& objectServer,
const std::string& path, int timeoutRetries = 0)
{
constexpr int maxTimeoutRetries = 3;
systemBus->async_method_call(
[&io, &interfaceMap, &objectServer, transaction, path, systemBus,
timeoutRetries](const boost::system::error_code ec,
const std::string& introspectXml) {
if (ec)
{
if (ec.value() == boost::system::errc::timed_out &&
timeoutRetries < maxTimeoutRetries)
{
doIntrospect(io, systemBus, transaction, interfaceMap,
objectServer, path, timeoutRetries + 1);
return;
}
std::cerr << "Introspect call failed with error: " << ec << ", "
<< ec.message()
<< " on process: " << transaction->processName
<< " path: " << path << "\n";
return;
}
tinyxml2::XMLDocument doc;
tinyxml2::XMLError e = doc.Parse(introspectXml.c_str());
if (e != tinyxml2::XMLError::XML_SUCCESS)
{
std::cerr << "XML parsing failed\n";
return;
}
tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
if (pRoot == nullptr)
{
std::cerr << "XML document did not contain any data\n";
return;
}
auto& thisPathMap = interfaceMap[path];
tinyxml2::XMLElement* pElement = pRoot->FirstChildElement("interface");
while (pElement != nullptr)
{
const char* ifaceName = pElement->Attribute("name");
if (ifaceName == nullptr)
{
continue;
}
thisPathMap[transaction->processName].emplace(ifaceName);
if (std::strcmp(ifaceName, assocDefsInterface) == 0)
{
doAssociations(io, systemBus, interfaceMap, objectServer,
transaction->processName, path);
}
pElement = pElement->NextSiblingElement("interface");
}
// Check if this new path has a pending association that can
// now be completed.
checkIfPendingAssociation(io, path, interfaceMap,
transaction->assocMaps, objectServer);
pElement = pRoot->FirstChildElement("node");
while (pElement != nullptr)
{
const char* childPath = pElement->Attribute("name");
if (childPath != nullptr)
{
std::string parentPath(path);
if (parentPath == "/")
{
parentPath.clear();
}
doIntrospect(io, systemBus, transaction, interfaceMap,
objectServer, parentPath + "/" + childPath);
}
pElement = pElement->NextSiblingElement("node");
}
},
transaction->processName, path, "org.freedesktop.DBus.Introspectable",
"Introspect");
}
void startNewIntrospect(
sdbusplus::asio::connection* systemBus, boost::asio::io_context& io,
InterfaceMapType& interfaceMap, const std::string& processName,
AssociationMaps& assocMaps,
#ifdef MAPPER_ENABLE_DEBUG
std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
globalStartTime,
#endif
sdbusplus::asio::object_server& objectServer)
{
if (needToIntrospect(processName))
{
std::shared_ptr<InProgressIntrospect> transaction =
std::make_shared<InProgressIntrospect>(systemBus, io, processName,
assocMaps
#ifdef MAPPER_ENABLE_DEBUG
,
globalStartTime
#endif
);
doIntrospect(io, systemBus, transaction, interfaceMap, objectServer,
"/");
}
}
void doListNames(
boost::asio::io_context& io, InterfaceMapType& interfaceMap,
sdbusplus::asio::connection* systemBus,
boost::container::flat_map<std::string, std::string>& nameOwners,
AssociationMaps& assocMaps, sdbusplus::asio::object_server& objectServer)
{
systemBus->async_method_call(
[&io, &interfaceMap, &nameOwners, &objectServer, systemBus,
&assocMaps](const boost::system::error_code ec,
std::vector<std::string> processNames) {
if (ec)
{
std::cerr << "Error getting names: " << ec << "\n";
std::exit(EXIT_FAILURE);
return;
}
// Try to make startup consistent
std::sort(processNames.begin(), processNames.end());
#ifdef MAPPER_ENABLE_DEBUG
std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
globalStartTime = std::make_shared<
std::chrono::time_point<std::chrono::steady_clock>>(
std::chrono::steady_clock::now());
#endif
for (const std::string& processName : processNames)
{
if (needToIntrospect(processName))
{
startNewIntrospect(systemBus, io, interfaceMap, processName,
assocMaps,
#ifdef MAPPER_ENABLE_DEBUG
globalStartTime,
#endif
objectServer);
updateOwners(systemBus, nameOwners, processName);
}
}
},
"org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus",
"ListNames");
}
// Remove parents of the passed in path that:
// 1) Only have the 3 default interfaces on them
// - Means D-Bus created these, not application code,
// with the Properties, Introspectable, and Peer ifaces
// 2) Have no other child for this owner
void removeUnneededParents(const std::string& objectPath,
const std::string& owner,
InterfaceMapType& interfaceMap)
{
auto parent = objectPath;
while (true)
{
auto pos = parent.find_last_of('/');
if ((pos == std::string::npos) || (pos == 0))
{
break;
}
parent = parent.substr(0, pos);
auto parentIt = interfaceMap.find(parent);
if (parentIt == interfaceMap.end())
{
break;
}
auto ifacesIt = parentIt->second.find(owner);
if (ifacesIt == parentIt->second.end())
{
break;
}
if (ifacesIt->second.size() != 3)
{
break;
}
auto childPath = parent + '/';
// Remove this parent if there isn't a remaining child on this owner
auto child = std::find_if(interfaceMap.begin(), interfaceMap.end(),
[&owner, &childPath](const auto& entry) {
return entry.first.starts_with(childPath) &&
(entry.second.find(owner) != entry.second.end());
});
if (child == interfaceMap.end())
{
parentIt->second.erase(ifacesIt);
if (parentIt->second.empty())
{
interfaceMap.erase(parentIt);
}
}
else
{
break;
}
}
}
int main()
{
boost::asio::io_context io;
std::shared_ptr<sdbusplus::asio::connection> systemBus =
std::make_shared<sdbusplus::asio::connection>(io);
sdbusplus::asio::object_server server(systemBus);
// Construct a signal set registered for process termination.
boost::asio::signal_set signals(io, SIGINT, SIGTERM);
signals.async_wait(
[&io](const boost::system::error_code&, int) { io.stop(); });
InterfaceMapType interfaceMap;
boost::container::flat_map<std::string, std::string> nameOwners;
auto nameChangeHandler = [&interfaceMap, &io, &nameOwners, &server,
systemBus](sdbusplus::message_t& message) {
std::string name; // well-known
std::string oldOwner; // unique-name
std::string newOwner; // unique-name
message.read(name, oldOwner, newOwner);
if (name.starts_with(':'))
{
// We should do nothing with unique-name connections.
return;
}
if (!oldOwner.empty())
{
processNameChangeDelete(io, nameOwners, name, oldOwner,
interfaceMap, associationMaps, server);
}
if (!newOwner.empty())
{
#ifdef MAPPER_ENABLE_DEBUG
auto transaction = std::make_shared<
std::chrono::time_point<std::chrono::steady_clock>>(
std::chrono::steady_clock::now());
#endif
// New daemon added
if (needToIntrospect(name))
{
nameOwners[newOwner] = name;
startNewIntrospect(systemBus.get(), io, interfaceMap, name,
associationMaps,
#ifdef MAPPER_ENABLE_DEBUG
transaction,
#endif
server);
}
}
};
sdbusplus::bus::match_t nameOwnerChanged(
static_cast<sdbusplus::bus_t&>(*systemBus),
sdbusplus::bus::match::rules::nameOwnerChanged(),
std::move(nameChangeHandler));
auto interfacesAddedHandler = [&io, &interfaceMap, &nameOwners,
&server](sdbusplus::message_t& message) {
sdbusplus::message::object_path objPath;
InterfacesAdded interfacesAdded;
message.read(objPath, interfacesAdded);
std::string wellKnown;
if (!getWellKnown(nameOwners, message.get_sender(), wellKnown))
{
return; // only introspect well-known
}
if (needToIntrospect(wellKnown))
{
processInterfaceAdded(io, interfaceMap, objPath, interfacesAdded,
wellKnown, associationMaps, server);
}
};
sdbusplus::bus::match_t interfacesAdded(
static_cast<sdbusplus::bus_t&>(*systemBus),
sdbusplus::bus::match::rules::interfacesAdded(),
std::move(interfacesAddedHandler));
auto interfacesRemovedHandler = [&io, &interfaceMap, &nameOwners,
&server](sdbusplus::message_t& message) {
sdbusplus::message::object_path objPath;
std::vector<std::string> interfacesRemoved;
message.read(objPath, interfacesRemoved);
auto connectionMap = interfaceMap.find(objPath.str);
if (connectionMap == interfaceMap.end())
{
return;
}
std::string sender;
if (!getWellKnown(nameOwners, message.get_sender(), sender))
{
return;
}
for (const std::string& interface : interfacesRemoved)
{
auto interfaceSet = connectionMap->second.find(sender);
if (interfaceSet == connectionMap->second.end())
{
continue;
}
if (interface == assocDefsInterface)
{
removeAssociation(io, objPath.str, sender, server,
associationMaps);
}
interfaceSet->second.erase(interface);
if (interfaceSet->second.empty())
{
// If this was the last interface on this connection,
// erase the connection
connectionMap->second.erase(interfaceSet);
// Instead of checking if every single path is the endpoint
// of an association that needs to be moved to pending,
// only check when the only remaining owner of this path is
// ourself, which would be because we still own the
// association path.
if ((connectionMap->second.size() == 1) &&
(connectionMap->second.begin()->first ==
"xyz.openbmc_project.ObjectMapper"))
{
// Remove the 2 association D-Bus paths and move the
// association to pending.
moveAssociationToPending(io, objPath.str, associationMaps,
server);
}
}
}
// If this was the last connection on this object path,
// erase the object path
if (connectionMap->second.empty())
{
interfaceMap.erase(connectionMap);
}
removeUnneededParents(objPath.str, sender, interfaceMap);
};
sdbusplus::bus::match_t interfacesRemoved(
static_cast<sdbusplus::bus_t&>(*systemBus),
sdbusplus::bus::match::rules::interfacesRemoved(),
std::move(interfacesRemovedHandler));
auto associationChangedHandler = [&io, &server, &nameOwners, &interfaceMap](
sdbusplus::message_t& message) {
std::string objectName;
boost::container::flat_map<std::string,
std::variant<std::vector<Association>>>
values;
message.read(objectName, values);
auto prop = values.find(assocDefsProperty);
if (prop != values.end())
{
std::vector<Association> associations =
std::get<std::vector<Association>>(prop->second);
std::string wellKnown;
if (!getWellKnown(nameOwners, message.get_sender(), wellKnown))
{
return;
}
associationChanged(io, server, associations, message.get_path(),
wellKnown, interfaceMap, associationMaps);
}
};
sdbusplus::bus::match_t assocChangedMatch(
static_cast<sdbusplus::bus_t&>(*systemBus),
sdbusplus::bus::match::rules::interface(
"org.freedesktop.DBus.Properties") +
sdbusplus::bus::match::rules::member("PropertiesChanged") +
sdbusplus::bus::match::rules::argN(0, assocDefsInterface),
std::move(associationChangedHandler));
std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
server.add_interface("/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper");
iface->register_method(
"GetAncestors", [&interfaceMap](std::string& reqPath,
std::vector<std::string>& interfaces) {
return getAncestors(interfaceMap, reqPath, interfaces);
});
iface->register_method(
"GetObject", [&interfaceMap](const std::string& path,
std::vector<std::string>& interfaces) {
return getObject(interfaceMap, path, interfaces);
});
iface->register_method(
"GetSubTree", [&interfaceMap](std::string& reqPath, int32_t depth,
std::vector<std::string>& interfaces) {
return getSubTree(interfaceMap, reqPath, depth, interfaces);
});
iface->register_method(
"GetSubTreePaths",
[&interfaceMap](std::string& reqPath, int32_t depth,
std::vector<std::string>& interfaces) {
return getSubTreePaths(interfaceMap, reqPath, depth, interfaces);
});
iface->register_method(
"GetAssociatedSubTree",
[&interfaceMap](const sdbusplus::message::object_path& associationPath,
const sdbusplus::message::object_path& reqPath,
int32_t depth, std::vector<std::string>& interfaces) {
return getAssociatedSubTree(interfaceMap, associationMaps,
associationPath, reqPath, depth,
interfaces);
});
iface->register_method(
"GetAssociatedSubTreePaths",
[&interfaceMap](const sdbusplus::message::object_path& associationPath,
const sdbusplus::message::object_path& reqPath,
int32_t depth, std::vector<std::string>& interfaces) {
return getAssociatedSubTreePaths(interfaceMap, associationMaps,
associationPath, reqPath, depth,
interfaces);
});
iface->initialize();
io.post([&]() {
doListNames(io, interfaceMap, systemBus.get(), nameOwners,
associationMaps, server);
});
systemBus->request_name("xyz.openbmc_project.ObjectMapper");
io.run();
}