OpenSolaris_b135/cmd/svr4pkg/pkgmk/mkpkgmap.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */


#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <pkgstrct.h>
#include <pkginfo.h>
#include <locale.h>
#include <libintl.h>
#include <unistd.h>
#include <stdlib.h>
#include <pkglib.h>
#include <install.h>
#include <libadm.h>
#include <libinst.h>

extern char	*basedir, *root, *rootlist[], **environ;

/*
 * IMPORTANT NOTE: PLEASE SEE THE DEFINITION OF temp[] BELOW BEFORE
 * CHANGING THE DEFINITION OF PATH_LGTH!!!!
 */

#define	PATH_LGTH 4096

#define	MAXPARAMS 256
#define	NRECURS 20

#define	MSG_BPARAMC	"parametric class specification for <%s> not allowed"
#define	MSG_SRCHLOC	"no object for <%s> found in local path"
#define	MSG_SRCHSRCH	"no object for <%s> found in search path"
#define	MSG_SRCHROOT	"no object for <%s> found in root directory"
#define	MSG_CONTENTS	"unable to process contents of object <%s>"
#define	MSG_WRITE	"write of entry failed, errno=%d"
#define	MSG_GARBDEFLT	"garbled default settings: %s"
#define	MSG_BANG	"unknown directive: %s"
#define	MSG_CHDIR	"unable to change directory to <%s>"
#define	MSG_INCOMPLETE	"processing of <%s> may be incomplete"
#define	MSG_NRECURS	"too many levels of include (limit is %d)"
#define	MSG_RDINCLUDE	"unable to process include file <%s>, errno=%d"
#define	MSG_IGNINCLUDE	"ignoring include file <%s>"
#define	MSG_NODEVICE	"device numbers cannot be determined for <%s>"

#define	WRN_BADATTR	"WARNING: attributes set to %04o %s %s for <%s>"
#define	WRN_BADATTRM	"WARNING: attributes set to %s %s %s for <%s>"
#define	WRN_FAKEBD	"WARNING: parametric paths may ignore BASEDIR"

#define	ERR_TEMP	"unable to obtain temporary file resources, errno=%d"
#define	ERR_ENVBUILD	"unable to build parameter environment, errno=%d"
#define	ERR_MAXPARAMS	"too many parameter definitions (limit is %d)"
#define	ERR_GETCWD	"unable to get current directory, errno=%d"
#define	ERR_PATHVAR	"cannot resolve all build parameters associated with " \
			    "path <%s>."

static struct cfent entry;
static FILE	*fp,
		*sfp[20];
static char	*dname[NRECURS],
		*params[256],
		*proto[NRECURS],
		*rootp[NRECURS][16],
		*srchp[NRECURS][16],
		*d_own[NRECURS],
		*d_grp[NRECURS],
		*rdonly[256];
static mode_t	d_mod[NRECURS];
static int	nfp = (-1);
static int	nrdonly = 0;
static int	errflg = 0;
static char	*separ = " \t\n, ";

/* libpkg/gpkgmap.c */
extern void	attrpreset(int mode, char *owner, char *group);
extern void	attrdefault();
static char	*findfile(char *path, char *local);
static char	*srchroot(char *path, char *copy);

static int	popenv(void);

static int	doattrib(void);
static void	doinclude(void);
static void	dorsearch(void);
static void	dosearch(void);
static void	error(int flag);
static void	lputenv(char *s);
static void	pushenv(char *file);
static void	translate(register char *pt, register char *copy);

