OpenSolaris_b135/cmd/svr4pkg/pkgrm/main.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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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


/*
 * System includes
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <locale.h>
#include <libintl.h>
#include <pkgstrct.h>
#include <pkgdev.h>
#include <pkginfo.h>
#include <pkglocs.h>
#include <pkglib.h>
#include <assert.h>

/*
 * libinstzones includes
 */

#include <instzones_api.h>

/*
 * consolidation pkg command library includes
 */

#include <pkglib.h>

/*
 * local pkg command library includes
 */

#include "install.h"
#include "libinst.h"
#include "libadm.h"
#include "messages.h"

/*
 * pkgrm local includes
 */

#include "quit.h"

/*
 * exported global variables
 */

/* these globals are set by ckreturn and used by quit.c */

int	admnflag = 0;	/* != 0 if any pkg op admin setting failure (4) */
int	doreboot = 0;	/* != 0 if reboot required after installation */
int	failflag = 0;	/* != 0 if fatal error has occurred (1) */
int	intrflag = 0;	/* != 0 if user selected quit (3) */
int	ireboot = 0;	/* != 0 if immediate reboot required */
int	nullflag = 0;	/* != 0 if admin interaction required (5) */
int	warnflag = 0;	/* != 0 if non-fatal error has occurred (2) */

/* imported by quit.c */
int	npkgs = 0;	/* the number of packages yet to be installed */

/* imported by presvr4.c */
int	started = 0;
char	*tmpdir = NULL;	/* location to place temporary files */

/* imported by various (many) */
struct admin	adm;	/* holds info about installation admin */
struct pkgdev	pkgdev;	/* holds info about the installation device */

/*
 * internal global variables
 */

static char	*admnfile = NULL;	/* file to use for installation admin */
static char	*pkginst = NULL;	/* current pkg/src instance 2 process */
static char	*vfstab_file = NULL;
static char	*zoneTempDir = (char *)NULL;

/* set by ckreturn() */

static int	interrupted = 0;	/* last pkg op was quit (1,2,3,4,5) */

static int	nointeract = 0;		/* non-zero - no user interaction */
static int	pkgrmremote = 0;	/* remove pkg objs stored remotely  */
static int	pkgverbose = 0;		/* non-zero if verbose mode selected */

/*
 * Assume the package complies with the standards as regards user
 * interaction during procedure scripts.
 */

static int	old_pkg = 0;
static int	old_symlinks = 0;
static int	no_map_client = 0;

/* Set by -O nozones: do not process any zones */

static boolean_t	noZones = B_FALSE;

/* Set by -O zonelist=<names...>: process only named zones */

static boolean_t	usedZoneList = B_FALSE;

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

static boolean_t	debugFlag = B_FALSE;

/*
 * imported (external) functions
 */

/* presvr4.c */

extern int	presvr4(char *pkg, int a_nointeract);

/* check.c */

extern int	preremove_verify(char **a_pkgList, zoneList_t a_zlst,
			char *a_zoneTempDir);
/* quit.c */

extern void	quitSetZonelist(zoneList_t a_zlst);

/*
 * imported (external) variables
 */

extern char	*pkgdir;

/* printable string - if string is null results in ??? */

#define	PSTR(STR) (((STR) == (char *)NULL) ? "???" : (STR))

#define	MAX_FDS	20

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

#define	INHERITFS	"inherited-filesystem="
#define	INHERITFS_LEN	((sizeof (INHERITFS))-1)

/*
 * forward declarations
 */

static void		ckreturn(int retcode);
static void		create_zone_adminfile(char **r_zoneAdminFile,
				char *a_zoneTempDir, char *a_admnfile);
static void		create_zone_tempdir(char **r_zoneTempDir,
				char *a_tmpdir);
static int		doRemove(int a_nodelete, char *a_altBinDir,
				int a_longestPkg, char *a_adminFile,
				char *a_zoneAdminFile, zoneList_t zlst);
static int		pkgRemove(int a_nodelete, char *a_altBinDir,
				char *a_adminFile, char **a_inheritedPkgDirs);
static int		pkgZoneCheckRemove(char *a_zoneName,
				char **a_inheritedPkgDirs, char *a_altBinDir,
				char *a_adminFile, char *a_stdoutPath,
				zone_state_t a_zoneState, boolean_t tmpzone);
static int		pkgZoneRemove(char *a_zoneName,
				char **a_inheritedPkgDirs, int a_nodelete,
				char *a_altBinDir, char *a_adminFile,
				zone_state_t a_zoneState, boolean_t tmpzone);
static void		resetreturn();
static void		usage(void);
static boolean_t	check_applicability(char *a_packageDir,
				char *a_pkgInst, char *a_rootPath,
				CAF_T a_flags);
static boolean_t	check_packages(char **a_pkgList, char *a_packageDir);
static boolean_t	path_valid(char *path);
static boolean_t	remove_packages(char **a_pkgList, int a_nodelete,
				int a_longestPkg, int a_repeat,
				char *a_altBinDir, char *a_pkgdir,
				char *a_spoolDir, boolean_t a_noZones);
static boolean_t	remove_packages_from_spool_directory(char **a_pkgList,
				int a_nodelete, int a_longestPkg, int a_repeat,
				char *a_altBinDir);
static boolean_t	remove_packages_in_global_no_zones(char **a_pkgList,
				int a_nodelete, int a_longestPkg, int a_repeat,
				char *a_altBinDir);
static boolean_t	remove_packages_in_global_with_zones(char **a_pkgList,
				int a_nodelete, int a_longestPkg, int a_repeat,
				char *a_altBinDir, char *a_pkgdir,
				zoneList_t a_zlst);
static boolean_t	remove_packages_in_nonglobal_zone(char **a_pkgList,
				int a_nodelete, int a_longestPkg, int a_repeat,
				char *a_altBinDir, char *a_pkgdir);
static boolean_t	shall_we_continue(char *a_pkgInst, int a_npkgs);

/*
 * *****************************************************************************
 * global external (public) functions
 * *****************************************************************************
 */

/*
 * Name:	main
 * Description:	main entry point for pkgrm
 * Returns:	int
 *   0        Successful completion
 *   1        Fatal error.
 *   2        Warning.
 *   3        Interruption.
 *   4        Administration.
 *   5        Administration. Interaction is required. Do not use pkgrm -n.
 *  10       Reboot after removal of all packages.
 *  20       Reboot after removal of this package.
 */

int
main(int argc, char **argv)
{
	char			**category = NULL;
	char			*altBinDir = (char *)NULL;
	char			*catg_arg = NULL;
	char			*p;
	char			*prog_full_name = NULL;
	char			*spoolDir = 0;
	int			c;
	int			longestPkg = 0;
	int			n;
	int			nodelete = 0;	/* dont rm files/run scripts */
	int			pkgLgth = 0;
	int			repeat;
	struct sigaction	nact;
	struct sigaction	oact;

	/* 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);

	/* tell quit which ckreturn function to call */

	quitSetCkreturnFunc(&ckreturn);

	/* 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);
	}

	if (z_running_in_global_zone() && !enable_local_fs()) {
		progerr(ERR_CANNOT_ENABLE_LOCAL_FS);
	}

	pkgserversetmode(DEFAULTMODE);

	/*
	 * ********************************************************************
	 * parse command line options
	 * ********************************************************************
	 */

	while ((c = getopt(argc, argv, "?Aa:b:FMnO:R:s:V:vY:Z")) != EOF) {
		switch (c) {
		/*
		 * Public interface: Allow admin to remove objects
		 * from a service area via a reference client.
		 * Remove the package files from the client's file
		 * system, absolutely. If a file is shared with other
		 * packages, the default behavior is to not remove
		 * the file from the client's file system.
		 */
		case 'A':
		    pkgrmremote++;
		    break;

		/*
		 * Public interface: Use the installation
		 * administration file, admin, in place of the
		 * default admin file. pkgrm first looks in the
		 * current working directory for the administration
		 * file.  If the specified administration file is not
		 * in the current working directory, pkgrm looks in
		 * the /var/sadm/install/admin directory for the
		 * administra- tion file.
		 */
		case 'a':
		    admnfile = flex_device(optarg, 0);
		    break;

		/*
		 * Not a public interface:  location where package executables
		 * can be found - default is /usr/sadm/install/bin.
		 */
		case 'b':
			if (!path_valid(optarg)) {
				progerr(ERR_PATH, optarg);
				quit(1);
			}
			if (isdir(optarg) != 0) {
				p = strerror(errno);
				progerr(ERR_CANNOT_USE_DIR, optarg, p);
				quit(1);
			}
			altBinDir = optarg;
			break;

		/*
		 * Not a public interface: pass -F option to
		 * pkgremove which suppresses the removal of any
		 * files and any class action scripts, and suppresses
		 * the running of any class action scripts.  The
		 * package files remain but the package looks like it
		 * is not installed. This is mainly for use by the
		 * upgrade process.
		 */
		case 'F':
		    nodelete++;
		    break;

		/*
		 * Public interface: Instruct pkgrm 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':
		    no_map_client = 1;
		    break;

		/*
		 * Public interface: package removal occurs in
		 * non-interactive mode.  Suppress output of the list of
		 * removed files. The default mode is interactive.
		 */
		case 'n':
		    nointeract++;
		    (void) echoSetFlag(B_FALSE);
		    break;

		/*
		 * Not a public interface: the -O option allows the behavior
		 * of the package tools to be modified. Recognized options:
		 * -> debug
		 * ---> enable debugging output
		 * -> nozones
		 * ---> act as though in global zone with no non-global zones
		 * -> inherited-filesystems
		 * ---> add specified file system to list of file systems
		 * ---> that are inherited from the global zone
		 * -> 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
		 * -> zonelist="<names...>"
		 * ---> add package to space-separated list of zones only
		 */

		case 'O':
			for (p = strtok(optarg, ","); p != (char *)NULL;
				p = strtok(NULL, ",")) {

				if (strcmp(p, "nozones") == 0) {
					noZones = B_TRUE;
					continue;
				}

				if (strncmp(p, INHERITFS, INHERITFS_LEN) == 0) {
					if (z_add_inherited_file_system(
						p+INHERITFS_LEN) == B_FALSE) {
						progerr(ERR_NOSUCH_INHERITED,
							p+INHERITFS_LEN);
						quit(1);
						/* NOTREACHED */
					}
					continue;
				}

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

				if (strcmp(p, "debug") == 0) {
					/* set debug flag/enable debug output */
					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;
				}

				if (strncmp(p, "zonelist=", 9) == 0) {
					if (z_set_zone_spec(p + 9) == -1)
						quit(1);
					usedZoneList = B_TRUE;
					continue;
				}

				/* -O option not recognized - issue warning */

				progerr(ERR_INVALID_O_OPTION, p);
				continue;
			}
			break;

		/*
		 * Public interface: defines 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.
		 */
		case 'R':
		    if (!set_inst_root(optarg)) {
			    progerr(ERR_ROOT_CMD);
			    exit(1);
		    }
		    break;

		/*
		 * Public interface: remove the specified package(s)
		 * from the directory spool.  The default directory
		 * for spooled packages is /var/sadm/pkg.
		 */
		case 's':
		    spoolDir = flex_device(optarg, 1);
		    break;

		/*
		 * Public interface: Allow admin to establish the client
		 * filesystem using a vfstab-like file of stable format.
		 */
		case 'V':
		    vfstab_file = flex_device(optarg, 2);
		    no_map_client = 0;
		    break;

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

		/*
		 * Public interface: remove packages based on the
		 * CATEGORY variable from the installed/spooled
		 * pkginfo file
		 */
		case 'Y':
		    catg_arg = strdup(optarg);

		    if ((category = get_categories(catg_arg)) == NULL) {
			    progerr(ERR_CAT_INV, catg_arg);
			    exit(1);
		    } else if (is_not_valid_category(category,
				    get_prog_name())) {
			    progerr(ERR_CAT_SYS);
			    exit(1);
		    } else if (is_not_valid_length(category)) {
			    progerr(ERR_CAT_LNGTH);
			    exit(1);
		    }

		    break;

		/*
		 * unrecognized option
		 */
		default:
		    usage();
		    /* NOTREACHED */
		}
	}

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

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

	(void) echoDebugSetFlag(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());
	}

	/* -s cannot be used with several */

	if (spoolDir != (char *)NULL) {
		if (admnfile != (char *)NULL) {
			progerr(ERR_SPOOLDIR_AND_ADMNFILE);
			usage();
			/* NOTREACHED */
		}

		if (pkgrmremote != 0) {
			progerr(ERR_SPOOLDIR_AND_PKGRMREMOTE);
			usage();
			/* NOTREACHED */
		}

		if (pkgverbose != 0) {
			progerr(ERR_SPOOLDIR_AND_PKGVERBOSE);
			usage();
			/* NOTREACHED */
		}

		if (is_an_inst_root() != 0) {
			progerr(ERR_SPOOLDIR_AND_INST_ROOT);
			usage();
			/* NOTREACHED */
		}
	}

	/* -V cannot be used with -A */

	if (no_map_client && pkgrmremote) {
		progerr(ERR_V_USED_AND_PKGRMREMOTE);
		usage();
		/* NOTREACHED */
	}

	/* -n used without pkg names or category */

	if (nointeract && (optind == argc) && (catg_arg == NULL)) {
		progerr(ERR_BAD_N_PKGRM);
		usage();
		/* NOTREACHED */
	}

	/* Error if specified zone list isn't valid on target */
	if (usedZoneList && z_verify_zone_spec() == -1)
		usage();

	/*
	 * 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);

	/* establish temporary directory to use */

	tmpdir = getenv("TMPDIR");
	if (tmpdir == NULL) {
		tmpdir = P_tmpdir;
	}

	echoDebug(DBG_PKGRM_TMPDIR, tmpdir);

	/* initialize path parameters */

	set_PKGpaths(get_inst_root());

	/*
	 * initialize installation admin parameters - if removing from a spool
	 * directory then the admin file is ignore.
	 */

	if (spoolDir == NULL) {
		echoDebug(DBG_PKGRM_ADMINFILE, admnfile ? admnfile : "");
		setadminFile(admnfile);
	}

	/*
	 * if running in the global zone, and non-global zones exist, then
	 * enable hollow package support so that any packages that are marked
	 * SUNW_PKG_HOLLOW=true will be correctly removed in non-global zones
	 * when removed directly in the global zone by the global zone admin.
	 */

	if (is_depend_pkginfo_DB()) {
		echoDebug(DBG_PKGRM_HOLLOW_ENABLED);
	} else if ((z_running_in_global_zone() == B_TRUE) &&
		(z_non_global_zones_exist() == B_TRUE)) {
		echoDebug(DBG_PKGRM_ENABLING_HOLLOW);
		set_depend_pkginfo_DB(B_TRUE);
	}

	/*
	 * See if user wants this to be handled as an old style pkg.
	 * NOTE : the ``exception_pkg()'' stuff is to be used only
	 * through on495. This function comes out for on1095. See
	 * PSARC 1993-546. -- JST
	 */
	if (getenv("NONABI_SCRIPTS") != NULL) {
		old_pkg = 1;
	}

	/*
	 * See if the user wants to process symlinks consistent with
	 * the old behavior.
	 */

	if (getenv("PKG_NONABI_SYMLINKS") != NULL) {
		old_symlinks = 1;
	}

	if (devtype((spoolDir ? spoolDir : get_PKGLOC()), &pkgdev) ||
	    pkgdev.dirname == NULL) {
		progerr(ERR_BAD_DEVICE, spoolDir ? spoolDir : get_PKGLOC());
		quit(1);
		/* NOTREACHED */
	}

	pkgdir = pkgdev.dirname;
	repeat = ((optind >= argc) && pkgdev.mount);

	/*
	 * error if there are packages on the command line and a category
	 * was specified
	 */

	if (optind < argc && catg_arg != NULL) {
		progerr(ERR_PKGS_AND_CAT_PKGRM);
		usage();
		/* NOTREACHED */
	}

	/*
	 * ********************************************************************
	 * main package processing "loop"
	 * ********************************************************************
	 */

	for (;;) {
		boolean_t	b;
		char		**pkglist;	/* points to array of pkgs */

		/*
		 * mount the spool device if required
		 */

		if (pkgdev.mount) {
			if (n = pkgmount(&pkgdev, NULL, 0, 0, 1)) {
				quit(n);
				/* NOTREACHED */
			}
		}

		if (chdir(pkgdev.dirname)) {
			progerr(ERR_CHDIR, pkgdev.dirname);
			quit(1);
			/* NOTREACHED */
		}

		/*
		 * spool device mounted/available - get the list of the
		 * packages to remove
		 */

		n = pkgGetPackageList(&pkglist, argv, optind,
			catg_arg, category, &pkgdev);

		switch (n) {
			case -1:	/* no packages found */
				echoDebug(DBG_PKGLIST_RM_NONFOUND,
					PSTR(pkgdev.dirname));
				progerr(ERR_NOPKGS, pkgdev.dirname);
				quit(1);
				/* NOTREACHED */

			case 0:		/* packages found */
				break;

			default:	/* "quit" error */
				echoDebug(DBG_PKGLIST_RM_ERROR,
					pkgdev.dirname, n);
				quit(n);
				/* NOTREACHED */
		}

		/*
		 * count the number of packages to remove
		 * NOTE: npkgs is a global variable that is referenced by quit.c
		 * when error messages are generated - it is referenced directly
		 * by the other functions called below...
		 */

		for (npkgs = 0; pkglist[npkgs] != (char *)NULL; /* void */) {
			pkgLgth = strlen(pkglist[npkgs]);
			if (pkgLgth > longestPkg) {
				longestPkg = pkgLgth;
			}
			echoDebug(DBG_PKG_SELECTED, npkgs, pkglist[npkgs]);
			npkgs++;
		}

		/* output number of packages to be removed */

		echoDebug(DBG_NUM_PKGS_TO_REMOVE, npkgs, longestPkg);

		/*
		 * package list generated - remove packages
		 */

		b = remove_packages(pkglist, nodelete, longestPkg, repeat,
			altBinDir, pkgdev.dirname, spoolDir, noZones);

		/*
		 * unmount the spool directory if necessary
		 */

		if (pkgdev.mount) {
			(void) chdir("/");
			if (pkgumount(&pkgdev)) {
				progerr(ERR_PKGUNMOUNT, pkgdev.bdevice);
				quit(99);
				/* NOTREACHED */

			}
		}

		/*
		 * continue with next sequence of packages if continue set
		 */

		if (b == B_TRUE) {
			continue;
		}

		/*
		 * not continuing - quit with 0 exit code
		 */

		quit(0);
		/* NOTREACHED */
#ifdef lint
		return (0);
#endif	/* lint */
	}
}

/*
 * *****************************************************************************
 * static internal (private) functions
 * *****************************************************************************
 */

/*
 * Name:	doRemove
 * Description:	Remove a package from the global zone, and optionally from one
 *		or more non-global zones.
 * Arguments:	a_nodelete: should the files and scripts remain installed?
 *			- if != 0 pass -F flag to pkgremove - suppress
 *			the removal of any files and any class action scripts
 *			and suppress the running of any class action scripts.
 *			The package files remain but the package looks like it
 *			is not installed. This is mainly for use by upgrade.
 *			- if == 0 do not pass -F flag to pkgremove - all
 *			files and class action scripts are removed, and any
 *			appropriate class action scripts are run.
 *		a_altBinDir - pointer to string representing location of the
 *			pkgremove executable to run. If not NULL, then pass
 *			the path specified to the -b option to pkgremove.
 *		a_longestPkg - length of the longest package "name" (for
 *			output format alignment)
 *		a_adminFile - pointer to string representing the admin
 *			file to pass to pkgremove when removing a package from
 *			the global zone only. Typically the admin file used for
 *			the global zone is the admin file passed in by the user.
 *			If this is == NULL no admin file is given to pkgremove.
 *		a_zoneAdminFile - pointer to string representing the admin
 *			file to pass to pkgremove when removing the package
 *			from a non-global zone only. Typically the admin file
 *			used for non-global zones supresses all checks since
 *			the dependency checking is done for all zones first
 *			before proceeding.
 *			A zoneAdminFile MUST be specified if a_zlst != NULL.
 *			A zoneAdminFile must NOT be specified if a_zlst == NULL.
 *		a_zlst - list of zones to process; NULL if no zones to process.
 * Returns:	int	(see ckreturn() function for details)
 *		0 - success
 *		1 - package operation failed (fatal error)
 *		2 - non-fatal error (warning)
 *		3 - user selected quit (operation interrupted)
 *		4 - admin settings prevented operation
 *		5 - interaction required and -n (non-interactive) specified
 *		"10" will be added to indicate "immediate reboot required"
 *		"20" will be added to indicate "reboot after install required"
 */

static int
doRemove(int a_nodelete, char *a_altBinDir, int a_longestPkg, char *a_adminFile,
	char *a_zoneAdminFile, zoneList_t a_zlst)
{
	boolean_t	b;
	char		**inheritedPkgDirs;
	char		*zoneName;
	char		ans[MAX_INPUT];
	int		n;
	int		zoneIndex;
	int		zonesSkipped;
	struct pkginfo	*pinfo = (struct pkginfo *)NULL;
	zone_state_t	zst;

	/* entry assertions */

	if (a_zlst != (zoneList_t)NULL) {
		/* zone list specified - zone admin file required */
		assert(a_zoneAdminFile != (char *)NULL);
		assert(*a_zoneAdminFile != '\0');
	} else {
		/* no zone list specified - no zone admin file needed */
		assert(a_zoneAdminFile == (char *)NULL);
	}

	/* NOTE: required 'pkgdir' set to spool directory or NULL */
	b = pkginfoIsPkgInstalled(&pinfo, pkginst);
	if (b == B_FALSE) {
		progerr(ERR_NO_SUCH_INSTANCE, pkginst);
		pkginfoFree(&pinfo);
		return (2);
	}

	/* entry debugging info */

	echoDebug(DBG_DOREMOVE_ENTRY);
	echoDebug(DBG_DOREMOVE_ARGS, PSTR(pinfo->pkginst), PSTR(pinfo->name),
		PSTR(pinfo->arch), PSTR(pinfo->version), PSTR(pinfo->basedir),
		PSTR(pinfo->catg), pinfo->status);

	if (!nointeract) {
		char	fmt1[100];

		/* create format based on max pkg name length */

		(void) snprintf(fmt1, sizeof (fmt1), "   %%-%d.%ds  %%s",
				a_longestPkg, a_longestPkg);

		if (pinfo->status == PI_SPOOLED) {
			echo(INFO_SPOOLED);
		} else {
			if (getuid()) {
				progerr(ERR_NOT_ROOT, get_prog_name());
				exit(1);
			}
			echo(INFO_INSTALL);
		}

		echo(fmt1, pinfo->pkginst, pinfo->name);

		if (pinfo->arch || pinfo->version) {
			char	fmt2[100];

			/* create format based on max pkg name length */

			(void) snprintf(fmt2, sizeof (fmt2), "   %%%d.%ds  ",
					a_longestPkg, a_longestPkg);

			/* LINTED variable format specifier to fprintf() */
			(void) fprintf(stderr, fmt2, "");

			if (pinfo->arch) {
				(void) fprintf(stderr, "(%s) ", pinfo->arch);
			}

			if (pinfo->version) {
				(void) fprintf(stderr, "%s", pinfo->version);
			}

			(void) fprintf(stderr, "\n");
		}

		n = ckyorn(ans, NULL, NULL, NULL, ASK_CONFIRM);
		if (n != 0) {
			quit(n);
			/* NOTREACHED */
		}

		if (strchr("yY", *ans) == NULL) {
			pkginfoFree(&pinfo);
			return (0);
		}
	}

	if (pinfo->status == PI_PRESVR4) {
		pkginfoFree(&pinfo);
		return (presvr4(pkginst, nointeract));
	}

	if (pinfo->status == PI_SPOOLED) {
		/* removal from a directory */
		echo(INFO_RMSPOOL, pkginst);
		pkginfoFree(&pinfo);
		return (rrmdir(pkginst));
	}

	/* exit if not root */

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

	pkginfoFree(&pinfo);

	zonesSkipped = 0;

	if (interrupted != 0) {
		echo(MSG_DOREMOVE_INTERRUPTED_B4_Z, pkginst);
		echoDebug(MSG_DOREMOVE_INTERRUPTED_B4_Z, pkginst);
		return (n);
	}

	echoDebug(DBG_REMOVE_FLAG_VALUES, "before pkgZoneRemove",
		admnflag, doreboot, failflag, interrupted,
		intrflag, ireboot, nullflag, warnflag);

	for (zoneIndex = 0;
	    a_zlst != NULL &&
	    (zoneName = z_zlist_get_zonename(a_zlst, zoneIndex)) != NULL;
	    zoneIndex++) {

		/* skip the zone if it is NOT running */

		zst = z_zlist_get_current_state(a_zlst, zoneIndex);
		if (zst != ZONE_STATE_RUNNING && zst != ZONE_STATE_MOUNTED) {
			zonesSkipped++;
			echoDebug(DBG_SKIPPING_ZONE, zoneName);
			continue;
		}

		echo(MSG_REMOVE_PKG_FROM_ZONE, pkginst, zoneName);
		echoDebug(DBG_REMOVE_PKG_FROM_ZONE, pkginst, zoneName);

		/* determine list of directories inherited from global zone */

		inheritedPkgDirs = z_zlist_get_inherited_pkg_dirs(a_zlst,
					zoneIndex);

		/*
		 * remove package from zone; use the zone admin file which
		 * suppresses all checks.
		 */

		n = pkgZoneRemove(z_zlist_get_scratch(a_zlst, zoneIndex),
			inheritedPkgDirs, a_nodelete, a_altBinDir,
			a_zoneAdminFile, zst, B_FALSE);

		/* set success/fail condition variables */

		ckreturn(n);

		echoDebug(DBG_REMOVE_FLAG_VALUES, "after pkgZoneRemove",
			admnflag, doreboot, failflag, interrupted, intrflag,
			ireboot, nullflag, warnflag);
	}

	if (zonesSkipped > 0) {
		echoDebug(DBG_ZONES_SKIPPED, zonesSkipped);

		for (zoneIndex = 0;
			(zoneName = z_zlist_get_zonename(a_zlst, zoneIndex)) !=
				(char *)NULL; zoneIndex++) {

			/* skip the zone if it IS running */

			zst = z_zlist_get_current_state(a_zlst, zoneIndex);
			if (zst == ZONE_STATE_RUNNING ||
			    zst == ZONE_STATE_MOUNTED) {
				zonesSkipped++;
				echoDebug(DBG_SKIPPING_ZONE_BOOT, zoneName);
				continue;
			}

			/* skip the zone if it is NOT bootable */

			if (z_zlist_is_zone_runnable(a_zlst,
						zoneIndex) == B_FALSE) {
				echo(MSG_SKIPPING_ZONE_NOT_RUNNABLE, zoneName);
				echoDebug(DBG_SKIPPING_ZONE_NOT_RUNNABLE,
					zoneName);
				continue;
			}

			/* mount up the zone */

			echo(MSG_BOOTING_ZONE, zoneName);
			echoDebug(DBG_BOOTING_ZONE, zoneName);

			b = z_zlist_change_zone_state(a_zlst, zoneIndex,
				ZONE_STATE_MOUNTED);
			if (b == B_FALSE) {
				progerr(ERR_CANNOT_BOOT_ZONE, zoneName);
				/* set fatal error return condition */
				ckreturn(1);
				continue;
			}

			echo(MSG_REMOVE_PKG_FROM_ZONE, pkginst, zoneName);

			/* determine list of dirs inherited from global zone */

			inheritedPkgDirs =
				z_zlist_get_inherited_pkg_dirs(a_zlst,
						zoneIndex);

			/*
			 * remove package from zone; use the zone admin file
			 * which suppresses all checks.
			 */

			n = pkgZoneRemove(z_zlist_get_scratch(a_zlst,
				zoneIndex), inheritedPkgDirs,
				a_nodelete, a_altBinDir, a_zoneAdminFile,
				ZONE_STATE_MOUNTED, B_TRUE);

			/* set success/fail condition variables */

			ckreturn(n);

			echoDebug(DBG_REMOVE_FLAG_VALUES, "after pkgZoneRemove",
				admnflag, doreboot, failflag, interrupted,
				intrflag, ireboot, nullflag, warnflag);

			/* restore original state of zone */

			echo(MSG_RESTORE_ZONE_STATE, zoneName);
			echoDebug(DBG_RESTORE_ZONE_STATE, zoneName);

			b = z_zlist_restore_zone_state(a_zlst, zoneIndex);
		}
	}

	/*
	 * Process global zone if it was either the only possible
	 * target (no list of zones specified) or it appears in the list
	 */
	if (a_zlst == NULL || z_on_zone_spec(GLOBAL_ZONENAME)) {
		/* reset interrupted flag before calling pkgremove */
		interrupted = 0;	/* last action was NOT quit */

		/*
		 * call pkgremove for this package for the global zone;
		 * use the admin file passed in by the user via -a.
		 */
		n = pkgRemove(a_nodelete, a_altBinDir, a_adminFile,
		    z_get_inherited_file_systems());

		/* set success/fail condition variables */
		ckreturn(n);
	}

	return (n);
}

/*
 *  function to clear out any exisiting error return conditions that may have
 *  been set by previous calls to ckreturn()
 */
static void
resetreturn()
{
	admnflag = 0;	/* != 0 if any pkg op admin setting failure (4) */
	doreboot = 0;	/* != 0 if reboot required after installation (>= 10) */
	failflag = 0;	/* != 0 if fatal error has occurred (1) */
	intrflag = 0;	/* != 0 if user selected quit (3) */
	ireboot = 0;	/* != 0 if immediate reboot required (>= 20) */
	nullflag = 0;	/* != 0 if admin interaction required (5) */
	warnflag = 0;	/* != 0 if non-fatal error has occurred (2) */
	interrupted = 0;	/* last pkg op was quit (1,2,3,4,5) */
}

/*
 *  function which checks the indicated return value
 *  and indicates disposition of installation
 */
static void
ckreturn(int retcode)
{
	/*
	 * entry debugging info
	 */

	echoDebug(DBG_PKGRM_CKRETURN, retcode, PSTR(pkginst));

	switch (retcode) {
	    case  0:		/* successful */
	    case 10:
	    case 20:
		break; /* empty case */

	    case  1:		/* package operation failed (fatal error) */
	    case 11:
	    case 21:
		failflag++;
		interrupted++;
		break;

	    case  2:		/* non-fatal error (warning) */
	    case 12:
	    case 22:
		warnflag++;
		interrupted++;
		break;

	    case  3:		/* user selected quit; operation interrupted */
	    case 13:
	    case 23:
		intrflag++;
		interrupted++;
		break;

	    case  4:		/* admin settings prevented operation */
	    case 14:
	    case 24:
		admnflag++;
		interrupted++;
		break;

	    case  5:		/* administration: interaction req (no -n) */
	    case 15:
	    case 25:
		nullflag++;
		interrupted++;
		break;

	    default:
		failflag++;
		interrupted++;
		return;
	}

	if (retcode >= 20) {
		ireboot++;
	} else if (retcode >= 10) {
		doreboot++;
	}
}

static int
pkgZoneCheckRemove(char *a_zoneName, char **a_inheritedPkgDirs,
	char *a_altBinDir, char *a_adminFile, char *a_stdoutPath,
	zone_state_t a_zoneState, boolean_t tmpzone)
{
	char	*arg[MAXARGS];
	char	*p;
	char	adminfd_path[PATH_MAX];
	char	path[PATH_MAX];
	int	fds[MAX_FDS];
	int	maxfds;
	int	n;
	int	nargs;

	/* entry assertions */

	assert(a_zoneName != (char *)NULL);
	assert(*a_zoneName != '\0');

	/* entry debugging info */

	echoDebug(DBG_PKGZONECHECKREMOVE_ENTRY);
	echoDebug(DBG_PKGZONECHECKREMOVE_ARGS, a_zoneName, PSTR(pkginst),
		PSTR(pkgdev.dirname), PSTR(a_adminFile), PSTR(a_stdoutPath));

	/* generate path to pkgremove */

	(void) snprintf(path, sizeof (path), "%s/pkgremove",
		a_altBinDir == (char *)NULL ? PKGBIN : a_altBinDir);

	/* start at first file descriptor */

	maxfds = 0;

	/*
	 * generate argument list for call to pkgremove
	 */

	/* start at argument 0 */

	nargs = 0;

	/* first argument is path to executable */

	arg[nargs++] = strdup(path);

	/* second argument is always: pass -O debug to pkgremove: debug mode */

	if (debugFlag == B_TRUE) {
		arg[nargs++] = "-O";
		arg[nargs++] = "debug";
	}

	/* pkgrm -b dir: pass -b to pkgremove */

	if (a_altBinDir != (char *)NULL) {
		arg[nargs++] = "-b";
		arg[nargs++] = a_altBinDir;
	}

	/*
	 * NONABI_SCRIPTS defined: pass -o to pkgremove; refers to a
	 * pkg requiring operator interaction during a procedure script
	 * (common before on1093)
	 */

	if (old_pkg) {
		arg[nargs++] = "-o";
	}

	/*
	 * PKG_NONABI_SYMLINKS defined: pass -y to pkgremove; process
	 * symlinks consistent with old behavior
	 */

	if (old_symlinks) {
		arg[nargs++] = "-y";
	}

	/* pkgrm -M: pass -M to pkgremove: don't mount client file systems */

	arg[nargs++] = "-M";

	/* pkgrm -A: pass -A to pkgremove */

	if (pkgrmremote) {
		arg[nargs++] = "-A";
	}

	/* pkgrm -v: pass -v to pkgremove: never trace scripts */

	/* pass "-O enable-hollow-package-support" */

	if (is_depend_pkginfo_DB()) {
		arg[nargs++] = "-O";
		arg[nargs++] = "enable-hollow-package-support";
	}

	/* pass -n to pkgremove: always in noninteractive mode */

	arg[nargs++] = "-n";

	/* pkgrm -a admin: pass -a admin to pkgremove: admin file */

	if (a_adminFile) {
		int fd;
		fd = openLocal(a_adminFile, O_RDONLY, tmpdir);
		if (fd < 0) {
			progerr(ERR_CANNOT_COPY_LOCAL, a_adminFile,
				errno, strerror(errno));
			return (1);
		}
		(void) snprintf(adminfd_path, sizeof (adminfd_path),
			"/proc/self/fd/%d", fd);
		fds[maxfds++] = fd;
		arg[nargs++] = "-a";
		arg[nargs++] = strdup(adminfd_path);
	}

	/*
	 * pkgadd -R root: pass -R /a to pkgremove in mounted zone
	 */
	if (a_zoneState == ZONE_STATE_MOUNTED) {
		arg[nargs++] = "-R";
		arg[nargs++] = "/a";
	}

	/* pkgrm -F: pass -F to pkgremove: always update DB only */

	arg[nargs++] = "-F";

	/* pass "-O preremovecheck" */

	arg[nargs++] = "-O";
	arg[nargs++] = "preremovecheck";

	/* add "-O addzonename" */

	arg[nargs++] = "-O";
	arg[nargs++] = "addzonename";

	/* add all inherited file systems */

	if (a_inheritedPkgDirs != (char **)NULL) {
		for (n = 0; a_inheritedPkgDirs[n] != (char *)NULL; n++) {
			char	ifs[MAXPATHLEN+22];
			(void) snprintf(ifs, sizeof (ifs),
				"inherited-filesystem=%s",
				a_inheritedPkgDirs[n]);
			arg[nargs++] = "-O";
			arg[nargs++] = strdup(ifs);
		}
	}

	/*
	 * add parent zone info/type
	 */

	p = z_get_zonename();
	if ((p != NULL) && (*p != '\0')) {
			char	zn[MAXPATHLEN];
			(void) snprintf(zn, sizeof (zn),
				"parent-zone-name=%s", p);
			arg[nargs++] = "-O";
			arg[nargs++] = strdup(zn);
	}

	/* current zone type */

	arg[nargs++] = "-O";
	if (z_running_in_global_zone() == B_TRUE) {
			char	zn[MAXPATHLEN];
			(void) snprintf(zn, sizeof (zn),
				"parent-zone-type=%s",
				TAG_VALUE_GLOBAL_ZONE);
			arg[nargs++] = strdup(zn);
	} else {
			char	zn[MAXPATHLEN];
			(void) snprintf(zn, sizeof (zn),
				"parent-zone-type=%s",
				TAG_VALUE_NONGLOBAL_ZONE);
			arg[nargs++] = strdup(zn);
	}

	/* Add arguments how to start the pkgserv */

	arg[nargs++] = "-O";
	arg[nargs++] = pkgmodeargument(tmpzone ? RUN_ONCE : pkgservergetmode());

	/* pass -N to pkgremove: program name to report */

	arg[nargs++] = "-N";
	arg[nargs++] = get_prog_name();

	/* add package instance name */

	arg[nargs++] = pkginst;

	/* terminate argument list */

	arg[nargs++] = NULL;

	/* execute pkgremove command */

	if (debugFlag == B_TRUE) {
		echoDebug(DBG_ZONE_EXEC_ENTER, a_zoneName, arg[0]);
		for (n = 0; arg[n]; n++) {
			echoDebug(DBG_ARG, n, arg[n]);
		}
	}

	/* terminate file descriptor list */

	fds[maxfds] = -1;

	/* exec command in zone */

	n = z_zone_exec(a_zoneName, path, arg, a_stdoutPath, (char *)NULL, fds);

	echoDebug(DBG_ZONE_EXEC_EXIT, a_zoneName, arg[0], n,
			PSTR(a_stdoutPath));

	/*
	 * close any files that were opened for use by the
	 * /proc/self/fd interface so they could be passed to programs
	 * via the z_zone_exec() interface
	 */

	for (; maxfds > 0; maxfds--) {
		(void) close(fds[maxfds-1]);
	}

	/* return results of pkgremove in zone execution */

	return (n);
}

static int
pkgZoneRemove(char *a_zoneName, char **a_inheritedPkgDirs,
	int a_nodelete, char *a_altBinDir, char *a_adminFile,
	zone_state_t a_zoneState, boolean_t tmpzone)
{
	char	*arg[MAXARGS];
	char	*p;
	char	adminfd_path[PATH_MAX];
	char	path[PATH_MAX];
	int	fds[MAX_FDS];
	int	maxfds;
	int	n;
	int	nargs;

	/* entry assertions */

	assert(a_zoneName != (char *)NULL);
	assert(*a_zoneName != '\0');

	/* entry debugging info */

	echoDebug(DBG_PKGZONEREMOVE_ENTRY);
	echoDebug(DBG_PKGZONEREMOVE_ARGS, a_zoneName, PSTR(pkginst),
		PSTR(pkgdev.dirname), a_nodelete, PSTR(a_adminFile));

	/* generate path to pkgremove */

	(void) snprintf(path, sizeof (path), "%s/pkgremove",
		a_altBinDir == (char *)NULL ? PKGBIN : a_altBinDir);

	/* start at first file descriptor */

	maxfds = 0;

	/*
	 * generate argument list for call to pkgremove
	 */

	/* start at argument 0 */

	nargs = 0;

	/* first argument is path to executable */

	arg[nargs++] = strdup(path);

	/* second argument is always: pass -O debug to pkgremove: debug mode */

	if (debugFlag == B_TRUE) {
		arg[nargs++] = "-O";
		arg[nargs++] = "debug";
	}

	/* pkgrm -b dir: pass -b to pkgremove */

	if (a_altBinDir != (char *)NULL) {
		arg[nargs++] = "-b";
		arg[nargs++] = a_altBinDir;
	}

	/*
	 * NONABI_SCRIPTS defined: pass -o to pkgremove; refers to a
	 * pkg requiring operator interaction during a procedure script
	 * (common before on1093)
	 */

	if (old_pkg) {
		arg[nargs++] = "-o";
	}

	/*
	 * PKG_NONABI_SYMLINKS defined: pass -y to pkgremove; process
	 * symlinks consistent with old behavior
	 */

	if (old_symlinks) {
		arg[nargs++] = "-y";
	}

	/* pkgrm -M: pass -M to pkgremove: don't mount client file systems */

	arg[nargs++] = "-M";

	/* pkgrm -A: pass -A to pkgremove */

	if (pkgrmremote) {
		arg[nargs++] = "-A";
	}

	/* pkgrm -v: pass -v to pkgremove: trace scripts */

	if (pkgverbose) {
		arg[nargs++] = "-v";
	}

	/* pass "-O enable-hollow-package-support" */

	if (is_depend_pkginfo_DB()) {
		arg[nargs++] = "-O";
		arg[nargs++] = "enable-hollow-package-support";
	}

	/* pkgrm -n: pass -n to pkgremove: noninteractive mode */

	if (nointeract) {
		arg[nargs++] = "-n";
	}

	/* pkgrm -a admin: pass -a admin to pkgremove: admin file */

	if (a_adminFile) {
		int fd;
		fd = openLocal(a_adminFile, O_RDONLY, tmpdir);
		if (fd < 0) {
			progerr(ERR_CANNOT_COPY_LOCAL, a_adminFile,
				errno, strerror(errno));
			return (1);
		}
		(void) snprintf(adminfd_path, sizeof (adminfd_path),
			"/proc/self/fd/%d", fd);
		fds[maxfds++] = fd;
		arg[nargs++] = "-a";
		arg[nargs++] = adminfd_path;
	}

	/*
	 * pkgadd -R root: pass -R /a to pkgremove in mounted zone
	 */
	if (a_zoneState == ZONE_STATE_MOUNTED) {
		arg[nargs++] = "-R";
		arg[nargs++] = "/a";
	}

	/* pkgrm -F: pass -F to pkgremove: update DB only */

	if (a_nodelete) {
		arg[nargs++] = "-F";
	}

	/* add "-O addzonename" */

	arg[nargs++] = "-O";
	arg[nargs++] = "addzonename";

	/* add all inherited file systems */

	if (a_inheritedPkgDirs != (char **)NULL) {
		for (n = 0; a_inheritedPkgDirs[n] != (char *)NULL; n++) {
			char	ifs[MAXPATHLEN+22];

			(void) snprintf(ifs, sizeof (ifs),
				"inherited-filesystem=%s",
				a_inheritedPkgDirs[n]);
			arg[nargs++] = "-O";
			arg[nargs++] = strdup(ifs);
		}
	}

	/*
	 * add parent zone info/type
	 */

	p = z_get_zonename();
	if ((p != NULL) && (*p != '\0')) {
			char	zn[MAXPATHLEN];
			(void) snprintf(zn, sizeof (zn),
				"parent-zone-name=%s", p);
			arg[nargs++] = "-O";
			arg[nargs++] = strdup(zn);
	}

	/* current zone type */

	arg[nargs++] = "-O";
	if (z_running_in_global_zone() == B_TRUE) {
			char	zn[MAXPATHLEN];
			(void) snprintf(zn, sizeof (zn),
				"parent-zone-type=%s",
				TAG_VALUE_GLOBAL_ZONE);
			arg[nargs++] = strdup(zn);
	} else {
			char	zn[MAXPATHLEN];
			(void) snprintf(zn, sizeof (zn),
				"parent-zone-type=%s",
				TAG_VALUE_NONGLOBAL_ZONE);
			arg[nargs++] = strdup(zn);
	}

	/* Add arguments how to start the pkgserv */

	arg[nargs++] = "-O";
	arg[nargs++] = pkgmodeargument(tmpzone ? RUN_ONCE : pkgservergetmode());

	/* pass -N to pkgremove: program name to report */

	arg[nargs++] = "-N";
	arg[nargs++] = get_prog_name();

	/* add package instance name */

	arg[nargs++] = pkginst;

	/* terminate argument list */

	arg[nargs++] = NULL;

	/* execute pkgremove command */

	if (debugFlag == B_TRUE) {
		echoDebug(DBG_ZONE_EXEC_ENTER, a_zoneName, arg[0]);
		for (n = 0; arg[n]; n++) {
			echoDebug(DBG_ARG, n, arg[n]);
		}
	}

	/* terminate file descriptor list */

	fds[maxfds] = -1;

	/* exec command in zone */

	n = z_zone_exec(a_zoneName, path, arg, (char *)NULL, (char *)NULL, fds);

	/*
	 * close any files that were opened for use by the
	 * /proc/self/fd interface so they could be passed to programs
	 * via the z_zone_exec() interface
	 */

	for (; maxfds > 0; maxfds--) {
		(void) close(fds[maxfds-1]);
	}

	return (n);
}

/*
 * Name:	pkgRemove
 * Description:	Invoke pkgremove in the current zone to perform a remove
 *		of a single package from the current zone or standalone system
 * Arguments:	a_nodelete: should the files and scripts remain installed?
 *			- if != 0 pass -F flag to pkgremove - suppress
 *			the removal of any files and any class action scripts
 *			and suppress the running of any class action scripts.
 *			The package files remain but the package looks like it
 *			is not installed. This is mainly for use by upgrade.
 *			- if == 0 do not pass -F flag to pkgremove - all
 *			files and class action scripts are removed, and any
 *			appropriate class action scripts are run.
 *		a_altBinDir - pointer to string representing location of the
 *			pkgremove executable to run. If not NULL, then pass
 *			the path specified to the -b option to pkgremove.
 *		a_adminFile - pointer to string representing the admin
 *			file to pass to pkgremove when removing the package.
 *			If this is == NULL no admin file is given to pkgremove.
 *		a_inheritedPkgDirs - pointer to array of strings, each one
 *			representing the non-global zones full path of a
 *			directory that is inherited from the global zone.
 * Returns:	int	(see ckreturn() function for details)
 *		0 - success
 *		1 - package operation failed (fatal error)
 *		2 - non-fatal error (warning)
 *		3 - user selected quit (operation interrupted)
 *		4 - admin settings prevented operation
 *		5 - interaction required and -n (non-interactive) specified
 *		"10" will be added to indicate "immediate reboot required"
 *		"20" will be added to indicate "reboot after install required"
 */

static int
pkgRemove(int a_nodelete, char *a_altBinDir, char *a_adminFile,
	char **a_inheritedPkgDirs)
{
	char	*arg[MAXARGS];
	char	*p;
	char	path[PATH_MAX];
	int	n;
	int	nargs;

	/* entry debugging info */

	echoDebug(DBG_PKGREMOVE_ENTRY);
	echoDebug(DBG_PKGREMOVE_ARGS, PSTR(pkginst), PSTR(pkgdev.dirname),
		a_nodelete, PSTR(a_adminFile));

	(void) snprintf(path, sizeof (path), "%s/pkgremove",
		a_altBinDir == (char *)NULL ? PKGBIN : a_altBinDir);

	nargs = 0;

	/* first argument is path to executable */

	arg[nargs++] = strdup(path);

	/* second argument is always: pass -O debug to pkgremove: debug mode */

	if (debugFlag == B_TRUE) {
		arg[nargs++] = "-O";
		arg[nargs++] = "debug";
	}

	/* Add arguments how to start the pkgserv */

	arg[nargs++] = "-O";
	arg[nargs++] = pkgmodeargument(pkgservergetmode());

	/* pkgrm -b dir: pass -b to pkgremove */

	if (a_altBinDir != (char *)NULL) {
		arg[nargs++] = "-b";
		arg[nargs++] = a_altBinDir;
	}

	/*
	 * NONABI_SCRIPTS defined: pass -o to pkgremove; refers to a
	 * pkg requiring operator interaction during a procedure script
	 * (common before on1093)
	 */

	if (old_pkg) {
		arg[nargs++] = "-o";
	}

	/*
	 * PKG_NONABI_SYMLINKS defined: pass -y to pkgremove; process
	 * symlinks consistent with old behavior
	 */

	if (old_symlinks) {
		arg[nargs++] = "-y";
	}

	/* pkgrm -M: pass -M to pkgrm: dont mount client file systems */

	if (no_map_client) {
		arg[nargs++] = "-M";
	}

	/* pkgrm -A: pass -A to pkgrm */

	if (pkgrmremote) {
		arg[nargs++] = "-A";
	}

	/* pkgrm -v: pass -v to pkgremove: trace scripts */

	if (pkgverbose) {
		arg[nargs++] = "-v";
	}

	/* pkgrm -n: pass -n to pkgremove: noninteractive mode */

	if (nointeract) {
		arg[nargs++] = "-n";
	}

	/* pkgrm -a admin: pass -a admin to pkgremove: admin file */

	if (a_adminFile) {
		arg[nargs++] = "-a";
		arg[nargs++] = strdup(a_adminFile);
	}

	/* pkgrm -V vfstab: pass -V vfstab to pkgremove: alternate vfstab */

	if (vfstab_file) {
		arg[nargs++] = "-V";
		arg[nargs++] = vfstab_file;
	}

	/* pkgrm -R root: pass -R root to pkgremove: alternative root */

	if (is_an_inst_root()) {
		arg[nargs++] = "-R";
		arg[nargs++] = get_inst_root();
	}

	/* pkgrm -F: pass -F to pkgremove: update DB only */

	if (a_nodelete) {
		arg[nargs++] = "-F";
	}

	/* add all inherited file systems */

	if (a_inheritedPkgDirs != (char **)NULL) {
		for (n = 0; a_inheritedPkgDirs[n] != (char *)NULL; n++) {
			char	ifs[MAXPATHLEN+22];
			(void) snprintf(ifs, sizeof (ifs),
				"inherited-filesystem=%s",
				a_inheritedPkgDirs[n]);
			arg[nargs++] = "-O";
			arg[nargs++] = strdup(ifs);
		}
	}

	/*
	 * add parent zone info/type
	 */

	p = z_get_zonename();
	if ((p != NULL) && (*p != '\0')) {
			char	zn[MAXPATHLEN];
			(void) snprintf(zn, sizeof (zn),
				"parent-zone-name=%s", p);
			arg[nargs++] = "-O";
			arg[nargs++] = strdup(zn);
	}

	/* current zone type */

	arg[nargs++] = "-O";
	if (z_running_in_global_zone() == B_TRUE) {
			char	zn[MAXPATHLEN];
			(void) snprintf(zn, sizeof (zn),
				"parent-zone-type=%s",
				TAG_VALUE_GLOBAL_ZONE);
			arg[nargs++] = strdup(zn);
	} else {
			char	zn[MAXPATHLEN];
			(void) snprintf(zn, sizeof (zn),
				"parent-zone-type=%s",
				TAG_VALUE_NONGLOBAL_ZONE);
			arg[nargs++] = strdup(zn);
	}

	/* pass -N to pkgremove: program name to report */

	arg[nargs++] = "-N";
	arg[nargs++] = get_prog_name();

	/* add package instance name */

	arg[nargs++] = pkginst;

	/* terminate argument list */

	arg[nargs++] = NULL;

	/*
	 * run the appropriate pkgremove command in the specified zone
	 */

	if (debugFlag == B_TRUE) {
		echoDebug(DBG_ZONE_EXEC_ENTER, "global", arg[0]);
		for (n = 0; arg[n]; n++) {
			echoDebug(DBG_ARG, n, arg[n]);
		}
	}

	/* execute pkgremove command */

	n = pkgexecv(NULL, NULL, NULL, NULL, arg);

	/* return results of pkgrm in this zone */

	return (n);
}

static void
usage(void)
{
	char	*prog = get_prog_name();

	(void) fprintf(stderr, ERR_USAGE_PKGRM, prog, prog);
	exit(1);
}

/*
 * Name:	remove_packages_in_global_with_zones
 * Description:	Remove packages from the global zone and from non-global zones
 *		when run from the global zone and when non-global zones are
 *		present.
 * Arguments:	a_pkgList - pointer to array of strings, each string specifying
 *			the name of one package to be removed.
 *		a_nodelete: should the files and scripts remain installed?
 *			- if != 0 pass -F flag to pkgremove - suppress
 *			the removal of any files and any class action scripts
 *			and suppress the running of any class action scripts.
 *			The package files remain but the package looks like it
 *			is not installed. This is mainly for use by upgrade.
 *			- if == 0 do not pass -F flag to pkgremove - all
 *			files and class action scripts are removed, and any
 *			appropriate class action scripts are run.
 *		a_longestPkg - length of the longest package "name" (for
 *			output format alignment)
 *		a_repeat - are there more packages avialable in "optind"
 *			- B_TRUE - process packages from optind
 *			- B_FALSE - do not process packages from optind
 *		a_altBinDir - pointer to string representing location of the
 *			pkgremove executable to run. If not NULL, then pass
 *			the path specified to the -b option to pkgremove.
 *		a_pkgdir - pointer to string representing the directory
 *			where the packages to be removed are located.
 *		a_zlst - list of zones to process; NULL if no zones to process.
 * Returns:	int	(see ckreturn() function for details)
 *		0 - success
 *		1 - package operation failed (fatal error)
 *		2 - non-fatal error (warning)
 *		3 - user selected quit (operation interrupted)
 *		4 - admin settings prevented operation
 *		5 - interaction required and -n (non-interactive) specified
 *		"10" will be added to indicate "immediate reboot required"
 *		"20" will be added to indicate "reboot after install required"
 */

