root/usr/src/cmd/cmd-inet/usr.lib/pppoe/logging.c
/*
 * 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.
 */

#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));
}