xref: /illumos-gate/usr/src/cmd/fs.d/nfs/lib/nfslog_config.c (revision 533affcbc7fc4d0c8132976ea454aaa715fe2307)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
24  */
25 
26 #include <ctype.h>
27 #include <errno.h>
28 #include <locale.h>
29 #include <stdarg.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <strings.h>
33 #include <string.h>
34 #include <syslog.h>
35 #include <nfs/nfs.h>
36 #include <assert.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <unistd.h>
40 #include <fcntl.h>
41 #include "nfslog_config.h"
42 
43 #define	ERROR_BUFSZ	100
44 
45 /*
46  * This flag controls where error messages go.
47  * Zero means that messages go to stderr.
48  * Non-zero means that messages go to syslog.
49  */
50 boolean_t nfsl_errs_to_syslog;
51 
52 /*
53  * Pointer to the global entry in the list
54  */
55 static nfsl_config_t *global = NULL;
56 
57 /*
58  * Pointer to the raw global entry in the list, this is the
59  * global entry without the expanded paths. This is used to
60  * complete configurations.
61  */
62 static nfsl_config_t *global_raw = NULL;
63 
64 /*
65  * Last modification time to config file.
66  */
67 static timestruc_t config_last_modification = { 0 };
68 
69 /*
70  * Whitespace characters to delimit fields in a line.
71  */
72 static const char *whitespace = " \t";
73 
74 static int getconfiglist(nfsl_config_t **, boolean_t);
75 static nfsl_config_t *create_config(char *, char *, char *, char *, char *,
76 			char *, int, boolean_t, int *);
77 static nfsl_config_t *create_global_raw(int *);
78 static int update_config(nfsl_config_t *, char *, char *, char *,
79 			char *, char *, char *, int, boolean_t, boolean_t);
80 static int update_field(char **, char *, char *, boolean_t *);
81 static nfsl_config_t *findconfig(nfsl_config_t **, char *, boolean_t,
82 			nfsl_config_t **);
83 static nfsl_config_t *getlastconfig(nfsl_config_t *);
84 static void complete_with_global(char **, char **, char **, char **,
85 			char **, int *);
86 #ifdef DEBUG
87 static void remove_config(nfsl_config_t **, nfsl_config_t *, nfsl_config_t **);
88 void nfsl_printconfig(nfsl_config_t *);
89 #endif /* DEBUG */
90 static char *gataline(FILE *, char *, char *, int);
91 static int get_info(char *, char **, char **, char **, char **, char **,
92 			char **, int *);
93 static void free_config(nfsl_config_t *);
94 static int is_legal_tag(char *);
95 static boolean_t is_complete_config(char *, char *, char *, char *);
96 
97 /*
98  * Read the configuration file and create a list of configuration
99  * parameters.  Returns zero for success or an errno value.
100  * The caller is responsible for freeing the returned configlist by calling
101  * nfsl_freeconfig_list().
102  *
103  * If the configuration file does not exist, *listpp points to a config entry
104  * containing the hardwired defaults.
105  */
106 int
107 nfsl_getconfig_list(nfsl_config_t **listpp)
108 {
109 	int error = 0;
110 	char *locale;
111 
112 	/*
113 	 * Set the locale correctly so that we can correctly identify
114 	 * alphabetic characters.
115 	 */
116 	if ((locale = getenv("LC_ALL")) != NULL)
117 		(void) setlocale(LC_ALL, locale);
118 	else if ((locale = getenv("LC_CTYPE")) != NULL)
119 		(void) setlocale(LC_CTYPE, locale);
120 	else if ((locale = getenv("LANG")) != NULL)
121 		(void) setlocale(LC_CTYPE, locale);
122 
123 	/*
124 	 * Allocate 'global_raw' structure, its contents are
125 	 * indirectly allocated by create_config().
126 	 */
127 	assert(global_raw == NULL);
128 	global_raw = create_global_raw(&error);
129 	if (global_raw == NULL)
130 		return (error);
131 
132 	/*
133 	 * Build global entry with hardwired defaults first.
134 	 */
135 	assert(global == NULL);
136 	global = create_config(DEFAULTTAG, DEFAULTDIR, BUFFERPATH, NULL,
137 	    FHPATH, LOGPATH, TRANSLOG_BASIC, B_TRUE, &error);
138 	*listpp = global;
139 	if (global == NULL) {
140 		free_config(global_raw);
141 		return (error);
142 	}
143 
144 	error = getconfiglist(listpp, B_FALSE);
145 	if (error != 0) {
146 		nfsl_freeconfig_list(listpp);
147 	} else {
148 		assert(global != NULL);
149 		/*
150 		 * The global entry was replaced with the one in the file,
151 		 * clear the UPDATED flag
152 		 */
153 		global->nc_flags &= ~NC_UPDATED;
154 	}
155 	return (error);
156 }
157 
158 /*
159  * Allocates memory for the 'global_raw' structure.
160  * The actual allocation of values for its components happens in
161  * update_config().
162  */
163 static nfsl_config_t *
164 create_global_raw(int *error)
165 {
166 	nfsl_config_t *p;
167 
168 	*error = 0;
169 	p = calloc(1, sizeof (*p));
170 	if (p == NULL)
171 		*error = ENOMEM;
172 
173 	return (p);
174 }
175 
176 /*
177  * Checks if the the configuration file has been modified since we last
178  * read it, if not simply returns, otherwise it re-reads it adding new
179  * configuration entries. Note that existing entries that no longer
180  * exist in the configuration file are not removed. Existing entries
181  * that are modified in the configuration file are updated in the list
182  * as well.
183  * if 'updated' is defined then it is set to TRUE if the list was modified.
184  *
185  * Note that if an error occurs, the list may be corrupted.
186  * It is the responsibility of the caller to free the list.
187  * If the configuration file does not exist, we simply return the list
188  * that we previously had, log a message and return success.
189  */
190 int
191 nfsl_checkconfig_list(nfsl_config_t **listpp, boolean_t *updated)
192 {
193 	struct stat st;
194 	int error = 0;
195 
196 	if (updated != NULL)
197 		*updated = B_FALSE;
198 
199 	if (stat(NFSL_CONFIG_FILE_PATH, &st) == -1) {
200 		error = errno;
201 		if (nfsl_errs_to_syslog) {
202 			syslog(LOG_ERR, gettext(
203 			    "Can't stat %s - %s"), NFSL_CONFIG_FILE_PATH,
204 			    strerror(error));
205 		} else {
206 			(void) fprintf(stderr, gettext(
207 			    "Can't stat %s - %s\n"), NFSL_CONFIG_FILE_PATH,
208 			    strerror(error));
209 		}
210 		return (0);
211 	}
212 
213 	if (config_last_modification.tv_sec == st.st_mtim.tv_sec &&
214 	    config_last_modification.tv_nsec == st.st_mtim.tv_nsec)
215 		return (0);
216 
217 	if (updated != NULL)
218 		*updated = B_TRUE;
219 
220 	return (getconfiglist(listpp, B_TRUE));
221 }
222 
223 /*
224  * Does the real work. Reads the configuration file and creates the
225  * list of entries. Assumes that *listpp contains at least one entry.
226  * The caller is responsible for freeing any config entries added to
227  * the list whether this routine returns an error or not.
228  *
229  * Returns 0 on success and updates the '*listpp' config list,
230  * Returns non-zero error value otherwise.
231  */
232 static int
233 getconfiglist(nfsl_config_t **listpp, boolean_t updating)
234 {
235 	FILE *fp;
236 	int error = 0;
237 	nfsl_config_t *listp = NULL, *tail = NULL;
238 	char linebuf[MAX_LINESZ];
239 	char errorbuf[ERROR_BUFSZ];
240 	char *tag, *defaultdir, *bufferpath, *rpclogpath, *fhpath, *logpath;
241 	int logformat;
242 	flock_t flock;
243 	struct stat st;
244 
245 	fp = fopen(NFSL_CONFIG_FILE_PATH, "r");
246 	if (fp == NULL) {
247 		if (updating) {
248 			(void) sprintf(errorbuf, "Can't open %s",
249 			    NFSL_CONFIG_FILE_PATH);
250 		} else {
251 			(void) sprintf(errorbuf,
252 			    "Can't open %s - using hardwired defaults",
253 			    NFSL_CONFIG_FILE_PATH);
254 		}
255 
256 		/*
257 		 * Use hardwired config.
258 		 */
259 		if (nfsl_errs_to_syslog)
260 			syslog(LOG_ERR, gettext("%s"), errorbuf);
261 		else
262 			(void) fprintf(stderr, gettext("%s\n"), errorbuf);
263 
264 		return (0);
265 	}
266 
267 	(void) memset((void *) &flock, 0, sizeof (flock));
268 	flock.l_type = F_RDLCK;
269 	if (fcntl(fileno(fp), F_SETLKW, &flock) == -1) {
270 		error = errno;
271 		if (nfsl_errs_to_syslog) {
272 			syslog(LOG_ERR, gettext(
273 			    "Can't lock %s - %s"), NFSL_CONFIG_FILE_PATH,
274 			    strerror(error));
275 		} else {
276 			(void) fprintf(stderr, gettext(
277 			    "Can't lock %s - %s\n"), NFSL_CONFIG_FILE_PATH,
278 			    strerror(error));
279 		}
280 		goto done;
281 	}
282 
283 	assert (*listpp != NULL);
284 	tail = getlastconfig(*listpp);
285 
286 	while (gataline(fp, NFSL_CONFIG_FILE_PATH, linebuf, sizeof (linebuf))) {
287 		if (linebuf[0] == '\0') {
288 			/*
289 			 * ignore lines that exceed max size
290 			 */
291 			continue;
292 		}
293 
294 		error = get_info(linebuf, &tag, &defaultdir, &bufferpath,
295 		    &rpclogpath, &fhpath, &logpath, &logformat);
296 		if (error != 0)
297 			break;
298 
299 		listp = findconfig(listpp, tag, B_FALSE, &tail);
300 		if (listp != NULL) {
301 			/*
302 			 * An entry with the same tag name exists,
303 			 * update the fields that changed.
304 			 */
305 			error = update_config(listp, tag, defaultdir,
306 			    bufferpath, rpclogpath, fhpath, logpath,
307 			    logformat, B_TRUE, B_TRUE);
308 			if (error)
309 				break;
310 		} else {
311 			/*
312 			 * New entry, create it.
313 			 */
314 			listp = create_config(tag, defaultdir,
315 			    bufferpath, rpclogpath, fhpath,
316 			    logpath, logformat, B_TRUE, &error);
317 			if (listp == NULL)
318 				break;
319 
320 			if (*listpp == NULL)
321 				*listpp = listp;
322 			else
323 				tail->nc_next = listp;
324 			tail = listp;
325 		}
326 
327 		assert(global != NULL);
328 	}
329 
330 	if (error == 0) {
331 		/*
332 		 * Get mtime while we have file locked
333 		 */
334 		error = fstat(fileno(fp), &st);
335 		if (error != 0) {
336 			error = errno;
337 			if (nfsl_errs_to_syslog) {
338 				syslog(LOG_ERR, gettext(
339 				    "Can't stat %s - %s"),
340 				    NFSL_CONFIG_FILE_PATH,
341 				    strerror(error));
342 			} else {
343 				(void) fprintf(stderr, gettext(
344 				    "Can't stat %s - %s\n"),
345 				    NFSL_CONFIG_FILE_PATH,
346 				    strerror(error));
347 			}
348 		}
349 		config_last_modification = st.st_mtim;
350 	}
351 
352 done:
353 	(void) fclose(fp);
354 	return (error);
355 }
356 
357 /*
358  * Creates the config structure with the values specified by the
359  * parameters. If defaultdir has been specified, all relative paths
360  * are prepended with this defaultdir.
361  * If 'complete' is set then this must represent a complete config entry
362  * as specified by is_complete_config(), otherwise no work is perfomed, and
363  * NULL is returned.
364  *
365  * Returns the newly created config structure on success.
366  * Returns NULL on failure and sets error to the appropriate error.
367  */
368 static nfsl_config_t *
369 create_config(
370     char *tag,
371     char *defaultdir,
372     char *bufferpath,
373     char *rpclogpath,
374     char *fhpath,
375     char *logpath,
376     int   logformat,
377     boolean_t complete,
378     int  *error)
379 {
380 	nfsl_config_t *config;
381 
382 	config = calloc(1, sizeof (*config));
383 	if (config == NULL) {
384 		*error = ENOMEM;
385 		return (NULL);
386 	}
387 
388 	*error = update_config(config, tag, defaultdir, bufferpath, rpclogpath,
389 	    fhpath, logpath, logformat, complete, B_TRUE);
390 	if (*error) {
391 		free(config);
392 		return (NULL);
393 	}
394 
395 	config->nc_flags &= ~NC_UPDATED;	/* This is a new entry */
396 
397 	return (config);
398 }
399 
400 
401 /*
402  * Updates the configuration entry with the new information provided,
403  * sets NC_UPDATED to indicate so. The entry is left untouched if all
404  * the fields are the same (except for 'nc_rpccookie', 'nc_transcookie'
405  * and 'nc_next').
406  * Prepends each path component with 'defauldir' if 'prepend' is set.
407  *
408  * Returns 0 on success, error otherwise.
409  * On error, the config entry is left in an inconsistent state.
410  * The only thing the caller can really do with it is free it.
411  */
412 static int
413 update_config(
414 	nfsl_config_t *config,
415 	char *tag,
416 	char *defaultdir,
417 	char *bufferpath,
418 	char *rpclogpath,
419 	char *fhpath,
420 	char *logpath,
421 	int   logformat,
422 	boolean_t complete,
423 	boolean_t prepend)
424 {
425 	boolean_t updated, config_updated = B_FALSE;
426 	int error = 0;
427 
428 	if (complete && !is_complete_config(tag, bufferpath, fhpath, logpath)) {
429 		/*
430 		 * Not a complete entry
431 		 */
432 		if (nfsl_errs_to_syslog) {
433 			syslog(LOG_ERR, gettext(
434 			    "update_config: \"%s\" not a complete "
435 			    "config entry."), tag);
436 		} else {
437 			(void) fprintf(stderr, gettext(
438 			    "update_config: \"%s\" not a complete "
439 			    "config entry.\n"), tag);
440 		}
441 		return (EINVAL);
442 	}
443 
444 	assert(tag != NULL);
445 	if (config->nc_name == NULL) {
446 		/*
447 		 * New entry
448 		 */
449 		if ((config->nc_name = strdup(tag)) == NULL) {
450 			error = ENOMEM;
451 			goto errout;
452 		}
453 	} else {
454 		assert(strcmp(config->nc_name, tag) == 0);
455 	}
456 
457 	error = update_field(
458 	    &config->nc_defaultdir, defaultdir, NULL, &updated);
459 	if (error != 0)
460 		goto errout;
461 	if (!prepend) {
462 		/*
463 		 * Do not prepend default directory.
464 		 */
465 		defaultdir = NULL;
466 	}
467 	config_updated |= updated;
468 	error = update_field(
469 	    &config->nc_bufferpath, bufferpath, defaultdir, &updated);
470 	if (error != 0)
471 		goto errout;
472 	config_updated |= updated;
473 	error = update_field(
474 	    &config->nc_rpclogpath, rpclogpath, defaultdir, &updated);
475 	if (error != 0)
476 		goto errout;
477 	config_updated |= updated;
478 	error = update_field(
479 	    &config->nc_fhpath, fhpath, defaultdir, &updated);
480 	if (error != 0)
481 		goto errout;
482 	config_updated |= updated;
483 	error = update_field(
484 	    &config->nc_logpath, logpath, defaultdir, &updated);
485 	if (error != 0)
486 		goto errout;
487 	config_updated |= updated;
488 	updated = (config->nc_logformat != logformat);
489 	if (updated)
490 		config->nc_logformat = logformat;
491 	config_updated |= updated;
492 
493 	if (config_updated)
494 		config->nc_flags |= NC_UPDATED;
495 
496 	if (strcmp(tag, DEFAULTTAG) == 0) {
497 		/*
498 		 * Have the default global config point to this entry.
499 		 */
500 		global = config;
501 
502 		/*
503 		 * Update the global_raw configuration entry.
504 		 * Make sure no expanding of paths occurs.
505 		 */
506 		error = update_config(global_raw, DEFAULTRAWTAG, defaultdir,
507 		    bufferpath, rpclogpath, fhpath, logpath, logformat,
508 		    complete, B_FALSE);
509 		if (error != 0)
510 			goto errout;
511 	}
512 
513 	return (error);
514 
515 errout:
516 	if (nfsl_errs_to_syslog) {
517 		syslog(LOG_ERR, gettext(
518 		    "update_config: Can't process \"%s\" config entry: %s"),
519 		    tag, strerror(error));
520 	} else {
521 		(void) fprintf(stderr, gettext(
522 		    "update_config: Can't process \"%s\" config entry: %s\n"),
523 		    tag, strerror(error));
524 	}
525 	return (error);
526 }
527 
528 /*
529  * Prepends 'prependir' to 'new' if 'prependir' is defined.
530  * Compares the value of '*old' with 'new', if it has changed,
531  * then sets whatever 'old' references equal to 'new'.
532  * Returns 0 on success, error otherwise.
533  * Sets '*updated' to B_TRUE if field was modified.
534  * The value of '*updated' is undefined on error.
535  */
536 static int
537 update_field(
538 	char **old,		/* pointer to config field */
539 	char *new,		/* updated value */
540 	char *prependdir,	/* prepend this directory to new */
541 	boolean_t *updated)	/* field was modified */
542 {
543 	char *tmp_new = NULL;
544 	int need_update = 0;
545 
546 	if (new != NULL) {
547 		if (prependdir != NULL && new[0] != '/') {
548 			tmp_new = malloc(strlen(prependdir) + strlen(new) + 2);
549 			if (tmp_new == NULL)
550 				return (ENOMEM);
551 			(void) sprintf(tmp_new, "%s/%s", prependdir, new);
552 		} else {
553 			if ((tmp_new = strdup(new)) == NULL)
554 				return (ENOMEM);
555 		}
556 	}
557 
558 	if (tmp_new != NULL) {
559 		if (*old == NULL)
560 			need_update++;
561 		else if (strcmp(tmp_new, *old) != 0) {
562 			free(*old);
563 			need_update++;
564 		}
565 		if (need_update)
566 			*old = tmp_new;
567 	} else if (*old != NULL) {
568 		need_update++;
569 		free(*old);
570 		*old = NULL;
571 	}
572 
573 	*updated = need_update != 0;
574 	return (0);
575 }
576 
577 #ifdef DEBUG
578 /*
579  * Removes and frees the 'config' entry from the list
580  * pointed to by '*listpp'.
581  * No error is reported if the entry does not exist.
582  * Updates '*tail' to point to the last item in the list.
583  */
584 static void
585 remove_config(
586 	nfsl_config_t **listpp,
587 	nfsl_config_t *config,
588 	nfsl_config_t **tail)
589 {
590 	nfsl_config_t *p, *prev;
591 
592 	prev = *listpp;
593 	for (p = *listpp; p != NULL; p = p->nc_next) {
594 		if (p == config) {
595 			if (p == prev) {
596 				/*
597 				 * first element of the list
598 				 */
599 				*listpp = prev->nc_next;
600 			} else
601 				prev->nc_next = p->nc_next;
602 			free_config(p);
603 			break;
604 		}
605 		prev = p;
606 	}
607 
608 	/*
609 	 * Find tail of the list.
610 	 */
611 	for (*tail = prev; (*tail)->nc_next != NULL; *tail = (*tail)->nc_next)
612 		;
613 }
614 #endif /* DEBUG */
615 
616 static void
617 free_config(nfsl_config_t *config)
618 {
619 	if (config == NULL)
620 		return;
621 	if (config->nc_name)
622 		free(config->nc_name);
623 	if (config->nc_defaultdir)
624 		free(config->nc_defaultdir);
625 	if (config->nc_bufferpath)
626 		free(config->nc_bufferpath);
627 	if (config->nc_rpclogpath)
628 		free(config->nc_rpclogpath);
629 	if (config->nc_fhpath)
630 		free(config->nc_fhpath);
631 	if (config->nc_logpath)
632 		free(config->nc_logpath);
633 	if (config == global)
634 		global = NULL;
635 	if (config == global_raw)
636 		global_raw = NULL;
637 	free(config);
638 }
639 
640 void
641 nfsl_freeconfig_list(nfsl_config_t **listpp)
642 {
643 	nfsl_config_t *next;
644 
645 	if (*listpp == NULL)
646 		return;
647 
648 	do {
649 		next = (*listpp)->nc_next;
650 		free_config(*listpp);
651 		*listpp = next;
652 	} while (*listpp);
653 
654 	free_config(global_raw);
655 }
656 
657 /*
658  * Returns a pointer to the first instance of 'tag' in the list.
659  * If 'remove' is true, then the entry is removed from the list and
660  * a pointer to it is returned.
661  * If '*tail' is not NULL, then it will point to the last element of
662  * the list. Note that this function assumes that *tail already
663  * points at the last element of the list.
664  * Returns NULL if the entry does not exist.
665  */
666 static nfsl_config_t *
667 findconfig(
668 	nfsl_config_t **listpp,
669 	char *tag, boolean_t remove,
670 	nfsl_config_t **tail)
671 {
672 	nfsl_config_t *p, *prev;
673 
674 	prev = *listpp;
675 	for (p = *listpp; p != NULL; p = p->nc_next) {
676 		if (strcmp(p->nc_name, tag) == 0) {
677 			if (remove) {
678 				if (p == prev) {
679 					/*
680 					 * first element of the list
681 					 */
682 					*listpp = prev->nc_next;
683 				} else
684 					prev->nc_next = p->nc_next;
685 
686 				if (tail != NULL && p == *tail) {
687 					/*
688 					 * Only update *tail if we removed
689 					 * the last element of the list, and we
690 					 * requested *tail to be updated.
691 					 */
692 					*tail = prev;
693 				}
694 			}
695 			return (p);
696 		}
697 		prev = p;
698 	}
699 
700 	return (NULL);
701 }
702 
703 static nfsl_config_t *
704 getlastconfig(nfsl_config_t *listp)
705 {
706 	nfsl_config_t *lastp = NULL;
707 
708 	for (; listp != NULL; listp = listp->nc_next)
709 		lastp = listp;
710 
711 	return (lastp);
712 }
713 
714 /*
715  * Returns a pointer to the first instance of 'tag' in the list.
716  * Returns NULL if the entry does not exist.
717  * Sets 'error' if the update of the list failed if necessary, and
718  * returns NULL.
719  */
720 nfsl_config_t *
721 nfsl_findconfig(nfsl_config_t *listp, char *tag, int *error)
722 {
723 	nfsl_config_t *config;
724 	boolean_t updated;
725 
726 	*error = 0;
727 	config = findconfig(&listp, tag, B_FALSE, (nfsl_config_t **)NULL);
728 	if (config == NULL) {
729 		/*
730 		 * Rebuild our list if the file has changed.
731 		 */
732 		*error = nfsl_checkconfig_list(&listp, &updated);
733 		if (*error != 0) {
734 			/*
735 			 * List may be corrupted, notify caller.
736 			 */
737 			return (NULL);
738 		}
739 		if (updated) {
740 			/*
741 			 * Search for tag again.
742 			 */
743 			config = findconfig(&listp, tag, B_FALSE,
744 			    (nfsl_config_t **)NULL);
745 		}
746 	}
747 
748 	return (config);
749 }
750 
751 /*
752  * Use the raw global values if any of the parameters is not defined.
753  */
754 static void
755 complete_with_global(
756 	char **defaultdir,
757 	char **bufferpath,
758 	char **rpclogpath,
759 	char **fhpath,
760 	char **logpath,
761 	int  *logformat)
762 {
763 	if (*defaultdir == NULL)
764 		*defaultdir = global_raw->nc_defaultdir;
765 	if (*bufferpath == NULL)
766 		*bufferpath = global_raw->nc_bufferpath;
767 	if (*rpclogpath == NULL)
768 		*rpclogpath = global_raw->nc_rpclogpath;
769 	if (*fhpath == NULL)
770 		*fhpath = global_raw->nc_fhpath;
771 	if (*logpath == NULL)
772 		*logpath = global_raw->nc_logpath;
773 	if (*logformat == 0)
774 		*logformat = global_raw->nc_logformat;
775 }
776 
777 /*
778  * Parses 'linebuf'. Returns 0 if a valid tag is found, otherwise non-zero.
779  * Unknown tokens are silently ignored.
780  * It is the responsibility of the caller to make a copy of the non-NULL
781  * parameters if they need to be used before linebuf is freed.
782  */
783 static int
784 get_info(
785 	char *linebuf,
786 	char **tag,
787 	char **defaultdir,
788 	char **bufferpath,
789 	char **rpclogpath,
790 	char **fhpath,
791 	char **logpath,
792 	int  *logformat)
793 {
794 	char *tok;
795 	char *tmp;
796 
797 	/* tag */
798 	*tag = NULL;
799 	tok = strtok(linebuf, whitespace);
800 	if (tok == NULL)
801 		goto badtag;
802 	if (!is_legal_tag(tok))
803 		goto badtag;
804 	*tag = tok;
805 
806 	*defaultdir = *bufferpath = *rpclogpath = NULL;
807 	*fhpath = *logpath = NULL;
808 	*logformat = 0;
809 
810 	while ((tok = strtok(NULL, whitespace)) != NULL) {
811 		if (strncmp(tok, "defaultdir=", strlen("defaultdir=")) == 0) {
812 			*defaultdir = tok + strlen("defaultdir=");
813 		} else if (strncmp(tok, "buffer=", strlen("buffer=")) == 0) {
814 			*bufferpath = tok + strlen("buffer=");
815 		} else if (strncmp(tok, "rpclog=", strlen("rpclog=")) == 0) {
816 			*rpclogpath = tok + strlen("rpclog=");
817 		} else if (strncmp(tok, "fhtable=", strlen("fhtable=")) == 0) {
818 			*fhpath = tok + strlen("fhtable=");
819 		} else if (strncmp(tok, "log=", strlen("log=")) == 0) {
820 			*logpath = tok + strlen("log=");
821 		} else if (strncmp(tok, "logformat=",
822 		    strlen("logformat=")) == 0) {
823 			tmp = tok + strlen("logformat=");
824 			if (strncmp(tmp, "extended", strlen("extended")) == 0) {
825 				*logformat = TRANSLOG_EXTENDED;
826 			} else {
827 				/*
828 				 * Use transaction log basic format if
829 				 * 'extended' was not specified.
830 				 */
831 				*logformat = TRANSLOG_BASIC;
832 			}
833 		}
834 	}
835 
836 	if (strcmp(*tag, DEFAULTTAG) != 0) {
837 		/*
838 		 * Use global values for fields not specified if
839 		 * this tag is not the global tag.
840 		 */
841 		complete_with_global(defaultdir, bufferpath,
842 		    rpclogpath, fhpath, logpath, logformat);
843 	}
844 
845 	return (0);
846 
847 badtag:
848 	if (nfsl_errs_to_syslog) {
849 		syslog(LOG_ERR, gettext(
850 		    "Bad tag found in config file."));
851 	} else {
852 		(void) fprintf(stderr, gettext(
853 		    "Bad tag found in config file.\n"));
854 	}
855 	return (-1);
856 }
857 
858 /*
859  * Returns True if we have all the elements of a complete configuration
860  * entry. A complete configuration has tag, bufferpath, fhpath and logpath
861  * defined to non-zero strings.
862  */
863 static boolean_t
864 is_complete_config(
865 	char *tag,
866 	char *bufferpath,
867 	char *fhpath,
868 	char *logpath)
869 {
870 	assert(tag != NULL);
871 	assert(strlen(tag) > 0);
872 
873 	if ((bufferpath != NULL && strlen(bufferpath) > 0) &&
874 	    (fhpath != NULL && strlen(fhpath) > 0) &&
875 	    (logpath != NULL && strlen(logpath) > 0))
876 		return (B_TRUE);
877 	return (B_FALSE);
878 }
879 
880 #ifdef DEBUG
881 /*
882  * Prints the configuration entry to stdout.
883  */
884 void
885 nfsl_printconfig(nfsl_config_t *config)
886 {
887 	if (config->nc_name)
888 		(void) printf("tag=%s\t", config->nc_name);
889 	if (config->nc_defaultdir)
890 		(void) printf("defaultdir=%s\t", config->nc_defaultdir);
891 	if (config->nc_logpath)
892 		(void) printf("logpath=%s\t", config->nc_logpath);
893 	if (config->nc_fhpath)
894 		(void) printf("fhpath=%s\t", config->nc_fhpath);
895 	if (config->nc_bufferpath)
896 		(void) printf("bufpath=%s\t", config->nc_bufferpath);
897 	if (config->nc_rpclogpath)
898 		(void) printf("rpclogpath=%s\t", config->nc_rpclogpath);
899 	if (config->nc_logformat == TRANSLOG_BASIC)
900 		(void) printf("logformat=basic");
901 	else if (config->nc_logformat == TRANSLOG_EXTENDED)
902 		(void) printf("logformat=extended");
903 	else
904 		(void) printf("config->nc_logformat=UNKNOWN");
905 
906 	if (config->nc_flags & NC_UPDATED)
907 		(void) printf("\tflags=NC_UPDATED");
908 	(void) printf("\n");
909 }
910 
911 /*
912  * Prints the configuration list to stdout.
913  */
914 void
915 nfsl_printconfig_list(nfsl_config_t *listp)
916 {
917 	for (; listp != NULL; listp = listp->nc_next) {
918 		nfsl_printconfig(listp);
919 		(void) printf("\n");
920 	}
921 }
922 #endif /* DEBUG */
923 
924 /*
925  * Returns non-zero if the given string is allowable for a tag, zero if
926  * not.
927  */
928 static int
929 is_legal_tag(char *tag)
930 {
931 	int i;
932 	int len;
933 
934 	if (tag == NULL)
935 		return (0);
936 	len = strlen(tag);
937 	if (len == 0)
938 		return (0);
939 
940 	for (i = 0; i < len; i++) {
941 		char c;
942 
943 		c = tag[i];
944 		if (!(isalnum((unsigned char)c) || c == '_'))
945 			return (0);
946 	}
947 
948 	return (1);
949 }
950 
951 /*
952  * gataline attempts to get a line from the configuration file,
953  * upto LINESZ. A line in the file is a concatenation of lines if the
954  * continuation symbol '\' is used at the end of the line. Returns
955  * line on success, a NULL on EOF, and an empty string on lines > linesz.
956  */
957 static char *
958 gataline(FILE *fp, char *path, char *line, int linesz)
959 {
960 	char *p = line;
961 	int len;
962 	int excess = 0;
963 
964 	*p = '\0';
965 
966 	for (;;) {
967 		if (fgets(p, linesz - (p-line), fp) == NULL) {
968 			return (*line ? line : NULL);   /* EOF */
969 		}
970 
971 		len = strlen(line);
972 		if (len <= 0) {
973 			p = line;
974 			continue;
975 		}
976 		p = &line[len - 1];
977 
978 		/*
979 		 * Is input line too long?
980 		 */
981 		if (*p != '\n') {
982 			excess = 1;
983 			/*
984 			 * Perhaps last char read was '\'. Reinsert it
985 			 * into the stream to ease the parsing when we
986 			 * read the rest of the line to discard.
987 			 */
988 			(void) ungetc(*p, fp);
989 			break;
990 		}
991 trim:
992 
993 		/* trim trailing white space */
994 		while (p >= line && isspace(*(uchar_t *)p))
995 			*p-- = '\0';
996 		if (p < line) {			/* empty line */
997 			p = line;
998 			continue;
999 		}
1000 
1001 		if (*p == '\\') {		/* continuation */
1002 			*p = '\0';
1003 			continue;
1004 		}
1005 
1006 		/*
1007 		 * Ignore comments. Comments start with '#'
1008 		 * which must be preceded by a whitespace, unless
1009 		 * '#' is the first character in the line.
1010 		 */
1011 		p = line;
1012 
1013 		while ((p = strchr(p, '#')) != NULL) {
1014 			if (p == line || isspace(*(p-1))) {
1015 				*p-- = '\0';
1016 				goto trim;
1017 			}
1018 			p++;
1019 		}
1020 
1021 		break;
1022 	}
1023 	if (excess) {
1024 		int c;
1025 
1026 		/*
1027 		 * discard rest of line and return an empty string.
1028 		 * done to set the stream to the correct place when
1029 		 * we are done with this line.
1030 		 */
1031 		while ((c = getc(fp)) != EOF) {
1032 			*p = c;
1033 			if (*p == '\n')		/* end of the long line */
1034 				break;
1035 			else if (*p == '\\') {		/* continuation */
1036 				if (getc(fp) == EOF)	/* ignore next char */
1037 					break;
1038 			}
1039 		}
1040 		if (nfsl_errs_to_syslog) {
1041 			syslog(LOG_ERR, gettext(
1042 			    "%s: line too long - ignored (max %d chars)"),
1043 			    path, linesz-1);
1044 		} else {
1045 			(void) fprintf(stderr, gettext(
1046 			    "%s: line too long - ignored (max %d chars)\n"),
1047 			    path, linesz-1);
1048 		}
1049 		*line = '\0';
1050 	}
1051 
1052 	return (line);
1053 }
1054