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