#!/bin/bash

# usttrace  by Pierre-Marc Fournier 2009
# Distributed under the GPLv2.

function error() {
	echo "$0: error: $1" 2>/dev/stderr
}

function sighandler() {
	echo "Caught Ctrl-C"
	if [ -z "$USTDPID" ]; then
		USTDPID="$(<$pidfilepath)"
	fi
	# Tell the daemon to die
	kill -SIGTERM "$USTDPID"

	echo "Waiting for ustd to shutdown..."
	wait "$USTDPID"

	rm "$pidfilepath"

	exit 0;
}

USTTRACE_DIR="$(dirname $0)"
if [ -x "${USTTRACE_DIR}/ustd/ustd" ] ; then
    # Use the not installed libraries instead
    USTD="${USTTRACE_DIR}/ustd/ustd"
    LIBINTERFORK_PATH="${USTTRACE_DIR}/libustfork/.libs/libustfork.so"
    LIBMALLOCWRAP_PATH="${USTTRACE_DIR}/libustinstr-malloc/.libs/libustinstr-malloc.so"
    LIBUST_PATH="${USTTRACE_DIR}/libust/.libs/libust.so"
else
    # Use the libraries that the dynamic link finds
    USTD="ustd"
    if [ ! -x "$(which ustd 2>/dev/null)" ]; then
        error "cannot find an executable ustd; make sure its location is in the PATH"
        exit 1
    fi
    LIBINTERFORK_PATH="libustfork.so"
    LIBMALLOCWRAP_PATH="libustinstr-malloc.so"
    LIBUST_PATH="libust.so.0"
fi

BASE_TRACE_DIR="${HOME}/.usttraces"

function usage () {
	echo "usage:  $0 OPTIONS COMMAND" 2>/dev/stderr
	echo "" 2>/dev/stderr
	echo "Options:" 2>/dev/stderr
	echo "    -l    Runtime link with UST library." 2>/dev/stderr
	echo "          (Needed only if program was not linked at compile time with libust.)" 2>/dev/stderr
	echo "    -L    Add path to ust libraries to LD_LIBRARY_PATH." 2>/dev/stderr
	echo "    -m    Instrument malloc calls." 2>/dev/stderr
	echo "    -f    Also trace forked processes." 2>/dev/stderr
	echo "    -s    Use system-wide daemon instead of creating one for this session." 2>/dev/stderr
	echo "    -S    Specify the subbuffer size." 2>/dev/stderr
	echo "    -N    Specify the number of subbuffers." 2>/dev/stderr
}

while getopts ":hlLmfsWS:N:" options; do
	case $options in
		l) arg_preload_libust=1;;
		L) arg_ld_std_ust=1;;
		m) arg_preload_malloc=1;;
		f) arg_preload_fork=1;;
		s) arg_syswide_daemon=1;;
		W) where=1;;
		S) export UST_SUBBUF_SIZE=$OPTARG;;
		N) export UST_SUBBUF_NUM=$OPTARG;;
		h) usage;
		   exit 0;;
		\?) usage
			exit 1;;
		*) usage
			exit 1;;
	esac
done
shift $(($OPTIND - 1))

if [ -n "$where" ]; then
	echo $BASE_TRACE_DIR/$(ls "$BASE_TRACE_DIR" | tail -n 1)
	exit 0
fi

# Prepare vars
CMD=$*

# Validate input
if [ -z "$HOME" ];
then
	error "no home specified"
fi

if [ -z "$CMD" ];
then
	error "no command specified"
	usage;
	exit 1
fi

# Create directory for trace output
DATESTRING="$(hostname)-$(date +%Y%m%d%H%M%S%N)"
OUTDIR="$BASE_TRACE_DIR/$DATESTRING"
mkdir -p "$OUTDIR"

# Choose ustd socket path
USTDSOCKPATH="/tmp/ustd-sock-$$"

if [ "$arg_syswide_daemon" != "1" ];
then
	pidfilepath="/tmp/usttrace-$USER-$(date +%Y%m%d%H%M%S%N)-ustd-pid"
	trap "sighandler $pidfilepath" SIGINT
	mkfifo -m 0600 "$pidfilepath"
	# Start daemon
	$USTD --pidfile "$pidfilepath" -s "$USTDSOCKPATH" -o "$OUTDIR" >"$OUTDIR/ustd.log" 2>&1 &
	# ustd sets up its server socket
	# ustd opens the pidfile, blocks because no one has opened it
	# we open pidfile
	# we block reading pidfile
	# ustd writes to pidfile
	# ustd closes pidfile
	# we unblock reading pidfile
	USTDPID="$(<$pidfilepath)"
	export UST_DAEMON_SOCKET="$USTDSOCKPATH"
fi

# Establish the environment for the command
(
    export UST_TRACE=1
    export UST_AUTOPROBE=1

    if [ "$arg_preload_libust" = "1" ];
    then
	if [ -n "${LIBUST_PATH%libust.so}" ] ; then
	    export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${LIBUST_PATH%libust.so}"
	fi
	export LD_PRELOAD="$LD_PRELOAD:$LIBUST_PATH"
    fi

    if [ "$arg_ld_std_ust" = "1" ];
    then
	if [ -n "$${LIBUST_PATH%libust.so}" ] ; then
	    export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${LIBUST_PATH%libust.so}"
	fi
    fi

    if [ "$arg_preload_malloc" = "1" ];
    then
	export LD_PRELOAD="$LD_PRELOAD:$LIBMALLOCWRAP_PATH"
    fi

    if [ "$arg_preload_fork" = "1" ];
    then
	export LD_PRELOAD="$LD_PRELOAD:$LIBINTERFORK_PATH"
    fi

# Execute the command
    $CMD 2>&1
) | tee "$OUTDIR/app.log"

## Because of the keepalive mechanism, we're sure that by the time
## we get here, the daemon is connected to all the buffers that still exist.
## Therefore we can politely ask it to die when it's done.

if [ "$arg_syswide_daemon" != "1" ];
then
	# Tell the daemon to die
	kill -SIGTERM "$USTDPID"

	echo "Waiting for ustd to shutdown..."
	wait "$USTDPID"

	rm "$pidfilepath"
fi

echo "Trace was output in: " $OUTDIR
