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>
12SM_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
29const char SmExcMagic[] = "sm_exc";
30const 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
51void
52sm_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
149static void
150sm_etype_os_print __P((
151	SM_EXC_T *exc,
152	SM_FILE_T *stream));
153
154static void
155sm_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
179const 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
194const 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
232static SM_EXC_T *sm_exc_vnew_x __P((const SM_EXC_TYPE_T *, va_list));
233
234static SM_EXC_T *
235sm_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
403SM_EXC_T *
404#if SM_VA_STD
405sm_exc_new_x(
406	const SM_EXC_TYPE_T *etype,
407	...)
408#else /* SM_VA_STD */
409sm_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
433void
434sm_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
477bool
478sm_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
499void
500sm_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
519void
520sm_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
529SM_EXC_HANDLER_T *SmExcHandler = NULL;
530static 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
554void
555sm_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
572void SM_DEAD_D
573sm_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
633void SM_DEAD_D
634#if SM_VA_STD
635sm_exc_raisenew_x(
636	const SM_EXC_TYPE_T *etype,
637	...)
638#else
639sm_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