static boolean_t
remove_packages_in_global_with_zones(char **a_pkgList, int a_nodelete,
	int a_longestPkg, int a_repeat, char *a_altBinDir, char *a_pkgdir,
	zoneList_t a_zlst)
{
static	char		*zoneAdminFile = (char *)NULL;

	boolean_t	b;
	char		**inheritedPkgDirs;
	char		*zoneName;
	char		*scratchName;
	char		preremovecheckPath[PATH_MAX+1];
	int		i;
	int		n;
	int		savenpkgs = npkgs;
	int		zoneIndex;
	int		zonesSkipped;
	zone_state_t	zst;

	/* entry assertions */

	assert(a_zlst != (zoneList_t)NULL);
	assert(a_pkgList != (char **)NULL);
	assert(a_longestPkg > 0);
	assert(a_pkgdir != (char *)NULL);
	assert(*a_pkgdir != '\0');

	/* entry debugging info */

	echoDebug(DBG_PKGREMPKGSGZWNGZ_ENTRY);
	echoDebug(DBG_PKGREMPKGSGZWNGZ_ARGS, a_nodelete, a_longestPkg,
		a_repeat, PSTR(a_altBinDir), PSTR(a_pkgdir));

	/* check all packages */

	if (check_packages(a_pkgList, a_pkgdir) != B_TRUE) {
		quit(1);
	}

	/* create temporary directory for use by zone operations */

	create_zone_tempdir(&zoneTempDir, tmpdir);

	/* create hands off settings admin file for use in a non-global zone */

	create_zone_adminfile(&zoneAdminFile, zoneTempDir, admnfile);

	/*
	 * all of the packages (as listed in the package list) are
	 * removed one at a time from all non-global zones and then
	 * from the global zone.
	 */

	for (i = 0; (pkginst = a_pkgList[i]) != NULL; i++) {
		/* reset interrupted flag before calling pkgremove */

		interrupted = 0;	/* last action was NOT quit */

		/* skip package if it is "in the global zone only" */

		if (pkgIsPkgInGzOnly(get_inst_root(), pkginst) == B_TRUE) {
			continue;
		}

		/*
		 * if operation failed in global zone do not propagate to
		 * non-global zones
		 */

		zonesSkipped = 0;

		if (interrupted != 0) {
			echo(MSG_DOREMOVE_INTERRUPTED, pkginst);
			echoDebug(DBG_DOREMOVE_INTERRUPTED, pkginst);
			break;
		}

		echoDebug(DBG_REMOVE_FLAG_VALUES, "before loop",
			admnflag, doreboot, failflag, interrupted,
			intrflag, ireboot, nullflag, warnflag);

		for (zoneIndex = 0;
			(zoneName = z_zlist_get_zonename(a_zlst, zoneIndex)) !=
				(char *)NULL; zoneIndex++) {

			/* skip the zone if it is NOT running */

			zst = z_zlist_get_current_state(a_zlst, zoneIndex);
			if (zst != ZONE_STATE_RUNNING &&
			    zst != ZONE_STATE_MOUNTED) {
				zonesSkipped++;
				echoDebug(DBG_SKIPPING_ZONE, zoneName);
				continue;
			}

			echo(MSG_CHECKREMOVE_PKG_IN_ZONE, pkginst, zoneName);
			echoDebug(DBG_CHECKREMOVE_PKG_IN_ZONE, pkginst,
				zoneName);

			scratchName = z_zlist_get_scratch(a_zlst, zoneIndex);

			(void) snprintf(preremovecheckPath,
				sizeof (preremovecheckPath),
				"%s/%s.%s.preremovecheck.txt",
				zoneTempDir, pkginst, scratchName);

			/* determine list of dirs inherited from global zone */

			inheritedPkgDirs =
				z_zlist_get_inherited_pkg_dirs(a_zlst,
					zoneIndex);

			/*
			 * dependency check this package this zone; use the
			 * user supplied admin file so that the appropriate
			 * level of dependency checking is (or is not) done.
			 */

			n = pkgZoneCheckRemove(scratchName, inheritedPkgDirs,
				a_altBinDir, admnfile, preremovecheckPath,
				zst, B_FALSE);

			/* set success/fail condition variables */

			ckreturn(n);

			echoDebug(DBG_REMOVE_FLAG_VALUES,
				"after pkgzonecheckremove",
				admnflag, doreboot, failflag, interrupted,
				intrflag, ireboot, nullflag, warnflag);
		}

		if (zonesSkipped == 0) {
			continue;
		}

		echoDebug(DBG_ZONES_SKIPPED, zonesSkipped);

		for (zoneIndex = 0;
			(zoneName = z_zlist_get_zonename(a_zlst, zoneIndex)) !=
				(char *)NULL; zoneIndex++) {

			/* skip the zone if it IS running */

			zst = z_zlist_get_current_state(a_zlst, zoneIndex);
			if (zst == ZONE_STATE_RUNNING ||
			    zst == ZONE_STATE_MOUNTED) {
				zonesSkipped++;
				echoDebug(DBG_SKIPPING_ZONE_BOOT, zoneName);
				continue;
			}

			/* skip the zone if it is NOT bootable */

			if (z_zlist_is_zone_runnable(a_zlst,
						zoneIndex) == B_FALSE) {
				echo(MSG_SKIPPING_ZONE_NOT_RUNNABLE, zoneName);
				echoDebug(DBG_SKIPPING_ZONE_NOT_RUNNABLE,
					zoneName);
				continue;
			}

			/* mount up the zone */

			echo(MSG_BOOTING_ZONE, zoneName);
			echoDebug(DBG_BOOTING_ZONE, zoneName);

			b = z_zlist_change_zone_state(a_zlst, zoneIndex,
				ZONE_STATE_MOUNTED);
			if (b == B_FALSE) {
				progerr(ERR_CANNOT_BOOT_ZONE, zoneName);
				/* set fatal error return condition */
				ckreturn(1);
				continue;
			}

			echo(MSG_CHECKREMOVE_PKG_IN_ZONE, pkginst, zoneName);
			echoDebug(DBG_CHECKREMOVE_PKG_IN_ZONE, pkginst,
					zoneName);

			scratchName = z_zlist_get_scratch(a_zlst, zoneIndex);

			(void) snprintf(preremovecheckPath,
				sizeof (preremovecheckPath),
				"%s/%s.%s.preremovecheck.txt",
				zoneTempDir, pkginst, scratchName);

			/* determine list of dirs inherited from global zone */

			inheritedPkgDirs =
				z_zlist_get_inherited_pkg_dirs(a_zlst,
					zoneIndex);

			/*
			 * dependency check this package this zone; use the
			 * user supplied admin file so that the appropriate
			 * level of dependency checking is (or is not) done.
			 */

			n = pkgZoneCheckRemove(scratchName, inheritedPkgDirs,
				a_altBinDir, admnfile, preremovecheckPath,
				ZONE_STATE_MOUNTED, B_TRUE);

			/* set success/fail condition variables */

			ckreturn(n);

			echoDebug(DBG_REMOVE_FLAG_VALUES,
				"after pkgzonecheckremove",
				admnflag, doreboot, failflag, interrupted,
				intrflag, ireboot, nullflag, warnflag);

			/* restore original state of zone */

			echo(MSG_RESTORE_ZONE_STATE, zoneName);
			echoDebug(DBG_RESTORE_ZONE_STATE, zoneName);

			b = z_zlist_restore_zone_state(a_zlst, zoneIndex);
		}
		npkgs--;
	}

	/*
	 * look at all pre-remove check files
	 */

	i = preremove_verify(a_pkgList, a_zlst, zoneTempDir);
	if (i != 0) {
		quit(i);
	}

	npkgs = savenpkgs;

	/*
	 * reset all error return condition variables that may have been
	 * set during package removal dependency checking so that they
	 * do not reflect on the success/failure of the actual package
	 * removal operations
	 */

	resetreturn();

	/*
	 * all of the packages (as listed in the package list) are
	 * removed one at a time.
	 */

	interrupted = 0;
	for (i = 0; (pkginst = a_pkgList[i]) != NULL; i++) {
		boolean_t	in_gz_only;
		started = 0;

		if (shall_we_continue(pkginst, npkgs) == B_FALSE) {
			continue;
		}

		in_gz_only = pkgIsPkgInGzOnly(get_inst_root(), pkginst);

		/* reset interrupted flag before calling pkgremove */

		interrupted = 0;

		/*
		 * pkgrm invoked from within the global zone and there are
		 * non-global zones configured:
		 * Remove the package from the global zone.
		 * If not removing the package from the global zone only,
		 * then remove the package from the list of zones specified.
		 */

		if (in_gz_only) {
			/* global zone only */
			n = doRemove(a_nodelete, a_altBinDir, a_longestPkg,
				admnfile, (char *)NULL, (zoneList_t)NULL);
		} else {
			/* global zone and non-global zones */
			n = doRemove(a_nodelete, a_altBinDir, a_longestPkg,
				zoneAdminFile, zoneAdminFile, a_zlst);
		}

		/* set success/fail condition variables */

		ckreturn(n);

		npkgs--;
	}

	/*
	 * all packages in the package list have been removed.
	 * Continue with removal if:
	 * -- immediate reboot is NOT required
	 * -- there are more packages to remove
	 * else return do NOT continue.
	 */

	if ((ireboot == 0) && (a_repeat != 0)) {
		return (B_TRUE);
	}

	/* return 'dont continue' */

	return (B_FALSE);
}

/*
 * Name:	remove_packages_in_nonglobal_zone
 * Description:	Remove packages in a non-global zone when run from a
 *		non-global zone.
 * Arguments:	a_pkgList - pointer to array of strings, each string specifying
 *			the name of one package to be removed.
 *		a_nodelete: should the files and scripts remain installed?
 *			- if != 0 pass -F flag to pkgremove - suppress
 *			the removal of any files and any class action scripts
 *			and suppress the running of any class action scripts.
 *			The package files remain but the package looks like it
 *			is not installed. This is mainly for use by upgrade.
 *			- if == 0 do not pass -F flag to pkgremove - all
 *			files and class action scripts are removed, and any
 *			appropriate class action scripts are run.
 *		a_longestPkg - length of the longest package "name" (for
 *			output format alignment)
 *		a_repeat - are there more packages avialable in "optind"
 *			- B_TRUE - process packages from optind
 *			- B_FALSE - do not process packages from optind
 *		a_altBinDir - pointer to string representing location of the
 *			pkgremove executable to run. If not NULL, then pass
 *			the path specified to the -b option to pkgremove.
 *		a_pkgdir - pointer to string representing the directory
 *			where the packages to be removed are located.
 * Returns:	int	(see ckreturn() function for details)
 *		0 - success
 *		1 - package operation failed (fatal error)
 *		2 - non-fatal error (warning)
 *		3 - user selected quit (operation interrupted)
 *		4 - admin settings prevented operation
 *		5 - interaction required and -n (non-interactive) specified
 *		"10" will be added to indicate "immediate reboot required"
 *		"20" will be added to indicate "reboot after install required"
 */

static boolean_t
remove_packages_in_nonglobal_zone(char **a_pkgList, int a_nodelete,
	int a_longestPkg, int a_repeat, char *a_altBinDir, char *a_pkgdir)
{
static	char		*zoneAdminFile = (char *)NULL;

	int		n;
	int		i;

	/* entry assertions */

	assert(a_pkgList != (char **)NULL);
	assert(a_longestPkg > 0);
	assert(a_pkgdir != (char *)NULL);
	assert(*a_pkgdir != '\0');

	/* entry debugging info */

	echoDebug(DBG_PKGREMPKGSNGZ_ENTRY);
	echoDebug(DBG_PKGREMPKGSNGZ_ARGS, a_nodelete, a_longestPkg,
		a_repeat, PSTR(a_altBinDir), PSTR(a_pkgdir));

	/* check all package */

	if (check_packages(a_pkgList, a_pkgdir) != B_TRUE) {
		quit(1);
	}

	/* create temporary directory for use by zone operations */

	create_zone_tempdir(&zoneTempDir, tmpdir);

	/* create hands off settings admin file for use in a non-global zone */

	create_zone_adminfile(&zoneAdminFile, zoneTempDir, admnfile);

	/*
	 * all of the packages (as listed in the package list) are
	 * removed one at a time.
	 */

	interrupted = 0;
	for (i = 0; (pkginst = a_pkgList[i]) != NULL; i++) {
		started = 0;

		if (shall_we_continue(pkginst, npkgs) == B_FALSE) {
			continue;
		}

		interrupted = 0;

		/*
		 * pkgrm invoked from within a non-global zone: remove
		 * the package from the current zone only - no non-global
		 * zones are possible.
		 */

		n = doRemove(a_nodelete, a_altBinDir, a_longestPkg,
			admnfile, (char *)NULL, (zoneList_t)NULL);

		/* set success/fail condition variables */

		ckreturn(n);

		npkgs--;
	}

	/*
	 * all packages in the package list have been removed.
	 * Continue with removal if:
	 * -- immediate reboot is NOT required
	 * -- there are more packages to remove
	 * else return do NOT continue.
	 */

	if ((ireboot == 0) && (a_repeat != 0)) {
		return (B_TRUE);
	}

	/* return 'dont continue' */

	return (B_FALSE);
}

