/* Logging routines.
*
* IRC Services is copyright (c) 1996-2007 Andrew Church.
* E-mail: <achurch@achurch.org>
* 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);
}
/*************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1