/* 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 #include #include #include #include #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:"; #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); }