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.49 2006/12/19 19:28:09 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
sm_etype_printf(exc,stream)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
sm_etype_os_print(exc,stream)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 static SM_EXC_T *sm_exc_vnew_x __P((const SM_EXC_TYPE_T *, va_list SM_NONVOLATILE));
233
234 static SM_EXC_T *
sm_exc_vnew_x(etype,ap)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
sm_exc_new_x(const SM_EXC_TYPE_T * etype,...)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_EXC_FREE -- Destroy a reference to an exception object.
425 **
426 ** Parameters:
427 ** exc -- exception object.
428 **
429 ** Returns:
430 ** none.
431 */
432
433 void
sm_exc_free(exc)434 sm_exc_free(exc)
435 SM_EXC_T *exc;
436 {
437 if (exc == NULL)
438 return;
439 SM_REQUIRE(exc->sm_magic == SmExcMagic);
440 if (exc->exc_refcount == 0)
441 return;
442 if (--exc->exc_refcount == 0)
443 {
444 int i, c;
445
446 for (i = 0; (c = exc->exc_type->etype_argformat[i]) != '\0';
447 ++i)
448 {
449 switch (c)
450 {
451 case 's':
452 case 'r':
453 sm_free(exc->exc_argv[i].v_str);
454 break;
455 case 'e':
456 sm_exc_free(exc->exc_argv[i].v_exc);
457 break;
458 }
459 }
460 exc->sm_magic = NULL;
461 sm_free(exc->exc_argv);
462 sm_free(exc);
463 }
464 }
465
466 /*
467 ** SM_EXC_MATCH -- Match exception category against a glob pattern.
468 **
469 ** Parameters:
470 ** exc -- exception.
471 ** pattern -- glob pattern.
472 **
473 ** Returns:
474 ** true iff match.
475 */
476
477 bool
sm_exc_match(exc,pattern)478 sm_exc_match(exc, pattern)
479 SM_EXC_T *exc;
480 const char *pattern;
481 {
482 if (exc == NULL)
483 return false;
484 SM_REQUIRE(exc->sm_magic == SmExcMagic);
485 return sm_match(exc->exc_type->etype_category, pattern);
486 }
487
488 /*
489 ** SM_EXC_WRITE -- Write exception message to a stream (wo trailing newline).
490 **
491 ** Parameters:
492 ** exc -- exception.
493 ** stream -- file for output.
494 **
495 ** Returns:
496 ** none.
497 */
498
499 void
sm_exc_write(exc,stream)500 sm_exc_write(exc, stream)
501 SM_EXC_T *exc;
502 SM_FILE_T *stream;
503 {
504 SM_REQUIRE_ISA(exc, SmExcMagic);
505 exc->exc_type->etype_print(exc, stream);
506 }
507
508 /*
509 ** SM_EXC_PRINT -- Print exception message to a stream (with trailing newline).
510 **
511 ** Parameters:
512 ** exc -- exception.
513 ** stream -- file for output.
514 **
515 ** Returns:
516 ** none.
517 */
518
519 void
sm_exc_print(exc,stream)520 sm_exc_print(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 (void) sm_io_putc(stream, SM_TIME_DEFAULT, '\n');
527 }
528
529 SM_EXC_HANDLER_T *SmExcHandler = NULL;
530 static SM_EXC_DEFAULT_HANDLER_T SmExcDefaultHandler = NULL;
531
532 /*
533 ** SM_EXC_NEWTHREAD -- Initialize exception handling for new process/thread.
534 **
535 ** Parameters:
536 ** h -- default exception handler.
537 **
538 ** Returns:
539 ** none.
540 */
541
542 /*
543 ** Initialize a new process or a new thread by clearing the
544 ** exception handler stack and optionally setting a default
545 ** exception handler function. Call this at the beginning of main,
546 ** or in a new process after calling fork, or in a new thread.
547 **
548 ** This function is a luxury, not a necessity.
549 ** If h != NULL then you can get the same effect by
550 ** wrapping the body of main, or the body of a forked child
551 ** or a new thread in SM_TRY ... SM_EXCEPT(e,"*") h(e); SM_END_TRY.
552 */
553
554 void
sm_exc_newthread(h)555 sm_exc_newthread(h)
556 SM_EXC_DEFAULT_HANDLER_T h;
557 {
558 SmExcHandler = NULL;
559 SmExcDefaultHandler = h;
560 }
561
562 /*
563 ** SM_EXC_RAISE_X -- Raise an exception.
564 **
565 ** Parameters:
566 ** exc -- exception.
567 **
568 ** Returns:
569 ** doesn't.
570 */
571
572 void SM_DEAD_D
sm_exc_raise_x(exc)573 sm_exc_raise_x(exc)
574 SM_EXC_T *exc;
575 {
576 SM_REQUIRE_ISA(exc, SmExcMagic);
577
578 if (SmExcHandler == NULL)
579 {
580 if (SmExcDefaultHandler != NULL)
581 {
582 SM_EXC_DEFAULT_HANDLER_T h;
583
584 /*
585 ** If defined, the default handler is expected
586 ** to terminate the current thread of execution
587 ** using exit() or pthread_exit().
588 ** If it instead returns normally, then we fall
589 ** through to the default case below. If it
590 ** raises an exception, then sm_exc_raise_x is
591 ** re-entered and, because we set SmExcDefaultHandler
592 ** to NULL before invoking h, we will again
593 ** end up in the default case below.
594 */
595
596 h = SmExcDefaultHandler;
597 SmExcDefaultHandler = NULL;
598 (*h)(exc);
599 }
600
601 /*
602 ** No exception handler, so print the error and exit.
603 ** To override this behaviour on a program wide basis,
604 ** call sm_exc_newthread or put an exception handler in main().
605 **
606 ** XXX TODO: map the exception category to an exit code
607 ** XXX from <sysexits.h>.
608 */
609
610 sm_exc_print(exc, smioerr);
611 exit(255);
612 }
613
614 if (SmExcHandler->eh_value == NULL)
615 SmExcHandler->eh_value = exc;
616 else
617 sm_exc_free(exc);
618
619 sm_longjmp_nosig(SmExcHandler->eh_context, 1);
620 }
621
622 /*
623 ** SM_EXC_RAISENEW_X -- shorthand for sm_exc_raise_x(sm_exc_new_x(...))
624 **
625 ** Parameters:
626 ** etype -- type of exception.
627 ** ap -- varargs.
628 **
629 ** Returns:
630 ** none.
631 */
632
633 void SM_DEAD_D
634 #if SM_VA_STD
sm_exc_raisenew_x(const SM_EXC_TYPE_T * etype,...)635 sm_exc_raisenew_x(
636 const SM_EXC_TYPE_T *etype,
637 ...)
638 #else
639 sm_exc_raisenew_x(etype, va_alist)
640 const SM_EXC_TYPE_T *etype;
641 va_dcl
642 #endif
643 {
644 SM_EXC_T *exc;
645 SM_VA_LOCAL_DECL
646
647 SM_VA_START(ap, etype);
648 exc = sm_exc_vnew_x(etype, ap);
649 SM_VA_END(ap);
650 sm_exc_raise_x(exc);
651 }
652