xref: /dflybsd-src/contrib/cvs-1.12/src/parseinfo.c (revision 744c01d0dc2aa1481a40e5b0988d15691602f5c9)
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 %u 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 %u for %s", num, option);
278     *val = xtimes (strtoul (p, NULL, 10), factor);
279     TRACE (TRACE_DATA, "readSizeT(): returnning %u 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     new->MaxCommentLeaderLength = 20;
297 #ifdef SERVER_SUPPORT
298     new->MaxCompressionLevel = 9;
299 #endif /* SERVER_SUPPORT */
300 #ifdef PROXY_SUPPORT
301     new->MaxProxyBufferSize = (size_t)(8 * 1024 * 1024); /* 8 megabytes,
302                                                           * by default.
303                                                           */
304 #endif /* PROXY_SUPPORT */
305 #ifdef AUTH_SERVER_SUPPORT
306     new->system_auth = true;
307 #endif /* AUTH_SERVER_SUPPORT */
308 
309     return new;
310 }
311 
312 
313 
314 void
315 free_config (struct config *data)
316 {
317     if (data->keywords) free_keywords (data->keywords);
318     free (data);
319 }
320 
321 
322 
323 /* Return true if this function has already been called for line LN of file
324  * INFOPATH.
325  */
326 bool
327 parse_error (const char *infopath, unsigned int ln)
328 {
329     static List *errors = NULL;
330     char *nodename = NULL;
331 
332     if (!errors)
333 	errors = getlist();
334 
335     nodename = Xasprintf ("%s/%u", infopath, ln);
336     if (findnode (errors, nodename))
337     {
338 	free (nodename);
339 	return true;
340     }
341 
342     push_string (errors, nodename);
343     return false;
344 }
345 
346 
347 
348 #ifdef ALLOW_CONFIG_OVERRIDE
349 const char * const allowed_config_prefixes[] = { ALLOW_CONFIG_OVERRIDE };
350 #endif /* ALLOW_CONFIG_OVERRIDE */
351 
352 
353 
354 /* Parse the CVS config file.  The syntax right now is a bit ad hoc
355  * but tries to draw on the best or more common features of the other
356  * *info files and various unix (or non-unix) config file syntaxes.
357  * Lines starting with # are comments.  Settings are lines of the form
358  * KEYWORD=VALUE.  There is currently no way to have a multi-line
359  * VALUE (would be nice if there was, probably).
360  *
361  * CVSROOT is the $CVSROOT directory
362  * (current_parsed_root->directory might not be set yet, so this
363  * function takes the cvsroot as a function argument).
364  *
365  * RETURNS
366  *   Always returns a fully initialized config struct, which on error may
367  *   contain only the defaults.
368  *
369  * ERRORS
370  *   Calls error(0, ...) on errors in addition to the return value.
371  *
372  *   xmalloc() failures are fatal, per usual.
373  */
374 struct config *
375 parse_config (const char *cvsroot, const char *path)
376 {
377     const char *infopath;
378     char *freeinfopath = NULL;
379     FILE *fp_info;
380     char *line = NULL;
381     unsigned int ln;		/* Input file line counter.  */
382     char *buf = NULL;
383     size_t buf_allocated = 0;
384     size_t len;
385     char *p;
386     struct config *retval;
387     /* PROCESSING	Whether config keys are currently being processed for
388      *			this root.
389      * PROCESSED	Whether any keys have been processed for this root.
390      *			This is initialized to true so that any initial keys
391      *			may be processed as global defaults.
392      */
393     bool processing = true;
394     bool processed = true;
395 
396     TRACE (TRACE_FUNCTION, "parse_config (%s)", cvsroot);
397 
398 #ifdef ALLOW_CONFIG_OVERRIDE
399     if (path)
400     {
401 	const char * const *prefix;
402 	char *npath = xcanonicalize_file_name (path);
403 	bool approved = false;
404 	for (prefix = allowed_config_prefixes; *prefix != NULL; prefix++)
405 	{
406 	    char *nprefix;
407 
408 	    if (!isreadable (*prefix)) continue;
409 	    nprefix = xcanonicalize_file_name (*prefix);
410 	    if (!strncmp (nprefix, npath, strlen (nprefix))
411 		&& (((*prefix)[strlen (*prefix)] != '/'
412 		     && strlen (npath) == strlen (nprefix))
413 		    || ((*prefix)[strlen (*prefix)] == '/'
414 			&& npath[strlen (nprefix)] == '/')))
415 		approved = true;
416 	    free (nprefix);
417 	    if (approved) break;
418 	}
419 	if (!approved)
420 	    error (1, 0, "Invalid path to config file specified: `%s'",
421 		   path);
422 	infopath = path;
423 	free (npath);
424     }
425     else
426 #endif
427 	infopath = freeinfopath =
428 	    Xasprintf ("%s/%s/%s", cvsroot, CVSROOTADM, CVSROOTADM_CONFIG);
429 
430     retval = new_config ();
431 
432     fp_info = CVS_FOPEN (infopath, "r");
433     if (!fp_info)
434     {
435 	/* If no file, don't do anything special.  */
436 	if (!existence_error (errno))
437 	{
438 	    /* Just a warning message; doesn't affect return
439 	       value, currently at least.  */
440 	    error (0, errno, "cannot open %s", infopath);
441 	}
442 	if (freeinfopath) free (freeinfopath);
443 	return retval;
444     }
445 
446     ln = 0;  /* Have not read any lines yet.  */
447     while (getline (&buf, &buf_allocated, fp_info) >= 0)
448     {
449 	ln++; /* Keep track of input file line number for error messages.  */
450 
451 	line = buf;
452 
453 	/* Skip leading white space.  */
454 	while (isspace (*line)) line++;
455 
456 	/* Skip comments.  */
457 	if (line[0] == '#')
458 	    continue;
459 
460 	/* Is there any kind of written standard for the syntax of this
461 	   sort of config file?  Anywhere in POSIX for example (I guess
462 	   makefiles are sort of close)?  Red Hat Linux has a bunch of
463 	   these too (with some GUI tools which edit them)...
464 
465 	   Along the same lines, we might want a table of keywords,
466 	   with various types (boolean, string, &c), as a mechanism
467 	   for making sure the syntax is consistent.  Any good examples
468 	   to follow there (Apache?)?  */
469 
470 	/* Strip the trailing newline.  There will be one unless we
471 	   read a partial line without a newline, and then got end of
472 	   file (or error?).  */
473 
474 	len = strlen (line) - 1;
475 	if (line[len] == '\n')
476 	    line[len--] = '\0';
477 
478 	/* Skip blank lines.  */
479 	if (line[0] == '\0')
480 	    continue;
481 
482 	TRACE (TRACE_DATA, "parse_info() examining line: `%s'", line);
483 
484 	/* Check for a root specification.  */
485 	if (line[0] == '[' && line[len] == ']')
486 	{
487 	    cvsroot_t *tmproot;
488 
489 	    line++[len] = '\0';
490 	    tmproot = parse_cvsroot (line);
491 
492 	    /* Ignoring method.  */
493 	    if (!tmproot
494 #if defined CLIENT_SUPPORT || defined SERVER_SUPPORT
495 		|| (tmproot->method != local_method
496 		    && (!tmproot->hostname || !isThisHost (tmproot->hostname)))
497 #endif /* CLIENT_SUPPORT || SERVER_SUPPORT */
498 		|| !isSamePath (tmproot->directory, cvsroot))
499 	    {
500 		if (processed) processing = false;
501 	    }
502 	    else
503 	    {
504 		TRACE (TRACE_FLOW, "Matched root section`%s'", line);
505 		processing = true;
506 		processed = false;
507 	    }
508 
509 	    continue;
510 	}
511 
512 	/* There is data on this line.  */
513 
514 	/* Even if the data is bad or ignored, consider data processed for
515 	 * this root.
516 	 */
517 	processed = true;
518 
519 	if (!processing)
520 	    /* ...but it is for a different root.  */
521 	     continue;
522 
523 	/* The first '=' separates keyword from value.  */
524 	p = strchr (line, '=');
525 	if (!p)
526 	{
527 	    if (!parse_error (infopath, ln))
528 		error (0, 0,
529 "%s [%d]: syntax error: missing `=' between keyword and value",
530 		       infopath, ln);
531 	    continue;
532 	}
533 
534 	*p++ = '\0';
535 
536 	if (strcmp (line, "RCSBIN") == 0)
537 	{
538 	    /* This option used to specify the directory for RCS
539 	       executables.  But since we don't run them any more,
540 	       this is a noop.  Silently ignore it so that a
541 	       repository can work with either new or old CVS.  */
542 	    ;
543 	}
544 	else if (strcmp (line, "SystemAuth") == 0)
545 #ifdef AUTH_SERVER_SUPPORT
546 	    readBool (infopath, "SystemAuth", p, &retval->system_auth);
547 #else
548 	{
549 	    /* Still parse the syntax but ignore the option.  That way the same
550 	     * config file can be used for local and server.
551 	     */
552 	    bool dummy;
553 	    readBool (infopath, "SystemAuth", p, &dummy);
554 	}
555 #endif
556 	else if (strcmp (line, "LocalKeyword") == 0)
557 	    RCS_setlocalid (infopath, ln, &retval->keywords, p);
558 	else if (strcmp (line, "KeywordExpand") == 0)
559 	    RCS_setincexc (&retval->keywords, p);
560 	else if (strcmp (line, "PreservePermissions") == 0)
561 	{
562 #ifdef PRESERVE_PERMISSIONS_SUPPORT
563 	    readBool (infopath, "PreservePermissions", p,
564 		      &retval->preserve_perms);
565 #else
566 	    if (!parse_error (infopath, ln))
567 		error (0, 0, "\
568 %s [%u]: warning: this CVS does not support PreservePermissions",
569 		       infopath, ln);
570 #endif
571 	}
572 	else if (strcmp (line, "TopLevelAdmin") == 0)
573 	    readBool (infopath, "TopLevelAdmin", p, &retval->top_level_admin);
574 	else if (strcmp (line, "LockDir") == 0)
575 	{
576 	    if (retval->lock_dir)
577 		free (retval->lock_dir);
578 	    retval->lock_dir = expand_path (p, cvsroot, false, infopath, ln);
579 	    /* Could try some validity checking, like whether we can
580 	       opendir it or something, but I don't see any particular
581 	       reason to do that now rather than waiting until lock.c.  */
582 	}
583 	else if (strcmp (line, "HistoryLogPath") == 0)
584 	{
585 	    if (retval->HistoryLogPath) free (retval->HistoryLogPath);
586 
587 	    /* Expand ~ & $VARs.  */
588 	    retval->HistoryLogPath = expand_path (p, cvsroot, false,
589 						  infopath, ln);
590 
591 	    if (retval->HistoryLogPath && !ISABSOLUTE (retval->HistoryLogPath))
592 	    {
593 		error (0, 0, "%s [%u]: HistoryLogPath must be absolute.",
594 		       infopath, ln);
595 		free (retval->HistoryLogPath);
596 		retval->HistoryLogPath = NULL;
597 	    }
598 	}
599 	else if (strcmp (line, "HistorySearchPath") == 0)
600 	{
601 	    if (retval->HistorySearchPath) free (retval->HistorySearchPath);
602 	    retval->HistorySearchPath = expand_path (p, cvsroot, false,
603 						     infopath, ln);
604 
605 	    if (retval->HistorySearchPath
606 		&& !ISABSOLUTE (retval->HistorySearchPath))
607 	    {
608 		error (0, 0, "%s [%u]: HistorySearchPath must be absolute.",
609 		       infopath, ln);
610 		free (retval->HistorySearchPath);
611 		retval->HistorySearchPath = NULL;
612 	    }
613 	}
614 	else if (strcmp (line, "LogHistory") == 0)
615 	{
616 	    if (strcmp (p, "all") != 0)
617 	    {
618 		static bool gotone = false;
619 		if (gotone)
620 		    error (0, 0, "\
621 %s [%u]: warning: duplicate LogHistory entry found.",
622 			   infopath, ln);
623 		else
624 		    gotone = true;
625 		free (retval->logHistory);
626 		retval->logHistory = xstrdup (p);
627 	    }
628 	}
629 	else if (strcmp (line, "RereadLogAfterVerify") == 0)
630 	{
631 	    if (!strcasecmp (p, "never"))
632 	      retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER;
633 	    else if (!strcasecmp (p, "always"))
634 	      retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
635 	    else if (!strcasecmp (p, "stat"))
636 	      retval->RereadLogAfterVerify = LOGMSG_REREAD_STAT;
637 	    else
638 	    {
639 		bool tmp;
640 		if (readBool (infopath, "RereadLogAfterVerify", p, &tmp))
641 		{
642 		    if (tmp)
643 			retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
644 		    else
645 			retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER;
646 		}
647 	    }
648 	}
649 	else if (strcmp (line, "TmpDir") == 0)
650 	{
651 	    if (retval->TmpDir) free (retval->TmpDir);
652 	    retval->TmpDir = expand_path (p, cvsroot, false, infopath, ln);
653 	    /* Could try some validity checking, like whether we can
654 	     * opendir it or something, but I don't see any particular
655 	     * reason to do that now rather than when the first function
656 	     * tries to create a temp file.
657 	     */
658 	}
659 	else if (strcmp (line, "UserAdminOptions") == 0)
660 	    retval->UserAdminOptions = xstrdup (p);
661 	else if (strcmp (line, "UseNewInfoFmtStrings") == 0)
662 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
663 	    readBool (infopath, "UseNewInfoFmtStrings", p,
664 		      &retval->UseNewInfoFmtStrings);
665 #else /* !SUPPORT_OLD_INFO_FMT_STRINGS */
666 	{
667 	    bool dummy;
668 	    if (readBool (infopath, "UseNewInfoFmtStrings", p, &dummy)
669 		&& !dummy)
670 		error (1, 0,
671 "%s [%u]: Old style info format strings not supported by this executable.",
672 		       infopath, ln);
673 	}
674 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
675 	else if (strcmp (line, "ImportNewFilesToVendorBranchOnly") == 0)
676 	    readBool (infopath, "ImportNewFilesToVendorBranchOnly", p,
677 		      &retval->ImportNewFilesToVendorBranchOnly);
678 	else if (strcmp (line, "PrimaryServer") == 0)
679 	    retval->PrimaryServer = parse_cvsroot (p);
680 #ifdef PROXY_SUPPORT
681 	else if (!strcmp (line, "MaxProxyBufferSize"))
682 	    readSizeT (infopath, "MaxProxyBufferSize", p,
683 		       &retval->MaxProxyBufferSize);
684 #endif /* PROXY_SUPPORT */
685 	else if (!strcmp (line, "MaxCommentLeaderLength"))
686 	    readSizeT (infopath, "MaxCommentLeaderLength", p,
687 		       &retval->MaxCommentLeaderLength);
688 	else if (!strcmp (line, "UseArchiveCommentLeader"))
689 	    readBool (infopath, "UseArchiveCommentLeader", p,
690 		      &retval->UseArchiveCommentLeader);
691 #ifdef SERVER_SUPPORT
692 	else if (!strcmp (line, "MinCompressionLevel"))
693 	    readSizeT (infopath, "MinCompressionLevel", p,
694 		       &retval->MinCompressionLevel);
695 	else if (!strcmp (line, "MaxCompressionLevel"))
696 	    readSizeT (infopath, "MaxCompressionLevel", p,
697 		       &retval->MaxCompressionLevel);
698 #endif /* SERVER_SUPPORT */
699 	else
700 	    /* We may be dealing with a keyword which was added in a
701 	       subsequent version of CVS.  In that case it is a good idea
702 	       to complain, as (1) the keyword might enable a behavior like
703 	       alternate locking behavior, in which it is dangerous and hard
704 	       to detect if some CVS's have it one way and others have it
705 	       the other way, (2) in general, having us not do what the user
706 	       had in mind when they put in the keyword violates the
707 	       principle of least surprise.  Note that one corollary is
708 	       adding new keywords to your CVSROOT/config file is not
709 	       particularly recommended unless you are planning on using
710 	       the new features.  */
711 	    if (!parse_error (infopath, ln))
712 		error (0, 0, "%s [%u]: unrecognized keyword `%s'",
713 		       infopath, ln, line);
714     }
715     if (ferror (fp_info))
716 	error (0, errno, "cannot read %s", infopath);
717     if (fclose (fp_info) < 0)
718 	error (0, errno, "cannot close %s", infopath);
719     if (freeinfopath) free (freeinfopath);
720     if (buf) free (buf);
721 
722     return retval;
723 }
724