OpenSolaris_b135/lib/brand/native/zone/sw_support.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.
 */

/*
 * sw_support does install, detach and attach processing for svr4 pkgs.
 */

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <stdarg.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <wait.h>
#include <zone.h>
#include <locale.h>
#include <libintl.h>
#include <libzonecfg.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <limits.h>
#include <stddef.h>
#include <dirent.h>

#include <libxml/xmlmemory.h>
#include <libxml/parser.h>

#include <fcntl.h>
#include <door.h>
#include <macros.h>
#include <libgen.h>
#include <fnmatch.h>
#include <strings.h>

#include <libzonecfg.h>

#define	ZONE_SUBPROC_OK			0
#define	ZONE_SUBPROC_USAGE		253
#define	ZONE_SUBPROC_NOTCOMPLETE	254
#define	ZONE_SUBPROC_FATAL		255

#define	Z_ERR		1
#define	Z_USAGE		2
#define	Z_FATAL		3

#define	SW_CMP_NONE	0x0
#define	SW_CMP_SRC	0x01
#define	SW_CMP_SILENT	0x02

#define	TMP_MANIFEST	"SUNWupdate.xml"
#define	PKG_PATH	"/var/sadm/pkg"
#define	CONTENTS_FILE	"/var/sadm/install/contents"
#define	SUNW_PKG_ALL_ZONES	"SUNW_PKG_ALLZONES=true\n"
#define	SUNW_PKG_THIS_ZONE	"SUNW_PKG_THISZONE=true\n"
#define	VERSION		"VERSION="
#define	PATCHLIST	"PATCHLIST="
#define	PATCHINFO	"PATCH_INFO_"
#define	PKGINFO_RD_LEN	128
#define	MY_BRAND_NAME	"native"

#define	EXEC_PREFIX	"exec "
#define	EXEC_LEN	(strlen(EXEC_PREFIX))
#define	RMCOMMAND	"/usr/bin/rm -rf"

/* 0755 is the default directory mode. */
#define	DEFAULT_DIR_MODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)

/* These are the file status indicators for the contents file */
#define	INST_RDY	'+'	/* entry is ready to installf -f */
#define	RM_RDY		'-'	/* entry is ready for removef -f */
#define	NOT_FND		'!'	/* entry (or part of entry) was not found */
#define	SERVED_FILE	'%'	/* using the file server's RO partition */
#define	STAT_NEXT	'@'	/* this is awaiting eptstat */
#define	DUP_ENTRY	'#'	/* there's a duplicate of this */
#define	CONFIRM_CONT	'*'	/* need to confirm contents */
#define	CONFIRM_ATTR	'~'	/* need to confirm attributes */
#define	ENTRY_OK	'\0'	/* entry is a confirmed file */

enum zn_ipd_fs {ZONE_IPD, ZONE_FS};

struct zone_pkginfo {
	boolean_t	zpi_all_zones;
	boolean_t	zpi_this_zone;
	int		zpi_patch_cnt;
	char		*zpi_version;
	char		*zpi_patchlist;
	char		**zpi_patchinfo;
};

typedef struct {
	uu_avl_node_t	patch_node;
	char		*patch_num;
	char		*patch_vers;
	uu_list_t	*obs_patches;
} patch_node_t;

typedef struct {
	uu_list_node_t	link;
	char		*patch_num;
} obs_patch_node_t;

typedef struct {
	uu_avl_t	*obs_patches_avl;
	zone_dochandle_t handle;
	int		res;
} patch_parms_t;

typedef struct {
	uu_avl_node_t	link;
	char		*patch_num;
	boolean_t	can_backout;
	boolean_t	installed;
} bo_patch_node_t;

static char *locale;
static char *zonename;
static char *zonepath;

/* List of pkgs that should be only on GZ */
static char **gz_only_pkgs_list = NULL;

/* used in attach_func() and signal handler */
static volatile boolean_t attach_interupted;

void
zperror(const char *str, boolean_t zonecfg_error)
{
	(void) fprintf(stderr, "%s: %s\n", str,
	    zonecfg_error ? zonecfg_strerror(errno) : strerror(errno));
}

static int sw_cmp(zone_dochandle_t, zone_dochandle_t, uint_t);

/* PRINTFLIKE1 */
void
zerror(const char *fmt, ...)
{
	va_list alist;

	va_start(alist, fmt);
	(void) fprintf(stderr, "zone '%s': ", zonename);
	(void) vfprintf(stderr, fmt, alist);
	(void) fprintf(stderr, "\n");
	va_end(alist);
}

static int
do_subproc(char *cmdbuf)
{
	void (*saveint)(int);
	void (*saveterm)(int);
	void (*savequit)(int);
	void (*savehup)(int);
	int pid, child, status;

	/*
	 * do_subproc() links stdin to /dev/null, which would break any
	 * interactive subprocess we try to launch here.
	 */
	if ((child = vfork()) == 0) {
		(void) execl("/bin/sh", "sh", "-c", cmdbuf, (char *)NULL);
	}

	if (child == -1)
		return (-1);

	saveint = sigset(SIGINT, SIG_IGN);
	saveterm = sigset(SIGTERM, SIG_IGN);
	savequit = sigset(SIGQUIT, SIG_IGN);
	savehup = sigset(SIGHUP, SIG_IGN);

	while ((pid = waitpid(child, &status, 0)) != child && pid != -1)
		;

	(void) sigset(SIGINT, saveint);
	(void) sigset(SIGTERM, saveterm);
	(void) sigset(SIGQUIT, savequit);
	(void) sigset(SIGHUP, savehup);

	return (pid == -1 ? -1 : status);
}

static int
subproc_status(const char *cmd, int status, boolean_t verbose_failure)
{
	if (WIFEXITED(status)) {
		int exit_code = WEXITSTATUS(status);

		if ((verbose_failure) && (exit_code != ZONE_SUBPROC_OK))
			zerror(gettext("'%s' failed with exit code %d."), cmd,
			    exit_code);

		return (exit_code);
	} else if (WIFSIGNALED(status)) {
		int signal = WTERMSIG(status);
		char sigstr[SIG2STR_MAX];

		if (sig2str(signal, sigstr) == 0) {
			zerror(gettext("'%s' terminated by signal SIG%s."), cmd,
			    sigstr);
		} else {
			zerror(gettext("'%s' terminated by an unknown signal."),
			    cmd);
		}
	} else {
		zerror(gettext("'%s' failed for unknown reasons."), cmd);
	}

	/*
	 * Assume a subprocess that died due to a signal or an unknown error
	 * should be considered an exit code of ZONE_SUBPROC_FATAL, as the
	 * user will likely need to do some manual cleanup.
	 */
	return (ZONE_SUBPROC_FATAL);
}

/*
 * Maintain a space separated list of unique pkg names.  PATH_MAX is used in
 * the pkg code as the maximum size for a pkg name.
 */
static int
add_pkg_to_str(char **str, char *pkg)
{
	int len, newlen;
	char tstr[PATH_MAX + 3];
	char *tmp;

	len = strlen(pkg);
	if (*str == NULL) {
		/* space for str + 2 spaces + NULL */
		if ((*str = (char *)malloc(len + 3)) == NULL)
			return (Z_NOMEM);
		(void) snprintf(*str, len + 3, " %s ", pkg);
		return (Z_OK);
	}

	(void) snprintf(tstr, sizeof (tstr), " %s ", pkg);
	if (strstr(*str, tstr) != NULL)
		return (Z_OK);

	/* space for str + 1 space + NULL */
	newlen = strlen(*str) + len + 2;
	if ((tmp = (char *)realloc(*str, newlen)) == NULL)
		return (Z_NOMEM);
	*str = tmp;
	(void) strlcat(*str, pkg, newlen);
	(void) strlcat(*str, " ", newlen);
	return (Z_OK);
}

/*
 * Process a list of pkgs from an entry in the contents file, adding each pkg
 * name to the list of pkgs.
 *
 * It is possible for the pkg name to be preceeded by a special character
 * which indicates some bookkeeping information for pkging.  Check if the
 * first char is not an Alpha char.  If so, skip over it.
 */
static int
add_pkg_list(char *lastp, char ***plist, int *pcnt, char **pkg_warn)
{
	char	*p;
	int	pkg_cnt = *pcnt;
	char	**pkgs = *plist;
	int	res = Z_OK;

	while ((p = strtok_r(NULL, " ", &lastp)) != NULL) {
		char	**tmpp;
		int	i;

		/* skip over any special pkg bookkeeping char */
		if (!isalpha(*p)) {
			if ((*p == NOT_FND || *p == DUP_ENTRY) &&
			    (res = add_pkg_to_str(pkg_warn, p + 1)) != Z_OK)
				break;
			p++;
		}

		/* Check if the pkg is already in the list */
		for (i = 0; i < pkg_cnt; i++) {
			if (strcmp(p, pkgs[i]) == 0)
				break;
		}

		if (i < pkg_cnt)
			continue;

		/* The pkg is not in the list; add it. */
		if ((tmpp = (char **)realloc(pkgs,
		    sizeof (char *) * (pkg_cnt + 1))) == NULL) {
			res = Z_NOMEM;
			break;
		}
		pkgs = tmpp;

		if ((pkgs[pkg_cnt] = strdup(p)) == NULL) {
			res = Z_NOMEM;
			break;
		}
		pkg_cnt++;
	}

	*plist = pkgs;
	*pcnt = pkg_cnt;

	return (res);
}

/*
 * Process an entry from the contents file (type "directory").  If the
 * directory path is in the list of ipds and is not under a lofs mount within
 * the ipd then add the associated list of pkgs to the pkg list.  The input
 * parameter "entry" will be broken up by the parser within this function so
 * its value will be modified when this function exits.
 *
 * The entries we are looking for will look something like:
 *	/usr d none 0755 root sys SUNWctpls SUNWidnl SUNWlibCf ....
 */
static int
get_path_pkgs(char *entry, char **ipds, char **fss, char ***pkgs, int *pkg_cnt,
    char **pkg_warn)
{
	char	*f1;
	char	*f2;
	char	*lastp;
	int	i;
	char	*nlp;

	if ((f1 = strtok_r(entry, " ", &lastp)) == NULL ||
	    (f2 = strtok_r(NULL, " ", &lastp)) == NULL || strcmp(f2, "d") != 0)
		return (Z_OK);

	/* Check if this directory entry is in the list of ipds. */
	for (i = 0; ipds[i] != NULL; i++) {
		char wildcard[MAXPATHLEN];

		/*
		 * We want to match on the path and any other directory
		 * entries under this path.  When we use FNM_PATHNAME then
		 * that means '/' will not be matched by a wildcard (*) so
		 * we omit FNM_PATHNAME on the call with the wildcard matching.
		 */
		(void) snprintf(wildcard, sizeof (wildcard), "%s/*", ipds[i]);
		if (fnmatch(ipds[i], f1, FNM_PATHNAME) == 0 ||
		    fnmatch(wildcard, f1, 0) == 0) {
			/* It looks like we do want the pkgs for this path. */
			break;
		}
	}

	/* This entry did not match any of the ipds. */
	if (ipds[i] == NULL)
		return (Z_OK);

	/*
	 * Check if there is a fs mounted under the ipd.  If so, ignore this
	 * entry.
	 */
	for (i = 0; fss[i] != NULL; i++) {
		char wildcard[MAXPATHLEN];

		(void) snprintf(wildcard, sizeof (wildcard), "%s/*", fss[i]);
		if (fnmatch(fss[i], f1, FNM_PATHNAME) == 0 ||
		    fnmatch(wildcard, f1, 0) == 0) {
			/* We should ignore this path. */
			break;
		}
	}

	/* If not null, then we matched an fs mount point so ignore entry. */
	if (fss[i] != NULL)
		return (Z_OK);

	/*
	 * We do want the pkgs for this entry.  First, skip over the next 4
	 * fields in the entry so that we call add_pkg_list starting with the
	 * pkg names.
	 */
	for (i = 0; i < 4 && strtok_r(NULL, " ", &lastp) != NULL; i++)
		;
	/* If there are < 4 fields this entry is corrupt, just skip it. */
	if (i < 4)
		return (Z_OK);

	/* strip newline from the line */
	nlp = (lastp + strlen(lastp) - 1);
	if (*nlp == '\n')
		*nlp = '\0';

	return (add_pkg_list(lastp, pkgs, pkg_cnt, pkg_warn));
}

/*
 * Read an entry from a pkginfo or contents file.  Some of these lines can
 * either be arbitrarily long or be continued by a backslash at the end of
 * the line.  This function coalesces lines that are longer than the read
 * buffer, and lines that are continued, into one buffer which is returned.
 * The caller must free this memory.  NULL is returned when we hit EOF or
 * if we run out of memory (errno is set to ENOMEM).
 */
static char *
read_pkg_data(FILE *fp)
{
	char *start;
	char *inp;
	char *p;
	int char_cnt = 0;

	errno = 0;
	if ((start = (char *)malloc(PKGINFO_RD_LEN)) == NULL) {
		errno = ENOMEM;
		return (NULL);
	}

	inp = start;
	while ((p = fgets(inp, PKGINFO_RD_LEN, fp)) != NULL) {
		int len;

		len = strlen(inp);
		if (inp[len - 1] == '\n' &&
		    (len == 1 || inp[len - 2] != '\\')) {
			char_cnt = len;
			break;
		}

		if (inp[len - 2] == '\\')
			char_cnt += len - 2;
		else
			char_cnt += PKGINFO_RD_LEN - 1;

		if ((p = realloc(start, char_cnt + PKGINFO_RD_LEN)) == NULL) {
			errno = ENOMEM;
			break;
		}

		start = p;
		inp = start + char_cnt;
	}

	if (errno == ENOMEM || (p == NULL && char_cnt == 0)) {
		free(start);
		start = NULL;
	}

	return (start);
}

static void
free_ipd_pkgs(char **pkgs, int cnt)
{
	int i;

	for (i = 0; i < cnt; i++)
		free(pkgs[i]);
	free(pkgs);
}

/*
 * Get a list of the inherited pkg dirs or fs entries configured for the
 * zone.  The type parameter will be either ZONE_IPD or ZONE_FS.
 */
static int
get_ipd_fs_list(zone_dochandle_t handle, enum zn_ipd_fs type, char ***list)
{
	int	res;
	struct zone_fstab fstab;
	int	cnt = 0;
	char	**entries = NULL;
	int	i;
	int	(*fp)(zone_dochandle_t, struct zone_fstab *);

	if (type == ZONE_IPD) {
		fp = zonecfg_getipdent;
		res = zonecfg_setipdent(handle);
	} else {
		fp = zonecfg_getfsent;
		res = zonecfg_setfsent(handle);
	}

	if (res != Z_OK)
		return (res);

	while (fp(handle, &fstab) == Z_OK) {
		char	**p;

		if ((p = (char **)realloc(entries,
		    sizeof (char *) * (cnt + 1))) == NULL) {
			res = Z_NOMEM;
			break;
		}
		entries = p;

		if ((entries[cnt] = strdup(fstab.zone_fs_dir)) == NULL) {
			res = Z_NOMEM;
			break;
		}

		cnt++;
	}

	if (type == ZONE_IPD)
		(void) zonecfg_endipdent(handle);
	else
		(void) zonecfg_endfsent(handle);

	/* Add a NULL terminating element. */
	if (res == Z_OK) {
		char	**p;

		if ((p = (char **)realloc(entries,
		    sizeof (char *) * (cnt + 1))) == NULL) {
			res = Z_NOMEM;
		} else {
			entries = p;
			entries[cnt] = NULL;
		}
	}

	if (res != Z_OK) {
		if (entries != NULL) {
			for (i = 0; i < cnt; i++)
				free(entries[i]);
			free(entries);
		}
		return (res);
	}

	*list = entries;
	return (Z_OK);
}

/*
 * Get the list of inherited-pkg-dirs (ipd) for the zone and then get the
 * list of pkgs that deliver into those dirs.
 */
static int
get_ipd_pkgs(zone_dochandle_t handle, char ***pkg_list, int *cnt)
{
	int	res;
	char	**ipds;
	char	**fss;
	int	pkg_cnt = 0;
	char	**pkgs = NULL;
	int	i;

	if ((res = get_ipd_fs_list(handle, ZONE_IPD, &ipds)) != Z_OK)
		return (res);

	if ((res = get_ipd_fs_list(handle, ZONE_FS, &fss)) != Z_OK) {
		for (i = 0; ipds[i] != NULL; i++)
			free(ipds[i]);
		free(ipds);
		return (res);
	}

	/* We only have to process the contents file if we have ipds. */
	if (ipds != NULL) {
		FILE	*fp;

		if ((fp = fopen(CONTENTS_FILE, "r")) != NULL) {
			char	*buf;
			char	*pkg_warn = NULL;

			while ((buf = read_pkg_data(fp)) != NULL) {
				res = get_path_pkgs(buf, ipds, fss, &pkgs,
				    &pkg_cnt, &pkg_warn);
				free(buf);
				if (res != Z_OK)
					break;
			}

			(void) fclose(fp);

			if (pkg_warn != NULL) {
				(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
				    "WARNING: package metadata problems "
				    "with the following packages:\n   %s\n"),
				    pkg_warn);
				free(pkg_warn);
			}
		}
	}

	for (i = 0; ipds[i] != NULL; i++)
		free(ipds[i]);
	free(ipds);

	for (i = 0; fss[i] != NULL; i++)
		free(fss[i]);
	free(fss);

	if (res != Z_OK) {
		free_ipd_pkgs(pkgs, pkg_cnt);
	} else {
		*pkg_list = pkgs;
		*cnt = pkg_cnt;
	}

	return (res);
}

