1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1985-2010 AT&T Intellectual Property          *
5*                      and is licensed under the                       *
6*                  Common Public License, Version 1.0                  *
7*                    by AT&T Intellectual Property                     *
8*                                                                      *
9*                A copy of the License is available at                 *
10*            http://www.opensource.org/licenses/cpl1.0.txt             *
11*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12*                                                                      *
13*              Information and Software Systems Research               *
14*                            AT&T Research                             *
15*                           Florham Park NJ                            *
16*                                                                      *
17*                 Glenn Fowler <gsf@research.att.com>                  *
18*                  David Korn <dgk@research.att.com>                   *
19*                   Phong Vo <kpv@research.att.com>                    *
20*                                                                      *
21***********************************************************************/
22#pragma prototyped
23/*
24 * Glenn Fowler
25 * AT&T Research
26 *
27 * error and message formatter
28 *
29 *	level is the error level
30 *	level >= error_info.core!=0 dumps core
31 *	level >= ERROR_FATAL calls error_info.exit
32 *	level < 0 is for debug tracing
33 *
34 * NOTE: id && ERROR_NOID && !ERROR_USAGE implies format=id for errmsg()
35 */
36
37#include "lclib.h"
38
39#include <ctype.h>
40#include <ccode.h>
41#include <namval.h>
42#include <sig.h>
43#include <stk.h>
44#include <times.h>
45#include <regex.h>
46
47/*
48 * 2007-03-19 move error_info from _error_info_ to (*_error_infop_)
49 *	      to allow future Error_info_t growth
50 *            by 2009 _error_info_ can be static
51 */
52
53#if _BLD_ast && defined(__EXPORT__)
54#define extern		extern __EXPORT__
55#endif
56
57extern Error_info_t	_error_info_;
58
59Error_info_t	_error_info_ =
60{
61	2, exit, write,
62	0,0,0,0,0,0,0,0,
63	0,			/* version			*/
64	0,			/* auxilliary			*/
65	0,0,0,0,0,0,0,		/* top of old context stack	*/
66	0,0,0,0,0,0,0,		/* old empty context		*/
67	0,			/* time				*/
68	translate,
69	0			/* catalog			*/
70};
71
72#undef	extern
73
74__EXTERN__(Error_info_t, _error_info_);
75
76__EXTERN__(Error_info_t*, _error_infop_);
77
78Error_info_t*	_error_infop_ = &_error_info_;
79
80/*
81 * these should probably be in error_info
82 */
83
84static struct State_s
85{
86	char*		prefix;
87	Sfio_t*		tty;
88	unsigned long	count;
89	int		breakpoint;
90	regex_t*	match;
91} error_state;
92
93#undef	ERROR_CATALOG
94#define ERROR_CATALOG	(ERROR_LIBRARY<<1)
95
96#define OPT_BREAK	1
97#define OPT_CATALOG	2
98#define OPT_CORE	3
99#define OPT_COUNT	4
100#define OPT_FD		5
101#define OPT_LIBRARY	6
102#define OPT_MASK	7
103#define OPT_MATCH	8
104#define OPT_PREFIX	9
105#define OPT_SYSTEM	10
106#define OPT_TIME	11
107#define OPT_TRACE	12
108
109static const Namval_t		options[] =
110{
111	"break",	OPT_BREAK,
112	"catalog",	OPT_CATALOG,
113	"core",		OPT_CORE,
114	"count",	OPT_COUNT,
115	"debug",	OPT_TRACE,
116	"fd",		OPT_FD,
117	"library",	OPT_LIBRARY,
118	"mask",		OPT_MASK,
119	"match",	OPT_MATCH,
120	"prefix",	OPT_PREFIX,
121	"system",	OPT_SYSTEM,
122	"time",		OPT_TIME,
123	"trace",	OPT_TRACE,
124	0,		0
125};
126
127/*
128 * called by stropt() to set options
129 */
130
131static int
132setopt(void* a, const void* p, register int n, register const char* v)
133{
134	NoP(a);
135	if (p)
136		switch (((Namval_t*)p)->value)
137		{
138		case OPT_BREAK:
139		case OPT_CORE:
140			if (n)
141				switch (*v)
142				{
143				case 'e':
144				case 'E':
145					error_state.breakpoint = ERROR_ERROR;
146					break;
147				case 'f':
148				case 'F':
149					error_state.breakpoint = ERROR_FATAL;
150					break;
151				case 'p':
152				case 'P':
153					error_state.breakpoint = ERROR_PANIC;
154					break;
155				default:
156					error_state.breakpoint = strtol(v, NiL, 0);
157					break;
158				}
159			else
160				error_state.breakpoint = 0;
161			if (((Namval_t*)p)->value == OPT_CORE)
162				error_info.core = error_state.breakpoint;
163			break;
164		case OPT_CATALOG:
165			if (n)
166				error_info.set |= ERROR_CATALOG;
167			else
168				error_info.clear |= ERROR_CATALOG;
169			break;
170		case OPT_COUNT:
171			if (n)
172				error_state.count = strtol(v, NiL, 0);
173			else
174				error_state.count = 0;
175			break;
176		case OPT_FD:
177			error_info.fd = n ? strtol(v, NiL, 0) : -1;
178			break;
179		case OPT_LIBRARY:
180			if (n)
181				error_info.set |= ERROR_LIBRARY;
182			else
183				error_info.clear |= ERROR_LIBRARY;
184			break;
185		case OPT_MASK:
186			if (n)
187				error_info.mask = strtol(v, NiL, 0);
188			else
189				error_info.mask = 0;
190			break;
191		case OPT_MATCH:
192			if (error_state.match)
193				regfree(error_state.match);
194			if (n)
195			{
196				if ((error_state.match || (error_state.match = newof(0, regex_t, 1, 0))) && regcomp(error_state.match, v, REG_EXTENDED|REG_LENIENT))
197				{
198					free(error_state.match);
199					error_state.match = 0;
200				}
201			}
202			else if (error_state.match)
203			{
204				free(error_state.match);
205				error_state.match = 0;
206			}
207			break;
208		case OPT_PREFIX:
209			if (n)
210				error_state.prefix = strdup(v);
211			else if (error_state.prefix)
212			{
213				free(error_state.prefix);
214				error_state.prefix = 0;
215			}
216			break;
217		case OPT_SYSTEM:
218			if (n)
219				error_info.set |= ERROR_SYSTEM;
220			else
221				error_info.clear |= ERROR_SYSTEM;
222			break;
223		case OPT_TIME:
224			error_info.time = n ? 1 : 0;
225			break;
226		case OPT_TRACE:
227			if (n)
228				error_info.trace = -strtol(v, NiL, 0);
229			else
230				error_info.trace = 0;
231			break;
232		}
233	return 0;
234}
235
236/*
237 * print a name with optional delimiter, converting unprintable chars
238 */
239
240static void
241print(register Sfio_t* sp, register char* name, char* delim)
242{
243	if (mbwide())
244		sfputr(sp, name, -1);
245	else
246	{
247#if CC_NATIVE != CC_ASCII
248		register int		c;
249		register unsigned char*	n2a;
250		register unsigned char*	a2n;
251		register int		aa;
252		register int		as;
253
254		n2a = ccmap(CC_NATIVE, CC_ASCII);
255		a2n = ccmap(CC_ASCII, CC_NATIVE);
256		aa = n2a['A'];
257		as = n2a[' '];
258		while (c = *name++)
259		{
260			c = n2a[c];
261			if (c & 0200)
262			{
263				c &= 0177;
264				sfputc(sp, '?');
265			}
266			if (c < as)
267			{
268				c += aa - 1;
269				sfputc(sp, '^');
270			}
271			c = a2n[c];
272			sfputc(sp, c);
273		}
274#else
275		register int		c;
276
277		while (c = *name++)
278		{
279			if (c & 0200)
280			{
281				c &= 0177;
282				sfputc(sp, '?');
283			}
284			if (c < ' ')
285			{
286				c += 'A' - 1;
287				sfputc(sp, '^');
288			}
289			sfputc(sp, c);
290		}
291#endif
292	}
293	if (delim)
294		sfputr(sp, delim, -1);
295}
296
297/*
298 * print error context FIFO stack
299 */
300
301#define CONTEXT(f,p)	(((f)&ERROR_PUSH)?((Error_context_t*)&(p)->context->context):((Error_context_t*)(p)))
302
303static void
304context(register Sfio_t* sp, register Error_context_t* cp)
305{
306	if (cp->context)
307		context(sp, CONTEXT(cp->flags, cp->context));
308	if (!(cp->flags & ERROR_SILENT))
309	{
310		if (cp->id)
311			print(sp, cp->id, NiL);
312		if (cp->line > ((cp->flags & ERROR_INTERACTIVE) != 0))
313		{
314			if (cp->file)
315				sfprintf(sp, ": \"%s\", %s %d", cp->file, ERROR_translate(NiL, NiL, ast.id, "line"), cp->line);
316			else
317				sfprintf(sp, "[%d]", cp->line);
318		}
319		sfputr(sp, ": ", -1);
320	}
321}
322
323/*
324 * debugging breakpoint
325 */
326
327extern void
328error_break(void)
329{
330	char*	s;
331
332	if (error_state.tty || (error_state.tty = sfopen(NiL, "/dev/tty", "r+")))
333	{
334		sfprintf(error_state.tty, "error breakpoint: ");
335		if (s = sfgetr(error_state.tty, '\n', 1))
336		{
337			if (streq(s, "q") || streq(s, "quit"))
338				exit(0);
339			stropt(s, options, sizeof(*options), setopt, NiL);
340		}
341	}
342}
343
344void
345error(int level, ...)
346{
347	va_list	ap;
348
349	va_start(ap, level);
350	errorv(NiL, level, ap);
351	va_end(ap);
352}
353
354void
355errorv(const char* id, int level, va_list ap)
356{
357	register int	n;
358	int		fd;
359	int		flags;
360	char*		s;
361	char*		t;
362	char*		format;
363	char*		library;
364	const char*	catalog;
365
366	int		line;
367	char*		file;
368
369#if !_PACKAGE_astsa
370	unsigned long	d;
371	struct tms	us;
372#endif
373
374	if (!error_info.init)
375	{
376		error_info.init = 1;
377		stropt(getenv("ERROR_OPTIONS"), options, sizeof(*options), setopt, NiL);
378	}
379	if (level > 0)
380	{
381		flags = level & ~ERROR_LEVEL;
382		level &= ERROR_LEVEL;
383	}
384	else
385		flags = 0;
386	if ((flags & (ERROR_USAGE|ERROR_NOID)) == ERROR_NOID)
387	{
388		format = (char*)id;
389		id = 0;
390	}
391	else
392		format = 0;
393	if (id)
394	{
395		catalog = (char*)id;
396		if (!*catalog || *catalog == ':')
397		{
398			catalog = 0;
399			library = 0;
400		}
401		else if ((library = strchr(catalog, ':')) && !*++library)
402			library = 0;
403	}
404	else
405	{
406		catalog = 0;
407		library = 0;
408	}
409	if (catalog)
410		id = 0;
411	else
412	{
413		id = (const char*)error_info.id;
414		catalog = error_info.catalog;
415	}
416	if (level < error_info.trace || (flags & ERROR_LIBRARY) && !(((error_info.set | error_info.flags) ^ error_info.clear) & ERROR_LIBRARY) || level < 0 && error_info.mask && !(error_info.mask & (1<<(-level - 1))))
417	{
418		if (level >= ERROR_FATAL)
419			(*error_info.exit)(level - 1);
420		return;
421	}
422	if (error_info.trace < 0)
423		flags |= ERROR_LIBRARY|ERROR_SYSTEM;
424	flags |= error_info.set | error_info.flags;
425	flags &= ~error_info.clear;
426	if (!library)
427		flags &= ~ERROR_LIBRARY;
428	fd = (flags & ERROR_OUTPUT) ? va_arg(ap, int) : error_info.fd;
429	if (error_info.write)
430	{
431		long	off;
432		char*	bas;
433
434		bas = stkptr(stkstd, 0);
435		if (off = stktell(stkstd))
436			stkfreeze(stkstd, 0);
437		file = error_info.id;
438		if (error_state.prefix)
439			sfprintf(stkstd, "%s: ", error_state.prefix);
440		if (flags & ERROR_USAGE)
441		{
442			if (flags & ERROR_NOID)
443				sfprintf(stkstd, "       ");
444			else
445				sfprintf(stkstd, "%s: ", ERROR_translate(NiL, NiL, ast.id, "Usage"));
446			if (file || opt_info.argv && (file = opt_info.argv[0]))
447				print(stkstd, file, " ");
448		}
449		else
450		{
451			if (level && !(flags & ERROR_NOID))
452			{
453				if (error_info.context && level > 0)
454					context(stkstd, CONTEXT(error_info.flags, error_info.context));
455				if (file)
456					print(stkstd, file, (flags & ERROR_LIBRARY) ? " " : ": ");
457				if (flags & (ERROR_CATALOG|ERROR_LIBRARY))
458				{
459					sfprintf(stkstd, "[");
460					if (flags & ERROR_CATALOG)
461						sfprintf(stkstd, "%s %s%s",
462							catalog ? catalog : ERROR_translate(NiL, NiL, ast.id, "DEFAULT"),
463							ERROR_translate(NiL, NiL, ast.id, "catalog"),
464							(flags & ERROR_LIBRARY) ? ", " : "");
465					if (flags & ERROR_LIBRARY)
466						sfprintf(stkstd, "%s %s",
467							library,
468							ERROR_translate(NiL, NiL, ast.id, "library"));
469					sfprintf(stkstd, "]: ");
470				}
471			}
472			if (level > 0 && error_info.line > ((flags & ERROR_INTERACTIVE) != 0))
473			{
474				if (error_info.file && *error_info.file)
475					sfprintf(stkstd, "\"%s\", ", error_info.file);
476				sfprintf(stkstd, "%s %d: ", ERROR_translate(NiL, NiL, ast.id, "line"), error_info.line);
477			}
478		}
479#if !_PACKAGE_astsa
480		if (error_info.time)
481		{
482			if ((d = times(&us)) < error_info.time || error_info.time == 1)
483				error_info.time = d;
484			sfprintf(stkstd, " %05lu.%05lu.%05lu ", d - error_info.time, (unsigned long)us.tms_utime, (unsigned long)us.tms_stime);
485		}
486#endif
487		switch (level)
488		{
489		case 0:
490			flags &= ~ERROR_SYSTEM;
491			break;
492		case ERROR_WARNING:
493			sfprintf(stkstd, "%s: ", ERROR_translate(NiL, NiL, ast.id, "warning"));
494			break;
495		case ERROR_PANIC:
496			sfprintf(stkstd, "%s: ", ERROR_translate(NiL, NiL, ast.id, "panic"));
497			break;
498		default:
499			if (level < 0)
500			{
501				s = ERROR_translate(NiL, NiL, ast.id, "debug");
502				if (error_info.trace < -1)
503					sfprintf(stkstd, "%s%d:%s", s, level, level > -10 ? " " : "");
504				else
505					sfprintf(stkstd, "%s: ", s);
506				for (n = 0; n < error_info.indent; n++)
507				{
508					sfputc(stkstd, ' ');
509					sfputc(stkstd, ' ');
510				}
511			}
512			break;
513		}
514		if (flags & ERROR_SOURCE)
515		{
516			/*
517			 * source ([version], file, line) message
518			 */
519
520			file = va_arg(ap, char*);
521			line = va_arg(ap, int);
522			s = ERROR_translate(NiL, NiL, ast.id, "line");
523			if (error_info.version)
524				sfprintf(stkstd, "(%s: \"%s\", %s %d) ", error_info.version, file, s, line);
525			else
526				sfprintf(stkstd, "(\"%s\", %s %d) ", file, s, line);
527		}
528		if (format || (format = va_arg(ap, char*)))
529		{
530			if (!(flags & ERROR_USAGE))
531				format = ERROR_translate(NiL, id, catalog, format);
532			sfvprintf(stkstd, format, ap);
533		}
534		if (!(flags & ERROR_PROMPT))
535		{
536			/*
537			 * level&ERROR_OUTPUT on return means message
538			 * already output
539			 */
540
541			if ((flags & ERROR_SYSTEM) && errno && errno != error_info.last_errno)
542			{
543				sfprintf(stkstd, " [%s]", fmterror(errno));
544				if (error_info.set & ERROR_SYSTEM)
545					errno = 0;
546				error_info.last_errno = (level >= 0) ? 0 : errno;
547			}
548			if (error_info.auxilliary && level >= 0)
549				level = (*error_info.auxilliary)(stkstd, level, flags);
550			sfputc(stkstd, '\n');
551		}
552		if (level > 0)
553		{
554			if ((level & ~ERROR_OUTPUT) > 1)
555				error_info.errors++;
556			else
557				error_info.warnings++;
558		}
559		if (level < 0 || !(level & ERROR_OUTPUT))
560		{
561			n = stktell(stkstd);
562			s = stkptr(stkstd, 0);
563			if (t = memchr(s, '\f', n))
564			{
565				n -= ++t - s;
566				s = t;
567			}
568#if HUH_19980401 /* nasty problems if sfgetr() is in effect! */
569			sfsync(sfstdin);
570#endif
571			sfsync(sfstdout);
572			sfsync(sfstderr);
573			if (fd == sffileno(sfstderr) && error_info.write == write)
574			{
575				sfwrite(sfstderr, s, n);
576				sfsync(sfstderr);
577			}
578			else
579				(*error_info.write)(fd, s, n);
580		}
581		else
582		{
583			s = 0;
584			level &= ERROR_LEVEL;
585		}
586		stkset(stkstd, bas, off);
587	}
588	else
589		s = 0;
590	if (level >= error_state.breakpoint && error_state.breakpoint && (!error_state.match || !regexec(error_state.match, s ? s : format, 0, NiL, 0)) && (!error_state.count || !--error_state.count))
591	{
592		if (error_info.core)
593		{
594#ifndef SIGABRT
595#ifdef	SIGQUIT
596#define SIGABRT	SIGQUIT
597#else
598#ifdef	SIGIOT
599#define SIGABRT	SIGIOT
600#endif
601#endif
602#endif
603#ifdef	SIGABRT
604			signal(SIGABRT, SIG_DFL);
605			kill(getpid(), SIGABRT);
606			pause();
607#else
608			abort();
609#endif
610		}
611		else
612			error_break();
613	}
614	if (level >= ERROR_FATAL)
615		(*error_info.exit)(level - ERROR_FATAL + 1);
616}
617
618/*
619 * error_info context control
620 */
621
622static Error_info_t*	freecontext;
623
624Error_info_t*
625errorctx(Error_info_t* p, int op, int flags)
626{
627	if (op & ERROR_POP)
628	{
629		if (!(_error_infop_ = p->context))
630			_error_infop_ = &_error_info_;
631		if (op & ERROR_FREE)
632		{
633			p->context = freecontext;
634			freecontext = p;
635		}
636		p = _error_infop_;
637	}
638	else
639	{
640		if (!p)
641		{
642			if (p = freecontext)
643				freecontext = freecontext->context;
644			else if (!(p = newof(0, Error_info_t, 1, 0)))
645				return 0;
646			*p = *_error_infop_;
647			p->errors = p->flags = p->line = p->warnings = 0;
648			p->catalog = p->file = 0;
649		}
650		if (op & ERROR_PUSH)
651		{
652			p->flags = flags;
653			p->context = _error_infop_;
654			_error_infop_ = p;
655		}
656		p->flags |= ERROR_PUSH;
657	}
658	return p;
659}
660