dbus-daemon: send signals on connection overflow
[platform/upstream/dbus.git] / tools / dbus-monitor.c
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
24 #include "dbus/dbus-internals.h"
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #ifdef DBUS_WIN
31 #include <winsock2.h>
32 #undef interface
33 #else
34 #include <sys/time.h>
35 #endif
36
37 #include <time.h>
38
39 #include "dbus-print-message.h"
40 #include "tool-common.h"
41
42 #define EAVESDROPPING_RULE "eavesdrop=true"
43
44 #ifndef STDOUT_FILENO
45 #define STDOUT_FILENO 1
46 #endif
47
48 /* http://www.tcpdump.org/linktypes.html */
49 #define LINKTYPE_DBUS 231
50
51 static DBusHandlerResult
52 monitor_filter_func (DBusConnection     *connection,
53                      DBusMessage        *message,
54                      void               *user_data)
55 {
56   long sec = 0, usec = 0;
57
58   _dbus_get_real_time (&sec, &usec);
59
60   print_message (message, FALSE, sec, usec);
61   
62   if (dbus_message_is_signal (message,
63                               DBUS_INTERFACE_LOCAL,
64                               "Disconnected"))
65     exit (0);
66
67   /* Monitors must not allow libdbus to reply to messages, so we eat
68    * the message. See bug 1719.
69    */
70   return DBUS_HANDLER_RESULT_HANDLED;
71 }
72
73 #define TRAP_NULL_STRING(str) ((str) ? (str) : "<none>")
74
75 typedef enum
76 {
77   PROFILE_ATTRIBUTE_FLAG_SERIAL = 1,
78   PROFILE_ATTRIBUTE_FLAG_REPLY_SERIAL = 2,
79   PROFILE_ATTRIBUTE_FLAG_SENDER = 4,
80   PROFILE_ATTRIBUTE_FLAG_DESTINATION = 8,
81   PROFILE_ATTRIBUTE_FLAG_PATH = 16,
82   PROFILE_ATTRIBUTE_FLAG_INTERFACE = 32,
83   PROFILE_ATTRIBUTE_FLAG_MEMBER = 64,
84   PROFILE_ATTRIBUTE_FLAG_ERROR_NAME = 128
85 } ProfileAttributeFlags;
86
87 static void
88 profile_print_headers (void)
89 {
90   printf ("#type\ttimestamp\tserial\tsender\tdestination\tpath\tinterface\tmember\n");
91   printf ("#\t\t\t\t\tin_reply_to\n");
92 }
93
94 static void
95 profile_print_with_attrs (const char *type, DBusMessage *message,
96   long sec, long usec, ProfileAttributeFlags attrs)
97 {
98   printf ("%s\t%ld.%06ld", type, sec, usec);
99
100   if (attrs & PROFILE_ATTRIBUTE_FLAG_SERIAL)
101     printf ("\t%u", dbus_message_get_serial (message));
102
103   if (attrs & PROFILE_ATTRIBUTE_FLAG_SENDER)
104     printf ("\t%s", TRAP_NULL_STRING (dbus_message_get_sender (message)));
105
106   if (attrs & PROFILE_ATTRIBUTE_FLAG_DESTINATION)
107     printf ("\t%s", TRAP_NULL_STRING (dbus_message_get_destination (message)));
108
109   if (attrs & PROFILE_ATTRIBUTE_FLAG_REPLY_SERIAL)
110     printf ("\t%u", dbus_message_get_reply_serial (message));
111
112   if (attrs & PROFILE_ATTRIBUTE_FLAG_PATH)
113     printf ("\t%s", TRAP_NULL_STRING (dbus_message_get_path (message)));
114
115   if (attrs & PROFILE_ATTRIBUTE_FLAG_INTERFACE)
116     printf ("\t%s", TRAP_NULL_STRING (dbus_message_get_interface (message)));
117
118   if (attrs & PROFILE_ATTRIBUTE_FLAG_MEMBER)
119     printf ("\t%s", TRAP_NULL_STRING (dbus_message_get_member (message)));
120
121   if (attrs & PROFILE_ATTRIBUTE_FLAG_ERROR_NAME)
122     printf ("\t%s", TRAP_NULL_STRING (dbus_message_get_error_name (message)));
123
124   printf ("\n");
125 }
126
127 static void
128 print_message_profile (DBusMessage *message)
129 {
130   static dbus_bool_t first = TRUE;
131   long sec = 0, usec = 0;
132
133   if (first)
134     {
135       profile_print_headers ();
136       first = FALSE;
137     }
138
139   _dbus_get_real_time (&sec, &usec);
140
141   switch (dbus_message_get_type (message))
142     {
143       case DBUS_MESSAGE_TYPE_METHOD_CALL:
144         profile_print_with_attrs ("mc", message, sec, usec,
145           PROFILE_ATTRIBUTE_FLAG_SERIAL |
146           PROFILE_ATTRIBUTE_FLAG_SENDER |
147           PROFILE_ATTRIBUTE_FLAG_DESTINATION |
148           PROFILE_ATTRIBUTE_FLAG_PATH |
149           PROFILE_ATTRIBUTE_FLAG_INTERFACE |
150           PROFILE_ATTRIBUTE_FLAG_MEMBER);
151         break;
152       case DBUS_MESSAGE_TYPE_METHOD_RETURN:
153         profile_print_with_attrs ("mr", message, sec, usec,
154           PROFILE_ATTRIBUTE_FLAG_SERIAL |
155           PROFILE_ATTRIBUTE_FLAG_SENDER |
156           PROFILE_ATTRIBUTE_FLAG_DESTINATION |
157           PROFILE_ATTRIBUTE_FLAG_REPLY_SERIAL);
158         break;
159       case DBUS_MESSAGE_TYPE_ERROR:
160         profile_print_with_attrs ("err", message, sec, usec,
161           PROFILE_ATTRIBUTE_FLAG_SERIAL |
162           PROFILE_ATTRIBUTE_FLAG_SENDER |
163           PROFILE_ATTRIBUTE_FLAG_DESTINATION |
164           PROFILE_ATTRIBUTE_FLAG_REPLY_SERIAL);
165         break;
166       case DBUS_MESSAGE_TYPE_SIGNAL:
167         profile_print_with_attrs ("sig", message, sec, usec,
168           PROFILE_ATTRIBUTE_FLAG_SERIAL |
169           PROFILE_ATTRIBUTE_FLAG_SENDER |
170           PROFILE_ATTRIBUTE_FLAG_DESTINATION |
171           PROFILE_ATTRIBUTE_FLAG_PATH |
172           PROFILE_ATTRIBUTE_FLAG_INTERFACE |
173           PROFILE_ATTRIBUTE_FLAG_MEMBER);
174         break;
175       default:
176         printf ("%s\t%ld.%06ld", "tun", sec, usec);
177         break;
178     }
179 }
180
181 static DBusHandlerResult
182 profile_filter_func (DBusConnection     *connection,
183                      DBusMessage        *message,
184                      void               *user_data)
185 {
186   print_message_profile (message);
187
188   if (dbus_message_is_signal (message,
189                               DBUS_INTERFACE_LOCAL,
190                               "Disconnected"))
191     exit (0);
192
193   return DBUS_HANDLER_RESULT_HANDLED;
194 }
195
196 typedef enum {
197     BINARY_MODE_NOT,
198     BINARY_MODE_RAW,
199     BINARY_MODE_PCAP
200 } BinaryMode;
201
202 static DBusHandlerResult
203 binary_filter_func (DBusConnection *connection,
204                     DBusMessage    *message,
205                     void           *user_data)
206 {
207   BinaryMode mode = _DBUS_POINTER_TO_INT (user_data);
208   char *blob;
209   int len;
210
211   /* It would be nice if we could do a zero-copy "peek" one day, but libdbus
212    * is so copy-happy that this isn't really a big deal.
213    */
214   if (!dbus_message_marshal (message, &blob, &len))
215     tool_oom ("retrieving message");
216
217   switch (mode)
218     {
219       case BINARY_MODE_PCAP:
220           {
221             long tv_sec, tv_usec;
222             /* seconds, microseconds, bytes captured (possibly truncated),
223              * original length.
224              * http://wiki.wireshark.org/Development/LibpcapFileFormat
225              */
226             dbus_uint32_t header[4] = { 0, 0, len, len };
227
228             /* If this gets padded then we'd need to write it out in pieces */
229             _DBUS_STATIC_ASSERT (sizeof (header) == 16);
230
231             _dbus_get_real_time (&tv_sec, &tv_usec);
232             header[0] = tv_sec;
233             header[1] = tv_usec;
234
235             if (!tool_write_all (STDOUT_FILENO, header, sizeof (header)))
236               {
237                 perror ("dbus-monitor: write");
238                 exit (1);
239               }
240           }
241         break;
242
243       case BINARY_MODE_NOT:
244         _dbus_assert_not_reached ("wrong filter function");
245         break;
246
247       case BINARY_MODE_RAW:
248       default:
249         /* nothing special, just the raw message stream */
250         break;
251     }
252
253   if (!tool_write_all (STDOUT_FILENO, blob, len))
254     {
255       perror ("dbus-monitor: write");
256       exit (1);
257     }
258
259   dbus_free (blob);
260
261   if (dbus_message_is_signal (message,
262                               DBUS_INTERFACE_LOCAL,
263                               "Disconnected"))
264     exit (0);
265
266   return DBUS_HANDLER_RESULT_HANDLED;
267 }
268
269 static void usage (char *name, int ecode) _DBUS_GNUC_NORETURN;
270
271 static void
272 usage (char *name, int ecode)
273 {
274   fprintf (stderr, "Usage: %s [--system | --session | --address ADDRESS] [--monitor | --profile | --pcap | --binary ] [watch expressions]\n", name);
275   exit (ecode);
276 }
277
278 static void
279 only_one_type (dbus_bool_t *seen_bus_type,
280                char        *name)
281 {
282   if (*seen_bus_type)
283     {
284       fprintf (stderr, "I only support monitoring one bus at a time!\n");
285       usage (name, 1);
286     }
287   else
288     {
289       *seen_bus_type = TRUE;
290     }
291 }
292
293 static dbus_bool_t
294 become_monitor (DBusConnection *connection,
295     int numFilters,
296     const char * const *filters)
297 {
298   DBusError error = DBUS_ERROR_INIT;
299   DBusMessage *m;
300   DBusMessage *r;
301   int i;
302   dbus_uint32_t zero = 0;
303   DBusMessageIter appender, array_appender;
304
305   m = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
306       DBUS_PATH_DBUS, DBUS_INTERFACE_MONITORING, "BecomeMonitor");
307
308   if (m == NULL)
309     tool_oom ("becoming a monitor");
310
311   dbus_message_iter_init_append (m, &appender);
312
313   if (!dbus_message_iter_open_container (&appender, DBUS_TYPE_ARRAY, "s",
314         &array_appender))
315     tool_oom ("opening string array");
316
317   for (i = 0; i < numFilters; i++)
318     {
319       if (!dbus_message_iter_append_basic (&array_appender, DBUS_TYPE_STRING,
320             &filters[i]))
321         tool_oom ("adding filter to array");
322     }
323
324   if (!dbus_message_iter_close_container (&appender, &array_appender) ||
325       !dbus_message_iter_append_basic (&appender, DBUS_TYPE_UINT32, &zero))
326     tool_oom ("finishing arguments");
327
328   r = dbus_connection_send_with_reply_and_block (connection, m, -1, &error);
329
330   if (r != NULL)
331     {
332       dbus_message_unref (r);
333     }
334   else if (dbus_error_has_name (&error, DBUS_ERROR_UNKNOWN_INTERFACE))
335     {
336       fprintf (stderr, "dbus-monitor: unable to enable new-style monitoring, "
337           "your dbus-daemon is too old. Falling back to eavesdropping.\n");
338       dbus_error_free (&error);
339     }
340   else
341     {
342       fprintf (stderr, "dbus-monitor: unable to enable new-style monitoring: "
343           "%s: \"%s\". Falling back to eavesdropping.\n",
344           error.name, error.message);
345       dbus_error_free (&error);
346     }
347
348   dbus_message_unref (m);
349
350   return (r != NULL);
351 }
352
353 int
354 main (int argc, char *argv[])
355 {
356   DBusConnection *connection;
357   DBusError error;
358   DBusBusType type = DBUS_BUS_SESSION;
359   DBusHandleMessageFunction filter_func = monitor_filter_func;
360   char *address = NULL;
361   dbus_bool_t seen_bus_type = FALSE;
362   BinaryMode binary_mode = BINARY_MODE_NOT;
363   int i = 0, j = 0, numFilters = 0;
364   char **filters = NULL;
365
366   /* Set stdout to be unbuffered; this is basically so that if people
367    * do dbus-monitor > file, then send SIGINT via Control-C, they
368    * don't lose the last chunk of messages.
369    */
370
371 #ifdef DBUS_WIN
372   setvbuf (stdout, NULL, _IONBF, 0);
373 #else
374   setvbuf (stdout, NULL, _IOLBF, 0);
375 #endif
376
377   for (i = 1; i < argc; i++)
378     {
379       char *arg = argv[i];
380
381       if (!strcmp (arg, "--system"))
382         {
383           only_one_type (&seen_bus_type, argv[0]);
384           type = DBUS_BUS_SYSTEM;
385         }
386       else if (!strcmp (arg, "--session"))
387         {
388           only_one_type (&seen_bus_type, argv[0]);
389           type = DBUS_BUS_SESSION;
390         }
391       else if (!strcmp (arg, "--address"))
392         {
393           only_one_type (&seen_bus_type, argv[0]);
394
395           if (i+1 < argc)
396             {
397               address = argv[i+1];
398               i++;
399             }
400           else
401             usage (argv[0], 1);
402         }
403       else if (!strcmp (arg, "--help"))
404         usage (argv[0], 0);
405       else if (!strcmp (arg, "--monitor"))
406         {
407           filter_func = monitor_filter_func;
408           binary_mode = BINARY_MODE_NOT;
409         }
410       else if (!strcmp (arg, "--profile"))
411         {
412           filter_func = profile_filter_func;
413           binary_mode = BINARY_MODE_NOT;
414         }
415       else if (!strcmp (arg, "--binary"))
416         {
417           filter_func = binary_filter_func;
418           binary_mode = BINARY_MODE_RAW;
419         }
420       else if (!strcmp (arg, "--pcap"))
421         {
422           filter_func = binary_filter_func;
423           binary_mode = BINARY_MODE_PCAP;
424         }
425       else if (!strcmp (arg, "--"))
426         continue;
427       else if (arg[0] == '-')
428         usage (argv[0], 1);
429       else {
430           unsigned int filter_len;
431           numFilters++;
432           /* Prepend a rule (and a comma) to enable the monitor to eavesdrop.
433            * Prepending allows the user to add eavesdrop=false at command line
434            * in order to disable eavesdropping when needed */
435           filter_len = strlen (EAVESDROPPING_RULE) + 1 + strlen (arg) + 1;
436
437           filters = (char **) realloc (filters, numFilters * sizeof (char *));
438           if (filters == NULL)
439             tool_oom ("adding a new filter slot");
440           filters[j] = (char *) malloc (filter_len);
441           if (filters[j] == NULL)
442             tool_oom ("adding a new filter");
443           snprintf (filters[j], filter_len, "%s,%s", EAVESDROPPING_RULE, arg);
444           j++;
445       }
446     }
447
448   dbus_error_init (&error);
449   
450   if (address != NULL)
451     {
452       connection = dbus_connection_open (address, &error);
453       if (connection)
454         {
455           if (!dbus_bus_register (connection, &error))
456             {
457               fprintf (stderr, "Failed to register connection to bus at %s: %s\n",
458                        address, error.message);
459               dbus_error_free (&error);
460               exit (1);
461             }
462         }
463     }
464   else
465     connection = dbus_bus_get (type, &error);
466   if (connection == NULL)
467     {
468       const char *where;
469       if (address != NULL)
470         where = address;
471       else
472         {
473           switch (type)
474             {
475             case DBUS_BUS_SYSTEM:
476               where = "system bus";
477               break;
478             case DBUS_BUS_SESSION:
479               where = "session bus";
480               break;
481             case DBUS_BUS_STARTER:
482             default:
483               /* We don't set type to anything else */
484               _dbus_assert_not_reached ("impossible bus type");
485             }
486         }
487       fprintf (stderr, "Failed to open connection to %s: %s\n",
488                where,
489                error.message);
490       dbus_error_free (&error);
491       exit (1);
492     }
493
494   /* Receive o.fd.Peer messages as normal messages, rather than having
495    * libdbus handle them internally, which is the wrong thing for
496    * a monitor */
497   dbus_connection_set_route_peer_messages (connection, TRUE);
498
499   if (!dbus_connection_add_filter (connection, filter_func,
500                                    _DBUS_INT_TO_POINTER (binary_mode), NULL))
501     {
502       fprintf (stderr, "Couldn't add filter!\n");
503       exit (1);
504     }
505
506   if (become_monitor (connection, numFilters,
507                       (const char * const *) filters))
508     {
509       /* no more preparation needed */
510     }
511   else if (numFilters)
512     {
513       size_t offset = 0;
514       for (i = 0; i < j; i++)
515         {
516           dbus_bus_add_match (connection, filters[i] + offset, &error);
517           if (dbus_error_is_set (&error) && i == 0 && offset == 0)
518             {
519               /* We might be talking to a pre-1.5.6 dbus-daemon
520               * which wouldn't understand eavesdrop=true.
521               * If this works, carry on with offset > 0
522               * on the remaining iterations. */
523               offset = strlen (EAVESDROPPING_RULE) + 1;
524               dbus_error_free (&error);
525               dbus_bus_add_match (connection, filters[i] + offset, &error);
526             }
527
528           if (dbus_error_is_set (&error))
529             {
530               fprintf (stderr, "Failed to setup match \"%s\": %s\n",
531                        filters[i], error.message);
532               dbus_error_free (&error);
533               exit (1);
534             }
535           free(filters[i]);
536         }
537     }
538   else
539     {
540       dbus_bus_add_match (connection,
541                           EAVESDROPPING_RULE,
542                           &error);
543       if (dbus_error_is_set (&error))
544         {
545           dbus_error_free (&error);
546           dbus_bus_add_match (connection,
547                               "",
548                               &error);
549           if (dbus_error_is_set (&error))
550             goto lose;
551         }
552     }
553
554   switch (binary_mode)
555     {
556       case BINARY_MODE_NOT:
557       case BINARY_MODE_RAW:
558       default:
559         /* no special header needed */
560         break;
561
562       case BINARY_MODE_PCAP:
563           {
564             /* We're not using libpcap because the file format is simple
565              * enough not to need it.
566              * http://wiki.wireshark.org/Development/LibpcapFileFormat */
567             struct {
568                 dbus_uint32_t magic;
569                 dbus_uint16_t major_version;
570                 dbus_uint16_t minor_version;
571                 dbus_int32_t timezone;
572                 dbus_uint32_t precision;
573                 dbus_uint32_t max_length;
574                 dbus_uint32_t link_type;
575             } header = {
576                 0xA1B2C3D4U,  /* magic number */
577                 2, 4,         /* v2.4 */
578                 0,            /* capture in GMT */
579                 0,            /* no opinion on timestamp precision */
580                 (1 << 27),    /* D-Bus spec says so */
581                 LINKTYPE_DBUS
582             };
583
584             /* Assert that there is no padding */
585             _DBUS_STATIC_ASSERT (sizeof (header) == 24);
586
587             if (!tool_write_all (STDOUT_FILENO, &header, sizeof (header)))
588               {
589                 perror ("dbus-monitor: write");
590                 exit (1);
591               }
592           }
593         break;
594     }
595
596   while (dbus_connection_read_write_dispatch(connection, -1))
597     ;
598   exit (0);
599  lose:
600   fprintf (stderr, "Error: %s\n", error.message);
601   exit (1);
602 }
603