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 } 724