/*
 * Name:	remove_packages_in_global_no_zones
 * Description:	Remove packages from the global zone only when run in the
 *		global zone and no non-global zones are installed.
 * Arguments:	a_pkgList - pointer to array of strings, each string specifying
 *			the name of one package to be removed.
 *		a_nodelete: should the files and scripts remain installed?
 *			- if != 0 pass -F flag to pkgremove - suppress
 *			the removal of any files and any class action scripts
 *			and suppress the running of any class action scripts.
 *			The package files remain but the package looks like it
 *			is not installed. This is mainly for use by upgrade.
 *			- if == 0 do not pass -F flag to pkgremove - all
 *			files and class action scripts are removed, and any
 *			appropriate class action scripts are run.
 *		a_longestPkg - length of the longest package "name" (for
 *			output format alignment)
 *		a_repeat - are there more packages avialable in "optind"
 *			- B_TRUE - process packages from optind
 *			- B_FALSE - do not process packages from optind
 *		a_altBinDir - pointer to string representing location of the
 *			pkgremove executable to run. If not NULL, then pass
 *			the path specified to the -b option to pkgremove.
 * Returns:	int	(see ckreturn() function for details)
 *		0 - success
 *		1 - package operation failed (fatal error)
 *		2 - non-fatal error (warning)
 *		3 - user selected quit (operation interrupted)
 *		4 - admin settings prevented operation
 *		5 - interaction required and -n (non-interactive) specified
 *		"10" will be added to indicate "immediate reboot required"
 *		"20" will be added to indicate "reboot after install required"
 */

static boolean_t
remove_packages_in_global_no_zones(char **a_pkgList, int a_nodelete,
	int a_longestPkg, int a_repeat, char *a_altBinDir)
{
	int	n;
	int	i;

	/* entry assertions */

	assert(a_pkgList != (char **)NULL);
	assert(a_longestPkg > 0);

	/* entry debugging info */

	echoDebug(DBG_PKGREMPKGSGZNNGZ_ENTRY);
	echoDebug(DBG_PKGREMPKGSGZNNGZ_ARGS, a_nodelete, a_longestPkg,
		a_repeat, PSTR(a_altBinDir));

	/*
	 * all of the packages (as listed in the package list) are
	 * removed one at a time.
	 */

	interrupted = 0;
	for (i = 0; (pkginst = a_pkgList[i]) != NULL; i++) {
		started = 0;

		if (shall_we_continue(pkginst, npkgs) == B_FALSE) {
			continue;
		}

		interrupted = 0;

		/*
		 * pkgrm invoked from within the global zone and there are
		 * NO non-global zones configured:
		 * Remove the package from the global zone only.
		 */

		n = doRemove(a_nodelete, a_altBinDir, a_longestPkg,
				admnfile, (char *)NULL, (zoneList_t)NULL);

		/* set success/fail condition variables */

		ckreturn(n);

		npkgs--;
	}

	/*
	 * all packages in the package list have been removed.
	 * Continue with removal if:
	 * -- immediate reboot is NOT required
	 * -- there are more packages to remove
	 * else return do NOT continue.
	 */

	if ((ireboot == 0) && (a_repeat != 0)) {
		return (B_TRUE);
	}

	/* return 'dont continue' */

	return (B_FALSE);
}

/*
 * Name:	remove_packages_from_spool_directory
 * Description:	Remove packages from a spool directory only.
 * Arguments:	a_pkgList - pointer to array of strings, each string specifying
 *			the name of one package to be removed.
 *		a_nodelete: should the files and scripts remain installed?
 *			- if != 0 pass -F flag to pkgremove - suppress
 *			the removal of any files and any class action scripts
 *			and suppress the running of any class action scripts.
 *			The package files remain but the package looks like it
 *			is not installed. This is mainly for use by upgrade.
 *			- if == 0 do not pass -F flag to pkgremove - all
 *			files and class action scripts are removed, and any
 *			appropriate class action scripts are run.
 *		a_longestPkg - length of the longest package "name" (for
 *			output format alignment)
 *		a_repeat - are there more packages avialable in "optind"
 *			- B_TRUE - process packages from optind
 *			- B_FALSE - do not process packages from optind
 *		a_altBinDir - pointer to string representing location of the
 *			pkgremove executable to run. If not NULL, then pass
 *			the path specified to the -b option to pkgremove.
 * Returns:	int	(see ckreturn() function for details)
 *		0 - success
 *		1 - package operation failed (fatal error)
 *		2 - non-fatal error (warning)
 *		3 - user selected quit (operation interrupted)
 *		4 - admin settings prevented operation
 *		5 - interaction required and -n (non-interactive) specified
 *		"10" will be added to indicate "immediate reboot required"
 *		"20" will be added to indicate "reboot after install required"
 */

static boolean_t
remove_packages_from_spool_directory(char **a_pkgList, int a_nodelete,
	int a_longestPkg, int a_repeat, char *a_altBinDir)
{
	int	n;
	int	i;

	/* entry assertions */

	assert(a_pkgList != (char **)NULL);
	assert(a_longestPkg > 0);

	/*
	 * all of the packages (as listed in the package list) are
	 * removed one at a time.
	 */

	interrupted = 0;
	for (i = 0; (pkginst = a_pkgList[i]) != NULL; i++) {
		started = 0;

		if (shall_we_continue(pkginst, npkgs) == B_FALSE) {
			continue;
		}

		interrupted = 0;

		/*
		 * pkgrm invoked from any type of zone BUT the target
		 * to be removed is a local spool directory: remove the
		 * packages from the spool directory only.
		 */

		n = doRemove(a_nodelete, a_altBinDir, a_longestPkg,
			admnfile, (char *)NULL, (zoneList_t)NULL);

		/* set success/fail condition variables */

		ckreturn(n);

		npkgs--;
	}

	/*
	 * all packages in the package list have been removed.
	 * Continue with removal if:
	 * -- immediate reboot is NOT required
	 * -- there are more packages to remove
	 * else return do NOT continue.
	 */

	if ((ireboot == 0) && (a_repeat != 0)) {
		return (B_TRUE);
	}

	/* return 'dont continue' */

	return (B_FALSE);
}

/*
 * Name:	remove_packages
 * Description:	Remove packages from the global zone, and optionally from one
 *		or more non-global zones, or from a specified spool directory.
 * Arguments:	a_pkgList - pointer to array of strings, each string specifying
 *			the name of one package to be removed.
 *		a_nodelete: should the files and scripts remain installed?
 *			- if != 0 pass -F flag to pkgremove - suppress
 *			the removal of any files and any class action scripts
 *			and suppress the running of any class action scripts.
 *			The package files remain but the package looks like it
 *			is not installed. This is mainly for use by upgrade.
 *			- if == 0 do not pass -F flag to pkgremove - all
 *			files and class action scripts are removed, and any
 *			appropriate class action scripts are run.
 *		a_longestPkg - length of the longest package "name" (for
 *			output format alignment)
 *		a_repeat - are there more packages avialable in "optind"
 *			- B_TRUE - process packages from optind
 *			- B_FALSE - do not process packages from optind
 *		a_altBinDir - pointer to string representing location of the
 *			pkgremove executable to run. If not NULL, then pass
 *			the path specified to the -b option to pkgremove.
 *		a_pkgdir - pointer to string representing the directory
 *			where the packages to be removed are located.
 *		a_spoolDir - pointer to string specifying spool directory
 *			to remove packages from. If != NULL then all zones
 *			processing is bypassed and the packages are removed
 *			from the specified spool directory only.
 *		a_noZones - if non-global zones are configured, should the
 *			packages be removed from the non-global zones?
 *			- B_TRUE - do NOT remove packages from non-global zones
 *			- B_FALSE - remove packages from non-global zones
 * Returns:	int	(see ckreturn() function for details)
 *		0 - success
 *		1 - package operation failed (fatal error)
 *		2 - non-fatal error (warning)
 *		3 - user selected quit (operation interrupted)
 *		4 - admin settings prevented operation
 *		5 - interaction required and -n (non-interactive) specified
 *		"10" will be added to indicate "immediate reboot required"
 *		"20" will be added to indicate "reboot after install required"
 */

static boolean_t
remove_packages(char **a_pkgList, int a_nodelete, int a_longestPkg,
	int a_repeat, char *a_altBinDir, char *a_pkgdir, char *a_spoolDir,
	boolean_t a_noZones)
{
	zoneList_t	zlst;
	boolean_t	b;

	/* entry assertions */

	assert(a_pkgList != (char **)NULL);

	echoDebug(DBG_REMOVEPKGS_ENTRY);
	echoDebug(DBG_REMOVEPKGS_ARGS, npkgs, a_nodelete, a_longestPkg,
		a_repeat, PSTR(a_pkgdir), PSTR(a_spoolDir));

	/*
	 * if removing from spool directory, bypass all zones checks
	 */

	if (a_spoolDir != (char *)NULL) {
		/* in non-global zone */

		echoDebug(DBG_REMOVE_PKGS_FROM_SPOOL, a_spoolDir);

		b = remove_packages_from_spool_directory(a_pkgList, a_nodelete,
			a_longestPkg, a_repeat, a_altBinDir);

		return (B_FALSE);
	}

	/* exit if not root */

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

	/*
	 * if running in the global zone AND one or more non-global
	 * zones exist, add packages in a 'zones aware' manner, else
	 * add packages in the standard 'non-zones aware' manner.
	 */

	if ((a_noZones == B_FALSE) && (z_running_in_global_zone() == B_FALSE)) {
		/* in non-global zone */

		echoDebug(DBG_IN_LZ);

		b = z_lock_this_zone(ZLOCKS_PKG_ADMIN);
		if (b != B_TRUE) {
			progerr(ERR_CANNOT_LOCK_THIS_ZONE);
			/* set fatal error return condition */
			ckreturn(1);
			return (B_FALSE);
		}

		b = remove_packages_in_nonglobal_zone(a_pkgList, a_nodelete,
			a_longestPkg, a_repeat, a_altBinDir, a_pkgdir);

		(void) z_unlock_this_zone(ZLOCKS_ALL);

		return (B_FALSE);
	}

	/* running in the global zone */

	b = z_non_global_zones_exist();
	if ((a_noZones == B_FALSE) && (b == B_TRUE)) {

		echoDebug(DBG_IN_GZ_WITH_LZ);

		/* get a list of all non-global zones */
		zlst = z_get_nonglobal_zone_list();
		if (zlst == (zoneList_t)NULL) {
			progerr(ERR_CANNOT_GET_ZONE_LIST);
			quit(1);
		}

		/* need to lock all of the zones */

		quitSetZonelist(zlst);
		b = z_lock_zones(zlst, ZLOCKS_PKG_ADMIN);
		if (b == B_FALSE) {
			z_free_zone_list(zlst);
			progerr(ERR_CANNOT_LOCK_ZONES);
			/* set fatal error return condition */
			ckreturn(1);
			return (B_FALSE);
		}

		/* add packages to all zones */

		b = remove_packages_in_global_with_zones(a_pkgList, a_nodelete,
			a_longestPkg, a_repeat, a_altBinDir, a_pkgdir, zlst);

		/* unlock all zones */

		(void) z_unlock_zones(zlst, ZLOCKS_ALL);
		quitSetZonelist((zoneList_t)NULL);

		/* free list of all non-global zones */

		z_free_zone_list(zlst);

		return (B_FALSE);
	}

	/* in global zone no non-global zones */

	echoDebug(DBG_IN_GZ_NO_LZ);

	b = z_lock_this_zone(ZLOCKS_PKG_ADMIN);
	if (b != B_TRUE) {
		progerr(ERR_CANNOT_LOCK_THIS_ZONE);
		/* set fatal error return condition */
		ckreturn(1);
		return (B_FALSE);
	}

	b = remove_packages_in_global_no_zones(a_pkgList, a_nodelete,
			a_longestPkg, a_repeat, a_altBinDir);

	(void) z_unlock_this_zone(ZLOCKS_ALL);

	return (B_FALSE);
}

