/*
* 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
* 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
*/
/*
*/
#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 <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include "nfslog_config.h"
/*
* This flag controls where error messages go.
* Zero means that messages go to stderr.
* Non-zero means that messages go to syslog.
*/
/*
* Pointer to the global entry in the list
*/
/*
* Pointer to the raw global entry in the list, this is the
* global entry without the expanded paths. This is used to
* complete configurations.
*/
/*
* Last modification time to config file.
*/
/*
* Whitespace characters to delimit fields in a line.
*/
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 *,
static int update_field(char **, char *, char *, boolean_t *);
nfsl_config_t **);
static void complete_with_global(char **, char **, char **, char **,
char **, int *);
#ifdef DEBUG
void nfsl_printconfig(nfsl_config_t *);
#endif /* DEBUG */
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 *);
/*
* Read the configuration file and create a list of configuration
* parameters. Returns zero for success or an errno value.
* The caller is responsible for freeing the returned configlist by calling
* nfsl_freeconfig_list().
*
* If the configuration file does not exist, *listpp points to a config entry
* containing the hardwired defaults.
*/
int
{
int error = 0;
char *locale;
/*
* Set the locale correctly so that we can correctly identify
* alphabetic characters.
*/
/*
* Allocate 'global_raw' structure, its contents are
* indirectly allocated by create_config().
*/
if (global_raw == NULL)
return (error);
/*
* Build global entry with hardwired defaults first.
*/
return (error);
}
else {
/*
* The global entry was replaced with the one in the file,
* clear the UPDATED flag
*/
}
return (error);
}
/*
* Allocates memory for the 'global_raw' structure.
* The actual allocation of values for its components happens in
* update_config().
*/
static nfsl_config_t *
{
nfsl_config_t *p;
*error = 0;
if (p = (nfsl_config_t *)malloc(sizeof (*p)))
(void) memset((void *)p, 0, sizeof (*p));
else
return (p);
}
/*
* Checks if the the configuration file has been modified since we last
* read it, if not simply returns, otherwise it re-reads it adding new
* configuration entries. Note that existing entries that no longer
* exist in the configuration file are not removed. Existing entries
* that are modified in the configuration file are updated in the list
* as well.
* if 'updated' is defined then it is set to TRUE if the list was modified.
*
* Note that if an error occurs, the list may be corrupted.
* It is the responsibility of the caller to free the list.
* If the configuration file does not exist, we simply return the list
* that we previously had, log a message and return success.
*/
int
{
int error = 0;
if (nfsl_errs_to_syslog) {
"Can't stat %s - %s"), NFSL_CONFIG_FILE_PATH,
} else {
"Can't stat %s - %s\n"), NFSL_CONFIG_FILE_PATH,
}
return (0);
}
return (0);
}
/*
* Does the real work. Reads the configuration file and creates the
* list of entries. Assumes that *listpp contains at least one entry.
* The caller is responsible for freeing any config entries added to
* the list whether this routine returns an error or not.
*
* Returns 0 on success and updates the '*listpp' config list,
* Returns non-zero error value otherwise.
*/
static int
{
int error = 0;
int logformat;
if (updating) {
} else {
"Can't open %s - using hardwired defaults",
}
/*
* Use hardwired config.
*/
if (nfsl_errs_to_syslog)
else
return (0);
}
if (nfsl_errs_to_syslog) {
"Can't lock %s - %s"), NFSL_CONFIG_FILE_PATH,
} else {
"Can't lock %s - %s\n"), NFSL_CONFIG_FILE_PATH,
}
goto done;
}
if (linebuf[0] == '\0') {
/*
* ignore lines that exceed max size
*/
continue;
}
break;
/*
* An entry with the same tag name exists,
* update the fields that changed.
*/
if (error)
break;
} else {
/*
* New entry, create it.
*/
break;
else
}
}
if (!error) {
/*
* Get mtime while we have file locked
*/
if (nfsl_errs_to_syslog) {
"Can't stat %s - %s"), NFSL_CONFIG_FILE_PATH,
} else {
"Can't stat %s - %s\n"), NFSL_CONFIG_FILE_PATH,
}
}
}
done:
return (error);
}
/*
* Creates the config structure with the values specified by the
* parameters. If defaultdir has been specified, all relative paths
* are prepended with this defaultdir.
* If 'complete' is set then this must represent a complete config entry
* as specified by is_complete_config(), otherwise no work is perfomed, and
* NULL is returned.
*
* Returns the newly created config structure on success.
* Returns NULL on failure and sets error to the appropriate error.
*/
static nfsl_config_t *
char *tag,
char *defaultdir,
char *bufferpath,
char *rpclogpath,
char *fhpath,
char *logpath,
int logformat,
int *error)
{
return (NULL);
}
if (*error) {
return (NULL);
}
return (config);
}
/*
* Updates the configuration entry with the new information provided,
* sets NC_UPDATED to indicate so. The entry is left untouched if all
* the fields are the same (except for 'nc_rpccookie', 'nc_transcookie'
* and 'nc_next').
* Prepends each path component with 'defauldir' if 'prepend' is set.
*
* Returns 0 on success, error otherwise.
* On error, the config entry is left in an inconsistent state.
* The only thing the caller can really do with it is free it.
*/
static int
char *tag,
char *defaultdir,
char *bufferpath,
char *rpclogpath,
char *fhpath,
char *logpath,
int logformat,
{
int error = 0;
/*
* Not a complete entry
*/
if (nfsl_errs_to_syslog) {
"update_config: \"%s\" not a complete config entry."),
tag);
} else {
"update_config: \"%s\" not a complete config entry.\n"),
tag);
}
return (EINVAL);
}
/*
* New entry
*/
goto errout;
}
} else
if (error = update_field(
goto errout;
if (!prepend) {
/*
* Do not prepend default directory.
*/
defaultdir = NULL;
}
if (error = update_field(
goto errout;
if (error = update_field(
goto errout;
if (error = update_field(
goto errout;
if (error = update_field(
goto errout;
if (updated)
if (config_updated)
/*
* Have the default global config point to this entry.
*/
/*
* Update the global_raw configuration entry.
* Make sure no expanding of paths occurs.
*/
goto errout;
}
return (error);
if (nfsl_errs_to_syslog) {
"update_config: Can't process \"%s\" config entry: %s"),
} else {
"update_config: Can't process \"%s\" config entry: %s\n"),
}
return (error);
}
/*
* Prepends 'prependir' to 'new' if 'prependir' is defined.
* Compares the value of '*old' with 'new', if it has changed,
* then sets whatever 'old' references equal to 'new'.
* Returns 0 on success, error otherwise.
* Sets '*updated' to B_TRUE if field was modified.
* The value of '*updated' is undefined on error.
*/
static int
char **old, /* pointer to config field */
char *new, /* updated value */
char *prependdir, /* prepend this directory to new */
{
int need_update = 0;
return (ENOMEM);
} else {
return (ENOMEM);
}
}
need_update++;
need_update++;
}
if (need_update)
need_update++;
}
*updated = need_update != 0;
return (0);
}
#ifdef DEBUG
/*
* Removes and frees the 'config' entry from the list
* pointed to by '*listpp'.
* No error is reported if the entry does not exist.
* Updates '*tail' to point to the last item in the list.
*/
static void
{
if (p == config) {
if (p == prev) {
/*
* first element of the list
*/
} else
free_config(p);
break;
}
prev = p;
}
/*
* Find tail of the list.
*/
;
}
#endif /* DEBUG */
static void
{
return;
if (config->nc_defaultdir)
if (config->nc_bufferpath)
if (config->nc_rpclogpath)
if (config->nc_logpath)
if (config == global_raw)
global_raw = NULL;
}
void
{
return;
do {
} while (*listpp);
}
/*
* Returns a pointer to the first instance of 'tag' in the list.
* If 'remove' is true, then the entry is removed from the list and
* a pointer to it is returned.
* If '*tail' is not NULL, then it will point to the last element of
* the list. Note that this function assumes that *tail already
* points at the last element of the list.
* Returns NULL if the entry does not exist.
*/
static nfsl_config_t *
{
if (remove) {
if (p == prev) {
/*
* first element of the list
*/
} else
/*
* Only update *tail if we removed
* the last element of the list, and we
* requested *tail to be updated.
*/
}
}
return (p);
}
prev = p;
}
return (NULL);
}
static nfsl_config_t *
{
return (lastp);
}
/*
* Returns a pointer to the first instance of 'tag' in the list.
* Returns NULL if the entry does not exist.
* Sets 'error' if the update of the list failed if necessary, and
* returns NULL.
*/
{
*error = 0;
/*
* Rebuild our list if the file has changed.
*/
/*
* List may be corrupted, notify caller.
*/
return (NULL);
}
if (updated) {
/*
* Search for tag again.
*/
(nfsl_config_t **)NULL);
}
}
return (config);
}
/*
* Use the raw global values if any of the parameters is not defined.
*/
static void
char **defaultdir,
char **bufferpath,
char **rpclogpath,
char **fhpath,
char **logpath,
int *logformat)
{
if (*defaultdir == NULL)
if (*bufferpath == NULL)
if (*rpclogpath == NULL)
if (*logformat == 0)
}
/*
* Parses 'linebuf'. Returns 0 if a valid tag is found, otherwise non-zero.
* Unknown tokens are silently ignored.
* It is the responsibility of the caller to make a copy of the non-NULL
* parameters if they need to be used before linebuf is freed.
*/
static int
char *linebuf,
char **tag,
char **defaultdir,
char **bufferpath,
char **rpclogpath,
char **fhpath,
char **logpath,
int *logformat)
{
char *tok;
char *tmp;
/* tag */
goto badtag;
if (!is_legal_tag(tok))
goto badtag;
*logformat = 0;
strlen("logformat=")) == 0) {
} else {
/*
* Use transaction log basic format if
* 'extended' was not specified.
*/
}
}
}
/*
* Use global values for fields not specified if
* this tag is not the global tag.
*/
}
return (0);
if (nfsl_errs_to_syslog) {
"Bad tag found in config file."));
} else {
"Bad tag found in config file.\n"));
}
return (-1);
}
/*
* Returns True if we have all the elements of a complete configuration
* entry. A complete configuration has tag, bufferpath, fhpath and logpath
* defined to non-zero strings.
*/
static boolean_t
char *tag,
char *bufferpath,
char *fhpath,
char *logpath)
{
return (B_TRUE);
return (B_FALSE);
}
#ifdef DEBUG
/*
* Prints the configuration entry to stdout.
*/
void
{
if (config->nc_defaultdir)
if (config->nc_logpath)
if (config->nc_bufferpath)
if (config->nc_rpclogpath)
(void) printf("logformat=basic");
(void) printf("logformat=extended");
else
(void) printf("config->nc_logformat=UNKNOWN");
(void) printf("\tflags=NC_UPDATED");
(void) printf("\n");
}
/*
* Prints the configuration list to stdout.
*/
void
{
(void) printf("\n");
}
}
#endif /* DEBUG */
/*
* Returns non-zero if the given string is allowable for a tag, zero if
* not.
*/
static int
{
int i;
int len;
return (0);
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);
}
/*
* gataline attempts to get a line from the configuration file,
* upto LINESZ. A line in the file is a concatenation of lines if the
* continuation symbol '\' is used at the end of the line. Returns
* line on success, a NULL on EOF, and an empty string on lines > linesz.
*/
static char *
register char *p = line;
register int len;
int excess = 0;
*p = '\0';
for (;;) {
}
if (len <= 0) {
p = line;
continue;
}
/*
* Is input line too long?
*/
if (*p != '\n') {
excess = 1;
/*
* Perhaps last char read was '\'. Reinsert it
* into the stream to ease the parsing when we
* read the rest of the line to discard.
*/
break;
}
trim:
/* trim trailing white space */
*p-- = '\0';
if (p < line) { /* empty line */
p = line;
continue;
}
if (*p == '\\') { /* continuation */
*p = '\0';
continue;
}
/*
* Ignore comments. Comments start with '#'
* which must be preceded by a whitespace, unless
* '#' is the first character in the line.
*/
p = line;
while (p = strchr(p, '#')) {
*p-- = '\0';
goto trim;
}
p++;
}
break;
}
if (excess) {
int c;
/*
* discard rest of line and return an empty string.
* done to set the stream to the correct place when
* we are done with this line.
*/
*p = c;
if (*p == '\n') /* end of the long line */
break;
else if (*p == '\\') { /* continuation */
break;
}
}
if (nfsl_errs_to_syslog) {
"%s: line too long - ignored (max %d chars)"),
} else {
"%s: line too long - ignored (max %d chars)\n"),
}
*line = '\0';
}
return (line);
}