| // SPDX-License-Identifier: Apache-2.0 | |
| // SPDX-FileCopyrightText: Copyright 2016 IBM Corporation | |
| #include "mapper.h" | |
| #include "internal.h" | |
| #include <errno.h> | |
| #include <stdbool.h> | |
| #include <stddef.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/timerfd.h> | |
| #include <systemd/sd-bus.h> | |
| #include <systemd/sd-event.h> | |
| #include <unistd.h> | |
| #define _public_ __attribute__((__visibility__("default"))) | |
| #define _unused_ __attribute__((unused)) | |
| static const char* async_wait_introspection_match = | |
| "type='signal'," | |
| "sender='xyz.openbmc_project.ObjectMapper'," | |
| "interface='xyz.openbmc_project.ObjectMapper.Private'," | |
| "member='IntrospectionComplete'"; | |
| static const char* async_wait_interfaces_added_match = | |
| "type='signal'," | |
| "interface='org.freedesktop.DBus.ObjectManager'," | |
| "member='InterfacesAdded'"; | |
| static const char* interfaces_removed_match = | |
| "type='signal'," | |
| "interface='org.freedesktop.DBus.ObjectManager'," | |
| "member='InterfacesRemoved'"; | |
| static const int mapper_busy_retries = 5; | |
| static const uint64_t mapper_busy_delay_interval_usec = 1000000; | |
| struct mapper_async_wait | |
| { | |
| char** objs; | |
| void (*callback)(int, void*); | |
| void* userdata; | |
| sd_event* loop; | |
| sd_bus* conn; | |
| sd_bus_slot* introspection_slot; | |
| sd_bus_slot* intf_slot; | |
| int* status; | |
| size_t count; | |
| int finished; | |
| int r; | |
| }; | |
| struct async_wait_callback_data | |
| { | |
| mapper_async_wait* wait; | |
| const char* path; | |
| sd_event_source* event_source; | |
| int retry; | |
| }; | |
| struct mapper_async_subtree | |
| { | |
| char* namespace; | |
| char* interface; | |
| void (*callback)(int, void*); | |
| void* userdata; | |
| sd_event* loop; | |
| sd_bus* conn; | |
| sd_bus_slot* slot; | |
| sd_event_source* event_source; | |
| int finished; | |
| int op; | |
| int retry; | |
| }; | |
| static int async_wait_match_introspection_complete(sd_bus_message*, void*, | |
| sd_bus_error*); | |
| static int async_wait_check_done(mapper_async_wait*); | |
| static void async_wait_done(int r, mapper_async_wait*); | |
| static int async_wait_get_objects(mapper_async_wait*); | |
| static int async_wait_getobject_callback(sd_bus_message*, void*, sd_bus_error*); | |
| static int async_subtree_match_callback(sd_bus_message*, void*, sd_bus_error*); | |
| static void async_subtree_done(int r, mapper_async_subtree*); | |
| static int async_subtree_getpaths(mapper_async_subtree*); | |
| static int async_subtree_getpaths_callback(sd_bus_message*, void*, | |
| sd_bus_error*); | |
| size_t sarraylen(char* array[]) | |
| { | |
| size_t count = 0; | |
| char** p = array; | |
| while (*p != NULL) | |
| { | |
| ++count; | |
| ++p; | |
| } | |
| return count; | |
| } | |
| void sarrayfree(char* array[]) | |
| { | |
| char** p = array; | |
| while (*p != NULL) | |
| { | |
| free(*p); | |
| ++p; | |
| } | |
| free(array); | |
| } | |
| char** sarraydup(char* array[]) | |
| { | |
| size_t count = sarraylen(array); | |
| size_t i; | |
| char** ret = NULL; | |
| ret = calloc(count + 1, sizeof(*ret)); | |
| if (!ret) | |
| { | |
| return NULL; | |
| } | |
| for (i = 0; i < count; ++i) | |
| { | |
| ret[i] = strdup(array[i]); | |
| if (!ret[i]) | |
| { | |
| goto error; | |
| } | |
| } | |
| return ret; | |
| error: | |
| sarrayfree(ret); | |
| return NULL; | |
| } | |
| static int async_wait_timeout_callback(_unused_ sd_event_source* s, | |
| _unused_ uint64_t usec, void* userdata) | |
| { | |
| int r; | |
| struct async_wait_callback_data* data = userdata; | |
| mapper_async_wait* wait = data->wait; | |
| sd_event_source_unref(data->event_source); | |
| r = sd_bus_call_method_async( | |
| wait->conn, NULL, "xyz.openbmc_project.ObjectMapper", | |
| "/xyz/openbmc_project/object_mapper", | |
| "xyz.openbmc_project.ObjectMapper", "GetObject", | |
| async_wait_getobject_callback, data, "sas", data->path, 0, NULL); | |
| if (r < 0) | |
| { | |
| async_wait_done(r, wait); | |
| free(data); | |
| } | |
| return 0; | |
| } | |
| static int async_wait_getobject_callback(sd_bus_message* m, void* userdata, | |
| _unused_ sd_bus_error* e) | |
| { | |
| size_t i; | |
| int r; | |
| struct async_wait_callback_data* data = userdata; | |
| mapper_async_wait* wait = data->wait; | |
| uint64_t next_retry; | |
| if (wait->finished) | |
| { | |
| goto exit; | |
| } | |
| if (sd_bus_message_is_method_error( | |
| m, "xyz.openbmc_project.Common.Error.ResourceNotFound")) | |
| { | |
| goto exit; | |
| } | |
| r = sd_bus_message_get_errno(m); | |
| if ((r == EBUSY || r == ENOBUFS) && data->retry < mapper_busy_retries) | |
| { | |
| r = sd_event_now(wait->loop, CLOCK_MONOTONIC, &next_retry); | |
| if (r < 0) | |
| { | |
| async_wait_done(r, wait); | |
| goto exit; | |
| } | |
| next_retry += mapper_busy_delay_interval_usec * (1 << data->retry); | |
| r = sd_event_add_time(wait->loop, &data->event_source, CLOCK_MONOTONIC, | |
| next_retry, 0, async_wait_timeout_callback, data); | |
| ++data->retry; | |
| if (r < 0) | |
| { | |
| async_wait_done(r, wait); | |
| goto exit; | |
| } | |
| return 0; | |
| } | |
| if (r) | |
| { | |
| async_wait_done(-r, wait); | |
| goto exit; | |
| } | |
| for (i = 0; i < wait->count; ++i) | |
| { | |
| if (!strcmp(data->path, wait->objs[i])) | |
| { | |
| wait->status[i] = 1; | |
| } | |
| } | |
| if (async_wait_check_done(wait)) | |
| { | |
| async_wait_done(0, wait); | |
| } | |
| exit: | |
| free(data); | |
| return 0; | |
| } | |
| static int async_wait_get_objects(mapper_async_wait* wait) | |
| { | |
| size_t i; | |
| int r; | |
| struct async_wait_callback_data* data = NULL; | |
| for (i = 0; i < wait->count; ++i) | |
| { | |
| if (wait->status[i]) | |
| { | |
| continue; | |
| } | |
| data = malloc(sizeof(*data)); | |
| data->wait = wait; | |
| data->path = wait->objs[i]; | |
| data->retry = 0; | |
| data->event_source = NULL; | |
| r = sd_bus_call_method_async( | |
| wait->conn, NULL, "xyz.openbmc_project.ObjectMapper", | |
| "/xyz/openbmc_project/object_mapper", | |
| "xyz.openbmc_project.ObjectMapper", "GetObject", | |
| async_wait_getobject_callback, data, "sas", wait->objs[i], 0, NULL); | |
| if (r < 0) | |
| { | |
| free(data); | |
| fprintf(stderr, "Error invoking method: %s\n", strerror(-r)); | |
| return r; | |
| } | |
| } | |
| return 0; | |
| } | |
| static int async_wait_match_introspection_complete( | |
| _unused_ sd_bus_message* m, void* w, _unused_ sd_bus_error* e) | |
| { | |
| int r; | |
| mapper_async_wait* wait = w; | |
| if (wait->finished) | |
| { | |
| return 0; | |
| } | |
| r = async_wait_get_objects(wait); | |
| if (r < 0) | |
| { | |
| async_wait_done(r, wait); | |
| } | |
| return 0; | |
| } | |
| static void async_wait_done(int r, mapper_async_wait* w) | |
| { | |
| if (w->finished) | |
| { | |
| return; | |
| } | |
| w->finished = 1; | |
| sd_bus_slot_unref(w->introspection_slot); | |
| sd_bus_slot_unref(w->intf_slot); | |
| if (w->callback) | |
| { | |
| w->callback(r, w->userdata); | |
| } | |
| } | |
| static int async_wait_check_done(mapper_async_wait* w) | |
| { | |
| size_t i; | |
| if (w->finished) | |
| { | |
| return 1; | |
| } | |
| for (i = 0; i < w->count; ++i) | |
| { | |
| if (!w->status[i]) | |
| { | |
| return 0; | |
| } | |
| } | |
| return 1; | |
| } | |
| _public_ void mapper_wait_async_free(mapper_async_wait* w) | |
| { | |
| free(w->status); | |
| sarrayfree(w->objs); | |
| free(w); | |
| } | |
| _public_ int mapper_wait_async(sd_bus* conn, sd_event* loop, char* objs[], | |
| void (*callback)(int, void*), void* userdata, | |
| mapper_async_wait** w) | |
| { | |
| int r; | |
| mapper_async_wait* wait = NULL; | |
| wait = malloc(sizeof(*wait)); | |
| if (!wait) | |
| { | |
| return -ENOMEM; | |
| } | |
| memset(wait, 0, sizeof(*wait)); | |
| wait->conn = conn; | |
| wait->loop = loop; | |
| wait->callback = callback; | |
| wait->userdata = userdata; | |
| wait->count = sarraylen(objs); | |
| if (!wait->count) | |
| { | |
| r = 0; | |
| goto free_wait; | |
| } | |
| wait->objs = sarraydup(objs); | |
| if (!wait->objs) | |
| { | |
| r = -ENOMEM; | |
| goto free_wait; | |
| } | |
| wait->status = malloc(sizeof(*wait->status) * wait->count); | |
| if (!wait->status) | |
| { | |
| r = -ENOMEM; | |
| goto free_objs; | |
| } | |
| memset(wait->status, 0, sizeof(*wait->status) * wait->count); | |
| r = sd_bus_add_match(conn, &wait->introspection_slot, | |
| async_wait_introspection_match, | |
| async_wait_match_introspection_complete, wait); | |
| if (r < 0) | |
| { | |
| fprintf(stderr, "Error adding match rule: %s\n", strerror(-r)); | |
| goto free_status; | |
| } | |
| r = sd_bus_add_match(conn, &wait->intf_slot, | |
| async_wait_interfaces_added_match, | |
| async_wait_match_introspection_complete, wait); | |
| if (r < 0) | |
| { | |
| fprintf(stderr, "Error adding match rule: %s\n", strerror(-r)); | |
| goto unref_name_slot; | |
| } | |
| r = async_wait_get_objects(wait); | |
| if (r < 0) | |
| { | |
| fprintf(stderr, "Error calling method: %s\n", strerror(-r)); | |
| goto unref_intf_slot; | |
| } | |
| *w = wait; | |
| return 0; | |
| unref_intf_slot: | |
| sd_bus_slot_unref(wait->intf_slot); | |
| unref_name_slot: | |
| sd_bus_slot_unref(wait->introspection_slot); | |
| free_status: | |
| free(wait->status); | |
| free_objs: | |
| sarrayfree(wait->objs); | |
| free_wait: | |
| free(wait); | |
| return r; | |
| } | |
| static int async_subtree_timeout_callback( | |
| _unused_ sd_event_source* s, _unused_ uint64_t usec, void* userdata) | |
| { | |
| int r; | |
| struct mapper_async_subtree* subtree = userdata; | |
| sd_event_source_unref(subtree->event_source); | |
| r = sd_bus_call_method_async( | |
| subtree->conn, NULL, "xyz.openbmc_project.ObjectMapper", | |
| "/xyz/openbmc_project/object_mapper", | |
| "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", | |
| async_subtree_getpaths_callback, subtree, "sias", subtree->namespace, 0, | |
| 1, subtree->interface); | |
| if (r < 0) | |
| { | |
| async_subtree_done(r, subtree); | |
| } | |
| return 0; | |
| } | |
| static int async_subtree_getpaths_callback(sd_bus_message* m, void* userdata, | |
| _unused_ sd_bus_error* e) | |
| { | |
| int r; | |
| struct mapper_async_subtree* subtree = userdata; | |
| uint64_t next_retry; | |
| if (subtree->finished) | |
| { | |
| goto exit; | |
| } | |
| r = sd_bus_message_get_errno(m); | |
| if (sd_bus_message_is_method_error( | |
| m, "xyz.openbmc_project.Common.Error.ResourceNotFound")) | |
| { | |
| if (subtree->op == MAPPER_OP_REMOVE) | |
| { | |
| r = 0; | |
| } | |
| else | |
| { | |
| goto exit; | |
| } | |
| } | |
| if ((r == EBUSY || r == ENOBUFS) && subtree->retry < mapper_busy_retries) | |
| { | |
| r = sd_event_now(subtree->loop, CLOCK_MONOTONIC, &next_retry); | |
| if (r < 0) | |
| { | |
| async_subtree_done(r, subtree); | |
| goto exit; | |
| } | |
| next_retry += mapper_busy_delay_interval_usec * (1 << subtree->retry); | |
| r = sd_event_add_time(subtree->loop, &subtree->event_source, | |
| CLOCK_MONOTONIC, next_retry, 0, | |
| async_subtree_timeout_callback, subtree); | |
| ++subtree->retry; | |
| if (r < 0) | |
| { | |
| async_subtree_done(r, subtree); | |
| goto exit; | |
| } | |
| return 0; | |
| } | |
| if (r) | |
| { | |
| async_subtree_done(-r, subtree); | |
| goto exit; | |
| } | |
| if (subtree->op == MAPPER_OP_REMOVE) | |
| { | |
| r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "s"); | |
| if (r < 0) | |
| { | |
| async_subtree_done(r, subtree); | |
| goto exit; | |
| } | |
| r = sd_bus_message_at_end(m, false); | |
| if (r < 0) | |
| { | |
| async_subtree_done(r, subtree); | |
| goto exit; | |
| } | |
| /* For remove, operation is complete when the interface is not present | |
| * we know it is empty if the returned array is empty | |
| */ | |
| if (r) | |
| async_subtree_done(0, subtree); | |
| } | |
| exit: | |
| return 0; | |
| } | |
| static int async_subtree_getpaths(mapper_async_subtree* subtree) | |
| { | |
| int r = 0; | |
| subtree->retry = 0; | |
| subtree->event_source = NULL; | |
| r = sd_bus_call_method_async( | |
| subtree->conn, NULL, "xyz.openbmc_project.ObjectMapper", | |
| "/xyz/openbmc_project/object_mapper", | |
| "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", | |
| async_subtree_getpaths_callback, subtree, "sias", subtree->namespace, 0, | |
| 1, subtree->interface); | |
| if (r < 0) | |
| { | |
| fprintf(stderr, "Error invoking method: %s\n", strerror(-r)); | |
| return r; | |
| } | |
| return 0; | |
| } | |
| static int async_subtree_match_callback(_unused_ sd_bus_message* m, void* t, | |
| _unused_ sd_bus_error* e) | |
| { | |
| int r; | |
| mapper_async_subtree* subtree = t; | |
| if (subtree->finished) | |
| { | |
| return 0; | |
| } | |
| r = async_subtree_getpaths(subtree); | |
| if (r < 0) | |
| { | |
| async_subtree_done(r, subtree); | |
| } | |
| return 0; | |
| } | |
| static void async_subtree_done(int r, mapper_async_subtree* t) | |
| { | |
| if (t->finished) | |
| { | |
| return; | |
| } | |
| t->finished = 1; | |
| sd_bus_slot_unref(t->slot); | |
| if (t->callback) | |
| { | |
| t->callback(r, t->userdata); | |
| } | |
| } | |
| _public_ int mapper_subtree_async(sd_bus* conn, sd_event* loop, char* namespace, | |
| char* interface, void (*callback)(int, void*), | |
| void* userdata, mapper_async_subtree** t, | |
| int op) | |
| { | |
| int r = 0; | |
| mapper_async_subtree* subtree = NULL; | |
| subtree = malloc(sizeof(*subtree)); | |
| if (!subtree) | |
| { | |
| return -ENOMEM; | |
| } | |
| memset(subtree, 0, sizeof(*subtree)); | |
| subtree->conn = conn; | |
| subtree->loop = loop; | |
| subtree->namespace = namespace; | |
| subtree->interface = interface; | |
| subtree->callback = callback; | |
| subtree->userdata = userdata; | |
| subtree->op = op; | |
| if (subtree->op == MAPPER_OP_REMOVE) | |
| { | |
| r = sd_bus_add_match(conn, &subtree->slot, interfaces_removed_match, | |
| async_subtree_match_callback, subtree); | |
| if (r < 0) | |
| { | |
| fprintf(stderr, "Error adding match rule: %s\n", strerror(-r)); | |
| goto unref_slot; | |
| } | |
| } | |
| else | |
| { | |
| /* Operation not supported */ | |
| r = -EINVAL; | |
| goto free_subtree; | |
| } | |
| r = async_subtree_getpaths(subtree); | |
| if (r < 0) | |
| { | |
| fprintf(stderr, "Error calling method: %s\n", strerror(-r)); | |
| goto unref_slot; | |
| } | |
| *t = subtree; | |
| return 0; | |
| unref_slot: | |
| sd_bus_slot_unref(subtree->slot); | |
| free_subtree: | |
| free(subtree); | |
| return r; | |
| } | |
| /* Internal: issues a GetObject call with an explicit interface filter. | |
| * Pass n_ifaces == 0 and iface == NULL for no filter (all services returned). | |
| * On success the caller owns *reply and must sd_bus_message_unref() it. | |
| */ | |
| static int do_get_object(sd_bus* conn, const char* obj, int n_ifaces, | |
| const char* iface, sd_bus_message** reply) | |
| { | |
| sd_bus_message* request = NULL; | |
| int r, retry = 0; | |
| r = sd_bus_message_new_method_call( | |
| conn, &request, "xyz.openbmc_project.ObjectMapper", | |
| "/xyz/openbmc_project/object_mapper", | |
| "xyz.openbmc_project.ObjectMapper", "GetObject"); | |
| if (r < 0) | |
| { | |
| goto exit; | |
| } | |
| r = sd_bus_message_append(request, "s", obj); | |
| if (r < 0) | |
| { | |
| goto exit; | |
| } | |
| r = sd_bus_message_append(request, "as", n_ifaces, iface); | |
| if (r < 0) | |
| { | |
| goto exit; | |
| } | |
| while (true) | |
| { | |
| r = sd_bus_call(conn, request, 0, NULL, reply); | |
| if (r == -EBUSY || r == -ENOBUFS) | |
| { | |
| if (retry >= mapper_busy_retries) | |
| break; | |
| usleep(mapper_busy_delay_interval_usec * (1 << retry)); | |
| ++retry; | |
| continue; | |
| } | |
| break; | |
| } | |
| exit: | |
| sd_bus_message_unref(request); | |
| return r; | |
| } | |
| _public_ int mapper_get_object(sd_bus* conn, const char* obj, | |
| sd_bus_message** reply) | |
| { | |
| return do_get_object(conn, obj, 0, NULL, reply); | |
| } | |
| static int read_first_service(sd_bus_message* reply, char** service) | |
| { | |
| int r; | |
| const char* tmp; | |
| r = sd_bus_message_enter_container(reply, 0, NULL); | |
| if (r < 0) | |
| { | |
| return r; | |
| } | |
| r = sd_bus_message_enter_container(reply, 0, NULL); | |
| if (r < 0) | |
| { | |
| return r; | |
| } | |
| r = sd_bus_message_read(reply, "s", &tmp); | |
| if (r < 0) | |
| { | |
| return r; | |
| } | |
| *service = strdup(tmp); | |
| if (*service == NULL) | |
| { | |
| r = -ENOMEM; | |
| } | |
| return r; | |
| } | |
| _public_ int mapper_get_service(sd_bus* conn, const char* obj, char** service) | |
| { | |
| sd_bus_message* reply = NULL; | |
| int r; | |
| r = mapper_get_object(conn, obj, &reply); | |
| if (r < 0) | |
| { | |
| goto exit; | |
| } | |
| r = read_first_service(reply, service); | |
| exit: | |
| sd_bus_message_unref(reply); | |
| return r; | |
| } | |
| /* | |
| * Like mapper_get_service but passes a required interface to GetObject so that | |
| * only services implementing that interface are considered. This is useful when | |
| * multiple services are registered for the same path and the caller knows which | |
| * interface it needs. | |
| */ | |
| _public_ int mapper_get_service_with_iface( | |
| sd_bus* conn, const char* obj, const char* interface, char** service) | |
| { | |
| sd_bus_message* reply = NULL; | |
| int r; | |
| r = do_get_object(conn, obj, 1, interface, &reply); | |
| if (r < 0) | |
| { | |
| goto exit; | |
| } | |
| r = read_first_service(reply, service); | |
| exit: | |
| sd_bus_message_unref(reply); | |
| return r; | |
| } |