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

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

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


#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <libintl.h>
#include <dirent.h>
#include <pkgstrct.h>
#include <pkgdev.h>
#include <pkglocs.h>
#include <archives.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <wait.h>

/*
 * libinstzones includes
 */

#include <instzones_api.h>

/*
 * consolidation pkg command library includes
 */

#include <pkglib.h>
#include <pkgweb.h>

/*
 * local pkg command library includes
 */

#include <install.h>
#include <libinst.h>
#include <libadm.h>
#include <dryrun.h>
#include <messages.h>

/*
 * pkginstall local includes
 */

#include "pkginstall.h"

extern int		pkgverbose;
extern fsblkcnt_t	pkgmap_blks; 		/* main.c */

extern struct pkgdev pkgdev;

extern char	tmpdir[];
extern char	pkgbin[];
extern char	instdir[];
extern char	saveSpoolInstallDir[];
extern char	*pkginst;

extern int	dbchg;
extern int	nosetuid;
extern int	nocnflct;
extern int	warnflag;

#define	DMRG_DONE	-1

#define	ck_efile(s, p)	\
		((p->cinfo.modtime >= 0) && \
		p->ainfo.local && \
		cverify(0, &p->ftype, s, &p->cinfo, 1))

static int	eocflag;

/*
 * The variable below indicates that fix_attributes() will be inadequate
 * because a replacement was permitted.
 */
static int	repl_permitted = 0;

static int	domerg(struct cfextra **extlist, int part, int nparts,
			int myclass, char **srcp, char **dstp,
			char **r_updated, char **r_skipped,
			char **r_anyPathLocal);
static void	endofclass(struct cfextra **extlist, int myclass,
			int ckflag, PKGserver server, VFP_T **a_cfTmpVfp);
static int	fix_attributes(struct cfextra **, int);
static int	dir_is_populated(char *dirpath);
static boolean_t absolutepath(char *path);
static boolean_t parametricpath(char *path, char **relocpath);

/* Used to keep track of the entries in extlist that are regular files. */
struct reg_files {
	struct reg_files *next;
	int val;
};
static struct reg_files *regfiles_head = NULL;

/*
 * This is the function that actually installs one volume (usually that's
 * all there is). Upon entry, the extlist is entirely correct:
 *
 *	1. It contains only those files which are to be installed
 *	   from all volumes.
 *	2. The mode bits in the ainfo structure for each file are set
 *	   correctly in accordance with administrative defaults.
 *	3. mstat.setuid/setgid reflect what the status *was* before
 *	   pkgdbmerg() processed compliance.
 */
