xref: /openbsd/gnu/usr.bin/cvs/src/parseinfo.c (revision f9bbbf45)
11e72d8d2Sderaadt /*
21e72d8d2Sderaadt  * Copyright (c) 1992, Brian Berliner and Jeff Polk
31e72d8d2Sderaadt  * Copyright (c) 1989-1992, Brian Berliner
41e72d8d2Sderaadt  *
51e72d8d2Sderaadt  * You may distribute under the terms of the GNU General Public License as
62286d8edStholo  * specified in the README file that comes with the CVS source distribution.
71e72d8d2Sderaadt  */
81e72d8d2Sderaadt 
91e72d8d2Sderaadt #include "cvs.h"
10780d15dfStholo #include "getline.h"
112286d8edStholo #include <assert.h>
121e72d8d2Sderaadt 
13bde78045Stholo extern char *logHistory;
14bde78045Stholo 
151e72d8d2Sderaadt /*
161e72d8d2Sderaadt  * Parse the INFOFILE file for the specified REPOSITORY.  Invoke CALLPROC for
171e72d8d2Sderaadt  * the first line in the file that matches the REPOSITORY, or if ALL != 0, any lines
181e72d8d2Sderaadt  * matching "ALL", or if no lines match, the last line matching "DEFAULT".
191e72d8d2Sderaadt  *
201e72d8d2Sderaadt  * Return 0 for success, -1 if there was not an INFOFILE, and >0 for failure.
211e72d8d2Sderaadt  */
221e72d8d2Sderaadt int
Parse_Info(infofile,repository,callproc,all)231e72d8d2Sderaadt Parse_Info (infofile, repository, callproc, all)
241e72d8d2Sderaadt     char *infofile;
251e72d8d2Sderaadt     char *repository;
2613571821Stholo     CALLPROC callproc;
271e72d8d2Sderaadt     int all;
281e72d8d2Sderaadt {
291e72d8d2Sderaadt     int err = 0;
301e72d8d2Sderaadt     FILE *fp_info;
31780d15dfStholo     char *infopath;
32780d15dfStholo     char *line = NULL;
33780d15dfStholo     size_t line_allocated = 0;
341e72d8d2Sderaadt     char *default_value = NULL;
3513571821Stholo     char *expanded_value= NULL;
361e72d8d2Sderaadt     int callback_done, line_number;
37bde78045Stholo     char *cp, *exp, *value, *srepos, bad;
381e72d8d2Sderaadt     const char *regex_err;
391e72d8d2Sderaadt 
4079822b2aStholo     if (current_parsed_root == NULL)
411e72d8d2Sderaadt     {
421e72d8d2Sderaadt 	/* XXX - should be error maybe? */
431e72d8d2Sderaadt 	error (0, 0, "CVSROOT variable not set");
441e72d8d2Sderaadt 	return (1);
451e72d8d2Sderaadt     }
461e72d8d2Sderaadt 
471e72d8d2Sderaadt     /* find the info file and open it */
4879822b2aStholo     infopath = xmalloc (strlen (current_parsed_root->directory)
49780d15dfStholo 			+ strlen (infofile)
50780d15dfStholo 			+ sizeof (CVSROOTADM)
5179822b2aStholo 			+ 3);
5279822b2aStholo     (void) sprintf (infopath, "%s/%s/%s", current_parsed_root->directory,
531e72d8d2Sderaadt 		    CVSROOTADM, infofile);
54780d15dfStholo     fp_info = CVS_FOPEN (infopath, "r");
55780d15dfStholo     if (fp_info == NULL)
56780d15dfStholo     {
57780d15dfStholo 	/* If no file, don't do anything special.  */
58780d15dfStholo 	if (!existence_error (errno))
59780d15dfStholo 	    error (0, errno, "cannot open %s", infopath);
602770ece5Stholo 	free (infopath);
61780d15dfStholo 	return 0;
62780d15dfStholo     }
631e72d8d2Sderaadt 
641e72d8d2Sderaadt     /* strip off the CVSROOT if repository was absolute */
651e72d8d2Sderaadt     srepos = Short_Repository (repository);
661e72d8d2Sderaadt 
671e72d8d2Sderaadt     if (trace)
681e72d8d2Sderaadt 	(void) fprintf (stderr, " -> ParseInfo(%s, %s, %s)\n",
691e72d8d2Sderaadt 			infopath, srepos, all ? "ALL" : "not ALL");
701e72d8d2Sderaadt 
711e72d8d2Sderaadt     /* search the info file for lines that match */
721e72d8d2Sderaadt     callback_done = line_number = 0;
73*f9bbbf45Sfgsch     while (get_line (&line, &line_allocated, fp_info) >= 0)
741e72d8d2Sderaadt     {
751e72d8d2Sderaadt 	line_number++;
761e72d8d2Sderaadt 
771e72d8d2Sderaadt 	/* skip lines starting with # */
781e72d8d2Sderaadt 	if (line[0] == '#')
791e72d8d2Sderaadt 	    continue;
801e72d8d2Sderaadt 
811e72d8d2Sderaadt 	/* skip whitespace at beginning of line */
829fe7c2c3Stholo 	for (cp = line; *cp && isspace ((unsigned char) *cp); cp++)
831e72d8d2Sderaadt 	    ;
841e72d8d2Sderaadt 
851e72d8d2Sderaadt 	/* if *cp is null, the whole line was blank */
861e72d8d2Sderaadt 	if (*cp == '\0')
871e72d8d2Sderaadt 	    continue;
881e72d8d2Sderaadt 
891e72d8d2Sderaadt 	/* the regular expression is everything up to the first space */
909fe7c2c3Stholo 	for (exp = cp; *cp && !isspace ((unsigned char) *cp); cp++)
911e72d8d2Sderaadt 	    ;
921e72d8d2Sderaadt 	if (*cp != '\0')
931e72d8d2Sderaadt 	    *cp++ = '\0';
941e72d8d2Sderaadt 
951e72d8d2Sderaadt 	/* skip whitespace up to the start of the matching value */
969fe7c2c3Stholo 	while (*cp && isspace ((unsigned char) *cp))
971e72d8d2Sderaadt 	    cp++;
981e72d8d2Sderaadt 
991e72d8d2Sderaadt 	/* no value to match with the regular expression is an error */
1001e72d8d2Sderaadt 	if (*cp == '\0')
1011e72d8d2Sderaadt 	{
1021e72d8d2Sderaadt 	    error (0, 0, "syntax error at line %d file %s; ignored",
1031e72d8d2Sderaadt 		   line_number, infofile);
1041e72d8d2Sderaadt 	    continue;
1051e72d8d2Sderaadt 	}
1061e72d8d2Sderaadt 	value = cp;
1071e72d8d2Sderaadt 
1081e72d8d2Sderaadt 	/* strip the newline off the end of the value */
1091e72d8d2Sderaadt 	if ((cp = strrchr (value, '\n')) != NULL)
1101e72d8d2Sderaadt 	    *cp = '\0';
1111e72d8d2Sderaadt 
1122770ece5Stholo 	if (expanded_value != NULL)
1132770ece5Stholo 	    free (expanded_value);
114c26070a5Stholo 	expanded_value = expand_path (value, infofile, line_number);
1151e72d8d2Sderaadt 
1161e72d8d2Sderaadt 	/*
1171e72d8d2Sderaadt 	 * At this point, exp points to the regular expression, and value
1181e72d8d2Sderaadt 	 * points to the value to call the callback routine with.  Evaluate
1191e72d8d2Sderaadt 	 * the regular expression against srepos and callback with the value
1201e72d8d2Sderaadt 	 * if it matches.
1211e72d8d2Sderaadt 	 */
1221e72d8d2Sderaadt 
1231e72d8d2Sderaadt 	/* save the default value so we have it later if we need it */
1241e72d8d2Sderaadt 	if (strcmp (exp, "DEFAULT") == 0)
1251e72d8d2Sderaadt 	{
1262770ece5Stholo 	    /* Is it OK to silently ignore all but the last DEFAULT
1272770ece5Stholo                expression?  */
128bde78045Stholo 	    if (default_value != NULL && default_value != &bad)
1292770ece5Stholo 		free (default_value);
130bde78045Stholo 	    default_value = (expanded_value != NULL ?
131bde78045Stholo 			     xstrdup (expanded_value) : &bad);
1321e72d8d2Sderaadt 	    continue;
1331e72d8d2Sderaadt 	}
1341e72d8d2Sderaadt 
1351e72d8d2Sderaadt 	/*
1361e72d8d2Sderaadt 	 * For a regular expression of "ALL", do the callback always We may
1371e72d8d2Sderaadt 	 * execute lots of ALL callbacks in addition to *one* regular matching
1381e72d8d2Sderaadt 	 * callback or default
1391e72d8d2Sderaadt 	 */
1401e72d8d2Sderaadt 	if (strcmp (exp, "ALL") == 0)
1411e72d8d2Sderaadt 	{
142bde78045Stholo 	    if (!all)
1431e72d8d2Sderaadt 		error(0, 0, "Keyword `ALL' is ignored at line %d in %s file",
1441e72d8d2Sderaadt 		      line_number, infofile);
145bde78045Stholo 	    else if (expanded_value != NULL)
146bde78045Stholo 		err += callproc (repository, expanded_value);
147bde78045Stholo 	    else
148bde78045Stholo 		err++;
1491e72d8d2Sderaadt 	    continue;
1501e72d8d2Sderaadt 	}
1511e72d8d2Sderaadt 
1521e72d8d2Sderaadt 	if (callback_done)
1531e72d8d2Sderaadt 	    /* only first matching, plus "ALL"'s */
1541e72d8d2Sderaadt 	    continue;
1551e72d8d2Sderaadt 
1561e72d8d2Sderaadt 	/* see if the repository matched this regular expression */
1571e72d8d2Sderaadt 	if ((regex_err = re_comp (exp)) != NULL)
1581e72d8d2Sderaadt 	{
1591e72d8d2Sderaadt 	    error (0, 0, "bad regular expression at line %d file %s: %s",
1601e72d8d2Sderaadt 		   line_number, infofile, regex_err);
1611e72d8d2Sderaadt 	    continue;
1621e72d8d2Sderaadt 	}
1631e72d8d2Sderaadt 	if (re_exec (srepos) == 0)
1641e72d8d2Sderaadt 	    continue;				/* no match */
1651e72d8d2Sderaadt 
1661e72d8d2Sderaadt 	/* it did, so do the callback and note that we did one */
167bde78045Stholo 	if (expanded_value != NULL)
16813571821Stholo 	    err += callproc (repository, expanded_value);
169bde78045Stholo 	else
170bde78045Stholo 	    err++;
1711e72d8d2Sderaadt 	callback_done = 1;
1721e72d8d2Sderaadt     }
173780d15dfStholo     if (ferror (fp_info))
174780d15dfStholo 	error (0, errno, "cannot read %s", infopath);
175780d15dfStholo     if (fclose (fp_info) < 0)
176780d15dfStholo 	error (0, errno, "cannot close %s", infopath);
1771e72d8d2Sderaadt 
1781e72d8d2Sderaadt     /* if we fell through and didn't callback at all, do the default */
1791e72d8d2Sderaadt     if (callback_done == 0 && default_value != NULL)
180bde78045Stholo     {
181bde78045Stholo 	if (default_value != &bad)
1821e72d8d2Sderaadt 	    err += callproc (repository, default_value);
183bde78045Stholo 	else
184bde78045Stholo 	    err++;
185bde78045Stholo     }
1861e72d8d2Sderaadt 
1871e72d8d2Sderaadt     /* free up space if necessary */
188bde78045Stholo     if (default_value != NULL && default_value != &bad)
1891e72d8d2Sderaadt 	free (default_value);
19013571821Stholo     if (expanded_value != NULL)
19113571821Stholo 	free (expanded_value);
192780d15dfStholo     free (infopath);
193780d15dfStholo     if (line != NULL)
194780d15dfStholo 	free (line);
1951e72d8d2Sderaadt 
1961e72d8d2Sderaadt     return (err);
1971e72d8d2Sderaadt }
1982286d8edStholo 
1992286d8edStholo 
2002286d8edStholo /* Parse the CVS config file.  The syntax right now is a bit ad hoc
2012286d8edStholo    but tries to draw on the best or more common features of the other
2022286d8edStholo    *info files and various unix (or non-unix) config file syntaxes.
2032286d8edStholo    Lines starting with # are comments.  Settings are lines of the form
2042286d8edStholo    KEYWORD=VALUE.  There is currently no way to have a multi-line
2052286d8edStholo    VALUE (would be nice if there was, probably).
2062286d8edStholo 
20779822b2aStholo    CVSROOT is the $CVSROOT directory (current_parsed_root->directory might not be
2082286d8edStholo    set yet).
2092286d8edStholo 
2102286d8edStholo    Returns 0 for success, negative value for failure.  Call
2112286d8edStholo    error(0, ...) on errors in addition to the return value.  */
2122286d8edStholo int
parse_config(cvsroot)2132286d8edStholo parse_config (cvsroot)
2142286d8edStholo     char *cvsroot;
2152286d8edStholo {
2162286d8edStholo     char *infopath;
2172286d8edStholo     FILE *fp_info;
2182286d8edStholo     char *line = NULL;
2192286d8edStholo     size_t line_allocated = 0;
2202286d8edStholo     size_t len;
2212286d8edStholo     char *p;
2222286d8edStholo     /* FIXME-reentrancy: If we do a multi-threaded server, this would need
2232286d8edStholo        to go to the per-connection data structures.  */
2242286d8edStholo     static int parsed = 0;
2252286d8edStholo 
2262286d8edStholo     /* Authentication code and serve_root might both want to call us.
2272286d8edStholo        Let this happen smoothly.  */
2282286d8edStholo     if (parsed)
2292286d8edStholo 	return 0;
2302286d8edStholo     parsed = 1;
2312286d8edStholo 
2322286d8edStholo     infopath = malloc (strlen (cvsroot)
2332286d8edStholo 			+ sizeof (CVSROOTADM_CONFIG)
2342286d8edStholo 			+ sizeof (CVSROOTADM)
2352286d8edStholo 			+ 10);
2362286d8edStholo     if (infopath == NULL)
2372286d8edStholo     {
2382286d8edStholo 	error (0, 0, "out of memory; cannot allocate infopath");
2392286d8edStholo 	goto error_return;
2402286d8edStholo     }
2412286d8edStholo 
2422286d8edStholo     strcpy (infopath, cvsroot);
2432286d8edStholo     strcat (infopath, "/");
2442286d8edStholo     strcat (infopath, CVSROOTADM);
2452286d8edStholo     strcat (infopath, "/");
2462286d8edStholo     strcat (infopath, CVSROOTADM_CONFIG);
2472286d8edStholo 
2482286d8edStholo     fp_info = CVS_FOPEN (infopath, "r");
2492286d8edStholo     if (fp_info == NULL)
2502286d8edStholo     {
2512286d8edStholo 	/* If no file, don't do anything special.  */
2522286d8edStholo 	if (!existence_error (errno))
2532286d8edStholo 	{
2542286d8edStholo 	    /* Just a warning message; doesn't affect return
2552286d8edStholo 	       value, currently at least.  */
2562286d8edStholo 	    error (0, errno, "cannot open %s", infopath);
2572286d8edStholo 	}
2582286d8edStholo 	free (infopath);
2592286d8edStholo 	return 0;
2602286d8edStholo     }
2612286d8edStholo 
262*f9bbbf45Sfgsch     while (get_line (&line, &line_allocated, fp_info) >= 0)
2632286d8edStholo     {
2642286d8edStholo 	/* Skip comments.  */
2652286d8edStholo 	if (line[0] == '#')
2662286d8edStholo 	    continue;
2672286d8edStholo 
2682286d8edStholo 	/* At least for the moment we don't skip whitespace at the start
2692286d8edStholo 	   of the line.  Too picky?  Maybe.  But being insufficiently
2702286d8edStholo 	   picky leads to all sorts of confusion, and it is a lot easier
2712286d8edStholo 	   to start out picky and relax it than the other way around.
2722286d8edStholo 
2732286d8edStholo 	   Is there any kind of written standard for the syntax of this
2742286d8edStholo 	   sort of config file?  Anywhere in POSIX for example (I guess
2752286d8edStholo 	   makefiles are sort of close)?  Red Hat Linux has a bunch of
2762286d8edStholo 	   these too (with some GUI tools which edit them)...
2772286d8edStholo 
2782286d8edStholo 	   Along the same lines, we might want a table of keywords,
2792286d8edStholo 	   with various types (boolean, string, &c), as a mechanism
2802286d8edStholo 	   for making sure the syntax is consistent.  Any good examples
2812286d8edStholo 	   to follow there (Apache?)?  */
2822286d8edStholo 
2832286d8edStholo 	/* Strip the training newline.  There will be one unless we
2842286d8edStholo 	   read a partial line without a newline, and then got end of
2852286d8edStholo 	   file (or error?).  */
2862286d8edStholo 
2872286d8edStholo 	len = strlen (line) - 1;
2882286d8edStholo 	if (line[len] == '\n')
2892286d8edStholo 	    line[len] = '\0';
2902286d8edStholo 
2912286d8edStholo 	/* Skip blank lines.  */
2922286d8edStholo 	if (line[0] == '\0')
2932286d8edStholo 	    continue;
2942286d8edStholo 
2952286d8edStholo 	/* The first '=' separates keyword from value.  */
2962286d8edStholo 	p = strchr (line, '=');
2972286d8edStholo 	if (p == NULL)
2982286d8edStholo 	{
2992286d8edStholo 	    /* Probably should be printing line number.  */
3002286d8edStholo 	    error (0, 0, "syntax error in %s: line '%s' is missing '='",
3012286d8edStholo 		   infopath, line);
3022286d8edStholo 	    goto error_return;
3032286d8edStholo 	}
3042286d8edStholo 
3052286d8edStholo 	*p++ = '\0';
3062286d8edStholo 
3072286d8edStholo 	if (strcmp (line, "RCSBIN") == 0)
3082286d8edStholo 	{
3092286d8edStholo 	    /* This option used to specify the directory for RCS
3102286d8edStholo 	       executables.  But since we don't run them any more,
3112286d8edStholo 	       this is a noop.  Silently ignore it so that a
3122286d8edStholo 	       repository can work with either new or old CVS.  */
3132286d8edStholo 	    ;
3142286d8edStholo 	}
3152286d8edStholo 	else if (strcmp (line, "SystemAuth") == 0)
3162286d8edStholo 	{
3172286d8edStholo 	    if (strcmp (p, "no") == 0)
3182286d8edStholo #ifdef AUTH_SERVER_SUPPORT
3192286d8edStholo 		system_auth = 0;
3202286d8edStholo #else
3212286d8edStholo 		/* Still parse the syntax but ignore the
3222286d8edStholo 		   option.  That way the same config file can
3232286d8edStholo 		   be used for local and server.  */
3242286d8edStholo 		;
3252286d8edStholo #endif
3262286d8edStholo 	    else if (strcmp (p, "yes") == 0)
3272286d8edStholo #ifdef AUTH_SERVER_SUPPORT
3282286d8edStholo 		system_auth = 1;
3292286d8edStholo #else
3302286d8edStholo 		;
3312286d8edStholo #endif
3322286d8edStholo 	    else
3332286d8edStholo 	    {
3342286d8edStholo 		error (0, 0, "unrecognized value '%s' for SystemAuth", p);
3352286d8edStholo 		goto error_return;
3362286d8edStholo 	    }
3372286d8edStholo 	}
3389c9c4491Stholo 	else if (strcmp (line, "tag") == 0) {
3399c9c4491Stholo 	    RCS_citag = strdup(p);
3409c9c4491Stholo 	    if (RCS_citag == NULL) {
3419c9c4491Stholo 		error (0, 0, "%s: no memory for local tag '%s'",
3429c9c4491Stholo 		       infopath, p);
3439c9c4491Stholo 		goto error_return;
3449c9c4491Stholo 	    }
3459c9c4491Stholo 	}
3469c9c4491Stholo 	else if (strcmp (line, "umask") == 0) {
3479c9c4491Stholo 	    cvsumask = (mode_t)(strtol(p, NULL, 8) & 0777);
3489c9c4491Stholo 	}
3497f14e9e2Sdjm 	else if (strcmp (line, "DisableMdocdate") == 0) {
3507f14e9e2Sdjm 	    if (strcmp (p, "no") == 0)
3517f14e9e2Sdjm 		disable_mdocdate = 0;
3527f14e9e2Sdjm 	    else if (strcmp (p, "yes") == 0)
3537f14e9e2Sdjm 		disable_mdocdate = 1;
3547f14e9e2Sdjm 	    else
3557f14e9e2Sdjm 	    {
3567f14e9e2Sdjm 		error (0, 0, "unrecognized value '%s' for DisableMdocdate", p);
3577f14e9e2Sdjm 		goto error_return;
3587f14e9e2Sdjm 	    }
3597f14e9e2Sdjm 	}
3609c9c4491Stholo 	else if (strcmp (line, "dlimit") == 0) {
3619c9c4491Stholo #ifdef BSD
3629c9c4491Stholo #include <sys/resource.h>
3639c9c4491Stholo 	    struct rlimit rl;
3649c9c4491Stholo 
3659c9c4491Stholo 	    if (getrlimit(RLIMIT_DATA, &rl) != -1) {
3669c9c4491Stholo 		rl.rlim_cur = atoi(p);
3679c9c4491Stholo 		rl.rlim_cur *= 1024;
3689c9c4491Stholo 
3699c9c4491Stholo 		(void) setrlimit(RLIMIT_DATA, &rl);
3709c9c4491Stholo 	    }
3719c9c4491Stholo #endif /* BSD */
3729c9c4491Stholo 	}
373a6f8fe38Smillert 	else if (strcmp (line, "DisableXProg") == 0)
374a6f8fe38Smillert 	{
375a6f8fe38Smillert 	    if (strcmp (p, "no") == 0)
376a6f8fe38Smillert #ifdef AUTH_SERVER_SUPPORT
377a6f8fe38Smillert 		disable_x_prog = 0;
378a6f8fe38Smillert #else
379a6f8fe38Smillert 		/* Still parse the syntax but ignore the
380a6f8fe38Smillert 		   option.  That way the same config file can
381a6f8fe38Smillert 		   be used for local and server.  */
382a6f8fe38Smillert 		;
383a6f8fe38Smillert #endif
384a6f8fe38Smillert 	    else if (strcmp (p, "yes") == 0)
385a6f8fe38Smillert #ifdef AUTH_SERVER_SUPPORT
386a6f8fe38Smillert 		disable_x_prog = 1;
387a6f8fe38Smillert #else
388a6f8fe38Smillert 		;
389a6f8fe38Smillert #endif
390a6f8fe38Smillert 	    else
391a6f8fe38Smillert 	    {
392a6f8fe38Smillert 		error (0, 0, "unrecognized value '%s' for DisableXProg", p);
393a6f8fe38Smillert 		goto error_return;
394a6f8fe38Smillert 	    }
395a6f8fe38Smillert 	}
39678c87a5cStholo 	else if (strcmp (line, "PreservePermissions") == 0)
39778c87a5cStholo 	{
39878c87a5cStholo 	    if (strcmp (p, "no") == 0)
39978c87a5cStholo 		preserve_perms = 0;
40078c87a5cStholo 	    else if (strcmp (p, "yes") == 0)
40178c87a5cStholo 	    {
40278c87a5cStholo #ifdef PRESERVE_PERMISSIONS_SUPPORT
40378c87a5cStholo 		preserve_perms = 1;
40478c87a5cStholo #else
40578c87a5cStholo 		error (0, 0, "\
40678c87a5cStholo warning: this CVS does not support PreservePermissions");
40778c87a5cStholo #endif
40878c87a5cStholo 	    }
40978c87a5cStholo 	    else
41078c87a5cStholo 	    {
41178c87a5cStholo 		error (0, 0, "unrecognized value '%s' for PreservePermissions",
41278c87a5cStholo 		       p);
41378c87a5cStholo 		goto error_return;
41478c87a5cStholo 	    }
41578c87a5cStholo 	}
416eea99451Stholo 	else if (strcmp (line, "TopLevelAdmin") == 0)
417eea99451Stholo 	{
418eea99451Stholo 	    if (strcmp (p, "no") == 0)
419eea99451Stholo 		top_level_admin = 0;
420eea99451Stholo 	    else if (strcmp (p, "yes") == 0)
421eea99451Stholo 		top_level_admin = 1;
422eea99451Stholo 	    else
423eea99451Stholo 	    {
424eea99451Stholo 		error (0, 0, "unrecognized value '%s' for TopLevelAdmin", p);
425eea99451Stholo 		goto error_return;
426eea99451Stholo 	    }
427eea99451Stholo 	}
4289fe7c2c3Stholo 	else if (strcmp (line, "LockDir") == 0)
4299fe7c2c3Stholo 	{
4309fe7c2c3Stholo 	    if (lock_dir != NULL)
4319fe7c2c3Stholo 		free (lock_dir);
4329fe7c2c3Stholo 	    lock_dir = xstrdup (p);
4339fe7c2c3Stholo 	    /* Could try some validity checking, like whether we can
4349fe7c2c3Stholo 	       opendir it or something, but I don't see any particular
4359fe7c2c3Stholo 	       reason to do that now rather than waiting until lock.c.  */
4369fe7c2c3Stholo 	}
437bde78045Stholo 	else if (strcmp (line, "LogHistory") == 0)
438bde78045Stholo 	{
439bde78045Stholo 	    if (strcmp (p, "all") != 0)
440bde78045Stholo 	    {
441bde78045Stholo 		logHistory=malloc(strlen (p) + 1);
442bde78045Stholo 		strcpy (logHistory, p);
443bde78045Stholo 	    }
444bde78045Stholo 	}
4452286d8edStholo 	else
4462286d8edStholo 	{
4472286d8edStholo 	    /* We may be dealing with a keyword which was added in a
4482286d8edStholo 	       subsequent version of CVS.  In that case it is a good idea
4492286d8edStholo 	       to complain, as (1) the keyword might enable a behavior like
4502286d8edStholo 	       alternate locking behavior, in which it is dangerous and hard
4512286d8edStholo 	       to detect if some CVS's have it one way and others have it
4522286d8edStholo 	       the other way, (2) in general, having us not do what the user
4532286d8edStholo 	       had in mind when they put in the keyword violates the
4542286d8edStholo 	       principle of least surprise.  Note that one corollary is
4552286d8edStholo 	       adding new keywords to your CVSROOT/config file is not
4562286d8edStholo 	       particularly recommended unless you are planning on using
4572286d8edStholo 	       the new features.  */
4582286d8edStholo 	    error (0, 0, "%s: unrecognized keyword '%s'",
4592286d8edStholo 		   infopath, line);
4602286d8edStholo 	    goto error_return;
4612286d8edStholo 	}
4622286d8edStholo     }
4632286d8edStholo     if (ferror (fp_info))
4642286d8edStholo     {
4652286d8edStholo 	error (0, errno, "cannot read %s", infopath);
4662286d8edStholo 	goto error_return;
4672286d8edStholo     }
4682286d8edStholo     if (fclose (fp_info) < 0)
4692286d8edStholo     {
4702286d8edStholo 	error (0, errno, "cannot close %s", infopath);
4712286d8edStholo 	goto error_return;
4722286d8edStholo     }
4732286d8edStholo     free (infopath);
4742286d8edStholo     if (line != NULL)
4752286d8edStholo 	free (line);
4762286d8edStholo     return 0;
4772286d8edStholo 
4782286d8edStholo  error_return:
4792286d8edStholo     if (infopath != NULL)
4802286d8edStholo 	free (infopath);
4812286d8edStholo     if (line != NULL)
4822286d8edStholo 	free (line);
4832286d8edStholo     return -1;
4842286d8edStholo }
485