#!/bin/sh
#
# opcontrol is a script to control OProfile
# opcontrol --help and opcontrol --list-events have info
#
# Copyright 2002
# Read the file COPYING
#
# Authors: John Levon, Philippe Elie, Will Cohen, Jens Wilke, Daniel Hansel
#
# Copyright IBM Corporation 2007
#
# NOTE: This script should be as shell independent as possible

SYSCTL=do_sysctl

# A replacement function for the sysctl (procps package) utility which is
# missing on some distribution (e.g. slack 7.0).
# Handles only the -w option of sysctl.
do_sysctl()
{
	if test "$1" != "-w"; then
		echo "$0 unknown sysctl option" >&2
		exit 1
	fi

	shift

	arg=`echo $1 | awk -F= '{print $1}'`
	val=`echo $1 | awk -F= '{print $2}'`

	dev_name=`echo $arg | tr . /`

	if test ! -f /proc/sys/$dev_name; then
		echo "/proc/sys/$dev_name does not exist or is not a regular file" >&2
		exit 1
	fi
	echo $val > /proc/sys/$dev_name
}

# Helper function to check if oprofile daemon is active.
# Takes one argument: the "lock file" for the oprofile daemon.
# The lock file may exist even if the daemon was killed or died in
# some way.  So we do a kill SIG_DFL to test whether the daemon is
# truly alive. If the lock file is stale (daemon dead), the kill will
# not return '0'.
is_oprofiled_active()
{
        [ -f "$1" ] && kill -0 `cat "$1"` 2>/dev/null
}

# check value is set
error_if_empty()
{
	if test -z "$2"; then
		echo "No value given for option $1" >&2
		do_help
		exit 1
	fi
}

# guess_number_base() checks if string is a valid octal(8), hexidecimal(16),
# or decimal number(10). The value is returned in $?. Returns 0, if string
# isn't a octal, hexidecimal, or decimal number.
guess_number_base()
{
	case "$1" in
		0[Xx]*[!0-9a-fA-F]*)	return 0  ;; # Bad hex string
		0[Xx][0-9a-fA-F]*)	return 16 ;; # Hex
		*[!0-9]*)		return 0  ;; # Some non-digit char
		[1-9]*)			return 10 ;; # Base 10
		0*[89]*)		return 0  ;; # Bad octal string
		0*)			return 8  ;; # Octal
	esac
	return 0
}

# check value is a valid number
error_if_not_number()
{
	error_if_empty "$1" "$2"
	guess_number_base "$2"
	if test "$?" -eq 0 ; then
		echo "Argument for $1, $2, is not a valid number." >&2
		exit 1
	fi
}

# check value is a base filename
error_if_not_valid_savename()
{
	error_if_empty "$1" "$2"
	bname=`basename "$2"`
	if test "$2" !=  "$bname"; then
		echo "Argument for $1, $2, cannot change directory." >&2
		exit 1
	fi
	case "$2" in
		# The following catches anything that is not
		# 0-9, a-z, A-Z, an '-', ':', ',', '.', or '/'
		*[!-[:alnum:]_:,./]*) 
			echo "Argument for $1, $2, not allow to have special characters" >&2
			exit 1;;
	esac
}

error_if_invalid_arg()
{
	error_if_empty "$1" "$2"
	case "$2" in
		# The following catches anything that is not
		# 0-9, a-z, A-Z, an '-', ':', ',', '.', or '/'
		*[!-[:alnum:]_:,./]*) 
			echo "Argument for $1, $2, is not valid argument." >&2
			exit 1;;
	esac
}

# rm_device arguments $1=file_name
rm_device()
{
	if test -c "$1"; then
		vecho "Removing $1"
		rm "$1"
	fi
}


# create_device arguments $1=file_name $2=MAJOR_NR $3=MINOR_NR
create_device()
{
	vecho "Doing mknod $1"
	mknod "$1" c $2 $3
	if test "$?" != "0"; then
		echo "Couldn't mknod $1" >&2
		exit 1
	fi
	chmod 700 "$1"
}


move_and_remove()
{
	if test -e $1; then
		mv $1 $SAMPLES_DIR/.tmp_reset.$$
		rm -rf $SAMPLES_DIR/.tmp_reset.$$
	fi
}


# verbose echo
vecho()
{
	if test -n "$VERBOSE"; then
		echo $@
	fi
}


is_tool_available()
{
	if which $1 > /dev/null 2>&1; then
		if test -x `which $1`; then
			return 1
		fi
	fi

	return 0
}


# print help message
do_help()
{
    cat >&2 <<EOF
opcontrol: usage:
   -l/--list-events list event types and unit masks
   -?/--help        this message
   -v/--version     show version
   --init           loads the oprofile module and oprofilefs
   --setup          give setup arguments (may be omitted)
   --status         show configuration
   --start-daemon   start daemon without starting profiling
   -s/--start       start data collection
   -d/--dump        flush the collected profiling data
   -t/--stop        stop data collection
   -h/--shutdown    stop data collection and kill daemon
   -V/--verbose[=all,sfile,arcs,samples,module,misc,ext]
                    be verbose in the daemon log
   --reset          clears out data from current session
   --save=name      save data from current session to session_name
   --deinit         unload the oprofile module and oprofilefs

   -e/--event=eventspec

      Choose an event. May be specified multiple times. Of the form
      "default" or "name:count:unitmask:kernel:user", where :

      name:     event name, e.g. CPU_CLK_UNHALTED or RTC_INTERRUPTS
      count:    reset counter value e.g. 100000
      unitmask: hardware unit mask e.g. 0x0f
      kernel:   whether to profile kernel: 0 or 1
      user:     whether to profile userspace: 0 or 1

   -p/--separate=type,[types]

       Separate profiles as follows :

       none:     no profile separation
       library:  separate shared library profiles per-application
       kernel:   same as library, plus kernel profiles
       thread:   per-thread/process profiles
       cpu:      per CPU profiles
       all:      all of the above

   -c/--callgraph=#depth         enable callgraph sample collection with a
                                 maximum depth. Use '0' to disable callgraph
                                 profiling.
   --session-dir=dir             place sample database in dir instead of
                                 default location (/var/lib/oprofile)
   -i/--image=name[,names]       list of binaries to profile (default is "all")
   --vmlinux=file                vmlinux kernel image
   --no-vmlinux                  no kernel image (vmlinux) available
   --kernel-range=start,end      kernel range vma address in hexadecimal
   --buffer-size=num             kernel buffer size in sample units.
                                 Rules: A non-zero value goes into effect after
                                 a '--shutdown/start' sequence.  A value of
                                 zero sets this parameter back to default value
                                 but does not go into effect until after a
                                 '--deinit/init' sequence.
   --buffer-watershed            kernel buffer watershed in sample units (2.6
                                 kernel). Same rules as defined for
                                 buffer-size.
   --cpu-buffer-size=num         per-cpu buffer size in units (2.6 kernel)
                                 Same rules as defined for buffer-size.
   --note-table-size             kernel notes buffer size in notes units (2.4
                                 kernel)

   --xen                         Xen image (for Xen only)
   --active-domains=<list>       List of domains in profiling session (for Xen)
                                 (list contains domain ids separated by commas)
EOF
}


# load the module and mount oprofilefs
load_module_26()
{
	grep oprofilefs /proc/filesystems >/dev/null
	if test "$?" -ne 0; then
		modprobe oprofile
		if test "$?" != "0"; then
			# couldn't load the module
			return
		fi
		grep oprofile /proc/modules >/dev/null
		if test "$?" != "0"; then
			# didn't find module
			return
		fi
		grep oprofilefs /proc/filesystems >/dev/null
		if test "$?" -ne 0; then
			# filesystem still not around
			return
		fi
	fi
	mkdir /dev/oprofile >/dev/null 2>&1
	grep oprofilefs /etc/mtab >/dev/null
	if test "$?" -ne 0; then
		mount -t oprofilefs nodev /dev/oprofile >/dev/null
	fi
	KERNEL_SUPPORT=yes
	OPROFILE_AVAILABLE=yes
}


load_module_24()
{
	grep oprof /proc/devices >/dev/null
	if test "$?" -ne 0; then
		modprobe oprofile
		if test "$?" != "0"; then
			# couldn't load a module
			return
		fi
		grep oprofile /proc/modules >/dev/null
		if test "$?" != "0"; then
			# didn't find module
			return
		fi
	fi
	KERNEL_SUPPORT=no
	OPROFILE_AVAILABLE=yes
}


load_module()
{
	OPROFILE_AVAILABLE=no
	load_module_26
	if test "$OPROFILE_AVAILABLE" != "yes"; then
		load_module_24
	fi
	if test "$OPROFILE_AVAILABLE" != "yes"; then
		echo "Kernel doesn't support oprofile" >&2
		exit 1
	fi
}

# setup variables related to path or daemon. Set vars according to following
# relationship: command-line-option > config-file-settings > defaults.
# Note that upon entry SESSION_DIR may be set by command-line option.
do_init_daemon_vars()
{
	# load settings from config file, keeping command-line value
	# of SESSION_DIR if necessary.
	if test -n "$SESSION_DIR"; then
		SAVED=$SESSION_DIR
	fi
	do_load_setup
	if test -n "$SAVED"; then
		SESSION_DIR=$SAVED
	fi

	# daemon parameters (as in op_config.h).  Note that we preserve
	# any previous value of SESSION_DIR
	if test -z "$SESSION_DIR"; then
		SESSION_DIR="/var/lib/oprofile"
	fi
	LOCK_FILE="$SESSION_DIR/lock"
	SAMPLES_DIR="$SESSION_DIR/samples"
	LOG_FILE="$SAMPLES_DIR/oprofiled.log"
	CURRENT_SAMPLES_DIR="$SAMPLES_DIR/current"
}


# pick the appropriate device mount based on kernel
decide_oprofile_device_mount()
{
	if test "$KERNEL_SUPPORT" = "yes"; then
		MOUNT="/dev/oprofile"
	else
		MOUNT="/proc/sys/dev/oprofile"
	fi
}


# pick the appropriate locations device for oprofile based on kernel
decide_oprofile_device()
{
	if test "$KERNEL_SUPPORT" = "yes"; then
		DEVICE_FILE="$MOUNT/buffer"
	else
		DEVICE_FILE="$SESSION_DIR/opdev"
		NOTE_DEVICE_FILE="$SESSION_DIR/opnotedev"
		HASH_MAP_DEVICE_FILE="$SESSION_DIR/ophashmapdev"
	fi
}

# initialise parameters
do_init()
{
	# for these three buffer size == 0 means use the default value
	# hard-coded in op_user.h
	BUF_SIZE=0
	BUF_WATERSHED=0
	CPU_BUF_SIZE=0
	NOTE_SIZE=0
	VMLINUX=
	XENIMAGE="none"
	VERBOSE=""
	SEPARATE_LIB=0
	SEPARATE_KERNEL=0
	SEPARATE_THREAD=0
	SEPARATE_CPU=0
	CALLGRAPH=0
	IBS_FETCH_EVENTS=""
	IBS_FETCH_COUNT=0
	IBS_FETCH_UNITMASK=0
	IBS_OP_EVENTS=""
	IBS_OP_COUNT=0
	IBS_OP_UNITMASK=0

	OPROFILED="$OPDIR/oprofiled"

	# location for daemon setup information
	SETUP_DIR="/root/.oprofile"
	SETUP_FILE="$SETUP_DIR/daemonrc"
	SEC_SETUP_FILE="$SETUP_DIR/daemonrc_new"

	# initialize daemon vars
	decide_oprofile_device_mount
	CPUTYPE=`cat $MOUNT/cpu_type`
	OP_COUNTERS=`ls $MOUNT/ | grep "^[0-9]\+\$" | tr "\n" " "`
	NR_CHOSEN=0

	do_init_daemon_vars
	decide_oprofile_device

	DEFAULT_EVENT=`$OPHELP --get-default-event`

	IS_TIMER=0
	IS_PERFMON=0
	if test "$CPUTYPE" = "timer"; then
		IS_TIMER=1
	else
		case "$CPUTYPE" in
			ia64/*)
				IS_PERFMON=$KERNEL_SUPPORT
				;;
		esac
	fi
}


create_dir()
{
	if test ! -d "$1"; then
		mkdir -p "$1"
		if test "$?" != "0"; then
			echo "Couldn't mkdir -p $1" >&2
			exit 1
		fi
		chmod 755 "$1"
	fi
}

get_event()
{
	GOTEVENT=`eval "echo \\$CHOSEN_EVENTS_$1"`
}

set_event()
{
	eval "CHOSEN_EVENTS_$1=$2"
}


# save all the setup related information
do_save_setup()
{
	create_dir "$SETUP_DIR"
	SAVE_SETUP_FILE="$SETUP_FILE"

# If the daemon is currently running, we want changes to the daemon config
# stored in the secondary cache file so that 'opcontrol --status' will
# show actual config data for the running daemon.  The next time the
# daemon is restarted, we'll reload the config data from this secondary
# cache file.

	if is_oprofiled_active "$LOCK_FILE"; then
		SETUP_FILE="$SEC_SETUP_FILE"
		echo "The profiling daemon is currently active, so changes to the configuration"
		echo "will be used the next time you restart oprofile after a --shutdown or --deinit."
	fi

	touch $SETUP_FILE
	chmod 644 $SETUP_FILE
	>$SETUP_FILE

	echo "SESSION_DIR=$SESSION_DIR" >>$SETUP_FILE

	if test "$NR_CHOSEN" != "0"; then
		for f in `seq 0 $((NR_CHOSEN - 1))`; do
			get_event $f
			echo "CHOSEN_EVENTS_${f}=$GOTEVENT" >>$SETUP_FILE
		done
	fi

	echo "NR_CHOSEN=$NR_CHOSEN" >>$SETUP_FILE

	echo "SEPARATE_LIB=$SEPARATE_LIB" >> $SETUP_FILE
	echo "SEPARATE_KERNEL=$SEPARATE_KERNEL" >> $SETUP_FILE
	echo "SEPARATE_THREAD=$SEPARATE_THREAD" >> $SETUP_FILE
	echo "SEPARATE_CPU=$SEPARATE_CPU" >> $SETUP_FILE
	echo "VMLINUX=$VMLINUX" >> $SETUP_FILE
	echo "IMAGE_FILTER=$IMAGE_FILTER" >> $SETUP_FILE
	# write the actual information to file
	if test "$BUF_SIZE" != "0"; then
		echo "BUF_SIZE=$BUF_SIZE" >> $SETUP_FILE
	fi
	if test "$BUF_WATERSHED" != "0"; then
		echo "BUF_WATERSHED=$BUF_WATERSHED" >> $SETUP_FILE
	fi
	if test "$KERNEL_SUPPORT" = "yes"; then
		echo "CPU_BUF_SIZE=$CPU_BUF_SIZE" >> $SETUP_FILE
	fi
	if test "$KERNEL_SUPPORT" != "yes"; then
		echo "NOTE_SIZE=$NOTE_SIZE" >> $SETUP_FILE
	fi
	echo "CALLGRAPH=$CALLGRAPH" >> $SETUP_FILE
	if test "$KERNEL_RANGE"; then
		echo "KERNEL_RANGE=$KERNEL_RANGE" >> $SETUP_FILE
	fi
	echo "XENIMAGE=$XENIMAGE" >> $SETUP_FILE
	if test "$XEN_RANGE"; then
		echo "XEN_RANGE=$XEN_RANGE" >> $SETUP_FILE
	fi
	SETUP_FILE="$SAVE_SETUP_FILE"
}


# reload all the setup-related information
do_load_setup()
{
# If a secondary setup file exists and the daemon is not running,
# then we'll move the data from the secondary file to the actual
# setup file to prepare for daemon startup.
	if test -z "$SESSION_DIR"; then
		__TMP_SESSION_DIR="/var/lib/oprofile"
	else
		__TMP_SESSION_DIR="$SESSION_DIR"
	fi

	if test -f "$SEC_SETUP_FILE"; then
		is_oprofiled_active "$__TMP_SESSION_DIR/lock" \
		     || mv "$SEC_SETUP_FILE" "$SETUP_FILE"
	fi

	if test ! -f "$SETUP_FILE"; then return; fi

	while IFS== read -r arg val; do
		case "$arg" in
			# The following catches anything that is not
			# 0-9, a-z, A-Z, or an '_'
			*[![:alnum:]_]*)
				echo "Invalid variable \"$arg\" in $SETUP_FILE."
				exit 1;;
		esac
		case "$val" in
			# The following catches anything that is not
			# 0-9, a-z, A-Z, an '-', ':', ',', '.', or '/'
			*[!-[:alnum:]_:,./]*) 
				echo "Invalid value \"$val\" for $arg in $SETUP_FILE."
				exit 1;;
		esac
		eval "${arg}=${val}"
	done < $SETUP_FILE
}


check_valid_args()
{
	if test -z "$VMLINUX"; then
		echo "No vmlinux file specified. You must specify the correct vmlinux file, e.g." >&2
		echo "opcontrol --vmlinux=/path/to/vmlinux" >&2
		echo "If you do not have a vmlinux file, use " >&2
		echo "opcontrol --no-vmlinux" >&2
		echo "Enter opcontrol --help for full options" >&2
		exit 1
	fi

	if test -f "$VMLINUX"; then
		return
	fi

	if test "$VMLINUX" = "none"; then
		return
	fi

	echo "The specified vmlinux file \"$VMLINUX\" doesn't exist." >&2
	exit 1

# similar check for Xen image
	if test -f "$XENIMAGE"; then
		return
	fi

	if test "$XENIMAGE" = "none"; then
		return
	fi

	echo "The specified XenImage file \"$XENIMAGE\" does not exist." >&2
	exit 1
}


# get start and end points of a file image (linux kernel or xen)
# get_image_range parameter: $1=type_of_image (linux or xen)
get_image_range()
{
	if test "$1" = "xen"; then
		if test ! -z "$XEN_RANGE"; then
			return;
		fi
		FILE_IMAGE="$XENIMAGE"
	else
		if test ! -z "$KERNEL_RANGE"; then
			return;
		fi
		FILE_IMAGE="$VMLINUX"
	fi

	if test "$FILE_IMAGE" = "none"; then
		return;
	fi

	if is_tool_available objdump; then
		echo "objdump is not installed on this system, use opcontrol --kernel-range=start,end or opcontrol --xen-range= or install objdump"
		exit 1
	fi

	# start at the start of .text, and end at _etext
	range_info=`objdump -h $FILE_IMAGE 2>/dev/null | grep " .text "`
	tmp1=`echo $range_info | awk '{print $4}'`
	tmp2=`objdump -t $FILE_IMAGE 2>/dev/null | grep "_etext$" | awk '{ print $1 }'`

	if test -z "$tmp1" -o -z "$tmp2"; then
		echo "The specified file $FILE_IMAGE does not seem to be valid" >&2
		echo "Make sure you are using the non-compressed image file (e.g. vmlinux not vmlinuz)" >&2
		vecho "found start as \"$tmp1\", end as \"$tmp2\"" >&2
		exit 1
	fi

	if test "$1" = "xen"; then
		XEN_RANGE="`echo $tmp1`,`echo $tmp2`"
		vecho "XEN_RANGE $XEN_RANGE"
	else
		KERNEL_RANGE="`echo $tmp1`,`echo $tmp2`"
		vecho "KERNEL_RANGE $KERNEL_RANGE"
	fi
}


# validate --separate= parameters. This function is called with IFS=,
# so on each argument is splitted
validate_separate_args()
{
	error_if_empty $1 $2	# we need at least one argument
	local i=1
	SEPARATE_LIB=0
	SEPARATE_KERNEL=0
	SEPARATE_THREAD=0
	SEPARATE_CPU=0
	while [ "$i" -lt "$#" ]; do
		shift
		case "$1" in
			lib|library)
				SEPARATE_LIB=1
				;;
			kernel)
				# first implied by second
				SEPARATE_LIB=1
				SEPARATE_KERNEL=1
				;;
			thread)
				SEPARATE_THREAD=1
				;;
			cpu)
				SEPARATE_CPU=1
				;;
			all)
				SEPARATE_LIB=1
				SEPARATE_KERNEL=1
				SEPARATE_THREAD=1
				SEPARATE_CPU=1
				;;
			none)
				SEPARATE_LIB=0
				SEPARATE_KERNEL=0
				SEPARATE_THREAD=0
				SEPARATE_CPU=0
				;;
			*)
				echo "invalid --separate= argument: $1"
				exit 1
		esac
	done
}


# check the counters make sense, and resolve the hardware allocation
verify_counters()
{
	if test "$IS_TIMER" = 1; then
		if test "$NR_CHOSEN" != 0; then
			echo "You cannot specify any performance counter events" >&2
			echo "because OProfile is in timer mode." >&2
			exit 1
		fi
		return
	fi

	OPHELP_ARGS=

	if test "$NR_CHOSEN" != 0; then
		for f in `seq 0 $((NR_CHOSEN - 1))`; do
			get_event $f
			if test "$GOTEVENT" != ""; then
				verify_ibs $GOTEVENT
				OPHELP_ARGS="$OPHELP_ARGS $GOTEVENT"
			fi
		done

		if test ! -z "$OPHELP_ARGS" ; then
			HW_CTRS=`$OPHELP --check-events $OPHELP_ARGS --callgraph=$CALLGRAPH`
			if test "$?" != 0; then
				exit 1
			fi
		fi
	fi
}


# setup any needed default value in chosen events
normalise_events()
{
	if test "$NR_CHOSEN" -le 0 || test "$IS_TIMER" = 1; then
		return
	fi

	for f in `seq 0 $((NR_CHOSEN - 1))`; do
		get_event $f
		if test "$GOTEVENT" != ""; then
			EVENT=`echo $GOTEVENT | awk -F: '{print $1}'`
			EVENT_VAL=`$OPHELP $EVENT`
			if test "$?" != 0; then
				exit 1
			fi
			COUNT=`echo $GOTEVENT | awk -F: '{print $2}'`
			UNIT_MASK=`echo $GOTEVENT | awk -F: '{print $3}'`
			KERNEL=`echo $GOTEVENT | awk -F: '{print $4}'`
			USER=`echo $GOTEVENT | awk -F: '{print $5}'`
			TMPEVENT="$EVENT:$COUNT:$UNIT_MASK"
			UNIT_MASK_NAMED="$UNIT_MASK"
			UNIT_MASK=`$OPHELP --unit-mask $TMPEVENT`
			if test "$?" != 0; then
				exit 1
			fi
			if test -z "$KERNEL"; then
				KERNEL=1
			fi
			if test -z "$USER"; then
				USER=1
			fi

			set_event $f "$EVENT:$COUNT:$UNIT_MASK:$KERNEL:$USER"
		fi
	done
}


# get and check specified options
do_options()
{
	EXCLUSIVE_ARGC=0
	SETUP=no
	NEED_SETUP=no
	SEEN_EVENT=0

	# note: default settings have already been loaded

	while [ "$#" -ne 0 ]
	do
		arg=`printf %s $1 | awk -F= '{print $1}'`
		val=`printf %s $1 | awk -F= '{print $2}'`
		shift
		if test -z "$val"; then
			local possibleval=$1
			printf %s $1 "$possibleval" | grep ^- >/dev/null 2>&1
			if test "$?" != "0"; then
				val=$possibleval
				if [ "$#" -ge 1 ]; then
					shift
				fi
			fi
		fi

		case "$arg" in

			--init)
				# this is already done in load_module
				# because need to know the processor type
				# and number of registers
				INIT=yes;
				EXCLUSIVE_ARGC=`expr $EXCLUSIVE_ARGC + 1`
				EXCLUSIVE_ARGV="$arg"
				;;

			--setup)
				SETUP=yes
				;;

			--start-daemon)
				if test "$KERNEL_SUPPORT" != "yes"; then
					echo "$arg unsupported. use \"--start\"" >&2
					exit 1
				fi
				START_DAEMON=yes
				EXCLUSIVE_ARGC=`expr $EXCLUSIVE_ARGC + 1`
				EXCLUSIVE_ARGV="$arg"
				;;

			-s|--start)
				START=yes
				EXCLUSIVE_ARGC=`expr $EXCLUSIVE_ARGC + 1`
				EXCLUSIVE_ARGV="$arg"
				;;

			-d|--dump)
				DUMP=yes
				ONLY_DUMP=yes
				EXCLUSIVE_ARGC=`expr $EXCLUSIVE_ARGC + 1`
				EXCLUSIVE_ARGV="$arg"
				;;

			-t|--stop)
				if test "$KERNEL_SUPPORT" != "yes"; then
					echo "$arg unsupported. use \"--shutdown\"" >&2
					exit 1
				fi
				DUMP=yes
				STOP=yes
				EXCLUSIVE_ARGC=`expr $EXCLUSIVE_ARGC + 1`
				EXCLUSIVE_ARGV="$arg"
				;;

			-h|--shutdown)
				DUMP=yes
				STOP=yes
				KILL_DAEMON=yes
				EXCLUSIVE_ARGC=`expr $EXCLUSIVE_ARGC + 1`
				EXCLUSIVE_ARGV="$arg"
				;;

			--status)
				STATUS=yes
				;;

			--reset)
				DUMP=yes
				RESET=yes
				EXCLUSIVE_ARGC=`expr $EXCLUSIVE_ARGC + 1`
				EXCLUSIVE_ARGV="$arg"
				;;

			--save)
				error_if_not_valid_savename "$arg" "$val"
				DUMP=yes
				SAVE_SESSION=yes
				SAVE_NAME=$val
				EXCLUSIVE_ARGC=`expr $EXCLUSIVE_ARGC + 1`
				EXCLUSIVE_ARGV="$arg"
				;;

			--deinit)
				DUMP=yes
				test ! -f "$LOCK_FILE" || {
					STOP=yes
					KILL_DAEMON=yes
				}
				DEINIT=yes
				EXCLUSIVE_ARGC=`expr $EXCLUSIVE_ARGC + 1`
				EXCLUSIVE_ARGV="$arg"
				;;

			# --setup options

			--session-dir)
				# already processed
				;;
			--buffer-size)
				error_if_not_number "$arg" "$val"
				BUF_SIZE=$val
				DO_SETUP=yes
				;;
			--buffer-watershed)
				if test "$KERNEL_SUPPORT" != "yes"; then
					echo "$arg unsupported for this kernel version"
					exit 1
				fi
				error_if_not_number "$arg" "$val"
				BUF_WATERSHED=$val
				DO_SETUP=yes
				;;
			--cpu-buffer-size)
				if test "$KERNEL_SUPPORT" != "yes"; then
					echo "$arg unsupported for this kernel version"
					exit 1
				fi
				error_if_not_number "$arg" "$val"
				CPU_BUF_SIZE=$val
				DO_SETUP=yes
				;;
			-e|--event)
				error_if_invalid_arg "$arg" "$val"
				# reset any read-in defaults from daemonrc
				if test "$SEEN_EVENT" = "0"; then
					NR_CHOSEN=0
					SEEN_EVENT=1
				fi
				if test "$val" = "default"; then
					val=$DEFAULT_EVENT
				fi
				set_event $NR_CHOSEN "$val"
				NR_CHOSEN=`expr $NR_CHOSEN + 1`
				DO_SETUP=yes
				;;
			-p|--separate)
				OLD_IFS=$IFS
				IFS=,
				validate_separate_args $arg $val
				IFS=$OLD_IFS
				DO_SETUP=yes
				;;
			-c|--callgraph)
				if test ! -f $MOUNT/backtrace_depth; then
					echo "Call-graph profiling unsupported on this kernel/hardware" >&2
					exit 1
				fi
				error_if_not_number "$arg" "$val"
				CALLGRAPH=$val
				DO_SETUP=yes
				;;
			--vmlinux)
				error_if_invalid_arg "$arg" "$val"
				VMLINUX=$val
				DO_SETUP=yes
				;;
			--no-vmlinux)
				VMLINUX=none
				DO_SETUP=yes
				;;
			--kernel-range)
				error_if_invalid_arg "$arg" "$val"
				KERNEL_RANGE=$val
				DO_SETUP=yes
				;;
			--xen)
				error_if_invalid_arg "$arg" "$val"
				XENIMAGE=$val
				DO_SETUP=yes
				;;
			--active-domains)
				error_if_invalid_arg $arg $val
				ACTIVE_DOMAINS=$val
				DO_SETUP=yes
				;;
			--note-table-size)
				if test "$KERNEL_SUPPORT" = "yes"; then
					echo "\"$arg\" meaningless on this kernel" >&2
					exit 1
				else
					error_if_not_number "$arg" "$val"
					NOTE_SIZE=$val
				fi
				DO_SETUP=yes
				;;
			-i|--image)
				error_if_invalid_arg "$arg" "$val"
				if test "$val" = "all"; then
					IMAGE_FILTER=
				else
					IMAGE_FILTER=$val
				fi
				DO_SETUP=yes
				;;

			-V|--verbose)
				if test -z "$val"; then
					VERBOSE="all"
				else
					error_if_invalid_arg "$arg" "$val"
					VERBOSE=$val
				fi
				;;

			-l|--list-events)
				EXCLUSIVE_ARGC=`expr $EXCLUSIVE_ARGC + 1`
				EXCLUSIVE_ARGV="$arg"
				exec $OPHELP
				;;

			*)
				echo "Unknown option \"$arg\". See opcontrol --help" >&2
				exit 1
				;;
		esac
	done

	normalise_events
	verify_counters

	# error checking to make sure options make sense
	if test "$EXCLUSIVE_ARGC" -gt 1; then
		echo "Option \"$EXCLUSIVE_ARGV\" not valid with other options." >&2
		exit 1
	fi

	if test "$SETUP" = "yes" -a "$DO_SETUP" != "yes"; then
		echo "No options specified for --setup." >&2
		exit 1
	fi

	if test -n "$VERBOSE"; then
		if test "$START" != "yes" -a "$START_DAEMON" != "yes"; then
			echo "Option --verbose may only be used with --start or --start-daemon" >&2
			exit 1
		fi
	fi

	if test "$DO_SETUP" = "yes"; then
		SETUP="$DO_SETUP"
	fi

	if test "$EXCLUSIVE_ARGC" -eq 1 -a "$SETUP" = "yes"; then
		if test "$EXCLUSIVE_ARGV" != "--start-daemon" -a "$EXCLUSIVE_ARGV" != "--start"; then
			echo "Option \"--setup\" not valid with \"$EXCLUSIVE_ARGV\"." >&2
			exit 1
		fi
	fi

	vecho "Parameters used:"
	vecho "SESSION_DIR $SESSION_DIR"
	vecho "LOCK_FILE   $LOCK_FILE"
	vecho "SAMPLES_DIR $SAMPLES_DIR"
	vecho "CURRENT_SAMPLES_DIR $CURRENT_SAMPLES_DIR"
	vecho "CPUTYPE $CPUTYPE"
	if test "$BUF_SIZE" != "0"; then
		vecho "BUF_SIZE $BUF_SIZE"
	else
		vecho "BUF_SIZE default value"
	fi
	if test "$BUF_WATERSHED" != "0"; then
		vecho "BUF_WATERSHED $BUF_WATERSHED"
	else
		vecho "BUF_WATERSHED default value"
	fi
	if test "$KERNEL_SUPPORT" = "yes"; then
		if test "$CPU_BUF_SIZE" != "0"; then
			vecho "CPU_BUF_SIZE $CPU_BUF_SIZE"
		else
			vecho "CPU_BUF_SIZE default value"
		fi
	fi

	vecho "SEPARATE_LIB $SEPARATE_LIB"
	vecho "SEPARATE_KERNEL $SEPARATE_KERNEL"
	vecho "SEPARATE_THREAD $SEPARATE_THREAD"
	vecho "SEPARATE_CPU $SEPARATE_CPU"
	vecho "CALLGRAPH $CALLGRAPH"
	vecho "VMLINUX $VMLINUX"
	vecho "KERNEL_RANGE $KERNEL_RANGE"
	vecho "XENIMAGE $XENIMAGE"
	vecho "XEN_RANGE $XEN_RANGE"
}


# stop any existing daemon
do_stop()
{
	if test ! -f "$LOCK_FILE"; then
		echo "Daemon not running" >&2
		return
	fi

	kill -0 `cat $LOCK_FILE` 2>/dev/null
	if test "$?" -ne 0; then
		echo "Detected stale lock file. Removing." >&2
		rm -f "$LOCK_FILE"
		return
	fi

	if test $KERNEL_SUPPORT = "yes" \
	    && test 0 != $(cat /dev/oprofile/enable); then
		echo "Stopping profiling."
		echo 0 >/dev/oprofile/enable
	fi
	kill -USR2 `cat $LOCK_FILE` 2>/dev/null
}


# kill the daemon process(es)
do_kill_daemon()
{
	if test ! -f "$LOCK_FILE"; then
		# no error message, do_kill_daemon imply stop and stop already
		# output "Daemon not running"
		return
	fi

	kill -0 `cat $LOCK_FILE` 2>/dev/null
	if test "$?" -ne 0; then
		echo "Detected stale lock file. Removing." >&2
		rm -f "$LOCK_FILE"
		return
	fi

	echo "Killing daemon."

	if test $KERNEL_SUPPORT = "yes"; then
		kill -TERM `cat $LOCK_FILE`
	else
		echo 1 >/proc/sys/dev/oprofile/dump_stop
	fi

	COUNT=0
	while test -n "`pidof oprofiled`"
	do
		sleep 1

		# because oprofiled only sets a variable inside the
		# signal handler itself, it's possible to miss a
		# signal just before it goes to sleep waiting for
		# data from the kernel that never arrives. So we
		# remind it it needs to die - this works because
		# the signal will bring oprofiled out of the kernel
		# back into userspace
		if test $KERNEL_SUPPORT = "yes"; then
			pid=`cat $LOCK_FILE 2>/dev/null`
			kill -TERM "$pid" 2>/dev/null
		fi

		COUNT=`expr $COUNT + 1`

		# IBS can generate a large number of samples/events.
		# Therefore, extend the delay before killing
		if test "$IBS_FETCH_COUNT" != "0" \
		     -o "$IBS_OP_COUNT" != "0" ; then
			DELAY_KILL=60
		else
			DELAY_KILL=15
		fi
		if test "$COUNT" -eq "$DELAY_KILL"; then
			echo "Daemon stuck shutting down; killing !"
			kill -9 `cat $LOCK_FILE`
		fi
	done
	sleep 1
	# already removed unless we forced the kill
	rm -f "$SESSION_DIR/lock"
}


rm_devices_24()
{
	rm_device "$DEVICE_FILE"
	rm_device "$NOTE_DEVICE_FILE"
	rm_device "$HASH_MAP_DEVICE_FILE"
}


create_devices_24()
{
	MAJOR_NR=`grep oprof /proc/devices | awk '{print $1}'`

	create_device $DEVICE_FILE $MAJOR_NR 0
	create_device $NOTE_DEVICE_FILE $MAJOR_NR 2
	create_device $HASH_MAP_DEVICE_FILE $MAJOR_NR 1
}

# create jitdump directory and remove any old files from
# a previous run
prep_jitdump() {
	local dumpdir=$SESSION_DIR/jitdump
	test -d $dumpdir || {
		mkdir -p $dumpdir;
		chmod 777 $dumpdir;
		return;
	}
	# VMs may already be running when profiling is started, so
	# remove only dump files that are not in use
	for I in $dumpdir/*; do
		test -f $I || continue;
		local pid=`basename $I .dump`;
		if test -d /proc/$pid; then
			local files=`find /proc/$pid/fd -lname $I`;
			test -n "$files" && continue;
		fi
		rm -f $I;
	done
}

# setup and start module
do_setup()
{
	create_dir "$SESSION_DIR"

	if test "$KERNEL_SUPPORT" != "yes"; then
		rm_devices_24
		create_devices_24
	fi

	create_dir "$CURRENT_SAMPLES_DIR"

	prep_jitdump;
}


# set a sysctl/oprofilefs parameter
set_param()
{
	if test "$KERNEL_SUPPORT" = "yes"; then
		echo $2 >$MOUNT/$1
	else
		$SYSCTL -w dev.oprofile.$1=$2
	fi
}


# set a sysctl/oprofilefs counter parameter
set_ctr_param()
{
	# no such thing for perfmon
	if test "$IS_PERFMON" = "yes"; then
		return
	fi

	if test "$KERNEL_SUPPORT" = "yes"; then
		if test -e $MOUNT/$1; then
			echo $3 >$MOUNT/$1/$2
		else
			echo -n "Error: counter $1 not available"
			if test -e /proc/sys/kernel/nmi_watchdog; then
				echo " nmi_watchdog using this resource ? Try:"
				echo "opcontrol --deinit"
				echo "echo 0 > /proc/sys/kernel/nmi_watchdog"
			fi
			exit 1
		fi
	else
		$SYSCTL -w dev.oprofile.$1.$2=$3
	fi
}


# returns 1 if $CPUTYPE is a PPC64 variant
is_non_cell_ppc64_variant()
{
	case "$1" in
	    ppc64/*cell*)
		return 0
		;;
	    ppc64/*)
		return 1
		;;
	    *)
		return 0
		;;
	esac
}


# The check_event_mapping_data procedure gives the
# opportunity to validate events and enforce any
# arch-specific restritions, etc.
check_event_mapping_data()
{

	is_non_cell_ppc64_variant $CPUTYPE
	if test $? -ne 0 ; then
		# For PPC64 architectures, the values required to program
		# MMCRs for the given event are returned along with the event.
		# Here we use those values to ensure that all chosen events
		# are from the same group.
		MMCR0=`echo $EVENT_STR | awk '{print $2}'`
		MMCR1=`echo $EVENT_STR | awk '{print $3}'`
		MMCRA=`echo $EVENT_STR | awk '{print $4}'`
		MMCR0_VAL=`echo $MMCR0 | awk -F: '{print $2}'`
		MMCR1_VAL=`echo $MMCR1 | awk -F: '{print $2}'`
		MMCRA_VAL=`echo $MMCRA | awk -F: '{print $2}'`

		## mmcr0, mmcr1, mmcra are for all ppc64 counters
		# Save first event mmcr settings to compare with additional
		# events.  All events must have the same mmcrx values i.e. be in
		# the same group.  Only one event is assigned per counter,
		# hence there will not be a conflict on the counters
		if [ "$MMCR0_CK_VAL" = "" ] ; then
			MMCR0_CK_VAL=$MMCR0_VAL
			MMCR1_CK_VAL=$MMCR1_VAL
			MMCRA_CK_VAL=$MMCRA_VAL
		else
			# make sure all events are from the same group
			if test $MMCR0_CK_VAL != $MMCR0_VAL \
				-o $MMCR1_CK_VAL != $MMCR1_VAL \
				-o $MMCRA_CK_VAL != $MMCRA_VAL ; then
				echo "ERROR: The specified events are not from the same group."
				echo "       Use 'opcontrol --list-events' to see event groupings."
				exit 1
			fi
		fi

		# Check if all user/kernel flags per-counter are matching.
		if [ "$USER_CK" = "" ] ; then
			USER_CK=$USER
			KERNEL_CK=$KERNEL
		else
			if test $USER_CK != $USER \
				-o $KERNEL_CK != $KERNEL ; then
				echo "ERROR: All kernel/user event flags must match."
				exit 1
			fi
		fi
	fi
	if [ "$CPUTYPE" = "ppc64/cell-be" ]; then
		event_num=`echo $EVENT_STR | awk '{print $1}'`
		# PPU event and cycle events can be measured at
		# the same time.  SPU event can not be measured
		# at the same time as any other event.  Similarly for
		# SPU Cycles

		# We use EVNT_MSK to track what events have already
		# been seen.  Valid values are:
		#    NULL string -  no events seen yet
		#    1 - PPU CYCLES or PPU Event seen
		#    2 - SPU CYCLES seen
		#    3 - SPU EVENT seen

		# check if event is PPU_CYCLES
		if [ "$event_num" = "1" ]; then
			if [ "$EVNT_MSK" = "1" ] || [ "$EVNT_MSK" = "" ]; then
				EVNT_MSK=1
			else
				echo "PPU CYCLES not compatible with previously specified event"
				exit 1
		fi

		# check if event is SPU_CYCLES
		elif [ "$event_num" = "2" ]; then
			if [ "$EVNT_MSK" = "" ]; then
				EVNT_MSK=2
			else
				echo "SPU CYCLES not compatible with any other event"
				exit 1
			fi

		# check if event is SPU Event profiling
		elif [ "$event_num" -ge "4100" ] && [ "$event_num" -le "4163" ] ; then
			if [ "$EVNT_MSK" = "" ]; then
				EVNT_MSK=3
			else
				echo "SPU event profiling not compatible with any other event"
				exit 1
			fi

			# Check to see that the kernel supports SPU event
			# profiling.  Note, if the file exits it should have
			# the LSB bit set to 1 indicating SPU event profiling
			# support. For now, it is sufficient to test that the
			# file exists.
			if test ! -f /dev/oprofile/cell_support; then
				echo "Kernel does not support SPU event profiling"
				exit 1
			fi

			# check if event is PPU Event profiling (all other
			# events are PPU events)
		else
			if [ "$EVNT_MSK" = "1" ] || [ "$EVNT_MSK" = "" ]; then
				EVNT_MSK=1
			else
				echo "PPU profiling not compatible with previously specified event"
				exit 1
			fi
		fi
	fi
	len=`echo -n $event_num | wc -c`
	num_chars_in_grpid=`expr $len - 2`
	GRP_NUM_VAL=`echo | awk '{print substr("'"${event_num}"'",1,"'"${num_chars_in_grpid}"'")}'`
	if [ "$GRP_NUM_CK_VAL" = "" ] ; then
		GRP_NUM_CK_VAL=$GRP_NUM_VAL
	else
		if test $GRP_NUM_CK_VAL != $GRP_NUM_VAL ; then
			echo "ERROR: The specified events are not from the same group." >&2
			echo "       Use 'opcontrol --list-events' to see event groupings." >&2
			exit 1
		fi
	fi
}


do_param_setup()
{
	# different names
	if test $BUF_SIZE != 0; then
		if test "$KERNEL_SUPPORT" = "yes"; then
			echo $BUF_SIZE >$MOUNT/buffer_size
		else
			$SYSCTL -w dev.oprofile.bufsize=$BUF_SIZE
		fi
	fi

	if test $BUF_WATERSHED != 0; then
		if test "$KERNEL_SUPPORT" = "yes"; then
			echo $BUF_WATERSHED >$MOUNT/buffer_watershed
		else
			echo "buffer-watershed not supported - ignored" >&2
		fi
	fi

	if test $CPU_BUF_SIZE != 0; then
		if test "$KERNEL_SUPPORT" = "yes"; then
			echo $CPU_BUF_SIZE >$MOUNT/cpu_buffer_size
		else
			echo "cpu-buffer-size not supported - ignored" >&2
		fi
	fi

	if test -n "$ACTIVE_DOMAINS"; then
		if test "$KERNEL_SUPPORT" = "yes"; then
			echo $ACTIVE_DOMAINS >$MOUNT/active_domains
		else
			echo "active-domains not supported - ignored" >&2
		fi
	fi

	if test $NOTE_SIZE != 0; then
		set_param notesize $NOTE_SIZE
	fi

	if test "$KERNEL_SUPPORT" = "yes" -a -f $MOUNT/backtrace_depth; then
		set_param backtrace_depth $CALLGRAPH
	elif test "$CALLGRAPH" != "0"; then
		echo "Call-graph profiling not supported - ignored" >&2
	fi

	if test "$IS_TIMER" = 1; then
		return
	fi

	# use the default setup if none set
	if test "$NR_CHOSEN" = 0; then
		set_event 0 $DEFAULT_EVENT
		NR_CHOSEN=1
		HW_CTRS=`$OPHELP --check-events $DEFAULT_EVENT --callgraph=$CALLGRAPH`
		echo "Using default event: $DEFAULT_EVENT"
	fi

	# Necessary in this case :
	# opcontrol ctr0-on ctr1-on then opcontrol ctr0-on
	for f in $OP_COUNTERS ; do
		set_ctr_param $f enabled 0
		set_ctr_param $f event 0
		set_ctr_param $f count 0

		if test -f $MOUNT/$f/extra ; then
		    set_ctr_param $f extra 0
		fi
	done

	# Check if driver has IBS support
	if test -d $MOUNT/ibs_fetch; then
		# Reset driver's IBS fetch setting
		set_param ibs_fetch/enable 0
	fi
	
	if test -d $MOUNT/ibs_op ; then
		# Reset driver's IBS op setting
		set_param ibs_op/enable 0
	fi	

	verify_counters

	OPROFILED_EVENTS=
	for f in `seq 0 $((NR_CHOSEN - 1))`; do
		get_event $f
		if test "$GOTEVENT" != ""; then
			EVENT=`echo $GOTEVENT | awk -F: '{print $1}'`
			EVENT_STR=`$OPHELP $EVENT`
			EVENT_VAL=`echo $EVENT_STR | awk '{print $1}'`
			COUNT=`echo $GOTEVENT | awk -F: '{print $2}'`
			UNIT_MASK=`echo $GOTEVENT | awk -F: '{print $3}'`
			KERNEL=`echo $GOTEVENT | awk -F: '{print $4}'`
			USER=`echo $GOTEVENT | awk -F: '{print $5}'`
			CTR=`echo $HW_CTRS | awk "{print \\$$((f + 1))}"`
			check_event_mapping_data

			if test "$EVENT" = "SPU_CYCLES"; then
				if test "$SEPARATE_KERNEL" = "1"; then
					SEPARATE_KERNEL=0
					echo "Ignoring --separate=kernel option with SPU_CYCLES"
				fi
				if test "$SEPARATE_LIB" = "0"; then
					SEPARATE_LIB=1
					echo "Forcing required option --separate=lib with SPU_CYCLES"
				fi

				# It is possible for a single application to be
				# running on all SPUs simultaneously.  Without
				# SEPARATE_CPU, the resulting sample data would
				# consist of a single sample file.  If all SPUs
				# were truly running the same code, the merging
				# of sample data would be fine.  However, an
				# application file may have multiple SPU images
				# embedded within it, resulting in different
				# code running on different SPUs.  Therefore,
				# we force SEPARATE_CPU in order to properly
				# handle this case.
				if test "$SEPARATE_CPU" = "0"; then
					SEPARATE_CPU=1
					echo "Forcing required option --separate=cpu with SPU_CYCLES"

				fi
			fi

			if [ "$CTR" = "ibs_fetch" -o "$CTR" = "ibs_op" ] ; then
				# Handle IBS events setup
				do_param_setup_ibs
				continue
			fi

			if test "$EVENT" = "RTC_INTERRUPTS"; then
				set_param rtc_value $COUNT
				$SYSCTL -w dev.oprofile.rtc_value=$COUNT
			else
				set_ctr_param $CTR enabled 1
				set_ctr_param $CTR event $EVENT_VAL
				loop_count=1
				for i in ${EVENT_STR}; do
					#Skip first argument of EVENT_STR (event val) since we've already
					#processed that value.
					if test "$loop_count" -gt 1; then
						KEY=`echo $i | awk -F: '{print $1}'`
						VAL=`echo $i | awk -F: '{print $2}'`
						set_ctr_param "" $KEY $VAL
					fi
					loop_count=$((loop_count+1))
				done
				set_ctr_param $CTR count $COUNT
				set_ctr_param $CTR kernel $KERNEL
				set_ctr_param $CTR user $USER
				set_ctr_param $CTR unit_mask $UNIT_MASK

				EXTRA=`$OPHELP --extra-mask $EVENT:$COUNT:$UNIT_MASK_NAMED`
				if test "$EXTRA" -ne 0 ; then
					if ! test -f $MOUNT/$CTR/extra ; then
						echo >&2 "Warning: $GOTEVENT has extra mask, but kernel does not support extra field"
						echo >&2 "Please update your kernel or use a different event. Will miscount."
					else
						set_ctr_param $CTR extra $EXTRA
					fi
				fi
			fi
			OPROFILED_EVENTS=${OPROFILED_EVENTS}$EVENT:$EVENT_VAL:
			OPROFILED_EVENTS=${OPROFILED_EVENTS}$CTR:$COUNT:$UNIT_MASK:
			OPROFILED_EVENTS=${OPROFILED_EVENTS}$KERNEL:$USER,
		fi
	done

	# For PPC64 architectures we need to set the enable_kernel and
	# enable_user flags for enabling/disabling user/kernel domain
	# profiling. All per-counter user/kernel flags must match.
	# This condition is checked previously by check_event_mapping_data.
	# This statement uses the last event's user/kernel flags to set
	# /dev/oprofile/enable_kernel and /dev/oprofile/enable_user.
	is_non_cell_ppc64_variant $CPUTYPE
	if test $? -ne 0 ; then
		set_param "enable_kernel" $KERNEL
		set_param "enable_user" $USER
	fi

}


do_start_daemon()
{

	if test -f "$LOCK_FILE"; then
		kill -0 `cat $LOCK_FILE` 2>/dev/null
		if test "$?" -eq 0; then
			return;
		else
			echo "Detected stale lock file. Removing." >&2
			rm -f "$LOCK_FILE"
		fi
	fi

	do_setup
	check_valid_args
	get_image_range "linux"
	get_image_range "xen"
	do_param_setup

	OPD_ARGS=" \
		--session-dir=$SESSION_DIR \
		--separate-lib=$SEPARATE_LIB \
		--separate-kernel=$SEPARATE_KERNEL \
		--separate-thread=$SEPARATE_THREAD \
		--separate-cpu=$SEPARATE_CPU"

	if test "$IS_TIMER" = 1; then
		OPD_ARGS="$OPD_ARGS --events="
	else
		if ! test -z "$OPROFILED_EVENTS"; then
			OPD_ARGS="$OPD_ARGS --events=$OPROFILED_EVENTS"
		fi
	fi

	if test "$VMLINUX" = "none"; then
		OPD_ARGS="$OPD_ARGS --no-vmlinux"
	else
		OPD_ARGS="$OPD_ARGS --vmlinux=$VMLINUX --kernel-range=$KERNEL_RANGE"
	fi

	if ! test "$XENIMAGE" = "none"; then
		OPD_ARGS="$OPD_ARGS --xen-image=$XENIMAGE --xen-range=$XEN_RANGE"
	fi

	if ! test -z "$IMAGE_FILTER"; then
		OPD_ARGS="$OPD_ARGS --image=$IMAGE_FILTER"
	fi

	if test -n "$VERBOSE"; then
		OPD_ARGS="$OPD_ARGS --verbose=$VERBOSE"
	fi

	help_start_daemon_with_ibs

	vecho "executing oprofiled $OPD_ARGS"

	$OPROFILED $OPD_ARGS

	COUNT=0
	while ! test -f "$SESSION_DIR/lock"
	do
		sleep 1
		COUNT=`expr $COUNT + 1`
		if test "$COUNT" -eq 10; then
			echo "Couldn't start oprofiled." >&2
			echo "Check the log file \"$LOG_FILE\" and kernel syslog" >&2
			exit 1
		fi
	done

	echo "Daemon started."
}

do_start()
{
	prep_jitdump;
	if test "$KERNEL_SUPPORT" = "yes"; then
		echo 1 >$MOUNT/enable
	fi
	kill -USR1 `cat $LOCK_FILE` 2>/dev/null
	echo "Profiler running."
}


# print status
do_status()
{
	OPROFILED_PID=`cat $SESSION_DIR/lock 2>/dev/null`
	if test -n "$OPROFILED_PID" -a -d "/proc/$OPROFILED_PID"; then
		if test "$KERNEL_SUPPORT" = yes \
		    && test 0 != $(cat /dev/oprofile/enable); then
			echo "Daemon running: pid $OPROFILED_PID"
		else
			echo "Daemon paused: pid $OPROFILED_PID"
		fi
	else
		echo "Daemon not running"
	fi

	if test "$NR_CHOSEN" != "0"; then
		for f in `seq 0 $((NR_CHOSEN - 1))`; do
			get_event $f
			echo "Event $f: $GOTEVENT"
		done
	fi

	SEPARATE=""
	if test "$SEPARATE_LIB" = "1"; then
		SEPARATE="library";
	fi
	if test "$SEPARATE_KERNEL" = "1"; then
		SEPARATE="$SEPARATE kernel";
	fi
	if test "$SEPARATE_THREAD" = "1"; then
		SEPARATE="$SEPARATE thread";
	fi
	if test "$SEPARATE_CPU" = "1"; then
		SEPARATE="$SEPARATE cpu";
	fi

	if test -z "$SEPARATE"; then
		SEPARATE=none
	fi

	echo "Separate options: $SEPARATE"
	echo "vmlinux file: $VMLINUX"

	if test -z "$IMAGE_FILTER"; then
		echo "Image filter: none"
	else
		echo "Image filter: $IMAGE_FILTER"
	fi

	echo "Call-graph depth: $CALLGRAPH"
	if test "$BUF_SIZE" != "0"; then
		echo "Buffer size: $BUF_SIZE"
	fi
	if test "$KERNEL_SUPPORT" != "yes"; then
		if test "$NOTE_SIZE" != "0"; then
			echo "Note buffer size: $NOTE_SIZE"
		fi
	else
		if test "$BUF_WATERSHED" != "0"; then
			echo "CPU buffer watershed: $BUF_WATERSHED"
		fi
		if test "$CPU_BUF_SIZE" != "0"; then
			echo "CPU buffer size: $CPU_BUF_SIZE"
		fi
	fi

	exit 0
}


# do_dump_data
# returns 0 if successful
# returns 1 if the daemon is unable to dump data
# exit 1 if we need to be root to dump
do_dump_data()
{
	# make sure that the daemon is not dead and gone
	if test -e "$SESSION_DIR/lock"; then
		OPROFILED_PID=`cat $SESSION_DIR/lock`
		if test ! -d "/proc/$OPROFILED_PID"; then
			echo "dump fail: daemon died during last run ?" >&2
			return 1;
		fi
	else
		return 1;
	fi

	if test "$KERNEL_SUPPORT" = "yes"; then
		if ! test -w $MOUNT/dump; then
			if test `id -u` != "0"; then
				echo "You must be root to dump with this kernel version"
				exit 1
			fi
		fi
		# trigger oprofiled to execute opjitconv
		if test -p "$SESSION_DIR/opd_pipe"; then
			echo do_jitconv >> $SESSION_DIR/opd_pipe
		fi
		rm -f "$SESSION_DIR/complete_dump"
		echo 1 > $MOUNT/dump
		# loop until the complete_dump file is created to
		# signal that the dump has been completed
		while [ \( ! -e "$SESSION_DIR/complete_dump" \) ]
		do
			if test ! -d "/proc/$OPROFILED_PID"; then
				echo "dump fail: either daemon died during last run or dies during dump" >&2
				return 1
			fi
			sleep 1;
		done
	else
		echo 1 > $MOUNT/dump
		# HACK !
		sleep 2
	fi
	cp -r /dev/oprofile/stats "$SAMPLES_DIR/current"

	return 0;
}


# do_dump
# returns 0 if successful
# exits if unsuccessful
do_dump()
{
	do_dump_data
	if test $? -ne 0 -a "$ONLY_DUMP" = "yes"; then
		echo "Unable to complete dump of oprofile data: is the oprofile daemon running?" >& 2
		exit 1;
	fi
	return 0;
}

# tell daemon to re-open the sample files
hup_daemon()
{
	if test -f "$LOCK_FILE"; then
		echo -n "Signalling daemon... "
		kill -HUP `cat $LOCK_FILE`
		echo "done"
	fi
}


# move all the sample files to a sample directory
do_save_session()
{
	SAVE_DIR="${SAMPLES_DIR}/${SAVE_NAME}"

	if test -e "$SAVE_DIR"; then
		echo "session $SAVE_DIR already exists" >&2
		exit 1
	fi

	if ! test -e $CURRENT_SAMPLES_DIR; then
		echo "$CURRENT_SAMPLES_DIR doesn't exist: nothing to save" >&2
		exit 0
	fi

	# FIXME: I don't think it's worth checking for empty current directory

	mv $CURRENT_SAMPLES_DIR $SAVE_DIR
	if test "$?" != "0"; then
		echo "Couldn't move $CURRENT_SAMPLES_DIR to $SAVE_DIR" >&2
		exit 1
	fi

	hup_daemon
}


# remove all the sample files
do_reset()
{
	if test -z "$SAMPLES_DIR"; then
		echo "opcontrol:do_reset() SAMPLES_DIR is empty!"
		exit 1;
	fi

	# daemon use {kern} and {root} subdir, it's not a typo to not use ${}
	move_and_remove $SAMPLES_DIR/current/{kern}
	move_and_remove $SAMPLES_DIR/current/{root}
	move_and_remove $SAMPLES_DIR/current/stats

	# clear temp directory for jitted code
	prep_jitdump;

	hup_daemon
}


do_deinit()
{
	# unmount /dev/oprofile if it is mounted
	OPROF_FS=`grep /dev/oprofile /etc/mtab`
	if test -n "$OPROF_FS"; then
		umount /dev/oprofile
	fi
	# unload the oprofile module if it is around
	OPROF_MOD=`lsmod | grep oprofile`
	if test -n "$OPROF_MOD"; then
		echo "Unloading oprofile module" >& 2
		rmmod oprofile
	fi
}


# The function that calls the appropriate operations
do_operations()
{
	# INIT always done by load_module to get access to cputype
	# thus INIT is a noop

	if test "$STATUS" = "yes"; then
		do_status
	fi

	if test "$SETUP" = "yes"; then
		check_valid_args
		do_save_setup
	fi

	if test "$START_DAEMON" = "yes"; then
		do_start_daemon
	fi

	if test "$START" = "yes"; then
		do_start_daemon
		do_start
	fi

	if test "$DUMP" = "yes"; then
		do_dump
	fi

	if test "$SAVE_SESSION" = "yes"; then
		do_save_session
	fi

	if test "$STOP" = "yes"; then
		do_stop
	fi

	if test "$KILL_DAEMON" = "yes"; then
		do_kill_daemon
	fi

	if test "$RESET" = "yes"; then
		do_reset
	fi

	if test "$DEINIT" = "yes"; then
		do_deinit
	fi
}

# early check for --version, --help and --session-dir
check_options_early()
{

	OPHELP="$OPDIR/ophelp"

	for i in $@; do
		# added to handle arg=val parameters
		arg=`printf %s $i | awk -F= '{print $1}'`
		val=`printf %s $i | awk -F= '{print $2}'`
		case "$arg" in
			-\?|--help)
				do_help
				exit 0
				;;

			-v|--version)
				echo -n "`basename $0`: "
				$OPHELP --version | cut -d' ' -f2-
				exit 0
				;;
			--session-dir)
				error_if_invalid_arg "$arg" "$val"
				SESSION_DIR="$val"
				DO_SETUP=yes
				# do not exit early
				;;

		esac
	done
}


# determine which module is loaded
check_version()
{
	OPROFILE_AVAILABLE=no
	grep oprofilefs /etc/mtab >/dev/null
	if test "$?" -eq 0; then
		# need to have oprofilefs mounted for this to work on 2.6
		KERNEL_SUPPORT=yes
		OPROFILE_AVAILABLE=yes
		return
	fi
	# need to have /proc/oprof available for this to work on 2.4
	grep oprof /proc/devices >/dev/null
	if test "$?" -eq 0; then
		KERNEL_SUPPORT=no
		OPROFILE_AVAILABLE=yes
		return
	fi
}

# error out if the module is not loaded
check_oprofile_available()
{
	if test "$OPROFILE_AVAILABLE" != "yes"; then
		echo "Kernel support not available, missing opcontrol --init as root ?"
		exit 1
	fi
}


try_reset_sample_file()
{
	# special case to avoid loading the module, it works only if the
	# daemon is not running because --reset imply --dump. Rather to check
	# if the daemon is running we check if the module is loaded because
	# we are only trying to avoid its load, if the check fails we fallback
	# to the normal dump / reset sequence.
	if test -z "$2" -a "$1" = "--reset"; then
		check_version
		if test "$OPROFILE_AVAILABLE" != "yes"; then
			do_init_daemon_vars
			do_reset
			exit 0
		fi
	fi
}

#
# Begin IBS Specific Functions
#
verify_ibs()
{
	IBS_EVENT=`echo $1| awk -F: '{print $1}'`
	IBS_COUNT=`echo $1 | awk -F: '{print $2}'`
	IBS_MASK=`echo $1 | awk -F: '{print $3}'`
	
	IBS_TYPE=`$OPHELP --check-events $1`
	if test "$?" != "0" ; then
		exit 1
	fi
			
	if [ "$IBS_TYPE" = "ibs_fetch " ] ; then
		# Check IBS_COUNT consistency
		if test "$IBS_FETCH_COUNT" = "0" ; then 
			IBS_FETCH_COUNT=$IBS_COUNT
			IBS_FETCH_MASK=$IBS_MASK
		elif test "$IBS_FETCH_COUNT" != "$IBS_COUNT" ; then
			echo "ERROR: All IBS Fetch must have the same count."
			exit 1
		fi

		# Check IBS_MASK consistency
		if test "$IBS_FETCH_MASK" != "$IBS_MASK" ; then
			echo "ERROR: All IBS Fetch must have the same unitmask."
			exit 1
		fi

		# Check IBS_FETCH_COUNT within range
		if test "$IBS_FETCH_COUNT" -gt 1048575 ; then 
			echo "ERROR: IBS Fetch count is too large."
			echo "       The maximum IBS-fetch count is 1048575."
			exit 1
		fi

	elif [ "$IBS_TYPE" = "ibs_op " ] ; then
		# Check IBS_COUNT consistency
		if test "$IBS_OP_COUNT" = "0" ; then 
			IBS_OP_COUNT=$IBS_COUNT
			IBS_OP_MASK=$IBS_MASK
		elif test "$IBS_OP_COUNT" != "$IBS_COUNT" ; then
			echo "All IBS Op must have the same count."
			exit 1
		fi

		# Check IBS_MASK consistency
		if test "$IBS_OP_MASK" != "$IBS_MASK" ; then
			echo "All IBS Op must have the same unitmask."
			exit 1
		fi
		
		# Check IBS_OP_COUNT within range
		case "$CPUTYPE" in
			x86-64/family10)
				if test "$IBS_OP_COUNT" -gt 1048575 ; then 
					echo "ERROR: IBS Op count is too large."
					echo "       The maximum IBS-fetch count is 1048575."
					exit 1
				fi
				;;

			x86-64/family12h|\
			x86-64/family14h|\
			x86-64/family15h)
				if test "$IBS_OP_COUNT" -gt 134217727 ; then 
					echo "ERROR: IBS Op count is too large."
					echo "       The maximum IBS-Op count is 134217727."
					exit 1
				fi
				;;
			*)
		esac
	fi

	return
}


do_param_setup_ibs()
{
	if test "$KERNEL_SUPPORT" != "yes" ; then
		echo "ERROR: No kernel support for IBS profiling."
		exit 1	
	fi

	# Check if driver has IBS support
	if test ! -d $MOUNT/ibs_fetch -o ! -d $MOUNT/ibs_op ; then
		echo "ERROR: No kernel support for IBS profiling."
		exit 1	
	fi	

	if test `echo $EVENT |  \
	awk '{ print substr($0, 1, 10)}'` = "IBS_FETCH_" ; then
		if test "$COUNT" != "0"; then
			if [ "$IBS_FETCH_EVENTS" = "" ] ; then
				IBS_FETCH_EVENTS="$EVENT"
			else
				IBS_FETCH_EVENTS="$IBS_FETCH_EVENTS,$EVENT"
			fi
			IBS_FETCH_COUNT=$COUNT
			set_param ibs_fetch/max_count $COUNT
			set_param ibs_fetch/rand_enable 1
			set_param ibs_fetch/enable 1
		else
			set_param ibs_fetch/enable 0
		fi

	elif test `echo $EVENT |  \
	awk '{ print substr($0, 1, 7)}'` = "IBS_OP_" ; then
		if test "$COUNT" != "0"; then
			if [ "$IBS_OP_EVENTS" = "" ] ; then
				IBS_OP_EVENTS="$EVENT"
			else
				IBS_OP_EVENTS="$IBS_OP_EVENTS,$EVENT"
			fi
			IBS_OP_COUNT=$COUNT
			IBS_OP_UNITMASK=$UNIT_MASK

			set_param ibs_op/max_count $COUNT
			set_param ibs_op/enable 1

			# NOTE: We default to use dispatched_op if available. 
			#       Some of the older family10 system does not have
			#       dispatched_ops feature.
			#       Dispatched op is enabled by bit 0 of the unitmask
			IBS_OP_DISPATCHED_OP=$(( IBS_OP_UNITMASK & 0x1 ))
			if test -f $MOUNT/ibs_op/dispatched_ops ; then
				set_param ibs_op/dispatched_ops $IBS_OP_DISPATCHED_OP
			else
				if test $IBS_OP_DISPATCHED_OP -eq 1 ; then
					echo "ERROR: IBS Op dispatched ops is not supported."
					exit 1
				fi
			fi
		
			# NOTE: BTA is enabled by bit 2 of the unitmask
			IBS_OP_BTA=$(( IBS_OP_UNITMASK & 0x4 ))
			if test -f $MOUNT/ibs_op/branch_target; then
				if [ "$IBS_OP_BTA" = "4" ] ; then
					set_param ibs_op/branch_target 1
				else
					set_param ibs_op/branch_target 0
				fi

				# TODO: Check if write successful
			else
				if test $IBS_OP_BTA -eq 1 ; then
					echo "ERROR: IBS Op Branch Target Address is not supported."
					exit 1
				fi
			fi
		else
			set_param ibs_op/enable 0
		fi
	fi
}


help_start_daemon_with_ibs()
{
	if test "$IBS_FETCH_COUNT" != "0" -o "$IBS_OP_COUNT" != "0" ; then
		OPD_ARGS="${OPD_ARGS} --ext-feature=ibs:"
		if test "$IBS_FETCH_COUNT" != "0"; then
			OPD_ARGS="${OPD_ARGS}fetch:$IBS_FETCH_EVENTS:$IBS_FETCH_COUNT:$IBS_FETCH_UNITMASK|"
		fi

		if test "$IBS_OP_COUNT" != "0"; then
			OPD_ARGS="${OPD_ARGS}op:$IBS_OP_EVENTS:$IBS_OP_COUNT:$IBS_OP_UNITMASK"
		fi
	fi
}

#
# End IBS Specific Functions
#

# main

# determine the location of opcontrol and related programs
if test -z "$OPDIR"; then
	BINDIR="/usr/bin"
	OPCONTROL=`$BINDIR/which $0`
	OPDIR=`$BINDIR/dirname $OPCONTROL`
fi

PATH=$OPDIR:/usr/local/bin:/usr/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin

check_options_early $@

if test -z "$1"; then
	do_help
	exit 0
fi

if test `id -u` = "0"; then
	try_reset_sample_file $@

	load_module
fi
check_version

# Except --reset, even the few operations allowed as non root needs the
# kernel support, if we don't error out now the error message will be obscure
check_oprofile_available

do_init
if test `id -u` != "0"; then
    if test -z "$2"; then
	case "$1" in
	    --dump|-d)
		ONLY_DUMP=yes
		do_dump
		exit 0;
		;;
	    --list-events|-l)
		exec $OPHELP
		exit 0;
		;;
	    *)
		echo "Normal users are limited to either '--dump' or '--list-events'." >&2
		exit 1
		;;
	esac
    else
	echo "Normal users are limited to either '--dump' or '--list-events'." >&2
	exit 1
    fi
fi

do_options $@
do_operations
