blob: 5a53e4d621d345e67753fe738eb30583b8aba264 [file] [log] [blame]
Matt Spinlerdd945862018-09-07 12:41:05 -05001#include "src/argument.hpp"
2
Ed Tanous60520632018-06-11 17:46:52 -07003#include <tinyxml2.h>
4
5#include <atomic>
6#include <boost/algorithm/string/predicate.hpp>
7#include <boost/container/flat_map.hpp>
8#include <boost/container/flat_set.hpp>
9#include <chrono>
10#include <iomanip>
11#include <iostream>
12#include <sdbusplus/asio/connection.hpp>
13#include <sdbusplus/asio/object_server.hpp>
14
15constexpr const char* OBJECT_MAPPER_DBUS_NAME =
16 "xyz.openbmc_project.ObjectMapper";
17constexpr const char* ASSOCIATIONS_INTERFACE = "org.openbmc.Associations";
Matt Spinler1036b4d2018-09-18 16:45:29 -050018constexpr const char* XYZ_ASSOCIATION_INTERFACE =
19 "xyz.openbmc_project.Association";
Ed Tanous60520632018-06-11 17:46:52 -070020
21// interface_map_type is the underlying datastructure the mapper uses.
22// The 3 levels of map are
23// object paths
24// connection names
25// interface names
26using interface_map_type = boost::container::flat_map<
27 std::string, boost::container::flat_map<
28 std::string, boost::container::flat_set<std::string>>>;
29
30using Association = std::tuple<std::string, std::string, std::string>;
31
Matt Spinler1036b4d2018-09-18 16:45:29 -050032// Associations and some metadata are stored in associationInterfaces.
33// The fields are:
34// * ifacePos - holds the D-Bus interface object
Matt Spinler937a2322019-01-23 13:54:22 -060035// * endpointsPos - holds the endpoints array that shadows the property
Matt Spinler1036b4d2018-09-18 16:45:29 -050036static constexpr auto ifacePos = 0;
37static constexpr auto endpointsPos = 1;
Matt Spinler1036b4d2018-09-18 16:45:29 -050038using Endpoints = std::vector<std::string>;
39boost::container::flat_map<
Matt Spinler937a2322019-01-23 13:54:22 -060040 std::string,
41 std::tuple<std::shared_ptr<sdbusplus::asio::dbus_interface>, Endpoints>>
Ed Tanous60520632018-06-11 17:46:52 -070042 associationInterfaces;
43
Matt Spinler937a2322019-01-23 13:54:22 -060044// The associationOwners map contains information about creators of
45// associations, so that when a org.openbmc.Association interface is
46// removed or its 'associations' property is changed, the mapper owned
47// association objects can be correctly handled. It is a map of the
48// object path of the org.openbmc.Association owner to a map of the
49// service the path is owned by, to a map of the association objects to
50// their endpoint paths:
51// map[ownerPath : map[service : map[assocPath : [endpoint paths]]]
52// For example:
53// [/logging/entry/1 :
54// [xyz.openbmc_project.Logging :
55// [/logging/entry/1/callout : [/system/cpu0],
56// /system/cpu0/fault : [/logging/entry/1]]]]
57
58using AssociationPaths =
59 boost::container::flat_map<std::string,
60 boost::container::flat_set<std::string>>;
61
62using AssociationOwnersType = boost::container::flat_map<
63 std::string, boost::container::flat_map<std::string, AssociationPaths>>;
64
65AssociationOwnersType associationOwners;
66
Matt Spinlerdd945862018-09-07 12:41:05 -050067static boost::container::flat_set<std::string> service_whitelist;
68static boost::container::flat_set<std::string> service_blacklist;
Matt Spinlerdd945862018-09-07 12:41:05 -050069
Ed Tanous60520632018-06-11 17:46:52 -070070/** Exception thrown when a path is not found in the object list. */
71struct NotFoundException final : public sdbusplus::exception_t
72{
73 const char* name() const noexcept override
74 {
75 return "org.freedesktop.DBus.Error.FileNotFound";
76 };
77 const char* description() const noexcept override
78 {
79 return "path or object not found";
80 };
81 const char* what() const noexcept override
82 {
83 return "org.freedesktop.DBus.Error.FileNotFound: "
84 "The requested object was not found";
85 };
86};
87
88bool get_well_known(
89 boost::container::flat_map<std::string, std::string>& owners,
90 const std::string& request, std::string& well_known)
91{
92 // If it's already a well known name, just return
93 if (!boost::starts_with(request, ":"))
94 {
95 well_known = request;
96 return true;
97 }
98
99 auto it = owners.find(request);
100 if (it == owners.end())
101 {
102 return false;
103 }
104 well_known = it->second;
105 return true;
106}
107
108void update_owners(sdbusplus::asio::connection* conn,
109 boost::container::flat_map<std::string, std::string>& owners,
110 const std::string& new_object)
111{
112 if (boost::starts_with(new_object, ":"))
113 {
114 return;
115 }
116 conn->async_method_call(
117 [&, new_object](const boost::system::error_code ec,
118 const std::string& nameOwner) {
119 if (ec)
120 {
121 std::cerr << "Error getting owner of " << new_object << " : "
122 << ec << "\n";
123 return;
124 }
125 owners[nameOwner] = new_object;
126 },
127 "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetNameOwner",
128 new_object);
129}
130
131void send_introspection_complete_signal(sdbusplus::asio::connection* system_bus,
132 const std::string& process_name)
133{
134 // TODO(ed) This signal doesn't get exposed properly in the
135 // introspect right now. Find out how to register signals in
136 // sdbusplus
137 sdbusplus::message::message m = system_bus->new_signal(
138 "/xyz/openbmc_project/object_mapper",
139 "xyz.openbmc_project.ObjectMapper.Private", "IntrospectionComplete");
140 m.append(process_name);
141 m.signal_send();
142}
143
144struct InProgressIntrospect
145{
146 InProgressIntrospect(
147 sdbusplus::asio::connection* system_bus, boost::asio::io_service& io,
Matt Spinleraecabe82018-09-19 13:25:42 -0500148 const std::string& process_name
149#ifdef DEBUG
150 ,
Ed Tanous60520632018-06-11 17:46:52 -0700151 std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
Matt Spinleraecabe82018-09-19 13:25:42 -0500152 global_start_time
153#endif
154 ) :
Ed Tanous60520632018-06-11 17:46:52 -0700155 system_bus(system_bus),
Matt Spinleraecabe82018-09-19 13:25:42 -0500156 io(io), process_name(process_name)
157#ifdef DEBUG
158 ,
Ed Tanous60520632018-06-11 17:46:52 -0700159 global_start_time(global_start_time),
160 process_start_time(std::chrono::steady_clock::now())
Matt Spinleraecabe82018-09-19 13:25:42 -0500161#endif
Ed Tanous60520632018-06-11 17:46:52 -0700162 {
163 }
164 ~InProgressIntrospect()
165 {
166 send_introspection_complete_signal(system_bus, process_name);
Matt Spinleraecabe82018-09-19 13:25:42 -0500167
168#ifdef DEBUG
Ed Tanous60520632018-06-11 17:46:52 -0700169 std::chrono::duration<float> diff =
170 std::chrono::steady_clock::now() - process_start_time;
171 std::cout << std::setw(50) << process_name << " scan took "
172 << diff.count() << " seconds\n";
173
174 // If we're the last outstanding caller globally, calculate the
175 // time it took
176 if (global_start_time != nullptr && global_start_time.use_count() == 1)
177 {
178 diff = std::chrono::steady_clock::now() - *global_start_time;
179 std::cout << "Total scan took " << diff.count()
180 << " seconds to complete\n";
181 }
Matt Spinleraecabe82018-09-19 13:25:42 -0500182#endif
Ed Tanous60520632018-06-11 17:46:52 -0700183 }
184 sdbusplus::asio::connection* system_bus;
185 boost::asio::io_service& io;
186 std::string process_name;
Matt Spinleraecabe82018-09-19 13:25:42 -0500187#ifdef DEBUG
Ed Tanous60520632018-06-11 17:46:52 -0700188 std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
189 global_start_time;
190 std::chrono::time_point<std::chrono::steady_clock> process_start_time;
Matt Spinleraecabe82018-09-19 13:25:42 -0500191#endif
Ed Tanous60520632018-06-11 17:46:52 -0700192};
193
Matt Spinler937a2322019-01-23 13:54:22 -0600194// Called when either a new org.openbmc.Associations interface was
195// created, or the associations property on that interface changed.
Ed Tanous60520632018-06-11 17:46:52 -0700196void addAssociation(sdbusplus::asio::object_server& objectServer,
197 const std::vector<Association>& associations,
Matt Spinler937a2322019-01-23 13:54:22 -0600198 const std::string& path, const std::string& owner)
Ed Tanous60520632018-06-11 17:46:52 -0700199{
Matt Spinler937a2322019-01-23 13:54:22 -0600200 AssociationPaths objects;
201
Ed Tanous60520632018-06-11 17:46:52 -0700202 for (const Association& association : associations)
203 {
204 std::string forward;
205 std::string reverse;
206 std::string endpoint;
207 std::tie(forward, reverse, endpoint) = association;
208
209 if (forward.size())
210 {
211 objects[path + "/" + forward].emplace(endpoint);
212 }
213 if (reverse.size())
214 {
215 if (endpoint.empty())
216 {
217 std::cerr << "Found invalid association on path " << path
218 << "\n";
219 continue;
220 }
221 objects[endpoint + "/" + reverse].emplace(path);
222 }
223 }
224 for (const auto& object : objects)
225 {
226 // the mapper exposes the new association interface but intakes
227 // the old
228
229 auto& iface = associationInterfaces[object.first];
Matt Spinler1036b4d2018-09-18 16:45:29 -0500230 auto& i = std::get<ifacePos>(iface);
231 auto& endpoints = std::get<endpointsPos>(iface);
Matt Spinler1036b4d2018-09-18 16:45:29 -0500232
233 // Only add new endpoints
234 for (auto& e : object.second)
235 {
236 if (std::find(endpoints.begin(), endpoints.end(), e) ==
237 endpoints.end())
238 {
239 endpoints.push_back(e);
240 }
241 }
242
243 // If the interface already exists, only need to update
244 // the property value, otherwise create it
245 if (i)
246 {
247 i->set_property("endpoints", endpoints);
248 }
249 else
250 {
251 i = objectServer.add_interface(object.first,
252 XYZ_ASSOCIATION_INTERFACE);
253 i->register_property("endpoints", endpoints);
254 i->initialize();
255 }
256 }
Matt Spinler1036b4d2018-09-18 16:45:29 -0500257
Matt Spinler937a2322019-01-23 13:54:22 -0600258 // Update associationOwners with the latest info
259 auto a = associationOwners.find(path);
260 if (a != associationOwners.end())
Matt Spinler1036b4d2018-09-18 16:45:29 -0500261 {
Matt Spinler937a2322019-01-23 13:54:22 -0600262 auto o = a->second.find(owner);
263 if (o != a->second.end())
Matt Spinler1036b4d2018-09-18 16:45:29 -0500264 {
Matt Spinler937a2322019-01-23 13:54:22 -0600265 o->second = std::move(objects);
Matt Spinler1036b4d2018-09-18 16:45:29 -0500266 }
267 else
268 {
Matt Spinler937a2322019-01-23 13:54:22 -0600269 a->second.emplace(owner, std::move(objects));
270 }
271 }
272 else
273 {
274 boost::container::flat_map<std::string, AssociationPaths> owners;
275 owners.emplace(owner, std::move(objects));
276 associationOwners.emplace(path, owners);
277 }
278}
Matt Spinler1036b4d2018-09-18 16:45:29 -0500279
Matt Spinler937a2322019-01-23 13:54:22 -0600280void removeAssociation(const std::string& sourcePath, const std::string& owner,
281 sdbusplus::asio::object_server& server)
282{
283 // Use associationOwners to find the association paths and endpoints
284 // that the passed in object path and service own. Remove all of
285 // these endpoints from the actual association D-Bus objects, and if
286 // the endpoints property is then empty, the whole association object
287 // can be removed. Note there can be multiple services that own an
288 // association, and also that sourcePath is the path of the object
289 // that contains the org.openbmc.Associations interface and not the
290 // association path itself.
291
292 // Find the services that have associations for this object path
293 auto owners = associationOwners.find(sourcePath);
294 if (owners == associationOwners.end())
295 {
296 return;
297 }
298
299 // Find the association paths and endpoints owned by this object
300 // path for this service.
301 auto assocs = owners->second.find(owner);
302 if (assocs == owners->second.end())
303 {
304 return;
305 }
306
307 for (const auto& [assocPath, endpointsToRemove] : assocs->second)
308 {
309 // Get the association D-Bus object for this assocPath
310 auto target = associationInterfaces.find(assocPath);
311 if (target == associationInterfaces.end())
312 {
313 continue;
314 }
315
316 // Remove the entries in the endpoints D-Bus property for this
317 // path/owner/association-path.
318 auto& existingEndpoints = std::get<endpointsPos>(target->second);
319 for (const auto& endpointToRemove : endpointsToRemove)
320 {
321 auto e = std::find(existingEndpoints.begin(),
322 existingEndpoints.end(), endpointToRemove);
323
324 if (e != existingEndpoints.end())
325 {
326 existingEndpoints.erase(e);
Matt Spinler1036b4d2018-09-18 16:45:29 -0500327 }
328 }
Matt Spinler937a2322019-01-23 13:54:22 -0600329
330 // Remove the association from D-Bus if there are no more endpoints,
331 // otherwise just update the endpoints property.
332 if (existingEndpoints.empty())
333 {
334 server.remove_interface(std::get<ifacePos>(target->second));
335 std::get<ifacePos>(target->second) = nullptr;
336 std::get<endpointsPos>(target->second).clear();
337 }
338 else
339 {
340 std::get<ifacePos>(target->second)
341 ->set_property("endpoints", existingEndpoints);
342 }
343 }
344
345 // Remove the associationOwners entries for this owning path/service.
346 owners->second.erase(assocs);
347 if (owners->second.empty())
348 {
349 associationOwners.erase(owners);
Ed Tanous60520632018-06-11 17:46:52 -0700350 }
351}
352
353void do_associations(sdbusplus::asio::connection* system_bus,
354 sdbusplus::asio::object_server& objectServer,
355 const std::string& processName, const std::string& path)
356{
357 system_bus->async_method_call(
Matt Spinler937a2322019-01-23 13:54:22 -0600358 [&objectServer, path, processName](
359 const boost::system::error_code ec,
360 const sdbusplus::message::variant<std::vector<Association>>&
361 variantAssociations) {
Ed Tanous60520632018-06-11 17:46:52 -0700362 if (ec)
363 {
364 std::cerr << "Error getting associations from " << path << "\n";
365 }
366 std::vector<Association> associations =
367 sdbusplus::message::variant_ns::get<std::vector<Association>>(
368 variantAssociations);
Matt Spinler937a2322019-01-23 13:54:22 -0600369 addAssociation(objectServer, associations, path, processName);
Ed Tanous60520632018-06-11 17:46:52 -0700370 },
371 processName, path, "org.freedesktop.DBus.Properties", "Get",
372 ASSOCIATIONS_INTERFACE, "associations");
373}
374
375void do_introspect(sdbusplus::asio::connection* system_bus,
376 std::shared_ptr<InProgressIntrospect> transaction,
377 interface_map_type& interface_map,
378 sdbusplus::asio::object_server& objectServer,
379 std::string path)
380{
381 system_bus->async_method_call(
382 [&interface_map, &objectServer, transaction, path,
383 system_bus](const boost::system::error_code ec,
384 const std::string& introspect_xml) {
385 if (ec)
386 {
387 std::cerr << "Introspect call failed with error: " << ec << ", "
388 << ec.message()
389 << " on process: " << transaction->process_name
390 << " path: " << path << "\n";
391 return;
392 }
393
394 tinyxml2::XMLDocument doc;
395
396 tinyxml2::XMLError e = doc.Parse(introspect_xml.c_str());
397 if (e != tinyxml2::XMLError::XML_SUCCESS)
398 {
399 std::cerr << "XML parsing failed\n";
400 return;
401 }
402
403 tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
404 if (pRoot == nullptr)
405 {
406 std::cerr << "XML document did not contain any data\n";
407 return;
408 }
409 auto& thisPathMap = interface_map[path];
Ed Tanous60520632018-06-11 17:46:52 -0700410 tinyxml2::XMLElement* pElement =
411 pRoot->FirstChildElement("interface");
412 while (pElement != nullptr)
413 {
414 const char* iface_name = pElement->Attribute("name");
415 if (iface_name == nullptr)
416 {
417 continue;
418 }
419
Matt Spinlerdd945862018-09-07 12:41:05 -0500420 std::string iface{iface_name};
421
Ed Tanousd4dd96a2018-11-12 11:37:44 -0800422 thisPathMap[transaction->process_name].emplace(iface_name);
423
Ed Tanous60520632018-06-11 17:46:52 -0700424 if (std::strcmp(iface_name, ASSOCIATIONS_INTERFACE) == 0)
425 {
426 do_associations(system_bus, objectServer,
427 transaction->process_name, path);
428 }
Ed Tanous60520632018-06-11 17:46:52 -0700429
430 pElement = pElement->NextSiblingElement("interface");
431 }
432
Ed Tanous50232cd2018-11-12 11:34:43 -0800433 pElement = pRoot->FirstChildElement("node");
434 while (pElement != nullptr)
Ed Tanous60520632018-06-11 17:46:52 -0700435 {
Ed Tanous50232cd2018-11-12 11:34:43 -0800436 const char* child_path = pElement->Attribute("name");
437 if (child_path != nullptr)
Ed Tanous60520632018-06-11 17:46:52 -0700438 {
Ed Tanous50232cd2018-11-12 11:34:43 -0800439 std::string parent_path(path);
440 if (parent_path == "/")
Ed Tanous60520632018-06-11 17:46:52 -0700441 {
Ed Tanous50232cd2018-11-12 11:34:43 -0800442 parent_path.clear();
Ed Tanous60520632018-06-11 17:46:52 -0700443 }
Ed Tanous50232cd2018-11-12 11:34:43 -0800444
445 do_introspect(system_bus, transaction, interface_map,
446 objectServer, parent_path + "/" + child_path);
Ed Tanous60520632018-06-11 17:46:52 -0700447 }
Ed Tanous50232cd2018-11-12 11:34:43 -0800448 pElement = pElement->NextSiblingElement("node");
Ed Tanous60520632018-06-11 17:46:52 -0700449 }
450 },
451 transaction->process_name, path, "org.freedesktop.DBus.Introspectable",
452 "Introspect");
453}
454
455bool need_to_introspect(const std::string& process_name)
456{
Matt Spinlerdd945862018-09-07 12:41:05 -0500457 auto inWhitelist =
458 std::find_if(service_whitelist.begin(), service_whitelist.end(),
459 [&process_name](const auto& prefix) {
460 return boost::starts_with(process_name, prefix);
461 }) != service_whitelist.end();
462
463 // This holds full service names, not prefixes
464 auto inBlacklist =
465 service_blacklist.find(process_name) != service_blacklist.end();
466
467 return inWhitelist && !inBlacklist;
Ed Tanous60520632018-06-11 17:46:52 -0700468}
469
470void start_new_introspect(
471 sdbusplus::asio::connection* system_bus, boost::asio::io_service& io,
472 interface_map_type& interface_map, const std::string& process_name,
Matt Spinleraecabe82018-09-19 13:25:42 -0500473#ifdef DEBUG
Ed Tanous60520632018-06-11 17:46:52 -0700474 std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
475 global_start_time,
Matt Spinleraecabe82018-09-19 13:25:42 -0500476#endif
Ed Tanous60520632018-06-11 17:46:52 -0700477 sdbusplus::asio::object_server& objectServer)
478{
479 if (need_to_introspect(process_name))
480 {
Ed Tanous60520632018-06-11 17:46:52 -0700481 std::shared_ptr<InProgressIntrospect> transaction =
Matt Spinleraecabe82018-09-19 13:25:42 -0500482 std::make_shared<InProgressIntrospect>(system_bus, io, process_name
483#ifdef DEBUG
484 ,
485 global_start_time
486#endif
487 );
Ed Tanous60520632018-06-11 17:46:52 -0700488
489 do_introspect(system_bus, transaction, interface_map, objectServer,
490 "/");
491 }
492}
493
494// TODO(ed) replace with std::set_intersection once c++17 is available
495template <class InputIt1, class InputIt2>
496bool intersect(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2)
497{
498 while (first1 != last1 && first2 != last2)
499 {
500 if (*first1 < *first2)
501 {
502 ++first1;
503 continue;
504 }
505 if (*first2 < *first1)
506 {
507 ++first2;
508 continue;
509 }
510 return true;
511 }
512 return false;
513}
514
515void doListNames(
516 boost::asio::io_service& io, interface_map_type& interface_map,
517 sdbusplus::asio::connection* system_bus,
518 boost::container::flat_map<std::string, std::string>& name_owners,
519 sdbusplus::asio::object_server& objectServer)
520{
521 system_bus->async_method_call(
522 [&io, &interface_map, &name_owners, &objectServer,
523 system_bus](const boost::system::error_code ec,
524 std::vector<std::string> process_names) {
525 if (ec)
526 {
527 std::cerr << "Error getting names: " << ec << "\n";
528 std::exit(EXIT_FAILURE);
529 return;
530 }
Ed Tanous60520632018-06-11 17:46:52 -0700531 // Try to make startup consistent
532 std::sort(process_names.begin(), process_names.end());
Matt Spinleraecabe82018-09-19 13:25:42 -0500533#ifdef DEBUG
Ed Tanous60520632018-06-11 17:46:52 -0700534 std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
535 global_start_time = std::make_shared<
536 std::chrono::time_point<std::chrono::steady_clock>>(
537 std::chrono::steady_clock::now());
Matt Spinleraecabe82018-09-19 13:25:42 -0500538#endif
Ed Tanous60520632018-06-11 17:46:52 -0700539 for (const std::string& process_name : process_names)
540 {
541 if (need_to_introspect(process_name))
542 {
543 start_new_introspect(system_bus, io, interface_map,
Matt Spinleraecabe82018-09-19 13:25:42 -0500544 process_name,
545#ifdef DEBUG
546 global_start_time,
547#endif
Ed Tanous60520632018-06-11 17:46:52 -0700548 objectServer);
549 update_owners(system_bus, name_owners, process_name);
550 }
551 }
552 },
553 "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus",
554 "ListNames");
555}
556
Matt Spinlerdd945862018-09-07 12:41:05 -0500557void splitArgs(const std::string& stringArgs,
558 boost::container::flat_set<std::string>& listArgs)
559{
560 std::istringstream args;
561 std::string arg;
562
563 args.str(stringArgs);
564
565 while (!args.eof())
566 {
567 args >> arg;
568 if (!arg.empty())
569 {
570 listArgs.insert(arg);
571 }
572 }
573}
574
Matt Spinler47c09752018-11-29 14:54:13 -0600575void addObjectMapResult(
576 std::vector<interface_map_type::value_type>& objectMap,
Matt Spinler9f0958e2018-09-11 08:26:10 -0500577 const std::string& objectPath,
578 const std::pair<std::string, boost::container::flat_set<std::string>>&
579 interfaceMap)
580{
581 // Adds an object path/service name/interface list entry to
Matt Spinler47c09752018-11-29 14:54:13 -0600582 // the results of GetSubTree and GetAncestors.
Matt Spinler9f0958e2018-09-11 08:26:10 -0500583 // If an entry for the object path already exists, just add the
584 // service name and interfaces to that entry, otherwise create
585 // a new entry.
586 auto entry = std::find_if(
Matt Spinler47c09752018-11-29 14:54:13 -0600587 objectMap.begin(), objectMap.end(),
Matt Spinler9f0958e2018-09-11 08:26:10 -0500588 [&objectPath](const auto& i) { return objectPath == i.first; });
589
Matt Spinler47c09752018-11-29 14:54:13 -0600590 if (entry != objectMap.end())
Matt Spinler9f0958e2018-09-11 08:26:10 -0500591 {
592 entry->second.emplace(interfaceMap);
593 }
594 else
595 {
596 interface_map_type::value_type object;
597 object.first = objectPath;
598 object.second.emplace(interfaceMap);
Matt Spinler47c09752018-11-29 14:54:13 -0600599 objectMap.push_back(object);
Matt Spinler9f0958e2018-09-11 08:26:10 -0500600 }
601}
602
Matt Spinlera82779f2019-01-09 12:39:42 -0600603// Remove parents of the passed in path that:
604// 1) Only have the 3 default interfaces on them
605// - Means D-Bus created these, not application code,
606// with the Properties, Introspectable, and Peer ifaces
607// 2) Have no other child for this owner
608void removeUnneededParents(const std::string& objectPath,
609 const std::string& owner,
610 interface_map_type& interface_map)
611{
612 auto parent = objectPath;
613
614 while (true)
615 {
616 auto pos = parent.find_last_of('/');
617 if ((pos == std::string::npos) || (pos == 0))
618 {
619 break;
620 }
621 parent = parent.substr(0, pos);
622
623 auto parent_it = interface_map.find(parent);
624 if (parent_it == interface_map.end())
625 {
626 break;
627 }
628
629 auto ifaces_it = parent_it->second.find(owner);
630 if (ifaces_it == parent_it->second.end())
631 {
632 break;
633 }
634
635 if (ifaces_it->second.size() != 3)
636 {
637 break;
638 }
639
640 auto child_path = parent + '/';
641
642 // Remove this parent if there isn't a remaining child on this owner
643 auto child = std::find_if(
644 interface_map.begin(), interface_map.end(),
645 [&owner, &child_path](const auto& entry) {
646 return boost::starts_with(entry.first, child_path) &&
647 (entry.second.find(owner) != entry.second.end());
648 });
649
650 if (child == interface_map.end())
651 {
652 parent_it->second.erase(ifaces_it);
653 if (parent_it->second.empty())
654 {
655 interface_map.erase(parent_it);
656 }
657 }
658 else
659 {
660 break;
661 }
662 }
663}
664
Ed Tanous60520632018-06-11 17:46:52 -0700665int main(int argc, char** argv)
666{
Matt Spinlerdd945862018-09-07 12:41:05 -0500667 auto options = ArgumentParser(argc, argv);
Ed Tanous60520632018-06-11 17:46:52 -0700668 boost::asio::io_service io;
669 std::shared_ptr<sdbusplus::asio::connection> system_bus =
670 std::make_shared<sdbusplus::asio::connection>(io);
671
Matt Spinlerdd945862018-09-07 12:41:05 -0500672 splitArgs(options["service-namespaces"], service_whitelist);
Matt Spinlerdd945862018-09-07 12:41:05 -0500673 splitArgs(options["service-blacklists"], service_blacklist);
674
Ed Tanousd4dd96a2018-11-12 11:37:44 -0800675 // TODO(Ed) Remove this once all service files are updated to not use this.
676 // For now, simply squash the input, and ignore it.
677 boost::container::flat_set<std::string> iface_whitelist;
678 splitArgs(options["interface-namespaces"], iface_whitelist);
679
Ed Tanous60520632018-06-11 17:46:52 -0700680 system_bus->request_name(OBJECT_MAPPER_DBUS_NAME);
681 sdbusplus::asio::object_server server(system_bus);
682
683 // Construct a signal set registered for process termination.
684 boost::asio::signal_set signals(io, SIGINT, SIGTERM);
685 signals.async_wait([&io](const boost::system::error_code& error,
686 int signal_number) { io.stop(); });
687
688 interface_map_type interface_map;
689 boost::container::flat_map<std::string, std::string> name_owners;
690
691 std::function<void(sdbusplus::message::message & message)>
692 nameChangeHandler = [&interface_map, &io, &name_owners, &server,
693 system_bus](sdbusplus::message::message& message) {
694 std::string name;
695 std::string old_owner;
696 std::string new_owner;
697
698 message.read(name, old_owner, new_owner);
699
700 if (!old_owner.empty())
701 {
702 if (boost::starts_with(old_owner, ":"))
703 {
704 auto it = name_owners.find(old_owner);
705 if (it != name_owners.end())
706 {
707 name_owners.erase(it);
708 }
709 }
710 // Connection removed
711 interface_map_type::iterator path_it = interface_map.begin();
712 while (path_it != interface_map.end())
713 {
Matt Spinler1036b4d2018-09-18 16:45:29 -0500714 // If an associations interface is being removed,
715 // also need to remove the corresponding associations
716 // objects and properties.
717 auto ifaces = path_it->second.find(name);
718 if (ifaces != path_it->second.end())
719 {
720 auto assoc = std::find(ifaces->second.begin(),
721 ifaces->second.end(),
722 ASSOCIATIONS_INTERFACE);
723
724 if (assoc != ifaces->second.end())
725 {
Matt Spinler937a2322019-01-23 13:54:22 -0600726 removeAssociation(path_it->first, name, server);
Matt Spinler1036b4d2018-09-18 16:45:29 -0500727 }
728 }
729
Ed Tanous60520632018-06-11 17:46:52 -0700730 path_it->second.erase(name);
731 if (path_it->second.empty())
732 {
733 // If the last connection to the object is gone,
734 // delete the top level object
735 path_it = interface_map.erase(path_it);
736 continue;
737 }
738 path_it++;
739 }
740 }
741
742 if (!new_owner.empty())
743 {
Matt Spinleraecabe82018-09-19 13:25:42 -0500744#ifdef DEBUG
Ed Tanous60520632018-06-11 17:46:52 -0700745 auto transaction = std::make_shared<
746 std::chrono::time_point<std::chrono::steady_clock>>(
747 std::chrono::steady_clock::now());
Matt Spinleraecabe82018-09-19 13:25:42 -0500748#endif
Ed Tanous60520632018-06-11 17:46:52 -0700749 // New daemon added
750 if (need_to_introspect(name))
751 {
752 name_owners[new_owner] = name;
753 start_new_introspect(system_bus.get(), io, interface_map,
Matt Spinleraecabe82018-09-19 13:25:42 -0500754 name,
755#ifdef DEBUG
756 transaction,
757#endif
758 server);
Ed Tanous60520632018-06-11 17:46:52 -0700759 }
760 }
761 };
762
763 sdbusplus::bus::match::match nameOwnerChanged(
764 static_cast<sdbusplus::bus::bus&>(*system_bus),
765 sdbusplus::bus::match::rules::nameOwnerChanged(), nameChangeHandler);
766
767 std::function<void(sdbusplus::message::message & message)>
768 interfacesAddedHandler = [&interface_map, &name_owners, &server](
769 sdbusplus::message::message& message) {
770 sdbusplus::message::object_path obj_path;
771 std::vector<std::pair<
772 std::string, std::vector<std::pair<
773 std::string, sdbusplus::message::variant<
774 std::vector<Association>>>>>>
775 interfaces_added;
776 message.read(obj_path, interfaces_added);
777 std::string well_known;
778 if (!get_well_known(name_owners, message.get_sender(), well_known))
779 {
780 return; // only introspect well-known
781 }
782 if (need_to_introspect(well_known))
783 {
784 auto& iface_list = interface_map[obj_path.str];
785
Matt Spinlercc74e4b2018-09-18 16:21:54 -0500786 for (const auto& interface_pair : interfaces_added)
Ed Tanous60520632018-06-11 17:46:52 -0700787 {
788 iface_list[well_known].emplace(interface_pair.first);
789
790 if (interface_pair.first == ASSOCIATIONS_INTERFACE)
791 {
792 const sdbusplus::message::variant<
793 std::vector<Association>>* variantAssociations =
794 nullptr;
795 for (const auto& interface : interface_pair.second)
796 {
797 if (interface.first == "associations")
798 {
799 variantAssociations = &(interface.second);
800 }
801 }
802 if (variantAssociations == nullptr)
803 {
804 std::cerr << "Illegal association found on "
805 << well_known << "\n";
806 continue;
807 }
808 std::vector<Association> associations =
809 sdbusplus::message::variant_ns::get<
810 std::vector<Association>>(*variantAssociations);
Matt Spinler937a2322019-01-23 13:54:22 -0600811 addAssociation(server, associations, obj_path.str,
812 well_known);
Ed Tanous60520632018-06-11 17:46:52 -0700813 }
814 }
Matt Spinler1b4a5022019-01-03 14:48:33 -0600815
816 // To handle the case where an object path is being created
817 // with 2 or more new path segments, check if the parent paths
818 // of this path are already in the interface map, and add them
819 // if they aren't with just the default freedesktop interfaces.
820 // This would be done via introspection if they would have
821 // already existed at startup. While we could also introspect
822 // them now to do the work, we know there aren't any other
823 // interfaces or we would have gotten signals for them as well,
824 // so take a shortcut to speed things up.
825 //
826 // This is all needed so that mapper operations can be done
827 // on the new parent paths.
828 using iface_map_iterator = interface_map_type::iterator;
829 using iface_map_value_type = boost::container::flat_map<
830 std::string, boost::container::flat_set<std::string>>;
831 using name_map_iterator = iface_map_value_type::iterator;
832
833 static const boost::container::flat_set<std::string>
834 default_ifaces{"org.freedesktop.DBus.Introspectable",
835 "org.freedesktop.DBus.Peer",
836 "org.freedesktop.DBus.Properties"};
837
838 std::string parent = obj_path.str;
839 auto pos = parent.find_last_of('/');
840
841 while (pos != std::string::npos)
842 {
843 parent = parent.substr(0, pos);
844
845 std::pair<iface_map_iterator, bool> parentEntry =
846 interface_map.insert(
847 std::make_pair(parent, iface_map_value_type{}));
848
849 std::pair<name_map_iterator, bool> ifaceEntry =
850 parentEntry.first->second.insert(
851 std::make_pair(well_known, default_ifaces));
852
853 if (!ifaceEntry.second)
854 {
855 // Entry was already there for this name so done.
856 break;
857 }
858
859 pos = parent.find_last_of('/');
860 }
Ed Tanous60520632018-06-11 17:46:52 -0700861 }
862 };
863
864 sdbusplus::bus::match::match interfacesAdded(
865 static_cast<sdbusplus::bus::bus&>(*system_bus),
866 sdbusplus::bus::match::rules::interfacesAdded(),
867 interfacesAddedHandler);
868
869 std::function<void(sdbusplus::message::message & message)>
870 interfacesRemovedHandler = [&interface_map, &name_owners, &server](
871 sdbusplus::message::message& message) {
872 sdbusplus::message::object_path obj_path;
873 std::vector<std::string> interfaces_removed;
874 message.read(obj_path, interfaces_removed);
875 auto connection_map = interface_map.find(obj_path.str);
876 if (connection_map == interface_map.end())
877 {
878 return;
879 }
880
881 std::string sender;
882 if (!get_well_known(name_owners, message.get_sender(), sender))
883 {
884 return;
885 }
886 for (const std::string& interface : interfaces_removed)
887 {
888 auto interface_set = connection_map->second.find(sender);
889 if (interface_set == connection_map->second.end())
890 {
891 continue;
892 }
893
894 if (interface == ASSOCIATIONS_INTERFACE)
895 {
Matt Spinler937a2322019-01-23 13:54:22 -0600896 removeAssociation(obj_path.str, sender, server);
Ed Tanous60520632018-06-11 17:46:52 -0700897 }
898
899 interface_set->second.erase(interface);
900 // If this was the last interface on this connection,
901 // erase the connection
902 if (interface_set->second.empty())
903 {
904 connection_map->second.erase(interface_set);
905 }
906 }
907 // If this was the last connection on this object path,
908 // erase the object path
909 if (connection_map->second.empty())
910 {
911 interface_map.erase(connection_map);
912 }
Matt Spinlera82779f2019-01-09 12:39:42 -0600913
914 removeUnneededParents(obj_path.str, sender, interface_map);
Ed Tanous60520632018-06-11 17:46:52 -0700915 };
916
917 sdbusplus::bus::match::match interfacesRemoved(
918 static_cast<sdbusplus::bus::bus&>(*system_bus),
919 sdbusplus::bus::match::rules::interfacesRemoved(),
920 interfacesRemovedHandler);
921
922 std::function<void(sdbusplus::message::message & message)>
923 associationChangedHandler =
Matt Spinler937a2322019-01-23 13:54:22 -0600924 [&server, &name_owners](sdbusplus::message::message& message) {
Ed Tanous60520632018-06-11 17:46:52 -0700925 std::string objectName;
926 boost::container::flat_map<
927 std::string,
928 sdbusplus::message::variant<std::vector<Association>>>
929 values;
930 message.read(objectName, values);
931 auto findAssociations = values.find("associations");
932 if (findAssociations != values.end())
933 {
934 std::vector<Association> associations =
935 sdbusplus::message::variant_ns::get<
936 std::vector<Association>>(findAssociations->second);
Matt Spinler937a2322019-01-23 13:54:22 -0600937
938 std::string well_known;
939 if (!get_well_known(name_owners, message.get_sender(),
940 well_known))
941 {
942 return;
943 }
944 addAssociation(server, associations, message.get_path(),
945 well_known);
Ed Tanous60520632018-06-11 17:46:52 -0700946 }
947 };
948 sdbusplus::bus::match::match associationChanged(
949 static_cast<sdbusplus::bus::bus&>(*system_bus),
950 sdbusplus::bus::match::rules::interface(
951 "org.freedesktop.DBus.Properties") +
952 sdbusplus::bus::match::rules::member("PropertiesChanged") +
953 sdbusplus::bus::match::rules::argN(0, ASSOCIATIONS_INTERFACE),
954 associationChangedHandler);
955
956 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
957 server.add_interface("/xyz/openbmc_project/object_mapper",
958 "xyz.openbmc_project.ObjectMapper");
959
960 iface->register_method(
Matt Spinler6a39e8c2018-11-13 11:11:36 -0600961 "GetAncestors", [&interface_map](std::string& req_path,
Ed Tanous60520632018-06-11 17:46:52 -0700962 std::vector<std::string>& interfaces) {
963 // Interfaces need to be sorted for intersect to function
964 std::sort(interfaces.begin(), interfaces.end());
965
James Feistd7322872019-01-28 11:25:00 -0800966 if (boost::ends_with(req_path, "/"))
Matt Spinler6a39e8c2018-11-13 11:11:36 -0600967 {
James Feistd7322872019-01-28 11:25:00 -0800968 req_path.pop_back();
969 }
970 if (req_path.size() &&
971 interface_map.find(req_path) == interface_map.end())
972 {
973 throw NotFoundException();
Matt Spinler6a39e8c2018-11-13 11:11:36 -0600974 }
975
Ed Tanous60520632018-06-11 17:46:52 -0700976 std::vector<interface_map_type::value_type> ret;
977 for (auto& object_path : interface_map)
978 {
979 auto& this_path = object_path.first;
Matt Spinlerea6516c2018-09-25 16:23:13 -0500980 if (boost::starts_with(req_path, this_path) &&
981 (req_path != this_path))
Ed Tanous60520632018-06-11 17:46:52 -0700982 {
983 if (interfaces.empty())
984 {
985 ret.emplace_back(object_path);
986 }
987 else
988 {
989 for (auto& interface_map : object_path.second)
990 {
991
992 if (intersect(interfaces.begin(), interfaces.end(),
993 interface_map.second.begin(),
994 interface_map.second.end()))
995 {
Matt Spinler47c09752018-11-29 14:54:13 -0600996 addObjectMapResult(ret, this_path,
997 interface_map);
Ed Tanous60520632018-06-11 17:46:52 -0700998 }
999 }
1000 }
1001 }
1002 }
Ed Tanous60520632018-06-11 17:46:52 -07001003
1004 return ret;
1005 });
1006
1007 iface->register_method(
1008 "GetObject", [&interface_map](const std::string& path,
1009 std::vector<std::string>& interfaces) {
Matt Spinler7a38d412018-09-18 16:13:25 -05001010 boost::container::flat_map<std::string,
1011 boost::container::flat_set<std::string>>
1012 results;
1013
Ed Tanous60520632018-06-11 17:46:52 -07001014 // Interfaces need to be sorted for intersect to function
1015 std::sort(interfaces.begin(), interfaces.end());
1016 auto path_ref = interface_map.find(path);
1017 if (path_ref == interface_map.end())
1018 {
1019 throw NotFoundException();
1020 }
1021 if (interfaces.empty())
1022 {
1023 return path_ref->second;
1024 }
1025 for (auto& interface_map : path_ref->second)
1026 {
1027 if (intersect(interfaces.begin(), interfaces.end(),
1028 interface_map.second.begin(),
1029 interface_map.second.end()))
1030 {
Matt Spinler7a38d412018-09-18 16:13:25 -05001031 results.emplace(interface_map.first, interface_map.second);
Ed Tanous60520632018-06-11 17:46:52 -07001032 }
1033 }
Matt Spinler7a38d412018-09-18 16:13:25 -05001034
1035 if (results.empty())
1036 {
1037 throw NotFoundException();
1038 }
1039
1040 return results;
Ed Tanous60520632018-06-11 17:46:52 -07001041 });
1042
1043 iface->register_method(
Matt Spinler153494e2018-11-07 16:35:40 -06001044 "GetSubTree", [&interface_map](std::string& req_path, int32_t depth,
1045 std::vector<std::string>& interfaces) {
Ed Tanous60520632018-06-11 17:46:52 -07001046 if (depth <= 0)
1047 {
1048 depth = std::numeric_limits<int32_t>::max();
1049 }
1050 // Interfaces need to be sorted for intersect to function
1051 std::sort(interfaces.begin(), interfaces.end());
1052 std::vector<interface_map_type::value_type> ret;
1053
James Feistd7322872019-01-28 11:25:00 -08001054 if (boost::ends_with(req_path, "/"))
Matt Spinler153494e2018-11-07 16:35:40 -06001055 {
James Feistd7322872019-01-28 11:25:00 -08001056 req_path.pop_back();
1057 }
1058 if (req_path.size() &&
1059 interface_map.find(req_path) == interface_map.end())
1060 {
1061 throw NotFoundException();
Matt Spinler153494e2018-11-07 16:35:40 -06001062 }
1063
Ed Tanous60520632018-06-11 17:46:52 -07001064 for (auto& object_path : interface_map)
1065 {
1066 auto& this_path = object_path.first;
Matt Spinler153494e2018-11-07 16:35:40 -06001067
1068 if (this_path == req_path)
1069 {
1070 continue;
1071 }
1072
Ed Tanous60520632018-06-11 17:46:52 -07001073 if (boost::starts_with(this_path, req_path))
1074 {
1075 // count the number of slashes past the search term
1076 int32_t this_depth =
1077 std::count(this_path.begin() + req_path.size(),
1078 this_path.end(), '/');
1079 if (this_depth <= depth)
1080 {
Ed Tanous60520632018-06-11 17:46:52 -07001081 for (auto& interface_map : object_path.second)
1082 {
1083 if (intersect(interfaces.begin(), interfaces.end(),
1084 interface_map.second.begin(),
Matt Spinler9f0958e2018-09-11 08:26:10 -05001085 interface_map.second.end()) ||
1086 interfaces.empty())
Ed Tanous60520632018-06-11 17:46:52 -07001087 {
Matt Spinler47c09752018-11-29 14:54:13 -06001088 addObjectMapResult(ret, this_path,
1089 interface_map);
Ed Tanous60520632018-06-11 17:46:52 -07001090 }
1091 }
Ed Tanous60520632018-06-11 17:46:52 -07001092 }
1093 }
1094 }
Matt Spinler6a39e8c2018-11-13 11:11:36 -06001095
Ed Tanous60520632018-06-11 17:46:52 -07001096 return ret;
1097 });
1098
1099 iface->register_method(
1100 "GetSubTreePaths",
Matt Spinler153494e2018-11-07 16:35:40 -06001101 [&interface_map](std::string& req_path, int32_t depth,
Ed Tanous60520632018-06-11 17:46:52 -07001102 std::vector<std::string>& interfaces) {
1103 if (depth <= 0)
1104 {
1105 depth = std::numeric_limits<int32_t>::max();
1106 }
1107 // Interfaces need to be sorted for intersect to function
1108 std::sort(interfaces.begin(), interfaces.end());
1109 std::vector<std::string> ret;
Matt Spinler153494e2018-11-07 16:35:40 -06001110
James Feistd7322872019-01-28 11:25:00 -08001111 if (boost::ends_with(req_path, "/"))
Matt Spinler153494e2018-11-07 16:35:40 -06001112 {
James Feistd7322872019-01-28 11:25:00 -08001113 req_path.pop_back();
1114 }
1115 if (req_path.size() &&
1116 interface_map.find(req_path) == interface_map.end())
1117 {
1118 throw NotFoundException();
Matt Spinler153494e2018-11-07 16:35:40 -06001119 }
1120
Ed Tanous60520632018-06-11 17:46:52 -07001121 for (auto& object_path : interface_map)
1122 {
1123 auto& this_path = object_path.first;
Matt Spinler153494e2018-11-07 16:35:40 -06001124
1125 if (this_path == req_path)
1126 {
1127 continue;
1128 }
1129
Ed Tanous60520632018-06-11 17:46:52 -07001130 if (boost::starts_with(this_path, req_path))
1131 {
1132 // count the number of slashes past the search term
1133 int this_depth =
1134 std::count(this_path.begin() + req_path.size(),
1135 this_path.end(), '/');
1136 if (this_depth <= depth)
1137 {
1138 bool add = interfaces.empty();
1139 for (auto& interface_map : object_path.second)
1140 {
1141 if (intersect(interfaces.begin(), interfaces.end(),
1142 interface_map.second.begin(),
1143 interface_map.second.end()))
1144 {
1145 add = true;
1146 break;
1147 }
1148 }
1149 if (add)
1150 {
1151 // TODO(ed) this is a copy
1152 ret.emplace_back(this_path);
1153 }
1154 }
1155 }
1156 }
Matt Spinler6a39e8c2018-11-13 11:11:36 -06001157
Ed Tanous60520632018-06-11 17:46:52 -07001158 return ret;
1159 });
1160
1161 iface->initialize();
1162
1163 io.post([&]() {
1164 doListNames(io, interface_map, system_bus.get(), name_owners, server);
1165 });
1166
Ed Tanous60520632018-06-11 17:46:52 -07001167 io.run();
1168}