/*
 * Return true if pkg_name is in the list of pkgs that deliver into an
 * inherited pkg directory for the zone.
 */
static boolean_t
dir_pkg(char *pkg_name, char **pkg_list, int cnt)
{
	int i;

	for (i = 0; i < cnt; i++) {
		if (strcmp(pkg_name, pkg_list[i]) == 0)
			return (B_TRUE);
	}

	return (B_FALSE);
}

/*
 * Keep track of obsoleted patches for this specific patch.  We don't need to
 * keep track of the patch version since once a patch is obsoleted, all prior
 * versions are also obsolete and there won't be any new versions.
 */
static int
add_obs_patch(patch_node_t *patch, char *num, uu_list_pool_t *patches_pool)
{
	obs_patch_node_t *obs;

	if (patch->obs_patches == NULL) {
		if ((patch->obs_patches = uu_list_create(patches_pool, NULL,
		    0)) == NULL)
			return (Z_NOMEM);
	}

	if ((obs = (obs_patch_node_t *)malloc(sizeof (obs_patch_node_t)))
	    == NULL)
		return (Z_NOMEM);

	if ((obs->patch_num = strdup(num)) == NULL) {
		free(obs);
		return (Z_NOMEM);
	}

	uu_list_node_init(obs, &obs->link, patches_pool);
	(void) uu_list_insert_before(patch->obs_patches, NULL, obs);

	return (Z_OK);
}

/*
 * Keep track of obsoleted patches.  We don't need to keep track of the patch
 * version since once a patch is obsoleted, all prior versions are also
 * obsolete and there won't be any new versions.
 */
static int
save_obs_patch(char *num, uu_avl_pool_t *patches_pool, uu_avl_t *obs_patches)
{
	patch_node_t	*patch;
	uu_avl_index_t where;

	if ((patch = (patch_node_t *)malloc(sizeof (patch_node_t))) == NULL)
		return (Z_NOMEM);

	if ((patch->patch_num = strdup(num)) == NULL) {
		free(patch);
		return (Z_NOMEM);
	}

	patch->patch_vers = NULL;
	patch->obs_patches = NULL;

	uu_avl_node_init(patch, &patch->patch_node, patches_pool);

	if (uu_avl_find(obs_patches, patch, NULL, &where) != NULL) {
		free(patch->patch_num);
		free(patch);
		return (Z_OK);
	}

	uu_avl_insert(obs_patches, patch, where);
	return (Z_OK);
}

/*
 * Keep a list of patches for a pkg.  If we see a newer version of a patch,
 * we only keep track of the newer version.
 */
static boolean_t
save_patch(patch_node_t *patch, uu_avl_t *patches_avl)
{
	patch_node_t *existing;
	uu_avl_index_t where;

	/*
	 * Check if this is a newer version of a patch we already have.
	 * If it is an older version of a patch we already have, ignore it.
	 */
	if ((existing = (patch_node_t *)uu_avl_find(patches_avl, patch, NULL,
	    &where)) != NULL) {
		char *endptr;
		ulong_t pvers, evers;

		pvers = strtoul(patch->patch_vers, &endptr, 10);
		evers = strtoul(existing->patch_vers, &endptr, 10);

		if (pvers <= evers)
			return (B_FALSE);

		/*
		 * Remove the lower version patch from the tree so we can
		 * insert the new higher version one.  We also discard the
		 * obsolete patch list from the old version since the new
		 * version will have its own, likely different, list.
		 */
		uu_avl_remove(patches_avl, existing);
		free(existing->patch_num);
		free(existing->patch_vers);
		if (existing->obs_patches != NULL) {
			obs_patch_node_t *op;
			void *cookie2 = NULL;

			while ((op = uu_list_teardown(existing->obs_patches,
			    &cookie2)) != NULL) {
				free(op->patch_num);
				free(op);
			}
			uu_list_destroy(existing->obs_patches);
		}
		free(existing);

		/*
		 * Now that the old one is gone, find the new location
		 * in the tree.
		 */
		(void) uu_avl_find(patches_avl, patch, NULL, &where);
	}

	uu_avl_insert(patches_avl, patch, where);
	return (B_TRUE);
}

/*
 * Check if a patch is on the list of obsoleted patches.  We don't need to
 * check the patch version since once a patch is obsoleted, all prior versions
 * are also obsolete and there won't be any new versions.
 */
static boolean_t
obsolete_patch(patch_node_t *patch, uu_avl_t *obs_patches)
{
	uu_avl_index_t	where;

	if (uu_avl_find(obs_patches, patch, NULL, &where) != NULL)
		return (B_TRUE);

	return (B_FALSE);
}

/* ARGSUSED */
static int
patch_node_compare(const void *l_arg, const void *r_arg, void *private)
{
	patch_node_t *l = (patch_node_t *)l_arg;
	patch_node_t *r = (patch_node_t *)r_arg;
	char *endptr;
	ulong_t lnum, rnum;

	lnum = strtoul(l->patch_num, &endptr, 10);
	rnum = strtoul(r->patch_num, &endptr, 10);

	if (lnum > rnum)
		return (1);
	if (lnum < rnum)
		return (-1);
	return (0);
}

/*
 * Parse the patchinfo string for the patch.
 *
 * We are parsing entries of the form:
 * PATCH_INFO_121454-02=Installed: Wed Dec  7 07:13:51 PST 2005 From: mum \
 *	Obsoletes: 120777-03 121087-02 119108-07 Requires: 119575-02 \
 *	119255-06 Incompatibles:
 *
 * A backed out patch will have "backed out\n" as the status.  We should
 * skip these patches.  We also ignore any entries that seem to be
 * corrupted.  Obsolete patches are saved in the obs_patches parameter
 * AVL list.
 */
static int
parse_info(char *patchinfo, uu_avl_pool_t *patches_pool, uu_avl_t *patches_avl,
    uu_avl_t *obs_patches, uu_list_pool_t *list_pool)
{
	char		*p;
	char		*lastp;
	char		*ep;
	char		*pvers;
	boolean_t	add_info = B_FALSE;
	patch_node_t	*patch;

	if (strlen(patchinfo) < (sizeof (PATCHINFO) - 1))
		return (Z_OK);

	/* Skip over "PATCH_INFO_" to get the patch id. */
	p = patchinfo + sizeof (PATCHINFO) - 1;
	if ((ep = strchr(p, '=')) == NULL)
		return (Z_OK);

	*ep++ = '\0';

	/* Ignore all but installed patches. */
	if (strncmp(ep, "Installed:", 10) != 0)
		return (Z_OK);

	/* remove newline */
	lastp = (ep + strlen(ep) - 1);
	if (*lastp == '\n')
		*lastp = '\0';

	if ((patch = (patch_node_t *)malloc(sizeof (patch_node_t))) == NULL)
		return (Z_NOMEM);

	if ((pvers = strchr(p, '-')) != NULL)
		*pvers++ = '\0';
	else
		pvers = "";

	if ((patch->patch_num = strdup(p)) == NULL) {
		free(patch);
		return (Z_NOMEM);
	}
	if ((patch->patch_vers = strdup(pvers)) == NULL) {
		free(patch->patch_num);
		free(patch);
		return (Z_NOMEM);
	}
	patch->obs_patches = NULL;

	uu_avl_node_init(patch, &patch->patch_node, patches_pool);
	if (!save_patch(patch, patches_avl)) {
		free(patch->patch_num);
		free(patch->patch_vers);
		assert(patch->obs_patches == NULL);
		free(patch);
		return (Z_OK);
	}

	/*
	 * Start with the first token.  This will probably be "Installed:".
	 * If we can't tokenize this entry, just return.
	 */
	if ((p = strtok_r(ep, " ", &lastp)) == NULL)
		return (Z_OK);

	do {
		if (strcmp(p, "Installed:") == 0 ||
		    strcmp(p, "Requires:") == 0 ||
		    strcmp(p, "From:") == 0 ||
		    strcmp(p, "Incompatibles:") == 0) {
			add_info = B_FALSE;
			continue;
		} else if (strcmp(p, "Obsoletes:") == 0) {
			add_info = B_TRUE;
			continue;
		}

		if (!add_info)
			continue;

		if ((pvers = strchr(p, '-')) != NULL)
			*pvers = '\0';

		/*
		 * We save all of the obsolete patches in one big list in the
		 * obs_patches AVL tree so that we know not to output those as
		 * part of the sw dependencies.  However, we also need to save
		 * the obsolete patch information for this sepcific patch so
		 * so that we can do the cross manifest patch checking
		 * correctly.
		 */
		if (save_obs_patch(p, patches_pool, obs_patches) != Z_OK)
			return (Z_NOMEM);
		if (add_obs_patch(patch, p, list_pool) != Z_OK)
			return (Z_NOMEM);
	} while ((p = strtok_r(NULL, " ", &lastp)) != NULL);

	return (Z_OK);
}

/*
 * AVL walker callback used to add patch to XML manifest.
 *
 * PATH_MAX is used in the pkg/patch code as the maximum size for the patch
 * number/version string.
 */
static int
avl_add_patch(void *e, void *p)
{
	xmlNodePtr	node;
	char		id[PATH_MAX];
	patch_node_t	*patch;
	patch_parms_t	*args;

	patch = e;
	args = p;

	/* skip this patch if it has been obsoleted */
	if (obsolete_patch(patch, args->obs_patches_avl))
		return (UU_WALK_NEXT);

	if (patch->patch_vers[0] == '\0')
		(void) snprintf(id, sizeof (id), "%s", patch->patch_num);
	else
		(void) snprintf(id, sizeof (id), "%s-%s", patch->patch_num,
		    patch->patch_vers);

	if ((args->res = zonecfg_add_patch(args->handle, id, (void **)&node))
	    != Z_OK)
		return (UU_WALK_DONE);

	if (patch->obs_patches != NULL) {
		obs_patch_node_t *op;

		for (op = uu_list_first(patch->obs_patches); op != NULL;
		    op = uu_list_next(patch->obs_patches, op)) {
			(void) snprintf(id, sizeof (id), "%s", op->patch_num);
			if ((args->res = zonecfg_add_patch_obs(id, node))
			    != Z_OK)
				return (UU_WALK_DONE);
		}
	}

	return (UU_WALK_NEXT);
}

static void
patch_avl_delete(uu_avl_t *patches_avl)
{
	if (patches_avl != NULL) {
		patch_node_t *p;
		void *cookie = NULL;

		while ((p = (patch_node_t *)uu_avl_teardown(patches_avl,
		    &cookie)) != NULL) {
			free(p->patch_num);
			free(p->patch_vers);

			if (p->obs_patches != NULL) {
				obs_patch_node_t *op;
				void *cookie2 = NULL;

				while ((op = uu_list_teardown(p->obs_patches,
				    &cookie2)) != NULL) {
					free(op->patch_num);
					free(op);
				}
				uu_list_destroy(p->obs_patches);
			}

			free(p);
		}

		uu_avl_destroy(patches_avl);
	}
}

/*
 * Add the unique, highest version patches that are associated with this pkg
 * to the sw inventory on the handle.
 */
static int
add_patches(zone_dochandle_t handle, struct zone_pkginfo *infop,
    uu_avl_pool_t *patches_pool, uu_avl_t *obs_patches,
    uu_list_pool_t *list_pool)
{
	int		i;
	int		res;
	uu_avl_t 	*patches_avl;
	patch_parms_t	args;

	if ((patches_avl = uu_avl_create(patches_pool, NULL, UU_DEFAULT))
	    == NULL)
		return (Z_NOMEM);

	for (i = 0; i < infop->zpi_patch_cnt; i++) {
		if ((res = parse_info(infop->zpi_patchinfo[i], patches_pool,
		    patches_avl, obs_patches, list_pool)) != Z_OK) {
			patch_avl_delete(patches_avl);
			return (res);
		}
	}

	args.obs_patches_avl = obs_patches;
	args.handle = handle;
	args.res = Z_OK;

	(void) uu_avl_walk(patches_avl, avl_add_patch, &args, 0);

	patch_avl_delete(patches_avl);
	return (args.res);
}

/*
 * Keep track of the pkgs we have already processed so that we can quickly
 * skip those pkgs while recursively doing dependents.
 */
static boolean_t
pkg_in_manifest(uu_avl_t *saw_pkgs, char *pname, uu_avl_pool_t *pkgs_pool)
{
	uu_avl_index_t where;

	if (uu_avl_find(saw_pkgs, pname, NULL, &where) == NULL) {
		zone_pkg_entry_t *pkg;

		/*
		 * We need to add it.  If we don't have memory we just skip
		 * this pkg since this routine improves performance but the
		 * algorithm is still correct without it.
		 */
		if ((pkg = (zone_pkg_entry_t *)
		    malloc(sizeof (zone_pkg_entry_t))) == NULL)
			return (B_FALSE);

		if ((pkg->zpe_name = strdup(pname)) == NULL) {
			free(pkg);
			return (B_FALSE);
		}

		pkg->zpe_vers = NULL;
		pkg->zpe_patches_avl = NULL;

		/* Insert pkg into the AVL tree. */
		uu_avl_node_init(pkg, &pkg->zpe_entry, pkgs_pool);
		uu_avl_insert(saw_pkgs, pkg, where);
		return (B_FALSE);
	}

	return (B_TRUE);
}

static void
free_pkginfo(struct zone_pkginfo *infop)
{
	free(infop->zpi_version);
	free(infop->zpi_patchlist);
	if (infop->zpi_patch_cnt > 0) {
		int i;

		for (i = 0; i < infop->zpi_patch_cnt; i++)
			free(infop->zpi_patchinfo[i]);
		free(infop->zpi_patchinfo);
	}
}

/*
 * Read the pkginfo file and populate the structure with the data we need
 * from this pkg for a sw inventory.
 */
static int
get_pkginfo(char *pkginfo, struct zone_pkginfo *infop)
{
	FILE	*fp;
	char	*buf;
	int	err = 0;

	infop->zpi_all_zones = B_FALSE;
	infop->zpi_this_zone = B_FALSE;
	infop->zpi_version = NULL;
	infop->zpi_patchlist = NULL;
	infop->zpi_patch_cnt = 0;
	infop->zpi_patchinfo = NULL;

	if ((fp = fopen(pkginfo, "r")) == NULL)
		return (errno);

	while ((buf = read_pkg_data(fp)) != NULL) {
		if (strncmp(buf, VERSION, sizeof (VERSION) - 1) == 0) {
			int len;

			if ((infop->zpi_version =
			    strdup(buf + sizeof (VERSION) - 1)) == NULL) {
				err = ENOMEM;
				break;
			}

			/* remove trailing newline */
			len = strlen(infop->zpi_version);
			infop->zpi_version[len - 1] = '\0';

		} else if (strcmp(buf, SUNW_PKG_ALL_ZONES) == 0) {
			infop->zpi_all_zones = B_TRUE;

		} else if (strcmp(buf, SUNW_PKG_THIS_ZONE) == 0) {
			infop->zpi_this_zone = B_TRUE;

		} else if (strncmp(buf, PATCHLIST, sizeof (PATCHLIST) - 1)
		    == 0) {
			int len;

			if ((infop->zpi_patchlist =
			    strdup(buf + sizeof (PATCHLIST) - 1)) == NULL) {
				err = ENOMEM;
				break;
			}

			/* remove trailing newline */
			len = strlen(infop->zpi_patchlist);
			infop->zpi_patchlist[len - 1] = '\0';

		} else if (strncmp(buf, PATCHINFO, sizeof (PATCHINFO) - 1)
		    == 0) {
			char **p;

			if ((p = (char **)realloc(infop->zpi_patchinfo,
			    sizeof (char *) * (infop->zpi_patch_cnt + 1)))
			    == NULL) {
				err = ENOMEM;
				break;
			}
			infop->zpi_patchinfo = p;

			if ((infop->zpi_patchinfo[infop->zpi_patch_cnt] =
			    strdup(buf)) == NULL) {
				err = ENOMEM;
				break;
			}
			infop->zpi_patch_cnt++;
		}

		free(buf);
	}

	free(buf);

	if (errno == ENOMEM) {
		err = ENOMEM;
		/* Clean up anything we did manage to allocate. */
		free_pkginfo(infop);
	}

	(void) fclose(fp);

	return (err);
}

/*
 * Add any dependent pkgs to the list.  The pkg depend file lists pkg
 * dependencies, one per line with an entry that looks like:
 *	P SUNWcar       Core Architecture, (Root)
 * See the depend(4) man page.
 */
static int
add_dependents(zone_dochandle_t handle, char *pname,
    uu_avl_pool_t *patches_pool, uu_avl_t *obs_patches,
    uu_list_pool_t *list_pool, uu_avl_t *saw_pkgs, uu_avl_pool_t *pkgs_pool)
{
	int		res = Z_OK;
	FILE		*fp;
	char		depend[MAXPATHLEN];
	char		*buf;
	struct stat	sbuf;

	(void) snprintf(depend, sizeof (depend), "%s/%s/install/depend",
	    PKG_PATH, pname);

	if (stat(depend, &sbuf) == -1 || !S_ISREG(sbuf.st_mode))
		return (Z_OK);

	if ((fp = fopen(depend, "r")) == NULL)
		return (Z_OK);

	while ((buf = read_pkg_data(fp)) != NULL) {
		char *deppkg;
		char *delims = " \t";
		char pkginfo[MAXPATHLEN];
		struct zone_pkginfo info;

		if (*buf != 'P') {
			free(buf);
			continue;
		}

		/* Skip past the leading 'P '. */
		if ((deppkg = strtok(buf + 2, delims)) == NULL) {
			free(buf);
			continue;
		}

		/* If the pkg is already in the manifest don't add it again. */
		if (pkg_in_manifest(saw_pkgs, deppkg, pkgs_pool)) {
			free(buf);
			continue;
		}

		(void) snprintf(pkginfo, sizeof (pkginfo), "%s/%s/pkginfo",
		    PKG_PATH, deppkg);

		if (stat(pkginfo, &sbuf) == -1 || !S_ISREG(sbuf.st_mode)) {
			free(buf);
			continue;
		}

		if (get_pkginfo(pkginfo, &info) != 0) {
			res = Z_NOMEM;
			free(buf);
			break;
		}

		if ((res = add_dependents(handle, deppkg, patches_pool,
		    obs_patches, list_pool, saw_pkgs, pkgs_pool)) == Z_OK &&
		    (res = zonecfg_add_pkg(handle, deppkg, info.zpi_version))
		    == Z_OK) {
			if (info.zpi_patch_cnt > 0)
				res = add_patches(handle, &info, patches_pool,
				    obs_patches, list_pool);
		}

		free(buf);
		free_pkginfo(&info);

		if (res != Z_OK)
			break;
	}

	(void) fclose(fp);
	return (res);
}

/* ARGSUSED */
static int
pkg_entry_nm_compare(const void *l_arg, const void *r_arg, void *private)
{
	zone_pkg_entry_t *pkg = (zone_pkg_entry_t *)l_arg;
	char *name = (char *)r_arg;

	return (strcmp(pkg->zpe_name, name));
}

/* ARGSUSED */
static int
pkg_entry_compare(const void *l_arg, const void *r_arg, void *private)
{
	zone_pkg_entry_t *l = (zone_pkg_entry_t *)l_arg;
	zone_pkg_entry_t *r = (zone_pkg_entry_t *)r_arg;

	return (strcmp(l->zpe_name, r->zpe_name));
}

static void
pkg_avl_delete(uu_avl_t *pavl)
{
	if (pavl != NULL) {
		zone_pkg_entry_t *p;
		void *cookie = NULL;

		while ((p = uu_avl_teardown(pavl, &cookie)) != NULL) {
			free(p->zpe_name);
			free(p);
		}

		uu_avl_destroy(pavl);
	}
}

/* Memory allocation failure, free up allocated memory */
static void
destroy_gz_only_list()
{
	int i;

	if (gz_only_pkgs_list != NULL) {
		for (i = 0; gz_only_pkgs_list[i] != NULL; i++)
			free(gz_only_pkgs_list[i]);
		free(gz_only_pkgs_list);
		gz_only_pkgs_list = NULL;
	}
}

/*
 * If a package is GZ only, it should not be installed in NGZ. This routine
 * builds the list of GZ only packages
 */
static int
load_gz_only_pkg_list()
{
	FILE *fp = NULL;
	char buf[80];
	int cnt = 0;

	/* If file exists, do the open */
	if ((fp = fopen("/var/sadm/install/gz-only-packages", "r")) == NULL)
		return (ENOENT);

	if ((gz_only_pkgs_list =
	    (char **)malloc(sizeof (char *))) == NULL) {
		(void) fclose(fp);
		return (Z_NOMEM);
	}

	while (fgets(buf, 80, fp) != NULL) {
		char **p = NULL;

		/* Take care to skip comments lines */
		if (buf[0] == '#' || buf[0] == '\n')
			continue;

		buf[strlen(buf) - 1] = '\0';

		/*
		 * Allocate memory for each entry in gz_only_pkgs_list.
		 * If unable to get memory, return Z_NOMEM to indicate
		 * allocation failure
		 */
		if ((gz_only_pkgs_list[cnt] = strdup(buf)) == NULL) {
			destroy_gz_only_list();
			(void) fclose(fp);
			return (Z_NOMEM);
		}
		cnt++;

		/* Allocate memory for the next pkg to be excluded */
		if ((p = (char **)realloc(gz_only_pkgs_list,
		    ((cnt + 1) * sizeof (char *)))) == NULL) {
			free(gz_only_pkgs_list[cnt-1]);
			gz_only_pkgs_list[cnt-1] = NULL;
			destroy_gz_only_list();
			(void) fclose(fp);
			return (Z_NOMEM);
		}
		gz_only_pkgs_list = p;
	}
	gz_only_pkgs_list[cnt] = NULL;

	(void) fclose(fp);

	return (Z_OK);
}

/*
 * Check if the package we are trying to install into a NGZ is a GZ only pkg.
 * If so, skip it's installation in the NGZ
 */
static boolean_t
is_gz_only_pkg(char *pkg)
{
	int i;

	for (i = 0; gz_only_pkgs_list[i] != NULL; i++) {
		if (strcmp(pkg, gz_only_pkgs_list[i]) == 0)
			return (B_TRUE);
	}
	return (B_FALSE);
}


/*
 * Take a software inventory of the global zone.  We need to get the set of
 * packages and patches that are on the global zone that the specified
 * non-global zone depends on.  The packages we need in the inventory are:
 *
 * - skip the package if SUNW_PKG_THISZONE is 'true'
 * otherwise,
 * - add the package if
 * a) SUNW_PKG_ALLZONES is 'true',
 * or
 * b) any file delivered by the package is in a file system that is inherited
 * from the global zone.
 * If the zone does not inherit any file systems (whole root)
 * then (b) will be skipped.
 *
 * For each of the packages that is being added to the inventory, we will also
 * add its dependent packages to the inventory.
 *
 * For each of the packages that is being added to the inventory, we will also
 * add all of the associated, unique patches to the inventory.
 *
 * See the comment for zonecfg_getpkgdata() for compatability restrictions on
 * how we must save the XML representation of the software inventory.
 */
static int
sw_inventory(zone_dochandle_t handle)
{
	char		pkginfo[MAXPATHLEN];
	int		res;
	int		gz_pkgs_loaded;
	struct dirent	*dp;
	DIR		*dirp;
	struct stat	buf;
	struct zone_pkginfo	info;
	int		pkg_cnt = 0;
	char		**pkgs = NULL;
	uu_avl_pool_t 	*pkgs_pool = NULL;
	uu_avl_pool_t 	*patches_pool = NULL;
	uu_list_pool_t 	*list_pool = NULL;
	uu_avl_t	*saw_pkgs = NULL;
	uu_avl_t 	*obs_patches = NULL;

	/*
	 * Get the list of GZ only packages from file gz-only-packages
	 * into memory
	 */
	if ((gz_pkgs_loaded = load_gz_only_pkg_list())
	    == Z_NOMEM) {
		res = Z_NOMEM;
		goto done;
	}

	if ((pkgs_pool = uu_avl_pool_create("pkgs_pool",
	    sizeof (zone_pkg_entry_t), offsetof(zone_pkg_entry_t, zpe_entry),
	    pkg_entry_nm_compare, UU_DEFAULT)) == NULL) {
		res = Z_NOMEM;
		goto done;
	}

	if ((saw_pkgs = uu_avl_create(pkgs_pool, NULL, UU_DEFAULT)) == NULL) {
		res = Z_NOMEM;
		goto done;
	}

	if ((patches_pool = uu_avl_pool_create("patches_pool",
	    sizeof (patch_node_t), offsetof(patch_node_t, patch_node),
	    patch_node_compare, UU_DEFAULT)) == NULL) {
		res = Z_NOMEM;
		goto done;
	}

	if ((list_pool = uu_list_pool_create("list_pool",
	    sizeof (obs_patch_node_t), offsetof(obs_patch_node_t, link), NULL,
	    UU_DEFAULT)) == NULL) {
		res = Z_NOMEM;
		goto done;
	}

	/*
	 * The obs_patches AVL tree saves all of the obsolete patches so
	 * that we know not to output those as part of the sw dependencies.
	 */
	if ((obs_patches = uu_avl_create(patches_pool, NULL, UU_DEFAULT))
	    == NULL) {
		res = Z_NOMEM;
		goto done;
	}

	if ((res = get_ipd_pkgs(handle, &pkgs, &pkg_cnt)) != Z_OK) {
		res = Z_NOMEM;
		goto done;
	}

	if ((dirp = opendir(PKG_PATH)) == NULL) {
		res = Z_NOMEM;
		goto done;
	}

	while ((dp = readdir(dirp)) != (struct dirent *)0) {
		if (strcmp(dp->d_name, ".") == 0 ||
		    strcmp(dp->d_name, "..") == 0)
			continue;

		/*
		 * If only gz-only-packages file exists and read
		 * into memory, check for a possible match
		 * If there is a match, skip over. Such a package is
		 * meant for GZ only installation.
		 */
		if ((gz_pkgs_loaded == Z_OK) &&
		    (is_gz_only_pkg(dp->d_name) == B_TRUE))
			continue;

		(void) snprintf(pkginfo, sizeof (pkginfo), "%s/%s/pkginfo",
		    PKG_PATH, dp->d_name);

		if (stat(pkginfo, &buf) == -1 || !S_ISREG(buf.st_mode))
			continue;

		if (get_pkginfo(pkginfo, &info) != 0) {
			res = Z_NOMEM;
			break;
		}

		if (!info.zpi_this_zone &&
		    (info.zpi_all_zones ||
		    dir_pkg(dp->d_name, pkgs, pkg_cnt)) &&
		    !pkg_in_manifest(saw_pkgs, dp->d_name, pkgs_pool)) {
			/*
			 * Add dependents first so any patches will get
			 * associated with the right pkg in the xml file.
			 */
			if ((res = add_dependents(handle, dp->d_name,
			    patches_pool, obs_patches, list_pool, saw_pkgs,
			    pkgs_pool)) == Z_OK &&
			    (res = zonecfg_add_pkg(handle, dp->d_name,
			    info.zpi_version)) == Z_OK) {
				if (info.zpi_patch_cnt > 0)
					res = add_patches(handle, &info,
					    patches_pool, obs_patches,
					    list_pool);
			}
		}

		free_pkginfo(&info);

		if (res != Z_OK)
			break;
	}

	(void) closedir(dirp);

done:
	/*
	 * Free up the heap memory that has been allocated for
	 * gz-only-packages
	 */
	destroy_gz_only_list();
	pkg_avl_delete(saw_pkgs);
	patch_avl_delete(obs_patches);
	if (pkgs_pool != NULL)
		uu_avl_pool_destroy(pkgs_pool);
	if (patches_pool != NULL)
		uu_avl_pool_destroy(patches_pool);
	if (list_pool != NULL)
		uu_list_pool_destroy(list_pool);
	free_ipd_pkgs(pkgs, pkg_cnt);

	if (res == Z_OK)
		zonecfg_set_swinv(handle);

	return (res);
}

/*
 * Get the information required to support detaching a zone.  This is
 * called on the source system when detaching (the detaching parameter should
 * be set to true) and on the destination system before attaching (the
 * detaching parameter should be false).
 *
 * For native Solaris zones, the detach/attach process involves validating
 * that the software on the global zone can support the zone when we attach.
 * To do this we take a software inventory of the global zone.  We also
 * have to keep track of the device configuration so that we can properly
 * recreate it on the destination.
 */
static int
get_detach_info(zone_dochandle_t handle, boolean_t detaching)
{
	int		res;

	if ((res = sw_inventory(handle)) != Z_OK)
		return (res);

	if (detaching)
		res = zonecfg_dev_manifest(handle);

	return (res);
}

/* ARGSUSED */
static int
zfm_print(const struct mnttab *p, void *r) {
	(void) fprintf(stderr, "  %s\n", p->mnt_mountp);
	return (0);
}

static void
detach_usage()
{
	(void) fprintf(stderr, gettext("usage:\t%s brand options: none\n"),
	    MY_BRAND_NAME);
}

static int
detach_func(int argc, char *argv[])
{
	int err, arg;
	zone_dochandle_t handle;
	boolean_t execute = B_TRUE;

	opterr = 0;
	optind = 0;
	if ((arg = getopt(argc, argv, "?n")) != EOF) {
		switch (arg) {
		case '?':
			if (optopt != '?') {
				(void) fprintf(stderr, gettext("%s brand: "
				    "invalid option: %c\n"), MY_BRAND_NAME,
				    optopt);
			}
			detach_usage();
			return (optopt == '?' ? Z_OK : ZONE_SUBPROC_USAGE);
		case 'n':
			execute = B_FALSE;
			break;
		default:
			(void) fprintf(stderr, gettext("%s brand: invalid "
			    "option: %c\n"), MY_BRAND_NAME, arg);
			return (ZONE_SUBPROC_USAGE);
		}
	}

	if (argc > optind) {
		detach_usage();
		return (ZONE_SUBPROC_USAGE);
	}

	/* Don't detach the zone if anything is still mounted there */
	if (execute && zonecfg_find_mounts(zonepath, NULL, NULL)) {
		(void) fprintf(stderr, gettext("These file systems are "
		    "mounted on subdirectories of %s.\n"), zonepath);
		(void) zonecfg_find_mounts(zonepath, zfm_print, NULL);
		return (ZONE_SUBPROC_NOTCOMPLETE);
	}

	if ((handle = zonecfg_init_handle()) == NULL) {
		(void) fprintf(stderr, gettext("brand detach program error: "
		    "%s\n"), strerror(errno));
		return (ZONE_SUBPROC_NOTCOMPLETE);
	}

	if ((err = zonecfg_get_handle(zonename, handle)) != Z_OK) {
		(void) fprintf(stderr, gettext("brand detach program error: "
		    "%s\n"), zonecfg_strerror(err));
		zonecfg_fini_handle(handle);
		return (ZONE_SUBPROC_NOTCOMPLETE);
	}

	if ((err = get_detach_info(handle, B_TRUE)) != Z_OK) {
		(void) fprintf(stderr, gettext("brand detach program error: "
		    "%s\n"), zonecfg_strerror(err));
		goto done;
	}

	if ((err = zonecfg_detach_save(handle, (execute ? 0 : ZONE_DRY_RUN)))
	    != Z_OK) {
		(void) fprintf(stderr, gettext("saving the detach manifest "
		    "failed: %s\n"), zonecfg_strerror(err));
		goto done;
	}

done:
	zonecfg_fini_handle(handle);

	return ((err == Z_OK) ? ZONE_SUBPROC_OK : ZONE_SUBPROC_FATAL);
}

/*
 * Validate attaching a zone but don't actually do the work.  The zone
 * does not have to exist, so there is some complexity getting a new zone
 * configuration set up so that we can perform the validation.  This is
 * handled within zonecfg_attach_manifest() which returns two handles; one
 * for the the full configuration to validate (rem_handle) and the other
 * (local_handle) containing only the zone configuration derived from the
 * manifest.
 */
static int
dryrun_attach(char *manifest_path)
{
	int fd;
	int err;
	int res;
	char atbrand[MAXNAMELEN];
	zone_dochandle_t local_handle;
	zone_dochandle_t rem_handle = NULL;

	if ((fd = open(manifest_path, O_RDONLY)) < 0) {
		(void) fprintf(stderr, gettext("could not open manifest path: "
		    "%s\n"), strerror(errno));
		return (ZONE_SUBPROC_NOTCOMPLETE);
	}

	if ((local_handle = zonecfg_init_handle()) == NULL) {
		(void) fprintf(stderr, gettext("brand attach program error: "
		    "%s\n"), strerror(errno));
		res = ZONE_SUBPROC_NOTCOMPLETE;
		goto done;
	}

	if ((rem_handle = zonecfg_init_handle()) == NULL) {
		(void) fprintf(stderr, gettext("brand attach program error: "
		    "%s\n"), strerror(errno));
		res = ZONE_SUBPROC_NOTCOMPLETE;
		goto done;
	}

	if ((err = zonecfg_attach_manifest(fd, local_handle, rem_handle))
	    != Z_OK) {
		res = ZONE_SUBPROC_NOTCOMPLETE;

		if (err == Z_INVALID_DOCUMENT) {
			char buf[6];

			bzero(buf, sizeof (buf));
			(void) lseek(fd, 0L, SEEK_SET);
			if (read(fd, buf, sizeof (buf) - 1) < 0 ||
			    strncmp(buf, "<?xml", 5) != 0)
				(void) fprintf(stderr, gettext("%s is not an "
				    "XML file\n"), manifest_path);
			else
				(void) fprintf(stderr, gettext("Cannot attach "
				    "to an earlier release of the operating "
				    "system\n"));
		} else {
			(void) fprintf(stderr, gettext("brand attach program "
			    "error: %s\n"), zonecfg_strerror(err));
		}
		goto done;
	}

	/*
	 * Retrieve remote handle brand type and determine whether it is
	 * native or not.
	 */
	if (zonecfg_get_brand(rem_handle, atbrand, sizeof (atbrand)) != Z_OK) {
		(void) fprintf(stderr, gettext("missing or invalid brand\n"));
		exit(ZONE_SUBPROC_FATAL);
	}

	if (strcmp(atbrand, MY_BRAND_NAME) != 0) {
		err = Z_ERR;
		(void) fprintf(stderr, gettext("Trying to attach a '%s' zone "
		    "to a '%s' configuration.\n"), atbrand, MY_BRAND_NAME);
		exit(ZONE_SUBPROC_FATAL);
	}

	/* Get the detach information for the locally defined zone. */
	res = Z_OK;
	if ((err = get_detach_info(local_handle, B_FALSE)) != Z_OK) {
		(void) fprintf(stderr, gettext("getting the attach information "
		    "failed: %s\n"), zonecfg_strerror(err));
		res = ZONE_SUBPROC_FATAL;
	} else {
		/* sw_cmp prints error msgs as necessary */
		if (sw_cmp(local_handle, rem_handle, SW_CMP_NONE) != Z_OK)
			res = ZONE_SUBPROC_FATAL;
	}

done:
	(void) close(fd);

	zonecfg_fini_handle(local_handle);
	zonecfg_fini_handle(rem_handle);

	return ((res == Z_OK) ? Z_OK : ZONE_SUBPROC_FATAL);
}

static int
mount_func(boolean_t force)
{
	zone_cmd_arg_t zarg;

	zarg.cmd = force ? Z_FORCEMOUNT : Z_MOUNT;
	zarg.bootbuf[0] = '\0';
	if (zonecfg_call_zoneadmd(zonename, &zarg, locale, B_FALSE) != 0) {
		zerror(gettext("call to %s failed"), "zoneadmd");
		return (Z_ERR);
	}
	return (Z_OK);
}

static int
unmount_func()
{
	zone_cmd_arg_t zarg;

	zarg.cmd = Z_UNMOUNT;
	if (zonecfg_call_zoneadmd(zonename, &zarg, locale, B_FALSE) != 0) {
		zerror(gettext("call to %s failed"), "zoneadmd");
		return (Z_ERR);
	}
	return (Z_OK);
}

/*
 * Setup a configured zone so we can mount it and zlogin to perform various
 * tasks.  Also put a copy of the zone configuration into the zone so we can
 * get a handle on it.
 *
 * The sequence of actions we are doing is this:
 *	[set zone state to installed]
 *	[mount zone]
 *	zlogin {zone} </etc/zones/{zone}.xml 'cat >/etc/zones/{zone}.xml'
 *
 * The unmount_configured_zone() function should be used to clean up from
 * this function.
 */
static boolean_t
mount_configured_zone(boolean_t *mounted)
{
	int		status;
	char		cmdbuf[2 * MAXPATHLEN];

	/*
	 * The zone has to be installed to mount and zlogin.  Temporarily set
	 * the state to 'installed'.
	 */
	if (zone_set_state(zonename, ZONE_STATE_INSTALLED) != Z_OK)
		return (B_FALSE);

	/* Mount the zone so we can zlogin. */
	if (mount_func(B_FALSE) != Z_OK)
		return (B_FALSE);
	*mounted = B_TRUE;

	/*
	 * We need to copy the zones xml configuration file into the
	 * zone so we can get a handle for the zone while running inside
	 * the zone.
	 */
	if (snprintf(cmdbuf, sizeof (cmdbuf), "/usr/sbin/zlogin -S %s "
	    "</etc/zones/%s.xml '/usr/bin/cat >/etc/zones/%s.xml'",
	    zonename, zonename, zonename) >= sizeof (cmdbuf))
		return (B_FALSE);

	status = do_subproc(cmdbuf);
	if (subproc_status("copy", status, B_TRUE) != ZONE_SUBPROC_OK)
		return (B_FALSE);

	return (B_TRUE);
}

/*
 * Cleanup a configured zone mounted using mount_configured_zone().
 * tasks.
 *
 * The sequence of actions we are doing is this:
 *	zlogin {zone} 'rm -f /etc/zones/{zone}.xml'
 *	[unmount zone]
 *	[set zone state to configured]
 */
static boolean_t
unmount_configured_zone(boolean_t mounted)
{
	boolean_t	res = B_TRUE;

	/* Cleanup from the previous actions. */
	if (mounted) {
		int status;
		char cmdbuf[2 * MAXPATHLEN];

		if (snprintf(cmdbuf, sizeof (cmdbuf),
		    "/usr/sbin/zlogin -S %s '/usr/bin/rm -f /etc/zones/%s.xml'",
		    zonename, zonename) >= sizeof (cmdbuf)) {
			res = B_FALSE;
		} else {
			status = do_subproc(cmdbuf);
			if (subproc_status("rm", status, B_TRUE)
			    != ZONE_SUBPROC_OK)
				res = B_FALSE;
		}

		if (unmount_func() != Z_OK)
			res =  B_FALSE;
	}

	if (zone_set_state(zonename, ZONE_STATE_CONFIGURED) != Z_OK)
		res = B_FALSE;

	return (res);
}

/*
 * Attempt to generate the information we need to make the zone look like
 * it was properly detached by using the pkg information contained within
 * the zone itself.
 * We will perform a dry-run detach within the zone to generate the xml file.
 * The successful result of this function is that we will have the "manifest"
 * xml file in the zonepath and we can use that to attach the zone.
 */
static boolean_t
gen_detach_info(char *manifest)
{
	int		status;
	boolean_t	mounted = B_FALSE;
	boolean_t	res = B_FALSE;
	char		cmdbuf[2 * MAXPATHLEN];

	if (!mount_configured_zone(&mounted))
		goto cleanup;

	/* Now run the detach command within the mounted zone. */
	if (snprintf(cmdbuf, sizeof (cmdbuf), "/usr/sbin/zlogin -S %s "
	    "'/usr/sbin/zoneadm -z %s detach -n' >%s/%s",
	    zonename, zonename, zonepath, manifest) >= sizeof (cmdbuf))
		goto cleanup;

	status = do_subproc(cmdbuf);
	if (subproc_status("detach", status, B_TRUE) != ZONE_SUBPROC_OK)
		goto cleanup;

	res = B_TRUE;

cleanup:
	if (!unmount_configured_zone(mounted))
		res =  B_FALSE;

	return (res);
}

/*
 * The zone needs to be updated so set it up for the update and initiate the
 * update within the scratch zone.  First set the state to incomplete so we can
 * force-mount the zone for the update operation.  We pass the -U option to the
 * mount so that the scratch zone is mounted without the zone's /etc and /var
 * being lofs mounted back into the scratch zone root.  This is done by
 * overloading the bootbuf string in the zone_cmd_arg_t to pass -U as an option
 * to the mount cmd.
 */
static int
attach_update(zone_dochandle_t handle)
{
	int err;
	int update_res;
	int status;
	zone_cmd_arg_t zarg;
	FILE *fp;
	struct zone_fstab fstab;
	char cmdbuf[(4 * MAXPATHLEN) + 20];

	if ((err = zone_set_state(zonename, ZONE_STATE_INCOMPLETE))
	    != Z_OK) {
		(void) fprintf(stderr, gettext("could not set state: %s\n"),
		    zonecfg_strerror(err));
		return (Z_FATAL);
	}

	zarg.cmd = Z_FORCEMOUNT;
	(void) strlcpy(zarg.bootbuf, "-U",  sizeof (zarg.bootbuf));
	if (zonecfg_call_zoneadmd(zonename, &zarg, locale, B_FALSE) != 0) {
		(void) fprintf(stderr, gettext("could not mount zone\n"));

		/* We reset the state since the zone wasn't modified yet. */
		if ((err = zone_set_state(zonename, ZONE_STATE_CONFIGURED))
		    != Z_OK) {
			(void) fprintf(stderr, gettext("could not reset state: "
			    "%s\n"), zonecfg_strerror(err));
		}
		return (Z_FATAL);
	}

	/*
	 * Move data files generated by sw_up_to_date() into the scratch
	 * zone's /tmp.
	 */
	(void) snprintf(cmdbuf, sizeof (cmdbuf), "exec /usr/bin/mv "
	    "%s/pkg_add %s/pkg_rm %s/lu/tmp",
	    zonepath, zonepath, zonepath);

	status = do_subproc(cmdbuf);
	if (subproc_status("mv", status, B_TRUE) != ZONE_SUBPROC_OK) {
		(void) fprintf(stderr, gettext("could not mv data files: %s\n"),
		    strerror(errno));
		goto fail;
	}

	/*
	 * Save list of inherit-pkg-dirs into zone.  Since the file is in
	 * /tmp we don't have to worry about deleting it.
	 */
	(void) snprintf(cmdbuf, sizeof (cmdbuf), "%s/lu/tmp/inherited",
	    zonepath);
	if ((fp = fopen(cmdbuf, "w")) == NULL) {
		(void) fprintf(stderr, gettext("could not save "
		    "inherit-pkg-dirs: %s\n"), strerror(errno));
		goto fail;
	}
	if (zonecfg_setipdent(handle) != Z_OK) {
		(void) fprintf(stderr, gettext("could not enumerate "
		    "inherit-pkg-dirs: %s\n"), zonecfg_strerror(err));
		goto fail;
	}
	while (zonecfg_getipdent(handle, &fstab) == Z_OK) {
		if (fprintf(fp, "%s\n", fstab.zone_fs_dir) < 0) {
			(void) fprintf(stderr, gettext("could not save "
			    "inherit-pkg-dirs: %s\n"), strerror(errno));
			(void) fclose(fp);
			goto fail;
		}
	}
	(void) zonecfg_endipdent(handle);
	if (fclose(fp) != 0) {
		(void) fprintf(stderr, gettext("could not save "
		    "inherit-pkg-dirs: %s\n"), strerror(errno));
		goto fail;
	}

	/* run the updater inside the scratch zone */
	(void) snprintf(cmdbuf, sizeof (cmdbuf),
	    "exec /usr/sbin/zlogin -S %s "
	    "/usr/lib/brand/native/attach_update %s", zonename, zonename);

	update_res = Z_OK;
	status = do_subproc(cmdbuf);
	if (subproc_status("attach_update", status, B_TRUE)
	    != ZONE_SUBPROC_OK) {
		(void) fprintf(stderr, gettext("could not update zone\n"));
		update_res = Z_ERR;
	}

	if (update_res == Z_OK) {
		/* Install the new /etc/release file. */
		(void) snprintf(cmdbuf, sizeof (cmdbuf),
		    "exec /usr/sbin/zlogin -S %s "
		    "'/usr/bin/cat >/a/etc/release' </etc/release", zonename);

		status = do_subproc(cmdbuf);
		if (subproc_status("/etc/release", status, B_TRUE)
		    != ZONE_SUBPROC_OK) {
			(void) fprintf(stderr, gettext("could not update "
			    "zone\n"));
			update_res = Z_ERR;
		}
	}

	zarg.cmd = Z_UNMOUNT;
	if (zonecfg_call_zoneadmd(zonename, &zarg, locale, B_FALSE) != 0) {
		(void) fprintf(stderr, gettext("could not unmount zone\n"));
		return (Z_ERR);
	}

	/*
	 * If the update script within the scratch zone failed for some reason
	 * we will now leave the zone in the incomplete state since we no
	 * longer know the state of the files within the zonepath.
	 */
	if (update_res == Z_ERR)
		return (Z_ERR);

	zonecfg_rm_detached(handle, B_FALSE);

	return (Z_OK);

fail:
	zarg.cmd = Z_UNMOUNT;
	if (zonecfg_call_zoneadmd(zonename, &zarg, locale, B_FALSE) != 0)
		(void) fprintf(stderr, gettext("could not unmount zone\n"));

	/* We reset the state since the zone wasn't modified yet. */
	if ((err = zone_set_state(zonename, ZONE_STATE_CONFIGURED))
	    != Z_OK) {
		errno = err;
		(void) fprintf(stderr, gettext("could not reset state: %s\n"),
		    zonecfg_strerror(err));
	}

	return (Z_ERR);
}

/* ARGSUSED */
static void
sigcleanup(int sig)
{
	attach_interupted = B_TRUE;
}

static boolean_t
valid_num(char *n)
{
	for (; isdigit(*n); n++)
		;

	if (*n != NULL)
		return (B_FALSE);
	return (B_TRUE);
}

/*
 * Take an input field, which must look like a positive int, and return the
 * numeric value of the field.  Return -1 if the input field does not look
 * like something we can convert.
 */
static int
fld2num(char *fld, char **nfld)
{
	char *ppoint;
	long n;

	if ((ppoint = strchr(fld, '.')) != NULL) {
		*ppoint = '\0';
		*nfld = ppoint + 1;
	} else {
		*nfld = NULL;
	}

	if (!valid_num(fld))
		return (-1);

	errno = 0;
	n = strtol(fld, (char **)NULL, 10);
	if (errno != 0)
		return (-1);

	return ((int)n);
}

/*
 * Step through two version strings that look like postive ints delimited by
 * decimals and compare them.  Example input can look like 2, 010.3, 75.02.09,
 * etc.  If the input does not look like this then we do a simple lexical
 * comparison of the two strings.  The string can be modified on exit of
 * this function.
 */
static int
fld_cmp(char *v1, char *v2)
{
	char *nxtfld1, *nxtfld2;
	int n1, n2;

	for (;;) {
		n1 = fld2num(v1, &nxtfld1);
		n2 = fld2num(v2, &nxtfld2);

		/*
		 * If either field is not a postive int, just compare them
		 * lexically.
		 */
		if (n1 < 0 || n2 < 0)
			return (strcmp(v1, v2));

		if (n1 > n2)
			return (1);

		if (n1 < n2)
			return (-1);

		/* They're equal */

		/* No more fields */
		if (nxtfld1 == NULL && nxtfld2 == NULL)
			return (0);

		/* Field 2 still has data so it is greater than field 1 */
		if (nxtfld1 == NULL)
			return (-1);

		/* Field 1 still has data so it is greater than field 2 */
		if (nxtfld2 == NULL)
			return (1);

		/* Both fields still have data, keep going. */
		v1 = nxtfld1;
		v2 = nxtfld2;
	}
}

/*
 * The result of the comparison is returned in the cmp parameter:
 *	 0 if both versions are equal.
 *	<0 if version1 is less than version 2.
 *	>0 if version1 is greater than version 2.
 * The function returns B_TRUE if there was an ENOMEM error, B_FALSE otherwise.
 *
 * This function handles the various version strings we can get from the
 * dependent pkg versions.  They usually look like:
 *	"1.21,REV=2005.01.17.23.31"
 *	"2.6.0,REV=10.0.3.2004.12.16.18.02"
 *
 * We can't do a simple lexical comparison since:
 *      2.6.0 would be greater than 2.20.0
 *	12 would be greater than 110
 *
 * If the input strings do not look like decimal delimted version strings
 * then we fall back to doing a simple lexical comparison.
 */
