/* Logging routines. * * IRC Services is copyright (c) 1996-2007 Andrew Church. * E-mail: * Parts written by Andrew Kempe and others. * This program is free but copyrighted software; see the file COPYING for * details. */ #include "services.h" /* Currently open log file */ static FILE *logfile; static char current_filename[PATH_MAX+1]; /* Memory log buffer (see open_memory_log()) */ static char logmem[LOGMEMSIZE]; static char *logmemptr = NULL; /* Are we in fatal() or fatal_perror()? (This is used to avoid infinite * recursion if wallops() does a fatal().) */ static int in_fatal = 0; /*************************************************************************/ /*************************************************************************/ /* Local routine to generate a filename from a filename pattern (possibly * containing %y/%m/%d for year/month/day). Result is returned in a static * buffer, and will not be longer than PATH_MAX characters. */ static char *gen_log_filename(void) { static char result[PATH_MAX+1]; const char *s, *from; char *to; time_t now = time(NULL); struct tm *tm = localtime(&now); tm->tm_year += 1900; tm->tm_mon++; from = LogFilename; if (!*from) { *result = 0; return result; } to = result; while ((s = strchr(from, '%')) != NULL) { to += snprintf(to, sizeof(result)-(to-result), "%.*s", s-from, from); s++; switch (*s) { case 'y': to += snprintf(to, sizeof(result)-(to-result), "%d", tm->tm_year); break; case 'm': to += snprintf(to, sizeof(result)-(to-result), "%02d", tm->tm_mon); break; case 'd': to += snprintf(to, sizeof(result)-(to-result), "%02d",tm->tm_mday); break; default: if (to-result < sizeof(result)-1) *to++ = *s; break; } from = s+1; } to += snprintf(to, sizeof(result)-(to-result), "%s", from); *to = 0; return result; } /*************************************************************************/ /* Local routine to check whether the log file needs to be rotated, and * rotate it if so. Assumes the log file is already open. */ static void check_log_rotate(void) { char *newname = gen_log_filename(); if (strlen(newname) > sizeof(current_filename)-1) newname[sizeof(current_filename)-1] = 0; if (strcmp(current_filename, gen_log_filename()) != 0) { if (!reopen_log()) log("Warning: Unable to rotate log file: %s", strerror(errno)); } } /*************************************************************************/ /*************************************************************************/ /* Local routines to write text to the log file and/or stderr as needed. */ static void vlogprintf(const char *fmt, va_list args) { if (nofork) { #ifdef NO_VA_COPY static int warned = 0; if (!warned) { fprintf(stderr, "*** Cannot write log messages to stderr because Services was compiled with\n" " an obsolete compiler. Please upgrade your compiler and recompile\n" " Services.\n" ); warned = 1; } #else va_list argscopy; va_copy(argscopy, args); vfprintf(stderr, fmt, argscopy); #endif } if (logfile) { check_log_rotate(); vfprintf(logfile, fmt, args); } else if (logmemptr) { char tmpbuf[BUFSIZE]; int len = vsnprintf(tmpbuf, sizeof(tmpbuf), fmt, args); if (len > LOGMEMSIZE - (logmemptr-logmem)) { int oldlen = len; len = LOGMEMSIZE - (logmemptr-logmem); if (len > 0) { if (tmpbuf[oldlen-1] == '\n') { tmpbuf[len-1] = '\n'; /* always end with a newline */ } else { len--; } } } if (len > 0) { memcpy(logmemptr, tmpbuf, len); logmemptr += len; } } } static void logprintf(const char *fmt, ...) FORMAT(printf,1,2); static void logprintf(const char *fmt, ...) { va_list args; va_start(args, fmt); vlogprintf(fmt, args); va_end(args); } static void logputs(const char *str) { logprintf("%s", str); } /*************************************************************************/ /* Local routine to write the time of day to the log. */ static void write_time() { time_t t; struct tm tm; char buf[256]; time(&t); tm = *localtime(&t); #if HAVE_GETTIMEOFDAY if (debug) { char *s; struct timeval tv; gettimeofday(&tv, NULL); strftime(buf, sizeof(buf)-1, "[%b %d %H:%M:%S", &tm); s = buf + strlen(buf); s += snprintf(s, sizeof(buf)-(s-buf), ".%06d", tv.tv_usec); strftime(s, sizeof(buf)-(s-buf)-1, " %Y] ", &tm); } else { #endif strftime(buf, sizeof(buf)-1, "[%b %d %H:%M:%S %Y] ", &tm); #if HAVE_GETTIMEOFDAY } #endif logputs(buf); } /*************************************************************************/ /*************************************************************************/ /* Open the log file. Return zero if the log file could not be opened, * else return nonzero (success). */ int open_log(void) { if (logfile) return 1; strscpy(current_filename, gen_log_filename(), sizeof(current_filename)); logfile = fopen(current_filename, "a"); if (logfile) { setbuf(logfile, NULL); if (logmemptr) { int res, errno_save; if (logmemptr > logmem) { res = fwrite(logmem, logmemptr-logmem, 1, logfile); errno_save = errno; } else { res = 1; /* i.e. no error */ #ifdef CLEAN_COMPILE errno_save = 0; /* warning killer for dumb compilers */ #endif } logmemptr = NULL; if (res != 1) { /* make sure this is AFTER the memory log has been closed! */ errno = errno_save; log_perror("open_log(): error writing memory log"); } } } return logfile!=NULL ? 1 : 0; } /*************************************************************************/ /* Open a virtual log file in memory. The contents of the memory log file * will be written to a physical log file when open_log() is called and * executes successfully. If a physical log file is already open, does * nothing. Always succeeds (and returns nonzero). */ int open_memory_log(void) { if (logfile || logmemptr) return 1; logmemptr = logmem; return 1; } /*************************************************************************/ /* Close the log file. */ void close_log(void) { if (logmemptr) { logmemptr = NULL; } if (logfile) { fclose(logfile); logfile = NULL; } } /*************************************************************************/ /* Reopen the log file with the current value of LogFilename (for use when * rehashing configuration files or rotating the log file). Return value * is like open_log(). */ int reopen_log(void) { char *newname; FILE *f; newname = gen_log_filename(); /* Make sure it will fit in current_filename later */ if (strlen(newname) > sizeof(current_filename)-1) newname[sizeof(current_filename)-1] = 0; f = fopen(newname, "a"); if (!f) return 0; setbuf(f, NULL); if (logfile) fclose(logfile); logfile = f; strcpy(current_filename, newname); /* safe b/c of length check above */ return 1; } /*************************************************************************/ /* Return nonzero if the log file is currently open, zero if closed. */ int log_is_open(void) { return logfile != NULL; } /*************************************************************************/ /*************************************************************************/ /* Log stuff to the log file with a datestamp. Note that errno is * preserved by this routine and log_perror(). */ void log(const char *fmt, ...) { va_list args; int errno_save = errno; va_start(args, fmt); write_time(); vlogprintf(fmt, args); logputs("\n"); va_end(args); errno = errno_save; } /*************************************************************************/ /* Like log(), but tack a ": " and a system error message (as returned by * strerror()) onto the end. */ void log_perror(const char *fmt, ...) { va_list args; int errno_save = errno; va_start(args, fmt); write_time(); vlogprintf(fmt, args); logprintf(": %s\n", (errno_save<0) ? hstrerror(-errno_save) : strerror(errno_save)); va_end(args); errno = errno_save; } /*************************************************************************/ /*************************************************************************/ /* Similar to log[_perror](), but used by modules to include the module * name at the beginning of the log message. Modules actually call * module_log[_perror](), which is a macro that inserts MODULE_NAME as the * first parameter in the call (since we can't access it ourselves). */ void _module_log(const char *modname, const char *fmt, ...) { va_list args; int errno_save = errno; va_start(args, fmt); write_time(); logprintf("%s: ", modname); vlogprintf(fmt, args); logputs("\n"); va_end(args); errno = errno_save; } /*************************************************************************/ void _module_log_perror(const char *modname, const char *fmt, ...) { va_list args; int errno_save = errno; va_start(args, fmt); write_time(); logprintf("%s: ", modname); vlogprintf(fmt, args); logprintf(": %s\n", (errno_save<0) ? hstrerror(-errno_save) : strerror(errno_save)); va_end(args); errno = errno_save; } /*************************************************************************/ /*************************************************************************/ /* We've hit something we can't recover from. Let people know what * happened, then go down. */ void fatal(const char *fmt, ...) { va_list args; char buf[4096]; if (in_fatal) return; in_fatal++; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); write_time(); logprintf("FATAL: %s\n", buf); if (servsock && sock_isconn(servsock)) wallops(NULL, "FATAL ERROR! %s", buf); exit(1); } /*************************************************************************/ /* Same thing, but do it like perror(). */ void fatal_perror(const char *fmt, ...) { va_list args; char buf[4096]; const char *errstr; if (in_fatal) return; in_fatal++; errstr = (errno<0) ? hstrerror(-errno) : strerror(errno); va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); write_time(); logprintf("FATAL: %s: %s\n", buf, errstr); if (servsock && sock_isconn(servsock)) wallops(NULL, "FATAL ERROR! %s: %s", buf, errstr); exit(1); } /*************************************************************************/