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

#
# Send the error message to the screen and to the logfile.
#
error()
{
        typeset fmt="$1"
        shift

        printf "${MSG_PREFIX}ERROR: ${fmt}\n" "$@"
        [[ -n $LOGFILE ]] && printf "[$(date)] ERROR: ${fmt}\n" "$@" >&2
}

fatal()
{
        typeset fmt="$1"
        shift

	error "$fmt" "$@"
	exit $EXIT_CODE
}

fail_fatal() {
	printf "ERROR: "
	printf "$@"
	printf "\n"
	exit $ZONE_SUBPROC_FATAL
}

#
# Send the provided printf()-style arguments to the screen and to the logfile.
#
log()
{
        typeset fmt="$1"
        shift

        printf "${MSG_PREFIX}${fmt}\n" "$@"
        [[ -n $LOGFILE ]] && printf "[$(date)] ${MSG_PREFIX}${fmt}\n" "$@" >&2
}

#
# Print provided text to the screen if the shell variable "OPT_V" is set.
# The text is always sent to the logfile.
#
vlog()
{
        typeset fmt="$1"
        shift

        [[ -n $OPT_V ]] && printf "${MSG_PREFIX}${fmt}\n" "$@"
        [[ -n $LOGFILE ]] && printf "[$(date)] ${MSG_PREFIX}${fmt}\n" "$@" >&2
}

#
# Validate that the directory is safe.
#
# It is possible for a malicious zone root user to modify a zone's filesystem
# so that modifications made to the zone's filesystem by administrators in the
# global zone modify the global zone's filesystem.  We can prevent this by
# ensuring that all components of paths accessed by scripts are real (i.e.,
# non-symlink) directories.
#
# NOTE: The specified path should be an absolute path as would be seen from
# within the zone.  Also, this function does not check parent directories.
# If, for example, you need to ensure that every component of the path
# '/foo/bar/baz' is a directory and not a symlink, then do the following:
#
#	safe_dir /foo
#	safe_dir /foo/bar
#	safe_dir /foo/bar/baz
#
safe_dir()
{
	typeset dir="$1"

	if [[ -h $ZONEROOT/$dir || ! -d $ZONEROOT/$dir ]]; then
		fatal "$e_baddir" "$dir"
	fi
}

# Like safe_dir except the dir doesn't have to exist.
safe_opt_dir()
{
	typeset dir="$1"

	[[ ! -e $ZONEROOT/$dir ]] && return

	if [[ -h $ZONEROOT/$dir || ! -d $ZONEROOT/$dir ]]; then
		fatal "$e_baddir" "$dir"
	fi
}

# Only make a copy if we haven't already done so.
safe_backup()
{
	typeset src="$1"
	typeset dst="$2"

	if [[ ! -h $src && ! -h $dst && ! -d $dst && ! -f $dst ]]; then
		/usr/bin/cp -p $src $dst || fatal "$e_badfile" "$src"
	fi
}

# Make a copy even if the destination already exists.
safe_copy()
{
	typeset src="$1"
	typeset dst="$2"

	if [[ ! -h $src && ! -h $dst && ! -d $dst ]]; then
		/usr/bin/cp -p $src $dst || fatal "$e_badfile" "$src"
	fi
}

# Move a file
safe_move()
{
	typeset src="$1"
	typeset dst="$2"

	if [[ ! -h $src && ! -h $dst && ! -d $dst ]]; then
		/usr/bin/mv $src $dst || fatal "$e_badfile" "$src"
	fi
}

safe_rm()
{
	if [[ ! -h $ZONEROOT/$1 && -f $ZONEROOT/$1 ]]; then
		rm -f "$ZONEROOT/$1"
	fi
}

