#!/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 2006 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
#ident	"%Z%%M%	%I%	%E% SMI"

export PATH=/usr/bin:/usr/sbin:/usr/ccs/bin
unset ENV TMPDIR
umask 022
cwd=$PWD

isa=$(uname -p)
if [[ $isa = sparc ]]; then
	isa64=sparcv9
elif [[ $isa = i386 ]]; then
	isa64=amd64
else
	isa64=unknown
fi

if [[ -n "$CODEMGR_WS" ]]; then
	sysroot=$CODEMGR_WS/proto/root_$isa
elif [[ -n "$ROOT" ]]; then
	sysroot=$ROOT
else
	sysroot=/
fi

quote=
eol='\'
files=

simchan=com.sun:fm:fmd$$
simroot=/tmp/fmd.$$
simscript=run
simpid=

truss_cmd=
truss_args=
dump_args=
inj_args=
fmd_args=

opt_h=false
opt_i=false
opt_s=false
opt_w=false
opt_x=false

function cp_so
{
	nm -ghp $1 2>/dev/null | while read addr type name; do
		[[ $type != T ]] && continue
		case $name in
		_fmd_init)        cp $1 $2/usr/lib/fm/fmd/plugins; return ;;
		fmd_fmri_nvl2str) cp $1 $2/usr/lib/fm/fmd/schemes; return ;;
		topo_load)        cp $1 $2/usr/lib/fm/topo/plugins; return ;;
		esac
	done
	die "\nunknown .so type -- $1"
}

function cp_topo
{
	mkdir -p $2/usr/lib/fm/topo/maps
	cp $1 $2/usr/lib/fm/topo/maps; 
	for platdir in $2/usr/platform/*/lib/fm/topo/maps; do
		rm -f $platdir/* 2>/dev/null
	done
}

function list_cmds
{
	for cmd in fmadm fmdump fmstat; do
		echo usr/sbin/$cmd
	done
}

function wait_status
{
	if [[ $1 -gt 128 ]]; then
		sig=$(kill -l $(($1 - 128)))
		die "fmd terminated from signal $sig (see $simroot)"
	elif [[ $1 -ne 0 ]]; then
		die "fmd terminated with status $1 (see $simroot)"
	fi
}

function wait_prompt
{
	echo "fmsim: [ Press return to $* ] \c"
	mode=$(stty -g)
	stty -echo -isig min 1 time 0
	read s; echo
	stty $mode
}

function die
{
	echo "fmsim: $*" >& 2
	$opt_w && wait_prompt exit
	[[ -n "$simpid" ]] && exit 1 || exit 2
}

while [[ $# -gt 0 ]]; do
	OPTIND=1; while getopts ':d:D:ehio:st:vVwx' c; do
		case "$c" in
		d)
			simroot=$OPTARG
			;;
		D)
			truss_cmd=dtrace
			truss_args="-s $OPTARG -c"
			quote="'"; eol=""
			;;
		e|v|V)
			dump_args="$dump_args -$c"
			;;
		h|i|s|w|x)
			eval opt_$c'='true
			;;
		o)
			fmd_args="$fmd_args -o $OPTARG"
			;;
		t)
			truss_cmd=truss
			truss_args="$OPTARG"
			;;
		:)
			die "option requires an argument -- $OPTARG"
			;;
		*)
			die "illegal option -- $OPTARG"
			;;
		esac
	done
	let OPTIND="$OPTIND - 1"; shift $OPTIND

	if [[ $# -gt 0 ]]; then
		if [[ -d $1 ]]; then
			files="$files $1/*"
		else
			files="$files $1"
		fi
		shift
	fi
done

for file in $files; do
	[[ -r $file ]] || die "input file is missing or not readable -- $file"
done

if $opt_h || [[ -z "$files" && $opt_i = false ]]; then
	echo "Usage: fmsim [-ehisvVwx] [-d dir] [-D a.d] [-o opt=val]" \
	    "[-t args] [file ...]"

	echo "\t-d  set the simulation root directory to the given location"
	echo "\t-D  start fmd(1M) using dtrace(1M) and specified D script"
	echo "\t-e  display error log content instead of fault log content"
	echo "\t-h  display usage information for fmsim and exit"
	echo "\t-i  set interactive mode: do not stop after sending events"
	echo "\t-o  set fmd(1M) option to specified value during simulation"
	echo "\t-s  set up simulation world but do not actually run simulation"
	echo "\t-t  start fmd(1M) using truss(1) and specified arguments"
	echo "\t-v  set verbose mode: display additional event detail"
	echo "\t-V  set very verbose mode: display complete event contents"
	echo "\t-w  wait for a keypress after simulation completes"
	echo "\t-x  delete simulation world if simulation is successful"

	exit 0
fi

echo "fmsim: creating simulation world $simroot ... \c"
[[ -d $simroot ]] || mkdir -p $simroot || exit 1
cd $simroot || exit 1
echo "done."

echo "fmsim: populating /var ... \c"
mkdir -p -m 0755 var/fm/fmd
mkdir -p -m 0700 var/fm/fmd/ckpt
mkdir -p -m 0700 var/fm/fmd/rsrc
mkdir -p -m 0700 var/fm/fmd/xprt
echo "done."

echo "fmsim: populating /usr/lib/fm from $sysroot ... \c"
(cd $sysroot && find usr/lib/fm -depth -print | cpio -pdmu $simroot)

for platdir in $sysroot/usr/platform/*/lib/fm; do
	[[ -d $platdir ]] && platdir=${platdir#$sysroot} || continue
	echo "fmsim: populating $platdir from $sysroot ... \c"
	(cd $sysroot && find ${platdir#/} -depth -print | cpio -pdmu $simroot)
done

echo "fmsim: populating /usr/lib/locale/$LANG from $sysroot ... \c"
(cd $sysroot && find usr/lib/locale/$LANG -depth -print | cpio -pdmu $simroot)

echo "fmsim: populating /usr/sbin from $sysroot ... \c"
(cd $sysroot && list_cmds | cpio -pdmu $simroot)

echo "fmsim: adding customizations:\c"
cd $cwd || exit $1

for file in $files; do
	base=$(basename $file)
	case $base in
	*.cmd)	die "\neversholt command file not yet supported -- $file" ;;
	fmd.conf) cp $file $simroot/etc/fm/fmd ;;
	*.conf)	cp $file $simroot/usr/lib/fm/fmd/plugins ;;
	*.dict)	cp $file $simroot/usr/lib/fm/dict ;;
	*.eft) cp $file $simroot/usr/lib/fm/eft ;;
	*.esc)	die "\neversholt source file not yet supported -- $file" ;;
	*.inj)	inj_args="$inj_args $file" ;;
	*.log)	inj_args="$inj_args $file" ;;
	*log)	inj_args="$inj_args $file" ;;
	*.mo)	cp $file $simroot/usr/lib/locale/$LANG/LC_MESSAGES ;;
	*.so)	cp_so $file $simroot ;;
	*.topo) die "\n .topo files not supported -- $file" ;;
	*.xml) cp_topo $file $simroot ;;
	*)	die "\nunknown file type or suffix -- $file" ;;
	esac
	echo " $base\c"
