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