/***********************************************************************
* *
* This software is part of the ast package *
* Copyright (c) 1985-2010 AT&T Intellectual Property *
* and is licensed under the *
* Common Public License, Version 1.0 *
* by AT&T Intellectual Property *
* *
* A copy of the License is available at *
* http://www.opensource.org/licenses/cpl1.0.txt *
* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
* *
* Information and Software Systems Research *
* AT&T Research *
* Florham Park NJ *
* *
* Glenn Fowler <gsf@research.att.com> *
* David Korn <dgk@research.att.com> *
* Phong Vo <kpv@research.att.com> *
* *
***********************************************************************/
#pragma prototyped
/*
* Glenn Fowler
* AT&T Research
*
* error and message formatter
*
* level is the error level
* level >= error_info.core!=0 dumps core
* level >= ERROR_FATAL calls error_info.exit
* level < 0 is for debug tracing
*
* NOTE: id && ERROR_NOID && !ERROR_USAGE implies format=id for errmsg()
*/
#include "lclib.h"
#include <ctype.h>
#include <ccode.h>
#include <namval.h>
#include <sig.h>
#include <stk.h>
#include <times.h>
#include <regex.h>
/*
* 2007-03-19 move error_info from _error_info_ to (*_error_infop_)
* to allow future Error_info_t growth
* by 2009 _error_info_ can be static
*/
#if _BLD_ast && defined(__EXPORT__)
#define extern extern __EXPORT__
#endif
extern Error_info_t _error_info_;
Error_info_t _error_info_ =
{
2, exit, write,
0,0,0,0,0,0,0,0,
0, /* version */
0, /* auxilliary */
0,0,0,0,0,0,0, /* top of old context stack */
0,0,0,0,0,0,0, /* old empty context */
0, /* time */
translate,
0 /* catalog */
};
#undef extern
__EXTERN__(Error_info_t, _error_info_);
__EXTERN__(Error_info_t*, _error_infop_);
Error_info_t* _error_infop_ = &_error_info_;
/*
* these should probably be in error_info
*/
static struct State_s
{
char* prefix;
Sfio_t* tty;
unsigned long count;
int breakpoint;
regex_t* match;
} error_state;
#undef ERROR_CATALOG
#define ERROR_CATALOG (ERROR_LIBRARY<<1)
#define OPT_BREAK 1
#define OPT_CATALOG 2
#define OPT_CORE 3
#define OPT_COUNT 4
#define OPT_FD 5
#define OPT_LIBRARY 6
#define OPT_MASK 7
#define OPT_MATCH 8
#define OPT_PREFIX 9
#define OPT_SYSTEM 10
#define OPT_TIME 11
#define OPT_TRACE 12
static const Namval_t options[] =
{
"break", OPT_BREAK,
"catalog", OPT_CATALOG,
"core", OPT_CORE,
"count", OPT_COUNT,
"debug", OPT_TRACE,
"fd", OPT_FD,
"library", OPT_LIBRARY,
"mask", OPT_MASK,
"match", OPT_MATCH,
"prefix", OPT_PREFIX,
"system", OPT_SYSTEM,
"time", OPT_TIME,
"trace", OPT_TRACE,
0, 0
};
/*
* called by stropt() to set options
*/
static int
setopt(void* a, const void* p, register int n, register const char* v)
{
NoP(a);
if (p)
switch (((Namval_t*)p)->value)
{
case OPT_BREAK:
case OPT_CORE:
if (n)
switch (*v)
{
case 'e':
case 'E':
error_state.breakpoint = ERROR_ERROR;
break;
case 'f':
case 'F':
error_state.breakpoint = ERROR_FATAL;
break;
case 'p':
case 'P':
error_state.breakpoint = ERROR_PANIC;
break;
default:
error_state.breakpoint = strtol(v, NiL, 0);
break;
}
else
error_state.breakpoint = 0;
if (((Namval_t*)p)->value == OPT_CORE)
error_info.core = error_state.breakpoint;
break;
case OPT_CATALOG:
if (n)
error_info.set |= ERROR_CATALOG;
else
error_info.clear |= ERROR_CATALOG;
break;
case OPT_COUNT:
if (n)
error_state.count = strtol(v, NiL, 0);
else
error_state.count = 0;
break;
case OPT_FD:
error_info.fd = n ? strtol(v, NiL, 0) : -1;
break;
case OPT_LIBRARY:
if (n)
error_info.set |= ERROR_LIBRARY;
else
error_info.clear |= ERROR_LIBRARY;
break;
case OPT_MASK:
if (n)
error_info.mask = strtol(v, NiL, 0);
else
error_info.mask = 0;
break;
case OPT_MATCH:
if (error_state.match)
regfree(error_state.match);
if (n)
{
if ((error_state.match || (error_state.match = newof(0, regex_t, 1, 0))) && regcomp(error_state.match, v, REG_EXTENDED|REG_LENIENT))
{
free(error_state.match);
error_state.match = 0;
}
}
else if (error_state.match)
{
free(error_state.match);
error_state.match = 0;
}
break;
case OPT_PREFIX:
if (n)
error_state.prefix = strdup(v);
else if (error_state.prefix)
{
free(error_state.prefix);
error_state.prefix = 0;
}
break;
case OPT_SYSTEM:
if (n)
error_info.set |= ERROR_SYSTEM;
else
error_info.clear |= ERROR_SYSTEM;
break;
case OPT_TIME:
error_info.time = n ? 1 : 0;
break;
case OPT_TRACE:
if (n)
error_info.trace = -strtol(v, NiL, 0);
else
error_info.trace = 0;
break;
}
return 0;
}
/*
* print a name with optional delimiter, converting unprintable chars
*/
static void
print(register Sfio_t* sp, register char* name, char* delim)
{
if (mbwide())
sfputr(sp, name, -1);
else
{
#if CC_NATIVE != CC_ASCII
register int c;
register unsigned char* n2a;
register unsigned char* a2n;
register int aa;
register int as;
n2a = ccmap(CC_NATIVE, CC_ASCII);
a2n = ccmap(CC_ASCII, CC_NATIVE);
aa = n2a['A'];
as = n2a[' '];
while (c = *name++)
{
c = n2a[c];
if (c & 0200)
{
c &= 0177;
sfputc(sp, '?');
}
if (c < as)
{
c += aa - 1;
sfputc(sp, '^');
}
c = a2n[c];
sfputc(sp, c);
}
#else
register int c;
while (c = *name++)
{
if (c & 0200)
{
c &= 0177;
sfputc(sp, '?');
}
if (c < ' ')
{
c += 'A' - 1;
sfputc(sp, '^');
}
sfputc(sp, c);
}
#endif
}
if (delim)
sfputr(sp, delim, -1);
}
/*
* print error context FIFO stack
*/
#define CONTEXT(f,p) (((f)&ERROR_PUSH)?((Error_context_t*)&(p)->context->context):((Error_context_t*)(p)))
static void
context(register Sfio_t* sp, register Error_context_t* cp)
{
if (cp->context)
context(sp, CONTEXT(cp->flags, cp->context));
if (!(cp->flags & ERROR_SILENT))
{
if (cp->id)
print(sp, cp->id, NiL);
if (cp->line > ((cp->flags & ERROR_INTERACTIVE) != 0))
{
if (cp->file)
sfprintf(sp, ": \"%s\", %s %d", cp->file, ERROR_translate(NiL, NiL, ast.id, "line"), cp->line);
else
sfprintf(sp, "[%d]", cp->line);
}
sfputr(sp, ": ", -1);
}
}
/*
* debugging breakpoint
*/
extern void
error_break(void)
{
char* s;
if (error_state.tty || (error_state.tty = sfopen(NiL, "/dev/tty", "r+")))
{
sfprintf(error_state.tty, "error breakpoint: ");
if (s = sfgetr(error_state.tty, '\n', 1))
{
if (streq(s, "q") || streq(s, "quit"))
exit(0);
stropt(s, options, sizeof(*options), setopt, NiL);
}
}
}
void
error(int level, ...)
{
va_list ap;
va_start(ap, level);
errorv(NiL, level, ap);
va_end(ap);
}
void
errorv(const char* id, int level, va_list ap)
{
register int n;
int fd;
int flags;
char* s;
char* t;
char* format;
char* library;
const char* catalog;
int line;
char* file;
#if !_PACKAGE_astsa
unsigned long d;
struct tms us;
#endif
if (!error_info.init)
{
error_info.init = 1;
stropt(getenv("ERROR_OPTIONS"), options, sizeof(*options), setopt, NiL);
}
if (level > 0)
{
flags = level & ~ERROR_LEVEL;
level &= ERROR_LEVEL;
}
else
flags = 0;
if ((flags & (ERROR_USAGE|ERROR_NOID)) == ERROR_NOID)
{
format = (char*)id;
id = 0;
}
else
format = 0;
if (id)
{
catalog = (char*)id;
if (!*catalog || *catalog == ':')
{
catalog = 0;
library = 0;
}
else if ((library = strchr(catalog, ':')) && !*++library)
library = 0;
}
else
{
catalog = 0;
library = 0;
}
if (catalog)
id = 0;
else
{
id = (const char*)error_info.id;
catalog = error_info.catalog;
}
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))))
{
if (level >= ERROR_FATAL)
(*error_info.exit)(level - 1);
return;
}
if (error_info.trace < 0)
flags |= ERROR_LIBRARY|ERROR_SYSTEM;
flags |= error_info.set | error_info.flags;
flags &= ~error_info.clear;
if (!library)
flags &= ~ERROR_LIBRARY;
fd = (flags & ERROR_OUTPUT) ? va_arg(ap, int) : error_info.fd;
if (error_info.write)
{
long off;
char* bas;
bas = stkptr(stkstd, 0);
if (off = stktell(stkstd))
stkfreeze(stkstd, 0);
file = error_info.id;
if (error_state.prefix)
sfprintf(stkstd, "%s: ", error_state.prefix);
if (flags & ERROR_USAGE)
{
if (flags & ERROR_NOID)
sfprintf(stkstd, " ");
else
sfprintf(stkstd, "%s: ", ERROR_translate(NiL, NiL, ast.id, "Usage"));
if (file || opt_info.argv && (file = opt_info.argv[0]))
print(stkstd, file, " ");
}
else
{
if (level && !(flags & ERROR_NOID))
{
if (error_info.context && level > 0)
context(stkstd, CONTEXT(error_info.flags, error_info.context));
if (file)
print(stkstd, file, (flags & ERROR_LIBRARY) ? " " : ": ");
if (flags & (ERROR_CATALOG|ERROR_LIBRARY))
{
sfprintf(stkstd, "[");
if (flags & ERROR_CATALOG)
sfprintf(stkstd, "%s %s%s",
catalog ? catalog : ERROR_translate(NiL, NiL, ast.id, "DEFAULT"),
ERROR_translate(NiL, NiL, ast.id, "catalog"),
(flags & ERROR_LIBRARY) ? ", " : "");
if (flags & ERROR_LIBRARY)
sfprintf(stkstd, "%s %s",
library,
ERROR_translate(NiL, NiL, ast.id, "library"));
sfprintf(stkstd, "]: ");
}
}
if (level > 0 && error_info.line > ((flags & ERROR_INTERACTIVE) != 0))
{
if (error_info.file && *error_info.file)
sfprintf(stkstd, "\"%s\", ", error_info.file);
sfprintf(stkstd, "%s %d: ", ERROR_translate(NiL, NiL, ast.id, "line"), error_info.line);
}
}
#if !_PACKAGE_astsa
if (error_info.time)
{
if ((d = times(&us)) < error_info.time || error_info.time == 1)
error_info.time = d;
sfprintf(stkstd, " %05lu.%05lu.%05lu ", d - error_info.time, (unsigned long)us.tms_utime, (unsigned long)us.tms_stime);
}
#endif
switch (level)
{
case 0:
flags &= ~ERROR_SYSTEM;
break;
case ERROR_WARNING:
sfprintf(stkstd, "%s: ", ERROR_translate(NiL, NiL, ast.id, "warning"));
break;
case ERROR_PANIC:
sfprintf(stkstd, "%s: ", ERROR_translate(NiL, NiL, ast.id, "panic"));
break;
default:
if (level < 0)
{
s = ERROR_translate(NiL, NiL, ast.id, "debug");
if (error_info.trace < -1)
sfprintf(stkstd, "%s%d:%s", s, level, level > -10 ? " " : "");
else
sfprintf(stkstd, "%s: ", s);
for (n = 0; n < error_info.indent; n++)
{
sfputc(stkstd, ' ');
sfputc(stkstd, ' ');
}
}
break;
}
if (flags & ERROR_SOURCE)
{
/*
* source ([version], file, line) message
*/
file = va_arg(ap, char*);
line = va_arg(ap, int);
s = ERROR_translate(NiL, NiL, ast.id, "line");
if (error_info.version)
sfprintf(stkstd, "(%s: \"%s\", %s %d) ", error_info.version, file, s, line);
else
sfprintf(stkstd, "(\"%s\", %s %d) ", file, s, line);
}
if (format || (format = va_arg(ap, char*)))
{
if (!(flags & ERROR_USAGE))
format = ERROR_translate(NiL, id, catalog, format);
sfvprintf(stkstd, format, ap);
}
if (!(flags & ERROR_PROMPT))
{
/*
* level&ERROR_OUTPUT on return means message
* already output
*/
if ((flags & ERROR_SYSTEM) && errno && errno != error_info.last_errno)
{
sfprintf(stkstd, " [%s]", fmterror(errno));
if (error_info.set & ERROR_SYSTEM)
errno = 0;
error_info.last_errno = (level >= 0) ? 0 : errno;
}
if (error_info.auxilliary && level >= 0)
level = (*error_info.auxilliary)(stkstd, level, flags);
sfputc(stkstd, '\n');
}
if (level > 0)
{
if ((level & ~ERROR_OUTPUT) > 1)
error_info.errors++;
else
error_info.warnings++;
}
if (level < 0 || !(level & ERROR_OUTPUT))
{
n = stktell(stkstd);
s = stkptr(stkstd, 0);
if (t = memchr(s, '\f', n))
{
n -= ++t - s;
s = t;
}
#if HUH_19980401 /* nasty problems if sfgetr() is in effect! */
sfsync(sfstdin);
#endif
sfsync(sfstdout);
sfsync(sfstderr);
if (fd == sffileno(sfstderr) && error_info.write == write)
{
sfwrite(sfstderr, s, n);
sfsync(sfstderr);
}
else
(*error_info.write)(fd, s, n);
}
else
{
s = 0;
level &= ERROR_LEVEL;
}
stkset(stkstd, bas, off);
}
else
s = 0;
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))
{
if (error_info.core)
{
#ifndef SIGABRT
#ifdef SIGQUIT
#define SIGABRT SIGQUIT
#else
#ifdef SIGIOT
#define SIGABRT SIGIOT
#endif
#endif
#endif
#ifdef SIGABRT
signal(SIGABRT, SIG_DFL);
kill(getpid(), SIGABRT);
pause();
#else
abort();
#endif
}
else
error_break();
}
if (level >= ERROR_FATAL)
(*error_info.exit)(level - ERROR_FATAL + 1);
}
/*
* error_info context control
*/
static Error_info_t* freecontext;
Error_info_t*
errorctx(Error_info_t* p, int op, int flags)
{
if (op & ERROR_POP)
{
if (!(_error_infop_ = p->context))
_error_infop_ = &_error_info_;
if (op & ERROR_FREE)
{
p->context = freecontext;
freecontext = p;
}
p = _error_infop_;
}
else
{
if (!p)
{
if (p = freecontext)
freecontext = freecontext->context;
else if (!(p = newof(0, Error_info_t, 1, 0)))
return 0;
*p = *_error_infop_;
p->errors = p->flags = p->line = p->warnings = 0;
p->catalog = p->file = 0;
}
if (op & ERROR_PUSH)
{
p->flags = flags;
p->context = _error_infop_;
_error_infop_ = p;
}
p->flags |= ERROR_PUSH;
}
return p;
}