#!/usr/bin/env bash

# This scripts present a friendly interface to the fiu remote control
# capabilities. Currently, it supports only the named pipe method (the only
# one implemented).

# default remote control over named pipes prefix; we use the same one as
# fiu-run so it's easier to use
FIFO_PREFIX="${TMPDIR:-/tmp}/fiu-ctrl"

# commands to send, will be filled by options processing; must be in the
# format supported by the fiu remote control (see fiu-rc.c for more details)
declare -a CMDS


HELP_MSG="
Usage: fiu-ctrl [options] PID [PID ...]

The following options are supported:

  -e fpname	Enable the given failure point name.
  -p prob	... with the given probability (defaults to 100%).
  -u failnum	... and this failnum (must be != 0) (defaults to 1).
  -i failinfo	... and this failinfo (defaults to 0).
  -d fpname	Disable the given failure point name.
  -f ctrlpath	Set the default prefix for remote control over named pipes.
		(defaults to \"$FIFO_PREFIX\", which is usually correct if
		the program was run using fiu-run(1)).

The -p, -u and -i options must come after the -e they affect.

For example:

  fiu-ctrl -e posix/io/read -p 25 -e libc/mm/malloc -p 5 12345

will tell the process with pid 12345 to enable the failure point
'posix/io/read' with a 25% of probability to fail, and the failure point
'libc/mm/malloc' with a 5% of probability to fail. And:

  fiu-ctrl -d posix/io/read 12345

will tell the same process to disable the previously enabled failure point.

You can control multiple processes at once by specifiying more than one
process ID.
"


#
# Parse the options
#

if [ $# -lt 1 ]; then
	echo "$HELP_MSG"
	exit 1
fi

function opts_reset() {
	# variables to store what we know so far; after a new name is found
	# the old one is added to $ENABLE
	NAME=""
	PROB=-1
	FAILNUM=1
	FAILINFO=0
}

function add_cmd() {
	if [ "$NAME" != "" ]; then
		if [ $PROB -ge 0 ]; then
			C="enable_random $NAME $PROB $FAILNUM $FAILINFO"
			CMDS[${#CMDS[*]}]="$C"
		else
			CMDS[${#CMDS[*]}]="enable $NAME $FAILNUM $FAILINFO"
		fi
		opts_reset;
	fi
}

opts_reset;
while getopts "+e:p:u:i:d:f:h" opt; do
	case $opt in
	e)
		# add the current one, if any
		add_cmd;
		opts_reset;
		NAME="$OPTARG"
		;;
	p)
		PROB="$OPTARG"
		;;
	u)
		FAILNUM="$OPTARG"
		;;
	i)
		FAILINFO="$OPTARG"
		;;
	f)
		FIFO_PREFIX="$OPTARG"
		;;
	d)
		CMDS[${#CMDS[*]}]="disable $OPTARG"
		opts_reset;
		;;
	h|*)
		echo "$HELP_MSG"
		exit 1
		;;
	esac;
done

# add leftovers
if [ "$NAME" != "" ]; then
	add_cmd;
fi

# eat the parameters we already processed
shift $(( $OPTIND - 1 ))

PIDS=""
PREFIXES=""

for i in "$@"; do
	if test -p "$i.out"; then
		PREFIXES="$PREFIXES $i"
	elif kill -0 $i > /dev/null 2> /dev/null && \
			test -p	"$FIFO_PREFIX-$i.out"; then
		PIDS="$PIDS $i"
	else
		echo "Error: unknown pid or named pipe $i, skipping"
		echo "Note that options must come before the PID"
	fi
done

#
# Send the commands
#

function send_cmd_fifo() {
	# $1 = complete fifo prefix
	# $2+ = command to send
	# echoes the reply
	P=$1
	shift 1
	echo "$@" > $P.in
	R="`cat $P.out`"
	if [ "$R" -eq -1 ]; then
		echo "$P: Command returned error"
	fi
}

for c in "${CMDS[@]}"; do
	for i in $PIDS; do
		send_cmd_fifo $FIFO_PREFIX-$i "$c"
	done
	for i in $PREFIXES; do
		send_cmd_fifo $i "$c"
	done
done

