/*
 * 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 (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved.
 */

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


#include <stdio.h>
#include <time.h>
#include <wait.h>
#include <stdlib.h>
#include <unistd.h>
#include <ulimit.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include <dirent.h>
#include <string.h>
#include <signal.h>
#include <locale.h>
#include <libintl.h>
#include <pkgstrct.h>
#include <pkginfo.h>
#include <pkgdev.h>
#include <pkglocs.h>
#include <pwd.h>
#include <assert.h>
#include <instzones_api.h>
#include <pkglib.h>
#include <pkgweb.h>
#include <install.h>
#include <libinst.h>
#include <libadm.h>
#include <dryrun.h>
#include <messages.h>
#include "pkginstall.h"

/* imported globals */

extern char	**environ;
extern char	*pkgabrv;
extern char	*pkgname;
extern char	*pkgarch;
extern char	*pkgvers;
extern char	pkgwild[];

/* libadm(3LIB) */

extern char	*get_install_root(void);

/* quit.c */

extern sighdlrFunc_t	*quitGetTrapHandler(void);
extern void		quitSetDstreamTmpdir(char *a_dstreamTempDir);
extern void		quitSetInstallStarted(boolean_t a_installStarted);
extern void		quitSetPkgask(boolean_t a_pkgaskFlag);
extern void		quitSetSilentExit(boolean_t a_silentExit);
extern void		quitSetUpdatingExisting(boolean_t a_updatingExisting);
extern void		quitSetZoneName(char *a_zoneName);


/* static globals */

static char	path[PATH_MAX];
static int	ck_instbase(void);
static int	cp_pkgdirs(void);
static int	merg_pkginfos(struct cl_attr **pclass,
		struct cl_attr ***mpclass);
static int	merg_respfile(void);
static int	mv_pkgdirs(void);
static int	rdonly(char *p);
static void	ck_w_dryrun(int (*func)(), int type);
static void	copyright(void), usage(void);
static void	do_pkgask(boolean_t a_run_request_as_root);
static void	rm_icas(char *casdir);
static void	set_dryrun_dir_loc(void);
static void	unpack(void);

void	ckreturn(int retcode, char *msg);

static char	*ro_params[] = {
	"PATH", "NAME", "PKG", "PKGINST",
	"VERSION", "ARCH",
	"INSTDATE", "CATEGORY",
	NULL
};

/*
 * The following variable is the name of the device to which stdin
 * is connected during execution of a procedure script. PROC_STDIN is
 * correct for all ABI compliant packages. For non-ABI-compliant
 * packages, the '-o' command line switch changes this to PROC_XSTDIN
 * to allow user interaction during these scripts. -- JST
 */
static char	*script_in = PROC_STDIN;	/* assume ABI compliance */

static char	*pkgdrtarg = NULL;
static char	*pkgcontsrc = NULL;
static int	non_abi_scripts = 0;
static char	*respfile = NULL;
static char	*srcinst = NULL;
static int	suppressCopyright = 0;
static int	nointeract = 0;

/* exported globals */

char		*msgtext;
char		*pkginst = (char *)NULL;
char		*rw_block_size = NULL;
char		ilockfile[PATH_MAX];
char		instdir[PATH_MAX];
char		saveSpoolInstallDir[PATH_MAX];
char		pkgbin[PATH_MAX];
char		pkgloc[PATH_MAX];
char		pkgloc_sav[PATH_MAX];
char		pkgsav[PATH_MAX];
char		rlockfile[PATH_MAX];
char		savlog[PATH_MAX];
char		tmpdir[PATH_MAX];
int		dbchg;
int		dparts = 0;
int		dreboot = 0;
int		failflag = 0;
static int	askflag = 0;		/* non-zero if invoked as "pkgask" */
int		ireboot = 0;
int		maxinst = 1;
int		nocnflct;
int		nosetuid;
int		pkgverbose = 0;
int		rprcflag;
int		warnflag = 0;
struct admin	adm;
struct cfextra	**extlist; /* pkgmap structure and other path info */
struct pkgdev	pkgdev;
fsblkcnt_t	pkgmap_blks = 0LL;

/*
 * this global is referenced by:
 * getinst - [RW] - incremented if:
 * - installing same instance again
 * - overwriting an existing instance
 * - not installing a new instance
 * quit - [RO] - if non-zero and started non-zero:
 * - the new <PKGINST>/install directory and rename <PKGINST>/install.save
 * - back to <PKGINST>/install
 * main.c - [RO] - if non-zero:
 * - alter manner in which parameters are setup for scripts
 * - set UPDATE=yes in environment
 */
static int		update = 0;

/* Set by -O debug: debug output is enabled? */

static boolean_t	debugFlag = B_FALSE;

/* Set by the -G option: install packages in global zone only */

static boolean_t	globalZoneOnly = B_FALSE;

/* Set by -O preinstallcheck */

static boolean_t	preinstallCheck = B_FALSE;

/* Set by -O parent-zone-name= */

static char		*parentZoneName = (char *)NULL;

/* Set by -O parent-zone-type= */

static char		*parentZoneType = (char *)NULL;

#define	DEFPATH		"/sbin:/usr/sbin:/usr/bin"
#define	MALSIZ	4	/* best guess at likely maximum value of MAXINST */
#define	LSIZE	256	/* maximum line size supported in copyright file */

#if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
#define	TEXT_DOMAIN "SYS_TEST"
#endif

/* This is the text for the "-O parent-zone-name=" option */

#define	PARENTZONENAME	"parent-zone-name="
#define	PARENTZONENAME_LEN	((sizeof (PARENTZONENAME))-1)

/* This is the text for the "-O parent-zone-type=" option */

#define	PARENTZONETYPE	"parent-zone-type="
#define	PARENTZONETYPE_LEN	((sizeof (PARENTZONETYPE))-1)

static char *cpio_names[] = {
	"root",
	"root.cpio",
	"reloc",
	"reloc.cpio",
	"root.Z",
	"root.cpio.Z",
	"reloc.Z",
	"reloc.cpio.Z",
	0
};

