#!/bin/ksh

# @(#)Install 1.49 95/02/17 SMI - installs unix and modules
#
# Author:  Jeff Bonwick
#
#	Please report any bugs to bonwick@eng.
#
# How Install works:
#
#	Install performs the following steps:
#
#	1. Figure out how to construct /kernel by looking at Makefile.uts,
#	   Makefile.$ISA (sparc default), Makefile.$KARCH (sun4c default),
#	   and the Makefiles in the module directories (uts/arch/*/Makefile).
#
#	2. Create the requested subset of /kernel in Install's temp space
#	   (/tmp/Install.username by default.)
#
#	3. Create a tar file (/tmp/Install.username/Install.tar) based on (3).
#
#	4. If -n was specified, exit.  If a target was specified using -T,
#	   rcp the tarfile to the target and exit.  If a target was specified
#	   using -t, rsh to the target machine and untar the tarfile in the
#	   target directory.
#
# If any of these steps fail, Install will give you an error message and,
# in most cases, suggest corrective measures.  Then, you can recover the
# install with "Install -R". (This is not required; it's just faster than
# starting from scratch.)
#
# One final comment:  Unfortunately, tar and I disagree on what
# constitutes a fatal error.  (tar -x will exit 0 even if it can't write
# anything in the current directory.)  Thus, I am reduced to grepping stderr
# for (what I consider) fatal and nonfatal error messages.  If you run into
# a situation where this doesn't behave the way you think it should (either
# an "Install failed" message after a successful install, or an "Install
# complete" message after it bombs), please let me know.

INSTALL=`basename $0`
DOT=`pwd`

TRAILER="Install.$LOGNAME"
INSTALL_STATE=${INSTALL_STATE-$HOME/.Install.state}
export INSTALL_STATE
INSTALL_DIR=${INSTALL_DIR-/tmp/$TRAILER}
if [ "`basename $INSTALL_DIR`" != "$TRAILER" ]; then
	INSTALL_DIR="$INSTALL_DIR/$TRAILER"
fi
export INSTALL_DIR
INSTALL_LIB=${INSTALL_LIB-$HOME/LibInstall}
export INSTALL_LIB
INSTALL_RC=${INSTALL_RC-$HOME/.Installrc}
export INSTALL_RC
INSTALL_CP=${INSTALL_CP-"cp -p"}
export INSTALL_CP
INSTALL_RCP=${INSTALL_RCP-"rcp -p"}
export INSTALL_RCP

STATE=0

DEFAULT_OPTIONS="-k sun4c -navx"
GLOM=no
GLOMNAME=kernel

trap 'fail "User Interrupt" "You can resume by typing \"$INSTALL -R\""' 1 2 3 15

