#include <ctype.h>
#include <errno.h>
#include <locale.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <syslog.h>
#include <nfs/nfs.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "nfslog_config.h"
#define ERROR_BUFSZ 100
boolean_t nfsl_errs_to_syslog;
static nfsl_config_t *global = NULL;
static nfsl_config_t *global_raw = NULL;
static timestruc_t config_last_modification = { 0 };
static const char *whitespace = " \t";
static int getconfiglist(nfsl_config_t **, boolean_t);
static nfsl_config_t *create_config(char *, char *, char *, char *, char *,
char *, int, boolean_t, int *);
static nfsl_config_t *create_global_raw(int *);
static int update_config(nfsl_config_t *, char *, char *, char *,
char *, char *, char *, int, boolean_t, boolean_t);
static int update_field(char **, char *, char *, boolean_t *);
static nfsl_config_t *findconfig(nfsl_config_t **, char *, boolean_t,
nfsl_config_t **);
static nfsl_config_t *getlastconfig(nfsl_config_t *);
static void complete_with_global(char **, char **, char **, char **,
char **, int *);
#ifdef DEBUG
static void remove_config(nfsl_config_t **, nfsl_config_t *, nfsl_config_t **);
void nfsl_printconfig(nfsl_config_t *);
#endif
static char *gataline(FILE *, char *, char *, int);
static int get_info(char *, char **, char **, char **, char **, char **,
char **, int *);
static void free_config(nfsl_config_t *);
static int is_legal_tag(char *);
static boolean_t is_complete_config(char *, char *, char *, char *);
int
nfsl_getconfig_list(nfsl_config_t **listpp)
{
int error = 0;
char *locale;
if ((locale = getenv("LC_ALL")) != NULL)
(void) setlocale(LC_ALL, locale);
else if ((locale = getenv("LC_CTYPE")) != NULL)
(void) setlocale(LC_CTYPE, locale);
else if ((locale = getenv("LANG")) != NULL)
(void) setlocale(LC_CTYPE, locale);
assert(global_raw == NULL);
global_raw = create_global_raw(&error);
if (global_raw == NULL)
return (error);
assert(global == NULL);
global = create_config(DEFAULTTAG, DEFAULTDIR, BUFFERPATH, NULL,
FHPATH, LOGPATH, TRANSLOG_BASIC, B_TRUE, &error);
*listpp = global;
if (global == NULL) {
free_config(global_raw);
return (error);
}
error = getconfiglist(listpp, B_FALSE);
if (error != 0) {
nfsl_freeconfig_list(listpp);
} else {
assert(global != NULL);
global->nc_flags &= ~NC_UPDATED;
}
return (error);
}
static nfsl_config_t *
create_global_raw(int *error)
{
nfsl_config_t *p;
*error = 0;
p = calloc(1, sizeof (*p));
if (p == NULL)
*error = ENOMEM;
return (p);
}
int
nfsl_checkconfig_list(nfsl_config_t **listpp, boolean_t *updated)
{
struct stat st;
int error = 0;
if (updated != NULL)
*updated = B_FALSE;
if (stat(NFSL_CONFIG_FILE_PATH, &st) == -1) {
error = errno;
if (nfsl_errs_to_syslog) {
syslog(LOG_ERR, gettext(
"Can't stat %s - %s"), NFSL_CONFIG_FILE_PATH,
strerror(error));
} else {
(void) fprintf(stderr, gettext(
"Can't stat %s - %s\n"), NFSL_CONFIG_FILE_PATH,
strerror(error));
}
return (0);
}
if (config_last_modification.tv_sec == st.st_mtim.tv_sec &&
config_last_modification.tv_nsec == st.st_mtim.tv_nsec)
return (0);
if (updated != NULL)
*updated = B_TRUE;
return (getconfiglist(listpp, B_TRUE));
}
static int
getconfiglist(nfsl_config_t **listpp, boolean_t updating)
{
FILE *fp;
int error = 0;
nfsl_config_t *listp = NULL, *tail = NULL;
char linebuf[MAX_LINESZ];
char errorbuf[ERROR_BUFSZ];
char *tag, *defaultdir, *bufferpath, *rpclogpath, *fhpath, *logpath;
int logformat;
flock_t flock;
struct stat st;
fp = fopen(NFSL_CONFIG_FILE_PATH, "r");
if (fp == NULL) {
if (updating) {
(void) sprintf(errorbuf, "Can't open %s",
NFSL_CONFIG_FILE_PATH);
} else {
(void) sprintf(errorbuf,
"Can't open %s - using hardwired defaults",
NFSL_CONFIG_FILE_PATH);
}
if (nfsl_errs_to_syslog)
syslog(LOG_ERR, gettext("%s"), errorbuf);
else
(void) fprintf(stderr, gettext("%s\n"), errorbuf);
return (0);
}
(void) memset((void *) &flock, 0, sizeof (flock));
flock.l_type = F_RDLCK;
if (fcntl(fileno(fp), F_SETLKW, &flock) == -1) {
error = errno;
if (nfsl_errs_to_syslog) {
syslog(LOG_ERR, gettext(
"Can't lock %s - %s"), NFSL_CONFIG_FILE_PATH,
strerror(error));
} else {
(void) fprintf(stderr, gettext(
"Can't lock %s - %s\n"), NFSL_CONFIG_FILE_PATH,
strerror(error));
}
goto done;
}
assert (*listpp != NULL);
tail = getlastconfig(*listpp);
while (gataline(fp, NFSL_CONFIG_FILE_PATH, linebuf, sizeof (linebuf))) {
if (linebuf[0] == '\0') {
continue;
}
error = get_info(linebuf, &tag, &defaultdir, &bufferpath,
&rpclogpath, &fhpath, &logpath, &logformat);
if (error != 0)
break;
listp = findconfig(listpp, tag, B_FALSE, &tail);
if (listp != NULL) {
error = update_config(listp, tag, defaultdir,
bufferpath, rpclogpath, fhpath, logpath,
logformat, B_TRUE, B_TRUE);
if (error)
break;
} else {
listp = create_config(tag, defaultdir,
bufferpath, rpclogpath, fhpath,
logpath, logformat, B_TRUE, &error);
if (listp == NULL)
break;
if (*listpp == NULL)
*listpp = listp;
else
tail->nc_next = listp;
tail = listp;
}
assert(global != NULL);
}
if (error == 0) {
error = fstat(fileno(fp), &st);
if (error != 0) {
error = errno;
if (nfsl_errs_to_syslog) {
syslog(LOG_ERR, gettext(
"Can't stat %s - %s"),
NFSL_CONFIG_FILE_PATH,
strerror(error));
} else {
(void) fprintf(stderr, gettext(
"Can't stat %s - %s\n"),
NFSL_CONFIG_FILE_PATH,
strerror(error));
}
}
config_last_modification = st.st_mtim;
}
done:
(void) fclose(fp);
return (error);
}
static nfsl_config_t *
create_config(
char *tag,
char *defaultdir,
char *bufferpath,
char *rpclogpath,
char *fhpath,
char *logpath,
int logformat,
boolean_t complete,
int *error)
{
nfsl_config_t *config;
config = calloc(1, sizeof (*config));
if (config == NULL) {
*error = ENOMEM;
return (NULL);
}
*error = update_config(config, tag, defaultdir, bufferpath, rpclogpath,
fhpath, logpath, logformat, complete, B_TRUE);
if (*error) {
free(config);
return (NULL);
}
config->nc_flags &= ~NC_UPDATED;
return (config);
}
static int
update_config(
nfsl_config_t *config,
char *tag,
char *defaultdir,
char *bufferpath,
char *rpclogpath,
char *fhpath,
char *logpath,
int logformat,
boolean_t complete,
boolean_t prepend)
{
boolean_t updated, config_updated = B_FALSE;
int error = 0;
if (complete && !is_complete_config(tag, bufferpath, fhpath, logpath)) {
if (nfsl_errs_to_syslog) {
syslog(LOG_ERR, gettext(
"update_config: \"%s\" not a complete "
"config entry."), tag);
} else {
(void) fprintf(stderr, gettext(
"update_config: \"%s\" not a complete "
"config entry.\n"), tag);
}
return (EINVAL);
}
assert(tag != NULL);
if (config->nc_name == NULL) {
if ((config->nc_name = strdup(tag)) == NULL) {
error = ENOMEM;
goto errout;
}
} else {
assert(strcmp(config->nc_name, tag) == 0);
}
error = update_field(
&config->nc_defaultdir, defaultdir, NULL, &updated);
if (error != 0)
goto errout;
if (!prepend) {
defaultdir = NULL;
}
config_updated |= updated;
error = update_field(
&config->nc_bufferpath, bufferpath, defaultdir, &updated);
if (error != 0)
goto errout;
config_updated |= updated;
error = update_field(
&config->nc_rpclogpath, rpclogpath, defaultdir, &updated);
if (error != 0)
goto errout;
config_updated |= updated;
error = update_field(
&config->nc_fhpath, fhpath, defaultdir, &updated);
if (error != 0)
goto errout;
config_updated |= updated;
error = update_field(
&config->nc_logpath, logpath, defaultdir, &updated);
if (error != 0)
goto errout;
config_updated |= updated;
updated = (config->nc_logformat != logformat);
if (updated)
config->nc_logformat = logformat;
config_updated |= updated;
if (config_updated)
config->nc_flags |= NC_UPDATED;
if (strcmp(tag, DEFAULTTAG) == 0) {
global = config;
error = update_config(global_raw, DEFAULTRAWTAG, defaultdir,
bufferpath, rpclogpath, fhpath, logpath, logformat,
complete, B_FALSE);
if (error != 0)
goto errout;
}
return (error);
errout:
if (nfsl_errs_to_syslog) {
syslog(LOG_ERR, gettext(
"update_config: Can't process \"%s\" config entry: %s"),
tag, strerror(error));
} else {
(void) fprintf(stderr, gettext(
"update_config: Can't process \"%s\" config entry: %s\n"),
tag, strerror(error));
}
return (error);
}
static int
update_field(
char **old,
char *new,
char *prependdir,
boolean_t *updated)
{
char *tmp_new = NULL;
int need_update = 0;
if (new != NULL) {
if (prependdir != NULL && new[0] != '/') {
tmp_new = malloc(strlen(prependdir) + strlen(new) + 2);
if (tmp_new == NULL)
return (ENOMEM);
(void) sprintf(tmp_new, "%s/%s", prependdir, new);
} else {
if ((tmp_new = strdup(new)) == NULL)
return (ENOMEM);
}
}
if (tmp_new != NULL) {
if (*old == NULL)
need_update++;
else if (strcmp(tmp_new, *old) != 0) {
free(*old);
need_update++;
}
if (need_update)
*old = tmp_new;
} else if (*old != NULL) {
need_update++;
free(*old);
*old = NULL;
}
*updated = need_update != 0;
return (0);
}
#ifdef DEBUG
static void
remove_config(
nfsl_config_t **listpp,
nfsl_config_t *config,
nfsl_config_t **tail)
{
nfsl_config_t *p, *prev;
prev = *listpp;
for (p = *listpp; p != NULL; p = p->nc_next) {
if (p == config) {
if (p == prev) {
*listpp = prev->nc_next;
} else
prev->nc_next = p->nc_next;
free_config(p);
break;
}
prev = p;
}
for (*tail = prev; (*tail)->nc_next != NULL; *tail = (*tail)->nc_next)
;
}
#endif
static void
free_config(nfsl_config_t *config)
{
if (config == NULL)
return;
if (config->nc_name)
free(config->nc_name);
if (config->nc_defaultdir)
free(config->nc_defaultdir);
if (config->nc_bufferpath)
free(config->nc_bufferpath);
if (config->nc_rpclogpath)
free(config->nc_rpclogpath);
if (config->nc_fhpath)
free(config->nc_fhpath);
if (config->nc_logpath)
free(config->nc_logpath);
if (config == global)
global = NULL;
if (config == global_raw)
global_raw = NULL;
free(config);
}
void
nfsl_freeconfig_list(nfsl_config_t **listpp)
{
nfsl_config_t *next;
if (*listpp == NULL)
return;
do {
next = (*listpp)->nc_next;
free_config(*listpp);
*listpp = next;
} while (*listpp);
free_config(global_raw);
}
static nfsl_config_t *
findconfig(
nfsl_config_t **listpp,
char *tag, boolean_t remove,
nfsl_config_t **tail)
{
nfsl_config_t *p, *prev;
prev = *listpp;
for (p = *listpp; p != NULL; p = p->nc_next) {
if (strcmp(p->nc_name, tag) == 0) {
if (remove) {
if (p == prev) {
*listpp = prev->nc_next;
} else
prev->nc_next = p->nc_next;
if (tail != NULL && p == *tail) {
*tail = prev;
}
}
return (p);
}
prev = p;
}
return (NULL);
}
static nfsl_config_t *
getlastconfig(nfsl_config_t *listp)
{
nfsl_config_t *lastp = NULL;
for (; listp != NULL; listp = listp->nc_next)
lastp = listp;
return (lastp);
}
nfsl_config_t *
nfsl_findconfig(nfsl_config_t *listp, char *tag, int *error)
{
nfsl_config_t *config;
boolean_t updated;
*error = 0;
config = findconfig(&listp, tag, B_FALSE, (nfsl_config_t **)NULL);
if (config == NULL) {
*error = nfsl_checkconfig_list(&listp, &updated);
if (*error != 0) {
return (NULL);
}
if (updated) {
config = findconfig(&listp, tag, B_FALSE,
(nfsl_config_t **)NULL);
}
}
return (config);
}
static void
complete_with_global(
char **defaultdir,
char **bufferpath,
char **rpclogpath,
char **fhpath,
char **logpath,
int *logformat)
{
if (*defaultdir == NULL)
*defaultdir = global_raw->nc_defaultdir;
if (*bufferpath == NULL)
*bufferpath = global_raw->nc_bufferpath;
if (*rpclogpath == NULL)
*rpclogpath = global_raw->nc_rpclogpath;
if (*fhpath == NULL)
*fhpath = global_raw->nc_fhpath;
if (*logpath == NULL)
*logpath = global_raw->nc_logpath;
if (*logformat == 0)
*logformat = global_raw->nc_logformat;
}
static int
get_info(
char *linebuf,
char **tag,
char **defaultdir,
char **bufferpath,
char **rpclogpath,
char **fhpath,
char **logpath,
int *logformat)
{
char *tok;
char *tmp;
*tag = NULL;
tok = strtok(linebuf, whitespace);
if (tok == NULL)
goto badtag;
if (!is_legal_tag(tok))
goto badtag;
*tag = tok;
*defaultdir = *bufferpath = *rpclogpath = NULL;
*fhpath = *logpath = NULL;
*logformat = 0;
while ((tok = strtok(NULL, whitespace)) != NULL) {
if (strncmp(tok, "defaultdir=", strlen("defaultdir=")) == 0) {
*defaultdir = tok + strlen("defaultdir=");
} else if (strncmp(tok, "buffer=", strlen("buffer=")) == 0) {
*bufferpath = tok + strlen("buffer=");
} else if (strncmp(tok, "rpclog=", strlen("rpclog=")) == 0) {
*rpclogpath = tok + strlen("rpclog=");
} else if (strncmp(tok, "fhtable=", strlen("fhtable=")) == 0) {
*fhpath = tok + strlen("fhtable=");
} else if (strncmp(tok, "log=", strlen("log=")) == 0) {
*logpath = tok + strlen("log=");
} else if (strncmp(tok, "logformat=",
strlen("logformat=")) == 0) {
tmp = tok + strlen("logformat=");
if (strncmp(tmp, "extended", strlen("extended")) == 0) {
*logformat = TRANSLOG_EXTENDED;
} else {
*logformat = TRANSLOG_BASIC;
}
}
}
if (strcmp(*tag, DEFAULTTAG) != 0) {
complete_with_global(defaultdir, bufferpath,
rpclogpath, fhpath, logpath, logformat);
}
return (0);
badtag:
if (nfsl_errs_to_syslog) {
syslog(LOG_ERR, gettext(
"Bad tag found in config file."));
} else {
(void) fprintf(stderr, gettext(
"Bad tag found in config file.\n"));
}
return (-1);
}
static boolean_t
is_complete_config(
char *tag,
char *bufferpath,
char *fhpath,
char *logpath)
{
assert(tag != NULL);
assert(strlen(tag) > 0);
if ((bufferpath != NULL && strlen(bufferpath) > 0) &&
(fhpath != NULL && strlen(fhpath) > 0) &&
(logpath != NULL && strlen(logpath) > 0))
return (B_TRUE);
return (B_FALSE);
}
#ifdef DEBUG
void
nfsl_printconfig(nfsl_config_t *config)
{
if (config->nc_name)
(void) printf("tag=%s\t", config->nc_name);
if (config->nc_defaultdir)
(void) printf("defaultdir=%s\t", config->nc_defaultdir);
if (config->nc_logpath)
(void) printf("logpath=%s\t", config->nc_logpath);
if (config->nc_fhpath)
(void) printf("fhpath=%s\t", config->nc_fhpath);
if (config->nc_bufferpath)
(void) printf("bufpath=%s\t", config->nc_bufferpath);
if (config->nc_rpclogpath)
(void) printf("rpclogpath=%s\t", config->nc_rpclogpath);
if (config->nc_logformat == TRANSLOG_BASIC)
(void) printf("logformat=basic");
else if (config->nc_logformat == TRANSLOG_EXTENDED)
(void) printf("logformat=extended");
else
(void) printf("config->nc_logformat=UNKNOWN");
if (config->nc_flags & NC_UPDATED)
(void) printf("\tflags=NC_UPDATED");
(void) printf("\n");
}
void
nfsl_printconfig_list(nfsl_config_t *listp)
{
for (; listp != NULL; listp = listp->nc_next) {
nfsl_printconfig(listp);
(void) printf("\n");
}
}
#endif
static int
is_legal_tag(char *tag)
{
int i;
int len;
if (tag == NULL)
return (0);
len = strlen(tag);
if (len == 0)
return (0);
for (i = 0; i < len; i++) {
char c;
c = tag[i];
if (!(isalnum((unsigned char)c) || c == '_'))
return (0);
}
return (1);
}
static char *
gataline(FILE *fp, char *path, char *line, int linesz)
{
char *p = line;
int len;
int excess = 0;
*p = '\0';
for (;;) {
if (fgets(p, linesz - (p-line), fp) == NULL) {
return (*line ? line : NULL);
}
len = strlen(line);
if (len <= 0) {
p = line;
continue;
}
p = &line[len - 1];
if (*p != '\n') {
excess = 1;
(void) ungetc(*p, fp);
break;
}
trim:
while (p >= line && isspace(*(uchar_t *)p))
*p-- = '\0';
if (p < line) {
p = line;
continue;
}
if (*p == '\\') {
*p = '\0';
continue;
}
p = line;
while ((p = strchr(p, '#')) != NULL) {
if (p == line || isspace(*(p-1))) {
*p-- = '\0';
goto trim;
}
p++;
}
break;
}
if (excess) {
int c;
while ((c = getc(fp)) != EOF) {
*p = c;
if (*p == '\n')
break;
else if (*p == '\\') {
if (getc(fp) == EOF)
break;
}
}
if (nfsl_errs_to_syslog) {
syslog(LOG_ERR, gettext(
"%s: line too long - ignored (max %d chars)"),
path, linesz-1);
} else {
(void) fprintf(stderr, gettext(
"%s: line too long - ignored (max %d chars)\n"),
path, linesz-1);
}
*line = '\0';
}
return (line);
}