OpenSolaris_b135/lib/brand/lx/zone/lx_install.ksh

#!/bin/ksh -p
#
# 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.
#

# Restrict executables to /bin, /usr/bin, /usr/sbin and /usr/sfw/bin
PATH=/bin:/usr/bin:/usr/sbin:/usr/sfw/bin

export PATH

# Setup i18n output
TEXTDOMAIN="SUNW_OST_OSCMD"
export TEXTDOMAIN

# Log passed arguments to file descriptor 2
log()
{
	[[ -n $logfile ]] && echo "$@" >&2
}

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

	printf "$fmt\n" "$@"
	[[ -n $logfile ]] && printf "$fmt\n" "$@" >&2
}

# Print and log provided text if the shell variable "verbose_mode" is set
verbose()
{
	[[ -n $verbose_mode ]] && echo "$@"
	[[ -n $logfile ]] && [[ -n $verbose_mode ]] && echo "$@" >&2
}

unsupported_cpu=\
$(gettext "ERROR: Cannot install branded zone: processor must be %s-compatible")

cmd_not_found=$(gettext "Required command '%s' cannot be found!")
cmd_not_exec=$(gettext "Required command '%s' not executable!")
zone_initfail=$(gettext "Attempt to initialize zone '%s' FAILED.")
path_abs=$(gettext "Pathname specified to -d '%s' must be absolute.")

cmd_h=$(gettext "%s -z <zone name> %s -h")
cmd_full=\
$(gettext "%s -z <zone name> %s [-v | -s] [-d <dir>|<device>] [<cluster> ... ]")

both_modes=$(gettext "%s: error: cannot select both silent and verbose modes")

not_found=$(gettext "%s: error: file or directory not found.")

wrong_type=\
$(gettext "%s: error: must be a gzip, bzip2, .Z or uncompressed tar archive.")

not_readable=$(gettext "Cannot read file '%s'")

no_install=$(gettext "Could not create install directory '%s'")
no_log=$(gettext "Could not create log directory '%s'")
no_logfile=$(gettext "Could not create log file '%s'")

root_full=$(gettext "Zonepath root %s exists and contains data; remove or move aside prior to install.")

install_zone=$(gettext "Installing zone '%s' at root directory '%s'")
install_from=$(gettext "from archive '%s'")

install_fail=$(gettext "Installation of zone '%s' FAILED.")
see_log=$(gettext "See the log file:\n  '%s'\nfor details.")

install_abort=$(gettext "Installation of zone '%s' aborted.")
install_good=$(gettext "Installation of zone '%s' completed successfully.")

# Check if commands passed in exist and are executable.
check_cmd()
{
	for cmd in "$@"; do
		if [[ ! -f $cmd ]]; then
			screenlog "$cmd_not_found" "$cmd"
			screenlog "$install_abort" "$zonename"
			exit $ZONE_SUBPROC_NOTCOMPLETE
		fi

		if [[ ! -x $cmd ]]; then
			screenlog "$cmd_not_exec" "$cmd"
			screenlog "$install_abort" "$zonename"
			exit $ZONE_SUBPROC_NOTCOMPLETE
		fi
	done
}

# Post process as tarball-installed zone for use by BrandZ.
init_tarzone()
{
	typeset rootdir="$1"

        if ! $branddir/lx_init_zone "$rootdir"; then
                screenlog "$zone_initfail" "$zonename"
                return 1
        fi
}

# Clean up on interrupt
trap_cleanup()
{
	msg=$(gettext "Installation cancelled due to interrupt.")

	screenlog "$msg"
	exit $int_code
}