#
# Replace the file with a wrapper pointing to the native brand code.
# However, we only do the replacement if the file hasn't already been
# replaced with our wrapper.  This function expects the cwd to be the
# location of the file we're replacing.
#
# Some of the files we're replacing are hardlinks to isaexec so we need to 'rm'
# the file before we setup the wrapper while others are hardlinks to rc scripts
# that we need to maintain.
#
safe_replace()
{
	typeset filename="$1"
	typeset runname="$2"
	typeset mode="$3"
	typeset own="$4"
	typeset rem="$5"

	if [ -h $filename -o ! -f $filename ]; then
		return
	fi

	egrep -s "Solaris Brand Replacement" $filename
	if [ $? -eq 0 ]; then
		return
	fi

	safe_backup $filename $filename.pre_p2v
	if [ $rem = "remove" ]; then
		rm -f $filename
	fi

	cat <<-END >$filename || exit 1
	#!/bin/sh -p
	#
	# Solaris Brand Replacement
	#
	# Attention.  This file has been replaced with a new version for
	# use in a virtualized environment.  Modification of this script is not
	# supported and all changes will be lost upon reboot.  The
	# {name}.pre_p2v version of this file is a backup copy of the
	# original and should not be deleted.
	#
	END

	echo ". $runname \"\$@\"" >>$filename || exit 1

	chmod $mode $filename
	chown $own $filename
}

safe_wrap()
{
	typeset filename="$1"
	typeset runname="$2"
	typeset mode="$3"
	typeset own="$4"

	if [ -f $filename ]; then
		log "$e_cannot_wrap" "$filename"
		exit 1
	fi

	cat <<-END >$filename || exit 1
	#!/bin/sh
	#
	# Solaris Brand Wrapper
	#
	# Attention.  This file has been created for use in a
	# virtualized environment.  Modification of this script
	# is not supported and all changes will be lost upon reboot.
	#
	END

	echo ". $runname \"\$@\"" >>$filename || exit 1

	chmod $mode $filename
	chown $own $filename
}

#
# Read zonecfg fs entries and save the relevant data, one entry per
# line.
# This assumes the properties from the zonecfg output, e.g.:
#	fs:
#		dir: /opt
#		special: /opt
#		raw not specified
#		type: lofs
#		options: [noexec,ro,noatime]
#
# and it assumes the order of the fs properties as above.
#
get_fs_info()
{
	zonecfg -z $zonename info fs | nawk '{
		if ($1 == "options:") {
			# Remove brackets.
			options=substr($2, 2, length($2) - 2);
			printf("%s %s %s %s\n", dir, type, special, options);
		} else if ($1 == "dir:") {
			dir=$2;
		} else if ($1 == "special:") {
			special=$2;
		} else if ($1 == "type:") {
			type=$2
		}
	}' >> $fstmpfile
}

#
# Mount zonecfg fs entries into the zonepath.
#
mnt_fs()
{
	if [ ! -s $fstmpfile ]; then
		return;
	fi

	# Sort the fs entries so we can handle nested mounts.
	sort $fstmpfile | nawk -v zonepath=$zonepath '{
		if (NF == 4)
			options="-o " $4;
		else
			options=""

		# Create the mount point.  Ignore errors since we might have
		# a nested mount with a pre-existing mount point.
		cmd="/usr/bin/mkdir -p " zonepath "/root" $1 " >/dev/null 2>&1"
		system(cmd);

		cmd="/usr/sbin/mount -F " $2 " " options " " $3 " " \
		    zonepath "/root" $1;
		if (system(cmd) != 0) {
			printf("command failed: %s\n", cmd);
			exit 1;
		}
	}' >>$LOGFILE
}

#
# Unmount zonecfg fs entries from the zonepath.
#
umnt_fs()
{
	if [ ! -s $fstmpfile ]; then
		return;
	fi

	# Reverse sort the fs entries so we can handle nested unmounts.
	sort -r $fstmpfile | nawk -v zonepath=$zonepath '{
		cmd="/usr/sbin/umount " zonepath "/root" $1
		if (system(cmd) != 0) {
			printf("command failed: %s\n", cmd);
		}
	}' >>$LOGFILE
}

# Find the dataset mounted on the zonepath.
get_zonepath_ds() {
	ZONEPATH_DS=`/usr/sbin/zfs list -H -t filesystem -o name,mountpoint | \
	    /usr/bin/nawk -v zonepath=$1 '{
		if ($2 == zonepath)
			print $1
	}'`

	if [ -z "$ZONEPATH_DS" ]; then
		fail_fatal "$f_no_ds"
	fi
}

#
# Perform any cleanup in the zoneroot after unpacking the archive.
#
post_unpack()
{
	( cd "$ZONEROOT" && \
	    find . \( -type b -o -type c \) -exec rm -f "{}" \; )
}

