/** * Copyright © 2016 IBM Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "config.h" #include "mapper.h" #include #include #include #include #include #include #include #include #include #include #include "internal.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, MAPPER_BUSNAME, MAPPER_PATH, MAPPER_INTERFACE, "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, MAPPER_BUSNAME, MAPPER_PATH, MAPPER_INTERFACE, "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, MAPPER_BUSNAME, MAPPER_PATH, MAPPER_INTERFACE, "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, MAPPER_BUSNAME, MAPPER_PATH, MAPPER_INTERFACE, "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; } _public_ int mapper_get_object(sd_bus* conn, const char* obj, sd_bus_message** reply) { sd_bus_message* request = NULL; int r, retry = 0; r = sd_bus_message_new_method_call(conn, &request, MAPPER_BUSNAME, MAPPER_PATH, MAPPER_INTERFACE, "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", 0, NULL); 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; } if (r < 0) goto exit; exit: sd_bus_message_unref(request); return r; } _public_ int mapper_get_service(sd_bus* conn, const char* obj, char** service) { sd_bus_message* reply = NULL; const char* tmp; int r; r = mapper_get_object(conn, obj, &reply); if (r < 0) goto exit; r = sd_bus_message_enter_container(reply, 0, NULL); if (r < 0) goto exit; r = sd_bus_message_enter_container(reply, 0, NULL); if (r < 0) goto exit; r = sd_bus_message_read(reply, "s", &tmp); if (r < 0) goto exit; *service = strdup(tmp); exit: sd_bus_message_unref(reply); return r; }