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