lx_distro_install.ksh revision 65488c97aeb108aeffd7b61db3b2b3bcb4fc9d72
#!/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.
#
#
# This script is called from /usr/lib/brand/lx/lx_install.
#
# options passed down from lx_install:
# -z $ZONENAME
# -r $LINUX_ROOT
#
# options passed down from zoneadm -z <zone-name> install
# -d <Linux-archives-dir>
# [core | server | desktop | development | all]
#
# The desktop cluster will be installed by default.
#
# Restrict executables to /bin, /usr/bin and /usr/sbin
PATH=/bin:/usr/bin:/usr/sbin
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
}
#
# Print to the screen if the shell variable "verbose_mode" is set, but always
# send the output to the log.
#
verboselog()
{
[[ -n $verbose_mode ]] && echo "$@"
[[ -n $logfile ]] && echo "$@" >&2
}
bad_rpmdir=$(gettext "'%s' is not a valid RPM directory!")
mb_req=$(gettext "(%s MB required, %s MB available)")
no_space=$(gettext "Not enough free space available in '%s'")
inst_clust=$(gettext "Installing cluster '%s'")
unknown_clust=$(gettext "ERROR: Unknown cluster name: '%s'")
unknown_media=$(gettext "Unknown or unreadable media loaded in %s")
eject_fail=$(gettext "Attempt to eject '%s' failed.")
lofi_failed=$(gettext "Attempt to add '%s' as lofi device FAILED.")
lofs_failed=$(gettext "Attempt to lofs mount '%s' on '%s' FAILED.")
media_spec=$(gettext "the provided media (%s)")
distro_mediafail=\
$(gettext "Attempt to determine Linux distribution from\n %s FAILED.")
mini_bootfail=$(gettext "Attempt to boot miniroot for zone '%s' FAILED.")
mini_copyfail=$(gettext "Attempt to copy miniroot for zone '%s' FAILED.")
mini_initfail=$(gettext "Attempt to initialize miniroot for zone '%s' FAILED.")
mini_instfail=$(gettext "Attempt to install RPM '%s' to miniroot FAILED.")
mini_mediafail=$(gettext "Install of zone '%s' miniroot from\n %s FAILED.")
mini_setfail=$(gettext "Attempt to setup miniroot for zone '%s' FAILED.")
mini_mntfsfail=\
$(gettext "Attempt to mount miniroot filesystems for zone '%s' FAILED.")
rpm_initfail=\
$(gettext "Attempt to initialize RPM database for zone '%s' FAILED.")
symlink_failed=$(gettext "Attempt to symbolically link '%s' to '%s' FAILED.")
discinfo_nofile=$(gettext "ERROR: Discinfo file '%s' not found!")
discinfo_notreadable=$(gettext "ERROR: Discinfo file '%s': not readable!")
discinfo_wrongarch=\
$(gettext "ERROR: '%s': disc architecture is '%s'; install requires 'i386'!")
wrong_serial=$(gettext "Incorrect serial number found on provided %s.")
wrong_ser_expect=$(gettext " (found #%s, expected #%s)")
wrong_cd=$(gettext "Incorrect CD inserted (found %s, wanted %s)")
zone_initrootfail=\
$(gettext "Attempt to initialize root filesystem for zone '%s' FAILED.")
zone_haltfail=$(gettext "Unable to halt zone '%s'!")
zone_instfail=$(gettext "Install of zone '%s' from '%s' FAILED '%s'.")
zone_mediafail=$(gettext "Install of zone '%s' from\n %s FAILED.")
zone_rootfail=\
$(gettext "ERROR: The specified zone root directory '%s' could not be created.")
zone_rootsub=\
$(gettext "ERROR: The specified zone root subdirectory '%s' does not exist.")
mk_mntfail=$(gettext "Could not create the mount directory '%s'")
mountfail=$(gettext "Mount of '%s' on '%s' FAILED.")
insert_discmsg=\
$(gettext "Please insert %s, or a\n %s DVD in the removable media")
mount_proper_iso1=$(gettext "Please mount the ISO for %s or a")
mount_proper_iso2=$(gettext "%s DVD on device '%s'")
silent_nodisc=$(gettext "ERROR: Cannot install from CDs in silent mode.")
silent_nolofi=\
$(gettext "ERROR: Cannot install from lofi-based CD ISOs in silent mode.")
install_msg=$(gettext "Installing zone '%s' from\n %s.")
install_ndiscs=\
$(gettext "You will need CDs 1 - %s (or the equivalent DVD) to")
install_nisos=\
$(gettext "You will need ISO images representing CDs 1 - %s (or the equivalent")
locate_npkgs=$(gettext "Attempting to locate %s packages...")
install_one_rpm=$(gettext "Installing 1 %spackage.")
install_nrpms_few=\
$(gettext "Installing %s %spackages; this may take a few minutes...")
install_nrpms_several=\
$(gettext "Installing %s %spackages; this may take several minutes...")
install_longwait=\
$(gettext "NOTE: There may be a long delay before you see further output.")
install_defmkfail=$(gettext "Could not create the temporary directory '%s'")
install_defcpfail=$(gettext "Could not make a local copy of deferred RPM '%s'")
install_dist=$(gettext "Installing distribution '%s'...")
install_zonefail=$(gettext "Attempt to install zone '%s' FAILED.")
no_distropath=$(gettext "ERROR: Distribution path '%s' doesn't exist.")
install_done=$(gettext "Installation of %s to zone\n '%s' completed %s.")
install_failed=$(gettext "Installation of %s to zone\n '%s' FAILED %s.")
eject_final_msg=\
$(gettext "Would you like the system to eject the %sinstall %s when")
eject_final_prompt=$(gettext "installation of '%s' is complete? (%s)")
eject_final_status=$(gettext "The %sinstall %s %s be ejected.")
#
# Get the device underlying a specified mounted file system and return it in
# the shell variable "mount_dev"
#
# Returns 0 on success, 1 on failure.
#
get_mountdev()
{
typeset mount_dir="$1"
typeset device
unset mount_dev
#
# Obtain information on the specified mounted device.
#
device=`{ df -k "$mount_dir" | egrep "^/" ; } 2>/dev/null` || return 1
mount_dev=$(echo $device | awk -e '{print $1}' 2>/dev/null)
[[ "`echo $mount_dev | cut -c 1`" = "/" ]] && return 0
unset mount_dev
return 1
}
#
# Get the directory name a specified device is mounted as and return it in
# the shell variable "mount_dir"
#
# Returns 0 on success, 1 on failre.
#
get_mountdir()
{
typeset mount_dev="$1"
typeset dir
unset mount_dir
[[ -b "$mount_dev" ]] || return 1
#
# Obtain information on the specified mounted device.
#
dir=`{ df -k "$mount_dev" | egrep "^/" ; } 2>/dev/null` || return 1
mount_dir=$(echo $dir | awk -e '{print $6}' 2>/dev/null)
[[ "`echo $mount_dir | cut -c 1`" = "/" ]] && return 0
unset mount_dir
return 1
}
#
# Check the free disk space of the passed filesystem against the passed
# argument.
#
# Returns 0 on success, 1 on failure.
#
check_mbfree()
{
typeset dir="$1"
typeset mb_required=$2
#
# Return free space in partition containing passed argument in MB
#
typeset mbfree=`{ LC_ALL=C df -k "$dir" | \
egrep -v Filesystem ; } 2>/dev/null` || return 1
mbfree=$(echo $mbfree | awk -e '{print $4}' 2>/dev/null)
((mbfree /= 1024))
if ((mbfree < mb_required)); then
screenlog "$no_space" "$zoneroot"
screenlog "$mb_req" "$mb_required" "$mb_free"
return 1
fi
return 0
}
#
# Find packages by attempting to expand passed RPM names to their full filenames
# in the passed RPM directory.
#
# Arguments:
#
# Argument 1: Path to mounted install media
# Arguments [2 - n]: RPM names to process
#
# The expanded filenames are returned in the shell array "rpm_names."
#
# For example:
#
# find_packages /mnt/iso dev kernel tetex redhat-menus
#
# would return something like:
#
# rpms_found[0]: dev-3.3.12.3-1.centos.0.i386.rpm
# rpms_found[1]: kernel-2.4.21-32.EL.i586.rpm
# rpms_found[2]: tetex-1.0.7-67.7.i386.rpm
# rpms_found[3]: redhat-menus-0.39-1.noarch.rpm
#
# The routine returns 0 on success, 1 on an error.
#
find_packages()
{
typeset found=0
typeset left=0
typeset rpmdir="$1/$rd_rpmdir"
typeset curdir=${PWD:=$(pwd)}
typeset arch
typeset procinfo
typeset rpmglob
typeset rpmfile
unset rpms_found
unset rpms_left
shift
cd "$rpmdir"
typeset rpmcheck="$(echo *.rpm)"
if [[ "$rpmcheck" = "*.rpm" ]]; then
screenlog "$bad_rpmdir" "$rpmdir"
cd "$curdir"
return 1
fi
#
# If the miniroot is booted, and the archs list isn't already set,
# ask the zone's rpm command for the list of compatible architectures.
#
if [[ -n $miniroot_booted && -z $archs ]]; then
procinfo=$(zlogin "$zonename" /bin/rpm --showrc | \
grep "^compatible archs")
[[ $? -eq 0 ]] &&
archs=$(echo $procinfo | sed 's/^compatible archs : //')
[[ -n $archs ]] &&
log "RPM-reported compatible architectures: $archs"
fi
#
# Either the miniroot isn't booted or asking rpm for the information
# failed for some reason, so make some reasonable assumptions.
#
if [[ -z $archs ]]; then
procinfo=$(LC_ALL=C psrinfo -vp | grep family)
#
# Check for additional processor capabilities
#
if [[ "$procinfo" = *" family 6 "* ||
"$procinfo" = *" family 15 "* ||
"$procinfo" = *" family 16 "* ||
"$procinfo" = *" family 17 "* ]]; then
if [[ "$procinfo" = *AuthenticAMD* ]]; then
#
# Linux gives "athlon" packages precedence
# over "i686" packages, so duplicate that
# here.
#
archs="athlon i686"
else
archs="i686"
fi
fi
archs="$archs i586 i486 i386 noarch"
log "Derived compatible architectures: $archs"
fi
verboselog "RPM source directory:\n \"$rpmdir\"\n"
if [[ $# -eq 1 ]]; then
msg=$(gettext "Attempting to locate 1 package...")
screenlog "$msg"
else
screenlog "$locate_npkgs" "$#"
fi
for rpm in "$@"; do
#
# Search for the appropriate RPM, using the compatible
# architecture list contained in "archs" to look for the best
# match.
#
# For example, if the processor is an i686, and the rpm is
# "glibc", the script will look for the files (in order):
#
# glibc[.-][0-9]*.i686.rpm
# glibc[.-][0-9]*.i586.rpm
# glibc[.-][0-9]*.i486.rpm
# glibc[.-][0-9]*.i386.rpm
# glibc[.-][0-9]*.noarch.rpm
# glibc[.-][0-9]*.fat.rpm
#
# and will stop when it finds the first match.
#
# TODO: Once the miniroot is booted, we should verify that
# the rpm name has been expanded to "$rpmfile" properly
# by comparing "$rpm" and the output of:
#
# zlogin -z <zone> /bin/rpm --qf '%{NAME}' -qp $rpmfile
#
for arch in $archs; do
#
# Use the filename globbing functionality of ksh's
# echo command to search for the file we want.
#
# If no matching file is found, echo will simply
# return the passed string.
#
rpmglob="$rpm[.-][0-9]*.$arch.rpm"
rpmfile="$(echo $rpmglob)"
[[ "$rpmfile" != "$rpmglob" ]] && break
unset rpmfile
done
if [[ -z $rpmfile ]]; then
rpms_left[$left]="$rpm"
((left += 1))
else
rpms_found[$found]="$rpmfile"
((found += 1))
fi
done
cd "$curdir"
log "\"$rpmdir\": matched $found of $# packages."
log "\"$rpmdir\": $left RPMs remaining."
return 0
}
#
# Build the rpm lists used to install a machine.
#
# The first argument is the number of discs in the distribution. The
# second, optional, argument is the metacluster to install.
#
# The array "distro_rpm[]" is built from the individual package RPM arrays
# read in from an individual distribution definition file.
#
build_rpm_list()
{
# Default to a desktop installation
typeset cluster=desktop
typeset cnt=0
typeset pkgs
for clust in "$@"; do
((cnt += 1))
case $clust in
core) cluster=core ;;
desk*) cluster=desktop ;;
serv*) cluster=server ;;
dev*) cluster=developer ;;
all) cluster=all
break;;
*) screenlog "$unknown_clust" "$clust"
exit $ZONE_SUBPROC_USAGE ;;
esac
done
if [ $cnt -gt 1 ]; then
msg=$(gettext "Too many install clusters specified")
screenlog "$msg"
exit $ZONE_SUBPROC_USAGE
fi
screenlog "$inst_clust" $cluster
case $cluster in
core) distro_rpms=$distro_core_rpms ;;
desktop) distro_rpms=$distro_desktop_rpms ;;
server) distro_rpms=$distro_server_rpms ;;
developer) distro_rpms=$distro_developer_rpms ;;
all) distro_rpms=$distro_all_rpms ;;
esac
# The RPMs in the miniroot must all be installed properly as well
distro_rpms="$distro_miniroot_rpms $distro_rpms"
}
#
# Install the "miniroot" minimal Linux environment that is booted single-user
# to complete the install.
#
# This works by doing feeding the RPM list needed for the installation one
# by one to rpm2cpio(1).
#
# Usage:
# install_miniroot <mounted media dir> <names of RPMS to install>
#
#
install_miniroot()
{
typeset mediadir="$1"
typeset rpm
shift
#
# There's a quirk in our version of ksh that sometimes resets the
# trap handler for the shell. Since RPM operations will be the
# longest part of any given install, make sure that an interrupt while
# the command is running will bring the miniroot down and clean up
# the interrupted install.
#
trap trap_cleanup INT
if [[ $# -eq 1 ]]; then
msg=$(gettext "Installing %s miniroot package...")
else
msg=$(gettext "Installing %s miniroot packages...")
fi
screenlog "\n$msg" "$#"
for rpm in "$@"; do
verboselog "\nInstalling \"$rpm\" to miniroot at\n" \
" \"$zoneroot\"..."
rpm2cpio "$mediadir/$rd_rpmdir/$rpm" | \
( cd "$rootdir" && cpio -idu ) 1>&2
if [[ $? -ne 0 ]]; then
screenlog "$mini_instfail" "$rpm"
return 1
fi
done
screenlog ""
return 0
}
#
# Install the zone from the mounted disc image by feeding a list of RPMs to
# install from this image to RPM running on the zone via zlogin(1).
#
# Usage:
# install_zone <path to mounted install media> [<names of RPMS to install>]
#
# If the caller doesn't supply a list of RPMs to install, we install any
# we previously stashed away in the deferred RPMs directory.
#
install_zone()
{
#
# Convert the passed install media pathname to a zone-relative path
# by stripping $rootpath from the head of the path.
#
typeset zonerpmdir="${1##$rootdir}/$rd_rpmdir"
typeset defdir="$rootdir/var/lx_install/deferred_rpms"
typeset mounted_root="$1"
typeset rpmopts="-i"
typeset defer
typeset deferred_found
typeset install_rpms
typeset nrpms
typeset rpm
typeset rpmerr
shift
#
# If the caller provided a list of RPMs, determine which of them
# should be installed now, and which should be deferred until
# later.
#
if [[ $# -gt 0 ]]; then
if [[ -n $deferred_rpms ]]; then
[[ -d $defdir ]] || if ! mkdir -p $defdir; then
screenlog "$install_defmkfail" "$mntdir"
return 1
fi
msg=$(gettext "Checking for deferred packages...")
screenlog "$msg"
find_packages "$mounted_root" $deferred_rpms
deferred_found="${rpms_found[@]}"
numdeferred=${#rpms_found[@]}
else
deferred_found=""
fi
install_rpms="$@"
nrpms=$#
#
# If this distro has any deferred RPMs, we want to simply
# copy them into the zone instead of installing them. We
# then remove them from the list of RPMs to be installed on
# this pass.
#
for rpm in $deferred_found; do
if echo "$install_rpms" | egrep -s "$rpm"; then
verboselog "Deferring installation of \"$rpm\""
#
# Remove the RPM from the install_rpms list
# and append it to the deferred_saved array
#
install_rpms=$(echo "$install_rpms " |
sed "s/ $rpm / /g")
# remove trailing spaces, if any
install_rpms=${install_rpms%%+( )}
deferred_saved[${#deferred_saved[@]}]="$rpm"
if ! cp "$mounted_root/$rd_rpmdir/$rpm" \
"$defdir"; then
screenlog "$install_defcpfail" "$rpm"
return 1
fi
fi
#
# If we've deferred the installation of EVERYTHING,
# simply return success
#
[[ -z $install_rpms ]] && return 0
done
[[ -n $deferred_found ]] & verbose ""
elif [[ -z $deferred_saved ]]; then
# There are no deferred RPMs to install, so we're done.
return 0
else
# Install the RPMs listed in the deferred_saved array
install_rpms=${deferred_saved[@]}
nrpms=${#deferred_saved[@]}
zonerpmdir=/var/lx_install/deferred_rpms
defer="deferred "
fi
#
# There's a quirk in our version of ksh that sometimes resets the
# trap handler for the shell. Since RPM operations will be the
# longest part of any given install, make sure that an interrupt while
# the command is running will bring the miniroot down and clean up
# the interrupted install.
#
trap trap_cleanup INT
#
# Print a message depending on how many RPMS we have to install.
#
# 25 RPMS seems like a reasonable boundary between when an install may
# take a "few" or "several" minutes; this may be tuned if needed.
#
screenlog ""
if [[ $nrpms -eq 1 ]]; then
screenlog "$install_one_rpm" "$defer"
elif [[ $nrpms -lt 25 ]]; then
screenlog "$install_nrpms_few" "$nrpms" "$defer"
else
screenlog "$install_nrpms_several" "$nrpms" "$defer"
#
# For installs of over 600 packages or so, it can take rpm a
# really, REALLY long time to output anything, even when
# running in verbose mode.
#
# For example, when doing an "all" install from a DVD or DVD
# ISO, depending on the speed of the optical drive and the
# speed of the machine's CPU(s), it may be up to TEN MINUTES or
# MORE before rpm prints out its "Processing..." message even
# though it is, in fact, processing the entire package list,
# checking for dependencies (something it is unfortunately
# entirely silent about.)
#
# Since the user might otherwise think the install was hung
# when running in verbose mode, warn them that it could be
# quite a while before they see any further output from the
# installer.
#
#
[[ $nrpms -gt 600 ]] && verbose "$install_longwait"
fi
log ""
log "Installing: $install_rpms"
log ""
log "NOTE: Any messages appearing below prefixed with \"warning:\""
log " and/or that do not cause the installer to abort the"
log " installation process may safely be ignored."
log ""
echo
# If verbose mode is selected, run rpm in verbose mode as well.
[[ -n $verbose_mode ]] && rpmopts="-ivh"
#
# LX_INSTALL must be defined when running this command in order to
# enable switches built into various emulated system calls to allow
# the dev package (which may not actually write to /dev) to function.
#
zlogin "$zonename" "( cd "$zonerpmdir" ; LX_INSTALL=1 \
/bin/rpm $rpmopts --force --aid --nosignature --root /a \
$install_rpms )"
rpmerr=$?
if [[ $rpmerr -ne 0 ]]; then
log ""
log "Zone rpm install command exited abnormally, code $rpmerr"
log ""
screenlog "$zone_instfail" "$zonename" "$zonerpmdir" "$rpmerr"
return 1
fi
log ""
log "$nrpms package(s) installed."
return 0
}
#
# Attempt to unmount all file systems passed on the command line
#
# Returns 0 if all umounts succeeded, otherwise the number of umount failures
#
umount_list()
{
typeset failures=0
typeset mounted
unset umount_failures
for mounted in "$@"; do
if ! umount "$mounted"; then
umount_failures="$umount_failures $mounted"
((failures += 1))
fi
done
return $failures
}
#
#
# Set up lofi mounts required for chroot(1M) to work on a new root directory
# located in /a within a zone.
#
newroot_lofimnt()
{
typeset dev
typeset mounted
typeset target
unset newroot_mounted
#
# /usr and /lib get lofs mounted in the zone on /native read-only
#
# $zoneroot/dev gets lofs mounted on /native/dev read/write to allow
# the use of native devices.
#
mount -F lofs -r /lib "$rootdir/a/native/lib" || return 1
newroot_mounted="$rootdir/a/native/lib"
if ! mount -F lofs -r /usr "$rootdir/a/native/usr"; then
umount "$rootdir/a/native/lib"
unset newroot_mounted
return 1
fi
newroot_mounted="$newroot_mounted $rootdir/a/native/usr"
if ! mount -F lofs "$zoneroot/root/native/dev" \
"$rootdir/a/native/dev"; then
umount_list $newroot_mounted
unset newroot_mounted
return 1
fi
newroot_mounted="$newroot_mounted $rootdir/a/native/dev"
#
# This is a bit ugly; to provide device access within the chrooted
# environment RPM will use for its install, we will create the same
# symlinks "$rootdir/dev" contains in the new dev directory, and will
# lofs mount the balance of "$rootdir/dev" into the same locations in
# /dev in the new filesystem we're installing to.
#
for dev in "$zoneroot"/root/dev/*
do
if [[ "$dev" = "$zoneroot/root/dev/*" ]]; then
log "ERROR: No files found in $zoneroot/root/dev"
umount_list $newroot_mounted
return 1
fi
target="$rootdir/a/dev/$(basename $dev)"
#
# If the device file is a symbolic link, create a new link
# in the target directory with the same source.
#
# If the device file is any other file or directory, lofs
# mount it from the device directory into the target directory.
#
if [[ -h $dev ]]; then
typeset source=$(LC_ALL=C file -h "$dev")
#
# Remove extraneous text from the output of file(1) so
# we're left only with the target path of the symbolic
# link.
#
source="${source##*link to }"
[[ -a "$target" ]] && /bin/rm -f "$target"
if ! ln -s "$source" "$target"; then
screenlog "$symlink_failed" "$source" "$target"
umount_list $newroot_mounted
unset newroot_mounted
return 1
fi
else
[[ ! -a "$target" ]] && touch "$target"
if ! mount -F lofs "$dev" "$target"; then
screenlog "$lofs_failed" "$dev" "$target"
umount_list $newroot_mounted
unset newroot_mounted
return 1
fi
newroot_mounted="$newroot_mounted $target"
fi
done
return 0
}
#
# Replace the root directory of a zone with the duplicate previously created
# in the zone's /a directory.
#
replace_miniroot()
{
#
# The zoneadm halt will automatically unmount any file systems
# mounted via lofs in the zone, so that saves us from having to
# methodically unmount each one.
#
if ! zoneadm -z "$zonename" halt; then
screenlog "$zone_haltfail" "$zonename"
return 1
fi
unset miniroot_booted
unset newroot_mounted
[[ -d "$zoneroot/a" ]] && rm -rf "$zoneroot/a"
[[ -d "$zoneroot/oldroot" ]] && rm -rf "$zoneroot/oldroot"
#
# Copy the logfile or we'll lose all details of the install into the
# new root directory, so strip "$zoneroot" off the pathname of the
# current logfile and use it to generate the pathname of the log file
# in the new root directory.
#
[[ -n $logfile && -f "$logfile" ]] &&
cp "$logfile" "$rootdir/a${logfile##$rootdir}"
mv -f "$rootdir/a" "$zoneroot/a" || return 1
mv -f "$rootdir" "$zoneroot/oldroot" || return 1
mv -f "$zoneroot/a" "$rootdir" || return 1
#
# After the directory munging above, we've moved the new copy of the
# logfile 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.
#
[[ -n $logfile ]] && exec 2>>"$logfile"
rm -rf "$zoneroot/oldroot"
#
# Remove the contents of the /dev directory created by the install.
#
# We don't technically need to do this, but the zone infrastructure
# will mount $zoneroot/dev atop $rootdir/dev anyway, hiding its
# contents so we may as well clean up after ourselves.
#
# The extra checks are some basic paranoia due to the potentially
# dangerous nature of this command but are not intended to catch all
# malicious cases
#
[[ "$rootdir" != "" && "$rootdir" != "/" ]] && rm -rf "$rootdir"/dev/*
return 0
}
setup_miniroot()
{
unset miniroot_booted
if ! "$cwd/lx_init_zone" "$rootdir" mini; then
screenlog "$mini_initfail" "$zonename"
return 1
fi
if ! copy_miniroot; then
screenlog "$mini_copyfail" "$zonename"
return 1
fi
#
# zoneadm gets upset if the zone root directory is group or world
# readable or executable, so make sure it isn't before proceeding.
#
chmod 0700 "$zoneroot"
msg=$(gettext "Booting zone miniroot...")
screenlog "$msg"
if ! zoneadm -z "$zonename" boot -f; then
screenlog "$mini_bootfail" "$zonename"
return 1
fi
miniroot_booted=1
#
# Now that the miniroot is booted, unset the compatible architecture
# list that find_packages was using for the miniroot so that it will
# get the list from rpm for the full install.
#
unset archs
#
# Mount all the filesystems needed to install the new root
# directory.
#
if ! newroot_lofimnt; then
screenlog "$mini_mntfsfail" "$zonename"
if [[ -n $newroot_mounted ]]; then
umount_list $newroot_mounted
unset newroot_mounted
fi
return 1
fi
#
# Attempt to initialize the RPM database for the new zone
#
if ! zlogin "$zonename" /bin/rpm --initdb --root /a; then
screenlog "$rpm_initfail" "$zonename"
return 1
fi
msg=$(gettext "Miniroot zone setup complete.")
screenlog "$msg"
return 0
}
finish_install()
{
#
# Perform some last cleanup tasks on the newly installed zone.
#
# Note that the zlogin commands aren't checked for errors, as the
# newly installed zone will still boot even if the commands fail.
#
typeset file
typeset defdir=$rootdir/var/lx_install/deferred_rpms
msg=$(gettext "Completing installation; this may take a few minutes.")
screenlog "$msg"
if [[ -d $defdir ]]; then
rm -f $defdir/*.rpm
rmdir $defdir
fi
# Run ldconfig in the new root
zlogin "$zonename" /usr/sbin/chroot /a \
/sbin/ldconfig -f /etc/ld.so.conf
#
# Create the /etc/shadow and /etc/gshadow files if they don't already
# exist
#
[[ -a "$rootdir/a/etc/shadow" ]] ||
zlogin "$zonename" /usr/sbin/chroot /a /usr/sbin/pwconv
[[ -a "$rootdir/a/etc/gshadow" ]] ||
zlogin "$zonename" /usr/sbin/chroot /a /usr/sbin/grpconv
#
# Make sure all init.d and rc[0-6].d links are set up properly.
#
for file in `ls "$rootdir/a/etc/init.d"`; do
zlogin "$zonename" /usr/sbin/chroot /a \
/sbin/chkconfig --del $file > /dev/null 2>&1
zlogin "$zonename" /usr/sbin/chroot /a \
/sbin/chkconfig --add $file > /dev/null 2>&1
done
replace_miniroot
rmdir -ps "$media_mntdir"
if ! "$cwd/lx_init_zone" "$rootdir"; then
screenlog "$zone_initrootfail" "$zonename"
return 1
fi
return 0
}
#
# Duplicate the installed "miniroot" image in a subdirectory of the base
# directory of the zone.
#
# This is done so that a new root directory can be created that will be used
# as the root of a chrooted directory that RPM running on the zone will install
# into.
#
copy_miniroot()
{
#
# Create the directory $zoneroot/a if it doesn't already exist
#
[[ -d "$zoneroot/a" ]] ||
{ mkdir -p "$zoneroot/a" || return 1 ; }
msg=$(gettext "Duplicating miniroot; this may take a few minutes...")
screenlog "$msg"
#
# Duplicate the miniroot to /a, but don't copy over any /etc/rc.d or
# lxsave_ files.
#
( cd "$rootdir"; find . -print | egrep -v "/etc/rc\.d|lxsave_" | \
cpio -pdm ../a )
[[ -d "$rootdir/a" ]] && rm -rf "$rootdir/a" 2>/dev/null
mv -f "$zoneroot/a" "$rootdir/a" || return 1
return 0
}
#
# Read the first six lines of the .discinfo file from the root of the passed
# disc directory (which should either be a mounted disc or ISO file.)
#
# The read lines will be used to set appropriate shell variables on success:
#
# rd_line[0]: Disc Set Serial Number (sets rd_serial)
# rd_line[1]: Distribution Release Name (sets rd_release)
# rd_line[2]: Distribution Architecture (sets rd_arch)
# rd_line[3]: Disc Number$[s] in Distribution (sets rd_cdnum)
# rd_line[4]: "base" directory for disc (currently unused)
# rd_line[5]: RPM directory for disc (sets rd_rpmdir)
#
# Returns 0 on success, 1 on failure.
#
read_discinfo()
{
typeset rd_file="$1/.discinfo"
unset rd_arch
unset rd_cdnum
unset rd_disctype
unset rd_pers
unset rd_release
unset rd_rpmdir
unset rd_serial
#
# If more than one argument was passed to read_discinfo, the second
# is a flag meaning that we should NOT print a warning message if
# we don't find a .discinfo file, as this is just a test to see if
# a distribution ISO is already mounted on the passed mount point.
#
if [[ ! -f "$rd_file" ]]; then
[[ $# -eq 1 ]] &&
screenlog "$discinfo_nofile" "$rd_file"
return 1
fi
verbose "Attempting to read \"$rd_file\"..."
if [[ ! -r "$rd_file" ]]; then
screenlog "$discinfo_notreadable" "$rd_file"
return 1
fi
typeset rd_line
typeset linenum=0
while read -r rd_line[$linenum]; do
#
# If .discinfo architecture isn't "i386," fail here as
# we only support i386 distros at this time.
#
if [[ $linenum = 2 && "${rd_line[2]}" != "i386" ]]; then
screenlog "$discinfo_wrongarch" "$rd_file" \
"${rd_line[2]}"
return 1
fi
#
# We've successfully read the first six lines of .discinfo
# into $rd_line, so do the appropriate shell variable munging.
#
if ((linenum == 5)); then
rd_serial=${rd_line[0]}
rd_release=${rd_line[1]}
# CentOS names their releases "final"
[[ "$rd_release" = "final" ]] && rd_release="CentOS"
#
# Line four of the .discinfo file contains either a
# single disc number for a CD or a comma delimited list
# representing the CDs contained on a particular DVD.
#
rd_cdnum=${rd_line[3]}
if [[ "$rd_cdnum" = *,* ]]; then
rd_disctype="DVD"
else
rd_disctype="CD"
fi
rd_rpmdir=${rd_line[5]}
#
# If the specified RPM directory doesn't exist, this is
# not a valid binary RPM disc (it's most likely a
# source RPM disc), so don't add it to the list of
# valid ISO files.
#
[[ ! -d "$1/$rd_rpmdir" ]] && return 1
if [[ "$rd_cdnum" = "1" &&
"$rd_release" = "Red Hat"* ]]; then
typeset rh_glob
#
# If this is a Red Hat release, get its
# personality name from the name of the
# redhat-release RPM package.
#
# Start by looking for the file
# "redhat-release-*.rpm" in the directory
# RedHat/RPMS of the ISO we're examining by
# using ksh's "echo" command to handle
# filename globbing.
#
# If no matching file is found, echo will
# simply return the passed string.
#
rh_glob="$1/RedHat/RPMS/redhat-release-*.rpm"
rd_pers="$(echo $rh_glob)"
if [[ "$rd_pers" != "$rh_glob" ]]; then
#
# An appropriate file was found, so
# extract the personality type from the
# filename.
#
# For example, the presence of the file:
#
# redhat-release-3WS-13.5.1.i386.rpm
#
# would indicate the ISO either
# represents a "WS" personality CD or
# a "WS" installation DVD.
#
# Start the extraction by deleting the
# pathname up to the personality type.
#
rh_glob="*/redhat-release-[0-9]"
rd_pers="${rd_pers##$rh_glob}"
#
# Now remove the trailing portion of the
# pathname to leave only the personality
# type, such as "WS" or "ES."
#
rd_pers="${rd_pers%%-*\.rpm}"
else
unset rd_pers
fi
fi
return 0
fi
((linenum += 1))
done < "$rd_file"
#
# The file didn't have at least six lines, so indicate that parsing
# failed.
#
return 1
}
#
# Mount install media within the zone.
#
# The media will be mounted at $zoneroot/root/media, either via a loopback
# mount (if it's a managed removable disc) or directly (if the media is an ISO
# file or if the specified filename is a block device.)
#
# Returns 0 on success, 1 on failure, 2 if no disc was available
#
mount_install_media()
{
typeset device="$1"
typeset mount_err
unset removable
unset zone_mounted
[[ -z $mntdir ]] && return 1
[[ -d $mntdir ]] || if ! mkdir -p $mntdir; then
screenlog "$mk_mntfail" "$mntdir"
unset mntdir
return 1
fi
if [[ "$install_media" = "disc" && "$managed_removable" = "1" ]]; then
#
# The removable disc device is an automatically managed one,
# so just wait for the device mounter to notice a disc has been
# inserted into the drive and for the disc to appear at the
# mount point.
#
typeset mount_interval=2
typeset mount_timeout=10
typeset mount_timer=0
typeset nickname=$(basename $device)
eject -q "$nickname" > /dev/null 2>&1 || return 2
removable="$nickname"
#
# Double check that the device was mounted. If it wasn't, that
# usually means the disc in the drive isn't in a format we can
# read or the physical disc is unreadable in some way.
#
# The mount_timer loop is needed because the "eject -q" above
# may report a disc is available before the mounter associated
# with the drive actually gets around to mounting the device,
# so we need to give it a chance to do so. The mount_interval
# allows us to short-circuit the timer loop as soon as the
# device is mounted.
#
while ((mount_timer < mount_timeout)); do
[[ -d "$device" ]] && break
sleep $mount_interval
((mount_timer += mount_interval))
done
if [[ ! -d "$device" ]]; then
screenlog "\n$unknown_media" "$device"
return 2
fi
mount -F lofs -r "$device" "$mntdir"
mount_err=$?
else
#
# Attempt to mount the media manually.
#
# First, make sure the passed device name really IS a device.
#
[[ -b "$device" ]] || return 2
#
# Now check to see if the device is already mounted and lofi
# mount the existing mount point into the zone if it is.
#
if get_mountdir "$device"; then
mount -F lofs -r "$mount_dir" "$mntdir"
mount_err=$?
else
[[ "$install_media" = "disc" ]] && removable="$device"
# It wasn't mounted, so go ahead and try to do so.
mount -F hsfs -r "$device" "$mntdir"
mount_err=$?
fi
# A mount_err of 33 means no suitable media was found
((mount_err == 33)) && return 2
fi
if ((mount_err != 0)); then
screenlog "$mountfail" "$device" "$mntdir"
unset mntdir
return 1
fi
zone_mounted="$mntdir"
verbose "Mount of \"$device\" on \"$mntdir\" succeeded."
return 0
}
# Eject the disc mounted on the passed directory name
eject_removable_disc()
{
screenlog ""
verbose " (Attempting to eject '$removable'... \c"
if [[ -n $zone_mounted ]]; then
umount "$zone_mounted"
unset zone_mounted
fi
if ! eject "$removable"; then
verbose "failed.)\n"
screenlog "$eject_fail" "$removable"
msg=$(gettext "Please eject the disc manually.")
screenlog "$msg"
else
verbose "done.)\n"
fi
unset removable
}
#
# Ask for the user to provide a disc or ISO.
#
# Returns 0 on success, 1 on failure.
#
prompt_for_media()
{
# No prompting is allowed in silent mode.
if [[ -n $silent_mode ]]; then
log "$silent_err_msg"
return 1
fi
if [[ "$1" != "" ]]; then
msg="$release_name, CD $1"
else
typeset disc=$(gettext "disc")
msg=$(gettext "any")
msg="$msg $release_name $disc"
fi
if [[ "$install_media" = "disc" ]]; then
screenlog "$insert_discmsg" "$msg" "$release_name"
msg=$(gettext "drive and press <RETURN>.")
screenlog " $msg"
[[ -n $removable ]] && eject_removable_disc
else
if [[ -n $zone_mounted ]]; then
umount "$mntdir"
unset zone_mounted
fi
#
# This is only be printed in the case of a user
# specifying a device name as an install medium.
# This is handy for testing the installer or if the user
# has ISOs stored in some strange way that somehow
# breaks the "install from ISO" mechanism, as ISOs
# can be manually added using lofiadm(1M) command and
# the resulting lofi device name passed to the
# installer.
#
screenlog "$mount_proper_iso1" "$msg"
screenlog " $mount_proper_iso2" "$release_name" "$mntdev"
msg=$(gettext "and press <RETURN>.")
screenlog " $msg"
fi
read && return 0
return 1
}
#
# Get a particular CD of a multi-disc set.
#
# This basically works by doing the following:
#
# 1) Mount the disc
# 2) Read the disc's .discinfo file to see which CD it is or represents
# 3) If it doesn't contain the desired CD, ask the user for a disc
# containing the CD we wanted.
#
# Returns 0 on success, 1 on failure.
#
get_cd()
{
typeset mntdev="$1"
typeset cdnum
typeset discname
typeset enter
typeset mount_err
typeset prompted
if [[ $# -eq 2 ]]; then
# Caller specified a particular CD to look for
cdnum="$2"
discname="$release_name, CD $cdnum"
else
# Caller wanted any disc
discname="a $release_name disc"
fi
verboselog "\nChecking for $discname on device"
verboselog " \"$mntdev\"\n"
while :; do
# Check to see if a distro disc is already mounted
mntdir="$media_mntdir"
unset rd_disctype
if ! read_discinfo "$mntdir" "test"; then
mount_install_media "$mntdev"
mount_err=$?
#
# If the mount succeeded, continue on in the main
# script
#
if ((mount_err == 0)); then
read_discinfo "$mntdir"
elif ((mount_err == 2)); then
# No medium was found, so prompt for one.
prompt_for_media "$cdnum" && prompted=1 continue
unset mntdir
return 1
else
# mount failed
unset mntdir
return 1
fi
fi
if [[ -n $distro_serial &&
"$rd_serial" != "$distro_serial" ]]; then
screenlog "$wrong_serial" "$install_disctype"
screenlog " $wrong_ser_expect" "$rd_serial" \
"$distro_serial"
#
# If we're installing from ISOs, don't prompt the user
# if the wrong serial number is present, as there's
# nothing they can do about it.
#
[[ "$install_media" = "ISO" ]] && return 1
prompt_for_media "$cdnum" && continue
umount "$mntdir"
unset zone_mountdir
return 1
fi
#
# Make sure that the mounted media is CD $cdnum.
#
# If it is, return to the caller, otherwise eject the
# disc and try again.
#
if [[ "$rd_disctype" = "CD" ]]; then
verboselog "Found CD #$rd_cdnum," \
"Serial #$rd_serial"
verboselog "Release Name \"$rd_release\""
[[ -n $rd_pers ]] &&
verboselog "Detected RedHat Personality" \
"\"$rd_pers\""
verboselog ""
# If we didn't care which CD it was, return success
[[ "$cdnum" = "" ]] && return 0
# Return if the CD number read is a match
[[ "$rd_cdnum" = "$cdnum" ]] && return 0
else
verboselog "\nFound DVD (representing CDs" \
"$rd_cdnum), Serial #$rd_serial"
verboselog "Release Name \"$rd_release\"\n"
[[ -n $rd_pers ]] &&
verboselog "Detected RedHat Personality" \
"\"$rd_pers\""
verboselog ""
# If we didn't care which CD it was, return success
[[ "$cdnum" = "" ]] && return 0
#
# Since a DVD represents multiple CDs, make sure the
# DVD inserted represents the CD we want.
#
{ echo "$rd_cdnum," | egrep -s "$cdnum," ; } &&
return 0
fi
if [[ -n $prompted ]]; then
if [[ "$rd_disctype" = "CD" ]]; then
screenlog "$wrong_cd" "$rd_cdnum" "$cdnum"
else
msg=$(gettext "Incorrect DVD inserted.")
screenlog "$msg"
log "(DVD represented CDs $rd_cdnum," \
" wanted CD $cdnum)"
fi
fi
#
# If we're installing from ISOs, don't prompt the user if the
# wrong CD is mounted, as there's nothing they can do about it.
#
[[ "$install_media" = "ISO" ]] && return 1
prompt_for_media "$cdnum" && prompted=1 && continue
umount "$mntdir"
unset zone_mountdir
return 1
done
}
#
# Find out which distro the mounted disc belongs to by comparing the
# mounted disc's serial number against those contained in the various
# distro files.
#
# When a match is found, the shell variable "distro_file" will be set to
# the name of the matching file. Since that will have been the last file
# sourced by the shell, there's no need for the caller to do it again; the
# variable is only set in case it's of some use later.
#
# Returns 0 on success, 1 on failure.
#
get_disc_distro()
{
typeset distro
typeset distro_files="$(echo $distro_dir/*.distro)"
unset distro_file
[[ "$distro_files" = "$distro_dir/*.distro" ]] && return 1
for distro in $distro_files; do
[[ ! -f "$distro" ]] && continue
verbose "Checking for disc distro \"$distro\"..."
. "$distro" > /dev/null
[[ "$rd_serial" != "$distro_serial" ]] && continue
distro_file="$distro"
release_name="$rd_release $distro_version"
distro_ncds=${#distro_cdorder[@]}
return 0
done
return 1
}
#
# Iterate through the install media to install the miniroot and full zone
#
# The install media may be physical discs, a lofi mounted ISO file, or
# iso files located in a directory specified by the user.
#
# All installations, regardless of media type, use a CD as their basic media
# unit. DVDs or ISOs representing DVDs actually contain multiple "CDs" of
# installation packages.
#
# The variable "distro_ncds," as set elsewhere, represents the number
# of CDs required to install the distribution. Whether the installation
# actually requires multiple physical discs or ISOs depends upon their content.
#
# Returns 0 on success, 1 on failure.
#
iterate_media()
{
typeset cdnum=1
typeset cds
typeset disc_rpms
typeset err_media
typeset err_msg
typeset install_type="$1"
typeset ldevs
typeset mountdev
typeset rh_pers
shift
if [[ "$install_type" = "miniroot" ]]; then
typeset i
disc_rpms=$distro_miniroot_rpms
err_msg="$mini_mediafail"
# For miniroot installs, ask for CDs in numerical order
cds[0]="zero_pad"
for i in ${distro_cdorder[@]}; do
cds[$cdnum]=$cdnum
((cdnum += 1))
done
cdnum=1
else
disc_rpms=$distro_rpms
err_msg="$zone_mediafail"
#
# For full zone installs, ask for CDs in the order RPM needs
# to find the packages.
#
set -A cds "zero_pad" ${distro_cdorder[@]}
fi
if [[ "$install_media" = "ISO" ]]; then
set -A ldevs "zero_pad" "$@"
else
mountdev="$1"
err_media="$release_name, CD ${cds[$cdnum]} (or DVD)"
fi
unset rpms_left_save
while ((cdnum <= distro_ncds)); do
[[ -z ${cds[$cdnum]} ]] && ((cdnum += 1)) && continue
if [[ "$install_media" = "ISO" ]]; then
typeset isonum="${cds[$cdnum]}"
#
# If this routine was called with a single ISO device
# name, it must be a DVD, so refer to that one lofi
# device (and associated ISO pathname)
#
[[ $# -eq 1 ]] && isonum=1
err_media="ISO \"${iso_pathnames[$isonum]}\""
mountdev="${ldevs[$isonum]}"
fi
#
# If the disc needed in the install order isn't the one in
# the drive, ask for the correct one.
#
if ! get_cd "$mountdev" "${cds[$cdnum]}"; then
screenlog "$err_msg" "$zonename" "$err_media"
return 1
fi
# set the RedHat personality type, if applicable
[[ -n $rd_pers && -z $rh_pers ]] && rh_pers=$rd_pers
#
# We now know the actual type of media being used, so
# modify the "err_media" string accordingly.
#
if [[ "$install_media" = "disc" ]]; then
if [[ "$rd_disctype" = "DVD" ]]; then
err_media="$release_name DVD"
else
err_media="$release_name, CD ${cds[$cdnum]}"
fi
fi
find_packages "$mntdir" $disc_rpms
#
# Save a copy of $rpms_left. Other functions clobber it.
#
rpms_left_save="${rpms_left[@]}"
if [[ -n $rpms_found ]]; then
if [[ "$install_type" = "miniroot" ]]; then
verboselog "\nInstalling miniroot from"
verboselog " $err_media...\n"
if ! install_miniroot "$mntdir" \
"${rpms_found[@]}"; then
screenlog "$err_msg" "$zonename" \
"$err_media"
return 1
fi
else
screenlog "\n$install_msg\n" "$zonename" \
"$err_media"
if ! install_zone "$mntdir" \
${rpms_found[@]}; then
screenlog "$err_msg" "$zonename" \
"$err_media"
return 1
fi
fi
#
# Mark installation from this CD (or ISO representing
# this CD) as completed.
#
if [[ "$rd_disctype" = "CD" ]]; then
unset cds[$cdnum]
fi
fi
# A DVD install takes a single disc, so stop iterating
[[ "$rd_disctype" = "DVD" ]] && break
# If there are no RPMs left, we're done.
[[ -z $rpms_left_save ]] && break
disc_rpms="$rpms_left_save"
((cdnum += 1))
if [[ "$install_media" != "ISO" ]]; then
#
# modify the err_media variable to reflect the next
# CD in the sequence
#
err_media="$release_name, CD ${cds[$cdnum]}"
else
# Unmount the last used ISO if appropriate
if [[ -n $zone_mounted ]]; then
umount "$zone_mounted"
unset zone_mounted
fi
fi
done
if [[ -n $zone_mounted ]]; then
umount "$zone_mounted"
unset zone_mounted
fi
if [[ -n $rpms_left_save ]]; then
#
# Uh oh - there were RPMS we couldn't locate. This COULD
# indicate a failed installation, but we need to check for
# a RedHat personality "missing" list first.
#
if [[ -n $rh_pers && "$rh_pers" != "AS" ]]; then
typeset missing
if [[ $rh_pers = "WS" ]]; then
missing="$distro_WS_missing"
elif [[ $rh_pers = "ES" ]]; then
missing="$distro_ES_missing"
fi
#
# If any packages left in "rpm_left_save" appear in the
# list of packages expected to be missing from this
# personality, remove them from the "rpm_left_save"
# list.
#
if [[ -n $missing ]]; then
typeset pkg
for pkg in $missing
do
rpm_left_save=$(echo "$rpm_left_save " |
sed "s/$pkg //g")
#
# If all of the packages in
# "rpm_left_save" appeared in this
# personality's list of "expected
# missing" packages, then the
# installation completed successfully.
#
[[ -z ${rpm_left_save%%+( )} ]] &&
return 0
done
fi
fi
log "\nERROR: Unable to locate some needed packages:\n" \
" ${rpms_left_save%%+( )}\n"
screenlog "$err_msg" "$zonename"
return 1
fi
return 0
}
#
# Install a zone from installation media
#
# Returns 0 on success, 1 on failure
#
install_from_media()
{
msg=$(gettext "Installing miniroot for zone '%s'.")
screenlog "$msg" "$zonename"
iterate_media "miniroot" $@ || return 1
if ! setup_miniroot; then
screenlog "$mini_setfail" "$zonename"
return 1
fi
msg=$(gettext "Performing full install for zone '%s'.")
screenlog "\n$msg" "$zonename"
iterate_media "full" $@ || return 1
#
# Attempt to install deferred RPMS, if any
#
if [[ -n $deferred_rpms ]]; then
if ! install_zone ""; then
return 1
fi
fi
finish_install
return $?
}
#
# Add an entry to the valid distro list.
#
# The passed argument is the ISO type ("CD Set" or "DVD")
#
add_to_distro_list()
{
typeset name
distro_file[${#distro_file[@]}]="$distro"
name="$release_name"
[[ -n $redhat_pers ]] && name="$name $redhat_pers"
select_name[${#select_name[@]}]="$name ($1)"
release[${#release[@]}]="$release_name"
iso_set[${#iso_set[@]}]="${iso_names[@]}"
verboselog "Distro \"$name\" ($1) found."
}
#
# Find out which distros we have ISO files to support
#
# Do this by cycling through the distro directory and reading each distro
# file in turn looking for:
#
# 1) The number of discs in a distribution
# 2) The serial number of the distribution
# 3) The name of the distribution
#
# Based on this, we can determine based on the ISO files available which
# distributions, if any, we have a complete set of files to support.
#
# The function returns the supported isos in the array "iso_set."
#
validate_iso_distros()
{
typeset cd
typeset disctype
typeset index
typeset iso
typeset ncds
typeset pers
typeset pers_cd
typeset pers_index
typeset serial
typeset distro_files="$(echo $distro_dir/*.distro)"
typeset nisos=${#iso_filename[@]}
unset distro_file
unset iso_set
unset release
unset select_name
if [[ "$distro_files" = "$distro_dir/*.distro" ]]; then
msg=$(gettext "Unable to find any distro files!")
screenlog "$msg"
return
fi
for distro in $distro_files; do
#
# We're done if we've already processed all available ISO files
# or if there were none in the first place.
#
((${#iso_filename[@]} == 0)) && break
[[ ! -f $distro ]] && continue
. "$distro" > /dev/null
ncds=${#distro_cdorder[@]}
unset iso_names
unset pers
unset pers_cd
verbose "\nChecking ISOs against distro file \"$distro\"..."
index=0
while ((index < nisos)); do
#
# If the filename has been nulled out, it's already
# been found as part of a distro, so continue to the
# next one.
#
if [[ -z ${iso_filename[$index]} ]]; then
((index += 1))
continue
fi
iso="${iso_filename[$index]}"
serial="${iso_serial[$index]}"
release_name="${iso_release[$index]}"
redhat_pers="${iso_pers[$index]}"
verbose " ISO \"$iso\":"
#
# If the serial number doesn't match that for
# this distro, check other ISOs
#
if [[ "$serial" != "$distro_serial" ]]; then
((index += 1))
continue
fi
verbose " Serial #$serial"
verbose " Release Name \"$release_name\""
[[ -n ${iso_pers[$index]} ]] &&
verbose " RedHat Personality \"$redhat_pers\""
if [[ "${iso_disctype[$index]}" = "CD" ]]; then
disctype="CD #"
cd="${iso_cdnum[$index]}"
else
disctype="DVD, representing CDs #"
cd=0
fi
verbose " ${disctype}${iso_cdnum[$index]}\n"
#
# Once we've matched a particular distro, don't check
# this ISO to see if it's part of any other.
#
unset iso_filename[$index]
iso_names[$cd]="$iso"
#
# A DVD-based distro consists of one and ONLY one disc,
# so process it now.
#
if [[ "${iso_disctype[$index]}" = "DVD" ]]; then
typeset dvd_discs=",${iso_cdnum[$index]}"
cd=1
while ((cd <= ncds)); do
dvd_discs=$(echo "$dvd_discs" |
sed "s/,$cd//")
((cd += 1))
done
#
# If no CDs are left in $dvd_discs, the DVD
# was a complete distribution, so add it to
# the valid distro list.
#
if [[ -z $dvd_discs ]]; then
add_to_distro_list "DVD"
unset iso_names[$cd]
fi
elif [[ -n ${iso_pers[$index]} ]]; then
#
# If this is a RedHat personality CD, save off
# some extra information about it so we can
# discern between mutiple personality discs
# later, if needed.
#
pers[${#pers[@]}]=${iso_pers[$index]}
pers_cd[${#pers_cd[@]}]="$iso"
fi
((index += 1))
done
#
# Check to see if we have ISOs representing a full CD set.
# If we don't, don't mark this as an available distro.
#
(( ${#iso_names[@]} != $ncds )) && continue
relase_name="$release_name $distro_version"
if [[ -z ${pers[@]} ]]; then
#
# If there were no personality discs, just add this
# ISO set to the distro list.
#
unset redhat_pers
add_to_distro_list "CD Set"
else
#
# If a valid CD-based distro was found and there are
# RedHat personality discs for that distro present,
# create entries for each personality in the available
# distro list.
#
pers_index=0
while ((pers_index < ${#pers[@]})); do
redhat_pers=${pers[$pers_index]}
if [[ -n ${pers_cd[$pers_index]} ]]; then
#
# RedHat personality discs are always
# disc 1 of a CD set, so if we found a
# valid personality disc for this set,
# set the disc 1 entry for this distro
# to the ISO for the proper personality
# disc.
#
iso_names[1]="${pers_cd[$pers_index]}"
add_to_distro_list "CD Set"
fi
((pers_index += 1))
done
fi
done
}
#
# Do a lofi add for the passed filename and set lofi_dev to the lofi
# device name lofiadm created for it (e.g. "/dev/lofi/1".)
#
# If the passed filename already has a lofi device name, simply set lofi_dir
# to the existing device name.
#
# Returns 0 on success, 1 on failure.
#
lofi_add()
{
typeset filename="$1"
lofi_dev=$(lofiadm "$filename" 2>/dev/null) && return 0
lofi_dev=$(lofiadm -a "$filename") && return 0
screenlog "$lofi_failed" "$filename"
return 1
}
#
# Delete the lofi device name passed in.
#
# Returns 0 on success, 1 on failure.
#
lofi_del()
{
typeset dev="$1"
[[ "$dev" != /dev/lofi/* ]] && return 1
if lofiadm -d "$dev" 2>/dev/null; then
[[ -n $lofi_dev ]] && unset lofi_dev
return 0
fi
return 1
}
#
# Mount the lofi device name passed in.
#
# Set the variable mntdir to the directory on which the lofi device is
# mounted.
#
# Returns 0 on success, 1 on failure.
#
lofi_mount()
{
typeset lofidev="$1"
typeset mntpoint="$2"
#
# Check to see if the lofi device is already mounted and return
# the existing mount point if it is.
#
get_mountdir "$lofidev" && { mntdir="$mount_dir" ; return 0 ; }
unset mntdir
if [[ ! -d "$mntpoint" ]]; then
if ! mkdir -p "$mntpoint"; then
log "Could not create mountpoint \"$mntpoint\"!\n"
return 1
fi
lofi_created="$mntpoint"
fi
verbose "Attempting mount of device \"$lofidev\""
verbose " on directory \"$mntpoint\"... \c"
if ! mount -F hsfs -r "$lofidev" "$mntpoint" 2>/dev/null; then
verbose "FAILED."
[[ -n $lofi_created ]] && rmdir -ps "$lofi_created" &&
unset lofi_created
return 1
fi
mntdir="$mntpoint"
verbose "succeeded."
return 0
}
#
# Unmount the lofi device name passed in, and remove the device mount point
# after unmounting the device.
#
# Returns 0 on success, 1 on failure.
#
lofi_umount()
{
typeset mntdev="$1"
#
# If the directory name passed wasn't mounted to begin with,
# just return success.
#
get_mountdir "$mntdev" || return 0
verbose "Unmounting device \"$mntdev\"... \c"
if ! umount "$mntdev" ; then
verbose "FAILED."
return 1
fi
verbose "succeeded."
return 0
}
# Scan the passed list of ISOs.
scan_isos()
{
typeset iso
typeset index=0
unset iso_serial
unset iso_release
unset iso_cdnum
unset iso_disctype
unset iso_filename
unset iso_pers
for iso in "$@"; do
verbose "Checking possible ISO\n \"$iso\"..."
if lofi_add "$iso"; then
verbose " added as lofi device \"$lofi_dev\""
if lofi_mount "$lofi_dev" "/tmp/lxiso"; then
if read_discinfo "$mntdir"; then
iso_release[$index]="$rd_release"
iso_serial[$index]="$rd_serial"
iso_cdnum[$index]="$rd_cdnum"
iso_disctype[$index]="$rd_disctype"
[[ -n $rd_pers ]] &&
iso_pers[$index]="$rd_pers"
iso_filename[$index]="$iso"
((index += 1))
fi
lofi_umount "$lofi_dev"
else
verbose " not a usable ISO image."
log "Unable to mount \"$lofi_dev\" (\"$iso\")"
fi
lofi_del "$lofi_dev"
else
verbose " not a valid ISO image."
fi
done
}
#
# Prompt the user with the first argument, then make a menu selection
# from the balance.
#
# This is effectively similar to the ksh "select" function, except it
# outputs to stdout.
#
# Shell variables set:
# choice - set to the menu number selected
# selection - set to the menu text selected
#
pick_one()
{
typeset menu_items
typeset menu_index
typeset reply
typeset prompt="$1"
shift
unset choice
set -A menu_items "$@"
until [[ -n $choice ]]; do
menu_index=1
echo "\n$prompt\n"
for f in "${menu_items[@]}"; do
echo "$menu_index) $f"
((menu_index += 1))
done
echo "\n$(gettext "Please select") (1-$#): " "\c"
read reply
echo
[[ -z $reply ]] && echo && continue
#
# Reprint menu selections if the answer was not a number in
# range of the menu items available
#
[[ $reply != +([0-9]) ]] && continue
((reply < 1)) || ((reply > $#)) && continue
choice=$reply
selection=${menu_items[((choice - 1))]}
done
}
#
# Select a distribution to install from the arguments passed and set
# "ndsitro" to the value chosen - 1 (so it may be used as an array index.)
#
# The routine will automatically return with ndisto set to 0 if only one
# argument is passed.
#
select_distro()
{
unset choice
unset ndistro
if (($# > 1)); then
if [[ -n $silent_mode ]]; then
typeset dist
log "ERROR: multiple distrubutions present in ISO" \
"directory but silent install"
log " mode specified. Distros available:"
for dist in "$@"; do
log " \"$dist\""
done
return 1
fi
pick_one \
"$(gettext "Which distro would you like to install?")" \
"$@"
fi
#
# Covers both the cases of when only one distro name is passed
# to the routine as well as when an EOF is sent to the distribution
# selection prompt.
#
if [[ -z $choice ]]; then
screenlog "$install_dist" "$1"
ndistro=0
else
screenlog "$install_dist" "$selection"
ndistro=$((choice - 1))
fi
return 0
}
#
# Install a zone from discs or manually lofi-mounted ISOs.
#
# Return 0 on success, 1 on failure
#
do_disc_install()
{
typeset path="$1"
typeset eject_final="N"
typeset install_status
#
# Get a disc, it doesn't matter which one.
#
# We don't know which distro this may be yet, so we can't yet
# ask for the first disc in the install order.
#
if ! get_cd "$path"; then
if [[ -z $silent_mode ]]; then
typeset distro_disc=\
$(gettext "a supported Linux distribution disc")
screenlog "\n$distro_mediafail" "$distro_disc ($path)"
fi
return 1
fi
if [[ -n $silent_mode && "$rd_disctype" = "CD" ]]; then
log "$silent_err_msg"
return 1
fi
if ! get_disc_distro "$mntdir"; then
msg=$(gettext "Unable to find a supported Linux release on")
screenlog "$msg"
screenlog " $media_spec" "$path"
umount "$mntdir" > /dev/null 2>&1
return 1
fi
check_mbfree $zoneroot $distro_mb_required || return 1
build_rpm_list $install_packages
echo
if [[ "$install_media" = "disc" ]]; then
#
# If we're in interactive mode, ask the user if they want the
# disc ejected when the installation is complete.
#
# Silent mode installs will require the user to manually run
# eject(1).
#
if [[ -n $removable && -z $silent_mode ]]; then
typeset ans
typeset disc
typeset status
typeset which=""
disc="$rd_disctype"
[[ "$disc" = "CD" ]] && which=$(gettext "final ")
#
# Ask the user if they want the install disc ejected
# when the installation is complete. Any answer but
# "n" or "N" is taken to mean yes, eject it.
#
eject_final="Y"
status=$(gettext "WILL")
screenlog "$eject_final_msg" "$which" "$disc"
screenlog " $eject_final_prompt" "$zonename" "[y]/n"
read ans && [[ "$ans" = [Nn]* ]] && eject_final="N" &&
status=$(gettext "will NOT")
screenlog "\n$eject_final_status\n" "$which" "$disc" \
"$status"
fi
screenlog "$install_ndiscs" "$distro_ncds"
msg=$(gettext "install %s.")
screenlog "$msg" "$release_name"
else
screenlog "$install_nisos" "$distro_ncds"
msg=$(gettext "DVD) to install %s.")
screenlog "$msg" "$release_name"
fi
install_from_media "$path"
install_status=$?
[[ "$eject_final" = "Y" ]] && eject_removable_disc
return $install_status
}
#
# Install a zone using the list of ISO files passed as arguments to this
# function.
#
# Return 0 on success, 1 on failure.
#
do_iso_install()
{
typeset install_status
typeset iso_path
typeset ldev
msg=$(gettext "Checking for valid Linux distribution ISO images...")
screenlog "\n$msg"
scan_isos "$@"
if [[ -z ${iso_filename[@]} ]]; then
msg=$(gettext "No valid ISO images available or mountable.")
screenlog "\n$msg"
return 1
fi
validate_iso_distros
if [[ -z ${release[@]} ]]; then
msg=$(gettext "No supported Linux distributions found.")
screenlog "\n$msg"
return 1
fi
select_distro "${select_name[@]}" || return 1
unset select_name
. ${distro_file[$ndistro]} > /dev/null
distro_ncds=${#distro_cdorder[@]}
check_mbfree $zoneroot $distro_mb_required || return 1
build_rpm_list $install_packages
unset lofi_devs
verboselog ""
for iso_path in ${iso_set[$ndistro]}; do
if ! lofi_add "$iso_path"; then
for ldev in $lofi_devs; do
lofi_del "$ldev"
done
return 1
fi
verboselog "Added \"$iso_path\""
verboselog " as \"$lofi_dev\""
lofi_devs="$lofi_devs $lofi_dev"
done
release_name="${release[$ndistro]}"
set -A iso_pathnames "zero_pad" ${iso_set[$ndistro]}
install_from_media $lofi_devs
install_status=$?
for ldev in $lofi_devs; do
lofi_del "$ldev"
done
unset lofi_devs
return $install_status
}
# Clean up on interrupt
trap_cleanup()
{
cd "$cwd"
msg=$(gettext "Interrupt received, cleaning up partial install...")
screenlog "$msg"
[[ -n $miniroot_booted ]] && zoneadm -z "$zonename" halt &&
unset miniroot_booted && unset newroot_mounted
#
# OK, why a sync here? Because certain commands may have written data
# to mounted file systems before the interrupt, and given just the right
# timing there may be buffered data not yet sent to the disk or the
# system may still be writing data to the disk. Either way, the umount
# will then fail because the system will still see the mounted
# filesystems as busy.
#
sync
if [[ -n $newroot_mounted ]]; then
umount_list $newroot_mounted
unset newroot_mounted
fi
if [[ -n $zone_mounted ]]; then
umount "$zone_mounted"
unset zone_mounted
fi
#
# Normally, this isn't needed but there is a window where mntdir is set
# before zone_mounted, so account for that case.
#
if [[ -n $mntdir ]]; then
umount "$mntdir"
unset mntdir
fi
[[ -n $lofi_dev ]] && lofi_del "$lofi_dev"
if [[ -n $lofi_devs ]]; then
typeset ldev
for ldev in $lofi_devs
do
lofi_del "$ldev"
done
unset lofi_devs
fi
[[ -n $lofi_created ]] && rmdir -ps "$lofi_created" &&
unset lofi_created
msg=$(gettext "Installation aborted.")
screenlog "$msg"
exit $ZONE_SUBPROC_FATAL
}
#
# Start of main script
#
cwd=$(dirname "$0")
distro_dir="$cwd/distros"
unset deferred_saved
unset distro_path
unset logfile
unset msg
unset newroot_mounted
unset silent_err_msg
unset silent_mode
unset verbose_mode
unset zone_mounted
unset zoneroot
unset zonename
#
# 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
#
# Process and set up various global option variables:
#
# distro_path - Path containing files that make up the distribution
# (e.g. a directory containing ISO files or a disc device)
# logfile - Name (if any) of the install log file
# zoneroot - Root directory for the zone to install
# zonename - Name of the zone to install
#
while getopts 'svxd:l:r:z:' opt; do
case $opt in
s) silent_mode=1; unset verbose_mode;;
v) verbose_mode=1; unset silent_mode;;
x) set -x;;
d) distro_path="$OPTARG";;
l) logfile="$OPTARG";;
r) zoneroot="$OPTARG";;
z) zonename="$OPTARG";;
esac
done
shift OPTIND-1
distro_path=${distro_path:=/cdrom/cdrom0}
install_packages="$@"
[[ -n $silent_mode ]] && exec 1>/dev/null
if [[ -z $zonename ]]; then
msg=$(gettext "ERROR: Cannot install - no zone name was specified")
screenlog "$msg"
echo
exit $ZONE_SUBPROC_NOTCOMPLETE
fi
if [[ -z $zoneroot ]]; then
msg=$(gettext "ERROR: Cannot install - no zone root directory was")
screenlog "$msg"
msg=$(gettext "specified.")
screenlog " $msg"
echo
exit $ZONE_SUBPROC_NOTCOMPLETE
fi
# Make sure the specified zone root directory exists
[[ -d "$zoneroot" ]] || mkdir -m 0700 -p "$zoneroot"
if [[ ! -d "$zoneroot" ]]; then
screenlog "$zone_rootfail" "$zoneroot"
echo
exit $ZONE_SUBPROC_NOTCOMPLETE
fi
rootdir="$zoneroot/root"
# Make sure the specified zone root subdirectory exists
[[ -d "$rootdir" ]] || mkdir -p "$rootdir"
if [[ ! -d "$rootdir" ]]; then
screenlog "$zone_rootsub" "$rootdir"
echo
exit $ZONE_SUBPROC_NOTCOMPLETE
fi
media_mntdir="$rootdir/media"
if [[ -n $logfile ]]; then
# If a log file was specified, log information regarding the install
log "\nInstallation started `date`"
log "Installing from path \"$distro_path\""
else
# Redirect stderr to /dev/null if silent mode is specified.
[[ -n $silent_mode ]] && exec 2>/dev/null
fi
distro_path=${distro_path:=$default_distro_path}
# From this point on, call trap_cleanup() on interrupt (^C)
trap trap_cleanup INT
verbose "Installing zone \"$zonename\" at root \"$zoneroot\""
release_name="supported Linux distribution"
#
# Based on the pathname, attempt to determine whether this will be a disc or
# lofi-based install or one using ISOs.
#
if [[ "$distro_path" = /cdrom/* || "$distro_path" = /media/* ||
"$distro_path" = /dev/dsk/* || "$distro_path" = /dev/lofi/* ]]; then
if [[ "$distro_path" = /dev/lofi/* ]]; then
silent_err_msg="$silent_nolofi"
install_media="lofi"
else
silent_err_msg="$silent_nodisc"
install_media="disc"
fi
if [[ "$distro_path" = /cdrom/* || "$distro_path" = /media/* ]]; then
managed_removable=1
else
managed_removable=0
fi
log "Installing zone \"$zonename\" at root \"$zoneroot\""
verboselog " Attempting ${install_media}-based install via:"
verboselog " \"$distro_path\""
do_disc_install "$distro_path"
else
typeset dir_start
typeset dir_file
dir_start=$(dirname "$distro_path" | cut -c 1)
[[ "$dir_start" != "/" ]] && distro_path="${PWD:=$(pwd)}/$distro_path"
if [[ ! -d "$distro_path" ]]; then
screenlog "$no_distropath" "$distro_path"
echo
exit $ZONE_SUBPROC_NOTCOMPLETE
fi
log "Installing zone \"$zonename\" at root \"$zoneroot\""
verboselog " Attempting ISO-based install from directory:"
verboselog " \"$distro_path\""
unset iso_files
for dir_file in $distro_path/*; do
#
# Skip this file if it's not a regular file or isn't readable
#
[[ ! -f $dir_file || ! -r $dir_file ]] && continue
#
# If it's an hsfs file, it's an ISO, so add it to the possible
# distro ISO list
#
filetype=$(LC_ALL=C fstyp $dir_file 2>/dev/null) &&
[[ "$filetype" = "hsfs" ]] &&
iso_files="$iso_files $dir_file"
done
install_media="ISO"
do_iso_install $iso_files
fi
if [[ $? -ne 0 ]]; then
cd "$cwd"
[[ -n $miniroot_booted ]] && zoneadm -z "$zonename" halt &&
unset miniroot_booted && unset newroot_mounted
if [[ -n $zone_mounted ]]; then
umount "$zone_mounted"
unset zone_mounted
fi
if [[ -n $newroot_mounted ]]; then
umount_list $newroot_mounted
unset newroot_mounted
fi
screenlog "\n$install_failed\n" "$release_name" "$zonename" "`date`"
msg=$(gettext "Cleaning up after failed install...")
screenlog "$msg"
#
# The extra checks are some basic paranoia due to the potentially
# dangerous nature of these commands but are not intended to catch all
# malicious cases.
#
[[ -d "$zoneroot/a" ]] && rm -rf "$zoneroot/a"
exit $ZONE_SUBPROC_FATAL
fi
screenlog "$install_done" "$release_name" "$zonename" "`date`"
exit $ZONE_SUBPROC_OK