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