xref: /illumos-gate/usr/src/cmd/sendmail/libsm/exc.c (revision 7ddc9b1afd18f260b9fb78ec7732facd91769131)
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
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 static SM_EXC_T *sm_exc_vnew_x __P((const SM_EXC_TYPE_T *, va_list SM_NONVOLATILE));
235 
236 static SM_EXC_T *
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
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
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
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
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
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
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
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
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