1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
|
/* 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. */
#include <sys/cdefs.h>
__RCSID("$NetBSD: error.c,v 1.3 2016/05/17 14:00:09 christos Exp $");
/* 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_ERR, "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_ERR,
"error (%d, %d) called recursively. Original message was:",
last_status, last_errnum);
syslog (LOG_DAEMON | LOG_ERR, "%s", last_message);
syslog (LOG_DAEMON | LOG_ERR,
"error (%d, %d) called recursively. Second message was:",
status, errnum);
syslog (LOG_DAEMON | LOG_ERR, "%s", buf2);
syslog (LOG_DAEMON | LOG_ERR, "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);
}
|