xref: /freebsd/contrib/sendmail/libsm/exc.c (revision 5ca8e32633c4ffbbcd6762e5888b6a4ba0708c6c)
1 /*
2  * Copyright (c) 2000-2002 Proofpoint, 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.50 2013-11-22 20:51:42 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 static SM_EXC_T *sm_exc_vnew_x __P((const SM_EXC_TYPE_T *, va_list));
233 
234 static SM_EXC_T *
235 sm_exc_vnew_x(etype, ap)
236 	const SM_EXC_TYPE_T *etype;
237 	va_list 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_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
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
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
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
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
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
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
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