/* Error.c -- Copyright 1989, 1991, 1993, 1995, 1996 Liam R. E. Quin. * All Rights Reserved. * This code is NOT in the public domain. * See the file COPYRIGHT for full details. */ /* Error.c $Id: error.c,v 1.15 2001/05/31 03:50:13 liam Exp $ * */ #include #include "api.h" #include "globals.h" /** Unix/C functions that need to be declared **/ extern fflush(); extern fprintf(); #ifdef HAVE_STDLIB_H # include # else extern int exit(); extern void abort(); extern char *getenv(); #endif #ifndef fputc extern fputc(); #endif #define _ERROR_C /* so that we don't say "extern int errno" */ #include "error.h" #include "lqtrace.h" /* including lqtrace.h means that Erorr depends on liblqtext. * THIS IS A BAD THING, and I want to fix it. */ #ifdef NEEDERRNO int errno = 0; #else # include extern int sys_nerr; extern CONST char * CONST sys_errlist[]; /* Sys_errlist contains the error messages that correspond to the * various values of errno defined in , and possibly others. * * Unix defines sys_errlist to have at least sys_nerr entries. * errno.h also defines * extern int errno; * so we don't do it here. On Unix systems, errno holds the number * of the last error encountered in a system call or library function. * (it contains rubbish if no error occurred). */ #endif #define E_NOMALLOC 4096 /* This is a fake Severity, that works like E_MEMORY but without * printing "out of memory" */ /* Error() should use varargs and vprintf(), but I am not convinced that * vprintf() is sufficiently widely available. */ /* * Error * Error Handling * *

Prints an error message, treating the given format argument as a * printf-style format. The remaining arguments are optional, as * for printf.

*