void
instvol(struct cfextra **extlist, char *srcinst, int part,
	int nparts, PKGserver pkgserver, VFP_T **a_cfTmpVfp,
	char **r_updated, char **r_skipped,
	char *a_zoneName)
{
	FILE		*listfp;
	char		*updated = (char *)NULL;
	char		*skipped = (char *)NULL;
	char		*anyPathLocal = (char *)NULL;
	char		*relocpath = (char *)NULL;
	char		*dstp;
	char		*listfile;
	char		*srcp;
	char		*pspool_loc;
	char		scrpt_dst[PATH_MAX];
	int		count;
	int		entryidx;	/* array of current package objects */
	int		n;
	int		nc = 0;
	int		pass;		/* pass count through the for loop. */
	int		tcount;
	struct cfent	*ept;
	struct cfextra	*ext;
	struct mergstat	*mstat;
	struct reg_files *rfp = NULL;

	/*
	 * r_updated and r_skipped are optional parameters that can be passed in
	 * by the caller if the caller wants to know if any objects are either
	 * updated or skipped. Do not initialize either r_updated or r_skipped;
	 * the call to instvol could be cumulative and any previous update or
	 * skipped indication must not be disturbed - these flags are only set,
	 * they must never be reset. These flags are "char *" pointers so that
	 * the object that was skipped or updated can be displayed in debugging
	 * output.
	 */

	if (part == 1) {
		pkgvolume(&pkgdev, srcinst, part, nparts);
	}

	tcount = 0;
	nc = cl_getn();

	/*
	 * For each class in this volume, install those files.
	 *
	 * NOTE : This loop index may be decremented by code below forcing a
	 * second trip through for the same class. This happens only when a
	 * class is split between an archive and the tree. Examples would be
	 * old WOS packages and the occasional class containing dynamic
	 * libraries which require special treatment.
	 */

	if (is_depend_pkginfo_DB() == B_FALSE) {
	    int		classidx;	/* the current class */

	    for (classidx = 0; classidx < nc; classidx++) {
		int pass_relative = 0;
		int rel_init = 0;

		eocflag = count = pass = 0;
		listfp = (FILE *)0;
		listfile = NULL;

		/* Now what do we pass to the class action script */

		if (cl_pthrel(classidx) == REL_2_CAS) {
			pass_relative = 1;
		}

		for (;;) {
			if (!tcount++) {
				/* first file to install */
				if (a_zoneName == (char *)NULL) {
					echo(MSG_INS_N_N, part, nparts);
				} else {
					echo(MSG_INS_N_N_LZ, part, nparts,
						a_zoneName);
				}
			}

			/*
			 * If there's an install class action script and no
			 * list file has been created yet, create that file
			 * and provide the pointer in listfp.
			 */
			if (cl_iscript(classidx) && !listfp) {
				/* create list file */
				putparam("TMPDIR", tmpdir);
				listfile = tempnam(tmpdir, "list");
				if ((listfp = fopen(listfile, "w")) == NULL) {
					progerr(ERR_WTMPFILE, listfile);
					quit(99);
				}
			}

			/*
			 * The following function goes through the package
			 * object list returning the array index of the next
			 * regular file. If it encounters a directory,
			 * symlink, named pipe or device, it just creates it.
			 */

			entryidx = domerg(extlist, (pass++ ? 0 : part), nparts,
				classidx, &srcp, &dstp, &updated, &skipped,
				&anyPathLocal);

			/* Evaluate the return code */
			if (entryidx == DMRG_DONE) {
				/*
				 * Set ept to the first entry in extlist
				 * which is guaranteed to exist so
				 * later checks against ept->ftype are
				 * not compared to NULL.
				 */
				ext = extlist[0];
				ept = &(ext->cf_ent);
				break; /* no more entries to process */
			}

			ext = extlist[entryidx];
			ept = &(ext->cf_ent);
			mstat = &(ext->mstat);

			/*
			 * If not installing from a partially spooled package
			 * (the "save/pspool" area), and the file contents can
			 * be changed (type is 'e' or 'v'), and the class is not
			 * "none": copy the file from the package (in pristine
			 * state with no actions performed) into the appropriate
			 * location in the packages destination "save/pspool"
			 * area.
			 */

			if ((!is_partial_inst()) &&
				((ept->ftype == 'e') || (ept->ftype == 'v')) &&
				(strcmp(ept->pkg_class, "none") != 0)) {

				if (absolutepath(ext->map_path) == B_TRUE &&
					parametricpath(ext->cf_ent.ainfo.local,
						&relocpath) == B_FALSE) {
					pspool_loc = ROOT;
				} else {
					pspool_loc = RELOC;
				}

				n = snprintf(scrpt_dst, PATH_MAX, "%s/%s/%s",
					saveSpoolInstallDir, pspool_loc,
					relocpath ? relocpath : ext->map_path);

				if (n >= PATH_MAX) {
					progerr(ERR_CREATE_PATH_2,
						saveSpoolInstallDir,
						ext->map_path);
					quit(99);
				}

				/* copy, preserve source file mode */

				if (cppath(MODE_SRC, srcp, scrpt_dst, 0644)) {
					warnflag++;
				}
			}

			/*
			 * If this isn't writeable anyway, it's not going
			 * into the list file. Only count it if it's going
			 * into the list file.
			 */
			if (is_fs_writeable(ext->cf_ent.path,
				&(ext->fsys_value)))
				count++;

			pkgvolume(&pkgdev, srcinst, part, nparts);

			/*
			 * If source verification is OK for this class, make
			 * sure the source we're passing to the class action
			 * script is useable.
			 */
			if (cl_svfy(classidx) != NOVERIFY) {
				if (cl_iscript(classidx) ||
					((ept->ftype == 'e') ||
					(ept->ftype == 'n'))) {
					if (ck_efile(srcp, ept)) {
						progerr(ERR_CORRUPT,
							srcp);
						logerr(getErrbufAddr());
						warnflag++;
						continue;
					}
				}
			}

			/*
			 * If there's a class action script for this class,
			 * just collect names in a temporary file
			 * that will be used as the stdin when the
			 * class action script is invoked.
			 */

			if ((cl_iscript(classidx)) &&
					((is_fs_writeable(ept->path,
						&(ext->fsys_value))))) {
				if (pass_relative) {
					if (!rel_init) {
						(void) fputs(instdir, listfp);
						(void) putc('\n', listfp);
						rel_init++;
					}
					(void) fputs(ext->map_path, listfp);
					(void) putc('\n', listfp);
				} else {
					(void) fputs(srcp ?
						srcp : "/dev/null", listfp);
					(void) putc(' ', listfp);
					(void) fputs(dstp, listfp);
					(void) putc('\n', listfp);
				}
				/*
				 * Note which entries in extlist are regular
				 * files to be installed via the class action
				 * script.
				 */
				if (regfiles_head == NULL) {
					assert(rfp == NULL);
					regfiles_head =
					    malloc(sizeof (struct reg_files));
					if (regfiles_head == NULL) {
						progerr(ERR_MEMORY, errno);
						quit(99);
					}
					regfiles_head->next = NULL;
					regfiles_head->val = entryidx;
					rfp = regfiles_head;
				} else {
					assert(rfp != NULL);
					rfp->next =
					    malloc(sizeof (struct reg_files));
					if (rfp->next == NULL) {
						progerr(ERR_MEMORY, errno);
						quit(99);
					}
					rfp = rfp->next;
					rfp->next = NULL;
					rfp->val = entryidx;
				}

				/*
				 * A warning message about unwritable targets
				 * in a class may be appropriate here.
				 */
				continue;
			}

			/*
			 * If not installing from a partially spooled package
			 * (the "save/pspool" area), and the file contents can
			 * be changed (type is 'e' or 'v') and the class
			 * identifier is not "none": copy the file from the
			 * package (in pristine state with no actions performed)
			 * into the appropriate location in the packages
			 * destination "save/pspool" area.
			 */

			if ((!is_partial_inst()) &&
			    ((ept->ftype == 'e') || (ept->ftype == 'v') &&
			    (strcmp(ept->pkg_class, "none") != 0))) {

				if (absolutepath(ext->map_path) == B_TRUE &&
					parametricpath(ext->cf_ent.ainfo.local,
						&relocpath) == B_FALSE) {
					pspool_loc = ROOT;
				} else {
					pspool_loc = RELOC;
				}

				n = snprintf(scrpt_dst, PATH_MAX, "%s/%s/%s",
					saveSpoolInstallDir, pspool_loc,
					relocpath ? relocpath : ext->map_path);

				if (n >= PATH_MAX) {
					progerr(ERR_CREATE_PATH_2,
						saveSpoolInstallDir,
						ext->map_path);
					quit(99);
				}

				/* copy, preserve source file mode */

				if (cppath(MODE_SRC, srcp, scrpt_dst, 0644)) {
					warnflag++;
				}
			}

			/*
			 * There are several tests here to determine
			 * how we're going to deal with objects
			 * intended for remote read-only filesystems.
			 * We don't use is_served() because this may be
			 * a server. We're actually interested in if
			 * it's *really* remote and *really* not
			 * writeable.
			 */

			n = is_remote_fs(ept->path, &(ext->fsys_value));
			if ((n != 0) &&
				!is_fs_writeable(ept->path,
				&(ext->fsys_value))) {

				/*
				 * Don't change the file, we can't write
				 * to it anyway.
				 */

				mstat->attrchg = 0;
				mstat->contchg = 0;

				/*
				 * If it's currently mounted, we can
				 * at least test it for existence.
				 */

				if (is_mounted(ept->path, &(ext->fsys_value))) {
					if (!isfile(NULL, dstp)) {
						echo(MSG_IS_PRESENT, dstp);
					} else {
						echo(WRN_INSTVOL_NONE, dstp);
					}
				} else {
					char *server_host;

					server_host = get_server_host(
						ext->fsys_value);

					/* If not, we're just stuck. */
					echo(WRN_INSTVOL_NOVERIFY,
						dstp, server_host);
				}

				continue;
			}

			/* echo output destination name */

			echo("%s", dstp);

			/*
			 * if no source then no need to copy/verify
			 */

			if (srcp == (char *)NULL) {
				continue;
			}

			/*
			 * If doing a partial installation (creating a
			 * non-global zone), extra steps need to be taken:
			 *
			 * 1) if the file is not type 'e' and not type 'v' and
			 * the class is "none": then the file must already
			 * exist (as a result of the initial non-global zone
			 * installation which caused all non-e/v files to be
			 * copied from the global zone to the non-global
			 * zone). If this is the case, verify that the file
			 * exists and has the correct attributes.
			 *
			 * 2) if the file is not type 'e' and not type 'v'
			 * and the class is NOT "none", *OR* if the file is
			 * type 'e' or type 'v': then check to see if the
			 * file is located in an area inherited from the
			 * global zone. If so, then there is no ability to
			 * change the file since inherited file systems are
			 * "read only" - just verify that the file exists and
			 * verify attributes only if not 'e' or 'v'.
			 */

			if (is_partial_inst() != 0) {

				/*
				 * determine if the destination package is in an
				 * area inherited from the global zone
				 */

				n = pkgMatchInherited(srcp, dstp,
					get_inst_root(), ept->ainfo.mode,
					ept->cinfo.modtime, ept->ftype,
					ept->cinfo.cksum);

				echoDebug(DBG_INSTVOL_PARTIAL_INST,
					srcp ? srcp : "", dstp ? dstp: "",
					((get_inst_root()) &&
					(strcmp(get_inst_root(), "/") != 0)) ?
					get_inst_root() : "",
					ept->ainfo.mode, ept->cinfo.modtime,
					ept->ftype, ept->cinfo.cksum, n);

				/*
				 * if not type 'e|v' and class 'none', then the
				 * file must already exist.
				 */

				if ((ept->ftype != 'e') &&
					(ept->ftype != 'v') &&
					(strcmp(cl_nam(ept->pkg_class_idx),
								"none") == 0)) {

					/*
					 * if the file is in a space inherited
					 * from the global zone, and if the
					 * contents or attributes are incorrect,
					 * then generate a warning that the
					 * global zone file contents and/or file
					 * attributes have been modified and
					 * that the modifications are extended
					 * to the non-global zone (inherited
					 * from the global zone).
					 */

					if (n == 0) {
						/* is file changed? */
						n = finalck(ept, 1, 1, B_TRUE);

						/* no - ok - continue */
						if (n == 0) {
							continue;
						}

						/* output warning message */
						logerr(NOTE_INSTVOL_FINALCKFAIL,
							pkginst, ext->map_path,
							a_zoneName, ept->path);
						continue;
					} else if (!finalck(ept, 1, 1,
								B_FALSE)) {
						/*
						 * non-e/v file of class "none"
						 * not inherited from the global
						 * zone: verify file already
						 * exists:everything checks here
						 */
						mstat->attrchg = 0;
						mstat->contchg = 0;
					}
					continue;
				}

				/*
				 * non-e/v file with class action script, or
				 * e/v file: if the file is in an area inherited
				 * from the global zone, then no need (or the
				 * ability) to update just accept the file as is
				 */

				if (n == B_TRUE) {
					/*
					 * the object is in an area inherited
					 * from the global zone and the objects
					 * attributes are verified
					 */

					mstat->attrchg = 0;
					mstat->contchg = 0;

					/* NOTE: package object skipped */

					if (skipped == (char *)NULL) {
						skipped = dstp;
					echoDebug(DBG_INSTVOL_OBJ_SKIPPED,
								skipped);
					}
					continue;
				}
			}

			/*
			 * Copy from source media to target path and fix file
			 * mode and permission now in case installation halted.
			 */

			if (z_path_is_inherited(dstp, ept->ftype,
			    get_inst_root()) == B_FALSE) {

				/*
				 * If the filesystem is read-only don't attempt
				 * to copy a file. Just check that the content
				 * and attributes of the file are correct.
				 *
				 * Normally this doesn't happen, because files,
				 * which don't change, are not returned by
				 * domerg(). However when installing a patch in
				 * a sparse zone, which was already installed
				 * in global zone with -G option, NGZ's
				 * contents db still contains the old record
				 * for this file and therefore domerg()
				 * considers these files to be different even
				 * though they are the same.
				 */
				n = 0;
				if (is_fs_writeable(ept->path,
				    &(ext->fsys_value)))
					n = cppath(MODE_SET|DIR_DISPLAY, srcp,
					    dstp, ept->ainfo.mode);

				if (n != 0) {
					warnflag++;
				} else if (!finalck(ept, 1, 1, B_FALSE)) {
					/*
					 * everything checks here
					 */
					mstat->attrchg = 0;
					mstat->contchg = 0;
				}
			}

			/* NOTE: a package object was updated */

			if (updated == (char *)NULL) {
				echoDebug(DBG_INSTVOL_OBJ_UPDATED, dstp);
				updated = dstp;
			}
		}

		/*
		 * We have now completed processing of all pathnames
		 * associated with this volume and class.
		 */
		if (cl_iscript(classidx)) {
			/*
			 * Execute appropriate class action script
			 * with list of source/destination pathnames
			 * as the input to the script.
			 */

			if (chdir(pkgbin)) {
				progerr(ERR_CHGDIR, pkgbin);
				quit(99);
			}

			if (listfp) {
				(void) fclose(listfp);
			}

			/*
			 * if the object associated with the class action script
			 * is in an area inherited from the global zone, then
			 * there is no need to run the class action script -
			 * assume that anything the script would do has already
			 * been done in the area shared from the global zone.
			 */

			/* nothing updated, nothing skipped */

			echoDebug(DBG_INSTVOL_CAS_INFO, is_partial_inst(),
				updated ? updated : "",
				skipped ? skipped : "",
				anyPathLocal ? anyPathLocal : "");

			if ((is_partial_inst() != 0) &&
					(updated == (char *)NULL) &&
					(anyPathLocal == (char *)NULL)) {

				/*
				 * installing in non-global zone, and no object
				 * has been updated (installed/verified in non-
				 * inherited area), and no path delivered by the
				 * package is in an area not inherited from the
				 * global zone (all paths delivered are in
				 * areas inherited from the global zone): do not
				 * run the class action script because the only
				 * affected areas are inherited (read only).
				 */

				echoDebug(DBG_INSTVOL_NOT_RUNNING_CAS,
					a_zoneName ? a_zoneName : "?",
					eocflag ? "ENDOFCLASS" :
							cl_iscript(classidx),
					cl_nam(classidx),
					cl_iscript(classidx));

				if ((r_skipped != (char **)NULL) &&
					(*r_skipped == (char *)NULL) &&
					(skipped == (char *)NULL)) {
					skipped = "postinstall";
					echoDebug(DBG_INSTVOL_OBJ_SKIPPED,
								skipped);
				}
			} else {
				/* run the class action script */

				echoDebug(DBG_INSTVOL_RUNNING_CAS,
					a_zoneName ? a_zoneName : "?",
					eocflag ? "ENDOFCLASS" :
							cl_iscript(classidx),
					cl_nam(classidx),
					cl_iscript(classidx));

				/* Use ULIMIT if supplied. */
				set_ulimit(cl_iscript(classidx), ERR_CASFAIL);

				if (eocflag) {
					/*
					 * end of class detected.
					 * Since there are no more volumes which
					 * contain pathnames associated with
					 * this class, execute class action
					 * script with the ENDOFCLASS argument;
					 * we do this even if none of the path
					 * names associated with this class and
					 * volume needed installation to
					 * guarantee the class action script is
					 * executed at least once during package
					 * installation.
					 */
					if (pkgverbose) {
						n = pkgexecl((listfp ?
							listfile : CAS_STDIN),
							CAS_STDOUT,
							CAS_USER, CAS_GRP,
							SHELL, "-x",
							cl_iscript(classidx),
							"ENDOFCLASS", NULL);
					} else {
						n = pkgexecl(
							(listfp ?
							listfile : CAS_STDIN),
							CAS_STDOUT, CAS_USER,
							CAS_GRP, SHELL,
							cl_iscript(classidx),
							"ENDOFCLASS", NULL);
					}
					ckreturn(n, ERR_CASFAIL);
				} else if (count) {
					/* execute class action script */
					if (pkgverbose) {
						n = pkgexecl(listfile,
							CAS_STDOUT, CAS_USER,
							CAS_GRP, SHELL, "-x",
							cl_iscript(classidx),
							NULL);
					} else {
						n = pkgexecl(listfile,
							CAS_STDOUT, CAS_USER,
							CAS_GRP, SHELL,
							cl_iscript(classidx),
							NULL);
					}
					ckreturn(n, ERR_CASFAIL);
				}

				/*
				 * Ensure the mod times on disk match those
				 * in the pkgmap. In this case, call cverify
				 * with checksumming disabled, since the only
				 * action that needs to be done is to verify
				 * that the attributes are correct.
				 */

				if ((rfp = regfiles_head) != NULL) {
					while (rfp != NULL) {
					    ept = &(extlist[rfp->val]->cf_ent);
					    cverify(1, &ept->ftype, ept->path,
						&ept->cinfo, 0);
					    rfp = rfp->next;
					}
					regfiles_free();
				}

				clr_ulimit();

				if ((r_updated != (char **)NULL) &&
					(*r_updated == (char *)NULL) &&
					(updated == (char *)NULL)) {
					updated = "postinstall";
					echoDebug(DBG_INSTVOL_OBJ_UPDATED,
								updated);
				}
			}
			if (listfile) {
				(void) remove(listfile);
			}
		}

		if (eocflag && (!is_partial_inst() || (is_partial_inst() &&
			strcmp(cl_nam(classidx), "none") != 0))) {
			if (cl_dvfy(classidx) == QKVERIFY && !repl_permitted) {
				/*
				 * The quick verify just fixes everything.
				 * If it returns 0, all is well. If it
				 * returns 1, then the class installation
				 * was incomplete and we retry on the
				 * stuff that failed in the conventional
				 * way (without a CAS). this is primarily
				 * to accomodate old archives such as are
				 * found in pre-2.5 WOS; but, it is also
				 * used when a critical dynamic library
				 * is not archived with its class.
				 */
				if (!fix_attributes(extlist, classidx)) {
					/*
					 * Reset the CAS pointer. If the
					 * function returns 0 then there
					 * was no script there in the first
					 * place and we'll just have to
					 * call this a miss.
					 */
					if (cl_deliscript(classidx))
						/*
						 * Decrement classidx for
						 * next pass.
						 */
						classidx--;
				}
			} else {
				/*
				 * Finalize merge. This checks to make sure
				 * file attributes are correct and any links
				 * specified are created.
				 */
				(void) endofclass(extlist, classidx,
					(cl_iscript(classidx) ? 0 : 1),
					pkgserver, a_cfTmpVfp);
			}
		}
	    }
	}

	/*
	 * Instead of creating links back to the GZ files the logic is
	 * to let zdo recreate the files from the GZ then invoke pkgadd to
	 * install the editable files and skip over any 'f'type files.
	 * The commented out block is to create the links which should be
	 * removed once the current code is tested to be correct.
	 */

	/*
	 * Go through extlist creating links for 'f'type files
	 * if we're in a global zone. Note that this code lies
	 * here instead of in the main loop to support CAF packages.
	 * In a CAF package the files are installed by the i.none script
	 * and don't exist until all files are done being processed, thus
	 * the additional loop through extlist.
	 */

	/*
	 * output appropriate completion message
	 */

	if (is_depend_pkginfo_DB() == B_TRUE) {
		/* updating database only (hollow package) */
		if (a_zoneName == (char *)NULL) {
			echo(MSG_DBUPD_N_N, part, nparts);
		} else {
			echo(MSG_DBUPD_N_N_LZ, part, nparts, a_zoneName);
		}
	} else if (tcount == 0) {
		/* updating package (non-hollow package) */
		if (a_zoneName == (char *)NULL) {
			echo(MSG_INST_N_N, part, nparts);
		} else {
			echo(MSG_INST_N_N_LZ, part, nparts, a_zoneName);
		}
	}

	/*
	 * if any package objects were updated (not inherited from the
	 * global zone or otherwise already in existence), set the updated
	 * flag as appropriate
	 */

	if (updated != (char *)NULL) {
		echoDebug(DBG_INSTVOL_OBJ_UPDATED, updated);
		if (r_updated != (char **)NULL) {
			*r_updated = updated;
		}
	}

	/*
	 * if any package objects were skipped (verified inherited from the
	 * global zone), set the skipped flag as appropriate
	 */

	if (skipped != (char *)NULL) {
		echoDebug(DBG_INSTVOL_OBJ_SKIPPED, skipped);
		if (r_skipped != (char **)NULL) {
			*r_skipped = skipped;
		}
	}
}

