1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
28/* All Rights Reserved */
29
30
31#include <stdio.h>
32#include <string.h>
33#include <errno.h>
34#include <ctype.h>
35#include <sys/types.h>
36#include <sys/stat.h>
37#include <limits.h>
38#include <pkgstrct.h>
39#include <pkginfo.h>
40#include <locale.h>
41#include <libintl.h>
42#include <unistd.h>
43#include <stdlib.h>
44#include <pkglib.h>
45#include <install.h>
46#include <libadm.h>
47#include <libinst.h>
48
49extern char	*basedir, *root, *rootlist[], **environ;
50
51/*
52 * IMPORTANT NOTE: PLEASE SEE THE DEFINITION OF temp[] BELOW BEFORE
53 * CHANGING THE DEFINITION OF PATH_LGTH!!!!
54 */
55
56#define	PATH_LGTH 4096
57
58#define	MAXPARAMS 256
59#define	NRECURS 20
60
61#define	MSG_BPARAMC	"parametric class specification for <%s> not allowed"
62#define	MSG_SRCHLOC	"no object for <%s> found in local path"
63#define	MSG_SRCHSRCH	"no object for <%s> found in search path"
64#define	MSG_SRCHROOT	"no object for <%s> found in root directory"
65#define	MSG_CONTENTS	"unable to process contents of object <%s>"
66#define	MSG_WRITE	"write of entry failed, errno=%d"
67#define	MSG_GARBDEFLT	"garbled default settings: %s"
68#define	MSG_BANG	"unknown directive: %s"
69#define	MSG_CHDIR	"unable to change directory to <%s>"
70#define	MSG_INCOMPLETE	"processing of <%s> may be incomplete"
71#define	MSG_NRECURS	"too many levels of include (limit is %d)"
72#define	MSG_RDINCLUDE	"unable to process include file <%s>, errno=%d"
73#define	MSG_IGNINCLUDE	"ignoring include file <%s>"
74#define	MSG_NODEVICE	"device numbers cannot be determined for <%s>"
75
76#define	WRN_BADATTR	"WARNING: attributes set to %04o %s %s for <%s>"
77#define	WRN_BADATTRM	"WARNING: attributes set to %s %s %s for <%s>"
78#define	WRN_FAKEBD	"WARNING: parametric paths may ignore BASEDIR"
79
80#define	ERR_TEMP	"unable to obtain temporary file resources, errno=%d"
81#define	ERR_ENVBUILD	"unable to build parameter environment, errno=%d"
82#define	ERR_MAXPARAMS	"too many parameter definitions (limit is %d)"
83#define	ERR_GETCWD	"unable to get current directory, errno=%d"
84#define	ERR_PATHVAR	"cannot resolve all build parameters associated with " \
85			    "path <%s>."
86
87static struct cfent entry;
88static FILE	*fp,
89		*sfp[20];
90static char	*dname[NRECURS],
91		*params[256],
92		*proto[NRECURS],
93		*rootp[NRECURS][16],
94		*srchp[NRECURS][16],
95		*d_own[NRECURS],
96		*d_grp[NRECURS],
97		*rdonly[256];
98static mode_t	d_mod[NRECURS];
99static int	nfp = (-1);
100static int	nrdonly = 0;
101static int	errflg = 0;
102static char	*separ = " \t\n, ";
103
104/* libpkg/gpkgmap.c */
105extern void	attrpreset(int mode, char *owner, char *group);
106extern void	attrdefault();
107static char	*findfile(char *path, char *local);
108static char	*srchroot(char *path, char *copy);
109
110static int	popenv(void);
111
112static int	doattrib(void);
113static void	doinclude(void);
114static void	dorsearch(void);
115static void	dosearch(void);
116static void	error(int flag);
117static void	lputenv(char *s);
118static void	pushenv(char *file);
119static void	translate(register char *pt, register char *copy);
120
121int
122mkpkgmap(char *outfile, char *protofile, char **envparam)
123{
124	FILE	*tmpfp;
125	char	*pt, *path, mybuff[PATH_LGTH];
126	char	**envsave;
127	int	c, fakebasedir;
128	int	i, n;
129
130	/*
131	 * NOTE: THE SIZE OF temp IS HARD CODED INTO CALLS TO fscanf.
132	 * YOU *MUST* MAKE SURE TO CHANGE THOSE CALLS IF THE SIZE OF temp
133	 * IS EVER CHANGED!!!!!!
134	 */
135	char	temp[PATH_LGTH];
136
137	if ((tmpfp = fopen(outfile, "w")) == NULL) {
138		progerr(gettext(ERR_TEMP), errno);
139		quit(99);
140	}
141	envsave = environ;
142	environ = params; /* use only local environ */
143	attrdefault();	/* assume no default attributes */
144
145	/*
146	 * Environment parameters are optional, so variable
147	 * (envparam[i]) could be NULL.
148	 */
149	for (i = 0; (envparam[i] != NULL) &&
150	    (pt = strchr(envparam[i], '=')); i++) {
151		*pt = '\0';
152		rdonly[nrdonly++] = qstrdup(envparam[i]);
153		*pt = '=';
154		if (putenv(qstrdup(envparam[i]))) { /* bugid 1090920 */
155			progerr(gettext(ERR_ENVBUILD), errno);
156			quit(99);
157		}
158		if (nrdonly >= MAXPARAMS) {
159			progerr(gettext(ERR_MAXPARAMS), MAXPARAMS);
160			quit(1);
161		}
162	}
163
164	pushenv(protofile);
165	errflg = 0;
166again:
167	fakebasedir = 0;
168	while (!feof(fp)) {
169		c = getc(fp);
170		while (isspace(c))
171			c = getc(fp);
172
173		if (c == '#') {
174			do c = getc(fp); while ((c != EOF) && (c != '\n'));
175			continue;
176		}
177		if (c == EOF)
178			break;
179
180		if (c == '!') {
181			/*
182			 * IMPORTANT NOTE: THE SIZE OF temp IS HARD CODED INTO
183			 * the FOLLOWING CALL TO fscanf -- YOU MUST CHANGE THIS
184			 * LINE IF THE SIZE OF fscanf IS EVER CHANGED!!!
185			 */
186			(void) fscanf(fp, "%4096s", temp);
187
188			if (strcmp(temp, "include") == 0)
189				doinclude();
190			else if (strcmp(temp, "rsearch") == 0)
191				dorsearch();
192			else if (strcmp(temp, "search") == 0)
193				dosearch();
194			else if (strcmp(temp, "default") == 0) {
195				if (doattrib())
196					break;
197			} else if (strchr(temp, '=')) {
198				translate(temp, mybuff);
199				/* put this into the local environment */
200				lputenv(mybuff);
201				(void) fscanf(fp, "%*[^\n]"); /* rest of line */
202				(void) fscanf(fp, "\n"); /* rest of line */
203			} else {
204				error(1);
205				logerr(gettext(MSG_BANG), temp);
206				(void) fscanf(fp, "%*[^\n]"); /* read of line */
207				(void) fscanf(fp, "\n"); /* read of line */
208			}
209			continue;
210		}
211		(void) ungetc(c, fp);
212
213		if ((n = gpkgmap(&entry, fp)) < 0) {
214			char	*errstr;
215
216			error(1);
217			errstr = getErrstr();
218			logerr(gettext("garbled entry"));
219			logerr(gettext("- pathname: %s"),
220			    (entry.path && *entry.path) ? entry.path :
221			    "Unknown");
222			logerr(gettext("- problem: %s"),
223			    (errstr && *errstr) ? errstr : "Unknown");
224			break;
225		}
226		if (n == 0)
227			break; /* done with file */
228
229		/* don't allow classname to be parametric */
230		if (entry.ftype != 'i') {
231			if (entry.pkg_class[0] == '$') {
232				error(1);
233				logerr(gettext(MSG_BPARAMC), entry.path);
234			}
235		}
236
237		if (strchr("dxlscbp", entry.ftype)) {
238			/*
239			 * We don't need to search for things without any
240			 * contents in them.
241			 */
242			if (strchr("cb", entry.ftype)) {
243				if (entry.ainfo.major == BADMAJOR ||
244				    entry.ainfo.minor == BADMINOR) {
245					error(1);
246					logerr(gettext(MSG_NODEVICE),
247					    entry.path);
248				}
249			}
250			path = NULL;
251		} else {
252			path = findfile(entry.path, entry.ainfo.local);
253			if (!path)
254				continue;
255
256			entry.ainfo.local = path;
257			if (strchr("fevin?", entry.ftype)) {
258				if (cverify(0, &entry.ftype, path,
259				    &entry.cinfo, 1)) {
260					error(1);
261					logerr(gettext(MSG_CONTENTS), path);
262				}
263			}
264		}
265
266		/* Warn if attributes are not set correctly. */
267		if (!strchr("isl", entry.ftype)) {
268			int dowarning = 0;
269			int hasbadmode = 0;
270
271			if (entry.ainfo.mode == NOMODE) {
272				entry.ainfo.mode = CURMODE;
273				dowarning = 1;
274				hasbadmode = 1;
275			}
276
277			if (strcmp(entry.ainfo.owner, NOOWNER) == 0) {
278				(void) strlcpy(entry.ainfo.owner, CUROWNER,
279						sizeof (entry.ainfo.owner));
280				dowarning = 1;
281			}
282
283			if (strcmp(entry.ainfo.group, NOGROUP) == 0) {
284				(void) strlcpy(entry.ainfo.group, CURGROUP,
285						sizeof (entry.ainfo.group));
286				dowarning = 1;
287			}
288
289
290			if (dowarning) {
291				if (hasbadmode)
292					logerr(gettext(WRN_BADATTRM),
293						"?",
294					    entry.ainfo.owner,
295					    entry.ainfo.group,
296					    entry.path);
297				else
298					logerr(gettext(WRN_BADATTR),
299						(int)entry.ainfo.mode,
300						entry.ainfo.owner,
301						entry.ainfo.group,
302						entry.path);
303			}
304		}
305
306		/*
307		 * Resolve build parameters (initial lower case) in
308		 * the link and target paths.
309		 */
310		if (strchr("ls", entry.ftype)) {
311			if (!RELATIVE(entry.ainfo.local) ||
312					PARAMETRIC(entry.ainfo.local)) {
313				if (mappath(1, entry.ainfo.local)) {
314					error(1);
315					logerr(gettext(ERR_PATHVAR),
316					    entry.ainfo.local);
317					break;
318				}
319
320				canonize(entry.ainfo.local);
321			}
322		}
323
324		/*
325		 * Warn if top level file or directory is an install
326		 * parameter
327		 */
328		if (entry.ftype != 'i') {
329			if (entry.path[0] == '$' && isupper(entry.path[1]))
330				fakebasedir = 1;
331		}
332
333		if (mappath(1, entry.path)) {
334			error(1);
335			logerr(gettext(ERR_PATHVAR), entry.path);
336			break;
337		}
338
339		canonize(entry.path);
340		if (ppkgmap(&entry, tmpfp)) {
341			error(1);
342			logerr(gettext(MSG_WRITE), errno);
343			break;
344		}
345	}
346
347	if (fakebasedir) {
348		logerr(gettext(WRN_FAKEBD));
349		fakebasedir = 0;
350	}
351
352	if (popenv())
353		goto again;
354
355	(void) fclose(tmpfp);
356	environ = envsave; /* restore environment */
357
358	return (errflg ? 1 : 0);
359}
360
361static char *
362findfile(char *path, char *local)
363{
364	struct stat statbuf;
365	static char host[PATH_MAX];
366	register char *pt;
367	char	temp[PATH_MAX], *basename;
368	int	i;
369
370	/*
371	 * map any parameters specified in path to their corresponding values
372	 * and make sure the path is in its canonical form; any parmeters for
373	 * which a value is not defined will be left unexpanded. Since this
374	 * is an actual search for a real file (which will not end up in the
375	 * package) - we map ALL variables (both build and Install).
376	 */
377	(void) strlcpy(temp, (local && local[0] ? local : path), sizeof (temp));
378	mappath(0, temp);
379	canonize(temp);
380
381	*host = '\0';
382	if (rootlist[0] || (basedir && (*temp != '/'))) {
383		/*
384		 * search for path in the pseudo-root/basedir directory; note
385		 * that package information files should NOT be included in
386		 * this list
387		 */
388		if (entry.ftype != 'i')
389			return (srchroot(temp, host));
390	}
391
392	/* looking for local object file  */
393	if (local && *local) {
394		basepath(temp, dname[nfp], NULL);
395		/*
396		 * If it equals "/dev/null", that just means it's an empty
397		 * file. Otherwise, we'll really be writing stuff, so we need
398		 * to verify the source.
399		 */
400		if (strcmp(temp, "/dev/null") != 0) {
401			if (stat(temp, &statbuf) ||
402			    !(statbuf.st_mode & S_IFREG)) {
403				error(1);
404				logerr(gettext(MSG_SRCHLOC), path);
405				return (NULL);
406			}
407		}
408		(void) strlcpy(host, temp, sizeof (host));
409		return (host);
410	}
411
412	for (i = 0; rootp[nfp][i]; i++) {
413		(void) snprintf(host, sizeof (host), "%s/%s", rootp[nfp][i],
414		    temp + (*temp == '/' ? 1 : 0));
415		if ((stat(host, &statbuf) == 0) &&
416		    (statbuf.st_mode & S_IFREG)) {
417			return (host);
418		}
419	}
420
421	pt = strrchr(temp, '/');
422	if (!pt++)
423		pt = temp;
424
425	basename = pt;
426
427	for (i = 0; srchp[nfp][i]; i++) {
428		(void) snprintf(host, sizeof (host), "%s/%s",
429			srchp[nfp][i], basename);
430		if ((stat(host, &statbuf) == 0) &&
431		    (statbuf.st_mode & S_IFREG)) {
432			return (host);
433		}
434	}
435
436	/* check current directory as a last resort */
437	(void) snprintf(host, sizeof (host), "%s/%s", dname[nfp], basename);
438	if ((stat(host, &statbuf) == 0) && (statbuf.st_mode & S_IFREG))
439		return (host);
440
441	error(1);
442	logerr(gettext(MSG_SRCHSRCH), path);
443	return (NULL);
444}
445
446static void
447dosearch(void)
448{
449	char temp[PATH_MAX], lookpath[PATH_MAX], *pt;
450	int n;
451
452	(void) fgets(temp, PATH_MAX, fp);
453	translate(temp, lookpath);
454
455	for (n = 0; srchp[nfp][n]; n++)
456		free(srchp[nfp][n]);
457
458	n = 0;
459	pt = strtok(lookpath, separ);
460	if (pt && *pt) {
461		do {
462			if (*pt != '/') {
463				/* make relative path an absolute directory */
464				(void) snprintf(temp, sizeof (temp),
465						"%s/%s", dname[nfp], pt);
466				pt = temp;
467			}
468			canonize(pt);
469			srchp[nfp][n++] = qstrdup(pt);
470		} while (pt = strtok(NULL, separ));
471		srchp[nfp][n] = NULL;
472	}
473}
474
475static void
476dorsearch(void)
477{
478	char temp[PATH_MAX], lookpath[PATH_MAX], *pt;
479	int n;
480
481	(void) fgets(temp, PATH_MAX, fp);
482	translate(temp, lookpath);
483
484	for (n = 0; rootp[nfp][n]; n++)
485		free(rootp[nfp][n]);
486
487	n = 0;
488	pt = strtok(lookpath, separ);
489	do {
490		if (*pt != '/') {
491			/* make relative path an absolute directory */
492			(void) snprintf(temp, sizeof (temp),
493					"%s/%s", dname[nfp], pt);
494			pt = temp;
495		}
496		canonize(pt);
497		rootp[nfp][n++] = qstrdup(pt);
498	} while (pt = strtok(NULL, separ));
499	rootp[nfp][n] = NULL;
500}
501
502/*
503 * This function reads the default mode, owner and group from the prototype
504 * file and makes that available.
505 */
506static int
507doattrib(void)
508{
509	char *pt, attrib[PATH_MAX], *mode_ptr, *owner_ptr, *group_ptr, *eol;
510	int mode;
511	char owner[ATRSIZ+1], group[ATRSIZ+1], attrib_save[(4*ATRSIZ)];
512
513	(void) fgets(attrib, PATH_MAX, fp);
514
515	(void) strlcpy(attrib_save, attrib, sizeof (attrib_save));
516
517	/*
518	 * Now resolve any variables that may be present. Start on group and
519	 * move backward since that keeps the resolved string from
520	 * overwriting any of the other entries. This is required since
521	 * mapvar() writes the resolved string over the string provided.
522	 */
523	mode_ptr = strtok(attrib, " \t");
524	owner_ptr = strtok(NULL, " \t");
525	group_ptr = strtok(NULL, " \t\n");
526	eol = strtok(NULL, " \t\n");
527	if (strtok(NULL, " \t\n")) {
528		/* extra tokens on the line */
529		error(1);
530		logerr(gettext(MSG_GARBDEFLT), (eol) ? eol :
531		    gettext("unreadable at end of line"));
532		return (1);
533	}
534
535	if (group_ptr && mapvar(1, group_ptr) == 0)
536		(void) strncpy(group, group_ptr, ATRSIZ);
537	else {
538		error(1);
539		logerr(gettext(MSG_GARBDEFLT), (attrib_save) ?
540		    ((attrib_save[0]) ? attrib_save : gettext("none")) :
541		    gettext("unreadable at group"));
542		return (1);
543	}
544
545	if (owner_ptr && mapvar(1, owner_ptr) == 0)
546		(void) strncpy(owner, owner_ptr, ATRSIZ);
547	else {
548		error(1);
549		logerr(gettext(MSG_GARBDEFLT), (attrib_save) ?
550		    ((attrib_save[0]) ? attrib_save : gettext("none")) :
551		    gettext("unreadable at owner"));
552		return (1);
553	}
554
555	/*
556	 * For mode, don't use scanf, since we want to force an octal
557	 * interpretation and need to limit the length of the owner and group
558	 * specifications.
559	 */
560	if (mode_ptr && mapvar(1, mode_ptr) == 0)
561		mode = strtol(mode_ptr, &pt, 8);
562	else {
563		error(1);
564		logerr(gettext(MSG_GARBDEFLT), (attrib_save) ?
565		    ((attrib_save[0]) ? attrib_save : gettext("none")) :
566		    gettext("unreadable at mode"));
567		return (1);
568	}
569
570	/* free any previous memory from qstrdup */
571	if (d_own[nfp])
572		free(d_own[nfp]);
573	if (d_grp[nfp])
574		free(d_grp[nfp]);
575
576	d_mod[nfp] = mode;
577	d_own[nfp] = qstrdup(owner);
578	d_grp[nfp] = qstrdup(group);
579
580	attrpreset(d_mod[nfp], d_own[nfp], d_grp[nfp]);
581
582	return (0);
583}
584
585static void
586doinclude(void)
587{
588	char	file[PATH_MAX];
589	char	temp[PATH_MAX];
590
591	(void) fgets(temp, PATH_MAX, fp);
592
593	/*
594	 * IMPORTANT NOTE: THE SIZE OF temp IS HARD CODED INTO THE
595	 * FOLLOWING CALL TO fscanf -- YOU MUST CHANGE THIS LINE IF
596	 * THE SIZE OF fscanf IS EVER CHANGED!!!
597	 */
598	(void) sscanf(temp, "%1024s", file);
599
600	translate(file, temp);
601	canonize(temp);
602
603	if (*temp == '\0')
604		return;
605	else if (*temp != '/')
606		(void) snprintf(file, sizeof (file), "%s/%s", dname[nfp], temp);
607	else
608		(void) strlcpy(file, temp, sizeof (file));
609
610	canonize(file);
611	pushenv(file);
612}
613
614/*
615 * This does what mappath() does except that it does it for ALL variables
616 * using whitespace as a token separator. This is used to resolve search
617 * paths and assignment statements. It doesn't effect the build versus
618 * install decision made for pkgmap variables.
619 */
620static void
621translate(register char *pt, register char *copy)
622{
623	char *pt2, varname[MAX_PKG_PARAM_LENGTH];
624
625token:
626	/* eat white space */
627	while (isspace(*pt))
628		pt++;
629	while (*pt && !isspace(*pt)) {
630		if (*pt == '$') {
631			pt2 = varname;
632			while (*++pt && !strchr("/= \t\n\r", *pt))
633				*pt2++ = *pt;
634			*pt2 = '\0';
635			if (pt2 = getenv(varname)) {
636				while (*pt2)
637					*copy++ = *pt2++;
638			}
639		} else
640			*copy++ = *pt++;
641	}
642	if (*pt) {
643		*copy++ = ' ';
644		goto token;
645	}
646	*copy = '\0';
647}
648
649static void
650error(int flag)
651{
652	static char *lasterr = NULL;
653
654	if (lasterr != proto[nfp]) {
655		lasterr = proto[nfp];
656		(void) fprintf(stderr, gettext("ERROR in %s:\n"), lasterr);
657	}
658	if (flag)
659		errflg++;
660}
661
662/* Set up defaults and change to the build directory. */
663static void
664pushenv(char *file)
665{
666	register char *pt;
667	static char	topdir[PATH_MAX];
668
669	if ((nfp+1) >= NRECURS) {
670		error(1);
671		logerr(gettext(MSG_NRECURS), NRECURS);
672		logerr(gettext(MSG_IGNINCLUDE), file);
673		return;
674	}
675
676	if (strcmp(file, "-") == 0) {
677		fp = stdin;
678	} else if ((fp = fopen(file, "r")) == NULL) {
679		error(1);
680		logerr(gettext(MSG_RDINCLUDE), file, errno);
681		if (nfp >= 0) {
682			logerr(gettext(MSG_IGNINCLUDE), file);
683			fp = sfp[nfp];
684			return;
685		} else
686			quit(1);
687	}
688	sfp[++nfp] = fp;
689	srchp[nfp][0] = NULL;
690	rootp[nfp][0] = NULL;
691	d_mod[nfp] = (mode_t)(-1);
692	d_own[nfp] = NULL;
693	d_grp[nfp] = NULL;
694
695	if (!nfp) {
696		/* upper level proto file */
697		proto[nfp] = file;
698		if (file[0] == '/')
699			pt = strcpy(topdir, file);
700		else {
701			/* path is relative to the prototype file specified */
702			pt = getcwd(NULL, PATH_MAX);
703			if (pt == NULL) {
704				progerr(gettext(ERR_GETCWD), errno);
705				quit(99);
706			}
707			(void) snprintf(topdir, sizeof (topdir),
708						"%s/%s", pt, file);
709		}
710		if (pt = strrchr(topdir, '/'))
711			*pt = '\0'; /* should always happen */
712		if (topdir[0] == '\0')
713			(void) strlcpy(topdir, "/", sizeof (topdir));
714		dname[nfp] = topdir;
715	} else {
716		proto[nfp] = qstrdup(file);
717		dname[nfp] = qstrdup(file);
718		if (pt = strrchr(dname[nfp], '/'))
719			*pt = '\0';
720		else {
721			/* same directory as the last prototype */
722			free(dname[nfp]);
723			dname[nfp] = qstrdup(dname[nfp-1]);
724			return; /* no need to canonize() or chdir() */
725		}
726	}
727
728	canonize(dname[nfp]);
729
730	if (chdir(dname[nfp])) {
731		error(1);
732		logerr(gettext(MSG_CHDIR), dname[nfp]);
733		if (!nfp)
734			quit(1); /* must be able to cd to upper level */
735		logerr(gettext(MSG_IGNINCLUDE), proto[nfp]);
736		(void) popenv();
737	}
738}
739
740/* Restore defaults and return to the prior directory. */
741static int
742popenv(void)
743{
744	int i;
745
746	(void) fclose(fp);
747	if (nfp) {
748		if (proto[nfp])
749			free(proto[nfp]);
750		if (dname[nfp])
751			free(dname[nfp]);
752		for (i = 0; srchp[nfp][i]; i++)
753			free(srchp[nfp][i]);
754		for (i = 0; rootp[nfp][i]; i++)
755			free(rootp[nfp][i]);
756		if (d_own[nfp])
757			free(d_own[nfp]);
758		if (d_grp[nfp])
759			free(d_grp[nfp]);
760
761		fp = sfp[--nfp];
762
763		if (chdir(dname[nfp])) {
764			error(1);
765			logerr(gettext(MSG_CHDIR), dname[nfp]);
766			logerr(gettext(MSG_INCOMPLETE), proto[nfp]);
767			return (popenv());
768		}
769		return (1);
770	}
771	return (0);
772}
773
774/*
775 * If this parameter isn't already in place, put it into the local
776 * environment. This means that command line directives override prototype
777 * file directives.
778 */
779static void
780lputenv(char *s)
781{
782	char *pt;
783	int i;
784
785	pt = strchr(s, '=');
786	if (!pt)
787		return;
788
789	*pt = '\0';
790	for (i = 0; i < nrdonly; i++) {
791		if (strcmp(rdonly[i], s) == 0) {
792			*pt = '=';
793			return;
794		}
795	}
796	*pt = '=';
797
798	if (putenv(qstrdup(s))) {
799		progerr(gettext(ERR_ENVBUILD), errno);
800		quit(99);
801	}
802}
803
804static char *
805srchroot(char *path, char *copy)
806{
807	struct stat statbuf;
808	int i;
809
810	i = 0;
811	root = rootlist[i++];
812	do {
813		/* convert with root & basedir info */
814		cvtpath(path, copy);
815		/* make it pretty again */
816		canonize(copy);
817
818		if (stat(copy, &statbuf) || !(statbuf.st_mode & S_IFREG)) {
819			root = rootlist[i++];
820			continue; /* host source must be a regular file */
821		}
822		return (copy);
823	} while (root != NULL);
824	error(1);
825	logerr(gettext(MSG_SRCHROOT), path);
826	return (NULL);
827}
828