/* * 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 (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nfslog_config.h" #define ERROR_BUFSZ 100 /* * This flag controls where error messages go. * Zero means that messages go to stderr. * Non-zero means that messages go to syslog. */ boolean_t nfsl_errs_to_syslog; /* * Pointer to the global entry in the list */ static nfsl_config_t *global = NULL; /* * Pointer to the raw global entry in the list, this is the * global entry without the expanded paths. This is used to * complete configurations. */ static nfsl_config_t *global_raw = NULL; /* * Last modification time to config file. */ static timestruc_t config_last_modification = { 0 }; /* * Whitespace characters to delimit fields in a line. */ 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 /* DEBUG */ 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 *); /* * 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 nfsl_getconfig_list(nfsl_config_t **listpp) { int error = 0; char *locale; /* * Set the locale correctly so that we can correctly identify * alphabetic characters. */ 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); /* * Allocate 'global_raw' structure, its contents are * indirectly allocated by create_config(). */ assert(global_raw == NULL); global_raw = create_global_raw(&error); if (global_raw == NULL) return (error); /* * Build global entry with hardwired defaults first. */ 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); } if (error = getconfiglist(listpp, B_FALSE)) nfsl_freeconfig_list(listpp); else { assert(global != NULL); /* * The global entry was replaced with the one in the file, * clear the UPDATED flag */ global->nc_flags &= ~NC_UPDATED; } 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 * create_global_raw(int *error) { nfsl_config_t *p; *error = 0; if (p = (nfsl_config_t *)malloc(sizeof (*p))) (void) memset((void *)p, 0, sizeof (*p)); else *error = ENOMEM; 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 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)); } /* * 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 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); } /* * Use hardwired config. */ 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') { /* * ignore lines that exceed max size */ continue; } if (error = get_info(linebuf, &tag, &defaultdir, &bufferpath, &rpclogpath, &fhpath, &logpath, &logformat)) break; if (listp = findconfig(listpp, tag, B_FALSE, &tail)) { /* * An entry with the same tag name exists, * update the fields that changed. */ error = update_config(listp, tag, defaultdir, bufferpath, rpclogpath, fhpath, logpath, logformat, B_TRUE, B_TRUE); if (error) break; } else { /* * New entry, create it. */ 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) { /* * Get mtime while we have file locked */ if (error = fstat(fileno(fp), &st)) { 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); } /* * 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 * 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; if ((config = (nfsl_config_t *)malloc(sizeof (*config))) == NULL) { *error = ENOMEM; return (NULL); } (void) memset((void *)config, 0, sizeof (*config)); *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; /* This is a new entry */ 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 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)) { /* * Not a complete entry */ 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) { /* * New entry */ if ((config->nc_name = strdup(tag)) == NULL) { error = ENOMEM; goto errout; } } else assert(strcmp(config->nc_name, tag) == 0); if (error = update_field( &config->nc_defaultdir, defaultdir, NULL, &updated)) goto errout; if (!prepend) { /* * Do not prepend default directory. */ defaultdir = NULL; } config_updated |= updated; if (error = update_field( &config->nc_bufferpath, bufferpath, defaultdir, &updated)) goto errout; config_updated |= updated; if (error = update_field( &config->nc_rpclogpath, rpclogpath, defaultdir, &updated)) goto errout; config_updated |= updated; if (error = update_field( &config->nc_fhpath, fhpath, defaultdir, &updated)) goto errout; config_updated |= updated; if (error = update_field( &config->nc_logpath, logpath, defaultdir, &updated)) 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) { /* * Have the default global config point to this entry. */ global = config; /* * Update the global_raw configuration entry. * Make sure no expanding of paths occurs. */ if (error = update_config(global_raw, DEFAULTRAWTAG, defaultdir, bufferpath, rpclogpath, fhpath, logpath, logformat, complete, B_FALSE)) 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); } /* * 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 update_field( char **old, /* pointer to config field */ char *new, /* updated value */ char *prependdir, /* prepend this directory to new */ boolean_t *updated) /* field was modified */ { 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 /* * 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 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) { /* * first element of the list */ *listpp = prev->nc_next; } else prev->nc_next = p->nc_next; free_config(p); break; } prev = p; } /* * Find tail of the list. */ for (*tail = prev; (*tail)->nc_next != NULL; *tail = (*tail)->nc_next) ; } #endif /* DEBUG */ 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); } /* * 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 * 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) { /* * first element of the list */ *listpp = prev->nc_next; } else prev->nc_next = p->nc_next; if (tail != NULL && p == *tail) { /* * Only update *tail if we removed * the last element of the list, and we * requested *tail to be updated. */ *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); } /* * 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. */ 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) { /* * Rebuild our list if the file has changed. */ if (*error = nfsl_checkconfig_list(&listp, &updated)) { /* * List may be corrupted, notify caller. */ return (NULL); } if (updated) { /* * Search for tag again. */ config = findconfig(&listp, tag, B_FALSE, (nfsl_config_t **)NULL); } } return (config); } /* * Use the raw global values if any of the parameters is not defined. */ 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; } /* * 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 get_info( char *linebuf, char **tag, char **defaultdir, char **bufferpath, char **rpclogpath, char **fhpath, char **logpath, int *logformat) { char *tok; char *tmp; /* tag */ *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)) { 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 { /* * Use transaction log basic format if * 'extended' was not specified. */ *logformat = TRANSLOG_BASIC; } } } if (strcmp(*tag, DEFAULTTAG) != 0) { /* * Use global values for fields not specified if * this tag is not the global tag. */ 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); } /* * 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 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 /* * Prints the configuration entry to stdout. */ 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"); } /* * Prints the configuration list to stdout. */ void nfsl_printconfig_list(nfsl_config_t *listp) { for (; listp != NULL; listp = listp->nc_next) { nfsl_printconfig(listp); (void) printf("\n"); } } #endif /* DEBUG */ /* * Returns non-zero if the given string is allowable for a tag, zero if * not. */ 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); } /* * 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 * gataline(FILE *fp, char *path, char *line, int linesz) { register char *p = line; register int len; int excess = 0; *p = '\0'; for (;;) { if (fgets(p, linesz - (p-line), fp) == NULL) { return (*line ? line : NULL); /* EOF */ } len = strlen(line); if (len <= 0) { p = line; continue; } p = &line[len - 1]; /* * 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. */ (void) ungetc(*p, fp); break; } trim: /* trim trailing white space */ while (p >= line && isspace(*(uchar_t *)p)) *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, '#')) { if (p == line || isspace(*(p-1))) { *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. */ while ((c = getc(fp)) != EOF) { *p = c; if (*p == '\n') /* end of the long line */ break; else if (*p == '\\') { /* continuation */ if (getc(fp) == EOF) /* ignore next char */ 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); }