/*
 * Name:	domerg
 * Description: For the specified class, review each entry and return the array
 *		index number of the next regular file to process. Hard links are
 *		skipped (they are created in endofclass() and directories,
 *		symlinks, pipes and devices are created here, as well as any
 *		file that already exists and has the correct attributes.
 * Arguments:	struct cfextra **extlist - [RO, *RW]
 *			- Pointer to list of cfextra structures representing
 *			  the pkgmap of the package to be installed
 *		int part - [RO, *RO]
 *			- the part of the package currently being processed;
 *			  packages begin with part "1" and proceed for the
 *			  number (nparts) that comprise the package (volume).
 *		int nparts - [RO, *RO]
 *			- the number of parts the package is divided into
 *		int myclass - [RO, *RO]
 *			- index into class array of the current class
 *		char **srcp - [RW, *RW]
 *			- pointer to pointer to string representing the source
 *			  path for the next package to process - if this
 *			  function returns != DMRG_DONE then this pointer is
 *			  set to a pointer to a string representing the source
 *			  path for the next object from the package to process
 *		char **dstp - [RW, *RW]
 *			- pointer to pointer to string representing the target
 *			  path for the next package to process - if this
 *			  function returns != DMRG_DONE then this pointer is
 *			  set to a pointer to a string representing the target
 *			  path for the next object from the package to process
 *		char **r_updated - [RO, *RW]
 *			- pointer to pointer to string - set if the last path
 *			  returned exists or does not need updating and the
 *			  object is NOT located in an area inherited from the
 *			  global zone. This is used to determine if the last
 *			  path object returned DOES exist in an area that is
 *			  inherited from the global zone. If no paths are
 *			  inherited from the global zone, this is always set
 *			  when a path to be installed exists and has the
 *			  correct contents.
 *		char **r_skipped - [RO, *RW]
 *			- pointer to pointer to string - set if the last path
 *			  returned exists or does not need updating and the
 *			  object IS located in an area inherited from the
 *			  global zone. This is used to determine if the last
 *			  path object returned does NOT exist in an area that
 *			  is inherited from the global zone. If no paths are
 *			  inherited from the global zone, this is never set.
 *		char **r_anyPathLocal - [RO, *RW]
 *			- pointer to pointer to string - set if any object
 *			  belonging to the package is NOT located in an area
 *			  inherited from the global zone. This is used to
 *			  determine if the package references ANY objects that
 *			  are NOT located in an area inherited from the global
 *			  zone - regardless of whether or not they need to be
 *			  updated (installed/copied). If no paths are inherited
 *			  from the global zone, this is always set when a path
 *			  to be installed already exists and has the correct
 *			  contents.
 * Returns:	int
 *			!= DMRG_DONE - index into extlist of the next path to
 *				be processed - that needs to be installed/copied
 *			== DMRG_DONE - all entries processed
 */

