xref: /illumos-gate/usr/src/cmd/svr4pkg/pkgmk/mkpkgmap.c (revision c9ffe217655ce62448bdb45d6f113f43b4dfcc54)
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 
49 extern 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 
87 static struct cfent entry;
88 static FILE	*fp,
89 		*sfp[20];
90 static 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];
98 static mode_t	d_mod[NRECURS];
99 static int	nfp = (-1);
100 static int	nrdonly = 0;
101 static int	errflg = 0;
102 static char	*separ = " \t\n, ";
103 
104 /* libpkg/gpkgmap.c */
105 extern void	attrpreset(int mode, char *owner, char *group);
106 extern void	attrdefault();
107 static char	*findfile(char *path, char *local);
108 static char	*srchroot(char *path, char *copy);
109 
110 static int	popenv(void);
111 
112 static int	doattrib(void);
113 static void	doinclude(void);
114 static void	dorsearch(void);
115 static void	dosearch(void);
116 static void	error(int flag);
117 static void	lputenv(char *s);
118 static void	pushenv(char *file);
119 static void	translate(register char *pt, register char *copy);
120 
121 int
122 mkpkgmap(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;
166 again:
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 
361 static char *
362 findfile(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 
446 static void
447 dosearch(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 
475 static void
476 dorsearch(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  */
506 static int
507 doattrib(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 
585 static void
586 doinclude(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  */
620 static void
621 translate(register char *pt, register char *copy)
622 {
623 	char *pt2, varname[MAX_PKG_PARAM_LENGTH];
624 
625 token:
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 
649 static void
650 error(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. */
663 static void
664 pushenv(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. */
741 static int
742 popenv(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  */
779 static void
780 lputenv(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 
804 static char *
805 srchroot(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