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