#!/bin/ksh
#
#ident	"@(#)bfu	1.69	96/04/03 SMI"
#
# Upgrade a machine from a cpio archive area in about 5 minutes.
# By Roger Faulkner and Jeff Bonwick, April 1993.
# (bfu == Bonwick/Faulkner Upgrade, a.k.a. Blindingly Fast Upgrade)
#
# Usage: bfu    <archive_dir> [root-dir]	# for normal machines
#        bfu -c <archive_dir> <exec-dir>	# for diskless clients
#
# You have to be super-user.  It's safest to run this from the
# system console, although I've run it under OW and even via
# remote login with no problems.
#
# You will have to reboot the system when the upgrade is complete.
#
# You should add any administrative files you care about to this list.
# Warning: there had better be no leading '/' on any of these filenames.

export GATE=on596-gate

files="
	etc/.login
	etc/acct/holidays
	etc/asppp.cf
	etc/auto_*
	etc/cron.d/at.deny
	etc/cron.d/cron.deny
	etc/default/*
	etc/devlink.tab
	etc/dfs/dfstab
	etc/driver_aliases
	etc/driver_classes
	etc/dumpdates
	etc/group
	etc/inet/*
	etc/inittab
	etc/iu.ap
	etc/logindevperm
	etc/lp/Systems
	etc/mail/aliases
	etc/mail/*.cf
	etc/mail/*.hf
	etc/mail/*.rc
	etc/minor_perm
	etc/name_to_sysnum
	etc/name_to_major
	etc/net/*/*
	etc/netconfig
	etc/nscd.conf
	etc/nsswitch.*
	etc/passwd
	etc/path_to_inst
	etc/profile
	etc/publickey
	etc/rc?.d/*
	etc/remote
	etc/resolv.conf
	etc/rmmount.conf
	etc/rpc
	etc/rpld.conf
	etc/saf/_sactab
	etc/saf/_sysconfig
	etc/saf/zsmon/_pmtab
	etc/shadow
	etc/skel/.profile
	etc/skel/local.*
	etc/syslog.conf
	etc/system
	etc/ttydefs
	etc/ttysrch
	etc/uucp/[A-Z]*
	etc/vfstab
	etc/vold.conf
	kernel/drv/pcic.conf
	kernel/misc/sysinit
	platform/i86pc/kernel/drv/aha.conf
	platform/i86pc/kernel/drv/asy.conf
	platform/prep/kernel/drv/asy.conf
	usr/kernel/drv/audiocs.conf
	var/spool/cron/crontabs/*
	var/yp/aliases
	var/yp/nicknames
"

# kernel modules that may not be in use, but need to be loaded
# for bfu to work properly
modlist="
	/usr/kernel/fs/pcfs
"

filelist() {
	find $files -depth -type f ! -name core -print 2>/dev/null | sort
}

#
# Make a local copy of bfu in /tmp and execute that instead.
# This makes us immune to loss of networking and/or changes
# to the original copy that might occur during execution.
#
cd .
abspath=`[[ $0 = /* ]] && print $0 || print $PWD/$0`
if [[ $abspath != /tmp/* ]]; then
	localpath=/tmp/bfu.$$
	print "Copying $abspath to $localpath"
	cp $abspath $localpath
	print "Executing $localpath $*\n"
	exec $localpath $*
fi

export PATH=/usr/bin:/usr/sbin:/sbin

fail() {
	print "$*"
	print "bfu aborting"
	exit 1
}

if [ "$1" = "-c" ]; then
	diskless=yes
	shift
else
	diskless=no
fi

test $# -ge 1 || fail "usage: bfu archive-dir [root-dir]"

isa=`uname -p`
karch=`uname -m`
plat=`uname -i`
test $karch = sun5f && karch=sun4u	# Hack to ease sun5f to sun4u transition

cpiodir=$1
[[ "$cpiodir" = */* ]] || cpiodir=/ws/$GATE/archives/$isa/$1

