|  | /** | 
|  | * 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 <stdbool.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <stdio.h> | 
|  | #include <errno.h> | 
|  | #include <unistd.h> | 
|  | #include <sys/timerfd.h> | 
|  | #include <systemd/sd-bus.h> | 
|  | #include <systemd/sd-event.h> | 
|  | #include "mapper.h" | 
|  |  | 
|  | 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; | 
|  | int 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 *); | 
|  |  | 
|  | static int sarraylen(char *array[]) | 
|  | { | 
|  | int count = 0; | 
|  | char **p = array; | 
|  |  | 
|  | while (*p != NULL) | 
|  | { | 
|  | ++count; | 
|  | ++p; | 
|  | } | 
|  |  | 
|  | return count; | 
|  | } | 
|  |  | 
|  | static void sarrayfree(char *array[]) | 
|  | { | 
|  | char **p = array; | 
|  | while (*p != NULL) | 
|  | { | 
|  | free(*p); | 
|  | ++p; | 
|  | } | 
|  | free(array); | 
|  | } | 
|  |  | 
|  | static char **sarraydup(char *array[]) | 
|  | { | 
|  | int count = sarraylen(array); | 
|  | int i; | 
|  | char **ret = NULL; | 
|  |  | 
|  | ret = malloc(sizeof(*ret) * count); | 
|  | 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(sd_event_source *s, 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, | 
|  | sd_bus_error *e) | 
|  | { | 
|  | int i, r; | 
|  | struct async_wait_callback_data *data = userdata; | 
|  | mapper_async_wait *wait = data->wait; | 
|  | uint64_t now; | 
|  |  | 
|  | if (wait->finished) | 
|  | goto exit; | 
|  |  | 
|  | r = sd_bus_message_get_errno(m); | 
|  | if (r == ENOENT) | 
|  | goto exit; | 
|  |  | 
|  | if (r == EBUSY && data->retry < mapper_busy_retries) | 
|  | { | 
|  | r = sd_event_now(wait->loop, CLOCK_MONOTONIC, &now); | 
|  | if (r < 0) | 
|  | { | 
|  | async_wait_done(r, wait); | 
|  | goto exit; | 
|  | } | 
|  |  | 
|  | ++data->retry; | 
|  | r = sd_event_add_time(wait->loop, &data->event_source, CLOCK_MONOTONIC, | 
|  | now + mapper_busy_delay_interval_usec, 0, | 
|  | async_wait_timeout_callback, data); | 
|  | 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) | 
|  | { | 
|  | int i, 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(sd_bus_message *m, void *w, | 
|  | 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) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | if (w->finished) | 
|  | return 1; | 
|  |  | 
|  | for (i = 0; i < w->count; ++i) | 
|  | if (!w->status[i]) | 
|  | return 0; | 
|  |  | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | void mapper_wait_async_free(mapper_async_wait *w) | 
|  | { | 
|  | free(w->status); | 
|  | sarrayfree(w->objs); | 
|  | free(w); | 
|  | } | 
|  |  | 
|  | 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) | 
|  | return 0; | 
|  |  | 
|  | 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(sd_event_source *s, 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, | 
|  | sd_bus_error *e) | 
|  | { | 
|  | int r; | 
|  | struct mapper_async_subtree *subtree = userdata; | 
|  | uint64_t now; | 
|  |  | 
|  | if (subtree->finished) | 
|  | goto exit; | 
|  |  | 
|  | r = sd_bus_message_get_errno(m); | 
|  |  | 
|  | if (r == ENOENT) | 
|  | { | 
|  | if (subtree->op == MAPPER_OP_REMOVE) | 
|  | r = 0; | 
|  | else | 
|  | goto exit; | 
|  | } | 
|  |  | 
|  | if (r == EBUSY && subtree->retry < mapper_busy_retries) | 
|  | { | 
|  | r = sd_event_now(subtree->loop, CLOCK_MONOTONIC, &now); | 
|  | if (r < 0) | 
|  | { | 
|  | async_subtree_done(r, subtree); | 
|  | goto exit; | 
|  | } | 
|  |  | 
|  | ++subtree->retry; | 
|  | r = sd_event_add_time(subtree->loop, &subtree->event_source, | 
|  | CLOCK_MONOTONIC, | 
|  | now + mapper_busy_delay_interval_usec, 0, | 
|  | async_subtree_timeout_callback, subtree); | 
|  | 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(sd_bus_message *m, void *t, | 
|  | 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); | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | int mapper_get_object(sd_bus *conn, const char *obj, sd_bus_message **reply) | 
|  | { | 
|  | sd_bus_error error = SD_BUS_ERROR_NULL; | 
|  | 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 (retry < mapper_busy_retries) | 
|  | { | 
|  | sd_bus_error_free(&error); | 
|  | r = sd_bus_call(conn, request, 0, &error, reply); | 
|  | if (r < 0 && sd_bus_error_get_errno(&error) == EBUSY) | 
|  | { | 
|  | ++retry; | 
|  |  | 
|  | if (retry != mapper_busy_retries) | 
|  | usleep(mapper_busy_delay_interval_usec); | 
|  | continue; | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (r < 0) | 
|  | goto exit; | 
|  |  | 
|  | exit: | 
|  | sd_bus_error_free(&error); | 
|  | sd_bus_message_unref(request); | 
|  |  | 
|  | return r; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } |