parseinfo.c revision 109655
1/*
2 * Copyright (c) 1992, Brian Berliner and Jeff Polk
3 * Copyright (c) 1989-1992, Brian Berliner
4 *
5 * You may distribute under the terms of the GNU General Public License as
6 * specified in the README file that comes with the CVS source distribution.
7 */
8
9#include "cvs.h"
10#include "getline.h"
11#include <assert.h>
12
13extern char *logHistory;
14
15/*
16 * Parse the INFOFILE file for the specified REPOSITORY.  Invoke CALLPROC for
17 * the first line in the file that matches the REPOSITORY, or if ALL != 0, any lines
18 * matching "ALL", or if no lines match, the last line matching "DEFAULT".
19 *
20 * Return 0 for success, -1 if there was not an INFOFILE, and >0 for failure.
21 */
22int
23Parse_Info (infofile, repository, callproc, all)
24    char *infofile;
25    char *repository;
26    CALLPROC callproc;
27    int all;
28{
29    int err = 0;
30    FILE *fp_info;
31    char *infopath;
32    char *line = NULL;
33    size_t line_allocated = 0;
34    char *default_value = NULL;
35    char *expanded_value= NULL;
36    int callback_done, line_number;
37    char *cp, *exp, *value, *srepos, bad;
38    const char *regex_err;
39
40    if (current_parsed_root == NULL)
41    {
42	/* XXX - should be error maybe? */
43	error (0, 0, "CVSROOT variable not set");
44	return (1);
45    }
46
47    /* find the info file and open it */
48    infopath = xmalloc (strlen (current_parsed_root->directory)
49			+ strlen (infofile)
50			+ sizeof (CVSROOTADM)
51			+ 3);
52    (void) sprintf (infopath, "%s/%s/%s", current_parsed_root->directory,
53		    CVSROOTADM, infofile);
54    fp_info = CVS_FOPEN (infopath, "r");
55    if (fp_info == NULL)
56    {
57	/* If no file, don't do anything special.  */
58	if (!existence_error (errno))
59	    error (0, errno, "cannot open %s", infopath);
60	free (infopath);
61	return 0;
62    }
63
64    /* strip off the CVSROOT if repository was absolute */
65    srepos = Short_Repository (repository);
66
67    if (trace)
68	(void) fprintf (stderr, "%s-> Parse_Info (%s, %s, %s)\n",
69#ifdef SERVER_SUPPORT
70			server_active ? "S" : " ",
71#else
72			"",
73#endif
74			infopath, srepos, all ? "ALL" : "not ALL");
75
76    /* search the info file for lines that match */
77    callback_done = line_number = 0;
78    while (getline (&line, &line_allocated, fp_info) >= 0)
79    {
80	line_number++;
81
82	/* skip lines starting with # */
83	if (line[0] == '#')
84	    continue;
85
86	/* skip whitespace at beginning of line */
87	for (cp = line; *cp && isspace ((unsigned char) *cp); cp++)
88	    ;
89
90	/* if *cp is null, the whole line was blank */
91	if (*cp == '\0')
92	    continue;
93
94	/* the regular expression is everything up to the first space */
95	for (exp = cp; *cp && !isspace ((unsigned char) *cp); cp++)
96	    ;
97	if (*cp != '\0')
98	    *cp++ = '\0';
99
100	/* skip whitespace up to the start of the matching value */
101	while (*cp && isspace ((unsigned char) *cp))
102	    cp++;
103
104	/* no value to match with the regular expression is an error */
105	if (*cp == '\0')
106	{
107	    error (0, 0, "syntax error at line %d file %s; ignored",
108		   line_number, infofile);
109	    continue;
110	}
111	value = cp;
112
113	/* strip the newline off the end of the value */
114	if ((cp = strrchr (value, '\n')) != NULL)
115	    *cp = '\0';
116
117	if (expanded_value != NULL)
118	    free (expanded_value);
119	expanded_value = expand_path (value, infofile, line_number);
120
121	/*
122	 * At this point, exp points to the regular expression, and value
123	 * points to the value to call the callback routine with.  Evaluate
124	 * the regular expression against srepos and callback with the value
125	 * if it matches.
126	 */
127
128	/* save the default value so we have it later if we need it */
129	if (strcmp (exp, "DEFAULT") == 0)
130	{
131	    /* Is it OK to silently ignore all but the last DEFAULT
132               expression?  */
133	    if (default_value != NULL && default_value != &bad)
134		free (default_value);
135	    default_value = (expanded_value != NULL ?
136			     xstrdup (expanded_value) : &bad);
137	    continue;
138	}
139
140	/*
141	 * For a regular expression of "ALL", do the callback always We may
142	 * execute lots of ALL callbacks in addition to *one* regular matching
143	 * callback or default
144	 */
145	if (strcmp (exp, "ALL") == 0)
146	{
147	    if (!all)
148		error(0, 0, "Keyword `ALL' is ignored at line %d in %s file",
149		      line_number, infofile);
150	    else if (expanded_value != NULL)
151		err += callproc (repository, expanded_value);
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	if ((regex_err = re_comp (exp)) != NULL)
163	{
164	    error (0, 0, "bad regular expression at line %d file %s: %s",
165		   line_number, infofile, regex_err);
166	    continue;
167	}
168	if (re_exec (srepos) == 0)
169	    continue;				/* no match */
170
171	/* it did, so do the callback and note that we did one */
172	if (expanded_value != NULL)
173	    err += callproc (repository, expanded_value);
174	else
175	    err++;
176	callback_done = 1;
177    }
178    if (ferror (fp_info))
179	error (0, errno, "cannot read %s", infopath);
180    if (fclose (fp_info) < 0)
181	error (0, errno, "cannot close %s", infopath);
182
183    /* if we fell through and didn't callback at all, do the default */
184    if (callback_done == 0 && default_value != NULL)
185    {
186	if (default_value != &bad)
187	    err += callproc (repository, default_value);
188	else
189	    err++;
190    }
191
192    /* free up space if necessary */
193    if (default_value != NULL && default_value != &bad)
194	free (default_value);
195    if (expanded_value != NULL)
196	free (expanded_value);
197    free (infopath);
198    if (line != NULL)
199	free (line);
200
201    return (err);
202}
203
204
205/* Parse the CVS config file.  The syntax right now is a bit ad hoc
206   but tries to draw on the best or more common features of the other
207   *info files and various unix (or non-unix) config file syntaxes.
208   Lines starting with # are comments.  Settings are lines of the form
209   KEYWORD=VALUE.  There is currently no way to have a multi-line
210   VALUE (would be nice if there was, probably).
211
212   CVSROOT is the $CVSROOT directory (current_parsed_root->directory might not be
213   set yet).
214
215   Returns 0 for success, negative value for failure.  Call
216   error(0, ...) on errors in addition to the return value.  */
217int
218parse_config (cvsroot)
219    char *cvsroot;
220{
221    char *infopath;
222    FILE *fp_info;
223    char *line = NULL;
224    size_t line_allocated = 0;
225    size_t len;
226    char *p;
227    /* FIXME-reentrancy: If we do a multi-threaded server, this would need
228       to go to the per-connection data structures.  */
229    static int parsed = 0;
230
231    /* Authentication code and serve_root might both want to call us.
232       Let this happen smoothly.  */
233    if (parsed)
234	return 0;
235    parsed = 1;
236
237    infopath = xmalloc (strlen (cvsroot)
238			+ sizeof (CVSROOTADM_CONFIG)
239			+ sizeof (CVSROOTADM)
240			+ 10);
241    if (infopath == NULL)
242    {
243	error (0, 0, "out of memory; cannot allocate infopath");
244	goto error_return;
245    }
246
247    strcpy (infopath, cvsroot);
248    strcat (infopath, "/");
249    strcat (infopath, CVSROOTADM);
250    strcat (infopath, "/");
251    strcat (infopath, CVSROOTADM_CONFIG);
252
253    fp_info = CVS_FOPEN (infopath, "r");
254    if (fp_info == NULL)
255    {
256	/* If no file, don't do anything special.  */
257	if (!existence_error (errno))
258	{
259	    /* Just a warning message; doesn't affect return
260	       value, currently at least.  */
261	    error (0, errno, "cannot open %s", infopath);
262	}
263	free (infopath);
264	return 0;
265    }
266
267    while (getline (&line, &line_allocated, fp_info) >= 0)
268    {
269	/* Skip comments.  */
270	if (line[0] == '#')
271	    continue;
272
273	/* At least for the moment we don't skip whitespace at the start
274	   of the line.  Too picky?  Maybe.  But being insufficiently
275	   picky leads to all sorts of confusion, and it is a lot easier
276	   to start out picky and relax it than the other way around.
277
278	   Is there any kind of written standard for the syntax of this
279	   sort of config file?  Anywhere in POSIX for example (I guess
280	   makefiles are sort of close)?  Red Hat Linux has a bunch of
281	   these too (with some GUI tools which edit them)...
282
283	   Along the same lines, we might want a table of keywords,
284	   with various types (boolean, string, &c), as a mechanism
285	   for making sure the syntax is consistent.  Any good examples
286	   to follow there (Apache?)?  */
287
288	/* Strip the training newline.  There will be one unless we
289	   read a partial line without a newline, and then got end of
290	   file (or error?).  */
291
292	len = strlen (line) - 1;
293	if (line[len] == '\n')
294	    line[len] = '\0';
295
296	/* Skip blank lines.  */
297	if (line[0] == '\0')
298	    continue;
299
300	/* The first '=' separates keyword from value.  */
301	p = strchr (line, '=');
302	if (p == NULL)
303	{
304	    /* Probably should be printing line number.  */
305	    error (0, 0, "syntax error in %s: line '%s' is missing '='",
306		   infopath, line);
307	    goto error_return;
308	}
309
310	*p++ = '\0';
311
312	if (strcmp (line, "RCSBIN") == 0)
313	{
314	    /* This option used to specify the directory for RCS
315	       executables.  But since we don't run them any more,
316	       this is a noop.  Silently ignore it so that a
317	       repository can work with either new or old CVS.  */
318	    ;
319	}
320	else if (strcmp (line, "SystemAuth") == 0)
321	{
322	    if (strcmp (p, "no") == 0)
323#ifdef AUTH_SERVER_SUPPORT
324		system_auth = 0;
325#else
326		/* Still parse the syntax but ignore the
327		   option.  That way the same config file can
328		   be used for local and server.  */
329		;
330#endif
331	    else if (strcmp (p, "yes") == 0)
332#ifdef AUTH_SERVER_SUPPORT
333		system_auth = 1;
334#else
335		;
336#endif
337	    else
338	    {
339		error (0, 0, "unrecognized value '%s' for SystemAuth", p);
340		goto error_return;
341	    }
342	}
343	else if (strcmp (line, "PreservePermissions") == 0)
344	{
345	    if (strcmp (p, "no") == 0)
346		preserve_perms = 0;
347	    else if (strcmp (p, "yes") == 0)
348	    {
349#ifdef PRESERVE_PERMISSIONS_SUPPORT
350		preserve_perms = 1;
351#else
352		error (0, 0, "\
353warning: this CVS does not support PreservePermissions");
354#endif
355	    }
356	    else
357	    {
358		error (0, 0, "unrecognized value '%s' for PreservePermissions",
359		       p);
360		goto error_return;
361	    }
362	}
363	else if (strcmp (line, "TopLevelAdmin") == 0)
364	{
365	    if (strcmp (p, "no") == 0)
366		top_level_admin = 0;
367	    else if (strcmp (p, "yes") == 0)
368		top_level_admin = 1;
369	    else
370	    {
371		error (0, 0, "unrecognized value '%s' for TopLevelAdmin", p);
372		goto error_return;
373	    }
374	}
375	else if (strcmp (line, "LockDir") == 0)
376	{
377	    if (lock_dir != NULL)
378		free (lock_dir);
379	    lock_dir = xstrdup (p);
380	    /* Could try some validity checking, like whether we can
381	       opendir it or something, but I don't see any particular
382	       reason to do that now rather than waiting until lock.c.  */
383	}
384	else if (strcmp (line, "LogHistory") == 0)
385	{
386	    if (strcmp (p, "all") != 0)
387	    {
388		logHistory=xmalloc(strlen (p) + 1);
389		strcpy (logHistory, p);
390	    }
391	}
392	else if (strcmp (line, "RereadLogAfterVerify") == 0)
393	{
394	    if (strcmp (p, "no") == 0 || strcmp (p, "never") == 0)
395	      RereadLogAfterVerify = LOGMSG_REREAD_NEVER;
396	    else if (strcmp (p, "yes") == 0 || strcmp (p, "always") == 0)
397	      RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
398	    else if (strcmp (p, "stat") == 0)
399	      RereadLogAfterVerify = LOGMSG_REREAD_STAT;
400	}
401	else
402	{
403	    /* We may be dealing with a keyword which was added in a
404	       subsequent version of CVS.  In that case it is a good idea
405	       to complain, as (1) the keyword might enable a behavior like
406	       alternate locking behavior, in which it is dangerous and hard
407	       to detect if some CVS's have it one way and others have it
408	       the other way, (2) in general, having us not do what the user
409	       had in mind when they put in the keyword violates the
410	       principle of least surprise.  Note that one corollary is
411	       adding new keywords to your CVSROOT/config file is not
412	       particularly recommended unless you are planning on using
413	       the new features.  */
414	    error (0, 0, "%s: unrecognized keyword '%s'",
415		   infopath, line);
416	    goto error_return;
417	}
418    }
419    if (ferror (fp_info))
420    {
421	error (0, errno, "cannot read %s", infopath);
422	goto error_return;
423    }
424    if (fclose (fp_info) < 0)
425    {
426	error (0, errno, "cannot close %s", infopath);
427	goto error_return;
428    }
429    free (infopath);
430    if (line != NULL)
431	free (line);
432    return 0;
433
434 error_return:
435    if (infopath != NULL)
436	free (infopath);
437    if (line != NULL)
438	free (line);
439    return -1;
440}
441