summaryrefslogtreecommitdiff
path: root/src/common/db_err.c
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@baserock.org>2015-02-17 17:25:57 +0000
committer <>2015-03-17 16:26:24 +0000
commit780b92ada9afcf1d58085a83a0b9e6bc982203d1 (patch)
tree598f8b9fa431b228d29897e798de4ac0c1d3d970 /src/common/db_err.c
parent7a2660ba9cc2dc03a69ddfcfd95369395cc87444 (diff)
downloadberkeleydb-db-6.1.23.tar.gz
Imported from /home/lorry/working-area/delta_berkeleydb/db-6.1.23.tar.gz.HEADdb-6.1.23master
Diffstat (limited to 'src/common/db_err.c')
-rw-r--r--src/common/db_err.c636
1 files changed, 552 insertions, 84 deletions
diff --git a/src/common/db_err.c b/src/common/db_err.c
index 6edc37b6..7acaa174 100644
--- a/src/common/db_err.c
+++ b/src/common/db_err.c
@@ -1,7 +1,7 @@
/*-
* See the file LICENSE for redistribution information.
*
- * Copyright (c) 1996, 2012 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2015 Oracle and/or its affiliates. All rights reserved.
*
* $Id$
*/
@@ -18,6 +18,11 @@
static void __db_msgcall __P((const DB_ENV *, const char *, va_list));
static void __db_msgfile __P((const DB_ENV *, const char *, va_list));
+#if defined(HAVE_ERROR_HISTORY)
+static void __db_thread_once_func __P((void));
+static void __db_deferred_free __P((void *));
+#endif
+
/*
* __db_fchk --
* General flags checking routine.
@@ -62,6 +67,9 @@ __db_ferr(env, name, iscombo)
const char *name;
int iscombo;
{
+ int ret;
+
+ ret = USR_ERR(env, EINVAL);
if (iscombo)
__db_errx(env, DB_STR_A("0054",
"illegal flag combination specified to %s", "%s"), name);
@@ -69,7 +77,7 @@ __db_ferr(env, name, iscombo)
__db_errx(env, DB_STR_A("0055",
"illegal flag specified to %s", "%s"), name);
- return (EINVAL);
+ return (ret);
}
/*
@@ -145,9 +153,24 @@ __db_assert(env, e, file, line)
if (DB_GLOBAL(j_assert) != NULL)
DB_GLOBAL(j_assert)(e, file, line);
else {
- __db_errx(env, DB_STR_A("0059",
- "assert failure: %s/%d: \"%s\"",
- "%s %d %s"), file, line, e);
+ /*
+ * If a panic has preceded this assertion failure, print that
+ * message as well -- it might be relevant.
+ */
+#ifdef HAVE_FAILCHK_BROADCAST
+ if (PANIC_ISSET(env)) {
+ REGENV *renv;
+ renv = (env == NULL || env->reginfo == NULL) ?
+ NULL : env->reginfo->primary;
+ __db_errx(env, DB_STR_A("0242",
+ "assert failure (%s/%d: %s) after panic %s",
+ "%s %d %s %s"), file, line, e,
+ renv == NULL ? "" : renv->failure_symptom);
+ } else
+#endif
+ __db_errx(env, DB_STR_A("0059",
+ "assert failure: %s/%d: \"%s\"",
+ "%s %d %s"), file, line, e);
__os_abort(env);
/* NOTREACHED */
@@ -156,8 +179,49 @@ __db_assert(env, e, file, line)
#endif
/*
+ * __env_panic_event -
+ * Notify the application of a db_register, failchk, or generic panic.
+ *
+ * PUBLIC: void __env_panic_event __P((ENV *, int));
+ */
+void
+__env_panic_event(env, errval)
+ ENV *env;
+ int errval;
+{
+ DB_ENV *dbenv;
+ REGENV *renv;
+ u_int32_t event;
+ void *info;
+ DB_EVENT_FAILCHK_INFO failinfo;
+
+ dbenv = env->dbenv;
+ info = &errval;
+ if (dbenv->db_paniccall != NULL) /* Deprecated */
+ dbenv->db_paniccall(dbenv, errval);
+ /*
+ * We check for DB_EVENT_FAILCHK and DB_EVENT_REG_PANIC first because
+ * they are not set by themselves. If one of those is set, it means that
+ * this panic is somewhat an expected consequence of a previous failure.
+ */
+ renv = (env->reginfo == NULL) ? NULL : env->reginfo->primary;
+ if (renv != NULL && renv->failure_panic) {
+ event = DB_EVENT_FAILCHK_PANIC;
+ failinfo.error = errval;
+ (void)strncpy(failinfo.symptom,
+ renv->failure_symptom, sizeof(failinfo.symptom));
+ failinfo.symptom[sizeof(failinfo.symptom) - 1] = '\0';
+ info = &failinfo;
+ } else if (renv != NULL && renv->reg_panic)
+ event = DB_EVENT_REG_PANIC;
+ else
+ event = DB_EVENT_PANIC;
+ DB_EVENT(env, event, info);
+}
+
+/*
* __env_panic_msg --
- * Just report that someone else paniced.
+ * Report that we noticed a panic which had been set somewhere else.
*
* PUBLIC: int __env_panic_msg __P((ENV *));
*/
@@ -165,28 +229,16 @@ int
__env_panic_msg(env)
ENV *env;
{
- DB_ENV *dbenv;
int ret;
- dbenv = env->dbenv;
-
ret = DB_RUNRECOVERY;
+ /* Make a note saying where this panic was detected. */
+ (void)USR_ERR(env, ret);
__db_errx(env, DB_STR("0060",
"PANIC: fatal region error detected; run recovery"));
- if (dbenv->db_paniccall != NULL) /* Deprecated */
- dbenv->db_paniccall(dbenv, ret);
-
- /* Must check for DB_EVENT_REG_PANIC panic first because it is never
- * set by itself. If set, it means panic came from DB_REGISTER code
- * only, otherwise it could be from many possible places in the code.
- */
- if ((env->reginfo != NULL) &&
- (((REGENV *)env->reginfo->primary)->reg_panic))
- DB_EVENT(env, DB_EVENT_REG_PANIC, &ret);
- else
- DB_EVENT(env, DB_EVENT_PANIC, &ret);
+ __env_panic_event(env, ret);
return (ret);
}
@@ -202,28 +254,13 @@ __env_panic(env, errval)
ENV *env;
int errval;
{
- DB_ENV *dbenv;
-
- dbenv = env->dbenv;
-
if (env != NULL) {
__env_panic_set(env, 1);
- __db_err(env, errval, DB_STR("0061", "PANIC"));
+ if (errval != DB_RUNRECOVERY)
+ __db_err(env, errval, DB_STR("0061", "PANIC"));
- if (dbenv->db_paniccall != NULL) /* Deprecated */
- dbenv->db_paniccall(dbenv, errval);
-
- /* Must check for DB_EVENT_REG_PANIC first because it is never
- * set by itself. If set, it means panic came from DB_REGISTER
- * code only, otherwise it could be from many possible places
- * in the code.
- */
- if ((env->reginfo != NULL) &&
- (((REGENV *)env->reginfo->primary)->reg_panic))
- DB_EVENT(env, DB_EVENT_REG_PANIC, &errval);
- else
- DB_EVENT(env, DB_EVENT_PANIC, &errval);
+ __env_panic_event(env, errval);
}
#if defined(DIAGNOSTIC) && !defined(CONFIG_TEST)
@@ -302,6 +339,9 @@ db_strerror(error)
case DB_LOG_VERIFY_BAD:
return (DB_STR("0071",
"DB_LOG_VERIFY_BAD: Log verification failed"));
+ case DB_META_CHKSUM_FAIL:
+ return (DB_STR("0247",
+ "DB_META_CHKSUM_FAIL: Checksum mismatch detected on a database metadata page"));
case DB_NOSERVER:
return (DB_STR("0072",
"DB_NOSERVER: No message dispatch call-back function has been configured"));
@@ -419,18 +459,21 @@ __db_syserr(env, error, fmt, va_alist)
DB_ENV *dbenv;
dbenv = env == NULL ? NULL : env->dbenv;
+ if (env != NULL)
+ (void)USR_ERR(env, error);
/*
* The same as DB->err, except we don't default to writing to stderr
* after any output channel has been configured, and we use a system-
* specific function to translate errors to strings.
*/
- DB_REAL_ERR(dbenv, error, DB_ERROR_SYSTEM, 0, fmt);
+ DB_REAL_ERR(dbenv,
+ error, error == 0 ? DB_ERROR_NOT_SET : DB_ERROR_SYSTEM, 0, fmt);
}
/*
* __db_err --
- * Standard error routine.
+ * Standard error routine with an error code.
*
* PUBLIC: void __db_err __P((const ENV *, int, const char *, ...))
* PUBLIC: __attribute__ ((__format__ (__printf__, 3, 4)));
@@ -450,6 +493,10 @@ __db_err(env, error, fmt, va_alist)
dbenv = env == NULL ? NULL : env->dbenv;
+ /* (If no deferred messages yet, at least?) add this calls' info.
+ (void)USR_ERR(env, error);
+ */
+
/*
* The same as DB->err, except we don't default to writing to stderr
* once an output channel has been configured.
@@ -459,7 +506,7 @@ __db_err(env, error, fmt, va_alist)
/*
* __db_errx --
- * Standard error routine.
+ * Standard error routine without any error code.
*
* PUBLIC: void __db_errx __P((const ENV *, const char *, ...))
* PUBLIC: __attribute__ ((__format__ (__printf__, 2, 3)));
@@ -500,25 +547,54 @@ __db_errcall(dbenv, error, error_set, fmt, ap)
const char *fmt;
va_list ap;
{
- char *p;
- char buf[2048]; /* !!!: END OF THE STACK DON'T TRUST SPRINTF. */
- char sysbuf[1024]; /* !!!: END OF THE STACK DON'T TRUST SPRINTF. */
+ char *end, *p;
+ char buf[2048 + DB_ERROR_HISTORY_SIZE];
+ char sysbuf[1024];
+#ifdef HAVE_ERROR_HISTORY
+ DB_MSGBUF *deferred_mb;
+ ptrdiff_t len;
+#endif
p = buf;
+ /* Reserve 1 byte at the end for '\0'. */
+ end = buf + sizeof(buf) - 1;
if (fmt != NULL)
p += vsnprintf(buf, sizeof(buf), fmt, ap);
+
if (error_set != DB_ERROR_NOT_SET)
- p += snprintf(p,
- sizeof(buf) - (size_t)(p - buf), ": %s",
+ p += snprintf(p, (size_t)(end - p), ": %s",
error_set == DB_ERROR_SET ? db_strerror(error) :
__os_strerror(error, sysbuf, sizeof(sysbuf)));
+#ifdef HAVE_ERROR_HISTORY
+ /*
+ * Append any messages (e.g., diagnostics) stashed away in the deferred
+ * msgbuf. Strncpy() can't be trusted to append '\0', do it "manually".
+ */
+ if ((deferred_mb = __db_deferred_get()) != NULL &&
+ (len = deferred_mb->cur - deferred_mb->buf) != 0) {
+ p += snprintf(p,
+ (size_t)(end - p), "\nErrors during this API call:");
+ if (len > (end - p))
+ len = end - p;
+ if (len != 0) {
+ memmove(p, deferred_mb->buf, (size_t)len);
+ p[len] = '\0';
+ }
+ }
+#endif
+
dbenv->db_errcall(dbenv, dbenv->db_errpfx, buf);
}
/*
* __db_errfile --
- * Do the error message work for FILE *s.
+ * Do the error message work for FILE *s. Combine the messages into a
+ * single fprintf() call, to avoid interspersed output when there are
+ * multiple active threads.
+ *
+ * Display a ": " after the dbenv prefix, if it has one.
+ * Display a ": " before the error message string, if it error was set.
*
* PUBLIC: void __db_errfile
* PUBLIC: __P((const DB_ENV *, int, db_error_set_t, const char *, va_list));
@@ -532,29 +608,62 @@ __db_errfile(dbenv, error, error_set, fmt, ap)
va_list ap;
{
FILE *fp;
- int need_sep;
- char sysbuf[1024]; /* !!!: END OF THE STACK DON'T TRUST SPRINTF. */
+ char *defintro, *defmsgs, *error_str, *prefix, *sep1, *sep2;
+ char sysbuf[200];
+ char prefix_buf[200];
+ char full_fmt[4096];
+#ifdef HAVE_ERROR_HISTORY
+ DB_MSGBUF *deferred_mb;
+ size_t room;
+#endif
+ prefix = sep1 = sep2 = error_str = "";
fp = dbenv == NULL ||
dbenv->db_errfile == NULL ? stderr : dbenv->db_errfile;
- need_sep = 0;
+ if (fmt == NULL)
+ fmt = "";
if (dbenv != NULL && dbenv->db_errpfx != NULL) {
- (void)fprintf(fp, "%s", dbenv->db_errpfx);
- need_sep = 1;
+ prefix = __db_fmt_quote(prefix_buf,
+ sizeof(prefix_buf), dbenv->db_errpfx);
+ sep1 = ": ";
}
- if (fmt != NULL && fmt[0] != '\0') {
- if (need_sep)
- (void)fprintf(fp, ": ");
- need_sep = 1;
- (void)vfprintf(fp, fmt, ap);
+ switch (error_set) {
+ case DB_ERROR_NOT_SET:
+ break;
+ case DB_ERROR_SET:
+ error_str = db_strerror(error);
+ sep2 = ": ";
+ break;
+ case DB_ERROR_SYSTEM:
+ error_str = __os_strerror(error, sysbuf, sizeof(sysbuf));
+ sep2 = ": ";
+ break;
}
- if (error_set != DB_ERROR_NOT_SET)
- (void)fprintf(fp, "%s%s",
- need_sep ? ": " : "",
- error_set == DB_ERROR_SET ? db_strerror(error) :
- __os_strerror(error, sysbuf, sizeof(sysbuf)));
- (void)fprintf(fp, "\n");
+#ifdef HAVE_ERROR_HISTORY
+ if ((deferred_mb = __db_deferred_get()) != NULL &&
+ deferred_mb->cur != deferred_mb->buf) {
+ defmsgs =
+ __db_fmt_quote(deferred_mb->buf, deferred_mb->len, NULL);
+ defintro = "\nErrors during this API call:";
+ /*
+ * If there are more deferred messages than will be displayed
+ * change the introductory message to warn of the truncation.
+ */
+ room = sizeof(full_fmt) - (strlen(sep1) +
+ strlen(fmt) + strlen(sep2) + strlen(error_str));
+ if (deferred_mb->len + strlen(defintro) > room) {
+ defintro =
+ "\nFirst recorded errors during this API call:";
+ memmove(defmsgs + room - 4, "...\n", 4);
+ }
+
+ } else
+#endif
+ defmsgs = defintro = "";
+ (void)snprintf(full_fmt, sizeof(full_fmt), "%s%s%s%s%s%s%s\n", prefix,
+ sep1, fmt, sep2, error_str, defintro, defmsgs);
+ (void)vfprintf(fp, full_fmt, ap);
(void)fflush(fp);
}
@@ -562,15 +671,15 @@ __db_errfile(dbenv, error, error_set, fmt, ap)
* __db_msgadd --
* Aggregate a set of strings into a buffer for the callback API.
*
- * PUBLIC: void __db_msgadd __P((ENV *, DB_MSGBUF *, const char *, ...))
+ * PUBLIC: void __db_msgadd __P((const ENV *, DB_MSGBUF *, const char *, ...))
* PUBLIC: __attribute__ ((__format__ (__printf__, 3, 4)));
*/
void
#ifdef STDC_HEADERS
-__db_msgadd(ENV *env, DB_MSGBUF *mbp, const char *fmt, ...)
+__db_msgadd(const ENV *env, DB_MSGBUF *mbp, const char *fmt, ...)
#else
__db_msgadd(env, mbp, fmt, va_alist)
- ENV *env;
+ const ENV *env;
DB_MSGBUF *mbp;
const char *fmt;
va_dcl
@@ -592,17 +701,17 @@ __db_msgadd(env, mbp, fmt, va_alist)
* Aggregate a set of strings into a buffer for the callback API.
*
* PUBLIC: void __db_msgadd_ap
- * PUBLIC: __P((ENV *, DB_MSGBUF *, const char *, va_list));
+ * PUBLIC: __P((const ENV *, DB_MSGBUF *, const char *, va_list));
*/
void
__db_msgadd_ap(env, mbp, fmt, ap)
- ENV *env;
+ const ENV *env;
DB_MSGBUF *mbp;
const char *fmt;
va_list ap;
{
- size_t len, olen;
- char buf[2048]; /* !!!: END OF THE STACK DON'T TRUST SPRINTF. */
+ size_t len, nlen, olen;
+ char buf[2048];
len = (size_t)vsnprintf(buf, sizeof(buf), fmt, ap);
@@ -613,9 +722,16 @@ __db_msgadd_ap(env, mbp, fmt, ap)
*/
olen = (size_t)(mbp->cur - mbp->buf);
if (olen + len >= mbp->len) {
- if (__os_realloc(env, mbp->len + len + 256, &mbp->buf))
+ /* Don't write too much for preallocated DB_MSGBUFs. */
+ if (F_ISSET(mbp, DB_MSGBUF_PREALLOCATED)) {
+ memset(mbp->cur, '*', mbp->len - olen);
+ mbp->cur = mbp->buf + mbp->len;
return;
- mbp->len += (len + 256);
+ }
+ nlen = mbp->len + len + (env == NULL ? 8192 : 256);
+ if (__os_realloc(env, nlen, &mbp->buf))
+ return;
+ mbp->len = nlen;
mbp->cur = mbp->buf + olen;
}
@@ -648,6 +764,42 @@ __db_msg(env, fmt, va_alist)
}
/*
+ * __db_debug_msg --
+ * Save a message to be displayed only if this API call returns an error.
+ * The message is discarded if this API call succeeds.
+ *
+ * PUBLIC: void __db_debug_msg __P((const ENV *, const char *, ...));
+ */
+void
+#ifdef STDC_HEADERS
+__db_debug_msg(const ENV *env, const char *fmt, ...)
+#else
+__db_debug_msg(env, fmt, va_alist)
+ const ENV *env;
+ const char *fmt;
+ va_dcl
+#endif
+{
+#ifdef HAVE_ERROR_HISTORY
+ DB_MSGBUF *mb;
+ va_list ap;
+
+ if (env == NULL || (mb = __db_deferred_get()) == NULL)
+ return;
+
+#ifdef STDC_HEADERS
+ va_start(ap, fmt);
+#else
+ va_start(ap);
+#endif
+ __db_msgadd_ap(env, mb, fmt, ap);
+ va_end(ap);
+#endif
+ COMPQUIET(env, NULL);
+ COMPQUIET(fmt, NULL);
+}
+
+/*
* __db_repmsg --
* Replication system message routine.
*
@@ -665,7 +817,7 @@ __db_repmsg(env, fmt, va_alist)
#endif
{
va_list ap;
- char buf[2048]; /* !!!: END OF THE STACK DON'T TRUST SPRINTF. */
+ char buf[2048];
#ifdef STDC_HEADERS
va_start(ap, fmt);
@@ -679,7 +831,7 @@ __db_repmsg(env, fmt, va_alist)
/*
* __db_msgcall --
- * Do the message work for callback functions.
+ * Do the message work for callback functions in DB_REAL_MSG().
*/
static void
__db_msgcall(dbenv, fmt, ap)
@@ -687,16 +839,15 @@ __db_msgcall(dbenv, fmt, ap)
const char *fmt;
va_list ap;
{
- char buf[2048]; /* !!!: END OF THE STACK DON'T TRUST SPRINTF. */
+ char buf[2048];
(void)vsnprintf(buf, sizeof(buf), fmt, ap);
-
dbenv->db_msgcall(dbenv, buf);
}
/*
* __db_msgfile --
- * Do the message work for FILE *s.
+ * Do the message work for FILE *s in DB_REAL_MSG().
*/
static void
__db_msgfile(dbenv, fmt, ap)
@@ -805,6 +956,13 @@ __db_check_txn(dbp, txn, assoc_locker, read_op)
if (IS_RECOVERING(env) || F_ISSET(dbp, DB_AM_RECOVER))
return (0);
+ if (txn != NULL && dbp->blob_threshold &&
+ F_ISSET(txn, (TXN_READ_UNCOMMITTED | TXN_SNAPSHOT))) {
+ __db_errx(env, DB_STR("0237",
+"Blob enabled databases do not support DB_READ_UNCOMMITTED and TXN_SNAPSHOT"));
+ return (EINVAL);
+ }
+
/*
* Check for common transaction errors:
* an operation on a handle whose open commit hasn't completed.
@@ -1095,9 +1253,9 @@ __db_space_err(dbp)
/*
* __db_failed --
- * Common failed thread message.
+ * Common failed thread message, e.g., after it is seen to have crashed.
*
- * PUBLIC: int __db_failed __P((const ENV *,
+ PUBLIC: int __db_failed __P((const ENV *,
* PUBLIC: const char *, pid_t, db_threadid_t));
*/
int
@@ -1108,11 +1266,321 @@ __db_failed(env, msg, pid, tid)
db_threadid_t tid;
{
DB_ENV *dbenv;
- char buf[DB_THREADID_STRLEN];
+ int ret;
+ char tidstr[DB_THREADID_STRLEN], failmsg[DB_FAILURE_SYMPTOM_SIZE];
dbenv = env->dbenv;
+ (void)dbenv->thread_id_string(dbenv, pid, tid, tidstr);
+ ret = USR_ERR(env, DB_RUNRECOVERY);
+ snprintf(failmsg, sizeof(failmsg), DB_STR_A("0113",
+ "Thread/process %s failed: %s", "%s %s"), tidstr, msg);
+ (void)__env_failure_remember(env, failmsg);
+ __db_errx(env, "%s", failmsg);
+ return (ret);
+}
- __db_errx(env, DB_STR_A("0113", "Thread/process %s failed: %s",
- "%s %s"), dbenv->thread_id_string(dbenv, pid, tid, buf), msg);
- return (DB_RUNRECOVERY);
+/*
+ * __env_failure_remember --
+ * If this failure of a process in the environment is about to set panic
+ * for the first time, record that a crashed thread was thw culprit.
+ * Do nothing if panic has already been set. There are no mutexes here;
+ * in order to avoid hanging on any crashed threads.
+ *
+ * PUBLIC: int __env_failure_remember __P((const ENV *, const char *));
+ */
+int
+__env_failure_remember(env, reason)
+ const ENV *env;
+ const char *reason;
+{
+ REGENV *renv;
+
+ renv = env->reginfo->primary;
+ if (renv == NULL || renv->panic || renv->failure_panic)
+ return (0);
+ renv->failure_panic = 1;
+ if (renv->failure_symptom[0] == '\0') {
+ (void)strncpy(renv->failure_symptom,
+ reason, sizeof(renv->failure_symptom));
+ renv->failure_symptom[sizeof(renv->failure_symptom) - 1] = '\0';
+ }
+ return (0);
+}
+
+#if defined(HAVE_ERROR_HISTORY)
+/*
+ * __db_deferred_free --
+ * Pthread_exit() calls this to release DB_GLOBAL(msgs_key)'s
+ * thread-local storage.
+ */
+static void
+__db_deferred_free(void *p)
+{
+ DB_MSGBUF *mb;
+
+ if ((mb = p) != NULL) {
+ (void)pthread_setspecific(DB_GLOBAL(msgs_key), NULL);
+ if (mb->buf != NULL)
+ __os_free(NULL, mb->buf);
+ free(mb);
+ }
+}
+
+/*
+ * __db_thread_once_func --
+ * The pthread_once() functions to initialize thread local storage.
+ */
+static void
+__db_thread_once_func()
+{
+ (void)pthread_key_create(&DB_GLOBAL(msgs_key), __db_deferred_free);
+}
+
+/*
+ * __db_thread_init --
+ * Initialization hook to be called at least once per process, before
+ * deferring any messages.
+ *
+ * PUBLIC: #ifdef HAVE_ERROR_HISTORY
+ * PUBLIC: void __db_thread_init __P((void));
+ * PUBLIC: #endif
+ */
+void
+__db_thread_init()
+{
+ /*
+ * Assign the thread-local storage identifier. Tell thread exit to clean
+ * up withl __db_deferred_free().
+ */
+ (void)pthread_once(&DB_GLOBAL(thread_once), __db_thread_once_func);
+}
+
+/*
+ * __db_diags --
+ *
+ * Save the context which triggers the "first notice" of an error code;
+ * i.e., its creation. It doesn't touch anything when err == 0.
+ *
+ * PUBLIC: #ifdef HAVE_ERROR_HISTORY
+ * PUBLIC: int __db_diags __P((const ENV *, int));
+ * PUBLIC: #endif
+ */
+ int
+__db_diags(env, err)
+ const ENV *env;
+ int err;
+{
+ DB_MSGBUF *mb;
+
+ if (err != 0 && (mb = __db_deferred_get()) != NULL)
+ (void)__db_remember_context(env, mb, err);
+ return (err);
+}
+
+/*
+ * __db_deferred_get --
+ * Get this thread's deferred DB_MSGBUF, possibly allocating it.
+ *
+ * PUBLIC: #ifdef HAVE_ERROR_HISTORY
+ * PUBLIC: DB_MSGBUF *__db_deferred_get __P((void));
+ * PUBLIC: #endif
+ */
+DB_MSGBUF *
+__db_deferred_get()
+{
+ DB_MSGBUF *mb;
+
+ if ((mb = pthread_getspecific(DB_GLOBAL(msgs_key))) == NULL) {
+ if ((mb = calloc(1, sizeof(*mb))) != NULL)
+ if (pthread_setspecific(DB_GLOBAL(msgs_key), mb) != 0) {
+ /* Nothing else is safe do on an error. */
+ free(mb);
+ mb = NULL;
+ }
+ }
+ return (mb);
+}
+
+/*
+ * __db_deferred_discard --
+ * Discard any saved-up deferred messages, at e.g. the end of the command.
+ *
+ * PUBLIC: #ifdef HAVE_ERROR_HISTORY
+ * PUBLIC: void __db_deferred_discard __P((void));
+ * PUBLIC: #endif
+ */
+void
+__db_deferred_discard()
+{
+ DB_MSGBUF *mb;
+
+ if ((mb = pthread_getspecific(DB_GLOBAL(msgs_key))) != NULL)
+ mb->cur = mb->buf;
+}
+
+/*
+ * __db_remember_context
+ * Save the context which triggers the "first notice" of an error code;
+ * i.e., its creation. Include the time, thread, recent portion of the
+ * stack, and the error number. Add replication info too?
+ *
+ * Return the error number passed in, or 0?
+ *
+ * PUBLIC: #ifdef HAVE_ERROR_HISTORY
+ * PUBLIC: int __db_remember_context __P((const ENV *, DB_MSGBUF *, int));
+ * PUBLIC: #endif
+ */
+ int
+ __db_remember_context(env, mb, err)
+ const ENV *env;
+ DB_MSGBUF *mb;
+ int err;
+{
+ DB_ENV *dbenv;
+ LOG *lp;
+ db_timespec now;
+ pid_t pid;
+ db_threadid_t tid;
+ char threadid[DB_THREADID_STRLEN], timestr[CTIME_BUFLEN];
+
+ /* Limit the amount of context messges which are remembered. */
+ if (mb->len >= DB_ERROR_HISTORY_SIZE)
+ return (0);
+
+ lp = NULL;
+ if (env == NULL) {
+ dbenv = NULL;
+ threadid[0] = '\0';
+ } else {
+ dbenv = env->dbenv;
+ dbenv->thread_id(dbenv, &pid, &tid);
+ (void)dbenv->thread_id_string(dbenv, pid, tid, threadid);
+ if (LOGGING_ON(env) && !IS_RECOVERING(env))
+ lp = env->lg_handle->reginfo.primary;
+ }
+
+ __os_gettime(env, &now, 0);
+ (void)__db_ctimespec(&now, timestr);
+ __db_msgadd(env, mb, "\n[%s][%s] %s",
+ timestr, threadid, db_strerror(err));
+ if (lp != NULL)
+ __db_msgadd(env, mb, " lsn [%lu][%lu]",
+ (u_long)lp->lsn.file, (u_long)lp->lsn.offset);
+
+#if defined(HAVE_BACKTRACE) && defined(HAVE_BACKTRACE_SYMBOLS)
+ /*
+ * Add many frames of stack trace to the record, skipping the first two
+ * frames: __os_stack_msgadd() and __db_remember_context().
+ */
+ __db_msgadd(env, mb, " from\n");
+ __os_stack_msgadd(env, mb, 15, 2, NULL);
+#endif
+
+ return (0);
+}
+#endif
+
+/*
+ * __db_ctimespec --
+ * Format a timespec in microseconds, similar to a terse __os_ctime(),
+ * storing the results into a CTIME_BUFLEN sized buffer.
+ * The result format depends on the availability of localtime, etc
+ * MM/DD HH:MM:SS.uuuuuu if strftime is available, or
+ * Jan DD HH:MM:SS.uuuuuu if only __os_ctime() is available.
+ * Both are small enough to use __os_ctime() sized buffer, e.g. 26.
+ * The other fields (year, day-of-week, ...) are intentionally removed.
+ *
+ * PUBLIC: char * __db_ctimespec __P((const db_timespec *, char *));
+ */
+char *
+__db_ctimespec(timespec, buf)
+ const db_timespec *timespec;
+ char *buf;
+{
+ char *d, date[CTIME_BUFLEN];
+#ifdef HAVE_STRFTIME
+ struct tm *tm_p;
+#ifdef HAVE_LOCALTIME_R
+ struct tm tm;
+#endif
+#endif
+
+ /* Print the time readably if possible; else print seconds. */
+#ifdef HAVE_STRFTIME
+#ifdef HAVE_LOCALTIME_R
+ tm_p = localtime_r(&timespec->tv_sec, &tm);
+#else
+ tm_p = localtime(&timespec->tv_sec);
+#endif
+ if (tm_p != NULL) {
+ d = date;
+ (void)strftime(d, sizeof(date), DB_GLOBAL(time_format), tm_p);
+ }
+ else
+#endif
+ {
+ /* Trim off the leading day-of-week; then the trailing year. */
+ d = __os_ctime(&timespec->tv_sec, date) + 4;
+ d[sizeof("Jan 01 00:00:00")] = '\0';
+ }
+ (void)snprintf(buf, CTIME_BUFLEN,
+ "%s.%06lu", d, (u_long)(timespec->tv_nsec / NS_PER_US));
+ buf[CTIME_BUFLEN - 1] = '\0'; /* In case of buggy snprintf. */
+ return (buf);
+}
+
+/*
+ * __db_fmt_quote --
+ * Copy a printf format string, quoting (doubling) each '%' along the way.
+ * Use this when inserting a user-defined string into a *printf format.
+ * If the src parameter is NULL, then quote in-place, shifting the
+ * rest of the string down by one character for each quote.
+ *
+ * PUBLIC: char *__db_fmt_quote __P((char *, size_t, const char *));
+ */
+char *
+__db_fmt_quote(dest, destsize, src)
+ char *dest;
+ size_t destsize;
+ const char *src;
+{
+ char *d, *end;
+ const char *s;
+ size_t len;
+
+ /* Stop early enough so that dest always has room for a '\0'. */
+ end = dest + destsize - 1;
+ if (src == NULL) {
+ d = dest;
+ while ((d = strchr(d, '%')) != NULL && d[1] != '\0') {
+ /*
+ * Shift the rest of the string by one byte to make
+ * space for another '%'. By starting at d and adding 1
+ * to the length, we double the '%' while copying the
+ * string and its terminating '\0'.
+ */
+ len = strlen(d) + 1;
+ memmove(d + 1, d, len);
+ /*
+ * We're done if the string now is larger than the
+ * reserved size; else advance over both '%'s.
+ */
+ if (d + len >= end) {
+ DB_ASSERT(NULL, d + len == end);
+ *end = '\0';
+ break;
+ }
+ d += 2;
+ }
+ } else {
+ for (s = src, d = dest; *s != '\0' && d < end; d++, s++)
+ if ((*d = *s) == '%') {
+ /* Discard a % at the end of the string. */
+ if (s[1] == '\0')
+ break;
+ *++d = '%';
+ }
+ *d = '\0';
+ }
+ return (dest);
}