blob: 1b2e7357e1faebcd7a345b7224890e0b68f61967 [file] [log] [blame]
/* 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>
#if defined(SVN_DEBUG) && APR_HAS_THREADS
#include <apr_thread_proc.h>
#endif
#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"
#include "private/svn_error_private.h"
#include "svn_private_config.h"
#if defined(SVN_DEBUG) && APR_HAS_THREADS
#include "private/svn_atomic.h"
#include "pools.h"
#endif
#ifdef SVN_DEBUG
# if APR_HAS_THREADS
static apr_threadkey_t *error_file_key = NULL;
static apr_threadkey_t *error_line_key = NULL;
/* No-op destructor for apr_threadkey_private_create(). */
static void null_threadkey_dtor(void *stuff) {}
/* Implements svn_atomic__str_init_func_t.
Callback used by svn_error__locate to initialize the thread-local
error location storage. This function will never return an
error string. */
static const char *
locate_init_once(void *ignored_baton)
{
/* Strictly speaking, this is a memory leak, since we're creating an
unmanaged, top-level pool and never destroying it. We do this
because this pool controls the lifetime of the thread-local
storage for error locations, and that storage must always be
available. */
apr_pool_t *threadkey_pool = svn_pool__create_unmanaged(TRUE);
apr_status_t status;
status = apr_threadkey_private_create(&error_file_key,
null_threadkey_dtor,
threadkey_pool);
if (status == APR_SUCCESS)
status = apr_threadkey_private_create(&error_line_key,
null_threadkey_dtor,
threadkey_pool);
/* If anything went wrong with the creation of the thread-local
storage, we'll revert to the old, thread-agnostic behaviour */
if (status != APR_SUCCESS)
error_file_key = error_line_key = NULL;
return NULL;
}
# endif /* APR_HAS_THREADS */
/* These location variables will be used in no-threads mode or if
thread-local storage is not available. */
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 */
/*
* 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_quick_wrapf
#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)
{
#ifdef SVN_DEBUG
# if APR_HAS_THREADS
static volatile svn_atomic_t init_status = 0;
svn_atomic__init_once_no_error(&init_status, locate_init_once, NULL);
if (error_file_key && error_line_key)
{
apr_status_t status;
status = apr_threadkey_private_set((char*)file, error_file_key);
if (status == APR_SUCCESS)
status = apr_threadkey_private_set((void*)line, error_line_key);
if (status == APR_SUCCESS)
return;
}
# endif /* APR_HAS_THREADS */
error_file = file;
error_line = line;
#endif /* SVN_DEBUG */
}
/* Cleanup function for errors. svn_error_clear () removes this so
errors that are properly handled *don't* hit this code. */
static apr_status_t err_abort(void *data)
{
svn_error_t *err = data; /* For easy viewing in a debugger */
SVN_UNUSED(err);
if (!getenv("SVN_DBG_NO_ABORT_ON_ERROR_LEAK"))
abort();
return APR_SUCCESS;
}
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;
#ifdef SVN_DEBUG
apr_status_t status = APR_ENOTIMPL;
#endif
/* Reuse the child's pool, or create our own. */
if (child)
pool = child->pool;
else
{
pool = svn_pool_create(NULL);
if (!pool)
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;
#ifdef SVN_DEBUG
#if APR_HAS_THREADS
if (error_file_key && error_line_key)
{
void *item;
status = apr_threadkey_private_get(&item, error_file_key);
if (status == APR_SUCCESS)
{
new_error->file = item;
status = apr_threadkey_private_get(&item, error_line_key);
if (status == APR_SUCCESS)
new_error->line = (long)item;
}
}
# endif /* APR_HAS_THREADS */
if (status != APR_SUCCESS)
{
new_error->file = error_file;
new_error->line = error_line;
}
if (! child)
apr_pool_cleanup_register(pool, new_error,
err_abort,
apr_pool_cleanup_null);
#endif /* SVN_DEBUG */
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,
SVN_VA_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);
}
svn_error_t *
svn_error_quick_wrapf(svn_error_t *child,
const char *fmt,
...)
{
svn_error_t *err;
va_list ap;
if (child == SVN_NO_ERROR)
return SVN_NO_ERROR;
err = make_error_internal(child->apr_err, child);
va_start(ap, fmt);
err->message = apr_pvsprintf(err->pool, fmt, ap);
va_end(ap);
return err;
}
/* 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_create(SVN_ERR_COMPOSED_ERROR, err2, NULL));
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);
if (chain->file)
chain->file = apr_pstrdup(pool, new_err->file);
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)
{
/* I don't think we can change the behavior here, but the additional
error chain doesn't define the root cause. Perhaps we should rev
this function. */
if (err->child /*&& err->child->apr_err != SVN_ERR_COMPOSED_ERROR*/)
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(const svn_error_t *err)
{
apr_pool_t *pool;
svn_error_t *new_err = NULL, *tmp_err = NULL;
if (!err)
return SVN_NO_ERROR;
pool = svn_pool_create(NULL);
if (!pool)
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 (tmp_err->file)
tmp_err->file = apr_pstrdup(pool, tmp_err->file);
}
#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(const 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__malfunction(TRUE, __FILE__, __LINE__,
NULL /* ### say something? */),
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_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;
subpool = svn_pool_create(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_warning2(FILE *stream, const svn_error_t *err, const char *prefix)
{
char buf[256];
#ifdef SVN_DEBUG
const char *symbolic_name = svn_error_symbolic_name(err->apr_err);
#endif
#ifdef SVN_DEBUG
if (symbolic_name)
svn_error_clear(
svn_cmdline_fprintf(stream, err->pool, "%swarning: apr_err=%s\n",
prefix, symbolic_name));
#endif
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(const 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);
}
#ifdef SVN_DEBUG
/* Defines svn__errno and svn__apr_errno */
#include "errorcode.inc"
#endif
const char *
svn_error_symbolic_name(apr_status_t statcode)
{
const err_defn *defn;
#ifdef SVN_DEBUG
int i;
#endif /* SVN_DEBUG */
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 == APR_SUCCESS)
return "SVN_NO_ERROR";
#ifdef SVN_DEBUG
/* Try errno.h symbols. */
/* Linear search through a sorted array */
for (i = 0; i < sizeof(svn__errno) / sizeof(svn__errno[0]); i++)
if (svn__errno[i].errcode == (int)statcode)
return svn__errno[i].errname;
/* Try APR errors. */
/* Linear search through a sorted array */
for (i = 0; i < sizeof(svn__apr_errno) / sizeof(svn__apr_errno[0]); i++)
if (svn__apr_errno[i].errcode == (int)statcode)
return svn__apr_errno[i].errname;
#endif /* SVN_DEBUG */
/* ### TODO: do we need APR_* error macros? What about APR_TO_OS_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;
}
svn_error_malfunction_handler_t
svn_error_get_malfunction_handler(void)
{
return 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);
}