You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

536 lines
13 KiB

/*
* lws-minimal-dbus-server
*
* Written in 2010-2019 by 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_dll2_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_dll2_add_head(&conn_ctx->next, &ctx->owner);
/* 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_dll2 *, rdt, nx,
ctx->owner.head) {
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_dll2 *, rdt, nx,
ctx->owner.head) {
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;
}
info.options |=
LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
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, 0);
}
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;
}