usage() {
	echo ""
	echo $1
	echo '
Usage: Install	[ -t target (e.g. machine:/tmp) ]
		[ -T (same as -t, but just copies a tarfile to target) ]
		[ -n (no target machine, just do it in /tmp (default)) ]
		[ -w Avocet workspace ]
		[ -k kernel arch (default: sun4c) ]
		[ -s source directory (default: /usr/src/uts) ]
		[ -u (install unix only) ]
		[ -m (install modules only) ]
		[ -a (install everything, i.e. unix + modules (default)) ]
		[ -v (verbose output (default)) ]
		[ -V (REALLY verbose output) ]
		[ -q (quiet) ]
		[ -c (clean up (remove temp files) when done (default) ]
		[ -p (preserve temp files -- useful for debugging) ]
		[ -d directory (where to create unix, modules, tarfile) ]
		[ -L (library create: put tarfile in $INSTALL_LIB/env.karch) ]
		[ -l lib (library extract: use $INSTALL_LIB/lib as source) ]
		[ -D library directory (default: ~/LibInstall) ]
		[ -g (glom all kernel files together under /platform/...) ]
		[ -G like -g, but specify kernel name (e.g. kernel.foo) ]
		[ -R (recover the previous Install) ]
		[ -h (help -- prints this message) ]
		[ list of modules to install ]

For more information, utter:

	man -M /opt/teamware/man Install
'
	exit 1
}

#
# Save the current state of Install
#

save_state() {
	rm -f $INSTALL_STATE
	(echo "# State of previous Install
TARGET=$TARGET
ENV_PATH=$ENV_PATH
ENV_NAME=$ENV_NAME
KARCH=$KARCH
UTS=$UTS
INSTALL_DIR=$INSTALL_DIR
INSTALL_LIB=$INSTALL_LIB
IMODE=$IMODE
LIBCREATE=$LIBCREATE
LIBSRC=$LIBSRC
VERBOSE=$VERBOSE
CLEANUP=$CLEANUP
GLOM=$GLOM
GLOMNAME=$GLOMNAME
XFLAG=$XFLAG
files='$files'
STATE=$STATE" >$INSTALL_STATE) || verbose "Warning: cannot save state"
}

#
# Restore the previous state of Install
#

restore_state() {
	test -s $INSTALL_STATE || fail "Can't find $INSTALL_STATE"
	eval "`cat $INSTALL_STATE`"
}

#
# Install failed -- print error messages and exit 2
#

fail() {
	save_state
	while [ $# -gt 0 ]
	do
		echo $1
		shift
	done
	echo "Install failed"
	exit 2
}

#
# Echo a string in verbose mode only
#

verbose() {
	test "$VERBOSE" != "q" && echo $1
}

#
# hack for tmpfs bug -- remove files gradually
#

remove_dir() {
	test -d $1 || return
	local_dot=`pwd`
	cd $1
	touch foo
	rm -f `find . -type f -print`
	cd $local_dot
	rm -rf $1
}

#
# Make tarfile
#

make_tarfile() {
	verbose "Creating tarfile $TARFILE"
	test -d $INSTALL_FILES || fail "Can't find $INSTALL_FILES"
	cd $INSTALL_FILES
	rm -f $TARFILE
	tar cf $TARFILE . || fail "Couldn't create tarfile $TARFILE"
	STATE=2
}

#
# Routines to copy files to the target machine
#

remote_fail() {
	fail "" "$1" "" \
		"Make sure that $TARGET_MACHINE is up." \
		"Check /etc/hosts.equiv, /etc/passwd, and /etc/shadow" \
		"Change permissions on $TARGET_MACHINE as necessary." \
		"Then, use \"$INSTALL -R\" to resume the install." ""
}

remote_install() {
	if [ "$IMODE" = "n" ]; then
		STATE=3
		return 0
	fi
	test -s $TARFILE || fail "$TARFILE missing or empty"
	verbose "Installing system on $TARGET"
	test -d $INSTALL_DIR || fail "Can't find $INSTALL_DIR"
	cd $INSTALL_DIR
	rm -f errors fatal nonfatal
	if [ "$IMODE" = "T" ]; then
		EMESG="Can't rcp to $TARGET"
		touch errors
		sh -e${SHV}c "$INSTALL_RCP $TARFILE $TARGET/Install.tar"
	else
		EMESG="Can't rcp to $TARGET_MACHINE"
		rsh $TARGET_MACHINE "(cd $TARGET_DIR; /usr/bin/tar x${V}f -)" \
			<$TARFILE 2>errors
	fi
	test $? -ne 0 && remote_fail "$EMESG"
	cd $INSTALL_DIR
	egrep "set time|warning|blocksize" errors >nonfatal
	egrep -v "set time|warning|blocksize" errors >fatal
	if [ -s fatal ]; then
		echo "Fatal errors from rsh:"
		cat fatal
		remote_fail "Can't install on $TARGET_MACHINE"
	fi
	if [ -s nonfatal -a "$VERBOSE" != "q" ]; then
		echo "Non-fatal errors from rsh:"
		cat nonfatal
	fi
	rm -f fatal nonfatal errors
	test "$IMODE" = "T" && echo "Files can be extracted on \
$TARGET_MACHINE using 'tar xvf $TARGET_DIR/Install.tar'"
	STATE=3
}

okexit() {
	cd /tmp
	test "$CLEANUP" = c && remove_dir $INSTALL_DIR
	save_state
	verbose "Install complete"
	exit 0
}

#
# Process options
#

RCOPTS=""
LIBCREATE="no"
LIBSRC=""
PFLAG=0
ENV_PATH=$CODEMGR_WS

test -s $INSTALL_RC && RCOPTS=`cat $INSTALL_RC`
set $INSTALL $DEFAULT_OPTIONS $RCOPTS $*
shift

while getopts t:T:nw:k:s:umavVqcpd:Ll:D:gG:xXRPh opt
do
	case $opt in
	  t|T)	TARGET="$OPTARG"; IMODE=$opt; CLEANUP="c";;
	    n)	TARGET=""; IMODE="n"; CLEANUP="p";;
	    w)	ENV_PATH="$OPTARG"; SRC="$ENV_PATH/usr/src";;
	    k)	KARCH="$OPTARG";;
	    s)	UTS="$OPTARG";;
	    u)	files="unix";;
	    m)	files="modules";;
	    a)	files="unix modules";;
	v|V|q)	VERBOSE=$opt;;
	  c|p)	CLEANUP=$opt;;
	    d)	INSTALL_DIR="$OPTARG/$TRAILER";;
	    L)	LIBCREATE="yes"; CLEANUP="c";;
	    l)	LIBSRC="$OPTARG";;
	    D)	INSTALL_LIB="$OPTARG";;
	    g)	GLOM=yes;;
	    G)	GLOM=yes; GLOMNAME="$OPTARG";;
	    x)	XFLAG=1;;
	    X)	XFLAG=0;;
	    R)	x=$OPTIND; restore_state; OPTIND=$x;;
	    P)	PFLAG=1;;
	    h)	usage "${INSTALL}: installs unix and modules";;
	   \?)	usage "Illegal option";;
	esac