#
# Output the usage message.
#
# This is done this way due to limitations in the way gettext strings are
# extracted from shell scripts and processed.  Use of this somewhat awkward
# syntax allows us to produce longer lines of text than otherwise would be
# possible without wrapping lines across more than one line of code.
#
usage()
{
	int_code=$ZONE_SUBPROC_USAGE

	echo $(gettext "Usage:")
	printf "  $cmd_h\n" "zoneadm" "install"
	printf "  $cmd_full\n" "zoneadm" "install"

	echo

	echo $(gettext "The installer will attempt to use the default system") \
	    $(gettext "removable disc device if <archive dir> is not") \
	    $(gettext "specified.") | fmt -80

	echo

	echo $(gettext "<cluster> specifies which package cluster you wish") \
	    $(gettext "to install.") | fmt -80

	echo
	echo $(gettext "The 'desktop' cluster will be installed by default.")
	echo
	echo $(gettext "The available clusters are:")
	echo "    + core"
	echo "    + server"
	echo "    + desktop"
	echo "    + development"
	echo "    + all"
	echo

	echo $(gettext "Each cluster includes all of the clusters preceding") \
	    $(gettext "it, so the 'server' cluster includes the 'core'") \
	    $(gettext "cluster, the 'desktop' cluster includes the 'core'") \
	    $(gettext "and 'server' clusters, and so on.") | fmt -80

	echo
	echo $(gettext "Examples")
	echo "========"

	echo $(gettext "Example 1: Install a base Linux system from CDs or a") \
	    $(gettext "DVD using the system default removable disc device:") |
	    fmt -80

	echo
	echo "    # zoneadm -z myzone install"
	echo

	echo $(gettext "Example 2: Install the 'server' cluster from CDs or") \
	    $(gettext "a DVD via an alternative removable disc device:") |
	    fmt -80

	echo
	echo "    # zoneadm -z myzone install -d /cdrom/cdrom1 server"
	echo

	echo $(gettext "Example 3: Install the desktop Linux environment") \
	    $(gettext "from an ISO image made available as '/dev/lofi/1' by") \
	    $(gettext "use of lofiadm(1M):") | fmt -80

	echo
	echo "    # zoneadm -z myzone install -d /dev/lofi/1 desktop"
	echo

	echo $(gettext "Example 4: Install the entire Linux environment from") \
	    $(gettext "ISO images located in the directory") \
	    "'/export/centos_3.8/isos':" | fmt -80

	echo
	echo "    # zoneadm -z myzone install -d /export/centos_3.8/isos all"
	echo

	echo $(gettext "Example 5: Install from a compressed tar archive of") \
	    $(gettext "an existing Linux installation (a tar ball) with") \
	    $(gettext "verbose output regarding the progress of the") \
	    $(gettext "installation:") | fmt -80

	echo
	echo "    # zoneadm -z myzone install -v -d /tmp/linux_full.tar.gz"
	echo

	echo $(gettext "Example 6: Install from a compressed tar archive of") \
	    $(gettext "an existing Linux installation (a tar ball) with NO") \
	    $(gettext "output regarding the progress of the installation") \
	    $(gettext "(silent mode.)") | fmt -80

	echo

	echo $(gettext "NOTE: Silent mode is only recommended for use by") \
	    $(gettext "shell scripts and other non-interactive programs:") |
	    fmt -80

	echo
	echo "    # zoneadm -z myzone install -d /tmp/linux_full.tar.gz -s"
	echo

	exit $int_code
}

#
# The main body of the script starts here.
#
# This script should never be called directly by a user but rather should
# only be called by zoneadm to install a BrandZ Linux zone.
#

#
# 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

#
# An unspecified exit or interrupt should exit with ZONE_SUBPROC_NOTCOMPLETE,
# meaning a user will not need to do an uninstall before attempting another
# install.
#
int_code=$ZONE_SUBPROC_NOTCOMPLETE

trap trap_cleanup INT

# If we weren't passed at least two arguments, exit now.
[[ $# -lt 2 ]] && usage

#
# This script is always started with a full path so we can extract the
# brand directory name here.
#
branddir=$(dirname "$0")
zonename="$1"
zoneroot="$2"

install_root="$zoneroot/root"
logdir="$install_root/var/log"

shift; shift	# remove zonename and zoneroot from arguments array

unset gtaropts
unset install_opts
unset install_src
unset msg
unset silent_mode
unset verbose_mode

while getopts "d:hsvX" opt
do
	case "$opt" in
		h) 	usage;;
		s)	silent_mode=1;;
		v)	verbose_mode=1;;
		d) 	install_src="$OPTARG" ;;
		X)	install_opts="$install_opts -x" ;;
		*)	usage;;
	esac
