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