xref: /netbsd-src/external/gpl2/xcvs/dist/src/parseinfo.c (revision 6a493d6bc668897c91594964a732d38505b70cbb)
1 /*
2  * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3  *
4  * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5  *                                  and others.
6  *
7  * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8  * Portions Copyright (C) 1989-1992, Brian Berliner
9  *
10  * You may distribute under the terms of the GNU General Public License as
11  * specified in the README file that comes with the CVS source distribution.
12  */
13 
14 #include "cvs.h"
15 #include "getline.h"
16 #include "history.h"
17 
18 /*
19  * Parse the INFOFILE file for the specified REPOSITORY.  Invoke CALLPROC for
20  * the first line in the file that matches the REPOSITORY, or if ALL != 0, any
21  * lines matching "ALL", or if no lines match, the last line matching
22  * "DEFAULT".
23  *
24  * Return 0 for success, -1 if there was not an INFOFILE, and >0 for failure.
25  */
26 int
27 Parse_Info (const char *infofile, const char *repository, CALLPROC callproc,
28             int opt, void *closure)
29 {
30     int err = 0;
31     FILE *fp_info;
32     char *infopath;
33     char *line = NULL;
34     size_t line_allocated = 0;
35     char *default_value = NULL;
36     int default_line = 0;
37     char *expanded_value;
38     bool callback_done;
39     int line_number;
40     char *cp, *exp, *value;
41     const char *srepos;
42     const char *regex_err;
43 
44     assert (repository);
45 
46     if (!current_parsed_root)
47     {
48 	/* XXX - should be error maybe? */
49 	error (0, 0, "CVSROOT variable not set");
50 	return 1;
51     }
52 
53     /* find the info file and open it */
54     infopath = Xasprintf ("%s/%s/%s", current_parsed_root->directory,
55 			  CVSROOTADM, infofile);
56     fp_info = CVS_FOPEN (infopath, "r");
57     if (!fp_info)
58     {
59 	/* If no file, don't do anything special.  */
60 	if (!existence_error (errno))
61 	    error (0, errno, "cannot open %s", infopath);
62 	free (infopath);
63 	return 0;
64     }
65 
66     /* strip off the CVSROOT if repository was absolute */
67     srepos = Short_Repository (repository);
68 
69     TRACE (TRACE_FUNCTION, "Parse_Info (%s, %s, %s)",
70 	   infopath, srepos,  (opt & PIOPT_ALL) ? "ALL" : "not ALL");
71 
72     /* search the info file for lines that match */
73     callback_done = false;
74     line_number = 0;
75     while (getline (&line, &line_allocated, fp_info) >= 0)
76     {
77 	line_number++;
78 
79 	/* skip lines starting with # */
80 	if (line[0] == '#')
81 	    continue;
82 
83 	/* skip whitespace at beginning of line */
84 	for (cp = line; *cp && isspace ((unsigned char) *cp); cp++)
85 	    ;
86 
87 	/* if *cp is null, the whole line was blank */
88 	if (*cp == '\0')
89 	    continue;
90 
91 	/* the regular expression is everything up to the first space */
92 	for (exp = cp; *cp && !isspace ((unsigned char) *cp); cp++)
93 	    ;
94 	if (*cp != '\0')
95 	    *cp++ = '\0';
96 
97 	/* skip whitespace up to the start of the matching value */
98 	while (*cp && isspace ((unsigned char) *cp))
99 	    cp++;
100 
101 	/* no value to match with the regular expression is an error */
102 	if (*cp == '\0')
103 	{
104 	    error (0, 0, "syntax error at line %d file %s; ignored",
105 		   line_number, infopath);
106 	    continue;
107 	}
108 	value = cp;
109 
110 	/* strip the newline off the end of the value */
111 	cp = strrchr (value, '\n');
112 	if (cp) *cp = '\0';
113 
114 	/*
115 	 * At this point, exp points to the regular expression, and value
116 	 * points to the value to call the callback routine with.  Evaluate
117 	 * the regular expression against srepos and callback with the value
118 	 * if it matches.
119 	 */
120 
121 	/* save the default value so we have it later if we need it */
122 	if (strcmp (exp, "DEFAULT") == 0)
123 	{
124 	    if (default_value)
125 	    {
126 		error (0, 0, "Multiple `DEFAULT' lines (%d and %d) in %s file",
127 		       default_line, line_number, infofile);
128 		free (default_value);
129 	    }
130 	    default_value = xstrdup (value);
131 	    default_line = line_number;
132 	    continue;
133 	}
134 
135 	/*
136 	 * For a regular expression of "ALL", do the callback always We may
137 	 * execute lots of ALL callbacks in addition to *one* regular matching
138 	 * callback or default
139 	 */
140 	if (strcmp (exp, "ALL") == 0)
141 	{
142 	    if (!(opt & PIOPT_ALL))
143 		error (0, 0, "Keyword `ALL' is ignored at line %d in %s file",
144 		       line_number, infofile);
145 	    else if ((expanded_value =
146 			expand_path (value, current_parsed_root->directory,
147 				     true, infofile, line_number)))
148 	    {
149 		err += callproc (repository, expanded_value, closure);
150 		free (expanded_value);
151 	    }
152 	    else
153 		err++;
154 	    continue;
155 	}
156 
157 	if (callback_done)
158 	    /* only first matching, plus "ALL"'s */
159 	    continue;
160 
161 	/* see if the repository matched this regular expression */
162 	regex_err = re_comp (exp);
163 	if (regex_err)
164 	{
165 	    error (0, 0, "bad regular expression at line %d file %s: %s",
166 		   line_number, infofile, regex_err);
167 	    continue;
168 	}
169 	if (re_exec (srepos) == 0)
170 	    continue;				/* no match */
171 
172 	/* it did, so do the callback and note that we did one */
173 	expanded_value = expand_path (value, current_parsed_root->directory,
174 				      true, infofile, line_number);
175 	if (expanded_value)
176 	{
177 	    err += callproc (repository, expanded_value, closure);
178 	    free (expanded_value);
179 	}
180 	else
181 	    err++;
182 	callback_done = true;
183     }
184     if (ferror (fp_info))
185 	error (0, errno, "cannot read %s", infopath);
186     if (fclose (fp_info) < 0)
187 	error (0, errno, "cannot close %s", infopath);
188 
189     /* if we fell through and didn't callback at all, do the default */
190     if (!callback_done && default_value)
191     {
192 	expanded_value = expand_path (default_value,
193 				      current_parsed_root->directory,
194 				      true, infofile, line_number);
195 	if (expanded_value)
196 	{
197 	    err += callproc (repository, expanded_value, closure);
198 	    free (expanded_value);
199 	}
200 	else
201 	    err++;
202     }
203 
204     /* free up space if necessary */
205     if (default_value) free (default_value);
206     free (infopath);
207     if (line) free (line);
208 
209     return err;
210 }
211 
212 
213 
214 /* Print a warning and return false if P doesn't look like a string specifying
215  * something that can be converted into a size_t.
216  *
217  * Sets *VAL to the parsed value when it is found to be valid.  *VAL will not
218  * be altered when false is returned.
219  */
220 static bool
221 readSizeT (const char *infopath, const char *option, const char *p,
222 	   size_t *val)
223 {
224     const char *q;
225     size_t num, factor = 1;
226 
227     if (!strcasecmp ("unlimited", p))
228     {
229 	*val = SIZE_MAX;
230 	return true;
231     }
232 
233     /* Record the factor character (kilo, mega, giga, tera).  */
234     if (!isdigit (p[strlen(p) - 1]))
235     {
236 	switch (p[strlen(p) - 1])
237 	{
238 	    case 'T':
239 		factor = xtimes (factor, 1024);
240 	    case 'G':
241 		factor = xtimes (factor, 1024);
242 	    case 'M':
243 		factor = xtimes (factor, 1024);
244 	    case 'k':
245 		factor = xtimes (factor, 1024);
246 		break;
247 	    default:
248 		error (0, 0,
249     "%s: Unknown %s factor: `%c'",
250 		       infopath, option, p[strlen(p)]);
251 		return false;
252 	}
253 	TRACE (TRACE_DATA, "readSizeT(): Found factor %zu for %s",
254 	       factor, option);
255     }
256 
257     /* Verify that *q is a number.  */
258     q = p;
259     while (q < p + strlen(p) - 1 /* Checked last character above.  */)
260     {
261 	if (!isdigit(*q))
262 	{
263 	    error (0, 0,
264 "%s: %s must be a postitive integer, not '%s'",
265 		   infopath, option, p);
266 	    return false;
267 	}
268 	q++;
269     }
270 
271     /* Compute final value.  */
272     num = strtoul (p, NULL, 10);
273     if (num == ULONG_MAX || num > SIZE_MAX)
274 	/* Don't return an error, just max out.  */
275 	num = SIZE_MAX;
276 
277     TRACE (TRACE_DATA, "readSizeT(): read number %zu for %s", num, option);
278     *val = xtimes (strtoul (p, NULL, 10), factor);
279     TRACE (TRACE_DATA, "readSizeT(): returnning %zu for %s", *val, option);
280     return true;
281 }
282 
283 
284 
285 /* Allocate and initialize a new config struct.  */
286 static inline struct config *
287 new_config (void)
288 {
289     struct config *new = xcalloc (1, sizeof (struct config));
290 
291     TRACE (TRACE_FLOW, "new_config ()");
292 
293     new->logHistory = xstrdup (ALL_HISTORY_REC_TYPES);
294     new->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
295     new->UserAdminOptions = xstrdup ("k");
296 #ifdef CVS_ADMIN_GROUP
297     new->UserAdminGroup = xstrdup (CVS_ADMIN_GROUP);
298 #else
299     new->UserAdminGroup = NULL;
300 #endif
301     new->MaxCommentLeaderLength = 20;
302 #ifdef SERVER_SUPPORT
303     new->MaxCompressionLevel = 9;
304 #endif /* SERVER_SUPPORT */
305 #ifdef PROXY_SUPPORT
306     new->MaxProxyBufferSize = (size_t)(8 * 1024 * 1024); /* 8 megabytes,
307                                                           * by default.
308                                                           */
309 #endif /* PROXY_SUPPORT */
310 #ifdef AUTH_SERVER_SUPPORT
311     new->system_auth = true;
312 #endif /* AUTH_SERVER_SUPPORT */
313 
314     return new;
315 }
316 
317 
318 
319 void
320 free_config (struct config *data)
321 {
322     if (data->keywords) free_keywords (data->keywords);
323     free (data);
324 }
325 
326 
327 
328 /* Return true if this function has already been called for line LN of file
329  * INFOPATH.
330  */
331 bool
332 parse_error (const char *infopath, unsigned int ln)
333 {
334     static List *errors = NULL;
335     char *nodename = NULL;
336 
337     if (!errors)
338 	errors = getlist();
339 
340     nodename = Xasprintf ("%s/%u", infopath, ln);
341     if (findnode (errors, nodename))
342     {
343 	free (nodename);
344 	return true;
345     }
346 
347     push_string (errors, nodename);
348     return false;
349 }
350 
351 
352 
353 #ifdef ALLOW_CONFIG_OVERRIDE
354 const char * const allowed_config_prefixes[] = { ALLOW_CONFIG_OVERRIDE };
355 #endif /* ALLOW_CONFIG_OVERRIDE */
356 
357 
358 
359 /* Parse the CVS config file.  The syntax right now is a bit ad hoc
360  * but tries to draw on the best or more common features of the other
361  * *info files and various unix (or non-unix) config file syntaxes.
362  * Lines starting with # are comments.  Settings are lines of the form
363  * KEYWORD=VALUE.  There is currently no way to have a multi-line
364  * VALUE (would be nice if there was, probably).
365  *
366  * CVSROOT is the $CVSROOT directory
367  * (current_parsed_root->directory might not be set yet, so this
368  * function takes the cvsroot as a function argument).
369  *
370  * RETURNS
371  *   Always returns a fully initialized config struct, which on error may
372  *   contain only the defaults.
373  *
374  * ERRORS
375  *   Calls error(0, ...) on errors in addition to the return value.
376  *
377  *   xmalloc() failures are fatal, per usual.
378  */
379 struct config *
380 parse_config (const char *cvsroot, const char *path)
381 {
382     const char *infopath;
383     char *freeinfopath = NULL;
384     FILE *fp_info;
385     char *line = NULL;
386     unsigned int ln;		/* Input file line counter.  */
387     char *buf = NULL;
388     size_t buf_allocated = 0;
389     size_t len;
390     char *p;
391     struct config *retval;
392     /* PROCESSING	Whether config keys are currently being processed for
393      *			this root.
394      * PROCESSED	Whether any keys have been processed for this root.
395      *			This is initialized to true so that any initial keys
396      *			may be processed as global defaults.
397      */
398     bool processing = true;
399     bool processed = true;
400 
401     TRACE (TRACE_FUNCTION, "parse_config (%s)", cvsroot);
402 
403 #ifdef ALLOW_CONFIG_OVERRIDE
404     if (path)
405     {
406 	const char * const *prefix;
407 	char *npath = xcanonicalize_file_name (path);
408 	bool approved = false;
409 	for (prefix = allowed_config_prefixes; *prefix != NULL; prefix++)
410 	{
411 	    char *nprefix;
412 
413 	    if (!isreadable (*prefix)) continue;
414 	    nprefix = xcanonicalize_file_name (*prefix);
415 	    if (!strncmp (nprefix, npath, strlen (nprefix))
416 		&& (((*prefix)[strlen (*prefix)] != '/'
417 		     && strlen (npath) == strlen (nprefix))
418 		    || ((*prefix)[strlen (*prefix)] == '/'
419 			&& npath[strlen (nprefix)] == '/')))
420 		approved = true;
421 	    free (nprefix);
422 	    if (approved) break;
423 	}
424 	if (!approved)
425 	    error (1, 0, "Invalid path to config file specified: `%s'",
426 		   path);
427 	infopath = path;
428 	free (npath);
429     }
430     else
431 #endif
432 	infopath = freeinfopath =
433 	    Xasprintf ("%s/%s/%s", cvsroot, CVSROOTADM, CVSROOTADM_CONFIG);
434 
435     retval = new_config ();
436 
437     fp_info = CVS_FOPEN (infopath, "r");
438     if (!fp_info)
439     {
440 	/* If no file, don't do anything special.  */
441 	if (!existence_error (errno))
442 	{
443 	    /* Just a warning message; doesn't affect return
444 	       value, currently at least.  */
445 	    error (0, errno, "cannot open %s", infopath);
446 	}
447 	if (freeinfopath) free (freeinfopath);
448 	return retval;
449     }
450 
451     ln = 0;  /* Have not read any lines yet.  */
452     while (getline (&buf, &buf_allocated, fp_info) >= 0)
453     {
454 	ln++; /* Keep track of input file line number for error messages.  */
455 
456 	line = buf;
457 
458 	/* Skip leading white space.  */
459 	while (isspace (*line)) line++;
460 
461 	/* Skip comments.  */
462 	if (line[0] == '#')
463 	    continue;
464 
465 	/* Is there any kind of written standard for the syntax of this
466 	   sort of config file?  Anywhere in POSIX for example (I guess
467 	   makefiles are sort of close)?  Red Hat Linux has a bunch of
468 	   these too (with some GUI tools which edit them)...
469 
470 	   Along the same lines, we might want a table of keywords,
471 	   with various types (boolean, string, &c), as a mechanism
472 	   for making sure the syntax is consistent.  Any good examples
473 	   to follow there (Apache?)?  */
474 
475 	/* Strip the trailing newline.  There will be one unless we
476 	   read a partial line without a newline, and then got end of
477 	   file (or error?).  */
478 
479 	len = strlen (line) - 1;
480 	if (line[len] == '\n')
481 	    line[len--] = '\0';
482 
483 	/* Skip blank lines.  */
484 	if (line[0] == '\0')
485 	    continue;
486 
487 	TRACE (TRACE_DATA, "parse_info() examining line: `%s'", line);
488 
489 	/* Check for a root specification.  */
490 	if (line[0] == '[' && line[len] == ']')
491 	{
492 	    cvsroot_t *tmproot;
493 
494 	    line++[len] = '\0';
495 	    tmproot = parse_cvsroot (line);
496 
497 	    /* Ignoring method.  */
498 	    if (!tmproot
499 #if defined CLIENT_SUPPORT || defined SERVER_SUPPORT
500 		|| (tmproot->method != local_method
501 		    && (!tmproot->hostname || !isThisHost (tmproot->hostname)))
502 #endif /* CLIENT_SUPPORT || SERVER_SUPPORT */
503 		|| !isSamePath (tmproot->directory, cvsroot))
504 	    {
505 		if (processed) processing = false;
506 	    }
507 	    else
508 	    {
509 		TRACE (TRACE_FLOW, "Matched root section`%s'", line);
510 		processing = true;
511 		processed = false;
512 	    }
513 
514 	    continue;
515 	}
516 
517 	/* There is data on this line.  */
518 
519 	/* Even if the data is bad or ignored, consider data processed for
520 	 * this root.
521 	 */
522 	processed = true;
523 
524 	if (!processing)
525 	    /* ...but it is for a different root.  */
526 	     continue;
527 
528 	/* The first '=' separates keyword from value.  */
529 	p = strchr (line, '=');
530 	if (!p)
531 	{
532 	    if (!parse_error (infopath, ln))
533 		error (0, 0,
534 "%s [%d]: syntax error: missing `=' between keyword and value",
535 		       infopath, ln);
536 	    continue;
537 	}
538 
539 	*p++ = '\0';
540 
541 	if (strcmp (line, "RCSBIN") == 0)
542 	{
543 	    /* This option used to specify the directory for RCS
544 	       executables.  But since we don't run them any more,
545 	       this is a noop.  Silently ignore it so that a
546 	       repository can work with either new or old CVS.  */
547 	    ;
548 	}
549 	else if (strcmp (line, "SystemAuth") == 0)
550 #ifdef AUTH_SERVER_SUPPORT
551 	    readBool (infopath, "SystemAuth", p, &retval->system_auth);
552 #else
553 	{
554 	    /* Still parse the syntax but ignore the option.  That way the same
555 	     * config file can be used for local and server.
556 	     */
557 	    bool dummy;
558 	    readBool (infopath, "SystemAuth", p, &dummy);
559 	}
560 #endif
561 	else if (strcmp (line, "LocalKeyword") == 0 ||
562 	    strcmp (line, "tag") == 0)
563 	    RCS_setlocalid (infopath, ln, &retval->keywords, p);
564 	else if (strcmp (line, "KeywordExpand") == 0)
565 	    RCS_setincexc (&retval->keywords, p);
566 	else if (strcmp (line, "PreservePermissions") == 0)
567 	{
568 #ifdef PRESERVE_PERMISSIONS_SUPPORT
569 	    readBool (infopath, "PreservePermissions", p,
570 		      &retval->preserve_perms);
571 #else
572 	    if (!parse_error (infopath, ln))
573 		error (0, 0, "\
574 %s [%u]: warning: this CVS does not support PreservePermissions",
575 		       infopath, ln);
576 #endif
577 	}
578 	else if (strcmp (line, "TopLevelAdmin") == 0)
579 	    readBool (infopath, "TopLevelAdmin", p, &retval->top_level_admin);
580 	else if (strcmp (line, "LockDir") == 0)
581 	{
582 	    if (retval->lock_dir)
583 		free (retval->lock_dir);
584 	    retval->lock_dir = expand_path (p, cvsroot, false, infopath, ln);
585 	    /* Could try some validity checking, like whether we can
586 	       opendir it or something, but I don't see any particular
587 	       reason to do that now rather than waiting until lock.c.  */
588 	}
589 	else if (strcmp (line, "HistoryLogPath") == 0)
590 	{
591 	    if (retval->HistoryLogPath) free (retval->HistoryLogPath);
592 
593 	    /* Expand ~ & $VARs.  */
594 	    retval->HistoryLogPath = expand_path (p, cvsroot, false,
595 						  infopath, ln);
596 
597 	    if (retval->HistoryLogPath && !ISABSOLUTE (retval->HistoryLogPath))
598 	    {
599 		error (0, 0, "%s [%u]: HistoryLogPath must be absolute.",
600 		       infopath, ln);
601 		free (retval->HistoryLogPath);
602 		retval->HistoryLogPath = NULL;
603 	    }
604 	}
605 	else if (strcmp (line, "HistorySearchPath") == 0)
606 	{
607 	    if (retval->HistorySearchPath) free (retval->HistorySearchPath);
608 	    retval->HistorySearchPath = expand_path (p, cvsroot, false,
609 						     infopath, ln);
610 
611 	    if (retval->HistorySearchPath
612 		&& !ISABSOLUTE (retval->HistorySearchPath))
613 	    {
614 		error (0, 0, "%s [%u]: HistorySearchPath must be absolute.",
615 		       infopath, ln);
616 		free (retval->HistorySearchPath);
617 		retval->HistorySearchPath = NULL;
618 	    }
619 	}
620 	else if (strcmp (line, "LogHistory") == 0)
621 	{
622 	    if (strcmp (p, "all") != 0)
623 	    {
624 		static bool gotone = false;
625 		if (gotone)
626 		    error (0, 0, "\
627 %s [%u]: warning: duplicate LogHistory entry found.",
628 			   infopath, ln);
629 		else
630 		    gotone = true;
631 		free (retval->logHistory);
632 		retval->logHistory = xstrdup (p);
633 	    }
634 	}
635 	else if (strcmp (line, "RereadLogAfterVerify") == 0)
636 	{
637 	    if (!strcasecmp (p, "never"))
638 	      retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER;
639 	    else if (!strcasecmp (p, "always"))
640 	      retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
641 	    else if (!strcasecmp (p, "stat"))
642 	      retval->RereadLogAfterVerify = LOGMSG_REREAD_STAT;
643 	    else
644 	    {
645 		bool tmp;
646 		if (readBool (infopath, "RereadLogAfterVerify", p, &tmp))
647 		{
648 		    if (tmp)
649 			retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
650 		    else
651 			retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER;
652 		}
653 	    }
654 	}
655 	else if (strcmp (line, "TmpDir") == 0)
656 	{
657 	    if (retval->TmpDir) free (retval->TmpDir);
658 	    retval->TmpDir = expand_path (p, cvsroot, false, infopath, ln);
659 	    /* Could try some validity checking, like whether we can
660 	     * opendir it or something, but I don't see any particular
661 	     * reason to do that now rather than when the first function
662 	     * tries to create a temp file.
663 	     */
664 	}
665 	else if (strcmp (line, "UserAdminGroup") == 0
666 	    || strcmp (line, "AdminGroup") == 0)
667 	    retval->UserAdminGroup = xstrdup (p);
668 	else if (strcmp (line, "UserAdminOptions") == 0
669 	    || strcmp (line, "AdminOptions") == 0)
670 	    retval->UserAdminOptions = xstrdup (p);
671 	else if (strcmp (line, "UseNewInfoFmtStrings") == 0)
672 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
673 	    readBool (infopath, "UseNewInfoFmtStrings", p,
674 		      &retval->UseNewInfoFmtStrings);
675 #else /* !SUPPORT_OLD_INFO_FMT_STRINGS */
676 	{
677 	    bool dummy;
678 	    if (readBool (infopath, "UseNewInfoFmtStrings", p, &dummy)
679 		&& !dummy)
680 		error (1, 0,
681 "%s [%u]: Old style info format strings not supported by this executable.",
682 		       infopath, ln);
683 	}
684 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
685 	else if (strcmp (line, "ImportNewFilesToVendorBranchOnly") == 0)
686 	    readBool (infopath, "ImportNewFilesToVendorBranchOnly", p,
687 		      &retval->ImportNewFilesToVendorBranchOnly);
688 	else if (strcmp (line, "PrimaryServer") == 0)
689 	    retval->PrimaryServer = parse_cvsroot (p);
690 #ifdef PROXY_SUPPORT
691 	else if (!strcmp (line, "MaxProxyBufferSize"))
692 	    readSizeT (infopath, "MaxProxyBufferSize", p,
693 		       &retval->MaxProxyBufferSize);
694 #endif /* PROXY_SUPPORT */
695 	else if (!strcmp (line, "MaxCommentLeaderLength"))
696 	    readSizeT (infopath, "MaxCommentLeaderLength", p,
697 		       &retval->MaxCommentLeaderLength);
698 	else if (!strcmp (line, "UseArchiveCommentLeader"))
699 	    readBool (infopath, "UseArchiveCommentLeader", p,
700 		      &retval->UseArchiveCommentLeader);
701 #ifdef SERVER_SUPPORT
702 	else if (!strcmp (line, "MinCompressionLevel"))
703 	    readSizeT (infopath, "MinCompressionLevel", p,
704 		       &retval->MinCompressionLevel);
705 	else if (!strcmp (line, "MaxCompressionLevel"))
706 	    readSizeT (infopath, "MaxCompressionLevel", p,
707 		       &retval->MaxCompressionLevel);
708 #endif /* SERVER_SUPPORT */
709 	else
710 	    /* We may be dealing with a keyword which was added in a
711 	       subsequent version of CVS.  In that case it is a good idea
712 	       to complain, as (1) the keyword might enable a behavior like
713 	       alternate locking behavior, in which it is dangerous and hard
714 	       to detect if some CVS's have it one way and others have it
715 	       the other way, (2) in general, having us not do what the user
716 	       had in mind when they put in the keyword violates the
717 	       principle of least surprise.  Note that one corollary is
718 	       adding new keywords to your CVSROOT/config file is not
719 	       particularly recommended unless you are planning on using
720 	       the new features.  */
721 	    if (!parse_error (infopath, ln))
722 		error (0, 0, "%s [%u]: unrecognized keyword `%s'",
723 		       infopath, ln, line);
724     }
725     if (ferror (fp_info))
726 	error (0, errno, "cannot read %s", infopath);
727     if (fclose (fp_info) < 0)
728 	error (0, errno, "cannot close %s", infopath);
729     if (freeinfopath) free (freeinfopath);
730     if (buf) free (buf);
731 
732     return retval;
733 }
734 
735 /* cvsacl patch */
736 int
737 parse_aclconfig (const char *cvsroot)
738 {
739     char *infopath;
740     FILE *fp_info;
741     char *line = NULL;
742     size_t line_allocated = 0;
743     size_t len;
744     char *p;
745     /* FIXME-reentrancy: If we do a multi-threaded server, this would need
746        to go to the per-connection data structures.  */
747     static int parsed = 0;
748 
749     /* Authentication code and serve_root might both want to call us.
750        Let this happen smoothly.  */
751     if (parsed)
752 	return 0;
753     parsed = 1;
754 
755     infopath = xmalloc (strlen (cvsroot)
756 			+ sizeof (CVSROOTADM_ACLCONFIG)
757 			+ sizeof (CVSROOTADM)
758 			+ 10);
759     if (infopath == NULL)
760     {
761 	error (0, 0, "out of memory; cannot allocate infopath");
762 	goto error_return;
763     }
764 
765     strcpy (infopath, cvsroot);
766     strcat (infopath, "/");
767     strcat (infopath, CVSROOTADM);
768     strcat (infopath, "/");
769     strcat (infopath, CVSROOTADM_ACLCONFIG);
770 
771     fp_info = CVS_FOPEN (infopath, "r");
772     if (fp_info == NULL)
773     {
774 	/* If no file, don't do anything special.  */
775 	if (!existence_error (errno))
776 	{
777 	    /* Just a warning message; doesn't affect return
778 	       value, currently at least.  */
779 	    error (0, errno, "cannot open %s", infopath);
780 	}
781 	free (infopath);
782 	return 0;
783     }
784 
785     while (getline (&line, &line_allocated, fp_info) >= 0)
786     {
787 	/* Skip comments.  */
788 	if (line[0] == '#')
789 	    continue;
790 
791 	len = strlen (line) - 1;
792 	if (line[len] == '\n')
793 	    line[len] = '\0';
794 
795 	/* Skip blank lines.  */
796 	if (line[0] == '\0')
797 	    continue;
798 
799 	/* The first '=' separates keyword from value.  */
800 	p = strchr (line, '=');
801 	if (p == NULL)
802 	{
803 	    /* Probably should be printing line number.  */
804 	    error (0, 0, "syntax error in %s: line '%s' is missing '='",
805 		   infopath, line);
806 	    goto error_return;
807 	}
808 
809 	*p++ = '\0';
810 
811 	if (strcmp (line, "UseCVSACL") == 0)
812 	{
813 	    if (strcmp (p, "no") == 0)
814 		use_cvs_acl = 0;
815 	    else if (strcmp (p, "yes") == 0)
816 		use_cvs_acl = 1;
817 	    else
818 	    {
819 		error (0, 0, "unrecognized value '%s' for UseCVSACL", p);
820 		goto error_return;
821 	    }
822 	}
823 	else if (strcmp (line, "UseSeperateACLFileForEachDir") == 0)
824 	{
825 	    if (strcmp (p, "no") == 0)
826 		use_separate_acl_file_for_each_dir = 0;
827 	    else if (strcmp (p, "yes") == 0)
828 		use_separate_acl_file_for_each_dir = 1;
829 	    else
830 	    {
831 		error (0, 0, "unrecognized value '%s' for UseSeperateACLFileForEachDir", p);
832 		goto error_return;
833 	    }
834 	}
835 	else if (strcmp (line, "StopAtFirstPermissionDenied") == 0)
836 	{
837 	    if (strcmp (p, "no") == 0)
838 		stop_at_first_permission_denied = 0;
839 	    else if (strcmp (p, "yes") == 0)
840 		stop_at_first_permission_denied = 1;
841 	    else
842 	    {
843 		error (0, 0, "unrecognized value '%s' for StopAtFirstPermissionDenied", p);
844 		goto error_return;
845 	    }
846 	}
847 	else if (strcmp (line, "CVSACLDefaultPermissions") == 0)
848 	{
849 	    if (cvs_acl_default_permissions != NULL)
850 		free (cvs_acl_default_permissions);
851 			if (!given_perms_valid (p))
852 				error (1,0,"Invalid CVS ACL Default Permissions: '%s' in CVSROOT/aclconfig", p);
853 		cvs_acl_default_permissions = xstrdup (p);
854 	}
855 	else if (strcmp (line, "UseCVSGroups") == 0)
856 	{
857 	    if (strcmp (p, "no") == 0)
858 		use_cvs_groups = 0;
859 	    else if (strcmp (p, "yes") == 0)
860 		use_cvs_groups = 1;
861 	    else
862 	    {
863 		error (0, 0, "unrecognized value '%s' for UseCVSGroups", p);
864 		goto error_return;
865 	    }
866 	}
867 	else if (strcmp (line, "UseSystemGroups") == 0)
868 	{
869 	    if (strcmp (p, "no") == 0)
870 		use_system_groups = 0;
871 	    else if (strcmp (p, "yes") == 0)
872 		use_system_groups = 1;
873 	    else
874 	    {
875 		error (0, 0, "unrecognized value '%s' for UseSystemGroups", p);
876 		goto error_return;
877 	    }
878 	}
879 	else if (strcmp (line, "CVSACLFileLocation") == 0)
880 	{
881 	    if (cvs_acl_file_location != NULL)
882 		free (cvs_acl_file_location);
883 		cvs_acl_file_location = xstrdup (p);
884 	}
885 	else if (strcmp (line, "CVSGroupsFileLocation") == 0)
886 	{
887 	    if (cvs_groups_file_location != NULL)
888 		free (cvs_groups_file_location);
889 		cvs_groups_file_location = xstrdup (p);
890 	}
891 	else if (strcmp (line, "CVSServerRunAsUser") == 0)
892 	{
893 	    if (cvs_server_run_as != NULL)
894 		free (cvs_server_run_as);
895 		cvs_server_run_as = xstrdup (p);
896 	}
897 
898     }
899 
900     if (ferror (fp_info))
901     {
902 	error (0, errno, "cannot read %s", infopath);
903 	goto error_return;
904     }
905     if (fclose (fp_info) < 0)
906     {
907 	error (0, errno, "cannot close %s", infopath);
908 	goto error_return;
909     }
910     free (infopath);
911     if (line != NULL)
912 	free (line);
913     return 0;
914 
915  error_return:
916     if (infopath != NULL)
917 	free (infopath);
918     if (line != NULL)
919 	free (line);
920     return -1;
921 }
922