done
shift OPTIND-1

# Providing more than one passed argument generates a usage message
if [[ $# -gt 1 ]]; then
	msg=$(gettext "ERROR: Too many arguments provided:")

	screenlog "$msg"
	screenlog "  \"%s\"" "$@"
	screenlog ""
	usage
fi

# Validate any free-form arguments
if [[ $# -eq 1 && "$1" != "core" && "$1" != "server" && "$1" != "desktop" &&
    "$1" != "development" && "$1" != "all" ]]; then
	msg=$(gettext "ERROR: Unknown cluster name specified: %s")

	screenlog "$msg" "\"$1\""
	screenlog ""
	usage
fi

# The install can't be both verbose AND silent...
if [[ -n $silent_mode && -n $verbose_mode ]]; then
	screenlog "$both_modes" "zoneadm install"
	screenlog ""
	usage
fi

#
# Validate that we're running on a i686-compatible CPU; abort the zone
# installation now if we're not.
#
procinfo=$(LC_ALL=C psrinfo -vp | grep family)

#
# All x86 processors in CPUID families 6, 15, 16 or 17 should be
# i686-compatible, assuming third party processor vendors follow AMD and
# Intel's lead.
#
if [[ "$procinfo" != *" x86 "* ]] ||
    [[ "$procinfo" != *" family 6 "* && "$procinfo" != *" family 15 "* &&
    "$procinfo" != *" family 16 "* && "$procinfo" != *" family 17 "* ]] ; then
	screenlog "$unsupported_cpu" "i686"
	exit $int_code
fi

if [[ -n $install_src ]]; then
	#
	# Validate $install_src.
	#
	# If install_src is a directory, assume it contains ISO images to
	# install from, otherwise treat the argument as if it points to a tar
	# ball file.
	#
	if [[ "`echo $install_src | cut -c 1`" != "/" ]]; then
		screenlog "$path_abs" "$install_src"
		exit $int_code
	fi

	if [[ ! -a "$install_src" ]]; then
		screenlog "$not_found" "$install_src"
		screenlog "$install_abort" "$zonename"
		exit $int_code
	fi

	if [[ ! -r "$install_src" ]]; then
		screenlog "$not_readable" "$install_src"
		screenlog "$install_abort" "$zonename"
		exit $int_code
	fi

	#
	# If install_src is a block device, a directory, a possible device
	# created via lofiadm(1M), or the directory used by a standard volume
	# management daemon, pass it on to the secondary install script.
	#
	# Otherwise, validate the passed filename to prepare for a tar ball
	# install.
	#
	if [[ ! -b "$install_src" && ! -d "$install_src" &&
	    "$install_src" != /dev/lofi/* && "$install_src" != /cdrom/* &&
	    "$install_src" != /media/* ]]; then
		if [[ ! -f "$install_src" ]]; then
			screenlog "$wrong_type" "$install_src"
			screenlog "$install_abort" "$zonename"
			exit $int_code
		fi

		filetype=`{ LC_ALL=C file $install_src | 
		    awk '{print $2}' ; } 2>/dev/null`

		if [[ "$filetype" = "gzip" ]]; then
			verbose "\"$install_src\": \"gzip\" archive"
			gtaropts="-xz"
		elif [[ "$filetype" = "bzip2" ]]; then
			verbose "\"$install_src\": \"bzip2\" archive"
			gtaropts="-xj"
		elif [[ "$filetype" = "compressed" ]]; then
			verbose "\"$install_src\": Lempel-Ziv" \
			    "compressed (\".Z\") archive."
			gtaropts="-xZ"
		elif [[ "$filetype" = "USTAR" ]]; then
			verbose "\"$install_src\":" \
			    "uncompressed (\"tar\") archive."
			gtaropts="-x"
		else
			screenlog "$wrong_type" "$install_src"
			screenlog "$install_abort" "$zonename"
			exit $int_code
		fi
	fi
fi

#
# Start silent operation and pass the flag to prepare pass the flag to
# the ISO installer, if needed.
#
if [[ -n $silent_mode ]]
then
	exec 1>/dev/null
	install_opts="$install_opts -s"
fi

#
# If verbose mode was specified, pass the verbose flag to lx_distro_install
# for ISO or disc installations and to gtar for tarball-based installs.
#
if [[ -n $verbose_mode ]]
then
	echo $(gettext "Verbose output mode enabled.")
	install_opts="$install_opts -v"
	[[ -n $gtaropts ]] && gtaropts="${gtaropts}v"
fi

[[ -n $gtaropts ]] && gtaropts="${gtaropts}f"

if [[ ! -d "$install_root" ]]
then
	if ! mkdir -p "$install_root" 2>/dev/null; then
		screenlog "$no_install" "$install_root"
		exit $int_code
	fi
fi

#
# Check for a non-empty root.
# 
cnt=`ls $install_root | wc -l`
if [ $cnt -ne 0 ]; then
	screenlog "$root_full" "$install_root"
	exit $int_code
fi

if [[ ! -d "$logdir" ]]
then
	if ! mkdir -p "$logdir" 2>/dev/null; then
		screenlog "$no_log" "$logdir"
		exit $int_code
	fi
fi

logfile="${logdir}/$zonename.install.$$.log"

if ! > $logfile; then
	screenlog "$no_logfile" "$logfile"
	exit $int_code
fi

# Redirect stderr to the log file to automatically log any error messages
exec 2>>"$logfile"

#
# From here on out, an unspecified exit or interrupt should exit with
# ZONE_SUBPROC_FATAL, meaning a user will need to do an uninstall before
# attempting another install, as we've modified the directories we were going
# to install to in some way.
#
int_code=$ZONE_SUBPROC_FATAL

log "Installation started for zone \"$zonename\" `/usr/bin/date`"

if [[ -n $gtaropts ]]; then
	check_cmd /usr/sfw/bin/gtar $branddir/lx_init_zone

	screenlog "$install_zone" "$zonename" "$zoneroot"
	screenlog "$install_from" "$install_src"
	echo
	echo $(gettext "This process may take several minutes.")
	echo

	if ! ( cd "$install_root" && gtar "$gtaropts" "$install_src" ) ; then
		log "Error: extraction from tar archive failed."
	else
		if ! [[ -d "${install_root}/bin" &&
		    -d "${install_root}/sbin" ]]; then
			log "Error: improper or incomplete tar archive."
		else
			$branddir/lx_init_zone "$install_root" &&
			    init_tarzone "$install_root"

			#
			# Emit the same code from here whether we're
			# interrupted or exiting normally.
			#
			int_code=$?
		fi
	fi

	if [[ $int_code -eq ZONE_SUBPROC_OK ]]; then
		log "Tar install completed for zone '$zonename' `date`."
	else
		log "Tar install failed for zone \"$zonename\" `date`."

	fi
else
	check_cmd $branddir/lx_distro_install

	$branddir/lx_distro_install -z "$zonename" -r "$zoneroot" \
	    -d "$install_src" -l "$logfile" $install_opts "$@"

	#
	# Emit the same code from here whether we're interrupted or exiting
	# normally.
	#
	int_code=$?

	[[ $int_code -eq $ZONE_SUBPROC_USAGE ]] && usage
fi

if [[ $int_code -ne $ZONE_SUBPROC_OK ]]; then
	screenlog ""
	screenlog "$install_fail" "$zonename"
	screenlog ""

	#
	# Only make a reference to the log file if one will exist after
	# zoneadm exits.
	#
	[[ $int_code -ne $ZONE_SUBPROC_NOTCOMPLETE ]] &&
	    screenlog "$see_log" "$logfile"

	exit $int_code
fi

#
# After the install completes, we've likely moved a new copy of the logfile into
# place atop the logfile we WERE writing to, so if we don't reopen the logfile
# here the shell will continue writing to the old logfile's inode, meaning we
# would lose all log information from this point on.
#
exec 2>>"$logfile"

screenlog ""
screenlog "$install_good" "$zonename"
screenlog ""

echo $(gettext "Details saved to log file:")
echo "    \"$logfile\""
echo

exit $ZONE_SUBPROC_OK