From 2aac730b9872386741e8299bfede362ee3c78a2a Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Mon, 26 Sep 2011 11:02:59 +0100 Subject: [PATCH 1/7] Add dbus-test-tool, currently with "echo" and "spam" modes This is installed by default, but easy to filter out for embedded systems or whatever. [ConnectionMode removed -Alban] [dbus-test-tool spam: check for errors in D-Bus replies -Alban] Bug: https://bugs.freedesktop.org/show_bug.cgi?id=34140 --- tools/.gitignore | 1 + tools/Makefile.am | 11 ++ tools/dbus-echo.c | 154 ++++++++++++++++++ tools/dbus-spam.c | 452 ++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/test-tool.c | 86 ++++++++++ tools/test-tool.h | 29 ++++ tools/tool-common.c | 59 +++++++ tools/tool-common.h | 37 +++++ 8 files changed, 829 insertions(+) create mode 100644 tools/dbus-echo.c create mode 100644 tools/dbus-spam.c create mode 100644 tools/test-tool.c create mode 100644 tools/test-tool.h create mode 100644 tools/tool-common.c create mode 100644 tools/tool-common.h diff --git a/tools/.gitignore b/tools/.gitignore index d7c21af..1e0d987 100644 --- a/tools/.gitignore +++ b/tools/.gitignore @@ -19,3 +19,4 @@ dbus-glib-bindings.h run-with-tmp-session-bus.conf print-introspect dbus-bus-introspect.xml +dbus-test-tool diff --git a/tools/Makefile.am b/tools/Makefile.am index 73d95fc..8d38c29 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -15,6 +15,7 @@ bin_PROGRAMS = \ dbus-launch \ dbus-monitor \ dbus-send \ + dbus-test-tool \ $(NULL) if DBUS_UNIX @@ -76,6 +77,16 @@ dbus_launch_LDADD = \ $(DBUS_X_LIBS) \ $(NULL) +dbus_test_tool_SOURCES = \ + dbus-echo.c \ + dbus-spam.c \ + tool-common.c \ + tool-common.h \ + test-tool.c \ + test-tool.h \ + $(NULL) +dbus_test_tool_LDADD = $(top_builddir)/dbus/libdbus-1.la + EXTRA_DIST = run-with-tmp-session-bus.sh strtoll.c strtoull.c CLEANFILES = \ run-with-tmp-session-bus.conf diff --git a/tools/dbus-echo.c b/tools/dbus-echo.c new file mode 100644 index 0000000..f2ba0a9 --- /dev/null +++ b/tools/dbus-echo.c @@ -0,0 +1,154 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-echo.c - a plain libdbus echo server + * + * Copyright © 2003 Philip Blundell + * Copyright © 2011 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#include +#include +#include + +#include + +#include "test-tool.h" +#include "tool-common.h" + +static void +usage (int exit_with) +{ + fprintf (stderr, + "Usage: dbus-test-tool echo [OPTIONS]\n" + "\n" + "Respond to all method calls with an empty reply.\n" + "\n" + "Options:\n" + "\n" + " --name=NAME claim this well-known name first\n" + "\n" + " --sleep=N sleep N milliseconds before sending each reply\n" + "\n" + " --session use the session bus (default)\n" + " --system use the system bus\n" + ); + exit (exit_with); +} + +static DBusHandlerResult +filter (DBusConnection *connection, + DBusMessage *message, + void *user_data) +{ + DBusMessage *reply; + DBusError error = DBUS_ERROR_INIT; + int *sleep_ms = user_data; + + if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_METHOD_CALL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (*sleep_ms > 0) + { + tool_millisleep (*sleep_ms); + } + + reply = dbus_message_new_method_return (message); + + if (reply == NULL) + tool_oom ("allocating reply"); + + if (!dbus_connection_send (connection, reply, NULL)) + tool_oom ("sending reply"); + + dbus_message_unref (reply); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +int +dbus_test_tool_echo (int argc, char **argv) +{ + DBusConnection *connection; + DBusError error = DBUS_ERROR_INIT; + DBusBusType type = DBUS_BUS_SESSION; + int i; + int sleep_ms = -1; + const char *name = NULL; + + /* argv[1] is the tool name, so start from 2 */ + + for (i = 2; i < argc; i++) + { + const char *arg = argv[i]; + + if (strcmp (arg, "--system") == 0) + { + type = DBUS_BUS_SYSTEM; + } + else if (strcmp (arg, "--session") == 0) + { + type = DBUS_BUS_SESSION; + } + else if (strstr (arg, "--name=") == arg) + { + name = arg + strlen ("--name="); + } + else if (strstr (arg, "--sleep-ms=") == arg) + { + sleep_ms = atoi (arg + strlen ("--sleep-ms=")); + } + else + { + usage (2); + } + } + + connection = dbus_bus_get (type, &error); + + if (connection == NULL) + { + fprintf (stderr, "Failed to connect to bus: %s: %s\n", + error.name, error.message); + dbus_error_free (&error); + return 1; + } + + if (name != NULL) + { + if (dbus_bus_request_name (connection, name, DBUS_NAME_FLAG_DO_NOT_QUEUE, + NULL) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) + { + fprintf (stderr, "failed to take bus name %s\n", name); + exit (1); + } + } + else + { + printf ("%s\n", dbus_bus_get_unique_name (connection)); + } + + if (!dbus_connection_add_filter (connection, filter, &sleep_ms, NULL)) + tool_oom ("adding message filter"); + + while (dbus_connection_read_write_dispatch (connection, -1)) + {} + + dbus_connection_unref (connection); + return 0; +} diff --git a/tools/dbus-spam.c b/tools/dbus-spam.c new file mode 100644 index 0000000..b66a9cf --- /dev/null +++ b/tools/dbus-spam.c @@ -0,0 +1,452 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-spam.c - a plain libdbus message-sender, loosely based on dbus-send + * + * Copyright © 2003 Philip Blundell + * Copyright © 2011 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#include +#include +#include +#include + +#include + +#include "test-tool.h" +#include "tool-common.h" + +static void +usage (int ecode) +{ + fprintf (stderr, + "Usage: dbus-test-tool spam [OPTIONS]\n" + "\n" + "Repeatedly call com.example.Spam() on the given D-Bus service.\n" + "\n" + "Options:\n" + "\n" + " --session use the session bus (default)\n" + " --system use the system bus\n" + "\n" + " --dest=NAME call methods on NAME (default " DBUS_SERVICE_DBUS ")\n" + "\n" + " --count=N send N messages (default 1)\n" + " --queue=N queue up N messages at a time (default 1)\n" + " --flood send all messages immediately\n" + " --no-reply set the NO_REPLY flag (implies --flood)\n" + "\n" + " --string send payload as a string (default)\n" + " --bytes send payload as a byte-array\n" + " --empty send an empty payload\n" + "\n" + " --payload=S use S as payload (default \"hello, world!\")\n" + " --stdin read payload from stdin, until EOF\n" + " --message-stdin read a complete D-Bus message from stdin\n" + " --random-size read whitespace-separated ASCII decimal\n" + " payload sizes from stdin and pick one randomly\n" + " for each message\n" + "\n" + " --seed=SEED seed for srand (default is time())\n" + "\n" + ); + exit (ecode); +} + +static void +pc_notify (DBusPendingCall *pc, + void *data) +{ + DBusMessage *message; + int *received_p = data; + DBusError error; + + dbus_error_init (&error); + + message = dbus_pending_call_steal_reply (pc); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + dbus_set_error_from_message (&error, message); + fprintf (stderr, "Failed to receive reply #%d: %s: %s\n", *received_p, + error.name, error.message); + } + else + { + VERBOSE (stderr, "received message #%d\n", *received_p); + } + + (*received_p)++; +} + +static void +consume_stdin (char **payload_p, + size_t *len_p) +{ + const size_t BLOCK_SIZE = 4096; + size_t len = BLOCK_SIZE; + size_t pos = 0; + size_t n; + char *buf; + + buf = dbus_malloc (len); + + if (buf == NULL) + tool_oom ("reading payload from stdin"); + + while (1) + { + if (len - pos < BLOCK_SIZE) + { + char *tmp = dbus_realloc (buf, len + BLOCK_SIZE); + + if (tmp == NULL) + tool_oom ("reading payload from stdin"); + + buf = tmp; + len += BLOCK_SIZE; + } + + n = fread (buf + pos, 1, len - pos, stdin); + + if (n <= 0) + { + /* EOF or error - treat as EOF */ + break; + } + + pos += n; + } + + *len_p = pos; + *payload_p = buf; +} + +int +dbus_test_tool_spam (int argc, char **argv) +{ + DBusConnection *connection; + DBusError error = DBUS_ERROR_INIT; + DBusBusType type = DBUS_BUS_SESSION; + const char *destination = DBUS_SERVICE_DBUS; + int i; + int count = 1; + int sent = 0; + int received = 0; + int queue_len = 1; + const char *payload = NULL; + char *payload_buf = NULL; + size_t payload_len; + int payload_type = DBUS_TYPE_STRING; + DBusMessage *template = NULL; + dbus_bool_t no_reply = FALSE; + unsigned int seed = time (NULL); + int n_random_sizes = 0; + unsigned int *random_sizes = NULL; + + /* argv[1] is the tool name, so start from 2 */ + + for (i = 2; i < argc; i++) + { + const char *arg = argv[i]; + + if (strcmp (arg, "--system") == 0) + { + type = DBUS_BUS_SYSTEM; + } + else if (strcmp (arg, "--session") == 0) + { + type = DBUS_BUS_SESSION; + } + else if (strstr (arg, "--count=") == arg) + { + count = atoi (arg + strlen ("--count=")); + + if (count < 1) + usage (2); + } + else if (strstr (arg, "--dest=") == arg) + { + destination = arg + strlen ("--dest="); + } + else if (strstr (arg, "--payload=") == arg) + { + payload = arg + strlen ("--payload="); + } + else if (strcmp (arg, "--stdin") == 0) + { + consume_stdin (&payload_buf, &payload_len); + payload = payload_buf; + } + else if (strcmp (arg, "--message-stdin") == 0) + { + consume_stdin (&payload_buf, &payload_len); + + template = dbus_message_demarshal (payload, payload_len, &error); + + if (template == NULL) + { + fprintf (stderr, "Unable to demarshal template message: %s: %s", + error.name, error.message); + exit (1); + } + + if (dbus_message_get_type (template) != DBUS_MESSAGE_TYPE_METHOD_CALL) + { + fprintf (stderr, "Template message must be a method call\n"); + exit (1); + } + } + else if (strcmp (arg, "--random-size") == 0) + { + unsigned int len, max = 0; + int consumed = 0; + const char *p; + + consume_stdin (&payload_buf, &payload_len); + + for (p = payload_buf; p < payload_buf + payload_len; p += consumed) + { + /* the space character matches any (or no) whitespace */ + if (sscanf (p, " %u %n", &len, &consumed) == 0) + break; + + n_random_sizes++; + } + + random_sizes = dbus_new0 (int, n_random_sizes); + + if (random_sizes == NULL) + tool_oom ("allocating array of message lengths"); + + for (p = payload_buf, i = 0; + p < payload_buf + payload_len && i < n_random_sizes; + p += consumed, i++) + { + sscanf (p, " %u %n", &len, &consumed); + random_sizes[i] = len; + + if (len > max) + max = len; + } + + dbus_free (payload_buf); + payload_len = max + 1; + payload_buf = dbus_new (char, payload_len); + payload = payload_buf; + + if (payload_buf == NULL) + tool_oom ("allocating maximum-sized payload"); + + memset (payload_buf, 'X', payload_len); + payload_buf[payload_len - 1] = '\0'; + } + else if (strcmp (arg, "--empty") == 0) + { + payload_type = DBUS_TYPE_INVALID; + } + else if (strcmp (arg, "--string") == 0) + { + payload_type = DBUS_TYPE_STRING; + } + else if (strcmp (arg, "--bytes") == 0) + { + payload_type = DBUS_TYPE_ARRAY; + } + else if (strcmp (arg, "--flood") == 0) + { + queue_len = -1; + } + else if (strcmp (arg, "--no-reply") == 0) + { + queue_len = -1; + no_reply = TRUE; + } + else if (strstr (arg, "--queue=") == arg) + { + queue_len = atoi (arg + strlen ("--queue=")); + + if (queue_len < 1) + usage (2); + } + else if (strstr (arg, "--seed=") == arg) + { + seed = strtoul (arg + strlen ("--seed="), NULL, 10); + } + else + { + usage (2); + } + } + + if (payload == NULL) + { + payload = "hello, world!"; + payload_len = strlen (payload); + } + + VERBOSE (stderr, "Will send up to %d messages, with up to %d queued\n", + count, queue_len); + + connection = dbus_bus_get_private (type, &error); + + if (connection == NULL) + { + fprintf (stderr, "Failed to connect to bus: %s: %s\n", + error.name, error.message); + dbus_error_free (&error); + return 1; + } + + while (no_reply ? sent < count : received < count) + { + while (sent < count && + (queue_len == -1 || sent < queue_len + received)) + { + DBusMessage *message; + + if (template != NULL) + { + message = dbus_message_copy (template); + + if (message == NULL) + tool_oom ("copying message"); + + dbus_message_set_no_reply (message, no_reply); + } + else + { + dbus_bool_t mem; + int chosen = -1; + unsigned int len = 0; + + message = dbus_message_new_method_call (destination, + "/", + "com.example", + "Spam"); + + if (message == NULL) + tool_oom ("allocating message"); + + dbus_message_set_no_reply (message, no_reply); + + switch (payload_type) + { + case DBUS_TYPE_STRING: + if (random_sizes != NULL) + { + /* this isn't fair, strictly speaking - the first few + * are a bit more likely to be chosen, unless + * RAND_MAX is divisible by n_random_sizes - but it's + * good enough for traffic-generation */ + len = random_sizes[rand () % n_random_sizes]; + payload_buf[len] = '\0'; + } + + mem = dbus_message_append_args (message, + DBUS_TYPE_STRING, &payload, + DBUS_TYPE_INVALID); + + if (random_sizes != NULL) + { + /* undo the truncation above */ + payload_buf[len] = 'X'; + } + + break; + + case DBUS_TYPE_ARRAY: + len = payload_len; + + /* as above, not strictly fair, but close enough */ + if (random_sizes != NULL) + len = random_sizes[rand () % n_random_sizes]; + + mem = dbus_message_append_args (message, + DBUS_TYPE_ARRAY, + DBUS_TYPE_BYTE, + &payload, + (dbus_uint32_t) len, + DBUS_TYPE_INVALID); + break; + + default: + mem = TRUE; + } + + if (!mem) + tool_oom ("building message"); + } + + if (no_reply) + { + if (!dbus_connection_send (connection, message, NULL)) + tool_oom ("sending message"); + + sent++; + } + else + { + DBusPendingCall *pc; + + if (!dbus_connection_send_with_reply (connection, + message, + &pc, + DBUS_TIMEOUT_INFINITE)) + tool_oom ("sending message"); + + VERBOSE (stderr, "sent message #%d\n", sent); + sent++; + + if (pc == NULL) + tool_oom ("sending message"); + + if (dbus_pending_call_get_completed (pc)) + pc_notify (pc, &received); + else if (!dbus_pending_call_set_notify (pc, pc_notify, &received, + NULL)) + tool_oom ("setting pending call notifier"); + + dbus_pending_call_unref (pc); + } + + dbus_message_unref (message); + } + + if (!dbus_connection_read_write_dispatch (connection, -1)) + { + fprintf (stderr, "Disconnected from bus\n"); + exit (1); + } + } + + dbus_connection_flush (connection); + + VERBOSE (stderr, "Done\n"); + + dbus_free (payload_buf); + + if (template != NULL) + dbus_message_unref (template); + + dbus_connection_close (connection); + dbus_connection_unref (connection); + dbus_shutdown (); + return 0; +} diff --git a/tools/test-tool.c b/tools/test-tool.c new file mode 100644 index 0000000..86af15f --- /dev/null +++ b/tools/test-tool.c @@ -0,0 +1,86 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-test-tool - D-Bus swiss army knife + * + * Copyright © 2003 Philip Blundell + * Copyright © 2011 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include "test-tool.h" + +#include +#include +#include + +#include + +static struct { + const char *name; + int (*callback) (int, char **); +} subcommands[] = { + { "echo", dbus_test_tool_echo }, + { "spam", dbus_test_tool_spam }, + { NULL, NULL } +}; + +static void usage (int exit_with) _DBUS_GNUC_NORETURN; + +static void +usage (int exit_with) +{ + int i; + + fprintf (stderr, + "Usage: dbus-test-tool SUBCOMMAND [OPTIONS]\n" + "\n" + "Known SUBCOMMANDs are:\n" + "\n" + ); + + for (i = 0; subcommands[i].name != NULL; i++) + { + fprintf (stderr, "- %s\n", subcommands[i].name); + } + + fprintf (stderr, + "\n" + "For more information: dbus-test-tool SUBCOMMAND --help\n" + ); + + exit (exit_with); +} + +int +main (int argc, char **argv) +{ + int i; + + if (argc < 2) + { + usage (2); + } + + for (i = 0; subcommands[i].name != NULL; i++) + { + if (!strcmp (argv[1], subcommands[i].name)) + return subcommands[i].callback (argc, argv); + } + + usage (2); + return 2; +} diff --git a/tools/test-tool.h b/tools/test-tool.h new file mode 100644 index 0000000..a6a4073 --- /dev/null +++ b/tools/test-tool.h @@ -0,0 +1,29 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-test-tool - D-Bus swiss army knife + * + * Copyright © 2003 Philip Blundell + * Copyright © 2011 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_TEST_TOOL_H +#define DBUS_TEST_TOOL_H + +int dbus_test_tool_echo (int argc, char **argv); +int dbus_test_tool_spam (int argc, char **argv); + +#endif diff --git a/tools/tool-common.c b/tools/tool-common.c new file mode 100644 index 0000000..2e48371 --- /dev/null +++ b/tools/tool-common.c @@ -0,0 +1,59 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* tool-common - common functionality for dbus-test-tool modules + * + * Copyright © 2003 Philip Blundell + * Copyright © 2011 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include "tool-common.h" + +#include +#include +#include +#include + +#ifdef DBUS_WIN +#include +#endif + +/* a hack to avoid having to depend on the static -util version of libdbus; + * it's useful for ancillary programs to be able to use the shared library */ +void +tool_millisleep (int ms) +{ +#ifdef DBUS_WIN + Sleep (ms); +#else + fd_set nothing; + struct timeval tv; + + tv.tv_sec = ms / 1000; + tv.tv_usec = (ms % 1000) * 1000; + + FD_ZERO (¬hing); + select (1, ¬hing, ¬hing, ¬hing, &tv); +#endif +} + +void +tool_oom (const char *doing) +{ + fprintf (stderr, "OOM while %s\n", doing); + exit (1); +} diff --git a/tools/tool-common.h b/tools/tool-common.h new file mode 100644 index 0000000..ae639ed --- /dev/null +++ b/tools/tool-common.h @@ -0,0 +1,37 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* tool-common - common functionality for dbus-test-tool modules + * + * Copyright © 2003 Philip Blundell + * Copyright © 2011 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_TOOL_COMMON_H +#define DBUS_TOOL_COMMON_H + +#include + +#if 1 +#define VERBOSE fprintf +#else +#define VERBOSE(...) do {} while (0) +#endif + +void tool_millisleep (int ms); +void tool_oom (const char *doing); + +#endif -- 1.8.5.3