static boolean_t
pkg_vers_cmp(char *vers1, char *vers2, int *cmp)
{
	char *v1, *v2;
	char *rev1, *rev2;
	int res;

	/* We need to modify the input strings so we dup them. */
	if ((v1 = strdup(vers1)) == NULL)
		return (B_TRUE);
	if ((v2 = strdup(vers2)) == NULL) {
		free(v1);
		return (B_TRUE);
	}

	/* Strip off a revision delimited by a comma. */
	if ((rev1 = strchr(v1, ',')) != NULL)
		*rev1++ = '\0';
	if ((rev2 = strchr(v2, ',')) != NULL)
		*rev2++ = '\0';

	res = fld_cmp(v1, v2);
	/* If the primary versions are not equal, return the result */
	if (res != 0) {
		*cmp = res;
		goto done;
	}

	/*
	 * All of the fields in the primary version strings are equal, check
	 * the rev, if it exists.
	 */

	/* No revs */
	if (rev1 == NULL && rev2 == NULL) {
		*cmp = 0;
		goto done;
	}

	/* Field 2 has a rev so it is greater than field 1 */
	if (rev1 == NULL) {
		*cmp = -1;
		goto done;
	}

	/* Field 1 has a rev so it is greater than field 2 */
	if (rev2 == NULL) {
		*cmp = 1;
		goto done;
	}

	/* If no recognized REV data then just lexically compare them */
	if (strncmp(rev1, "REV=", 4) != 0 || strncmp(rev2, "REV=", 4) != 0) {
		*cmp = strcmp(rev1, rev2);
		goto done;
	}

	/* Both fields have revs, check them. */
	*cmp = fld_cmp(rev1 + 4, rev2 + 4);

done:
	free(v1);
	free(v2);

	return (B_FALSE);
}

/*
 * Walk all of the patches on the pkg, looking to see if the specified patch
 * has been obsoleted by one of those patches.
 */
static boolean_t
is_obsolete(zone_pkg_entry_t *pkg, zone_pkg_entry_t *patchid)
{
	uu_avl_walk_t	*patch_walk;
	zone_pkg_entry_t *patch;
	boolean_t res;

	if (pkg->zpe_patches_avl == NULL)
		return (B_FALSE);

	patch_walk = uu_avl_walk_start(pkg->zpe_patches_avl, UU_WALK_ROBUST);
	if (patch_walk == NULL)
		return (B_FALSE);

	res = B_FALSE;
	while ((patch = uu_avl_walk_next(patch_walk)) != NULL) {
		uu_avl_index_t where;

		if (patch->zpe_patches_avl == NULL)
			continue;

		/* Check the obsolete list on the patch. */
		if (uu_avl_find(patch->zpe_patches_avl, patchid, NULL, &where)
		    != NULL) {
			res = B_TRUE;
			break;
		}
	}

	uu_avl_walk_end(patch_walk);
	return (res);
}

/*
 * Build a list of unique patches from the input pkg_patches list.
 * If the pkg parameter is not null then we will check the patches on that
 * pkg to see if any of the pkg_patches have been obsoleted.  We don't
 * add those obsoleted patches to the unique list.
 * Returns B_FALSE if an error occurs.
 */
static boolean_t
add_patch(uu_avl_t *pkg_patches, uu_avl_t *unique, zone_pkg_entry_t *pkg,
    uu_avl_pool_t *pkg_pool)
{
	uu_avl_walk_t	*walk;
	zone_pkg_entry_t *pkg_patch;

	if (pkg_patches == NULL)
		return (B_TRUE);

	walk = uu_avl_walk_start(pkg_patches, UU_WALK_ROBUST);
	if (walk == NULL)
		return (B_FALSE);

	while ((pkg_patch = uu_avl_walk_next(walk)) != NULL) {
		uu_avl_index_t where;
		zone_pkg_entry_t *patch;

		/* Skip adding it if we already have it. */
		if (uu_avl_find(unique, pkg_patch, NULL, &where) != NULL)
			continue;

		/* Likewise, skip adding it if it has been obsoleted. */
		if (pkg != NULL && is_obsolete(pkg, pkg_patch))
			continue;

		/* We need to add it so make a duplicate. */
		if ((patch = (zone_pkg_entry_t *)
		    malloc(sizeof (zone_pkg_entry_t))) == NULL) {
			uu_avl_walk_end(walk);
			return (B_FALSE);
		}

		if ((patch->zpe_name = strdup(pkg_patch->zpe_name)) == NULL) {
			free(patch);
			uu_avl_walk_end(walk);
			return (B_FALSE);
		}
		if ((patch->zpe_vers = strdup(pkg_patch->zpe_vers)) == NULL) {
			free(patch->zpe_name);
			free(patch);
			uu_avl_walk_end(walk);
			return (B_FALSE);
		}
		patch->zpe_patches_avl = NULL;

		/* Insert patch into the unique patch AVL tree. */
		uu_avl_node_init(patch, &patch->zpe_entry, pkg_pool);
		uu_avl_insert(unique, patch, where);
	}
	uu_avl_walk_end(walk);

	return (B_TRUE);
}

/*
 * Common code for sw_cmp which will check flags, update res and print the
 * section header.  Return true if we should be silent.
 */
static boolean_t
prt_header(int *res, uint_t flag, boolean_t *do_header, char *hdr)
{
	*res = Z_ERR;
	if (flag & SW_CMP_SILENT)
		return (B_TRUE);

	if (*do_header) {
		/* LINTED E_SEC_PRINTF_VAR_FMT */
		(void) fprintf(stderr, hdr);
		*do_header = B_FALSE;
	}
	return (B_FALSE);
}

/*
 * Compare the software on the local global zone and source system global
 * zone.  Used when we are trying to attach a zone during migration or
 * when checking if a ZFS snapshot is still usable for a ZFS clone.
 * l_handle is for the local system and s_handle is for the source system.
 * These have a snapshot of the appropriate packages and patches in the global
 * zone for the two machines.
 * The functions called here can print any messages that are needed to
 * inform the user about package or patch problems.
 * The flag parameter controls how the messages are printed.  If the
 * SW_CMP_SILENT bit is set in the flag then no messages will be printed
 * but we still compare the sw and return an error if there is a mismatch.
 */
static int
sw_cmp(zone_dochandle_t l_handle, zone_dochandle_t s_handle, uint_t flag)
{
	char		*hdr;
	int		res;
	int		err;
	boolean_t	do_header;
	uu_avl_pool_t	*pkg_pool = NULL;
	uu_avl_t	*src_pkgs = NULL;
	uu_avl_t	*dst_pkgs = NULL;
	uu_avl_t	*src_patches = NULL;
	uu_avl_t	*dst_patches = NULL;
	zone_pkg_entry_t *src_pkg;
	zone_pkg_entry_t *dst_pkg;
	zone_pkg_entry_t *src_patch;
	zone_pkg_entry_t *dst_patch;
	uu_avl_walk_t	*walk;

	/* Set res to cover any of these memory allocation errors. */
	res = Z_NOMEM;
	if ((pkg_pool = uu_avl_pool_create("pkgs_pool",
	    sizeof (zone_pkg_entry_t), offsetof(zone_pkg_entry_t, zpe_entry),
	    pkg_entry_compare, UU_DEFAULT)) == NULL)
		goto done;

	if ((src_pkgs = uu_avl_create(pkg_pool, NULL, UU_DEFAULT)) == NULL)
		goto done;

	if ((dst_pkgs = uu_avl_create(pkg_pool, NULL, UU_DEFAULT)) == NULL)
		goto done;

	if ((src_patches = uu_avl_create(pkg_pool, NULL, UU_DEFAULT)) == NULL)
		goto done;

	if ((dst_patches = uu_avl_create(pkg_pool, NULL, UU_DEFAULT)) == NULL)
		goto done;

	res = Z_OK;
	if ((err = zonecfg_getpkgdata(s_handle, pkg_pool, src_pkgs)) != Z_OK) {
		res = errno = err;
		zperror(gettext("could not get package data for detached zone"),
		    B_TRUE);
		goto done;
	}
	if ((err = zonecfg_getpkgdata(l_handle, pkg_pool, dst_pkgs)) != Z_OK) {
		res = errno = err;
		zperror(gettext("could not get package data for global zone"),
		    B_TRUE);
		goto done;
	}

	/*
	 * Check the source host for pkgs (and versions) that are not on the
	 * local host.
	 */
	hdr = gettext("These packages installed on the source system "
	    "are inconsistent with this system:\n");
	do_header = B_TRUE;

	if ((walk = uu_avl_walk_start(src_pkgs, UU_WALK_ROBUST)) == NULL) {
		res = Z_NOMEM;
		goto done;
	}
	while ((src_pkg = uu_avl_walk_next(walk)) != NULL) {
		int cmp;
		uu_avl_index_t where;

		dst_pkg = uu_avl_find(dst_pkgs, src_pkg, NULL, &where);

		/*
		 * Build up a list of unique patches for the src system but
		 * don't track patches that are obsoleted on the dst system
		 * since they don't matter.
		 */
		if (!add_patch(src_pkg->zpe_patches_avl, src_patches, dst_pkg,
		    pkg_pool)) {
			res = Z_NOMEM;
			goto done;
		}

		if (dst_pkg == NULL) {
			/* src pkg is not installed on dst */
			if (prt_header(&res, flag, &do_header, hdr))
				break;

			(void) fprintf(stderr,
			    gettext("\t%s: not installed\n\t\t(%s)\n"),
			    src_pkg->zpe_name, src_pkg->zpe_vers);
			continue;
		}

		/* Check pkg version */
		if (pkg_vers_cmp(src_pkg->zpe_vers, dst_pkg->zpe_vers, &cmp)) {
			res = Z_NOMEM;
			goto done;
		}

		if (cmp != 0) {
			if (prt_header(&res, flag, &do_header, hdr))
				break;

			(void) fprintf(stderr, gettext(
			    "\t%s: version mismatch\n\t\t(%s)\n\t\t(%s)\n"),
			    src_pkg->zpe_name, src_pkg->zpe_vers,
			    dst_pkg->zpe_vers);
		}
	}
	uu_avl_walk_end(walk);

	/*
	 * Now check the local host for pkgs that were not on the source host.
	 * We already handled version mismatches in the loop above.
	 */
	hdr = gettext("These packages installed on this system were "
	    "not installed on the source system:\n");
	do_header = B_TRUE;

	if ((walk = uu_avl_walk_start(dst_pkgs, UU_WALK_ROBUST)) == NULL) {
		res = Z_NOMEM;
		goto done;
	}
	while ((dst_pkg = uu_avl_walk_next(walk)) != NULL) {
		uu_avl_index_t where;

		/*
		 * Build up a list of unique patches for the dst system.  We
		 * don't worry about tracking obsolete patches that were on the
		 * src since we only want to report the results of moving to
		 * the dst system.
		 */
		if (!add_patch(dst_pkg->zpe_patches_avl, dst_patches, NULL,
		    pkg_pool)) {
			res = Z_NOMEM;
			goto done;
		}

		src_pkg = uu_avl_find(src_pkgs, dst_pkg, NULL, &where);
		if (src_pkg == NULL) {
			/* dst pkg is not installed on src */
			if (prt_header(&res, flag, &do_header, hdr))
				break;

			(void) fprintf(stderr, gettext("\t%s (%s)\n"),
			    dst_pkg->zpe_name, dst_pkg->zpe_vers);
		}
	}
	uu_avl_walk_end(walk);

	/*
	 * Check the source host for patches that are not on the local host.
	 */
	hdr = gettext("These patches installed on the source system "
	    "are inconsistent with this system:\n");
	do_header = B_TRUE;

	if ((walk = uu_avl_walk_start(src_patches, UU_WALK_ROBUST)) == NULL) {
		res = Z_NOMEM;
		goto done;
	}
	while ((src_patch = uu_avl_walk_next(walk)) != NULL) {
		uu_avl_index_t where;

		dst_patch = uu_avl_find(dst_patches, src_patch, NULL, &where);
		if (dst_patch == NULL) {
			/* src patch is not installed on dst */
			if (prt_header(&res, flag, &do_header, hdr))
				break;

			(void) fprintf(stderr,
			    gettext("\t%s-%s: not installed\n"),
			    src_patch->zpe_name, src_patch->zpe_vers);
			continue;
		}

		/*
		 * Check patch version.  We assume the patch versions are
		 * properly structured with a leading 0 if necessary (e.g. 01).
		 */
		assert(strlen(src_patch->zpe_vers) ==
		    strlen(dst_patch->zpe_vers));
		if (strcmp(src_patch->zpe_vers, dst_patch->zpe_vers) != 0) {
			if (prt_header(&res, flag, &do_header, hdr))
				break;

			(void) fprintf(stderr,
			    gettext("\t%s: version mismatch\n\t\t(%s) (%s)\n"),
			    src_patch->zpe_name, src_patch->zpe_vers,
			    dst_patch->zpe_vers);
		}
	}
	uu_avl_walk_end(walk);

	/*
	 * Check the local host for patches that were not on the source host.
	 * We already handled version mismatches in the loop above.
	 */
	hdr = gettext("These patches installed on this system were "
	    "not installed on the source system:\n");
	do_header = B_TRUE;

	if ((walk = uu_avl_walk_start(dst_patches, UU_WALK_ROBUST)) == NULL) {
		res = Z_NOMEM;
		goto done;
	}
	while ((dst_patch = uu_avl_walk_next(walk)) != NULL) {
		uu_avl_index_t where;

		src_patch = uu_avl_find(src_patches, dst_patch, NULL, &where);
		if (src_patch == NULL) {
			/* dst patch is not installed on src */
			if (prt_header(&res, flag, &do_header, hdr))
				break;

			(void) fprintf(stderr, gettext("\t%s-%s\n"),
			    dst_patch->zpe_name, dst_patch->zpe_vers);
		}
	}
	uu_avl_walk_end(walk);

done:
	if (res == Z_NOMEM)
		zerror(gettext("Out of memory"));

	/* free avl structs */
	pkg_avl_delete(src_pkgs);
	pkg_avl_delete(dst_pkgs);
	pkg_avl_delete(src_patches);
	pkg_avl_delete(dst_patches);
	if (pkg_pool != NULL)
		uu_avl_pool_destroy(pkg_pool);

	return (res);
}

/*
 * Compare the software on the local global zone and source system global
 * zone.  Used to determine if/how we have to update the zone during attach.
 * We generate the data files needed by the update process in this case.
 * l_handle is for the local system and s_handle is for the source system.
 * These have a snapshot of the appropriate packages and patches in the global
 * zone for the two machines.
 *
 * The algorithm we use to compare the pkgs is as follows:
 * 1) pkg on src but not on dst
 *	remove src pkg (allowed in order to handle obsolete pkgs - note that
 *	this only applies to dependent pkgs, not generic pkgs installed into
 *	the zone by the zone admin)
 * 2) pkg on dst but not on src
 *	add pkg
 * 3) pkg on src with higher rev than on dst
 *	fail (downgrade)
 * 4) pkg on dst with higher rev than on src
 *	remove src pkg & add new
 * 5) pkg version is the same
 *	a) patch on src but not on dst
 *		fail (downgrade, unless obsoleted)
 *	b) patch on dst but not on src
 *		remove src pkg & add new
 *	c) patch on src with higher rev than on dst
 *		fail (downgrade, unless obsoleted)
 *	d) patch on dst with higher rev than on src
 *		remove src pkg & add new
 *
 * We run this algorithm in 2 passes, first looking at the pkgs from the src
 * system and then looking at the pkgs from the dst system.
 *
 * As with the sw_cmp function, we return Z_OK if there is no work to be
 * done (the attach can just happen) or Z_ERR if we have to update the pkgs
 * within the zone.  We can also return Z_FATAL if we had a real error during
 * this process.
 */
