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