#
# Determine flar compression style from identification file.
#
get_compression()
{
	typeset ident=$1
	typeset line=$(grep "^files_compressed_method=" $ident)

	print ${line##*=}
}

#
# Determine flar archive style from identification file.
#
get_archiver()
{
        typeset ident=$1
        typeset line=$(grep "^files_archived_method=" $ident)

        print ${line##*=}
}

#
# Unpack flar into current directory (which should be zoneroot).  The flash
# archive is standard input.  See flash_archive(4) man page.
# 
# We can't use "flar split" since it will only unpack into a directory called
# "archive".  We need to unpack in place in order to properly handle nested
# fs mounts within the zone root.  This function does the unpacking into the
# current directory.
#
# This code is derived from the gen_split() function in /usr/sbin/flar so
# we keep the same style as the original.
#
install_flar()
{
	typeset result
        typeset archiver_command
        typeset archiver_arguments

	vlog "cd $ZONEROOT && $stage1 "$insrc" | install_flar"

	# Read cookie
	read -r input_line
	if (( $? != 0 )); then
		log "$not_readable" "$install_media"
		return 1
	fi
	# The cookie has format FlAsH-aRcHiVe-m.n where m and n are integers.
	if [[ ${input_line%%-[0-9]*.[0-9]*} != "FlAsH-aRcHiVe" ]]; then
		log "$not_flar"
		return 1
	fi

	while [ true ]
	do
		# We should always be at the start of a section here
		read -r input_line
		if [[ ${input_line%%=*} != "section_begin" ]]; then
			log "$bad_flar"
			return 1
		fi
		section_name=${input_line##*=}

		# If we're at the archive, we're done skipping sections.
		if [[ "$section_name" == "archive" ]]; then
			break
		fi
		
		#
		# Save identification section to a file so we can determine
		# how to unpack the archive.
		#
		if [[ "$section_name" == "identification" ]]; then
			/usr/bin/rm -f identification
			while read -r input_line
			do
				if [[ ${input_line%%=*} == \
				    "section_begin" ]]; then
					/usr/bin/rm -f identification
					log "$bad_flar"
					return 1
				fi

				if [[ $input_line == \
				    "section_end=$section_name" ]]; then
					break;
				fi
				echo $input_line >> identification
			done

			continue
		fi

		#
		# Otherwise skip past this section; read lines until detecting
		# section_end.  According to flash_archive(4) we can have
		# an arbitrary number of sections but the archive section
		# must be last.
		#
		success=0
		while read -r input_line
		do
			if [[ $input_line == "section_end=$section_name" ]];
			then
				success=1
				break
			fi
			# Fail if we miss the end of the section
			if [[ ${input_line%%=*} == "section_begin" ]]; then
				/usr/bin/rm -f identification
				log "$bad_flar"
				return 1
			fi
		done
		if (( $success == 0 )); then
			#
			# If we get here we read to the end of the file before
			# seeing the end of the section we were reading.
			#
			/usr/bin/rm -f identification
			log "$bad_flar"
			return 1
		fi
	done

	# Check for an archive made from a ZFS root pool.
	egrep -s "^rootpool=" identification
        if (( $? == 0 )); then
		/usr/bin/rm -f identification
                log "$bad_zfs_flar"
                return 1
        fi

	# Get the information needed to unpack the archive.
	archiver=$(get_archiver identification)
	if [[ $archiver == "pax" ]]; then
		# pax archiver specified
		archiver_command="/usr/bin/pax"
		if [[ -s $fspaxfile ]]; then
			archiver_arguments="-r -p e -c \
			    $(/usr/bin/cat $fspaxfile)"
		else
			archiver_arguments="-r -p e"
		fi
	elif [[ $archiver == "cpio" || -z $archiver ]]; then
		# cpio archived specified OR no archiver specified - use default
		archiver_command="/usr/bin/cpio"
		archiver_arguments="-icdumfE $fscpiofile"
	else
		# unknown archiver specified
		log "$unknown_archiver" $archiver
		return 1
	fi

	if [[ ! -x $archiver_command ]]; then
		/usr/bin/rm -f identification
		log "$cmd_not_exec" $archiver_command
		return 1
	fi 

	compression=$(get_compression identification)

	# We're done with the identification file
	/usr/bin/rm -f identification

	# Extract archive
	if [[ $compression == "compress" ]]; then
		/usr/bin/zcat | \
		    $archiver_command $archiver_arguments 2>/dev/null
	else
		$archiver_command $archiver_arguments 2>/dev/null
	fi
	result=$?

	post_unpack

	(( $result != 0 )) && return 1

	return 0 
}

#
# Get the archive base.
#
# We must unpack the archive in the right place within the zonepath so
# that files are installed into the various mounted filesystems that are set
# up in the zone's configuration.  These are already mounted for us by the
# mntfs function.
#
# Archives can be made of either a physical host's root file system or a
# zone's zonepath.  For a physical system, if the archive is made using an
# absolute path (/...) we can't use it.  For a zone the admin can make the
# archive from a variety of locations;
#
#   a) zonepath itself: This will be a single dir, probably named with the
#      zone name, it will contain a root dir and under the root we'll see all
#      the top level dirs; etc, var, usr...  We must be above the ZONEPATH
#      when we unpack the archive but this will only work if the the archive's
#      top-level dir name matches the ZONEPATH base-level dir name.  If not,
#      this is an error.
#
#   b) inside the zonepath: We'll see root and it will contain all the top
#      level dirs; etc, var, usr....  We must be in the ZONEPATH when we unpack
#      the archive.
#
#   c) inside the zonepath root: We'll see all the top level dirs, ./etc,
#      ./var, ./usr....  This is also the case we see when we get an archive
#      of a physical sytem.  We must be in ZONEROOT when we unpack the archive.
#
# Note that there can be a directory named "root" under the ZONEPATH/root
# directory.
#
# This function handles the above possibilities so that we reject absolute
# path archives and figure out where in the file system we need to be to
# properly unpack the archive into the zone.  It sets the ARCHIVE_BASE
# variable to the location where the achive should be unpacked.
#
get_archive_base()
{
	stage1=$1
	archive=$2
	stage2=$3

	vlog "$m_analyse_archive"

	base=`$stage1 $archive | $stage2 2>/dev/null | nawk -F/ '{
		# Check for an absolute path archive
		if (substr($0, 1, 1) == "/")
			exit 1

		if ($1 != ".")
			dirs[$1] = 1
		else
			dirs[$2] = 1
	}
	END {
		for (d in dirs) {
			cnt++
			if (d == "bin")  sawbin = 1
			if (d == "etc")  sawetc = 1
			if (d == "root") sawroot = 1
			if (d == "var")  sawvar = 1
                }

		if (cnt == 1) {
			# If only one top-level dir named root, we are in the
			# zonepath, otherwise this must be an archive *of*
			# the zonepath so print the top-level dir name.
			if (sawroot)
				print "*zonepath*"
			else
				for (d in dirs) print d
		} else {
			# We are either in the zonepath or in the zonepath/root
			# (or at the top level of a full system archive which
			# looks like the zonepath/root case).  Figure out which
			# one.
			if (sawroot && !sawbin && !sawetc && !sawvar)
				print "*zonepath*"
			else
				print "*zoneroot*"
		}
	}'`

	if (( $? != 0 )); then
		umnt_fs
		fatal "$e_absolute_archive"
	fi

	if [[ "$base" == "*zoneroot*" ]]; then
		ARCHIVE_BASE=$ZONEROOT
	elif [[ "$base" == "*zonepath*" ]]; then
		ARCHIVE_BASE=$ZONEPATH
	else
		# We need to be in the dir above the ZONEPATH but we need to
		# validate that $base matches the final component of ZONEPATH.
		bname=`basename $ZONEPATH`

		if [[ "$bname" != "$base" ]]; then
			umnt_fs
			fatal "$e_mismatch_archive" "$base" "$bname"
		fi
		ARCHIVE_BASE=`dirname $ZONEPATH`
	fi
}

#
# Unpack cpio archive into zoneroot.
#
install_cpio()
{
	stage1=$1
	archive=$2

	get_archive_base "$stage1" "$archive" "cpio -it"

	cpioopts="-idmfE $fscpiofile"

	vlog "cd \"$ARCHIVE_BASE\" && $stage1 \"$archive\" | cpio $cpioopts"

	# Ignore errors from cpio since we expect some errors depending on
	# how the archive was made.
	( cd "$ARCHIVE_BASE" && $stage1 "$archive" | cpio $cpioopts )

	post_unpack

	return 0
}

#
# Unpack pax archive into zoneroot.
#
install_pax()
{
	archive=$1

	get_archive_base "cat" "$archive" "pax"

	if [[ -s $fspaxfile ]]; then
		filtopt="-c $(/usr/bin/cat $fspaxfile)"
	fi

	vlog "cd \"$ARCHIVE_BASE\" && pax -r -f \"$archive\" $filtopt"

	# Ignore errors from pax since we expect some errors depending on
	# how the archive was made.
	( cd "$ARCHIVE_BASE" && pax -r -f "$archive" $filtopt )

	post_unpack

	return 0
}

#
# Unpack UFS dump into zoneroot.
#
install_ufsdump()
{
	archive=$1

	vlog "cd \"$ZONEROOT\" && ufsrestore rf \"$archive\""

	#
	# ufsrestore goes interactive if you ^C it.  To prevent that,
	# we make sure its stdin is not a terminal.
	#
	( cd "$ZONEROOT" && ufsrestore rf "$archive" < /dev/null )
	result=$?

	post_unpack

	return $result
}

#
# Copy directory hierarchy into zoneroot.
#
install_dir()
{
	source_dir=$1

	cpioopts="-pdm"

	first=1
	filt=$(for i in $(cat $fspaxfile)
		do
			echo $i | egrep -s "/" && continue
			if [[ $first == 1 ]]; then
				printf "^%s" $i
				first=0
			else
				printf "|^%s" $i
			fi
		done)

	list=$(cd "$source_dir" && ls -d * | egrep -v "$filt")
	flist=$(for i in $list
	do
		printf "%s " "$i"
	done)
	findopts="-xdev ( -type d -o -type f -o -type l ) -print"

	vlog "cd \"$source_dir\" && find $flist $findopts | "
	vlog "cpio $cpioopts \"$ZONEROOT\""

	# Ignore errors from cpio since we expect some errors depending on
	# how the archive was made.
	( cd "$source_dir" && find $flist $findopts | \
	    cpio $cpioopts "$ZONEROOT" )

	post_unpack

	return 0
}

#
# This is a common function for laying down a zone image from a variety of
# different sources.  This can be used to either install a fresh zone or as
# part of zone migration during attach.
#
# The first argument specifies the type of image: archive, directory or stdin.
# The second argument specifies the image itself.  In the case of stdin, the
# second argument specifies the format of the stream (cpio, flar, etc.).
# Any validation or post-processing on the image is done elsewhere.
#
# This function calls a 'sanity_check' function which must be provided by
# the script which includes this code.
#
install_image()
{
	intype=$1
	insrc=$2

	if [[ -z "$intype" || -z "$insrc" ]]; then
		return 1
	fi

	filetype="unknown"
	filetypename="unknown"
	stage1="cat"

	if [[ "$intype" == "directory" ]]; then
		if [[ "$insrc" == "-" ]]; then
			# Indicates that the existing zonepath is prepopulated.
			filetype="existing"
			filetypename="existing"
		else
			if [[ "$(echo $insrc | cut -c 1)" != "/" ]]; then
				fatal "$e_path_abs" "$insrc"
			fi

			if [[ ! -e "$insrc" ]]; then
				log "$e_not_found" "$insrc"
				fatal "$e_install_abort"
			fi

			if [[ ! -r "$insrc" ]]; then
				log "$e_not_readable" "$insrc"
				fatal "$e_install_abort"
			fi

			if [[ ! -d "$insrc" ]]; then
				log "$e_not_dir"
				fatal "$e_install_abort"
			fi

			sanity_check $insrc

			filetype="directory"
			filetypename="directory"
		fi

	else
		# Common code for both archive and stdin stream.

		if [[ "$intype" == "archive" ]]; then
			if [[ ! -f "$insrc" ]]; then
				log "$e_unknown_archive"
				fatal "$e_install_abort"
			fi
			ftype="$(LC_ALL=C file $insrc | cut -d: -f 2)"
		else
			# For intype == stdin, the insrc parameter specifies
			# the stream format coming on stdin.
			ftype="$insrc"
			insrc="-"
		fi

		# Setup vars for the archive type we have.
		case "$ftype" in
		*cpio*)		filetype="cpio"
				filetypename="cpio archive"
			;;
		*bzip2*)	filetype="bzip2"
				filetypename="bzipped cpio archive"
			;;
		*gzip*)		filetype="gzip"
				filetypename="gzipped cpio archive"
			;;
		*ufsdump*)	filetype="ufsdump"
				filetypename="ufsdump archive"
			;;
		"flar")
				filetype="flar"
				filetypename="flash archive"
			;;
		"flash")
				filetype="flar"
				filetypename="flash archive"
			;;
		*Flash\ Archive*)
				filetype="flar"
				filetypename="flash archive"
			;;
		"tar")
				filetype="tar"
				filetypename="tar archive"
			;;
		*USTAR\ tar\ archive)
				filetype="tar"
				filetypename="tar archive"
			;;
		"pax")
				filetype="xustar"
				filetypename="pax (xustar) archive"
			;;
		*USTAR\ tar\ archive\ extended\ format*)
				filetype="xustar"
				filetypename="pax (xustar) archive"
			;;
		"zfs")
				filetype="zfs"
				filetypename="ZFS send stream"
			;;
		*ZFS\ snapshot\ stream*)
				filetype="zfs"
				filetypename="ZFS send stream"
			;;
		*)		log "$e_unknown_archive"
				fatal "$e_install_abort"
			;;
		esac
	fi

	vlog "$filetypename"

	# Check for a non-empty root if no '-d -' option. 
	if [[ "$filetype" != "existing" ]]; then
		cnt=$(ls $ZONEROOT | wc -l)
		if (( $cnt != 0 )); then
			fatal "$e_root_full" "$ZONEROOT"
		fi
	fi

	fstmpfile=$(/usr/bin/mktemp -t -p /var/tmp)
	if [[ -z "$fstmpfile" ]]; then
		fatal "$e_tmpfile"
	fi

	# Make sure we always have the files holding the directories to filter
	# out when extracting from a CPIO or PAX archive.  We'll add the fs
	# entries to these files in get_fs_info()
	fscpiofile=$(/usr/bin/mktemp -t -p /var/tmp fs.cpio.XXXXXX)
	if [[ -z "$fscpiofile" ]]; then
		rm -f $fstmpfile
		fatal "$e_tmpfile"
	fi

	# Filter out these directories.
	echo 'dev/*' >>$fscpiofile
	echo 'devices/*' >>$fscpiofile
	echo 'devices' >>$fscpiofile
	echo 'proc/*' >>$fscpiofile
	echo 'tmp/*' >>$fscpiofile
	echo 'var/run/*' >>$fscpiofile
	echo 'system/contract/*' >>$fscpiofile
	echo 'system/object/*' >>$fscpiofile

	fspaxfile=$(/usr/bin/mktemp -t -p /var/tmp fs.pax.XXXXXX)
	if [[ -z "$fspaxfile" ]]; then
		rm -f $fstmpfile $fscpiofile
		fatal "$e_tmpfile"
	fi

	printf "%s " \
	    "dev devices proc tmp var/run system/contract system/object" \
	    >>$fspaxfile

	# Set up any fs mounts so the archive will install into the correct
	# locations.
	get_fs_info
	mnt_fs
	if (( $? != 0 )); then
		umnt_fs >/dev/null 2>&1
		rm -f $fstmpfile $fscpiofile $fspaxfile
		fatal "$mount_failed"
	fi

	if [[ "$filetype" == "existing" ]]; then
		log "$no_installing"
	else
		log "$installing"
	fi

	#
	# Install the image into the zonepath.
	#
	unpack_result=0
	stage1="cat"
	if [[ "$filetype" == "gzip" ]]; then
		stage1="gzcat"
		filetype="cpio"
	elif [[ "$filetype" == "bzip2" ]]; then
		stage1="bzcat"
		filetype="cpio"
	fi

	if [[ "$filetype" == "cpio" ]]; then
		install_cpio "$stage1" "$insrc"
		unpack_result=$?

	elif [[ "$filetype" == "flar" ]]; then
		( cd "$ZONEROOT" && $stage1 $insrc | install_flar )
		unpack_result=$?

	elif [[ "$filetype" == "xustar" ]]; then
		install_pax "$insrc"
		unpack_result=$?

	elif [[ "$filetype" = "tar" ]]; then
		vlog "cd \"$ZONEROOT\" && tar -xf \"$insrc\""
		# Ignore errors from tar since we expect some errors depending
		# on how the archive was made.
		( cd "$ZONEROOT" && tar -xf "$insrc" )
		unpack_result=0
		post_unpack

	elif [[ "$filetype" == "ufsdump" ]]; then
		install_ufsdump "$insrc"
		unpack_result=$?

	elif [[ "$filetype" == "directory" ]]; then
		install_dir "$insrc"
		unpack_result=$?

	elif [[ "$filetype" == "zfs" ]]; then
		#
		# Given a 'zfs send' stream file, receive the snapshot into
		# the zone's dataset.  We're getting the original system's
		# zonepath dataset.  Destroy the existing dataset created
		# above since this recreates it.
		#
		if [[ -z "$DATASET" ]]; then
			fatal "$f_nodataset"
		fi
		/usr/sbin/zfs destroy "$DATASET"
		if (( $? != 0 )); then
			log "$f_zfsdestroy" "$DATASET"
		fi

		vlog "$stage1 $insrc | zfs receive -F $DATASET"
		( $stage1 $insrc | /usr/sbin/zfs receive -F $DATASET )
		unpack_result=$?
	fi

	# Clean up any fs mounts used during unpacking.
	umnt_fs
	rm -f $fstmpfile $fscpiofile $fspaxfile

	chmod 700 $zonepath

	(( $unpack_result != 0 )) && fatal "$f_unpack_failed"

	# Verify this is a valid image.
	sanity_check $ZONEROOT

	return 0
}