static int
sw_up_to_date(zone_dochandle_t l_handle, zone_dochandle_t s_handle)
{
	int		res = Z_OK;
	int		err;
	int		cmp;
	FILE		*fp_add = NULL, *fp_rm = NULL;
	uu_avl_pool_t	*pkg_pool = NULL;
	uu_avl_t	*src_pkgs = NULL;
	uu_avl_t	*dst_pkgs = NULL;
	uu_avl_walk_t	*walk;
	zone_pkg_entry_t *src_pkg;
	zone_pkg_entry_t *dst_pkg;
	char		fname[MAXPATHLEN];
	boolean_t	failed = B_FALSE;

	(void) snprintf(fname, sizeof (fname), "%s/pkg_add", zonepath);
	if ((fp_add = fopen(fname, "w")) == NULL) {
		zperror(gettext("could not save list of packages to add"),
		    B_FALSE);
		goto fatal;
	}

	(void) snprintf(fname, sizeof (fname), "%s/pkg_rm", zonepath);
	if ((fp_rm = fopen(fname, "w")) == NULL) {
		zperror(gettext("could not save list of packages to remove"),
		    B_FALSE);
		goto fatal;
	}

	if ((pkg_pool = uu_avl_pool_create("pkgs_pool",
	    sizeof (zone_pkg_entry_t), offsetof(zone_pkg_entry_t, zpe_entry),
	    pkg_entry_compare, UU_DEFAULT)) == NULL)
		goto fatal;

	if ((src_pkgs = uu_avl_create(pkg_pool, NULL, UU_DEFAULT)) == NULL)
		goto fatal;

	if ((dst_pkgs = uu_avl_create(pkg_pool, NULL, UU_DEFAULT)) == NULL)
		goto fatal;

	if ((err = zonecfg_getpkgdata(s_handle, pkg_pool, src_pkgs)) != Z_OK) {
		errno = err;
		zperror(gettext("could not get package data for detached zone"),
		    B_TRUE);
		goto fatal;
	}
	if ((err = zonecfg_getpkgdata(l_handle, pkg_pool, dst_pkgs)) != Z_OK) {
		errno = err;
		zperror(gettext("could not get package data for global zone"),
		    B_TRUE);
		goto fatal;
	}

	/*
	 * First Pass
	 *
	 * Start by checking each pkg from the src system.  We need to handle
	 * the following:
	 *	1) pkg on src but not on dst
	 *		rm old pkg (allowed in order to handle obsolete pkgs)
	 *	3) pkg on src with higher rev than on dst
	 *		fail (downgrade)
	 *	5) pkg ver same
	 *		a) patch on src but not on dst
	 *			fail (downgrade)
	 *		c) patch on src with higher rev than on dst
	 *			fail (downgrade)
	 */
	if ((walk = uu_avl_walk_start(src_pkgs, UU_WALK_ROBUST)) == NULL) {
		zerror(gettext("Out of memory"));
		goto fatal;
	}

	while ((src_pkg = uu_avl_walk_next(walk)) != NULL) {
		uu_avl_index_t where;
		uu_avl_walk_t	*patch_walk;
		zone_pkg_entry_t *src_patch;

		dst_pkg = uu_avl_find(dst_pkgs, src_pkg, NULL, &where);

		if (dst_pkg == NULL) {
			/* src pkg is not installed on dst */
			if (fprintf(fp_rm, "%s\n", src_pkg->zpe_name) < 0) {
				zperror(gettext("could not save list of "
				    "packages to remove"), B_FALSE);
				goto fatal;
			}
			res = Z_ERR;
			continue;
		}

		/* Check pkg version to determine how to proceed. */
		if (pkg_vers_cmp(src_pkg->zpe_vers, dst_pkg->zpe_vers, &cmp)) {
			zerror(gettext("Out of memory"));
			goto fatal;
		}

		if (cmp > 0) {
			/* src pkg has higher vers than dst pkg */
			zerror(gettext("ERROR: attempt to downgrade package "
			    "%s %s to version %s"), src_pkg->zpe_name,
			    src_pkg->zpe_vers, dst_pkg->zpe_vers);
			failed = B_TRUE;
			continue;
		}

		/*
		 * src pkg has lower vers than dst pkg, we'll handle
		 * this in the loop where we process the dst pkgs.
		 */
		if (cmp < 0)
			continue;

		/* src and dst pkgs have the same version. */

		/*
		 * If src pkg has no patches, then we're done with this pkg.
		 * Any patches on the dst pkg are handled in the 2nd pass.
		 */
		if (src_pkg->zpe_patches_avl == NULL)
			continue;

		if (dst_pkg->zpe_patches_avl == NULL) {
			/*
			 * We have the same pkg on the src and dst but the src
			 * pkg has patches and the dst pkg does not, so this
			 * would be a downgrade!  Disallow this.
			 */
			zerror(gettext("ERROR: attempt to downgrade package "
			    "%s, the source had patches but this system does "
			    "not.  Patches:"), src_pkg->zpe_name);
			failed = B_TRUE;

			patch_walk = uu_avl_walk_start(src_pkg->zpe_patches_avl,
			    UU_WALK_ROBUST);
			if (patch_walk == NULL) {
				zerror(gettext("Out of memory"));
				goto fatal;
			}

			while ((src_patch = uu_avl_walk_next(patch_walk))
			    != NULL) {
				(void) fprintf(stderr, "\t%s-%s\n",
				    src_patch->zpe_name, src_patch->zpe_vers);
			}

			uu_avl_walk_end(patch_walk);
			continue;
		}

		patch_walk = uu_avl_walk_start(src_pkg->zpe_patches_avl,
		    UU_WALK_ROBUST);
		if (patch_walk == NULL) {
			zerror(gettext("Out of memory"));
			goto fatal;
		}

		while ((src_patch = uu_avl_walk_next(patch_walk)) != NULL) {
			zone_pkg_entry_t *dst_patch;

			dst_patch = uu_avl_find(dst_pkg->zpe_patches_avl,
			    src_patch, NULL, &where);

			if (dst_patch == NULL) {
				/*
				 * We have the same pkg on the src and dst but
				 * the src pkg has a patch that the dst pkg
				 * does not, so this would be a downgrade!  We
				 * need to disallow this but first double check
				 * that this patch has not been obsoleted by
				 * some other patch that is installed on the
				 * dst.  If the patch is obsolete, the pkg will
				 * be handled in the 2nd pass.
				 */
				if (is_obsolete(dst_pkg, src_patch))
					continue;

				zerror(gettext("ERROR: attempt to downgrade "
				    "package %s, the source had patch %s-%s "
				    "which is not installed on this system\n"),
				    src_pkg->zpe_name, src_patch->zpe_name,
				    src_patch->zpe_vers);

				failed = B_TRUE;
				continue;
			}

			/* Check if the src patch is newer than the dst patch */
			if (strcmp(src_patch->zpe_vers, dst_patch->zpe_vers)
			    > 0) {
				/*
				 * We have a patch on the src with higher rev
				 * than the patch on the dst so this would be a
				 * downgrade!  We need to disallow this but
				 * first double check that this patch has not
				 * been obsoleted by some other patch that is
				 * installed on the dst.  If the patch is
				 * obsolete, the pkg will be handled in the 2nd
				 * pass.
				 */
				if (is_obsolete(dst_pkg, src_patch))
					continue;

				zerror(gettext("ERROR: attempt to downgrade "
				    "package %s, the source had patch %s-%s "
				    "but this system only has %s-%s\n"),
				    src_pkg->zpe_name, src_patch->zpe_name,
				    src_patch->zpe_vers, dst_patch->zpe_name,
				    dst_patch->zpe_vers);

				failed = B_TRUE;
				continue;
			}

			/*
			 * If the src patch is the same rev or older than the
			 * dst patch we'll handle that in the second pass.
			 */
		}

		uu_avl_walk_end(patch_walk);
	}

	uu_avl_walk_end(walk);

	if (failed)
		goto fatal;

	/*
	 * Second Pass
	 *
	 * Now check each pkg from the dst system.  We need to handle
	 * the following:
	 *	2) pkg on dst but not on src
	 *		add pkg
	 *	4) pkg on dst with higher rev than on src
	 *		remove old pkg & add current
	 *	5) pkg ver same
	 *		b) patch on dst but not on src
	 *			remove old pkg & add
	 *		d) patch on dst with higher rev than on src
	 *			remove old pkg & add
	 */
	if ((walk = uu_avl_walk_start(dst_pkgs, UU_WALK_ROBUST)) == NULL) {
		zerror(gettext("Out of memory"));
		goto fatal;
	}

	while ((dst_pkg = uu_avl_walk_next(walk)) != NULL) {
		uu_avl_index_t where;
		uu_avl_walk_t	*patch_walk;
		zone_pkg_entry_t *dst_patch;

		src_pkg = uu_avl_find(src_pkgs, dst_pkg, NULL, &where);

		if (src_pkg == NULL) {
			/* dst pkg was not installed on src */
			if (fprintf(fp_add, "%s\n", dst_pkg->zpe_name) < 0) {
				zperror(gettext("could not save list of "
				    "packages to add"), B_FALSE);
				goto fatal;
			}
			res = Z_ERR;
			continue;
		}

		/* Check pkg version to determine how to proceed. */
		if (pkg_vers_cmp(dst_pkg->zpe_vers, src_pkg->zpe_vers, &cmp)) {
			zerror(gettext("Out of memory"));
			goto fatal;
		}

		if (cmp > 0) {
			/* dst pkg has higher vers than src pkg */
			if (fprintf(fp_rm, "%s\n", dst_pkg->zpe_name) < 0) {
				zperror(gettext("could not save list of "
				    "packages to remove"), B_FALSE);
				goto fatal;
			}
			if (fprintf(fp_add, "%s\n", dst_pkg->zpe_name) < 0) {
				zperror(gettext("could not save list of "
				    "packages to add"), B_FALSE);
				goto fatal;
			}
			res = Z_ERR;
			continue;
		}

		/*
		 * cmp < 0 was handled in the first loop.  This would
		 * be a downgrade so we should have already failed.
		 */
		assert(cmp >= 0);

		/* src and dst pkgs have the same version. */

		/* If dst pkg has no patches, then we're done with this pkg. */
		if (dst_pkg->zpe_patches_avl == NULL)
			continue;

		if (src_pkg->zpe_patches_avl == NULL) {
			/*
			 * We have the same pkg on the src and dst
			 * but the dst pkg has patches and the src
			 * pkg does not.   Just replace the pkg.
			 *
			 * We don't want to actually add the original pkg
			 * to the list of pkgs to remove here since the
			 * pkginstall command relies on the presence of the
			 * installed pkg with the same version number to set
			 * the UPDATE environment variable for the Class Action
			 * Scripts.  Instead, we just re-install the pkg so the
			 * right thing happens.
			 */
			if (fprintf(fp_add, "%s\n", dst_pkg->zpe_name) < 0) {
				zperror(gettext("could not save list of "
				    "packages to add"), B_FALSE);
				goto fatal;
			}
			res = Z_ERR;
			continue;
		}

		patch_walk = uu_avl_walk_start(dst_pkg->zpe_patches_avl,
		    UU_WALK_ROBUST);
		if (patch_walk == NULL) {
			zerror(gettext("Out of memory"));
			goto fatal;
		}

		while ((dst_patch = uu_avl_walk_next(patch_walk)) != NULL) {
			zone_pkg_entry_t *src_patch;

			src_patch = uu_avl_find(src_pkg->zpe_patches_avl,
			    dst_patch, NULL, &where);

			if (src_patch == NULL) {
				/*
				 * We have the same pkg on the src and dst but
				 * the dst pkg has a patch that the src pkg
				 * does not.  Just replace the pkg.
				 *
				 * See the comment above for why we don't want
				 * to actually remove the pkg.
				 */
				if (fprintf(fp_add, "%s\n", dst_pkg->zpe_name)
				    < 0) {
					zperror(gettext("could not save list "
					    "of packages to add"), B_FALSE);
					goto fatal;
				}
				res = Z_ERR;
				continue;
			}

			/* Check if the dst patch is newer than the src patch */
			if (strcmp(dst_patch->zpe_vers, src_patch->zpe_vers)
			    > 0) {
				/*
				 * We have a patch on the dst with higher rev
				 * than the patch on the src.  Just replace the
				 * pkg.
				 *
				 * See the comment above for why we don't want
				 * to actually remove the pkg.
				 */
				if (fprintf(fp_add, "%s\n", dst_pkg->zpe_name)
				    < 0) {
					zperror(gettext("could not save list "
					    "of packages to add"), B_FALSE);
					goto fatal;
				}
				res = Z_ERR;
				continue;
			}

			/*
			 * If the dst patch is the same rev then we can ignore
			 * this pkg.  If it is older than the src patch we
			 * handled that in the first pass and we should have
			 * already failed.
			 */
			assert(strcmp(dst_patch->zpe_vers, src_patch->zpe_vers)
			    >= 0);
		}

		uu_avl_walk_end(patch_walk);
	}

	uu_avl_walk_end(walk);

	if (fclose(fp_add) != 0) {
		zperror(gettext("could not save list of packages to add"),
		    B_FALSE);
		goto fatal;
	}
	fp_add = NULL;
	if (fclose(fp_rm) != 0) {
		zperror(gettext("could not save list of packages to remove"),
		    B_FALSE);
		goto fatal;
	}

	/* free avl structs */
	pkg_avl_delete(src_pkgs);
	pkg_avl_delete(dst_pkgs);
	uu_avl_pool_destroy(pkg_pool);

	return (res);

fatal:
	/* free avl structs */
	pkg_avl_delete(src_pkgs);
	pkg_avl_delete(dst_pkgs);
	if (pkg_pool != NULL)
		uu_avl_pool_destroy(pkg_pool);

	if (fp_add != NULL)
		(void) fclose(fp_add);
	if (fp_rm != NULL)
		(void) fclose(fp_rm);

	/* clean up data files left behind */
	(void) snprintf(fname, sizeof (fname), "%s/pkg_add", zonepath);
	(void) unlink(fname);
	(void) snprintf(fname, sizeof (fname), "%s/pkg_rm", zonepath);
	(void) unlink(fname);

	return (Z_FATAL);
}

/*
 * During attach we go through and fix up the /dev entries for the zone
 * we are attaching.  In order to regenerate /dev with the correct devices,
 * the old /dev will be removed, the zone readied (which generates a new
 * /dev) then halted, then we use the info from the manifest to update
 * the modes, owners, etc. on the new /dev.
 */
static int
dev_fix(char *zonename, zone_dochandle_t handle)
{
	int			err;
	int			status;
	struct zone_devpermtab	devtab;
	zone_cmd_arg_t		zarg;
	char			devpath[MAXPATHLEN];
				/* 6: "exec " and " " */
	char			cmdbuf[sizeof (RMCOMMAND) + MAXPATHLEN + 6];

	if (snprintf(devpath, sizeof (devpath), "%s/dev", zonepath)
	    >= sizeof (devpath))
		return (Z_TOO_BIG);

	/*
	 * "exec" the command so that the returned status is that of
	 * RMCOMMAND and not the shell.
	 */
	(void) snprintf(cmdbuf, sizeof (cmdbuf), EXEC_PREFIX RMCOMMAND " %s",
	    devpath);
	status = do_subproc(cmdbuf);
	if ((err = subproc_status(RMCOMMAND, status, B_TRUE)) !=
	    ZONE_SUBPROC_OK) {
		(void) fprintf(stderr,
		    gettext("could not remove existing /dev\n"));
		return (Z_ERR);
	}

	/* In order to ready the zone, it must be in the installed state */
	if ((err = zone_set_state(zonename, ZONE_STATE_INSTALLED)) != Z_OK) {
		errno = err;
		zperror(gettext("could not reset state"), B_TRUE);
		return (Z_ERR);
	}

	/* We have to ready the zone to regen the dev tree */
	zarg.cmd = Z_READY;
	if (zonecfg_call_zoneadmd(zonename, &zarg, locale, B_FALSE) != 0) {
		zerror(gettext("call to %s failed"), "zoneadmd");
		/* attempt to restore zone to configured state */
		(void) zone_set_state(zonename, ZONE_STATE_CONFIGURED);
		return (Z_ERR);
	}

	zarg.cmd = Z_HALT;
	if (zonecfg_call_zoneadmd(zonename, &zarg, locale, B_FALSE) != 0) {
		zerror(gettext("call to %s failed"), "zoneadmd");
		/* attempt to restore zone to configured state */
		(void) zone_set_state(zonename, ZONE_STATE_CONFIGURED);
		return (Z_ERR);
	}

	/* attempt to restore zone to configured state */
	(void) zone_set_state(zonename, ZONE_STATE_CONFIGURED);

	if (zonecfg_setdevperment(handle) != Z_OK) {
		(void) fprintf(stderr,
		    gettext("unable to enumerate device entries\n"));
		return (Z_ERR);
	}

	while (zonecfg_getdevperment(handle, &devtab) == Z_OK) {
		int err;

		if ((err = zonecfg_devperms_apply(handle,
		    devtab.zone_devperm_name, devtab.zone_devperm_uid,
		    devtab.zone_devperm_gid, devtab.zone_devperm_mode,
		    devtab.zone_devperm_acl)) != Z_OK && err != Z_INVAL)
			(void) fprintf(stderr, gettext("error updating device "
			    "%s: %s\n"), devtab.zone_devperm_name,
			    zonecfg_strerror(err));

		free(devtab.zone_devperm_acl);
	}

	(void) zonecfg_enddevperment(handle);

	return (Z_OK);
}

/*
 * Check the pkg to see if the patch backout data exists for the specified
 * patch in the "bo" parameter.  Mark the patch if we cannot back it out.
 * Only return false if we hit a fatal error.
 */
