1/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2/* dbus-monitor.c  Utility program to monitor messages on the bus
3 *
4 * Copyright (C) 2003 Philip Blundell <philb@gnu.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19 *
20 */
21
22#include <config.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26
27#ifdef DBUS_WIN
28#include <winsock2.h>
29#undef interface
30#else
31#include <sys/time.h>
32#endif
33
34#include <time.h>
35
36#include "dbus-print-message.h"
37
38#define EAVESDROPPING_RULE "eavesdrop=true"
39
40#ifdef DBUS_WIN
41
42/* gettimeofday is not defined on windows */
43#define DBUS_SECONDS_SINCE_1601 11644473600LL
44#define DBUS_USEC_IN_SEC        1000000LL
45
46#ifdef DBUS_WINCE
47
48#ifndef _IOLBF
49#define _IOLBF 0x40
50#endif
51#ifndef _IONBF
52#define _IONBF 0x04
53#endif
54
55void
56GetSystemTimeAsFileTime (LPFILETIME ftp)
57{
58  SYSTEMTIME st;
59  GetSystemTime (&st);
60  SystemTimeToFileTime (&st, ftp);
61}
62#endif
63
64static int
65gettimeofday (struct timeval *__p,
66	      void *__t)
67{
68  union {
69      unsigned long long ns100; /*time since 1 Jan 1601 in 100ns units */
70      FILETIME           ft;
71    } now;
72
73  GetSystemTimeAsFileTime (&now.ft);
74  __p->tv_usec = (long) ((now.ns100 / 10LL) % DBUS_USEC_IN_SEC);
75  __p->tv_sec  = (long)(((now.ns100 / 10LL) / DBUS_SECONDS_SINCE_1601) - DBUS_SECONDS_SINCE_1601);
76
77  return 0;
78}
79#endif
80
81inline static void
82oom (const char *doing)
83{
84  fprintf (stderr, "OOM while %s\n", doing);
85  exit (1);
86}
87
88static DBusHandlerResult
89monitor_filter_func (DBusConnection     *connection,
90		     DBusMessage        *message,
91		     void               *user_data)
92{
93  print_message (message, FALSE);
94
95  if (dbus_message_is_signal (message,
96                              DBUS_INTERFACE_LOCAL,
97                              "Disconnected"))
98    exit (0);
99
100  /* Conceptually we want this to be
101   * DBUS_HANDLER_RESULT_NOT_YET_HANDLED, but this raises
102   * some problems.  See bug 1719.
103   */
104  return DBUS_HANDLER_RESULT_HANDLED;
105}
106
107#ifdef __APPLE__
108#define PROFILE_TIMED_FORMAT "%s\t%lu\t%d"
109#else
110#define PROFILE_TIMED_FORMAT "%s\t%lu\t%lu"
111#endif
112#define TRAP_NULL_STRING(str) ((str) ? (str) : "<none>")
113
114typedef enum
115{
116  PROFILE_ATTRIBUTE_FLAG_SERIAL = 1,
117  PROFILE_ATTRIBUTE_FLAG_REPLY_SERIAL = 2,
118  PROFILE_ATTRIBUTE_FLAG_SENDER = 4,
119  PROFILE_ATTRIBUTE_FLAG_DESTINATION = 8,
120  PROFILE_ATTRIBUTE_FLAG_PATH = 16,
121  PROFILE_ATTRIBUTE_FLAG_INTERFACE = 32,
122  PROFILE_ATTRIBUTE_FLAG_MEMBER = 64,
123  PROFILE_ATTRIBUTE_FLAG_ERROR_NAME = 128
124} ProfileAttributeFlags;
125
126static void
127profile_print_with_attrs (const char *type, DBusMessage *message,
128  struct timeval *t, ProfileAttributeFlags attrs)
129{
130  printf (PROFILE_TIMED_FORMAT, type, t->tv_sec, t->tv_usec);
131
132  if (attrs & PROFILE_ATTRIBUTE_FLAG_SERIAL)
133    printf ("\t%u", dbus_message_get_serial (message));
134
135  if (attrs & PROFILE_ATTRIBUTE_FLAG_REPLY_SERIAL)
136    printf ("\t%u", dbus_message_get_reply_serial (message));
137
138  if (attrs & PROFILE_ATTRIBUTE_FLAG_SENDER)
139    printf ("\t%s", TRAP_NULL_STRING (dbus_message_get_sender (message)));
140
141  if (attrs & PROFILE_ATTRIBUTE_FLAG_DESTINATION)
142    printf ("\t%s", TRAP_NULL_STRING (dbus_message_get_destination (message)));
143
144  if (attrs & PROFILE_ATTRIBUTE_FLAG_PATH)
145    printf ("\t%s", TRAP_NULL_STRING (dbus_message_get_path (message)));
146
147  if (attrs & PROFILE_ATTRIBUTE_FLAG_INTERFACE)
148    printf ("\t%s", TRAP_NULL_STRING (dbus_message_get_interface (message)));
149
150  if (attrs & PROFILE_ATTRIBUTE_FLAG_MEMBER)
151    printf ("\t%s", TRAP_NULL_STRING (dbus_message_get_member (message)));
152
153  if (attrs & PROFILE_ATTRIBUTE_FLAG_ERROR_NAME)
154    printf ("\t%s", TRAP_NULL_STRING (dbus_message_get_error_name (message)));
155
156  printf ("\n");
157}
158
159static void
160print_message_profile (DBusMessage *message)
161{
162  struct timeval t;
163
164  if (gettimeofday (&t, NULL) < 0)
165    {
166      printf ("un\n");
167      return;
168    }
169
170  switch (dbus_message_get_type (message))
171    {
172      case DBUS_MESSAGE_TYPE_METHOD_CALL:
173	profile_print_with_attrs ("mc", message, &t,
174	  PROFILE_ATTRIBUTE_FLAG_SERIAL |
175	  PROFILE_ATTRIBUTE_FLAG_SENDER |
176	  PROFILE_ATTRIBUTE_FLAG_PATH |
177	  PROFILE_ATTRIBUTE_FLAG_INTERFACE |
178	  PROFILE_ATTRIBUTE_FLAG_MEMBER);
179	break;
180      case DBUS_MESSAGE_TYPE_METHOD_RETURN:
181	profile_print_with_attrs ("mr", message, &t,
182	  PROFILE_ATTRIBUTE_FLAG_SERIAL |
183	  PROFILE_ATTRIBUTE_FLAG_DESTINATION |
184	  PROFILE_ATTRIBUTE_FLAG_REPLY_SERIAL);
185	break;
186      case DBUS_MESSAGE_TYPE_ERROR:
187	profile_print_with_attrs ("err", message, &t,
188	  PROFILE_ATTRIBUTE_FLAG_SERIAL |
189	  PROFILE_ATTRIBUTE_FLAG_DESTINATION |
190	  PROFILE_ATTRIBUTE_FLAG_REPLY_SERIAL);
191	break;
192      case DBUS_MESSAGE_TYPE_SIGNAL:
193	profile_print_with_attrs ("sig", message, &t,
194	  PROFILE_ATTRIBUTE_FLAG_SERIAL |
195	  PROFILE_ATTRIBUTE_FLAG_PATH |
196	  PROFILE_ATTRIBUTE_FLAG_INTERFACE |
197	  PROFILE_ATTRIBUTE_FLAG_MEMBER);
198	break;
199      default:
200	printf (PROFILE_TIMED_FORMAT "\n", "tun", t.tv_sec, t.tv_usec);
201	break;
202    }
203}
204
205static DBusHandlerResult
206profile_filter_func (DBusConnection	*connection,
207		     DBusMessage	*message,
208		     void		*user_data)
209{
210  print_message_profile (message);
211
212  if (dbus_message_is_signal (message,
213                              DBUS_INTERFACE_LOCAL,
214                              "Disconnected"))
215    exit (0);
216
217  return DBUS_HANDLER_RESULT_HANDLED;
218}
219
220static void
221usage (char *name, int ecode)
222{
223  fprintf (stderr, "Usage: %s [--system | --session | --address ADDRESS] [--monitor | --profile ] [watch expressions]\n", name);
224  exit (ecode);
225}
226
227static void
228only_one_type (dbus_bool_t *seen_bus_type,
229               char        *name)
230{
231  if (*seen_bus_type)
232    {
233      fprintf (stderr, "I only support monitoring one bus at a time!\n");
234      usage (name, 1);
235    }
236  else
237    {
238      *seen_bus_type = TRUE;
239    }
240}
241
242int
243main (int argc, char *argv[])
244{
245  DBusConnection *connection;
246  DBusError error;
247  DBusBusType type = DBUS_BUS_SESSION;
248  DBusHandleMessageFunction filter_func = monitor_filter_func;
249  char *address = NULL;
250  dbus_bool_t seen_bus_type = FALSE;
251
252  int i = 0, j = 0, numFilters = 0;
253  char **filters = NULL;
254
255  /* Set stdout to be unbuffered; this is basically so that if people
256   * do dbus-monitor > file, then send SIGINT via Control-C, they
257   * don't lose the last chunk of messages.
258   */
259
260#ifdef DBUS_WIN
261  setvbuf (stdout, NULL, _IONBF, 0);
262#else
263  setvbuf (stdout, NULL, _IOLBF, 0);
264#endif
265
266  for (i = 1; i < argc; i++)
267    {
268      char *arg = argv[i];
269
270      if (!strcmp (arg, "--system"))
271        {
272          only_one_type (&seen_bus_type, argv[0]);
273          type = DBUS_BUS_SYSTEM;
274        }
275      else if (!strcmp (arg, "--session"))
276        {
277          only_one_type (&seen_bus_type, argv[0]);
278          type = DBUS_BUS_SESSION;
279        }
280      else if (!strcmp (arg, "--address"))
281        {
282          only_one_type (&seen_bus_type, argv[0]);
283
284          if (i+1 < argc)
285            {
286              address = argv[i+1];
287              i++;
288            }
289          else
290            usage (argv[0], 1);
291        }
292      else if (!strcmp (arg, "--help"))
293	usage (argv[0], 0);
294      else if (!strcmp (arg, "--monitor"))
295	filter_func = monitor_filter_func;
296      else if (!strcmp (arg, "--profile"))
297	filter_func = profile_filter_func;
298      else if (!strcmp (arg, "--"))
299	continue;
300      else if (arg[0] == '-')
301	usage (argv[0], 1);
302      else {
303          unsigned int filter_len;
304          numFilters++;
305          /* Prepend a rule (and a comma) to enable the monitor to eavesdrop.
306           * Prepending allows the user to add eavesdrop=false at command line
307           * in order to disable eavesdropping when needed */
308          filter_len = strlen (EAVESDROPPING_RULE) + 1 + strlen (arg) + 1;
309
310          filters = (char **) realloc (filters, numFilters * sizeof (char *));
311          if (filters == NULL)
312            oom ("adding a new filter slot");
313          filters[j] = (char *) malloc (filter_len * sizeof (char *));
314          if (filters[j] == NULL)
315            oom ("adding a new filter");
316          snprintf (filters[j], filter_len, "%s,%s", EAVESDROPPING_RULE, arg);
317          j++;
318      }
319    }
320
321  dbus_error_init (&error);
322
323  if (address != NULL)
324    {
325      connection = dbus_connection_open (address, &error);
326      if (connection)
327        {
328          if (!dbus_bus_register (connection, &error))
329      	    {
330              fprintf (stderr, "Failed to register connection to bus at %s: %s\n",
331      	               address, error.message);
332              dbus_error_free (&error);
333              exit (1);
334      	    }
335        }
336    }
337  else
338    connection = dbus_bus_get (type, &error);
339  if (connection == NULL)
340    {
341      const char *where;
342      if (address != NULL)
343        where = address;
344      else
345        {
346          switch (type)
347            {
348            case DBUS_BUS_SYSTEM:
349              where = "system bus";
350              break;
351            case DBUS_BUS_SESSION:
352              where = "session bus";
353              break;
354            default:
355              where = "";
356            }
357        }
358      fprintf (stderr, "Failed to open connection to %s: %s\n",
359               where,
360               error.message);
361      dbus_error_free (&error);
362      exit (1);
363    }
364
365  if (numFilters)
366    {
367      for (i = 0; i < j; i++)
368        {
369          dbus_bus_add_match (connection, filters[i], &error);
370          if (dbus_error_is_set (&error))
371            {
372              fprintf (stderr, "Failed to setup match \"%s\": %s\n",
373                       filters[i], error.message);
374              dbus_error_free (&error);
375              exit (1);
376            }
377	  free(filters[i]);
378        }
379    }
380  else
381    {
382      dbus_bus_add_match (connection,
383		          EAVESDROPPING_RULE ",type='signal'",
384		          &error);
385      if (dbus_error_is_set (&error))
386        goto lose;
387      dbus_bus_add_match (connection,
388		          EAVESDROPPING_RULE ",type='method_call'",
389		          &error);
390      if (dbus_error_is_set (&error))
391        goto lose;
392      dbus_bus_add_match (connection,
393		          EAVESDROPPING_RULE ",type='method_return'",
394		          &error);
395      if (dbus_error_is_set (&error))
396        goto lose;
397      dbus_bus_add_match (connection,
398		          EAVESDROPPING_RULE ",type='error'",
399		          &error);
400      if (dbus_error_is_set (&error))
401        goto lose;
402    }
403
404  if (!dbus_connection_add_filter (connection, filter_func, NULL, NULL)) {
405    fprintf (stderr, "Couldn't add filter!\n");
406    exit (1);
407  }
408
409  while (dbus_connection_read_write_dispatch(connection, -1))
410    ;
411  exit (0);
412 lose:
413  fprintf (stderr, "Error: %s\n", error.message);
414  exit (1);
415}
416
417