int
main(int argc, char *argv[])
{
	VFP_T			*cfTmpVfp = NULL;	/* temporary */
	VFP_T			*pkgmapVfp;	/* "../pkgmap" file */
	boolean_t		run_request_as_root = B_FALSE;
	char			**np;
	char			*abi_comp_ptr;
	char			*abi_nm_ptr;
	char			*abi_sym_ptr;
	char			*admnfile = NULL;
	char			*device;
	char			*p;
	char			*prog_full_name = NULL;
	char			*pt;
	char			*updated = (char *)NULL;
	char			*vfstab_file = NULL;
	char			*zoneName = (char *)NULL;
	char			cbuf[MAX_PKG_PARAM_LENGTH];
	char			cmdbin[PATH_MAX];
	char			p_pkginfo[PATH_MAX];
	char			p_pkgmap[PATH_MAX];
	char			param[MAX_PKG_PARAM_LENGTH];
	char			script[PATH_MAX];
	char			altscript[PATH_MAX];
	char			*temp;
	int			c;
	int			disableAttributes = 0;
	int			err;
	int			init_install = 0;
	int			is_comp_arch;
	int			live_continue = 0;
	int			map_client = 1;
	int			n;
	int			nparts;
	int			npkgs;
	int			part;
	int			saveSpoolInstall = 0;
	boolean_t		cont_file_read;
	struct cl_attr		**pclass = NULL;
	struct cl_attr		**mergd_pclass = NULL;
	struct pkginfo		*prvinfo;
	struct sigaction	nact;
	struct sigaction	oact;
	struct stat		statb;
	struct statvfs64	svfsb;
	time_t			clock;
	PKGserver		pkgserver = NULL;

	/* reset contents of all default paths */

	(void) memset(path, '\0', sizeof (path));
	(void) memset(cmdbin, '\0', sizeof (cmdbin));
	(void) memset(script, '\0', sizeof (script));
	(void) memset(cbuf, '\0', sizeof (cbuf));
	(void) memset(param, '\0', sizeof (param));

	/* initialize locale environment */

	(void) setlocale(LC_ALL, "");
	(void) textdomain(TEXT_DOMAIN);

	/* initialize program name */

	prog_full_name = argv[0];
	(void) set_prog_name(argv[0]);

	/* tell spmi zones interface how to access package output functions */

	z_set_output_functions(echo, echoDebug, progerr);

	/* exit if not root */

	if (getuid()) {
		progerr(ERR_NOT_ROOT, get_prog_name());
		exit(1);
		/* NOTREACHED */
	}

	/*
	 * determine how pkgmap() deals with environment variables:
	 *  - MAPALL - resolve all variables
	 *  - MAPBUILD - map only build variables
	 *  - MAPINSTALL - map only install variables
	 *  - MAPNONE - map no variables
	 */

	setmapmode(MAPINSTALL);

	/* set sane umask */

	(void) umask(0022);

	/* initially no source "device" */

	device = NULL;

	/* reset npkgs (used as pkg remaining count in quit.c) */

	npkgs = 0;

	/* Read PKG_INSTALL_ROOT from the environment, if it's there. */

	if (!set_inst_root(getenv("PKG_INSTALL_ROOT"))) {
		progerr(ERR_ROOT_SET);
		exit(1);
	}

	pkgserversetmode(DEFAULTMODE);

	/* parse command line options */

	while ((c = getopt(argc, argv,
		"?Aa:B:b:Cc:D:d:eFf:GhIiMm:N:noO:p:R:r:StV:vyz")) != EOF) {

		switch (c) {

		/*
		 * Same as pkgadd: This disables attribute checking.
		 * It speeds up installation a little bit.
		 */
		case 'A':
			disableAttributes++;
			break;

		/*
		 * Same as pkgadd: Define an installation administration
		 * file, admin, to be used in place of the default
		 * administration file.  The token none overrides the use
		 * of any admin file, and thus forces interaction with the
		 * user. Unless a full path name is given, pkgadd first
		 * looks in the current working directory for the
		 * administration file.  If the specified administration
		 * file is not in the current working directory, pkgadd
		 * looks in the /var/sadm/install/admin directory for the
		 * administration file.
		 */
		case 'a':
			admnfile = flex_device(optarg, 0);
			break;

		/*
		 * Same as pkgadd: control block size given to
		 * pkginstall - block size used in read()/write() loop;
		 * default is st_blksize from stat() of source file.
		 */
		case 'B':
			rw_block_size = optarg;
			break;

		/*
		 * Same as pkgadd: location where executables needed
		 * by procedure scripts can be found
		 * default is /usr/sadm/install/bin.
		 */
		case 'b':
			if (!path_valid(optarg)) {
				progerr(ERR_PATH, optarg);
				exit(1);
			}
			if (isdir(optarg) != 0) {
				char *p = strerror(errno);
				progerr(ERR_CANNOT_USE_DIR, optarg, p);
				exit(1);
			}
			(void) strlcpy(cmdbin, optarg, sizeof (cmdbin));
			break;

		/*
		 * Same as pkgadd: This disables checksum tests on
		 * the source files. It speeds up installation a little bit.
		 */
		case 'C':
			(void) checksum_off();
			break;

		/*
		 * Same as pkgadd: This allows designation of a
		 * continuation file. It is the same format as a dryrun file
		 * but it is used to take up where the dryrun left off.
		 */
		case 'c':
			pkgcontsrc = optarg;
			set_continue_mode();
			set_dr_info(DR_TYPE, INSTALL_TYPE);
			init_contfile(pkgcontsrc);
			break;

		/*
		 * Same as pkgadd: This allows designation of a
		 * dryrun file. This pkgadd will create dryrun files
		 * in the directory provided.
		 */
		case 'D':
			pkgdrtarg = optarg;
			set_dryrun_mode();
			set_dr_info(DR_TYPE, INSTALL_TYPE);
			break;

		/*
		 * Same as pkgadd: Install or copy a package from
		 * device. device can be a full path name to a directory
		 * or the identifiers for tape, floppy disk, or removable
		 * disk - for example, /var/tmp or /floppy/floppy_name.
		 * It can also be a device alias - for example,
		 * /floppy/floppy0, or a datastream created by pkgtrans.
		 */
		case 'd':
			device = flex_device(optarg, 1);
			break;

		/*
		 * Different from pkgadd: disable the 32 char name
		 * limit extension
		 */
		case 'e':
			(void) set_ABI_namelngth();
			break;

		/*
		 * Different from pkgadd: specify file system type for
		 * the package device. Must be used with -m.
		 */
		case 'f':
			pkgdev.fstyp = optarg;
			break;

		/*
		 * Same as pkgadd: install package in global zone only.
		 */
		case 'G':
			globalZoneOnly = B_TRUE;
			break;

		/*
		 * Same as pkgadd: Enable hollow package support. When
		 * specified, for any package that has SUNW_PKG_HOLLOW=true:
		 *  Do not calculate and verify package size against target.
		 *  Do not run any package procedure or class action scripts.
		 *  Do not create any target directories.
		 *  Do not perform any script locking.
		 *  Do not install any components of any package.
		 *  Do not output any status or database update messages.
		 */
		case 'h':
			set_depend_pkginfo_DB(B_TRUE);
			break;

		/*
		 * Same as pkgadd: Informs scripts that this is
		 * an initial install by setting the environment parameter
		 * PKG_INIT_INSTALL=TRUE for all scripts. They may use it as
		 * they see fit, safe in the knowledge that the target
		 * filesystem is tabula rasa.
		 */
		case 'I':
			init_install++;
			break;

		/*
		 * Different from pkgadd: use by pkgask.
		 */
		case 'i':
			askflag++;
			quitSetPkgask(B_TRUE);
			break;

		/*
		 * Same as pkgadd: Instruct pkgadd not to use the
		 * $root_path/etc/vfstab file for determining the client's
		 * mount points. This option assumes the mount points are
		 * correct on the server and it behaves consistently with
		 * Solaris 2.5 and earlier releases.
		 */
		case 'M':
			map_client = 0;
			break;

		/*
		 * Different from pkgadd: specify device to use for package
		 * source.
		 */
		case 'm':
			pkgdev.mount = optarg;
			pkgdev.rdonly++;
			pkgdev.mntflg++;
			break;

		/*
		 * Different from pkgadd: specify program name to use
		 * for messages.
		 */
		case 'N':
			(void) set_prog_name(optarg);
			break;

		/*
		 * Same as pkgadd: installation occurs in
		 * non-interactive mode.  Suppress output of the list of
		 * installed files. The default mode is interactive.
		 */
		case 'n':
			nointeract++;
			(void) echoSetFlag(B_FALSE);
			break;

		/*
		 * Almost same as pkgadd: the -O option allows the behavior
		 * of the package tools to be modified. Recognized options:
		 * -> debug
		 * ---> enable debugging output
		 * -> preinstallcheck
		 * ---> perform a "pre installation" check of the specified
		 * ---> package - suppress all regular output and cause a
		 * ---> series of one or more "name=value" pair format lines
		 * ---> to be output that describes the "installability" of
		 * ---> the specified package
		 * -> enable-hollow-package-support
		 * --> Enable hollow package support. When specified, for any
		 * --> package that has SUNW_PKG_HOLLOW=true:
		 * --> Do not calculate and verify package size against target
		 * --> Do not run any package procedure or class action scripts
		 * --> Do not create or remove any target directories
		 * --> Do not perform any script locking
		 * --> Do not install or uninstall any components of any package
		 * --> Do not output any status or database update messages
		 */
		case 'O':
			for (p = strtok(optarg, ","); p != (char *)NULL;
				p = strtok(NULL, ",")) {

				/* process debug option */

				if (strcmp(p, "debug") == 0) {
					/* set debug flag/enable debug output */
					if (debugFlag == B_TRUE) {
						smlSetVerbose(B_TRUE);
					}
					debugFlag = B_TRUE;
					(void) echoDebugSetFlag(debugFlag);

					/* debug info on arguments to pkgadd */
					for (n = 0; n < argc && argv[n]; n++) {
						echoDebug(DBG_ARG, n, argv[n]);
					}

					continue;
				}

				/* process enable-hollow-package-support opt */

				if (strcmp(p,
					"enable-hollow-package-support") == 0) {
					set_depend_pkginfo_DB(B_TRUE);
					continue;
				}

				/* process preinstallcheck option */

				if (strcmp(p, "preinstallcheck") == 0) {
					preinstallCheck = B_TRUE;
					nointeract++;	/* -n */
					suppressCopyright++;	/* -S */
					quitSetSilentExit(B_TRUE);
					continue;
				}

				/* process addzonename option */

				if (strcmp(p, "addzonename") == 0) {
					/*
					 * set zone name to add to messages;
					 * first look in the current environment
					 * and use the default package zone name
					 * if it is set; otherwise, use the name
					 * of the current zone
					 */
					zoneName =
						getenv(PKG_ZONENAME_VARIABLE);

					if ((zoneName == (char *)NULL) ||
							(*zoneName == '\0')) {
						zoneName = z_get_zonename();
					}

					if (zoneName != (char *)NULL) {
						if (*zoneName != '\0') {
							quitSetZoneName(
								zoneName);
						} else {
							zoneName = (char *)NULL;
						}
					}
					continue;
				}

				/* process parent-zone-name option */

				if (strncmp(p, PARENTZONENAME,
						PARENTZONENAME_LEN) == 0) {
					parentZoneName = p+PARENTZONENAME_LEN;
					continue;
				}

				/* process parent-zone-type option */

				if (strncmp(p, PARENTZONETYPE,
						PARENTZONETYPE_LEN) == 0) {
					parentZoneType = p+PARENTZONETYPE_LEN;
					continue;
				}

				if (strncmp(p, PKGSERV_MODE,
				    PKGSERV_MODE_LEN) == 0) {
					pkgserversetmode(pkgparsemode(p +
					    PKGSERV_MODE_LEN));
					continue;
				}

				/* option not recognized - issue warning */

				progerr(ERR_INVALID_O_OPTION, p);
				continue;

			}
			break;

		/*
		 * Different from pkgadd: This is an old non-ABI package
		 */
		case 'o':
			non_abi_scripts++;
			break;

		/*
		 * Different from pkgadd: specify number of parts to package.
		 */
		case 'p':
			dparts = ds_getinfo(optarg);
			break;

		/*
		 * Same as pkgadd: Define the full path name of a
		 * directory to use as the root_path.  All files,
		 * including package system information files, are
		 * relocated to a directory tree starting in the specified
		 * root_path. The root_path may be specified when
		 * installing to a client from a server (for example,
		 * /export/root/client1).
		 */
		case 'R':
			if (!set_inst_root(optarg)) {
				progerr(ERR_ROOT_CMD);
				exit(1);
			}
			break;

		/*
		 * Same as pkgadd: Identify a file or directory which
		 * contains output from a previous pkgask(1M)
		 * session. This file supplies the interaction responses
		 * that would be requested by the package in interactive
		 * mode. response must be a full pathname.
		 */
		case 'r':
			respfile = flex_device(optarg, 2);
			break;

		/*
		 * Same as pkgadd: suppress copyright notice being
		 * output during installation.
		 */
		case 'S':
			suppressCopyright++;
			break;

		/*
		 * Same as pkgadd: disable save spool area creation;
		 * do not spool any partial package contents, that is,
		 * suppress the creation and population of the package save
		 * spool area (var/sadm/pkg/PKG/save/pspool/PKG).
		 */
		case 't':
			disable_spool_create();
			break;

		/*
		 * Same as pkgadd: Specify an alternative fs_file to map
		 * the client's file systems.  For example, used in
		 * situations where the $root_path/etc/vfstab file is
		 * non-existent or unreliable. Informs the pkginstall
		 * portion to mount up a client filesystem based upon the
		 * supplied vfstab-like file of stable format.
		 */
		case 'V':
			vfstab_file = flex_device(optarg, 2);
			map_client = 1;
			break;

		/*
		 * Same as pkgadd: Trace all of the scripts that get
		 * executed by pkgadd, located in the pkginst/install
		 * directory. This option is used for debugging the
		 * procedural and non-procedural scripts
		 */
		case 'v':
			pkgverbose++;
			break;

		/*
		 * Different from pkgadd: process this package using
		 * old non-ABI symlinks
		 */
		case 'y':
			set_nonABI_symlinks();
			break;

		/*
		 * Same as pkgadd: perform fresh install from
		 * package save spool area. When set, the package contents
		 * are installed from the package spool save area instead
		 * of from the package root area, so that the original
		 * source packages are not required to install the
		 * package. If the -h option is also specified and the
		 * package is hollow, then this option is ignored. When -z
		 * is specified:
		 *  - Editable files are installed from the package instance
		 *    save area.
		 *  - Volatile files are installed from the package instance
		 *    save area.
		 *  - Executable and data files are installed from the final
		 *    installed location as specified in the pkgmap file.
		 *  - Installation scripts are run from the package spool
		 *    save area.
		 */
		case 'z':
			saveSpoolInstall++;
			break;

		/*
		 * unrecognized option
		 */
		default:
			usage();
			/*NOTREACHED*/
			/*
			 * Although usage() calls a noreturn function,
			 * needed to add return (1);  so that main() would
			 * pass compilation checks. The statement below
			 * should never be executed.
			 */
			return (1);
		}
	}

	/*
	 * ********************************************************************
	 * validate command line options
	 * ********************************************************************
	 */

	/* set "debug echo" flag according to setting of "-O debug" option */

	(void) echoDebugSetFlag(debugFlag);
	(void) log_set_verbose(debugFlag);

	/* output entry debugging information */

	if (z_running_in_global_zone()) {
		echoDebug(DBG_ENTRY_IN_GZ, prog_full_name);
	} else {
		echoDebug(DBG_ENTRY_IN_LZ, prog_full_name, getzoneid(),
		    z_get_zonename());
	}

	if (in_continue_mode() && !in_dryrun_mode()) {
		progerr(ERR_LIVE_CONTINUE_NOT_SUPPORTED);
		usage();
		/*NOTREACHED*/
	}

	/* pkgask requires a response file */

	if (askflag && (respfile == NULL)) {
		usage();
		/*NOTREACHED*/
	}

	/* if device specified, set appropriate device in pkgdev */

	if (device) {
		if (pkgdev.mount) {
			pkgdev.bdevice = device;
		} else {
			pkgdev.cdevice = device;
		}
	}

	/* if file system type specified, must have a device to mount */

	if (pkgdev.fstyp && !pkgdev.mount) {
		progerr(ERR_F_REQUIRES_M);
		usage();
		/*NOTREACHED*/
	}

	/* BEGIN DATA GATHERING PHASE */

	/*
	 * Get the mount table info and store internally.
	 */
	cont_file_read = B_FALSE;
	if (in_continue_mode()) {
		int error;
		cont_file_read = read_continuation(&error);
		if (error == -1) {
			quit(99);
			/*NOTREACHED*/
		}
		if (!in_dryrun_mode()) {
			live_continue = 1;
		}
	}
	/* Read the mount table if not done in continuation mode */
	if (!cont_file_read) {
		if (get_mntinfo(map_client, vfstab_file)) {
			quit(99);
			/*NOTREACHED*/
		}
	}

	/*
	 * This function defines the standard /var/... directories used later
	 * to construct the paths to the various databases.
	 */

	set_PKGpaths(get_inst_root());

	/*
	 * If this is being installed on a client whose /var filesystem is
	 * mounted in some odd way, remap the administrative paths to the
	 * real filesystem. This could be avoided by simply mounting up the
	 * client now; but we aren't yet to the point in the process where
	 * modification of the filesystem is permitted.
	 */
	if (is_an_inst_root()) {
		int fsys_value;

		fsys_value = fsys(get_PKGLOC());
		if (use_srvr_map_n(fsys_value))
			set_PKGLOC(server_map(get_PKGLOC(), fsys_value));

		fsys_value = fsys(get_PKGADM());
		if (use_srvr_map_n(fsys_value))
			set_PKGADM(server_map(get_PKGADM(), fsys_value));
	}

	/*
	 * Initialize pkginfo PKGSAV entry, just in case we dryrun to
	 * somewhere else.
	 */
	set_infoloc(get_PKGLOC());

	/* pull off directory and package name from end of command line */

	switch (argc-optind) {
	case 0:	/* missing directory and package instance */
		progerr(ERR_MISSING_DIR_AND_PKG);
		usage();
		/*NOTREACHED*/
	case 1: /* missing package instance */
		progerr(ERR_MISSING_PKG_INSTANCE);
		usage();
		/*NOTREACHED*/
	case 2:	/* just right! */
		pkgdev.dirname = argv[optind++];
		srcinst = argv[optind++];
		break;
	default:	/* too many args! */
		progerr(ERR_TOO_MANY_CMD_ARGS);
		usage();
		break;
	}

	(void) pkgparam(NULL, NULL);  /* close up prior pkg file if needed */

	/*
	 * Initialize installation admin parameters by reading
	 * the adminfile.
	 */

	if (!askflag && !live_continue) {
		echoDebug(DBG_PKGINSTALL_ADMINFILE, admnfile ? admnfile : "");
		setadminFile(admnfile);
	}

	/*
	 * about to perform first operation that could be modified by the
	 * preinstall check option - if preinstall check is selected (that is,
	 * only gathering dependencies), then output a debug message to
	 * indicate that the check is beginning. Also turn echo() output
	 * off and set various other flags.
	 */

	if (preinstallCheck == B_TRUE) {
		(void) echoSetFlag(B_FALSE);
		echoDebug(DBG_PKGINSTALL_PREINSCHK,
			pkginst ? pkginst : (srcinst ? srcinst : ""),
			zoneName ? zoneName : "global");
		cksetPreinstallCheck(B_TRUE);
		cksetZoneName(zoneName);
		/* inform quit that the install has started */
		quitSetInstallStarted(B_TRUE);
	}

	/*
	 * validate the "rscriptalt" admin file setting
	 * The rscriptalt admin file parameter may be set to either
	 * RSCRIPTALT_ROOT or RSCRIPTALT_NOACCESS:
	 * --> If rscriptalt is not set, or is set to RSCRIPTALT_NOACCESS,
	 * --> or is set to any value OTHER than RSCRIPTALT_ROOT, then
	 * --> assume that the parameter is set to RSCRIPTALT_NOACCESS
	 * If rscriptalt is set to RSCRIPTALT_ROOT, then run request scripts
	 * as the "root" user if user "install" is not defined.
	 * Otherwise, assume rscriptalt is set to RSCRIPTALT_NOACCESS, and run
	 * request scripts as the "alternative" user if user "install" is not
	 * defined, as appropriate for the current setting of the NONABI_SCRIPTS
	 * environment variable.
	 */

	if (ADMSET(RSCRIPTALT)) {
		p = adm.RSCRIPTALT;
		echoDebug(DBG_PKGINSTALL_RSCRIPT_SET_TO, RSCRIPTALT_KEYWORD, p);
		if (strcasecmp(p, RSCRIPTALT_ROOT) == 0) {
			/* rscriptalt=root */
			run_request_as_root = B_TRUE;
		} else if (strcasecmp(p, RSCRIPTALT_NOACCESS) == 0) {
			/* rscriptalt=noaccess */
			run_request_as_root = B_FALSE;
		} else {
			/* rscriptalt=??? */
			logerr(WRN_RSCRIPTALT_BAD, RSCRIPTALT_KEYWORD, p,
				RSCRIPTALT_ROOT, RSCRIPTALT_NOACCESS);
			logerr(WRN_RSCRIPTALT_USING, RSCRIPTALT_KEYWORD,
				RSCRIPTALT_NOACCESS);
			run_request_as_root = B_FALSE;
		}
	} else {
		/* rscriptalt not set - assume rscriptalt=noaccess */
		echoDebug(DBG_PKGINSTALL_RSCRIPT_NOT_SET, RSCRIPTALT_KEYWORD);
		run_request_as_root = B_FALSE;
	}

	echoDebug(DBG_PKGINSTALL_RSCRIPT_IS_ROOT, run_request_as_root);

	/*
	 * hook SIGINT and SIGHUP interrupts into quit.c's trap handler
	 */

	/* hold SIGINT/SIGHUP interrupts */

	(void) sighold(SIGHUP);
	(void) sighold(SIGINT);

	/* connect quit.c:trap() to SIGINT */

	nact.sa_handler = quitGetTrapHandler();
	nact.sa_flags = SA_RESTART;
	(void) sigemptyset(&nact.sa_mask);

	(void) sigaction(SIGINT, &nact, &oact);

	/* connect quit.c:trap() to SIGHUP */

	nact.sa_handler = quitGetTrapHandler();
	nact.sa_flags = SA_RESTART;
	(void) sigemptyset(&nact.sa_mask);

	(void) sigaction(SIGHUP, &nact, &oact);

	/* release hold on signals */

	(void) sigrelse(SIGHUP);
	(void) sigrelse(SIGINT);

	/*
	 * create required /var... directories if they do not exist;
	 * this function will call quit(99) if any required path cannot
	 * be created.
	 */

	ckdirs();

	tzset();

	/*
	 * create path to temporary directory "installXXXXXX" - if TMPDIR
	 * environment variable is set, create the directory in $TMPDIR;
	 * otherwise, create the directory in P_tmpdir.
	 */

	pt = getenv("TMPDIR");
	(void) snprintf(tmpdir, sizeof (tmpdir), "%s/installXXXXXX",
		((pt != (char *)NULL) && (*pt != '\0')) ? pt : P_tmpdir);

	echoDebug(DBG_PKGINSTALL_TMPDIR, tmpdir);

	if ((mktemp(tmpdir) == NULL) || mkdir(tmpdir, 0771)) {
		progerr(ERR_MKDIR, tmpdir);
		quit(99);
		/*NOTREACHED*/
	}

	/*
	 * if the package device is a file containing a package stream,
	 * unpack the stream into a temporary directory
	 */

	if ((isdir(pkgdev.dirname) != 0) &&
		(pkgdev.cdevice == (char *)NULL) &&
		(pkgdev.bdevice == (char *)NULL) &&
		(isfile((char *)NULL, pkgdev.dirname) == 0)) {

		char		*idsName = (char *)NULL;
		char		*pkgnames[2];
		char		*device = pkgdev.dirname;
		boolean_t	b;

		echoDebug(DBG_PKGINSTALL_DS_ISFILE, pkgdev.dirname);

		/*
		 * validate the package source device - return pkgdev info that
		 * describes the package source device.
		 */

		if (devtype(device, &pkgdev)) {
			progerr(ERR_BAD_DEVICE, device);
			quit(99);
			/* NOTREACHED */
		}

		/* generate the list of packages to verify */

		pkgnames[0] = srcinst;
		pkgnames[1] = (char *)NULL;

		b = open_package_datastream(1, pkgnames, (char *)NULL,
			pkgdev.dirname, (int *)NULL, &idsName, tmpdir, &pkgdev,
			1);

		if (b == B_FALSE) {
			progerr(ERR_CANNOT_OPEN_PKG_STREAM,
				pkgdev.dirname ? pkgdev.dirname : "?");
			quit(99);
			/*NOTREACHED*/
		}

		/* make sure temporary directory is removed on exit */

		quitSetDstreamTmpdir(pkgdev.dirname);

		/* unpack the package instance from the data stream */

		b = unpack_package_from_stream(idsName, srcinst,
							pkgdev.dirname);
		if (b == B_FALSE) {
			progerr(ERR_CANNOT_UNPACK_PKGSTRM,
				srcinst ? srcinst : "?",
				idsName ? idsName : "?",
				pkgdev.dirname ? pkgdev.dirname : "?");
			quit(99);
			/*NOTREACHED*/
		}

		/* close the datastream - no longer needed */

		echoDebug(DBG_CLOSING_STREAM, idsName, pkgdev.dirname);
		(void) ds_close(1);
	}

	if (snprintf(instdir, PATH_MAX, "%s/%s", pkgdev.dirname, srcinst)
	    >= PATH_MAX) {
		progerr(ERR_SNPRINTF, instdir);
		quit(99);
		/*NOTREACHED*/
	}

	zoneName = getenv(PKG_ZONENAME_VARIABLE);

	/*
	 * If the environment has a CLIENT_BASEDIR, that takes precedence
	 * over anything we will construct. We need to save it here because
	 * in three lines, the current environment goes away.
	 */
	(void) set_env_cbdir();	/* copy over environ */

	getuserlocale();

	/*
	 * current environment has been read; clear environment out
	 * so putparam() can be used to populate the new environment
	 * to be passed to any executables/scripts.
	 */

	environ = NULL;

	/* write parent condition information to environment */

	putConditionInfo(parentZoneName, parentZoneType);

	putuserlocale();

	if (init_install) {
		putparam("PKG_INIT_INSTALL", "TRUE");
	}

	if (is_an_inst_root()) {
		export_client_env(get_inst_root());
	}

	if (zoneName != (char *)NULL) {
		putparam(PKG_ZONENAME_VARIABLE, zoneName);
	}

	putparam("INST_DATADIR", pkgdev.dirname);

	if (non_abi_scripts) {
		putparam("NONABI_SCRIPTS", "TRUE");
	}

	if (nonABI_symlinks()) {
		putparam("PKG_NONABI_SYMLINKS", "TRUE");
	}

	if (get_ABI_namelngth()) {
		putparam("PKG_ABI_NAMELENGTH", "TRUE");
	}

	/* establish path and oambase */

	if (cmdbin[0] == '\0') {
		(void) strlcpy(cmdbin, PKGBIN, sizeof (cmdbin));
	}

	(void) snprintf(path, sizeof (path), "%s:%s", DEFPATH, cmdbin);

	putparam("PATH", path);

	putparam("OAMBASE", OAMBASE);

	(void) snprintf(p_pkginfo, sizeof (p_pkginfo),
			"%s/%s", instdir, PKGINFO);
	(void) snprintf(p_pkgmap, sizeof (p_pkgmap),
			"%s/%s", instdir, PKGMAP);

	/* Read the environment (from pkginfo or '-e') ... */
	abi_nm_ptr = getenv("PKG_ABI_NAMELENGTH");

	/* Disable the 32 char name limit extension */
	if (abi_nm_ptr && strncasecmp(abi_nm_ptr, "TRUE", 4) == 0) {
		(void) set_ABI_namelngth();
	}

	/*
	 * This tests the pkginfo and pkgmap files for validity and
	 * puts all delivered pkginfo variables (except for PATH) into
	 * our environment. This is where a delivered pkginfo BASEDIR
	 * would come from. See set_basedirs() below.
	 */

	if (pkgenv(srcinst, p_pkginfo, p_pkgmap)) {
		quit(1);
		/*NOTREACHED*/
	}

	echo("\n%s(%s) %s", pkgname, pkgarch, pkgvers);

	/*
	 * If this script was invoked by 'pkgask', just
	 * execute request script and quit (do_pkgask()).
	 */

	if (askflag) {
		do_pkgask(run_request_as_root);
	}

	/* validate package contents file */

	if (vcfile() == 0) {
		quit(99);
	}

	/* if not in dryrun mode aquire packaging lock */

	if (!in_dryrun_mode()) {
		/* acquire the package lock - at install initialization */
		if (!lockinst(get_prog_name(), srcinst, "install-initial")) {
			quit(99);
			/*NOTREACHED*/
		}
	}

	/*
	 * Now do all the various setups based on ABI compliance
	 */

	/* Read the environment (from pkginfo or '-o') ... */
	abi_comp_ptr = getenv("NONABI_SCRIPTS");

	/* Read the environment (from pkginfo or '-y') ... */
	abi_sym_ptr = getenv("PKG_NONABI_SYMLINKS");

	/* bug id 4244631, not ABI compliant */
	if (abi_comp_ptr && strncasecmp(abi_comp_ptr, "TRUE", 4) == 0) {
		script_in = PROC_XSTDIN;
		non_abi_scripts = 1;
	}

	/* Set symlinks to be processed the old way */
	if (abi_sym_ptr && strncasecmp(abi_sym_ptr, "TRUE", 4) == 0) {
		set_nonABI_symlinks();
	}

	/*
	 * At this point, script_in, non_abi_scripts & the environment are
	 * all set correctly for the ABI status of the package.
	 */

	if (pt = getenv("MAXINST")) {
		maxinst = atol(pt);
	}

	/*
	 * See if were are installing a package that only wants to update
	 * the database or only install files associated with CAS's. We
	 * only check the PKG_HOLLOW_VARIABLE variable if told to do so by
	 * the caller.
	 */

	if (is_depend_pkginfo_DB()) {
		pt = getenv(PKG_HOLLOW_VARIABLE);
		if ((pt != NULL) && (strncasecmp(pt, "true", 4) == 0)) {
			echoDebug(DBG_PKGREMOVE_HOLLOW_ENABLED);
			if (disableAttributes) {
				disable_attribute_check();
			}

			/*
			 * this is a hollow package and hollow package support
			 * is enabled -- override admin settings to suppress
			 * checks that do not make sense since no scripts will
			 * be executed and no files will be installed.
			 */

			setadminSetting("conflict", "nocheck");
			setadminSetting("setuid", "nocheck");
			setadminSetting("action", "nocheck");
			setadminSetting("partial", "nocheck");
			setadminSetting("space", "nocheck");
			setadminSetting("authentication", "nocheck");
		} else {
			echoDebug(DBG_PKGREMOVE_HOLLOW_DISABLED);
			set_depend_pkginfo_DB(B_FALSE);
		}
	}

	/*
	 * if performing a fresh install to a non-global zone, and doing
	 * more than just updating the package database (that is, the
	 * package to install is NOT "hollow"), then set the global flag
	 * that directs installation is from partially spooled packages
	 * (that is, packages installed in the global zone).
	 */

	if (saveSpoolInstall && (!is_depend_pkginfo_DB())) {
		set_partial_inst();
	} else {
		saveSpoolInstall = 0;
	}

	/*
	 * verify that we are not trying to install an
	 * INTONLY package with no interaction
	 */

	if (pt = getenv("INTONLY")) {
		if (askflag || nointeract) {
			progerr(ERR_INTONLY, pkgabrv ? pkgabrv : "?");
			quit(1);
			/*NOTREACHED*/
		}
	}

	if (!suppressCopyright && !pkgdev.cdevice) {
		copyright();
	}

	/*
	 * inspect the system to determine if any instances of the
	 * package being installed already exist on the system
	 */

	prvinfo = (struct pkginfo *)calloc(MALSIZ, sizeof (struct pkginfo));
	if (prvinfo == NULL) {
		progerr(ERR_MEMORY, errno);
		quit(99);
		/*NOTREACHED*/
	}

	for (;;) {
		if (pkginfo(&prvinfo[npkgs], pkgwild, NULL, NULL)) {
			if ((errno == ESRCH) || (errno == ENOENT)) {
				break;
			}
			progerr(ERR_SYSINFO, errno);
			quit(99);
			/*NOTREACHED*/
		}
		if ((++npkgs % MALSIZ) == 0) {
			prvinfo = (struct pkginfo *)realloc(prvinfo,
				(npkgs+MALSIZ) * sizeof (struct pkginfo));
			if (prvinfo == NULL) {
				progerr(ERR_MEMORY, errno);
				quit(99);
				/*NOTREACHED*/
			}
		}
	}

	/*
	 * Determine the correct package instance based on how many packages are
	 * already installed. If there are none (npkgs == 0), getinst() just
	 * returns the package abbreviation. Otherwise, getinst() interacts with
	 * the user (or reads the admin file) to determine if an instance which
	 * is already installed should be overwritten, or possibly install a new
	 * instance of this package
	 */

	pkginst = getinst(&update, prvinfo, npkgs, preinstallCheck);

	/* set "update flag" if updating an existing instance of this package */

	if (update) {
		setUpdate();
	}

	/*
	 * Some pkgs (SUNWcsr) already spooled to the zone, check the
	 * value of UPDATE in their postinstall script.  After a pkg
	 * has been patched UPDATE exists statically in the pkginfo
	 * file and this value must be reset when installing a zone.
	 */

	if (saveSpoolInstall != 0 && !isUpdate()) {
		putparam("UPDATE", "");
	}

	/* inform quit() if updating existing or installing new instance */

	quitSetUpdatingExisting(update ? B_TRUE : B_FALSE);

	if (respfile) {
		(void) set_respfile(respfile, pkginst, RESP_RO);
	}

	(void) snprintf(pkgloc, sizeof (pkgloc),
			"%s/%s", get_PKGLOC(), pkginst);

	(void) snprintf(pkgbin, sizeof (pkgbin),
			"%s/install", pkgloc);

	(void) snprintf(pkgsav, sizeof (pkgsav),
			"%s/save", pkgloc);

	if (snprintf(saveSpoolInstallDir, PATH_MAX, "%s/pspool/%s", pkgsav,
			pkginst) < 0) {
		progerr(ERR_SNPRINTF, saveSpoolInstallDir);
		quit(99);
		/*NOTREACHED*/
	}

	(void) snprintf(ilockfile, sizeof (ilockfile),
			"%s/!I-Lock!", pkgloc);
	(void) snprintf(rlockfile, sizeof (rlockfile),
			"%s/!R-Lock!", pkgloc);
	(void) snprintf(savlog, sizeof (savlog),
			"%s/logs/%s", get_PKGADM(), pkginst);

	putparam("PKGINST", pkginst);
	putparam("PKGSAV", pkgsav);

	/*
	 * Be sure request script has access to PKG_INSTALL_ROOT if there is
	 * one
	 */

	put_path_params();

	if (!map_client) {
		putparam("PKG_NO_UNIFIED", "TRUE");
	}

	/*
	 * This maps the client filesystems into the server's space.
	 */

	if (map_client && !mount_client()) {
		logerr(MSG_MANMOUNT);
	}

	/*
	 * If this is an UPDATE then either this is exactly the same version
	 * and architecture of an installed package or a different package is
	 * intended to entirely replace an installed package of the same name
	 * with a different VERSION or ARCH string.
	 * Don't merge any databases if only gathering dependencies.
	 */

	if ((preinstallCheck == B_FALSE) && (update)) {
		/*
		 * If this version and architecture is already installed,
		 * merge the installed and installing parameters and inform
		 * all procedure scripts by defining UPDATE in the
		 * environment.
		 */

		if (is_samepkg()) {
			/*
			 * If it's the same ARCH and VERSION, then a merge
			 * and copy operation is necessary.
			 */

			if (n = merg_pkginfos(pclass, &mergd_pclass)) {
				quit(n);
				/*NOTREACHED*/
			}

			if (n = cp_pkgdirs()) {
				quit(n);
				/*NOTREACHED*/
			}

		} else {
			/*
			 * If it's a different ARCH and/or VERSION then this
			 * is an "instance=overwrite" situation. The
			 * installed base needs to be confirmed and the
			 * package directories renamed.
			 */

			if (n = ck_instbase()) {
				quit(n);
				/*NOTREACHED*/
			}

			if (n = mv_pkgdirs()) {
				quit(n);
				/*NOTREACHED*/
			}
		}

		putparam("UPDATE", "yes");

	}

	if (in_dryrun_mode()) {
		set_dryrun_dir_loc();
	}

	if (preinstallCheck == B_FALSE) {
		/*
		 * Determine if the package has been partially installed on or
		 * removed from this system.
		 */
		ck_w_dryrun(ckpartial, PARTIAL);

		/*
		 * make sure current runlevel is appropriate
		 */
		ck_w_dryrun(ckrunlevel, RUNLEVEL);
	} else {
		int	r;

		/*
		 * Just gathering dependencies - determine if the package has
		 * been partially installed on or removed from this system and
		 * output information to stdout
		 */
		r = ckpartial();
		(void) fprintf(stdout, "ckpartialinstall=%d\n", r == 8 ? 1 : 0);
		(void) fprintf(stdout, "ckpartialremove=%d\n", r == 9 ? 1 : 0);

		/*
		 * make sure current runlevel is appropriate
		 */
		r = ckrunlevel();
		(void) fprintf(stdout, "ckrunlevel=%d\n", r);
	}

	if (pkgdev.cdevice) {
		/* get first volume which contains info files */
		unpack();
		if (!suppressCopyright) {
			copyright();
		}
	}

	/* update the lock - at the request script */

	lockupd("request");

	/*
	 * If no response file has been provided, initialize response file by
	 * executing any request script provided by this package. Initialize
	 * the response file if not gathering dependencies only.
	 */

	if ((!rdonly_respfile()) && (preinstallCheck == B_FALSE)) {
		(void) snprintf(path, sizeof (path),
			"%s/%s", instdir, REQUEST_FILE);
		n = reqexec(update, path, non_abi_scripts,
			run_request_as_root);
		if (in_dryrun_mode()) {
			set_dr_info(REQUESTEXITCODE, n);
		}

		ckreturn(n, ERR_REQUEST);
	}

	/*
	 * Look for all parameters in response file which begin with a
	 * capital letter, and place them in the environment.
	 */

	if ((is_a_respfile()) && (preinstallCheck == B_FALSE)) {
		if (n = merg_respfile()) {
			quit(n);
			/*NOTREACHED*/
		}
	}

	/*
	 * Run a checkinstall script if one is provided by the package.
	 * Don't execute checkinstall script if we are only updating the DB.
	 * Don't execute checkinstall script if only gathering dependencies.
	 */

	/* update the lock - at the checkinstall script */
	lockupd("checkinstall");

	/* Execute checkinstall script if one is provided. */
	(void) snprintf(script, sizeof (script), "%s/install/checkinstall",
			instdir);
	if (access(script, F_OK) != 0) {
		/* no script present */
		echoDebug(DBG_PKGINSTALL_COC_NONE, pkginst, script,
			zoneName ? zoneName : "global");
	} else if (is_depend_pkginfo_DB()) {
		/* updating db only: skip checkinstall script */
		echoDebug(DBG_PKGINSTALL_COC_DBUPD, pkginst, script,
			zoneName ? zoneName : "global");
	} else if (preinstallCheck == B_TRUE) {
		/* only gathering dependencies: skip checkinstall script */
		echoDebug(DBG_PKGINSTALL_COC_NODEL, pkginst, script,
			zoneName ? zoneName : "global");
	} else {
		/* script present and ok to run: run the script */
		if (zoneName == (char *)NULL) {
			echo(MSG_PKGINSTALL_EXECOC_GZ);
			echoDebug(DBG_PKGINSTALL_EXECOC_GZ, pkginst, script);
		} else {
			echo(MSG_PKGINSTALL_EXECOC_LZ, zoneName);
			echoDebug(DBG_PKGINSTALL_EXECOC_LZ, pkginst, script,
				zoneName);
		}
		n = chkexec(update, script);
		if (in_dryrun_mode()) {
			set_dr_info(CHECKEXITCODE, n);
		}

		if (n == 3) {
			echo(WRN_CHKINSTALL);
			ckreturn(4, NULL);
		} else if (n == 7) {
			/* access returned error */
			progerr(ERR_CHKINSTALL_NOSCRIPT, script);
			ckreturn(4, ERR_CHKINSTALL);
		} else {
			ckreturn(n, ERR_CHKINSTALL);
		}
	}

	/*
	 * Now that the internal data structures are initialized, we can
	 * initialize the dryrun files (which may be the same files).
	 */

	if (pkgdrtarg) {
		init_dryrunfile(pkgdrtarg);
	}

	/*
	 * Look for all parameters in response file which begin with a
	 * capital letter, and place them in the environment.
	 */
	if (is_a_respfile()) {
		if (n = merg_respfile()) {
			quit(n);
			/*NOTREACHED*/
		}
	}

	/* update the lock - doing analysis */

	lockupd("analysis");

	/*
	 * Determine package base directory and client base directory
	 * if appropriate. Then encapsulate them for future retrieval.
	 */
	if ((err = set_basedirs(isreloc(instdir), adm.basedir, pkginst,
		nointeract)) != 0) {
		quit(err);
		/*NOTREACHED*/
	}

	/*
	 * Create the base directory if specified.
	 * Don't create if we are only updating the DB.
	 * Don't create if only gathering dependencies.
	 */

	if (!is_depend_pkginfo_DB() &&
		!preinstallCheck && is_a_basedir()) {
		mkbasedir(!nointeract, get_basedir());
		echo(MSG_BASE_USED, get_basedir());
	}

	/*
	 * Store PKG_INSTALL_ROOT, BASEDIR & CLIENT_BASEDIR in our
	 * environment for later use by procedure scripts.
	 */
	put_path_params();

	/*
	 * the following two checks are done in the corresponding
	 * ck() routine, but are repeated here to avoid re-processing
	 * the database if we are administered to not include these
	 * processes
	 */
	if (ADM(setuid, "nochange")) {
		nosetuid++;	/* Clear setuid/gid bits. */
	}

	if (ADM(conflict, "nochange")) {
		nocnflct++;	/* Don't install conflicting files. */
	}

	/*
	 * Get the filesystem space information for the filesystem on which
	 * the "contents" file resides.
	 */

	svfsb.f_bsize = 8192;
	svfsb.f_frsize = 1024;

	if (statvfs64(get_PKGADM(), &svfsb) == -1) {
		int	lerrno = errno;
		if (!access(get_PKGADM(), F_OK)) {
			progerr(ERR_PKGINSTALL_STATVFS, get_PKGADM(),
				strerror(errno));
			logerr("(errno %d)", lerrno);
			quit(99);
			/*NOTREACHED*/
		}
	}

	/*
	 * Get the number of blocks used by the pkgmap, ocfile()
	 * needs this to properly determine its space requirements.
	 */

	if (stat(p_pkgmap, &statb) == -1) {
		progerr(ERR_PKGINSTALL_STATOF, p_pkgmap, strerror(errno));
		quit(99);
		/*NOTREACHED*/
	}

	pkgmap_blks = nblk(statb.st_size, svfsb.f_bsize, svfsb.f_frsize);

	/*
	 * Merge information in memory with the "contents" file; this creates
	 * a temporary version of the "contents" file. Note that in dryrun
	 * mode, we still need to record the contents file data somewhere,
	 * but we do it in the dryrun directory.
	 */

	if (in_dryrun_mode()) {
		if (n = set_cfdir(pkgdrtarg)) {
			quit(n);
			/*NOTREACHED*/
		}
	} else {
		if (n = set_cfdir(NULL)) {
			quit(n);
			/*NOTREACHED*/
		}
	}
	if (!ocfile(&pkgserver, &cfTmpVfp, pkgmap_blks)) {
		quit(99);
		/*NOTREACHED*/
	}

	/*
	 * if cpio is being used,  tell pkgdbmerg since attributes will
	 * have to be check and repaired on all file and directories
	 */
	for (np = cpio_names; *np != NULL; np++) {
		(void) snprintf(path, sizeof (path),
			"%s/%s", instdir, *np);
		if (iscpio(path, &is_comp_arch)) {
			is_WOS_arch();
			break;
		}
	}

	/* Establish the class list and the class attributes. */
	cl_sets(getenv("CLASSES"));
	find_CAS(I_ONLY, pkgbin, instdir);

	if (vfpOpen(&pkgmapVfp, p_pkgmap, "r", VFP_NEEDNOW) != 0) {
		progerr(ERR_PKGMAP, p_pkgmap);
		quit(99);
		/*NOTREACHED*/
	}

	/*
	 * This modifies the path list entries in memory to reflect
	 * how they should look after the merg is complete
	 */

	nparts = sortmap(&extlist, pkgmapVfp, pkgserver, cfTmpVfp, zoneName);

	if ((n = files_installed()) > 0) {
		if (n > 1) {
			echo(MSG_INST_MANY, n);
		} else {
			echo(MSG_INST_ONE, n);
		}
	}

	/*
	 * Check ulimit requirement (provided in pkginfo). The purpose of
	 * this limit is to terminate pathological file growth resulting from
	 * file edits in scripts. It does not apply to files in the pkgmap
	 * and it does not apply to any database files manipulated by the
	 * installation service.
	 */
	if (pt = getenv("ULIMIT")) {
		if (assign_ulimit(pt) == -1) {
			progerr(ERR_BADULIMIT, pt);
			quit(99);
			/*NOTREACHED*/
		}
		putparam("PKG_ULIMIT", "TRUE");
	}

	/*
	 * If only gathering dependencies, check and output status of all
	 * remaining dependencies and exit.
	 */

	if (preinstallCheck == B_TRUE) {
		/* update the lock file - final checking */

		lockupd("preinstallcheck");

		/* verify package information files are not corrupt */

		(void) fprintf(stdout, "ckpkgfiles=%d\n", ckpkgfiles());

		/* verify package dependencies */

		(void) fprintf(stdout, "ckdepend=%d\n", ckdepend());

		/* Check space requirements */

		(void) fprintf(stdout, "ckspace=%d\n", ckspace());

		/*
		 * Determine if any objects provided by this package conflict
		 * with the files of previously installed packages.
		 */

		(void) fprintf(stdout, "ckconflict=%d\n", ckconflct());

		/*
		 * Determine if any objects provided by this package will be
		 * installed with setuid or setgid enabled.
		 */

		(void) fprintf(stdout, "cksetuid=%d\n", cksetuid());

		/*
		 * Determine if any packaging scripts provided with this package
		 * will execute as a priviledged user.
		 */

		(void) fprintf(stdout, "ckpriv=%d\n", ckpriv());

		/* Verify neccessary package installation directories exist */

		(void) fprintf(stdout, "ckpkgdirs=%d\n", ckpkgdirs());

		/*
		 * ****** preinstall check done - exit ******
		 */

		echoDebug(DBG_PKGINSTALL_PREINSCHK_OK);
		quit(0);
		/*NOTREACHED*/
	}

	/*
	 * Not gathering dependencies only, proceed to check dependencies
	 * and continue with the package installation operation.
	 */

	/*
	 * verify package information files are not corrupt
	 */
	ck_w_dryrun(ckpkgfiles, PKGFILES);

	/*
	 * verify package dependencies
	 */
	ck_w_dryrun(ckdepend, DEPEND);

	/*
	 * Check space requirements.
	 */
	ck_w_dryrun(ckspace, SPACE);

	/*
	 * Determine if any objects provided by this package conflict with
	 * the files of previously installed packages.
	 */
	ck_w_dryrun(ckconflct, CONFLICT);

	/*
	 * Determine if any objects provided by this package will be
	 * installed with setuid or setgid enabled.
	 */
	ck_w_dryrun(cksetuid, SETUID);

	/*
	 * Determine if any packaging scripts provided with this package will
	 * execute as a priviledged user.
	 */
	ck_w_dryrun(ckpriv, PRIV);

	/*
	 * Verify neccessary package installation directories exist.
	 */
	ck_w_dryrun(ckpkgdirs, PKGDIRS);

	/*
	 * If we have assumed that we were installing setuid or conflicting
	 * files, and the user chose to do otherwise, we need to read in the
	 * package map again and re-merg with the "contents" file
	 */

	if (rprcflag) {
		nparts = sortmap(&extlist, pkgmapVfp, pkgserver,
				cfTmpVfp, zoneName);
	}

	(void) vfpClose(&pkgmapVfp);

	/* BEGIN INSTALLATION PHASE */
	if (in_dryrun_mode()) {
		echo(MSG_PKGINSTALL_DRYRUN, pkgname, pkginst);
	} else if (zoneName == (char *)NULL) {
		echo(MSG_PKGINSTALL_INSIN_GZ, pkgname, pkginst);
	} else {
		echo(MSG_PKGINSTALL_INSIN_LZ, pkgname, pkginst, zoneName);
	}

	/* inform quit that the install has started */

	quitSetInstallStarted(B_TRUE);

	/*
	 * This replaces the contents file with recently created temp version
	 * which contains information about the objects being installed.
	 * Under old lock protocol it closes both files and releases the
	 * locks. Beginning in Solaris 2.7, this lock method should be
	 * reviewed.
	 */

	n = swapcfile(pkgserver, &cfTmpVfp, pkginst, dbchg);
	if (n == RESULT_WRN) {
		warnflag++;
	} else if (n == RESULT_ERR) {
		quit(99);
		/*NOTREACHED*/
	}

	/*
	 * Create install-specific lockfile to indicate start of
	 * installation. This is really just an information file. If the
	 * process dies, the initial lockfile (from lockinst(), is
	 * relinquished by the kernel, but this one remains in support of the
	 * post-mortem.
	 */

	if (access(ilockfile, F_OK) == 0) {
		(void) remove(ilockfile);
	}

	if (open(ilockfile, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, 0644) < 0) {
		progerr(ERR_LOCKFILE, ilockfile);
		quit(99);
		/*NOTREACHED*/
	}

	(void) time(&clock);

	/*
	 * We do not want the time in locale in the pkginfo.
	 * save the LC_TIME and set it to C. Reset it with saved one
	 * after cftime().
	 */
	temp = setlocale(LC_TIME, NULL);
	(void) setlocale(LC_TIME, "C");

	/* LINTED warning: do not use cftime(); ... */
	(void) cftime(cbuf, "%b %d \045Y \045H:\045M", &clock);
	putparam("INSTDATE", qstrdup(cbuf));
	(void) setlocale(LC_TIME, temp);

	/*
	 * Store information about package being installed;
	 * modify installation parameters as neccessary and
	 * copy contents of 'install' directory into $pkgloc
	 */
	merginfo(mergd_pclass, saveSpoolInstall);

	/* If this was just a dryrun, then quit() will write out that file. */
	if (in_dryrun_mode()) {
		quit(0);
		/*NOTREACHED*/
	}

	/*
	 * Execute preinstall script, if one was provided with the
	 * package. We check the package to avoid running an old
	 * preinstall script if one was provided with a prior instance.
	 * Don't execute preinstall script if we are only updating the DB.
	 */

	/* update the lock - at the preinstall altscript */
	lockupd("preinstall");

	/* preinstall script in the media (package source) */
	(void) snprintf(altscript, sizeof (altscript), "%s/install/preinstall",
			instdir);

	/* preinstall script in the pkgbin instead of media */
	(void) snprintf(script, sizeof (script), "%s/preinstall", pkgbin);

	if (access(altscript, F_OK) != 0) {
		/* no script present */
		echoDebug(DBG_PKGINSTALL_POCALT_NONE, pkginst, altscript,
			zoneName ? zoneName : "global");
	} else if (access(script, F_OK) != 0) {
		/* no script present */
		echoDebug(DBG_PKGINSTALL_POC_NONE, pkginst, script,
			zoneName ? zoneName : "global");
	} else if (is_depend_pkginfo_DB()) {
		/* updating db only: skip preinstall script */
		echoDebug(DBG_PKGINSTALL_POC_DBUPD, pkginst, script,
			zoneName ? zoneName : "global");
	} else {
		/* script present and ok to run: run the script */
		assert(preinstallCheck == B_FALSE);

		set_ulimit("preinstall", ERR_PREINSTALL);
		if (zoneName == (char *)NULL) {
			echo(MSG_PKGINSTALL_EXEPOC_GZ);
			echoDebug(DBG_PKGINSTALL_EXEPOC_GZ, pkginst, script);
		} else {
			echo(MSG_PKGINSTALL_EXEPOC_LZ, zoneName);
			echoDebug(DBG_PKGINSTALL_EXEPOC_LZ, pkginst, script,
				zoneName);
		}
		putparam("PKG_PROC_script", "preinstall");
		if (pkgverbose) {
			ckreturn(pkgexecl(script_in, PROC_STDOUT,
				PROC_USER, PROC_GRP, SHELL, "-x",
				script, NULL), ERR_PREINSTALL);
		} else {
			ckreturn(pkgexecl(script_in, PROC_STDOUT,
				PROC_USER, PROC_GRP, SHELL, script,
				NULL), ERR_PREINSTALL);
		}

		clr_ulimit();
		(void) remove(script);	/* no longer needed. */
	}

	/*
	 * Check delivered package for a postinstall script while
	 * we're still on volume 1.
	 */

	(void) snprintf(script, sizeof (script),
			"%s/install/postinstall", instdir);
	if (access(script, F_OK) == 0) {
		(void) snprintf(script, sizeof (script),
					"%s/postinstall", pkgbin);
	} else {
		script[0] = '\0';
	}

	/* update the lock - at the install phase */

	lockupd("install");

	/*
	 * install package one part (volume) at a time
	 */

	part = 1;
	while (part <= nparts) {
		if ((part > 1) && pkgdev.cdevice) {
			unpack();
		}

		instvol(extlist, srcinst, part, nparts,
			pkgserver, &cfTmpVfp, &updated, zoneName);

		if (part++ >= nparts) {
			break;
		}
	}

	z_destroyMountTable();

	/*
	 * Now that all install class action scripts have been used, we
	 * delete them from the package directory.
	 */
	rm_icas(pkgbin);

	if (globalZoneOnly) {
		boolean_t   b;
		b = pkgAddPackageToGzonlyList(pkginst, get_inst_root());
		if (b == B_FALSE) {
			progerr(ERR_PKGINSTALL_GZONLY_ADD, pkginst);
			ckreturn(1, NULL);
		}
	}

	/*
	 * Execute postinstall script, if any
	 * Don't execute postinstall script if we are only updating the DB.
	 */

	echoDebug(DBG_PKGINSTALL_INSDONE, is_depend_pkginfo_DB(),
		is_depend_pkginfo_DB(), saveSpoolInstall,
		updated ? updated : "",
		script ? script : "",
		script ? access(script, F_OK) : -1);

	/* update the lock - at the postinstall script */
	lockupd("postinstall");

	if ((script == (char *)NULL) || (*script == '\0')) {
		echoDebug(DBG_PKGINSTALL_POIS_NOPATH, pkginst,
			zoneName ? zoneName : "global");
	} else if (access(script, F_OK) != 0) {
		echoDebug(DBG_PKGINSTALL_POIS_NONE, pkginst, script,
			zoneName ? zoneName : "global");
	} else if (is_depend_pkginfo_DB()) {
		echoDebug(DBG_PKGINSTALL_POIS_DBUPD, pkginst, script,
			zoneName ? zoneName : "global");
	} else if ((saveSpoolInstall != 0) && (updated == (char *)NULL)) {
		/*
		 * fresh installing into non-global zone, no object was
		 * updated (installed/verified in area), so do not run
		 * the postinstall script.
		 */
		echoDebug(DBG_PKGINSTALL_POIS_NOUPDATING,
			zoneName ? zoneName : "global", pkginst, script);
	} else {
		/* script present and ok to run: run the script */
		set_ulimit("postinstall", ERR_POSTINSTALL);
		if (zoneName == (char *)NULL) {
			echo(MSG_PKGINSTALL_EXEPIC_GZ);
			echoDebug(DBG_PKGINSTALL_EXEPIC_GZ, pkginst, script);
		} else {
			echo(MSG_PKGINSTALL_EXEPIC_LZ, zoneName);
			echoDebug(DBG_PKGINSTALL_EXEPIC_LZ, pkginst, script,
				zoneName);
		}
		putparam("PKG_PROC_SCRIPT", "postinstall");
		putparam("TMPDIR", tmpdir);
		if (pkgverbose) {
			ckreturn(pkgexecl(script_in, PROC_STDOUT,
				PROC_USER, PROC_GRP, SHELL, "-x",
				script, NULL), ERR_POSTINSTALL);
		} else {
			ckreturn(pkgexecl(script_in, PROC_STDOUT,
				PROC_USER, PROC_GRP, SHELL, script,
				NULL), ERR_POSTINSTALL);
		}

		clr_ulimit();
		(void) remove(script);	/* no longer needed */
	}

	if (!warnflag && !failflag) {
		(void) remove(rlockfile);
		(void) remove(ilockfile);
		(void) remove(savlog);
	}

	/* release the generic package lock */

	(void) unlockinst();

	pkgcloseserver(pkgserver);

	quit(0);
	/* LINTED: no return */
}

/*
 * This function merges the environment data in the response file with the
 * current environment.
 */
static int
merg_respfile()
{
	int retcode = 0;
	char *resppath = get_respfile();
	char *locbasedir;
	char param[MAX_PKG_PARAM_LENGTH], *value;
	FILE *fp;

	if ((fp = fopen(resppath, "r")) == NULL) {
		progerr(ERR_RESPONSE, resppath);
		return (99);
	}

	param[0] = '\0';

	while (value = fpkgparam(fp, param)) {
		if (!isupper(param[0])) {
			param[0] = '\0';
			continue;
		}

		if (rdonly(param)) {
			progerr(ERR_RDONLY, param);
			param[0] = '\0';
			continue;
		}

		/*
		 * If this is an update, and the response file
		 * specifies the BASEDIR, make sure it matches the
		 * existing installation base. If it doesn't, we have
		 * to quit.
		 */
		if (update && strcmp("BASEDIR", param) == 0) {
			locbasedir = getenv("BASEDIR");
			if (locbasedir && strcmp(value, locbasedir) != 0) {
				char *dotptr;
				/* Get srcinst down to a name. */
				if (dotptr = strchr(srcinst, '.'))
					*dotptr = '\000';
				progerr(ERR_NEWBD, srcinst,
					locbasedir, value);
				retcode = 99;
			}
		}

		putparam(param, value);
		param[0] = '\0';
	}
	(void) fclose(fp);

	return (retcode);
}

/*
 * This scans the installed pkginfo file for the current BASEDIR. If this
 * BASEDIR is different from the current BASEDIR, there will definitely be
 * problems.
 */
static int
ck_instbase(void)
{
	int retcode = 0;
	char param[MAX_PKG_PARAM_LENGTH], *value;
	char pkginfo_path[PATH_MAX];
	FILE *fp;

	/* Open the old pkginfo file. */
	(void) snprintf(pkginfo_path, sizeof (pkginfo_path),
			"%s/%s", pkgloc, PKGINFO);
	if ((fp = fopen(pkginfo_path, "r")) == NULL) {
		progerr(ERR_PKGINFO, pkginfo_path);
		return (99);
	}

	param[0] = '\000';

	while (value = fpkgparam(fp, param)) {
		if (strcmp("BASEDIR", param) == 0) {
			if (adm.basedir && *(adm.basedir) &&
				strchr("/$", *(adm.basedir))) {
				char *dotptr;

				/*
				 * Get srcinst down to a name.
				 */
				if (dotptr = strchr(srcinst, '.'))
					*dotptr = '\000';
				if (strcmp(value,
					adm.basedir) != 0) {
					progerr(ERR_ADMBD, srcinst,
						value, adm.basedir);
					retcode = 4;
					break;
				}
			} else if (ADM(basedir, "ask"))
				/*
				 * If it's going to ask later, let it know
				 * that it *must* agree with the BASEDIR we
				 * just picked up.
				 */
				adm.basedir = "update";

			putparam(param, value);
			break;
		}

		param[0] = '\0';
	}
	(void) fclose(fp);

	return (retcode);
}