done
shift `expr $OPTIND - 1`

ENV_NAME=`basename $ENV_PATH`

#
# The rest of the command line is a list of individual files to copy.
# If non-null, this list overrides the -uma options.
#

test $# -gt 0 && files="$*"

case $VERBOSE in
	v)	V="v"; SHV="x";;
	V)	V="v"; SHV="x"; set -x;;
	q)	V=""; SHV="";;
esac

#
# Create temp directory for Install's files
#

test -d $INSTALL_DIR || mkdir -p $INSTALL_DIR || fail "Can't mkdir $INSTALL_DIR"

TARFILE=$INSTALL_DIR/Install.${KARCH}.tar
INSTALL_FILES=$INSTALL_DIR/$KARCH

#
# Extract the target machine and target directory from a target of the
# form "machine:/dir"
#

if [ "$IMODE" != "n" ]; then
	eval ` echo $TARGET | nawk -F':' '{ 
		if (NF != 2 || !length($1) || !length($2))
			print "usage \"Invalid target\""
		print "TARGET_MACHINE=" $1 ";"
		print "TARGET_DIR=" $2 ";"
	}'`
fi

#
# Allow the use of library source or target for the install
#

if [ -n "$LIBSRC" ]; then
	LIBSRC="`basename $LIBSRC .tar`.tar"
	TARFILE=$INSTALL_LIB/$LIBSRC
	test -s $TARFILE || fail "Can't find tarfile $TARFILE"
	verbose "Installing from library tarfile $TARFILE"
	STATE=2
elif [ "$LIBCREATE" = "yes" ]; then
	test -d $INSTALL_LIB \
		|| mkdir -p $INSTALL_LIB \
		|| fail "Can't mkdir $INSTALL_LIB"
	TARFILE="$INSTALL_LIB/${ENV_NAME}.${KARCH}.tar"
fi

#
# The next three lines allow recovery and activation with -R,
# and library installs with -l.
#

test $STATE -eq 1 && make_tarfile
test $STATE -eq 2 && remote_install
test $STATE -eq 3 && okexit