static int
domerg(struct cfextra **extlist, int part, int nparts,
	int myclass, char **srcp, char **dstp,
	char **r_updated, char **r_skipped,
	char **r_anyPathLocal)
{
	boolean_t	stateFlag = B_FALSE;
	int		i;
	int		msg_ugid;
	static int	maxvol = 0;
	static int	svindx = 0;
	static int	svpart = 0;
	struct cfent	*ept = (struct cfent *)NULL;
	struct mergstat *mstat = (struct mergstat *)NULL;

	/* reset returned path pointers */

	*dstp = (char *)NULL;
	*srcp = (char *)NULL;

	/* set to start or continue based on which part being processed */

	if (part != 0) {
		maxvol = 0;
		svindx = 0;
		svpart = part;
	} else {
		i = svindx;
		part = svpart;
	}

	/*
	 * This goes through the pkgmap entries one by one testing them
	 * for inclusion in the package database as well as for validity
	 * against existing files.
	 */
	for (i = svindx; extlist[i]; i++) {
		ept = &(extlist[i]->cf_ent);
		mstat = &(extlist[i]->mstat);

		/*
		 * as paths are processed, if the "anyPathLocal" flag has not
		 * been set, if the object is not of type 'i' (package script),
		 * check to see if the object is in an area inherited from the
		 * global zone - if not, set "anyPathLocal" to the path found,
		 * indicating that at least one path is in an area that is not
		 * inherited from the global zone.
		 */

		if ((r_anyPathLocal != (char **)NULL) &&
			(*r_anyPathLocal == (char *)NULL) &&
			(ept->ftype != 'i') &&
			(z_path_is_inherited(ept->path, ept->ftype,
						get_inst_root()) == B_FALSE)) {
			echoDebug(DBG_INSTVOL_OBJ_LOCAL, ept->path);
			*r_anyPathLocal = ept->path;
		}

		/* if this isn't the class of current interest, skip it */

		if (myclass != ept->pkg_class_idx) {
			continue;
		}

		/* if the class is invalid, announce it & exit */
		if (ept->pkg_class_idx == -1) {
			progerr(ERR_CLIDX, ept->pkg_class_idx,
			    (ept->path && *ept->path) ? ept->path : "unknown");
			logerr(gettext("pathname=%s"),
			    (ept->path && *ept->path) ? ept->path : "unknown");
			logerr(gettext("class=<%s>"),
			    (ept->pkg_class && *ept->pkg_class) ?
			    ept->pkg_class : "Unknown");
			logerr(gettext("CLASSES=<%s>"),
			    getenv("CLASSES") ? getenv("CLASSES") : "Not Set");
			quit(99);
		}

		/*
		 * Next check to see if we are going to try to delete a
		 * populated directory in some distressing way.
		 */
		if (mstat->dir2nondir)
			if (dir_is_populated(ept->path)) {
				logerr(WRN_INSTVOL_NOTDIR, ept->path);
				warnflag++;
				mstat->denied = 1;	/* install denied! */
				continue;
			} else {	/* Replace is OK. */
				/*
				 * Remove this directory, so it won't
				 * interfere with creation of the new object.
				 */
				if (rmdir(ept->path)) {
					/*
					 * If it didn't work, there's nothing
					 * we can do. To continue would
					 * likely corrupt the filesystem
					 * which is unacceptable.
					 */
					progerr(ERR_RMDIR, ept->path);
					quit(99);
				}

				repl_permitted = 1;	/* flag it */
			}

		/* adjust the max volume number appropriately */

		if (ept->volno > maxvol) {
			maxvol = ept->volno;
		}

		/* if this part goes into another volume, skip it */

		if (part != ept->volno) {
			continue;
		}

		/*
		 * If it's a conflicting file and it's not supposed to be
		 * installed, note it and skip.
		 */
		if (nocnflct && mstat->shared && ept->ftype != 'e') {
			if (mstat->contchg || mstat->attrchg) {
				echo(MSG_SHIGN, ept->path);
			}
			continue;
		}

		/*
		 * If we want to set uid or gid but user says no, note it.
		 * Remember that the actual mode bits in the structure have
		 * already been adjusted and the mstat flag is telling us
		 * about the original mode.
		 */
		if (nosetuid && (mstat->setuid || mstat->setgid)) {
			msg_ugid = 1;	/* don't repeat attribute message. */
			if (is_fs_writeable(ept->path,
				&(extlist[i]->fsys_value))) {
				if (!(mstat->contchg) && mstat->attrchg) {
					echo(MSG_UGMOD, ept->path);
				} else {
					echo(MSG_UGID, ept->path);
				}
			}
		} else {
			msg_ugid = 0;
		}

		switch (ept->ftype) {
			case 'l':	/* hard link */
				/* links treated as object "update/skip" */
				stateFlag = B_TRUE;
				continue; /* defer to final proc */

			case 's': /* for symlink, verify without fix first */
				/* links treated as object "update/skip" */
				stateFlag = B_TRUE;

				/* Do this only for default verify */
				if (cl_dvfy(myclass) == DEFAULT) {
					if (averify(0, &ept->ftype,
						ept->path, &ept->ainfo))
						echo(MSG_SLINK, ept->path);
				}

				/*FALLTHRU*/

			case 'd':	/* directory */
			case 'x':	/* exclusive directory */
			case 'c':	/* character special device */
			case 'b':	/* block special device */
			case 'p':	/* named pipe */
				/* these NOT treated as object "update/skip" */
				stateFlag = B_FALSE;

				/*
				 * If we can't get to it for legitimate reasons,
				 * don't try to verify it.
				 */
				if ((z_path_is_inherited(ept->path, ept->ftype,
				    get_inst_root())) ||
				    is_remote_fs(ept->path,
				    &(extlist[i]->fsys_value)) &&
				    !is_fs_writeable(ept->path,
				    &(extlist[i]->fsys_value))) {
					mstat->attrchg = 0;
					mstat->contchg = 0;
					break;
				}

				if (averify(1, &ept->ftype, ept->path,
							&ept->ainfo) == 0) {
					mstat->contchg = mstat->attrchg = 0;
				} else {
					progerr(ERR_CREATE_PKGOBJ, ept->path);
					logerr(getErrbufAddr());
					warnflag++;
				}

				break;

			case 'i':	/* information file */
				/* not treated as object "update/skip" */
				stateFlag = B_FALSE;
				break;

			default:
				/* all files treated as object "update/skip" */
				stateFlag = B_TRUE;
				break;
		}

		/*
		 * Both contchg and shared flags have to be taken into
		 * account. contchg is set if the file is already present
		 * in the package database, if it does not exist or if it
		 * exists and is modified.
		 * The shared flag is set when 'e' or 'v' file is not
		 * present in the package database, exists and is not
		 * modified. It also has to be checked here.
		 * Shared flag is also set when file is present in package
		 * database and owned by more than one package, but for
		 * this case contchg has already been set.
		 */
		if (mstat->contchg || (mstat->shared &&
		    ((ept->ftype == 'e') || (ept->ftype == 'v')))) {
			*dstp = ept->path;
			if ((ept->ftype == 'f') || (ept->ftype == 'e') ||
				(ept->ftype == 'v')) {
				*srcp = ept->ainfo.local;
				if (is_partial_inst() != 0) {
					if (*srcp[0] == '~') {
						/* translate source pathname */
						*srcp = srcpath(instdir,
							extlist[i]->map_path,
							part, nparts);
					} else {
						*srcp = extlist[i]->map_path;
					}
				} else {
					if (*srcp[0] == '~') {
						/* translate source pathname */
						*srcp = srcpath(instdir,
						    &(ept->ainfo.local[1]),
						    part, nparts);
					}
				}

				echoDebug(DBG_DOMERG_NO_SUCH_FILE,
					ept->ftype, cl_nam(ept->pkg_class_idx),
					ept->path);
			} else {
				/*
				 * At this point, we're returning a non-file
				 * that couldn't be created in the standard
				 * way. If it refers to a filesystem that is
				 * not writeable by us, don't waste the
				 * calling process's time.
				 */
				if (!is_fs_writeable(ept->path,
					&(extlist[i]->fsys_value))) {
					echoDebug(DBG_DOMERG_NOT_WRITABLE,
						ept->ftype,
						cl_nam(ept->pkg_class_idx),
						ept->path);
					continue;
				}

				*srcp = NULL;
				echoDebug(DBG_DOMERG_NOT_THERE,
					ept->ftype, cl_nam(ept->pkg_class_idx),
					ept->path);
			}

			svindx = i+1;
			backup(*dstp, 1);
			return (i);
		}

		if (mstat->attrchg) {
			backup(ept->path, 0);
			if (!msg_ugid)
				echo(MSG_ATTRIB, ept->path);

			/* fix the attributes now for robustness sake */
			if (averify(1, &ept->ftype,
				ept->path,
				&ept->ainfo) == 0) {
				mstat->attrchg = 0;
			}
		}

		/*
		 * package object exists, or does not need updating: if the path
		 * is in an area inherited from the global zone, then treat
		 * the object as if it were "skipped" - if the path is not in an
		 * area inherited from the global zone, then treat the object as
		 * if it were "updated"
		 */

		/* LINTED warning: statement has no consequent: if */
		if ((stateFlag == B_FALSE) || (ept == (struct cfent *)NULL)) {
			/*
			 * the object in question is a directory or special
			 * file - the fact that this type of object already
			 * exists or does not need updating must not trigger
			 * the object updated/object skipped indication -
			 * that would cause class action scripts to be run
			 * when installing a new non-global zone - that action
			 * must only be done when a file object that is in
			 * an area inherited from the global zone is present.
			 */
		} else if (z_path_is_inherited(ept->path, ept->ftype,
						get_inst_root()) == B_TRUE) {
			if (r_skipped != (char **)NULL) {
				if (*r_skipped == (char *)NULL) {
					echoDebug(DBG_INSTVOL_OBJ_SKIPPED,
								ept->path);
					*r_skipped = ept->path;
				}
			}
		} else {
			if (r_updated != (char **)NULL) {
				if (*r_updated == (char *)NULL) {
					echoDebug(DBG_INSTVOL_OBJ_UPDATED,
								ept->path);
				}
				*r_updated = ept->path;
			}
		}
	}

	if (maxvol == part) {
		eocflag++;	/* endofclass */
	}

	return (DMRG_DONE);	/* no remaining entries on this volume */
}