int
mkpkgmap(char *outfile, char *protofile, char **envparam)
{
	FILE	*tmpfp;
	char	*pt, *path, mybuff[PATH_LGTH];
	char	**envsave;
	int	c, fakebasedir;
	int	i, n;

	/*
	 * NOTE: THE SIZE OF temp IS HARD CODED INTO CALLS TO fscanf.
	 * YOU *MUST* MAKE SURE TO CHANGE THOSE CALLS IF THE SIZE OF temp
	 * IS EVER CHANGED!!!!!!
	 */
	char	temp[PATH_LGTH];

	if ((tmpfp = fopen(outfile, "w")) == NULL) {
		progerr(gettext(ERR_TEMP), errno);
		quit(99);
	}
	envsave = environ;
	environ = params; /* use only local environ */
	attrdefault();	/* assume no default attributes */

	/*
	 * Environment parameters are optional, so variable
	 * (envparam[i]) could be NULL.
	 */
	for (i = 0; (envparam[i] != NULL) &&
	    (pt = strchr(envparam[i], '=')); i++) {
		*pt = '\0';
		rdonly[nrdonly++] = qstrdup(envparam[i]);
		*pt = '=';
		if (putenv(qstrdup(envparam[i]))) { /* bugid 1090920 */
			progerr(gettext(ERR_ENVBUILD), errno);
			quit(99);
		}
		if (nrdonly >= MAXPARAMS) {
			progerr(gettext(ERR_MAXPARAMS), MAXPARAMS);
			quit(1);
		}
	}

	pushenv(protofile);
	errflg = 0;
again:
	fakebasedir = 0;
	while (!feof(fp)) {
		c = getc(fp);
		while (isspace(c))
			c = getc(fp);

		if (c == '#') {
			do c = getc(fp); while ((c != EOF) && (c != '\n'));
			continue;
		}
		if (c == EOF)
			break;

		if (c == '!') {
			/*
			 * IMPORTANT NOTE: THE SIZE OF temp IS HARD CODED INTO
			 * the FOLLOWING CALL TO fscanf -- YOU MUST CHANGE THIS
			 * LINE IF THE SIZE OF fscanf IS EVER CHANGED!!!
			 */
			(void) fscanf(fp, "%4096s", temp);

			if (strcmp(temp, "include") == 0)
				doinclude();
			else if (strcmp(temp, "rsearch") == 0)
				dorsearch();
			else if (strcmp(temp, "search") == 0)
				dosearch();
			else if (strcmp(temp, "default") == 0) {
				if (doattrib())
					break;
			} else if (strchr(temp, '=')) {
				translate(temp, mybuff);
				/* put this into the local environment */
				lputenv(mybuff);
				(void) fscanf(fp, "%*[^\n]"); /* rest of line */
				(void) fscanf(fp, "\n"); /* rest of line */
			} else {
				error(1);
				logerr(gettext(MSG_BANG), temp);
				(void) fscanf(fp, "%*[^\n]"); /* read of line */
				(void) fscanf(fp, "\n"); /* read of line */
			}
			continue;
		}
		(void) ungetc(c, fp);

		if ((n = gpkgmap(&entry, fp)) < 0) {
			char	*errstr;

			error(1);
			errstr = getErrstr();
			logerr(gettext("garbled entry"));
			logerr(gettext("- pathname: %s"),
			    (entry.path && *entry.path) ? entry.path :
			    "Unknown");
			logerr(gettext("- problem: %s"),
			    (errstr && *errstr) ? errstr : "Unknown");
			break;
		}
		if (n == 0)
			break; /* done with file */

		/* don't allow classname to be parametric */
		if (entry.ftype != 'i') {
			if (entry.pkg_class[0] == '$') {
				error(1);
				logerr(gettext(MSG_BPARAMC), entry.path);
			}
		}

		if (strchr("dxlscbp", entry.ftype)) {
			/*
			 * We don't need to search for things without any
			 * contents in them.
			 */
			if (strchr("cb", entry.ftype)) {
				if (entry.ainfo.major == BADMAJOR ||
				    entry.ainfo.minor == BADMINOR) {
					error(1);
					logerr(gettext(MSG_NODEVICE),
					    entry.path);
				}
			}
			path = NULL;
		} else {
			path = findfile(entry.path, entry.ainfo.local);
			if (!path)
				continue;

			entry.ainfo.local = path;
			if (strchr("fevin?", entry.ftype)) {
				if (cverify(0, &entry.ftype, path,
				    &entry.cinfo, 1)) {
					error(1);
					logerr(gettext(MSG_CONTENTS), path);
				}
			}
		}

		/* Warn if attributes are not set correctly. */
		if (!strchr("isl", entry.ftype)) {
			int dowarning = 0;
			int hasbadmode = 0;

			if (entry.ainfo.mode == NOMODE) {
				entry.ainfo.mode = CURMODE;
				dowarning = 1;
				hasbadmode = 1;
			}

			if (strcmp(entry.ainfo.owner, NOOWNER) == 0) {
				(void) strlcpy(entry.ainfo.owner, CUROWNER,
						sizeof (entry.ainfo.owner));
				dowarning = 1;
			}

			if (strcmp(entry.ainfo.group, NOGROUP) == 0) {
				(void) strlcpy(entry.ainfo.group, CURGROUP,
						sizeof (entry.ainfo.group));
				dowarning = 1;
			}


			if (dowarning) {
				if (hasbadmode)
					logerr(gettext(WRN_BADATTRM),
						"?",
					    entry.ainfo.owner,
					    entry.ainfo.group,
					    entry.path);
				else
					logerr(gettext(WRN_BADATTR),
						(int)entry.ainfo.mode,
						entry.ainfo.owner,
						entry.ainfo.group,
						entry.path);
			}
		}

		/*
		 * Resolve build parameters (initial lower case) in
		 * the link and target paths.
		 */
		if (strchr("ls", entry.ftype)) {
			if (!RELATIVE(entry.ainfo.local) ||
					PARAMETRIC(entry.ainfo.local)) {
				if (mappath(1, entry.ainfo.local)) {
					error(1);
					logerr(gettext(ERR_PATHVAR),
					    entry.ainfo.local);
					break;
				}

				canonize(entry.ainfo.local);
			}
		}

		/*
		 * Warn if top level file or directory is an install
		 * parameter
		 */
		if (entry.ftype != 'i') {
			if (entry.path[0] == '$' && isupper(entry.path[1]))
				fakebasedir = 1;
		}

		if (mappath(1, entry.path)) {
			error(1);
			logerr(gettext(ERR_PATHVAR), entry.path);
			break;
		}

		canonize(entry.path);
		if (ppkgmap(&entry, tmpfp)) {
			error(1);
			logerr(gettext(MSG_WRITE), errno);
			break;
		}
	}

	if (fakebasedir) {
		logerr(gettext(WRN_FAKEBD));
		fakebasedir = 0;
	}

	if (popenv())
		goto again;

	(void) fclose(tmpfp);
	environ = envsave; /* restore environment */

	return (errflg ? 1 : 0);
}