static boolean_t
collect_patch_backout(char *pkg_path, bo_patch_node_t *bo)
{
	struct stat buf;
	char backout_path[MAXPATHLEN];
	char backout_data[MAXPATHLEN];

	if (snprintf(backout_path, sizeof (backout_path), "%s/save/%s",
	    pkg_path, bo->patch_num) >= sizeof (backout_path)) {
		zerror(gettext("Package path is too long"));
		return (B_FALSE);
	}

	if (snprintf(backout_data, sizeof (backout_data), "%s/undo.Z",
	    backout_path) >= sizeof (backout_data)) {
		zerror(gettext("Package path is too long"));
		return (B_FALSE);
	}

	if (stat(backout_data, &buf) == 0)
		return (B_TRUE);

	if (snprintf(backout_data, sizeof (backout_data), "%s/undo",
	    backout_path) >= sizeof (backout_data)) {
		zerror(gettext("Package path is too long"));
		return (B_FALSE);
	}

	if (stat(backout_data, &buf) == 0)
		return (B_TRUE);

	if (snprintf(backout_data, sizeof (backout_data), "%s/obsolete.Z",
	    backout_path) >= sizeof (backout_data)) {
		zerror(gettext("Package path is too long"));
		return (B_FALSE);
	}

	if (stat(backout_data, &buf) == 0)
		return (B_TRUE);

	if (snprintf(backout_data, sizeof (backout_data), "%s/obsolete",
	    backout_path) >= sizeof (backout_data)) {
		zerror(gettext("Package path is too long"));
		return (B_FALSE);
	}

	if (stat(backout_data, &buf) == 0)
		return (B_TRUE);

	/* There is no backout data for the patch. */
	bo->can_backout = B_FALSE;
	return (B_TRUE);
}

/*
 * Parse pkg patch_list and check if any of the backout patches are in the pkg.
 * For each backout, collect the data about backing it out.  Only return false
 * if we hit a fatal error.
 */
static boolean_t
collect_pkg_backout(char *pkg_path, char *patch_list, uu_avl_t *backout_list,
    uu_avl_pool_t *backout_pool)
{
	char *tmp;
	char *patch;
	char *lastp;

	if (patch_list == NULL)
		return (B_TRUE);

	for (tmp = patch_list; (patch = strtok_r(tmp, " ", &lastp)) != NULL;
	    tmp = NULL) {
		uu_list_index_t where;
		bo_patch_node_t *found;
		bo_patch_node_t backout;

		backout.patch_num = patch;
		uu_avl_node_init(&backout, &backout.link, backout_pool);

		if ((found = uu_avl_find(backout_list, &backout, NULL, &where))
		    != NULL) {
			found->installed = B_TRUE;
			if (!collect_patch_backout(pkg_path, found))
				return (B_FALSE);
		}
	}

	return (B_TRUE);
}

/*
 * Walk all of the pkgs installed to determine if one of the patches to backout
 * is installed on that pkg.  To figure out what patches are installed we have
 * to look at each pkg, since patch data is stored in individual pkginfo files.
 *
 * Once that is done, the patches to backout will each be flagged so we
 * can verify that the backout list is valid.
 *
 * We're doing this from the global zone, but this is just a sanity check and
 * nothing is being modified.  The actual backout happens in the scratch zone.
 */
static boolean_t
backout_ok(uu_avl_t *backout_list, uu_avl_pool_t *backout_pool)
{
	boolean_t res = B_TRUE;
	struct dirent *dp;
	DIR *dirp;
	uu_avl_walk_t *walk;
	bo_patch_node_t *backout;
	boolean_t do_header = B_TRUE;
	char pkg_dir[MAXPATHLEN];

	if (backout_list == NULL)
		return (B_TRUE);

	if (snprintf(pkg_dir, sizeof (pkg_dir), "%s/root/%s", zonepath,
	    PKG_PATH) >= sizeof (pkg_dir))
		return (B_FALSE);

	if ((dirp = opendir(pkg_dir)) == NULL)
		return (B_FALSE);

	while ((dp = readdir(dirp)) != NULL) {
		char pkgpath[MAXPATHLEN];
		char pkginfo[MAXPATHLEN];
		struct stat buf;
		struct zone_pkginfo info;

		if (strcmp(dp->d_name, ".") == 0 ||
		    strcmp(dp->d_name, "..") == 0)
			continue;

		if (snprintf(pkgpath, sizeof (pkgpath), "%s/%s", pkg_dir,
		    dp->d_name) >= sizeof (pkgpath))
			continue;

		if (snprintf(pkginfo, sizeof (pkginfo), "%s/pkginfo", pkgpath)
		    >= sizeof (pkginfo))
			continue;

		if (stat(pkginfo, &buf) == -1 || !S_ISREG(buf.st_mode))
			continue;

		if (get_pkginfo(pkginfo, &info) != 0) {
			res = B_FALSE;
			break;
		}

		res = collect_pkg_backout(pkgpath, info.zpi_patchlist,
		    backout_list, backout_pool);

		free_pkginfo(&info);

		if (!res)
			break;
	}

	(void) closedir(dirp);

	/* If we had a fatal error in the loop above, we're done. */
	if (!res)
		return (B_FALSE);

	/*
	 * Walk the list of patches to see if any are not installed or cannot
	 * be backed out.
	 */
	if ((walk = uu_avl_walk_start(backout_list, UU_WALK_ROBUST)) == NULL) {
		zerror(gettext("Out of memory"));
		return (B_FALSE);
	}

	while ((backout = uu_avl_walk_next(walk)) != NULL) {
		if (!backout->installed || !backout->can_backout) {
			if (do_header) {
				(void) fprintf(stderr, gettext("The following "
				    "patches cannot be removed:\n"));
				do_header = B_FALSE;
			}
			if (!backout->installed)
				(void) fprintf(stderr,
				    gettext("\t%s is not installed\n"),
				    backout->patch_num);
			else
				(void) fprintf(stderr, gettext("\t%s was "
				    "installed without creating its backout "
				    "data\n"), backout->patch_num);
			res = B_FALSE;
		}
	}

	uu_avl_walk_end(walk);

	return (res);
}

/*
 * Run patchrm in the scratch zone to backout the specified patches.
 * Return B_TRUE if the backout suceeded, otherwise, return B_FALSE.
 */
static boolean_t
backout_patches(uu_avl_t *backout_list)
{
	boolean_t res = B_FALSE;
	uu_avl_walk_t *walk;
	bo_patch_node_t *backout;
	boolean_t mounted = B_FALSE;
	int status;
	int len;
	char *patchrm = "/usr/sbin/zlogin -S %s "
	    "'/usr/bin/date >>/a/var/sadm/system/logs/backout_log; "
	    "/usr/sbin/patchrm -R /a ";
	char *patchlog = " 2>&1 | "
	    "tee -a /a/var/sadm/system/logs/backout_log'\n";
	char *cmdbuf = NULL;
	char *tmp;

	if (backout_list == NULL)
		return (B_TRUE);

	if (!mount_configured_zone(&mounted))
		goto cleanup;

	len = strlen(patchrm) + strlen(zonename) + strlen(patchlog) + 1;
	if ((cmdbuf = (char *)malloc(len)) == NULL) {
		zerror(gettext("Out of memory"));
		goto cleanup;
	}

	/* Walk the list of patches to build the backout command. */
	if ((walk = uu_avl_walk_start(backout_list, UU_WALK_ROBUST)) == NULL) {
		zerror(gettext("Out of memory"));
		goto cleanup;
	}

	(void) printf(gettext("Backing out patches: "));

	/* LINTED E_SEC_PRINTF_VAR_FMT */
	(void) snprintf(cmdbuf, len, patchrm, zonename);
	while ((backout = uu_avl_walk_next(walk)) != NULL) {
		(void) printf("%s ", backout->patch_num);

		len += strlen(backout->patch_num) + 1;
		if ((tmp = (char *)realloc(cmdbuf, len)) == NULL) {
			uu_avl_walk_end(walk);
			zerror(gettext("Out of memory"));
			goto cleanup;
		}
		cmdbuf = tmp;
		(void) strlcat(cmdbuf, backout->patch_num, len);
		(void) strlcat(cmdbuf, " ", len);
	}

	(void) printf("\n");

	uu_avl_walk_end(walk);
	(void) strlcat(cmdbuf, patchlog, len);

	/* Now run the patchrm command within the mounted zone. */
	status = do_subproc(cmdbuf);
	if (subproc_status("patchrm", status, B_TRUE) != ZONE_SUBPROC_OK) {
		zerror(gettext("Backing out the patches failed.\n"
		    "See the /var/sadm/system/logs/backout_log file in the "
		    "zone for details.\n"));
		goto cleanup;
	}

	res = B_TRUE;

cleanup:
	if (cmdbuf != NULL)
		free(cmdbuf);
	if (!unmount_configured_zone(mounted))
		res =  B_FALSE;

	return (res);
}

/* ARGSUSED */
static int
backout_patch_node_compare(const void *l_arg, const void *r_arg, void *private)
{
	bo_patch_node_t *l = (bo_patch_node_t *)l_arg;
	bo_patch_node_t *r = (bo_patch_node_t *)r_arg;

	return (strcmp(l->patch_num, r->patch_num));
}

static boolean_t
init_backout_data(uu_avl_pool_t **backout_pool, uu_avl_t **backout_list)
{
	if ((*backout_pool = uu_avl_pool_create("backout_pool",
	    sizeof (bo_patch_node_t), offsetof(bo_patch_node_t, link),
	    backout_patch_node_compare, UU_DEFAULT)) == NULL)
		return (B_FALSE);

	if ((*backout_list = uu_avl_create(*backout_pool, NULL, UU_DEFAULT))
	    == NULL)
		return (B_FALSE);

	return (B_TRUE);
}

static void
cleanup_backout_data(uu_avl_pool_t *backout_pool, uu_avl_t *backout_list)
{
	if (backout_list != NULL) {
		bo_patch_node_t *p;
		void *cookie = NULL;

		while ((p = uu_avl_teardown(backout_list, &cookie)) != NULL)
			free(p);

		uu_avl_destroy(backout_list);
	}

	if (backout_pool != NULL)
		uu_avl_pool_destroy(backout_pool);
}

static void
attach_usage()
{
	(void) fprintf(stderr, gettext("usage:\t%s brand options: "
	    "[-b patchid]* [-u]\n"), MY_BRAND_NAME);
	(void) fprintf(stderr, gettext("\tSpecify one or more -b patchid "
	    "options to backout the patch from the\n\tzone.\n"));
	(void) fprintf(stderr, gettext("\tSpecify -u to update the zone to "
	    "the current system software.\n"));
}

static int
attach_func(int argc, char *argv[])
{
	int err, arg;
	int res = ZONE_SUBPROC_NOTCOMPLETE;
	zone_dochandle_t handle = NULL;
	zone_dochandle_t athandle = NULL;
	char brand[MAXNAMELEN], atbrand[MAXNAMELEN];
	boolean_t execute = B_TRUE;
	boolean_t update = B_FALSE;
	char *manifest_path;
	char tmp_path[MAXPATHLEN];
	uu_avl_pool_t *backout_pool = NULL;
	uu_avl_t *backout_list = NULL;
	bo_patch_node_t *backout;
	uu_avl_index_t where;

	opterr = 0;
	optind = 0;
	while ((arg = getopt(argc, argv, "?b:Fn:u")) != EOF) {
		switch (arg) {
		case '?':
			if (optopt != '?') {
				(void) fprintf(stderr, gettext("%s brand: "
				    "invalid option: %c\n"), MY_BRAND_NAME,
				    optopt);
			}
			attach_usage();
			res = (optopt == '?' ? ZONE_SUBPROC_OK :
			    ZONE_SUBPROC_USAGE);
			goto done;
		case 'b':
			if (backout_pool == NULL &&
			    !init_backout_data(&backout_pool, &backout_list))
				goto done;
			if ((backout = (bo_patch_node_t *)malloc(
			    sizeof (bo_patch_node_t))) == NULL)
				goto done;

			backout->patch_num = optarg;
			backout->can_backout = B_TRUE;
			backout->installed = B_FALSE;
			uu_avl_node_init(backout, &backout->link, backout_pool);

			if (uu_avl_find(backout_list, backout, NULL, &where)
			    == NULL) {
				(void) uu_avl_insert(backout_list, backout,
				    where);
			} else {
				free(backout);
			}
			break;
		case 'n':
			execute = B_FALSE;
			manifest_path = optarg;
			break;
		case 'u':
			update = B_TRUE;
			break;
		default:
			(void) fprintf(stderr, gettext("%s brand: invalid "
			    "option: %c\n"), MY_BRAND_NAME, optopt);
			res = ZONE_SUBPROC_USAGE;
			goto done;
		}
	}

	if (argc > optind) {
		attach_usage();
		res = ZONE_SUBPROC_USAGE;
		goto done;
	}

	/* dry-run and update flags are mutually exclusive */
	if (!execute && update) {
		(void) fprintf(stderr, gettext("-n and -u flags are mutually "
		    "exclusive\n"));
		res = ZONE_SUBPROC_USAGE;
		goto done;
	}

	/*
	 * If there are any patches to backout, check if each patch was
	 * installed with its backout data.
	 */
	if (!backout_ok(backout_list, backout_pool))
		goto done;

	/*
	 * If the no-execute option was specified, we need to branch down
	 * a completely different path since there is no zone required to be
	 * configured for this option.
	 */
	if (!execute) {
		res = dryrun_attach(manifest_path);
		goto done;
	}

	if ((handle = zonecfg_init_handle()) == NULL) {
		(void) fprintf(stderr, gettext("brand attach program error: "
		    "%s\n"), strerror(errno));
		goto done;
	}

	if ((err = zonecfg_get_handle(zonename, handle)) != Z_OK) {
		(void) fprintf(stderr, gettext("brand attach program error: "
		    "%s\n"), zonecfg_strerror(err));
		zonecfg_fini_handle(handle);
		goto done;
	}

	if ((athandle = zonecfg_init_handle()) == NULL) {
		(void) fprintf(stderr, gettext("brand attach program error: "
		    "%s\n"), strerror(errno));
		goto done;
	}

	/* Get the detach information for the locally defined zone. */
	if ((err = get_detach_info(handle, B_FALSE)) != Z_OK) {
		(void) fprintf(stderr, gettext("getting the attach information "
		    "failed: %s\n"), zonecfg_strerror(err));
		goto done;
	}

	/*
	 * If the zone was previously detached, then we can read its manifest
	 * to check that the detached and locally defined zones are both of
	 * the same brand.
	 */
	if (zonecfg_get_attach_handle(zonepath, ZONE_DETACHED, zonename,
	    B_TRUE, athandle) == Z_OK) {

		if ((zonecfg_get_brand(handle, brand, sizeof (brand)) != 0) ||
		    (zonecfg_get_brand(athandle, atbrand, sizeof (atbrand))
		    != 0)) {
			(void) fprintf(stderr, gettext("missing or invalid "
			    "brand\n"));
			goto done;
		}

		if (strcmp(atbrand, brand) != 0) {
			(void) fprintf(stderr, gettext("Trying to attach a "
			    "'%s' zone to a '%s' configuration.\n"), atbrand,
			    brand);
			goto done;
		}

		zonecfg_fini_handle(athandle);
		if ((athandle = zonecfg_init_handle()) == NULL) {
			(void) fprintf(stderr, gettext("brand attach program "
			    "error: %s\n"), strerror(errno));
			goto done;
		}
	}

	/* Now backout the specified patches. */
	if (!backout_patches(backout_list)) {
		res = ZONE_SUBPROC_FATAL;
		goto done;
	}

	/*
	 * Now that any patches have been backed out, regenerate the zone
	 * detach data.
	 *
	 * Even if no patches were backed out, we want to regenerate the
	 * detach data using the latest software on this target system so
	 * that we take advantage of any bug fixes on this release.
	 */
	if (snprintf(tmp_path, sizeof (tmp_path), "%s/%s", zonepath,
	    TMP_MANIFEST) >= sizeof (tmp_path) ||
	    !gen_detach_info(TMP_MANIFEST)) {
		(void) fprintf(stderr, gettext("Cannot generate the "
		    "information needed to attach this zone.\n"));
		goto done;
	}

	if ((err = zonecfg_get_attach_handle(zonepath, TMP_MANIFEST, zonename,
	    B_TRUE, athandle)) != Z_OK) {
		(void) fprintf(stderr, gettext("brand attach program "
		    "error: %s\n"), zonecfg_strerror(err));
		(void) unlink(tmp_path);
		goto done;
	}

	(void) unlink(tmp_path);

	/*
	 * We set the result to be fatal here.  If we make it through the sw
	 * check and possible update cleanly, then we set the result to OK.
	 * Otherwise, we goto done and the result remains fatal.
	 */
	res = ZONE_SUBPROC_FATAL;

	/*
	 * If we're doing an update on attach, and the zone does need to be
	 * updated, then run the update.
	 */
	if (update) {
		char fname[MAXPATHLEN];

		(void) sigset(SIGINT, sigcleanup);

		if ((err = sw_up_to_date(handle, athandle)) != Z_OK) {
			if (err != Z_FATAL && !attach_interupted)
				err = attach_update(handle);
		}

		(void) sigset(SIGINT, SIG_DFL);

		/*
		 * Clean up data files left behind by sw_up_to_date().
		 * Don't worry if the files don't exist; they might have been
		 * cleaned up by the attach_update() function.
		 */
		(void) snprintf(fname, sizeof (fname), "%s/pkg_add", zonepath);
		(void) unlink(fname);
		(void) snprintf(fname, sizeof (fname), "%s/pkg_rm", zonepath);
		(void) unlink(fname);

		if (attach_interupted || err != Z_OK)
			goto done;

	} else {
		/* sw_cmp prints error msgs as necessary */
		if (sw_cmp(handle, athandle, SW_CMP_NONE) != Z_OK)
			goto done;

		if (dev_fix(zonename, athandle) != Z_OK)
			goto done;
	}

	if ((err = zone_set_state(zonename, ZONE_STATE_INSTALLED)) != Z_OK) {
		(void) fprintf(stderr, gettext("could not reset state: %s\n"),
		    zonecfg_strerror(err));
		goto done;
	}

	res = ZONE_SUBPROC_OK;

done:
	if (handle != NULL)
		zonecfg_fini_handle(handle);
	if (athandle != NULL)
		zonecfg_fini_handle(athandle);
	cleanup_backout_data(backout_pool, backout_list);

	return (res);
}

