/* ** Copyright (C) 2001-2007 by Carnegie Mellon University. ** ** @OPENSOURCE_HEADER_START@ ** ** Use of the SILK system and related source code is subject to the terms ** of the following licenses: ** ** GNU Public License (GPL) Rights pursuant to Version 2, June 1991 ** Government Purpose License Rights (GPLR) pursuant to DFARS 252.225-7013 ** ** NO WARRANTY ** ** ANY INFORMATION, MATERIALS, SERVICES, INTELLECTUAL PROPERTY OR OTHER ** PROPERTY OR RIGHTS GRANTED OR PROVIDED BY CARNEGIE MELLON UNIVERSITY ** PURSUANT TO THIS LICENSE (HEREINAFTER THE "DELIVERABLES") ARE ON AN ** "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY ** KIND, EITHER EXPRESS OR IMPLIED AS TO ANY MATTER INCLUDING, BUT NOT ** LIMITED TO, WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE, ** MERCHANTABILITY, INFORMATIONAL CONTENT, NONINFRINGEMENT, OR ERROR-FREE ** OPERATION. CARNEGIE MELLON UNIVERSITY SHALL NOT BE LIABLE FOR INDIRECT, ** SPECIAL OR CONSEQUENTIAL DAMAGES, SUCH AS LOSS OF PROFITS OR INABILITY ** TO USE SAID INTELLECTUAL PROPERTY, UNDER THIS LICENSE, REGARDLESS OF ** WHETHER SUCH PARTY WAS AWARE OF THE POSSIBILITY OF SUCH DAMAGES. ** LICENSEE AGREES THAT IT WILL NOT MAKE ANY WARRANTY ON BEHALF OF ** CARNEGIE MELLON UNIVERSITY, EXPRESS OR IMPLIED, TO ANY PERSON ** CONCERNING THE APPLICATION OF OR THE RESULTS TO BE OBTAINED WITH THE ** DELIVERABLES UNDER THIS LICENSE. ** ** Licensee hereby agrees to defend, indemnify, and hold harmless Carnegie ** Mellon University, its trustees, officers, employees, and agents from ** all claims or demands made against them (and any related losses, ** expenses, or attorney's fees) arising out of, or relating to Licensee's ** and/or its sub licensees' negligent use or willful misuse of or ** negligent conduct or willful misconduct regarding the Software, ** facilities, or other rights or assistance granted by Carnegie Mellon ** University under this License, including, but not limited to, any ** claims of product liability, personal injury, death, damage to ** property, or violation of any laws or regulations. ** ** Carnegie Mellon University Software Engineering Institute authored ** documents are sponsored by the U.S. Department of Defense under ** Contract F19628-00-C-0003. Carnegie Mellon University retains ** copyrights in all material produced under this contract. The U.S. ** Government retains a non-exclusive, royalty-free license to publish or ** reproduce these documents, or allow others to do so, for U.S. ** Government purposes only pursuant to the copyright license under the ** contract clause at 252.227.7013. ** ** @OPENSOURCE_HEADER_END@ */ /* ** log.c ** ** Library to support file-based logging. Cobbled together based on ** the report.c code written by M. Duggan. ** ** Suresh Konda ** 4/23/2003 */ #include "silk.h" RCSIDENT("$SiLK: sklog.c 7647 2007-06-26 17:08:53Z mthomas $"); #include "utils.h" #include "sklog.h" #include "skstringmap.h" #include #include /* LOCAL DEFINES AND TYPEDEFS */ #ifndef MAILX # ifdef SK_MAILER # define MAILX SK_MAILER # else # ifdef LINUX # define MAILX "mail" # else # define MAILX "mailx" # endif # endif #endif /* size of buffer to use when using a syslog facility that does not * provide vsyslog() */ #define MSGBUF_SIZE (4 * PATH_MAX) /* size of our hostname field; not all systems seem to define * HOST_NAME_MAX, so we'll use 255+1 from POSIX. */ #define SKLOG_HOST_NAME_MAX 256 /* hour at which to rotate the logs */ #define SKLOG_ROTATE_HOUR 0 /* when using log rotation, the suffix to add to file names */ #define SKLOG_SUFFIX ".log" /* the program to use to compress the logs after rotating, or * undefined to not compress the older log files */ #define SKLOG_COMPRESSOR "gzip" /* default log level */ #define SKLOG_DEFAULT_LEVEL LOG_INFO /* default syslog facility */ #define SKLOG_SYSFACILITY LOG_USER /* default syslog options */ #define SKLOG_SYSOPTIONS LOG_PID /* legacy logging: level to use for logMsg() and vlogMsg() routines */ #define SKLOG_LEGACY_LOG_LEVEL LOG_INFO /* legacy logging: level to use for report() and vreport() routines */ #define SKLOG_LEGACY_REPORT_LEVEL LOG_ALERT /* invoke the proper logging function if the log has been setup and * the log is open */ #define SKLOG_CALL_LOGGER(pri, fmt, args) \ if ( !(logctx && logctx->l_open && logctx->l_func)) { /*no-op*/ } \ else { logctx->l_func((pri), (fmt), (args)); } /* a wrapper around SKLOG_CALL_LOGGER() that sets up and tears down * the va_list. */ #define SKLOG_VARARG_CALL_LOGGER(pri, fmt) \ { \ va_list _args; \ va_start(_args, (fmt)); \ SKLOG_CALL_LOGGER((pri), (fmt), _args); \ va_end(_args); \ } /* invoke the lock and unlock functions if they are defined */ #define SKLOG_LOCK() \ if ( !logctx->l_lock_fn) { /*no-op*/ } \ else { logctx->l_lock_fn(logctx->l_lock_data); } #define SKLOG_UNLOCK() \ if ( !logctx->l_unlock_fn) { /*no-op*/ } \ else { logctx->l_unlock_fn(logctx->l_lock_data); } /* possible logging destinations */ typedef enum { /* no destination has been set */ SKLOG_DEST_NOT_SET = 0, /* no logs will be written */ SKLOG_DEST_NONE, /* write to a single log file */ SKLOG_DEST_PATH, /* for legacy support, write to multiple files in a * directory---support rotation and compression */ SKLOG_DEST_DIRECTORY, /* write to stdout; same as DEST_PATH with FILE* of stdout */ SKLOG_DEST_STDOUT, /* write to stderr; same as DEST_PATH with FILE* of stderr */ SKLOG_DEST_STDERR, /* write using syslog() */ SKLOG_DEST_SYSLOG, /* write to syslog() and to stderr. Adds LOG_PERROR to syslog */ SKLOG_DEST_BOTH } sklog_dest_t; /* internal log function */ typedef void (*sklog_fn_t)(int priority, const char *fmt, va_list args); /* structure to support logging with syslog(3) */ typedef struct _sklog_syslog { int options; int facility; } sklog_system_t; /* structure needed to hold everything to support logging outside of * syslog */ typedef struct _sklog_simple { /* function to call to prepend the time/machine stamp to the message */ sklog_stamp_fn_t stamp_fn; char machine_name[SKLOG_HOST_NAME_MAX]; char path[PATH_MAX]; char mail_user[PATH_MAX]; const char *app_name; FILE *fp; unsigned use_mail :1; } sklog_simple_t; /* structure used in conjuction with sklog_simple_t when log rotation * is desired */ typedef struct _sklog_rotated { /* time of next scheduled log rotation */ time_t rolltime; /* the directory in which to write all log files */ char dir[PATH_MAX]; /* basename of the log files; the date and SKLOG_SUFFIX will be * appended. */ char basename[PATH_MAX]; } sklog_rotated_t; /* the actual logging context */ typedef struct _sklog_context { /* holds all the syslog-specific info */ sklog_system_t l_sys; /* holds the info when logging to a single file */ sklog_simple_t l_sim; /* holds info required in addition to l_sim when using log rotatation */ sklog_rotated_t l_rot; /* function to call to write a message to the log; varies * depending on type of log being used */ sklog_fn_t l_func; /* functions to call to lock and unlock the log. */ sklog_lock_fn_t l_lock_fn; sklog_lock_fn_t l_unlock_fn; /* data passed to the l_lock_fn() and l_unlock_fn(). */ void *l_lock_data; /* the command line invocation of the application */ char *l_cmd; /* which levels of messages to log */ int l_mask; /* what features users requested in sklogSetup() */ int l_features; /* whether the log is open */ unsigned l_open :1; /* which log destination is being used */ sklog_dest_t l_dest; } sklog_context_t; /* LOCAL FUNCTION DECLARATIONS */ static void _logCompress(char *file); static void _logMailSend( const char *user, const char *subject, const char *fmt, va_list args); static size_t _logMakeStamp(char *buf, size_t buflen); static int _logOptionsHandler(clientData cData, int opt_index, char *opt_arg); static void _logRotatedLog(int priority, const char *fmt, va_list args); static int _logRotatedOpen(void); static void _logSimpleClose(void); static void _logSimpleLog(int priority, const char *fmt, va_list args); static int _logSimpleOpen(void); static int _logStringifyCommand(int argc, char * const *argv); static void _logSimpleVPrintf(int priority, const char *fmt, va_list args); static void _logVSyslog(int priority, const char *fmt, va_list args); static void _logWriteCommandLine(void); /* LOCAL VARIABLE DEFINITIONS */ /* we have a static log structure, and a static pointer that we will * point at it once the logger has been initialized. */ static sklog_context_t logger; static sklog_context_t *logctx = NULL; /* available destinations */ static const sk_stringmap_entry_t log_dest[] = { {"none", SKLOG_DEST_NONE}, {"stdout", SKLOG_DEST_STDOUT}, {"stderr", SKLOG_DEST_STDERR}, {"syslog", SKLOG_DEST_SYSLOG}, #ifdef LOG_PERROR {"both", SKLOG_DEST_BOTH}, #endif {0,0} /* sentinel */ }; /* available levels */ static const sk_stringmap_entry_t log_level[] = { {"emerg", LOG_EMERG}, {"alert", LOG_ALERT}, {"crit", LOG_CRIT}, {"err", LOG_ERR}, {"warning", LOG_WARNING}, {"notice", LOG_NOTICE}, {"info", LOG_INFO}, {"debug", LOG_DEBUG}, {0,0} /* sentinel */ }; /* available facilities */ static const sk_stringmap_entry_t log_facility[] = { {"user", LOG_USER}, {"local0", LOG_LOCAL0}, {"local1", LOG_LOCAL1}, {"local2", LOG_LOCAL2}, {"local3", LOG_LOCAL3}, {"local4", LOG_LOCAL4}, {"local5", LOG_LOCAL5}, {"local6", LOG_LOCAL6}, {"local7", LOG_LOCAL7}, {"daemon", LOG_DAEMON}, {0,0} /* sentinel */ }; /* OPTIONS SETUP */ #define NUM_OPTIONS 7 static const char *opt_values[NUM_OPTIONS]; typedef enum { OPT_WARNINGS_EMAIL, OPT_LOG_DIRECTORY, OPT_LOG_BASENAME, OPT_LOG_PATHNAME, OPT_LOG_DESTINATION, OPT_LOG_LEVEL, OPT_LOG_SYSFACILITY } logOptionsEnum; static struct option logOptions[] = { {"warnings-email", REQUIRED_ARG, 0, OPT_WARNINGS_EMAIL}, {"log-directory", REQUIRED_ARG, 0, OPT_LOG_DIRECTORY}, {"log-basename", REQUIRED_ARG, 0, OPT_LOG_BASENAME}, {"log-pathname", REQUIRED_ARG, 0, OPT_LOG_PATHNAME}, {"log-destination", REQUIRED_ARG, 0, OPT_LOG_DESTINATION}, {"log-level", REQUIRED_ARG, 0, OPT_LOG_LEVEL}, {"log-sysfacility", REQUIRED_ARG, 0, OPT_LOG_SYSFACILITY}, {0,0,0,0} /* sentinel entry */ }; static int logOptionsIsUsed[] = { SKLOG_FEATURE_EMAIL, SKLOG_FEATURE_LEGACY, SKLOG_FEATURE_LEGACY, SKLOG_FEATURE_LEGACY, SKLOG_FEATURE_SYSLOG, SKLOG_FEATURE_SYSLOG | SKLOG_FEATURE_LEGACY, SKLOG_FEATURE_SYSLOG }; /* FUNCTION DEFINITIONS */ /* * Only compile these functions when they have not been defined as * macros. */ #if !defined(EMERGMSG) int EMERGMSG(const char *fmt, ...) { SKLOG_VARARG_CALL_LOGGER(LOG_EMERG, fmt); return 0; } #endif #if !defined(ALERTMSG) int ALERTMSG(const char *fmt, ...) { SKLOG_VARARG_CALL_LOGGER(LOG_ALERT, fmt); return 0; } #endif #if !defined(CRITMSG) int CRITMSG(const char *fmt, ...) { SKLOG_VARARG_CALL_LOGGER(LOG_CRIT, fmt); return 0; } #endif #if !defined(ERRMSG) int ERRMSG(const char *fmt, ...) { SKLOG_VARARG_CALL_LOGGER(LOG_ERR, fmt); return 0; } #endif #if !defined(WARNINGMSG) int WARNINGMSG(const char *fmt, ...) { SKLOG_VARARG_CALL_LOGGER(LOG_WARNING, fmt); return 0; } #endif #if !defined(NOTICEMSG) int NOTICEMSG(const char *fmt, ...) { SKLOG_VARARG_CALL_LOGGER(LOG_NOTICE, fmt); return 0; } #endif #if !defined(INFOMSG) int INFOMSG(const char *fmt, ...) { SKLOG_VARARG_CALL_LOGGER(LOG_INFO, fmt); return 0; } #endif #if !defined(DEBUGMSG) int DEBUGMSG(const char *fmt, ...) { SKLOG_VARARG_CALL_LOGGER(LOG_DEBUG, fmt); return 0; } #endif int EMERGMSG_v(const char *fmt, va_list args) { SKLOG_CALL_LOGGER(LOG_EMERG, fmt, args); return 0; } int ALERTMSG_v(const char *fmt, va_list args) { SKLOG_CALL_LOGGER(LOG_ALERT, fmt, args); return 0; } int CRITMSG_v(const char *fmt, va_list args) { SKLOG_CALL_LOGGER(LOG_CRIT, fmt, args); return 0; } int ERRMSG_v(const char *fmt, va_list args) { SKLOG_CALL_LOGGER(LOG_ERR, fmt, args); return 0; } int WARNINGMSG_v(const char *fmt, va_list args) { SKLOG_CALL_LOGGER(LOG_WARNING, fmt, args); return 0; } int NOTICEMSG_v(const char *fmt, va_list args) { SKLOG_CALL_LOGGER(LOG_NOTICE, fmt, args); return 0; } int INFOMSG_v(const char *fmt, va_list args) { SKLOG_CALL_LOGGER(LOG_INFO, fmt, args); return 0; } int DEBUGMSG_v(const char *fmt, va_list args) { SKLOG_CALL_LOGGER(LOG_DEBUG, fmt, args); return 0; } /* * _logCompress(file); * * Compress the log file 'file'. Will call free() on the 'file' * parameter. */ static void _logCompress(char *file) { pid_t pid; if (file == NULL) { INFOMSG("_logCompress passed NULL pointer"); return; } #ifndef SKLOG_COMPRESSOR free(file); return; #else /* Fork */ pid = fork(); if (pid == -1) { ERRMSG("Couldn't fork for compression: %s", strerror(errno)); free(file); return; } /* Parent (original process) reaps Child 1 and returns */ if (pid != 0) { /* Wait for Child 1 to exit. */ waitpid(pid, NULL, 0); free(file); return; } /* Child 1 forks */ pid = fork(); if (pid == -1) { ERRMSG("Child couldn't fork for compression: %s", strerror(errno)); _exit(EXIT_FAILURE); } /* Child 1 immediately exits, so Parent can stop waiting */ if (pid != 0) { _exit(EXIT_SUCCESS); } /* Child 2 executes the compressor */ if (execlp(SKLOG_COMPRESSOR, SKLOG_COMPRESSOR, "-f", file, (char*)NULL) == -1) { ERRMSG(("Error invoking " SKLOG_COMPRESSOR ": %s"), strerror(errno)); _exit(EXIT_FAILURE); } /* Should never get here. */ assert(0); abort(); #endif /* SKLOG_COMPRESSOR */ } #ifdef MAILX static void _logMailSend( const char *user, const char *subject, const char *fmt, va_list args) { char body[MSGBUF_SIZE]; char command[PATH_MAX]; int len; FILE *fp; char *cp; size_t written; /* verify user and subject are specified and do not contain " nor \ */ if (user == NULL || user[0] == '\0') { INFOMSG("Not sending mail: user not given"); return; } if (strchr(user, '\"') || strchr(user, '\\')) { INFOMSG("Not sending mail: user contains illegal character"); return; } if (subject == NULL) { INFOMSG("Not sending mail: subject not given"); return; } if (strchr(subject, '\"') || strchr(subject, '\\')) { INFOMSG("Not sending mail: subject contains illegal character"); return; } /* create the body, add a trailing newline */ vsnprintf(body, sizeof(body)-2, fmt, args); body[sizeof(body) - 2] = '\0'; len = strlen(body); body[len] = '\n'; body[len+1] = '\0'; /* create command to invoke the mailer */ len = snprintf(command, sizeof(command), "%s -s \"%s\" \"%s\"", MAILX, subject, user); if (len == -1 || len >= (int)sizeof(command)) { INFOMSG("Not sending mail: mail command too long"); return; } /* invoke the mailer */ DEBUGMSG("invoking the mailer '%s'", command); fp = popen(command, "w"); if (fp == NULL) { INFOMSG("Not sending mail: cannot invoke '%s'", command); return; } /* write body to the mailer */ cp = body; len = strlen(body)+1; while (len && (written = fwrite(cp, 1, len, fp))) { cp += written; len -= written; } if (len) { INFOMSG("Error writing body to mailer"); } fflush(fp); if (pclose(fp) == -1) { INFOMSG("Error closing the mailer"); } } #endif /* MAILX */ /* * size = _logMakeStamp(buf, buflen) * * Add a time, machine, application, and PID stamp on the front of * the character array 'buf', whose length is 'buflen'. Return the * number of characters written to the buffer. */ static size_t _logMakeStamp(char *buf, size_t buflen) { time_t t; struct tm ts; size_t len; t = time(NULL); localtime_r(&t, &ts); /* Format time as "May 4 01:02:03" */ len = strftime(buf, buflen, "%h %e %T", &ts); assert(len < buflen); len += snprintf(buf+len, buflen-len, " %s %s[%ld]: ", logctx->l_sim.machine_name, logctx->l_sim.app_name, (long)getpid()); return len; } /* * A simple options handler that stores the string 'opt_arg' in the * array pointed to by cData. */ static int _logOptionsHandler(clientData cData, int opt_index, char *opt_arg) { const char **args = (const char **)cData; assert(opt_index < (int)(sizeof(opt_values)/sizeof(char*))); args[opt_index] = opt_arg; return 0; } /* * _logRotatedLog(priority, fmt, args); * * Lock the mutex for the log. If the rollover-time for the log * has passed call _logRotatedOpen() to open a new log file. Use * _logSimpleVPrintf() to print the message to the log. */ static void _logRotatedLog(int priority, const char *fmt, va_list args) { char msgbuf[MSGBUF_SIZE]; FILE *rotated_fp = NULL; char *rotated_path = NULL; int rv; if (logctx && logctx->l_open) { SKLOG_LOCK(); if (logctx->l_rot.rolltime < time(NULL)) { /* Must rotate logs. First, grab current log file. */ rotated_fp = logctx->l_sim.fp; rotated_path = strdup(logctx->l_sim.path); /* Log a message about rotating the log. */ (void)logctx->l_sim.stamp_fn(msgbuf, sizeof(msgbuf)); fprintf(rotated_fp, "%sLog rollover\n", msgbuf); /* Open the new log file */ rv = _logRotatedOpen(); if (rv) { /* Could not open new file. Continue to use existing * log file. */ (void)logctx->l_sim.stamp_fn(msgbuf, sizeof(msgbuf)); fprintf(rotated_fp, ("%sLog not rotated--" "error opening log new log '%s': %s\n"), msgbuf, logctx->l_sim.path, strerror(rv)); /* restore the old settings */ logctx->l_sim.fp = rotated_fp; strncpy(logctx->l_sim.path, rotated_path, sizeof(logctx->l_sim.path)); rotated_fp = NULL; free(rotated_path); rotated_path = NULL; } } /* Print the original message to the log */ _logSimpleVPrintf(priority, fmt, args); SKLOG_UNLOCK(); } /* if we rotated the log; close the existing log file and compress * it. */ if (rotated_fp) { fclose(rotated_fp); _logCompress(rotated_path); } } /* * errno = _logRotatedOpen(); * * Open a new log file when the caller has requested log rotation, * and set the time when the next rotation will occur. * * This function will overwrite the current log path and log FILE * pointer; this function does not close the current log file. * * Returns 0 on success. On failure, the errno of the system call * that failed is returned. The rollover time will be advanced * regardless if the function succeeds or fails. */ static int _logRotatedOpen(void) { char date[32]; time_t t; struct tm ts; int rv; /* get current time */ t = time(NULL); localtime_r(&t, &ts); strftime(date, sizeof(date), "%Y%m%d", &ts); /* compute the roll-over time by setting hour to last second of * today, adding a second to get to midnight, then adding the * rollover hour. Do this before we rotate, so that if rotation * fails we don't try again until the next rotation time. */ #ifndef SKLOG_TESTING_LOG ts.tm_hour = 23; ts.tm_min = 59; ts.tm_sec = 59; logctx->l_rot.rolltime = mktime(&ts) + 1 + (SKLOG_ROTATE_HOUR * 3600); #else /* rotate each minute */ strftime(date, sizeof(date), "%Y%m%d:%R", &ts); if (ts.tm_sec > 55) { ++ts.tm_min; } ts.tm_sec = 0; ++ts.tm_min; logctx->l_rot.rolltime = mktime(&ts); #endif /* SKLOG_TESTING_LOG */ /* fill in the simple path with the new name */ snprintf(logctx->l_sim.path, sizeof(logctx->l_sim.path), "%s/%s-%s%s", logctx->l_rot.dir, logctx->l_rot.basename, date, SKLOG_SUFFIX); /* if this is the initial open, use _logOpenSimple() to set the * applicataion and machine names. otherwise, just fopen() the * new location */ if (logctx->l_sim.fp == NULL) { rv = _logSimpleOpen(); if (rv) { return rv; } } else { logctx->l_sim.fp = fopen(logctx->l_sim.path, "a"); if (NULL == logctx->l_sim.fp) { return errno; } } return 0; } /* * _logSimpleClose(); * * Close the "simple" logger that writes to a file or to stdout/stderr. */ static void _logSimpleClose(void) { if (logctx->l_sim.fp) { SKLOG_LOCK(); if ((logctx->l_sim.fp != stdout) && (logctx->l_sim.fp != stderr)) { fclose(logctx->l_sim.fp); } logctx->l_sim.fp = NULL; SKLOG_UNLOCK(); } } /* * _logSimpleLog(priority, fmt, args); * * Lock the mutex for the log and call _logSimpleVPrintf() to print * a message to the log. */ static void _logSimpleLog(int priority, const char *fmt, va_list args) { if (logctx && logctx->l_open) { SKLOG_LOCK(); _logSimpleVPrintf(priority, fmt, args); SKLOG_UNLOCK(); } } /* * errno = _logSimpleOpen(); * * Open a "simple" logger that writes to a file or to stdout or * stderr. Will also fill the logctx with the names of the * application and machine. Return 0 on success; on failure, * return the errno of the system call that failed. */ static int _logSimpleOpen(void) { sklog_simple_t *simplog = &logctx->l_sim; struct utsname u; char *cp; simplog->app_name = skAppName(); if (NULL == simplog->stamp_fn) { simplog->stamp_fn = &_logMakeStamp; } /* set the machine name; use only host part of a FQDN */ if (uname(&u) == -1) { return errno; } cp = strchr(u.nodename, '.'); if (cp) { *cp = '\0'; } strncpy(simplog->machine_name, u.nodename, sizeof(simplog->machine_name)); simplog->machine_name[sizeof(simplog->machine_name)-1] = '\0'; if (0 == strcmp("stdout", simplog->path)) { simplog->fp = stdout; } else if (0 == strcmp("stderr", simplog->path)) { simplog->fp = stderr; } else { simplog->fp = fopen(simplog->path, "a"); if (NULL == simplog->fp) { return errno; } } return 0; } /* * _logSimpleLog(priority, fmt, args); * * Create a message using the format 'fmt' and variable list 'args' * and write that message with the specified 'priority' to the * current FILE pointer given in the simple-log. The caller should * lock the mutex of the log before calling this function. */ static void _logSimpleVPrintf(int priority, const char *fmt, va_list args) { char msgbuf[MSGBUF_SIZE]; size_t len; if ( !(LOG_MASK(priority) & logctx->l_mask)) { return; } len = logctx->l_sim.stamp_fn(msgbuf, sizeof(msgbuf)); vsnprintf(msgbuf+len, (sizeof(msgbuf)-len), fmt, args); fprintf(logctx->l_sim.fp, "%s\n", msgbuf); fflush(logctx->l_sim.fp); } /* * status = _logStringifyCommand(argc, argv); * * Create a string holding the comamnd line paramters and store it * on the global context. Return 0 on success, or errno on * failure. */ static int _logStringifyCommand( int argc, char * const *argv) { size_t cmd_len; size_t rem_len; char *cp; int i; /* free an existing string */ if (logctx->l_cmd) { free(logctx->l_cmd); } /* get length of command string */ cmd_len = 1 + argc; for (i = 0; i < argc; ++i) { cmd_len += strlen(argv[i]); } logctx->l_cmd = calloc(cmd_len, sizeof(char)); if (!logctx->l_cmd) { return errno; } cp = logctx->l_cmd; rem_len = cmd_len; for (i = 0; i < argc; ++i) { if (i > 0) { *cp = ' '; ++cp; --rem_len; } strncat(cp, argv[i], rem_len); cp += strlen(argv[i]); assert((cp - logctx->l_cmd) < (int)cmd_len); rem_len = cmd_len - (cp - logctx->l_cmd); } return 0; } /* * _logVSyslog(priority, fmt, args); * * Create a message using the format 'fmt' and variable list * 'args', then write that message to syslog() with the specified * 'priority'. */ static void _logVSyslog(int priority, const char *fmt, va_list args) { char msgbuf[MSGBUF_SIZE]; vsnprintf(msgbuf, sizeof(msgbuf), fmt, args); msgbuf[sizeof(msgbuf)-1] = '\0'; syslog(priority, "%s", msgbuf); } /* * _logWriteCommandLine(); * * Write the command line string stored on the global context to * the log, free the string, and reset it to NULL. Assumes the log * is open and the command line string is non NULL. */ static void _logWriteCommandLine(void) { assert (logctx && logctx->l_open && logctx->l_cmd); NOTICEMSG("%s", logctx->l_cmd); free(logctx->l_cmd); logctx->l_cmd = NULL; } /* write the message to the log */ #if !defined(sklog) void sklog(int priority, const char *fmt, ...) { SKLOG_VARARG_CALL_LOGGER(priority, fmt); } #endif /* close a log if open. */ void sklogClose(void) { if (logctx && logctx->l_open) { NOTICEMSG("Stopped logging."); logctx->l_open = 0; switch (logctx->l_dest) { case SKLOG_DEST_NOT_SET: case SKLOG_DEST_NONE: break; case SKLOG_DEST_PATH: case SKLOG_DEST_STDOUT: case SKLOG_DEST_STDERR: case SKLOG_DEST_DIRECTORY: _logSimpleClose(); break; case SKLOG_DEST_BOTH: case SKLOG_DEST_SYSLOG: closelog(); break; } logctx->l_func = NULL; } } /* create command line string; write it to the log if open */ void sklogCommandLine(int argc, char * const *argv) { int rv; if ( !logctx) { return; } rv = _logStringifyCommand(argc, argv); if (rv != 0) { return; } if (logctx->l_open) { _logWriteCommandLine(); } return; } /* get the destination file handle */ FILE *sklogGetDestination(void) { if (logctx) { switch (logctx->l_dest) { case SKLOG_DEST_DIRECTORY: case SKLOG_DEST_STDOUT: case SKLOG_DEST_STDERR: case SKLOG_DEST_PATH: return logctx->l_sim.fp; case SKLOG_DEST_BOTH: return stderr; default: return NULL; } } return NULL; } /* get the log directory */ char *sklogGetDirectory(char *buf, size_t bufsize) { if ( !(logctx && logctx->l_rot.dir && logctx->l_rot.dir[0])) { return NULL; } strncpy(buf, logctx->l_rot.dir, bufsize); if ('\0' != buf[bufsize-1]) { /* buffer too short */ buf[bufsize-1] = '\0'; return NULL; } return buf; } /* open a log that has been Setup() and had its destination set */ int sklogOpen(void) { int rv; if (!logctx) { skAppPrintErr("Must setup the log before opening it"); return -1; } if (logctx->l_open) { /* called multiple times */ return 0; } switch (logctx->l_dest) { case SKLOG_DEST_NOT_SET: skAppPrintErr("Must set log destination prior to opening log"); return -1; case SKLOG_DEST_NONE: break; case SKLOG_DEST_DIRECTORY: rv = _logRotatedOpen(); if (rv) { skAppPrintErr("Unable to open log file '%s': %s", logctx->l_sim.path, strerror(rv)); return -1; } logctx->l_func = &_logRotatedLog; break; case SKLOG_DEST_STDOUT: case SKLOG_DEST_STDERR: case SKLOG_DEST_PATH: rv = _logSimpleOpen(); if (rv) { skAppPrintErr("Unable to open log file '%s': %s", logctx->l_sim.path, strerror(rv)); return -1; } logctx->l_func = &_logSimpleLog; break; case SKLOG_DEST_BOTH: #ifdef LOG_PERROR logctx->l_sys.options |= LOG_PERROR; #endif /* FALLTHROUGH */ case SKLOG_DEST_SYSLOG: openlog(skAppName(), logctx->l_sys.options, logctx->l_sys.facility); #ifdef HAVE_VSYSLOG logctx->l_func = &vsyslog; #else logctx->l_func = &_logVSyslog; #endif break; } logctx->l_open = 1; if (logctx->l_cmd) { _logWriteCommandLine(); } return 0; } /* print the usage of the options defined by this library */ void sklogOptionsUsage(FILE *fp) { int i, j; int features = INT32_MAX; if (logctx) { features = logctx->l_features; } for (i = 0; logOptions[i].name; ++i) { /* skip options that are not part of our feature set */ if ( !(logOptionsIsUsed[i] & features)) { continue; } fprintf(fp, "--%s %s. ", logOptions[i].name, SK_OPTION_HAS_ARG(logOptions[i])); switch ((logOptionsEnum)i) { case OPT_WARNINGS_EMAIL: fprintf(fp, ("Sends e-mail to these address(es) when fatal errors\n" "\toccur; a comma-separated list with no spaces")); break; case OPT_LOG_DIRECTORY: fprintf(fp, ("Writes log files to this directory and enables log\n" "\trotatation; must be the complete path to an existing" " directory")); break; case OPT_LOG_BASENAME: fprintf(fp, ("Uses this name as the basename for files in the\n" "\t%s. Def. '%s'"), logOptions[OPT_LOG_DIRECTORY].name, skAppName()); break; case OPT_LOG_PATHNAME: fprintf(fp, ("Writes log messages to this single file and disables\n" "\tlog rotation; must be a complete pathname")); break; case OPT_LOG_DESTINATION: fprintf(fp, ("Specifies the log destination. Acceptable values:\n" "\t")); for (j = 0; log_dest[j].name; ++j) { fprintf(fp, "'%s', ", log_dest[j].name); } fprintf(fp, "or\n\tcomplete path to a log file"); break; case OPT_LOG_LEVEL: fprintf(fp, ("Enable logging of messages of this severity." " Def. ")); for (j = 0; log_level[j].name; ++j) { if (SKLOG_DEFAULT_LEVEL == log_level[j].id) { fprintf(fp, "%s\n", log_level[j].name); break; } } fprintf(fp, "\tChoices: %s", log_level[0].name); for (j = 1; log_level[j].name; ++j) { fprintf(fp, ", %s", log_level[j].name); } break; case OPT_LOG_SYSFACILITY: fprintf(fp, ("Sets the facility to use for syslog() messages.\n" "\tDef. ")); for (j = 0; log_facility[j].name; ++j) { if (SKLOG_SYSFACILITY == log_facility[j].id) { fprintf(fp, ("%s (%" PRIu32 "). "), log_facility[j].name, log_facility[j].id); break; } } fprintf(fp, ("Specify as an integer or one of the following names:\n" "\t%s"), log_facility[0].name); for (j = 1; log_facility[j].name; ++j) { fprintf(fp, ",%s", log_facility[j].name); } fprintf(fp, ".\n\tSee syslog(3) and" " /usr/include/sys/syslog.h for integer values"); break; } fprintf(fp, "\n"); } } /* verify we got all the options we needed. */ int sklogOptionsVerify(void) { int dest_count = 0; int err_count = 0; /* only one of directory, pathname, or destination may be given, * and one must be given */ if (opt_values[OPT_LOG_DIRECTORY] != NULL) { ++dest_count; } if (opt_values[OPT_LOG_PATHNAME] != NULL) { ++dest_count; } if (opt_values[OPT_LOG_DESTINATION] != NULL) { ++dest_count; } if (dest_count == 0) { if ((logctx->l_features & (SKLOG_FEATURE_LEGACY|SKLOG_FEATURE_SYSLOG)) == (SKLOG_FEATURE_LEGACY|SKLOG_FEATURE_SYSLOG)) { skAppPrintErr("One of --%s, --%s, or\n\t--%s is required", logOptions[OPT_LOG_DIRECTORY].name, logOptions[OPT_LOG_PATHNAME].name, logOptions[OPT_LOG_DESTINATION].name); ++err_count; } else if (logctx->l_features & SKLOG_FEATURE_LEGACY) { skAppPrintErr("Either --%s or --%s is required", logOptions[OPT_LOG_DIRECTORY].name, logOptions[OPT_LOG_PATHNAME].name); ++err_count; } else if (logctx->l_features & SKLOG_FEATURE_SYSLOG) { skAppPrintErr("The --%s switch is required", logOptions[OPT_LOG_DESTINATION].name); ++err_count; } } else if (dest_count > 1) { if ((logctx->l_features & (SKLOG_FEATURE_LEGACY|SKLOG_FEATURE_SYSLOG)) == (SKLOG_FEATURE_LEGACY|SKLOG_FEATURE_SYSLOG)) { skAppPrintErr(("Only one of --%s, --%s, or\n" "\t--%s may be specified"), logOptions[OPT_LOG_DIRECTORY].name, logOptions[OPT_LOG_PATHNAME].name, logOptions[OPT_LOG_DESTINATION].name); ++err_count; } else if (logctx->l_features & SKLOG_FEATURE_LEGACY) { skAppPrintErr("Only one of --%s or --%s may be specified", logOptions[OPT_LOG_DIRECTORY].name, logOptions[OPT_LOG_PATHNAME].name); ++err_count; } else { assert(0); abort(); } } if (opt_values[OPT_LOG_BASENAME] && !opt_values[OPT_LOG_DIRECTORY]) { skAppPrintErr("May only use --%s when --%s is specified", logOptions[OPT_LOG_BASENAME].name, logOptions[OPT_LOG_DIRECTORY].name); ++err_count; } if (opt_values[OPT_LOG_DIRECTORY]) { if (sklogSetDirectory(opt_values[OPT_LOG_DIRECTORY], opt_values[OPT_LOG_BASENAME])) { ++err_count; } } if (opt_values[OPT_LOG_PATHNAME]) { if (opt_values[OPT_LOG_PATHNAME][0] != '/') { skAppPrintErr(("The --%s switch requires a complete path\n" "\t('%s' does not begin with a slash)"), logOptions[OPT_LOG_PATHNAME].name, opt_values[OPT_LOG_PATHNAME]); ++err_count; } else if (sklogSetDestination(opt_values[OPT_LOG_PATHNAME])) { ++err_count; } } if (opt_values[OPT_LOG_DESTINATION]) { if (sklogSetDestination(opt_values[OPT_LOG_DESTINATION])) { ++err_count; } } if (logctx->l_features & SKLOG_FEATURE_EMAIL) { if (opt_values[OPT_WARNINGS_EMAIL]) { if (sklogSetFatalEmail(opt_values[OPT_WARNINGS_EMAIL])) { ++err_count; } } else if (logctx->l_dest != SKLOG_DEST_SYSLOG && logctx->l_dest != SKLOG_DEST_BOTH) { skAppPrintErr("The --%s switch is required", logOptions[OPT_WARNINGS_EMAIL].name); ++err_count; } } if (opt_values[OPT_LOG_LEVEL]) { if (sklogSetLevel(opt_values[OPT_LOG_LEVEL])) { ++err_count; } } if (opt_values[OPT_LOG_SYSFACILITY]) { if (sklogSetFacilityByName(opt_values[OPT_LOG_SYSFACILITY])) { ++err_count; } } if (err_count) { return -1; } return 0; } /* set the destination */ int sklogSetDestination(const char *destination) { sk_stringmap_t *str_map = NULL; sk_stringmap_status_t rv_map; sk_stringmap_entry_t *map_entry; int rv = -1; assert(logctx); if (logctx->l_open) { skAppPrintErr("Cannot set destination after opening log"); return -1; } if (destination[0] == '/') { /* treat it as a pathname */ logctx->l_dest = SKLOG_DEST_PATH; if (dirExists(destination)) { skAppPrintErr("The %s must name a file, not a directory", logOptions[OPT_LOG_DESTINATION].name); return -1; } strncpy(logctx->l_sim.path, destination, sizeof(logctx->l_sim.path)); if ('\0' != logctx->l_sim.path[sizeof(logctx->l_sim.path)-1]) { skAppPrintErr("The %s path is too long", logOptions[OPT_LOG_DESTINATION].name); return -1; } return 0; } /* else, see which of the possible destinations it matches */ /* create a stringmap of the available entries */ if (SKSTRINGMAP_OK != skStringMapCreate(&str_map)) { skAppPrintErr("Unable to create stringmap"); goto END; } if (skStringMapAddIDArray(str_map, -1, log_dest) != SKSTRINGMAP_OK) { goto END; } /* attempt to match */ rv_map = skStringMapGetEntry(&map_entry, str_map, destination); switch (rv_map) { case SKSTRINGMAP_OK: logctx->l_dest = (sklog_dest_t)map_entry->id; rv = 0; break; case SKSTRINGMAP_PARSE_AMBIGUOUS: skAppPrintErr("The %s value '%s' is ambiguous", logOptions[OPT_LOG_DESTINATION].name, destination); goto END; case SKSTRINGMAP_PARSE_NO_MATCH: skAppPrintErr(("The %s value '%s' is not complete path and\n" "\tdoesn't match known keys"), logOptions[OPT_LOG_DESTINATION].name, destination); goto END; default: skAppPrintErr("Unexpected return value from string-map parser (%d)", rv_map); goto END; } if (logctx->l_dest == SKLOG_DEST_STDOUT) { strncpy(logctx->l_sim.path, "stdout", sizeof(logctx->l_sim.path)); } else if (logctx->l_dest == SKLOG_DEST_STDERR) { strncpy(logctx->l_sim.path, "stderr", sizeof(logctx->l_sim.path)); } END: if (str_map) { skStringMapDestroy(str_map); } return rv; } /* set the logger to use a directory with log rotation */ int sklogSetDirectory(const char *dir_name, const char *base_name) { assert(logctx); if (logctx->l_open) { skAppPrintErr("Cannot set directory after opening log."); return -1; } /* verify basename, or use skAppName if basename was not given */ if (base_name == NULL || base_name[0] == '\0') { base_name = skAppName(); } else if (strchr(base_name, '/')) { skAppPrintErr("The %s may not contain '/'", logOptions[OPT_LOG_BASENAME].name); return -1; } /* verify directory name */ if (dir_name == NULL || dir_name[0] == '\0') { skAppPrintErr("The %s is empty", logOptions[OPT_LOG_DIRECTORY].name); return -1; } if (!dirExists(dir_name)) { skAppPrintErr("The %s '%s'\n\tis not an existing directory", logOptions[OPT_LOG_DIRECTORY].name, dir_name); return -1; } if (dir_name[0] != '/') { skAppPrintErr(("Must use full path to %s\n" "\t('%s' does not begin with a slash)"), logOptions[OPT_LOG_DIRECTORY].name, dir_name); return -1; } /* copy directory name */ strncpy(logctx->l_rot.dir, dir_name, sizeof(logctx->l_rot.dir)); if ('\0' != logctx->l_rot.dir[sizeof(logctx->l_rot.dir)-1]) { skAppPrintErr("The %s is too long: '%s'", logOptions[OPT_LOG_DIRECTORY].name, dir_name); return -1; } /* copy base name */ strncpy(logctx->l_rot.basename, base_name, sizeof(logctx->l_rot.basename)); if ('\0' != logctx->l_rot.basename[sizeof(logctx->l_rot.basename)-1]) { skAppPrintErr("The %s is too long: '%s'", logOptions[OPT_LOG_BASENAME].name, base_name); return -1; } logctx->l_dest = SKLOG_DEST_DIRECTORY; return 0; } /* set the facility for syslog() */ int sklogSetFacility(int facility) { assert(logctx); if (logctx->l_open) { skAppPrintErr("Cannot set facility after opening log."); return -1; } if (logctx->l_dest == SKLOG_DEST_BOTH || logctx->l_dest == SKLOG_DEST_SYSLOG) { logctx->l_sys.facility = facility; return 0; } skAppPrintErr("Cannot set facility unless %s is 'syslog' or 'both'", logOptions[OPT_LOG_DESTINATION].name); return -1; } /* set the facility for syslog() by name; can be a name or an integer */ int sklogSetFacilityByName(const char *name_or_number) { sk_stringmap_t *str_map = NULL; sk_stringmap_status_t rv_map; sk_stringmap_entry_t *found_entry; uint32_t facility; int rv = -1; assert(logctx); if (logctx->l_open) { skAppPrintErr("Cannot set facility after opening log."); return -1; } /* try to parse the facility as a number */ rv = skStringParseUint32(&facility, name_or_number, 0, INT32_MAX); if (rv == 0) { /* was parsable as a number */ return sklogSetFacility(facility); } /* a return value of -3 means the value was unparsable--we will * try to treat it as a name. any other value indicates an * error */ if (rv != -3) { skAppPrintErr("Unable to parse %s value '%s'", logOptions[OPT_LOG_SYSFACILITY].name, name_or_number); return -1; } /* reset rv */ rv = -1; /* create a stringmap of the available levels */ if (SKSTRINGMAP_OK != skStringMapCreate(&str_map)) { skAppPrintErr("Unable to create stringmap"); goto END; } if (skStringMapAddIDArray(str_map, -1, log_facility) != SKSTRINGMAP_OK) { goto END; } /* attempt to match */ rv_map = skStringMapGetEntry(&found_entry, str_map, name_or_number); switch (rv_map) { case SKSTRINGMAP_OK: rv = sklogSetFacility(found_entry->id); break; case SKSTRINGMAP_PARSE_AMBIGUOUS: skAppPrintErr("%s value '%s' is ambiguous", logOptions[OPT_LOG_SYSFACILITY].name, name_or_number); break; case SKSTRINGMAP_PARSE_NO_MATCH: skAppPrintErr("%s value '%s' is not recognized", logOptions[OPT_LOG_SYSFACILITY].name, name_or_number); break; default: skAppPrintErr("Unexpected return value from string-map parser (%d)", rv_map); break; } END: if (str_map) { skStringMapDestroy(str_map); } return rv; } /* set the email address to use for fatal errors */ int sklogSetFatalEmail(const char *user) { const char *cp; if (logctx->l_dest == SKLOG_DEST_BOTH || logctx->l_dest == SKLOG_DEST_SYSLOG) { skAppPrintErr("Cannot use email notification when syslog() is used"); return -1; } if (user[0] == '\0') { skAppPrintErr("Cannot set user email to the empty string"); return -1; } if ((cp = strchr(user, '\"')) || (cp = strchr(user, '\\'))) { skAppPrintErr("Email address '%s' contains unsupported character '%c'", user, *cp); return -1; } strncpy(logctx->l_sim.mail_user, user, sizeof(logctx->l_sim.mail_user)); if ('\0' != logctx->l_sim.mail_user[sizeof(logctx->l_sim.mail_user)-1]) { skAppPrintErr("Email address '%s' is too long", user); return -1; } return 0; } /* set logging level to all levels through 'level', a string. */ int sklogSetLevel(const char *level) { sk_stringmap_t *str_map = NULL; sk_stringmap_status_t rv_map; sk_stringmap_entry_t *found_entry; int rv = -1; /* create a stringmap of the available levels */ if (SKSTRINGMAP_OK != skStringMapCreate(&str_map)) { skAppPrintErr("Unable to create stringmap"); goto END; } if (skStringMapAddIDArray(str_map, -1, log_level) != SKSTRINGMAP_OK) { goto END; } /* attempt to match */ rv_map = skStringMapGetEntry(&found_entry, str_map, level); switch (rv_map) { case SKSTRINGMAP_OK: sklogSetMask(LOG_UPTO(found_entry->id)); rv = 0; break; case SKSTRINGMAP_PARSE_AMBIGUOUS: skAppPrintErr("%s value '%s' is ambiguous", logOptions[OPT_LOG_LEVEL].name, level); break; case SKSTRINGMAP_PARSE_NO_MATCH: skAppPrintErr("%s value '%s' is not recognized", logOptions[OPT_LOG_LEVEL].name, level); break; default: skAppPrintErr("Unexpected return value from string-map parser (%d)", rv_map); break; } END: if (str_map) { skStringMapDestroy(str_map); } return rv; } /* set lock and unlock functions */ int sklogSetLocking( sklog_lock_fn_t locker, sklog_lock_fn_t unlocker, void *data) { if (!logctx) { skAppPrintErr("Must setup the log before setting lock functions"); return -1; } logctx->l_lock_fn = locker; logctx->l_unlock_fn = unlocker; logctx->l_lock_data = data; return 0; } /* set the mask for the logger */ int sklogSetMask(int new_mask) { int old_mask = logctx->l_mask; logctx->l_mask = new_mask; switch (logctx->l_dest) { case SKLOG_DEST_NOT_SET: case SKLOG_DEST_NONE: case SKLOG_DEST_PATH: case SKLOG_DEST_DIRECTORY: case SKLOG_DEST_STDOUT: case SKLOG_DEST_STDERR: break; case SKLOG_DEST_BOTH: case SKLOG_DEST_SYSLOG: old_mask = setlogmask(new_mask); break; } return old_mask; } /* set the function to make that timestamp. will be used instead of * _logMakeStamp(). */ int sklogSetStampFunction(sklog_stamp_fn_t makestamp) { if (!logctx) { skAppPrintErr("Must setup the log before setting lock functions"); return -1; } if (logctx->l_dest == SKLOG_DEST_BOTH || logctx->l_dest == SKLOG_DEST_SYSLOG) { skAppPrintErr("Stamp function is ignored when syslog() is used"); return 0; } if (makestamp == NULL) { skAppPrintErr("Stamp function cannot be NULL"); return -1; } logctx->l_sim.stamp_fn = makestamp; return 0; } /* initialize all variables for logging */ int sklogSetup(int feature_flags) { static struct option options_used[NUM_OPTIONS+1]; int opt_count = 0; int i; /* initialize the logging context */ logctx = &logger; memset(logctx, 0, sizeof(sklog_context_t)); logctx->l_dest = SKLOG_DEST_NOT_SET; logctx->l_mask = LOG_UPTO(SKLOG_DEFAULT_LEVEL); logctx->l_sys.options = SKLOG_SYSOPTIONS; logctx->l_sys.facility = SKLOG_SYSFACILITY; logctx->l_features = feature_flags; assert(NUM_OPTIONS == (sizeof(logOptions)/sizeof(struct option) - 1)); assert(NUM_OPTIONS == (sizeof(logOptionsIsUsed)/sizeof(int))); /* loop through the options, copying those that the caller needs * into 'options_used'. 'opt_count' is the number of 'struct * option' entries we have */ for (i = 0; logOptions[i].name; ++i) { if (feature_flags & logOptionsIsUsed[i]) { /* use this option */ memcpy(&(options_used[opt_count]), &(logOptions[i]), sizeof(struct option)); ++opt_count; } } /* set sentinel */ memset(&(options_used[opt_count]), 0, sizeof(struct option)); /* register the options */ if (opt_count > 0) { if (optionsRegister((void*)options_used, &_logOptionsHandler, (clientData)opt_values)) { return -1; } } return 0; } /* close the logger */ void sklogTeardown(void) { if (logctx == NULL) { /* either never set up or already shut down */ return; } sklogClose(); if (logctx->l_cmd) { free(logctx->l_cmd); } memset(logctx, 0, sizeof(sklog_context_t)); logctx = NULL; } /* write the log message */ void sklogv(int priority, const char *fmt, va_list args) { SKLOG_CALL_LOGGER(priority, fmt, args); } /* ** ************************************************************************** ** OLD LOGGING LEGACY COMPATIBILITY SUPPORT ** ************************************************************************** */ #ifndef logMsg /* * Sometimes it's useful to * * #define skAppPrintErr printf * * to see where we've messed up our formatting. When doing that, we * obviously do not want to compile this function. */ int logMsg(const char *fmt, ...) { SKLOG_VARARG_CALL_LOGGER(SKLOG_LEGACY_LOG_LEVEL, fmt); return 1; } #endif /* !defined(logMsg) */ int vlogMsg(const char *fmt, va_list args) { SKLOG_CALL_LOGGER(SKLOG_LEGACY_LOG_LEVEL, fmt, args); return 1; } #undef report int report( const char *subject, const char *fmt, ...) { va_list args; va_start(args, fmt); SKLOG_CALL_LOGGER(SKLOG_LEGACY_REPORT_LEVEL, fmt, args); if (logctx && logctx->l_sim.mail_user) { #ifdef MAILX _logMailSend(logctx->l_sim.mail_user, subject, fmt, args); #else /* avoid unused variable warnings */ INFOMSG("No mailer specified; not running 'mail -s \"%s\" \"%s\"", user, subject); #endif } va_end(args); return 1; } /* ** Local Variables: ** mode:c ** indent-tabs-mode:nil ** c-basic-offset:4 ** End: */