/*
 * Determine if the provided directory is populated. Return 0 if so and 1 if
 * not. This also returns 0 if the dirpath is not a directory or if it does
 * not exist.
 */
static int
dir_is_populated(char *dirpath) {
	DIR	*dirfp;
	struct	dirent *drp;
	int	retcode = 0;

	if ((dirfp = opendir(dirpath)) != NULL) {
		while ((drp = readdir(dirfp)) != NULL) {
			if (strcmp(drp->d_name, ".") == 0) {
				continue;
			}
			if (strcmp(drp->d_name, "..") == 0) {
				continue;
			}
			/*
			 * If we get here, there's a real file in the
			 * directory
			 */
			retcode = 1;
			break;
		}
		(void) closedir(dirfp);
	}

	return (retcode);
}

/*
 * This is the function that cleans up the installation of this class.
 * This is where hard links get put in since the stuff they're linking
 * probably exists by now.
 */
static void
endofclass(struct cfextra **extlist, int myclass, int ckflag,
	PKGserver pkgserver, VFP_T **a_cfTmpVfp)
{
	char		*temppath;
	char 		*pspool_loc;
	char 		*relocpath = (char *)NULL;
	char 		scrpt_dst[PATH_MAX];
	int		flag;
	int		idx;
	int		n;
	struct cfent	*ept;	/* entry from the internal list */
	struct cfextra	entry;	/* entry from the package database */
	struct mergstat	*mstat;	/* merge status */
	struct pinfo	*pinfo;

	/* open the package database (contents) file */

	if (!ocfile(&pkgserver, a_cfTmpVfp, pkgmap_blks)) {
		quit(99);
	}

	echo(MSG_VERIFYING_CLASS, cl_nam(myclass));

	for (idx = 0; /* void */; idx++) {
		/* find next package object in this class */
		while (extlist[idx]) {
			if ((extlist[idx]->cf_ent.ftype != 'i') &&
				extlist[idx]->cf_ent.pkg_class_idx == myclass) {
				break;
			}
			idx++;
		}

		if (extlist[idx] == NULL)
			break;


		ept = &(extlist[idx]->cf_ent);
		mstat = &(extlist[idx]->mstat);

		temppath = extlist[idx]->client_path;

		/*
		 * At this point  the only difference between the entry
		 * in the contents file and the entry in extlist[] is
		 * that the status indicator contains CONFIRM_CONT.
		 * This function should return one or something is wrong.
		 */

		n = srchcfile(&(entry.cf_ent), temppath, pkgserver);

		if (n < 0) {
			char	*errstr = getErrstr();
			progerr(ERR_CFBAD);
			logerr(gettext("pathname=%s"),
				entry.cf_ent.path && *entry.cf_ent.path ?
				entry.cf_ent.path : "Unknown");
			logerr(gettext("problem=%s"),
				(errstr && *errstr) ? errstr : "Unknown");
			quit(99);
		} else if (n != 1) {
			/*
			 * Check if path should be in the package
			 * database.
			 */
			if ((mstat->shared && nocnflct)) {
				continue;
			}
			progerr(ERR_CFMISSING, ept->path);
			quit(99);
		}

		/*
		 * If merge was not appropriate for this object, now is the
		 * time to choose one or the other.
		 */
		if (mstat->denied) {
			/*
			 * If installation was denied AFTER the package
			 * database was updated, skip this. We've already
			 * announced the discrepancy and the verifications
			 * that follow will make faulty decisions based on
			 * the ftype, which may not be correct.
			 */
			progerr(ERR_COULD_NOT_INSTALL, ept->path);
			warnflag++;
		} else {
			if (mstat->replace)
				/*
				 * This replaces the old entry with the new
				 * one. This should never happen in the new
				 * DB since the entries are already identical.
				 */
				repl_cfent(ept, &(entry.cf_ent));

			/*
			 * Validate this entry and change the status flag in
			 * the package database.
			 */
			if (ept->ftype == RM_RDY) {
				(void) eptstat(&(entry.cf_ent), pkginst,
					STAT_NEXT);
			} else {
				/* check the hard link now. */
				if (ept->ftype == 'l') {
					if (averify(0, &ept->ftype,
						ept->path, &ept->ainfo)) {
						echo(MSG_HRDLINK,
							ept->path);
						mstat->attrchg++;
					}
				}

				/*
				 * Don't install or verify objects for
				 * remote, read-only filesystems.  We need
				 * only flag them as shared from some server.
				 * Otherwise, ok to do final check.
				 */
				if (is_remote_fs(ept->path,
					&(extlist[idx]->fsys_value)) &&
					!is_fs_writeable(ept->path,
					&(extlist[idx]->fsys_value))) {
					flag = -1;
				} else {
					boolean_t inheritedFlag;
					inheritedFlag =
					    z_path_is_inherited(ept->path,
						ept->ftype, get_inst_root());
					flag = finalck(ept, mstat->attrchg,
						(ckflag ? mstat->contchg :
						(-1)), inheritedFlag);
				}

				pinfo = entry.cf_ent.pinfo;

				/* Find this package in the list. */
				while (pinfo) {
					if (strcmp(pkginst, pinfo->pkg) == 0) {
						break;
					}
					pinfo = pinfo->next;
				}

				/*
				 * If this package owns this file, then store
				 * it in the database with the appropriate
				 * status. Need to check pinfo in case it
				 * points to NULL which could happen if
				 * pinfo->next = NULL above.
				 */
				if (pinfo) {
					if (flag < 0 || is_served(ept->path,
						&(extlist[idx]->fsys_value))) {
						/*
						 * This is provided to
						 * clients by a server.
						 */
						pinfo->status = SERVED_FILE;
					} else {
						/*
						 * It's either there or it's
						 * not.
						 */
						pinfo->status = (flag ?
							NOT_FND : ENTRY_OK);
					}
				}
			}
		}

		/*
		 * If not installing from a partially spooled package, the
		 * "save/pspool" area, and the file contents can be
		 * changed (type is 'e' or 'v'), and the class IS "none":
		 * copy the installed volatile file into the appropriate
		 * location in the packages destination "save/pspool" area.
		 */

		if ((!is_partial_inst()) &&
			((ept->ftype == 'e') || (ept->ftype == 'v')) &&
			(strcmp(ept->pkg_class, "none") == 0)) {

			if (absolutepath(extlist[idx]->map_path) == B_TRUE &&
				parametricpath(extlist[idx]->cf_ent.ainfo.local,
					&relocpath) == B_FALSE) {
				pspool_loc = ROOT;
			} else {
				pspool_loc = RELOC;
			}

			n = snprintf(scrpt_dst, PATH_MAX, "%s/%s/%s",
				saveSpoolInstallDir, pspool_loc,
				relocpath ? relocpath : extlist[idx]->map_path);

			if (n >= PATH_MAX) {
				progerr(ERR_CREATE_PATH_2,
					saveSpoolInstallDir,
					extlist[idx]->map_path);
				quit(99);
			}

			/* copy, preserve source file mode */

			if (cppath(MODE_SRC, ept->path, scrpt_dst, 0644)) {
				warnflag++;
			}
		}

		/*
		 * Now insert this potentially changed package database
		 * entry.
		 */
		if (entry.cf_ent.npkgs) {
			if (putcvfpfile(&(entry.cf_ent), *a_cfTmpVfp)) {
				quit(99);
			}
		}
	}

	n = swapcfile(pkgserver, a_cfTmpVfp, pkginst, dbchg);
	if (n == RESULT_WRN) {
		warnflag++;
	} else if (n == RESULT_ERR) {
		quit(99);
	}
}