save_state

cd $DOT
DOTDOT=`cd ..; pwd`

#
# Try to be smart: if DOTDOT ends in uts, then infer UTS and KARCH from DOT
# Otherwise, if SRC is set, infer UTS = $SRC/uts and, if not already specified,
# set KARCH = sun4c.
#

if [ "`basename $DOTDOT`" = "uts" ]; then
	UTS=$DOTDOT
	KARCH=`basename $DOT`
fi

if [ -z "$UTS" -a -n "$SRC" ]; then
	UTS="${SRC}/uts"
	test -z "$KARCH" && KARCH=sun4c
fi

if [ "$LIBCREATE" = "yes" ]; then
	TARFILE=$INSTALL_LIB/${ENV_NAME}.${KARCH}.tar
else
	TARFILE=$INSTALL_DIR/Install.${KARCH}.tar
fi
INSTALL_FILES=$INSTALL_DIR/$KARCH
save_state

cd $DOT
test -z "$UTS" && fail 'Cannot find kernel sources -- $SRC not set'
test -d "$UTS" || fail "${UTS}: no such directory"

#
# Convert UTS into an absolute path.
#

cd $UTS
UTS=`pwd`

test "`basename $UTS`" = "uts" || \
	verbose "Warning: source path $UTS doesn't end in 'uts'"

remove_dir $INSTALL_DIR/$KARCH
rm -f $TARFILE

# The awk script below looks in Makefile.uts to find out what module
# subdirectories to make.  It then looks in Makefile.$KARCH (for root
# modules) and Makefile.$ISA (for usr modules) to create a list of
# all possible modules.  Finally, it looks at each module's Makefile
# to determine where it belongs and what links are required.
# This script makes three assumptions:
#
# 1) Module subdirectories are specified in Makefile.uts by lines of the
#    form:
#
#	ROOT_FOO_DIR = $(ROOT_MOD_DIR)/foo
#	USR_BAR_DIR = $(USR_MOD_DIR)/bar
#
# 2) The corresponding lists of modules appear in Makefile.$KARCH and
#    Makefile.$ISA on one or more lines of the form:
#
#	FOO_KMODS = foo bar
#	FOO_KMODS += red white blue
#
# 3) Each module directory has a Makefile with lines of the form:
#
#	ROOTMODULE = $(ROOT_FOO_DIR)/$(MODULE)
#	USRMODULE = $(USR_FOO_DIR)/$(MODULE)
#	ROOTLINK* = $(ROOT_BAR_DIR)/something
#	USRLINK* = $(USR_BAR_DIR)/something
#
# If the structure of Makefile.{$KARCH,uts,$ISA} changes in a way that
# invalidates these assumptions, you'll need to pick up a new version of
# Install.

case $KARCH in
	sun[45]*)	ISA=sparc;;
	i86*)		ISA=i86;;
	prep | ppc)	ISA=ppc;;
esac

verbose "Source = $UTS, ISA = $ISA, kernel = $KARCH"

UTS_MAKE=Makefile.uts
ISA_MAKE=$ISA/Makefile.$ISA
KARCH_MAKE=$KARCH/Makefile.$KARCH
PSM_MAKE=$UTS/../Makefile.psm

test -d $KARCH || fail "${KARCH}: invalid kernel architecture"
test -d $ISA || fail "${ISA}: invalid instruction set architecture"
test -s $UTS_MAKE || fail "Can't find $UTS_MAKE"
test -s $ISA_MAKE || fail "Can't find $ISA_MAKE"
test -s $KARCH_MAKE || fail "Can't find $KARCH_MAKE"
test -s $PSM_MAKE || fail "Can't find $PSM_MAKE"
DEVFS="./$ISA/devfs/Makefile"

verbose "Copying files to ${INSTALL_FILES}..."
test -d $INSTALL_FILES || mkdir -p $INSTALL_FILES

