diff --git a/CMakeLists.txt b/CMakeLists.txt index 31824282f..ba7dac783 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ option(LWS_WITH_DISTRO_RECOMMENDED "Enable features recommended for distro packa # option(LWS_ROLE_H1 "Compile with support for http/1 (needed for ws)" ON) option(LWS_ROLE_WS "Compile with support for websockets" ON) +option(LWS_ROLE_DBUS "Compile with support for DBUS" OFF) option(LWS_WITH_HTTP2 "Compile with server support for HTTP/2" OFF) option(LWS_WITH_LWSWS "Libwebsockets Webserver" OFF) option(LWS_WITH_CGI "Include CGI (spawn process with network-connected stdin/out/err) APIs" OFF) @@ -129,6 +130,7 @@ if(LWS_WITH_DISTRO_RECOMMENDED) # libev + libevent cannot coexist at build-time set(LWS_WITH_LIBEVENT 0) set(LWS_WITHOUT_EXTENSIONS 0) + set(LWS_ROLE_DBUS 1) endif() if(NOT DEFINED CMAKE_BUILD_TYPE) @@ -681,7 +683,44 @@ CHECK_INCLUDE_FILE(sys/capability.h LWS_HAVE_SYS_CAPABILITY_H) CHECK_INCLUDE_FILE(malloc.h LWS_HAVE_MALLOC_H) CHECK_INCLUDE_FILE(pthread.h LWS_HAVE_PTHREAD_H) -CHECK_LIBRARY_EXISTS(cap cap_set_flag "" LWS_HAVE_LIBCAP) +CHECK_LIBRARY_EXISTS(cap cap_set_flag "" LWS_HAVE_LIBCAP) + +if (LWS_ROLE_DBUS) + if (NOT LWS_HAVE_LIBDBUS) + message(FATAL_ERROR "Install dbus-devel, or libdbus-1-dev etc") + endif() + + if (NOT LWS_DBUS_LIB) + set(LWS_DBUS_LIB "dbus-1") + endif() + + CHECK_LIBRARY_EXISTS(${LWS_DBUS_LIB} dbus_connection_set_watch_functions "" LWS_HAVE_LIBDBUS) + + if (NOT LWS_DBUS_INCLUDE1) + # look in fedora and debian / ubuntu place + if (EXISTS "/usr/include/dbus-1.0") + set(LWS_DBUS_INCLUDE1 "/usr/include/dbus-1.0") + else() + message(FATAL_ERROR "Set LWS_DBUS_INCLUDE1 to /usr/include/dbus-1.0 or wherever the main dbus includes are") + endif() + endif() + + if (NOT LWS_DBUS_INCLUDE2) + # look in fedora... debian / ubuntu has the ARCH in the path... + if (EXISTS "/usr/lib64/dbus-1.0/include") + set(LWS_DBUS_INCLUDE2 "/usr/lib64/dbus-1.0/include") + else() + message(FATAL_ERROR "Set LWS_DBUS_INCLUDE2 to /usr/lib/ARCH-linux-gnu/dbus-1.0/include or wherever dbus-arch-deps.h is on your system") + endif() + endif() + + set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES};${LWS_DBUS_INCLUDE1};${LWS_DBUS_INCLUDE2}) + + CHECK_C_SOURCE_COMPILES("#include + int main(void) { + return 0; + }" LWS_DBUS_CHECK_OK) +endif() if (LWS_WITH_LIBUV) CHECK_INCLUDE_FILE(uv-version.h LWS_HAVE_UV_VERSION_H) @@ -811,6 +850,11 @@ if (LWS_ROLE_CGI) lib/roles/cgi/ops-cgi.c) endif() +if (LWS_ROLE_DBUS) + list(APPEND SOURCES + lib/roles/dbus/dbus.c) +endif() + if (LWS_WITH_ACCESS_LOG) list(APPEND SOURCES lib/roles/http/server/access-log.c) @@ -1416,6 +1460,13 @@ if (LWS_WITH_HUBBUB) list(APPEND LIB_LIST ${LIBHUBBUB_LIBRARIES} ) endif() +if (LWS_ROLE_DBUS) + message("dbus include dir 1: ${LWS_DBUS_INCLUDE1}") + message("dbus include dir 2: ${LWS_DBUS_INCLUDE2}") + include_directories("${LWS_DBUS_INCLUDE1}") + include_directories("${LWS_DBUS_INCLUDE2}") + list(APPEND LIB_LIST ${LWS_DBUS_LIB}) +endif() # # Platform specific libs. diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index a8e62901f..6c6d1e3ba 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -13,6 +13,7 @@ #cmakedefine LWS_ROLE_RAW #cmakedefine LWS_ROLE_H2 #cmakedefine LWS_ROLE_CGI +#cmakedefine LWS_ROLE_DBUS /* Define to 1 to use wolfSSL/CyaSSL as a replacement for OpenSSL. * LWS_OPENSSL_SUPPORT needs to be set also for this to work. */ diff --git a/include/libwebsockets/lws-dbus.h b/include/libwebsockets/lws-dbus.h new file mode 100644 index 000000000..63cfd15f1 --- /dev/null +++ b/include/libwebsockets/lws-dbus.h @@ -0,0 +1,90 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * must be included manually as + * + * #include + * + * if dbus apis needed + */ + +#if !defined(__LWS_DBUS_H__) +#define __LWS_DBUS_H__ + +#include + +/* helper type to simplify implementing methods as individual functions */ +typedef DBusHandlerResult (*lws_dbus_message_handler)(DBusConnection *conn, + DBusMessage *message, DBusMessage **reply, void *d); + +struct lws_dbus_ctx; +typedef void (*lws_dbus_closing_t)(struct lws_dbus_ctx *ctx); + +struct lws_dbus_ctx { + struct lws_dll next; /* dbusserver ctx: HEAD of accepted list */ + struct lws_vhost *vh; /* the vhost we logically bind to in lws */ + int tsi; /* the lws thread service index (0 if only one service + thread as is the default */ + DBusConnection *conn; + DBusServer *dbs; + DBusWatch *w[4]; + DBusPendingCall *pc; + + char hup; + char timeouts; + + /* cb_closing callback will be called after the connection and this + * related ctx struct have effectively gone out of scope. + * + * The callback should close and clean up the connection and free the + * ctx. + */ + lws_dbus_closing_t cb_closing; +}; + +/** + * lws_dbus_connection_setup() - bind dbus connection object to lws event loop + * + * \param ctx: additional information about the connection + * \param conn: the DBusConnection object to bind + * + * This configures a DBusConnection object to use lws for watchers and timeout + * operations. + */ +LWS_VISIBLE LWS_EXTERN int +lws_dbus_connection_setup(struct lws_dbus_ctx *ctx, DBusConnection *conn, + lws_dbus_closing_t cb_closing); + +/** + * lws_dbus_server_listen() - bind dbus connection object to lws event loop + * + * \param ctx: additional information about the connection + * \param ads: the DBUS address to listen on, eg, "unix:abstract=mysocket" + * \param err: a DBusError object to take any extra error information + * \param new_conn: a callback function to prepare new accepted connections + * + * This creates a DBusServer and binds it to the lws event loop, and your + * callback to accept new connections. + */ +LWS_VISIBLE LWS_EXTERN DBusServer * +lws_dbus_server_listen(struct lws_dbus_ctx *ctx, const char *ads, + DBusError *err, DBusNewConnectionFunction new_conn); + +#endif diff --git a/lib/core/context.c b/lib/core/context.c index 70d33d47e..cdd1b87e2 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -34,6 +34,9 @@ const struct lws_role_ops *available_roles[] = { #endif #if defined(LWS_ROLE_WS) &role_ops_ws, +#endif +#if defined(LWS_ROLE_DBUS) + &role_ops_dbus, #endif NULL }; diff --git a/lib/core/libwebsockets.c b/lib/core/libwebsockets.c index f6dfc375b..2d62259b9 100644 --- a/lib/core/libwebsockets.c +++ b/lib/core/libwebsockets.c @@ -962,7 +962,8 @@ __lws_close_free_wsi_final(struct lws *wsi) { int n; - if (lws_socket_is_valid(wsi->desc.sockfd) && !lws_ssl_close(wsi)) { + if (!wsi->shadow && + lws_socket_is_valid(wsi->desc.sockfd) && !lws_ssl_close(wsi)) { lwsl_debug("%s: wsi %p: fd %d\n", __func__, wsi, wsi->desc.sockfd); n = compatible_close(wsi->desc.sockfd); if (n) diff --git a/lib/core/private.h b/lib/core/private.h index 9fe93eac3..c623c24da 100644 --- a/lib/core/private.h +++ b/lib/core/private.h @@ -386,6 +386,9 @@ struct lws_context_per_thread { #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) struct lws_pt_role_http http; #endif +#if defined(LWS_ROLE_DBUS) + struct lws_pt_role_dbus dbus; +#endif /* --- event library based members --- */ @@ -849,6 +852,9 @@ struct lws { #if defined(LWS_ROLE_WS) struct _lws_websocket_related *ws; /* allocated if we upgrade to ws */ #endif +#if defined(LWS_ROLE_DBUS) + struct _lws_dbus_mode_related dbus; +#endif const struct lws_role_ops *role_ops; lws_wsi_state_t wsistate; @@ -962,6 +968,7 @@ struct lws { unsigned int could_have_pending:1; /* detect back-to-back writes */ unsigned int outer_will_close:1; + unsigned int shadow:1; /* we do not control fd lifecycle at all */ #ifdef LWS_WITH_ACCESS_LOG unsigned int access_log_pending:1; diff --git a/lib/roles/dbus/README.md b/lib/roles/dbus/README.md new file mode 100644 index 000000000..7d479da43 --- /dev/null +++ b/lib/roles/dbus/README.md @@ -0,0 +1,83 @@ +# DBUS Role Support + +## DBUS-related distro packages + +Fedora: dbus-devel +Debian / Ubuntu: libdbus-1-dev + +## Enabling for build at cmake + +Fedora example: +``` +$ cmake .. -DLWS_ROLE_DBUS=1 -DLWS_DBUS_INCLUDE2="/usr/lib64/dbus-1.0/include" +``` + +Ubuntu example: +``` +$ cmake .. -DLWS_ROLE_DBUS=1 -DLWS_DBUS_INCLUDE2="/usr/lib/x86_64-linux-gnu/dbus-1.0/include" +``` + +Dbus requires two include paths, which you can force by setting `LWS_DBUS_INCLUDE1` +and `LWS_DBUS_INCLUDE2`. Although INCLUDE1 is usually guessable, both can be +forced to allow cross-build. + +If these are not forced, then lws cmake will try to check some popular places, +for `LWS_DBUS_INCLUDE1`, on both Fedora and Debian / Ubuntu, this is +`/usr/include/dbus-1.0`... if the directory exists, it is used. + +For `LWS_DBUS_INCLUDE2`, it is the arch-specific dbus header which may be +packaged separately than the main dbus headers. On Fedora, this is in +`/usr/lib[64]/dbus-1.0/include`... if not given externally, lws cmake will +try `/usr/lib64/dbus-1.0/include`. On Debian / Ubuntu, the package installs +it in an arch-specific dir like `/usr/lib/x86_64-linux-gnu/dbus-1.0/include`, +you should force the path. + +The library path is usually \[lib\] "dbus-1", but this can also be forced if +you want to build cross or use a special build, via `LWS_DBUS_LIB`. + +## Building against local dbus build + +If you built your own local dbus and installed it in /usr/local, then +this is the incantation to direct lws to use the local version of dbus: + +``` +cmake .. -DLWS_ROLE_DBUS=1 -DLWS_DBUS_INCLUDE1="/usr/local/include/dbus-1.0" -DLWS_DBUS_INCLUDE2="/usr/local/lib/dbus-1.0/include" -DLWS_DBUS_LIB="/usr/local/lib/libdbus-1.so" +``` + +You'll also need to give the loader a helping hand to do what you want if +there's a perfectly good dbus lib already in `/usr/lib[64]` using `LD_PRELOAD` +like this + +``` +LD_PRELOAD=/usr/local/lib/libdbus-1.so.3.24.0 myapp +``` + +## Lws dbus api exports + +Because of the irregular situation with libdbus includes, if lws exports the +dbus helpers, which use dbus types, as usual from `#include ` +then if lws was compiled with dbus role support it forces all users to take +care about the dbus include path mess whether they use dbus themselves or not. + +For that reason, if you need access to the lws dbus apis, you must explicitly +include them by + +``` +#include +``` + +This includes `` and so requires the include paths set up. But +otherwise non-dbus users that don't include `libwebsockets/lws-dbus.h` don't +have to care about it. + +## DBUS and valgrind + +https://cgit.freedesktop.org/dbus/dbus/tree/README.valgrind + +1) One-time 6KiB "Still reachable" caused by abstract unix domain socket + libc +`getgrouplist()` via nss... bug since 2004(!) + +https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=273051 + + + diff --git a/lib/roles/dbus/dbus.c b/lib/roles/dbus/dbus.c new file mode 100644 index 000000000..d509cfd34 --- /dev/null +++ b/lib/roles/dbus/dbus.c @@ -0,0 +1,527 @@ +/* + * libwebsockets - dbus role + * + * Copyright (C) 2010-2018 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * + * This role for wrapping dbus fds in a wsi + role is unusual in that the + * wsi it creates and binds to the role do not have control over the related fd + * lifecycle. In fact dbus doesn't inform us directly about the lifecycle of + * the fds it wants to be managed by the lws event loop. + * + * What it does tell us is when it wants to wait on POLLOUT and / or POLLIN, + * and since it should stop any watchers before close, we take the approach to + * create a lightweight "shadow" wsi for any fd from dbus that has a POLLIN or + * POLLOUT wait active. When the dbus fd asks to have no wait active, we + * destroy the wsi, since this is indistinguishable from dbus close path + * behaviour. If it actually stays alive and later asks to wait again, well no + * worries we create a new shadow wsi until it looks like it is closing again. + */ + +#include + +#include + +/* + * retreives existing or creates new shadow wsi for fd owned by dbus stuff. + * + * Requires vhost lock + */ + +static struct lws * +__lws_shadow_wsi(struct lws_dbus_ctx *ctx, DBusWatch *w, int fd, int create_ok) +{ + struct lws *wsi; + + if (fd < 0 || fd >= (int)ctx->vh->context->fd_limit_per_thread) { + lwsl_err("%s: fd %d vs fds_count %d\n", __func__, fd, + (int)ctx->vh->context->fd_limit_per_thread); + assert(0); + + return NULL; + } + + wsi = wsi_from_fd(ctx->vh->context, fd); + if (wsi) { + assert(wsi->opaque_parent_data == ctx); + + return wsi; + } + + if (!create_ok) + return NULL; + + wsi = lws_zalloc(sizeof(*wsi), "shadow wsi"); + if (wsi == NULL) { + lwsl_err("Out of mem\n"); + return NULL; + } + + lwsl_info("%s: creating shadow wsi\n", __func__); + + wsi->context = ctx->vh->context; + wsi->desc.sockfd = fd; + lws_role_transition(wsi, 0, LRS_ESTABLISHED, &role_ops_dbus); + wsi->protocol = ctx->vh->protocols; + wsi->tsi = ctx->tsi; + wsi->shadow = 1; + wsi->opaque_parent_data = ctx; + ctx->w[0] = w; + + lws_vhost_bind_wsi(ctx->vh, wsi); + if (__insert_wsi_socket_into_fds(ctx->vh->context, wsi)) { + lwsl_err("inserting wsi socket into fds failed\n"); + lws_vhost_unbind_wsi(wsi); + lws_free(wsi); + return NULL; + } + + ctx->vh->context->count_wsi_allocated++; + + return wsi; +} + +/* + * Requires vhost lock + */ + +static int +__lws_shadow_wsi_destroy(struct lws_dbus_ctx *ctx, struct lws *wsi) +{ + lwsl_info("%s: destroying shadow wsi\n", __func__); + + if (__remove_wsi_socket_from_fds(wsi)) { + lwsl_err("%s: unable to remove %d from fds\n", __func__, + wsi->desc.sockfd); + + return 1; + } + + ctx->vh->context->count_wsi_allocated--; + lws_vhost_unbind_wsi(wsi); + + lws_free(wsi); + + return 0; +} + + +static void +handle_dispatch_status(DBusConnection *c, DBusDispatchStatus s, void *data) +{ + lwsl_info("%s: new dbus dispatch status: %d\n", __func__, s); +} + +/* + * These are complicated by the fact libdbus can have two separate DBusWatch + * objects for the same fd, to control watching POLLIN and POLLOUT individually. + * + * However we will actually watch using poll(), where the unit is the fd, and + * it has a unified events field with just POLLIN / POLLOUT flags. + * + * So we have to be prepared for one or two watchers coming in any order. + */ + +static dbus_bool_t +lws_dbus_add_watch(DBusWatch *w, void *data) +{ + struct lws_dbus_ctx *ctx = (struct lws_dbus_ctx *)data; + struct lws_context_per_thread *pt = &ctx->vh->context->pt[ctx->tsi]; + unsigned int flags = 0, lws_flags = 0; + struct lws *wsi; + int n; + + lws_pt_lock(pt, __func__); + + wsi = __lws_shadow_wsi(ctx, w, dbus_watch_get_unix_fd(w), 1); + if (!wsi) { + lws_pt_unlock(pt); + lwsl_err("%s: unable to get wsi\n", __func__); + + return FALSE; + } + + for (n = 0; n < (int)LWS_ARRAY_SIZE(ctx->w); n++) + if (w == ctx->w[n]) + break; + + if (n == (int)LWS_ARRAY_SIZE(ctx->w)) + for (n = 0; n < (int)LWS_ARRAY_SIZE(ctx->w); n++) + if (!ctx->w[n]) { + ctx->w[n] = w; + break; + } + + for (n = 0; n < (int)LWS_ARRAY_SIZE(ctx->w); n++) + if (ctx->w[n]) + flags |= dbus_watch_get_flags(ctx->w[n]); + + if (flags & DBUS_WATCH_READABLE) + lws_flags |= LWS_POLLIN; + if (flags & DBUS_WATCH_WRITABLE) + lws_flags |= LWS_POLLOUT; + + lwsl_info("%s: w %p, fd %d, data %p, flags %d\n", __func__, w, + dbus_watch_get_unix_fd(w), data, lws_flags); + + __lws_change_pollfd(wsi, 0, lws_flags); + + lws_pt_unlock(pt); + + return TRUE; +} + +static int +check_destroy_shadow_wsi(struct lws_dbus_ctx *ctx, struct lws *wsi) +{ + int n; + + if (!wsi) + return 0; + + for (n = 0; n < (int)LWS_ARRAY_SIZE(ctx->w); n++) + if (ctx->w[n]) + return 0; + + __lws_shadow_wsi_destroy(ctx, wsi); + + if (!ctx->conn || !ctx->hup || ctx->timeouts) + return 0; + + if (dbus_connection_get_dispatch_status(ctx->conn) == + DBUS_DISPATCH_DATA_REMAINS) + return 0; + + if (ctx->cb_closing) + ctx->cb_closing(ctx); + + return 1; +} + +static void +lws_dbus_remove_watch(DBusWatch *w, void *data) +{ + struct lws_dbus_ctx *ctx = (struct lws_dbus_ctx *)data; + struct lws_context_per_thread *pt = &ctx->vh->context->pt[ctx->tsi]; + unsigned int flags = 0, lws_flags = 0; + struct lws *wsi; + int n; + + lws_pt_lock(pt, __func__); + + wsi = __lws_shadow_wsi(ctx, w, dbus_watch_get_unix_fd(w), 0); + if (!wsi) + goto bail; + + for (n = 0; n < (int)LWS_ARRAY_SIZE(ctx->w); n++) + if (w == ctx->w[n]) { + ctx->w[n] = NULL; + break; + } + + for (n = 0; n < (int)LWS_ARRAY_SIZE(ctx->w); n++) + if (ctx->w[n]) + flags |= dbus_watch_get_flags(ctx->w[n]); + + if ((~flags) & DBUS_WATCH_READABLE) + lws_flags |= LWS_POLLIN; + if ((~flags) & DBUS_WATCH_WRITABLE) + lws_flags |= LWS_POLLOUT; + + lwsl_info("%s: w %p, fd %d, data %p, clearing lws flags %d\n", + __func__, w, dbus_watch_get_unix_fd(w), data, lws_flags); + + __lws_change_pollfd(wsi, lws_flags, 0); + +bail: + lws_pt_unlock(pt); +} + +static void +lws_dbus_toggle_watch(DBusWatch *w, void *data) +{ + if (dbus_watch_get_enabled(w)) + lws_dbus_add_watch(w, data); + else + lws_dbus_remove_watch(w, data); +} + + +static dbus_bool_t +lws_dbus_add_timeout(DBusTimeout *t, void *data) +{ + struct lws_dbus_ctx *ctx = (struct lws_dbus_ctx *)data; + struct lws_context_per_thread *pt = &ctx->vh->context->pt[ctx->tsi]; + int ms = dbus_timeout_get_interval(t); + struct lws_role_dbus_timer *dbt; + time_t ti = time(NULL); + + if (!dbus_timeout_get_enabled(t)) + return TRUE; + + if (ms < 1000) + ms = 1000; + + dbt = lws_malloc(sizeof(*dbt), "dbus timer"); + if (!dbt) + return FALSE; + + lwsl_info("%s: adding timeout %dms\n", __func__, + dbus_timeout_get_interval(t)); + + dbt->data = t; + dbt->fire = ti + (ms < 1000); + dbt->timer_list.prev = NULL; + dbt->timer_list.next = NULL; + lws_dll_add_front(&dbt->timer_list, &pt->dbus.timer_list_head); + + ctx->timeouts++; + + return TRUE; +} + +static void +lws_dbus_remove_timeout(DBusTimeout *t, void *data) +{ + struct lws_dbus_ctx *ctx = (struct lws_dbus_ctx *)data; + struct lws_context_per_thread *pt = &ctx->vh->context->pt[ctx->tsi]; + + lwsl_info("%s: t %p, data %p\n", __func__, t, data); + + lws_start_foreach_dll_safe(struct lws_dll *, rdt, nx, + pt->dbus.timer_list_head.next) { + struct lws_role_dbus_timer *r = lws_container_of(rdt, + struct lws_role_dbus_timer, timer_list); + if (t == r->data) { + lws_dll_remove(rdt); + lws_free(rdt); + ctx->timeouts--; + break; + } + } lws_end_foreach_dll_safe(rdt, nx); +} + +static void +lws_dbus_toggle_timeout(DBusTimeout *t, void *data) +{ + if (dbus_timeout_get_enabled(t)) + lws_dbus_add_timeout(t, data); + else + lws_dbus_remove_timeout(t, data); +} + +/* + * This sets up a connection along the same lines as + * dbus_connection_setup_with_g_main(), but for using the lws event loop. + */ + +int +lws_dbus_connection_setup(struct lws_dbus_ctx *ctx, DBusConnection *conn, + lws_dbus_closing_t cb_closing) +{ + int n; + + ctx->conn = conn; + ctx->cb_closing = cb_closing; + ctx->hup = 0; + ctx->timeouts = 0; + for (n = 0; n < (int)LWS_ARRAY_SIZE(ctx->w); n++) + ctx->w[n] = NULL; + + if (!dbus_connection_set_watch_functions(conn, lws_dbus_add_watch, + lws_dbus_remove_watch, + lws_dbus_toggle_watch, + ctx, NULL)) { + lwsl_err("%s: dbus_connection_set_watch_functions fail\n", + __func__); + return 1; + } + + if (!dbus_connection_set_timeout_functions(conn, + lws_dbus_add_timeout, + lws_dbus_remove_timeout, + lws_dbus_toggle_timeout, + ctx, NULL)) { + lwsl_err("%s: dbus_connection_set_timeout_functions fail\n", + __func__); + return 1; + } + + dbus_connection_set_dispatch_status_function(conn, + handle_dispatch_status, + ctx, NULL); + + return 0; +} + +/* + * This wraps dbus_server_listen(), additionally taking care of the event loop + * -related setups. + */ + +DBusServer * +lws_dbus_server_listen(struct lws_dbus_ctx *ctx, const char *ads, DBusError *e, + DBusNewConnectionFunction new_conn) +{ + ctx->cb_closing = NULL; + ctx->hup = 0; + ctx->timeouts = 0; + + ctx->dbs = dbus_server_listen(ads, e); + if (!ctx->dbs) + return NULL; + + dbus_server_set_new_connection_function(ctx->dbs, new_conn, ctx, NULL); + + if (!dbus_server_set_watch_functions(ctx->dbs, lws_dbus_add_watch, + lws_dbus_remove_watch, + lws_dbus_toggle_watch, + ctx, NULL)) { + lwsl_err("%s: dbus_connection_set_watch_functions fail\n", + __func__); + goto bail; + } + + if (!dbus_server_set_timeout_functions(ctx->dbs, lws_dbus_add_timeout, + lws_dbus_remove_timeout, + lws_dbus_toggle_timeout, + ctx, NULL)) { + lwsl_err("%s: dbus_connection_set_timeout_functions fail\n", + __func__); + goto bail; + } + + return ctx->dbs; + +bail: + dbus_server_disconnect(ctx->dbs); + dbus_server_unref(ctx->dbs); + + return NULL; +} + + +/* + * There shouldn't be a race here with watcher removal and poll wait, because + * everything including the dbus activity is serialized in one event loop. + * + * If it removes the watcher and we remove the wsi and fd entry before this, + * actually we can no longer map the fd to this invalidated wsi pointer to call + * this. + */ + +static int +rops_handle_POLLIN_dbus(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd) +{ + struct lws_dbus_ctx *ctx = + (struct lws_dbus_ctx *)wsi->opaque_parent_data; + unsigned int flags = 0; + int n; + + if (pollfd->revents & LWS_POLLIN) + flags |= DBUS_WATCH_READABLE; + if (pollfd->revents & LWS_POLLOUT) + flags |= DBUS_WATCH_WRITABLE; + + if (pollfd->revents & (LWS_POLLHUP)) + ctx->hup = 1; + + /* + * POLLIN + POLLOUT gets us called here on the corresponding shadow + * wsi. wsi->opaque_parent_data is the watcher handle bound to the wsi + */ + + for (n = 0; n < (int)LWS_ARRAY_SIZE(ctx->w); n++) + if (ctx->w[n] && !dbus_watch_handle(ctx->w[n], flags)) + lwsl_err("%s: dbus_watch_handle failed\n", __func__); + + if (ctx->conn) { + lwsl_info("%s: conn: flags %d\n", __func__, flags); + + while (dbus_connection_get_dispatch_status(ctx->conn) == + DBUS_DISPATCH_DATA_REMAINS) + dbus_connection_dispatch(ctx->conn); + + handle_dispatch_status(NULL, DBUS_DISPATCH_DATA_REMAINS, NULL); + + check_destroy_shadow_wsi(ctx, wsi); + } else + if (ctx->dbs) + /* ??? */ + lwsl_debug("%s: dbs: %d\n", __func__, flags); + + return LWS_HPI_RET_HANDLED; +} + +static int +rops_periodic_checks_dbus(struct lws_context *context, int tsi, time_t now) +{ + struct lws_context_per_thread *pt = &context->pt[tsi]; + + /* + * locking shouldn't be needed here, because periodic_checks is called + * from the tsi-specific service thread context, and only the same + * service thread can modify stuff on the same pt. + */ + + lws_start_foreach_dll_safe(struct lws_dll *, rdt, nx, + pt->dbus.timer_list_head.next) { + struct lws_role_dbus_timer *r = lws_container_of(rdt, + struct lws_role_dbus_timer, timer_list); + + if (now > r->fire) { + lwsl_notice("%s: firing timer\n", __func__); + dbus_timeout_handle(r->data); + lws_dll_remove(rdt); + lws_free(rdt); + } + } lws_end_foreach_dll_safe(rdt, nx); + + return 0; +} + +struct lws_role_ops role_ops_dbus = { + /* role name */ "dbus", + /* alpn id */ NULL, + /* check_upgrades */ NULL, + /* init_context */ NULL, + /* init_vhost */ NULL, + /* destroy_vhost */ NULL, + /* periodic_checks */ rops_periodic_checks_dbus, + /* service_flag_pending */ NULL, + /* handle_POLLIN */ rops_handle_POLLIN_dbus, + /* handle_POLLOUT */ NULL, + /* perform_user_POLLOUT */ NULL, + /* callback_on_writable */ NULL, + /* tx_credit */ NULL, + /* write_role_protocol */ NULL, + /* encapsulation_parent */ NULL, + /* alpn_negotiated */ NULL, + /* close_via_role_protocol */ NULL, + /* close_role */ NULL, + /* close_kill_connection */ NULL, + /* destroy_role */ NULL, + /* adoption_bind */ NULL, + /* client_bind */ NULL, + /* writeable cb clnt, srv */ { 0, 0 }, + /* close cb clnt, srv */ { 0, 0 }, + /* protocol_bind_cb c,s */ { 0, 0 }, + /* protocol_unbind_cb c,s */ { 0, 0 }, + /* file_handle */ 0, +}; diff --git a/lib/roles/dbus/private.h b/lib/roles/dbus/private.h new file mode 100644 index 000000000..c35b50199 --- /dev/null +++ b/lib/roles/dbus/private.h @@ -0,0 +1,42 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2018 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * This is included from core/private.h if LWS_ROLE_DBUS + */ + +#include + +extern struct lws_role_ops role_ops_dbus; + +#define lwsi_role_dbus(wsi) (wsi->role_ops == &role_ops_dbus) + +struct lws_role_dbus_timer { + struct lws_dll timer_list; + void *data; + time_t fire; +}; + +struct lws_pt_role_dbus { + struct lws_dll timer_list_head; +}; + +struct _lws_dbus_mode_related { + DBusConnection *conn; +}; diff --git a/lib/roles/private.h b/lib/roles/private.h index 26f717786..5f93b86e8 100644 --- a/lib/roles/private.h +++ b/lib/roles/private.h @@ -291,6 +291,12 @@ extern struct lws_role_ops role_ops_raw_skt, role_ops_raw_file, role_ops_listen, #define lwsi_role_cgi(wsi) (0) #endif +#if defined(LWS_ROLE_DBUS) + #include "roles/dbus/private.h" +#else + #define lwsi_role_dbus(wsi) (0) +#endif + enum { LWS_HP_RET_BAIL_OK, LWS_HP_RET_BAIL_DIE, diff --git a/minimal-examples/README.md b/minimal-examples/README.md index 525ad1eaf..d80dece60 100644 --- a/minimal-examples/README.md +++ b/minimal-examples/README.md @@ -1,6 +1,7 @@ |name|demonstrates| ---|--- client-server|Minimal examples providing client and server connections simultaneously +dbus-server|Minimal examples showing how to integrate DBUS into lws event loop http-client|Minimal examples providing an http client http-server|Minimal examples providing an http server raw|Minimal examples related to adopting raw file or socket descriptors into the event loop diff --git a/minimal-examples/dbus-client/README.md b/minimal-examples/dbus-client/README.md new file mode 100644 index 000000000..7951825e7 --- /dev/null +++ b/minimal-examples/dbus-client/README.md @@ -0,0 +1,4 @@ +|Example|Demonstrates| +---|--- +minimal-dbus-client|Shows how to connect to a DBusServer dbus server like minimal-dbus-server + diff --git a/minimal-examples/dbus-client/minimal-dbus-client/CMakeLists.txt b/minimal-examples/dbus-client/minimal-dbus-client/CMakeLists.txt new file mode 100644 index 000000000..674bb096a --- /dev/null +++ b/minimal-examples/dbus-client/minimal-dbus-client/CMakeLists.txt @@ -0,0 +1,120 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) +include(CheckLibraryExists) + +set(SAMP lws-minimal-dbus-client) +set(SRCS minimal-dbus-client.c) + +if (NOT LWS_WITH_MINIMAL_EXAMPLES) + CHECK_LIBRARY_EXISTS(dbus-1 dbus_connection_set_watch_functions "" LWS_HAVE_LIBDBUS) + if (NOT LWS_HAVE_LIBDBUS) + message(FATAL_ERROR "Install dbus-devel, or libdbus-1-dev etc") + endif() + + if (NOT LWS_DBUS_LIB) + set(LWS_DBUS_LIB "dbus-1") + endif() + + if (NOT LWS_DBUS_INCLUDE1) + # look in fedora and debian / ubuntu place + if (EXISTS "/usr/include/dbus-1.0") + set(LWS_DBUS_INCLUDE1 "/usr/include/dbus-1.0") + else() + message(FATAL_ERROR "Set LWS_DBUS_INCLUDE1 to /usr/include/dbus-1.0 or wherever the main dbus includes are") + endif() + endif() + + if (NOT LWS_DBUS_INCLUDE2) + # look in fedora... debian / ubuntu has the ARCH in the path... + if (EXISTS "/usr/lib64/dbus-1.0/include") + set(LWS_DBUS_INCLUDE2 "/usr/lib64/dbus-1.0/include") + else() + message(FATAL_ERROR "Set LWS_DBUS_INCLUDE2 to /usr/lib/ARCH-linux-gnu/dbus-1.0/include or wherever dbus-arch-deps.h is on your system") + endif() + endif() + + set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES};${LWS_DBUS_INCLUDE1};${LWS_DBUS_INCLUDE2}) + + if (NOT LWS_DBUS_INCLUDE1 OR NOT LWS_DBUS_INCLUDE2) + message(FATAL_ERROR "To build with libdbus, LWS_DBUS_INCLUDE1/2 must be given. See lib/roles/dbus/README.md") + endif() + +endif() + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + + endif() +ENDMACRO() + + +set(requirements 1) +require_lws_config(LWS_ROLE_DBUS 1 requirements) +require_lws_config(LWS_WITHOUT_CLIENT 0 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + include_directories("${LWS_DBUS_INCLUDE1}") + include_directories("${LWS_DBUS_INCLUDE2}") + list(APPEND LIB_LIST dbus-1) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared ${LWS_DBUS_LIB}) + else() + target_link_libraries(${SAMP} websockets ${LWS_DBUS_LIB}) + endif() +endif() diff --git a/minimal-examples/dbus-client/minimal-dbus-client/README.md b/minimal-examples/dbus-client/minimal-dbus-client/README.md new file mode 100644 index 000000000..42563c61f --- /dev/null +++ b/minimal-examples/dbus-client/minimal-dbus-client/README.md @@ -0,0 +1,49 @@ +# lws minimal dbus client + +This demonstrates nonblocking, asynchronous dbus method calls as the client. + +## build + +Using libdbus requires additional non-default include paths setting, same as +is necessary for lws build described in ./lib/roles/dbus/README.md + +CMake can guess one path and the library name usually, see the README above +for details of how to override for custom libdbus and cross build. + +Fedora example: +``` +$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib64/dbus-1.0/include" +$ make +``` + +Ubuntu example: +``` +$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib/x86_64-linux-gnu/dbus-1.0/include" +$ make +``` + +## usage + +Commandline option|Meaning +---|--- +-d |Debug verbosity in decimal, eg, -d15 + +The minimal client connects to the minimal dbus server example, which is +expected to be listening on its default abstract unix domain socket path. + +It call the server Echo method with "Hello!" and returns to the event loop. +When the reply comes, it prints the returned message. + +Afterwards it just sits there receiving unsolicited messages from the server +example, until closed by the user. + +``` + $ ./lws-minimal-dbus-client +ctx +[2018/10/05 06:08:31:4901] NOTICE: pending_call_notify +[2018/10/05 06:08:31:4929] USER: pending_call_notify: received 'Hello!' +^C[2018/10/05 06:09:22:4409] NOTICE: destroy_dbus_client_conn +[2018/10/05 06:09:22:4691] NOTICE: Exiting cleanly +... +``` + diff --git a/minimal-examples/dbus-client/minimal-dbus-client/minimal-dbus-client.c b/minimal-examples/dbus-client/minimal-dbus-client/minimal-dbus-client.c new file mode 100644 index 000000000..7e30d13be --- /dev/null +++ b/minimal-examples/dbus-client/minimal-dbus-client/minimal-dbus-client.c @@ -0,0 +1,279 @@ +/* + * lws-minimal-dbus-client + * + * Copyright (C) 2018 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This demonstrates a minimal session dbus server that uses the lws event loop, + * making it possible to integrate it with other lws features. + */ + +#include +#include +#include +#include +#include + +#include +#include + +static struct lws_dbus_ctx *dbus_ctx; +static struct lws_context *context; +static int interrupted; + +#define THIS_INTERFACE "org.libwebsockets.test" +#define THIS_OBJECT "/org/libwebsockets/test" +#define THIS_BUSNAME "org.libwebsockets.test" + +#define THIS_LISTEN_PATH "unix:abstract=org.libwebsockets.test" + + +static DBusHandlerResult +client_message_handler(DBusConnection *conn, DBusMessage *message, void *data) +{ + const char *str; + + lwsl_info("%s: Got D-Bus request: %s.%s on %s\n", __func__, + dbus_message_get_interface(message), + dbus_message_get_member(message), + dbus_message_get_path(message)); + + if (!dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, + &str, DBUS_TYPE_INVALID)) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + lwsl_notice("%s: '%s'\n", __func__, str); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static void +destroy_dbus_client_conn(struct lws_dbus_ctx *ctx) +{ + if (!ctx || !ctx->conn) + return; + + lwsl_notice("%s\n", __func__); + + dbus_connection_remove_filter(ctx->conn, client_message_handler, ctx); + dbus_connection_close(ctx->conn); + dbus_connection_unref(ctx->conn); + + free(ctx); +} + +/* + * This callback is coming when lws has noticed the fd took a POLLHUP. The + * ctx has effectively gone out of scope before this, and the connection can + * be cleaned up and the ctx freed. + */ + +static void +cb_closing(struct lws_dbus_ctx *ctx) +{ + lwsl_err("%s: closing\n", __func__); + + if (ctx == dbus_ctx) + dbus_ctx = NULL; + + destroy_dbus_client_conn(ctx); +} + +static struct lws_dbus_ctx * +create_dbus_client_conn(struct lws_vhost *vh, int tsi, const char *ads) +{ + struct lws_dbus_ctx *ctx; + DBusError err; + + ctx = malloc(sizeof(*ctx)); + if (!ctx) + return NULL; + + memset(ctx, 0, sizeof(*ctx)); + + ctx->vh = vh; + ctx->tsi = tsi; + + dbus_error_init(&err); + + /* connect to the daemon bus */ + ctx->conn = dbus_connection_open_private(ads, &err); + if (!ctx->conn) { + lwsl_err("%s: Failed to connect: %s\n", + __func__, err.message); + goto fail; + } + + dbus_connection_set_exit_on_disconnect(ctx->conn, 0); + + if (!dbus_connection_add_filter(ctx->conn, client_message_handler, + ctx, NULL)) { + lwsl_err("%s: Failed to add filter\n", __func__); + goto fail; + } + + /* + * This is the part that binds the connection to lws watcher and + * timeout handling provided by lws + */ + + if (lws_dbus_connection_setup(ctx, ctx->conn, cb_closing)) { + lwsl_err("%s: connection bind to lws failed\n", __func__); + goto fail; + } + + lwsl_notice("%s: created OK\n", __func__); + + return ctx; + +fail: + dbus_error_free(&err); + + free(ctx); + + return NULL; +} + + +void sigint_handler(int sig) +{ + interrupted = 1; +} + +/* + * This gets called if we timed out waiting for the server reply, or the + * reply arrived. + */ + +static void +pending_call_notify(DBusPendingCall *pending, void *data) +{ + // struct lws_dbus_ctx *ctx = (struct lws_dbus_ctx *)data; + const char *payload; + DBusMessage *msg; + + if (!dbus_pending_call_get_completed(pending)) { + lwsl_err("%s: timed out waiting for reply\n", __func__); + + goto bail; + } + + msg = dbus_pending_call_steal_reply(pending); + if (!msg) + goto bail; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &payload, + DBUS_TYPE_INVALID)) { + goto bail1; + } + + lwsl_user("%s: received '%s'\n", __func__, payload); + +bail1: + dbus_message_unref(msg); +bail: + dbus_pending_call_unref(pending); +} + +static int +remote_method_call(struct lws_dbus_ctx *ctx) +{ + DBusMessage *msg; + const char *payload = "Hello!"; + int ret = 1; + + msg = dbus_message_new_method_call( + /* dest */ THIS_BUSNAME, + /* object-path */ THIS_OBJECT, + /* interface */ THIS_INTERFACE, + /* method */ "Echo"); + if (!msg) + return 1; + + if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &payload, + DBUS_TYPE_INVALID)) + goto bail; + + if (!dbus_connection_send_with_reply(ctx->conn, msg, + &ctx->pc, + DBUS_TIMEOUT_USE_DEFAULT)) { + lwsl_err("%s: unable to send\n", __func__); + + goto bail; + } + + dbus_pending_call_set_notify(ctx->pc, pending_call_notify, ctx, NULL); + + ret = 0; + +bail: + dbus_message_unref(msg); + + return ret; +} + +int main(int argc, const char **argv) +{ + struct lws_vhost *vh; + struct lws_context_creation_info info; + const char *p; + int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE + /* for LLL_ verbosity above NOTICE to be built into lws, + * lws must have been configured and built with + * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ + /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ + /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ + /* | LLL_DEBUG */ /* | LLL_THREAD */; + + signal(SIGINT, sigint_handler); + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS minimal DBUS client\n"); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS; + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + vh = lws_create_vhost(context, &info); + if (!vh) + goto bail; + + dbus_ctx = create_dbus_client_conn(vh, 0, THIS_LISTEN_PATH); + if (!dbus_ctx) + goto bail1; + + if (remote_method_call(dbus_ctx)) + goto bail2; + + /* lws event loop (default poll one) */ + + while (n >= 0 && !interrupted) + n = lws_service(context, 1000); + +bail2: + destroy_dbus_client_conn(dbus_ctx); + +bail1: + /* this is required for valgrind-cleanliness */ + dbus_shutdown(); + lws_context_destroy(context); + + lwsl_notice("Exiting cleanly\n"); + + return 0; + +bail: + lwsl_err("%s: failed to start\n", __func__); + lws_context_destroy(context); + + return 1; +} diff --git a/minimal-examples/dbus-server/README.md b/minimal-examples/dbus-server/README.md new file mode 100644 index 000000000..95039204c --- /dev/null +++ b/minimal-examples/dbus-server/README.md @@ -0,0 +1,3 @@ +|Example|Demonstrates| +---|--- +minimal-dbus-server|Shows how to run a DBUS session server using lws event loop diff --git a/minimal-examples/dbus-server/minimal-dbus-server/CMakeLists.txt b/minimal-examples/dbus-server/minimal-dbus-server/CMakeLists.txt new file mode 100644 index 000000000..7260d5a4d --- /dev/null +++ b/minimal-examples/dbus-server/minimal-dbus-server/CMakeLists.txt @@ -0,0 +1,120 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) +include(CheckLibraryExists) + +set(SAMP lws-minimal-dbus-server) +set(SRCS main.c) + +if (NOT LWS_WITH_MINIMAL_EXAMPLES) + CHECK_LIBRARY_EXISTS(dbus-1 dbus_connection_set_watch_functions "" LWS_HAVE_LIBDBUS) + if (NOT LWS_HAVE_LIBDBUS) + message(FATAL_ERROR "Install dbus-devel, or libdbus-1-dev etc") + endif() + + if (NOT LWS_DBUS_LIB) + set(LWS_DBUS_LIB "dbus-1") + endif() + + if (NOT LWS_DBUS_INCLUDE1) + # look in fedora and debian / ubuntu place + if (EXISTS "/usr/include/dbus-1.0") + set(LWS_DBUS_INCLUDE1 "/usr/include/dbus-1.0") + else() + message(FATAL_ERROR "Set LWS_DBUS_INCLUDE1 to /usr/include/dbus-1.0 or wherever the main dbus includes are") + endif() + endif() + + if (NOT LWS_DBUS_INCLUDE2) + # look in fedora... debian / ubuntu has the ARCH in the path... + if (EXISTS "/usr/lib64/dbus-1.0/include") + set(LWS_DBUS_INCLUDE2 "/usr/lib64/dbus-1.0/include") + else() + message(FATAL_ERROR "Set LWS_DBUS_INCLUDE2 to /usr/lib/ARCH-linux-gnu/dbus-1.0/include or wherever dbus-arch-deps.h is on your system") + endif() + endif() + + set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES};${LWS_DBUS_INCLUDE1};${LWS_DBUS_INCLUDE2}) + + if (NOT LWS_DBUS_INCLUDE1 OR NOT LWS_DBUS_INCLUDE2) + message(FATAL_ERROR "To build with libdbus, LWS_DBUS_INCLUDE1/2 must be given. See lib/roles/dbus/README.md") + endif() + +endif() + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + + endif() +ENDMACRO() + + +set(requirements 1) +require_lws_config(LWS_ROLE_DBUS 1 requirements) +require_lws_config(LWS_WITHOUT_SERVER 0 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + include_directories("${LWS_DBUS_INCLUDE1}") + include_directories("${LWS_DBUS_INCLUDE2}") + list(APPEND LIB_LIST dbus-1) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared ${LWS_DBUS_LIB}) + else() + target_link_libraries(${SAMP} websockets ${LWS_DBUS_LIB}) + endif() +endif() diff --git a/minimal-examples/dbus-server/minimal-dbus-server/README.md b/minimal-examples/dbus-server/minimal-dbus-server/README.md new file mode 100644 index 000000000..7b61eb14b --- /dev/null +++ b/minimal-examples/dbus-server/minimal-dbus-server/README.md @@ -0,0 +1,96 @@ +# lws minimal dbus server + +## build + +Using libdbus requires additional non-default include paths setting, same as +is necessary for lws build described in ./lib/roles/dbus/README.md + +CMake can guess one path and the library name usually, see the README above +for details of how to override for custom libdbus and cross build. + +Fedora example: +``` +$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib64/dbus-1.0/include" +$ make +``` + +Ubuntu example: +``` +$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib/x86_64-linux-gnu/dbus-1.0/include" +$ make +``` + +## usage + +Commandline option|Meaning +---|--- +-d |Debug verbosity in decimal, eg, -d15 +--session | Bind to session bus instead of creating private abstract unix socket + +By default the minimal server listens using its own abstract unix socket +at `unix:abstract=org.libwebsockets.test`. + +You can also run it instead as a participant on the session bus, without its own +unix socket, by giving `--session`. + +### Examples using the default private abstract unix socket + +``` + $ ./lws-minimal-dbus-server +[2018/10/03 07:08:02:6448] USER: LWS minimal dbus server +[2018/10/03 07:08:02:6693] NOTICE: Creating Vhost 'default' port 0, 1 protocols, IPv6 off +... +``` + +You can communicate with the dbus server over its private abstract socket using, eg + +``` +$ gdbus introspect --address unix:abstract=org.libwebsockets.test --dest org.libwebsockets.test --object-path /org/libwebsockets/test +node /org/example/TestObject { + interface org.freedesktop.DBus.Introspectable { + methods: + Introspect(out s data); + signals: + properties: + }; + interface org.freedesktop.DBus.Properties { + methods: + Get(in s interface, +... +``` + +``` +$ gdbus call --address unix:abstract=org.libwebsockets.test --dest org.libwebsockets.test --object-path /org/libwebsockets/test --method org.libwebsockets.test.Echo HELLO +('HELLO',) +``` + +### Examples using the DBUS session bus + +``` + $ ./lws-minimal-dbus-server --session +[2018/10/03 07:08:02:6448] USER: LWS minimal dbus server +[2018/10/03 07:08:02:6693] NOTICE: Creating Vhost 'default' port 0, 1 protocols, IPv6 off +... +``` + +You can communicate with the dbus server over the session bus using, eg + +``` +$ gdbus introspect --session --dest org.libwebsockets.test --object-path /org/libwebsockets/test +node /org/example/TestObject { + interface org.freedesktop.DBus.Introspectable { + methods: + Introspect(out s data); + signals: + properties: + }; + interface org.freedesktop.DBus.Properties { + methods: + Get(in s interface, +... +``` + +``` +$ gdbus call --session --dest org.libwebsockets.test --object-path /org/libwebsockets/test --method org.libwebsockets.test.Echo HELLO +('HELLO',) +``` diff --git a/minimal-examples/dbus-server/minimal-dbus-server/main.c b/minimal-examples/dbus-server/minimal-dbus-server/main.c new file mode 100644 index 000000000..863439c42 --- /dev/null +++ b/minimal-examples/dbus-server/minimal-dbus-server/main.c @@ -0,0 +1,532 @@ +/* + * lws-minimal-dbus-server + * + * Copyright (C) 2018 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This demonstrates a minimal session dbus server that uses the lws event loop, + * making it possible to integrate it with other lws features. + * + * The dbus server parts are based on "Sample code illustrating basic use of + * D-BUS" (presumed Public Domain) here: + * + * https://github.com/fbuihuu/samples-dbus/blob/master/dbus-server.c + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +static struct lws_context *context; +static const char *version = "0.1"; +static int interrupted; +static struct lws_dbus_ctx dbus_ctx, ctx_listener; +static char session; + +#define THIS_INTERFACE "org.libwebsockets.test" +#define THIS_OBJECT "/org/libwebsockets/test" +#define THIS_BUSNAME "org.libwebsockets.test" + +#define THIS_LISTEN_PATH "unix:abstract=org.libwebsockets.test" + +static const char * +server_introspection_xml = + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " " + " \n" + + "\n"; + +static DBusHandlerResult +dmh_introspect(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) +{ + dbus_message_append_args(*reply, DBUS_TYPE_STRING, + &server_introspection_xml, DBUS_TYPE_INVALID); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +dmh_get(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) +{ + const char *interface, *property; + DBusError err; + + dbus_error_init(&err); + + if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID)) { + dbus_message_unref(*reply); + *reply = dbus_message_new_error(m, err.name, err.message); + dbus_error_free(&err); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (strcmp(property, "Version")) /* Unknown property */ + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + dbus_message_append_args(*reply, DBUS_TYPE_STRING, &version, + DBUS_TYPE_INVALID); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +dmh_getall(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) +{ + DBusMessageIter arr, di, iter, va; + const char *property = "Version"; + + dbus_message_iter_init_append(*reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &arr); + + /* Append all properties name/value pairs */ + dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, NULL, &di); + dbus_message_iter_append_basic(&di, DBUS_TYPE_STRING, &property); + dbus_message_iter_open_container(&di, DBUS_TYPE_VARIANT, "s", &va); + dbus_message_iter_append_basic(&va, DBUS_TYPE_STRING, &version); + dbus_message_iter_close_container(&di, &va); + dbus_message_iter_close_container(&arr, &di); + + dbus_message_iter_close_container(&iter, &arr); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +dmh_ping(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) +{ + const char *pong = "Pong"; + + dbus_message_append_args(*reply, DBUS_TYPE_STRING, &pong, + DBUS_TYPE_INVALID); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +dmh_echo(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) +{ + const char *msg; + DBusError err; + + dbus_error_init(&err); + + if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, + &msg, DBUS_TYPE_INVALID)) { + dbus_message_unref(*reply); + *reply = dbus_message_new_error(m, err.name, err.message); + dbus_error_free(&err); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + dbus_message_append_args(*reply, DBUS_TYPE_STRING, &msg, + DBUS_TYPE_INVALID); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +dmh_emit_signal(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) +{ + DBusMessage *r = dbus_message_new_signal(THIS_OBJECT, THIS_INTERFACE, + "OnEmitSignal"); + + if (!r) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (!dbus_connection_send(c, r, NULL)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + /* and send the original empty reply after */ + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +dmh_emit_quit(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) +{ + interrupted = 1; + + return DBUS_HANDLER_RESULT_HANDLED; +} + +struct lws_dbus_methods { + const char *inter; + const char *call; + lws_dbus_message_handler handler; +} meths[] = { + { DBUS_INTERFACE_INTROSPECTABLE, "Introspect", dmh_introspect }, + { DBUS_INTERFACE_PROPERTIES, "Get", dmh_get }, + { DBUS_INTERFACE_PROPERTIES, "GetAll", dmh_getall }, + { THIS_INTERFACE, "Ping", dmh_ping }, + { THIS_INTERFACE, "Echo", dmh_echo }, + { THIS_INTERFACE, "EmitSignal", dmh_emit_signal }, + { THIS_INTERFACE, "Quit", dmh_emit_quit }, +}; + +static DBusHandlerResult +server_message_handler(DBusConnection *conn, DBusMessage *message, void *data) +{ + struct lws_dbus_methods *mp = meths; + DBusHandlerResult result; + DBusMessage *reply = NULL; + size_t n; + + lwsl_info("%s: Got D-Bus request: %s.%s on %s\n", __func__, + dbus_message_get_interface(message), + dbus_message_get_member(message), + dbus_message_get_path(message)); + + for (n = 0; n < LWS_ARRAY_SIZE(meths); n++) { + if (dbus_message_is_method_call(message, mp->inter, mp->call)) { + reply = dbus_message_new_method_return(message); + if (!reply) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + result = mp->handler(conn, message, &reply, data); + + if (result == DBUS_HANDLER_RESULT_HANDLED && + !dbus_connection_send(conn, reply, NULL)) + result = DBUS_HANDLER_RESULT_NEED_MEMORY; + + dbus_message_unref(reply); + + return result; + } + + mp++; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static const DBusObjectPathVTable server_vtable = { + .message_function = server_message_handler +}; + +static void +destroy_dbus_server_conn(struct lws_dbus_ctx *ctx) +{ + if (!ctx->conn) + return; + + lwsl_notice("%s\n", __func__); + + dbus_connection_unregister_object_path(ctx->conn, THIS_OBJECT); + lws_dll_remove(&ctx->next); + dbus_connection_unref(ctx->conn); +} + +static void +cb_closing(struct lws_dbus_ctx *ctx) +{ + lwsl_err("%s: closing\n", __func__); + destroy_dbus_server_conn(ctx); + + free(ctx); +} + + +static void +new_conn(DBusServer *server, DBusConnection *conn, void *data) +{ + struct lws_dbus_ctx *conn_ctx, *ctx = (struct lws_dbus_ctx *)data; + + lwsl_notice("%s: vh %s\n", __func__, lws_get_vhost_name(ctx->vh)); + + conn_ctx = malloc(sizeof(*conn_ctx)); + if (!conn_ctx) + return; + + memset(conn_ctx, 0, sizeof(*conn_ctx)); + + conn_ctx->tsi = ctx->tsi; + conn_ctx->vh = ctx->vh; + conn_ctx->conn = conn; + + if (lws_dbus_connection_setup(conn_ctx, conn, cb_closing)) { + lwsl_err("%s: connection bind to lws failed\n", __func__); + goto bail; + } + + if (!dbus_connection_register_object_path(conn, THIS_OBJECT, + &server_vtable, conn_ctx)) { + lwsl_err("%s: Failed to register object path\n", __func__); + goto bail; + } + + lws_dll_add_front(&conn_ctx->next, &ctx->next); + + /* we take on responsibility for explicit close / unref with this... */ + dbus_connection_ref(conn); + + return; + +bail: + free(conn_ctx); +} + +static int +create_dbus_listener(const char *ads) +{ + DBusError e; + + dbus_error_init(&e); + + if (!lws_dbus_server_listen(&ctx_listener, ads, &e, new_conn)) { + lwsl_err("%s: failed\n", __func__); + dbus_error_free(&e); + + return 1; + } + + return 0; +} + +static int +create_dbus_server_conn(struct lws_dbus_ctx *ctx, DBusBusType type) +{ + DBusError err; + int rv; + + dbus_error_init(&err); + + /* connect to the daemon bus */ + ctx->conn = dbus_bus_get(type, &err); + if (!ctx->conn) { + lwsl_err("%s: Failed to get a session DBus connection: %s\n", + __func__, err.message); + goto fail; + } + + /* + * by default dbus will call exit() when this connection closes... + * we have to shut down other things cleanly, so disable that + */ + dbus_connection_set_exit_on_disconnect(ctx->conn, 0); + + rv = dbus_bus_request_name(ctx->conn, THIS_BUSNAME, + DBUS_NAME_FLAG_REPLACE_EXISTING, &err); + if (rv != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + lwsl_err("%s: Failed to request name on bus: %s\n", + __func__, err.message); + goto fail; + } + + if (!dbus_connection_register_object_path(ctx->conn, THIS_OBJECT, + &server_vtable, NULL)) { + lwsl_err("%s: Failed to register object path for TestObject\n", + __func__); + dbus_bus_release_name(ctx->conn, THIS_BUSNAME, &err); + goto fail; + } + + /* + * This is the part that binds the connection to lws watcher and + * timeout handling provided by lws + */ + + if (lws_dbus_connection_setup(ctx, ctx->conn, cb_closing)) { + lwsl_err("%s: connection bind to lws failed\n", __func__); + goto fail; + } + + lwsl_notice("%s: created OK\n", __func__); + + return 0; + +fail: + dbus_error_free(&err); + + return 1; +} + +/* + * Cleanly release the connection + */ + +static void +destroy_dbus_server_listener(struct lws_dbus_ctx *ctx) +{ + dbus_server_disconnect(ctx->dbs); + + lws_start_foreach_dll_safe(struct lws_dll *, rdt, nx, + ctx->next.next) { + struct lws_dbus_ctx *r = + lws_container_of(rdt, struct lws_dbus_ctx, next); + + dbus_connection_close(r->conn); + dbus_connection_unref(r->conn); + free(r); + } lws_end_foreach_dll_safe(rdt, nx); + + dbus_server_unref(ctx->dbs); +} + +/* + * DBUS can send messages outside the usual client-initiated RPC concept. + * + * You can receive them using a message filter. + */ + +static void +spam_connected_clients(struct lws_dbus_ctx *ctx) +{ + + /* send connected clients an unsolicited message */ + + lws_start_foreach_dll_safe(struct lws_dll *, rdt, nx, + ctx->next.next) { + struct lws_dbus_ctx *r = + lws_container_of(rdt, struct lws_dbus_ctx, next); + + + DBusMessage *msg; + const char *payload = "Unsolicited message"; + + msg = dbus_message_new(DBUS_NUM_MESSAGE_TYPES + 1); + if (!msg) { + lwsl_err("%s: new message failed\n", __func__); + } + + dbus_message_append_args(msg, DBUS_TYPE_STRING, &payload, + DBUS_TYPE_INVALID); + if (!dbus_connection_send(r->conn, msg, NULL)) { + lwsl_err("%s: unable to send\n", __func__); + } + + lwsl_notice("%s\n", __func__); + + dbus_message_unref(msg); + + } lws_end_foreach_dll_safe(rdt, nx); + +} + + +void sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + const char *p; + int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE + /* for LLL_ verbosity above NOTICE to be built into lws, + * lws must have been configured and built with + * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ + /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ + /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ + /* | LLL_DEBUG */ /* | LLL_THREAD */; + + signal(SIGINT, sigint_handler); + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS minimal DBUS server\n"); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS; + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + dbus_ctx.tsi = 0; + ctx_listener.tsi = 0; + ctx_listener.vh = dbus_ctx.vh = lws_create_vhost(context, &info); + if (!dbus_ctx.vh) + goto bail; + + session = !!lws_cmdline_option(argc, argv, "--session"); + + if (session) { + /* create the dbus connection, loosely bound to our lws vhost */ + + if (create_dbus_server_conn(&dbus_ctx, DBUS_BUS_SESSION)) + goto bail; + } else { + if (create_dbus_listener(THIS_LISTEN_PATH)) { + lwsl_err("%s: create_dbus_listener failed\n", __func__); + goto bail; + } + } + + /* lws event loop (default poll one) */ + + while (n >= 0 && !interrupted) { + if (!session) + spam_connected_clients(&ctx_listener); + n = lws_service(context, 1000); + } + + if (session) + destroy_dbus_server_conn(&dbus_ctx); + else + destroy_dbus_server_listener(&ctx_listener); + + /* this is required for valgrind-cleanliness */ + dbus_shutdown(); + lws_context_destroy(context); + + lwsl_notice("Exiting cleanly\n"); + + return 0; + +bail: + lwsl_err("%s: failed to start\n", __func__); + + lws_context_destroy(context); + + return 1; +}