1 |
/* Logging routines. |
2 |
* |
3 |
* IRC Services is copyright (c) 1996-2009 Andrew Church. |
4 |
* E-mail: <achurch@achurch.org> |
5 |
* Parts written by Andrew Kempe and others. |
6 |
* This program is free but copyrighted software; see the file GPL.txt for |
7 |
* details. |
8 |
*/ |
9 |
|
10 |
#include "services.h" |
11 |
#include <netdb.h> /* for hstrerror() */ |
12 |
|
13 |
/* Pattern for generating logfile names */ |
14 |
static char logfile_pattern[PATH_MAX+1]; |
15 |
|
16 |
/* Currently open log file */ |
17 |
static FILE *logfile; |
18 |
static char current_filename[PATH_MAX+1]; |
19 |
|
20 |
/* Memory log buffer (see open_memory_log()) */ |
21 |
static char logmem[LOGMEMSIZE]; |
22 |
static char *logmemptr = NULL; |
23 |
|
24 |
/* Are we in fatal() or fatal_perror()? (This is used to avoid infinite |
25 |
* recursion if wallops() does a fatal().) */ |
26 |
static int in_fatal = 0; |
27 |
|
28 |
/*************************************************************************/ |
29 |
/*************************************************************************/ |
30 |
|
31 |
/* Local routine to generate a filename from a filename pattern (possibly |
32 |
* containing %y/%m/%d for year/month/day). Result is returned in a static |
33 |
* buffer, and will not be longer than PATH_MAX characters. |
34 |
*/ |
35 |
|
36 |
static char *gen_log_filename(void) |
37 |
{ |
38 |
static char result[PATH_MAX+1]; |
39 |
const char *s, *from; |
40 |
char *to; |
41 |
|
42 |
time_t now = time(NULL); |
43 |
struct tm *tm = localtime(&now); |
44 |
tm->tm_year += 1900; |
45 |
tm->tm_mon++; |
46 |
|
47 |
from = LogFilename; |
48 |
if (!*from) { |
49 |
*result = 0; |
50 |
return result; |
51 |
} |
52 |
to = result; |
53 |
|
54 |
while ((s = strchr(from, '%')) != NULL) { |
55 |
to += snprintf(to, sizeof(result)-(to-result), "%.*s", s-from, from); |
56 |
s++; |
57 |
switch (*s) { |
58 |
case 'y': |
59 |
to += snprintf(to, sizeof(result)-(to-result), "%d", tm->tm_year); |
60 |
break; |
61 |
case 'm': |
62 |
to += snprintf(to, sizeof(result)-(to-result), "%02d", tm->tm_mon); |
63 |
break; |
64 |
case 'd': |
65 |
to += snprintf(to, sizeof(result)-(to-result), "%02d",tm->tm_mday); |
66 |
break; |
67 |
default: |
68 |
if (to-result < sizeof(result)-1) |
69 |
*to++ = *s; |
70 |
break; |
71 |
} |
72 |
from = s+1; |
73 |
} |
74 |
to += snprintf(to, sizeof(result)-(to-result), "%s", from); |
75 |
|
76 |
*to = 0; |
77 |
return result; |
78 |
} |
79 |
|
80 |
/*************************************************************************/ |
81 |
|
82 |
/* Local routine to check whether the log file needs to be rotated, and |
83 |
* rotate it if so. Assumes the log file is already open. |
84 |
*/ |
85 |
|
86 |
static void check_log_rotate(void) |
87 |
{ |
88 |
char *newname = gen_log_filename(); |
89 |
if (strlen(newname) > sizeof(current_filename)-1) |
90 |
newname[sizeof(current_filename)-1] = 0; |
91 |
if (strcmp(current_filename, gen_log_filename()) != 0) { |
92 |
if (!reopen_log()) |
93 |
log("Warning: Unable to rotate log file: %s", strerror(errno)); |
94 |
} |
95 |
} |
96 |
|
97 |
/*************************************************************************/ |
98 |
/*************************************************************************/ |
99 |
|
100 |
/* Local routines to write text to the log file and/or stderr as needed. */ |
101 |
|
102 |
static void vlogprintf(const char *fmt, va_list args) |
103 |
{ |
104 |
if (nofork) { |
105 |
va_list args_copy; |
106 |
va_copy(args_copy, args); |
107 |
vfprintf(stderr, fmt, args_copy); |
108 |
va_end(args_copy); |
109 |
} |
110 |
if (logfile) { |
111 |
vfprintf(logfile, fmt, args); |
112 |
} else if (logmemptr) { |
113 |
char tmpbuf[BUFSIZE]; |
114 |
int len = vsnprintf(tmpbuf, sizeof(tmpbuf), fmt, args); |
115 |
if (len > LOGMEMSIZE - (logmemptr-logmem)) { |
116 |
int oldlen = len; |
117 |
len = LOGMEMSIZE - (logmemptr-logmem); |
118 |
if (len > 0) { |
119 |
if (tmpbuf[oldlen-1] == '\n') { |
120 |
tmpbuf[len-1] = '\n'; /* always end with a newline */ |
121 |
} else { |
122 |
len--; |
123 |
} |
124 |
} |
125 |
} |
126 |
if (len > 0) { |
127 |
memcpy(logmemptr, tmpbuf, len); |
128 |
logmemptr += len; |
129 |
} |
130 |
} |
131 |
} |
132 |
|
133 |
static void logprintf(const char *fmt, ...) FORMAT(printf,1,2); |
134 |
static void logprintf(const char *fmt, ...) |
135 |
{ |
136 |
va_list args; |
137 |
va_start(args, fmt); |
138 |
vlogprintf(fmt, args); |
139 |
va_end(args); |
140 |
} |
141 |
|
142 |
static void logputs(const char *str) |
143 |
{ |
144 |
logprintf("%s", str); |
145 |
} |
146 |
|
147 |
/*************************************************************************/ |
148 |
|
149 |
/* Local routine to write the time of day to the log. */ |
150 |
|
151 |
static void write_time(void) |
152 |
{ |
153 |
time_t t; |
154 |
struct tm tm; |
155 |
char buf[256]; |
156 |
|
157 |
time(&t); |
158 |
tm = *localtime(&t); |
159 |
#if HAVE_GETTIMEOFDAY |
160 |
if (debug) { |
161 |
char *s; |
162 |
struct timeval tv; |
163 |
gettimeofday(&tv, NULL); |
164 |
strftime(buf, sizeof(buf)-1, "[%b %d %H:%M:%S", &tm); |
165 |
s = buf + strlen(buf); |
166 |
s += snprintf(s, sizeof(buf)-(s-buf), ".%06d", (int)tv.tv_usec); |
167 |
strftime(s, sizeof(buf)-(s-buf)-1, " %Y] ", &tm); |
168 |
} else { |
169 |
#endif |
170 |
strftime(buf, sizeof(buf)-1, "[%b %d %H:%M:%S %Y] ", &tm); |
171 |
#if HAVE_GETTIMEOFDAY |
172 |
} |
173 |
#endif |
174 |
logputs(buf); |
175 |
} |
176 |
|
177 |
/*************************************************************************/ |
178 |
/*************************************************************************/ |
179 |
|
180 |
/* Set the filename pattern to use for generating the log filename. */ |
181 |
|
182 |
void set_logfile(const char *pattern) |
183 |
{ |
184 |
if (pattern) { /* Just to be safe */ |
185 |
strscpy(logfile_pattern, pattern, sizeof(logfile_pattern)); |
186 |
} |
187 |
} |
188 |
|
189 |
/*************************************************************************/ |
190 |
|
191 |
/* Open the log file. Return zero if the log file could not be opened, |
192 |
* else return nonzero (success). |
193 |
*/ |
194 |
|
195 |
int open_log(void) |
196 |
{ |
197 |
if (logfile) |
198 |
return 1; |
199 |
strbcpy(current_filename, gen_log_filename()); |
200 |
logfile = fopen(current_filename, "a"); |
201 |
if (logfile) { |
202 |
setbuf(logfile, NULL); |
203 |
if (logmemptr) { |
204 |
int res, errno_save; |
205 |
if (logmemptr > logmem) { |
206 |
res = fwrite(logmem, logmemptr-logmem, 1, logfile); |
207 |
errno_save = errno; |
208 |
} else { |
209 |
res = 1; /* i.e. no error */ |
210 |
#if CLEAN_COMPILE |
211 |
errno_save = 0; /* warning killer for dumb compilers */ |
212 |
#endif |
213 |
} |
214 |
logmemptr = NULL; |
215 |
if (res != 1) { |
216 |
/* make sure this is AFTER the memory log has been closed! */ |
217 |
errno = errno_save; |
218 |
log_perror("open_log(): error writing memory log"); |
219 |
} |
220 |
} |
221 |
} |
222 |
return logfile!=NULL ? 1 : 0; |
223 |
} |
224 |
|
225 |
/*************************************************************************/ |
226 |
|
227 |
/* Open a virtual log file in memory. The contents of the memory log file |
228 |
* will be written to a physical log file when open_log() is called and |
229 |
* executes successfully. If a physical log file is already open, does |
230 |
* nothing. Always succeeds (and returns nonzero). |
231 |
*/ |
232 |
|
233 |
int open_memory_log(void) |
234 |
{ |
235 |
if (logfile || logmemptr) |
236 |
return 1; |
237 |
logmemptr = logmem; |
238 |
return 1; |
239 |
} |
240 |
|
241 |
/*************************************************************************/ |
242 |
|
243 |
/* Close the log file. */ |
244 |
|
245 |
void close_log(void) |
246 |
{ |
247 |
if (logmemptr) { |
248 |
logmemptr = NULL; |
249 |
} |
250 |
if (logfile) { |
251 |
fclose(logfile); |
252 |
logfile = NULL; |
253 |
} |
254 |
} |
255 |
|
256 |
/*************************************************************************/ |
257 |
|
258 |
/* Reopen the log file with the current value of LogFilename (for use when |
259 |
* rehashing configuration files or rotating the log file). Return value |
260 |
* is like open_log(). |
261 |
*/ |
262 |
|
263 |
int reopen_log(void) |
264 |
{ |
265 |
char *newname; |
266 |
FILE *f; |
267 |
|
268 |
newname = gen_log_filename(); |
269 |
/* Make sure it will fit in current_filename later */ |
270 |
if (strlen(newname) > sizeof(current_filename)-1) |
271 |
newname[sizeof(current_filename)-1] = 0; |
272 |
f = fopen(newname, "a"); |
273 |
if (!f) |
274 |
return 0; |
275 |
setbuf(f, NULL); |
276 |
if (logfile) |
277 |
fclose(logfile); |
278 |
logfile = f; |
279 |
strcpy(current_filename, newname); /* safe b/c of length check above */ |
280 |
return 1; |
281 |
} |
282 |
|
283 |
/*************************************************************************/ |
284 |
|
285 |
/* Return nonzero if the log file is currently open, zero if closed. */ |
286 |
|
287 |
int log_is_open(void) |
288 |
{ |
289 |
return logfile != NULL; |
290 |
} |
291 |
|
292 |
/*************************************************************************/ |
293 |
/*************************************************************************/ |
294 |
|
295 |
/* Log stuff to the log file with a datestamp when debug >= debuglevel. |
296 |
* errno is preserved. If debuglevel is greater than zero, "debug: " is |
297 |
* prefixed to the message. If do_perror is nonzero, the message is |
298 |
* followed by a ": " and system error message, a la perror() (if errno<0, |
299 |
* it is treated as an herror value from hostname resolution). If |
300 |
* modulename is not NULL, it is used as a prefix to the error message. |
301 |
* |
302 |
* [module_]log[_perror][_debug]() are implemented as macros which call |
303 |
* this function. |
304 |
*/ |
305 |
|
306 |
void do_log(int debuglevel, int do_perror, const char *modulename, |
307 |
const char *fmt, ...) |
308 |
{ |
309 |
if (debug >= debuglevel) { |
310 |
va_list args; |
311 |
int errno_save = errno; |
312 |
|
313 |
va_start(args, fmt); |
314 |
if (logfile) |
315 |
check_log_rotate(); |
316 |
write_time(); |
317 |
if (debuglevel > 0) |
318 |
logputs("debug: "); |
319 |
if (modulename) |
320 |
logprintf("(%s) ", modulename); |
321 |
vlogprintf(fmt, args); |
322 |
if (do_perror) { |
323 |
logprintf(": %s", |
324 |
(errno_save<0) ? hstrerror(-errno_save) |
325 |
: strerror(errno_save)); |
326 |
} |
327 |
logputs("\n"); |
328 |
va_end(args); |
329 |
errno = errno_save; |
330 |
} |
331 |
} |
332 |
|
333 |
/*************************************************************************/ |
334 |
/*************************************************************************/ |
335 |
|
336 |
/* We've hit something we can't recover from. Let people know what |
337 |
* happened, then go down. |
338 |
*/ |
339 |
|
340 |
void fatal(const char *fmt, ...) |
341 |
{ |
342 |
va_list args; |
343 |
char buf[4096]; |
344 |
|
345 |
if (in_fatal) |
346 |
exit(2); |
347 |
in_fatal++; |
348 |
va_start(args, fmt); |
349 |
vsnprintf(buf, sizeof(buf), fmt, args); |
350 |
va_end(args); |
351 |
if (logfile) |
352 |
check_log_rotate(); |
353 |
write_time(); |
354 |
logprintf("FATAL: %s\n", buf); |
355 |
if (servsock && sock_isconn(servsock)) |
356 |
wallops(NULL, "FATAL ERROR! %s", buf); |
357 |
exit(1); |
358 |
} |
359 |
|
360 |
/*************************************************************************/ |
361 |
|
362 |
/* Same thing, but do it like perror(). */ |
363 |
|
364 |
void fatal_perror(const char *fmt, ...) |
365 |
{ |
366 |
va_list args; |
367 |
char buf[4096]; |
368 |
const char *errstr; |
369 |
|
370 |
if (in_fatal) |
371 |
exit(2); |
372 |
in_fatal++; |
373 |
errstr = (errno<0) ? hstrerror(-errno) : strerror(errno); |
374 |
va_start(args, fmt); |
375 |
vsnprintf(buf, sizeof(buf), fmt, args); |
376 |
va_end(args); |
377 |
if (logfile) |
378 |
check_log_rotate(); |
379 |
write_time(); |
380 |
logprintf("FATAL: %s: %s\n", buf, errstr); |
381 |
if (servsock && sock_isconn(servsock)) |
382 |
wallops(NULL, "FATAL ERROR! %s: %s", buf, errstr); |
383 |
exit(1); |
384 |
} |
385 |
|
386 |
/*************************************************************************/ |
387 |
|
388 |
/* |
389 |
* Local variables: |
390 |
* c-file-style: "stroustrup" |
391 |
* c-file-offsets: ((case-label . *) (statement-case-intro . *)) |
392 |
* indent-tabs-mode: nil |
393 |
* End: |
394 |
* |
395 |
* vim: expandtab shiftwidth=4: |
396 |
*/ |