1/* error.c: common exception handling for Subversion 2 * 3 * ==================================================================== 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 * ==================================================================== 21 */ 22 23 24 25#include <stdarg.h> 26 27#include <apr_general.h> 28#include <apr_pools.h> 29#include <apr_strings.h> 30 31#include <zlib.h> 32 33#ifndef SVN_ERR__TRACING 34#define SVN_ERR__TRACING 35#endif 36#include "svn_cmdline.h" 37#include "svn_error.h" 38#include "svn_pools.h" 39#include "svn_utf.h" 40 41#ifdef SVN_DEBUG 42/* XXX FIXME: These should be protected by a thread mutex. 43 svn_error__locate and make_error_internal should cooperate 44 in locking and unlocking it. */ 45 46/* XXX TODO: Define mutex here #if APR_HAS_THREADS */ 47static const char * volatile error_file = NULL; 48static long volatile error_line = -1; 49 50/* file_line for the non-debug case. */ 51static const char SVN_FILE_LINE_UNDEFINED[] = "svn:<undefined>"; 52#endif /* SVN_DEBUG */ 53 54#include "svn_private_config.h" 55#include "private/svn_error_private.h" 56 57 58/* 59 * Undefine the helpers for creating errors. 60 * 61 * *NOTE*: Any use of these functions in any other function may need 62 * to call svn_error__locate() because the macro that would otherwise 63 * do this is being undefined and the filename and line number will 64 * not be properly set in the static error_file and error_line 65 * variables. 66 */ 67#undef svn_error_create 68#undef svn_error_createf 69#undef svn_error_quick_wrap 70#undef svn_error_wrap_apr 71 72/* Note: Although this is a "__" function, it was historically in the 73 * public ABI, so we can never change it or remove its signature, even 74 * though it is now only used in SVN_DEBUG mode. */ 75void 76svn_error__locate(const char *file, long line) 77{ 78#if defined(SVN_DEBUG) 79 /* XXX TODO: Lock mutex here */ 80 error_file = file; 81 error_line = line; 82#endif 83} 84 85 86/* Cleanup function for errors. svn_error_clear () removes this so 87 errors that are properly handled *don't* hit this code. */ 88#if defined(SVN_DEBUG) 89static apr_status_t err_abort(void *data) 90{ 91 svn_error_t *err = data; /* For easy viewing in a debugger */ 92 err = err; /* Fake a use for the variable to avoid compiler warnings */ 93 94 if (!getenv("SVN_DBG_NO_ABORT_ON_ERROR_LEAK")) 95 abort(); 96 return APR_SUCCESS; 97} 98#endif 99 100 101static svn_error_t * 102make_error_internal(apr_status_t apr_err, 103 svn_error_t *child) 104{ 105 apr_pool_t *pool; 106 svn_error_t *new_error; 107 108 /* Reuse the child's pool, or create our own. */ 109 if (child) 110 pool = child->pool; 111 else 112 { 113 if (apr_pool_create(&pool, NULL)) 114 abort(); 115 } 116 117 /* Create the new error structure */ 118 new_error = apr_pcalloc(pool, sizeof(*new_error)); 119 120 /* Fill 'er up. */ 121 new_error->apr_err = apr_err; 122 new_error->child = child; 123 new_error->pool = pool; 124#if defined(SVN_DEBUG) 125 new_error->file = error_file; 126 new_error->line = error_line; 127 /* XXX TODO: Unlock mutex here */ 128 129 if (! child) 130 apr_pool_cleanup_register(pool, new_error, 131 err_abort, 132 apr_pool_cleanup_null); 133#endif 134 135 return new_error; 136} 137 138 139 140/*** Creating and destroying errors. ***/ 141 142svn_error_t * 143svn_error_create(apr_status_t apr_err, 144 svn_error_t *child, 145 const char *message) 146{ 147 svn_error_t *err; 148 149 err = make_error_internal(apr_err, child); 150 151 if (message) 152 err->message = apr_pstrdup(err->pool, message); 153 154 return err; 155} 156 157 158svn_error_t * 159svn_error_createf(apr_status_t apr_err, 160 svn_error_t *child, 161 const char *fmt, 162 ...) 163{ 164 svn_error_t *err; 165 va_list ap; 166 167 err = make_error_internal(apr_err, child); 168 169 va_start(ap, fmt); 170 err->message = apr_pvsprintf(err->pool, fmt, ap); 171 va_end(ap); 172 173 return err; 174} 175 176 177svn_error_t * 178svn_error_wrap_apr(apr_status_t status, 179 const char *fmt, 180 ...) 181{ 182 svn_error_t *err, *utf8_err; 183 va_list ap; 184 char errbuf[255]; 185 const char *msg_apr, *msg; 186 187 err = make_error_internal(status, NULL); 188 189 if (fmt) 190 { 191 /* Grab the APR error message. */ 192 apr_strerror(status, errbuf, sizeof(errbuf)); 193 utf8_err = svn_utf_cstring_to_utf8(&msg_apr, errbuf, err->pool); 194 if (utf8_err) 195 msg_apr = NULL; 196 svn_error_clear(utf8_err); 197 198 /* Append it to the formatted message. */ 199 va_start(ap, fmt); 200 msg = apr_pvsprintf(err->pool, fmt, ap); 201 va_end(ap); 202 if (msg_apr) 203 { 204 err->message = apr_pstrcat(err->pool, msg, ": ", msg_apr, NULL); 205 } 206 else 207 { 208 err->message = msg; 209 } 210 } 211 212 return err; 213} 214 215 216svn_error_t * 217svn_error_quick_wrap(svn_error_t *child, const char *new_msg) 218{ 219 if (child == SVN_NO_ERROR) 220 return SVN_NO_ERROR; 221 222 return svn_error_create(child->apr_err, 223 child, 224 new_msg); 225} 226 227/* Messages in tracing errors all point to this static string. */ 228static const char error_tracing_link[] = "traced call"; 229 230svn_error_t * 231svn_error__trace(const char *file, long line, svn_error_t *err) 232{ 233#ifndef SVN_DEBUG 234 235 /* We shouldn't even be here, but whatever. Just return the error as-is. */ 236 return err; 237 238#else 239 240 /* Only do the work when an error occurs. */ 241 if (err) 242 { 243 svn_error_t *trace; 244 svn_error__locate(file, line); 245 trace = make_error_internal(err->apr_err, err); 246 trace->message = error_tracing_link; 247 return trace; 248 } 249 return SVN_NO_ERROR; 250 251#endif 252} 253 254 255svn_error_t * 256svn_error_compose_create(svn_error_t *err1, 257 svn_error_t *err2) 258{ 259 if (err1 && err2) 260 { 261 svn_error_compose(err1, 262 svn_error_quick_wrap(err2, 263 _("Additional errors:"))); 264 return err1; 265 } 266 return err1 ? err1 : err2; 267} 268 269 270void 271svn_error_compose(svn_error_t *chain, svn_error_t *new_err) 272{ 273 apr_pool_t *pool = chain->pool; 274 apr_pool_t *oldpool = new_err->pool; 275 276 while (chain->child) 277 chain = chain->child; 278 279#if defined(SVN_DEBUG) 280 /* Kill existing handler since the end of the chain is going to change */ 281 apr_pool_cleanup_kill(pool, chain, err_abort); 282#endif 283 284 /* Copy the new error chain into the old chain's pool. */ 285 while (new_err) 286 { 287 chain->child = apr_palloc(pool, sizeof(*chain->child)); 288 chain = chain->child; 289 *chain = *new_err; 290 if (chain->message) 291 chain->message = apr_pstrdup(pool, new_err->message); 292 chain->pool = pool; 293#if defined(SVN_DEBUG) 294 if (! new_err->child) 295 apr_pool_cleanup_kill(oldpool, new_err, err_abort); 296#endif 297 new_err = new_err->child; 298 } 299 300#if defined(SVN_DEBUG) 301 apr_pool_cleanup_register(pool, chain, 302 err_abort, 303 apr_pool_cleanup_null); 304#endif 305 306 /* Destroy the new error chain. */ 307 svn_pool_destroy(oldpool); 308} 309 310svn_error_t * 311svn_error_root_cause(svn_error_t *err) 312{ 313 while (err) 314 { 315 if (err->child) 316 err = err->child; 317 else 318 break; 319 } 320 321 return err; 322} 323 324svn_error_t * 325svn_error_find_cause(svn_error_t *err, apr_status_t apr_err) 326{ 327 svn_error_t *child; 328 329 for (child = err; child; child = child->child) 330 if (child->apr_err == apr_err) 331 return child; 332 333 return SVN_NO_ERROR; 334} 335 336svn_error_t * 337svn_error_dup(svn_error_t *err) 338{ 339 apr_pool_t *pool; 340 svn_error_t *new_err = NULL, *tmp_err = NULL; 341 342 if (apr_pool_create(&pool, NULL)) 343 abort(); 344 345 for (; err; err = err->child) 346 { 347 if (! new_err) 348 { 349 new_err = apr_palloc(pool, sizeof(*new_err)); 350 tmp_err = new_err; 351 } 352 else 353 { 354 tmp_err->child = apr_palloc(pool, sizeof(*tmp_err->child)); 355 tmp_err = tmp_err->child; 356 } 357 *tmp_err = *err; 358 tmp_err->pool = pool; 359 if (tmp_err->message) 360 tmp_err->message = apr_pstrdup(pool, tmp_err->message); 361 } 362 363#if defined(SVN_DEBUG) 364 apr_pool_cleanup_register(pool, tmp_err, 365 err_abort, 366 apr_pool_cleanup_null); 367#endif 368 369 return new_err; 370} 371 372void 373svn_error_clear(svn_error_t *err) 374{ 375 if (err) 376 { 377#if defined(SVN_DEBUG) 378 while (err->child) 379 err = err->child; 380 apr_pool_cleanup_kill(err->pool, err, err_abort); 381#endif 382 svn_pool_destroy(err->pool); 383 } 384} 385 386svn_boolean_t 387svn_error__is_tracing_link(svn_error_t *err) 388{ 389#ifdef SVN_ERR__TRACING 390 /* ### A strcmp()? Really? I think it's the best we can do unless 391 ### we add a boolean field to svn_error_t that's set only for 392 ### these "placeholder error chain" items. Not such a bad idea, 393 ### really... */ 394 return (err && err->message && !strcmp(err->message, error_tracing_link)); 395#else 396 return FALSE; 397#endif 398} 399 400svn_error_t * 401svn_error_purge_tracing(svn_error_t *err) 402{ 403#ifdef SVN_ERR__TRACING 404 svn_error_t *new_err = NULL, *new_err_leaf = NULL; 405 406 if (! err) 407 return SVN_NO_ERROR; 408 409 do 410 { 411 svn_error_t *tmp_err; 412 413 /* Skip over any trace-only links. */ 414 while (err && svn_error__is_tracing_link(err)) 415 err = err->child; 416 417 /* The link must be a real link in the error chain, otherwise an 418 error chain with trace only links would map into SVN_NO_ERROR. */ 419 if (! err) 420 return svn_error_create( 421 SVN_ERR_ASSERTION_ONLY_TRACING_LINKS, 422 svn_error_compose_create( 423 svn_error__malfunction(TRUE, __FILE__, __LINE__, 424 NULL /* ### say something? */), 425 err), 426 NULL); 427 428 /* Copy the current error except for its child error pointer 429 into the new error. Share any message and source filename 430 strings from the error. */ 431 tmp_err = apr_palloc(err->pool, sizeof(*tmp_err)); 432 *tmp_err = *err; 433 tmp_err->child = NULL; 434 435 /* Add a new link to the new chain (creating the chain if necessary). */ 436 if (! new_err) 437 { 438 new_err = tmp_err; 439 new_err_leaf = tmp_err; 440 } 441 else 442 { 443 new_err_leaf->child = tmp_err; 444 new_err_leaf = tmp_err; 445 } 446 447 /* Advance to the next link in the original chain. */ 448 err = err->child; 449 } while (err); 450 451 return new_err; 452#else /* SVN_ERR__TRACING */ 453 return err; 454#endif /* SVN_ERR__TRACING */ 455} 456 457/* ### The logic around omitting (sic) apr_err= in maintainer mode is tightly 458 ### coupled to the current sole caller.*/ 459static void 460print_error(svn_error_t *err, FILE *stream, const char *prefix) 461{ 462 char errbuf[256]; 463 const char *err_string; 464 svn_error_t *temp_err = NULL; /* ensure initialized even if 465 err->file == NULL */ 466 /* Pretty-print the error */ 467 /* Note: we can also log errors here someday. */ 468 469#ifdef SVN_DEBUG 470 /* Note: err->file is _not_ in UTF-8, because it's expanded from 471 the __FILE__ preprocessor macro. */ 472 const char *file_utf8; 473 474 if (err->file 475 && !(temp_err = svn_utf_cstring_to_utf8(&file_utf8, err->file, 476 err->pool))) 477 svn_error_clear(svn_cmdline_fprintf(stream, err->pool, 478 "%s:%ld", err->file, err->line)); 479 else 480 { 481 svn_error_clear(svn_cmdline_fputs(SVN_FILE_LINE_UNDEFINED, 482 stream, err->pool)); 483 svn_error_clear(temp_err); 484 } 485 486 { 487 const char *symbolic_name; 488 if (svn_error__is_tracing_link(err)) 489 /* Skip it; the error code will be printed by the real link. */ 490 svn_error_clear(svn_cmdline_fprintf(stream, err->pool, ",\n")); 491 else if ((symbolic_name = svn_error_symbolic_name(err->apr_err))) 492 svn_error_clear(svn_cmdline_fprintf(stream, err->pool, 493 ": (apr_err=%s)\n", symbolic_name)); 494 else 495 svn_error_clear(svn_cmdline_fprintf(stream, err->pool, 496 ": (apr_err=%d)\n", err->apr_err)); 497 } 498#endif /* SVN_DEBUG */ 499 500 /* "traced call" */ 501 if (svn_error__is_tracing_link(err)) 502 { 503 /* Skip it. We already printed the file-line coordinates. */ 504 } 505 /* Only print the same APR error string once. */ 506 else if (err->message) 507 { 508 svn_error_clear(svn_cmdline_fprintf(stream, err->pool, 509 "%sE%06d: %s\n", 510 prefix, err->apr_err, err->message)); 511 } 512 else 513 { 514 /* Is this a Subversion-specific error code? */ 515 if ((err->apr_err > APR_OS_START_USEERR) 516 && (err->apr_err <= APR_OS_START_CANONERR)) 517 err_string = svn_strerror(err->apr_err, errbuf, sizeof(errbuf)); 518 /* Otherwise, this must be an APR error code. */ 519 else if ((temp_err = svn_utf_cstring_to_utf8 520 (&err_string, apr_strerror(err->apr_err, errbuf, 521 sizeof(errbuf)), err->pool))) 522 { 523 svn_error_clear(temp_err); 524 err_string = _("Can't recode error string from APR"); 525 } 526 527 svn_error_clear(svn_cmdline_fprintf(stream, err->pool, 528 "%sE%06d: %s\n", 529 prefix, err->apr_err, err_string)); 530 } 531} 532 533void 534svn_handle_error(svn_error_t *err, FILE *stream, svn_boolean_t fatal) 535{ 536 svn_handle_error2(err, stream, fatal, "svn: "); 537} 538 539void 540svn_handle_error2(svn_error_t *err, 541 FILE *stream, 542 svn_boolean_t fatal, 543 const char *prefix) 544{ 545 /* In a long error chain, there may be multiple errors with the same 546 error code and no custom message. We only want to print the 547 default message for that code once; printing it multiple times 548 would add no useful information. The 'empties' array below 549 remembers the codes of empty errors already seen in the chain. 550 551 We could allocate it in err->pool, but there's no telling how 552 long err will live or how many times it will get handled. So we 553 use a subpool. */ 554 apr_pool_t *subpool; 555 apr_array_header_t *empties; 556 svn_error_t *tmp_err; 557 558 /* ### The rest of this file carefully avoids using svn_pool_*(), 559 preferring apr_pool_*() instead. I can't remember why -- it may 560 be an artifact of r843793, or it may be for some deeper reason -- 561 but I'm playing it safe and using apr_pool_*() here too. */ 562 apr_pool_create(&subpool, err->pool); 563 empties = apr_array_make(subpool, 0, sizeof(apr_status_t)); 564 565 tmp_err = err; 566 while (tmp_err) 567 { 568 svn_boolean_t printed_already = FALSE; 569 570 if (! tmp_err->message) 571 { 572 int i; 573 574 for (i = 0; i < empties->nelts; i++) 575 { 576 if (tmp_err->apr_err == APR_ARRAY_IDX(empties, i, apr_status_t) ) 577 { 578 printed_already = TRUE; 579 break; 580 } 581 } 582 } 583 584 if (! printed_already) 585 { 586 print_error(tmp_err, stream, prefix); 587 if (! tmp_err->message) 588 { 589 APR_ARRAY_PUSH(empties, apr_status_t) = tmp_err->apr_err; 590 } 591 } 592 593 tmp_err = tmp_err->child; 594 } 595 596 svn_pool_destroy(subpool); 597 598 fflush(stream); 599 if (fatal) 600 { 601 /* Avoid abort()s in maintainer mode. */ 602 svn_error_clear(err); 603 604 /* We exit(1) here instead of abort()ing so that atexit handlers 605 get called. */ 606 exit(EXIT_FAILURE); 607 } 608} 609 610 611void 612svn_handle_warning(FILE *stream, svn_error_t *err) 613{ 614 svn_handle_warning2(stream, err, "svn: "); 615} 616 617void 618svn_handle_warning2(FILE *stream, svn_error_t *err, const char *prefix) 619{ 620 char buf[256]; 621 622 svn_error_clear(svn_cmdline_fprintf 623 (stream, err->pool, 624 _("%swarning: W%06d: %s\n"), 625 prefix, err->apr_err, 626 svn_err_best_message(err, buf, sizeof(buf)))); 627 fflush(stream); 628} 629 630const char * 631svn_err_best_message(svn_error_t *err, char *buf, apr_size_t bufsize) 632{ 633 /* Skip over any trace records. */ 634 while (svn_error__is_tracing_link(err)) 635 err = err->child; 636 if (err->message) 637 return err->message; 638 else 639 return svn_strerror(err->apr_err, buf, bufsize); 640} 641 642 643/* svn_strerror() and helpers */ 644 645/* Duplicate of the same typedef in tests/libsvn_subr/error-code-test.c */ 646typedef struct err_defn { 647 svn_errno_t errcode; /* 160004 */ 648 const char *errname; /* SVN_ERR_FS_CORRUPT */ 649 const char *errdesc; /* default message */ 650} err_defn; 651 652/* To understand what is going on here, read svn_error_codes.h. */ 653#define SVN_ERROR_BUILD_ARRAY 654#include "svn_error_codes.h" 655 656char * 657svn_strerror(apr_status_t statcode, char *buf, apr_size_t bufsize) 658{ 659 const err_defn *defn; 660 661 for (defn = error_table; defn->errdesc != NULL; ++defn) 662 if (defn->errcode == (svn_errno_t)statcode) 663 { 664 apr_cpystrn(buf, _(defn->errdesc), bufsize); 665 return buf; 666 } 667 668 return apr_strerror(statcode, buf, bufsize); 669} 670 671const char * 672svn_error_symbolic_name(apr_status_t statcode) 673{ 674 const err_defn *defn; 675 676 for (defn = error_table; defn->errdesc != NULL; ++defn) 677 if (defn->errcode == (svn_errno_t)statcode) 678 return defn->errname; 679 680 /* "No error" is not in error_table. */ 681 if (statcode == SVN_NO_ERROR) 682 return "SVN_NO_ERROR"; 683 684 return NULL; 685} 686 687 688 689/* Malfunctions. */ 690 691svn_error_t * 692svn_error_raise_on_malfunction(svn_boolean_t can_return, 693 const char *file, int line, 694 const char *expr) 695{ 696 if (!can_return) 697 abort(); /* Nothing else we can do as a library */ 698 699 /* The filename and line number of the error source needs to be set 700 here because svn_error_createf() is not the macro defined in 701 svn_error.h but the real function. */ 702 svn_error__locate(file, line); 703 704 if (expr) 705 return svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL, 706 _("In file '%s' line %d: assertion failed (%s)"), 707 file, line, expr); 708 else 709 return svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL, 710 _("In file '%s' line %d: internal malfunction"), 711 file, line); 712} 713 714svn_error_t * 715svn_error_abort_on_malfunction(svn_boolean_t can_return, 716 const char *file, int line, 717 const char *expr) 718{ 719 svn_error_t *err = svn_error_raise_on_malfunction(TRUE, file, line, expr); 720 721 svn_handle_error2(err, stderr, FALSE, "svn: "); 722 abort(); 723 return err; /* Not reached. */ 724} 725 726/* The current handler for reporting malfunctions, and its default setting. */ 727static svn_error_malfunction_handler_t malfunction_handler 728 = svn_error_abort_on_malfunction; 729 730svn_error_malfunction_handler_t 731svn_error_set_malfunction_handler(svn_error_malfunction_handler_t func) 732{ 733 svn_error_malfunction_handler_t old_malfunction_handler 734 = malfunction_handler; 735 736 malfunction_handler = func; 737 return old_malfunction_handler; 738} 739 740/* Note: Although this is a "__" function, it is in the public ABI, so 741 * we can never remove it or change its signature. */ 742svn_error_t * 743svn_error__malfunction(svn_boolean_t can_return, 744 const char *file, int line, 745 const char *expr) 746{ 747 return malfunction_handler(can_return, file, line, expr); 748} 749 750 751/* Misc. */ 752 753svn_error_t * 754svn_error__wrap_zlib(int zerr, const char *function, const char *message) 755{ 756 apr_status_t status; 757 const char *zmsg; 758 759 if (zerr == Z_OK) 760 return SVN_NO_ERROR; 761 762 switch (zerr) 763 { 764 case Z_STREAM_ERROR: 765 status = SVN_ERR_STREAM_MALFORMED_DATA; 766 zmsg = _("stream error"); 767 break; 768 769 case Z_MEM_ERROR: 770 status = APR_ENOMEM; 771 zmsg = _("out of memory"); 772 break; 773 774 case Z_BUF_ERROR: 775 status = APR_ENOMEM; 776 zmsg = _("buffer error"); 777 break; 778 779 case Z_VERSION_ERROR: 780 status = SVN_ERR_STREAM_UNRECOGNIZED_DATA; 781 zmsg = _("version error"); 782 break; 783 784 case Z_DATA_ERROR: 785 status = SVN_ERR_STREAM_MALFORMED_DATA; 786 zmsg = _("corrupt data"); 787 break; 788 789 default: 790 status = SVN_ERR_STREAM_UNRECOGNIZED_DATA; 791 zmsg = _("unknown error"); 792 break; 793 } 794 795 if (message != NULL) 796 return svn_error_createf(status, NULL, "zlib (%s): %s: %s", function, 797 zmsg, message); 798 else 799 return svn_error_createf(status, NULL, "zlib (%s): %s", function, zmsg); 800} 801