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