/*
 * This function goes through and fixes all the attributes. This is called
 * out by using DST_QKVERIFY=this_class in the pkginfo file. The primary
 * use for this is to fix up files installed by a class action script
 * which is time-critical and reliable enough to assume likely success.
 * The first such format was for WOS compressed-cpio'd file sets.
 * The second format is the Class Archive Format.
 */
static int
fix_attributes(struct cfextra **extlist, int idx)
{
	struct	cfextra *ext;
	int	i, retval = 1;
	int 	nc = cl_getn();
	int	n;
	struct cfent *ept;
	struct mergstat *mstat;
	char scrpt_dst[PATH_MAX];
	char *pspool_loc;
	char *relocpath = (char *)NULL;

	for (i = 0; extlist[i]; i++) {
		ext = extlist[i];
		ept = &(extlist[i]->cf_ent);
		mstat = &(extlist[i]->mstat);

		/*
		 * We don't care about 'i'nfo files because, they
		 * aren't laid down, 'e'ditable files can change
		 * anyway, so who cares and 's'ymlinks were already
		 * fixed in domerg(); however, certain old WOS
		 * package symlinks depend on a bug in the old
		 * pkgadd which has recently been expunged. For
		 * those packages in 2.2, we repeat the verification
		 * of symlinks.
		 *
		 * By 2.6 or so, ftype == 's' should be added to this.
		 */
		if (ept->ftype == 'i' || ept->ftype == 'e' ||
			(mstat->shared && nocnflct))
			continue;

		if (mstat->denied) {
			progerr(ERR_COULD_NOT_INSTALL, ept->path);
			warnflag++;
			continue;
		}

		if (ept->pkg_class_idx < 0 || ept->pkg_class_idx > nc) {
			progerr(ERR_CLIDX, ept->pkg_class_idx,
			    (ept->path && *ept->path) ? ept->path : "unknown");
			continue;
		}

		/* If this is the right class, do the fast verify. */
		if (ept->pkg_class_idx == idx) {
			if (fverify(1, &ept->ftype, ept->path,
				&ept->ainfo, &ept->cinfo) == 0) {
				mstat->attrchg = 0;
				mstat->contchg =  0;
			} else	/* We'll try full verify later */
				retval = 0;
		}
		/*
		 * Need to copy the installed volitale file back to the
		 * partial spooled area if we are installing to a local zone
		 * or similar installation method.
		 */

		if ((!is_partial_inst()) &&
			((ept->ftype == 'e') || (ept->ftype == 'v')) &&
			(strcmp(ept->pkg_class, "none") == 0)) {

			if (absolutepath(ext->map_path) == B_TRUE &&
				parametricpath(ext->cf_ent.ainfo.local,
					&relocpath) == B_FALSE) {
				pspool_loc = ROOT;
			} else {
				pspool_loc = RELOC;
			}

			n = snprintf(scrpt_dst, PATH_MAX, "%s/%s/%s",
				saveSpoolInstallDir, pspool_loc,
				relocpath ? relocpath : ext->map_path);

			if (n >= PATH_MAX) {
				progerr(ERR_CREATE_PATH_2,
					saveSpoolInstallDir,
					ext->map_path);
				quit(99);
			}

			/* copy, preserve source file mode */

			if (cppath(MODE_SRC, ept->path, scrpt_dst, 0644)) {
				warnflag++;
			}
		}
	}

	return (retval);
}

