1 /* 2 * Copyright (c) 2000-2002 Sendmail, Inc. and its suppliers. 3 * All rights reserved. 4 * 5 * By using this file, you agree to the terms and conditions set 6 * forth in the LICENSE file which can be found at the top level of 7 * the sendmail distribution. 8 * 9 */ 10 11 #include <sm/gen.h> 12 SM_RCSID("@(#)$Id: exc.c,v 1.48 2003/12/05 22:45:24 ca Exp $") 13 14 /* 15 ** exception handling 16 ** For documentation, see exc.html 17 */ 18 19 #include <ctype.h> 20 #include <string.h> 21 22 #include <sm/errstring.h> 23 #include <sm/exc.h> 24 #include <sm/heap.h> 25 #include <sm/string.h> 26 #include <sm/varargs.h> 27 #include <sm/io.h> 28 29 const char SmExcMagic[] = "sm_exc"; 30 const char SmExcTypeMagic[] = "sm_exc_type"; 31 32 /* 33 ** SM_ETYPE_PRINTF -- printf for exception types. 34 ** 35 ** Parameters: 36 ** exc -- exception. 37 ** stream -- file for output. 38 ** 39 ** Returns: 40 ** none. 41 */ 42 43 /* 44 ** A simple formatted print function that can be used as the print function 45 ** by most exception types. It prints the printcontext string, interpreting 46 ** occurrences of %0 through %9 as references to the argument vector. 47 ** If exception argument 3 is an int or long, then %3 will print the 48 ** argument in decimal, and %o3 or %x3 will print it in octal or hex. 49 */ 50 51 void 52 sm_etype_printf(exc, stream) 53 SM_EXC_T *exc; 54 SM_FILE_T *stream; 55 { 56 size_t n = strlen(exc->exc_type->etype_argformat); 57 const char *p, *s; 58 char format; 59 60 for (p = exc->exc_type->etype_printcontext; *p != '\0'; ++p) 61 { 62 if (*p != '%') 63 { 64 (void) sm_io_putc(stream, SM_TIME_DEFAULT, *p); 65 continue; 66 } 67 ++p; 68 if (*p == '\0') 69 { 70 (void) sm_io_putc(stream, SM_TIME_DEFAULT, '%'); 71 break; 72 } 73 if (*p == '%') 74 { 75 (void) sm_io_putc(stream, SM_TIME_DEFAULT, '%'); 76 continue; 77 } 78 format = '\0'; 79 if (isalpha(*p)) 80 { 81 format = *p++; 82 if (*p == '\0') 83 { 84 (void) sm_io_putc(stream, SM_TIME_DEFAULT, '%'); 85 (void) sm_io_putc(stream, SM_TIME_DEFAULT, 86 format); 87 break; 88 } 89 } 90 if (isdigit(*p)) 91 { 92 size_t i = *p - '0'; 93 if (i < n) 94 { 95 switch (exc->exc_type->etype_argformat[i]) 96 { 97 case 's': 98 case 'r': 99 s = exc->exc_argv[i].v_str; 100 if (s == NULL) 101 s = "(null)"; 102 sm_io_fputs(stream, SM_TIME_DEFAULT, s); 103 continue; 104 case 'i': 105 sm_io_fprintf(stream, 106 SM_TIME_DEFAULT, 107 format == 'o' ? "%o" 108 : format == 'x' ? "%x" 109 : "%d", 110 exc->exc_argv[i].v_int); 111 continue; 112 case 'l': 113 sm_io_fprintf(stream, 114 SM_TIME_DEFAULT, 115 format == 'o' ? "%lo" 116 : format == 'x' ? "%lx" 117 : "%ld", 118 exc->exc_argv[i].v_long); 119 continue; 120 case 'e': 121 sm_exc_write(exc->exc_argv[i].v_exc, 122 stream); 123 continue; 124 } 125 } 126 } 127 (void) sm_io_putc(stream, SM_TIME_DEFAULT, '%'); 128 if (format) 129 (void) sm_io_putc(stream, SM_TIME_DEFAULT, format); 130 (void) sm_io_putc(stream, SM_TIME_DEFAULT, *p); 131 } 132 } 133 134 /* 135 ** Standard exception types. 136 */ 137 138 /* 139 ** SM_ETYPE_OS_PRINT -- Print OS related exception. 140 ** 141 ** Parameters: 142 ** exc -- exception. 143 ** stream -- file for output. 144 ** 145 ** Returns: 146 ** none. 147 */ 148 149 static void 150 sm_etype_os_print __P(( 151 SM_EXC_T *exc, 152 SM_FILE_T *stream)); 153 154 static void 155 sm_etype_os_print(exc, stream) 156 SM_EXC_T *exc; 157 SM_FILE_T *stream; 158 { 159 int err = exc->exc_argv[0].v_int; 160 char *syscall = exc->exc_argv[1].v_str; 161 char *sysargs = exc->exc_argv[2].v_str; 162 163 if (sysargs) 164 sm_io_fprintf(stream, SM_TIME_DEFAULT, "%s: %s failed: %s", 165 sysargs, syscall, sm_errstring(err)); 166 else 167 sm_io_fprintf(stream, SM_TIME_DEFAULT, "%s failed: %s", syscall, 168 sm_errstring(err)); 169 } 170 171 /* 172 ** SmEtypeOs represents the failure of a Unix system call. 173 ** The three arguments are: 174 ** int errno (eg, ENOENT) 175 ** char *syscall (eg, "open") 176 ** char *sysargs (eg, NULL or "/etc/mail/sendmail.cf") 177 */ 178 179 const SM_EXC_TYPE_T SmEtypeOs = 180 { 181 SmExcTypeMagic, 182 "E:sm.os", 183 "isr", 184 sm_etype_os_print, 185 NULL, 186 }; 187 188 /* 189 ** SmEtypeErr is a completely generic error which should only be 190 ** used in applications and test programs. Libraries should use 191 ** more specific exception codes. 192 */ 193 194 const SM_EXC_TYPE_T SmEtypeErr = 195 { 196 SmExcTypeMagic, 197 "E:sm.err", 198 "r", 199 sm_etype_printf, 200 "%0", 201 }; 202 203 /* 204 ** SM_EXC_VNEW_X -- Construct a new exception object. 205 ** 206 ** Parameters: 207 ** etype -- type of exception. 208 ** ap -- varargs. 209 ** 210 ** Returns: 211 ** pointer to exception object. 212 */ 213 214 /* 215 ** This is an auxiliary function called by sm_exc_new_x and sm_exc_raisenew_x. 216 ** 217 ** If an exception is raised, then to avoid a storage leak, we must: 218 ** (a) Free all storage we have allocated. 219 ** (b) Free all exception arguments in the varargs list. 220 ** Getting this right is tricky. 221 ** 222 ** To see why (b) is required, consider the code fragment 223 ** SM_EXCEPT(exc, "*") 224 ** sm_exc_raisenew_x(&MyEtype, exc); 225 ** SM_END_TRY 226 ** In the normal case, sm_exc_raisenew_x will allocate and raise a new 227 ** exception E that owns exc. When E is eventually freed, exc is also freed. 228 ** In the exceptional case, sm_exc_raisenew_x must free exc before raising 229 ** an out-of-memory exception so that exc is not leaked. 230 */ 231 232 SM_EXC_T * 233 sm_exc_vnew_x(etype, ap) 234 const SM_EXC_TYPE_T *etype; 235 va_list SM_NONVOLATILE ap; 236 { 237 /* 238 ** All variables that are modified in the SM_TRY clause and 239 ** referenced in the SM_EXCEPT clause must be declared volatile. 240 */ 241 242 /* NOTE: Type of si, i, and argc *must* match */ 243 SM_EXC_T * volatile exc = NULL; 244 int volatile si = 0; 245 SM_VAL_T * volatile argv = NULL; 246 int i, argc; 247 248 SM_REQUIRE_ISA(etype, SmExcTypeMagic); 249 argc = strlen(etype->etype_argformat); 250 SM_TRY 251 { 252 /* 253 ** Step 1. Allocate the exception structure. 254 ** On failure, scan the varargs list and free all 255 ** exception arguments. 256 */ 257 258 exc = sm_malloc_x(sizeof(SM_EXC_T)); 259 exc->sm_magic = SmExcMagic; 260 exc->exc_refcount = 1; 261 exc->exc_type = etype; 262 exc->exc_argv = NULL; 263 264 /* 265 ** Step 2. Allocate the argument vector. 266 ** On failure, free exc, scan the varargs list and free all 267 ** exception arguments. On success, scan the varargs list, 268 ** and copy the arguments into argv. 269 */ 270 271 argv = sm_malloc_x(argc * sizeof(SM_VAL_T)); 272 exc->exc_argv = argv; 273 for (i = 0; i < argc; ++i) 274 { 275 switch (etype->etype_argformat[i]) 276 { 277 case 'i': 278 argv[i].v_int = SM_VA_ARG(ap, int); 279 break; 280 case 'l': 281 argv[i].v_long = SM_VA_ARG(ap, long); 282 break; 283 case 'e': 284 argv[i].v_exc = SM_VA_ARG(ap, SM_EXC_T*); 285 break; 286 case 's': 287 argv[i].v_str = SM_VA_ARG(ap, char*); 288 break; 289 case 'r': 290 SM_REQUIRE(etype->etype_argformat[i+1] == '\0'); 291 argv[i].v_str = SM_VA_ARG(ap, char*); 292 break; 293 default: 294 sm_abort("sm_exc_vnew_x: bad argformat '%c'", 295 etype->etype_argformat[i]); 296 } 297 } 298 299 /* 300 ** Step 3. Scan argv, and allocate space for all 301 ** string arguments. si is the number of elements 302 ** of argv that have been processed so far. 303 ** On failure, free exc, argv, all the exception arguments 304 ** and all of the strings that have been copied. 305 */ 306 307 for (si = 0; si < argc; ++si) 308 { 309 switch (etype->etype_argformat[si]) 310 { 311 case 's': 312 { 313 char *str = argv[si].v_str; 314 if (str != NULL) 315 argv[si].v_str = sm_strdup_x(str); 316 } 317 break; 318 case 'r': 319 { 320 char *fmt = argv[si].v_str; 321 if (fmt != NULL) 322 argv[si].v_str = sm_vstringf_x(fmt, ap); 323 } 324 break; 325 } 326 } 327 } 328 SM_EXCEPT(e, "*") 329 { 330 if (exc == NULL || argv == NULL) 331 { 332 /* 333 ** Failure in step 1 or step 2. 334 ** Scan ap and free all exception arguments. 335 */ 336 337 for (i = 0; i < argc; ++i) 338 { 339 switch (etype->etype_argformat[i]) 340 { 341 case 'i': 342 (void) SM_VA_ARG(ap, int); 343 break; 344 case 'l': 345 (void) SM_VA_ARG(ap, long); 346 break; 347 case 'e': 348 sm_exc_free(SM_VA_ARG(ap, SM_EXC_T*)); 349 break; 350 case 's': 351 case 'r': 352 (void) SM_VA_ARG(ap, char*); 353 break; 354 } 355 } 356 } 357 else 358 { 359 /* 360 ** Failure in step 3. Scan argv and free 361 ** all exception arguments and all string 362 ** arguments that have been duplicated. 363 ** Then free argv. 364 */ 365 366 for (i = 0; i < argc; ++i) 367 { 368 switch (etype->etype_argformat[i]) 369 { 370 case 'e': 371 sm_exc_free(argv[i].v_exc); 372 break; 373 case 's': 374 case 'r': 375 if (i < si) 376 sm_free(argv[i].v_str); 377 break; 378 } 379 } 380 sm_free(argv); 381 } 382 sm_free(exc); 383 sm_exc_raise_x(e); 384 } 385 SM_END_TRY 386 387 return exc; 388 } 389 390 /* 391 ** SM_EXC_NEW_X -- Construct a new exception object. 392 ** 393 ** Parameters: 394 ** etype -- type of exception. 395 ** ... -- varargs. 396 ** 397 ** Returns: 398 ** pointer to exception object. 399 */ 400 401 SM_EXC_T * 402 #if SM_VA_STD 403 sm_exc_new_x( 404 const SM_EXC_TYPE_T *etype, 405 ...) 406 #else /* SM_VA_STD */ 407 sm_exc_new_x(etype, va_alist) 408 const SM_EXC_TYPE_T *etype; 409 va_dcl 410 #endif /* SM_VA_STD */ 411 { 412 SM_EXC_T *exc; 413 SM_VA_LOCAL_DECL 414 415 SM_VA_START(ap, etype); 416 exc = sm_exc_vnew_x(etype, ap); 417 SM_VA_END(ap); 418 return exc; 419 } 420 421 /* 422 ** SM_ADDREF -- Add a reference to an exception object. 423 ** 424 ** Parameters: 425 ** exc -- exception object. 426 ** 427 ** Returns: 428 ** exc itself. 429 */ 430 431 SM_EXC_T * 432 sm_addref(exc) 433 SM_EXC_T *exc; 434 { 435 SM_REQUIRE_ISA(exc, SmExcMagic); 436 if (exc->exc_refcount != 0) 437 ++exc->exc_refcount; 438 return exc; 439 } 440 441 /* 442 ** SM_EXC_FREE -- Destroy a reference to an exception object. 443 ** 444 ** Parameters: 445 ** exc -- exception object. 446 ** 447 ** Returns: 448 ** none. 449 */ 450 451 void 452 sm_exc_free(exc) 453 SM_EXC_T *exc; 454 { 455 if (exc == NULL) 456 return; 457 SM_REQUIRE(exc->sm_magic == SmExcMagic); 458 if (exc->exc_refcount == 0) 459 return; 460 if (--exc->exc_refcount == 0) 461 { 462 int i, c; 463 464 for (i = 0; (c = exc->exc_type->etype_argformat[i]) != '\0'; 465 ++i) 466 { 467 switch (c) 468 { 469 case 's': 470 case 'r': 471 sm_free(exc->exc_argv[i].v_str); 472 break; 473 case 'e': 474 sm_exc_free(exc->exc_argv[i].v_exc); 475 break; 476 } 477 } 478 exc->sm_magic = NULL; 479 sm_free(exc->exc_argv); 480 sm_free(exc); 481 } 482 } 483 484 /* 485 ** SM_EXC_MATCH -- Match exception category against a glob pattern. 486 ** 487 ** Parameters: 488 ** exc -- exception. 489 ** pattern -- glob pattern. 490 ** 491 ** Returns: 492 ** true iff match. 493 */ 494 495 bool 496 sm_exc_match(exc, pattern) 497 SM_EXC_T *exc; 498 const char *pattern; 499 { 500 if (exc == NULL) 501 return false; 502 SM_REQUIRE(exc->sm_magic == SmExcMagic); 503 return sm_match(exc->exc_type->etype_category, pattern); 504 } 505 506 /* 507 ** SM_EXC_WRITE -- Write exception message to a stream (wo trailing newline). 508 ** 509 ** Parameters: 510 ** exc -- exception. 511 ** stream -- file for output. 512 ** 513 ** Returns: 514 ** none. 515 */ 516 517 void 518 sm_exc_write(exc, stream) 519 SM_EXC_T *exc; 520 SM_FILE_T *stream; 521 { 522 SM_REQUIRE_ISA(exc, SmExcMagic); 523 exc->exc_type->etype_print(exc, stream); 524 } 525 526 /* 527 ** SM_EXC_PRINT -- Print exception message to a stream (with trailing newline). 528 ** 529 ** Parameters: 530 ** exc -- exception. 531 ** stream -- file for output. 532 ** 533 ** Returns: 534 ** none. 535 */ 536 537 void 538 sm_exc_print(exc, stream) 539 SM_EXC_T *exc; 540 SM_FILE_T *stream; 541 { 542 SM_REQUIRE_ISA(exc, SmExcMagic); 543 exc->exc_type->etype_print(exc, stream); 544 (void) sm_io_putc(stream, SM_TIME_DEFAULT, '\n'); 545 } 546 547 SM_EXC_HANDLER_T *SmExcHandler = NULL; 548 static SM_EXC_DEFAULT_HANDLER_T SmExcDefaultHandler = NULL; 549 550 /* 551 ** SM_EXC_NEWTHREAD -- Initialize exception handling for new process/thread. 552 ** 553 ** Parameters: 554 ** h -- default exception handler. 555 ** 556 ** Returns: 557 ** none. 558 */ 559 560 /* 561 ** Initialize a new process or a new thread by clearing the 562 ** exception handler stack and optionally setting a default 563 ** exception handler function. Call this at the beginning of main, 564 ** or in a new process after calling fork, or in a new thread. 565 ** 566 ** This function is a luxury, not a necessity. 567 ** If h != NULL then you can get the same effect by 568 ** wrapping the body of main, or the body of a forked child 569 ** or a new thread in SM_TRY ... SM_EXCEPT(e,"*") h(e); SM_END_TRY. 570 */ 571 572 void 573 sm_exc_newthread(h) 574 SM_EXC_DEFAULT_HANDLER_T h; 575 { 576 SmExcHandler = NULL; 577 SmExcDefaultHandler = h; 578 } 579 580 /* 581 ** SM_EXC_RAISE_X -- Raise an exception. 582 ** 583 ** Parameters: 584 ** exc -- exception. 585 ** 586 ** Returns: 587 ** doesn't. 588 */ 589 590 void SM_DEAD_D 591 sm_exc_raise_x(exc) 592 SM_EXC_T *exc; 593 { 594 SM_REQUIRE_ISA(exc, SmExcMagic); 595 596 if (SmExcHandler == NULL) 597 { 598 if (SmExcDefaultHandler != NULL) 599 { 600 SM_EXC_DEFAULT_HANDLER_T h; 601 602 /* 603 ** If defined, the default handler is expected 604 ** to terminate the current thread of execution 605 ** using exit() or pthread_exit(). 606 ** If it instead returns normally, then we fall 607 ** through to the default case below. If it 608 ** raises an exception, then sm_exc_raise_x is 609 ** re-entered and, because we set SmExcDefaultHandler 610 ** to NULL before invoking h, we will again 611 ** end up in the default case below. 612 */ 613 614 h = SmExcDefaultHandler; 615 SmExcDefaultHandler = NULL; 616 (*h)(exc); 617 } 618 619 /* 620 ** No exception handler, so print the error and exit. 621 ** To override this behaviour on a program wide basis, 622 ** call sm_exc_newthread or put an exception handler in main(). 623 ** 624 ** XXX TODO: map the exception category to an exit code 625 ** XXX from <sysexits.h>. 626 */ 627 628 sm_exc_print(exc, smioerr); 629 exit(255); 630 } 631 632 if (SmExcHandler->eh_value == NULL) 633 SmExcHandler->eh_value = exc; 634 else 635 sm_exc_free(exc); 636 637 sm_longjmp_nosig(SmExcHandler->eh_context, 1); 638 } 639 640 /* 641 ** SM_EXC_RAISENEW_X -- shorthand for sm_exc_raise_x(sm_exc_new_x(...)) 642 ** 643 ** Parameters: 644 ** etype -- type of exception. 645 ** ap -- varargs. 646 ** 647 ** Returns: 648 ** none. 649 */ 650 651 void SM_DEAD_D 652 #if SM_VA_STD 653 sm_exc_raisenew_x( 654 const SM_EXC_TYPE_T *etype, 655 ...) 656 #else 657 sm_exc_raisenew_x(etype, va_alist) 658 const SM_EXC_TYPE_T *etype; 659 va_dcl 660 #endif 661 { 662 SM_EXC_T *exc; 663 SM_VA_LOCAL_DECL 664 665 SM_VA_START(ap, etype); 666 exc = sm_exc_vnew_x(etype, ap); 667 SM_VA_END(ap); 668 sm_exc_raise_x(exc); 669 } 670