static char *
findfile(char *path, char *local)
{
	struct stat statbuf;
	static char host[PATH_MAX];
	register char *pt;
	char	temp[PATH_MAX], *basename;
	int	i;

	/*
	 * map any parameters specified in path to their corresponding values
	 * and make sure the path is in its canonical form; any parmeters for
	 * which a value is not defined will be left unexpanded. Since this
	 * is an actual search for a real file (which will not end up in the
	 * package) - we map ALL variables (both build and Install).
	 */
	(void) strlcpy(temp, (local && local[0] ? local : path), sizeof (temp));
	mappath(0, temp);
	canonize(temp);

	*host = '\0';
	if (rootlist[0] || (basedir && (*temp != '/'))) {
		/*
		 * search for path in the pseudo-root/basedir directory; note
		 * that package information files should NOT be included in
		 * this list
		 */
		if (entry.ftype != 'i')
			return (srchroot(temp, host));
	}

	/* looking for local object file  */
	if (local && *local) {
		basepath(temp, dname[nfp], NULL);
		/*
		 * If it equals "/dev/null", that just means it's an empty
		 * file. Otherwise, we'll really be writing stuff, so we need
		 * to verify the source.
		 */
		if (strcmp(temp, "/dev/null") != 0) {
			if (stat(temp, &statbuf) ||
			    !(statbuf.st_mode & S_IFREG)) {
				error(1);
				logerr(gettext(MSG_SRCHLOC), path);
				return (NULL);
			}
		}
		(void) strlcpy(host, temp, sizeof (host));
		return (host);
	}

	for (i = 0; rootp[nfp][i]; i++) {
		(void) snprintf(host, sizeof (host), "%s/%s", rootp[nfp][i],
		    temp + (*temp == '/' ? 1 : 0));
		if ((stat(host, &statbuf) == 0) &&
		    (statbuf.st_mode & S_IFREG)) {
			return (host);
		}
	}

	pt = strrchr(temp, '/');
	if (!pt++)
		pt = temp;

	basename = pt;

	for (i = 0; srchp[nfp][i]; i++) {
		(void) snprintf(host, sizeof (host), "%s/%s",
			srchp[nfp][i], basename);
		if ((stat(host, &statbuf) == 0) &&
		    (statbuf.st_mode & S_IFREG)) {
			return (host);
		}
	}

	/* check current directory as a last resort */
	(void) snprintf(host, sizeof (host), "%s/%s", dname[nfp], basename);
	if ((stat(host, &statbuf) == 0) && (statbuf.st_mode & S_IFREG))
		return (host);

	error(1);
	logerr(gettext(MSG_SRCHSRCH), path);
	return (NULL);
}