[[ "$cpiodir" = /* ]] || fail "archive-dir must be an absolute path"

if [ $diskless = no ]; then
	root=${2:-/}
	[[ "$root" = /* ]] || fail "root-dir must be an absolute path"
	usrroot=$root
	usr=${usrroot%/}/usr
	rootlist=$root
	if [ $plat != $karch -a -f ${cpiodir}/${plat}.root \
	    -a -f ${cpiodir}/${plat}.usr ]
	then
		allarchs="$karch $plat"
	else
		allarchs=$karch
	fi

	rootslice=`df -k $root | nawk 'NR > 1 { print $1 }' | sed s/dsk/rdsk/`
	print "Loading $cpiodir on $root"
else
	usrroot=$2
	usr=$2/usr
	[[ "$usr" = /export/exec/* ]] || fail "exec-dir $usrroot sounds bogus"
	cd $cpiodir
	test -f generic.root || fail "$cpiodir/generic.root missing"
	allarchs=`ls *.root | grep -v generic.root | sed -e 's/.root$//'`
	rootlist=""
	for root in /export/root/*
	do
		test -f $root/etc/vfstab &&
			egrep -s $usrroot $root/etc/vfstab &&
			rootlist="$rootlist $root"
	done
	test -n "$rootlist" || fail "no clients to upgrade"
	print "Loading $cpiodir usr archives on:\n\t$usr\n"
	print "Loading $cpiodir root archives on:"
	for root in $rootlist
	do
		print "\t$root"
	done
fi

set `id`
test "$1" = "uid=0(root)" || fail "You must be super-user to run this script."

print "\nCreating bfu execution environment ..."

#
# Save off a few critical libraries and commands, so that bfu will
# continue to function properly even in the face of major
# kernel/library/command incompatibilities during a live upgrade.
# (You can successfully upgrade from Jupiter FCS to the latest stuff!)
#
bfucmd="
	/usr/bin/cat
	/usr/bin/chmod
	/usr/bin/cmp
	/usr/bin/cp
	/usr/bin/cpio
	/usr/bin/date
	/usr/bin/dd
	/usr/bin/echo
	/usr/bin/egrep
	/usr/bin/expr
	/usr/bin/file
	/usr/bin/find
	/usr/bin/grep
	/usr/bin/head
	/usr/bin/ksh
	/usr/ccs/bin/ld
	/usr/sbin/lockfs
	/usr/bin/ls
	/usr/bin/mkdir
	/usr/bin/mv
	/usr/bin/nawk
	/usr/bin/rm
	/usr/bin/sed
	/usr/bin/sh
	/usr/bin/sleep
	/usr/bin/sort
	/usr/sbin/sync
	/usr/bin/tail
	/usr/sbin/tar
	/usr/bin/touch
	/usr/bin/uname
	/usr/bin/uniq
	${FASTFS-/ws/$GATE/public/bin/$isa/fastfs}
"

rm -rf /tmp/bfubin
mkdir /tmp/bfubin
cp $bfucmd /tmp/bfubin

#
# If available, use ldd to determine which libraries bfu depends on.
# Otherwise, just make an educated guess.
#
if [ -x /usr/bin/ldd ]; then
	bfulib="`ldd $bfucmd | nawk '$3 ~ /lib/ { print $3 }' | sort | uniq`"
else
	bfulib="
		/usr/lib/libc.so.1
		/usr/lib/libc2.so
		/usr/lib/libdl.so.1
		/usr/lib/libelf.so.1
		/usr/lib/libintl.so.1
		/usr/lib/libld.so.2
		/usr/lib/libmapmalloc.so.1
		/usr/lib/libmp.so.1
		/usr/lib/libnsl.so.1
		/usr/lib/libsec.so.1
		/usr/lib/libsocket.so.1
		/usr/lib/libw.so.1
	"
fi

rm -rf /tmp/bfulib
mkdir /tmp/bfulib
cp $bfulib /tmp/bfulib

export PATH=/tmp/bfubin:$PATH
export LD_LIBRARY_PATH=/tmp/bfulib

print "Verifying archives ..."

for a in generic $allarchs
do
	test -r $cpiodir/$a.usr  || fail "bfu archive $cpiodir/$a.usr missing"
	test -r $cpiodir/$a.root || fail "bfu archive $cpiodir/$a.root missing"
done

for root in $rootlist
do
	cd $root || fail "Cannot cd $root"
	prologue=${root%/}/bfu.prologue
	if [ -f $prologue ]; then
		print "Executing $prologue"
		$prologue || fail "$prologue failed"
	fi
done

print "Performing basic sanity checks ..."

for dir in $usr $rootlist
do
	test -d $dir || fail "$dir does not exist"
	test -w $dir || fail "$dir is not writable"
	cd $dir || fail "Cannot cd $dir"
done

#
# Perform additional sanity checks if we are upgrading the live system.
#
if [ "$rootlist" = "/" ]
then
	#
	# Filesystem space checks
	#
	set $root 4 $usr 6
	while [ $# -gt 0 ]
	do
		test "`df -b $1 | tail -1 | nawk '{ print $2 }'`" -ge ${2}000 ||
			fail "Less than $2 MB free on $1 -- bfu not safe."
		shift 2
	done
	#
	# Disable kernel module unloading
	#
	print "Disabling kernel module unloading ... \c"
	test -x /usr/bin/adb || fail "/usr/bin/adb not found: bfu not safe."
	echo "moddebug/W10" | adb -kw /dev/ksyms /dev/mem | grep moddebug
	ls $cpiodir >>/dev/null		# loads elfexec and networking
	for mod in $modlist
	do
		if [ -f $mod ]; then
			modload $mod
		fi
	done
	#
	# Save and restore system-maintained files.
	# This allows 'who' and 'df' to work after the upgrade is finished.
	#
	live_files="
		var/adm/utmp
		var/adm/utmpx
		var/adm/wtmp
		var/adm/wtmpx
		var/adm/spellhist
		var/adm/aculog
		var/log/syslog
		var/log/authlog
		var/saf/zsmon/log
		etc/mnttab  
	"
	rm -rf /tmp/live_files.cpio
	ls $live_files | cpio -ocB >/tmp/live_files.cpio 2>/dev/null
	#
	# Kill off sendmail so that (1) mail doesn't bounce during the
	# interval where /etc/aliases is (effectively) empty, and (2)
	# the alias database doesn't get rebuilt against the empty
	# /etc/aliases file due to incoming mail.  If that happens,
	# then since bfu preserves modification time when it restores
	# the real /etc/aliases the database will incorrectly appear
	# to be up-to-date.  To fully nail case (2) bfu also removes
	# the alias database files after restoring configuration files
	# in case another user triggered an alias rebuild by sending
	# mail during the bfu.
	#
	print "Killing sendmail ..."
	/etc/init.d/sendmail stop
fi

print "Turning on delayed i/o ..."
fastfs -f $rootlist $usr
fastfs $rootlist $usr

#
# Usage: extract_archives (root|usr) arch-list
#
extract_archives() {
	base=$1
	shift
	test $base = root && cd $root || cd $usrroot
	for archive in $*
	do
		print "Extracting $archive.$base ... \c"
		test -h platform/$archive && rm platform/$archive
		cpio -idmucB <$cpiodir/$archive.$base 2>&1 | egrep -v \
		'"usr/openwin"|"usr/share/man"|"dev/fd"|"home"|"proc"| error.s'
		sleep 2	# ensures that later archives have later timestamps
	done
	cd $root
}

#
# Hacks to work around minor annoyances and make life more pleasant.
# Part 1 of 2: usr stuff
#
rm -f $usr/lib/locale/C/* 2>/dev/null	# formerly files, now dirs (1141066)
rm -f $usr/kernel/drv/win 2>/dev/null		# nuked
rm -f $usr/kernel/drv/win.conf 2>/dev/null	# nuked
rm -rf $usr/platform/SUNW,SPARCstation-10,SX	# formerly dir, now symlink

test $diskless = yes && extract_archives usr generic $allarchs

for root in $rootlist
do
	SECONDS=0		# time each iteration

	cd $root || fail "Cannot cd $root"
	rootprefix=${root%/}

	print "\nSaving configuration files in $rootprefix/bfu.child ... \c"
	rm -rf bfu.default bfu.restore	# historical
	rm -rf bfu.child bfu.conflicts
	mkdir bfu.child bfu.conflicts
	filelist | cpio -pdmu bfu.child
	test -f etc/motd && mv etc/motd etc/motd.old

	if [ $diskless = no ]; then
		cpio -idmucB 'usr/platform/'$karch'/lib/fs/ufs/*' \
			<$cpiodir/$karch.usr 2>/dev/null
		case $isa in
		    sparc)
			if [[ "$rootslice" = /dev/rdsk/* ]]; then
				print "Installing boot block."
				cd $usr/platform/$karch/lib/fs/ufs
				installboot ./bootblk $rootslice
			fi
			;;
		    i386)
			NEWPBOOTDIR=/ws/$GATE/public/pboot
			NEWPBOOT=${NEWPBOOTDIR}/pboot
			NEWBOOTBLK=${NEWPBOOTDIR}/bootblk
			PBOOTDIR=$usr/platform/$karch/lib/fs/ufs
			PBOOT=${PBOOTDIR}/pboot
			BOOTBLK=${PBOOTDIR}/bootblk

			# they should already be there, but...
			if [ -f $NEWPBOOT -a ! -f $PBOOT ]; then
				print "Installing pboot from $NEWPBOOTDIR"
				cp $NEWPBOOT $PBOOT
			fi
			if [ -f $NEWBOOTBLK -a ! -f $BOOTBLK ]; then
				print "Installing bootblk from $NEWPBOOTDIR"
				cp $NEWBOOTBLK $BOOTBLK
			fi

			if [ -f $NEWPBOOT -a -f $PBOOT ]; then
				LATEST=`ls -Lt $PBOOT $NEWPBOOT | head -1`
				if [ "$LATEST" = "$NEWPBOOT" ]; then
					print "Updating pboot from $NEWPBOOT"
					cp $NEWPBOOT $PBOOT
				fi
			fi
			if [ -f $NEWBOOTBLK -a -f $BOOTBLK ]; then
				LATEST=`ls -Lt $BOOTBLK $NEWBOOTBLK | head -1`
				if [ "$LATEST" = "$NEWBOOTBLK" ]; then
					print "Updating bootblk from $NEWBOOTBLK"
					cp $NEWBOOTBLK $BOOTBLK
				fi
			fi
			if [[ "$rootslice" = /dev/rdsk/* ]]; then
				print "Installing boot block."
				cd $PBOOTDIR
				installboot ./pboot ./bootblk ${rootslice%??}s2
			fi
			;;
		    ppc)
			NEWVOFDIR=/ws/$GATE/public/VOF
			NEWVOF=${NEWVOFDIR}/openfirmware.x41.${plat}
			VOF=$root/platform/${plat}/openfirmware.x41
			OLDVOF=$root/platform/${karch}/openfirmware.x41

			# OLDVOF location doesn't jive with where install
			# puts it.
			rm -f ${OLDVOF}
			# copy new VOF if not already on system
			if [ -f $NEWVOF -a ! -f $VOF ]; then
				print "Installing new VOF from $NEWVOF"
				cp $NEWVOF $VOF
			fi
			# update VOF
			if [ -f $NEWVOF -a -f $VOF ]; then
				LATEST=`ls -Lt $VOF $NEWVOF | head -1`
				if [ "$LATEST" = "$NEWVOF" ]; then
					print "Updating VOF from $NEWVOF"
					cp $NEWVOF $VOF
				fi
			fi
			if [ -f $VOF ]; then
				DASHF=-f
			else
				VOF=
				DASHF=
			fi
			if [[ "$rootslice" = /dev/rdsk/* ]]; then
				print "Installing boot block."
				cd $usr/platform/$karch/lib/fs/ufs
				installboot $DASHF $VOF ./bootblk $rootslice
			fi
			;;
		    *)
			;;	# unknown ISA
		esac
		
	fi

	if [ $diskless = yes ]; then
		node=${root##*/}
		archlist=""
		for arch in $allarchs
		do
			egrep -s '/export/exec/.*'$arch'/usr/kvm' \
				$root/etc/vfstab ||
				test -d $root/platform/$arch &&
				archlist="$archlist $arch"
		done
	else
		node=`uname -n`
		archlist=$allarchs
		extract_archives usr generic $archlist
	fi

	extract_archives root generic $archlist

	#
	# Restore live_files if we are upgrading the live system.
	#
	test "$root" = "/" && cpio -idmucB </tmp/live_files.cpio 2>/dev/null

	touch reconfigure

	print "Removing duplicate kernel binaries."

	for arch in $archlist
	do
		p=$rootprefix/platform/$arch/kernel
		k=$rootprefix/kernel
		u=$usr/kernel

		for d in `find $p $k $u -type f -print | sed 's,.*/kernel/,,' |
			sort | uniq -d`
		do
			set `ls -ct $p/$d $k/$d $u/$d 2>/dev/null`
			print rm $2 $3
			rm $2 $3
		done
	done

	print "\nRestoring configuration files.\n"

	cd $root
	rm -rf bfu.ancestor
	test -d bfu.parent && mv bfu.parent bfu.ancestor
	mkdir bfu.parent
	filelist | cpio -pdmu bfu.parent 2>/dev/null
	cd bfu.child
	for file in `filelist`
	do
		parent=$rootprefix/bfu.parent/$file
		child=$rootprefix/bfu.child/$file
		ancestor=$rootprefix/bfu.ancestor/$file
		conflicts=$rootprefix/bfu.conflicts/$file
		actual=$rootprefix/$file

		cmp -s $child $actual && continue
		test -f $parent || continue
		if cmp -s $child $ancestor; then
			print "      update: $file"
			continue
		fi
		if (cmp $child $parent 2>&1) | egrep -s 'EOF on '$parent; then
			print "     restore: $file"
			cp -p $child $actual
			continue
		fi
		if cmp -s $parent $ancestor; then
			print "old \c"
		else
			print "NEW \c"
		fi
		print "conflict: $file"
		(cd $root; print $file | cpio -pdmu bfu.conflicts 2>/dev/null)
		cp -p $child $actual
	done

	print "\nFor each file in conflict, your version has been restored."
	print "The new versions are under $rootprefix/bfu.conflicts."
	print "\nMAKE SURE YOU RESOLVE ALL CONFLICTS BEFORE REBOOTING.\n"

	cd $root

	print "bfu'ed from $cpiodir on `date +%m/%d/%y`" >>etc/motd
	egrep -v '^(Sun Microsystems|SunOS Internal|Callisto|bfu.ed from)' \
		etc/motd.old >>etc/motd 2>/dev/null

	#
	# Hacks to work around minor annoyances and make life more pleasant.
	# Part 2 of 2: root stuff
	#
	rm -f kadb				# not needed in post-kbi world
	rm -f etc/rc2.d/S73autofs		# is now S74autofs
	rm -f var/spool/cron/crontabs/sysadm	# very existence a bug (1165307)
	rm -f var/statmon/state			# to prevent lockd/statd hangs
	rm -f etc/mail/aliases.dir etc/mail/aliases.pag	# force alias rebuild
	for f in etc/auto_*			# to make autofs happy
	do
		file $f | grep executable >/dev/null || chmod -x $f
	done
	if [ -f usr/lib/print/conv_lp -a ! -f etc/printers.conf ]; then
		#
		# Need to convert printer configuration files
		#
		chroot $root /usr/lib/print/conv_lp >/dev/null
		if [ -d etc/lp/printers ]; then
			cd etc/lp/printers
			for printer in `/bin/ls -d [A-Za-z0-9]* 2>/dev/null`
			do
				egrep -s "Remote:" ${printer}/configuration &&
					rm -rf $printer ../interfaces/$printer
			done
		fi
		cd $root
	fi

	epilogue=$rootprefix/bfu.epilogue
	if [ -f $epilogue ]; then
		print "Executing $epilogue"
		$epilogue || print "WARNING: $epilogue failed"
	fi

	((min = (SECONDS + 30) / 60))

	print "Upgrade of $node took $min minutes."

	#
	# Do logging in the background so that if the automounter is gone,
	# bfu doesn't wedge at this point.
	#
	log=/ws/$GATE/public/bfu.log
	(test -w $log && print \
		"`date +%y%m%d` $node `uname -rv` $arch $cpiodir $min" >>$log) &

done

print "Turning off delayed i/o and syncing filesystems ..."
sync
fastfs -s $rootlist $usr
fastfs $rootlist $usr
sync
lockfs -f $rootlist $usr

print "Edit configuration files as necessary, then reboot."

exit 0
