blob: c78d8ea6748f900f0edb3dce1aba0f45624b878b [file] [log] [blame]
/* -*- 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 <philb@gnu.org>
* 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 <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <dbus/dbus.h>
#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;
}