summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_subr/error.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_subr/error.c')
-rw-r--r--subversion/libsvn_subr/error.c800
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);
+}
OpenPOWER on IntegriCloud