1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * PPPoE Server-mode daemon log file support.
24  *
25  * Copyright (c) 2000-2001 by Sun Microsystems, Inc.
26  * All rights reserved.
27  */
28 
29 #pragma ident	"%Z%%M%	%I%	%E% SMI"
30 
31 #include <stdio.h>
32 #include <unistd.h>
33 #include <stdarg.h>
34 #include <alloca.h>
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <string.h>
38 #include <syslog.h>
39 #include <assert.h>
40 #include <sys/types.h>
41 
42 #include "common.h"
43 #include "logging.h"
44 
45 /* Not all functions are used by all applications.  Let lint know this. */
46 /*LINTLIBRARY*/
47 
48 const char *prog_name = "none";	/* Subsystem name for syslog */
49 int log_level;			/* Higher number for more detail. */
50 
51 static int curlogfd = -1;	/* Current log file */
52 static const char *curfname;	/* Name of current log file */
53 static const char *stderr_name = "stderr";
54 
55 #define	SMALLSTR	254	/* Don't allocate for most strings. */
56 
57 /*
58  * Returns -1 on error (with errno set), 0 on blocked write (file
59  * system full), or N (buffer length) on success.
60  */
61 static int
62 dowrite(int fd, const void *buf, int len)
63 {
64 	int retv;
65 	const uint8_t *bp = (uint8_t *)buf;
66 
67 	while (len > 0) {
68 		retv = write(fd, bp, len);
69 		if (retv == 0) {
70 			break;
71 		}
72 		if (retv == -1) {
73 			if (errno != EINTR)
74 				break;
75 		} else {
76 			bp += retv;
77 			len -= retv;
78 		}
79 	}
80 	if (len <= 0)
81 		return (bp - (uint8_t *)buf);
82 	return (retv);
83 }
84 
85 /* A close that avoids closing stderr */
86 static int
87 doclose(void)
88 {
89 	int	retval = 0;
90 
91 	if (curlogfd == -1)
92 		return (0);
93 	if ((curlogfd != STDERR_FILENO) || (curfname != stderr_name))
94 		retval = close(curlogfd);
95 	curlogfd = -1;
96 	return (retval);
97 }
98 
99 /*
100  * Log levels are 0 for no messages, 1 for errors, 2 for warnings, 3
101  * for informational messages, and 4 for debugging messages.
102  */
103 static void
104 vlogat(int loglev, const char *fmt, va_list args)
105 {
106 	char timbuf[64];
107 	char regbuf[SMALLSTR+2];
108 	char *ostr;
109 	int timlen;
110 	int slen;
111 	char *nstr;
112 	int err1, err2;
113 	int sloglev;
114 	int retv;
115 	va_list args2;
116 	static int xlate_loglev[] = {
117 		LOG_ERR, LOG_WARNING, LOG_INFO, LOG_DEBUG
118 	};
119 
120 	if (loglev >= log_level)
121 		return;
122 
123 	timbuf[0] = '\0';
124 	timlen = 0;
125 	if (curlogfd >= 0) {
126 		time_t now = time(NULL);
127 
128 		/*
129 		 * Form a time/date string for file (non-syslog) logging.
130 		 * Caution: string broken in two so that SCCS doesn't mangle
131 		 * the %-T-% sequence.
132 		 */
133 		timlen = strftime(timbuf, sizeof (timbuf), "%Y/%m/%d %T"
134 		    "%Z: ", localtime(&now));
135 	}
136 
137 	/* Try formatting once into the small buffer. */
138 	va_copy(args2, args);
139 	slen = vsnprintf(regbuf, SMALLSTR, fmt, args);
140 	if (slen < SMALLSTR) {
141 		ostr = regbuf;
142 	} else {
143 		/*
144 		 * Length returned by vsnprintf doesn't include null,
145 		 * and may also be missing a terminating \n.
146 		 */
147 		ostr = alloca(slen + 2);
148 		slen = vsnprintf(ostr, slen + 1, fmt, args2);
149 	}
150 
151 	/* Don't bother logging empty lines. */
152 	if (slen <= 0)
153 		return;
154 
155 	/* Tack on a \n if needed. */
156 	if (ostr[slen - 1] != '\n') {
157 		ostr[slen++] = '\n';
158 		ostr[slen] = '\0';
159 	}
160 
161 	/* Translate our log levels into syslog standard values */
162 	assert(loglev >= 0 && loglev < Dim(xlate_loglev));
163 	sloglev = xlate_loglev[loglev];
164 
165 	/* Log each line separately */
166 	for (; *ostr != '\0'; ostr = nstr + 1) {
167 		nstr = strchr(ostr, '\n');
168 
169 		/* Ignore zero-length lines. */
170 		if (nstr == ostr)
171 			continue;
172 
173 		slen = nstr - ostr + 1;
174 
175 		/*
176 		 * If we're supposed to be logging to a file, then try
177 		 * that first.  Ditch the file and revert to syslog if
178 		 * any errors occur.
179 		 */
180 		if (curlogfd >= 0) {
181 			if ((retv = dowrite(curlogfd, timbuf, timlen)) > 0)
182 				retv = dowrite(curlogfd, ostr, slen);
183 
184 			/*
185 			 * If we've successfully logged this line,
186 			 * then go do the next one.
187 			 */
188 			if (retv > 0)
189 				continue;
190 
191 			/* Save errno (if any) and close log file */
192 			err1 = errno;
193 			if (doclose() == -1)
194 				err2 = errno;
195 			else
196 				err2 = 0;
197 
198 			/*
199 			 * Recursion is safe here because we cleared
200 			 * out curlogfd above.
201 			 */
202 			if (retv == -1)
203 				logerr("write log %s: %s", curfname,
204 				    mystrerror(err1));
205 			else
206 				logerr("cannot write %s", curfname);
207 			if (err2 == 0)
208 				logdbg("closed log %s", curfname);
209 			else
210 				logerr("closing log %s: %s", curfname,
211 				    mystrerror(err2));
212 		}
213 		syslog(sloglev, "%.*s", slen, ostr);
214 	}
215 }
216 
217 /* Log at debug level */
218 void
219 logdbg(const char *fmt, ...)
220 {
221 	va_list args;
222 
223 	va_start(args, fmt);
224 	vlogat(LOGLVL_DBG, fmt, args);
225 	va_end(args);
226 }
227 
228 /* Log informational messages */
229 void
230 loginfo(const char *fmt, ...)
231 {
232 	va_list args;
233 
234 	va_start(args, fmt);
235 	vlogat(LOGLVL_INFO, fmt, args);
236 	va_end(args);
237 }
238 
239 /* Log warning messages */
240 void
241 logwarn(const char *fmt, ...)
242 {
243 	va_list args;
244 
245 	va_start(args, fmt);
246 	vlogat(LOGLVL_WARN, fmt, args);
247 	va_end(args);
248 }
249 
250 /* Log error messages */
251 void
252 logerr(const char *fmt, ...)
253 {
254 	va_list args;
255 
256 	va_start(args, fmt);
257 	vlogat(LOGLVL_ERR, fmt, args);
258 	va_end(args);
259 }
260 
261 /* Log a strerror message */
262 void
263 logstrerror(const char *emsg)
264 {
265 	logerr("%s: %s\n", emsg, mystrerror(errno));
266 }
267 
268 void
269 log_to_stderr(int dbglvl)
270 {
271 	log_level = dbglvl;
272 	if (curlogfd >= 0)
273 		close_log_files();
274 	curlogfd = STDERR_FILENO;
275 	curfname = stderr_name;
276 }
277 
278 /*
279  * Set indicated log file and debug level.
280  */
281 void
282 log_for_service(const char *fname, int dbglvl)
283 {
284 	int err1, err2;
285 	boolean_t closed;
286 
287 	log_level = dbglvl;
288 	if (fname != NULL &&
289 	    (*fname == '\0' || strcasecmp(fname, "syslog") == 0))
290 		fname = NULL;
291 	if (fname == NULL && curfname == NULL)
292 		return;
293 	err1 = err2 = 0;
294 	closed = B_FALSE;
295 	if (curlogfd >= 0) {
296 		if (fname == curfname ||
297 		    (fname != NULL && strcmp(fname, curfname) == 0)) {
298 			curfname = fname;
299 			return;
300 		}
301 		if (doclose() == -1)
302 			err1 = errno;
303 		closed = B_TRUE;
304 	}
305 	if (fname != NULL) {
306 		curlogfd = open(fname, O_WRONLY|O_APPEND|O_CREAT, 0600);
307 		if (curlogfd == -1)
308 			err2 = errno;
309 	}
310 	if (closed) {
311 		if (err1 == 0)
312 			logdbg("closed log %s", curfname);
313 		else
314 			logerr("closing log %s: %s", curfname,
315 			    mystrerror(err1));
316 	}
317 	if (fname != NULL) {
318 		if (err2 == 0)
319 			logdbg("opened log %s", fname);
320 		else
321 			logerr("opening log %s: %s", fname, mystrerror(err2));
322 	}
323 	curfname = fname;
324 }
325 
326 /*
327  * Close any open log file.  This is used for SIGHUP (to support log
328  * file rotation) and when execing.
329  */
330 void
331 close_log_files(void)
332 {
333 	int err = 0;
334 
335 	if (curlogfd >= 0) {
336 		if (doclose() == -1)
337 			err = errno;
338 		if (err == 0)
339 			logdbg("closed log %s", curfname);
340 		else
341 			logerr("closing log %s: %s", curfname,
342 			    mystrerror(err));
343 	}
344 }
345 
346 /*
347  * Reopen syslog connection; in case it was closed.
348  */
349 void
350 reopen_log(void)
351 {
352 	openlog(prog_name, LOG_PID | LOG_NDELAY | LOG_NOWAIT, LOG_DAEMON);
353 	/* I control the log level */
354 	(void) setlogmask(LOG_UPTO(LOG_DEBUG));
355 }
356