-/* Copyright (c) 2009, 2010 Nicira Networks
+/* Copyright (c) 2009, 2010, 2011 Nicira Networks
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
     return error;
 }
 
+/* Returns an ovsdb_error that represents an internal error for file name
+ * 'file' and line number 'line', with 'details' (formatted as with printf())
+ * as the associated message.  The caller is responsible for freeing the
+ * returned error.
+ *
+ * If 'inner_error' is nonnull then the returned error is wrapped around
+ * 'inner_error'.  Takes ownership of 'inner_error'.  */
 struct ovsdb_error *
-ovsdb_internal_error(const char *file, int line, const char *details, ...)
+ovsdb_internal_error(struct ovsdb_error *inner_error,
+                     const char *file, int line, const char *details, ...)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
     struct backtrace backtrace;
 
     ds_put_format(&ds, " (%s %s%s)", program_name, VERSION, BUILDNR);
 
+    if (inner_error) {
+        char *s = ovsdb_error_to_string(inner_error);
+        ds_put_format(&ds, " (generated from: %s)", s);
+        free(s);
+
+        ovsdb_error_destroy(inner_error);
+    }
+
     error = ovsdb_error("internal error", "%s", ds_cstr(&ds));
 
     ds_destroy(&ds);
 
-/* Copyright (c) 2009, 2010 Nicira Networks
+/* Copyright (c) 2009, 2010, 2011 Nicira Networks
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
                                      const char *details, ...)
     PRINTF_FORMAT(2, 3);
 
-struct ovsdb_error *ovsdb_internal_error(const char *file, int line,
+struct ovsdb_error *ovsdb_internal_error(struct ovsdb_error *error,
+                                         const char *file, int line,
                                          const char *details, ...)
-    PRINTF_FORMAT(3, 4)
+    PRINTF_FORMAT(4, 5)
     WARN_UNUSED_RESULT;
-#define OVSDB_BUG(MSG) ovsdb_internal_error(__FILE__, __LINE__, "%s", MSG)
+
+/* Returns a pointer to an ovsdb_error that represents an internal error for
+ * the current file name and line number with MSG as the associated message.
+ * The caller is responsible for freeing the internal error. */
+#define OVSDB_BUG(MSG)                                      \
+    ovsdb_internal_error(NULL, __FILE__, __LINE__, "%s", MSG)
+
+/* Returns a pointer to an ovsdb_error that represents an internal error for
+ * the current file name and line number, with MSG as the associated message.
+ * If ERROR is nonnull then the internal error is wrapped around ERROR.  Takes
+ * ownership of ERROR.  The caller is responsible for freeing the returned
+ * error. */
+#define OVSDB_WRAP_BUG(MSG, ERROR)                          \
+    ovsdb_internal_error(ERROR, __FILE__, __LINE__, "%s", MSG)
 
 void ovsdb_error_destroy(struct ovsdb_error *);
 struct ovsdb_error *ovsdb_error_clone(const struct ovsdb_error *)
 
-/* Copyright (c) 2009, 2010 Nicira Networks
+/* Copyright (c) 2009, 2010, 2011 Nicira Networks
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
         if (r->old) {
             error = ovsdb_txn_adjust_row_refs(txn, r->old, column, -1);
             if (error) {
-                ovsdb_error_destroy(error);
-                return OVSDB_BUG("error decreasing refcount");
+                return OVSDB_WRAP_BUG("error decreasing refcount", error);
             }
         }
         if (r->new) {
      * was really a no-op. */
     error = for_each_txn_row(txn, determine_changes);
     if (error) {
-        ovsdb_error_destroy(error);
-        return OVSDB_BUG("can't happen");
+        return OVSDB_WRAP_BUG("can't happen", error);
     }
     if (list_is_empty(&txn->txn_tables)) {
         ovsdb_txn_abort(txn);