/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 1997-2001 by Sun Microsystems, Inc.
* All rights reserved.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
*
* trace.c -- a simple translator from spec source to c source for
* a apptrace interposer library. This file implements the
* (interface to) the front end. Other files implement the middle
* and databases, and generate.c implements the back end.
*
*/
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include <time.h>
#include <string.h>
#include "parser.h"
#include "trace.h"
#include "util.h"
#include "db.h"
#include "symtab.h"
#include "io.h"
#include "printfuncs.h"
#include "errlog.h"
#include "parseproto.h"
static int Verbose;
/* File globals. This would be better as a class. */
/* The first four (commented out) of these enums are defined in parser.h */
enum {
/* XLATOR_KW_NOTFOUND = 0, */
/* XLATOR_KW_FUNC, */
/* XLATOR_KW_DATA */
/* XLATOR_KW_END */
XLATOR_KW_EXCP = 4,
XLATOR_KW_DECL,
XLATOR_KW_INCL,
XLATOR_KW_ERRNO,
XLATOR_KW_ERRVAL,
XLATOR_KW_ARCH,
XLATOR_KW_WEAK
};
#define FIRST_TOKEN 4 /* Must match the first token in the above enum */
static xlator_keyword_t Keywords[] = {
{ "exception", XLATOR_KW_EXCP },
{ "declaration", XLATOR_KW_DECL },
{ "include", XLATOR_KW_INCL },
{ "errno", XLATOR_KW_ERRNO },
{ "errval", XLATOR_KW_ERRVAL},
{ "arch", XLATOR_KW_ARCH},
{ "weak", XLATOR_KW_WEAK},
{ "weakfor", XLATOR_KW_WEAK},
{ "alias", XLATOR_KW_WEAK},
{ NULL, XLATOR_KW_NOTFOUND }
};
static struct stats_t {
int libraries,
files,
interfaces,
lines;
int errors,
warnings,
skips;
time_t start,
end;
} Statistics;
#define LINE (m.mi_line_number-(m.mi_nlines-1))
static void stats_init(void);
static void stats_report(void);
static int collect_binding(int const, char *, int);
static int collect_prototype(char *, int, int);
static int collect_include(char *, int);
static int collect_errval(char *, int);
static int collect_arch(char *);
static void generate_includes(void);
static void generate_init(void);
static void generate_interface(void);
static void generate_closedown(void);
static int generate_aux_file();
/* Local (static) parsing functions. */
static char *to_actual();
static int to_basetype(char *);
static char *de_const(char *);
static char *strpqcpy(char *, char *, char *);
/*
* xlator_init -- initialize translator, called at startup-time
* with a struct translator_info of information the translator
* might need, returning a list of ``interesting'' spec keywords
* for the front end to select and pass to the back end translator.
*
*/
xlator_keyword_t *
xlator_init(const Translator_info *t_info)
{
int i;
errlog(BEGIN, "xlator_init() {");
/* Save interesting parameters. */
stats_init();
db_set_source_directory(".");
db_set_target_directory(".");
Verbose = t_info->ti_verbosity;
seterrseverity(Verbose); /* Ditto. */
db_set_output_file(t_info->ti_output_file);
db_set_arch(t_info->ti_arch);
/* Display passed argument and return value. */
errlog(VERBOSE, "Keywords[] = {");
for (i = 0; Keywords[i].key != NULL; i++) {
errlog(VERBOSE, " \"%s\", ", Keywords[i].key);
}
errlog(VERBOSE, " (char *) NULL");
errlog(VERBOSE, "};");
errlog(END, "}");
return (Keywords);
}
/*
* xlator_startlib -- called on starting a new library, so back end
* translator can decide to change output file/directory if desired.
*/
int
xlator_startlib(char const *libname)
{
errlog(BEGIN, "xlator_startlib() ");
Statistics.libraries++;
db_set_current_library(libname);
errlog(VERBOSE, "now in library \"%s\"", libname);
errlog(END, "}");
return (SUCCESS_RC);
}
/*
* xlator_startfile -- ditto, called on starting each new spec file in the
* specified library.
*/
int
xlator_startfile(char const *filename)
{
int rc = SUCCESS_RC;
char infile[MAXLINE],
outfile[MAXLINE],
*lib = db_get_current_library();
seterrline(0, filename, "", "");
errlog(BEGIN, "xlator_startfile() {");
Statistics.files++;
db_set_current_file(filename);
errlog(TRACING, "now in file \"%s\" in lib \"%s\"",
filename, lib);
/* Generate filenames. */
(void) snprintf(infile, sizeof (infile), "%s", filename);
(void) snprintf(outfile, sizeof (outfile), "%s.c",
db_get_output_file());
/* Open .c file. */
if (open_code_file() == NO) {
rc = ERROR_RC;
}
generate_init(); /* Write stuff to the c file. */
symtab_clear_includes(); /* Clear out the per-file data. */
errlog(END, "}");
return (rc);
}
/*
* xlator_start_if -- tritto, called on starting each new
* interface in the spec file.
*/
int
xlator_start_if(const Meta_info m, int const token, char *value)
{
char ifname[BUFSIZ];
char *kw;
switch (token) {
case XLATOR_KW_FUNC:
kw = "Function";
break;
case XLATOR_KW_DATA:
kw = "Data";
break;
default:
/* This should never happen */
errlog(ERROR,
"\"%s\", line %d: Implementation error! "
"Please file a bug\n", __FILE__, __LINE__);
return (XLATOR_FATAL);
}
seterrline(LINE, m.mi_filename, kw, value);
errlog(BEGIN, "xlator_start_if() {");
/*
* XXX Note whether interface is function or data in some state data item.
* We'll need it later when writing interceptors.
*/
Statistics.interfaces++;
(void) strpqcpy(ifname, value, nextsep2(value));
if (*ifname == '\0') {
errlog(INPUT|ERROR|FATAL,
"missing argument in \"%s\" line", kw);
}
db_set_current_interface(ifname);
errlog(VERBOSE, "interface='%s'", value);
if (token == XLATOR_KW_DATA) {
Statistics.skips++;
errlog(VERBOSE, "telling front end to skip '%s'", value);
errlog(END, "}");
return (SKIP_RC); /* Tell front end to skip it for us. */
}
errlog(TRACING, "now in interface \"%s\"", value);
symtab_new_function(m.mi_line_number, m.mi_filename);
/* Also cleans junk out of symbol table. */
errlog(END, "}");
return (SUCCESS_RC);
}
/*
* xlator_take_kvpair -- the primary call: collect a datum provide by the
* front-end wrapper.
*/
int
xlator_take_kvpair(Meta_info m, int const token, char *value)
{
int retval;
char *key = Keywords[token-FIRST_TOKEN].key;
int line = LINE; /* TBD */
symtab_set_filename(m.mi_filename);
value = strnormalize(value);
seterrline(line, m.mi_filename, key, value);
errlog(BEGIN, "xlator_take_kvpair() {");
Statistics.lines++;
errlog(VERBOSE, "key='%s', value='%s'",
(key) ? key : "<nil>",
(value) ? value : "<nil>");
switch (token) {
case XLATOR_KW_DECL:
/*
* XXX Check state item to see that it is a function,
* else do not emit interceptor
*/
symtab_clear_function(); /* Always use last one. */
errlog(END, "}");
retval = collect_prototype(value, line, m.mi_ext_cnt);
break;
case XLATOR_KW_INCL:
errlog(END, "}"); /* Use union of all includes. */
retval = collect_include(value, line);
if (retval == ERROR_RC) {
errlog(FATAL|INPUT, "Bad include line in spec file");
}
break;
case XLATOR_KW_EXCP:
symtab_clear_exception(); /* Always use last. */
retval = collect_binding(token, value, line);
break;
case XLATOR_KW_ERRNO:
symtab_clear_errval(); /* Always use last. */
retval = collect_errval("errno", line);
break;
case XLATOR_KW_ERRVAL:
symtab_clear_errval(); /* Always use last. */
retval = collect_errval(value, line);
break;
case XLATOR_KW_ARCH:
retval = collect_arch(value);
break;
case XLATOR_KW_WEAK:
if (m.mi_extended == 1) {
errlog(ERROR, "\"%s\", line %d: "
"Warning: Cannot use extends with a weak "
"interface",
m.mi_filename,
m.mi_line_number);
}
retval = SUCCESS_RC;
break;
default:
retval = ERROR_RC;
}
errlog(END, "}");
return (retval);
}
/*
* xlator_end_if -- called at the end of the interface, to trigger
* per-interface processing now entire thing has been seen.
*/
/*ARGSUSED*/
int
xlator_end_if(const Meta_info m, char const *value)
{
seterrline(LINE, m.mi_filename, "end", value);
errlog(BEGIN, "xlator_end_if() {");
if (symtab_get_skip() == YES) {
symtab_set_skip(NO);
Statistics.skips++;
} else {
generate_interface();
}
errlog(END, "}");
return (SUCCESS_RC);
}
/*
* xlator_endfile -- called at the end of the file, to trigger per-file
* processing.
*/
int
xlator_endfile(void)
{
errlog(BEGIN, "xlator_endfile() {");
generate_closedown();
errlog(END, "}");
return ((commit_code_file() == YES)? SUCCESS_RC: ERROR_RC);
}
/*
* xlator_endlib -- ditto, at the end of the library.
*/
int
xlator_endlib(void)
{
errlog(BEGIN, "xlator_endlib() {");
errlog(END, "}");
return (SUCCESS_RC);
}
/*
* xlator_end -- the end of the processing, called so translator
* can do cleanup, write makefiles, etc.
*/
int
xlator_end(void)
{
int rc = SUCCESS_RC;
errlog(BEGIN, "xlator_end() {");
rc += !generate_aux_file();
stats_report();
errlog(END, "}");
return (rc);
}
/*
** utilities for this layer/phase only.
*/
/*
* stats_init -- note what time it is...
*/
static void
stats_init(void)
{
Statistics.start = time(NULL);
}
/*
* stats_report -- say how much we just did
*/
#define max(a, b) (a > b)? a: b
static void
stats_report(void)
{
double seconds;
Statistics.end = time(NULL);
seconds = difftime(Statistics.end, Statistics.start);
switch (Verbose) {
default:
/*FALLTHROUGH*/
case 1:
(void) fprintf(stderr, "Statistics:\n"
" %d libraries\n %d files\n"
" %d interfaces\n %d lines\n"
" %d errors\n %d warnings\n"
" %d skips\n"
"in %.0f seconds, at %.1f lines/minute.\n",
Statistics.libraries, Statistics.files,
Statistics.interfaces, Statistics.lines,
Statistics.errors, Statistics.warnings,
Statistics.skips,
seconds, Statistics.lines*60.0/seconds);
break;
case 0:
if (Statistics.errors != 0 || Statistics.warnings != 0) {
(void) fprintf(stderr,
"spec2trace: %d errors %d warnings.\n",
Statistics.errors, Statistics.warnings);
}
break;
}
}
/*
* Tiny stats class...
*/
void
stats_add_warning(void)
{
Statistics.warnings++;
}
void
stats_add_error(void)
{
Statistics.errors++;
}
/*
* collect_includes -- collect a global list of include files,
* converting the comma- or space-separated input list into a
* structure for the database to store.
* As this can cause problems will ill-structured
* files, there is a mechanism to allow exclusion of
* certain files, (or certain combinations). At
* the moment, the mechanism is TBD, as is the second arg.
*/
/*ARGSUSED1*/
int
collect_include(char *p, int line)
{
char *include;
int len;
errlog(BEGIN, "collect_include() {");
if ((include = strtok(p, ", ")) != NULL) {
for (; include != NULL; include = strtok(NULL, ", ")) {
include = skipb(include);
/*
* Make sure the include file's name
* has legitimate C syntax - i.e. it's in double
* quotes or angle brackets.
*/
if (*include != '"' && *include != '<')
return (ERROR_RC);
len = strlen(include);
if (include[len-1] != '"' && include[len-1] != '>')
return (ERROR_RC);
/*
* If include filename syntax is OK, add it to
* the list
*/
symtab_add_includes(include);
}
}
errlog(END, "}");
return (SUCCESS_RC);
}
/*
* collect_binding -- take a binding and stuff it into the database
* in canonical form (with the word return in it).
*/
int
collect_binding(int const token, char *value, int line)
{
char *file = db_get_current_file();
errlog(BEGIN, "collect_binding() {");
errlog(VERBOSE, "name=\"%s\", value=\"%s\", line=%d\n",
Keywords[token-FIRST_TOKEN].key, value, line);
if (token == XLATOR_KW_EXCP) {
symtab_set_exception(value, line, file);
} else {
errlog(FATAL|INPUT, "programmer error: impossible binding.");
}
errlog(END, "}");
return (SUCCESS_RC);
}
/*
* collect_errval -- collect the error variable name (only)
* from the line. This is expected to be the first
* or only thing in a space- or comma-separated list.
* Collecting errno/errval possible value is left TBD.
*/
int
collect_errval(char *p, int line)
{
char *name;
errlog(BEGIN, "collect_errval() {");
name = strtok(p, " \t\n\r");
symtab_set_errval(name, line, db_get_current_file(), "int", "int", 0);
errlog(END, "}");
return (SUCCESS_RC);
}
/*
* collect_arch -- collect architecture.
*/
int
collect_arch(char *value)
{
char const *arch = db_get_arch();
char *buf, *p;
char *t;
errlog(BEGIN, "collect_arch() {");
if (value == 0 || *value == '\0')
errlog(FATAL|INPUT, "No architectures defined in ARCH line");
if ((buf = strdup(value)) == NULL)
errlog(FATAL, "Could not allocate memory in ARCH directive");
t = buf;
while ((p = strtok(t, " \r\t\n")) != NULL) {
if (strcmp(p, arch) == 0 || strcmp(p, "all") == 0)
goto cleanup;
t = NULL;
}
symtab_set_skip(YES);
cleanup:
free(buf);
return (SUCCESS_RC);
}
/*
* de_const -- get rid of const meta-types. This is actually a
* dodge to avoid writing a base-type function early in the
* process. This may turn into to_basetype() or to_primitivetype().
*/
static char *
de_const(char *type)
{
char *p, *q;
int i;
p = skipb(type);
q = strstr(type, "const");
if (q > p) {
for (i = 0; i < 5; i++) {
*q++ = '\0';
}
(void) sprintf(type, "%s%s", strnormalize(p), q);
return (type);
} else if (p == q) {
return (skipb(nextsep(p)));
} else {
return (type);
}
}
/*
* to_basetype -- convert a C type declaration into its base type and return
* the number of levels of indirection.
* Destructive and eats ``const''.
*/
static int
to_basetype(char *str)
{
char *p = str,
buffer[MAXLINE+1],
*q = &buffer[0];
int levels = 0;
assert(strlen(str) < MAXLINE, "string exceeded MAXLINE");
buffer[0] = NULL;
for (; *p != NULL; p++) {
switch (*p) {
case ' ': /* Convert spaces to single ' '. */
if (*(q-1) != ' ')
*q++ = ' ';
break;
case '*': /* Convert * to _P. */
if (*(q-1) != ' ')
*q++ = ' ';
levels++;
break;
case 'c': /* This might be a const */
if (strncmp(p, "const", 5) == 0) {
p += 4;
} else {
*q++ = *p;
}
break;
default:
/* Otherwise just copy. */
*q++ = *p;
break;
}
*q = NULL;
}
assert(q < &buffer[MAXLINE], "q fell off end of buffer");
q--;
while (*q == ' ') {
*q-- = NULL;
}
assert(strlen(buffer) < MAXLINE, "buffer length exceeded MAXLINE");
(void) strcpy(str, buffer);
return (levels);
}
/*
* to_actual -- create an actual-argument list for use
* when calling the function.
*/
static char *
to_actual(void)
{
ENTRY *p;
static char buffer[MAXLINE+1];
int n;
*buffer = NULL;
if ((p = symtab_get_first_arg()) != NULL) {
n = MAXLINE - snprintf(buffer, MAXLINE, "%s", name_of(p));
for (p = symtab_get_next_arg(); p != NULL;
p = symtab_get_next_arg()) {
if (*name_of(p) != NULL)
n -= snprintf(strend(buffer), n,
", %s", name_of(p));
}
}
return (buffer);
}
/*
* strpqcpy -- string copy that takes whatever begins with p and ends
* just before q.
*/
static char *
strpqcpy(char *target, char *p, char *q)
{
char saved;
saved = *q;
*q = NULL;
(void) strcpy(target, p);
*q = saved;
return (target);
}
#ifndef lint
int
breakpoint(void)
{
return (0);
}
#endif
int
collect_prototype(char *p, int line, int extcnt)
{
char f_type[BUFSIZ]; /* The function. */
char f_basetype[BUFSIZ];
char f_name[BUFSIZ];
char a_name[BUFSIZ]; /* The arguments. */
char a_basetype[BUFSIZ];
char a_type[BUFSIZ];
char *file = db_get_current_file();
char *interface = db_get_current_interface();
char *q;
char const *parse_err;
char tmp_proto[BUFSIZ], buf[BUFSIZ];
decl_t *pp, *funargs;
type_t *tp;
int levels, a_levels;
tmp_proto[BUFSIZ-1] = 0;
errlog(BEGIN, "collect_prototype() {");
if (p[strlen(p)-1] != ';')
(void) snprintf(tmp_proto, BUFSIZ, "%s;", p);
else
(void) snprintf(tmp_proto, BUFSIZ, "%s", p);
/* save prototype in symbol table */
symtab_set_prototype(p);
errlog(VERBOSE, "parsing prototype: %s\n", tmp_proto);
/* Parse Prototype */
if ((parse_err = decl_Parse(tmp_proto, &pp)) != NULL) {
errlog(FATAL|INPUT, "bad prototype: %s\n\t%s\n", parse_err, p);
}
if (extcnt == 0) {
char *dname = decl_GetName(pp);
if (strcmp(interface, dname) != 0)
errlog(FATAL|INPUT, "function and declaration"
" name mismatch\nfunction name = %s,"
" declaration name = %s\n", interface,
dname);
}
tp = decl_GetType(pp);
if (type_IsPtrFun(tp)) {
errlog(FATAL|INPUT, "function %s is declared as a data item"
" (pointer to function)\n", interface);
} else if (!type_IsFunction(tp)) {
errlog(FATAL|INPUT, "function %s is declared as a data item",
interface);
}
if (type_IsVarargs(tp)) {
symtab_set_skip(YES);
decl_Destroy(pp);
return (SUCCESS_RC);
}
decl_GetTraceInfo(pp, f_type, f_basetype, &funargs);
(void) sprintf(buf, "%s", strnormalize(f_type));
(void) strcpy(f_type, buf);
(void) sprintf(buf, "%s", strnormalize(f_basetype));
(void) strcpy(f_basetype, buf);
levels = to_basetype(f_basetype);
/* get interface name from 'Begin' line */
(void) strpqcpy(f_name, interface, nextsep(interface));
(void) decl_SetName(pp, f_name);
errlog(VERBOSE, "f_name=%s, f_basetype=%s, f_type=%s\n",
f_name, f_basetype, f_type);
symtab_set_function(f_name, line, file, f_type, f_basetype, levels);
db_add_print_types(f_basetype,
(q = de_const(type_of(symtab_get_function()))));
symtab_add_print_types(f_basetype, q);
/* args list */
while (funargs) {
(void) snprintf(a_type, BUFSIZ, "%s ",
strnormalize(declspec_ToString(buf, funargs->d_ds)));
(void) snprintf(a_basetype, BUFSIZ, "%s",
strnormalize(de_const(declspec_ToString(buf,
funargs->d_ds))));
tp = funargs->d_type;
for (a_levels = 0; tp; ) {
if (tp->t_dt == DD_PTR || tp->t_dt == DD_ARY) {
(void) strcat(a_type, "*");
a_levels++;
}
tp = tp->t_next;
}
/*
* XXX: This is a hack to work around bug in yacc parser
* "int foo(void)" prototypes get interpreted as having 1
* argument with the d_name of the argument being NULL.
*/
if (funargs->d_name) {
(void) snprintf(a_name, 20, "%s", funargs->d_name);
errlog(VERBOSE,
"a_name = %s, a_basetype = %s, a_type = %s\n",
a_name, a_basetype, a_type);
symtab_add_args(a_name, line, file,
a_type, a_basetype, a_levels);
db_add_print_types(a_basetype,
q = de_const(type_of(symtab_get_last_arg())));
symtab_add_print_types(a_basetype, q);
}
funargs = funargs->d_next;
}
symtab_set_formals(decl_ToFormal(pp));
symtab_set_actuals(to_actual());
symtab_set_cast(decl_ToString(buf, DTS_CAST, pp, NULL));
decl_Destroy(pp);
errlog(END, "}");
return (SUCCESS_RC);
}
/*
* generators
*/
/*
* generate_init -- prime the code generator as required.
*/
static void
generate_init(void)
{
errlog(BEGIN, "generate_init() {");
(void) fprintf(Headfp,
"/*\n"
" * Generated by spec2trace %s: do not edit this file.\n */\n\n",
TRACE_VERSION);
(void) fprintf(Headfp,
"#ifndef true\n"
"#define\ttrue 1\n"
"#define\tfalse 0\n"
"#endif\n\n"
"static char const *oparen = \"(\";\n"
"static char const *retstr = \" return = \";\n"
"static char const *errnostr = \" errno = \";\n"
"static char const *nilstr = \"<nil>\";\n"
"\n");
errlog(END, "}");
}
/*
* generate_interface -- call the two main parts of the per-interface
* code generation.
*/
static void
generate_interface(void)
{
ENTRY *function = symtab_get_function();
errlog(BEGIN, "generate_interface() {");
/* Check for required information. */
if (validity_of(function) == NO) {
symtab_set_skip(YES);
errlog(WARNING|INPUT, "no prototype for interface "
"it will be skipped");
errlog(END, "}");
return;
}
/* Generate the current interface 's print-functions declarations. */
generate_print_declarations(Bodyfp);
/* Generate the linkage part (a function and a struct */
generate_linkage(function);
/* Generate the actual interceptor. */
generate_interceptor(function);
errlog(END, "}");
}
/*
* generate_closedown -- produce includes.
*/
static void
generate_closedown(void)
{
errlog(BEGIN, "generate_closedown() {");
/* Print includes to primary file. */
generate_includes();
(void) putc('\n', Headfp);
errlog(END, "}");
}
/*
* generate_aux_file -- generate one additional .pf file with
* print-function pointers.
*/
static int
generate_aux_file(void)
{
FILE *fp;
char pathname[MAXLINE];
errlog(BEGIN, "generate_aux_file() {");
/* Open file */
(void) snprintf(pathname, sizeof (pathname), "%s.pf",
db_get_output_file());
errlog(TRACING, "output file = '%s'", pathname);
if ((fp = fopen(pathname, "w")) == NULL) {
errlog(FATAL, "%s: %s", pathname, strerror(errno));
}
/*
* Declare and initialize all print function pointers to null.
* Some spec files result in nothing being put into the .pf
* file. We must create the file since make(1) does not cope
* well with absent files that it expects to have built. So
* now the build gets empty compilation unit warnings... So
* we unconditionally create a static pointer.
*/
(void) fprintf(fp,
"/* Do not edit this file: it is a generated one. */\n\n"
"static char const *__abi_place_holder;\n\n");
generate_print_definitions(fp);
/* Close file */
if (fclose(fp) != 0) {
errlog(FATAL, "fclose %s: %s", pathname, strerror(errno));
}
errlog(END, "}");
return (YES);
}
/*
* generate_includes -- generate #includes to Headfp
*/
static void
generate_includes(void)
{
char *include;
errlog(BEGIN, "generate_includes() {");
errlog(TRACING, "includes=");
for (include = symtab_get_first_include(); include != NULL;
include = symtab_get_next_include())
(void) fprintf(Headfp, "#include %s\n", include);
(void) fprintf(Headfp, "\n#include <stdio.h>\n"
"#include <dlfcn.h>\n"
"#include <apptrace.h>\n\n");
errlog(TRACING, "\n");
errlog(END, "}");
}