nawk \
	-v isa="$ISA" \
	-v insd="$INSTALL_FILES" \
	-v files="$files" \
	-v verbose=$VERBOSE \
	-v karch=$KARCH \
	-v copy="$INSTALL_CP" \
	-v devfs=$DEVFS \
	-v auxfiles=$XFLAG \
	-v ptiflag=$PFLAG \
	-v glom=$GLOM \
	'
function run(s) {
	if (verbose != "q")
		print s
	if (system(s))
		exit 1
}
function cpf(f1,f2) {
	if (verbose != "q")
		print copy " " f1 " " f2
	if (system(copy " " f1 " " f2))
		print "WARNING: copy of " f1 " failed"
}
function makedir(dir) {
	if (!touched[dir]) {
		run("test -d " dir " || mkdir -p " dir)
		touched[dir]=1
	}
	return dir
}
function vprint(s) {
	if (verbose == "V")
		print s
}
function try_run(s) {
	if (verbose != "q")
		print s
	if (system(s))
		return 1
	return 0
}
function exist(s) {
	if (verbose != "q")
		print "test -f " s
	return !system("test -f " s)
}

BEGIN {
# Temporary hack for x86 until the source is cleaned up.
# Take away isa_bin as soon as i86 directory goes away.
	isa_bin = isa
	if (isa == "i86")
		isa_bin = "i386"
	srcbase["ROOT"]="./" karch
	srcbase["USR"]="./" isa
	unixdir=insd "/kernel"
	base["ROOT"]=insd "/kernel"
	base["USR"]=insd "/usr/kernel"
	base["ROOT_PLATFORM"]=insd "/platform"
# If you do NOT want the SVVS modules, comment out the next two lines.
	src["ROOT_SVVS"]="/svvs/"
	src["USR_SVVS"]="/svvs/"
# If you do NOT want the excluded modules, comment out the next two lines.
	src["ROOT_XMODS"]="/"
	src["USR_XMODS"]="/"
#
# KBI hacks
#
	machbase = "platform/" karch
	machkernel = machbase "/kernel"
	unixdir = insd "/" machkernel

	if (glom == "yes") {
		base["ROOT"]= insd "/" machkernel
		base["USR"]= insd "/" machkernel
	}
}
$3 ~ /^..(ROOT|USR)_MOD_DIR.\// {
	vprint($0)
	split($1,foo,"_")
	root=foo[1]
	mdir=substr($3,index($3,"/")+1)
	dirs[root]=dirs[root] mdir " "
	src[root "_" foo[2]]="/"
	targ[root "_" foo[2]]=base[root] "/" mdir
}
$3 ~ /^..(ROOT_PSM)_MOD_DIR.\// {
	vprint($0)
	split($1,foo,"_")
	root=foo[1]
	mdir=substr($3,index($3,"/")+1)
	dirs[root]=dirs[root] mdir " "
	src[root "_" foo[2] "_" foo[3]]="/"
	targ[root "_" foo[2] "_" foo[3]]=base[root] "/" mdir
}
/^GENLIB_DIR.*=/ {
	genunix="." substr($3, index($3, "/")) "/genunix"
	gsub(/..PLATFORM./, karch, genunix);
}
FILENAME != "Makefile.uts" && $1 ~ /KMODS|XMODS/ {
	root = (FILENAME == isa "/Makefile." isa) ? "USR" : "ROOT"
	split($1,foo,"_")
	x=root "_" foo[1]
	if (x in src) {
		vprint($0)
		for (i = 3; i <= NF; i++) {
			if (($i == "ramdisk") || ($i == "consfb"))
				continue;
			nmods++
			modname[nmods]=$i
			modnum[$i]=nmods
			modsrcd[nmods]=srcbase[root] src[x] $i
		}
	}
}
END {
	split(files, modlist)
	for (m in modlist) {
		mm = modlist[m]
		if (mm == "modules") {
			for (i = 1; i <= nmods; i++)
				modcopy[i]=1
			continue
		}
		if (mm == "unix") {
			makedir(unixdir);
			run(copy " " srcbase["ROOT"] "/unix/unix " unixdir)
			continue
		}
		if ((i = modnum[mm]) > 0)
			modcopy[i]=1
		else {
			print mm ": invalid module"
			exit 1
		}
	}
	for(i = 1; i <= nmods; i++) {
		if (!modcopy[i]) 
			continue
                confsrc = ""
		drvfiles[1] = ""
		classfile = ""
		ptifile = ""
		modmake=modsrcd[i] "/Makefile"
		while (getline <modmake > 0) {
			if ($1 == "MODULE") {
				modname[i] = $3
				modsrc[i] = modsrcd[i] "/" modname[i]
			}
			if ($1 ~ /^(ROOT|USR)(LINK|MODULE$)/) {
				vprint($0)
				slash=index($3,"/")
				dir=makedir(targ[substr($3,3,slash-8)])
				if (index(modsrcd[i], karch) && glom == "no")
					sub("kernel", machkernel, dir)
				makedir(dir)
				if ($1 ~ /^(ROOT|USR)MODULE$/) {
					modtarg[i]=dir
#Oh, hack me to death with a wet noodle ... the profile driver is machine
#dependent but the conf file is not.
					if (modname[i] == "profile") {
						modsrc[i] = "./" isa "/profile/profile"
					}
					if (modname[i] == "wsdrv") {
						continue;
					}
					run(copy " " modsrc[i] " " modtarg[i])
				}
				else {
					linkmod=substr($3,slash)
					if (linkmod ~ /MODULE/)
						linkmod = "/" modname[i]
					m=modtarg[i] "/" modname[i]
					run("ln " m " " dir linkmod)
				}
			}
                        if ($1 == "CONF_SRCDIR") {
				vprint($0)
				slash=index($3,"/")
                                confsrc = "." substr($3,slash)
                        }
		}
		if (karch == "sun4") {
			if (modname[i] == "sd") {
				confsrc = "./sun4/io/scsi/targets"
			}
		} else {
			if (modname[i] ~ /^(esp|le)$/) {
				confsrc = "";
			}
		}
		if (confsrc != "")
			cpf(confsrc "/" modname[i] ".conf", modtarg[i])
		close(modmake)
	}

	run(copy " " genunix " " base["ROOT"] )

#
# Make symlinks for KBI for different root node names for a given platform
#
	km = "./" karch "/Makefile"
	while (getline <km > 0) {
		if ($1 == "PLAT_LINKS") {
			for (i = 3; i <= NF; i++) {
				run("ln -s " karch " " insd "/platform/" $i)
			}
		}
	}
	close(km)

	while (getline <devfs > 0) {
		if ($1 == "SRCDIR")
			configdir = "." substr($3, index($3, "/")) "/"
		if ($1 == "CLASSFILE")
			classfile = $3
		if ($1 == "PTIFILE")
			ptifile = $3
		if ($1 == "DRVFILES")
			split(substr($0, index($0,$3)), drvfiles)
	}
	close(devfs)

	if (classfile != "" && auxfiles == 1) {
		dir=makedir(targ["ROOT_DRV"])
		cpf(configdir classfile, dir)
	}
	if (ptifile != "" && ptiflag == 1) {
		dir=makedir(insd "/etc")
		cpf(configdir ptifile, dir)
	}
	if (drvfiles[1] != "" && auxfiles == 1) {
		dir=makedir(insd "/etc")
		for (file in drvfiles)
			cpf(configdir drvfiles[file], dir)
	}
}' $UTS_MAKE $ISA_MAKE $KARCH_MAKE $PSM_MAKE || fail "Files missing in environment"

if [ "$GLOMNAME" != kernel ]; then
	cd $INSTALL_FILES/platform/$KARCH
	mv kernel $GLOMNAME
fi

STATE=1;	# All files copied to $INSTALL_FILES successfuly
make_tarfile;	# sets STATE=2 if successful
remote_install;	# sets STATE=3 if successful
okexit
