/* -*- 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 * Copyright © 2014 Collabora Ltd. * * 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 dbus_bool_t ignore_errors = FALSE; 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" " --ignore-errors ignore errors\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" " --messages-per-conn=N after sending messages-per-conn, wait\n" " for the pending replies if any, then reconnect\n" " (default: don't reconnect)\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 (!ignore_errors && 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 = NULL; DBusError error = DBUS_ERROR_INIT; DBusBusType type = DBUS_BUS_SESSION; const char *destination = DBUS_SERVICE_DBUS; int i; int count = 1; int sent = 0; unsigned int sent_in_this_conn = 0; int received = 0; unsigned int received_before_this_conn = 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 flood = FALSE; dbus_bool_t no_reply = FALSE; unsigned int messages_per_conn = 0; 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 (strcmp (arg, "--ignore-errors") == 0) { ignore_errors = TRUE; } 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); payload = payload_buf; 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 j, 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, j = 0; p < payload_buf + payload_len && j < n_random_sizes; p += consumed, j++) { sscanf (p, " %u %n", &len, &consumed); random_sizes[j] = 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) { if (queue_len > 1) usage (2); if (messages_per_conn > 0) usage (2); flood = TRUE; queue_len = -1; } else if (strcmp (arg, "--no-reply") == 0) { if (queue_len > 1) usage (2); queue_len = -1; no_reply = TRUE; } else if (strstr (arg, "--queue=") == arg) { if (flood || no_reply) usage (2); 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 if (strstr (arg, "--messages-per-conn=") == arg) { messages_per_conn = atoi (arg + strlen ("--messages-per-conn=")); if (messages_per_conn > 0 && flood) usage (2); } else { usage (2); } } srand (seed); if (payload == NULL) { payload = "hello, world!"; payload_len = strlen (payload); } VERBOSE (stderr, "Will send up to %d messages, with up to %d queued, max %d per connection\n", count, queue_len, messages_per_conn); while (no_reply ? sent < count : received < count) { /* Connect? * - In the first iteration * or * - When messages_per_conn messages have been sent and no replies are being waited for */ if (connection == NULL || (messages_per_conn > 0 && sent_in_this_conn == messages_per_conn && (no_reply || received - received_before_this_conn == messages_per_conn))) { if (connection != NULL) { dbus_connection_flush (connection); dbus_connection_close (connection); dbus_connection_unref (connection); } VERBOSE (stderr, "New connection.\n"); 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); dbus_free (random_sizes); dbus_free (payload_buf); return 1; } sent_in_this_conn = 0; received_before_this_conn = received; } /* Send another message? Only if we don't exceed the 3 limits: * - total amount of messages * - messages sent on this connection * - queue */ while (sent < count && (messages_per_conn == 0 || sent_in_this_conn < messages_per_conn) && (queue_len == -1 || sent_in_this_conn < queue_len + received - received_before_this_conn)) { 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; 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"); VERBOSE (stderr, "sent message #%d\n", sent); sent++; sent_in_this_conn++; } 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++; sent_in_this_conn++; 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); } } if (connection != NULL) { dbus_connection_flush (connection); dbus_connection_close (connection); dbus_connection_unref (connection); } VERBOSE (stderr, "Done\n"); dbus_free (payload_buf); dbus_free (random_sizes); if (template != NULL) dbus_message_unref (template); dbus_shutdown (); return 0; }