/*
 * Since this is an overwrite of a different version of the package, none of
 * the old files should remain, so we rename them.
 */
static int
mv_pkgdirs(void)
{
	/*
	 * If we're not in dryrun mode and we can find an old set of package
	 * files over which the new ones will be written, do the rename.
	 */
	if (!in_dryrun_mode() && pkgloc[0] && !access(pkgloc, F_OK)) {
		(void) snprintf(pkgloc_sav, sizeof (pkgloc_sav),
			"%s/.save.%s", get_PKGLOC(),
			pkginst);
		if (pkgloc_sav[0] && !access(pkgloc_sav, F_OK)) {
			(void) rrmdir(pkgloc_sav);
		}

		if (rename(pkgloc, pkgloc_sav) == -1) {
			progerr(ERR_PKGBINREN, pkgloc, pkgloc_sav);
			return (99);
		}
	}

	return (0);
}

/*
 * Name:	merg_pkginfos
 * Description:	This function scans the installed pkginfo and merges that
 *		environment with the installing environment according to
 *		the following rules:
 *
 *		1. CLASSES is a union of the installed and installing CLASSES
 *			lists.
 *		2. The installed BASEDIR takes precedence. If it doesn't agree
 *		   with an administratively imposed BASEDIR, an ERROR is issued.
 *		3. All other installing parameters are preserved.
 *		4. All installed parameters are added if they do not overwrite
 *		   an existing installing parameter.
 *
 *		The current environment contains the pkginfo settings for the
 *		new package to be installed or to be updated.
 *
 * Arguments:	pclass - returned list of current classes involved in install
 *		mpclass - pointer to returned list of current install classes
 * Returns:	int
 *		== 0 - all OK
 *		!= 0 - an error code if a fatal error occurred
 */

