xref: /openbsd/lib/libc/gen/syslog_r.c (revision 17df1aa7)
1 /*	$OpenBSD: syslog_r.c,v 1.2 2009/11/21 10:23:31 chl Exp $ */
2 /*
3  * Copyright (c) 1983, 1988, 1993
4  *	The Regents of the University of California.  All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. Neither the name of the University nor the names of its contributors
15  *    may be used to endorse or promote products derived from this software
16  *    without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30 
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <sys/syslog.h>
34 #include <sys/uio.h>
35 #include <sys/un.h>
36 #include <netdb.h>
37 
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <paths.h>
41 #include <stdio.h>
42 #include <string.h>
43 #include <time.h>
44 #include <unistd.h>
45 #include <stdarg.h>
46 
47 extern char	*__progname;		/* Program name, from crt0. */
48 
49 static void	disconnectlog_r(struct syslog_data *);	/* disconnect from syslogd */
50 static void	connectlog_r(struct syslog_data *);	/* (re)connect to syslogd */
51 
52 void	__vsyslog_r(int pri, struct syslog_data *, size_t (*)(char *, size_t),
53     const char *, va_list);
54 
55 /* Reentrant version of syslog, i.e. syslog_r() */
56 
57 void
58 syslog_r(int pri, struct syslog_data *data, const char *fmt, ...)
59 {
60 	va_list ap;
61 
62 	va_start(ap, fmt);
63 	vsyslog_r(pri, data, fmt, ap);
64 	va_end(ap);
65 }
66 
67 void
68 vsyslog_r(int pri, struct syslog_data *data, const char *fmt, va_list ap)
69 {
70 	const char *ident;
71 
72 	__vsyslog_r(pri, data, NULL, fmt, ap);
73 
74 	/* close the socket without losing log_tag */
75 	ident = data->log_tag;
76 	closelog_r(data);
77 	data->log_tag = ident;
78 }
79 
80 /*
81  * This is used by both syslog_r and syslog.  The latter supplies
82  * a non-NULL gettime callback for filling in the date, but we also
83  * use the presence of that callback to decide whether it's safe
84  * to call strerror and what the name of the caller is
85  */
86 void
87 __vsyslog_r(int pri, struct syslog_data *data,
88     size_t (*gettime)(char *, size_t), const char *fmt, va_list ap)
89 {
90 	int cnt;
91 	char ch, *p, *t;
92 	int fd, saved_errno, error;
93 #define	TBUF_LEN	2048
94 #define	FMT_LEN		1024
95 	char *stdp, tbuf[TBUF_LEN], fmt_cpy[FMT_LEN];
96 	int tbuf_left, fmt_left, prlen;
97 
98 #define	INTERNALLOG	LOG_ERR|LOG_CONS|LOG_PERROR|LOG_PID
99 	/* Check for invalid bits. */
100 	if (pri & ~(LOG_PRIMASK|LOG_FACMASK)) {
101 		syslog_r(INTERNALLOG, data,
102 		    "syslog%s: unknown facility/priority: %x",
103 		    gettime != NULL ? "" : "_r", pri);
104 		pri &= LOG_PRIMASK|LOG_FACMASK;
105 	}
106 
107 	/* Check priority against setlogmask values. */
108 	if (!(LOG_MASK(LOG_PRI(pri)) & data->log_mask))
109 		return;
110 
111 	saved_errno = errno;
112 
113 	/* Set default facility if none specified. */
114 	if ((pri & LOG_FACMASK) == 0)
115 		pri |= data->log_fac;
116 
117 	p = tbuf;
118 	tbuf_left = TBUF_LEN;
119 
120 #define	DEC()	\
121 	do {					\
122 		if (prlen < 0)			\
123 			prlen = 0;		\
124 		if (prlen >= tbuf_left)		\
125 			prlen = tbuf_left - 1;	\
126 		p += prlen;			\
127 		tbuf_left -= prlen;		\
128 	} while (0)
129 
130 	prlen = snprintf(p, tbuf_left, "<%d>", pri);
131 	DEC();
132 
133 	/*
134 	 * syslogd will expand time automagically for reentrant case, and
135 	 * for normal case, invoke the callback to do it just do like before
136 	 */
137 	if (gettime != NULL) {
138 		prlen = gettime(p, tbuf_left);
139 		DEC();
140 	}
141 
142 	if (data->log_stat & LOG_PERROR)
143 		stdp = p;
144 	if (data->log_tag == NULL)
145 		data->log_tag = __progname;
146 	if (data->log_tag != NULL) {
147 		prlen = snprintf(p, tbuf_left, "%s", data->log_tag);
148 		DEC();
149 	}
150 	if (data->log_stat & LOG_PID) {
151 		prlen = snprintf(p, tbuf_left, "[%ld]", (long)getpid());
152 		DEC();
153 	}
154 	if (data->log_tag != NULL) {
155 		if (tbuf_left > 1) {
156 			*p++ = ':';
157 			tbuf_left--;
158 		}
159 		if (tbuf_left > 1) {
160 			*p++ = ' ';
161 			tbuf_left--;
162 		}
163 	}
164 
165 	/* strerror() is not reentrant */
166 
167 	for (t = fmt_cpy, fmt_left = FMT_LEN; (ch = *fmt); ++fmt) {
168 		if (ch == '%' && fmt[1] == 'm') {
169 			++fmt;
170 			if (gettime != NULL) {
171 				prlen = snprintf(t, fmt_left, "%s",
172 				    strerror(saved_errno));
173 			} else {
174 				prlen = snprintf(t, fmt_left, "Error %d",
175 				    saved_errno);
176 			}
177 			if (prlen < 0)
178 				prlen = 0;
179 			if (prlen >= fmt_left)
180 				prlen = fmt_left - 1;
181 			t += prlen;
182 			fmt_left -= prlen;
183 		} else if (ch == '%' && fmt[1] == '%' && fmt_left > 2) {
184 			*t++ = '%';
185 			*t++ = '%';
186 			fmt++;
187 			fmt_left -= 2;
188 		} else {
189 			if (fmt_left > 1) {
190 				*t++ = ch;
191 				fmt_left--;
192 			}
193 		}
194 	}
195 	*t = '\0';
196 
197 	prlen = vsnprintf(p, tbuf_left, fmt_cpy, ap);
198 	DEC();
199 	cnt = p - tbuf;
200 
201 	/* Output to stderr if requested. */
202 	if (data->log_stat & LOG_PERROR) {
203 		struct iovec iov[2];
204 
205 		iov[0].iov_base = stdp;
206 		iov[0].iov_len = cnt - (stdp - tbuf);
207 		iov[1].iov_base = "\n";
208 		iov[1].iov_len = 1;
209 		(void)writev(STDERR_FILENO, iov, 2);
210 	}
211 
212 	/* Get connected, output the message to the local logger. */
213 	if (!data->opened)
214 		openlog_r(data->log_tag, data->log_stat, 0, data);
215 	connectlog_r(data);
216 
217 	/*
218 	 * If the send() failed, there are two likely scenarios:
219 	 *  1) syslogd was restarted
220 	 *  2) /dev/log is out of socket buffer space
221 	 * We attempt to reconnect to /dev/log to take care of
222 	 * case #1 and keep send()ing data to cover case #2
223 	 * to give syslogd a chance to empty its socket buffer.
224 	 */
225 	if ((error = send(data->log_file, tbuf, cnt, 0)) < 0) {
226 		if (errno != ENOBUFS) {
227 			disconnectlog_r(data);
228 			connectlog_r(data);
229 		}
230 		do {
231 			struct timespec rqt = { 0, 1000 };
232 
233 			nanosleep(&rqt, NULL);
234 			if ((error = send(data->log_file, tbuf, cnt, 0)) >= 0)
235 				break;
236 		} while (errno == ENOBUFS);
237 	}
238 
239 	/*
240 	 * Output the message to the console; try not to block
241 	 * as a blocking console should not stop other processes.
242 	 * Make sure the error reported is the one from the syslogd failure.
243 	 */
244 	if (error == -1 && (data->log_stat & LOG_CONS) &&
245 	    (fd = open(_PATH_CONSOLE, O_WRONLY|O_NONBLOCK, 0)) >= 0) {
246 		struct iovec iov[2];
247 
248 		p = strchr(tbuf, '>') + 1;
249 		iov[0].iov_base = p;
250 		iov[0].iov_len = cnt - (p - tbuf);
251 		iov[1].iov_base = "\r\n";
252 		iov[1].iov_len = 2;
253 		(void)writev(fd, iov, 2);
254 		(void)close(fd);
255 	}
256 }
257 
258 static void
259 disconnectlog_r(struct syslog_data *data)
260 {
261 	/*
262 	 * If the user closed the FD and opened another in the same slot,
263 	 * that's their problem.  They should close it before calling on
264 	 * system services.
265 	 */
266 	if (data->log_file != -1) {
267 		close(data->log_file);
268 		data->log_file = -1;
269 	}
270 	data->connected = 0;		/* retry connect */
271 }
272 
273 static void
274 connectlog_r(struct syslog_data *data)
275 {
276 	struct sockaddr_un SyslogAddr;	/* AF_UNIX address of local logger */
277 
278 	if (data->log_file == -1) {
279 		if ((data->log_file = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1)
280 			return;
281 		(void)fcntl(data->log_file, F_SETFD, 1);
282 	}
283 	if (data->log_file != -1 && !data->connected) {
284 		memset(&SyslogAddr, '\0', sizeof(SyslogAddr));
285 		SyslogAddr.sun_len = sizeof(SyslogAddr);
286 		SyslogAddr.sun_family = AF_UNIX;
287 		strlcpy(SyslogAddr.sun_path, _PATH_LOG,
288 		    sizeof(SyslogAddr.sun_path));
289 		if (connect(data->log_file, (struct sockaddr *)&SyslogAddr,
290 		    sizeof(SyslogAddr)) == -1) {
291 			(void)close(data->log_file);
292 			data->log_file = -1;
293 		} else
294 			data->connected = 1;
295 	}
296 }
297 
298 void
299 openlog_r(const char *ident, int logstat, int logfac, struct syslog_data *data)
300 {
301 	if (ident != NULL)
302 		data->log_tag = ident;
303 	data->log_stat = logstat;
304 	if (logfac != 0 && (logfac &~ LOG_FACMASK) == 0)
305 		data->log_fac = logfac;
306 
307 	if (data->log_stat & LOG_NDELAY)	/* open immediately */
308 		connectlog_r(data);
309 
310 	data->opened = 1;	/* ident and facility has been set */
311 }
312 
313 void
314 closelog_r(struct syslog_data *data)
315 {
316 	(void)close(data->log_file);
317 	data->log_file = -1;
318 	data->connected = 0;
319 	data->log_tag = NULL;
320 }
321 
322