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, " -> ParseInfo(%s, %s, %s)\n",
69			infopath, srepos, all ? "ALL" : "not ALL");
70
71    /* search the info file for lines that match */
72    callback_done = line_number = 0;
73    while (get_line (&line, &line_allocated, fp_info) >= 0)
74    {
75	line_number++;
76
77	/* skip lines starting with # */
78	if (line[0] == '#')
79	    continue;
80
81	/* skip whitespace at beginning of line */
82	for (cp = line; *cp && isspace ((unsigned char) *cp); cp++)
83	    ;
84
85	/* if *cp is null, the whole line was blank */
86	if (*cp == '\0')
87	    continue;
88
89	/* the regular expression is everything up to the first space */
90	for (exp = cp; *cp && !isspace ((unsigned char) *cp); cp++)
91	    ;
92	if (*cp != '\0')
93	    *cp++ = '\0';
94
95	/* skip whitespace up to the start of the matching value */
96	while (*cp && isspace ((unsigned char) *cp))
97	    cp++;
98
99	/* no value to match with the regular expression is an error */
100	if (*cp == '\0')
101	{
102	    error (0, 0, "syntax error at line %d file %s; ignored",
103		   line_number, infofile);
104	    continue;
105	}
106	value = cp;
107
108	/* strip the newline off the end of the value */
109	if ((cp = strrchr (value, '\n')) != NULL)
110	    *cp = '\0';
111
112	if (expanded_value != NULL)
113	    free (expanded_value);
114	expanded_value = expand_path (value, infofile, line_number);
115
116	/*
117	 * At this point, exp points to the regular expression, and value
118	 * points to the value to call the callback routine with.  Evaluate
119	 * the regular expression against srepos and callback with the value
120	 * if it matches.
121	 */
122
123	/* save the default value so we have it later if we need it */
124	if (strcmp (exp, "DEFAULT") == 0)
125	{
126	    /* Is it OK to silently ignore all but the last DEFAULT
127               expression?  */
128	    if (default_value != NULL && default_value != &bad)
129		free (default_value);
130	    default_value = (expanded_value != NULL ?
131			     xstrdup (expanded_value) : &bad);
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 (!all)
143		error(0, 0, "Keyword `ALL' is ignored at line %d in %s file",
144		      line_number, infofile);
145	    else if (expanded_value != NULL)
146		err += callproc (repository, expanded_value);
147	    else
148		err++;
149	    continue;
150	}
151
152	if (callback_done)
153	    /* only first matching, plus "ALL"'s */
154	    continue;
155
156	/* see if the repository matched this regular expression */
157	if ((regex_err = re_comp (exp)) != NULL)
158	{
159	    error (0, 0, "bad regular expression at line %d file %s: %s",
160		   line_number, infofile, regex_err);
161	    continue;
162	}
163	if (re_exec (srepos) == 0)
164	    continue;				/* no match */
165
166	/* it did, so do the callback and note that we did one */
167	if (expanded_value != NULL)
168	    err += callproc (repository, expanded_value);
169	else
170	    err++;
171	callback_done = 1;
172    }
173    if (ferror (fp_info))
174	error (0, errno, "cannot read %s", infopath);
175    if (fclose (fp_info) < 0)
176	error (0, errno, "cannot close %s", infopath);
177
178    /* if we fell through and didn't callback at all, do the default */
179    if (callback_done == 0 && default_value != NULL)
180    {
181	if (default_value != &bad)
182	    err += callproc (repository, default_value);
183	else
184	    err++;
185    }
186
187    /* free up space if necessary */
188    if (default_value != NULL && default_value != &bad)
189	free (default_value);
190    if (expanded_value != NULL)
191	free (expanded_value);
192    free (infopath);
193    if (line != NULL)
194	free (line);
195
196    return (err);
197}
198
199
200/* Parse the CVS config file.  The syntax right now is a bit ad hoc
201   but tries to draw on the best or more common features of the other
202   *info files and various unix (or non-unix) config file syntaxes.
203   Lines starting with # are comments.  Settings are lines of the form
204   KEYWORD=VALUE.  There is currently no way to have a multi-line
205   VALUE (would be nice if there was, probably).
206
207   CVSROOT is the $CVSROOT directory (current_parsed_root->directory might not be
208   set yet).
209
210   Returns 0 for success, negative value for failure.  Call
211   error(0, ...) on errors in addition to the return value.  */
212int
213parse_config (cvsroot)
214    char *cvsroot;
215{
216    char *infopath;
217    FILE *fp_info;
218    char *line = NULL;
219    size_t line_allocated = 0;
220    size_t len;
221    char *p;
222    /* FIXME-reentrancy: If we do a multi-threaded server, this would need
223       to go to the per-connection data structures.  */
224    static int parsed = 0;
225
226    /* Authentication code and serve_root might both want to call us.
227       Let this happen smoothly.  */
228    if (parsed)
229	return 0;
230    parsed = 1;
231
232    infopath = malloc (strlen (cvsroot)
233			+ sizeof (CVSROOTADM_CONFIG)
234			+ sizeof (CVSROOTADM)
235			+ 10);
236    if (infopath == NULL)
237    {
238	error (0, 0, "out of memory; cannot allocate infopath");
239	goto error_return;
240    }
241
242    strcpy (infopath, cvsroot);
243    strcat (infopath, "/");
244    strcat (infopath, CVSROOTADM);
245    strcat (infopath, "/");
246    strcat (infopath, CVSROOTADM_CONFIG);
247
248    fp_info = CVS_FOPEN (infopath, "r");
249    if (fp_info == NULL)
250    {
251	/* If no file, don't do anything special.  */
252	if (!existence_error (errno))
253	{
254	    /* Just a warning message; doesn't affect return
255	       value, currently at least.  */
256	    error (0, errno, "cannot open %s", infopath);
257	}
258	free (infopath);
259	return 0;
260    }
261
262    while (get_line (&line, &line_allocated, fp_info) >= 0)
263    {
264	/* Skip comments.  */
265	if (line[0] == '#')
266	    continue;
267
268	/* At least for the moment we don't skip whitespace at the start
269	   of the line.  Too picky?  Maybe.  But being insufficiently
270	   picky leads to all sorts of confusion, and it is a lot easier
271	   to start out picky and relax it than the other way around.
272
273	   Is there any kind of written standard for the syntax of this
274	   sort of config file?  Anywhere in POSIX for example (I guess
275	   makefiles are sort of close)?  Red Hat Linux has a bunch of
276	   these too (with some GUI tools which edit them)...
277
278	   Along the same lines, we might want a table of keywords,
279	   with various types (boolean, string, &c), as a mechanism
280	   for making sure the syntax is consistent.  Any good examples
281	   to follow there (Apache?)?  */
282
283	/* Strip the training newline.  There will be one unless we
284	   read a partial line without a newline, and then got end of
285	   file (or error?).  */
286
287	len = strlen (line) - 1;
288	if (line[len] == '\n')
289	    line[len] = '\0';
290
291	/* Skip blank lines.  */
292	if (line[0] == '\0')
293	    continue;
294
295	/* The first '=' separates keyword from value.  */
296	p = strchr (line, '=');
297	if (p == NULL)
298	{
299	    /* Probably should be printing line number.  */
300	    error (0, 0, "syntax error in %s: line '%s' is missing '='",
301		   infopath, line);
302	    goto error_return;
303	}
304
305	*p++ = '\0';
306
307	if (strcmp (line, "RCSBIN") == 0)
308	{
309	    /* This option used to specify the directory for RCS
310	       executables.  But since we don't run them any more,
311	       this is a noop.  Silently ignore it so that a
312	       repository can work with either new or old CVS.  */
313	    ;
314	}
315	else if (strcmp (line, "SystemAuth") == 0)
316	{
317	    if (strcmp (p, "no") == 0)
318#ifdef AUTH_SERVER_SUPPORT
319		system_auth = 0;
320#else
321		/* Still parse the syntax but ignore the
322		   option.  That way the same config file can
323		   be used for local and server.  */
324		;
325#endif
326	    else if (strcmp (p, "yes") == 0)
327#ifdef AUTH_SERVER_SUPPORT
328		system_auth = 1;
329#else
330		;
331#endif
332	    else
333	    {
334		error (0, 0, "unrecognized value '%s' for SystemAuth", p);
335		goto error_return;
336	    }
337	}
338	else if (strcmp (line, "tag") == 0) {
339	    RCS_citag = strdup(p);
340	    if (RCS_citag == NULL) {
341		error (0, 0, "%s: no memory for local tag '%s'",
342		       infopath, p);
343		goto error_return;
344	    }
345	}
346	else if (strcmp (line, "umask") == 0) {
347	    cvsumask = (mode_t)(strtol(p, NULL, 8) & 0777);
348	}
349	else if (strcmp (line, "DisableMdocdate") == 0) {
350	    if (strcmp (p, "no") == 0)
351		disable_mdocdate = 0;
352	    else if (strcmp (p, "yes") == 0)
353		disable_mdocdate = 1;
354	    else
355	    {
356		error (0, 0, "unrecognized value '%s' for DisableMdocdate", p);
357		goto error_return;
358	    }
359	}
360	else if (strcmp (line, "dlimit") == 0) {
361#ifdef BSD
362#include <sys/resource.h>
363	    struct rlimit rl;
364
365	    if (getrlimit(RLIMIT_DATA, &rl) != -1) {
366		rl.rlim_cur = atoi(p);
367		rl.rlim_cur *= 1024;
368
369		(void) setrlimit(RLIMIT_DATA, &rl);
370	    }
371#endif /* BSD */
372	}
373	else if (strcmp (line, "DisableXProg") == 0)
374	{
375	    if (strcmp (p, "no") == 0)
376#ifdef AUTH_SERVER_SUPPORT
377		disable_x_prog = 0;
378#else
379		/* Still parse the syntax but ignore the
380		   option.  That way the same config file can
381		   be used for local and server.  */
382		;
383#endif
384	    else if (strcmp (p, "yes") == 0)
385#ifdef AUTH_SERVER_SUPPORT
386		disable_x_prog = 1;
387#else
388		;
389#endif
390	    else
391	    {
392		error (0, 0, "unrecognized value '%s' for DisableXProg", p);
393		goto error_return;
394	    }
395	}
396	else if (strcmp (line, "PreservePermissions") == 0)
397	{
398	    if (strcmp (p, "no") == 0)
399		preserve_perms = 0;
400	    else if (strcmp (p, "yes") == 0)
401	    {
402#ifdef PRESERVE_PERMISSIONS_SUPPORT
403		preserve_perms = 1;
404#else
405		error (0, 0, "\
406warning: this CVS does not support PreservePermissions");
407#endif
408	    }
409	    else
410	    {
411		error (0, 0, "unrecognized value '%s' for PreservePermissions",
412		       p);
413		goto error_return;
414	    }
415	}
416	else if (strcmp (line, "TopLevelAdmin") == 0)
417	{
418	    if (strcmp (p, "no") == 0)
419		top_level_admin = 0;
420	    else if (strcmp (p, "yes") == 0)
421		top_level_admin = 1;
422	    else
423	    {
424		error (0, 0, "unrecognized value '%s' for TopLevelAdmin", p);
425		goto error_return;
426	    }
427	}
428	else if (strcmp (line, "LockDir") == 0)
429	{
430	    if (lock_dir != NULL)
431		free (lock_dir);
432	    lock_dir = xstrdup (p);
433	    /* Could try some validity checking, like whether we can
434	       opendir it or something, but I don't see any particular
435	       reason to do that now rather than waiting until lock.c.  */
436	}
437	else if (strcmp (line, "LogHistory") == 0)
438	{
439	    if (strcmp (p, "all") != 0)
440	    {
441		logHistory=malloc(strlen (p) + 1);
442		strcpy (logHistory, p);
443	    }
444	}
445	else
446	{
447	    /* We may be dealing with a keyword which was added in a
448	       subsequent version of CVS.  In that case it is a good idea
449	       to complain, as (1) the keyword might enable a behavior like
450	       alternate locking behavior, in which it is dangerous and hard
451	       to detect if some CVS's have it one way and others have it
452	       the other way, (2) in general, having us not do what the user
453	       had in mind when they put in the keyword violates the
454	       principle of least surprise.  Note that one corollary is
455	       adding new keywords to your CVSROOT/config file is not
456	       particularly recommended unless you are planning on using
457	       the new features.  */
458	    error (0, 0, "%s: unrecognized keyword '%s'",
459		   infopath, line);
460	    goto error_return;
461	}
462    }
463    if (ferror (fp_info))
464    {
465	error (0, errno, "cannot read %s", infopath);
466	goto error_return;
467    }
468    if (fclose (fp_info) < 0)
469    {
470	error (0, errno, "cannot close %s", infopath);
471	goto error_return;
472    }
473    free (infopath);
474    if (line != NULL)
475	free (line);
476    return 0;
477
478 error_return:
479    if (infopath != NULL)
480	free (infopath);
481    if (line != NULL)
482	free (line);
483    return -1;
484}
485