/*- * Copyright (c) 2003 Andrey Simonenko * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "config.h" #ifndef lint static const char rcsid[] ATTR_UNUSED = "@(#)$Id: ipa_log.c,v 1.1.4.9 2007/05/11 16:29:59 simon Exp $"; #endif /* !lint */ #include #include #include #include #include #include #include #include #include #include #include #include #include "ipa_mod.h" #include "dlapi.h" #include "confcommon.h" #include "memfunc.h" #include "ipa_ac.h" #include "ipa_db.h" #include "ipa_cmd.h" #include "ipa_ctl.h" #include "ipa_time.h" #include "ipa_log.h" #include "ipa_main.h" #include "ipa_rules.h" #include "ipa_autorules.h" /* * To prevent possible recursion from mem_* functions, * libc memory allocation function are used in this function. */ /* * open_log(), logmsg() and mod_logmsg() save errno on enter * and restore errno on exit, to allow external function to check * value of errno, changed by some function, which caused error. */ #define LOG_STREAM_BUF_SIZE 1024 /* * Don't call read for caught standard output and error stream * more than LOG_STREAM_READ_CALLS, to get other code a chance * to be executed. */ #define LOG_STREAM_READ_CALLS 10 /* vlogmsgx() or something like this. */ void (*xvlogmsgx)(int, const char *, va_list); const char *log_ident = IPA_LOG_IDENT; /* -i */ const char *log_file = NULL; /* -o */ static int syslog_logopt; /* Syslog log options. */ static long mypid; /* PID of current process. */ static int log_fd = -1; /* File descriptor of log file. */ #ifndef LOG_FILE_PERM # define LOG_FILE_PERM (S_IRUSR|S_IWUSR|S_IRGRP) /* 0640 */ #endif static const char *const priomsg[] = { /* IPA_LOG_xxx -> Message */ "", "W: ", "E: " }; static const int log_priority[] = { /* IPA_LOG_xxx -> LOG_xxx */ LOG_INFO, LOG_WARNING, LOG_ERR }; #ifdef WITH_PTHREAD # define STRERRBUF_SIZE 128 /* Size of buffer for strerror_r(). */ #endif /* * Init settings for syslog only. */ void init_log(void) { if (log_file == NULL) syslog_logopt = LOG_PID | #ifdef LOG_ODELAY LOG_ODELAY | #endif #ifdef LOG_NOWAIT LOG_NOWAIT | #endif (debug ? #ifdef LOG_PERROR LOG_PERROR #else 0 #endif : LOG_CONS); } /* * This function is called by modules to output their * log messages. */ void mod_logmsg(const char *mod_name, int priority, int code, const char *format, va_list ap) { int rv, allocated = 0; int errno_save; char buf[LOG_BUF_SIZE]; /* Try to use buffer in stack. */ char *ptr; errno_save = errno; /* Save errno. */ if ( (rv = vsnprintf(buf, sizeof buf, format, ap)) < 0) ptr = "(mod_logmsg: vsnprintf failed)"; else if (rv >= sizeof buf) { if ( (ptr = malloc(++rv)) == NULL) ptr = "(mod_logmsg: malloc failed)"; else { if (vsnprintf(ptr, rv, format, ap) < 0) { free(ptr); ptr = "(mod_logmsg: vsnprintf failed again)"; } else allocated = 1; } } else ptr = buf; if (code != 0) { errno = code; logmsg(priority, "MOD %s: %s", mod_name, ptr); } else logmsgx(priority, "MOD %s: %s", mod_name, ptr); if (allocated) free(ptr); errno = errno_save; /* Restore errno. */ } /* * Write a message with a timestamp to the log file and to * standard error if needed. If stat(2) for the opened * log file failed, then try to open/create it again. */ static void log_message(const char *message) { int fd; time_t t; struct iovec iov[3]; struct stat statbuf; #ifdef WITH_PTHREAD char buf[26]; char strerrbuf[STRERRBUF_SIZE]; #endif iov[2].iov_base = "\n"; iov[2].iov_len = 1; for (;;) { if (log_fd < 0) { if ( (log_fd = open(log_file, O_WRONLY|O_APPEND|O_CREAT, LOG_FILE_PERM)) < 0) { if (debug) { iov[0].iov_base = "log_message: open log file failed: "; iov[0].iov_len = 35; #ifdef WITH_PTHREAD iov[1].iov_base = strerror_r(errno, strerrbuf, sizeof strerrbuf) == 0 ? strerrbuf : ""; #else iov[1].iov_base = strerror(errno); #endif iov[1].iov_len = strlen(iov[1].iov_base); (void)fflush(stdout); (void)writev(STDERR_FILENO, iov, 3); } else /* Can't open/create log file and can't log to stderr. */ return; } break; } else { if (stat(log_file, &statbuf) < 0) { (void)close(log_fd); log_fd = -1; } else break; } } if (debug) { fd = STDERR_FILENO; (void)fflush(stdout); } else fd = log_fd; if (time(&t) == (time_t)-1) { iov[0].iov_base = "log_message: time failed: "; iov[0].iov_len = 26; #ifdef WITH_PTHREAD iov[1].iov_base = strerror_r(errno, strerrbuf, sizeof strerrbuf) == 0 ? strerrbuf : ""; #else iov[1].iov_base = strerror(errno); #endif iov[1].iov_len = strlen(iov[1].iov_base); } else { /* DDD MMM dd hh:mm:ss YYYY\n\0 */ #ifdef WITH_PTHREAD iov[0].iov_base = ctime_r(&t, buf) + 4; /* Skip DDD. */ #else iov[0].iov_base = ctime(&t) + 4; /* Skip DDD. */ #endif iov[0].iov_len = 20; /* MMM...YYY */ iov[1].iov_base = (char *)message; iov[1].iov_len = strlen(message); } for (;;) { if (writev(fd, iov, 3) < 0) if (debug && fd != STDERR_FILENO) { (void)close(log_fd); log_fd = -1; return; } if (debug && fd == STDERR_FILENO) { if ( (fd = log_fd) < 0) break; } else break; } } /* * Call openlog for syslog (real open of log descriptor is delayed). */ void open_log(void) { int errno_save; if (log_file == NULL) { errno_save = errno; /* Save errno. */ openlog(log_ident, syslog_logopt, LOG_USER); errno = errno_save; /* Restore errno. */ } else { mypid = (long)getpid(); log_fd = -1; } } /* * Close log descriptor. */ void close_log(void) { if (log_file == NULL) closelog(); else if (log_fd >= 0) { (void)close(log_fd); log_fd = -1; } } /* * Wrapper for log function from memfunc.c. */ void mvlogmsgx_wrapper(const char *format, va_list ap) { /* * Since mxxx functions log messages when errors * occurred, we simply use fix log priority here. */ vlogmsgx(IPA_LOG_ERR, format, ap); } /* * vprintf-like function, do not output message for errno. */ void vlogmsgx(int priority, const char *format, va_list ap) { if (log_file == NULL) { #ifdef HAVE_VSYSLOG vsyslog(log_priority[priority], format, ap); #else char buf[LOG_BUF_SIZE]; if (vsnprintf(buf, sizeof buf, format, ap) >= 0) syslog(log_priority[priority], buf); #endif #ifdef LOG_PERROR return; #endif } { int rv, allocated1, allocated2; char *ptr1, *ptr2; char buf1[LOG_BUF_SIZE]; char buf2[LOG_BUF_SIZE + 128]; allocated1 = allocated2 = 0; if ( (rv = vsnprintf(buf1, sizeof buf1, format, ap)) < 0) ptr1 = "(vlogmsgx: vsnprintf failed for ptr1)"; else if (rv >= sizeof buf1) { if ( (ptr1 = malloc(++rv)) == NULL) ptr1 = "(vlogmsgx: malloc failed for ptr1)"; else { if (vsnprintf(ptr1, rv, format, ap) < 0) { free(ptr1); ptr1 = "(vlogmsgx: vsnprintf failed again for ptr1)"; } else allocated1 = 1; } } else ptr1 = buf1; if ( (rv = snprintf(buf2, sizeof buf2, " %s[%ld]: %s%s", log_ident, mypid, priomsg[priority], ptr1)) < 0) ptr2 = "(vlogmsgx: vsnprintf failed for ptr2)"; else if (rv >= sizeof buf2) { if ( (ptr2 = malloc(++rv)) == NULL) ptr2 = "(vlogmsgx: malloc failed for ptr2)"; else { if (vsnprintf(ptr2, rv, format, ap) < 0) { free(ptr2); ptr2 = "(vlogmsgx: vsnprintf failed again for ptr2)"; } else allocated2 = 1; } } else ptr2 = buf2; log_message(ptr2); if (allocated1) free(ptr1); if (allocated2) free(ptr2); } } /* * The same as vlogmsgx, but output everything on stderr. */ void vlogmsgx_stderr(int priority, const char *format, va_list ap) { fflush(stdout); switch (priority) { case IPA_LOG_ERR: fprintf(stderr, "Error: "); break; case IPA_LOG_WARNING: fprintf(stderr, "Warning: "); break; default: /* IPA_LOG_INFO */ fprintf(stderr, "Info: "); } vfprintf(stderr, format, ap); fprintf(stderr, "\n"); } /* * printf-like function, do not output message for errno. */ void logmsgx(int priority, const char *format, ...) { va_list ap; va_start(ap, format); vlogmsgx(priority, format, ap); va_end(ap); } /* * printf-like function, also output message for errno. */ void logmsg(int priority, const char *format, ...) { va_list ap; if (errno == 0) { va_start(ap, format); vlogmsgx(priority, format, ap); va_end(ap); return; } { int rv, allocated = 0; int errno_save; char buf[LOG_BUF_SIZE]; /* Try to use buffer in stack. */ char *ptr; #ifdef WITH_PTHREAD char strerrbuf[STRERRBUF_SIZE]; #endif errno_save = errno; /* Save errno. */ va_start(ap, format); if ( (rv = vsnprintf(buf, sizeof buf, format, ap)) < 0) ptr = "(logmsg: vsnprintf failed)"; else if (rv >= sizeof buf) { if ( (ptr = malloc(++rv)) == NULL) ptr = "(logmsg: malloc failed)"; else { if (vsnprintf(ptr, rv, format, ap) < 0) { free(ptr); ptr = "(logmsg: vsnprintf failed again)"; } else allocated = 1; } } else ptr = buf; va_end(ap); #ifdef WITH_PTHREAD if (strerror_r(errno_save, strerrbuf, sizeof strerrbuf) == 0) logmsgx(priority, "%s: %s", ptr, strerrbuf); else logmsgx(priority, "%s: error code %d", ptr, errno_save); #else logmsgx(priority, "%s: %s", ptr, strerror(errno_save)); #endif if (allocated) free(ptr); errno = errno_save; /* Restore errno. */ } } /* * Call xvlomsgx() which is a pointer to some vlogmsgx-like function. */ void xlogmsgx(int priority, const char *format, ...) { va_list ap; va_start(ap, format); xvlogmsgx(priority, format, ap); va_end(ap); } /* * Read data from nonblockable fd and log them prepending with * stream name string, interpret '\0' and '\n' as new lines, call * read() syscall for fd no more than LOG_STREAM_READ_CALLS times. * This function is used only by log_stdout and log_stderr. */ void log_stream(int fd, const char *stream_name) { char string[LOG_STREAM_BUF_SIZE], *ptr1, *ptr2; int i; u_int ncalls = 0; /* Number of read() invocations. */ ssize_t nread; /* Number of read bytes from last read(). */ size_t ngot; /* Number of valid bytes in string. */ size_t nleft; /* Number of not used bytes in string. */ ptr1 = string; nleft = sizeof(string) - 1; ngot = 0; for (;;) { if ( (nread = read(fd, ptr1, nleft)) < 0) { if (errno == EWOULDBLOCK) { if (ptr1 != string) logmsgx(IPA_LOG_WARNING, "*%s: %s", stream_name, string); break; } else { logmsg(IPA_LOG_ERR, "log_stream(%s): read", stream_name); break; } } ngot += nread; for (i = 0, ptr1 = ptr2 = string; i < ngot; ++ptr2, ++i) switch (*ptr2) { case '\n': *ptr2 = '\0'; /* FALLTHROUGH */ case '\0': logmsgx(IPA_LOG_WARNING, "*%s: %s", stream_name, ptr1); ptr1 = ptr2 + 1; } if (ptr1 == string) { ptr1[ngot] = '\0'; ngot = 0; nleft = sizeof(string) - 1; logmsgx(IPA_LOG_WARNING, "*%s: %s", stream_name, ptr1); } else if (ptr1 != ptr2) { ngot = ptr2 - ptr1; memcpy(string, ptr1, ngot); ptr1 = string + ngot; *ptr1 = '\0'; nleft = sizeof(string) - ngot - 1; } else { ptr1 = string; ngot = 0; nleft = sizeof(string) - 1; } if (++ncalls == LOG_STREAM_READ_CALLS) { if (ngot != 0) /* and has '\0' in string. */ logmsgx(IPA_LOG_WARNING, "*%s: %s", stream_name, string); break; } } }