diff options
author | Doohwan Kim <dh8210.kim@samsung.com> | 2015-02-26 17:39:16 +0900 |
---|---|---|
committer | Doohwan Kim <dh8210.kim@samsung.com> | 2015-02-26 17:39:45 +0900 |
commit | da07ee69267b4557028357eae802d2a7a57c34e7 (patch) | |
tree | 02a5d7590fc35cd3ad08aa8894ca5f32947a1da1 /src/common | |
parent | 34c41c0eecd122a3dd92b30f30267ea4d951343a (diff) | |
download | murphy-tizen_3.0.2015.q1_common.tar.gz murphy-tizen_3.0.2015.q1_common.tar.bz2 murphy-tizen_3.0.2015.q1_common.zip |
Initialize Tizen 3.0submit/tizen_3.0.2015.q1_common/20150320.000000submit/tizen/20150313.025220accepted/tizen/wearable/20150313.084641accepted/tizen/tv/20150313.084515accepted/tizen/mobile/20150313.084815accepted/tizen/common/20150313.083743accepted/tizen/3.0.2015.q1/common/20150323.084221tizen_3.0.2015.q2_commontizen_3.0.2015.q1_commonaccepted/tizen_3.0.2015.q1_common
Signed-off-by: Doohwan Kim <dh8210.kim@samsung.com>
Change-Id: I2ad7e23e121506673a724f512fea429489928451
Diffstat (limited to 'src/common')
108 files changed, 48989 insertions, 0 deletions
diff --git a/src/common/Makefile b/src/common/Makefile new file mode 100644 index 0000000..2c0a593 --- /dev/null +++ b/src/common/Makefile @@ -0,0 +1,7 @@ +ifneq ($(strip $(MAKECMDGOALS)),) +%: + $(MAKE) -C .. $(MAKECMDGOALS) +else +all: + $(MAKE) -C .. all +endif diff --git a/src/common/dbus-error.h b/src/common/dbus-error.h new file mode 100644 index 0000000..1704cec --- /dev/null +++ b/src/common/dbus-error.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2012, 2013, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_DBUS_ERROR_H__ +#define __MURPHY_DBUS_ERROR_H__ + +#define __ERR(error) "org.freedesktop.DBus.Error."#error + +#define MRP_DBUS_ERROR_FAILED __ERR(Failed) +#define MRP_DBUS_ERROR_NO_MEMORY __ERR(NoMemory) +#define MRP_DBUS_ERROR_SERVICE_UNKNOWN __ERR(ServiceUnknown) +#define MRP_DBUS_ERROR_NAME_HAS_NO_OWNER __ERR(NameHasNoOwner) +#define MRP_DBUS_ERROR_NO_REPLY __ERR(NoReply) +#define MRP_DBUS_ERROR_IO_ERROR __ERR(IOError) +#define MRP_DBUS_ERROR_BAD_ADDRESS __ERR(BadAddress) +#define MRP_DBUS_ERROR_NOT_SUPPORTED __ERR(NotSupported) +#define MRP_DBUS_ERROR_LIMITS_EXCEEDED __ERR(LimitsExceeded) +#define MRP_DBUS_ERROR_ACCESS_DENIED __ERR(AccessDenied) +#define MRP_DBUS_ERROR_AUTH_FAILED __ERR(AuthFailed) +#define MRP_DBUS_ERROR_NO_SERVER __ERR(NoServer) +#define MRP_DBUS_ERROR_TIMEOUT __ERR(Timeout) +#define MRP_DBUS_ERROR_NO_NETWORK __ERR(NoNetwork) +#define MRP_DBUS_ERROR_ADDRESS_IN_USE __ERR(AddressInUse) +#define MRP_DBUS_ERROR_DISCONNECTED __ERR(Disconnected) +#define MRP_DBUS_ERROR_INVALID_ARGS __ERR(InvalidArgs) +#define MRP_DBUS_ERROR_FILE_NOT_FOUND __ERR(FileNotFound) +#define MRP_DBUS_ERROR_FILE_EXISTS __ERR(FileExists) +#define MRP_DBUS_ERROR_UNKNOWN_METHOD __ERR(UnknownMethod) +#define MRP_DBUS_ERROR_UNKNOWN_OBJECT __ERR(UnknownObject) +#define MRP_DBUS_ERROR_UNKNOWN_INTERFACE __ERR(UnknownInterface) +#define MRP_DBUS_ERROR_UNKNOWN_PROPERTY __ERR(UnknownProperty) +#define MRP_DBUS_ERROR_PROPERTY_READ_ONLY __ERR(PropertyReadOnly) +#define MRP_DBUS_ERROR_TIMED_OUT __ERR(TimedOut) +#define MRP_DBUS_ERROR_MATCH_RULE_NOT_FOUND __ERR(MatchRuleNotFound) +#define MRP_DBUS_ERROR_MATCH_RULE_INVALID __ERR(MatchRuleInvalid) +#define MRP_DBUS_ERROR_SPAWN_EXEC_FAILED __ERR(Spawn.ExecFailed) +#define MRP_DBUS_ERROR_SPAWN_FORK_FAILED __ERR(Spawn.ForkFailed) +#define MRP_DBUS_ERROR_SPAWN_CHILD_EXITED __ERR(Spawn.ChildExited) +#define MRP_DBUS_ERROR_SPAWN_CHILD_SIGNALED __ERR(Spawn.ChildSignaled) +#define MRP_DBUS_ERROR_SPAWN_FAILED __ERR(Spawn.Failed) +#define MRP_DBUS_ERROR_SPAWN_SETUP_FAILED __ERR(Spawn.FailedToSetup) +#define MRP_DBUS_ERROR_SPAWN_CONFIG_INVALID __ERR(Spawn.ConfigInvalid) +#define MRP_DBUS_ERROR_SPAWN_SERVICE_INVALID __ERR(Spawn.ServiceNotValid) +#define MRP_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND __ERR(Spawn.ServiceNotFound) +#define MRP_DBUS_ERROR_SPAWN_PERMISSIONS_INVALID __ERR(Spawn.PermissionsInvalid) +#define MRP_DBUS_ERROR_SPAWN_FILE_INVALID __ERR(Spawn.FileInvalid) +#define MRP_DBUS_ERROR_SPAWN_NO_MEMORY __ERR(Spawn.NoMemory) +#define MRP_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN __ERR(UnixProcessIdUnknown) +#define MRP_DBUS_ERROR_INVALID_SIGNATURE __ERR(InvalidSignature) +#define MRP_DBUS_ERROR_INVALID_FILE_CONTENT __ERR(InvalidFileContent) +#define MRP_DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN __ERR(AdtAuditDataUnknown) +#define MRP_DBUS_ERROR_OBJECT_PATH_IN_USE __ERR(ObjectPathInUse) +#define MRP_DBUS_ERROR_INCONSISTENT_MESSAGE __ERR(InconsistentMessage) +#define MRP_DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN __ERR(SELinuxSecurityContextUnknown) + +#endif /* __MURPHY_MRP_DBUS_ERROR_H__ */ diff --git a/src/common/dbus-libdbus-glue.c b/src/common/dbus-libdbus-glue.c new file mode 100644 index 0000000..d1bb093 --- /dev/null +++ b/src/common/dbus-libdbus-glue.c @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <dbus/dbus.h> + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/log.h> +#include <murphy/common/list.h> +#include <murphy/common/mainloop.h> + +typedef struct dbus_glue_s dbus_glue_t; + +typedef struct { + dbus_glue_t *glue; + mrp_io_watch_t *mw; + DBusWatch *dw; + mrp_list_hook_t hook; +} watch_t; + + +typedef struct { + dbus_glue_t *glue; + mrp_timer_t *mt; + DBusTimeout *dt; + mrp_list_hook_t hook; +} timeout_t; + + +struct dbus_glue_s { + DBusConnection *conn; + mrp_mainloop_t *ml; + mrp_list_hook_t watches; + mrp_list_hook_t timers; + mrp_deferred_t *pump; +}; + + +static dbus_int32_t data_slot = -1; + +static void dispatch_watch(mrp_io_watch_t *mw, int fd, mrp_io_event_t events, + void *user_data) +{ + watch_t *watch = (watch_t *)user_data; + DBusConnection *conn = watch->glue->conn; + unsigned int mask = 0; + + MRP_UNUSED(mw); + MRP_UNUSED(fd); + + if (events & MRP_IO_EVENT_IN) + mask |= DBUS_WATCH_READABLE; + if (events & MRP_IO_EVENT_OUT) + mask |= DBUS_WATCH_WRITABLE; + if (events & MRP_IO_EVENT_HUP) + mask |= DBUS_WATCH_HANGUP; + if (events & MRP_IO_EVENT_ERR) + mask |= DBUS_WATCH_ERROR; + + dbus_connection_ref(conn); + dbus_watch_handle(watch->dw, mask); + dbus_connection_unref(conn); +} + + +static void watch_freed_cb(void *data) +{ + watch_t *watch = (watch_t *)data; + + if (watch != NULL) { + mrp_list_delete(&watch->hook); + mrp_del_io_watch(watch->mw); + mrp_free(watch); + } +} + + +static dbus_bool_t add_watch(DBusWatch *dw, void *data) +{ + dbus_glue_t *glue = (dbus_glue_t *)data; + watch_t *watch; + mrp_io_watch_t *mw; + mrp_io_event_t mask; + int fd; + unsigned int flags; + + mrp_debug("adding D-BUS watch %p (%s)", dw, + dbus_watch_get_enabled(dw) ? "enabled" : "disabled"); + + if (!dbus_watch_get_enabled(dw)) + return TRUE; + + fd = dbus_watch_get_unix_fd(dw); + flags = dbus_watch_get_flags(dw); + mask = MRP_IO_EVENT_HUP | MRP_IO_EVENT_ERR; + + if (flags & DBUS_WATCH_READABLE) + mask |= MRP_IO_EVENT_IN; + if (flags & DBUS_WATCH_WRITABLE) + mask |= MRP_IO_EVENT_OUT; + + mrp_debug("event mask for fd %d: %s%s", fd, + mask & MRP_IO_EVENT_IN ? "read" : "", + mask & MRP_IO_EVENT_OUT ? "write" : ""); + + if ((watch = mrp_allocz(sizeof(*watch))) != NULL) { + mrp_list_init(&watch->hook); + mw = mrp_add_io_watch(glue->ml, fd, mask, dispatch_watch, watch); + + if (mw != NULL) { + watch->glue = glue; + watch->mw = mw; + watch->dw = dw; + dbus_watch_set_data(dw, watch, watch_freed_cb); + mrp_list_append(&glue->watches, &watch->hook); + + return TRUE; + } + else + mrp_free(watch); + } + + return FALSE; +} + + +static void del_watch(DBusWatch *dw, void *data) +{ + watch_t *watch = (watch_t *)dbus_watch_get_data(dw); + + MRP_UNUSED(data); + + mrp_debug("deleting D-BUS watch %p...", dw); + + if (watch != NULL) { + mrp_del_io_watch(watch->mw); + watch->mw = NULL; + } +} + + +static void toggle_watch(DBusWatch *dw, void *data) +{ + mrp_debug("Toggling D-BUS watch %p...", dw); + + if (dbus_watch_get_enabled(dw)) + add_watch(dw, data); + else + del_watch(dw, data); +} + + +static void dispatch_timeout(mrp_timer_t *mt, void *user_data) +{ + timeout_t *timer = (timeout_t *)user_data; + + MRP_UNUSED(mt); + + mrp_debug("dispatching D-BUS timeout %p...", timer->dt); + + dbus_timeout_handle(timer->dt); +} + + +static void timeout_freed_cb(void *data) +{ + timeout_t *timer = (timeout_t *)data; + + if (timer != NULL) { + mrp_list_delete(&timer->hook); + mrp_del_timer(timer->mt); + + mrp_free(timer); + } +} + + +static dbus_bool_t add_timeout(DBusTimeout *dt, void *data) +{ + dbus_glue_t *glue = (dbus_glue_t *)data; + timeout_t *timer; + mrp_timer_t *mt; + unsigned int msecs; + + mrp_debug("adding D-BUS timeout %p...", dt); + + if ((timer = mrp_allocz(sizeof(*timer))) != NULL) { + mrp_list_init(&timer->hook); + msecs = dbus_timeout_get_interval(dt); + mt = mrp_add_timer(glue->ml, msecs, dispatch_timeout, timer); + + if (mt != NULL) { + timer->glue = glue; + timer->mt = mt; + timer->dt = dt; + dbus_timeout_set_data(dt, timer, timeout_freed_cb); + mrp_list_append(&glue->timers, &timer->hook); + + return TRUE; + } + else + mrp_free(timer); + } + + return FALSE; +} + + +static void del_timeout(DBusTimeout *dt, void *data) +{ + timeout_t *timer = (timeout_t *)dbus_timeout_get_data(dt); + + MRP_UNUSED(data); + + mrp_debug("deleting D-BUS timeout %p...", dt); + + if (timer != NULL) { + mrp_del_timer(timer->mt); + timer->mt = NULL; + } +} + + +static void toggle_timeout(DBusTimeout *dt, void *data) +{ + mrp_debug("toggling D-BUS timeout %p...", dt); + + if (dbus_timeout_get_enabled(dt)) + add_timeout(dt, data); + else + del_timeout(dt, data); +} + + +static void wakeup_mainloop(void *data) +{ + dbus_glue_t *glue = (dbus_glue_t *)data; + + mrp_debug("waking up mainloop..."); + + mrp_enable_deferred(glue->pump); +} + + +static void glue_free_cb(void *data) +{ + dbus_glue_t *glue = (dbus_glue_t *)data; + mrp_list_hook_t *p, *n; + watch_t *watch; + timeout_t *timer; + + mrp_list_foreach(&glue->watches, p, n) { + watch = mrp_list_entry(p, typeof(*watch), hook); + + mrp_list_delete(&watch->hook); + mrp_del_io_watch(watch->mw); + + mrp_free(watch); + } + + mrp_list_foreach(&glue->timers, p, n) { + timer = mrp_list_entry(p, typeof(*timer), hook); + + mrp_list_delete(&timer->hook); + mrp_del_timer(timer->mt); + + mrp_free(timer); + } + + mrp_free(glue); +} + + +static void pump_cb(mrp_deferred_t *d, void *user_data) +{ + dbus_glue_t *glue = (dbus_glue_t *)user_data; + + mrp_debug("dispatching dbus connection %p...", glue->conn); + + if (dbus_connection_dispatch(glue->conn) == DBUS_DISPATCH_COMPLETE) + mrp_disable_deferred(d); +} + + +static void dispatch_status_cb(DBusConnection *conn, DBusDispatchStatus status, + void *user_data) +{ + dbus_glue_t *glue = (dbus_glue_t *)user_data; + + MRP_UNUSED(conn); + + switch (status) { + case DBUS_DISPATCH_COMPLETE: + mrp_debug("dispatching status for %p: complete", conn); + mrp_disable_deferred(glue->pump); + break; + + case DBUS_DISPATCH_DATA_REMAINS: + case DBUS_DISPATCH_NEED_MEMORY: + default: + mrp_debug("dispatching status for %p: not complete yet", conn); + mrp_enable_deferred(glue->pump); + break; + } +} + + +int mrp_dbus_setup_connection(mrp_mainloop_t *ml, DBusConnection *conn) +{ + dbus_glue_t *glue; + + if (!dbus_connection_allocate_data_slot(&data_slot)) + return FALSE; + + if (dbus_connection_get_data(conn, data_slot) != NULL) + return FALSE; + + if ((glue = mrp_allocz(sizeof(*glue))) != NULL) { + mrp_list_init(&glue->watches); + mrp_list_init(&glue->timers); + glue->pump = mrp_add_deferred(ml, pump_cb, glue); + + if (glue->pump == NULL) { + mrp_free(glue); + return FALSE; + } + + glue->ml = ml; + glue->conn = conn; + } + else + return FALSE; + + if (!dbus_connection_set_data(conn, data_slot, glue, glue_free_cb)) + return FALSE; + + dbus_connection_set_dispatch_status_function(conn, dispatch_status_cb, + glue, NULL); + + dbus_connection_set_wakeup_main_function(conn, wakeup_mainloop, + glue, NULL); + + return + dbus_connection_set_watch_functions(conn, add_watch, del_watch, + toggle_watch, glue, NULL) && + dbus_connection_set_timeout_functions(conn, add_timeout, del_timeout, + toggle_timeout, glue, NULL); +} diff --git a/src/common/dbus-libdbus-transport.c b/src/common/dbus-libdbus-transport.c new file mode 100644 index 0000000..e19dac8 --- /dev/null +++ b/src/common/dbus-libdbus-transport.c @@ -0,0 +1,1745 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/list.h> +#include <murphy/common/log.h> +#include <murphy/common/msg.h> +#include <murphy/common/transport.h> +#include <murphy/common/dbus-libdbus.h> +#include <murphy/common/dbus-transport.h> + +#define DBUS "dbus" +#define DBUSL 4 + +#define TRANSPORT_PATH "/murphy/transport" +#define TRANSPORT_INTERFACE "Murphy.Transport" +#define TRANSPORT_MESSAGE "DeliverMessage" +#define TRANSPORT_DATA "DeliverData" +#define TRANSPORT_RAW "DeliverRaw" +#define TRANSPORT_METHOD "DeliverMessage" + +#define ANY_ADDRESS "any" + +typedef struct { + MRP_TRANSPORT_PUBLIC_FIELDS; /* common transport fields */ + mrp_dbus_t *dbus; /* D-BUS connection */ + int bound : 1; /* whether bound to an address */ + int peer_resolved : 1; /* connected and peer name known */ + mrp_dbusaddr_t local; /* address we're bound to */ + mrp_dbusaddr_t remote; /* address we're connected to */ +} dbus_t; + + +static uint32_t nauto; /* for autobinding */ + + +static int dbus_msg_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data); +static int dbus_data_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data); +static int dbus_raw_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data); + +static void peer_state_cb(mrp_dbus_t *dbus, const char *name, int up, + const char *owner, void *user_data); + +static mrp_dbus_msg_t *msg_encode(mrp_dbus_t *dbus, const char *destination, + const char *path, const char *interface, + const char *member, const char *sender_id, + mrp_msg_t *msg); +static mrp_msg_t *msg_decode(mrp_dbus_msg_t *msg, const char **sender_id); + +static mrp_dbus_msg_t *data_encode(mrp_dbus_t *dbus, const char *destination, + const char *path, const char *interface, + const char *member, const char *sender_id, + void *data, uint16_t tag); +static void *data_decode(mrp_dbus_msg_t *msg, uint16_t *tag, + const char **sender_id); + +static mrp_dbus_msg_t *raw_encode(mrp_dbus_t *dbus, const char *destination, + const char *path, const char *interface, + const char *member, const char *sender_id, + void *data, size_t size); +static void *raw_decode(mrp_dbus_msg_t *msg, size_t *sizep, + const char **sender_id); + + +static socklen_t parse_address(const char *str, mrp_dbusaddr_t *addr, + socklen_t size) +{ + const char *p, *e; + char *q; + size_t l, n; + + if (size < sizeof(*addr)) { + errno = EINVAL; + return FALSE; + } + + if (strncmp(str, DBUS":", DBUSL + 1)) + return 0; + else + str += DBUSL + 1; + + /* + * The format of the address is + * dbus:[bus-address]@address/path + * eg. + * dbus:[session]@:1.33/client1, or + * dbus:[unix:abstract=/tmp/dbus-Xx2Kpi...a572]@:1.33/client1 + */ + + p = str; + q = addr->db_fqa; + l = sizeof(addr->db_fqa) - 1; + + /* get bus address */ + if (*p != '[') { + errno = EINVAL; + return 0; + } + else + p++; + + e = strchr(p, ']'); + + if (e == NULL) { + errno = EINVAL; + return 0; + } + + n = e - p; + if (n >= l) { + errno = ENAMETOOLONG; + return 0; + } + + /* save bus address */ + strncpy(q, p, n); + q[n] = '\0'; + addr->db_bus = q; + + q += n + 1; + l -= n + 1; + p = e + 1; + + /* get (local or remote) address on bus */ + if (*p != '@') + addr->db_addr = ANY_ADDRESS; + else { + p++; + e = strchr(p, '/'); + + if (e == NULL) { + errno = EINVAL; + return 0; + } + + n = e - p; + if (n >= l) { + errno = ENAMETOOLONG; + return 0; + } + + /* save address on bus */ + strncpy(q, p, n); + q[n] = '\0'; + addr->db_addr = q; + + q += n + 1; + l -= n + 1; + p = e; + } + + /* get object path */ + if (*p != '/') { + errno = EINVAL; + return 0; + } + + n = snprintf(q, l, "%s%s", TRANSPORT_PATH, p); + if (n >= l) { + errno = ENAMETOOLONG; + return 0; + } + + addr->db_path = q; + addr->db_family = MRP_AF_DBUS; + + return sizeof(*addr); +} + + +static mrp_dbusaddr_t *copy_address(mrp_dbusaddr_t *dst, mrp_dbusaddr_t *src) +{ + char *p, *q; + size_t l, n; + + dst->db_family = src->db_family; + + /* copy bus address */ + p = src->db_bus; + q = dst->db_fqa; + l = sizeof(dst->db_fqa) - 1; + + n = strlen(p); + if (l < n + 1) { + errno = EOVERFLOW; + return NULL; + } + + dst->db_bus = q; + memcpy(q, p, n + 1); + q += n + 1; + l -= n + 1; + + /* copy address */ + p = src->db_addr; + n = strlen(p); + if (l < n + 1) { + errno = EOVERFLOW; + return NULL; + } + + dst->db_addr = q; + memcpy(q, p, n + 1); + q += n + 1; + l -= n + 1; + + /* copy path */ + p = src->db_path; + n = strlen(p); + if (l < n + 1) { + errno = EOVERFLOW; + return NULL; + } + + dst->db_path = q; + memcpy(q, p, n + 1); + q += n + 1; + l -= n + 1; + + return dst; +} + + +static inline int check_address(mrp_sockaddr_t *addr, socklen_t addrlen) +{ + mrp_dbusaddr_t *a = (mrp_dbusaddr_t *)addr; + + return (a && a->db_family == MRP_AF_DBUS && addrlen == sizeof(*a)); +} + + +static size_t peer_address(mrp_sockaddr_t *addrp, const char *sender, + const char *path) +{ + mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp; + const char *p; + char *q; + int l, n; + + q = addr->db_fqa; + l = sizeof(addr->db_fqa) - 1; + p = ANY_ADDRESS; + n = 3; + + addr->db_bus = q; + memcpy(q, p, n + 1); + q += n + 1; + l -= n + 1; + + addr->db_addr = q; + p = sender; + n = strlen(sender); + + if (l < n + 1) + return 0; + + memcpy(q, p, n + 1); + q += n + 1; + l -= n + 1; + + addr->db_path = q; + p = path; + n = strlen(p); + + if (l < n + 1) + return 0; + + memcpy(q, p, n + 1); + + return sizeof(addrp); +} + + +static socklen_t dbus_resolve(const char *str, mrp_sockaddr_t *addr, + socklen_t size, const char **typep) +{ + socklen_t len; + + len = parse_address(str, (mrp_dbusaddr_t *)addr, size); + + if (len > 0) { + if (typep != NULL) + *typep = DBUS; + } + + return len; +} + + +static int dbus_open(mrp_transport_t *mt) +{ + MRP_UNUSED(mt); + + return TRUE; +} + + +static int dbus_createfrom(mrp_transport_t *mt, void *conn) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbus_t *dbus = (mrp_dbus_t *)conn; + + t->dbus = mrp_dbus_ref(dbus); + + if (t->dbus != NULL) + return TRUE; + else + return FALSE; +} + + +static int dbus_bind(mrp_transport_t *mt, mrp_sockaddr_t *addrp, + socklen_t addrlen) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbus_t *dbus = NULL; + mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp; + int (*cb)(mrp_dbus_t *, mrp_dbus_msg_t *, void *); + const char *method; + + if (t->bound) { + errno = EINVAL; + goto fail; + } + + if (!check_address(addrp, addrlen)) { + errno = EINVAL; + goto fail; + } + + if (t->dbus == NULL) { + dbus = mrp_dbus_connect(t->ml, addr->db_bus, NULL); + + if (dbus == NULL) { + errno = ECONNRESET; + goto fail; + } + else { + t->dbus = dbus; + + if (addr->db_addr != NULL && strcmp(addr->db_addr, ANY_ADDRESS)) { + if (!mrp_dbus_acquire_name(t->dbus, addr->db_addr, NULL)) { + errno = EADDRINUSE; /* XXX TODO, should check error... */ + goto fail; + } + } + } + } + else { + /* XXX TODO: should check given address against address of the bus */ + } + + copy_address(&t->local, addr); + + switch (t->mode) { + case MRP_TRANSPORT_MODE_DATA: + method = TRANSPORT_DATA; + cb = dbus_data_cb; + break; + case MRP_TRANSPORT_MODE_RAW: + method = TRANSPORT_RAW; + cb = dbus_raw_cb; + break; + case MRP_TRANSPORT_MODE_MSG: + method = TRANSPORT_MESSAGE; + cb = dbus_msg_cb; + break; + default: + errno = EPROTOTYPE; + goto fail; + } + + if (!mrp_dbus_export_method(t->dbus, addr->db_path, TRANSPORT_INTERFACE, + method, cb, t)) { + errno = EIO; + goto fail; + } + else { + t->bound = TRUE; + return TRUE; + } + + fail: + if (dbus != NULL) { + mrp_dbus_unref(dbus); + t->dbus = NULL; + } + + return FALSE; +} + + +static int dbus_autobind(mrp_transport_t *mt, mrp_sockaddr_t *addrp) +{ + mrp_dbusaddr_t *a = (mrp_dbusaddr_t *)addrp; + char astr[MRP_SOCKADDR_SIZE]; + mrp_sockaddr_t addr; + socklen_t alen; + + snprintf(astr, sizeof(astr), "dbus:[%s]/auto/%u", a->db_bus, nauto++); + + alen = dbus_resolve(astr, &addr, sizeof(addr), NULL); + + if (alen > 0) + return dbus_bind(mt, &addr, alen); + else + return FALSE; +} + + +static void dbus_close(mrp_transport_t *mt) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbusaddr_t *addr; + const char *method; + int (*cb)(mrp_dbus_t *, mrp_dbus_msg_t *, void *); + + if (t->bound) { + switch (t->mode) { + case MRP_TRANSPORT_MODE_DATA: + method = TRANSPORT_DATA; + cb = dbus_data_cb; + break; + case MRP_TRANSPORT_MODE_RAW: + method = TRANSPORT_RAW; + cb = dbus_raw_cb; + break; + default: + case MRP_TRANSPORT_MODE_MSG: + method = TRANSPORT_MESSAGE; + cb = dbus_msg_cb; + } + + addr = (mrp_dbusaddr_t *)&t->local; + mrp_dbus_remove_method(t->dbus, addr->db_path, TRANSPORT_INTERFACE, + method, cb, t); + } + + if (t->connected && t->remote.db_addr != NULL) + mrp_dbus_forget_name(t->dbus, t->remote.db_addr, peer_state_cb, t); + + mrp_dbus_unref(t->dbus); + t->dbus = NULL; +} + + +static int dbus_msg_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *dmsg, void *user_data) +{ + mrp_transport_t *mt = (mrp_transport_t *)user_data; + dbus_t *t = (dbus_t *)mt; + mrp_sockaddr_t addr; + socklen_t alen; + const char *sender, *sender_path; + mrp_msg_t *msg; + + MRP_UNUSED(dbus); + + msg = msg_decode(dmsg, &sender_path); + + if (msg != NULL) { + sender = mrp_dbus_msg_sender(dmsg); + + if (mt->connected) { + if (!t->peer_resolved || !strcmp(t->remote.db_addr, sender)) + MRP_TRANSPORT_BUSY(mt, { + mt->evt.recvmsg(mt, msg, mt->user_data); + }); + } + else { + peer_address(&addr, sender, sender_path); + alen = sizeof(addr); + + MRP_TRANSPORT_BUSY(mt, { + mt->evt.recvmsgfrom(mt, msg, &addr, alen, mt->user_data); + }); + } + + mrp_msg_unref(msg); + + mt->check_destroy(mt); + } + else { + mrp_log_error("Failed to decode message."); + } + + return TRUE; +} + + +static int dbus_data_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *dmsg, void *user_data) +{ + mrp_transport_t *mt = (mrp_transport_t *)user_data; + dbus_t *t = (dbus_t *)mt; + mrp_sockaddr_t addr; + socklen_t alen; + const char *sender, *sender_path; + uint16_t tag; + void *decoded; + + MRP_UNUSED(dbus); + + decoded = data_decode(dmsg, &tag, &sender_path); + + if (decoded != NULL) { + sender = mrp_dbus_msg_sender(dmsg); + + if (mt->connected) { + if (!t->peer_resolved || !strcmp(t->remote.db_addr, sender)) + MRP_TRANSPORT_BUSY(mt, { + mt->evt.recvdata(mt, decoded, tag, mt->user_data); + }); + } + else { + peer_address(&addr, sender, sender_path); + alen = sizeof(addr); + + MRP_TRANSPORT_BUSY(mt, { + mt->evt.recvdatafrom(mt, decoded, tag, &addr, alen, + mt->user_data); + }); + } + + mt->check_destroy(mt); + } + else { + mrp_log_error("Failed to decode custom data message."); + } + + return TRUE; +} + + +static int dbus_raw_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *dmsg, void *user_data) +{ + mrp_transport_t *mt = (mrp_transport_t *)user_data; + dbus_t *t = (dbus_t *)mt; + mrp_sockaddr_t addr; + socklen_t alen; + const char *sender, *sender_path; + void *data; + size_t size; + + MRP_UNUSED(dbus); + + data = raw_decode(dmsg, &size, &sender_path); + + if (data != NULL) { + sender = mrp_dbus_msg_sender(dmsg); + + if (mt->connected) { + if (!t->peer_resolved || !strcmp(t->remote.db_addr, sender)) + MRP_TRANSPORT_BUSY(mt, { + mt->evt.recvraw(mt, data, size, mt->user_data); + }); + } + else { + peer_address(&addr, sender, sender_path); + alen = sizeof(addr); + + MRP_TRANSPORT_BUSY(mt, { + mt->evt.recvrawfrom(mt, data, size, &addr, alen, + mt->user_data); + }); + } + + mt->check_destroy(mt); + } + else { + mrp_log_error("Failed to decode raw message."); + } + + return TRUE; +} + + +static void peer_state_cb(mrp_dbus_t *dbus, const char *name, int up, + const char *owner, void *user_data) +{ + dbus_t *t = (dbus_t *)user_data; + mrp_sockaddr_t addr; + + MRP_UNUSED(dbus); + MRP_UNUSED(name); + + if (up) { + peer_address(&addr, owner, t->remote.db_path); + copy_address(&t->remote, (mrp_dbusaddr_t *)&addr); + t->peer_resolved = TRUE; + } + else { + /* + * XXX TODO: + * It would be really tempting here to call + * mt->evt.closed(mt, ECONNRESET, mt->user_data) + * to notify the user about the fact our peer went down. + * However, that would not be in line with the other + * transports which call the closed event handler only + * upon foricble transport closes upon errors. + * + * The transport interface abstraction (especially the + * available set of events) anyway needs some eyeballing, + * so the right thing to do might be to define a new event + * for disconnection and call the handler for that here... + */ + } + +} + + +static int dbus_connect(mrp_transport_t *mt, mrp_sockaddr_t *addrp, + socklen_t addrlen) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp; + + if (!check_address(addrp, addrlen)) { + errno = EINVAL; + return FALSE; + } + + if (t->dbus == NULL) { + t->dbus = mrp_dbus_connect(t->ml, addr->db_bus, NULL); + + if (t->dbus == NULL) { + errno = ECONNRESET; + return FALSE; + } + } + else { + /* XXX TODO: check given address against address of the bus */ + } + + if (!t->bound) + if (!dbus_autobind(mt, addrp)) + return FALSE; + + if (mrp_dbus_follow_name(t->dbus, addr->db_addr, peer_state_cb, t)) { + copy_address(&t->remote, addr); + + return TRUE; + } + else + return FALSE; +} + + +static int dbus_disconnect(mrp_transport_t *mt) +{ + dbus_t *t = (dbus_t *)mt; + + if (t->connected && t->remote.db_addr != NULL) { + mrp_dbus_forget_name(t->dbus, t->remote.db_addr, peer_state_cb, t); + mrp_clear(&t->remote); + t->peer_resolved = FALSE; + } + + return TRUE; +} + + +static int dbus_sendmsgto(mrp_transport_t *mt, mrp_msg_t *msg, + mrp_sockaddr_t *addrp, socklen_t addrlen) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp; + mrp_dbus_msg_t *m; + int success; + + if (check_address(addrp, addrlen)) { + if (t->dbus == NULL && !dbus_autobind(mt, addrp)) + return FALSE; + + m = msg_encode(t->dbus, addr->db_addr, addr->db_path, + TRANSPORT_INTERFACE, TRANSPORT_MESSAGE, + t->local.db_path, msg); + + if (m != NULL) { + if (mrp_dbus_send_msg(t->dbus, m)) + success = TRUE; + else { + errno = ECOMM; + success = FALSE; + } + + mrp_dbus_msg_unref(m); + } + else + success = FALSE; + } + else { + errno = EINVAL; + success = FALSE; + } + + return success; +} + + +static int dbus_sendmsg(mrp_transport_t *mt, mrp_msg_t *msg) +{ + dbus_t *t = (dbus_t *)mt; + mrp_sockaddr_t *addr = (mrp_sockaddr_t *)&t->remote; + socklen_t alen = sizeof(t->remote); + + return dbus_sendmsgto(mt, msg, addr, alen); +} + + +static int dbus_sendrawto(mrp_transport_t *mt, void *data, size_t size, + mrp_sockaddr_t *addrp, socklen_t addrlen) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp; + mrp_dbus_msg_t *m; + int success; + + + MRP_UNUSED(mt); + MRP_UNUSED(data); + MRP_UNUSED(size); + MRP_UNUSED(addr); + MRP_UNUSED(addrlen); + + if (check_address(addrp, addrlen)) { + if (t->dbus == NULL && !dbus_autobind(mt, addrp)) + return FALSE; + + m = raw_encode(t->dbus, addr->db_addr, addr->db_path, + TRANSPORT_INTERFACE, TRANSPORT_RAW, + t->local.db_path, data, size); + + if (m != NULL) { + if (mrp_dbus_send_msg(t->dbus, m)) + success = TRUE; + else { + errno = ECOMM; + success = FALSE; + } + + mrp_dbus_msg_unref(m); + } + else + success = FALSE; + } + else { + errno = EINVAL; + success = FALSE; + } + + return success; +} + + +static int dbus_sendraw(mrp_transport_t *mt, void *data, size_t size) +{ + dbus_t *t = (dbus_t *)mt; + mrp_sockaddr_t *addr = (mrp_sockaddr_t *)&t->remote; + socklen_t alen = sizeof(t->remote); + + return dbus_sendrawto(mt, data, size, addr, alen); +} + + +static int dbus_senddatato(mrp_transport_t *mt, void *data, uint16_t tag, + mrp_sockaddr_t *addrp, socklen_t addrlen) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp; + mrp_dbus_msg_t *m; + int success; + + if (check_address(addrp, addrlen)) { + if (t->dbus == NULL && !dbus_autobind(mt, addrp)) + return FALSE; + + m = data_encode(t->dbus, addr->db_addr, addr->db_path, + TRANSPORT_INTERFACE, TRANSPORT_DATA, + t->local.db_path, data, tag); + + if (m != NULL) { + if (mrp_dbus_send_msg(t->dbus, m)) + success = TRUE; + else { + errno = ECOMM; + success = FALSE; + } + + mrp_dbus_msg_unref(m); + } + else + success = FALSE; + } + else { + errno = EINVAL; + success = FALSE; + } + + return success; +} + + +static int dbus_senddata(mrp_transport_t *mt, void *data, uint16_t tag) +{ + dbus_t *t = (dbus_t *)mt; + mrp_sockaddr_t *addr = (mrp_sockaddr_t *)&t->remote; + socklen_t alen = sizeof(t->remote); + + return dbus_senddatato(mt, data, tag, addr, alen); +} + + +static const char *get_array_signature(uint16_t type) +{ +#define MAP(from, to) \ + case MRP_MSG_FIELD_##from: \ + return MRP_DBUS_TYPE_##to##_AS_STRING; + + switch (type) { + MAP(STRING, STRING); + MAP(BOOL , BOOLEAN); + MAP(UINT8 , UINT16); + MAP(SINT8 , INT16); + MAP(UINT16, UINT16); + MAP(SINT16, INT16); + MAP(UINT32, UINT32); + MAP(SINT32, INT32); + MAP(UINT64, UINT64); + MAP(SINT64, INT64); + MAP(DOUBLE, DOUBLE); + MAP(BLOB , BYTE); + default: + return NULL; + } +} + + +static mrp_dbus_msg_t *msg_encode(mrp_dbus_t *dbus, const char *destination, + const char *path, const char *interface, + const char *member, const char *sender_id, + mrp_msg_t *msg) +{ + /* + * Notes: There is a type mismatch between our and DBUS types for + * 8-bit integers (D-BUS does not have a signed 8-bit type) + * and boolean types (D-BUS has uint32_t booleans, C99 fails + * to specify the type and gcc uses a signed char). The + * QUIRKY versions of the macros take care of these mismatches. + */ + +#define BASIC_SIMPLE(_i, _mtype, _dtype, _val) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + vptr = &(_val); \ + \ + if (!mrp_dbus_msg_append_basic(_i, type, vptr)) \ + goto fail; \ + break + +#define BASIC_QUIRKY(_i, _mtype, _dtype, _mval, _dval) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + _dval = _mval; \ + vptr = &_dval; \ + \ + if (!mrp_dbus_msg_append_basic(_i, type, vptr)) \ + goto fail; \ + break + +#define ARRAY_SIMPLE(_i, _mtype, _dtype, _val) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + vptr = &_val; \ + \ + if (!mrp_dbus_msg_append_basic(_i, type, vptr)) \ + goto fail; \ + break + +#define ARRAY_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + _dvar = _mvar; \ + vptr = &_dvar; \ + \ + if (!mrp_dbus_msg_append_basic(_i, type, vptr)) \ + goto fail; \ + break + + mrp_dbus_msg_t *m; + mrp_list_hook_t *p, *n; + mrp_msg_field_t *f; + uint16_t base; + uint32_t asize, i; + const char *sig; + int type, len; + void *vptr; + dbus_bool_t bln; + uint16_t u16, blb; + int16_t s16; + + m = mrp_dbus_msg_method_call(dbus, destination, path, interface, member); + + if (m == NULL) + return NULL; + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_OBJECT_PATH, + (void *)sender_id)) + goto fail; + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &msg->nfield)) + goto fail; + + mrp_list_foreach(&msg->fields, p, n) { + f = mrp_list_entry(p, typeof(*f), hook); + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &f->tag) || + !mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &f->type)) + goto fail; + + switch (f->type) { + BASIC_SIMPLE(m, STRING, STRING , f->str); + BASIC_QUIRKY(m, BOOL , BOOLEAN, f->bln, bln); + BASIC_QUIRKY(m, UINT8 , UINT16 , f->u8 , u16); + BASIC_QUIRKY(m, SINT8 , INT16 , f->s8 , s16); + BASIC_SIMPLE(m, UINT16, UINT16 , f->u16); + BASIC_SIMPLE(m, SINT16, INT16 , f->s16); + BASIC_SIMPLE(m, UINT32, UINT32 , f->u32); + BASIC_SIMPLE(m, SINT32, INT32 , f->s32); + BASIC_SIMPLE(m, UINT64, UINT64 , f->u64); + BASIC_SIMPLE(m, SINT64, INT64 , f->s64); + BASIC_SIMPLE(m, DOUBLE, DOUBLE , f->dbl); + + case MRP_MSG_FIELD_BLOB: + vptr = f->blb; + len = (int)f->size[0]; + sig = get_array_signature(f->type); + asize = len; + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &asize)) + goto fail; + + if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, sig)) + goto fail; + + for (i = 0; i < asize; i++) { + blb = ((uint8_t *)f->blb)[i]; + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_BYTE, &blb)) + goto fail; + } + + if (!mrp_dbus_msg_close_container(m)) + goto fail; + break; + + default: + if (f->type & MRP_MSG_FIELD_ARRAY) { + base = f->type & ~(MRP_MSG_FIELD_ARRAY); + asize = f->size[0]; + sig = get_array_signature(base); + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &asize)) + goto fail; + + if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, sig)) + goto fail; + + for (i = 0; i < asize; i++) { + switch (base) { + ARRAY_SIMPLE(m, STRING, STRING , f->astr[i]); + ARRAY_QUIRKY(m, BOOL , BOOLEAN, f->abln[i], bln); + ARRAY_QUIRKY(m, UINT8 , UINT16 , f->au8[i] , u16); + ARRAY_QUIRKY(m, SINT8 , INT16 , f->as8[i] , s16); + ARRAY_SIMPLE(m, UINT16, UINT16 , f->au16[i]); + ARRAY_SIMPLE(m, SINT16, INT16 , f->as16[i]); + ARRAY_SIMPLE(m, UINT32, UINT32 , f->au32[i]); + ARRAY_SIMPLE(m, SINT32, INT32 , f->as32[i]); + ARRAY_SIMPLE(m, UINT64, UINT64 , f->au64[i]); + ARRAY_SIMPLE(m, DOUBLE, DOUBLE , f->adbl[i]); + + case MRP_MSG_FIELD_BLOB: + goto fail; + + default: + goto fail; + } + } + + if (!mrp_dbus_msg_close_container(m)) + goto fail; + } + else + goto fail; + } + } + + return m; + + fail: + if (m != NULL) + mrp_dbus_msg_unref(m); + + errno = ECOMM; + + return FALSE; + +#undef BASIC_SIMPLE +#undef BASIC_QUIRKY +#undef ARRAY_SIMPLE +#undef ARRAY_QUIRKY +} + + +static mrp_msg_t *msg_decode(mrp_dbus_msg_t *m, const char **sender_id) +{ +#define BASIC_SIMPLE(_i, _mtype, _dtype, _var) \ + case MRP_MSG_FIELD_##_mtype: \ + if (!mrp_dbus_msg_read_basic(_i, MRP_DBUS_TYPE_##_dtype, \ + &(_var))) \ + goto fail; \ + \ + if (!mrp_msg_append(msg, tag, type, (_var))) \ + goto fail; \ + break + +#define BASIC_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar) \ + case MRP_MSG_FIELD_##_mtype: \ + if (!mrp_dbus_msg_read_basic(_i, MRP_DBUS_TYPE_##_dtype, \ + &(_dvar))) \ + goto fail; \ + \ + _mvar = _dvar; \ + if (!mrp_msg_append(msg, tag, type, (_mvar))) \ + goto fail; \ + break + +#define ARRAY_SIMPLE(_i, _mtype, _dtype, _var) \ + case MRP_MSG_FIELD_##_mtype: \ + if (!mrp_dbus_msg_read_basic(_i, MRP_DBUS_TYPE_##_dtype, \ + &(_var))) \ + goto fail; \ + break + +#define ARRAY_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar) \ + case MRP_MSG_FIELD_##_mtype: \ + if (!mrp_dbus_msg_read_basic(_i, MRP_DBUS_TYPE_##_dtype, \ + &(_dvar))) \ + goto fail; \ + \ + _mvar = _dvar; \ + break + +#define APPEND_ARRAY(_type, _var) \ + case MRP_MSG_FIELD_##_type: \ + if (!mrp_msg_append(msg, tag, \ + MRP_MSG_FIELD_ARRAY | \ + MRP_MSG_FIELD_##_type, \ + n, _var)) \ + goto fail; \ + break + + mrp_msg_t *msg; + mrp_msg_value_t v; + uint16_t u16; + int16_t s16; + uint32_t u32; + uint16_t nfield, tag, type, base, i; + uint32_t n, j; + int asize; + const char *sender, *sig; + + msg = NULL; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_OBJECT_PATH, &sender)) + goto fail; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &nfield)) + goto fail; + + msg = mrp_msg_create_empty(); + + if (msg == NULL) + goto fail; + + for (i = 0; i < nfield; i++) { + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &tag)) + goto fail; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &type)) + goto fail; + + switch (type) { + BASIC_SIMPLE(m, STRING, STRING , v.str); + BASIC_QUIRKY(m, BOOL , BOOLEAN, v.bln, u32); + BASIC_QUIRKY(m, UINT8 , UINT16 , v.u8 , u16); + BASIC_QUIRKY(m, SINT8 , INT16 , v.s8 , s16); + BASIC_SIMPLE(m, UINT16, UINT16 , v.u16); + BASIC_SIMPLE(m, SINT16, INT16 , v.s16); + BASIC_SIMPLE(m, UINT32, UINT32 , v.u32); + BASIC_SIMPLE(m, SINT32, INT32 , v.s32); + BASIC_SIMPLE(m, UINT64, UINT64 , v.u64); + BASIC_SIMPLE(m, SINT64, INT64 , v.s64); + BASIC_SIMPLE(m, DOUBLE, DOUBLE , v.dbl); + + case MRP_MSG_FIELD_BLOB: + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &n)) + goto fail; + + { + uint8_t blb[n]; + + if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, NULL)) + goto fail; + + for (j = 0; j < n; j++) { + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_BYTE, + blb + j)) + goto fail; + } + + if (!mrp_dbus_msg_exit_container(m)) + goto fail; + + asize = n; + if (!mrp_msg_append(msg, tag, type, asize, blb)) + goto fail; + } + break; + + default: + if (!(type & MRP_MSG_FIELD_ARRAY)) + goto fail; + + base = type & ~(MRP_MSG_FIELD_ARRAY); + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &n)) + goto fail; + + sig = get_array_signature(base); + if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, sig)) + goto fail; + + { + char *astr[n]; + uint32_t dbln[n]; + bool abln[n]; + uint8_t au8 [n]; + int8_t as8 [n]; + uint16_t au16[n]; + int16_t as16[n]; + uint32_t au32[n]; + int32_t as32[n]; + uint64_t au64[n]; + int64_t as64[n]; + double adbl[n]; + + for (j = 0; j < n; j++) { + switch (base) { + ARRAY_SIMPLE(m, STRING, STRING , astr[j]); + ARRAY_QUIRKY(m, BOOL , BOOLEAN, abln[j], dbln[j]); + ARRAY_QUIRKY(m, UINT8 , UINT16 , au8[j] , au16[j]); + ARRAY_QUIRKY(m, SINT8 , INT16 , as8[j] , as16[j]); + ARRAY_SIMPLE(m, UINT16, UINT16 , au16[j]); + ARRAY_SIMPLE(m, SINT16, INT16 , as16[j]); + ARRAY_SIMPLE(m, UINT32, UINT32 , au32[j]); + ARRAY_SIMPLE(m, SINT32, INT32 , as32[j]); + ARRAY_SIMPLE(m, UINT64, UINT64 , au64[j]); + ARRAY_SIMPLE(m, SINT64, INT64 , as64[j]); + ARRAY_SIMPLE(m, DOUBLE, DOUBLE , adbl[j]); + default: + goto fail; + } + } + + switch (base) { + APPEND_ARRAY(STRING, astr); + APPEND_ARRAY(BOOL , abln); + APPEND_ARRAY(UINT8 , au8 ); + APPEND_ARRAY(SINT8 , as8 ); + APPEND_ARRAY(UINT16, au16); + APPEND_ARRAY(SINT16, as16); + APPEND_ARRAY(UINT32, au32); + APPEND_ARRAY(SINT32, as32); + APPEND_ARRAY(UINT64, au64); + APPEND_ARRAY(SINT64, as64); + APPEND_ARRAY(DOUBLE, adbl); + default: + goto fail; + } + } + + if (!mrp_dbus_msg_exit_container(m)) + goto fail; + } + } + + if (sender_id != NULL) + *sender_id = sender; + + return msg; + + fail: + mrp_msg_unref(msg); + errno = EBADMSG; + + return NULL; + +#undef BASIC_SIMPLE +#undef BASIC_QUIRKY +#undef ARRAY_SIMPLE +#undef ARRAY_QUIRKY +#undef APPEND_ARRAY +} + + +static mrp_dbus_msg_t *data_encode(mrp_dbus_t *dbus, const char *destination, + const char *path, const char *interface, + const char *member, const char *sender_id, + void *data, uint16_t tag) +{ +#define BASIC_SIMPLE(_mtype, _dtype, _val) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + vptr = &(_val); \ + \ + if (!mrp_dbus_msg_append_basic(m, type, vptr)) \ + goto fail; \ + break + +#define BASIC_QUIRKY(_mtype, _dtype, _mval, _dval) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + _dval = _mval; \ + vptr = &_dval; \ + \ + if (!mrp_dbus_msg_append_basic(m, type, vptr)) \ + goto fail; \ + break + +#define ARRAY_SIMPLE(_mtype, _dtype, _val) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + vptr = &_val; \ + \ + if (!mrp_dbus_msg_append_basic(m, type, vptr)) \ + goto fail; \ + break + +#define ARRAY_QUIRKY(_mtype, _dtype, _mvar, _dvar) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + _dvar = _mvar; \ + vptr = &_dvar; \ + \ + if (!mrp_dbus_msg_append_basic(m, type, vptr)) \ + goto fail; \ + break + + mrp_dbus_msg_t *m; + mrp_data_descr_t *descr; + mrp_data_member_t *fields, *f; + int nfield; + uint16_t type, base; + mrp_msg_value_t *v; + void *vptr; + uint32_t n, j; + int i, blblen; + const char *sig; + uint16_t u16; + int16_t s16; + uint32_t bln, asize; + + m = mrp_dbus_msg_method_call(dbus, destination, path, interface, member); + + if (m == NULL) + return NULL; + + descr = mrp_msg_find_type(tag); + + if (descr == NULL) + goto fail; + + fields = descr->fields; + nfield = descr->nfield; + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_OBJECT_PATH, + (void *)sender_id)) + goto fail; + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &tag)) + goto fail; + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &nfield)) + goto fail; + + for (i = 0, f = fields; i < nfield; i++, f++) { + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &f->tag) || + !mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &f->type)) + goto fail; + + v = (mrp_msg_value_t *)(data + f->offs); + + switch (f->type) { + BASIC_SIMPLE(STRING, STRING , v->str); + BASIC_QUIRKY(BOOL , BOOLEAN, v->bln, bln); + BASIC_QUIRKY(UINT8 , UINT16 , v->u8 , u16); + BASIC_QUIRKY(SINT8 , INT16 , v->s8 , s16); + BASIC_SIMPLE(UINT16, UINT16 , v->u16); + BASIC_SIMPLE(SINT16, INT16 , v->s16); + BASIC_SIMPLE(UINT32, UINT32 , v->u32); + BASIC_SIMPLE(SINT32, INT32 , v->s32); + BASIC_SIMPLE(UINT64, UINT64 , v->u64); + BASIC_SIMPLE(SINT64, INT64 , v->s64); + BASIC_SIMPLE(DOUBLE, DOUBLE , v->dbl); + + case MRP_MSG_FIELD_BLOB: + sig = get_array_signature(f->type); + blblen = mrp_data_get_blob_size(data, descr, i); + asize = blblen; + + if (blblen == -1) + goto fail; + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &asize)) + goto fail; + + if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, sig)) + goto fail; + + for (i = 0; i < blblen; i++) + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_BYTE, + f->blb + i)) + goto fail; + + if (!mrp_dbus_msg_close_container(m)) + goto fail; + break; + + default: + if (!(f->type & MRP_MSG_FIELD_ARRAY)) + goto fail; + + base = f->type & ~(MRP_MSG_FIELD_ARRAY); + n = mrp_data_get_array_size(data, descr, i); + sig = get_array_signature(base); + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &n)) + goto fail; + + if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, sig)) + goto fail; + + for (j = 0; j < n; j++) { + switch (base) { + ARRAY_SIMPLE(STRING, STRING , v->astr[j]); + ARRAY_QUIRKY(BOOL , BOOLEAN, v->abln[j], bln); + ARRAY_QUIRKY(UINT8 , UINT16 , v->au8[j] , u16); + ARRAY_QUIRKY(SINT8 , INT16 , v->as8[j] , s16); + ARRAY_SIMPLE(UINT16, UINT16 , v->au16[j]); + ARRAY_SIMPLE(SINT16, INT16 , v->as16[j]); + ARRAY_SIMPLE(UINT32, UINT32 , v->au32[j]); + ARRAY_SIMPLE(SINT32, INT32 , v->as32[j]); + ARRAY_SIMPLE(UINT64, UINT64 , v->au64[j]); + ARRAY_SIMPLE(DOUBLE, DOUBLE , v->adbl[j]); + + case MRP_MSG_FIELD_BLOB: + goto fail; + + default: + goto fail; + } + } + + if (!mrp_dbus_msg_close_container(m)) + goto fail; + } + } + + return m; + + fail: + if (m != NULL) + mrp_dbus_msg_unref(m); + + errno = ECOMM; + + return NULL; + +#undef BASIC_SIMPLE +#undef BASIC_QUIRKY +#undef ARRAY_SIMPLE +#undef ARRAY_QUIRKY +} + + +static mrp_data_member_t *member_type(mrp_data_member_t *fields, int nfield, + uint16_t tag) +{ + mrp_data_member_t *f; + int i; + + for (i = 0, f = fields; i < nfield; i++, f++) + if (f->tag == tag) + return f; + + return NULL; +} + + +static void *data_decode(mrp_dbus_msg_t *m, uint16_t *tagp, + const char **sender_id) +{ +#define HANDLE_SIMPLE(_i, _mtype, _dtype, _var) \ + case MRP_MSG_FIELD_##_mtype: \ + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_##_dtype, \ + &(_var))) \ + goto fail; \ + break + +#define HANDLE_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar) \ + case MRP_MSG_FIELD_##_mtype: \ + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_##_dtype, \ + &(_dvar))) \ + goto fail; \ + \ + _mvar = _dvar; \ + break + + void *data; + mrp_data_descr_t *descr; + mrp_data_member_t *fields, *f; + int nfield; + uint16_t tag, type, base; + mrp_msg_value_t *v; + uint32_t n, j, size; + int i; + const char *sender, *sig; + uint32_t u32; + uint16_t u16; + int16_t s16; + + tag = 0; + data = NULL; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_OBJECT_PATH, &sender)) + goto fail; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &tag)) + goto fail; + + descr = mrp_msg_find_type(tag); + + if (descr == NULL) + goto fail; + + *tagp = tag; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &nfield)) + goto fail; + + if (nfield != descr->nfield) + goto fail; + + fields = descr->fields; + data = mrp_allocz(descr->size); + + if (MRP_UNLIKELY(data == NULL)) + goto fail; + + for (i = 0; i < nfield; i++) { + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &tag)) + goto fail; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &type)) + goto fail; + + f = member_type(fields, nfield, tag); + + if (MRP_UNLIKELY(f == NULL)) + goto fail; + + v = (mrp_msg_value_t *)(data + f->offs); + + switch (type) { + HANDLE_SIMPLE(&im, STRING, STRING , v->str); + HANDLE_QUIRKY(&im, BOOL , BOOLEAN, v->bln, u32); + HANDLE_QUIRKY(&im, UINT8 , UINT16 , v->u8 , u16); + HANDLE_QUIRKY(&im, SINT8 , INT16 , v->s8 , s16); + HANDLE_SIMPLE(&im, UINT16, UINT16 , v->u16); + HANDLE_SIMPLE(&im, SINT16, INT16 , v->s16); + HANDLE_SIMPLE(&im, UINT32, UINT32 , v->u32); + HANDLE_SIMPLE(&im, SINT32, INT32 , v->s32); + HANDLE_SIMPLE(&im, UINT64, UINT64 , v->u64); + HANDLE_SIMPLE(&im, SINT64, INT64 , v->s64); + HANDLE_SIMPLE(&im, DOUBLE, DOUBLE , v->dbl); + + case MRP_MSG_FIELD_BLOB: + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &size)) + goto fail; + + sig = MRP_DBUS_TYPE_BYTE_AS_STRING; + + if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, sig)) + goto fail; + + { + uint8_t blb[size]; + + for (j = 0; j < size; j++) + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_BYTE, + blb + j)) + goto fail; + + v->blb = mrp_alloc(size); + + if (v->blb == NULL && size != 0) + goto fail; + + memcpy(v->blb, blb, size); + } + + if (!mrp_dbus_msg_exit_container(m)) + goto fail; + break; + + default: + if (!(f->type & MRP_MSG_FIELD_ARRAY)) + goto fail; + + base = type & ~(MRP_MSG_FIELD_ARRAY); + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &n)) + goto fail; + + if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, NULL)) + goto fail; + + size = n; + + switch (base) { + case MRP_MSG_FIELD_STRING: size *= sizeof(*v->astr); break; + case MRP_MSG_FIELD_BOOL: size *= sizeof(*v->abln); break; + case MRP_MSG_FIELD_UINT8: size *= sizeof(*v->au8); break; + case MRP_MSG_FIELD_SINT8: size *= sizeof(*v->as8); break; + case MRP_MSG_FIELD_UINT16: size *= sizeof(*v->au16); break; + case MRP_MSG_FIELD_SINT16: size *= sizeof(*v->as16); break; + case MRP_MSG_FIELD_UINT32: size *= sizeof(*v->au32); break; + case MRP_MSG_FIELD_SINT32: size *= sizeof(*v->as32); break; + case MRP_MSG_FIELD_UINT64: size *= sizeof(*v->au64); break; + case MRP_MSG_FIELD_SINT64: size *= sizeof(*v->as64); break; + case MRP_MSG_FIELD_DOUBLE: size *= sizeof(*v->adbl); break; + default: + goto fail; + } + + v->aany = mrp_allocz(size); + if (v->aany == NULL) + goto fail; + + for (j = 0; j < n; j++) { + uint32_t dbln[n]; + uint16_t au16[n]; + int16_t as16[n]; + + switch (base) { + HANDLE_SIMPLE(&ia, STRING, STRING , v->astr[j]); + HANDLE_QUIRKY(&ia, BOOL , BOOLEAN, v->abln[j], dbln[j]); + HANDLE_QUIRKY(&ia, UINT8 , UINT16 , v->au8[j] , au16[j]); + HANDLE_QUIRKY(&ia, SINT8 , INT16 , v->as8[j] , as16[j]); + HANDLE_SIMPLE(&ia, UINT16, UINT16 , v->au16[j]); + HANDLE_SIMPLE(&ia, SINT16, INT16 , v->as16[j]); + HANDLE_SIMPLE(&ia, UINT32, UINT32 , v->au32[j]); + HANDLE_SIMPLE(&ia, SINT32, INT32 , v->as32[j]); + HANDLE_SIMPLE(&ia, UINT64, UINT64 , v->au64[j]); + HANDLE_SIMPLE(&ia, SINT64, INT64 , v->as64[j]); + HANDLE_SIMPLE(&ia, DOUBLE, DOUBLE , v->adbl[j]); + } + + if (base == MRP_MSG_FIELD_STRING) { + v->astr[j] = mrp_strdup(v->astr[j]); + if (v->astr[j] == NULL) + goto fail; + } + } + + if (!mrp_dbus_msg_exit_container(m)) + goto fail; + } + + if (f->type == MRP_MSG_FIELD_STRING) { + v->str = mrp_strdup(v->str); + if (v->str == NULL) + goto fail; + } + } + + if (sender_id != NULL) + *sender_id = sender; + + return data; + + fail: + mrp_data_free(data, tag); + errno = EBADMSG; + + return NULL; +} + + +static mrp_dbus_msg_t *raw_encode(mrp_dbus_t *dbus, const char *destination, + const char *path, const char *interface, + const char *member, const char *sender_id, + void *data, size_t size) +{ + mrp_dbus_msg_t *m; + const char *sig; + uint32_t i, n; + + m = mrp_dbus_msg_method_call(dbus, destination, path, interface, member); + + if (m == NULL) + return NULL; + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_OBJECT_PATH, + (void *)sender_id)) + goto fail; + + sig = MRP_DBUS_TYPE_BYTE_AS_STRING; + n = size; + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &n)) + goto fail; + + if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, sig)) + goto fail; + + for (i = 0; i < n; i++) + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_BYTE, data + i)) + goto fail; + + if (!mrp_dbus_msg_close_container(m)) + goto fail; + + return m; + + fail: + mrp_dbus_msg_unref(m); + + errno = ECOMM; + + return NULL; +} + + +static void *raw_decode(mrp_dbus_msg_t *m, size_t *sizep, + const char **sender_id) +{ + const char *sender, *sig; + void *data; + uint32_t n, i; + + data = NULL; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_OBJECT_PATH, &sender)) + goto fail; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &n)) + goto fail; + + sig = MRP_DBUS_TYPE_BYTE_AS_STRING; + + if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, sig)) + goto fail; + + { + uint8_t databuf[n]; + + for (i = 0; i < n; i++) + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_BYTE, databuf + i)) + goto fail; + + data = mrp_alloc(n); + + if (data == NULL && n != 0) + goto fail; + + memcpy(data, databuf, n); + } + + if (!mrp_dbus_msg_exit_container(m)) + goto fail; + + if (sizep != NULL) + *sizep = (size_t)n; + + if (sender_id != NULL) + *sender_id = sender; + + return data; + + fail: + errno = EBADMSG; + + return NULL; +} + + +MRP_REGISTER_TRANSPORT(dbus, DBUS, dbus_t, dbus_resolve, + dbus_open, dbus_createfrom, dbus_close, NULL, + dbus_bind, NULL, NULL, + dbus_connect, dbus_disconnect, + dbus_sendmsg, dbus_sendmsgto, + dbus_sendraw, dbus_sendrawto, + dbus_senddata, dbus_senddatato, + NULL, NULL, + NULL, NULL, + NULL, NULL); diff --git a/src/common/dbus-libdbus.c b/src/common/dbus-libdbus.c new file mode 100644 index 0000000..6dcf9fa --- /dev/null +++ b/src/common/dbus-libdbus.c @@ -0,0 +1,2078 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/log.h> +#include <murphy/common/list.h> +#include <murphy/common/hashtbl.h> +#include <murphy/common/refcnt.h> +#include <murphy/common/utils.h> +#include <murphy/common/mainloop.h> +#include <murphy/common/dbus-libdbus.h> + + +#define DBUS_ADMIN_SERVICE "org.freedesktop.DBus" +#define DBUS_ADMIN_INTERFACE "org.freedesktop.DBus" +#define DBUS_ADMIN_PATH "/org/freedesktop/DBus" +#define DBUS_NAME_CHANGED "NameOwnerChanged" + + +struct mrp_dbus_s { + char *address; /* bus address */ + DBusConnection *conn; /* actual D-BUS connection */ + mrp_mainloop_t *ml; /* murphy mainloop */ + mrp_htbl_t *methods; /* method handler table */ + mrp_htbl_t *signals; /* signal handler table */ + mrp_list_hook_t name_trackers; /* peer (name) watchers */ + mrp_list_hook_t calls; /* pending calls */ + uint32_t call_id; /* next call id */ + const char *unique_name; /* our unique D-BUS address */ + int priv; /* whether a private connection */ + int signal_filter; /* if signal dispatching is set up */ + int register_fallback; /* if the fallback object is set up */ + mrp_refcnt_t refcnt; /* reference count */ +}; + + +struct mrp_dbus_msg_s { + DBusMessage *msg; /* actual D-BUS message */ + mrp_refcnt_t refcnt; /* reference count */ + mrp_list_hook_t iterators; /* iterator stack */ + mrp_list_hook_t arrays; /* implicitly freed related arrays */ +}; + + +typedef struct { + DBusMessageIter it; /* actual iterator */ + char *peeked; /* peeked contents, or NULL */ + mrp_list_hook_t hook; /* hook to iterator stack */ +} msg_iter_t; + + +typedef struct { + mrp_list_hook_t hook; + char **items; + size_t nitem; +} msg_array_t; + +/* + * Notes: + * + * At the moment we administer DBUS method and signal handlers + * in a very primitive way (subject to be changed later). For + * every bus instance we maintain two hash tables, one for methods + * and another for signals. Each method and signal handler is + * hashed in only by it's method/signal name to a linked list of + * method or signal handlers. + * + * When dispatching a method, we look up the chain with a matching + * method name, or the chain for "" in case a matching chain is + * not found, and invoke the handler which best matches the + * received message (by looking at the path, interface and name). + * Only one such handler is invoked at most. + * + * For signals we look up both the chain with a matching name and + * the chain for "" and invoke all signal handlers that match the + * received message (regardless of their return value). + */ + + +typedef struct { + char *member; /* signal/method name */ + mrp_list_hook_t handlers; /* handlers with matching member */ +} handler_list_t; + +typedef struct { + mrp_list_hook_t hook; + char *sender; + char *path; + char *interface; + char *member; + mrp_dbus_handler_t handler; + void *user_data; +} handler_t; + +#define method_t handler_t +#define signal_t handler_t + + +typedef struct { + mrp_list_hook_t hook; /* hook to name tracker list */ + char *name; /* name to track */ + mrp_dbus_name_cb_t cb; /* status change callback */ + void *user_data; /* opaque callback user data */ + int32_t qid; /* initial query ID */ +} name_tracker_t; + + +typedef struct { + mrp_dbus_t *dbus; /* DBUS connection */ + int32_t id; /* call id */ + mrp_dbus_reply_cb_t cb; /* completion notification callback */ + void *user_data; /* opaque callback data */ + DBusPendingCall *pend; /* pending DBUS call */ + mrp_list_hook_t hook; /* hook to list of pending calls */ +} call_t; + + +typedef struct { + mrp_mainloop_t *ml; /* mainloop for bus connection */ + const char *address; /* address of bus */ +} bus_spec_t; + +static mrp_htbl_t *buses; + + + +static DBusHandlerResult dispatch_signal(DBusConnection *c, + DBusMessage *msg, void *data); +static DBusHandlerResult dispatch_method(DBusConnection *c, + DBusMessage *msg, void *data); +static void purge_name_trackers(mrp_dbus_t *dbus); +static void purge_calls(mrp_dbus_t *dbus); +static void handler_list_free_cb(void *key, void *entry); +static void handler_free(handler_t *h); +static int name_owner_change_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *m, + void *data); +static void call_free(call_t *call); +static void free_msg_array(msg_array_t *a); + + + +static int purge_filters(void *key, void *entry, void *user_data) +{ + mrp_dbus_t *dbus = (mrp_dbus_t *)user_data; + handler_list_t *l = (handler_list_t *)entry; + mrp_list_hook_t *p, *n; + handler_t *h; + + MRP_UNUSED(key); + + mrp_list_foreach(&l->handlers, p, n) { + h = mrp_list_entry(p, handler_t, hook); + mrp_dbus_remove_filter(dbus, + h->sender, h->path, h->interface, + h->member, NULL); + } + + return MRP_HTBL_ITER_MORE; +} + + +void dbus_disconnect(mrp_dbus_t *dbus) +{ + if (dbus) { + mrp_htbl_remove(buses, dbus->conn, FALSE); + + if (dbus->signals) { + mrp_htbl_foreach(dbus->signals, purge_filters, dbus); + mrp_htbl_destroy(dbus->signals, TRUE); + } + if (dbus->methods) + mrp_htbl_destroy(dbus->methods, TRUE); + + if (dbus->conn != NULL) { + if (dbus->signal_filter) + dbus_connection_remove_filter(dbus->conn, dispatch_signal, + dbus); + if (dbus->register_fallback) + dbus_connection_unregister_object_path(dbus->conn, "/"); + if (dbus->priv) + dbus_connection_close(dbus->conn); + dbus_connection_unref(dbus->conn); + } + + purge_name_trackers(dbus); + purge_calls(dbus); + + mrp_free(dbus->address); + dbus->conn = NULL; + dbus->ml = NULL; + + mrp_free(dbus); + } +} + + +static int bus_cmp(const void *key1, const void *key2) +{ + return key2 - key1; +} + + +static uint32_t bus_hash(const void *key) +{ + uint32_t h; + + h = (ptrdiff_t)key; + h >>= 2 * sizeof(key); + + return h; +} + + +static int find_bus_by_spec(void *key, void *object, void *user_data) +{ + mrp_dbus_t *dbus = (mrp_dbus_t *)object; + bus_spec_t *spec = (bus_spec_t *)user_data; + + MRP_UNUSED(key); + + if (dbus->ml == spec->ml && !strcmp(dbus->address, spec->address)) + return TRUE; + else + return FALSE; +} + + +static mrp_dbus_t *dbus_get(mrp_mainloop_t *ml, const char *address) +{ + mrp_htbl_config_t hcfg; + bus_spec_t spec; + + if (buses == NULL) { + mrp_clear(&hcfg); + + hcfg.comp = bus_cmp; + hcfg.hash = bus_hash; + hcfg.free = NULL; + + buses = mrp_htbl_create(&hcfg); + + return NULL; + } + else { + spec.ml = ml; + spec.address = address; + + return mrp_htbl_find(buses, find_bus_by_spec, &spec); + } +} + + +mrp_dbus_t *mrp_dbus_connect(mrp_mainloop_t *ml, const char *address, + mrp_dbus_err_t *errp) +{ + static struct DBusObjectPathVTable vtable = { + .message_function = dispatch_method + }; + + mrp_htbl_config_t hcfg; + mrp_dbus_t *dbus; + + if ((dbus = dbus_get(ml, address)) != NULL) + return mrp_dbus_ref(dbus); + + if ((dbus = mrp_allocz(sizeof(*dbus))) == NULL) + return NULL; + + mrp_list_init(&dbus->calls); + mrp_list_init(&dbus->name_trackers); + mrp_refcnt_init(&dbus->refcnt); + + dbus->ml = ml; + + + mrp_dbus_error_init(errp); + + /* + * connect to the bus + */ + + if (!strcmp(address, "system")) + dbus->conn = dbus_bus_get(DBUS_BUS_SYSTEM, errp); + else if (!strcmp(address, "session")) + dbus->conn = dbus_bus_get(DBUS_BUS_SESSION, errp); + else { + dbus->conn = dbus_connection_open_private(address, errp); + dbus->priv = TRUE; + + if (dbus->conn == NULL || !dbus_bus_register(dbus->conn, errp)) + goto fail; + } + + if (dbus->conn == NULL) + goto fail; + + dbus->address = mrp_strdup(address); + dbus->unique_name = dbus_bus_get_unique_name(dbus->conn); + + /* + * set up with mainloop + */ + + if (!mrp_dbus_setup_connection(ml, dbus->conn)) + goto fail; + + /* + * set up our message dispatchers and take our name on the bus + */ + + if (!dbus_connection_add_filter(dbus->conn, dispatch_signal, dbus, NULL)) { + dbus_set_error(errp, DBUS_ERROR_FAILED, + "Failed to set up signal dispatching."); + goto fail; + } + dbus->signal_filter = TRUE; + + if (!dbus_connection_register_fallback(dbus->conn, "/", &vtable, dbus)) { + dbus_set_error(errp, DBUS_ERROR_FAILED, + "Failed to set up method dispatching."); + goto fail; + } + dbus->register_fallback = TRUE; + + mrp_clear(&hcfg); + hcfg.comp = mrp_string_comp; + hcfg.hash = mrp_string_hash; + hcfg.free = handler_list_free_cb; + + if ((dbus->methods = mrp_htbl_create(&hcfg)) == NULL) { + dbus_set_error(errp, DBUS_ERROR_FAILED, + "Failed to create DBUS method table."); + goto fail; + } + + if ((dbus->signals = mrp_htbl_create(&hcfg)) == NULL) { + dbus_set_error(errp, DBUS_ERROR_FAILED, + "Failed to create DBUS signal table."); + goto fail; + } + + + /* + * install handler for NameOwnerChanged for tracking clients/peers + */ + + if (!mrp_dbus_add_signal_handler(dbus, DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH, + DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED, + name_owner_change_cb, NULL)) { + dbus_set_error(errp, DBUS_ERROR_FAILED, + "Failed to install NameOwnerChanged handler."); + goto fail; + } + + /* install a 'safe' filter to avoid receiving all name change signals */ + mrp_dbus_install_filter(dbus, + DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH, + DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED, + DBUS_ADMIN_SERVICE, NULL); + + mrp_list_init(&dbus->name_trackers); + dbus->call_id = 1; + + if (mrp_htbl_insert(buses, dbus->conn, dbus)) + return dbus; + + fail: + dbus_disconnect(dbus); + return NULL; +} + + +mrp_dbus_t *mrp_dbus_ref(mrp_dbus_t *dbus) +{ + return mrp_ref_obj(dbus, refcnt); +} + + +int mrp_dbus_unref(mrp_dbus_t *dbus) +{ + if (mrp_unref_obj(dbus, refcnt)) { + dbus_disconnect(dbus); + + return TRUE; + } + else + return FALSE; +} + + +int mrp_dbus_acquire_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_err_t *error) +{ + int flags, status; + + mrp_dbus_error_init(error); + + flags = DBUS_NAME_FLAG_REPLACE_EXISTING | DBUS_NAME_FLAG_DO_NOT_QUEUE; + status = dbus_bus_request_name(dbus->conn, name, flags, error); + + if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) + return TRUE; + else { + if (status == DBUS_REQUEST_NAME_REPLY_EXISTS) { + if (error) + dbus_error_free(error); + dbus_set_error(error, DBUS_ERROR_FAILED, "name already taken"); + } + return FALSE; + } +} + + +int mrp_dbus_release_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_err_t *error) +{ + mrp_dbus_error_init(error); + + if (dbus_bus_release_name(dbus->conn, name, error) != -1) + return TRUE; + else + return FALSE; +} + + +const char *mrp_dbus_get_unique_name(mrp_dbus_t *dbus) +{ + return dbus->unique_name; +} + + +static void name_owner_query_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *m, void *data) +{ + name_tracker_t *t = (name_tracker_t *)data; + DBusMessage *msg = m->msg; + const char *owner; + int state; + + if (t->cb != NULL) { /* tracker still active */ + t->qid = 0; + state = dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_RETURN; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &owner, + DBUS_TYPE_INVALID)) + owner = "<unknown>"; + + t->cb(dbus, t->name, state, owner, t->user_data); + } + else /* already requested to delete */ + mrp_free(t); +} + + +static int name_owner_change_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *m, void *data) +{ + const char *name, *prev, *next; + mrp_list_hook_t *p, *n; + name_tracker_t *t; + DBusMessage *msg; + + MRP_UNUSED(data); + + msg = m->msg; + + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL) + return FALSE; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &prev, + DBUS_TYPE_STRING, &next, + DBUS_TYPE_INVALID)) + return FALSE; + +#if 0 + /* + * Notes: XXX TODO + * In principle t->cb could call mrp_dbus_forget for some other D-BUS + * address than name. If that happened to be n (== p->hook.next) this + * would result in a crash or memory corruption in the next iteration + * of this loop (when handling n). We can easily get around this + * problem by + * + * 1. administering in mrp_dbus_t that we're handing a NameOwnerChange + * 2. checking for this in mrp_dbus_forget_name and if it is the case + * only marking the affected entry for deletion + * 3. removing entries marked for deletion in this loop (or just + * ignoring them and making another pass in the end removing any + * such entry). + */ +#endif + + mrp_list_foreach(&dbus->name_trackers, p, n) { + t = mrp_list_entry(p, name_tracker_t, hook); + + if (!strcmp(name, t->name)) + t->cb(dbus, name, next && *next, next, t->user_data); + } + + return TRUE; +} + + +int mrp_dbus_follow_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_name_cb_t cb, void *user_data) +{ + name_tracker_t *t; + + if ((t = mrp_allocz(sizeof(*t))) != NULL) { + if ((t->name = mrp_strdup(name)) != NULL) { + t->cb = cb; + t->user_data = user_data; + + if (mrp_dbus_install_filter(dbus, + DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH, + DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED, + name, NULL)) { + mrp_list_append(&dbus->name_trackers, &t->hook); + + t->qid = mrp_dbus_call(dbus, + DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH, + DBUS_ADMIN_SERVICE, "GetNameOwner", 5000, + name_owner_query_cb, t, + DBUS_TYPE_STRING, t->name, + DBUS_TYPE_INVALID); + return TRUE; + } + else { + mrp_free(t->name); + mrp_free(t); + } + } + } + + return FALSE; +} + + +int mrp_dbus_forget_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_name_cb_t cb, void *user_data) +{ + mrp_list_hook_t *p, *n; + name_tracker_t *t; + + mrp_dbus_remove_filter(dbus, + DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH, + DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED, + name, NULL); + + mrp_list_foreach(&dbus->name_trackers, p, n) { + t = mrp_list_entry(p, name_tracker_t, hook); + + if (t->cb == cb && t->user_data == user_data && !strcmp(t->name,name)) { + mrp_list_delete(&t->hook); + mrp_free(t->name); + + if (!t->qid) + mrp_free(t); + else { + t->cb = NULL; + t->user_data = NULL; + t->name = NULL; + } + + return TRUE; + } + } + + return FALSE; +} + + +static void purge_name_trackers(mrp_dbus_t *dbus) +{ + mrp_list_hook_t *p, *n; + name_tracker_t *t; + + mrp_list_foreach(&dbus->name_trackers, p, n) { + t = mrp_list_entry(p, name_tracker_t, hook); + + mrp_list_delete(p); + mrp_dbus_remove_filter(dbus, DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH, + DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED, + t->name, NULL); + mrp_free(t->name); + mrp_free(t); + } +} + + +static handler_t *handler_alloc(const char *sender, const char *path, + const char *interface, const char *member, + mrp_dbus_handler_t handler, void *user_data) +{ + handler_t *h; + + if ((h = mrp_allocz(sizeof(*h))) != NULL) { + h->sender = mrp_strdup(sender); + h->path = mrp_strdup(path); + h->interface = mrp_strdup(interface); + h->member = mrp_strdup(member); + + if ((path && !h->path) || !h->interface || !h->member) { + handler_free(h); + return NULL; + } + + h->handler = handler; + h->user_data = user_data; + + return h; + } + + return NULL; +} + + +static void handler_free(handler_t *h) +{ + if (h != NULL) { + mrp_free(h->sender); + mrp_free(h->path); + mrp_free(h->interface); + mrp_free(h->member); + + mrp_free(h); + } +} + + +static handler_list_t *handler_list_alloc(const char *member) +{ + handler_list_t *l; + + if ((l = mrp_allocz(sizeof(*l))) != NULL) { + if ((l->member = mrp_strdup(member)) != NULL) + mrp_list_init(&l->handlers); + else { + mrp_free(l); + l = NULL; + } + } + + return l; +} + + +static inline void handler_list_free(handler_list_t *l) +{ + mrp_list_hook_t *p, *n; + handler_t *h; + + mrp_list_foreach(&l->handlers, p, n) { + h = mrp_list_entry(p, handler_t, hook); + mrp_list_delete(p); + handler_free(h); + } + + mrp_free(l->member); + mrp_free(l); +} + + +static void handler_list_free_cb(void *key, void *entry) +{ + MRP_UNUSED(key); + + handler_list_free((handler_list_t *)entry); +} + + +static inline int handler_specificity(handler_t *h) +{ + int score = 0; + + if (h->path && *h->path) + score |= 0x4; + if (h->interface && *h->interface) + score |= 0x2; + if (h->member && *h->member) + score |= 0x1; + + return score; +} + + +static void handler_list_insert(handler_list_t *l, handler_t *handler) +{ + mrp_list_hook_t *p, *n; + handler_t *h; + int score; + + score = handler_specificity(handler); + + mrp_list_foreach(&l->handlers, p, n) { + h = mrp_list_entry(p, handler_t, hook); + + if (score >= handler_specificity(h)) { + mrp_list_append(h->hook.prev, &handler->hook); /* add before h */ + return; + } + } + + mrp_list_append(&l->handlers, &handler->hook); +} + + +static handler_t *handler_list_lookup(handler_list_t *l, const char *path, + const char *interface, const char *member, + mrp_dbus_handler_t handler, + void *user_data) +{ + mrp_list_hook_t *p, *n; + handler_t *h; + + mrp_list_foreach(&l->handlers, p, n) { + h = mrp_list_entry(p, handler_t, hook); + + if (h->handler == handler && user_data == h->user_data && + path && !strcmp(path, h->path) && + interface && !strcmp(interface, h->interface) && + member && !strcmp(member, h->member)) + return h; + } + + return NULL; +} + + +static handler_t *handler_list_find(handler_list_t *l, const char *path, + const char *interface, const char *member) +{ +#define MATCHES(h, field) (!*field || !*h->field || !strcmp(field, h->field)) + mrp_list_hook_t *p, *n; + handler_t *h; + + mrp_list_foreach(&l->handlers, p, n) { + h = mrp_list_entry(p, handler_t, hook); + + if (MATCHES(h, path) && MATCHES(h, interface) && MATCHES(h, member)) + return h; + } + + return NULL; +#undef MATCHES +} + + +int mrp_dbus_export_method(mrp_dbus_t *dbus, const char *path, + const char *interface, const char *member, + mrp_dbus_handler_t handler, void *user_data) +{ + handler_list_t *methods; + handler_t *m; + + if ((methods = mrp_htbl_lookup(dbus->methods, (void *)member)) == NULL) { + if ((methods = handler_list_alloc(member)) == NULL) + return FALSE; + + mrp_htbl_insert(dbus->methods, methods->member, methods); + } + + m = handler_alloc(NULL, path, interface, member, handler, user_data); + if (m != NULL) { + handler_list_insert(methods, m); + return TRUE; + } + else + return FALSE; +} + + +int mrp_dbus_remove_method(mrp_dbus_t *dbus, const char *path, + const char *interface, const char *member, + mrp_dbus_handler_t handler, void *user_data) +{ + handler_list_t *methods; + handler_t *m; + + if ((methods = mrp_htbl_lookup(dbus->methods, (void *)member)) == NULL) + return FALSE; + + m = handler_list_lookup(methods, path, interface, member, + handler, user_data); + if (m != NULL) { + mrp_list_delete(&m->hook); + handler_free(m); + + return TRUE; + } + else + return FALSE; +} + + +int mrp_dbus_add_signal_handler(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, mrp_dbus_handler_t handler, + void *user_data) +{ + handler_list_t *signals; + handler_t *s; + + if ((signals = mrp_htbl_lookup(dbus->signals, (void *)member)) == NULL) { + if ((signals = handler_list_alloc(member)) == NULL) + return FALSE; + + if (!mrp_htbl_insert(dbus->signals, signals->member, signals)) { + handler_list_free(signals); + return FALSE; + } + } + + s = handler_alloc(sender, path, interface, member, handler, user_data); + if (s != NULL) { + handler_list_insert(signals, s); + return TRUE; + } + else { + handler_free(s); + if (mrp_list_empty(&signals->handlers)) + mrp_htbl_remove(dbus->signals, signals->member, TRUE); + return FALSE; + } +} + + + +int mrp_dbus_del_signal_handler(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, mrp_dbus_handler_t handler, + void *user_data) +{ + handler_list_t *signals; + handler_t *s; + + MRP_UNUSED(sender); + + if ((signals = mrp_htbl_lookup(dbus->signals, (void *)member)) == NULL) + return FALSE; + + s = handler_list_lookup(signals, path, interface, member, + handler, user_data); + if (s != NULL) { + mrp_list_delete(&s->hook); + handler_free(s); + + if (mrp_list_empty(&signals->handlers)) + mrp_htbl_remove(dbus->signals, (void *)member, TRUE); + + return TRUE; + } + else + return FALSE; +} + + + +int mrp_dbus_subscribe_signal(mrp_dbus_t *dbus, + mrp_dbus_handler_t handler, void *user_data, + const char *sender, const char *path, + const char *interface, const char *member, ...) +{ + va_list ap; + int success; + + + if (mrp_dbus_add_signal_handler(dbus, sender, path, interface, member, + handler, user_data)) { + va_start(ap, member); + success = mrp_dbus_install_filterv(dbus, + sender, path, interface, member, ap); + va_end(ap); + + if (success) + return TRUE; + else + mrp_dbus_del_signal_handler(dbus, sender, path, interface, member, + handler, user_data); + } + + return FALSE; +} + + +int mrp_dbus_unsubscribe_signal(mrp_dbus_t *dbus, + mrp_dbus_handler_t handler, void *user_data, + const char *sender, const char *path, + const char *interface, const char *member, ...) +{ + va_list ap; + int status; + + status = mrp_dbus_del_signal_handler(dbus, sender, path, interface, member, + handler, user_data); + va_start(ap, member); + status &= mrp_dbus_remove_filterv(dbus, + sender, path, interface, member, ap); + va_end(ap); + + return status; +} + + +int mrp_dbus_install_filterv(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, va_list args) +{ +#define ADD_TAG(tag, value) do { \ + if (value != NULL) { \ + l = snprintf(p, n, "%s%s='%s'", p == filter ? "" : ",", \ + tag, value); \ + if (l >= n) \ + return FALSE; \ + n -= l; \ + p += l; \ + } \ + } while (0) + + va_list ap; + DBusError error; + char filter[1024], *p, argn[16], *val; + int n, l, i; + + p = filter; + n = sizeof(filter); + + ADD_TAG("type" , "signal"); + ADD_TAG("sender" , sender); + ADD_TAG("path" , path); + ADD_TAG("interface", interface); + ADD_TAG("member" , member); + + va_copy(ap, args); + i = 0; + while ((val = va_arg(ap, char *)) != NULL) { + snprintf(argn, sizeof(argn), "arg%d", i); + ADD_TAG(argn, val); + i++; + } + va_end(ap); + + dbus_error_init(&error); + dbus_bus_add_match(dbus->conn, filter, &error); + + if (dbus_error_is_set(&error)) { + mrp_log_error("Failed to install filter '%s' (error: %s).", filter, + mrp_dbus_errmsg(&error)); + dbus_error_free(&error); + + return FALSE; + } + else + return TRUE; + +} + + +int mrp_dbus_install_filter(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, ...) +{ + va_list ap; + int status; + + va_start(ap, member); + status = mrp_dbus_install_filterv(dbus, + sender, path, interface, member, ap); + va_end(ap); + + return status; +} + + +int mrp_dbus_remove_filterv(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, va_list args) +{ + va_list ap; + char filter[1024], *p, argn[16], *val; + int n, l, i; + + p = filter; + n = sizeof(filter); + + ADD_TAG("type" , "signal"); + ADD_TAG("sender" , sender); + ADD_TAG("path" , path); + ADD_TAG("interface", interface); + ADD_TAG("member" , member); + + va_copy(ap, args); + i = 0; + while ((val = va_arg(ap, char *)) != NULL) { + snprintf(argn, sizeof(argn), "arg%d", i); + ADD_TAG(argn, val); + i++; + } + va_end(ap); + + dbus_bus_remove_match(dbus->conn, filter, NULL); + return TRUE; +#undef ADD_TAG +} + + +int mrp_dbus_remove_filter(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, ...) +{ + va_list ap; + int status; + + va_start(ap, member); + status = mrp_dbus_remove_filterv(dbus, sender, path, interface, member, ap); + va_end(ap); + + return status; +} + + +static inline mrp_dbus_msg_t *create_message(DBusMessage *msg) +{ + mrp_dbus_msg_t *m; + + if (msg != NULL) { + if ((m = mrp_allocz(sizeof(*m))) != NULL) { + mrp_refcnt_init(&m->refcnt); + mrp_list_init(&m->iterators); + mrp_list_init(&m->arrays); + m->msg = dbus_message_ref(msg); + } + } + else + m = NULL; + + return m; +} + + +mrp_dbus_msg_t *mrp_dbus_msg_ref(mrp_dbus_msg_t *m) +{ + return mrp_ref_obj(m, refcnt); +} + + +static void rewind_message(mrp_dbus_msg_t *m) +{ + mrp_list_hook_t *p, *n; + msg_iter_t *it; + msg_array_t *a; + + mrp_list_foreach(&m->iterators, p, n) { + it = mrp_list_entry(p, typeof(*it), hook); + + mrp_list_delete(&it->hook); + mrp_free(it->peeked); + mrp_free(it); + } + + mrp_list_foreach(&m->arrays, p, n) { + a = mrp_list_entry(p, typeof(*a), hook); + + free_msg_array(a); + } +} + + +static void free_message(mrp_dbus_msg_t *m) +{ + mrp_list_hook_t *p, *n; + msg_iter_t *it; + msg_array_t *a; + + mrp_list_foreach(&m->iterators, p, n) { + it = mrp_list_entry(p, typeof(*it), hook); + + mrp_list_delete(&it->hook); + mrp_free(it->peeked); + mrp_free(it); + } + + mrp_list_foreach(&m->arrays, p, n) { + a = mrp_list_entry(p, typeof(*a), hook); + + free_msg_array(a); + } + + mrp_free(m); +} + + +int mrp_dbus_msg_unref(mrp_dbus_msg_t *m) +{ + DBusMessage *msg; + + if (mrp_unref_obj(m, refcnt)) { + msg = m->msg; + + free_message(m); + + if (msg != NULL) + dbus_message_unref(msg); + + return TRUE; + } + else + return FALSE; +} + + +static DBusHandlerResult dispatch_method(DBusConnection *c, + DBusMessage *msg, void *data) +{ +#define SAFESTR(str) (str ? str : "<none>") + const char *path = dbus_message_get_path(msg); + const char *interface = dbus_message_get_interface(msg); + const char *member = dbus_message_get_member(msg); + + mrp_dbus_t *dbus = (mrp_dbus_t *)data; + mrp_dbus_msg_t *m = NULL; + int r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + handler_list_t *l; + handler_t *h; + + MRP_UNUSED(c); + + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_METHOD_CALL || !member) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + mrp_debug("path='%s', interface='%s', member='%s')...", + SAFESTR(path), SAFESTR(interface), SAFESTR(member)); + + if ((l = mrp_htbl_lookup(dbus->methods, (void *)member)) != NULL) { + retry: + if ((h = handler_list_find(l, path, interface, member)) != NULL) { + if (m == NULL) + m = create_message(msg); + + if (m != NULL && h->handler(dbus, m, h->user_data)) + r = DBUS_HANDLER_RESULT_HANDLED; + + goto out; + } + } + else { + if ((l = mrp_htbl_lookup(dbus->methods, "")) != NULL) + goto retry; + } + + out: + mrp_dbus_msg_unref(m); + + if (r == DBUS_HANDLER_RESULT_NOT_YET_HANDLED) + mrp_debug("Unhandled method path=%s, %s.%s.", SAFESTR(path), + SAFESTR(interface), SAFESTR(member)); + + return r; +} + + +static DBusHandlerResult dispatch_signal(DBusConnection *c, + DBusMessage *msg, void *data) +{ +#define MATCHES(h, field) (!*field || !h->field || !*h->field || \ + !strcmp(field, h->field)) + + const char *path = dbus_message_get_path(msg); + const char *interface = dbus_message_get_interface(msg); + const char *member = dbus_message_get_member(msg); + + mrp_dbus_t *dbus = (mrp_dbus_t *)data; + mrp_dbus_msg_t *m = NULL; + mrp_list_hook_t *p, *n; + handler_list_t *l; + handler_t *h; + int retried = FALSE; + int handled = FALSE; + + MRP_UNUSED(c); + + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL || !member) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + mrp_debug("%s(path='%s', interface='%s', member='%s')...", + __FUNCTION__, + SAFESTR(path), SAFESTR(interface), SAFESTR(member)); + + if ((l = mrp_htbl_lookup(dbus->signals, (void *)member)) != NULL) { + retry: + mrp_list_foreach(&l->handlers, p, n) { + h = mrp_list_entry(p, handler_t, hook); + + if (MATCHES(h,path) && MATCHES(h,interface) && MATCHES(h,member)) { + if (m == NULL) + m = create_message(msg); + + if (m == NULL) + goto out; + + h->handler(dbus, m, h->user_data); + handled = TRUE; + + rewind_message(m); + } + } + } + + if (!retried) { + if ((l = mrp_htbl_lookup(dbus->signals, "")) != NULL) { + retried = TRUE; + goto retry; + } + } + + if (!handled) + mrp_debug("Unhandled signal path=%s, %s.%s.", SAFESTR(path), + SAFESTR(interface), SAFESTR(member)); + + out: + mrp_dbus_msg_unref(m); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +#undef MATCHES +#undef SAFESTR +} + + +static int append_args_inttype(DBusMessage *msg, int type, va_list ap) +{ + void *vptr; + void **aptr; + int atype, alen; + int r = TRUE; + + while (type != MRP_DBUS_TYPE_INVALID && r) { + switch (type) { + case MRP_DBUS_TYPE_BYTE: + case MRP_DBUS_TYPE_BOOLEAN: + case MRP_DBUS_TYPE_INT16: + case MRP_DBUS_TYPE_UINT16: + case MRP_DBUS_TYPE_INT32: + case MRP_DBUS_TYPE_UINT32: + case MRP_DBUS_TYPE_INT64: + case MRP_DBUS_TYPE_UINT64: + case MRP_DBUS_TYPE_DOUBLE: + case MRP_DBUS_TYPE_UNIX_FD: + vptr = va_arg(ap, void *); + r = dbus_message_append_args(msg, type, vptr, DBUS_TYPE_INVALID); + break; + + case MRP_DBUS_TYPE_STRING: + case MRP_DBUS_TYPE_OBJECT_PATH: + case MRP_DBUS_TYPE_SIGNATURE: + vptr = va_arg(ap, void *); + r = dbus_message_append_args(msg, type, &vptr, DBUS_TYPE_INVALID); + break; + + case MRP_DBUS_TYPE_ARRAY: + atype = va_arg(ap, int); + aptr = va_arg(ap, void **); + alen = va_arg(ap, int); + r = dbus_message_append_args(msg, DBUS_TYPE_ARRAY, + atype, &aptr, alen, DBUS_TYPE_INVALID); + break; + + default: + return FALSE; + } + + type = va_arg(ap, int); + } + + return r; +} + + +static void call_reply_cb(DBusPendingCall *pend, void *user_data) +{ + call_t *call = (call_t *)user_data; + DBusMessage *reply; + mrp_dbus_msg_t *m; + + reply = dbus_pending_call_steal_reply(pend); + m = create_message(reply); + + call->pend = NULL; + mrp_list_delete(&call->hook); + + call->cb(call->dbus, m, call->user_data); + + mrp_dbus_msg_unref(m); + dbus_message_unref(reply); + dbus_pending_call_unref(pend); + + call_free(call); +} + + +int32_t mrp_dbus_call(mrp_dbus_t *dbus, const char *dest, const char *path, + const char *interface, const char *member, int timeout, + mrp_dbus_reply_cb_t cb, void *user_data, int type, ...) +{ + va_list ap; + int32_t id; + call_t *call; + DBusMessage *msg; + DBusPendingCall *pend; + int success; + + call = NULL; + pend = NULL; + + msg = dbus_message_new_method_call(dest, path, interface, member); + + if (msg == NULL) + return 0; + + if (cb != NULL) { + if ((call = mrp_allocz(sizeof(*call))) != NULL) { + mrp_list_init(&call->hook); + + call->dbus = dbus; + call->id = dbus->call_id++; + call->cb = cb; + call->user_data = user_data; + + id = call->id; + } + else + goto fail; + } + else + id = dbus->call_id++; + + if (type == DBUS_TYPE_INVALID) + success = TRUE; + else { + va_start(ap, type); + success = append_args_inttype(msg, type, ap); + va_end(ap); + } + + if (!success) + goto fail; + + if (cb == NULL) { + dbus_message_set_no_reply(msg, TRUE); + if (!dbus_connection_send(dbus->conn, msg, NULL)) + goto fail; + } + else { + if (!dbus_connection_send_with_reply(dbus->conn, msg, &pend, timeout)) + goto fail; + + if (!dbus_pending_call_set_notify(pend, call_reply_cb, call, NULL)) + goto fail; + } + + if (cb != NULL) { + mrp_list_append(&dbus->calls, &call->hook); + call->pend = pend; + } + + dbus_message_unref(msg); + + return id; + + fail: + if (pend != NULL) + dbus_pending_call_unref(pend); + + if(msg != NULL) + dbus_message_unref(msg); + + call_free(call); + + return 0; +} + + +int32_t mrp_dbus_send(mrp_dbus_t *dbus, const char *dest, const char *path, + const char *interface, const char *member, int timeout, + mrp_dbus_reply_cb_t cb, void *user_data, + mrp_dbus_msg_t *m) +{ + int32_t id; + call_t *call; + DBusPendingCall *pend; + DBusMessage *msg; + int method; + + call = NULL; + pend = NULL; + msg = m->msg; + + if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_SIGNAL) { + if (cb != NULL) + goto fail; + else + method = FALSE; + } + else + method = TRUE; + + if (cb != NULL) { + if ((call = mrp_allocz(sizeof(*call))) != NULL) { + mrp_list_init(&call->hook); + + call->dbus = dbus; + call->id = dbus->call_id++; + call->cb = cb; + call->user_data = user_data; + + id = call->id; + } + else + goto fail; + } + else + id = dbus->call_id++; + + if (!dbus_message_set_destination(msg, dest)) + goto fail; + if (!dbus_message_set_path(msg, path)) + goto fail; + if (!dbus_message_set_interface(msg, interface)) + goto fail; + if (!dbus_message_set_member(msg, member)) + goto fail; + + if (cb == NULL) { + if (method) + dbus_message_set_no_reply(msg, TRUE); + if (!dbus_connection_send(dbus->conn, msg, NULL)) + goto fail; + } + else { + if (!dbus_connection_send_with_reply(dbus->conn, msg, &pend, timeout)) + goto fail; + + if (!dbus_pending_call_set_notify(pend, call_reply_cb, call, NULL)) + goto fail; + } + + if (cb != NULL) { + mrp_list_append(&dbus->calls, &call->hook); + call->pend = pend; + } + + return id; + + fail: + if (pend != NULL) + dbus_pending_call_unref(pend); + + call_free(call); + + return 0; +} + + +int mrp_dbus_send_msg(mrp_dbus_t *dbus, mrp_dbus_msg_t *m) +{ + return dbus_connection_send(dbus->conn, m->msg, NULL); +} + + +int mrp_dbus_call_cancel(mrp_dbus_t *dbus, int32_t id) +{ + mrp_list_hook_t *p, *n; + call_t *call; + + mrp_list_foreach(&dbus->calls, p, n) { + call = mrp_list_entry(p, call_t, hook); + + if (call->id == id) { + mrp_list_delete(p); + + dbus_pending_call_cancel(call->pend); + dbus_pending_call_unref(call->pend); + call->pend = NULL; + + call_free(call); + return TRUE; + } + } + + return FALSE; +} + + +int mrp_dbus_reply(mrp_dbus_t *dbus, mrp_dbus_msg_t *m, int type, ...) +{ + va_list ap; + DBusMessage *msg, *rpl; + int success; + + msg = m->msg; + rpl = dbus_message_new_method_return(msg); + + if (rpl == NULL) + return FALSE; + + if (type == DBUS_TYPE_INVALID) + success = TRUE; + else { + va_start(ap, type); + success = append_args_inttype(rpl, type, ap); + va_end(ap); + } + + if (!success) + goto fail; + + if (!dbus_connection_send(dbus->conn, rpl, NULL)) + goto fail; + + dbus_message_unref(rpl); + + return TRUE; + + fail: + if(rpl != NULL) + dbus_message_unref(rpl); + + return FALSE; +} + + +int mrp_dbus_reply_error(mrp_dbus_t *dbus, mrp_dbus_msg_t *m, + const char *errname, const char *errmsg, int type, ...) +{ + va_list ap; + DBusMessage *msg, *rpl; + int success; + + msg = m->msg; + rpl = dbus_message_new_error(msg, errname, errmsg); + + if (rpl == NULL) + return FALSE; + + if (type == DBUS_TYPE_INVALID) + success = TRUE; + else { + va_start(ap, type); + success = append_args_inttype(rpl, type, ap); + va_end(ap); + } + + if (!success) + goto fail; + + if (!dbus_connection_send(dbus->conn, rpl, NULL)) + goto fail; + + dbus_message_unref(rpl); + + return TRUE; + + fail: + if(rpl != NULL) + dbus_message_unref(rpl); + + return FALSE; +} + + +static void call_free(call_t *call) +{ + if (call != NULL) + mrp_free(call); +} + + +static void purge_calls(mrp_dbus_t *dbus) +{ + mrp_list_hook_t *p, *n; + call_t *call; + + mrp_list_foreach(&dbus->calls, p, n) { + call = mrp_list_entry(p, call_t, hook); + + mrp_list_delete(&call->hook); + + if (call->pend != NULL) + dbus_pending_call_unref(call->pend); + + mrp_free(call); + } +} + + +int mrp_dbus_signal(mrp_dbus_t *dbus, const char *dest, const char *path, + const char *interface, const char *member, int type, ...) +{ + va_list ap; + DBusMessage *msg; + int success; + + msg = dbus_message_new_signal(path, interface, member); + + if (msg == NULL) + return 0; + + if (type == DBUS_TYPE_INVALID) + success = TRUE; + else { + va_start(ap, type); + success = append_args_inttype(msg, type, ap); + va_end(ap); + } + + if (!success) + goto fail; + + if (dest && *dest && !dbus_message_set_destination(msg, dest)) + goto fail; + + if (!dbus_connection_send(dbus->conn, msg, NULL)) + goto fail; + + dbus_message_unref(msg); + + return TRUE; + + fail: + /* + * XXX TODO: Hmm... IIRC, libdbus unrefs messages upon failure. If it + * was really so, this would corrupt/crash. Check this from + * libdbus code. + */ + if(msg != NULL) + dbus_message_unref(msg); + + return 0; +} + + +mrp_dbus_msg_t *mrp_dbus_msg_method_call(mrp_dbus_t *bus, + const char *destination, + const char *path, + const char *interface, + const char *member) +{ + mrp_dbus_msg_t *m; + DBusMessage *msg; + + MRP_UNUSED(bus); + + msg = dbus_message_new_method_call(destination, path, interface, member); + + if (msg != NULL) { + m = create_message(msg); + dbus_message_unref(msg); + } + else + m = NULL; + + return m; +} + + +mrp_dbus_msg_t *mrp_dbus_msg_method_return(mrp_dbus_t *bus, + mrp_dbus_msg_t *m) +{ + mrp_dbus_msg_t *mr; + DBusMessage *msg; + + MRP_UNUSED(bus); + + msg = dbus_message_new_method_return(m->msg); + + if (msg != NULL) { + mr = create_message(msg); + dbus_message_unref(msg); + } + else + mr = NULL; + + return mr; +} + + +mrp_dbus_msg_t *mrp_dbus_msg_error(mrp_dbus_t *bus, mrp_dbus_msg_t *m, + mrp_dbus_err_t *err) +{ + mrp_dbus_msg_t *me; + DBusMessage *msg; + + MRP_UNUSED(bus); + + msg = dbus_message_new_error(m->msg, err->name, err->message); + + if (msg != NULL) { + me = create_message(msg); + dbus_message_unref(msg); + } + else + me = NULL; + + return me; +} + + +mrp_dbus_msg_t *mrp_dbus_msg_signal(mrp_dbus_t *bus, + const char *destination, + const char *path, + const char *interface, + const char *member) +{ + mrp_dbus_msg_t *m; + DBusMessage *msg; + + MRP_UNUSED(bus); + + msg = dbus_message_new_signal(path, interface, member); + + if (msg != NULL) { + if (!destination || dbus_message_set_destination(msg, destination)) { + m = create_message(msg); + dbus_message_unref(msg); + } + else { + dbus_message_unref(msg); + m = NULL; + } + } + else + m = NULL; + + return m; +} + + +mrp_dbus_msg_type_t mrp_dbus_msg_type(mrp_dbus_msg_t *m) +{ + return (mrp_dbus_msg_type_t)dbus_message_get_type(m->msg); +} + +#define WRAP_GETTER(type, what) \ + type mrp_dbus_msg_##what(mrp_dbus_msg_t *m) \ + { \ + return dbus_message_get_##what(m->msg); \ + } \ + struct __mrp_dbus_allow_trailing_semicolon + +WRAP_GETTER(const char *, path); +WRAP_GETTER(const char *, interface); +WRAP_GETTER(const char *, member); +WRAP_GETTER(const char *, destination); +WRAP_GETTER(const char *, sender); + +#undef WRAP_GETTER + + +static msg_iter_t *message_iterator(mrp_dbus_msg_t *m, int append) +{ + msg_iter_t *it; + + if (mrp_list_empty(&m->iterators)) { + if ((it = mrp_allocz(sizeof(*it))) != NULL) { + mrp_list_init(&it->hook); + mrp_list_append(&m->iterators, &it->hook); + + if (append) + dbus_message_iter_init_append(m->msg, &it->it); + else + dbus_message_iter_init(m->msg, &it->it); + } + } + else + it = mrp_list_entry(&m->iterators.next, typeof(*it), hook); + + return it; +} + + +msg_iter_t *current_iterator(mrp_dbus_msg_t *m) +{ + msg_iter_t *it; + + if (!mrp_list_empty(&m->iterators)) + it = mrp_list_entry(m->iterators.prev, typeof(*it), hook); + else + it = NULL; + + return it; +} + + +int mrp_dbus_msg_open_container(mrp_dbus_msg_t *m, char type, + const char *contents) +{ + msg_iter_t *it, *parent; + + if ((parent = current_iterator(m)) == NULL && + (parent = message_iterator(m, TRUE)) == NULL) + return FALSE; + + if ((it = mrp_allocz(sizeof(*it))) != NULL) { + mrp_list_init(&it->hook); + + if (dbus_message_iter_open_container(&parent->it, type, contents, + &it->it)) { + mrp_list_append(&m->iterators, &it->hook); + + return TRUE; + } + + mrp_free(it); + } + + return FALSE; +} + + +int mrp_dbus_msg_close_container(mrp_dbus_msg_t *m) +{ + msg_iter_t *it, *parent; + int r; + + it = current_iterator(m); + + if (it == NULL || it == message_iterator(m, FALSE)) + return FALSE; + + mrp_list_delete(&it->hook); + + if ((parent = current_iterator(m)) != NULL) + r = dbus_message_iter_close_container(&parent->it, &it->it); + else + r = FALSE; + + mrp_free(it); + + return r; +} + + +int mrp_dbus_msg_append_basic(mrp_dbus_msg_t *m, char type, void *valuep) +{ + msg_iter_t *it; + + if (!dbus_type_is_basic(type)) + return FALSE; + + if ((it = current_iterator(m)) != NULL || + (it = message_iterator(m, TRUE)) != NULL) { + if (type != MRP_DBUS_TYPE_STRING && + type != MRP_DBUS_TYPE_OBJECT_PATH && + type != MRP_DBUS_TYPE_SIGNATURE) + return dbus_message_iter_append_basic(&it->it, type, valuep); + else + return dbus_message_iter_append_basic(&it->it, type, &valuep); + } + else + return FALSE; +} + + +int mrp_dbus_msg_enter_container(mrp_dbus_msg_t *m, char type, + const char *contents) +{ + msg_iter_t *it, *parent; + char *signature; + + if ((parent = current_iterator(m)) == NULL && + (parent = message_iterator(m, FALSE)) == NULL) + return FALSE; + + if (dbus_message_iter_get_arg_type(&parent->it) != type) + return FALSE; + + if ((it = mrp_allocz(sizeof(*it))) != NULL) { + mrp_list_init(&it->hook); + mrp_list_append(&m->iterators, &it->hook); + + dbus_message_iter_recurse(&parent->it, &it->it); + + if (contents != NULL) { + /* XXX TODO: proper signature checking */ + signature = dbus_message_iter_get_signature(&it->it); + if (strcmp(contents, signature)) + mrp_log_error("*** %s(): signature mismath ('%s' != '%s')", + __FUNCTION__, contents, signature); + mrp_free(signature); + } + + dbus_message_iter_next(&parent->it); + + return TRUE; + } + + return FALSE; +} + + +int mrp_dbus_msg_exit_container(mrp_dbus_msg_t *m) +{ + msg_iter_t *it; + + if ((it = current_iterator(m)) == NULL || it == message_iterator(m, FALSE)) + return FALSE; + + mrp_list_delete(&it->hook); + + mrp_free(it->peeked); + mrp_free(it); + + return TRUE; +} + + +int mrp_dbus_msg_read_basic(mrp_dbus_msg_t *m, char type, void *valuep) +{ + msg_iter_t *it; + + if (!dbus_type_is_basic(type)) + return FALSE; + + if ((it = current_iterator(m)) != NULL || + (it = message_iterator(m, FALSE)) != NULL) { + if (dbus_message_iter_get_arg_type(&it->it) == type) { + dbus_message_iter_get_basic(&it->it, valuep); + dbus_message_iter_next(&it->it); + + return TRUE; + } + } + + return FALSE; +} + + +static void free_msg_array(msg_array_t *a) +{ + if (a == NULL) + return; + + mrp_list_delete(&a->hook); + mrp_free(a->items); + mrp_free(a); +} + + +int mrp_dbus_msg_read_array(mrp_dbus_msg_t *m, char type, + void **itemsp, size_t *nitemp) +{ + msg_iter_t *it; + msg_array_t *a; + DBusMessageIter sub; + void *items; + int nitem, atype; + + if (!dbus_type_is_basic(type)) + return FALSE; + + if ((it = current_iterator(m)) == NULL && + (it = message_iterator(m, FALSE)) == NULL) + return FALSE; + + if (dbus_message_iter_get_arg_type(&it->it) != DBUS_TYPE_ARRAY) + return FALSE; + + dbus_message_iter_recurse(&it->it, &sub); + atype = dbus_message_iter_get_arg_type(&sub); + + if (atype == MRP_DBUS_TYPE_INVALID) { + items = NULL; + nitem = 0; + + goto out; + } + + if (atype != type) + return FALSE; + + /* for fixed types, just use the libdbus function */ + if (type != MRP_DBUS_TYPE_STRING && type != MRP_DBUS_TYPE_OBJECT_PATH) { + nitem = -1; + items = NULL; + dbus_message_iter_get_fixed_array(&sub, (void *)&items, &nitem); + + if (nitem == -1) + return FALSE; + } + /* for string-like types, collect items into an implicitly freed array */ + else { + a = mrp_allocz(sizeof(*a)); + + if (a == NULL) + return FALSE; + + mrp_list_init(&a->hook); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + if (mrp_reallocz(a->items, a->nitem, a->nitem + 1) != NULL) { + dbus_message_iter_get_basic(&sub, a->items + a->nitem); + a->nitem++; + dbus_message_iter_next(&sub); + } + else { + free_msg_array(a); + return FALSE; + } + } + + mrp_list_append(&m->arrays, &a->hook); + + items = a->items; + nitem = a->nitem; + } + + out: + dbus_message_iter_next(&it->it); + + *itemsp = items; + *nitemp = (size_t)nitem; + + return TRUE; +} + + +mrp_dbus_type_t mrp_dbus_msg_arg_type(mrp_dbus_msg_t *m, const char **contents) +{ + msg_iter_t *it; + DBusMessageIter sub; + char type; + + if ((it = current_iterator(m)) != NULL || + (it = message_iterator(m, FALSE)) != NULL) { + type = dbus_message_iter_get_arg_type(&it->it); + + if (dbus_type_is_container(type)) { + mrp_free(it->peeked); + + if (contents != NULL) { + dbus_message_iter_recurse(&it->it, &sub); + it->peeked = dbus_message_iter_get_signature(&sub); + *contents = it->peeked; + } + } + + return type; + } + + return MRP_DBUS_TYPE_INVALID; +} diff --git a/src/common/dbus-libdbus.h b/src/common/dbus-libdbus.h new file mode 100644 index 0000000..8c729d9 --- /dev/null +++ b/src/common/dbus-libdbus.h @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2012, 2013, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_DBUS_LIBDBUS_H__ +#define __MURPHY_DBUS_LIBDBUS_H__ + +#include <murphy/common/mainloop.h> +#include <murphy/common/dbus-error.h> +#include <dbus/dbus.h> + +/** Type for a D-Bus (connection). */ +struct mrp_dbus_s; +typedef struct mrp_dbus_s mrp_dbus_t; + +/** Type for a D-Bus message. */ +struct mrp_dbus_msg_s; +typedef struct mrp_dbus_msg_s mrp_dbus_msg_t; + +/** Type for a D-Bus error. */ +typedef DBusError mrp_dbus_err_t; + +/** D-BUS method or signal callback type. */ +typedef int (*mrp_dbus_handler_t)(mrp_dbus_t *, mrp_dbus_msg_t *, void *); + +/** Create a new connection to the given bus. */ +mrp_dbus_t *mrp_dbus_connect(mrp_mainloop_t *ml, const char *address, + mrp_dbus_err_t *errp); +#define mrp_dbus_get mrp_dbus_connect + +/** Set up a DBusConnection with a mainloop. */ +int mrp_dbus_setup_connection(mrp_mainloop_t *ml, DBusConnection *conn); + +/** Increase the reference count of the given DBus (connection). */ +mrp_dbus_t *mrp_dbus_ref(mrp_dbus_t *dbus); + +/** Decrease the reference count of the given DBus (connection). */ +int mrp_dbus_unref(mrp_dbus_t *dbus); + +/** Acquire the given name on the given bus (connection). */ +int mrp_dbus_acquire_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_err_t *error); + +/** Release the given name on the given bus (connection). */ +int mrp_dbus_release_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_err_t *error); + +/** Type for a name tracking callback. */ +typedef void (*mrp_dbus_name_cb_t)(mrp_dbus_t *, const char *, int, + const char *, void *); +/** Start tracking the given name. */ +int mrp_dbus_follow_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_name_cb_t cb, void *user_data); +/** Stop tracking the given name. */ +int mrp_dbus_forget_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_name_cb_t cb, void *user_data); + +/** Export a method to the bus. */ +int mrp_dbus_export_method(mrp_dbus_t *dbus, const char *path, + const char *interface, const char *member, + mrp_dbus_handler_t handler, void *user_data); + +/** Remove an exported method. */ +int mrp_dbus_remove_method(mrp_dbus_t *dbus, const char *path, + const char *interface, const char *member, + mrp_dbus_handler_t handler, void *user_data); + +/** Install a filter and add a handler for the given signal on the bus. */ +MRP_NULLTERM int mrp_dbus_subscribe_signal(mrp_dbus_t *dbus, + mrp_dbus_handler_t handler, + void *user_data, + const char *sender, + const char *path, + const char *interface, + const char *member, ...); + +/** Remove the signal handler and filter for the given signal on the bus. */ +MRP_NULLTERM int mrp_dbus_unsubscribe_signal(mrp_dbus_t *dbus, + mrp_dbus_handler_t handler, + void *user_data, + const char *sender, + const char *path, + const char *interface, + const char *member, ...); + +/** Install a filter for the given message on the bus. */ +MRP_NULLTERM int mrp_dbus_install_filter(mrp_dbus_t *dbus, + const char *sender, + const char *path, + const char *interface, + const char *member, ...); + +/** Install a filter for the given message on the bus. */ +int mrp_dbus_install_filterv(mrp_dbus_t *dbus, + const char *sender, + const char *path, + const char *interface, + const char *member, + va_list ap); + +/** Remove a filter for the given message on the bus. */ +MRP_NULLTERM int mrp_dbus_remove_filter(mrp_dbus_t *dbus, + const char *sender, + const char *path, + const char *interface, + const char *member, ...); + +/** Remove a filter for the given message on the bus. */ +int mrp_dbus_remove_filterv(mrp_dbus_t *dbus, + const char *sender, + const char *path, + const char *interface, + const char *member, + va_list ap); + +/** Add a signal handler for the gvien signal on the bus. */ +int mrp_dbus_add_signal_handler(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, mrp_dbus_handler_t handler, + void *user_data); + +/** Remove the given signal handler for the given signal on the bus. */ +int mrp_dbus_del_signal_handler(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, mrp_dbus_handler_t handler, + void *user_data); + +/** Type of a method call reply callback. */ +typedef void (*mrp_dbus_reply_cb_t)(mrp_dbus_t *dbus, mrp_dbus_msg_t *reply, + void *user_data); + +/** Call the given method on the bus. */ +int32_t mrp_dbus_call(mrp_dbus_t *dbus, const char *dest, + const char *path, const char *interface, + const char *member, int timeout, + mrp_dbus_reply_cb_t cb, void *user_data, + int dbus_type, ...); + +/** Cancel an ongoing method call on the bus. */ +int mrp_dbus_call_cancel(mrp_dbus_t *dbus, int32_t id); + +/** Send a reply to the given method call on the bus. */ +int mrp_dbus_reply(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, int type, ...); + +/** Send an error reply to the given method call on the bus. */ +int mrp_dbus_reply_error(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, + const char *errname, const char *errmsg, + int type, ...); + +/** Emit the given signal on the bus. */ +int mrp_dbus_signal(mrp_dbus_t *dbus, const char *dest, const char *path, + const char *interface, const char *member, int type, ...); + +/** Send the given method call message on the bus. */ +int32_t mrp_dbus_send(mrp_dbus_t *dbus, const char *dest, const char *path, + const char *interface, const char *member, int timeout, + mrp_dbus_reply_cb_t cb, void *user_data, + mrp_dbus_msg_t *msg); + +/** Send the given message on the bus. */ +int mrp_dbus_send_msg(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg); + +/** Get our unique name on the bus. */ +const char *mrp_dbus_get_unique_name(mrp_dbus_t *dbus); + +/** Initialize the given error. */ +static inline mrp_dbus_err_t *mrp_dbus_error_init(mrp_dbus_err_t *err) +{ + if (err != NULL) + dbus_error_init(err); + + return err; +} + +/** Set the given error buffer up with the error name and message. */ +static inline mrp_dbus_err_t *mrp_dbus_error_set(mrp_dbus_err_t *err, + const char *name, + const char *message) +{ + dbus_set_error_const(err, name, message); + + return err; +} + + +/** Get the error message from the given bus error message. */ +static inline const char *mrp_dbus_errmsg(mrp_dbus_err_t *err) +{ + if (err && dbus_error_is_set(err)) + return err->message; + else + return "unknown DBUS error"; +} + + +/** Increase the reference count of a message. */ +mrp_dbus_msg_t *mrp_dbus_msg_ref(mrp_dbus_msg_t *m); + +/** Decrease the reference count of a message, freeing it if necessary. */ +int mrp_dbus_msg_unref(mrp_dbus_msg_t *m); + + +/** Create a new method call message. */ +mrp_dbus_msg_t *mrp_dbus_msg_method_call(mrp_dbus_t *bus, + const char *destination, + const char *path, + const char *interface, + const char *member); + +/** Create a new method return message. */ +mrp_dbus_msg_t *mrp_dbus_msg_method_return(mrp_dbus_t *bus, + mrp_dbus_msg_t *msg); + +/** Create a new error reply message. */ +mrp_dbus_msg_t *mrp_dbus_msg_error(mrp_dbus_t *bus, mrp_dbus_msg_t *msg, + mrp_dbus_err_t *err); + +/** Create a new signal message. */ +mrp_dbus_msg_t *mrp_dbus_msg_signal(mrp_dbus_t *bus, + const char *destination, + const char *path, + const char *interface, + const char *member); + +/** Bus message types. */ +typedef enum { +# define TYPE(type) MRP_DBUS_MESSAGE_TYPE_##type = DBUS_MESSAGE_TYPE_##type + TYPE(INVALID), + TYPE(METHOD_CALL), + TYPE(METHOD_RETURN), + TYPE(ERROR), + TYPE(SIGNAL) +# undef TYPE +} mrp_dbus_msg_type_t; + +/** Get the type of the given message. */ +mrp_dbus_msg_type_t mrp_dbus_msg_type(mrp_dbus_msg_t *msg); + +/** Message type checking convenience functions. */ +#define TYPE_CHECK_FUNCTION(type, TYPE) \ + static inline int mrp_dbus_msg_is_##type(mrp_dbus_msg_t *msg) \ + { \ + return mrp_dbus_msg_type(msg) == MRP_DBUS_MESSAGE_TYPE_##TYPE; \ + } \ + struct __mrp_dbus_allow_traling_semicolon + +TYPE_CHECK_FUNCTION(method_call , METHOD_CALL); +TYPE_CHECK_FUNCTION(method_return, METHOD_RETURN); +TYPE_CHECK_FUNCTION(error , ERROR); +TYPE_CHECK_FUNCTION(signal , SIGNAL); + +/** Message argument types. */ +typedef enum { +#define TYPE(t) MRP_DBUS_TYPE_##t = DBUS_TYPE_##t + TYPE(INVALID), + TYPE(BYTE), + TYPE(BOOLEAN), + TYPE(INT16), + TYPE(UINT16), + TYPE(INT32), + TYPE(UINT32), + TYPE(INT64), + TYPE(UINT64), + TYPE(DOUBLE), + TYPE(STRING), + TYPE(OBJECT_PATH), + TYPE(SIGNATURE), + TYPE(UNIX_FD), + TYPE(ARRAY), + TYPE(VARIANT), + TYPE(STRUCT), + TYPE(DICT_ENTRY), +#undef TYPE +} mrp_dbus_type_t; + +/** Message argument types as strings. */ +#define MRP_DBUS_TYPE_BYTE_AS_STRING DBUS_TYPE_BYTE_AS_STRING +#define MRP_DBUS_TYPE_BOOLEAN_AS_STRING DBUS_TYPE_BOOLEAN_AS_STRING +#define MRP_DBUS_TYPE_INT16_AS_STRING DBUS_TYPE_INT16_AS_STRING +#define MRP_DBUS_TYPE_UINT16_AS_STRING DBUS_TYPE_UINT16_AS_STRING +#define MRP_DBUS_TYPE_INT32_AS_STRING DBUS_TYPE_INT32_AS_STRING +#define MRP_DBUS_TYPE_UINT32_AS_STRING DBUS_TYPE_UINT32_AS_STRING +#define MRP_DBUS_TYPE_INT64_AS_STRING DBUS_TYPE_INT64_AS_STRING +#define MRP_DBUS_TYPE_UINT64_AS_STRING DBUS_TYPE_UINT64_AS_STRING +#define MRP_DBUS_TYPE_DOUBLE_AS_STRING DBUS_TYPE_DOUBLE_AS_STRING +#define MRP_DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_STRING_AS_STRING +#define MRP_DBUS_TYPE_OBJECT_PATH_AS_STRING DBUS_TYPE_OBJECT_PATH_AS_STRING +#define MRP_DBUS_TYPE_SIGNATURE_AS_STRING DBUS_TYPE_SIGNATURE_AS_STRING +#define MRP_DBUS_TYPE_UNIX_FD_AS_STRING DBUS_TYPE_UNIX_FD_AS_STRING +#define MRP_DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_ARRAY_AS_STRING +#define MRP_DBUS_TYPE_VARIANT_AS_STRING DBUS_TYPE_VARIANT_AS_STRING +#define MRP_DBUS_TYPE_STRUCT_AS_STRING DBUS_TYPE_STRUCT_AS_STRING +#define MRP_DBUS_TYPE_DICT_ENTRY_AS_STRING DBUS_TYPE_DICT_ENTRY_AS_STRING + +/** Get the path of the given message. */ +const char *mrp_dbus_msg_path(mrp_dbus_msg_t *msg); + +/** Get the interface of the given message. */ +const char *mrp_dbus_msg_interface(mrp_dbus_msg_t *msg); + +/** Get the member of the given message. */ +const char *mrp_dbus_msg_member(mrp_dbus_msg_t *msg); + +/** Get the destination of the given message. */ +const char *mrp_dbus_msg_destination(mrp_dbus_msg_t *msg); + +/** Get the sender of the given message. */ +const char *mrp_dbus_msg_sender(mrp_dbus_msg_t *msg); + +/** Open a new container of the given type and cotained types. */ +int mrp_dbus_msg_open_container(mrp_dbus_msg_t *m, char type, + const char *contents); + +/** Close the current container. */ +int mrp_dbus_msg_close_container(mrp_dbus_msg_t *m); + +/** Append an argument of a basic type to the given message. */ +int mrp_dbus_msg_append_basic(mrp_dbus_msg_t *m, char type, void *valuep); + +/** Get the type of the current message argument. */ +mrp_dbus_type_t mrp_dbus_msg_arg_type(mrp_dbus_msg_t *m, const char **contents); + +/** Open the current container (of the given type and contents) for reading. */ +int mrp_dbus_msg_enter_container(mrp_dbus_msg_t *msg, char type, + const char *contents); + +/** Exit from the container being currently read. */ +int mrp_dbus_msg_exit_container(mrp_dbus_msg_t *m); + +/** Read the next argument (of basic type) from the given message. */ +int mrp_dbus_msg_read_basic(mrp_dbus_msg_t *m, char type, void *valuep); + +/** Read the next array of one of the basic types. */ +int mrp_dbus_msg_read_array(mrp_dbus_msg_t *msg, char type, + void **itemsp, size_t *nitemp); + +#endif /* __MURPHY_DBUS_LIBDBUS_H__ */ diff --git a/src/common/dbus-sdbus-glue.c b/src/common/dbus-sdbus-glue.c new file mode 100644 index 0000000..a2b98c6 --- /dev/null +++ b/src/common/dbus-sdbus-glue.c @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2012, 2013, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/log.h> +#include <murphy/common/mainloop.h> + +#include <murphy/common/dbus-sdbus.h> + +#define USEC_TO_MSEC(usec) ((unsigned int)((usec) / 1000)) +#define MSEC_TO_USEC(msec) ((uint64_t)(msec) * 1000) + +typedef struct { + sd_bus *bus; + mrp_mainloop_t *ml; + mrp_subloop_t *sl; + int events; +} bus_glue_t; + + +static int bus_prepare(void *user_data) +{ + MRP_UNUSED(user_data); + + return FALSE; +} + + +static int bus_query(void *user_data, struct pollfd *fds, int nfd, int *timeout) +{ + bus_glue_t *b = (bus_glue_t *)user_data; + uint64_t usec; + + if (nfd > 0) { + fds[0].fd = sd_bus_get_fd(b->bus); + fds[0].events = sd_bus_get_events(b->bus) | POLLIN | POLLHUP; + fds[0].revents = 0; + + if (sd_bus_get_timeout(b->bus, &usec) < 0) + *timeout = -1; + else + *timeout = USEC_TO_MSEC(usec); + + mrp_debug("fd: %d, events: 0x%x, timeout: %u", fds[0].fd, + fds[0].events, *timeout); + } + + return 1; +} + + +static int bus_check(void *user_data, struct pollfd *fds, int nfd) +{ + bus_glue_t *b = (bus_glue_t *)user_data; + + if (nfd > 0) { + b->events = fds[0].revents; + + if (b->events != 0) + return TRUE; + } + else + b->events = 0; + + return FALSE; +} + + +static void bus_dispatch(void *user_data) +{ + bus_glue_t *b = (bus_glue_t *)user_data; + + mrp_debug("dispatching events 0x%x to sd_bus %p", b->events, b->bus); + + if (b->events & MRP_IO_EVENT_HUP) + mrp_debug("sd_bus peer has closed the connection"); + + while (sd_bus_process(b->bus, NULL) > 0) + sd_bus_flush(b->bus); + + mrp_debug("done dispatching"); +} + + +int mrp_dbus_setup_with_mainloop(mrp_mainloop_t *ml, sd_bus *bus) +{ + static mrp_subloop_ops_t bus_ops = { + .prepare = bus_prepare, + .query = bus_query, + .check = bus_check, + .dispatch = bus_dispatch + }; + + bus_glue_t *b; + + + if ((b = mrp_allocz(sizeof(*b))) != NULL) { + /* XXX TODO: Hmm... is this really needed ? */ + while (sd_bus_process(bus, NULL) > 0) + sd_bus_flush(bus); + + b->bus = bus; + b->ml = ml; + b->sl = mrp_add_subloop(ml, &bus_ops, b); + + if (b->sl != NULL) + return TRUE; + else + mrp_free(b); + } + + return FALSE; +} diff --git a/src/common/dbus-sdbus-transport.c b/src/common/dbus-sdbus-transport.c new file mode 100644 index 0000000..3b62ac3 --- /dev/null +++ b/src/common/dbus-sdbus-transport.c @@ -0,0 +1,1782 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/list.h> +#include <murphy/common/log.h> +#include <murphy/common/msg.h> +#include <murphy/common/transport.h> +#include <murphy/common/dbus-sdbus.h> +#include <murphy/common/dbus-transport.h> + +#define DBUS "dbus" +#define DBUSL 4 + +#define TRANSPORT_PATH "/murphy/transport" +#define TRANSPORT_INTERFACE "Murphy.Transport" +#define TRANSPORT_MESSAGE "DeliverMessage" +#define TRANSPORT_DATA "DeliverData" +#define TRANSPORT_RAW "DeliverRaw" +#define TRANSPORT_METHOD "DeliverMessage" + +#define ANY_ADDRESS "any" + +typedef struct { + MRP_TRANSPORT_PUBLIC_FIELDS; /* common transport fields */ + mrp_dbus_t *dbus; /* D-BUS connection */ + int bound : 1; /* whether bound to an address */ + int peer_resolved : 1; /* connected and peer name known */ + mrp_dbusaddr_t local; /* address we're bound to */ + mrp_dbusaddr_t remote; /* address we're connected to */ +} dbus_t; + + +static uint32_t nauto; /* for autobinding */ + + +static int dbus_msg_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data); +static int dbus_data_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data); +static int dbus_raw_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data); + +static void peer_state_cb(mrp_dbus_t *dbus, const char *name, int up, + const char *owner, void *user_data); + +static mrp_dbus_msg_t *msg_encode(mrp_dbus_t *dbus, const char *destination, + const char *path, const char *interface, + const char *member, const char *sender_id, + mrp_msg_t *msg); +static mrp_msg_t *msg_decode(mrp_dbus_msg_t *msg, const char **sender_id); + +static mrp_dbus_msg_t *data_encode(mrp_dbus_t *dbus, const char *destination, + const char *path, const char *interface, + const char *member, const char *sender_id, + void *data, uint16_t tag); +static void *data_decode(mrp_dbus_msg_t *msg, uint16_t *tag, + const char **sender_id); + +static mrp_dbus_msg_t *raw_encode(mrp_dbus_t *dbus, const char *destination, + const char *path, const char *interface, + const char *member, const char *sender_id, + void *data, size_t size); +static void *raw_decode(mrp_dbus_msg_t *msg, size_t *sizep, + const char **sender_id); + + +static socklen_t parse_address(const char *str, mrp_dbusaddr_t *addr, + socklen_t size) +{ + const char *p, *e; + char *q; + size_t l, n; + + if (size < sizeof(*addr)) { + errno = EINVAL; + return FALSE; + } + + if (strncmp(str, DBUS":", DBUSL + 1)) + return 0; + else + str += DBUSL + 1; + + /* + * The format of the address is + * dbus:[bus-address]@address/path + * eg. + * dbus:[session]@:1.33/client1, or + * dbus:[unix:abstract=/tmp/dbus-Xx2Kpi...a572]@:1.33/client1 + */ + + p = str; + q = addr->db_fqa; + l = sizeof(addr->db_fqa); + + /* get bus address */ + if (*p != '[') { + errno = EINVAL; + return 0; + } + else + p++; + + e = strchr(p, ']'); + + if (e == NULL) { + errno = EINVAL; + return 0; + } + + n = e - p; + if (n >= l) { + errno = ENAMETOOLONG; + return 0; + } + + /* save bus address */ + strncpy(q, p, n); + q[n] = '\0'; + addr->db_bus = q; + + q += n + 1; + l -= n + 1; + p = e + 1; + + /* get (local or remote) address on bus */ + if (*p != '@') + addr->db_addr = ANY_ADDRESS; + else { + p++; + e = strchr(p, '/'); + + if (e == NULL) { + errno = EINVAL; + return 0; + } + + n = e - p; + if (n >= l) { + errno = ENAMETOOLONG; + return 0; + } + + /* save address on bus */ + strncpy(q, p, n); + q[n] = '\0'; + addr->db_addr = q; + + q += n + 1; + l -= n + 1; + p = e; + } + + /* get object path */ + if (*p != '/') { + errno = EINVAL; + return 0; + } + + n = snprintf(q, l, "%s%s", TRANSPORT_PATH, p); + if (n >= l) { + errno = ENAMETOOLONG; + return 0; + } + + addr->db_path = q; + addr->db_family = MRP_AF_DBUS; + + return sizeof(*addr); +} + + +static mrp_dbusaddr_t *copy_address(mrp_dbusaddr_t *dst, mrp_dbusaddr_t *src) +{ + char *p, *q; + size_t l, n; + + dst->db_family = src->db_family; + + /* copy bus address */ + p = src->db_bus; + q = dst->db_fqa; + l = sizeof(dst->db_fqa); + + n = strlen(p); + if (l < n + 1) { + errno = EOVERFLOW; + return NULL; + } + + dst->db_bus = q; + memcpy(q, p, n + 1); + q += n + 1; + l -= n + 1; + + /* copy address */ + p = src->db_addr; + n = strlen(p); + if (l < n + 1) { + errno = EOVERFLOW; + return NULL; + } + + dst->db_addr = q; + memcpy(q, p, n + 1); + q += n + 1; + l -= n + 1; + + /* copy path */ + p = src->db_path; + n = strlen(p); + if (l < n + 1) { + errno = EOVERFLOW; + return NULL; + } + + dst->db_path = q; + memcpy(q, p, n + 1); + q += n + 1; + l -= n + 1; + + return dst; +} + + +static inline int check_address(mrp_sockaddr_t *addr, socklen_t addrlen) +{ + mrp_dbusaddr_t *a = (mrp_dbusaddr_t *)addr; + + return (a && a->db_family == MRP_AF_DBUS && addrlen == sizeof(*a)); +} + + +static size_t peer_address(mrp_sockaddr_t *addrp, const char *sender, + const char *path) +{ + mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp; + const char *p; + char *q; + int l, n; + + q = addr->db_fqa; + l = sizeof(addr->db_fqa); + p = ANY_ADDRESS; + n = 3; + + addr->db_bus = q; + memcpy(q, p, n + 1); + q += n + 1; + l -= n + 1; + + addr->db_addr = q; + p = sender; + n = strlen(sender); + + if (l < n + 1) + return 0; + + memcpy(q, p, n + 1); + q += n + 1; + l -= n + 1; + + addr->db_path = q; + p = path; + n = strlen(p); + + if (l < n + 1) + return 0; + + memcpy(q, p, n + 1); + + return sizeof(addrp); +} + + +static socklen_t dbus_resolve(const char *str, mrp_sockaddr_t *addr, + socklen_t size, const char **typep) +{ + socklen_t len; + + len = parse_address(str, (mrp_dbusaddr_t *)addr, size); + + if (len > 0) { + if (typep != NULL) + *typep = DBUS; + } + + return len; +} + + +static int dbus_open(mrp_transport_t *mt) +{ + MRP_UNUSED(mt); + + return TRUE; +} + + +static int dbus_createfrom(mrp_transport_t *mt, void *conn) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbus_t *dbus = (mrp_dbus_t *)conn; + + t->dbus = mrp_dbus_ref(dbus); + + if (t->dbus != NULL) + return TRUE; + else + return FALSE; +} + + +static int dbus_bind(mrp_transport_t *mt, mrp_sockaddr_t *addrp, + socklen_t addrlen) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbus_t *dbus = NULL; + mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp; + int (*cb)(mrp_dbus_t *, mrp_dbus_msg_t *, void *); + const char *method; + + if (t->bound) { + errno = EINVAL; + goto fail; + } + + if (!check_address(addrp, addrlen)) { + errno = EINVAL; + goto fail; + } + + if (t->dbus == NULL) { + dbus = mrp_dbus_connect(t->ml, addr->db_bus, NULL); + + if (dbus == NULL) { + errno = ECONNRESET; + goto fail; + } + else { + t->dbus = dbus; + + if (addr->db_addr != NULL && strcmp(addr->db_addr, ANY_ADDRESS)) { + if (!mrp_dbus_acquire_name(t->dbus, addr->db_addr, NULL)) { + errno = EADDRINUSE; /* XXX TODO, should check error... */ + goto fail; + } + } + } + } + else { + /* XXX TODO: should check given address against address of the bus */ + } + + copy_address(&t->local, addr); + + switch (t->mode) { + case MRP_TRANSPORT_MODE_DATA: + method = TRANSPORT_DATA; + cb = dbus_data_cb; + break; + case MRP_TRANSPORT_MODE_RAW: + method = TRANSPORT_RAW; + cb = dbus_raw_cb; + break; + case MRP_TRANSPORT_MODE_MSG: + method = TRANSPORT_MESSAGE; + cb = dbus_msg_cb; + break; + default: + errno = EPROTOTYPE; + goto fail; + } + + if (!mrp_dbus_export_method(t->dbus, addr->db_path, TRANSPORT_INTERFACE, + method, cb, t)) { + errno = EIO; + goto fail; + } + else { + t->bound = TRUE; + return TRUE; + } + + fail: + if (dbus != NULL) { + mrp_dbus_unref(dbus); + t->dbus = NULL; + } + + return FALSE; +} + + +static int dbus_autobind(mrp_transport_t *mt, mrp_sockaddr_t *addrp) +{ + mrp_dbusaddr_t *a = (mrp_dbusaddr_t *)addrp; + char astr[MRP_SOCKADDR_SIZE]; + mrp_sockaddr_t addr; + socklen_t alen; + + snprintf(astr, sizeof(astr), "dbus:[%s]/auto/%u", a->db_bus, nauto++); + + alen = dbus_resolve(astr, &addr, sizeof(addr), NULL); + + if (alen > 0) + return dbus_bind(mt, &addr, alen); + else + return FALSE; +} + + +static void dbus_close(mrp_transport_t *mt) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbusaddr_t *addr; + const char *method; + int (*cb)(mrp_dbus_t *, mrp_dbus_msg_t *, void *); + + if (t->bound) { + switch (t->mode) { + case MRP_TRANSPORT_MODE_DATA: + method = TRANSPORT_DATA; + cb = dbus_data_cb; + break; + case MRP_TRANSPORT_MODE_RAW: + method = TRANSPORT_RAW; + cb = dbus_raw_cb; + break; + default: + case MRP_TRANSPORT_MODE_MSG: + method = TRANSPORT_MESSAGE; + cb = dbus_msg_cb; + } + + addr = (mrp_dbusaddr_t *)&t->local; + mrp_dbus_remove_method(t->dbus, addr->db_path, TRANSPORT_INTERFACE, + method, cb, t); + } + + if (t->connected && t->remote.db_addr != NULL) + mrp_dbus_forget_name(t->dbus, t->remote.db_addr, peer_state_cb, t); + + mrp_dbus_unref(t->dbus); + t->dbus = NULL; +} + + +static int dbus_msg_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *dmsg, void *user_data) +{ + mrp_transport_t *mt = (mrp_transport_t *)user_data; + dbus_t *t = (dbus_t *)mt; + mrp_sockaddr_t addr; + socklen_t alen; + const char *sender, *sender_path; + mrp_msg_t *msg; + + MRP_UNUSED(dbus); + + msg = msg_decode(dmsg, &sender_path); + + if (msg != NULL) { + sender = mrp_dbus_msg_sender(dmsg); + + if (mt->connected) { + if (!t->peer_resolved || !strcmp(t->remote.db_addr, sender)) + MRP_TRANSPORT_BUSY(mt, { + mt->evt.recvmsg(mt, msg, mt->user_data); + }); + } + else { + peer_address(&addr, sender, sender_path); + alen = sizeof(addr); + + MRP_TRANSPORT_BUSY(mt, { + mt->evt.recvmsgfrom(mt, msg, &addr, alen, mt->user_data); + }); + } + + mrp_msg_unref(msg); + + mt->check_destroy(mt); + } + else { + mrp_log_error("Failed to decode message."); + } + + return TRUE; +} + + +static int dbus_data_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *dmsg, void *user_data) +{ + mrp_transport_t *mt = (mrp_transport_t *)user_data; + dbus_t *t = (dbus_t *)mt; + mrp_sockaddr_t addr; + socklen_t alen; + const char *sender, *sender_path; + uint16_t tag; + void *decoded; + + MRP_UNUSED(dbus); + + decoded = data_decode(dmsg, &tag, &sender_path); + + if (decoded != NULL) { + sender = mrp_dbus_msg_sender(dmsg); + + if (mt->connected) { + if (!t->peer_resolved || !strcmp(t->remote.db_addr, sender)) + MRP_TRANSPORT_BUSY(mt, { + mt->evt.recvdata(mt, decoded, tag, mt->user_data); + }); + } + else { + peer_address(&addr, sender, sender_path); + alen = sizeof(addr); + + MRP_TRANSPORT_BUSY(mt, { + mt->evt.recvdatafrom(mt, decoded, tag, &addr, alen, + mt->user_data); + }); + } + + mt->check_destroy(mt); + } + else { + mrp_log_error("Failed to decode custom data message."); + } + + return TRUE; +} + + +static int dbus_raw_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *dmsg, void *user_data) +{ + mrp_transport_t *mt = (mrp_transport_t *)user_data; + dbus_t *t = (dbus_t *)mt; + mrp_sockaddr_t addr; + socklen_t alen; + const char *sender, *sender_path; + void *data; + size_t size; + + MRP_UNUSED(dbus); + + data = raw_decode(dmsg, &size, &sender_path); + + if (data != NULL) { + sender = mrp_dbus_msg_sender(dmsg); + + if (mt->connected) { + if (!t->peer_resolved || !strcmp(t->remote.db_addr, sender)) + MRP_TRANSPORT_BUSY(mt, { + mt->evt.recvraw(mt, data, size, mt->user_data); + }); + } + else { + peer_address(&addr, sender, sender_path); + alen = sizeof(addr); + + MRP_TRANSPORT_BUSY(mt, { + mt->evt.recvrawfrom(mt, data, size, &addr, alen, + mt->user_data); + }); + } + + mt->check_destroy(mt); + } + else { + mrp_log_error("Failed to decode raw message."); + } + + return TRUE; +} + + +static void peer_state_cb(mrp_dbus_t *dbus, const char *name, int up, + const char *owner, void *user_data) +{ + dbus_t *t = (dbus_t *)user_data; + mrp_sockaddr_t addr; + + MRP_UNUSED(dbus); + MRP_UNUSED(name); + + if (up) { + peer_address(&addr, owner, t->remote.db_path); + copy_address(&t->remote, (mrp_dbusaddr_t *)&addr); + t->peer_resolved = TRUE; + } + else { + /* + * XXX TODO: + * It would be really tempting here to call + * mt->evt.closed(mt, ECONNRESET, mt->user_data) + * to notify the user about the fact our peer went down. + * However, that would not be in line with the other + * transports which call the closed event handler only + * upon foricble transport closes upon errors. + * + * The transport interface abstraction (especially the + * available set of events) anyway needs some eyeballing, + * so the right thing to do might be to define a new event + * for disconnection and call the handler for that here... + */ + } + +} + + +static int dbus_connect(mrp_transport_t *mt, mrp_sockaddr_t *addrp, + socklen_t addrlen) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp; + + if (!check_address(addrp, addrlen)) { + errno = EINVAL; + return FALSE; + } + + if (t->dbus == NULL) { + t->dbus = mrp_dbus_connect(t->ml, addr->db_bus, NULL); + + if (t->dbus == NULL) { + errno = ECONNRESET; + return FALSE; + } + } + else { + /* XXX TODO: check given address against address of the bus */ + } + + if (!t->bound) + if (!dbus_autobind(mt, addrp)) + return FALSE; + + if (mrp_dbus_follow_name(t->dbus, addr->db_addr, peer_state_cb, t)) { + copy_address(&t->remote, addr); + + return TRUE; + } + else + return FALSE; +} + + +static int dbus_disconnect(mrp_transport_t *mt) +{ + dbus_t *t = (dbus_t *)mt; + + if (t->connected && t->remote.db_addr != NULL) { + mrp_dbus_forget_name(t->dbus, t->remote.db_addr, peer_state_cb, t); + mrp_clear(&t->remote); + t->peer_resolved = FALSE; + } + + return TRUE; +} + + +static int dbus_sendmsgto(mrp_transport_t *mt, mrp_msg_t *msg, + mrp_sockaddr_t *addrp, socklen_t addrlen) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp; + mrp_dbus_msg_t *m; + int success; + + if (check_address(addrp, addrlen)) { + if (t->dbus == NULL && !dbus_autobind(mt, addrp)) + return FALSE; + + m = msg_encode(t->dbus, addr->db_addr, addr->db_path, + TRANSPORT_INTERFACE, TRANSPORT_MESSAGE, + t->local.db_path, msg); + + if (m != NULL) { + if (mrp_dbus_send_msg(t->dbus, m)) + success = TRUE; + else { + errno = ECOMM; + success = FALSE; + } + + mrp_dbus_msg_unref(m); + } + else + success = FALSE; + } + else { + errno = EINVAL; + success = FALSE; + } + + return success; +} + + +static int dbus_sendmsg(mrp_transport_t *mt, mrp_msg_t *msg) +{ + dbus_t *t = (dbus_t *)mt; + mrp_sockaddr_t *addr = (mrp_sockaddr_t *)&t->remote; + socklen_t alen = sizeof(t->remote); + + return dbus_sendmsgto(mt, msg, addr, alen); +} + + +static int dbus_sendrawto(mrp_transport_t *mt, void *data, size_t size, + mrp_sockaddr_t *addrp, socklen_t addrlen) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp; + mrp_dbus_msg_t *m; + int success; + + + MRP_UNUSED(mt); + MRP_UNUSED(data); + MRP_UNUSED(size); + MRP_UNUSED(addr); + MRP_UNUSED(addrlen); + + if (check_address(addrp, addrlen)) { + if (t->dbus == NULL && !dbus_autobind(mt, addrp)) + return FALSE; + + m = raw_encode(t->dbus, addr->db_addr, addr->db_path, + TRANSPORT_INTERFACE, TRANSPORT_RAW, + t->local.db_path, data, size); + + if (m != NULL) { + if (mrp_dbus_send_msg(t->dbus, m)) + success = TRUE; + else { + errno = ECOMM; + success = FALSE; + } + + mrp_dbus_msg_unref(m); + } + else + success = FALSE; + } + else { + errno = EINVAL; + success = FALSE; + } + + return success; +} + + +static int dbus_sendraw(mrp_transport_t *mt, void *data, size_t size) +{ + dbus_t *t = (dbus_t *)mt; + mrp_sockaddr_t *addr = (mrp_sockaddr_t *)&t->remote; + socklen_t alen = sizeof(t->remote); + + return dbus_sendrawto(mt, data, size, addr, alen); +} + + +static int dbus_senddatato(mrp_transport_t *mt, void *data, uint16_t tag, + mrp_sockaddr_t *addrp, socklen_t addrlen) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp; + mrp_dbus_msg_t *m; + int success; + + if (check_address(addrp, addrlen)) { + if (t->dbus == NULL && !dbus_autobind(mt, addrp)) + return FALSE; + + m = data_encode(t->dbus, addr->db_addr, addr->db_path, + TRANSPORT_INTERFACE, TRANSPORT_DATA, + t->local.db_path, data, tag); + + if (m != NULL) { + if (mrp_dbus_send_msg(t->dbus, m)) + success = TRUE; + else { + errno = ECOMM; + success = FALSE; + } + + mrp_dbus_msg_unref(m); + } + else + success = FALSE; + } + else { + errno = EINVAL; + success = FALSE; + } + + return success; +} + + +static int dbus_senddata(mrp_transport_t *mt, void *data, uint16_t tag) +{ + dbus_t *t = (dbus_t *)mt; + mrp_sockaddr_t *addr = (mrp_sockaddr_t *)&t->remote; + socklen_t alen = sizeof(t->remote); + + return dbus_senddatato(mt, data, tag, addr, alen); +} + + +static const char *get_array_signature(uint16_t type) +{ +#define MAP(from, to) \ + case MRP_MSG_FIELD_##from: \ + return MRP_DBUS_TYPE_##to##_AS_STRING; + + switch (type) { + MAP(STRING, STRING); + MAP(BOOL , BOOLEAN); + MAP(UINT8 , UINT16); + MAP(SINT8 , INT16); + MAP(UINT16, UINT16); + MAP(SINT16, INT16); + MAP(UINT32, UINT32); + MAP(SINT32, INT32); + MAP(UINT64, UINT64); + MAP(SINT64, INT64); + MAP(DOUBLE, DOUBLE); + MAP(BLOB , BYTE); + default: + return NULL; + } +} + + +static mrp_dbus_msg_t *msg_encode(mrp_dbus_t *dbus, const char *destination, + const char *path, const char *interface, + const char *member, const char *sender_id, + mrp_msg_t *msg) +{ + /* + * Notes: There is a type mismatch between our and DBUS types for + * 8-bit integers (D-BUS does not have a signed 8-bit type) + * and boolean types (D-BUS has uint32_t booleans, C99 fails + * to specify the type and gcc uses a signed char). The + * QUIRKY versions of the macros take care of these mismatches. + */ + +#define BASIC_SIMPLE(_i, _mtype, _dtype, _val) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + vptr = &(_val); \ + \ + if (!mrp_dbus_msg_append_basic(_i, type, vptr)) \ + goto fail; \ + break + +#define BASIC_STRING(_i, _mtype, _dtype, _val) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + vptr = (_val); \ + \ + if (!mrp_dbus_msg_append_basic(_i, type, vptr)) \ + goto fail; \ + break + +#define BASIC_QUIRKY(_i, _mtype, _dtype, _mval, _dval) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + _dval = _mval; \ + vptr = &_dval; \ + \ + if (!mrp_dbus_msg_append_basic(_i, type, vptr)) \ + goto fail; \ + break + +#define ARRAY_SIMPLE(_i, _mtype, _dtype, _val) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + vptr = &_val; \ + \ + if (!mrp_dbus_msg_append_basic(_i, type, vptr)) \ + goto fail; \ + break + +#define ARRAY_STRING(_i, _mtype, _dtype, _val) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + vptr = _val; \ + \ + if (!mrp_dbus_msg_append_basic(_i, type, vptr)) \ + goto fail; \ + break + +#define ARRAY_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + _dvar = _mvar; \ + vptr = &_dvar; \ + \ + if (!mrp_dbus_msg_append_basic(_i, type, vptr)) \ + goto fail; \ + break + + mrp_dbus_msg_t *m; + mrp_list_hook_t *p, *n; + mrp_msg_field_t *f; + uint16_t base; + uint32_t asize, i; + const char *sig; + int type, len; + void *vptr; + uint32_t bln; + uint16_t u16, blb; + int16_t s16; + + m = mrp_dbus_msg_method_call(dbus, destination, path, interface, member); + + if (m == NULL) + return NULL; + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_OBJECT_PATH, + (void *)sender_id)) + goto fail; + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &msg->nfield)) + goto fail; + + mrp_list_foreach(&msg->fields, p, n) { + f = mrp_list_entry(p, typeof(*f), hook); + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &f->tag) || + !mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &f->type)) + goto fail; + + switch (f->type) { + BASIC_STRING(m, STRING, STRING , f->str); + BASIC_QUIRKY(m, BOOL , BOOLEAN, f->bln, bln); + BASIC_QUIRKY(m, UINT8 , UINT16 , f->u8 , u16); + BASIC_QUIRKY(m, SINT8 , INT16 , f->s8 , s16); + BASIC_SIMPLE(m, UINT16, UINT16 , f->u16); + BASIC_SIMPLE(m, SINT16, INT16 , f->s16); + BASIC_SIMPLE(m, UINT32, UINT32 , f->u32); + BASIC_SIMPLE(m, SINT32, INT32 , f->s32); + BASIC_SIMPLE(m, UINT64, UINT64 , f->u64); + BASIC_SIMPLE(m, SINT64, INT64 , f->s64); + BASIC_SIMPLE(m, DOUBLE, DOUBLE , f->dbl); + + case MRP_MSG_FIELD_BLOB: + vptr = f->blb; + len = (int)f->size[0]; + sig = get_array_signature(f->type); + asize = len; + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &asize)) + goto fail; + + if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, NULL)) + goto fail; + + for (i = 0; i < asize; i++) { + blb = ((uint8_t *)f->blb)[i]; + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &blb)) + goto fail; + } + + if (!mrp_dbus_msg_close_container(m)) + goto fail; + break; + + default: + if (f->type & MRP_MSG_FIELD_ARRAY) { + base = f->type & ~(MRP_MSG_FIELD_ARRAY); + asize = f->size[0]; + sig = get_array_signature(base); + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &asize)) + goto fail; + + if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, sig)) + goto fail; + + for (i = 0; i < asize; i++) { + switch (base) { + ARRAY_STRING(m, STRING, STRING , f->astr[i]); + ARRAY_QUIRKY(m, BOOL , BOOLEAN, f->abln[i], bln); + ARRAY_QUIRKY(m, UINT8 , UINT16 , f->au8[i] , u16); + ARRAY_QUIRKY(m, SINT8 , INT16 , f->as8[i] , s16); + ARRAY_SIMPLE(m, UINT16, UINT16 , f->au16[i]); + ARRAY_SIMPLE(m, SINT16, INT16 , f->as16[i]); + ARRAY_SIMPLE(m, UINT32, UINT32 , f->au32[i]); + ARRAY_SIMPLE(m, SINT32, INT32 , f->as32[i]); + ARRAY_SIMPLE(m, UINT64, UINT64 , f->au64[i]); + ARRAY_SIMPLE(m, DOUBLE, DOUBLE , f->adbl[i]); + + case MRP_MSG_FIELD_BLOB: + goto fail; + + default: + goto fail; + } + } + + if (!mrp_dbus_msg_close_container(m)) + goto fail; + } + else + goto fail; + } + } + + return m; + + fail: + if (m != NULL) + mrp_dbus_msg_unref(m); + + errno = ECOMM; + + return FALSE; + +#undef BASIC_SIMPLE +#undef BASIC_STRING +#undef BASIC_QUIRKY +#undef ARRAY_SIMPLE +#undef ARRAY_STRING +#undef ARRAY_QUIRKY +} + + +static mrp_msg_t *msg_decode(mrp_dbus_msg_t *m, const char **sender_id) +{ +#define BASIC_SIMPLE(_i, _mtype, _dtype, _var) \ + case MRP_MSG_FIELD_##_mtype: \ + if (!mrp_dbus_msg_read_basic(_i, MRP_DBUS_TYPE_##_dtype, \ + &(_var))) \ + goto fail; \ + \ + if (!mrp_msg_append(msg, tag, type, (_var))) \ + goto fail; \ + break + +#define BASIC_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar) \ + case MRP_MSG_FIELD_##_mtype: \ + if (!mrp_dbus_msg_read_basic(_i, MRP_DBUS_TYPE_##_dtype, \ + &(_dvar))) \ + goto fail; \ + \ + _mvar = _dvar; \ + if (!mrp_msg_append(msg, tag, type, (_mvar))) \ + goto fail; \ + break + +#define ARRAY_SIMPLE(_i, _mtype, _dtype, _var) \ + case MRP_MSG_FIELD_##_mtype: \ + if (!mrp_dbus_msg_read_basic(_i, MRP_DBUS_TYPE_##_dtype, \ + &(_var))) \ + goto fail; \ + break + +#define ARRAY_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar) \ + case MRP_MSG_FIELD_##_mtype: \ + if (!mrp_dbus_msg_read_basic(_i, MRP_DBUS_TYPE_##_dtype, \ + &(_dvar))) \ + goto fail; \ + \ + _mvar = _dvar; \ + break + +#define APPEND_ARRAY(_type, _var) \ + case MRP_MSG_FIELD_##_type: \ + if (!mrp_msg_append(msg, tag, \ + MRP_MSG_FIELD_ARRAY | \ + MRP_MSG_FIELD_##_type, \ + n, _var)) \ + goto fail; \ + break + + mrp_msg_t *msg; + mrp_msg_value_t v; + uint16_t u16; + int16_t s16; + uint32_t u32; + uint16_t nfield, tag, type, base, i; + uint32_t n, j; + int asize; + const char *sender, *sig; + + msg = NULL; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_OBJECT_PATH, &sender)) + goto fail; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &nfield)) + goto fail; + + msg = mrp_msg_create_empty(); + + if (msg == NULL) + goto fail; + + for (i = 0; i < nfield; i++) { + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &tag)) + goto fail; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &type)) + goto fail; + + switch (type) { + BASIC_SIMPLE(m, STRING, STRING , v.str); + BASIC_QUIRKY(m, BOOL , BOOLEAN, v.bln, u32); + BASIC_QUIRKY(m, UINT8 , UINT16 , v.u8 , u16); + BASIC_QUIRKY(m, SINT8 , INT16 , v.s8 , s16); + BASIC_SIMPLE(m, UINT16, UINT16 , v.u16); + BASIC_SIMPLE(m, SINT16, INT16 , v.s16); + BASIC_SIMPLE(m, UINT32, UINT32 , v.u32); + BASIC_SIMPLE(m, SINT32, INT32 , v.s32); + BASIC_SIMPLE(m, UINT64, UINT64 , v.u64); + BASIC_SIMPLE(m, SINT64, INT64 , v.s64); + BASIC_SIMPLE(m, DOUBLE, DOUBLE , v.dbl); + + case MRP_MSG_FIELD_BLOB: + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &n)) + goto fail; + + { + uint8_t blb[n]; + + if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, NULL)) + goto fail; + + for (j = 0; j < n; j++) { + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_BYTE, + blb + j)) + goto fail; + } + + if (!mrp_dbus_msg_exit_container(m)) + goto fail; + + asize = n; + if (!mrp_msg_append(msg, tag, type, asize, blb)) + goto fail; + } + break; + + default: + if (!(type & MRP_MSG_FIELD_ARRAY)) + goto fail; + + base = type & ~(MRP_MSG_FIELD_ARRAY); + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &n)) + goto fail; + + sig = get_array_signature(base); + if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, sig)) + goto fail; + + { + char *astr[n]; + uint32_t dbln[n]; + bool abln[n]; + uint8_t au8 [n]; + int8_t as8 [n]; + uint16_t au16[n]; + int16_t as16[n]; + uint32_t au32[n]; + int32_t as32[n]; + uint64_t au64[n]; + int64_t as64[n]; + double adbl[n]; + + for (j = 0; j < n; j++) { + switch (base) { + ARRAY_SIMPLE(m, STRING, STRING , astr[j]); + ARRAY_QUIRKY(m, BOOL , BOOLEAN, abln[j], dbln[j]); + ARRAY_QUIRKY(m, UINT8 , UINT16 , au8[j] , au16[j]); + ARRAY_QUIRKY(m, SINT8 , INT16 , as8[j] , as16[j]); + ARRAY_SIMPLE(m, UINT16, UINT16 , au16[j]); + ARRAY_SIMPLE(m, SINT16, INT16 , as16[j]); + ARRAY_SIMPLE(m, UINT32, UINT32 , au32[j]); + ARRAY_SIMPLE(m, SINT32, INT32 , as32[j]); + ARRAY_SIMPLE(m, UINT64, UINT64 , au64[j]); + ARRAY_SIMPLE(m, SINT64, INT64 , as64[j]); + ARRAY_SIMPLE(m, DOUBLE, DOUBLE , adbl[j]); + default: + goto fail; + } + } + + switch (base) { + APPEND_ARRAY(STRING, astr); + APPEND_ARRAY(BOOL , abln); + APPEND_ARRAY(UINT8 , au8 ); + APPEND_ARRAY(SINT8 , as8 ); + APPEND_ARRAY(UINT16, au16); + APPEND_ARRAY(SINT16, as16); + APPEND_ARRAY(UINT32, au32); + APPEND_ARRAY(SINT32, as32); + APPEND_ARRAY(UINT64, au64); + APPEND_ARRAY(SINT64, as64); + APPEND_ARRAY(DOUBLE, adbl); + default: + goto fail; + } + } + + if (!mrp_dbus_msg_exit_container(m)) + goto fail; + } + } + + if (sender_id != NULL) + *sender_id = sender; + + return msg; + + fail: + mrp_msg_unref(msg); + errno = EBADMSG; + + return NULL; + +#undef BASIC_SIMPLE +#undef BASIC_QUIRKY +#undef ARRAY_SIMPLE +#undef ARRAY_QUIRKY +#undef APPEND_ARRAY +} + + +static mrp_dbus_msg_t *data_encode(mrp_dbus_t *dbus, const char *destination, + const char *path, const char *interface, + const char *member, const char *sender_id, + void *data, uint16_t tag) +{ +#define BASIC_SIMPLE(_mtype, _dtype, _val) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + vptr = &(_val); \ + \ + if (!mrp_dbus_msg_append_basic(m, type, vptr)) \ + goto fail; \ + break + +#define BASIC_STRING(_mtype, _dtype, _val) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + vptr = _val; \ + \ + if (!mrp_dbus_msg_append_basic(m, type, vptr)) \ + goto fail; \ + break + +#define BASIC_QUIRKY(_mtype, _dtype, _mval, _dval) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + _dval = _mval; \ + vptr = &_dval; \ + \ + if (!mrp_dbus_msg_append_basic(m, type, vptr)) \ + goto fail; \ + break + +#define ARRAY_SIMPLE(_mtype, _dtype, _val) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + vptr = &_val; \ + \ + if (!mrp_dbus_msg_append_basic(m, type, vptr)) \ + goto fail; \ + break + +#define ARRAY_STRING(_mtype, _dtype, _val) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + vptr = _val; \ + \ + if (!mrp_dbus_msg_append_basic(m, type, vptr)) \ + goto fail; \ + break + +#define ARRAY_QUIRKY(_mtype, _dtype, _mvar, _dvar) \ + case MRP_MSG_FIELD_##_mtype: \ + type = MRP_DBUS_TYPE_##_dtype; \ + _dvar = _mvar; \ + vptr = &_dvar; \ + \ + if (!mrp_dbus_msg_append_basic(m, type, vptr)) \ + goto fail; \ + break + + mrp_dbus_msg_t *m; + mrp_data_descr_t *descr; + mrp_data_member_t *fields, *f; + int nfield; + uint16_t type, base; + mrp_msg_value_t *v; + void *vptr; + uint32_t n, j; + int i, blblen; + const char *sig; + uint16_t u16; + int16_t s16; + uint32_t bln, asize; + + m = mrp_dbus_msg_method_call(dbus, destination, path, interface, member); + + if (m == NULL) + return NULL; + + descr = mrp_msg_find_type(tag); + + if (descr == NULL) + goto fail; + + fields = descr->fields; + nfield = descr->nfield; + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_OBJECT_PATH, + (void *)sender_id)) + goto fail; + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &tag)) + goto fail; + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &nfield)) + goto fail; + + for (i = 0, f = fields; i < nfield; i++, f++) { + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &f->tag) || + !mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &f->type)) + goto fail; + + v = (mrp_msg_value_t *)(data + f->offs); + + switch (f->type) { + BASIC_STRING(STRING, STRING , v->str); + BASIC_QUIRKY(BOOL , BOOLEAN, v->bln, bln); + BASIC_QUIRKY(UINT8 , UINT16 , v->u8 , u16); + BASIC_QUIRKY(SINT8 , INT16 , v->s8 , s16); + BASIC_SIMPLE(UINT16, UINT16 , v->u16); + BASIC_SIMPLE(SINT16, INT16 , v->s16); + BASIC_SIMPLE(UINT32, UINT32 , v->u32); + BASIC_SIMPLE(SINT32, INT32 , v->s32); + BASIC_SIMPLE(UINT64, UINT64 , v->u64); + BASIC_SIMPLE(SINT64, INT64 , v->s64); + BASIC_SIMPLE(DOUBLE, DOUBLE , v->dbl); + + case MRP_MSG_FIELD_BLOB: + sig = get_array_signature(f->type); + blblen = mrp_data_get_blob_size(data, descr, i); + asize = blblen; + + if (blblen == -1) + goto fail; + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &asize)) + goto fail; + + if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, sig)) + goto fail; + + for (i = 0; i < blblen; i++) + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_BYTE, + f->blb + i)) + goto fail; + + if (!mrp_dbus_msg_close_container(m)) + goto fail; + break; + + default: + if (!(f->type & MRP_MSG_FIELD_ARRAY)) + goto fail; + + base = f->type & ~(MRP_MSG_FIELD_ARRAY); + n = mrp_data_get_array_size(data, descr, i); + sig = get_array_signature(base); + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &n)) + goto fail; + + if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, sig)) + goto fail; + + for (j = 0; j < n; j++) { + switch (base) { + ARRAY_STRING(STRING, STRING , v->astr[j]); + ARRAY_QUIRKY(BOOL , BOOLEAN, v->abln[j], bln); + ARRAY_QUIRKY(UINT8 , UINT16 , v->au8[j] , u16); + ARRAY_QUIRKY(SINT8 , INT16 , v->as8[j] , s16); + ARRAY_SIMPLE(UINT16, UINT16 , v->au16[j]); + ARRAY_SIMPLE(SINT16, INT16 , v->as16[j]); + ARRAY_SIMPLE(UINT32, UINT32 , v->au32[j]); + ARRAY_SIMPLE(SINT32, INT32 , v->as32[j]); + ARRAY_SIMPLE(UINT64, UINT64 , v->au64[j]); + ARRAY_SIMPLE(DOUBLE, DOUBLE , v->adbl[j]); + + case MRP_MSG_FIELD_BLOB: + goto fail; + + default: + goto fail; + } + } + + if (!mrp_dbus_msg_close_container(m)) + goto fail; + } + } + + return m; + + fail: + if (m != NULL) + mrp_dbus_msg_unref(m); + + errno = ECOMM; + + return NULL; + +#undef BASIC_SIMPLE +#undef BASIC_QUIRKY +#undef ARRAY_SIMPLE +#undef ARRAY_QUIRKY +} + + +static mrp_data_member_t *member_type(mrp_data_member_t *fields, int nfield, + uint16_t tag) +{ + mrp_data_member_t *f; + int i; + + for (i = 0, f = fields; i < nfield; i++, f++) + if (f->tag == tag) + return f; + + return NULL; +} + + +static void *data_decode(mrp_dbus_msg_t *m, uint16_t *tagp, + const char **sender_id) +{ +#define HANDLE_SIMPLE(_i, _mtype, _dtype, _var) \ + case MRP_MSG_FIELD_##_mtype: \ + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_##_dtype, \ + &(_var))) \ + goto fail; \ + break + +#define HANDLE_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar) \ + case MRP_MSG_FIELD_##_mtype: \ + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_##_dtype, \ + &(_dvar))) \ + goto fail; \ + \ + _mvar = _dvar; \ + break + + void *data; + mrp_data_descr_t *descr; + mrp_data_member_t *fields, *f; + int nfield; + uint16_t tag, type, base; + mrp_msg_value_t *v; + uint32_t n, j, size; + int i; + const char *sender, *sig; + uint32_t u32; + uint16_t u16; + int16_t s16; + + tag = 0; + data = NULL; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_OBJECT_PATH, &sender)) + goto fail; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &tag)) + goto fail; + + descr = mrp_msg_find_type(tag); + + if (descr == NULL) + goto fail; + + *tagp = tag; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &nfield)) + goto fail; + + if (nfield != descr->nfield) + goto fail; + + fields = descr->fields; + data = mrp_allocz(descr->size); + + if (MRP_UNLIKELY(data == NULL)) + goto fail; + + for (i = 0; i < nfield; i++) { + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &tag)) + goto fail; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &type)) + goto fail; + + f = member_type(fields, nfield, tag); + + if (MRP_UNLIKELY(f == NULL)) + goto fail; + + v = (mrp_msg_value_t *)(data + f->offs); + + switch (type) { + HANDLE_SIMPLE(&im, STRING, STRING , v->str); + HANDLE_QUIRKY(&im, BOOL , BOOLEAN, v->bln, u32); + HANDLE_QUIRKY(&im, UINT8 , UINT16 , v->u8 , u16); + HANDLE_QUIRKY(&im, SINT8 , INT16 , v->s8 , s16); + HANDLE_SIMPLE(&im, UINT16, UINT16 , v->u16); + HANDLE_SIMPLE(&im, SINT16, INT16 , v->s16); + HANDLE_SIMPLE(&im, UINT32, UINT32 , v->u32); + HANDLE_SIMPLE(&im, SINT32, INT32 , v->s32); + HANDLE_SIMPLE(&im, UINT64, UINT64 , v->u64); + HANDLE_SIMPLE(&im, SINT64, INT64 , v->s64); + HANDLE_SIMPLE(&im, DOUBLE, DOUBLE , v->dbl); + + case MRP_MSG_FIELD_BLOB: + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &size)) + goto fail; + + sig = MRP_DBUS_TYPE_BYTE_AS_STRING; + + if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, sig)) + goto fail; + + { + uint8_t blb[size]; + + for (j = 0; j < size; j++) + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_BYTE, + blb + j)) + goto fail; + + v->blb = mrp_alloc(size); + + if (v->blb == NULL && size != 0) + goto fail; + + memcpy(v->blb, blb, size); + } + + if (!mrp_dbus_msg_exit_container(m)) + goto fail; + break; + + default: + if (!(f->type & MRP_MSG_FIELD_ARRAY)) + goto fail; + + base = type & ~(MRP_MSG_FIELD_ARRAY); + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &n)) + goto fail; + + if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, NULL)) + goto fail; + + size = n; + + switch (base) { + case MRP_MSG_FIELD_STRING: size *= sizeof(*v->astr); break; + case MRP_MSG_FIELD_BOOL: size *= sizeof(*v->abln); break; + case MRP_MSG_FIELD_UINT8: size *= sizeof(*v->au8); break; + case MRP_MSG_FIELD_SINT8: size *= sizeof(*v->as8); break; + case MRP_MSG_FIELD_UINT16: size *= sizeof(*v->au16); break; + case MRP_MSG_FIELD_SINT16: size *= sizeof(*v->as16); break; + case MRP_MSG_FIELD_UINT32: size *= sizeof(*v->au32); break; + case MRP_MSG_FIELD_SINT32: size *= sizeof(*v->as32); break; + case MRP_MSG_FIELD_UINT64: size *= sizeof(*v->au64); break; + case MRP_MSG_FIELD_SINT64: size *= sizeof(*v->as64); break; + case MRP_MSG_FIELD_DOUBLE: size *= sizeof(*v->adbl); break; + default: + goto fail; + } + + v->aany = mrp_allocz(size); + if (v->aany == NULL) + goto fail; + + for (j = 0; j < n; j++) { + uint32_t dbln[n]; + uint16_t au16[n]; + int16_t as16[n]; + + switch (base) { + HANDLE_SIMPLE(&ia, STRING, STRING , v->astr[j]); + HANDLE_QUIRKY(&ia, BOOL , BOOLEAN, v->abln[j], dbln[j]); + HANDLE_QUIRKY(&ia, UINT8 , UINT16 , v->au8[j] , au16[j]); + HANDLE_QUIRKY(&ia, SINT8 , INT16 , v->as8[j] , as16[j]); + HANDLE_SIMPLE(&ia, UINT16, UINT16 , v->au16[j]); + HANDLE_SIMPLE(&ia, SINT16, INT16 , v->as16[j]); + HANDLE_SIMPLE(&ia, UINT32, UINT32 , v->au32[j]); + HANDLE_SIMPLE(&ia, SINT32, INT32 , v->as32[j]); + HANDLE_SIMPLE(&ia, UINT64, UINT64 , v->au64[j]); + HANDLE_SIMPLE(&ia, SINT64, INT64 , v->as64[j]); + HANDLE_SIMPLE(&ia, DOUBLE, DOUBLE , v->adbl[j]); + } + + if (base == MRP_MSG_FIELD_STRING) { + v->astr[j] = mrp_strdup(v->astr[j]); + if (v->astr[j] == NULL) + goto fail; + } + } + + if (!mrp_dbus_msg_exit_container(m)) + goto fail; + } + + if (f->type == MRP_MSG_FIELD_STRING) { + v->str = mrp_strdup(v->str); + if (v->str == NULL) + goto fail; + } + } + + if (sender_id != NULL) + *sender_id = sender; + + return data; + + fail: + mrp_data_free(data, tag); + errno = EBADMSG; + + return NULL; +} + + +static mrp_dbus_msg_t *raw_encode(mrp_dbus_t *dbus, const char *destination, + const char *path, const char *interface, + const char *member, const char *sender_id, + void *data, size_t size) +{ + mrp_dbus_msg_t *m; + const char *sig; + uint32_t i, n; + + m = mrp_dbus_msg_method_call(dbus, destination, path, interface, member); + + if (m == NULL) + return NULL; + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_OBJECT_PATH, + (void *)sender_id)) + goto fail; + + sig = MRP_DBUS_TYPE_BYTE_AS_STRING; + n = size; + + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &n)) + goto fail; + + if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, sig)) + goto fail; + + for (i = 0; i < n; i++) + if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_BYTE, data + i)) + goto fail; + + if (!mrp_dbus_msg_close_container(m)) + goto fail; + + return m; + + fail: + mrp_dbus_msg_unref(m); + + errno = ECOMM; + + return NULL; +} + + +static void *raw_decode(mrp_dbus_msg_t *m, size_t *sizep, + const char **sender_id) +{ + const char *sender, *sig; + void *data; + uint32_t n, i; + + data = NULL; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_OBJECT_PATH, &sender)) + goto fail; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &n)) + goto fail; + + sig = MRP_DBUS_TYPE_BYTE_AS_STRING; + + if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, sig)) + goto fail; + + { + uint8_t databuf[n]; + + for (i = 0; i < n; i++) + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_BYTE, databuf + i)) + goto fail; + + data = mrp_alloc(n); + + if (data == NULL && n != 0) + goto fail; + + memcpy(data, databuf, n); + } + + if (!mrp_dbus_msg_exit_container(m)) + goto fail; + + if (sizep != NULL) + *sizep = (size_t)n; + + if (sender_id != NULL) + *sender_id = sender; + + return data; + + fail: + errno = EBADMSG; + + return NULL; +} + + +MRP_REGISTER_TRANSPORT(dbus, DBUS, dbus_t, dbus_resolve, + dbus_open, dbus_createfrom, dbus_close, NULL, + dbus_bind, NULL, NULL, + dbus_connect, dbus_disconnect, + dbus_sendmsg, dbus_sendmsgto, + dbus_sendraw, dbus_sendrawto, + dbus_senddata, dbus_senddatato, + NULL, NULL, + NULL, NULL); diff --git a/src/common/dbus-sdbus.c b/src/common/dbus-sdbus.c new file mode 100644 index 0000000..a2fb9e9 --- /dev/null +++ b/src/common/dbus-sdbus.c @@ -0,0 +1,1955 @@ +/* + * Copyright (c) 2012, 2013, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <errno.h> + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/log.h> +#include <murphy/common/list.h> +#include <murphy/common/hashtbl.h> +#include <murphy/common/refcnt.h> +#include <murphy/common/utils.h> +#include <murphy/common/mainloop.h> +#include <murphy/common/dbus-sdbus.h> + +#define BUS_SERVICE "org.freedesktop.DBus" +#define BUS_PATH "/org/freedesktop/DBus" +#define BUS_INTERFACE "org.freedesktop.DBus" +#define BUS_NAME_CHANGED "NameOwnerChanged" +#define BUS_GET_OWNER "GetNameOwner" + +/* XXX check these... */ +#define SDBUS_ERROR_FAILED "org.DBus.error.failed" + +/* D-Bus name request flags and reply statuses. */ +#define SDBUS_NAME_REQUEST_REPLACE 0x2 +#define SDBUS_NAME_REQUEST_DONTQ 0x4 + +#define SDBUS_NAME_STATUS_OWNER 0x1 +#define SDBUS_NAME_STATUS_QUEUING 0x2 +#define SDBUS_NAME_STATUS_EXISTS 0x3 +#define SDBUS_NAME_STATUS_GOTIT 0x4 + +#define SDBUS_NAME_STATUS_RELEASED 0x1 +#define SDBUS_NAME_STATUS_UNKNOWN 0x2 +#define SDBUS_NAME_STATUS_FOREIGN 0x3 + +#define USEC_TO_MSEC(usec) ((unsigned int)((usec) / 1000)) +#define MSEC_TO_USEC(msec) ((uint64_t)(msec) * 1000) + +struct mrp_dbus_s { + char *address; /* bus address */ + sd_bus *bus; /* actual D-BUS connection */ + mrp_mainloop_t *ml; /* murphy mainloop */ + mrp_subloop_t *sl; /* subloop for pumping the bus */ + mrp_htbl_t *objects; /* object path (refcount) table */ + mrp_htbl_t *methods; /* method handler table */ + mrp_htbl_t *signals; /* signal handler table */ + mrp_list_hook_t name_trackers; /* peer (name) watchers */ + mrp_list_hook_t calls; /* pending calls */ + uint32_t call_id; /* next call id */ + const char *unique_name; /* our unique D-BUS address */ + int priv; /* whether a private connection */ + int signal_filter; /* if signal dispatching is set up */ + int register_fallback; /* if the fallback object is set up */ + mrp_refcnt_t refcnt; /* reference count */ +}; + + +struct mrp_dbus_msg_s { + sd_bus_message *msg; /* actual D-Bus message */ + mrp_refcnt_t refcnt; /* reference count */ + mrp_list_hook_t arrays; /* implicitly freed related arrays */ +}; + + +typedef struct { + mrp_dbus_type_t type; + mrp_list_hook_t hook; + void *items; + size_t nitem; +} msg_array_t; + +/* + * Notes: + * + * At the moment we administer DBUS method and signal handlers + * in a very primitive way (subject to be changed later). For + * every bus instance we maintain two hash tables, one for methods + * and another for signals. Each method and signal handler is + * hashed in only by it's method/signal name to a linked list of + * method or signal handlers. + * + * When dispatching a method, we look up the chain with a matching + * method name, or the chain for "" in case a matching chain is + * not found, and invoke the handler which best matches the + * received message (by looking at the path, interface and name). + * Only one such handler is invoked at most. + * + * For signals we look up both the chain with a matching name and + * the chain for "" and invoke all signal handlers that match the + * received message (regardless of their return value). + */ + +typedef struct { + char *path; /* object path */ + int cnt; /* reference count */ +} object_t; + +typedef struct { + char *member; /* signal/method name */ + mrp_list_hook_t handlers; /* handlers with matching member */ +} handler_list_t; + +typedef struct { + mrp_list_hook_t hook; + char *sender; + char *path; + char *interface; + char *member; + mrp_dbus_handler_t handler; + void *user_data; +} handler_t; + +#define method_t handler_t +#define signal_t handler_t + + +typedef struct { + mrp_list_hook_t hook; /* hook to name tracker list */ + char *name; /* name to track */ + mrp_dbus_name_cb_t cb; /* status change callback */ + void *user_data; /* opaque callback user data */ + int32_t qid; /* initial query ID */ +} name_tracker_t; + + +typedef struct { + mrp_dbus_t *dbus; /* DBUS connection */ + int32_t id; /* call id */ + mrp_dbus_reply_cb_t cb; /* completion notification callback */ + void *user_data; /* opaque callback data */ + uint64_t serial; /* DBUS call */ + mrp_list_hook_t hook; /* hook to list of pending calls */ + sd_bus_message *msg; /* original message */ +} call_t; + + +typedef struct { + mrp_mainloop_t *ml; /* mainloop for bus connection */ + const char *address; /* address of bus */ +} bus_spec_t; + +static mrp_htbl_t *buses; + + + +static int dispatch_signal(sd_bus *b, int r, sd_bus_message *msg, void *data); +static int dispatch_method(sd_bus *b, int r, sd_bus_message *msg, void *data); + +static void purge_name_trackers(mrp_dbus_t *dbus); +static void purge_calls(mrp_dbus_t *dbus); +static void handler_list_free_cb(void *key, void *entry); +static void handler_free(handler_t *h); +static int name_owner_change_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *m, + void *data); +static void call_free(call_t *call); +static void object_free_cb(void *key, void *entry); + + +static int purge_objects(void *key, void *entry, void *user_data) +{ + mrp_dbus_t *dbus = (mrp_dbus_t *)user_data; + object_t *o = (object_t *)entry; + + MRP_UNUSED(key); + + sd_bus_remove_object(dbus->bus, o->path, dispatch_method, dbus); + + return MRP_HTBL_ITER_MORE; +} + + +static int purge_filters(void *key, void *entry, void *user_data) +{ + mrp_dbus_t *dbus = (mrp_dbus_t *)user_data; + handler_list_t *l = (handler_list_t *)entry; + mrp_list_hook_t *p, *n; + handler_t *h; + + MRP_UNUSED(key); + + mrp_list_foreach(&l->handlers, p, n) { + h = mrp_list_entry(p, handler_t, hook); + mrp_dbus_remove_filter(dbus, + h->sender, h->path, h->interface, + h->member, NULL); + } + + return MRP_HTBL_ITER_MORE; +} + + +static void dbus_disconnect(mrp_dbus_t *dbus) +{ + if (dbus) { + mrp_htbl_remove(buses, dbus->bus, FALSE); + + if (dbus->objects) { + mrp_htbl_foreach(dbus->signals, purge_objects, dbus); + mrp_htbl_destroy(dbus->objects, TRUE); + } + + if (dbus->signals) { + mrp_htbl_foreach(dbus->signals, purge_filters, dbus); + mrp_htbl_destroy(dbus->signals, TRUE); + } + if (dbus->methods) + mrp_htbl_destroy(dbus->methods, TRUE); + + purge_name_trackers(dbus); + purge_calls(dbus); + + if (dbus->bus != NULL) { + if (dbus->signal_filter) + sd_bus_remove_filter(dbus->bus, dispatch_signal, dbus); + if (dbus->register_fallback) + sd_bus_remove_fallback(dbus->bus, "/", dispatch_method, dbus); + if (dbus->priv) + sd_bus_close(dbus->bus); + else + sd_bus_unref(dbus->bus); + } + + mrp_free(dbus->address); + dbus->bus = NULL; + dbus->ml = NULL; + + mrp_free(dbus); + } +} + + +static int bus_cmp(const void *key1, const void *key2) +{ + return key2 - key1; +} + + +static uint32_t bus_hash(const void *key) +{ + uint32_t h; + + h = (ptrdiff_t)key; + h >>= 2 * sizeof(key); + + return h; +} + + +static int find_bus_by_spec(void *key, void *object, void *user_data) +{ + mrp_dbus_t *dbus = (mrp_dbus_t *)object; + bus_spec_t *spec = (bus_spec_t *)user_data; + + MRP_UNUSED(key); + + if (dbus->ml == spec->ml && !strcmp(dbus->address, spec->address)) + return TRUE; + else + return FALSE; +} + + +static mrp_dbus_t *dbus_get(mrp_mainloop_t *ml, const char *address) +{ + mrp_htbl_config_t hcfg; + bus_spec_t spec; + + if (buses == NULL) { + mrp_clear(&hcfg); + + hcfg.comp = bus_cmp; + hcfg.hash = bus_hash; + hcfg.free = NULL; + + buses = mrp_htbl_create(&hcfg); + + return NULL; + } + else { + spec.ml = ml; + spec.address = address; + + return mrp_htbl_find(buses, find_bus_by_spec, &spec); + } +} + + +mrp_dbus_t *mrp_dbus_connect(mrp_mainloop_t *ml, const char *address, + mrp_dbus_err_t *errp) +{ + mrp_htbl_config_t hcfg; + mrp_dbus_t *dbus; + + if ((dbus = dbus_get(ml, address)) != NULL) + return mrp_dbus_ref(dbus); + + if ((dbus = mrp_allocz(sizeof(*dbus))) == NULL) + return NULL; + + mrp_list_init(&dbus->calls); + mrp_list_init(&dbus->name_trackers); + mrp_refcnt_init(&dbus->refcnt); + + dbus->ml = ml; + + mrp_dbus_error_init(errp); + + /* + * connect to the bus + */ + + if (!strcmp(address, "system")) { + if (sd_bus_open_system(&dbus->bus) != 0) + goto fail; + } + else if (!strcmp(address, "session")) { + if (sd_bus_open_user(&dbus->bus) != 0) + goto fail; + } + else { + dbus->priv = TRUE; + + if (sd_bus_new(&dbus->bus) != 0) + goto fail; + else { + if (sd_bus_set_address(dbus->bus, address) != 0) + goto fail; + + if (sd_bus_start(dbus->bus) != 0) + goto fail; + } + } + + dbus->address = mrp_strdup(address); + if (sd_bus_get_unique_name(dbus->bus, &dbus->unique_name) != 0) + goto fail; + + /* + * set up with mainloop + */ + + if (!mrp_dbus_setup_with_mainloop(ml, dbus->bus)) + goto fail; + + /* + * set up our message dispatchers and take our name on the bus + */ + + if (sd_bus_add_filter(dbus->bus, dispatch_signal, dbus) != 0) { + mrp_dbus_error_set(errp, SDBUS_ERROR_FAILED, + "Failed to set up signal dispatching."); + goto fail; + } + dbus->signal_filter = TRUE; + + if (sd_bus_add_fallback(dbus->bus, "/", dispatch_method, dbus) != 0) { + mrp_dbus_error_set(errp, SDBUS_ERROR_FAILED, + "Failed to set up method dispatching."); + goto fail; + } + dbus->register_fallback = TRUE; + + mrp_clear(&hcfg); + hcfg.comp = mrp_string_comp; + hcfg.hash = mrp_string_hash; + hcfg.free = object_free_cb; + + if ((dbus->objects = mrp_htbl_create(&hcfg)) == NULL) { + mrp_dbus_error_set(errp, SDBUS_ERROR_FAILED, + "Failed to create DBUS object path table."); + goto fail; + } + + hcfg.free = handler_list_free_cb; + + if ((dbus->methods = mrp_htbl_create(&hcfg)) == NULL) { + mrp_dbus_error_set(errp, SDBUS_ERROR_FAILED, + "Failed to create DBUS method table."); + goto fail; + } + + if ((dbus->signals = mrp_htbl_create(&hcfg)) == NULL) { + mrp_dbus_error_set(errp, SDBUS_ERROR_FAILED, + "Failed to create DBUS signal table."); + goto fail; + } + + + /* + * install handler for NameOwnerChanged for tracking clients/peers + */ + + if (!mrp_dbus_add_signal_handler(dbus, + BUS_SERVICE, BUS_PATH, BUS_INTERFACE, + BUS_NAME_CHANGED, name_owner_change_cb, + NULL)) { + mrp_dbus_error_set(errp, SDBUS_ERROR_FAILED, + "Failed to install NameOwnerChanged handler."); + goto fail; + } + + /* install a 'safe' filter to avoid receiving all name change signals */ + mrp_dbus_install_filter(dbus, + BUS_SERVICE, BUS_PATH, BUS_INTERFACE, + BUS_NAME_CHANGED, BUS_SERVICE, NULL); + + mrp_list_init(&dbus->name_trackers); + dbus->call_id = 1; + + if (mrp_htbl_insert(buses, dbus->bus, dbus)) + return dbus; + + fail: + dbus_disconnect(dbus); + return NULL; +} + + +mrp_dbus_t *mrp_dbus_ref(mrp_dbus_t *dbus) +{ + return mrp_ref_obj(dbus, refcnt); +} + + +int mrp_dbus_unref(mrp_dbus_t *dbus) +{ + if (mrp_unref_obj(dbus, refcnt)) { + dbus_disconnect(dbus); + + return TRUE; + } + else + return FALSE; +} + + +int mrp_dbus_acquire_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_err_t *error) +{ + int flags = SDBUS_NAME_REQUEST_REPLACE | SDBUS_NAME_REQUEST_DONTQ; + int status; + + mrp_dbus_error_init(error); + + status = sd_bus_request_name(dbus->bus, name, flags); + + if (status == SDBUS_NAME_STATUS_OWNER || status == SDBUS_NAME_STATUS_GOTIT) + return TRUE; + else { + mrp_dbus_error_set(error, SDBUS_ERROR_FAILED, "failed to request name"); + + return FALSE; + } +} + + +int mrp_dbus_release_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_err_t *error) +{ + int status; + + mrp_dbus_error_init(error); + + status = sd_bus_release_name(dbus->bus, name); + + if (status == SDBUS_NAME_STATUS_RELEASED) + return TRUE; + else { + mrp_dbus_error_set(error, SDBUS_ERROR_FAILED, "failed to release name"); + + return FALSE; + } +} + + +const char *mrp_dbus_get_unique_name(mrp_dbus_t *dbus) +{ + return dbus->unique_name; +} + + +static void name_owner_query_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *m, void *data) +{ + name_tracker_t *t = (name_tracker_t *)data; + const char *owner; + int state; + + if (t->cb != NULL) { /* tracker still active */ + t->qid = 0; + state = !mrp_dbus_msg_is_error(m); + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_STRING, &owner)) + owner = "<unknown>"; + + t->cb(dbus, t->name, state, owner, t->user_data); + } + else /* already requested to delete */ + mrp_free(t); +} + + +static int name_owner_change_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *m, void *data) +{ + const char *name, *prev, *next; + mrp_list_hook_t *p, *n; + name_tracker_t *t; + + MRP_UNUSED(data); + + if (mrp_dbus_msg_type(m) != MRP_DBUS_MESSAGE_TYPE_SIGNAL) + return FALSE; + + if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_STRING, &name) || + !mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_STRING, &prev) || + !mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_STRING, &next)) + return FALSE; + +#if 0 + /* + * Notes: XXX TODO + * In principle t->cb could call mrp_dbus_forget for some other D-BUS + * address than name. If that happened to be n (== p->hook.next) this + * would result in a crash or memory corruption in the next iteration + * of this loop (when handling n). We can easily get around this + * problem by + * + * 1. administering in mrp_dbus_t that we're handing a NameOwnerChange + * 2. checking for this in mrp_dbus_forget_name and if it is the case + * only marking the affected entry for deletion + * 3. removing entries marked for deletion in this loop (or just + * ignoring them and making another pass in the end removing any + * such entry). + */ +#endif + + mrp_list_foreach(&dbus->name_trackers, p, n) { + t = mrp_list_entry(p, name_tracker_t, hook); + + if (!strcmp(name, t->name)) + t->cb(dbus, name, next && *next, next, t->user_data); + } + + return TRUE; +} + + +int mrp_dbus_follow_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_name_cb_t cb, void *user_data) +{ + name_tracker_t *t; + + if ((t = mrp_allocz(sizeof(*t))) != NULL) { + if ((t->name = mrp_strdup(name)) != NULL) { + t->cb = cb; + t->user_data = user_data; + + if (mrp_dbus_install_filter(dbus, + BUS_SERVICE, BUS_PATH, BUS_INTERFACE, + BUS_NAME_CHANGED, name, + NULL)) { + mrp_list_append(&dbus->name_trackers, &t->hook); + + t->qid = mrp_dbus_call(dbus, + BUS_SERVICE, BUS_PATH, BUS_INTERFACE, + BUS_GET_OWNER, 5000, + name_owner_query_cb, t, + MRP_DBUS_TYPE_STRING, t->name, + MRP_DBUS_TYPE_INVALID); + return TRUE; + } + else { + mrp_free(t->name); + mrp_free(t); + } + } + } + + return FALSE; +} + + +int mrp_dbus_forget_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_name_cb_t cb, void *user_data) +{ + mrp_list_hook_t *p, *n; + name_tracker_t *t; + + mrp_dbus_remove_filter(dbus, + BUS_SERVICE, BUS_PATH, BUS_INTERFACE, + BUS_NAME_CHANGED, name, + NULL); + + mrp_list_foreach(&dbus->name_trackers, p, n) { + t = mrp_list_entry(p, name_tracker_t, hook); + + if (t->cb == cb && t->user_data == user_data && !strcmp(t->name,name)) { + mrp_list_delete(&t->hook); + mrp_free(t->name); + + if (!t->qid) + mrp_free(t); + else { + t->cb = NULL; + t->user_data = NULL; + t->name = NULL; + } + + return TRUE; + } + } + + return FALSE; +} + + +static void purge_name_trackers(mrp_dbus_t *dbus) +{ + mrp_list_hook_t *p, *n; + name_tracker_t *t; + + mrp_list_foreach(&dbus->name_trackers, p, n) { + t = mrp_list_entry(p, name_tracker_t, hook); + + mrp_list_delete(p); + mrp_dbus_remove_filter(dbus, + BUS_SERVICE, BUS_PATH, BUS_INTERFACE, + BUS_NAME_CHANGED, t->name, + NULL); + mrp_free(t->name); + mrp_free(t); + } +} + + +static handler_t *handler_alloc(const char *sender, const char *path, + const char *interface, const char *member, + mrp_dbus_handler_t handler, void *user_data) +{ + handler_t *h; + + if ((h = mrp_allocz(sizeof(*h))) != NULL) { + h->sender = mrp_strdup(sender); + h->path = mrp_strdup(path); + h->interface = mrp_strdup(interface); + h->member = mrp_strdup(member); + + if ((path && !h->path) || !h->interface || !h->member) { + handler_free(h); + return NULL; + } + + h->handler = handler; + h->user_data = user_data; + + return h; + } + + return NULL; +} + + +static void handler_free(handler_t *h) +{ + if (h != NULL) { + mrp_free(h->sender); + mrp_free(h->path); + mrp_free(h->interface); + mrp_free(h->member); + + mrp_free(h); + } +} + + +static handler_list_t *handler_list_alloc(const char *member) +{ + handler_list_t *l; + + if ((l = mrp_allocz(sizeof(*l))) != NULL) { + if ((l->member = mrp_strdup(member)) != NULL) + mrp_list_init(&l->handlers); + else { + mrp_free(l); + l = NULL; + } + } + + return l; +} + + +static inline void handler_list_free(handler_list_t *l) +{ + mrp_list_hook_t *p, *n; + handler_t *h; + + mrp_list_foreach(&l->handlers, p, n) { + h = mrp_list_entry(p, handler_t, hook); + mrp_list_delete(p); + handler_free(h); + } + + mrp_free(l->member); + mrp_free(l); +} + + +static void handler_list_free_cb(void *key, void *entry) +{ + MRP_UNUSED(key); + + handler_list_free((handler_list_t *)entry); +} + + +static inline int handler_specificity(handler_t *h) +{ + int score = 0; + + if (h->path && *h->path) + score |= 0x4; + if (h->interface && *h->interface) + score |= 0x2; + if (h->member && *h->member) + score |= 0x1; + + return score; +} + + +static void handler_list_insert(handler_list_t *l, handler_t *handler) +{ + mrp_list_hook_t *p, *n; + handler_t *h; + int score; + + score = handler_specificity(handler); + + mrp_list_foreach(&l->handlers, p, n) { + h = mrp_list_entry(p, handler_t, hook); + + if (score >= handler_specificity(h)) { + mrp_list_append(h->hook.prev, &handler->hook); /* add before h */ + return; + } + } + + mrp_list_append(&l->handlers, &handler->hook); +} + + +static handler_t *handler_list_lookup(handler_list_t *l, const char *path, + const char *interface, const char *member, + mrp_dbus_handler_t handler, + void *user_data) +{ + mrp_list_hook_t *p, *n; + handler_t *h; + + mrp_list_foreach(&l->handlers, p, n) { + h = mrp_list_entry(p, handler_t, hook); + + if (h->handler == handler && user_data == h->user_data && + path && !strcmp(path, h->path) && + interface && !strcmp(interface, h->interface) && + member && !strcmp(member, h->member)) + return h; + } + + return NULL; +} + + +static handler_t *handler_list_find(handler_list_t *l, const char *path, + const char *interface, const char *member) +{ +#define MATCHES(h, field) (!*field || !*h->field || !strcmp(field, h->field)) + mrp_list_hook_t *p, *n; + handler_t *h; + + mrp_list_foreach(&l->handlers, p, n) { + h = mrp_list_entry(p, handler_t, hook); + + if (MATCHES(h, path) && MATCHES(h, interface) && MATCHES(h, member)) + return h; + } + + return NULL; +#undef MATCHES +} + + +static void object_free_cb(void *key, void *entry) +{ + object_t *o = (object_t *)entry; + + MRP_UNUSED(key); + + mrp_free(o->path); + mrp_free(o); +} + + +static object_t *object_add(mrp_dbus_t *dbus, const char *path) +{ + object_t *o; + + o = mrp_alloc(sizeof(*o)); + + if (o != NULL) { + o->path = mrp_strdup(path); + o->cnt = 1; + + if (o->path == NULL) { + mrp_free(o); + return NULL; + } + + if (sd_bus_add_object(dbus->bus, o->path, dispatch_method, dbus) == 0) { + if (mrp_htbl_insert(dbus->objects, o->path, o)) + return o; + else + sd_bus_remove_object(dbus->bus, o->path, dispatch_method, dbus); + } + + mrp_free(o->path); + mrp_free(o); + } + + return NULL; +} + + +static object_t *object_lookup(mrp_dbus_t *dbus, const char *path) +{ + return mrp_htbl_lookup(dbus->objects, (void *)path); +} + + +static int object_ref(mrp_dbus_t *dbus, const char *path) +{ + object_t *o; + + if ((o = object_lookup(dbus, path)) != NULL) { + o->cnt++; + return TRUE; + } + + if (object_add(dbus, path) != NULL) + return TRUE; + else + return FALSE; +} + + +static void object_unref(mrp_dbus_t *dbus, const char *path) +{ + object_t *o; + + if ((o = object_lookup(dbus, path)) != NULL) { + o->cnt--; + + if (o->cnt <= 0) { + mrp_htbl_remove(dbus->objects, (void *)path, FALSE); + sd_bus_remove_object(dbus->bus, o->path, dispatch_method, dbus); + + mrp_free(o->path); + mrp_free(o); + } + } +} + + +int mrp_dbus_export_method(mrp_dbus_t *dbus, const char *path, + const char *interface, const char *member, + mrp_dbus_handler_t handler, void *user_data) +{ + handler_list_t *methods; + handler_t *m; + + if (!object_ref(dbus, path)) + return FALSE; + + if ((methods = mrp_htbl_lookup(dbus->methods, (void *)member)) == NULL) { + if ((methods = handler_list_alloc(member)) == NULL) + goto fail; + + mrp_htbl_insert(dbus->methods, methods->member, methods); + } + + m = handler_alloc(NULL, path, interface, member, handler, user_data); + + if (m != NULL) { + handler_list_insert(methods, m); + + return TRUE; + } + + fail: + object_unref(dbus, path); + + return FALSE; +} + + +int mrp_dbus_remove_method(mrp_dbus_t *dbus, const char *path, + const char *interface, const char *member, + mrp_dbus_handler_t handler, void *user_data) +{ + handler_list_t *methods; + handler_t *m; + + if ((methods = mrp_htbl_lookup(dbus->methods, (void *)member)) == NULL) + return FALSE; + + m = handler_list_lookup(methods, path, interface, member, + handler, user_data); + if (m != NULL) { + object_unref(dbus, path); + mrp_list_delete(&m->hook); + handler_free(m); + + return TRUE; + } + else + return FALSE; +} + + +int mrp_dbus_add_signal_handler(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, mrp_dbus_handler_t handler, + void *user_data) +{ + handler_list_t *signals; + handler_t *s; + + if ((signals = mrp_htbl_lookup(dbus->signals, (void *)member)) == NULL) { + if ((signals = handler_list_alloc(member)) == NULL) + return FALSE; + + if (!mrp_htbl_insert(dbus->signals, signals->member, signals)) { + handler_list_free(signals); + return FALSE; + } + } + + s = handler_alloc(sender, path, interface, member, handler, user_data); + if (s != NULL) { + handler_list_insert(signals, s); + return TRUE; + } + else { + handler_free(s); + if (mrp_list_empty(&signals->handlers)) + mrp_htbl_remove(dbus->signals, signals->member, TRUE); + return FALSE; + } +} + + + +int mrp_dbus_del_signal_handler(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, mrp_dbus_handler_t handler, + void *user_data) +{ + handler_list_t *signals; + handler_t *s; + + MRP_UNUSED(sender); + + if ((signals = mrp_htbl_lookup(dbus->signals, (void *)member)) == NULL) + return FALSE; + + s = handler_list_lookup(signals, path, interface, member, + handler, user_data); + if (s != NULL) { + mrp_list_delete(&s->hook); + handler_free(s); + + if (mrp_list_empty(&signals->handlers)) + mrp_htbl_remove(dbus->signals, (void *)member, TRUE); + + return TRUE; + } + else + return FALSE; +} + + + +int mrp_dbus_subscribe_signal(mrp_dbus_t *dbus, + mrp_dbus_handler_t handler, void *user_data, + const char *sender, const char *path, + const char *interface, const char *member, ...) +{ + va_list ap; + int success; + + + if (mrp_dbus_add_signal_handler(dbus, sender, path, interface, member, + handler, user_data)) { + va_start(ap, member); + success = mrp_dbus_install_filterv(dbus, + sender, path, interface, member, ap); + va_end(ap); + + if (success) + return TRUE; + else + mrp_dbus_del_signal_handler(dbus, sender, path, interface, member, + handler, user_data); + } + + return FALSE; +} + + +int mrp_dbus_unsubscribe_signal(mrp_dbus_t *dbus, + mrp_dbus_handler_t handler, void *user_data, + const char *sender, const char *path, + const char *interface, const char *member, ...) +{ + va_list ap; + int status; + + status = mrp_dbus_del_signal_handler(dbus, sender, path, interface, member, + handler, user_data); + va_start(ap, member); + status &= mrp_dbus_remove_filterv(dbus, + sender, path, interface, member, ap); + va_end(ap); + + return status; +} + + +int mrp_dbus_install_filterv(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, va_list args) +{ +#define ADD_TAG(tag, value) do { \ + if (value != NULL) { \ + l = snprintf(p, n, "%s%s='%s'", p == filter ? "" : ",", \ + tag, value); \ + if (l >= n) \ + return FALSE; \ + n -= l; \ + p += l; \ + } \ + } while (0) + + va_list ap; + char filter[1024], *p, argn[16], *val; + int n, l, i; + + p = filter; + n = sizeof(filter); + + ADD_TAG("type" , "signal"); + ADD_TAG("sender" , sender); + ADD_TAG("path" , path); + ADD_TAG("interface", interface); + ADD_TAG("member" , member); + + va_copy(ap, args); + i = 0; + while ((val = va_arg(ap, char *)) != NULL) { + snprintf(argn, sizeof(argn), "arg%d", i); + ADD_TAG(argn, val); + i++; + } + va_end(ap); + + if (sd_bus_add_match(dbus->bus, filter, NULL, NULL) != 0) { + mrp_log_error("Failed to install filter '%s'.", filter); + + return FALSE; + } + else + return TRUE; + +} + + +int mrp_dbus_install_filter(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, ...) +{ + va_list ap; + int status; + + va_start(ap, member); + status = mrp_dbus_install_filterv(dbus, + sender, path, interface, member, ap); + va_end(ap); + + return status; +} + + +int mrp_dbus_remove_filterv(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, va_list args) +{ + va_list ap; + char filter[1024], *p, argn[16], *val; + int n, l, i; + + p = filter; + n = sizeof(filter); + + ADD_TAG("type" , "signal"); + ADD_TAG("sender" , sender); + ADD_TAG("path" , path); + ADD_TAG("interface", interface); + ADD_TAG("member" , member); + + va_copy(ap, args); + i = 0; + while ((val = va_arg(ap, char *)) != NULL) { + snprintf(argn, sizeof(argn), "arg%d", i); + ADD_TAG(argn, val); + i++; + } + va_end(ap); + + sd_bus_remove_match(dbus->bus, filter, NULL, NULL); + + return TRUE; +#undef ADD_TAG +} + + +int mrp_dbus_remove_filter(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, ...) +{ + va_list ap; + int status; + + va_start(ap, member); + status = mrp_dbus_remove_filterv(dbus, sender, path, interface, member, ap); + va_end(ap); + + return status; +} + + +static int element_size(mrp_dbus_type_t type) +{ + switch (type) { + case MRP_DBUS_TYPE_BYTE: return sizeof(char); + case MRP_DBUS_TYPE_BOOLEAN: return sizeof(uint32_t); + case MRP_DBUS_TYPE_INT16: + case MRP_DBUS_TYPE_UINT16: return sizeof(uint16_t); + case MRP_DBUS_TYPE_INT32: + case MRP_DBUS_TYPE_UINT32: return sizeof(uint32_t); + case MRP_DBUS_TYPE_INT64: + case MRP_DBUS_TYPE_UINT64: return sizeof(uint64_t); + case MRP_DBUS_TYPE_DOUBLE: return sizeof(double); + case MRP_DBUS_TYPE_STRING: return sizeof(char *); + case MRP_DBUS_TYPE_OBJECT_PATH: return sizeof(char *); + case MRP_DBUS_TYPE_SIGNATURE: return sizeof(char *); + default: + return FALSE; + } + +} + + +static inline mrp_dbus_msg_t *create_message(sd_bus_message *msg, int ref) +{ + mrp_dbus_msg_t *m; + + if (msg != NULL) { + if ((m = mrp_allocz(sizeof(*m))) != NULL) { + mrp_refcnt_init(&m->refcnt); + mrp_list_init(&m->arrays); + if (ref) + m->msg = sd_bus_message_ref(msg); + else + m->msg = msg; + } + + return m; + } + else + return NULL; +} + + +static void free_msg_array(msg_array_t *a) +{ + void *ptr; + size_t esize, i; + int string; + + if (a == NULL) + return; + + mrp_list_delete(&a->hook); + + if ((esize = element_size(a->type)) != 0) { + if (a->type == MRP_DBUS_TYPE_STRING || + a->type == MRP_DBUS_TYPE_OBJECT_PATH || + a->type == MRP_DBUS_TYPE_SIGNATURE) + string = TRUE; + else + string = FALSE; + + if (string) + for (i = 0, ptr = a->items; i < a->nitem; i++, ptr += esize) + mrp_free(ptr); + + mrp_free(a->items); + } + else + mrp_log_error("Hmm... looks like we have a corrupted implicit array."); + + mrp_free(a); +} + + +static void free_message(mrp_dbus_msg_t *m) +{ + mrp_list_hook_t *p, *n; + msg_array_t *a; + + mrp_list_foreach(&m->arrays, p, n) { + a = mrp_list_entry(p, typeof(*a), hook); + free_msg_array(a); + } + + mrp_free(m); +} + + +mrp_dbus_msg_t *mrp_dbus_msg_ref(mrp_dbus_msg_t *m) +{ + return mrp_ref_obj(m, refcnt); +} + + +int mrp_dbus_msg_unref(mrp_dbus_msg_t *m) +{ + if (mrp_unref_obj(m, refcnt)) { + sd_bus_message_unref(m->msg); + free_message(m); + + return TRUE; + } + else + return FALSE; +} + + +static inline int verify_type(sd_bus_message *msg, int expected_type) +{ + uint8_t type; + + if (sd_bus_message_get_type(msg, &type) != 0 || type == expected_type) + return FALSE; + else + return TRUE; +} + + +static int dispatch_method(sd_bus *bus,int ret, sd_bus_message *msg, void *data) +{ +#define SAFESTR(str) (str ? str : "<none>") + mrp_dbus_t *dbus = (mrp_dbus_t *)data; + mrp_dbus_msg_t *m = NULL; + const char *path = sd_bus_message_get_path(msg); + const char *interface = sd_bus_message_get_interface(msg); + const char *member = sd_bus_message_get_member(msg); + int r = FALSE; + handler_list_t *l; + handler_t *h; + + MRP_UNUSED(bus); + MRP_UNUSED(ret); + + if (!verify_type(msg, MRP_DBUS_MESSAGE_TYPE_METHOD_CALL) || !member) + return r; + + mrp_debug("path='%s', interface='%s', member='%s')...", + SAFESTR(path), SAFESTR(interface), SAFESTR(member)); + + if ((l = mrp_htbl_lookup(dbus->methods, (void *)member)) != NULL) { + retry: + if ((h = handler_list_find(l, path, interface, member)) != NULL) { + sd_bus_message_rewind(msg, TRUE); + + if (m == NULL) + m = create_message(msg, TRUE); + + if (h->handler(dbus, m, h->user_data)) + r = TRUE; + + goto out; + } + } + else { + if ((l = mrp_htbl_lookup(dbus->methods, "")) != NULL) + goto retry; + } + + out: + if (!r) + mrp_debug("Unhandled method path=%s, %s.%s.", SAFESTR(path), + SAFESTR(interface), SAFESTR(member)); + + mrp_dbus_msg_unref(m); + + return r; +} + + +static int dispatch_signal(sd_bus *bus,int ret, sd_bus_message *msg, void *data) +{ +#define MATCHES(h, field) (!*field || !h->field || !*h->field || \ + !strcmp(field, h->field)) + mrp_dbus_t *dbus = (mrp_dbus_t *)data; + mrp_dbus_msg_t *m = NULL; + const char *path = sd_bus_message_get_path(msg); + const char *interface = sd_bus_message_get_interface(msg); + const char *member = sd_bus_message_get_member(msg); + mrp_list_hook_t *p, *n; + handler_list_t *l; + handler_t *h; + int retried = FALSE; + int handled = FALSE; + + MRP_UNUSED(bus); + MRP_UNUSED(ret); + + if (!verify_type(msg, MRP_DBUS_MESSAGE_TYPE_SIGNAL) || !member) + return FALSE; + + mrp_debug("%s(path='%s', interface='%s', member='%s')...", + __FUNCTION__, SAFESTR(path), SAFESTR(interface), SAFESTR(member)); + + if ((l = mrp_htbl_lookup(dbus->signals, (void *)member)) != NULL) { + retry: + mrp_list_foreach(&l->handlers, p, n) { + h = mrp_list_entry(p, handler_t, hook); + + if (MATCHES(h,path) && MATCHES(h,interface) && MATCHES(h,member)) { + sd_bus_message_rewind(msg, TRUE); + + if (m == NULL) + m = create_message(msg, TRUE); + + h->handler(dbus, m, h->user_data); + handled = TRUE; + } + } + } + + if (!retried) { + if ((l = mrp_htbl_lookup(dbus->signals, "")) != NULL) { + retried = TRUE; + goto retry; + } + } + + if (!handled) + mrp_debug("Unhandled signal path=%s, %s.%s.", SAFESTR(path), + SAFESTR(interface), SAFESTR(member)); + + mrp_dbus_msg_unref(m); + + return FALSE; +#undef MATCHES +#undef SAFESTR +} + + +static int append_args_strtype(mrp_dbus_msg_t *msg, const char *types, + va_list ap) +{ + MRP_UNUSED(msg); + MRP_UNUSED(types); + MRP_UNUSED(ap); + + return FALSE; +} + + +static int append_args_inttype(sd_bus_message *msg, int type, va_list ap) +{ + void *vptr; + int atype, elen, i; + void **aptr; + int alen; + char stype[2] = { '\0', '\0' }; + int r = 0; + + (void)append_args_strtype; + + while (type != MRP_DBUS_TYPE_INVALID) { + switch (type) { + case MRP_DBUS_TYPE_BYTE: + case MRP_DBUS_TYPE_BOOLEAN: + case MRP_DBUS_TYPE_INT16: + case MRP_DBUS_TYPE_UINT16: + case MRP_DBUS_TYPE_INT32: + case MRP_DBUS_TYPE_UINT32: + case MRP_DBUS_TYPE_INT64: + case MRP_DBUS_TYPE_UINT64: + case MRP_DBUS_TYPE_DOUBLE: + case MRP_DBUS_TYPE_STRING: + case MRP_DBUS_TYPE_OBJECT_PATH: + case MRP_DBUS_TYPE_SIGNATURE: + case MRP_DBUS_TYPE_UNIX_FD: + vptr = va_arg(ap, void *); + r = sd_bus_message_append_basic(msg, type, vptr); + break; + + case MRP_DBUS_TYPE_ARRAY: + atype = va_arg(ap, int); + aptr = va_arg(ap, void **); + alen = va_arg(ap, int); + + switch (atype) { +#define LEN(_type, _size) case MRP_DBUS_TYPE_##_type: elen = _size; break + LEN(BYTE , sizeof(uint8_t)); + LEN(BOOLEAN , sizeof(uint32_t)); + LEN(INT16 , sizeof(int16_t)); + LEN(UINT16 , sizeof(uint16_t)); + LEN(INT32 , sizeof(int32_t)); + LEN(UINT32 , sizeof(uint32_t)); + LEN(INT64 , sizeof(int64_t)); + LEN(UINT64 , sizeof(uint64_t)); + LEN(DOUBLE , sizeof(double)); + LEN(STRING , sizeof(const char *)); + LEN(OBJECT_PATH, sizeof(const char *)); + LEN(SIGNATURE , sizeof(const char *)); + LEN(UNIX_FD , sizeof(int)); +#undef LEN + default: + return FALSE; + } + + stype[0] = atype; + if (sd_bus_message_open_container(msg, type, stype) != 0) + return FALSE; + for (i = 0; i < alen; i++, aptr += elen) + if (sd_bus_message_append_basic(msg, atype, aptr) != 0) + return FALSE; + if (sd_bus_message_close_container(msg) != 0) + return FALSE; + else + return TRUE; + break; + + default: + return FALSE; + } + + type = va_arg(ap, int); + } + + return (r == 0 ? TRUE : FALSE); +} + + +static int call_reply_cb(sd_bus *bus, int ret, sd_bus_message *msg, + void *user_data) +{ + call_t *call = (call_t *)user_data; + mrp_dbus_msg_t *reply = create_message(msg, TRUE); + sd_bus_error error; + + MRP_UNUSED(bus); + + call->serial = 0; + mrp_list_delete(&call->hook); + + if (ret == 0) { + reply = create_message(msg, TRUE); + sd_bus_message_rewind(reply->msg, TRUE); + } + else { + sd_bus_message *err = NULL; + + if (ret == ETIMEDOUT) + error = SD_BUS_ERROR_MAKE(MRP_DBUS_ERROR_TIMEOUT, + "D-Bus call timed out"); + else + error = SD_BUS_ERROR_MAKE(MRP_DBUS_ERROR_FAILED, + "D-Bus call failed"); + + if (sd_bus_message_new_method_error(bus, call->msg, &error, &err) == 0) + reply = create_message(err, FALSE); + else + reply = NULL; + } + + call->cb(call->dbus, reply, call->user_data); + call_free(call); + mrp_dbus_msg_unref(reply); + + return TRUE; +} + + +int32_t mrp_dbus_call(mrp_dbus_t *dbus, const char *dest, const char *path, + const char *interface, const char *member, int timeout, + mrp_dbus_reply_cb_t cb, void *user_data, int type, ...) +{ + va_list ap; + int32_t id; + call_t *call; + sd_bus_message *msg; + int success; + + call = NULL; + + if (sd_bus_message_new_method_call(dbus->bus, dest, path, interface, member, + &msg) != 0) + return 0; + + if (cb != NULL) { + if ((call = mrp_allocz(sizeof(*call))) != NULL) { + mrp_list_init(&call->hook); + + call->dbus = dbus; + call->id = dbus->call_id++; + call->cb = cb; + call->user_data = user_data; + + id = call->id; + } + else + goto fail; + } + else + id = dbus->call_id++; + + if (type == MRP_DBUS_TYPE_INVALID) + success = TRUE; + else { + va_start(ap, type); + success = append_args_inttype(msg, type, ap); + va_end(ap); + } + + if (!success) + goto fail; + + if (cb == NULL) { + sd_bus_message_set_no_reply(msg, TRUE); + if (sd_bus_send(dbus->bus, msg, NULL) != 0) + goto fail; + sd_bus_message_unref(msg); + } + else { + if (sd_bus_send_with_reply(dbus->bus, msg, call_reply_cb, call, + timeout * 1000, &call->serial) != 0) + goto fail; + + mrp_list_append(&dbus->calls, &call->hook); + call->msg = msg; + } + + return id; + + fail: + sd_bus_message_unref(msg); + call_free(call); + + return 0; +} + + +int mrp_dbus_send_msg(mrp_dbus_t *dbus, mrp_dbus_msg_t *m) +{ + /*bus_message_dump(m->msg);*/ + + if (sd_bus_send(dbus->bus, m->msg, NULL) == 0) + return TRUE; + else + return FALSE; +} + + +int mrp_dbus_call_cancel(mrp_dbus_t *dbus, int32_t id) +{ + mrp_list_hook_t *p, *n; + call_t *call; + + mrp_list_foreach(&dbus->calls, p, n) { + call = mrp_list_entry(p, call_t, hook); + + if (call->id == id) { + mrp_list_delete(p); + + sd_bus_send_with_reply_cancel(dbus->bus, call->serial); + call->serial = 0; + + call_free(call); + return TRUE; + } + } + + return FALSE; +} + + +int mrp_dbus_reply(mrp_dbus_t *dbus, mrp_dbus_msg_t *m, int type, ...) +{ + va_list ap; + sd_bus_message *rpl; + int success; + + if (sd_bus_message_new_method_return(dbus->bus, m->msg, &rpl) != 0) + return FALSE; + + va_start(ap, type); + success = append_args_inttype(rpl, type, ap); + va_end(ap); + + if (!success) + goto fail; + + if (sd_bus_send(dbus->bus, rpl, NULL) != 0) + goto fail; + + sd_bus_message_unref(rpl); + + return TRUE; + + fail: + sd_bus_message_unref(rpl); + + return FALSE; +} + + +int mrp_dbus_reply_error(mrp_dbus_t *dbus, mrp_dbus_msg_t *m, + const char *errname, const char *errmsg, int type, ...) +{ + va_list ap; + sd_bus_message *rpl; + int success; + sd_bus_error err = SD_BUS_ERROR_NULL;; + + sd_bus_error_set_const(&err, errname, errmsg); + + if (sd_bus_message_new_method_error(dbus->bus, m->msg, &err, &rpl) != 0) + return FALSE; + + va_start(ap, type); + success = append_args_inttype(rpl, type, ap); + va_end(ap); + + if (!success) + goto fail; + + if (sd_bus_send(dbus->bus, rpl, NULL) != 0) + goto fail; + + sd_bus_message_unref(rpl); + + return TRUE; + + fail: + sd_bus_message_unref(rpl); + + return FALSE; +} + + +static void call_free(call_t *call) +{ + if (call != NULL) { + sd_bus_message_unref(call->msg); + mrp_free(call); + } +} + + +static void purge_calls(mrp_dbus_t *dbus) +{ + mrp_list_hook_t *p, *n; + call_t *call; + + mrp_list_foreach(&dbus->calls, p, n) { + call = mrp_list_entry(p, call_t, hook); + + mrp_list_delete(&call->hook); + + if (call->serial != 0) + sd_bus_send_with_reply_cancel(dbus->bus, call->serial); + + mrp_free(call); + } +} + + +int mrp_dbus_signal(mrp_dbus_t *dbus, const char *dest, const char *path, + const char *interface, const char *member, int type, ...) +{ + va_list ap; + sd_bus_message *msg; + int success; + + if (sd_bus_message_new_signal(dbus->bus, path, interface, member, + &msg) != 0) + return 0; + + va_start(ap, type); + success = append_args_inttype(msg, type, ap); + va_end(ap); + + if (!success) + goto fail; + + if (dest != NULL) + if (sd_bus_message_set_destination(msg, dest) != 0) + goto fail; + + if (sd_bus_send(dbus->bus, msg, NULL) != 0) + goto fail; + + sd_bus_message_unref(msg); + + return TRUE; + + fail: + sd_bus_message_unref(msg); + + return 0; +} + + +mrp_dbus_msg_t *mrp_dbus_msg_method_call(mrp_dbus_t *dbus, + const char *destination, + const char *path, + const char *interface, + const char *member) +{ + sd_bus_message *msg; + + if (sd_bus_message_new_method_call(dbus->bus, destination, + path, interface, member, &msg) == 0) + return create_message(msg, FALSE); + else + return NULL; +} + + +mrp_dbus_msg_t *mrp_dbus_msg_method_return(mrp_dbus_t *dbus, + mrp_dbus_msg_t *msg) +{ + sd_bus_message *req, *rpl; + + req = (sd_bus_message *)msg; + + if (sd_bus_message_new_method_return(dbus->bus, req, &rpl) == 0) + return create_message(rpl, FALSE); + else + return NULL; +} + + +mrp_dbus_msg_t *mrp_dbus_msg_error(mrp_dbus_t *dbus, mrp_dbus_msg_t *m, + mrp_dbus_err_t *err) +{ + sd_bus_message *req, *rpl; + + req = m->msg; + + if (sd_bus_message_new_method_error(dbus->bus, req, err, &rpl) == 0) + return create_message(rpl, FALSE); + else + return NULL; +} + + +mrp_dbus_msg_t *mrp_dbus_msg_signal(mrp_dbus_t *dbus, + const char *destination, + const char *path, + const char *interface, + const char *member) +{ + sd_bus_message *msg = NULL; + + if (sd_bus_message_new_signal(dbus->bus, path, interface, member, + &msg) == 0) { + if (destination != NULL) { + if (sd_bus_message_set_destination(msg, destination) != 0) { + sd_bus_message_unref(msg); + msg = NULL; + } + } + } + + return create_message(msg, FALSE); +} + + +mrp_dbus_msg_type_t mrp_dbus_msg_type(mrp_dbus_msg_t *m) +{ + uint8_t type; + + if (sd_bus_message_get_type(m->msg, &type) == 0) + return (mrp_dbus_msg_type_t)type; + else + return MRP_DBUS_MESSAGE_TYPE_INVALID; +} + +#define WRAP_GETTER(type, what) \ + type mrp_dbus_msg_##what(mrp_dbus_msg_t *m) \ + { \ + return sd_bus_message_get_##what((sd_bus_message *)m->msg); \ + } \ + struct __mrp_dbus_allow_trailing_semicolon + +WRAP_GETTER(const char *, path); +WRAP_GETTER(const char *, interface); +WRAP_GETTER(const char *, member); +WRAP_GETTER(const char *, destination); +WRAP_GETTER(const char *, sender); + +#undef WRAP_GETTER + + +int mrp_dbus_msg_open_container(mrp_dbus_msg_t *m, char type, + const char *contents) +{ + return sd_bus_message_open_container(m->msg, type, contents) == 0; +} + + +int mrp_dbus_msg_close_container(mrp_dbus_msg_t *m) +{ + return sd_bus_message_close_container(m->msg) == 0; +} + + +int mrp_dbus_msg_append_basic(mrp_dbus_msg_t *m, char type, void *valuep) +{ + return sd_bus_message_append_basic(m->msg, type, valuep) == 0; +} + + +int mrp_dbus_msg_enter_container(mrp_dbus_msg_t *m, char type, + const char *contents) +{ + return sd_bus_message_enter_container(m->msg, type, contents) == 1; +} + + +int mrp_dbus_msg_exit_container(mrp_dbus_msg_t *m) +{ + return sd_bus_message_exit_container(m->msg) == 1; +} + + +int mrp_dbus_msg_read_basic(mrp_dbus_msg_t *m, char type, void *valuep) +{ + return sd_bus_message_read_basic(m->msg, type, valuep) == 1; +} + + +int mrp_dbus_msg_read_array(mrp_dbus_msg_t *m, char type, + void **itemsp, size_t *nitemp) +{ + char sub[2] = { (char)type, '\0' }; + msg_array_t *a; + int offs; + size_t esize; + + if ((esize = element_size(type)) == 0) + return FALSE; + + if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, sub)) + return FALSE; + + if ((a = mrp_allocz(sizeof(*a))) == NULL) + goto fail; + + a->type = type; + mrp_list_init(&a->hook); + + offs = 0; + while (mrp_dbus_msg_arg_type(m, NULL) != MRP_DBUS_TYPE_INVALID) { + if (!mrp_realloc(a->items, offs + esize)) + goto fail; + + if (!mrp_dbus_msg_read_basic(m, type, a->items + offs)) + goto fail; + else + a->nitem++; + + offs += esize; + } + + mrp_dbus_msg_exit_container(m); + + mrp_list_append(&m->arrays, &a->hook); + *itemsp = a->items; + *nitemp = a->nitem; + + return TRUE; + + fail: + mrp_dbus_msg_exit_container(m); + free_msg_array(a); + + return FALSE; +} + + +mrp_dbus_type_t mrp_dbus_msg_arg_type(mrp_dbus_msg_t *m, const char **contents) +{ + char type; + + if (sd_bus_message_peek_type(m->msg, &type, contents) >= 0) + return (mrp_dbus_type_t)type; + else + return MRP_DBUS_TYPE_INVALID; +} diff --git a/src/common/dbus-sdbus.h b/src/common/dbus-sdbus.h new file mode 100644 index 0000000..982b0f6 --- /dev/null +++ b/src/common/dbus-sdbus.h @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2012, 2013, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_SD_BUS_H__ +#define __MURPHY_SD_BUS_H__ + +#include <systemd/sd-bus.h> +#include <systemd/sd-bus-protocol.h> + +#include <murphy/common/mainloop.h> +#include <murphy/common/dbus-error.h> + +/** Type for a D-Bus (connection). */ +struct mrp_dbus_s; +typedef struct mrp_dbus_s mrp_dbus_t; + +/** Type for a D-Bus message. */ +typedef struct mrp_dbus_msg_s mrp_dbus_msg_t; + +/** Type for a D-Bus error. */ +typedef sd_bus_error mrp_dbus_err_t; + +/** D-BUS method or signal callback type. */ +typedef int (*mrp_dbus_handler_t)(mrp_dbus_t *, mrp_dbus_msg_t *, void *); + +/** Create a new connection to the given bus. */ +mrp_dbus_t *mrp_dbus_connect(mrp_mainloop_t *ml, const char *address, + mrp_dbus_err_t *errp); +#define mrp_dbus_get mrp_dbus_connect + +/** Set up an sd-bus instance with a mainloop. */ +int mrp_dbus_setup_sd_bus(mrp_mainloop_t *ml, sd_bus *bus); + +/** Increase the reference count of the given DBus (connection). */ +mrp_dbus_t *mrp_dbus_ref(mrp_dbus_t *dbus); + +/** Decrease the reference count of the given DBus (connection). */ +int mrp_dbus_unref(mrp_dbus_t *dbus); + +/** Acquire the given name on the given bus (connection). */ +int mrp_dbus_acquire_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_err_t *error); + +/** Release the given name on the given bus (connection). */ +int mrp_dbus_release_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_err_t *error); + +/** Type for a name tracking callback. */ +typedef void (*mrp_dbus_name_cb_t)(mrp_dbus_t *, const char *, int, + const char *, void *); +/** Start tracking the given name. */ +int mrp_dbus_follow_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_name_cb_t cb, void *user_data); +/** Stop tracking the given name. */ +int mrp_dbus_forget_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_name_cb_t cb, void *user_data); + +/** Export a method to the bus. */ +int mrp_dbus_export_method(mrp_dbus_t *dbus, const char *path, + const char *interface, const char *member, + mrp_dbus_handler_t handler, void *user_data); + +/** Remove an exported method. */ +int mrp_dbus_remove_method(mrp_dbus_t *dbus, const char *path, + const char *interface, const char *member, + mrp_dbus_handler_t handler, void *user_data); + +/** Install a filter and add a handler for the given signal on the bus. */ +MRP_NULLTERM int mrp_dbus_subscribe_signal(mrp_dbus_t *dbus, + mrp_dbus_handler_t handler, + void *user_data, + const char *sender, + const char *path, + const char *interface, + const char *member, ...); + +/** Remove the signal handler and filter for the given signal on the bus. */ +MRP_NULLTERM int mrp_dbus_unsubscribe_signal(mrp_dbus_t *dbus, + mrp_dbus_handler_t handler, + void *user_data, + const char *sender, + const char *path, + const char *interface, + const char *member, ...); + +/** Install a filter for the given message on the bus. */ +MRP_NULLTERM int mrp_dbus_install_filter(mrp_dbus_t *dbus, + const char *sender, + const char *path, + const char *interface, + const char *member, ...); + +/** Install a filter for the given message on the bus. */ +int mrp_dbus_install_filterv(mrp_dbus_t *dbus, + const char *sender, + const char *path, + const char *interface, + const char *member, + va_list ap); + +/** Remove a filter for the given message on the bus. */ +MRP_NULLTERM int mrp_dbus_remove_filter(mrp_dbus_t *dbus, + const char *sender, + const char *path, + const char *interface, + const char *member, ...); + +/** Remove a filter for the given message on the bus. */ +int mrp_dbus_remove_filterv(mrp_dbus_t *dbus, + const char *sender, + const char *path, + const char *interface, + const char *member, + va_list ap); + +/** Add a signal handler for the gvien signal on the bus. */ +int mrp_dbus_add_signal_handler(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, mrp_dbus_handler_t handler, + void *user_data); + +/** Remove the given signal handler for the given signal on the bus. */ +int mrp_dbus_del_signal_handler(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, mrp_dbus_handler_t handler, + void *user_data); + +/** Type of a method call reply callback. */ +typedef void (*mrp_dbus_reply_cb_t)(mrp_dbus_t *dbus, mrp_dbus_msg_t *reply, + void *user_data); + +/** Call the given method on the bus. */ +int32_t mrp_dbus_call(mrp_dbus_t *dbus, const char *dest, + const char *path, const char *interface, + const char *member, int timeout, + mrp_dbus_reply_cb_t cb, void *user_data, + int dbus_type, ...); + +/** Cancel an ongoing method call on the bus. */ +int mrp_dbus_call_cancel(mrp_dbus_t *dbus, int32_t id); + +/** Send a reply to the given method call on the bus. */ +int mrp_dbus_reply(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, int type, ...); + +/** Send an error reply to the given method call on the bus. */ +int mrp_dbus_reply_error(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, + const char *errname, const char *errmsg, + int type, ...); + +/** Emit the given signal on the bus. */ +int mrp_dbus_signal(mrp_dbus_t *dbus, const char *dest, const char *path, + const char *interface, const char *member, int type, ...); + +/** Send the given message on the bus. */ +int mrp_dbus_send_msg(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg); + +/** Get our unique name on the bus. */ +const char *mrp_dbus_get_unique_name(mrp_dbus_t *dbus); + +/** Initialize the given error. */ +static inline mrp_dbus_err_t *mrp_dbus_error_init(mrp_dbus_err_t *err) +{ + if (err != NULL) + memset(err, 0, sizeof(*err)); + + return err; +} + + +/** Set the given error buffer up with the error name and message. */ +static inline mrp_dbus_err_t *mrp_dbus_error_set(mrp_dbus_err_t *err, + const char *name, + const char *message) +{ + sd_bus_error_set(err, name, "%s", message); + + return err; +} + + +/** Get the error message from the given bus error message. */ +static inline const char *mrp_dbus_errmsg(mrp_dbus_err_t *err) +{ + if (err && sd_bus_error_is_set(err)) + return err->message; + else + return "unknown DBUS error"; +} + + +/** Increase the reference count of a message. */ +mrp_dbus_msg_t *mrp_dbus_msg_ref(mrp_dbus_msg_t *m); + +/** Decrease the reference count of a message, freeing it if necessary. */ +int mrp_dbus_msg_unref(mrp_dbus_msg_t *m); + + +/** Create a new method call message. */ +mrp_dbus_msg_t *mrp_dbus_msg_method_call(mrp_dbus_t *bus, + const char *destination, + const char *path, + const char *interface, + const char *member); + +/** Create a new method return message. */ +mrp_dbus_msg_t *mrp_dbus_msg_method_return(mrp_dbus_t *bus, + mrp_dbus_msg_t *msg); + +/** Create a new error reply message. */ +mrp_dbus_msg_t *mrp_dbus_msg_error(mrp_dbus_t *bus, mrp_dbus_msg_t *msg, + mrp_dbus_err_t *err); + +/** Create a new signal message. */ +mrp_dbus_msg_t *mrp_dbus_msg_signal(mrp_dbus_t *bus, + const char *destination, + const char *path, + const char *interface, + const char *member); + +/** Bus message types. */ +typedef enum { +#ifndef SD_BUS_MESSAGE_TYPE_INVALID +# define SD_BUS_MESSAGE_TYPE_INVALID _SD_BUS_MESSAGE_TYPE_INVALID +#endif +# define MAP(t, f) MRP_DBUS_MESSAGE_TYPE_##t = SD_BUS_MESSAGE_TYPE_##f + MAP(INVALID , INVALID), + MAP(METHOD_CALL , METHOD_CALL), + MAP(METHOD_RETURN, METHOD_RETURN), + MAP(ERROR , METHOD_ERROR), + MAP(SIGNAL , SIGNAL) +# undef MAP +} mrp_dbus_msg_type_t; + +/** Get the type of the given message. */ +mrp_dbus_msg_type_t mrp_dbus_msg_type(mrp_dbus_msg_t *msg); + +/** Message type checking convenience functions. */ +#define TYPE_CHECK_FUNCTION(type, TYPE) \ + static inline int mrp_dbus_msg_is_##type(mrp_dbus_msg_t *msg) \ + { \ + return mrp_dbus_msg_type(msg) == MRP_DBUS_MESSAGE_TYPE_##TYPE; \ + } \ + struct __mrp_dbus_allow_traling_semicolon + +TYPE_CHECK_FUNCTION(method_call , METHOD_CALL); +TYPE_CHECK_FUNCTION(method_return, METHOD_RETURN); +TYPE_CHECK_FUNCTION(error , ERROR); +TYPE_CHECK_FUNCTION(signal , SIGNAL); + +/** Message argument types. */ +typedef enum { +#ifndef SD_BUS_TYPE_INVALID +# define SD_BUS_TYPE_INVALID _SD_BUS_TYPE_INVALID +#endif +#define TYPE(t) MRP_DBUS_TYPE_##t = SD_BUS_TYPE_##t + TYPE(INVALID), + TYPE(BYTE), + TYPE(BOOLEAN), + TYPE(INT16), + TYPE(UINT16), + TYPE(INT32), + TYPE(UINT32), + TYPE(INT64), + TYPE(UINT64), + TYPE(DOUBLE), + TYPE(STRING), + TYPE(OBJECT_PATH), + TYPE(SIGNATURE), + TYPE(UNIX_FD), + TYPE(ARRAY), + TYPE(VARIANT), + TYPE(STRUCT), + TYPE(DICT_ENTRY), + TYPE(STRUCT_BEGIN), + TYPE(STRUCT_END), + TYPE(DICT_ENTRY_BEGIN), + TYPE(DICT_ENTRY_END) +#undef TYPE +} mrp_dbus_type_t; + +/** Message argument types as strings. */ +static const char _type_as_string[][2] = { +#define MAP(_type) [SD_BUS_TYPE_##_type] = { SD_BUS_TYPE_##_type, '\0' } + MAP(BYTE), + MAP(BOOLEAN), + MAP(INT16), + MAP(UINT16), + MAP(INT32), + MAP(UINT32), + MAP(INT64), + MAP(UINT64), + MAP(DOUBLE), + MAP(STRING), + MAP(OBJECT_PATH), + MAP(SIGNATURE), + MAP(UNIX_FD), + MAP(ARRAY), + MAP(VARIANT), + MAP(STRUCT), + MAP(DICT_ENTRY), + MAP(STRUCT_BEGIN), + MAP(STRUCT_END), + MAP(DICT_ENTRY_BEGIN), + MAP(DICT_ENTRY_END) +#undef MAP +}; + +#define _STRTYPE(_type) _type_as_string[SD_BUS_TYPE_##_type] +#define _EVAL(_type) _type +#define MRP_DBUS_TYPE_BYTE_AS_STRING _EVAL(_STRTYPE(BYTE)) +#define MRP_DBUS_TYPE_BOOLEAN_AS_STRING _EVAL(_STRTYPE(BOOLEAN)) +#define MRP_DBUS_TYPE_INT16_AS_STRING _EVAL(_STRTYPE(INT16)) +#define MRP_DBUS_TYPE_UINT16_AS_STRING _EVAL(_STRTYPE(UINT16)) +#define MRP_DBUS_TYPE_INT32_AS_STRING _EVAL(_STRTYPE(INT32)) +#define MRP_DBUS_TYPE_UINT32_AS_STRING _EVAL(_STRTYPE(UINT32)) +#define MRP_DBUS_TYPE_INT64_AS_STRING _EVAL(_STRTYPE(INT64)) +#define MRP_DBUS_TYPE_UINT64_AS_STRING _EVAL(_STRTYPE(UINT64)) +#define MRP_DBUS_TYPE_DOUBLE_AS_STRING _EVAL(_STRTYPE(DOUBLE)) +#define MRP_DBUS_TYPE_STRING_AS_STRING _EVAL(_STRTYPE(STRING)) +#define MRP_DBUS_TYPE_OBJECT_PATH_AS_STRING _EVAL(_STRTYPE(OBJECT_PATH)) +#define MRP_DBUS_TYPE_SIGNATURE_AS_STRING _EVAL(_STRTYPE(SIGNATURE)) +#define MRP_DBUS_TYPE_UNIX_FD_AS_STRING _EVAL(_STRTYPE(UNIX_FD)) +#define MRP_DBUS_TYPE_ARRAY_AS_STRING _EVAL(_STRTYPE(ARRAY)) +#define MRP_DBUS_TYPE_VARIANT_AS_STRING _EVAL(_STRTYPE(VARIANT)) +#define MRP_DBUS_TYPE_STRUCT_AS_STRING _EVAL(_STRTYPE(STRUCT)) +#define MRP_DBUS_TYPE_DICT_ENTRY_AS_STRING _EVAL(_STRTYPE(DICT_ENTRY)) + +/** Get the path of the given message. */ +const char *mrp_dbus_msg_path(mrp_dbus_msg_t *msg); + +/** Get the interface of the given message. */ +const char *mrp_dbus_msg_interface(mrp_dbus_msg_t *msg); + +/** Get the member of the given message. */ +const char *mrp_dbus_msg_member(mrp_dbus_msg_t *msg); + +/** Get the destination of the given message. */ +const char *mrp_dbus_msg_destination(mrp_dbus_msg_t *msg); + +/** Get the sender of the given message. */ +const char *mrp_dbus_msg_sender(mrp_dbus_msg_t *msg); + +/** Open a new container of the given type and cotained types. */ +int mrp_dbus_msg_open_container(mrp_dbus_msg_t *m, char type, + const char *contents); + +/** Close the current container. */ +int mrp_dbus_msg_close_container(mrp_dbus_msg_t *m); + +/** Append an argument of a basic type to the given message. */ +int mrp_dbus_msg_append_basic(mrp_dbus_msg_t *m, char type, void *valuep); + +/** Get the type of the current message argument. */ +mrp_dbus_type_t mrp_dbus_msg_arg_type(mrp_dbus_msg_t *m, const char **contents); + +/** Open the current container (of the given type and contents) for reading. */ +int mrp_dbus_msg_enter_container(mrp_dbus_msg_t *msg, char type, + const char *contents); + +/** Exit from the container being currently read. */ +int mrp_dbus_msg_exit_container(mrp_dbus_msg_t *m); + +/** Read the next argument (of basic type) from the given message. */ +int mrp_dbus_msg_read_basic(mrp_dbus_msg_t *m, char type, void *valuep); + +/** Read the next array of one of the basic types. */ +int mrp_dbus_msg_read_array(mrp_dbus_msg_t *m, char type, + void **itemsp, size_t *nitemp); + +/** Set up an sd_bus to be pumped by a murphy mainloop. */ +int mrp_dbus_setup_with_mainloop(mrp_mainloop_t *ml, sd_bus *bus); +#endif /* __MURPHY_SD_BUS_H__ */ diff --git a/src/common/dbus-transport.c b/src/common/dbus-transport.c new file mode 100644 index 0000000..dda1c8e --- /dev/null +++ b/src/common/dbus-transport.c @@ -0,0 +1,1721 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/list.h> +#include <murphy/common/log.h> +#include <murphy/common/msg.h> +#include <murphy/common/transport.h> +#include <murphy/common/libdbus.h> +#include <murphy/common/dbus-transport.h> + +#define DBUS "dbus" +#define DBUSL 4 + +#define TRANSPORT_PATH "/murphy/transport" +#define TRANSPORT_INTERFACE "Murphy.Transport" +#define TRANSPORT_MESSAGE "DeliverMessage" +#define TRANSPORT_DATA "DeliverData" +#define TRANSPORT_RAW "DeliverRaw" +#define TRANSPORT_METHOD "DeliverMessage" + +#define ANY_ADDRESS "any" + +typedef struct { + MRP_TRANSPORT_PUBLIC_FIELDS; /* common transport fields */ + mrp_dbus_t *dbus; /* D-BUS connection */ + int bound : 1; /* whether bound to an address */ + int peer_resolved : 1; /* connected and peer name known */ + mrp_dbusaddr_t local; /* address we're bound to */ + mrp_dbusaddr_t remote; /* address we're connected to */ +} dbus_t; + + +static uint32_t nauto; /* for autobinding */ + + +static int dbus_msg_cb(mrp_dbus_t *dbus, DBusMessage *msg, void *user_data); +static int dbus_data_cb(mrp_dbus_t *dbus, DBusMessage *msg, void *user_data); +static int dbus_raw_cb(mrp_dbus_t *dbus, DBusMessage *msg, void *user_data); + +static void peer_state_cb(mrp_dbus_t *dbus, const char *name, int up, + const char *owner, void *user_data); + +static DBusMessage *msg_encode(const char *sender_id, mrp_msg_t *msg); +static mrp_msg_t *msg_decode(DBusMessage *m, const char **sender_id); + +static DBusMessage *data_encode(const char *sender_id, + void *data, uint16_t tag); +static void *data_decode(DBusMessage *m, uint16_t *tag, const char **sender_id); + +static DBusMessage *raw_encode(const char *sender_id, void *data, size_t size); +static void *raw_decode(DBusMessage *m, size_t *sizep, const char **sender_id); + + + +static socklen_t parse_address(const char *str, mrp_dbusaddr_t *addr, + socklen_t size) +{ + const char *p, *e; + char *q; + size_t l, n; + + if (size < sizeof(*addr)) { + errno = EINVAL; + return FALSE; + } + + if (strncmp(str, DBUS":", DBUSL + 1)) + return 0; + else + str += DBUSL + 1; + + /* + * The format of the address is + * dbus:[bus-address]@address/path + * eg. + * dbus:[session]@:1.33/client1, or + * dbus:[unix:abstract=/tmp/dbus-Xx2Kpi...a572]@:1.33/client1 + */ + + p = str; + q = addr->db_fqa; + l = sizeof(addr->db_fqa); + + /* get bus address */ + if (*p != '[') { + errno = EINVAL; + return 0; + } + else + p++; + + e = strchr(p, ']'); + + if (e == NULL) { + errno = EINVAL; + return 0; + } + + n = e - p; + if (n >= l) { + errno = ENAMETOOLONG; + return 0; + } + + /* save bus address */ + strncpy(q, p, n); + q[n] = '\0'; + addr->db_bus = q; + + q += n + 1; + l -= n + 1; + p = e + 1; + + /* get (local or remote) address on bus */ + if (*p != '@') + addr->db_addr = ANY_ADDRESS; + else { + p++; + e = strchr(p, '/'); + + if (e == NULL) { + errno = EINVAL; + return 0; + } + + n = e - p; + if (n >= l) { + errno = ENAMETOOLONG; + return 0; + } + + /* save address on bus */ + strncpy(q, p, n); + q[n] = '\0'; + addr->db_addr = q; + + q += n + 1; + l -= n + 1; + p = e; + } + + /* get object path */ + if (*p != '/') { + errno = EINVAL; + return 0; + } + + n = snprintf(q, l, "%s%s", TRANSPORT_PATH, p); + if (n >= l) { + errno = ENAMETOOLONG; + return 0; + } + + addr->db_path = q; + addr->db_family = MRP_AF_DBUS; + + return sizeof(*addr); +} + + +static mrp_dbusaddr_t *copy_address(mrp_dbusaddr_t *dst, mrp_dbusaddr_t *src) +{ + char *p, *q; + size_t l, n; + + dst->db_family = src->db_family; + + /* copy bus address */ + p = src->db_bus; + q = dst->db_fqa; + l = sizeof(dst->db_fqa); + + n = strlen(p); + if (l < n + 1) { + errno = EOVERFLOW; + return NULL; + } + + dst->db_bus = q; + memcpy(q, p, n + 1); + q += n + 1; + l -= n + 1; + + /* copy address */ + p = src->db_addr; + n = strlen(p); + if (l < n + 1) { + errno = EOVERFLOW; + return NULL; + } + + dst->db_addr = q; + memcpy(q, p, n + 1); + q += n + 1; + l -= n + 1; + + /* copy path */ + p = src->db_path; + n = strlen(p); + if (l < n + 1) { + errno = EOVERFLOW; + return NULL; + } + + dst->db_path = q; + memcpy(q, p, n + 1); + q += n + 1; + l -= n + 1; + + return dst; +} + + +static inline int check_address(mrp_sockaddr_t *addr, socklen_t addrlen) +{ + mrp_dbusaddr_t *a = (mrp_dbusaddr_t *)addr; + + return (a && a->db_family == MRP_AF_DBUS && addrlen == sizeof(*a)); +} + + +static size_t peer_address(mrp_sockaddr_t *addrp, const char *sender, + const char *path) +{ + mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp; + const char *p; + char *q; + int l, n; + + q = addr->db_fqa; + l = sizeof(addr->db_fqa); + p = ANY_ADDRESS; + n = 3; + + addr->db_bus = q; + memcpy(q, p, n + 1); + q += n + 1; + l -= n + 1; + + addr->db_addr = q; + p = sender; + n = strlen(sender); + + if (l < n + 1) + return 0; + + memcpy(q, p, n + 1); + q += n + 1; + l -= n + 1; + + addr->db_path = q; + p = path; + n = strlen(p); + + if (l < n + 1) + return 0; + + memcpy(q, p, n + 1); + + return sizeof(addrp); +} + + +static socklen_t dbus_resolve(const char *str, mrp_sockaddr_t *addr, + socklen_t size, const char **typep) +{ + socklen_t len; + + len = parse_address(str, (mrp_dbusaddr_t *)addr, size); + + if (len > 0) { + if (typep != NULL) + *typep = DBUS; + } + + return len; +} + + +static int dbus_open(mrp_transport_t *mt) +{ + MRP_UNUSED(mt); + + return TRUE; +} + + +static int dbus_createfrom(mrp_transport_t *mt, void *conn) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbus_t *dbus = (mrp_dbus_t *)conn; + + t->dbus = mrp_dbus_ref(dbus); + + if (t->dbus != NULL) + return TRUE; + else + return FALSE; +} + + +static int dbus_bind(mrp_transport_t *mt, mrp_sockaddr_t *addrp, + socklen_t addrlen) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbus_t *dbus = NULL; + mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp; + int (*cb)(mrp_dbus_t *, DBusMessage *, void *); + const char *method; + + if (t->bound) { + errno = EINVAL; + goto fail; + } + + if (!check_address(addrp, addrlen)) { + errno = EINVAL; + goto fail; + } + + if (t->dbus == NULL) { + dbus = mrp_dbus_connect(t->ml, addr->db_bus, NULL); + + if (dbus == NULL) { + errno = ECONNRESET; + goto fail; + } + else { + t->dbus = dbus; + + if (addr->db_addr != NULL && strcmp(addr->db_addr, ANY_ADDRESS)) { + if (!mrp_dbus_acquire_name(t->dbus, addr->db_addr, NULL)) { + errno = EADDRINUSE; /* XXX TODO, should check error... */ + goto fail; + } + } + } + } + else { + /* XXX TODO: should check given address against address of the bus */ + } + + copy_address(&t->local, addr); + + switch (t->mode) { + case MRP_TRANSPORT_MODE_DATA: + method = TRANSPORT_DATA; + cb = dbus_data_cb; + break; + case MRP_TRANSPORT_MODE_RAW: + method = TRANSPORT_RAW; + cb = dbus_raw_cb; + break; + case MRP_TRANSPORT_MODE_MSG: + method = TRANSPORT_MESSAGE; + cb = dbus_msg_cb; + break; + default: + errno = EPROTOTYPE; + goto fail; + } + + if (!mrp_dbus_export_method(t->dbus, addr->db_path, TRANSPORT_INTERFACE, + method, cb, t)) { + errno = EIO; + goto fail; + } + else { + t->bound = TRUE; + return TRUE; + } + + fail: + if (dbus != NULL) { + mrp_dbus_unref(dbus); + t->dbus = NULL; + } + + return FALSE; +} + + +static int dbus_autobind(mrp_transport_t *mt, mrp_sockaddr_t *addrp) +{ + mrp_dbusaddr_t *a = (mrp_dbusaddr_t *)addrp; + char astr[MRP_SOCKADDR_SIZE]; + mrp_sockaddr_t addr; + socklen_t alen; + + snprintf(astr, sizeof(astr), "dbus:[%s]/auto/%u", a->db_bus, nauto++); + + alen = dbus_resolve(astr, &addr, sizeof(addr), NULL); + + if (alen > 0) + return dbus_bind(mt, &addr, alen); + else + return FALSE; +} + + +static void dbus_close(mrp_transport_t *mt) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbusaddr_t *addr; + const char *method; + int (*cb)(mrp_dbus_t *, DBusMessage *, void *); + + if (t->bound) { + switch (t->mode) { + case MRP_TRANSPORT_MODE_DATA: + method = TRANSPORT_DATA; + cb = dbus_data_cb; + break; + case MRP_TRANSPORT_MODE_RAW: + method = TRANSPORT_RAW; + cb = dbus_raw_cb; + break; + default: + case MRP_TRANSPORT_MODE_MSG: + method = TRANSPORT_MESSAGE; + cb = dbus_msg_cb; + } + + addr = (mrp_dbusaddr_t *)&t->local; + mrp_dbus_remove_method(t->dbus, addr->db_path, TRANSPORT_INTERFACE, + method, cb, t); + } + + if (t->connected && t->remote.db_addr != NULL) + mrp_dbus_forget_name(t->dbus, t->remote.db_addr, peer_state_cb, t); + + mrp_dbus_unref(t->dbus); + t->dbus = NULL; +} + + +static int dbus_msg_cb(mrp_dbus_t *dbus, DBusMessage *dmsg, void *user_data) +{ + mrp_transport_t *mt = (mrp_transport_t *)user_data; + dbus_t *t = (dbus_t *)mt; + mrp_sockaddr_t addr; + socklen_t alen; + const char *sender, *sender_path; + mrp_msg_t *msg; + + MRP_UNUSED(dbus); + + msg = msg_decode(dmsg, &sender_path); + + if (msg != NULL) { + sender = dbus_message_get_sender(dmsg); + + if (mt->connected) { + if (!t->peer_resolved || !strcmp(t->remote.db_addr, sender)) + MRP_TRANSPORT_BUSY(mt, { + mt->evt.recvmsg(mt, msg, mt->user_data); + }); + } + else { + peer_address(&addr, sender, sender_path); + alen = sizeof(addr); + + MRP_TRANSPORT_BUSY(mt, { + mt->evt.recvmsgfrom(mt, msg, &addr, alen, mt->user_data); + }); + } + + mrp_msg_unref(msg); + + mt->check_destroy(mt); + } + else { + mrp_log_error("Failed to decode message."); + } + + return TRUE; +} + + +static int dbus_data_cb(mrp_dbus_t *dbus, DBusMessage *dmsg, void *user_data) +{ + mrp_transport_t *mt = (mrp_transport_t *)user_data; + dbus_t *t = (dbus_t *)mt; + mrp_sockaddr_t addr; + socklen_t alen; + const char *sender, *sender_path; + uint16_t tag; + void *decoded; + + MRP_UNUSED(dbus); + + decoded = data_decode(dmsg, &tag, &sender_path); + + if (decoded != NULL) { + sender = dbus_message_get_sender(dmsg); + + if (mt->connected) { + if (!t->peer_resolved || !strcmp(t->remote.db_addr, sender)) + MRP_TRANSPORT_BUSY(mt, { + mt->evt.recvdata(mt, decoded, tag, mt->user_data); + }); + } + else { + peer_address(&addr, sender, sender_path); + alen = sizeof(addr); + + MRP_TRANSPORT_BUSY(mt, { + mt->evt.recvdatafrom(mt, decoded, tag, &addr, alen, + mt->user_data); + }); + } + + mt->check_destroy(mt); + } + else { + mrp_log_error("Failed to decode custom data message."); + } + + return TRUE; +} + + +static int dbus_raw_cb(mrp_dbus_t *dbus, DBusMessage *dmsg, void *user_data) +{ + mrp_transport_t *mt = (mrp_transport_t *)user_data; + dbus_t *t = (dbus_t *)mt; + mrp_sockaddr_t addr; + socklen_t alen; + const char *sender, *sender_path; + void *data; + size_t size; + + MRP_UNUSED(dbus); + + data = raw_decode(dmsg, &size, &sender_path); + + if (data != NULL) { + sender = dbus_message_get_sender(dmsg); + + if (mt->connected) { + if (!t->peer_resolved || !strcmp(t->remote.db_addr, sender)) + MRP_TRANSPORT_BUSY(mt, { + mt->evt.recvraw(mt, data, size, mt->user_data); + }); + } + else { + peer_address(&addr, sender, sender_path); + alen = sizeof(addr); + + MRP_TRANSPORT_BUSY(mt, { + mt->evt.recvrawfrom(mt, data, size, &addr, alen, + mt->user_data); + }); + } + + mt->check_destroy(mt); + } + else { + mrp_log_error("Failed to decode raw message."); + } + + return TRUE; +} + + +static void peer_state_cb(mrp_dbus_t *dbus, const char *name, int up, + const char *owner, void *user_data) +{ + dbus_t *t = (dbus_t *)user_data; + mrp_sockaddr_t addr; + + MRP_UNUSED(dbus); + MRP_UNUSED(name); + + if (up) { + peer_address(&addr, owner, t->remote.db_path); + copy_address(&t->remote, (mrp_dbusaddr_t *)&addr); + t->peer_resolved = TRUE; + } + else { + /* + * XXX TODO: + * It would be really tempting here to call + * mt->evt.closed(mt, ECONNRESET, mt->user_data) + * to notify the user about the fact our peer went down. + * However, that would not be in line with the other + * transports which call the closed event handler only + * upon foricble transport closes upon errors. + * + * The transport interface abstraction (especially the + * available set of events) anyway needs some eyeballing, + * so the right thing to do might be to define a new event + * for disconnection and call the handler for that here... + */ + } + +} + + +static int dbus_connect(mrp_transport_t *mt, mrp_sockaddr_t *addrp, + socklen_t addrlen) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp; + + if (!check_address(addrp, addrlen)) { + errno = EINVAL; + return FALSE; + } + + if (t->dbus == NULL) { + t->dbus = mrp_dbus_connect(t->ml, addr->db_bus, NULL); + + if (t->dbus == NULL) { + errno = ECONNRESET; + return FALSE; + } + } + else { + /* XXX TODO: check given address against address of the bus */ + } + + if (!t->bound) + if (!dbus_autobind(mt, addrp)) + return FALSE; + + if (mrp_dbus_follow_name(t->dbus, addr->db_addr, peer_state_cb, t)) { + copy_address(&t->remote, addr); + + return TRUE; + } + else + return FALSE; +} + + +static int dbus_disconnect(mrp_transport_t *mt) +{ + dbus_t *t = (dbus_t *)mt; + + if (t->connected && t->remote.db_addr != NULL) { + mrp_dbus_forget_name(t->dbus, t->remote.db_addr, peer_state_cb, t); + mrp_clear(&t->remote); + t->peer_resolved = FALSE; + } + + return TRUE; +} + + +static int dbus_sendmsgto(mrp_transport_t *mt, mrp_msg_t *msg, + mrp_sockaddr_t *addrp, socklen_t addrlen) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp; + DBusMessage *m; + int success; + + if (check_address(addrp, addrlen)) { + if (t->dbus == NULL && !dbus_autobind(mt, addrp)) + return FALSE; + + m = msg_encode(t->local.db_path, msg); + + if (m != NULL) { + if (mrp_dbus_send(t->dbus, addr->db_addr, addr->db_path, + TRANSPORT_INTERFACE, TRANSPORT_MESSAGE, + 0, NULL, NULL, m)) + success = TRUE; + else { + errno = ECOMM; + success = FALSE; + } + + dbus_message_unref(m); + } + else + success = FALSE; + } + else { + errno = EINVAL; + success = FALSE; + } + + return success; +} + + +static int dbus_sendmsg(mrp_transport_t *mt, mrp_msg_t *msg) +{ + dbus_t *t = (dbus_t *)mt; + mrp_sockaddr_t *addr = (mrp_sockaddr_t *)&t->remote; + socklen_t alen = sizeof(t->remote); + + return dbus_sendmsgto(mt, msg, addr, alen); +} + + +static int dbus_sendrawto(mrp_transport_t *mt, void *data, size_t size, + mrp_sockaddr_t *addrp, socklen_t addrlen) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp; + DBusMessage *m; + int success; + + + MRP_UNUSED(mt); + MRP_UNUSED(data); + MRP_UNUSED(size); + MRP_UNUSED(addr); + MRP_UNUSED(addrlen); + + if (check_address(addrp, addrlen)) { + if (t->dbus == NULL && !dbus_autobind(mt, addrp)) + return FALSE; + + m = raw_encode(t->local.db_path, data, size); + + if (m != NULL) { + if (mrp_dbus_send(t->dbus, addr->db_addr, addr->db_path, + TRANSPORT_INTERFACE, TRANSPORT_RAW, + 0, NULL, NULL, m)) + success = TRUE; + else { + errno = ECOMM; + success = FALSE; + } + + dbus_message_unref(m); + } + else + success = FALSE; + } + else { + errno = EINVAL; + success = FALSE; + } + + return success; +} + + +static int dbus_sendraw(mrp_transport_t *mt, void *data, size_t size) +{ + dbus_t *t = (dbus_t *)mt; + mrp_sockaddr_t *addr = (mrp_sockaddr_t *)&t->remote; + socklen_t alen = sizeof(t->remote); + + return dbus_sendrawto(mt, data, size, addr, alen); +} + + +static int dbus_senddatato(mrp_transport_t *mt, void *data, uint16_t tag, + mrp_sockaddr_t *addrp, socklen_t addrlen) +{ + dbus_t *t = (dbus_t *)mt; + mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp; + DBusMessage *m; + int success; + + if (check_address(addrp, addrlen)) { + if (t->dbus == NULL && !dbus_autobind(mt, addrp)) + return FALSE; + + m = data_encode(t->local.db_path, data, tag); + + if (m != NULL) { + if (mrp_dbus_send(t->dbus, addr->db_addr, addr->db_path, + TRANSPORT_INTERFACE, TRANSPORT_DATA, + 0, NULL, NULL, m)) + success = TRUE; + else { + errno = ECOMM; + success = FALSE; + } + + dbus_message_unref(m); + } + else + success = FALSE; + } + else { + errno = EINVAL; + success = FALSE; + } + + return success; +} + + +static int dbus_senddata(mrp_transport_t *mt, void *data, uint16_t tag) +{ + dbus_t *t = (dbus_t *)mt; + mrp_sockaddr_t *addr = (mrp_sockaddr_t *)&t->remote; + socklen_t alen = sizeof(t->remote); + + return dbus_senddatato(mt, data, tag, addr, alen); +} + + +static const char *get_array_signature(uint16_t type) +{ +#define MAP(from, to) \ + case MRP_MSG_FIELD_##from: \ + return DBUS_TYPE_##to##_AS_STRING; + + switch (type) { + MAP(STRING, STRING); + MAP(BOOL , BOOLEAN); + MAP(UINT8 , UINT16); + MAP(SINT8 , INT16); + MAP(UINT16, UINT16); + MAP(SINT16, INT16); + MAP(UINT32, UINT32); + MAP(SINT32, INT32); + MAP(UINT64, UINT64); + MAP(SINT64, INT64); + MAP(DOUBLE, DOUBLE); + MAP(BLOB , BYTE ); + default: + return NULL; + } +} + + +static DBusMessage *msg_encode(const char *sender_id, mrp_msg_t *msg) +{ + /* + * Notes: There is a type mismatch between our and DBUS types for + * 8-bit integers (D-BUS does not have a signed 8-bit type) + * and boolean types (D-BUS has uint32_t booleans, C99 fails + * to specify the type and gcc uses a signed char). The + * QUIRKY versions of the macros take care of these mismatches. + */ + +#define BASIC_SIMPLE(_i, _mtype, _dtype, _val) \ + case MRP_MSG_FIELD_##_mtype: \ + type = DBUS_TYPE_##_dtype; \ + vptr = &(_val); \ + \ + if (!dbus_message_iter_append_basic(_i, type, vptr)) \ + goto fail; \ + break + +#define BASIC_QUIRKY(_i, _mtype, _dtype, _mval, _dval) \ + case MRP_MSG_FIELD_##_mtype: \ + type = DBUS_TYPE_##_dtype; \ + _dval = _mval; \ + vptr = &_dval; \ + \ + if (!dbus_message_iter_append_basic(_i, type, vptr)) \ + goto fail; \ + break + +#define ARRAY_SIMPLE(_i, _mtype, _dtype, _val) \ + case MRP_MSG_FIELD_##_mtype: \ + type = DBUS_TYPE_##_dtype; \ + vptr = &_val; \ + \ + if (!dbus_message_iter_append_basic(_i, type, vptr)) \ + goto fail; \ + break + +#define ARRAY_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar) \ + case MRP_MSG_FIELD_##_mtype: \ + type = DBUS_TYPE_##_dtype; \ + _dvar = _mvar; \ + vptr = &_dvar; \ + \ + if (!dbus_message_iter_append_basic(_i, type, vptr)) \ + goto fail; \ + break + + DBusMessage *m; + mrp_list_hook_t *p, *n; + mrp_msg_field_t *f; + uint16_t base; + uint32_t asize, i; + DBusMessageIter im, ia; + const char *sig; + int type, len; + void *vptr; + dbus_bool_t bln; + uint16_t u16; + int16_t s16; + + m = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL); + + if (m == NULL) + return NULL; + + dbus_message_iter_init_append(m, &im); + + if (!dbus_message_iter_append_basic(&im, DBUS_TYPE_OBJECT_PATH, &sender_id)) + goto fail; + + if (!dbus_message_iter_append_basic(&im, DBUS_TYPE_UINT16, &msg->nfield)) + goto fail; + + mrp_list_foreach(&msg->fields, p, n) { + f = mrp_list_entry(p, typeof(*f), hook); + + if (!dbus_message_iter_append_basic(&im, DBUS_TYPE_UINT16, &f->tag) || + !dbus_message_iter_append_basic(&im, DBUS_TYPE_UINT16, &f->type)) + goto fail; + + switch (f->type) { + BASIC_SIMPLE(&im, STRING, STRING , f->str); + BASIC_QUIRKY(&im, BOOL , BOOLEAN, f->bln, bln); + BASIC_QUIRKY(&im, UINT8 , UINT16 , f->u8 , u16); + BASIC_QUIRKY(&im, SINT8 , INT16 , f->s8 , s16); + BASIC_SIMPLE(&im, UINT16, UINT16 , f->u16); + BASIC_SIMPLE(&im, SINT16, INT16 , f->s16); + BASIC_SIMPLE(&im, UINT32, UINT32 , f->u32); + BASIC_SIMPLE(&im, SINT32, INT32 , f->s32); + BASIC_SIMPLE(&im, UINT64, UINT64 , f->u64); + BASIC_SIMPLE(&im, SINT64, INT64 , f->s64); + BASIC_SIMPLE(&im, DOUBLE, DOUBLE , f->dbl); + + case MRP_MSG_FIELD_BLOB: + vptr = f->blb; + len = (int)f->size[0]; + sig = get_array_signature(f->type); + + if (!dbus_message_iter_open_container(&im, DBUS_TYPE_ARRAY, + sig, &ia) || + !dbus_message_iter_append_fixed_array(&ia, sig[0], + &vptr, len) || + !dbus_message_iter_close_container(&im, &ia)) + goto fail; + break; + + default: + if (f->type & MRP_MSG_FIELD_ARRAY) { + base = f->type & ~(MRP_MSG_FIELD_ARRAY); + asize = f->size[0]; + sig = get_array_signature(base); + + if (!dbus_message_iter_append_basic(&im, + DBUS_TYPE_UINT32, &asize)) + goto fail; + + if (!dbus_message_iter_open_container(&im, DBUS_TYPE_ARRAY, + sig, &ia)) + goto fail; + + for (i = 0; i < asize; i++) { + switch (base) { + ARRAY_SIMPLE(&ia, STRING, STRING , f->astr[i]); + ARRAY_QUIRKY(&ia, BOOL , BOOLEAN, f->abln[i], bln); + ARRAY_QUIRKY(&ia, UINT8 , UINT16 , f->au8[i] , u16); + ARRAY_QUIRKY(&ia, SINT8 , INT16 , f->as8[i] , s16); + ARRAY_SIMPLE(&ia, UINT16, UINT16 , f->au16[i]); + ARRAY_SIMPLE(&ia, SINT16, INT16 , f->as16[i]); + ARRAY_SIMPLE(&ia, UINT32, UINT32 , f->au32[i]); + ARRAY_SIMPLE(&ia, SINT32, INT32 , f->as32[i]); + ARRAY_SIMPLE(&ia, UINT64, UINT64 , f->au64[i]); + ARRAY_SIMPLE(&ia, DOUBLE, DOUBLE , f->adbl[i]); + + case MRP_MSG_FIELD_BLOB: + goto fail; + + default: + goto fail; + } + } + + if (!dbus_message_iter_close_container(&im, &ia)) + goto fail; + } + else + goto fail; + } + } + + return m; + + fail: + if (m != NULL) + dbus_message_unref(m); + + errno = ECOMM; + + return FALSE; + +#undef BASIC_SIMPLE +#undef BASIC_QUIRKY +#undef ARRAY_SIMPLE +#undef ARRAY_QUIRKY +} + + +static mrp_msg_t *msg_decode(DBusMessage *m, const char **sender_id) +{ +#define BASIC_SIMPLE(_i, _mtype, _dtype, _var) \ + case MRP_MSG_FIELD_##_mtype: \ + if (dbus_message_iter_get_arg_type(_i) != DBUS_TYPE_##_dtype) \ + goto fail; \ + dbus_message_iter_get_basic(_i, &(_var)); \ + dbus_message_iter_next(_i); \ + \ + if (!mrp_msg_append(msg, tag, type, (_var))) \ + goto fail; \ + break + +#define BASIC_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar) \ + case MRP_MSG_FIELD_##_mtype: \ + if (dbus_message_iter_get_arg_type(_i) != DBUS_TYPE_##_dtype) \ + goto fail; \ + dbus_message_iter_get_basic(_i, &(_dvar)); \ + dbus_message_iter_next(_i); \ + \ + _mvar = _dvar; \ + if (!mrp_msg_append(msg, tag, type, (_mvar))) \ + goto fail; \ + break + +#define ARRAY_SIMPLE(_i, _mtype, _dtype, _var) \ + case MRP_MSG_FIELD_##_mtype: \ + if (dbus_message_iter_get_arg_type(_i) != DBUS_TYPE_##_dtype) \ + goto fail; \ + dbus_message_iter_get_basic(_i, &(_var)); \ + dbus_message_iter_next(_i); \ + break + +#define ARRAY_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar) \ + case MRP_MSG_FIELD_##_mtype: \ + if (dbus_message_iter_get_arg_type(_i) != DBUS_TYPE_##_dtype) \ + goto fail; \ + dbus_message_iter_get_basic(_i, &(_dvar)); \ + dbus_message_iter_next(_i); \ + \ + _mvar = _dvar; \ + break + +#define APPEND_ARRAY(_type, _var) \ + case MRP_MSG_FIELD_##_type: \ + if (!mrp_msg_append(msg, tag, \ + MRP_MSG_FIELD_ARRAY | \ + MRP_MSG_FIELD_##_type, \ + n, _var)) \ + goto fail; \ + break + + mrp_msg_t *msg; + mrp_msg_value_t v; + uint16_t u16; + int16_t s16; + uint32_t u32; + DBusMessageIter im, ia; + uint16_t nfield, tag, type, base, i; + uint32_t n, j; + int asize; + const char *sender; + + msg = NULL; + + if (!dbus_message_iter_init(m, &im)) + goto fail; + + if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_OBJECT_PATH) + goto fail; + + dbus_message_iter_get_basic(&im, &sender); + dbus_message_iter_next(&im); + + if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_UINT16) + goto fail; + + dbus_message_iter_get_basic(&im, &nfield); + dbus_message_iter_next(&im); + + msg = mrp_msg_create_empty(); + + if (msg == NULL) + goto fail; + + for (i = 0; i < nfield; i++) { + if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_UINT16) + goto fail; + + dbus_message_iter_get_basic(&im, &tag); + dbus_message_iter_next(&im); + + if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_UINT16) + goto fail; + + dbus_message_iter_get_basic(&im, &type); + dbus_message_iter_next(&im); + + switch (type) { + BASIC_SIMPLE(&im, STRING, STRING , v.str); + BASIC_QUIRKY(&im, BOOL , BOOLEAN, v.bln, u32); + BASIC_QUIRKY(&im, UINT8 , UINT16 , v.u8 , u16); + BASIC_QUIRKY(&im, SINT8 , INT16 , v.s8 , s16); + BASIC_SIMPLE(&im, UINT16, UINT16 , v.u16); + BASIC_SIMPLE(&im, SINT16, INT16 , v.s16); + BASIC_SIMPLE(&im, UINT32, UINT32 , v.u32); + BASIC_SIMPLE(&im, SINT32, INT32 , v.s32); + BASIC_SIMPLE(&im, UINT64, UINT64 , v.u64); + BASIC_SIMPLE(&im, SINT64, INT64 , v.s64); + BASIC_SIMPLE(&im, DOUBLE, DOUBLE , v.dbl); + + case MRP_MSG_FIELD_BLOB: + if (dbus_message_iter_get_element_type(&im) != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_recurse(&im, &ia); + dbus_message_iter_get_fixed_array(&ia, &v.blb, &asize); + dbus_message_iter_next(&im); + if (!mrp_msg_append(msg, tag, type, asize, v.blb)) + goto fail; + break; + + default: + if (!(type & MRP_MSG_FIELD_ARRAY)) + goto fail; + + base = type & ~(MRP_MSG_FIELD_ARRAY); + + if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_UINT32) + goto fail; + + dbus_message_iter_get_basic(&im, &n); + dbus_message_iter_next(&im); + + if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_ARRAY) + goto fail; + dbus_message_iter_recurse(&im, &ia); + dbus_message_iter_next(&im); + + { + char *astr[n]; + uint32_t dbln[n]; + bool abln[n]; + uint8_t au8 [n]; + int8_t as8 [n]; + uint16_t au16[n]; + int16_t as16[n]; + uint32_t au32[n]; + int32_t as32[n]; + uint64_t au64[n]; + int64_t as64[n]; + double adbl[n]; + + for (j = 0; j < n; j++) { + switch (base) { + ARRAY_SIMPLE(&ia, STRING, STRING , astr[j]); + ARRAY_QUIRKY(&ia, BOOL , BOOLEAN, abln[j], dbln[j]); + ARRAY_QUIRKY(&ia, UINT8 , UINT16 , au8[j] , au16[j]); + ARRAY_QUIRKY(&ia, SINT8 , INT16 , as8[j] , as16[j]); + ARRAY_SIMPLE(&ia, UINT16, UINT16 , au16[j]); + ARRAY_SIMPLE(&ia, SINT16, INT16 , as16[j]); + ARRAY_SIMPLE(&ia, UINT32, UINT32 , au32[j]); + ARRAY_SIMPLE(&ia, SINT32, INT32 , as32[j]); + ARRAY_SIMPLE(&ia, UINT64, UINT64 , au64[j]); + ARRAY_SIMPLE(&ia, SINT64, INT64 , as64[j]); + ARRAY_SIMPLE(&ia, DOUBLE, DOUBLE , adbl[j]); + default: + goto fail; + } + } + + switch (base) { + APPEND_ARRAY(STRING, astr); + APPEND_ARRAY(BOOL , abln); + APPEND_ARRAY(UINT8 , au8 ); + APPEND_ARRAY(SINT8 , as8 ); + APPEND_ARRAY(UINT16, au16); + APPEND_ARRAY(SINT16, as16); + APPEND_ARRAY(UINT32, au32); + APPEND_ARRAY(SINT32, as32); + APPEND_ARRAY(UINT64, au64); + APPEND_ARRAY(SINT64, as64); + APPEND_ARRAY(DOUBLE, adbl); + default: + goto fail; + } + } + } + } + + if (sender_id != NULL) + *sender_id = sender; + + return msg; + + fail: + mrp_msg_unref(msg); + errno = EBADMSG; + + return NULL; + +#undef BASIC_SIMPLE +#undef BASIC_QUIRKY +#undef ARRAY_SIMPLE +#undef ARRAY_QUIRKY +#undef APPEND_ARRAY +} + + +static DBusMessage *data_encode(const char *sender_id, void *data, uint16_t tag) +{ +#define BASIC_SIMPLE(_mtype, _dtype, _val) \ + case MRP_MSG_FIELD_##_mtype: \ + type = DBUS_TYPE_##_dtype; \ + vptr = &(_val); \ + \ + if (!dbus_message_iter_append_basic(&im, type, vptr)) \ + goto fail; \ + break + +#define BASIC_QUIRKY(_mtype, _dtype, _mval, _dval) \ + case MRP_MSG_FIELD_##_mtype: \ + type = DBUS_TYPE_##_dtype; \ + _dval = _mval; \ + vptr = &_dval; \ + \ + if (!dbus_message_iter_append_basic(&im, type, vptr)) \ + goto fail; \ + break + +#define ARRAY_SIMPLE(_mtype, _dtype, _val) \ + case MRP_MSG_FIELD_##_mtype: \ + type = DBUS_TYPE_##_dtype; \ + vptr = &_val; \ + \ + if (!dbus_message_iter_append_basic(&ia, type, vptr)) \ + goto fail; \ + break + +#define ARRAY_QUIRKY(_mtype, _dtype, _mvar, _dvar) \ + case MRP_MSG_FIELD_##_mtype: \ + type = DBUS_TYPE_##_dtype; \ + _dvar = _mvar; \ + vptr = &_dvar; \ + \ + if (!dbus_message_iter_append_basic(&ia, type, vptr)) \ + goto fail; \ + break + + DBusMessage *m; + mrp_data_descr_t *descr; + mrp_data_member_t *fields, *f; + int nfield; + uint16_t type, base; + mrp_msg_value_t *v; + void *vptr; + uint32_t n, j; + int i, blblen; + DBusMessageIter im, ia; + const char *sig; + uint16_t u16; + int16_t s16; + uint32_t bln; + + m = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL); + + if (m == NULL) + return NULL; + + descr = mrp_msg_find_type(tag); + + if (descr == NULL) + goto fail; + + fields = descr->fields; + nfield = descr->nfield; + + dbus_message_iter_init_append(m, &im); + + if (!dbus_message_iter_append_basic(&im, + DBUS_TYPE_OBJECT_PATH, &sender_id)) + goto fail; + + if (!dbus_message_iter_append_basic(&im, DBUS_TYPE_UINT16, &tag)) + goto fail; + + if (!dbus_message_iter_append_basic(&im, DBUS_TYPE_UINT16, &nfield)) + goto fail; + + for (i = 0, f = fields; i < nfield; i++, f++) { + if (!dbus_message_iter_append_basic(&im, DBUS_TYPE_UINT16, &f->tag) || + !dbus_message_iter_append_basic(&im, DBUS_TYPE_UINT16, &f->type)) + goto fail; + + v = (mrp_msg_value_t *)(data + f->offs); + + switch (f->type) { + BASIC_SIMPLE(STRING, STRING , v->str); + BASIC_QUIRKY(BOOL , BOOLEAN, v->bln, bln); + BASIC_QUIRKY(UINT8 , UINT16 , v->u8 , u16); + BASIC_QUIRKY(SINT8 , INT16 , v->s8 , s16); + BASIC_SIMPLE(UINT16, UINT16 , v->u16); + BASIC_SIMPLE(SINT16, INT16 , v->s16); + BASIC_SIMPLE(UINT32, UINT32 , v->u32); + BASIC_SIMPLE(SINT32, INT32 , v->s32); + BASIC_SIMPLE(UINT64, UINT64 , v->u64); + BASIC_SIMPLE(SINT64, INT64 , v->s64); + BASIC_SIMPLE(DOUBLE, DOUBLE , v->dbl); + + case MRP_MSG_FIELD_BLOB: + sig = get_array_signature(f->type); + blblen = mrp_data_get_blob_size(data, descr, i); + + if (blblen == -1) + goto fail; + + if (!dbus_message_iter_open_container(&im, DBUS_TYPE_ARRAY, + sig, &ia) || + !dbus_message_iter_append_fixed_array(&ia, sig[0], + &f->blb, blblen) || + !dbus_message_iter_close_container(&im, &ia)) + goto fail; + break; + + default: + if (!(f->type & MRP_MSG_FIELD_ARRAY)) + goto fail; + + base = f->type & ~(MRP_MSG_FIELD_ARRAY); + n = mrp_data_get_array_size(data, descr, i); + sig = get_array_signature(base); + + if (!dbus_message_iter_append_basic(&im, DBUS_TYPE_UINT32, &n)) + goto fail; + + if (!dbus_message_iter_open_container(&im, DBUS_TYPE_ARRAY, + sig, &ia)) + goto fail; + + for (j = 0; j < n; j++) { + switch (base) { + ARRAY_SIMPLE(STRING, STRING , v->astr[j]); + ARRAY_QUIRKY(BOOL , BOOLEAN, v->abln[j], bln); + ARRAY_QUIRKY(UINT8 , UINT16 , v->au8[j] , u16); + ARRAY_QUIRKY(SINT8 , INT16 , v->as8[j] , s16); + ARRAY_SIMPLE(UINT16, UINT16 , v->au16[j]); + ARRAY_SIMPLE(SINT16, INT16 , v->as16[j]); + ARRAY_SIMPLE(UINT32, UINT32 , v->au32[j]); + ARRAY_SIMPLE(SINT32, INT32 , v->as32[j]); + ARRAY_SIMPLE(UINT64, UINT64 , v->au64[j]); + ARRAY_SIMPLE(DOUBLE, DOUBLE , v->adbl[j]); + + case MRP_MSG_FIELD_BLOB: + goto fail; + + default: + goto fail; + } + } + + if (!dbus_message_iter_close_container(&im, &ia)) + goto fail; + } + } + + return m; + + fail: + if (m != NULL) + dbus_message_unref(m); + + errno = ECOMM; + + return NULL; + +#undef BASIC_SIMPLE +#undef BASIC_QUIRKY +#undef ARRAY_SIMPLE +#undef ARRAY_QUIRKY +} + + +static mrp_data_member_t *member_type(mrp_data_member_t *fields, int nfield, + uint16_t tag) +{ + mrp_data_member_t *f; + int i; + + for (i = 0, f = fields; i < nfield; i++, f++) + if (f->tag == tag) + return f; + + return NULL; +} + + +static void *data_decode(DBusMessage *m, uint16_t *tagp, const char **sender_id) +{ +#define HANDLE_SIMPLE(_i, _mtype, _dtype, _var) \ + case MRP_MSG_FIELD_##_mtype: \ + if (dbus_message_iter_get_arg_type(_i) != DBUS_TYPE_##_dtype) \ + goto fail; \ + dbus_message_iter_get_basic(_i, &(_var)); \ + dbus_message_iter_next(_i); \ + break + +#define HANDLE_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar) \ + case MRP_MSG_FIELD_##_mtype: \ + if (dbus_message_iter_get_arg_type(_i) != DBUS_TYPE_##_dtype) \ + goto fail; \ + dbus_message_iter_get_basic(_i, &(_dvar)); \ + dbus_message_iter_next(_i); \ + \ + _mvar = _dvar; \ + break + + void *data; + mrp_data_descr_t *descr; + mrp_data_member_t *fields, *f; + int nfield; + uint16_t tag, type, base; + mrp_msg_value_t *v; + uint32_t n, j, size; + int i, blblen; + DBusMessageIter im, ia; + const char *sender; + uint32_t u32; + uint16_t u16; + int16_t s16; + + tag = 0; + data = NULL; + + if (!dbus_message_iter_init(m, &im)) + goto fail; + + if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_OBJECT_PATH) + goto fail; + + dbus_message_iter_get_basic(&im, &sender); + dbus_message_iter_next(&im); + + if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_UINT16) + goto fail; + + dbus_message_iter_get_basic(&im, &tag); + dbus_message_iter_next(&im); + + descr = mrp_msg_find_type(tag); + + if (descr == NULL) + goto fail; + + *tagp = tag; + + if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_UINT16) + goto fail; + + dbus_message_iter_get_basic(&im, &nfield); + dbus_message_iter_next(&im); + + if (nfield != descr->nfield) + goto fail; + + fields = descr->fields; + data = mrp_allocz(descr->size); + + if (MRP_UNLIKELY(data == NULL)) + goto fail; + + for (i = 0; i < nfield; i++) { + if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_UINT16) + goto fail; + + dbus_message_iter_get_basic(&im, &tag); + dbus_message_iter_next(&im); + + if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_UINT16) + goto fail; + + dbus_message_iter_get_basic(&im, &type); + dbus_message_iter_next(&im); + + f = member_type(fields, nfield, tag); + + if (MRP_UNLIKELY(f == NULL)) + goto fail; + + v = (mrp_msg_value_t *)(data + f->offs); + + switch (type) { + HANDLE_SIMPLE(&im, STRING, STRING , v->str); + HANDLE_QUIRKY(&im, BOOL , BOOLEAN, v->bln, u32); + HANDLE_QUIRKY(&im, UINT8 , UINT16 , v->u8 , u16); + HANDLE_QUIRKY(&im, SINT8 , INT16 , v->s8 , s16); + HANDLE_SIMPLE(&im, UINT16, UINT16 , v->u16); + HANDLE_SIMPLE(&im, SINT16, INT16 , v->s16); + HANDLE_SIMPLE(&im, UINT32, UINT32 , v->u32); + HANDLE_SIMPLE(&im, SINT32, INT32 , v->s32); + HANDLE_SIMPLE(&im, UINT64, UINT64 , v->u64); + HANDLE_SIMPLE(&im, SINT64, INT64 , v->s64); + HANDLE_SIMPLE(&im, DOUBLE, DOUBLE , v->dbl); + + case MRP_MSG_FIELD_BLOB: + if (dbus_message_iter_get_element_type(&ia) != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_recurse(&im, &ia); + dbus_message_iter_get_fixed_array(&ia, &v->blb, &blblen); + dbus_message_iter_next(&im); + v->blb = mrp_datadup(v->blb, blblen); + if (v->blb == NULL) + goto fail; + break; + + default: + if (!(f->type & MRP_MSG_FIELD_ARRAY)) + goto fail; + + base = type & ~(MRP_MSG_FIELD_ARRAY); + + if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_UINT32) + goto fail; + + dbus_message_iter_get_basic(&im, &n); + dbus_message_iter_next(&im); + + if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_ARRAY) + goto fail; + + dbus_message_iter_recurse(&im, &ia); + dbus_message_iter_next(&im); + + size = n; + + switch (base) { + case MRP_MSG_FIELD_STRING: size *= sizeof(*v->astr); break; + case MRP_MSG_FIELD_BOOL: size *= sizeof(*v->abln); break; + case MRP_MSG_FIELD_UINT8: size *= sizeof(*v->au8); break; + case MRP_MSG_FIELD_SINT8: size *= sizeof(*v->as8); break; + case MRP_MSG_FIELD_UINT16: size *= sizeof(*v->au16); break; + case MRP_MSG_FIELD_SINT16: size *= sizeof(*v->as16); break; + case MRP_MSG_FIELD_UINT32: size *= sizeof(*v->au32); break; + case MRP_MSG_FIELD_SINT32: size *= sizeof(*v->as32); break; + case MRP_MSG_FIELD_UINT64: size *= sizeof(*v->au64); break; + case MRP_MSG_FIELD_SINT64: size *= sizeof(*v->as64); break; + case MRP_MSG_FIELD_DOUBLE: size *= sizeof(*v->adbl); break; + default: + goto fail; + } + + v->aany = mrp_allocz(size); + if (v->aany == NULL) + goto fail; + + for (j = 0; j < n; j++) { + uint32_t dbln[n]; + uint16_t au16[n]; + int16_t as16[n]; + + switch (base) { + HANDLE_SIMPLE(&ia, STRING, STRING , v->astr[j]); + HANDLE_QUIRKY(&ia, BOOL , BOOLEAN, v->abln[j], dbln[j]); + HANDLE_QUIRKY(&ia, UINT8 , UINT16 , v->au8[j] , au16[j]); + HANDLE_QUIRKY(&ia, SINT8 , INT16 , v->as8[j] , as16[j]); + HANDLE_SIMPLE(&ia, UINT16, UINT16 , v->au16[j]); + HANDLE_SIMPLE(&ia, SINT16, INT16 , v->as16[j]); + HANDLE_SIMPLE(&ia, UINT32, UINT32 , v->au32[j]); + HANDLE_SIMPLE(&ia, SINT32, INT32 , v->as32[j]); + HANDLE_SIMPLE(&ia, UINT64, UINT64 , v->au64[j]); + HANDLE_SIMPLE(&ia, SINT64, INT64 , v->as64[j]); + HANDLE_SIMPLE(&ia, DOUBLE, DOUBLE , v->adbl[j]); + } + + if (base == MRP_MSG_FIELD_STRING) { + v->astr[j] = mrp_strdup(v->astr[j]); + if (v->astr[j] == NULL) + goto fail; + } + } + } + + if (f->type == MRP_MSG_FIELD_STRING) { + v->str = mrp_strdup(v->str); + if (v->str == NULL) + goto fail; + } + } + + if (sender_id != NULL) + *sender_id = sender; + + return data; + + fail: + mrp_data_free(data, tag); + errno = EBADMSG; + + return NULL; +} + + +static DBusMessage *raw_encode(const char *sender_id, void *data, size_t size) +{ + DBusMessage *m; + DBusMessageIter im, ia; + const char *sig; + int len; + + m = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL); + + if (m != NULL) { + dbus_message_iter_init_append(m, &im); + + if (!dbus_message_iter_append_basic(&im, + DBUS_TYPE_OBJECT_PATH, &sender_id)) + goto fail; + + sig = DBUS_TYPE_BYTE_AS_STRING; + len = (int)size; + + if (!dbus_message_iter_open_container(&im, DBUS_TYPE_ARRAY, sig, &ia) || + !dbus_message_iter_append_fixed_array(&ia, sig[0], &data, len) || + !dbus_message_iter_close_container(&im, &ia)) + goto fail; + + return m; + } + else + return NULL; + + fail: + if (m != NULL) + dbus_message_unref(m); + + errno = ECOMM; + + return NULL; +} + + +static void *raw_decode(DBusMessage *m, size_t *sizep, const char **sender_id) +{ + DBusMessageIter im, ia; + const char *sender; + void *data; + int len; + + data = NULL; + + if (!dbus_message_iter_init(m, &im)) + goto fail; + + if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_OBJECT_PATH) + goto fail; + + dbus_message_iter_get_basic(&im, &sender); + dbus_message_iter_next(&im); + + if (dbus_message_iter_get_element_type(&ia) != DBUS_TYPE_BYTE) + goto fail; + + if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_ARRAY) + goto fail; + + dbus_message_iter_recurse(&im, &ia); + dbus_message_iter_get_fixed_array(&ia, &data, &len); + + data = mrp_datadup(data, len); + + if (sizep != NULL) + *sizep = (size_t)len; + + if (sender_id != NULL) + *sender_id = sender; + + return data; + + fail: + errno = EBADMSG; + + return NULL; +} + + +MRP_REGISTER_TRANSPORT(dbus, DBUS, dbus_t, dbus_resolve, + dbus_open, dbus_createfrom, dbus_close, NULL, + dbus_bind, NULL, NULL, + dbus_connect, dbus_disconnect, + dbus_sendmsg, dbus_sendmsgto, + dbus_sendraw, dbus_sendrawto, + dbus_senddata, dbus_senddatato, + NULL, NULL, + NULL, NULL, + NULL, NULL); + diff --git a/src/common/dbus-transport.h b/src/common/dbus-transport.h new file mode 100644 index 0000000..77b5335 --- /dev/null +++ b/src/common/dbus-transport.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_DBUS_TRANSPORT_H__ +#define __MURPHY_DBUS_TRANSPORT_H__ + +#include <murphy/common/transport.h> + +#define MRP_AF_DBUS 0xDB + +#define MRP_DBUSADDR_BASE \ + __SOCKADDR_COMMON(db_); \ + char *db_bus; /* D-BUS bus address */ \ + char *db_addr; /* address on bus */ \ + char *db_path /* instance path */ \ + +typedef struct { + MRP_DBUSADDR_BASE; +} _mrp_dbusaddr_base_t; + + +typedef struct { + MRP_DBUSADDR_BASE; + char db_fqa[MRP_SOCKADDR_SIZE - sizeof(_mrp_dbusaddr_base_t)]; +} mrp_dbusaddr_t; + + + +#endif /* __MURPHY_DBUS_TRANSPORT_H__ */ diff --git a/src/common/debug-auto-register.c b/src/common/debug-auto-register.c new file mode 100644 index 0000000..974e343 --- /dev/null +++ b/src/common/debug-auto-register.c @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +static void __attribute__((constructor)) register_debug_data(void) +{ + mrp_debug_file_t *df; + int i; + + for (i = 0; (df = debug_files[i]) != NULL; i++) + mrp_debug_register_file(df); +} + +static void __attribute__((destructor)) unregister_debug_data(void) +{ + mrp_debug_file_t *df; + int i; + + for (i = 0; (df = debug_files[i]) != NULL; i++) + mrp_debug_unregister_file(df); +} + diff --git a/src/common/debug-info.h b/src/common/debug-info.h new file mode 100644 index 0000000..970d4c9 --- /dev/null +++ b/src/common/debug-info.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_DEBUG_INFO_H__ +#define __MURPHY_DEBUG_INFO_H__ + +#include <murphy/common/macros.h> +#include <murphy/common/list.h> + +MRP_CDECL_BEGIN + +/* + * line number information for a single function + */ + +typedef struct { + const char *func; /* name of the function */ + int line; /* start at this line */ +} mrp_debug_info_t; + + +/* + * funcion - line number mapping for a single file + */ + +typedef struct { + mrp_list_hook_t hook; /* hook for startup registration */ + const char *file; /* file name */ + mrp_debug_info_t *info; /* function information */ +} mrp_debug_file_t; + +MRP_CDECL_END + +#endif /* __MURPHY_DEBUG_INFO_H__ */ diff --git a/src/common/debug.c b/src/common/debug.c new file mode 100644 index 0000000..9f5e091 --- /dev/null +++ b/src/common/debug.c @@ -0,0 +1,418 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define _GNU_SOURCE +#include <link.h> +#include <elf.h> + +#include <stdarg.h> +#include <limits.h> + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/list.h> +#include <murphy/common/log.h> +#include <murphy/common/hashtbl.h> +#include <murphy/common/utils.h> +#include <murphy/common/debug.h> + +#define WILDCARD "*" + +int mrp_debug_stamp = 0; /* debug config stamp */ + +static int debug_enabled; /* debug messages enabled */ +static mrp_htbl_t *rules_on; /* enabling rules */ +static mrp_htbl_t *rules_off; /* disabling rules */ + +static void free_rule_cb(void *key, void *entry) +{ + MRP_UNUSED(key); + + mrp_free(entry); +} + + +static int init_rules(void) +{ + mrp_htbl_config_t hcfg; + + mrp_clear(&hcfg); + hcfg.comp = mrp_string_comp; + hcfg.hash = mrp_string_hash; + hcfg.free = free_rule_cb; + + rules_on = mrp_htbl_create(&hcfg); + rules_off = mrp_htbl_create(&hcfg); + + if (rules_on == NULL || rules_off == NULL) + return FALSE; + else + return TRUE; +} + + +static void reset_rules(void) +{ + if (rules_on != NULL) { + mrp_htbl_destroy(rules_on , TRUE); + rules_on = NULL; + } + if (rules_off != NULL) { + mrp_htbl_destroy(rules_off, TRUE); + rules_off = NULL; + } +} + + +void mrp_debug_reset(void) +{ + debug_enabled = FALSE; + reset_rules(); +} + + +int mrp_debug_enable(int enabled) +{ + int prev = debug_enabled; + + debug_enabled = !!enabled; + mrp_log_enable(MRP_LOG_MASK_DEBUG); + mrp_debug_stamp++; + + return prev; +} + + +static int add_rule(const char *func, const char *file, int line, int off) +{ + mrp_htbl_t *ht; + char *rule, *r, buf[PATH_MAX * 2]; + + if (rules_on == NULL) + if (!init_rules()) + return FALSE; + + r = rule = NULL; + + if (!off) + ht = rules_on; + else + ht = rules_off; + + if (func != NULL && file == NULL && line == 0) { + r = mrp_htbl_lookup(ht, (void *)func); + rule = (char *)func; + } + else if (func != NULL && file != NULL && line == 0) { + snprintf(buf, sizeof(buf), "%s@%s", func, file); + r = mrp_htbl_lookup(ht, (void *)buf); + rule = buf; + } + else if (func == NULL && file != NULL && line == 0) { + snprintf(buf, sizeof(buf), "@%s", file); + r = mrp_htbl_lookup(ht, (void *)buf); + rule = buf; + } + else if (func == NULL && file != NULL && line > 0) { + snprintf(buf, sizeof(buf), "%s:%d", file, line); + r = mrp_htbl_lookup(ht, (void *)buf); + rule = buf; + } + + if (r != NULL) + return FALSE; + + rule = mrp_strdup(rule); + if (rule == NULL) + return FALSE; + + if (mrp_htbl_insert(ht, rule, rule)) { + mrp_debug_stamp++; + + return TRUE; + } + else { + mrp_free(rule); + + return FALSE; + } +} + + +static int del_rule(const char *func, const char *file, int line, int off) +{ + mrp_htbl_t *ht; + char *r, buf[PATH_MAX * 2]; + + if (rules_on == NULL) + if (!init_rules()) + return FALSE; + + r = NULL; + + if (!off) + ht = rules_on; + else + ht = rules_off; + + if (func != NULL && file == NULL && line == 0) { + r = mrp_htbl_remove(ht, (void *)func, TRUE); + } + else if (func != NULL && file != NULL && line == 0) { + snprintf(buf, sizeof(buf), "%s@%s", func, file); + r = mrp_htbl_remove(ht, (void *)buf, TRUE); + } + else if (func == NULL && file != NULL && line == 0) { + snprintf(buf, sizeof(buf), "@%s", file); + r = mrp_htbl_remove(ht, (void *)buf, TRUE); + } + else if (func == NULL && file != NULL && line > 0) { + snprintf(buf, sizeof(buf), "%s:%d", file, line); + r = mrp_htbl_remove(ht, (void *)buf, TRUE); + } + + if (r != NULL) { + mrp_debug_stamp++; + + return TRUE; + } + else + return FALSE; +} + + +int mrp_debug_set_config(const char *cmd) +{ + char buf[2 * PATH_MAX + 1], *colon, *at, *eq; + char *func, *file, *end; + size_t len; + int del, off, line; + + if (*cmd == '+' || *cmd == '-') + del = (*cmd++ == '-'); + else + del = FALSE; + + eq = strchr(cmd, '='); + + if (eq == NULL) { + strncpy(buf, cmd, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + off = FALSE; + } + else { + if (!strcmp(eq + 1, "on")) + off = FALSE; + else if (!strcmp(eq + 1, "off")) + off = TRUE; + else + return FALSE; + + len = eq - cmd; + if (len >= sizeof(buf)) + len = sizeof(buf) - 1; + + strncpy(buf, cmd, len); + buf[len] = '\0'; + } + + colon = strchr(buf, ':'); + + if (colon != NULL) { + if (strchr(buf, '@') != NULL) + return FALSE; + + *colon = '\0'; + func = NULL; + file = buf; + line = strtoul(colon + 1, &end, 10); + + if (end && *end) + return FALSE; + + mrp_log_info("%s file='%s', line=%d, %s", del ? "del" : "add", + file, line, off ? "off" : "on"); + } + else { + at = strchr(buf, '@'); + + if (at != NULL) { + *at = '\0'; + func = (at == buf ? NULL : buf); + file = at + 1; + line = 0; + + mrp_log_info("%s func='%s', file='%s', %s", del ? "del" : "add", + func ? func : "", file, off ? "off" : "on"); + } + else { + func = buf; + file = NULL; + line = 0; + + mrp_log_info("%s func='%s' %s", del ? "del" : "add", + func, off ? "off" : "on"); + } + } + + if (!del) + return add_rule(func, file, line, off); + else + return del_rule(func, file, line, off); + + return TRUE; +} + + +typedef struct { + FILE *fp; + int on; +} dump_t; + + +static int dump_rule_cb(void *key, void *object, void *user_data) +{ + dump_t *d = (dump_t *)user_data; + FILE *fp = d->fp; + const char *state = d->on ? "on" : "off"; + + MRP_UNUSED(key); + + fprintf(fp, " %s %s\n", (char *)object, state); + + return MRP_HTBL_ITER_MORE; +} + + +int mrp_debug_dump_config(FILE *fp) +{ + dump_t d; + + fprintf(fp, "Debugging is %sabled\n", debug_enabled ? "en" : "dis"); + + if (rules_on != NULL) { + fprintf(fp, "Debugging rules:\n"); + + d.fp = fp; + d.on = TRUE; + mrp_htbl_foreach(rules_on , dump_rule_cb, &d); + d.on = FALSE; + mrp_htbl_foreach(rules_off, dump_rule_cb, &d); + } + else + fprintf(fp, "No debugging rules defined.\n"); + + return TRUE; +} + + +void mrp_debug_msg(const char *file, int line, const char *func, + const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + mrp_log_msgv(MRP_LOG_DEBUG, file, line, func, format, ap); + va_end(ap); +} + + +int mrp_debug_check(const char *func, const char *file, int line) +{ + char buf[2 * PATH_MAX], *base; + void *key; + + if (!debug_enabled || rules_on == NULL) + return FALSE; + + base = NULL; + key = (void *)func; + if (mrp_htbl_lookup(rules_on, key) != NULL) + goto check_suppress; + + base = strrchr(file, '/'); + if (base != NULL) + base++; + + key = buf; + + snprintf(buf, sizeof(buf), "@%s", file); + if (mrp_htbl_lookup(rules_on, key) != NULL) + goto check_suppress; + + if (base != NULL) { + snprintf(buf, sizeof(buf), "@%s", base); + if (mrp_htbl_lookup(rules_on, key) != NULL) + goto check_suppress; + } + + snprintf(buf, sizeof(buf), "%s@%s", func, file); + if (mrp_htbl_lookup(rules_on, key) != NULL) + goto check_suppress; + + snprintf(buf, sizeof(buf), "%s:%d", file, line); + if (mrp_htbl_lookup(rules_on, key) != NULL) + goto check_suppress; + + if (mrp_htbl_lookup(rules_on, (void *)WILDCARD) == NULL) + return FALSE; + + + check_suppress: + if (rules_off == NULL) + return TRUE; + + key = (void *)func; + if (mrp_htbl_lookup(rules_off, key) != NULL) + return FALSE; + + key = buf; + + snprintf(buf, sizeof(buf), "@%s", file); + if (mrp_htbl_lookup(rules_off, key) != NULL) + return FALSE; + + if (base != NULL) { + snprintf(buf, sizeof(buf), "@%s", base); + if (mrp_htbl_lookup(rules_off, key) != NULL) + return FALSE; + } + + snprintf(buf, sizeof(buf), "%s@%s", func, file); + if (mrp_htbl_lookup(rules_off, key) != NULL) + return FALSE; + + snprintf(buf, sizeof(buf), "%s:%d", file, line); + if (mrp_htbl_lookup(rules_off, key) != NULL) + return FALSE; + + return TRUE; +} + + diff --git a/src/common/debug.h b/src/common/debug.h new file mode 100644 index 0000000..3828402 --- /dev/null +++ b/src/common/debug.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_DEBUG_H__ +#define __MURPHY_DEBUG_H__ + +#include <stdio.h> + +#include <murphy/common/macros.h> +#include <murphy/common/debug-info.h> + +MRP_CDECL_BEGIN + +/** Log a debug message if the invoking debug site is enabled. */ +#define mrp_debug(fmt, args...) do { \ + static int __site_stamp = -1; \ + static int __site_enabled; \ + \ + if (MRP_UNLIKELY(__site_stamp != mrp_debug_stamp)) { \ + __site_enabled = mrp_debug_check(__FUNCTION__, \ + __FILE__, __LINE__); \ + __site_stamp = mrp_debug_stamp; \ + } \ + \ + if (MRP_UNLIKELY(__site_enabled)) \ + mrp_debug_msg(__LOC__, fmt, ## args); \ + } while (0) + + +/** mrp_debug variant with explicitly passed site info. */ +#define mrp_debug_at(_file, _line, _func, fmt, args...) do { \ + static int __site_stamp = -1; \ + static int __site_enabled; \ + \ + if (MRP_UNLIKELY(__site_stamp != mrp_debug_stamp)) { \ + __site_enabled = mrp_debug_check(_func, _file, _line); \ + __site_stamp = mrp_debug_stamp; \ + } \ + \ + if (MRP_UNLIKELY(__site_enabled)) \ + mrp_debug_msg(_file, _line, _func, fmt, ## args); \ + } while (0) + + +/** Run a block of code if the invoking debug site is enabled. */ +#define mrp_debug_code(...) do { \ + static int __site_stamp = -1; \ + static int __site_enabled; \ + \ + if (MRP_UNLIKELY(__site_stamp != mrp_debug_stamp)) { \ + __site_enabled = mrp_debug_check(__FUNCTION__, \ + __FILE__, __LINE__); \ + __site_stamp = mrp_debug_stamp; \ + } \ + \ + if (MRP_UNLIKELY(__site_enabled)) { \ + __VA_ARGS__; \ + } \ + } while (0) + +/** Global debug configuration stamp, exported for minimum-overhead checking. */ +extern int mrp_debug_stamp; + +/** Enable/disable debug messages globally. */ +int mrp_debug_enable(int enabled); + +/** Reset all debug configuration to the defaults. */ +void mrp_debug_reset(void); + +/** Apply the debug configuration settings given in cmd. */ +int mrp_debug_set_config(const char *cmd); + +/** Dump the active debug configuration. */ +int mrp_debug_dump_config(FILE *fp); + +/** Low-level log wrapper for debug messages. */ +void mrp_debug_msg(const char *file, int line, const char *func, + const char *format, ...) MRP_PRINTF_LIKE(4, 5); + +/** Check if the given debug site is enabled. */ +int mrp_debug_check(const char *func, const char *file, int line); + +MRP_CDECL_END + +#endif /* __MURPHY_DEBUG_H__ */ diff --git a/src/common/dgram-transport.c b/src/common/dgram-transport.c new file mode 100644 index 0000000..b54ed62 --- /dev/null +++ b/src/common/dgram-transport.c @@ -0,0 +1,866 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <netdb.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/log.h> +#include <murphy/common/msg.h> +#include <murphy/common/transport.h> + +#ifndef UNIX_PATH_MAX +# define UNIX_PATH_MAX sizeof(((struct sockaddr_un *)NULL)->sun_path) +#endif + +#define UDP4 "udp4" +#define UDP4L 4 +#define UDP6 "udp6" +#define UDP6L 4 +#define UNXD "unxd" +#define UNXDL 4 + + +#define DEFAULT_SIZE 1024 /* default input buffer size */ + +typedef struct { + MRP_TRANSPORT_PUBLIC_FIELDS; /* common transport fields */ + int sock; /* UDP socket */ + int family; /* socket family */ + mrp_io_watch_t *iow; /* socket I/O watch */ + void *ibuf; /* input buffer */ + size_t isize; /* input buffer size */ + size_t idata; /* amount of input data */ +} dgrm_t; + + +static void dgrm_recv_cb(mrp_io_watch_t *w, int fd, mrp_io_event_t events, + void *user_data); +static int dgrm_disconnect(mrp_transport_t *mu); +static int open_socket(dgrm_t *u, int family); + + +/* + * XXX TODO: + * + * There is an almost verbatim copy of this in stream-transport.c + * The only differences are the actual address type specifier + * prefixes... Combine these and separate the result out to a + * new transport-priv.[hc]. + */ + + +static int parse_address(const char *str, int *familyp, char *nodep, + size_t nsize, char **servicep, const char **typep) +{ + char *node, *service; + const char *type; + int family; + size_t l, nl; + + node = (char *)str; + + if (!strncmp(node, UDP4":", l=UDP4L+1)) { + family = AF_INET; + type = UDP4; + node += l; + } + else if (!strncmp(node, UDP6":", l=UDP6L+1)) { + family = AF_INET6; + type = UDP6; + node += l; + } + else if (!strncmp(node, UNXD":", l=UNXDL+1)) { + family = AF_UNIX; + type = UNXD; + node += l; + } + else { + if (node[0] == '[') family = AF_INET6; + else if (node[0] == '/') family = AF_UNIX; + else if (node[0] == '@') family = AF_UNIX; + else family = AF_UNSPEC; + + type = NULL; + } + + switch (family) { + case AF_INET: + service = strrchr(node, ':'); + if (service == NULL) { + errno = EINVAL; + return -1; + } + + nl = service - node; + service++; + + case AF_INET6: + service = strrchr(node, ':'); + + if (service == NULL || service == node) { + errno = EINVAL; + return -1; + } + + if (node[0] == '[') { + node++; + + if (service[-1] != ']') { + errno = EINVAL; + return -1; + } + + nl = service - node - 1; + } + else + nl = service - node; + service++; + break; + + case AF_UNSPEC: + if (!strncmp(node, "tcp:", l=4)) + node += l; + service = strrchr(node, ':'); + + if (service == NULL || service == node) { + errno = EINVAL; + return -1; + } + + if (node[0] == '[') { + node++; + family = AF_INET6; + + if (service[-1] != ']') { + errno = EINVAL; + return -1; + } + + nl = service - node - 1; + } + else { + family = AF_INET; + nl = service - node; + } + service++; + break; + + case AF_UNIX: + service = NULL; + nl = strlen(node); + } + + if (nl >= nsize) { + errno = ENOMEM; + return -1; + } + + strncpy(nodep, node, nl); + nodep[nl] = '\0'; + *servicep = service; + *familyp = family; + if (typep != NULL) + *typep = type; + + return 0; +} + + +static socklen_t dgrm_resolve(const char *str, mrp_sockaddr_t *addr, + socklen_t size, const char **typep) +{ + struct addrinfo *ai, hints; + struct sockaddr_un *un; + char node[UNIX_PATH_MAX], *port; + socklen_t len; + + mrp_clear(&hints); + + if (parse_address(str, &hints.ai_family, node, sizeof(node), + &port, typep) < 0) + return 0; + + switch (hints.ai_family) { + case AF_UNIX: + un = &addr->unx; + len = MRP_OFFSET(typeof(*un), sun_path) + strlen(node) + 1; + + if (size < len) + errno = ENOMEM; + else { + un->sun_family = AF_UNIX; + strncpy(un->sun_path, node, UNIX_PATH_MAX-1); + if (un->sun_path[0] == '@') + un->sun_path[0] = '\0'; + } + + /* When binding the socket, we don't need the null at the end */ + len--; + + break; + + case AF_INET: + case AF_INET6: + default: + if (getaddrinfo(node, port, &hints, &ai) == 0) { + if (ai->ai_addrlen <= size) { + memcpy(addr, ai->ai_addr, ai->ai_addrlen); + len = ai->ai_addrlen; + } + else + len = 0; + + freeaddrinfo(ai); + } + else + len = 0; + } + + return len; +} + + +static int dgrm_open(mrp_transport_t *mu) +{ + dgrm_t *u = (dgrm_t *)mu; + + u->sock = -1; + u->family = -1; + + return TRUE; +} + + +static int dgrm_createfrom(mrp_transport_t *mu, void *conn) +{ + dgrm_t *u = (dgrm_t *)mu; + int on; + mrp_io_event_t events; + + u->sock = *(int *)conn; + + if (u->sock >= 0) { + if (mu->flags & MRP_TRANSPORT_REUSEADDR) { + on = 1; + setsockopt(u->sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + } + if (mu->flags & MRP_TRANSPORT_NONBLOCK) { + on = 1; + fcntl(u->sock, F_SETFL, O_NONBLOCK, on); + } + + events = MRP_IO_EVENT_IN | MRP_IO_EVENT_HUP; + u->iow = mrp_add_io_watch(u->ml, u->sock, events, dgrm_recv_cb, u); + + if (u->iow != NULL) + return TRUE; + } + + return FALSE; +} + + +static int dgrm_bind(mrp_transport_t *mu, mrp_sockaddr_t *addr, + socklen_t addrlen) +{ + dgrm_t *u = (dgrm_t *)mu; + + if (u->sock != -1 || !u->connected) { + if (open_socket(u, addr->any.sa_family)) + if (bind(u->sock, &addr->any, addrlen) == 0) + return TRUE; + } + + return FALSE; +} + + +static int dgrm_listen(mrp_transport_t *mt, int backlog) +{ + MRP_UNUSED(mt); + MRP_UNUSED(backlog); + + return TRUE; /* can be connected to without listening */ +} + + +static void dgrm_close(mrp_transport_t *mu) +{ + dgrm_t *u = (dgrm_t *)mu; + + mrp_del_io_watch(u->iow); + u->iow = NULL; + + mrp_free(u->ibuf); + u->ibuf = NULL; + u->isize = 0; + u->idata = 0; + + if (u->sock >= 0){ + close(u->sock); + u->sock = -1; + } +} + + +static void dgrm_recv_cb(mrp_io_watch_t *w, int fd, mrp_io_event_t events, + void *user_data) +{ + dgrm_t *u = (dgrm_t *)user_data; + mrp_transport_t *mu = (mrp_transport_t *)u; + mrp_sockaddr_t addr; + socklen_t addrlen; + uint32_t size; + ssize_t n; + void *data; + int old, error; + + MRP_UNUSED(w); + + if (events & MRP_IO_EVENT_IN) { + if (u->idata == u->isize) { + if (u->isize != 0) { + old = u->isize; + u->isize *= 2; + } + else { + old = 0; + u->isize = DEFAULT_SIZE; + } + if (!mrp_reallocz(u->ibuf, old, u->isize)) { + error = ENOMEM; + fatal_error: + closed: + dgrm_disconnect(mu); + + if (u->evt.closed != NULL) + MRP_TRANSPORT_BUSY(mu, { + mu->evt.closed(mu, error, mu->user_data); + }); + + u->check_destroy(mu); + return; + } + } + + if (recv(fd, &size, sizeof(size), MSG_PEEK) != sizeof(size)) { + error = EIO; + goto fatal_error; + } + + size = ntohl(size); + + if (u->isize < size + sizeof(size)) { + old = u->isize; + u->isize = size + sizeof(size); + + if (!mrp_reallocz(u->ibuf, old, u->isize)) { + error = ENOMEM; + goto fatal_error; + } + } + + addrlen = sizeof(addr); + n = recvfrom(fd, u->ibuf, size + sizeof(size), 0, &addr.any, &addrlen); + + if (n != (ssize_t)(size + sizeof(size))) { + error = n < 0 ? EIO : EPROTO; + goto fatal_error; + } + + data = u->ibuf + sizeof(size); + error = mu->recv_data(mu, data, size, &addr, addrlen); + + if (error) + goto fatal_error; + + if (u->check_destroy(mu)) + return; + } + + if (events & MRP_IO_EVENT_HUP) { + error = 0; + goto closed; + } +} + + +static int open_socket(dgrm_t *u, int family) +{ + mrp_io_event_t events; + int on; + long nb; + + u->sock = socket(family, SOCK_DGRAM, 0); + + if (u->sock != -1) { + if (u->flags & MRP_TRANSPORT_REUSEADDR) { + on = 1; + setsockopt(u->sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + } + if (u->flags & MRP_TRANSPORT_NONBLOCK) { + nb = 1; + fcntl(u->sock, F_SETFL, O_NONBLOCK, nb); + } + if (u->flags & MRP_TRANSPORT_CLOEXEC) { + on = 1; + fcntl(u->sock, F_SETFL, O_CLOEXEC, on); + } + + events = MRP_IO_EVENT_IN | MRP_IO_EVENT_HUP; + u->iow = mrp_add_io_watch(u->ml, u->sock, events, dgrm_recv_cb, u); + + if (u->iow != NULL) + return TRUE; + else { + close(u->sock); + u->sock = -1; + } + } + + return FALSE; +} + + +static int dgrm_connect(mrp_transport_t *mu, mrp_sockaddr_t *addr, + socklen_t addrlen) +{ + dgrm_t *u = (dgrm_t *)mu; + int on; + long nb; + + if (MRP_UNLIKELY(u->family != -1 && u->family != addr->any.sa_family)) + return FALSE; + + if (MRP_UNLIKELY(u->sock == -1)) { + if (!open_socket(u, addr->any.sa_family)) + return FALSE; + } + + if (connect(u->sock, &addr->any, addrlen) == 0) { + on = 1; + setsockopt(u->sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + nb = 1; + fcntl(u->sock, F_SETFL, O_NONBLOCK, nb); + + return TRUE; + } + + return FALSE; +} + + +static int dgrm_disconnect(mrp_transport_t *mu) +{ + dgrm_t *u = (dgrm_t *)mu; + struct sockaddr none = { .sa_family = AF_UNSPEC, }; + + + if (u->connected) { + connect(u->sock, &none, sizeof(none)); + + return TRUE; + } + else + return FALSE; +} + + +static int dgrm_send(mrp_transport_t *mu, mrp_msg_t *msg) +{ + dgrm_t *u = (dgrm_t *)mu; + struct iovec iov[2]; + void *buf; + ssize_t size, n; + uint32_t len; + + if (u->connected) { + size = mrp_msg_default_encode(msg, &buf); + + if (size >= 0) { + len = htonl(size); + iov[0].iov_base = &len; + iov[0].iov_len = sizeof(len); + iov[1].iov_base = buf; + iov[1].iov_len = size; + + n = writev(u->sock, iov, 2); + mrp_free(buf); + + if (n == (ssize_t)(size + sizeof(len))) + return TRUE; + else { + if (n == -1 && errno == EAGAIN) { + mrp_log_error("%s(): XXX TODO: this sucks, need to add " + "output queuing for dgrm-transport.", + __FUNCTION__); + } + } + } + } + + return FALSE; +} + + +static int dgrm_sendto(mrp_transport_t *mu, mrp_msg_t *msg, + mrp_sockaddr_t *addr, socklen_t addrlen) +{ + dgrm_t *u = (dgrm_t *)mu; + struct iovec iov[2]; + void *buf; + ssize_t size, n; + uint32_t len; + struct msghdr hdr; + + if (MRP_UNLIKELY(u->sock == -1)) { + if (!open_socket(u, ((struct sockaddr *)addr)->sa_family)) + return FALSE; + } + + size = mrp_msg_default_encode(msg, &buf); + + if (size >= 0) { + len = htonl(size); + iov[0].iov_base = &len; + iov[0].iov_len = sizeof(len); + iov[1].iov_base = buf; + iov[1].iov_len = size; + + hdr.msg_name = addr; + hdr.msg_namelen = addrlen; + hdr.msg_iov = iov; + hdr.msg_iovlen = MRP_ARRAY_SIZE(iov); + + hdr.msg_control = NULL; + hdr.msg_controllen = 0; + hdr.msg_flags = 0; + + n = sendmsg(u->sock, &hdr, 0); + mrp_free(buf); + + if (n == (ssize_t)(size + sizeof(len))) + return TRUE; + else { + if (n == -1 && errno == EAGAIN) { + mrp_log_error("%s(): XXX TODO: dgrm-transport send failed", + __FUNCTION__); + } + } + } + + return FALSE; +} + + +static int dgrm_sendraw(mrp_transport_t *mu, void *data, size_t size) +{ + dgrm_t *u = (dgrm_t *)mu; + ssize_t n; + + if (u->connected) { + n = write(u->sock, data, size); + + if (n == (ssize_t)size) + return TRUE; + else { + if (n == -1 && errno == EAGAIN) { + mrp_log_error("%s(): XXX TODO: this sucks, need to add " + "output queuing for dgrm-transport.", + __FUNCTION__); + } + } + } + + return FALSE; +} + + +static int dgrm_sendrawto(mrp_transport_t *mu, void *data, size_t size, + mrp_sockaddr_t *addr, socklen_t addrlen) +{ + dgrm_t *u = (dgrm_t *)mu; + ssize_t n; + + if (MRP_UNLIKELY(u->sock == -1)) { + if (!open_socket(u, ((struct sockaddr *)addr)->sa_family)) + return FALSE; + } + + n = sendto(u->sock, data, size, 0, &addr->any, addrlen); + + if (n == (ssize_t)size) + return TRUE; + else { + if (n == -1 && errno == EAGAIN) { + mrp_log_error("%s(): XXX TODO: dgrm-transport send failed", + __FUNCTION__); + } + } + + return FALSE; +} + + +static int senddatato(mrp_transport_t *mu, void *data, uint16_t tag, + mrp_sockaddr_t *addr, socklen_t addrlen) +{ + dgrm_t *u = (dgrm_t *)mu; + mrp_data_descr_t *type; + ssize_t n; + void *buf; + size_t size, reserve, len; + uint32_t *lenp; + uint16_t *tagp; + + if (MRP_UNLIKELY(u->sock == -1)) { + if (!open_socket(u, ((struct sockaddr *)addr)->sa_family)) + return FALSE; + } + + type = mrp_msg_find_type(tag); + + if (type != NULL) { + reserve = sizeof(*lenp) + sizeof(*tagp); + size = mrp_data_encode(&buf, data, type, reserve); + + if (size > 0) { + lenp = buf; + len = size - sizeof(*lenp); + tagp = buf + sizeof(*lenp); + *lenp = htobe32(len); + *tagp = htobe16(tag); + + if (u->connected) + n = send(u->sock, buf, len + sizeof(*lenp), 0); + else + n = sendto(u->sock, buf, len + sizeof(*lenp), 0, &addr->any, + addrlen); + + mrp_free(buf); + + if (n == (ssize_t)(len + sizeof(*lenp))) + return TRUE; + else { + if (n == -1 && errno == EAGAIN) { + mrp_log_error("%s(): XXX TODO: dgrm-transport send" + " needs queuing", __FUNCTION__); + } + } + } + } + + return FALSE; +} + + +static int dgrm_senddata(mrp_transport_t *mu, void *data, uint16_t tag) +{ + if (mu->connected) + return senddatato(mu, data, tag, NULL, 0); + else + return FALSE; +} + + +static int dgrm_senddatato(mrp_transport_t *mu, void *data, uint16_t tag, + mrp_sockaddr_t *addr, socklen_t addrlen) +{ + return senddatato(mu, data, tag, addr, addrlen); +} + + +static int sendnativeto(mrp_transport_t *mu, void *data, uint32_t type_id, + mrp_sockaddr_t *addr, socklen_t addrlen) +{ + dgrm_t *u = (dgrm_t *)mu; + mrp_typemap_t *map = u->map; + void *buf; + size_t size, reserve; + uint32_t *lenp; + ssize_t n; + + if (MRP_UNLIKELY(u->sock == -1)) { + if (!open_socket(u, ((struct sockaddr *)addr)->sa_family)) + return FALSE; + } + + reserve = sizeof(*lenp); + + if (mrp_encode_native(data, type_id, reserve, &buf, &size, map) > 0) { + lenp = buf; + *lenp = htobe32(size - sizeof(*lenp)); + + if (u->connected) + n = send(u->sock, buf, size, 0); + else + n = sendto(u->sock, buf, size, 0, &addr->any, addrlen); + + mrp_free(buf); + + if (n == (ssize_t)size) + return TRUE; + else { + if (n == -1 && errno == EAGAIN) { + mrp_log_error("%s(): XXX TODO: dgrm-transport send" + " needs queuing", __FUNCTION__); + } + } + } + + return FALSE; +} + + +static int dgrm_sendnative(mrp_transport_t *mu, void *data, uint32_t type_id) +{ + if (mu->connected) + return sendnativeto(mu, data, type_id, NULL, 0); + else + return FALSE; +} + + +static int dgrm_sendnativeto(mrp_transport_t *mu, void *data, uint32_t type_id, + mrp_sockaddr_t *addr, socklen_t addrlen) +{ + return sendnativeto(mu, data, type_id, addr, addrlen); +} + + +static int sendjsonto(mrp_transport_t *mu, mrp_json_t *msg, + mrp_sockaddr_t *addr, socklen_t addrlen) +{ + dgrm_t *u = (dgrm_t *)mu; + struct iovec iov[2]; + const char *s; + ssize_t size, n; + uint32_t len; + + if (MRP_UNLIKELY(u->sock == -1)) { + if (!open_socket(u, ((struct sockaddr *)addr)->sa_family)) + return FALSE; + } + + if (u->connected && (s = mrp_json_object_to_string(msg)) != NULL) { + size = strlen(s); + len = htobe32(size); + + iov[0].iov_base = &len; + iov[0].iov_len = sizeof(len); + iov[1].iov_base = (char *)s; + iov[1].iov_len = size; + + if (u->connected) + n = writev(u->sock, iov, 2); + else { + struct msghdr mh; + + mh.msg_name = &addr->any; + mh.msg_namelen = addrlen; + mh.msg_iov = iov; + mh.msg_iovlen = MRP_ARRAY_SIZE(iov); + mh.msg_control = NULL; + mh.msg_controllen = 0; + mh.msg_flags = 0; + + n = sendmsg(u->sock, &mh, 0); + } + + if (n == (ssize_t)(size + sizeof(len))) + return TRUE; + else { + if (n == -1 && errno == EAGAIN) { + mrp_log_error("%s(): XXX TODO: this sucks, need to add " + "output queuing for dgrm-transport.", + __FUNCTION__); + } + } + } + + return FALSE; +} + + +static int dgrm_sendjson(mrp_transport_t *mu, mrp_json_t *msg) +{ + if (mu->connected) + return sendjsonto(mu, msg, NULL, 0); + else + return FALSE; +} + + +static int dgrm_sendjsonto(mrp_transport_t *mu, mrp_json_t *msg, + mrp_sockaddr_t *addr, socklen_t addrlen) +{ + return sendjsonto(mu, msg, addr, addrlen); +} + + +MRP_REGISTER_TRANSPORT(udp4, UDP4, dgrm_t, dgrm_resolve, + dgrm_open, dgrm_createfrom, dgrm_close, NULL, + dgrm_bind, dgrm_listen, NULL, + dgrm_connect, dgrm_disconnect, + dgrm_send, dgrm_sendto, + dgrm_sendraw, dgrm_sendrawto, + dgrm_senddata, dgrm_senddatato, + NULL, NULL, + dgrm_sendnative, dgrm_sendnativeto, + dgrm_sendjson, dgrm_sendjsonto); + +MRP_REGISTER_TRANSPORT(udp6, UDP6, dgrm_t, dgrm_resolve, + dgrm_open, dgrm_createfrom, dgrm_close, NULL, + dgrm_bind, dgrm_listen, NULL, + dgrm_connect, dgrm_disconnect, + dgrm_send, dgrm_sendto, + dgrm_sendraw, dgrm_sendrawto, + dgrm_senddata, dgrm_senddatato, + NULL, NULL, + dgrm_sendnative, dgrm_sendnativeto, + dgrm_sendjson, dgrm_sendjsonto); + +MRP_REGISTER_TRANSPORT(unxdgrm, UNXD, dgrm_t, dgrm_resolve, + dgrm_open, dgrm_createfrom, dgrm_close, NULL, + dgrm_bind, dgrm_listen, NULL, + dgrm_connect, dgrm_disconnect, + dgrm_send, dgrm_sendto, + dgrm_sendraw, dgrm_sendrawto, + dgrm_senddata, dgrm_senddatato, + NULL, NULL, + dgrm_sendnative, dgrm_sendnativeto, + dgrm_sendjson, dgrm_sendjsonto); diff --git a/src/common/ecore-glue.c b/src/common/ecore-glue.c new file mode 100644 index 0000000..8ad6238 --- /dev/null +++ b/src/common/ecore-glue.c @@ -0,0 +1,372 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <errno.h> + +#include <Ecore.h> + +#include <murphy/common/mm.h> +#include <murphy/common/mainloop.h> + + +typedef struct { + int ecore; +} ecore_glue_t; + + +typedef struct { + Ecore_Fd_Handler *ec_io; + void (*cb)(void *glue_data, + void *id, int fd, mrp_io_event_t events, + void *user_data); + mrp_io_event_t mask; + void *user_data; + void *glue_data; +} io_t; + + +typedef struct { + Ecore_Timer *ec_t; + void (*cb)(void *glue_data, void *id, void *user_data); + void *user_data; + void *glue_data; +} tmr_t; + + +typedef struct { + Ecore_Timer *ec_t; + void (*cb)(void *glue_data, void *id, void *user_data); + void *user_data; + void *glue_data; +} dfr_t; + + +#define D(fmt, args...) do { \ + printf("* [%s]: "fmt"\n", __FUNCTION__ , ## args); \ + } while (0) + + +static void *add_io(void *glue_data, int fd, mrp_io_event_t events, + void (*cb)(void *glue_data, void *id, int fd, + mrp_io_event_t events, void *user_data), + void *user_data); +static void del_io(void *glue_data, void *id); + +static void *add_timer(void *glue_data, unsigned int msecs, + void (*cb)(void *glue_data, void *id, void *user_data), + void *user_data); +static void del_timer(void *glue_data, void *id); +static void mod_timer(void *glue_data, void *id, unsigned int msecs); + +static void *add_defer(void *glue_data, + void (*cb)(void *glue_data, void *id, void *user_data), + void *user_data); +static void del_defer(void *glue_data, void *id); +static void mod_defer(void *glue_data, void *id, int enabled); + + +static int io_check_hup(int fd) +{ + char buf[1]; + int saved_errno, n; + + saved_errno = errno; + n = recv(fd, buf, 1, MSG_PEEK); + errno = saved_errno; + + return (n == 0); +} + + +static Eina_Bool io_cb(void *user_data, Ecore_Fd_Handler *ec_io) +{ + io_t *io = (io_t *)user_data; + mrp_io_event_t events = MRP_IO_EVENT_NONE; + int fd = ecore_main_fd_handler_fd_get(ec_io); + + if (ecore_main_fd_handler_active_get(ec_io, ECORE_FD_READ)) + events |= MRP_IO_EVENT_IN; + if (ecore_main_fd_handler_active_get(ec_io, ECORE_FD_WRITE)) + events |= MRP_IO_EVENT_OUT; + if (ecore_main_fd_handler_active_get(ec_io, ECORE_FD_ERROR)) + events |= MRP_IO_EVENT_ERR; + +#if 0 /* Pfoof... ecore cannot monitor for HUP. */ + if (ecore_main_fd_handler_active_get(ec_io, NO_SUCH_ECORE_EVENT)) + events |= MRP_IO_EVENT_HUP; +#else + if ((io->mask & MRP_IO_EVENT_HUP) && (events & MRP_IO_EVENT_IN)) + if (io_check_hup(fd)) + events |= MRP_IO_EVENT_HUP; +#endif + + io->cb(io->glue_data, io, fd, events, io->user_data); + + return ECORE_CALLBACK_RENEW; +} + + +static void *add_io(void *glue_data, int fd, mrp_io_event_t events, + void (*cb)(void *glue_data, void *id, int fd, + mrp_io_event_t events, void *user_data), + void *user_data) +{ + int mask = 0; + io_t *io; + + io = mrp_allocz(sizeof(*io)); + + if (io != NULL) { + if (events & MRP_IO_EVENT_IN) mask |= ECORE_FD_READ; + if (events & MRP_IO_EVENT_OUT) mask |= ECORE_FD_WRITE; +#if 0 /* Pfoof... ecore cannot monitor for HUP. */ + if (events & MRP_IO_EVENT_HUP) mask |= NO_SUCH_ECORE_EVENT; +#endif + if (events & MRP_IO_EVENT_ERR) mask |= ECORE_FD_ERROR; + + io->mask = events; + io->ec_io = ecore_main_fd_handler_add(fd, mask, io_cb, io, NULL, NULL); + + if (io->ec_io != NULL) { + io->cb = cb; + io->user_data = user_data; + io->glue_data = glue_data; + + return io; + } + else + mrp_free(io); + } + + return NULL; +} + + +static void del_io(void *glue_data, void *id) +{ + io_t *io = (io_t *)id; + + MRP_UNUSED(glue_data); + + ecore_main_fd_handler_del(io->ec_io); + mrp_free(io); +} + + +static Eina_Bool timer_cb(void *user_data) +{ + tmr_t *t = (tmr_t *)user_data; + + t->cb(t->glue_data, t, t->user_data); + + return ECORE_CALLBACK_RENEW; +} + + +static void *add_timer(void *glue_data, unsigned int msecs, + void (*cb)(void *glue_data, void *id, void *user_data), + void *user_data) +{ + double interval = (1.0 * msecs) / 1000.0; + tmr_t *t; + + t = mrp_allocz(sizeof(*t)); + + if (t != NULL) { + t->ec_t = ecore_timer_add(interval, timer_cb, t); + + if (t->ec_t != NULL) { + t->cb = cb; + t->user_data = user_data; + t->glue_data = glue_data; + + return t; + } + else + mrp_free(t); + } + + return NULL; +} + + +static void del_timer(void *glue_data, void *id) +{ + tmr_t *t = (tmr_t *)id; + + MRP_UNUSED(glue_data); + + ecore_timer_del(t->ec_t); + mrp_free(t); +} + + +static void mod_timer(void *glue_data, void *id, unsigned int msecs) +{ + tmr_t *t = (tmr_t *)id; + double interval = (1.0 * msecs) / 1000.0; + + MRP_UNUSED(glue_data); + + if (t != NULL) { + /* + * Notes: + * ecore_timer_reset needs to be called after updating the + * interval. Otherwise the change will not be effective. + * + * In practice, since we use mod_timer to update our super- + * loop briding timer to latch at the next mrp_mainloop_t + * timer moment, this could cause our mainloop to hang + * right in the beginning, since the bridging timer has an + * initial interval of (uint32_t)-1 (no next event). If we + * have no other event sources than timers this would cause + * our mainloop to hang indefinitely. If we have other event + * sources (I/O or signals), the mainloop would hang till a + * non-timer event comes in. + */ + ecore_timer_interval_set(t->ec_t, interval); + ecore_timer_reset(t->ec_t); + } +} + + +static Eina_Bool defer_cb(void *user_data) +{ + dfr_t *d = (dfr_t *)user_data; + + d->cb(d->glue_data, d, d->user_data); + + return ECORE_CALLBACK_RENEW; +} + + +static void *add_defer(void *glue_data, + void (*cb)(void *glue_data, void *id, void *user_data), + void *user_data) +{ + dfr_t *d; + + d = mrp_allocz(sizeof(*d)); + + if (d != NULL) { + d->ec_t = ecore_timer_add(0.0, defer_cb, d); + + if (d->ec_t != NULL) { + d->cb = cb; + d->user_data = user_data; + d->glue_data = glue_data; + + return d; + } + else + mrp_free(d); + } + + return NULL; +} + + +static void del_defer(void *glue_data, void *id) +{ + dfr_t *d = (dfr_t *)id; + + MRP_UNUSED(glue_data); + + ecore_timer_del(d->ec_t); + mrp_free(d); +} + + +static void mod_defer(void *glue_data, void *id, int enabled) +{ + dfr_t *d = (dfr_t *)id; + + MRP_UNUSED(glue_data); + + if (enabled) + ecore_timer_thaw(d->ec_t); + else + ecore_timer_freeze(d->ec_t); +} + + +static void unregister(void *data) +{ + MRP_UNUSED(data); +} + + +static mrp_superloop_ops_t ecore_ops = { + .add_io = add_io, + .del_io = del_io, + .add_timer = add_timer, + .del_timer = del_timer, + .mod_timer = mod_timer, + .add_defer = add_defer, + .del_defer = del_defer, + .mod_defer = mod_defer, + .unregister = unregister, +}; + + +static const char *ecore_glue = "murphy-ecore-glue"; +static mrp_mainloop_t *ecore_ml; + + +int mrp_mainloop_register_with_ecore(mrp_mainloop_t *ml) +{ + return mrp_set_superloop(ml, &ecore_ops, (void *)ecore_glue); +} + + +int mrp_mainloop_unregister_from_ecore(mrp_mainloop_t *ml) +{ + return mrp_mainloop_unregister(ml); +} + + +mrp_mainloop_t *mrp_mainloop_ecore_get(void) +{ + if (ecore_ml == NULL) { + ecore_init(); + + ecore_ml = mrp_mainloop_create(); + + if (ecore_ml != NULL) { + if (!mrp_mainloop_register_with_ecore(ecore_ml)) { + mrp_mainloop_destroy(ecore_ml); + ecore_ml = NULL; + } + } + } + + return ecore_ml; +} diff --git a/src/common/ecore-glue.h b/src/common/ecore-glue.h new file mode 100644 index 0000000..ebf53a6 --- /dev/null +++ b/src/common/ecore-glue.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_ECORE_H__ +#define __MURPHY_ECORE_H__ + +MRP_CDECL_BEGIN + +/** Register the given murphy mainloop with the ecore mainloop. */ +int mrp_mainloop_register_with_ecore(mrp_mainloop_t *ml); + +/** Unrgister the given murphy mainloop from the ecore mainloop. */ +int mrp_mainloop_unregister_from_ecore(mrp_mainloop_t *ml); + +/** Create a murphy mainloop and set it up with the ecore mainloop. */ +mrp_mainloop_t *mrp_mainloop_ecore_get(void); + +MRP_CDECL_END + +#endif /* __MURPHY_ECORE_H__ */ diff --git a/src/common/env.c b/src/common/env.c new file mode 100644 index 0000000..4ea8bad --- /dev/null +++ b/src/common/env.c @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2012 - 2013, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> + +const char *mrp_env_config_key(const char *config, const char *key) +{ + const char *beg; + int len; + + if (config != NULL) { + len = strlen(key); + + beg = config; + while (beg != NULL) { + beg = strstr(beg, key); + + if (beg != NULL) { + if ((beg == config || beg[-1] == ':') && + (beg[len] == '=' || beg[len] == ':' || beg[len] == '\0')) + return (beg[len] == '=' ? beg + len + 1 : ""); + else + beg++; + } + } + } + + return NULL; +} + + +int32_t mrp_env_config_int32(const char *cfg, const char *key, int32_t defval) +{ + const char *v; + char *end; + int i; + + v = mrp_env_config_key(cfg, key); + + if (v != NULL) { + if (*v) { + i = strtol(v, &end, 10); + + if (end && (!*end || *end == ':')) + return i; + } + } + + return defval; +} + + +uint32_t mrp_env_config_uint32(const char *cfg, const char *key, + uint32_t defval) +{ + const char *v; + char *end; + int i; + + v = mrp_env_config_key(cfg, key); + + if (v != NULL) { + if (*v) { + i = strtol(v, &end, 10); + + if (end && (!*end || *end == ':')) + return i; + } + } + + return defval; +} + + +int mrp_env_config_bool(const char *config, const char *key, bool defval) +{ + const char *v; + + v = mrp_env_config_key(config, key); + + if (v != NULL) { + if (*v) { + if ((!strncasecmp(v, "false", 5) && (v[5] == ':' || !v[5])) || + (!strncasecmp(v, "true" , 4) && (v[4] == ':' || !v[4]))) + return (v[0] == 't' || v[0] == 'T'); + if ((!strncasecmp(v, "no" , 2) && (v[2] == ':' || !v[2])) || + (!strncasecmp(v, "yes", 3) && (v[3] == ':' || !v[3]))) + return (v[0] == 'y' || v[0] == 'Y'); + if ((!strncasecmp(v, "on" , 2) && (v[2] == ':' || !v[2])) || + (!strncasecmp(v, "off", 3) && (v[3] == ':' || !v[3]))) + return (v[1] == 'n' || v[1] == 'N'); + } + else if (*v == '\0') + return !defval; /* hmm... is this right */ + } + + return defval; +} + + +int mrp_env_config_string(const char *cfg, const char *key, + const char *defval, char *buf, size_t size) +{ + const char *v; + char *end; + int len; + + v = mrp_env_config_key(cfg, key); + + if (v == NULL) + v = defval; + + end = strchr(v, ':'); + + if (end != NULL) + len = end - v; + else + len = strlen(v); + + len = snprintf(buf, size, "%*.*s", len, len, v); + + if (len >= (int)size - 1) + buf[size - 1] = '\0'; + + return len; +} diff --git a/src/common/env.h b/src/common/env.h new file mode 100644 index 0000000..1a739e8 --- /dev/null +++ b/src/common/env.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2012 - 2013, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_ENV_H__ +#define __MURPHY_ENV_H__ + +#include <stdint.h> +#include <stdbool.h> + +/** Extract the value for the given key from the config string. */ +const char *mrp_env_config_key(const char *config, const char *key); + +/** Extract an int32 value for key, returning defval if not found. */ +int32_t mrp_env_config_int32(const char *cfg, const char *key, int32_t defval); + +/** Extract an uint32 value for key, returning defval if not found. */ +uint32_t mrp_env_config_uint32(const char *cfg, const char *key, + uint32_t defval); + +/** Extract a boolean value for key, returning defval if not found. */ +bool mrp_env_config_bool(const char *config, const char *key, bool defval); + +/** Extract a string value for key to buf (of size), defaulting to defval. */ +int mrp_env_config_string(const char *cfg, const char *key, + const char *defval, char *buf, size_t size); + +#endif /* __MURPHY_ENV_H__ */ diff --git a/src/common/file-utils.c b/src/common/file-utils.c new file mode 100644 index 0000000..ce78c80 --- /dev/null +++ b/src/common/file-utils.c @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <stdbool.h> +#include <dirent.h> +#include <regex.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <murphy/common/macros.h> +#include <murphy/common/debug.h> +#include <murphy/common/file-utils.h> + + +static const char *translate_glob(const char *pattern, char *glob, size_t size) +{ + MRP_UNUSED(glob); + MRP_UNUSED(size); + + /* XXX FIXME: translate pattern to glob-like */ + + return pattern; +} + + +static inline mrp_dirent_type_t dirent_type(mode_t mode) +{ +#define MAP_TYPE(x, y) if (S_IS##x(mode)) return MRP_DIRENT_##y + + MAP_TYPE(REG, REG); + MAP_TYPE(DIR, DIR); + MAP_TYPE(LNK, LNK); + MAP_TYPE(CHR, CHR); + MAP_TYPE(BLK, BLK); + MAP_TYPE(FIFO, FIFO); + MAP_TYPE(SOCK, SOCK); + + return MRP_DIRENT_UNKNOWN; + +#undef MAP_TYPE +} + + +int mrp_scan_dir(const char *path, const char *pattern, mrp_dirent_type_t mask, + mrp_scan_dir_cb_t cb, void *user_data) +{ + DIR *dp; + struct dirent *de; + struct stat st; + regex_t regexp; + const char *prefix; + char glob[1024], file[PATH_MAX]; + size_t size; + int stop; + mrp_dirent_type_t type; + + if ((dp = opendir(path)) == NULL) + return FALSE; + + if (pattern != NULL) { + prefix = MRP_PATTERN_GLOB; + size = sizeof(MRP_PATTERN_GLOB) - 1; + + if (!strncmp(pattern, prefix, size)) { + pattern = translate_glob(pattern + size, glob, sizeof(glob)); + + if (pattern == NULL) { + closedir(dp); + return FALSE; + } + } + else { + prefix = MRP_PATTERN_REGEX; + size = sizeof(MRP_PATTERN_REGEX) - 1; + + if (!strncmp(pattern, prefix, size)) + pattern += size; + } + + if (regcomp(®exp, pattern, REG_EXTENDED | REG_NOSUB) != 0) { + closedir(dp); + return FALSE; + } + } + + stop = FALSE; + while ((de = readdir(dp)) != NULL && !stop) { + if (pattern != NULL && regexec(®exp, de->d_name, 0, NULL, 0) != 0) + continue; + + snprintf(file, sizeof(file), "%s/%s", path, de->d_name); + + if (((mask & MRP_DIRENT_LNK ? lstat : stat))(file, &st) != 0) + continue; + + type = dirent_type(st.st_mode); + if (!(type & mask)) + continue; + + stop = !cb(de->d_name, type, user_data); + } + + + closedir(dp); + if (pattern != NULL) + regfree(®exp); + + return TRUE; +} + + +int mrp_find_file(const char *file, const char **dirs, int mode, char *buf, + size_t size) +{ + const char *dir; + char path[PATH_MAX]; + int i; + + if (file[0] != '/') { + if (dirs != NULL) { + for (dir = dirs[i=0]; dir != NULL; dir = dirs[++i]) { + if (snprintf(path, sizeof(path), "%s/%s", + dir, file) >= (ssize_t)sizeof(path)) + continue; + + if (access(path, mode) == 0) { + file = path; + goto found; + } + } + } + + if (snprintf(path, sizeof(path), "./%s", file) < (ssize_t)sizeof(path)) { + if (access(path, mode) == 0) { + file = path; + goto found; + } + } + } + else { + if (access(file, mode) == 0) + goto found; + } + + errno = ENOENT; + return -1; + + found: + if (buf != NULL && size > 0) + snprintf(buf, size, "%s", file); + + return 0; +} + + +int mrp_mkdir(const char *path, mode_t mode) +{ + const char *p; + char *q, buf[PATH_MAX]; + int n, undo[PATH_MAX / 2]; + struct stat st; + + if (path == NULL || path[0] == '\0') { + errno = path ? EINVAL : EFAULT; + return -1; + } + + /* + * Notes: + * Our directory creation algorithm logic closely resembles what + * 'mkdir -p' does. We simply walk the given path component by + * component, testing if each one exist. If an existing one is + * not a directory we bail out. Missing ones we try to create with + * the given mode, bailing out if we fail. + * + * Unlike 'mkdir -p' whenever we fail we clean up by removing + * all directories we have created (or at least we try). + * + * Similarly to 'mkdir -p' we don't try to be overly 'smart' about + * the path we're handling. Especially we never try to treat '..' + * in any special way. This is very much intentional and the idea + * is to let the caller try to create a full directory hierarchy + * atomically, either succeeeding creating the full hierarchy, or + * none of it. To see the consequences of these design choices, + * consider what are the possible outcomes of a call like + * + * mrp_mkdir("/home/kli/bin/../sbin/../scripts/../etc/../doc", 0755); + */ + + p = path; + q = buf; + n = 0; + while (1) { + if (q - buf >= (ptrdiff_t)sizeof(buf) - 1) { + errno = ENAMETOOLONG; + goto cleanup; + } + + if (*p && *p != '/') { + *q++ = *p++; + continue; + } + + *q = '\0'; + + mrp_debug("checking/creating '%s'...", buf); + + if (q != buf) { + if (stat(buf, &st) < 0) { + if (errno != ENOENT) + goto cleanup; + + if (mkdir(buf, mode) < 0) + goto cleanup; + + undo[n++] = q - buf; + } + else { + if (!S_ISDIR(st.st_mode)) { + errno = ENOTDIR; + goto cleanup; + } + } + } + + while (*p == '/') + p++; + + if (!*p) + break; + + *q++ = '/'; + } + + return 0; + + cleanup: + while (--n >= 0) { + buf[undo[n]] = '\0'; + mrp_debug("cleaning up '%s'...", buf); + rmdir(buf); + } + + return -1; +} + + +char *mrp_normalize_path(char *buf, size_t size, const char *path) +{ + const char *p; + char *q; + int n, back[PATH_MAX / 2]; + + if (path == NULL) + return NULL; + + if (*path == '\0') { + if (size > 0) { + *buf = '\0'; + return buf; + } + else { + overflow: + errno = ENAMETOOLONG; + return NULL; + } + } + + p = path; + q = buf; + n = 0; + + while (*p) { + if (q - buf + 1 >= (ptrdiff_t)size) + goto overflow; + + if (*p == '/') { + back[n++] = q - buf; + *q++ = *p++; + + skip_slashes: + while (*p == '/') + p++; + + /* + * '.' + * + * We skip './' including all trailing slashes. Note that + * the code is arranged so that whenever we skip trailing + * slashes, we automatically check and skip also trailing + * './'s too... + */ + + if (p[0] == '.' && (p[1] == '/' || p[1] == '\0')) { + p++; + goto skip_slashes; + } + + /* + * '..' + * + * We throw away the last incorrectly saved backtracking + * point (we saved it for this '../'). Then if we can still + * backtrack, we do so. Otherwise (we're at the beginning + * already), if the path is absolute, we just ignore the + * current '../' (can't go above '/'), otherwise we keep it + * for relative pathes. + */ + + if (p[0] == '.' && p[1] == '.' && (p[2] == '/' || p[2] == '\0')) { + n--; /* throw away */ + if (n > 0) { /* can still backtrack */ + if (back[n - 1] >= 0) /* previous not a '..' */ + q = buf + back[n - 1] + 1; + } + else { + if (q > buf && buf[0] == '/') /* for absolute pathes */ + q = buf + 1; /* reset to start */ + else { /* for relative pathes */ + if (q - buf + 4 >= (ptrdiff_t)size) + goto overflow; + + q[0] = '.'; /* append this '..' */ + q[1] = '.'; + q[2] = '/'; + q += 3; + back[n] = -1; /* block backtracking */ + } + } + + p += 2; + goto skip_slashes; + } + } + else + *q++ = *p++; + } + + /* + * finally for other than '/' align trailing slashes + */ + + if (p > path + 1 && p[-1] != '/') + if (q > buf + 1 && q[-1] == '/') + q--; + + *q = '\0'; + + return buf; +} diff --git a/src/common/file-utils.h b/src/common/file-utils.h new file mode 100644 index 0000000..dd7266c --- /dev/null +++ b/src/common/file-utils.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_FILEUTILS_H__ +#define __MURPHY_FILEUTILS_H__ + +#include <dirent.h> +#include <sys/types.h> +#include <sys/stat.h> + +/* + * Routines for scanning a directory for matching entries. + */ + +/** Directory entry types. */ +typedef enum { + MRP_DIRENT_UNKNOWN = 0, /* unknown */ + MRP_DIRENT_NONE = 0x00, /* unknown */ + MRP_DIRENT_FIFO = 0x01, /* FIFO */ + MRP_DIRENT_CHR = 0x02, /* character device */ + MRP_DIRENT_DIR = 0x04, /* directory */ + MRP_DIRENT_BLK = 0x08, /* block device */ + MRP_DIRENT_REG = 0x10, /* regular file */ + MRP_DIRENT_LNK = 0x20, /* symbolic link */ + MRP_DIRENT_SOCK = 0x40, /* socket */ + MRP_DIRENT_ANY = 0xff, /* mask of all types */ +} mrp_dirent_type_t; + + +#define MRP_PATTERN_GLOB "glob:" /* a globbing pattern */ +#define MRP_PATTERN_REGEX "regex:" /* a regexp pattern */ + + +/** Directory scanning callback type. */ +typedef int (*mrp_scan_dir_cb_t)(const char *entry, mrp_dirent_type_t type, + void *user_data); + +/** Scan a directory, calling cb with all matching entries. */ +int mrp_scan_dir(const char *path, const char *pattern, mrp_dirent_type_t mask, + mrp_scan_dir_cb_t cb, void *user_data); + +/** Do an #include-like search for the given file among the given dirs. */ +int mrp_find_file(const char *file, const char **dirs, int mode, char *buf, + size_t size); + +/** Create a directory, creating leading path as necessary. */ +int mrp_mkdir(const char *path, mode_t mode); + + +/** Parse a path into a normalized form, removing ../'s and ./'s. */ +char *mrp_normalize_path(char *buf, size_t size, const char *path); + +#endif /* __MURPHY_FILEUTILS_H__ */ diff --git a/src/common/fragbuf.c b/src/common/fragbuf.c new file mode 100644 index 0000000..740e138 --- /dev/null +++ b/src/common/fragbuf.c @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <endian.h> + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/log.h> +#include <murphy/common/fragbuf.h> + +struct mrp_fragbuf_s { + void *data; /* actual data buffer */ + int size; /* size of the buffer */ + int used; /* amount of data in the bufer */ + int framed : 1; /* whether data is framed */ +}; + + +static void *fragbuf_ensure(mrp_fragbuf_t *buf, size_t size) +{ + int more; + + if (buf->size - buf->used < (int)size) { + more = size - (buf->size - buf->used); + + if (mrp_reallocz(buf->data, buf->size, buf->size + more) == NULL) + return NULL; + else + buf->size += more; + } + + return buf->data + buf->used; +} + + +size_t mrp_fragbuf_used(mrp_fragbuf_t *buf) +{ + return buf->used; +} + + +size_t mrp_fragbuf_missing(mrp_fragbuf_t *buf) +{ + void *ptr; + int offs; + uint32_t size; + + if (!buf->framed || !buf->used) + return 0; + + /* find the last frame */ + ptr = buf->data; + offs = 0; + while (offs < buf->used) { + size = be32toh(*(uint32_t *)ptr); + offs += sizeof(size) + size; + } + + /* get the amount of data missing */ + return offs - buf->used; +} + + +int fragbuf_init(mrp_fragbuf_t *buf, int framed, int pre_alloc) +{ + buf->data = NULL; + buf->size = 0; + buf->used = 0; + buf->framed = framed; + + if (pre_alloc <= 0 || fragbuf_ensure(buf, pre_alloc)) + return TRUE; + else + return FALSE; +} + + +mrp_fragbuf_t *mrp_fragbuf_create(int framed, size_t pre_alloc) +{ + mrp_fragbuf_t *buf; + + buf = mrp_allocz(sizeof(*buf)); + + if (buf != NULL) { + if (fragbuf_init(buf, framed, pre_alloc)) + return buf; + + mrp_free(buf); + } + + return NULL; +} + + +void mrp_fragbuf_reset(mrp_fragbuf_t *buf) +{ + if (buf != NULL) { + mrp_free(buf->data); + buf->data = NULL; + buf->size = 0; + buf->used = 0; + } +} + +void mrp_fragbuf_destroy(mrp_fragbuf_t *buf) +{ + if (buf != NULL) { + mrp_free(buf->data); + mrp_free(buf); + } +} + + +void *mrp_fragbuf_alloc(mrp_fragbuf_t *buf, size_t size) +{ + void *ptr; + + ptr = fragbuf_ensure(buf, size); + + if (ptr != NULL) + buf->used += size; + + return ptr; +} + + +int mrp_fragbuf_trim(mrp_fragbuf_t *buf, void *ptr, size_t osize, size_t nsize) +{ + size_t diff; + + if (ptr + osize == buf->data + buf->used) { /* looks like the last alloc */ + if (nsize <= osize) { + diff = osize - nsize; + buf->used -= diff; + + return TRUE; + } + } + + return FALSE; +} + + +int mrp_fragbuf_push(mrp_fragbuf_t *buf, void *data, size_t size) +{ + void *ptr; + + ptr = fragbuf_ensure(buf, size); + + if (ptr != NULL) { + memcpy(ptr, data, size); + buf->used += size; + + return TRUE; + } + else + return FALSE; +} + + +int mrp_fragbuf_pull(mrp_fragbuf_t *buf, void **datap, size_t *sizep) +{ + void *data; + uint32_t size; + + if (buf == NULL || buf->used <= 0) + return FALSE; + + if (MRP_UNLIKELY(*datap && + (*datap < buf->data || *datap > buf->data + buf->used))) { + mrp_log_warning("%s(): *** looks like we're called with an unreset " + "datap pointer... ***", __FUNCTION__); + } + + /* start of iteration */ + if (*datap == NULL) { + if (!buf->framed) { + *datap = buf->data; + *sizep = buf->used; + + return TRUE; + } + else { + if (buf->used < (int)sizeof(size)) + return FALSE; + + size = be32toh(*(uint32_t *)buf->data); + + if (buf->used >= (int)(sizeof(size) + size)) { + *datap = buf->data + sizeof(size); + *sizep = size; + + return TRUE; + } + else + return FALSE; + } + } + /* continue iteration */ + else { + if (!buf->framed) { + data = *datap + *sizep; + + if (buf->data <= data && data < buf->data + buf->used) { + memmove(buf->data, data, buf->used - (data - buf->data)); + buf->used -= (data - buf->data); + + *datap = buf->data; + *sizep = buf->used; + + return TRUE; + } + else { + if (data == buf->data + buf->used) + buf->used = 0; + + return FALSE; + } + } + else { + if (*datap != buf->data + sizeof(size)) + return FALSE; + + size = be32toh(*(uint32_t *)buf->data); + + if ((int)(size + sizeof(size)) <= buf->used) { + memmove(buf->data, buf->data + size + sizeof(size), + buf->used - (size + sizeof(size))); + buf->used -= size + sizeof(size); + } + else + return FALSE; + + if (buf->used <= (int)sizeof(size)) + return FALSE; + + size = be32toh(*(uint32_t *)buf->data); + data = buf->data + sizeof(size); + + if (buf->used >= (int)(size + sizeof(size))) { + *datap = data; + *sizep = size; + + return TRUE; + } + + return FALSE; + } + } +} diff --git a/src/common/fragbuf.h b/src/common/fragbuf.h new file mode 100644 index 0000000..6e2ecc6 --- /dev/null +++ b/src/common/fragbuf.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_FRAGBUF_H__ +#define __MURPHY_FRAGBUF_H__ + +#include <murphy/common/macros.h> + +MRP_CDECL_BEGIN + +/* + * Fragment collector buffers. + * + * As the name implies, a fragment collector buffer can be used + * to collect message fragments and reassemble messages that were + * transmitted in arbitrary pieces. + * + * Messages are expected to be transmitted in frames where each + * frame simply consist of a 32-bit message size followed by + * the actual message data. On the sending side you can simply + * send each message prefixed with its size. On the receiving side + * you keep feeding the received chunks of data to a fragment + * collector buffer (using mrp_fragbuf_push). After each chunk you + * can iterate through the fully reassembled messages (by calling + * mrp_fragbuf_pull until it returns FALSE). Messages are removed + * automatically from the collector buffer as you iterate through + * them. + * + * You can also create a collector buffer in frameless mode. Such a + * buffer will always return immediately all available data as you + * iterate through it. + */ + +/** Buffer for collecting fragments of (framed or unframed) message data. */ +typedef struct mrp_fragbuf_s mrp_fragbuf_t; + +/** Initialize the given fragment collector buffer. */ +mrp_fragbuf_t *mrp_fragbuf_create(int framed, size_t pre_alloc); + +/** Initialize the given data collector buffer. */ +int mrp_fragbuf_init(mrp_fragbuf_t *buf, int framed, size_t pre_alloc); + +/** Reset the given data collector buffer. */ +void mrp_fragbuf_reset(mrp_fragbuf_t *buf); + +/** Destroy the given data collector buffer, freeing all associated memory. */ +void mrp_fragbuf_destroy(mrp_fragbuf_t *buf); + +/** Return the amount of buffer space currently in used in th buffer. */ +size_t mrp_fragbuf_used(mrp_fragbuf_t *buf); + +/** Return the amount of bytes missing from the last message. */ +size_t mrp_fragbuf_missing(mrp_fragbuf_t *buf); + +/** Allocate a buffer of the given size from the buffer. */ +void *mrp_fragbuf_alloc(mrp_fragbuf_t *buf, size_t size); + +/** Trim the last allocation to nsize bytes. */ +int mrp_fragbuf_trim(mrp_fragbuf_t *buf, void *ptr, size_t osize, size_t nsize); + +/** Append the given data to the buffer. */ +int mrp_fragbuf_push(mrp_fragbuf_t *buf, void *data, size_t size); + +/** Iterate through the given buffer, pulling and freeing assembled messages. */ +int mrp_fragbuf_pull(mrp_fragbuf_t *buf, void **data, size_t *size); + +MRP_CDECL_END + +#endif /* __MURPHY_FRAGBUF_H__ */ diff --git a/src/common/glib-glue.c b/src/common/glib-glue.c new file mode 100644 index 0000000..2df2ea7 --- /dev/null +++ b/src/common/glib-glue.c @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <glib.h> + +#include <murphy/common/mm.h> +#include <murphy/common/mainloop.h> + + +typedef struct { + GMainLoop *gml; +} glib_glue_t; + + +typedef struct { + GIOChannel *gl_ioc; + guint gl_iow; + void (*cb)(void *glue_data, + void *id, int fd, mrp_io_event_t events, + void *user_data); + mrp_io_event_t mask; + void *user_data; + void *glue_data; +} io_t; + + +typedef struct { + guint gl_t; + void (*cb)(void *glue_data, void *id, void *user_data); + void *user_data; + void *glue_data; +} tmr_t; + + +typedef struct { + guint gl_t; + void (*cb)(void *glue_data, void *id, void *user_data); + void *user_data; + void *glue_data; +} dfr_t; + + +#define D(fmt, args...) do { \ + printf("* [%s]: "fmt"\n", __FUNCTION__ , ## args); \ + } while (0) + + +static void *add_io(void *glue_data, int fd, mrp_io_event_t events, + void (*cb)(void *glue_data, void *id, int fd, + mrp_io_event_t events, void *user_data), + void *user_data); +static void del_io(void *glue_data, void *id); + +static void *add_timer(void *glue_data, unsigned int msecs, + void (*cb)(void *glue_data, void *id, void *user_data), + void *user_data); +static void del_timer(void *glue_data, void *id); +static void mod_timer(void *glue_data, void *id, unsigned int msecs); + +static void *add_defer(void *glue_data, + void (*cb)(void *glue_data, void *id, void *user_data), + void *user_data); +static void del_defer(void *glue_data, void *id); +static void mod_defer(void *glue_data, void *id, int enabled); + + +static gboolean io_cb(GIOChannel *ioc, GIOCondition cond, gpointer user_data) +{ + io_t *io = (io_t *)user_data; + mrp_io_event_t events = MRP_IO_EVENT_NONE; + int fd = g_io_channel_unix_get_fd(ioc); + + if (cond & G_IO_IN) + events |= MRP_IO_EVENT_IN; + if (cond & G_IO_OUT) + events |= MRP_IO_EVENT_OUT; + if (cond & G_IO_ERR) + events |= MRP_IO_EVENT_ERR; + if (cond & G_IO_HUP) + events |= MRP_IO_EVENT_HUP; + + io->cb(io->glue_data, io, fd, events, io->user_data); + + return TRUE; +} + + +static void *add_io(void *glue_data, int fd, mrp_io_event_t events, + void (*cb)(void *glue_data, void *id, int fd, + mrp_io_event_t events, void *user_data), + void *user_data) +{ + GIOCondition mask = 0; + GIOChannel *ioc; + io_t *io; + + ioc = g_io_channel_unix_new(fd); + + if (ioc == NULL) + return NULL; + + io = mrp_allocz(sizeof(*io)); + + if (io != NULL) { + if (events & MRP_IO_EVENT_IN ) mask |= G_IO_IN; + if (events & MRP_IO_EVENT_OUT) mask |= G_IO_OUT; + if (events & MRP_IO_EVENT_HUP) mask |= G_IO_HUP; + if (events & MRP_IO_EVENT_ERR) mask |= G_IO_ERR; + + io->mask = events; + io->gl_ioc = ioc; + io->gl_iow = g_io_add_watch(ioc, mask, io_cb, io); + + if (io->gl_iow != 0) { + io->cb = cb; + io->user_data = user_data; + io->glue_data = glue_data; + + return io; + } + else { + g_io_channel_unref(ioc); + mrp_free(io); + } + } + + return NULL; +} + + +static void del_io(void *glue_data, void *id) +{ + io_t *io = (io_t *)id; + + MRP_UNUSED(glue_data); + + g_source_remove(io->gl_iow); + g_io_channel_unref(io->gl_ioc); + mrp_free(io); +} + + +static gboolean timer_cb(gpointer user_data) +{ + tmr_t *t = (tmr_t *)user_data; + + t->cb(t->glue_data, t, t->user_data); + + return TRUE; +} + + +static void *add_timer(void *glue_data, unsigned int msecs, + void (*cb)(void *glue_data, void *id, void *user_data), + void *user_data) +{ + tmr_t *t; + + t = mrp_allocz(sizeof(*t)); + + if (t != NULL) { + t->gl_t = g_timeout_add(msecs, timer_cb, t); + + if (t->gl_t != 0) { + t->cb = cb; + t->user_data = user_data; + t->glue_data = glue_data; + + return t; + } + else + mrp_free(t); + } + + return NULL; +} + + +static void del_timer(void *glue_data, void *id) +{ + tmr_t *t = (tmr_t *)id; + + MRP_UNUSED(glue_data); + + g_source_remove(t->gl_t); + mrp_free(t); +} + + +static void mod_timer(void *glue_data, void *id, unsigned int msecs) +{ + tmr_t *t = (tmr_t *)id; + + MRP_UNUSED(glue_data); + + if (t != NULL) { + g_source_remove(t->gl_t); + t->gl_t = g_timeout_add(msecs, timer_cb, t); + } +} + + +static gboolean defer_cb(void *user_data) +{ + dfr_t *d = (dfr_t *)user_data; + + d->cb(d->glue_data, d, d->user_data); + + return TRUE; +} + + +static void *add_defer(void *glue_data, + void (*cb)(void *glue_data, void *id, void *user_data), + void *user_data) +{ + dfr_t *d; + + d = mrp_allocz(sizeof(*d)); + + if (d != NULL) { + d->gl_t = g_timeout_add(0, defer_cb, d); + + if (d->gl_t != 0) { + d->cb = cb; + d->user_data = user_data; + d->glue_data = glue_data; + + return d; + } + else + mrp_free(d); + } + + return NULL; +} + + +static void del_defer(void *glue_data, void *id) +{ + dfr_t *d = (dfr_t *)id; + + MRP_UNUSED(glue_data); + + if (d->gl_t != 0) + g_source_remove(d->gl_t); + + mrp_free(d); +} + + +static void mod_defer(void *glue_data, void *id, int enabled) +{ + dfr_t *d = (dfr_t *)id; + + MRP_UNUSED(glue_data); + + if (enabled && !d->gl_t) + d->gl_t = g_timeout_add(0, defer_cb, d); + else if (!enabled && d->gl_t) { + g_source_remove(d->gl_t); + d->gl_t = 0; + } +} + + +static void unregister(void *data) +{ + glib_glue_t *glue = (glib_glue_t *)data; + + g_main_loop_unref(glue->gml); + + mrp_free(glue); +} + + +static mrp_superloop_ops_t glib_ops = { + .add_io = add_io, + .del_io = del_io, + .add_timer = add_timer, + .del_timer = del_timer, + .mod_timer = mod_timer, + .add_defer = add_defer, + .del_defer = del_defer, + .mod_defer = mod_defer, + .unregister = unregister, +}; + + +int mrp_mainloop_register_with_glib(mrp_mainloop_t *ml, GMainLoop *gml) +{ + glib_glue_t *glue; + + glue = mrp_allocz(sizeof(*glue)); + + if (glue != NULL) { + glue->gml = g_main_loop_ref(gml); + + if (mrp_set_superloop(ml, &glib_ops, glue)) + return TRUE; + else { + g_main_loop_unref(gml); + mrp_free(glue); + } + } + + return FALSE; +} + + +int mrp_mainloop_unregister_from_glib(mrp_mainloop_t *ml) +{ + return mrp_mainloop_unregister(ml); +} + + +mrp_mainloop_t *mrp_mainloop_glib_get(GMainLoop *gml) +{ + mrp_mainloop_t *ml; + + if (gml != NULL) { + ml = mrp_mainloop_create(); + + if (ml != NULL) { + if (mrp_mainloop_register_with_glib(ml, gml)) + return ml; + else + mrp_mainloop_destroy(ml); + } + } + + return NULL; +} diff --git a/src/common/glib-glue.h b/src/common/glib-glue.h new file mode 100644 index 0000000..88fef45 --- /dev/null +++ b/src/common/glib-glue.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_GLIB_H__ +#define __MURPHY_GLIB_H__ + +#include <murphy/common/mainloop.h> +#include <glib.h> + +MRP_CDECL_BEGIN + +/** Register the given murphy mainloop with the glib mainloop. */ +int mrp_mainloop_register_with_glib(mrp_mainloop_t *ml, GMainLoop *gml); + +/** Unrgister the given murphy mainloop from the glib mainloop. */ +int mrp_mainloop_unregister_from_glib(mrp_mainloop_t *ml); + +/** Create a murphy mainloop and set it up with the glib mainloop. */ +mrp_mainloop_t *mrp_mainloop_glib_get(GMainLoop *gml); + +MRP_CDECL_END + +#endif /* __MURPHY_GLIB_H__ */ diff --git a/src/common/hashtbl.c b/src/common/hashtbl.c new file mode 100644 index 0000000..3f49d83 --- /dev/null +++ b/src/common/hashtbl.c @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdint.h> + +#include "murphy/common/mm.h" +#include "murphy/common/list.h" +#include "murphy/common/hashtbl.h" + +#define MIN_NBUCKET 8 +#define MAX_NBUCKET 128 + +typedef struct { /* a hash bucket */ + mrp_list_hook_t entries; /* hook to hash table entries */ + mrp_list_hook_t used; /* hook to list of buckets in use */ +} bucket_t; + +typedef struct { /* a hash table entry */ + mrp_list_hook_t hook; /* hook to bucket chain */ + void *key; /* key for this entry */ + void *obj; /* object for this entry */ +} entry_t; + +typedef struct { /* iterator state */ + mrp_list_hook_t *bp, *bn; /* current bucket hook pointers */ + mrp_list_hook_t *ep, *en; /* current entry hook pointers */ + entry_t *entry; /* current entry */ + int verdict; /* remove-from-cb verdict */ +} iter_t; + +struct mrp_htbl_s { + bucket_t *buckets; /* hash table buckets */ + size_t nbucket; /* this many of them */ + mrp_list_hook_t used; /* buckets in use */ + mrp_htbl_comp_fn_t comp; /* key comparison function */ + mrp_htbl_hash_fn_t hash; /* key hash function */ + mrp_htbl_free_fn_t free; /* function to free an entry */ + iter_t *iter; /* active iterator state */ +}; + + +static size_t calc_buckets(size_t nbucket) +{ + size_t n; + + if (nbucket < MIN_NBUCKET) + nbucket = MIN_NBUCKET; + if (nbucket > MAX_NBUCKET) + nbucket = MAX_NBUCKET; + + for (n = MIN_NBUCKET; n < nbucket; n <<= 1) + ; + + return n; +} + + +mrp_htbl_t *mrp_htbl_create(mrp_htbl_config_t *cfg) +{ + mrp_htbl_t *ht; + size_t i, nbucket; + + if (cfg->comp && cfg->hash) { + if ((ht = mrp_allocz(sizeof(*ht))) != NULL) { + if (cfg->nbucket != 0) + nbucket = cfg->nbucket; + else { + if (cfg->nentry != 0) + nbucket = cfg->nentry / 4; + else + nbucket = 4 * MIN_NBUCKET; + } + + ht->nbucket = calc_buckets(nbucket); + ht->comp = cfg->comp; + ht->hash = cfg->hash; + ht->free = cfg->free; + + mrp_list_init(&ht->used); + + ht->buckets = mrp_allocz(sizeof(*ht->buckets) * ht->nbucket); + if (ht->buckets != NULL) { + for (i = 0; i < ht->nbucket; i++) { + mrp_list_init(&ht->buckets[i].entries); + mrp_list_init(&ht->buckets[i].used); + } + + return ht; + } + else { + mrp_free(ht); + ht = NULL; + } + } + } + + return NULL; +} + + +void mrp_htbl_destroy(mrp_htbl_t *ht, int free) +{ + if (ht != NULL) { + if (free) + mrp_htbl_reset(ht, free); + + mrp_free(ht->buckets); + mrp_free(ht); + } +} + + +static inline void free_entry(mrp_htbl_t *ht, entry_t *entry, int free) +{ + if (free && ht->free) + ht->free(entry->key, entry->obj); + mrp_free(entry); +} + + +void mrp_htbl_reset(mrp_htbl_t *ht, int free) +{ + mrp_list_hook_t *bp, *bn, *ep, *en; + bucket_t *bucket; + entry_t *entry; + + mrp_list_foreach(&ht->used, bp, bn) { + bucket = mrp_list_entry(bp, bucket_t, used); + + mrp_list_foreach(&bucket->entries, ep, en) { + entry = mrp_list_entry(ep, entry_t, hook); + mrp_list_delete(ep); + free_entry(ht, entry, free); + } + + mrp_list_delete(&bucket->used); + } +} + + +int mrp_htbl_insert(mrp_htbl_t *ht, void *key, void *object) +{ + uint32_t idx = ht->hash(key) & (ht->nbucket - 1); + bucket_t *bucket = ht->buckets + idx; + int first = mrp_list_empty(&bucket->entries); + entry_t *entry; + + if ((entry = mrp_allocz(sizeof(*entry))) != NULL) { + entry->key = key; + entry->obj = object; + mrp_list_append(&bucket->entries, &entry->hook); + if (first) + mrp_list_append(&ht->used, &bucket->used); + + return TRUE; + } + else + return FALSE; +} + + +static inline entry_t *lookup(mrp_htbl_t *ht, void *key, bucket_t **bucketp) +{ + uint32_t idx = ht->hash(key) & (ht->nbucket - 1); + bucket_t *bucket = ht->buckets + idx; + mrp_list_hook_t *p, *n; + entry_t *entry; + + mrp_list_foreach(&bucket->entries, p, n) { + entry = mrp_list_entry(p, entry_t, hook); + + if (!ht->comp(entry->key, key)) { + if (bucketp != NULL) + *bucketp = bucket; + return entry; + } + } + + return NULL; +} + + +void *mrp_htbl_lookup(mrp_htbl_t *ht, void *key) +{ + entry_t *entry; + + entry = lookup(ht, key, NULL); + if (entry != NULL) + return entry->obj; + else + return NULL; +} + + +static void delete_from_bucket(mrp_htbl_t *ht, bucket_t *bucket, entry_t *entry) +{ + mrp_list_hook_t *eh = &entry->hook; + + + /* + * If there is an iterator active and this entry would + * have been the next one to iterate over, we need to + * update the iterator to skip to the next entry instead + * as this one will be removed. Failing to update the + * iterator could crash mrp_htbl_foreach or drive it into + * an infinite loop. + */ + + if (ht->iter != NULL && ht->iter->en == eh) + ht->iter->en = eh->next; + + mrp_list_delete(eh); + + + /* + * If the bucket became empty, unlink it from the used list. + * If also there is an iterator active and this bucket would + * have been the next one to iterate over, we need to + * update the iterator to skip to the next bucket instead + * as this one just became empty and will be removed from + * the used bucket list. Failing to update the iterator + * could drive mrp_htbl_foreach into an infinite loop + * because of the unexpected hop from the used bucket list + * (to a single empty bucket). + */ + + if (mrp_list_empty(&bucket->entries)) { + if (ht->iter != NULL && ht->iter->bn == &bucket->used) + ht->iter->bn = bucket->used.next; + + mrp_list_delete(&bucket->used); + } +} + + +void *mrp_htbl_remove(mrp_htbl_t *ht, void *key, int free) +{ + bucket_t *bucket; + entry_t *entry; + void *object; + + /* + * We need to check the found entry and its hash-bucket + * against any potentially active iterator. Special care + * needs to be taken if the entry is being iterated over + * or if the bucket becomes empty and it would be the next + * bucket to iterate over. The former is taken care of + * here while the latter is handled in delete_from_bucket. + */ + if ((entry = lookup(ht, key, &bucket)) != NULL) { + delete_from_bucket(ht, bucket, entry); + object = entry->obj; + + if (ht->iter != NULL && entry == ht->iter->entry) /* being iterated */ + ht->iter->verdict = free ? MRP_HTBL_ITER_DELETE : 0; + else { + free_entry(ht, entry, free); + } + } + else + object = NULL; + + return object; +} + + +int mrp_htbl_foreach(mrp_htbl_t *ht, mrp_htbl_iter_cb_t cb, void *user_data) +{ + iter_t iter; + bucket_t *bucket; + entry_t *entry; + int cb_verdict, ht_verdict; + + /* + * Now we can only handle a single callback-based iterator. + * If there is already one we're busy so just bail out. + */ + if (ht->iter != NULL) + return FALSE; + + mrp_clear(&iter); + ht->iter = &iter; + + mrp_list_foreach(&ht->used, iter.bp, iter.bn) { + bucket = mrp_list_entry(iter.bp, bucket_t, used); + + mrp_list_foreach(&bucket->entries, iter.ep, iter.en) { + iter.entry = entry = mrp_list_entry(iter.ep, entry_t, hook); + cb_verdict = cb(entry->key, entry->obj, user_data); + ht_verdict = iter.verdict; + + /* delete was called from cb (unhashed entry and marked it) */ + if (ht_verdict & MRP_HTBL_ITER_DELETE) { + free_entry(ht, entry, TRUE); + } + else { + /* cb wants us to unhash (safe even if unhashed in remove) */ + if (cb_verdict & MRP_HTBL_ITER_UNHASH) + mrp_list_delete(iter.ep); + /* cb want us to free entry (and remove was not called) */ + if (cb_verdict & MRP_HTBL_ITER_DELETE) + free_entry(ht, entry, TRUE); + + /* cb wants to stop iterating */ + if (!(cb_verdict & MRP_HTBL_ITER_MORE)) + goto out; + } + } + } + + out: + ht->iter = NULL; + + return TRUE; +} + + +void *mrp_htbl_find(mrp_htbl_t *ht, mrp_htbl_find_cb_t cb, void *user_data) +{ + iter_t iter; + bucket_t *bucket; + entry_t *entry, *found; + + /* + * Bail out if there is also an iterator active... + */ + if (ht->iter != NULL) + return FALSE; + + mrp_clear(&iter); + ht->iter = &iter; + found = NULL; + + mrp_list_foreach(&ht->used, iter.bp, iter.bn) { + bucket = mrp_list_entry(iter.bp, bucket_t, used); + + mrp_list_foreach(&bucket->entries, iter.ep, iter.en) { + entry = mrp_list_entry(iter.ep, entry_t, hook); + + if (cb(entry->key, entry->obj, user_data)) { + found = entry->obj; + goto out; + } + } + } + + out: + ht->iter = NULL; + + return found; +} diff --git a/src/common/hashtbl.h b/src/common/hashtbl.h new file mode 100644 index 0000000..6dd7c05 --- /dev/null +++ b/src/common/hashtbl.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_HASHTBL_H__ +#define __MURPHY_HASHTBL_H__ + +#include <stdint.h> + +#include <murphy/common/macros.h> + +MRP_CDECL_BEGIN + +typedef struct mrp_htbl_s mrp_htbl_t; + +/** Prototype for key comparison functions. */ +typedef int (*mrp_htbl_comp_fn_t)(const void *key1, const void *key2); + +/** Prototoype for key hash functions. */ +typedef uint32_t (*mrp_htbl_hash_fn_t)(const void *key); + +/** Prototype for functions used to free entries. */ +typedef void (*mrp_htbl_free_fn_t)(void *key, void *object); + + +/* + * hash table configuration + */ +typedef struct { + size_t nentry; /* estimated entries */ + mrp_htbl_comp_fn_t comp; /* comparison function */ + mrp_htbl_hash_fn_t hash; /* hash function */ + mrp_htbl_free_fn_t free; /* freeing function */ + size_t nbucket; /* number of buckets, or 0 */ +} mrp_htbl_config_t; + + +/** Create a new hash table with the given configuration. */ +mrp_htbl_t *mrp_htbl_create(mrp_htbl_config_t *cfg); + +/** Destroy a hash table, free all entries unless @free is FALSE. */ +void mrp_htbl_destroy(mrp_htbl_t *ht, int free); + +/** Reset a hash table, also free all entries unless @free is FALSE. */ +void mrp_htbl_reset(mrp_htbl_t *ht, int free); + +/** Insert the given @key/@object pair to the hash table. */ +int mrp_htbl_insert(mrp_htbl_t *ht, void *key, void *object); + +/** Remove and return the object for @key, also free unless @free is FALSE. */ +void *mrp_htbl_remove(mrp_htbl_t *ht, void *key, int free); + +/** Look up the object corresponding to @key. */ +void *mrp_htbl_lookup(mrp_htbl_t *ht, void *key); + +/** Find the first matching entry in a hash table. */ +typedef int (*mrp_htbl_find_cb_t)(void *key, void *object, void *user_data); +void *mrp_htbl_find(mrp_htbl_t *ht, mrp_htbl_find_cb_t cb, void *user_data); + + +/* + * hash table iterators + */ + +enum { + MRP_HTBL_ITER_STOP = 0x0, /* stop iterating */ + MRP_HTBL_ITER_MORE = 0x1, /* keep iterating */ + MRP_HTBL_ITER_UNHASH = 0x2, /* unhash without freeing */ + MRP_HTBL_ITER_DELETE = 0x6, /* unhash and free */ +}; + +typedef int (*mrp_htbl_iter_cb_t)(void *key, void *object, void *user_data); +int mrp_htbl_foreach(mrp_htbl_t *ht, mrp_htbl_iter_cb_t cb, void *user_data); + +MRP_CDECL_END + +#endif /* __MURPHY_HASHTBL_H__ */ diff --git a/src/common/internal-transport.c b/src/common/internal-transport.c new file mode 100644 index 0000000..7ffae37 --- /dev/null +++ b/src/common/internal-transport.c @@ -0,0 +1,585 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> + +#include <murphy/common.h> + +#define INTERNAL "internal" + +typedef struct internal_s internal_t; + +struct internal_s { + MRP_TRANSPORT_PUBLIC_FIELDS; /* common transport fields */ + char name[MRP_SOCKADDR_SIZE]; /* bound connection name */ + mrp_sockaddr_t address; /* internal connection name*/ + bool active; + bool bound; + bool listening; + + internal_t *endpoint; /* that we are connected to */ +}; + +typedef struct { + void *data; + size_t size; + internal_t *u; + mrp_sockaddr_t *addr; + socklen_t addrlen; + bool free_data; + int offset; + bool custom; + uint16_t tag; + + mrp_list_hook_t hook; +} internal_message_t; + + +/* storage for the global data, TODO: refactor away */ +static mrp_htbl_t *servers = NULL; +static mrp_htbl_t *connections = NULL; +static mrp_list_hook_t msg_queue; +static mrp_deferred_t *d; +static uint32_t cid; + + +static void process_queue(mrp_deferred_t *d, void *user_data) +{ + internal_message_t *msg; + internal_t *endpoint; + mrp_list_hook_t *p, *n; + + MRP_UNUSED(user_data); + + mrp_disable_deferred(d); + + mrp_list_foreach(&msg_queue, p, n) { + + msg = mrp_list_entry(p, typeof(*msg), hook); + + if (!msg) { + mrp_log_error("no message!"); + goto end; + } + + if (!msg->u->connected) { + if (!msg->addr) { + mrp_log_error("connected transport without address!"); + goto end; + } + + /* Find the recipient. Look first from the server table.*/ + endpoint = mrp_htbl_lookup(servers, msg->addr->data); + + if (!endpoint) { + + /* Look next from the general connections table. */ + endpoint = mrp_htbl_lookup(connections, msg->addr->data); + } + } + else { + endpoint = msg->u->endpoint; + } + + if (!endpoint || !endpoint->recv_data) { + mrp_log_error("no endpoint matching the address"); + goto end; + } + + /* skip the length word when sending */ + endpoint->recv_data( + (mrp_transport_t *) endpoint, msg->data + msg->offset, + msg->size, &msg->u->address, MRP_SOCKADDR_SIZE); + +end: + if (msg) { + + if (msg->free_data) { + if (msg->custom) { + /* FIXME: should be mrp_data_free(msg->data, msg->tag); */ + mrp_free(msg->data); + } + else + mrp_msg_unref(msg->data); + } + + mrp_list_delete(&msg->hook); + mrp_free(msg); + } + } +} + + +static int internal_initialize_table(internal_t *u) +{ + mrp_htbl_config_t servers_conf; + mrp_htbl_config_t connections_conf; + + MRP_UNUSED(u); + + if (servers && connections && d) + return 0; /* already initialized */ + + servers_conf.comp = mrp_string_comp; + servers_conf.hash = mrp_string_hash; + servers_conf.free = NULL; + servers_conf.nbucket = 0; + servers_conf.nentry = 10; + + servers = mrp_htbl_create(&servers_conf); + + if (!servers) + goto error; + + connections_conf.comp = mrp_string_comp; + connections_conf.hash = mrp_string_hash; + connections_conf.free = NULL; + connections_conf.nbucket = 0; + connections_conf.nentry = 10; + + connections = mrp_htbl_create(&connections_conf); + + if (!connections) + goto error; + + mrp_list_init(&msg_queue); + + cid = 0; + + d = mrp_add_deferred(u->ml, process_queue, NULL); + + if (!d) + goto error; + + mrp_disable_deferred(d); + + return 0; + +error: + + if (servers) + mrp_htbl_destroy(servers, FALSE); + + servers = NULL; + + if (connections) + mrp_htbl_destroy(connections, FALSE); + + connections = NULL; + + return -1; +} + + +static socklen_t internal_resolve(const char *str, mrp_sockaddr_t *addr, + socklen_t size, const char **typep) +{ + int len; + + MRP_UNUSED(size); + + if (!str) + return 0; + + len = strlen(str); + + if (len <= 9 || len >= MRP_SOCKADDR_SIZE) + return 0; + + if (strncmp("internal:", str, 9)) + return 0; + + if (typep) + *typep = INTERNAL; + + memcpy(addr->data, str+9, len-9+1); + + return len-9; +} + + +static int internal_open(mrp_transport_t *mu) +{ + internal_t *u = (internal_t *)mu; + + if (internal_initialize_table(u) < 0) + return FALSE; + + memset(u->name, 0, MRP_SOCKADDR_SIZE); + memset(u->address.data, 0, MRP_SOCKADDR_SIZE); + + u->active = FALSE; + + snprintf(u->address.data, MRP_SOCKADDR_SIZE, INTERNAL"_%d", cid++); + + mrp_htbl_insert(connections, u->address.data, mu); + + return TRUE; +} + + +static int internal_bind(mrp_transport_t *mu, mrp_sockaddr_t *addr, + socklen_t addrlen) +{ + internal_t *u = (internal_t *)mu; + + if (internal_initialize_table(u) < 0) + return FALSE; + + memcpy(u->name, addr->data, addrlen+1); + + mrp_htbl_insert(servers, u->name, u); + + u->active = TRUE; + u->bound = TRUE; + + return TRUE; +} + + +static int internal_listen(mrp_transport_t *mu, int backlog) +{ + internal_t *u = (internal_t *)mu; + + MRP_UNUSED(backlog); + + if (!u->bound) + return FALSE; + + u->listening = TRUE; + + return TRUE; +} + + +static int internal_accept(mrp_transport_t *mt, mrp_transport_t *mlt) +{ + internal_t *t = (internal_t *) mt; + internal_t *lt = (internal_t *) mlt; + internal_t *client = lt->endpoint; + + t->endpoint = client; + client->endpoint = t; + + lt->endpoint = NULL; /* connection process is now over */ + + return TRUE; +} + + +static void remove_messages(internal_t *u) +{ + internal_message_t *msg; + mrp_list_hook_t *p, *n; + + mrp_list_foreach(&msg_queue, p, n) { + msg = mrp_list_entry(p, typeof(*msg), hook); + + if (strcmp(msg->addr->data, u->name) == 0 + || strcmp(msg->addr->data, u->address.data) == 0) { + + if (msg->free_data) { + if (msg->custom) { + /* FIXME: should be mrp_data_free(msg->data, msg->tag); */ + mrp_free(msg->data); + } + else + mrp_msg_unref(msg->data); + } + + mrp_list_delete(&msg->hook); + mrp_free(msg); + } + } +} + + +static void internal_close(mrp_transport_t *mu) +{ + internal_t *u = (internal_t *)mu; + + /* Is this client or server? If server, go remove the connection from + * servers table. */ + + if (u->bound) { + /* server listening socket */ + mrp_htbl_remove(servers, u->name, FALSE); + u->bound = FALSE; + } + + mrp_htbl_remove(connections, u->address.data, FALSE); + + u->active = FALSE; + + remove_messages(u); +} + + +static int internal_connect(mrp_transport_t *mu, mrp_sockaddr_t *addr, + socklen_t addrlen) +{ + internal_t *u = (internal_t *)mu; + internal_t *host; + mrp_transport_t *mt; + + MRP_UNUSED(addrlen); + + /* client connecting */ + + if (!servers) { + mrp_log_error("no servers available for connecting"); + return FALSE; + } + + host = mrp_htbl_lookup(servers, addr->data); + + if (!host) { + mrp_log_error("server '%s' wasn't found", addr->data); + return FALSE; + } + + mt = (mrp_transport_t *) host; + + host->endpoint = u; /* temporary connection data */ + + host->evt.connection(mt, mt->user_data); + + return TRUE; +} + + +static int internal_disconnect(mrp_transport_t *mu) +{ + internal_t *u = (internal_t *)mu; + + if (u->connected) { + internal_t *endpoint = u->endpoint; + + if (endpoint) { + endpoint->endpoint = NULL; + mrp_transport_disconnect((mrp_transport_t *) endpoint); + } + u->endpoint = NULL; + } + + return TRUE; +} + + +static int internal_sendto(mrp_transport_t *mu, mrp_msg_t *data, + mrp_sockaddr_t *addr, socklen_t addrlen) +{ + internal_t *u = (internal_t *)mu; + void *buf; + size_t size; + internal_message_t *msg; + + size = mrp_msg_default_encode(data, &buf); + + if (size == 0 || buf == NULL) { + return FALSE; + } + + msg = mrp_allocz(sizeof(internal_message_t)); + + if (!msg) + return FALSE; + + msg->addr = addr; + msg->addrlen = addrlen; + msg->data = buf; + msg->free_data = FALSE; + msg->offset = 0; + msg->size = size; + msg->u = u; + msg->custom = FALSE; + + mrp_list_init(&msg->hook); + mrp_list_append(&msg_queue, &msg->hook); + + mrp_enable_deferred(d); + + return TRUE; +} + + +static int internal_send(mrp_transport_t *mu, mrp_msg_t *msg) +{ + if (!mu->connected) { + return FALSE; + } + + return internal_sendto(mu, msg, NULL, 0); +} + + +static int internal_sendrawto(mrp_transport_t *mu, void *data, size_t size, + mrp_sockaddr_t *addr, socklen_t addrlen) +{ + internal_t *u = (internal_t *)mu; + internal_message_t *msg; + + msg = mrp_allocz(sizeof(internal_message_t)); + + if (!msg) + return FALSE; + + msg->addr = addr; + msg->addrlen = addrlen; + msg->data = data; + msg->free_data = FALSE; + msg->offset = 0; + msg->size = size; + msg->u = u; + msg->custom = FALSE; + + mrp_list_init(&msg->hook); + mrp_list_append(&msg_queue, &msg->hook); + + mrp_enable_deferred(d); + + return TRUE; +} + + +static int internal_sendraw(mrp_transport_t *mu, void *data, size_t size) +{ + if (!mu->connected) { + return FALSE; + } + + return internal_sendrawto(mu, data, size, NULL, 0); +} + + +static size_t encode_custom_data(void *data, void **newdata, uint16_t tag) +{ + mrp_data_descr_t *type = mrp_msg_find_type(tag); + uint32_t *lenp; + uint16_t *tagp; + size_t reserve, size; + int len; + void *buf; + + if (type == NULL) { + mrp_log_error("type not found!"); + return 0; + } + + reserve = sizeof(*lenp) + sizeof(*tagp); + size = mrp_data_encode(&buf, data, type, reserve); + + if (size == 0) { + mrp_log_error("data encoding failed"); + return 0; + } + + /* some format conversion */ + + lenp = buf; + len = size - sizeof(*lenp); + tagp = buf + sizeof(*lenp); + + *tagp = htobe16(tag); + + *newdata = buf; + + return len; +} + + +static int internal_senddatato(mrp_transport_t *mu, void *data, uint16_t tag, + mrp_sockaddr_t *addr, socklen_t addrlen) +{ + internal_t *u = (internal_t *)mu; + mrp_data_descr_t *type = mrp_msg_find_type(tag); + void *newdata = NULL; + size_t size; + internal_message_t *msg; + + if (type == NULL) + return FALSE; + + msg = mrp_allocz(sizeof(internal_message_t)); + + if (!msg) + return FALSE; + + size = encode_custom_data(data, &newdata, tag); + + if (!newdata) { + mrp_log_error("custom data encoding failed"); + mrp_free(msg); + return FALSE; + } + + msg->addr = addr; + msg->addrlen = addrlen; + msg->data = newdata; + msg->free_data = TRUE; + msg->offset = 4; + msg->size = size; + msg->u = u; + msg->custom = TRUE; + msg->tag = tag; + + mrp_list_init(&msg->hook); + mrp_list_append(&msg_queue, &msg->hook); + + mrp_enable_deferred(d); + + return TRUE; +} + + +static int internal_senddata(mrp_transport_t *mu, void *data, uint16_t tag) +{ + if (!mu->connected) { + return FALSE; + } + + return internal_senddatato(mu, data, tag, NULL, 0); +} + + + + +MRP_REGISTER_TRANSPORT(internal, INTERNAL, internal_t, internal_resolve, + internal_open, NULL, internal_close, NULL, + internal_bind, internal_listen, internal_accept, + internal_connect, internal_disconnect, + internal_send, internal_sendto, + internal_sendraw, internal_sendrawto, + internal_senddata, internal_senddatato, + NULL, NULL, + NULL, NULL, + NULL, NULL); diff --git a/src/common/json.c b/src/common/json.c new file mode 100644 index 0000000..c275330 --- /dev/null +++ b/src/common/json.c @@ -0,0 +1,599 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <errno.h> +#include <stdarg.h> +#include <string.h> + +#include "murphy/config.h" +#include <murphy/common/macros.h> +#include <murphy/common/log.h> +#include <murphy/common/debug.h> +#include <murphy/common/json.h> + +/** Type for a JSON parser. */ +typedef struct json_tokener mrp_json_parser_t; + +static mrp_json_parser_t *parser; + +mrp_json_t *mrp_json_create(mrp_json_type_t type, ...) +{ + mrp_json_t *o; + const char *s; + bool b; + int i, l; + double d; + va_list ap; + + va_start(ap, type); + switch (type) { + case MRP_JSON_STRING: + s = va_arg(ap, const char *); + l = va_arg(ap, int); + if (l < 0) + o = json_object_new_string(s); + else + o = json_object_new_string_len(s, l); + break; + case MRP_JSON_BOOLEAN: + b = va_arg(ap, int); + o = json_object_new_boolean(b); + break; + case MRP_JSON_INTEGER: + i = va_arg(ap, int); + o = json_object_new_int(i); + break; + case MRP_JSON_DOUBLE: + d = va_arg(ap, double); + o = json_object_new_double(d); + break; + case MRP_JSON_OBJECT: + o = json_object_new_object(); + break; + case MRP_JSON_ARRAY: + o = json_object_new_array(); + break; + default: + o = NULL; + } + va_end(ap); + + return o; +} + + +mrp_json_t *mrp_json_clone(mrp_json_t *o) +{ + if (o != NULL) + return mrp_json_string_to_object(mrp_json_object_to_string(o), -1); + else + return NULL; +} + + +mrp_json_t *mrp_json_string_to_object(const char *s, int len) +{ + if (parser == NULL) { + parser = json_tokener_new(); + + if (parser == NULL) + return NULL; + } + else + json_tokener_reset(parser); + + if (len < 0) + len = strlen(s); + + return json_tokener_parse_ex(parser, s, len); +} + + +const char *mrp_json_object_to_string(mrp_json_t *o) +{ + if (o != NULL) + return json_object_to_json_string(o); + else + return "{}"; +} + + +mrp_json_t *mrp_json_ref(mrp_json_t *o) +{ + return json_object_get(o); +} + + +void mrp_json_unref(mrp_json_t *o) +{ + json_object_put(o); +} + + +mrp_json_type_t mrp_json_get_type(mrp_json_t *o) +{ + return json_object_get_type(o); +} + + +int mrp_json_is_type(mrp_json_t *o, mrp_json_type_t type) +{ + return json_object_is_type(o, type); +} + + +void mrp_json_add(mrp_json_t *o, const char *key, mrp_json_t *m) +{ + json_object_object_add(o, key, m); +} + + +mrp_json_t *mrp_json_add_member(mrp_json_t *o, const char *key, + mrp_json_type_t type, ...) +{ + mrp_json_t *m; + const char *s; + bool b; + int i, l; + double d; + va_list ap; + + va_start(ap, type); + switch (type) { + case MRP_JSON_STRING: + s = va_arg(ap, const char *); + l = va_arg(ap, int); + if (l < 0) + m = json_object_new_string(s); + else + m = json_object_new_string_len(s, l); + break; + case MRP_JSON_BOOLEAN: + b = va_arg(ap, int); + m = json_object_new_boolean(b); + break; + case MRP_JSON_INTEGER: + i = va_arg(ap, int); + m = json_object_new_int(i); + break; + case MRP_JSON_DOUBLE: + d = va_arg(ap, double); + m = json_object_new_double(d); + break; + case MRP_JSON_OBJECT: + m = json_object_new_object(); + break; + case MRP_JSON_ARRAY: + m = json_object_new_array(); + break; + default: + m = NULL; + errno = EINVAL; + } + va_end(ap); + + if (m != NULL) + json_object_object_add(o, key, m); + + return m; +} + + +mrp_json_t *mrp_json_add_array(mrp_json_t *o, const char *key, + mrp_json_type_t type, ...) +{ + va_list ap; + void *arr; + size_t cnt, i; + mrp_json_t *a; + + va_start(ap, type); + arr = va_arg(ap, void *); + cnt = va_arg(ap, size_t); + a = mrp_json_create(MRP_JSON_ARRAY); + + if (a == NULL) + goto fail; + + switch (type) { + case MRP_JSON_STRING: + for (i = 0; i < cnt; i++) { + if (!mrp_json_array_append_string(a, ((char **)arr)[i])) + goto fail; + } + break; + + case MRP_JSON_INTEGER: + for (i = 0; i < cnt; i++) { + if (!mrp_json_array_append_integer(a, ((int *)arr)[i])) + goto fail; + } + break; + + case MRP_JSON_DOUBLE: + for (i = 0; i < cnt; i++) { + if (!mrp_json_array_append_double(a, ((double *)arr)[i])) + goto fail; + } + break; + + case MRP_JSON_BOOLEAN: + for (i = 0; i < cnt; i++) { + if (!mrp_json_array_append_boolean(a, ((bool *)arr)[i])) + goto fail; + } + break; + + default: + goto fail; + + } + + va_end(ap); + + mrp_json_add(o, key, a); + return a; + + fail: + va_end(ap); + mrp_json_unref(a); + + return NULL; +} + + +mrp_json_t *mrp_json_get(mrp_json_t *o, const char *key) +{ + mrp_json_iter_t it; + const char *k; + mrp_json_t *v; + + mrp_json_foreach_member(o, k, v, it) { + if (!strcmp(k, key)) + return v; + } + + return NULL; +} + + +int mrp_json_get_member(mrp_json_t *o, const char *key, + mrp_json_type_t type, ...) +{ + const char **s; + bool *b; + int *i; + double *d; + mrp_json_t *m, **mp; + int success; + va_list ap; + + success = FALSE; + va_start(ap, type); + + m = mrp_json_get(o, key); + + if (m != NULL) { + if (json_object_is_type(m, type)) { + success = TRUE; + switch (type) { + case MRP_JSON_STRING: + s = va_arg(ap, const char **); + *s = json_object_get_string(m); + break; + case MRP_JSON_BOOLEAN: + b = va_arg(ap, bool *); + *b = json_object_get_boolean(m); + break; + case MRP_JSON_INTEGER: + i = va_arg(ap, int *); + *i = json_object_get_int(m); + break; + case MRP_JSON_DOUBLE: + d = va_arg(ap, double *); + *d = json_object_get_double(m); + break; + case MRP_JSON_OBJECT: + mp = va_arg(ap, mrp_json_t **); + *mp = m; + break; + case MRP_JSON_ARRAY: + mp = va_arg(ap, mrp_json_t **); + *mp = m; + break; + default: + success = FALSE; + } + } + else + errno = EINVAL; + } + else { + errno = ENOENT; + success = FALSE; + } + + va_end(ap); + + return success; +} + + +void mrp_json_del_member(mrp_json_t *o, const char *key) +{ + json_object_object_del(o, key); +} + + +int mrp_json_array_length(mrp_json_t *a) +{ + return json_object_array_length(a); +} + + +int mrp_json_array_append(mrp_json_t *a, mrp_json_t *v) +{ + return json_object_array_add(a, v) == 0; +} + + +mrp_json_t *mrp_json_array_append_item(mrp_json_t *a, mrp_json_type_t type, ...) +{ + mrp_json_t *v; + const char *s; + bool b; + int i, l; + double d; + va_list ap; + + va_start(ap, type); + switch (type) { + case MRP_JSON_STRING: + s = va_arg(ap, const char *); + l = va_arg(ap, int); + if (l < 0) + v = json_object_new_string(s); + else + v = json_object_new_string_len(s, l); + break; + case MRP_JSON_BOOLEAN: + b = va_arg(ap, int); + v = json_object_new_boolean(b); + break; + case MRP_JSON_INTEGER: + i = va_arg(ap, int); + v = json_object_new_int(i); + break; + case MRP_JSON_DOUBLE: + d = va_arg(ap, double); + v = json_object_new_double(d); + break; + case MRP_JSON_OBJECT: + v = va_arg(ap, mrp_json_t *); + break; + case MRP_JSON_ARRAY: + v = va_arg(ap, mrp_json_t *); + break; + default: + v = NULL; + errno = EINVAL; + } + va_end(ap); + + if (v != NULL) { + if (json_object_array_add(a, v) == 0) + return v; + else { + mrp_json_unref(v); + errno = ENOMEM; + } + } + + return NULL; +} + + +int mrp_json_array_set(mrp_json_t *a, int idx, mrp_json_t *v) +{ + return json_object_array_put_idx(a, idx, v); +} + + +int mrp_json_array_set_item(mrp_json_t *a, int idx, mrp_json_type_t type, ...) +{ + mrp_json_t *v; + const char *s; + bool b; + int i, l; + double d; + va_list ap; + + va_start(ap, type); + switch (type) { + case MRP_JSON_STRING: + s = va_arg(ap, const char *); + l = va_arg(ap, int); + if (l < 0) + v = json_object_new_string(s); + else + v = json_object_new_string_len(s, l); + break; + case MRP_JSON_BOOLEAN: + b = va_arg(ap, int); + v = json_object_new_boolean(b); + break; + case MRP_JSON_INTEGER: + i = va_arg(ap, int); + v = json_object_new_int(i); + break; + case MRP_JSON_DOUBLE: + d = va_arg(ap, double); + v = json_object_new_double(d); + break; + case MRP_JSON_OBJECT: + v = va_arg(ap, mrp_json_t *); + break; + case MRP_JSON_ARRAY: + v = va_arg(ap, mrp_json_t *); + break; + default: + v = NULL; + errno = EINVAL; + } + va_end(ap); + + if (v != NULL) + return json_object_array_put_idx(a, idx, v); + else { + errno = ENOMEM; + return FALSE; + } +} + + +mrp_json_t *mrp_json_array_get(mrp_json_t *a, int idx) +{ + return json_object_array_get_idx(a, idx); +} + + +int mrp_json_array_get_item(mrp_json_t *a, int idx, mrp_json_type_t type, ...) +{ + const char **s; + bool *b; + int *i; + double *d; + mrp_json_t *v, **vp; + int success; + va_list ap; + + success = FALSE; + va_start(ap, type); + + v = json_object_array_get_idx(a, idx); + + if (v != NULL) { + if (json_object_is_type(v, type)) { + success = TRUE; + switch (type) { + case MRP_JSON_STRING: + s = va_arg(ap, const char **); + *s = json_object_get_string(v); + break; + case MRP_JSON_BOOLEAN: + b = va_arg(ap, bool *); + *b = json_object_get_boolean(v); + break; + case MRP_JSON_INTEGER: + i = va_arg(ap, int *); + *i = json_object_get_int(v); + break; + case MRP_JSON_DOUBLE: + d = va_arg(ap, double *); + *d = json_object_get_double(v); + break; + case MRP_JSON_OBJECT: + vp = va_arg(ap, mrp_json_t **); + *vp = v; + break; + case MRP_JSON_ARRAY: + vp = va_arg(ap, mrp_json_t **); + *vp = v; + break; + default: + success = FALSE; + errno = EINVAL; + } + } + else + errno = EINVAL; + } + else + errno = ENOENT; + + va_end(ap); + + return success; +} + + +int mrp_json_parse_object(char **strp, int *lenp, mrp_json_t **op) +{ + char *str; + int len; + mrp_json_t *o = NULL; + json_tokener *tok = NULL; + int res = -1; + + if (strp == NULL || *strp == NULL) { + *op = NULL; + if (lenp != NULL) + *lenp = 0; + + return 0; + } + + str = *strp; + len = lenp ? *lenp : 0; + + if (len <= 0) + len = strlen(str); + + tok = json_tokener_new(); + + if (tok != NULL) { + o = json_tokener_parse_ex(tok, str, len); + + if (o != NULL) { + *strp += tok->char_offset; + if (lenp != NULL) + *lenp -= tok->char_offset; + + res = 0; + } + else { +#ifdef HAVE_JSON_TOKENER_GET_ERROR + if (json_tokener_get_error(tok) != json_tokener_success) + errno = EINVAL; +#else + if (tok->err != json_tokener_success) + errno = EINVAL; +#endif + else + res = 0; + } + + json_tokener_free(tok); + } + else + errno = ENOMEM; + + *op = o; + return res; +} diff --git a/src/common/json.h b/src/common/json.h new file mode 100644 index 0000000..746a6ef --- /dev/null +++ b/src/common/json.h @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_JSON_H__ +#define __MURPHY_JSON_H__ + +#include <stdarg.h> +#include <stdbool.h> + +#include "murphy/config.h" + +#ifndef JSON_INCLUDE_PATH_JSONC +# include <json/json.h> +# include <json/linkhash.h> +/* workaround for older broken json-c not exposing json_object_iter */ +# include <json/json_object_private.h> +#else +# include <json-c/json.h> +# include <json-c/linkhash.h> +/* workaround for older broken json-c not exposing json_object_iter */ +# include <json-c/json_object_private.h> +#endif + +#include <murphy/common/macros.h> + +MRP_CDECL_BEGIN + +/* + * We use json-c as the underlying json implementation, However, we do + * not want direct json-c dependencies to spread all over the code base + * (at least not yet). So we try to define here an envelop layer that + * hides json-c underneath. + */ + +/** Type of a JSON object. */ +typedef json_object mrp_json_t; + +/** JSON object/member types. */ +typedef enum { + MRP_JSON_STRING = json_type_string, + MRP_JSON_BOOLEAN = json_type_boolean, + MRP_JSON_INTEGER = json_type_int, + MRP_JSON_DOUBLE = json_type_double, + MRP_JSON_OBJECT = json_type_object, + MRP_JSON_ARRAY = json_type_array +} mrp_json_type_t; + +/** Type for a JSON member iterator. */ +typedef json_object_iter mrp_json_iter_t; + +/** Create a new JSON object of the given type. */ +mrp_json_t *mrp_json_create(mrp_json_type_t type, ...); + +/** Clone the given JSON object, creating a new private copy of it. */ +mrp_json_t *mrp_json_clone(mrp_json_t *o); + +/** Deserialize a string to a JSON object. */ +mrp_json_t *mrp_json_string_to_object(const char *str, int len); + +/** Serialize a JSON object to a string. */ +const char *mrp_json_object_to_string(mrp_json_t *o); + +/** Add a reference to the given JSON object. */ +mrp_json_t *mrp_json_ref(mrp_json_t *o); + +/** Remove a reference from the given JSON object, freeing if it was last. */ +void mrp_json_unref(mrp_json_t *o); + +/** Get the type of a JSON object. */ +mrp_json_type_t mrp_json_get_type(mrp_json_t *o); + +/** Check if a JSON object has the given type. */ +int mrp_json_is_type(mrp_json_t *o, mrp_json_type_t type); + +/** Convenience macros to get values of JSON objects of basic types. */ +#define mrp_json_string_value(o) json_object_get_string(o) +#define mrp_json_integer_value(o) json_object_get_int(o) +#define mrp_json_double_value(o) json_object_get_double(o) +#define mrp_json_boolean_value(o) json_object_get_boolean(o) + +/** Set a member of a JSON object. */ +void mrp_json_add(mrp_json_t *o, const char *key, mrp_json_t *m); + +/** Create a new JSON object and set it as a member of another object. */ +mrp_json_t *mrp_json_add_member(mrp_json_t *o, const char *key, + mrp_json_type_t type, ...); + +/** Convenience macros to add members of various basic types. */ +#define mrp_json_add_string(o, key, s) \ + mrp_json_add_member(o, key, MRP_JSON_STRING, s, -1) + +#define mrp_json_add_string_slice(o, key, s, l) \ + mrp_json_add_member(o, key, MRP_JSON_STRING, s, l) + +#define mrp_json_add_integer(o, key, i) \ + mrp_json_add_member(o, key, MRP_JSON_INTEGER, i) + +#define mrp_json_add_double(o, key, d) \ + mrp_json_add_member(o, key, MRP_JSON_DOUBLE, d) + +#define mrp_json_add_boolean(o, key, b) \ + mrp_json_add_member(o, key, MRP_JSON_BOOLEAN, (int)b) + +/** Add an array member from a native C array of the given type. */ +mrp_json_t *mrp_json_add_array(mrp_json_t *o, const char *key, + mrp_json_type_t type, ...); + +/** Convenience macros for adding arrays of various basic types. */ +#define mrp_json_add_string_array(o, key, arr, size) \ + mrp_json_add_array(o, key, MRP_JSON_STRING, arr, size) + +#define mrp_json_add_int_array(o, key, arr, size) \ + mrp_json_add_array(o, key, MRP_JSON_INTEGER, arr, size) + +#define mrp_json_add_double_array(o, key, arr, size) \ + mrp_json_add_array(o, key, MRP_JSON_DOUBLE, arr, size) + +#define mrp_json_add_boolean_array(o, key, arr, size) \ + mrp_json_add_array(o, key, MRP_JSON_BOOLEAN, arr, size) + +/** Get the member of a JSON object as a json object. */ +mrp_json_t *mrp_json_get(mrp_json_t *o, const char *key); + +/** Get the member of a JSON object in a type specific format. */ +int mrp_json_get_member(mrp_json_t *o, const char *key, + mrp_json_type_t type, ...); + +/** Convenience macros to get members of various types. */ +#define mrp_json_get_string(o, key, sptr) \ + mrp_json_get_member(o, key, MRP_JSON_STRING, sptr) + +#define mrp_json_get_integer(o, key, iptr) \ + mrp_json_get_member(o, key, MRP_JSON_INTEGER, iptr) + +#define mrp_json_get_double(o, key, dptr) \ + mrp_json_get_member(o, key, MRP_JSON_DOUBLE, dptr) + +#define mrp_json_get_boolean(o, key, bptr) \ + mrp_json_get_member(o, key, MRP_JSON_BOOLEAN, bptr) + +#define mrp_json_get_array(o, key, aptr) \ + mrp_json_get_member(o, key, MRP_JSON_ARRAY, aptr) + +#define mrp_json_get_object(o, key, optr) \ + mrp_json_get_member(o, key, MRP_JSON_OBJECT, optr) + +/** Delete a member of a JSON object. */ +void mrp_json_del_member(mrp_json_t *o, const char *key); + +/** Get the length of a JSON array object. */ +int mrp_json_array_length(mrp_json_t *a); + +/** Append a JSON object to an array object. */ +int mrp_json_array_append(mrp_json_t *a, mrp_json_t *v); + +/** Create and append a new item to a JSON array object. */ +mrp_json_t *mrp_json_array_append_item(mrp_json_t *a, mrp_json_type_t type, + ...); + +/** Convenience macros for appending array items of basic types. */ +#define mrp_json_array_append_string(a, s) \ + mrp_json_array_append_item(a, MRP_JSON_STRING, s, -1) + +#define mrp_json_array_append_string_slice(a, s, l) \ + mrp_json_array_append_item(a, MRP_JSON_STRING, s, l) + + +#define mrp_json_array_append_integer(a, i) \ + mrp_json_array_append_item(a, MRP_JSON_INTEGER, (int)i) + +#define mrp_json_array_append_double(a, d) \ + mrp_json_array_append_item(a, MRP_JSON_DOUBLE, 1.0*d) + +#define mrp_json_array_append_boolean(a, b) \ + mrp_json_array_append_item(a, MRP_JSON_BOOLEAN, (int)b) + +/** Add a JSON object to a given index of an array object. */ +int mrp_json_array_set(mrp_json_t *a, int idx, mrp_json_t *v); + +/** Add a JSON object to a given index of an array object. */ +int mrp_json_array_set_item(mrp_json_t *a, int idx, mrp_json_type_t type, ...); + +/** Get the object at a given index of a JSON array object. */ +mrp_json_t *mrp_json_array_get(mrp_json_t *a, int idx); + +/** Get the element of a JSON array object at an index. */ +int mrp_json_array_get_item(mrp_json_t *a, int idx, mrp_json_type_t type, ...); + +/** Convenience macros to get items of certain types from an array. */ +#define mrp_json_array_get_string(a, idx, sptr) \ + mrp_json_array_get_item(a, idx, MRP_JSON_STRING, sptr) + +#define mrp_json_array_get_integer(a, idx, iptr) \ + mrp_json_array_get_item(a, idx, MRP_JSON_INTEGER, iptr) + +#define mrp_json_array_get_double(a, idx, dptr) \ + mrp_json_array_get_item(a, idx, MRP_JSON_DOUBLE, dptr) + +#define mrp_json_array_get_boolean(a, idx, bptr) \ + mrp_json_array_get_item(a, idx, MRP_JSON_BOOLEAN, bptr) + +#define mrp_json_array_get_array(a, idx, aptr) \ + mrp_json_array_get_item(a, idx, MRP_JSON_ARRAY, aptr) + +#define mrp_json_array_get_object(a, idx, optr) \ + mrp_json_array_get_item(a, idx, MRP_JSON_OBJECT, optr) + +/** Iterate through the members of an object. */ +#define mrp_json_foreach_member(o, _k, _v, it) \ + for (it.entry = json_object_get_object((o))->head; \ + (it.entry ? \ + (_k = it.key = it.entry->k, \ + _v = it.val = (mrp_json_t *)it.entry->v, \ + it.entry) : 0); \ + it.entry = it.entry->next) + +/** Parse a JSON object from the given string. */ +int mrp_json_parse_object(char **str, int *len, mrp_json_t **op); + +MRP_CDECL_END + +#endif /* __MURPHY_JSON_H__ */ diff --git a/src/common/libdbus-glue.c b/src/common/libdbus-glue.c new file mode 100644 index 0000000..d1bb093 --- /dev/null +++ b/src/common/libdbus-glue.c @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <dbus/dbus.h> + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/log.h> +#include <murphy/common/list.h> +#include <murphy/common/mainloop.h> + +typedef struct dbus_glue_s dbus_glue_t; + +typedef struct { + dbus_glue_t *glue; + mrp_io_watch_t *mw; + DBusWatch *dw; + mrp_list_hook_t hook; +} watch_t; + + +typedef struct { + dbus_glue_t *glue; + mrp_timer_t *mt; + DBusTimeout *dt; + mrp_list_hook_t hook; +} timeout_t; + + +struct dbus_glue_s { + DBusConnection *conn; + mrp_mainloop_t *ml; + mrp_list_hook_t watches; + mrp_list_hook_t timers; + mrp_deferred_t *pump; +}; + + +static dbus_int32_t data_slot = -1; + +static void dispatch_watch(mrp_io_watch_t *mw, int fd, mrp_io_event_t events, + void *user_data) +{ + watch_t *watch = (watch_t *)user_data; + DBusConnection *conn = watch->glue->conn; + unsigned int mask = 0; + + MRP_UNUSED(mw); + MRP_UNUSED(fd); + + if (events & MRP_IO_EVENT_IN) + mask |= DBUS_WATCH_READABLE; + if (events & MRP_IO_EVENT_OUT) + mask |= DBUS_WATCH_WRITABLE; + if (events & MRP_IO_EVENT_HUP) + mask |= DBUS_WATCH_HANGUP; + if (events & MRP_IO_EVENT_ERR) + mask |= DBUS_WATCH_ERROR; + + dbus_connection_ref(conn); + dbus_watch_handle(watch->dw, mask); + dbus_connection_unref(conn); +} + + +static void watch_freed_cb(void *data) +{ + watch_t *watch = (watch_t *)data; + + if (watch != NULL) { + mrp_list_delete(&watch->hook); + mrp_del_io_watch(watch->mw); + mrp_free(watch); + } +} + + +static dbus_bool_t add_watch(DBusWatch *dw, void *data) +{ + dbus_glue_t *glue = (dbus_glue_t *)data; + watch_t *watch; + mrp_io_watch_t *mw; + mrp_io_event_t mask; + int fd; + unsigned int flags; + + mrp_debug("adding D-BUS watch %p (%s)", dw, + dbus_watch_get_enabled(dw) ? "enabled" : "disabled"); + + if (!dbus_watch_get_enabled(dw)) + return TRUE; + + fd = dbus_watch_get_unix_fd(dw); + flags = dbus_watch_get_flags(dw); + mask = MRP_IO_EVENT_HUP | MRP_IO_EVENT_ERR; + + if (flags & DBUS_WATCH_READABLE) + mask |= MRP_IO_EVENT_IN; + if (flags & DBUS_WATCH_WRITABLE) + mask |= MRP_IO_EVENT_OUT; + + mrp_debug("event mask for fd %d: %s%s", fd, + mask & MRP_IO_EVENT_IN ? "read" : "", + mask & MRP_IO_EVENT_OUT ? "write" : ""); + + if ((watch = mrp_allocz(sizeof(*watch))) != NULL) { + mrp_list_init(&watch->hook); + mw = mrp_add_io_watch(glue->ml, fd, mask, dispatch_watch, watch); + + if (mw != NULL) { + watch->glue = glue; + watch->mw = mw; + watch->dw = dw; + dbus_watch_set_data(dw, watch, watch_freed_cb); + mrp_list_append(&glue->watches, &watch->hook); + + return TRUE; + } + else + mrp_free(watch); + } + + return FALSE; +} + + +static void del_watch(DBusWatch *dw, void *data) +{ + watch_t *watch = (watch_t *)dbus_watch_get_data(dw); + + MRP_UNUSED(data); + + mrp_debug("deleting D-BUS watch %p...", dw); + + if (watch != NULL) { + mrp_del_io_watch(watch->mw); + watch->mw = NULL; + } +} + + +static void toggle_watch(DBusWatch *dw, void *data) +{ + mrp_debug("Toggling D-BUS watch %p...", dw); + + if (dbus_watch_get_enabled(dw)) + add_watch(dw, data); + else + del_watch(dw, data); +} + + +static void dispatch_timeout(mrp_timer_t *mt, void *user_data) +{ + timeout_t *timer = (timeout_t *)user_data; + + MRP_UNUSED(mt); + + mrp_debug("dispatching D-BUS timeout %p...", timer->dt); + + dbus_timeout_handle(timer->dt); +} + + +static void timeout_freed_cb(void *data) +{ + timeout_t *timer = (timeout_t *)data; + + if (timer != NULL) { + mrp_list_delete(&timer->hook); + mrp_del_timer(timer->mt); + + mrp_free(timer); + } +} + + +static dbus_bool_t add_timeout(DBusTimeout *dt, void *data) +{ + dbus_glue_t *glue = (dbus_glue_t *)data; + timeout_t *timer; + mrp_timer_t *mt; + unsigned int msecs; + + mrp_debug("adding D-BUS timeout %p...", dt); + + if ((timer = mrp_allocz(sizeof(*timer))) != NULL) { + mrp_list_init(&timer->hook); + msecs = dbus_timeout_get_interval(dt); + mt = mrp_add_timer(glue->ml, msecs, dispatch_timeout, timer); + + if (mt != NULL) { + timer->glue = glue; + timer->mt = mt; + timer->dt = dt; + dbus_timeout_set_data(dt, timer, timeout_freed_cb); + mrp_list_append(&glue->timers, &timer->hook); + + return TRUE; + } + else + mrp_free(timer); + } + + return FALSE; +} + + +static void del_timeout(DBusTimeout *dt, void *data) +{ + timeout_t *timer = (timeout_t *)dbus_timeout_get_data(dt); + + MRP_UNUSED(data); + + mrp_debug("deleting D-BUS timeout %p...", dt); + + if (timer != NULL) { + mrp_del_timer(timer->mt); + timer->mt = NULL; + } +} + + +static void toggle_timeout(DBusTimeout *dt, void *data) +{ + mrp_debug("toggling D-BUS timeout %p...", dt); + + if (dbus_timeout_get_enabled(dt)) + add_timeout(dt, data); + else + del_timeout(dt, data); +} + + +static void wakeup_mainloop(void *data) +{ + dbus_glue_t *glue = (dbus_glue_t *)data; + + mrp_debug("waking up mainloop..."); + + mrp_enable_deferred(glue->pump); +} + + +static void glue_free_cb(void *data) +{ + dbus_glue_t *glue = (dbus_glue_t *)data; + mrp_list_hook_t *p, *n; + watch_t *watch; + timeout_t *timer; + + mrp_list_foreach(&glue->watches, p, n) { + watch = mrp_list_entry(p, typeof(*watch), hook); + + mrp_list_delete(&watch->hook); + mrp_del_io_watch(watch->mw); + + mrp_free(watch); + } + + mrp_list_foreach(&glue->timers, p, n) { + timer = mrp_list_entry(p, typeof(*timer), hook); + + mrp_list_delete(&timer->hook); + mrp_del_timer(timer->mt); + + mrp_free(timer); + } + + mrp_free(glue); +} + + +static void pump_cb(mrp_deferred_t *d, void *user_data) +{ + dbus_glue_t *glue = (dbus_glue_t *)user_data; + + mrp_debug("dispatching dbus connection %p...", glue->conn); + + if (dbus_connection_dispatch(glue->conn) == DBUS_DISPATCH_COMPLETE) + mrp_disable_deferred(d); +} + + +static void dispatch_status_cb(DBusConnection *conn, DBusDispatchStatus status, + void *user_data) +{ + dbus_glue_t *glue = (dbus_glue_t *)user_data; + + MRP_UNUSED(conn); + + switch (status) { + case DBUS_DISPATCH_COMPLETE: + mrp_debug("dispatching status for %p: complete", conn); + mrp_disable_deferred(glue->pump); + break; + + case DBUS_DISPATCH_DATA_REMAINS: + case DBUS_DISPATCH_NEED_MEMORY: + default: + mrp_debug("dispatching status for %p: not complete yet", conn); + mrp_enable_deferred(glue->pump); + break; + } +} + + +int mrp_dbus_setup_connection(mrp_mainloop_t *ml, DBusConnection *conn) +{ + dbus_glue_t *glue; + + if (!dbus_connection_allocate_data_slot(&data_slot)) + return FALSE; + + if (dbus_connection_get_data(conn, data_slot) != NULL) + return FALSE; + + if ((glue = mrp_allocz(sizeof(*glue))) != NULL) { + mrp_list_init(&glue->watches); + mrp_list_init(&glue->timers); + glue->pump = mrp_add_deferred(ml, pump_cb, glue); + + if (glue->pump == NULL) { + mrp_free(glue); + return FALSE; + } + + glue->ml = ml; + glue->conn = conn; + } + else + return FALSE; + + if (!dbus_connection_set_data(conn, data_slot, glue, glue_free_cb)) + return FALSE; + + dbus_connection_set_dispatch_status_function(conn, dispatch_status_cb, + glue, NULL); + + dbus_connection_set_wakeup_main_function(conn, wakeup_mainloop, + glue, NULL); + + return + dbus_connection_set_watch_functions(conn, add_watch, del_watch, + toggle_watch, glue, NULL) && + dbus_connection_set_timeout_functions(conn, add_timeout, del_timeout, + toggle_timeout, glue, NULL); +} diff --git a/src/common/libdbus.c b/src/common/libdbus.c new file mode 100644 index 0000000..a4c7a24 --- /dev/null +++ b/src/common/libdbus.c @@ -0,0 +1,1471 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/log.h> +#include <murphy/common/list.h> +#include <murphy/common/hashtbl.h> +#include <murphy/common/refcnt.h> +#include <murphy/common/utils.h> +#include <murphy/common/mainloop.h> +#include <murphy/common/libdbus.h> + + +#define DBUS_ADMIN_SERVICE "org.freedesktop.DBus" +#define DBUS_ADMIN_INTERFACE "org.freedesktop.DBus" +#define DBUS_ADMIN_PATH "/org/freedesktop/DBus" +#define DBUS_NAME_CHANGED "NameOwnerChanged" + + +struct mrp_dbus_s { + char *address; /* bus address */ + DBusConnection *conn; /* actual D-BUS connection */ + mrp_mainloop_t *ml; /* murphy mainloop */ + mrp_htbl_t *methods; /* method handler table */ + mrp_htbl_t *signals; /* signal handler table */ + mrp_list_hook_t name_trackers; /* peer (name) watchers */ + mrp_list_hook_t calls; /* pending calls */ + uint32_t call_id; /* next call id */ + const char *unique_name; /* our unique D-BUS address */ + int priv; /* whether a private connection */ + int signal_filter; /* if signal dispatching is set up */ + int register_fallback; /* if the fallback object is set up */ + mrp_refcnt_t refcnt; /* reference count */ +}; + + +/* + * Notes: + * + * At the moment we administer DBUS method and signal handlers + * in a very primitive way (subject to be changed later). For + * every bus instance we maintain two hash tables, one for methods + * and another for signals. Each method and signal handler is + * hashed in only by it's method/signal name to a linked list of + * method or signal handlers. + * + * When dispatching a method, we look up the chain with a matching + * method name, or the chain for "" in case a matching chain is + * not found, and invoke the handler which best matches the + * received message (by looking at the path, interface and name). + * Only one such handler is invoked at most. + * + * For signals we look up both the chain with a matching name and + * the chain for "" and invoke all signal handlers that match the + * received message (regardless of their return value). + */ + + +typedef struct { + char *member; /* signal/method name */ + mrp_list_hook_t handlers; /* handlers with matching member */ +} handler_list_t; + +typedef struct { + mrp_list_hook_t hook; + char *sender; + char *path; + char *interface; + char *member; + mrp_dbus_handler_t handler; + void *user_data; +} handler_t; + +#define method_t handler_t +#define signal_t handler_t + + +typedef struct { + mrp_list_hook_t hook; /* hook to name tracker list */ + char *name; /* name to track */ + mrp_dbus_name_cb_t cb; /* status change callback */ + void *user_data; /* opaque callback user data */ + int32_t qid; /* initial query ID */ +} name_tracker_t; + + +typedef struct { + mrp_dbus_t *dbus; /* DBUS connection */ + int32_t id; /* call id */ + mrp_dbus_reply_cb_t cb; /* completion notification callback */ + void *user_data; /* opaque callback data */ + DBusPendingCall *pend; /* pending DBUS call */ + mrp_list_hook_t hook; /* hook to list of pending calls */ +} call_t; + + +typedef struct { + mrp_mainloop_t *ml; /* mainloop for bus connection */ + const char *address; /* address of bus */ +} bus_spec_t; + +static mrp_htbl_t *buses; + + + +static DBusHandlerResult dispatch_signal(DBusConnection *c, + DBusMessage *msg, void *data); +static DBusHandlerResult dispatch_method(DBusConnection *c, + DBusMessage *msg, void *data); +static void purge_name_trackers(mrp_dbus_t *dbus); +static void purge_calls(mrp_dbus_t *dbus); +static void handler_list_free_cb(void *key, void *entry); +static void handler_free(handler_t *h); +static int name_owner_change_cb(mrp_dbus_t *dbus, DBusMessage *msg, void *data); +static void call_free(call_t *call); + + + + +static int purge_filters(void *key, void *entry, void *user_data) +{ + mrp_dbus_t *dbus = (mrp_dbus_t *)user_data; + handler_list_t *l = (handler_list_t *)entry; + mrp_list_hook_t *p, *n; + handler_t *h; + + MRP_UNUSED(key); + + mrp_list_foreach(&l->handlers, p, n) { + h = mrp_list_entry(p, handler_t, hook); + mrp_dbus_remove_filter(dbus, + h->sender, h->path, h->interface, + h->member, NULL); + } + + return MRP_HTBL_ITER_MORE; +} + + +void dbus_disconnect(mrp_dbus_t *dbus) +{ + if (dbus) { + mrp_htbl_remove(buses, dbus->conn, FALSE); + + if (dbus->signals) { + mrp_htbl_foreach(dbus->signals, purge_filters, dbus); + mrp_htbl_destroy(dbus->signals, TRUE); + } + if (dbus->methods) + mrp_htbl_destroy(dbus->methods, TRUE); + + if (dbus->conn != NULL) { + if (dbus->signal_filter) + dbus_connection_remove_filter(dbus->conn, dispatch_signal, + dbus); + if (dbus->register_fallback) + dbus_connection_unregister_object_path(dbus->conn, "/"); + if (dbus->priv) + dbus_connection_close(dbus->conn); + dbus_connection_unref(dbus->conn); + } + + purge_name_trackers(dbus); + purge_calls(dbus); + + mrp_free(dbus->address); + dbus->conn = NULL; + dbus->ml = NULL; + + mrp_free(dbus); + } +} + + +static int bus_cmp(const void *key1, const void *key2) +{ + return key2 - key1; +} + + +static uint32_t bus_hash(const void *key) +{ + uint32_t h; + + h = (ptrdiff_t)key; + h >>= 2 * sizeof(key); + + return h; +} + + +static int find_bus_by_spec(void *key, void *object, void *user_data) +{ + mrp_dbus_t *dbus = (mrp_dbus_t *)object; + bus_spec_t *spec = (bus_spec_t *)user_data; + + MRP_UNUSED(key); + + if (dbus->ml == spec->ml && !strcmp(dbus->address, spec->address)) + return TRUE; + else + return FALSE; +} + + +static mrp_dbus_t *dbus_get(mrp_mainloop_t *ml, const char *address) +{ + mrp_htbl_config_t hcfg; + bus_spec_t spec; + + if (buses == NULL) { + mrp_clear(&hcfg); + + hcfg.comp = bus_cmp; + hcfg.hash = bus_hash; + hcfg.free = NULL; + + buses = mrp_htbl_create(&hcfg); + + return NULL; + } + else { + spec.ml = ml; + spec.address = address; + + return mrp_htbl_find(buses, find_bus_by_spec, &spec); + } +} + + +mrp_dbus_t *mrp_dbus_connect(mrp_mainloop_t *ml, const char *address, + DBusError *errp) +{ + static struct DBusObjectPathVTable vtable = { + .message_function = dispatch_method + }; + + mrp_htbl_config_t hcfg; + mrp_dbus_t *dbus; + + if ((dbus = dbus_get(ml, address)) != NULL) + return mrp_dbus_ref(dbus); + + if ((dbus = mrp_allocz(sizeof(*dbus))) == NULL) + return NULL; + + mrp_list_init(&dbus->calls); + mrp_list_init(&dbus->name_trackers); + mrp_refcnt_init(&dbus->refcnt); + + dbus->ml = ml; + + + mrp_dbus_error_init(errp); + + /* + * connect to the bus + */ + + if (!strcmp(address, "system")) + dbus->conn = dbus_bus_get(DBUS_BUS_SYSTEM, errp); + else if (!strcmp(address, "session")) + dbus->conn = dbus_bus_get(DBUS_BUS_SESSION, errp); + else { + dbus->conn = dbus_connection_open_private(address, errp); + dbus->priv = TRUE; + + if (dbus->conn == NULL || !dbus_bus_register(dbus->conn, errp)) + goto fail; + } + + if (dbus->conn == NULL) + goto fail; + + dbus->address = mrp_strdup(address); + dbus->unique_name = dbus_bus_get_unique_name(dbus->conn); + + /* + * set up with mainloop + */ + + if (!mrp_dbus_setup_connection(ml, dbus->conn)) + goto fail; + + /* + * set up our message dispatchers and take our name on the bus + */ + + if (!dbus_connection_add_filter(dbus->conn, dispatch_signal, dbus, NULL)) { + dbus_set_error(errp, DBUS_ERROR_FAILED, + "Failed to set up signal dispatching."); + goto fail; + } + dbus->signal_filter = TRUE; + + if (!dbus_connection_register_fallback(dbus->conn, "/", &vtable, dbus)) { + dbus_set_error(errp, DBUS_ERROR_FAILED, + "Failed to set up method dispatching."); + goto fail; + } + dbus->register_fallback = TRUE; + + mrp_clear(&hcfg); + hcfg.comp = mrp_string_comp; + hcfg.hash = mrp_string_hash; + hcfg.free = handler_list_free_cb; + + if ((dbus->methods = mrp_htbl_create(&hcfg)) == NULL) { + dbus_set_error(errp, DBUS_ERROR_FAILED, + "Failed to create DBUS method table."); + goto fail; + } + + if ((dbus->signals = mrp_htbl_create(&hcfg)) == NULL) { + dbus_set_error(errp, DBUS_ERROR_FAILED, + "Failed to create DBUS signal table."); + goto fail; + } + + + /* + * install handler for NameOwnerChanged for tracking clients/peers + */ + + if (!mrp_dbus_add_signal_handler(dbus, DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH, + DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED, + name_owner_change_cb, NULL)) { + dbus_set_error(errp, DBUS_ERROR_FAILED, + "Failed to install NameOwnerChanged handler."); + goto fail; + } + + /* install a 'safe' filter to avoid receiving all name change signals */ + mrp_dbus_install_filter(dbus, + DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH, + DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED, + DBUS_ADMIN_SERVICE, NULL); + + mrp_list_init(&dbus->name_trackers); + dbus->call_id = 1; + + if (mrp_htbl_insert(buses, dbus->conn, dbus)) + return dbus; + + fail: + dbus_disconnect(dbus); + return NULL; +} + + +mrp_dbus_t *mrp_dbus_ref(mrp_dbus_t *dbus) +{ + return mrp_ref_obj(dbus, refcnt); +} + + +int mrp_dbus_unref(mrp_dbus_t *dbus) +{ + if (mrp_unref_obj(dbus, refcnt)) { + dbus_disconnect(dbus); + + return TRUE; + } + else + return FALSE; +} + + +int mrp_dbus_acquire_name(mrp_dbus_t *dbus, const char *name, DBusError *error) +{ + int flags, status; + + mrp_dbus_error_init(error); + + flags = DBUS_NAME_FLAG_REPLACE_EXISTING | DBUS_NAME_FLAG_DO_NOT_QUEUE; + status = dbus_bus_request_name(dbus->conn, name, flags, error); + + if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) + return TRUE; + else { + if (status == DBUS_REQUEST_NAME_REPLY_EXISTS) { + if (error) + dbus_error_free(error); + dbus_set_error(error, DBUS_ERROR_FAILED, "name already taken"); + } + return FALSE; + } +} + + +int mrp_dbus_release_name(mrp_dbus_t *dbus, const char *name, DBusError *error) +{ + mrp_dbus_error_init(error); + + if (dbus_bus_release_name(dbus->conn, name, error) != -1) + return TRUE; + else + return FALSE; +} + + +const char *mrp_dbus_get_unique_name(mrp_dbus_t *dbus) +{ + return dbus->unique_name; +} + +static void name_owner_query_cb(mrp_dbus_t *dbus, DBusMessage *msg, void *data) +{ + name_tracker_t *t = (name_tracker_t *)data; + const char *owner; + int state; + + if (t->cb != NULL) { /* tracker still active */ + t->qid = 0; + state = dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_RETURN; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &owner, + DBUS_TYPE_INVALID)) + owner = "<unknown>"; + + t->cb(dbus, t->name, state, owner, t->user_data); + } + else /* already requested to delete */ + mrp_free(t); +} + + +static int name_owner_change_cb(mrp_dbus_t *dbus, DBusMessage *msg, void *data) +{ + const char *name, *prev, *next; + mrp_list_hook_t *p, *n; + name_tracker_t *t; + + MRP_UNUSED(data); + + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL) + return FALSE; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &prev, + DBUS_TYPE_STRING, &next, + DBUS_TYPE_INVALID)) + return FALSE; + + /* + * Notes: XXX TODO + * In principle t->cb could call mrp_dbus_forget for some other D-BUS + * address than name. If that happened to be n (== p->hook.next) this + * would result in a crash or memory corruption in the next iteration + * of this loop (when handling n). We can easily get around this + * problem by + * + * 1) adminstering in mrp_dbus_t that we're handing a NameOwnerChange + * 2) checking for this in mrp_dbus_forget_name and if it is the case + * only marking the affected entry for deletion + * 3) removing entries marked for deletion in this loop (or just + * ignoring them and making another pass in the end removing any + * such entry). + */ + + mrp_list_foreach(&dbus->name_trackers, p, n) { + t = mrp_list_entry(p, name_tracker_t, hook); + + if (!strcmp(name, t->name)) + t->cb(dbus, name, next && *next, next, t->user_data); + } + + return TRUE; +} + + +int mrp_dbus_follow_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_name_cb_t cb, void *user_data) +{ + name_tracker_t *t; + + if ((t = mrp_allocz(sizeof(*t))) != NULL) { + if ((t->name = mrp_strdup(name)) != NULL) { + t->cb = cb; + t->user_data = user_data; + + if (mrp_dbus_install_filter(dbus, + DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH, + DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED, + name, NULL)) { + mrp_list_append(&dbus->name_trackers, &t->hook); + + t->qid = mrp_dbus_call(dbus, + DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH, + DBUS_ADMIN_SERVICE, "GetNameOwner", 5000, + name_owner_query_cb, t, + DBUS_TYPE_STRING, &t->name, + DBUS_TYPE_INVALID); + return TRUE; + } + else { + mrp_free(t->name); + mrp_free(t); + } + } + } + + return FALSE; +} + + +int mrp_dbus_forget_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_name_cb_t cb, void *user_data) +{ + mrp_list_hook_t *p, *n; + name_tracker_t *t; + + mrp_dbus_remove_filter(dbus, + DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH, + DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED, + name, NULL); + + mrp_list_foreach(&dbus->name_trackers, p, n) { + t = mrp_list_entry(p, name_tracker_t, hook); + + if (t->cb == cb && t->user_data == user_data && !strcmp(t->name,name)) { + mrp_list_delete(&t->hook); + mrp_free(t->name); + + if (!t->qid) + mrp_free(t); + else { + t->cb = NULL; + t->user_data = NULL; + t->name = NULL; + } + + return TRUE; + } + } + + return FALSE; +} + + +static void purge_name_trackers(mrp_dbus_t *dbus) +{ + mrp_list_hook_t *p, *n; + name_tracker_t *t; + + mrp_list_foreach(&dbus->name_trackers, p, n) { + t = mrp_list_entry(p, name_tracker_t, hook); + + mrp_list_delete(p); + mrp_dbus_remove_filter(dbus, DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH, + DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED, + t->name, NULL); + mrp_free(t->name); + mrp_free(t); + } +} + + +static handler_t *handler_alloc(const char *sender, const char *path, + const char *interface, const char *member, + mrp_dbus_handler_t handler, void *user_data) +{ + handler_t *h; + + if ((h = mrp_allocz(sizeof(*h))) != NULL) { + h->sender = mrp_strdup(sender); + h->path = mrp_strdup(path); + h->interface = mrp_strdup(interface); + h->member = mrp_strdup(member); + + if ((path && !h->path) || !h->interface || !h->member) { + handler_free(h); + return NULL; + } + + h->handler = handler; + h->user_data = user_data; + + return h; + } + + return NULL; +} + + +static void handler_free(handler_t *h) +{ + if (h != NULL) { + mrp_free(h->sender); + mrp_free(h->path); + mrp_free(h->interface); + mrp_free(h->member); + + mrp_free(h); + } +} + + +static handler_list_t *handler_list_alloc(const char *member) +{ + handler_list_t *l; + + if ((l = mrp_allocz(sizeof(*l))) != NULL) { + if ((l->member = mrp_strdup(member)) != NULL) + mrp_list_init(&l->handlers); + else { + mrp_free(l); + l = NULL; + } + } + + return l; +} + + +static inline void handler_list_free(handler_list_t *l) +{ + mrp_list_hook_t *p, *n; + handler_t *h; + + mrp_list_foreach(&l->handlers, p, n) { + h = mrp_list_entry(p, handler_t, hook); + mrp_list_delete(p); + handler_free(h); + } + + mrp_free(l->member); + mrp_free(l); +} + + +static void handler_list_free_cb(void *key, void *entry) +{ + MRP_UNUSED(key); + + handler_list_free((handler_list_t *)entry); +} + + +static inline int handler_specificity(handler_t *h) +{ + int score = 0; + + if (h->path && *h->path) + score |= 0x4; + if (h->interface && *h->interface) + score |= 0x2; + if (h->member && *h->member) + score |= 0x1; + + return score; +} + + +static void handler_list_insert(handler_list_t *l, handler_t *handler) +{ + mrp_list_hook_t *p, *n; + handler_t *h; + int score; + + score = handler_specificity(handler); + + mrp_list_foreach(&l->handlers, p, n) { + h = mrp_list_entry(p, handler_t, hook); + + if (score >= handler_specificity(h)) { + mrp_list_append(h->hook.prev, &handler->hook); /* add before h */ + return; + } + } + + mrp_list_append(&l->handlers, &handler->hook); +} + + +static handler_t *handler_list_lookup(handler_list_t *l, const char *path, + const char *interface, const char *member, + mrp_dbus_handler_t handler, + void *user_data) +{ + mrp_list_hook_t *p, *n; + handler_t *h; + + mrp_list_foreach(&l->handlers, p, n) { + h = mrp_list_entry(p, handler_t, hook); + + if (h->handler == handler && user_data == h->user_data && + path && !strcmp(path, h->path) && + interface && !strcmp(interface, h->interface) && + member && !strcmp(member, h->member)) + return h; + } + + return NULL; +} + + +static handler_t *handler_list_find(handler_list_t *l, const char *path, + const char *interface, const char *member) +{ +#define MATCHES(h, field) (!*field || !*h->field || !strcmp(field, h->field)) + mrp_list_hook_t *p, *n; + handler_t *h; + + mrp_list_foreach(&l->handlers, p, n) { + h = mrp_list_entry(p, handler_t, hook); + + if (MATCHES(h, path) && MATCHES(h, interface) && MATCHES(h, member)) + return h; + } + + return NULL; +#undef MATCHES +} + + +int mrp_dbus_export_method(mrp_dbus_t *dbus, const char *path, + const char *interface, const char *member, + mrp_dbus_handler_t handler, void *user_data) +{ + handler_list_t *methods; + handler_t *m; + + if ((methods = mrp_htbl_lookup(dbus->methods, (void *)member)) == NULL) { + if ((methods = handler_list_alloc(member)) == NULL) + return FALSE; + + mrp_htbl_insert(dbus->methods, methods->member, methods); + } + + m = handler_alloc(NULL, path, interface, member, handler, user_data); + if (m != NULL) { + handler_list_insert(methods, m); + return TRUE; + } + else + return FALSE; +} + + +int mrp_dbus_remove_method(mrp_dbus_t *dbus, const char *path, + const char *interface, const char *member, + mrp_dbus_handler_t handler, void *user_data) +{ + handler_list_t *methods; + handler_t *m; + + if ((methods = mrp_htbl_lookup(dbus->methods, (void *)member)) == NULL) + return FALSE; + + m = handler_list_lookup(methods, path, interface, member, + handler, user_data); + if (m != NULL) { + mrp_list_delete(&m->hook); + handler_free(m); + + return TRUE; + } + else + return FALSE; +} + + +int mrp_dbus_add_signal_handler(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, mrp_dbus_handler_t handler, + void *user_data) +{ + handler_list_t *signals; + handler_t *s; + + if ((signals = mrp_htbl_lookup(dbus->signals, (void *)member)) == NULL) { + if ((signals = handler_list_alloc(member)) == NULL) + return FALSE; + + if (!mrp_htbl_insert(dbus->signals, signals->member, signals)) { + handler_list_free(signals); + return FALSE; + } + } + + s = handler_alloc(sender, path, interface, member, handler, user_data); + if (s != NULL) { + handler_list_insert(signals, s); + return TRUE; + } + else { + handler_free(s); + if (mrp_list_empty(&signals->handlers)) + mrp_htbl_remove(dbus->signals, signals->member, TRUE); + return FALSE; + } +} + + + +int mrp_dbus_del_signal_handler(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, mrp_dbus_handler_t handler, + void *user_data) +{ + handler_list_t *signals; + handler_t *s; + + MRP_UNUSED(sender); + + if ((signals = mrp_htbl_lookup(dbus->signals, (void *)member)) == NULL) + return FALSE; + + s = handler_list_lookup(signals, path, interface, member, + handler, user_data); + if (s != NULL) { + mrp_list_delete(&s->hook); + handler_free(s); + + if (mrp_list_empty(&signals->handlers)) + mrp_htbl_remove(dbus->signals, (void *)member, TRUE); + + return TRUE; + } + else + return FALSE; +} + + + +int mrp_dbus_subscribe_signal(mrp_dbus_t *dbus, + mrp_dbus_handler_t handler, void *user_data, + const char *sender, const char *path, + const char *interface, const char *member, ...) +{ + va_list ap; + int success; + + + if (mrp_dbus_add_signal_handler(dbus, sender, path, interface, member, + handler, user_data)) { + va_start(ap, member); + success = mrp_dbus_install_filterv(dbus, + sender, path, interface, member, ap); + va_end(ap); + + if (success) + return TRUE; + else + mrp_dbus_del_signal_handler(dbus, sender, path, interface, member, + handler, user_data); + } + + return FALSE; +} + + +int mrp_dbus_unsubscribe_signal(mrp_dbus_t *dbus, + mrp_dbus_handler_t handler, void *user_data, + const char *sender, const char *path, + const char *interface, const char *member, ...) +{ + va_list ap; + int status; + + status = mrp_dbus_del_signal_handler(dbus, sender, path, interface, member, + handler, user_data); + va_start(ap, member); + status &= mrp_dbus_remove_filterv(dbus, + sender, path, interface, member, ap); + va_end(ap); + + return status; +} + + +int mrp_dbus_install_filterv(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, va_list args) +{ +#define ADD_TAG(tag, value, ...) do { \ + if (value != NULL) { \ + l = snprintf(p, n, "%s%s='%s'", p == filter ? "" : ",", \ + tag, value); \ + if (l >= n) \ + do { __VA_ARGS__; } while (0); \ + n -= l; \ + p += l; \ + } \ + } while (0) + + va_list ap; + DBusError error; + char filter[1024], *p, argn[16], *val; + int n, l, i; + + p = filter; + n = sizeof(filter); + + ADD_TAG("type" , "signal" , return FALSE); + ADD_TAG("sender" , sender , return FALSE); + ADD_TAG("path" , path , return FALSE); + ADD_TAG("interface", interface, return FALSE); + ADD_TAG("member" , member , return FALSE); + + va_copy(ap, args); + i = 0; + while ((val = va_arg(ap, char *)) != NULL) { + snprintf(argn, sizeof(argn), "arg%d", i); + ADD_TAG(argn, val, { va_end(ap); return FALSE; }); + i++; + } + va_end(ap); + + dbus_error_init(&error); + dbus_bus_add_match(dbus->conn, filter, &error); + + if (dbus_error_is_set(&error)) { + mrp_log_error("Failed to install filter '%s' (error: %s).", filter, + mrp_dbus_errmsg(&error)); + dbus_error_free(&error); + + return FALSE; + } + else + return TRUE; + +} + + +int mrp_dbus_install_filter(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, ...) +{ + va_list ap; + int status; + + va_start(ap, member); + status = mrp_dbus_install_filterv(dbus, + sender, path, interface, member, ap); + va_end(ap); + + return status; +} + + +int mrp_dbus_remove_filterv(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, va_list args) +{ + va_list ap; + char filter[1024], *p, argn[16], *val; + int n, l, i; + + p = filter; + n = sizeof(filter); + + ADD_TAG("type" , "signal" , return FALSE); + ADD_TAG("sender" , sender , return FALSE); + ADD_TAG("path" , path , return FALSE); + ADD_TAG("interface", interface, return FALSE); + ADD_TAG("member" , member , return FALSE); + + va_copy(ap, args); + i = 0; + while ((val = va_arg(ap, char *)) != NULL) { + snprintf(argn, sizeof(argn), "arg%d", i); + ADD_TAG(argn, val, { va_end(ap); return FALSE; }); + i++; + } + va_end(ap); + + dbus_bus_remove_match(dbus->conn, filter, NULL); + return TRUE; +#undef ADD_TAG +} + + +int mrp_dbus_remove_filter(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, ...) +{ + va_list ap; + int status; + + va_start(ap, member); + status = mrp_dbus_remove_filterv(dbus, sender, path, interface, member, ap); + va_end(ap); + + return status; +} + + + +static DBusHandlerResult dispatch_method(DBusConnection *c, + DBusMessage *msg, void *data) +{ +#define SAFESTR(str) (str ? str : "<none>") + const char *path = dbus_message_get_path(msg); + const char *interface = dbus_message_get_interface(msg); + const char *member = dbus_message_get_member(msg); + + mrp_dbus_t *dbus = (mrp_dbus_t *)data; + handler_list_t *l; + handler_t *h; + + MRP_UNUSED(c); + + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_METHOD_CALL || !member) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + mrp_debug("path='%s', interface='%s', member='%s')...", + SAFESTR(path), SAFESTR(interface), SAFESTR(member)); + + if ((l = mrp_htbl_lookup(dbus->methods, (void *)member)) != NULL) { + retry: + if ((h = handler_list_find(l, path, interface, member)) != NULL) { + if (h->handler(dbus, msg, h->user_data)) + return DBUS_HANDLER_RESULT_HANDLED; + else + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + } + else { + if ((l = mrp_htbl_lookup(dbus->methods, "")) != NULL) + goto retry; + } + + mrp_debug("Unhandled method path=%s, %s.%s.", SAFESTR(path), + SAFESTR(interface), SAFESTR(member)); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + + +static DBusHandlerResult dispatch_signal(DBusConnection *c, + DBusMessage *msg, void *data) +{ +#define MATCHES(h, field) (!*field || !h->field || !*h->field || \ + !strcmp(field, h->field)) + + const char *path = dbus_message_get_path(msg); + const char *interface = dbus_message_get_interface(msg); + const char *member = dbus_message_get_member(msg); + + mrp_dbus_t *dbus = (mrp_dbus_t *)data; + mrp_list_hook_t *p, *n; + handler_list_t *l; + handler_t *h; + int retried = FALSE; + int handled = FALSE; + + MRP_UNUSED(c); + + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL || !member) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + mrp_debug("%s(path='%s', interface='%s', member='%s')...", + __FUNCTION__, + SAFESTR(path), SAFESTR(interface), SAFESTR(member)); + + if ((l = mrp_htbl_lookup(dbus->signals, (void *)member)) != NULL) { + retry: + mrp_list_foreach(&l->handlers, p, n) { + h = mrp_list_entry(p, handler_t, hook); + + if (MATCHES(h,path) && MATCHES(h,interface) && MATCHES(h,member)) { + h->handler(dbus, msg, h->user_data); + handled = TRUE; + } + } + } + + if (!retried) { + if ((l = mrp_htbl_lookup(dbus->signals, "")) != NULL) { + retried = TRUE; + goto retry; + } + } + + if (!handled) + mrp_debug("Unhandled signal path=%s, %s.%s.", SAFESTR(path), + SAFESTR(interface), SAFESTR(member)); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +#undef MATCHES +#undef SAFESTR +} + + +static void call_reply_cb(DBusPendingCall *pend, void *user_data) +{ + call_t *call = (call_t *)user_data; + DBusMessage *reply; + + reply = dbus_pending_call_steal_reply(pend); + + call->pend = NULL; + mrp_list_delete(&call->hook); + + call->cb(call->dbus, reply, call->user_data); + + dbus_message_unref(reply); + dbus_pending_call_unref(pend); + + call_free(call); +} + + +int32_t mrp_dbus_call(mrp_dbus_t *dbus, const char *dest, const char *path, + const char *interface, const char *member, int timeout, + mrp_dbus_reply_cb_t cb, void *user_data, int type, ...) +{ + va_list ap; + int32_t id; + call_t *call; + DBusMessage *msg; + DBusPendingCall *pend; + int success; + + call = NULL; + pend = NULL; + + msg = dbus_message_new_method_call(dest, path, interface, member); + + if (msg == NULL) + return 0; + + if (cb != NULL) { + if ((call = mrp_allocz(sizeof(*call))) != NULL) { + mrp_list_init(&call->hook); + + call->dbus = dbus; + call->id = dbus->call_id++; + call->cb = cb; + call->user_data = user_data; + + id = call->id; + } + else + goto fail; + } + else + id = dbus->call_id++; + + if (type == DBUS_TYPE_INVALID) + success = TRUE; + else { + va_start(ap, type); + success = dbus_message_append_args_valist(msg, type, ap); + va_end(ap); + } + + if (!success) + goto fail; + + if (cb == NULL) { + dbus_message_set_no_reply(msg, TRUE); + if (!dbus_connection_send(dbus->conn, msg, NULL)) + goto fail; + } + else { + if (!dbus_connection_send_with_reply(dbus->conn, msg, &pend, timeout)) + goto fail; + + if (!dbus_pending_call_set_notify(pend, call_reply_cb, call, NULL)) + goto fail; + } + + if (cb != NULL) { + mrp_list_append(&dbus->calls, &call->hook); + call->pend = pend; + } + + dbus_message_unref(msg); + + return id; + + fail: + if (pend != NULL) + dbus_pending_call_unref(pend); + + if(msg != NULL) + dbus_message_unref(msg); + + call_free(call); + + return 0; +} + + +int32_t mrp_dbus_send(mrp_dbus_t *dbus, const char *dest, const char *path, + const char *interface, const char *member, int timeout, + mrp_dbus_reply_cb_t cb, void *user_data, DBusMessage *msg) +{ + int32_t id; + call_t *call; + DBusPendingCall *pend; + int method; + + call = NULL; + pend = NULL; + + if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_SIGNAL) { + if (cb != NULL) + goto fail; + else + method = FALSE; + } + else + method = TRUE; + + if (cb != NULL) { + if ((call = mrp_allocz(sizeof(*call))) != NULL) { + mrp_list_init(&call->hook); + + call->dbus = dbus; + call->id = dbus->call_id++; + call->cb = cb; + call->user_data = user_data; + + id = call->id; + } + else + goto fail; + } + else + id = dbus->call_id++; + + if (!dbus_message_set_destination(msg, dest)) + goto fail; + if (!dbus_message_set_path(msg, path)) + goto fail; + if (!dbus_message_set_interface(msg, interface)) + goto fail; + if (!dbus_message_set_member(msg, member)) + goto fail; + + if (cb == NULL) { + if (method) + dbus_message_set_no_reply(msg, TRUE); + if (!dbus_connection_send(dbus->conn, msg, NULL)) + goto fail; + } + else { + if (!dbus_connection_send_with_reply(dbus->conn, msg, &pend, timeout)) + goto fail; + + if (!dbus_pending_call_set_notify(pend, call_reply_cb, call, NULL)) + goto fail; + } + + if (cb != NULL) { + mrp_list_append(&dbus->calls, &call->hook); + call->pend = pend; + } + + return id; + + fail: + if (pend != NULL) + dbus_pending_call_unref(pend); + + if(msg != NULL) + dbus_message_unref(msg); + + call_free(call); + + return 0; +} + + +int mrp_dbus_send_msg(mrp_dbus_t *dbus, DBusMessage *msg) +{ + return dbus_connection_send(dbus->conn, msg, NULL); +} + + +int mrp_dbus_call_cancel(mrp_dbus_t *dbus, int32_t id) +{ + mrp_list_hook_t *p, *n; + call_t *call; + + mrp_list_foreach(&dbus->calls, p, n) { + call = mrp_list_entry(p, call_t, hook); + + if (call->id == id) { + mrp_list_delete(p); + + dbus_pending_call_cancel(call->pend); + dbus_pending_call_unref(call->pend); + call->pend = NULL; + + call_free(call); + return TRUE; + } + } + + return FALSE; +} + + +int mrp_dbus_reply(mrp_dbus_t *dbus, DBusMessage *msg, int type, ...) +{ + va_list ap; + DBusMessage *rpl; + int success; + + rpl = dbus_message_new_method_return(msg); + + if (rpl == NULL) + return FALSE; + + if (type == DBUS_TYPE_INVALID) + success = TRUE; + else { + va_start(ap, type); + success = dbus_message_append_args_valist(rpl, type, ap); + va_end(ap); + } + + if (!success) + goto fail; + + if (!dbus_connection_send(dbus->conn, rpl, NULL)) + goto fail; + + dbus_message_unref(rpl); + + return TRUE; + + fail: + if(rpl != NULL) + dbus_message_unref(rpl); + + return FALSE; +} + + +int mrp_dbus_reply_error(mrp_dbus_t *dbus, DBusMessage *msg, + const char *errname, const char *errmsg, int type, ...) +{ + va_list ap; + DBusMessage *rpl; + int success; + + rpl = dbus_message_new_error(msg, errname, errmsg); + + if (rpl == NULL) + return FALSE; + + if (type == DBUS_TYPE_INVALID) + success = TRUE; + else { + va_start(ap, type); + success = dbus_message_append_args_valist(rpl, type, ap); + va_end(ap); + } + + if (!success) + goto fail; + + if (!dbus_connection_send(dbus->conn, rpl, NULL)) + goto fail; + + dbus_message_unref(rpl); + + return TRUE; + + fail: + if(rpl != NULL) + dbus_message_unref(rpl); + + return FALSE; +} + + +static void call_free(call_t *call) +{ + if (call != NULL) + mrp_free(call); +} + + +static void purge_calls(mrp_dbus_t *dbus) +{ + mrp_list_hook_t *p, *n; + call_t *call; + + mrp_list_foreach(&dbus->calls, p, n) { + call = mrp_list_entry(p, call_t, hook); + + mrp_list_delete(&call->hook); + + if (call->pend != NULL) + dbus_pending_call_unref(call->pend); + + mrp_free(call); + } +} + + +int mrp_dbus_signal(mrp_dbus_t *dbus, const char *dest, const char *path, + const char *interface, const char *member, int type, ...) +{ + va_list ap; + DBusMessage *msg; + int success; + + msg = dbus_message_new_signal(path, interface, member); + + if (msg == NULL) + return 0; + + if (type == DBUS_TYPE_INVALID) + success = TRUE; + else { + va_start(ap, type); + success = dbus_message_append_args_valist(msg, type, ap); + va_end(ap); + } + + if (!success) + goto fail; + + if (dest && *dest && !dbus_message_set_destination(msg, dest)) + goto fail; + + if (!dbus_connection_send(dbus->conn, msg, NULL)) + goto fail; + + dbus_message_unref(msg); + + return TRUE; + + fail: + /* + * XXX TODO: Hmm... IIRC, libdbus unrefs messages upon failure. If it + * was really so, this would corrupt/crash. Check this from + * libdbus code. + */ + if(msg != NULL) + dbus_message_unref(msg); + + return 0; +} diff --git a/src/common/libdbus.h b/src/common/libdbus.h new file mode 100644 index 0000000..84f29b7 --- /dev/null +++ b/src/common/libdbus.h @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_DBUS_H__ +#define __MURPHY_DBUS_H__ + +#include <murphy/common/mainloop.h> +#include <dbus/dbus.h> + +#define MRP_AF_DBUS 0xDB + +/** Our D-BUS (connection) abstraction. */ +struct mrp_dbus_s; +typedef struct mrp_dbus_s mrp_dbus_t; + +/** D-BUS method or signal callback type. */ +typedef int (*mrp_dbus_handler_t)(mrp_dbus_t *, DBusMessage *, void *); + +/** Create a new connection to the given bus. */ +mrp_dbus_t *mrp_dbus_connect(mrp_mainloop_t *ml, const char *address, + DBusError *errp); +#define mrp_dbus_get mrp_dbus_connect + + +/** Set up a DBusConnection with a mainloop. */ +int mrp_dbus_setup_connection(mrp_mainloop_t *ml, DBusConnection *conn); + +/** Increase the reference count of the given DBus (connection). */ +mrp_dbus_t *mrp_dbus_ref(mrp_dbus_t *dbus); + +/** Decrease the reference count of the given DBus (connection). */ +int mrp_dbus_unref(mrp_dbus_t *dbus); + +/** Acquire the given name on the given bus (connection). */ +int mrp_dbus_acquire_name(mrp_dbus_t *dbus, const char *name, DBusError *error); + +/** Release the given name on the given bus (connection). */ +int mrp_dbus_release_name(mrp_dbus_t *dbus, const char *name, DBusError *error); + +typedef void (*mrp_dbus_name_cb_t)(mrp_dbus_t *, const char *, int, + const char *, void *); +int mrp_dbus_follow_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_name_cb_t cb, void *user_data); +int mrp_dbus_forget_name(mrp_dbus_t *dbus, const char *name, + mrp_dbus_name_cb_t cb, void *user_data); + +int mrp_dbus_export_method(mrp_dbus_t *dbus, const char *path, + const char *interface, const char *member, + mrp_dbus_handler_t handler, void *user_data); + +int mrp_dbus_remove_method(mrp_dbus_t *dbus, const char *path, + const char *interface, const char *member, + mrp_dbus_handler_t handler, void *user_data); + +MRP_NULLTERM int mrp_dbus_subscribe_signal(mrp_dbus_t *dbus, + mrp_dbus_handler_t handler, + void *user_data, + const char *sender, + const char *path, + const char *interface, + const char *member, ...); + +MRP_NULLTERM int mrp_dbus_unsubscribe_signal(mrp_dbus_t *dbus, + mrp_dbus_handler_t handler, + void *user_data, + const char *sender, + const char *path, + const char *interface, + const char *member, ...); + +MRP_NULLTERM int mrp_dbus_install_filter(mrp_dbus_t *dbus, + const char *sender, + const char *path, + const char *interface, + const char *member, ...); +int mrp_dbus_install_filterv(mrp_dbus_t *dbus, + const char *sender, + const char *path, + const char *interface, + const char *member, + va_list ap); + +MRP_NULLTERM int mrp_dbus_remove_filter(mrp_dbus_t *dbus, + const char *sender, + const char *path, + const char *interface, + const char *member, ...); + +int mrp_dbus_remove_filterv(mrp_dbus_t *dbus, + const char *sender, + const char *path, + const char *interface, + const char *member, + va_list ap); + +int mrp_dbus_add_signal_handler(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, mrp_dbus_handler_t handler, + void *user_data); +int mrp_dbus_del_signal_handler(mrp_dbus_t *dbus, const char *sender, + const char *path, const char *interface, + const char *member, mrp_dbus_handler_t handler, + void *user_data); + +typedef void (*mrp_dbus_reply_cb_t)(mrp_dbus_t *dbus, DBusMessage *reply, + void *user_data); + +int32_t mrp_dbus_call(mrp_dbus_t *dbus, const char *dest, + const char *path, const char *interface, + const char *member, int timeout, + mrp_dbus_reply_cb_t cb, void *user_data, + int dbus_type, ...); +int mrp_dbus_call_cancel(mrp_dbus_t *dbus, int32_t id); + +int mrp_dbus_reply(mrp_dbus_t *dbus, DBusMessage *msg, int type, ...); + +int mrp_dbus_reply_error(mrp_dbus_t *dbus, DBusMessage *msg, + const char *errname, const char *errmsg, + int type, ...); + +int mrp_dbus_signal(mrp_dbus_t *dbus, const char *dest, const char *path, + const char *interface, const char *member, int type, ...); + +int32_t mrp_dbus_send(mrp_dbus_t *dbus, const char *dest, const char *path, + const char *interface, const char *member, int timeout, + mrp_dbus_reply_cb_t cb, void *user_data, + DBusMessage *msg); + +int mrp_dbus_send_msg(mrp_dbus_t *dbus, DBusMessage *msg); + +const char *mrp_dbus_get_unique_name(mrp_dbus_t *dbus); + +static inline void mrp_dbus_error_init(DBusError *error) +{ + /* + * Prevent libdbus error messages for NULL DBusError's... + */ + if (error != NULL) + dbus_error_init(error); +} + + +static inline const char *mrp_dbus_errmsg(DBusError *err) +{ + if (err && dbus_error_is_set(err)) + return err->message; + else + return "unknown DBUS error"; +} + + +#endif /* __MURPHY_DBUS_H__ */ diff --git a/src/common/list.h b/src/common/list.h new file mode 100644 index 0000000..16c0ae4 --- /dev/null +++ b/src/common/list.h @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_LIST_H__ +#define __MURPHY_LIST_H__ + +#include <murphy/common/macros.h> + + +MRP_CDECL_BEGIN + + +/** \file + * A simple doubly-linked circular list implementation, obviously inspired + * by the linux kernel. + */ + + +/** A list hook. Used both a list head and to hook up objects to the list. */ +typedef struct mrp_list_hook_s mrp_list_hook_t; +struct mrp_list_hook_s { + mrp_list_hook_t *prev; + mrp_list_hook_t *next; +}; + +/** Macro to initialize a list to be empty. */ +#define MRP_LIST_INIT(list) { .prev = &(list), .next = &(list) } + +/** Macro to define a list and initialize it to be empty. */ +#define MRP_LIST_HOOK(list) mrp_list_hook_t list = MRP_LIST_INIT(list) + +/** Initialize a list to be empty. */ +static inline void mrp_list_init(mrp_list_hook_t *list) +{ + list->prev = list->next = list; +} + +/** Check if a list is empty. */ +static inline int mrp_list_empty(mrp_list_hook_t *list) +{ + if (list->next == list->prev) { + if (list->next == list) + return TRUE; + +#ifdef __MURPHY_LIST_ALLOW_NULL + if (!list->next) + return TRUE; +#endif + } + + return FALSE; +} + +/** Append a new item to a list (add it after the last item). */ +static inline void mrp_list_append(mrp_list_hook_t *list, mrp_list_hook_t *item) +{ + if (mrp_list_empty(list)) { + list->next = list->prev = item; + item->next = item->prev = list; + } + else { + mrp_list_hook_t *prev = list->prev; + + prev->next = item; + item->prev = prev; + item->next = list; + list->prev = item; + } +} + +/** Prepend a new item to a list (add it before the first item). */ +static inline void mrp_list_prepend(mrp_list_hook_t *list, + mrp_list_hook_t *item) +{ + if (mrp_list_empty(list)) { + list->next = list->prev = item; + item->next = item->prev = list; + } + else { + mrp_list_hook_t *next = list->next; + + list->next = item; + item->prev = list; + item->next = next; + next->prev = item; + } +} + +/** Insert a new item to the list before a given item. */ +static inline void mrp_list_insert_before(mrp_list_hook_t *next, + mrp_list_hook_t *item) +{ + mrp_list_append(next, item); +} + +/** Insert a new item to the list after a given item. */ +static inline void mrp_list_insert_after(mrp_list_hook_t *prev, + mrp_list_hook_t *item) +{ + mrp_list_prepend(prev, item); +} + +/** Delete the given item from the list. */ +static inline void mrp_list_delete(mrp_list_hook_t *item) +{ + mrp_list_hook_t *prev, *next; + + if (!mrp_list_empty(item)) { + prev = item->prev; + next = item->next; + + prev->next = next; + next->prev = prev; + + item->prev = item->next = item; + } +} + +/** Reattach a list to a new hook. Initialize old hook to be empty. */ +static inline void mrp_list_move(mrp_list_hook_t *new_hook, + mrp_list_hook_t *old_hook) +{ + *new_hook = *old_hook; + + new_hook->next->prev = new_hook; + new_hook->prev->next = new_hook; + + mrp_list_init(old_hook); +} + + +/** Update a list when the address of a hook has changed (eg. by realloc). */ +static inline void mrp_list_update_address(mrp_list_hook_t *new_addr, + mrp_list_hook_t *old_addr) +{ + mrp_list_hook_t *prev, *next; + ptrdiff_t diff; + + diff = new_addr - old_addr; + prev = new_addr->prev; + next = new_addr->next; + + prev->next += diff; + next->prev += diff; +} + + +/** Macro to iterate through a list (current item safe to remove). */ +#define mrp_list_foreach(list, p, n) \ + if ((list)->next != NULL) \ + for (p = (list)->next, n = p->next; p != (list); p = n, n = n->next) + +/** Macro to iterate through a list backwards (current item safe to remove). */ +#define mrp_list_foreach_back(list, p, n) \ + if ((list)->prev != NULL) \ + for (p = (list)->prev, n = p->prev; p != (list); p = n, n = n->prev) + +/** Macro to get a pointer to a embedding structure from a list pointer. */ +#ifndef __cplusplus +# define PTR_ARITH_TYPE void +#else +# define PTR_ARITH_TYPE char +#endif + +#define mrp_list_entry(ptr, type, member) \ + (type *)(((PTR_ARITH_TYPE *)(ptr)) - MRP_OFFSET(type, member)) + + +MRP_CDECL_END + + +#endif /* __MURPHY_LIST_H__ */ + diff --git a/src/common/log.c b/src/common/log.c new file mode 100644 index 0000000..e35c43a --- /dev/null +++ b/src/common/log.c @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <stdarg.h> +#include <syslog.h> + +#include <murphy/common/mm.h> +#include <murphy/common/list.h> +#include <murphy/common/log.h> + +typedef struct { + mrp_list_hook_t hook; + char *name; + mrp_logger_t logger; + void *data; + int builtin; +} log_target_t; + +static log_target_t stderr_target; +static log_target_t stdout_target; +static log_target_t syslog_target; +static log_target_t file_target; + +static MRP_LIST_HOOK(log_targets); +static int log_mask = MRP_LOG_MASK_ERROR; +static log_target_t *log_target = NULL; + + +mrp_log_mask_t mrp_log_parse_levels(const char *levels) +{ + const char *p; + mrp_log_mask_t mask; + + mask = 0; + + if (levels == NULL) { + if (mask == 0) + mask = 1; + else { + mask <<= 1; + mask |= 1; + } + } + else { + p = levels; + while (p && *p) { +# define MATCHES(s, l) (!strcmp(s, l) || \ + !strncmp(s, l",", sizeof(l",") - 1)) + + if (MATCHES(p, "info")) + mask |= MRP_LOG_MASK_INFO; + else if (MATCHES(p, "error")) + mask |= MRP_LOG_MASK_ERROR; + else if (MATCHES(p, "warning")) + mask |= MRP_LOG_MASK_WARNING; + else if (MATCHES(p, "none") || MATCHES(p, "off")) + mask = 0; + else + return -1; + + if ((p = strchr(p, ',')) != NULL) + p += 1; + +# undef MATCHES + } + } + + return mask; +} + + +const char *mrp_log_parse_target(const char *target) +{ + return target; +} + + +const char *mrp_log_dump_mask(mrp_log_mask_t mask, char *buf, size_t size) +{ + char *p, *t; + int n, l; + + if (!mask) + return "none"; + + p = buf; + l = size; + + t = ""; + *p = '\0'; + + if (mask & MRP_LOG_MASK_INFO) { + n = snprintf(p, l, "info"); + p += n; + l -= n; + t = ","; + } + if (mask & MRP_LOG_MASK_WARNING) { + n = snprintf(p, l, "%swarning", t); + p += n; + l -= n; + t = ","; + } + if (mask & MRP_LOG_MASK_ERROR) { + n = snprintf(p, l, "%serror", t); + p += n; + l -= n; + t = ","; + } + + return buf; +} + + +mrp_log_mask_t mrp_log_enable(mrp_log_mask_t enabled) +{ + mrp_log_mask_t old_mask = log_mask; + + log_mask |= enabled; + + return old_mask; +} + + +mrp_log_mask_t mrp_log_disable(mrp_log_mask_t disabled) +{ + mrp_log_mask_t old_mask = log_mask; + + log_mask &= ~disabled; + + return old_mask; +} + + +mrp_log_mask_t mrp_log_set_mask(mrp_log_mask_t enabled) +{ + mrp_log_mask_t old_mask = log_mask; + + log_mask = enabled; + + return old_mask; +} + + +static log_target_t *find_target(const char *name) +{ + log_target_t *t; + mrp_list_hook_t *p, *n; + + mrp_list_foreach(&log_targets, p, n) { + t = mrp_list_entry(p, typeof(*t), hook); + + if (t->name == name || !strcmp(t->name, name)) + return t; + } + + return NULL; +} + + +int mrp_log_set_target(const char *name) +{ + log_target_t *target; + const char *path; + + if (!strncmp(name, "file:", 5)) { + path = name + 5; + name = "file"; + } + else + path = NULL; + + target = find_target(name); + + if (target == NULL || (target == &file_target && path == NULL)) + return FALSE; + + /* close files opened by us, if any */ + if (log_target == &file_target) { + if (file_target.data != NULL) { + fclose(file_target.data); + file_target.data = NULL; + } + } + + log_target = target; + + /* open any new files if we have to */ + if (target == &file_target) { + target->data = fopen(path, "a"); + + if (target->data == NULL) { + log_target = &syslog_target; + + return FALSE; + } + } + + return TRUE; +} + + +const char *mrp_log_get_target(void) +{ + return log_target->name; +} + + +int mrp_log_get_targets(const char **targets, size_t size) +{ + mrp_list_hook_t *p, *n; + log_target_t *t; + int cnt; + + cnt = 0; + mrp_list_foreach(&log_targets, p, n) { + if (cnt == (int)size) + break; + + t = mrp_list_entry(p, typeof(*t), hook); + targets[cnt++] = t->name; + } + + return cnt; +} + + +int mrp_log_register_target(const char *name, mrp_logger_t logger, void *data) +{ + log_target_t *target; + + if (find_target(name) != NULL) + return FALSE; + + target = mrp_allocz(sizeof(*target)); + + mrp_list_init(&target->hook); + target->name = mrp_strdup(name); + target->logger = logger; + target->data = data; + + if (target->name != NULL) { + mrp_list_append(&log_targets, &target->hook); + + return TRUE; + } + else { + mrp_free(target); + + return FALSE; + } +} + + +int mrp_log_unregister_target(const char *name) +{ + log_target_t *target; + + target = find_target(name); + + if (target == NULL || target->builtin) + return FALSE; + + if (log_target == target) + log_target = &stderr_target; + + mrp_list_delete(&target->hook); + mrp_free(target->name); + mrp_free(target); + + return TRUE; +} + + +static void log_msgv(void *data, mrp_log_level_t level, const char *file, + int line, const char *func, const char *format, + va_list ap) +{ + FILE *fp = data; + int lvl; + const char *prefix; + char prfx[2*1024]; + + if (!(log_mask & (1 << level))) + return; + + MRP_UNUSED(file); + MRP_UNUSED(line); + + switch (level) { + case MRP_LOG_ERROR: lvl = LOG_ERR; prefix = "E: "; break; + case MRP_LOG_WARNING: lvl = LOG_WARNING; prefix = "W: "; break; + case MRP_LOG_INFO: lvl = LOG_INFO; prefix = "I: "; break; + case MRP_LOG_DEBUG: lvl = LOG_INFO; + snprintf(prfx, sizeof(prfx) - 1, "D: [%s] ", func); + prfx[sizeof(prfx)-1] = '\0'; + prefix = prfx; + break; + default: + return; + } + + if (fp == NULL) + vsyslog(lvl, format, ap); + else { + fputs(prefix, fp); + vfprintf(fp, format, ap); fputs("\n", fp); + fflush(fp); + } +} + + +void mrp_log_msgv(mrp_log_level_t level, const char *file, + int line, const char *func, const char *format, + va_list ap) +{ + static int busy = 0; + mrp_logger_t logger = log_target->logger; + void *data = log_target->data; + + if (MRP_UNLIKELY(busy != 0)) + return; + + if (!(log_mask & (1 << level))) + return; + + busy++; + logger(data, level, file, line, func, format, ap); + busy--; +} + + +void mrp_log_msg(mrp_log_level_t level, const char *file, + int line, const char *func, const char *format, ...) +{ + va_list ap; + + if (!(log_mask & (1 << level))) + return; + + va_start(ap, format); + mrp_log_msgv(level, file, line, func, format, ap); + va_end(ap); +} + + +/* + * workaround for not being able to initialize log_fp to stderr + */ + +static __attribute__((constructor)) void set_default_logging(void) +{ + mrp_list_init(&stderr_target.hook); + stderr_target.name = "stderr"; + stderr_target.logger = log_msgv; + stderr_target.data = stderr; + stderr_target.builtin = TRUE; + + mrp_list_init(&stdout_target.hook); + stdout_target.name = "stdout"; + stdout_target.logger = log_msgv; + stdout_target.data = stdout; + stdout_target.builtin = TRUE; + + mrp_list_init(&syslog_target.hook); + syslog_target.name = "syslog"; + syslog_target.logger = log_msgv; + syslog_target.data = NULL; + syslog_target.builtin = TRUE; + + mrp_list_init(&file_target.hook); + file_target.name = "file"; + file_target.logger = log_msgv; + file_target.data = NULL; + file_target.builtin = TRUE; + + mrp_list_prepend(&log_targets, &file_target.hook); + mrp_list_prepend(&log_targets, &syslog_target.hook); + mrp_list_prepend(&log_targets, &stderr_target.hook); + mrp_list_prepend(&log_targets, &stdout_target.hook); + + log_target = &stderr_target; +} diff --git a/src/common/log.h b/src/common/log.h new file mode 100644 index 0000000..61e69c5 --- /dev/null +++ b/src/common/log.h @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_LOG_H__ +#define __MURPHY_LOG_H__ + +/** \file + * Logging functions and macros. + */ + +#include <stdarg.h> + +#include <murphy/common/macros.h> +#include <murphy/common/debug.h> + +MRP_CDECL_BEGIN + +#define MRP_LOG_NAME_ERROR "error" /**< name for error level */ +#define MRP_LOG_NAME_WARNING "warning" /**< name for warning level */ +#define MRP_LOG_NAME_INFO "info" /**< name for info level */ +#define MRP_LOG_NAME_DEBUG "debug" /**< name for debug level */ + + +/** + * Logging levels. + */ +typedef enum { + MRP_LOG_ERROR = 0, /**< error log level */ + MRP_LOG_WARNING, /**< warning log level */ + MRP_LOG_INFO, /**< info log level */ + MRP_LOG_DEBUG, /**< debug log level */ +} mrp_log_level_t; + + +/** + * Logging masks. + */ +typedef enum { + MRP_LOG_MASK_ERROR = 0x01, /**< error logging mask */ + MRP_LOG_MASK_WARNING = 0x02, /**< warning logging mask */ + MRP_LOG_MASK_INFO = 0x04, /**< info logging mask */ + MRP_LOG_MASK_DEBUG = 0x08, /**< debug logging mask */ +} mrp_log_mask_t; + +#define MRP_LOG_MASK(level) (1 << ((level)-1)) /**< mask of level */ +#define MRP_LOG_UPTO(level) ((1 << (level+1))-1) /**< mask up to level */ + + +/** Parse a string of comma-separated log level names to a log mask. */ +mrp_log_mask_t mrp_log_parse_levels(const char *levels); + +/** Write the given log mask as a string to the given buffer. */ +const char *mrp_log_dump_mask(mrp_log_mask_t mask, char *buf, size_t size); + +/** Clear current logging level and enable levels in mask. */ +mrp_log_mask_t mrp_log_set_mask(mrp_log_mask_t mask); + +/** Enable logging for levels in mask. */ +mrp_log_mask_t mrp_log_enable(mrp_log_mask_t mask); + +/** Disable logging for levels in mask. */ +mrp_log_mask_t mrp_log_disable(mrp_log_mask_t mask); + +/** Get the current logging level mask. */ +#define mrp_log_get_mask() mrp_log_disable(0) + +/** + * Logging target names. + */ +#define MRP_LOG_NAME_STDOUT "stdout" +#define MRP_LOG_NAME_STDERR "stderr" +#define MRP_LOG_NAME_SYSLOG "syslog" + +/** + * Logging targets. + */ +#define MRP_LOG_TO_STDOUT "stdout" +#define MRP_LOG_TO_STDERR "stderr" +#define MRP_LOG_TO_SYSLOG "syslog" +#define MRP_LOG_TO_FILE(path) ((const char *)(path)) + + +/** Parse a log target name to MRP_LOG_TO_*. */ +const char *mrp_log_parse_target(const char *target); + +/** Set logging target. */ +int mrp_log_set_target(const char *target); + +/** Get the current log target. */ +const char *mrp_log_get_target(void); + +/** Get all available logging targets. */ +int mrp_log_get_targets(const char **targets, size_t size); + +/** Log an error. */ +#define mrp_log_error(fmt, args...) \ + mrp_log_msg(MRP_LOG_ERROR, __LOC__, fmt , ## args) + +/** Log a warning. */ +#define mrp_log_warning(fmt, args...) \ + mrp_log_msg(MRP_LOG_WARNING, __LOC__, fmt , ## args) + +/** Log an informational message. */ +#define mrp_log_info(fmt, args...) \ + mrp_log_msg(MRP_LOG_INFO, __LOC__, fmt , ## args) + +/** Generic logging function. */ +void mrp_log_msg(mrp_log_level_t level, + const char *file, int line, const char *func, + const char *format, ...) MRP_PRINTF_LIKE(5, 6); + +/** Generic logging function for easy wrapping. */ +void mrp_log_msgv(mrp_log_level_t level, const char *file, + int line, const char *func, const char *format, va_list ap); + +/** Type for custom logging functions. */ +typedef void (*mrp_logger_t)(void *user_data, + mrp_log_level_t level, const char *file, + int line, const char *func, const char *format, + va_list ap); + +/** Register a new logging target. */ +int mrp_log_register_target(const char *name, mrp_logger_t logger, + void *user_data); + +/** Unregister the given logging target. */ +int mrp_log_unregister_target(const char *name); + +MRP_CDECL_END + +#endif /* __MURPHY_LOG_H__ */ diff --git a/src/common/macros.h b/src/common/macros.h new file mode 100644 index 0000000..1fde94a --- /dev/null +++ b/src/common/macros.h @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_MACROS_H__ +#define __MURPHY_MACROS_H__ + +#include <stddef.h> + +#ifndef FALSE +# define FALSE 0 +# define TRUE (!FALSE) +#endif + +#ifdef __cplusplus +# define typeof(expr) decltype(expr) +#endif + +/** Align ptr to multiple of align. */ +#define MRP_ALIGN(ptr, align) (((ptr) + ((align)-1)) & ~((align)-1)) + +/** Get the offset of the given member in a struct/union of the given type. */ +#define MRP_OFFSET(type, member) ((ptrdiff_t)(&(((type *)0)->member))) + +/** Determine the dimension of the given array. */ +#define MRP_ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) + +#ifndef __GNUC__ +# define __FUNCTION__ __func__ +#endif + +#ifdef __GNUC__ + /** MAX that evalutes its arguments only once. */ +# define MRP_MAX(a, b) ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a > _b ? _a : _b; \ + }) + + /** MIN that evalutes its arguments only once. */ +# define MRP_MIN(a, b) ({ \ + typeof(a) _a = (a), _b = (b); \ + _a < _b ? _a : _b; \ + }) + + /** Likeliness branch-prediction hint for the compiler. */ +# define MRP_LIKELY(cond) __builtin_expect((cond), 1) + + /** Unlikeliness branch-prediction hint for the compiler. */ +# define MRP_UNLIKELY(cond) __builtin_expect((cond), 0) + + /** Prevent symbol from being exported (overriden by linker scripts). */ +# define MRP_HIDDEN __attribute__ ((visibility ("hidden"))) + + /** Request a symbol to be exported (overriden by linker scripts). */ +# define MRP_EXPORT __attribute__ ((visibility ("default"))) + + /** Ask the compiler to check for the presence of a NULL-sentinel. */ +# define MRP_NULLTERM __attribute__((sentinel)) + + /** Ask for printf-like format string checks of calls to this function. */ +# define MRP_PRINTF_LIKE(format_idx, first_arg_idx) \ + __attribute__ ((format (printf, format_idx, first_arg_idx))) + + /** Mark a function to be called before main is entered. */ +# define MRP_INIT __attribute__((constructor(65535))) +# define MRP_INIT_AT(prio) __attribute__ ((constructor(prio))) + + /** Mark a function to be called after main returns, or exit is called. */ +# define MRP_EXIT __attribute__ ((destructor(65535))) +# define MRP_EXIT_AT(prio) __attribute__ ((destructor(prio))) + +/** Mark a variable unused. */ +# define MRP_UNUSED(var) (void)var +#else /* ! __GNUC__ */ +# define MRP_LIKELY(cond) (cond) +# define MRP_UNLIKELY(cond) (cond) +# define MRP_HIDDEN +# define MRP_EXPORT +# define MRP_NULLTERM +# define MRP_PRINTF_LIKE(format_idx, first_arg_idx) +# define __FUNCTION__ __func__ +# define MRP_INIT +# define MRP_INIT_AT +# define MRP_EXIT +# define MRP_EXIT_AT +# define MRP_UNUSED(var) +#endif + +/** Macro that can be used to pass the location of its usage. */ +# define __LOC__ __FILE__, __LINE__, __FUNCTION__ + +/** Assertions. */ +#ifndef NDEBUG +# define MRP_ASSERT(expr, fmt, args...) do { \ + if (!(expr)) { \ + printf("assertion '%s' failed at %s@%s:%d: "fmt"\n", #expr, \ + __FUNCTION__, __FILE__, __LINE__, ## args); \ + abort(); \ + } \ + } while (0) +#else +# define MRP_ASSERT(expr, msg) do { } while (0) +#endif + +/** Create a version integer from a (major, minor, micro) tuple. */ +#define MRP_VERSION_INT(maj, min, mic) \ + ((((maj) & 0xff) << 16) | (((min) & 0xff) << 8) | ((mic) & 0xff)) + +/** Create a version string from a (const) (major, minor, micro) tuple. */ +#define MRP_VERSION_STRING(maj, min, mic) #maj"."#min"."#mic + +/** Extract major version from a version integer. */ +#define MRP_VERSION_MAJOR(ver) (((ver) >> 16) & 0xff) + +/** Extract minor version from a version integer. */ +#define MRP_VERSION_MINOR(ver) (((ver) >> 8) & 0xff) + +/** Extract micro version from a version integer. */ +#define MRP_VERSION_MICRO(ver) ((ver) & 0xff) + +/** Macro to stringify a macro argument. */ +#define MRP_STRINGIFY(arg) #arg + +/** C++-compatibility macros. */ +#ifdef __cplusplus +# define MRP_CDECL_BEGIN extern "C" { +# define MRP_CDECL_END } +#else +# define MRP_CDECL_BEGIN +# define MRP_CDECL_END +#endif + +#endif /* __MURPHY_MACROS_H__ */ + diff --git a/src/common/mainloop.c b/src/common/mainloop.c new file mode 100644 index 0000000..5702c15 --- /dev/null +++ b/src/common/mainloop.c @@ -0,0 +1,2625 @@ +/* + * Copyright (c) 2012-2014, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <unistd.h> +#include <errno.h> +#include <time.h> +#include <signal.h> +#include <limits.h> +#include <stdarg.h> +#include <sys/epoll.h> +#include <sys/signalfd.h> +#include <sys/socket.h> + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/log.h> +#include <murphy/common/list.h> +#include <murphy/common/hashtbl.h> +#include <murphy/common/json.h> +#include <murphy/common/msg.h> +#include <murphy/common/mainloop.h> + +#define USECS_PER_SEC (1000 * 1000) +#define USECS_PER_MSEC (1000) +#define NSECS_PER_USEC (1000) + +/* + * I/O watches + */ + +struct mrp_io_watch_s { + mrp_list_hook_t hook; /* to list of watches */ + mrp_list_hook_t deleted; /* to list of pending delete */ + int (*free)(void *ptr); /* cb to free memory */ + mrp_mainloop_t *ml; /* mainloop */ + int fd; /* file descriptor to watch */ + mrp_io_event_t events; /* events of interest */ + mrp_io_watch_cb_t cb; /* user callback */ + void *user_data; /* opaque user data */ + struct pollfd *pollfd; /* associated pollfd */ + mrp_list_hook_t slave; /* watches with the same fd */ + int wrhup; /* EPOLLHUPs delivered */ +}; + +#define is_master(w) !mrp_list_empty(&(w)->hook) +#define is_slave(w) !mrp_list_empty(&(w)->slave) + + +/* + * timers + */ + +struct mrp_timer_s { + mrp_list_hook_t hook; /* to list of timers */ + mrp_list_hook_t deleted; /* to list of pending delete */ + int (*free)(void *ptr); /* cb to free memory */ + mrp_mainloop_t *ml; /* mainloop */ + unsigned int msecs; /* timer interval */ + uint64_t expire; /* next expiration time */ + mrp_timer_cb_t cb; /* user callback */ + void *user_data; /* opaque user data */ +}; + + +/* + * deferred callbacks + */ + +struct mrp_deferred_s { + mrp_list_hook_t hook; /* to list of cbs */ + mrp_list_hook_t deleted; /* to list of pending delete */ + int (*free)(void *ptr); /* cb to free memory */ + mrp_mainloop_t *ml; /* mainloop */ + mrp_deferred_cb_t cb; /* user callback */ + void *user_data; /* opaque user data */ + int inactive : 1; +}; + + +/* + * signal handlers + */ + +struct mrp_sighandler_s { + mrp_list_hook_t hook; /* to list of handlers */ + mrp_list_hook_t deleted; /* to list of pending delete */ + int (*free)(void *ptr); /* cb to free memory */ + mrp_mainloop_t *ml; /* mainloop */ + int signum; /* signal number */ + mrp_sighandler_cb_t cb; /* user callback */ + void *user_data; /* opaque user data */ +}; + + +/* + * wakeup notifications + */ + +struct mrp_wakeup_s { + mrp_list_hook_t hook; /* to list of wakeup cbs */ + mrp_list_hook_t deleted; /* to list of pending delete */ + int (*free)(void *ptr); /* cb to free memory */ + mrp_mainloop_t *ml; /* mainloop */ + mrp_wakeup_event_t events; /* wakeup event mask */ + uint64_t lpf; /* wakeup at most this often */ + uint64_t next; /* next wakeup time */ + mrp_timer_t *timer; /* forced interval timer */ + mrp_wakeup_cb_t cb; /* user callback */ + void *user_data; /* opaque user data */ +}; + +#define mark_deleted(o) do { \ + (o)->cb = NULL; \ + mrp_list_append(&(o)->ml->deleted, &(o)->deleted); \ + } while (0) + +#define is_deleted(o) ((o)->cb == NULL) + + +/* + * any of the above data structures linked to the list of deleted items + * + * When deleted, the above data structures are first unlinked from their + * native list and linked to the special list of deleted entries. At an + * appropriate point upon every iteration of the main loop this list is + * checked and all entries are freed. This structure is used to get a + * pointer to the real structure that we need free. For this to work link + * hooks in all of the above structures need to be kept at the same offset + * as it is in deleted_t. + */ + +typedef struct { + mrp_list_hook_t hook; /* unfreed deleted items */ + mrp_list_hook_t deleted; /* to list of pending delete */ + int (*free)(void *ptr); /* cb to free memory */ +} deleted_t; + + +/* + * file descriptor table + * + * We do not want to associate direct pointers to related data structures + * with epoll. We might get delivered pending events for deleted fds (at + * least for unix domain sockets this seems to be the case) and with direct + * pointers we'd get delivered a dangling pointer together with the event. + * Instead we keep these structures in an fd table and use the fd to look + * up the associated data structure for events. We ignore events for which + * no data structure is found. In the fd table we keep a fixed size direct + * table for a small amount of fds (we expect to be using at most in the + * vast majority of cases) and we hash in the rest. + */ + +#define FDTBL_SIZE 64 + +typedef struct { + void *t[FDTBL_SIZE]; + mrp_htbl_t *h; +} fdtbl_t; + + +/* + * external mainloops + */ + +struct mrp_subloop_s { + mrp_list_hook_t hook; /* to list of subloops */ + mrp_list_hook_t deleted; /* to list of pending delete */ + int (*free)(void *ptr); /* cb to free memory */ + mrp_mainloop_t *ml; /* main loop */ + mrp_subloop_ops_t *cb; /* subloop glue callbacks */ + void *user_data; /* opaque subloop data */ + int epollfd; /* epollfd for this subloop */ + struct epoll_event *events; /* epoll event buffer */ + int nevent; /* epoll event buffer size */ + fdtbl_t *fdtbl; /* file descriptor table */ + mrp_io_watch_t *w; /* watch for epollfd */ + struct pollfd *pollfds; /* pollfds for this subloop */ + int npollfd; /* number of pollfds */ + int pending; /* pending events */ + int poll; /* need to poll for events */ +}; + + +/* + * event busses + */ + +struct mrp_event_bus_s { + char *name; /* bus name */ + mrp_list_hook_t hook; /* to list of busses */ + mrp_mainloop_t *ml; /* associated mainloop */ + mrp_list_hook_t watches; /* event watches on this bus */ + int busy; /* whether pumping events */ + int dead; +}; + + +/* + * event watches + */ + +struct mrp_event_watch_s { + mrp_list_hook_t hook; /* to list of event watches */ + mrp_event_bus_t *bus; /* associated event bus */ + mrp_event_mask_t mask; /* mask of watched events */ + mrp_event_watch_cb_t cb; /* notification callback */ + void *user_data; /* opaque user data */ + int dead : 1; /* marked for deletion */ +}; + + +/* + * pending events + */ + +typedef struct { + mrp_list_hook_t hook; /* to event queue */ + mrp_event_bus_t *bus; /* bus for this event */ + uint32_t id; /* event id */ + int format; /* attached data format */ + void *data; /* attached data */ +} pending_event_t; + + +/* + * main loop + */ + +struct mrp_mainloop_s { + int epollfd; /* our epoll descriptor */ + struct epoll_event *events; /* epoll event buffer */ + int nevent; /* epoll event buffer size */ + fdtbl_t *fdtbl; /* file descriptor table */ + + mrp_list_hook_t iowatches; /* list of I/O watches */ + int niowatch; /* number of I/O watches */ + mrp_io_event_t iomode; /* default event trigger mode */ + + mrp_list_hook_t timers; /* list of timers */ + mrp_timer_t *next_timer; /* next expiring timer */ + + mrp_list_hook_t deferred; /* list of deferred cbs */ + mrp_list_hook_t inactive_deferred; /* inactive defferred cbs */ + + mrp_list_hook_t wakeups; /* list of wakeup cbs */ + + int poll_timeout; /* next poll timeout */ + int poll_result; /* return value from poll */ + + int sigfd; /* signal polling fd */ + sigset_t sigmask; /* signal mask */ + mrp_io_watch_t *sigwatch; /* sigfd I/O watch */ + mrp_list_hook_t sighandlers; /* signal handlers */ + + mrp_list_hook_t subloops; /* external main loops */ + + mrp_list_hook_t deleted; /* unfreed deleted items */ + int quit; /* TRUE if _quit called */ + int exit_code; /* returned from _run */ + + mrp_superloop_ops_t *super_ops; /* superloop options */ + void *super_data; /* superloop glue data */ + void *iow; /* superloop epollfd watch */ + void *timer; /* superloop timer */ + void *work; /* superloop deferred work */ + + mrp_list_hook_t busses; /* known event busses */ + mrp_list_hook_t eventq; /* pending events */ + mrp_deferred_t *eventd; /* deferred event pump cb */ +}; + + +static mrp_event_def_t *events; /* registered events */ +static int nevent; /* number of events */ +static MRP_LIST_HOOK (ewatches); /* global, synchronous 'bus' */ + + +static void dump_pollfds(const char *prefix, struct pollfd *fds, int nfd); +static void adjust_superloop_timer(mrp_mainloop_t *ml); +static size_t poll_events(void *id, mrp_mainloop_t *ml, void **bufp); +static void pump_events(mrp_deferred_t *d, void *user_data); + +/* + * fd table manipulation + */ + +static int fd_cmp(const void *key1, const void *key2) +{ + return key2 - key1; +} + + +static uint32_t fd_hash(const void *key) +{ + uint32_t h; + + h = (uint32_t)(ptrdiff_t)key; + + return h; +} + + + +static fdtbl_t *fdtbl_create(void) +{ + fdtbl_t *ft; + mrp_htbl_config_t hcfg; + + if ((ft = mrp_allocz(sizeof(*ft))) != NULL) { + mrp_clear(&hcfg); + + hcfg.comp = fd_cmp; + hcfg.hash = fd_hash; + hcfg.free = NULL; + hcfg.nbucket = 16; + + ft->h = mrp_htbl_create(&hcfg); + + if (ft->h != NULL) + return ft; + else + mrp_free(ft); + } + + return NULL; +} + + +static void fdtbl_destroy(fdtbl_t *ft) +{ + if (ft != NULL) { + mrp_htbl_destroy(ft->h, FALSE); + mrp_free(ft); + } +} + + +static void *fdtbl_lookup(fdtbl_t *ft, int fd) +{ + if (fd >= 0 && ft != NULL) { + if (fd < FDTBL_SIZE) + return ft->t[fd]; + else + return mrp_htbl_lookup(ft->h, (void *)(ptrdiff_t)fd); + } + + return NULL; +} + + +static int fdtbl_insert(fdtbl_t *ft, int fd, void *ptr) +{ + if (fd >= 0 && ft != NULL) { + if (fd < FDTBL_SIZE) { + if (ft->t[fd] == NULL) { + ft->t[fd] = ptr; + return 0; + } + else + errno = EEXIST; + } + else { + if (mrp_htbl_insert(ft->h, (void *)(ptrdiff_t)fd, ptr)) + return 0; + else + errno = EEXIST; + } + } + else + errno = EINVAL; + + return -1; +} + + +static void fdtbl_remove(fdtbl_t *ft, int fd) +{ + if (fd >= 0 && ft != NULL) { + if (fd < FDTBL_SIZE) + ft->t[fd] = NULL; + else + mrp_htbl_remove(ft->h, (void *)(ptrdiff_t)fd, FALSE); + } +} + + +/* + * I/O watches + */ + +static uint32_t epoll_event_mask(mrp_io_watch_t *master, mrp_io_watch_t *ignore) +{ + mrp_io_watch_t *w; + mrp_list_hook_t *p, *n; + uint32_t mask; + + mask = (master != ignore ? + master->events : master->events & MRP_IO_TRIGGER_EDGE); + + mrp_list_foreach(&master->slave, p, n) { + w = mrp_list_entry(p, typeof(*w), slave); + + if (w != ignore) + mask |= w->events; + } + + mrp_debug("epoll event mask for I/O watch %p: %d", master, mask); + + return mask; +} + + +static int epoll_add_slave(mrp_io_watch_t *master, mrp_io_watch_t *slave) +{ + mrp_mainloop_t *ml = master->ml; + struct epoll_event evt; + + evt.events = epoll_event_mask(master, NULL) | slave->events; + evt.data.u64 = 0; + evt.data.fd = master->fd; + + if (epoll_ctl(ml->epollfd, EPOLL_CTL_MOD, master->fd, &evt) == 0) { + mrp_list_append(&master->slave, &slave->slave); + + return 0; + } + + return -1; +} + + +static int epoll_add(mrp_io_watch_t *w) +{ + mrp_mainloop_t *ml = w->ml; + mrp_io_watch_t *master; + struct epoll_event evt; + + if (fdtbl_insert(ml->fdtbl, w->fd, w) == 0) { + evt.events = w->events; + evt.data.u64 = 0; /* init full union for valgrind... */ + evt.data.fd = w->fd; + + if (epoll_ctl(ml->epollfd, EPOLL_CTL_ADD, w->fd, &evt) == 0) { + mrp_list_append(&ml->iowatches, &w->hook); + ml->niowatch++; + + return 0; + } + else + fdtbl_remove(ml->fdtbl, w->fd); + } + else { + if (errno == EEXIST) { + master = fdtbl_lookup(ml->fdtbl, w->fd); + + if (master != NULL) + return epoll_add_slave(master, w); + } + } + + return -1; +} + + +static int epoll_del(mrp_io_watch_t *w) +{ + mrp_mainloop_t *ml = w->ml; + mrp_io_watch_t *master; + struct epoll_event evt; + int status; + + if (is_master(w)) + master = w; + else + master = fdtbl_lookup(ml->fdtbl, w->fd); + + if (master != NULL) { + evt.events = epoll_event_mask(master, w); + evt.data.u64 = 0; /* init full union for valgrind... */ + evt.data.fd = w->fd; + + if ((evt.events & MRP_IO_EVENT_ALL) == 0) { + fdtbl_remove(ml->fdtbl, w->fd); + status = epoll_ctl(ml->epollfd, EPOLL_CTL_DEL, w->fd, &evt); + + if (status == 0 || (errno == EBADF || errno == ENOENT)) + ml->niowatch--; + } + else + status = epoll_ctl(ml->epollfd, EPOLL_CTL_MOD, w->fd, &evt); + + if (status == 0 || (errno == EBADF || errno == ENOENT)) + return 0; + else + mrp_log_error("Failed to update epoll for deleted I/O watch %p " + "(fd %d, %d: %s).", w, w->fd, errno, strerror(errno)); + } + else { + mrp_log_error("Failed to find master for deleted I/O watch %p " + "(fd %d).", w, w->fd); + errno = EINVAL; + } + + return -1; +} + + +static int free_io_watch(void *ptr) +{ + mrp_io_watch_t *w = (mrp_io_watch_t *)ptr; + mrp_mainloop_t *ml = w->ml; + mrp_io_watch_t *master; + + master = fdtbl_lookup(ml->fdtbl, w->fd); + + if (master == w) { + fdtbl_remove(ml->fdtbl, w->fd); + + if (!mrp_list_empty(&w->slave)) { + /* relink first slave as new master to mainloop */ + master = mrp_list_entry(w->slave.next, typeof(*master), slave); + mrp_list_append(&ml->iowatches, &master->hook); + + fdtbl_insert(ml->fdtbl, master->fd, master); + } + } + + mrp_list_delete(&w->slave); + mrp_free(w); + + return TRUE; +} + + +mrp_io_watch_t *mrp_add_io_watch(mrp_mainloop_t *ml, int fd, + mrp_io_event_t events, + mrp_io_watch_cb_t cb, void *user_data) +{ + mrp_io_watch_t *w; + + if (fd < 0 || cb == NULL) + return NULL; + + if ((w = mrp_allocz(sizeof(*w))) != NULL) { + mrp_list_init(&w->hook); + mrp_list_init(&w->deleted); + mrp_list_init(&w->slave); + w->ml = ml; + w->fd = fd; + w->events = events & MRP_IO_EVENT_ALL; + + switch (events & MRP_IO_TRIGGER_MASK) { + case 0: + if (ml->iomode == MRP_IO_TRIGGER_EDGE) + w->events |= MRP_IO_TRIGGER_EDGE; + break; + case MRP_IO_TRIGGER_EDGE: + w->events |= MRP_IO_TRIGGER_EDGE; + break; + case MRP_IO_TRIGGER_LEVEL: + break; + default: + mrp_log_warning("Invalid I/O event trigger mode 0x%x.", + events & MRP_IO_TRIGGER_MASK); + break; + } + + w->cb = cb; + w->user_data = user_data; + w->free = free_io_watch; + + if (epoll_add(w) != 0) { + mrp_free(w); + w = NULL; + } + else + mrp_debug("added I/O watch %p (fd %d, events 0x%x)", w, w->fd, w->events); + } + + return w; +} + + +void mrp_del_io_watch(mrp_io_watch_t *w) +{ + /* + * Notes: It is not safe to free the watch here as there might be + * a delivered but unprocessed epoll event with a pointer + * to the watch. We just mark it deleted and take care of + * the actual deletion in the dispatching loop. + */ + + if (w != NULL && !is_deleted(w)) { + mrp_debug("marking I/O watch %p (fd %d) deleted", w, w->fd); + + mark_deleted(w); + w->events = 0; + + epoll_del(w); + } +} + + +mrp_mainloop_t *mrp_get_io_watch_mainloop(mrp_io_watch_t *w) +{ + return w ? w->ml : NULL; +} + + +int mrp_set_io_event_mode(mrp_mainloop_t *ml, mrp_io_event_t mode) +{ + if (mode == MRP_IO_TRIGGER_LEVEL || mode == MRP_IO_TRIGGER_EDGE) { + ml->iomode = mode; + return TRUE; + } + else { + mrp_log_error("Invalid I/O event mode 0x%x.", mode); + return FALSE; + } +} + + +mrp_io_event_t mrp_get_io_event_mode(mrp_mainloop_t *ml) +{ + return ml->iomode ? ml->iomode : MRP_IO_TRIGGER_LEVEL; +} + + +/* + * timers + */ + +static uint64_t time_now(void) +{ + struct timespec ts; + uint64_t now; + + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ts.tv_sec * USECS_PER_SEC; + now += ts.tv_nsec / NSECS_PER_USEC; + + return now; +} + + +static inline int usecs_to_msecs(uint64_t usecs) +{ + int msecs; + + msecs = (usecs + USECS_PER_MSEC - 1) / USECS_PER_MSEC; + + return msecs; +} + + +static void insert_timer(mrp_timer_t *t) +{ + mrp_mainloop_t *ml = t->ml; + mrp_list_hook_t *p, *n; + mrp_timer_t *t1, *next; + int inserted; + + /* + * Notes: + * If there is ever a need to run a large number of + * simultaneous timers, we need to change this to a + * self-balancing data structure, eg. an red-black tree. + */ + + inserted = FALSE; + next = NULL; + mrp_list_foreach(&ml->timers, p, n) { + t1 = mrp_list_entry(p, mrp_timer_t, hook); + + if (!is_deleted(t1)) { + if (t->expire <= t1->expire) { + mrp_list_prepend(p->prev, &t->hook); + inserted = TRUE; + break; + } + if (next == NULL) + next = t1; + } + } + + if (!inserted) + mrp_list_append(&ml->timers, &t->hook); + + if (next) + ml->next_timer = next; + else { + ml->next_timer = t; + adjust_superloop_timer(ml); + } +} + + +static inline void rearm_timer(mrp_timer_t *t) +{ + mrp_list_delete(&t->hook); + t->expire = time_now() + t->msecs * USECS_PER_MSEC; + insert_timer(t); +} + + +static mrp_timer_t *find_next_timer(mrp_mainloop_t *ml) +{ + mrp_list_hook_t *p, *n; + mrp_timer_t *t = NULL; + + mrp_list_foreach(&ml->timers, p, n) { + t = mrp_list_entry(p, typeof(*t), hook); + + if (!is_deleted(t)) + break; + else + t = NULL; + } + + ml->next_timer = t; + return t; +} + + +static int free_timer(void *ptr) +{ + mrp_timer_t *t = (mrp_timer_t *)ptr; + + mrp_free(t); + + return TRUE; +} + + + +mrp_timer_t *mrp_add_timer(mrp_mainloop_t *ml, unsigned int msecs, + mrp_timer_cb_t cb, void *user_data) +{ + mrp_timer_t *t; + + if (cb == NULL) + return NULL; + + if ((t = mrp_allocz(sizeof(*t))) != NULL) { + mrp_list_init(&t->hook); + mrp_list_init(&t->deleted); + t->ml = ml; + t->expire = time_now() + msecs * USECS_PER_MSEC; + t->msecs = msecs; + t->cb = cb; + t->user_data = user_data; + t->free = free_timer; + + insert_timer(t); + } + + return t; +} + + +void mrp_mod_timer(mrp_timer_t *t, unsigned int msecs) +{ + if (t != NULL && !is_deleted(t)) { + if (msecs != MRP_TIMER_RESTART) + t->msecs = msecs; + + rearm_timer(t); + } +} + + +void mrp_del_timer(mrp_timer_t *t) +{ + /* + * Notes: It is not safe to simply free this entry here as we might + * be dispatching with this entry being the next to process. + * We check for this and if it is not the case we relink this + * to the list of deleted items which will be then processed + * at end of the mainloop iteration. Otherwise we only mark the + * this entry for deletion and the rest will be taken care of in + * dispatch_timers(). + */ + + if (t != NULL && !is_deleted(t)) { + mrp_debug("marking timer %p deleted", t); + + mark_deleted(t); + + if (t->ml->next_timer == t) { + find_next_timer(t->ml); + adjust_superloop_timer(t->ml); + } + } +} + + +mrp_mainloop_t *mrp_get_timer_mainloop(mrp_timer_t *t) +{ + return t ? t->ml : NULL; +} + + +/* + * deferred/idle callbacks + */ + +mrp_deferred_t *mrp_add_deferred(mrp_mainloop_t *ml, mrp_deferred_cb_t cb, + void *user_data) +{ + mrp_deferred_t *d; + + if (cb == NULL) + return NULL; + + if ((d = mrp_allocz(sizeof(*d))) != NULL) { + mrp_list_init(&d->hook); + mrp_list_init(&d->deleted); + d->ml = ml; + d->cb = cb; + d->user_data = user_data; + + mrp_list_append(&ml->deferred, &d->hook); + adjust_superloop_timer(ml); + } + + return d; +} + + +void mrp_del_deferred(mrp_deferred_t *d) +{ + /* + * Notes: It is not safe to simply free this entry here as we might + * be dispatching with this entry being the next to process. + * We just mark this here deleted and take care of the rest + * in the dispatching loop. + */ + + if (d != NULL && !is_deleted(d)) { + mrp_debug("marking deferred %p deleted", d); + mark_deleted(d); + } +} + + +void mrp_disable_deferred(mrp_deferred_t *d) +{ + if (d != NULL) + d->inactive = TRUE; +} + + +static inline void disable_deferred(mrp_deferred_t *d) +{ + if (MRP_LIKELY(d->inactive)) { + mrp_list_delete(&d->hook); + mrp_list_append(&d->ml->inactive_deferred, &d->hook); + } + +} + + +void mrp_enable_deferred(mrp_deferred_t *d) +{ + if (d != NULL) { + if (!is_deleted(d)) { + d->inactive = FALSE; + mrp_list_delete(&d->hook); + mrp_list_append(&d->ml->deferred, &d->hook); + } + } +} + + +mrp_mainloop_t *mrp_get_deferred_mainloop(mrp_deferred_t *d) +{ + return d ? d->ml : NULL; +} + + +/* + * signal notifications + */ + +static void dispatch_signals(mrp_io_watch_t *w, int fd, + mrp_io_event_t events, void *user_data) +{ + mrp_mainloop_t *ml = mrp_get_io_watch_mainloop(w); + struct signalfd_siginfo sig; + mrp_list_hook_t *p, *n; + mrp_sighandler_t *h; + int signum; + + MRP_UNUSED(events); + MRP_UNUSED(user_data); + + while (read(fd, &sig, sizeof(sig)) > 0) { + signum = sig.ssi_signo; + + mrp_list_foreach(&ml->sighandlers, p, n) { + h = mrp_list_entry(p, typeof(*h), hook); + + if (!is_deleted(h)) { + if (h->signum == signum) + h->cb(h, signum, h->user_data); + } + } + } +} + + +static int setup_sighandlers(mrp_mainloop_t *ml) +{ + if (ml->sigfd == -1) { + sigemptyset(&ml->sigmask); + + ml->sigfd = signalfd(-1, &ml->sigmask, SFD_NONBLOCK | SFD_CLOEXEC); + + if (ml->sigfd == -1) + return FALSE; + + ml->sigwatch = mrp_add_io_watch(ml, ml->sigfd, MRP_IO_EVENT_IN, + dispatch_signals, NULL); + + if (ml->sigwatch == NULL) { + close(ml->sigfd); + return FALSE; + } + } + + return TRUE; +} + + +mrp_sighandler_t *mrp_add_sighandler(mrp_mainloop_t *ml, int signum, + mrp_sighandler_cb_t cb, void *user_data) +{ + mrp_sighandler_t *s; + + if (cb == NULL || ml->sigfd == -1) + return NULL; + + if ((s = mrp_allocz(sizeof(*s))) != NULL) { + mrp_list_init(&s->hook); + mrp_list_init(&s->deleted); + s->ml = ml; + s->signum = signum; + s->cb = cb; + s->user_data = user_data; + + mrp_list_append(&ml->sighandlers, &s->hook); + sigaddset(&ml->sigmask, s->signum); + signalfd(ml->sigfd, &ml->sigmask, SFD_NONBLOCK|SFD_CLOEXEC); + sigprocmask(SIG_BLOCK, &ml->sigmask, NULL); + } + + return s; +} + + +static void recalc_sigmask(mrp_mainloop_t *ml) +{ + mrp_list_hook_t *p, *n; + mrp_sighandler_t *h; + + sigprocmask(SIG_UNBLOCK, &ml->sigmask, NULL); + sigemptyset(&ml->sigmask); + + mrp_list_foreach(&ml->sighandlers, p, n) { + h = mrp_list_entry(p, typeof(*h), hook); + if (!is_deleted(h)) + sigaddset(&ml->sigmask, h->signum); + } + + sigprocmask(SIG_BLOCK, &ml->sigmask, NULL); +} + + +void mrp_del_sighandler(mrp_sighandler_t *h) +{ + if (h != NULL && !is_deleted(h)) { + mrp_debug("marking sighandler %p deleted", h); + + mark_deleted(h); + recalc_sigmask(h->ml); + } +} + + +mrp_mainloop_t *mrp_get_sighandler_mainloop(mrp_sighandler_t *h) +{ + return h ? h->ml : NULL; +} + + +/* + * wakeup notifications + */ + +static void wakeup_cb(mrp_wakeup_t *w, mrp_wakeup_event_t event, uint64_t now) +{ + if (w->next > now) { + mrp_debug("skipping wakeup %p because of low-pass filter", w); + return; + } + + w->cb(w, event, w->user_data); + + if (w->lpf != MRP_WAKEUP_NOLIMIT) + w->next = now + w->lpf; + + if (w->timer != NULL) + mrp_mod_timer(w->timer, MRP_TIMER_RESTART); +} + + +static void forced_wakeup_cb(mrp_timer_t *t, void *user_data) +{ + mrp_wakeup_t *w = (mrp_wakeup_t *)user_data; + + MRP_UNUSED(t); + + if (is_deleted(w)) + return; + + mrp_debug("dispatching forced wakeup cb %p", w); + + wakeup_cb(w, MRP_WAKEUP_EVENT_LIMIT, time_now()); +} + + +mrp_wakeup_t *mrp_add_wakeup(mrp_mainloop_t *ml, mrp_wakeup_event_t events, + unsigned int lpf_msecs, unsigned int force_msecs, + mrp_wakeup_cb_t cb, void *user_data) +{ + mrp_wakeup_t *w; + + if (cb == NULL) + return NULL; + + if (lpf_msecs > force_msecs && force_msecs != MRP_WAKEUP_NOLIMIT) + return NULL; + + if ((w = mrp_allocz(sizeof(*w))) != NULL) { + mrp_list_init(&w->hook); + mrp_list_init(&w->deleted); + w->ml = ml; + w->events = events; + w->cb = cb; + w->user_data = user_data; + + w->lpf = lpf_msecs * USECS_PER_MSEC; + + if (lpf_msecs != MRP_WAKEUP_NOLIMIT) + w->next = time_now() + w->lpf; + + if (force_msecs != MRP_WAKEUP_NOLIMIT) { + w->timer = mrp_add_timer(ml, force_msecs, forced_wakeup_cb, w); + + if (w->timer == NULL) { + mrp_free(w); + return NULL; + } + } + + mrp_list_append(&ml->wakeups, &w->hook); + } + + return w; +} + + +void mrp_del_wakeup(mrp_wakeup_t *w) +{ + /* + * Notes: It is not safe to simply free this entry here as we might + * be dispatching with this entry being the next to process. + * We just mark this here deleted and take care of the rest + * in the dispatching loop. + */ + + if (w != NULL && !is_deleted(w)) { + mrp_debug("marking wakeup %p deleted", w); + mark_deleted(w); + } +} + + +mrp_mainloop_t *mrp_get_wakeup_mainloop(mrp_wakeup_t *w) +{ + return w ? w->ml : NULL; +} + + +/* + * external mainloops we pump + */ + +static int free_subloop(void *ptr) +{ + mrp_subloop_t *sl = (mrp_subloop_t *)ptr; + + mrp_debug("freeing subloop %p", sl); + + mrp_free(sl->pollfds); + mrp_free(sl->events); + mrp_free(sl); + + return TRUE; +} + + +static void subloop_event_cb(mrp_io_watch_t *w, int fd, mrp_io_event_t events, + void *user_data) +{ + mrp_subloop_t *sl = (mrp_subloop_t *)user_data; + + MRP_UNUSED(w); + MRP_UNUSED(fd); + MRP_UNUSED(events); + + mrp_debug("subloop %p has events, setting poll to TRUE", sl); + + sl->poll = TRUE; +} + + +mrp_subloop_t *mrp_add_subloop(mrp_mainloop_t *ml, mrp_subloop_ops_t *ops, + void *user_data) +{ + mrp_subloop_t *sl; + + if (ops == NULL || user_data == NULL) + return NULL; + + if ((sl = mrp_allocz(sizeof(*sl))) != NULL) { + mrp_list_init(&sl->hook); + mrp_list_init(&sl->deleted); + sl->free = free_subloop; + sl->ml = ml; + sl->cb = ops; + sl->user_data = user_data; + sl->epollfd = epoll_create1(EPOLL_CLOEXEC); + sl->fdtbl = fdtbl_create(); + + if (sl->epollfd >= 0 && sl->fdtbl != NULL) { + sl->w = mrp_add_io_watch(ml, sl->epollfd, MRP_IO_EVENT_IN, + subloop_event_cb, sl); + + if (sl->w != NULL) + mrp_list_append(&ml->subloops, &sl->hook); + else + goto fail; + } + else { + fail: + close(sl->epollfd); + fdtbl_destroy(sl->fdtbl); + mrp_free(sl); + sl = NULL; + } + } + + return sl; +} + + +void mrp_del_subloop(mrp_subloop_t *sl) +{ + struct epoll_event dummy; + int i; + + /* + * Notes: It is not safe to free the loop here as there might be + * a delivered but unprocessed epoll event with a pointers + * to the loops pollfds. However, since we do not dispatch + * loops by traversing the list of loops, it is safe to relink + * it to the list of data structures to be deleted at the + * end of the next main loop iteration. So we just remove the + * pollfds from epoll, mark this as deleted and relink it. + */ + + if (sl != NULL && !is_deleted(sl)) { + mrp_debug("deactivating and marking subloop %p deleted", sl); + + mrp_del_io_watch(sl->w); + + /* XXX TODO: Why ? close(sl->epollfd) should be enough... */ + for (i = 0; i < sl->npollfd; i++) + epoll_ctl(sl->epollfd, EPOLL_CTL_DEL, sl->pollfds[i].fd, &dummy); + + close(sl->epollfd); + sl->epollfd = -1; + fdtbl_destroy(sl->fdtbl); + sl->fdtbl = NULL; + + mark_deleted(sl); + } +} + + +/* + * external mainloop that pumps us + */ + + +static void super_io_cb(void *super_data, void *id, int fd, + mrp_io_event_t events, void *user_data) +{ + mrp_mainloop_t *ml = (mrp_mainloop_t *)user_data; + mrp_superloop_ops_t *ops = ml->super_ops; + + MRP_UNUSED(super_data); + MRP_UNUSED(id); + MRP_UNUSED(fd); + MRP_UNUSED(events); + + ops->mod_defer(ml->super_data, ml->work, TRUE); +} + + +static void super_timer_cb(void *super_data, void *id, void *user_data) +{ + mrp_mainloop_t *ml = (mrp_mainloop_t *)user_data; + mrp_superloop_ops_t *ops = ml->super_ops; + + MRP_UNUSED(super_data); + MRP_UNUSED(id); + + ops->mod_defer(ml->super_data, ml->work, TRUE); +} + + +static void super_work_cb(void *super_data, void *id, void *user_data) +{ + mrp_mainloop_t *ml = (mrp_mainloop_t *)user_data; + mrp_superloop_ops_t *ops = ml->super_ops; + unsigned int timeout; + + MRP_UNUSED(super_data); + MRP_UNUSED(id); + + mrp_mainloop_poll(ml, FALSE); + mrp_mainloop_dispatch(ml); + + if (!ml->quit) { + mrp_mainloop_prepare(ml); + + /* + * Notes: + * + * Some mainloop abstractions (eg. the one in PulseAudio) + * have deferred callbacks that starve all other event + * processing until no more deferred callbacks are pending. + * For this reason, we cannot map our deferred callbacks + * directly to superloop deferred callbacks (in some cases + * this could starve the superloop indefinitely). Hence, if + * we have enabled deferred callbacks, we arm our timer with + * 0 timeout to let the superloop do one round of its event + * processing. + */ + + timeout = mrp_list_empty(&ml->deferred) ? ml->poll_timeout : 0; + ops->mod_timer(ml->super_data, ml->timer, timeout); + ops->mod_defer(ml->super_data, ml->work, FALSE); + } + else { + ops->del_io(ml->super_data, ml->iow); + ops->del_timer(ml->super_data, ml->timer); + ops->del_defer(ml->super_data, ml->work); + + ml->iow = NULL; + ml->timer = NULL; + ml->work = NULL; + } +} + + +static void adjust_superloop_timer(mrp_mainloop_t *ml) +{ + mrp_superloop_ops_t *ops = ml->super_ops; + unsigned int timeout; + + if (ops == NULL) + return; + + mrp_mainloop_prepare(ml); + timeout = mrp_list_empty(&ml->deferred) ? ml->poll_timeout : 0; + ops->mod_timer(ml->super_data, ml->timer, timeout); +} + + +int mrp_set_superloop(mrp_mainloop_t *ml, mrp_superloop_ops_t *ops, + void *loop_data) +{ + mrp_io_event_t events; + int timeout; + + if (ml->super_ops == NULL) { + if (ops->poll_io != NULL) + ops->poll_events = poll_events; + + ml->super_ops = ops; + ml->super_data = loop_data; + + mrp_mainloop_prepare(ml); + + events = MRP_IO_EVENT_IN | MRP_IO_EVENT_OUT | MRP_IO_EVENT_HUP; + ml->iow = ops->add_io(ml->super_data, ml->epollfd, events, + super_io_cb, ml); + ml->work = ops->add_defer(ml->super_data, super_work_cb, ml); + + /* + * Notes: + * + * Some mainloop abstractions (eg. the one in PulseAudio) + * have deferred callbacks that starve all other event + * processing until no more deferred callbacks are pending. + * For this reason, we cannot map our deferred callbacks + * directly to superloop deferred callbacks (in some cases + * this could starve the superloop indefinitely). Hence, if + * we have enabled deferred callbacks, we arm our timer with + * 0 timeout to let the superloop do one round of its event + * processing. + */ + + timeout = mrp_list_empty(&ml->deferred) ? ml->poll_timeout : 0; + ml->timer = ops->add_timer(ml->super_data, timeout, super_timer_cb, ml); + + if (ml->iow != NULL && ml->timer != NULL && ml->work != NULL) + return TRUE; + else + mrp_clear_superloop(ml); + } + + return FALSE; +} + + +int mrp_clear_superloop(mrp_mainloop_t *ml) +{ + mrp_superloop_ops_t *ops = ml->super_ops; + void *data = ml->super_data; + + if (ops != NULL) { + if (ml->iow != NULL) { + ops->del_io(data, ml->iow); + ml->iow = NULL; + } + + if (ml->work != NULL) { + ops->del_defer(data, ml->work); + ml->work = NULL; + } + + if (ml->timer != NULL) { + ops->del_timer(data, ml->timer); + ml->timer = NULL; + } + + ml->super_ops = NULL; + ml->super_data = NULL; + + ops->unregister(data); + + return TRUE; + } + else + return FALSE; +} + + +int mrp_mainloop_unregister(mrp_mainloop_t *ml) +{ + return mrp_clear_superloop(ml); +} + + +/* + * mainloop + */ + +static void purge_io_watches(mrp_mainloop_t *ml) +{ + mrp_list_hook_t *p, *n, *sp, *sn; + mrp_io_watch_t *w, *s; + + mrp_list_foreach(&ml->iowatches, p, n) { + w = mrp_list_entry(p, typeof(*w), hook); + mrp_list_delete(&w->hook); + mrp_list_delete(&w->deleted); + + mrp_list_foreach(&w->slave, sp, sn) { + s = mrp_list_entry(sp, typeof(*s), slave); + mrp_list_delete(&s->slave); + mrp_free(s); + } + + mrp_free(w); + } +} + + +static void purge_timers(mrp_mainloop_t *ml) +{ + mrp_list_hook_t *p, *n; + mrp_timer_t *t; + + mrp_list_foreach(&ml->timers, p, n) { + t = mrp_list_entry(p, typeof(*t), hook); + mrp_list_delete(&t->hook); + mrp_list_delete(&t->deleted); + mrp_free(t); + } +} + + +static void purge_deferred(mrp_mainloop_t *ml) +{ + mrp_list_hook_t *p, *n; + mrp_deferred_t *d; + + mrp_list_foreach(&ml->deferred, p, n) { + d = mrp_list_entry(p, typeof(*d), hook); + mrp_list_delete(&d->hook); + mrp_list_delete(&d->deleted); + mrp_free(d); + } + + mrp_list_foreach(&ml->inactive_deferred, p, n) { + d = mrp_list_entry(p, typeof(*d), hook); + mrp_list_delete(&d->hook); + mrp_list_delete(&d->deleted); + mrp_free(d); + } +} + + +static void purge_sighandlers(mrp_mainloop_t *ml) +{ + mrp_list_hook_t *p, *n; + mrp_sighandler_t *s; + + mrp_list_foreach(&ml->sighandlers, p, n) { + s = mrp_list_entry(p, typeof(*s), hook); + mrp_list_delete(&s->hook); + mrp_list_delete(&s->deleted); + mrp_free(s); + } +} + + +static void purge_wakeups(mrp_mainloop_t *ml) +{ + mrp_list_hook_t *p, *n; + mrp_wakeup_t *w; + + mrp_list_foreach(&ml->wakeups, p, n) { + w = mrp_list_entry(p, typeof(*w), hook); + mrp_list_delete(&w->hook); + mrp_list_delete(&w->deleted); + mrp_free(w); + } +} + + +static void purge_deleted(mrp_mainloop_t *ml) +{ + mrp_list_hook_t *p, *n; + deleted_t *d; + + mrp_list_foreach(&ml->deleted, p, n) { + d = mrp_list_entry(p, typeof(*d), deleted); + mrp_list_delete(&d->deleted); + mrp_list_delete(&d->hook); + if (d->free == NULL) { + mrp_debug("purging deleted object %p", d); + mrp_free(d); + } + else { + mrp_debug("purging deleted object %p (free cb: %p)", d, d->free); + if (!d->free(d)) { + mrp_log_error("Failed to free purged item %p.", d); + mrp_list_prepend(p, &d->deleted); + } + } + } +} + + +static void purge_subloops(mrp_mainloop_t *ml) +{ + mrp_list_hook_t *p, *n; + mrp_subloop_t *sl; + + mrp_list_foreach(&ml->subloops, p, n) { + sl = mrp_list_entry(p, typeof(*sl), hook); + mrp_list_delete(&sl->hook); + mrp_list_delete(&sl->deleted); + free_subloop(sl); + } +} + + +mrp_mainloop_t *mrp_mainloop_create(void) +{ + mrp_mainloop_t *ml; + + if ((ml = mrp_allocz(sizeof(*ml))) != NULL) { + ml->epollfd = epoll_create1(EPOLL_CLOEXEC); + ml->sigfd = -1; + ml->fdtbl = fdtbl_create(); + + if (ml->epollfd >= 0 && ml->fdtbl != NULL) { + mrp_list_init(&ml->iowatches); + mrp_list_init(&ml->timers); + mrp_list_init(&ml->deferred); + mrp_list_init(&ml->inactive_deferred); + mrp_list_init(&ml->sighandlers); + mrp_list_init(&ml->wakeups); + mrp_list_init(&ml->deleted); + mrp_list_init(&ml->subloops); + mrp_list_init(&ml->busses); + mrp_list_init(&ml->eventq); + + ml->eventd = mrp_add_deferred(ml, pump_events, ml); + if (ml->eventd == NULL) + goto fail; + mrp_disable_deferred(ml->eventd); + + if (!setup_sighandlers(ml)) + goto fail; + } + else { + fail: + close(ml->epollfd); + fdtbl_destroy(ml->fdtbl); + mrp_free(ml); + ml = NULL; + } + } + + + + return ml; +} + + +void mrp_mainloop_destroy(mrp_mainloop_t *ml) +{ + if (ml != NULL) { + mrp_clear_superloop(ml); + purge_io_watches(ml); + purge_timers(ml); + purge_deferred(ml); + purge_sighandlers(ml); + purge_wakeups(ml); + purge_subloops(ml); + purge_deleted(ml); + + close(ml->sigfd); + close(ml->epollfd); + fdtbl_destroy(ml->fdtbl); + + mrp_free(ml->events); + mrp_free(ml); + } +} + + +static int prepare_subloop(mrp_subloop_t *sl) +{ + /* + * Notes: + * + * If we have a relatively large number of file descriptors to + * poll but typically only a small fraction of them has pending + * events per mainloop iteration epoll has significant advantages + * over poll. This is the main reason why our mainloop uses epoll. + * However, there is a considerable amount of pain one needs to + * go through to integrate an external poll-based (sub-)mainloop + * (e.g. glib's GMainLoop) with an epoll-based mainloop. I mean, + * just look at the code below ! + * + * If it eventually turns out that we typically only have a small + * number of file descriptors while at the same time we practically + * always need to pump GMainLoop, it is probably a good idea to + * bite the bullet and change our mainloop to be poll-based as well. + * But let's not go there yet... + */ + + + struct epoll_event evt; + struct pollfd *fds, *pollfds; + int timeout; + int nfd, npollfd, n, i; + int nmatch; + int fd, idx; + + MRP_UNUSED(dump_pollfds); + + mrp_debug("preparing subloop %p", sl); + + pollfds = sl->pollfds; + npollfd = sl->npollfd; + + if (sl->cb->prepare(sl->user_data)) { + mrp_debug("subloop %p prepare reported ready, dispatching it", sl); + sl->cb->dispatch(sl->user_data); + } + sl->poll = FALSE; + + nfd = npollfd; + fds = nfd ? mrp_allocz(nfd * sizeof(*fds)) : NULL; + + MRP_ASSERT(nfd == 0 || fds != NULL, "failed to allocate pollfd's"); + + while ((n = sl->cb->query(sl->user_data, fds, nfd, &timeout)) > nfd) { + fds = mrp_reallocz(fds, nfd, n); + nfd = n; + MRP_ASSERT(fds != NULL, "failed to allocate pollfd's"); + } + nfd = n; + + +#if 0 + printf("-------------------------\n"); + dump_pollfds("old: ", sl->pollfds, sl->npollfd); + dump_pollfds("new: ", fds, nfd); + printf("-------------------------\n"); +#endif + + + /* + * skip over the identical portion of the old and new pollfd's + */ + + for (i = nmatch = 0; i < npollfd && i < n; i++, nmatch++) { + if (fds[i].fd != pollfds[i].fd || + fds[i].events != pollfds[i].events) + break; + else + fds[i].revents = pollfds[i].revents = 0; + } + + + if (nmatch == npollfd && npollfd == nfd) { + mrp_free(fds); + goto out; + } + + + /* + * replace file descriptors with the new set (remove old, add new) + */ + + for (i = 0; i < npollfd; i++) { + fd = pollfds[i].fd; + fdtbl_remove(sl->fdtbl, fd); + if (epoll_ctl(sl->epollfd, EPOLL_CTL_DEL, fd, &evt) < 0) { + if (errno != EBADF && errno != ENOENT) + mrp_log_error("Failed to delete subloop fd %d from epoll " + "(%d: %s)", fd, errno, strerror(errno)); + } + } + + for (i = 0; i < nfd; i++) { + fd = fds[i].fd; + idx = i + 1; + + evt.events = fds[i].events; + evt.data.u64 = 0; /* init full union for valgrind... */ + evt.data.fd = fd; + + if (fdtbl_insert(sl->fdtbl, fd, (void *)(ptrdiff_t)idx) == 0) { + if (epoll_ctl(sl->epollfd, EPOLL_CTL_ADD, fd, &evt) != 0) { + mrp_log_error("Failed to add subloop fd %d to epoll " + "(%d: %s)", fd, errno, strerror(errno)); + } + } + else { + mrp_log_error("Failed to add subloop fd %d to fd table " + "(%d: %s)", fd, errno, strerror(errno)); + } + + fds[i].revents = 0; + } + + mrp_free(sl->pollfds); + sl->pollfds = fds; + sl->npollfd = nfd; + + + /* + * resize event buffer if needed + */ + + if (sl->nevent < nfd) { + sl->nevent = nfd; + sl->events = mrp_realloc(sl->events, sl->nevent * sizeof(*sl->events)); + + MRP_ASSERT(sl->events != NULL || sl->nevent == 0, + "can't allocate epoll event buffer"); + } + + out: + mrp_debug("subloop %p: fds: %d, timeout: %d, poll: %s", + sl, sl->npollfd, timeout, sl->poll ? "TRUE" : "FALSE"); + + return timeout; +} + + +static int prepare_subloops(mrp_mainloop_t *ml) +{ + mrp_list_hook_t *p, *n; + mrp_subloop_t *sl; + int ext_timeout, min_timeout; + + min_timeout = INT_MAX; + + mrp_list_foreach(&ml->subloops, p, n) { + sl = mrp_list_entry(p, typeof(*sl), hook); + + if (!is_deleted(sl)) { + ext_timeout = prepare_subloop(sl); + min_timeout = MRP_MIN(min_timeout, ext_timeout); + } + else + mrp_debug("skipping deleted subloop %p", sl); + } + + return min_timeout; +} + + +#if 0 +static inline void dump_timers(mrp_mainloop_t *ml) +{ + mrp_timer_t *t; + mrp_list_hook_t *p, *n; + int i; + mrp_timer_t *next = NULL; + + mrp_debug("timer dump:"); + i = 0; + mrp_list_foreach(&ml->timers, p, n) { + t = mrp_list_entry(p, typeof(*t), hook); + + mrp_debug(" #%d: %p, @%u, next %llu (%s)", i, t, t->msecs, t->expire, + is_deleted(t) ? "DEAD" : "alive"); + + if (!is_deleted(t) && next == NULL) + next = t; + + i++; + } + + mrp_debug("next timer: %p", ml->next_timer); + mrp_debug("poll timer: %d", ml->poll_timeout); + + if (next != NULL && ml->next_timer != NULL && + !is_deleted(ml->next_timer) && next != ml->next_timer) { + mrp_debug("*** BUG ml->next_timer is not the nearest !!! ***"); + if (getenv("__MURPHY_TIMER_CHECK_ABORT") != NULL) + abort(); + } +} +#endif + + +int mrp_mainloop_prepare(mrp_mainloop_t *ml) +{ + mrp_timer_t *next_timer; + int timeout, ext_timeout; + uint64_t now; + + if (!mrp_list_empty(&ml->deferred)) { + timeout = 0; + } + else { + next_timer = ml->next_timer; + + if (next_timer == NULL) + timeout = -1; + else { + now = time_now(); + if (MRP_UNLIKELY(next_timer->expire <= now)) + timeout = 0; + else + timeout = usecs_to_msecs(next_timer->expire - now); + } + } + + ext_timeout = prepare_subloops(ml); + + if (ext_timeout != -1 && timeout != -1) + ml->poll_timeout = MRP_MIN(timeout, ext_timeout); + else if (ext_timeout == -1) + ml->poll_timeout = timeout; + else + ml->poll_timeout = ext_timeout; + + if (ml->nevent < ml->niowatch) { + ml->nevent = ml->niowatch; + ml->events = mrp_realloc(ml->events, ml->nevent * sizeof(*ml->events)); + + MRP_ASSERT(ml->events != NULL, "can't allocate epoll event buffer"); + } + + mrp_debug("mainloop %p prepared: %d I/O watches, timeout %d", ml, + ml->niowatch, ml->poll_timeout); + + return TRUE; +} + + +static size_t poll_events(void *id, mrp_mainloop_t *ml, void **bufp) +{ + void *buf; + int n; + + if (MRP_UNLIKELY(id != ml->iow)) { + mrp_log_error("superloop polling with invalid I/O watch (%p != %p)", + id, ml->iow); + *bufp = NULL; + return 0; + } + + buf = mrp_allocz(ml->nevent * sizeof(ml->events[0])); + + if (buf != NULL) { + n = epoll_wait(ml->epollfd, buf, ml->nevent, 0); + + if (n < 0) + n = 0; + } + else + n = 0; + + *bufp = buf; + return n * sizeof(ml->events[0]); +} + + +int mrp_mainloop_poll(mrp_mainloop_t *ml, int may_block) +{ + int n, timeout; + + timeout = may_block && mrp_list_empty(&ml->deferred) ? ml->poll_timeout : 0; + + if (ml->nevent > 0) { + if (ml->super_ops == NULL || ml->super_ops->poll_io == NULL) { + mrp_debug("polling %d descriptors with timeout %d", + ml->nevent, timeout); + + n = epoll_wait(ml->epollfd, ml->events, ml->nevent, timeout); + + if (n < 0 && errno == EINTR) + n = 0; + } + else { + mrp_superloop_ops_t *super_ops = ml->super_ops; + void *super_data = ml->super_data; + void *id = ml->iow; + void *buf = ml->events; + size_t size = ml->nevent * sizeof(ml->events[0]); + + size = super_ops->poll_io(super_data, id, buf, size); + n = size / sizeof(ml->events[0]); + + MRP_ASSERT(n * sizeof(ml->events[0]) == size, + "superloop passed us a partial epoll_event"); + } + + mrp_debug("mainloop %p has %d/%d I/O events waiting", ml, n, + ml->nevent); + + ml->poll_result = n; + } + else { + /* + * Notes: Practically we should never branch here because + * we always have at least ml->sigfd registered for epoll. + */ + if (timeout > 0) + usleep(timeout * USECS_PER_MSEC); + + ml->poll_result = 0; + } + + return TRUE; +} + + +static int poll_subloop(mrp_subloop_t *sl) +{ + struct epoll_event *e; + struct pollfd *pfd; + int fd, idx, n, i; + + if (sl->poll) { + n = epoll_wait(sl->epollfd, sl->events, sl->nevent, 0); + + if (n < 0 && errno == EINTR) + n = 0; + + for (i = 0, e = sl->events; i < n; i++, e++) { + fd = e->data.fd; + idx = ((int)(ptrdiff_t)fdtbl_lookup(sl->fdtbl, fd)) - 1; + + if (0 <= idx && idx < sl->npollfd) { + pfd = sl->pollfds + idx; + pfd->revents = e->events; + } + } + + mrp_debug("subloop %p has %d fds ready", sl, sl->npollfd); + + return n; + } + else { + mrp_debug("subloop %p has poll flag off", sl); + + return 0; + } +} + + +static void dispatch_wakeup(mrp_mainloop_t *ml) +{ + mrp_list_hook_t *p, *n; + mrp_wakeup_t *w; + mrp_wakeup_event_t event; + uint64_t now; + + if (ml->poll_timeout == 0) { + mrp_debug("skipping wakeup callbacks (poll timeout was 0)"); + return; + } + + if (ml->poll_result == 0) { + mrp_debug("woken up by timeout"); + event = MRP_WAKEUP_EVENT_TIMER; + } + else { + mrp_debug("woken up by I/O (or signal)"); + event = MRP_WAKEUP_EVENT_IO; + } + + now = time_now(); + + mrp_list_foreach(&ml->wakeups, p, n) { + w = mrp_list_entry(p, typeof(*w), hook); + + if (!(w->events & event)) + continue; + + if (!is_deleted(w)) { + mrp_debug("dispatching wakeup cb %p", w); + wakeup_cb(w, event, now); + } + else + mrp_debug("skipping deleted wakeup cb %p", w); + + if (ml->quit) + break; + } +} + + +static void dispatch_deferred(mrp_mainloop_t *ml) +{ + mrp_list_hook_t *p, *n; + mrp_deferred_t *d; + + mrp_list_foreach(&ml->deferred, p, n) { + d = mrp_list_entry(p, typeof(*d), hook); + + if (!is_deleted(d) && !d->inactive) { + mrp_debug("dispatching active deferred cb %p", d); + d->cb(d, d->user_data); + } + else + mrp_debug("skipping %s deferred cb %p", + is_deleted(d) ? "deleted" : "inactive", d); + + if (!is_deleted(d) && d->inactive) + disable_deferred(d); + + if (ml->quit) + break; + } +} + + +static void dispatch_timers(mrp_mainloop_t *ml) +{ + mrp_list_hook_t *p, *n; + mrp_timer_t *t; + uint64_t now; + + now = time_now(); + + mrp_list_foreach(&ml->timers, p, n) { + t = mrp_list_entry(p, typeof(*t), hook); + + if (!is_deleted(t)) { + if (t->expire <= now) { + mrp_debug("dispatching expired timer %p", t); + + t->cb(t, t->user_data); + + if (!is_deleted(t)) + rearm_timer(t); + } + else + break; + } + else + mrp_debug("skipping deleted timer %p", t); + + if (ml->quit) + break; + } +} + + +static void dispatch_subloops(mrp_mainloop_t *ml) +{ + mrp_list_hook_t *p, *n; + mrp_subloop_t *sl; + + mrp_list_foreach(&ml->subloops, p, n) { + sl = mrp_list_entry(p, typeof(*sl), hook); + + if (!is_deleted(sl)) { + poll_subloop(sl); + + if (sl->cb->check(sl->user_data, sl->pollfds, + sl->npollfd)) { + mrp_debug("dispatching subloop %p", sl); + sl->cb->dispatch(sl->user_data); + } + else + mrp_debug("skipping subloop %p, check said no", sl); + } + } +} + + +static void dispatch_slaves(mrp_io_watch_t *w, struct epoll_event *e) +{ + mrp_io_watch_t *s; + mrp_list_hook_t *p, *n; + mrp_io_event_t events; + + events = e->events & ~(MRP_IO_EVENT_INOUT & w->events); + + mrp_list_foreach(&w->slave, p, n) { + if (events == MRP_IO_EVENT_NONE) + break; + + s = mrp_list_entry(p, typeof(*s), slave); + + if (!is_deleted(s)) { + mrp_debug("dispatching slave I/O watch %p (fd %d)", s, s->fd); + s->cb(s, s->fd, events, s->user_data); + } + else + mrp_debug("skipping slave I/O watch %p (fd %d)", s, s->fd); + + events &= ~(MRP_IO_EVENT_INOUT & s->events); + } +} + + +static void dispatch_poll_events(mrp_mainloop_t *ml) +{ + struct epoll_event *e; + mrp_io_watch_t *w, *tblw; + int i, fd; + + for (i = 0, e = ml->events; i < ml->poll_result; i++, e++) { + fd = e->data.fd; + w = fdtbl_lookup(ml->fdtbl, fd); + + if (w == NULL) { + mrp_debug("ignoring event for deleted fd %d", fd); + continue; + } + + if (!is_deleted(w)) { + mrp_debug("dispatching I/O watch %p (fd %d)", w, fd); + w->cb(w, w->fd, e->events, w->user_data); + } + else + mrp_debug("skipping deleted I/O watch %p (fd %d)", w, fd); + + if (!mrp_list_empty(&w->slave)) + dispatch_slaves(w, e); + + if (e->events & EPOLLRDHUP) { + tblw = fdtbl_lookup(ml->fdtbl, w->fd); + + if (tblw == w) { + mrp_debug("forcibly stop polling fd %d for watch %p", w->fd, w); + epoll_del(w); + } + else if (tblw != NULL) + mrp_debug("don't stop polling reused fd %d of watch %p", + w->fd, w); + } + else { + if ((e->events & EPOLLHUP) && !is_deleted(w)) { + /* + * Notes: + * + * If the user does not react to EPOLLHUPs delivered + * we stop monitoring the fd to avoid sitting in an + * infinite busy loop just delivering more EPOLLHUP + * notifications... + */ + + if (w->wrhup++ > 5) { + tblw = fdtbl_lookup(ml->fdtbl, w->fd); + + if (tblw == w) { + mrp_debug("forcibly stop polling fd %d for watch %p", + w->fd, w); + epoll_del(w); + } + else if (tblw != NULL) + mrp_debug("don't stop polling reused fd %d of watch %p", + w->fd, w); + } + } + } + + if (ml->quit) + break; + } + + if (ml->quit) + return; + + dispatch_subloops(ml); + + mrp_debug("done dispatching poll events"); +} + + +int mrp_mainloop_dispatch(mrp_mainloop_t *ml) +{ + dispatch_wakeup(ml); + + if (ml->quit) + goto quit; + + dispatch_deferred(ml); + + if (ml->quit) + goto quit; + + dispatch_timers(ml); + + if (ml->quit) + goto quit; + + dispatch_poll_events(ml); + + quit: + purge_deleted(ml); + + return !ml->quit; +} + + +int mrp_mainloop_iterate(mrp_mainloop_t *ml) +{ + return + mrp_mainloop_prepare(ml) && + mrp_mainloop_poll(ml, TRUE) && + mrp_mainloop_dispatch(ml) && + !ml->quit; +} + + +int mrp_mainloop_run(mrp_mainloop_t *ml) +{ + while (mrp_mainloop_iterate(ml)) + ; + + return ml->exit_code; +} + + +void mrp_mainloop_quit(mrp_mainloop_t *ml, int exit_code) +{ + ml->exit_code = exit_code; + ml->quit = TRUE; +} + + +/* + * debugging routines + */ + + +static void dump_pollfds(const char *prefix, struct pollfd *fds, int nfd) +{ + char *t; + int i; + + printf("%s (%d): ", prefix, nfd); + for (i = 0, t = ""; i < nfd; i++, t = ", ") + printf("%s%d/0x%x", t, fds[i].fd, fds[i].events); + printf("\n"); +} + + +/* + * event bus and events + */ + +static inline void *ref_event_data(void *data, int format) +{ + switch (format & MRP_EVENT_FORMAT_MASK) { + case MRP_EVENT_FORMAT_JSON: + return mrp_json_ref((mrp_json_t *)data); + case MRP_EVENT_FORMAT_MSG: + return mrp_msg_ref((mrp_msg_t *)data); + default: + return data; + } +} + + +static inline void unref_event_data(void *data, int format) +{ + switch (format & MRP_EVENT_FORMAT_MASK) { + case MRP_EVENT_FORMAT_JSON: + mrp_json_unref((mrp_json_t *)data); + break; + case MRP_EVENT_FORMAT_MSG: + mrp_msg_unref((mrp_msg_t *)data); + break; + default: + break; + } +} + + +mrp_event_bus_t *mrp_event_bus_get(mrp_mainloop_t *ml, const char *name) +{ + mrp_list_hook_t *p, *n; + mrp_event_bus_t *bus; + + if (name == NULL || !strcmp(name, MRP_GLOBAL_BUS_NAME)) + return MRP_GLOBAL_BUS; + + mrp_list_foreach(&ml->busses, p, n) { + bus = mrp_list_entry(p, typeof(*bus), hook); + + if (!strcmp(bus->name, name)) + return bus; + } + + bus = mrp_allocz(sizeof(*bus)); + + if (bus == NULL) + return NULL; + + bus->name = mrp_strdup(name); + + if (bus->name == NULL) { + mrp_free(bus); + return NULL; + } + + mrp_list_init(&bus->hook); + mrp_list_init(&bus->watches); + bus->ml = ml; + + mrp_list_append(&ml->busses, &bus->hook); + + return bus; +} + + +uint32_t mrp_event_id(const char *name) +{ + mrp_event_def_t *e; + int i; + + if (events != NULL) + for (i = 0, e = events; i < nevent; i++, e++) + if (!strcmp(e->name, name)) + return e->id; + + if (!mrp_reallocz(events, nevent, nevent + 1)) + return 0; + + e = events + nevent; + + e->id = nevent; + e->name = mrp_strdup(name); + + if (e->name == NULL) { + mrp_reallocz(events, nevent + 1, nevent); + return 0; + } + + nevent++; + + return e->id; +} + + +const char *mrp_event_name(uint32_t id) +{ + if ((int)id < nevent) + return events[id].name; + else + return MRP_EVENT_UNKNOWN_NAME; +} + + +char *mrp_event_dump_mask(mrp_event_mask_t *mask, char *buf, size_t size) +{ + char *p, *t; + int l, n, id; + + p = buf; + l = (int)size; + t = ""; + + MRP_MASK_FOREACH_SET(mask, id, 1) { + n = snprintf(p, l, "%s%s", t, mrp_event_name(id)); + t = "|"; + + if (n >= l) + return "<insufficient mask dump buffer>"; + + p += n; + l -= n; + } + + return buf; +} + + +mrp_event_watch_t *mrp_event_add_watch(mrp_event_bus_t *bus, uint32_t id, + mrp_event_watch_cb_t cb, void *user_data) +{ + mrp_list_hook_t *watches = bus ? &bus->watches : &ewatches; + mrp_event_watch_t *w; + + w = mrp_allocz(sizeof(*w)); + + if (w == NULL) + return NULL; + + mrp_list_init(&w->hook); + mrp_mask_init(&w->mask); + w->bus = bus; + w->cb = cb; + w->user_data = user_data; + + if (!mrp_mask_set(&w->mask, id)) { + mrp_free(w); + return NULL; + } + + mrp_list_append(watches, &w->hook); + + mrp_debug("added event watch %p for event %d (%s) on bus %s", w, id, + mrp_event_name(id), bus ? bus->name : MRP_GLOBAL_BUS_NAME); + + return w; +} + + +mrp_event_watch_t *mrp_event_add_watch_mask(mrp_event_bus_t *bus, + mrp_event_mask_t *mask, + mrp_event_watch_cb_t cb, + void *user_data) +{ + mrp_list_hook_t *watches = bus ? &bus->watches : &ewatches; + mrp_event_watch_t *w; + char events[512]; + + w = mrp_allocz(sizeof(*w)); + + if (w == NULL) + return NULL; + + mrp_list_init(&w->hook); + mrp_mask_init(&w->mask); + w->bus = bus; + w->cb = cb; + w->user_data = user_data; + + if (!mrp_mask_copy(&w->mask, mask)) { + mrp_free(w); + return NULL; + } + + mrp_list_append(watches, &w->hook); + + mrp_debug("added event watch %p for events <%s> on bus %s", w, + mrp_event_dump_mask(&w->mask, events, sizeof(events)), + bus ? bus->name : MRP_GLOBAL_BUS_NAME); + + return w; +} + + +void mrp_event_del_watch(mrp_event_watch_t *w) +{ + if (w == NULL) + return; + + if (w->bus != NULL && w->bus->busy) { + w->dead = TRUE; + w->bus->dead++; + return; + } + + mrp_list_delete(&w->hook); + mrp_mask_reset(&w->mask); + mrp_free(w); +} + + +void bus_purge_dead(mrp_event_bus_t *bus) +{ + mrp_event_watch_t *w; + mrp_list_hook_t *p, *n; + + if (!bus->dead) + return; + + mrp_list_foreach(&bus->watches, p, n) { + w = mrp_list_entry(p, typeof(*w), hook); + + if (!w->dead) + continue; + + mrp_list_delete(&w->hook); + mrp_mask_reset(&w->mask); + mrp_free(w); + } + + bus->dead = 0; +} + + +static int queue_event(mrp_event_bus_t *bus, uint32_t id, void *data, + mrp_event_flag_t flags) +{ + pending_event_t *e; + + e = mrp_allocz(sizeof(*e)); + + if (e == NULL) + return -1; + + mrp_list_init(&e->hook); + e->bus = bus; + e->id = id; + e->format = flags & MRP_EVENT_FORMAT_MASK; + e->data = ref_event_data(data, e->format); + mrp_list_append(&bus->ml->eventq, &e->hook); + + mrp_enable_deferred(bus->ml->eventd); + + return 0; +} + + +static int emit_event(mrp_event_bus_t *bus, uint32_t id, void *data, + mrp_event_flag_t flags) +{ + mrp_list_hook_t *watches; + mrp_event_watch_t *w; + mrp_list_hook_t *p, *n; + + if (bus) + watches = &bus->watches; + else { + if (!(flags & MRP_EVENT_SYNCHRONOUS)) { + errno = EINVAL; + return -1; + } + watches = &ewatches; + } + + if (bus) + bus->busy++; + + mrp_debug("emitting event 0x%x (%s) on bus <%s>", id, mrp_event_name(id), + bus ? bus->name : MRP_GLOBAL_BUS_NAME); + + mrp_list_foreach(watches, p, n) { + w = mrp_list_entry(p, typeof(*w), hook); + + if (w->dead) + continue; + + if (mrp_mask_test(&w->mask, id)) + w->cb(w, id, flags & MRP_EVENT_FORMAT_MASK, data, w->user_data); + } + + if (bus) { + bus->busy--; + + if (!bus->busy) + bus_purge_dead(bus); + } + + return 0; +} + + +static void pump_events(mrp_deferred_t *d, void *user_data) +{ + mrp_mainloop_t *ml = (mrp_mainloop_t *)user_data; + mrp_list_hook_t *p, *n; + pending_event_t *e; + + pump: + mrp_list_foreach(&ml->eventq, p, n) { + e = mrp_list_entry(p, typeof(*e), hook); + + emit_event(e->bus, e->id, e->data, e->format); + + mrp_list_delete(&e->hook); + unref_event_data(e->data, e->format); + + mrp_free(e); + } + + if (!mrp_list_empty(&ml->eventq)) + goto pump; + + mrp_disable_deferred(d); +} + + +int mrp_emit_event(mrp_event_bus_t *bus, uint32_t id, mrp_event_flag_t flags, + void *data) +{ + int status; + + if (flags & MRP_EVENT_SYNCHRONOUS) { + ref_event_data(data, flags); + status = emit_event(bus, id, data, flags); + unref_event_data(data, flags); + + return status; + } + else { + if (bus != NULL) + return queue_event(bus, id, data, flags); + + errno = EOPNOTSUPP; + return -1; + } +} + + +int _mrp_event_emit_msg(mrp_event_bus_t *bus, uint32_t id, + mrp_event_flag_t flags, ...) +{ + mrp_msg_t *msg; + uint16_t tag; + va_list ap; + int status; + + va_start(ap, flags); + tag = va_arg(ap, unsigned int); + msg = tag ? mrp_msg_createv(tag, ap) : NULL; + va_end(ap); + + flags &= ~MRP_EVENT_FORMAT_MASK; + status = mrp_emit_event(bus, id, flags | MRP_EVENT_FORMAT_MSG, msg); + mrp_msg_unref(msg); + + return status; +} + + +MRP_INIT static void init_events(void) +{ + MRP_ASSERT(mrp_event_id(MRP_EVENT_UNKNOWN_NAME) == MRP_EVENT_UNKNOWN, + "reserved id 0x%x for builtin event <%s> already taken", + MRP_EVENT_UNKNOWN, MRP_EVENT_UNKNOWN_NAME); +} diff --git a/src/common/mainloop.h b/src/common/mainloop.h new file mode 100644 index 0000000..4e7eee2 --- /dev/null +++ b/src/common/mainloop.h @@ -0,0 +1,431 @@ +/* + * Copyright (c) 2012-2014, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_MAINLOOP_H__ +#define __MURPHY_MAINLOOP_H__ + +#include <signal.h> +#include <stdint.h> +#include <sys/poll.h> +#include <sys/epoll.h> + +#include <murphy/common/macros.h> + +MRP_CDECL_BEGIN + +typedef struct mrp_mainloop_s mrp_mainloop_t; + +/* + * I/O watches + */ + +/** I/O events */ +typedef enum { + MRP_IO_EVENT_NONE = 0x0, + MRP_IO_EVENT_IN = EPOLLIN, + MRP_IO_EVENT_PRI = EPOLLPRI, + MRP_IO_EVENT_OUT = EPOLLOUT, + MRP_IO_EVENT_RDHUP = EPOLLRDHUP, + MRP_IO_EVENT_WRHUP = EPOLLHUP, + MRP_IO_EVENT_HUP = EPOLLRDHUP|EPOLLHUP, + MRP_IO_EVENT_ERR = EPOLLERR, + MRP_IO_EVENT_INOUT = EPOLLIN|EPOLLOUT, + MRP_IO_EVENT_ALL = EPOLLIN|EPOLLPRI|EPOLLOUT|EPOLLRDHUP|EPOLLERR, + /* event trigger modes */ + MRP_IO_TRIGGER_LEVEL = 0x1U << 25, + MRP_IO_TRIGGER_EDGE = EPOLLET, + MRP_IO_TRIGGER_MASK = MRP_IO_TRIGGER_LEVEL|MRP_IO_TRIGGER_EDGE +} mrp_io_event_t; + +typedef struct mrp_io_watch_s mrp_io_watch_t; + +/** I/O watch notification callback type. */ +typedef void (*mrp_io_watch_cb_t)(mrp_io_watch_t *w, int fd, + mrp_io_event_t events, void *user_data); +/** Register a new file descriptor to watch. */ +mrp_io_watch_t *mrp_add_io_watch(mrp_mainloop_t *ml, int fd, + mrp_io_event_t events, + mrp_io_watch_cb_t cb, void *user_data); +/** Unregister an I/O watch. */ +void mrp_del_io_watch(mrp_io_watch_t *watch); + +/** Get the mainloop of an I/O watch. */ +mrp_mainloop_t *mrp_get_io_watch_mainloop(mrp_io_watch_t *watch); + +/** Set the default event trigger mode for the given mainloop. */ +int mrp_set_io_event_mode(mrp_mainloop_t *ml, mrp_io_event_t mode); + +/** Get the default event trigger mode of the given mainloop. */ +mrp_io_event_t mrp_get_io_event_mode(mrp_mainloop_t *ml); + +/* + * timers + */ + +typedef struct mrp_timer_s mrp_timer_t; + +/** Timer notification callback type. */ +typedef void (*mrp_timer_cb_t)(mrp_timer_t *t, void *user_data); + +/** Add a new timer. */ +mrp_timer_t *mrp_add_timer(mrp_mainloop_t *ml, unsigned int msecs, + mrp_timer_cb_t cb, void *user_data); +/** Modify the timeout or rearm the given timer. */ +#define MRP_TIMER_RESTART (unsigned int)-1 +void mrp_mod_timer(mrp_timer_t *t, unsigned int msecs); + +/** Delete a timer. */ +void mrp_del_timer(mrp_timer_t *t); + +/** Get the mainloop of a timer. */ +mrp_mainloop_t *mrp_get_timer_mainloop(mrp_timer_t *t); + + +/* + * deferred callbacks + */ + +typedef struct mrp_deferred_s mrp_deferred_t; + +/** Deferred callback notification callback type. */ +typedef void (*mrp_deferred_cb_t)(mrp_deferred_t *d, void *user_data); + +/** Add a deferred callback. */ +mrp_deferred_t *mrp_add_deferred(mrp_mainloop_t *ml, mrp_deferred_cb_t cb, + void *user_data); +/** Remove a deferred callback. */ +void mrp_del_deferred(mrp_deferred_t *d); + +/** Disable a deferred callback. */ +void mrp_disable_deferred(mrp_deferred_t *d); + +/** Enable a deferred callback. */ +void mrp_enable_deferred(mrp_deferred_t *d); + +/** Get the mainloop of a deferred callback. */ +mrp_mainloop_t *mrp_get_deferred_mainloop(mrp_deferred_t *d); + + +/* + * signals + */ + +typedef struct mrp_sighandler_s mrp_sighandler_t; + +/* Signal handler callback type. */ +typedef void (*mrp_sighandler_cb_t)(mrp_sighandler_t *h, int signum, + void *user_data); +/** Register a signal handler. */ +mrp_sighandler_t *mrp_add_sighandler(mrp_mainloop_t *ml, int signum, + mrp_sighandler_cb_t cb, void *user_data); +/** Unregister a signal handler. */ +void mrp_del_sighandler(mrp_sighandler_t *h); + +/** Get the mainloop of a signal handler. */ +mrp_mainloop_t *mrp_get_sighandler_mainloop(mrp_sighandler_t *h); + + +/* + * wakeup callbacks + */ + +/** I/O events */ +typedef enum { + MRP_WAKEUP_EVENT_NONE = 0x0, /* no event */ + MRP_WAKEUP_EVENT_TIMER = 0x1, /* woken up by timeout */ + MRP_WAKEUP_EVENT_IO = 0x2, /* woken up by I/O (or signal) */ + MRP_WAKEUP_EVENT_ANY = 0x3, /* mask of all selectable events */ + MRP_WAKEUP_EVENT_LIMIT = 0x4, /* woken up by forced trigger */ +} mrp_wakeup_event_t; + +typedef struct mrp_wakeup_s mrp_wakeup_t; + +/** Wakeup callback notification type. */ +typedef void (*mrp_wakeup_cb_t)(mrp_wakeup_t *w, mrp_wakeup_event_t event, + void *user_data); + +/** Add a wakeup callback for the specified events. lpf_msecs and + * force_msecs specifiy two limiting intervals in milliseconds. + * lpf_msecs is a low-pass filtering interval. It is guaranteed that + * the wakeup callback will not be invoked more ofter than this. + * force_msecs is a forced trigger interval. It is guaranteed that + * the wakeup callback will be triggered at least this often. You can + * MRP_WAKEUP_NOLIMIT to omit either or both limiting intervals. */ +#define MRP_WAKEUP_NOLIMIT ((unsigned int)0) +mrp_wakeup_t *mrp_add_wakeup(mrp_mainloop_t *ml, mrp_wakeup_event_t events, + unsigned int lpf_msecs, unsigned int force_msecs, + mrp_wakeup_cb_t cb, void *user_data); + +/** Remove a wakeup callback. */ +void mrp_del_wakeup(mrp_wakeup_t *w); + +/** Get the mainloop of a wakeup callback. */ +mrp_mainloop_t *mrp_get_wakeup_mainloop(mrp_wakeup_t *w); + + +/* + * subloops - external mainloops pumped by this mainloop + */ + +typedef struct mrp_subloop_s mrp_subloop_t; + +typedef struct { + int (*prepare)(void *user_data); + int (*query)(void *user_data, struct pollfd *fds, int nfd, int *timeout); + int (*check)(void *user_data, struct pollfd *fds, int nfd); + void (*dispatch)(void *user_data); +} mrp_subloop_ops_t; + + +/** Register an external mainloop to be pumped by the given mainloop. */ +mrp_subloop_t *mrp_add_subloop(mrp_mainloop_t *ml, mrp_subloop_ops_t *ops, + void *user_data); + +/** Stop pumping a registered external mainloop. */ +void mrp_del_subloop(mrp_subloop_t *sl); + + +/* + * superloops - external mainloop to pump murphy mainloops + */ + +typedef struct { + void *(*add_io)(void *glue_data, int fd, mrp_io_event_t events, + void (*cb)(void *glue_data, void *id, int fd, + mrp_io_event_t events, void *user_data), + void *user_data); + void (*del_io)(void *glue_data, void *id); + + void *(*add_timer)(void *glue_data, unsigned int msecs, + void (*cb)(void *glue_data, void *id, void *user_data), + void *user_data); + void (*del_timer)(void *glue_data, void *id); + void (*mod_timer)(void *glue_data, void *id, unsigned int msecs); + + void *(*add_defer)(void *glue_data, + void (*cb)(void *glue_data, void *id, void *user_data), + void *user_data); + void (*del_defer)(void *glue_data, void *id); + void (*mod_defer)(void *glue_data, void *id, int enabled); + void (*unregister)(void *glue_data); + + /* + * Notes: + * + * This is a band-aid attempt to get our mainloop run under the + * threaded event loop of xwalk which has strict limitations about + * what (type of) thread can access which functionality of the + * event loop. In particular, the I/O watch equivalent is limited + * for the I/O thread. In the current media element resource infra + * integration code of xwalk, the related code is run in the UI + * thread. This makes interacting from there with the daemon using + * the stock resource libraries (in particular, pumping the mainloop) + * non-straightforward. + * + * The superloop glue code is supposed to use poll_events to trigger + * a nonblocking epoll_wait on our epoll fd to retrieve (and cache) + * pending epoll events from the kernel. poll_io is then used by us + * to retrieve pending epoll events from the glue code. The glue code + * needs to take care of any necessary locking to protect itself/us + * from potentially concurrent invocations of poll_io and poll_events + * from different threads. + * + * The superloop abstraction now became really really ugly. poll_io + * and poll_events implicitly assume/know that add_io/del_io is only + * used for adding a single superloop I/O watch for our epoll fd. + * The I/O watch id in the functions below is passed around only for + * doublechecing this. Probably we should change the I/O watch part + * of the superloop API to be actually less generic and only usable + * for the epoll fd to avoid further confusion. + */ + size_t (*poll_events)(void *id, mrp_mainloop_t *ml, void **events); + size_t (*poll_io)(void *glue_data, void *id, void *buf, size_t size); +} mrp_superloop_ops_t; + + +/** Set a superloop to pump the given mainloop. */ +int mrp_set_superloop(mrp_mainloop_t *ml, mrp_superloop_ops_t *ops, + void *loop_data); + +/** Clear the superloop that pumps the given mainloop. */ +int mrp_clear_superloop(mrp_mainloop_t *ml); + +/** Unregister a mainloop from its superloop if it has one. */ +int mrp_mainloop_unregister(mrp_mainloop_t *ml); + +/* + * mainloop + */ + +/** Create a new mainloop. */ +mrp_mainloop_t *mrp_mainloop_create(void); + +/** Destroy an existing mainloop, free all I/O watches, timers, etc. */ +void mrp_mainloop_destroy(mrp_mainloop_t *ml); + +/** Keep iterating the mainloop until mrp_mainloop_quit is called. */ +int mrp_mainloop_run(mrp_mainloop_t *ml); + +/** Prepare the mainloop for polling. */ +int mrp_mainloop_prepare(mrp_mainloop_t *ml); + +/** Poll the mainloop. */ +int mrp_mainloop_poll(mrp_mainloop_t *ml, int may_block); + +/** Dispatch pending events of the mainloop. */ +int mrp_mainloop_dispatch(mrp_mainloop_t *ml); + +/** Run a single prepare-poll-dispatch cycle of the mainloop. */ +int mrp_mainloop_iterate(mrp_mainloop_t *ml); + +/** Quit the mainloop. */ +void mrp_mainloop_quit(mrp_mainloop_t *ml, int exit_code); + +/* + * event bus and and events + */ + +#include <murphy/common/mask.h> +#include <murphy/common/msg.h> + +#define MRP_GLOBAL_BUS NULL /* global synchronous bus */ +#define MRP_GLOBAL_BUS_NAME "global" /* global synchronous bus name */ + +/** event flags */ +typedef enum { + MRP_EVENT_ASYNCHRONOUS = 0x00, /* deliver asynchronously */ + MRP_EVENT_SYNCHRONOUS = 0x01, /* deliver synchronously */ + MRP_EVENT_FORMAT_JSON = 0x01 << 1, /* attached data is JSON */ + MRP_EVENT_FORMAT_MSG = 0x02 << 1, /* attached data is mrp_msg_t */ + MRP_EVENT_FORMAT_CUSTOM = 0x03 << 1, /* attached data is of custom format */ + MRP_EVENT_FORMAT_MASK = 0x03 << 1, +} mrp_event_flag_t; + +/** Reserved event id for unknown events. */ +#define MRP_EVENT_UNKNOWN 0 + +/** Reserved name for unknown events. */ +#define MRP_EVENT_UNKNOWN_NAME "unknown" + +/** + * Event definition for registering event(name)s. + */ +typedef struct { + char *name; /* event name */ + uint32_t id; /* event id assigned by murphy */ +} mrp_event_def_t; + +/** Opaque event watch type. */ +typedef struct mrp_event_watch_s mrp_event_watch_t; + +/** Event watch notification callback type. */ +typedef void (*mrp_event_watch_cb_t)(mrp_event_watch_t *w, uint32_t id, + int format, void *data, void *user_data); + +/** Opaque event bus type. */ +typedef struct mrp_event_bus_s mrp_event_bus_t; + +/** Event mask type (a bitmask of event ids). */ +typedef mrp_mask_t mrp_event_mask_t; + +/** Look up (or create) the event bus with the given name. */ +mrp_event_bus_t *mrp_event_bus_get(mrp_mainloop_t *ml, const char *name); +#define mrp_event_bus_create mrp_event_bus_get + +/** Look up (or register) the id of the given event. */ +uint32_t mrp_event_id(const char *name); +#define mrp_event_register mrp_event_id + +/** Look up the name of the given event. */ +const char *mrp_event_name(uint32_t id); + +/** Dump the names of the events set in mask. */ +char *mrp_event_dump_mask(mrp_event_mask_t *mask, char *buf, size_t size); + +/** Register an event watch for a single event on the given bus. */ +mrp_event_watch_t *mrp_event_add_watch(mrp_event_bus_t *bus, uint32_t id, + mrp_event_watch_cb_t cb, void *user_data); + +/** Register an event watch for a number of events on the given bus. */ +mrp_event_watch_t *mrp_event_add_watch_mask(mrp_event_bus_t *bus, + mrp_event_mask_t *mask, + mrp_event_watch_cb_t cb, + void *user_data); + +/** Unregister the given event watch. */ +void mrp_event_del_watch(mrp_event_watch_t *w); + +/** Emit the given event with the data attached on the given bus. */ +int mrp_event_emit(mrp_event_bus_t *bus, uint32_t id, mrp_event_flag_t flags, + void *data); + +/** Convenience macro to emit an event with JSON data attached. */ +#define mrp_event_emit_json(bus, id, flags, data) \ + mrp_event_emit((bus), (id), (flags) | MRP_EVENT_FORMAT_JSON, (data)) + +/** Convenience macro to emit an event with mrp_msg_t data attached. */ +#define mrp_event_emit_msg(bus, id, flags, ...) \ + _mrp_event_emit_msg((bus), (id), (flags), __VA_ARGS__, MRP_MSG_END) + +/** Emit an event with mrp_msg_t data attached. */ +int _mrp_event_emit_msg(mrp_event_bus_t *bus, uint32_t id, + mrp_event_flag_t flags, ...); + +/** Convenience macro to emit an event with custom data attached. */ +#define mrp_event_emit_custom(bus, id, data, flags) \ + mrp_event_emit((bus), (id), (data), (flags) | MRP_EVENT_FORMAT_CUSTOM) + +/** Convenience macros for autoregistering a table of events on startup. */ +#define MRP_EVENT(_name, _id) [_id] = { .name = _name, .id = 0 } +#define MRP_REGISTER_EVENTS(_event_table, ...) \ + static mrp_event_def_t _event_table[] = { \ + __VA_ARGS__, \ + { .name = NULL } \ + }; \ + \ + MRP_INIT static void register_##_event_table(void) \ + { \ + mrp_event_def_t *e; \ + \ + for (e = _event_table; e->name != NULL; e++) { \ + e->id = mrp_event_register(e->name); \ + \ + if (e->id != MRP_EVENT_UNKNOWN) \ + mrp_log_info("Event '%s' registered as 0x%x.", \ + e->name, e->id); \ + else \ + mrp_log_error("Failed to register event '%s'.", \ + e->name); \ + } \ + } \ + struct __mrp_allow_trailing_semicolon + +MRP_CDECL_END + +#endif /* __MURPHY_MAINLOOP_H__ */ diff --git a/src/common/mask.h b/src/common/mask.h new file mode 100644 index 0000000..12ed336 --- /dev/null +++ b/src/common/mask.h @@ -0,0 +1,464 @@ +/* + * Copyright (c) 2014, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_MASK_H__ +#define __MURPHY_MASK_H__ + +#include <stdint.h> + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> + + +MRP_CDECL_BEGIN + +/** Type used to store bits in bitmasks. */ +typedef uint64_t _mask_t; + + +/** + * trivial representation of a bitmask of arbitrary size + */ + +typedef struct { + int nbit; /* number of bits in this mask */ + union { + _mask_t bits; /* bits for nbit <= 64 */ + _mask_t *bitp; /* bits for nbit > 64 */ + }; +} mrp_mask_t; + + +/** Macro to intialize a bitmask to empty. */ +#define MRP_MASK_EMPTY { .nbit = 64, .bits = 0 } +#define MRP_MASK_INIT(m) do { (m)->nbit = 64; (m)->bits = 0; } while (0) + +/** Macro to declare a bitmask variable and initialize it. */ +#define MRP_MASK(m) mrp_mask_t m = MRP_MASK_EMPTY + +/* Various bit-fiddling macros. */ +#define MRP_MASK_BIT(bit) (1ULL << (bit)) +#define MRP_MASK_UPTO(bit) ((1ULL << (bit)) - 1) +#define MRP_MASK_BELOW(bit) (MRP_MASK_UPTO(bit) >> 1) + + +#define _BITS_PER_WORD ((int)(sizeof(_mask_t) * 8)) +#define _WORD(bit) ((bit) / _BITS_PER_WORD) +#define _BIT(bit) ((bit) & (_BITS_PER_WORD - 1)) +#define _MASK(bit) (0x1ULL << (bit)) + + +/** Initialize the given mask. */ +static inline void mrp_mask_init(mrp_mask_t *m) +{ +#ifndef __cplusplus + *m = (mrp_mask_t)MRP_MASK_EMPTY; +#else + MRP_MASK_INIT(m); +#endif +} + + +/** Reset the given mask. */ +static inline void mrp_mask_reset(mrp_mask_t *m) +{ + if (m->nbit > _BITS_PER_WORD) + mrp_free(m->bitp); + + mrp_mask_init(m); +} + + +/** Ensure the given mask to accomodate the given number of bits. */ +static inline mrp_mask_t *mrp_mask_ensure(mrp_mask_t *m, int bits) +{ + _mask_t w; + int o, n; + + if (bits <= m->nbit) + return m; + + if (m->nbit == _BITS_PER_WORD) { + w = m->bits; + n = (bits + _BITS_PER_WORD - 1) / _BITS_PER_WORD; + + m->bitp = (_mask_t *)mrp_allocz(n * sizeof(*m->bitp)); + + if (m->bitp == NULL) { + m->bits = w; + + return NULL; + } + + m->bitp[0] = w; + m->nbit = n * _BITS_PER_WORD; + } + else { + o = m->nbit / _BITS_PER_WORD; + n = (bits + _BITS_PER_WORD - 1) / _BITS_PER_WORD; + + if (!mrp_reallocz(m->bitp, o, n + 1)) + return NULL; + + m->nbit = n * _BITS_PER_WORD; + } + + return m; +} + + +/** Resize mask to accomodate the given number of bits, truncate if possible. */ +static inline mrp_mask_t *mrp_mask_trunc(mrp_mask_t *m, int bits) +{ + int n; + uint64_t *bitp; + + if (m->nbit <= bits) + return mrp_mask_ensure(m, bits); + + n = (bits + _BITS_PER_WORD - 1) / _BITS_PER_WORD; + + if (n == 1) { + bitp = m->bitp; + m->bits = bitp[0]; + + mrp_free(bitp); + } + else + mrp_reallocz(m->bitp, m->nbit / _BITS_PER_WORD, n); + + m->nbit = n * _BITS_PER_WORD; + + return m; +} + + +/** Set the given bit in the mask. */ +static inline mrp_mask_t *mrp_mask_set(mrp_mask_t *m, int bit) +{ + int w, b; + + if (!mrp_mask_ensure(m, bit)) + return NULL; + + b = _BIT(bit); + + if (m->nbit == _BITS_PER_WORD) + m->bits |= _MASK(b); + else { + w = _WORD(bit); + m->bitp[w] |= _MASK(b); + } + + return m; +} + + +/** Clear the given bit in the mask. */ +static inline mrp_mask_t *mrp_mask_clear(mrp_mask_t *m, int bit) +{ + int w, b; + + if (bit >= m->nbit) + return m; + + b = _BIT(bit); + + if (m->nbit == _BITS_PER_WORD) + m->bits &= ~_MASK(b); + else { + w = _WORD(bit); + m->bitp[w] &= ~_MASK(b); + } + + return m; +} + + +/** Test the given bit in the mask. */ +static inline int mrp_mask_test(mrp_mask_t *m, int bit) +{ + int w, b; + + if (bit >= m->nbit) + return 0; + + b = _BIT(bit); + + if (m->nbit == _BITS_PER_WORD) + return !!(m->bits & _MASK(b)); + else { + w = _WORD(bit); + return !!(m->bitp[w] & _MASK(b)); + } +} + + +/** Copy the given mask, overwriting dst. */ +static inline mrp_mask_t *mrp_mask_copy(mrp_mask_t *dst, mrp_mask_t *src) +{ + mrp_mask_reset(dst); + + dst->nbit = src->nbit; + + if (src->nbit == _BITS_PER_WORD) + *dst = *src; + else { + dst->bitp = (_mask_t *)mrp_alloc(dst->nbit * _BITS_PER_WORD); + + if (dst->bitp == NULL) + return NULL; + + memcpy(dst->bitp, src->bitp, dst->nbit * _BITS_PER_WORD); + } + + return dst; +} + + +/** Set all bits in src into dst (dst |= src). */ +static inline mrp_mask_t *mrp_mask_or(mrp_mask_t *dst, mrp_mask_t *src) +{ + int i, n; + + if (!mrp_mask_ensure(dst, src->nbit)) + return NULL; + + if (src->nbit == _BITS_PER_WORD) { + if (dst->nbit == _BITS_PER_WORD) + dst->bits |= src->bits; + else + dst->bitp[0] |= src->bits; + } + else { + n = src->nbit / _BITS_PER_WORD; + + for (i = 0; i < n; i++) + dst->bitp[i] |= src->bitp[i]; + } + + return dst; +} + + +/** Mask all bits in dst with the corresponding ones from src (dst &= src). */ +static inline mrp_mask_t *mrp_mask_and(mrp_mask_t *dst, mrp_mask_t *src) +{ + int i, n; + + n = MRP_MIN(dst->nbit, src->nbit); + mrp_mask_trunc(dst, n); + + n /= _BITS_PER_WORD; + + if (dst->nbit == _BITS_PER_WORD) { + if (src->nbit == _BITS_PER_WORD) + dst->bits &= src->bits; + else + dst->bits &= src->bitp[0]; + } + else { + for (i = 0; i < n; i++) + dst->bitp[i] &= src->bitp[i]; + } + + return dst; +} + + +/** Set all bits in src into dst (dst ^= src). */ +static inline mrp_mask_t *mrp_mask_xor(mrp_mask_t *dst, mrp_mask_t *src) +{ + int i, n; + + if (!mrp_mask_ensure(dst, src->nbit)) + return NULL; + + if (src->nbit == _BITS_PER_WORD) { + if (dst->nbit == _BITS_PER_WORD) + dst->bits |= src->bits; + else + dst->bitp[0] |= src->bits; + +#if 0 + /* + * Hmm... this would consider those bits in src which are not + * actually there but are in dst to be implicit 0's. However, + * I'm not sure if this really is a good idea... Needs a bit + * exposure to using this code to decide. + */ + + n = dst->nbit / _BITS_PER_WORD; + for (i = 1; i < n; i++) + dst->bitp[i] ^= 0; +#endif + } + else { + n = src->nbit / _BITS_PER_WORD; + for (i = 0; i < n; i++) + dst->bitp[i] ^= src->bitp[i]; + +#if 0 + /* + * Hmm... ditto for this piece of code. + */ + + n = dst->nbit / _BITS_PER_WORD; + while (i < n) + dst->bitp[i] ^= 0; +#endif + } + + return dst; +} + + +/** Negate all bits in mask (~mask). */ +static inline mrp_mask_t *mrp_mask_neg(mrp_mask_t *m) +{ + int i, n; + + if (m->nbit == _BITS_PER_WORD) + m->bits = ~m->bits; + else { + n = m->nbit / _BITS_PER_WORD; + + for (i = 0; i < n; i++) + m->bitp[i] = ~m->bitp[i]; + } + + return m; +} + + +/** Find the first bit set (1-based indexing) in the given mask. */ +static inline int mrp_ffsll(_mask_t bits) +{ +#ifdef __GNUC__ + return __builtin_ffsll(bits); +#else + _mask_t mask = 0xffffffff; + int w, n; + + if (!bits) + return 0; + + n = 0; + w = _BITS_PER_WORD / 2; + while (w) { + if (!(bits & mask)) { + bits >>= w; + mask >>= w / 2; + n += w; + w /= 2; + } + else { + bits &= mask; + mask >>= w / 2; + w /= 2; + } + } + + return n + 1; +#endif +} + + +/** Get the first bit set starting at the given bit. */ +static inline int mrp_mask_next_set(mrp_mask_t *m, int bit) +{ + _mask_t wrd, clr; + int w, b, n; + + while (bit < m->nbit - 1) { + w = _WORD(bit); + b = _BIT(bit); + + if (m->nbit == _BITS_PER_WORD) + wrd = m->bits; + else + wrd = m->bitp[w]; + + clr = ~(_MASK(b) - 1); + n = mrp_ffsll(wrd & clr); + + if (n > 0) + return w * _BITS_PER_WORD + n - 1; + + bit = (bit + _BITS_PER_WORD) & ~(_BITS_PER_WORD - 1); + } + + return -1; +} + + +/** Get the first bit cleared starting at the given bit. */ +static inline int mrp_mask_next_clear(mrp_mask_t *m, int bit) +{ + _mask_t wrd, clr; + int w, b, n; + + while (bit < m->nbit - 1) { + w = _WORD(bit); + b = _BIT(bit); + + if (m->nbit == _BITS_PER_WORD) + wrd = m->bits; + else + wrd = m->bitp[w]; + + clr = _MASK(b) - 1; + n = mrp_ffsll(~(wrd | clr)); + + if (n > 0) + return w * _BITS_PER_WORD + n - 1; + + bit = (bit + _BITS_PER_WORD) & ~(_BITS_PER_WORD - 1); + } + + return -1; +} + + +/** Loop through all bits set in a mask. */ +#define MRP_MASK_FOREACH_SET(m, bit, start) \ + for (bit = mrp_mask_next_set(m, start); \ + bit >= 0; \ + bit = mrp_mask_next_set(m, bit + 1)) + + +/** Loop through all bits cleared in a mask. */ +#define MRP_MASK_FOREACH_CLEAR(m, bit, start) \ + for (bit = mrp_mask_next_clear(m, start); \ + bit >= 0; \ + bit = mrp_mask_next_clear(m, bit + 1)) + +MRP_CDECL_END + +#endif /* __MURPHY_MASK_H__ */ diff --git a/src/common/mm.c b/src/common/mm.c new file mode 100644 index 0000000..b461241 --- /dev/null +++ b/src/common/mm.c @@ -0,0 +1,1414 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <errno.h> +#include <unistd.h> +#include <execinfo.h> + +#include <murphy/common/macros.h> +#include <murphy/common/log.h> +#include <murphy/common/list.h> +#include <murphy/common/mm.h> +#include <murphy/common/hashtbl.h> + + +#define DEFAULT_DEPTH 8 /* default backtrace depth */ +#define MAX_DEPTH 128 /* max. backtrace depth */ + +/* + * memory allocator state + */ + +typedef struct { + mrp_list_hook_t blocks; /* list of allocated blocks */ + size_t hdrsize; /* header size */ + int depth; /* backtrace depth */ + uint32_t cur_blocks; /* currently allocated blocks */ + uint32_t max_blocks; /* max allocated blocks */ + uint64_t cur_alloc; /* currently allocated memory */ + uint64_t max_alloc; /* max allocated memory */ + int poison; /* poisoning pattern */ + size_t chunk_size; /* object pool chunk size */ + mrp_mm_type_t mode; /* passthru/debug mode */ + + void *(*alloc)(size_t size, const char *file, int line, const char *func); + void *(*realloc)(void *ptr, size_t size, const char *file, + int line, const char *func); + int (*memalign)(void **ptr, size_t align, size_t size, + const char *file, int line, const char *func); + void (*free)(void *ptr, const char *file, int line, const char *func); +} mm_t; + + +/* + * memory block + */ + +typedef struct { + mrp_list_hook_t hook; /* to allocated blocks */ + mrp_list_hook_t more; /* with the same backtrace */ + const char *file; /* file of immediate caller */ + int line; /* line of immediate caller */ + const char *func; /* name of immediate caller */ + size_t size; /* requested size */ + void *bt[]; /* for accessing backtrace */ +} memblk_t; + + + +static mm_t __mm = { /* allocator state */ + .hdrsize = MRP_ALIGN(MRP_OFFSET(memblk_t, bt[DEFAULT_DEPTH]), + MRP_MM_ALIGN), + .depth = DEFAULT_DEPTH, + .poison = 0xdeadbeef, +}; + + +static const char *get_config_key(const char *config, const char *key) +{ + const char *beg; + int len; + + if (config != NULL) { + len = strlen(key); + + beg = config; + while (beg != NULL) { + beg = strstr(beg, key); + + if (beg != NULL) { + if ((beg == config || beg[-1] == ':') && + (beg[len] == '=' || beg[len] == ':' || beg[len] == '\0')) + return (beg[len] == '=' ? beg + len + 1 : ""); + else + beg++; + } + } + } + + return NULL; +} + + +static int32_t get_config_int32(const char *cfg, const char *key, + int32_t defval) +{ + const char *v; + char *end; + int i; + + v = get_config_key(cfg, key); + + if (v != NULL) { + if (*v) { + i = strtol(v, &end, 10); + + if (end && (!*end || *end == ':')) + return i; + } + } + + return defval; +} + + +static uint32_t get_config_uint32(const char *cfg, const char *key, + uint32_t defval) +{ + const char *v; + char *end; + int i; + + v = get_config_key(cfg, key); + + if (v != NULL) { + if (*v) { + i = strtol(v, &end, 10); + + if (end && (!*end || *end == ':')) + return i; + } + } + + return defval; +} + + +static int get_config_bool(const char *config, const char *key, int defval) +{ + const char *v; + + v = get_config_key(config, key); + + if (v != NULL) { + if (*v) { + + if ((!strncasecmp(v, "false", 5) && (v[5] == ':' || !v[5])) || + (!strncasecmp(v, "true" , 4) && (v[4] == ':' || !v[4]))) + return (v[0] == 't' || v[0] == 'T'); + } + else if (*v == '\0') + return TRUE; + } + + return defval; +} + + +static int get_config_string(const char *cfg, const char *key, + const char *defval, char *buf, size_t size) +{ + const char *v; + char *end; + int len; + + v = get_config_key(cfg, key); + + if (v == NULL) + v = defval; + + end = strchr(v, ':'); + + if (end != NULL) + len = end - v; + else + len = strlen(v); + + len = snprintf(buf, size, "%*.*s", len, len, v); + + if (len >= (int)size - 1) + buf[size - 1] = '\0'; + + return len; +} + + + +MRP_INIT_AT(101) static void setup(void) +{ + char *config = getenv(MRP_MM_CONFIG_ENVVAR); + + mrp_list_init(&__mm.blocks); + + __mm.depth = get_config_int32(config, "depth", DEFAULT_DEPTH); + + if (__mm.depth > MAX_DEPTH) + __mm.depth = MAX_DEPTH; + + __mm.hdrsize = MRP_ALIGN(MRP_OFFSET(memblk_t, bt[__mm.depth]), + MRP_MM_ALIGN); + + __mm.cur_blocks = 0; + __mm.max_blocks = 0; + __mm.cur_alloc = 0; + __mm.max_alloc = 0; + + __mm.poison = get_config_uint32(config, "poison", 0xdeadbeef); + __mm.chunk_size = sysconf(_SC_PAGESIZE) * 2; + + if (config == NULL || !get_config_bool(config, "debug", FALSE)) + mrp_mm_config(MRP_MM_PASSTHRU); + else + mrp_mm_config(MRP_MM_DEBUG); +} + + +static void __attribute__((destructor)) cleanup(void) +{ + if (__mm.mode == MRP_MM_DEBUG) { + mrp_mm_dump(stdout); + /*mrp_mm_check(stdout);*/ + } +} + + + +int32_t mrp_mm_config_int32(const char *key, int32_t defval) +{ + return get_config_int32(getenv(MRP_MM_CONFIG_ENVVAR), key, defval); +} + + +uint32_t mrp_mm_config_uint32(const char *key, uint32_t defval) +{ + return get_config_uint32(getenv(MRP_MM_CONFIG_ENVVAR), key, defval); +} + + +int mrp_mm_config_bool(const char *key, int defval) +{ + return get_config_bool(getenv(MRP_MM_CONFIG_ENVVAR), key, defval); +} + + +int mrp_mm_config_string(const char *key, const char *defval, + char *buf, size_t size) +{ + return get_config_string(getenv(MRP_MM_CONFIG_ENVVAR), key, defval, + buf, size); +} + + +/* + * memblk handling + */ + +static memblk_t *memblk_alloc(size_t size, const char *file, int line, + const char *func, void **bt) +{ + memblk_t *blk; + + if (MRP_UNLIKELY(size == 0)) + blk = NULL; + else { + if ((blk = malloc(__mm.hdrsize + size)) != NULL) { + mrp_list_init(&blk->hook); + mrp_list_init(&blk->more); + mrp_list_append(&__mm.blocks, &blk->hook); + + blk->file = file; + blk->line = line; + blk->func = func; + blk->size = size; + + memcpy(blk->bt, bt, __mm.depth * sizeof(*bt)); + + __mm.cur_blocks++; + __mm.cur_alloc += size; + + __mm.max_blocks = MRP_MAX(__mm.max_blocks, __mm.cur_blocks); + __mm.max_alloc = MRP_MAX(__mm.max_alloc , __mm.cur_alloc); + } + } + + return blk; +} + + +static void memblk_free(memblk_t *blk, const char *file, int line, + const char *func, void **bt) +{ + MRP_UNUSED(file); + MRP_UNUSED(line); + MRP_UNUSED(func); + MRP_UNUSED(bt); + + if (blk != NULL) { + mrp_list_delete(&blk->hook); + + __mm.cur_blocks--; + __mm.cur_alloc -= blk->size; + + if (__mm.poison != 0) + memset(&blk->bt[__mm.depth], __mm.poison, blk->size); + + free(blk); + } +} + + +static memblk_t *memblk_resize(memblk_t *blk, size_t size, const char *file, + int line, const char *func, void **bt) +{ + memblk_t *resized; + + if (blk != NULL) { + mrp_list_delete(&blk->hook); + + if (size != 0) { + resized = realloc(blk, __mm.hdrsize + size); + + if (resized != NULL) { + mrp_list_init(&resized->hook); + + mrp_list_append(&__mm.blocks, &resized->hook); + + __mm.cur_alloc -= resized->size; + __mm.cur_alloc += size; + __mm.max_alloc = MRP_MAX(__mm.max_alloc, __mm.cur_alloc); + + resized->file = file; + resized->line = line; + resized->func = func; + + memcpy(resized->bt, bt, __mm.depth * sizeof(*bt)); + + resized->size = size; + } + else + mrp_list_append(&__mm.blocks, &blk->hook); + } + else { + resized = NULL; + memblk_free(blk, file, line, func, bt); + } + + return resized; + } + else + return memblk_alloc(size, file, line, func, bt); +} + + +static inline void *memblk_to_ptr(memblk_t *blk) +{ + if (blk != NULL) + return (void *)&blk->bt[__mm.depth]; + else + return NULL; +} + + +static inline memblk_t *ptr_to_memblk(void *ptr) +{ + /* + * XXX Hmm... maybe we should also add pre- and post-sentinels + * and check them here to have minimal protection/detection of + * trivial buffer overflow bugs when running in debug mode. + */ + + if (ptr != NULL) + return ptr - MRP_OFFSET(memblk_t, bt[__mm.depth]); + else + return NULL; +} + + +/* + * debugging allocator + */ + +static inline int __mm_backtrace(void **bt, size_t size) +{ + int i, n; + + n = backtrace(bt, (int)size); + for (i = n; i < (int)size; i++) + bt[i] = NULL; + + return n; +} + + +static void *__mm_alloc(size_t size, const char *file, int line, + const char *func) +{ + memblk_t *blk; + void *bt[__mm.depth + 1]; + + __mm_backtrace(bt, MRP_ARRAY_SIZE(bt)); + blk = memblk_alloc(size, file, line, func, bt + 1); + + return memblk_to_ptr(blk); +} + + +static void *__mm_realloc(void *ptr, size_t size, const char *file, + int line, const char *func) +{ + memblk_t *blk; + void *bt[__mm.depth + 1]; + + __mm_backtrace(bt, MRP_ARRAY_SIZE(bt)); + blk = ptr_to_memblk(ptr); + + if (blk != NULL) + blk = memblk_resize(blk, size, file, line, func, bt + 1); + else + blk = memblk_alloc(size, file, line, func, bt + 1); + + return memblk_to_ptr(blk); +} + + +static int __mm_memalign(void **ptr, size_t align, size_t size, + const char *file, int line, const char *func) +{ + MRP_UNUSED(align); + MRP_UNUSED(size); + MRP_UNUSED(file); + MRP_UNUSED(line); + MRP_UNUSED(func); + + *ptr = NULL; + errno = ENOSYS; + + mrp_log_error("XXX %s not implemented!!!", __FUNCTION__); + return -1; +} + + +static void __mm_free(void *ptr, const char *file, int line, + const char *func) +{ + memblk_t *blk; + void *bt[__mm.depth + 1]; + + if (ptr != NULL) { + __mm_backtrace(bt, MRP_ARRAY_SIZE(bt)); + blk = ptr_to_memblk(ptr); + + if (blk != NULL) + memblk_free(blk, file, line, func, bt + 1); + } +} + + +/* + * passthru allocator + */ + +static void *__passthru_alloc(size_t size, const char *file, int line, + const char *func) +{ + MRP_UNUSED(file); + MRP_UNUSED(line); + MRP_UNUSED(func); + + if (MRP_UNLIKELY(size == 0)) + return NULL; + else + return malloc(size); +} + + +static void *__passthru_realloc(void *ptr, size_t size, const char *file, + int line, const char *func) +{ + MRP_UNUSED(file); + MRP_UNUSED(line); + MRP_UNUSED(func); + + return realloc(ptr, size); +} + + +static int __passthru_memalign(void **ptr, size_t align, size_t size, + const char *file, int line, const char *func) +{ + MRP_UNUSED(file); + MRP_UNUSED(line); + MRP_UNUSED(func); + + return posix_memalign(ptr, align, size); +} + + +static void __passthru_free(void *ptr, const char *file, int line, + const char *func) +{ + MRP_UNUSED(file); + MRP_UNUSED(line); + MRP_UNUSED(func); + + free(ptr); +} + + +/* + * common public interface - uses either passthru or debugging + */ + +void *mrp_mm_alloc(size_t size, const char *file, int line, const char *func) +{ + return __mm.alloc(size, file, line, func); +} + + +void *mrp_mm_realloc(void *ptr, size_t size, const char *file, int line, + const char *func) +{ + return __mm.realloc(ptr, size, file, line, func); +} + + +char *mrp_mm_strdup(const char *s, const char *file, int line, const char *func) +{ + char *p; + size_t size; + + if (s != NULL) { + size = strlen(s) + 1; + p = mrp_mm_alloc(size, file, line, func); + + if (p != NULL) + strcpy(p, s); + } + else + p = NULL; + + return p; +} + + +int mrp_mm_memalign(void **ptr, size_t align, size_t size, const char *file, + int line, const char *func) +{ + return __mm.memalign(ptr, align, size, file, line, func); +} + + +void mrp_mm_free(void *ptr, const char *file, int line, const char *func) +{ + return __mm.free(ptr, file, line, func); +} + + +int mrp_mm_config(mrp_mm_type_t type) +{ + if (__mm.cur_blocks != 0) + return FALSE; + + switch (type) { + case MRP_MM_PASSTHRU: + __mm.alloc = __passthru_alloc; + __mm.realloc = __passthru_realloc; + __mm.memalign = __passthru_memalign; + __mm.free = __passthru_free; + __mm.mode = MRP_MM_PASSTHRU; + return TRUE; + + case MRP_MM_DEBUG: + __mm.alloc = __mm_alloc; + __mm.realloc = __mm_realloc; + __mm.memalign = __mm_memalign; + __mm.free = __mm_free; + __mm.mode = MRP_MM_DEBUG; + return TRUE; + + default: + mrp_log_error("Invalid memory allocator type 0x%x requested.", type); + return FALSE; + } +} + + +#define NBUCKET 1024 + +static int btcmp(void **bt1, void **bt2) +{ + ptrdiff_t diff; + int i; + + for (i = 0; i < __mm.depth; i++) { + diff = bt1[i] - bt2[i]; + + if (diff < 0) + return -1; + else if (diff > 0) + return +1; + } + + return 0; +} + + +static uint32_t blkhash(memblk_t *blk) +{ + uint32_t h; + int i; + + h = 0; + for (i = 0; i < __mm.depth; i++) + h ^= (blk->bt[i] - NULL) & 0xffffffffUL; + + return h % NBUCKET; +} + + +static memblk_t *blkfind(mrp_list_hook_t *buckets, memblk_t *blk) +{ + uint32_t h = blkhash(blk); + mrp_list_hook_t *head = buckets + h; + mrp_list_hook_t *p, *n; + memblk_t *b; + + mrp_list_foreach(head, p, n) { + b = mrp_list_entry(p, typeof(*b), hook); + if (!btcmp(&b->bt[0], &blk->bt[0])) + return b; + } + + return NULL; +} + + +static void collect_blocks(mrp_list_hook_t *buckets) +{ + mrp_list_hook_t *p, *n; + memblk_t *head, *blk; + uint32_t h; + int i; + + for (i = 0; i < NBUCKET; i++) + mrp_list_init(buckets + i); + + mrp_list_foreach(&__mm.blocks, p, n) { + blk = mrp_list_entry(p, typeof(*blk), hook); + + mrp_list_init(&blk->more); + head = blkfind(buckets, blk); + + if (head != NULL) { + mrp_list_append(&head->more, &blk->more); + head->size += blk->size; + } + else { + h = blkhash(blk); + mrp_list_delete(&blk->hook); + mrp_list_append(buckets + h, &blk->hook); + } + } +} + + +static uint32_t group_usage(memblk_t *head, int exclude_head) +{ + mrp_list_hook_t *p, *n; + memblk_t *blk; + uint32_t total; + + total = exclude_head ? 0 : head->size; + mrp_list_foreach(&head->more, p, n) { + blk = mrp_list_entry(p, typeof(*blk), more); + total += blk->size; + } + + return total; +} + + +static void dump_group(FILE *fp, memblk_t *head) +{ + mrp_list_hook_t *p, *n; + memblk_t *blk; + char **syms, *sym; + uint32_t total; + int nblk, i; + + fprintf(fp, "Allocations with call stack fingerprint:\n"); + syms = backtrace_symbols(head->bt, __mm.depth); + for (i = 0; i < __mm.depth && head->bt[i]; i++) { + sym = syms && syms[i] ? strrchr(syms[i], '/') : NULL; + fprintf(fp, " %p (%s)\n", head->bt[i], sym ? sym + 1 : "<unknown>"); + } + free(syms); + + total = head->size - group_usage(head, TRUE); + nblk = 1; + + fprintf(fp, " %lu bytes at %p\n", (unsigned long)total, + memblk_to_ptr(head)); + + mrp_list_foreach(&head->more, p, n) { + blk = mrp_list_entry(p, typeof(*blk), more); + + total += blk->size; + nblk++; + + fprintf(fp, " %zd bytes at %p\n", blk->size, memblk_to_ptr(blk)); + } + + if (nblk > 1) + fprintf(fp, " total %lu bytes in %d blocks\n", + (unsigned long)total, nblk); +} + + +static void sort_blocks(mrp_list_hook_t *buckets, mrp_list_hook_t *sorted) +{ + mrp_list_hook_t *bp, *bn, *sp, *sn; + memblk_t *head, *entry, *next; + int i; + + mrp_list_init(sorted); + + for (i = 0; i < NBUCKET; i++) { + mrp_list_foreach(buckets + i, bp, bn) { + head = mrp_list_entry(bp, typeof(*head), hook); + + next = NULL; + mrp_list_foreach(sorted, sp, sn) { + entry = mrp_list_entry(sp, typeof(*entry), hook); + + if (head->size <= entry->size) { + next = entry; + break; + } + } + + mrp_list_delete(&head->hook); + + if (next != NULL) + mrp_list_insert_before(&next->hook, &head->hook); + else + mrp_list_append(sorted, &head->hook); + } + } +} + + +static void dump_blocks(FILE *fp, mrp_list_hook_t *sorted) +{ + mrp_list_hook_t *p, *n; + memblk_t *head; + + mrp_list_foreach(sorted, p, n) { + head = mrp_list_entry(p, typeof(*head), hook); + dump_group(fp, head); + } +} + + +static void relink_blocks(mrp_list_hook_t *sorted) +{ + mrp_list_hook_t *p, *n; + memblk_t *head; + uint32_t rest; + + mrp_list_foreach(sorted, p, n) { + head = mrp_list_entry(p, typeof(*head), hook); + mrp_list_delete(&head->hook); + mrp_list_append(&__mm.blocks, &head->hook); + + rest = group_usage(head, TRUE); + head->size -= rest; + } +} + + +void mrp_mm_dump(FILE *fp) +{ + mrp_list_hook_t buckets[NBUCKET]; + mrp_list_hook_t sorted; + + mrp_list_init(&sorted); + + collect_blocks(buckets); + sort_blocks(buckets, &sorted); + dump_blocks(fp, &sorted); + relink_blocks(&sorted); + + fprintf(fp, "Max: %llu bytes (%.2f M, %.2f G), %ld blocks\n", + (unsigned long long)__mm.max_alloc, + 1.0 * __mm.max_alloc / (1024 * 1024), + 1.0 * __mm.max_alloc / (1024 * 1024 * 1024), + (unsigned long)__mm.max_blocks); + fprintf(fp, "Current: %llu bytes (%.2f M, %.2f G) in %ld blocks.\n", + (unsigned long long)__mm.cur_alloc, + 1.0 * __mm.cur_alloc / (1024 * 1024), + 1.0 * __mm.cur_alloc / (1024 * 1024 * 1024), + (unsigned long)__mm.cur_blocks); +} + + +void mrp_mm_check(FILE *fp) +{ + mrp_mm_dump(fp); +} + + + + + +/* + * object pool interface + */ + +typedef unsigned int mask_t; + +#define W sizeof(mask_t) +#define B (W * 8) +#define MASK_BYTES (sizeof(mask_t)) +#define MASK_BITS (MASK_BYTES * 8) +#define MASK_EMPTY ((mask_t)-1) +#define MASK_FULL ((mask_t) 0) + +typedef struct pool_chunk_s pool_chunk_t; + +static int pool_calc_sizes(mrp_objpool_t *pool); +static int pool_grow(mrp_objpool_t *pool, int nobj); +static int pool_shrink(mrp_objpool_t *pool, int nobj); +static pool_chunk_t *chunk_alloc(int nperchunk); +static void chunk_free(pool_chunk_t *chunk); +static inline int chunk_empty(pool_chunk_t *chunk); +static void pool_foreach_object(mrp_objpool_t *pool, + void (*cb)(void *obj, void *user_data), + void *user_data); +static void chunk_foreach_object(pool_chunk_t *chunk, + void (*cb)(void *obj, void *user_data), + void *user_data); + + +/* + * an object pool + */ + +struct mrp_objpool_s { + char *name; /* verbose pool name */ + size_t limit; /* max. number of objects */ + size_t objsize; /* size of a single object */ + size_t prealloc; /* preallocate this many */ + size_t nobj; /* currently allocated */ + int (*setup)(void *); /* object setup callback */ + void (*cleanup)(void *); /* object cleanup callback */ + uint32_t flags; /* pool flags */ + int poison; /* poisoning pattern */ + + size_t nperchunk; /* objects per chunk */ + size_t dataidx; /* data */ + mrp_list_hook_t space; /* chunk with frees slots */ + size_t nspace; /* number of such chunks */ + mrp_list_hook_t full; /* fully allocated chunks */ + size_t nfull; /* number of such chunks */ +}; + + +/* + * a chunk of memory allocated to an object pool + */ + +struct pool_chunk_s { + mrp_objpool_t *pool; /* pool we're alloced to */ + mrp_list_hook_t hook; /* hook to chunk list */ + mask_t cache; /* cache bits */ + mask_t used[]; /* allocation mask */ +}; + + + +mrp_objpool_t *mrp_objpool_create(mrp_objpool_config_t *cfg) +{ + mrp_objpool_t *pool; + + if ((pool = mrp_allocz(sizeof(*pool))) != NULL) { + if ((pool->name = mrp_strdup(cfg->name)) == NULL) + goto fail; + + pool->limit = cfg->limit; + pool->objsize = MRP_MAX(cfg->objsize, (size_t)MRP_MM_OBJSIZE_MIN); + pool->prealloc = cfg->prealloc; + pool->setup = cfg->setup; + pool->cleanup = cfg->cleanup; + pool->flags = cfg->flags; + pool->poison = cfg->poison; + + mrp_list_init(&pool->space); + mrp_list_init(&pool->full); + pool->nspace = 0; + pool->nfull = 0; + + if (!pool_calc_sizes(pool)) + goto fail; + + if (!mrp_objpool_grow(pool, pool->prealloc)) + goto fail; + + mrp_debug("pool <%s> created, with %zd/%zd objects.", pool->name, + pool->prealloc, pool->limit); + + return pool; + } + + + fail: + mrp_objpool_destroy(pool); + return NULL; +} + + +static void free_object(void *obj, void *user_data) +{ + mrp_objpool_t *pool = (mrp_objpool_t *)user_data; + + printf("Releasing unfreed object %p from pool <%s>.\n", obj, pool->name); + mrp_objpool_free(obj); +} + + +void mrp_objpool_destroy(mrp_objpool_t *pool) +{ + if (pool != NULL) { + if (pool->cleanup != NULL) + pool_foreach_object(pool, free_object, pool); + + mrp_free(pool->name); + mrp_free(pool); + } +} + + +void *mrp_objpool_alloc(mrp_objpool_t *pool) +{ + pool_chunk_t *chunk; + void *obj; + unsigned int cidx, uidx, sidx; + + if (pool->limit && pool->nobj >= pool->limit) + return NULL; + + if (mrp_list_empty(&pool->space)) { + if (!pool_grow(pool, 1)) + return NULL; + } + + chunk = mrp_list_entry(pool->space.next, pool_chunk_t, hook); + cidx = ffs(chunk->cache); + + if (!cidx) { + mrp_log_error("object pool bug: no free slots in cache mask."); + return NULL; + } + else + cidx--; + + uidx = ffs(chunk->used[cidx]); + + if (!uidx) { + mrp_log_error("object pool bug: no free slots in used mask."); + return NULL; + } + else + uidx--; + + sidx = cidx * MASK_BITS + uidx; + obj = ((void *)&chunk->used[pool->dataidx]) + (sidx * pool->objsize); + + mrp_debug("%p: %u/%u: %u, offs %zd\n", obj, cidx, uidx, sidx, + sidx * pool->objsize); + + chunk->used[cidx] &= ~(1 << uidx); + + if (chunk->used[cidx] == MASK_FULL) { + chunk->cache &= ~(1 << cidx); + + if (chunk->cache == MASK_FULL) { /* chunk exhausted */ + mrp_list_delete(&chunk->hook); + pool->nspace--; + mrp_list_append(&pool->full, &chunk->hook); + pool->nfull++; + } + } + + if (pool->setup == NULL || pool->setup(obj)) { + pool->nobj++; + return obj; + } + else { + mrp_objpool_free(obj); + return NULL; + } +} + + +void mrp_objpool_free(void *obj) +{ + pool_chunk_t *chunk; + mrp_objpool_t *pool; + unsigned int cidx, uidx, sidx; + mask_t cache, used; + void *base; + + if (obj == NULL) + return; + + chunk = (pool_chunk_t *)(((ptrdiff_t)obj) & ~(__mm.chunk_size - 1)); + pool = chunk->pool; + + base = (void *)&chunk->used[pool->dataidx]; + sidx = (obj - base) / pool->objsize; + cidx = sidx / MASK_BITS; + uidx = sidx & (MASK_BITS - 1); + + mrp_debug("%p: %u/%u: %u, offs %zd\n", obj, cidx, uidx, sidx, + sidx * pool->objsize); + + cache = chunk->cache; + used = chunk->used[cidx]; + + if (used & (1 << uidx)) { + mrp_log_error("Trying to free unallocated object %p of pool <%s>.", + obj, pool->name); + return; + } + + if (pool->cleanup != NULL) + pool->cleanup(obj); + + if (pool->flags & MRP_OBJPOOL_FLAG_POISON) + memset(obj, pool->poison, pool->objsize); + + chunk->used[cidx] |= (1 << uidx); + chunk->cache |= (1 << cidx); + + if (cache == MASK_FULL) { /* chunk was full */ + mrp_list_delete(&chunk->hook); + pool->nfull--; + mrp_list_append(&pool->space, &chunk->hook); + pool->nspace++; + } + + pool->nobj--; +} + + +int mrp_objpool_grow(mrp_objpool_t *pool, int nobj) +{ + int nchunk = (nobj + pool->nperchunk - 1) / pool->nperchunk; + + return pool_grow(pool, nchunk) == nchunk; +} + + +int mrp_objpool_shrink(mrp_objpool_t *pool, int nobj) +{ + int nchunk = (nobj + pool->nperchunk - 1) / pool->nperchunk; + + return pool_shrink(pool, nchunk) == nchunk; +} + + +static int pool_calc_sizes(mrp_objpool_t *pool) +{ + size_t S, C, Hf, Hv, P; + size_t n, T; + + if (!pool->objsize) + return FALSE; + + pool->objsize = MRP_ALIGN(pool->objsize, MRP_MM_ALIGN); + + /* + * Pool chunks consist of an administrative header followed by object + * slots each of which can be either claimed/allocated or free. The + * header contains a back pointer to the pool, a hook to one of the + * chunk lists and a two-level bit-mask for slot allocation status. + * The two-level mask consists of a 32-bit cache word and actual slot + * status words. The nth bit of the cache word caches whether there are + * any free among the nth - (n + 31)th slots. The slot status words keep + * the status of the actual slots. To find a free slot we find the idx + * of the 1st word with a free slot from the cache and then the free + * slot index in that word. To be able to use FFS we use inverted bit + * semantics (0=allocated, 1=free) and we populate the words starting + * at the LSB. + * + * Here we calculate how many objects we'll be able to squeeze into a + * single pool chunk and how many mask bits we'll need to administer + * the status of these. To do this we use the following equations: + * + * 1) Hf + Hv + n * S = C + * 2) Hv = W + (n + B - 1) / B * W + * where + * C: chunk size + * S: object size (aligned to our minimum alignment) + * Hf: header size, fixed part + * Hv: Header size, variable part (allocation mask) + * n: number of objects / chunk + * W: bitmask word size in bytes + * B: bitmask word size in bits + * + * Solving the equations for n gives us + * n = (B*C - B*Hf - W*(2*B - 1)) / (B*S + W) + * + * If any, the only non-obvious thing below is that instead of trying + * to express padding as part of the equation system (which seems to be + * way beyond my abilities in math nowadays), we initally assume no + * padding then check and compensate for it in the end if necessary. + */ + + Hf = sizeof(pool_chunk_t); + C = __mm.chunk_size; + P = 0; + + S = MRP_ALIGN(pool->objsize, MRP_MM_ALIGN); + n = (B * C - B * Hf - W * (2*B - 1)) / (B * S + W); + Hv = W + W * (n + B - 1) / B; + + P = (Hf + Hv) % sizeof(void *); + if (P != 0) { + P = sizeof(void *) - P; + + if (Hv + Hf + P + n * S > C) { + n--; + Hv = W + W * (n + B - 1) / B; + } + } + + T = Hf + Hv + P + n * S; + + if (T > C) { + mrp_log_error("Could not size pool '%s' properly.", pool->name); + return FALSE; + } + + pool->nperchunk = n; + pool->dataidx = (n + B - 1) / B; + + if (pool->limit && (pool->limit % pool->nperchunk) != 0) + pool->limit += (pool->nperchunk - (pool->limit % pool->nperchunk)); + + return TRUE; +} + + +static int pool_grow(mrp_objpool_t *pool, int nchunk) +{ + pool_chunk_t *chunk; + int cnt; + + for (cnt = 0; cnt < nchunk; cnt++) { + chunk = chunk_alloc(pool->nperchunk); + + if (chunk != NULL) { + chunk->pool = pool; + mrp_list_append(&pool->space, &chunk->hook); + pool->nspace++; + } + else + break; + } + + return cnt; +} + + +static int pool_shrink(mrp_objpool_t *pool, int nchunk) +{ + mrp_list_hook_t *p, *n; + pool_chunk_t *chunk; + int cnt; + + cnt = 0; + mrp_list_foreach(&pool->space, p, n) { + chunk = mrp_list_entry(p, pool_chunk_t, hook); + + if (chunk_empty(chunk)) { + mrp_list_delete(&chunk->hook); + chunk_free(chunk); + pool->nspace--; + cnt++; + } + + if (cnt >= nchunk) + break; + } + + return cnt; +} + + +static void pool_foreach_object(mrp_objpool_t *pool, + void (*cb)(void *obj, void *user_data), + void *user_data) +{ + mrp_list_hook_t *p, *n; + pool_chunk_t *chunk; + + mrp_list_foreach(&pool->full, p, n) { + chunk = mrp_list_entry(p, pool_chunk_t, hook); + chunk_foreach_object(chunk, cb, user_data); + } + + mrp_list_foreach(&pool->space, p, n) { + chunk = mrp_list_entry(p, pool_chunk_t, hook); + chunk_foreach_object(chunk, cb, user_data); + } +} + + +static void chunk_foreach_object(pool_chunk_t *chunk, + void (*cb)(void *obj, void *user_data), + void *user_data) +{ + mrp_objpool_t *pool = chunk->pool; + void *obj; + int sidx, cidx, uidx; + mask_t used; + + sidx = 0; + while (sidx < (int)pool->nperchunk) { + cidx = sidx / MASK_BITS; + uidx = sidx & (MASK_BITS - 1); + used = chunk->used[cidx]; + + if (!(used & (1 << uidx))) { + obj = ((void *)&chunk->used[pool->dataidx]) + (sidx*pool->objsize); + cb(obj, user_data); + sidx++; + } + else { + if (used == MASK_EMPTY) + sidx = (sidx + MASK_BITS) & ~(MASK_BITS - 1); + else + sidx++; + } + } +} + + +static inline int chunk_empty(pool_chunk_t *chunk) +{ + mask_t mask; + int i, n; + + if (chunk->cache != (MASK_EMPTY & 0xffff)) + return FALSE; + else { + for (n = chunk->pool->nperchunk, i = 0; n > 0; n -= MASK_BITS, i++) { + if (n >= (int)MASK_BITS) + mask = MASK_EMPTY; + else + mask = (1 << n) - 1; + + if ((chunk->used[i] & mask) != mask) + return FALSE; + } + + return TRUE; + } +} + + +static void chunk_init(pool_chunk_t *chunk, int nperchunk) +{ + int nword, left, i; + + mrp_list_init(&chunk->hook); + + left = nperchunk; + nword = (nperchunk + MASK_BITS - 1) / MASK_BITS; + + /* + * initialize allocation bitmask + * + * Note that every bit that corresponds to a non-allocatable slots + * we mark as reserved. This keeps both the allocation and freeing + * code paths simpler. + */ + + chunk->cache = (1 << nword) - 1; + + for (i = 0; left > 0; i++) { + if (left >= (int)MASK_BITS) + chunk->used[i] = MASK_EMPTY; + else + chunk->used[i] = (((mask_t)1) << left) - 1; + + left -= B; + } +} + + +static pool_chunk_t *chunk_alloc(int nperchunk) +{ + void *chunk; + int err; + + err = posix_memalign(&chunk, __mm.chunk_size, __mm.chunk_size); + + if (err == 0) { + memset(chunk, 0, __mm.chunk_size); + chunk_init((pool_chunk_t *)chunk, nperchunk); + } + else + chunk = NULL; + + return chunk; +} + + +static void chunk_free(pool_chunk_t *chunk) +{ + free(chunk); +} + + +#if 0 +static void test_sizes(void) +{ + size_t S, C, Hf, Hv, P; + size_t i, n, T, Hv1, n1, T1; + int ok, ok1; + + Hf = sizeof(pool_chunk_t); + C = __mm.chunk_size; + P = 0; + + printf(" C: %zd\n", C); + printf("Hf: %zd\n", Hf); + + for (i = 1; i < __mm.chunk_size / 8; i++) { + S = MRP_ALIGN(i, MRP_MM_ALIGN); + n = (B * C - B * Hf - W * (2*B - 1)) / (B * S + W); + Hv = W + W * (n + B - 1) / B; + + P = (Hf + Hv) % sizeof(void *); + if (P != 0) { + P = sizeof(void *) - P; + + if (Hv + Hf + P + n * S > C) { + n--; + Hv = W + W * (n + B - 1) / B; + } + } + + T = Hf + Hv + P + n * S; + ok = T <= C; + + n1 = n + 1; + Hv1 = W + W * (n1 + B - 1) / B; + T1 = Hf + Hv1 + P + n1 * S; + ok1 = T1 > C; + + printf(" i = %zd: %zd * %zd + %zd (%zd: %s, %zd: %s)\n", i, n, S, P, + T , ok ? "OK" : "FAIL", + T1, ok1 ? "OK" : "FAIL"); + { + size_t hs, us; + + us = sizeof(uint32_t); + hs = (Hf + Hv + P) / us; + + printf(" H+P: %zd (%zd * %zd = %zd)\n", Hf + Hv + P, + hs, us, hs * us); + + if (((Hf + Hv + P) % sizeof(void *)) != 0) { + printf("Padding error!\n"); + exit(1); + } + } + + if (!ok || !ok1) + exit(1); + } +} +#endif diff --git a/src/common/mm.h b/src/common/mm.h new file mode 100644 index 0000000..73ffacb --- /dev/null +++ b/src/common/mm.h @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_MM_H__ +#define __MURPHY_MM_H__ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> + +#include <murphy/common/macros.h> + +MRP_CDECL_BEGIN + +#define MRP_MM_ALIGN 8 /* object alignment */ +#define MRP_MM_CONFIG_ENVVAR "__MURPHY_MM_CONFIG" + +#define mrp_alloc(size) mrp_mm_alloc((size), __LOC__) +#define mrp_free(ptr) mrp_mm_free((ptr), __LOC__) +#define mrp_strdup(s) mrp_mm_strdup((s), __LOC__) +#define mrp_datadup(ptr, size) ({ \ + typeof(ptr) _ptr = mrp_alloc(size); \ + \ + if (_ptr != NULL) \ + memcpy(_ptr, ptr, size); \ + \ + _ptr; \ + }) + +#define mrp_allocz(size) ({ \ + void *_ptr; \ + \ + if ((_ptr = mrp_mm_alloc(size, __LOC__)) != NULL) \ + memset(_ptr, 0, size); \ + \ + _ptr;}) + +#define mrp_calloc(n, size) mrp_allocz((n) * (size)) + +#define mrp_reallocz(ptr, o, n) ({ \ + typeof(ptr) _ptr; \ + typeof(o) _o; \ + typeof(n) _n = (n); \ + size_t _size = sizeof(*_ptr) * (_n); \ + \ + if ((ptr) != NULL) \ + _o = o; \ + else \ + _o = 0; \ + \ + _ptr = (typeof(ptr))mrp_mm_realloc(ptr, _size, __LOC__); \ + if (_ptr != NULL || _n == 0) { \ + if ((unsigned)(_n) > (unsigned)(_o)) \ + memset(_ptr + (_o), 0, \ + ((_n)-(_o)) * sizeof(*_ptr)); \ + ptr = _ptr; \ + } \ + _ptr; }) + +#define mrp_realloc(ptr, size) ({ \ + typeof(ptr) _ptr; \ + size_t _size = size; \ + \ + _ptr = (typeof(ptr))mrp_mm_realloc(ptr, _size, __LOC__); \ + if (_ptr != NULL || _size == 0) \ + ptr = _ptr; \ + \ + _ptr; }) + +#define mrp_clear(obj) memset((obj), 0, sizeof(*(obj))) + + +#define mrp_alloc_array(type, n) ((type *)mrp_alloc(sizeof(type) * (n))) +#define mrp_allocz_array(type, n) ((type *)mrp_allocz(sizeof(type) * (n))) + +typedef enum { + MRP_MM_PASSTHRU = 0, /* passthru allocator */ + MRP_MM_DEFAULT = MRP_MM_PASSTHRU, /* default is passthru */ + MRP_MM_DEBUG /* debugging allocator */ +} mrp_mm_type_t; + + +int mrp_mm_config(mrp_mm_type_t type); +void mrp_mm_check(FILE *fp); +void mrp_mm_dump(FILE *fp); + +void *mrp_mm_alloc(size_t size, const char *file, int line, const char *func); +void *mrp_mm_realloc(void *ptr, size_t size, const char *file, int line, + const char *func); +char *mrp_mm_strdup(const char *s, const char *file, int line, + const char *func); +int mrp_mm_memalign(void **ptr, size_t align, size_t size, const char *file, + int line, const char *func); +void mrp_mm_free(void *ptr, const char *file, int line, const char *func); + + + + +#define MRP_MM_OBJSIZE_MIN 16 /* minimum object size */ + +enum { + MRP_OBJPOOL_FLAG_POISON = 0x1, /* poison free'd objects */ +}; + + +/* + * object pool configuration + */ + +typedef struct { + char *name; /* verbose pool name */ + size_t limit; /* max. number of objects */ + size_t objsize; /* size of a single object */ + size_t prealloc; /* preallocate this many */ + int (*setup)(void *); /* object setup callback */ + void (*cleanup)(void *); /* object cleanup callback */ + uint32_t flags; /* MRP_OBJPOOL_FLAG_* */ + int poison; /* poisoning pattern */ +} mrp_objpool_config_t; + + +typedef struct mrp_objpool_s mrp_objpool_t; + +/** Create a new object pool with the given configuration. */ +mrp_objpool_t *mrp_objpool_create(mrp_objpool_config_t *cfg); + +/** Destroy an object pool, freeing all associated memory. */ +void mrp_objpool_destroy(mrp_objpool_t *pool); + +/** Allocate a new object from the pool. */ +void *mrp_objpool_alloc(mrp_objpool_t *pool); + +/** Free the given object. */ +void mrp_objpool_free(void *obj); + +/** Grow @pool to accomodate @nobj new objects. */ +int mrp_objpool_grow(mrp_objpool_t *pool, int nobj); + +/** Shrink @pool by @nobj new objects, if possible. */ +int mrp_objpool_shrink(mrp_objpool_t *pool, int nobj); + +/** Get the value of a boolean key from the configuration. */ +int mrp_mm_config_bool(const char *key, int defval); + +/** Get the value of a boolean key from the configuration. */ +int32_t mrp_mm_config_int32(const char *key, int32_t defval); + +/** Get the value of a boolean key from the configuration. */ +uint32_t mrp_mm_config_uint32(const char *key, uint32_t defval); + +/** Get the value of a string key from the configuration. */ +int mrp_mm_config_string(const char *key, const char *defval, + char *buf, size_t size); + +MRP_CDECL_END + +#endif /* __MURPHY_MM_H__ */ + diff --git a/src/common/msg.c b/src/common/msg.c new file mode 100644 index 0000000..2db8214 --- /dev/null +++ b/src/common/msg.c @@ -0,0 +1,2222 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include <errno.h> +#include <stdarg.h> +#include <ctype.h> +#include <arpa/inet.h> + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/log.h> +#include <murphy/common/list.h> +#include <murphy/common/msg.h> + +#define NDIRECT_TYPE 256 /* directly indexed types */ + +static mrp_data_descr_t **direct_types; /* directly indexed types */ +static mrp_data_descr_t **other_types; /* linearly searched types */ +static int nother_type; + + +static inline void destroy_field(mrp_msg_field_t *f) +{ + uint32_t i; + + if (f != NULL) { + mrp_list_delete(&f->hook); + + switch (f->type) { + case MRP_MSG_FIELD_STRING: + mrp_free(f->str); + break; + + case MRP_MSG_FIELD_BLOB: + mrp_free(f->blb); + break; + + default: + if (f->type & MRP_MSG_FIELD_ARRAY) { + if ((f->type & ~MRP_MSG_FIELD_ARRAY) == MRP_MSG_FIELD_STRING) { + for (i = 0; i < f->size[0]; i++) { + mrp_free(f->astr[i]); + } + } + + mrp_free(f->aany); + } + break; + } + + mrp_free(f); + } +} + + +static inline mrp_msg_field_t *create_field(uint16_t tag, va_list *ap) +{ + mrp_msg_field_t *f; + uint16_t type, base; + uint32_t size; + void *blb; + + type = va_arg(*ap, uint32_t); + +#define CREATE(_f, _tag, _type, _fldtype, _fld, _last, _errlbl) do { \ + \ + (_f) = mrp_allocz(MRP_OFFSET(typeof(*_f), _last) + \ + sizeof(_f->_last)); \ + \ + if ((_f) != NULL) { \ + mrp_list_init(&(_f)->hook); \ + (_f)->tag = _tag; \ + (_f)->type = _type; \ + (_f)->_fld = va_arg(*ap, _fldtype); \ + } \ + else { \ + goto _errlbl; \ + } \ + } while (0) + +#define CREATE_ARRAY(_f, _tag, _type, _fld, _fldtype, _errlbl) do { \ + uint16_t _base; \ + uint32_t _i; \ + \ + (_f) = mrp_allocz(MRP_OFFSET(typeof(*_f), size[1])); \ + \ + if ((_f) != NULL) { \ + mrp_list_init(&(_f)->hook); \ + (_f)->tag = _tag; \ + (_f)->type = _type | MRP_MSG_FIELD_ARRAY; \ + _base = _type & ~MRP_MSG_FIELD_ARRAY; \ + \ + _f->size[0] = va_arg(*ap, uint32_t); \ + _f->_fld = mrp_allocz_array(typeof(*_f->_fld), \ + _f->size[0]); \ + \ + if (_f->_fld == NULL && _f->size[0] != 0) \ + goto _errlbl; \ + else \ + memcpy(_f->_fld, va_arg(*ap, typeof(_f->_fld)), \ + _f->size[0] * sizeof(_f->_fld[0])); \ + \ + if (_base == MRP_MSG_FIELD_STRING) { \ + for (_i = 0; _i < _f->size[0]; _i++) { \ + _f->astr[_i] = mrp_strdup(_f->astr[_i]); \ + if (_f->astr[_i] == NULL) \ + goto _errlbl; \ + } \ + } \ + } \ + else \ + goto _errlbl; \ + } while (0) + + f = NULL; + + switch (type) { + case MRP_MSG_FIELD_STRING: + CREATE(f, tag, type, char *, str, str, fail); + f->str = mrp_strdup(f->str); + if (f->str == NULL) + goto fail; + break; + case MRP_MSG_FIELD_BOOL: + CREATE(f, tag, type, int, bln, bln, fail); + break; + case MRP_MSG_FIELD_UINT8: + CREATE(f, tag, type, unsigned int, u8, u8, fail); + break; + case MRP_MSG_FIELD_SINT8: + CREATE(f, tag, type, signed int, s8, s8, fail); + break; + case MRP_MSG_FIELD_UINT16: + CREATE(f, tag, type, unsigned int, u16, u16, fail); + break; + case MRP_MSG_FIELD_SINT16: + CREATE(f, tag, type, signed int, s16, s16, fail); + break; + case MRP_MSG_FIELD_UINT32: + CREATE(f, tag, type, unsigned int, u32, u32, fail); + break; + case MRP_MSG_FIELD_SINT32: + CREATE(f, tag, type, signed int, s32, s32, fail); + break; + case MRP_MSG_FIELD_UINT64: + CREATE(f, tag, type, uint64_t, u64, u64, fail); + break; + case MRP_MSG_FIELD_SINT64: + CREATE(f, tag, type, int64_t, s64, s64, fail); + break; + case MRP_MSG_FIELD_DOUBLE: + CREATE(f, tag, type, double, dbl, dbl, fail); + break; + + case MRP_MSG_FIELD_BLOB: + size = va_arg(*ap, uint32_t); + CREATE(f, tag, type, void *, blb, size[0], fail); + + blb = f->blb; + f->size[0] = size; + f->blb = mrp_allocz(size); + + if (f->blb != NULL) { + memcpy(f->blb, blb, size); + f->size[0] = size; + } + else + goto fail; + break; + + default: + if (!(type & MRP_MSG_FIELD_ARRAY)) { + errno = EINVAL; + goto fail; + } + + base = type & ~MRP_MSG_FIELD_ARRAY; + + switch (base) { + case MRP_MSG_FIELD_STRING: + CREATE_ARRAY(f, tag, base, astr, char *, fail); + break; + case MRP_MSG_FIELD_BOOL: + CREATE_ARRAY(f, tag, base, abln, int, fail); + break; + case MRP_MSG_FIELD_UINT8: + CREATE_ARRAY(f, tag, base, au8, unsigned int, fail); + break; + case MRP_MSG_FIELD_SINT8: + CREATE_ARRAY(f, tag, base, as8, int, fail); + break; + case MRP_MSG_FIELD_UINT16: + CREATE_ARRAY(f, tag, base, au16, unsigned int, fail); + break; + case MRP_MSG_FIELD_SINT16: + CREATE_ARRAY(f, tag, base, as16, int, fail); + break; + case MRP_MSG_FIELD_UINT32: + CREATE_ARRAY(f, tag, base, au32, unsigned int, fail); + break; + case MRP_MSG_FIELD_SINT32: + CREATE_ARRAY(f, tag, base, as32, int, fail); + break; + case MRP_MSG_FIELD_UINT64: + CREATE_ARRAY(f, tag, base, au64, unsigned long long, fail); + break; + case MRP_MSG_FIELD_SINT64: + CREATE_ARRAY(f, tag, base, as64, long long, fail); + break; + case MRP_MSG_FIELD_DOUBLE: + CREATE_ARRAY(f, tag, base, adbl, double, fail); + break; + default: + errno = EINVAL; + goto fail; + } + break; + } + + return f; + + fail: + destroy_field(f); + return NULL; + +#undef CREATE +#undef CREATE_ARRAY +} + + +static void msg_destroy(mrp_msg_t *msg) +{ + mrp_list_hook_t *p, *n; + mrp_msg_field_t *f; + + if (msg != NULL) { + mrp_list_foreach(&msg->fields, p, n) { + f = mrp_list_entry(p, typeof(*f), hook); + destroy_field(f); + } + + mrp_free(msg); + } +} + + +mrp_msg_t *mrp_msg_createv(uint16_t tag, va_list ap) +{ + mrp_msg_t *msg; + mrp_msg_field_t *f; + va_list aq; + + va_copy(aq, ap); + if ((msg = mrp_allocz(sizeof(*msg))) != NULL) { + mrp_list_init(&msg->fields); + mrp_refcnt_init(&msg->refcnt); + + while (tag != MRP_MSG_FIELD_INVALID) { + f = create_field(tag, &aq); + + if (f != NULL) { + mrp_list_append(&msg->fields, &f->hook); + msg->nfield++; + } + else { + msg_destroy(msg); + msg = NULL; + goto out; + } + tag = va_arg(aq, uint32_t); + } + } + out: + va_end(aq); + + return msg; +} + + +mrp_msg_t *mrp_msg_create(uint16_t tag, ...) +{ + mrp_msg_t *msg; + va_list ap; + + va_start(ap, tag); + msg = mrp_msg_createv(tag, ap); + va_end(ap); + + return msg; +} + + +mrp_msg_t *mrp_msg_ref(mrp_msg_t *msg) +{ + return mrp_ref_obj(msg, refcnt); +} + + +void mrp_msg_unref(mrp_msg_t *msg) +{ + if (mrp_unref_obj(msg, refcnt)) + msg_destroy(msg); +} + + +int mrp_msg_append(mrp_msg_t *msg, uint16_t tag, ...) +{ + mrp_msg_field_t *f; + va_list ap; + + va_start(ap, tag); + f = create_field(tag, &ap); + va_end(ap); + + if (f != NULL) { + mrp_list_append(&msg->fields, &f->hook); + msg->nfield++; + return TRUE; + } + else + return FALSE; +} + + +int mrp_msg_prepend(mrp_msg_t *msg, uint16_t tag, ...) +{ + mrp_msg_field_t *f; + va_list ap; + + va_start(ap, tag); + f = create_field(tag, &ap); + va_end(ap); + + if (f != NULL) { + mrp_list_prepend(&msg->fields, &f->hook); + msg->nfield++; + return TRUE; + } + else + return FALSE; +} + + +int mrp_msg_set(mrp_msg_t *msg, uint16_t tag, ...) +{ + mrp_msg_field_t *of, *nf; + va_list ap; + + of = mrp_msg_find(msg, tag); + + if (of != NULL) { + va_start(ap, tag); + nf = create_field(tag, &ap); + va_end(ap); + + if (nf != NULL) { + mrp_list_append(&of->hook, &nf->hook); + destroy_field(of); + + return TRUE; + } + } + + return FALSE; +} + + +int mrp_msg_iterate(mrp_msg_t *msg, void **it, uint16_t *tagp, uint16_t *typep, + mrp_msg_value_t *valp, size_t *sizep) +{ + mrp_list_hook_t *p = *(mrp_list_hook_t **)it; + mrp_msg_field_t *f; + + if (p == NULL) + p = msg->fields.next; + + if (p == &msg->fields) + return FALSE; + + f = mrp_list_entry(p, typeof(*f), hook); + + *tagp = f->tag; + *typep = f->type; + + switch (f->type) { +#define HANDLE_TYPE(type, member) \ + case MRP_MSG_FIELD_##type: \ + valp->member = f->member; \ + if (sizep != NULL) \ + *sizep = sizeof(typeof(f->member)); \ + break + + HANDLE_TYPE(BOOL , bln); + HANDLE_TYPE(UINT8 , u8); + HANDLE_TYPE(SINT8 , s8); + HANDLE_TYPE(UINT16, u16); + HANDLE_TYPE(SINT16, s16); + HANDLE_TYPE(UINT32, u32); + HANDLE_TYPE(SINT32, s32); + HANDLE_TYPE(UINT64, u64); + HANDLE_TYPE(SINT64, s64); + HANDLE_TYPE(DOUBLE, dbl); + + case MRP_MSG_FIELD_STRING: + valp->str = f->str; + if (sizep != NULL) + *sizep = strlen(f->str); + break; + + case MRP_MSG_FIELD_BLOB: + valp->blb = f->blb; + if (sizep != NULL) + *sizep = (size_t)f->size[0]; + break; + + default: + if (f->type & MRP_MSG_FIELD_ARRAY) { + valp->aany = f->aany; + if (sizep != NULL) + *sizep = f->size[0]; + } + else + return FALSE; +#undef HANDLE_TYPE + } + + *it = p->next; + + return TRUE; +} + + +mrp_msg_field_t *mrp_msg_find(mrp_msg_t *msg, uint16_t tag) +{ + mrp_msg_field_t *f; + mrp_list_hook_t *p, *n; + + mrp_list_foreach(&msg->fields, p, n) { + f = mrp_list_entry(p, typeof(*f), hook); + if (f->tag == tag) + return f; + } + + return NULL; +} + + +int mrp_msg_get(mrp_msg_t *msg, ...) +{ +#define HANDLE_TYPE(_type, _member) \ + case MRP_MSG_FIELD_##_type: \ + valp = va_arg(ap, typeof(valp)); \ + valp->_member = f->_member; \ + break + +#define HANDLE_ARRAY(_type, _member) \ + case MRP_MSG_FIELD_##_type: \ + cntp = va_arg(ap, typeof(cntp)); \ + valp = va_arg(ap, typeof(valp)); \ + *cntp = f->size[0]; \ + valp->_member = f->_member; \ + break + + + mrp_msg_field_t *f; + mrp_msg_value_t *valp; + uint32_t *cntp; + mrp_list_hook_t *start, *p; + uint16_t tag, type; + int found; + va_list ap; + + va_start(ap, msg); + + /* + * Okay... this might look a bit weird at first sight. This is + * mostly because we don't use the standard list iterating macros + * in the inner loop. There is a good reason for that: we want to + * minimise the number of times we scan the message which is just + * a linked list of fields. We do this by arranging the nested + * loops below in such a way that if the order of fields to fetch + * in the argument list matches the order of fields in the message + * we end up running the outer and inner loops in a 'phase lock'. + * So if the caller fetches the fields in the correct order we end + * up scanning the message at most once but only up to the last + * field to fetch. + */ + + start = msg->fields.next; + found = FALSE; + + while ((tag = va_arg(ap, unsigned int)) != MRP_MSG_FIELD_INVALID) { + type = va_arg(ap, unsigned int); + found = FALSE; + + for (p = start; p != start->prev; p = p->next) { + if (p == &msg->fields) + continue; + + f = mrp_list_entry(p, typeof(*f), hook); + + if (f->tag != tag) + continue; + + if (f->type != type) + goto out; + + switch (type) { + HANDLE_TYPE(STRING, str); + HANDLE_TYPE(BOOL , bln); + HANDLE_TYPE(UINT8 , u8 ); + HANDLE_TYPE(SINT8 , s8 ); + HANDLE_TYPE(UINT16, u16); + HANDLE_TYPE(SINT16, s16); + HANDLE_TYPE(UINT32, u32); + HANDLE_TYPE(SINT32, s32); + HANDLE_TYPE(UINT64, u64); + HANDLE_TYPE(SINT64, s64); + HANDLE_TYPE(DOUBLE, dbl); + default: + if (type & MRP_MSG_FIELD_ARRAY) { + switch (type & ~MRP_MSG_FIELD_ARRAY) { + HANDLE_ARRAY(STRING, astr); + HANDLE_ARRAY(BOOL , abln); + HANDLE_ARRAY(UINT8 , au8 ); + HANDLE_ARRAY(SINT8 , as8 ); + HANDLE_ARRAY(UINT16, au16); + HANDLE_ARRAY(SINT16, as16); + HANDLE_ARRAY(UINT32, au32); + HANDLE_ARRAY(SINT32, as32); + HANDLE_ARRAY(UINT64, au64); + HANDLE_ARRAY(SINT64, as64); + HANDLE_ARRAY(DOUBLE, adbl); + default: + goto out; + + } + } + else + goto out; + } + + start = p->next; + found = TRUE; + break; + } + + if (!found) + break; + } + + out: + va_end(ap); + + return found; + +#undef HANDLE_TYPE +#undef HANDLE_ARRAY + +} + + +int mrp_msg_iterate_get(mrp_msg_t *msg, void **it, ...) +{ +#define HANDLE_TYPE(_type, _member) \ + case MRP_MSG_FIELD_##_type: \ + valp = va_arg(ap, typeof(valp)); \ + valp->_member = f->_member; \ + break + +#define HANDLE_ARRAY(_type, _member) \ + case MRP_MSG_FIELD_##_type: \ + cntp = va_arg(ap, typeof(cntp)); \ + valp = va_arg(ap, typeof(valp)); \ + *cntp = f->size[0]; \ + valp->_member = f->_member; \ + break + +#define ANY_TYPE(_type, _member) \ + case MRP_MSG_FIELD_##_type: \ + valp->_member = f->_member; \ + break + + mrp_msg_field_t *f; + mrp_msg_value_t *valp; + uint32_t *cntp; + mrp_list_hook_t *start, *p; + uint16_t tag, type, *typep; + int found; + va_list ap; + + va_start(ap, it); + + /* + * Okay... this might look a bit weird at first sight. This is + * mostly because we don't use the standard list iterating macros + * in the inner loop. There is a good reason for that: we want to + * minimise the number of times we scan the message which is just + * a linked list of fields. We do this by arranging the nested + * loops below in such a way that if the order of fields to fetch + * in the argument list matches the order of fields in the message + * we end up running the outer and inner loops in a 'phase lock'. + * So if the caller fetches the fields in the correct order we end + * up scanning the message at most once but only up to the last + * field to fetch. + */ + + start = (*it) ? (mrp_list_hook_t *)*it : msg->fields.next; + found = FALSE; + + while ((tag = va_arg(ap, unsigned int)) != MRP_MSG_FIELD_INVALID) { + type = va_arg(ap, unsigned int); + found = FALSE; + + if (type == MRP_MSG_FIELD_ANY) { + typep = va_arg(ap, uint16_t *); + valp = va_arg(ap, mrp_msg_value_t *); + } + else { + typep = NULL; + valp = NULL; + } + + for (p = start; p != start->prev; p = p->next) { + if (p == &msg->fields) + continue; + + f = mrp_list_entry(p, typeof(*f), hook); + + if (f->tag != tag) + continue; + + if (type == MRP_MSG_FIELD_ANY) { + *typep = f->type; + switch (f->type) { + ANY_TYPE(STRING, str); + ANY_TYPE(BOOL , bln); + ANY_TYPE(UINT8 , u8 ); + ANY_TYPE(SINT8 , s8 ); + ANY_TYPE(UINT16, u16); + ANY_TYPE(SINT16, s16); + ANY_TYPE(UINT32, u32); + ANY_TYPE(SINT32, s32); + ANY_TYPE(UINT64, u64); + ANY_TYPE(SINT64, s64); + ANY_TYPE(DOUBLE, dbl); + default: + mrp_log_error("XXX TODO: currently cannot fetch array " + "message fields with iterators."); + } + + goto next; + } + + if (f->type != type) + goto out; + + switch (type) { + HANDLE_TYPE(STRING, str); + HANDLE_TYPE(BOOL , bln); + HANDLE_TYPE(UINT8 , u8 ); + HANDLE_TYPE(SINT8 , s8 ); + HANDLE_TYPE(UINT16, u16); + HANDLE_TYPE(SINT16, s16); + HANDLE_TYPE(UINT32, u32); + HANDLE_TYPE(SINT32, s32); + HANDLE_TYPE(UINT64, u64); + HANDLE_TYPE(SINT64, s64); + HANDLE_TYPE(DOUBLE, dbl); + default: + if (type & MRP_MSG_FIELD_ARRAY) { + switch (type & ~MRP_MSG_FIELD_ARRAY) { + HANDLE_ARRAY(STRING, astr); + HANDLE_ARRAY(BOOL , abln); + HANDLE_ARRAY(UINT8 , au8 ); + HANDLE_ARRAY(SINT8 , as8 ); + HANDLE_ARRAY(UINT16, au16); + HANDLE_ARRAY(SINT16, as16); + HANDLE_ARRAY(UINT32, au32); + HANDLE_ARRAY(SINT32, as32); + HANDLE_ARRAY(UINT64, au64); + HANDLE_ARRAY(SINT64, as64); + HANDLE_ARRAY(DOUBLE, adbl); + default: + goto out; + + } + } + else + goto out; + } + + next: + start = p->next; + found = TRUE; + break; + } + + if (!found) + break; + } + + out: + va_end(ap); + + if (found) + *it = start; + + return found; + +#undef HANDLE_TYPE +#undef HANDLE_ARRAY + +} + + +static const char *field_type_name(uint16_t type) +{ +#define BASIC(t, n) [MRP_MSG_FIELD_##t] = n +#define ARRAY(t, n) [MRP_MSG_FIELD_##t] = "array of "n"s" + static const char *basic[] = { + BASIC(STRING, "string" ), + BASIC(BOOL , "boolean"), + BASIC(UINT8 , "uint8" ), + BASIC(SINT8 , "sint8" ), + BASIC(UINT16, "uint16" ), + BASIC(SINT16, "sint16" ), + BASIC(UINT32, "uint32" ), + BASIC(SINT32, "sint32" ), + BASIC(UINT64, "uint64" ), + BASIC(SINT64, "sint64" ), + BASIC(DOUBLE, "double" ), + BASIC(BLOB , "blob" ) + }; + + static const char *array[] = { + ARRAY(STRING, "string" ), + ARRAY(BOOL , "boolean"), + ARRAY(UINT8 , "uint8" ), + ARRAY(SINT8 , "sint8" ), + ARRAY(UINT16, "uint16" ), + ARRAY(SINT16, "sint16" ), + ARRAY(UINT32, "uint32" ), + ARRAY(SINT32, "sint32" ), + ARRAY(UINT64, "uint64" ), + ARRAY(SINT64, "sint64" ), + ARRAY(DOUBLE, "double" ), + ARRAY(BLOB , "blob" ) + }; +#undef BASIC +#undef ARRAY + + uint16_t base; + + if (MRP_MSG_FIELD_INVALID < type && type <= MRP_MSG_FIELD_MAX) + return basic[type]; + else { + if (type & MRP_MSG_FIELD_ARRAY) { + base = type & ~MRP_MSG_FIELD_ARRAY; + + if (MRP_MSG_FIELD_INVALID < base && base <= MRP_MSG_FIELD_MAX) + return array[base]; + } + } + + return "unknown type"; +} + + +int mrp_msg_dump(mrp_msg_t *msg, FILE *fp) +{ + mrp_msg_field_t *f; + mrp_list_hook_t *p, *n; + int l; + uint32_t i; + uint16_t base; + const char *tname; + + if (msg == NULL) + return fprintf(fp, "{\n <no message>\n}\n"); + + l = fprintf(fp, "{\n"); + mrp_list_foreach(&msg->fields, p, n) { + f = mrp_list_entry(p, typeof(*f), hook); + + l += fprintf(fp, " 0x%x ", f->tag); + +#define DUMP(_indent, _fmt, _typename, _val) \ + l += fprintf(fp, "%*.*s= <%s> "_fmt"\n", _indent, _indent, "", \ + _typename, _val) + + tname = field_type_name(f->type); + switch (f->type) { + case MRP_MSG_FIELD_STRING: + DUMP(0, "'%s'", tname, f->str); + break; + case MRP_MSG_FIELD_BOOL: + DUMP(0, "%s", tname, f->bln ? "true" : "false"); + break; + case MRP_MSG_FIELD_UINT8: + DUMP(0, "%u", tname, f->u8); + break; + case MRP_MSG_FIELD_SINT8: + DUMP(0, "%d", tname, f->s8); + break; + case MRP_MSG_FIELD_UINT16: + DUMP(0, "%u", tname, f->u16); + break; + case MRP_MSG_FIELD_SINT16: + DUMP(0, "%d", tname, f->s16); + break; + case MRP_MSG_FIELD_UINT32: + DUMP(0, "%u", tname, f->u32); + break; + case MRP_MSG_FIELD_SINT32: + DUMP(0, "%d", tname, f->s32); + break; + case MRP_MSG_FIELD_UINT64: + DUMP(0, "%Lu", tname, (long long unsigned)f->u64); + break; + case MRP_MSG_FIELD_SINT64: + DUMP(0, "%Ld", tname, (long long signed)f->s64); + break; + case MRP_MSG_FIELD_DOUBLE: + DUMP(0, "%f", tname, f->dbl); + break; + case MRP_MSG_FIELD_BLOB: { + char *p; + uint32_t i; + + fprintf(fp, "= <%s> <%u bytes, ", tname, f->size[0]); + + for (i = 0, p = f->blb; i < f->size[0]; i++, p++) { + if (isprint(*p) && *p != '\n' && *p != '\t' && *p != '\r') + fprintf(fp, "%c", *p); + else + fprintf(fp, "."); + } + fprintf(fp, ">\n"); + } + break; + + default: + if (f->type & MRP_MSG_FIELD_ARRAY) { + base = f->type & ~MRP_MSG_FIELD_ARRAY; + tname = field_type_name(base); + + fprintf(fp, "\n"); + for (i = 0; i < f->size[0]; i++) { + switch (base) { + case MRP_MSG_FIELD_STRING: + DUMP(8, "'%s'", tname, f->astr[i]); + break; + case MRP_MSG_FIELD_BOOL: + DUMP(8, "%s", tname, f->abln[i] ? "true" : "false"); + break; + case MRP_MSG_FIELD_UINT8: + DUMP(8, "%u", tname, f->au8[i]); + break; + case MRP_MSG_FIELD_SINT8: + DUMP(8, "%d", tname, f->as8[i]); + break; + case MRP_MSG_FIELD_UINT16: + DUMP(8, "%u", tname, f->au16[i]); + break; + case MRP_MSG_FIELD_SINT16: + DUMP(8, "%d", tname, f->as16[i]); + break; + case MRP_MSG_FIELD_UINT32: + DUMP(8, "%u", tname, f->au32[i]); + break; + case MRP_MSG_FIELD_SINT32: + DUMP(8, "%d", tname, f->as32[i]); + break; + case MRP_MSG_FIELD_UINT64: + DUMP(8, "%Lu", tname, + (unsigned long long)f->au64[i]); + break; + case MRP_MSG_FIELD_SINT64: + DUMP(8, "%Ld", tname, + (long long)f->as64[i]); + break; + case MRP_MSG_FIELD_DOUBLE: + DUMP(8, "%f", tname, f->adbl[i]); + break; + default: + fprintf(fp, "%*.*s= <%s>\n", 8, 8, "", tname); + break; + } + } + } + else + fprintf(fp, "= <%s>\n", tname); + } + } + l += fprintf(fp, "}\n"); + + return l; +#undef DUMP +} + + +#define MSG_MIN_CHUNK 32 + +ssize_t mrp_msg_default_encode(mrp_msg_t *msg, void **bufp) +{ + mrp_msg_field_t *f; + mrp_list_hook_t *p, *n; + mrp_msgbuf_t mb; + uint32_t len, asize, i; + uint16_t type; + size_t size; + + size = msg->nfield * (2 * sizeof(uint16_t) + sizeof(uint64_t)); + + if (mrp_msgbuf_write(&mb, size)) { + MRP_MSGBUF_PUSH(&mb, htobe16(MRP_MSG_TAG_DEFAULT), 1, nomem); + MRP_MSGBUF_PUSH(&mb, htobe16(msg->nfield), 1, nomem); + + mrp_list_foreach(&msg->fields, p, n) { + f = mrp_list_entry(p, typeof(*f), hook); + + MRP_MSGBUF_PUSH(&mb, htobe16(f->tag) , 1, nomem); + MRP_MSGBUF_PUSH(&mb, htobe16(f->type), 1, nomem); + + switch (f->type) { + case MRP_MSG_FIELD_STRING: + len = strlen(f->str) + 1; + MRP_MSGBUF_PUSH(&mb, htobe32(len), 1, nomem); + MRP_MSGBUF_PUSH_DATA(&mb, f->str, len, 1, nomem); + break; + + case MRP_MSG_FIELD_BOOL: + MRP_MSGBUF_PUSH(&mb, htobe32(f->bln ? TRUE : FALSE), 1, nomem); + break; + + case MRP_MSG_FIELD_UINT8: + MRP_MSGBUF_PUSH(&mb, f->u8, 1, nomem); + break; + + case MRP_MSG_FIELD_SINT8: + MRP_MSGBUF_PUSH(&mb, f->s8, 1, nomem); + break; + + case MRP_MSG_FIELD_UINT16: + MRP_MSGBUF_PUSH(&mb, htobe16(f->u16), 1, nomem); + break; + + case MRP_MSG_FIELD_SINT16: + MRP_MSGBUF_PUSH(&mb, htobe16(f->s16), 1, nomem); + break; + + case MRP_MSG_FIELD_UINT32: + MRP_MSGBUF_PUSH(&mb, htobe32(f->u32), 1, nomem); + break; + + case MRP_MSG_FIELD_SINT32: + MRP_MSGBUF_PUSH(&mb, htobe32(f->s32), 1, nomem); + break; + + case MRP_MSG_FIELD_UINT64: + MRP_MSGBUF_PUSH(&mb, htobe64(f->u64), 1, nomem); + break; + + case MRP_MSG_FIELD_SINT64: + MRP_MSGBUF_PUSH(&mb, htobe64(f->s64), 1, nomem); + break; + + case MRP_MSG_FIELD_DOUBLE: + MRP_MSGBUF_PUSH(&mb, f->dbl, 1, nomem); + break; + + case MRP_MSG_FIELD_BLOB: + len = f->size[0]; + MRP_MSGBUF_PUSH(&mb, htobe32(len), 1, nomem); + MRP_MSGBUF_PUSH_DATA(&mb, f->blb, len, 1, nomem); + break; + + default: + if (f->type & MRP_MSG_FIELD_ARRAY) { + type = f->type & ~(MRP_MSG_FIELD_ARRAY); + asize = f->size[0]; + MRP_MSGBUF_PUSH(&mb, htobe32(asize), 1, nomem); + + for (i = 0; i < asize; i++) { + switch (type) { + case MRP_MSG_FIELD_STRING: + len = strlen(f->astr[i]) + 1; + MRP_MSGBUF_PUSH(&mb, htobe32(len), 1, nomem); + MRP_MSGBUF_PUSH_DATA(&mb, f->astr[i], len, + 1, nomem); + break; + + case MRP_MSG_FIELD_BOOL: + MRP_MSGBUF_PUSH(&mb, htobe32(f->abln[i]?TRUE:FALSE), + 1, nomem); + break; + + case MRP_MSG_FIELD_UINT8: + MRP_MSGBUF_PUSH(&mb, f->au8[i], 1, nomem); + break; + + case MRP_MSG_FIELD_SINT8: + MRP_MSGBUF_PUSH(&mb, f->as8[i], 1, nomem); + break; + + case MRP_MSG_FIELD_UINT16: + MRP_MSGBUF_PUSH(&mb, htobe16(f->au16[i]), 1, nomem); + break; + + case MRP_MSG_FIELD_SINT16: + MRP_MSGBUF_PUSH(&mb, htobe16(f->as16[i]), 1, nomem); + break; + + case MRP_MSG_FIELD_UINT32: + MRP_MSGBUF_PUSH(&mb, htobe32(f->au32[i]), 1, nomem); + break; + + case MRP_MSG_FIELD_SINT32: + MRP_MSGBUF_PUSH(&mb, htobe32(f->as32[i]), 1, nomem); + break; + + case MRP_MSG_FIELD_UINT64: + MRP_MSGBUF_PUSH(&mb, htobe64(f->au64[i]), 1, nomem); + break; + + case MRP_MSG_FIELD_SINT64: + MRP_MSGBUF_PUSH(&mb, htobe64(f->as64[i]), 1, nomem); + break; + + case MRP_MSG_FIELD_DOUBLE: + MRP_MSGBUF_PUSH(&mb, f->adbl[i], 1, nomem); + break; + + default: + goto invalid_type; + } + } + } + else { + invalid_type: + errno = EINVAL; + mrp_msgbuf_cancel(&mb); + nomem: + *bufp = NULL; + return -1; + } + } + } + } + + *bufp = mb.buf; + return mb.p - mb.buf; +} + + +mrp_msg_t *mrp_msg_default_decode(void *buf, size_t size) +{ + mrp_msg_t *msg; + mrp_msgbuf_t mb; + mrp_msg_value_t v; + void *value; + uint16_t nfield, tag, type, base; + uint32_t len, n, i, j; + + msg = mrp_msg_create_empty(); + + if (msg == NULL) + return NULL; + + mrp_msgbuf_read(&mb, buf, size); + + nfield = be16toh(MRP_MSGBUF_PULL(&mb, typeof(nfield), 1, nodata)); + + for (i = 0; i < nfield; i++) { + tag = be16toh(MRP_MSGBUF_PULL(&mb, typeof(tag) , 1, nodata)); + type = be16toh(MRP_MSGBUF_PULL(&mb, typeof(type), 1, nodata)); + + switch (type) { + case MRP_MSG_FIELD_STRING: + len = be32toh(MRP_MSGBUF_PULL(&mb, typeof(len), 1, nodata)); + if (len > 0) + value = MRP_MSGBUF_PULL_DATA(&mb, len, 1, nodata); + else + value = ""; + if (!mrp_msg_append(msg, tag, type, value)) + goto fail; + break; + + case MRP_MSG_FIELD_BOOL: + v.bln = be32toh(MRP_MSGBUF_PULL(&mb, uint32_t, 1, nodata)); + if (!mrp_msg_append(msg, tag, type, v.bln)) + goto fail; + break; + + case MRP_MSG_FIELD_UINT8: + v.u8 = MRP_MSGBUF_PULL(&mb, typeof(v.u8), 1, nodata); + if (!mrp_msg_append(msg, tag, type, v.u8)) + goto fail; + break; + + case MRP_MSG_FIELD_SINT8: + v.s8 = MRP_MSGBUF_PULL(&mb, typeof(v.s8), 1, nodata); + if (!mrp_msg_append(msg, tag, type, v.s8)) + goto fail; + break; + + case MRP_MSG_FIELD_UINT16: + v.u16 = be16toh(MRP_MSGBUF_PULL(&mb, typeof(v.u16), 1, nodata)); + if (!mrp_msg_append(msg, tag, type, v.u16)) + goto fail; + break; + + case MRP_MSG_FIELD_SINT16: + v.s16 = be16toh(MRP_MSGBUF_PULL(&mb, typeof(v.s16), 1, nodata)); + if (!mrp_msg_append(msg, tag, type, v.s16)) + goto fail; + break; + + case MRP_MSG_FIELD_UINT32: + v.u32 = be32toh(MRP_MSGBUF_PULL(&mb, typeof(v.u32), 1, nodata)); + if (!mrp_msg_append(msg, tag, type, v.u32)) + goto fail; + break; + + case MRP_MSG_FIELD_SINT32: + v.s32 = be32toh(MRP_MSGBUF_PULL(&mb, typeof(v.s32), 1, nodata)); + if (!mrp_msg_append(msg, tag, type, v.s32)) + goto fail; + break; + + case MRP_MSG_FIELD_UINT64: + v.u64 = be64toh(MRP_MSGBUF_PULL(&mb, typeof(v.u64), 1, nodata)); + if (!mrp_msg_append(msg, tag, type, v.u64)) + goto fail; + break; + + case MRP_MSG_FIELD_SINT64: + v.s64 = be64toh(MRP_MSGBUF_PULL(&mb, typeof(v.s64), 1, nodata)); + if (!mrp_msg_append(msg, tag, type, v.s64)) + goto fail; + break; + + case MRP_MSG_FIELD_DOUBLE: + v.dbl = MRP_MSGBUF_PULL(&mb, typeof(v.dbl), 1, nodata); + if (!mrp_msg_append(msg, tag, type, v.dbl)) + goto fail; + break; + + case MRP_MSG_FIELD_BLOB: + len = be32toh(MRP_MSGBUF_PULL(&mb, typeof(len), 1, nodata)); + value = MRP_MSGBUF_PULL_DATA(&mb, len, 1, nodata); + if (!mrp_msg_append(msg, tag, type, len, value)) + goto fail; + break; + + default: + if (!(type & MRP_MSG_FIELD_ARRAY)) { + errno = EINVAL; + goto fail; + } + + base = type & ~MRP_MSG_FIELD_ARRAY; + n = be32toh(MRP_MSGBUF_PULL(&mb, typeof(n), 1, nodata)); + { + char *astr[n]; + bool abln[n]; + uint8_t au8 [n]; + int8_t as8 [n]; + uint16_t au16[n]; + int16_t as16[n]; + uint32_t au32[n]; + int32_t as32[n]; + uint64_t au64[n]; + int64_t as64[n]; + double adbl[n]; + + for (j = 0; j < n; j++) { + + switch (base) { + case MRP_MSG_FIELD_STRING: + len = be32toh(MRP_MSGBUF_PULL(&mb, typeof(len), + 1, nodata)); + if (len > 0) + astr[j] = MRP_MSGBUF_PULL_DATA(&mb, len, 1, nodata); + else + astr[j] = ""; + break; + + case MRP_MSG_FIELD_BOOL: + abln[j] = be32toh(MRP_MSGBUF_PULL(&mb, uint32_t, 1, + nodata)); + break; + + case MRP_MSG_FIELD_UINT8: + au8[j] = MRP_MSGBUF_PULL(&mb, typeof(v.u8), 1, nodata); + break; + + case MRP_MSG_FIELD_SINT8: + as8[j] = MRP_MSGBUF_PULL(&mb, typeof(v.s8), 1, nodata); + break; + + case MRP_MSG_FIELD_UINT16: + au16[j] = be16toh(MRP_MSGBUF_PULL(&mb, typeof(v.u16), + 1, nodata)); + break; + + case MRP_MSG_FIELD_SINT16: + as16[j] = be16toh(MRP_MSGBUF_PULL(&mb, typeof(v.s16), + 1, nodata)); + break; + + case MRP_MSG_FIELD_UINT32: + au32[j] = be32toh(MRP_MSGBUF_PULL(&mb, typeof(v.u32), + 1, nodata)); + break; + + case MRP_MSG_FIELD_SINT32: + as32[j] = be32toh(MRP_MSGBUF_PULL(&mb, typeof(v.s32), + 1, nodata)); + break; + + case MRP_MSG_FIELD_UINT64: + au64[j] = be64toh(MRP_MSGBUF_PULL(&mb, typeof(v.u64), + 1, nodata)); + break; + + case MRP_MSG_FIELD_SINT64: + as64[j] = be64toh(MRP_MSGBUF_PULL(&mb, typeof(v.s64), + 1, nodata)); + break; + + case MRP_MSG_FIELD_DOUBLE: + adbl[j] = MRP_MSGBUF_PULL(&mb, typeof(v.dbl), + 1, nodata); + break; + + default: + errno = EINVAL; + goto fail; + } + } + +#define HANDLE_TYPE(_type, _var) \ + case _type: \ + if (!mrp_msg_append(msg, tag, \ + MRP_MSG_FIELD_ARRAY |_type, \ + n, _var)) \ + goto fail; \ + break + + switch (base) { + HANDLE_TYPE(MRP_MSG_FIELD_STRING, astr); + HANDLE_TYPE(MRP_MSG_FIELD_BOOL , abln); + HANDLE_TYPE(MRP_MSG_FIELD_UINT8 , au8 ); + HANDLE_TYPE(MRP_MSG_FIELD_SINT8 , as8 ); + HANDLE_TYPE(MRP_MSG_FIELD_UINT16, au16); + HANDLE_TYPE(MRP_MSG_FIELD_SINT16, as16); + HANDLE_TYPE(MRP_MSG_FIELD_UINT32, au32); + HANDLE_TYPE(MRP_MSG_FIELD_SINT32, as32); + HANDLE_TYPE(MRP_MSG_FIELD_UINT64, au64); + HANDLE_TYPE(MRP_MSG_FIELD_SINT64, as64); + HANDLE_TYPE(MRP_MSG_FIELD_DOUBLE, adbl); + default: + errno = EINVAL; + goto fail; + } +#undef HANDLE_TYPE + } + } + } + + return msg; + + + fail: + nodata: + mrp_msg_unref(msg); + return NULL; +} + + +static int guarded_array_size(void *data, mrp_data_member_t *array) +{ +#define MAX_ITEMS (32 * 1024) + uint16_t base; + void *value, *guard; + size_t size; + int cnt; + + if (array->type & MRP_MSG_FIELD_ARRAY) { + base = array->type & ~MRP_MSG_FIELD_ARRAY; + + switch (base) { + case MRP_MSG_FIELD_STRING: size = sizeof(array->str); break; + case MRP_MSG_FIELD_BOOL: size = sizeof(array->bln); break; + case MRP_MSG_FIELD_UINT8: size = sizeof(array->u8); break; + case MRP_MSG_FIELD_SINT8: size = sizeof(array->s8); break; + case MRP_MSG_FIELD_UINT16: size = sizeof(array->u16); break; + case MRP_MSG_FIELD_SINT16: size = sizeof(array->s16); break; + case MRP_MSG_FIELD_UINT32: size = sizeof(array->u32); break; + case MRP_MSG_FIELD_SINT32: size = sizeof(array->s32); break; + case MRP_MSG_FIELD_UINT64: size = sizeof(array->u64); break; + case MRP_MSG_FIELD_SINT64: size = sizeof(array->s64); break; + case MRP_MSG_FIELD_DOUBLE: size = sizeof(array->dbl); break; + default: return -1; + } + + guard = &array->str; + value = *(void **)(data + array->offs); + for (cnt = 0; cnt < MAX_ITEMS; cnt++, value += size) { + if (!memcmp(value, guard, size)) + return cnt + 1; + } + } + + return -1; +#undef MAX_ITEMS +} + + +static int counted_array_size(void *data, mrp_data_member_t *cnt) +{ + void *val = data + cnt->offs; + + switch (cnt->type) { + case MRP_MSG_FIELD_UINT8: return (int)*(uint8_t *)val; + case MRP_MSG_FIELD_SINT8: return (int)*( int8_t *)val; + case MRP_MSG_FIELD_UINT16: return (int)*(uint16_t *)val; + case MRP_MSG_FIELD_SINT16: return (int)*( int16_t *)val; + case MRP_MSG_FIELD_UINT32: return (int)*(uint32_t *)val; + case MRP_MSG_FIELD_SINT32: return (int)*( int32_t *)val; + } + + return -1; +} + + +static int get_array_size(void *data, mrp_data_descr_t *type, int idx) +{ + mrp_data_member_t *arr; + + if (0 < idx && idx < type->nfield) { + arr = type->fields + idx; + + if (arr->type & MRP_MSG_FIELD_ARRAY) { + if (arr->guard) + return guarded_array_size(data, arr); + else { + if ((int)arr->u32 < type->nfield) + return counted_array_size(data, type->fields + arr->u32); + } + } + } + + return -1; +} + + +int mrp_data_get_array_size(void *data, mrp_data_descr_t *type, int idx) +{ + return get_array_size(data, type, idx); +} + + +static int get_blob_size(void *data, mrp_data_descr_t *type, int idx) +{ + mrp_data_member_t *blb, *cnt; + void *val; + + if (0 < idx && idx < type->nfield) { + blb = type->fields + idx; + + if ((int)blb->u32 < type->nfield) { + cnt = type->fields + blb->u32; + val = data + cnt->offs; + + switch (cnt->type) { + case MRP_MSG_FIELD_UINT8: return (int)*(uint8_t *)val; + case MRP_MSG_FIELD_SINT8: return (int)*( int8_t *)val; + case MRP_MSG_FIELD_UINT16: return (int)*(uint16_t *)val; + case MRP_MSG_FIELD_SINT16: return (int)*( int16_t *)val; + case MRP_MSG_FIELD_UINT32: return (int)*(uint32_t *)val; + case MRP_MSG_FIELD_SINT32: return (int)*( int32_t *)val; + } + } + } + + return -1; +} + + +int mrp_data_get_blob_size(void *data, mrp_data_descr_t *type, int idx) +{ + return get_blob_size(data, type, idx); +} + + +static int check_and_init_array_descr(mrp_data_descr_t *type, int idx) +{ + mrp_data_member_t *array, *cnt, *m; + int i; + + array = type->fields + idx; + + if (!array->guard) { + cnt = NULL; + + for (i = 0, m = type->fields; i < type->nfield; i++, m++) { + if (m->offs == array->u32) { + cnt = m; + break; + } + } + + if (cnt == NULL || cnt >= array) + return FALSE; + + if (cnt->type < MRP_MSG_FIELD_UINT8 || cnt->type > MRP_MSG_FIELD_SINT32) + return FALSE; + + array->u32 = i; + + return TRUE; + } + else { + return TRUE; + } +} + + +int mrp_msg_register_type(mrp_data_descr_t *type) +{ + mrp_data_member_t *f; + int idx, i; + + if (direct_types == NULL) { + direct_types = mrp_allocz_array(typeof(*direct_types), NDIRECT_TYPE); + + if (direct_types == NULL) + return FALSE; + } + + if (type->tag == MRP_MSG_TAG_DEFAULT) { + errno = EINVAL; + return FALSE; + } + + mrp_list_init(&type->allocated); + + /* enumerate fields, check arrays, collect extra allocations */ + for (i = 0, f = type->fields; i < type->nfield; i++, f++) { + f->tag = (uint16_t)i + 1; + + if (f->type & MRP_MSG_FIELD_ARRAY) { + if (!check_and_init_array_descr(type, i)) + return FALSE; + + mrp_list_append(&type->allocated, &f->hook); + } + else { + switch (f->type) { + case MRP_MSG_FIELD_STRING: + case MRP_MSG_FIELD_BLOB: + mrp_list_append(&type->allocated, &f->hook); + } + } + } + + if (type->tag <= NDIRECT_TYPE) { + idx = type->tag - 1; + + if (direct_types[idx] == NULL) + direct_types[idx] = type; + else + return FALSE; + } + else { + if (mrp_reallocz(other_types, nother_type, nother_type + 1) != NULL) + other_types[nother_type++] = type; + else + return FALSE; + } + + return TRUE; +} + + +mrp_data_descr_t *mrp_msg_find_type(uint16_t tag) +{ + int i; + + if (MRP_UNLIKELY(tag == MRP_MSG_TAG_DEFAULT)) + return NULL; + + if (tag <= NDIRECT_TYPE) + return direct_types[tag - 1]; + else { + for (i = 0; i < nother_type; i++) { + if (other_types[i] != NULL && other_types[i]->tag == tag) + return other_types[i]; + } + } + + return NULL; +} + + +static __attribute__((destructor)) void cleanup_types(void) +{ + mrp_free(direct_types); + mrp_free(other_types); + nother_type = 0; +} + + +size_t mrp_data_encode(void **bufp, void *data, mrp_data_descr_t *descr, + size_t reserve) +{ + mrp_data_member_t *fields, *f; + int nfield; + uint16_t type; + mrp_msgbuf_t mb; + mrp_msg_value_t *v; + uint32_t len, asize, blblen, j; + int i, cnt; + size_t size; + + fields = descr->fields; + nfield = descr->nfield; + size = reserve + nfield * (2 * sizeof(uint16_t) + sizeof(uint64_t)); + + if (mrp_msgbuf_write(&mb, size)) { + if (reserve) + mrp_msgbuf_reserve(&mb, reserve, 1); + + for (i = 0, f = fields; i < nfield; i++, f++) { + MRP_MSGBUF_PUSH(&mb, htobe16(f->tag) , 1, nomem); + + v = (mrp_msg_value_t *)(data + f->offs); + + switch (f->type) { + case MRP_MSG_FIELD_STRING: + len = strlen(v->str) + 1; + MRP_MSGBUF_PUSH(&mb, htobe32(len), 1, nomem); + MRP_MSGBUF_PUSH_DATA(&mb, v->str, len, 1, nomem); + break; + + case MRP_MSG_FIELD_BOOL: + MRP_MSGBUF_PUSH(&mb, htobe32(v->bln ? TRUE : FALSE), 1, nomem); + break; + + case MRP_MSG_FIELD_UINT8: + MRP_MSGBUF_PUSH(&mb, v->u8, 1, nomem); + break; + + case MRP_MSG_FIELD_SINT8: + MRP_MSGBUF_PUSH(&mb, v->s8, 1, nomem); + break; + + case MRP_MSG_FIELD_UINT16: + MRP_MSGBUF_PUSH(&mb, htobe16(v->u16), 1, nomem); + break; + + case MRP_MSG_FIELD_SINT16: + MRP_MSGBUF_PUSH(&mb, htobe16(v->s16), 1, nomem); + break; + + case MRP_MSG_FIELD_UINT32: + MRP_MSGBUF_PUSH(&mb, htobe32(v->u32), 1, nomem); + break; + + case MRP_MSG_FIELD_SINT32: + MRP_MSGBUF_PUSH(&mb, htobe32(v->s32), 1, nomem); + break; + + case MRP_MSG_FIELD_UINT64: + MRP_MSGBUF_PUSH(&mb, htobe64(v->u64), 1, nomem); + break; + + case MRP_MSG_FIELD_SINT64: + MRP_MSGBUF_PUSH(&mb, htobe64(v->s64), 1, nomem); + break; + + case MRP_MSG_FIELD_DOUBLE: + MRP_MSGBUF_PUSH(&mb, v->dbl, 1, nomem); + break; + + case MRP_MSG_FIELD_BLOB: + blblen = (uint32_t)get_blob_size(data, descr, i); + + if (blblen == (uint32_t)-1) + goto invalid_type; + + MRP_MSGBUF_PUSH(&mb, htobe32(v->u32), 1, nomem); + MRP_MSGBUF_PUSH_DATA(&mb, v->blb, blblen, 1, nomem); + break; + + default: + if (f->type & MRP_MSG_FIELD_ARRAY) { + type = f->type & ~(MRP_MSG_FIELD_ARRAY); + cnt = get_array_size(data, descr, i); + + if (cnt < 0) + goto invalid_type; + + asize = (uint32_t)cnt; + MRP_MSGBUF_PUSH(&mb, htobe32(asize), 1, nomem); + + for (j = 0; j < asize; j++) { + switch (type) { + case MRP_MSG_FIELD_STRING: + len = strlen(v->astr[j]) + 1; + MRP_MSGBUF_PUSH(&mb, htobe32(len), 1, nomem); + MRP_MSGBUF_PUSH_DATA(&mb, v->astr[j], len, + 1, nomem); + break; + + case MRP_MSG_FIELD_BOOL: + MRP_MSGBUF_PUSH(&mb, htobe32(v->abln[j]?TRUE:FALSE), + 1, nomem); + break; + + case MRP_MSG_FIELD_UINT8: + MRP_MSGBUF_PUSH(&mb, v->au8[j], 1, nomem); + break; + + case MRP_MSG_FIELD_SINT8: + MRP_MSGBUF_PUSH(&mb, v->as8[j], 1, nomem); + break; + + case MRP_MSG_FIELD_UINT16: + MRP_MSGBUF_PUSH(&mb, htobe16(v->au16[j]), 1, nomem); + break; + + case MRP_MSG_FIELD_SINT16: + MRP_MSGBUF_PUSH(&mb, htobe16(v->as16[j]), 1, nomem); + break; + + case MRP_MSG_FIELD_UINT32: + MRP_MSGBUF_PUSH(&mb, htobe32(v->au32[j]), 1, nomem); + break; + + case MRP_MSG_FIELD_SINT32: + MRP_MSGBUF_PUSH(&mb, htobe32(v->as32[j]), 1, nomem); + break; + + case MRP_MSG_FIELD_UINT64: + MRP_MSGBUF_PUSH(&mb, htobe64(v->au64[j]), 1, nomem); + break; + + case MRP_MSG_FIELD_SINT64: + MRP_MSGBUF_PUSH(&mb, htobe64(v->as64[j]), 1, nomem); + break; + + case MRP_MSG_FIELD_DOUBLE: + MRP_MSGBUF_PUSH(&mb, v->adbl[j], 1, nomem); + break; + + default: + goto invalid_type; + } + } + } + else { + invalid_type: + errno = EINVAL; + mrp_msgbuf_cancel(&mb); + nomem: + *bufp = NULL; + return 0; + } + } + } + } + + *bufp = mb.buf; + return (size_t)(mb.p - mb.buf); +} + + +static mrp_data_member_t *member_type(mrp_data_member_t *fields, int nfield, + uint16_t tag) +{ + mrp_data_member_t *f; + int i; + + for (i = 0, f = fields; i < nfield; i++, f++) + if (f->tag == tag) + return f; + + return NULL; +} + + +void *mrp_data_decode(void **bufp, size_t *sizep, mrp_data_descr_t *descr) +{ + void *data; + mrp_data_member_t *fields, *f; + int nfield; + mrp_msgbuf_t mb; + uint16_t tag, base; + mrp_msg_value_t *v; + void *value; + uint32_t len, n, j, size; + int i; + + fields = descr->fields; + nfield = descr->nfield; + data = mrp_allocz(descr->size); + + if (MRP_UNLIKELY(data == NULL)) + return NULL; + + mrp_msgbuf_read(&mb, *bufp, *sizep); + + for (i = 0; i < nfield; i++) { + tag = be16toh(MRP_MSGBUF_PULL(&mb, typeof(tag) , 1, nodata)); + f = member_type(fields, nfield, tag); + + if (MRP_UNLIKELY(f == NULL)) + goto unknown_field; + + v = (mrp_msg_value_t *)(data + f->offs); + + switch (f->type) { + case MRP_MSG_FIELD_STRING: + len = be32toh(MRP_MSGBUF_PULL(&mb, typeof(len), 1, nodata)); + if (len > 0) + value = MRP_MSGBUF_PULL_DATA(&mb, len, 1, nodata); + else + value = ""; + v->str = mrp_strdup((char *)value); + if (v->str == NULL) + goto nomem; + break; + + case MRP_MSG_FIELD_BOOL: + v->bln = be32toh(MRP_MSGBUF_PULL(&mb, uint32_t, 1, nodata)); + break; + + case MRP_MSG_FIELD_UINT8: + v->u8 = MRP_MSGBUF_PULL(&mb, typeof(v->u8), 1, nodata); + break; + + case MRP_MSG_FIELD_SINT8: + v->s8 = MRP_MSGBUF_PULL(&mb, typeof(v->s8), 1, nodata); + break; + + case MRP_MSG_FIELD_UINT16: + v->u16 = be16toh(MRP_MSGBUF_PULL(&mb, typeof(v->u16), 1, nodata)); + break; + + case MRP_MSG_FIELD_SINT16: + v->s16 = be16toh(MRP_MSGBUF_PULL(&mb, typeof(v->s16), 1, nodata)); + break; + + case MRP_MSG_FIELD_UINT32: + v->u32 = be32toh(MRP_MSGBUF_PULL(&mb, typeof(v->u32), 1, nodata)); + break; + + case MRP_MSG_FIELD_SINT32: + v->s32 = be32toh(MRP_MSGBUF_PULL(&mb, typeof(v->s32), 1, nodata)); + break; + + case MRP_MSG_FIELD_UINT64: + v->u64 = be64toh(MRP_MSGBUF_PULL(&mb, typeof(v->u64), 1, nodata)); + break; + + case MRP_MSG_FIELD_SINT64: + v->s64 = be64toh(MRP_MSGBUF_PULL(&mb, typeof(v->s64), 1, nodata)); + break; + + case MRP_MSG_FIELD_DOUBLE: + v->dbl = MRP_MSGBUF_PULL(&mb, typeof(v->dbl), 1, nodata); + break; + + case MRP_MSG_FIELD_BLOB: + len = be32toh(MRP_MSGBUF_PULL(&mb, typeof(len), 1, nodata)); + value = MRP_MSGBUF_PULL_DATA(&mb, len, 1, nodata); + v->blb = mrp_datadup(value, len); + if (v->blb == NULL) + goto nomem; + break; + + default: + if (!(f->type & MRP_MSG_FIELD_ARRAY)) { + unknown_field: + errno = EINVAL; + goto fail; + } + + base = f->type & ~MRP_MSG_FIELD_ARRAY; + n = be32toh(MRP_MSGBUF_PULL(&mb, typeof(n), 1, nodata)); + + if (!f->guard && get_array_size(data, descr, i) != (int)n) { + errno = EINVAL; + goto fail; + } + + size = n; + + switch (base) { + case MRP_MSG_FIELD_STRING: size *= sizeof(*v->astr); break; + case MRP_MSG_FIELD_BOOL: size *= sizeof(*v->abln); break; + case MRP_MSG_FIELD_UINT8: size *= sizeof(*v->au8); break; + case MRP_MSG_FIELD_SINT8: size *= sizeof(*v->as8); break; + case MRP_MSG_FIELD_UINT16: size *= sizeof(*v->au16); break; + case MRP_MSG_FIELD_SINT16: size *= sizeof(*v->as16); break; + case MRP_MSG_FIELD_UINT32: size *= sizeof(*v->au32); break; + case MRP_MSG_FIELD_SINT32: size *= sizeof(*v->as32); break; + case MRP_MSG_FIELD_UINT64: size *= sizeof(*v->au64); break; + case MRP_MSG_FIELD_SINT64: size *= sizeof(*v->as64); break; + case MRP_MSG_FIELD_DOUBLE: size *= sizeof(*v->adbl); break; + default: + errno = EINVAL; + goto fail; + } + + v->aany = mrp_allocz(size); + if (v->aany == NULL) + goto nomem; + + for (j = 0; j < n; j++) { + switch (base) { + case MRP_MSG_FIELD_STRING: + len = be32toh(MRP_MSGBUF_PULL(&mb, typeof(len), + 1, nodata)); + if (len > 0) + value = MRP_MSGBUF_PULL_DATA(&mb, len, 1, nodata); + else + value = ""; + + v->astr[j] = mrp_strdup(value); + if (v->astr[j] == NULL) + goto nomem; + break; + + case MRP_MSG_FIELD_BOOL: + v->abln[j] = be32toh(MRP_MSGBUF_PULL(&mb, uint32_t, + 1, nodata)); + break; + + case MRP_MSG_FIELD_UINT8: + v->au8[j] = MRP_MSGBUF_PULL(&mb, typeof(v->u8), + 1, nodata); + break; + + case MRP_MSG_FIELD_SINT8: + v->as8[j] = MRP_MSGBUF_PULL(&mb, typeof(v->s8), + 1, nodata); + break; + + case MRP_MSG_FIELD_UINT16: + v->au16[j] = be16toh(MRP_MSGBUF_PULL(&mb, + typeof(v->u16), + 1, nodata)); + break; + + case MRP_MSG_FIELD_SINT16: + v->as16[j] = be16toh(MRP_MSGBUF_PULL(&mb, + typeof(v->s16), + 1, nodata)); + break; + + case MRP_MSG_FIELD_UINT32: + v->au32[j] = be32toh(MRP_MSGBUF_PULL(&mb, + typeof(v->u32), + 1, nodata)); + break; + + case MRP_MSG_FIELD_SINT32: + v->as32[j] = be32toh(MRP_MSGBUF_PULL(&mb, + typeof(v->s32), + 1, nodata)); + break; + + case MRP_MSG_FIELD_UINT64: + v->au64[j] = be64toh(MRP_MSGBUF_PULL(&mb, + typeof(v->u64), + 1, nodata)); + break; + + case MRP_MSG_FIELD_SINT64: + v->as64[j] = be64toh(MRP_MSGBUF_PULL(&mb, + typeof(v->s64), + 1, nodata)); + break; + + case MRP_MSG_FIELD_DOUBLE: + v->adbl[j] = MRP_MSGBUF_PULL(&mb, typeof(v->dbl), + 1, nodata); + break; + + default: + errno = EINVAL; + goto fail; + } + } + } + } + + *bufp = mb.buf; + *sizep -= mb.p - mb.buf; + return data; + + nodata: + nomem: + fail: + if (data != NULL) { + for (i = 0, f = fields; i < nfield; i++, f++) { + switch (f->type) { + case MRP_MSG_FIELD_STRING: + case MRP_MSG_FIELD_BLOB: + mrp_free(*(void **)(data + f->offs)); + } + } + + mrp_free(data); + } + + return NULL; +} + + +int mrp_data_dump(void *data, mrp_data_descr_t *descr, FILE *fp) +{ +#define DUMP(_indent, _fmt, _typename, _val) \ + l += fprintf(fp, "%*.*s= <%s> "_fmt"\n", _indent, _indent, "", \ + _typename, _val) + + mrp_data_member_t *dm; + mrp_msg_value_t *v; + uint16_t base; + int i, j, l, cnt; + const char *tname; + + + l = fprintf(fp, "{\n"); + for (i = 0, dm = descr->fields; i < descr->nfield; i++, dm++) { + l += fprintf(fp, " @%d ", dm->offs); + v = (mrp_msg_value_t *)(data + dm->offs); + tname = field_type_name(dm->type); + + switch (dm->type) { + case MRP_MSG_FIELD_STRING: + DUMP(0, "'%s'", tname, v->str); + break; + case MRP_MSG_FIELD_BOOL: + DUMP(0, "%s", tname, v->bln ? "true" : "false"); + break; + case MRP_MSG_FIELD_UINT8: + DUMP(0, "%u", tname, v->u8); + break; + case MRP_MSG_FIELD_SINT8: + DUMP(0, "%d", tname, v->s8); + break; + case MRP_MSG_FIELD_UINT16: + DUMP(0, "%u", tname, v->u16); + break; + case MRP_MSG_FIELD_SINT16: + DUMP(0, "%d", tname, v->s16); + break; + case MRP_MSG_FIELD_UINT32: + DUMP(0, "%u", tname, v->u32); + break; + case MRP_MSG_FIELD_SINT32: + DUMP(0, "%d", tname, v->s32); + break; + case MRP_MSG_FIELD_UINT64: + DUMP(0, "%Lu", tname, (long long unsigned)v->u64); + break; + case MRP_MSG_FIELD_SINT64: + DUMP(0, "%Ld", tname, (long long signed)v->s64); + break; + case MRP_MSG_FIELD_DOUBLE: + DUMP(0, "%f", tname, v->dbl); + break; + default: + if (dm->type & MRP_MSG_FIELD_ARRAY) { + base = dm->type & ~MRP_MSG_FIELD_ARRAY; + cnt = get_array_size(data, descr, i); + + if (cnt < 0) { + fprintf(fp, "= <%s> ???\n", tname); + continue; + } + + fprintf(fp, "= <%s> (%d)\n", tname, cnt); + tname = field_type_name(base); + + for (j = 0; j < cnt; j++) { + switch (base) { + case MRP_MSG_FIELD_STRING: + DUMP(8, "'%s'", tname, v->astr[j]); + break; + case MRP_MSG_FIELD_BOOL: + DUMP(8, "%s", tname, v->abln[j] ? "true" : "false"); + break; + case MRP_MSG_FIELD_UINT8: + DUMP(8, "%u", tname, v->au8[j]); + break; + case MRP_MSG_FIELD_SINT8: + DUMP(8, "%d", tname, v->as8[j]); + break; + case MRP_MSG_FIELD_UINT16: + DUMP(8, "%u", tname, v->au16[j]); + break; + case MRP_MSG_FIELD_SINT16: + DUMP(8, "%d", tname, v->as16[j]); + break; + case MRP_MSG_FIELD_UINT32: + DUMP(8, "%u", tname, v->au32[j]); + break; + case MRP_MSG_FIELD_SINT32: + DUMP(8, "%d", tname, v->as32[j]); + break; + case MRP_MSG_FIELD_UINT64: + DUMP(8, "%Lu", tname, (long long unsigned)v->au64[j]); + break; + case MRP_MSG_FIELD_SINT64: + DUMP(8, "%Ld", tname, (long long signed)v->as64[j]); + break; + case MRP_MSG_FIELD_DOUBLE: + DUMP(8, "%f", tname, v->adbl[j]); + break; + default: + fprintf(fp, "%*.*s<%s>\n", 8, 8, "", tname); + break; + } + } + } + } + } + l += fprintf(fp, "}\n"); + + return l; +} + + +int mrp_data_free(void *data, uint16_t tag) +{ + mrp_data_descr_t *type; + mrp_list_hook_t *p, *n; + mrp_data_member_t *f; + void *ptr; + int i, idx, cnt; + + if (data == NULL) + return TRUE; + + type = mrp_msg_find_type(tag); + + if (type != NULL) { + mrp_list_foreach(&type->allocated, p, n) { + f = mrp_list_entry(p, typeof(*f), hook); + ptr = *(void **)(data + f->offs); + + if (f->type == (MRP_MSG_FIELD_ARRAY | MRP_MSG_FIELD_STRING)) { + idx = f - type->fields; + cnt = get_array_size(data, type, idx); + + for (i = 0; i < cnt; i++) + mrp_free(((char **)ptr)[i]); + } + + mrp_free(ptr); + } + + mrp_free(data); + + return TRUE; + } + else + return FALSE; +} + + +void *mrp_msgbuf_write(mrp_msgbuf_t *mb, size_t size) +{ + mrp_clear(mb); + + mb->buf = mrp_allocz(size); + + if (mb->buf != NULL) { + mb->size = size; + mb->p = mb->buf; + mb->l = size; + + return mb->p; + } + else + return NULL; +} + + +void mrp_msgbuf_read(mrp_msgbuf_t *mb, void *buf, size_t size) +{ + mb->buf = mb->p = buf; + mb->size = mb->l = size; +} + + +void mrp_msgbuf_cancel(mrp_msgbuf_t *mb) +{ + mrp_free(mb->buf); + mb->buf = mb->p = NULL; +} + + +void *mrp_msgbuf_ensure(mrp_msgbuf_t *mb, size_t size) +{ + int diff; + + if (MRP_UNLIKELY(size > mb->l)) { + diff = size - mb->l; + + if (diff < MSG_MIN_CHUNK) + diff = MSG_MIN_CHUNK; + + mb->p -= (ptrdiff_t)mb->buf; + + if (mrp_realloc(mb->buf, mb->size + diff)) { + memset(mb->buf + mb->size, 0, diff); + mb->size += diff; + mb->p += (ptrdiff_t)mb->buf; + mb->l += diff; + } + else + mrp_msgbuf_cancel(mb); + } + + return mb->p; +} + + +void *mrp_msgbuf_reserve(mrp_msgbuf_t *mb, size_t size, size_t align) +{ + void *reserved; + ptrdiff_t offs, pad; + size_t len; + + len = size; + offs = mb->p - mb->buf; + + if (offs % align != 0) { + pad = align - (offs % align); + len += pad; + } + else + pad = 0; + + if (mrp_msgbuf_ensure(mb, len)) { + if (pad != 0) + memset(mb->p, 0, pad); + + reserved = mb->p + pad; + + mb->p += len; + mb->l -= len; + } + else + reserved = NULL; + + return reserved; +} + + +void *mrp_msgbuf_pull(mrp_msgbuf_t *mb, size_t size, size_t align) +{ + void *pulled; + ptrdiff_t offs, pad; + size_t len; + + len = size; + offs = mb->p - mb->buf; + + if (offs % align != 0) { + pad = align - (offs % align); + len += pad; + } + else + pad = 0; + + if (mb->l >= len) { + pulled = mb->p + pad; + + mb->p += len; + mb->l -= len; + } + else + pulled = NULL; + + return pulled; +} diff --git a/src/common/msg.h b/src/common/msg.h new file mode 100644 index 0000000..e61805b --- /dev/null +++ b/src/common/msg.h @@ -0,0 +1,461 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_MSG_H__ +#define __MURPHY_MSG_H__ + +#include <stdio.h> +#include <stdbool.h> +#include <stdarg.h> +#include <stdint.h> + +#include <murphy/common/list.h> +#include <murphy/common/refcnt.h> + +MRP_CDECL_BEGIN + +/* + * message field types + */ + +#define A(t) MRP_MSG_FIELD_##t +typedef enum { + MRP_MSG_FIELD_INVALID = 0x00, /* defined invalid type */ + MRP_MSG_FIELD_STRING = 0x01, /* mqi_varchar */ + MRP_MSG_FIELD_INTEGER = 0x02, /* mqi_integer */ + MRP_MSG_FIELD_UNSIGNED = 0x03, /* mqi_unsignd */ + MRP_MSG_FIELD_DOUBLE = 0x04, /* mqi_floating */ + MRP_MSG_FIELD_BOOL = 0x05, /* boolean */ + MRP_MSG_FIELD_UINT8 = 0x06, /* unsigned 8-bit integer */ + MRP_MSG_FIELD_SINT8 = 0x07, /* signed 8-bit integer */ + MRP_MSG_FIELD_INT8 = A(SINT8), /* alias for SINT8 */ + MRP_MSG_FIELD_UINT16 = 0x08, /* unsigned 16-bit integer */ + MRP_MSG_FIELD_SINT16 = 0x09, /* signed 16-bit integer */ + MRP_MSG_FIELD_INT16 = A(SINT16), /* alias for SINT16 */ + MRP_MSG_FIELD_UINT32 = 0x0a, /* unsigned 32-bit integer */ + MRP_MSG_FIELD_SINT32 = 0x0b, /* signed 32-bit integer */ + MRP_MSG_FIELD_INT32 = A(SINT32), /* alias for SINT32 */ + MRP_MSG_FIELD_UINT64 = 0x0c, /* unsigned 64-bit integer */ + MRP_MSG_FIELD_SINT64 = 0x0d, /* signed 64-bit integer */ + MRP_MSG_FIELD_INT64 = A(SINT64), /* alias for SINT64 */ + MRP_MSG_FIELD_BLOB = 0x0e, /* a blob (not allowed in arrays) */ + MRP_MSG_FIELD_MAX = 0x0e, + MRP_MSG_FIELD_ANY = 0x0f, /* any type of field when querying */ + + MRP_MSG_FIELD_ARRAY = 0x80, /* bit-mask to mark arrays */ +} mrp_msg_field_type_t; +#undef A + +#define MRP_MSG_END ((char *)MRP_MSG_FIELD_INVALID) /* NULL */ + +#define MRP_MSG_FIELD_ARRAY_OF(t) (MRP_MSG_FIELD_ARRAY | MRP_MSG_FIELD_##t) +#define MRP_MSG_FIELD_IS_ARRAY(t) ((t) & MRP_MSG_FIELD_ARRAY) +#define MRP_MSG_FIELD_ARRAY_TYPE(t) ((t) & ~MRP_MSG_FIELD_ARRAY) + +#define MRP_MSG_TAG_STRING(tag, arg) (tag), MRP_MSG_FIELD_STRING, (arg) +#define MRP_MSG_TAG_BOOL(tag, arg) (tag), MRP_MSG_FIELD_BOOL , (arg) +#define MRP_MSG_TAG_UINT8(tag, arg) (tag), MRP_MSG_FIELD_UINT8 , (arg) +#define MRP_MSG_TAG_SINT8(tag, arg) (tag), MRP_MSG_FIELD_SINT8 , (arg) +#define MRP_MSG_TAG_UINT16(tag, arg) (tag), MRP_MSG_FIELD_UINT16, (arg) +#define MRP_MSG_TAG_SINT16(tag, arg) (tag), MRP_MSG_FIELD_SINT16, (arg) +#define MRP_MSG_TAG_UINT32(tag, arg) (tag), MRP_MSG_FIELD_UINT32, (arg) +#define MRP_MSG_TAG_SINT32(tag, arg) (tag), MRP_MSG_FIELD_SINT32, (arg) +#define MRP_MSG_TAG_UINT64(tag, arg) (tag), MRP_MSG_FIELD_UINT64, (arg) +#define MRP_MSG_TAG_SINT64(tag, arg) (tag), MRP_MSG_FIELD_SINT64, (arg) +#define MRP_MSG_TAG_DOUBLE(tag, arg) (tag), MRP_MSG_FIELD_DOUBLE, (arg) +#define MRP_MSG_TAG_BLOB(tag, arg) (tag), MRP_MSG_FIELD_BLOB , (arg) + +#define MRP_MSG_TAGGED(tag, type, ...) (tag), (type), __VA_ARGS__ +#define MRP_MSG_TAG_ARRAY(tag, type, cnt, arr) \ + (tag), MRP_MSG_FIELD_ARRAY | MRP_MSG_FIELD_##type, (cnt), (arr) +#define MRP_MSG_TAG_STRING_ARRAY(tag, cnt, arr) \ + MRP_MSG_TAG_ARRAY((tag), STRING, (cnt), (arr)) +#define MRP_MSG_TAG_BOOL_ARRAY(tag, cnt, arr) \ + MRP_MSG_TAG_ARRAY((tag), BOOL, (cnt), (arr)) +#define MRP_MSG_TAG_UINT8_ARRAY(tag, cnt, arr) \ + MRP_MSG_TAG_ARRAY((tag), UINT8, (cnt), (arr)) +#define MRP_MSG_TAG_SINT8_ARRAY(tag, cnt, arr) \ + MRP_MSG_TAG_ARRAY((tag), SINT8, (cnt), (arr)) +#define MRP_MSG_TAG_UINT16_ARRAY(tag, cnt, arr) \ + MRP_MSG_TAG_ARRAY((tag), UINT16, (cnt), (arr)) +#define MRP_MSG_TAG_SINT16_ARRAY(tag, cnt, arr) \ + MRP_MSG_TAG_ARRAY((tag), SINT16, (cnt), (arr)) +#define MRP_MSG_TAG_UINT32_ARRAY(tag, cnt, arr) \ + MRP_MSG_TAG_ARRAY((tag), UINT32, (cnt), (arr)) +#define MRP_MSG_TAG_SINT32_ARRAY(tag, cnt, arr) \ + MRP_MSG_TAG_ARRAY((tag), SINT32, (cnt), (arr)) +#define MRP_MSG_TAG_UINT64_ARRAY(tag, cnt, arr) \ + MRP_MSG_TAG_ARRAY((tag), UINT64, (cnt), (arr)) +#define MRP_MSG_TAG_SINT64_ARRAY(tag, cnt, arr) \ + MRP_MSG_TAG_ARRAY((tag), SINT64, (cnt), (arr)) +#define MRP_MSG_TAG_DOUBLE_ARRAY(tag, cnt, arr) \ + MRP_MSG_TAG_ARRAY((tag), DOUBLE, (cnt), (arr)) +#define MRP_MSG_TAG_BLOB_ARRAY(tag, cnt, arr) \ + MRP_MSG_TAG_ARRAY((tag), BLOB, (cnt), (arr)) + +#define MRP_MSG_TAG_ANY(tag, typep, valuep) \ + (tag), MRP_MSG_FIELD_ANY, (typep), (valuep) + + +/** Sentinel to pass in as the last argument to mrp_msg_create. */ +#define MRP_MSG_FIELD_END NULL + + +/* + * generic messages + * + * A generic message is just a collection of message fields. By default + * transports are in generic messaging mode in which case they take messages + * as input (for transmission) and provide messages as events (for receiption). + * A generic message field consists of a field tag, a field type, the actual + * type-specific field value, and for certain types a size. + * + * The field tag is used by the communicating parties to attach semantic + * meaning to the field data. One can think of it as the 'name' of the field + * within a message. It is not interpreted by the messaging layer in any way. + * The field type defines what kind of data the field contains contains and + * it must be one of the predefined MRP_MSG_FIELD_* types. The actual field + * data then depends on the type. size is only used for those data types that + * require a size (blobs and arrays). + */ + +#define MRP_MSG_VALUE_UNION union { \ + char *str; \ + bool bln; \ + uint8_t u8; \ + int8_t s8; \ + uint16_t u16; \ + int16_t s16; \ + uint32_t u32; \ + int32_t s32; \ + uint64_t u64; \ + int64_t s64; \ + double dbl; \ + void *blb; \ + void *aany; \ + char **astr; \ + bool *abln; \ + uint8_t *au8; \ + int8_t *as8; \ + uint16_t *au16; \ + int16_t *as16; \ + uint32_t *au32; \ + int32_t *as32; \ + uint64_t *au64; \ + int64_t *as64; \ + double *adbl; \ + } + +typedef MRP_MSG_VALUE_UNION mrp_msg_value_t; + +typedef struct { + mrp_list_hook_t hook; /* hook to list of fields */ + uint16_t tag; /* message field tag */ + uint16_t type; /* message field type */ + MRP_MSG_VALUE_UNION; /* message field value */ + uint32_t size[0]; /* size, if an array or a blob */ +} mrp_msg_field_t; + + +typedef struct { + mrp_list_hook_t fields; /* list of message fields */ + size_t nfield; /* number of fields */ + mrp_refcnt_t refcnt; /* reference count */ +} mrp_msg_t; + + +/** Create a new message. */ +mrp_msg_t *mrp_msg_create(uint16_t tag, ...) MRP_NULLTERM; + +/** Create a new message. */ +mrp_msg_t *mrp_msg_createv(uint16_t tag, va_list ap); + +/** Macro to create an empty message. */ +#define mrp_msg_create_empty() mrp_msg_create(MRP_MSG_FIELD_INVALID, NULL) + +/** Increase refcount of the given message. */ +mrp_msg_t *mrp_msg_ref(mrp_msg_t *msg); + +/** Decrease the refcount, free the message if refcount drops to zero. */ +void mrp_msg_unref(mrp_msg_t *msg); + +/** Append a field to a message. */ +int mrp_msg_append(mrp_msg_t *msg, uint16_t tag, ...); + +/** Prepend a field to a message. */ +int mrp_msg_prepend(mrp_msg_t *msg, uint16_t tag, ...); + +/** Set a field in a message to the given value. */ +int mrp_msg_set(mrp_msg_t *msg, uint16_t tag, ...); + +/** Iterate through the fields of a message. You must not any of the + fields while iterating. */ +int mrp_msg_iterate(mrp_msg_t *msg, void **it, uint16_t *tagp, + uint16_t *typep, mrp_msg_value_t *valp, size_t *sizep); + +/** Iterate through the matching fields of a message. You should not delete + * any of the fields while iterating through the message. */ +int mrp_msg_iterate_matching(mrp_msg_t *msg, void **it, uint16_t *tagp, + uint16_t *typep, mrp_msg_value_t *valp, + size_t *sizep); + +/** Find a field in a message. */ +mrp_msg_field_t *mrp_msg_find(mrp_msg_t *msg, uint16_t tag); + +/** Get the given fields (with matching tags and types) from the message. */ +int mrp_msg_get(mrp_msg_t *msg, ...) MRP_NULLTERM; + +/** Iterate through the message getting the given fields. */ +int mrp_msg_iterate_get(mrp_msg_t *msg, void **it, ...); + +/** Dump a message. */ +int mrp_msg_dump(mrp_msg_t *msg, FILE *fp); + +/** Encode the given message using the default message encoder. */ +ssize_t mrp_msg_default_encode(mrp_msg_t *msg, void **bufp); + +/** Decode the given message using the default message decoder. */ +mrp_msg_t *mrp_msg_default_decode(void *buf, size_t size); + + +/* + * custom data types + * + * In addition to generic messages, you can instruct the messaging and + * transport layers to encode/decode messages directly from/to custom data + * structures. To do so you need to describe your data structures and register + * them using data descriptors. A descriptor basically consists of a type + * tag, structure size, number of members and and array of structure member + * descriptors. + * + * The data type tag is used to identify the descriptor and consequently + * the custom data type both during sending and receiving (ie. encoding and + * decoding). It is assigned by the registering entity, it must be unique, + * and it cannot be MRP_MSG_TAG_DEFAULT (0x0), or else registration will + * fail. The size is used to allocate necessary memory for the data on the + * receiving end. The member descriptors are used to describe the offset + * and types of the members within the custom data type. + */ + +#define MRP_MSG_TAG_DEFAULT 0x0 /* tag for default encode/decoder */ + +typedef struct { + uint16_t offs; /* offset within structure */ + uint16_t tag; /* tag for this member */ + uint16_t type; /* type of this member */ + bool guard; /* whether sentinel-terminated */ + MRP_MSG_VALUE_UNION; /* sentinel or offset of count field */ + mrp_list_hook_t hook; /* hook to list of extra allocations */ +} mrp_data_member_t; + + +typedef struct { + mrp_refcnt_t refcnt; /* reference count */ + uint16_t tag; /* structure tag */ + size_t size; /* size of this structure */ + int nfield; /* number of members */ + mrp_data_member_t *fields; /* member descriptors */ + mrp_list_hook_t allocated; /* fields needing extra allocation */ +} mrp_data_descr_t; + + +/** Convenience macro to declare a custom data type (and its members). */ +#define MRP_DATA_DESCRIPTOR(_var, _tag, _type, ...) \ + static mrp_data_member_t _var##_members[] = { \ + __VA_ARGS__ \ + }; \ + \ + static mrp_data_descr_t _var = { \ + .size = sizeof(_type), \ + .tag = _tag, \ + .fields = _var##_members, \ + .nfield = MRP_ARRAY_SIZE(_var##_members) \ + } + +/** Convenience macro to declare a data member. */ +#define MRP_DATA_MEMBER(_data_type, _member, _member_type) { \ + .offs = MRP_OFFSET(_data_type, _member), \ + .type = _member_type, \ + .guard = FALSE \ + } + +/** Convenience macro to declare an array data member with a count field. */ +#define MRP_DATA_ARRAY_COUNT(_data_type, _array, _count, _base_type) { \ + .offs = MRP_OFFSET(_data_type, _array), \ + .type = MRP_MSG_FIELD_ARRAY | _base_type, \ + .guard = FALSE, \ + { .u32 = MRP_OFFSET(_data_type, _count) } \ + } + +/** Convenience macro to declare an array data member with a sentinel value. */ +#define MRP_DATA_ARRAY_GUARD(_data_type, _array, _guard_member, _guard_val, \ + _base_type) { \ + .offs = MRP_OFFSET(_data_type, _array), \ + .type = MRP_MSG_FIELD_ARRAY | _base_type, \ + .guard = TRUE, \ + { ._guard_member = _guard_val } \ + } + +/** Convenience macro to declare a blob data member with a count field. */ +#define MRP_DATA_BLOB_MEMBER(_data_type, _blob, _count) { \ + .offs = MRP_OFFSET(_data_type, _blob), \ + .type = MRP_MSG_FIELD_BLOB, \ + .guard = FALSE, \ + .u32 = MRP_OFFSET(_data_type, _count) \ + } + + +/** Encode a structure using the given message descriptor. */ +size_t mrp_data_encode(void **bufp, void *data, mrp_data_descr_t *descr, + size_t reserve); + +/** Decode a structure using the given message descriptor. */ +void *mrp_data_decode(void **bufp, size_t *sizep, mrp_data_descr_t *descr); + +/** Dump the given data buffer. */ +int mrp_data_dump(void *data, mrp_data_descr_t *descr, FILE *fp); + +/** Get the size of a data array member. */ +int mrp_data_get_array_size(void *data, mrp_data_descr_t *type, int idx); + +/** Get the size of a data blob member. */ +int mrp_data_get_blob_size(void *data, mrp_data_descr_t *type, int idx); + +/** Register a new custom data type with the messaging/transport layer. */ +int mrp_msg_register_type(mrp_data_descr_t *type); + +/** Look up the data type descriptor corresponding to the given tag. */ +mrp_data_descr_t *mrp_msg_find_type(uint16_t tag); + +/** Free the given custom data allocated by the messaging layer. */ +int mrp_data_free(void *data, uint16_t tag); + +/* + * message encoding/decoding buffer + * + * This message buffer and the associated functions and macros can be + * used to write message encoding/decoding functions for bitpipe-type + * transports, ie. for transports where the underlying IPC just provides + * a raw data connection between the communication endpoints and does not + * impose/expect any structure on/from the data being transmitted. + * + * Practically all the basic stream and datagram socket transports are + * such. They use the default encoding/decoding functions provided by + * the messaging layer together with a very simple transport frame scheme, + * where each frame consists of the amount a size indicating the size of + * the encoded message in the bitpipe and the actual encoded message data. + * + * Note that at the moment this framing scheme is rather implicit in the + * sense that you won't find a data type representing a frame. Rather the + * framing is simply done in the sending/receiving code of the individual + * transports. + */ + +typedef struct { + void *buf; /* buffer to encode to/decode from */ + size_t size; /* size of the buffer */ + void *p; /* encoding/decoding pointer */ + size_t l; /* space left in the buffer */ +} mrp_msgbuf_t; + + + +/** Initialize the given message buffer for writing. */ +void *mrp_msgbuf_write(mrp_msgbuf_t *mb, size_t size); + +/** Initialize the given message buffer for reading. */ +void mrp_msgbuf_read(mrp_msgbuf_t *mb, void *buf, size_t size); + +/** Deinitialize the given message buffer, usually due to some error. */ +void mrp_msgbuf_cancel(mrp_msgbuf_t *mb); + +/** Reallocate the buffer if needed to accomodate size bytes of data. */ +void *mrp_msgbuf_ensure(mrp_msgbuf_t *mb, size_t size); + +/** Reserve the given amount of space from the buffer. */ +void *mrp_msgbuf_reserve(mrp_msgbuf_t *mb, size_t size, size_t align); + +/** Pull the given amount of data from the buffer. */ +void *mrp_msgbuf_pull(mrp_msgbuf_t *mb, size_t size, size_t align); + +/** Push data with alignment to the buffer, jumping to errlbl on errors. */ +#define MRP_MSGBUF_PUSH(mb, data, align, errlbl) do { \ + size_t _size = sizeof(data); \ + typeof(data) *_ptr; \ + \ + _ptr = mrp_msgbuf_reserve((mb), _size, (align)); \ + \ + if (_ptr != NULL) \ + *_ptr = data; \ + else \ + goto errlbl; \ + } while (0) + +/** Push aligned data to the buffer, jumping to errlbl on errors. */ +#define MRP_MSGBUF_PUSH_DATA(mb, data, size, align, errlbl) do { \ + size_t _size = (size); \ + void *_ptr; \ + \ + _ptr = mrp_msgbuf_reserve((mb), _size, (align)); \ + \ + if (_ptr != NULL) \ + memcpy(_ptr, data, _size); \ + else \ + goto errlbl; \ + } while (0) + +/** Pull aligned data of type from the buffer, jump to errlbl on errors. */ +#define MRP_MSGBUF_PULL(mb, type, align, errlbl) ({ \ + size_t _size = sizeof(type); \ + type *_ptr; \ + \ + _ptr = mrp_msgbuf_pull((mb), _size, (align)); \ + \ + if (_ptr == NULL) \ + goto errlbl; \ + \ + *_ptr; \ + }) + +/** Pull aligned data of type from the buffer, jump to errlbl on errors. */ +#define MRP_MSGBUF_PULL_DATA(mb, size, align, errlbl) ({ \ + size_t _size = size; \ + void *_ptr; \ + \ + _ptr = mrp_msgbuf_pull((mb), _size, (align)); \ + \ + if (_ptr == NULL) \ + goto errlbl; \ + \ + _ptr; \ + }) + +MRP_CDECL_END + +#endif /* __MURPHY_MSG_H__ */ diff --git a/src/common/murphy-common.pc.in b/src/common/murphy-common.pc.in new file mode 100644 index 0000000..0b75762 --- /dev/null +++ b/src/common/murphy-common.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: murphy-common +Description: Murphy policy framework, common library. +Requires: +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lmurphy-common @JSON_LIBS@ +Cflags: -I${includedir} @JSON_CFLAGS@ diff --git a/src/common/murphy-dbus-libdbus.pc.in b/src/common/murphy-dbus-libdbus.pc.in new file mode 100644 index 0000000..d6efb6f --- /dev/null +++ b/src/common/murphy-dbus-libdbus.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: murphy-dbus +Description: Murphy policy framework, libdbus based dbus library. +Requires: murphy-common +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lmurphy-dbus-libdbus @LIBDBUS_LIBS@ +Cflags: -I${includedir} @LIBDBUS_CFLAGS@ diff --git a/src/common/murphy-dbus-sdbus.pc.in b/src/common/murphy-dbus-sdbus.pc.in new file mode 100644 index 0000000..94008ee --- /dev/null +++ b/src/common/murphy-dbus-sdbus.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: murphy-sd-bus +Description: Murphy policy framework, systemd-bus based dbus library. +Requires: murphy-common +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lmurphy-sd-bus @SDBUS_LIBS@ +Cflags: -I${includedir} @SDBUS_CFLAGS@ diff --git a/src/common/murphy-ecore.pc.in b/src/common/murphy-ecore.pc.in new file mode 100644 index 0000000..626a07b --- /dev/null +++ b/src/common/murphy-ecore.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: murphy-ecore +Description: Murphy policy framework, EFL/ecore mainloop glue library. +Requires: murphy-common +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lmurphy-ecore @ECORE_LIBS@ +Cflags: -I${includedir} @ECORE_CFLAGS@ diff --git a/src/common/murphy-glib.pc.in b/src/common/murphy-glib.pc.in new file mode 100644 index 0000000..5193219 --- /dev/null +++ b/src/common/murphy-glib.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: murphy-glib +Description: Murphy policy framework, GLIB mainloop glue library. +Requires: murphy-common +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lmurphy-glib @GLIB_LIBS@ +Cflags: -I${includedir} @GLIB_CFLAGS@ diff --git a/src/common/murphy-libdbus.pc.in b/src/common/murphy-libdbus.pc.in new file mode 100644 index 0000000..bcda7b4 --- /dev/null +++ b/src/common/murphy-libdbus.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: murphy-dbus +Description: Murphy policy framework, dbus library. +Requires: murphy-common +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lmurphy-libdbus @LIBDBUS_LIBS@ +Cflags: -I${includedir} @LIBDBUS_CFLAGS@ diff --git a/src/common/murphy-pulse.pc.in b/src/common/murphy-pulse.pc.in new file mode 100644 index 0000000..2655307 --- /dev/null +++ b/src/common/murphy-pulse.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: murphy-pulse +Description: Murphy policy framework, PulseAudio mainloop glue library. +Requires: murphy-common +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lmurphy-pulse +Cflags: -I${includedir} @PULSE_CFLAGS@ diff --git a/src/common/murphy-qt.pc.in b/src/common/murphy-qt.pc.in new file mode 100644 index 0000000..95c6aba --- /dev/null +++ b/src/common/murphy-qt.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: murphy-qt +Description: Murphy policy framework, Qt mainloop glue library. +Requires: murphy-common +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lmurphy-qt @QTCORE_LIBS@ +Cflags: -I${includedir} @QTCORE_CFLAGS@ diff --git a/src/common/native-types.c b/src/common/native-types.c new file mode 100644 index 0000000..d9890e3 --- /dev/null +++ b/src/common/native-types.c @@ -0,0 +1,1626 @@ +/* + * Copyright (c) 2012, 2013, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <errno.h> + +#include <murphy/common/macros.h> +#include <murphy/common/debug.h> +#include <murphy/common/log.h> +#include <murphy/common/mm.h> +#include <murphy/common/list.h> +#include <murphy/common/tlv.h> +#include <murphy/common/native-types.h> + + +/* + * TLV tags we use when encoding/decoding our native types + */ + +typedef enum { + TAG_NONE = MRP_TLV_UNTAGGED, /* untagged data */ + TAG_STRUCT, /* a native structure */ + TAG_MEMBER, /* a native structure member */ + TAG_ARRAY, /* an array */ + TAG_NELEM, /* size of an array (in elements) */ +} tag_t; + + +/* + * extra header we use to keep track of memory while decoding + */ + +typedef struct { + mrp_list_hook_t hook; /* hook to chunk list */ + char data[0]; /* user-visible data */ +} chunk_t; + + +static int encode_struct(mrp_tlv_t *tlv, void *data, mrp_native_type_t *t, + mrp_typemap_t *idmap); +static int decode_struct(mrp_tlv_t *tlv, mrp_list_hook_t **chunks, + void **datap, uint32_t *idp, mrp_typemap_t *idmap); +static int print_struct(char **buf, size_t *size, int level, + void *data, mrp_native_type_t *t); +static void free_native(mrp_native_type_t *t); + +static void *alloc_chunk(mrp_list_hook_t **chunks, size_t size); +static void free_chunks(mrp_list_hook_t *chunks); + + +/* + * list and table of registered native types + */ + +static MRP_LIST_HOOK(types); +static int ntype; + +static mrp_native_type_t **typetbl; + + +static mrp_native_member_t *native_member(mrp_native_type_t *t, int idx) +{ + if (0 <= idx && idx < (int)t->nmember) + return t->members + idx; + else { + errno = EINVAL; + return NULL; + } +} + + +static int member_index(mrp_native_type_t *t, const char *name) +{ + mrp_native_member_t *m; + size_t i; + + for (i = 0, m = t->members; i < t->nmember; i++, m++) + if (!strcmp(m->any.name, name)) + return m - t->members; + + return -1; +} + + +static int copy_member(mrp_native_type_t *t, mrp_native_member_t *m) +{ + mrp_native_member_t *tm; + size_t size; + + if ((tm = native_member(t, member_index(t, m->any.name))) != NULL) + return tm - t->members; + else + tm = t->members + t->nmember; + + *tm = *m; + + if (*m->any.name != '"') + tm->any.name = mrp_strdup(m->any.name); + else { + size = strlen(m->any.name) + 1 - 2; + + if ((tm->any.name = mrp_allocz(size)) != NULL) + strncpy(tm->any.name, m->any.name + 1, size - 1); + } + + if (tm->any.name != NULL) { + t->nmember++; + return tm - t->members; + } + else + return -1; +} + + +static mrp_native_type_t *find_type(const char *type_name) +{ + mrp_native_type_t *t; + mrp_list_hook_t *p, *n; + + mrp_list_foreach(&types, p, n) { + t = mrp_list_entry(p, typeof(*t), hook); + + if (!strcmp(t->name, type_name)) + return t; + } + + return NULL; +} + + +static mrp_native_type_t *lookup_type(uint32_t id) +{ + mrp_native_type_t *t; + mrp_list_hook_t *p, *n; + + /* XXX TODO: turn this into a real lookup instead of linear search */ + + if (1 <= id && id <= (uint32_t)ntype) + if ((t = typetbl[id]) != NULL && t->id == id) + return t; + + mrp_log_warning("Type lookup for %u failed, doing linear search...\n", id); + + mrp_list_foreach(&types, p, n) { + t = mrp_list_entry(p, typeof(*t), hook); + + if (t->id == id) + return t; + } + + return NULL; +} + + +static mrp_native_type_t *member_type(mrp_native_member_t *m) +{ + mrp_native_type_t *t; + + if (m->any.type != MRP_TYPE_STRUCT) + t = lookup_type(m->any.type); + else + t = lookup_type(m->strct.data_type.id); + + if (t == NULL) + errno = EINVAL; + + return t; +} + + +static inline uint32_t map_type(uint32_t id, mrp_typemap_t *idmap) +{ + uint32_t mapped = MRP_INVALID_TYPE; + + if (id < MRP_TYPE_STRUCT || idmap == NULL) + mapped = id; + else { + while (idmap->type_id != MRP_INVALID_TYPE) { + if (idmap->type_id == id) { + mapped = MRP_TYPE_STRUCT + idmap->mapped; + break; + } + else + idmap++; + } + } + + return mapped; +} + + +static inline uint32_t mapped_type(uint32_t mapped, mrp_typemap_t *idmap) +{ + uint32_t id = MRP_INVALID_TYPE; + + if (mapped < MRP_TYPE_STRUCT || idmap == NULL) + id = mapped; + else { + while (idmap->type_id != MRP_INVALID_TYPE) { + if (MRP_TYPE_STRUCT + idmap->mapped == mapped) { + id = idmap->type_id; + break; + } + else + idmap++; + } + } + + return id; +} + + +uint32_t mrp_type_id(const char *type_name) +{ + mrp_native_type_t *t; + + if ((t = find_type(type_name)) != NULL) + return t->id; + else + return MRP_INVALID_TYPE; +} + + +static size_t type_size(uint32_t id) +{ + mrp_native_type_t *t = lookup_type(id); + + if (t != NULL) + return t->size; + else + return 0; +} + + +static int matching_types(mrp_native_type_t *t1, mrp_native_type_t *t2) +{ + MRP_UNUSED(t1); + MRP_UNUSED(t2); + + /* XXX TODO */ + return 0; +} + + +static void register_default_types(void) +{ +#define DEFAULT_NTYPE (MRP_TYPE_STRUCT + 1) + +#define DECLARE_TYPE(_ctype, _mtype) \ + static mrp_native_type_t _mtype##_type = { \ + .name = #_ctype, \ + .id = MRP_TYPE_##_mtype, \ + .size = sizeof(_ctype), \ + .members = NULL, \ + .nmember = 0, \ + .hook = { NULL, NULL } \ + } + +#define REGISTER_TYPE(_type) \ + mrp_list_init(&(_type)->hook); \ + mrp_list_append(&types, &(_type)->hook); \ + typetbl[(_type)->id] = (_type) + + if (mrp_reallocz(typetbl, 0, DEFAULT_NTYPE) == NULL) { + mrp_log_error("Failed to initialize native type table."); + abort(); + } + + DECLARE_TYPE( int8_t , INT8 ); + DECLARE_TYPE(uint8_t , UINT8 ); + DECLARE_TYPE(int16_t , INT16 ); + DECLARE_TYPE(uint16_t , UINT16); + DECLARE_TYPE(int32_t , INT32 ); + DECLARE_TYPE(uint32_t , UINT32); + DECLARE_TYPE(int64_t , INT64 ); + DECLARE_TYPE(uint64_t , UINT64); + DECLARE_TYPE(float , FLOAT ); + DECLARE_TYPE(double , DOUBLE); + DECLARE_TYPE(bool , BOOL ); + DECLARE_TYPE(int , INT ); + DECLARE_TYPE(unsigned int , UINT ); + DECLARE_TYPE(short , SHORT ); + DECLARE_TYPE(unsigned short, USHORT); + DECLARE_TYPE(size_t , SIZET ); + DECLARE_TYPE(ssize_t , SSIZET); + DECLARE_TYPE(char * , STRING); + DECLARE_TYPE(void * , BLOB ); + DECLARE_TYPE(void * , ARRAY ); + DECLARE_TYPE(void * , STRUCT); + + REGISTER_TYPE(&INT8_type); + REGISTER_TYPE(&UINT8_type); + REGISTER_TYPE(&INT16_type); + REGISTER_TYPE(&UINT16_type); + REGISTER_TYPE(&INT32_type); + REGISTER_TYPE(&UINT32_type); + REGISTER_TYPE(&INT64_type); + REGISTER_TYPE(&UINT64_type); + REGISTER_TYPE(&FLOAT_type); + REGISTER_TYPE(&DOUBLE_type); + REGISTER_TYPE(&BOOL_type); + REGISTER_TYPE(&INT_type); + REGISTER_TYPE(&UINT_type); + REGISTER_TYPE(&SHORT_type); + REGISTER_TYPE(&USHORT_type); + REGISTER_TYPE(&SIZET_type); + REGISTER_TYPE(&SSIZET_type); + REGISTER_TYPE(&STRING_type); + REGISTER_TYPE(&BLOB_type); + REGISTER_TYPE(&ARRAY_type); + REGISTER_TYPE(&STRUCT_type); + + ntype = DEFAULT_NTYPE; + +#undef DECLARE_TYPE +#undef REGISTER_TYPE +} + + +uint32_t mrp_register_native(mrp_native_type_t *type) +{ + mrp_native_type_t *existing = find_type(type->name); + mrp_native_type_t *t, *elemt; + mrp_native_member_t *s, *d, *m; + int idx; + + (void)member_type; + + if (existing != NULL && !matching_types(existing, type)) { + errno = EEXIST; + return MRP_INVALID_TYPE; + } + + if (ntype == 0) + register_default_types(); + + if ((t = mrp_allocz(sizeof(*t))) == NULL) + return MRP_INVALID_TYPE; + + mrp_list_init(&t->hook); + t->name = mrp_strdup(type->name); + + if (t->name == NULL) + goto fail; + + t->size = type->size; + t->members = mrp_allocz_array(mrp_native_member_t, type->nmember); + + if (t->members == NULL && type->nmember != 0) + goto fail; + + /* + * Notes: + * + * While we copy the members, we also take care of reordering them + * so that any member that another one depends on ('size' members) + * get registered (and consequently encoded and decoded) before the + * dependant members. + */ + + s = type->members; + d = t->members; + while (t->nmember < type->nmember) { + /* make sure there are no duplicate members */ + if (native_member(type, member_index(type, s->any.name)) != s) { + errno = EINVAL; + goto fail; + } + + /* skip already copied members */ + while (member_index(t, s->any.name) >= 0) + s++; + + switch (s->any.type) { + case MRP_TYPE_BLOB: + m = native_member(t, member_index(t, s->blob.size.name)); + + if (m == NULL) { + m = native_member(type, + member_index(type, s->blob.size.name)); + + if (m == NULL) + goto fail; + else + idx = copy_member(t, m); + + if (idx < 0) + goto fail; + } + else + idx = m - t->members; + + if (copy_member(t, s) < 0) + goto fail; + + d = t->members + t->nmember; + d->blob.size.idx = idx; + + break; + + case MRP_TYPE_ARRAY: + if (s->array.kind == MRP_ARRAY_SIZE_EXPLICIT) { + m = native_member(t, member_index(t, s->array.size.name)); + + if (m == NULL) { + m = native_member(type, + member_index(type, s->array.size.name)); + + if (m == NULL) + goto fail; + else + idx = copy_member(t, m); + + if (idx < 0) + goto fail; + + } + else + idx = m - t->members; + + d = t->members + t->nmember; + + if (copy_member(t, s) < 0) + goto fail; + + d->array.size.idx = idx; + } + else { + d = t->members + t->nmember; + + if (copy_member(t, s) < 0) + goto fail; + } + + d->array.elem.id = mrp_type_id(d->array.elem.name); + + if (d->array.elem.id == MRP_INVALID_TYPE) + goto fail; + + if (s->array.kind == MRP_ARRAY_SIZE_GUARDED) { + elemt = lookup_type(d->array.elem.id); + + if (elemt == NULL) + goto fail; + + if (elemt->id < MRP_TYPE_ARRAY) + idx = 0; + else { + idx = member_index(elemt, s->array.size.name); + d->array.size.idx = member_index(elemt, s->array.size.name); + + if (d->array.size.idx == (uint32_t)-1) + goto fail; + } + } + + break; + + case MRP_TYPE_STRUCT: + d = t->members + t->nmember; + + if (copy_member(t, s) < 0) + goto fail; + + d->strct.data_type.id = mrp_type_id(d->strct.data_type.name); + + if (d->strct.data_type.id == MRP_INVALID_TYPE) + goto fail; + break; + + default: + if (copy_member(t, s) < 0) + goto fail; + } + } + + if (mrp_reallocz(typetbl, ntype, ntype + 1) == NULL) + goto fail; + + t->id = ntype; + mrp_list_append(&types, &t->hook); + typetbl[ntype] = t; + ntype++; + + return t->id; + + fail: + free_native(t); + + return MRP_INVALID_TYPE; +} + + +static void free_native(mrp_native_type_t *t) +{ + mrp_native_member_t *m; + size_t i; + + if (t == NULL) + return; + + mrp_list_delete(&t->hook); + + mrp_free(t->name); + for (i = 0, m = t->members; i < t->nmember; i++, m++) + mrp_free(m->any.name); + mrp_free(t); +} + + +static int encode_basic(mrp_tlv_t *tlv, mrp_type_t type, mrp_value_t *v) +{ + switch (type) { + case MRP_TYPE_INT8: return mrp_tlv_push_int8 (tlv, TAG_NONE, v->s8); + case MRP_TYPE_UINT8: return mrp_tlv_push_uint8 (tlv, TAG_NONE, v->u8); + case MRP_TYPE_INT16: return mrp_tlv_push_int16 (tlv, TAG_NONE, v->s16); + case MRP_TYPE_UINT16: return mrp_tlv_push_uint16(tlv, TAG_NONE, v->u16); + case MRP_TYPE_INT32: return mrp_tlv_push_int32 (tlv, TAG_NONE, v->s32); + case MRP_TYPE_UINT32: return mrp_tlv_push_uint32(tlv, TAG_NONE, v->u32); + case MRP_TYPE_INT64: return mrp_tlv_push_int64 (tlv, TAG_NONE, v->s64); + case MRP_TYPE_UINT64: return mrp_tlv_push_uint64(tlv, TAG_NONE, v->u64); + case MRP_TYPE_FLOAT: return mrp_tlv_push_float (tlv, TAG_NONE, v->flt); + case MRP_TYPE_DOUBLE: return mrp_tlv_push_double(tlv, TAG_NONE, v->dbl); + case MRP_TYPE_BOOL: return mrp_tlv_push_bool (tlv, TAG_NONE, v->bln); + case MRP_TYPE_STRING: return mrp_tlv_push_string(tlv, TAG_NONE, v->str); + + case MRP_TYPE_INT: + return mrp_tlv_push_int32 (tlv, TAG_NONE, (int32_t)v->i); + case MRP_TYPE_UINT: + return mrp_tlv_push_uint32(tlv, TAG_NONE, (uint32_t)v->ui); + case MRP_TYPE_SHORT: + return mrp_tlv_push_int32 (tlv, TAG_NONE, (int32_t)v->si); + case MRP_TYPE_USHORT: + return mrp_tlv_push_uint32(tlv, TAG_NONE, (uint32_t)v->usi); + case MRP_TYPE_SIZET: + return mrp_tlv_push_uint32(tlv, TAG_NONE, (uint32_t)v->sz); + case MRP_TYPE_SSIZET: + return mrp_tlv_push_int32 (tlv, TAG_NONE, (int32_t)v->ssz); + + default: + return -1; + } +} + + +static inline int get_blob_size(void *base, mrp_native_type_t *t, + mrp_native_blob_t *m, size_t *sizep) +{ + mrp_native_member_t *sizem; + mrp_value_t *v; + + if ((sizem = native_member(t, m->size.idx)) == NULL) + return -1; + + if (sizem->any.layout == MRP_LAYOUT_INDIRECT) + v = *(void **)base; + else + v = base; + + switch (sizem->any.type) { + case MRP_TYPE_INT8: *sizep = v->s8; return 0; + case MRP_TYPE_UINT8: *sizep = v->u8; return 0; + case MRP_TYPE_INT16: *sizep = v->s16; return 0; + case MRP_TYPE_UINT16: *sizep = v->u16; return 0; + case MRP_TYPE_INT32: *sizep = v->s32; return 0; + case MRP_TYPE_UINT32: *sizep = v->u32; return 0; + case MRP_TYPE_INT64: *sizep = (size_t)v->s32; return 0; + case MRP_TYPE_UINT64: *sizep = (size_t)v->u32; return 0; + default: + errno = EINVAL; + return -1; + } +} + + +static int guard_offset_and_size(mrp_native_array_t *m, size_t *offsp, + size_t *sizep) +{ + mrp_native_type_t *t = lookup_type(m->elem.id); + mrp_native_member_t *g; + + if (t == NULL) + return -1; + + switch (t->id) { + case MRP_TYPE_INT8: + case MRP_TYPE_UINT8: + case MRP_TYPE_INT16: + case MRP_TYPE_UINT16: + case MRP_TYPE_INT32: + case MRP_TYPE_UINT32: + case MRP_TYPE_INT64: + case MRP_TYPE_UINT64: + case MRP_TYPE_FLOAT: + case MRP_TYPE_DOUBLE: + case MRP_TYPE_BOOL: + case MRP_TYPE_STRING: + case MRP_TYPE_INT: + case MRP_TYPE_UINT: + case MRP_TYPE_SHORT: + case MRP_TYPE_USHORT: + case MRP_TYPE_SIZET: + case MRP_TYPE_SSIZET: + *offsp = 0; + *sizep = t->size; + return 0; + + default: + if ((g = native_member(t, m->size.idx)) == NULL) + return -1; + + *offsp = g->any.offs; + *sizep = type_size(g->any.type); + return 0; + } +} + + +static inline int get_explicit_array_size(void *base, mrp_native_type_t *t, + mrp_native_array_t *m) +{ + mrp_native_member_t *nelemm; + mrp_value_t *v; + int n; + + if ((nelemm = native_member(t, m->size.idx)) == NULL) + return -1; + if (nelemm->any.layout == MRP_LAYOUT_INDIRECT) + v = *(void **)(base + nelemm->any.offs); + else + v = base + nelemm->any.offs; + + switch (nelemm->any.type) { + case MRP_TYPE_INT8: n = v->s8; break; + case MRP_TYPE_UINT8: n = v->u8; break; + case MRP_TYPE_INT16: n = v->s16; break; + case MRP_TYPE_UINT16: n = v->u16; break; + case MRP_TYPE_INT32: n = v->s32; break; + case MRP_TYPE_UINT32: n = v->u32; break; + case MRP_TYPE_INT64: n = (int)v->s64; break; + case MRP_TYPE_UINT64: n = (int)v->u64; break; + + case MRP_TYPE_INT: n = (int) v->i; break; + case MRP_TYPE_UINT: n = (unsigned int) v->ui; break; + case MRP_TYPE_SHORT: n = (short) v->si; break; + case MRP_TYPE_USHORT: n = (unsigned short)v->usi; break; + case MRP_TYPE_SIZET: n = (size_t) v->sz; break; + case MRP_TYPE_SSIZET: n = (ssize_t) v->ssz; break; + + default: + errno = EINVAL; + return -1; + } + + return n; +} + + +static inline int get_guarded_array_size(void *arrp, mrp_native_array_t *m) +{ + mrp_value_t *guard; + size_t goffs, gsize, esize; + int n; + + if ((esize = type_size(m->elem.id)) == 0) + return -1; + + if (guard_offset_and_size(m, &goffs, &gsize) < 0) + return -1; + + guard = &m->sentinel; + + for (n = 0; memcmp(arrp + n * esize + goffs, guard, gsize); n++) + ; + return n; +} + + +static int get_array_size(void *base, mrp_native_type_t *t, void *arrp, + mrp_native_array_t *m, size_t *nelemp, + size_t *esizep) +{ + int n; + + if ((*esizep = type_size(m->elem.id)) == 0) + return -1; + + switch (m->kind) { + case MRP_ARRAY_SIZE_FIXED: + *nelemp = m->size.nelem; + return 0; + + case MRP_ARRAY_SIZE_EXPLICIT: + if ((n = get_explicit_array_size(base, t, m)) < 0) + return -1; + + *nelemp = (size_t)n; + return 0; + + case MRP_ARRAY_SIZE_GUARDED: + if ((n = get_guarded_array_size(arrp, m)) < 0) + return -1; + + *nelemp = (size_t)n; + return 0; + + default: + return -1; + } +} + + +static int terminate_guarded_array(void *elem, mrp_native_array_t *m, + mrp_native_type_t *mt) +{ + mrp_native_member_t *g; + + if (m->elem.id <= MRP_TYPE_STRING) + memcpy(elem, &m->sentinel, mt->size); + else if (m->elem.id > MRP_TYPE_STRUCT) { + if ((g = native_member(mt, m->size.idx)) == NULL) + return -1; + + memcpy(elem + g->any.offs, &m->sentinel, type_size(g->any.type)); + } + + return 0; +} + + +static int encode_array(mrp_tlv_t *tlv, void *arrp, mrp_native_array_t *m, + size_t nelem, size_t elem_size, mrp_typemap_t *idmap) +{ + mrp_native_type_t *t; + mrp_value_t *v; + void *elem; + size_t i; + + if (mrp_tlv_push_uint32(tlv, TAG_ARRAY, map_type(m->elem.id, idmap)) < 0) + return -1; + + if (mrp_tlv_push_uint32(tlv, TAG_NELEM, nelem) < 0) + return -1; + + if ((t = lookup_type(m->elem.id)) == NULL) + return -1; + + for (i = 0, elem = arrp; i < nelem; i++, elem += elem_size) { + v = elem; + + switch (t->id) { + case MRP_TYPE_STRING: + v = *(void **)elem; + case MRP_TYPE_INT8: + case MRP_TYPE_UINT8: + case MRP_TYPE_INT16: + case MRP_TYPE_UINT16: + case MRP_TYPE_INT32: + case MRP_TYPE_UINT32: + case MRP_TYPE_INT64: + case MRP_TYPE_UINT64: + case MRP_TYPE_FLOAT: + case MRP_TYPE_DOUBLE: + case MRP_TYPE_BOOL: + case MRP_TYPE_INT: + case MRP_TYPE_UINT: + case MRP_TYPE_SHORT: + case MRP_TYPE_USHORT: + case MRP_TYPE_SIZET: + case MRP_TYPE_SSIZET: + if (encode_basic(tlv, t->id, v) < 0) + return -1; + break; + + case MRP_TYPE_BLOB: /* XXX TODO implement blobs */ + return -1; + + case MRP_TYPE_ARRAY: + return -1; + + default: + /* an MRP_TYPE_STRUCT */ + if (encode_struct(tlv, elem, t, idmap) < 0) + return -1; + break; + } + } + + return 0; +} + + +static int encode_struct(mrp_tlv_t *tlv, void *data, mrp_native_type_t *t, + mrp_typemap_t *idmap) +{ + mrp_native_member_t *m; + mrp_native_type_t *mt; + mrp_value_t *v; + uint32_t idx; + size_t size, nelem; + + if (t == NULL) + return -1; + + if (mrp_tlv_push_uint32(tlv, TAG_STRUCT, map_type(t->id, idmap)) < 0) + return -1; + + for (idx = 0, m = t->members; idx < t->nmember; idx++, m++) { + if (mrp_tlv_push_uint32(tlv, TAG_MEMBER, idx) < 0) + return -1; + + if (m->any.layout == MRP_LAYOUT_INDIRECT) + v = *(void **)(data + m->any.offs); + else + v = data + m->any.offs; + + switch (m->any.type) { + case MRP_TYPE_INT8: + case MRP_TYPE_UINT8: + case MRP_TYPE_INT16: + case MRP_TYPE_UINT16: + case MRP_TYPE_INT32: + case MRP_TYPE_UINT32: + case MRP_TYPE_INT64: + case MRP_TYPE_UINT64: + case MRP_TYPE_FLOAT: + case MRP_TYPE_DOUBLE: + case MRP_TYPE_BOOL: + case MRP_TYPE_STRING: + case MRP_TYPE_INT: + case MRP_TYPE_UINT: + case MRP_TYPE_SHORT: + case MRP_TYPE_USHORT: + case MRP_TYPE_SIZET: + case MRP_TYPE_SSIZET: + if (encode_basic(tlv, m->any.type, v) < 0) + return -1; + break; + + case MRP_TYPE_BLOB: /* XXX TODO implement blobs */ + if (get_blob_size(data, t, &m->blob, &size) < 0) + return -1; + return -1; + + case MRP_TYPE_ARRAY: + if (get_array_size(data, t, v->ptr, &m->array, &nelem, &size) < 0) + return -1; + if (encode_array(tlv, v->ptr, &m->array, nelem, + size, idmap) < 0) + return -1; + break; + + case MRP_TYPE_STRUCT: + if ((mt = lookup_type(m->strct.data_type.id)) == NULL) + return -1; + if (encode_struct(tlv, v->ptr, mt, idmap) < 0) + return -1; + break; + + default: + return -1; + } + } + + return 0; +} + + +int mrp_encode_native(void *data, uint32_t id, size_t reserve, void **bufp, + size_t *sizep, mrp_typemap_t *idmap) +{ + mrp_native_type_t *t = lookup_type(id); + mrp_tlv_t tlv; + + *bufp = NULL; + *sizep = 0; + + if (t == NULL) + return -1; + + if (mrp_tlv_setup_write(&tlv, reserve + 4096) < 0) + return -1; + + if (reserve > 0) + if (mrp_tlv_reserve(&tlv, reserve, 1) == NULL) + goto fail; + + if (encode_struct(&tlv, data, t, idmap) < 0) + goto fail; + + mrp_tlv_trim(&tlv); + mrp_tlv_steal(&tlv, bufp, sizep); + + return 0; + + fail: + mrp_tlv_cleanup(&tlv); + return -1; +} + + +static void *allocate_indirect(mrp_list_hook_t **chunks, mrp_value_t *v, + mrp_native_member_t *m, mrp_typemap_t *idmap) +{ + size_t size; + + switch (m->any.type) { + case MRP_TYPE_INT8: + case MRP_TYPE_UINT8: + return (v->ptr = alloc_chunk(chunks, sizeof(int8_t))); + case MRP_TYPE_INT16: + case MRP_TYPE_UINT16: + return (v->ptr = alloc_chunk(chunks, sizeof(int16_t))); + case MRP_TYPE_INT32: + case MRP_TYPE_UINT32: + return (v->ptr = alloc_chunk(chunks, sizeof(int32_t))); + case MRP_TYPE_INT64: + case MRP_TYPE_UINT64: + return (v->ptr = alloc_chunk(chunks, sizeof(int64_t))); + case MRP_TYPE_FLOAT: + return (v->ptr = alloc_chunk(chunks, sizeof(float))); + case MRP_TYPE_DOUBLE: + return (v->ptr = alloc_chunk(chunks, sizeof(double))); + case MRP_TYPE_BOOL: + return (v->ptr = alloc_chunk(chunks, sizeof(bool))); + case MRP_TYPE_STRING: + return v; /* will be allocated by TLV pull */ + case MRP_TYPE_BLOB: + return v; /* will be allocated by decoder */ + case MRP_TYPE_ARRAY: + return v; /* will be allocated by decoder */ + case MRP_TYPE_STRUCT: + if ((size = type_size(mapped_type(m->strct.data_type.id, idmap))) == 0) + return NULL; + return (v->ptr = alloc_chunk(chunks, size)); + default: + return NULL; + } +} + + +static void *alloc_str_chunk(size_t size, void *chunksp) +{ + return alloc_chunk((mrp_list_hook_t **)chunksp, size); +} + + +static int decode_basic(mrp_tlv_t *tlv, mrp_list_hook_t **chunks, + mrp_type_t type, mrp_value_t *v) +{ + int32_t i; + uint32_t u; + + switch (type) { + case MRP_TYPE_INT8: return mrp_tlv_pull_int8 (tlv, TAG_NONE, &v->s8); + case MRP_TYPE_UINT8: return mrp_tlv_pull_uint8 (tlv, TAG_NONE, &v->u8); + case MRP_TYPE_INT16: return mrp_tlv_pull_int16 (tlv, TAG_NONE, &v->s16); + case MRP_TYPE_UINT16: return mrp_tlv_pull_uint16(tlv, TAG_NONE, &v->u16); + case MRP_TYPE_INT32: return mrp_tlv_pull_int32 (tlv, TAG_NONE, &v->s32); + case MRP_TYPE_UINT32: return mrp_tlv_pull_uint32(tlv, TAG_NONE, &v->u32); + case MRP_TYPE_INT64: return mrp_tlv_pull_int64 (tlv, TAG_NONE, &v->s64); + case MRP_TYPE_UINT64: return mrp_tlv_pull_uint64(tlv, TAG_NONE, &v->u64); + case MRP_TYPE_FLOAT: return mrp_tlv_pull_float (tlv, TAG_NONE, &v->flt); + case MRP_TYPE_DOUBLE: return mrp_tlv_pull_double(tlv, TAG_NONE, &v->dbl); + case MRP_TYPE_BOOL: return mrp_tlv_pull_bool (tlv, TAG_NONE, &v->bln); + case MRP_TYPE_STRING: + return mrp_tlv_pull_string(tlv, TAG_NONE, &v->strp, + -1, alloc_str_chunk, chunks); + + case MRP_TYPE_INT: + if (mrp_tlv_pull_int32(tlv, TAG_NONE, &i) < 0) + return -1; + v->i = (int)i; + return 0; + + case MRP_TYPE_UINT: + if (mrp_tlv_pull_uint32(tlv, TAG_NONE, &u) < 0) + return -1; + v->ui = (unsigned int)u; + return 0; + + case MRP_TYPE_SHORT: + if (mrp_tlv_pull_int32(tlv, TAG_NONE, &i) < 0) + return -1; + v->si = (short)i; + return 0; + + case MRP_TYPE_USHORT: + if (mrp_tlv_pull_uint32(tlv, TAG_NONE, &u) < 0) + return -1; + v->usi = (unsigned short)u; + return 0; + + case MRP_TYPE_SIZET: + if (mrp_tlv_pull_uint32(tlv, TAG_NONE, &u) < 0) + return -1; + v->sz = (size_t)u; + return 0; + + case MRP_TYPE_SSIZET: + if (mrp_tlv_pull_int32(tlv, TAG_NONE, &i) < 0) + return -1; + v->ssz = (ssize_t)i; + return 0; + + default: + return -1; + } +} + + +static int decode_array(mrp_tlv_t *tlv, mrp_list_hook_t **chunks, + void **arrp, mrp_native_array_t *m, + void *data, mrp_native_type_t *t, + mrp_typemap_t *idmap) +{ + mrp_native_type_t *mt; + mrp_value_t *v; + void *elem, *base; + size_t elem_size, i; + uint32_t id, nelem; + int n, guard; + + if (mrp_tlv_pull_uint32(tlv, TAG_ARRAY, &id) < 0) + return -1; + + if ((id = mapped_type(id, idmap)) != m->elem.id) + return -1; + + if ((elem_size = type_size(id)) == 0) + return -1; + + if (mrp_tlv_pull_uint32(tlv, TAG_NELEM, &nelem) < 0) + return -1; + + if ((mt = lookup_type(m->elem.id)) == NULL) + return -1; + + switch (m->kind) { + case MRP_ARRAY_SIZE_EXPLICIT: + if ((n = get_explicit_array_size(data, t, m)) < 0) + return -1; + guard = 0; + break; + case MRP_ARRAY_SIZE_FIXED: + n = m->size.nelem; + guard = 0; + break; + case MRP_ARRAY_SIZE_GUARDED: + n = nelem; + guard = 1; + break; + default: + return -1; + } + + if (n != (int)nelem) + return -1; + + switch (m->layout) { + case MRP_LAYOUT_INLINED: + base = (void *)arrp; + break; + case MRP_LAYOUT_INDIRECT: + case MRP_LAYOUT_DEFAULT: + if ((*arrp = alloc_chunk(chunks, (nelem + guard) * elem_size)) == NULL) + return (nelem + guard) ? -1 : 0; + base = *arrp; + break; + default: + return -1; + } + + for (i = 0, elem = base; i < nelem; i++, elem += elem_size) { + v = elem; + + switch (mt->id) { + case MRP_TYPE_INT8: + case MRP_TYPE_UINT8: + case MRP_TYPE_INT16: + case MRP_TYPE_UINT16: + case MRP_TYPE_INT32: + case MRP_TYPE_UINT32: + case MRP_TYPE_INT64: + case MRP_TYPE_UINT64: + case MRP_TYPE_FLOAT: + case MRP_TYPE_DOUBLE: + case MRP_TYPE_BOOL: + case MRP_TYPE_STRING: + case MRP_TYPE_INT: + case MRP_TYPE_UINT: + case MRP_TYPE_SHORT: + case MRP_TYPE_USHORT: + case MRP_TYPE_SIZET: + case MRP_TYPE_SSIZET: + if (decode_basic(tlv, chunks, mt->id, v) < 0) + return -1; + break; + + case MRP_TYPE_BLOB: /* XXX TODO implement blobs */ + return -1; + + case MRP_TYPE_ARRAY: + return -1; + + default: + /* an MRP_TYPE_STRUCT */ + if (decode_struct(tlv, chunks, &elem, &id, idmap) < 0) + return -1; + } + } + + if (guard) { + if (terminate_guarded_array(elem, m, mt) < 0) + return -1; + } + + return 0; +} + + +static int decode_struct(mrp_tlv_t *tlv, mrp_list_hook_t **chunks, + void **datap, uint32_t *idp, mrp_typemap_t *idmap) +{ + mrp_native_type_t *t; + mrp_native_member_t *m; + mrp_value_t *v; + char *str, **strp; + size_t max, i; + uint32_t idx, id; + + if (datap == NULL) { + errno = EFAULT; + return -1; + } + + if (mrp_tlv_pull_uint32(tlv, TAG_STRUCT, &id) < 0) + return -1; + else + id = mapped_type(id, idmap); + + if (*idp) { + if (*idp != id) { + errno = EINVAL; + return -1; + } + } + else + *idp = id; + + if ((t = lookup_type(id)) == NULL) + return -1; + + if (*datap == NULL) + if ((*datap = alloc_chunk(chunks, t->size)) == NULL) + return -1; + + for (i = 0, m = t->members; i < t->nmember; i++, m++) { + if (mrp_tlv_pull_uint32(tlv, TAG_MEMBER, &idx) < 0) + return -1; + + v = *datap + m->any.offs; + + if (m->any.layout == MRP_LAYOUT_INDIRECT) { + if ((v = allocate_indirect(chunks, v, m, idmap)) == NULL) + return -1; + } + + switch (m->any.type) { + case MRP_TYPE_INT8: + case MRP_TYPE_UINT8: + case MRP_TYPE_INT16: + case MRP_TYPE_UINT16: + case MRP_TYPE_INT32: + case MRP_TYPE_UINT32: + case MRP_TYPE_INT64: + case MRP_TYPE_UINT64: + case MRP_TYPE_FLOAT: + case MRP_TYPE_DOUBLE: + case MRP_TYPE_BOOL: + case MRP_TYPE_INT: + case MRP_TYPE_UINT: + case MRP_TYPE_SHORT: + case MRP_TYPE_USHORT: + case MRP_TYPE_SIZET: + case MRP_TYPE_SSIZET: + if (decode_basic(tlv, chunks, m->any.type, v) < 0) + return -1; + break; + + case MRP_TYPE_STRING: + if (m->any.layout == MRP_LAYOUT_INLINED) { + max = m->str.size; + str = v->str; + strp = &str; + } + else { + max = (size_t)-1; + strp = &v->strp; + } + if (mrp_tlv_pull_string(tlv, TAG_NONE, strp, max, + alloc_str_chunk, chunks) < 0) + return -1; + break; + + case MRP_TYPE_BLOB: /* XXX TODO implement blobs */ + return -1; + + case MRP_TYPE_ARRAY: + if (decode_array(tlv, chunks, &v->ptr, &m->array, + *datap, t, idmap) < 0) + return -1; + break; + + case MRP_TYPE_STRUCT: + id = m->strct.data_type.id; + if (decode_struct(tlv, chunks, &v->ptr, &id, idmap) < 0) + return -1; + break; + + default: + return -1; + } + } + + return 0; +} + + +int mrp_decode_native(void **bufp, size_t *sizep, void **datap, uint32_t *idp, + mrp_typemap_t *idmap) +{ + mrp_tlv_t tlv; + mrp_list_hook_t *chunks; + void *data; + size_t diff; + + chunks = NULL; + data = NULL; + + if (mrp_tlv_setup_read(&tlv, *bufp, *sizep) < 0) + return -1; + + if (decode_struct(&tlv, &chunks, &data, idp, idmap) == 0) { + diff = mrp_tlv_offset(&tlv); + + if (diff <= *sizep) { + *bufp += diff; + *sizep -= diff; + *datap = data; + + return 0; + } + } + + free_chunks(chunks); + + return -1; +} + + +void mrp_free_native(void *data, uint32_t id) +{ + mrp_list_hook_t *chunks; + + MRP_UNUSED(id); + + if (data != NULL) { + chunks = ((void *)data) - MRP_OFFSET(chunk_t, data); + free_chunks(chunks); + } +} + + +#define INDENT(_level, _fmt) "%*.*s"_fmt, _level * 4, _level * 4, "" + +#define PRINT(_l, _p, _size, fmt, args...) do { \ + ssize_t _n; \ + _n = snprintf((_p), (_size), INDENT(_l, fmt), ## args); \ + if (_n >= (ssize_t)(_size)) \ + return -1; \ + (_p) += _n; \ + (_size) -= _n; \ + } while (0) + + +static int print_basic(int level, char **bufp, size_t *sizep, int type, + const char *name, mrp_value_t *v) +{ +#define NAME name ? name : "", name ? " = " : "" + char *p = *bufp; + size_t size = *sizep; + + if (type >= MRP_TYPE_BLOB) + return -1; + + switch (type) { + case MRP_TYPE_INT8: + PRINT(level, p, size, "%s%s%d\n", NAME, v->s8); + break; + case MRP_TYPE_UINT8: + PRINT(level, p, size, "%s%s%u\n", NAME, v->u8); + break; + + case MRP_TYPE_INT16: + PRINT(level, p, size, "%s%s%d\n", NAME, v->s16); + break; + case MRP_TYPE_UINT16: + PRINT(level, p, size, "%s%s%u\n", NAME, v->u16); + break; + + case MRP_TYPE_INT32: + PRINT(level, p, size, "%s%s%d\n", NAME, v->s32); + break; + case MRP_TYPE_UINT32: + PRINT(level, p, size, "%s%s%u\n", NAME, v->u32); + break; + + case MRP_TYPE_INT64: + PRINT(level, p, size, "%s%s%lld\n", NAME, (long long)v->s64); + break; + case MRP_TYPE_UINT64: + PRINT(level, p, size, "%s%s%llu\n", NAME, + (unsigned long long)v->s64); + break; + + case MRP_TYPE_FLOAT: + PRINT(level, p, size, "%s%s%f\n", NAME, v->flt); + break; + case MRP_TYPE_DOUBLE: + PRINT(level, p, size, "%s%s%f\n", NAME, v->dbl); + break; + + case MRP_TYPE_BOOL: + PRINT(level, p, size, "%s%s%s\n", NAME, + v->bln ? "<true>" : "<false>"); + break; + + case MRP_TYPE_STRING: + PRINT(level, p, size, "%s%s%s\n", NAME, + v->str ? v->str : "<null>"); + break; + + case MRP_TYPE_INT: + PRINT(level, p, size, "%s%s%d\n", NAME, v->i); + break; + case MRP_TYPE_UINT: + PRINT(level, p, size, "%s%s%u\n", NAME, v->ui); + break; + + case MRP_TYPE_SHORT: + PRINT(level, p, size, "%s%s%hd\n", NAME, v->si); + break; + case MRP_TYPE_USHORT: + PRINT(level, p, size, "%s%s%hu\n", NAME, v->usi); + break; + + case MRP_TYPE_SIZET: + PRINT(level, p, size, "%s%s%zu\n", NAME, v->sz); + break; + case MRP_TYPE_SSIZET: + PRINT(level, p, size, "%s%s%zd\n", NAME, v->ssz); + break; + + default: + PRINT(level, p, size, "%s%s%s\n", NAME, "<unknown>"); + } + + *bufp = p; + *sizep = size; + + return 0; + +#undef NAME +} + + +static int print_array(char **bufp, size_t *sizep, int level, + void *arrp, mrp_native_array_t *a, size_t nelem, + size_t elem_size) +{ + mrp_native_type_t *et; + mrp_value_t *v; + void *elem; + size_t i; + char *p; + size_t size; + + p = *bufp; + size = *sizep; + + if ((et = lookup_type(a->elem.id)) == NULL) + return -1; + + PRINT(level, p, size, "%s = [%s", a->name, nelem == 0 ? "]" : "\n"); + level++; + + for (i = 0, elem = arrp; i < nelem; i++, elem += elem_size) { + v = elem; + + switch (et->id) { + case MRP_TYPE_STRING: + v = *(void **)elem; + case MRP_TYPE_INT8: + case MRP_TYPE_UINT8: + case MRP_TYPE_INT16: + case MRP_TYPE_UINT16: + case MRP_TYPE_INT32: + case MRP_TYPE_UINT32: + case MRP_TYPE_INT64: + case MRP_TYPE_UINT64: + case MRP_TYPE_FLOAT: + case MRP_TYPE_DOUBLE: + case MRP_TYPE_BOOL: + case MRP_TYPE_INT: + case MRP_TYPE_UINT: + case MRP_TYPE_SHORT: + case MRP_TYPE_USHORT: + case MRP_TYPE_SIZET: + case MRP_TYPE_SSIZET: + if (print_basic(level, &p, &size, et->id, NULL, v) < 0) + return -1; + break; + + case MRP_TYPE_BLOB: + PRINT(level, p, size, "<blob>\n"); + break; + + case MRP_TYPE_ARRAY: + return -1; + + default: + /* an MRP_TYPE_STRUCT */ + if (print_struct(&p, &size, level, elem, et) < 0) + return -1; + break; + } + } + + level--; + PRINT(level, p, size, "%s\n", nelem == 0 ? "" : "]"); + + *bufp = p; + *sizep = size; + + return 0; +} + + +static int print_struct(char **bufp, size_t *sizep, int level, + void *data, mrp_native_type_t *t) +{ + mrp_native_member_t *m; + mrp_native_type_t *mt; + mrp_value_t *v; + uint32_t idx; + size_t esize, nelem; + char *p; + size_t size; + + if (data == NULL) { + **bufp = '\0'; + + return 0; + } + + if (t == NULL) + return -1; + + p = *bufp; + size = *sizep; + PRINT(level, p, size, "{\n"); + level++; + + for (idx = 0, m = t->members; idx < t->nmember; idx++, m++) { + if (m->any.layout == MRP_LAYOUT_INDIRECT) + v = *(void **)(data + m->any.offs); + else + v = data + m->any.offs; + + switch (m->any.type) { + case MRP_TYPE_INT8: + case MRP_TYPE_UINT8: + case MRP_TYPE_INT16: + case MRP_TYPE_UINT16: + case MRP_TYPE_INT32: + case MRP_TYPE_UINT32: + case MRP_TYPE_INT64: + case MRP_TYPE_UINT64: + case MRP_TYPE_FLOAT: + case MRP_TYPE_DOUBLE: + case MRP_TYPE_BOOL: + case MRP_TYPE_INT: + case MRP_TYPE_UINT: + case MRP_TYPE_SHORT: + case MRP_TYPE_USHORT: + case MRP_TYPE_SIZET: + case MRP_TYPE_SSIZET: + case MRP_TYPE_STRING: + if (print_basic(level, &p, &size, m->any.type, m->any.name, v) < 0) + return -1; + break; + + case MRP_TYPE_BLOB: /* XXX TODO implement blobs */ + PRINT(level, p, size, "%s = <blob>\n", m->any.name); + break; + + case MRP_TYPE_ARRAY: + if (get_array_size(data, t, v->ptr, &m->array, &nelem, &esize) < 0) + return -1; + if (print_array(&p, &size, level, v->ptr, &m->array, + nelem, esize) < 0) + return -1; + break; + + case MRP_TYPE_STRUCT: + if ((mt = lookup_type(m->strct.data_type.id)) == NULL) + return -1; + if (print_struct(&p, &size, level, v->ptr, mt) < 0) + return -1; + break; + + default: + return -1; + } + } + + level--; + PRINT(level, p, size, "}\n"); + + *bufp = p; + *sizep = size; + + return 0; +} + + +ssize_t mrp_print_native(char *buf, size_t size, void *data, uint32_t id) +{ + mrp_native_type_t *t; + char *p; + + p = buf; + + if (id < MRP_TYPE_STRUCT || (t = lookup_type(id)) == NULL) { + errno = EINVAL; + return -1; + } + + if (print_struct(&p, &size, 0, data, t) == 0) + return (ssize_t)(p - buf); + else + return -1; +} + + +static inline size_t chunk_size(size_t size) +{ + return MRP_OFFSET(chunk_t, data[size]); +} + + +static void *alloc_chunk(mrp_list_hook_t **chunks, size_t size) +{ + chunk_t *chunk; + + if (size == 0) + return NULL; + + if (*chunks == NULL) { + if ((*chunks = mrp_allocz(sizeof(*chunks))) == NULL) + return NULL; + else + mrp_list_init(*chunks); + } + + if ((chunk = mrp_allocz(chunk_size(size))) == NULL) + return NULL; + + mrp_list_init(&chunk->hook); + mrp_list_append(*chunks, &chunk->hook); + + return &chunk->data[0]; +} + + +static void free_chunks(mrp_list_hook_t *chunks) +{ + mrp_list_hook_t *p, *n; + + if (chunks != NULL) { + mrp_list_foreach(chunks, p, n) { + mrp_list_delete(p); + + if (p != chunks) + mrp_free(p); + } + + mrp_free(chunks); + } +} diff --git a/src/common/native-types.h b/src/common/native-types.h new file mode 100644 index 0000000..ac6dacf --- /dev/null +++ b/src/common/native-types.h @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2012, 2013, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_COMMON_NATIVE_TYPES_H__ +#define __MURPHY_COMMON_NATIVE_TYPES_H__ + +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> + +#include <murphy/common/macros.h> +#include <murphy/common/list.h> + +MRP_CDECL_BEGIN + +#define MRP_INVALID_TYPE ((uint32_t)-1) + + +/** + * pre-defined native type ids + */ + +typedef enum { + MRP_TYPE_UNKNOWN = 0, + MRP_TYPE_INT8, + MRP_TYPE_UINT8, + MRP_TYPE_INT16, + MRP_TYPE_UINT16, + MRP_TYPE_INT32, + MRP_TYPE_UINT32, + MRP_TYPE_INT64, + MRP_TYPE_UINT64, + MRP_TYPE_FLOAT, + MRP_TYPE_DOUBLE, + MRP_TYPE_BOOL, + MRP_TYPE_INT, + MRP_TYPE_UINT, + MRP_TYPE_SHORT, + MRP_TYPE_USHORT, + MRP_TYPE_SIZET, + MRP_TYPE_SSIZET, + MRP_TYPE_STRING, + MRP_TYPE_BLOB, + MRP_TYPE_ARRAY, + MRP_TYPE_STRUCT, + MRP_TYPE_MAX +} mrp_type_t; + + +/** + * data type values + */ + +typedef union { + int8_t s8; + int8_t *s8p; + uint8_t u8; + uint8_t *u8p; + int16_t s16; + int16_t *s16p; + uint16_t u16; + uint16_t *u16p; + int32_t s32; + int32_t *s32p; + uint32_t u32; + uint32_t *u32p; + int64_t s64; + int64_t *s64p; + uint64_t u64; + uint64_t *u64p; + float flt; + float *fltp; + double dbl; + double *dblp; + bool bln; + bool *blnp; + void *blb; + char str[0]; + char *strp; + int i; + int *ip; + unsigned int ui; + unsigned int *uip; + short si; + short *sip; + unsigned short usi; + unsigned short *usip; + size_t sz; + size_t *szp; + ssize_t ssz; + ssize_t *sszp; + void *ptr; + void **ptrp; +} mrp_value_t; + + +/** + * type id map (for transport-specific mapping of type ids) + */ + +typedef struct { + uint32_t type_id; /* native type id */ + uint32_t mapped; /* mapped type id */ +} mrp_typemap_t; + + +/** Macro to initialize a typemap entry. */ +#define MRP_TYPEMAP(_mapped_id, _type_id) \ + { .type_id = _type_id, .mapped = _mapped_id } + +/** Macro to set a typemap termination entry. */ +#define MRP_TYPEMAP_END \ + { MRP_INVALID_TYPE, MRP_INVALID_TYPE } + +/** + * type and member descriptors + */ + +typedef enum { + MRP_LAYOUT_DEFAULT = 0, /* default, type-specific layout */ + MRP_LAYOUT_INLINED, /* inlined/embedded layout */ + MRP_LAYOUT_INDIRECT, /* indirect layout */ +} mrp_layout_t; + +#define MRP_NATIVE_COMMON_FIELDS /* fields common to all members */ \ + char *name; /* name of this member */ \ + uint32_t type; /* type id of this member */ \ + size_t offs; /* offset from base pointer */ \ + mrp_layout_t layout /* member layout */ + +typedef struct { + MRP_NATIVE_COMMON_FIELDS; /* common fields to all members */ +} mrp_native_any_t; + +typedef struct { /* a blob member */ + MRP_NATIVE_COMMON_FIELDS; /* common member fields */ + union { /* size-indicating member */ + char *name; /* name */ + uint32_t idx; /* or index */ + } size; +} mrp_native_blob_t; + +typedef enum { + MRP_ARRAY_SIZE_EXPLICIT, /* explicitly sized array */ + MRP_ARRAY_SIZE_GUARDED, /* sentinel-guarded array */ + MRP_ARRAY_SIZE_FIXED, /* a fixed size array */ +} mrp_array_size_t; + +typedef struct { + MRP_NATIVE_COMMON_FIELDS; /* common member fields */ + size_t size; /* inlined buffer size */ +} mrp_native_string_t; + +typedef struct { /* an array member */ + MRP_NATIVE_COMMON_FIELDS; /* common member fields */ + mrp_array_size_t kind; /* which kind of array */ + union { /* contained element type */ + char *name; /* name */ + uint32_t id; /* or type id */ + } elem; + union { /* size or guard member */ + char *name; /* name */ + uint32_t idx; /* or index */ + size_t nelem; /* or number of elements */ + } size; + mrp_value_t sentinel; /* sentinel value, if guarded */ +} mrp_native_array_t; + +typedef struct { /* member of type struct */ + MRP_NATIVE_COMMON_FIELDS; /* common member fields */ + union { /* struct type */ + char *name; /* name */ + uint32_t id; /* or type id */ + } data_type; +} mrp_native_struct_t; + +typedef union { + mrp_native_any_t any; + mrp_native_string_t str; + mrp_native_blob_t blob; + mrp_native_array_t array; + mrp_native_struct_t strct; +} mrp_native_member_t; + +typedef struct { + char *name; /* name of this type */ + uint32_t id; /* assigned id for this type */ + size_t size; /* size of this type */ + mrp_native_member_t *members; /* members of this type if any */ + size_t nmember; /* number of members */ + mrp_list_hook_t hook; /* to list of registered types */ +} mrp_native_type_t; + + +/** Helper macro to initialize native member fields. */ +#define __MRP_MEMBER_INIT(_objtype, _member, _type) \ + .name = #_member, \ + .type = _type, \ + .offs = MRP_OFFSET(_objtype, _member) + +/** Helper macro to declare a native member with a given type an layout. */ +#define __MRP_MEMBER(_objtype, _type, _member, _layout) \ + { \ + .any = { \ + __MRP_MEMBER_INIT(_objtype, _member, _type), \ + .layout = MRP_LAYOUT_##_layout, \ + } \ + } + +/** Declare an indirect string member of the native type. */ +#define MRP_INDIRECT_STRING(_objtype, _member, _size) \ + __MRP_MEMBER(_objtype, _member, MRP_TYPE_STRING, INDIRECT) + +/** Declare an inlined string member of the native type. */ +#define MRP_INLINED_STRING(_objtype, _member, _size) \ + { \ + .str = { \ + __MRP_MEMBER_INIT(_objtype, _member, MRP_TYPE_STRING), \ + .layout = MRP_LAYOUT_INLINED, \ + .size = _size, \ + } \ + } + +/** By default declare a string members indirect. */ +#define MRP_DEFAULT_STRING(_objtype, _member, _size) \ + __MRP_MEMBER(_objtype, MRP_TYPE_STRING, _member, INDIRECT) + +/** Declare an explicitly sized array member of the native typet. */ +#define MRP_SIZED_ARRAY(_objtype, _member, _layout, _type, _size) \ + { \ + .array = { \ + __MRP_MEMBER_INIT(_objtype, _member, MRP_TYPE_ARRAY), \ + .layout = MRP_LAYOUT_##_layout, \ + .kind = MRP_ARRAY_SIZE_EXPLICIT, \ + .elem = { .name = #_type, }, \ + .size = { .name = #_size, }, \ + } \ + } + +/** Declare a sentinel-guarded array member of the native type. */ +#define MRP_GUARDED_ARRAY(_objtype, _member, _layout, _type, _guard, \ + ...) \ + { \ + .array = { \ + __MRP_MEMBER_INIT(_objtype, _member, MRP_TYPE_ARRAY), \ + .layout = MRP_LAYOUT_##_layout, \ + .kind = MRP_ARRAY_SIZE_GUARDED, \ + .elem = { .name = #_type, }, \ + .size = { .name = #_guard, }, \ + .sentinel = { __VA_ARGS__ }, \ + } \ + } + +/** Declare a fixed array member of the native type. */ +#define MRP_FIXED_ARRAY(_objtype, _member, _layout, _type) \ + { \ + .array = { \ + __MRP_MEMBER_INIT(_objtype, _member, MRP_TYPE_ARRAY), \ + .layout = MRP_LAYOUT_##_layout, \ + .kind = MRP_ARRAY_SIZE_FIXED, \ + .elem = { .name = #_type, }, \ + .size = { \ + .nelem = MRP_ARRAY_SIZE(((_objtype *)0x0)->_member) \ + }, \ + } \ + } + +/** Declare a struct member of the native type. */ +#define MRP_STRUCT(_objtype, _member, _layout, _type) \ + { \ + .strct = { \ + __MRP_MEMBER_INIT(_objtype, _member, MRP_TYPE_STRUCT), \ + .data_type = { .name = #_type }, \ + } \ + } + +/** Macros for declaring basic members of the native type. */ +#define MRP_INT8(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_INT8 , _m, _l) +#define MRP_UINT8(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_UINT8 , _m, _l) +#define MRP_INT16(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_INT16 , _m, _l) +#define MRP_UINT16(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_UINT16, _m, _l) +#define MRP_INT32(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_INT32 , _m, _l) +#define MRP_UINT32(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_UINT32, _m, _l) +#define MRP_INT64(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_INT64 , _m, _l) +#define MRP_UINT64(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_UINT64, _m, _l) +#define MRP_FLOAT(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_FLOAT , _m, _l) +#define MRP_DOUBLE(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_DOUBLE, _m, _l) +#define MRP_BOOL(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_BOOL , _m, _l) + +#define MRP_INT(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_INT , _m, _l) +#define MRP_UINT(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_UINT , _m, _l) +#define MRP_SHORT(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_SHORT , _m, _l) +#define MRP_USHORT(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_USHORT, _m, _l) +#define MRP_SIZET(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_SIZET , _m, _l) +#define MRP_SSIZET(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_SSIZET, _m, _l) + +/** Macro for declaring string members of the native type. */ +#define MRP_STRING(_objtype, _member, _layout) \ + MRP_##_layout##_STRING(_objtype, _member, \ + sizeof(((_objtype *)0x0)->_member)) + +/** Macro for declaring array members of the native type. */ +#define MRP_ARRAY(_objtype, _member, _layout, _kind, ...) \ + MRP_##_kind##_ARRAY(_objtype, _member, _layout, __VA_ARGS__) + +/** Macro to declare a native type. */ +#define MRP_NATIVE_TYPE(_var, _type, ...) \ + mrp_native_member_t _var##_members[] = { \ + __VA_ARGS__ \ + }; \ + mrp_native_type_t _var = { \ + .id = -1, \ + .name = #_type, \ + .size = sizeof(_type), \ + .members = _var##_members, \ + .nmember = MRP_ARRAY_SIZE(_var##_members), \ + .hook = { NULL, NULL }, \ + } + +/** Declare and register the given native type. */ +uint32_t mrp_register_native(mrp_native_type_t *type); + +/** Look up the type id of the given native type name. */ +uint32_t mrp_native_id(const char *type_name); + +/** Encode data of the given native type. */ +int mrp_encode_native(void *data, uint32_t id, size_t reserve, void **bufp, + size_t *sizep, mrp_typemap_t *idmap); + +/** Decode data of (the given) native type (if specified). */ +int mrp_decode_native(void **bufp, size_t *sizep, void **datap, uint32_t *idp, + mrp_typemap_t *idmap); + +/** Free data of the given native type, obtained from mrp_decode_native. */ +void mrp_free_native(void *data, uint32_t id); + +/** Print data of the given native type. */ +ssize_t mrp_print_native(char *buf, size_t size, void *data, uint32_t id); + +MRP_CDECL_END + +#endif /* __MURPHY_COMMON_NATIVE_TYPES_H__ */ diff --git a/src/common/process.c b/src/common/process.c new file mode 100644 index 0000000..71a10e1 --- /dev/null +++ b/src/common/process.c @@ -0,0 +1,1077 @@ +/* + * Copyright (c) 2013, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <unistd.h> +#include <errno.h> +#include <sys/inotify.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <linux/cn_proc.h> +#include <linux/netlink.h> +#include <linux/connector.h> +#include <linux/filter.h> + +#include <murphy/common/process.h> +#include <murphy/common.h> + + +#define MURPHY_PROCESS_INOTIFY_DIR "/var/run/murphy/processes" + +struct mrp_pid_watch_s { + pid_t pid; +}; + +typedef struct { + char *path; /* file name token */ + pid_t pid; + char *filename; + + /* either this ... */ + mrp_process_watch_handler_t process_cb; + /* ... or this is set. */ + mrp_pid_watch_handler_t pid_cb; + + void *userdata; + mrp_io_watch_t *inotify_cb; +} i_watch_t; + +typedef struct { + mrp_pid_watch_handler_t cb; + void *user_data; + mrp_pid_watch_t *w; /* identify the client */ + + mrp_list_hook_t hook; +} nl_pid_client_t; + +typedef struct { + pid_t pid; + char pid_s[16]; /* memory for hashing */ + + mrp_list_hook_t clients; + int n_clients; + int busy : 1; + int dead : 1; +} nl_pid_watch_t; + +/* murphy pid file directory notify */ +static int dir_fd; +static int i_n_process_watches; + +/* inotify */ +static int i_fd; +static mrp_htbl_t *i_watches; +static mrp_io_watch_t *i_wd; + +/* netlink process listening */ +static int nl_sock; +static bool subscribed; +static mrp_io_watch_t *nl_wd; +static mrp_htbl_t *nl_watches; +static int nl_n_pid_watches; + +static bool id_ok(const char *id) +{ + int i, len; + /* restrict the input */ + + len = strlen(id); + + for (i = 0; i < len; i++) { + bool character, number, special; + + character = ((id[i] >= 'A' && id[i] <= 'Z') || + (id[i] >= 'a' && id[i] <= 'z')); + + number = (id[i] >= '0' && id[i] <= '9'); + + special = (id[i] == '-' || id[i] == '_'); + + if (!(character || number || special)) + return FALSE; + } + + return TRUE; +} + + +static char *path_from_id(const char *id) +{ + char buf[PATH_MAX]; + int ret; + + if (!id || !id_ok(id)) + return NULL; + + ret = snprintf(buf, PATH_MAX, "%s/%s", MURPHY_PROCESS_INOTIFY_DIR, id); + + if (ret < 0 || ret >= PATH_MAX) + return NULL; + + return mrp_strdup(buf); +} + + +static void htbl_free_nl_watch(void *key, void *object) +{ + nl_pid_watch_t *w = (nl_pid_watch_t *) object; + + MRP_UNUSED(key); + + if (!w->busy) + mrp_free(w); + else + w->dead = TRUE; +} + + +static void htbl_free_i_watch(void *key, void *object) +{ + i_watch_t *w = (i_watch_t *) object; + + MRP_UNUSED(key); + + mrp_free(w->path); + mrp_free(w->filename); + mrp_free(w); +} + + +static int initialize_dir() +{ + /* TODO: check if the directory is present; if not, create it */ + + return 0; +} + + +static void process_change(mrp_io_watch_t *wd, int fd, mrp_io_event_t events, + void *user_data) +{ + struct inotify_event *is; + int bufsize = sizeof(struct inotify_event) + PATH_MAX + 1; + char buf[bufsize]; + i_watch_t *w; + FILE *f; + + MRP_UNUSED(wd); + MRP_UNUSED(user_data); + + if (!i_watches) + return; + + if (events & MRP_IO_EVENT_IN) { + int read_bytes; + int processed_bytes = 0; + + read_bytes = read(fd, buf, bufsize - 1); + + if (read_bytes < 0) { + mrp_log_error("Failed to read event from inotify: %s", + strerror(errno)); + return; + } + + buf[read_bytes] = '\0'; + + while (processed_bytes < read_bytes) { + char *filename = NULL; + + /* the kernel doesn't allow to read incomplete events */ + is = (struct inotify_event *) (buf + processed_bytes); + + processed_bytes += sizeof(struct inotify_event) + is->len; + + if (is->len == 0) { + /* no file name */ + continue; + } + + if (is->wd != dir_fd) { + /* wrong descriptor? */ + continue; + } + + filename = path_from_id(is->name); + + if (!filename) + continue; + + w = (i_watch_t *) mrp_htbl_lookup(i_watches, filename); + + if (w) { + f = fopen(filename, "r"); + + if (f) { + fclose(f); + mrp_log_info("Received inotify event for %s, READY", w->path); + w->process_cb(w->path, MRP_PROCESS_STATE_READY, w->userdata); + } + else { + mrp_log_info("Received inotify event for %s, NOT READY", w->path); + w->process_cb(w->path, MRP_PROCESS_STATE_NOT_READY, w->userdata); + } + } + mrp_free(filename); + } + } +} + + +static int send_proc_cmd(enum proc_cn_mcast_op cmd) +{ + int data_size = sizeof(enum proc_cn_mcast_op); + + /* connector message size */ + int cn_size = sizeof(struct cn_msg); + + /* total size of bytes we need */ + int message_size = NLMSG_SPACE(cn_size + data_size); + + /* aligned size */ + int payload_size = NLMSG_LENGTH(message_size - sizeof(struct nlmsghdr)); + + /* helper pointers */ + struct nlmsghdr *nl; + struct cn_msg *cn; + + /* message data */ + char buf[message_size]; + + if (nl_sock <= 0) { + mrp_log_error("invalid netlink socket %d", nl_sock); + return -1; + } + + /* structs point to the aligned memory */ + nl = (struct nlmsghdr *) buf; + cn = (struct cn_msg *) NLMSG_DATA(nl); + + /* fill the structures */ + nl->nlmsg_len = payload_size; + nl->nlmsg_seq = 0; + nl->nlmsg_pid = getpid(); + nl->nlmsg_type = NLMSG_DONE; + nl->nlmsg_flags = 0; + + cn->len = data_size; + cn->seq = 0; + cn->ack = 0; + cn->id.idx = CN_IDX_PROC; + cn->id.val = CN_VAL_PROC; + cn->flags = 0; + + /* all this was done for this data */ + memcpy(cn->data, &cmd, data_size); + + return send(nl_sock, buf, message_size, 0); +} + + +static int subscribe_proc_events() +{ + int ret = send_proc_cmd(PROC_CN_MCAST_LISTEN); + + if (ret >= 0) + subscribed = TRUE; + + return ret; +} + + +static int unsubscribe_proc_events() +{ + int ret = send_proc_cmd(PROC_CN_MCAST_IGNORE); + + if (ret >= 0) + subscribed = FALSE; + + return ret; +} + + +static void nl_watch(mrp_io_watch_t *w, int fd, mrp_io_event_t events, + void *user_data) +{ + char buf[4096]; + struct nlmsghdr *nl; + struct sockaddr_nl addr; + unsigned int sockaddr_len; + ssize_t len; + + MRP_UNUSED(w); + MRP_UNUSED(events); + MRP_UNUSED(user_data); + + sockaddr_len = sizeof(struct sockaddr_nl); + + len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *) &addr, + &sockaddr_len); + + if (len < 0) { + mrp_log_error("failed to read data from socket"); + return; + } + + if (addr.nl_pid != 0) { + mrp_log_error("message wasn't from the kernel"); + return; + } + + /* set pointer to the first message in the buffer */ + nl = (struct nlmsghdr *) buf; + + while (NLMSG_OK(nl, (unsigned int) len)) { + struct cn_msg *cn; + + /* we are expecting a non-multipart message -- filter errors and + * others away */ + if (nl->nlmsg_type != NLMSG_DONE) { + if (nl->nlmsg_type == NLMSG_ERROR) { + /* TODO: error processing, resynchronization */ + } + nl = NLMSG_NEXT(nl, len); + continue; + } + + cn = (struct cn_msg *) NLMSG_DATA(nl); + + if (cn->id.idx == CN_IDX_PROC && cn->id.val == CN_VAL_PROC) { + struct proc_event *ev = (struct proc_event *) cn->data; + + switch (ev->what) { + case PROC_EVENT_EXIT: + { + mrp_list_hook_t *p, *n; + nl_pid_watch_t *nl_w; + char pid_s[16]; + int ret; + + mrp_log_info("process %d exited", + ev->event_data.exit.process_pid); + + ret = snprintf(pid_s, sizeof(pid_s), "%u", + (unsigned int) ev->event_data.exit.process_pid); + + if (ret < 0 || ret >= (int) sizeof(pid_s)) + break; + + /* check the pid */ + nl_w = (nl_pid_watch_t *) mrp_htbl_lookup(nl_watches, pid_s); + + if (!nl_w) { + mrp_log_error("pid %s exited but no-one was following it", pid_s); + break; + } + + nl_w->busy = TRUE; + mrp_list_foreach(&nl_w->clients, p, n) { + nl_pid_client_t *client; + + client = mrp_list_entry(p, typeof(*client), hook); + client->cb(nl_w->pid, MRP_PROCESS_STATE_NOT_READY, + client->user_data); + } + if (nl_w->dead) + mrp_free(nl_w); + else + nl_w->busy = FALSE; + + /* TODO: should we automatically free the wathces? Or let + * client do that to preserver symmetricity? */ + break; + } + default: + /* the filter isn't working for some reason */ + mrp_log_error("some other message!\n"); + break; + } + } + + nl = NLMSG_NEXT(nl, len); + } +} + + +static int initialize(mrp_mainloop_t *ml, bool process, bool pid) +{ + if (process) { + + if (initialize_dir() < 0) + goto error; + + if (i_fd <= 0) { + i_fd = inotify_init(); + + if (i_fd <= 0) + goto error; + } + + if (dir_fd <= 0) { + + dir_fd = inotify_add_watch(i_fd, MURPHY_PROCESS_INOTIFY_DIR, + IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_MODIFY); + + if (dir_fd < 0) + goto error; + } + + if (!i_wd) { + i_wd = mrp_add_io_watch(ml, i_fd, MRP_IO_EVENT_IN, process_change, NULL); + + if (!i_wd) + goto error; + } + + if (!i_watches) { + mrp_htbl_config_t watches_conf; + + watches_conf.comp = mrp_string_comp; + watches_conf.hash = mrp_string_hash; + watches_conf.free = htbl_free_i_watch; + watches_conf.nbucket = 0; + watches_conf.nentry = 10; + + i_watches = mrp_htbl_create(&watches_conf); + + if (!i_watches) + goto error; + } + } + + if (pid) { + if (nl_sock <= 0) { + struct sockaddr_nl nl_addr; + int nl_options = SOCK_NONBLOCK | SOCK_DGRAM | SOCK_CLOEXEC; + struct sock_filter block[] = { + BPF_STMT(BPF_RET | BPF_K, 0x0), + }; + struct sock_fprog fp; + + /* socket creation */ + + nl_sock = socket(PF_NETLINK, nl_options, NETLINK_CONNECTOR); + + if (nl_sock <= 0) + goto error; + + memset(&nl_addr, 0, sizeof(struct sockaddr_nl)); + memset(&fp, 0, sizeof(struct sock_fprog)); + + /* bind the socket to the address */ + + nl_addr.nl_pid = getpid(); + nl_addr.nl_family = AF_NETLINK; + nl_addr.nl_groups = CN_IDX_PROC; + + if (bind(nl_sock, (struct sockaddr *) &nl_addr, + sizeof(struct sockaddr_nl)) < 0) + goto error; + + fp.filter = block; + fp.len = 1; + + /* set socket filter that blocks everything */ + if (setsockopt(nl_sock, SOL_SOCKET, SO_ATTACH_FILTER, &fp, + sizeof(struct sock_fprog)) < 0) { + mrp_log_error("setting blocking socket filter failed: %s", + strerror(errno)); + goto error; + } + + nl_wd = mrp_add_io_watch(ml, nl_sock, MRP_IO_EVENT_IN, nl_watch, NULL); + } + + if (!nl_watches) { + mrp_htbl_config_t watches_conf; + + watches_conf.comp = mrp_string_comp; + watches_conf.hash = mrp_string_hash; + watches_conf.free = htbl_free_nl_watch; + watches_conf.nbucket = 0; + watches_conf.nentry = 10; + + nl_watches = mrp_htbl_create(&watches_conf); + + if (!nl_watches) + goto error; + } + } + + return 0; + +error: + mrp_log_error("initialization error"); + + if (process) { + + if (i_watches) { + mrp_htbl_destroy(i_watches, FALSE); + i_watches = NULL; + } + + if (i_wd) { + mrp_del_io_watch(i_wd); + i_wd = NULL; + } + + if (i_fd && dir_fd) { + inotify_rm_watch(i_fd, dir_fd); + dir_fd = -1; + } + + i_n_process_watches = 0; + } + + if (pid) { + + if (nl_sock > 0) { + close(nl_sock); + nl_sock = -1; + } + + nl_n_pid_watches = 0; + } + + return -1; +} + + +static int initialize_process(mrp_mainloop_t *ml) +{ + return initialize(ml, TRUE, FALSE); +} + + +static int initialize_pid(mrp_mainloop_t *ml) +{ + return initialize(ml, FALSE, TRUE); +} + + +int mrp_process_set_state(const char *id, mrp_process_state_t state) +{ + char *path = NULL; + FILE *f; + int ret = -1; + + if (initialize_dir() < 0) + goto end; + + path = path_from_id(id); + + if (!path) + goto end; + + switch (state) { + case MRP_PROCESS_STATE_UNKNOWN: + case MRP_PROCESS_STATE_NOT_READY: + if (unlink(path) < 0) { + if (errno != ENOENT) { + goto end; + } + } + break; + case MRP_PROCESS_STATE_READY: + f = fopen(path, "w"); + if (!f) + goto end; + + fclose(f); + break; + } + + ret = 0; + +end: + mrp_free(path); + return ret; +} + + +static void filter_add_pid(struct sock_filter *p, pid_t pid, int proc_offset) +{ + int proc_event_data_offset = proc_offset + + offsetof(struct proc_event, event_data); + /* event_data is an union, leaving out */ + int proc_event_data_exit_pid_offset = proc_event_data_offset + + offsetof(struct exit_proc_event, process_pid); + + struct sock_filter bpf[] = { + /* load the pid value ... */ + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, proc_event_data_exit_pid_offset), + + /* ... if it is the pid we're comparing it to ... */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(pid), 0, 1), + + /* ... return success immediately ... */ + BPF_STMT(BPF_RET | BPF_K, 0xffffffff), + + /* ... otherwise proceed to the next comparison or exit. */ + }; + + mrp_debug("adding pid %d to filter\n", pid); + + memcpy(p, bpf, 3*sizeof(struct sock_filter)); +} + + +static int filter_update(pid_t pids[], int len_pids) +{ + int nl_type_offset = offsetof(struct nlmsghdr, nlmsg_type); + int cn_offset = NLMSG_LENGTH(0); + int cn_id_offset = cn_offset + offsetof(struct cn_msg, id); + int cn_idx_offset = cn_id_offset + offsetof(struct cb_id, idx); + int cn_val_offset = cn_id_offset + offsetof(struct cb_id, val); + int proc_offset = cn_offset + offsetof(struct cn_msg, data); + int proc_what_offset = proc_offset + offsetof(struct proc_event, what); + + struct sock_fprog fp; + struct sock_filter *bpf, *iter; + + struct sock_filter bpf_header[] = { + + /* check that the message has only one part or is an error + * ( NLMSG_DONE || NLMSG_ERROR) -- return error immediately */ + + /* load the message type */ + BPF_STMT(BPF_LD | BPF_H | BPF_ABS, nl_type_offset), + + /* check the error type */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htons(NLMSG_ERROR), 0, 1), + + /* return if error */ + BPF_STMT(BPF_RET | BPF_K, 0xffffffff), + + /* check the done type */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htons(NLMSG_DONE), 1, 0), + + /* filter the packet if not NLMSG_DONE */ + BPF_STMT(BPF_RET | BPF_K, 0x0), + + /* check that the message is from the proc connector + * ( CN_IDX_PROC && CN_VAL_PROC ) */ + + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, cn_idx_offset), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(CN_IDX_PROC), 1, 0), + BPF_STMT(BPF_RET | BPF_K, 0x0), + + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, cn_val_offset), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(CN_VAL_PROC), 1, 0), + BPF_STMT(BPF_RET | BPF_K, 0x0), + + /* check that the message is a PROC_EVENT_EXIT message */ + + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, proc_what_offset), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(PROC_EVENT_EXIT), 1, 0), + BPF_STMT(BPF_RET | BPF_K, 0x0), + }; + + struct sock_filter bpf_footer[] = { + /* if there was no pid match then filter the packet */ + BPF_STMT (BPF_RET | BPF_K, 0x0), + }; + + int len_bpf_header = sizeof(bpf_header); + int len_bpf_footer = sizeof(bpf_footer); + /* three statements */ + int len_bpf_pids = sizeof(struct sock_filter) * len_pids * 3; + int len = len_bpf_header + len_bpf_pids + len_bpf_footer; + int i; + + if (nl_sock <= 0) { + mrp_log_error("invalid netlink socket %d", nl_sock); + goto error; + } + + /* build the filter */ + + bpf = (struct sock_filter *) mrp_allocz(len); + + if (!bpf) + goto error; + + iter = bpf; + + memcpy(iter, bpf_header, len_bpf_header); + + iter = iter + (len_bpf_header / sizeof(struct sock_filter)); + + /* check that the PID is one that we are following */ + for (i = 0; i < len_pids; i++, iter=iter+3) { + filter_add_pid(iter, pids[i], proc_offset); + } + + memcpy(iter, bpf_footer, len_bpf_footer); + + memset(&fp, 0, sizeof(struct sock_fprog)); + fp.filter = bpf; + fp.len = len / sizeof(struct sock_filter); + + if (setsockopt(nl_sock, SOL_SOCKET, SO_ATTACH_FILTER, &fp, + sizeof(struct sock_fprog)) < 0) + mrp_log_error("setting socket filter failed: %s", strerror(errno)); + + mrp_free(bpf); + +error: + return -1; +} + + +struct key_data_s { + int index; + pid_t *pids; +}; + + +static int gather_pids_cb(void *key, void *object, void *user_data) +{ + struct key_data_s *kd = (struct key_data_s *) user_data; + nl_pid_watch_t *w = (nl_pid_watch_t *) object; + + MRP_UNUSED(key); + + kd->pids[kd->index] = w->pid; + kd->index++; + + return MRP_HTBL_ITER_MORE; +} + + +static int pid_filter_update() +{ + pid_t pids[nl_n_pid_watches]; + + struct key_data_s kd; + + kd.index = 0; + kd.pids = pids; + + mrp_htbl_foreach(nl_watches, gather_pids_cb, &kd); + + return filter_update(pids, kd.index); +} + + +mrp_process_state_t mrp_process_query_state(const char *id) +{ + char *path; + FILE *f; + + if (initialize_dir() < 0) + return MRP_PROCESS_STATE_UNKNOWN; + + path = path_from_id(id); + + if (!path) + return MRP_PROCESS_STATE_UNKNOWN; + + f = fopen(path, "r"); + + mrp_free(path); + + if (f) { + fclose(f); + return MRP_PROCESS_STATE_READY; + } + + return MRP_PROCESS_STATE_NOT_READY; +} + + +mrp_process_state_t mrp_pid_query_state(pid_t pid) +{ + char path[64]; + struct stat s; + mrp_process_state_t state = MRP_PROCESS_STATE_UNKNOWN; + int ret; + + ret = snprintf(path, sizeof(path), "/proc/%u", (unsigned int) pid); + + if (ret < 0 || ret >= (int) sizeof(path)) + goto end; + + ret = stat(path, &s); + + if (ret < 0 && (errno == ENOENT || errno == ENOTDIR)) { + state = MRP_PROCESS_STATE_NOT_READY; + } + else if (ret == 0 && S_ISDIR(s.st_mode)) { + state = MRP_PROCESS_STATE_READY; + } + +end: + return state; +} + + +int mrp_process_set_watch(const char *id, mrp_mainloop_t *ml, + mrp_process_watch_handler_t cb, void *userdata) +{ + i_watch_t *w = NULL; + + if (initialize_process(ml) < 0) + goto error; + + w = (i_watch_t *) mrp_allocz(sizeof(i_watch_t)); + + if (!w) + goto error; + + w->inotify_cb = i_wd; + w->userdata = userdata; + w->process_cb = cb; + + w->path = mrp_strdup(id); + if (!w->path) + goto error; + + w->filename = path_from_id(id); + if (!w->filename) + goto error; + + if (mrp_htbl_insert(i_watches, w->filename, w) < 0) + goto error; + + i_n_process_watches++; + + return 0; + +error: + if (w) { + mrp_free(w->path); + mrp_free(w->filename); + mrp_free(w); + } + + return -1; +} + + +mrp_pid_watch_t *mrp_pid_set_watch(pid_t pid, mrp_mainloop_t *ml, + mrp_pid_watch_handler_t cb, void *userdata) +{ + nl_pid_watch_t *nl_w = NULL; + nl_pid_client_t *client = NULL; + char pid_s[16]; + bool already_inserted = TRUE; + int ret; + + if (initialize_pid(ml) < 0) + goto error; + + ret = snprintf(pid_s, sizeof(pid_s), "%u", (unsigned int) pid); + + if (ret < 0 || ret >= (int) sizeof(pid_s)) + goto error; + + nl_w = (nl_pid_watch_t *) mrp_htbl_lookup(nl_watches, pid_s); + + if (!nl_w) { + + nl_w = (nl_pid_watch_t *) mrp_allocz(sizeof(nl_pid_watch_t)); + + if (!nl_w) + goto error; + + mrp_list_init(&nl_w->clients); + nl_w->pid = pid; + memcpy(nl_w->pid_s, pid_s, sizeof(nl_w->pid_s)); + + already_inserted = FALSE; + } + + client = (nl_pid_client_t *) mrp_allocz(sizeof(nl_pid_client_t)); + + if (!client) { + if (!already_inserted) + mrp_free(nl_w); + goto error; + } + + client->cb = cb; + client->user_data = userdata; + client->w = (mrp_pid_watch_t *) mrp_allocz(sizeof(mrp_pid_watch_t)); + + if (!client->w) { + mrp_free(nl_w); + goto error; + } + + client->w->pid = pid; + + mrp_list_init(&client->hook); + mrp_list_append(&nl_w->clients, &client->hook); + nl_w->n_clients++; + + if (!already_inserted) { + if (mrp_htbl_insert(nl_watches, nl_w->pid_s, nl_w) < 0) { + mrp_list_delete(&client->hook); + mrp_free(nl_w); + goto error; + } + + nl_n_pid_watches++; + } + + pid_filter_update(); + + if (!subscribed) + subscribe_proc_events(); + + /* check that the pid is still there -- return error if not */ + + if (mrp_pid_query_state(pid) != MRP_PROCESS_STATE_READY) + goto error_process; + + return client->w; + +error_process: + mrp_pid_remove_watch(client->w); + client = NULL; + +error: + if (client) { + mrp_free(client); + mrp_free(client->w); + } + + return NULL; +} + + +static void update_map() +{ + if (i_n_process_watches <= 0) { + if (i_fd > 0 && dir_fd > 0) { + inotify_rm_watch(i_fd, dir_fd); + dir_fd = -1; + } + i_n_process_watches = 0; + } + + if ((i_n_process_watches) == 0) { + mrp_htbl_destroy(i_watches, TRUE); + i_watches = NULL; + } +} + + +int mrp_process_remove_watch(const char *id) +{ + i_watch_t *w; + char *filename; + + if (!i_watches) + return -1; + + filename = path_from_id(id); + + w = (i_watch_t *) mrp_htbl_lookup(i_watches, (void *)filename); + + if (!w) { + mrp_free(filename); + return -1; + } + + mrp_htbl_remove(i_watches, (void *) filename, TRUE); + i_n_process_watches--; + + update_map(); + + mrp_free(filename); + return 0; +} + + +int mrp_pid_remove_watch(mrp_pid_watch_t *w) +{ + nl_pid_watch_t *nl_w = NULL; + nl_pid_client_t *client = NULL; + char pid_s[16]; + mrp_list_hook_t *p, *n; + bool found = FALSE; + int ret; + + if (!w) + goto error; + + ret = snprintf(pid_s, sizeof(pid_s), "%u", (unsigned int) w->pid); + + if (ret < 0 || ret >= (int) sizeof(pid_s)) + goto error; + + nl_w = (nl_pid_watch_t *) mrp_htbl_lookup(nl_watches, pid_s); + + if (!nl_w) { + mrp_log_error("no corresponding pid watch found"); + goto error; + } + + mrp_list_foreach(&nl_w->clients, p, n) { + client = mrp_list_entry(p, typeof(*client), hook); + if (client->w == w) { + found = TRUE; + break; + } + } + + if (!found) { + mrp_log_error("not registered to watch the pid"); + goto error; + } + + mrp_list_delete(&client->hook); + mrp_free(client); + nl_w->n_clients--; + + if (nl_w->n_clients == 0) { + /* no-one is interested in this pid anymore */ + mrp_htbl_remove(nl_watches, pid_s, TRUE); + nl_n_pid_watches--; + + pid_filter_update(); + + if (nl_n_pid_watches == 0) { + /* no-one is following pids anymore */ + if (subscribed) + unsubscribe_proc_events(); + } + } + + mrp_free(w); + return 0; + +error: + mrp_free(w); + return -1; +} diff --git a/src/common/process.h b/src/common/process.h new file mode 100644 index 0000000..956a367 --- /dev/null +++ b/src/common/process.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2013, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_PROCESS_H__ +#define __MURPHY_PROCESS_H__ + +#include <murphy/common/macros.h> +#include <murphy/common/mainloop.h> + +MRP_CDECL_BEGIN + +/* functions for the murphy family of processes */ + +typedef enum { + MRP_PROCESS_STATE_UNKNOWN, + MRP_PROCESS_STATE_READY, + MRP_PROCESS_STATE_NOT_READY +} mrp_process_state_t; + +typedef void (*mrp_process_watch_handler_t)(const char *, + mrp_process_state_t, void *); + +int mrp_process_set_state(const char *id, mrp_process_state_t state); + +mrp_process_state_t mrp_process_query_state(const char *id); + +int mrp_process_set_watch(const char *id, mrp_mainloop_t *ml, + mrp_process_watch_handler_t cb, void *userdata); + +int mrp_process_remove_watch(const char *id); + +/* functions to track external processes by pid */ + +typedef struct mrp_pid_watch_s mrp_pid_watch_t; + +typedef void (*mrp_pid_watch_handler_t)(pid_t, + mrp_process_state_t, void *); + +mrp_process_state_t mrp_pid_query_state(pid_t pid); + +mrp_pid_watch_t *mrp_pid_set_watch(pid_t pid, mrp_mainloop_t *ml, + mrp_pid_watch_handler_t cb, void *userdata); + +int mrp_pid_remove_watch(mrp_pid_watch_t *w); + + +MRP_CDECL_END + +#endif /* __MURPHY_PROCESS_H__ */ diff --git a/src/common/pulse-glue.c b/src/common/pulse-glue.c new file mode 100644 index 0000000..98dbf24 --- /dev/null +++ b/src/common/pulse-glue.c @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#include <pulse/mainloop-api.h> +#include <pulse/timeval.h> + +#include <murphy/common/mm.h> +#include <murphy/common/mainloop.h> + +#include <murphy/common/pulse-glue.h> + + +typedef struct { + pa_mainloop_api *pa; +} pulse_glue_t; + + +typedef struct { + pa_io_event *pa_io; + void (*cb)(void *glue_data, + void *id, int fd, mrp_io_event_t events, + void *user_data); + void *user_data; + void *glue_data; +} io_t; + + +typedef struct { + pa_time_event *pa_t; + void (*cb)(void *glue_data, void *id, void *user_data); + void *user_data; + void *glue_data; +} tmr_t; + + +typedef struct { + pa_defer_event *pa_d; + void (*cb)(void *glue_data, void *id, void *user_data); + void *user_data; + void *glue_data; +} dfr_t; + + +static void *add_io(void *glue_data, int fd, mrp_io_event_t events, + void (*cb)(void *glue_data, void *id, int fd, + mrp_io_event_t events, void *user_data), + void *user_data); +static void del_io(void *glue_data, void *id); + +static void *add_timer(void *glue_data, unsigned int msecs, + void (*cb)(void *glue_data, void *id, void *user_data), + void *user_data); +static void del_timer(void *glue_data, void *id); +static void mod_timer(void *glue_data, void *id, unsigned int msecs); + +static void *add_defer(void *glue_data, + void (*cb)(void *glue_data, void *id, void *user_data), + void *user_data); +static void del_defer(void *glue_data, void *id); +static void mod_defer(void *glue_data, void *id, int enabled); + + +static void io_cb(pa_mainloop_api *pa, pa_io_event *e, int fd, + pa_io_event_flags_t mask, void *user_data) +{ + io_t *io = (io_t *)user_data; + mrp_io_event_t events = MRP_IO_EVENT_NONE; + + MRP_UNUSED(pa); + MRP_UNUSED(e); + + if (mask & PA_IO_EVENT_INPUT) events |= MRP_IO_EVENT_IN; + if (mask & PA_IO_EVENT_OUTPUT) events |= MRP_IO_EVENT_OUT; + if (mask & PA_IO_EVENT_HANGUP) events |= MRP_IO_EVENT_HUP; + if (mask & PA_IO_EVENT_ERROR) events |= MRP_IO_EVENT_ERR; + + io->cb(io->glue_data, io, fd, events, io->user_data); +} + + +static void *add_io(void *glue_data, int fd, mrp_io_event_t events, + void (*cb)(void *glue_data, void *id, int fd, + mrp_io_event_t events, void *user_data), + void *user_data) +{ + pulse_glue_t *glue = (pulse_glue_t *)glue_data; + pa_mainloop_api *pa = glue->pa; + pa_io_event_flags_t mask = PA_IO_EVENT_NULL; + io_t *io; + + io = mrp_allocz(sizeof(*io)); + + if (io != NULL) { + if (events & MRP_IO_EVENT_IN) mask |= PA_IO_EVENT_INPUT; + if (events & MRP_IO_EVENT_OUT) mask |= PA_IO_EVENT_OUTPUT; + if (events & MRP_IO_EVENT_HUP) mask |= PA_IO_EVENT_HANGUP; + if (events & MRP_IO_EVENT_ERR) mask |= PA_IO_EVENT_ERROR; + + io->pa_io = pa->io_new(pa, fd, mask, io_cb, io); + + if (io->pa_io != NULL) { + io->cb = cb; + io->user_data = user_data; + io->glue_data = glue_data; + + return io; + } + else + mrp_free(io); + } + + return NULL; +} + + +static void del_io(void *glue_data, void *id) +{ + pulse_glue_t *glue = (pulse_glue_t *)glue_data; + pa_mainloop_api *pa = glue->pa; + io_t *io = (io_t *)id; + + pa->io_free(io->pa_io); + mrp_free(io); +} + + +static void timer_cb(pa_mainloop_api *pa, pa_time_event *e, + const struct timeval *tv, void *user_data) +{ + tmr_t *t = (tmr_t *)user_data; + + MRP_UNUSED(pa); + MRP_UNUSED(e); + MRP_UNUSED(tv); + + t->cb(t->glue_data, t, t->user_data); +} + + +static void *add_timer(void *glue_data, unsigned int msecs, + void (*cb)(void *glue_data, void *id, void *user_data), + void *user_data) +{ + pulse_glue_t *glue = (pulse_glue_t *)glue_data; + pa_mainloop_api *pa = glue->pa; + struct timeval tv; + tmr_t *t; + + t = mrp_allocz(sizeof(*t)); + + if (t != NULL) { + pa_gettimeofday(&tv); + + tv.tv_sec += msecs / 1000; + tv.tv_usec += 1000 * (msecs % 1000); + + t->pa_t = pa->time_new(pa, &tv, timer_cb, t); + + if (t->pa_t != NULL) { + t->cb = cb; + t->user_data = user_data; + t->glue_data = glue_data; + + return t; + } + else + mrp_free(t); + } + + return NULL; +} + + +static void del_timer(void *glue_data, void *id) +{ + pulse_glue_t *glue = (pulse_glue_t *)glue_data; + pa_mainloop_api *pa = glue->pa; + tmr_t *t = (tmr_t *)id; + + pa->time_free(t->pa_t); + mrp_free(t); +} + + +static void mod_timer(void *glue_data, void *id, unsigned int msecs) +{ + pulse_glue_t *glue = (pulse_glue_t *)glue_data; + pa_mainloop_api *pa = glue->pa; + tmr_t *t = (tmr_t *)id; + struct timeval tv; + + if (t != NULL) { + pa_gettimeofday(&tv); + + tv.tv_sec += msecs / 1000; + tv.tv_usec += 1000 * (msecs % 1000); + + pa->time_restart(t->pa_t, &tv); + } +} + + +static void defer_cb(pa_mainloop_api *pa, pa_defer_event *e, void *user_data) +{ + dfr_t *d = (dfr_t *)user_data; + + MRP_UNUSED(pa); + MRP_UNUSED(e); + + d->cb(d->glue_data, d, d->user_data); +} + + +static void *add_defer(void *glue_data, + void (*cb)(void *glue_data, void *id, void *user_data), + void *user_data) +{ + pulse_glue_t *glue = (pulse_glue_t *)glue_data; + pa_mainloop_api *pa = glue->pa; + dfr_t *d; + + d = mrp_allocz(sizeof(*d)); + + if (d != NULL) { + d->pa_d = pa->defer_new(pa, defer_cb, d); + + if (d->pa_d != NULL) { + d->cb = cb; + d->user_data = user_data; + d->glue_data = glue_data; + + return d; + } + else + mrp_free(d); + } + + return NULL; +} + + +static void del_defer(void *glue_data, void *id) +{ + pulse_glue_t *glue = (pulse_glue_t *)glue_data; + pa_mainloop_api *pa = glue->pa; + dfr_t *d = (dfr_t *)id; + + pa->defer_free(d->pa_d); + mrp_free(d); +} + + +static void mod_defer(void *glue_data, void *id, int enabled) +{ + pulse_glue_t *glue = (pulse_glue_t *)glue_data; + pa_mainloop_api *pa = glue->pa; + dfr_t *d = (dfr_t *)id; + + pa->defer_enable(d->pa_d, !!enabled); +} + + +static void unregister(void *data) +{ + pulse_glue_t *glue = (pulse_glue_t *)data; + + mrp_free(glue); +} + + +static mrp_superloop_ops_t pa_ops = { + .add_io = add_io, + .del_io = del_io, + .add_timer = add_timer, + .del_timer = del_timer, + .mod_timer = mod_timer, + .add_defer = add_defer, + .del_defer = del_defer, + .mod_defer = mod_defer, + .unregister = unregister, +}; + + +int mrp_mainloop_register_with_pulse(mrp_mainloop_t *ml, pa_mainloop_api *pa) +{ + pulse_glue_t *glue; + + glue = mrp_allocz(sizeof(*glue)); + + if (glue != NULL) { + glue->pa = pa; + + if (mrp_set_superloop(ml, &pa_ops, glue)) + return TRUE; + else + mrp_free(glue); + } + + return FALSE; +} + + +int mrp_mainloop_unregister_from_pulse(mrp_mainloop_t *ml) +{ + return mrp_mainloop_unregister(ml); +} + + + +static mrp_mainloop_t *pulse_ml; + +mrp_mainloop_t *mrp_mainloop_pulse_get(pa_mainloop_api *pa) +{ + if (pulse_ml == NULL) { + pulse_ml = mrp_mainloop_create(); + + if (pulse_ml != NULL) { + if (!mrp_mainloop_register_with_pulse(pulse_ml, pa)) { + mrp_mainloop_destroy(pulse_ml); + pulse_ml = NULL; + } + } + } + + return pulse_ml; +} diff --git a/src/common/pulse-glue.h b/src/common/pulse-glue.h new file mode 100644 index 0000000..24fa3e8 --- /dev/null +++ b/src/common/pulse-glue.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_PULSE_H__ +#define __MURPHY_PULSE_H__ + +#include <murphy/common/mainloop.h> +#include <pulse/mainloop.h> + +/** Register the given murphy mainloop with the given pulse mainloop. */ +int mrp_mainloop_register_with_pulse(mrp_mainloop_t *ml, pa_mainloop_api *pa); + +/** Unrgister the given murphy mainloop from the given pulse mainloop. */ +int mrp_mainloop_unregister_from_pulse(mrp_mainloop_t *ml); + +/** Create a murphy mainloop and set it up with the given pulse mainloop. */ +mrp_mainloop_t *mrp_mainloop_pulse_get(pa_mainloop_api *pa); + +#endif /* __MURPHY_PULSE_H__ */ diff --git a/src/common/pulse-subloop.c b/src/common/pulse-subloop.c new file mode 100644 index 0000000..7e08b6c --- /dev/null +++ b/src/common/pulse-subloop.c @@ -0,0 +1,572 @@ +/* + * Copyright (c) 2014, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdbool.h> +#include <sys/time.h> + +#include <murphy/common/debug.h> +#include <murphy/common/mm.h> +#include <murphy/common/list.h> +#include <murphy/common/pulse-subloop.h> + + +struct pa_murphy_mainloop { + mrp_mainloop_t *ml; + pa_mainloop_api api; + mrp_list_hook_t io_events; + mrp_list_hook_t time_events; + mrp_list_hook_t defer_events; + mrp_list_hook_t io_dead; + mrp_list_hook_t time_dead; + mrp_list_hook_t defer_dead; +}; + + +struct pa_io_event { + pa_murphy_mainloop *m; + int fd; + mrp_io_watch_t *w; + pa_io_event_cb_t cb; + pa_io_event_destroy_cb_t destroy; + void *userdata; + mrp_list_hook_t hook; + int busy : 1; + int dead : 1; +}; + + +struct pa_time_event { + pa_murphy_mainloop *m; + mrp_timer_t *t; + struct timeval tv; + pa_time_event_cb_t cb; + pa_time_event_destroy_cb_t destroy; + void *userdata; + mrp_list_hook_t hook; + int busy : 1; + int dead : 1; +}; + + +struct pa_defer_event { + pa_murphy_mainloop *m; + mrp_deferred_t *d; + pa_defer_event_cb_t cb; + pa_defer_event_destroy_cb_t destroy; + void *userdata; + mrp_list_hook_t hook; + int busy : 1; + int dead : 1; +}; + + +pa_murphy_mainloop *pa_murphy_mainloop_new(mrp_mainloop_t *ml) +{ + pa_murphy_mainloop *m; + + if (ml == NULL) + return NULL; + + m = mrp_allocz(sizeof(*m)); + + if (m == NULL) + return NULL; + + m->ml = ml; + mrp_list_init(&m->io_events); + mrp_list_init(&m->time_events); + mrp_list_init(&m->defer_events); + mrp_list_init(&m->io_dead); + mrp_list_init(&m->time_dead); + mrp_list_init(&m->defer_dead); + + return m; +} + + +static void cleanup_io_events(pa_murphy_mainloop *m) +{ + mrp_list_hook_t *p, *n; + pa_io_event *io; + + mrp_list_foreach(&m->io_events, p, n) { + io = mrp_list_entry(p, typeof(*io), hook); + + mrp_list_delete(&io->hook); + mrp_del_io_watch(io->w); + io->w = NULL; + + if (io->destroy != NULL) { + io->dead = true; + io->destroy(&io->m->api, io, io->userdata); + } + + mrp_free(io); + } + + mrp_list_foreach(&m->io_dead, p, n) { + io = mrp_list_entry(p, typeof(*io), hook); + mrp_list_delete(&io->hook); + mrp_free(io); + } +} + + +static void cleanup_time_events(pa_murphy_mainloop *m) +{ + mrp_list_hook_t *p, *n; + pa_time_event *t; + + mrp_list_foreach(&m->time_events, p, n) { + t = mrp_list_entry(p, typeof(*t), hook); + + mrp_list_delete(&t->hook); + mrp_del_timer(t->t); + t->t = NULL; + + if (t->destroy != NULL) { + t->dead = true; + t->destroy(&t->m->api, t, t->userdata); + } + + mrp_free(t); + } + + mrp_list_foreach(&m->time_dead, p, n) { + t = mrp_list_entry(p, typeof(*t), hook); + mrp_list_delete(&t->hook); + mrp_free(t); + } +} + + +static void cleanup_defer_events(pa_murphy_mainloop *m) +{ + mrp_list_hook_t *p, *n; + pa_defer_event *d; + + mrp_list_foreach(&m->defer_events, p, n) { + d = mrp_list_entry(p, typeof(*d), hook); + + mrp_list_delete(&d->hook); + mrp_del_deferred(d->d); + d->d = NULL; + + if (d->destroy != NULL) { + d->dead = true; + d->destroy(&d->m->api, d, d->userdata); + } + + mrp_free(d); + } + + mrp_list_foreach(&m->defer_dead, p, n) { + d = mrp_list_entry(p, typeof(*d), hook); + mrp_list_delete(&d->hook); + mrp_free(d); + } +} + + +void pa_murphy_mainloop_free(pa_murphy_mainloop *m) +{ + if (m == NULL) + return; + + cleanup_io_events(m); + cleanup_time_events(m); + cleanup_defer_events(m); +} + + +static void io_event_cb(mrp_io_watch_t *w, int fd, mrp_io_event_t events, + void *userdata) +{ + pa_io_event *io = (pa_io_event *)userdata; + pa_io_event_flags_t flags = 0; + + MRP_UNUSED(w); + + mrp_debug("PA I/O event 0x%x for watch %p (fd %d)", events, io, fd); + + if (events & MRP_IO_EVENT_IN) flags |= PA_IO_EVENT_INPUT; + if (events & MRP_IO_EVENT_OUT) flags |= PA_IO_EVENT_OUTPUT; + if (events & MRP_IO_EVENT_HUP) flags |= PA_IO_EVENT_HANGUP; + if (events & MRP_IO_EVENT_ERR) flags |= PA_IO_EVENT_ERROR; + + io->busy = true; + io->cb(&io->m->api, io, fd, flags, io->userdata); + io->busy = false; + + if (io->dead) { + mrp_list_delete(&io->hook); + mrp_free(io); + } +} + + +static pa_io_event *io_new(pa_mainloop_api *api, int fd, pa_io_event_flags_t e, + pa_io_event_cb_t cb, void *userdata) +{ + pa_murphy_mainloop *m = (pa_murphy_mainloop *)api->userdata; + mrp_io_event_t events = 0; + pa_io_event *io; + + mrp_debug("PA create I/O watch for fd %d, events 0x%x", fd, e); + + io = mrp_allocz(sizeof(*io)); + + if (io == NULL) + return NULL; + + mrp_list_init(&io->hook); + + if (e & PA_IO_EVENT_INPUT) events |= MRP_IO_EVENT_IN; + if (e & PA_IO_EVENT_OUTPUT) events |= MRP_IO_EVENT_OUT; + if (e & PA_IO_EVENT_HANGUP) events |= MRP_IO_EVENT_HUP; /* RDHUP ? */ + if (e & PA_IO_EVENT_ERROR) events |= MRP_IO_EVENT_ERR; + + io->m = m; + io->fd = fd; + io->cb = cb; + io->userdata = userdata; + io->w = mrp_add_io_watch(m->ml, fd, events, io_event_cb, io); + + if (io->w != NULL) + mrp_list_append(&m->io_events, &io->hook); + else { + mrp_free(io); + io = NULL; + } + + return io; +} + + +static void io_enable(pa_io_event *io, pa_io_event_flags_t e) +{ + pa_murphy_mainloop *m = io->m; + mrp_io_event_t events = 0; + + mrp_debug("PA enable events 0x%x for I/O watch %p (fd %d)", e, io, io->fd); + + mrp_del_io_watch(io->w); + io->w = NULL; + + if (e & PA_IO_EVENT_INPUT) events |= MRP_IO_EVENT_IN; + if (e & PA_IO_EVENT_OUTPUT) events |= MRP_IO_EVENT_OUT; + if (e & PA_IO_EVENT_HANGUP) events |= MRP_IO_EVENT_HUP; /* RDHUP ? */ + if (e & PA_IO_EVENT_ERROR) events |= MRP_IO_EVENT_ERR; + + io->w = mrp_add_io_watch(m->ml, io->fd, events, io_event_cb, io); +} + + +static void io_free(pa_io_event *io) +{ + pa_murphy_mainloop *m = io->m; + + mrp_debug("PA free I/O watch %p (fd %d)", io, io->fd); + + mrp_list_delete(&io->hook); + mrp_del_io_watch(io->w); + io->w = NULL; + + io->dead = true; + + if (!io->busy && !io->dead) { + io->busy = true; + if (io->destroy != NULL) + io->destroy(&io->m->api, io, io->userdata); + mrp_free(io); + } + else + mrp_list_append(&m->io_dead, &io->hook); +} + + +static void io_set_destroy(pa_io_event *io, pa_io_event_destroy_cb_t cb) +{ + mrp_debug("PA set I/O watch destroy callback for %p (fd %d) to %p", + io, io->fd, cb); + + io->destroy = cb; +} + + + + +static void time_event_cb(mrp_timer_t *tmr, void *userdata) +{ + pa_time_event *t = (pa_time_event *)userdata; + + MRP_UNUSED(tmr); + + mrp_debug("PA time event for timer %p", t); + + mrp_del_timer(t->t); + t->t = NULL; + + t->busy = true; + t->cb(&t->m->api, t, &t->tv, t->userdata); + t->busy = false; + + if (t->dead) { + mrp_del_timer(t->t); + mrp_list_delete(&t->hook); + mrp_free(t); + } +} + + +static unsigned int timeval_diff(const struct timeval *from, + const struct timeval *to) +{ + int msecs, musecs, diff; + + msecs = (to->tv_sec - from->tv_sec) * 1000; + musecs = ((int)to->tv_usec - (int)from->tv_usec) / 1000; + + diff = msecs + musecs; + + if (diff >= 0) + return (unsigned int)diff; + else + return 0; +} + + +static pa_time_event *time_new(pa_mainloop_api *api, const struct timeval *tv, + pa_time_event_cb_t cb, void *userdata) +{ + pa_murphy_mainloop *m = (pa_murphy_mainloop *)api->userdata; + pa_time_event *t; + struct timeval now; + + gettimeofday(&now, NULL); + + mrp_debug("PA create timer for %u msecs", timeval_diff(&now, tv)); + + t = mrp_allocz(sizeof(*t)); + + if (t == NULL) + return NULL; + + mrp_list_init(&t->hook); + + t->m = m; + t->cb = cb; + t->userdata = userdata; + t->t = mrp_add_timer(m->ml, timeval_diff(&now, tv), time_event_cb, t); + + if (t->t != NULL) + mrp_list_append(&m->time_events, &t->hook); + else { + mrp_free(t); + t = NULL; + } + + return t; +} + + +static void time_restart(pa_time_event *t, const struct timeval *tv) +{ + pa_murphy_mainloop *m = t->m; + struct timeval now; + + gettimeofday(&now, NULL); + + mrp_debug("PA restart timer %p with %u msecs", t, timeval_diff(&now, tv)); + + mrp_del_timer(t->t); + t->t = NULL; + + t->t = mrp_add_timer(m->ml, timeval_diff(&now, tv), time_event_cb, t); +} + + +static void time_free(pa_time_event *t) +{ + pa_murphy_mainloop *m = t->m; + + mrp_debug("PA free timer %p", t); + + mrp_list_delete(&t->hook); + mrp_del_timer(t->t); + t->t = NULL; + + t->dead = true; + + if (!t->busy && !t->dead) { + t->busy = true; + if (t->destroy != NULL) + t->destroy(&t->m->api, t, t->userdata); + mrp_free(t); + } + else + mrp_list_append(&m->time_dead, &t->hook); +} + + +static void time_set_destroy(pa_time_event *t, pa_time_event_destroy_cb_t cb) +{ + mrp_debug("PA set timer destroy callback for %p to %p", t, cb); + + t->destroy = cb; +} + + + + +static void defer_event_cb(mrp_deferred_t *def, void *userdata) +{ + pa_defer_event *d = (pa_defer_event *)userdata; + + MRP_UNUSED(def); + + mrp_debug("PA defer event for %p", d); + + d->busy = true; + d->cb(&d->m->api, d, d->userdata); + d->busy = false; + + if (d->dead) { + mrp_del_deferred(d->d); + mrp_list_delete(&d->hook); + mrp_free(d); + } +} + + +static pa_defer_event *defer_new(pa_mainloop_api *api, pa_defer_event_cb_t cb, + void *userdata) +{ + pa_murphy_mainloop *m = (pa_murphy_mainloop *)api->userdata; + pa_defer_event *d; + + mrp_debug("PA create defer event"); + + d = mrp_allocz(sizeof(*d)); + + if (d == NULL) + return NULL; + + mrp_list_init(&d->hook); + + d->m = m; + d->cb = cb; + d->userdata = userdata; + d->d = mrp_add_deferred(m->ml, defer_event_cb, d); + + if (d->d != NULL) + mrp_list_append(&m->defer_events, &d->hook); + else { + mrp_free(d); + d = NULL; + } + + return d; +} + + +static void defer_enable(pa_defer_event *d, int enable) +{ + mrp_debug("PA %s defer event %p", enable ? "enable" : "disable", d); + + if (enable) + mrp_enable_deferred(d->d); + else + mrp_disable_deferred(d->d); +} + + +static void defer_free(pa_defer_event *d) +{ + pa_murphy_mainloop *m = d->m; + + mrp_debug("PA free defer event %p", d); + + mrp_list_delete(&d->hook); + mrp_del_deferred(d->d); + d->d = NULL; + + d->dead = true; + + if (!d->busy && !d->dead) { + d->busy = true; + if (d->destroy != NULL) + d->destroy(&d->m->api, d, d->userdata); + mrp_free(d); + } + else + mrp_list_append(&m->defer_dead, &d->hook); +} + + +static void defer_set_destroy(pa_defer_event *d, pa_defer_event_destroy_cb_t cb) +{ + mrp_debug("PA set defer event destroy callback for %p to %p", d, cb); + + d->destroy = cb; +} + + +static void quit(pa_mainloop_api *api, int retval) +{ + pa_murphy_mainloop *m = (pa_murphy_mainloop *)api->userdata; + + mrp_mainloop_quit(m->ml, retval); +} + + + +pa_mainloop_api *pa_murphy_mainloop_get_api(pa_murphy_mainloop *m) +{ + pa_mainloop_api api = { + .userdata = m, + .io_new = io_new, + .io_enable = io_enable, + .io_free = io_free, + .io_set_destroy = io_set_destroy, + .time_new = time_new, + .time_restart = time_restart, + .time_free = time_free, + .time_set_destroy = time_set_destroy, + .defer_new = defer_new, + .defer_enable = defer_enable, + .defer_free = defer_free, + .defer_set_destroy = defer_set_destroy, + .quit = quit + }; + + m->api = api; + + return &m->api; +} diff --git a/src/common/pulse-subloop.h b/src/common/pulse-subloop.h new file mode 100644 index 0000000..f2fbe17 --- /dev/null +++ b/src/common/pulse-subloop.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2014, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_PULSE_SUBLOOP_H__ +#define __MURPHY_PULSE_SUBLOOP_H__ + +#include <murphy/common/macros.h> +#include <murphy/common/mainloop.h> + +#include <pulse/mainloop-api.h> + +MRP_CDECL_BEGIN + +/** An opaque Murphy main loop object. */ +typedef struct pa_murphy_mainloop pa_murphy_mainloop; + +/** Create a new Murphy PA main loop object for the specified main loop. */ +pa_murphy_mainloop *pa_murphy_mainloop_new(mrp_mainloop_t *ml); + +/** Free the Murphy PA main loop object. */ +void pa_murphy_mainloop_free(pa_murphy_mainloop *m); + +/** Return the abstract main loop API for the PA Murphy main loop object. */ +pa_mainloop_api *pa_murphy_mainloop_get_api(pa_murphy_mainloop *m); + +MRP_CDECL_END + +#endif /* __PULSE_SUBLOOP_H__ */ diff --git a/src/common/qt-glue-priv.h b/src/common/qt-glue-priv.h new file mode 100644 index 0000000..cb31531 --- /dev/null +++ b/src/common/qt-glue-priv.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __MURPHY_QT_GLUE_PRIV_H_ +#define __MURPHY_QT_GLUE_PRIV_H_ + +#include <murphy/config.h> + +#ifdef QT_ENABLED + +#include <sys/types.h> +#include <sys/socket.h> + +#include <QSocketNotifier> +#include <QTimer> +#include <murphy/common/mainloop.h> + +class QtGlue : public QObject +{ +Q_OBJECT + +public: + QtGlue(QObject *parent = NULL); +}; + +class QtIO : public QObject +{ + Q_OBJECT + +public: + enum Event { + Read = 0x01, + Write = 0x02, + Exception = 0x04 + }; + Q_DECLARE_FLAGS(EventMask, Event) + + QtIO (int fd, EventMask events, QObject *parent = NULL); + ~QtIO(); + +public Q_SLOTS: + void readyRead (int fd); + void readyWrite (int fd); + void exception (int fd); + +private: + EventMask m_events; + QSocketNotifier *m_fdIn; + QSocketNotifier *m_fdOut; + QSocketNotifier *m_fdExcep; + +public: + void (*cb)(void *glue_data, + void *id, int fd, mrp_io_event_t events, + void *user_data); + void *user_data; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS (QtIO::EventMask) + +class QtTimer : public QObject +{ +Q_OBJECT + +public: + QtTimer (int msecs, QObject *parent = NULL); + ~QtTimer (); + + void setInterval (int msecs); + void start (); + void stop (); + void enable (); + void disable(); + +private Q_SLOTS: + void timedout(); + +private: + QTimer *m_timer; + int m_interval; + bool m_disabled; + +public: + void (*cb)(void *glue_data, void *id, void *user_data); + void *user_data; +}; + +#endif + +#endif /* __MURPHY_QT_GLUE_PRIV_H_ */ diff --git a/src/common/qt-glue.cpp b/src/common/qt-glue.cpp new file mode 100644 index 0000000..8fbf636 --- /dev/null +++ b/src/common/qt-glue.cpp @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "qt-glue-priv.h" +#include "qt-glue-priv.moc.h" +#include <sys/types.h> +#include <sys/socket.h> + +#include <QObject> +#include <QSocketNotifier> +#include <QTimer> + +#include <murphy/config.h> +#include <murphy/common/mm.h> +#include <murphy/common/debug.h> +#include <murphy/common/mainloop.h> +#include <murphy/common/qt-glue.h> + +static mrp_mainloop_t *qt_ml; + + +/* + * QtGlue + */ + +QtGlue::QtGlue(QObject *parent) + : QObject(parent) +{ +} + + +/* + * QtIO + */ + +QtIO::QtIO (int fd, EventMask events, QObject *parent) + : QObject (parent) + , m_events(events), m_fdIn(0), m_fdOut(0), m_fdExcep(0) + , cb(0), user_data(0) +{ + if (events & Read) { + m_fdIn = new QSocketNotifier(fd, QSocketNotifier::Read, this); + + m_fdIn->setEnabled (true); + + QObject::connect (m_fdIn, SIGNAL(activated(int)), this, + SLOT(readyRead(int))); + } + if (events & Write) { + m_fdOut = new QSocketNotifier(fd, QSocketNotifier::Write, this); + + m_fdOut->setEnabled (true); + + QObject::connect (m_fdOut, SIGNAL(activated(int)), this, + SLOT(readyWrite(int))); + } + if (events & Exception) { + m_fdExcep = new QSocketNotifier(fd, QSocketNotifier::Exception, this); + + m_fdExcep->setEnabled (true); + + QObject::connect (m_fdExcep, SIGNAL(activated(int)), this, + SLOT(exception(int))); + } +} + + +QtIO::~QtIO() { + if (m_fdIn) + delete m_fdIn; + if (m_fdOut) + delete m_fdOut; + if (m_fdExcep) + delete m_fdExcep; +} + + +void QtIO::readyRead (int fd) +{ + if(cb) + cb(parent(), this, fd, MRP_IO_EVENT_IN, user_data); +} + + +void QtIO::readyWrite (int fd) +{ + if (cb) + cb(parent(), this, fd, MRP_IO_EVENT_OUT, user_data); +} + + +static bool check_hup(int fd) +{ + char buf[1]; + ssize_t n; + + n = recv(fd, buf, sizeof(buf), MSG_DONTWAIT | MSG_PEEK); + + if (n == 0) + return true; + else + return false; +} + + +void QtIO::exception (int fd) +{ + mrp_io_event_t events; + + if (!check_hup(fd)) + events = MRP_IO_EVENT_HUP; + else + events = (mrp_io_event_t)(MRP_IO_EVENT_ERR | MRP_IO_EVENT_HUP); + + if (cb) + cb(parent(), this, fd, events, user_data); +} + + +/* + * QtTimer + */ + +QtTimer::QtTimer (int msecs, QObject *parent) + : QObject (parent) + , m_timer(new QTimer(this)), m_interval(msecs >= 0 ? msecs : 0) + , cb(0), user_data(0) +{ + m_timer->setInterval (m_interval); + m_timer->setSingleShot (false); + + QObject::connect (m_timer, SIGNAL(timeout()), this, SLOT(timedout())); +} + + +QtTimer::~QtTimer () +{ + delete m_timer; +} + + +void QtTimer::setInterval (int msecs) +{ + m_interval = (msecs >= 0 ? msecs : 0); + + m_timer->setInterval (m_interval); + + if (m_timer->isActive()) { + m_timer->stop (); + m_timer->start (); + } +} + + +void QtTimer::start () +{ + if (!m_timer->isActive()) + m_timer->start (); +} + + +void QtTimer::stop () +{ + if (m_timer->isActive()) + m_timer->stop(); +} + + +void QtTimer::disable() +{ + if (!m_disabled) { + delete m_timer; + + m_timer = 0; + m_disabled = true; + } +} + + +void QtTimer::enable() +{ + if (m_disabled) { + m_timer = new QTimer(this); + + setInterval (m_interval); + + connect (m_timer, SIGNAL(timeout()), this, SLOT(timedout())); + + m_timer->start (); + m_disabled = false; + } +} + + +void QtTimer::timedout() +{ + mrp_debug("timer %p latched", this); + + if (cb) + cb(parent(), this, user_data); +} + + +static void *add_io(void *glue_data, int fd, mrp_io_event_t events, + void (*cb)(void *glue_data, void *id, int fd, + mrp_io_event_t events, void *user_data), + void *user_data) +{ + QtGlue *qt_glue = (QtGlue *)glue_data; + QtIO *io; + QtIO::EventMask mask; + + mask = 0; + if (events & MRP_IO_EVENT_IN) + mask |= QtIO::Read; + if (events & MRP_IO_EVENT_OUT) + mask |= QtIO::Write; + if (events & (MRP_IO_EVENT_ERR | MRP_IO_EVENT_HUP)) + mask |= QtIO::Exception; + + io = new QtIO (fd, mask, qt_glue); + + if (io) { + mrp_debug("added I/O watch %p (events 0x%x) on fd %d", io, events, fd); + + io->cb = cb; + io->user_data = user_data; + } + + return io; +} + + +static void del_io(void *glue_data, void *id) +{ + QtIO *io = (QtIO *)id; + + MRP_UNUSED(glue_data); + + mrp_debug("deleting I/O watch %p", io); + + delete io; +} + + +static void *add_timer(void *glue_data, unsigned int msecs, + void (*cb)(void *glue_data, void *id, void *user_data), + void *user_data) +{ + QtGlue *qt_glue = (QtGlue *)glue_data; + QtTimer *t = new QtTimer(msecs, qt_glue); + + mrp_debug("created timer %p with %d msecs interval", t, msecs); + + if (t) { + t->cb = cb; + t->user_data = user_data; + t->start(); + } + + return t; +} + + +static void del_timer(void *glue_data, void *id) +{ + QtTimer *t = (QtTimer *)id; + + MRP_UNUSED(glue_data); + + mrp_debug("deleting timer %p", t); + + delete t; +} + + +static void mod_timer(void *glue_data, void *id, unsigned int msecs) +{ + QtTimer *t = (QtTimer *)id; + + MRP_UNUSED(glue_data); + + mrp_debug("setting timer %p to %d msecs interval", t, msecs); + + if (t != NULL) + t->setInterval(msecs); +} + + +static void *add_defer(void *glue_data, + void (*cb)(void *glue_data, void *id, void *user_data), + void *user_data) +{ + QtGlue *qt_glue = (QtGlue *)glue_data; + QtTimer *t = new QtTimer(0, qt_glue); + + mrp_debug("created timer %p", t); + + if (t) { + t->cb = cb; + t->user_data = user_data; + t->start(); + } + + return t; +} + + +static void del_defer(void *glue_data, void *id) +{ + QtTimer *t = (QtTimer *)id; + + MRP_UNUSED(glue_data); + + mrp_debug("deleting timer %p", t); + + delete t; +} + + +static void mod_defer(void *glue_data, void *id, int enabled) +{ + QtTimer *t = (QtTimer *)id; + + MRP_UNUSED(glue_data); + + mrp_debug("%s timer %p", enabled ? "enabling" : "disabling", t); + + if (enabled) + t->enable(); + else + t->disable(); +} + + +static void unregister(void *glue_data) +{ + QtGlue *qt_glue = (QtGlue *)glue_data; + + mrp_debug("unregistering mainloop"); + + delete qt_glue; +} + + +int mrp_mainloop_register_with_qt(mrp_mainloop_t *ml) +{ + static mrp_superloop_ops_t qt_ops; + QtGlue *qt_glue; + + qt_ops.add_io = add_io; + qt_ops.del_io = del_io; + qt_ops.add_timer = add_timer; + qt_ops.del_timer = del_timer; + qt_ops.mod_timer = mod_timer; + qt_ops.add_defer = add_defer; + qt_ops.del_defer = del_defer; + qt_ops.mod_defer = mod_defer; + qt_ops.unregister = unregister; + + qt_glue = new QtGlue (); + + return mrp_set_superloop(ml, &qt_ops, (void *)qt_glue); +} + + +int mrp_mainloop_unregister_from_qt(mrp_mainloop_t *ml) +{ + return mrp_mainloop_unregister(ml); +} + + +mrp_mainloop_t *mrp_mainloop_qt_get(void) +{ + if (qt_ml == NULL) { + qt_ml = mrp_mainloop_create(); + + if (qt_ml != NULL) { + if (!mrp_mainloop_register_with_qt(qt_ml)) { + mrp_mainloop_destroy(qt_ml); + qt_ml = NULL; + } + } + } + + return qt_ml; +} diff --git a/src/common/qt-glue.h b/src/common/qt-glue.h new file mode 100644 index 0000000..8b2bda5 --- /dev/null +++ b/src/common/qt-glue.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_QT_H__ +#define __MURPHY_QT_H__ + +#include <murphy/common/macros.h> +#include <murphy/common/mainloop.h> + +MRP_CDECL_BEGIN + +/** Register the given murphy mainloop with the qt mainloop. */ +int mrp_mainloop_register_with_qt(mrp_mainloop_t *ml); + +/** Unrgister the given murphy mainloop from the qt mainloop. */ +int mrp_mainloop_unregister_from_qt(mrp_mainloop_t *ml); + +/** Create a murphy mainloop and set it up with the qt mainloop. */ +mrp_mainloop_t *mrp_mainloop_qt_get(void); + +MRP_CDECL_END + +#endif /* __MURPHY_QT_H__ */ diff --git a/src/common/refcnt.h b/src/common/refcnt.h new file mode 100644 index 0000000..dbaf8c2 --- /dev/null +++ b/src/common/refcnt.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_REFCNT_H__ +#define __MURPHY_REFCNT_H__ + +/* + * A place/typeholder, so we can switch easily to atomic type + * if/when necessary. + */ + +#include <murphy/common/macros.h> +#include <murphy/common/log.h> + +#define __MURPHY_REFCNT_CHECK__ + +MRP_CDECL_BEGIN + +typedef int mrp_refcnt_t; + +static inline void *_mrp_ref_obj(void *obj, off_t offs) +{ + mrp_refcnt_t *refcnt; + + if (obj != NULL) { + refcnt = (mrp_refcnt_t *) ((char *) obj + offs); + (*refcnt)++; + } + + return obj; +} + +static inline int _mrp_unref_obj(void *obj, off_t offs +#ifdef __MURPHY_REFCNT_CHECK__ + , const char *file + , int line + , const char *func +#endif + ) +{ + mrp_refcnt_t *refcnt; + + if (obj != NULL) { + refcnt = (mrp_refcnt_t *) ((char *) obj + offs); + --(*refcnt); + + if (*refcnt == 0) + return TRUE; + +#ifdef __MURPHY_REFCNT_CHECK__ +# define W mrp_log_error + + if (*refcnt < 0) { + W("****************** REFCOUNTING BUG WARNING ******************"); + W("* Reference-counting bug detected. The reference count of"); + W("* object %p (@offs %d) has dropped to %d.", obj, (int)offs, + (int)*refcnt); + W("* The offending unref call was made at:"); + W("* %s@%s:%d", func ? func : "<unkown>", + file ? file : "<unknown>", line); + W("*************************************************************"); + } + +#undef W +#endif + } + + return FALSE; +} + + +static inline void mrp_refcnt_init(mrp_refcnt_t *refcnt) +{ + *refcnt = 1; +} + +#define mrp_ref_obj(obj, member) \ + (typeof(obj))_mrp_ref_obj(obj, MRP_OFFSET(typeof(*(obj)), member)) + +#ifndef __MURPHY_REFCNT_CHECK__ +# define mrp_unref_obj(obj, member) \ + _mrp_unref_obj(obj, MRP_OFFSET(typeof(*(obj)), member)) +#else +# define mrp_unref_obj(obj, member) \ + _mrp_unref_obj(obj, MRP_OFFSET(typeof(*(obj)), member), __LOC__) +#endif + +MRP_CDECL_END + +#endif /* __MURPHY_REFCNT_H__ */ diff --git a/src/common/socket-utils.c b/src/common/socket-utils.c new file mode 100644 index 0000000..4f7d957 --- /dev/null +++ b/src/common/socket-utils.c @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> + +#include <murphy/common/macros.h> + +static int reject_fd = -1; + + +static inline int reserve_reject_fd(void) +{ + if (reject_fd < 0) + reject_fd = open("/dev/null", O_RDONLY); + + return reject_fd; +} + + +static void MRP_INIT reserve_reject(void) +{ + reserve_reject_fd(); +} + + +int mrp_reject_connection(int sock, struct sockaddr *addr, socklen_t *alen) +{ + struct sockaddr *a, buf; + socklen_t *l, len; + int fd; + + if (addr != NULL) { + a = addr; + l = alen; + } + else { + len = sizeof(buf); + a = &buf; + l = &len; + } + + fd = accept(sock, a, l); + + if (fd >= 0) { + close(fd); + + return 0; + } + + if (errno != EMFILE) + return -1; + + if (reject_fd < 0) { + errno = ENOENT; + + return -1; + } + + close(reject_fd); + reject_fd = -1; + + fd = accept(sock, a, l); + + if (fd >= 0) + close(fd); + + reserve_reject_fd(); + + return (fd >= 0 ? 0 : -1); +} diff --git a/src/common/socket-utils.h b/src/common/socket-utils.h new file mode 100644 index 0000000..ad1b998 --- /dev/null +++ b/src/common/socket-utils.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2014, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_SOCKET_UTILS_H__ +#define __MURPHY_SOCKET_UTILS_H__ + +#include <sys/socket.h> + +#include <murphy/common/macros.h> + +MRP_CDECL_BEGIN + +int mrp_reject_connection(int sock, struct sockaddr *addr, socklen_t alen); + +MRP_CDECL_END + +#endif /* __MURPHY_SOCKET_UTILS_H__ */ diff --git a/src/common/stream-transport.c b/src/common/stream-transport.c new file mode 100644 index 0000000..d8ee1e6 --- /dev/null +++ b/src/common/stream-transport.c @@ -0,0 +1,853 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <netdb.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <netinet/in.h> +#include <sys/un.h> +#include <sys/uio.h> + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/log.h> +#include <murphy/common/msg.h> +#include <murphy/common/fragbuf.h> +#include <murphy/common/socket-utils.h> +#include <murphy/common/transport.h> + +#ifndef UNIX_PATH_MAX +# define UNIX_PATH_MAX sizeof(((struct sockaddr_un *)NULL)->sun_path) +#endif + +#define TCP4 "tcp4" +#define TCP4L 4 +#define TCP6 "tcp6" +#define TCP6L 4 +#define UNXS "unxs" +#define UNXSL 4 + +#define DEFAULT_SIZE 128 /* default input buffer size */ + +typedef struct { + MRP_TRANSPORT_PUBLIC_FIELDS; /* common transport fields */ + int sock; /* TCP socket */ + mrp_io_watch_t *iow; /* socket I/O watch */ + mrp_fragbuf_t *buf; /* fragment buffer */ +} strm_t; + + +static void strm_recv_cb(mrp_io_watch_t *w, int fd, mrp_io_event_t events, + void *user_data); +static int strm_disconnect(mrp_transport_t *mt); +static int open_socket(strm_t *t, int family); + + + +static int parse_address(const char *str, int *familyp, char *nodep, + size_t nsize, char **servicep, const char **typep) +{ + char *node, *service; + const char *type; + int family; + size_t l, nl; + + node = (char *)str; + + if (!strncmp(node, TCP4":", l=TCP4L+1)) { + family = AF_INET; + type = TCP4; + node += l; + } + else if (!strncmp(node, TCP6":", l=TCP6L+1)) { + family = AF_INET6; + type = TCP6; + node += l; + } + else if (!strncmp(node, UNXS":", l=UNXSL+1)) { + family = AF_UNIX; + type = UNXS; + node += l; + } + else { + if (node[0] == '[') family = AF_INET6; + else if (node[0] == '/') family = AF_UNIX; + else if (node[0] == '@') family = AF_UNIX; + else family = AF_UNSPEC; + + type = NULL; + } + + switch (family) { + case AF_INET: + service = strrchr(node, ':'); + if (service == NULL) { + errno = EINVAL; + return -1; + } + + nl = service - node; + service++; + + case AF_INET6: + service = strrchr(node, ':'); + + if (service == NULL || service == node) { + errno = EINVAL; + return -1; + } + + if (node[0] == '[') { + node++; + + if (service[-1] != ']') { + errno = EINVAL; + return -1; + } + + nl = service - node - 1; + } + else + nl = service - node; + + service++; + break; + + case AF_UNSPEC: + if (!strncmp(node, "tcp:", l=4)) + node += l; + service = strrchr(node, ':'); + + if (service == NULL || service == node) { + errno = EINVAL; + return -1; + } + + if (node[0] == '[') { + node++; + family = AF_INET6; + + if (service[-1] != ']') { + errno = EINVAL; + return -1; + } + + nl = service - node - 1; + } + else { + family = AF_INET; + nl = service - node; + } + service++; + break; + + case AF_UNIX: + service = NULL; + nl = strlen(node); + } + + if (nl >= nsize) { + errno = ENOMEM; + return -1; + } + + strncpy(nodep, node, nl); + nodep[nl] = '\0'; + *servicep = service; + *familyp = family; + if (typep != NULL) + *typep = type; + + return 0; +} + + +static socklen_t strm_resolve(const char *str, mrp_sockaddr_t *addr, + socklen_t size, const char **typep) +{ + struct addrinfo *ai, hints; + struct sockaddr_un *un; + char node[UNIX_PATH_MAX], *port; + socklen_t len; + + mrp_clear(&hints); + + if (parse_address(str, &hints.ai_family, node, sizeof(node), + &port, typep) < 0) + return 0; + + switch (hints.ai_family) { + case AF_UNIX: + un = &addr->unx; + len = MRP_OFFSET(typeof(*un), sun_path) + strlen(node) + 1; + + if (size < len) + errno = ENOMEM; + else { + un->sun_family = AF_UNIX; + strncpy(un->sun_path, node, UNIX_PATH_MAX-1); + if (un->sun_path[0] == '@') + un->sun_path[0] = '\0'; + } + + /* When binding the socket, we don't need the null at the end */ + len--; + + break; + + case AF_INET: + case AF_INET6: + default: + if (getaddrinfo(node, port, &hints, &ai) == 0) { + if (ai->ai_addrlen <= size) { + memcpy(addr, ai->ai_addr, ai->ai_addrlen); + len = ai->ai_addrlen; + } + else + len = 0; + + freeaddrinfo(ai); + } + else + len = 0; + } + + return len; +} + + +static int strm_open(mrp_transport_t *mt) +{ + strm_t *t = (strm_t *)mt; + + t->sock = -1; + + return TRUE; +} + + +static int set_nonblocking(int sock, int nonblocking) +{ + long nb = (nonblocking ? 1 : 0); + + return fcntl(sock, F_SETFL, O_NONBLOCK, nb); +} + + +static int set_cloexec(int fd, int cloexec) +{ + int on = cloexec ? 1 : 0; + + return fcntl(fd, F_SETFL, O_CLOEXEC, on); +} + + +static int set_reuseaddr(int sock, int reuseaddr) +{ + int on; + + if (reuseaddr) { + on = 1; + return setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + } + else + return 0; +} + + +static int strm_createfrom(mrp_transport_t *mt, void *conn) +{ + strm_t *t = (strm_t *)mt; + mrp_io_event_t events; + + t->sock = *(int *)conn; + + if (t->sock >= 0) { + if (mt->flags & MRP_TRANSPORT_REUSEADDR) + if (set_reuseaddr(t->sock, true) < 0) + return FALSE; + + if (mt->flags & MRP_TRANSPORT_NONBLOCK || t->listened) + if (set_nonblocking(t->sock, true) < 0) + return FALSE; + + if (t->connected || t->listened) { + if (!t->connected || + (t->buf = mrp_fragbuf_create(TRUE, 0)) != NULL) { + events = MRP_IO_EVENT_IN | MRP_IO_EVENT_HUP; + t->iow = mrp_add_io_watch(t->ml, t->sock, events, + strm_recv_cb, t); + + if (t->iow != NULL) + return TRUE; + + mrp_fragbuf_destroy(t->buf); + t->buf = NULL; + } + } + } + + return FALSE; +} + + +static void strm_close(mrp_transport_t *mt) +{ + strm_t *t = (strm_t *)mt; + + mrp_debug("closing transport %p", mt); + + mrp_del_io_watch(t->iow); + t->iow = NULL; + + mrp_fragbuf_destroy(t->buf); + t->buf = NULL; + + if (t->sock >= 0){ + close(t->sock); + t->sock = -1; + } +} + + +static int strm_bind(mrp_transport_t *mt, mrp_sockaddr_t *addr, + socklen_t addrlen) +{ + strm_t *t = (strm_t *)mt; + + if (t->sock != -1 || open_socket(t, addr->any.sa_family)) { + if (bind(t->sock, &addr->any, addrlen) == 0) { + mrp_debug("transport %p bound", mt); + return TRUE; + } + } + + mrp_debug("failed to bind transport %p", mt); + return FALSE; +} + + +static int strm_listen(mrp_transport_t *mt, int backlog) +{ + strm_t *t = (strm_t *)mt; + + if (t->sock != -1 && t->iow != NULL && t->evt.connection != NULL) { + if (set_nonblocking(t->sock, true) < 0) + return FALSE; + + if (listen(t->sock, backlog) == 0) { + mrp_debug("transport %p listening", mt); + t->listened = TRUE; + return TRUE; + } + } + + mrp_debug("transport %p failed to listen", mt); + return FALSE; +} + + +static int strm_accept(mrp_transport_t *mt, mrp_transport_t *mlt) +{ + strm_t *t, *lt; + mrp_sockaddr_t addr; + socklen_t addrlen; + mrp_io_event_t events; + + t = (strm_t *)mt; + lt = (strm_t *)mlt; + + if (lt->sock < 0) { + errno = EBADF; + + return FALSE; + } + + addrlen = sizeof(addr); + t->sock = accept(lt->sock, &addr.any, &addrlen); + + if (t->sock >= 0) { + if (mt->flags & MRP_TRANSPORT_REUSEADDR) + if (set_reuseaddr(t->sock, true) < 0) + goto reject; + + if (mt->flags & MRP_TRANSPORT_NONBLOCK) + if (set_nonblocking(t->sock, true) < 0) + goto reject; + + if (mt->flags & MRP_TRANSPORT_CLOEXEC) + if (set_cloexec(t->sock, true) < 0) + goto reject; + + t->buf = mrp_fragbuf_create(TRUE, 0); + events = MRP_IO_EVENT_IN | MRP_IO_EVENT_HUP; + t->iow = mrp_add_io_watch(t->ml, t->sock, events, strm_recv_cb, t); + + if (t->iow != NULL && t->buf != NULL) { + mrp_debug("accepted connection on transport %p/%p", mlt, mt); + return TRUE; + } + else { + mrp_fragbuf_destroy(t->buf); + t->buf = NULL; + close(t->sock); + t->sock = -1; + } + } + else { + reject: + if (mrp_reject_connection(lt->sock, NULL, 0) < 0) { + mrp_log_error("%s(): accept failed, closing transport %p (%d: %s).", + __FUNCTION__, mlt, errno, strerror(errno)); + strm_close(mlt); + + /* Notes: + * Unfortunately we cannot safely emit a closed event here. + * The closed event is semantically attached to an accepted + * tranport being closed and there is no equivalent for a + * listening transport (we should have had a generic error + * event). There for the transport owner expects and treats + * (IOW casts) the associated user_data accordingly. That + * would end up in a disaster... Once we cleanup/rework the + * transport infra, this needs to be done better. + */ + } + else + mrp_log_error("%s(): rejected connection for transport %p (%d: %s).", + __FUNCTION__, mlt, errno, strerror(errno)); + } + + return FALSE; +} + + +static void strm_recv_cb(mrp_io_watch_t *w, int fd, mrp_io_event_t events, + void *user_data) +{ + strm_t *t = (strm_t *)user_data; + mrp_transport_t *mt = (mrp_transport_t *)t; + void *data, *buf; + uint32_t pending; + size_t size; + ssize_t n; + int error; + + MRP_UNUSED(w); + + mrp_debug("event 0x%x for transport %p", events, t); + + if (events & MRP_IO_EVENT_IN) { + if (MRP_UNLIKELY(mt->listened != 0)) { + MRP_TRANSPORT_BUSY(mt, { + mrp_debug("connection event on transport %p", mt); + mt->evt.connection(mt, mt->user_data); + }); + + t->check_destroy(mt); + return; + } + + while (ioctl(fd, FIONREAD, &pending) == 0 && pending > 0) { + buf = mrp_fragbuf_alloc(t->buf, pending); + + if (buf == NULL) { + error = ENOMEM; + fatal_error: + mrp_debug("transport %p closed with error %d", mt, error); + closed: + strm_disconnect(mt); + + if (t->evt.closed != NULL) + MRP_TRANSPORT_BUSY(mt, { + mt->evt.closed(mt, error, mt->user_data); + }); + + t->check_destroy(mt); + return; + } + + n = read(fd, buf, pending); + + if (n >= 0) { + if (n < (ssize_t)pending) + mrp_fragbuf_trim(t->buf, buf, pending, n); + } + + if (n < 0 && errno != EAGAIN) { + error = EIO; + goto fatal_error; + } + } + + data = NULL; + size = 0; + while (mrp_fragbuf_pull(t->buf, &data, &size)) { + if (t->mode != MRP_TRANSPORT_MODE_JSON) + error = t->recv_data(mt, data, size, NULL, 0); + else { + mrp_json_t *msg = mrp_json_string_to_object(data, size); + + if (msg != NULL) { + error = t->recv_data((mrp_transport_t *)t, msg, 0, NULL, 0); + mrp_json_unref(msg); + } + else + error = EILSEQ; + } + + if (error) + goto fatal_error; + + if (t->check_destroy(mt)) + return; + } + } + + if (events & MRP_IO_EVENT_HUP) { + mrp_debug("transport %p closed by peer", mt); + error = 0; + goto closed; + } +} + + +static int open_socket(strm_t *t, int family) +{ + mrp_io_event_t events; + + t->sock = socket(family, SOCK_STREAM, 0); + + if (t->sock != -1) { + if (t->flags & MRP_TRANSPORT_REUSEADDR) + if (set_reuseaddr(t->sock, true) < 0) + goto fail; + + if (t->flags & MRP_TRANSPORT_NONBLOCK) + if (set_nonblocking(t->sock, true) < 0) + goto fail; + + if (t->flags & MRP_TRANSPORT_CLOEXEC) + if (set_cloexec(t->sock, true) < 0) + goto fail; + + events = MRP_IO_EVENT_IN | MRP_IO_EVENT_HUP; + t->iow = mrp_add_io_watch(t->ml, t->sock, events, strm_recv_cb, t); + + if (t->iow != NULL) + return TRUE; + else { + fail: + close(t->sock); + t->sock = -1; + } + } + + return FALSE; +} + + +static int strm_connect(mrp_transport_t *mt, mrp_sockaddr_t *addr, + socklen_t addrlen) +{ + strm_t *t = (strm_t *)mt; + mrp_io_event_t events; + + t->sock = socket(addr->any.sa_family, SOCK_STREAM, 0); + + if (t->sock < 0) + goto fail; + + if (connect(t->sock, &addr->any, addrlen) == 0) { + if (set_reuseaddr(t->sock, true) < 0 || + set_nonblocking(t->sock, true) < 0) + goto close_and_fail; + + t->buf = mrp_fragbuf_create(TRUE, 0); + + if (t->buf != NULL) { + events = MRP_IO_EVENT_IN | MRP_IO_EVENT_HUP; + t->iow = mrp_add_io_watch(t->ml, t->sock, events, strm_recv_cb, t); + + if (t->iow != NULL) { + mrp_debug("connected transport %p", mt); + + return TRUE; + } + + mrp_fragbuf_destroy(t->buf); + t->buf = NULL; + } + } + + if (t->sock != -1) { + close_and_fail: + close(t->sock); + t->sock = -1; + } + + fail: + mrp_debug("failed to connect transport %p", mt); + + return FALSE; +} + + +static int strm_disconnect(mrp_transport_t *mt) +{ + strm_t *t = (strm_t *)mt; + + if (t->connected/* || t->iow != NULL*/) { + mrp_del_io_watch(t->iow); + t->iow = NULL; + + shutdown(t->sock, SHUT_RDWR); + + mrp_fragbuf_destroy(t->buf); + t->buf = NULL; + + mrp_debug("disconnected transport %p", mt); + + return TRUE; + } + else + return FALSE; +} + + +static int strm_send(mrp_transport_t *mt, mrp_msg_t *msg) +{ + strm_t *t = (strm_t *)mt; + struct iovec iov[2]; + void *buf; + ssize_t size, n; + uint32_t len; + + if (t->connected) { + size = mrp_msg_default_encode(msg, &buf); + + if (size >= 0) { + len = htobe32(size); + iov[0].iov_base = &len; + iov[0].iov_len = sizeof(len); + iov[1].iov_base = buf; + iov[1].iov_len = size; + + n = writev(t->sock, iov, 2); + mrp_free(buf); + + if (n == (ssize_t)(size + sizeof(len))) + return TRUE; + else { + if (n == -1 && errno == EAGAIN) { + mrp_log_error("%s(): XXX TODO: this sucks, need to add " + "output queuing for strm-transport.", + __FUNCTION__); + } + } + } + } + + return FALSE; +} + + +static int strm_sendraw(mrp_transport_t *mt, void *data, size_t size) +{ + strm_t *t = (strm_t *)mt; + ssize_t n; + + if (t->connected) { + n = write(t->sock, data, size); + + if (n == (ssize_t)size) + return TRUE; + else { + if (n == -1 && errno == EAGAIN) { + mrp_log_error("%s(): XXX TODO: this sucks, need to add " + "output queuing for strm-transport.", + __FUNCTION__); + } + } + } + + return FALSE; +} + + +static int strm_senddata(mrp_transport_t *mt, void *data, uint16_t tag) +{ + strm_t *t = (strm_t *)mt; + mrp_data_descr_t *type; + ssize_t n; + void *buf; + size_t size, reserve, len; + uint32_t *lenp; + uint16_t *tagp; + + if (t->connected) { + type = mrp_msg_find_type(tag); + + if (type != NULL) { + reserve = sizeof(*lenp) + sizeof(*tagp); + size = mrp_data_encode(&buf, data, type, reserve); + + if (size > 0) { + lenp = buf; + len = size - sizeof(*lenp); + tagp = buf + sizeof(*lenp); + *lenp = htobe32(len); + *tagp = htobe16(tag); + + n = write(t->sock, buf, len + sizeof(*lenp)); + + mrp_free(buf); + + if (n == (ssize_t)(len + sizeof(*lenp))) + return TRUE; + else { + if (n == -1 && errno == EAGAIN) { + mrp_log_error("%s(): XXX TODO: this sucks, need to add" + " output queueing for strm-transport.", + __FUNCTION__); + } + } + } + } + } + + return FALSE; +} + + +static int strm_sendnative(mrp_transport_t *mt, void *data, uint32_t type_id) +{ + strm_t *t = (strm_t *)mt; + mrp_typemap_t *map = t->map; + void *buf; + size_t size, reserve; + uint32_t *lenp; + ssize_t n; + + if (t->connected) { + reserve = sizeof(*lenp); + + if (mrp_encode_native(data, type_id, reserve, &buf, &size, map) == 0) { + lenp = buf; + *lenp = htobe32(size - sizeof(*lenp)); + + n = write(t->sock, buf, size); + + mrp_free(buf); + + if (n == (ssize_t)size) + return TRUE; + else { + if (n == -1 && errno == EAGAIN) { + mrp_log_error("%s(): XXX TODO: this sucks, need to add" + " output queueing for strm-transport.", + __FUNCTION__); + } + } + } + } + + return FALSE; +} + + +static int strm_sendjson(mrp_transport_t *mt, mrp_json_t *msg) +{ + strm_t *t = (strm_t *)mt; + struct iovec iov[2]; + const char *s; + ssize_t size, n; + uint32_t len; + + if (t->connected && (s = mrp_json_object_to_string(msg)) != NULL) { + size = strlen(s); + len = htobe32(size); + iov[0].iov_base = &len; + iov[0].iov_len = sizeof(len); + iov[1].iov_base = (void *)s; + iov[1].iov_len = size; + + n = writev(t->sock, iov, 2); + + if (n == (ssize_t)(size + sizeof(len))) + return TRUE; + else { + if (n == -1 && errno == EAGAIN) { + mrp_log_error("%s(): XXX TODO: this sucks, need to add " + "output queuing for strm-transport.", + __FUNCTION__); + } + } + } + + return FALSE; +} + + +MRP_REGISTER_TRANSPORT(tcp4, TCP4, strm_t, strm_resolve, + strm_open, strm_createfrom, strm_close, NULL, + strm_bind, strm_listen, strm_accept, + strm_connect, strm_disconnect, + strm_send, NULL, + strm_sendraw, NULL, + strm_senddata, NULL, + NULL, NULL, + strm_sendnative, NULL, + strm_sendjson, NULL); + +MRP_REGISTER_TRANSPORT(tcp6, TCP6, strm_t, strm_resolve, + strm_open, strm_createfrom, strm_close, NULL, + strm_bind, strm_listen, strm_accept, + strm_connect, strm_disconnect, + strm_send, NULL, + strm_sendraw, NULL, + strm_senddata, NULL, + NULL, NULL, + strm_sendnative, NULL, + strm_sendjson, NULL); + +MRP_REGISTER_TRANSPORT(unxstrm, UNXS, strm_t, strm_resolve, + strm_open, strm_createfrom, strm_close, NULL, + strm_bind, strm_listen, strm_accept, + strm_connect, strm_disconnect, + strm_send, NULL, + strm_sendraw, NULL, + strm_senddata, NULL, + NULL, NULL, + strm_sendnative, NULL, + strm_sendjson, NULL); diff --git a/src/common/tests/Makefile.am b/src/common/tests/Makefile.am new file mode 100644 index 0000000..0162fad --- /dev/null +++ b/src/common/tests/Makefile.am @@ -0,0 +1,144 @@ +AM_CFLAGS = $(WARNING_CFLAGS) -I$(top_builddir) + +noinst_PROGRAMS = mm-test hash-test hash12-test msg-test transport-test \ + internal-transport-test process-watch-test native-test \ + mkdir-test path-test mask-test + +if LIBDBUS_ENABLED +noinst_PROGRAMS += mainloop-test dbus-test +endif + +noinst_PROGRAMS += fragbuf-test + +# memory management test +mm_test_SOURCES = mm-test.c +mm_test_CFLAGS = $(AM_CFLAGS) +mm_test_LDADD = ../../libmurphy-common.la + +# hash table test +hash_test_SOURCES = hash-test.c +hash_test_CFLAGS = $(AM_CFLAGS) +hash_test_LDADD = ../../libmurphy-common.la + +# hash12-test +hash12_test_SOURCES = hash12-test.c +hash12_test_CFLAGS = $(AM_CFLAGS) +hash12_test_LDADD = ../../libmurphy-common.la + +# mainloop test +mainloop_test_SOURCES = mainloop-test.c +mainloop_test_CFLAGS = $(AM_CFLAGS) $(GLIB_CFLAGS) $(LIBDBUS_CFLAGS) +mainloop_test_LDADD = ../../libmurphy-common.la $(GLIB_LIBS) $(LIBDBUS_LIBS) +if PULSE_ENABLED +mainloop_test_CFLAGS += $(PULSE_CFLAGS) +mainloop_test_LDADD += ../../libmurphy-pulse.la $(PULSE_LIBS) +endif +if ECORE_ENABLED +mainloop_test_CFLAGS += $(ECORE_CFLAGS) +mainloop_test_LDADD += ../../libmurphy-ecore.la $(ECORE_LIBS) +endif +if GLIB_ENABLED +mainloop_test_CFLAGS += $(GLIB_CFLAGS) +mainloop_test_LDADD += ../../libmurphy-glib.la $(GLIB_LIBS) +endif + +if QT_ENABLED +noinst_LTLIBRARIES = libmainloop-qt-test.la +libmainloop_qt_test_la_SOURCES = mainloop-qt-test.cpp +libmainloop_qt_test_la_CPPFLAGS = $(AM_CFLAGS) $(QTCORE_CFLAGS) +libmainloop_qt_test_la_LIBADD = ../../libmurphy-common.la \ + ../../libmurphy-qt.la $(QTCORE_LIBS) +mainloop_test_LDADD += libmainloop-qt-test.la $(QTCORE_LIBS) -lstdc++ +endif + +# msg test +msg_test_SOURCES = msg-test.c +msg_test_CFLAGS = $(AM_CFLAGS) +msg_test_LDADD = ../../libmurphy-common.la + +# native type test +native_test_SOURCES = native-test.c +native_test_CFLAGS = $(AM_CFLAGS) +native_test_LDADD = ../../libmurphy-common.la + +# transport test +transport_test_SOURCES = transport-test.c +transport_test_CFLAGS = $(AM_CFLAGS) +transport_test_LDADD = ../../libmurphy-common.la + +# internal transport test +internal_transport_test_SOURCES = internal-transport-test.c +internal_transport_test_CFLAGS = $(AM_CFLAGS) +internal_transport_test_LDADD = ../../libmurphy-common.la + +# process watch test +process_watch_test_SOURCES = process-test.c +process_watch_test_CFLAGS = $(AM_CFLAGS) +process_watch_test_LDADD = ../../libmurphy-common.la + +if LIBDBUS_ENABLED +transport_test_LDADD += ../../libmurphy-libdbus.la + +noinst_PROGRAMS += mainloop-test + +# DBUS tests +noinst_PROGRAMS += dbus-test +dbus_test_SOURCES = dbus-test.c +dbus_test_CFLAGS = $(AM_CFLAGS) $(LIBDBUS_CFLAGS) +dbus_test_LDADD = ../../libmurphy-libdbus.la ../../libmurphy-common.la $(LIBDBUS_LIBS) + +noinst_PROGRAMS += libdbus-test libdbus-transport-test +libdbus_test_SOURCES = libdbus-test.c +libdbus_test_CFLAGS = $(AM_CFLAGS) $(LIBDBUS_CFLAGS) +libdbus_test_LDADD = ../../libmurphy-dbus-libdbus.la ../../libmurphy-common.la $(LIBDBUS_LIBS) + +libdbus_transport_test_SOURCES = libdbus-transport-test.c +libdbus_transport_test_CFLAGS = $(AM_CFLAGS) +libdbus_transport_test_LDADD = ../../libmurphy-common.la \ + ../../libmurphy-dbus-libdbus.la +endif + +if SDBUS_ENABLED +noinst_PROGRAMS += sdbus-test dbus-sdbus-test sdbus-transport-test sdbus-error-message + +sdbus_test_SOURCES = sdbus-test.c +sdbus_test_CFLAGS = $(AM_CFLAGS) $(SDBUS_CFLAGS) +sdbus_test_LDADD = ../../libmurphy-common.la $(SDBUS_LIBS) + +dbus_sdbus_test_SOURCES = dbus-sdbus-test.c +dbus_sdbus_test_CFLAGS = $(AM_CFLAGS) $(SDBUS_CFLAGS) +dbus_sdbus_test_LDADD = \ + ../../libmurphy-common.la \ + ../../libmurphy-dbus-sdbus.la + +sdbus_transport_test_SOURCES = libdbus-transport-test.c +sdbus_transport_test_CFLAGS = $(AM_CFLAGS) +sdbus_transport_test_LDADD = \ + ../../libmurphy-common.la \ + ../../libmurphy-dbus-sdbus.la + +sdbus_error_message_SOURCES = sdbus-error-message.c +sdbus_error_message_CFLAGS = $(AM_CFLAGS) $(SDBUS_CFLAGS) +sdbus_error_message_LDADD = \ + ../../libmurphy-common.la \ + ../../libmurphy-dbus-sdbus.la +endif + +# fragbuf test +fragbuf_test_SOURCES = fragbuf-test.c +fragbuf_test_CFLAGS = $(AM_CFLAGS) +fragbuf_test_LDADD = ../../libmurphy-common.la + +# mkdir-test +mkdir_test_SOURCES = mkdir-test.c +mkdir_test_CFLAGS = $(AM_CFLAGS) -I. +mkdir_test_LDADD = ../../libmurphy-common.la + +# path-test +path_test_SOURCES = path-test.c +path_test_CFLAGS = $(AM_CFLAGS) -I. +path_test_LDADD = ../../libmurphy-common.la + +mask_test_SOURCES = mask-test.c +mask_test_CFLAGS = $(AM_CFLAGS) -I. +mask_test_LDADD = ../../libmurphy-common.la diff --git a/src/common/tests/dbus-pump.c b/src/common/tests/dbus-pump.c new file mode 100644 index 0000000..4dc323d --- /dev/null +++ b/src/common/tests/dbus-pump.c @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <dbus/dbus.h> + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/log.h> +#include <murphy/common/list.h> +#include <murphy/common/mainloop.h> + +typedef struct dbus_glue_s dbus_glue_t; + +typedef struct { + dbus_glue_t *glue; + mrp_io_watch_t *mw; + DBusWatch *dw; + mrp_list_hook_t hook; +} watch_t; + + +typedef struct { + dbus_glue_t *glue; + mrp_timer_t *mt; + DBusTimeout *dt; + mrp_list_hook_t hook; +} timeout_t; + + +struct dbus_glue_s { + DBusConnection *conn; + mrp_mainloop_t *ml; + mrp_list_hook_t watches; + mrp_list_hook_t timers; + mrp_deferred_t *pump; +}; + + +static dbus_int32_t data_slot = -1; + +static void dispatch_watch(mrp_io_watch_t *mw, int fd, mrp_io_event_t events, + void *user_data) +{ + watch_t *watch = (watch_t *)user_data; + DBusConnection *conn = watch->glue->conn; + unsigned int mask = 0; + + MRP_UNUSED(mw); + MRP_UNUSED(fd); + + if (events & MRP_IO_EVENT_IN) + mask |= DBUS_WATCH_READABLE; + if (events & MRP_IO_EVENT_OUT) + mask |= DBUS_WATCH_WRITABLE; + if (events & MRP_IO_EVENT_HUP) + mask |= DBUS_WATCH_HANGUP; + if (events & MRP_IO_EVENT_ERR) + mask |= DBUS_WATCH_ERROR; + + dbus_connection_ref(conn); + dbus_watch_handle(watch->dw, mask); + dbus_connection_unref(conn); +} + + +static void watch_freed_cb(void *data) +{ + watch_t *watch = (watch_t *)data; + + if (watch != NULL) { + mrp_list_delete(&watch->hook); + mrp_del_io_watch(watch->mw); + mrp_free(watch); + } +} + + +static dbus_bool_t add_watch(DBusWatch *dw, void *data) +{ + dbus_glue_t *glue = (dbus_glue_t *)data; + watch_t *watch; + mrp_io_watch_t *mw; + mrp_io_event_t mask; + int fd; + unsigned int flags; + + if (!dbus_watch_get_enabled(dw)) + return TRUE; + + fd = dbus_watch_get_unix_fd(dw); + flags = dbus_watch_get_flags(dw); + mask = MRP_IO_EVENT_HUP | MRP_IO_EVENT_ERR; + + if (flags & DBUS_WATCH_READABLE) + mask |= MRP_IO_EVENT_IN; + if (flags & DBUS_WATCH_WRITABLE) + mask |= MRP_IO_EVENT_OUT; + + if ((watch = mrp_allocz(sizeof(*watch))) != NULL) { + mrp_list_init(&watch->hook); + mw = mrp_add_io_watch(glue->ml, fd, mask, dispatch_watch, watch); + + if (mw != NULL) { + watch->glue = glue; + watch->mw = mw; + watch->dw = dw; + dbus_watch_set_data(dw, watch, watch_freed_cb); + mrp_list_append(&glue->watches, &watch->hook); + + return TRUE; + } + else + mrp_free(watch); + } + + return FALSE; +} + + +static void del_watch(DBusWatch *dw, void *data) +{ + watch_t *watch = (watch_t *)dbus_watch_get_data(dw); + + MRP_UNUSED(data); + + if (watch != NULL) { + mrp_del_io_watch(watch->mw); + watch->mw = NULL; + } +} + + +static void toggle_watch(DBusWatch *dw, void *data) +{ + if (dbus_watch_get_enabled(dw)) + add_watch(dw, data); + else + del_watch(dw, data); +} + + +static void dispatch_timeout(mrp_timer_t *mt, void *user_data) +{ + timeout_t *timer = (timeout_t *)user_data; + + MRP_UNUSED(mt); + + dbus_timeout_handle(timer->dt); +} + + +static void timeout_freed_cb(void *data) +{ + timeout_t *timer = (timeout_t *)data; + + if (timer != NULL) { + mrp_list_delete(&timer->hook); + mrp_del_timer(timer->mt); + + mrp_free(timer); + } +} + + +static dbus_bool_t add_timeout(DBusTimeout *dt, void *data) +{ + dbus_glue_t *glue = (dbus_glue_t *)data; + timeout_t *timer; + mrp_timer_t *mt; + unsigned int msecs; + + if ((timer = mrp_allocz(sizeof(*timer))) != NULL) { + mrp_list_init(&timer->hook); + msecs = dbus_timeout_get_interval(dt); + mt = mrp_add_timer(glue->ml, msecs, dispatch_timeout, timer); + + if (mt != NULL) { + timer->glue = glue; + timer->mt = mt; + timer->dt = dt; + dbus_timeout_set_data(dt, timer, timeout_freed_cb); + mrp_list_append(&glue->timers, &timer->hook); + + return TRUE; + } + else + mrp_free(timer); + } + + return FALSE; +} + + +static void del_timeout(DBusTimeout *dt, void *data) +{ + timeout_t *timer = (timeout_t *)dbus_timeout_get_data(dt); + + MRP_UNUSED(data); + + if (timer != NULL) { + mrp_del_timer(timer->mt); + timer->mt = NULL; + } +} + + +static void toggle_timeout(DBusTimeout *dt, void *data) +{ + if (dbus_timeout_get_enabled(dt)) + add_timeout(dt, data); + else + del_timeout(dt, data); +} + + +static void wakeup_mainloop(void *data) +{ + dbus_glue_t *glue = (dbus_glue_t *)data; + + mrp_enable_deferred(glue->pump); +} + + +static void glue_free_cb(void *data) +{ + dbus_glue_t *glue = (dbus_glue_t *)data; + mrp_list_hook_t *p, *n; + watch_t *watch; + timeout_t *timer; + + mrp_list_foreach(&glue->watches, p, n) { + watch = mrp_list_entry(p, typeof(*watch), hook); + + mrp_list_delete(&watch->hook); + mrp_del_io_watch(watch->mw); + + mrp_free(watch); + } + + mrp_list_foreach(&glue->timers, p, n) { + timer = mrp_list_entry(p, typeof(*timer), hook); + + mrp_list_delete(&timer->hook); + mrp_del_timer(timer->mt); + + mrp_free(timer); + } + + mrp_free(glue); +} + + +static void pump_cb(mrp_deferred_t *d, void *user_data) +{ + dbus_glue_t *glue = (dbus_glue_t *)user_data; + + if (dbus_connection_dispatch(glue->conn) == DBUS_DISPATCH_COMPLETE) + mrp_disable_deferred(d); +} + + +static void dispatch_status_cb(DBusConnection *conn, DBusDispatchStatus status, + void *user_data) +{ + dbus_glue_t *glue = (dbus_glue_t *)user_data; + + MRP_UNUSED(conn); + + switch (status) { + case DBUS_DISPATCH_COMPLETE: + mrp_disable_deferred(glue->pump); + break; + + case DBUS_DISPATCH_DATA_REMAINS: + case DBUS_DISPATCH_NEED_MEMORY: + default: + mrp_enable_deferred(glue->pump); + break; + } +} + + +int mrp_setup_dbus_connection(mrp_mainloop_t *ml, DBusConnection *conn) +{ + dbus_glue_t *glue; + + if (!dbus_connection_allocate_data_slot(&data_slot)) + return FALSE; + + if (dbus_connection_get_data(conn, data_slot) != NULL) + return FALSE; + + if ((glue = mrp_allocz(sizeof(*glue))) != NULL) { + mrp_list_init(&glue->watches); + mrp_list_init(&glue->timers); + glue->pump = mrp_add_deferred(ml, pump_cb, glue); + + if (glue->pump == NULL) { + mrp_free(glue); + return FALSE; + } + + glue->ml = ml; + glue->conn = conn; + } + else + return FALSE; + + if (!dbus_connection_set_data(conn, data_slot, glue, glue_free_cb)) + return FALSE; + + dbus_connection_set_dispatch_status_function(conn, dispatch_status_cb, + glue, NULL); + + dbus_connection_set_wakeup_main_function(conn, wakeup_mainloop, + glue, NULL); + + return + dbus_connection_set_watch_functions(conn, add_watch, del_watch, + toggle_watch, glue, NULL) && + dbus_connection_set_timeout_functions(conn, add_timeout, del_timeout, + toggle_timeout, glue, NULL); +} + diff --git a/src/common/tests/dbus-sdbus-test.c b/src/common/tests/dbus-sdbus-test.c new file mode 100644 index 0000000..a7fcb86 --- /dev/null +++ b/src/common/tests/dbus-sdbus-test.c @@ -0,0 +1,563 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <netdb.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/socket.h> + +#define _GNU_SOURCE +#include <getopt.h> + +#include <murphy/common.h> +#include <murphy/common/dbus-sdbus.h> + +#define SERVER_NAME "org.test.murphy-server" +#define SERVER_PATH "/server" +#define SERVER_INTERFACE "Murphy.Server" +#define PING "ping" +#define CLIENT_NAME "org.test.murphy-client" +#define CLIENT_PATH "/client" +#define CLIENT_INTERFACE "Murphy.Client" +#define PONG "pong" + + +typedef struct { + char *busaddr; + char *srvname; + int server; + int log_mask; + const char *log_target; + mrp_mainloop_t *ml; + mrp_timer_t *timer; + uint32_t seqno; + mrp_dbus_t *dbus; + const char *name; + int32_t cid; + int server_up; + int all_pongs; +} context_t; + + +static mrp_dbus_msg_t *create_pong_signal(mrp_dbus_t *dbus, const char *dest, + uint32_t seq) +{ + const char *sig = "u"; + mrp_dbus_msg_t *msg; + + msg = mrp_dbus_msg_signal(dbus, dest, SERVER_PATH, SERVER_INTERFACE, PONG); + + if (msg != NULL) { + if (mrp_dbus_msg_open_container(msg, MRP_DBUS_TYPE_ARRAY, sig) && + mrp_dbus_msg_append_basic(msg, MRP_DBUS_TYPE_UINT32, &seq) && + mrp_dbus_msg_close_container(msg)) + return msg; + else + mrp_dbus_msg_unref(msg); + } + + return NULL; +} + + +static uint32_t parse_pong_signal(mrp_dbus_msg_t *msg) +{ + const char *sig = "u"; + uint32_t seq; + + if (mrp_dbus_msg_enter_container(msg, MRP_DBUS_TYPE_ARRAY, sig) && + mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_UINT32, &seq) && + mrp_dbus_msg_exit_container(msg)) + return seq; + else + return (uint32_t)-1; +} + + +static int ping_handler(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data) +{ + context_t *c = (context_t *)user_data; + mrp_dbus_msg_t *pong; + uint32_t seq; + const char *dest; + + MRP_UNUSED(c); + + if (mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_UINT32, &seq)) + mrp_log_info("-> ping request #%u", seq); + else + mrp_log_error("-> malformed ping request"); + + if (mrp_dbus_reply(dbus, msg, MRP_DBUS_TYPE_UINT32, &seq, + MRP_DBUS_TYPE_INVALID)) + mrp_log_info("<- ping reply #%u", seq); + else + mrp_log_error("Failed to send ping reply #%u.", seq); + + if (seq & 0x1) + dest = mrp_dbus_msg_sender(msg); + else + dest = NULL; + + if ((pong = create_pong_signal(dbus, dest, seq)) != NULL) { + if (mrp_dbus_send_msg(dbus, pong)) + mrp_log_info("<- pong %s #%u", dest ? "signal" : "broadcast", seq); + else + mrp_log_error("Failed to send pong signal #%u.", seq); + + mrp_dbus_msg_unref(pong); + } + else + mrp_log_error("Failed to create pong signal #%u.", seq); + + return TRUE; +} + + +static int name_owner_changed(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, + void *user_data) +{ + context_t *c = (context_t *)user_data; + const char *name, *prev, *next; + + MRP_UNUSED(c); + MRP_UNUSED(dbus); + + if (mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_STRING, &name) && + mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_STRING, &prev) && + mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_STRING, &next)) + mrp_log_info("Name %s was reassigned from %s to %s...", name, + prev, next); + else + mrp_log_error("Failed to parse NameOwnerChanged signal."); + + return TRUE; +} + + +static void server_setup(context_t *c) +{ + c->dbus = mrp_dbus_connect(c->ml, c->busaddr, NULL); + + if (c->dbus == NULL) { + mrp_log_error("Failed to create D-BUS connection to '%s' bus.", + c->busaddr); + exit(1); + } + + c->name = mrp_dbus_get_unique_name(c->dbus); + mrp_log_info("Our address is %s on the bus...", + c->name ? c->name : "unknown"); + + if (c->srvname && *c->srvname) { + if (!mrp_dbus_acquire_name(c->dbus, c->srvname, NULL)) { + mrp_log_error("Failed to acquire D-BUS name '%s' on bus '%s'.", + c->srvname, c->busaddr); + exit(1); + } + } + + if (!mrp_dbus_export_method(c->dbus, SERVER_PATH, SERVER_INTERFACE, + PING, ping_handler, c)) { + mrp_log_error("Failed to export D-BUS method '%s'.", PING); + exit(1); + } + + if (!mrp_dbus_subscribe_signal(c->dbus, name_owner_changed, c, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "NameOwnerChanged", + NULL)) { + mrp_log_error("Failed to subscribe to NameOwnerChanged signals."); + exit(1); + } +} + + +void server_cleanup(context_t *c) +{ + if (c->srvname && *c->srvname) + mrp_dbus_release_name(c->dbus, c->srvname, NULL); + + mrp_dbus_remove_method(c->dbus, SERVER_PATH, SERVER_INTERFACE, + PING, ping_handler, c); + mrp_dbus_unref(c->dbus); +} + + +static void ping_reply(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data) +{ + context_t *c = (context_t *)user_data; + uint32_t seq; + + MRP_UNUSED(dbus); + MRP_UNUSED(user_data); + + if (mrp_dbus_msg_type(msg) == MRP_DBUS_MESSAGE_TYPE_ERROR) { + mrp_log_error("Received errorping reply."); + + return; + } + + if (mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_UINT32, &seq)) + mrp_log_info("-> ping reply #%u", seq); + else + mrp_log_error("Received malformedping reply."); + + c->cid = 0; +} + + +static void ping_request(context_t *c) +{ + uint32_t seq; + + if (c->cid != 0) { + mrp_log_warning("Previous ping request still unanswered..."); + return; + } + + seq = c->seqno++; + c->cid = mrp_dbus_call(c->dbus, + c->srvname, SERVER_PATH, SERVER_INTERFACE, + PING, 500, ping_reply, c, + MRP_DBUS_TYPE_UINT32, &seq, + MRP_DBUS_TYPE_INVALID); + + if (c->cid > 0) + mrp_log_info("<- ping request #%u", seq); + else + mrp_log_warning("Failed to send ping request #%u.", seq); +} + + +static void send_cb(mrp_timer_t *t, void *user_data) +{ + context_t *c = (context_t *)user_data; + + MRP_UNUSED(t); + + ping_request(c); +} + + +static int pong_handler(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data) +{ + context_t *c = (context_t *)user_data; + uint32_t seq; + + MRP_UNUSED(c); + MRP_UNUSED(dbus); + + if ((seq = parse_pong_signal(msg)) != (uint32_t)-1) + mrp_log_info("-> pong signal #%u", seq); + else + mrp_log_error("-> malformed pong signal"); + + return TRUE; +} + + +static void server_status_cb(mrp_dbus_t *dbus, const char *name, int up, + const char *owner, void *user_data) +{ + context_t *c = (context_t *)user_data; + + MRP_UNUSED(dbus); + MRP_UNUSED(name); + + if (up) { + mrp_log_info("%s came up (as %s)", name, owner); + + if (c->timer == NULL) { + c->timer = mrp_add_timer(c->ml, 1000, send_cb, c); + + if (c->timer == NULL) { + mrp_log_error("Failed to create D-BUS sending timer."); + exit(1); + } + } + } + else { + mrp_log_info("%s went down", name); + + if (c->timer != NULL) { + mrp_del_timer(c->timer); + c->timer = NULL; + } + } +} + + +static void client_setup(context_t *c) +{ + const char *dest; + + c->dbus = mrp_dbus_connect(c->ml, c->busaddr, NULL); + + if (c->dbus == NULL) { + mrp_log_error("Failed to create D-BUS connection to '%s' bus.", + c->busaddr); + exit(1); + } + + c->name = mrp_dbus_get_unique_name(c->dbus); + mrp_log_info("Our address is %s on the bus...", + c->name ? c->name : "unknown"); + + mrp_dbus_follow_name(c->dbus, c->srvname, server_status_cb, c); + + if (c->all_pongs) { + mrp_log_info("Subscribing for all pong signals..."); + dest = NULL; + } + else { + mrp_log_info("Subscribing only for pong signals to us..."); + dest = c->name; + } + + if (!mrp_dbus_subscribe_signal(c->dbus, pong_handler, c, + dest, SERVER_PATH, SERVER_INTERFACE, + PONG, NULL)) { + mrp_log_error("Failed to subscribe for signal '%s/%s.%s'.", SERVER_PATH, + SERVER_INTERFACE, PONG); + exit(1); + } + + c->timer = mrp_add_timer(c->ml, 1000, send_cb, c); + + if (c->timer == NULL) { + mrp_log_error("Failed to create D-BUS sending timer."); + exit(1); + } +} + + +static void client_cleanup(context_t *c) +{ + mrp_dbus_forget_name(c->dbus, c->srvname, server_status_cb, c); + mrp_del_timer(c->timer); + mrp_dbus_unsubscribe_signal(c->dbus, pong_handler, c, + c->name, SERVER_PATH, SERVER_INTERFACE, + PONG, NULL); + mrp_dbus_unref(c->dbus); +} + + +static void print_usage(const char *argv0, int exit_code, const char *fmt, ...) +{ + va_list ap; + + if (fmt && *fmt) { + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + } + + printf("usage: %s [options]\n\n" + "The possible options are:\n" + " -s, --server run as test server (default)\n" + " -b, --bus connect the given D-BUS\n" + " If omitted, defaults to the session bus.\n" + " -a, --all-pongs subscribe for all pong signals\n" + " If omitted, only pong with the client address are handled.\n" + " -t, --log-target=TARGET log target to use\n" + " TARGET is one of stderr,stdout,syslog, or a logfile path\n" + " -l, --log-level=LEVELS logging level to use\n" + " LEVELS is a comma separated list of info, error and warning\n" + " -v, --verbose increase logging verbosity\n" + " -d, --debug site enable debug message for <site>\n" + " -h, --help show help on usage\n", + argv0); + + if (exit_code < 0) + return; + else + exit(exit_code); +} + + +static void config_set_defaults(context_t *ctx) +{ + mrp_clear(ctx); + ctx->busaddr = "session"; + ctx->srvname = SERVER_NAME; + ctx->server = FALSE; + ctx->log_mask = MRP_LOG_UPTO(MRP_LOG_DEBUG); + ctx->log_target = MRP_LOG_TO_STDERR; +} + + +int parse_cmdline(context_t *ctx, int argc, char **argv) +{ +# define OPTIONS "sab:n:l:t:vd:h" + struct option options[] = { + { "server" , no_argument , NULL, 's' }, + { "bus" , required_argument, NULL, 'b' }, + { "name" , required_argument, NULL, 'n' }, + { "all-pongs" , no_argument , NULL, 'a' }, + { "log-level" , required_argument, NULL, 'l' }, + { "log-target", required_argument, NULL, 't' }, + { "verbose" , optional_argument, NULL, 'v' }, + { "debug" , required_argument, NULL, 'd' }, + { "help" , no_argument , NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + + int opt; + + config_set_defaults(ctx); + + while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) { + switch (opt) { + case 's': + ctx->server = TRUE; + break; + + case 'b': + ctx->busaddr = optarg; + break; + + case 'n': + ctx->srvname = optarg; + break; + + case 'a': + ctx->all_pongs = TRUE; + break; + + case 'v': + ctx->log_mask <<= 1; + ctx->log_mask |= 1; + break; + + case 'l': + ctx->log_mask = mrp_log_parse_levels(optarg); + if (ctx->log_mask < 0) + print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg); + break; + + case 't': + ctx->log_target = mrp_log_parse_target(optarg); + if (!ctx->log_target) + print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg); + break; + + case 'd': + ctx->log_mask |= MRP_LOG_MASK_DEBUG; + mrp_debug_set_config(optarg); + mrp_debug_enable(TRUE); + break; + + case 'h': + print_usage(argv[0], -1, ""); + exit(0); + break; + + default: + print_usage(argv[0], EINVAL, "invalid option '%c'", opt); + } + } + + return TRUE; +} + + +static void signal_handler(mrp_sighandler_t *h, int signum, void *user_data) +{ + mrp_mainloop_t *ml = mrp_get_sighandler_mainloop(h); + context_t *c = (context_t *)user_data; + + MRP_UNUSED(c); + + switch (signum) { + case SIGINT: + mrp_log_info("Got SIGINT, stopping..."); + if (ml != NULL) + mrp_mainloop_quit(ml, 0); + else + exit(0); + break; + + case SIGTERM: + mrp_log_info("Got SIGTERM, stopping..."); + if (ml != NULL) + mrp_mainloop_quit(ml, 0); + else + exit(0); + break; + } +} + + +int main(int argc, char *argv[]) +{ + context_t c; + + mrp_clear(&c); + + if (!parse_cmdline(&c, argc, argv)) + exit(1); + + mrp_log_set_mask(c.log_mask); + mrp_log_set_target(c.log_target); + + if (c.server) + mrp_log_info("Running as server, using D-BUS '%s'...", c.busaddr); + else + mrp_log_info("Running as client, using D-BUS '%s'...", c.busaddr); + + c.ml = mrp_mainloop_create(); + + if (c.ml == NULL) { + mrp_log_error("Failed to create mainloop."); + exit(1); + } + + mrp_add_sighandler(c.ml, SIGINT , signal_handler, &c); + + if (c.server) + server_setup(&c); + else + client_setup(&c); + + mrp_mainloop_run(c.ml); + + if (c.server) + server_cleanup(&c); + else + client_cleanup(&c); + + return 0; +} diff --git a/src/common/tests/dbus-test.c b/src/common/tests/dbus-test.c new file mode 100644 index 0000000..e0ab062 --- /dev/null +++ b/src/common/tests/dbus-test.c @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <netdb.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/socket.h> + +#define _GNU_SOURCE +#include <getopt.h> + +#include <murphy/common.h> +#include <murphy/common/libdbus.h> + +#define SERVER_NAME "org.test.murphy-server" +#define SERVER_PATH "/server" +#define SERVER_INTERFACE "Murphy.Server" +#define PING "ping" +#define CLIENT_NAME "org.test.murphy-client" +#define CLIENT_PATH "/client" +#define CLIENT_INTERFACE "Murphy.Client" +#define PONG "pong" + + +typedef struct { + char *busaddr; + char *srvname; + int server; + int log_mask; + const char *log_target; + mrp_mainloop_t *ml; + mrp_timer_t *timer; + uint32_t seqno; + mrp_dbus_t *dbus; + const char *name; + int32_t cid; + int server_up; + int all_pongs; +} context_t; + + +static int ping_handler(mrp_dbus_t *dbus, DBusMessage *msg, void *user_data) +{ + context_t *c = (context_t *)user_data; + uint32_t seq; + const char *dest; + + MRP_UNUSED(c); + + if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_CALL && + dbus_message_get_args(msg, NULL, + DBUS_TYPE_UINT32, &seq, + DBUS_TYPE_INVALID)) + mrp_log_info("-> ping request #%u", seq); + else + mrp_log_error("-> malformed ping request"); + + if (!mrp_dbus_reply(dbus, msg, + DBUS_TYPE_UINT32, &seq, + DBUS_TYPE_INVALID)) + mrp_log_error("Failed to send ping reply #%u.", seq); + else + mrp_log_info("<- ping reply #%u", seq); + + if (seq & 0x1) + dest = dbus_message_get_sender(msg); + else + dest = NULL; + + if (!mrp_dbus_signal(dbus, dest, SERVER_PATH, SERVER_INTERFACE, PONG, + DBUS_TYPE_UINT32, &seq, + DBUS_TYPE_INVALID)) + mrp_log_error("Failed to send pong signal #%u.", seq); + else + mrp_log_info("<- pong %s #%u", dest ? "signal" : "broadcast", seq); + + return TRUE; +} + + +static void server_setup(context_t *c) +{ + c->dbus = mrp_dbus_connect(c->ml, c->busaddr, NULL); + + if (c->dbus == NULL) { + mrp_log_error("Failed to create D-BUS connection to '%s' bus.", + c->busaddr); + exit(1); + } + + c->name = mrp_dbus_get_unique_name(c->dbus); + mrp_log_info("Our address is %s on the bus...", + c->name ? c->name : "unknown"); + + if (c->srvname && *c->srvname) { + if (!mrp_dbus_acquire_name(c->dbus, c->srvname, NULL)) { + mrp_log_error("Failed to acquire D-BUS name '%s' on bus '%s'.", + c->srvname, c->busaddr); + exit(1); + } + } + + if (!mrp_dbus_export_method(c->dbus, SERVER_PATH, SERVER_INTERFACE, + PING, ping_handler, c)) { + mrp_log_error("Failed to export D-BUS method '%s'.", PING); + exit(1); + } +} + + +void server_cleanup(context_t *c) +{ + if (c->srvname && *c->srvname) + mrp_dbus_release_name(c->dbus, c->srvname, NULL); + mrp_dbus_remove_method(c->dbus, SERVER_PATH, SERVER_INTERFACE, + PING, ping_handler, c); + mrp_dbus_unref(c->dbus); +} + + +static void ping_reply(mrp_dbus_t *dbus, DBusMessage *msg, void *user_data) +{ + context_t *c = (context_t *)user_data; + uint32_t seq; + + MRP_UNUSED(dbus); + MRP_UNUSED(user_data); + + if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_ERROR) { + const char *ename, *emsg; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &ename, + DBUS_TYPE_STRING, &emsg, + DBUS_TYPE_INVALID)) { + ename = "<unknown>"; + emsg = "<unknown>"; + } + + mrp_log_error("Received error reply (%s, %s) to ping.", ename, emsg); + + c->cid = 0; + return; + } + + if (dbus_message_get_args(msg, NULL, + DBUS_TYPE_UINT32, &seq, + DBUS_TYPE_INVALID)) + mrp_log_info("-> ping reply #%u", seq); + else + mrp_log_error("Received malformed ping reply."); + + c->cid = 0; +} + + +static void ping_request(context_t *c) +{ + uint32_t seq; + + if (c->cid != 0) { + mrp_log_warning("Previous ping request still unanswered..."); + return; + } + + seq = c->seqno++; + c->cid = mrp_dbus_call(c->dbus, + c->srvname, SERVER_PATH, SERVER_INTERFACE, + PING, 500, ping_reply, c, + DBUS_TYPE_UINT32, &seq, + DBUS_TYPE_INVALID); + + if (c->cid > 0) + mrp_log_info("<- ping request #%u", seq); + else + mrp_log_warning("Failed to send ping request #%u.", seq); +} + + +static void send_cb(mrp_timer_t *t, void *user_data) +{ + context_t *c = (context_t *)user_data; + + MRP_UNUSED(t); + + ping_request(c); +} + + +static int pong_handler(mrp_dbus_t *dbus, DBusMessage *msg, void *user_data) +{ + context_t *c = (context_t *)user_data; + uint32_t seq; + + MRP_UNUSED(c); + MRP_UNUSED(dbus); + + if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_SIGNAL && + dbus_message_get_args(msg, NULL, + DBUS_TYPE_UINT32, &seq, + DBUS_TYPE_INVALID)) + mrp_log_info("-> pong signal #%u", seq); + else + mrp_log_error("-> malformed pong signal"); + + return TRUE; +} + + +static void server_status_cb(mrp_dbus_t *dbus, const char *name, int up, + const char *owner, void *user_data) +{ + context_t *c = (context_t *)user_data; + + MRP_UNUSED(dbus); + MRP_UNUSED(name); + + if (up) { + mrp_log_info("%s came up (as %s)", name, owner); + + if (c->timer == NULL) { + c->timer = mrp_add_timer(c->ml, 1000, send_cb, c); + + if (c->timer == NULL) { + mrp_log_error("Failed to create D-BUS sending timer."); + exit(1); + } + } + } + else { + mrp_log_info("%s went down", name); + + if (c->timer != NULL) { + mrp_del_timer(c->timer); + c->timer = NULL; + } + } +} + + +static void client_setup(context_t *c) +{ + const char *dest; + + c->dbus = mrp_dbus_connect(c->ml, c->busaddr, NULL); + + if (c->dbus == NULL) { + mrp_log_error("Failed to create D-BUS connection to '%s' bus.", + c->busaddr); + exit(1); + } + + c->name = mrp_dbus_get_unique_name(c->dbus); + mrp_log_info("Our address is %s on the bus...", + c->name ? c->name : "unknown"); + + mrp_dbus_follow_name(c->dbus, c->srvname, server_status_cb, c); + + if (c->all_pongs) { + mrp_log_info("Subscribing for all pong signals..."); + dest = NULL; + } + else { + mrp_log_info("Subscribing only for pong signals to us..."); + dest = c->name; + } + + if (!mrp_dbus_subscribe_signal(c->dbus, pong_handler, c, + dest, SERVER_PATH, SERVER_INTERFACE, + PONG, NULL)) { + mrp_log_error("Failed to subscribe for signal '%s/%s.%s'.", SERVER_PATH, + SERVER_INTERFACE, PONG); + exit(1); + } + + c->timer = mrp_add_timer(c->ml, 1000, send_cb, c); + + if (c->timer == NULL) { + mrp_log_error("Failed to create D-BUS sending timer."); + exit(1); + } +} + + +static void client_cleanup(context_t *c) +{ + mrp_dbus_follow_name(c->dbus, c->srvname, server_status_cb, c); + mrp_del_timer(c->timer); + mrp_dbus_subscribe_signal(c->dbus, pong_handler, c, + c->name, SERVER_PATH, SERVER_INTERFACE, + PONG, NULL); + mrp_dbus_unref(c->dbus); +} + + +static void print_usage(const char *argv0, int exit_code, const char *fmt, ...) +{ + va_list ap; + + if (fmt && *fmt) { + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + } + + printf("usage: %s [options]\n\n" + "The possible options are:\n" + " -s, --server run as test server (default)\n" + " -b, --bus connect the given D-BUS\n" + " If omitted, defaults to the session bus.\n" + " -a, --all-pongs subscribe for all pong signals\n" + " If omitted, only pong with the client address are handled.\n" + " -t, --log-target=TARGET log target to use\n" + " TARGET is one of stderr,stdout,syslog, or a logfile path\n" + " -l, --log-level=LEVELS logging level to use\n" + " LEVELS is a comma separated list of info, error and warning\n" + " -v, --verbose increase logging verbosity\n" + " -d, --debug enable debug messages\n" + " -h, --help show help on usage\n", + argv0); + + if (exit_code < 0) + return; + else + exit(exit_code); +} + + +static void config_set_defaults(context_t *ctx) +{ + mrp_clear(ctx); + ctx->busaddr = "session"; + ctx->srvname = SERVER_NAME; + ctx->server = FALSE; + ctx->log_mask = MRP_LOG_UPTO(MRP_LOG_DEBUG); + ctx->log_target = MRP_LOG_TO_STDERR; +} + + +int parse_cmdline(context_t *ctx, int argc, char **argv) +{ +# define OPTIONS "sab:n:l:t:vdh" + struct option options[] = { + { "server" , no_argument , NULL, 's' }, + { "bus" , required_argument, NULL, 'b' }, + { "name" , required_argument, NULL, 'n' }, + { "all-pongs" , no_argument , NULL, 'a' }, + { "log-level" , required_argument, NULL, 'l' }, + { "log-target", required_argument, NULL, 't' }, + { "verbose" , optional_argument, NULL, 'v' }, + { "debug" , no_argument , NULL, 'd' }, + { "help" , no_argument , NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + + int opt, debug; + + debug = FALSE; + config_set_defaults(ctx); + + while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) { + switch (opt) { + case 's': + ctx->server = TRUE; + break; + + case 'b': + ctx->busaddr = optarg; + break; + + case 'n': + ctx->srvname = optarg; + break; + + case 'a': + ctx->all_pongs = TRUE; + break; + + case 'v': + ctx->log_mask <<= 1; + ctx->log_mask |= 1; + break; + + case 'l': + ctx->log_mask = mrp_log_parse_levels(optarg); + if (ctx->log_mask < 0) + print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg); + break; + + case 't': + ctx->log_target = mrp_log_parse_target(optarg); + if (!ctx->log_target) + print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg); + break; + + case 'd': + debug = TRUE; + break; + + case 'h': + print_usage(argv[0], -1, ""); + exit(0); + break; + + default: + print_usage(argv[0], EINVAL, "invalid option '%c'", opt); + } + } + + if (debug) + ctx->log_mask |= MRP_LOG_MASK_DEBUG; + + return TRUE; +} + + +static void signal_handler(mrp_sighandler_t *h, int signum, void *user_data) +{ + mrp_mainloop_t *ml = mrp_get_sighandler_mainloop(h); + context_t *c = (context_t *)user_data; + + MRP_UNUSED(c); + + switch (signum) { + case SIGINT: + mrp_log_info("Got SIGINT, stopping..."); + if (ml != NULL) + mrp_mainloop_quit(ml, 0); + else + exit(0); + break; + + case SIGTERM: + mrp_log_info("Got SIGTERM, stopping..."); + if (ml != NULL) + mrp_mainloop_quit(ml, 0); + else + exit(0); + break; + } +} + + +int main(int argc, char *argv[]) +{ + context_t c; + + mrp_clear(&c); + + if (!parse_cmdline(&c, argc, argv)) + exit(1); + + mrp_log_set_mask(c.log_mask); + mrp_log_set_target(c.log_target); + + if (c.server) + mrp_log_info("Running as server, using D-BUS '%s'...", c.busaddr); + else + mrp_log_info("Running as client, using D-BUS '%s'...", c.busaddr); + + c.ml = mrp_mainloop_create(); + + if (c.ml == NULL) { + mrp_log_error("Failed to create mainloop."); + exit(1); + } + + mrp_add_sighandler(c.ml, SIGINT , signal_handler, &c); + + if (c.server) + server_setup(&c); + else + client_setup(&c); + + mrp_mainloop_run(c.ml); + + if (c.server) + server_cleanup(&c); + else + client_cleanup(&c); + + return 0; +} diff --git a/src/common/tests/fragbuf-test.c b/src/common/tests/fragbuf-test.c new file mode 100644 index 0000000..4da9a34 --- /dev/null +++ b/src/common/tests/fragbuf-test.c @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <errno.h> +#include <getopt.h> + +#include <murphy/common/mm.h> +#include <murphy/common/log.h> +#include <murphy/common/fragbuf.h> + + +#define fatal(fmt, args...) do { \ + mrp_log_error(fmt, ## args); \ + exit(1); \ + } while (0) + + +typedef struct { + int log_mask; + const char *log_target; + int framed; +} context_t; + +context_t ctx; + +void check_message(void *data, size_t size, char **messages, + int *chk, int *offs) +{ + char *p, *d; + int l; + + if (ctx.framed) { + if (!strncmp(messages[*chk], data, size) && !messages[*chk][size]) + mrp_debug("message check: OK"); + else + fatal("message check: failed"); + + *chk += 1; + } + else { + d = data; + while (size > 0) { + p = messages[*chk] + *offs; + l = strlen(p); + + if (l > (int)size) + l = (int)size; + + if (strncmp(p, d, l)) + fatal("message check: failed"); + + *offs += l; + size -= l; + d += l; + + if (messages[*chk][*offs] == '\0') { + *chk += 1; + *offs = 0; + } + } + mrp_debug("message check: OK"); + } +} + + +void dump_buffer(mrp_fragbuf_t *buf, char **messages, int *chk, int *offs) +{ + void *data; + size_t size; + int cnt; + + data = NULL; + size = 0; + cnt = 0; + + while (mrp_fragbuf_pull(buf, &data, &size)) { + mrp_log_info("got message: (%zd bytes) [%*.*s]", size, + (int)size, (int)size, (char *)data); + + check_message(data, size, messages, chk, offs); + + cnt++; + } + + if (!cnt) + mrp_debug("no full messages in buffer"); + else + mrp_debug("pulled %d messages from buffer...", cnt); +} + + +int test(mrp_fragbuf_t *buf, size_t *chunks, int dump_interval) +{ + char *messages[] = { + "Ticking away the moments", + "That make up a dull day", + "Fritter and waste the hours", + "In an off-hand way", + "Kicking around on a piece of ground", + "In your home town", + "Waiting for someone or something", + "To show you the way", + "Tired of lying in the sunshine", + "Staying home to watch the rain", + "You are young and life is long", + "And there is time to kill today", + "And then the one day you find", + "Ten years have got behind you", + "No one told you when to run", + "You missed the starting gun", + "And you run and you run", + "To catch up with the sun", + "But it's sinking", + "Racing around", + "To come up behind you again", + "The sun is the same", + "In a relative way", + "But you're older", + "Shorter of breath", + "And one day closer to death", + "Every year is getting shorter", + "Never seem to find the time", + "Plans that either come to naught", + "Or half a page of scribbled lines", + "Hanging on in quiet desperation", + "Is the English way", + "The time is gone", + "The song is over", + "Thought I'd something more to say", + "Home", + "Home again", + "I like to be here", + "When I can", + "When I come home", + "Cold and tired", + "It's good to warm my bones", + "Beside the fire", + "Far away", + "Across the field", + "Tolling on the iron bell", + "Calls the faithful to their knees", + "To hear the softly spoken magic spell...", + "test #1", + "test #2", + "this is a test #3", + "message #4", + "message #5", + "test message #6", + "a test #7", + "the quick brown (#8)", + "fox (#9)", + "jumps over the (#10)", + "lazy dog (#11)", + "this is another test message (#12)", + "and here is one more for you (#13)", + "foo (#14)", + "bar (#15)", + "foobar (#16)", + "barfoo (#17)", + "xyzzykukkuluuruu (#18)" + }; + + char *msg, *p; + uint32_t size, nbo_size; + size_t n, total; + int dump, chk, offs, i, j; + + dump = chk = offs = 0; + + for (i = 0; i < (int)MRP_ARRAY_SIZE(messages); i++) { + msg = messages[i]; + size = strlen(msg); + + total = 0; + p = msg; + + if (ctx.framed) { + nbo_size = htobe32(size); + if (!mrp_fragbuf_push(buf, &nbo_size, sizeof(nbo_size))) + fatal("failed to push message size to buffer"); + } + + for (j = 0; *p != '\0'; j++) { + if (!chunks[j]) + j = 0; + n = chunks[j]; + if (n > strlen(p)) + n = strlen(p); + + mrp_debug("pushing %zd bytes (%*.*s)...", n, (int)n, (int)n, p); + + if (!mrp_fragbuf_push(buf, p, n)) + fatal("failed to push %*.*s to buffer", (int)n, (int)n, p); + + p += n; + total += n; + + dump++; + + if (!dump_interval || + (dump_interval > 0 && !(dump % dump_interval))) + dump_buffer(buf, messages, &chk, &offs); + } + + if (dump_interval < -1) { + if (i && !(i % -dump_interval)) + dump_buffer(buf, messages, &chk, &offs); + } + } + + dump_buffer(buf, messages, &chk, &offs); + + return TRUE; +} + + +static void print_usage(const char *argv0, int exit_code, const char *fmt, ...) +{ + va_list ap; + + if (fmt && *fmt) { + va_start(ap, fmt); + vprintf(fmt, ap); + printf("\n"); + va_end(ap); + } + + printf("usage: %s [options]\n\n" + "The possible options are:\n" + " -t, --log-target=TARGET log target to use\n" + " TARGET is one of stderr,stdout,syslog, or a logfile path\n" + " -l, --log-level=LEVELS logging level to use\n" + " LEVELS is a comma separated list of info, error and warning\n" + " -v, --verbose increase logging verbosity\n" + " -d, --debug enable debug messages\n" + " -n, --non-framed set buffer to non-framed mode\n" + " -h, --help show help on usage\n", + argv0); + + if (exit_code < 0) + return; + else + exit(exit_code); +} + + +static void config_set_defaults(void) +{ + mrp_clear(&ctx); + ctx.log_mask = MRP_LOG_UPTO(MRP_LOG_INFO); + ctx.log_target = MRP_LOG_TO_STDOUT; + ctx.framed = TRUE; +} + + +void parse_cmdline(int argc, char **argv) +{ +# define OPTIONS "l:t:vd:nh" + struct option options[] = { + { "log-level" , required_argument, NULL, 'l' }, + { "log-target", required_argument, NULL, 't' }, + { "verbose" , optional_argument, NULL, 'v' }, + { "debug" , required_argument, NULL, 'd' }, + { "non-framed", no_argument , NULL, 'n' }, + { "help" , no_argument , NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + + int opt; + + config_set_defaults(); + + while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) { + switch (opt) { + case 'v': + ctx.log_mask <<= 1; + ctx.log_mask |= 1; + break; + + case 'l': + ctx.log_mask = mrp_log_parse_levels(optarg); + if (ctx.log_mask < 0) + print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg); + break; + + case 't': + ctx.log_target = mrp_log_parse_target(optarg); + if (!ctx.log_target) + print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg); + break; + + case 'd': + ctx.log_mask |= MRP_LOG_MASK_DEBUG; + mrp_debug_set_config(optarg); + mrp_debug_enable(TRUE); + break; + + case'n': + ctx.framed = FALSE; + break; + + case 'h': + print_usage(argv[0], -1, ""); + exit(0); + break; + + case '?': + if (opterr) + print_usage(argv[0], EINVAL, ""); + break; + + default: + print_usage(argv[0], EINVAL, "invalid option '%c'", opt); + } + } +} + +int main(int argc, char *argv[]) +{ + mrp_fragbuf_t *buf; + size_t chunkstbl[][8] = { + { 3, 1, 2, 3, 5, 0, 0, 0 }, + { 1, 2, 3, 4, 3, 2, 1, 0 }, + { 1, 5, 3, 4, 2, 1, 1, 0 }, + { 4, 3, 2, 1, 2, 3, 4, 0 }, + }; + size_t *chunks; + size_t single[] = { 1, 0 }; + int intervals[] = { 1, 2, 3, 4, 5, 0, -1 }; + int i, j, interval; + + parse_cmdline(argc, argv); + + mrp_log_set_mask(ctx.log_mask); + mrp_log_set_target(ctx.log_target); + + buf = mrp_fragbuf_create(ctx.framed, 0); + + if (buf == NULL) + fatal("failed to create data collecting buffer"); + + for (i = 0; i < (int)MRP_ARRAY_SIZE(intervals); i++) { + interval = intervals[i]; + for (j = 0; j < (int)MRP_ARRAY_SIZE(chunkstbl); j++) { + chunks = &chunkstbl[j][0]; + mrp_log_info("testing with interval %d, chunks #%d", interval, j); + test(buf, chunks, interval); + test(buf, single, interval); + mrp_log_info("testing with interval %d, chunks #%d", -i -2, j); + test(buf, chunks, -i - 2); + test(buf, single, -i - 2); + } + } + + mrp_fragbuf_destroy(buf); + + return 0; +} diff --git a/src/common/tests/glib-pump.c b/src/common/tests/glib-pump.c new file mode 100644 index 0000000..ece07ff --- /dev/null +++ b/src/common/tests/glib-pump.c @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <glib.h> + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/log.h> +#include <murphy/common/mainloop.h> + + +/* + * A simple glue layer to pump GMainLoop from mrp_mainloop_t. This + * will pretty much be turned into a murphy plugin as such... + */ + + +typedef struct { + GMainLoop *ml; + GMainContext *mc; + gint maxprio; + mrp_subloop_t *sl; +} glib_glue_t; + +static glib_glue_t *glib_glue; + + +static int glib_prepare(void *user_data) +{ + glib_glue_t *glue = (glib_glue_t *)user_data; + + return g_main_context_prepare(glue->mc, &glue->maxprio); +} + + +static int glib_query(void *user_data, struct pollfd *fds, int nfd, + int *timeout) +{ + glib_glue_t *glue = (glib_glue_t *)user_data; + + return g_main_context_query(glue->mc, glue->maxprio, timeout, + (GPollFD *)fds, nfd); +} + + +static int glib_check(void *user_data, struct pollfd *fds, int nfd) +{ + glib_glue_t *glue = (glib_glue_t *)user_data; + + return g_main_context_check(glue->mc, glue->maxprio, (GPollFD *)fds, nfd); + +} + + +static void glib_dispatch(void *user_data) +{ + glib_glue_t *glue = (glib_glue_t *)user_data; + + g_main_context_dispatch(glue->mc); + +} + + +static int glib_pump_setup(mrp_mainloop_t *ml) +{ + static mrp_subloop_ops_t glib_ops = { + .prepare = glib_prepare, + .query = glib_query, + .check = glib_check, + .dispatch = glib_dispatch + }; + + GMainContext *main_context; + GMainLoop *main_loop; + + if (sizeof(GPollFD) != sizeof(struct pollfd)) { + mrp_log_error("sizeof(GPollFD:%zd) != sizeof(struct pollfd:%zd)\n", + sizeof(GPollFD), sizeof(struct pollfd)); + return FALSE; + } + + main_context = NULL; + main_loop = NULL; + glib_glue = NULL; + + if ((main_context = g_main_context_default()) != NULL && + (main_loop = g_main_loop_new(main_context, FALSE)) != NULL && + (glib_glue = mrp_allocz(sizeof(*glib_glue))) != NULL) { + + glib_glue->mc = main_context; + glib_glue->ml = main_loop; + glib_glue->sl = mrp_add_subloop(ml, &glib_ops, glib_glue); + + if (glib_glue->sl != NULL) + return TRUE; + else + mrp_log_error("glib-pump failed to register subloop."); + } + + /* all of these handle a NULL argument gracefully... */ + g_main_loop_unref(main_loop); + g_main_context_unref(main_context); + + mrp_free(glib_glue); + glib_glue = NULL; + + return FALSE; +} + + +static void glib_pump_cleanup(void) +{ + if (glib_glue != NULL) { + mrp_del_subloop(glib_glue->sl); + + g_main_loop_unref(glib_glue->ml); + g_main_context_unref(glib_glue->mc); + + mrp_free(glib_glue); + glib_glue = NULL; + } +} + diff --git a/src/common/tests/hash-test.c b/src/common/tests/hash-test.c new file mode 100644 index 0000000..6195775 --- /dev/null +++ b/src/common/tests/hash-test.c @@ -0,0 +1,419 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> + +#include <murphy/common/mm.h> +#include <murphy/common/list.h> +#include <murphy/common/macros.h> +#include <murphy/common/hashtbl.h> + +#define MEMBER_OFFSET MRP_OFFSET +#define ALLOC_ARR(type, n) mrp_allocz(sizeof(type) * (n)) +#define FREE mrp_free +#define STRDUP mrp_strdup + +#define hash_tbl_t mrp_htbl_t +#define hash_tbl_cfg_t mrp_htbl_config_t +#define hash_tbl_create mrp_htbl_create +#define hash_tbl_delete mrp_htbl_destroy +#define hash_tbl_add mrp_htbl_insert +#define hash_tbl_del mrp_htbl_remove +#define hash_tbl_lookup mrp_htbl_lookup + +#define list_hook_t mrp_list_hook_t +#define list_init mrp_list_init +#define list_append mrp_list_append +#define list_delete mrp_list_delete + +#define NKEY 4 +#define NPHASE 0xff + +#define INFO(fmt, args...) do { \ + printf("[%s] "fmt"\n" , __FUNCTION__ , ## args); \ + fflush(stdout); \ + } while (0) + +#define ERROR(fmt, args...) do { \ + printf("[%s] error: "fmt"\n" , __FUNCTION__, ## args); \ + fflush(stdout); \ + } while (0) + +#define FATAL(fmt, args...) do { \ + printf("[%s] fatal error: "fmt"\n" , __FUNCTION__, ## args); \ + fflush(stdout); \ + exit(1); \ + } while (0) + +#define MKSTR(fmt, args...) ({ \ + char *_ptr, _buf[64] = ""; \ + snprintf(_buf, sizeof(_buf), fmt , ## args); \ + _ptr = STRDUP(_buf); \ + _ptr; }) + +#define ENTRY_KEY(entry, idx) ({ \ + char *_key; \ + switch ((idx)) { \ + case 0: _key = (entry)->str1; break; \ + case 1: _key = (entry)->str2; break; \ + case 2: _key = (entry)->str3; break; \ + case 3: _key = (entry)->str4; break; \ + default: FATAL("invalid key idx %d", (idx)); \ + } \ + _key; }) + +#define PATTERN_BIT(pattern, idx) \ + (pattern & (1 << ((idx) & ((sizeof(pattern) * 8) - 1)))) + +typedef struct { + char *str1; + int int1; + char *str2; + list_hook_t hook; + char *str3; + int int2; + char *str4; +} entry_t; + + +typedef struct { + hash_tbl_t *ht; + size_t size; + + entry_t *entries; + int nentry; + + int keyidx; + uint32_t pattern; +} test_t; + + +test_t test; + +void +populate(void) +{ + entry_t *entry; + char *key; + int i; + + INFO("populating..."); + + for (i = 0, entry = test.entries; i < test.nentry; i++, entry++) { + key = ENTRY_KEY(entry, test.keyidx); + + if (hash_tbl_add(test.ht, key, entry)) + INFO("hashed in entry '%s'", key); + else + FATAL("failed to hash in entry '%s'", key); + } + + INFO("done."); +} + + +void +evict(void) +{ + entry_t *entry, *found; + char *key; + int i; + + INFO("evicting..."); + + for (i = 0, entry = test.entries; i < test.nentry; i++, entry++) { + if (PATTERN_BIT(test.pattern, i)) { + key = ENTRY_KEY(entry, test.keyidx); + found = hash_tbl_del(test.ht, key, FALSE); + + if (found != entry) + FATAL("expected entry to delete '%s' not found (%p != %p)", + key, found, entry); + + INFO("removed entry '%s' (%p)", key, found); + } + } + + INFO("done."); +} + + +void +readd(void) +{ + entry_t *entry, *found; + char *key; + int i; + + INFO("re-adding..."); + + for (i = 0, entry = test.entries; i < test.nentry; i++, entry++) { + if (PATTERN_BIT(test.pattern, i)) { + key = ENTRY_KEY(entry, test.keyidx); + found = hash_tbl_lookup(test.ht, key); + + if (found != NULL) + FATAL("unexpected entry to re-add '%s' found (%p)", key, found); + + if (!hash_tbl_add(test.ht, key, entry)) + FATAL("failed to re-add entry '%s'", key); + + INFO("re-added entry '%s'", key); + } + } + + INFO("done."); +} + + +void +check(void) +{ + entry_t *entry, *found; + char *key; + int i; + + INFO("checking..."); + + for (i = 0, entry = test.entries; i < test.nentry; i++, entry++) { + key = ENTRY_KEY(entry, test.keyidx); + found = hash_tbl_lookup(test.ht, key); + + if (!PATTERN_BIT(test.pattern, i)) { + if (found != entry) + FATAL("expected entry '%s' not found (%p != %p)", + key, found, entry); + } + else { + if (found != NULL) + FATAL("unexpected entry '%s' found", key); + } + } + + INFO("done."); +} + + +void +empty_cb(char *key, entry_t *entry, void *data) +{ + (void)data; + + FATAL("unexpected entry %p (%s) in hash table", entry, key); +} + + +void +reset(void) +{ + entry_t *entry, *found; + char *key; + int i; + + INFO("resetting..."); + + for (i = 0, entry = test.entries; i < test.nentry; i++, entry++) { + key = ENTRY_KEY(entry, test.keyidx); + found = hash_tbl_del(test.ht, key, FALSE); + + if (found != entry) + FATAL("expected entry %s not found (%p != %p)", + key, found, entry); + + INFO("removed entry '%s' (%p)", key, found); + } + + INFO("done."); +} + + +unsigned int hash_func(const void *key) +{ + unsigned int h; + const char *p; + + for (h = 0, p = key; *p; p++) { + h <<= 1; + h ^= *p; + } + + return h; +} + + +int cmp_func(const void *key1, const void *key2) +{ + return strcmp(key1, key2); +} + + +void +test_init(void) +{ + int i; + entry_t *entry; + + INFO("setting up tests..."); + + if ((test.entries = ALLOC_ARR(entry_t, test.nentry)) == NULL) + FATAL("failed to allocate test set"); + + for (i = 0, entry = test.entries; i < test.nentry; i++, entry++) { + list_init(&entry->hook); + + entry->str1 = MKSTR("entry-string-%d:1", i); + entry->int1 = i; + entry->str2 = MKSTR("entry-string-%d:2", i); + entry->str3 = MKSTR("entry-string-%d:3", i); + entry->int2 = i * 2; + entry->str4 = MKSTR("entry-string-%d:4", i); + + if (!entry->str1 || !entry->str2 || !entry->str3 || !entry->str4) + FATAL("failed to initialize test set"); + } + + INFO("test setup done."); +} + + +void +test_exit(void) +{ + entry_t *entry; + int i; + + INFO("cleaning up tests..."); + + for (i = 0, entry = test.entries; i < test.nentry; i++, entry++) { + FREE(entry->str1); + FREE(entry->str2); + FREE(entry->str3); + FREE(entry->str4); + } + + FREE(test.entries); + + test.entries = NULL; + test.nentry = 0; + + INFO("test cleanup done."); +} + + +void +test_run(void) +{ + hash_tbl_cfg_t cfg; + entry_t *entry; + int i, j; + + + /* + * Create a hash table, run a test loop consisting of + * + * 1) populate table + * 2) selectively remove entries + * 3) check the table + * 4) check the entries (for corruption) + * 5) reset the table + * + * then delete the hash table + */ + + cfg.nbucket = test.size / 4; + cfg.hash = hash_func; + cfg.comp = cmp_func; + cfg.free = NULL; + test.ht = hash_tbl_create(&cfg); + + if (test.ht == NULL) + FATAL("failed to create hash table (#%d, size %zd)", + test.keyidx, test.size); + + for (i = 0, entry = test.entries; i < test.nentry; i++, entry++) { + populate(); + + test.pattern = 0; + for (j = 0; j < NPHASE; j++) { + INFO("Running test phase #%d...", j); + + evict(); + check(); + readd(); + + test.pattern++; + + INFO("done."); + } + + reset(); + } + + hash_tbl_delete(test.ht, FALSE); + test.ht = NULL; +} + + +int +main(int argc, char *argv[]) +{ + int i; + + memset(&test, 0, sizeof(test)); + + if (argc < 2 || (test.nentry = (int)strtoul(argv[1], NULL, 10)) <= 16) + test.nentry = 16; + + test_init(); + + for (i = 0; i < NKEY; i++) { + test.keyidx = i; + test.size = test.nentry; test_run(); + test.size = test.nentry / 2; test_run(); + test.size = test.nentry / 4; test_run(); + } + + test_exit(); + + return 0; +} + + +/* + * Local Variables: + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + * vim:set expandtab shiftwidth=4: + */ + diff --git a/src/common/tests/hash12-test.c b/src/common/tests/hash12-test.c new file mode 100644 index 0000000..667c212 --- /dev/null +++ b/src/common/tests/hash12-test.c @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2014, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdbool.h> +#include <murphy/common.h> + +#define LE_STRING "/org/murphy/resource/0/%d" + +// Placeholder structure +typedef struct { + void *pointer; +} test_object; + +static void htbl_free_test_object(void *key, void *object) { + test_object *obj = object; + + if (key) + mrp_free(key); + + if (obj) + mrp_free(obj); +} + +int main(int argc, char *argv[]) { + mrp_htbl_config_t cfg; + + mrp_htbl_t *table = NULL; + test_object *object = NULL; + + char *string = NULL; + size_t string_size = 0; + int written_count = 0; + + MRP_UNUSED(argc); + MRP_UNUSED(argv); + + cfg.comp = mrp_string_comp; + cfg.hash = mrp_string_hash; + cfg.free = htbl_free_test_object; + cfg.nbucket = 0; // nentry/4 -> smaller than min -> 8 by def + cfg.nentry = 10; + + table = mrp_htbl_create(&cfg); + if (!table) { + printf("blergh @Â creating initial hash table\n"); + return 1; + } + + // broken range: 12 - 66 + for (int i = 0; i < 12; i++) { + object = mrp_allocz(sizeof(test_object)); + if (!object) { + printf("blergh @Â allocating object %d\n", i); + return 1; + } + // allocz should handle this, but let's just have a test value written there + object->pointer = NULL; + + string_size = snprintf(NULL, 0, LE_STRING, i); + if (!string_size) { + printf("blergh @Â calculating string %d size\n", i); + return 1; + } + // we need the null character as well + string_size++; + + string = mrp_allocz(string_size); + if (!string) { + printf("blergh @Â allocating string %d\n", i); + return 1; + } + + written_count = snprintf(string, string_size, LE_STRING, i); + if (written_count <= 0 || written_count + 1 < (int)string_size) { + printf("blergh @Â writing string %d\n", i); + return 1; + } + + mrp_htbl_insert(table, string, object); + mrp_htbl_remove(table, string, TRUE); + } + + mrp_htbl_destroy(table, TRUE); + printf("Successfully finished the test\n"); + return 0; +} diff --git a/src/common/tests/internal-transport-test.c b/src/common/tests/internal-transport-test.c new file mode 100644 index 0000000..e4fa131 --- /dev/null +++ b/src/common/tests/internal-transport-test.c @@ -0,0 +1,784 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <netdb.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/socket.h> + +#define _GNU_SOURCE +#include <getopt.h> + +#include <murphy/common.h> + + +/* + * tags for generic message fields + */ + +#define TAG_SEQ ((uint16_t)0x1) +#define TAG_MSG ((uint16_t)0x2) +#define TAG_U8 ((uint16_t)0x3) +#define TAG_S8 ((uint16_t)0x4) +#define TAG_U16 ((uint16_t)0x5) +#define TAG_S16 ((uint16_t)0x6) +#define TAG_DBL ((uint16_t)0x7) +#define TAG_BLN ((uint16_t)0x8) +#define TAG_ASTR ((uint16_t)0x9) +#define TAG_AU32 ((uint16_t)0xa) +#define TAG_RPL ((uint16_t)0xb) +#define TAG_END MRP_MSG_FIELD_END + +#define U32_GUARD (uint32_t)-1 + +/* + * our test custom data type + */ + +#define TAG_CUSTOM 0x1 + +typedef struct { + uint32_t seq; + char *msg; + uint8_t u8; + int8_t s8; + uint16_t u16; + int16_t s16; + double dbl; + bool bln; + char **astr; + uint32_t nstr; + uint32_t fsck; + uint32_t *au32; + char *rpl; +} custom_t; + + +MRP_DATA_DESCRIPTOR(custom_descr, TAG_CUSTOM, custom_t, + MRP_DATA_MEMBER(custom_t, seq, MRP_MSG_FIELD_UINT32), + MRP_DATA_MEMBER(custom_t, msg, MRP_MSG_FIELD_STRING), + MRP_DATA_MEMBER(custom_t, u8, MRP_MSG_FIELD_UINT8 ), + MRP_DATA_MEMBER(custom_t, s8, MRP_MSG_FIELD_SINT8 ), + MRP_DATA_MEMBER(custom_t, u16, MRP_MSG_FIELD_UINT16), + MRP_DATA_MEMBER(custom_t, s16, MRP_MSG_FIELD_SINT16), + MRP_DATA_MEMBER(custom_t, dbl, MRP_MSG_FIELD_DOUBLE), + MRP_DATA_MEMBER(custom_t, bln, MRP_MSG_FIELD_BOOL ), + MRP_DATA_MEMBER(custom_t, rpl, MRP_MSG_FIELD_STRING), + MRP_DATA_MEMBER(custom_t, nstr, MRP_MSG_FIELD_UINT32), + MRP_DATA_MEMBER(custom_t, fsck, MRP_MSG_FIELD_UINT32), + MRP_DATA_ARRAY_COUNT(custom_t, astr, nstr, + MRP_MSG_FIELD_STRING), + MRP_DATA_ARRAY_GUARD(custom_t, au32, u32, U32_GUARD, + MRP_MSG_FIELD_UINT32)); + +MRP_DATA_DESCRIPTOR(buggy_descr, TAG_CUSTOM, custom_t, + MRP_DATA_MEMBER(custom_t, seq, MRP_MSG_FIELD_UINT32), + MRP_DATA_MEMBER(custom_t, msg, MRP_MSG_FIELD_STRING), + MRP_DATA_MEMBER(custom_t, u8, MRP_MSG_FIELD_UINT8 ), + MRP_DATA_MEMBER(custom_t, s8, MRP_MSG_FIELD_SINT8 ), + MRP_DATA_MEMBER(custom_t, u16, MRP_MSG_FIELD_UINT16), + MRP_DATA_MEMBER(custom_t, s16, MRP_MSG_FIELD_SINT16), + MRP_DATA_MEMBER(custom_t, dbl, MRP_MSG_FIELD_DOUBLE), + MRP_DATA_MEMBER(custom_t, bln, MRP_MSG_FIELD_BOOL ), + MRP_DATA_MEMBER(custom_t, rpl, MRP_MSG_FIELD_STRING), + MRP_DATA_MEMBER(custom_t, nstr, MRP_MSG_FIELD_UINT32), + MRP_DATA_MEMBER(custom_t, fsck, MRP_MSG_FIELD_UINT32), + MRP_DATA_ARRAY_COUNT(custom_t, astr, fsck, + MRP_MSG_FIELD_STRING), + MRP_DATA_ARRAY_GUARD(custom_t, au32, u32, U32_GUARD, + MRP_MSG_FIELD_UINT32)); + +mrp_data_descr_t *data_descr; + +typedef struct { + mrp_mainloop_t *ml; + mrp_transport_t *lt, *st; + char *addrstr; + mrp_sockaddr_t addr; + socklen_t alen; + const char *atype; + int server; + int sock; + mrp_io_watch_t *iow; + mrp_timer_t *timer; + int custom; + int buggy; + int connect; + int stream; + int log_mask; + const char *log_target; + uint32_t seqno; + mrp_list_hook_t clients; +} context_t; + +typedef struct { + int id; + mrp_transport_t *t; + context_t *c; + mrp_list_hook_t hook; +} client_t; + + +void recv_msg(mrp_transport_t *t, mrp_msg_t *msg, void *user_data); +void recvfrom_msg(mrp_transport_t *t, mrp_msg_t *msg, mrp_sockaddr_t *addr, + socklen_t addrlen, void *user_data); + +void recv_custom(mrp_transport_t *t, void *data, uint16_t tag, void *user_data); +void recvfrom_custom(mrp_transport_t *t, void *data, uint16_t tag, + mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data); + + + +void dump_msg(mrp_msg_t *msg, FILE *fp) +{ + mrp_msg_dump(msg, fp); +} + + +void srv_recvfrom_msg(mrp_transport_t *t, mrp_msg_t *msg, mrp_sockaddr_t *addr, + socklen_t addrlen, void *user_data) +{ + context_t *c = (context_t *)user_data; + mrp_msg_field_t *f; + uint32_t seq; + char buf[256]; + int status; + + mrp_log_info("received a message"); + dump_msg(msg, stdout); + + seq = 0; + if ((f = mrp_msg_find(msg, TAG_SEQ)) != NULL) { + if (f->type == MRP_MSG_FIELD_UINT32) + seq = f->u32; + } + + snprintf(buf, sizeof(buf), "reply to message #%u", seq); + + if (!mrp_msg_append(msg, TAG_RPL, MRP_MSG_FIELD_STRING, buf, + TAG_END)) { + mrp_log_info("failed to append to received message"); + exit(1); + } + + if (c->connect) + status = mrp_transport_send(t, msg); + else + status = mrp_transport_sendto(t, msg, addr, addrlen); + + if (status) + mrp_log_info("reply successfully sent"); + else + mrp_log_error("failed to send reply"); + + /* message unreffed by transport layer */ +} + + +void recvfrom_msg(mrp_transport_t *t, mrp_msg_t *msg, mrp_sockaddr_t *addr, + socklen_t addrlen, void *user_data) +{ + MRP_UNUSED(t); + MRP_UNUSED(addr); + MRP_UNUSED(addrlen); + MRP_UNUSED(user_data); + + mrp_log_info("client received a message"); + dump_msg(msg, stdout); +} + + +void srv_recv_msg(mrp_transport_t *t, mrp_msg_t *msg, void *user_data) +{ + return srv_recvfrom_msg(t, msg, NULL, 0, user_data); +} + + +void recv_msg(mrp_transport_t *t, mrp_msg_t *msg, void *user_data) +{ + return recvfrom_msg(t, msg, NULL, 0, user_data); +} + + +void dump_custom(custom_t *msg, FILE *fp) +{ + uint32_t i; + + mrp_data_dump(msg, data_descr, fp); + fprintf(fp, "{\n"); + fprintf(fp, " seq = %u\n" , msg->seq); + fprintf(fp, " msg = '%s'\n", msg->msg); + fprintf(fp, " u8 = %u\n" , msg->u8); + fprintf(fp, " s8 = %d\n" , msg->s8); + fprintf(fp, " u16 = %u\n" , msg->u16); + fprintf(fp, " s16 = %d\n" , msg->s16); + fprintf(fp, " dbl = %f\n" , msg->dbl); + fprintf(fp, " bln = %s\n" , msg->bln ? "true" : "false"); + fprintf(fp, " astr = (%u)\n", msg->nstr); + for (i = 0; i < msg->nstr; i++) + fprintf(fp, " %s\n", msg->astr[i]); + fprintf(fp, " au32 =\n"); + for (i = 0; msg->au32[i] != U32_GUARD; i++) + fprintf(fp, " %u\n", msg->au32[i]); + fprintf(fp, " rpl = '%s'\n", msg->rpl); + fprintf(fp, "}\n"); +} + + +void free_custom(custom_t *msg) +{ + mrp_data_free(msg, data_descr->tag); +} + + + +void srv_recvfrom_custom(mrp_transport_t *t, void *data, uint16_t tag, + mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data) +{ + context_t *c = (context_t *)user_data; + custom_t *msg = (custom_t *)data; + custom_t rpl; + char buf[256]; + uint32_t au32[] = { 9, 8, 7, 6, 5, -1 }; + int status; + + mrp_log_info("server received custom message of type 0x%x", tag); + dump_custom(data, stdout); + + if (tag != data_descr->tag) { + mrp_log_error("Tag 0x%x != our custom type (0x%x).", + tag, data_descr->tag); + exit(1); + } + + rpl = *msg; + snprintf(buf, sizeof(buf), "reply to message #%u", msg->seq); + rpl.rpl = buf; + rpl.au32 = au32; + + if (c->connect) + status = mrp_transport_senddata(t, &rpl, data_descr->tag); + else + status = mrp_transport_senddatato(t, &rpl, data_descr->tag, + addr, addrlen); + if (status) + mrp_log_info("reply successfully sent"); + else + mrp_log_error("failed to send reply"); + + free_custom(msg); +} + +void recvfrom_custom(mrp_transport_t *t, void *data, uint16_t tag, + mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data) +{ + custom_t *msg = (custom_t *)data; + + MRP_UNUSED(t); + MRP_UNUSED(data); + MRP_UNUSED(addr); + MRP_UNUSED(addrlen); + MRP_UNUSED(user_data); + + mrp_log_info("received custom message of type 0x%x", tag); + dump_custom(data, stdout); + + if (tag != data_descr->tag) { + mrp_log_error("Tag 0x%x != our custom type (0x%x).", + tag, data_descr->tag); + exit(1); + } + + free_custom(msg); +} + + +void recv_custom(mrp_transport_t *t, void *data, uint16_t tag, void *user_data) +{ + recvfrom_custom(t, data, tag, NULL, 0, user_data); +} + +void srv_recv_custom(mrp_transport_t *t, void *data, uint16_t tag, void *user_data) +{ + srv_recvfrom_custom(t, data, tag, NULL, 0, user_data); +} + +void closed_evt(mrp_transport_t *t, int error, void *user_data) +{ + context_t *c = (context_t *)user_data; + + MRP_UNUSED(t); + MRP_UNUSED(c); + + if (error) { + mrp_log_error("Connection closed with error %d (%s).", error, + strerror(error)); + exit(1); + } + else { + mrp_log_info("Peer has closed the connection."); + exit(0); + } +} + + +void connection_evt(mrp_transport_t *lt, void *user_data) +{ + context_t *c = (context_t *)user_data; + int flags; + + mrp_log_info("connection event!"); + + flags = MRP_TRANSPORT_REUSEADDR | MRP_TRANSPORT_NONBLOCK; + c->st = mrp_transport_accept(lt, c, flags); + + if (c->st == NULL) { + mrp_log_error("Failed to accept new connection."); + exit(1); + } +} + + +void type_init(context_t *c) +{ + if (c->buggy && c->server) { + data_descr = &buggy_descr; + mrp_log_info("Deliberately using buggy data descriptor..."); + } + else + data_descr = &custom_descr; + + if (!mrp_msg_register_type(data_descr)) { + mrp_log_error("Failed to register custom data type."); + exit(1); + } +} + + +void server_init(context_t *c) +{ + static mrp_transport_evt_t evt = { + { .recvmsg = NULL }, + { .recvmsgfrom = NULL }, + .closed = closed_evt, + .connection = connection_evt + }; + + int flags; + + if (c->custom) { + evt.recvdata = srv_recv_custom; + evt.recvdatafrom = srv_recvfrom_custom; + } + else { + evt.recvmsg = srv_recv_msg; + evt.recvmsgfrom = srv_recvfrom_msg; + } + + + flags = MRP_TRANSPORT_REUSEADDR | + (c->custom ? MRP_TRANSPORT_MODE_DATA : 0); + c->lt = mrp_transport_create(c->ml, c->atype, &evt, c, flags); + + if (c->lt == NULL) { + mrp_log_error("Failed to create listening server transport."); + exit(1); + } + + if (!mrp_transport_bind(c->lt, &c->addr, c->alen)) { + mrp_log_error("Failed to bind transport to address %s.", c->addrstr); + exit(1); + } + + if (c->stream) { + if (!mrp_transport_listen(c->lt, 0)) { + mrp_log_error("Failed to listen on server transport."); + exit(1); + } + } +} + + +void send_msg(client_t *client) +{ + mrp_msg_t *msg; + uint32_t seq; + char buf[256]; + char *astr[] = { "this", "is", "an", "array", "of", "strings" }; + uint32_t au32[] = { 1, 2, 3, + 1 << 16, 2 << 16, 3 << 16, + 1 << 24, 2 << 24, 3 << 24 }; + uint32_t nstr = MRP_ARRAY_SIZE(astr); + uint32_t nu32 = MRP_ARRAY_SIZE(au32); + int status; + context_t *c = client->c; + + seq = c->seqno++; + snprintf(buf, sizeof(buf), "this is message #%u", (unsigned int)seq); + + msg = mrp_msg_create(TAG_SEQ , MRP_MSG_FIELD_UINT32, seq, + TAG_MSG , MRP_MSG_FIELD_STRING, buf, + TAG_U8 , MRP_MSG_FIELD_UINT8 , seq & 0xf, + TAG_S8 , MRP_MSG_FIELD_SINT8 , -(seq & 0xf), + TAG_U16 , MRP_MSG_FIELD_UINT16, seq, + TAG_S16 , MRP_MSG_FIELD_SINT16, - seq, + TAG_DBL , MRP_MSG_FIELD_DOUBLE, seq / 3.0, + TAG_BLN , MRP_MSG_FIELD_BOOL , seq & 0x1, + TAG_ASTR, MRP_MSG_FIELD_ARRAY_OF(STRING), nstr, astr, + TAG_AU32, MRP_MSG_FIELD_ARRAY_OF(UINT32), nu32, au32, + TAG_END); + + if (msg == NULL) { + mrp_log_error("Failed to create new message."); + exit(1); + } + + if (c->connect) + status = mrp_transport_send(client->t, msg); + else + status = mrp_transport_sendto(client->t, msg, &c->addr, c->alen); + + if (!status) { + mrp_log_error("Failed to send message #%d.", seq); + exit(1); + } + else + mrp_log_info("Message #%d succesfully sent.", seq); + + mrp_msg_unref(msg); +} + + +void send_custom(client_t *client) +{ + custom_t msg; + char buf[256]; + char *astr[] = { "this", "is", "a", "test", "string", "array" }; + uint32_t au32[] = { 1, 2, 3, 4, 5, 6, 7, -1 }; + int status; + context_t *c = client->c; + uint32_t seq = c->seqno++; + + msg.seq = seq; + snprintf(buf, sizeof(buf), "this is message #%u", (unsigned int)seq); + msg.msg = buf; + msg.u8 = seq & 0xf; + msg.s8 = -(seq & 0xf); + msg.u16 = seq; + msg.s16 = - seq; + msg.dbl = seq / 3.0; + msg.bln = seq & 0x1; + msg.astr = astr; + msg.nstr = MRP_ARRAY_SIZE(astr); + msg.fsck = 1000; + msg.au32 = au32; + msg.rpl = ""; + + if (c->connect) + status = mrp_transport_senddata(client->t, &msg, data_descr->tag); + else + status = mrp_transport_senddatato(client->t, &msg, data_descr->tag, + &c->addr, c->alen); + + if (!status) { + mrp_log_error("Failed to send message #%d.", msg.seq); + exit(1); + } + else + mrp_log_info("Message #%d succesfully sent.", msg.seq); +} + + + +void send_cb(mrp_timer_t *t, void *user_data) +{ + client_t *client = (client_t *)user_data; + context_t *c = client->c; + + MRP_UNUSED(t); + + if (c->custom) + send_custom(client); + else + send_msg(client); +} + + +void client_init(context_t *c) +{ + static mrp_transport_evt_t evt = { + { .recvmsg = NULL }, + { .recvmsgfrom = NULL }, + .closed = closed_evt, + .connection = NULL + }; + + int flags; + client_t *client; + + if (c->custom) { + evt.recvdata = recv_custom; + evt.recvdatafrom = recvfrom_custom; + } + else { + evt.recvmsg = recv_msg; + evt.recvmsgfrom = recvfrom_msg; + } + + client = mrp_allocz(sizeof(client_t)); + mrp_list_init(&client->hook); + client->c = c; + + mrp_list_append(&c->clients, &client->hook); + + flags = c->custom ? MRP_TRANSPORT_MODE_DATA : 0; + client->t = mrp_transport_create(c->ml, c->atype, &evt, client, flags); + + if (client->t == NULL) { + mrp_log_error("Failed to create new transport."); + exit(1); + } + + if (!strcmp(c->atype, "unxd")) { + char addrstr[] = "unxd:@stream-test-client"; + mrp_sockaddr_t addr; + socklen_t alen; + + alen = mrp_transport_resolve(NULL, addrstr, &addr, sizeof(addr), NULL); + if (alen <= 0) { + mrp_log_error("Failed to resolve transport address '%s'.", addrstr); + exit(1); + } + + if (!mrp_transport_bind(client->t, &addr, alen)) { + mrp_log_error("Failed to bind to transport address '%s'.", addrstr); + exit(1); + } + } + + if (c->connect) { + if (!mrp_transport_connect(client->t, &c->addr, c->alen)) { + mrp_log_error("Failed to connect to %s.", c->addrstr); + exit(1); + } + } + + + c->timer = mrp_add_timer(c->ml, 1000, send_cb, client); + + if (c->timer == NULL) { + mrp_log_error("Failed to create send timer."); + exit(1); + } +} + + +static void print_usage(const char *argv0, int exit_code, const char *fmt, ...) +{ + va_list ap; + + if (fmt && *fmt) { + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + } + + printf("usage: %s [options] [transport-address]\n\n" + "The possible options are:\n" + " -s, --server run as test server (default)\n" + " -C, --connect connect transport\n" + " For connection-oriented transports, this is automatic.\n" + " -a, --address address to use\n" + " -c, --custom use custom messages\n" + " -m, --message use generic messages (default)\n" + " -b, --buggy use buggy data descriptors\n" + " -t, --log-target=TARGET log target to use\n" + " TARGET is one of stderr,stdout,syslog, or a logfile path\n" + " -l, --log-level=LEVELS logging level to use\n" + " LEVELS is a comma separated list of info, error and warning\n" + " -v, --verbose increase logging verbosity\n" + " -d, --debug enable debug messages\n" + " -h, --help show help on usage\n", + argv0); + + if (exit_code < 0) + return; + else + exit(exit_code); +} + + +static void config_set_defaults(context_t *ctx) +{ + mrp_clear(ctx); + ctx->addrstr = "tcp4:127.0.0.1:3000"; + ctx->server = FALSE; + ctx->custom = FALSE; + ctx->log_mask = MRP_LOG_UPTO(MRP_LOG_DEBUG); + ctx->log_target = MRP_LOG_TO_STDERR; +} + + +int parse_cmdline(context_t *ctx, int argc, char **argv) +{ +# define OPTIONS "scmbCa:l:t:vdh" + struct option options[] = { + { "server" , no_argument , NULL, 's' }, + { "address" , required_argument, NULL, 'a' }, + { "custom" , no_argument , NULL, 'c' }, + { "connect" , no_argument , NULL, 'C' }, + { "message" , no_argument , NULL, 'm' }, + { "buggy" , no_argument , NULL, 'b' }, + { "log-level" , required_argument, NULL, 'l' }, + { "log-target", required_argument, NULL, 't' }, + { "verbose" , optional_argument, NULL, 'v' }, + { "debug" , no_argument , NULL, 'd' }, + { "help" , no_argument , NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + + int opt, debug; + + debug = FALSE; + config_set_defaults(ctx); + + while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) { + switch (opt) { + case 's': + ctx->server = TRUE; + break; + + case 'c': + ctx->custom = TRUE; + break; + + case 'm': + ctx->custom = FALSE; + break; + + case 'b': + ctx->buggy = TRUE; + break; + + case 'C': + ctx->connect = TRUE; + break; + + case 'a': + ctx->addrstr = optarg; + break; + + case 'v': + ctx->log_mask <<= 1; + ctx->log_mask |= 1; + break; + + case 'l': + ctx->log_mask = mrp_log_parse_levels(optarg); + if (ctx->log_mask < 0) + print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg); + break; + + case 't': + ctx->log_target = mrp_log_parse_target(optarg); + if (!ctx->log_target) + print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg); + break; + + case 'd': + debug = TRUE; + break; + + case 'h': + print_usage(argv[0], -1, ""); + exit(0); + break; + + default: + print_usage(argv[0], EINVAL, "invalid option '%c'", opt); + } + } + + if (debug) + ctx->log_mask |= MRP_LOG_MASK_DEBUG; + + return TRUE; +} + + +int main(int argc, char *argv[]) +{ + context_t c; + + mrp_clear(&c); + + if (!parse_cmdline(&c, argc, argv)) + exit(1); + + mrp_log_set_mask(c.log_mask); + mrp_log_set_target(c.log_target); + + mrp_log_info("Using address '%s'...", c.addrstr); + + mrp_list_init(&c.clients); + + if (c.custom) + mrp_log_info("Using custom messages..."); + else + mrp_log_info("Using generic messages..."); + + if (!strncmp(c.addrstr, "tcp", 3) || + !strncmp(c.addrstr, "unxs", 4)) { + c.stream = TRUE; + c.connect = TRUE; + } + + c.alen = mrp_transport_resolve(NULL, c.addrstr, + &c.addr, sizeof(c.addr), &c.atype); + if (c.alen <= 0) { + mrp_log_error("Failed to resolve transport address '%s'.", c.addrstr); + exit(1); + } + + c.ml = mrp_mainloop_create(); + + type_init(&c); + + server_init(&c); + + client_init(&c); + client_init(&c); + + mrp_mainloop_run(c.ml); + + return 0; +} diff --git a/src/common/tests/libdbus-test.c b/src/common/tests/libdbus-test.c new file mode 100644 index 0000000..1f68bc4 --- /dev/null +++ b/src/common/tests/libdbus-test.c @@ -0,0 +1,482 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <netdb.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/socket.h> + +#define _GNU_SOURCE +#include <getopt.h> + +#include <murphy/common.h> +#include <murphy/common/dbus-libdbus.h> + +#define SERVER_NAME "org.test.murphy-server" +#define SERVER_PATH "/server" +#define SERVER_INTERFACE "Murphy.Server" +#define PING "ping" +#define CLIENT_NAME "org.test.murphy-client" +#define CLIENT_PATH "/client" +#define CLIENT_INTERFACE "Murphy.Client" +#define PONG "pong" + + +typedef struct { + char *busaddr; + int server; + int log_mask; + const char *log_target; + mrp_mainloop_t *ml; + mrp_timer_t *timer; + uint32_t seqno; + mrp_dbus_t *dbus; + const char *name; + int32_t cid; + int server_up; + int all_pongs; +} context_t; + + +static int ping_handler(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data) +{ + context_t *c = (context_t *)user_data; + uint32_t seq; + const char *dest; + + MRP_UNUSED(c); + + if (mrp_dbus_msg_type(msg) == MRP_DBUS_MESSAGE_TYPE_METHOD_CALL) { + if (mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_UINT32, &seq)) + mrp_log_info("-> ping request #%u", seq); + else + mrp_log_error("-> malformed ping request"); + + if (!mrp_dbus_reply(dbus, msg, + MRP_DBUS_TYPE_UINT32, &seq, + MRP_DBUS_TYPE_INVALID)) + mrp_log_error("Failed to send ping reply #%u.", seq); + else + mrp_log_info("<- ping reply #%u", seq); + + if (seq & 0x1) + dest = mrp_dbus_msg_sender(msg); + else + dest = NULL; + + if (!mrp_dbus_signal(dbus, dest, SERVER_PATH, SERVER_INTERFACE, PONG, + MRP_DBUS_TYPE_UINT32, &seq, + MRP_DBUS_TYPE_INVALID)) + mrp_log_error("Failed to send pong signal #%u.", seq); + else + mrp_log_info("<- pong %s #%u", dest ? "signal" : "broadcast", seq); + } + + return TRUE; +} + + +static void server_setup(context_t *c) +{ + c->dbus = mrp_dbus_connect(c->ml, c->busaddr, NULL); + + if (c->dbus == NULL) { + mrp_log_error("Failed to create D-BUS connection to '%s' bus.", + c->busaddr); + exit(1); + } + + c->name = mrp_dbus_get_unique_name(c->dbus); + mrp_log_info("Our address is %s on the bus...", + c->name ? c->name : "unknown"); + + if (!mrp_dbus_acquire_name(c->dbus, SERVER_NAME, NULL)) { + mrp_log_error("Failed to acquire D-BUS name '%s' on bus '%s'.", + SERVER_NAME, c->busaddr); + exit(1); + } + + if (!mrp_dbus_export_method(c->dbus, SERVER_PATH, SERVER_INTERFACE, + PING, ping_handler, c)) { + mrp_log_error("Failed to export D-BUS method '%s'.", PING); + exit(1); + } +} + + +void server_cleanup(context_t *c) +{ + mrp_dbus_release_name(c->dbus, SERVER_NAME, NULL); + mrp_dbus_remove_method(c->dbus, SERVER_PATH, SERVER_INTERFACE, + PING, ping_handler, c); + mrp_dbus_unref(c->dbus); +} + + +static void ping_reply(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data) +{ + context_t *c = (context_t *)user_data; + uint32_t seq; + + MRP_UNUSED(dbus); + MRP_UNUSED(user_data); + + if (mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_UINT32, &seq)) + mrp_log_info("-> ping reply #%u", seq); + else + mrp_log_error("Received malformed ping reply."); + + c->cid = 0; +} + + +static void ping_request(context_t *c) +{ + uint32_t seq; + + if (c->cid != 0) { + mrp_log_warning("Previous ping request still unanswered..."); + return; + } + + seq = c->seqno++; + c->cid = mrp_dbus_call(c->dbus, + SERVER_NAME, SERVER_PATH, SERVER_INTERFACE, + PING, 500, ping_reply, c, + MRP_DBUS_TYPE_UINT32, &seq, + MRP_DBUS_TYPE_INVALID); + + if (c->cid > 0) + mrp_log_info("<- ping request #%u", seq); + else + mrp_log_warning("Failed to send ping request #%u.", seq); +} + + +static void send_cb(mrp_timer_t *t, void *user_data) +{ + context_t *c = (context_t *)user_data; + + MRP_UNUSED(t); + + ping_request(c); +} + + +static int pong_handler(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data) +{ + context_t *c = (context_t *)user_data; + uint32_t seq; + + MRP_UNUSED(c); + MRP_UNUSED(dbus); + + if (mrp_dbus_msg_type(msg) == MRP_DBUS_MESSAGE_TYPE_SIGNAL) { + if (mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_UINT32, &seq)) + mrp_log_info("-> pong signal #%u", seq); + else + mrp_log_error("-> malformed pong signal"); + } + + return TRUE; +} + + +static void server_status_cb(mrp_dbus_t *dbus, const char *name, int up, + const char *owner, void *user_data) +{ + context_t *c = (context_t *)user_data; + + MRP_UNUSED(dbus); + MRP_UNUSED(name); + + if (up) { + mrp_log_info("%s came up (as %s)", name, owner); + + if (c->timer == NULL) { + c->timer = mrp_add_timer(c->ml, 1000, send_cb, c); + + if (c->timer == NULL) { + mrp_log_error("Failed to create D-BUS sending timer."); + exit(1); + } + } + } + else { + mrp_log_info("%s went down", name); + + if (c->timer != NULL) { + mrp_del_timer(c->timer); + c->timer = NULL; + } + } +} + + +static void client_setup(context_t *c) +{ + const char *dest; + + c->dbus = mrp_dbus_connect(c->ml, c->busaddr, NULL); + + if (c->dbus == NULL) { + mrp_log_error("Failed to create D-BUS connection to '%s' bus.", + c->busaddr); + exit(1); + } + + c->name = mrp_dbus_get_unique_name(c->dbus); + mrp_log_info("Our address is %s on the bus...", + c->name ? c->name : "unknown"); + + mrp_dbus_follow_name(c->dbus, SERVER_NAME, server_status_cb, c); + + if (c->all_pongs) { + mrp_log_info("Subscribing for all pong signals..."); + dest = NULL; + } + else { + mrp_log_info("Subscribing only for pong signals to us..."); + dest = c->name; + } + + if (!mrp_dbus_subscribe_signal(c->dbus, pong_handler, c, + dest, SERVER_PATH, SERVER_INTERFACE, + PONG, NULL)) { + mrp_log_error("Failed to subscribe for signal '%s/%s.%s'.", SERVER_PATH, + SERVER_INTERFACE, PONG); + exit(1); + } + + c->timer = mrp_add_timer(c->ml, 1000, send_cb, c); + + if (c->timer == NULL) { + mrp_log_error("Failed to create D-BUS sending timer."); + exit(1); + } +} + + +static void client_cleanup(context_t *c) +{ + mrp_dbus_follow_name(c->dbus, SERVER_NAME, server_status_cb, c); + mrp_del_timer(c->timer); + mrp_dbus_subscribe_signal(c->dbus, pong_handler, c, + c->name, SERVER_PATH, SERVER_INTERFACE, + PONG, NULL); + mrp_dbus_unref(c->dbus); +} + + +static void print_usage(const char *argv0, int exit_code, const char *fmt, ...) +{ + va_list ap; + + if (fmt && *fmt) { + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + } + + printf("usage: %s [options]\n\n" + "The possible options are:\n" + " -s, --server run as test server (default)\n" + " -b, --bus connect the given D-BUS\n" + " If omitted, defaults to the session bus.\n" + " -a, --all-pongs subscribe for all pong signals\n" + " If omitted, only pong with the client address are handled.\n" + " -t, --log-target=TARGET log target to use\n" + " TARGET is one of stderr,stdout,syslog, or a logfile path\n" + " -l, --log-level=LEVELS logging level to use\n" + " LEVELS is a comma separated list of info, error and warning\n" + " -v, --verbose increase logging verbosity\n" + " -d, --debug enable debug messages\n" + " -h, --help show help on usage\n", + argv0); + + if (exit_code < 0) + return; + else + exit(exit_code); +} + + +static void config_set_defaults(context_t *ctx) +{ + mrp_clear(ctx); + ctx->busaddr = "session"; + ctx->server = FALSE; + ctx->log_mask = MRP_LOG_UPTO(MRP_LOG_DEBUG); + ctx->log_target = MRP_LOG_TO_STDERR; +} + + +int parse_cmdline(context_t *ctx, int argc, char **argv) +{ +# define OPTIONS "sab:l:t:vdh" + struct option options[] = { + { "server" , no_argument , NULL, 's' }, + { "bus" , required_argument, NULL, 'b' }, + { "all-pongs" , no_argument , NULL, 'a' }, + { "log-level" , required_argument, NULL, 'l' }, + { "log-target", required_argument, NULL, 't' }, + { "verbose" , optional_argument, NULL, 'v' }, + { "debug" , no_argument , NULL, 'd' }, + { "help" , no_argument , NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + + int opt, debug; + + debug = FALSE; + config_set_defaults(ctx); + + while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) { + switch (opt) { + case 's': + ctx->server = TRUE; + break; + + case 'b': + ctx->busaddr = optarg; + break; + + case 'a': + ctx->all_pongs = TRUE; + break; + + case 'v': + ctx->log_mask <<= 1; + ctx->log_mask |= 1; + break; + + case 'l': + ctx->log_mask = mrp_log_parse_levels(optarg); + if (ctx->log_mask < 0) + print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg); + break; + + case 't': + ctx->log_target = mrp_log_parse_target(optarg); + if (!ctx->log_target) + print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg); + break; + + case 'd': + debug = TRUE; + break; + + case 'h': + print_usage(argv[0], -1, ""); + exit(0); + break; + + default: + print_usage(argv[0], EINVAL, "invalid option '%c'", opt); + } + } + + if (debug) + ctx->log_mask |= MRP_LOG_MASK_DEBUG; + + return TRUE; +} + + +static void signal_handler(mrp_sighandler_t *h, int signum, void *user_data) +{ + mrp_mainloop_t *ml = mrp_get_sighandler_mainloop(h); + context_t *c = (context_t *)user_data; + + MRP_UNUSED(c); + + switch (signum) { + case SIGINT: + mrp_log_info("Got SIGINT, stopping..."); + if (ml != NULL) + mrp_mainloop_quit(ml, 0); + else + exit(0); + break; + + case SIGTERM: + mrp_log_info("Got SIGTERM, stopping..."); + if (ml != NULL) + mrp_mainloop_quit(ml, 0); + else + exit(0); + break; + } +} + + +int main(int argc, char *argv[]) +{ + context_t c; + + mrp_clear(&c); + + if (!parse_cmdline(&c, argc, argv)) + exit(1); + + mrp_log_set_mask(c.log_mask); + mrp_log_set_target(c.log_target); + + if (c.server) + mrp_log_info("Running as server, using D-BUS '%s'...", c.busaddr); + else + mrp_log_info("Running as client, using D-BUS '%s'...", c.busaddr); + + c.ml = mrp_mainloop_create(); + + if (c.ml == NULL) { + mrp_log_error("Failed to create mainloop."); + exit(1); + } + + mrp_add_sighandler(c.ml, SIGINT , signal_handler, &c); + + if (c.server) + server_setup(&c); + else + client_setup(&c); + + mrp_mainloop_run(c.ml); + + if (c.server) + server_cleanup(&c); + else + client_cleanup(&c); + + return 0; +} diff --git a/src/common/tests/libdbus-transport-test.c b/src/common/tests/libdbus-transport-test.c new file mode 100644 index 0000000..06c2320 --- /dev/null +++ b/src/common/tests/libdbus-transport-test.c @@ -0,0 +1,847 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <netdb.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/socket.h> + +#define _GNU_SOURCE +#include <getopt.h> + +#include <murphy/common.h> + +/* + * tags for generic message fields + */ + +#define TAG_SEQ ((uint16_t)0x1) +#define TAG_MSG ((uint16_t)0x2) +#define TAG_U8 ((uint16_t)0x3) +#define TAG_S8 ((uint16_t)0x4) +#define TAG_U16 ((uint16_t)0x5) +#define TAG_S16 ((uint16_t)0x6) +#define TAG_DBL ((uint16_t)0x7) +#define TAG_BLN ((uint16_t)0x8) +#define TAG_ASTR ((uint16_t)0x9) +#define TAG_AU32 ((uint16_t)0xa) +#define TAG_RPL ((uint16_t)0xb) +#define TAG_END MRP_MSG_FIELD_END + +#define U32_GUARD (uint32_t)-1 + +/* + * our test custom data type + */ + +#define TAG_CUSTOM 0x1 + +typedef struct { + uint32_t seq; + char *msg; + uint8_t u8; + int8_t s8; + uint16_t u16; + int16_t s16; + double dbl; + bool bln; + char **astr; + uint32_t nstr; + uint32_t fsck; + uint32_t *au32; + char *rpl; +} custom_t; + + +MRP_DATA_DESCRIPTOR(custom_descr, TAG_CUSTOM, custom_t, + MRP_DATA_MEMBER(custom_t, seq, MRP_MSG_FIELD_UINT32), + MRP_DATA_MEMBER(custom_t, msg, MRP_MSG_FIELD_STRING), + MRP_DATA_MEMBER(custom_t, u8, MRP_MSG_FIELD_UINT8 ), + MRP_DATA_MEMBER(custom_t, s8, MRP_MSG_FIELD_SINT8 ), + MRP_DATA_MEMBER(custom_t, u16, MRP_MSG_FIELD_UINT16), + MRP_DATA_MEMBER(custom_t, s16, MRP_MSG_FIELD_SINT16), + MRP_DATA_MEMBER(custom_t, dbl, MRP_MSG_FIELD_DOUBLE), + MRP_DATA_MEMBER(custom_t, bln, MRP_MSG_FIELD_BOOL ), + MRP_DATA_MEMBER(custom_t, rpl, MRP_MSG_FIELD_STRING), + MRP_DATA_MEMBER(custom_t, nstr, MRP_MSG_FIELD_UINT32), + MRP_DATA_MEMBER(custom_t, fsck, MRP_MSG_FIELD_UINT32), + MRP_DATA_ARRAY_COUNT(custom_t, astr, nstr, + MRP_MSG_FIELD_STRING), + MRP_DATA_ARRAY_GUARD(custom_t, au32, u32, U32_GUARD, + MRP_MSG_FIELD_UINT32)); + +MRP_DATA_DESCRIPTOR(buggy_descr, TAG_CUSTOM, custom_t, + MRP_DATA_MEMBER(custom_t, seq, MRP_MSG_FIELD_UINT32), + MRP_DATA_MEMBER(custom_t, msg, MRP_MSG_FIELD_STRING), + MRP_DATA_MEMBER(custom_t, u8, MRP_MSG_FIELD_UINT8 ), + MRP_DATA_MEMBER(custom_t, s8, MRP_MSG_FIELD_SINT8 ), + MRP_DATA_MEMBER(custom_t, u16, MRP_MSG_FIELD_UINT16), + MRP_DATA_MEMBER(custom_t, s16, MRP_MSG_FIELD_SINT16), + MRP_DATA_MEMBER(custom_t, dbl, MRP_MSG_FIELD_DOUBLE), + MRP_DATA_MEMBER(custom_t, bln, MRP_MSG_FIELD_BOOL ), + MRP_DATA_MEMBER(custom_t, rpl, MRP_MSG_FIELD_STRING), + MRP_DATA_MEMBER(custom_t, nstr, MRP_MSG_FIELD_UINT32), + MRP_DATA_MEMBER(custom_t, fsck, MRP_MSG_FIELD_UINT32), + MRP_DATA_ARRAY_COUNT(custom_t, astr, fsck, + MRP_MSG_FIELD_STRING), + MRP_DATA_ARRAY_GUARD(custom_t, au32, u32, U32_GUARD, + MRP_MSG_FIELD_UINT32)); + +mrp_data_descr_t *data_descr; + + +typedef enum { + MODE_DEFAULT = 0, + MODE_MESSAGE = 1, + MODE_DATA = 2, + MODE_RAW = 3, +} msg_mode_t; + + +typedef struct { + mrp_mainloop_t *ml; + mrp_transport_t *lt, *t; + char *addrstr; + mrp_sockaddr_t addr; + socklen_t alen; + const char *atype; + int server; + int sock; + mrp_io_watch_t *iow; + mrp_timer_t *timer; + int mode; + int buggy; + int connect; + int stream; + int log_mask; + const char *log_target; + uint32_t seqno; +} context_t; + + +void recv_msg(mrp_transport_t *t, mrp_msg_t *msg, void *user_data); +void recvfrom_msg(mrp_transport_t *t, mrp_msg_t *msg, mrp_sockaddr_t *addr, + socklen_t addrlen, void *user_data); + +void recv_data(mrp_transport_t *t, void *data, uint16_t tag, void *user_data); +void recvfrom_data(mrp_transport_t *t, void *data, uint16_t tag, + mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data); + +void recvraw(mrp_transport_t *t, void *data, size_t size, void *user_data); +void recvrawfrom(mrp_transport_t *t, void *data, size_t size, + mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data); + + +void dump_msg(mrp_msg_t *msg, FILE *fp) +{ + mrp_msg_dump(msg, fp); +} + + +void recvfrom_msg(mrp_transport_t *t, mrp_msg_t *msg, mrp_sockaddr_t *addr, + socklen_t addrlen, void *user_data) +{ + context_t *c = (context_t *)user_data; + mrp_msg_field_t *f; + uint32_t seq; + char buf[256]; + int status; + + mrp_log_info("received a message"); + dump_msg(msg, stdout); + + if (c->server) { + seq = 0; + if ((f = mrp_msg_find(msg, TAG_SEQ)) != NULL) { + if (f->type == MRP_MSG_FIELD_UINT32) + seq = f->u32; + } + + snprintf(buf, sizeof(buf), "reply to message #%u", seq); + + if (!mrp_msg_append(msg, TAG_RPL, MRP_MSG_FIELD_STRING, buf, + TAG_END)) { + mrp_log_info("failed to append to received message"); + exit(1); + } + + if (c->connect) + status = mrp_transport_send(t, msg); + else + status = mrp_transport_sendto(t, msg, addr, addrlen); + + if (status) + mrp_log_info("reply successfully sent"); + else + mrp_log_error("failed to send reply"); + + /* message unreffed by transport layer */ + } +} + + +void recv_msg(mrp_transport_t *t, mrp_msg_t *msg, void *user_data) +{ + return recvfrom_msg(t, msg, NULL, 0, user_data); +} + + +void dump_custom(custom_t *msg, FILE *fp) +{ + uint32_t i; + + mrp_data_dump(msg, data_descr, fp); + fprintf(fp, "{\n"); + fprintf(fp, " seq = %u\n" , msg->seq); + fprintf(fp, " msg = '%s'\n", msg->msg); + fprintf(fp, " u8 = %u\n" , msg->u8); + fprintf(fp, " s8 = %d\n" , msg->s8); + fprintf(fp, " u16 = %u\n" , msg->u16); + fprintf(fp, " s16 = %d\n" , msg->s16); + fprintf(fp, " dbl = %f\n" , msg->dbl); + fprintf(fp, " bln = %s\n" , msg->bln ? "true" : "false"); + fprintf(fp, " astr = (%u)\n", msg->nstr); + for (i = 0; i < msg->nstr; i++) + fprintf(fp, " %s\n", msg->astr[i]); + fprintf(fp, " au32 =\n"); + for (i = 0; msg->au32[i] != U32_GUARD; i++) + fprintf(fp, " %u\n", msg->au32[i]); + fprintf(fp, " rpl = '%s'\n", msg->rpl); + fprintf(fp, "}\n"); +} + + +void free_custom(custom_t *msg) +{ + mrp_data_free(msg, data_descr->tag); +} + + +void recvfrom_data(mrp_transport_t *t, void *data, uint16_t tag, + mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data) +{ + context_t *c = (context_t *)user_data; + custom_t *msg = (custom_t *)data; + custom_t rpl; + char buf[256]; + uint32_t au32[] = { 9, 8, 7, 6, 5, -1 }; + int status; + + mrp_log_info("received custom message of type 0x%x", tag); + dump_custom(data, stdout); + + if (tag != data_descr->tag) { + mrp_log_error("Tag 0x%x != our custom type (0x%x).", + tag, data_descr->tag); + exit(1); + } + + if (c->server) { + rpl = *msg; + snprintf(buf, sizeof(buf), "reply to message #%u", msg->seq); + rpl.rpl = buf; + rpl.au32 = au32; + + if (c->connect) + status = mrp_transport_senddata(t, &rpl, data_descr->tag); + else + status = mrp_transport_senddatato(t, &rpl, data_descr->tag, + addr, addrlen); + if (status) + mrp_log_info("reply successfully sent"); + else + mrp_log_error("failed to send reply"); + } + + free_custom(msg); +} + + +void recv_data(mrp_transport_t *t, void *data, uint16_t tag, void *user_data) +{ + recvfrom_data(t, data, tag, NULL, 0, user_data); +} + + +void dump_raw(void *data, size_t size, FILE *fp) +{ + int len = (int)size; + + fprintf(fp, "[%*.*s]\n", len, len, (char *)data); +} + + +void recvfrom_raw(mrp_transport_t *t, void *data, size_t size, + mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data) +{ + context_t *c = (context_t *)user_data; + char rpl[256]; + size_t rpl_size; + int status; + + rpl_size = snprintf(rpl, sizeof(rpl), "reply to message [%*.*s]", + (int)size, (int)size, (char *)data); + + mrp_log_info("received raw message"); + dump_raw(data, size, stdout); + + if (strncmp((char *)data, "reply to ", 9) != 0) { + if (c->connect) + status = mrp_transport_sendraw(t, rpl, rpl_size); + else + status = mrp_transport_sendrawto(t, rpl, rpl_size, addr, addrlen); + + if (status) + mrp_log_info("reply successfully sent"); + else + mrp_log_error("failed to send reply"); + } +} + + +void recv_raw(mrp_transport_t *t, void *data, size_t size, void *user_data) +{ + recvfrom_raw(t, data, size, NULL, 0, user_data); +} + + + +void closed_evt(mrp_transport_t *t, int error, void *user_data) +{ + context_t *c = (context_t *)user_data; + + MRP_UNUSED(t); + MRP_UNUSED(c); + + if (error) { + mrp_log_error("Connection closed with error %d (%s).", error, + strerror(error)); + exit(1); + } + else { + mrp_log_info("Peer has closed the connection."); + exit(0); + } +} + + +void connection_evt(mrp_transport_t *lt, void *user_data) +{ + context_t *c = (context_t *)user_data; + int flags; + + flags = MRP_TRANSPORT_REUSEADDR | MRP_TRANSPORT_NONBLOCK; + c->t = mrp_transport_accept(lt, c, flags); + + if (c->t == NULL) { + mrp_log_error("Failed to accept new connection."); + exit(1); + } +} + + +void type_init(context_t *c) +{ + if (c->buggy && c->server) { + data_descr = &buggy_descr; + mrp_log_info("Deliberately using buggy data descriptor..."); + } + else + data_descr = &custom_descr; + + if (!mrp_msg_register_type(data_descr)) { + mrp_log_error("Failed to register custom data type."); + exit(1); + } +} + + +void server_init(context_t *c) +{ + static mrp_transport_evt_t evt = { + { .recvmsg = NULL }, + { .recvmsgfrom = NULL }, + .closed = NULL, + .connection = NULL, + }; + + int flags; + + type_init(c); + + switch (c->mode) { + case MODE_DATA: + evt.recvdata = recv_data; + evt.recvdatafrom = recvfrom_data; + break; + case MODE_RAW: + evt.recvraw = recv_raw; + evt.recvrawfrom = recvfrom_raw; + break; + case MODE_MESSAGE: + default: + evt.recvmsg = recv_msg; + evt.recvmsgfrom = recvfrom_msg; + } + + if (c->stream) { + evt.connection = connection_evt; + evt.closed = closed_evt; + } + + flags = MRP_TRANSPORT_REUSEADDR; + + switch (c->mode) { + case MODE_DATA: flags |= MRP_TRANSPORT_MODE_DATA; break; + case MODE_RAW: flags |= MRP_TRANSPORT_MODE_RAW; break; + default: + case MODE_MESSAGE: flags |= MRP_TRANSPORT_MODE_MSG; + } + + c->lt = mrp_transport_create(c->ml, c->atype, &evt, c, flags); + + if (c->lt == NULL) { + mrp_log_error("Failed to create listening server transport."); + exit(1); + } + + if (!mrp_transport_bind(c->lt, &c->addr, c->alen)) { + mrp_log_error("Failed to bind transport to address %s.", c->addrstr); + exit(1); + } + + if (c->stream) { + if (!mrp_transport_listen(c->lt, 0)) { + mrp_log_error("Failed to listen on server transport."); + exit(1); + } + } +} + + +void send_msg(context_t *c) +{ + mrp_msg_t *msg; + uint32_t seq; + char buf[256]; + char *astr[] = { "this", "is", "an", "array", "of", "strings" }; + uint32_t au32[] = { 1, 2, 3, + 1 << 16, 2 << 16, 3 << 16, + 1 << 24, 2 << 24, 3 << 24 }; + uint32_t nstr = MRP_ARRAY_SIZE(astr); + uint32_t nu32 = MRP_ARRAY_SIZE(au32); + int status; + + seq = c->seqno++; + snprintf(buf, sizeof(buf), "this is message #%u", (unsigned int)seq); + + msg = mrp_msg_create(TAG_SEQ , MRP_MSG_FIELD_UINT32, seq, + TAG_MSG , MRP_MSG_FIELD_STRING, buf, + TAG_U8 , MRP_MSG_FIELD_UINT8 , seq & 0xf, + TAG_S8 , MRP_MSG_FIELD_SINT8 , -(seq & 0xf), + TAG_U16 , MRP_MSG_FIELD_UINT16, seq, + TAG_S16 , MRP_MSG_FIELD_SINT16, - seq, + TAG_DBL , MRP_MSG_FIELD_DOUBLE, seq / 3.0, + TAG_BLN , MRP_MSG_FIELD_BOOL , seq & 0x1, + TAG_ASTR, MRP_MSG_FIELD_ARRAY_OF(STRING), nstr, astr, + TAG_AU32, MRP_MSG_FIELD_ARRAY_OF(UINT32), nu32, au32, + TAG_END); + + if (msg == NULL) { + mrp_log_error("Failed to create new message."); + exit(1); + } + + if (c->connect) + status = mrp_transport_send(c->t, msg); + else + status = mrp_transport_sendto(c->t, msg, &c->addr, c->alen); + + if (!status) { + mrp_log_error("Failed to send message #%d.", seq); + exit(1); + } + else + mrp_log_info("Message #%d succesfully sent.", seq); + + mrp_msg_unref(msg); +} + + +void send_data(context_t *c) +{ + uint32_t seq = c->seqno++; + custom_t msg; + char buf[256]; + char *astr[] = { "this", "is", "a", "test", "string", "array" }; + uint32_t au32[] = { 1, 2, 3, 4, 5, 6, 7, -1 }; + int status; + + msg.seq = seq; + snprintf(buf, sizeof(buf), "this is message #%u", (unsigned int)seq); + msg.msg = buf; + msg.u8 = seq & 0xf; + msg.s8 = -(seq & 0xf); + msg.u16 = seq; + msg.s16 = - seq; + msg.dbl = seq / 3.0; + msg.bln = seq & 0x1; + msg.astr = astr; + msg.nstr = MRP_ARRAY_SIZE(astr); + msg.fsck = 1000; + msg.au32 = au32; + msg.rpl = ""; + + if (c->connect) + status = mrp_transport_senddata(c->t, &msg, data_descr->tag); + else + status = mrp_transport_senddatato(c->t, &msg, data_descr->tag, + &c->addr, c->alen); + + if (!status) { + mrp_log_error("Failed to send message #%d.", msg.seq); + exit(1); + } + else + mrp_log_info("Message #%d succesfully sent.", msg.seq); +} + + +void send_raw(context_t *c) +{ + uint32_t seq = c->seqno++; + char msg[256]; + size_t size; + int status; + + size = snprintf(msg, sizeof(msg), "this is message #%u", seq); + + if (c->connect) + status = mrp_transport_sendraw(c->t, msg, size); + else + status = mrp_transport_sendrawto(c->t, msg, size, &c->addr, c->alen); + + if (!status) { + mrp_log_error("Failed to send raw message #%d.", seq); + exit(1); + } + else + mrp_log_info("Message #%u succesfully sent.", seq); +} + + + +void send_cb(mrp_timer_t *t, void *user_data) +{ + context_t *c = (context_t *)user_data; + + MRP_UNUSED(t); + + switch (c->mode) { + case MODE_DATA: send_data(c); break; + case MODE_RAW: send_raw(c); break; + default: + case MODE_MESSAGE: send_msg(c); + } +} + + +void client_init(context_t *c) +{ + static mrp_transport_evt_t evt = { + { .recvmsg = NULL }, + { .recvmsgfrom = NULL }, + .closed = closed_evt, + .connection = NULL + }; + + int flags; + + type_init(c); + + switch (c->mode) { + case MODE_DATA: + evt.recvdata = recv_data; + evt.recvdatafrom = recvfrom_data; + flags = MRP_TRANSPORT_MODE_DATA; + break; + case MODE_RAW: + evt.recvraw = recv_raw; + evt.recvrawfrom = recvfrom_raw; + flags = MRP_TRANSPORT_MODE_RAW; + break; + default: + case MODE_MESSAGE: + evt.recvmsg = recv_msg; + evt.recvmsgfrom = recvfrom_msg; + flags = MRP_TRANSPORT_MODE_MSG; + } + + c->t = mrp_transport_create(c->ml, c->atype, &evt, c, flags); + + if (c->t == NULL) { + mrp_log_error("Failed to create new transport."); + exit(1); + } + + if (!strcmp(c->atype, "unxd")) { + char addrstr[] = "unxd:@stream-test-client"; + mrp_sockaddr_t addr; + socklen_t alen; + + alen = mrp_transport_resolve(NULL, addrstr, &addr, sizeof(addr), NULL); + if (alen <= 0) { + mrp_log_error("Failed to resolve transport address '%s'.", addrstr); + exit(1); + } + + if (!mrp_transport_bind(c->t, &addr, alen)) { + mrp_log_error("Failed to bind to transport address '%s'.", addrstr); + exit(1); + } + } + + if (c->connect) { + if (!mrp_transport_connect(c->t, &c->addr, c->alen)) { + mrp_log_error("Failed to connect to %s.", c->addrstr); + exit(1); + } + } + + + c->timer = mrp_add_timer(c->ml, 1000, send_cb, c); + + if (c->timer == NULL) { + mrp_log_error("Failed to create send timer."); + exit(1); + } +} + + +static void print_usage(const char *argv0, int exit_code, const char *fmt, ...) +{ + va_list ap; + + if (fmt && *fmt) { + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + } + + printf("usage: %s [options] [transport-address]\n\n" + "The possible options are:\n" + " -s, --server run as test server (default)\n" + " -C, --connect connect transport\n" + " For connection-oriented transports, this is automatic.\n" + " -a, --address address to use\n" + " -c, --custom use custom messages\n" + " -m, --message use generic messages (default)\n" + " -r, --raw use raw messages\n" + " -b, --buggy use buggy data descriptors\n" + " -t, --log-target=TARGET log target to use\n" + " TARGET is one of stderr,stdout,syslog, or a logfile path\n" + " -l, --log-level=LEVELS logging level to use\n" + " LEVELS is a comma separated list of info, error and warning\n" + " -v, --verbose increase logging verbosity\n" + " -d, --debug enable debug messages\n" + " -h, --help show help on usage\n", + argv0); + + if (exit_code < 0) + return; + else + exit(exit_code); +} + + +static void config_set_defaults(context_t *ctx) +{ + mrp_clear(ctx); + ctx->addrstr = "tcp4:127.0.0.1:3000"; + ctx->server = FALSE; + ctx->log_mask = MRP_LOG_UPTO(MRP_LOG_DEBUG); + ctx->log_target = MRP_LOG_TO_STDERR; +} + + +int parse_cmdline(context_t *ctx, int argc, char **argv) +{ +# define OPTIONS "scmrbCa:l:t:v:d:h" + struct option options[] = { + { "server" , no_argument , NULL, 's' }, + { "address" , required_argument, NULL, 'a' }, + { "custom" , no_argument , NULL, 'c' }, + { "message" , no_argument , NULL, 'm' }, + { "raw" , no_argument , NULL, 'r' }, + { "connect" , no_argument , NULL, 'C' }, + + { "buggy" , no_argument , NULL, 'b' }, + { "log-level" , required_argument, NULL, 'l' }, + { "log-target", required_argument, NULL, 't' }, + { "verbose" , optional_argument, NULL, 'v' }, + { "debug" , required_argument, NULL, 'd' }, + { "help" , no_argument , NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + + int opt; + + config_set_defaults(ctx); + + while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) { + switch (opt) { + case 's': + ctx->server = TRUE; + break; + + case 'c': + if (ctx->mode == MODE_DEFAULT) + ctx->mode = MODE_DATA; + else { + mrp_log_error("Multiple modes requested."); + exit(1); + } + break; + + case 'm': + if (ctx->mode == MODE_DEFAULT) + ctx->mode = MODE_MESSAGE; + else { + mrp_log_error("Multiple modes requested."); + exit(1); + } + break; + + case 'r': + if (ctx->mode == MODE_DEFAULT) + ctx->mode = MODE_RAW; + else { + mrp_log_error("Multiple modes requested."); + exit(1); + } + break; + + case 'b': + ctx->buggy = TRUE; + break; + + case 'C': + ctx->connect = TRUE; + break; + + case 'a': + ctx->addrstr = optarg; + break; + + case 'v': + ctx->log_mask <<= 1; + ctx->log_mask |= 1; + break; + + case 'l': + ctx->log_mask = mrp_log_parse_levels(optarg); + if (ctx->log_mask < 0) + print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg); + break; + + case 't': + ctx->log_target = mrp_log_parse_target(optarg); + if (!ctx->log_target) + print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg); + break; + + case 'd': + ctx->log_mask |= MRP_LOG_MASK_DEBUG; + mrp_debug_set_config(optarg); + mrp_debug_enable(TRUE); + break; + + case 'h': + print_usage(argv[0], -1, ""); + exit(0); + break; + + default: + print_usage(argv[0], EINVAL, "invalid option '%c'", opt); + } + } + + return TRUE; +} + + +int main(int argc, char *argv[]) +{ + context_t c; + + if (!parse_cmdline(&c, argc, argv)) + exit(1); + + mrp_log_set_mask(c.log_mask); + mrp_log_set_target(c.log_target); + + if (c.server) + mrp_log_info("Running as server, using address '%s'...", c.addrstr); + else + mrp_log_info("Running as client, using address '%s'...", c.addrstr); + + switch (c.mode) { + case MODE_DATA: mrp_log_info("Using custom data messages..."); break; + case MODE_RAW: mrp_log_info("Using raw messages..."); break; + default: + case MODE_MESSAGE: mrp_log_info("Using generic messages..."); + } + + if (!strncmp(c.addrstr, "tcp", 3) || !strncmp(c.addrstr, "unxs", 4) || + !strncmp(c.addrstr, "wsck", 4)) { + c.stream = TRUE; + c.connect = TRUE; + } + + c.alen = mrp_transport_resolve(NULL, c.addrstr, + &c.addr, sizeof(c.addr), &c.atype); + if (c.alen <= 0) { + mrp_log_error("Failed to resolve transport address '%s'.", c.addrstr); + exit(1); + } + + c.ml = mrp_mainloop_create(); + + if (c.server) + server_init(&c); + else + client_init(&c); + + mrp_mainloop_run(c.ml); + + return 0; +} diff --git a/src/common/tests/mainloop-ecore-test.c b/src/common/tests/mainloop-ecore-test.c new file mode 100644 index 0000000..d094fac --- /dev/null +++ b/src/common/tests/mainloop-ecore-test.c @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef ECORE_ENABLED + +struct ecore_config_s { + int dummy; +}; + + +mrp_mainloop_t *ecore_mainloop_create(test_config_t *cfg) +{ + cfg->ml = mrp_mainloop_ecore_get(); + + return cfg->ml; +} + + +int ecore_mainloop_run(test_config_t *cfg) +{ + MRP_UNUSED(cfg); + + ecore_main_loop_begin(); + + return TRUE; +} + + +int ecore_mainloop_quit(test_config_t *cfg) +{ + MRP_UNUSED(cfg); + + ecore_main_loop_quit(); + + return TRUE; +} + + +int ecore_mainloop_cleanup(test_config_t *cfg) +{ + mrp_mainloop_unregister(cfg->ml); + mrp_mainloop_destroy(cfg->ml); + + cfg->ml = NULL; + + return TRUE; +} + + +#else + + +mrp_mainloop_t *ecore_mainloop_create(test_config_t *cfg) +{ + MRP_UNUSED(cfg); + + mrp_log_error("EFL/ecore mainloop support is not available."); + exit(1); +} + + +int ecore_mainloop_run(test_config_t *cfg) +{ + MRP_UNUSED(cfg); + + mrp_log_error("EFL/ecore mainloop support is not available."); + exit(1); +} + + +int ecore_mainloop_quit(test_config_t *cfg) +{ + MRP_UNUSED(cfg); + + mrp_log_error("EFL/ecore mainloop support is not available."); + exit(1); +} + + +int ecore_mainloop_cleanup(test_config_t *cfg) +{ + MRP_UNUSED(cfg); + + mrp_log_error("EFL/ecore mainloop support is not available."); + exit(1); +} + + +#endif diff --git a/src/common/tests/mainloop-glib-test.c b/src/common/tests/mainloop-glib-test.c new file mode 100644 index 0000000..a13741e --- /dev/null +++ b/src/common/tests/mainloop-glib-test.c @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef GLIB_ENABLED + +#include <murphy/common/glib-glue.h> + +struct glib_config_s { + GMainLoop *gml; +}; + + +mrp_mainloop_t *glib_mainloop_create(test_config_t *cfg) +{ + glib_config_t *glib; + mrp_mainloop_t *ml; + + glib = mrp_allocz(sizeof(*glib)); + + if (glib != NULL) { + glib->gml = g_main_loop_new(NULL, FALSE); + ml = mrp_mainloop_glib_get(glib->gml); + + if (ml != NULL) { + cfg->glib = glib; + cfg->ml = ml; + + return ml; + } + else { + g_main_loop_unref(glib->gml); + mrp_free(glib); + } + } + + return NULL; +} + + +int glib_mainloop_run(test_config_t *cfg) +{ + if (cfg->glib != NULL) { + g_main_loop_run(cfg->glib->gml); + return TRUE; + } + else + return FALSE; +} + + +int glib_mainloop_quit(test_config_t *cfg) +{ + if (cfg->glib != NULL) { + g_main_loop_quit(cfg->glib->gml); + return TRUE; + } + else + return FALSE; +} + + +int glib_mainloop_cleanup(test_config_t *cfg) +{ + if (cfg->glib != NULL) { + mrp_mainloop_unregister(cfg->ml); + mrp_mainloop_destroy(cfg->ml); + cfg->ml = NULL; + + g_main_loop_unref(cfg->glib->gml); + mrp_free(cfg->glib); + cfg->glib = NULL; + + return TRUE; + } + else + return FALSE; +} + + +#else + + +mrp_mainloop_t *glib_mainloop_create(test_config_t *cfg) +{ + MRP_UNUSED(cfg); + + mrp_log_error("glib mainloop support is not available."); + exit(1); +} + + +int glib_mainloop_run(test_config_t *cfg) +{ + MRP_UNUSED(cfg); + + mrp_log_error("glib mainloop support is not available."); + exit(1); +} + + +int glib_mainloop_quit(test_config_t *cfg) +{ + MRP_UNUSED(cfg); + + mrp_log_error("glib mainloop support is not available."); + exit(1); +} + + +int glib_mainloop_cleanup(test_config_t *cfg) +{ + MRP_UNUSED(cfg); + + mrp_log_error("glib mainloop support is not available."); + exit(1); +} + + +#endif diff --git a/src/common/tests/mainloop-pulse-test.c b/src/common/tests/mainloop-pulse-test.c new file mode 100644 index 0000000..58945dc --- /dev/null +++ b/src/common/tests/mainloop-pulse-test.c @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef PULSE_ENABLED + +#include <murphy/common/pulse-glue.h> + +struct pulse_config_s { + pa_mainloop *pa_main; + pa_mainloop_api *pa; +}; + + +mrp_mainloop_t *pulse_mainloop_create(test_config_t *cfg) +{ + pulse_config_t *pulse; + mrp_mainloop_t *ml; + + pulse = mrp_allocz(sizeof(*pulse)); + + if (pulse != NULL) { + pulse->pa_main = pa_mainloop_new(); + pulse->pa = pa_mainloop_get_api(pulse->pa_main); + ml = mrp_mainloop_pulse_get(pulse->pa); + + if (ml != NULL) { + cfg->pulse = pulse; + cfg->ml = ml; + + return ml; + } + else { + pa_mainloop_free(pulse->pa_main); + mrp_free(pulse); + } + } + + return NULL; +} + + +int pulse_mainloop_run(test_config_t *cfg) +{ + int retval; + + if (cfg->pulse && cfg->pulse->pa != NULL) { + pa_mainloop_run(cfg->pulse->pa_main, &retval); + + return TRUE; + } + else + return FALSE; +} + + +int pulse_mainloop_quit(test_config_t *cfg) +{ + if (cfg->pulse && cfg->pulse->pa) { + pa_mainloop_quit(cfg->pulse->pa_main, 0); + return TRUE; + } + else + return FALSE; +} + + +int pulse_mainloop_cleanup(test_config_t *cfg) +{ + if (cfg->pulse != NULL) { + mrp_mainloop_unregister(cfg->ml); + mrp_mainloop_destroy(cfg->ml); + cfg->ml = NULL; + + pa_mainloop_free(cfg->pulse->pa_main); + mrp_free(cfg->pulse); + cfg->pulse = NULL; + + return TRUE; + } + else + return FALSE; +} + + +#else + + +mrp_mainloop_t *pulse_mainloop_create(test_config_t *cfg) +{ + mrp_log_error("PulseAudio mainloop support is not available."); + exit(1); +} + + +int pulse_mainloop_run(test_config_t *cfg) +{ + mrp_log_error("PulseAudio mainloop support is not available."); + exit(1); +} + + +int pulse_mainloop_quit(test_config_t *cfg) +{ + mrp_log_error("PulseAudio mainloop support is not available."); + exit(1); +} + + +int pulse_mainloop_cleanup(test_config_t *cfg) +{ + mrp_log_error("PulseAudio mainloop support is not available."); + exit(1); +} + + +#endif diff --git a/src/common/tests/mainloop-qt-test.cpp b/src/common/tests/mainloop-qt-test.cpp new file mode 100644 index 0000000..4c047a6 --- /dev/null +++ b/src/common/tests/mainloop-qt-test.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <murphy/config.h> +#include <murphy/common/mm.h> +#include <murphy/common/log.h> +#include <murphy/common/mainloop.h> + +#include "mainloop-qt-test.h" + +#include <QCoreApplication> +#include <murphy/common/qt-glue.h> + + +typedef struct qt_config_s { + QCoreApplication *app; + mrp_mainloop_t *ml; +} qt_config_t ; + +static qt_config_t *qt; + +mrp_mainloop_t *qt_mainloop_create() +{ + mrp_mainloop_t *ml = NULL; + + if (qt == NULL) { + int argc = 0; + char **argv = 0; + + qt = (qt_config_t *)mrp_allocz(sizeof(*qt)); + if (!qt) return NULL; + + qt->app = new QCoreApplication(argc, argv); + ml = mrp_mainloop_qt_get(); + + if (ml == NULL) { + delete qt->app; + mrp_free(qt); + qt = NULL; + } + } + else { + return qt->ml; + } + + return ml; +} + +int qt_mainloop_run() +{ + if (qt != NULL) { + QCoreApplication::exec(); + return TRUE; + } + else + return FALSE; +} + +int qt_mainloop_quit() +{ + if (qt != NULL) { + QCoreApplication::quit(); + return TRUE; + } + else + return FALSE; +} + +int qt_mainloop_cleanup(mrp_mainloop_t *ml) +{ + if (qt != NULL) { + mrp_mainloop_unregister(ml); + mrp_mainloop_destroy(ml); + + delete qt->app; + mrp_free(qt); + qt = NULL; + + return TRUE; + } + else + return FALSE; +} diff --git a/src/common/tests/mainloop-qt-test.h b/src/common/tests/mainloop-qt-test.h new file mode 100644 index 0000000..cfa3f3e --- /dev/null +++ b/src/common/tests/mainloop-qt-test.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_QT_TEST_H_ +#define __MURPHY_QT_TEST_H_ + +#include <murphy/config.h> +#include <murphy/common/macros.h> +#include <murphy/common/mainloop.h> + +MRP_CDECL_BEGIN + +mrp_mainloop_t *qt_mainloop_create(void); +int qt_mainloop_run(void); +int qt_mainloop_quit(void); +int qt_mainloop_cleanup(mrp_mainloop_t *ml); + +MRP_CDECL_END + +#endif /* __MURPHY_QT_TEST_H_ */ diff --git a/src/common/tests/mainloop-test.c b/src/common/tests/mainloop-test.c new file mode 100644 index 0000000..77dea36 --- /dev/null +++ b/src/common/tests/mainloop-test.c @@ -0,0 +1,1795 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <stdint.h> +#include <string.h> +#include <stdarg.h> +#include <unistd.h> +#include <signal.h> +#include <getopt.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <dbus/dbus.h> + +#include <murphy/config.h> +#include <murphy/common/macros.h> +#include <murphy/common/debug.h> +#include <murphy/common/log.h> +#include <murphy/common/mm.h> +#include <murphy/common/mainloop.h> + +#ifdef PULSE_ENABLED +# include <pulse/mainloop.h> +# include <murphy/common/pulse-glue.h> +#endif + +#ifdef ECORE_ENABLED +# include <Ecore.h> +# include <murphy/common/ecore-glue.h> +#endif + +#ifdef GLIB_ENABLED +# include <glib.h> +# include "glib-pump.c" +# include <murphy/common/glib-glue.h> +#endif + +#ifdef QT_ENABLED +#include <murphy/common/qt-glue.h> +#endif + +#define info(fmt, args...) do { \ + fprintf(stdout, "I: "fmt"\n" , ## args); \ + fflush(stdout); \ + } while (0) + +#define warning(fmt, args...) do { \ + fprintf(stderr, "W: "fmt"\n" , ## args); \ + fflush(stderr); \ + } while (0) + +#define error(fmt, args...) do { \ + fprintf(stderr, "E: "fmt"\n" , ## args); \ + fflush(stderr); \ + } while (0) + +#define fatal(fmt, args...) do { \ + fprintf(stderr, "C: "fmt"\n" , ## args); \ + fflush(stderr); \ + exit(1); \ + } while (0) + +#define USECS_PER_SEC (1000 * 1000) + +#define DEFAULT_RUNTIME 30 /* run for 30 seconds */ + + +enum { + MAINLOOP_NATIVE, + MAINLOOP_PULSE, + MAINLOOP_ECORE, + MAINLOOP_GLIB, + MAINLOOP_QT +}; + + +struct pulse_config_s; +typedef struct pulse_config_s pulse_config_t; + +struct ecore_config_s; +typedef struct ecore_config_s ecore_config_t; + +struct glib_config_s; +typedef struct glib_config_s glib_config_t; + + + +typedef struct { + int nio; + int ntimer; + int deferred; + int nsignal; + + int ngio; + int ngtimer; + + int ndbus_method; + int ndbus_signal; + + int log_mask; + const char *log_target; + + int mainloop_type; + + mrp_mainloop_t *ml; + pulse_config_t *pulse; + ecore_config_t *ecore; + glib_config_t *glib; + + int nrunning; + int runtime; + + pid_t child; + unsigned int wlpf; + unsigned int wfrc; +} test_config_t; + + +#include "mainloop-pulse-test.c" +#include "mainloop-ecore-test.c" +#include "mainloop-glib-test.c" +#include "mainloop-qt-test.h" + +static test_config_t cfg; + + +static mrp_mainloop_t *mainloop_create(test_config_t *cfg); +static void mainloop_run(test_config_t *cfg); +static void mainloop_quit(test_config_t *cfg); +static void mainloop_cleanup(test_config_t *cfg); + + +/* + * native timers + */ + +#define TIMER_INTERVALS 1, 2, 3, 4, 6, 8, 1, 3, 12, 15, 18, 21, 24 + + +typedef struct { + int id; + mrp_timer_t *timer; + int interval; + int count; + int target; + struct timeval prev; +} test_timer_t; + + +static test_timer_t *timers; + +static mrp_wakeup_t *wakeup; +static mrp_wakeup_t *wuplim; + + + +static int timeval_diff(struct timeval *tv1, struct timeval *tv2) +{ + int64_t u1, u2; + + u1 = tv1->tv_sec * USECS_PER_SEC + tv1->tv_usec; + u2 = tv2->tv_sec * USECS_PER_SEC + tv2->tv_usec; + + return (int)(u1 - u2); +} + + +static void timeval_now(struct timeval *tv) +{ + gettimeofday(tv, NULL); +} + + +void timer_cb(mrp_timer_t *timer, void *user_data) +{ + test_timer_t *t = (test_timer_t *)user_data; + struct timeval now; + double diff, error; + + MRP_UNUSED(timer); + + timeval_now(&now); + diff = timeval_diff(&now, &t->prev) / 1000.0; + error = diff - t->interval; + if (error < 0.0) + error = -error; + + info("MRPH timer #%d: %d/%d, diff %.2f (lag %.2f, %.3f %%)", + t->id, t->count, t->target, diff, error, 100 * error / diff); + + t->count++; + t->prev = now; + + if (t->count >= t->target) { + info("MRPH timer #%d has finished.", t->id); + + mrp_del_timer(t->timer); + t->timer = NULL; + cfg.nrunning--; + } +} + + +static void setup_timers(mrp_mainloop_t *ml) +{ + test_timer_t *t; + int intervals[] = { TIMER_INTERVALS, 0 }, *iv = intervals; + int msecs, i; + + if ((timers = mrp_allocz_array(test_timer_t, cfg.ntimer)) != NULL) { + for (i = 0, t = timers; i < cfg.ntimer; i++, t++) { + t->id = i; + + msecs = *iv; + while (cfg.runtime / msecs < 1 && msecs > 0) + msecs /= 2; + msecs *= 1000; + if (!msecs) + msecs = 500; + + t->interval = msecs; + t->target = 1000 * cfg.runtime / msecs; + if (!t->target) + continue; + + timeval_now(&t->prev); + t->timer = mrp_add_timer(ml, t->interval, timer_cb, t); + + if (t->timer != NULL) + info("MRPH timer #%d: interval=%d, target=%d", t->id, *iv, + t->target); + else + fatal("MRPH timer #%d: failed to create", t->id); + + cfg.nrunning++; + iv++; + if (!*iv) + iv = intervals; + } + } + else + if (cfg.ntimer > 0) + fatal("could not allocate %d timers", cfg.ntimer); +} + + +static void check_timers(void) +{ + test_timer_t *t; + int i; + + for (i = 0, t = timers; i < cfg.ntimer; i++, t++) { + if (t->target != 0 && t->count != t->target) + warning("MRPH timer #%d: FAIL (only %d/%d)", t->id, t->count, + t->target); + else + info("MRPH timer #%d: OK (%d/%d)", t->id, t->count, t->target); + } +} + + +/* + * native I/O + */ + +#define IO_INTERVALS 1, 3, 5, 9, 12, 15, 18, 21 + +typedef struct { + int id; + int pipe[2]; + mrp_io_watch_t *watch; + mrp_timer_t *timer; + int target; + int sent; + int received; +} test_io_t; + + +static test_io_t *ios; + + +static void send_io(mrp_timer_t *timer, void *user_data) +{ + test_io_t *w = (test_io_t *)user_data; + char buf[1024]; + int plural, size; + + MRP_UNUSED(timer); + + plural = (w->target - w->sent) != 1; + size = snprintf(buf, sizeof(buf), + "I/O #%d: %d message%s remain%s.", w->id, + w->target - w->sent, + plural ? "s" : "", plural ? "" : "s"); + + if (write(w->pipe[1], buf, size) < 0) { + /* just ignore it... */ + } + w->sent++; + + info("MRPH I/O #%d: sent message %d/%d.", w->id, w->sent, w->target); + + if (w->sent >= w->target) { + info("MRPH I/O #%d: sending done.", w->id); + + close(w->pipe[1]); + mrp_del_timer(timer); + w->timer = NULL; + + cfg.nrunning--; + } +} + + +static void recv_io(mrp_io_watch_t *watch, int fd, mrp_io_event_t events, + void *user_data) +{ + test_io_t *w = (test_io_t *)user_data; + char buf[1024]; + int size; + + MRP_UNUSED(watch); + + if (watch != w->watch) + fatal("MRPH I/O #%d called with incorrect data.", w->id); + + if (events & MRP_IO_EVENT_IN) { + size = read(fd, buf, sizeof(buf) - 1); + + if (size > 0) { + w->received++; + buf[size] = '\0'; + info("MRPH I/O #%d: received message [%s]", w->id, buf); + } + else + warning("MRPH I/O #%d: got empty message", w->id); + } + + if (events & MRP_IO_EVENT_HUP) { + info("MRPH I/O #%d: receiver done (got %d/%d)", w->id, w->received, + w->sent); + close(w->pipe[0]); + mrp_del_io_watch(watch); + } +} + + +void setup_io(mrp_mainloop_t *ml) +{ + test_io_t *w; + mrp_io_event_t mask; + int intervals[] = { IO_INTERVALS, 0 }, *iv = intervals; + int msecs, i; + + + if ((ios = mrp_allocz_array(test_io_t, cfg.nio)) != NULL) { + mask = MRP_IO_EVENT_IN | MRP_IO_EVENT_HUP; + + for (i = 0, w = ios; i < cfg.nio; i++, w++) { + w->id = i; + + msecs = *iv; + while (cfg.runtime / msecs < 1 && msecs > 0) + msecs /= 2; + msecs *= 1000; + if (!msecs) + msecs = 500; + + w->target = 1000 * cfg.runtime / msecs; + if (!w->target) + continue; + + if (pipe(w->pipe) != 0) + fatal("MRPH I/O #%d: could not create pipe", w->id); + + w->watch = mrp_add_io_watch(ml, w->pipe[0], mask, recv_io, w); + w->timer = mrp_add_timer(ml, msecs, send_io, w); + + if (w->timer == NULL) + fatal("MRPH I/O #%d: could not create I/O timer", w->id); + + if (w->watch == NULL) + fatal("MRPH I/O #%d: could not create I/O watch", w->id); + else + info("MRPH I/O #%d: interval=%d, target=%d", w->id, *iv, + w->target); + + cfg.nrunning++; + iv++; + if (!*iv) + iv = intervals; + } + } + else + if (cfg.nio > 0) + fatal("could not allocate %d I/O watches", cfg.nio); +} + + +static void check_io(void) +{ + test_io_t *w; + int i; + + for (i = 0, w = ios; i < cfg.nio; i++, w++) { + if (w->target != 0 && w->sent != w->received) + warning("MRPH I/O #%d: FAIL (only %d/%d)", w->id, w->received, + w->sent); + else + info("MRPH I/O #%d: OK (%d/%d)", w->id, w->received, w->sent); + } +} + + +/* + * native deferred/idle callbacks + */ + + +static void setup_deferred(void) +{ + return; +} + + + +/* + * native signals + */ + +#define SIG_INTERVALS 1, 5, 9, 3, 6, 12 +#define SIGNUMS { SIGUSR1, SIGUSR2, SIGTERM, SIGCONT, SIGQUIT, 0 } + +static const char *signames[] = { + [SIGINT] = "SIGINT", [SIGTERM] = "SIGTERM", [SIGQUIT] = "SIGQUIT", + [SIGCONT] = "SIGCONT", [SIGUSR1] = "SIGUSR1", [SIGUSR2] = "SIGUSR2", + [SIGCHLD] = "SIGCHLD" +}; + + +typedef struct { + int id; + int signum; + mrp_sighandler_t *watch; + mrp_timer_t *timer; + int target; + int sent; + int received; +} test_signal_t; + +test_signal_t *signals; + + +static void send_signal(mrp_timer_t *timer, void *user_data) +{ + test_signal_t *t = (test_signal_t *)user_data; + + MRP_UNUSED(timer); + + if (t->sent >= t->target) + return; + + kill(getpid(), t->signum); + t->sent++; + info("MRPH signal #%d: sent signal %d/%d of %s", t->id, + t->sent, t->target, strsignal(t->signum)); + + if (t->sent >= t->target) { + info("MRPH signal #%d: sending done", t->id); + mrp_del_timer(t->timer); + t->timer = NULL; + } +} + + +static void recv_signal(mrp_sighandler_t *h, int signum, void *user_data) +{ + test_signal_t *t = (test_signal_t *)user_data; + + MRP_UNUSED(h); + + if (h != t->watch) + fatal("MRPH signal #%d called with incorrect data", t->id); + + t->received++; + info("MRPH signal #%d: received signal %d/%d of %s", t->id, + t->received, t->target, signames[signum]); + + if (t->sent >= t->target) { + info("MRPH signal #%d: receiving done", t->id); + cfg.nrunning--; + } +} + + +static void setup_signals(mrp_mainloop_t *ml) +{ + test_signal_t *t; + int intervals[] = { SIG_INTERVALS, 0 }, *iv = intervals; + int signums[] = SIGNUMS, *s = signums; + int msecs, i; + + if ((signals = mrp_allocz_array(test_signal_t, cfg.nsignal)) != NULL) { + for (i = 0, t = signals; i < cfg.nsignal; i++, t++) { + t->id = i; + t->signum = *s; + + msecs = *iv; + while (cfg.runtime / msecs < 1 && msecs > 0) + msecs /= 2; + msecs *= 1000; + if (!msecs) + msecs = 500; + + t->target = 1000 * cfg.runtime / msecs; + if (!t->target) + continue; + + t->watch = mrp_add_sighandler(ml, *s, recv_signal, t); + t->timer = mrp_add_timer(ml, msecs, send_signal, t); + + if (t->timer == NULL) + fatal("MRPH signal #%d: could not create timer", t->id); + + if (t->watch == NULL) + fatal("MRPH signal #%d: could not create watch", t->id); + else + info("MRPH signal #%d: interval=%d, target=%d", t->id, *iv, + t->target); + + cfg.nrunning++; + iv++; + if (!*iv) + iv = intervals; + + s++; + if (!*s) + s = signums; + } + } + else + if (cfg.nsignal > 0) + fatal("could not allocate %d signal watches", cfg.nsignal); +} + + +static void check_signals(void) +{ + test_signal_t *t; + int i; + + for (i = 0, t = signals; i < cfg.nsignal; i++, t++) { + if (t->sent < t->received) + warning("MRPH signal #%d: FAIL (only %d/%d", t->id, + t->received, t->sent); + else + info("MRPH signal #%d: OK (%d/%d)", t->id, t->received, t->sent); + } +} + + +static void wakeup_cb(mrp_wakeup_t *w, mrp_wakeup_event_t event, + void *user_data) +{ + static struct timeval prev[2] = { {0, 0}, {0, 0} }; + const char *evt; + struct timeval now; + double diff; + int id; + + MRP_UNUSED(w); + MRP_UNUSED(user_data); + + timeval_now(&now); + + switch (event) { + case MRP_WAKEUP_EVENT_TIMER: evt = "timer"; break; + case MRP_WAKEUP_EVENT_IO: evt = "I/O (or signal)"; break; + case MRP_WAKEUP_EVENT_LIMIT: evt = "limit"; break; + default: evt = "???"; + } + + id = user_data ? 1 : 0; + + if (MRP_LIKELY(prev[id].tv_usec != 0)) { + diff = timeval_diff(&now, &prev[id]) / 1000.0; + info("woken up #%d by %s, %.2f msecs since previous", id, evt, diff); + } + + prev[id] = now; +} + + +static void setup_wakeup(mrp_mainloop_t *ml) +{ + unsigned int nolim = MRP_WAKEUP_NOLIMIT; + + if (cfg.child == 0) + return; + + wakeup = mrp_add_wakeup(ml, MRP_WAKEUP_EVENT_ANY, nolim, nolim, + wakeup_cb, (void *)0); + wuplim = mrp_add_wakeup(ml, MRP_WAKEUP_EVENT_ANY, cfg.wlpf, cfg.wfrc, + wakeup_cb, (void *)1); +} + + +static void cleanup_wakeup(void) +{ + mrp_del_wakeup(wakeup); + wakeup = NULL; + mrp_del_wakeup(wuplim); + wuplim = NULL; +} + + +static void check_quit(mrp_timer_t *timer, void *user_data) +{ + MRP_UNUSED(user_data); + + if (cfg.nrunning <= 0) { + mrp_del_timer(timer); + mainloop_quit(&cfg); + } +} + + + +#ifdef GLIB_ENABLED +/* + * glib timers + */ + +#define GTIMER_INTERVALS 1, 2, 3, 4, 6, 8, 1, 3, 12, 15, 18, 21, 24 + +typedef struct { + int id; + guint gsrc; + int interval; + int count; + int target; + struct timeval prev; +} glib_timer_t; + + +static glib_timer_t *gtimers; + + +static gboolean glib_timer_cb(gpointer user_data) +{ + glib_timer_t *t = (glib_timer_t *)user_data; + struct timeval now; + double diff, error; + + timeval_now(&now); + diff = timeval_diff(&now, &t->prev) / 1000.0; + error = diff - t->interval; + if (error < 0.0) + error = -error; + + info("GLIB timer #%d: %d/%d, diff %.2f (lag %.2f, %.3f %%)", + t->id, t->count, t->target, diff, error, 100 * error / diff); + + t->count++; + t->prev = now; + + if (t->count >= t->target) { + info("GLIB timer #%d has finished.", t->id); + + t->gsrc = 0; + cfg.nrunning--; + return FALSE; + } + else + return TRUE; +} + + +static void setup_glib_timers(void) +{ + glib_timer_t *t; + int intervals[] = { GTIMER_INTERVALS, 0 }, *iv = intervals; + int msecs, i; + + if ((gtimers = mrp_allocz_array(glib_timer_t, cfg.ngtimer)) != NULL) { + for (i = 0, t = gtimers; i < cfg.ngtimer; i++, t++) { + t->id = i; + + msecs = *iv; + while (cfg.runtime / msecs < 1 && msecs > 0) + msecs /= 2; + msecs *= 1000; + if (!msecs) + msecs = 500; + + t->interval = msecs; + t->target = 1000 * cfg.runtime / msecs; + if (!t->target) + continue; + + timeval_now(&t->prev); + t->gsrc = g_timeout_add(msecs, glib_timer_cb, t); + + if (t->gsrc != 0) + info("GLIB timer #%d: interval=%d, target=%d", t->id, *iv, + t->target); + else + fatal("GLIB timer #%d: failed to create", t->id); + + cfg.nrunning++; + iv++; + if (!*iv) + iv = intervals; + } + } + else + if (cfg.ntimer > 0) + fatal("could not allocate %d GLIB timers", cfg.ngtimer); +} + + +static void check_glib_timers(void) +{ + glib_timer_t *t; + int i; + + for (i = 0, t = gtimers; i < cfg.ngtimer; i++, t++) { + if (t->target != 0 && t->count != t->target) + warning("GLIB timer #%d: FAIL (only %d/%d)", t->id, t->count, + t->target); + else + info("GLIB timer #%d: OK (%d/%d)", t->id, t->count, t->target); + } +} + + +/* + * glib I/O + */ + +#define GIO_INTERVALS 1, 3, 4, 5, 6, 7, 9, 12, 15, 18, 21 + +typedef struct { + int id; + int pipe[2]; + GIOChannel *gioc; + guint gsrc; + guint timer; + int target; + int sent; + int received; +} glib_io_t; + + +static glib_io_t *gios; + + +static gboolean glib_send_io(gpointer user_data) +{ + glib_io_t *t = (glib_io_t *)user_data; + char buf[1024]; + int plural, size; + + plural = (t->target - t->sent) != 1; + size = snprintf(buf, sizeof(buf), + "I/O #%d: %d message%s remain%s.", t->id, + t->target - t->sent, + plural ? "s" : "", plural ? "" : "s"); + + if (write(t->pipe[1], buf, size) < 0) { + /* just ignore it... */ + } + t->sent++; + + info("GLIB I/O #%d: sent message %d/%d.", t->id, t->sent, t->target); + + if (t->sent >= t->target) { + info("GLIB I/O #%d: sending done.", t->id); + + close(t->pipe[1]); + t->timer = 0; + + cfg.nrunning--; + return FALSE; + } + else + return TRUE; +} + + +static gboolean glib_recv_io(GIOChannel *ioc, GIOCondition cond, + gpointer user_data) +{ + glib_io_t *t = (glib_io_t *)user_data; + int fd = g_io_channel_unix_get_fd(ioc); + char buf[1024]; + int size; + + if (cond & G_IO_IN) { + size = read(fd, buf, sizeof(buf) - 1); + + if (size > 0) { + t->received++; + buf[size] = '\0'; + info("GLIB I/O #%d: received message [%s]", t->id, buf); + } + else + warning("GLIB I/O #%d: got empty message", t->id); + } + + if (cond & G_IO_HUP) { + info("GLIB I/O #%d: receiver done (got %d/%d)", t->id, t->received, + t->sent); + close(fd); + return FALSE; + } + else + return TRUE; +} + + +void setup_glib_io(void) +{ + glib_io_t *t; + GIOCondition cond; + int intervals[] = { GIO_INTERVALS, 0 }, *iv = intervals; + int msecs, i; + + if ((gios = mrp_allocz_array(glib_io_t, cfg.ngio)) != NULL) { + cond = G_IO_IN | G_IO_HUP; + + for (i = 0, t = gios; i < cfg.ngio; i++, t++) { + t->id = i; + + msecs = *iv; + while (cfg.runtime / msecs < 1 && msecs > 0) + msecs /= 2; + msecs *= 1000; + if (!msecs) + msecs = 500; + + t->target = 1000 * cfg.runtime / msecs; + if (!t->target) + continue; + + if (pipe(t->pipe) != 0) + fatal("GLIB I/O #%d: could not create pipe", t->id); + + t->gioc = g_io_channel_unix_new(t->pipe[0]); + if (t->gioc == NULL) + fatal("GLIB I/O #%d: failed to create I/O channel", t->id); + + t->gsrc = g_io_add_watch(t->gioc, cond, glib_recv_io, t); + if (t->gsrc == 0) + fatal("GLIB I/O #%d: failed to add I/O watch", t->id); + + t->timer = g_timeout_add(msecs, glib_send_io, t); + if (t->timer == 0) + fatal("GLIB I/O #%d: could not create I/O timer", t->id); + + info("GLIB I/O #%d: interval=%d, target=%d", t->id, *iv, + t->target); + + cfg.nrunning++; + iv++; + if (!*iv) + iv = intervals; + } + } + else + if (cfg.ngio > 0) + fatal("could not allocate %d glib I/O watches", cfg.ngio); +} + + +static void check_glib_io(void) +{ + glib_io_t *t; + int i; + + for (i = 0, t = gios; i < cfg.ngio; i++, t++) { + if (t->target != 0 && t->sent != t->received) { + warning("GLIB I/O #%d (fd %d): FAIL (only %d/%d)", + t->id, t->pipe[0], t->received, t->sent); + } + + else + info("GLIB I/O #%d (fd %d): OK (%d/%d)", t->id, t->pipe[0], + t->received, t->sent); + } +} + +static void glib_pump_cleanup(void); +#endif + + +/* + * DBUS tests (quite a mess the whole shebang...) + */ + +#define DBUS_PATH "/" +#define DBUS_IFACE "org.murphy.test" +#define DBUS_METHOD "message" +#define DBUS_SIGNAL "signal" + +typedef struct { + int pipe[2]; + pid_t client; + char address[256]; + + mrp_mainloop_t *ml; + DBusConnection *conn; + mrp_timer_t *sigtimer; + + int nmethod; + int nack; + int nsignal; +} dbus_test_t; + + +static dbus_test_t dbus_test = { pipe: { -1, -1 } }; + + + + +int mrp_setup_dbus_connection(mrp_mainloop_t *ml, DBusConnection *conn); + + +static void open_dbus_pipe(void) +{ + if (pipe(dbus_test.pipe) < 0) + fatal("failed to opend pipe for DBUS tests"); +} + + +static void close_dbus_pipe(char *dir) +{ + while (*dir) { + switch (*dir++) { + case 'r': + if (dbus_test.pipe[0] != -1) { + close(dbus_test.pipe[0]); + dbus_test.pipe[0] = -1; + } + break; + + case 'w': + if (dbus_test.pipe[1] != -1) { + close(dbus_test.pipe[1]); + dbus_test.pipe[1] = -1; + } + break; + } + } +} + + +static void recv_dbus_reply(DBusPendingCall *pending, void *user_data) +{ + DBusMessage *msg; + char *reply; + + MRP_UNUSED(user_data); + + if ((msg = dbus_pending_call_steal_reply(pending)) != NULL) { + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, + &reply, DBUS_TYPE_INVALID)) { + info("DBUS test: got reply #%d '%s'", dbus_test.nack, reply); + dbus_test.nack++; + } + + dbus_message_unref(msg); + } + + dbus_pending_call_unref(pending); + + if (dbus_test.nack >= cfg.ndbus_method) { + char dummy[256]; + + cfg.nrunning--; + + /* block until the client is done */ + if (read(dbus_test.pipe[0], dummy, sizeof(dummy)) < 0) { + /* just ignore it... */ + } + } +} + + +static int send_dbus_message(DBusConnection *conn, char *addr, char *buf) +{ + DBusMessage *msg; + DBusPendingCall *pending; + + msg = dbus_message_new_method_call(addr, DBUS_PATH, + DBUS_IFACE, DBUS_METHOD); + + if (msg == NULL) + fatal("failed to create DBUS message"); + + if (!dbus_message_append_args(msg, + DBUS_TYPE_STRING, &buf, DBUS_TYPE_INVALID)) + fatal("failed to add arguments to DBUS method call"); + + if (!dbus_connection_send_with_reply(conn, msg, &pending, 5000)) + fatal("failed to send DBUS message"); + + if (!dbus_pending_call_set_notify(pending, recv_dbus_reply, NULL, NULL)) + fatal("failed to set pending call notification callback"); + + dbus_message_unref(msg); + + return TRUE; +} + + +static int send_dbus_reply(DBusConnection *conn, DBusMessage *msg, char *buf) +{ + DBusMessage *reply; + + if ((reply = dbus_message_new_method_return(msg)) != NULL) { + if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &buf, + DBUS_TYPE_INVALID)) + fatal("failed to add arguments to DBUS method reply"); + + if (!dbus_connection_send(conn, reply, NULL)) + fatal("failed to send DBUS reply"); + + dbus_message_unref(reply); + } + + dbus_test.nmethod++; + if (dbus_test.nmethod >= cfg.ndbus_method) + cfg.nrunning--; + + return TRUE; +} + + +static DBusConnection *connect_to_dbus(char *name) +{ + DBusConnection *conn; + DBusError error; + unsigned int flags; + int status; + + dbus_error_init(&error); + + if ((conn = dbus_bus_get(DBUS_BUS_SESSION, NULL)) != NULL) { + if (!name || !*name) + return conn; + + flags = DBUS_NAME_FLAG_REPLACE_EXISTING | DBUS_NAME_FLAG_DO_NOT_QUEUE; + status = dbus_bus_request_name(conn, name, flags, &error); + + if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) + return conn; + else + error("failed to get name '%s' on DBUS (error: %s)", name, + error.message ? error.message : "unknown"); + } + + return NULL; +} + + +static void client_send_msg(mrp_timer_t *t, void *user_data) +{ + char buf[1024]; + + MRP_UNUSED(user_data); + + if (dbus_test.nmethod < cfg.ndbus_method) { + snprintf(buf, sizeof(buf), "DBUS message #%d", dbus_test.nmethod); + send_dbus_message(dbus_test.conn, dbus_test.address, buf); + + info("DBUS client: sent #%d message", dbus_test.nmethod); + + dbus_test.nmethod++; + } + + if (dbus_test.nmethod >= cfg.ndbus_method) { + mrp_del_timer(t); + if (cfg.ndbus_method == 0) + cfg.nrunning--; + else { + /* cfg.nrunning updated only once we've received the last reply */ + } + } +} + + +static void setup_dbus_client(mrp_mainloop_t *ml) +{ + DBusConnection *conn; + int i, nmethod, nsignal; + size_t size; + ssize_t amount_read; + + nmethod = cfg.ndbus_method; + nsignal = cfg.ndbus_signal; + mrp_clear(&cfg); + cfg.ndbus_method = nmethod; + cfg.ndbus_signal = nsignal; + + mrp_mainloop_quit(ml, 0); +#ifdef GLIB_ENABLED + glib_pump_cleanup(); +#endif + mrp_mainloop_destroy(ml); + + for (i = 3; i < 1024; i++) + if (i != dbus_test.pipe[0]) + close(i); + + size = sizeof(dbus_test.address) - 1; + amount_read = read(dbus_test.pipe[0], dbus_test.address, size); + if (amount_read > 0) { + dbus_test.address[amount_read] = '\0'; + info("DBUS test: got address '%s'", dbus_test.address); + } + + /*sleep(5);*/ + + if ((ml = dbus_test.ml = mrp_mainloop_create()) == NULL) + fatal("failed to create mainloop"); + + cfg.ml = ml; + + if ((conn = dbus_test.conn = connect_to_dbus(NULL)) == NULL) + fatal("failed to connect to DBUS"); + + if (!mrp_setup_dbus_connection(ml, conn)) + fatal("failed to setup DBUS connection with mainloop"); + + if (mrp_add_timer(ml, 1000, client_send_msg, NULL) == NULL) + fatal("failed to create DBUS message sending timer"); + + if (mrp_add_timer(ml, 1000, check_quit, NULL) == NULL) + fatal("failed to create quit-check timer"); + + cfg.nrunning++; +} + + +static DBusHandlerResult dispatch_method(DBusConnection *c, + DBusMessage *msg, void *data) +{ +#define SAFESTR(str) (str ? str : "<none>") + const char *path = dbus_message_get_path(msg); + const char *interface = dbus_message_get_interface(msg); + const char *member = dbus_message_get_member(msg); + const char *message; + char reply[1024]; + + MRP_UNUSED(data); + + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_METHOD_CALL || !member) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (strcmp(path, DBUS_PATH) || + strcmp(interface, DBUS_IFACE) || + strcmp(member, DBUS_METHOD)) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + /*info("DBUS server: got call: path='%s', interface='%s', member='%s')...", + SAFESTR(path), SAFESTR(interface), SAFESTR(member));*/ + + if (dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &message, DBUS_TYPE_INVALID)) { + snprintf(reply, sizeof(reply), "ACK: got '%s'", message); + if (!send_dbus_reply(c, msg, reply)) + fatal("failed to sent reply to DBUS message"); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + + + +static void setup_dbus_server(mrp_mainloop_t *ml) +{ + static struct DBusObjectPathVTable vtable = { + .message_function = dispatch_method + }; + + char *addr = "org.murphy.test"; + + MRP_UNUSED(ml); + + if ((dbus_test.conn = connect_to_dbus(addr)) == NULL) + fatal("failed to connect to DBUS"); + + if (!mrp_setup_dbus_connection(ml, dbus_test.conn)) + fatal("failed to setup DBUS connection with mainloop"); + + if (!dbus_connection_register_fallback(dbus_test.conn, "/", &vtable, NULL)) + fatal("failed to set up method dispatching"); + + if (write(dbus_test.pipe[1], addr, strlen(addr) + 1) < 0) { + /* just ignore it... */ + } + + cfg.nrunning++; +} + + + +static void fork_dbus_client(mrp_mainloop_t *ml) +{ + dbus_test.client = cfg.child = fork(); + + switch (dbus_test.client) { + case -1: + fatal("failed to fork DBUS test client"); + break; + + case 0: + setup_dbus_client(ml); + break; + + default: + info("DBUS test: child pid %u", dbus_test.client); + close(0); + /*sleep(10);*/ + setup_dbus_server(ml); + } +} + + +static void sigchild_handler(mrp_sighandler_t *h, int signum, void *user_data) +{ + int status; + + MRP_UNUSED(user_data); + + info("DBUS test: received signal %d (%s)", signum, signames[signum]); + + if (dbus_test.client != 0) { + if (waitpid(dbus_test.client, &status, WNOHANG) == dbus_test.client) { + info("DBUS test: client exited with status %d.", status); + dbus_test.client = 0; + close_dbus_pipe("w"); + mrp_del_sighandler(h); + cfg.nrunning--; + } + else + error("waitpid failed for pid %u", dbus_test.client); + } +} + + +static void setup_dbus_tests(mrp_mainloop_t *ml) +{ + mrp_sighandler_t *h; + + if (cfg.ndbus_method == 0 && cfg.ndbus_signal == 0) + return; + + if ((h = mrp_add_sighandler(ml, SIGCHLD, sigchild_handler, NULL)) != NULL) { + open_dbus_pipe(); + fork_dbus_client(ml); + } + else + fatal("failed create SIGCHLD handler"); +} + + +static void check_dbus(void) +{ + if (cfg.ndbus_method == 0 && cfg.ndbus_signal == 0) + return; + + if (dbus_test.client != 0) { + if (dbus_test.nmethod == cfg.ndbus_method) + info("DBUS test: method calls: OK (%d/%d)", + dbus_test.nmethod, cfg.ndbus_method); + else + error("DBUS test: method calls: FAILED (%d/%d)", + dbus_test.nmethod, cfg.ndbus_method); + } + else { + if (dbus_test.nack == cfg.ndbus_method) + info("DBUS test: method replies: OK (%d/%d)", + dbus_test.nack, cfg.ndbus_method); + else + error("DBUS test: method replies: FAILED (%d/%d)", + dbus_test.nack, cfg.ndbus_method); + } +} + + + +#include "dbus-pump.c" + + + +static void config_set_defaults(test_config_t *cfg) +{ + mrp_clear(cfg); + + cfg->nio = 5; + cfg->ntimer = 10; + cfg->nsignal = 5; + cfg->ngio = 5; + cfg->ngtimer = 10; + + cfg->ndbus_method = 10; + cfg->ndbus_signal = 10; + + cfg->log_mask = MRP_LOG_UPTO(MRP_LOG_DEBUG); + cfg->log_target = MRP_LOG_TO_STDERR; + + cfg->wlpf = 1750; + cfg->wfrc = 5000; + + cfg->runtime = DEFAULT_RUNTIME; +} + + +static void print_usage(const char *argv0, int exit_code, const char *fmt, ...) +{ + va_list ap; + + if (fmt && *fmt) { + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + } + + printf("usage: %s [options]\n\n" + "The possible options are:\n" + " -r, --runtime how many seconds to run tests\n" + " -i, --ios number of I/O watches\n" + " -t, --timers number of timers\n" + " -s, --signals number of POSIX signals\n" + " -I, --glib-ios number of glib I/O watches\n" + " -T, --glib-timers number of glib timers\n" + " -S, --dbus-signals number of D-Bus signals\n" + " -M, --dbus-methods number of D-Bus methods\n" + " -o, --log-target=TARGET log target to use\n" + " TARGET is one of stderr,stdout,syslog, or a logfile path\n" + " -l, --log-level=LEVELS logging level to use\n" + " LEVELS is a comma separated list of info, error and warning\n" + " -v, --verbose increase logging verbosity\n" + " -d, --debug site enable debug messages for <site>\n" +#ifdef PULSE_ENABLED + " -p, --pulse use pulse mainloop\n" +#endif +#ifdef ECORE_ENABLED + " -e, --ecore use ecore mainloop\n" +#endif +#ifdef GLIB_ENABLED + " -g, --glib use glib mainloop\n" +#endif +#ifdef QT_ENABLED + " -q, --qt use qt mainloop\n" +#endif + " -h, --help show help on usage\n", + argv0); + + if (exit_code < 0) + return; + else + exit(exit_code); +} + + +int parse_cmdline(test_config_t *cfg, int argc, char **argv) +{ +#ifdef PULSE_ENABLED +# define PULSE_OPTION "p" +#else +# define PULSE_OPTION "" +#endif +#ifdef ECORE_ENABLED +# define ECORE_OPTION "e" +#else +# define ECORE_OPTION "" +#endif +#ifdef GLIB_ENABLED +# define GLIB_OPTION "g" +#else +# define GLIB_OPTION "" +#endif +#ifdef QT_ENABLED +# define QT_OPTION "q" +#else +# define QT_OPTION "" +#endif + + +# define OPTIONS "r:i:t:s:I:T:S:M:l:w:W:o:vd:h" \ + PULSE_OPTION""ECORE_OPTION""GLIB_OPTION""QT_OPTION + struct option options[] = { + { "runtime" , required_argument, NULL, 'r' }, + { "ios" , required_argument, NULL, 'i' }, + { "timers" , required_argument, NULL, 't' }, + { "signals" , required_argument, NULL, 's' }, + { "glib-ios" , required_argument, NULL, 'I' }, + { "glib-timers" , required_argument, NULL, 'T' }, + { "dbus-signals", required_argument, NULL, 'S' }, + { "dbus-methods", required_argument, NULL, 'M' }, +#ifdef PULSE_ENABLED + { "pulse" , no_argument , NULL, 'p' }, +#endif +#ifdef ECORE_ENABLED + { "ecore" , no_argument , NULL, 'e' }, +#endif +#ifdef GLIB_ENABLED + { "glib" , no_argument , NULL, 'g' }, +#endif +#ifdef QT_ENABLED + { "qt" , no_argument , NULL, 'q' }, +#endif + { "wakeup-lpf" , required_argument, NULL, 'w' }, + { "wakeup-force", required_argument, NULL, 'W' }, + { "log-level" , required_argument, NULL, 'l' }, + { "log-target" , required_argument, NULL, 'o' }, + { "verbose" , optional_argument, NULL, 'v' }, + { "debug" , required_argument, NULL, 'd' }, + { "help" , no_argument , NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + char *end; + int opt; + + config_set_defaults(cfg); + + while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) { + switch (opt) { + case 'r': + cfg->runtime = (int)strtoul(optarg, &end, 10); + if (end && *end) + print_usage(argv[0], EINVAL, + "invalid runtime length '%s'.", optarg); + break; + + case 'i': + cfg->nio = (int)strtoul(optarg, &end, 10); + if (end && *end) + print_usage(argv[0], EINVAL, + "invalid number of I/O watches '%s'.", optarg); + break; + + case 't': + cfg->ntimer = (int)strtoul(optarg, &end, 10); + if (end && *end) + print_usage(argv[0], EINVAL, + "invalid number of timers '%s'.", optarg); + break; + + case 's': + cfg->nsignal = (int)strtoul(optarg, &end, 10); + if (end && *end) + print_usage(argv[0], EINVAL, + "invalid number of signals '%s'.", optarg); + break; + + case 'I': + cfg->ngio = (int)strtoul(optarg, &end, 10); + if (end && *end) + print_usage(argv[0], EINVAL, + "invalid number of glib I/O watches '%s'.", optarg); + break; + + case 'T': + cfg->ngtimer = (int)strtoul(optarg, &end, 10); + if (end && *end) + print_usage(argv[0], EINVAL, + "invalid number of glib timers '%s'.", optarg); + break; + + case 'S': + cfg->ndbus_signal = (int)strtoul(optarg, &end, 10); + if (end && *end) + print_usage(argv[0], EINVAL, + "invalid number of DBUS signals '%s'.", optarg); + break; + + case 'M': + cfg->ndbus_method = (int)strtoul(optarg, &end, 10); + if (end && *end) + print_usage(argv[0], EINVAL, + "invalid number of DBUS methods '%s'.", optarg); + break; + +#ifdef PULSE_ENABLED + case 'p': + cfg->mainloop_type = MAINLOOP_PULSE; + break; +#endif + +#ifdef ECORE_ENABLED + case 'e': + cfg->mainloop_type = MAINLOOP_ECORE; + break; +#endif + +#ifdef GLIB_ENABLED + case 'g': + cfg->mainloop_type = MAINLOOP_GLIB; + break; +#endif + +#ifdef QT_ENABLED + case 'q': + cfg->mainloop_type = MAINLOOP_QT; + break; +#endif + + case 'w': + cfg->wlpf = (int)strtoul(optarg, &end, 10); + if (end && *end) + print_usage(argv[0], EINVAL, + "invalid wakeup low-pass filter limit '%s'.", + optarg); + break; + + case 'W': + cfg->wfrc = (int)strtoul(optarg, &end, 10); + if (end && *end) + print_usage(argv[0], EINVAL, + "invalid wakeup force trigger limit '%s'.", + optarg); + break; + + case 'v': + cfg->log_mask <<= 1; + cfg->log_mask |= 1; + break; + + case 'l': + cfg->log_mask = mrp_log_parse_levels(optarg); + if (cfg->log_mask < 0) + print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg); + break; + + case 'o': + cfg->log_target = mrp_log_parse_target(optarg); + if (!cfg->log_target) + print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg); + break; + + case 'd': + cfg->log_mask |= MRP_LOG_MASK_DEBUG; + mrp_debug_set_config(optarg); + mrp_debug_enable(TRUE); + break; + + case 'h': + print_usage(argv[0], -1, ""); + exit(0); + break; + + default: + print_usage(argv[0], EINVAL, "invalid option '%c'", opt); + } + } + + return TRUE; +} + + +static mrp_mainloop_t *mainloop_create(test_config_t *cfg) +{ + switch (cfg->mainloop_type) { + case MAINLOOP_NATIVE: + cfg->ml = mrp_mainloop_create(); + break; + + case MAINLOOP_PULSE: + pulse_mainloop_create(cfg); + break; + + case MAINLOOP_ECORE: + ecore_mainloop_create(cfg); + break; + + case MAINLOOP_GLIB: + glib_mainloop_create(cfg); + break; + +#ifdef QT_ENABLED + case MAINLOOP_QT: + cfg->ml = qt_mainloop_create(); + break; +#endif + + default: + mrp_log_error("Invalid mainloop type 0x%x.", cfg->mainloop_type); + exit(1); + } + + if (cfg->ml == NULL) { + mrp_log_error("Failed to create mainloop."); + exit(1); + } + + return cfg->ml; +} + + +static void mainloop_run(test_config_t *cfg) +{ + switch (cfg->mainloop_type) { + case MAINLOOP_NATIVE: + mrp_mainloop_run(cfg->ml); + break; + + case MAINLOOP_PULSE: + pulse_mainloop_run(cfg); + break; + + case MAINLOOP_ECORE: + ecore_mainloop_run(cfg); + break; + + case MAINLOOP_GLIB: + glib_mainloop_run(cfg); + break; + +#ifdef QT_ENABLED + case MAINLOOP_QT: + qt_mainloop_run(); + break; +#endif + + default: + mrp_log_error("Invalid mainloop type 0x%x.", cfg->mainloop_type); + exit(1); + } +} + + +static void mainloop_quit(test_config_t *cfg) +{ + switch (cfg->mainloop_type) { + case MAINLOOP_NATIVE: + mrp_mainloop_quit(cfg->ml, 0); + break; + + case MAINLOOP_PULSE: + pulse_mainloop_quit(cfg); + break; + + case MAINLOOP_ECORE: + ecore_mainloop_quit(cfg); + break; + + case MAINLOOP_GLIB: + glib_mainloop_quit(cfg); + break; + +#ifdef QT_ENABLED + case MAINLOOP_QT: + qt_mainloop_quit(); + break; +#endif + + default: + mrp_log_error("Invalid mainloop type 0x%x.", cfg->mainloop_type); + exit(1); + } +} + + +void mainloop_cleanup(test_config_t *cfg) +{ + switch (cfg->mainloop_type) { + case MAINLOOP_NATIVE: + break; + + case MAINLOOP_PULSE: + pulse_mainloop_cleanup(cfg); + break; + + case MAINLOOP_ECORE: + ecore_mainloop_cleanup(cfg); + break; + + case MAINLOOP_GLIB: + glib_mainloop_cleanup(cfg); + break; + +#ifdef QT_ENABLED + case MAINLOOP_QT: + qt_mainloop_cleanup(cfg->ml); + cfg->ml = NULL; + break; +#endif + + default: + mrp_log_error("Unknown mainloop type (0x%x).", cfg->mainloop_type); + exit(1); + } +} + + +int main(int argc, char *argv[]) +{ + mrp_mainloop_t *ml; + + mrp_clear(&cfg); + parse_cmdline(&cfg, argc, argv); + + mrp_log_set_mask(cfg.log_mask); + mrp_log_set_target(cfg.log_target); + + ml = mainloop_create(&cfg); + + if (ml == NULL) + fatal("failed to create main loop."); + + dbus_test.ml = ml; + setup_dbus_tests(ml); + ml = dbus_test.ml; + + setup_timers(ml); + setup_io(ml); + setup_signals(ml); + MRP_UNUSED(setup_deferred); /* XXX TODO: add deferred tests... */ + +#ifdef GLIB_ENABLED + if (cfg.mainloop_type != MAINLOOP_GLIB && cfg.mainloop_type != MAINLOOP_QT) { + if (cfg.ngio > 0 || cfg.ngtimer > 0) + glib_pump_setup(ml); + } + + setup_glib_io(); + setup_glib_timers(); +#endif + + if (mrp_add_timer(ml, 1000, check_quit, NULL) == NULL) + fatal("failed to create quit-check timer"); + + setup_wakeup(ml); + + mainloop_run(&cfg); + + check_io(); + check_timers(); + check_signals(); + +#ifdef GLIB_ENABLED + check_glib_io(); + check_glib_timers(); +#endif + + if (dbus_test.client != 0) + close(dbus_test.pipe[1]); /* let the client continue */ + + check_dbus(); + +#ifdef GLIB_ENABLED + if (cfg.mainloop_type != MAINLOOP_GLIB) { + if (cfg.ngio > 0 || cfg.ngtimer > 0) + glib_pump_cleanup(); + } +#endif + + cleanup_wakeup(); + + mainloop_cleanup(&cfg); +} diff --git a/src/common/tests/mask-test.c b/src/common/tests/mask-test.c new file mode 100644 index 0000000..e4db6d0 --- /dev/null +++ b/src/common/tests/mask-test.c @@ -0,0 +1,130 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <time.h> +#include <sys/types.h> + +#include <murphy/common/mask.h> + +int main(int argc, char *argv[]) +{ + uint64_t bits; + int i, j, prev, set, n, cnt, clr, bit; + mrp_mask_t m = MRP_MASK_EMPTY, m1; + int b[] = { 0, 1, 5, 16, 32, 48, 97, 112, 113, 114, 295, 313, -1 }; + + cnt = argc > 1 ? strtoul(argv[1], NULL, 10) : 100; + + srand((unsigned int)time(NULL) ^ (unsigned int)getpid()); + + bits = 0x17; + bits <<= 35; + n = mrp_ffsll(bits); + printf("ffsl(0x%lx) = %d\n", bits, n); + + for (i = 0; i < cnt; i++) { + bits = (unsigned long)rand(); + n = mrp_ffsll(bits); + clr = ~((((unsigned long)-1) >> (n - 1)) << (n - 1)); + + if (n > 1) { + if ((bits & clr) != 0) { + fail: + printf("ffs(0x%lx) = %d: FAIL\n", bits, n); + exit(1); + } + else + printf("ffs(0x%lx) = %d: OK\n", bits, n); + } + + if (n != __builtin_ffsl(bits)) + goto fail; + + } + + for (i = 0; b[i] != -1; i++) { + printf("setting bit %d...\n", b[i]); + mrp_mask_set(&m, b[i]); + if (!mrp_mask_test(&m, b[i])) { + printf("testing bit %d: FAILED\n", b[i]); + exit(1); + } + } + + + prev = 0; + for (i = 0; b[i] != -1; i++) { + for (j = prev + 1; j < b[i]; j++) { + set = mrp_mask_test(&m, j); + if (set) { + printf("negative mask_test(%d): FAILED\n", j); + exit(1); + } + } + + set = mrp_mask_test(&m, b[i]); + + if (!set) { + printf("mask_test(%d): FAILED\n", b[i]); + exit(1); + } + + prev = b[i]; + } + + printf("mask tests: OK\n"); + + MRP_MASK_FOREACH_SET(&m, bit, 0) { + printf("next bit set: %d\n", bit); + } + + MRP_MASK_FOREACH_CLEAR(&m, bit, 150) { + printf("next bit clear: %d\n", bit); + } + + mrp_mask_neg(&m); + MRP_MASK_FOREACH_CLEAR(&m, bit, 150) { + printf("next bit clear: %d\n", bit); + } + + MRP_MASK_FOREACH_CLEAR(&m, bit, 0) { + printf("next bit clear (negated): %d\n", bit); + } + + mrp_mask_neg(&m); + + mrp_mask_copy(&m1, &m); + mrp_mask_neg(&m1); + mrp_mask_or(&m1, &m); + + MRP_MASK_FOREACH_SET(&m1, bit, 0) { + printf("next bit set (or'd): %d\n", bit); + } + + mrp_mask_copy(&m1, &m); + mrp_mask_neg(&m1); + mrp_mask_xor(&m1, &m); + + MRP_MASK_FOREACH_SET(&m1, bit, 0) { + printf("next bit set (neg'd+xor'd): %d\n", bit); + } + + mrp_mask_copy(&m1, &m); + mrp_mask_neg(&m1); + mrp_mask_and(&m1, &m); + + MRP_MASK_FOREACH_SET(&m1, bit, 0) { + printf("next bit set (neg'd+and'd): %d\n", bit); + } + + mrp_mask_copy(&m1, &m); + mrp_mask_and(&m1, &m); + + MRP_MASK_FOREACH_SET(&m1, bit, 0) { + printf("next bit set (and'd): %d\n", bit); + } + + mrp_mask_reset(&m); + + return 0; +} diff --git a/src/common/tests/mkdir-test.c b/src/common/tests/mkdir-test.c new file mode 100644 index 0000000..611b82a --- /dev/null +++ b/src/common/tests/mkdir-test.c @@ -0,0 +1,21 @@ +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> + +#include <murphy/common/file-utils.h> + +int main(int argc, char *argv[]) +{ + int i; + + for (i = 1; i < argc; i++) { + printf("Trying to create directory '%s'..\n", argv[i]); + if (mrp_mkdir(argv[i], 0755) < 0) + printf("failed (%d: %s)\n", errno, strerror(errno)); + else + printf("ok\n"); + } + + return 0; +} diff --git a/src/common/tests/mm-test.c b/src/common/tests/mm-test.c new file mode 100644 index 0000000..c22b866 --- /dev/null +++ b/src/common/tests/mm-test.c @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <murphy/common/mm.h> + +#define fatal(fmt, args...) do { \ + fprintf(stderr, "fatal error: "fmt"\n" , ## args); \ + exit(1); \ + } while (0) + +#define error(fmt, args...) do { \ + fprintf(stdout, "error: "fmt"\n" , ## args); \ + } while (0) + +#define info(fmt, args...) do { \ + fprintf(stdout, fmt"\n" , ## args); \ + } while (0) + + + +static int basic_tests(int n) +{ + void **ptrs; + char buf[1024], *p; + int i; + + mrp_mm_config(MRP_MM_DEBUG); + + ptrs = mrp_allocz(n * sizeof(*ptrs)); + + if (ptrs == NULL) + fatal("Failed to allocate pointer table."); + + for (i = 0; i < n; i++) { + snprintf(buf, sizeof(buf), "#%d: message number %d (0x%x)", i, i, i); + + p = ptrs[i] = mrp_strdup(buf); + + if (p != NULL) { + if (!strcmp(buf, p)) { + printf("'%s' was duplicated as '%s'\n", buf, p); + } + else { + printf("'%s' was incorrectly duplicated as '%s'\n", buf, p); + return FALSE; + } + } + else { + printf("failed to duplicate '%s'\n", buf); + return FALSE; + } + } + + mrp_mm_check(stdout); + + for (i = 0; i < n; i += 2) { + mrp_free(ptrs[i]); + ptrs[i] = NULL; + } + + mrp_mm_check(stdout); + + for (i = 0; i < n; i++) { + mrp_free(ptrs[i]); + ptrs[i] = NULL; + } + + mrp_mm_check(stdout); + + mrp_free(ptrs); + + mrp_mm_check(stdout); + + return TRUE; +} + + +typedef struct { + char name[32]; + int i; + double d; + char *s; + void *p; +} obj_t; + + +#define NAME_FORMAT "#%d test object" +#define POISON 0xf3 + +static int obj_setup(void *ptr) +{ + static int idx = 0; + obj_t *obj = ptr; + + snprintf(obj->name, sizeof(obj->name), NAME_FORMAT, idx); + obj->i = idx; + obj->d = 2.0 * idx; + obj->s = mrp_strdup(obj->name); + obj->p = ptr; + + return TRUE; +} + + +static void obj_cleanup(void *ptr) +{ + obj_t *obj = ptr; + + mrp_free(obj->s); +} + + +static int obj_check(obj_t *obj, int alloced) +{ + char name[32]; + + if (alloced) { + snprintf(name, sizeof(name), NAME_FORMAT, obj->i); + + return (!strcmp(name, obj->name) && !strcmp(name, obj->s) && + obj->d == 2 * obj->i && obj->p == obj); + } + else { + char check[sizeof(obj_t)]; + + memset(check, POISON, sizeof(check)); + if (memcmp(check, obj, sizeof(*obj))) + error("Object %p not properly poisoned.", obj); + } + + return TRUE; +} + + +static int pool_tests(void) +{ + mrp_objpool_config_t cfg; + mrp_objpool_t *pool; + obj_t **ptrs; + int limit, prealloc, i, max; + int success; + + limit = 0; + prealloc = 512; + max = 8382; + ptrs = mrp_allocz(max * sizeof(obj_t)); + + if (ptrs == NULL) { + error("Failed to allocate check pointer table."); + return FALSE; + } + + cfg.name = "test pool"; + cfg.limit = limit; + cfg.objsize = sizeof(obj_t); + cfg.prealloc = prealloc; + cfg.setup = obj_setup; + cfg.cleanup = obj_cleanup; + cfg.poison = POISON; + cfg.flags = MRP_OBJPOOL_FLAG_POISON; + + info("Creating object pool..."); + pool = mrp_objpool_create(&cfg); + + if (pool == NULL) { + error("Failed to create test object pool."); + return FALSE; + } + + info("Allocating objects..."); + for (i = 0; i < max; i++) { + ptrs[i] = mrp_objpool_alloc(pool); + + if (ptrs[i] == NULL) { + error("Failed to allocate test object #%d.", i); + success = FALSE; + goto out; + } + + if (!obj_check(ptrs[i], TRUE)) { + error("Object check failed for %p.", ptrs[i]); + success = FALSE; + } + } + + info("Freeing objects..."); + for (i = 0; i < max; i += 2) { + mrp_objpool_free(ptrs[i]); + obj_check(ptrs[i], FALSE); + ptrs[i] = NULL; + } + + info("Reallocating objects..."); + for (i = 0; i < max; i += 2) { + ptrs[i] = mrp_objpool_alloc(pool); + + if (ptrs[i] == NULL) { + error("Failed to re-allocate test object #%d.", i); + success = FALSE; + goto out; + } + + if (!obj_check(ptrs[i], TRUE)) { + error("Object check failed for %p.", ptrs[i]); + success = FALSE; + } + + } + + info("Freeing objects..."); + for (i = 0; i < max; i++) { + mrp_objpool_free(ptrs[i]); + ptrs[i] = NULL; + } + + info("Reallocating again objects..."); + for (i = 0; i < max; i++) { + ptrs[i] = mrp_objpool_alloc(pool); + + if (ptrs[i] == NULL) { + error("Failed to re-allocate test object #%d.", i); + success = FALSE; + goto out; + } + + if (!obj_check(ptrs[i], TRUE)) { + error("Object check failed for %p.", ptrs[i]); + success = FALSE; + } + } + + out: + mrp_free(ptrs); + info("Destroying object pool..."); + mrp_objpool_destroy(pool); + + return success; +} + + +int main(int argc, char *argv[]) +{ + int max; + + if (argc > 1) + max = (int)strtol(argv[1], NULL, 10); + else + max = 256; + + info("Running basic tests..."); + basic_tests(max); + + info("Running object pool tests..."); + pool_tests(); + + return 0; +} diff --git a/src/common/tests/msg-test.c b/src/common/tests/msg-test.c new file mode 100644 index 0000000..44c5cd2 --- /dev/null +++ b/src/common/tests/msg-test.c @@ -0,0 +1,823 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <murphy/common.h> + +#include <murphy/common/msg.h> +#include <murphy/common/msg.c> + +#define TYPE(type, name) [MRP_MSG_FIELD_##type] = name +const char *types[] = { + TYPE(INVALID, "invalid"), + TYPE(STRING , "string" ), + TYPE(BOOL , "bool" ), + TYPE(SINT8 , "sint8" ), + TYPE(UINT8 , "uint8" ), + TYPE(SINT16 , "sint16" ), + TYPE(UINT16 , "uint16" ), + TYPE(SINT32 , "sint32" ), + TYPE(UINT32 , "uint32" ), + TYPE(SINT64 , "sint64" ), + TYPE(UINT64 , "uint64" ), + TYPE(DOUBLE , "double" ), + TYPE(BLOB , "blob" ), + NULL, +}; +#undef TYPE + + +uint16_t get_type(const char **types, const char *name) +{ + const char **t; + + for (t = types; *t != NULL; t++) { + if (!strcmp(*t, name)) + return (uint16_t)(t - types); + } + + return MRP_MSG_FIELD_INVALID; +} + + +void test_default_encode_decode(int argc, char **argv) +{ + mrp_msg_t *msg, *decoded; + void *encoded; + ssize_t size; + uint16_t tag, type, prev_tag; + uint8_t u8; + int8_t s8; + uint16_t u16; + int16_t s16; + uint32_t u32; + int32_t s32; + uint64_t u64; + int64_t s64; + double dbl; + bool bln; + char *val, *end; + int i, ok; + + if ((msg = mrp_msg_create_empty()) == NULL) { + mrp_log_error("Failed to create new message."); + exit(1); + } + + prev_tag = 0; + i = 1; + while (i < argc) { + + if ('0' <= *argv[i] && *argv[i] <= '9') { + if (argc <= i + 2) { + mrp_log_error("Missing field type or value."); + exit(1); + } + + tag = prev_tag = (uint16_t)strtoul(argv[i++], &end, 0); + if (end && *end) { + mrp_log_error("Invalid field tag '%s'.", argv[i]); + exit(1); + } + } + else { + if (argc <= i + 1) { + mrp_log_error("Missing field type or value."); + exit(1); + } + + tag = ++prev_tag; + } + + type = get_type(types, argv[i++]); + val = argv[i++]; + + if (type == MRP_MSG_FIELD_INVALID) { + mrp_log_error("Invalid field type '%s'.", argv[i + 1]); + exit(1); + } + + switch (type) { + case MRP_MSG_FIELD_STRING: + ok = mrp_msg_append(msg, tag, type, val); + break; + + case MRP_MSG_FIELD_BOOL: + if (!strcasecmp(val, "true")) + bln = TRUE; + else if (!strcasecmp(val, "false")) + bln = FALSE; + else { + mrp_log_error("Invalid boolean value '%s'.", val); + exit(1); + } + ok = mrp_msg_append(msg, tag, type, bln); + break; + +#define HANDLE_INT(_bits, _uget, _sget) \ + case MRP_MSG_FIELD_UINT##_bits: \ + u##_bits = (uint##_bits##_t)strtoul(val, &end, 0); \ + if (end && *end) { \ + mrp_log_error("Invalid uint%d value '%s'.", _bits, val); \ + exit(1); \ + } \ + ok = mrp_msg_append(msg, tag, type, u##_bits); \ + break; \ + case MRP_MSG_FIELD_SINT##_bits: \ + s##_bits = (int##_bits##_t)strtol(val, &end, 0); \ + if (end && *end) { \ + mrp_log_error("Invalid sint%d value '%s'.", _bits, val); \ + exit(1); \ + } \ + ok = mrp_msg_append(msg, tag, type, s##_bits); \ + break + + HANDLE_INT(8 , strtol , strtoul); + HANDLE_INT(16, strtol , strtoul); + HANDLE_INT(32, strtol , strtoul); + HANDLE_INT(64, strtoll, strtoull); + + case MRP_MSG_FIELD_DOUBLE: + dbl = strtod(val, &end); + if (end && *end) { + mrp_log_error("Invalid double value '%s'.", val); + exit(1); + } + ok = mrp_msg_append(msg, tag, type, dbl); + break; + + default: + mrp_log_error("Invalid (or unimplemented) type 0x%x (%s).", + type, argv[i + 1]); + ok = FALSE; + } + + if (!ok) { + mrp_log_error("Failed to add field to message."); + exit(1); + } + } + + mrp_msg_dump(msg, stdout); + + size = mrp_msg_default_encode(msg, &encoded); + if (size <= 0) { + mrp_log_error("Failed to encode message with default encoder."); + exit(1); + } + + mrp_log_info("encoded message size: %d", (int)size); + + decoded = mrp_msg_default_decode(encoded, size); + if (decoded == NULL) { + mrp_log_error("Failed to decode message with default decoder."); + exit(1); + } + + mrp_msg_dump(decoded, stdout); + + mrp_msg_unref(msg); + mrp_msg_unref(decoded); +} + + +typedef struct { + char *str1; + uint16_t u16; + int32_t s32; + char *str2; + double dbl1; + bool bln1; + double dbl2; + char *str3; + bool bln2; +} data1_t; + +typedef struct { + char *str; + uint8_t u8; + bool bln; +} data2_t; + +typedef struct { + char *str; + uint16_t u16; + int32_t s32; + double dbl; +} data3_t; + +#if 0 +typedef struct { + uint16_t offs; /* member offset within structure */ + uint16_t tag; /* tag for member */ + uint16_t type; /* type of this member */ +} mrp_msg_member_t; + +typedef struct { + uint16_t tag; /* structure tag */ + size_t size; /* structure size */ + int nfield; /* number of members */ + mrp_msg_member_t *fields; /* member descriptor */ +} mrp_msg_descr_t; +#endif + +#define DUMP_FIELD(memb, fmt) printf(" %s: "fmt"\n", #memb, d->memb) + +int cmp_data1(data1_t *d1, data1_t *d2) +{ + return + !strcmp(d1->str1, d2->str1) && + !strcmp(d1->str2, d2->str2) && + !strcmp(d1->str3, d2->str3) && + d1->u16 == d2->u16 && + d1->s32 == d2->s32 && + d1->dbl1 == d2->dbl1 && + d1->bln1 == d2->bln1 && + d1->dbl2 == d2->dbl2 && + d1->bln2 == d2->bln2; +} + +int cmp_data2(data2_t *d1, data2_t *d2) +{ + return + !strcmp(d1->str, d2->str) && + d1->u8 == d2->u8 && + d1->bln == d2->bln; +} + +int cmp_data3(data3_t *d1, data3_t *d2) +{ + return + !strcmp(d1->str, d2->str) && + d1->u16 == d2->u16 && + d1->s32 == d2->s32 && + d1->dbl == d2->dbl; +} + +void dump_data1(char *prefix, data1_t *d) +{ + printf("%s{\n", prefix); + DUMP_FIELD(str1, "%s"); + DUMP_FIELD(u16 , "%u"); + DUMP_FIELD(s32 , "%d"); + DUMP_FIELD(str2, "%s"); + DUMP_FIELD(dbl1, "%f"); + DUMP_FIELD(bln1, "%d"); + DUMP_FIELD(dbl2, "%f"); + DUMP_FIELD(str2, "%s"); + DUMP_FIELD(bln2, "%d"); + printf("}\n"); + +} + +void dump_data2(char *prefix, data2_t *d) +{ + printf("%s{\n", prefix); + DUMP_FIELD(str, "%s"); + DUMP_FIELD(u8 , "%u"); + DUMP_FIELD(bln, "%d"); + printf("}\n"); +} + +void dump_data3(char *prefix, data3_t *d) +{ + printf("%s{\n", prefix); + DUMP_FIELD(str, "%s"); + DUMP_FIELD(u16, "%u"); + DUMP_FIELD(s32, "%d"); + DUMP_FIELD(dbl, "%f"); + printf("}\n"); +} + +#undef DUMP_FIELD + +static size_t mrp_msg_encode(void **bufp, void *data, + mrp_data_member_t *fields, int nfield); + +static void *mrp_msg_decode(void **bufp, size_t *sizep, size_t data_size, + mrp_data_member_t *fields, int nfield); + +void test_custom_encode_decode(void) +{ +#define DESCRIBE(_type, _memb, _tag, _ftype) { \ + .offs = MRP_OFFSET(_type, _memb), \ + .tag = _tag, \ + .type = MRP_MSG_FIELD_##_ftype, \ + .guard = FALSE, \ + { NULL }, \ + .hook = { NULL, NULL } \ + } + + mrp_data_member_t data1_descr[] = { + DESCRIBE(data1_t, str1, 0x1, STRING), + DESCRIBE(data1_t, u16, 0x2, UINT16), + DESCRIBE(data1_t, str1, 0x1, STRING), + DESCRIBE(data1_t, u16 , 0x2, UINT16), + DESCRIBE(data1_t, s32 , 0x3, SINT32), + DESCRIBE(data1_t, str2, 0x4, STRING), + DESCRIBE(data1_t, dbl1, 0x5, DOUBLE), + DESCRIBE(data1_t, bln1, 0x6, BOOL ), + DESCRIBE(data1_t, dbl2, 0x7, DOUBLE), + DESCRIBE(data1_t, str3, 0x8, STRING), + DESCRIBE(data1_t, bln2, 0x9, BOOL ), + }; + int data1_nfield = MRP_ARRAY_SIZE(data1_descr); + + mrp_data_member_t data2_descr[] = { + DESCRIBE(data2_t, str, 0x1, STRING), + DESCRIBE(data2_t, u8 , 0x2, UINT8 ), + DESCRIBE(data2_t, bln, 0x3, BOOL ), + }; + int data2_nfield = MRP_ARRAY_SIZE(data2_descr); + + mrp_data_member_t data3_descr[] = { + DESCRIBE(data3_t, str, 0x1, STRING), + DESCRIBE(data3_t, u16, 0x2, UINT16), + DESCRIBE(data3_t, s32, 0x3, SINT32), + DESCRIBE(data3_t, dbl, 0x4, DOUBLE), + }; + int data3_nfield = MRP_ARRAY_SIZE(data3_descr); + +#define TAG_DATA1 0x1 +#define TAG_DATA2 0x2 +#define TAG_DATA3 0x3 + + + data1_t data1 = { + .str1 = "data1, str1", + .u16 = 32768U, + .s32 = -12345678, + .str2 = "data1, str2", + .dbl1 = 9.81, + .bln1 = TRUE, + .dbl2 = -3.141, + .str3 = "data1, str3", + .bln2 = FALSE + }; + data2_t data2 = { + .str = "data2, str", + .u8 = 128, + .bln = TRUE + }; + data3_t data3 = { + .str = "data3, str", + .u16 = 32768U, + .s32 = -12345678, + .dbl = 1.2345 + }; + + data1_t *d1; + data2_t *d2; + data3_t *d3; + void *buf; + size_t size; + + size = mrp_msg_encode(&buf, &data1, data1_descr, data1_nfield); + + if (size <= 0) { + mrp_log_error("failed to encode data1_t"); + exit(1); + } + + d1 = mrp_msg_decode(&buf, &size, sizeof(data1_t), data1_descr,data1_nfield); + + if (d1 == NULL) { + mrp_log_error("failed to decode encoded data1_t"); + exit(1); + } + + dump_data1("original data1: ", &data1); + dump_data1("decoded data1: ", d1); + if (!cmp_data1(&data1, d1)) { + mrp_log_error("Original and decoded data1_t do not match!"); + exit(1); + } + else + mrp_log_info("ok, original and decoded match..."); + + + size = mrp_msg_encode(&buf, &data2, data2_descr, data2_nfield); + + if (size <= 0) { + mrp_log_error("failed to encode data2_t"); + exit(1); + } + + d2 = mrp_msg_decode(&buf, &size, sizeof(data2_t), data2_descr,data2_nfield); + + if (d2 == NULL) { + mrp_log_error("failed to decode encoded data2_t"); + exit(1); + } + + dump_data2("original data2: ", &data2); + dump_data2("decoded data2: ", d2); + if (!cmp_data2(&data2, d2)) { + mrp_log_error("Original and decoded data2_t do not match!"); + exit(1); + } + else + mrp_log_info("ok, original and decoded match..."); + + + size = mrp_msg_encode(&buf, &data3, data3_descr, data3_nfield); + + if (size <= 0) { + mrp_log_error("failed to encode data3_t"); + exit(1); + } + + d3 = mrp_msg_decode(&buf, &size, sizeof(data3_t), data3_descr,data3_nfield); + + if (d3 == NULL) { + mrp_log_error("failed to decode encoded data3_t"); + exit(1); + } + + dump_data3("original data3: ", &data3); + dump_data3("decoded data3: ", d3); + if (!cmp_data3(&data3, d3)) { + mrp_log_error("Original and decoded data3_t do not match!"); + exit(1); + } + else + mrp_log_info("ok, original and decoded match..."); +} + + +static void test_basic(void) +{ + mrp_msg_t *msg; + char *str1, *str2; + uint16_t u16; + int16_t s16; + uint32_t u32; + int32_t s32; + double dbl1, dbl2; + int i; + + struct field_t { + uint16_t tag; + uint16_t type; + void *ptr; + } f[] = { + { 0x1, MRP_MSG_FIELD_STRING, &str1 }, + { 0x2, MRP_MSG_FIELD_STRING, &str2 }, + { 0x3, MRP_MSG_FIELD_UINT16, &u16 }, + { 0x4, MRP_MSG_FIELD_SINT16, &s16 }, + { 0x5, MRP_MSG_FIELD_UINT32, &u32 }, + { 0x6, MRP_MSG_FIELD_SINT32, &s32 }, + { 0x7, MRP_MSG_FIELD_DOUBLE, &dbl1 }, + { 0x8, MRP_MSG_FIELD_DOUBLE, &dbl2 } + }; + + msg = mrp_msg_create(MRP_MSG_TAG_STRING(0x1, "string 0x1"), + MRP_MSG_TAG_STRING(0x2, "string 0x2"), + MRP_MSG_TAG_UINT16(0x3, 3), + MRP_MSG_TAG_SINT16(0x4, -4), + MRP_MSG_TAG_UINT32(0x5, 5), + MRP_MSG_TAG_SINT32(0x6, -6), + MRP_MSG_TAG_DOUBLE(0x7, 3.14), + MRP_MSG_TAG_DOUBLE(0x8, -9.81), + MRP_MSG_END); + + if (msg == NULL) { + mrp_log_error("Failed to create message."); + exit(1); + } + else + mrp_log_info("Message created OK."); + + + if (!mrp_msg_get(msg, + 0x1, MRP_MSG_FIELD_STRING, &str1, + 0x2, MRP_MSG_FIELD_STRING, &str2, + 0x3, MRP_MSG_FIELD_UINT16, &u16, + 0x4, MRP_MSG_FIELD_SINT16, &s16, + 0x5, MRP_MSG_FIELD_UINT32, &u32, + 0x6, MRP_MSG_FIELD_SINT32, &s32, + 0x7, MRP_MSG_FIELD_DOUBLE, &dbl1, + 0x8, MRP_MSG_FIELD_DOUBLE, &dbl2, + MRP_MSG_END)) { + mrp_log_error("Failed to get message fields."); + exit(1); + } + else { + mrp_log_info("Got message fields:"); + mrp_log_info(" str1='%s', str2='%s'", str1, str2); + mrp_log_info(" u16=%u, s16=%d", u16, s16); + mrp_log_info(" u32=%u, s32=%d", u32, s32); + mrp_log_info(" dbl1=%f, dbl2=%f", dbl1, dbl2); + } + + if (!mrp_msg_get(msg, + 0x8, MRP_MSG_FIELD_DOUBLE, &dbl2, + 0x7, MRP_MSG_FIELD_DOUBLE, &dbl1, + 0x6, MRP_MSG_FIELD_SINT32, &s32, + 0x5, MRP_MSG_FIELD_UINT32, &u32, + 0x4, MRP_MSG_FIELD_SINT16, &s16, + 0x3, MRP_MSG_FIELD_UINT16, &u16, + 0x2, MRP_MSG_FIELD_STRING, &str2, + 0x1, MRP_MSG_FIELD_STRING, &str1, + MRP_MSG_END)) { + mrp_log_error("Failed to get message fields."); + exit(1); + } + else { + mrp_log_info("Got message fields:"); + mrp_log_info(" str1='%s', str2='%s'", str1, str2); + mrp_log_info(" u16=%u, s16=%d", u16, s16); + mrp_log_info(" u32=%u, s32=%d", u32, s32); + mrp_log_info(" dbl1=%f, dbl2=%f", dbl1, dbl2); + } + + +#define TAG(idx) f[(idx) & 0x7].tag +#define TYPE(idx) f[(idx) & 0x7].type +#define PTR(idx) f[(idx) & 0x7].ptr +#define FIELD(idx) TAG((idx)), TYPE((idx)), PTR((idx)) + + for (i = 0; i < (int)MRP_ARRAY_SIZE(f); i++) { + if (!mrp_msg_get(msg, + FIELD(i+0), FIELD(i+1), FIELD(i+2), FIELD(i+3), + FIELD(i+4), FIELD(i+5), FIELD(i+6), FIELD(i+7), + MRP_MSG_END)) { + mrp_log_error("Failed to get message fields for offset %d.", i); + exit(1); + } + else { + mrp_log_info("Got message fields for offset %d:", i); + mrp_log_info(" str1='%s', str2='%s'", str1, str2); + mrp_log_info(" u16=%u, s16=%d", u16, s16); + mrp_log_info(" u32=%u, s32=%d", u32, s32); + mrp_log_info(" dbl1=%f, dbl2=%f", dbl1, dbl2); + } + } + + if (mrp_msg_get(msg, + 0x9, MRP_MSG_FIELD_STRING, &str1, MRP_MSG_END)) { + mrp_log_error("Hmm... non-existent field found."); + exit(1); + } + else + mrp_log_info("Ok, non-existent field not found..."); +} + + +int main(int argc, char *argv[]) +{ + mrp_log_set_mask(MRP_LOG_UPTO(MRP_LOG_DEBUG)); + mrp_log_set_target(MRP_LOG_TO_STDOUT); + + test_basic(); + + test_default_encode_decode(argc, argv); + test_custom_encode_decode(); + + return 0; +} + + +static size_t mrp_msg_encode(void **bufp, void *data, + mrp_data_member_t *fields, int nfield) +{ + mrp_data_member_t *f; + mrp_msgbuf_t mb; + mrp_msg_value_t *v; + uint32_t len; + int i; + size_t size; + + size = nfield * (2 * sizeof(uint16_t) + sizeof(uint64_t)); + + if (mrp_msgbuf_write(&mb, size)) { + for (i = 0, f = fields; i < nfield; i++, f++) { + MRP_MSGBUF_PUSH(&mb, htobe16(f->tag) , 1, nomem); + + v = (mrp_msg_value_t *)(data + f->offs); + + switch (f->type) { + case MRP_MSG_FIELD_STRING: + len = strlen(v->str) + 1; + MRP_MSGBUF_PUSH(&mb, htobe32(len), 1, nomem); + MRP_MSGBUF_PUSH_DATA(&mb, v->str, len, 1, nomem); + break; + + case MRP_MSG_FIELD_BOOL: + MRP_MSGBUF_PUSH(&mb, htobe32(v->bln ? TRUE : FALSE), 1, nomem); + break; + + case MRP_MSG_FIELD_UINT8: + MRP_MSGBUF_PUSH(&mb, v->u8, 1, nomem); + break; + + case MRP_MSG_FIELD_SINT8: + MRP_MSGBUF_PUSH(&mb, v->s8, 1, nomem); + break; + + case MRP_MSG_FIELD_UINT16: + MRP_MSGBUF_PUSH(&mb, htobe16(v->u16), 1, nomem); + break; + + case MRP_MSG_FIELD_SINT16: + MRP_MSGBUF_PUSH(&mb, htobe16(v->s16), 1, nomem); + break; + + case MRP_MSG_FIELD_UINT32: + MRP_MSGBUF_PUSH(&mb, htobe32(v->u32), 1, nomem); + break; + + case MRP_MSG_FIELD_SINT32: + MRP_MSGBUF_PUSH(&mb, htobe32(v->s32), 1, nomem); + break; + + case MRP_MSG_FIELD_UINT64: + MRP_MSGBUF_PUSH(&mb, htobe64(v->u64), 1, nomem); + break; + + case MRP_MSG_FIELD_SINT64: + MRP_MSGBUF_PUSH(&mb, htobe64(v->s64), 1, nomem); + break; + + case MRP_MSG_FIELD_DOUBLE: + MRP_MSGBUF_PUSH(&mb, v->dbl, 1, nomem); + break; + + case MRP_MSG_FIELD_BLOB: + errno = EOPNOTSUPP; + /* intentional fall through */ + + default: + if (f->type & MRP_MSG_FIELD_ARRAY) { + errno = EOPNOTSUPP; + mrp_log_error("XXX TODO: MRP_MSG_FIELD_ARRAY " + "not implemented"); + } + else + errno = EINVAL; + + mrp_msgbuf_cancel(&mb); + nomem: + *bufp = NULL; + return 0; + } + } + } + + *bufp = mb.buf; + return (size_t)(mb.p - mb.buf); +} + + +#if 0 +static mrp_data_member_t *member_type(mrp_data_member_t *fields, int nfield, + uint16_t tag) +{ + mrp_data_member_t *f; + int i; + + for (i = 0, f = fields; i < nfield; i++, f++) + if (f->tag == tag) + return f; + + return NULL; +} +#endif + +static void *mrp_msg_decode(void **bufp, size_t *sizep, size_t data_size, + mrp_data_member_t *fields, int nfield) +{ + void *data; + mrp_data_member_t *f; + mrp_msgbuf_t mb; + uint16_t tag; + mrp_msg_value_t *v; + void *value; + uint32_t len; + int i; + + if (MRP_UNLIKELY((data = mrp_allocz(data_size)) == NULL)) + return NULL; + + mrp_msgbuf_read(&mb, *bufp, *sizep); + + for (i = 0; i < nfield; i++) { + tag = be16toh(MRP_MSGBUF_PULL(&mb, typeof(tag) , 1, nodata)); + f = member_type(fields, nfield, tag); + + if (MRP_UNLIKELY(f == NULL)) + goto unknown_field; + + v = (mrp_msg_value_t *)(data + f->offs); + + switch (f->type) { + case MRP_MSG_FIELD_STRING: + len = be32toh(MRP_MSGBUF_PULL(&mb, typeof(len), 1, nodata)); + value = MRP_MSGBUF_PULL_DATA(&mb, len, 1, nodata); + v->str = mrp_strdup((char *)value); + if (v->str == NULL) + goto nomem; + break; + + case MRP_MSG_FIELD_BOOL: + v->bln = be32toh(MRP_MSGBUF_PULL(&mb, uint32_t, 1, nodata)); + break; + + case MRP_MSG_FIELD_UINT8: + v->u8 = MRP_MSGBUF_PULL(&mb, typeof(v->u8), 1, nodata); + break; + + case MRP_MSG_FIELD_SINT8: + v->s8 = MRP_MSGBUF_PULL(&mb, typeof(v->s8), 1, nodata); + break; + + case MRP_MSG_FIELD_UINT16: + v->u16 = be16toh(MRP_MSGBUF_PULL(&mb, typeof(v->u16), 1, nodata)); + break; + + case MRP_MSG_FIELD_SINT16: + v->s16 = be16toh(MRP_MSGBUF_PULL(&mb, typeof(v->s16), 1, nodata)); + break; + + case MRP_MSG_FIELD_UINT32: + v->u32 = be32toh(MRP_MSGBUF_PULL(&mb, typeof(v->u32), 1, nodata)); + break; + + case MRP_MSG_FIELD_SINT32: + v->s32 = be32toh(MRP_MSGBUF_PULL(&mb, typeof(v->s32), 1, nodata)); + break; + + case MRP_MSG_FIELD_UINT64: + v->u64 = be64toh(MRP_MSGBUF_PULL(&mb, typeof(v->u64), 1, nodata)); + break; + + case MRP_MSG_FIELD_SINT64: + v->s64 = be64toh(MRP_MSGBUF_PULL(&mb, typeof(v->s64), 1, nodata)); + break; + + case MRP_MSG_FIELD_DOUBLE: + v->dbl = MRP_MSGBUF_PULL(&mb, typeof(v->dbl), 1, nodata); + break; + + case MRP_MSG_FIELD_BLOB: + errno = EOPNOTSUPP; + default: + if (f->type & MRP_MSG_FIELD_ARRAY) { + errno = EOPNOTSUPP; + mrp_log_error("XXX TODO: MRP_MSG_FIELD_ARRAY " + "not implemented"); + } + else { + unknown_field: + errno = EINVAL; + } + goto fail; + } + } + + *bufp = mb.buf; + *sizep -= mb.p - mb.buf; + return data; + + nodata: + nomem: + fail: + if (data != NULL) { + for (i = 0, f = fields; i < nfield; i++, f++) { + switch (f->type) { + case MRP_MSG_FIELD_STRING: + case MRP_MSG_FIELD_BLOB: + mrp_free(data + f->offs); + } + } + + mrp_free(data); + } + + return NULL; +} diff --git a/src/common/tests/native-test.c b/src/common/tests/native-test.c new file mode 100644 index 0000000..171f5a4 --- /dev/null +++ b/src/common/tests/native-test.c @@ -0,0 +1,307 @@ +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <murphy/common/macros.h> +#include <murphy/common/debug.h> +#include <murphy/common/log.h> +#include <murphy/common/native-types.h> + + +typedef enum { + MUSIC, + MOVIE, + BOOK, + PAINTING, +} art_type_t; + + +typedef struct { + art_type_t type; + char *artist; + char *title; + uint16_t year; + char *location; + double price; +} art_t; + + +typedef enum { + LEFT = 0, + RIGHT, + BOTH +} hand_t; + + +typedef enum { + MALE = 0, + FEMALE = 1, +} gender_t; + + +typedef struct { + char *name; + gender_t gender; + int age; + char **languages; + unsigned int height; + float weight; + char nationality[32]; + hand_t hand; + bool glasses; + art_t *favourites; + size_t nfavourite; +} person_t; + + +typedef struct { + person_t *father; + person_t *mother; + person_t *children; +} family_t; + + +art_t paps_favourites[] = { + { + BOOK , + "Douglas Adams", "Dirk Gently's Holistic Detective Agency", + 1987, "bookshelf", 9.5 + }, + { + MUSIC, + "Megadeth", "Sweating Bullets", + 1992, "pocket", 12.5 + }, + { + MUSIC, + "Sentenced", "Noose", + 1996, "phone", 12 + }, + { + MOVIE, + "Bananas", "Woody Allen", + 1971, "PVR", 20.5 + } +}; + + +char *paps_languages[] = { + "english", "swedish", "finnish", NULL +}; + +person_t pap = { + .name = "Pap", + .gender = MALE, + .age = 30, + .languages = paps_languages, + .height = 180, + .weight = 84.5, + .nationality = "martian", + .hand = RIGHT, + .glasses = false, + .favourites = paps_favourites, + .nfavourite = MRP_ARRAY_SIZE(paps_favourites), +}; + + +art_t moms_favourites[] = { + { + BOOK , + "Douglas Adams", "THHGTTG", + 1982, "bookshelf", 11.8 + }, + { + MUSIC, + "Megadeth", "Sweating Bullets", + 1992, "pocket", 12.5 + }, + { + MOVIE, + "Hottie Chick", "GGW-II", + 1996, "PVR", 0.5 + }, + { + BOOK , + "Douglas Adams", "The Long Dark Tea-Time of the Soul", + 1988, "Kindle Touch", 8.50 + } +}; + + +char *moms_languages[] = { + "finnish", "english", "swedish", "french", NULL +}; + +person_t mom = { + .name = "Mom", + .gender = FEMALE, + .age = 28, + .languages = moms_languages, + .height = 165, + .weight = 57.8, + .nationality = "venusian", + .hand = LEFT, + .glasses = true, + .favourites = moms_favourites, + .nfavourite = MRP_ARRAY_SIZE(moms_favourites), +}; + + +char *kids_languages[] = { + "english", "finnish", "swedish", NULL +}; + +person_t tom_dick_and_harry[] = { + { + .name = "Tom", + .gender = MALE, + .age = 10, + .languages = kids_languages + 1, + .height = 135, + .weight = 40.5, + .nationality = "UFO", + .hand = BOTH, + .glasses = false, + .favourites = NULL, + .nfavourite = 0, + }, + { + .name = "Dick", + .gender = MALE, + .age = 12, + .languages = kids_languages, + .height = 145, + .weight = 45.5, + .nationality = "UFO", + .hand = RIGHT, + .glasses = true, + .favourites = paps_favourites + 1, + .nfavourite = MRP_ARRAY_SIZE(paps_favourites) - 2, + }, + { + .name = "Harry", + .gender = MALE, + .age = 14, + .languages = kids_languages + 2, + .height = 165, + .weight = 60.5, + .nationality = "UFO", + .hand = LEFT, + .glasses = false, + .favourites = moms_favourites + 1, + .nfavourite = MRP_ARRAY_SIZE(moms_favourites) - 2, + }, + { + .name = NULL, + }, +}; + + +family_t family = { &pap, &mom, &tom_dick_and_harry[0] }; + + +int main(int argc, char *argv[]) +{ + MRP_NATIVE_TYPE(art_type, art_t, + MRP_UINT32(art_t, type , DEFAULT), + MRP_STRING(art_t, artist , DEFAULT), + MRP_STRING(art_t, title , DEFAULT), + MRP_UINT16(art_t, year , DEFAULT), + MRP_STRING(art_t, location, DEFAULT), + MRP_DOUBLE(art_t, price , DEFAULT)); + MRP_NATIVE_TYPE(person_type, person_t, + MRP_STRING(person_t, name , DEFAULT), + MRP_UINT32(person_t, gender , DEFAULT), + MRP_INT (person_t, age , DEFAULT), + MRP_ARRAY (person_t, languages , DEFAULT, GUARDED, + char *, "", .strp = NULL), + MRP_UINT (person_t, height , DEFAULT), + MRP_FLOAT (person_t, weight , DEFAULT), + MRP_STRING(person_t, nationality, INLINED), + MRP_UINT32(person_t, hand , DEFAULT), + MRP_BOOL (person_t, glasses , DEFAULT), + MRP_ARRAY (person_t, favourites , DEFAULT, SIZED, + art_t, nfavourite), + MRP_SIZET (person_t, nfavourite , DEFAULT)); + MRP_NATIVE_TYPE(family_type, family_t, + MRP_STRUCT(family_t, father , DEFAULT, person_t), + MRP_STRUCT(family_t, mother , DEFAULT, person_t), + MRP_ARRAY (family_t, children, DEFAULT, GUARDED, + person_t, name, .strp = NULL)); + mrp_typemap_t map[4]; + + uint32_t art_type_id, person_type_id, family_type_id; + void *ebuf; + size_t esize; + int fd; + void *dbuf; + family_t *decoded; + char dump[16 * 1024]; + + MRP_UNUSED(argc); + MRP_UNUSED(argv); + + mrp_log_set_mask(MRP_LOG_UPTO(MRP_LOG_INFO)); + + art_type_id = mrp_register_native(&art_type); + + if (art_type_id == MRP_INVALID_TYPE) + mrp_log_error("Failed to register art_t type."); + else + mrp_log_info("Type art_t sucessfully registered."); + + person_type_id = mrp_register_native(&person_type); + + if (person_type_id == MRP_INVALID_TYPE) + mrp_log_error("Failed to register person_t type."); + else + mrp_log_info("Type person_t sucessfully registered."); + + family_type_id = mrp_register_native(&family_type); + + if (family_type_id == MRP_INVALID_TYPE) + mrp_log_error("Failed to register family_t type."); + else + mrp_log_info("Type family_t sucessfully registered."); + + ebuf = NULL; + + map[0] = (mrp_typemap_t)MRP_TYPEMAP(1, art_type_id ); + map[1] = (mrp_typemap_t)MRP_TYPEMAP(2, person_type_id); + map[2] = (mrp_typemap_t)MRP_TYPEMAP(3, family_type_id); + map[3] = (mrp_typemap_t)MRP_TYPEMAP_END; + + if (mrp_encode_native(&family, family_type_id, 0, &ebuf, &esize, map) < 0) { + mrp_log_error("Failed to encode test data."); + exit(1); + } + else + mrp_log_info("Test data successfully encoded (%zd bytes).", esize); + + if ((fd = open("type-test.encoded", + O_CREAT | O_TRUNC | O_WRONLY, 0644)) >= 0) { + if (write(fd, ebuf, esize) != (ssize_t)esize) + mrp_log_error("Failed to write encoded data."); + close(fd); + } + + if (mrp_decode_native(&ebuf, &esize, &dbuf, &family_type_id, map) < 0) { + mrp_log_error("Failed to decode test data."); + exit(1); + } + else + mrp_log_info("Test data sucessfully decoded."); + + decoded = dbuf; + + if (mrp_print_native(dump, sizeof(dump), decoded, family_type_id) >= 0) + mrp_log_info("dump of decoded data: %s", dump); + else + mrp_log_error("Failed to dump decoded data."); + + mrp_free_native(dbuf, family_type_id); + + return 0; +} diff --git a/src/common/tests/path-test.c b/src/common/tests/path-test.c new file mode 100644 index 0000000..c2b0918 --- /dev/null +++ b/src/common/tests/path-test.c @@ -0,0 +1,50 @@ +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <murphy/common/file-utils.h> + +int main(int argc, char *argv[]) +{ + int i; + size_t size; + char *p, buf[PATH_MAX]; + struct stat ost, nst; + + if (argc > 1) { + size = strtoul(argv[1], &p, 10); + if (*p || size > sizeof(buf)) + size = sizeof(buf); + } + else + size = sizeof(buf); + + for (i = 1; i < argc; i++) { + printf("'%s':\n", argv[i]); + if ((p = mrp_normalize_path(buf, size, argv[i])) != NULL) { + printf(" -> '%s'\n", p); + + if (stat(argv[i], &ost) < 0) + printf(" Non-existing path, can't test in practice...\n"); + else{ + if (stat(buf, &nst) == 0 && + ost.st_dev == nst.st_dev && ost.st_ino == nst.st_ino) + printf(" Filesystem-equality check: OK.\n"); + else { + printf(" Filesystem-equality check: FAILED\n"); + exit(1); + } + } + } + else { + printf(" failed (%d: %s)\n", errno, strerror(errno)); + exit(1); + } + } + + return 0; +} diff --git a/src/common/tests/process-test.c b/src/common/tests/process-test.c new file mode 100644 index 0000000..41a55e6 --- /dev/null +++ b/src/common/tests/process-test.c @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2013, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <murphy/common.h> +#include <murphy/common/process.h> + +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> + + +static void process_watch(const char *id, mrp_process_state_t s, + void *userdata) +{ + mrp_mainloop_t *ml = (mrp_mainloop_t *) userdata; + + printf("process watch received event for %s: %s (%p)\n", + id, s == MRP_PROCESS_STATE_READY ? "ready" : "not ready", userdata); + + mrp_mainloop_quit(ml, 0); +} + + +static void test_process_watch(mrp_mainloop_t *ml) +{ + mrp_process_state_t s = mrp_process_query_state("foobar"); + + printf("initial state %s\n", + s == MRP_PROCESS_STATE_READY ? "ready" : "not ready"); + + if (mrp_process_set_state("foobar", MRP_PROCESS_STATE_READY) < 0) { + printf("error setting the state 1\n"); + } + + s = mrp_process_query_state("foobar"); + + printf("second state %s\n", + s == MRP_PROCESS_STATE_READY ? "ready" : "not ready"); + + if (mrp_process_set_state("foobar", MRP_PROCESS_STATE_NOT_READY) < 0) { + printf("error setting the state 2\n"); + } + + s = mrp_process_query_state("foobar"); + + printf("third state %s\n", + s == MRP_PROCESS_STATE_READY ? "ready" : "not ready"); + + if (mrp_process_set_watch("foobar", ml, process_watch, ml) < 0) { + printf("failed to register watch\n"); + } + + printf("setting state to ready\n"); + + if (mrp_process_set_state("foobar", MRP_PROCESS_STATE_READY) < 0) { + printf("error setting the state 3\n"); + } + + mrp_mainloop_run(ml); + + printf("removing the watch\n"); + + if(mrp_process_remove_watch("foobar") < 0) { + printf("failed to remove watch\n"); + } +} + +static void pid_watch(pid_t pid, mrp_process_state_t s, void *userdata) +{ + mrp_mainloop_t *ml = (mrp_mainloop_t *) userdata; + + printf("pid watch received event for %d: %s (%p)\n", + pid, s == MRP_PROCESS_STATE_READY ? "ready" : "not ready", userdata); + + mrp_mainloop_quit(ml, 0); +} + +static void test_pid_watch(mrp_mainloop_t *ml) +{ + pid_t pid = fork(); + + if (pid < 0) { + printf("error forking\n"); + } + else if (pid > 0) { + mrp_pid_watch_t *w; + + if (mrp_pid_query_state(pid) != MRP_PROCESS_STATE_READY) { + printf("failed to query the process READY state\n"); + } + + printf("setting pid watch\n"); + w = mrp_pid_set_watch(pid, ml, pid_watch, ml); + + printf("killing the process '%d'\n", pid); + kill(pid, 15); + waitpid(pid, NULL, 0); + + printf("running main loop\n"); + mrp_mainloop_run(ml); + + if (mrp_pid_query_state(pid) != MRP_PROCESS_STATE_NOT_READY) { + printf("failed to query the process NOT READY state\n"); + } + printf("removing the watch\n"); + mrp_pid_remove_watch(w); + } +} + +int main(int argc, char **argv) { + mrp_mainloop_t *ml = mrp_mainloop_create(); + + if (argc == 2 && strcmp(argv[1], "pid") == 0) { + test_pid_watch(ml); + } + else if (argc == 2 && strcmp(argv[1], "process") == 0) { + test_process_watch(ml); + } + else { + printf("Usage: process-watch-test <process|pid>\n"); + } + + mrp_mainloop_destroy(ml); +} + diff --git a/src/common/tests/sdbus-error-message.c b/src/common/tests/sdbus-error-message.c new file mode 100644 index 0000000..1e4b9f2 --- /dev/null +++ b/src/common/tests/sdbus-error-message.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2013, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <stdarg.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <murphy/common.h> +#include <murphy/core.h> +#include <murphy/common/dbus-sdbus.h> + +static int msg_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *data) +{ + mrp_dbus_err_t err; + mrp_dbus_msg_t *reply; + const char *member = mrp_dbus_msg_member(msg); + const char *iface = mrp_dbus_msg_interface(msg); + const char *path = mrp_dbus_msg_path(msg); + + MRP_UNUSED(data); + + printf("Message callback called -- member: '%s', path: '%s'," + " interface: '%s'\n", member, path, iface); + + mrp_dbus_error_init(&err); + mrp_dbus_error_set(&err, "org.freedesktop.DBus.Error.Failed", "Error message"); + + reply = mrp_dbus_msg_error(dbus, msg, &err); + + if (reply) { + mrp_dbus_send_msg(dbus, reply); + mrp_dbus_msg_unref(reply); + } + return TRUE; +} + +int main() +{ + mrp_dbus_t *dbus; + mrp_mainloop_t *ml; + + ml = mrp_mainloop_create(); + + if (!(dbus = mrp_dbus_connect(ml, "session", NULL))) { + printf("Failed to connect to D-Bus\n"); + } + + if (!mrp_dbus_acquire_name(dbus, "org.example", NULL)) { + printf("Failed to acquire name on D-Bus\n"); + goto error; + } + + if (!mrp_dbus_export_method(dbus, "/example", "org.example", "member", + msg_cb, NULL)) { + printf("Failed to register method\n"); + goto error; + } + + printf("waiting for 'dbus-send --session --print-reply --type=method_call" + "--dest=org.example /example org.example.member'\n"); + + mrp_mainloop_run(ml); + + return 0; + +error: + return 1; +} diff --git a/src/common/tests/sdbus-test.c b/src/common/tests/sdbus-test.c new file mode 100644 index 0000000..f785139 --- /dev/null +++ b/src/common/tests/sdbus-test.c @@ -0,0 +1,226 @@ +#include <murphy/common/macros.h> +#include <murphy/common/debug.h> +#include <murphy/common/log.h> +#include <murphy/common/mm.h> +#include <murphy/common/mainloop.h> + +#include "sd-bus.h" +#include "bus-message.h" + +#define USEC_TO_MSEC(usec) ((unsigned int)((usec) / 1000)) + +typedef struct { + sd_bus *bus; + mrp_mainloop_t *ml; + mrp_subloop_t *sl; +} bus_t; + +static void signal_handler(mrp_sighandler_t *h, int signum, void *user_data) +{ + MRP_UNUSED(user_data); + + switch (signum) { + case SIGINT: + case SIGTERM: + case SIGQUIT: + mrp_log_info("Received signal %d (%s), exiting...", signum, + strsignal(signum)); + mrp_mainloop_quit(mrp_get_sighandler_mainloop(h), 0); + } +} + + +static int bus_prepare(void *user_data) +{ + MRP_UNUSED(user_data); + + return FALSE; +} + + +static int bus_query(void *user_data, struct pollfd *fds, int nfd, + int *timeout) +{ + bus_t *b = (bus_t *)user_data; + uint64_t usec; + + mrp_log_info("nfd: %d", nfd); + + if (nfd > 0) { + fds[0].fd = sd_bus_get_fd(b->bus); + fds[0].events = sd_bus_get_events(b->bus) | POLLIN; + fds[0].revents = 0; + + if (sd_bus_get_timeout(b->bus, &usec) < 0) + *timeout = -1; + else + *timeout = USEC_TO_MSEC(usec); + + mrp_log_info("fd: %d, events: 0x%x, timeout: %u", fds[0].fd, + fds[0].events, *timeout); + } + + return 1; +} + + +static int bus_check(void *user_data, struct pollfd *fds, int nfd) +{ + MRP_UNUSED(user_data); + + if (nfd > 0 && fds[0].revents != 0) + return TRUE; + else + return FALSE; +} + + +static void bus_dispatch(void *user_data) +{ + bus_t *b = (bus_t *)user_data; + + if (sd_bus_process(b->bus, NULL) > 0) + sd_bus_flush(b->bus); +} + + +static int bus_signal_cb(sd_bus *bus, int ret, sd_bus_message *m, void *user_data) +{ + mrp_log_info("%s(): got bus signal...", __FUNCTION__); + + bus_message_dump(m); + + return 0; +} + + +static int bus_method_cb(sd_bus *bus, int ret, sd_bus_message *m, void *user_data) +{ + mrp_log_info("%s(): got bus method call message %p...", __FUNCTION__, m); + + bus_message_dump(m); + + if (!strcmp(sd_bus_message_get_member(m), "unhandled")) + return FALSE; + else + return TRUE; +} + + +static int bus_return_cb(sd_bus *bus, int ret, sd_bus_message *m, void *user_data) +{ + mrp_log_info("%s(): got bus method reply...", __FUNCTION__); + + bus_message_dump(m); + + return 0; +} + + +static void emit_signal(mrp_timer_t *t, void *user_data) +{ + sd_bus *bus = (sd_bus *)user_data; + + sd_bus_emit_signal(bus, "/foo/bar", "foo.bar", "foobar", NULL); +} + + +static void call_method(mrp_timer_t *t, void *user_data) +{ + sd_bus *bus = (sd_bus *)user_data; + sd_bus_message *msg = NULL; + int r; + uint64_t serial; + + r = sd_bus_message_new_method_call(bus, "org.freedesktop.DBus", + "/", "org.freedesktop.DBus", "GetId", + &msg); + + if (r != 0) { + mrp_log_error("Failed to create new method call message."); + return; + } + + r = sd_bus_send_with_reply(bus, msg, bus_return_cb, NULL, 100000 * 1000, &serial); + + if (r != 0) + mrp_log_error("Failed to call method... (r = %d)", r); +} + + +int main(int argc, char *argv[]) +{ + static mrp_subloop_ops_t bus_ops = { + .prepare = bus_prepare, + .query = bus_query, + .check = bus_check, + .dispatch = bus_dispatch + }; + + mrp_mainloop_t *ml = NULL; + mrp_timer_t *ts = NULL; + mrp_timer_t *tm = NULL; + sd_bus *bus = NULL; + int r; + bus_t *b; + + mrp_log_set_mask(MRP_LOG_UPTO(MRP_LOG_INFO)); + + ml = mrp_mainloop_create(); + r = sd_bus_open_user(&bus); + + if (ml == NULL || r != 0) + goto fail; + + mrp_add_sighandler(ml, SIGINT , signal_handler, NULL); + mrp_add_sighandler(ml, SIGTERM, signal_handler, NULL); + mrp_add_sighandler(ml, SIGQUIT, signal_handler, NULL); + + b = mrp_allocz(sizeof(*b)); + + if (b == NULL) + goto fail; + + sd_bus_add_match(bus, "type='signal'" , bus_signal_cb, bus); +#if 0 + sd_bus_add_match(bus, "type='method_call'" , bus_method_cb, bus); + sd_bus_add_match(bus, "type='method_return'", bus_return_cb, bus); +#else + sd_bus_add_fallback(bus, "/", bus_method_cb, bus); +#endif + + while (sd_bus_process(bus, NULL) > 0) + sd_bus_flush(bus); + + b->bus = bus; + b->ml = ml; + b->sl = mrp_add_subloop(ml, &bus_ops, b); + + if (b->sl == NULL) { + mrp_log_error("Failed to register D-Bus subloop."); + exit(1); + } + +#if 0 + if ((ts = mrp_add_timer(ml, 1000, emit_signal, bus)) == NULL) { + mrp_log_error("Failed to create signal emission timer."); + exit(1); + } +#endif + + if ((ts = mrp_add_timer(ml, 1000, call_method, bus)) == NULL) { + mrp_log_error("Failed to create method call timer."); + exit(1); + } + + mrp_mainloop_run(ml); + + fail: + mrp_del_timer(ts); + mrp_del_timer(tm); + + sd_bus_unref(bus); + mrp_mainloop_destroy(ml); + + return 0; +} diff --git a/src/common/tests/transport-test.c b/src/common/tests/transport-test.c new file mode 100644 index 0000000..e5797a0 --- /dev/null +++ b/src/common/tests/transport-test.c @@ -0,0 +1,998 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <netdb.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/socket.h> + +#define _GNU_SOURCE +#include <getopt.h> + +#include <murphy/common.h> + + +/* + * tags for generic message fields + */ + +#define TAG_SEQ ((uint16_t)0x1) +#define TAG_MSG ((uint16_t)0x2) +#define TAG_U8 ((uint16_t)0x3) +#define TAG_S8 ((uint16_t)0x4) +#define TAG_U16 ((uint16_t)0x5) +#define TAG_S16 ((uint16_t)0x6) +#define TAG_DBL ((uint16_t)0x7) +#define TAG_BLN ((uint16_t)0x8) +#define TAG_ASTR ((uint16_t)0x9) +#define TAG_AU32 ((uint16_t)0xa) +#define TAG_RPL ((uint16_t)0xb) +#define TAG_END MRP_MSG_FIELD_END + +#define U32_GUARD (uint32_t)-1 + +/* + * our test custom data type + */ + +#define TAG_CUSTOM 0x1 + +typedef struct { + uint32_t seq; + char *msg; + uint8_t u8; + int8_t s8; + uint16_t u16; + int16_t s16; + double dbl; + bool bln; + char **astr; + uint32_t nstr; + uint32_t fsck; + uint32_t *au32; + char *rpl; +} custom_t; + + +typedef custom_t native_t; + +static uint32_t native_id; + +MRP_DATA_DESCRIPTOR(custom_descr, TAG_CUSTOM, custom_t, + MRP_DATA_MEMBER(custom_t, seq, MRP_MSG_FIELD_UINT32), + MRP_DATA_MEMBER(custom_t, msg, MRP_MSG_FIELD_STRING), + MRP_DATA_MEMBER(custom_t, u8, MRP_MSG_FIELD_UINT8 ), + MRP_DATA_MEMBER(custom_t, s8, MRP_MSG_FIELD_SINT8 ), + MRP_DATA_MEMBER(custom_t, u16, MRP_MSG_FIELD_UINT16), + MRP_DATA_MEMBER(custom_t, s16, MRP_MSG_FIELD_SINT16), + MRP_DATA_MEMBER(custom_t, dbl, MRP_MSG_FIELD_DOUBLE), + MRP_DATA_MEMBER(custom_t, bln, MRP_MSG_FIELD_BOOL ), + MRP_DATA_MEMBER(custom_t, rpl, MRP_MSG_FIELD_STRING), + MRP_DATA_MEMBER(custom_t, nstr, MRP_MSG_FIELD_UINT32), + MRP_DATA_MEMBER(custom_t, fsck, MRP_MSG_FIELD_UINT32), + MRP_DATA_ARRAY_COUNT(custom_t, astr, nstr, + MRP_MSG_FIELD_STRING), + MRP_DATA_ARRAY_GUARD(custom_t, au32, u32, U32_GUARD, + MRP_MSG_FIELD_UINT32)); + +MRP_DATA_DESCRIPTOR(buggy_descr, TAG_CUSTOM, custom_t, + MRP_DATA_MEMBER(custom_t, seq, MRP_MSG_FIELD_UINT32), + MRP_DATA_MEMBER(custom_t, msg, MRP_MSG_FIELD_STRING), + MRP_DATA_MEMBER(custom_t, u8, MRP_MSG_FIELD_UINT8 ), + MRP_DATA_MEMBER(custom_t, s8, MRP_MSG_FIELD_SINT8 ), + MRP_DATA_MEMBER(custom_t, u16, MRP_MSG_FIELD_UINT16), + MRP_DATA_MEMBER(custom_t, s16, MRP_MSG_FIELD_SINT16), + MRP_DATA_MEMBER(custom_t, dbl, MRP_MSG_FIELD_DOUBLE), + MRP_DATA_MEMBER(custom_t, bln, MRP_MSG_FIELD_BOOL ), + MRP_DATA_MEMBER(custom_t, rpl, MRP_MSG_FIELD_STRING), + MRP_DATA_MEMBER(custom_t, nstr, MRP_MSG_FIELD_UINT32), + MRP_DATA_MEMBER(custom_t, fsck, MRP_MSG_FIELD_UINT32), + MRP_DATA_ARRAY_COUNT(custom_t, astr, fsck, + MRP_MSG_FIELD_STRING), + MRP_DATA_ARRAY_GUARD(custom_t, au32, u32, U32_GUARD, + MRP_MSG_FIELD_UINT32)); + + + +mrp_data_descr_t *data_descr; + + +typedef enum { + MODE_DEFAULT = 0, + MODE_MESSAGE = 1, + MODE_DATA = 2, + MODE_RAW = 3, + MODE_NATIVE = 4, +} msg_mode_t; + + +typedef struct { + mrp_mainloop_t *ml; + mrp_transport_t *lt, *t; + char *addrstr; + mrp_sockaddr_t addr; + socklen_t alen; + const char *atype; + int server; + int sock; + mrp_io_watch_t *iow; + mrp_timer_t *timer; + int mode; + int buggy; + int connect; + int stream; + int log_mask; + const char *log_target; + uint32_t seqno; +} context_t; + + +void recv_msg(mrp_transport_t *t, mrp_msg_t *msg, void *user_data); +void recvfrom_msg(mrp_transport_t *t, mrp_msg_t *msg, mrp_sockaddr_t *addr, + socklen_t addrlen, void *user_data); + +void recv_data(mrp_transport_t *t, void *data, uint16_t tag, void *user_data); +void recvfrom_data(mrp_transport_t *t, void *data, uint16_t tag, + mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data); + +void recvraw(mrp_transport_t *t, void *data, size_t size, void *user_data); +void recvrawfrom(mrp_transport_t *t, void *data, size_t size, + mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data); + + +void dump_msg(mrp_msg_t *msg, FILE *fp) +{ + mrp_msg_dump(msg, fp); +} + + +void recvfrom_msg(mrp_transport_t *t, mrp_msg_t *msg, mrp_sockaddr_t *addr, + socklen_t addrlen, void *user_data) +{ + context_t *c = (context_t *)user_data; + mrp_msg_field_t *f; + uint32_t seq; + char buf[256]; + int status; + + mrp_log_info("received a message"); + dump_msg(msg, stdout); + + if (c->server) { + seq = 0; + if ((f = mrp_msg_find(msg, TAG_SEQ)) != NULL) { + if (f->type == MRP_MSG_FIELD_UINT32) + seq = f->u32; + } + + snprintf(buf, sizeof(buf), "reply to message #%u", seq); + + if (!mrp_msg_append(msg, TAG_RPL, MRP_MSG_FIELD_STRING, buf, + TAG_END)) { + mrp_log_info("failed to append to received message"); + exit(1); + } + + if (c->connect) + status = mrp_transport_send(t, msg); + else + status = mrp_transport_sendto(t, msg, addr, addrlen); + + if (status) + mrp_log_info("reply successfully sent"); + else + mrp_log_error("failed to send reply"); + + /* message unreffed by transport layer */ + } +} + + +void recv_msg(mrp_transport_t *t, mrp_msg_t *msg, void *user_data) +{ + return recvfrom_msg(t, msg, NULL, 0, user_data); +} + + +void dump_custom(custom_t *msg, FILE *fp) +{ + uint32_t i; + + mrp_data_dump(msg, data_descr, fp); + fprintf(fp, "{\n"); + fprintf(fp, " seq = %u\n" , msg->seq); + fprintf(fp, " msg = '%s'\n", msg->msg); + fprintf(fp, " u8 = %u\n" , msg->u8); + fprintf(fp, " s8 = %d\n" , msg->s8); + fprintf(fp, " u16 = %u\n" , msg->u16); + fprintf(fp, " s16 = %d\n" , msg->s16); + fprintf(fp, " dbl = %f\n" , msg->dbl); + fprintf(fp, " bln = %s\n" , msg->bln ? "true" : "false"); + fprintf(fp, " astr = (%u)\n", msg->nstr); + for (i = 0; i < msg->nstr; i++) + fprintf(fp, " %s\n", msg->astr[i]); + fprintf(fp, " au32 =\n"); + for (i = 0; msg->au32[i] != U32_GUARD; i++) + fprintf(fp, " %u\n", msg->au32[i]); + fprintf(fp, " rpl = '%s'\n", msg->rpl); + fprintf(fp, "}\n"); +} + + +void free_custom(custom_t *msg) +{ + mrp_data_free(msg, data_descr->tag); +} + + +void recvfrom_data(mrp_transport_t *t, void *data, uint16_t tag, + mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data) +{ + context_t *c = (context_t *)user_data; + custom_t *msg = (custom_t *)data; + custom_t rpl; + char buf[256]; + uint32_t au32[] = { 9, 8, 7, 6, 5, -1 }; + int status; + + mrp_log_info("received custom message of type 0x%x", tag); + dump_custom(data, stdout); + + if (tag != data_descr->tag) { + mrp_log_error("Tag 0x%x != our custom type (0x%x).", + tag, data_descr->tag); + exit(1); + } + + if (c->server) { + rpl = *msg; + snprintf(buf, sizeof(buf), "reply to message #%u", msg->seq); + rpl.rpl = buf; + rpl.au32 = au32; + + if (c->connect) + status = mrp_transport_senddata(t, &rpl, data_descr->tag); + else + status = mrp_transport_senddatato(t, &rpl, data_descr->tag, + addr, addrlen); + if (status) + mrp_log_info("reply successfully sent"); + else + mrp_log_error("failed to send reply"); + } + + free_custom(msg); +} + + +void recv_data(mrp_transport_t *t, void *data, uint16_t tag, void *user_data) +{ + recvfrom_data(t, data, tag, NULL, 0, user_data); +} + + +void dump_raw(void *data, size_t size, FILE *fp) +{ + int len = (int)size; + + fprintf(fp, "[%*.*s]\n", len, len, (char *)data); +} + + +void recvfrom_raw(mrp_transport_t *t, void *data, size_t size, + mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data) +{ + context_t *c = (context_t *)user_data; + char rpl[256]; + size_t rpl_size; + int status; + + rpl_size = snprintf(rpl, sizeof(rpl), "reply to message [%*.*s]", + (int)size, (int)size, (char *)data); + + mrp_log_info("received raw message"); + dump_raw(data, size, stdout); + + if (strncmp((char *)data, "reply to ", 9) != 0) { + if (c->connect) + status = mrp_transport_sendraw(t, rpl, rpl_size); + else + status = mrp_transport_sendrawto(t, rpl, rpl_size, addr, addrlen); + + if (status) + mrp_log_info("reply successfully sent"); + else + mrp_log_error("failed to send reply"); + } +} + + +void recv_raw(mrp_transport_t *t, void *data, size_t size, void *user_data) +{ + recvfrom_raw(t, data, size, NULL, 0, user_data); +} + + +void free_native(native_t *msg) +{ + mrp_free_native(msg, native_id); +} + + +void recvfrom_native(mrp_transport_t *t, void *data, uint32_t type_id, + mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data) +{ + context_t *c = (context_t *)user_data; + native_t *msg = (native_t *)data; + native_t rpl; + char buf[256]; + uint32_t au32[] = { 9, 8, 7, 6, 5, -1 }; + int status; + + mrp_log_info("received native message of type 0x%x", type_id); + dump_custom(data, stdout); + + if (type_id != native_id) { + mrp_log_error("Received type 0x%x, expected 0x%x.", type_id, native_id); + exit(1); + } + + if (c->server) { + rpl = *msg; + snprintf(buf, sizeof(buf), "reply to message #%u", msg->seq); + rpl.rpl = buf; + rpl.au32 = au32; + + if (c->connect) + status = mrp_transport_sendnative(t, &rpl, native_id); + else + status = mrp_transport_sendnativeto(t, &rpl, native_id, + addr, addrlen); + if (status) + mrp_log_info("reply successfully sent"); + else + mrp_log_error("failed to send reply"); + } + + free_native(msg); +} + + +void recv_native(mrp_transport_t *t, void *data, uint32_t type_id, + void *user_data) +{ + recvfrom_native(t, data, type_id, NULL, 0, user_data); +} + + +void closed_evt(mrp_transport_t *t, int error, void *user_data) +{ + context_t *c = (context_t *)user_data; + + MRP_UNUSED(t); + MRP_UNUSED(c); + + if (error) { + mrp_log_error("Connection closed with error %d (%s).", error, + strerror(error)); + exit(1); + } + else { + mrp_log_info("Peer has closed the connection."); + exit(0); + } +} + + +void connection_evt(mrp_transport_t *lt, void *user_data) +{ + context_t *c = (context_t *)user_data; + int flags; + + flags = MRP_TRANSPORT_REUSEADDR | MRP_TRANSPORT_NONBLOCK; + c->t = mrp_transport_accept(lt, c, flags); + + if (c->t == NULL) { + mrp_log_error("Failed to accept new connection."); + exit(1); + } +} + + +void type_init(context_t *c) +{ + if (c->buggy && c->server) { + data_descr = &buggy_descr; + mrp_log_info("Deliberately using buggy data descriptor..."); + } + else + data_descr = &custom_descr; + + if (!mrp_msg_register_type(data_descr)) { + mrp_log_error("Failed to register custom data type."); + exit(1); + } +} + + +void register_native(void) +{ + MRP_NATIVE_TYPE(native_type, native_t, + MRP_UINT32(native_t, seq , DEFAULT), + MRP_STRING(native_t, msg , DEFAULT), + MRP_UINT8 (native_t, u8 , DEFAULT), + MRP_INT8 (native_t, s8 , DEFAULT), + MRP_UINT16(native_t, u16 , DEFAULT), + MRP_INT16 (native_t, s16 , DEFAULT), + MRP_DOUBLE(native_t, dbl , DEFAULT), + MRP_BOOL (native_t, bln , DEFAULT), + MRP_ARRAY (native_t, astr , DEFAULT, SIZED, + char *, nstr), + MRP_UINT32(native_t, nstr , DEFAULT), + MRP_ARRAY (native_t, au32 , DEFAULT, GUARDED, + uint32_t, "", .u32 = -1), + MRP_STRING(native_t, rpl , DEFAULT)); + + + if ((native_id = mrp_register_native(&native_type)) != MRP_INVALID_TYPE) + mrp_log_info("Successfully registered native type 'native_t'."); + else { + mrp_log_error("Failed to register native type 'native_t'."); + exit(1); + } +} + + +void server_init(context_t *c) +{ + static mrp_transport_evt_t evt = { + { .recvmsg = NULL }, + { .recvmsgfrom = NULL }, + .closed = NULL, + .connection = NULL, + }; + + int flags; + + type_init(c); + + switch (c->mode) { + case MODE_DATA: + evt.recvdata = recv_data; + evt.recvdatafrom = recvfrom_data; + break; + case MODE_RAW: + evt.recvraw = recv_raw; + evt.recvrawfrom = recvfrom_raw; + break; + case MODE_NATIVE: + evt.recvnative = recv_native; + evt.recvnativefrom = recvfrom_native; + break; + case MODE_MESSAGE: + default: + evt.recvmsg = recv_msg; + evt.recvmsgfrom = recvfrom_msg; + } + + if (c->stream) { + evt.connection = connection_evt; + evt.closed = closed_evt; + } + + flags = MRP_TRANSPORT_REUSEADDR; + + switch (c->mode) { + case MODE_DATA: flags |= MRP_TRANSPORT_MODE_DATA; break; + case MODE_RAW: flags |= MRP_TRANSPORT_MODE_RAW; break; + case MODE_NATIVE: flags |= MRP_TRANSPORT_MODE_NATIVE; break; + default: + case MODE_MESSAGE: flags |= MRP_TRANSPORT_MODE_MSG; + } + + c->lt = mrp_transport_create(c->ml, c->atype, &evt, c, flags); + + if (c->lt == NULL) { + mrp_log_error("Failed to create listening server transport."); + exit(1); + } + + if (!mrp_transport_bind(c->lt, &c->addr, c->alen)) { + mrp_log_error("Failed to bind transport to address %s.", c->addrstr); + exit(1); + } + + if (c->stream) { + if (!mrp_transport_listen(c->lt, 0)) { + mrp_log_error("Failed to listen on server transport."); + exit(1); + } + } +} + + +void send_msg(context_t *c) +{ + mrp_msg_t *msg; + uint32_t seq; + char buf[256]; + char *astr[] = { "this", "is", "an", "array", "of", "strings" }; + uint32_t au32[] = { 1, 2, 3, + 1 << 16, 2 << 16, 3 << 16, + 1 << 24, 2 << 24, 3 << 24 }; + uint32_t nstr = MRP_ARRAY_SIZE(astr); + uint32_t nu32 = MRP_ARRAY_SIZE(au32); + int status; + + seq = c->seqno++; + snprintf(buf, sizeof(buf), "this is message #%u", (unsigned int)seq); + + msg = mrp_msg_create(TAG_SEQ , MRP_MSG_FIELD_UINT32, seq, + TAG_MSG , MRP_MSG_FIELD_STRING, buf, + TAG_U8 , MRP_MSG_FIELD_UINT8 , seq & 0xf, + TAG_S8 , MRP_MSG_FIELD_SINT8 , -(seq & 0xf), + TAG_U16 , MRP_MSG_FIELD_UINT16, seq, + TAG_S16 , MRP_MSG_FIELD_SINT16, - seq, + TAG_DBL , MRP_MSG_FIELD_DOUBLE, seq / 3.0, + TAG_BLN , MRP_MSG_FIELD_BOOL , seq & 0x1, + TAG_ASTR, MRP_MSG_FIELD_ARRAY_OF(STRING), nstr, astr, + TAG_AU32, MRP_MSG_FIELD_ARRAY_OF(UINT32), nu32, au32, + TAG_END); + + if (msg == NULL) { + mrp_log_error("Failed to create new message."); + exit(1); + } + + if (c->connect) + status = mrp_transport_send(c->t, msg); + else + status = mrp_transport_sendto(c->t, msg, &c->addr, c->alen); + + if (!status) { + mrp_log_error("Failed to send message #%d.", seq); + exit(1); + } + else + mrp_log_info("Message #%d succesfully sent.", seq); + + mrp_msg_unref(msg); +} + + +void send_data(context_t *c) +{ + uint32_t seq = c->seqno++; + custom_t msg; + char buf[256]; + char *astr[] = { "this", "is", "a", "test", "string", "array" }; + uint32_t au32[] = { 1, 2, 3, 4, 5, 6, 7, -1 }; + int status; + + msg.seq = seq; + snprintf(buf, sizeof(buf), "this is message #%u", (unsigned int)seq); + msg.msg = buf; + msg.u8 = seq & 0xf; + msg.s8 = -(seq & 0xf); + msg.u16 = seq; + msg.s16 = - seq; + msg.dbl = seq / 3.0; + msg.bln = seq & 0x1; + msg.astr = astr; + msg.nstr = MRP_ARRAY_SIZE(astr); + msg.fsck = 1000; + msg.au32 = au32; + msg.rpl = ""; + + if (c->connect) + status = mrp_transport_senddata(c->t, &msg, data_descr->tag); + else + status = mrp_transport_senddatato(c->t, &msg, data_descr->tag, + &c->addr, c->alen); + + if (!status) { + mrp_log_error("Failed to send message #%d.", msg.seq); + exit(1); + } + else + mrp_log_info("Message #%d succesfully sent.", msg.seq); +} + + +void send_raw(context_t *c) +{ + uint32_t seq = c->seqno++; + char msg[256]; + size_t size; + int status; + + size = snprintf(msg, sizeof(msg), "this is message #%u", seq); + + if (c->connect) + status = mrp_transport_sendraw(c->t, msg, size); + else + status = mrp_transport_sendrawto(c->t, msg, size, &c->addr, c->alen); + + if (!status) { + mrp_log_error("Failed to send raw message #%d.", seq); + exit(1); + } + else + mrp_log_info("Message #%u succesfully sent.", seq); +} + + +void send_native(context_t *c) +{ + uint32_t seq = c->seqno++; + custom_t msg; + char buf[256]; + char *astr[] = { "this", "is", "a", "test", "string", "array" }; + uint32_t au32[] = { 1, 2, 3, 4, 5, 6, 7, -1 }; + int status; + + msg.seq = seq; + snprintf(buf, sizeof(buf), "this is message #%u", (unsigned int)seq); + msg.msg = buf; + msg.u8 = seq & 0xf; + msg.s8 = -(seq & 0xf); + msg.u16 = seq; + msg.s16 = - seq; + msg.dbl = seq / 3.0; + msg.bln = seq & 0x1; + msg.astr = astr; + msg.nstr = MRP_ARRAY_SIZE(astr); + msg.fsck = 1000; + msg.au32 = au32; + msg.rpl = ""; + + if (c->connect) + status = mrp_transport_sendnative(c->t, &msg, native_id); + else + status = mrp_transport_sendnativeto(c->t, &msg, native_id, + &c->addr, c->alen); + + if (!status) { + mrp_log_error("Failed to send message #%d.", msg.seq); + exit(1); + } + else + mrp_log_info("Message #%d succesfully sent.", msg.seq); +} + + +void send_cb(mrp_timer_t *t, void *user_data) +{ + context_t *c = (context_t *)user_data; + + MRP_UNUSED(t); + + switch (c->mode) { + case MODE_DATA: send_data(c); break; + case MODE_RAW: send_raw(c); break; + case MODE_NATIVE: send_native(c); break; + default: + case MODE_MESSAGE: send_msg(c); + } +} + + +void client_init(context_t *c) +{ + static mrp_transport_evt_t evt = { + { .recvmsg = NULL }, + { .recvmsgfrom = NULL }, + .closed = closed_evt, + .connection = NULL + }; + + int flags; + + type_init(c); + + switch (c->mode) { + case MODE_DATA: + evt.recvdata = recv_data; + evt.recvdatafrom = recvfrom_data; + flags = MRP_TRANSPORT_MODE_DATA; + break; + case MODE_RAW: + evt.recvraw = recv_raw; + evt.recvrawfrom = recvfrom_raw; + flags = MRP_TRANSPORT_MODE_RAW; + break; + case MODE_NATIVE: + evt.recvnative = recv_native; + evt.recvnativefrom = recvfrom_native; + flags = MRP_TRANSPORT_MODE_NATIVE; + break; + default: + case MODE_MESSAGE: + evt.recvmsg = recv_msg; + evt.recvmsgfrom = recvfrom_msg; + flags = MRP_TRANSPORT_MODE_MSG; + } + + c->t = mrp_transport_create(c->ml, c->atype, &evt, c, flags); + + if (c->t == NULL) { + mrp_log_error("Failed to create new transport."); + exit(1); + } + + if (!strcmp(c->atype, "unxd")) { + char addrstr[] = "unxd:@stream-test-client"; + mrp_sockaddr_t addr; + socklen_t alen; + + alen = mrp_transport_resolve(NULL, addrstr, &addr, sizeof(addr), NULL); + if (alen <= 0) { + mrp_log_error("Failed to resolve transport address '%s'.", addrstr); + exit(1); + } + + if (!mrp_transport_bind(c->t, &addr, alen)) { + mrp_log_error("Failed to bind to transport address '%s'.", addrstr); + exit(1); + } + } + + if (c->connect) { + if (!mrp_transport_connect(c->t, &c->addr, c->alen)) { + mrp_log_error("Failed to connect to %s.", c->addrstr); + exit(1); + } + } + + + c->timer = mrp_add_timer(c->ml, 1000, send_cb, c); + + if (c->timer == NULL) { + mrp_log_error("Failed to create send timer."); + exit(1); + } +} + + +static void print_usage(const char *argv0, int exit_code, const char *fmt, ...) +{ + va_list ap; + + if (fmt && *fmt) { + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + } + + printf("usage: %s [options] [transport-address]\n\n" + "The possible options are:\n" + " -s, --server run as test server (default)\n" + " -C, --connect connect transport\n" + " For connection-oriented transports, this is automatic.\n" + " -a, --address address to use\n" + " -c, --custom use custom messages\n" + " -m, --message use generic messages (default)\n" + " -r, --raw use raw messages\n" + " -n, --native use native messages\n" + " -b, --buggy use buggy data descriptors\n" + " -t, --log-target=TARGET log target to use\n" + " TARGET is one of stderr,stdout,syslog, or a logfile path\n" + " -l, --log-level=LEVELS logging level to use\n" + " LEVELS is a comma separated list of info, error and warning\n" + " -v, --verbose increase logging verbosity\n" + " -d, --debug enable debug messages\n" + " -h, --help show help on usage\n", + argv0); + + if (exit_code < 0) + return; + else + exit(exit_code); +} + + +static void config_set_defaults(context_t *ctx) +{ + mrp_clear(ctx); + ctx->addrstr = "tcp4:127.0.0.1:3000"; + ctx->server = FALSE; + ctx->log_mask = MRP_LOG_UPTO(MRP_LOG_DEBUG); + ctx->log_target = MRP_LOG_TO_STDERR; +} + + +int parse_cmdline(context_t *ctx, int argc, char **argv) +{ +# define OPTIONS "scmrnbCa:l:t:v:d:h" + struct option options[] = { + { "server" , no_argument , NULL, 's' }, + { "address" , required_argument, NULL, 'a' }, + { "custom" , no_argument , NULL, 'c' }, + { "message" , no_argument , NULL, 'm' }, + { "raw" , no_argument , NULL, 'r' }, + { "native" , no_argument , NULL, 'n' }, + { "connect" , no_argument , NULL, 'C' }, + + { "buggy" , no_argument , NULL, 'b' }, + { "log-level" , required_argument, NULL, 'l' }, + { "log-target", required_argument, NULL, 't' }, + { "verbose" , optional_argument, NULL, 'v' }, + { "debug" , required_argument, NULL, 'd' }, + { "help" , no_argument , NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + + int opt; + + config_set_defaults(ctx); + + while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) { + switch (opt) { + case 's': + ctx->server = TRUE; + break; + + case 'c': + if (ctx->mode == MODE_DEFAULT) + ctx->mode = MODE_DATA; + else { + mrp_log_error("Multiple modes requested."); + exit(1); + } + break; + + case 'm': + if (ctx->mode == MODE_DEFAULT) + ctx->mode = MODE_MESSAGE; + else { + mrp_log_error("Multiple modes requested."); + exit(1); + } + break; + + case 'r': + if (ctx->mode == MODE_DEFAULT) + ctx->mode = MODE_RAW; + else { + mrp_log_error("Multiple modes requested."); + exit(1); + } + break; + + case 'n': + if (ctx->mode == MODE_DEFAULT) + ctx->mode = MODE_NATIVE; + else { + mrp_log_error("Multiple modes requested."); + exit(1); + } + break; + + case 'b': + ctx->buggy = TRUE; + break; + + case 'C': + ctx->connect = TRUE; + break; + + case 'a': + ctx->addrstr = optarg; + break; + + case 'v': + ctx->log_mask <<= 1; + ctx->log_mask |= 1; + break; + + case 'l': + ctx->log_mask = mrp_log_parse_levels(optarg); + if (ctx->log_mask < 0) + print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg); + break; + + case 't': + ctx->log_target = mrp_log_parse_target(optarg); + if (!ctx->log_target) + print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg); + break; + + case 'd': + ctx->log_mask |= MRP_LOG_MASK_DEBUG; + mrp_debug_set_config(optarg); + mrp_debug_enable(TRUE); + break; + + case 'h': + print_usage(argv[0], -1, ""); + exit(0); + break; + + default: + print_usage(argv[0], EINVAL, "invalid option '%c'", opt); + } + } + + return TRUE; +} + + +int main(int argc, char *argv[]) +{ + context_t c; + + if (!parse_cmdline(&c, argc, argv)) + exit(1); + + mrp_log_set_mask(c.log_mask); + mrp_log_set_target(c.log_target); + + if (c.server) + mrp_log_info("Running as server, using address '%s'...", c.addrstr); + else + mrp_log_info("Running as client, using address '%s'...", c.addrstr); + + switch (c.mode) { + case MODE_DATA: mrp_log_info("Using custom data messages..."); break; + case MODE_RAW: mrp_log_info("Using raw messages..."); break; + case MODE_NATIVE: + register_native(); + mrp_log_info("Using native messages..."); + break; + default: + case MODE_MESSAGE: mrp_log_info("Using generic messages..."); + } + + if (!strncmp(c.addrstr, "tcp", 3) || !strncmp(c.addrstr, "unxs", 4) || + !strncmp(c.addrstr, "wsck", 4)) { + c.stream = TRUE; + c.connect = TRUE; + } + + c.alen = mrp_transport_resolve(NULL, c.addrstr, + &c.addr, sizeof(c.addr), &c.atype); + if (c.alen <= 0) { + mrp_log_error("Failed to resolve transport address '%s'.", c.addrstr); + exit(1); + } + + c.ml = mrp_mainloop_create(); + + if (c.server) + server_init(&c); + else + client_init(&c); + + mrp_mainloop_run(c.ml); + + return 0; +} diff --git a/src/common/tlv.c b/src/common/tlv.c new file mode 100644 index 0000000..fd216a3 --- /dev/null +++ b/src/common/tlv.c @@ -0,0 +1,662 @@ +/* + * Copyright (c) 2012, 2013, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <errno.h> + +#include <murphy/common/macros.h> +#include <murphy/common/debug.h> +#include <murphy/common/log.h> +#include <murphy/common/mm.h> +#include <murphy/common/tlv.h> + +#define TLV_MIN_PREALLOC 4096 +#define TLV_MIN_CHUNK 64 + +int mrp_tlv_setup_write(mrp_tlv_t *tlv, size_t prealloc) +{ + if (prealloc < TLV_MIN_PREALLOC) + prealloc = TLV_MIN_PREALLOC; + + if ((tlv->buf = mrp_allocz(prealloc)) == NULL) + return -1; + + tlv->size = prealloc; + tlv->p = tlv->buf; + tlv->write = 1; + + return 0; +} + + +static inline size_t tlv_space(mrp_tlv_t *tlv) +{ + if (tlv->size > 0 && tlv->write) + return tlv->size - (tlv->p - tlv->buf); + else + return 0; +} + + +static inline size_t tlv_data(mrp_tlv_t *tlv) +{ + if (!tlv->write) + return tlv->size - (tlv->p - tlv->buf); + else + return tlv->p - tlv->buf; +} + + +int mrp_tlv_ensure(mrp_tlv_t *tlv, size_t size) +{ + size_t left, diff; + + if (!tlv->write) + return -1; + + if ((left = tlv_space(tlv)) < size) { + diff = size - left; + + if (diff < TLV_MIN_CHUNK) + diff = TLV_MIN_CHUNK; + + tlv->p -= (ptrdiff_t)tlv->buf; + + if (mrp_realloc(tlv->buf, tlv->size + diff) == NULL) { + tlv->p += (ptrdiff_t)tlv->buf; + + return -1; + } + + memset(tlv->buf + tlv->size, 0, diff); + + tlv->size += diff; + tlv->p += (ptrdiff_t)tlv->buf; + } + + return 0; +} + + +void *mrp_tlv_reserve(mrp_tlv_t *tlv, size_t size, int align) +{ + void *reserved; + ptrdiff_t offs, pad; + size_t len; + + offs = tlv->p - tlv->buf; + + if (align > 1) + pad = align - (offs & (align - 1)); + else + pad = 0; + + len = size + pad; + + if (mrp_tlv_ensure(tlv, len) < 0) + return NULL; + + if (pad) + memset(tlv->p, 0, pad); + + reserved = tlv->p + pad; + tlv->p += len; + + return reserved; +} + + +int mrp_tlv_setup_read(mrp_tlv_t *tlv, void *buf, size_t size) +{ + tlv->buf = tlv->p = buf; + tlv->size = size; + tlv->write = 0; + + return 0; +} + + +static void *tlv_consume(mrp_tlv_t *tlv, size_t size) +{ + char *p; + + if (tlv_data(tlv) < size) + return NULL; + + p = tlv->p; + tlv->p += size; + + return p; +} + + +void mrp_tlv_trim(mrp_tlv_t *tlv) +{ + size_t left; + + if (!tlv->write) + return; + + if ((left = tlv_space(tlv)) == 0) + return; + + tlv->p -= (ptrdiff_t)tlv->buf; + + if (mrp_realloc(tlv->buf, tlv->size - left) != NULL) { + tlv->size -= left; + tlv->p += (ptrdiff_t)tlv->buf; + } +} + + +size_t mrp_tlv_offset(mrp_tlv_t *tlv) +{ + return (size_t)(tlv->p - tlv->buf); +} + + +void mrp_tlv_cleanup(mrp_tlv_t *tlv) +{ + if (tlv->write) + mrp_free(tlv->buf); + + tlv->buf = tlv->p = NULL; + tlv->size = 0; +} + + +void mrp_tlv_steal(mrp_tlv_t *tlv, void **bufp, size_t *sizep) +{ + if (tlv->write) { + *bufp = tlv->buf; + *sizep = tlv->p - tlv->buf; + + tlv->buf = tlv->p = NULL; + tlv->size = 0; + } + else { + *bufp = NULL; + *sizep = 0; + } +} + + +static inline int push_tag(mrp_tlv_t *tlv, uint32_t tag) +{ + uint32_t *tagp; + + if (tag) { + if ((tagp = mrp_tlv_reserve(tlv, sizeof(*tagp), 1)) == NULL) + return -1; + else + *tagp = htobe32(tag); + } + + return 0; +} + + +int mrp_tlv_push_int8(mrp_tlv_t *tlv, uint32_t tag, int8_t v) +{ + int8_t *p; + + if (push_tag(tlv, tag) < 0) + return -1; + + if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) { + *p = v; + + return 0; + } + + return -1; +} + + +int mrp_tlv_push_uint8(mrp_tlv_t *tlv, uint32_t tag, uint8_t v) +{ + uint8_t *p; + + if (push_tag(tlv, tag) < 0) + return -1; + + if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) { + *p = v; + + return 0; + } + + return -1; +} + + +int mrp_tlv_push_int16(mrp_tlv_t *tlv, uint32_t tag, int16_t v) +{ + int16_t *p; + + if (push_tag(tlv, tag) < 0) + return -1; + + if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) { + *p = htobe16(v); + + return 0; + } + + return -1; +} + + +int mrp_tlv_push_uint16(mrp_tlv_t *tlv, uint32_t tag, uint16_t v) +{ + uint16_t *p; + + if (push_tag(tlv, tag) < 0) + return -1; + + if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) { + *p = htobe16(v); + + return 0; + } + + return -1; +} + + +int mrp_tlv_push_int32(mrp_tlv_t *tlv, uint32_t tag, int32_t v) +{ + int32_t *p; + + if (push_tag(tlv, tag) < 0) + return -1; + + if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) { + *p = htobe32(v); + + return 0; + } + + return -1; +} + + +int mrp_tlv_push_uint32(mrp_tlv_t *tlv, uint32_t tag, uint32_t v) +{ + uint32_t *p; + + if (push_tag(tlv, tag) < 0) + return -1; + + if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) { + *p = htobe32(v); + + return 0; + } + + return -1; +} + + +int mrp_tlv_push_int64(mrp_tlv_t *tlv, uint32_t tag, int64_t v) +{ + int64_t *p; + + if (push_tag(tlv, tag) < 0) + return -1; + + if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) { + *p = htobe64(v); + + return 0; + } + + return -1; +} + + +int mrp_tlv_push_uint64(mrp_tlv_t *tlv, uint32_t tag, uint64_t v) +{ + uint64_t *p; + + if (push_tag(tlv, tag) < 0) + return -1; + + if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) { + *p = htobe64(v); + + return 0; + } + + return -1; +} + + +int mrp_tlv_push_float(mrp_tlv_t *tlv, uint32_t tag, float v) +{ + float *p; + + if (push_tag(tlv, tag) < 0) + return -1; + + if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) { + *p = v; + + return 0; + } + + return -1; +} + + +int mrp_tlv_push_double(mrp_tlv_t *tlv, uint32_t tag, double v) +{ + double *p; + + if (push_tag(tlv, tag) < 0) + return -1; + + if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) { + *p = v; + + return 0; + } + + return -1; +} + + +int mrp_tlv_push_bool(mrp_tlv_t *tlv, uint32_t tag, bool v) +{ + bool *p; + + if (push_tag(tlv, tag) < 0) + return -1; + + if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) { + *p = v; + + return 0; + } + + return -1; +} + + +int mrp_tlv_push_string(mrp_tlv_t *tlv, uint32_t tag, const char *str) +{ + uint32_t *sizep; + char *strp; + size_t len = str ? strlen(str) + 1 : 0; + + if (push_tag(tlv, tag) < 0) + return -1; + + if ((sizep = mrp_tlv_reserve(tlv, sizeof(*sizep), 1)) == NULL) + return -1; + + *sizep = htobe32((uint32_t)len); + + if (len > 0) { + if ((strp = mrp_tlv_reserve(tlv, len, 1)) == NULL) + return -1; + + strcpy(strp, str); + } + + return 0; +} + + +int pull_tag(mrp_tlv_t *tlv, uint32_t tag) +{ + uint32_t *tagp; + + if (tag) { + if ((tagp = tlv_consume(tlv, sizeof(*tagp))) == NULL) + return -1; + + if (be32toh(*tagp) != tag) + return -1; + } + + return 0; +} + + +int mrp_tlv_pull_int8(mrp_tlv_t *tlv, uint32_t tag, int8_t *v) +{ + int8_t *p; + + if (pull_tag(tlv, tag) < 0) + return -1; + + if ((p = tlv_consume(tlv, sizeof(*p))) == NULL) + return -1; + + *v = *p; + + return 0; +} + + +int mrp_tlv_pull_uint8(mrp_tlv_t *tlv, uint32_t tag, uint8_t *v) +{ + uint8_t *p; + + if (pull_tag(tlv, tag) < 0) + return -1; + + if ((p = tlv_consume(tlv, sizeof(*p))) == NULL) + return -1; + + *v = *p; + + return 0; +} + + +int mrp_tlv_pull_int16(mrp_tlv_t *tlv, uint32_t tag, int16_t *v) +{ + int16_t *p; + + if (pull_tag(tlv, tag) < 0) + return -1; + + if ((p = tlv_consume(tlv, sizeof(*p))) == NULL) + return -1; + + *v = be16toh(*p); + + return 0; +} + + +int mrp_tlv_pull_uint16(mrp_tlv_t *tlv, uint32_t tag, uint16_t *v) +{ + uint16_t *p; + + if (pull_tag(tlv, tag) < 0) + return -1; + + if ((p = tlv_consume(tlv, sizeof(*p))) == NULL) + return -1; + + *v = be16toh(*p); + + return 0; +} + + +int mrp_tlv_pull_int32(mrp_tlv_t *tlv, uint32_t tag, int32_t *v) +{ + int32_t *p; + + if (pull_tag(tlv, tag) < 0) + return -1; + + if ((p = tlv_consume(tlv, sizeof(*p))) == NULL) + return -1; + + *v = be32toh(*p); + + return 0; +} + + +int mrp_tlv_pull_uint32(mrp_tlv_t *tlv, uint32_t tag, uint32_t *v) +{ + uint32_t *p; + + if (pull_tag(tlv, tag) < 0) + return -1; + + if ((p = tlv_consume(tlv, sizeof(*p))) == NULL) + return -1; + + *v = be32toh(*p); + + return 0; +} + + +int mrp_tlv_pull_int64(mrp_tlv_t *tlv, uint32_t tag, int64_t *v) +{ + int64_t *p; + + if (pull_tag(tlv, tag) < 0) + return -1; + + if ((p = tlv_consume(tlv, sizeof(*p))) == NULL) + return -1; + + *v = be64toh(*p); + + return 0; +} + + +int mrp_tlv_pull_uint64(mrp_tlv_t *tlv, uint32_t tag, uint64_t *v) +{ + uint64_t *p; + + if (pull_tag(tlv, tag) < 0) + return -1; + + if ((p = tlv_consume(tlv, sizeof(*p))) == NULL) + return -1; + + *v = be64toh(*p); + + return 0; +} + + +int mrp_tlv_pull_float(mrp_tlv_t *tlv, uint32_t tag, float *v) +{ + float *p; + + if (pull_tag(tlv, tag) < 0) + return -1; + + if ((p = tlv_consume(tlv, sizeof(*p))) == NULL) + return -1; + + *v = *p; + + return 0; +} + + +int mrp_tlv_pull_double(mrp_tlv_t *tlv, uint32_t tag, double *v) +{ + double *p; + + if (pull_tag(tlv, tag) < 0) + return -1; + + if ((p = tlv_consume(tlv, sizeof(*p))) == NULL) + return -1; + + *v = *p; + + return 0; +} + + +int mrp_tlv_pull_bool(mrp_tlv_t *tlv, uint32_t tag, bool *v) +{ + bool *p; + + if (pull_tag(tlv, tag) < 0) + return -1; + + if ((p = tlv_consume(tlv, sizeof(*p))) == NULL) + return -1; + + *v = *p; + + return 0; +} + + +int mrp_tlv_pull_string(mrp_tlv_t *tlv, uint32_t tag, char **v, size_t max, + void *(alloc)(size_t, void *), void *alloc_data) +{ + uint32_t *sizep, size; + char *str; + + if (pull_tag(tlv, tag) < 0) + return -1; + + if ((sizep = tlv_consume(tlv, sizeof(*sizep))) == NULL) + return -1; + + size = be32toh(*sizep); + + if (max != (size_t)-1 && max < size) { + errno = EOVERFLOW; + return -1; + } + + if (size > 0) { + if ((str = tlv_consume(tlv, size)) == NULL) + return -1; + + if (*v == NULL) + if ((*v = alloc(size, alloc_data)) == NULL) + return -1; + + strncpy(*v, str, size - 1); + (*v)[size - 1] = '\0'; + } + else + *v = NULL; + + return 0; +} diff --git a/src/common/tlv.h b/src/common/tlv.h new file mode 100644 index 0000000..b746546 --- /dev/null +++ b/src/common/tlv.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2012, 2013, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MRP_COMMON_TLV_H__ +#define __MRP_COMMON_TLV_H__ + +#include <stdint.h> +#include <stdbool.h> + +#include <murphy/common/macros.h> + +MRP_CDECL_BEGIN + +#define MRP_TLV_UNTAGGED 0 + +/** + * a tagged-value-list encoding/decoding buffer + */ + +typedef struct { + void *buf; /* actual data buffer */ + size_t size; /* allocated buffer size */ + void *p; /* encoding/decoding pointer */ + int write : 1; /* whether set up for writing */ +} mrp_tlv_t; + +/** Set up the given TLV buffer for encoding. */ +int mrp_tlv_setup_write(mrp_tlv_t *tlv, size_t prealloc); + +/** Set up the given TLV buffer for decoding. */ +int mrp_tlv_setup_read(mrp_tlv_t *tlv, void *buf, size_t size); + +/** Clean up the given TLV buffer. */ +void mrp_tlv_cleanup(mrp_tlv_t *tlv); + +/** Ensure the given amount of space is available in the TLV buffer. */ +int mrp_tlv_ensure(mrp_tlv_t *tlv, size_t size); + +/** Reserve the given amount of buffer space from the TLV buffer. */ +void *mrp_tlv_reserve(mrp_tlv_t *tlv, size_t size, int align); + +/** Take ownership of the data buffer from the TLV buffer. */ +void mrp_tlv_steal(mrp_tlv_t *tlv, void **bufp, size_t *sizep); + +/** Trim the data buffer of the TLV buffer to current amount of data. */ +void mrp_tlv_trim(mrp_tlv_t *tlv); + +/** Get the current read/write offset from the TLV buffer. */ +size_t mrp_tlv_offset(mrp_tlv_t *tlv); + +/** Add an int8_t with an optional tag to the TLV buffer. */ +int mrp_tlv_push_int8(mrp_tlv_t *tlv, uint32_t tag, int8_t v); + +/** Add an uint8_t with an optional tag to the TLV buffer. */ +int mrp_tlv_push_uint8(mrp_tlv_t *tlv, uint32_t tag, uint8_t v); + +/** Add an int16_t with an optional tag to the TLV buffer. */ +int mrp_tlv_push_int16(mrp_tlv_t *tlv, uint32_t tag, int16_t v); + +/** Add an uint16_t with an optional tag to the TLV buffer. */ +int mrp_tlv_push_uint16(mrp_tlv_t *tlv, uint32_t tag, uint16_t v); + +/** Add an int32_t with an optional tag to the TLV buffer. */ +int mrp_tlv_push_int32(mrp_tlv_t *tlv, uint32_t tag, int32_t v); + +/** Add an uint32_t with an optional tag to the TLV buffer. */ +int mrp_tlv_push_uint32(mrp_tlv_t *tlv, uint32_t tag, uint32_t v); + +/** Add an int64_t with an optional tag to the TLV buffer. */ +int mrp_tlv_push_int64(mrp_tlv_t *tlv, uint32_t tag, int64_t v); + +/** Add an uint64_t with an optional tag to the TLV buffer. */ +int mrp_tlv_push_uint64(mrp_tlv_t *tlv, uint32_t tag, uint64_t v); + +/** Add an float with an optional tag to the TLV buffer. */ +int mrp_tlv_push_float(mrp_tlv_t *tlv, uint32_t tag, float v); + +/** Add an double with an optional tag to the TLV buffer. */ +int mrp_tlv_push_double(mrp_tlv_t *tlv, uint32_t tag, double v); + +/** Add a boolean with an optional tag to the TLV buffer. */ +int mrp_tlv_push_bool(mrp_tlv_t *tlv, uint32_t tag, bool v); + +/** Add a string with an optional tag to the TLV buffer. */ +int mrp_tlv_push_string(mrp_tlv_t *tlv, uint32_t tag, const char *str); + + +int mrp_tlv_pull_int8(mrp_tlv_t *tlv, uint32_t tag, int8_t *v); +int mrp_tlv_pull_uint8(mrp_tlv_t *tlv, uint32_t tag, uint8_t *v); +int mrp_tlv_pull_int16(mrp_tlv_t *tlv, uint32_t tag, int16_t *v); +int mrp_tlv_pull_uint16(mrp_tlv_t *tlv, uint32_t tag, uint16_t *v); +int mrp_tlv_pull_int32(mrp_tlv_t *tlv, uint32_t tag, int32_t *v); +int mrp_tlv_pull_uint32(mrp_tlv_t *tlv, uint32_t tag, uint32_t *v); +int mrp_tlv_pull_int64(mrp_tlv_t *tlv, uint32_t tag, int64_t *v); +int mrp_tlv_pull_uint64(mrp_tlv_t *tlv, uint32_t tag, uint64_t *v); +int mrp_tlv_pull_float(mrp_tlv_t *tlv, uint32_t tag, float *v); +int mrp_tlv_pull_double(mrp_tlv_t *tlv, uint32_t tag, double *v); +int mrp_tlv_pull_bool(mrp_tlv_t *tlv, uint32_t tag, bool *v); +int mrp_tlv_pull_string(mrp_tlv_t *tlv, uint32_t tag, char **v, size_t max, + void *(alloc)(size_t, void *), void *alloc_data); + +MRP_CDECL_END + +#endif /* __MRP_COMMON_TLV_H__ */ diff --git a/src/common/transport.c b/src/common/transport.c new file mode 100644 index 0000000..d00c588 --- /dev/null +++ b/src/common/transport.c @@ -0,0 +1,829 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include <errno.h> + +#include <murphy/common/mm.h> +#include <murphy/common/list.h> +#include <murphy/common/log.h> +#include <murphy/common/native-types.h> +#include <murphy/common/transport.h> + +static int check_destroy(mrp_transport_t *t); +static int recv_data(mrp_transport_t *t, void *data, size_t size, + mrp_sockaddr_t *addr, socklen_t addrlen); +static inline int purge_destroyed(mrp_transport_t *t); + + +static MRP_LIST_HOOK(transports); +static mrp_sighandler_t *pipe_handler; + + +static int check_request_callbacks(mrp_transport_req_t *req) +{ + /* XXX TODO: hmm... this probably needs more thought/work */ + + if (!req->open || !req->close) + return FALSE; + + if (req->accept) { + if (!req->sendmsg || !req->sendraw || !req->senddata) + return FALSE; + } + else { + if (!req->sendmsgto || !req->sendrawto || !req->senddatato) + return FALSE; + } + + if (( req->connect && !req->disconnect) || + (!req->connect && req->disconnect)) + return FALSE; + + return TRUE; +} + + +int mrp_transport_register(mrp_transport_descr_t *d) +{ + if (!check_request_callbacks(&d->req)) + return FALSE; + + if (d->size >= sizeof(mrp_transport_t)) { + mrp_list_init(&d->hook); + mrp_list_append(&transports, &d->hook); + + return TRUE; + } + else + return FALSE; +} + + +void mrp_transport_unregister(mrp_transport_descr_t *d) +{ + mrp_list_delete(&d->hook); +} + + +static mrp_transport_descr_t *find_transport(const char *type) +{ + mrp_transport_descr_t *d; + mrp_list_hook_t *p, *n; + + mrp_list_foreach(&transports, p, n) { + d = mrp_list_entry(p, typeof(*d), hook); + if (!strcmp(d->type, type)) + return d; + } + + return NULL; +} + + +static int check_event_callbacks(mrp_transport_evt_t *evt) +{ + /* + * For connection-oriented transports we require a recv* callback + * and a closed callback. + * + * For connectionless transports we only require a recvfrom* callback. + * A recv* callback is optional, however the transport cannot be put + * to connected mode (usually for doing sender-based filtering) if + * recv* is omitted. + */ + + if (evt->connection != NULL) { + if (evt->recvmsg == NULL || evt->closed == NULL) + return FALSE; + } + else { + if (evt->recvmsgfrom == NULL) + return FALSE; + } + + return TRUE; +} + + +static void sigpipe_handler(mrp_sighandler_t *h, int sig, void *user_data) +{ + MRP_UNUSED(h); + MRP_UNUSED(user_data); + + mrp_debug("caught signal %d (%s)...", sig, strsignal(sig)); +} + + +mrp_transport_t *mrp_transport_create(mrp_mainloop_t *ml, const char *type, + mrp_transport_evt_t *evt, void *user_data, + int flags) +{ + mrp_transport_descr_t *d; + mrp_transport_t *t; + + if (!pipe_handler) + pipe_handler = mrp_add_sighandler(ml, SIGPIPE, sigpipe_handler, NULL); + + if (!check_event_callbacks(evt)) { + errno = EINVAL; + return NULL; + } + + if ((d = find_transport(type)) != NULL) { + if ((t = mrp_allocz(d->size)) != NULL) { + t->descr = d; + t->ml = ml; + t->evt = *evt; + t->user_data = user_data; + + t->check_destroy = check_destroy; + t->recv_data = recv_data; + t->flags = flags & ~MRP_TRANSPORT_MODE_MASK; + t->mode = flags & MRP_TRANSPORT_MODE_MASK; + + if (!t->descr->req.open(t)) { + mrp_free(t); + t = NULL; + } + } + } + else + t = NULL; + + return t; +} + + +mrp_transport_t *mrp_transport_create_from(mrp_mainloop_t *ml, const char *type, + void *conn, mrp_transport_evt_t *evt, + void *user_data, int flags, + int state) +{ + mrp_transport_descr_t *d; + mrp_transport_t *t; + + if (!pipe_handler) + pipe_handler = mrp_add_sighandler(ml, SIGPIPE, sigpipe_handler, NULL); + + if (!check_event_callbacks(evt)) { + errno = EINVAL; + return NULL; + } + + if ((d = find_transport(type)) != NULL) { + if ((t = mrp_allocz(d->size)) != NULL) { + t->descr = d; + t->ml = ml; + t->evt = *evt; + t->user_data = user_data; + + t->check_destroy = check_destroy; + t->recv_data = recv_data; + t->flags = flags & ~MRP_TRANSPORT_MODE_MASK; + t->mode = flags & MRP_TRANSPORT_MODE_MASK; + + t->connected = !!(state & MRP_TRANSPORT_CONNECTED); + t->listened = !!(state & MRP_TRANSPORT_LISTENED); + + if (t->connected && t->listened) { + mrp_free(t); + return NULL; + } + + if (!t->descr->req.createfrom(t, conn)) { + mrp_free(t); + t = NULL; + } + } + } + else + t = NULL; + + return t; +} + + +int mrp_transport_setopt(mrp_transport_t *t, const char *opt, const void *val) +{ + if (t != NULL) { + if (t->descr->req.setopt != NULL) + return t->descr->req.setopt(t, opt, val); + else { + if (t->mode == MRP_TRANSPORT_MODE_NATIVE) { + if (!strcmp(opt, MRP_TRANSPORT_OPT_TYPEMAP)) { + t->map = (void *)val; + return TRUE; + } + } + } + } + + return FALSE; +} + + +static inline int type_matches(const char *type, const char *addr) +{ + while (*type == *addr) + type++, addr++; + + return (*type == '\0' && *addr == ':'); +} + + +socklen_t mrp_transport_resolve(mrp_transport_t *t, const char *str, + mrp_sockaddr_t *addr, socklen_t size, + const char **typep) +{ + mrp_transport_descr_t *d; + mrp_list_hook_t *p, *n; + socklen_t l; + + if (t != NULL) + return t->descr->resolve(str, addr, size, typep); + else { + mrp_list_foreach(&transports, p, n) { + d = mrp_list_entry(p, typeof(*d), hook); + l = d->resolve(str, addr, size, typep); + + if (l > 0) + return l; + } + } + + return 0; +} + + +int mrp_transport_bind(mrp_transport_t *t, mrp_sockaddr_t *addr, + socklen_t addrlen) +{ + if (t != NULL) { + if (t->descr->req.bind != NULL) + return t->descr->req.bind(t, addr, addrlen); + else + return TRUE; /* assume no binding is needed */ + } + else + return FALSE; +} + + +int mrp_transport_listen(mrp_transport_t *t, int backlog) +{ + int result; + + if (t != NULL) { + if (t->descr->req.listen != NULL) { + MRP_TRANSPORT_BUSY(t, { + result = t->descr->req.listen(t, backlog); + }); + + purge_destroyed(t); + + return result; + } + } + + return FALSE; +} + + +mrp_transport_t *mrp_transport_accept(mrp_transport_t *lt, + void *user_data, int flags) +{ + mrp_transport_t *t; + + if ((t = mrp_allocz(lt->descr->size)) != NULL) { + bool failed = FALSE; + t->descr = lt->descr; + t->ml = lt->ml; + t->evt = lt->evt; + t->user_data = user_data; + + t->check_destroy = check_destroy; + t->recv_data = recv_data; + t->flags = (lt->flags & MRP_TRANSPORT_INHERIT) | flags; + t->flags = t->flags & ~MRP_TRANSPORT_MODE_MASK; + t->mode = lt->mode; + t->map = lt->map; + + MRP_TRANSPORT_BUSY(t, { + if (!t->descr->req.accept(t, lt)) { + failed = TRUE; + } + else { + t->connected = TRUE; + } + }); + + if (failed) { + mrp_free(t); + t = NULL; + } + } + + return t; +} + + +static inline int purge_destroyed(mrp_transport_t *t) +{ + if (t->destroyed && !t->busy) { + mrp_debug("destroying transport %p...", t); + mrp_free(t); + return TRUE; + } + else + return FALSE; +} + + +void mrp_transport_destroy(mrp_transport_t *t) +{ + if (t != NULL) { + t->destroyed = TRUE; + + MRP_TRANSPORT_BUSY(t, { + t->descr->req.disconnect(t); + t->descr->req.close(t); + }); + + purge_destroyed(t); + } +} + + +static int check_destroy(mrp_transport_t *t) +{ + return purge_destroyed(t); +} + + +int mrp_transport_connect(mrp_transport_t *t, mrp_sockaddr_t *addr, + socklen_t addrlen) +{ + int result; + + if (!t->connected) { + + /* make sure we can deliver reception noifications */ + if (t->evt.recvmsg == NULL) { + errno = EINVAL; + return FALSE; + } + + MRP_TRANSPORT_BUSY(t, { + if (t->descr->req.connect(t, addr, addrlen)) { + t->connected = TRUE; + result = TRUE; + } + else + result = FALSE; + }); + + purge_destroyed(t); + } + else { + errno = EISCONN; + result = FALSE; + } + + return result; +} + + +int mrp_transport_disconnect(mrp_transport_t *t) +{ + int result; + + if (t != NULL && t->connected) { + MRP_TRANSPORT_BUSY(t, { + if (t->descr->req.disconnect(t)) { + t->connected = FALSE; + result = TRUE; + } + else + result = TRUE; + }); + + purge_destroyed(t); + } + else + result = FALSE; + + return result; +} + + +int mrp_transport_send(mrp_transport_t *t, mrp_msg_t *msg) +{ + int result; + + if (t->connected && t->descr->req.sendmsg) { + MRP_TRANSPORT_BUSY(t, { + result = t->descr->req.sendmsg(t, msg); + }); + + purge_destroyed(t); + } + else + result = FALSE; + + return result; +} + + +int mrp_transport_sendto(mrp_transport_t *t, mrp_msg_t *msg, + mrp_sockaddr_t *addr, socklen_t addrlen) +{ + int result; + + if (t->descr->req.sendmsgto) { + MRP_TRANSPORT_BUSY(t, { + result = t->descr->req.sendmsgto(t, msg, addr, addrlen); + }); + + purge_destroyed(t); + } + else + result = FALSE; + + return result; +} + + +int mrp_transport_sendraw(mrp_transport_t *t, void *data, size_t size) +{ + int result; + + if (t->connected && + t->mode == MRP_TRANSPORT_MODE_RAW && t->descr->req.sendraw) { + MRP_TRANSPORT_BUSY(t, { + result = t->descr->req.sendraw(t, data, size); + }); + + purge_destroyed(t); + } + else + result = FALSE; + + return result; +} + + +int mrp_transport_sendrawto(mrp_transport_t *t, void *data, size_t size, + mrp_sockaddr_t *addr, socklen_t addrlen) +{ + int result; + + if (t->mode == MRP_TRANSPORT_MODE_RAW && t->descr->req.sendrawto) { + MRP_TRANSPORT_BUSY(t, { + result = t->descr->req.sendrawto(t, data, size, addr, addrlen); + }); + + purge_destroyed(t); + } + else + result = FALSE; + + return result; +} + + +int mrp_transport_senddata(mrp_transport_t *t, void *data, uint16_t tag) +{ + int result; + + if (t->connected && + t->mode == MRP_TRANSPORT_MODE_DATA && t->descr->req.senddata) { + MRP_TRANSPORT_BUSY(t, { + result = t->descr->req.senddata(t, data, tag); + }); + + purge_destroyed(t); + } + else + result = FALSE; + + return result; +} + + +int mrp_transport_senddatato(mrp_transport_t *t, void *data, uint16_t tag, + mrp_sockaddr_t *addr, socklen_t addrlen) +{ + int result; + + if (t->mode == MRP_TRANSPORT_MODE_DATA && t->descr->req.senddatato) { + MRP_TRANSPORT_BUSY(t, { + result = t->descr->req.senddatato(t, data, tag, addr, addrlen); + }); + + purge_destroyed(t); + } + else + result = FALSE; + + return result; +} + + +int mrp_transport_sendcustom(mrp_transport_t *t, void *data) +{ + int result; + + if (t->mode == MRP_TRANSPORT_MODE_CUSTOM && t->descr->req.sendcustom) { + MRP_TRANSPORT_BUSY(t, { + result = t->descr->req.sendcustom(t, data); + }); + + purge_destroyed(t); + } + else + result = FALSE; + + return result; +} + + +int mrp_transport_sendcustomto(mrp_transport_t *t, void *data, + mrp_sockaddr_t *addr, socklen_t addrlen) +{ + int result; + + if (t->mode == MRP_TRANSPORT_MODE_CUSTOM && t->descr->req.sendcustomto) { + MRP_TRANSPORT_BUSY(t, { + result = t->descr->req.sendcustomto(t, data, addr, addrlen); + }); + + purge_destroyed(t); + } + else + result = FALSE; + + return result; +} + + +int mrp_transport_sendnative(mrp_transport_t *t, void *data, uint32_t type_id) +{ + int result; + + if (t->mode == MRP_TRANSPORT_MODE_NATIVE && t->descr->req.sendnative) { + MRP_TRANSPORT_BUSY(t, { + result = t->descr->req.sendnative(t, data, type_id); + }); + + purge_destroyed(t); + } + else + result = FALSE; + + return result; +} + + +int mrp_transport_sendnativeto(mrp_transport_t *t, void *data, uint32_t type_id, + mrp_sockaddr_t *addr, socklen_t addrlen) +{ + int result; + + if (t->mode == MRP_TRANSPORT_MODE_NATIVE && t->descr->req.sendnativeto) { + MRP_TRANSPORT_BUSY(t, { + result = t->descr->req.sendnativeto(t, data, type_id, + addr, addrlen); + }); + + purge_destroyed(t); + } + else + result = FALSE; + + return result; +} + + +int mrp_transport_sendjson(mrp_transport_t *t, mrp_json_t *msg) +{ + int result; + + if (t->mode == MRP_TRANSPORT_MODE_JSON && t->descr->req.sendjson) { + MRP_TRANSPORT_BUSY(t, { + result = t->descr->req.sendjson(t, msg); + }); + + purge_destroyed(t); + } + else + result = FALSE; + + return result; +} + + +int mrp_transport_sendjsonto(mrp_transport_t *t, mrp_json_t *msg, + mrp_sockaddr_t *addr, socklen_t addrlen) +{ + int result; + + if (t->mode == MRP_TRANSPORT_MODE_JSON && t->descr->req.sendjsonto) { + MRP_TRANSPORT_BUSY(t, { + result = t->descr->req.sendjsonto(t, msg, addr, addrlen); + }); + + purge_destroyed(t); + } + else + result = FALSE; + + return result; +} + + +static int recv_data(mrp_transport_t *t, void *data, size_t size, + mrp_sockaddr_t *addr, socklen_t addrlen) +{ + mrp_data_descr_t *type; + uint16_t tag; + mrp_msg_t *msg; + uint32_t type_id; + void *decoded; + + switch (t->mode) { + case MRP_TRANSPORT_MODE_DATA: + tag = be16toh(*(uint16_t *)data); + data += sizeof(tag); + size -= sizeof(tag); + type = mrp_msg_find_type(tag); + + if (type != NULL) { + decoded = mrp_data_decode(&data, &size, type); + + if (decoded != NULL && size == 0) { + if (t->connected && t->evt.recvdata) { + MRP_TRANSPORT_BUSY(t, { + t->evt.recvdata(t, decoded, tag, t->user_data); + }); + } + else if (t->evt.recvdatafrom) { + MRP_TRANSPORT_BUSY(t, { + t->evt.recvdatafrom(t, decoded, tag, addr, addrlen, + t->user_data); + }); + } + else + mrp_free(decoded); /* no callback, discard */ + + return 0; + } + else { + if (decoded != NULL) { + mrp_free(decoded); + return -EMSGSIZE; + } + else + return -errno; + } + } + else + return -ENOPROTOOPT; + break; + + case MRP_TRANSPORT_MODE_RAW: + if (t->connected) { + MRP_TRANSPORT_BUSY(t, { + t->evt.recvraw(t, data, size, t->user_data); + }); + } + else { + MRP_TRANSPORT_BUSY(t, { + t->evt.recvrawfrom(t, data, size, addr, addrlen, + t->user_data); + }); + } + return 0; + + case MRP_TRANSPORT_MODE_MSG: + tag = be16toh(*(uint16_t *)data); + data += sizeof(tag); + size -= sizeof(tag); + + if (tag != MRP_MSG_TAG_DEFAULT || + (msg = mrp_msg_default_decode(data, size)) == NULL) { + return -EPROTO; + } + else { + if (t->connected) { + MRP_TRANSPORT_BUSY(t, { + t->evt.recvmsg(t, msg, t->user_data); + }); + } + else { + MRP_TRANSPORT_BUSY(t, { + t->evt.recvmsgfrom(t, msg, addr, addrlen, + t->user_data); + }); + } + + mrp_msg_unref(msg); + + return 0; + } + break; + + case MRP_TRANSPORT_MODE_CUSTOM: + if (t->connected) { + if (t->evt.recvcustom) { + MRP_TRANSPORT_BUSY(t, { + t->evt.recvcustom(t, data, t->user_data); + }); + + return 0; + } + } + else { + if (t->evt.recvcustomfrom) { + MRP_TRANSPORT_BUSY(t, { + t->evt.recvcustomfrom(t, data, addr, addrlen, + t->user_data); + }); + + return 0; + } + } + return -EPROTOTYPE; + + case MRP_TRANSPORT_MODE_NATIVE: + type_id = 0; + if (mrp_decode_native(&data, &size, &decoded, &type_id, t->map) < 0) + return -EPROTO; + + if (decoded == NULL || size != 0) { + mrp_free_native(decoded, type_id); + return -EPROTO; + } + + if (t->connected) { + MRP_TRANSPORT_BUSY(t, { + t->evt.recvnative(t, decoded, type_id, t->user_data); + }); + } + else { + MRP_TRANSPORT_BUSY(t, { + t->evt.recvnativefrom(t, decoded, type_id, addr, addrlen, + t->user_data); + }); + } + return 0; + + case MRP_TRANSPORT_MODE_JSON: + if (t->connected) { + if (t->evt.recvjson) { + MRP_TRANSPORT_BUSY(t, { + t->evt.recvjson(t, data, t->user_data); + }); + } + } + else { + if (t->evt.recvjsonfrom) { + MRP_TRANSPORT_BUSY(t, { + t->evt.recvjsonfrom(t, data, addr, addrlen, + t->user_data); + }); + } + } + return 0; + + default: + return -EPROTOTYPE; + } +} + diff --git a/src/common/transport.h b/src/common/transport.h new file mode 100644 index 0000000..7b57a2c --- /dev/null +++ b/src/common/transport.h @@ -0,0 +1,559 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_TRANSPORT_H__ +#define __MURPHY_TRANSPORT_H__ + +#include <sys/socket.h> +#include <netinet/in.h> +#include <sys/un.h> + +#include <murphy/common/macros.h> +#include <murphy/common/list.h> +#include <murphy/common/mainloop.h> +#include <murphy/common/msg.h> +#include <murphy/common/native-types.h> + +/* + * json-c and JSON-Glib have a symbol clash on json_object_get_type. + * Unfortunately we'd really need to include our own json.h here to + * get mrp_json_t defined. That however pulls in json-c's headers as + * our implementation uses json-c and the type itself is just a + * typedef'd alias to json_object. + * + * Now if some unfortunate sould ends up directly or indirectly + * including both our transport.h, and consequently json.h, and + * JSON-Glib, we'll trigger the symbol clash. + * + * As a workaround if we detect that JSON-Glib has already been + * included we'll compile with alternative signatures (void *, + * instead of mrp_json_t *) and omit including json.h. Also we + * let people give us a warning by defining __JSON_GLIB_DANGER__ + * that they will or might include JSON-Glib, in which case + * we also compile with the alternative signatures. Oh boy... + */ + +#if !defined(__JSON_TYPES_H__) && !defined(__JSON_GLIB_DANGER__) +# include <murphy/common/json.h> +#else +# define mrp_json_t void +#endif + +MRP_CDECL_BEGIN + +typedef struct mrp_transport_s mrp_transport_t; + + + +/* + * transport socket address + */ + +#define MRP_SOCKADDR_SIZE 256 + +typedef union { + struct sockaddr any; + struct sockaddr_in ipv4; + struct sockaddr_in6 ipv6; + struct sockaddr_un unx; + char data[MRP_SOCKADDR_SIZE]; +} mrp_sockaddr_t; + + +static inline mrp_sockaddr_t *mrp_sockaddr_cpy(mrp_sockaddr_t *d, + mrp_sockaddr_t *s, socklen_t n) +{ + memcpy(d, s, n); + return d; +} + + +/* + * various transport flags + */ + +typedef enum { + MRP_TRANSPORT_MODE_MSG = 0x00, /* generic message encoding */ + MRP_TRANSPORT_MODE_RAW = 0x01, /* uses bitpipe mode */ + MRP_TRANSPORT_MODE_DATA = 0x02, /* uses registered data types */ + MRP_TRANSPORT_MODE_CUSTOM = 0x03, /* custom message encoding */ + MRP_TRANSPORT_MODE_NATIVE = 0x04, /* uses registered native-types */ + MRP_TRANSPORT_MODE_JSON = 0x05, /* uses JSON messages */ +} mrp_transport_mode_t; + +typedef enum { + MRP_TRANSPORT_MODE_MASK = 0x0f, /* mask of mode bits */ + MRP_TRANSPORT_INHERIT = 0x0f, /* mask of all inherited flags */ + + MRP_TRANSPORT_REUSEADDR = 0x010, + MRP_TRANSPORT_NONBLOCK = 0x020, + MRP_TRANSPORT_CLOEXEC = 0x040, + MRP_TRANSPORT_CONNECTED = 0x080, + MRP_TRANSPORT_LISTENED = 0x001, +} mrp_transport_flag_t; + +#define MRP_TRANSPORT_MODE(t) ((t)->flags & MRP_TRANSPORT_MODE_MASK) + + +#define MRP_TRANSPORT_OPT_TYPEMAP "type-map" + +/* + * transport requests + * + * Transport requests correspond to top-down event propagation in the + * communication stack. These requests are made by the core tansport + * abstraction layer to the underlying actual transport implementation + * to carry out the implementation-specific details of some transport + * operation. + */ + +typedef struct { + /** Open a new transport. */ + int (*open)(mrp_transport_t *t); + /** Create a new transport from an existing backend object. */ + int (*createfrom)(mrp_transport_t *t, void *obj); + /** Bind a transport to a given transport-specific address. */ + int (*bind)(mrp_transport_t *t, mrp_sockaddr_t *addr, socklen_t addrlen); + /** Listen on a transport for incoming connections. */ + int (*listen)(mrp_transport_t *t, int backlog); + /** Accept a new transport connection over an existing transport. */ + int (*accept)(mrp_transport_t *t, mrp_transport_t *lt); + /** Connect a transport to an endpoint. */ + int (*connect)(mrp_transport_t *t, mrp_sockaddr_t *addr, + socklen_t addrlen); + /** Disconnect a transport, if it is connection-oriented. */ + int (*disconnect)(mrp_transport_t *t); + /** Close a transport, free all resources from open/accept/connect. */ + void (*close)(mrp_transport_t *t); + /** Set a (possibly type specific) transport option. */ + int (*setopt)(mrp_transport_t *t, const char *opt, const void *value); + /** Send a message over a (connected) transport. */ + int (*sendmsg)(mrp_transport_t *t, mrp_msg_t *msg); + /** Send raw data over a (connected) transport. */ + int (*sendraw)(mrp_transport_t *t, void *buf, size_t size); + /** Send registered data over a (connected) transport. */ + int (*senddata)(mrp_transport_t *t, void *data, uint16_t tag); + /** Send data with a custom encoder over a transport. */ + int (*sendcustom)(mrp_transport_t *t, void *data); + /** Send a native type over a (connected) transport. */ + int (*sendnative)(mrp_transport_t *t, void *data, uint32_t type_id); + /** Send a JSON message over a (connected) transport. */ + int (*sendjson)(mrp_transport_t *t, mrp_json_t *msg); + + /** Send a message over a(n unconnected) transport. */ + int (*sendmsgto)(mrp_transport_t *t, mrp_msg_t *msg, mrp_sockaddr_t *addr, + socklen_t addrlen); + /** Send raw data over a(n unconnected) transport. */ + int (*sendrawto)(mrp_transport_t *t, void *buf, size_t size, + mrp_sockaddr_t *addr, socklen_t addrlen); + /** Send registered data over a(n unconnected) transport. */ + int (*senddatato)(mrp_transport_t *t, void *data, uint16_t tag, + mrp_sockaddr_t *addr, socklen_t addrlen); + /** Send data with a custom encoder over a transport. */ + int (*sendcustomto)(mrp_transport_t *t, void *data, + mrp_sockaddr_t *addr, socklen_t addrlen); + /** Send a native type over a transport. */ + int (*sendnativeto)(mrp_transport_t *t, void *data, uint32_t type_id, + mrp_sockaddr_t *addr, socklen_t addrlen); + /** Send a JSON messgae over a(n unconnected) transport. */ + int (*sendjsonto)(mrp_transport_t *t, mrp_json_t *msg, mrp_sockaddr_t *addr, + socklen_t addrlen); +} mrp_transport_req_t; + + +/* + * transport events + * + * Transport events correspond to bottom-up event propagation in the + * communication stack. These callbacks are made by the actual transport + * implementation to the generic transport abstraction to inform it + * about relevant transport events, such as the reception of data, or + * transport disconnection by the peer. + */ + +typedef struct { + /** Message received on a connected transport. */ + union { + /** Generic message callback for connected transports. */ + void (*recvmsg)(mrp_transport_t *t, mrp_msg_t *msg, void *user_data); + /** Raw data callback for connected transports. */ + void (*recvraw)(mrp_transport_t *t, void *data, size_t size, + void *user_data); + /** Registered data callback for connected transports. */ + void (*recvdata)(mrp_transport_t *t, void *data, uint16_t tag, + void *user_data); + /** Custom encoded data callback for connected transports. */ + void (*recvcustom)(mrp_transport_t *t, void *data, + void *user_data); + /** Native type callback for connected transports. */ + void (*recvnative)(mrp_transport_t *t, void *data, uint32_t type_id, + void *user_data); + /** JSON type callback for connected transports. */ + void (*recvjson)(mrp_transport_t *t, mrp_json_t *msg, void *user_data); + }; + + /** Message received on an unconnected transport. */ + union { + /** Generic message callback for unconnected transports. */ + void (*recvmsgfrom)(mrp_transport_t *t, mrp_msg_t *msg, + mrp_sockaddr_t *addr, socklen_t addrlen, + void *user_data); + /** Raw data callback for unconnected transports. */ + void (*recvrawfrom)(mrp_transport_t *t, void *data, size_t size, + mrp_sockaddr_t *addr, socklen_t addrlen, + void *user_data); + /** Registered data callback for unconnected transports. */ + void (*recvdatafrom)(mrp_transport_t *t, void *data, uint16_t tag, + mrp_sockaddr_t *addr, socklen_t addrlen, + void *user_data); + /** Custom encoded data callback for unconnected transports. */ + void (*recvcustomfrom)(mrp_transport_t *t, void *data, + mrp_sockaddr_t *addr, socklen_t addrlen, + void *user_data); + /** Native type callback for unconnected transports. */ + void (*recvnativefrom)(mrp_transport_t *t, void *data, uint32_t type_id, + mrp_sockaddr_t *addr, socklen_t addrlen, + void *user_data); + /** JSON type callback for unconnected transports. */ + void (*recvjsonfrom)(mrp_transport_t *t, mrp_json_t *msg, + mrp_sockaddr_t *addr, socklen_t addrlen, + void *user_data); + }; + /** Connection closed by peer. */ + void (*closed)(mrp_transport_t *t, int error, void *user_data); + /** Connection attempt on a socket being listened on. */ + void (*connection)(mrp_transport_t *t, void *user_data); +} mrp_transport_evt_t; + + +/* + * transport descriptor + */ + +typedef struct { + const char *type; /* transport type name */ + size_t size; /* full transport struct size */ + mrp_transport_req_t req; /* transport requests */ + socklen_t (*resolve)(const char *str, mrp_sockaddr_t *addr, + socklen_t addrlen, const char **typep); + mrp_list_hook_t hook; /* to list of registered transports */ +} mrp_transport_descr_t; + + +/* + * transport + */ + +#define MRP_TRANSPORT_PUBLIC_FIELDS \ + mrp_mainloop_t *ml; \ + mrp_transport_descr_t *descr; \ + mrp_transport_evt_t evt; \ + int (*check_destroy)(mrp_transport_t *t); \ + int (*recv_data)(mrp_transport_t *t, void *data, \ + size_t size, \ + mrp_sockaddr_t *addr, \ + socklen_t addrlen); \ + void *user_data; \ + mrp_typemap_t *map; \ + int flags; \ + int mode; \ + int busy; \ + int connected : 1; \ + int listened : 1; \ + int destroyed : 1 \ + + +struct mrp_transport_s { + MRP_TRANSPORT_PUBLIC_FIELDS; +}; + + +/* + * Notes: + * + * Transports can get destructed in two slightly different ways. + * + * 1) + * Someone calls mrp_transport_destroy while the transport is + * idle, ie. with no callbacks or operations being active. This + * is simple and straightforward: + * - mrp_transport_destroy calls req.disconnect + * - mrp_transport_destroy calls req.close + * - mrp_transport_destroy check and sees the transport is idle + * so it frees the transport + * + * 2) + * Someone calls mrp_tansport_destroy while the transport is + * busy, ie. it has an unfinished callback or operation running. + * This typically happens when an operation or callback function, + * or a user function called from either of those calls + * mrp_transport_destroy as a result of a received message, or a + * (communication) error. In this case destroying the transport + * is less straightforward and needs to get delayed to avoid + * shooting out the transport underneath the active operation or + * callback. + * + * To handle the latter case, the generic (ie. top-level) transport + * layer has a member function check_destroy. This function checks + * for pending destroy requests and destroys the transport if it + * is not busy. All transport backends MUST CALL this function and + * CHECK ITS RETURN VALUE, whenever a user callback or a transport + * callback (ie. bottom-up event propagation) function invoked by + * the backend returns. + * + * If the transport has been left intact, check_destroy returns + * FALSE and processing can continue normally, taking into account + * that any transport state stored locally in the stack frame of the + * backend function might have changed during the callback. However, + * if check_destroy returns TRUE, it has nuked the transport and the + * backend MUST NOT touch or try to dereference the transport any more + * as its resources have already been released. + */ + + +/* + * convenience macros + */ + +/** + * Macro to mark a transport busy while running a block of code. + * + * The backend needs to make sure the transport is not freed while a + * transport request or event callback function is active. Similarly, + * the backend needs to check if the transport has been marked for + * destruction whenever an event callback returns and trigger the + * destruction if it is necessary and possible (ie. the above criterium + * of not being active is fullfilled). + * + * These are the easiest to accomplish using the provided MRP_TRANSPORT_BUSY + * macro and the check_destroy callback member provided by mrp_transport_t. + * + * 1) Use the provided MRP_TRANSPORT_BUSY macro to enclose al blocks of + * code that invoke event callbacks. Do not do a return directly + * from within the enclosed call blocks, rather just set a flag + * within the block, check it after the block and do the return + * from there if necessary. + * + * 2) Call mrp_transport_t->check_destroy after any call to an event + * callback. check_destroy will check for any pending destroy + * request and perform the actual destruction if it is necessary + * and possible. If the transport has been left intact, check_destroy + * returns FALSE. However, if the transport has been destroyed and + * freed it returns TRUE, in which case the caller must not attempt + * to use or dereference the transport data structures any more. + */ + + +#ifndef __MRP_TRANSPORT_DISABLE_CODE_CHECK__ +# define W mrp_log_error +# define __TRANSPORT_CHK_BLOCK(...) do { \ + static int __checked = FALSE, __warned = FALSE; \ + \ + if (MRP_UNLIKELY(!__checked)) { \ + __checked = TRUE; \ + if (MRP_UNLIKELY(!__warned && \ + strstr(#__VA_ARGS__, "return") != NULL)) { \ + W("*********************** WARNING ********************"); \ + W("* You seem to directly do a return from a block of *"); \ + W("* code protected by MRP_TRANSPORT_BUSY. Are you *"); \ + W("* absolutely sure you know what you are doing and *"); \ + W("* that you are also doing it correctly ? *"); \ + W("****************************************************"); \ + W("The suspicious code block is located at: "); \ + W(" %s@%s:%d", __FUNCTION__, __FILE__, __LINE__); \ + W("and it looks like this:"); \ + W("---------------------------------------------"); \ + W("%s", #__VA_ARGS__); \ + W("---------------------------------------------"); \ + W("If you understand what MRP_TRANSPORT_BUSY does and"); \ + W("how, and you are sure about the corretness of your"); \ + W("code you can disable this error message by"); \ + W("#defining __MRP_TRANSPORT_DISABLE_CODE_CHECK__"); \ + W("when compiling %s.", __FILE__); \ + __warned = TRUE; \ + } \ + } \ + } while (0) +#else +# define __TRANSPORT_CHK_BLOCK(...) do { } while (0) +#endif + +#define MRP_TRANSPORT_BUSY(t, ...) do { \ + __TRANSPORT_CHK_BLOCK(__VA_ARGS__); \ + (t)->busy++; \ + __VA_ARGS__ \ + (t)->busy--; \ + } while (0) + + + +/** Automatically register a transport on startup. */ +#define MRP_REGISTER_TRANSPORT(_prfx, _typename, _structtype, _resolve, \ + _open, _createfrom, _close, _setopt, \ + _bind, _listen, _accept, \ + _connect, _disconnect, \ + _sendmsg, _sendmsgto, \ + _sendraw, _sendrawto, \ + _senddata, _senddatato, \ + _sendcustom, _sendcustomto, \ + _sendnative, _sendnativeto, \ + _sendjson, _sendjsonto) \ + static void _prfx##_register_transport(void) \ + __attribute__((constructor)); \ + \ + static void _prfx##_register_transport(void) { \ + static mrp_transport_descr_t descriptor = { \ + .type = _typename, \ + .size = sizeof(_structtype), \ + .resolve = _resolve, \ + .req = { \ + .open = _open, \ + .createfrom = _createfrom, \ + .bind = _bind, \ + .listen = _listen, \ + .accept = _accept, \ + .close = _close, \ + .setopt = _setopt, \ + .connect = _connect, \ + .disconnect = _disconnect, \ + .sendmsg = _sendmsg, \ + .sendmsgto = _sendmsgto, \ + .sendraw = _sendraw, \ + .sendrawto = _sendrawto, \ + .senddata = _senddata, \ + .senddatato = _senddatato, \ + .sendcustom = _sendcustom, \ + .sendcustomto = _sendcustomto, \ + .sendnative = _sendnative, \ + .sendnativeto = _sendnativeto, \ + .sendjson = _sendjson, \ + .sendjsonto = _sendjsonto, \ + }, \ + }; \ + \ + if (!mrp_transport_register(&descriptor)) \ + mrp_log_error("Failed to register transport '%s'.", \ + _typename); \ + else \ + mrp_log_info("Registered transport '%s'.", _typename); \ + } \ + struct mrp_allow_trailing_semicolon + + + +/** Register a new transport type. */ +int mrp_transport_register(mrp_transport_descr_t *d); + +/** Unregister a transport. */ +void mrp_transport_unregister(mrp_transport_descr_t *d); + +/** Create a new transport. */ +mrp_transport_t *mrp_transport_create(mrp_mainloop_t *ml, const char *type, + mrp_transport_evt_t *evt, + void *user_data, int flags); + +/** Create a new transport from a backend object. */ +mrp_transport_t *mrp_transport_create_from(mrp_mainloop_t *ml, const char *type, + void *conn, mrp_transport_evt_t *evt, + void *user_data, int flags, + int state); + +/** Set a (possibly type-specific) transport option. */ +int mrp_transport_setopt(mrp_transport_t *t, const char *opt, const void *val); + +/** Resolve an address string to a transport-specific address. */ +socklen_t mrp_transport_resolve(mrp_transport_t *t, const char *str, + mrp_sockaddr_t *addr, socklen_t addrlen, + const char **type); + +/** Bind a given transport to a transport-specific address. */ +int mrp_transport_bind(mrp_transport_t *t, mrp_sockaddr_t *addr, + socklen_t addrlen); + +/** Listen for incoming connection on the given transport. */ +int mrp_transport_listen(mrp_transport_t *t, int backlog); + +/** Accept and create a new transport connection. */ +mrp_transport_t *mrp_transport_accept(mrp_transport_t *t, + void *user_data, int flags); + +/** Destroy a transport. */ +void mrp_transport_destroy(mrp_transport_t *t); + +/** Connect a transport to the given address. */ +int mrp_transport_connect(mrp_transport_t *t, mrp_sockaddr_t *addr, + socklen_t addrlen); + +/** Disconnect a transport. */ +int mrp_transport_disconnect(mrp_transport_t *t); + +/** Send a message through the given (connected) transport. */ +int mrp_transport_send(mrp_transport_t *t, mrp_msg_t *msg); + +/** Send a message through the given transport to the remote address. */ +int mrp_transport_sendto(mrp_transport_t *t, mrp_msg_t *msg, + mrp_sockaddr_t *addr, socklen_t addrlen); + +/** Send raw data through the given (connected) transport. */ +int mrp_transport_sendraw(mrp_transport_t *t, void *data, size_t size); + +/** Send raw data through the given transport to the remote address. */ +int mrp_transport_sendrawto(mrp_transport_t *t, void *data, size_t size, + mrp_sockaddr_t *addr, socklen_t addrlen); + +/** Send registered data through the given (connected) transport. */ +int mrp_transport_senddata(mrp_transport_t *t, void *data, uint16_t tag); + +/** Send registered data through the given transport to the remote address. */ +int mrp_transport_senddatato(mrp_transport_t *t, void *data, uint16_t tag, + mrp_sockaddr_t *addr, socklen_t addrlen); + +/** Send custom data through the given (connected) transport. */ +int mrp_transport_sendcustom(mrp_transport_t *t, void *data); + +/** Send registered data through the given transport to the remote address. */ +int mrp_transport_sendcustomto(mrp_transport_t *t, void *data, + mrp_sockaddr_t *addr, socklen_t addrlen); + +/** Send a native type through the given (connected) transport. */ +int mrp_transport_sendnative(mrp_transport_t *t, void *data, uint32_t type_id); + +/** Send a native type through the given transport to the remote address. */ +int mrp_transport_sendnativeto(mrp_transport_t *t, void *data, uint32_t type_id, + mrp_sockaddr_t *addr, socklen_t addrlen); + +/** Send a JSON message through the given (connected) transport. */ +int mrp_transport_sendjson(mrp_transport_t *t, mrp_json_t *msg); + +/** Send a JSON message through the given transport to the remote address. */ +int mrp_transport_sendjsonto(mrp_transport_t *t, mrp_json_t *msg, + mrp_sockaddr_t *addr, socklen_t addrlen); +MRP_CDECL_END + +#endif /* __MURPHY_TRANSPORT_H__ */ diff --git a/src/common/utils.c b/src/common/utils.c new file mode 100644 index 0000000..8fd1f76 --- /dev/null +++ b/src/common/utils.c @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <stdarg.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <murphy/common/log.h> +#include <murphy/common/utils.h> + +#define MSG_OK "OK" + +static int notify_parent(int fd, const char *fmt, ...) +{ + va_list ap; + int len; + + va_start(ap, fmt); + len = vdprintf(fd, fmt, ap); + va_end(ap); + + return (len > 0); +} + + +int mrp_daemonize(const char *dir, const char *new_out, const char *new_err) +{ + pid_t pid; + int in, out, err; + char msg[1024]; + int chnl[2], len; + + /* + * create a pipe for communicating back the child status + */ + + if (pipe(chnl) == -1) { + mrp_log_error("Failed to create pipe to get child status (%d: %s).", + errno, strerror(errno)); + return FALSE; + } + + + /* + * fork, change to our new working directory and create a new session + */ + + switch ((pid = fork())) { + case -1: /* failed */ + mrp_log_error("Could not daemonize, fork failed (%d: %s).", + errno, strerror(errno)); + return FALSE; + + case 0: /* child */ + close(chnl[0]); + break; + + default: /* parent */ + close(chnl[1]); + + /* + * wait for and check the status report from the child + */ + + len = read(chnl[0], msg, sizeof(msg) - 1); + + if (len > 0) { + msg[len] = '\0'; + + if (!strcmp(msg, MSG_OK)) { + mrp_log_info("Successfully daemonized."); + exit(0); + } + else + mrp_log_error("Daemonizing failed after fork: %s.", msg); + } + else + mrp_log_error("Daemonizing failed in forked child."); + + return FALSE; + } + + + if (chdir(dir) != 0) { + mrp_log_error("Could not daemonize, failed to chdir to %s (%d: %s).", + dir, errno, strerror(errno)); + return FALSE; + } + + if (setsid() < 0) { + notify_parent(chnl[1], "Failed to create new session (%d: %s).", + errno, strerror(errno)); + exit(1); + } + + + /* + * fork again and redirect our stdin, stdout, and stderr + */ + + switch ((pid = fork())) { + case -1: /* failed */ + notify_parent(chnl[1], "Could not daemonize, fork failed (%d: %s).", + errno, strerror(errno)); + exit(1); + + case 0: /* child */ + break; + + default: /* parent */ + close(chnl[1]); + exit(0); + } + + + if ((in = open("/dev/null", O_RDONLY)) < 0) { + notify_parent(chnl[1], "Failed to open /dev/null (%d: %s).", errno, + strerror(errno)); + exit(1); + } + + if ((out = open(new_out, O_WRONLY)) < 0) { + notify_parent(chnl[1], "Failed to open %s (%d: %s).", new_out, errno, + strerror(errno)); + exit(1); + } + + if ((err = open(new_err, O_WRONLY)) < 0) { + notify_parent(chnl[1], "Failed to open %s (%d: %s).", new_err, errno, + strerror(errno)); + exit(1); + } + + if (dup2(in, fileno(stdin)) < 0) { + notify_parent(chnl[1], "Failed to redirect stdin (%d: %s).", errno, + strerror(errno)); + exit(1); + } + + if (dup2(out, fileno(stdout)) < 0) { + notify_parent(chnl[1], "Failed to redirect stdout (%d: %s).", errno, + strerror(errno)); + exit(1); + } + + if (dup2(err, fileno(stderr)) < 0) { + notify_parent(chnl[1], "Failed to redirect stderr (%d: %s).", errno, + strerror(errno)); + exit(1); + } + + close(in); + close(out); + close(err); + + notify_parent(chnl[1], "%s", MSG_OK); + + return TRUE; +} + + +int mrp_string_comp(const void *key1, const void *key2) +{ + return strcmp(key1, key2); +} + + +uint32_t mrp_string_hash(const void *key) +{ + uint32_t h; + const char *p; + + for (h = 0, p = key; *p; p++) { + h <<= 1; + h ^= *p; + } + + return h; +} diff --git a/src/common/utils.h b/src/common/utils.h new file mode 100644 index 0000000..fef28a8 --- /dev/null +++ b/src/common/utils.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_UTILS_H__ +#define __MURPHY_UTILS_H__ + +#include <stdint.h> + +int mrp_daemonize(const char *dir, const char *new_out, const char *new_err); + +int mrp_string_comp(const void *key1, const void *key2); +uint32_t mrp_string_hash(const void *key); + +#endif /* __MURPHY_UTILS_H__ */ diff --git a/src/common/websocket.c b/src/common/websocket.c new file mode 100644 index 0000000..25d3dc8 --- /dev/null +++ b/src/common/websocket.c @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <murphy/common/macros.h> +#include <murphy/common/websocket.h> + + +void mrp_websock_set_loglevel(mrp_websock_loglevel_t mask) +{ + wsl_set_loglevel(mask); +} + + +mrp_websock_context_t *mrp_websock_create_context(mrp_mainloop_t *ml, + mrp_websock_config_t *cfg) +{ + return wsl_create_context(ml, cfg); +} + + +mrp_websock_context_t *mrp_websock_ref_context(mrp_websock_context_t *ctx) +{ + return wsl_ref_context(ctx); +} + + +int mrp_websock_unref_context(mrp_websock_context_t *ctx) +{ + return wsl_unref_context(ctx); +} + + +mrp_websock_t *mrp_websock_connect(mrp_websock_context_t *ctx, + struct sockaddr *sa, const char *protocol, + mrp_wsl_ssl_t ssl, void *user_data) +{ + return wsl_connect(ctx, sa, protocol, ssl, user_data); +} + + +mrp_websock_t *mrp_websock_accept_pending(mrp_websock_context_t *ctx, + void *user_data) +{ + return wsl_accept_pending(ctx, user_data); +} + + +void mrp_websock_reject_pending(mrp_websock_context_t *ctx) +{ + wsl_reject_pending(ctx); +} + + +void *mrp_websock_close(mrp_websock_t *sck) +{ + return wsl_close(sck); +} + + +int mrp_websock_send(mrp_websock_t *sck, void *payload, size_t size) +{ + return wsl_send(sck, payload, size); +} + + +int mrp_websock_server_http_file(mrp_websock_t *sck, const char *path, + const char *mime) +{ + return wsl_serve_http_file(sck, path, mime); +} diff --git a/src/common/websocket.h b/src/common/websocket.h new file mode 100644 index 0000000..cd380f2 --- /dev/null +++ b/src/common/websocket.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_WEBSOCKET_H__ +#define __MURPHY_WEBSOCKET_H__ + +#include <murphy/common/macros.h> +#include <murphy/common/websocklib.h> + +MRP_CDECL_BEGIN + +/* + * websocket types (mapped) + */ + +typedef wsl_ctx_cfg_t mrp_websock_config_t; +typedef wsl_ctx_t mrp_websock_context_t; +typedef wsl_sck_t mrp_websock_t; +typedef wsl_callbacks_t mrp_websock_evt_t; +typedef wsl_proto_t mrp_websock_proto_t; +typedef wsl_ssl_t mrp_wsl_ssl_t; + +/* + * websocket log levels (mapped) + */ + +typedef enum { +#define MAP(mrp, wsl) MRP_WEBSOCK_LOG_##mrp = WSL_LOG_##wsl + MAP(NONE , NONE), + MAP(ERROR , ERROR), + MAP(WARNING, WARNING), + MAP(INFO , INFO), + MAP(DEBUG , DEBUG), + MAP(ALL , ALL), + MAP(PARSER , PARSER), + MAP(EXT , EXT), + MAP(CLIENT , CLIENT), + MAP(EXTRA , EXTRA), + MAP(VERBOSE, VERBOSE) +#undef MAP +} mrp_websock_loglevel_t; + + + +/* + * websocket function prototypes + */ + +/** Set websocket logging level. */ +void mrp_websock_set_loglevel(mrp_websock_loglevel_t mask); + +/** Create a websocket context. */ +mrp_websock_context_t *mrp_websock_create_context(mrp_mainloop_t *ml, + mrp_websock_config_t *cfg); + +/** Add a reference to a websocket context. */ +mrp_websock_context_t *mrp_websock_ref_context(mrp_websock_context_t *ctx); + +/** Remove a context reference. */ +int mrp_websock_unref_context(mrp_websock_context_t *ctx); + +/** Create and connect a websocket to a given address. */ +mrp_websock_t *mrp_websock_connect(mrp_websock_context_t *ctx, + struct sockaddr *sa, const char *protocol, + mrp_wsl_ssl_t ssl, void *user_data); + +/** Accept a pending connection of a context. */ +mrp_websock_t *mrp_websock_accept_pending(mrp_websock_context_t *ctx, + void *user_data); + +/** Reject a pending connection of a context. */ +void mrp_websock_reject_pending(mrp_websock_context_t *ctx); + +/** Close a websocket. Return the user_data of it's associated context. */ +void *mrp_websock_close(mrp_websock_t *sck); + +/** Send data over a connected websocket. */ +int mrp_websock_send(mrp_websock_t *sck, void *payload, size_t size); + +/** Serve the given file, with MIME type, over the given websocket. */ +int mrp_websock_server_http_file(mrp_websock_t *sck, const char *path, + const char *mime); + +MRP_CDECL_END + + +#endif /* __MURPHY_WEBSOCKET_H__ */ diff --git a/src/common/websocklib.c b/src/common/websocklib.c new file mode 100644 index 0000000..0595c46 --- /dev/null +++ b/src/common/websocklib.c @@ -0,0 +1,2105 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <net/if.h> +#include <arpa/inet.h> + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/log.h> +#include <murphy/common/refcnt.h> +#include <murphy/common/mainloop.h> +#include <murphy/common/fragbuf.h> +#include <murphy/common/hashtbl.h> + +#include "websocklib.h" + +#define LWS_EVENT_OK 0 /* event handler result: ok */ +#define LWS_EVENT_DENY 1 /* event handler result: deny */ +#define LWS_EVENT_ERROR 1 /* event handler result: error */ +#define LWS_EVENT_CLOSE -1 /* event handler result: close */ + +/* libwebsocket status used to close sockets upon error */ +#ifndef WEBSOCKETS_OLD +# define LWS_INTERNAL_ERROR LWS_CLOSE_STATUS_UNEXPECTED_CONDITION +#else +# define LWS_INTERNAL_ERROR LWS_CLOSE_STATUS_PROTOCOL_ERR /* arrghmm... */ +#endif + +/* SSL modes */ +#define LWS_NO_SSL 0 /* no SSL at all */ +#define LWS_SSL 1 /* SSL, deny self-signed certs */ +#define LWS_SSL_SELFSIGNED 2 /* SSL, allow self-signed certs */ + +/* + * define shorter aliasen for libwebsocket types + */ + +typedef struct libwebsocket lws_t; +typedef struct libwebsocket_context lws_ctx_t; +typedef struct libwebsocket_extension lws_ext_t; +typedef struct libwebsocket_protocols lws_proto_t; +typedef enum libwebsocket_callback_reasons lws_event_t; +#ifdef WEBSOCKETS_CONTEXT_INFO + typedef struct lws_context_creation_info lws_cci_t; +#else /* !WEBSOCKETS_CONTEXT_INFO */ +typedef struct { + int port; + const char *iface; + struct libwebsocket_protocols *protocols; + struct libwebsocket_extension *extensions; + const char *ssl_cert_filepath; + const char *ssl_private_key_filepath; + const char *ssl_ca_filepath; + const char *ssl_cipher_list; + int gid; + int uid; + unsigned int options; + void *user; + int ka_time; + int ka_probes; + int ka_interval; +} lws_cci_t; +#endif /* !WEBSOCKETS_CONTEXT_INFO */ + +static lws_ext_t *lws_get_internal_extensions(void); + +/* + * a libwebsocket fd we (e)poll + * + * Unfortunately the mechanism offered by libwebsockets for external + * mainloop integration uses event mask diffs when asking the mainloop + * to modify what an fd is polled for. This forces us to do double + * bookkeeping: we need to to keep track of the current event mask for + * all descriptors just to figure out the new mask when libwebsockets + * hands us a diff. + */ + +typedef struct { + int fd; /* libwebsocket file descriptor */ + uint32_t events; /* monitored (epoll) events */ +} pollfd_t; + + +typedef enum { + POLLFD_SET = 0, + POLLFD_CLEAR = TRUE, + POLLFD_CHANGE, +} pollfd_op_t; + +/* + * a websocket context + */ + +struct wsl_ctx_s { + lws_ctx_t *ctx; /* libwebsocket context */ + wsl_proto_t *http; /* has HTTP as upper layer protocol */ + wsl_proto_t *protos; /* protocols */ + int nproto; /* number of protocols */ + lws_proto_t *lws_protos; /* libwebsocket protocols */ + mrp_refcnt_t refcnt; /* reference count */ + int epollfd; /* epoll descriptor */ + mrp_io_watch_t *w; /* I/O watch for epollfd */ + mrp_mainloop_t *ml; /* pumping mainloop */ + pollfd_t *fds; /* polled descriptors */ + int nfd; /* number descriptors */ + void *user_data; /* opaque user data */ + lws_t *pending; /* pending connection */ + void *pending_user; /* user_data of pending */ + wsl_proto_t *pending_proto; /* protocol of pending */ + mrp_list_hook_t pure_http; /* pure HTTP sockets */ +}; + +/* + * a websocket instance + */ + +struct wsl_sck_s { + wsl_ctx_t *ctx; /* associated context */ + lws_t *sck; /* libwebsocket instance */ + wsl_proto_t *proto; /* protocol data */ + wsl_sendmode_t send_mode; /* libwebsocket write mode */ + mrp_fragbuf_t *buf; /* fragment collection buffer */ + void *user_data; /* opaque user data */ + wsl_sck_t **sckptr; /* back pointer from sck to us */ + int closing : 1; /* close in progress */ + int pure_http : 1; /* pure HTTP socket */ + int busy; /* upper-layer callback(s) active */ + mrp_list_hook_t hook; /* to pure HTTP list, if such */ +}; + + +/* + * mark a socket busy while executing a piece of code + */ + +#define SOCKET_BUSY_REGION(sck, ...) do { \ + (sck)->busy++; \ + __VA_ARGS__; \ + (sck)->busy--; \ + } while (0) + + + +static int http_event(lws_ctx_t *ws_ctx, lws_t *ws, lws_event_t event, + void *user, void *in, size_t len); +static int wsl_event(lws_ctx_t *ws_ctx, lws_t *ws, lws_event_t event, + void *user, void *in, size_t len); +static void destroy_context(wsl_ctx_t *ctx); + +static void MRP_EXIT destroy_context_table(void); + + + +static inline uint32_t map_poll_to_event(int in) +{ + uint32_t mask = 0; + + if (in & POLLIN) mask |= MRP_IO_EVENT_IN; + if (in & POLLOUT) mask |= MRP_IO_EVENT_OUT; + if (in & POLLHUP) mask |= MRP_IO_EVENT_HUP; + if (in & POLLERR) mask |= MRP_IO_EVENT_ERR; + + return mask; + +} + + +static inline short map_event_to_poll(uint32_t in) +{ + short mask = 0; + + if (in & MRP_IO_EVENT_IN) mask |= POLLIN; + if (in & MRP_IO_EVENT_OUT) mask |= POLLOUT; + if (in & MRP_IO_EVENT_HUP) mask |= POLLHUP; + if (in & MRP_IO_EVENT_ERR) mask |= POLLERR; + + return mask; +} + + +static int add_fd(wsl_ctx_t *wsc, int fd, int events) +{ + struct epoll_event e; + + if (wsc != NULL) { + e.data.u64 = 0; + e.data.fd = fd; + e.events = map_poll_to_event(events); + + if (epoll_ctl(wsc->epollfd, EPOLL_CTL_ADD, fd, &e) == 0) { + if (mrp_reallocz(wsc->fds, wsc->nfd, wsc->nfd + 1) != NULL) { + wsc->fds[wsc->nfd].fd = fd; + wsc->fds[wsc->nfd].events = e.events; + wsc->nfd++; + + return TRUE; + } + else + epoll_ctl(wsc->epollfd, EPOLL_CTL_DEL, fd, &e); + } + } + + return FALSE; +} + + +static int del_fd(wsl_ctx_t *wsc, int fd) +{ + struct epoll_event e; + int i; + + if (wsc != NULL) { + e.data.u64 = 0; + e.data.fd = fd; + e.events = 0; + epoll_ctl(wsc->epollfd, EPOLL_CTL_DEL, fd, &e); + + for (i = 0; i < wsc->nfd; i++) { + if (wsc->fds[i].fd == fd) { + if (i < wsc->nfd - 1) + memmove(wsc->fds + i, wsc->fds + i + 1, + (wsc->nfd - i - 1) * sizeof(*wsc->fds)); + + mrp_reallocz(wsc->fds, wsc->nfd, wsc->nfd - 1); + wsc->nfd--; + + return TRUE; + } + } + } + + return FALSE; +} + + +static pollfd_t *find_fd(wsl_ctx_t *wsc, int fd) +{ + int i; + + if (wsc != NULL) { + for (i = 0; i < wsc->nfd; i++) + if (wsc->fds[i].fd == fd) + return wsc->fds + i; + } + + return NULL; +} + + +static int mod_fd(wsl_ctx_t *wsc, int fd, int events, int op) +{ + struct epoll_event e; + pollfd_t *wfd; + + if (wsc != NULL) { + wfd = find_fd(wsc, fd); + + if (wfd != NULL) { + e.data.u64 = 0; + e.data.fd = fd; + + switch (op) { + case POLLFD_CLEAR: + e.events = wfd->events & ~map_poll_to_event(events); + break; + case POLLFD_SET: + e.events = wfd->events | map_poll_to_event(events); + break; + case POLLFD_CHANGE: + e.events = wfd->events = map_poll_to_event(events); + break; + default: + return FALSE; + } + + if (epoll_ctl(wsc->epollfd, EPOLL_CTL_MOD, fd, &e) == 0) + return TRUE; + } + } + + return FALSE; +} + + +static void purge_fds(wsl_ctx_t *wsc) +{ + if (wsc != NULL) { + mrp_free(wsc->fds); + wsc->fds = NULL; + wsc->nfd = 0; + } +} + + +static void epoll_event(mrp_io_watch_t *w, int fd, mrp_io_event_t mask, + void *user_data) +{ + wsl_ctx_t *wsc = (wsl_ctx_t *)user_data; + pollfd_t *wfd; + struct epoll_event *events, *e; + int nevent, n, i; + struct pollfd pollfd; + + MRP_UNUSED(w); + MRP_UNUSED(fd); + + if (wsc->nfd <= 0 || !(mask & MRP_IO_EVENT_IN)) + return; + + nevent = wsc->nfd; + events = alloca(nevent * sizeof(*events)); + + while ((n = epoll_wait(wsc->epollfd, events, nevent, 0)) > 0) { + mrp_debug("got %d epoll events for websocket context %p", n, wsc); + + for (i = 0, e = events; i < n; i++, e++) { + wfd = find_fd(wsc, e->data.fd); + + if (wfd != NULL) { + pollfd.fd = wfd->fd; + pollfd.events = map_event_to_poll(wfd->events); + pollfd.revents = map_event_to_poll(e->events); + + mrp_debug("delivering events 0x%x to websocket fd %d", + pollfd.revents, pollfd.fd); + + libwebsocket_service_fd(wsc->ctx, &pollfd); + } + } + } +} + + +/* + * context handling + */ + +#ifdef WEBSOCKETS_OLD + +/* + * Notes: + * In some environments we might be forced to run with really old + * versions of libwebsockets. This causes some amount of pain as + * some of the recent features in libwebsockets are essential for + * building a reasonable abstraction on top of it. + * + * Most notably, versions prior to 0291eb3..d764e84 (Oct 19 2012) + * do not support per-context user data. Since we need to associate + * our context with that of libwebsockets we have to build an extra + * mechanism for mapping between the two when user data support is + * not available. We use an extra hash table to store our context + * and use directly the (low 32-bits of the) libwebsocket context + * pointer as the key to store and fetch it. + * + * To minimize unreadibility and other code uglification factors, + * most of the code that deals with this version-dependent extra + * mechanism is put into the contamination chamber formed by the + * triplet of {set,get,clear}_context_userdata routines below. + * + * Note that since we decided (maybe unecessarily) to do mainloop + * integration also on a per-context basis, with old versions we + * also have a phase error: the first pollfd manipulation events + * for a context being created come __before__ the libwebsockets + * context creation routine returns, IOW before we get a chance to + * administer the reverse mapping between the contexts. This is + * unfortunate because if we cannot find our context for a pollfd + * request/event we just cannot handle it. This in turn would result + * in a practically useless context as its socket would not be + * (e)polled at all. + * + * The ugly pending_userdata kludge below is to overcome this problem. + * While creating a context, we register its intended userdata as the + * pending userdata before calling libwebsockets context creation + * routine and clear it afterwards. get_context_userdata knows to + * return the pending_userdata if it cannot do the reverse mapping + * using the libwebsocket context pointer. While this is quite ugly, + * I really don't see any other way. A limitation of this is that we + * cannot have two unfinished contexts being created in parallel, but + * that really should not happen under any circumstances anyway. + */ + + +static mrp_htbl_t *ctxtbl; +static void *pending_userdata; + +static int ctx_cmp(const void *ctx1, const void *ctx2) +{ + return ctx2 - ctx1; +} + +static uint32_t ctx_hash(const void *ctx) +{ + uint64_t h; + + h = (ptrdiff_t)ctx; + + return (uint32_t)(h & 0xffffffff); +} + +static int create_context_table(void) +{ + mrp_htbl_config_t hcfg; + + if (ctxtbl == NULL) { + mrp_clear(&hcfg); + + hcfg.comp = ctx_cmp; + hcfg.hash = ctx_hash; + hcfg.free = NULL; + + ctxtbl = mrp_htbl_create(&hcfg); + + return (ctxtbl != NULL); + } + else + return TRUE; +} + +static void destroy_context_table(void) +{ + if (ctxtbl != NULL) + mrp_htbl_destroy(ctxtbl, FALSE); +} + + +static int set_pending_userdata(void *ptr) +{ + if (pending_userdata == NULL) { + pending_userdata = ptr; + return TRUE; + } + else + return FALSE; +} + + +static void clear_pending_userdata(void *ptr) +{ + if (pending_userdata == ptr) + pending_userdata = NULL; +} + + +static void *get_pending_userdata(void) +{ + return pending_userdata; +} + + +static int set_context_userdata(lws_ctx_t *ws_ctx, wsl_ctx_t *ctx) +{ + if (ctxtbl == NULL) + create_context_table(); + + if (ctxtbl != NULL) + return mrp_htbl_insert(ctxtbl, ws_ctx, ctx); + else + return FALSE; +} + + +static wsl_ctx_t *get_context_userdata(lws_ctx_t *ws_ctx) +{ + wsl_ctx_t *ctx; + + if (ctxtbl != NULL) + ctx = (wsl_ctx_t *)mrp_htbl_lookup(ctxtbl, (void *)ws_ctx); + else + ctx = NULL; + + if (ctx != NULL) + return ctx; + else + return get_pending_userdata(); +} + + +static void clear_context_userdata(lws_ctx_t *ws_ctx) +{ + if (ctxtbl != NULL) + mrp_htbl_remove(ctxtbl, ws_ctx, FALSE); +} + + +static lws_ctx_t *lws_create_ctx(lws_cci_t *cci) +{ + lws_ctx_t *ws_ctx; + + set_pending_userdata(cci->user); + + ws_ctx = libwebsocket_create_context(cci->port, cci->iface, + cci->protocols, cci->extensions, + cci->ssl_cert_filepath, + cci->ssl_private_key_filepath, + /* no ssl_ca */ + cci->gid, cci->uid, cci->options + /*no user_data*/); + if (ws_ctx != NULL) + set_context_userdata(ws_ctx, cci->user); + + clear_pending_userdata(cci->user); + + return ws_ctx; +} + +#else /* !WEBSOCKETS_OLD */ + +static void destroy_context_table(void) +{ + return; +} + + +static wsl_ctx_t *get_context_userdata(lws_ctx_t *ws_ctx) +{ + return libwebsocket_context_user(ws_ctx); +} + + +static void clear_context_userdata(lws_ctx_t *ws_ctx) +{ + MRP_UNUSED(ws_ctx); +} + + +static lws_ctx_t *lws_create_ctx(lws_cci_t *cci) +{ +#ifdef WEBSOCKETS_CONTEXT_INFO + return libwebsocket_create_context(cci); +#else + return libwebsocket_create_context(cci->port, cci->iface, + cci->protocols, cci->extensions, + cci->ssl_cert_filepath, + cci->ssl_private_key_filepath, + cci->ssl_ca_filepath, + cci->gid, cci->uid, cci->options, + cci->user); +#endif +} + +#endif /* !WEBSOCKETS_OLD */ + +static lws_ext_t *lws_get_internal_extensions(void) +{ +#ifdef WEBSOCKETS_QUERY_EXTENSIONS + return libwebsocket_get_internal_extensions(); +#else + return libwebsocket_internal_extensions; +#endif +} + +static int find_device(struct sockaddr *sa, char *buf, size_t size) +{ + struct sockaddr *ia; + struct ifreq ifreq[64]; + struct ifconf ifconf; + int status, sck, n, i; + + /* + * XXX FIXME: we only handle primary addresses at the moment... + */ + + if (size < IFNAMSIZ) { + errno = ENOBUFS; + return -1; + } + + if (sa->sa_family != AF_INET) { /* libwebsockets can't handle IPv6 */ + errno = EAFNOSUPPORT; + return -1; + } + + if (((struct sockaddr_in *)sa)->sin_addr.s_addr == 0x0) { + *buf = '\0'; + return 0; + } + + sck = socket(AF_INET, SOCK_DGRAM, 0); + + if (sck < 0) + return -1; + + ifconf.ifc_len = sizeof(ifreq); + ifconf.ifc_buf = (char *)&ifreq[0]; + + status = ioctl(sck, SIOCGIFCONF, &ifconf); + close(sck); + + if (status < 0) + return -1; + + n = ifconf.ifc_len / sizeof(ifreq[0]); + + for (i = 0; i < n; i++) { + ia = &ifreq[i].ifr_addr; + + if (ia->sa_family == sa->sa_family) { + if (((struct sockaddr_in *)sa)->sin_addr.s_addr == + ((struct sockaddr_in *)ia)->sin_addr.s_addr) { + strncpy(buf, ifreq[i].ifr_name, IFNAMSIZ - 1); + buf[IFNAMSIZ - 1] = '\0'; + return 0; + } + } + } + + errno = EADDRNOTAVAIL; + return -1; +} + + +wsl_ctx_t *wsl_create_context(mrp_mainloop_t *ml, wsl_ctx_cfg_t *cfg) +{ + lws_ext_t *builtin = lws_get_internal_extensions(); + lws_cci_t cci; + wsl_ctx_t *ctx; + wsl_proto_t *up, *http; + lws_proto_t *lws_protos, *lp; + int lws_nproto; + mrp_io_event_t events; + char ifname[IFNAMSIZ + 1]; + int i; + + ctx = NULL; + mrp_clear(&cci); + + if (cfg->addr != NULL) { + if (find_device(cfg->addr, ifname, sizeof(ifname)) < 0) + return NULL; + else { + mrp_debug("address mapped to device '%s'", + *ifname ? ifname : "<any>"); + + cci.iface = ifname; + + switch (cfg->addr->sa_family) { + case AF_INET: + cci.port = ntohs(((struct sockaddr_in *)cfg->addr)->sin_port); + break; + case AF_INET6: + cci.port = ntohs(((struct sockaddr_in6 *)cfg->addr)->sin6_port); + break; + default: + goto fail; + } + } + } + + ctx = mrp_allocz(sizeof(*ctx)); + + if (ctx == NULL) + goto fail; + + mrp_refcnt_init(&ctx->refcnt); + mrp_list_init(&ctx->pure_http); + + ctx->protos = cfg->protos; + ctx->nproto = cfg->nproto; + + if (!strcmp(cfg->protos[0].name, "http") || + !strcmp(cfg->protos[0].name, "http-only")) + http = &cfg->protos[0]; + else + http = NULL; + + lws_nproto = (http ? cfg->nproto : cfg->nproto + 1) + 1; + lws_protos = mrp_allocz_array(lws_proto_t, lws_nproto); + + if (lws_protos == NULL) + goto fail; + + lws_protos[0].name = "http"; + lws_protos[0].callback = http_event; + if (!http) + lws_protos[0].per_session_data_size = sizeof(void *); + else + lws_protos[0].per_session_data_size = sizeof(void *); + + lp = lws_protos + 1; + up = cfg->protos + (http ? 1 : 0); + + for (i = (http ? 1 : 0); i < cfg->nproto; i++) { + lp->name = up->name; + lp->callback = wsl_event; + lp->per_session_data_size = sizeof(void *); + + lp++; + up++; + } + + ctx->lws_protos = lws_protos; + ctx->http = http; + + ctx->epollfd = epoll_create1(EPOLL_CLOEXEC); + + if (ctx->epollfd < 0) + goto fail; + + events = MRP_IO_EVENT_IN; + ctx->ml = ml; + ctx->w = mrp_add_io_watch(ml, ctx->epollfd, events, epoll_event, ctx); + + if (ctx->w == NULL) + goto fail; + + cci.protocols = lws_protos; + cci.extensions = builtin; + cci.user = ctx; + cci.gid = cfg->gid; + cci.uid = cfg->uid; + + cci.ssl_cert_filepath = cfg->ssl_cert; + cci.ssl_private_key_filepath = cfg->ssl_pkey; + cci.ssl_ca_filepath = cfg->ssl_ca; + cci.ssl_cipher_list = cfg->ssl_ciphers; + + cci.options = 0; + cci.ka_time = cfg->timeout; + cci.ka_probes = cfg->nprobe; + cci.ka_interval = cfg->interval; + + ctx->ctx = lws_create_ctx(&cci); + + if (ctx->ctx != NULL) { + ctx->user_data = cfg->user_data; + + return ctx; + } + + fail: + if (ctx != NULL) { + if (ctx->epollfd >= 0) { + mrp_del_io_watch(ctx->w); + close(ctx->epollfd); + } + + mrp_free(ctx); + } + + return NULL; +} + + +wsl_ctx_t *wsl_ref_context(wsl_ctx_t *ctx) +{ + return mrp_ref_obj(ctx, refcnt); +} + + +int wsl_unref_context(wsl_ctx_t *ctx) +{ + if (mrp_unref_obj(ctx, refcnt)) { + mrp_debug("refcount of context %p dropped to zero", ctx); + destroy_context(ctx); + + return TRUE; + } + else + return FALSE; +} + + +static void destroy_context(wsl_ctx_t *ctx) +{ + if (ctx != NULL) { + mrp_debug("destroying context %p", ctx); + + mrp_del_io_watch(ctx->w); + ctx->w = NULL; + + close(ctx->epollfd); + ctx->epollfd = -1; + + purge_fds(ctx); + + if (ctx->ctx != NULL) { + clear_context_userdata(ctx->ctx); + libwebsocket_context_destroy(ctx->ctx); + } + + mrp_free(ctx->lws_protos); + mrp_free(ctx); + } +} + + +static wsl_proto_t *find_context_protocol(wsl_ctx_t *ctx, const char *protocol) +{ + wsl_proto_t *up; + int i; + + if (protocol != NULL) { + for (i = 0, up = ctx->protos; i < ctx->nproto; i++, up++) + if (!strcmp(up->name, protocol)) + return up; + } + + return NULL; +} + + +static wsl_sck_t *find_pure_http(wsl_ctx_t *ctx, lws_t *ws) +{ + mrp_list_hook_t *p, *n; + wsl_sck_t *sck; + + /* + * Notes: + * We expect an extremely low number of concurrent pure + * HTTP connections so we do asimple linear search here. + * We can change this if this turns out to be a false + * assumption. + */ + + mrp_list_foreach(&ctx->pure_http, p, n) { + sck = mrp_list_entry(p, typeof(*sck), hook); + + if (sck->sck == ws) + return sck; + } + + return NULL; +} + + +wsl_sck_t *wsl_connect(wsl_ctx_t *ctx, struct sockaddr *sa, + const char *protocol, wsl_ssl_t ssl, void *user_data) +{ + wsl_sck_t *sck, **ptr; + wsl_proto_t *up; + int port; + void *aptr; + char abuf[256]; + const char *astr; + + switch (sa->sa_family) { + case AF_INET: + aptr = &((struct sockaddr_in *)sa)->sin_addr; + port = ntohs(((struct sockaddr_in *)sa)->sin_port); + break; + case AF_INET6: + aptr = &((struct sockaddr_in6 *)sa)->sin6_addr; + port = ntohs(((struct sockaddr_in6 *)sa)->sin6_port); + break; + default: + errno = EINVAL; + return NULL; + } + + astr = inet_ntop(sa->sa_family, aptr, abuf, sizeof(abuf)); + + if (astr == NULL) + return NULL; + + up = find_context_protocol(ctx, protocol); + + if (up == NULL) { + errno = ENOPROTOOPT; + return NULL; + } + + sck = mrp_allocz(sizeof(*sck)); + ptr = mrp_allocz(sizeof(*ptr)); + + if (sck != NULL && ptr != NULL) { + /* + * Now we need to create and connect a new libwebsocket instance + * within the given context. We also need to set up a one-to-one + * mapping between the underlying libwebsocket and our wsl_sck_t + * so that we can handle both top-down (sending) and bottom-up + * (receiving) event propagation in the stack. + * + * We use the user data associated with the libwebsocket instance + * to store a back pointer to us. Whenever the socket instance + * is deleted locally (as opposed to our peer closing the session) + * we need to prevent the propagation of any potentially pending + * events to our deleted wsl_sck_t (which might have been freed). + * This we do by clearing the back pointer from the instance to us. + * + * However, since libwebsockets does not provide an API for this, + * as a trick we use an indirect back pointer and store a pointer + * to the actual back pointer also in wsl_sck_t here. This way we + * can always clear the back pointer when we need to. + * + * Also note, that memory management for the associated user data + * is asymmetric in many sense. For client connections, we allocate + * the data buffer and pass it on to libwebsockets. For incoming + * connections the user data buffer is allocated by libwebsockets + * and we only get a chance to fill it in the event handler for + * connection establishment. However, for both incoming and outgoing + * connections libwebsockets will free the buffer on behalf of us. + * + * The exact same notes apply to wsl_accept_pending below... + */ + + mrp_list_init(&sck->hook); + sck->ctx = wsl_ref_context(ctx); + sck->proto = up; + sck->buf = mrp_fragbuf_create(/*up->framed*/TRUE, 0); + + if (sck->buf != NULL) { + sck->user_data = user_data; + + if (strncmp(protocol, "http", 4)) { /* Think harder, Homer ! */ + *ptr = sck; + sck->sckptr = ptr; + } + else + mrp_list_append(&ctx->pure_http, &sck->hook); + + sck->sck = libwebsocket_client_connect_extended(ctx->ctx, + astr, port, + ssl, + "/", astr, astr, + protocol, -1, + ptr); + + if (sck->sck != NULL) + return sck; + + mrp_fragbuf_destroy(sck->buf); + mrp_list_delete(&sck->hook); + } + + wsl_unref_context(ctx); + mrp_free(ptr); + mrp_free(sck); + } + + return NULL; +} + + +wsl_sck_t *wsl_accept_pending(wsl_ctx_t *ctx, void *user_data) +{ + wsl_sck_t *sck, **ptr; + + if (ctx->pending == NULL || ctx->pending_proto == NULL) + return NULL; + + mrp_debug("accepting pending websocket connection %p/%p", ctx->pending, + ctx->pending_user); + + sck = mrp_allocz(sizeof(*sck)); + + if (sck != NULL) { + mrp_list_init(&sck->hook); + + /* + * Notes: + * The same notes apply here for context creation as for + * wsl_connect above... + */ + sck->ctx = wsl_ref_context(ctx); + sck->buf = mrp_fragbuf_create(/*ctx->pending_proto->framed*/TRUE, 0); + + if (sck->buf != NULL) { + sck->proto = ctx->pending_proto; + sck->user_data = user_data; + sck->sck = ctx->pending; + ptr = (wsl_sck_t **)ctx->pending_user; + sck->sckptr = ptr; + + mrp_debug("pending connection was a %s websocket", + ptr != NULL ? "real" : "HTTP"); + + if (ptr != NULL) /* genuine websocket */ + *ptr = sck; + else /* pure http socket */ + mrp_list_append(&ctx->pure_http, &sck->hook); + + /* let the event handler know we accepted the client */ + ctx->pending = NULL; + /* for pure http communicate sck back in pending_user */ + ctx->pending_user = (ptr == NULL ? sck : NULL); + ctx->pending_proto = NULL; + + return sck; + } + + wsl_unref_context(ctx); + mrp_free(sck); + } + + return NULL; +} + + +void wsl_reject_pending(wsl_ctx_t *ctx) +{ + mrp_debug("reject pending websocket (%s) connection %p/%p", + ctx->pending_proto->name, ctx->pending, ctx->pending_user); + + /* + * Nothing to do here really... just don't clear ctx->pending so the + * event handler will know to reject once it regains control. + */ +} + + +#ifdef WEBSOCKETS_CLOSE_SESSION + +/* + * WTF ? The prototype for this has been moved from libwebsockets.h to + * the uninstalled private-libwebsockets.h. If this is really going to + * be made private eventually, how is one supposed to close a websocket + * without closing its context and a side effect all other websockets + * associated with the same context ? + */ +extern void libwebsocket_close_and_free_session(struct libwebsocket_context *, + struct libwebsocket *, + enum lws_close_status); + + +void *wsl_close(wsl_sck_t *sck) +{ + wsl_ctx_t *ctx; + void *user_data; + int status; + + user_data = NULL; + + if (sck != NULL) { + if (sck->sck != NULL && sck->busy <= 0) { + mrp_debug("closing websocket %p/%p", sck, sck->sck); + + status = LWS_CLOSE_STATUS_NORMAL; + ctx = sck->ctx; + + sck->closing = TRUE; + libwebsocket_close_and_free_session(ctx->ctx, sck->sck, status); + sck->sck = NULL; + + if (sck->sckptr != NULL) /* genuine websocket */ + *sck->sckptr = NULL; + else /* pure http socket */ + mrp_list_delete(&sck->hook); + + if (ctx != NULL) { + user_data = ctx->user_data; + wsl_unref_context(ctx); + sck->ctx = NULL; + } + + mrp_fragbuf_destroy(sck->buf); + sck->buf = NULL; + + mrp_debug("freeing websocket %p", sck); + mrp_free(sck); + } + else { + mrp_debug("marking websocket %p/%p for closing", sck, sck->sck); + sck->closing = TRUE; + } + } + + return user_data; +} + + +#else /* !WEBSOCKET_CLOSE_SESSION */ + +void *wsl_close(wsl_sck_t *sck) +{ + lws_ctx_t *ws_ctx; + lws_t *ws; + void *user_data; + + /* + * With recent libwebsockets libwebsocket_close_and_free_session has + * been fully turned into a private library symbol. According to the + * docs the official way to trigger closing a websocket from the + * 'upper layers' (ie. outside of libwebsocket event callbacks) is to + * 1) administer the fact that the websocket should be closed + * 2) enable pollouts for the websocket (callback_on_writable) + * 3) hope that libwebsockets will not decide to omit delivering a + * LWS_CALLBACK_{CLIENT,SERVER}_WRITEABLE event, and + * 4) in the event callback check if the websocket is marked for + * deletion, and if it is reutrn -1 to indicate libwebsockets that + * it should close the socket + * Hmm... I guess simple elegance was not one of the design principles. + * + * Anyway, here's our second attempt to implement this indirect socket + * closing scheme without too much memory corruption and leaks... Argh. + * + * Notes: XXX TODO + * Currently we only check and handle pending deletion when + * dealing with *_WRITEABLE events. Probably we should also do + * it for a few other events as well, for instance for *_RECEIVE + * and *_CALLBACK_HTTP). + */ + + user_data = NULL; + + if (sck != NULL) { + if (sck->sck != NULL && sck->busy <= 0) { + mrp_debug("closing %s websocket %p/%p", + sck->sckptr ? "real" : "HTTP", sck->sck, sck); + + ws = sck->sck; + sck->sck = NULL; + sck->closing = TRUE; + + /* clear the back pointer to us */ + if (sck->sckptr != NULL) + *sck->sckptr = NULL; + else + mrp_list_delete(&sck->hook); + + if (sck->ctx != NULL) { + ws_ctx = sck->ctx->ctx; + user_data = sck->ctx->user_data; + wsl_unref_context(sck->ctx); + sck->ctx = NULL; + } + else + ws_ctx = NULL; + + mrp_fragbuf_destroy(sck->buf); + sck->buf = NULL; + + mrp_debug("freeing websocket %p", sck); + mrp_free(sck); + + if (ws_ctx != NULL) + libwebsocket_callback_on_writable(ws_ctx, ws); + } + else + sck->closing = TRUE; + } + + return user_data; +} + + +#endif /* !WEBSOCKET_CLOSE_SESSION */ + + +static int check_closed(wsl_sck_t *sck) +{ + if (sck != NULL) { + if (sck->closing && sck->busy <= 0) { + wsl_close(sck); + return TRUE; + } + } + + return FALSE; +} + + +int wsl_set_sendmode(wsl_sck_t *sck, wsl_sendmode_t mode) +{ + const char *name; + + switch (mode) { + case WSL_SEND_TEXT: name = "text"; break; + case WSL_SEND_BINARY: name = "binary"; break; + default: return FALSE; + } + + mrp_debug("websocket %p/%p mode changed to %s", sck, sck->sck, name); + sck->send_mode = mode; + + return TRUE; +} + + +int wsl_send(wsl_sck_t *sck, void *payload, size_t size) +{ + unsigned char *buf; + size_t pre, post, total; + uint32_t *len; + + if (sck != NULL && sck->sck != NULL) { + if (sck->proto->framed) { + pre = LWS_SEND_BUFFER_PRE_PADDING; + post = LWS_SEND_BUFFER_POST_PADDING; + buf = alloca(pre + sizeof(*len) + size + post); + len = (uint32_t *)(buf + pre); + *len = htobe32(size); + + memcpy(buf + pre + sizeof(*len), payload, size); + total = sizeof(*len) + size; + } + else { + pre = LWS_SEND_BUFFER_PRE_PADDING; + post = LWS_SEND_BUFFER_POST_PADDING; + buf = alloca(pre + size + post); + + memcpy(buf + pre, payload, size); + total = size; + } + +#if (WSL_SEND_TEXT != 0) + if (!sck->send_mode) + sck->send_mode = WSL_SEND_TEXT; +#endif + + if (libwebsocket_write(sck->sck, buf + pre, total, sck->send_mode) >= 0) + return TRUE; + } + + return FALSE; +} + + +int wsl_serve_http_file(wsl_sck_t *sck, const char *path, const char *type) +{ + mrp_debug("serving file '%s' (%s) over websocket %p", path, type, sck->sck); + +#ifndef WEBSOCKETS_OLD +# ifdef WEBSOCKETS_SERVE_FILE_EXTRAARG + if (libwebsockets_serve_http_file(sck->ctx->ctx, sck->sck, path, + type, NULL) == 0) + return TRUE; + else + return FALSE; +# else + if (libwebsockets_serve_http_file(sck->ctx->ctx, sck->sck, path, type) == 0) + return TRUE; + else + return FALSE; +# endif +#else + if (libwebsockets_serve_http_file(sck->sck, path, type) == 0) + return TRUE; + else + return FALSE; +#endif +} + + +#ifdef LWS_OPENSSL_SUPPORT + +static void load_extra_certs(wsl_ctx_t *ctx, void *user, lws_event_t event) +{ + int is_server; + + if (ctx != NULL && ctx->load_certs != NULL) { + if (event == LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS) + is_server = TRUE; + else + is_server = FALSE; + + ctx->load_certs(ctx, (SSL_CTX *)user, is_server); + } +} + + +static int verify_client_cert(void *user, void *in, size_t len) +{ + X509_STORE_CTX *x509_ctx; + SSL *ssl; + int pre_ok; + + if (verify_client_cert_cb != NULL) { + x509_ctx = (X509_STORE_CTX *)user; + ssl = (SSL *)in; + pre_ok = (int)len; + + if (verify_client_cert_cb(x509_ctx, ssl, pre_ok)) + return TRUE; + else + return FALSE; + } + else + return TRUE; +} + +#else /* !LWS_OPENSSL_SUPPORT */ + +static void load_extra_certs(wsl_ctx_t *ctx, void *user, lws_event_t event) +{ + MRP_UNUSED(ctx); + MRP_UNUSED(user); + MRP_UNUSED(event); + + return; +} + + +static int verify_client_cert(void *user, void *in, size_t len) +{ + MRP_UNUSED(user); + MRP_UNUSED(in); + MRP_UNUSED(len); + + return TRUE; +} + +#endif + + + +static int http_event(lws_ctx_t *ws_ctx, lws_t *ws, lws_event_t event, + void *user, void *in, size_t len) +{ + wsl_ctx_t *ctx = get_context_userdata(ws_ctx); + wsl_sck_t *sck; + wsl_proto_t *up; + const char *ext, *uri; + int fd, mask, status, accepted; + + switch (event) { + case LWS_CALLBACK_ESTABLISHED: + mrp_debug("client-handshake completed on websocket %p/%p", ws, user); + return LWS_EVENT_OK; + + case LWS_CALLBACK_CLOSED: + mrp_debug("websocket %p/%p closed", ws, user); + return LWS_EVENT_OK; + + case LWS_CALLBACK_CLIENT_ESTABLISHED: + mrp_debug("server-handshake completed on websocket %p/%p", ws, user); + return LWS_EVENT_OK; + + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + mrp_debug("client connection failed"); + return LWS_EVENT_OK; + + case LWS_CALLBACK_RECEIVE: + mrp_debug("received HTTP data from client"); + return LWS_EVENT_OK; + + case LWS_CALLBACK_CLIENT_RECEIVE: + mrp_debug("recived HTTP data from server"); + return LWS_EVENT_OK; + + case LWS_CALLBACK_CLIENT_RECEIVE_PONG: + mrp_debug("client received pong"); + return LWS_EVENT_OK; + + /* + * mainloop integration + */ +#ifdef WEBSOCKETS_CHANGE_MODE_POLL_FD + + case LWS_CALLBACK_ADD_POLL_FD: { + struct libwebsocket_pollargs *pa = (struct libwebsocket_pollargs *)in; + fd = pa->fd; + mask = pa->events; + + mrp_debug("start polling fd %d for events 0x%x", fd, mask); + if (add_fd(ctx, fd, mask)) + return LWS_EVENT_OK; + else + return LWS_EVENT_ERROR; + } + + case LWS_CALLBACK_DEL_POLL_FD: { + struct libwebsocket_pollargs *pa = (struct libwebsocket_pollargs *)in; + fd = pa->fd; + + mrp_debug("stop polling fd %d", fd); + if (del_fd(ctx, fd)) + return LWS_EVENT_OK; + else + return LWS_EVENT_ERROR; + } + + case LWS_CALLBACK_CHANGE_MODE_POLL_FD: { + struct libwebsocket_pollargs *pa = (struct libwebsocket_pollargs *)in; + fd = pa->fd; + mask = pa->events; + + mrp_debug("setting poll events to 0x%x for fd %d", mask, fd); + if (mod_fd(ctx, fd, mask, FALSE)) + return LWS_EVENT_OK; + else + return LWS_EVENT_ERROR; + } + +#else /* WEBSOCKETS_CHANGE_MODE_POLL_FD */ + + case LWS_CALLBACK_ADD_POLL_FD: +#ifdef WEBSOCKETS_CONTEXT_INFO /* just brilliant... */ + fd = (ptrdiff_t)in; +#else + fd = (ptrdiff_t)user; +#endif + mask = (int)len; + mrp_debug("start polling fd %d for events 0x%x", fd, mask); + if (add_fd(ctx, fd, mask)) + return LWS_EVENT_OK; + else + return LWS_EVENT_ERROR; + + case LWS_CALLBACK_DEL_POLL_FD: +#ifdef WEBSOCKETS_CONTEXT_INFO /* just brilliant... */ + fd = (ptrdiff_t)in; +#else + fd = (ptrdiff_t)user; +#endif + mrp_debug("stop polling fd %d", fd); + if (del_fd(ctx, fd)) + return LWS_EVENT_OK; + else + return LWS_EVENT_ERROR; + + case LWS_CALLBACK_SET_MODE_POLL_FD: +#ifdef WEBSOCKETS_CONTEXT_INFO /* just brilliant... */ + fd = (ptrdiff_t)in; +#else + fd = (ptrdiff_t)user; +#endif + mask = (int)len; + mrp_debug("enable poll events 0x%x for fd %d", mask, fd); + if (mod_fd(ctx, fd, mask, FALSE)) + return LWS_EVENT_OK; + else + return LWS_EVENT_ERROR; + + case LWS_CALLBACK_CLEAR_MODE_POLL_FD: +#ifdef WEBSOCKETS_CONTEXT_INFO /* just brilliant... */ + fd = (ptrdiff_t)in; +#else + fd = (ptrdiff_t)user; +#endif + mask = (int)len; + mrp_debug("disable poll events 0x%x for fd %d", mask, fd); + if (mod_fd(ctx, fd, mask, TRUE)) + return LWS_EVENT_OK; + else + return LWS_EVENT_ERROR; + +#endif /* WEBSOCKETS_CHANGE_MODE_POLL_FD */ + + case LWS_CALLBACK_SERVER_WRITEABLE: +#ifndef WEBSOCKETS_CLOSE_SESSION + sck = find_pure_http(ctx, ws); + + if (sck == NULL) { + mrp_debug("asking to close unassociated websocket %p", ws); + return LWS_EVENT_CLOSE; + } +#endif + mrp_debug("socket server side writeable again"); + return LWS_EVENT_OK; + + case LWS_CALLBACK_CLIENT_WRITEABLE: +#ifndef WEBSOCKETS_CLOSE_SESSION + sck = find_pure_http(ctx, ws); + + if (sck == NULL) { + mrp_debug("asking to close unassociated websocket %p", ws); + return LWS_EVENT_CLOSE; + } +#endif + mrp_debug("socket client side writeable again"); + return LWS_EVENT_OK; + + /* + * clients wanting to stay pure HTTP clients + * + * Notes: + * Clients that stay pure HTTP clients (ie. do not negotiate a + * websocket connection) never get an LWS_CALLBACK_ESTABLISHED + * event emitted for. This is a bit unfortunate, since that is + * the event we map to the incoming connection event of our + * transport layer. + * + * However, we'd really like to keep pure HTTP and websocket + * connections as much equal as possible. First and foremost + * this means that we'd like to associate our own websocklib + * wsl_sck_t socket context to lws_t and vice versa. Also + * similarly to websocket connections we want to give the upper + * layer a chance to accept or reject the connection. + * + * Since there is no ESTABLISHED event for pure HTTP clients, + * we have to emulate one such here. We need to check if test + * ws belongs to a known connection by checking if it has an + * associated wsl_sck_t. If not we need to call the upper layer + * to let it accept or reject the connection. If it has already + * we need to call the reception handler of the upper layer. + * + * However, unfortunately libwebsockets never allocates user + * data for the HTTP websockets even we specify a non-zero size + * for protocol 0. Hence, we cannot use our normal mechanism of + * associating the upper layer wsl_sck_t context using the ws + * user data. Instead we need to separately keep track of HTTP + * websockets and look up the associated wsl_sck_t using this + * secondary bookkeeping. + */ + + +#ifdef WEBSOCKETS_FILTER_HTTP_CONNECTION + case LWS_CALLBACK_FILTER_HTTP_CONNECTION: + return 0; +#endif + + case LWS_CALLBACK_HTTP: + uri = (const char *)in; + + if (ctx->http == NULL) { + mrp_debug("denying HTTP request of '%s' for httpless context", uri); + return LWS_EVENT_DENY; + } + + sck = find_pure_http(ctx, ws); + + if (sck != NULL) { /* known socket, deliver event */ + deliver_event: + up = sck->proto; + + if (up != NULL) { + SOCKET_BUSY_REGION(sck, { + up->cbs.recv(sck, in, strlen(uri), sck->user_data, + up->proto_data); + up->cbs.check(sck, sck->user_data, up->proto_data); + }); + + sck = find_pure_http(ctx, ws); + + if (check_closed(sck)) + return 0; + } + + status = LWS_EVENT_OK; + } + else { /* unknown socket, needs to accept */ + if (ctx->pending != NULL) { + mrp_log_error("Multiple pending connections, rejecting."); + return LWS_EVENT_DENY; + } + + up = ctx->http; + + ctx->pending = ws; + ctx->pending_user = NULL; + ctx->pending_proto = up; + + wsl_ref_context(ctx); + up->cbs.connection(ctx, "XXX TODO dig out peer address", up->name, + ctx->user_data, up->proto_data); + sck = ctx->pending_user; + ctx->pending_user = NULL; + + /* XXX TODO + * check if sockets gets properly closed and freed if + * cb->connection calls close on the 'listening' websocket in + * the transport layer... + */ + + accepted = (ctx->pending == NULL); + wsl_unref_context(ctx); + + if (accepted) + goto deliver_event; + else + status = LWS_EVENT_DENY; + } + + return status; + +#ifndef WEBSOCKETS_OLD + case LWS_CALLBACK_HTTP_FILE_COMPLETION: + uri = (const char *)in; + if (uri != NULL) + mrp_debug("serving '%s' over HTTP completed", uri); + else + mrp_debug("serving HTTP content completed"); + + sck = find_pure_http(ctx, ws); + + if (sck != NULL) { /* known socket, deliver event */ + up = sck->proto; + + if (up != NULL) { + SOCKET_BUSY_REGION(sck, { + up->cbs.http_done(sck, in, sck->user_data, + up->proto_data); + up->cbs.check(sck, sck->user_data, up->proto_data); + }); + + sck = find_pure_http(ctx, ws); + + if (check_closed(sck)) + return 0; + } + + status = LWS_EVENT_OK; + } + + return LWS_EVENT_OK; +#endif + + /* + * events always routed to protocols[0] + * + * XXX TODO: we need to open up for the upper layers using + * optionally settable wsl_ctx_t-level callbacks at least + * + * FILTER_NETWORK_CONNECTION + * FILTER_PROTOCOL_CONNECTION + * OPENSSL_* + * + * Probably for the sake of completeness we should open up + * all of these... + */ + + case LWS_CALLBACK_FILTER_NETWORK_CONNECTION: + fd = (ptrdiff_t)user; + /* we don't filter based on the socket/address */ + return LWS_EVENT_OK; + + case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: + /* we don't filter based on headers */ + return LWS_EVENT_OK; + + case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS: + load_extra_certs(ctx, user, event); + return LWS_EVENT_OK; + +#ifdef LWS_OPENSSL_SUPPORT + if (ctx != NULL && ctx->load_certs != NULL) + ctx->load_certs(ctx, user, FALSE); +#endif + return LWS_EVENT_OK; + + case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS: + load_extra_certs(ctx, user, TRUE); + return LWS_EVENT_OK; + + case LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION: + if (verify_client_cert(user, in, len)) + return LWS_EVENT_OK; + else + return LWS_EVENT_DENY; + + case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: + /* no extra headers we'd like to add */ + return LWS_EVENT_OK; + + case LWS_CALLBACK_CONFIRM_EXTENSION_OKAY: + ext = (const char *)in; + /* deny all extensions on the server side */ + mrp_debug("denying server extension '%s'", ext); + return LWS_EVENT_DENY; + + case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED: + ext = (const char *)in; + /* deny all extensions on the client side */ + mrp_debug("denying client extension '%s'", ext); + return LWS_EVENT_DENY; + + default: + break; + } + + return LWS_EVENT_DENY; +} + + +static int wsl_event(lws_ctx_t *ws_ctx, lws_t *ws, lws_event_t event, + void *user, void *in, size_t len) +{ + wsl_ctx_t *ctx = get_context_userdata(ws_ctx); + wsl_sck_t *sck; + wsl_proto_t *up; + void *data; + size_t size; + uint32_t total; + const char *ext; + lws_proto_t *proto; + int status; + + MRP_UNUSED(ext); + MRP_UNUSED(ws_ctx); + + switch (event) { + case LWS_CALLBACK_ESTABLISHED: + mrp_debug("client-handshake completed on websocket %p/%p", ws, user); + + /* + * Connection acceptance is a bit tricky. Once libwebsockets + * has completed its handshaking phase with the client it lets + * us know about a new established connection. This is what we + * want to map to an incoming connection attempt. Since we don't + * want to know about the internals of the upper layer, neither + * want the upper layer to know about our internals, the only + * way to pass information about the connection around in the + * context at this point. + * + * To keep things simple we only prepare and handle once + * outstanding connection attemp at a time. This is equivalent + * to listening on a stream-socket with a backlog of 1. Since we + * run single-threaded it shouldn't ever be possible to have more + * than one pending connection if the upper layer does things + * right but we do check for this and reject multiple pending + * connections here... + * + * We store the pending websocket instance and its associated + * user data in the context then call the connection notifier + * callback. If the upper layer wants to accept the connection + * it calls wsl_accept_pending. That in turn digs these out from + * the context to set up and hook together things properly. If all + * goes fine wsl_accept_pending clears pending and pending_user + * from the context. If something fails or the upper layer decides + * not to accept the connection, pending and pending_user stay + * intact in which case we'll reject the client here once the + * callback returns. + */ + + if (ctx->pending != NULL) { + mrp_log_error("Multiple pending connections, rejecting."); + return LWS_EVENT_DENY; + } + + + proto = (lws_proto_t *)libwebsockets_get_protocol(ws); + up = find_context_protocol(ctx, proto->name); + + if (up == NULL) { + mrp_debug("unknown protocol '%s' requested, rejecting", + proto ? proto->name : "<none>"); + return LWS_EVENT_DENY; + } + else + mrp_debug("found descriptor %p for protocol '%s'", up, up->name); + + ctx->pending = ws; + ctx->pending_user = user; + ctx->pending_proto = up; + + wsl_ref_context(ctx); + up->cbs.connection(ctx, "XXX TODO dig out peer address", up->name, + ctx->user_data, up->proto_data); + + /* XXX TODO + * check if sockets gets properly closed and freed if + * cb->connection calls close on the 'listening' websocket in + * the transport layer... + */ + + if (ctx->pending == NULL) /* connection accepted */ + status = LWS_EVENT_OK; + else /* connection rejected */ + status = LWS_EVENT_DENY; + wsl_unref_context(ctx); + + return status; + + case LWS_CALLBACK_CLOSED: + proto = (lws_proto_t *)libwebsockets_get_protocol(ws); + up = find_context_protocol(ctx, proto->name); + mrp_debug("websocket %p/%p (%s) closed", ws, user, + up ? up->name : "<unknown>"); + + sck = *(wsl_sck_t **)user; + up = sck ? sck->proto : NULL; + + if (up != NULL) { + SOCKET_BUSY_REGION(sck, { + up->cbs.closed(sck, 0, sck->user_data, up->proto_data); + up->cbs.check(sck, sck->user_data, up->proto_data); + }); + + check_closed(sck); + } + return LWS_EVENT_OK; + + case LWS_CALLBACK_CLIENT_ESTABLISHED: + mrp_debug("server-handshake completed on websocket %p/%p", ws, user); + return LWS_EVENT_OK; + + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + mrp_debug("client connection failed"); + return LWS_EVENT_OK; + + case LWS_CALLBACK_RECEIVE: + case LWS_CALLBACK_CLIENT_RECEIVE: + mrp_debug("%zu bytes received on websocket %p/%p", len, ws, user); + mrp_debug("%zd remaining from this message", + libwebsockets_remaining_packet_payload(ws)); + + sck = *(wsl_sck_t **)user; + up = sck ? sck->proto : NULL; + + if (up != NULL) { + if (!up->framed && !mrp_fragbuf_missing(sck->buf)) { + /* new packet of an unframed protocol, push message size */ + total = len + libwebsockets_remaining_packet_payload(ws); + mrp_debug("unframed protocol, total message size %u", total); + + total = htobe32(total); + mrp_fragbuf_push(sck->buf, &total, sizeof(total)); + } + + if (mrp_fragbuf_push(sck->buf, in, len)) { + data = NULL; + size = 0; + + while (mrp_fragbuf_pull(sck->buf, &data, &size)) { + mrp_debug("websocket %p/%p has a message of %zd bytes", + ws, user, size); + + SOCKET_BUSY_REGION(sck, { + up->cbs.recv(sck, data, size, sck->user_data, + up->proto_data); + up->cbs.check(sck, sck->user_data, up->proto_data); + }); + + if (check_closed(sck)) + break; + } + } + else { + mrp_log_error("failed to push data to fragment buffer"); + + SOCKET_BUSY_REGION(sck, { + wsl_close(sck); +#if 0 /* + * XXX Hmm... calling wsl_close instead of this now. Should be tested + * if that really works. + */ + sck->closing = TRUE; /* make sure sck gets closed */ + up->cbs.closed(sck, ENOBUFS, sck->user_data, + up->proto_data); + libwebsocket_close_and_free_session(ctx->ctx, sck->sck, + LWS_INTERNAL_ERROR); + up->cbs.check(sck, sck->user_data, up->proto_data); +#endif + }); + + check_closed(sck); + return -1; + } + } + return LWS_EVENT_OK; + + case LWS_CALLBACK_SERVER_WRITEABLE: +#ifndef WEBSOCKETS_CLOSE_SESSION + sck = *(wsl_sck_t **)user; + + if (sck == NULL) { + mrp_debug("asking to close unassociated websocket %p", ws); + + return LWS_EVENT_CLOSE; + } +#endif + mrp_debug("socket server side writeable again"); + return LWS_EVENT_OK; + + case LWS_CALLBACK_CLIENT_WRITEABLE: +#ifndef WEBSOCKETS_CLOSE_SESSION + sck = *(wsl_sck_t **)user; + + if (sck == NULL) { + mrp_debug("asking to close unassociated websocket %p", ws); + + return LWS_EVENT_CLOSE; + } +#endif + mrp_debug("socket client side writeable again"); + return LWS_EVENT_OK; + + default: + break; + } + + return LWS_EVENT_OK; +} + + + +/* + * logging + */ + +#ifndef WEBSOCKETS_OLD + +#ifdef WEBSOCKETS_LOG_WITH_LEVEL +static void libwebsockets(int level, const char *line) +#else +static void libwebsockets(const char *line) +#endif +{ + const char *ts, *ll; + const char *b, *e, *lvl; + int l, ls; + uint32_t mask; + +#ifdef WEBSOCKETS_LOG_WITH_LEVEL + MRP_UNUSED(level); +#else + /* + * If our (shaky) configure-time check gives a false negative, + * we'll expect only line but will be passed both level and line. + * Try catching it instead of crashing on it here... + */ + if (line < (const char *)(LLL_CLIENT << 3)) + return; +#endif + + if ((mask = mrp_log_get_mask()) == 0) + return; + + /* + * Notes: + * libwebsockets logging infrastructure has independently maskable + * log classes and supports overriding its default logger. The log + * classes are the regular error, warning, info, and debug classes + * plus the libwebsockets-specific parser, header, extension, and + * client classes. The logging infra filters the messages based on + * their class, then formats the message and passes it on to the + * (default builtin, or externally set) logger function. This gets + * a fully formatted log message that consists of a timestamp, a + * log class prefix and the message itself which typically contains + * at least one terminating newline. + * + * Because of the semantic content of the messages coming from + * libwebsockets we'd like to preserve the class of errors and + * warnings but convert the rest to debug messages. Additionally, + * we'd like to keep the message format as consistent with the + * murphy infra as possible with a reasonable effort. This means + * stripping the timestamp and log class, as these are provided + * by the murphy infra (if configured so). However, for the + * libwebsockets-specific parser-, header-, extension-, and client- + * classes we want to keep the extra information carried by the + * log class as part of the message. + * + * Because the libwebsockets log messages are terminated by '\n', + * we also prepare here to properly bridge multiline messages to + * the murphy infra (although I'm not sure the library ever issues + * such messages). + * + * So to sum it up the necessary steps to bridge messages here are: + * 1) strip timestamp, + * 2) dig out and strip log class + * 3) map log class to murphy infra, ie. + * keep errors and warnings, squash the rest to debug + * 4) break multiline messages to lines + * 5) pass each line on to the murphy infra, + * for parser-, header-, extension-, and client-messages + * prefix each line with the class + * + */ + + lvl = "???"; + ls = 3; + + ts = strchr(line, '['); + ll = ts != NULL ? strchr(ts, ']') : NULL; + + /* strip timestamp, dig out log level, find beginning of the message */ + if (ll != NULL && ll[1] == ' ') { + ll += 2; + b = strchr(ll, ':'); + + if (b != NULL && b[1] == ' ') { + b += 2; + + while (*b == ' ') + b++; + + /* map log level: debug, info, err, warn, or other */ + switch (*ll) { + case 'D': + if (!(mask & MRP_LOG_MASK_DEBUG)) + return; + lvl = "d"; + break; + case 'I': + if (!(mask & MRP_LOG_MASK_INFO)) + return; + lvl = "i"; + break; + case 'W': + if (!(mask & MRP_LOG_MASK_WARNING)) + return; + lvl = "w"; + break; + case 'E': + if (ll[1] == 'R') { + if (!(mask & MRP_LOG_MASK_ERROR)) + return; + lvl = "e"; + } + else { + other: + if (!(mask & MRP_LOG_MASK_DEBUG)) + return; + lvl = ll; + e = strchr(lvl, ':'); + + if (e != NULL) + ls = e - lvl; + else { + lvl = "???:"; + ls = 4; + } + } + break; + + default: + goto other; + } + } + else + goto unknown; + } + else { + unknown: + /* if we get confused with the format, default to logging it all */ + lvl = NULL; + b = line; + } + +#ifdef WEBSOCKETS_LOG_WITH_LEVEL + switch (level) { + case LLL_ERR: lvl = "e"; ls = 0; break; + case LLL_WARN: lvl = "w"; ls = 0; break; + case LLL_INFO: lvl = "i"; ls = 0; break; + case LLL_DEBUG: lvl = "d"; ls = 0; break; + case LLL_NOTICE: lvl = "d"; ls = 0; break; + case LLL_PARSER: lvl = "parser" ; ls = 6; break; + case LLL_HEADER: lvl = "header" ; ls = 6; break; + case LLL_EXT: lvl = "ext" ; ls = 3; break; + case LLL_CLIENT: lvl = "client" ; ls = 6; break; + case LLL_LATENCY: lvl = "latency"; ls = 7; break; + default: lvl = "???" ; ls = 3; break; + } + + b = line; + while (*b == ' ' || *b == '\t') + b++; +#endif + + /* break the message to lines and pass it on to the murphy infra */ + e = strchr(b, '\n'); + while (e || b) { + if (e) + l = e - b; + else + l = strlen(b); + + if (!l) + break; + + switch (lvl[0] | (lvl[1] << 8)) { + case 'd': mrp_debug("%*.*s", l, l, b); break; + case 'i': mrp_debug("%*.*s", l, l, b); break; + case 'w': mrp_log_warning("libwebsockets: %*.*s", l, l, b); break; + case 'e': mrp_log_error("libwebsockets: %*.*s", l, l, b); break; + default: mrp_debug("[%*.*s] %*.*s", ls, ls, lvl, l, l, b); + } + + if (e != NULL) { + b = e + 1; + e = strchr(b, '\n'); + } + else + b = NULL; + } +} + + +void wsl_set_loglevel(wsl_loglevel_t mask) +{ + lws_set_log_level(mask, libwebsockets); +} + + +#else /* WEBSOCKETS_OLD */ + +void wsl_set_loglevel(wsl_loglevel_t mask) +{ + MRP_UNUSED(mask); + + mrp_log_warning("libwebsockets too old to redirect logs..."); +} + +#endif /* WEBSOCKETS_OLD */ diff --git a/src/common/websocklib.h b/src/common/websocklib.h new file mode 100644 index 0000000..83dbb95 --- /dev/null +++ b/src/common/websocklib.h @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_WEBSOCKLIB_H__ +#define __MURPHY_WEBSOCKLIB_H__ + +#include <sys/socket.h> + +#include <libwebsockets.h> + +#include <murphy/common/macros.h> +#include <murphy/common/mainloop.h> + +MRP_CDECL_BEGIN + +/* + * websocket context + * + * A websocket context is basically a libwebsocket_context plus the + * additional glue data and code necessary to integrate the context + * into our mainloop. For our transport abstraction, we create one + * context per transport instance. However, accepted transports do + * share their context with the listening transport (ie. the server- + * side libwebsocket) they were accepted on. + * + * XXX TODO We probably need to change this so that we create one + * context per address/port (or in libwebsockets case device/port). + * + */ + +typedef struct wsl_ctx_s wsl_ctx_t; + + +/* + * websocket + * + * A websocket is a libwebsocket instance together with its + * associated websocket context. + */ +typedef struct wsl_sck_s wsl_sck_t; + + +/* + * websocket event callbacks to the upper transport layer + * + * These callbacks are used to deliver events from the underlying + * websocket transport layer to the upper murphy transport layer. + */ +typedef struct { + /** Connection attempt on a websocket. */ + void (*connection)(wsl_ctx_t *ctx, char *addr, const char *protocol, + void *user_data, void *proto_data); + /** Websocket connection closed by peer. */ + void (*closed)(wsl_sck_t *sck, int error, void *user_data, + void *proto_data); + /** Data received on websocket. */ + void (*recv)(wsl_sck_t *sck, void *data, size_t size, void *user_data, + void *proto_data); + /** Check if transport should be destroyed. */ + int (*check)(wsl_sck_t *sck, void *user_data, void *proto_data); + + /** HTTP (content) request completed. */ + void (*http_done)(wsl_sck_t *sck, const char *uri, void *user_data, + void *proto_data); + +#ifdef LWS_OPENSSL_SUPPORT + /** Load extra client or server certificates, if necessary. */ + void (*load_certs)(wsl_ctx_t *ctx, SSL_CTX *ssl, int is_server); +#else + void (*load_certs)(wsl_ctx_t *, void *, int); +#endif +} wsl_callbacks_t; + + +/* + * websocket protocol + * + * A websocket protocol is a protocol name together with protocol-specific + * upper-layer callbacks. + */ +typedef struct { + const char *name; /* protocol name */ + wsl_callbacks_t cbs; /* event/request callbacks */ + int framed; /* whether a framed protocol */ + void *proto_data; /* protocol-specific user data */ +} wsl_proto_t; + + +/* + * websocket write modes + */ + +typedef enum { + WSL_SEND_TEXT = LWS_WRITE_TEXT, /* text mode */ + WSL_SEND_BINARY = LWS_WRITE_BINARY, /* binary/blob mode */ +#if 0 + WSL_SEND_HTTP = LWS_WRITE_HTTP /* HTTP mode */ +#endif + +#define WSL_SEND_TEXT WSL_SEND_TEXT + +} wsl_sendmode_t; + + +/* + * logging levels + */ + +#ifndef WEBSOCKETS_OLD + +typedef enum { + WSL_LOG_NONE = 0x0, + WSL_LOG_ERROR = LLL_ERR, + WSL_LOG_WARNING = LLL_WARN, + WSL_LOG_INFO = LLL_INFO, + WSL_LOG_DEBUG = LLL_DEBUG, + WSL_LOG_ALL = LLL_ERR | LLL_WARN | LLL_INFO | LLL_DEBUG, + WSL_LOG_PARSER = LLL_PARSER, + WSL_LOG_HEADER = LLL_HEADER, + WSL_LOG_EXT = LLL_EXT, + WSL_LOG_CLIENT = LLL_CLIENT, + WSL_LOG_EXTRA = LLL_PARSER | LLL_HEADER | LLL_EXT | LLL_CLIENT, + WSL_LOG_VERBOSE = WSL_LOG_ALL | WSL_LOG_EXTRA +} wsl_loglevel_t; + +#else /* !WEBSOCKETS_OLD */ + +typedef enum { + WSL_LOG_NONE = 0x0, + WSL_LOG_ERROR = 0x0, + WSL_LOG_WARNING = 0x0, + WSL_LOG_INFO = 0x0, + WSL_LOG_DEBUG = 0x0, + WSL_LOG_ALL = 0x0, + WSL_LOG_PARSER = 0x0, + WSL_LOG_HEADER = 0x0, + WSL_LOG_EXT = 0x0, + WSL_LOG_CLIENT = 0x0, + WSL_LOG_EXTRA = 0x0, + WSL_LOG_VERBOSE = 0x0, +} wsl_loglevel_t; + +#endif /* !WEBSOCKETS_OLD */ + +typedef enum { + WSL_NO_SSL = 0, /* plain connection, no SSL */ + WSL_SSL = 1, /* SSL, deny self-signed certs */ + WSL_SSL_SELFSIGNED = 2, /* SSL, allow self-signed certs */ +} wsl_ssl_t; + + +/* + * websockets context configuration + */ + +#define WSL_NO_GID -1 +#define WSL_NO_UID -1 + +typedef struct { + struct sockaddr *addr; /* address/port to listen on */ + wsl_proto_t *protos; /* protocols to serve */ + int nproto; /* number of protocols */ + const char *ssl_cert; /* SSL certificate path */ + const char *ssl_pkey; /* SSL private key path */ + const char *ssl_ca; /* SSL CA path */ + const char *ssl_ciphers; /* SSL cipher list */ + int gid; /* group ID to change to, or -1 */ + int uid; /* user ID to change to, or -1 */ + void *user_data; /* opaque user data */ + int timeout; /* keepalive timeout */ + int nprobe; /* number of keepalive probes */ + int interval; /* keepalive probe interval */ +} wsl_ctx_cfg_t; + + +/** Set libwebsock logging level _and_ redirect to murphy logging infra. */ +void wsl_set_loglevel(wsl_loglevel_t mask); + +/** Create a websocket context. */ +wsl_ctx_t *wsl_create_context(mrp_mainloop_t *ml, wsl_ctx_cfg_t *cfg); + +/** Add a reference to a context. */ +wsl_ctx_t *wsl_ref_context(wsl_ctx_t *ctx); + +/** Remove a context reference, destroying it once the last is gone. */ +int wsl_unref_context(wsl_ctx_t *ctx); + +/** Create a new websocket connection using a given protocol. */ +wsl_sck_t *wsl_connect(wsl_ctx_t *ctx, struct sockaddr *sa, + const char *protocol, wsl_ssl_t ssl, void *user_data); + +/** Accept a pending connection. */ +wsl_sck_t *wsl_accept_pending(wsl_ctx_t *ctx, void *user_data); + +/** Reject a pending connection. */ +void wsl_reject_pending(wsl_ctx_t *ctx); + +/** Close a websocket connection. Return user_data of the associated context. */ +void *wsl_close(wsl_sck_t *sck); + +/** Set websocket write mode (binary or text). */ +int wsl_set_sendmode(wsl_sck_t *sck, wsl_sendmode_t mode); + +/** Send data over a wbesocket. */ +int wsl_send(wsl_sck_t *sck, void *payload, size_t size); + +/** Serve the given file over the given socket. */ +int wsl_serve_http_file(wsl_sck_t *sck, const char *path, const char *mime); + +MRP_CDECL_END + +#endif /* __MURPHY_WEBSOCKLIB_H__ */ diff --git a/src/common/wsck-transport.c b/src/common/wsck-transport.c new file mode 100644 index 0000000..fe44271 --- /dev/null +++ b/src/common/wsck-transport.c @@ -0,0 +1,995 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <netdb.h> +#include <fcntl.h> +#include <limits.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <arpa/inet.h> + +#include <libwebsockets.h> + +#include <murphy/common/macros.h> +#include <murphy/common/mm.h> +#include <murphy/common/list.h> +#include <murphy/common/log.h> +#include <murphy/common/transport.h> +#include <murphy/common/json.h> + +#include "websocklib.h" +#include "wsck-transport.h" + +#define WSCKP "wsck" /* websocket transport prefix */ +#define WSCKL 4 /* websocket transport prefix length */ + + +/* + * a websocket transport instance + */ + +typedef struct { + MRP_TRANSPORT_PUBLIC_FIELDS; /* common transport fields */ + wsl_ctx_t *ctx; /* websocket context */ + wsl_sck_t *sck; /* websocket instance */ + int send_mode; /* websocket send mode */ + const char *http_root; /* HTTP content root */ + mrp_wsck_urimap_t *uri_table; /* URI-to-path table */ + mrp_wsck_mimemap_t *mime_table; /* suffix to MIME-type table */ + const char *ssl_cert; /* path to SSL certificate */ + const char *ssl_pkey; /* path to SSL private key */ + const char *ssl_ca; /* path to SSL CA */ + wsl_ssl_t ssl; /* SSL mode (wsl_ssl_t) */ + char *protocol; /* websocket protocol name */ + wsl_proto_t proto[2]; /* protocol setup */ + mrp_list_hook_t http_clients; /* pure HTTP clients */ +} wsck_t; + + +/* + * a pure HTTP client instance + */ + +typedef struct { + wsl_sck_t *sck; /* websocket towards client */ + mrp_list_hook_t hook; /* hook to listening socket */ + const char *http_root; /* HTTP content root */ + mrp_wsck_urimap_t *uri_table; /* URI to path mapping */ + mrp_wsck_mimemap_t *mime_table; /* suffix to MIME type mapping */ +} http_client_t; + + +/* + * default file suffix to MIME type mapping table + */ + +static mrp_wsck_mimemap_t mime_table[] = { + { "js" , "application/javascript" }, + { "html", "text/html" }, + { "htm ", "text/html" }, + { "txt" , "text/plain" }, + { NULL, NULL } +}; + + +static int resolve_address(const char *str, mrp_wsckaddr_t *wa, socklen_t alen); + +static void connection_cb(wsl_ctx_t *ctx, char *addr, const char *protocol, + void *user_data, void *proto_data); +static void closed_cb(wsl_sck_t *sck, int error, void *user_data, + void *proto_data); +static void recv_cb(wsl_sck_t *sck, void *data, size_t size, void *user_data, + void *proto_data); +static int check_cb(wsl_sck_t *sck, void *user_data, void *proto_data); + +static void http_connection_cb(wsl_ctx_t *ctx, char *addr, const char *protocol, + void *user_data, void *proto_data); +static void http_closed_cb(wsl_sck_t *sck, int error, void *user_data, + void *proto_data); +static void http_req_cb(wsl_sck_t *sck, void *data, size_t size, + void *user_data, void *proto_data); +static int http_check_cb(wsl_sck_t *sck, void *user_data, void *proto_data); +static void http_done_cb(wsl_sck_t *sck, const char *uri, void *user_data, + void *proto_data); + +static socklen_t wsck_resolve(const char *str, mrp_sockaddr_t *addr, + socklen_t size, const char **typep) +{ + mrp_wsckaddr_t *wa = (mrp_wsckaddr_t *)addr; + socklen_t len; + + len = resolve_address(str, wa, size); + + if (len <= 0) + return 0; + else { + if (typep != NULL) + *typep = WSCKP; + + return len; + } +} + + +static int wsck_open(mrp_transport_t *mt) +{ + wsck_t *t = (wsck_t *)mt; + + mrp_list_init(&t->http_clients); + wsl_set_loglevel(WSL_LOG_ALL/* | WSL_LOG_EXTRA*/); + + return TRUE; +} + + +static int wsck_createfrom(mrp_transport_t *mt, void *conn) +{ + wsck_t *t = (wsck_t *)mt; + + MRP_UNUSED(conn); + + mrp_list_init(&t->http_clients); + + return FALSE; +} + + +static void wsck_close(mrp_transport_t *mt) +{ + wsck_t *t = (wsck_t *)mt; + wsl_ctx_t *ctx = t->ctx; + wsl_sck_t *sck = t->sck; + void *user_data; + + t->sck = NULL; + t->ctx = NULL; + mrp_free(t->protocol); + t->protocol = NULL; + + user_data = wsl_close(sck); + + if (user_data == t) /* was our associated context */ + wsl_unref_context(ctx); +} + + +static int wsck_setopt(mrp_transport_t *mt, const char *opt, const void *val) +{ + wsck_t *t = (wsck_t *)mt; + int success; + + if (!strcmp(opt, MRP_WSCK_OPT_SENDMODE) && val != NULL) { + if (!strcmp(val, "binary")) + t->send_mode = WSL_SEND_BINARY; + else if (!strcmp(val, "text")) + t->send_mode = WSL_SEND_TEXT; + else + return FALSE; + + if (t->sck != NULL) + return wsl_set_sendmode(t->sck, t->send_mode); + else + return TRUE; + } + + success = TRUE; + + if (!strcmp(opt, MRP_WSCK_OPT_HTTPDIR)) + t->http_root = val; + else if (!strcmp(opt, MRP_WSCK_OPT_MIMEMAP)) + t->mime_table = (void *)val; + else if (!strcmp(opt, MRP_WSCK_OPT_URIMAP)) + t->uri_table = (void *)val; + else if (!strcmp(opt, MRP_WSCK_OPT_SSL_CERT)) + t->ssl_cert = (const char *)val; + else if (!strcmp(opt, MRP_WSCK_OPT_SSL_PKEY)) + t->ssl_pkey = (const char *)val; + else if (!strcmp(opt, MRP_WSCK_OPT_SSL_CA)) + t->ssl_ca = (const char *)val; + else if (!strcmp(opt, MRP_WSCK_OPT_SSL)) + t->ssl = *(wsl_ssl_t *)val; + else + success = FALSE; + + return success; +} + + +static int wsck_bind(mrp_transport_t *mt, mrp_sockaddr_t *addr, + socklen_t addrlen) +{ + wsck_t *t = (wsck_t *)mt; + wsl_proto_t proto[] = { + { + .name = "http", + .cbs = { .connection = http_connection_cb, + .closed = http_closed_cb, + .recv = http_req_cb, + .check = http_check_cb, + .http_done = http_done_cb, + .load_certs = NULL, }, + .framed = FALSE, + .proto_data = NULL + }, + { + .name = "murphy", + .cbs = { .connection = connection_cb, + .closed = closed_cb, + .recv = recv_cb, + .check = check_cb, + .http_done = NULL, + .load_certs = NULL, }, + .framed = FALSE, + .proto_data = NULL + } + }; + wsl_ctx_cfg_t cfg; + mrp_wsckaddr_t *wa; + struct sockaddr *sa; + + if (addr->any.sa_family != MRP_AF_WSCK || addrlen != sizeof(*wa)) + return FALSE; + + if (t->ctx != NULL) + return FALSE; + + wa = (mrp_wsckaddr_t *)addr; + + switch (wa->wsck_addr.family) { + case AF_INET: sa = (struct sockaddr *)&wa->wsck_addr.v4; break; + case AF_INET6: sa = (struct sockaddr *)&wa->wsck_addr.v6; break; + default: + errno = EAFNOSUPPORT; + return FALSE; + } + + if ((t->protocol = mrp_strdup(wa->wsck_proto)) == NULL) + return FALSE; + + t->proto[0] = proto[0]; + t->proto[1] = proto[1]; + + t->proto[1].name = t->protocol; + + mrp_clear(&cfg); + cfg.addr = sa; + cfg.protos = &t->proto[0]; + cfg.nproto = MRP_ARRAY_SIZE(t->proto); + cfg.ssl_cert = t->ssl_cert; + cfg.ssl_pkey = t->ssl_pkey; + cfg.ssl_ca = t->ssl_ca; + cfg.gid = WSL_NO_GID; + cfg.uid = WSL_NO_UID; + cfg.user_data = t; + + t->ctx = wsl_create_context(t->ml, &cfg); + + if (t->ctx != NULL) + return TRUE; + else + return FALSE; +} + + +static int wsck_listen(mrp_transport_t *mt, int backlog) +{ + MRP_UNUSED(mt); + MRP_UNUSED(backlog); + + mt->listened = TRUE; + + return TRUE; +} + + +static int wsck_accept(mrp_transport_t *mt, mrp_transport_t *mlt) +{ + wsck_t *lt = (wsck_t *)mlt; + wsck_t *t = (wsck_t *)mt; + + t->sck = wsl_accept_pending(lt->ctx, t); + + if (t->sck != NULL) { + mrp_debug("accepted websocket connection %p", mlt); + + /* default to mode inherited from listening transport */ + t->send_mode = lt->send_mode; + wsl_set_sendmode(t->sck, t->send_mode); + + /* inherit pure HTTP settings by default */ + t->http_root = lt->http_root; + t->uri_table = lt->uri_table; + t->mime_table = lt->mime_table; + + return TRUE; + } + else { + mrp_debug("failed to accept websocket connection on %p", mlt); + + return FALSE; + } +} + + +static int wsck_connect(mrp_transport_t *mt, mrp_sockaddr_t *addr, + socklen_t addrlen) +{ + wsck_t *t = (wsck_t *)mt; + wsl_proto_t proto = { + .name = "murphy", + .cbs = { .connection = connection_cb, + .closed = closed_cb, + .recv = recv_cb, + .check = check_cb, }, + .framed = FALSE, + .proto_data = NULL + }; + + wsl_ctx_cfg_t cfg; + mrp_wsckaddr_t *wa; + struct sockaddr *sa; + if (addr->any.sa_family != MRP_AF_WSCK || addrlen != sizeof(*wa)) + return FALSE; + + if (t->ctx != NULL) + return FALSE; + + wa = (mrp_wsckaddr_t *)addr; + + switch (wa->wsck_addr.family) { + case AF_INET: sa = (struct sockaddr *)&wa->wsck_addr.v4; break; + case AF_INET6: sa = (struct sockaddr *)&wa->wsck_addr.v6; break; + default: + errno = EAFNOSUPPORT; + return FALSE; + } + + if ((t->protocol = mrp_strdup(wa->wsck_proto)) == NULL) + return FALSE; + + proto.name = t->protocol; + t->proto[0] = proto; + + mrp_clear(&cfg); + cfg.addr = NULL; + cfg.protos = &t->proto[0]; + cfg.nproto = 1; + cfg.ssl_cert = t->ssl_cert; + cfg.ssl_pkey = t->ssl_pkey; + cfg.ssl_ca = t->ssl_ca; + cfg.gid = WSL_NO_GID; + cfg.uid = WSL_NO_UID; + cfg.user_data = t; + + t->ctx = wsl_create_context(t->ml, &cfg); + + if (t->ctx == NULL) + return FALSE; + + t->sck = wsl_connect(t->ctx, sa, t->protocol, t->ssl, t); + + if (t->sck != NULL) { + t->connected = TRUE; + + return TRUE; + } + else { + wsl_unref_context(t->ctx); + t->ctx = NULL; + } + + return FALSE; +} + + +static int wsck_disconnect(mrp_transport_t *mt) +{ + wsck_t *t = (wsck_t *)mt; + wsl_ctx_t *ctx = t->ctx; + wsl_sck_t *sck = t->sck; + void *user_data; + + t->sck = NULL; + t->ctx = NULL; + + user_data = wsl_close(sck); + + if (user_data == t) /* was our associated context */ + wsl_unref_context(ctx); + + return TRUE; +} + + +static int wsck_send(mrp_transport_t *mt, mrp_msg_t *msg) +{ + wsck_t *t = (wsck_t *)mt; + void *buf; + ssize_t size; + int success; + + size = mrp_msg_default_encode(msg, &buf); + + if (wsl_send(t->sck, buf, size)) + success = TRUE; + else + success = FALSE; + + mrp_free(buf); + + return success; +} + + +static int wsck_sendraw(mrp_transport_t *mt, void *data, size_t size) +{ + wsck_t *t = (wsck_t *)mt; + + return wsl_send(t->sck, data, size); +} + + +static int wsck_senddata(mrp_transport_t *mt, void *data, uint16_t tag) +{ + wsck_t *t = (wsck_t *)mt; + mrp_data_descr_t *type; + void *buf; + size_t size, reserve; + uint16_t *tagp; + int status; + + type = mrp_msg_find_type(tag); + + if (type != NULL) { + reserve = sizeof(*tagp); + size = mrp_data_encode(&buf, data, type, reserve); + + if (size > 0) { + tagp = buf; + *tagp = htobe16(tag); + + status = wsl_send(t->sck, buf, size); + + mrp_free(buf); + return status; + } + } + + return FALSE; +} + + +static int wsck_sendcustom(mrp_transport_t *mt, void *data) +{ + wsck_t *t = (wsck_t *)mt; + mrp_json_t *json = (mrp_json_t *)data; + const char *s; + int status; + + s = mrp_json_object_to_string(json); + + /* + * Notes: + * Although json-c internally counts the length of the serialized + * object, it does not provide an API to get it out together with + * the string. Great... + */ + + if (s != NULL) + status = wsl_send(t->sck, (void *)s, strlen(s)); + else + status = FALSE; + + return status; +} + + +static inline int looks_ipv4(const char *p) +{ + if (isdigit(p[0])) { + if (p[1] == '.') + return TRUE; + + if (isdigit(p[1])) { + if (p[2] == '.') + return TRUE; + + if (isdigit(p[2])) { + if (p[3] == '.') + return TRUE; + } + } + } + + return FALSE; +} + + +static int resolve_address(const char *str, mrp_wsckaddr_t *wa, socklen_t alen) +{ + struct addrinfo *ai, hints; + const char *node, *port, *proto; + char nbuf[256], pbuf[32]; + int family, status; + size_t len; + + if (strncmp(str, WSCKP":", WSCKL + 1) != 0) + return 0; + else + str += WSCKL + 1; + + node = (char *)str; + + if (node[0] == '[') { + node++; + family = AF_INET6; + port = strchr(node, ']'); + } + else if (looks_ipv4(node)) { + family = AF_INET; + port = strchr(node, ':'); + } + else { + family = AF_UNSPEC; + port = strrchr(node, ':'); + } + + if (port == NULL || (*port != ':' && *port != ']')) { + errno = EINVAL; + return -1; + } + + len = port - node; + + if (len > sizeof(nbuf) - 1) { + errno = EOVERFLOW; + return -1; + } + + strncpy(nbuf, node, len); + nbuf[len] = '\0'; + + if (*port == ']') + port++; + + if (*port != ':') { + errno = EINVAL; + return -1; + } + + port++; + proto = strchr(port, '/'); + + if (proto != NULL) { + len = proto - port; + + if (len > sizeof(pbuf) - 1) { + errno = EOVERFLOW; + return -1; + } + + strncpy(pbuf, port, len); + pbuf[len] = '\0'; + + proto++; + if (strlen(proto) > sizeof(wa->wsck_proto) - 1) { + errno = EOVERFLOW; + return -1; + } + } + else { + proto = MRP_WSCK_DEFPROTO; + len = strlen(port); + + if (len > sizeof(pbuf) - 1) { + errno = EOVERFLOW; + return -1; + } + + strcpy(pbuf, port); + } + + mrp_clear(&hints); + hints.ai_family = family; + + status = getaddrinfo(nbuf, pbuf, &hints, &ai); + + switch (status) { + case 0: + if (ai->ai_addrlen <= alen) { + wa->wsck_family = MRP_AF_WSCK; + memcpy(&wa->wsck_addr, ai->ai_addr, ai->ai_addrlen); + strcpy(wa->wsck_proto, proto); + + len = sizeof(*wa); + } + else { + errno = EOVERFLOW; + len = -1; + } + + freeaddrinfo(ai); + return len; + +#define MAP_ERROR(ai_err, err) \ + case EAI_##ai_err: \ + errno = err; \ + return -1 + + MAP_ERROR(AGAIN , EAGAIN); + MAP_ERROR(BADFLAGS , EADDRNOTAVAIL); + MAP_ERROR(FAIL , EHOSTUNREACH); + MAP_ERROR(FAMILY , EPFNOSUPPORT); + MAP_ERROR(MEMORY , ENOMEM); + MAP_ERROR(NONAME , EHOSTUNREACH); + MAP_ERROR(SERVICE , EAFNOSUPPORT); + MAP_ERROR(SOCKTYPE , EHOSTUNREACH); + MAP_ERROR(SYSTEM , EHOSTUNREACH); +#ifdef EAI_ADDRFAMILY + MAP_ERROR(ADDRFAMILY, EHOSTUNREACH); +#endif +#ifdef EAI_NODATA + MAP_ERROR(NODATA , EHOSTUNREACH); +#endif + + default: + errno = EHOSTUNREACH; + } + + return -1; +} + + +#if 0 +static int print_address(char *buf, size_t size, mrp_wsckaddr_t *wa) +{ + struct sockaddr *saddr; + socklen_t salen; + char nbuf[256], pbuf[32], *b, *e; + int status; + + if (wa->wsck_family != MRP_AF_WSCK) { + invalid: + errno = EINVAL; + return -1; + } + + switch (wa->wsck_addr.family) { + case AF_INET: + saddr = (struct sockaddr *)&wa->wsck_addr.v4; + salen = sizeof(wa->wsck_addr.v4); + b = ""; + e = ""; + break; + case AF_INET6: + saddr = (struct sockaddr *)&wa->wsck_addr.v6; + salen = sizeof(wa->wsck_addr.v6); + b = "["; + e = "]"; + break; + default: + goto invalid; + } + + status = getnameinfo(saddr, salen, nbuf, sizeof(nbuf), pbuf, sizeof(pbuf), + NI_NUMERICHOST | NI_NUMERICSERV); + + if (status == 0) + return snprintf(buf, size, "wsck:%s%s%s:%s/%s", + b, nbuf, e, pbuf, wa->wsck_proto); + else { + printf("error: %d: %s\n", status, gai_strerror(status)); + + errno = EINVAL; + return -1; + } +} +#endif + +static void connection_cb(wsl_ctx_t *ctx, char *addr, const char *protocol, + void *user_data, void *proto_data) +{ + wsck_t *t = (wsck_t *)user_data; + + MRP_UNUSED(addr); + MRP_UNUSED(proto_data); + + mrp_debug("incoming connection (%s) for context %p", protocol, ctx); + + if (t->listened) { + MRP_TRANSPORT_BUSY(t, { + t->evt.connection((mrp_transport_t *)t, t->user_data); + }); + } + else + mrp_log_error("connection attempt on non-listened transport %p", t); +} + + +static void closed_cb(wsl_sck_t *sck, int error, void *user_data, + void *proto_data) +{ + wsck_t *t = (wsck_t *)user_data; + + MRP_UNUSED(proto_data); + + mrp_debug("websocket %p closed", sck); + + if (t->evt.closed != NULL) + MRP_TRANSPORT_BUSY(t, { + t->evt.closed((mrp_transport_t *)t, error, t->user_data); + }); +} + + +static void recv_cb(wsl_sck_t *sck, void *data, size_t size, void *user_data, + void *proto_data) +{ + wsck_t *t = (wsck_t *)user_data; + + MRP_UNUSED(proto_data); + + mrp_debug("%zu bytes on websocket %p", size, sck); + + MRP_TRANSPORT_BUSY(t, { + if (t->mode != MRP_TRANSPORT_MODE_CUSTOM) + t->recv_data((mrp_transport_t *)t, data, size, NULL, 0); + else { + mrp_json_t *json = mrp_json_string_to_object(data, size); + + if (json != NULL) { + t->recv_data((mrp_transport_t *)t, json, 0, NULL, 0); + mrp_json_unref(json); + } + } + }); +} + + +static int check_cb(wsl_sck_t *sck, void *user_data, void *proto_data) +{ + wsck_t *t = (wsck_t *)user_data; + + MRP_UNUSED(proto_data); + + mrp_debug("checking if transport %p (%p) has been destroyed", t, sck); + + if (t != NULL) { + if (t->check_destroy((mrp_transport_t *)t)) { + mrp_debug("transport has been destroyed"); + return TRUE; + } + else + mrp_debug("transport has not been destroyed"); + } + + return FALSE; +} + + +static http_client_t *http_create_client(wsck_t *lt) +{ + http_client_t *c; + + c = mrp_allocz(sizeof(*c)); + + if (c != NULL) { + mrp_list_init(&c->hook); + c->sck = wsl_accept_pending(lt->ctx, c); + + if (c->sck != NULL) { + c->http_root = lt->http_root; + c->uri_table = lt->uri_table; + c->mime_table = lt->mime_table; + + return c; + } + else { + mrp_free(c); + c = NULL; + } + } + + return c; +} + + +static void http_destroy_client(http_client_t *c) +{ + if (c != NULL) { + mrp_list_delete(&c->hook); + wsl_close(c->sck); + mrp_free(c); + } +} + + +const char *http_mapuri(http_client_t *c, const char *uri, + char *buf, size_t size) +{ + mrp_wsck_urimap_t *um; + mrp_wsck_mimemap_t *mm; + const char *suff, *root, *r, *s; + + root = c->http_root ? c->http_root : "/"; + + if (c->uri_table != NULL) { + for (um = c->uri_table; um->uri != NULL; um++) { + if (!strcmp(uri, um->uri)) { + if (um->path[0] != '/') { + r = root; + s = "/"; + } + else { + r = ""; + s = ""; + } + + if (snprintf(buf, size, "%s%s%s", r, s, um->path) < (int)size) + return um->type; + else + return NULL; + } + } + } + + if (c->http_root != NULL) { + if (snprintf(buf, size, "%s/%s", root, uri) >= (int)size) + return NULL; + + suff = strrchr(uri, '.'); + + if (suff == NULL) + return "text/plain"; + else + suff++; + + if (c->mime_table != NULL) { + for (mm = c->mime_table; mm->suffix != NULL; mm++) { + if (!strcmp(mm->suffix, suff)) + return mm->type; + } + } + + for (mm = mime_table; mm->suffix != NULL; mm++) { + if (!strcmp(mm->suffix, suff)) + return mm->type; + } + } + + return NULL; +} + + +static void http_connection_cb(wsl_ctx_t *ctx, char *addr, const char *protocol, + void *user_data, void *proto_data) +{ + wsck_t *t = (wsck_t *)user_data; + http_client_t *c; + + MRP_UNUSED(addr); + MRP_UNUSED(proto_data); + + mrp_debug("incoming %s connection for context %p", protocol, ctx); + + if (t->http_root != NULL || t->uri_table != NULL) { + c = http_create_client(t); + + if (c != NULL) + mrp_debug("accepted pure HTTP client for context %p", ctx); + else + mrp_log_error("failed to create new HTTP client"); + } + else + mrp_debug("rejecting pure HTTP client for context %p", ctx); +} + + +static void http_closed_cb(wsl_sck_t *sck, int error, void *user_data, + void *proto_data) +{ + http_client_t *c = (http_client_t *)user_data; + + MRP_UNUSED(proto_data); + MRP_UNUSED(error); + + if (error) + mrp_debug("HTTP client socket %p closed with error %d", sck, error); + else + mrp_debug("HTTP client socket %p closed", sck); + + http_destroy_client(c); +} + + +static void http_req_cb(wsl_sck_t *sck, void *data, size_t size, + void *user_data, void *proto_data) +{ + http_client_t *c = (http_client_t *)user_data; + const char *uri = (const char *)data; + const char *type; + char path[PATH_MAX]; + + MRP_UNUSED(size); + MRP_UNUSED(proto_data); + + mrp_debug("HTTP request for URI '%s' on socket %p", uri, c->sck); + + type = http_mapuri(c, uri, path, sizeof(path)); + + if (type != NULL) { + mrp_debug("mapped to '%s' (%s)", path, type); + wsl_serve_http_file(sck, path, type); + } + else + mrp_debug("failed to map URI"); +} + + +static int http_check_cb(wsl_sck_t *sck, void *user_data, void *proto_data) +{ + http_client_t *c = (http_client_t *)user_data; + + MRP_UNUSED(c); + MRP_UNUSED(sck); + MRP_UNUSED(user_data); + MRP_UNUSED(proto_data); + + return FALSE; +} + + +static void http_done_cb(wsl_sck_t *sck, const char *uri, void *user_data, + void *proto_data) +{ + http_client_t *c = (http_client_t *)user_data; + + MRP_UNUSED(proto_data); + + mrp_debug("HTTP request for '%s' done, closing socket %p.", uri, sck); + + http_destroy_client(c); +} + + +MRP_REGISTER_TRANSPORT(wsck, WSCKP, wsck_t, wsck_resolve, + wsck_open, wsck_createfrom, wsck_close, wsck_setopt, + wsck_bind, wsck_listen, wsck_accept, + wsck_connect, wsck_disconnect, + wsck_send, NULL, + wsck_sendraw, NULL, + wsck_senddata, NULL, + wsck_sendcustom, NULL, + NULL, NULL, + NULL, NULL); diff --git a/src/common/wsck-transport.h b/src/common/wsck-transport.h new file mode 100644 index 0000000..c35f29f --- /dev/null +++ b/src/common/wsck-transport.h @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MURPHY_WEBSOCKET_TRANSPORT_H__ +#define __MURPHY_WEBSOCKET_TRANSPORT_H__ + +#include <murphy/common/macros.h> +#include <murphy/common/transport.h> + +MRP_CDECL_BEGIN + +#define MRP_AF_WSCK 0xDC /* stolen address family */ + + +/* + * websocket transport address + */ + +#define MRP_WSCKADDR_BASE \ + __SOCKADDR_COMMON(wsck_); /* wsck_family: MRP_AF_WSCK */ \ + union { /* websocket address */ \ + sa_family_t family; \ + struct sockaddr_in v4; \ + struct sockaddr_in6 v6; \ + } wsck_addr \ + +typedef struct { + MRP_WSCKADDR_BASE; +} _mrp_wsckaddr_base_t; + + +#define MRP_WSCK_DEFPROTO "murphy" +#define MRP_WSCK_PROTOLEN (MRP_SOCKADDR_SIZE - sizeof(_mrp_wsckaddr_base_t)) + + +typedef struct { + MRP_WSCKADDR_BASE; /* websocket address */ + char wsck_proto[MRP_WSCK_PROTOLEN]; /* websocket protocol */ +} mrp_wsckaddr_t; + + +/* + * websocket transport options and values + */ + +#define MRP_WSCK_OPT_SENDMODE "send-mode" /* sendmode option name */ +#define MRP_WSCK_SENDMODE_TEXT "text" /* sendmode text option */ +#define MRP_WSCK_SENDMODE_BINARY "binary" /* sendmode blob option */ + + +#define MRP_WSCK_OPT_HTTPDIR "http-dir" /* HTTP content root */ +#define MRP_WSCK_OPT_MIMEMAP "mime-map" /* suffix-MIME table */ +#define MRP_WSCK_OPT_URIMAP "uri-map" /* URI-path table */ +#define MRP_WSCK_OPT_SSL_CERT "ssl-cert" /* path to SSL certificate */ +#define MRP_WSCK_OPT_SSL_PKEY "ssl-pkey" /* path to SSL priv. key */ +#define MRP_WSCK_OPT_SSL_CA "ssl-ca" /* path to SSL CA */ +#define MRP_WSCK_OPT_SSL "ssl" /* whether to connect with SSL */ + +/* + * It is also possible to serve content over HTTP on a websocket transport. + * + * This is primarily intended for serving javascript API libraries to + * clients talking to you via the same websocket transport. The served + * libraries hide the details of the underlying communication protocol + * and present a more developer-friendly conventional javascript API. + * + * Currently the websocket transport provides two mechanisms for + * configuring HTTP content serving. + * + * 1) You can put all the files you're willing to expose via HTTP to a + * dedicated directory and configure it to the transport as the + * MRP_WSCK_OPT_HTTPROOT option. If you serve any other types of + * files than HTML (*.htm, *.html), javascript (*.js), or text + * (*.txt) files than you should also push down a table to map + * the extra file suffices to MIME types. You can do this using + * the MRP_SCK_OPT_MIMEMAP transport option. + * + * 2) You can use a mapping table that maps URIs to file path / mime + * type pairs. You can push this table down to the transport as + * the MRP_WSCK_URIMAP transport option. + * + * HTTPROOT takes a char *, URIMAP takes a mrp_wsck_urimap_t *, and + * MIMEMAP takes a mrp_wsck_mimemap_t * as their values. Both URI + * and MIME type tables need to be NULL-terminated. If you set both + * HTTPROOT and URIMAP, URIMAP entries with relative path names will + * be treated relative to HTTPROOT. + * + * Notes: + * + * If you push down any of these options, the websocket backend + * will use the provided values as such __without__ making an + * internal copy. IOW, you better make sure that the passed values + * are valid throughout the full lifetime of the transport (and + * if that is a transport you listen on also the lifetime of all + * transports accepted on that transport) otherwise you'll end up + * with severe memory corruption. + * + */ + +typedef struct { + const char *uri; /* exported URI */ + const char *path; /* path to file */ + const char *type; /* MIME type to use */ +} mrp_wsck_urimap_t; + +typedef struct { + const char *suffix; /* filename suffix */ + const char *type; /* MIME type */ +} mrp_wsck_mimemap_t; + + + + +MRP_CDECL_END + +#endif /* __MURPHY_WEBSOCKET_TRANSPORT_H__ */ |