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