diff options
Diffstat (limited to 'src/error.c')
-rw-r--r-- | src/error.c | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/src/error.c b/src/error.c new file mode 100644 index 0000000..7f355bc --- /dev/null +++ b/src/error.c @@ -0,0 +1,267 @@ +/* error.c -- error handler for noninteractive utilities + Copyright (C) 1990-1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. */ + +/* David MacKenzie */ +/* Brian Berliner added support for CVS */ + +#include "cvs.h" +#include "vasnprintf.h" + +/* Out of memory errors which could not be forwarded to the client are sent to + * the syslog when it is available. + */ +#ifdef HAVE_SYSLOG_H +# include <syslog.h> +# ifndef LOG_DAEMON /* for ancient syslogs */ +# define LOG_DAEMON 0 +# endif +#endif /* HAVE_SYSLOG_H */ + + + +/* If non-zero, error will use the CVS protocol to stdout to report error + * messages. This will only be set in the CVS server parent process. + * + * Most other code is run via do_cvs_command, which forks off a child + * process and packages up its stderr in the protocol. + */ +int error_use_protocol; + +#ifndef strerror +extern char *strerror (int); +#endif + + + +/* Print the program name and error message MESSAGE, which is a printf-style + * format string with optional args, like: + * + * PROGRAM_NAME CVS_CMD_NAME: MESSAGE: ERRNUM + * + * or, when STATUS is non-zero: + * + * PROGRAM_NAME [CVS_CMD_NAME aborted]: MESSAGE: ERRNUM + * + * CVS_CMD_NAME & ERRMSG may or may not appear in the output (the `:' before + * ERRMSG will disappear as well when ERRNUM is not present). ERRMSG + * represents the system dependent message returned by strerror (ERRNUM), when + * ERRNUM is non-zero. + * + * Exit with status EXIT_FAILURE if STATUS is nonzero. + * + * If this function fails to get any memory it might request, it attempts to + * log a "memory exhausted" message to the syslog, when syslog is available, + * without any further attempts to allocate memory, before exiting. See NOTES + * below for more information on this functions memory allocation. + * + * INPUTS + * status When non-zero, exit with EXIT_FAILURE rather than returning. + * errnum When non-zero, interpret as global ERRNO for the purpose of + * generating additional error text. + * message A printf style format string. + * ... Variable number of args, as printf. + * + * GLOBALS + * program_name The name of this executable, for the output message. + * cvs_cmd_name Output in the error message, when it exists. + * errno Accessed simply to save and restore it before + * returning. + * + * NOTES + * This function goes to fairly great lengths to avoid allocating memory so + * that it can relay out-of-memory error messages to the client. Any error + * messages which fit in under 256 characters (after expanding MESSAGE with + * ARGS but before adding any ERRNUM text) should not require memory + * allocation before they are sent on to cvs_outerr(). Unfortunately, + * cvs_outerr() and the buffer functions it uses to send messages to the + * client still don't make this same sort of effort, so in local mode + * out-of-memory errors will probably get printed properly to stderr but if a + * memory outage happens on the server, the admin will need to consult the + * syslog to find out what went wrong. + * + * I think this is largely cleaned up to the point where it does the right + * thing for the server, whether the normal server_active (child process) + * case or the error_use_protocol (parent process) case. The one exception + * is that STATUS nonzero for error_use_protocol probably doesn't work yet; + * in that case still need to use the pending_error machinery in server.c. + * + * error() does not molest errno; some code (e.g. Entries_Open) depends + * on being able to say something like: + * + * error (0, 0, "foo"); + * error (0, errno, "bar"); + * + * RETURNS + * Sometimes. ;) + */ +void +error (int status, int errnum, const char *message, ...) +{ + va_list args; + int save_errno = errno; + + /* Various buffers we attempt to use to generate the error message. */ + char statbuf[256]; + char *buf; + size_t length; + char statbuf2[384]; + char *buf2; + char statcmdbuf[32]; + char *cmdbuf; + char *emptybuf = ""; + + static const char *last_message = NULL; + static int last_status; + static int last_errnum; + + /* Initialize these to avoid a lot of special case error handling. */ + buf = statbuf; + buf2 = statbuf2; + cmdbuf = emptybuf; + + /* Expand the message the user passed us. */ + length = sizeof (statbuf); + va_start (args, message); + buf = vasnprintf (statbuf, &length, message, args); + va_end (args); + if (!buf) goto memerror; + + /* Expand the cvs commmand name to <cmd> or [<cmd> aborted]. + * + * I could squeeze this into the buf2 printf below, but this makes the code + * easier to read and I don't think error messages are printed often enough + * to make this a major performance hit. I think the memory cost is about + * 40 bytes. + */ + if (cvs_cmd_name) + { + length = sizeof (statcmdbuf); + cmdbuf = asnprintf (statcmdbuf, &length, " %s%s%s", + status ? "[" : "", + cvs_cmd_name, + status ? " aborted]" : ""); + /* Else cmdbuf still = emptybuf. */ + if (!cmdbuf) goto memerror; + } + /* Else cmdbuf still = emptybuf. */ + + /* Now put it all together. */ + length = sizeof (statbuf2); + buf2 = asnprintf (statbuf2, &length, "%s%s: %s%s%s\n", + program_name, cmdbuf, buf, + errnum ? ": " : "", errnum ? strerror (errnum) : ""); + if (!buf2) goto memerror; + + /* Send the final message to the client or log it. + * + * Set this recursion block first since this is the only function called + * here which can cause error() to be called a second time. + */ + if (last_message) goto recursion_error; + last_message = buf2; + last_status = status; + last_errnum = errnum; + cvs_outerr (buf2, length); + + /* Reset our recursion lock. This needs to be done before the call to + * exit() to allow the exit handlers to make calls to error(). + */ + last_message = NULL; + + /* Done, if we're exiting. */ + if (status) + exit (EXIT_FAILURE); + + /* Free anything we may have allocated. */ + if (buf != statbuf) free (buf); + if (buf2 != statbuf2) free (buf2); + if (cmdbuf != statcmdbuf && cmdbuf != emptybuf) free (cmdbuf); + + /* Restore errno per our charter. */ + errno = save_errno; + + /* Done. */ + return; + +memerror: + /* Make one last attempt to log the problem in the syslog since that + * should not require new memory, then abort. + * + * No second attempt is made to send the information to the client - if we + * got here then that already failed once and this prevents us from + * entering an infinite loop. + * + * FIXME + * If the buffer routines can be altered in such a way that a single, + * short, statically allocated message could be sent without needing to + * allocate new memory, then it would still be safe to call cvs_outerr + * with the message here. + */ +#if HAVE_SYSLOG_H + syslog (LOG_DAEMON | LOG_EMERG, "Memory exhausted. Aborting."); +#endif /* HAVE_SYSLOG_H */ + + goto sidestep_done; + +recursion_error: +#if HAVE_SYSLOG_H + /* Syslog the problem since recursion probably means that we encountered an + * error while attempting to send the last error message to the client. + */ + + syslog (LOG_DAEMON | LOG_EMERG, + "error (%d, %d) called recursively. Original message was:", + last_status, last_errnum); + syslog (LOG_DAEMON | LOG_EMERG, "%s", last_message); + + + syslog (LOG_DAEMON | LOG_EMERG, + "error (%d, %d) called recursively. Second message was:", + status, errnum); + syslog (LOG_DAEMON | LOG_EMERG, "%s", buf2); + + syslog (LOG_DAEMON | LOG_EMERG, "Aborting."); +#endif /* HAVE_SYSLOG_H */ + +sidestep_done: + /* Reset our recursion lock. This needs to be done before the call to + * exit() to allow the exit handlers to make calls to error(). + */ + last_message = NULL; + + exit (EXIT_FAILURE); +} + + + +/* Print the program name and error message MESSAGE, which is a printf-style + format string with optional args to the file specified by FP. + If ERRNUM is nonzero, print its corresponding system error message. + Exit with status EXIT_FAILURE if STATUS is nonzero. */ +/* VARARGS */ +void +fperrmsg (FILE *fp, int status, int errnum, char *message, ...) +{ + va_list args; + + fprintf (fp, "%s: ", program_name); + va_start (args, message); + vfprintf (fp, message, args); + va_end (args); + if (errnum) + fprintf (fp, ": %s", strerror (errnum)); + putc ('\n', fp); + fflush (fp); + if (status) + exit (EXIT_FAILURE); +} |