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.49 2006/12/19 19:28:09 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
sm_etype_printf(exc,stream)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
sm_etype_os_print(exc,stream)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 static SM_EXC_T *sm_exc_vnew_x __P((const SM_EXC_TYPE_T *, va_list SM_NONVOLATILE));
235
236 static SM_EXC_T *
sm_exc_vnew_x(etype,ap)237 sm_exc_vnew_x(etype, ap)
238 const SM_EXC_TYPE_T *etype;
239 va_list SM_NONVOLATILE ap;
240 {
241 /*
242 ** All variables that are modified in the SM_TRY clause and
243 ** referenced in the SM_EXCEPT clause must be declared volatile.
244 */
245
246 /* NOTE: Type of si, i, and argc *must* match */
247 SM_EXC_T * volatile exc = NULL;
248 int volatile si = 0;
249 SM_VAL_T * volatile argv = NULL;
250 int i, argc;
251
252 SM_REQUIRE_ISA(etype, SmExcTypeMagic);
253 argc = strlen(etype->etype_argformat);
254 SM_TRY
255 {
256 /*
257 ** Step 1. Allocate the exception structure.
258 ** On failure, scan the varargs list and free all
259 ** exception arguments.
260 */
261
262 exc = sm_malloc_x(sizeof(SM_EXC_T));
263 exc->sm_magic = SmExcMagic;
264 exc->exc_refcount = 1;
265 exc->exc_type = etype;
266 exc->exc_argv = NULL;
267
268 /*
269 ** Step 2. Allocate the argument vector.
270 ** On failure, free exc, scan the varargs list and free all
271 ** exception arguments. On success, scan the varargs list,
272 ** and copy the arguments into argv.
273 */
274
275 argv = sm_malloc_x(argc * sizeof(SM_VAL_T));
276 exc->exc_argv = argv;
277 for (i = 0; i < argc; ++i)
278 {
279 switch (etype->etype_argformat[i])
280 {
281 case 'i':
282 argv[i].v_int = SM_VA_ARG(ap, int);
283 break;
284 case 'l':
285 argv[i].v_long = SM_VA_ARG(ap, long);
286 break;
287 case 'e':
288 argv[i].v_exc = SM_VA_ARG(ap, SM_EXC_T*);
289 break;
290 case 's':
291 argv[i].v_str = SM_VA_ARG(ap, char*);
292 break;
293 case 'r':
294 SM_REQUIRE(etype->etype_argformat[i+1] == '\0');
295 argv[i].v_str = SM_VA_ARG(ap, char*);
296 break;
297 default:
298 sm_abort("sm_exc_vnew_x: bad argformat '%c'",
299 etype->etype_argformat[i]);
300 }
301 }
302
303 /*
304 ** Step 3. Scan argv, and allocate space for all
305 ** string arguments. si is the number of elements
306 ** of argv that have been processed so far.
307 ** On failure, free exc, argv, all the exception arguments
308 ** and all of the strings that have been copied.
309 */
310
311 for (si = 0; si < argc; ++si)
312 {
313 switch (etype->etype_argformat[si])
314 {
315 case 's':
316 {
317 char *str = argv[si].v_str;
318 if (str != NULL)
319 argv[si].v_str = sm_strdup_x(str);
320 }
321 break;
322 case 'r':
323 {
324 char *fmt = argv[si].v_str;
325 if (fmt != NULL)
326 argv[si].v_str = sm_vstringf_x(fmt, ap);
327 }
328 break;
329 }
330 }
331 }
332 SM_EXCEPT(e, "*")
333 {
334 if (exc == NULL || argv == NULL)
335 {
336 /*
337 ** Failure in step 1 or step 2.
338 ** Scan ap and free all exception arguments.
339 */
340
341 for (i = 0; i < argc; ++i)
342 {
343 switch (etype->etype_argformat[i])
344 {
345 case 'i':
346 (void) SM_VA_ARG(ap, int);
347 break;
348 case 'l':
349 (void) SM_VA_ARG(ap, long);
350 break;
351 case 'e':
352 sm_exc_free(SM_VA_ARG(ap, SM_EXC_T*));
353 break;
354 case 's':
355 case 'r':
356 (void) SM_VA_ARG(ap, char*);
357 break;
358 }
359 }
360 }
361 else
362 {
363 /*
364 ** Failure in step 3. Scan argv and free
365 ** all exception arguments and all string
366 ** arguments that have been duplicated.
367 ** Then free argv.
368 */
369
370 for (i = 0; i < argc; ++i)
371 {
372 switch (etype->etype_argformat[i])
373 {
374 case 'e':
375 sm_exc_free(argv[i].v_exc);
376 break;
377 case 's':
378 case 'r':
379 if (i < si)
380 sm_free(argv[i].v_str);
381 break;
382 }
383 }
384 sm_free(argv);
385 }
386 sm_free(exc);
387 sm_exc_raise_x(e);
388 }
389 SM_END_TRY
390
391 return exc;
392 }
393
394 /*
395 ** SM_EXC_NEW_X -- Construct a new exception object.
396 **
397 ** Parameters:
398 ** etype -- type of exception.
399 ** ... -- varargs.
400 **
401 ** Returns:
402 ** pointer to exception object.
403 */
404
405 SM_EXC_T *
406 #if SM_VA_STD
sm_exc_new_x(const SM_EXC_TYPE_T * etype,...)407 sm_exc_new_x(
408 const SM_EXC_TYPE_T *etype,
409 ...)
410 #else /* SM_VA_STD */
411 sm_exc_new_x(etype, va_alist)
412 const SM_EXC_TYPE_T *etype;
413 va_dcl
414 #endif /* SM_VA_STD */
415 {
416 SM_EXC_T *exc;
417 SM_VA_LOCAL_DECL
418
419 SM_VA_START(ap, etype);
420 exc = sm_exc_vnew_x(etype, ap);
421 SM_VA_END(ap);
422 return exc;
423 }
424
425 /*
426 ** SM_EXC_FREE -- Destroy a reference to an exception object.
427 **
428 ** Parameters:
429 ** exc -- exception object.
430 **
431 ** Returns:
432 ** none.
433 */
434
435 void
sm_exc_free(exc)436 sm_exc_free(exc)
437 SM_EXC_T *exc;
438 {
439 if (exc == NULL)
440 return;
441 SM_REQUIRE(exc->sm_magic == SmExcMagic);
442 if (exc->exc_refcount == 0)
443 return;
444 if (--exc->exc_refcount == 0)
445 {
446 int i, c;
447
448 for (i = 0; (c = exc->exc_type->etype_argformat[i]) != '\0';
449 ++i)
450 {
451 switch (c)
452 {
453 case 's':
454 case 'r':
455 sm_free(exc->exc_argv[i].v_str);
456 break;
457 case 'e':
458 sm_exc_free(exc->exc_argv[i].v_exc);
459 break;
460 }
461 }
462 exc->sm_magic = NULL;
463 sm_free(exc->exc_argv);
464 sm_free(exc);
465 }
466 }
467
468 /*
469 ** SM_EXC_MATCH -- Match exception category against a glob pattern.
470 **
471 ** Parameters:
472 ** exc -- exception.
473 ** pattern -- glob pattern.
474 **
475 ** Returns:
476 ** true iff match.
477 */
478
479 bool
sm_exc_match(exc,pattern)480 sm_exc_match(exc, pattern)
481 SM_EXC_T *exc;
482 const char *pattern;
483 {
484 if (exc == NULL)
485 return false;
486 SM_REQUIRE(exc->sm_magic == SmExcMagic);
487 return sm_match(exc->exc_type->etype_category, pattern);
488 }
489
490 /*
491 ** SM_EXC_WRITE -- Write exception message to a stream (wo trailing newline).
492 **
493 ** Parameters:
494 ** exc -- exception.
495 ** stream -- file for output.
496 **
497 ** Returns:
498 ** none.
499 */
500
501 void
sm_exc_write(exc,stream)502 sm_exc_write(exc, stream)
503 SM_EXC_T *exc;
504 SM_FILE_T *stream;
505 {
506 SM_REQUIRE_ISA(exc, SmExcMagic);
507 exc->exc_type->etype_print(exc, stream);
508 }
509
510 /*
511 ** SM_EXC_PRINT -- Print exception message to a stream (with trailing newline).
512 **
513 ** Parameters:
514 ** exc -- exception.
515 ** stream -- file for output.
516 **
517 ** Returns:
518 ** none.
519 */
520
521 void
sm_exc_print(exc,stream)522 sm_exc_print(exc, stream)
523 SM_EXC_T *exc;
524 SM_FILE_T *stream;
525 {
526 SM_REQUIRE_ISA(exc, SmExcMagic);
527 exc->exc_type->etype_print(exc, stream);
528 (void) sm_io_putc(stream, SM_TIME_DEFAULT, '\n');
529 }
530
531 SM_EXC_HANDLER_T *SmExcHandler = NULL;
532 static SM_EXC_DEFAULT_HANDLER_T SmExcDefaultHandler = NULL;
533
534 /*
535 ** SM_EXC_NEWTHREAD -- Initialize exception handling for new process/thread.
536 **
537 ** Parameters:
538 ** h -- default exception handler.
539 **
540 ** Returns:
541 ** none.
542 */
543
544 /*
545 ** Initialize a new process or a new thread by clearing the
546 ** exception handler stack and optionally setting a default
547 ** exception handler function. Call this at the beginning of main,
548 ** or in a new process after calling fork, or in a new thread.
549 **
550 ** This function is a luxury, not a necessity.
551 ** If h != NULL then you can get the same effect by
552 ** wrapping the body of main, or the body of a forked child
553 ** or a new thread in SM_TRY ... SM_EXCEPT(e,"*") h(e); SM_END_TRY.
554 */
555
556 void
sm_exc_newthread(h)557 sm_exc_newthread(h)
558 SM_EXC_DEFAULT_HANDLER_T h;
559 {
560 SmExcHandler = NULL;
561 SmExcDefaultHandler = h;
562 }
563
564 /*
565 ** SM_EXC_RAISE_X -- Raise an exception.
566 **
567 ** Parameters:
568 ** exc -- exception.
569 **
570 ** Returns:
571 ** doesn't.
572 */
573
574 void SM_DEAD_D
sm_exc_raise_x(exc)575 sm_exc_raise_x(exc)
576 SM_EXC_T *exc;
577 {
578 SM_REQUIRE_ISA(exc, SmExcMagic);
579
580 if (SmExcHandler == NULL)
581 {
582 if (SmExcDefaultHandler != NULL)
583 {
584 SM_EXC_DEFAULT_HANDLER_T h;
585
586 /*
587 ** If defined, the default handler is expected
588 ** to terminate the current thread of execution
589 ** using exit() or pthread_exit().
590 ** If it instead returns normally, then we fall
591 ** through to the default case below. If it
592 ** raises an exception, then sm_exc_raise_x is
593 ** re-entered and, because we set SmExcDefaultHandler
594 ** to NULL before invoking h, we will again
595 ** end up in the default case below.
596 */
597
598 h = SmExcDefaultHandler;
599 SmExcDefaultHandler = NULL;
600 (*h)(exc);
601 }
602
603 /*
604 ** No exception handler, so print the error and exit.
605 ** To override this behaviour on a program wide basis,
606 ** call sm_exc_newthread or put an exception handler in main().
607 **
608 ** XXX TODO: map the exception category to an exit code
609 ** XXX from <sysexits.h>.
610 */
611
612 sm_exc_print(exc, smioerr);
613 exit(255);
614 }
615
616 if (SmExcHandler->eh_value == NULL)
617 SmExcHandler->eh_value = exc;
618 else
619 sm_exc_free(exc);
620
621 sm_longjmp_nosig(SmExcHandler->eh_context, 1);
622 }
623
624 /*
625 ** SM_EXC_RAISENEW_X -- shorthand for sm_exc_raise_x(sm_exc_new_x(...))
626 **
627 ** Parameters:
628 ** etype -- type of exception.
629 ** ap -- varargs.
630 **
631 ** Returns:
632 ** none.
633 */
634
635 void SM_DEAD_D
636 #if SM_VA_STD
sm_exc_raisenew_x(const SM_EXC_TYPE_T * etype,...)637 sm_exc_raisenew_x(
638 const SM_EXC_TYPE_T *etype,
639 ...)
640 #else
641 sm_exc_raisenew_x(etype, va_alist)
642 const SM_EXC_TYPE_T *etype;
643 va_dcl
644 #endif
645 {
646 SM_EXC_T *exc;
647 SM_VA_LOCAL_DECL
648
649 SM_VA_START(ap, etype);
650 exc = sm_exc_vnew_x(etype, ap);
651 SM_VA_END(ap);
652 sm_exc_raise_x(exc);
653 }
654