/*
 * Check to see if first charcter in path is a '/'.
 *
 * Return:
 * 			B_TRUE - if path is prepended with '/'
 * 			B_FALSE - if not
 */
static boolean_t
absolutepath(char *path)
{
	assert(path != NULL);
	assert(path[0] != '\0');

	return (path[0] == '/' ? B_TRUE : B_FALSE);
}

/*
 * Check to see if path contains a '$' which makes it
 * a parametric path and therefore relocatable.
 *
 * Parameters:
 *             path - The path to determine if it is absolute
 *             relocpath - The value of the unconditioned path
 *                         i.e. $OPTDIR/usr/ls
 * Return:
 * 			B_TRUE - if path is a parametric path
 * 			B_FALSE - if not
 */
static boolean_t
parametricpath(char *path, char **relocpath)
{
	assert(path != NULL);
	assert(path[0] != '\0');

	/*
	 * If this is a valid parametric path then a '$' MUST occur at the
	 * first or second character.
	 */

	if (path[0] == '$' || path[1] == '$') {
		/*
		 * If a parametric path exists then when copying the
		 * path to the pspool directoy from the installing
		 * pkgs reloc directory we want to use the uncononditional
		 * varaiable path.
		 */
		*relocpath = (path + 1);
		return (B_TRUE);
	}
	return (B_FALSE);
}

void
regfiles_free()
{
	if (regfiles_head != NULL) {
		struct reg_files *rfp = regfiles_head->next;

		while (rfp != NULL) {
			free(regfiles_head);
			regfiles_head = rfp;
			rfp = regfiles_head->next;
		}
		free(regfiles_head);
		regfiles_head = NULL;
	}
}