static boolean_t
add_opts(char *buf, int bsize, char opt, char *arg)
{
	char argbuf[MAXPATHLEN];

	if (snprintf(argbuf, sizeof (argbuf), " -%c '%s'", opt, arg)
	    > sizeof (argbuf) || strlcat(buf, argbuf, bsize) >= bsize) {
		(void) fprintf(stderr, gettext("Command line too long"));
		return (B_TRUE);
	}

	return (B_FALSE);
}

static boolean_t
add_opt(char *buf, int bsize, char opt)
{
	char argbuf[4];

	(void) snprintf(argbuf, sizeof (argbuf), " -%c", opt);
	if (strlcat(buf, argbuf, bsize) >= bsize) {
		(void) fprintf(stderr, gettext("Command line too long"));
		return (B_TRUE);
	}

	return (B_FALSE);
}

static void
install_usage()
{
	(void) fprintf(stderr, gettext("usage:\t%s brand options:\n"
	    "\tWith no options the zone is freshly installed:\n"
	    "\t\tinstall\n"
	    "\tAlternatively, the zone may be installed using an existing\n"
	    "\tsystem image from an archive or a directory:\n"
	    "\t\tinstall -a archive (-u | -p) [-v | -s] [-b patchid]*\n"
	    "\t\tinstall -d directory (-u | -p) [-v | -s] [-b patchid]*\n"),
	    MY_BRAND_NAME);
}

static int
install_func(int argc, char *argv[])
{
	char cmdbuf[NCARGS];
	char argbuf[NCARGS];
	int arg;
	int status;
	boolean_t image_install = B_FALSE;
	char image_install_arg = '\0';

	argbuf[0] = '\0';

	opterr = 0;
	optind = 0;
	while ((arg = getopt(argc, argv, "?a:b:d:psuvx:")) != EOF) {
		switch (arg) {
		case '?':
			if (optopt != '?') {
				(void) fprintf(stderr, gettext("%s brand: "
				    "invalid option: %c\n"), MY_BRAND_NAME,
				    optopt);
			}
			install_usage();
			return (optopt == '?' ? Z_OK : ZONE_SUBPROC_USAGE);

		case 'a':
			if (image_install) {
				install_usage();
				return (ZONE_SUBPROC_USAGE);
			}
			image_install = B_TRUE;
			if (add_opts(argbuf, sizeof (argbuf), arg, optarg))
				return (Z_ERR);
			break;
		case 'b':
			image_install_arg = optopt;
			if (add_opts(argbuf, sizeof (argbuf), arg, optarg))
				return (Z_ERR);
			break;
		case 'd':
			if (image_install) {
				install_usage();
				return (ZONE_SUBPROC_USAGE);
			}
			image_install = B_TRUE;
			if (add_opts(argbuf, sizeof (argbuf), arg, optarg))
				return (Z_ERR);
			break;
		case 'p':
			image_install_arg = optopt;
			if (add_opt(argbuf, sizeof (argbuf), arg))
				return (Z_ERR);
			break;
		case 's':
			image_install_arg = optopt;
			if (add_opt(argbuf, sizeof (argbuf), arg))
				return (Z_ERR);
			break;
		case 'u':
			image_install_arg = optopt;
			if (add_opt(argbuf, sizeof (argbuf), arg))
				return (Z_ERR);
			break;
		case 'v':
			image_install_arg = optopt;
			if (add_opt(argbuf, sizeof (argbuf), arg))
				return (Z_ERR);
			break;
		case 'x':
			if (strcmp(optarg, "nodataset") != 0) {
				(void) fprintf(stderr, gettext("%s brand: "
				    "invalid option: %c\n"), MY_BRAND_NAME,
				    arg);
				return (ZONE_SUBPROC_USAGE);
			}
			/* Ignore option, handled in zoneadm. */
			break;
		default:
			(void) fprintf(stderr, gettext("%s brand: invalid "
			    "option: %c\n"), MY_BRAND_NAME, optopt);
			return (ZONE_SUBPROC_USAGE);
		}
	}

	if (argc > optind) {
		install_usage();
		return (ZONE_SUBPROC_USAGE);
	}

	/*
	 * Either install the zone using a system image or using the
	 * traditional lu support.
	 */
	if (image_install) {
		if (snprintf(cmdbuf, sizeof (cmdbuf),
		    "/usr/lib/brand/native/image_install %s %s %s", zonename,
		    zonepath, argbuf) >= sizeof (cmdbuf))
			return (Z_ERR);

	} else {
		struct stat	stbuf;
		char		rootpath[MAXPATHLEN]; /* zone root path */

		if (snprintf(rootpath, sizeof (rootpath), "%s/root", zonepath)
		    >= sizeof (rootpath)) {
			(void) fprintf(stderr,
			    gettext("Zonepath %s is too long.\n"), zonepath);
			return (Z_ERR);
		}

		if (stat(rootpath, &stbuf) == 0) {
			struct dirent	*dp;
			DIR		*dirp;
			boolean_t	empty = B_TRUE;

			if ((dirp = opendir(rootpath)) == NULL) {
				(void) fprintf(stderr, gettext("Could not "
				    "open rootpath %s\n"), rootpath);
				return (Z_ERR);
			}

			/* Verify that the dir is empty. */
			while ((dp = readdir(dirp)) != NULL) {
				if (strcmp(dp->d_name, ".") == 0 ||
				    strcmp(dp->d_name, "..") == 0)
					continue;

				empty = B_FALSE;
				break;
			}
			(void) closedir(dirp);

			if (!empty) {
				(void) fprintf(stderr, gettext("Zonepath root "
				    "%s exists and contains data; remove or "
				    "move aside prior to install.\n"),
				    rootpath);
				return (Z_ERR);
			}
		}

		if (image_install_arg != '\0') {
			(void) fprintf(stderr, gettext("%s brand: option: %c "
			    "is only valid with -a or -d\n"), MY_BRAND_NAME,
			    image_install_arg);
			return (ZONE_SUBPROC_USAGE);
		}

		if (snprintf(cmdbuf, sizeof (cmdbuf),
		    "/usr/lib/lu/lucreatezone -z %s", zonename) >=
		    sizeof (cmdbuf))
			return (Z_ERR);
	}

	/*
	 * According to the Application Packaging Developer's Guide, a
	 * "checkinstall" script when included in a package is executed as
	 * the user "install", if such a user exists, or by the user
	 * "nobody".  In order to support this dubious behavior, the path
	 * to the zone being constructed is opened up during the life of
	 * the command laying down the zone's root file system.  Once this
	 * has completed, regardless of whether it was successful, the
	 * path to the zone is again restricted.
	 */
	if (chmod(zonepath, DEFAULT_DIR_MODE) != 0) {
		zperror(zonepath, B_FALSE);
		return (Z_ERR);
	}

	status = do_subproc(cmdbuf);

	if (chmod(zonepath, S_IRWXU) != 0) {
		zperror(zonepath, B_FALSE);
		return (Z_ERR);
	}

	if (subproc_status("install", status, B_FALSE) != ZONE_SUBPROC_OK)
		return (Z_ERR);

	return (Z_OK);
}

/* ARGSUSED */
static int
postclone_func(int argc, char *argv[])
{
	int		status;
	boolean_t	res = B_TRUE;
	struct stat	sbuf;
	char		cmdbuf[2 * MAXPATHLEN];

	/* Ignore any arguments. */

	/*
	 * Trusted Extensions requires that cloned zones use the same sysid
	 * configuration, so it is not appropriate to perform any
	 * post-clone reconfiguration.
	 */
	if (is_system_labeled())
		return (ZONE_SUBPROC_OK);

	/* If the zone is already sys-unconfiged, then we're done. */
	if (snprintf(cmdbuf, sizeof (cmdbuf), "%s/root/etc/.UNCONFIGURED",
	    zonepath) >= sizeof (cmdbuf))
		return (ZONE_SUBPROC_FATAL);

	if (stat(cmdbuf, &sbuf) == 0 && S_ISREG(sbuf.st_mode))
		return (ZONE_SUBPROC_OK);

	/*
	 * Mount the zone.  The zone is still in the INCOMPLETE state, so we
	 * have to force mount it.
	 */
	if (mount_func(B_TRUE) != Z_OK)
		return (ZONE_SUBPROC_FATAL);

	/* sys-unconfig the zone */
	if (snprintf(cmdbuf, sizeof (cmdbuf), "/usr/sbin/zlogin -S %s "
	    "'/usr/sbin/sys-unconfig -R /a'", zonename) >= sizeof (cmdbuf)) {
		res = B_FALSE;
	} else {
		status = do_subproc(cmdbuf);
		if (subproc_status("sys-unconfig failed", status, B_TRUE)
		    != ZONE_SUBPROC_OK)
			res = B_FALSE;
	}

	if (unmount_func() != Z_OK)
		res =  B_FALSE;

	return (res ? ZONE_SUBPROC_OK : ZONE_SUBPROC_FATAL);
}

/*
 * Perform any necessary housekeeping tasks we need to do before we take
 * a ZFS snapshot of the zone.  What this really entails is that we are
 * taking a sw inventory of the source zone, like we do when we detach,
 * so that there is the XML manifest in the snapshot.  We use that to
 * validate the snapshot if it is the source of a clone at some later time.
 */
static int
presnap_func(int argc, char *argv[])
{
	int err;
	zone_dochandle_t handle;

	opterr = 0;
	optind = 0;
	if (getopt(argc, argv, "") != EOF)
		return (ZONE_SUBPROC_USAGE);

	if ((handle = zonecfg_init_handle()) == NULL) {
		(void) fprintf(stderr, gettext("brand pre-snapshot program "
		    "error: %s\n"), strerror(errno));
		return (Z_ERR);
	}

	if ((err = zonecfg_get_handle(zonename, handle)) != Z_OK) {
		(void) fprintf(stderr, gettext("brand pre-snapshot program "
		    "error: %s\n"), zonecfg_strerror(err));
		zonecfg_fini_handle(handle);
		return (Z_ERR);
	}

	if ((err = get_detach_info(handle, B_TRUE)) != Z_OK) {
		(void) fprintf(stderr, gettext("brand pre-snapshot program "
		    "error: %s\n"), zonecfg_strerror(err));
		zonecfg_fini_handle(handle);
		return (Z_ERR);
	}

	if ((err = zonecfg_detach_save(handle, 0)) != Z_OK) {
		(void) fprintf(stderr, gettext("saving the detach manifest "
		    "failed: %s\n"), zonecfg_strerror(err));
		zonecfg_fini_handle(handle);
		return (Z_ERR);
	}

	zonecfg_fini_handle(handle);

	return (Z_OK);
}

/*
 * Perform any necessary housekeeping tasks we need to do after we take
 * a ZFS snapshot of the zone.  What this really entails is removing the
 * sw inventory XML file from the zone.  It is still in the snapshot where
 * we want it, but we don't want it in the source zone itself.
 */
static int
postsnap_func(int argc, char *argv[])
{
	int err;
	zone_dochandle_t handle;

	opterr = 0;
	optind = 0;
	if (getopt(argc, argv, "") != EOF)
		return (ZONE_SUBPROC_USAGE);

	if ((handle = zonecfg_init_handle()) == NULL) {
		(void) fprintf(stderr, gettext("brand post-snapshot program "
		    "error: %s\n"), strerror(errno));
		return (Z_ERR);
	}

	if ((err = zonecfg_get_handle(zonename, handle)) != Z_OK) {
		(void) fprintf(stderr, gettext("brand post-snapshot program "
		    "error: %s\n"), zonecfg_strerror(err));
		zonecfg_fini_handle(handle);
		return (Z_ERR);
	}

	zonecfg_rm_detached(handle, B_FALSE);
	zonecfg_fini_handle(handle);

	return (Z_OK);
}

/*
 * We are using an explicit snapshot from some earlier point in time so
 * we need to validate it.  This involves checking the sw inventory that
 * we took when we made the snapshot to verify that the current sw config
 * on the host is still valid to run a zone made from this snapshot.
 */
static int
validatesnap_func(int argc, char *argv[])
{
	int err;
	zone_dochandle_t handle;
	zone_dochandle_t athandle = NULL;
	char *snapshot_name;
	char *snap_path;

	opterr = 0;
	optind = 0;
	if (getopt(argc, argv, "") != EOF)
		return (ZONE_SUBPROC_USAGE);

	if (argc < 2)
		return (ZONE_SUBPROC_USAGE);

	snapshot_name = argv[0];
	snap_path = argv[1];

	if ((handle = zonecfg_init_handle()) == NULL) {
		(void) fprintf(stderr, gettext("brand validate-snapshot "
		    "program error: %s\n"), strerror(errno));
		return (Z_ERR);
	}

	if ((err = zonecfg_get_handle(zonename, handle)) != Z_OK) {
		(void) fprintf(stderr, gettext("brand validate-snapshot "
		    "program error: %s\n"), zonecfg_strerror(err));
		zonecfg_fini_handle(handle);
		return (Z_ERR);
	}

	if ((athandle = zonecfg_init_handle()) == NULL) {
		(void) fprintf(stderr, gettext("brand validate-snapshot "
		    "program error: %s\n"), strerror(errno));
		goto done;
	}

	if ((err = zonecfg_get_attach_handle(snap_path, ZONE_DETACHED, zonename,
	    B_TRUE, athandle)) != Z_OK) {
		if (err == Z_NO_ZONE)
			(void) fprintf(stderr, gettext("snapshot %s was not "
			    "taken\n\tby a 'zoneadm clone' command.  It can "
			    "not be used to clone zones.\n"), snapshot_name);
		else
			(void) fprintf(stderr, gettext("snapshot %s is "
			    "out-dated\n\tIt can no longer be used to clone "
			    "zones on this system.\n"), snapshot_name);
		goto done;
	}

	/* Get the detach information for the locally defined zone. */
	if ((err = get_detach_info(handle, B_FALSE)) != Z_OK) {
		errno = err;
		zperror(gettext("getting the attach information failed"),
		    B_TRUE);
		goto done;
	}

	if ((err = sw_cmp(handle, athandle, SW_CMP_SILENT)) != Z_OK)
		(void) fprintf(stderr, gettext("snapshot %s is out-dated\n\t"
		    "It can no longer be used to clone zones on this "
		    "system.\n"), snapshot_name);

done:
	zonecfg_fini_handle(handle);
	if (athandle != NULL)
		zonecfg_fini_handle(athandle);

	return ((err == Z_OK) ? Z_OK : ZONE_SUBPROC_FATAL);
}

static void
usage()
{
	(void) fprintf(stderr, gettext("sw_support invalid arguments\n"));
	exit(253);
}

int
main(int argc, char **argv)
{
	int err = ZONE_SUBPROC_FATAL;
	char *cmd = NULL;

	if ((locale = setlocale(LC_ALL, "")) == NULL)
		locale = "C";
	(void) textdomain(TEXT_DOMAIN);

	if (argc < 4)
		usage();

	cmd = argv[1];
	zonename = argv[2];
	zonepath = argv[3];

	argc -= 4;
	argv = &argv[4];

	if (strcmp(cmd, "attach") == 0)
		err = attach_func(argc, argv);
	else if (strcmp(cmd, "detach") == 0)
		err = detach_func(argc, argv);
	else if (strcmp(cmd, "install") == 0)
		err = install_func(argc, argv);
	else if (strcmp(cmd, "postclone") == 0)
		err = postclone_func(argc, argv);
	else if (strcmp(cmd, "presnap") == 0)
		err = presnap_func(argc, argv);
	else if (strcmp(cmd, "postsnap") == 0)
		err = postsnap_func(argc, argv);
	else if (strcmp(cmd, "validatesnap") == 0)
		err = validatesnap_func(argc, argv);

	return (err);
}