1*86d7f5d3SJohn Marino /*
2*86d7f5d3SJohn Marino * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3*86d7f5d3SJohn Marino *
4*86d7f5d3SJohn Marino * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5*86d7f5d3SJohn Marino * and others.
6*86d7f5d3SJohn Marino *
7*86d7f5d3SJohn Marino * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8*86d7f5d3SJohn Marino * Portions Copyright (C) 1989-1992, Brian Berliner
9*86d7f5d3SJohn Marino *
10*86d7f5d3SJohn Marino * You may distribute under the terms of the GNU General Public License as
11*86d7f5d3SJohn Marino * specified in the README file that comes with the CVS source distribution.
12*86d7f5d3SJohn Marino */
13*86d7f5d3SJohn Marino
14*86d7f5d3SJohn Marino #include "cvs.h"
15*86d7f5d3SJohn Marino #include "getline.h"
16*86d7f5d3SJohn Marino #include "history.h"
17*86d7f5d3SJohn Marino
18*86d7f5d3SJohn Marino /*
19*86d7f5d3SJohn Marino * Parse the INFOFILE file for the specified REPOSITORY. Invoke CALLPROC for
20*86d7f5d3SJohn Marino * the first line in the file that matches the REPOSITORY, or if ALL != 0, any
21*86d7f5d3SJohn Marino * lines matching "ALL", or if no lines match, the last line matching
22*86d7f5d3SJohn Marino * "DEFAULT".
23*86d7f5d3SJohn Marino *
24*86d7f5d3SJohn Marino * Return 0 for success, -1 if there was not an INFOFILE, and >0 for failure.
25*86d7f5d3SJohn Marino */
26*86d7f5d3SJohn Marino int
Parse_Info(const char * infofile,const char * repository,CALLPROC callproc,int opt,void * closure)27*86d7f5d3SJohn Marino Parse_Info (const char *infofile, const char *repository, CALLPROC callproc,
28*86d7f5d3SJohn Marino int opt, void *closure)
29*86d7f5d3SJohn Marino {
30*86d7f5d3SJohn Marino int err = 0;
31*86d7f5d3SJohn Marino FILE *fp_info;
32*86d7f5d3SJohn Marino char *infopath;
33*86d7f5d3SJohn Marino char *line = NULL;
34*86d7f5d3SJohn Marino size_t line_allocated = 0;
35*86d7f5d3SJohn Marino char *default_value = NULL;
36*86d7f5d3SJohn Marino int default_line = 0;
37*86d7f5d3SJohn Marino char *expanded_value;
38*86d7f5d3SJohn Marino bool callback_done;
39*86d7f5d3SJohn Marino int line_number;
40*86d7f5d3SJohn Marino char *cp, *exp, *value;
41*86d7f5d3SJohn Marino const char *srepos;
42*86d7f5d3SJohn Marino const char *regex_err;
43*86d7f5d3SJohn Marino
44*86d7f5d3SJohn Marino assert (repository);
45*86d7f5d3SJohn Marino
46*86d7f5d3SJohn Marino if (!current_parsed_root)
47*86d7f5d3SJohn Marino {
48*86d7f5d3SJohn Marino /* XXX - should be error maybe? */
49*86d7f5d3SJohn Marino error (0, 0, "CVSROOT variable not set");
50*86d7f5d3SJohn Marino return 1;
51*86d7f5d3SJohn Marino }
52*86d7f5d3SJohn Marino
53*86d7f5d3SJohn Marino /* find the info file and open it */
54*86d7f5d3SJohn Marino infopath = Xasprintf ("%s/%s/%s", current_parsed_root->directory,
55*86d7f5d3SJohn Marino CVSROOTADM, infofile);
56*86d7f5d3SJohn Marino fp_info = CVS_FOPEN (infopath, "r");
57*86d7f5d3SJohn Marino if (!fp_info)
58*86d7f5d3SJohn Marino {
59*86d7f5d3SJohn Marino /* If no file, don't do anything special. */
60*86d7f5d3SJohn Marino if (!existence_error (errno))
61*86d7f5d3SJohn Marino error (0, errno, "cannot open %s", infopath);
62*86d7f5d3SJohn Marino free (infopath);
63*86d7f5d3SJohn Marino return 0;
64*86d7f5d3SJohn Marino }
65*86d7f5d3SJohn Marino
66*86d7f5d3SJohn Marino /* strip off the CVSROOT if repository was absolute */
67*86d7f5d3SJohn Marino srepos = Short_Repository (repository);
68*86d7f5d3SJohn Marino
69*86d7f5d3SJohn Marino TRACE (TRACE_FUNCTION, "Parse_Info (%s, %s, %s)",
70*86d7f5d3SJohn Marino infopath, srepos, (opt & PIOPT_ALL) ? "ALL" : "not ALL");
71*86d7f5d3SJohn Marino
72*86d7f5d3SJohn Marino /* search the info file for lines that match */
73*86d7f5d3SJohn Marino callback_done = false;
74*86d7f5d3SJohn Marino line_number = 0;
75*86d7f5d3SJohn Marino while (getline (&line, &line_allocated, fp_info) >= 0)
76*86d7f5d3SJohn Marino {
77*86d7f5d3SJohn Marino line_number++;
78*86d7f5d3SJohn Marino
79*86d7f5d3SJohn Marino /* skip lines starting with # */
80*86d7f5d3SJohn Marino if (line[0] == '#')
81*86d7f5d3SJohn Marino continue;
82*86d7f5d3SJohn Marino
83*86d7f5d3SJohn Marino /* skip whitespace at beginning of line */
84*86d7f5d3SJohn Marino for (cp = line; *cp && isspace ((unsigned char) *cp); cp++)
85*86d7f5d3SJohn Marino ;
86*86d7f5d3SJohn Marino
87*86d7f5d3SJohn Marino /* if *cp is null, the whole line was blank */
88*86d7f5d3SJohn Marino if (*cp == '\0')
89*86d7f5d3SJohn Marino continue;
90*86d7f5d3SJohn Marino
91*86d7f5d3SJohn Marino /* the regular expression is everything up to the first space */
92*86d7f5d3SJohn Marino for (exp = cp; *cp && !isspace ((unsigned char) *cp); cp++)
93*86d7f5d3SJohn Marino ;
94*86d7f5d3SJohn Marino if (*cp != '\0')
95*86d7f5d3SJohn Marino *cp++ = '\0';
96*86d7f5d3SJohn Marino
97*86d7f5d3SJohn Marino /* skip whitespace up to the start of the matching value */
98*86d7f5d3SJohn Marino while (*cp && isspace ((unsigned char) *cp))
99*86d7f5d3SJohn Marino cp++;
100*86d7f5d3SJohn Marino
101*86d7f5d3SJohn Marino /* no value to match with the regular expression is an error */
102*86d7f5d3SJohn Marino if (*cp == '\0')
103*86d7f5d3SJohn Marino {
104*86d7f5d3SJohn Marino error (0, 0, "syntax error at line %d file %s; ignored",
105*86d7f5d3SJohn Marino line_number, infopath);
106*86d7f5d3SJohn Marino continue;
107*86d7f5d3SJohn Marino }
108*86d7f5d3SJohn Marino value = cp;
109*86d7f5d3SJohn Marino
110*86d7f5d3SJohn Marino /* strip the newline off the end of the value */
111*86d7f5d3SJohn Marino cp = strrchr (value, '\n');
112*86d7f5d3SJohn Marino if (cp) *cp = '\0';
113*86d7f5d3SJohn Marino
114*86d7f5d3SJohn Marino /*
115*86d7f5d3SJohn Marino * At this point, exp points to the regular expression, and value
116*86d7f5d3SJohn Marino * points to the value to call the callback routine with. Evaluate
117*86d7f5d3SJohn Marino * the regular expression against srepos and callback with the value
118*86d7f5d3SJohn Marino * if it matches.
119*86d7f5d3SJohn Marino */
120*86d7f5d3SJohn Marino
121*86d7f5d3SJohn Marino /* save the default value so we have it later if we need it */
122*86d7f5d3SJohn Marino if (strcmp (exp, "DEFAULT") == 0)
123*86d7f5d3SJohn Marino {
124*86d7f5d3SJohn Marino if (default_value)
125*86d7f5d3SJohn Marino {
126*86d7f5d3SJohn Marino error (0, 0, "Multiple `DEFAULT' lines (%d and %d) in %s file",
127*86d7f5d3SJohn Marino default_line, line_number, infofile);
128*86d7f5d3SJohn Marino free (default_value);
129*86d7f5d3SJohn Marino }
130*86d7f5d3SJohn Marino default_value = xstrdup (value);
131*86d7f5d3SJohn Marino default_line = line_number;
132*86d7f5d3SJohn Marino continue;
133*86d7f5d3SJohn Marino }
134*86d7f5d3SJohn Marino
135*86d7f5d3SJohn Marino /*
136*86d7f5d3SJohn Marino * For a regular expression of "ALL", do the callback always We may
137*86d7f5d3SJohn Marino * execute lots of ALL callbacks in addition to *one* regular matching
138*86d7f5d3SJohn Marino * callback or default
139*86d7f5d3SJohn Marino */
140*86d7f5d3SJohn Marino if (strcmp (exp, "ALL") == 0)
141*86d7f5d3SJohn Marino {
142*86d7f5d3SJohn Marino if (!(opt & PIOPT_ALL))
143*86d7f5d3SJohn Marino error (0, 0, "Keyword `ALL' is ignored at line %d in %s file",
144*86d7f5d3SJohn Marino line_number, infofile);
145*86d7f5d3SJohn Marino else if ((expanded_value =
146*86d7f5d3SJohn Marino expand_path (value, current_parsed_root->directory,
147*86d7f5d3SJohn Marino true, infofile, line_number)))
148*86d7f5d3SJohn Marino {
149*86d7f5d3SJohn Marino err += callproc (repository, expanded_value, closure);
150*86d7f5d3SJohn Marino free (expanded_value);
151*86d7f5d3SJohn Marino }
152*86d7f5d3SJohn Marino else
153*86d7f5d3SJohn Marino err++;
154*86d7f5d3SJohn Marino continue;
155*86d7f5d3SJohn Marino }
156*86d7f5d3SJohn Marino
157*86d7f5d3SJohn Marino if (callback_done)
158*86d7f5d3SJohn Marino /* only first matching, plus "ALL"'s */
159*86d7f5d3SJohn Marino continue;
160*86d7f5d3SJohn Marino
161*86d7f5d3SJohn Marino /* see if the repository matched this regular expression */
162*86d7f5d3SJohn Marino regex_err = re_comp (exp);
163*86d7f5d3SJohn Marino if (regex_err)
164*86d7f5d3SJohn Marino {
165*86d7f5d3SJohn Marino error (0, 0, "bad regular expression at line %d file %s: %s",
166*86d7f5d3SJohn Marino line_number, infofile, regex_err);
167*86d7f5d3SJohn Marino continue;
168*86d7f5d3SJohn Marino }
169*86d7f5d3SJohn Marino if (re_exec (srepos) == 0)
170*86d7f5d3SJohn Marino continue; /* no match */
171*86d7f5d3SJohn Marino
172*86d7f5d3SJohn Marino /* it did, so do the callback and note that we did one */
173*86d7f5d3SJohn Marino expanded_value = expand_path (value, current_parsed_root->directory,
174*86d7f5d3SJohn Marino true, infofile, line_number);
175*86d7f5d3SJohn Marino if (expanded_value)
176*86d7f5d3SJohn Marino {
177*86d7f5d3SJohn Marino err += callproc (repository, expanded_value, closure);
178*86d7f5d3SJohn Marino free (expanded_value);
179*86d7f5d3SJohn Marino }
180*86d7f5d3SJohn Marino else
181*86d7f5d3SJohn Marino err++;
182*86d7f5d3SJohn Marino callback_done = true;
183*86d7f5d3SJohn Marino }
184*86d7f5d3SJohn Marino if (ferror (fp_info))
185*86d7f5d3SJohn Marino error (0, errno, "cannot read %s", infopath);
186*86d7f5d3SJohn Marino if (fclose (fp_info) < 0)
187*86d7f5d3SJohn Marino error (0, errno, "cannot close %s", infopath);
188*86d7f5d3SJohn Marino
189*86d7f5d3SJohn Marino /* if we fell through and didn't callback at all, do the default */
190*86d7f5d3SJohn Marino if (!callback_done && default_value)
191*86d7f5d3SJohn Marino {
192*86d7f5d3SJohn Marino expanded_value = expand_path (default_value,
193*86d7f5d3SJohn Marino current_parsed_root->directory,
194*86d7f5d3SJohn Marino true, infofile, line_number);
195*86d7f5d3SJohn Marino if (expanded_value)
196*86d7f5d3SJohn Marino {
197*86d7f5d3SJohn Marino err += callproc (repository, expanded_value, closure);
198*86d7f5d3SJohn Marino free (expanded_value);
199*86d7f5d3SJohn Marino }
200*86d7f5d3SJohn Marino else
201*86d7f5d3SJohn Marino err++;
202*86d7f5d3SJohn Marino }
203*86d7f5d3SJohn Marino
204*86d7f5d3SJohn Marino /* free up space if necessary */
205*86d7f5d3SJohn Marino if (default_value) free (default_value);
206*86d7f5d3SJohn Marino free (infopath);
207*86d7f5d3SJohn Marino if (line) free (line);
208*86d7f5d3SJohn Marino
209*86d7f5d3SJohn Marino return err;
210*86d7f5d3SJohn Marino }
211*86d7f5d3SJohn Marino
212*86d7f5d3SJohn Marino
213*86d7f5d3SJohn Marino
214*86d7f5d3SJohn Marino /* Print a warning and return false if P doesn't look like a string specifying
215*86d7f5d3SJohn Marino * something that can be converted into a size_t.
216*86d7f5d3SJohn Marino *
217*86d7f5d3SJohn Marino * Sets *VAL to the parsed value when it is found to be valid. *VAL will not
218*86d7f5d3SJohn Marino * be altered when false is returned.
219*86d7f5d3SJohn Marino */
220*86d7f5d3SJohn Marino static bool
readSizeT(const char * infopath,const char * option,const char * p,size_t * val)221*86d7f5d3SJohn Marino readSizeT (const char *infopath, const char *option, const char *p,
222*86d7f5d3SJohn Marino size_t *val)
223*86d7f5d3SJohn Marino {
224*86d7f5d3SJohn Marino const char *q;
225*86d7f5d3SJohn Marino size_t num, factor = 1;
226*86d7f5d3SJohn Marino
227*86d7f5d3SJohn Marino if (!strcasecmp ("unlimited", p))
228*86d7f5d3SJohn Marino {
229*86d7f5d3SJohn Marino *val = SIZE_MAX;
230*86d7f5d3SJohn Marino return true;
231*86d7f5d3SJohn Marino }
232*86d7f5d3SJohn Marino
233*86d7f5d3SJohn Marino /* Record the factor character (kilo, mega, giga, tera). */
234*86d7f5d3SJohn Marino if (!isdigit (p[strlen(p) - 1]))
235*86d7f5d3SJohn Marino {
236*86d7f5d3SJohn Marino switch (p[strlen(p) - 1])
237*86d7f5d3SJohn Marino {
238*86d7f5d3SJohn Marino case 'T':
239*86d7f5d3SJohn Marino factor = xtimes (factor, 1024);
240*86d7f5d3SJohn Marino case 'G':
241*86d7f5d3SJohn Marino factor = xtimes (factor, 1024);
242*86d7f5d3SJohn Marino case 'M':
243*86d7f5d3SJohn Marino factor = xtimes (factor, 1024);
244*86d7f5d3SJohn Marino case 'k':
245*86d7f5d3SJohn Marino factor = xtimes (factor, 1024);
246*86d7f5d3SJohn Marino break;
247*86d7f5d3SJohn Marino default:
248*86d7f5d3SJohn Marino error (0, 0,
249*86d7f5d3SJohn Marino "%s: Unknown %s factor: `%c'",
250*86d7f5d3SJohn Marino infopath, option, p[strlen(p)]);
251*86d7f5d3SJohn Marino return false;
252*86d7f5d3SJohn Marino }
253*86d7f5d3SJohn Marino TRACE (TRACE_DATA, "readSizeT(): Found factor %u for %s",
254*86d7f5d3SJohn Marino factor, option);
255*86d7f5d3SJohn Marino }
256*86d7f5d3SJohn Marino
257*86d7f5d3SJohn Marino /* Verify that *q is a number. */
258*86d7f5d3SJohn Marino q = p;
259*86d7f5d3SJohn Marino while (q < p + strlen(p) - 1 /* Checked last character above. */)
260*86d7f5d3SJohn Marino {
261*86d7f5d3SJohn Marino if (!isdigit(*q))
262*86d7f5d3SJohn Marino {
263*86d7f5d3SJohn Marino error (0, 0,
264*86d7f5d3SJohn Marino "%s: %s must be a postitive integer, not '%s'",
265*86d7f5d3SJohn Marino infopath, option, p);
266*86d7f5d3SJohn Marino return false;
267*86d7f5d3SJohn Marino }
268*86d7f5d3SJohn Marino q++;
269*86d7f5d3SJohn Marino }
270*86d7f5d3SJohn Marino
271*86d7f5d3SJohn Marino /* Compute final value. */
272*86d7f5d3SJohn Marino num = strtoul (p, NULL, 10);
273*86d7f5d3SJohn Marino if (num == ULONG_MAX || num > SIZE_MAX)
274*86d7f5d3SJohn Marino /* Don't return an error, just max out. */
275*86d7f5d3SJohn Marino num = SIZE_MAX;
276*86d7f5d3SJohn Marino
277*86d7f5d3SJohn Marino TRACE (TRACE_DATA, "readSizeT(): read number %u for %s", num, option);
278*86d7f5d3SJohn Marino *val = xtimes (strtoul (p, NULL, 10), factor);
279*86d7f5d3SJohn Marino TRACE (TRACE_DATA, "readSizeT(): returnning %u for %s", *val, option);
280*86d7f5d3SJohn Marino return true;
281*86d7f5d3SJohn Marino }
282*86d7f5d3SJohn Marino
283*86d7f5d3SJohn Marino
284*86d7f5d3SJohn Marino
285*86d7f5d3SJohn Marino /* Allocate and initialize a new config struct. */
286*86d7f5d3SJohn Marino static inline struct config *
new_config(void)287*86d7f5d3SJohn Marino new_config (void)
288*86d7f5d3SJohn Marino {
289*86d7f5d3SJohn Marino struct config *new = xcalloc (1, sizeof (struct config));
290*86d7f5d3SJohn Marino
291*86d7f5d3SJohn Marino TRACE (TRACE_FLOW, "new_config ()");
292*86d7f5d3SJohn Marino
293*86d7f5d3SJohn Marino new->logHistory = xstrdup (ALL_HISTORY_REC_TYPES);
294*86d7f5d3SJohn Marino new->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
295*86d7f5d3SJohn Marino new->UserAdminOptions = xstrdup ("k");
296*86d7f5d3SJohn Marino new->MaxCommentLeaderLength = 20;
297*86d7f5d3SJohn Marino #ifdef SERVER_SUPPORT
298*86d7f5d3SJohn Marino new->MaxCompressionLevel = 9;
299*86d7f5d3SJohn Marino #endif /* SERVER_SUPPORT */
300*86d7f5d3SJohn Marino #ifdef PROXY_SUPPORT
301*86d7f5d3SJohn Marino new->MaxProxyBufferSize = (size_t)(8 * 1024 * 1024); /* 8 megabytes,
302*86d7f5d3SJohn Marino * by default.
303*86d7f5d3SJohn Marino */
304*86d7f5d3SJohn Marino #endif /* PROXY_SUPPORT */
305*86d7f5d3SJohn Marino #ifdef AUTH_SERVER_SUPPORT
306*86d7f5d3SJohn Marino new->system_auth = true;
307*86d7f5d3SJohn Marino #endif /* AUTH_SERVER_SUPPORT */
308*86d7f5d3SJohn Marino
309*86d7f5d3SJohn Marino return new;
310*86d7f5d3SJohn Marino }
311*86d7f5d3SJohn Marino
312*86d7f5d3SJohn Marino
313*86d7f5d3SJohn Marino
314*86d7f5d3SJohn Marino void
free_config(struct config * data)315*86d7f5d3SJohn Marino free_config (struct config *data)
316*86d7f5d3SJohn Marino {
317*86d7f5d3SJohn Marino if (data->keywords) free_keywords (data->keywords);
318*86d7f5d3SJohn Marino free (data);
319*86d7f5d3SJohn Marino }
320*86d7f5d3SJohn Marino
321*86d7f5d3SJohn Marino
322*86d7f5d3SJohn Marino
323*86d7f5d3SJohn Marino /* Return true if this function has already been called for line LN of file
324*86d7f5d3SJohn Marino * INFOPATH.
325*86d7f5d3SJohn Marino */
326*86d7f5d3SJohn Marino bool
parse_error(const char * infopath,unsigned int ln)327*86d7f5d3SJohn Marino parse_error (const char *infopath, unsigned int ln)
328*86d7f5d3SJohn Marino {
329*86d7f5d3SJohn Marino static List *errors = NULL;
330*86d7f5d3SJohn Marino char *nodename = NULL;
331*86d7f5d3SJohn Marino
332*86d7f5d3SJohn Marino if (!errors)
333*86d7f5d3SJohn Marino errors = getlist();
334*86d7f5d3SJohn Marino
335*86d7f5d3SJohn Marino nodename = Xasprintf ("%s/%u", infopath, ln);
336*86d7f5d3SJohn Marino if (findnode (errors, nodename))
337*86d7f5d3SJohn Marino {
338*86d7f5d3SJohn Marino free (nodename);
339*86d7f5d3SJohn Marino return true;
340*86d7f5d3SJohn Marino }
341*86d7f5d3SJohn Marino
342*86d7f5d3SJohn Marino push_string (errors, nodename);
343*86d7f5d3SJohn Marino return false;
344*86d7f5d3SJohn Marino }
345*86d7f5d3SJohn Marino
346*86d7f5d3SJohn Marino
347*86d7f5d3SJohn Marino
348*86d7f5d3SJohn Marino #ifdef ALLOW_CONFIG_OVERRIDE
349*86d7f5d3SJohn Marino const char * const allowed_config_prefixes[] = { ALLOW_CONFIG_OVERRIDE };
350*86d7f5d3SJohn Marino #endif /* ALLOW_CONFIG_OVERRIDE */
351*86d7f5d3SJohn Marino
352*86d7f5d3SJohn Marino
353*86d7f5d3SJohn Marino
354*86d7f5d3SJohn Marino /* Parse the CVS config file. The syntax right now is a bit ad hoc
355*86d7f5d3SJohn Marino * but tries to draw on the best or more common features of the other
356*86d7f5d3SJohn Marino * *info files and various unix (or non-unix) config file syntaxes.
357*86d7f5d3SJohn Marino * Lines starting with # are comments. Settings are lines of the form
358*86d7f5d3SJohn Marino * KEYWORD=VALUE. There is currently no way to have a multi-line
359*86d7f5d3SJohn Marino * VALUE (would be nice if there was, probably).
360*86d7f5d3SJohn Marino *
361*86d7f5d3SJohn Marino * CVSROOT is the $CVSROOT directory
362*86d7f5d3SJohn Marino * (current_parsed_root->directory might not be set yet, so this
363*86d7f5d3SJohn Marino * function takes the cvsroot as a function argument).
364*86d7f5d3SJohn Marino *
365*86d7f5d3SJohn Marino * RETURNS
366*86d7f5d3SJohn Marino * Always returns a fully initialized config struct, which on error may
367*86d7f5d3SJohn Marino * contain only the defaults.
368*86d7f5d3SJohn Marino *
369*86d7f5d3SJohn Marino * ERRORS
370*86d7f5d3SJohn Marino * Calls error(0, ...) on errors in addition to the return value.
371*86d7f5d3SJohn Marino *
372*86d7f5d3SJohn Marino * xmalloc() failures are fatal, per usual.
373*86d7f5d3SJohn Marino */
374*86d7f5d3SJohn Marino struct config *
parse_config(const char * cvsroot,const char * path)375*86d7f5d3SJohn Marino parse_config (const char *cvsroot, const char *path)
376*86d7f5d3SJohn Marino {
377*86d7f5d3SJohn Marino const char *infopath;
378*86d7f5d3SJohn Marino char *freeinfopath = NULL;
379*86d7f5d3SJohn Marino FILE *fp_info;
380*86d7f5d3SJohn Marino char *line = NULL;
381*86d7f5d3SJohn Marino unsigned int ln; /* Input file line counter. */
382*86d7f5d3SJohn Marino char *buf = NULL;
383*86d7f5d3SJohn Marino size_t buf_allocated = 0;
384*86d7f5d3SJohn Marino size_t len;
385*86d7f5d3SJohn Marino char *p;
386*86d7f5d3SJohn Marino struct config *retval;
387*86d7f5d3SJohn Marino int rescan = 0;
388*86d7f5d3SJohn Marino /* PROCESSING Whether config keys are currently being processed for
389*86d7f5d3SJohn Marino * this root.
390*86d7f5d3SJohn Marino * PROCESSED Whether any keys have been processed for this root.
391*86d7f5d3SJohn Marino * This is initialized to true so that any initial keys
392*86d7f5d3SJohn Marino * may be processed as global defaults.
393*86d7f5d3SJohn Marino */
394*86d7f5d3SJohn Marino bool processing = true;
395*86d7f5d3SJohn Marino bool processed = true;
396*86d7f5d3SJohn Marino
397*86d7f5d3SJohn Marino TRACE (TRACE_FUNCTION, "parse_config (%s)", cvsroot);
398*86d7f5d3SJohn Marino
399*86d7f5d3SJohn Marino #ifdef ALLOW_CONFIG_OVERRIDE
400*86d7f5d3SJohn Marino if (path)
401*86d7f5d3SJohn Marino {
402*86d7f5d3SJohn Marino const char * const *prefix;
403*86d7f5d3SJohn Marino char *npath = xcanonicalize_file_name (path);
404*86d7f5d3SJohn Marino bool approved = false;
405*86d7f5d3SJohn Marino for (prefix = allowed_config_prefixes; *prefix != NULL; prefix++)
406*86d7f5d3SJohn Marino {
407*86d7f5d3SJohn Marino char *nprefix;
408*86d7f5d3SJohn Marino
409*86d7f5d3SJohn Marino if (!isreadable (*prefix)) continue;
410*86d7f5d3SJohn Marino nprefix = xcanonicalize_file_name (*prefix);
411*86d7f5d3SJohn Marino if (!strncmp (nprefix, npath, strlen (nprefix))
412*86d7f5d3SJohn Marino && (((*prefix)[strlen (*prefix)] != '/'
413*86d7f5d3SJohn Marino && strlen (npath) == strlen (nprefix))
414*86d7f5d3SJohn Marino || ((*prefix)[strlen (*prefix)] == '/'
415*86d7f5d3SJohn Marino && npath[strlen (nprefix)] == '/')))
416*86d7f5d3SJohn Marino approved = true;
417*86d7f5d3SJohn Marino free (nprefix);
418*86d7f5d3SJohn Marino if (approved) break;
419*86d7f5d3SJohn Marino }
420*86d7f5d3SJohn Marino if (!approved)
421*86d7f5d3SJohn Marino error (1, 0, "Invalid path to config file specified: `%s'",
422*86d7f5d3SJohn Marino path);
423*86d7f5d3SJohn Marino infopath = path;
424*86d7f5d3SJohn Marino free (npath);
425*86d7f5d3SJohn Marino }
426*86d7f5d3SJohn Marino else
427*86d7f5d3SJohn Marino #endif
428*86d7f5d3SJohn Marino infopath = freeinfopath =
429*86d7f5d3SJohn Marino Xasprintf ("%s/%s/%s", cvsroot, CVSROOTADM, CVSROOTADM_CONFIG);
430*86d7f5d3SJohn Marino
431*86d7f5d3SJohn Marino retval = new_config ();
432*86d7f5d3SJohn Marino
433*86d7f5d3SJohn Marino again:
434*86d7f5d3SJohn Marino fp_info = CVS_FOPEN (infopath, "r");
435*86d7f5d3SJohn Marino if (!fp_info)
436*86d7f5d3SJohn Marino {
437*86d7f5d3SJohn Marino /* If no file, don't do anything special. */
438*86d7f5d3SJohn Marino if (!existence_error (errno))
439*86d7f5d3SJohn Marino {
440*86d7f5d3SJohn Marino /* Just a warning message; doesn't affect return
441*86d7f5d3SJohn Marino value, currently at least. */
442*86d7f5d3SJohn Marino error (0, errno, "cannot open %s", infopath);
443*86d7f5d3SJohn Marino }
444*86d7f5d3SJohn Marino if (freeinfopath) free (freeinfopath);
445*86d7f5d3SJohn Marino return retval;
446*86d7f5d3SJohn Marino }
447*86d7f5d3SJohn Marino
448*86d7f5d3SJohn Marino ln = 0; /* Have not read any lines yet. */
449*86d7f5d3SJohn Marino while (getline (&buf, &buf_allocated, fp_info) >= 0)
450*86d7f5d3SJohn Marino {
451*86d7f5d3SJohn Marino ln++; /* Keep track of input file line number for error messages. */
452*86d7f5d3SJohn Marino
453*86d7f5d3SJohn Marino line = buf;
454*86d7f5d3SJohn Marino
455*86d7f5d3SJohn Marino /* Skip leading white space. */
456*86d7f5d3SJohn Marino while (isspace (*line)) line++;
457*86d7f5d3SJohn Marino
458*86d7f5d3SJohn Marino /* Skip comments. */
459*86d7f5d3SJohn Marino if (line[0] == '#')
460*86d7f5d3SJohn Marino continue;
461*86d7f5d3SJohn Marino
462*86d7f5d3SJohn Marino /* Is there any kind of written standard for the syntax of this
463*86d7f5d3SJohn Marino sort of config file? Anywhere in POSIX for example (I guess
464*86d7f5d3SJohn Marino makefiles are sort of close)? Red Hat Linux has a bunch of
465*86d7f5d3SJohn Marino these too (with some GUI tools which edit them)...
466*86d7f5d3SJohn Marino
467*86d7f5d3SJohn Marino Along the same lines, we might want a table of keywords,
468*86d7f5d3SJohn Marino with various types (boolean, string, &c), as a mechanism
469*86d7f5d3SJohn Marino for making sure the syntax is consistent. Any good examples
470*86d7f5d3SJohn Marino to follow there (Apache?)? */
471*86d7f5d3SJohn Marino
472*86d7f5d3SJohn Marino /* Strip the trailing newline. There will be one unless we
473*86d7f5d3SJohn Marino read a partial line without a newline, and then got end of
474*86d7f5d3SJohn Marino file (or error?). */
475*86d7f5d3SJohn Marino
476*86d7f5d3SJohn Marino len = strlen (line) - 1;
477*86d7f5d3SJohn Marino if (line[len] == '\n')
478*86d7f5d3SJohn Marino line[len--] = '\0';
479*86d7f5d3SJohn Marino
480*86d7f5d3SJohn Marino /* Skip blank lines. */
481*86d7f5d3SJohn Marino if (line[0] == '\0')
482*86d7f5d3SJohn Marino continue;
483*86d7f5d3SJohn Marino
484*86d7f5d3SJohn Marino TRACE (TRACE_DATA, "parse_info() examining line: `%s'", line);
485*86d7f5d3SJohn Marino
486*86d7f5d3SJohn Marino /* Check for a root specification. */
487*86d7f5d3SJohn Marino if (line[0] == '[' && line[len] == ']')
488*86d7f5d3SJohn Marino {
489*86d7f5d3SJohn Marino cvsroot_t *tmproot;
490*86d7f5d3SJohn Marino
491*86d7f5d3SJohn Marino line++[len] = '\0';
492*86d7f5d3SJohn Marino tmproot = parse_cvsroot (line);
493*86d7f5d3SJohn Marino
494*86d7f5d3SJohn Marino /* Ignoring method. */
495*86d7f5d3SJohn Marino if (!tmproot
496*86d7f5d3SJohn Marino #if defined CLIENT_SUPPORT || defined SERVER_SUPPORT
497*86d7f5d3SJohn Marino || (tmproot->method != local_method
498*86d7f5d3SJohn Marino && (!tmproot->hostname || !isThisHost (tmproot->hostname)))
499*86d7f5d3SJohn Marino #endif /* CLIENT_SUPPORT || SERVER_SUPPORT */
500*86d7f5d3SJohn Marino || !isSamePath (tmproot->directory, cvsroot))
501*86d7f5d3SJohn Marino {
502*86d7f5d3SJohn Marino if (processed) processing = false;
503*86d7f5d3SJohn Marino }
504*86d7f5d3SJohn Marino else
505*86d7f5d3SJohn Marino {
506*86d7f5d3SJohn Marino TRACE (TRACE_FLOW, "Matched root section`%s'", line);
507*86d7f5d3SJohn Marino processing = true;
508*86d7f5d3SJohn Marino processed = false;
509*86d7f5d3SJohn Marino }
510*86d7f5d3SJohn Marino
511*86d7f5d3SJohn Marino continue;
512*86d7f5d3SJohn Marino }
513*86d7f5d3SJohn Marino
514*86d7f5d3SJohn Marino /* There is data on this line. */
515*86d7f5d3SJohn Marino
516*86d7f5d3SJohn Marino /* Even if the data is bad or ignored, consider data processed for
517*86d7f5d3SJohn Marino * this root.
518*86d7f5d3SJohn Marino */
519*86d7f5d3SJohn Marino processed = true;
520*86d7f5d3SJohn Marino
521*86d7f5d3SJohn Marino if (!processing)
522*86d7f5d3SJohn Marino /* ...but it is for a different root. */
523*86d7f5d3SJohn Marino continue;
524*86d7f5d3SJohn Marino
525*86d7f5d3SJohn Marino /* The first '=' separates keyword from value. */
526*86d7f5d3SJohn Marino p = strchr (line, '=');
527*86d7f5d3SJohn Marino if (!p)
528*86d7f5d3SJohn Marino {
529*86d7f5d3SJohn Marino if (!parse_error (infopath, ln))
530*86d7f5d3SJohn Marino error (0, 0,
531*86d7f5d3SJohn Marino "%s [%d]: syntax error: missing `=' between keyword and value",
532*86d7f5d3SJohn Marino infopath, ln);
533*86d7f5d3SJohn Marino continue;
534*86d7f5d3SJohn Marino }
535*86d7f5d3SJohn Marino
536*86d7f5d3SJohn Marino *p++ = '\0';
537*86d7f5d3SJohn Marino
538*86d7f5d3SJohn Marino if (strcmp (line, "RCSBIN") == 0)
539*86d7f5d3SJohn Marino {
540*86d7f5d3SJohn Marino /* This option used to specify the directory for RCS
541*86d7f5d3SJohn Marino executables. But since we don't run them any more,
542*86d7f5d3SJohn Marino this is a noop. Silently ignore it so that a
543*86d7f5d3SJohn Marino repository can work with either new or old CVS. */
544*86d7f5d3SJohn Marino ;
545*86d7f5d3SJohn Marino }
546*86d7f5d3SJohn Marino else if (strcmp (line, "SystemAuth") == 0)
547*86d7f5d3SJohn Marino #ifdef AUTH_SERVER_SUPPORT
548*86d7f5d3SJohn Marino readBool (infopath, "SystemAuth", p, &retval->system_auth);
549*86d7f5d3SJohn Marino #else
550*86d7f5d3SJohn Marino {
551*86d7f5d3SJohn Marino /* Still parse the syntax but ignore the option. That way the same
552*86d7f5d3SJohn Marino * config file can be used for local and server.
553*86d7f5d3SJohn Marino */
554*86d7f5d3SJohn Marino bool dummy;
555*86d7f5d3SJohn Marino readBool (infopath, "SystemAuth", p, &dummy);
556*86d7f5d3SJohn Marino }
557*86d7f5d3SJohn Marino #endif
558*86d7f5d3SJohn Marino else if (strcmp (line, "LocalKeyword") == 0 ||
559*86d7f5d3SJohn Marino strcmp (line, "tag") == 0)
560*86d7f5d3SJohn Marino RCS_setlocalid (infopath, ln, &retval->keywords, p);
561*86d7f5d3SJohn Marino else if (strcmp (line, "KeywordExpand") == 0 ||
562*86d7f5d3SJohn Marino strcmp (line, "tagexpand") == 0)
563*86d7f5d3SJohn Marino RCS_setincexc (&retval->keywords, p);
564*86d7f5d3SJohn Marino else if (strcmp (line, "PreservePermissions") == 0)
565*86d7f5d3SJohn Marino {
566*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
567*86d7f5d3SJohn Marino readBool (infopath, "PreservePermissions", p,
568*86d7f5d3SJohn Marino &retval->preserve_perms);
569*86d7f5d3SJohn Marino #else
570*86d7f5d3SJohn Marino if (!parse_error (infopath, ln))
571*86d7f5d3SJohn Marino error (0, 0, "\
572*86d7f5d3SJohn Marino %s [%u]: warning: this CVS does not support PreservePermissions",
573*86d7f5d3SJohn Marino infopath, ln);
574*86d7f5d3SJohn Marino #endif
575*86d7f5d3SJohn Marino }
576*86d7f5d3SJohn Marino else if (strcmp (line, "TopLevelAdmin") == 0)
577*86d7f5d3SJohn Marino readBool (infopath, "TopLevelAdmin", p, &retval->top_level_admin);
578*86d7f5d3SJohn Marino else if (strcmp (line, "LockDir") == 0)
579*86d7f5d3SJohn Marino {
580*86d7f5d3SJohn Marino if (retval->lock_dir)
581*86d7f5d3SJohn Marino free (retval->lock_dir);
582*86d7f5d3SJohn Marino retval->lock_dir = expand_path (p, cvsroot, false, infopath, ln);
583*86d7f5d3SJohn Marino /* Could try some validity checking, like whether we can
584*86d7f5d3SJohn Marino opendir it or something, but I don't see any particular
585*86d7f5d3SJohn Marino reason to do that now rather than waiting until lock.c. */
586*86d7f5d3SJohn Marino }
587*86d7f5d3SJohn Marino else if (strcmp (line, "HistoryLogPath") == 0)
588*86d7f5d3SJohn Marino {
589*86d7f5d3SJohn Marino if (retval->HistoryLogPath) free (retval->HistoryLogPath);
590*86d7f5d3SJohn Marino
591*86d7f5d3SJohn Marino /* Expand ~ & $VARs. */
592*86d7f5d3SJohn Marino retval->HistoryLogPath = expand_path (p, cvsroot, false,
593*86d7f5d3SJohn Marino infopath, ln);
594*86d7f5d3SJohn Marino
595*86d7f5d3SJohn Marino if (retval->HistoryLogPath && !ISABSOLUTE (retval->HistoryLogPath))
596*86d7f5d3SJohn Marino {
597*86d7f5d3SJohn Marino error (0, 0, "%s [%u]: HistoryLogPath must be absolute.",
598*86d7f5d3SJohn Marino infopath, ln);
599*86d7f5d3SJohn Marino free (retval->HistoryLogPath);
600*86d7f5d3SJohn Marino retval->HistoryLogPath = NULL;
601*86d7f5d3SJohn Marino }
602*86d7f5d3SJohn Marino }
603*86d7f5d3SJohn Marino else if (strcmp (line, "HistorySearchPath") == 0)
604*86d7f5d3SJohn Marino {
605*86d7f5d3SJohn Marino if (retval->HistorySearchPath) free (retval->HistorySearchPath);
606*86d7f5d3SJohn Marino retval->HistorySearchPath = expand_path (p, cvsroot, false,
607*86d7f5d3SJohn Marino infopath, ln);
608*86d7f5d3SJohn Marino
609*86d7f5d3SJohn Marino if (retval->HistorySearchPath
610*86d7f5d3SJohn Marino && !ISABSOLUTE (retval->HistorySearchPath))
611*86d7f5d3SJohn Marino {
612*86d7f5d3SJohn Marino error (0, 0, "%s [%u]: HistorySearchPath must be absolute.",
613*86d7f5d3SJohn Marino infopath, ln);
614*86d7f5d3SJohn Marino free (retval->HistorySearchPath);
615*86d7f5d3SJohn Marino retval->HistorySearchPath = NULL;
616*86d7f5d3SJohn Marino }
617*86d7f5d3SJohn Marino }
618*86d7f5d3SJohn Marino else if (strcmp (line, "LogHistory") == 0)
619*86d7f5d3SJohn Marino {
620*86d7f5d3SJohn Marino if (strcmp (p, "all") != 0)
621*86d7f5d3SJohn Marino {
622*86d7f5d3SJohn Marino static bool gotone = false;
623*86d7f5d3SJohn Marino if (gotone)
624*86d7f5d3SJohn Marino error (0, 0, "\
625*86d7f5d3SJohn Marino %s [%u]: warning: duplicate LogHistory entry found.",
626*86d7f5d3SJohn Marino infopath, ln);
627*86d7f5d3SJohn Marino else
628*86d7f5d3SJohn Marino gotone = true;
629*86d7f5d3SJohn Marino free (retval->logHistory);
630*86d7f5d3SJohn Marino retval->logHistory = xstrdup (p);
631*86d7f5d3SJohn Marino }
632*86d7f5d3SJohn Marino }
633*86d7f5d3SJohn Marino else if (strcmp (line, "RereadLogAfterVerify") == 0)
634*86d7f5d3SJohn Marino {
635*86d7f5d3SJohn Marino if (!strcasecmp (p, "never"))
636*86d7f5d3SJohn Marino retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER;
637*86d7f5d3SJohn Marino else if (!strcasecmp (p, "always"))
638*86d7f5d3SJohn Marino retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
639*86d7f5d3SJohn Marino else if (!strcasecmp (p, "stat"))
640*86d7f5d3SJohn Marino retval->RereadLogAfterVerify = LOGMSG_REREAD_STAT;
641*86d7f5d3SJohn Marino else
642*86d7f5d3SJohn Marino {
643*86d7f5d3SJohn Marino bool tmp;
644*86d7f5d3SJohn Marino if (readBool (infopath, "RereadLogAfterVerify", p, &tmp))
645*86d7f5d3SJohn Marino {
646*86d7f5d3SJohn Marino if (tmp)
647*86d7f5d3SJohn Marino retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
648*86d7f5d3SJohn Marino else
649*86d7f5d3SJohn Marino retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER;
650*86d7f5d3SJohn Marino }
651*86d7f5d3SJohn Marino }
652*86d7f5d3SJohn Marino }
653*86d7f5d3SJohn Marino else if (strcmp (line, "TmpDir") == 0)
654*86d7f5d3SJohn Marino {
655*86d7f5d3SJohn Marino if (retval->TmpDir) free (retval->TmpDir);
656*86d7f5d3SJohn Marino retval->TmpDir = expand_path (p, cvsroot, false, infopath, ln);
657*86d7f5d3SJohn Marino /* Could try some validity checking, like whether we can
658*86d7f5d3SJohn Marino * opendir it or something, but I don't see any particular
659*86d7f5d3SJohn Marino * reason to do that now rather than when the first function
660*86d7f5d3SJohn Marino * tries to create a temp file.
661*86d7f5d3SJohn Marino */
662*86d7f5d3SJohn Marino }
663*86d7f5d3SJohn Marino else if (strcmp (line, "UserAdminOptions") == 0)
664*86d7f5d3SJohn Marino retval->UserAdminOptions = xstrdup (p);
665*86d7f5d3SJohn Marino else if (strcmp (line, "UseNewInfoFmtStrings") == 0)
666*86d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
667*86d7f5d3SJohn Marino readBool (infopath, "UseNewInfoFmtStrings", p,
668*86d7f5d3SJohn Marino &retval->UseNewInfoFmtStrings);
669*86d7f5d3SJohn Marino #else /* !SUPPORT_OLD_INFO_FMT_STRINGS */
670*86d7f5d3SJohn Marino {
671*86d7f5d3SJohn Marino bool dummy;
672*86d7f5d3SJohn Marino if (readBool (infopath, "UseNewInfoFmtStrings", p, &dummy)
673*86d7f5d3SJohn Marino && !dummy)
674*86d7f5d3SJohn Marino error (1, 0,
675*86d7f5d3SJohn Marino "%s [%u]: Old style info format strings not supported by this executable.",
676*86d7f5d3SJohn Marino infopath, ln);
677*86d7f5d3SJohn Marino }
678*86d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
679*86d7f5d3SJohn Marino else if (strcmp (line, "ImportNewFilesToVendorBranchOnly") == 0)
680*86d7f5d3SJohn Marino readBool (infopath, "ImportNewFilesToVendorBranchOnly", p,
681*86d7f5d3SJohn Marino &retval->ImportNewFilesToVendorBranchOnly);
682*86d7f5d3SJohn Marino else if (strcmp (line, "PrimaryServer") == 0)
683*86d7f5d3SJohn Marino retval->PrimaryServer = parse_cvsroot (p);
684*86d7f5d3SJohn Marino #ifdef PROXY_SUPPORT
685*86d7f5d3SJohn Marino else if (!strcmp (line, "MaxProxyBufferSize"))
686*86d7f5d3SJohn Marino readSizeT (infopath, "MaxProxyBufferSize", p,
687*86d7f5d3SJohn Marino &retval->MaxProxyBufferSize);
688*86d7f5d3SJohn Marino #endif /* PROXY_SUPPORT */
689*86d7f5d3SJohn Marino else if (!strcmp (line, "MaxCommentLeaderLength"))
690*86d7f5d3SJohn Marino readSizeT (infopath, "MaxCommentLeaderLength", p,
691*86d7f5d3SJohn Marino &retval->MaxCommentLeaderLength);
692*86d7f5d3SJohn Marino else if (!strcmp (line, "UseArchiveCommentLeader"))
693*86d7f5d3SJohn Marino readBool (infopath, "UseArchiveCommentLeader", p,
694*86d7f5d3SJohn Marino &retval->UseArchiveCommentLeader);
695*86d7f5d3SJohn Marino #ifdef SERVER_SUPPORT
696*86d7f5d3SJohn Marino else if (!strcmp (line, "MinCompressionLevel"))
697*86d7f5d3SJohn Marino readSizeT (infopath, "MinCompressionLevel", p,
698*86d7f5d3SJohn Marino &retval->MinCompressionLevel);
699*86d7f5d3SJohn Marino else if (!strcmp (line, "MaxCompressionLevel"))
700*86d7f5d3SJohn Marino readSizeT (infopath, "MaxCompressionLevel", p,
701*86d7f5d3SJohn Marino &retval->MaxCompressionLevel);
702*86d7f5d3SJohn Marino #endif /* SERVER_SUPPORT */
703*86d7f5d3SJohn Marino else
704*86d7f5d3SJohn Marino /* We may be dealing with a keyword which was added in a
705*86d7f5d3SJohn Marino subsequent version of CVS. In that case it is a good idea
706*86d7f5d3SJohn Marino to complain, as (1) the keyword might enable a behavior like
707*86d7f5d3SJohn Marino alternate locking behavior, in which it is dangerous and hard
708*86d7f5d3SJohn Marino to detect if some CVS's have it one way and others have it
709*86d7f5d3SJohn Marino the other way, (2) in general, having us not do what the user
710*86d7f5d3SJohn Marino had in mind when they put in the keyword violates the
711*86d7f5d3SJohn Marino principle of least surprise. Note that one corollary is
712*86d7f5d3SJohn Marino adding new keywords to your CVSROOT/config file is not
713*86d7f5d3SJohn Marino particularly recommended unless you are planning on using
714*86d7f5d3SJohn Marino the new features. */
715*86d7f5d3SJohn Marino if (!parse_error (infopath, ln))
716*86d7f5d3SJohn Marino error (0, 0, "%s [%u]: unrecognized keyword `%s'",
717*86d7f5d3SJohn Marino infopath, ln, line);
718*86d7f5d3SJohn Marino }
719*86d7f5d3SJohn Marino if (ferror (fp_info))
720*86d7f5d3SJohn Marino error (0, errno, "cannot read %s", infopath);
721*86d7f5d3SJohn Marino if (fclose (fp_info) < 0)
722*86d7f5d3SJohn Marino error (0, errno, "cannot close %s", infopath);
723*86d7f5d3SJohn Marino if (freeinfopath) free (freeinfopath);
724*86d7f5d3SJohn Marino if (buf) free (buf);
725*86d7f5d3SJohn Marino
726*86d7f5d3SJohn Marino if (path == NULL && !rescan++) {
727*86d7f5d3SJohn Marino infopath = freeinfopath =
728*86d7f5d3SJohn Marino Xasprintf ("%s/%s/%s", cvsroot, CVSROOTADM, CVSROOTADM_OPTIONS);
729*86d7f5d3SJohn Marino buf = NULL;
730*86d7f5d3SJohn Marino goto again;
731*86d7f5d3SJohn Marino }
732*86d7f5d3SJohn Marino
733*86d7f5d3SJohn Marino return retval;
734*86d7f5d3SJohn Marino }
735