static int
merg_pkginfos(struct cl_attr **pclass, struct cl_attr ***mpclass)
{
	FILE	*fp;
	char	SUNW_PKG_ALLZONES[MAX_PKG_PARAM_LENGTH] = {'\0'};
	char	SUNW_PKG_HOLLOW[MAX_PKG_PARAM_LENGTH] = {'\0'};
	char	SUNW_PKG_THISZONE[MAX_PKG_PARAM_LENGTH] = {'\0'};
	char	*newValue;
	char	*oldValue;
	char	*pkgName;
	char	*pkgVersion;
	char	param[MAX_PKG_PARAM_LENGTH];
	char	pkginfo_path[PATH_MAX];
	int	retcode = 0;

	/* obtain the name of the package (for error messages) */

	pkgName = getenv("PKG");
	if (pkgName == NULL) {
		pkgName = "*current*";	/* default name */
	}

	/* obtain the version of the package (for error messages) */

	pkgVersion = getenv("VERSION");
	if (pkgVersion == NULL) {
		pkgVersion = "*current*";	/* default version */
	}

	/* open installed package pkginfo file */

	(void) snprintf(pkginfo_path, sizeof (pkginfo_path),
			"%s/%s", pkgloc, PKGINFO);
	if ((fp = fopen(pkginfo_path, "r")) == NULL) {
		progerr(ERR_PKGINFO, pkginfo_path);
		return (99);
	}

	/* entry debugging info */

	echoDebug(DBG_MERGINFOS_ENTRY, pkginfo_path);

	/*
	 * cycle through the currently installed package's pkginfo parameters
	 * and let the currently installed package's settings survive if the
	 * update to the package does not provide an overriding value
	 */

	for (param[0] = '\0'; (oldValue = fpkgparam(fp, param)) != NULL;
		param[0] = '\0') {

		boolean_t	setZoneAttribute = B_FALSE;

		/* debug info - attribute currently set to value */

		echoDebug(DBG_MERGINFOS_SET_TO, param, oldValue);

		/*
		 * if zone package attribute is present in the currently
		 * installed package, then remember the value for the
		 * specific zone package attribute, and set the flag that
		 * indicates a zone package attribute is being processed.
		 */

		if (strcmp(param, PKG_THISZONE_VARIABLE) == 0) {
			/* SUNW_PKG_THISZONE currently set */
			setZoneAttribute = B_TRUE;
			(void) strlcpy(SUNW_PKG_THISZONE, oldValue,
					sizeof (SUNW_PKG_THISZONE));
		} else if (strcmp(param, PKG_ALLZONES_VARIABLE) == 0) {
			/* SUNW_PKG_ALLZONES currently set */
			setZoneAttribute = B_TRUE;
			(void) strlcpy(SUNW_PKG_ALLZONES, oldValue,
					sizeof (SUNW_PKG_ALLZONES));
		} else if (strcmp(param, PKG_HOLLOW_VARIABLE) == 0) {
			/* SUNW_PKG_THISZONE currently set */
			setZoneAttribute = B_TRUE;
			(void) strlcpy(SUNW_PKG_HOLLOW, oldValue,
					sizeof (SUNW_PKG_HOLLOW));
		}

		/* handle CLASSES currently being set */

		if (strcmp(param, "CLASSES") == 0) {
			echoDebug(DBG_MERGINFOS_SET_CLASSES, oldValue);
			/* create a list of the current classes */
			(void) setlist(&pclass, qstrdup(oldValue));
			/* set pointer to list of current classes */
			*mpclass = pclass;
			continue;
		}

		/* handle BASEDIR currently being set */

		if (strcmp("BASEDIR", param) == 0) {
			if (adm.basedir && *(adm.basedir) &&
				strchr("/$", *(adm.basedir))) {
				char *dotptr;

				/* Get srcinst down to a* name */

				if (dotptr = strchr(srcinst, '.')) {
					*dotptr = '\000';
				}
				if (strcmp(oldValue, adm.basedir) != 0) {
					progerr(ERR_ADMBD, srcinst,
						oldValue, adm.basedir);
					/* administration */
					retcode = 4;
					break;
				}
			} else if (ADM(basedir, "ask")) {
				/*
				 * If it's going to ask
				 * later, let it know that it
				 * *must* agree with the
				 * BASEDIR we just picked up.
				 */
				adm.basedir = "update";
				echoDebug(DBG_MERGINFOS_ASK_BASEDIR);
			}

			echoDebug(DBG_MERGINFOS_SET_BASEDIR, oldValue);
			putparam(param, oldValue);
			continue;
		}

		/*
		 * determine if there is a new value for this attribute.
		 */

		newValue = getenv(param);

		/*
		 * If there is no new value, and a zone attribute
		 * is being changed, it is the same as setting the zone package
		 * attribute to 'false' - make sure current setting is 'false'.
		 */

		if ((newValue == NULL) &&
		    (setZoneAttribute == B_TRUE) &&
		    (strcasecmp(oldValue, "false") != 0)) {

			/* unset existing non-"false" zone pkg attr */
			progerr(ERR_MERGINFOS_UNSET_ZONEATTR,
				pkgName, pkgVersion, param, oldValue);
			retcode = 1;
			break;
		}

		/* retain old value if no new value specified */

		if (newValue == NULL) {
			/* no new value - retain the old value */
			echoDebug(DBG_MERGINFOS_RETAIN_OLD, param, oldValue);
			putparam(param, oldValue);
			continue;
		}

		/* note if the old and new values are the same */

		if (strcmp(newValue, oldValue) == 0) {
			/* set existing package parameter to same value */
			echoDebug(DBG_MERGINFOS_SET_DUPLICATE, param, oldValue);
			continue;
		}

		/*
		 * Check if old and new values differ.
		 * Error if zone parameter
		 */

		if (setZoneAttribute == B_TRUE) {
			/* illegal change to zone attribute */

			progerr(ERR_MERGINFOS_CHANGE_ZONEATTR, pkgName,
				pkgVersion, param, oldValue, newValue);

			/* set return code to "fatal error" */
			retcode = 1;
			break;
		}

		/* note valid change to existing package parameter */

		echoDebug(DBG_MERGINFOS_SET_CHANGE, param,
				oldValue, newValue);
	}

	/* close handle on currently installed package's pkginfo file */

	(void) fclose(fp);

	/* return error if not successful up to this point */

	if (retcode != 0) {
		echoDebug(DBG_MERGINFOS_EXIT, pkginfo_path, retcode);

		return (retcode);
	}

	/*
	 * verify that no zone attribute has been
	 * set to an invalid value
	 */

	/* SUNW_PKG_ALLZONES */

	newValue = getenv(PKG_ALLZONES_VARIABLE);

	/*
	 * complain if setting SUNW_PKG_ALLZONES to other than "false"
	 */


	if ((newValue != NULL) && (*SUNW_PKG_ALLZONES == '\0') &&
	    (strcasecmp(newValue, "false") != 0)) {
		/* change ALLZONES from "true" to "false" (unset) */
		progerr(ERR_MERGINFOS_SET_ZONEATTR, pkgName,
		    pkgVersion, PKG_ALLZONES_VARIABLE, newValue);
		return (1);
	}

	/* SUNW_PKG_THISZONE */

	newValue = getenv(PKG_THISZONE_VARIABLE);

	/*
	 * complain if setting SUNW_PKG_THISZONE to other than "false"
	 */

	if ((newValue != NULL) && (*SUNW_PKG_THISZONE == '\0') &&
	    (strcasecmp(newValue, "false") != 0)) {
		/* change THISZONE from "true" to "false" (unset) */
		progerr(ERR_MERGINFOS_SET_ZONEATTR, pkgName,
		    pkgVersion, PKG_THISZONE_VARIABLE, newValue);
		return (1);
	}

	/* SUNW_PKG_HOLLOW */

	newValue = getenv(PKG_HOLLOW_VARIABLE);

	/* complain if setting SUNW_PKG_HOLLOW to other than "false" */

	if ((newValue != NULL) && (*SUNW_PKG_HOLLOW == '\0') &&
	    (strcasecmp(newValue, "false") != 0)) {
		/* change HOLLOW from "true" to 'false" (unset) */
		progerr(ERR_MERGINFOS_SET_ZONEATTR, pkgName,
		    pkgVersion, PKG_HOLLOW_VARIABLE, newValue);
		return (1);
	}

	echoDebug(DBG_MERGINFOS_EXIT, pkginfo_path, 0);

	return (0);
}

