diff options
Diffstat (limited to 'src/common/db_err.c')
-rw-r--r-- | src/common/db_err.c | 636 |
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(×pec->tv_sec, &tm); +#else + tm_p = localtime(×pec->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(×pec->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); } |