bin/109478: Reentrant syslog functions
Steven Kreuzer
skreuzer at f2o.org
Fri Feb 23 22:10:06 UTC 2007
>Number: 109478
>Category: bin
>Synopsis: Reentrant syslog functions
>Confidential: no
>Severity: non-critical
>Priority: low
>Responsible: freebsd-bugs
>State: open
>Quarter:
>Keywords:
>Date-Required:
>Class: update
>Submitter-Id: current-users
>Arrival-Date: Fri Feb 23 22:10:05 GMT 2007
>Closed-Date:
>Last-Modified:
>Originator: Steven Kreuzer
>Release: 6.2-STABLE
>Organization:
>Environment:
FreeBSD escobar.exit2shell.com 6.2-STABLE FreeBSD 6.2-STABLE #3: Wed Feb 7 18:28:56 EST 2007 root at escobar.exit2shell.com:/usr/obj/usr/src/sys/ESCOBAR i386
>Description:
The patch essentially takes OpenBSD's reentrant syslog functions
(openlog_r, closelog_r, syslog_r and vsyslog_r) and makes them
available in FreeBSD. Hopefully this will make building of packages
such as spamd under FreeBSD easier since the source shouldn't require
modifications.
>How-To-Repeat:
>Fix:
Patch attached with submission follows:
Index: sys/sys/syslog.h
===================================================================
RCS file: /usr/share/cvs/freebsd/src/sys/sys/syslog.h,v
retrieving revision 1.26
diff -u -r1.26 syslog.h
--- sys/sys/syslog.h 7 Jan 2005 02:29:24 -0000 1.26
+++ sys/sys/syslog.h 7 Feb 2007 23:16:08 -0000
@@ -151,6 +151,20 @@
};
#endif
+/* Used by reentrant functions */
+
+struct syslog_data {
+ int log_file;
+ int connected;
+ int opened;
+ int log_stat;
+ const char *log_tag;
+ int log_fac;
+ int log_mask;
+};
+
+#define SYSLOG_DATA_INIT {-1, 0, 0, 0, (const char *)0, LOG_USER, 0xff}
+
#ifdef _KERNEL
#define LOG_PRINTF -1 /* pseudo-priority to indicate use of printf */
#endif
@@ -194,6 +208,12 @@
int setlogmask(int);
void syslog(int, const char *, ...) __printflike(2, 3);
void vsyslog(int, const char *, __va_list) __printflike(2, 0);
+void closelog_r(struct syslog_data *);
+void openlog_r(const char *, int, int, struct syslog_data *);
+int setlogmask_r(int, struct syslog_data *);
+void syslog_r(int, struct syslog_data *, const char *, ...)
+ __printflike(3, 4);
+void vsyslog_r(int, struct syslog_data *, const char *, __va_list);
__END_DECLS
#endif /* !_KERNEL */
Index: lib/libc/gen/syslog.c
===================================================================
RCS file: /usr/share/cvs/freebsd/src/lib/libc/gen/syslog.c,v
retrieving revision 1.39
diff -u -r1.39 syslog.c
--- lib/libc/gen/syslog.c 9 Jan 2007 00:27:55 -0000 1.39
+++ lib/libc/gen/syslog.c 7 Feb 2007 23:16:43 -0000
@@ -56,14 +56,7 @@
#include "libc_private.h"
-static int LogFile = -1; /* fd for log */
-static int status; /* connection status */
-static int opened; /* have done openlog() */
-static int LogStat = 0; /* status bits, set by openlog() */
-static const char *LogTag = NULL; /* string to tag the entry with */
-static int LogFacility = LOG_USER; /* default facility code */
-static int LogMask = 0xff; /* mask of priorities to be logged */
-static pthread_mutex_t syslog_mutex = PTHREAD_MUTEX_INITIALIZER;
+static struct syslog_data sdata = SYSLOG_DATA_INIT;
#define THREAD_LOCK() \
do { \
@@ -74,9 +67,8 @@
if (__isthreaded) _pthread_mutex_unlock(&syslog_mutex); \
} while(0)
-static void disconnectlog(void); /* disconnect from syslogd */
-static void connectlog(void); /* (re)connect to syslogd */
-static void openlog_unlocked(const char *, int, int);
+static void connectlog_r(struct syslog_data *data);
+static void disconnectlog_r(struct syslog_data *data);
enum {
NOCONN = 0,
@@ -93,29 +85,6 @@
};
/*
- * stdio write hook for writing to a static string buffer
- * XXX: Maybe one day, dynamically allocate it so that the line length
- * is `unlimited'.
- */
-static int
-writehook(void *cookie, const char *buf, int len)
-{
- struct bufcookie *h; /* private `handle' */
-
- h = (struct bufcookie *)cookie;
- if (len > h->left) {
- /* clip in case of wraparound */
- len = h->left;
- }
- if (len > 0) {
- (void)memcpy(h->base, buf, len); /* `write' it. */
- h->base += len;
- h->left -= len;
- }
- return len;
-}
-
-/*
* syslog, vsyslog --
* print message on log file; output is intended for syslogd(8).
*/
@@ -132,166 +101,197 @@
void
vsyslog(int pri, const char *fmt, va_list ap)
{
+ vsyslog_r(pri, &sdata, fmt, ap);
+}
+
+void
+openlog(const char *ident, int logstat, int logfac)
+{
+ openlog_r(ident, logstat, logfac, &sdata);
+}
+
+void
+closelog(void)
+{
+ closelog_r(&sdata);
+}
+
+/* setlogmask -- set the log mask level */
+int
+setlogmask(int pmask)
+{
+ return setlogmask_r(pmask, &sdata);
+}
+
+/* Reentrant version of syslog, i.e. syslog_r() */
+
+void
+syslog_r(int pri, struct syslog_data *data, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsyslog_r(pri, data, fmt, ap);
+ va_end(ap);
+}
+
+void
+vsyslog_r(int pri, struct syslog_data *data, const char *fmt, va_list ap)
+{
int cnt;
- char ch, *p;
+ char ch, *p, *t;
time_t now;
- int fd, saved_errno;
- char *stdp, tbuf[2048], fmt_cpy[1024], timbuf[26], errstr[64];
- FILE *fp, *fmt_fp;
- struct bufcookie tbuf_cookie;
- struct bufcookie fmt_cookie;
+ int fd, saved_errno, error;
+#define TBUF_LEN 2048
+#define FMT_LEN 1024
+ char *stdp, tbuf[TBUF_LEN], fmt_cpy[FMT_LEN];
+ int tbuf_left, fmt_left, prlen;
#define INTERNALLOG LOG_ERR|LOG_CONS|LOG_PERROR|LOG_PID
/* Check for invalid bits. */
if (pri & ~(LOG_PRIMASK|LOG_FACMASK)) {
- syslog(INTERNALLOG,
- "syslog: unknown facility/priority: %x", pri);
+ if (data == &sdata) {
+ syslog(INTERNALLOG,
+ "syslog: unknown facility/priority: %x", pri);
+ } else {
+ syslog_r(INTERNALLOG, data,
+ "syslog_r: unknown facility/priority: %x", pri);
+ }
pri &= LOG_PRIMASK|LOG_FACMASK;
}
- saved_errno = errno;
-
- THREAD_LOCK();
-
/* Check priority against setlogmask values. */
- if (!(LOG_MASK(LOG_PRI(pri)) & LogMask)) {
- THREAD_UNLOCK();
+ if (!(LOG_MASK(LOG_PRI(pri)) & data->log_mask))
return;
- }
+
+ saved_errno = errno;
/* Set default facility if none specified. */
if ((pri & LOG_FACMASK) == 0)
- pri |= LogFacility;
+ pri |= data->log_fac;
- /* Create the primary stdio hook */
- tbuf_cookie.base = tbuf;
- tbuf_cookie.left = sizeof(tbuf);
- fp = fwopen(&tbuf_cookie, writehook);
- if (fp == NULL) {
- THREAD_UNLOCK();
- return;
- }
-
- /* Build the message. */
- (void)time(&now);
- (void)fprintf(fp, "<%d>", pri);
- (void)fprintf(fp, "%.15s ", ctime_r(&now, timbuf) + 4);
- if (LogStat & LOG_PERROR) {
- /* Transfer to string buffer */
- (void)fflush(fp);
- stdp = tbuf + (sizeof(tbuf) - tbuf_cookie.left);
- }
- if (LogTag == NULL)
- LogTag = _getprogname();
- if (LogTag != NULL)
- (void)fprintf(fp, "%s", LogTag);
- if (LogStat & LOG_PID)
- (void)fprintf(fp, "[%d]", getpid());
- if (LogTag != NULL) {
- (void)fprintf(fp, ": ");
+ /* If we have been called through syslog(), no need for reentrancy. */
+ if (data == &sdata)
+ (void)time(&now);
+
+ p = tbuf;
+ tbuf_left = TBUF_LEN;
+
+#define DEC() \
+ do { \
+ if (prlen < 0) \
+ prlen = 0; \
+ if (prlen >= tbuf_left) \
+ prlen = tbuf_left - 1; \
+ p += prlen; \
+ tbuf_left -= prlen; \
+ } while (0)
+
+ prlen = snprintf(p, tbuf_left, "<%d>", pri);
+ DEC();
+
+ /*
+ * syslogd will expand time automagically for reentrant case, and
+ * for normal case, just do like before
+ */
+ if (data == &sdata) {
+ prlen = strftime(p, tbuf_left, "%h %e %T ", localtime(&now));
+ DEC();
+ }
+
+ if (data->log_stat & LOG_PERROR)
+ stdp = p;
+ if (data->log_tag == NULL)
+ data->log_tag = __progname;
+ if (data->log_tag != NULL) {
+ prlen = snprintf(p, tbuf_left, "%s", data->log_tag);
+ DEC();
+ }
+ if (data->log_stat & LOG_PID) {
+ prlen = snprintf(p, tbuf_left, "[%ld]", (long)getpid());
+ DEC();
+ }
+ if (data->log_tag != NULL) {
+ if (tbuf_left > 1) {
+ *p++ = ':';
+ tbuf_left--;
+ }
+ if (tbuf_left > 1) {
+ *p++ = ' ';
+ tbuf_left--;
+ }
}
- /* Check to see if we can skip expanding the %m */
- if (strstr(fmt, "%m")) {
-
- /* Create the second stdio hook */
- fmt_cookie.base = fmt_cpy;
- fmt_cookie.left = sizeof(fmt_cpy) - 1;
- fmt_fp = fwopen(&fmt_cookie, writehook);
- if (fmt_fp == NULL) {
- fclose(fp);
- THREAD_UNLOCK();
- return;
- }
+ /* strerror() is not reentrant */
- /*
- * Substitute error message for %m. Be careful not to
- * molest an escaped percent "%%m". We want to pass it
- * on untouched as the format is later parsed by vfprintf.
- */
- for ( ; (ch = *fmt); ++fmt) {
- if (ch == '%' && fmt[1] == 'm') {
- ++fmt;
- strerror_r(saved_errno, errstr, sizeof(errstr));
- fputs(errstr, fmt_fp);
- } else if (ch == '%' && fmt[1] == '%') {
- ++fmt;
- fputc(ch, fmt_fp);
- fputc(ch, fmt_fp);
+ for (t = fmt_cpy, fmt_left = FMT_LEN; (ch = *fmt); ++fmt) {
+ if (ch == '%' && fmt[1] == 'm') {
+ ++fmt;
+ if (data == &sdata) {
+ prlen = snprintf(t, fmt_left, "%s",
+ strerror(saved_errno));
} else {
- fputc(ch, fmt_fp);
+ prlen = snprintf(t, fmt_left, "Error %d",
+ saved_errno);
+ }
+ if (prlen < 0)
+ prlen = 0;
+ if (prlen >= fmt_left)
+ prlen = fmt_left - 1;
+ t += prlen;
+ fmt_left -= prlen;
+ } else if (ch == '%' && fmt[1] == '%' && fmt_left > 2) {
+ *t++ = '%';
+ *t++ = '%';
+ fmt++;
+ fmt_left -= 2;
+ } else {
+ if (fmt_left > 1) {
+ *t++ = ch;
+ fmt_left--;
}
}
-
- /* Null terminate if room */
- fputc(0, fmt_fp);
- fclose(fmt_fp);
-
- /* Guarantee null termination */
- fmt_cpy[sizeof(fmt_cpy) - 1] = '\0';
-
- fmt = fmt_cpy;
}
+ *t = '\0';
- (void)vfprintf(fp, fmt, ap);
- (void)fclose(fp);
-
- cnt = sizeof(tbuf) - tbuf_cookie.left;
-
- /* Remove a trailing newline */
- if (tbuf[cnt - 1] == '\n')
- cnt--;
+ prlen = vsnprintf(p, tbuf_left, fmt_cpy, ap);
+ DEC();
+ cnt = p - tbuf;
/* Output to stderr if requested. */
- if (LogStat & LOG_PERROR) {
+ if (data->log_stat & LOG_PERROR) {
struct iovec iov[2];
- struct iovec *v = iov;
- v->iov_base = stdp;
- v->iov_len = cnt - (stdp - tbuf);
- ++v;
- v->iov_base = "\n";
- v->iov_len = 1;
+ iov[0].iov_base = stdp;
+ iov[0].iov_len = cnt - (stdp - tbuf);
+ iov[1].iov_base = "\n";
+ iov[1].iov_len = 1;
(void)_writev(STDERR_FILENO, iov, 2);
}
/* Get connected, output the message to the local logger. */
- if (!opened)
- openlog_unlocked(LogTag, LogStat | LOG_NDELAY, 0);
- connectlog();
+ if (!data->opened)
+ openlog_r(data->log_tag, data->log_stat, 0, data);
+ connectlog_r(data);
/*
- * If the send() failed, there are two likely scenarios:
+ * If the send() failed, there are two likely scenarios:
* 1) syslogd was restarted
- * 2) /var/run/log is out of socket buffer space, which
- * in most cases means local DoS.
- * We attempt to reconnect to /var/run/log to take care of
+ * 2) /dev/log is out of socket buffer space
+ * We attempt to reconnect to /dev/log to take care of
* case #1 and keep send()ing data to cover case #2
* to give syslogd a chance to empty its socket buffer.
- *
- * If we are working with a priveleged socket, then take
- * only one attempt, because we don't want to freeze a
- * critical application like su(1) or sshd(8).
- *
*/
-
- if (send(LogFile, tbuf, cnt, 0) < 0) {
+ if ((error = send(data->log_file, tbuf, cnt, 0)) < 0) {
if (errno != ENOBUFS) {
- disconnectlog();
- connectlog();
+ disconnectlog_r(data);
+ connectlog_r(data);
}
do {
_usleep(1);
- if (send(LogFile, tbuf, cnt, 0) >= 0) {
- THREAD_UNLOCK();
- return;
- }
- if (status == CONNPRIV)
+ if ((error = send(data->log_file, tbuf, cnt, 0)) >= 0)
break;
} while (errno == ENOBUFS);
- } else {
- THREAD_UNLOCK();
- return;
}
/*
@@ -299,137 +299,95 @@
* as a blocking console should not stop other processes.
* Make sure the error reported is the one from the syslogd failure.
*/
- if (LogStat & LOG_CONS &&
+ if (error == -1 && (data->log_stat & LOG_CONS) &&
(fd = _open(_PATH_CONSOLE, O_WRONLY|O_NONBLOCK, 0)) >= 0) {
struct iovec iov[2];
- struct iovec *v = iov;
-
+
p = strchr(tbuf, '>') + 1;
- v->iov_base = p;
- v->iov_len = cnt - (p - tbuf);
- ++v;
- v->iov_base = "\r\n";
- v->iov_len = 2;
+ iov[0].iov_base = p;
+ iov[0].iov_len = cnt - (p - tbuf);
+ iov[1].iov_base = "\r\n";
+ iov[1].iov_len = 2;
(void)_writev(fd, iov, 2);
(void)_close(fd);
}
- THREAD_UNLOCK();
+ if (data != &sdata)
+ closelog_r(data);
}
-/* Should be called with mutex acquired */
static void
-disconnectlog(void)
+disconnectlog_r(struct syslog_data *data)
{
- /*
- * If the user closed the FD and opened another in the same slot,
- * that's their problem. They should close it before calling on
- * system services.
- */
- if (LogFile != -1) {
- _close(LogFile);
- LogFile = -1;
- }
- status = NOCONN; /* retry connect */
+ /*
+ * If the user closed the FD and opened another in the same slot,
+ * that's their problem. They should close it before calling on
+ * system services.
+ */
+ if (data->log_file != -1) {
+ _close(data->log_file);
+ data->log_file = -1;
+ }
+ data->connected = 0; /* retry connect */
}
-/* Should be called with mutex acquired */
static void
-connectlog(void)
+connectlog_r(struct syslog_data *data)
{
- struct sockaddr_un SyslogAddr; /* AF_UNIX address of local logger */
-
- if (LogFile == -1) {
- if ((LogFile = _socket(AF_UNIX, SOCK_DGRAM, 0)) == -1)
- return;
- (void)_fcntl(LogFile, F_SETFD, 1);
- }
- if (LogFile != -1 && status == NOCONN) {
- SyslogAddr.sun_len = sizeof(SyslogAddr);
- SyslogAddr.sun_family = AF_UNIX;
-
- /*
- * First try priveleged socket. If no success,
- * then try default socket.
- */
- (void)strncpy(SyslogAddr.sun_path, _PATH_LOG_PRIV,
- sizeof SyslogAddr.sun_path);
- if (_connect(LogFile, (struct sockaddr *)&SyslogAddr,
- sizeof(SyslogAddr)) != -1)
- status = CONNPRIV;
-
- if (status == NOCONN) {
- (void)strncpy(SyslogAddr.sun_path, _PATH_LOG,
- sizeof SyslogAddr.sun_path);
- if (_connect(LogFile, (struct sockaddr *)&SyslogAddr,
- sizeof(SyslogAddr)) != -1)
- status = CONNDEF;
- }
-
- if (status == NOCONN) {
- /*
- * Try the old "/dev/log" path, for backward
- * compatibility.
- */
- (void)strncpy(SyslogAddr.sun_path, _PATH_OLDLOG,
- sizeof SyslogAddr.sun_path);
- if (_connect(LogFile, (struct sockaddr *)&SyslogAddr,
- sizeof(SyslogAddr)) != -1)
- status = CONNDEF;
- }
+ struct sockaddr_un SyslogAddr; /* AF_UNIX address of local logger */
- if (status == NOCONN) {
- (void)_close(LogFile);
- LogFile = -1;
- }
- }
+ if (data->log_file == -1) {
+ if ((data->log_file = _socket(AF_UNIX, SOCK_DGRAM, 0)) == -1)
+ return;
+ (void)_fcntl(data->log_file, F_SETFD, 1);
+ }
+ if (data->log_file != -1 && !data->connected) {
+ memset(&SyslogAddr, '\0', sizeof(SyslogAddr));
+ SyslogAddr.sun_len = sizeof(SyslogAddr);
+ SyslogAddr.sun_family = AF_UNIX;
+ strlcpy(SyslogAddr.sun_path, _PATH_LOG,
+ sizeof(SyslogAddr.sun_path));
+ if (_connect(data->log_file, (struct sockaddr *)&SyslogAddr,
+ sizeof(SyslogAddr)) == -1) {
+ (void)_close(data->log_file);
+ data->log_file = -1;
+ } else
+ data->connected = 1;
+ }
}
-static void
-openlog_unlocked(const char *ident, int logstat, int logfac)
+void
+openlog_r(const char *ident, int logstat, int logfac, struct syslog_data *data)
{
- if (ident != NULL)
- LogTag = ident;
- LogStat = logstat;
- if (logfac != 0 && (logfac &~ LOG_FACMASK) == 0)
- LogFacility = logfac;
+ if (ident != NULL)
+ data->log_tag = ident;
+ data->log_stat = logstat;
+ if (logfac != 0 && (logfac &~ LOG_FACMASK) == 0)
+ data->log_fac = logfac;
- if (LogStat & LOG_NDELAY) /* open immediately */
- connectlog();
+ if (data->log_stat & LOG_NDELAY) /* open immediately */
+ connectlog_r(data);
- opened = 1; /* ident and facility has been set */
-}
-
-void
-openlog(const char *ident, int logstat, int logfac)
-{
- THREAD_LOCK();
- openlog_unlocked(ident, logstat, logfac);
- THREAD_UNLOCK();
+ data->opened = 1; /* ident and facility has been set */
}
-
void
-closelog(void)
+closelog_r(struct syslog_data *data)
{
- THREAD_LOCK();
- (void)_close(LogFile);
- LogFile = -1;
- LogTag = NULL;
- status = NOCONN;
- THREAD_UNLOCK();
+ (void)_close(data->log_file);
+ data->log_file = -1;
+ data->connected = 0;
+ data->log_tag = NULL;
}
/* setlogmask -- set the log mask level */
int
-setlogmask(int pmask)
+setlogmask_r(int pmask, struct syslog_data *data)
{
- int omask;
+ int omask;
- THREAD_LOCK();
- omask = LogMask;
- if (pmask != 0)
- LogMask = pmask;
- THREAD_UNLOCK();
- return (omask);
+ omask = data->log_mask;
+ if (pmask != 0)
+ data->log_mask = pmask;
+ return (omask);
}
>Release-Note:
>Audit-Trail:
>Unformatted:
More information about the freebsd-bugs
mailing list