1/* 2 * lws-minimal-dbus-server 3 * 4 * Written in 2010-2019 by Andy Green <andy@warmcat.com> 5 * 6 * This file is made available under the Creative Commons CC0 1.0 7 * Universal Public Domain Dedication. 8 * 9 * This demonstrates a minimal session dbus server that uses the lws event loop, 10 * making it possible to integrate it with other lws features. 11 * 12 * The dbus server parts are based on "Sample code illustrating basic use of 13 * D-BUS" (presumed Public Domain) here: 14 * 15 * https://github.com/fbuihuu/samples-dbus/blob/master/dbus-server.c 16 */ 17 18#include <stdbool.h> 19#include <string.h> 20#include <stdio.h> 21#include <stdlib.h> 22#include <unistd.h> 23#include <signal.h> 24 25#include <libwebsockets.h> 26#include <libwebsockets/lws-dbus.h> 27 28static struct lws_context *context; 29static const char *version = "0.1"; 30static int interrupted; 31static struct lws_dbus_ctx dbus_ctx, ctx_listener; 32static char session; 33 34#define THIS_INTERFACE "org.libwebsockets.test" 35#define THIS_OBJECT "/org/libwebsockets/test" 36#define THIS_BUSNAME "org.libwebsockets.test" 37 38#define THIS_LISTEN_PATH "unix:abstract=org.libwebsockets.test" 39 40static const char * 41server_introspection_xml = 42 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE 43 "<node>\n" 44 " <interface name='" DBUS_INTERFACE_INTROSPECTABLE "'>\n" 45 " <method name='Introspect'>\n" 46 " <arg name='data' type='s' direction='out' />\n" 47 " </method>\n" 48 " </interface>\n" 49 50 " <interface name='" DBUS_INTERFACE_PROPERTIES "'>\n" 51 " <method name='Get'>\n" 52 " <arg name='interface' type='s' direction='in' />\n" 53 " <arg name='property' type='s' direction='in' />\n" 54 " <arg name='value' type='s' direction='out' />\n" 55 " </method>\n" 56 " <method name='GetAll'>\n" 57 " <arg name='interface' type='s' direction='in'/>\n" 58 " <arg name='properties' type='a{sv}' direction='out'/>\n" 59 " </method>\n" 60 " </interface>\n" 61 62 " <interface name='"THIS_INTERFACE"'>\n" 63 " <property name='Version' type='s' access='read' />\n" 64 " <method name='Ping' >\n" 65 " <arg type='s' direction='out' />\n" 66 " </method>\n" 67 " <method name='Echo'>\n" 68 " <arg name='string' direction='in' type='s'/>\n" 69 " <arg type='s' direction='out' />\n" 70 " </method>\n" 71 " <method name='EmitSignal'>\n" 72 " </method>\n" 73 " <method name='Quit'>\n" 74 " </method>\n" 75 " <signal name='OnEmitSignal'>\n" 76 " </signal>" 77 " </interface>\n" 78 79 "</node>\n"; 80 81static DBusHandlerResult 82dmh_introspect(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) 83{ 84 dbus_message_append_args(*reply, DBUS_TYPE_STRING, 85 &server_introspection_xml, DBUS_TYPE_INVALID); 86 87 return DBUS_HANDLER_RESULT_HANDLED; 88} 89 90static DBusHandlerResult 91dmh_get(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) 92{ 93 const char *interface, *property; 94 DBusError err; 95 96 dbus_error_init(&err); 97 98 if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &interface, 99 DBUS_TYPE_STRING, &property, 100 DBUS_TYPE_INVALID)) { 101 dbus_message_unref(*reply); 102 *reply = dbus_message_new_error(m, err.name, err.message); 103 dbus_error_free(&err); 104 105 return DBUS_HANDLER_RESULT_HANDLED; 106 } 107 108 if (strcmp(property, "Version")) /* Unknown property */ 109 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 110 111 dbus_message_append_args(*reply, DBUS_TYPE_STRING, &version, 112 DBUS_TYPE_INVALID); 113 114 return DBUS_HANDLER_RESULT_HANDLED; 115} 116 117static DBusHandlerResult 118dmh_getall(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) 119{ 120 DBusMessageIter arr, di, iter, va; 121 const char *property = "Version"; 122 123 dbus_message_iter_init_append(*reply, &iter); 124 dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &arr); 125 126 /* Append all properties name/value pairs */ 127 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, NULL, &di); 128 dbus_message_iter_append_basic(&di, DBUS_TYPE_STRING, &property); 129 dbus_message_iter_open_container(&di, DBUS_TYPE_VARIANT, "s", &va); 130 dbus_message_iter_append_basic(&va, DBUS_TYPE_STRING, &version); 131 dbus_message_iter_close_container(&di, &va); 132 dbus_message_iter_close_container(&arr, &di); 133 134 dbus_message_iter_close_container(&iter, &arr); 135 136 return DBUS_HANDLER_RESULT_HANDLED; 137} 138 139static DBusHandlerResult 140dmh_ping(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) 141{ 142 const char *pong = "Pong"; 143 144 dbus_message_append_args(*reply, DBUS_TYPE_STRING, &pong, 145 DBUS_TYPE_INVALID); 146 147 return DBUS_HANDLER_RESULT_HANDLED; 148} 149 150static DBusHandlerResult 151dmh_echo(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) 152{ 153 const char *msg; 154 DBusError err; 155 156 dbus_error_init(&err); 157 158 if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, 159 &msg, DBUS_TYPE_INVALID)) { 160 dbus_message_unref(*reply); 161 *reply = dbus_message_new_error(m, err.name, err.message); 162 dbus_error_free(&err); 163 164 return DBUS_HANDLER_RESULT_HANDLED; 165 } 166 167 dbus_message_append_args(*reply, DBUS_TYPE_STRING, &msg, 168 DBUS_TYPE_INVALID); 169 170 return DBUS_HANDLER_RESULT_HANDLED; 171} 172 173static DBusHandlerResult 174dmh_emit_signal(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) 175{ 176 DBusMessage *r = dbus_message_new_signal(THIS_OBJECT, THIS_INTERFACE, 177 "OnEmitSignal"); 178 179 if (!r) 180 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 181 182 if (!dbus_connection_send(c, r, NULL)) 183 return DBUS_HANDLER_RESULT_NEED_MEMORY; 184 185 /* and send the original empty reply after */ 186 187 return DBUS_HANDLER_RESULT_HANDLED; 188} 189 190static DBusHandlerResult 191dmh_emit_quit(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) 192{ 193 interrupted = 1; 194 195 return DBUS_HANDLER_RESULT_HANDLED; 196} 197 198struct lws_dbus_methods { 199 const char *inter; 200 const char *call; 201 lws_dbus_message_handler handler; 202} meths[] = { 203 { DBUS_INTERFACE_INTROSPECTABLE, "Introspect", dmh_introspect }, 204 { DBUS_INTERFACE_PROPERTIES, "Get", dmh_get }, 205 { DBUS_INTERFACE_PROPERTIES, "GetAll", dmh_getall }, 206 { THIS_INTERFACE, "Ping", dmh_ping }, 207 { THIS_INTERFACE, "Echo", dmh_echo }, 208 { THIS_INTERFACE, "EmitSignal", dmh_emit_signal }, 209 { THIS_INTERFACE, "Quit", dmh_emit_quit }, 210}; 211 212static DBusHandlerResult 213server_message_handler(DBusConnection *conn, DBusMessage *message, void *data) 214{ 215 struct lws_dbus_methods *mp = meths; 216 DBusHandlerResult result; 217 DBusMessage *reply = NULL; 218 size_t n; 219 220 lwsl_info("%s: Got D-Bus request: %s.%s on %s\n", __func__, 221 dbus_message_get_interface(message), 222 dbus_message_get_member(message), 223 dbus_message_get_path(message)); 224 225 for (n = 0; n < LWS_ARRAY_SIZE(meths); n++) { 226 if (dbus_message_is_method_call(message, mp->inter, mp->call)) { 227 reply = dbus_message_new_method_return(message); 228 if (!reply) 229 return DBUS_HANDLER_RESULT_NEED_MEMORY; 230 231 result = mp->handler(conn, message, &reply, data); 232 233 if (result == DBUS_HANDLER_RESULT_HANDLED && 234 !dbus_connection_send(conn, reply, NULL)) 235 result = DBUS_HANDLER_RESULT_NEED_MEMORY; 236 237 dbus_message_unref(reply); 238 239 return result; 240 } 241 242 mp++; 243 } 244 245 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 246} 247 248static const DBusObjectPathVTable server_vtable = { 249 .message_function = server_message_handler 250}; 251 252static void 253destroy_dbus_server_conn(struct lws_dbus_ctx *ctx) 254{ 255 if (!ctx->conn) 256 return; 257 258 lwsl_notice("%s\n", __func__); 259 260 dbus_connection_unregister_object_path(ctx->conn, THIS_OBJECT); 261 lws_dll2_remove(&ctx->next); 262 dbus_connection_unref(ctx->conn); 263} 264 265static void 266cb_closing(struct lws_dbus_ctx *ctx) 267{ 268 lwsl_err("%s: closing\n", __func__); 269 destroy_dbus_server_conn(ctx); 270 271 free(ctx); 272} 273 274 275static void 276new_conn(DBusServer *server, DBusConnection *conn, void *data) 277{ 278 struct lws_dbus_ctx *conn_ctx, *ctx = (struct lws_dbus_ctx *)data; 279 280 lwsl_notice("%s: vh %s\n", __func__, lws_get_vhost_name(ctx->vh)); 281 282 conn_ctx = malloc(sizeof(*conn_ctx)); 283 if (!conn_ctx) 284 return; 285 286 memset(conn_ctx, 0, sizeof(*conn_ctx)); 287 288 conn_ctx->tsi = ctx->tsi; 289 conn_ctx->vh = ctx->vh; 290 conn_ctx->conn = conn; 291 292 if (lws_dbus_connection_setup(conn_ctx, conn, cb_closing)) { 293 lwsl_err("%s: connection bind to lws failed\n", __func__); 294 goto bail; 295 } 296 297 if (!dbus_connection_register_object_path(conn, THIS_OBJECT, 298 &server_vtable, conn_ctx)) { 299 lwsl_err("%s: Failed to register object path\n", __func__); 300 goto bail; 301 } 302 303 lws_dll2_add_head(&conn_ctx->next, &ctx->owner); 304 305 /* we take on responsibility for explicit close / unref with this... */ 306 dbus_connection_ref(conn); 307 308 return; 309 310bail: 311 free(conn_ctx); 312} 313 314static int 315create_dbus_listener(const char *ads) 316{ 317 DBusError e; 318 319 dbus_error_init(&e); 320 321 if (!lws_dbus_server_listen(&ctx_listener, ads, &e, new_conn)) { 322 lwsl_err("%s: failed\n", __func__); 323 dbus_error_free(&e); 324 325 return 1; 326 } 327 328 return 0; 329} 330 331static int 332create_dbus_server_conn(struct lws_dbus_ctx *ctx, DBusBusType type) 333{ 334 DBusError err; 335 int rv; 336 337 dbus_error_init(&err); 338 339 /* connect to the daemon bus */ 340 ctx->conn = dbus_bus_get(type, &err); 341 if (!ctx->conn) { 342 lwsl_err("%s: Failed to get a session DBus connection: %s\n", 343 __func__, err.message); 344 goto fail; 345 } 346 347 /* 348 * by default dbus will call exit() when this connection closes... 349 * we have to shut down other things cleanly, so disable that 350 */ 351 dbus_connection_set_exit_on_disconnect(ctx->conn, 0); 352 353 rv = dbus_bus_request_name(ctx->conn, THIS_BUSNAME, 354 DBUS_NAME_FLAG_REPLACE_EXISTING, &err); 355 if (rv != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { 356 lwsl_err("%s: Failed to request name on bus: %s\n", 357 __func__, err.message); 358 goto fail; 359 } 360 361 if (!dbus_connection_register_object_path(ctx->conn, THIS_OBJECT, 362 &server_vtable, NULL)) { 363 lwsl_err("%s: Failed to register object path for TestObject\n", 364 __func__); 365 dbus_bus_release_name(ctx->conn, THIS_BUSNAME, &err); 366 goto fail; 367 } 368 369 /* 370 * This is the part that binds the connection to lws watcher and 371 * timeout handling provided by lws 372 */ 373 374 if (lws_dbus_connection_setup(ctx, ctx->conn, cb_closing)) { 375 lwsl_err("%s: connection bind to lws failed\n", __func__); 376 goto fail; 377 } 378 379 lwsl_notice("%s: created OK\n", __func__); 380 381 return 0; 382 383fail: 384 dbus_error_free(&err); 385 386 return 1; 387} 388 389/* 390 * Cleanly release the connection 391 */ 392 393static void 394destroy_dbus_server_listener(struct lws_dbus_ctx *ctx) 395{ 396 dbus_server_disconnect(ctx->dbs); 397 398 lws_start_foreach_dll_safe(struct lws_dll2 *, rdt, nx, 399 ctx->owner.head) { 400 struct lws_dbus_ctx *r = 401 lws_container_of(rdt, struct lws_dbus_ctx, next); 402 403 dbus_connection_close(r->conn); 404 dbus_connection_unref(r->conn); 405 free(r); 406 } lws_end_foreach_dll_safe(rdt, nx); 407 408 dbus_server_unref(ctx->dbs); 409} 410 411/* 412 * DBUS can send messages outside the usual client-initiated RPC concept. 413 * 414 * You can receive them using a message filter. 415 */ 416 417static void 418spam_connected_clients(struct lws_dbus_ctx *ctx) 419{ 420 421 /* send connected clients an unsolicited message */ 422 423 lws_start_foreach_dll_safe(struct lws_dll2 *, rdt, nx, 424 ctx->owner.head) { 425 struct lws_dbus_ctx *r = 426 lws_container_of(rdt, struct lws_dbus_ctx, next); 427 428 429 DBusMessage *msg; 430 const char *payload = "Unsolicited message"; 431 432 msg = dbus_message_new(DBUS_NUM_MESSAGE_TYPES + 1); 433 if (!msg) { 434 lwsl_err("%s: new message failed\n", __func__); 435 } 436 437 dbus_message_append_args(msg, DBUS_TYPE_STRING, &payload, 438 DBUS_TYPE_INVALID); 439 if (!dbus_connection_send(r->conn, msg, NULL)) { 440 lwsl_err("%s: unable to send\n", __func__); 441 } 442 443 lwsl_notice("%s\n", __func__); 444 445 dbus_message_unref(msg); 446 447 } lws_end_foreach_dll_safe(rdt, nx); 448 449} 450 451 452void sigint_handler(int sig) 453{ 454 interrupted = 1; 455} 456 457int main(int argc, const char **argv) 458{ 459 struct lws_context_creation_info info; 460 const char *p; 461 int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE 462 /* for LLL_ verbosity above NOTICE to be built into lws, 463 * lws must have been configured and built with 464 * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ 465 /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ 466 /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ 467 /* | LLL_DEBUG */ /* | LLL_THREAD */; 468 469 signal(SIGINT, sigint_handler); 470 471 if ((p = lws_cmdline_option(argc, argv, "-d"))) 472 logs = atoi(p); 473 474 lws_set_log_level(logs, NULL); 475 lwsl_user("LWS minimal DBUS server\n"); 476 477 memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ 478 info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS; 479 context = lws_create_context(&info); 480 if (!context) { 481 lwsl_err("lws init failed\n"); 482 return 1; 483 } 484 485 info.options |= 486 LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; 487 488 dbus_ctx.tsi = 0; 489 ctx_listener.tsi = 0; 490 ctx_listener.vh = dbus_ctx.vh = lws_create_vhost(context, &info); 491 if (!dbus_ctx.vh) 492 goto bail; 493 494 session = !!lws_cmdline_option(argc, argv, "--session"); 495 496 if (session) { 497 /* create the dbus connection, loosely bound to our lws vhost */ 498 499 if (create_dbus_server_conn(&dbus_ctx, DBUS_BUS_SESSION)) 500 goto bail; 501 } else { 502 if (create_dbus_listener(THIS_LISTEN_PATH)) { 503 lwsl_err("%s: create_dbus_listener failed\n", __func__); 504 goto bail; 505 } 506 } 507 508 /* lws event loop (default poll one) */ 509 510 while (n >= 0 && !interrupted) { 511 if (!session) 512 spam_connected_clients(&ctx_listener); 513 n = lws_service(context, 0); 514 } 515 516 if (session) 517 destroy_dbus_server_conn(&dbus_ctx); 518 else 519 destroy_dbus_server_listener(&ctx_listener); 520 521 /* this is required for valgrind-cleanliness */ 522 dbus_shutdown(); 523 lws_context_destroy(context); 524 525 lwsl_notice("Exiting cleanly\n"); 526 527 return 0; 528 529bail: 530 lwsl_err("%s: failed to start\n", __func__); 531 532 lws_context_destroy(context); 533 534 return 1; 535} 536