1 /** \file report.c report function for noninteractive utilities
2  *
3  * For license terms, see the file COPYING in this directory.
4  *
5  * This code is distantly descended from the error.c module written by
6  * David MacKenzie <djm@gnu.ai.mit.edu>.  It was redesigned and
7  * rewritten by Dave Bodenstab, then redesigned again by ESR, then
8  * bludgeoned into submission for SunOS 4.1.3 by Chris Cheyney
9  * <cheyney@netcom.com>.  It works even when the return from
10  * vprintf(3) is unreliable.
11  */
12 
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16 #include <stdio.h>
17 #include <errno.h>
18 #include <string.h>
19 #if defined(HAVE_SYSLOG)
20 #include <syslog.h>
21 #endif
22 #include "i18n.h"
23 #include "fetchmail.h"
24 
25 #if defined(HAVE_VPRINTF) || defined(HAVE_DOPRNT) || defined(_LIBC) || defined(HAVE_STDARG_H)
26 # if HAVE_STDARG_H
27 #  include <stdarg.h>
28 #  define VA_START(args, lastarg) va_start(args, lastarg)
29 # else
30 #  include <varargs.h>
31 #  define VA_START(args, lastarg) va_start(args)
32 # endif
33 #else
34 # define va_alist a1, a2, a3, a4, a5, a6, a7, a8
35 # define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8;
36 #endif
37 
38 #define MALLOC(n)	xmalloc(n)
39 #define REALLOC(n,s)	xrealloc(n,s)
40 
41 /* Used by report_build() and report_complete() to accumulate partial messages.
42  */
43 static unsigned int partial_message_size = 0;
44 static unsigned int partial_message_size_used = 0;
45 static char *partial_message;
46 static int partial_suppress_tag = 0;
47 /* default size for the allocation of the report buffer */
48 const size_t defaultsize = 4096;
49 
50 static unsigned unbuffered;
51 static unsigned int use_syslog;
52 
53 #ifdef _LIBC
54 /* In the GNU C library, there is a predefined variable for this.  */
55 
56 # define program_name program_invocation_name
57 # include <errno.h>
58 
59 #else
60 
61 # if !HAVE_STRERROR && !defined(strerror)
strerror(int errnum)62 char *strerror (int errnum)
63 {
64     extern char *sys_errlist[];
65     extern int sys_nerr;
66 
67     if (errnum > 0 && errnum <= sys_nerr)
68 	return sys_errlist[errnum];
69     return GT_("Unknown system error");
70 }
71 # endif	/* HAVE_STRERROR */
72 #endif	/* _LIBC */
73 
74 /* Print the program name and error message MESSAGE, which is a printf-style
75    format string with optional args. */
76 /* VARARGS */
77 void
78 #ifdef HAVE_STDARG_H
report(FILE * errfp,const char * message,...)79 report (FILE *errfp, const char *message, ...)
80 #else
81 report (FILE *errfp, message, va_alist)
82      const char *message;
83      va_dcl
84 #endif
85 {
86 #ifdef VA_START
87     va_list args;
88 #endif
89 
90     /* If a partially built message exists, print it now so it's not lost.  */
91     if (partial_message_size_used != 0)
92     {
93 	partial_message_size_used = 0;
94 	report (errfp, GT_("%s (log message incomplete)\n"), partial_message);
95     }
96 
97 #if defined(HAVE_SYSLOG)
98     if (use_syslog)
99     {
100 	int priority;
101 
102 #ifdef VA_START
103 	VA_START (args, message);
104 #endif
105 	priority = (errfp == stderr) ? LOG_ERR : LOG_INFO;
106 
107 #ifdef HAVE_VSYSLOG
108 	vsyslog (priority, message, args);
109 #else
110 	{
111 	    char *a1 = va_arg(args, char *);
112 	    char *a2 = va_arg(args, char *);
113 	    char *a3 = va_arg(args, char *);
114 	    char *a4 = va_arg(args, char *);
115 	    char *a5 = va_arg(args, char *);
116 	    char *a6 = va_arg(args, char *);
117 	    char *a7 = va_arg(args, char *);
118 	    char *a8 = va_arg(args, char *);
119 	    syslog (priority, message, a1, a2, a3, a4, a5, a6, a7, a8);
120 	}
121 #endif
122 
123 #ifdef VA_START
124 	va_end(args);
125 #endif
126     }
127     else /* i. e. not using syslog */
128 #endif
129     {
130 	if ( *message == '\n' )
131 	{
132 	    fputc( '\n', errfp );
133 	    ++message;
134 	}
135 	if (!partial_suppress_tag)
136 		fprintf (errfp, "%s: ", program_name);
137 	partial_suppress_tag = 0;
138 
139 #ifdef VA_START
140 	VA_START (args, message);
141 # if defined(HAVE_VPRINTF) || defined(_LIBC)
142 	vfprintf (errfp, message, args);
143 # else
144 	_doprnt (message, args, errfp);
145 # endif
146 	va_end (args);
147 #else
148 	fprintf (errfp, message, a1, a2, a3, a4, a5, a6, a7, a8);
149 #endif
150 	fflush (errfp);
151     }
152 }
153 
154 /**
155  * Configure the report module. The output is set according to
156  * \a mode.
157  */
report_init(int mode)158 void report_init(int mode /** 0: regular output, 1: unbuffered output, -1: syslog */)
159 {
160     switch(mode)
161     {
162     case 0:			/* errfp, buffered */
163     default:
164 	unbuffered = FALSE;
165 	use_syslog = FALSE;
166 	break;
167 
168     case 1:			/* errfp, unbuffered */
169 	unbuffered = TRUE;
170 	use_syslog = FALSE;
171 	break;
172 
173 #ifdef HAVE_SYSLOG
174     case -1:			/* syslogd */
175 	unbuffered = FALSE;
176 	use_syslog = TRUE;
177 	break;
178 #endif /* HAVE_SYSLOG */
179     }
180 }
181 
rep_ensuresize(size_t increment)182 static void rep_ensuresize(size_t increment) {
183     if (partial_message_size == 0)
184     {
185 	/* initialization */
186 	partial_message_size_used = 0;
187 	/* avoid too many small allocations initially */
188 	if (increment < defaultsize) increment = defaultsize;
189 	partial_message_size = increment;
190 	partial_message = (char *)MALLOC (partial_message_size);
191     }
192     else /* already have buffer -> resize if too little room */
193     {
194 	if (increment < defaultsize) increment = defaultsize;
195 	if (partial_message_size - partial_message_size_used < increment)
196 	{
197 	    partial_message_size += increment;
198 	    partial_message = (char *)REALLOC (partial_message, partial_message_size);
199 	}
200     }
201 }
202 
203 /* Build an report message by appending MESSAGE, which is a printf-style
204    format string with optional args, to the existing report message (which may
205    be empty.)  The completed report message is finally printed (and reset to
206    empty) by calling report_complete().
207    If an intervening call to report() occurs when a partially constructed
208    message exists, then, in an attempt to keep the messages in their proper
209    sequence, the partial message will be printed as-is (with a trailing
210    newline) before report() prints its message. */
211 
212 
213 /* VARARGS */
214 #ifdef HAVE_STDARG_H
report_vgetsize(const char * message,va_list args)215 static int report_vgetsize(const char *message, va_list args)
216 {
217     char tmp[1];
218 
219     return vsnprintf(tmp, 1, message, args);
220 }
221 
222 /* note that report_vbuild assumes that the buffer was already allocated. */
223 /* VARARGS */
report_vbuild(const char * message,va_list args)224 static int report_vbuild(const char *message, va_list args)
225 {
226     int n;
227 
228     n = vsnprintf (partial_message + partial_message_size_used,
229 		   partial_message_size - partial_message_size_used,
230 		   message, args);
231 
232     /* output error, f. i. EILSEQ */
233     if (n < 0)
234 	    return -1;
235 
236     if (n > 0)
237     {
238 	partial_message_size_used += n;
239     }
240 
241     return n;
242 }
243 #endif
244 
245 void
246 #ifdef HAVE_STDARG_H
report_build(FILE * errfp,const char * message,...)247 report_build (FILE *errfp, const char *message, ...)
248 #else
249 report_build (FILE *errfp, message, va_alist)
250      const char *message;
251      va_dcl
252 #endif
253 {
254     int n;
255 #ifdef VA_START
256     va_list args;
257 #endif
258 
259 /* the logic is to first calculate the size,
260  * then reallocate, then fill the buffer
261  */
262 
263 #if defined(VA_START)
264     VA_START(args, message);
265     n = report_vgetsize(message, args);
266     va_end(args);
267 
268     rep_ensuresize(n + 1);
269 
270     VA_START(args, message);
271     (void)report_vbuild(message, args);
272     va_end(args);
273 #else
274     {
275 	char tmp[1];
276 	/* note that SUSv2 specifies that with the 2nd argument zero, an
277 	 * unspecified value less than 1 were to be returned. This is not
278 	 * useful, so pass 1. */
279 	n = snprintf (tmp, 1,
280 		      message, a1, a2, a3, a4, a5, a6, a7, a8);
281 
282 	if (n > 0)
283 	    rep_ensuresize(n + 1);
284     }
285 
286     n = snprintf (partial_message + partial_message_size_used,
287 		    partial_message_size - partial_message_size_used,
288 		    message, a1, a2, a3, a4, a5, a6, a7, a8);
289 
290     if (n > 0) partial_message_size_used += n;
291 
292 #endif
293 
294     if (unbuffered && partial_message_size_used != 0)
295     {
296 	partial_message_size_used = 0;
297 	fputs(partial_message, errfp);
298     }
299 }
300 
report_flush(FILE * errfp)301 void report_flush(FILE *errfp)
302 {
303     if (partial_message_size_used != 0)
304     {
305 	partial_message_size_used = 0;
306 	report(errfp, "%s", partial_message);
307 	partial_suppress_tag = 1;
308     }
309 }
310 
311 /* Complete a report message by appending MESSAGE, which is a printf-style
312    format string with optional args, to the existing report message (which may
313    be empty.)  The completed report message is then printed (and reset to
314    empty.) */
315 /* VARARGS */
316 void
317 #ifdef HAVE_STDARG_H
report_complete(FILE * errfp,const char * message,...)318 report_complete (FILE *errfp, const char *message, ...)
319 #else
320 report_complete (FILE *errfp, message, va_alist)
321      const char *message;
322      va_dcl
323 #endif
324 {
325     int n;
326 #ifdef VA_START
327     va_list args;
328 
329     VA_START(args, message);
330     n = report_vgetsize(message, args);
331     va_end(args);
332 
333     rep_ensuresize(n + 1);
334 
335     VA_START(args, message);
336     (void)report_vbuild(message, args);
337     va_end(args);
338 #else
339     report_build(errfp, message, a1, a2, a3, a4, a5, a6, a7, a8);
340 #endif
341 
342     /* Finally... print it.  */
343     partial_message_size_used = 0;
344 
345     if (unbuffered)
346     {
347 	fputs(partial_message, errfp);
348 	fflush (errfp);
349     }
350     else
351 	report(errfp, "%s", partial_message);
352 }
353 
354 /* Sometimes we want to have at most one error per line.  This
355    variable controls whether this mode is selected or not.  */
356 static int error_one_per_line;
357 
358 /* If errnum is nonzero, print its corresponding system error message. */
359 void
360 #ifdef HAVE_STDARG_H
report_at_line(FILE * errfp,int errnum,const char * file_name,unsigned int line_number,const char * message,...)361 report_at_line (FILE *errfp, int errnum, const char *file_name,
362 	       unsigned int line_number, const char *message, ...)
363 #else
364 report_at_line (FILE *errfp, errnum, file_name, line_number, message, va_alist)
365      int errnum;
366      const char *file_name;
367      unsigned int line_number;
368      const char *message;
369      va_dcl
370 #endif
371 {
372 #ifdef VA_START
373     va_list args;
374 #endif
375 
376     if (error_one_per_line)
377     {
378 	static const char *old_file_name;
379 	static unsigned int old_line_number;
380 
381 	if (old_line_number == line_number &&
382 	    (file_name == old_file_name || (old_file_name != NULL && 0 == strcmp (old_file_name, file_name))))
383 	    /* Simply return and print nothing.  */
384 	    return;
385 
386 	old_file_name = file_name;
387 	old_line_number = line_number;
388     }
389 
390     fflush (errfp);
391     if ( *message == '\n' )
392     {
393 	fputc( '\n', errfp );
394 	++message;
395     }
396     fprintf (errfp, "%s:", program_name);
397 
398     if (file_name != NULL)
399 	fprintf (errfp, "%s:%u: ", file_name, line_number);
400 
401 #ifdef VA_START
402     VA_START (args, message);
403 # if defined(HAVE_VPRINTF) || defined(_LIBC)
404     vfprintf (errfp, message, args);
405 # else
406     _doprnt (message, args, errfp);
407 # endif
408     va_end (args);
409 #else
410     fprintf (errfp, message, a1, a2, a3, a4, a5, a6, a7, a8);
411 #endif
412 
413     if (errnum)
414 	fprintf (errfp, ": %s", strerror (errnum));
415     putc ('\n', errfp);
416     fflush (errfp);
417 }
418