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 */ 26int 27Parse_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 */ 220static bool 221readSizeT (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. */ 286static inline struct config * 287new_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#ifdef CVS_ADMIN_GROUP 297 new->UserAdminGroup = xstrdup (CVS_ADMIN_GROUP); 298#else 299 new->UserAdminGroup = NULL; 300#endif 301 new->MaxCommentLeaderLength = 20; 302#ifdef SERVER_SUPPORT 303 new->MaxCompressionLevel = 9; 304#endif /* SERVER_SUPPORT */ 305#ifdef PROXY_SUPPORT 306 new->MaxProxyBufferSize = (size_t)(8 * 1024 * 1024); /* 8 megabytes, 307 * by default. 308 */ 309#endif /* PROXY_SUPPORT */ 310#ifdef AUTH_SERVER_SUPPORT 311 new->system_auth = true; 312#endif /* AUTH_SERVER_SUPPORT */ 313 314 return new; 315} 316 317 318 319void 320free_config (struct config *data) 321{ 322 if (data->keywords) free_keywords (data->keywords); 323 free (data); 324} 325 326 327 328/* Return true if this function has already been called for line LN of file 329 * INFOPATH. 330 */ 331bool 332parse_error (const char *infopath, unsigned int ln) 333{ 334 static List *errors = NULL; 335 char *nodename = NULL; 336 337 if (!errors) 338 errors = getlist(); 339 340 nodename = Xasprintf ("%s/%u", infopath, ln); 341 if (findnode (errors, nodename)) 342 { 343 free (nodename); 344 return true; 345 } 346 347 push_string (errors, nodename); 348 return false; 349} 350 351 352 353#ifdef ALLOW_CONFIG_OVERRIDE 354const char * const allowed_config_prefixes[] = { ALLOW_CONFIG_OVERRIDE }; 355#endif /* ALLOW_CONFIG_OVERRIDE */ 356 357 358 359/* Parse the CVS config file. The syntax right now is a bit ad hoc 360 * but tries to draw on the best or more common features of the other 361 * *info files and various unix (or non-unix) config file syntaxes. 362 * Lines starting with # are comments. Settings are lines of the form 363 * KEYWORD=VALUE. There is currently no way to have a multi-line 364 * VALUE (would be nice if there was, probably). 365 * 366 * CVSROOT is the $CVSROOT directory 367 * (current_parsed_root->directory might not be set yet, so this 368 * function takes the cvsroot as a function argument). 369 * 370 * RETURNS 371 * Always returns a fully initialized config struct, which on error may 372 * contain only the defaults. 373 * 374 * ERRORS 375 * Calls error(0, ...) on errors in addition to the return value. 376 * 377 * xmalloc() failures are fatal, per usual. 378 */ 379struct config * 380parse_config (const char *cvsroot, const char *path) 381{ 382 const char *infopath; 383 char *freeinfopath = NULL; 384 FILE *fp_info; 385 char *line = NULL; 386 unsigned int ln; /* Input file line counter. */ 387 char *buf = NULL; 388 size_t buf_allocated = 0; 389 size_t len; 390 char *p; 391 struct config *retval; 392 /* PROCESSING Whether config keys are currently being processed for 393 * this root. 394 * PROCESSED Whether any keys have been processed for this root. 395 * This is initialized to true so that any initial keys 396 * may be processed as global defaults. 397 */ 398 bool processing = true; 399 bool processed = true; 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 strcmp (line, "tag") == 0) 563 RCS_setlocalid (infopath, ln, &retval->keywords, p); 564 else if (strcmp (line, "KeywordExpand") == 0) 565 RCS_setincexc (&retval->keywords, p); 566 else if (strcmp (line, "PreservePermissions") == 0) 567 { 568#ifdef PRESERVE_PERMISSIONS_SUPPORT 569 readBool (infopath, "PreservePermissions", p, 570 &retval->preserve_perms); 571#else 572 if (!parse_error (infopath, ln)) 573 error (0, 0, "\ 574%s [%u]: warning: this CVS does not support PreservePermissions", 575 infopath, ln); 576#endif 577 } 578 else if (strcmp (line, "TopLevelAdmin") == 0) 579 readBool (infopath, "TopLevelAdmin", p, &retval->top_level_admin); 580 else if (strcmp (line, "LockDir") == 0) 581 { 582 if (retval->lock_dir) 583 free (retval->lock_dir); 584 retval->lock_dir = expand_path (p, cvsroot, false, infopath, ln); 585 /* Could try some validity checking, like whether we can 586 opendir it or something, but I don't see any particular 587 reason to do that now rather than waiting until lock.c. */ 588 } 589 else if (strcmp (line, "HistoryLogPath") == 0) 590 { 591 if (retval->HistoryLogPath) free (retval->HistoryLogPath); 592 593 /* Expand ~ & $VARs. */ 594 retval->HistoryLogPath = expand_path (p, cvsroot, false, 595 infopath, ln); 596 597 if (retval->HistoryLogPath && !ISABSOLUTE (retval->HistoryLogPath)) 598 { 599 error (0, 0, "%s [%u]: HistoryLogPath must be absolute.", 600 infopath, ln); 601 free (retval->HistoryLogPath); 602 retval->HistoryLogPath = NULL; 603 } 604 } 605 else if (strcmp (line, "HistorySearchPath") == 0) 606 { 607 if (retval->HistorySearchPath) free (retval->HistorySearchPath); 608 retval->HistorySearchPath = expand_path (p, cvsroot, false, 609 infopath, ln); 610 611 if (retval->HistorySearchPath 612 && !ISABSOLUTE (retval->HistorySearchPath)) 613 { 614 error (0, 0, "%s [%u]: HistorySearchPath must be absolute.", 615 infopath, ln); 616 free (retval->HistorySearchPath); 617 retval->HistorySearchPath = NULL; 618 } 619 } 620 else if (strcmp (line, "LogHistory") == 0) 621 { 622 if (strcmp (p, "all") != 0) 623 { 624 static bool gotone = false; 625 if (gotone) 626 error (0, 0, "\ 627%s [%u]: warning: duplicate LogHistory entry found.", 628 infopath, ln); 629 else 630 gotone = true; 631 free (retval->logHistory); 632 retval->logHistory = xstrdup (p); 633 } 634 } 635 else if (strcmp (line, "RereadLogAfterVerify") == 0) 636 { 637 if (!strcasecmp (p, "never")) 638 retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER; 639 else if (!strcasecmp (p, "always")) 640 retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS; 641 else if (!strcasecmp (p, "stat")) 642 retval->RereadLogAfterVerify = LOGMSG_REREAD_STAT; 643 else 644 { 645 bool tmp; 646 if (readBool (infopath, "RereadLogAfterVerify", p, &tmp)) 647 { 648 if (tmp) 649 retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS; 650 else 651 retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER; 652 } 653 } 654 } 655 else if (strcmp (line, "TmpDir") == 0) 656 { 657 if (retval->TmpDir) free (retval->TmpDir); 658 retval->TmpDir = expand_path (p, cvsroot, false, infopath, ln); 659 /* Could try some validity checking, like whether we can 660 * opendir it or something, but I don't see any particular 661 * reason to do that now rather than when the first function 662 * tries to create a temp file. 663 */ 664 } 665 else if (strcmp (line, "UserAdminGroup") == 0 666 || strcmp (line, "AdminGroup") == 0) 667 retval->UserAdminGroup = xstrdup (p); 668 else if (strcmp (line, "UserAdminOptions") == 0 669 || strcmp (line, "AdminOptions") == 0) 670 retval->UserAdminOptions = xstrdup (p); 671 else if (strcmp (line, "UseNewInfoFmtStrings") == 0) 672#ifdef SUPPORT_OLD_INFO_FMT_STRINGS 673 readBool (infopath, "UseNewInfoFmtStrings", p, 674 &retval->UseNewInfoFmtStrings); 675#else /* !SUPPORT_OLD_INFO_FMT_STRINGS */ 676 { 677 bool dummy; 678 if (readBool (infopath, "UseNewInfoFmtStrings", p, &dummy) 679 && !dummy) 680 error (1, 0, 681"%s [%u]: Old style info format strings not supported by this executable.", 682 infopath, ln); 683 } 684#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */ 685 else if (strcmp (line, "ImportNewFilesToVendorBranchOnly") == 0) 686 readBool (infopath, "ImportNewFilesToVendorBranchOnly", p, 687 &retval->ImportNewFilesToVendorBranchOnly); 688 else if (strcmp (line, "PrimaryServer") == 0) 689 retval->PrimaryServer = parse_cvsroot (p); 690#ifdef PROXY_SUPPORT 691 else if (!strcmp (line, "MaxProxyBufferSize")) 692 readSizeT (infopath, "MaxProxyBufferSize", p, 693 &retval->MaxProxyBufferSize); 694#endif /* PROXY_SUPPORT */ 695 else if (!strcmp (line, "MaxCommentLeaderLength")) 696 readSizeT (infopath, "MaxCommentLeaderLength", p, 697 &retval->MaxCommentLeaderLength); 698 else if (!strcmp (line, "UseArchiveCommentLeader")) 699 readBool (infopath, "UseArchiveCommentLeader", p, 700 &retval->UseArchiveCommentLeader); 701#ifdef SERVER_SUPPORT 702 else if (!strcmp (line, "MinCompressionLevel")) 703 readSizeT (infopath, "MinCompressionLevel", p, 704 &retval->MinCompressionLevel); 705 else if (!strcmp (line, "MaxCompressionLevel")) 706 readSizeT (infopath, "MaxCompressionLevel", p, 707 &retval->MaxCompressionLevel); 708#endif /* SERVER_SUPPORT */ 709 else 710 /* We may be dealing with a keyword which was added in a 711 subsequent version of CVS. In that case it is a good idea 712 to complain, as (1) the keyword might enable a behavior like 713 alternate locking behavior, in which it is dangerous and hard 714 to detect if some CVS's have it one way and others have it 715 the other way, (2) in general, having us not do what the user 716 had in mind when they put in the keyword violates the 717 principle of least surprise. Note that one corollary is 718 adding new keywords to your CVSROOT/config file is not 719 particularly recommended unless you are planning on using 720 the new features. */ 721 if (!parse_error (infopath, ln)) 722 error (0, 0, "%s [%u]: unrecognized keyword `%s'", 723 infopath, ln, line); 724 } 725 if (ferror (fp_info)) 726 error (0, errno, "cannot read %s", infopath); 727 if (fclose (fp_info) < 0) 728 error (0, errno, "cannot close %s", infopath); 729 if (freeinfopath) free (freeinfopath); 730 if (buf) free (buf); 731 732 return retval; 733} 734