static void
set_dryrun_dir_loc(void)
{
	/* Set pkg location to the dryrun directory */
	set_PKGLOC(pkgdrtarg);
	(void) snprintf(pkgloc, sizeof (pkgloc),
			"%s/%s", get_PKGLOC(), pkginst);
	(void) snprintf(pkgbin, sizeof (pkgbin),
			"%s/install", pkgloc);
	(void) snprintf(pkgsav, sizeof (pkgsav),
			"%s/save", pkgloc);
	(void) snprintf(ilockfile, sizeof (ilockfile),
			"%s/!I-Lock!", pkgloc);
	(void) snprintf(rlockfile, sizeof (rlockfile),
			"%s/!R-Lock!", pkgloc);
	(void) snprintf(savlog, sizeof (savlog),
			"%s/logs/%s", get_PKGADM(), pkginst);
}

/*
 * If we are updating a pkg, then we need to copy the "old" pkgloc so that
 * any scripts that got removed in the new version aren't left around.  So we
 * copy it here to .save.pkgloc, then in quit() we can restore our state, or
 * remove it.
 */
static int
cp_pkgdirs(void)
{
	if (in_dryrun_mode()) {
		set_dryrun_dir_loc();
	}

	/*
	 * If we're not in dryrun mode and we can find an old set of package
	 * files over which the new ones will be written, do the copy.
	 */
	if (!in_dryrun_mode() && pkgloc[0] && !access(pkgloc, F_OK)) {
		int status;
		int r;

		(void) snprintf(pkgloc_sav, sizeof (pkgloc_sav), "%s/.save.%s",
			get_PKGLOC(), pkginst);

		/*
		 * Even though it takes a while, we use a recursive copy here
		 * because if the current pkgadd fails for any reason, we
		 * don't want to lose this data.
		 */
		r = e_ExecCmdList(&status, (char **)NULL, (char *)NULL,
			"/usr/bin/cp", "cp", "-r", pkgloc, pkgloc_sav,
			(char *)NULL);

		if ((r != 0) || (status == -1) || (WEXITSTATUS(status) != 0)) {
			progerr(ERR_PKGBINCP, pkgloc, pkgloc_sav);
			return (99);
		}
	}

	return (0);
}

