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