xref: /freebsd/contrib/sendmail/libsm/exc.c (revision 4f29da19bd44f0e99f021510460a81bf754c21d2)
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