static void
dosearch(void)
{
	char temp[PATH_MAX], lookpath[PATH_MAX], *pt;
	int n;

	(void) fgets(temp, PATH_MAX, fp);
	translate(temp, lookpath);

	for (n = 0; srchp[nfp][n]; n++)
		free(srchp[nfp][n]);

	n = 0;
	pt = strtok(lookpath, separ);
	if (pt && *pt) {
		do {
			if (*pt != '/') {
				/* make relative path an absolute directory */
				(void) snprintf(temp, sizeof (temp),
						"%s/%s", dname[nfp], pt);
				pt = temp;
			}
			canonize(pt);
			srchp[nfp][n++] = qstrdup(pt);
		} while (pt = strtok(NULL, separ));
		srchp[nfp][n] = NULL;
	}
}

static void
dorsearch(void)
{
	char temp[PATH_MAX], lookpath[PATH_MAX], *pt;
	int n;

	(void) fgets(temp, PATH_MAX, fp);
	translate(temp, lookpath);

	for (n = 0; rootp[nfp][n]; n++)
		free(rootp[nfp][n]);

	n = 0;
	pt = strtok(lookpath, separ);
	do {
		if (*pt != '/') {
			/* make relative path an absolute directory */
			(void) snprintf(temp, sizeof (temp),
					"%s/%s", dname[nfp], pt);
			pt = temp;
		}
		canonize(pt);
		rootp[nfp][n++] = qstrdup(pt);
	} while (pt = strtok(NULL, separ));
	rootp[nfp][n] = NULL;
}

/*
 * This function reads the default mode, owner and group from the prototype
 * file and makes that available.
 */