The error message is prepended by the command name (using the * cmdname global variable, if set, or the value of the $CMDNAME * environment variable otherwise), the program name (using the * value of the global `progname', assigned by LQT_InitFromArgv from * argv[0] if not already set), and a string denoting the severity * of the error, as determined by the Severity argument.

*

The Severity argument is a combination using bitwise or of * the values defined in , of which the most commonly used * are as follows:

*

E_FATAL, which makes Error call exit and terminate the * program;

. *

E_WARN, * which makes Error print warning: , and does * not call exit;

*

E_BUG, used on an assertion failure or on detecting a severe * problem that should be caught by testing; if any trace flags are * set, E_BUG makes Error call abort to generate a core dump.

*

E_MEMORY; you should always include this if you think it might * not be safe to call malloc, for example because the heap is * corrupted or there is no more free memory.

*

E_SYS, which indicates a failed system or library call, and * makes Error print the corresponding system error message using * errno; be warned that on most systems, printf and other stdio * functions may cause errno to be set even when there is no error, * since they call isatty, which sets errno as a side-effect.

*

E_INTERNAL, which makes Error prepend the message with the * string internal error: ;

*

E_MULTILINE, which should be used on all lines of a multi-line * error message where Error is called multiple times; the last call * to Error in the sequence must include the E_LASTLINE flag;

*

E_LASTLINE, which is only ever used on the last of a sequence * of several successive calls to Error to build up a single message * that spans several lines; in the case of E_FATAL errors, it is * only on this call that Error will call exit, for example.

* * An embedded newline in a string will cause a core dump on some * systems. * Error appends a newline automatically, so the safest * thing to do is to omit the newline. *
*/ /*VARARGS2*/ void Error(Severity, format, a, b, c, d, e, f, g, h) unsigned int Severity; CONST char *format; int a, b, c, d, e, f, g, h; { extern char *cmdname; register char *p, *Start; int esav = errno; /* Esav holds the value of errno on entry, since library functions * such as printf() will (in general) alter errno. */ /* really could do with a way of telling if stdout is open! */ (void) fflush(stdout); (void) fflush(stderr); if (!format || !*format) { static char PanicBuf[100 + sizeof(__FILE__)]; /* Someone called Error() without giving a format, or (more likely) * forgot the Severity argument and did * Error("oh dear"); * so we have to invent a message that the user can report to * technical report (or that will help the programmer!) */ (void) sprintf(PanicBuf, "%s: Error(%d, 0) called with %s error message", __FILE__, Severity, (format == (char *) 0) ? "NULL" : "empty"); format = PanicBuf; Severity |= E_FATAL | E_INTERNAL | E_NOMALLOC; /* We also forbid malloc(), in case the programmer actually did * Error(E_WARN, strcpy(malloc(......)...)); */ } else if (Severity == 0) { /* You have to use one of the E_* values from error.h, none of * which are ever zero. This might mark a programmer doing * Error(!E_FATAL, ....) * but this is a mistake... * Since we have an actual error message (because we are in the * "else" branch), we'll print two error messages... */ Error(E_INTERNAL|E_WARN, "Error() called with Severity (arg 1) 0"); Severity |= (E_FATAL|E_INTERNAL|E_NOMALLOC); } /* Ensure that there is no newline at the end of the format passed to * fprintf, so that we can append a system error message if we so * choose: */ for (Start = p = format; *p; p++) { if (*p == '\n' || *p == '\r') { /* set newlines and carriage returns to nul: * TODO FIXME: this fails if there is a newline in * a constant string. */ do { *p = '\0'; ++p; } while (*p == '\r' || *p == '\n' || *p == '\f'); if (*p) { /* there are more lines to follow: */ Error(Severity|E_MULTILINE, Start, a, b, c, d, e, f, g, h ); Start = p; } else if (Start != format) { /* the last line of many */ Error(Severity|E_LASTLINE, Start, a, b, c, d, e, f, g, h ); return; } else { /* single line error message, * deal with it here */ break; } } } if (Start != format) { /* there were earlier message lines, * so this is the last (and it had no trailing \n): */ Severity |= E_LASTLINE; } if (!cmdname) { /* Shell scripts can do * CMDNAME="henry" * in order to make error messages print as * henry: thisprog: error: .... * * We only ever do the getenv() once, so later changes made with * putenv("CMDNAME", "new value") will not be reflected here. * This is intentional, as it means that error messages are less * likely to change diring a single run of the program! */ cmdname = getenv("CMDNAME"); if (cmdname && cmdname[0] == '\0') { cmdname = NULL; /* This can happen if someone does * CMDNAME="" * in the shell. */ } } /** Should really do this for each newline in the error message: **/ /* Note: in order to do this properly, we'd have to parse the * format string, handling each "%" individually, coping with "*" * (as in "%*.*d) correctly. We can't allocate a buffer with * malloc() if there was a MEMORY error, of course, and we have * no idea how large a fixed-size buffer to use. * This is because you could do * Error(E_WARN, "A %s b", "\n\n\n"); * if you wanted (or if a filename contained a newline). * However, that doesn't seem to matter much in practice. */ /** Print the error message! **/ /* First the command name */ if (cmdname) { (void) fprintf(stderr, "%s: ", cmdname); } /* Now the program name */ if (progname) { (void) fprintf(stderr, "%s: ", progname); } else { (void) fprintf(stderr, "[progname unset]: "); /* You could call this obnoxious, but the assumption is that * the programmer will catch this error (of not setting * progname) as soon as the first diagnostic is printed! */ } if (!(Severity & E_MULTILINE)) { if (Severity & E_INTERNAL) { (void) fprintf(stderr, "internal "); } if (Severity & E_FATAL) { (void) fprintf(stderr, "fatal error: "); } else { if (Severity & E_INTERNAL) { (void) fprintf(stderr, "error: "); /* Hence E_INTERNAL|E_WARN turns into "internal error: " */ } else { (void) fprintf(stderr, "warning: "); } } if (Severity & E_USAGE) { (void) fprintf(stderr, "usage: "); } if (Severity & E_MEMORY) { /* Apart from making programs that check malloc() a lot smaller * (they don't each have to say "out of memory"...), the E_MEMORY * flag can be used to inhibit a pop-up window on systems where * the window system calls malloc()... otherwise we would get * into a mess whilst trying to open the new window! * * This implementation does not use pop-up dialogue boxes, but * I have others that do, and that are compatible with this one * from the caller's point of view. * * (No I don't, I lost them. Oh dear.) */ (void) fprintf(stderr, "out of memory: "); } } /* Now call printf() to put out the actual message. */ (void) fprintf(stderr, format, a, b, c, d, e, f, g, h); /* NOTE: we can't report an error if stderr is broken... so * don't check the return value... */ if (Severity & E_SYS) { /* Print a Unix perror()-style error message if asked so to do. * We use the value of errno that we saved right at the start in * esav, in case fprintf() or getenv() or something clobbered errno. */ #ifdef NEEDERRNO switch (errno) { case 0: /* no error */ break; case ENOENT: (void) fprintf(stderr, " no such file or directory"); break; /** Add more values here if you implement them **/ default: (void) fprintf(stderr, " unknown system error %d", esav); errno = 0; break; } #else if (esav > 0 && esav < sys_nerr) { (void) fprintf(stderr, ": %s", sys_errlist[esav]); } else if (esav != 0) { (void) fprintf(stderr, " unknown system error %d", esav); } #endif } (void) fputc('\n', stderr); /* Finally, terminate the message. * TODO: check that the tty is in "onlcr" mode, and, if not, * put a \n\r at the start of the message and a \r at the end * NOTDONE FIXME */ if (Severity & E_XHINT) { if (cmdname && *cmdname) { (void) fprintf(stderr, "%s: ", cmdname); } if (progname && *progname) { (void) fprintf(stderr, "%s: use the -x option for an explanation\n", progname); } else { (void) fprintf(stderr, "use the -x option for an explanation\n"); } } if (!(Severity & E_MULTILINE) || (Severity & E_LASTLINE)) { if ((Severity & E_ABORT) == E_ABORT) { if ((Severity & E_BUG) == E_BUG) { if (LQT_TraceFlagsSet(LQTRACE_ABORT_ON_ERROR)) { fprintf(stderr, "%s: generating core dump for debugging.\n", progname); (void) fflush(stderr); (void) fflush(stdout); abort(); } else { exit(1); } } else { fprintf(stderr, "%s: abort: generating core dump for debugging.\n", progname ); (void) fflush(stderr); (void) fflush(stdout); abort(); } } if (Severity & E_FATAL) { exit(1); } } (void) fflush(stdout); } #ifdef NEEDERRNO /* Define fake open and fopen routines that set errno for a better error * message. Another way would be to have them call Error() directly, but * that would break code that tested return values. * * Note that open and fopen are defined in error.h to be _e_open and _e_fopen, * so we are not really defining open() and fopen() here at all. If you * get a message from the loader about __e_open being multiply defined, * or being defined more than once, you should edit error.h and change those * names to something else (perhaps Prisilla and Gertrude) and recompile * everything that uses error.h yourself. */ int open(filename, mode, flags) char *filename; int mode; int flags; { register int Result; #undef open /* so we can call the real one */ if ((Result = open(filename, mode, flags)) >= 0) { errno = 0; /* Unix does not set errno in this case, but we might as well */ return Result; } else { /* if there is some other way of determining the error, it would * be worth doing here... */ errno = ENOENT; /* No entry in the directory -- i.e., file does not exist */ return Result; } } FILE * fopen(filename, mode) char *filename; char *mode; { register FILE *Result; errno = 0; #undef fopen /* so we can call the real one */ if ((Result = fopen(filename, mode)) != (FILE *) 0) { return Result; } /* if there is some other way of determining the error, it would * be worth doing here... */ errno = ENOENT; /* No entry in the directory: i.e., file does not exist */ return Result; } #endif /*NEEDERRNO*/