# Setup i18n output
TEXTDOMAIN="SUNW_OST_OSCMD"
export TEXTDOMAIN

e_cannot_wrap=$(gettext "%s: error: wrapper file already exists")
e_baddir=$(gettext "Invalid '%s' directory within the zone")
e_badfile=$(gettext "Invalid '%s' file within the zone")
e_path_abs=$(gettext "Pathname specified to -a '%s' must be absolute.")
e_not_found=$(gettext "%s: error: file or directory not found.")
e_install_abort=$(gettext "Installation aborted.")
e_not_readable=$(gettext "Cannot read directory '%s'")
e_not_dir=$(gettext "Error: must be a directory")
e_unknown_archive=$(gettext "Error: Unknown archive format. Must be a flash archive, a cpio archive (can also be gzipped or bzipped), a pax XUSTAR archive, or a level 0 ufsdump archive.")
e_absolute_archive=$(gettext "Error: archive contains absolute paths instead of relative paths.")
e_mismatch_archive=$(gettext "Error: the archive top-level directory (%s) does not match the zonepath (%s).")
e_tmpfile=$(gettext "Unable to create temporary file")
e_root_full=$(gettext "Zonepath root %s exists and contains data; remove or move aside prior to install.")
f_mkdir=$(gettext "Unable to create directory %s.")
f_chmod=$(gettext "Unable to chmod directory %s.")
f_chown=$(gettext "Unable to chown directory %s.")