done

cd $simroot || exit 1
echo " done."

echo "fmsim: generating script ... \c"
cat >$simscript <<EOS
#!/bin/ksh -p
#
# Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
#ident	"@(#)fmsim.ksh	1.5	06/10/11 SMI"

#
# fmsim(1M) script generated for $simroot $(date)
#

export LD_LIBRARY_PATH=$simroot/usr/lib:$simroot/usr/lib/fm
export LD_LIBRARY_PATH_64=$simroot/usr/lib/64:$simroot/usr/lib/fm/$isa64

export _THREAD_ERROR_DETECTION=2

exec $truss_cmd $truss_args $quote./usr/lib/fm/fmd/fmd -R $simroot $eol
    -o fg=true -o clock=simulated $eol
    -o rpc.adm.prog=0 -o rpc.adm.path=$simroot/rpc $eol
    -o sysevent-transport:device=/dev/null $eol
    -o sysevent-transport:channel=$simchan $fmd_args$quote

EOS

chmod 0555 $simscript
echo "done."

if $opt_s; then
	echo "fmsim: simulation is saved in $simroot"
	exit 0
fi

export LD_LIBRARY_PATH=$simroot/usr/lib:$simroot/usr/lib/fm
export LD_LIBRARY_PATH_64=$simroot/usr/lib/64:$simroot/usr/lib/fm/$isa64

echo "fmsim: simulation $$ running fmd(1M)\c"
./usr/lib/fm/fmd/fmd -V | cut -d: -f2

./$simscript &
simpid=$!
trap '' INT HUP
cd $cwd
i=0

while [[ ! -s $simroot/rpc ]]; do
	[[ $i -ge 30 ]] && kill -9 $simpid >/dev/null 2>&1
	kill -0 $simpid >/dev/null 2>&1 || break
	let i="$i + 1"
	sleep 1
done

kill -0 $simpid >/dev/null 2>&1 || {
	wait $simpid
	wait_status $?
}

echo "fmsim: rpc adm requests can rendezvous at" $(<$simroot/rpc)
echo "fmsim: injectors should use channel $simchan"
echo "fmsim: debuggers should attach to PID $simpid"

for arg in $inj_args; do
	echo "fmsim: injecting events from $arg ... \c"
	$simroot/usr/lib/fm/fmd/fminject -q -c $simchan $arg || {
		echo "fmsim: fminject failed for $arg: aborting simulation" >& 2
		kill $simpid >/dev/null 2>&1
	}
	echo "done."
done

if [[ $opt_i = false ]]; then
	echo "fmsim: injecting event to advance to end-of-time ... \c"
	echo 'endhrtime;' | $simroot/usr/lib/fm/fmd/fminject -q -c $simchan -
	echo "done."
fi

wait $simpid
status=$?

if [[ -f $simroot/var/fm/fmd/errlog ]]; then
	echo; $simroot/usr/sbin/fmdump -R $simroot $dump_args; echo
fi

wait_status $status
$opt_w && wait_prompt exit
$opt_x && rm -rf $simroot

exit 0
