| /** |
| * 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 <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; |
| char *intf = NULL; |
| 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; |
| } |
| |
| sd_bus_message_read(m, "as", 1, &intf); |
| if (subtree->op == MAPPER_OP_REMOVE) { |
| /* For remove, operation is complete when the interface is not present */ |
| if (intf == NULL) |
| 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; |
| } |