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