/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* PPPoE Server-mode daemon log file support.
*
* Copyright (c) 2000-2001 by Sun Microsystems, Inc.
* All rights reserved.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <alloca.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <syslog.h>
#include <assert.h>
#include <sys/types.h>
#include "common.h"
#include "logging.h"
/* Not all functions are used by all applications. Let lint know this. */
/*LINTLIBRARY*/
const char *prog_name = "none"; /* Subsystem name for syslog */
int log_level; /* Higher number for more detail. */
static int curlogfd = -1; /* Current log file */
static const char *curfname; /* Name of current log file */
static const char *stderr_name = "stderr";
#define SMALLSTR 254 /* Don't allocate for most strings. */
/*
* Returns -1 on error (with errno set), 0 on blocked write (file
* system full), or N (buffer length) on success.
*/
static int
dowrite(int fd, const void *buf, int len)
{
int retv;
const uint8_t *bp = (uint8_t *)buf;
while (len > 0) {
retv = write(fd, bp, len);
if (retv == 0) {
break;
}
if (retv == -1) {
if (errno != EINTR)
break;
} else {
bp += retv;
len -= retv;
}
}
if (len <= 0)
return (bp - (uint8_t *)buf);
return (retv);
}
/* A close that avoids closing stderr */
static int
doclose(void)
{
int retval = 0;
if (curlogfd == -1)
return (0);
if ((curlogfd != STDERR_FILENO) || (curfname != stderr_name))
retval = close(curlogfd);
curlogfd = -1;
return (retval);
}
/*
* Log levels are 0 for no messages, 1 for errors, 2 for warnings, 3
* for informational messages, and 4 for debugging messages.
*/
static void
vlogat(int loglev, const char *fmt, va_list args)
{
char timbuf[64];
char regbuf[SMALLSTR+2];
char *ostr;
int timlen;
int slen;
char *nstr;
int err1, err2;
int sloglev;
int retv;
va_list args2;
static int xlate_loglev[] = {
LOG_ERR, LOG_WARNING, LOG_INFO, LOG_DEBUG
};
if (loglev >= log_level)
return;
timbuf[0] = '\0';
timlen = 0;
if (curlogfd >= 0) {
time_t now = time(NULL);
/*
* Form a time/date string for file (non-syslog) logging.
* Caution: string broken in two so that SCCS doesn't mangle
* the %-T-% sequence.
*/
timlen = strftime(timbuf, sizeof (timbuf), "%Y/%m/%d %T"
"%Z: ", localtime(&now));
}
/* Try formatting once into the small buffer. */
va_copy(args2, args);
slen = vsnprintf(regbuf, SMALLSTR, fmt, args);
if (slen < SMALLSTR) {
ostr = regbuf;
} else {
/*
* Length returned by vsnprintf doesn't include null,
* and may also be missing a terminating \n.
*/
ostr = alloca(slen + 2);
slen = vsnprintf(ostr, slen + 1, fmt, args2);
}
/* Don't bother logging empty lines. */
if (slen <= 0)
return;
/* Tack on a \n if needed. */
if (ostr[slen - 1] != '\n') {
ostr[slen++] = '\n';
ostr[slen] = '\0';
}
/* Translate our log levels into syslog standard values */
assert(loglev >= 0 && loglev < Dim(xlate_loglev));
sloglev = xlate_loglev[loglev];
/* Log each line separately */
for (; *ostr != '\0'; ostr = nstr + 1) {
nstr = strchr(ostr, '\n');
/* Ignore zero-length lines. */
if (nstr == ostr)
continue;
slen = nstr - ostr + 1;
/*
* If we're supposed to be logging to a file, then try
* that first. Ditch the file and revert to syslog if
* any errors occur.
*/
if (curlogfd >= 0) {
if ((retv = dowrite(curlogfd, timbuf, timlen)) > 0)
retv = dowrite(curlogfd, ostr, slen);
/*
* If we've successfully logged this line,
* then go do the next one.
*/
if (retv > 0)
continue;
/* Save errno (if any) and close log file */
err1 = errno;
if (doclose() == -1)
err2 = errno;
else
err2 = 0;
/*
* Recursion is safe here because we cleared
* out curlogfd above.
*/
if (retv == -1)
logerr("write log %s: %s", curfname,
mystrerror(err1));
else
logerr("cannot write %s", curfname);
if (err2 == 0)
logdbg("closed log %s", curfname);
else
logerr("closing log %s: %s", curfname,
mystrerror(err2));
}
syslog(sloglev, "%.*s", slen, ostr);
}
}
/* Log at debug level */
void
logdbg(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vlogat(LOGLVL_DBG, fmt, args);
va_end(args);
}
/* Log informational messages */
void
loginfo(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vlogat(LOGLVL_INFO, fmt, args);
va_end(args);
}
/* Log warning messages */
void
logwarn(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vlogat(LOGLVL_WARN, fmt, args);
va_end(args);
}
/* Log error messages */
void
logerr(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vlogat(LOGLVL_ERR, fmt, args);
va_end(args);
}
/* Log a strerror message */
void
logstrerror(const char *emsg)
{
logerr("%s: %s\n", emsg, mystrerror(errno));
}
void
log_to_stderr(int dbglvl)
{
log_level = dbglvl;
if (curlogfd >= 0)
close_log_files();
curlogfd = STDERR_FILENO;
curfname = stderr_name;
}
/*
* Set indicated log file and debug level.
*/
void
log_for_service(const char *fname, int dbglvl)
{
int err1, err2;
boolean_t closed;
log_level = dbglvl;
if (fname != NULL &&
(*fname == '\0' || strcasecmp(fname, "syslog") == 0))
fname = NULL;
if (fname == NULL && curfname == NULL)
return;
err1 = err2 = 0;
closed = B_FALSE;
if (curlogfd >= 0) {
if (fname == curfname ||
(fname != NULL && strcmp(fname, curfname) == 0)) {
curfname = fname;
return;
}
if (doclose() == -1)
err1 = errno;
closed = B_TRUE;
}
if (fname != NULL) {
curlogfd = open(fname, O_WRONLY|O_APPEND|O_CREAT, 0600);
if (curlogfd == -1)
err2 = errno;
}
if (closed) {
if (err1 == 0)
logdbg("closed log %s", curfname);
else
logerr("closing log %s: %s", curfname,
mystrerror(err1));
}
if (fname != NULL) {
if (err2 == 0)
logdbg("opened log %s", fname);
else
logerr("opening log %s: %s", fname, mystrerror(err2));
}
curfname = fname;
}
/*
* Close any open log file. This is used for SIGHUP (to support log
* file rotation) and when execing.
*/
void
close_log_files(void)
{
int err = 0;
if (curlogfd >= 0) {
if (doclose() == -1)
err = errno;
if (err == 0)
logdbg("closed log %s", curfname);
else
logerr("closing log %s: %s", curfname,
mystrerror(err));
}
}
/*
* Reopen syslog connection; in case it was closed.
*/
void
reopen_log(void)
{
openlog(prog_name, LOG_PID | LOG_NDELAY | LOG_NOWAIT, LOG_DAEMON);
/* I control the log level */
(void) setlogmask(LOG_UPTO(LOG_DEBUG));
}