/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (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
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdlib.h>
#include <locale.h>
#include <limits.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/varargs.h>
#include <synch.h>
#include <thread.h>
#include <string.h>
#include <unistd.h>
#include "nscd_log.h"
#include "nscd_config.h"
#include "nscd_switch.h"
#include "cache.h"
/*
* old nscd debug levels
*/
#define DBG_OFF 0
#define DBG_CANT_FIND 2
#define DBG_NETLOOKUPS 4
#define DBG_ALL 6
/* max. chars in a nscd log entry */
#define LOGBUFLEN 1024
/* configuration for the nscd log component */
int _nscd_log_comp = 0x0;
int _nscd_log_level = 0x0;
static char _nscd_logfile[PATH_MAX] = { 0 };
#define NSCD_DEBUG_NONE '0'
#define NSCD_DEBUG_OPEN '1'
#define NSCD_DEBUG_CLOSE '2'
static char _nscd_debug = NSCD_DEBUG_NONE;
static char _nscd_logfile_d[PATH_MAX] = { 0 };
static char _nscd_logfile_s[PATH_MAX] = { 0 };
/* statistics data */
static nscd_cfg_stat_global_log_t logstats = {
NSCD_CFG_STAT_GROUP_INFO_GLOBAL_LOG, 0 };
/* if no log file specified, log entry goes to stderr */
int _logfd = 2;
/* close old log file and open a new one */
static nscd_rc_t
_nscd_set_lf(
char *lf)
{
int newlogfd;
char *me = "_nscd_set_lf";
/*
* don't try and open the log file /dev/null
*/
if (lf == NULL || *lf == 0) {
/* ignore empty log file specs */
return (NSCD_SUCCESS);
} else if (strcmp(lf, "/dev/null") == 0) {
(void) strlcpy(_nscd_logfile, lf, PATH_MAX);
if (_logfd >= 0)
(void) close(_logfd);
_logfd = -1;
return (NSCD_SUCCESS);
} else if (strcmp(lf, "stderr") == 0) {
(void) strlcpy(_nscd_logfile, lf, PATH_MAX);
if (_logfd != -1 && _logfd != 2)
(void) close(_logfd);
_logfd = 2;
return (NSCD_SUCCESS);
} else {
/*
* In order to open this file securely, we'll try a few tricks
*/
if ((newlogfd = open(lf, O_EXCL|O_WRONLY|O_CREAT, 0644)) < 0) {
/*
* File already exists... now we need to get cute
* since opening a file in a world-writeable directory
* safely is hard = it could be a hard link or a
* symbolic link to a system file.
*/
struct stat before;
if (lstat(lf, &before) < 0) {
if (_nscd_debug == NSCD_DEBUG_NONE)
_nscd_logit(me, "Cannot open new "
"logfile \"%s\": %sn",
lf, strerror(errno));
return (NSCD_CFG_FILE_OPEN_ERROR);
}
if (S_ISREG(before.st_mode) && /* no symbolic links */
(before.st_nlink == 1) && /* no hard links */
(before.st_uid == 0)) { /* owned by root */
if ((newlogfd =
open(lf, O_APPEND|O_WRONLY, 0644)) < 0) {
if (_nscd_debug == NSCD_DEBUG_NONE)
_nscd_logit(me,
"Cannot open new "\
"logfile \"%s\": %s\n", lf,
strerror(errno));
return (NSCD_CFG_FILE_OPEN_ERROR);
}
} else {
if (_nscd_debug == NSCD_DEBUG_NONE)
_nscd_logit(me, "Cannot use specified "
"logfile \"%s\": "\
"file is/has links or isn't "
"owned by root\n", lf);
return (NSCD_CFG_FILE_OPEN_ERROR);
}
}
(void) close(_logfd);
(void) strlcpy(_nscd_logfile, lf, PATH_MAX);
_logfd = newlogfd;
if (_nscd_debug == NSCD_DEBUG_NONE)
_nscd_logit(me, "Start of new logfile %s\n", lf);
}
return (NSCD_SUCCESS);
}
/* log an entry to the configured nscd log file */
void
_nscd_logit(
char *funcname,
char *format,
...)
{
static mutex_t loglock = DEFAULTMUTEX;
struct timeval tv;
char tid_buf[32];
char pid_buf[32];
char buffer[LOGBUFLEN];
int safechars, offset;
va_list ap;
if (_logfd < 0)
return;
if (_nscd_debug == NSCD_DEBUG_OPEN) {
(void) mutex_lock(&loglock);
if (_nscd_debug == NSCD_DEBUG_OPEN &&
*_nscd_logfile_d != '\0' &&
(strcmp(_nscd_logfile, "/dev/null") == 0 ||
strcmp(_nscd_logfile, "stderr") == 0)) {
(void) strlcpy(_nscd_logfile_s,
_nscd_logfile, PATH_MAX);
(void) _nscd_set_lf(_nscd_logfile_d);
}
_nscd_debug = NSCD_DEBUG_NONE;
(void) mutex_unlock(&loglock);
} else if (_nscd_debug == NSCD_DEBUG_CLOSE) {
(void) mutex_lock(&loglock);
if (_nscd_debug == NSCD_DEBUG_CLOSE)
(void) _nscd_set_lf(_nscd_logfile_s);
_nscd_debug = NSCD_DEBUG_NONE;
(void) mutex_unlock(&loglock);
}
va_start(ap, format);
if (gettimeofday(&tv, NULL) != 0 ||
ctime_r(&tv.tv_sec, buffer, LOGBUFLEN) == NULL) {
(void) snprintf(buffer, LOGBUFLEN,
"<time conversion failed>\t");
} else {
(void) sprintf(tid_buf, "--%d", thr_self());
(void) sprintf(pid_buf, "--%ld", getpid());
/*
* ctime_r() includes some stuff we don't want;
* adjust length to overwrite " YYYY\n" and
* include tid string length.
*/
offset = strlen(buffer) - 6;
safechars = LOGBUFLEN - (offset - 1);
(void) snprintf(buffer + offset,
safechars, ".%.4ld%s%s\t%s:\n\t\t",
tv.tv_usec/100, tid_buf, pid_buf,
funcname);
}
offset = strlen(buffer);
safechars = LOGBUFLEN - (offset - 1);
/*LINTED: E_SEC_PRINTF_VAR_FMT*/
if (vsnprintf(buffer + offset, safechars, format, ap) >
safechars) {
(void) strncat(buffer, "...\n", LOGBUFLEN);
}
(void) mutex_lock(&loglock);
(void) write(_logfd, buffer, strlen(buffer));
logstats.entries_logged++;
(void) mutex_unlock(&loglock);
va_end(ap);
}
/*
* Map old nscd debug level (0 -10) to log level:
* -- >= 6: DBG_ALL --> NSCD_LOG_LEVEL_ALL
* -- >= 4: DBG_DBG_NETLOOKUPS --> NSCD_LOG_LEVEL_CANT_FIND
* -- >= 2: DBG_CANT_FIND --> NSCD_LOG_LEVEL_CANT_FIND
* -- >= 0: DBG_OFF --> NSCD_LOG_LEVEL_NONE
*/
static int
debug_to_log_level(
int level)
{
if (level >= 0 && level <= 10) {
if (level >= DBG_ALL)
return (NSCD_LOG_LEVEL_ALL);
else if (level >= DBG_NETLOOKUPS)
return (NSCD_LOG_LEVEL_CANT_FIND);
else if (level >= DBG_CANT_FIND)
return (NSCD_LOG_LEVEL_CANT_FIND);
else if (level >= DBG_OFF)
return (NSCD_LOG_LEVEL_NONE);
}
return (level);
}
/* ARGSUSED */
nscd_rc_t
_nscd_cfg_log_notify(
void *data,
struct nscd_cfg_param_desc *pdesc,
nscd_cfg_id_t *nswdb,
nscd_cfg_flag_t dflag,
nscd_cfg_error_t **errorp,
void *cookie)
{
nscd_cfg_global_log_t *logcfg;
int off;
/*
* At init time, the whole group of config params are received.
* At update time, group or individual parameter value could
* be received.
*/
if (_nscd_cfg_flag_is_set(dflag, NSCD_CFG_DFLAG_GROUP)) {
logcfg = (nscd_cfg_global_log_t *)data;
_nscd_log_comp = logcfg->debug_comp;
_nscd_log_level = logcfg->debug_level;
/*
* logcfg->logfile should have been opened
* by _nscd_cfg_log_verify()
*/
return (NSCD_SUCCESS);
}
/*
* individual config parameter
*/
off = offsetof(nscd_cfg_global_log_t, debug_comp);
if (pdesc->p_offset == off) {
_nscd_log_comp = *(nscd_cfg_bitmap_t *)data;
return (NSCD_SUCCESS);
}
off = offsetof(nscd_cfg_global_log_t, debug_level);
if (pdesc->p_offset == off)
_nscd_log_level = *(nscd_cfg_bitmap_t *)data;
/*
* logcfg->logfile should have been opened
* by _nscd_cfg_log_verify()
*/
return (NSCD_SUCCESS);
}
/* ARGSUSED */
nscd_rc_t
_nscd_cfg_log_verify(
void *data,
struct nscd_cfg_param_desc *pdesc,
nscd_cfg_id_t *nswdb,
nscd_cfg_flag_t dflag,
nscd_cfg_error_t **errorp,
void **cookie)
{
nscd_cfg_global_log_t *logcfg;
nscd_cfg_bitmap_t bt;
int off;
/*
* There is no switch db specific config params
* for the nscd log component. It is a bug if
* the input param description is global.
*/
if (_nscd_cfg_flag_is_not_set(pdesc->pflag, NSCD_CFG_PFLAG_GLOBAL))
return (NSCD_CFG_PARAM_DESC_ERROR);
/*
* At init time, the whole group of config params are received.
* At update time, group or individual parameter value could
* be received.
*/
if (_nscd_cfg_flag_is_set(dflag, NSCD_CFG_DFLAG_GROUP)) {
logcfg = (nscd_cfg_global_log_t *)data;
if (_nscd_cfg_bitmap_valid(logcfg->debug_comp,
NSCD_LOG_ALL) == 0)
return (NSCD_CFG_SYNTAX_ERROR);
if (_nscd_cfg_bitmap_valid(logcfg->debug_level,
NSCD_LOG_LEVEL_ALL) == 0)
return (NSCD_CFG_SYNTAX_ERROR);
if (logcfg->logfile != NULL)
return (_nscd_set_lf(logcfg->logfile));
return (NSCD_SUCCESS);
}
/*
* individual config parameter
*/
off = offsetof(nscd_cfg_global_log_t, debug_comp);
if (pdesc->p_offset == off) {
bt = *(nscd_cfg_bitmap_t *)data;
if (_nscd_cfg_bitmap_valid(bt, NSCD_LOG_ALL) == 0)
return (NSCD_CFG_SYNTAX_ERROR);
return (NSCD_SUCCESS);
}
off = offsetof(nscd_cfg_global_log_t, debug_level);
if (pdesc->p_offset == off) {
bt = *(nscd_cfg_bitmap_t *)data;
if (_nscd_cfg_bitmap_valid(bt, NSCD_LOG_LEVEL_ALL) == 0)
return (NSCD_CFG_SYNTAX_ERROR);
return (NSCD_SUCCESS);
}
off = offsetof(nscd_cfg_global_log_t, logfile);
if (pdesc->p_offset == off) {
if (data != NULL)
return (_nscd_set_lf((char *)data));
else
return (NSCD_SUCCESS);
}
return (NSCD_CFG_PARAM_DESC_ERROR);
}
/* ARGSUSED */
nscd_rc_t
_nscd_cfg_log_get_stat(
void **stat,
struct nscd_cfg_stat_desc *sdesc,
nscd_cfg_id_t *nswdb,
nscd_cfg_flag_t *dflag,
void (**free_stat)(void *stat),
nscd_cfg_error_t **errorp)
{
*(nscd_cfg_stat_global_log_t **)stat = &logstats;
/* indicate the statistics are static, i.e., do not free */
*dflag = _nscd_cfg_flag_set(*dflag, NSCD_CFG_DFLAG_STATIC_DATA);
return (NSCD_SUCCESS);
}
/*
* set the name of the current log file and make it current.
*/
nscd_rc_t
_nscd_set_log_file(
char *name)
{
nscd_rc_t rc;
nscd_cfg_handle_t *h;
rc = _nscd_cfg_get_handle("logfile", NULL, &h, NULL);
if (rc != NSCD_SUCCESS)
return (rc);
rc = _nscd_cfg_set(h, name, NULL);
_nscd_cfg_free_handle(h);
if (rc != NSCD_SUCCESS)
exit(rc);
return (NSCD_SUCCESS);
}
/* Set debug level to the new one and make it current */
nscd_rc_t
_nscd_set_debug_level(
int level)
{
nscd_rc_t rc;
nscd_cfg_handle_t *h;
int l = 0;
int c = -1;
/* old nscd debug level is 1 to 10, map it to log_level and log_comp */
if (level >= 0 && level <= 10) {
l = debug_to_log_level(level);
c = NSCD_LOG_CACHE;
} else
l = level;
if (level < 0)
c = -1 * level / 1000000;
if (c != -1) {
rc = _nscd_cfg_get_handle("debug-components", NULL, &h, NULL);
if (rc != NSCD_SUCCESS)
return (rc);
rc = _nscd_cfg_set(h, &c, NULL);
_nscd_cfg_free_handle(h);
if (rc != NSCD_SUCCESS)
exit(rc);
}
rc = _nscd_cfg_get_handle("debug-level", NULL, &h, NULL);
if (rc != NSCD_SUCCESS)
return (rc);
if (level < 0)
l = -1 * level % 1000000;
rc = _nscd_cfg_set(h, &l, NULL);
_nscd_cfg_free_handle(h);
if (rc != NSCD_SUCCESS)
exit(rc);
return (NSCD_SUCCESS);
}
void
_nscd_get_log_info(
char *level,
int llen,
char *file,
int flen)
{
if (_nscd_log_level != 0)
(void) snprintf(level, llen, "%d", _nscd_log_level);
if (*_nscd_logfile != '\0')
(void) strlcpy(file, _nscd_logfile, flen);
}