/*
 * This implements the pkgask function. It just executes the request script
 * and stores the results in a response file.
 */
static void
do_pkgask(boolean_t a_run_request_as_root)
{
	if (pkgdev.cdevice) {
		unpack();
		if (!suppressCopyright) {
			copyright();
		}
	}
	(void) snprintf(path, sizeof (path), "%s/%s", instdir, REQUEST_FILE);
	if (access(path, F_OK)) {
		progerr(ERR_NOREQUEST);
		quit(1);
		/*NOTREACHED*/
	}

	(void) set_respfile(respfile, srcinst, RESP_WR);

	if (is_a_respfile()) {
		ckreturn(reqexec(update, path, non_abi_scripts,
			a_run_request_as_root), ERR_REQUEST);
	} else {
		failflag++;
	}

	if (warnflag || failflag) {
		(void) remove(respfile);
		echo("\nResponse file <%s> was not created.",
			get_respfile());
	} else {
		echo("\nResponse file <%s> was created.",
			get_respfile());
	}

	quit(0);
	/*NOTREACHED*/
}

/*
 * This function runs a check utility and acts appropriately based upon the
 * return code. It deals appropriately with the dryrun file if it is present.
 */
static void
ck_w_dryrun(int (*func)(), int type)
{
	int n;

	n = func();
	if (in_dryrun_mode())
		set_dr_info(type, !n);

	if (n) {
		quit(n);
		/*NOTREACHED*/
	}
}

/*
 * This function deletes all install class action scripts from the package
 * directory on the root filesystem.
 */
static void
rm_icas(char *cas_dir)
{
	DIR	*pdirfp;
	struct	dirent *dp;
	char path[PATH_MAX];

	if ((pdirfp = opendir(cas_dir)) == NULL)
		return;

	while ((dp = readdir(pdirfp)) != NULL) {
		if (dp->d_name[0] == '.')
			continue;

		if (dp->d_name[0] == 'i' && dp->d_name[1] == '.') {
			(void) snprintf(path, sizeof (path),
				"%s/%s", cas_dir, dp->d_name);
			(void) remove(path);
		}
	}
	(void) closedir(pdirfp);
}

void
ckreturn(int retcode, char *msg)
{
	switch (retcode) {
		case 2:
		case 12:
		case 22:
		warnflag++;
		if (msg) {
			progerr("%s", msg);
		}
		/*FALLTHRU*/
		case 10:
		case 20:
		if (retcode >= 10 && retcode < 20) {
			dreboot++;
		}
		if (retcode >= 20) {
			ireboot++;
		}
		/*FALLTHRU*/
		case 0:
		break; /* okay */

		case -1:
		retcode = 99;
		/*FALLTHRU*/
		case 99:
		case 1:
		case 11:
		case 21:
		case 4:
		case 14:
		case 24:
		case 5:
		case 15:
		case 25:
		if (msg) {
			progerr("%s", msg);
		}
		/*FALLTHRU*/
		case 3:
		case 13:
		case 23:
		quit(retcode);
		/*NOTREACHED*/
		default:
		if (msg) {
			progerr("%s", msg);
		}
		quit(1);
		/*NOTREACHED*/
	}
}

static void
copyright(void)
{
	FILE	*fp;
	char	line[LSIZE];
	char	path[PATH_MAX];

	/* Compose full path for copyright file */
	(void) snprintf(path, sizeof (path), "%s/%s", instdir, COPYRIGHT_FILE);

	if ((fp = fopen(path, "r")) == NULL) {
		if (getenv("VENDOR") != NULL)
			echo(getenv("VENDOR"));
	} else {
		while (fgets(line, LSIZE, fp))
			(void) fprintf(stdout, "%s", line); /* bug #1083713 */
		(void) fclose(fp);
	}
}

static int
rdonly(char *p)
{
	int	i;

	for (i = 0; ro_params[i]; i++) {
		if (strcmp(p, ro_params[i]) == 0)
			return (1);
	}
	return (0);
}

static void
unpack(void)
{
	/*
	 * read in next part from stream, even if we decide
	 * later that we don't need it
	 */
	if (dparts < 1) {
		progerr(ERR_DSTREAMCNT);
		quit(99);
		/*NOTREACHED*/
	}
	if ((access(instdir, F_OK) == 0) && rrmdir(instdir)) {
		progerr(ERR_RMDIR, instdir);
		quit(99);
		/*NOTREACHED*/
	}
	if (mkdir(instdir, 0755)) {
		progerr(ERR_MKDIR, instdir);
		quit(99);
		/*NOTREACHED*/
	}
	if (chdir(instdir)) {
		progerr(ERR_CHDIR, instdir);
		quit(99);
		/*NOTREACHED*/
	}
	if (!ds_fd_open()) {
		dparts = ds_findpkg(pkgdev.cdevice, srcinst);
		if (dparts < 1) {
			progerr(ERR_DSARCH, srcinst);
			quit(99);
			/*NOTREACHED*/
		}
	}

	dparts--;

	if (ds_next(pkgdev.cdevice, instdir)) {
		progerr(ERR_DSTREAM);
		quit(99);
		/*NOTREACHED*/
	}
	if (chdir(get_PKGADM())) {
		progerr(ERR_CHDIR, get_PKGADM());
		quit(99);
		/*NOTREACHED*/
	}
	ds_close(1);
}

static void
usage(void)
{
	(void) fprintf(stderr, ERR_USAGE_PKGINSTALL);
	exit(1);
	/*NOTREACHED*/
}