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