1/* error.c -- error handler for noninteractive utilities
2   Copyright (C) 1990-1992 Free Software Foundation, Inc.
3
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License as published by
6   the Free Software Foundation; either version 2, or (at your option)
7   any later version.
8
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.  */
13
14/* David MacKenzie */
15/* Brian Berliner added support for CVS */
16
17#include "cvs.h"
18#include "vasnprintf.h"
19
20/* Out of memory errors which could not be forwarded to the client are sent to
21 * the syslog when it is available.
22 */
23#ifdef HAVE_SYSLOG_H
24# include <syslog.h>
25# ifndef LOG_DAEMON   /* for ancient syslogs */
26#   define LOG_DAEMON 0
27# endif
28#endif /* HAVE_SYSLOG_H */
29
30
31
32/* If non-zero, error will use the CVS protocol to stdout to report error
33 * messages.  This will only be set in the CVS server parent process.
34 *
35 * Most other code is run via do_cvs_command, which forks off a child
36 * process and packages up its stderr in the protocol.
37 */
38int error_use_protocol;
39
40#ifndef strerror
41extern char *strerror (int);
42#endif
43
44
45
46/* Print the program name and error message MESSAGE, which is a printf-style
47 * format string with optional args, like:
48 *
49 *   PROGRAM_NAME CVS_CMD_NAME: MESSAGE: ERRNUM
50 *
51 * or, when STATUS is non-zero:
52 *
53 *   PROGRAM_NAME [CVS_CMD_NAME aborted]: MESSAGE: ERRNUM
54 *
55 * CVS_CMD_NAME & ERRMSG may or may not appear in the output (the `:' before
56 * ERRMSG will disappear as well when ERRNUM is not present).  ERRMSG
57 * represents the system dependent message returned by strerror (ERRNUM), when
58 * ERRNUM is non-zero.
59 *
60 * Exit with status EXIT_FAILURE if STATUS is nonzero.
61 *
62 * If this function fails to get any memory it might request, it attempts to
63 * log a "memory exhausted" message to the syslog, when syslog is available,
64 * without any further attempts to allocate memory, before exiting.  See NOTES
65 * below for more information on this functions memory allocation.
66 *
67 * INPUTS
68 *   status	When non-zero, exit with EXIT_FAILURE rather than returning.
69 *   errnum	When non-zero, interpret as global ERRNO for the purpose of
70 *		generating additional error text.
71 *   message	A printf style format string.
72 *   ...	Variable number of args, as printf.
73 *
74 * GLOBALS
75 *   program_name	The name of this executable, for the output message.
76 *   cvs_cmd_name	Output in the error message, when it exists.
77 *   errno		Accessed simply to save and restore it before
78 *			returning.
79 *
80 * NOTES
81 *   This function goes to fairly great lengths to avoid allocating memory so
82 *   that it can relay out-of-memory error messages to the client.  Any error
83 *   messages which fit in under 256 characters (after expanding MESSAGE with
84 *   ARGS but before adding any ERRNUM text) should not require memory
85 *   allocation before they are sent on to cvs_outerr().  Unfortunately,
86 *   cvs_outerr() and the buffer functions it uses to send messages to the
87 *   client still don't make this same sort of effort, so in local mode
88 *   out-of-memory errors will probably get printed properly to stderr but if a
89 *   memory outage happens on the server, the admin will need to consult the
90 *   syslog to find out what went wrong.
91 *
92 *   I think this is largely cleaned up to the point where it does the right
93 *   thing for the server, whether the normal server_active (child process)
94 *   case or the error_use_protocol (parent process) case.  The one exception
95 *   is that STATUS nonzero for error_use_protocol probably doesn't work yet;
96 *   in that case still need to use the pending_error machinery in server.c.
97 *
98 *   error() does not molest errno; some code (e.g. Entries_Open) depends
99 *   on being able to say something like:
100 *
101 *      error (0, 0, "foo");
102 *      error (0, errno, "bar");
103 *
104 * RETURNS
105 *   Sometimes.  ;)
106 */
107void
108error (int status, int errnum, const char *message, ...)
109{
110    va_list args;
111    int save_errno = errno;
112
113    /* Various buffers we attempt to use to generate the error message.  */
114    char statbuf[256];
115    char *buf;
116    size_t length;
117    char statbuf2[384];
118    char *buf2;
119    char statcmdbuf[32];
120    char *cmdbuf;
121    char *emptybuf = "";
122
123    static const char *last_message = NULL;
124    static int last_status;
125    static int last_errnum;
126
127    /* Initialize these to avoid a lot of special case error handling.  */
128    buf = statbuf;
129    buf2 = statbuf2;
130    cmdbuf = emptybuf;
131
132    /* Expand the message the user passed us.  */
133    length = sizeof (statbuf);
134    va_start (args, message);
135    buf = vasnprintf (statbuf, &length, message, args);
136    va_end (args);
137    if (!buf) goto memerror;
138
139    /* Expand the cvs commmand name to <cmd> or [<cmd> aborted].
140     *
141     * I could squeeze this into the buf2 printf below, but this makes the code
142     * easier to read and I don't think error messages are printed often enough
143     * to make this a major performance hit.  I think the memory cost is about
144     * 40 bytes.
145     */
146    if (cvs_cmd_name)
147    {
148	length = sizeof (statcmdbuf);
149	cmdbuf = asnprintf (statcmdbuf, &length, " %s%s%s",
150			    status ? "[" : "",
151			    cvs_cmd_name,
152			    status ? " aborted]" : "");
153	/* Else cmdbuf still = emptybuf.  */
154	if (!cmdbuf) goto memerror;
155    }
156    /* Else cmdbuf still = emptybuf.  */
157
158    /* Now put it all together.  */
159    length = sizeof (statbuf2);
160    buf2 = asnprintf (statbuf2, &length, "%s%s: %s%s%s\n",
161                      program_name, cmdbuf, buf,
162                      errnum ? ": " : "", errnum ? strerror (errnum) : "");
163    if (!buf2) goto memerror;
164
165    /* Send the final message to the client or log it.
166     *
167     * Set this recursion block first since this is the only function called
168     * here which can cause error() to be called a second time.
169     */
170    if (last_message) goto recursion_error;
171    last_message = buf2;
172    last_status = status;
173    last_errnum = errnum;
174    cvs_outerr (buf2, length);
175
176    /* Reset our recursion lock.  This needs to be done before the call to
177     * exit() to allow the exit handlers to make calls to error().
178     */
179    last_message = NULL;
180
181    /* Done, if we're exiting.  */
182    if (status)
183	exit (EXIT_FAILURE);
184
185    /* Free anything we may have allocated.  */
186    if (buf != statbuf) free (buf);
187    if (buf2 != statbuf2) free (buf2);
188    if (cmdbuf != statcmdbuf && cmdbuf != emptybuf) free (cmdbuf);
189
190    /* Restore errno per our charter.  */
191    errno = save_errno;
192
193    /* Done.  */
194    return;
195
196memerror:
197    /* Make one last attempt to log the problem in the syslog since that
198     * should not require new memory, then abort.
199     *
200     * No second attempt is made to send the information to the client - if we
201     * got here then that already failed once and this prevents us from
202     * entering an infinite loop.
203     *
204     * FIXME
205     *   If the buffer routines can be altered in such a way that a single,
206     *   short, statically allocated message could be sent without needing to
207     *   allocate new memory, then it would still be safe to call cvs_outerr
208     *   with the message here.
209     */
210#if HAVE_SYSLOG_H
211    syslog (LOG_DAEMON | LOG_EMERG, "Memory exhausted.  Aborting.");
212#endif /* HAVE_SYSLOG_H */
213
214    goto sidestep_done;
215
216recursion_error:
217#if HAVE_SYSLOG_H
218    /* Syslog the problem since recursion probably means that we encountered an
219     * error while attempting to send the last error message to the client.
220     */
221
222    syslog (LOG_DAEMON | LOG_EMERG,
223	    "error (%d, %d) called recursively.  Original message was:",
224	    last_status, last_errnum);
225    syslog (LOG_DAEMON | LOG_EMERG, "%s", last_message);
226
227
228    syslog (LOG_DAEMON | LOG_EMERG,
229            "error (%d, %d) called recursively.  Second message was:",
230	    status, errnum);
231    syslog (LOG_DAEMON | LOG_EMERG, "%s", buf2);
232
233    syslog (LOG_DAEMON | LOG_EMERG, "Aborting.");
234#endif /* HAVE_SYSLOG_H */
235
236sidestep_done:
237    /* Reset our recursion lock.  This needs to be done before the call to
238     * exit() to allow the exit handlers to make calls to error().
239     */
240    last_message = NULL;
241
242    exit (EXIT_FAILURE);
243}
244
245
246
247/* Print the program name and error message MESSAGE, which is a printf-style
248   format string with optional args to the file specified by FP.
249   If ERRNUM is nonzero, print its corresponding system error message.
250   Exit with status EXIT_FAILURE if STATUS is nonzero.  */
251/* VARARGS */
252void
253fperrmsg (FILE *fp, int status, int errnum, char *message, ...)
254{
255    va_list args;
256
257    fprintf (fp, "%s: ", program_name);
258    va_start (args, message);
259    vfprintf (fp, message, args);
260    va_end (args);
261    if (errnum)
262	fprintf (fp, ": %s", strerror (errnum));
263    putc ('\n', fp);
264    fflush (fp);
265    if (status)
266	exit (EXIT_FAILURE);
267}
268