set handlers patch

Havoc Pennington hp at redhat.com
Fri Nov 24 18:10:22 PST 2006


Havoc Pennington wrote:
> Attached is an API patch (you'll notice the implementation is empty) to 
> add dbus_set_log_handler() and dbus_set_bug_handler().
> 
> The primary purpose of dbus_set_log_handler() is to allow the bus daemon 
> to use syslog() instead of printing to stderr.
> 
> The primary purpose of dbus_set_bug_handler() is to allow apps with 
> special needs to hook into failed checks and control the behavior to the 
> extent possible.
> 

Attached is a patch with slightly changed API and an implementation. The 
implementation inherits some cruft from the current 
_dbus_warn/_dbus_verbose implementations, might clean that up in an 
additional patch after putting this one in.

Havoc

-------------- next part --------------
Index: ChangeLog
===================================================================
RCS file: /cvs/dbus/dbus/ChangeLog,v
retrieving revision 1.1206
diff -u -p -r1.1206 ChangeLog
--- ChangeLog	15 Nov 2006 03:07:59 -0000	1.1206
+++ ChangeLog	25 Nov 2006 01:59:24 -0000
@@ -1,3 +1,9 @@
+2006-11-24  Havoc Pennington  <hp at redhat.com>
+
+	* dbus/dbus-misc.c: Add new API dbus_set_bug_handler(),
+	dbus_set_log_handler(). Move the implementations of _dbus_warn,
+	dbus_warn_check_failed, and _dbus_verbose into this file.
+
 2006-11-14  Havoc Pennington  <hp at redhat.com>
 	
 	* dbus/dbus-misc.c, dbus/dbus-misc.h: Move
Index: dbus/dbus-errors.c
===================================================================
RCS file: /cvs/dbus/dbus/dbus/dbus-errors.c,v
retrieving revision 1.32
diff -u -p -r1.32 dbus-errors.c
--- dbus/dbus-errors.c	21 Oct 2006 17:08:08 -0000	1.32
+++ dbus/dbus-errors.c	25 Nov 2006 01:59:24 -0000
@@ -145,7 +145,27 @@ message_from_error (const char *error)
  * warnings, exit on a failed assertion, or even crash in those cases
  * (in other words, incorrect use of the API results in undefined
  * behavior, possibly accompanied by helpful debugging output if
- * you're lucky).
+ * you're lucky). See dbus_set_bug_handler() for more details.
+ *
+ * Functions have a #DBusError parameter only if they can report
+ * multiple different errors that need to be distinguished.  Some
+ * functions can report zero errors (they always succeed), while
+ * others have only one possible error (usually "insufficient
+ * memory").  If a function has only one possible error it may simply
+ * return #FALSE or #NULL to indicate that the error occurred, and not
+ * bother with #DBusError. See the documentation for each specific
+ * function for more.
+ *
+ * #DBusError may be set from a message of type
+ * #DBUS_MESSAGE_TYPE_ERROR received from another application. In
+ * these cases, you should not make any assumptions about the
+ * content of error names and messages, other than those guaranteed
+ * by the D-Bus protocol (for example, the protocol guarantees
+ * valid UTF-8 and sane message length). The remote app can provide
+ * any error name and message it wants, though, and libdbus has
+ * no way to validate it.
+ * dbus_connection_send_with_reply_and_block() is one example
+ * of a function that gets its error from a remote application.
  * 
  * @{
  */
Index: dbus/dbus-internals.c
===================================================================
RCS file: /cvs/dbus/dbus/dbus/dbus-internals.c,v
retrieving revision 1.55
diff -u -p -r1.55 dbus-internals.c
--- dbus/dbus-internals.c	15 Nov 2006 01:52:01 -0000	1.55
+++ dbus/dbus-internals.c	25 Nov 2006 01:59:24 -0000
@@ -191,195 +191,6 @@
  */
 const char _dbus_no_memory_message[] = "Not enough memory";
 
-static dbus_bool_t warn_initted = FALSE;
-static dbus_bool_t fatal_warnings = FALSE;
-static dbus_bool_t fatal_warnings_on_check_failed = TRUE;
-
-static void
-init_warnings(void)
-{
-  if (!warn_initted)
-    {
-      const char *s;
-      s = _dbus_getenv ("DBUS_FATAL_WARNINGS");
-      if (s && *s)
-        {
-          if (*s == '0')
-            {
-              fatal_warnings = FALSE;
-              fatal_warnings_on_check_failed = FALSE;
-            }
-          else if (*s == '1')
-            {
-              fatal_warnings = TRUE;
-              fatal_warnings_on_check_failed = TRUE;
-            }
-          else
-            {
-              fprintf(stderr, "DBUS_FATAL_WARNINGS should be set to 0 or 1 if set, not '%s'",
-                      s);
-            }
-        }
-
-      warn_initted = TRUE;
-    }
-}
-
-/**
- * Prints a warning message to stderr. Can optionally be made to exit
- * fatally by setting DBUS_FATAL_WARNINGS, but this is rarely
- * used. This function should be considered pretty much equivalent to
- * fprintf(stderr). _dbus_warn_check_failed() on the other hand is
- * suitable for use when a programming mistake has been made.
- *
- * @param format printf-style format string.
- */
-void
-_dbus_warn (const char *format,
-            ...)
-{
-  va_list args;
-
-  if (!warn_initted)
-    init_warnings ();
-  
-  va_start (args, format);
-  vfprintf (stderr, format, args);
-  va_end (args);
-
-  if (fatal_warnings)
-    {
-      fflush (stderr);
-      _dbus_abort ();
-    }
-}
-
-/**
- * Prints a "critical" warning to stderr when an assertion fails;
- * differs from _dbus_warn primarily in that it prefixes the pid and
- * defaults to fatal. This should be used only when a programming
- * error has been detected. (NOT for unavoidable errors that an app
- * might handle - those should be returned as DBusError.) Calling this
- * means "there is a bug"
- */
-void
-_dbus_warn_check_failed(const char *format,
-                        ...)
-{
-  va_list args;
-  
-  if (!warn_initted)
-    init_warnings ();
-
-  fprintf (stderr, "process %lu: ", _dbus_getpid ());
-  
-  va_start (args, format);
-  vfprintf (stderr, format, args);
-  va_end (args);
-
-  if (fatal_warnings_on_check_failed)
-    {
-      fflush (stderr);
-      _dbus_abort ();
-    }
-}
-
-#ifdef DBUS_ENABLE_VERBOSE_MODE
-
-static dbus_bool_t verbose_initted = FALSE;
-static dbus_bool_t verbose = TRUE;
-
-/** Whether to show the current thread in verbose messages */
-#define PTHREAD_IN_VERBOSE 0
-#if PTHREAD_IN_VERBOSE
-#include <pthread.h>
-#endif
-
-static inline void
-_dbus_verbose_init (void)
-{
-  if (!verbose_initted)
-    {
-      const char *p = _dbus_getenv ("DBUS_VERBOSE"); 
-      verbose = p != NULL && *p == '1';
-      verbose_initted = TRUE;
-    }
-}
-
-/**
- * Implementation of dbus_is_verbose() macro if built with verbose logging
- * enabled.
- * @returns whether verbose logging is active.
- */
-dbus_bool_t
-_dbus_is_verbose_real (void)
-{
-  _dbus_verbose_init ();
-  return verbose;
-}
-
-/**
- * Prints a warning message to stderr
- * if the user has enabled verbose mode.
- * This is the real function implementation,
- * use _dbus_verbose() macro in code.
- *
- * @param format printf-style format string.
- */
-void
-_dbus_verbose_real (const char *format,
-                    ...)
-{
-  va_list args;
-  static dbus_bool_t need_pid = TRUE;
-  int len;
-  
-  /* things are written a bit oddly here so that
-   * in the non-verbose case we just have the one
-   * conditional and return immediately.
-   */
-  if (!_dbus_is_verbose_real())
-    return;
-
-  /* Print out pid before the line */
-  if (need_pid)
-    {
-#if PTHREAD_IN_VERBOSE
-      fprintf (stderr, "%lu: 0x%lx: ", _dbus_getpid (), pthread_self ());
-#else
-      fprintf (stderr, "%lu: ", _dbus_getpid ());
-#endif
-    }
-      
-
-  /* Only print pid again if the next line is a new line */
-  len = strlen (format);
-  if (format[len-1] == '\n')
-    need_pid = TRUE;
-  else
-    need_pid = FALSE;
-  
-  va_start (args, format);
-  vfprintf (stderr, format, args);
-  va_end (args);
-
-  fflush (stderr);
-}
-
-/**
- * Reinitializes the verbose logging code, used
- * as a hack in dbus-spawn.c so that a child
- * process re-reads its pid
- *
- */
-void
-_dbus_verbose_reset_real (void)
-{
-  verbose_initted = FALSE;
-}
-
-#endif /* DBUS_ENABLE_VERBOSE_MODE */
-
 /**
  * Duplicates a string. Result must be freed with
  * dbus_free(). Returns #NULL if memory allocation fails.
Index: dbus/dbus-internals.h
===================================================================
RCS file: /cvs/dbus/dbus/dbus/dbus-internals.h,v
retrieving revision 1.65
diff -u -p -r1.65 dbus-internals.h
--- dbus/dbus-internals.h	15 Nov 2006 01:52:01 -0000	1.65
+++ dbus/dbus-internals.h	25 Nov 2006 01:59:25 -0000
@@ -45,7 +45,6 @@ void _dbus_warn               (const cha
 void _dbus_warn_check_failed  (const char *format,
                                ...) _DBUS_GNUC_PRINTF (1, 2);
 
-
 #if defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
 #define _DBUS_FUNCTION_NAME __func__
 #elif defined(__GNUC__)
Index: dbus/dbus-misc.c
===================================================================
RCS file: /cvs/dbus/dbus/dbus/dbus-misc.c,v
retrieving revision 1.2
diff -u -p -r1.2 dbus-misc.c
--- dbus/dbus-misc.c	15 Nov 2006 03:07:59 -0000	1.2
+++ dbus/dbus-misc.c	25 Nov 2006 01:59:25 -0000
@@ -4,7 +4,7 @@
  * Copyright (C) 2006 Red Hat, Inc.
  *
  * Licensed under the Academic Free License version 2.1
- * 
+ *
  * 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
@@ -14,7 +14,7 @@
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
@@ -25,6 +25,422 @@
 #include "dbus-misc.h"
 #include "dbus-internals.h"
 #include "dbus-string.h"
+#include <stdio.h>
+#include <stdarg.h>
+
+/**
+ * @defgroup DBusMiscInternals Internal of miscellaneous API
+ * @ingroup DBusInternals
+ * @brief Implementation details of dbus-misc.c
+ *@{
+ */
+
+typedef struct {
+  DBusLogHandlerFunction func; /**< the handler */
+  void *data;                  /**< handler data */
+  DBusFreeFunction free_data_func;  /**< free handler data */
+} DBusLogHandler;
+
+
+static DBusLogHandler log_handler_bug = { NULL, NULL, NULL };
+static DBusLogHandler log_handler_warning = { NULL, NULL, NULL };
+static DBusLogHandler log_handler_verbose = { NULL, NULL, NULL };
+
+typedef struct {
+  DBusBugHandlerFunction func; /**< the handler */
+  void *data;                  /**< handler data */
+  DBusFreeFunction free_data_func;  /**< free handler data */
+} DBusBugHandler;
+
+
+static DBusBugHandler bug_handler = { NULL, NULL, NULL };
+
+
+#if 0
+static dbus_bool_t _dbus_log (DBusLogLevel level,
+                              const char  *format,
+                              ...) _DBUS_GNUC_PRINTF(2, 3);
+#endif
+
+/* the point of this is mostly to avoid confusion when debugging an OOM-handling problem with --verbose */
+static void
+log_oom_warning (const char *format)
+{
+  fputs ("No memory to print a D-Bus message with format string: ", stderr);
+  fputs (format, stderr);
+  fputs ("\n", stderr);
+  fflush (stderr);
+}
+
+static DBusLogHandler*
+get_handler (DBusLogLevel level)
+{
+  DBusLogHandler *handler = NULL;
+  switch (level)
+    {
+    case DBUS_LOG_LEVEL_BUG:
+      handler = &log_handler_bug;
+      break;
+    case DBUS_LOG_LEVEL_WARNING:
+      handler = &log_handler_warning;
+      break;
+    case DBUS_LOG_LEVEL_VERBOSE:
+      handler = &log_handler_verbose;
+      break;
+    }
+  return handler;
+}
+
+static void
+_dbus_log_literal (DBusLogLevel      level,
+                   const DBusString *str)
+{
+  DBusLogHandler *handler;
+  
+  handler = get_handler (level);
+
+  if (handler->func)
+    {
+      (* handler->func) (_dbus_string_get_const_data (str), handler->data);
+    }
+  else
+    {
+      fputs (_dbus_string_get_const_data (str), stderr);
+    }
+}
+
+static dbus_bool_t
+_dbus_log_valist (DBusLogLevel level,
+                  const char  *format,
+                  va_list      args)
+{
+  DBusString str;
+
+  /* Unfortunately, this function could disastrously recurse if an
+   * assertion fails in any code it uses, but I'm not sure what to do
+   * about that short of cut-and-pasting everything. (Maybe we
+   * should.)
+   */
+
+  if (!_dbus_string_init (&str))
+    return FALSE;
+
+  if (!_dbus_string_append_printf_valist (&str, format, args))
+    {
+      _dbus_string_free (&str);
+      return FALSE;
+    }
+
+  _dbus_log_literal (level, &str);
+
+  _dbus_string_free (&str);
+
+  return TRUE;
+}
+
+#if 0
+static dbus_bool_t
+_dbus_log (DBusLogLevel level,
+           const char  *format,
+           ...)
+{
+  va_list args;
+  dbus_bool_t ok;
+
+  va_start (args, format);
+  ok = _dbus_log_valist (level, format, args);
+  va_end (args);
+
+  return ok;
+}
+#endif
+
+static dbus_bool_t warn_initted = FALSE;
+static dbus_bool_t fatal_warnings = FALSE;
+static dbus_bool_t fatal_bugs = TRUE;
+
+static void
+init_warnings(void)
+{
+  if (!warn_initted)
+    {
+      const char *s;
+      s = _dbus_getenv ("DBUS_FATAL_WARNINGS");
+      if (s && *s)
+        {
+          if (*s == '0')
+            {
+              fatal_warnings = FALSE;
+              fatal_bugs = FALSE;
+            }
+          else if (*s == '1')
+            {
+              fatal_warnings = TRUE;
+              fatal_bugs = TRUE;
+            }
+          else
+            {
+              fprintf(stderr, "DBUS_FATAL_WARNINGS should be set to 0 or 1 if set, not '%s'",
+                      s);
+            }
+        }
+
+      warn_initted = TRUE;
+    }
+}
+
+/**
+ * Prints a warning message to stderr. Can optionally be made to exit
+ * fatally by setting DBUS_FATAL_WARNINGS, but this is rarely
+ * used. This function should be considered pretty much equivalent to
+ * fprintf(stderr). _dbus_warn_check_failed() on the other hand is
+ * suitable for use when a programming mistake has been made.
+ *
+ * dbus_set_log_handler() can be used to override what this function
+ * does.
+ *
+ * libdbus really should not call this, and mostly does not;
+ * recoverable errors should instead be reported to the caller, and
+ * bugs should be reported with _dbus_warn_check_failed(). Printing to
+ * stderr may be appropriate if something really should never happen
+ * but is not a bug in the application using libdbus, for example a
+ * misconfigured install of libdbus could be reported with
+ * _dbus_warn().
+ *
+ * @param format printf-style format string.
+ */
+void
+_dbus_warn (const char *format,
+            ...)
+{
+  va_list args;
+
+  if (!warn_initted)
+    init_warnings ();
+
+  va_start (args, format);
+  if (!_dbus_log_valist (DBUS_LOG_LEVEL_WARNING, format, args))
+    log_oom_warning (format);
+  va_end (args);
+
+  if (fatal_warnings)
+    {
+      _dbus_abort ();
+    }
+}
+
+/**
+ * Indicates that an assertion failed due to an application bug, as
+ * described in the given message. The "bug handler" from
+ * dbus_set_bug_handler() will be invoked and usually a log handler
+ * from dbus_set_log_handler() will be invoked as well.
+ *
+ * The message logged will contain the process ID of the failing app.
+ *
+ * This function should be used only when a programming error has been
+ * detected. (NOT for unavoidable errors that an app might handle -
+ * those should be returned as DBusError.) Calling this means "there
+ * is a bug"
+ */
+void
+_dbus_warn_check_failed (const char *format,
+                        ...)
+{
+  va_list args;
+  DBusBugHandlingAction action;
+  DBusString str;
+  
+  action = DBUS_BUG_ACTION_LEAVE_DEFAULT;
+  if (bug_handler.func != NULL)
+    {
+      action = (* bug_handler.func) (bug_handler.data);
+      if (action == DBUS_BUG_ACTION_FORCE_SILENTLY_IGNORE)
+        return;
+    }
+
+  if (!warn_initted)
+    init_warnings ();
+
+  va_start (args, format);
+  if (_dbus_string_init (&str) &&
+      _dbus_string_append_printf (&str, "process %lu: ", _dbus_getpid ()) &&
+      _dbus_string_append_printf_valist (&str, format, args))
+    {
+      _dbus_log_literal (DBUS_LOG_LEVEL_BUG, &str);
+    }
+  else
+    {
+      log_oom_warning (format);
+    }
+  va_end (args);
+
+  _dbus_string_free (&str);
+  
+  if ((action == DBUS_BUG_ACTION_LEAVE_DEFAULT && fatal_bugs) ||
+      action == DBUS_BUG_ACTION_FORCE_LOG_AND_ABORT)
+    {
+      _dbus_abort ();
+    }
+}
+
+#ifdef DBUS_ENABLE_VERBOSE_MODE
+
+static dbus_bool_t verbose_initted = FALSE;
+static dbus_bool_t verbose = TRUE;
+
+/** Whether to show the current thread in verbose messages */
+#define PTHREAD_IN_VERBOSE 0
+#if PTHREAD_IN_VERBOSE
+#include <pthread.h>
+#endif
+
+static inline void
+_dbus_verbose_init (void)
+{
+  if (!verbose_initted)
+    {
+      const char *p = _dbus_getenv ("DBUS_VERBOSE");
+      verbose = p != NULL && *p == '1';
+      verbose_initted = TRUE;
+    }
+}
+
+/**
+ * Implementation of dbus_is_verbose() macro if built with verbose logging
+ * enabled.
+ * @returns whether verbose logging is active.
+ */
+dbus_bool_t
+_dbus_is_verbose_real (void)
+{
+  _dbus_verbose_init ();
+  return verbose;
+}
+
+/**
+ * Prints a warning message to stderr
+ * if the user has enabled verbose mode.
+ * This is the real function implementation,
+ * use _dbus_verbose() macro in code.
+ *
+ * @param format printf-style format string.
+ */
+void
+_dbus_verbose_real (const char *format,
+                    ...)
+{
+  va_list args;
+  static dbus_bool_t need_pid = TRUE;
+  int len;
+  DBusString str;
+  
+  /* things are written a bit oddly here so that
+   * in the non-verbose case we just have the one
+   * conditional and return immediately.
+   */
+  if (!_dbus_is_verbose_real())
+    return;
+
+  if (!_dbus_string_init (&str))
+    {
+      log_oom_warning (format);
+      return;
+    }
+  
+  /* Print out pid before the line */
+  if (need_pid)
+    {
+#if PTHREAD_IN_VERBOSE
+      if (!_dbus_string_append_printf (&str, "%lu: 0x%lx: ", _dbus_getpid (), pthread_self ()))
+        {
+          log_oom_warning (format);
+          _dbus_string_free (&str);
+          return;
+        }
+#else
+      if (!_dbus_string_append_printf (&str, "%lu: ", _dbus_getpid ()))
+        {
+          log_oom_warning (format);
+          _dbus_string_free (&str);
+          return;
+        }
+#endif
+    }
+
+  /* Only print pid again if the next line is a new line */
+  len = strlen (format);
+  if (format[len-1] == '\n')
+    need_pid = TRUE;
+  else
+    need_pid = FALSE;
+
+  va_start (args, format);
+  if (!_dbus_string_append_printf_valist (&str, format, args))
+    {
+      log_oom_warning (format);
+      _dbus_string_free (&str);
+      return;
+    }
+  _dbus_log_literal (DBUS_LOG_LEVEL_VERBOSE, &str);
+  va_end (args);
+
+  _dbus_string_free (&str);
+}
+
+/**
+ * Reinitializes the verbose logging code, used
+ * as a hack in dbus-spawn.c so that a child
+ * process re-reads its pid
+ *
+ */
+void
+_dbus_verbose_reset_real (void)
+{
+  verbose_initted = FALSE;
+}
+
+#endif /* DBUS_ENABLE_VERBOSE_MODE */
+
+static dbus_bool_t handlers_initialized = FALSE;
+
+static void
+handlers_shutdown_func (void *data)
+{
+  dbus_set_log_handler (DBUS_LOG_LEVEL_BUG, NULL, NULL, NULL);
+  dbus_set_log_handler (DBUS_LOG_LEVEL_WARNING, NULL, NULL, NULL);
+  dbus_set_log_handler (DBUS_LOG_LEVEL_VERBOSE, NULL, NULL, NULL);
+
+  dbus_set_bug_handler (NULL, NULL, NULL);
+
+  handlers_initialized = FALSE;
+}
+
+static void
+init_handlers(void)
+{
+  if (!handlers_initialized)
+    {
+      if (!_dbus_register_shutdown_func (handlers_shutdown_func,
+                                         NULL))
+        {
+          /* For now, we silently eat this OOM error. I think the
+           * right fix is to move the "ShutdownClosure" in the
+           * _dbus_register_shutdown_func implementation to a public
+           * header, and have each place that registers a shutdown
+           * func allocate it as a static global var. But it's
+           * not like there should be any problem here in any real
+           * app.
+           */
+
+          return;
+        }
+
+      handlers_initialized = TRUE;
+    }
+}
+
+/** @} */ /* End of internals */
+
 
 /**
  * @defgroup DBusMisc Miscellaneous
@@ -35,10 +451,214 @@
  */
 
 /**
+ * Sets a handler function for printing messages.
+ *
+ * libdbus can print several kinds of message, each kind has a log level.
+ *
+ * Each log level may have a handler replacing the default dbus
+ * handler for that log level. The default handler prints the message
+ * to the console (stdout or stderr).
+ *
+ * See the documentation on each level in the #DBusLogLevel enum
+ * documentation.
+ *
+ * A typical use of this function is to use syslog() instead of
+ * printing to the console.
+ *
+ * @note When implementing this function, remember that the message passed
+ * to your log handler is <em>NOT</em> a format string. So for
+ * example, you want to write syslog("%s", message) rather than
+ * syslog(message).
+ *
+ * @note It's a bad idea to make any D-Bus calls from inside the log
+ * handler, because D-Bus will not have dropped any thread locks it has,
+ * and if the log handler is called for an assertion failure and
+ * the additional D-Bus calls fail again, you could get into an
+ * infinite recursion.
+ *
+ * @note The log handler can be called from any thread that libdbus
+ * is called from.
+ *
+ * @note dbus_set_log_handler() does not have any thread locking, so
+ * should only be called at application initialization time before
+ * multiple threads are in use with libdbus.
+ *
+ * @since 1.2
+ *
+ * @param level log level to set the function for
+ * @param func function to be invoked to handle log messages, or #NULL to restore the default
+ * @param func_data data to pass to the function
+ * @param free_data_func function used to free the data if a new log handler is set at the same level or dbus_shutdown() is invoked
+ *
+ */
+void
+dbus_set_log_handler (DBusLogLevel            level,
+                      DBusLogHandlerFunction  func,
+                      void                   *func_data,
+                      DBusFreeFunction        free_data_func)
+{
+  DBusLogHandler *handler;
+  DBusFreeFunction old_func;
+  void *old_data;
+
+  _dbus_return_if_fail (level == DBUS_LOG_LEVEL_BUG ||
+                        level == DBUS_LOG_LEVEL_VERBOSE ||
+                        level == DBUS_LOG_LEVEL_WARNING);
+
+  init_handlers ();
+
+  handler = get_handler (level);
+
+  _dbus_assert (handler != NULL);
+
+  if (handler->free_data_func)
+    {
+      old_func = handler->free_data_func;
+      old_data = handler->data;
+    }
+  else
+    {
+      old_func = NULL;
+      old_data = NULL;
+    }
+
+  handler->func = func;
+  handler->data = func_data;
+  handler->free_data_func = free_data_func;
+
+  if (old_func != NULL)
+    {
+      (* old_func) (old_data);
+    }
+}
+
+/**
+ * Sets a function to be called when libdbus detects an application
+ * bug.
+ *
+ * The bug handler function is called when libdbus detects that the
+ * application programmer is using libdbus in an undefined way. The
+ * most common bug detected by libdbus is that a passed-in pointer is
+ * #NULL when it is not allowed to be #NULL according to the
+ * documented ABI contract. However, there are a number of others bugs
+ * libdbus checks for.
+ *
+ * The bug handler can do anything you like, and then returns a value
+ * telling libdbus what further action to take. See the documentation
+ * for each value in the #DBusBugHandlingAction enum for more.
+ *
+ * Most applications should not set a bug handler, or at least should
+ * return #DBUS_BUG_ACTION_LEAVE_DEFAULT to use the default bug
+ * handling behavior.  For example, distributions may set
+ * DBUS_FATAL_WARNINGS=1 to make bugs fatal during a beta test cycle
+ * and then set DBUS_FATAL_WARNINGS=0 to make bugs nonfatal in
+ * production releases. Applications that don't honor this setting may
+ * be considered antisocial unless they are special in some way.
+ *
+ * There are few semantic guarantees surrounding bug handling. libdbus
+ * often will not be able to recover gracefully after a bug is
+ * detected; you should think of a detected bug much as you would
+ * think of a segmentation fault or illegal memory access.
+ *
+ * If you continue after a bug is reported, most likely some expected
+ * function was not performed; this may cause a cascading effect of
+ * additional bugs or crashes, in libdbus or in your application.
+ *
+ * libdbus behavior after reporting a bug is completely undefined.
+ * Remember that memory may be corrupted, data structures may be
+ * inconsistent, or a function may have behaved unexpectedly.  If you
+ * ask libdbus to ignore a bug, your program may be able to continue,
+ * or it may not.
+ *
+ * Ignoring bugs is strongly discouraged in security-sensitive
+ * applications or daemons; you do not want a security-sensitive
+ * program to continue in a confused or unknown state.
+ * #DBUS_BUG_ACTION_FORCE_LOG_AND_ABORT is advisable in secure
+ * daemons. Of course, it's also advisable to avoid having bugs in
+ * these daemons in the first place, so be careful.
+ *
+ * There are no guarantees that bugs will be detected; libdbus can be
+ * compiled with "checks" disabled, which will mean it does not check
+ * for application bugs. Also, no specific bug checks are guaranteed
+ * to exist, since there may be performance or other reasons to omit
+ * certain checks. The checks that exist may change over time. They
+ * are not considered part of the ABI contract.
+ *
+ * The behavior of #DBUS_BUG_ACTION_LEAVE_DEFAULT is not defined by the
+ * ABI contract. Right now it varies based on the DBUS_FATAL_WARNINGS
+ * environment variable but this is not guaranteed.
+ *
+ * If you ask libdbus to log the bug, it may call the log handler
+ * at level #DBUS_LOG_LEVEL_BUG multiple times to log a multi-line
+ * problem. So if you want to do something once per bug, do it
+ * in the bug handler, not the log handler.
+ *
+ * @note <em>libdbus bug checks will never be triggered for error conditions
+ * outside of the application author's control.</em> Recoverable
+ * errors are reported to the caller of the function as documented for
+ * that function. See #DBusError for some more details on recoverable
+ * error conditions.
+ *
+ * @note Again: any time the bug handler function is invoked, it is a bug
+ * that you can fix, and the bug most likely has serious or
+ * potentially serious consequences. This is a <em>bug</em> handler,
+ * not an error handler. In a correct application, the bug handler
+ * will never be used. That's why it's called the bug handler.
+ *
+ * @note It's a bad idea to make any D-Bus calls from inside the bug
+ * handler, because D-Bus will not have dropped any thread locks it has
+ * and in general will be in who-knows-what state.
+ *
+ * @note The bug handler can be called from any thread that libdbus
+ * is called from.
+ *
+ * @note dbus_set_bug_handler() does not have any thread locking, so
+ * should only be called at application initialization time before
+ * multiple threads are in use with libdbus.
+ *
+ * @since 1.2
+ *
+ * @param func function to be invoked to handle bugs, or #NULL to restore the default
+ * @param func_data data to pass to the function
+ * @param free_data_func function used to free the data if a new bug handler is set or dbus_shutdown() is invoked
+ *
+ */
+void
+dbus_set_bug_handler (DBusBugHandlerFunction  func,
+                      void                   *func_data,
+                      DBusFreeFunction        free_data_func)
+{
+  DBusFreeFunction old_func;
+  void *old_data;
+
+  init_handlers ();
+
+  if (bug_handler.free_data_func)
+    {
+      old_func = bug_handler.free_data_func;
+      old_data = bug_handler.data;
+    }
+  else
+    {
+      old_func = NULL;
+      old_data = NULL;
+    }
+
+  bug_handler.func = func;
+  bug_handler.data = func_data;
+  bug_handler.free_data_func = free_data_func;
+
+  if (old_func != NULL)
+    {
+      (* old_func) (old_data);
+    }
+}
+
+/**
  * Obtains the machine UUID of the machine this process is running on.
  *
  * The returned string must be freed with dbus_free().
- * 
+ *
  * This UUID is guaranteed to remain the same until the next reboot
  * (unless the sysadmin foolishly changes it and screws themselves).
  * It will usually remain the same across reboots also, but hardware
@@ -99,11 +719,157 @@ dbus_get_local_machine_id (void)
 
 #include "dbus-test.h"
 #include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+/* _dbus_strdup would get involved in the OOM handling testing,
+ * we want to keep this outside of dbus_malloc
+ */
+static char*
+xstrdup (const char *str)
+{
+  size_t len;
+  char *copy;
+  
+  if (str == NULL)
+    return NULL;
+  
+  len = strlen (str);
 
+  copy = malloc (len + 1);
+  if (copy == NULL)
+    return NULL;
+
+  memcpy (copy, str, len + 1);
+  
+  return copy;
+}
+
+static DBusBugHandlingAction
+bug_handler_log_and_ignore(void *data)
+{
+  return DBUS_BUG_ACTION_FORCE_LOG_AND_IGNORE;
+}
+
+static void
+log_handler_that_copies_message (const char *message,
+                                void       *data)
+{
+  char** m_p = data;
+  if (*m_p == NULL) /* multiple calls are allowed */
+    *m_p = xstrdup (message);
+}
+
+static void
+log_handler_free_message (void *data)
+{
+  char** m_p = data;
+  free (*m_p);
+  *m_p = NULL;
+}
+
+static void
+check_resulting_message (const char *m,
+                         const char *expected)
+{
+  if (m == NULL)
+    {
+      fprintf (stderr, "log message should have been saved, expecting '%s'\n", expected);
+      exit (1);
+    }
+
+  /* strstr not strcmp because the log stuff can add the pid */
+  if (strstr(m, expected) == NULL)
+    {
+      fprintf (stderr, "wrong log message '%s' was expecting '%s'\n", m, expected);
+      exit (1);
+    }
+}
+
+static dbus_bool_t
+test_log_handler(void *data)
+{
+  char *m;
+  DBusLogLevel level;
+
+  level = _DBUS_POINTER_TO_INT (data);
+
+  m = NULL;
+  dbus_set_log_handler (level, log_handler_that_copies_message,
+                        &m,
+                        log_handler_free_message);
+  switch (level)
+    {
+    case DBUS_LOG_LEVEL_BUG:
+#ifndef DBUS_DISABLE_CHECKS
+      dbus_set_bug_handler (bug_handler_log_and_ignore, NULL, NULL);
+      _dbus_warn_check_failed ("Testing a check failed");
+      dbus_set_bug_handler (NULL, NULL, NULL);
+
+      check_resulting_message (m, "Testing a check failed");
+#endif
+      break;
+    case DBUS_LOG_LEVEL_WARNING:
+      {
+        dbus_bool_t old;
+
+        init_warnings ();
+        
+        old = fatal_warnings;
+        fatal_warnings = FALSE;
+        _dbus_warn ("Testing a warning");
+        fatal_warnings = old;
+        check_resulting_message (m, "Testing a warning");
+      }
+      break;
+    case DBUS_LOG_LEVEL_VERBOSE:
+#ifdef DBUS_ENABLE_VERBOSE_MODE
+      {
+        dbus_bool_t old;
+
+        _dbus_verbose_init ();
+        
+        old = verbose;
+        verbose = TRUE;
+        _dbus_verbose ("Testing verbose logging");
+        verbose = old;
+        check_resulting_message (m, "Testing verbose logging");
+      }
+#endif /* Verbose mode */
+      break;
+    }
+
+  dbus_set_log_handler (level, NULL, NULL, NULL);
+
+  if (m != NULL)
+    {
+      fprintf (stderr, "Free function apparently not called for log at level %d\n", level);
+      exit (1);
+    }
+
+  return TRUE;
+}
 
 dbus_bool_t
 _dbus_misc_test (void)
 {
+  /* The OOM tests would not work right now, since we just don't log
+   * when we run out, and print an annoying warning to boot.  There's
+   * a small issue with setting log handlers when out of memory, also.
+   */
+  
+#ifndef DBUS_DISABLE_CHECKS
+  test_log_handler (_DBUS_INT_TO_POINTER (DBUS_LOG_LEVEL_BUG));
+  /* _dbus_test_oom_handling("bug logging", test_log_handler, _DBUS_INT_TO_POINTER (DBUS_LOG_LEVEL_BUG)); */
+#endif
+
+#ifdef DBUS_ENABLE_VERBOSE_MODE
+  test_log_handler (_DBUS_INT_TO_POINTER (DBUS_LOG_LEVEL_VERBOSE));
+  /* _dbus_test_oom_handling("verbose logging", test_log_handler, _DBUS_INT_TO_POINTER (DBUS_LOG_LEVEL_VERBOSE)); */
+#endif
+
+  test_log_handler (_DBUS_INT_TO_POINTER (DBUS_LOG_LEVEL_WARNING));
+  /* _dbus_test_oom_handling("warn logging", test_log_handler, _DBUS_INT_TO_POINTER (DBUS_LOG_LEVEL_WARNING)); */
   
   return TRUE;
 }
Index: dbus/dbus-misc.h
===================================================================
RCS file: /cvs/dbus/dbus/dbus/dbus-misc.h,v
retrieving revision 1.2
diff -u -p -r1.2 dbus-misc.h
--- dbus/dbus-misc.h	15 Nov 2006 03:07:59 -0000	1.2
+++ dbus/dbus-misc.h	25 Nov 2006 01:59:25 -0000
@@ -29,6 +29,7 @@
 
 #include <dbus/dbus-types.h>
 #include <dbus/dbus-errors.h>
+#include <dbus/dbus-memory.h>
 
 DBUS_BEGIN_DECLS
 
@@ -37,8 +38,113 @@ DBUS_BEGIN_DECLS
  * @{
  */
 
-char*       dbus_get_local_machine_id              (void);
+/** Distinguishes kinds of log message. */
+typedef enum {
+  DBUS_LOG_LEVEL_BUG,     /**< Used to log bugs. Handler called only if the
+                           * #DBusBugHandlerFunction indicates that
+                           * the bug should be logged. Only called
+                           * if libdbus was compiled with "checks" or
+                           * "asserts" enabled.
+                           * @since 1.2
+                           */
+  DBUS_LOG_LEVEL_WARNING, /**< Used to log warnings. There are very few of these,
+                           * they occur when a something goes horribly
+                           * wrong and it's not a defined recoverable
+                           * error returned by the API and it's not a
+                           * programming mistake on anyone's part.
+                           * Might be used for example if the libdbus
+                           * installation is broken.
+                           * @since 1.2
+                           */
+  DBUS_LOG_LEVEL_VERBOSE  /**< Used to log "debug spew" only if libdbus
+                           * was compiled with verbose mode available and
+                           * verbose mode is currently enabled via
+                           * environment variable or other configuration.
+                           * @since 1.2
+                           */
+} DBusLogLevel;
+
+/** Returned by the #DBusBugHandlerFunction to indicate what libdbus should do next. */
+typedef enum {
+  DBUS_BUG_ACTION_LEAVE_DEFAULT,         /**< Most bug handlers should
+                                          * return this value, which
+                                          * indicates that the default
+                                          * action should be taken;
+                                          * the default action may be
+                                          * controlled by environment
+                                          * variables, config files,
+                                          * or compile-time options.
+                                          * The default action is not
+                                          * guaranteed by the ABI
+                                          * contract, but right now is
+                                          * to log the bug and then
+                                          * optionally exit depending
+                                          * on the DBUS_FATAL_WARNINGS
+                                          * environment variable.
+                                          * 
+                                          * @since 1.2
+                                          */
+  DBUS_BUG_ACTION_FORCE_LOG_AND_ABORT,   /**< Force libdbus to call
+                                          * the log handler at level
+                                          * #DBUS_LOG_LEVEL_BUG and
+                                          * then exit the program,
+                                          * regardless of environment
+                                          * variables or
+                                          * configuration.
+                                          *
+                                          * @since 1.2
+                                          */
+  DBUS_BUG_ACTION_FORCE_LOG_AND_IGNORE,  /**< Force libdbus to call
+                                          * the log handler at level
+                                          * #DBUS_LOG_LEVEL_BUG and
+                                          * then do nothing further,
+                                          * regardless of environment
+                                          * variables or
+                                          * configuration.
+                                          *
+                                          * @note The behavior of
+                                          * libdbus is undefined after
+                                          * the bug handler returns;
+                                          * it may well crash,
+                                          * immediately or at a later
+                                          * time.
+                                          *
+                                          * @since 1.2
+                                          */
+  DBUS_BUG_ACTION_FORCE_SILENTLY_IGNORE  /**< Force libdbus to do
+                                          * nothing, regardless of
+                                          * environment variables or
+                                          * configuration.
+                                          *
+                                          * @note The behavior of
+                                          * libdbus is undefined after
+                                          * the bug handler returns;
+                                          * it may well crash,
+                                          * immediately or at a later
+                                          * time.
+                                          *
+                                          * @since 1.2
+                                          */
+} DBusBugHandlingAction;
+
+
+/** Bug handler function, see dbus_set_bug_handler(). @since 1.2 */
+typedef DBusBugHandlingAction (* DBusBugHandlerFunction) (void       *data);
+
+/** Log handler function, see dbus_set_log_handler(). @since 1.2 */
+typedef void                  (* DBusLogHandlerFunction) (const char *message,
+                                                          void       *data);
+
+void dbus_set_log_handler (DBusLogLevel            level,
+                           DBusLogHandlerFunction  func,
+                           void                   *func_data,
+                           DBusFreeFunction        free_data_func);
+void dbus_set_bug_handler (DBusBugHandlerFunction  func,
+                           void                   *func_data,
+                           DBusFreeFunction        free_data_func);
 
+char*       dbus_get_local_machine_id              (void);
+                                  
 /** @} */
 
 DBUS_END_DECLS
Index: dbus/dbus-sysdeps.c
===================================================================
RCS file: /cvs/dbus/dbus/dbus/dbus-sysdeps.c,v
retrieving revision 1.118
diff -u -p -r1.118 dbus-sysdeps.c
--- dbus/dbus-sysdeps.c	21 Oct 2006 18:17:02 -0000	1.118
+++ dbus/dbus-sysdeps.c	25 Nov 2006 01:59:26 -0000
@@ -70,8 +70,14 @@ void
 _dbus_abort (void)
 {
   const char *s;
+
+  fflush (stderr); /* in case stuff was logged prior to abort. I guess stderr
+                    * is often not buffered anyway.
+                    */
   
   _dbus_print_backtrace ();
+
+  fflush (stderr);
   
   s = _dbus_getenv ("DBUS_BLOCK_ON_ABORT");
   if (s && *s)


More information about the dbus mailing list