Fix reference.
[dragonfly.git] / contrib / cvs-1.12.12 / 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
14 #include "cvs.h"
15 #include "getline.h"
16 #include "history.h"
17
18
19
20 /*
21  * Parse the INFOFILE file for the specified REPOSITORY.  Invoke CALLPROC for
22  * the first line in the file that matches the REPOSITORY, or if ALL != 0, any
23  * lines matching "ALL", or if no lines match, the last line matching
24  * "DEFAULT".
25  *
26  * Return 0 for success, -1 if there was not an INFOFILE, and >0 for failure.
27  */
28 int
29 Parse_Info (const char *infofile, const char *repository, CALLPROC callproc,
30             int opt, void *closure)
31 {
32     int err = 0;
33     FILE *fp_info;
34     char *infopath;
35     char *line = NULL;
36     size_t line_allocated = 0;
37     char *default_value = NULL;
38     int default_line = 0;
39     char *expanded_value;
40     int callback_done, line_number;
41     char *cp, *exp, *value;
42     const char *srepos;
43     const char *regex_err;
44
45     assert (repository);
46
47     if (current_parsed_root == NULL)
48     {
49         /* XXX - should be error maybe? */
50         error (0, 0, "CVSROOT variable not set");
51         return 1;
52     }
53
54     /* find the info file and open it */
55     infopath = Xasprintf ("%s/%s/%s", current_parsed_root->directory,
56                           CVSROOTADM, infofile);
57     fp_info = CVS_FOPEN (infopath, "r");
58     if (fp_info == NULL)
59     {
60         /* If no file, don't do anything special.  */
61         if (!existence_error (errno))
62             error (0, errno, "cannot open %s", infopath);
63         free (infopath);
64         return 0;
65     }
66
67     /* strip off the CVSROOT if repository was absolute */
68     srepos = Short_Repository (repository);
69
70     TRACE (TRACE_FUNCTION, "Parse_Info (%s, %s, %s)",
71            infopath, srepos,  (opt & PIOPT_ALL) ? "ALL" : "not ALL");
72
73     /* search the info file for lines that match */
74     callback_done = line_number = 0;
75     while (getline (&line, &line_allocated, fp_info) >= 0)
76     {
77         line_number++;
78
79         /* skip lines starting with # */
80         if (line[0] == '#')
81             continue;
82
83         /* skip whitespace at beginning of line */
84         for (cp = line; *cp && isspace ((unsigned char) *cp); cp++)
85             ;
86
87         /* if *cp is null, the whole line was blank */
88         if (*cp == '\0')
89             continue;
90
91         /* the regular expression is everything up to the first space */
92         for (exp = cp; *cp && !isspace ((unsigned char) *cp); cp++)
93             ;
94         if (*cp != '\0')
95             *cp++ = '\0';
96
97         /* skip whitespace up to the start of the matching value */
98         while (*cp && isspace ((unsigned char) *cp))
99             cp++;
100
101         /* no value to match with the regular expression is an error */
102         if (*cp == '\0')
103         {
104             error (0, 0, "syntax error at line %d file %s; ignored",
105                    line_number, infopath);
106             continue;
107         }
108         value = cp;
109
110         /* strip the newline off the end of the value */
111         if ((cp = strrchr (value, '\n')) != NULL)
112             *cp = '\0';
113
114         /*
115          * At this point, exp points to the regular expression, and value
116          * points to the value to call the callback routine with.  Evaluate
117          * the regular expression against srepos and callback with the value
118          * if it matches.
119          */
120
121         /* save the default value so we have it later if we need it */
122         if (strcmp (exp, "DEFAULT") == 0)
123         {
124             if (default_value != NULL)
125             {
126                 error (0, 0, "Multiple `DEFAULT' lines (%d and %d) in %s file",
127                        default_line, line_number, infofile);
128                 free (default_value);
129             }
130             default_value = xstrdup (value);
131             default_line = line_number;
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 (!(opt & PIOPT_ALL))
143                 error (0, 0, "Keyword `ALL' is ignored at line %d in %s file",
144                        line_number, infofile);
145             else if ((expanded_value = expand_path (value, infofile,
146                                                     line_number, 1))
147                      != NULL )
148             {
149                 err += callproc (repository, expanded_value, closure);
150                 free (expanded_value);
151             }
152             else
153                 err++;
154             continue;
155         }
156
157         if (callback_done)
158             /* only first matching, plus "ALL"'s */
159             continue;
160
161         /* see if the repository matched this regular expression */
162         if ((regex_err = re_comp (exp)) != NULL)
163         {
164             error (0, 0, "bad regular expression at line %d file %s: %s",
165                    line_number, infofile, regex_err);
166             continue;
167         }
168         if (re_exec (srepos) == 0)
169             continue;                           /* no match */
170
171         /* it did, so do the callback and note that we did one */
172         if ((expanded_value = expand_path( value, infofile, line_number, 1)
173             ) != NULL)
174         {
175             err += callproc (repository, expanded_value, closure);
176             free (expanded_value);
177         }
178         else
179             err++;
180         callback_done = 1;
181     }
182     if (ferror (fp_info))
183         error (0, errno, "cannot read %s", infopath);
184     if (fclose (fp_info) < 0)
185         error (0, errno, "cannot close %s", infopath);
186
187     /* if we fell through and didn't callback at all, do the default */
188     if (callback_done == 0 && default_value != NULL)
189     {
190         if ((expanded_value = expand_path (default_value, infofile,
191                                            line_number, 1)
192             ) != NULL)
193         {
194             err += callproc (repository, expanded_value, closure);
195             free (expanded_value);
196         }
197         else
198             err++;
199     }
200
201     /* free up space if necessary */
202     if (default_value != NULL)
203         free (default_value);
204     free (infopath);
205     if (line != NULL)
206         free (line);
207
208     return err;
209 }
210
211
212
213 /* Print a warning and return false if P doesn't look like a string specifying
214  * something that can be converted into a size_t.
215  *
216  * Sets *VAL to the parsed value when it is found to be valid.  *VAL will not
217  * be altered when false is returned.
218  */
219 static bool
220 readSizeT (const char *infopath, const char *option, const char *p,
221            size_t *val)
222 {
223     const char *q;
224     size_t num, factor = 1;
225
226     if (!strcasecmp ("unlimited", p))
227     {
228         *val = SIZE_MAX;
229         return true;
230     }
231
232     /* Record the factor character (kilo, mega, giga, tera).  */
233     if (!isdigit (p[strlen(p) - 1]))
234     {
235         switch (p[strlen(p) - 1])
236         {
237             case 'T':
238                 factor = xtimes (factor, 1024);
239             case 'G':
240                 factor = xtimes (factor, 1024);
241             case 'M':
242                 factor = xtimes (factor, 1024);
243             case 'k':
244                 factor = xtimes (factor, 1024);
245                 break;
246             default:
247                 error (0, 0,
248     "%s: Unknown %s factor: `%c'",
249                        infopath, option, p[strlen(p)]);
250                 return false;
251         }
252         TRACE (TRACE_DATA, "readSizeT(): Found factor %u for %s",
253                factor, option);
254     }
255
256     /* Verify that *q is a number.  */
257     q = p;
258     while (q < p + strlen(p) - 1 /* Checked last character above.  */)
259     {
260         if (!isdigit(*q))
261         {
262             error (0, 0,
263 "%s: %s must be a postitive integer, not '%s'",
264                    infopath, option, p);
265             return false;
266         }
267         q++;
268     }
269
270     /* Compute final value.  */
271     num = strtoul (p, NULL, 10);
272     if (num == ULONG_MAX || num > SIZE_MAX)
273         /* Don't return an error, just max out.  */
274         num = SIZE_MAX;
275
276     TRACE (TRACE_DATA, "readSizeT(): read number %u for %s", num, option);
277     *val = xtimes (strtoul (p, NULL, 10), factor);
278     TRACE (TRACE_DATA, "readSizeT(): returnning %u for %s", *val, option);
279     return true;
280 }
281
282
283
284 /* Allocate and initialize a new config struct.  */
285 static inline struct config *
286 new_config (void)
287 {
288     struct config *new = xcalloc (1, sizeof (struct config));
289
290     TRACE (TRACE_FLOW, "new_config ()");
291
292     new->logHistory = xstrdup (ALL_HISTORY_REC_TYPES);
293     new->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
294     new->UserAdminOptions = xstrdup ("k");
295     new->MaxCommentLeaderLength = 20;
296 #ifdef PROXY_SUPPORT
297     new->MaxProxyBufferSize = (size_t)(8 * 1024 * 1024); /* 8 megabytes,
298                                                           * by default.
299                                                           */
300 #endif /* PROXY_SUPPORT */
301 #ifdef AUTH_SERVER_SUPPORT
302     new->system_auth = true;
303 #endif /* AUTH_SERVER_SUPPORT */
304
305     return new;
306 }
307
308
309
310 void
311 free_config (struct config *data)
312 {
313     if (data->keywords) free_keywords (data->keywords);
314     free (data);
315 }
316
317
318
319 /* Return true if this function has already been called for line LN of file
320  * INFOPATH.
321  */
322 bool
323 parse_error (const char *infopath, unsigned int ln)
324 {
325     static List *errors = NULL;
326     char *nodename = NULL;
327
328     if (!errors)
329         errors = getlist();
330
331     nodename = Xasprintf ("%s/%u", infopath, ln);
332     if (findnode (errors, nodename))
333     {
334         free (nodename);
335         return true;
336     }
337
338     push_string (errors, nodename);
339     return false;
340 }
341
342
343
344 /* Parse the CVS config file.  The syntax right now is a bit ad hoc
345  * but tries to draw on the best or more common features of the other
346  * *info files and various unix (or non-unix) config file syntaxes.
347  * Lines starting with # are comments.  Settings are lines of the form
348  * KEYWORD=VALUE.  There is currently no way to have a multi-line
349  * VALUE (would be nice if there was, probably).
350  *
351  * CVSROOT is the $CVSROOT directory
352  * (current_parsed_root->directory might not be set yet, so this
353  * function takes the cvsroot as a function argument).
354  *
355  * RETURNS
356  *   Always returns a fully initialized config struct, which on error may
357  *   contain only the defaults.
358  *
359  * ERRORS
360  *   Calls error(0, ...) on errors in addition to the return value.
361  *
362  *   xmalloc() failures are fatal, per usual.
363  */
364 struct config *
365 parse_config (const char *cvsroot)
366 {
367     char *infopath;
368     FILE *fp_info;
369     char *line = NULL;
370     unsigned int ln;            /* Input file line counter.  */
371     size_t line_allocated = 0;
372     size_t len;
373     char *p;
374     struct config *retval;
375
376     TRACE (TRACE_FUNCTION, "parse_config (%s)", cvsroot);
377
378     retval = new_config ();
379
380     infopath = Xasprintf ("%s/%s/%s", cvsroot, CVSROOTADM, CVSROOTADM_CONFIG);
381
382     fp_info = CVS_FOPEN (infopath, "r");
383     if (fp_info == NULL)
384     {
385         /* If no file, don't do anything special.  */
386         if (!existence_error (errno))
387         {
388             /* Just a warning message; doesn't affect return
389                value, currently at least.  */
390             error (0, errno, "cannot open %s", infopath);
391         }
392         free (infopath);
393         return retval;
394     }
395
396     ln = 0;  /* Have not read any lines yet.  */
397     while (getline (&line, &line_allocated, fp_info) >= 0)
398     {
399         ln++; /* Keep track of input file line number for error messages.  */
400
401         /* Skip comments.  */
402         if (line[0] == '#')
403             continue;
404
405         /* At least for the moment we don't skip whitespace at the start
406            of the line.  Too picky?  Maybe.  But being insufficiently
407            picky leads to all sorts of confusion, and it is a lot easier
408            to start out picky and relax it than the other way around.
409
410            Is there any kind of written standard for the syntax of this
411            sort of config file?  Anywhere in POSIX for example (I guess
412            makefiles are sort of close)?  Red Hat Linux has a bunch of
413            these too (with some GUI tools which edit them)...
414
415            Along the same lines, we might want a table of keywords,
416            with various types (boolean, string, &c), as a mechanism
417            for making sure the syntax is consistent.  Any good examples
418            to follow there (Apache?)?  */
419
420         /* Strip the trailing newline.  There will be one unless we
421            read a partial line without a newline, and then got end of
422            file (or error?).  */
423
424         len = strlen (line) - 1;
425         if (line[len] == '\n')
426             line[len] = '\0';
427
428         /* Skip blank lines.  */
429         if (line[0] == '\0')
430             continue;
431
432         TRACE (TRACE_DATA, "parse_info() examining line: `%s'", line);
433
434         /* The first '=' separates keyword from value.  */
435         p = strchr (line, '=');
436         if (p == NULL)
437         {
438             if (!parse_error (infopath, ln))
439                 error (0, 0,
440 "%s [%d]: syntax error: missing `=' between keyword and value",
441                        infopath, ln);
442             continue;
443         }
444
445         *p++ = '\0';
446
447         if (strcmp (line, "RCSBIN") == 0)
448         {
449             /* This option used to specify the directory for RCS
450                executables.  But since we don't run them any more,
451                this is a noop.  Silently ignore it so that a
452                repository can work with either new or old CVS.  */
453             ;
454         }
455         else if (strcmp (line, "SystemAuth") == 0)
456 #ifdef AUTH_SERVER_SUPPORT
457             readBool (infopath, "SystemAuth", p, &retval->system_auth);
458 #else
459         {
460             /* Still parse the syntax but ignore the option.  That way the same
461              * config file can be used for local and server.
462              */
463             bool dummy;
464             readBool (infopath, "SystemAuth", p, &dummy);
465         }
466 #endif
467         else if (strcmp (line, "LocalKeyword") == 0)
468             RCS_setlocalid (infopath, ln, &retval->keywords, p);
469         else if (strcmp (line, "KeywordExpand") == 0)
470             RCS_setincexc (&retval->keywords, p);
471         else if (strcmp (line, "PreservePermissions") == 0)
472         {
473 #ifdef PRESERVE_PERMISSIONS_SUPPORT
474             readBool (infopath, "PreservePermissions", p,
475                       &retval->preserve_perms);
476 #else
477             if (!parse_error (infopath, ln))
478                 error (0, 0, "\
479 %s [%u]: warning: this CVS does not support PreservePermissions",
480                        infopath, ln);
481 #endif
482         }
483         else if (strcmp (line, "TopLevelAdmin") == 0)
484             readBool (infopath, "TopLevelAdmin", p, &retval->top_level_admin);
485         else if (strcmp (line, "LockDir") == 0)
486         {
487             if (retval->lock_dir != NULL)
488                 free (retval->lock_dir);
489             retval->lock_dir = xstrdup (p);
490             /* Could try some validity checking, like whether we can
491                opendir it or something, but I don't see any particular
492                reason to do that now rather than waiting until lock.c.  */
493         }
494         else if (strcmp (line, "LogHistory") == 0)
495         {
496             if (strcmp (p, "all") != 0)
497             {
498                 static bool gotone = false;
499                 if (gotone)
500                     error (0, 0, "\
501 %s [%u]: warning: duplicate LogHistory entry found.",
502                            infopath, ln);
503                 else
504                     gotone = true;
505                 free (retval->logHistory);
506                 retval->logHistory = xstrdup (p);
507             }
508         }
509         else if (strcmp (line, "RereadLogAfterVerify") == 0)
510         {
511             if (!strcasecmp (p, "never"))
512               retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER;
513             else if (!strcasecmp (p, "always"))
514               retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
515             else if (!strcasecmp (p, "stat"))
516               retval->RereadLogAfterVerify = LOGMSG_REREAD_STAT;
517             else
518             {
519                 bool tmp;
520                 if (readBool (infopath, "RereadLogAfterVerify", p, &tmp))
521                 {
522                     if (tmp)
523                         retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
524                     else
525                         retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER;
526                 }
527             }
528         }
529         else if (strcmp (line, "UserAdminOptions") == 0)
530             retval->UserAdminOptions = xstrdup (p);
531         else if (strcmp (line, "UseNewInfoFmtStrings") == 0)
532 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
533             readBool (infopath, "UseNewInfoFmtStrings", p,
534                       &retval->UseNewInfoFmtStrings);
535 #else /* !SUPPORT_OLD_INFO_FMT_STRINGS */
536         {
537             bool dummy;
538             if (readBool (infopath, "UseNewInfoFmtStrings", p, &dummy)
539                 && !dummy)
540                 error (1, 0,
541 "%s [%u]: Old style info format strings not supported by this executable.",
542                        infopath, ln);
543         }
544 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
545         else if (strcmp (line, "ImportNewFilesToVendorBranchOnly") == 0)
546             readBool (infopath, "ImportNewFilesToVendorBranchOnly", p,
547                       &retval->ImportNewFilesToVendorBranchOnly);
548         else if (strcmp (line, "PrimaryServer") == 0)
549             retval->PrimaryServer = parse_cvsroot (p);
550 #ifdef PROXY_SUPPORT
551         else if (!strcmp (line, "MaxProxyBufferSize"))
552             readSizeT (infopath, "MaxProxyBufferSize", p,
553                        &retval->MaxProxyBufferSize);
554 #endif /* PROXY_SUPPORT */
555         else if (!strcmp (line, "MaxCommentLeaderLength"))
556             readSizeT (infopath, "MaxCommentLeaderLength", p,
557                        &retval->MaxCommentLeaderLength);
558         else if (!strcmp (line, "UseArchiveCommentLeader"))
559             readBool (infopath, "UseArchiveCommentLeader", p,
560                       &retval->UseArchiveCommentLeader);
561         else
562             /* We may be dealing with a keyword which was added in a
563                subsequent version of CVS.  In that case it is a good idea
564                to complain, as (1) the keyword might enable a behavior like
565                alternate locking behavior, in which it is dangerous and hard
566                to detect if some CVS's have it one way and others have it
567                the other way, (2) in general, having us not do what the user
568                had in mind when they put in the keyword violates the
569                principle of least surprise.  Note that one corollary is
570                adding new keywords to your CVSROOT/config file is not
571                particularly recommended unless you are planning on using
572                the new features.  */
573             if (!parse_error (infopath, ln))
574                 error (0, 0, "%s [%u]: unrecognized keyword `%s'",
575                        infopath, ln, line);
576     }
577     if (ferror (fp_info))
578         error (0, errno, "cannot read %s", infopath);
579     if (fclose (fp_info) < 0)
580         error (0, errno, "cannot close %s", infopath);
581     free (infopath);
582     if (line != NULL)
583         free (line);
584     return retval;
585 }