8322 nl: misleading-indentation
[unleashed/tickless.git] / usr / src / cmd / svr4pkg / pkgmk / mkpkgmap.c
bloba0c327fb0ca37a456bca7d189197594fc59760c5
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
23 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
28 /* All Rights Reserved */
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>
49 extern char *basedir, *root, *rootlist[], **environ;
52 * IMPORTANT NOTE: PLEASE SEE THE DEFINITION OF temp[] BELOW BEFORE
53 * CHANGING THE DEFINITION OF PATH_LGTH!!!!
56 #define PATH_LGTH 4096
58 #define MAXPARAMS 256
59 #define NRECURS 20
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>"
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"
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>."
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, ";
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);
110 static int popenv(void);
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);
122 mkpkgmap(char *outfile, char *protofile, char **envparam)
124 FILE *tmpfp;
125 char *pt, *path, mybuff[PATH_LGTH];
126 char **envsave;
127 int c, fakebasedir;
128 int i, n;
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!!!!!!
135 char temp[PATH_LGTH];
137 if ((tmpfp = fopen(outfile, "w")) == NULL) {
138 progerr(gettext(ERR_TEMP), errno);
139 quit(99);
141 envsave = environ;
142 environ = params; /* use only local environ */
143 attrdefault(); /* assume no default attributes */
146 * Environment parameters are optional, so variable
147 * (envparam[i]) could be NULL.
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);
158 if (nrdonly >= MAXPARAMS) {
159 progerr(gettext(ERR_MAXPARAMS), MAXPARAMS);
160 quit(1);
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);
173 if (c == '#') {
174 do c = getc(fp); while ((c != EOF) && (c != '\n'));
175 continue;
177 if (c == EOF)
178 break;
180 if (c == '!') {
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!!!
186 (void) fscanf(fp, "%4096s", temp);
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 */
209 continue;
211 (void) ungetc(c, fp);
213 if ((n = gpkgmap(&entry, fp)) < 0) {
214 char *errstr;
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;
226 if (n == 0)
227 break; /* done with file */
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);
237 if (strchr("dxlscbp", entry.ftype)) {
239 * We don't need to search for things without any
240 * contents in them.
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);
250 path = NULL;
251 } else {
252 path = findfile(entry.path, entry.ainfo.local);
253 if (!path)
254 continue;
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);
266 /* Warn if attributes are not set correctly. */
267 if (!strchr("isl", entry.ftype)) {
268 int dowarning = 0;
269 int hasbadmode = 0;
271 if (entry.ainfo.mode == NOMODE) {
272 entry.ainfo.mode = CURMODE;
273 dowarning = 1;
274 hasbadmode = 1;
277 if (strcmp(entry.ainfo.owner, NOOWNER) == 0) {
278 (void) strlcpy(entry.ainfo.owner, CUROWNER,
279 sizeof (entry.ainfo.owner));
280 dowarning = 1;
283 if (strcmp(entry.ainfo.group, NOGROUP) == 0) {
284 (void) strlcpy(entry.ainfo.group, CURGROUP,
285 sizeof (entry.ainfo.group));
286 dowarning = 1;
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);
307 * Resolve build parameters (initial lower case) in
308 * the link and target paths.
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;
320 canonize(entry.ainfo.local);
325 * Warn if top level file or directory is an install
326 * parameter
328 if (entry.ftype != 'i') {
329 if (entry.path[0] == '$' && isupper(entry.path[1]))
330 fakebasedir = 1;
333 if (mappath(1, entry.path)) {
334 error(1);
335 logerr(gettext(ERR_PATHVAR), entry.path);
336 break;
339 canonize(entry.path);
340 if (ppkgmap(&entry, tmpfp)) {
341 error(1);
342 logerr(gettext(MSG_WRITE), errno);
343 break;
347 if (fakebasedir) {
348 logerr(gettext(WRN_FAKEBD));
349 fakebasedir = 0;
352 if (popenv())
353 goto again;
355 (void) fclose(tmpfp);
356 environ = envsave; /* restore environment */
358 return (errflg ? 1 : 0);
361 static char *
362 findfile(char *path, char *local)
364 struct stat statbuf;
365 static char host[PATH_MAX];
366 register char *pt;
367 char temp[PATH_MAX], *basename;
368 int i;
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).
377 (void) strlcpy(temp, (local && local[0] ? local : path), sizeof (temp));
378 mappath(0, temp);
379 canonize(temp);
381 *host = '\0';
382 if (rootlist[0] || (basedir && (*temp != '/'))) {
384 * search for path in the pseudo-root/basedir directory; note
385 * that package information files should NOT be included in
386 * this list
388 if (entry.ftype != 'i')
389 return (srchroot(temp, host));
392 /* looking for local object file */
393 if (local && *local) {
394 basepath(temp, dname[nfp], NULL);
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.
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);
408 (void) strlcpy(host, temp, sizeof (host));
409 return (host);
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);
421 pt = strrchr(temp, '/');
422 if (!pt++)
423 pt = temp;
425 basename = pt;
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);
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);
441 error(1);
442 logerr(gettext(MSG_SRCHSRCH), path);
443 return (NULL);
446 static void
447 dosearch(void)
449 char temp[PATH_MAX], lookpath[PATH_MAX], *pt;
450 int n;
452 (void) fgets(temp, PATH_MAX, fp);
453 translate(temp, lookpath);
455 for (n = 0; srchp[nfp][n]; n++)
456 free(srchp[nfp][n]);
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;
468 canonize(pt);
469 srchp[nfp][n++] = qstrdup(pt);
470 } while (pt = strtok(NULL, separ));
471 srchp[nfp][n] = NULL;
475 static void
476 dorsearch(void)
478 char temp[PATH_MAX], lookpath[PATH_MAX], *pt;
479 int n;
481 (void) fgets(temp, PATH_MAX, fp);
482 translate(temp, lookpath);
484 for (n = 0; rootp[nfp][n]; n++)
485 free(rootp[nfp][n]);
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;
496 canonize(pt);
497 rootp[nfp][n++] = qstrdup(pt);
498 } while (pt = strtok(NULL, separ));
499 rootp[nfp][n] = NULL;
503 * This function reads the default mode, owner and group from the prototype
504 * file and makes that available.
506 static int
507 doattrib(void)
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)];
513 (void) fgets(attrib, PATH_MAX, fp);
515 (void) strlcpy(attrib_save, attrib, sizeof (attrib_save));
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.
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);
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);
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);
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.
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);
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]);
576 d_mod[nfp] = mode;
577 d_own[nfp] = qstrdup(owner);
578 d_grp[nfp] = qstrdup(group);
580 attrpreset(d_mod[nfp], d_own[nfp], d_grp[nfp]);
582 return (0);
585 static void
586 doinclude(void)
588 char file[PATH_MAX];
589 char temp[PATH_MAX];
591 (void) fgets(temp, PATH_MAX, fp);
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!!!
598 (void) sscanf(temp, "%1024s", file);
600 translate(file, temp);
601 canonize(temp);
603 if (*temp == NULL)
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));
610 canonize(file);
611 pushenv(file);
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.
620 static void
621 translate(register char *pt, register char *copy)
623 char *pt2, varname[MAX_PKG_PARAM_LENGTH];
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++;
639 } else
640 *copy++ = *pt++;
642 if (*pt) {
643 *copy++ = ' ';
644 goto token;
646 *copy = '\0';
649 static void
650 error(int flag)
652 static char *lasterr = NULL;
654 if (lasterr != proto[nfp]) {
655 lasterr = proto[nfp];
656 (void) fprintf(stderr, gettext("ERROR in %s:\n"), lasterr);
658 if (flag)
659 errflg++;
662 /* Set up defaults and change to the build directory. */
663 static void
664 pushenv(char *file)
666 register char *pt;
667 static char topdir[PATH_MAX];
669 if ((nfp+1) >= NRECURS) {
670 error(1);
671 logerr(gettext(MSG_NRECURS), NRECURS);
672 logerr(gettext(MSG_IGNINCLUDE), file);
673 return;
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);
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;
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);
707 (void) snprintf(topdir, sizeof (topdir),
708 "%s/%s", pt, file);
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() */
728 canonize(dname[nfp]);
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();
740 /* Restore defaults and return to the prior directory. */
741 static int
742 popenv(void)
744 int i;
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]);
761 fp = sfp[--nfp];
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());
769 return (1);
771 return (0);
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.
779 static void
780 lputenv(char *s)
782 char *pt;
783 int i;
785 pt = strchr(s, '=');
786 if (!pt)
787 return;
789 *pt = '\0';
790 for (i = 0; i < nrdonly; i++) {
791 if (strcmp(rdonly[i], s) == 0) {
792 *pt = '=';
793 return;
796 *pt = '=';
798 if (putenv(qstrdup(s))) {
799 progerr(gettext(ERR_ENVBUILD), errno);
800 quit(99);
804 static char *
805 srchroot(char *path, char *copy)
807 struct stat statbuf;
808 int i;
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);
818 if (stat(copy, &statbuf) || !(statbuf.st_mode & S_IFREG)) {
819 root = rootlist[i++];
820 continue; /* host source must be a regular file */
822 return (copy);
823 } while (root != NULL);
824 error(1);
825 logerr(gettext(MSG_SRCHROOT), path);
826 return (NULL);