m_analyse_archive=$(gettext "Analysing the archive")

not_readable=$(gettext "Cannot read file '%s'")
not_flar=$(gettext "Input is not a flash archive")
bad_flar=$(gettext "Flash archive is a corrupt")
bad_zfs_flar=$(gettext "Flash archive contains a ZFS send stream.\n\tRecreate the flar using the -L option with cpio or pax.")
f_unpack_failed=$(gettext "Unpacking the archive failed")
unknown_archiver=$(gettext "Archiver %s is not supported")
cmd_not_exec=$(gettext "Required command '%s' not executable!")

#
# Exit values used by the script, as #defined in <sys/zone.h>
#
#	ZONE_SUBPROC_OK
#	===============
#	Installation was successful
#
#	ZONE_SUBPROC_USAGE
#	==================
#	Improper arguments were passed, so print a usage message before exiting
#
#	ZONE_SUBPROC_NOTCOMPLETE
#	========================
#	Installation did not complete, but another installation attempt can be
#	made without an uninstall
#
#	ZONE_SUBPROC_FATAL
#	==================
#	Installation failed and an uninstall will be required before another
#	install can be attempted
#
ZONE_SUBPROC_OK=0
ZONE_SUBPROC_USAGE=253
ZONE_SUBPROC_NOTCOMPLETE=254
ZONE_SUBPROC_FATAL=255

