1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-09 00:00:04 +01:00

role: dbus

This adds support for the integrating libdbus into the lws event loop.

Unlike the other roles, lws doesn't completely adopt the fd and libdbus insists
to retain control over the fd lifecycle.  However libdbus provides apis for
foreign code (lws) to provide event loop services to libdbus for the fd.

Accordingly, unlike the other roles rx and writeable are not subsumed into
lws callback messages and the events remain the property of libdbus.

A context struct wrapper is provided that is available in the libdbus
callbacks to bridge between the lws and dbus worlds, along with
a minimal example dbus client and server.
This commit is contained in:
Andy Green 2018-10-02 10:50:24 +08:00
parent bbbdc85421
commit 31dfc4aa12
19 changed files with 2017 additions and 2 deletions

View file

@ -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 <dbus/dbus.h>
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.

View file

@ -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. */

View file

@ -0,0 +1,90 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
*
* 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 <libwebsockets/lws-dbus.h>
*
* if dbus apis needed
*/
#if !defined(__LWS_DBUS_H__)
#define __LWS_DBUS_H__
#include <dbus/dbus.h>
/* 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

View file

@ -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
};

View file

@ -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)

View file

@ -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;

83
lib/roles/dbus/README.md Normal file
View file

@ -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 <libwebsockets.h>`
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 <libwebsockets/lws-dbus.h>
```
This includes `<dbus/dbus.h>` 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

527
lib/roles/dbus/dbus.c Normal file
View file

@ -0,0 +1,527 @@
/*
* libwebsockets - dbus role
*
* Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
*
* 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 <core/private.h>
#include <libwebsockets/lws-dbus.h>
/*
* 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,
};

42
lib/roles/dbus/private.h Normal file
View file

@ -0,0 +1,42 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
*
* 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 <dbus/dbus.h>
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;
};

View file

@ -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,

View file

@ -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

View file

@ -0,0 +1,4 @@
|Example|Demonstrates|
---|---
minimal-dbus-client|Shows how to connect to a DBusServer dbus server like minimal-dbus-server

View file

@ -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 <libwebsockets.h>\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()

View file

@ -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 <loglevel>|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
...
```

View file

@ -0,0 +1,279 @@
/*
* lws-minimal-dbus-client
*
* Copyright (C) 2018 Andy Green <andy@warmcat.com>
*
* 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 <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libwebsockets.h>
#include <libwebsockets/lws-dbus.h>
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;
}

View file

@ -0,0 +1,3 @@
|Example|Demonstrates|
---|---
minimal-dbus-server|Shows how to run a DBUS session server using lws event loop

View file

@ -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 <libwebsockets.h>\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()

View file

@ -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 <loglevel>|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',)
```

View file

@ -0,0 +1,532 @@
/*
* lws-minimal-dbus-server
*
* Copyright (C) 2018 Andy Green <andy@warmcat.com>
*
* 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 <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <libwebsockets.h>
#include <libwebsockets/lws-dbus.h>
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
"<node>\n"
" <interface name='" DBUS_INTERFACE_INTROSPECTABLE "'>\n"
" <method name='Introspect'>\n"
" <arg name='data' type='s' direction='out' />\n"
" </method>\n"
" </interface>\n"
" <interface name='" DBUS_INTERFACE_PROPERTIES "'>\n"
" <method name='Get'>\n"
" <arg name='interface' type='s' direction='in' />\n"
" <arg name='property' type='s' direction='in' />\n"
" <arg name='value' type='s' direction='out' />\n"
" </method>\n"
" <method name='GetAll'>\n"
" <arg name='interface' type='s' direction='in'/>\n"
" <arg name='properties' type='a{sv}' direction='out'/>\n"
" </method>\n"
" </interface>\n"
" <interface name='"THIS_INTERFACE"'>\n"
" <property name='Version' type='s' access='read' />\n"
" <method name='Ping' >\n"
" <arg type='s' direction='out' />\n"
" </method>\n"
" <method name='Echo'>\n"
" <arg name='string' direction='in' type='s'/>\n"
" <arg type='s' direction='out' />\n"
" </method>\n"
" <method name='EmitSignal'>\n"
" </method>\n"
" <method name='Quit'>\n"
" </method>\n"
" <signal name='OnEmitSignal'>\n"
" </signal>"
" </interface>\n"
"</node>\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;
}