/* * Description: Simple D-Bus server with a single echo method that * returns the string the client sent it. * Author: James Hunt * Date: 15 October 2013. * Notes: * * - To run this program: * * $ bin/test_nih_dbus_server unix:abstract=/com/hunt/james/foo com.hunt.james.Foo /com/hunt/james/Foo * * - To see what is advertised: * * $ gdbus introspect --session --dest com.hunt.james.Foo --object-path / --recurse * * - To call the EchoMethod via the D-Bus session bus: * * $ msg='hello world' * $ dbus-send --session --print-reply --dest=com.hunt.james.Foo \ * /com/hunt/james/Foo com.hunt.james.EchoMethod \ * string:"$msg" * * - To call the EchoMethod via the D-Bus private socket: * * $ msg='hello world' * $ dbus-send --address=unix:abstract=/com/hunt/james/foo \ * --print-reply --dest=com.hunt.james.Foo \ * /com/hunt/james/Foo com.hunt.james.EchoMethod \ * string:"$msg" */ /* standard build: gcc -std=gnu99 -ggdb -Wall -pedantic -o bin/test_nih_dbus_server test_nih_dbus_server.c `pkg-config --cflags --libs libnih libnih-dbus libupstart dbus-1` debug build: cflags=$(export PKG_CONFIG_PATH=/data/testing/lib/pkgconfig:$PKG_CONFIG_PATH;\ export ACDIR=/data/testing/share/aclocal:$ACDIR;pkg-config --cflags libnih libnih-dbus libupstart dbus-1) libs=$(export PKG_CONFIG_PATH=/data/testing/lib/pkgconfig:$PKG_CONFIG_PATH;\ export ACDIR=/data/testing/share/aclocal:$ACDIR;pkg-config --libs libnih libnih-dbus libupstart dbus-1) cc -std=gnu99 -ggdb -Wall -pedantic -o bin/test_nih_dbus_server test_nih_dbus_server.c $libs -Wl,-rpath -Wl,/data/testing/lib */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "upstart.h" NihList *clients = NULL; const char *program_name = NULL; /* D-Bus path */ char *dbus_path = NULL; /* socket */ DBusServer *private_dbus_server = NULL; /* Publically accessible name */ DBusConnection *public_dbus_server = NULL; /********************************************************************/ static DBusHandlerResult echo_method (NihDBusObject * object, NihDBusMessage *message); static const NihDBusArg echo_method_args[] = { { "string", "s", NIH_DBUS_ARG_IN }, { "value", "s", NIH_DBUS_ARG_OUT }, { NULL, } }; static const NihDBusArg echo_handled_signal_args[] = { { "value", "s", NIH_DBUS_ARG_OUT }, { NULL, } }; static const NihDBusMethod interface_methods[] = { { "EchoMethod", echo_method_args, echo_method }, { NULL } }; static const NihDBusSignal interface_signals[] = { { "EchoHandled", echo_handled_signal_args, NULL }, { NULL } }; const NihDBusInterface my_interfaces = { "com.hunt.james" , /* name */ interface_methods , /* interface methods */ interface_signals , /* interface signals */ NULL /* interface properties */ }; const NihDBusInterface *interfaces[] = { &my_interfaces, NULL }; /********************************************************************/ /* prototypes */ void notify_clients (const char *string) __attribute__ ((unused)); static DBusHandlerResult echo_method (NihDBusObject *object, NihDBusMessage *message); int echo_handled (DBusConnection *connection, const char *origin_path, const char *string); /********************************************************************/ void notify_clients (const char *string) { int ret; NIH_LIST_FOREACH (clients, iter) { NihListEntry *entry = (NihListEntry *)iter; DBusConnection *conn = (DBusConnection *)entry->data; /* Send dbus signal to each client * a la `control_emit_event_emitted()'. */ ret = echo_handled (conn, dbus_path, string); if (ret < 0) { nih_fatal ("echo_handled returned %d", ret); exit (EXIT_FAILURE); } } } /* * Accepts a string from the client, and sends back a string message * containing the original string. */ static DBusHandlerResult echo_method (NihDBusObject *object, NihDBusMessage *message) { DBusMessageIter iter; DBusMessage *reply; int arg_type; /* D-Bus's static string */ const char *string_dbus; /* Our copy */ char *string; nih_local char *reply_string = NULL; nih_assert (object); nih_assert (message); dbus_message_iter_init (message->message, &iter); arg_type = dbus_message_iter_get_arg_type (&iter); /* We expect a string type */ assert (arg_type == DBUS_TYPE_STRING); dbus_message_iter_get_basic (&iter, &string_dbus); string = nih_strdup (message, string_dbus); if (! string) { /* Tell libdbus (NOT the client) that we need more memory */ return DBUS_HANDLER_RESULT_NEED_MEMORY; } /* If the sender doesn't care about a reply, don't bother * wasting effort constructing and sending one. */ if (dbus_message_get_no_reply (message->message)) return DBUS_HANDLER_RESULT_HANDLED; /* Construct the reply message. */ reply = dbus_message_new_method_return (message->message); nih_assert (reply); dbus_message_iter_init_append (reply, &iter); reply_string = NIH_MUST (nih_sprintf (NULL, "You said, \"%s\"", string)); /* Marshal a char * onto the message */ if (! dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &reply_string)) { dbus_message_unref (reply); reply = NULL; return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } /* Send the reply, appending it to the outgoing queue. */ NIH_MUST (dbus_connection_send (message->connection, reply, NULL)); dbus_message_unref (reply); /* Signal other clients that this method was called */ notify_clients (string); /* FIXME: try returning DBUS_HANDLER_RESULT_NOT_YET_HANDLED to see * what happens! */ return DBUS_HANDLER_RESULT_HANDLED; } /* This is essentially Upstart's control_emit_event_emitted() function */ int echo_handled (DBusConnection *connection, const char *origin_path, const char *string) { DBusMessage *signal; DBusMessageIter iter; nih_assert (connection); nih_assert (origin_path); nih_assert (string); /* Construct the message. */ signal = dbus_message_new_signal (origin_path, "com.hunt.james", "EchoHandled"); if (! signal) return -1; dbus_message_iter_init_append (signal, &iter); /* Marshal a char * onto the message */ if (! dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &string)) { dbus_message_unref (signal); return -1; } /* Send the signal, appending it to the outgoing queue. */ if (! dbus_connection_send (connection, signal, NULL)) { dbus_message_unref (signal); return -1; } dbus_message_unref (signal); return 0; } void signal_handler (void *data, NihSignal *signal) { nih_message ("Caught signal - shutting down"); nih_main_loop_exit (0); } void connect_register_all (DBusConnection *connection) { NihDBusObject *object; nih_assert (connection); object = nih_dbus_object_new (NULL, /* parent */ connection, dbus_path, interfaces, NULL /* data */); nih_assert (object); } int connect_handler (DBusServer *server, DBusConnection *connection) { NihListEntry *entry; #if 0 unsigned long int pid = 0; unsigned long int uid = 0; #endif nih_assert (server); nih_assert (connection); nih_assert (dbus_path); /* FIXME: cannot query connector yet as they haven't been granted * connection rights yet! */ #if 0 if (! dbus_connection_get_unix_process_id (connection, &pid)) nih_warn ("Cannot query pid"); if (! dbus_connection_get_unix_user (connection, &uid)) nih_warn ("Cannot query user"); nih_message ("XXX:%s:%d:connect from pid %d (uid %d)", __func__, __LINE__, (int)pid, (int)uid); #else nih_message ("XXX:%s:%d:client connected", __func__, __LINE__); #endif connect_register_all (connection); /* Record client */ entry = NIH_MUST (nih_list_entry_new (clients)); entry->data = connection; nih_list_add (clients, &entry->entry); return TRUE; } void disconnect_handler (DBusConnection *connection) { unsigned long int pid = 0; unsigned long int uid = 0; int handled = FALSE; nih_assert (connection); if (! dbus_connection_get_unix_process_id (connection, &pid)) nih_warn ("Cannot query pid"); if (! dbus_connection_get_unix_user (connection, &uid)) nih_warn ("Cannot query user"); nih_message ("XXX:%s:%d:disconnect from pid %d (uid %d)", __func__, __LINE__, (int)pid, (int)uid); NIH_LIST_FOREACH (clients, iter) { NihListEntry *entry = (NihListEntry *)iter; if (entry->data == connection) { nih_message ("Client connection %p dropped", entry->data); nih_free (entry); handled = TRUE; break; } } nih_assert (handled); } void setup_private_server (const char *private_address) { private_dbus_server = nih_dbus_server (private_address, connect_handler, disconnect_handler); nih_assert (private_dbus_server); } void setup_public_server (const char *public_address) { DBusError dbus_error; int ret; public_dbus_server = nih_dbus_bus (DBUS_BUS_SESSION, disconnect_handler); dbus_error_init (&dbus_error); connect_register_all (public_dbus_server); ret = dbus_bus_request_name (public_dbus_server, public_address, DBUS_NAME_FLAG_DO_NOT_QUEUE, &dbus_error); if (ret < 0) { nih_fatal ("Failed to register public D-Bus name '%s': name='%s' message='%s'", public_address, dbus_error.name, dbus_error.message); dbus_error_free (&dbus_error); dbus_connection_unref (public_dbus_server); exit (EXIT_FAILURE); } else if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { nih_fatal ("Failed to register public D-Bus name '%s': already in use", public_address); dbus_connection_unref (public_dbus_server); exit (EXIT_FAILURE); } } int main (int argc, char *argv[]) { char *private_address = NULL; char *public_address = NULL; program_name = argv[0]; if (argc != 4) { nih_message ("usage: %s ", program_name); nih_message ("\n"); nih_message ("(example private-address: 'unix:abstract=/com/hunt/james/foo')"); nih_message ("(example public-address: 'com.hunt.james.Foo')"); nih_message ("(example path: '/com/hunt/james/Foo')"); exit (EXIT_FAILURE); } private_address = argv[1]; public_address = argv[2]; dbus_path = argv[3]; nih_message ("private_address='%s'", private_address); nih_message ("public_address='%s'", public_address); clients = NIH_MUST (nih_list_new (NULL)); /* Signals */ nih_signal_set_handler (SIGTERM, nih_signal_handler); NIH_MUST (nih_signal_add_handler (NULL, SIGTERM, signal_handler, NULL)); /* Servers */ setup_private_server (private_address); setup_public_server (public_address); /******************************************/ nih_message ("Running main loop at pid %d", (int)getpid ()); nih_main_loop (); /******************************************/ nih_message ("Cleaning up"); /* Free all client connections and list entries */ NIH_LIST_FOREACH_SAFE (clients, iter) { NihListEntry *entry = (NihListEntry *)iter; DBusConnection *conn = (DBusConnection *)entry->data; nih_free (entry); } dbus_connection_unref (public_dbus_server); dbus_server_unref (private_dbus_server); /******************************************/ nih_message ("Done"); return (0); }