static int
doattrib(void)
{
	char *pt, attrib[PATH_MAX], *mode_ptr, *owner_ptr, *group_ptr, *eol;
	int mode;
	char owner[ATRSIZ+1], group[ATRSIZ+1], attrib_save[(4*ATRSIZ)];

	(void) fgets(attrib, PATH_MAX, fp);

	(void) strlcpy(attrib_save, attrib, sizeof (attrib_save));

	/*
	 * Now resolve any variables that may be present. Start on group and
	 * move backward since that keeps the resolved string from
	 * overwriting any of the other entries. This is required since
	 * mapvar() writes the resolved string over the string provided.
	 */
	mode_ptr = strtok(attrib, " \t");
	owner_ptr = strtok(NULL, " \t");
	group_ptr = strtok(NULL, " \t\n");
	eol = strtok(NULL, " \t\n");
	if (strtok(NULL, " \t\n")) {
		/* extra tokens on the line */
		error(1);
		logerr(gettext(MSG_GARBDEFLT), (eol) ? eol :
		    gettext("unreadable at end of line"));
		return (1);
	}

	if (group_ptr && mapvar(1, group_ptr) == 0)
		(void) strncpy(group, group_ptr, ATRSIZ);
	else {
		error(1);
		logerr(gettext(MSG_GARBDEFLT), (attrib_save) ?
		    ((attrib_save[0]) ? attrib_save : gettext("none")) :
		    gettext("unreadable at group"));
		return (1);
	}

	if (owner_ptr && mapvar(1, owner_ptr) == 0)
		(void) strncpy(owner, owner_ptr, ATRSIZ);
	else {
		error(1);
		logerr(gettext(MSG_GARBDEFLT), (attrib_save) ?
		    ((attrib_save[0]) ? attrib_save : gettext("none")) :
		    gettext("unreadable at owner"));
		return (1);
	}

	/*
	 * For mode, don't use scanf, since we want to force an octal
	 * interpretation and need to limit the length of the owner and group
	 * specifications.
	 */
	if (mode_ptr && mapvar(1, mode_ptr) == 0)
		mode = strtol(mode_ptr, &pt, 8);
	else {
		error(1);
		logerr(gettext(MSG_GARBDEFLT), (attrib_save) ?
		    ((attrib_save[0]) ? attrib_save : gettext("none")) :
		    gettext("unreadable at mode"));
		return (1);
	}

	/* free any previous memory from qstrdup */
	if (d_own[nfp])
		free(d_own[nfp]);
	if (d_grp[nfp])
		free(d_grp[nfp]);

	d_mod[nfp] = mode;
	d_own[nfp] = qstrdup(owner);
	d_grp[nfp] = qstrdup(group);

	attrpreset(d_mod[nfp], d_own[nfp], d_grp[nfp]);

	return (0);
}

static void
doinclude(void)
{
	char	file[PATH_MAX];
	char	temp[PATH_MAX];

	(void) fgets(temp, PATH_MAX, fp);

	/*
	 * IMPORTANT NOTE: THE SIZE OF temp IS HARD CODED INTO THE
	 * FOLLOWING CALL TO fscanf -- YOU MUST CHANGE THIS LINE IF
	 * THE SIZE OF fscanf IS EVER CHANGED!!!
	 */
	(void) sscanf(temp, "%1024s", file);

	translate(file, temp);
	canonize(temp);

	if (*temp == NULL)
		return;
	else if (*temp != '/')
		(void) snprintf(file, sizeof (file), "%s/%s", dname[nfp], temp);
	else
		(void) strlcpy(file, temp, sizeof (file));

	canonize(file);
	pushenv(file);
}

/*
 * This does what mappath() does except that it does it for ALL variables
 * using whitespace as a token separator. This is used to resolve search
 * paths and assignment statements. It doesn't effect the build versus
 * install decision made for pkgmap variables.
 */
static void
translate(register char *pt, register char *copy)
{
	char *pt2, varname[MAX_PKG_PARAM_LENGTH];

token:
	/* eat white space */
	while (isspace(*pt))
		pt++;
	while (*pt && !isspace(*pt)) {
		if (*pt == '$') {
			pt2 = varname;
			while (*++pt && !strchr("/= \t\n\r", *pt))
				*pt2++ = *pt;
			*pt2 = '\0';
			if (pt2 = getenv(varname)) {
				while (*pt2)
					*copy++ = *pt2++;
			}
		} else
			*copy++ = *pt++;
	}
	if (*pt) {
		*copy++ = ' ';
		goto token;
	}
	*copy = '\0';
}

static void
error(int flag)
{
	static char *lasterr = NULL;

	if (lasterr != proto[nfp]) {
		lasterr = proto[nfp];
		(void) fprintf(stderr, gettext("ERROR in %s:\n"), lasterr);
	}
	if (flag)
		errflg++;
}

