1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2013 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include <sys/capability.h>
26 #include "bus-internal.h"
27 #include "bus-message.h"
29 #include "bus-signature.h"
30 #include "bus-introspect.h"
31 #include "bus-objects.h"
34 static int node_vtable_get_userdata(
37 struct node_vtable *c,
39 sd_bus_error *error) {
50 r = c->find(bus, path, c->interface, u, &u, error);
53 if (sd_bus_error_is_set(error))
54 return -sd_bus_error_get_errno(error);
65 static void *vtable_property_convert_userdata(const sd_bus_vtable *p, void *u) {
68 return (uint8_t*) u + p->x.property.offset;
71 static int vtable_property_get_userdata(
74 struct vtable_member *p,
76 sd_bus_error *error) {
86 r = node_vtable_get_userdata(bus, path, p->parent, &u, error);
89 if (bus->nodes_modified)
92 *userdata = vtable_property_convert_userdata(p->vtable, u);
96 static int add_enumerated_to_set(
99 struct node_enumerator *first,
101 sd_bus_error *error) {
103 struct node_enumerator *c;
110 LIST_FOREACH(enumerators, c, first) {
111 char **children = NULL, **k;
113 if (bus->nodes_modified)
116 r = c->callback(bus, prefix, c->userdata, &children, error);
119 if (sd_bus_error_is_set(error))
120 return -sd_bus_error_get_errno(error);
122 STRV_FOREACH(k, children) {
128 if (!object_path_is_valid(*k)){
134 if (!object_path_startswith(*k, prefix)) {
139 r = set_consume(s, *k);
152 static int add_subtree_to_set(
157 sd_bus_error *error) {
167 r = add_enumerated_to_set(bus, prefix, n->enumerators, s, error);
170 if (bus->nodes_modified)
173 LIST_FOREACH(siblings, i, n->child) {
176 if (!object_path_startswith(i->path, prefix))
183 r = set_consume(s, t);
184 if (r < 0 && r != -EEXIST)
187 r = add_subtree_to_set(bus, prefix, i, s, error);
190 if (bus->nodes_modified)
197 static int get_child_nodes(
202 sd_bus_error *error) {
212 s = set_new(string_hash_func, string_compare_func);
216 r = add_subtree_to_set(bus, prefix, n, s, error);
226 static int node_callbacks_run(
229 struct node_callback *first,
230 bool require_fallback,
231 bool *found_object) {
233 struct node_callback *c;
238 assert(found_object);
240 LIST_FOREACH(callbacks, c, first) {
241 _cleanup_bus_error_free_ sd_bus_error error_buffer = SD_BUS_ERROR_NULL;
243 if (bus->nodes_modified)
246 if (require_fallback && !c->is_fallback)
249 *found_object = true;
251 if (c->last_iteration == bus->iteration_counter)
254 c->last_iteration = bus->iteration_counter;
256 r = sd_bus_message_rewind(m, true);
260 r = c->callback(bus, m, c->userdata, &error_buffer);
261 r = bus_maybe_reply_error(m, r, &error_buffer);
269 #define CAPABILITY_SHIFT(x) (((x) >> __builtin_ctzll(_SD_BUS_VTABLE_CAPABILITY_MASK)) & 0xFFFF)
271 static int check_access(sd_bus *bus, sd_bus_message *m, struct vtable_member *c, sd_bus_error *error) {
272 _cleanup_bus_creds_unref_ sd_bus_creds *creds = NULL;
281 /* If the entire bus is trusted let's grant access */
285 /* If the member is marked UNPRIVILEGED let's grant access */
286 if (c->vtable->flags & SD_BUS_VTABLE_UNPRIVILEGED)
289 /* If we are not connected to kdbus we cannot retrieve the
290 * effective capability set without race. Since we need this
291 * for a security decision we cannot use racy data, hence
292 * don't request it. */
294 r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_UID|SD_BUS_CREDS_EFFECTIVE_CAPS, &creds);
296 r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_UID, &creds);
300 /* Check have the caller has the requested capability
301 * set. Note that the flags value contains the capability
302 * number plus one, which we need to subtract here. We do this
303 * so that we have 0 as special value for "default
305 cap = CAPABILITY_SHIFT(c->vtable->flags);
307 cap = CAPABILITY_SHIFT(c->parent->vtable[0].flags);
313 r = sd_bus_creds_has_effective_cap(creds, cap);
317 /* Caller has same UID as us, then let's grant access */
318 r = sd_bus_creds_get_uid(creds, &uid);
324 return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Access to %s.%s() not permitted.", c->interface, c->member);
327 static int method_callbacks_run(
330 struct vtable_member *c,
331 bool require_fallback,
332 bool *found_object) {
334 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
335 const char *signature;
342 assert(found_object);
344 if (require_fallback && !c->parent->is_fallback)
347 r = check_access(bus, m, c, &error);
349 return bus_maybe_reply_error(m, r, &error);
351 r = node_vtable_get_userdata(bus, m->path, c->parent, &u, &error);
353 return bus_maybe_reply_error(m, r, &error);
354 if (bus->nodes_modified)
357 *found_object = true;
359 if (c->last_iteration == bus->iteration_counter)
362 c->last_iteration = bus->iteration_counter;
364 r = sd_bus_message_rewind(m, true);
368 signature = sd_bus_message_get_signature(m, true);
372 if (!streq(strempty(c->vtable->x.method.signature), signature))
373 return sd_bus_reply_method_errorf(
375 SD_BUS_ERROR_INVALID_ARGS,
376 "Invalid arguments '%s' to call %s.%s(), expecting '%s'.",
377 signature, c->interface, c->member, strempty(c->vtable->x.method.signature));
379 /* Keep track what the signature of the reply to this message
380 * should be, so that this can be enforced when sealing the
382 m->enforced_reply_signature = strempty(c->vtable->x.method.result);
384 if (c->vtable->x.method.handler) {
385 r = c->vtable->x.method.handler(bus, m, u, &error);
386 return bus_maybe_reply_error(m, r, &error);
389 /* If the method callback is NULL, make this a successful NOP */
390 r = sd_bus_reply_method_return(m, NULL);
397 static int invoke_property_get(
399 const sd_bus_vtable *v,
401 const char *interface,
402 const char *property,
403 sd_bus_message *reply,
405 sd_bus_error *error) {
417 if (v->x.property.get) {
418 r = v->x.property.get(bus, path, interface, property, reply, userdata, error);
421 if (sd_bus_error_is_set(error))
422 return -sd_bus_error_get_errno(error);
426 /* Automatic handling if no callback is defined. */
428 if (streq(v->x.property.signature, "as"))
429 return sd_bus_message_append_strv(reply, *(char***) userdata);
431 assert(signature_is_single(v->x.property.signature, false));
432 assert(bus_type_is_basic(v->x.property.signature[0]));
434 switch (v->x.property.signature[0]) {
436 case SD_BUS_TYPE_STRING:
437 case SD_BUS_TYPE_SIGNATURE:
438 p = strempty(*(char**) userdata);
441 case SD_BUS_TYPE_OBJECT_PATH:
442 p = *(char**) userdata;
451 return sd_bus_message_append_basic(reply, v->x.property.signature[0], p);
454 static int invoke_property_set(
456 const sd_bus_vtable *v,
458 const char *interface,
459 const char *property,
460 sd_bus_message *value,
462 sd_bus_error *error) {
473 if (v->x.property.set) {
474 r = v->x.property.set(bus, path, interface, property, value, userdata, error);
477 if (sd_bus_error_is_set(error))
478 return -sd_bus_error_get_errno(error);
482 /* Automatic handling if no callback is defined. */
484 assert(signature_is_single(v->x.property.signature, false));
485 assert(bus_type_is_basic(v->x.property.signature[0]));
487 switch (v->x.property.signature[0]) {
489 case SD_BUS_TYPE_STRING:
490 case SD_BUS_TYPE_OBJECT_PATH:
491 case SD_BUS_TYPE_SIGNATURE: {
495 r = sd_bus_message_read_basic(value, v->x.property.signature[0], &p);
503 free(*(char**) userdata);
504 *(char**) userdata = n;
510 r = sd_bus_message_read_basic(value, v->x.property.signature[0], userdata);
520 static int property_get_set_callbacks_run(
523 struct vtable_member *c,
524 bool require_fallback,
526 bool *found_object) {
528 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
529 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
536 assert(found_object);
538 if (require_fallback && !c->parent->is_fallback)
541 r = vtable_property_get_userdata(bus, m->path, c, &u, &error);
543 return bus_maybe_reply_error(m, r, &error);
544 if (bus->nodes_modified)
547 *found_object = true;
549 r = sd_bus_message_new_method_return(m, &reply);
554 /* Note that we do not protect against reexecution
555 * here (using the last_iteration check, see below),
556 * should the node tree have changed and we got called
557 * again. We assume that property Get() calls are
558 * ultimately without side-effects or if they aren't
559 * then at least idempotent. */
561 r = sd_bus_message_open_container(reply, 'v', c->vtable->x.property.signature);
565 /* Note that we do not do an access check here. Read
566 * access to properties is always unrestricted, since
567 * PropertiesChanged signals broadcast contents
570 r = invoke_property_get(bus, c->vtable, m->path, c->interface, c->member, reply, u, &error);
572 return bus_maybe_reply_error(m, r, &error);
574 if (bus->nodes_modified)
577 r = sd_bus_message_close_container(reply);
582 if (c->vtable->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY)
583 return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_PROPERTY_READ_ONLY, "Property '%s' is not writable.", c->member);
585 /* Avoid that we call the set routine more than once
586 * if the processing of this message got restarted
587 * because the node tree changed. */
588 if (c->last_iteration == bus->iteration_counter)
591 c->last_iteration = bus->iteration_counter;
593 r = sd_bus_message_enter_container(m, 'v', c->vtable->x.property.signature);
597 r = check_access(bus, m, c, &error);
599 return bus_maybe_reply_error(m, r, &error);
601 r = invoke_property_set(bus, c->vtable, m->path, c->interface, c->member, m, u, &error);
603 return bus_maybe_reply_error(m, r, &error);
605 if (bus->nodes_modified)
608 r = sd_bus_message_exit_container(m);
613 r = sd_bus_send(bus, reply, NULL);
620 static int vtable_append_one_property(
622 sd_bus_message *reply,
624 struct node_vtable *c,
625 const sd_bus_vtable *v,
627 sd_bus_error *error) {
637 r = sd_bus_message_open_container(reply, 'e', "sv");
641 r = sd_bus_message_append(reply, "s", v->x.property.member);
645 r = sd_bus_message_open_container(reply, 'v', v->x.property.signature);
649 r = invoke_property_get(bus, v, path, c->interface, v->x.property.member, reply, vtable_property_convert_userdata(v, userdata), error);
652 if (bus->nodes_modified)
655 r = sd_bus_message_close_container(reply);
659 r = sd_bus_message_close_container(reply);
666 static int vtable_append_all_properties(
668 sd_bus_message *reply,
670 struct node_vtable *c,
672 sd_bus_error *error) {
674 const sd_bus_vtable *v;
682 if (c->vtable[0].flags & SD_BUS_VTABLE_HIDDEN)
685 for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) {
686 if (v->type != _SD_BUS_VTABLE_PROPERTY && v->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY)
689 if (v->flags & SD_BUS_VTABLE_HIDDEN)
692 r = vtable_append_one_property(bus, reply, path, c, v, userdata, error);
695 if (bus->nodes_modified)
702 static int property_get_all_callbacks_run(
705 struct node_vtable *first,
706 bool require_fallback,
708 bool *found_object) {
710 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
711 struct node_vtable *c;
712 bool found_interface;
717 assert(found_object);
719 r = sd_bus_message_new_method_return(m, &reply);
723 r = sd_bus_message_open_container(reply, 'a', "{sv}");
727 found_interface = !iface ||
728 streq(iface, "org.freedesktop.DBus.Properties") ||
729 streq(iface, "org.freedesktop.DBus.Peer") ||
730 streq(iface, "org.freedesktop.DBus.Introspectable");
732 LIST_FOREACH(vtables, c, first) {
733 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
736 if (require_fallback && !c->is_fallback)
739 r = node_vtable_get_userdata(bus, m->path, c, &u, &error);
741 return bus_maybe_reply_error(m, r, &error);
742 if (bus->nodes_modified)
747 *found_object = true;
749 if (iface && !streq(c->interface, iface))
751 found_interface = true;
753 r = vtable_append_all_properties(bus, reply, m->path, c, u, &error);
755 return bus_maybe_reply_error(m, r, &error);
756 if (bus->nodes_modified)
760 if (!found_interface) {
761 r = sd_bus_reply_method_errorf(
763 SD_BUS_ERROR_UNKNOWN_INTERFACE,
764 "Unknown interface '%s'.", iface);
771 r = sd_bus_message_close_container(reply);
775 r = sd_bus_send(bus, reply, NULL);
782 static bool bus_node_with_object_manager(sd_bus *bus, struct node *n) {
786 if (n->object_manager)
790 return bus_node_with_object_manager(bus, n->parent);
795 static bool bus_node_exists(
799 bool require_fallback) {
801 struct node_vtable *c;
802 struct node_callback *k;
808 /* Tests if there's anything attached directly to this node
809 * for the specified path */
811 LIST_FOREACH(callbacks, k, n->callbacks) {
812 if (require_fallback && !k->is_fallback)
818 LIST_FOREACH(vtables, c, n->vtables) {
819 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
821 if (require_fallback && !c->is_fallback)
824 if (node_vtable_get_userdata(bus, path, c, NULL, &error) > 0)
826 if (bus->nodes_modified)
830 return !require_fallback && (n->enumerators || n->object_manager);
833 static int process_introspect(
837 bool require_fallback,
838 bool *found_object) {
840 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
841 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
842 _cleanup_set_free_free_ Set *s = NULL;
843 const char *previous_interface = NULL;
844 struct introspect intro;
845 struct node_vtable *c;
852 assert(found_object);
854 r = get_child_nodes(bus, m->path, n, &s, &error);
856 return bus_maybe_reply_error(m, r, &error);
857 if (bus->nodes_modified)
860 r = introspect_begin(&intro, bus->trusted);
864 r = introspect_write_default_interfaces(&intro, bus_node_with_object_manager(bus, n));
868 empty = set_isempty(s);
870 LIST_FOREACH(vtables, c, n->vtables) {
871 if (require_fallback && !c->is_fallback)
874 r = node_vtable_get_userdata(bus, m->path, c, NULL, &error);
876 r = bus_maybe_reply_error(m, r, &error);
879 if (bus->nodes_modified) {
888 if (c->vtable[0].flags & SD_BUS_VTABLE_HIDDEN)
891 if (!streq_ptr(previous_interface, c->interface)) {
893 if (previous_interface)
894 fputs(" </interface>\n", intro.f);
896 fprintf(intro.f, " <interface name=\"%s\">\n", c->interface);
899 r = introspect_write_interface(&intro, c->vtable);
903 previous_interface = c->interface;
906 if (previous_interface)
907 fputs(" </interface>\n", intro.f);
910 /* Nothing?, let's see if we exist at all, and if not
911 * refuse to do anything */
912 r = bus_node_exists(bus, n, m->path, require_fallback);
915 if (bus->nodes_modified)
921 *found_object = true;
923 r = introspect_write_child_nodes(&intro, s, m->path);
927 r = introspect_finish(&intro, bus, m, &reply);
931 r = sd_bus_send(bus, reply, NULL);
938 introspect_free(&intro);
942 static int object_manager_serialize_path(
944 sd_bus_message *reply,
947 bool require_fallback,
948 sd_bus_error *error) {
950 const char *previous_interface = NULL;
951 bool found_something = false;
952 struct node_vtable *i;
962 n = hashmap_get(bus->nodes, prefix);
966 LIST_FOREACH(vtables, i, n->vtables) {
969 if (require_fallback && !i->is_fallback)
972 r = node_vtable_get_userdata(bus, path, i, &u, error);
975 if (bus->nodes_modified)
980 if (!found_something) {
982 /* Open the object part */
984 r = sd_bus_message_open_container(reply, 'e', "oa{sa{sv}}");
988 r = sd_bus_message_append(reply, "o", path);
992 r = sd_bus_message_open_container(reply, 'a', "{sa{sv}}");
996 found_something = true;
999 if (!streq_ptr(previous_interface, i->interface)) {
1001 /* Maybe close the previous interface part */
1003 if (previous_interface) {
1004 r = sd_bus_message_close_container(reply);
1008 r = sd_bus_message_close_container(reply);
1013 /* Open the new interface part */
1015 r = sd_bus_message_open_container(reply, 'e', "sa{sv}");
1019 r = sd_bus_message_append(reply, "s", i->interface);
1023 r = sd_bus_message_open_container(reply, 'a', "{sv}");
1028 r = vtable_append_all_properties(bus, reply, path, i, u, error);
1031 if (bus->nodes_modified)
1034 previous_interface = i->interface;
1037 if (previous_interface) {
1038 r = sd_bus_message_close_container(reply);
1042 r = sd_bus_message_close_container(reply);
1047 if (found_something) {
1048 r = sd_bus_message_close_container(reply);
1052 r = sd_bus_message_close_container(reply);
1060 static int object_manager_serialize_path_and_fallbacks(
1062 sd_bus_message *reply,
1064 sd_bus_error *error) {
1074 /* First, add all vtables registered for this path */
1075 r = object_manager_serialize_path(bus, reply, path, path, false, error);
1078 if (bus->nodes_modified)
1081 /* Second, add fallback vtables registered for any of the prefixes */
1082 prefix = alloca(strlen(path) + 1);
1083 OBJECT_PATH_FOREACH_PREFIX(prefix, path) {
1084 r = object_manager_serialize_path(bus, reply, prefix, path, true, error);
1087 if (bus->nodes_modified)
1094 static int process_get_managed_objects(
1098 bool require_fallback,
1099 bool *found_object) {
1101 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1102 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
1103 _cleanup_set_free_free_ Set *s = NULL;
1110 assert(found_object);
1112 if (!bus_node_with_object_manager(bus, n))
1115 r = get_child_nodes(bus, m->path, n, &s, &error);
1118 if (bus->nodes_modified)
1121 r = sd_bus_message_new_method_return(m, &reply);
1125 r = sd_bus_message_open_container(reply, 'a', "{oa{sa{sv}}}");
1129 empty = set_isempty(s);
1131 struct node_vtable *c;
1133 /* Hmm, so we have no children? Then let's check
1134 * whether we exist at all, i.e. whether at least one
1137 LIST_FOREACH(vtables, c, n->vtables) {
1139 if (require_fallback && !c->is_fallback)
1157 SET_FOREACH(path, s, i) {
1158 r = object_manager_serialize_path_and_fallbacks(bus, reply, path, &error);
1162 if (bus->nodes_modified)
1167 r = sd_bus_message_close_container(reply);
1171 r = sd_bus_send(bus, reply, NULL);
1178 static int object_find_and_run(
1182 bool require_fallback,
1183 bool *found_object) {
1186 struct vtable_member vtable_key, *v;
1192 assert(found_object);
1194 n = hashmap_get(bus->nodes, p);
1198 /* First, try object callbacks */
1199 r = node_callbacks_run(bus, m, n->callbacks, require_fallback, found_object);
1202 if (bus->nodes_modified)
1205 if (!m->interface || !m->member)
1208 /* Then, look for a known method */
1209 vtable_key.path = (char*) p;
1210 vtable_key.interface = m->interface;
1211 vtable_key.member = m->member;
1213 v = hashmap_get(bus->vtable_methods, &vtable_key);
1215 r = method_callbacks_run(bus, m, v, require_fallback, found_object);
1218 if (bus->nodes_modified)
1222 /* Then, look for a known property */
1223 if (streq(m->interface, "org.freedesktop.DBus.Properties")) {
1226 get = streq(m->member, "Get");
1228 if (get || streq(m->member, "Set")) {
1230 r = sd_bus_message_rewind(m, true);
1234 vtable_key.path = (char*) p;
1236 r = sd_bus_message_read(m, "ss", &vtable_key.interface, &vtable_key.member);
1238 return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected interface and member parameters");
1240 v = hashmap_get(bus->vtable_properties, &vtable_key);
1242 r = property_get_set_callbacks_run(bus, m, v, require_fallback, get, found_object);
1247 } else if (streq(m->member, "GetAll")) {
1250 r = sd_bus_message_rewind(m, true);
1254 r = sd_bus_message_read(m, "s", &iface);
1256 return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected interface parameter");
1261 r = property_get_all_callbacks_run(bus, m, n->vtables, require_fallback, iface, found_object);
1266 } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
1268 if (!isempty(sd_bus_message_get_signature(m, true)))
1269 return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected no parameters");
1271 r = process_introspect(bus, m, n, require_fallback, found_object);
1275 } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.ObjectManager", "GetManagedObjects")) {
1277 if (!isempty(sd_bus_message_get_signature(m, true)))
1278 return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected no parameters");
1280 r = process_get_managed_objects(bus, m, n, require_fallback, found_object);
1285 if (bus->nodes_modified)
1288 if (!*found_object) {
1289 r = bus_node_exists(bus, n, m->path, require_fallback);
1293 *found_object = true;
1299 int bus_process_object(sd_bus *bus, sd_bus_message *m) {
1302 bool found_object = false;
1307 if (bus->hello_flags & KDBUS_HELLO_MONITOR)
1310 if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL)
1313 if (hashmap_isempty(bus->nodes))
1316 /* Never respond to broadcast messages */
1317 if (bus->bus_client && !m->destination)
1323 pl = strlen(m->path);
1327 bus->nodes_modified = false;
1329 r = object_find_and_run(bus, m, m->path, false, &found_object);
1333 /* Look for fallback prefixes */
1334 OBJECT_PATH_FOREACH_PREFIX(prefix, m->path) {
1336 if (bus->nodes_modified)
1339 r = object_find_and_run(bus, m, prefix, true, &found_object);
1344 } while (bus->nodes_modified);
1349 if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Get") ||
1350 sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Set"))
1351 r = sd_bus_reply_method_errorf(
1353 SD_BUS_ERROR_UNKNOWN_PROPERTY,
1354 "Unknown property or interface.");
1356 r = sd_bus_reply_method_errorf(
1358 SD_BUS_ERROR_UNKNOWN_METHOD,
1359 "Unknown method '%s' or interface '%s'.", m->member, m->interface);
1367 static struct node *bus_node_allocate(sd_bus *bus, const char *path) {
1368 struct node *n, *parent;
1370 _cleanup_free_ char *s = NULL;
1376 assert(path[0] == '/');
1378 n = hashmap_get(bus->nodes, path);
1382 r = hashmap_ensure_allocated(&bus->nodes, string_hash_func, string_compare_func);
1390 if (streq(path, "/"))
1393 e = strrchr(path, '/');
1396 p = strndupa(path, MAX(1, path - e));
1398 parent = bus_node_allocate(bus, p);
1403 n = new0(struct node, 1);
1409 s = NULL; /* do not free */
1411 r = hashmap_put(bus->nodes, n->path, n);
1419 LIST_PREPEND(siblings, parent->child, n);
1424 static void bus_node_gc(sd_bus *b, struct node *n) {
1437 assert(hashmap_remove(b->nodes, n->path) == n);
1440 LIST_REMOVE(siblings, n->parent->child, n);
1443 bus_node_gc(b, n->parent);
1447 static int bus_add_object(
1451 sd_bus_message_handler_t callback,
1454 struct node_callback *c;
1458 assert_return(bus, -EINVAL);
1459 assert_return(object_path_is_valid(path), -EINVAL);
1460 assert_return(callback, -EINVAL);
1461 assert_return(!bus_pid_changed(bus), -ECHILD);
1463 n = bus_node_allocate(bus, path);
1467 c = new0(struct node_callback, 1);
1474 c->callback = callback;
1475 c->userdata = userdata;
1476 c->is_fallback = fallback;
1478 LIST_PREPEND(callbacks, n->callbacks, c);
1479 bus->nodes_modified = true;
1485 bus_node_gc(bus, n);
1489 static int bus_remove_object(
1493 sd_bus_message_handler_t callback,
1496 struct node_callback *c;
1499 assert_return(bus, -EINVAL);
1500 assert_return(object_path_is_valid(path), -EINVAL);
1501 assert_return(callback, -EINVAL);
1502 assert_return(!bus_pid_changed(bus), -ECHILD);
1504 n = hashmap_get(bus->nodes, path);
1508 LIST_FOREACH(callbacks, c, n->callbacks)
1509 if (c->callback == callback && c->userdata == userdata && c->is_fallback == fallback)
1514 LIST_REMOVE(callbacks, n->callbacks, c);
1517 bus_node_gc(bus, n);
1518 bus->nodes_modified = true;
1523 _public_ int sd_bus_add_object(sd_bus *bus,
1525 sd_bus_message_handler_t callback,
1528 return bus_add_object(bus, false, path, callback, userdata);
1531 _public_ int sd_bus_remove_object(sd_bus *bus,
1533 sd_bus_message_handler_t callback,
1536 return bus_remove_object(bus, false, path, callback, userdata);
1539 _public_ int sd_bus_add_fallback(sd_bus *bus,
1541 sd_bus_message_handler_t callback,
1544 return bus_add_object(bus, true, prefix, callback, userdata);
1547 _public_ int sd_bus_remove_fallback(sd_bus *bus,
1549 sd_bus_message_handler_t callback,
1552 return bus_remove_object(bus, true, prefix, callback, userdata);
1555 static void free_node_vtable(sd_bus *bus, struct node_vtable *w) {
1561 if (w->interface && w->node && w->vtable) {
1562 const sd_bus_vtable *v;
1564 for (v = w->vtable; v->type != _SD_BUS_VTABLE_END; v++) {
1565 struct vtable_member *x = NULL;
1569 case _SD_BUS_VTABLE_METHOD: {
1570 struct vtable_member key;
1572 key.path = w->node->path;
1573 key.interface = w->interface;
1574 key.member = v->x.method.member;
1576 x = hashmap_remove(bus->vtable_methods, &key);
1580 case _SD_BUS_VTABLE_PROPERTY:
1581 case _SD_BUS_VTABLE_WRITABLE_PROPERTY: {
1582 struct vtable_member key;
1584 key.path = w->node->path;
1585 key.interface = w->interface;
1586 key.member = v->x.property.member;
1587 x = hashmap_remove(bus->vtable_properties, &key);
1599 static unsigned long vtable_member_hash_func(const void *a, const uint8_t hash_key[HASH_KEY_SIZE]) {
1600 const struct vtable_member *m = a;
1601 uint8_t hash_key2[HASH_KEY_SIZE];
1606 ret = string_hash_func(m->path, hash_key);
1608 /* Use a slightly different hash key for the interface */
1609 memcpy(hash_key2, hash_key, HASH_KEY_SIZE);
1611 ret ^= string_hash_func(m->interface, hash_key2);
1613 /* And an even different one for the member */
1615 ret ^= string_hash_func(m->member, hash_key2);
1620 static int vtable_member_compare_func(const void *a, const void *b) {
1621 const struct vtable_member *x = a, *y = b;
1627 r = strcmp(x->path, y->path);
1631 r = strcmp(x->interface, y->interface);
1635 return strcmp(x->member, y->member);
1638 static int add_object_vtable_internal(
1641 const char *interface,
1642 const sd_bus_vtable *vtable,
1644 sd_bus_object_find_t find,
1647 struct node_vtable *c = NULL, *i, *existing = NULL;
1648 const sd_bus_vtable *v;
1652 assert_return(bus, -EINVAL);
1653 assert_return(object_path_is_valid(path), -EINVAL);
1654 assert_return(interface_name_is_valid(interface), -EINVAL);
1655 assert_return(vtable, -EINVAL);
1656 assert_return(vtable[0].type == _SD_BUS_VTABLE_START, -EINVAL);
1657 assert_return(vtable[0].x.start.element_size == sizeof(struct sd_bus_vtable), -EINVAL);
1658 assert_return(!bus_pid_changed(bus), -ECHILD);
1659 assert_return(!streq(interface, "org.freedesktop.DBus.Properties") &&
1660 !streq(interface, "org.freedesktop.DBus.Introspectable") &&
1661 !streq(interface, "org.freedesktop.DBus.Peer") &&
1662 !streq(interface, "org.freedesktop.DBus.ObjectManager"), -EINVAL);
1664 r = hashmap_ensure_allocated(&bus->vtable_methods, vtable_member_hash_func, vtable_member_compare_func);
1668 r = hashmap_ensure_allocated(&bus->vtable_properties, vtable_member_hash_func, vtable_member_compare_func);
1672 n = bus_node_allocate(bus, path);
1676 LIST_FOREACH(vtables, i, n->vtables) {
1677 if (i->is_fallback != fallback) {
1682 if (streq(i->interface, interface)) {
1684 if (i->vtable == vtable) {
1693 c = new0(struct node_vtable, 1);
1700 c->is_fallback = fallback;
1702 c->userdata = userdata;
1705 c->interface = strdup(interface);
1706 if (!c->interface) {
1711 for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) {
1715 case _SD_BUS_VTABLE_METHOD: {
1716 struct vtable_member *m;
1718 if (!member_name_is_valid(v->x.method.member) ||
1719 !signature_is_valid(strempty(v->x.method.signature), false) ||
1720 !signature_is_valid(strempty(v->x.method.result), false) ||
1721 !(v->x.method.handler || (isempty(v->x.method.signature) && isempty(v->x.method.result))) ||
1722 v->flags & (SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) {
1727 m = new0(struct vtable_member, 1);
1735 m->interface = c->interface;
1736 m->member = v->x.method.member;
1739 r = hashmap_put(bus->vtable_methods, m, m);
1748 case _SD_BUS_VTABLE_WRITABLE_PROPERTY:
1750 if (!(v->x.property.set || bus_type_is_basic(v->x.property.signature[0]))) {
1757 case _SD_BUS_VTABLE_PROPERTY: {
1758 struct vtable_member *m;
1760 if (!member_name_is_valid(v->x.property.member) ||
1761 !signature_is_single(v->x.property.signature, false) ||
1762 !(v->x.property.get || bus_type_is_basic(v->x.property.signature[0]) || streq(v->x.property.signature, "as")) ||
1763 v->flags & SD_BUS_VTABLE_METHOD_NO_REPLY ||
1764 (!!(v->flags & SD_BUS_VTABLE_PROPERTY_CONST) + !!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) + !!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) > 1 ||
1765 (v->flags & SD_BUS_VTABLE_UNPRIVILEGED && v->type == _SD_BUS_VTABLE_PROPERTY)) {
1770 m = new0(struct vtable_member, 1);
1778 m->interface = c->interface;
1779 m->member = v->x.property.member;
1782 r = hashmap_put(bus->vtable_properties, m, m);
1791 case _SD_BUS_VTABLE_SIGNAL:
1793 if (!member_name_is_valid(v->x.signal.member) ||
1794 !signature_is_valid(strempty(v->x.signal.signature), false) ||
1795 v->flags & SD_BUS_VTABLE_UNPRIVILEGED) {
1808 LIST_INSERT_AFTER(vtables, n->vtables, existing, c);
1809 bus->nodes_modified = true;
1815 free_node_vtable(bus, c);
1817 bus_node_gc(bus, n);
1821 static int remove_object_vtable_internal(
1824 const char *interface,
1825 const sd_bus_vtable *vtable,
1827 sd_bus_object_find_t find,
1830 struct node_vtable *c;
1833 assert_return(bus, -EINVAL);
1834 assert_return(object_path_is_valid(path), -EINVAL);
1835 assert_return(interface_name_is_valid(interface), -EINVAL);
1836 assert_return(!bus_pid_changed(bus), -ECHILD);
1838 n = hashmap_get(bus->nodes, path);
1842 LIST_FOREACH(vtables, c, n->vtables)
1843 if (streq(c->interface, interface) &&
1844 c->is_fallback == fallback &&
1845 c->vtable == vtable &&
1847 c->userdata == userdata)
1853 LIST_REMOVE(vtables, n->vtables, c);
1855 free_node_vtable(bus, c);
1856 bus_node_gc(bus, n);
1858 bus->nodes_modified = true;
1863 _public_ int sd_bus_add_object_vtable(
1866 const char *interface,
1867 const sd_bus_vtable *vtable,
1870 return add_object_vtable_internal(bus, path, interface, vtable, false, NULL, userdata);
1873 _public_ int sd_bus_remove_object_vtable(
1876 const char *interface,
1877 const sd_bus_vtable *vtable,
1880 return remove_object_vtable_internal(bus, path, interface, vtable, false, NULL, userdata);
1883 _public_ int sd_bus_add_fallback_vtable(
1886 const char *interface,
1887 const sd_bus_vtable *vtable,
1888 sd_bus_object_find_t find,
1891 return add_object_vtable_internal(bus, path, interface, vtable, true, find, userdata);
1894 _public_ int sd_bus_remove_fallback_vtable(
1897 const char *interface,
1898 const sd_bus_vtable *vtable,
1899 sd_bus_object_find_t find,
1902 return remove_object_vtable_internal(bus, path, interface, vtable, true, find, userdata);
1905 _public_ int sd_bus_add_node_enumerator(
1908 sd_bus_node_enumerator_t callback,
1911 struct node_enumerator *c;
1915 assert_return(bus, -EINVAL);
1916 assert_return(object_path_is_valid(path), -EINVAL);
1917 assert_return(callback, -EINVAL);
1918 assert_return(!bus_pid_changed(bus), -ECHILD);
1920 n = bus_node_allocate(bus, path);
1924 c = new0(struct node_enumerator, 1);
1931 c->callback = callback;
1932 c->userdata = userdata;
1934 LIST_PREPEND(enumerators, n->enumerators, c);
1936 bus->nodes_modified = true;
1942 bus_node_gc(bus, n);
1946 _public_ int sd_bus_remove_node_enumerator(
1949 sd_bus_node_enumerator_t callback,
1952 struct node_enumerator *c;
1955 assert_return(bus, -EINVAL);
1956 assert_return(object_path_is_valid(path), -EINVAL);
1957 assert_return(callback, -EINVAL);
1958 assert_return(!bus_pid_changed(bus), -ECHILD);
1960 n = hashmap_get(bus->nodes, path);
1964 LIST_FOREACH(enumerators, c, n->enumerators)
1965 if (c->callback == callback && c->userdata == userdata)
1971 LIST_REMOVE(enumerators, n->enumerators, c);
1974 bus_node_gc(bus, n);
1976 bus->nodes_modified = true;
1981 static int emit_properties_changed_on_interface(
1985 const char *interface,
1986 bool require_fallback,
1987 bool *found_interface,
1990 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1991 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
1992 bool has_invalidating = false, has_changing = false;
1993 struct vtable_member key = {};
1994 struct node_vtable *c;
2004 assert(found_interface);
2006 n = hashmap_get(bus->nodes, prefix);
2010 r = sd_bus_message_new_signal(bus, &m, path, "org.freedesktop.DBus.Properties", "PropertiesChanged");
2014 r = sd_bus_message_append(m, "s", interface);
2018 r = sd_bus_message_open_container(m, 'a', "{sv}");
2023 key.interface = interface;
2025 LIST_FOREACH(vtables, c, n->vtables) {
2026 if (require_fallback && !c->is_fallback)
2029 if (!streq(c->interface, interface))
2032 r = node_vtable_get_userdata(bus, path, c, &u, &error);
2035 if (bus->nodes_modified)
2040 *found_interface = true;
2043 /* If the caller specified a list of
2044 * properties we include exactly those in the
2045 * PropertiesChanged message */
2047 STRV_FOREACH(property, names) {
2048 struct vtable_member *v;
2050 assert_return(member_name_is_valid(*property), -EINVAL);
2052 key.member = *property;
2053 v = hashmap_get(bus->vtable_properties, &key);
2057 /* If there are two vtables for the same
2058 * interface, let's handle this property when
2059 * we come to that vtable. */
2063 assert_return(v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE ||
2064 v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION, -EDOM);
2066 assert_return(!(v->vtable->flags & SD_BUS_VTABLE_HIDDEN), -EDOM);
2068 if (v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) {
2069 has_invalidating = true;
2073 has_changing = true;
2075 r = vtable_append_one_property(bus, m, m->path, c, v->vtable, u, &error);
2078 if (bus->nodes_modified)
2082 const sd_bus_vtable *v;
2084 /* If the caller specified no properties list
2085 * we include all properties that are marked
2086 * as changing in the message. */
2088 for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) {
2089 if (v->type != _SD_BUS_VTABLE_PROPERTY && v->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY)
2092 if (v->flags & SD_BUS_VTABLE_HIDDEN)
2095 if (v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) {
2096 has_invalidating = true;
2100 if (!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE))
2103 has_changing = true;
2105 r = vtable_append_one_property(bus, m, m->path, c, v, u, &error);
2108 if (bus->nodes_modified)
2114 if (!has_invalidating && !has_changing)
2117 r = sd_bus_message_close_container(m);
2121 r = sd_bus_message_open_container(m, 'a', "s");
2125 if (has_invalidating) {
2126 LIST_FOREACH(vtables, c, n->vtables) {
2127 if (require_fallback && !c->is_fallback)
2130 if (!streq(c->interface, interface))
2133 r = node_vtable_get_userdata(bus, path, c, &u, &error);
2136 if (bus->nodes_modified)
2142 STRV_FOREACH(property, names) {
2143 struct vtable_member *v;
2145 key.member = *property;
2146 assert_se(v = hashmap_get(bus->vtable_properties, &key));
2147 assert(c == v->parent);
2149 if (!(v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION))
2152 r = sd_bus_message_append(m, "s", *property);
2157 const sd_bus_vtable *v;
2159 for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) {
2160 if (v->type != _SD_BUS_VTABLE_PROPERTY && v->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY)
2163 if (v->flags & SD_BUS_VTABLE_HIDDEN)
2166 if (!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION))
2169 r = sd_bus_message_append(m, "s", v->x.property.member);
2177 r = sd_bus_message_close_container(m);
2181 r = sd_bus_send(bus, m, NULL);
2188 _public_ int sd_bus_emit_properties_changed_strv(
2191 const char *interface,
2194 BUS_DONT_DESTROY(bus);
2195 bool found_interface = false;
2199 assert_return(bus, -EINVAL);
2200 assert_return(object_path_is_valid(path), -EINVAL);
2201 assert_return(interface_name_is_valid(interface), -EINVAL);
2202 assert_return(!bus_pid_changed(bus), -ECHILD);
2204 if (!BUS_IS_OPEN(bus->state))
2207 /* A non-NULL but empty names list means nothing needs to be
2208 generated. A NULL list OTOH indicates that all properties
2209 that are set to EMITS_CHANGE or EMITS_INVALIDATION shall be
2210 included in the PropertiesChanged message. */
2211 if (names && names[0] == NULL)
2215 bus->nodes_modified = false;
2217 r = emit_properties_changed_on_interface(bus, path, path, interface, false, &found_interface, names);
2220 if (bus->nodes_modified)
2223 prefix = alloca(strlen(path) + 1);
2224 OBJECT_PATH_FOREACH_PREFIX(prefix, path) {
2225 r = emit_properties_changed_on_interface(bus, prefix, path, interface, true, &found_interface, names);
2228 if (bus->nodes_modified)
2232 } while (bus->nodes_modified);
2234 return found_interface ? 0 : -ENOENT;
2237 _public_ int sd_bus_emit_properties_changed(
2240 const char *interface,
2241 const char *name, ...) {
2245 assert_return(bus, -EINVAL);
2246 assert_return(object_path_is_valid(path), -EINVAL);
2247 assert_return(interface_name_is_valid(interface), -EINVAL);
2248 assert_return(!bus_pid_changed(bus), -ECHILD);
2250 if (!BUS_IS_OPEN(bus->state))
2256 names = strv_from_stdarg_alloca(name);
2258 return sd_bus_emit_properties_changed_strv(bus, path, interface, names);
2261 static int interfaces_added_append_one_prefix(
2266 const char *interface,
2267 bool require_fallback) {
2269 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
2270 bool found_interface = false;
2271 struct node_vtable *c;
2282 n = hashmap_get(bus->nodes, prefix);
2286 LIST_FOREACH(vtables, c, n->vtables) {
2287 if (require_fallback && !c->is_fallback)
2290 if (!streq(c->interface, interface))
2293 r = node_vtable_get_userdata(bus, path, c, &u, &error);
2296 if (bus->nodes_modified)
2301 if (!found_interface) {
2302 r = sd_bus_message_append_basic(m, 's', interface);
2306 r = sd_bus_message_open_container(m, 'a', "{sv}");
2310 found_interface = true;
2313 r = vtable_append_all_properties(bus, m, path, c, u, &error);
2316 if (bus->nodes_modified)
2320 if (found_interface) {
2321 r = sd_bus_message_close_container(m);
2326 return found_interface;
2329 static int interfaces_added_append_one(
2333 const char *interface) {
2343 r = interfaces_added_append_one_prefix(bus, m, path, path, interface, false);
2346 if (bus->nodes_modified)
2349 prefix = alloca(strlen(path) + 1);
2350 OBJECT_PATH_FOREACH_PREFIX(prefix, path) {
2351 r = interfaces_added_append_one_prefix(bus, m, prefix, path, interface, true);
2354 if (bus->nodes_modified)
2361 _public_ int sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, char **interfaces) {
2362 BUS_DONT_DESTROY(bus);
2364 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
2368 assert_return(bus, -EINVAL);
2369 assert_return(object_path_is_valid(path), -EINVAL);
2370 assert_return(!bus_pid_changed(bus), -ECHILD);
2372 if (!BUS_IS_OPEN(bus->state))
2375 if (strv_isempty(interfaces))
2379 bus->nodes_modified = false;
2382 m = sd_bus_message_unref(m);
2384 r = sd_bus_message_new_signal(bus, &m, path, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded");
2388 r = sd_bus_message_append_basic(m, 'o', path);
2392 r = sd_bus_message_open_container(m, 'a', "{sa{sv}}");
2396 STRV_FOREACH(i, interfaces) {
2397 assert_return(interface_name_is_valid(*i), -EINVAL);
2399 r = sd_bus_message_open_container(m, 'e', "sa{sv}");
2403 r = interfaces_added_append_one(bus, m, path, *i);
2407 if (bus->nodes_modified)
2410 r = sd_bus_message_close_container(m);
2415 if (bus->nodes_modified)
2418 r = sd_bus_message_close_container(m);
2422 } while (bus->nodes_modified);
2424 return sd_bus_send(bus, m, NULL);
2427 _public_ int sd_bus_emit_interfaces_added(sd_bus *bus, const char *path, const char *interface, ...) {
2430 assert_return(bus, -EINVAL);
2431 assert_return(object_path_is_valid(path), -EINVAL);
2432 assert_return(!bus_pid_changed(bus), -ECHILD);
2434 if (!BUS_IS_OPEN(bus->state))
2437 interfaces = strv_from_stdarg_alloca(interface);
2439 return sd_bus_emit_interfaces_added_strv(bus, path, interfaces);
2442 _public_ int sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path, char **interfaces) {
2443 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
2446 assert_return(bus, -EINVAL);
2447 assert_return(object_path_is_valid(path), -EINVAL);
2448 assert_return(!bus_pid_changed(bus), -ECHILD);
2450 if (!BUS_IS_OPEN(bus->state))
2453 if (strv_isempty(interfaces))
2456 r = sd_bus_message_new_signal(bus, &m, path, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved");
2460 r = sd_bus_message_append_basic(m, 'o', path);
2464 r = sd_bus_message_append_strv(m, interfaces);
2468 return sd_bus_send(bus, m, NULL);
2471 _public_ int sd_bus_emit_interfaces_removed(sd_bus *bus, const char *path, const char *interface, ...) {
2474 assert_return(bus, -EINVAL);
2475 assert_return(object_path_is_valid(path), -EINVAL);
2476 assert_return(!bus_pid_changed(bus), -ECHILD);
2478 if (!BUS_IS_OPEN(bus->state))
2481 interfaces = strv_from_stdarg_alloca(interface);
2483 return sd_bus_emit_interfaces_removed_strv(bus, path, interfaces);
2486 _public_ int sd_bus_add_object_manager(sd_bus *bus, const char *path) {
2489 assert_return(bus, -EINVAL);
2490 assert_return(object_path_is_valid(path), -EINVAL);
2491 assert_return(!bus_pid_changed(bus), -ECHILD);
2493 n = bus_node_allocate(bus, path);
2497 n->object_manager = true;
2498 bus->nodes_modified = true;
2502 _public_ int sd_bus_remove_object_manager(sd_bus *bus, const char *path) {
2505 assert_return(bus, -EINVAL);
2506 assert_return(object_path_is_valid(path), -EINVAL);
2507 assert_return(!bus_pid_changed(bus), -ECHILD);
2509 n = hashmap_get(bus->nodes, path);
2513 if (!n->object_manager)
2516 n->object_manager = false;
2517 bus->nodes_modified = true;
2518 bus_node_gc(bus, n);