/*
 * Name:		path_valid
 * Description:	Checks a string for being a valid path
 *
 * Arguments:	path - path to validate
 *
 * Returns :	B_TRUE - success, B_FALSE otherwise.
 *		B_FALSE means path was null, too long (>PATH_MAX),
 *		or too short (<1)
 */
static boolean_t
path_valid(char *path)
{
	if (path == NULL) {
		return (B_FALSE);
	} else if (strlen(path) > PATH_MAX) {
		return (B_FALSE);
	} else if (strlen(path) >= 1) {
		return (B_TRUE);
	} else {
		/* path < 1 */
		return (B_FALSE);
	}
}

/*
 */

static boolean_t
check_packages(char **a_pkgList, char *a_packageDir)
{
	int	savenpkgs = npkgs;
	int	i;
	CAF_T	flags = 0;

	/* set flags for applicability check */

	if (z_running_in_global_zone() == B_TRUE) {
		flags |= CAF_IN_GLOBAL_ZONE;
	}

	/*
	 * for each package to remove, verify that the package is installed
	 * and is removable.
	 */

	for (i = 0; (pkginst = a_pkgList[i]) != NULL; i++) {
		/* check package applicability */
		if (check_applicability(a_packageDir, pkginst, get_inst_root(),
			flags) == B_FALSE) {
			progerr(ERR_PKG_NOT_REMOVABLE, pkginst);
			npkgs = savenpkgs;
			return (B_FALSE);
		}
		npkgs--;
	}

	npkgs = savenpkgs;
	return (B_TRUE);
}

/*
 * - is this package removable from this zone?
 * - does the scope of remove conflict with existing installation
 */

static boolean_t
check_applicability(char *a_packageDir, char *a_pkgInst,
	char *a_rootPath, CAF_T a_flags)
{
	FILE		*pkginfoFP;
	boolean_t	all_zones;	/* pkg is "all zones" only */
	char		pkginfoPath[PATH_MAX];
	char		pkgpath[PATH_MAX];
	int		len;

	/* entry assertions */

	assert(a_packageDir != (char *)NULL);
	assert(*a_packageDir != '\0');
	assert(a_pkgInst != (char *)NULL);
	assert(*a_pkgInst != '\0');

	/* normalize root path */

	if (a_rootPath == (char *)NULL) {
		a_rootPath = "";
	}

	/*
	 * determine if this package is currently installed
	 * if not installed return success - operation will fail
	 * when the removal is attempted
	 */

	if (pkginfoIsPkgInstalled((struct pkginfo **)NULL, a_pkgInst) !=
		B_TRUE) {
		return (B_TRUE);
	}

	/*
	 * calculate paths to various objects
	 */

	len = snprintf(pkgpath, sizeof (pkgpath), "%s/%s", a_packageDir,
			a_pkgInst);
	if (len > sizeof (pkgpath)) {
		progerr(ERR_CREATE_PATH_2, a_packageDir, a_pkgInst);
		return (B_FALSE);
	}

	/* if not installed then just return */

	if (isdir(pkgpath) != 0) {
		progerr(ERR_NO_PKGDIR, pkgpath, a_pkgInst, strerror(errno));
		return (B_TRUE);
	}

	len = snprintf(pkginfoPath, sizeof (pkginfoPath),
			"%s/pkginfo", pkgpath);
	if (len > sizeof (pkgpath)) {
		progerr(ERR_CREATE_PATH_2, pkgpath, "pkginfo");
		return (B_FALSE);
	}

	/*
	 * gather information from this packages pkginfo file
	 */

	pkginfoFP = fopen(pkginfoPath, "r");

	if (pkginfoFP == (FILE *)NULL) {
		progerr(ERR_NO_PKG_INFOFILE, a_pkgInst, pkginfoPath,
							strerror(errno));
		return (B_FALSE);
	}

	/* determine "ALLZONES" setting for this package */

	all_zones = pkginfoParamTruth(pkginfoFP, PKG_ALLZONES_VARIABLE,
			"true", B_FALSE);

	/* close pkginfo file */

	(void) fclose(pkginfoFP);

	/* gather information from the global zone only file */

	/*
	 * verify package applicability based on information gathered;
	 * the package IS currently installed....
	 */

	/* pkg ALLZONES=true & not running in global zone */

	if ((all_zones == B_TRUE) && (!(a_flags & CAF_IN_GLOBAL_ZONE))) {
		progerr(ERR_ALLZONES_AND_IN_LZ_PKGRM, a_pkgInst);
		return (B_FALSE);
	}

	return (B_TRUE);
}

/*
 * Name:	shall_we_continue
 * Description: Called from within a loop that is installing packages,
 *		this function examines various global variables and decides
 *		whether or not to ask an appropriate question, and wait for
 *		and appropriate reply.
 * Arguments:	<<global variables>>
 * Returns:	B_TRUE - continue processing with next package
 *		B_FALSE - do not continue processing with next package
 */

static boolean_t
shall_we_continue(char *a_pkgInst, int a_npkgs)
{
	char	ans[MAX_INPUT];
	int	n;

	/* return FALSE if immediate reboot required */

	if (ireboot) {
		ptext(stderr, MSG_SUSPEND_RM, a_pkgInst);
		return (B_FALSE);
	}

	/* return TRUE if not interrupted */

	if (!interrupted) {
		return (B_TRUE);
	}

	/* output appropriate interrupt message */

	echo(a_npkgs == 1 ? MSG_1MORETODO : MSG_MORETODO, a_npkgs);

	/* if running with no interaction (-n) do not ask question */

	if (nointeract) {
		quit(0);
		/* NOTREACHED */
	}

	/* interaction possible: ask question */

	n = ckyorn(ans, NULL, NULL, NULL, ASK_CONTINUE_RM);
	if (n != 0) {
		quit(n);
		/* NOTREACHED */
	}

	if (strchr("yY", *ans) == NULL) {
		quit(0);
		/* NOTREACHED */
	}
	return (B_TRUE);
}

/*
 * Name:	create_zone_adminfile
 * Description: Given a zone temporary directory and optionally an existing
 *		administration file, generate an administration file that
 *		can be used to perform "non-interactive" operations in a
 *		non-global zone.
 * Arguments:	r_zoneAdminFile - pointer to handle that will contain a
 *			string representing the path to the temporary
 *			administration file created - this must be NULL
 *			before the first call to this function - on
 *			subsequent calls if the pointer is NOT null then
 *			the existing string will NOT be overwritten.
 *		a_zoneTempDir - pointer to string representing the path
 *			to the zone temporary directory to create the
 *			temporary administration file in
 *		a_admnfile - pointer to string representing the path to
 *			an existing "user" administration file - the
 *			administration file created will contain the
 *			settings contained in this file, modified as
 *			appropriate to supress any interaction;
 *			If this is == NULL then the administration file
 *			created will not contain any extra settings
 * Returns:	void
 * NOTE:	Any string returned is placed in new storage for the
 *		calling method. The caller must use 'free' to dispose
 *		of the storage once the string is no longer needed.
 * NOTE:	On any error this function will call 'quit(1)'
 */

static void
create_zone_adminfile(char **r_zoneAdminFile, char *a_zoneTempDir,
	char *a_admnfile)
{
	boolean_t	b;

	/* entry assertions */

	assert(r_zoneAdminFile != (char **)NULL);
	assert(a_zoneTempDir != (char *)NULL);
	assert(*a_zoneTempDir != '\0');

	/* entry debugging info */

	echoDebug(DBG_CREATE_ZONE_ADMINFILE, a_zoneTempDir, PSTR(a_admnfile));

	/* if temporary name already exists, do not overwrite */

	if (*r_zoneAdminFile != (char *)NULL) {
		return;
	}

	/* create temporary name */

	*r_zoneAdminFile = tempnam(a_zoneTempDir, "zadmn");
	b = z_create_zone_admin_file(*r_zoneAdminFile, a_admnfile);
	if (b == B_FALSE) {
		progerr(ERR_CREATE_TMPADMIN, *r_zoneAdminFile,
			strerror(errno));
		quit(1);
		/* NOTREACHED */
	}

	echoDebug(DBG_CREATED_ZONE_ADMINFILE, *r_zoneAdminFile);
}

/*
 * Name:	create_zone_tempdir
 * Description: Given a system temporary directory, create a "zone" specific
 *		temporary directory and return the path to the directory
 *		created.
 * Arguments:	r_zoneTempDir - pointer to handle that will contain a
 *			string representing the path to the temporary
 *			directory created - this must be NULL before the
 *			first call to this function - on subsequent calls
 *			if the pointer is NOT null then the existing string
 *			will NOT be overwritten.
 *		a_zoneTempDir - pointer to string representing the path
 *			to the system temporary directory to create the
 *			temporary zone directory in
 * Returns:	void
 * NOTE:	Any string returned is placed in new storage for the
 *		calling method. The caller must use 'free' to dispose
 *		of the storage once the string is no longer needed.
 * NOTE:	On any error this function will call 'quit(1)'
 * NOTE:	This function calls "quitSetZoneTmpdir" on success to
 *		register the directory created with quit() so that the
 *		directory will be automatically deleted on exit.
 */

static void
create_zone_tempdir(char **r_zoneTempDir, char *a_tmpdir)
{
	boolean_t	b;

	/* entry assertions */

	assert(r_zoneTempDir != (char **)NULL);
	assert(a_tmpdir != (char *)NULL);
	assert(*a_tmpdir != '\0');

	/* entry debugging info */

	echoDebug(DBG_CREATE_ZONE_TEMPDIR, a_tmpdir);

	/* if temporary directory already exists, do not overwrite */

	if (*r_zoneTempDir != (char *)NULL) {
		return;
	}

	/* create temporary directory */

	b = setup_temporary_directory(r_zoneTempDir, a_tmpdir, "ztemp");
	if (b == B_FALSE) {
		progerr(ERR_ZONETEMPDIR, a_tmpdir, strerror(errno));
		quit(1);
		/* NOTREACHED */
	}

	/* register with quit() to directory is removed on exit */

	quitSetZoneTmpdir(*r_zoneTempDir);

	/* exit debugging info */

	echoDebug(DBG_CREATED_ZONE_TEMPDIR, *r_zoneTempDir);
}