/* error.c: common exception handling for Subversion * * ==================================================================== * Copyright (c) 2000-2004 CollabNet. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://subversion.tigris.org/license-1.html. * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://subversion.tigris.org/. * ==================================================================== */ #include #include #include #include #include #include "svn_cmdline.h" #include "svn_error.h" #ifdef SVN_DEBUG /* file_line for the non-debug case. */ static const char SVN_FILE_LINE_UNDEFINED[] = "svn:"; #endif /* SVN_DEBUG */ #include "svn_private_config.h" /*** Helpers for creating errors ***/ #undef svn_error_create #undef svn_error_createf #undef svn_error_quick_wrap #undef svn_error_wrap_apr /* XXX FIXME: These should be protected by a thread mutex. svn_error__locate and make_error_internal should cooperate in locking and unlocking it. */ /* XXX TODO: Define mutex here #if APR_HAS_THREADS */ static const char *error_file = NULL; static long error_line = -1; void svn_error__locate(const char *file, long line) { /* XXX TODO: Lock mutex here */ error_file = file; error_line = line; } /* Cleanup function for errors. svn_error_clear () removes this so errors that are properly handled *don't* hit this code. */ #if defined(SVN_DEBUG_ERROR) static apr_status_t err_abort(void *data) { svn_error_t *err = data; /* For easy viewing in a debugger */ abort(); err = err; /* Fake a use for the variable */ } #endif static svn_error_t * make_error_internal(apr_status_t apr_err, svn_error_t *child) { apr_pool_t *pool; svn_error_t *new_error; /* Reuse the child's pool, or create our own. */ if (child) pool = child->pool; else { if (apr_pool_create(&pool, NULL)) abort(); } /* Create the new error structure */ new_error = apr_pcalloc(pool, sizeof(*new_error)); /* Fill 'er up. */ new_error->apr_err = apr_err; new_error->child = child; new_error->pool = pool; new_error->file = error_file; new_error->line = error_line; /* XXX TODO: Unlock mutex here */ #if defined(SVN_DEBUG_ERROR) if (! child) apr_pool_cleanup_register(pool, new_error, err_abort, NULL); #endif return new_error; } /*** Creating and destroying errors. ***/ svn_error_t * svn_error_create(apr_status_t apr_err, svn_error_t *child, const char *message) { svn_error_t *err; err = make_error_internal(apr_err, child); if (message) err->message = apr_pstrdup(err->pool, message); return err; } svn_error_t * svn_error_createf(apr_status_t apr_err, svn_error_t *child, const char *fmt, ...) { svn_error_t *err; va_list ap; err = make_error_internal(apr_err, child); va_start(ap, fmt); err->message = apr_pvsprintf(err->pool, fmt, ap); va_end(ap); return err; } svn_error_t * svn_error_wrap_apr(apr_status_t status, const char *fmt, ...) { svn_error_t *err, *utf8_err; va_list ap; char errbuf[255]; const char *msg_apr, *msg; err = make_error_internal(status, NULL); if (fmt) { /* Grab the APR error message. */ apr_strerror(status, errbuf, sizeof(errbuf)); utf8_err = svn_utf_cstring_to_utf8(&msg_apr, errbuf, err->pool); if (utf8_err) msg_apr = NULL; svn_error_clear(utf8_err); /* Append it to the formatted message. */ va_start(ap, fmt); msg = apr_pvsprintf(err->pool, fmt, ap); va_end(ap); err->message = apr_psprintf(err->pool, "%s%s%s", msg, (msg_apr) ? ": " : "", (msg_apr) ? msg_apr : ""); } return err; } svn_error_t * svn_error_quick_wrap(svn_error_t *child, const char *new_msg) { return svn_error_create(child->apr_err, child, new_msg); } void svn_error_compose(svn_error_t *chain, svn_error_t *new_err) { apr_pool_t *pool = chain->pool; apr_pool_t *oldpool = new_err->pool; while (chain->child) chain = chain->child; #if defined(SVN_DEBUG_ERROR) /* Kill existing handler since the end of the chain is going to change */ apr_pool_cleanup_kill(pool, chain, err_abort); #endif /* Copy the new error chain into the old chain's pool. */ while (new_err) { chain->child = apr_palloc(pool, sizeof(*chain->child)); chain = chain->child; *chain = *new_err; if (chain->message) chain->message = apr_pstrdup(pool, new_err->message); chain->pool = pool; #if defined(SVN_DEBUG_ERROR) if (! new_err->child) apr_pool_cleanup_kill(oldpool, new_err, err_abort); #endif new_err = new_err->child; } #if defined(SVN_DEBUG_ERROR) apr_pool_cleanup_register(pool, chain, err_abort, NULL); #endif /* Destroy the new error chain. */ apr_pool_destroy(oldpool); } svn_error_t * svn_error_dup(svn_error_t *err) { apr_pool_t *pool; svn_error_t *new_err = NULL, *tmp_err = NULL; if (apr_pool_create(&pool, NULL)) abort(); for (; err; err = err->child) { if (! new_err) { new_err = apr_palloc(pool, sizeof(*new_err)); tmp_err = new_err; } else { tmp_err->child = apr_palloc(pool, sizeof(*tmp_err->child)); tmp_err = tmp_err->child; } *tmp_err = *err; tmp_err->pool = pool; if (tmp_err->message) tmp_err->message = apr_pstrdup(pool, tmp_err->message); } #if defined(SVN_DEBUG_ERROR) apr_pool_cleanup_register(pool, tmp_err, err_abort, NULL); #endif return new_err; } void svn_error_clear(svn_error_t *err) { if (err) { #if defined(SVN_DEBUG_ERROR) while (err->child) err = err->child; apr_pool_cleanup_kill(err->pool, err, err_abort); #endif apr_pool_destroy(err->pool); } } static void print_error(svn_error_t *err, FILE *stream, const char *prefix) { char errbuf[256]; const char *err_string; svn_error_t *temp_err = NULL; /* ensure initialized even if err->file == NULL */ /* Pretty-print the error */ /* Note: we can also log errors here someday. */ #ifdef SVN_DEBUG /* Note: err->file is _not_ in UTF-8, because it's expanded from the __FILE__ preprocessor macro. */ const char *file_utf8; if (err->file && !(temp_err = svn_utf_cstring_to_utf8(&file_utf8, err->file, err->pool))) svn_error_clear(svn_cmdline_fprintf(stream, err->pool, "%s:%ld", err->file, err->line)); else { svn_error_clear(svn_cmdline_fputs(SVN_FILE_LINE_UNDEFINED, stream, err->pool)); svn_error_clear(temp_err); } svn_error_clear(svn_cmdline_fprintf(stream, err->pool, ": (apr_err=%d)\n", err->apr_err)); #endif /* SVN_DEBUG */ /* Only print the same APR error string once. */ if (err->message) { svn_error_clear(svn_cmdline_fprintf(stream, err->pool, "%s%s\n", prefix, err->message)); } else { /* Is this a Subversion-specific error code? */ if ((err->apr_err > APR_OS_START_USEERR) && (err->apr_err <= APR_OS_START_CANONERR)) err_string = svn_strerror(err->apr_err, errbuf, sizeof(errbuf)); /* Otherwise, this must be an APR error code. */ else if ((temp_err = svn_utf_cstring_to_utf8 (&err_string, apr_strerror(err->apr_err, errbuf, sizeof(errbuf)), err->pool))) { svn_error_clear(temp_err); err_string = _("Can't recode error string from APR"); } svn_error_clear(svn_cmdline_fprintf(stream, err->pool, "%s%s\n", prefix, err_string)); } } void svn_handle_error(svn_error_t *err, FILE *stream, svn_boolean_t fatal) { svn_handle_error2(err, stream, fatal, "svn: "); } void svn_handle_error2(svn_error_t *err, FILE *stream, svn_boolean_t fatal, const char *prefix) { /* In a long error chain, there may be multiple errors with the same error code and no custom message. We only want to print the default message for that code once; printing it multiple times would add no useful information. The 'empties' array below remembers the codes of empty errors already seen in the chain. We could allocate it in err->pool, but there's no telling how long err will live or how many times it will get handled. So we use a subpool. */ apr_pool_t *subpool; apr_array_header_t *empties; /* ### The rest of this file carefully avoids using svn_pool_*(), preferring apr_pool_*() instead. I can't remember why -- it may be an artifact of r3719, or it may be for some deeper reason -- but I'm playing it safe and using apr_pool_*() here too. */ apr_pool_create(&subpool, err->pool); empties = apr_array_make(subpool, 0, sizeof(apr_status_t)); while (err) { int i; svn_boolean_t printed_already = FALSE; if (! err->message) { for (i = 0; i < empties->nelts; i++) { if (err->apr_err == ((apr_status_t *)empties->elts)[i]) { printed_already = TRUE; break; } } } if (! printed_already) { print_error(err, stream, prefix); if (! err->message) { (*((apr_status_t *) apr_array_push(empties))) = err->apr_err; } } err = err->child; } apr_pool_destroy(subpool); fflush(stream); if (fatal) /* XXX Shouldn't we exit(1) here instead, so that atexit handlers get called? --xbc */ abort(); } void svn_handle_warning(FILE *stream, svn_error_t *err) { svn_handle_warning2(stream, err, "svn: "); } void svn_handle_warning2(FILE *stream, svn_error_t *err, const char *prefix) { char buf[256]; svn_error_clear(svn_cmdline_fprintf (stream, err->pool, _("%swarning: %s\n"), prefix, svn_err_best_message(err, buf, sizeof(buf)))); fflush(stream); } const char * svn_err_best_message(svn_error_t *err, char *buf, apr_size_t bufsize) { if (err->message) return err->message; else return svn_strerror(err->apr_err, buf, bufsize); } /* svn_strerror() and helpers */ typedef struct { svn_errno_t errcode; const char *errdesc; } err_defn; /* To understand what is going on here, read svn_error_codes.h. */ #define SVN_ERROR_BUILD_ARRAY #include "svn_error_codes.h" char * svn_strerror(apr_status_t statcode, char *buf, apr_size_t bufsize) { const err_defn *defn; for (defn = error_table; defn->errdesc != NULL; ++defn) if (defn->errcode == (svn_errno_t)statcode) { apr_cpystrn(buf, _(defn->errdesc), bufsize); return buf; } return apr_strerror(statcode, buf, bufsize); }