Merge from vendor branch CVS:
[dragonfly.git] / contrib / cvs-1.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  * Parse the INFOFILE file for the specified REPOSITORY.  Invoke CALLPROC for
20  * the first line in the file that matches the REPOSITORY, or if ALL != 0, any
21  * lines matching "ALL", or if no lines match, the last line matching
22  * "DEFAULT".
23  *
24  * Return 0 for success, -1 if there was not an INFOFILE, and >0 for failure.
25  */
26 int
27 Parse_Info (const char *infofile, const char *repository, CALLPROC callproc,
28             int opt, void *closure)
29 {
30     int err = 0;
31     FILE *fp_info;
32     char *infopath;
33     char *line = NULL;
34     size_t line_allocated = 0;
35     char *default_value = NULL;
36     int default_line = 0;
37     char *expanded_value;
38     bool callback_done;
39     int line_number;
40     char *cp, *exp, *value;
41     const char *srepos;
42     const char *regex_err;
43
44     assert (repository);
45
46     if (!current_parsed_root)
47     {
48         /* XXX - should be error maybe? */
49         error (0, 0, "CVSROOT variable not set");
50         return 1;
51     }
52
53     /* find the info file and open it */
54     infopath = Xasprintf ("%s/%s/%s", current_parsed_root->directory,
55                           CVSROOTADM, infofile);
56     fp_info = CVS_FOPEN (infopath, "r");
57     if (!fp_info)
58     {
59         /* If no file, don't do anything special.  */
60         if (!existence_error (errno))
61             error (0, errno, "cannot open %s", infopath);
62         free (infopath);
63         return 0;
64     }
65
66     /* strip off the CVSROOT if repository was absolute */
67     srepos = Short_Repository (repository);
68
69     TRACE (TRACE_FUNCTION, "Parse_Info (%s, %s, %s)",
70            infopath, srepos,  (opt & PIOPT_ALL) ? "ALL" : "not ALL");
71
72     /* search the info file for lines that match */
73     callback_done = false;
74     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         cp = strrchr (value, '\n');
112         if (cp) *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)
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 =
146                         expand_path (value, current_parsed_root->directory,
147                                      true, infofile, line_number)))
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         regex_err = re_comp (exp);
163         if (regex_err)
164         {
165             error (0, 0, "bad regular expression at line %d file %s: %s",
166                    line_number, infofile, regex_err);
167             continue;
168         }
169         if (re_exec (srepos) == 0)
170             continue;                           /* no match */
171
172         /* it did, so do the callback and note that we did one */
173         expanded_value = expand_path (value, current_parsed_root->directory,
174                                       true, infofile, line_number);
175         if (expanded_value)
176         {
177             err += callproc (repository, expanded_value, closure);
178             free (expanded_value);
179         }
180         else
181             err++;
182         callback_done = true;
183     }
184     if (ferror (fp_info))
185         error (0, errno, "cannot read %s", infopath);
186     if (fclose (fp_info) < 0)
187         error (0, errno, "cannot close %s", infopath);
188
189     /* if we fell through and didn't callback at all, do the default */
190     if (!callback_done && default_value)
191     {
192         expanded_value = expand_path (default_value,
193                                       current_parsed_root->directory,
194                                       true, infofile, line_number);
195         if (expanded_value)
196         {
197             err += callproc (repository, expanded_value, closure);
198             free (expanded_value);
199         }
200         else
201             err++;
202     }
203
204     /* free up space if necessary */
205     if (default_value) free (default_value);
206     free (infopath);
207     if (line) free (line);
208
209     return err;
210 }
211
212
213
214 /* Print a warning and return false if P doesn't look like a string specifying
215  * something that can be converted into a size_t.
216  *
217  * Sets *VAL to the parsed value when it is found to be valid.  *VAL will not
218  * be altered when false is returned.
219  */
220 static bool
221 readSizeT (const char *infopath, const char *option, const char *p,
222            size_t *val)
223 {
224     const char *q;
225     size_t num, factor = 1;
226
227     if (!strcasecmp ("unlimited", p))
228     {
229         *val = SIZE_MAX;
230         return true;
231     }
232
233     /* Record the factor character (kilo, mega, giga, tera).  */
234     if (!isdigit (p[strlen(p) - 1]))
235     {
236         switch (p[strlen(p) - 1])
237         {
238             case 'T':
239                 factor = xtimes (factor, 1024);
240             case 'G':
241                 factor = xtimes (factor, 1024);
242             case 'M':
243                 factor = xtimes (factor, 1024);
244             case 'k':
245                 factor = xtimes (factor, 1024);
246                 break;
247             default:
248                 error (0, 0,
249     "%s: Unknown %s factor: `%c'",
250                        infopath, option, p[strlen(p)]);
251                 return false;
252         }
253         TRACE (TRACE_DATA, "readSizeT(): Found factor %u for %s",
254                factor, option);
255     }
256
257     /* Verify that *q is a number.  */
258     q = p;
259     while (q < p + strlen(p) - 1 /* Checked last character above.  */)
260     {
261         if (!isdigit(*q))
262         {
263             error (0, 0,
264 "%s: %s must be a postitive integer, not '%s'",
265                    infopath, option, p);
266             return false;
267         }
268         q++;
269     }
270
271     /* Compute final value.  */
272     num = strtoul (p, NULL, 10);
273     if (num == ULONG_MAX || num > SIZE_MAX)
274         /* Don't return an error, just max out.  */
275         num = SIZE_MAX;
276
277     TRACE (TRACE_DATA, "readSizeT(): read number %u for %s", num, option);
278     *val = xtimes (strtoul (p, NULL, 10), factor);
279     TRACE (TRACE_DATA, "readSizeT(): returnning %u for %s", *val, option);
280     return true;
281 }
282
283
284
285 /* Allocate and initialize a new config struct.  */
286 static inline struct config *
287 new_config (void)
288 {
289     struct config *new = xcalloc (1, sizeof (struct config));
290
291     TRACE (TRACE_FLOW, "new_config ()");
292
293     new->logHistory = xstrdup (ALL_HISTORY_REC_TYPES);
294     new->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
295     new->UserAdminOptions = xstrdup ("k");
296     new->MaxCommentLeaderLength = 20;
297 #ifdef SERVER_SUPPORT
298     new->MaxCompressionLevel = 9;
299 #endif /* SERVER_SUPPORT */
300 #ifdef PROXY_SUPPORT
301     new->MaxProxyBufferSize = (size_t)(8 * 1024 * 1024); /* 8 megabytes,
302                                                           * by default.
303                                                           */
304 #endif /* PROXY_SUPPORT */
305 #ifdef AUTH_SERVER_SUPPORT
306     new->system_auth = true;
307 #endif /* AUTH_SERVER_SUPPORT */
308
309     return new;
310 }
311
312
313
314 void
315 free_config (struct config *data)
316 {
317     if (data->keywords) free_keywords (data->keywords);
318     free (data);
319 }
320
321
322
323 /* Return true if this function has already been called for line LN of file
324  * INFOPATH.
325  */
326 bool
327 parse_error (const char *infopath, unsigned int ln)
328 {
329     static List *errors = NULL;
330     char *nodename = NULL;
331
332     if (!errors)
333         errors = getlist();
334
335     nodename = Xasprintf ("%s/%u", infopath, ln);
336     if (findnode (errors, nodename))
337     {
338         free (nodename);
339         return true;
340     }
341
342     push_string (errors, nodename);
343     return false;
344 }
345
346
347
348 #ifdef ALLOW_CONFIG_OVERRIDE
349 const char * const allowed_config_prefixes[] = { ALLOW_CONFIG_OVERRIDE };
350 #endif /* ALLOW_CONFIG_OVERRIDE */
351
352
353
354 /* Parse the CVS config file.  The syntax right now is a bit ad hoc
355  * but tries to draw on the best or more common features of the other
356  * *info files and various unix (or non-unix) config file syntaxes.
357  * Lines starting with # are comments.  Settings are lines of the form
358  * KEYWORD=VALUE.  There is currently no way to have a multi-line
359  * VALUE (would be nice if there was, probably).
360  *
361  * CVSROOT is the $CVSROOT directory
362  * (current_parsed_root->directory might not be set yet, so this
363  * function takes the cvsroot as a function argument).
364  *
365  * RETURNS
366  *   Always returns a fully initialized config struct, which on error may
367  *   contain only the defaults.
368  *
369  * ERRORS
370  *   Calls error(0, ...) on errors in addition to the return value.
371  *
372  *   xmalloc() failures are fatal, per usual.
373  */
374 struct config *
375 parse_config (const char *cvsroot, const char *path)
376 {
377     const char *infopath;
378     char *freeinfopath = NULL;
379     FILE *fp_info;
380     char *line = NULL;
381     unsigned int ln;            /* Input file line counter.  */
382     char *buf = NULL;
383     size_t buf_allocated = 0;
384     size_t len;
385     char *p;
386     struct config *retval;
387     /* PROCESSING       Whether config keys are currently being processed for
388      *                  this root.
389      * PROCESSED        Whether any keys have been processed for this root.
390      *                  This is initialized to true so that any initial keys
391      *                  may be processed as global defaults.
392      */
393     bool processing = true;
394     bool processed = true;
395
396     TRACE (TRACE_FUNCTION, "parse_config (%s)", cvsroot);
397
398 #ifdef ALLOW_CONFIG_OVERRIDE
399     if (path)
400     {
401         const char * const *prefix;
402         char *npath = xcanonicalize_file_name (path);
403         bool approved = false;
404         for (prefix = allowed_config_prefixes; *prefix != NULL; prefix++)
405         {
406             char *nprefix;
407
408             if (!isreadable (*prefix)) continue;
409             nprefix = xcanonicalize_file_name (*prefix);
410             if (!strncmp (nprefix, npath, strlen (nprefix))
411                 && (((*prefix)[strlen (*prefix)] != '/'
412                      && strlen (npath) == strlen (nprefix))
413                     || ((*prefix)[strlen (*prefix)] == '/'
414                         && npath[strlen (nprefix)] == '/')))
415                 approved = true;
416             free (nprefix);
417             if (approved) break;
418         }
419         if (!approved)
420             error (1, 0, "Invalid path to config file specified: `%s'",
421                    path);
422         infopath = path;
423         free (npath);
424     }
425     else
426 #endif
427         infopath = freeinfopath =
428             Xasprintf ("%s/%s/%s", cvsroot, CVSROOTADM, CVSROOTADM_CONFIG);
429
430     retval = new_config ();
431
432     fp_info = CVS_FOPEN (infopath, "r");
433     if (!fp_info)
434     {
435         /* If no file, don't do anything special.  */
436         if (!existence_error (errno))
437         {
438             /* Just a warning message; doesn't affect return
439                value, currently at least.  */
440             error (0, errno, "cannot open %s", infopath);
441         }
442         if (freeinfopath) free (freeinfopath);
443         return retval;
444     }
445
446     ln = 0;  /* Have not read any lines yet.  */
447     while (getline (&buf, &buf_allocated, fp_info) >= 0)
448     {
449         ln++; /* Keep track of input file line number for error messages.  */
450
451         line = buf;
452
453         /* Skip leading white space.  */
454         while (isspace (*line)) line++;
455
456         /* Skip comments.  */
457         if (line[0] == '#')
458             continue;
459
460         /* Is there any kind of written standard for the syntax of this
461            sort of config file?  Anywhere in POSIX for example (I guess
462            makefiles are sort of close)?  Red Hat Linux has a bunch of
463            these too (with some GUI tools which edit them)...
464
465            Along the same lines, we might want a table of keywords,
466            with various types (boolean, string, &c), as a mechanism
467            for making sure the syntax is consistent.  Any good examples
468            to follow there (Apache?)?  */
469
470         /* Strip the trailing newline.  There will be one unless we
471            read a partial line without a newline, and then got end of
472            file (or error?).  */
473
474         len = strlen (line) - 1;
475         if (line[len] == '\n')
476             line[len--] = '\0';
477
478         /* Skip blank lines.  */
479         if (line[0] == '\0')
480             continue;
481
482         TRACE (TRACE_DATA, "parse_info() examining line: `%s'", line);
483
484         /* Check for a root specification.  */
485         if (line[0] == '[' && line[len] == ']')
486         {
487             cvsroot_t *tmproot;
488
489             line++[len] = '\0';
490             tmproot = parse_cvsroot (line);
491
492             /* Ignoring method.  */
493             if (!tmproot
494 #if defined CLIENT_SUPPORT || defined SERVER_SUPPORT
495                 || (tmproot->method != local_method
496                     && (!tmproot->hostname || !isThisHost (tmproot->hostname)))
497 #endif /* CLIENT_SUPPORT || SERVER_SUPPORT */
498                 || !isSamePath (tmproot->directory, cvsroot))
499             {
500                 if (processed) processing = false;
501             }
502             else
503             {
504                 TRACE (TRACE_FLOW, "Matched root section`%s'", line);
505                 processing = true;
506                 processed = false;
507             }
508
509             continue;
510         }
511
512         /* There is data on this line.  */
513
514         /* Even if the data is bad or ignored, consider data processed for
515          * this root.
516          */
517         processed = true;
518
519         if (!processing)
520             /* ...but it is for a different root.  */
521              continue;
522
523         /* The first '=' separates keyword from value.  */
524         p = strchr (line, '=');
525         if (!p)
526         {
527             if (!parse_error (infopath, ln))
528                 error (0, 0,
529 "%s [%d]: syntax error: missing `=' between keyword and value",
530                        infopath, ln);
531             continue;
532         }
533
534         *p++ = '\0';
535
536         if (strcmp (line, "RCSBIN") == 0)
537         {
538             /* This option used to specify the directory for RCS
539                executables.  But since we don't run them any more,
540                this is a noop.  Silently ignore it so that a
541                repository can work with either new or old CVS.  */
542             ;
543         }
544         else if (strcmp (line, "SystemAuth") == 0)
545 #ifdef AUTH_SERVER_SUPPORT
546             readBool (infopath, "SystemAuth", p, &retval->system_auth);
547 #else
548         {
549             /* Still parse the syntax but ignore the option.  That way the same
550              * config file can be used for local and server.
551              */
552             bool dummy;
553             readBool (infopath, "SystemAuth", p, &dummy);
554         }
555 #endif
556         else if (strcmp (line, "LocalKeyword") == 0)
557             RCS_setlocalid (infopath, ln, &retval->keywords, p);
558         else if (strcmp (line, "KeywordExpand") == 0)
559             RCS_setincexc (&retval->keywords, p);
560         else if (strcmp (line, "PreservePermissions") == 0)
561         {
562 #ifdef PRESERVE_PERMISSIONS_SUPPORT
563             readBool (infopath, "PreservePermissions", p,
564                       &retval->preserve_perms);
565 #else
566             if (!parse_error (infopath, ln))
567                 error (0, 0, "\
568 %s [%u]: warning: this CVS does not support PreservePermissions",
569                        infopath, ln);
570 #endif
571         }
572         else if (strcmp (line, "TopLevelAdmin") == 0)
573             readBool (infopath, "TopLevelAdmin", p, &retval->top_level_admin);
574         else if (strcmp (line, "LockDir") == 0)
575         {
576             if (retval->lock_dir)
577                 free (retval->lock_dir);
578             retval->lock_dir = expand_path (p, cvsroot, false, infopath, ln);
579             /* Could try some validity checking, like whether we can
580                opendir it or something, but I don't see any particular
581                reason to do that now rather than waiting until lock.c.  */
582         }
583         else if (strcmp (line, "HistoryLogPath") == 0)
584         {
585             if (retval->HistoryLogPath) free (retval->HistoryLogPath);
586
587             /* Expand ~ & $VARs.  */
588             retval->HistoryLogPath = expand_path (p, cvsroot, false,
589                                                   infopath, ln);
590
591             if (retval->HistoryLogPath && !ISABSOLUTE (retval->HistoryLogPath))
592             {
593                 error (0, 0, "%s [%u]: HistoryLogPath must be absolute.",
594                        infopath, ln);
595                 free (retval->HistoryLogPath);
596                 retval->HistoryLogPath = NULL;
597             }
598         }
599         else if (strcmp (line, "HistorySearchPath") == 0)
600         {
601             if (retval->HistorySearchPath) free (retval->HistorySearchPath);
602             retval->HistorySearchPath = expand_path (p, cvsroot, false,
603                                                      infopath, ln);
604
605             if (retval->HistorySearchPath
606                 && !ISABSOLUTE (retval->HistorySearchPath))
607             {
608                 error (0, 0, "%s [%u]: HistorySearchPath must be absolute.",
609                        infopath, ln);
610                 free (retval->HistorySearchPath);
611                 retval->HistorySearchPath = NULL;
612             }
613         }
614         else if (strcmp (line, "LogHistory") == 0)
615         {
616             if (strcmp (p, "all") != 0)
617             {
618                 static bool gotone = false;
619                 if (gotone)
620                     error (0, 0, "\
621 %s [%u]: warning: duplicate LogHistory entry found.",
622                            infopath, ln);
623                 else
624                     gotone = true;
625                 free (retval->logHistory);
626                 retval->logHistory = xstrdup (p);
627             }
628         }
629         else if (strcmp (line, "RereadLogAfterVerify") == 0)
630         {
631             if (!strcasecmp (p, "never"))
632               retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER;
633             else if (!strcasecmp (p, "always"))
634               retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
635             else if (!strcasecmp (p, "stat"))
636               retval->RereadLogAfterVerify = LOGMSG_REREAD_STAT;
637             else
638             {
639                 bool tmp;
640                 if (readBool (infopath, "RereadLogAfterVerify", p, &tmp))
641                 {
642                     if (tmp)
643                         retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
644                     else
645                         retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER;
646                 }
647             }
648         }
649         else if (strcmp (line, "TmpDir") == 0)
650         {
651             if (retval->TmpDir) free (retval->TmpDir);
652             retval->TmpDir = expand_path (p, cvsroot, false, infopath, ln);
653             /* Could try some validity checking, like whether we can
654              * opendir it or something, but I don't see any particular
655              * reason to do that now rather than when the first function
656              * tries to create a temp file.
657              */
658         }
659         else if (strcmp (line, "UserAdminOptions") == 0)
660             retval->UserAdminOptions = xstrdup (p);
661         else if (strcmp (line, "UseNewInfoFmtStrings") == 0)
662 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
663             readBool (infopath, "UseNewInfoFmtStrings", p,
664                       &retval->UseNewInfoFmtStrings);
665 #else /* !SUPPORT_OLD_INFO_FMT_STRINGS */
666         {
667             bool dummy;
668             if (readBool (infopath, "UseNewInfoFmtStrings", p, &dummy)
669                 && !dummy)
670                 error (1, 0,
671 "%s [%u]: Old style info format strings not supported by this executable.",
672                        infopath, ln);
673         }
674 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
675         else if (strcmp (line, "ImportNewFilesToVendorBranchOnly") == 0)
676             readBool (infopath, "ImportNewFilesToVendorBranchOnly", p,
677                       &retval->ImportNewFilesToVendorBranchOnly);
678         else if (strcmp (line, "PrimaryServer") == 0)
679             retval->PrimaryServer = parse_cvsroot (p);
680 #ifdef PROXY_SUPPORT
681         else if (!strcmp (line, "MaxProxyBufferSize"))
682             readSizeT (infopath, "MaxProxyBufferSize", p,
683                        &retval->MaxProxyBufferSize);
684 #endif /* PROXY_SUPPORT */
685         else if (!strcmp (line, "MaxCommentLeaderLength"))
686             readSizeT (infopath, "MaxCommentLeaderLength", p,
687                        &retval->MaxCommentLeaderLength);
688         else if (!strcmp (line, "UseArchiveCommentLeader"))
689             readBool (infopath, "UseArchiveCommentLeader", p,
690                       &retval->UseArchiveCommentLeader);
691 #ifdef SERVER_SUPPORT
692         else if (!strcmp (line, "MinCompressionLevel"))
693             readSizeT (infopath, "MinCompressionLevel", p,
694                        &retval->MinCompressionLevel);
695         else if (!strcmp (line, "MaxCompressionLevel"))
696             readSizeT (infopath, "MaxCompressionLevel", p,
697                        &retval->MaxCompressionLevel);
698 #endif /* SERVER_SUPPORT */
699         else
700             /* We may be dealing with a keyword which was added in a
701                subsequent version of CVS.  In that case it is a good idea
702                to complain, as (1) the keyword might enable a behavior like
703                alternate locking behavior, in which it is dangerous and hard
704                to detect if some CVS's have it one way and others have it
705                the other way, (2) in general, having us not do what the user
706                had in mind when they put in the keyword violates the
707                principle of least surprise.  Note that one corollary is
708                adding new keywords to your CVSROOT/config file is not
709                particularly recommended unless you are planning on using
710                the new features.  */
711             if (!parse_error (infopath, ln))
712                 error (0, 0, "%s [%u]: unrecognized keyword `%s'",
713                        infopath, ln, line);
714     }
715     if (ferror (fp_info))
716         error (0, errno, "cannot read %s", infopath);
717     if (fclose (fp_info) < 0)
718         error (0, errno, "cannot close %s", infopath);
719     if (freeinfopath) free (freeinfopath);
720     if (buf) free (buf);
721
722     return retval;
723 }