/* Set up defaults and change to the build directory. */
static void
pushenv(char *file)
{
	register char *pt;
	static char	topdir[PATH_MAX];

	if ((nfp+1) >= NRECURS) {
		error(1);
		logerr(gettext(MSG_NRECURS), NRECURS);
		logerr(gettext(MSG_IGNINCLUDE), file);
		return;
	}

	if (strcmp(file, "-") == 0) {
		fp = stdin;
	} else if ((fp = fopen(file, "r")) == NULL) {
		error(1);
		logerr(gettext(MSG_RDINCLUDE), file, errno);
		if (nfp >= 0) {
			logerr(gettext(MSG_IGNINCLUDE), file);
			fp = sfp[nfp];
			return;
		} else
			quit(1);
	}
	sfp[++nfp] = fp;
	srchp[nfp][0] = NULL;
	rootp[nfp][0] = NULL;
	d_mod[nfp] = (mode_t)(-1);
	d_own[nfp] = NULL;
	d_grp[nfp] = NULL;

	if (!nfp) {
		/* upper level proto file */
		proto[nfp] = file;
		if (file[0] == '/')
			pt = strcpy(topdir, file);
		else {
			/* path is relative to the prototype file specified */
			pt = getcwd(NULL, PATH_MAX);
			if (pt == NULL) {
				progerr(gettext(ERR_GETCWD), errno);
				quit(99);
			}
			(void) snprintf(topdir, sizeof (topdir),
						"%s/%s", pt, file);
		}
		if (pt = strrchr(topdir, '/'))
			*pt = '\0'; /* should always happen */
		if (topdir[0] == '\0')
			(void) strlcpy(topdir, "/", sizeof (topdir));
		dname[nfp] = topdir;
	} else {
		proto[nfp] = qstrdup(file);
		dname[nfp] = qstrdup(file);
		if (pt = strrchr(dname[nfp], '/'))
			*pt = '\0';
		else {
			/* same directory as the last prototype */
			free(dname[nfp]);
			dname[nfp] = qstrdup(dname[nfp-1]);
			return; /* no need to canonize() or chdir() */
		}
	}

	canonize(dname[nfp]);

	if (chdir(dname[nfp])) {
		error(1);
		logerr(gettext(MSG_CHDIR), dname[nfp]);
		if (!nfp)
			quit(1); /* must be able to cd to upper level */
		logerr(gettext(MSG_IGNINCLUDE), proto[nfp]);
		(void) popenv();
	}
}

/* Restore defaults and return to the prior directory. */
static int
popenv(void)
{
	int i;

	(void) fclose(fp);
	if (nfp) {
		if (proto[nfp])
			free(proto[nfp]);
		if (dname[nfp])
			free(dname[nfp]);
		for (i = 0; srchp[nfp][i]; i++)
			free(srchp[nfp][i]);
		for (i = 0; rootp[nfp][i]; i++)
			free(rootp[nfp][i]);
		if (d_own[nfp])
			free(d_own[nfp]);
		if (d_grp[nfp])
			free(d_grp[nfp]);

		fp = sfp[--nfp];

		if (chdir(dname[nfp])) {
			error(1);
			logerr(gettext(MSG_CHDIR), dname[nfp]);
			logerr(gettext(MSG_INCOMPLETE), proto[nfp]);
			return (popenv());
		}
		return (1);
	}
	return (0);
}

/*
 * If this parameter isn't already in place, put it into the local
 * environment. This means that command line directives override prototype
 * file directives.
 */
static void
lputenv(char *s)
{
	char *pt;
	int i;

	pt = strchr(s, '=');
	if (!pt)
		return;

	*pt = '\0';
	for (i = 0; i < nrdonly; i++) {
		if (strcmp(rdonly[i], s) == 0) {
			*pt = '=';
			return;
		}
	}
	*pt = '=';

	if (putenv(qstrdup(s))) {
		progerr(gettext(ERR_ENVBUILD), errno);
		quit(99);
	}
}

static char *
srchroot(char *path, char *copy)
{
	struct stat statbuf;
	int i;

	i = 0;
	root = rootlist[i++];
	do {
		/* convert with root & basedir info */
		cvtpath(path, copy);
		/* make it pretty again */
		canonize(copy);

		if (stat(copy, &statbuf) || !(statbuf.st_mode & S_IFREG)) {
			root = rootlist[i++];
			continue; /* host source must be a regular file */
		}
		return (copy);
	} while (root != NULL);
	error(1);
	logerr(gettext(MSG_SRCHROOT), path);
	return (NULL);
}