xref: /openbsd/gnu/usr.bin/cvs/src/parseinfo.c (revision f9bbbf45)
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 
13 extern 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  */
22 int
Parse_Info(infofile,repository,callproc,all)23 Parse_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.  */
212 int
parse_config(cvsroot)213 parse_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, "\
406 warning: 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