diff options
Diffstat (limited to 'subversion/libsvn_subr/error.c')
-rw-r--r-- | subversion/libsvn_subr/error.c | 800 |
1 files changed, 800 insertions, 0 deletions
diff --git a/subversion/libsvn_subr/error.c b/subversion/libsvn_subr/error.c new file mode 100644 index 0000000..2f04320 --- /dev/null +++ b/subversion/libsvn_subr/error.c @@ -0,0 +1,800 @@ +/* error.c: common exception handling for Subversion + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <stdarg.h> + +#include <apr_general.h> +#include <apr_pools.h> +#include <apr_strings.h> + +#include <zlib.h> + +#ifndef SVN_ERR__TRACING +#define SVN_ERR__TRACING +#endif +#include "svn_cmdline.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_utf.h" + +#ifdef SVN_DEBUG +/* 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 * volatile error_file = NULL; +static long volatile error_line = -1; + +/* file_line for the non-debug case. */ +static const char SVN_FILE_LINE_UNDEFINED[] = "svn:<undefined>"; +#endif /* SVN_DEBUG */ + +#include "svn_private_config.h" +#include "private/svn_error_private.h" + + +/* + * Undefine the helpers for creating errors. + * + * *NOTE*: Any use of these functions in any other function may need + * to call svn_error__locate() because the macro that would otherwise + * do this is being undefined and the filename and line number will + * not be properly set in the static error_file and error_line + * variables. + */ +#undef svn_error_create +#undef svn_error_createf +#undef svn_error_quick_wrap +#undef svn_error_wrap_apr + +/* Note: Although this is a "__" function, it was historically in the + * public ABI, so we can never change it or remove its signature, even + * though it is now only used in SVN_DEBUG mode. */ +void +svn_error__locate(const char *file, long line) +{ +#if defined(SVN_DEBUG) + /* XXX TODO: Lock mutex here */ + error_file = file; + error_line = line; +#endif +} + + +/* Cleanup function for errors. svn_error_clear () removes this so + errors that are properly handled *don't* hit this code. */ +#if defined(SVN_DEBUG) +static apr_status_t err_abort(void *data) +{ + svn_error_t *err = data; /* For easy viewing in a debugger */ + err = err; /* Fake a use for the variable to avoid compiler warnings */ + + if (!getenv("SVN_DBG_NO_ABORT_ON_ERROR_LEAK")) + abort(); + return APR_SUCCESS; +} +#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; +#if defined(SVN_DEBUG) + new_error->file = error_file; + new_error->line = error_line; + /* XXX TODO: Unlock mutex here */ + + if (! child) + apr_pool_cleanup_register(pool, new_error, + err_abort, + apr_pool_cleanup_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); + if (msg_apr) + { + err->message = apr_pstrcat(err->pool, msg, ": ", msg_apr, NULL); + } + else + { + err->message = msg; + } + } + + return err; +} + + +svn_error_t * +svn_error_quick_wrap(svn_error_t *child, const char *new_msg) +{ + if (child == SVN_NO_ERROR) + return SVN_NO_ERROR; + + return svn_error_create(child->apr_err, + child, + new_msg); +} + +/* Messages in tracing errors all point to this static string. */ +static const char error_tracing_link[] = "traced call"; + +svn_error_t * +svn_error__trace(const char *file, long line, svn_error_t *err) +{ +#ifndef SVN_DEBUG + + /* We shouldn't even be here, but whatever. Just return the error as-is. */ + return err; + +#else + + /* Only do the work when an error occurs. */ + if (err) + { + svn_error_t *trace; + svn_error__locate(file, line); + trace = make_error_internal(err->apr_err, err); + trace->message = error_tracing_link; + return trace; + } + return SVN_NO_ERROR; + +#endif +} + + +svn_error_t * +svn_error_compose_create(svn_error_t *err1, + svn_error_t *err2) +{ + if (err1 && err2) + { + svn_error_compose(err1, + svn_error_quick_wrap(err2, + _("Additional errors:"))); + return err1; + } + return err1 ? err1 : err2; +} + + +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) + /* 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) + if (! new_err->child) + apr_pool_cleanup_kill(oldpool, new_err, err_abort); +#endif + new_err = new_err->child; + } + +#if defined(SVN_DEBUG) + apr_pool_cleanup_register(pool, chain, + err_abort, + apr_pool_cleanup_null); +#endif + + /* Destroy the new error chain. */ + svn_pool_destroy(oldpool); +} + +svn_error_t * +svn_error_root_cause(svn_error_t *err) +{ + while (err) + { + if (err->child) + err = err->child; + else + break; + } + + return err; +} + +svn_error_t * +svn_error_find_cause(svn_error_t *err, apr_status_t apr_err) +{ + svn_error_t *child; + + for (child = err; child; child = child->child) + if (child->apr_err == apr_err) + return child; + + return SVN_NO_ERROR; +} + +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) + apr_pool_cleanup_register(pool, tmp_err, + err_abort, + apr_pool_cleanup_null); +#endif + + return new_err; +} + +void +svn_error_clear(svn_error_t *err) +{ + if (err) + { +#if defined(SVN_DEBUG) + while (err->child) + err = err->child; + apr_pool_cleanup_kill(err->pool, err, err_abort); +#endif + svn_pool_destroy(err->pool); + } +} + +svn_boolean_t +svn_error__is_tracing_link(svn_error_t *err) +{ +#ifdef SVN_ERR__TRACING + /* ### A strcmp()? Really? I think it's the best we can do unless + ### we add a boolean field to svn_error_t that's set only for + ### these "placeholder error chain" items. Not such a bad idea, + ### really... */ + return (err && err->message && !strcmp(err->message, error_tracing_link)); +#else + return FALSE; +#endif +} + +svn_error_t * +svn_error_purge_tracing(svn_error_t *err) +{ +#ifdef SVN_ERR__TRACING + svn_error_t *new_err = NULL, *new_err_leaf = NULL; + + if (! err) + return SVN_NO_ERROR; + + do + { + svn_error_t *tmp_err; + + /* Skip over any trace-only links. */ + while (err && svn_error__is_tracing_link(err)) + err = err->child; + + /* The link must be a real link in the error chain, otherwise an + error chain with trace only links would map into SVN_NO_ERROR. */ + if (! err) + return svn_error_create( + SVN_ERR_ASSERTION_ONLY_TRACING_LINKS, + svn_error_compose_create( + svn_error__malfunction(TRUE, __FILE__, __LINE__, + NULL /* ### say something? */), + err), + NULL); + + /* Copy the current error except for its child error pointer + into the new error. Share any message and source filename + strings from the error. */ + tmp_err = apr_palloc(err->pool, sizeof(*tmp_err)); + *tmp_err = *err; + tmp_err->child = NULL; + + /* Add a new link to the new chain (creating the chain if necessary). */ + if (! new_err) + { + new_err = tmp_err; + new_err_leaf = tmp_err; + } + else + { + new_err_leaf->child = tmp_err; + new_err_leaf = tmp_err; + } + + /* Advance to the next link in the original chain. */ + err = err->child; + } while (err); + + return new_err; +#else /* SVN_ERR__TRACING */ + return err; +#endif /* SVN_ERR__TRACING */ +} + +/* ### The logic around omitting (sic) apr_err= in maintainer mode is tightly + ### coupled to the current sole caller.*/ +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); + } + + { + const char *symbolic_name; + if (svn_error__is_tracing_link(err)) + /* Skip it; the error code will be printed by the real link. */ + svn_error_clear(svn_cmdline_fprintf(stream, err->pool, ",\n")); + else if ((symbolic_name = svn_error_symbolic_name(err->apr_err))) + svn_error_clear(svn_cmdline_fprintf(stream, err->pool, + ": (apr_err=%s)\n", symbolic_name)); + else + svn_error_clear(svn_cmdline_fprintf(stream, err->pool, + ": (apr_err=%d)\n", err->apr_err)); + } +#endif /* SVN_DEBUG */ + + /* "traced call" */ + if (svn_error__is_tracing_link(err)) + { + /* Skip it. We already printed the file-line coordinates. */ + } + /* Only print the same APR error string once. */ + else if (err->message) + { + svn_error_clear(svn_cmdline_fprintf(stream, err->pool, + "%sE%06d: %s\n", + prefix, err->apr_err, 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, + "%sE%06d: %s\n", + prefix, err->apr_err, 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; + svn_error_t *tmp_err; + + /* ### 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 r843793, 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)); + + tmp_err = err; + while (tmp_err) + { + svn_boolean_t printed_already = FALSE; + + if (! tmp_err->message) + { + int i; + + for (i = 0; i < empties->nelts; i++) + { + if (tmp_err->apr_err == APR_ARRAY_IDX(empties, i, apr_status_t) ) + { + printed_already = TRUE; + break; + } + } + } + + if (! printed_already) + { + print_error(tmp_err, stream, prefix); + if (! tmp_err->message) + { + APR_ARRAY_PUSH(empties, apr_status_t) = tmp_err->apr_err; + } + } + + tmp_err = tmp_err->child; + } + + svn_pool_destroy(subpool); + + fflush(stream); + if (fatal) + { + /* Avoid abort()s in maintainer mode. */ + svn_error_clear(err); + + /* We exit(1) here instead of abort()ing so that atexit handlers + get called. */ + exit(EXIT_FAILURE); + } +} + + +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: W%06d: %s\n"), + prefix, err->apr_err, + 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) +{ + /* Skip over any trace records. */ + while (svn_error__is_tracing_link(err)) + err = err->child; + if (err->message) + return err->message; + else + return svn_strerror(err->apr_err, buf, bufsize); +} + + +/* svn_strerror() and helpers */ + +/* Duplicate of the same typedef in tests/libsvn_subr/error-code-test.c */ +typedef struct err_defn { + svn_errno_t errcode; /* 160004 */ + const char *errname; /* SVN_ERR_FS_CORRUPT */ + const char *errdesc; /* default message */ +} 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); +} + +const char * +svn_error_symbolic_name(apr_status_t statcode) +{ + const err_defn *defn; + + for (defn = error_table; defn->errdesc != NULL; ++defn) + if (defn->errcode == (svn_errno_t)statcode) + return defn->errname; + + /* "No error" is not in error_table. */ + if (statcode == SVN_NO_ERROR) + return "SVN_NO_ERROR"; + + return NULL; +} + + + +/* Malfunctions. */ + +svn_error_t * +svn_error_raise_on_malfunction(svn_boolean_t can_return, + const char *file, int line, + const char *expr) +{ + if (!can_return) + abort(); /* Nothing else we can do as a library */ + + /* The filename and line number of the error source needs to be set + here because svn_error_createf() is not the macro defined in + svn_error.h but the real function. */ + svn_error__locate(file, line); + + if (expr) + return svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL, + _("In file '%s' line %d: assertion failed (%s)"), + file, line, expr); + else + return svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL, + _("In file '%s' line %d: internal malfunction"), + file, line); +} + +svn_error_t * +svn_error_abort_on_malfunction(svn_boolean_t can_return, + const char *file, int line, + const char *expr) +{ + svn_error_t *err = svn_error_raise_on_malfunction(TRUE, file, line, expr); + + svn_handle_error2(err, stderr, FALSE, "svn: "); + abort(); + return err; /* Not reached. */ +} + +/* The current handler for reporting malfunctions, and its default setting. */ +static svn_error_malfunction_handler_t malfunction_handler + = svn_error_abort_on_malfunction; + +svn_error_malfunction_handler_t +svn_error_set_malfunction_handler(svn_error_malfunction_handler_t func) +{ + svn_error_malfunction_handler_t old_malfunction_handler + = malfunction_handler; + + malfunction_handler = func; + return old_malfunction_handler; +} + +/* Note: Although this is a "__" function, it is in the public ABI, so + * we can never remove it or change its signature. */ +svn_error_t * +svn_error__malfunction(svn_boolean_t can_return, + const char *file, int line, + const char *expr) +{ + return malfunction_handler(can_return, file, line, expr); +} + + +/* Misc. */ + +svn_error_t * +svn_error__wrap_zlib(int zerr, const char *function, const char *message) +{ + apr_status_t status; + const char *zmsg; + + if (zerr == Z_OK) + return SVN_NO_ERROR; + + switch (zerr) + { + case Z_STREAM_ERROR: + status = SVN_ERR_STREAM_MALFORMED_DATA; + zmsg = _("stream error"); + break; + + case Z_MEM_ERROR: + status = APR_ENOMEM; + zmsg = _("out of memory"); + break; + + case Z_BUF_ERROR: + status = APR_ENOMEM; + zmsg = _("buffer error"); + break; + + case Z_VERSION_ERROR: + status = SVN_ERR_STREAM_UNRECOGNIZED_DATA; + zmsg = _("version error"); + break; + + case Z_DATA_ERROR: + status = SVN_ERR_STREAM_MALFORMED_DATA; + zmsg = _("corrupt data"); + break; + + default: + status = SVN_ERR_STREAM_UNRECOGNIZED_DATA; + zmsg = _("unknown error"); + break; + } + + if (message != NULL) + return svn_error_createf(status, NULL, "zlib (%s): %s: %s", function, + zmsg, message); + else + return svn_error_createf(status, NULL, "zlib (%s): %s", function, zmsg); +} |