#!/bin/sh
############################################################ IDENT(1)
#
# $Title: Script to generate DTrace data as JSON output for graphing $
# $Copyright: 2022 Devin Teske. All rights reserved. $
# $FrauBSD: dwatch-json/grafnet/grafnet 2022-08-22 15:48:30 -0700 freebsdfrau $
#
############################################################ PROGRAM

VERSION='$Version: 1.4 $'

_pgm=$( readlink -f "$0" ) # Real program
_pgm="${_pgm##*/}" # Real basename
	# NB: pgm reserved by includes (use _pgm)

############################################################ ENVIRONMENT

#
# Directories
#
# NB: Since these are inherited from environment, they should be prefixed with
#     the real service name, allowing multiple suites to have discrete setting
#
: "${GRAFNET_ETC_DIR:=/usr/local/etc/$_pgm}"
: "${GRAFNET_LOG_DIR:=/var/log/$_pgm}"
: "${GRAFNET_RUN_DIR:=/var/run/$_pgm}"

#
# Abstract the code away from knowing the name of the suite
#
# NB: Copying the suite controller (this script) to a script by another name,
#     the above and below environment variables should the only things you have
#     to alter for the newly named suite.
#
SUITE_ETC_DIR="$GRAFNET_ETC_DIR"
SUITE_LOG_DIR="$GRAFNET_LOG_DIR"
SUITE_RUN_DIR="$GRAFNET_RUN_DIR"

#
# Miscellaneous environment
#
: "${EDITOR:=vi}"
: "${UNAME_s:=$( uname -s )}"

#
# Adjust PATH when invoked via service(8)
# NB: So we can find $_pgm-logs in logscmd()
# NB: So we can find $_pgm-rotate in rotatecmd()
#
progdir=$( readlink -f "$0" ) || exit
progdir="${progdir%/*}"
case "$PATH" in
"$progdir"|"$progdir":*|*:"$progdir"|*:"$progdir":*) : OK ;;
*) PATH="$PATH${PATH:+:}$progdir"
esac

############################################################ CONFIGURATION

SUITE_SERVICE_DWATCH_MODULE=json-net-config
SUITE_TRACE_DWATCH_MODULE=json-net-raw

DEFAULT_CONFIG="$SUITE_ETC_DIR/$_pgm.conf"
ERRFILE="$SUITE_RUN_DIR/$_pgm.stderr"
LOGFILE="$SUITE_LOG_DIR/$_pgm.log"
PIDFILE="$SUITE_RUN_DIR/$_pgm.pid"
REPORT_TYPE="$_pgm"

SUITE_CONFIG_SYS_VAR="${_pgm}_config"
SUITE_CONFIG_SYS=$( sysrc -inqs "$_pgm" "$SUITE_CONFIG_SYS_VAR" )
SUITE_CONFIG_SYS_FILE=$( sysrc -Finqs "$_pgm" "$SUITE_CONFIG_SYS_VAR" )

#
# Variables we ignore in the suite configuration file
#
# NB: These are variables that influence either the suite or the dwatch(1)
#     modules that the suite instruments and should NOT be considered when
#     creating DTrace code for generating statistics.
#
SUITE_CONFIG_IGNORE="
	EVENT_TEST
	HOSTNAME
	IGNORE
	PROBE
	REPORT_TYPE
	UNMATCHED_LABEL
" # END-QUOTE

#
# Ancillary directories
# NB: Directory determined here is specific to telegraf, independent of suite
#
case "$UNAME_s" in
FreeBSD) STATS_CONFDIR=/usr/local/etc ;;
      *) STATS_CONFDIR=/etc
esac
SUITE_STATS_CONFIG="$STATS_CONFDIR/$_pgm/stats.conf"

#
# For the `tail' command, default number of lines
#
DEFAULT_TAIL_NUMLINES=10

############################################################ INCLUDES

. /usr/share/bsdconfig/strings.subr || exit
. /usr/share/bsdconfig/sysrc.subr || exit

############################################################ GLOBALS

#
# Global exit status
#
SUCCESS=0
FAILURE=1

#
# Command-line options
#
SUITE_CONFIG="${SUITE_CONFIG_SYS:-$DEFAULT_CONFIG}"	# -c file

############################################################ FUNCTIONS

die()
{
	local fmt="$1"
	exec >&2
	if [ "$fmt" ]; then
		shift 1 # fmt
		printf "%s: $fmt\n" "$_pgm" "$@"
	fi
	exit ${FAILURE:-1}
}

usage()
{
	local fmt="$1"
	local cmdfmt="\t%-25s %s\n"
	local optfmt="\t%-10s %s\n"
	local config_sys
	exec >&2
	if [ "$fmt" ]; then
		shift 1 # fmt
		printf "%s: $fmt\n" "$_pgm" "$@"
	fi
	printf "Usage: %s [-hv] [-c config] command ...\n" "$_pgm"
	printf "Options:\n"
	printf "$optfmt" "-c file" "Config file."
	printf "$optfmt" "" "Program: $DEFAULT_CONFIG"
	if [ "$SUITE_CONFIG_SYS" ]; then
		config_sys="$SUITE_CONFIG_SYS [$SUITE_CONFIG_SYS_FILE]"
	else
		config_sys="Use sysrc $SUITE_CONFIG_SYS_VAR=..."
	fi
	printf "$optfmt" "" "System:  $config_sys"
	printf "$optfmt" "" "Current: $SUITE_CONFIG"
	printf "$optfmt" "-h" "Print usage statement and exit."
	printf "$optfmt" "-v" "Print version information and exit."
	printf "Commands:\n"
	printf "$cmdfmt" "- [-dh]" "Run in foreground logging to stdout."
	printf "$cmdfmt" "edit [-ahls]" \
		"Edit config file. Uses \$EDITOR [$EDITOR]."
	printf "$cmdfmt" "list|ls [-aghl] [regex]" "List config variables."
	printf "$cmdfmt" "logs [-h]" "List log files."
	printf "$cmdfmt" "restart [-ahs]" "Restart service."
	printf "$cmdfmt" "rotate [-h]" "Rotate log files."
	printf "$cmdfmt" "show [-aghlNnu] [var ...]" "Show config variables."
	printf "$cmdfmt" "status [-ahs]" "Service status."
	printf "$cmdfmt" "start [-ahs]" "Start service."
	printf "$cmdfmt" "" "JSON log:  $LOGFILE"
	printf "$cmdfmt" "" "Error log: $ERRFILE"
	printf "$cmdfmt" "stop [-ahs]" "Stop service."
	printf "$cmdfmt" "tail [-fh] [-n num]" "Tail JSON log."
	printf "$cmdfmt" "trace [-hnu] [var ...]" "Trace config variables."
	die
}

############################################################ COMMANDS

editcmd_usage()
{
	local fmt="$1"
	local optfmt="\t%-5s %s\n"
	exec >&2
	if [ "$fmt" ]; then
		shift 1 # fmt
		printf "%s: $fmt\n" "$_pgm" "$@"
	fi
	printf "Usage: %s edit [-ahls]\n" "$_pgm"
	printf "Options:\n"
	printf "$optfmt" "-a" \
		"Edit both \`$_pgm' and \`${_pgm}_stats' configs."
	printf "$optfmt" "-h" "Print usage statement and exit."
	printf "$optfmt" "-l" "List files instead of using \$EDITOR [$EDITOR]."
	printf "$optfmt" "-s" "Edit only \`${_pgm}_stats' service config."
	die
}

editcmd()
{
	local OPTIND=1 OPTARG flag
	local configs="$SUITE_CONFIG"
	local config
	local list_only=0

	while getopts ahls flag; do
		case "$flag" in
		a) configs="$SUITE_CONFIG $SUITE_STATS_CONFIG" ;;
		l) list_only=$(( $list_only + 1 )) ;;
		s) configs="$SUITE_STATS_CONFIG" ;;
		*) editcmd_usage # NOTREACHED
		esac
	done
	shift $(( $OPTIND - 1 ))

	[ $# -eq 0 ] || editcmd_usage "Too many arguments" # NOTREACHED

	if [ $list_only -eq 1 ]; then
		for config in $configs; do
			echo $config
		done
		return
	elif [ $list_only -eq 2 ]; then
		ls -l $configs
		return
	elif [ $list_only -gt 2 ]; then
		printf "%s: edit: Too many -l options (max 2)\n" "$_pgm" >&2
		return
	fi

	$EDITOR $configs
}

listcmd_usage()
{
	local fmt="$1"
	local optfmt="\t%-5s %s\n"
	exec >&2
	if [ "$fmt" ]; then
		shift 1 # fmt
		printf "%s: $fmt\n" "$_pgm" "$@"
	fi
	printf "Usage: %s list [-aghl] [regex]\n" "$_pgm"
	printf "Options:\n"
	printf "$optfmt" "-a" "Show all variables."
	printf "$optfmt" "-g" "Show global variables (not shown by default)."
	printf "$optfmt" "-h" "Print usage statement and exit."
	printf "$optfmt" "-l" "Show label variables (not shown by default)."
	die
}

listcmd()
{
	local OPTIND=1 OPTARG flag
	local show_global_vars=
	local show_label_vars=
	local conf="$SUITE_CONFIG"
	local confvars
	local tmp
	local var

	while getopts aghl flag; do
		case "$flag" in
		a) show_global_vars=1 show_label_vars=1 ;;
		g) show_global_vars=1 ;;
		l) show_label_vars=1 ;;
		*) listcmd_usage # NOTREACHED
		esac
	done
	shift $(( $OPTIND - 1 ))

	[ $# -le 1 ] || listcmd_usage "Too many arguments" # NOTREACHED

	#
	# Get full list of variables set by config
	# NB: NL is a basic element of parsing in this suite, always hidden
	#
	confvars=$( sysrc -aNf "$conf" | grep -v '^NL$' )

	#
	# If NOT given `-a' or `-g', elide IGNORE variables
	#
	if [ ! "$show_global_vars" ]; then
		IGNORE=$( sysrc -qnf "$conf" IGNORE )
		for var in $SUITE_CONFIG_IGNORE $IGNORE; do
			local __listcmd_ignore_$var=1
		done
		tmp=
		for var in $confvars; do
			f_isset __listcmd_ignore_$var || tmp="$tmp$NL$var"
		done
		confvars="${tmp#$NL}"
	fi

	#
	# If NOT given `-a' or `-l', elide `*_label' variables
	#
	if [ ! "$show_label_vars" ]; then
		tmp=
		for var in $confvars; do
			[ "$var" = "${var%_label}" ] || continue
			tmp="$tmp$NL$var"
		done
		confvars="${tmp#$NL}"
	fi

	#
	# Return success immediately if no variables configured
	# NB: Prevents empty newline from being printed
	#
	[ "$confvars" ] || return 0 # SUCCESS

	#
	# List configured variables
	#
	if [ $# -eq 1 ]; then
		# NB: --color automatically tests stdin for console
		echo "$confvars" | grep --color -- "$1"
	else
		echo "$confvars"
	fi
}

logscmd_usage()
{
	local fmt="$1"
	local optfmt="\t%-5s %s\n"
	exec >&2
	if [ "$fmt" ]; then
		shift 1 # fmt
		printf "%s: $fmt\n" "$_pgm" "$@"
	fi
	printf "Usage: %s logs [-h]\n" "$_pgm"
	printf "Options:\n"
	printf "$optfmt" "-h" "Print usage statement and exit."
	die
}

logscmd()
{
	local OPTIND=1 OPTARG flag

	while getopts h flag; do
		case "$flag" in
		*) logscmd_usage # NOTREACHED
		esac
	done
	shift $(( $OPTIND - 1 ))

	[ $# -eq 0 ] || logscmd_usage "Too many arguments" # NOTREACHED

	"$_pgm"-logs "$@"
}

rotatecmd_usage()
{
	local fmt="$1"
	local optfmt="\t%-5s %s\n"
	exec >&2
	if [ "$fmt" ]; then
		shift 1 # fmt
		printf "%s: $fmt\n" "$_pgm" "$@"
	fi
	printf "Usage: %s rotate [-h]\n" "$_pgm"
	printf "Options:\n"
	printf "$optfmt" "-h" "Print usage statement and exit."
	die
}

rotatecmd()
{
	local OPTIND=1 OPTARG flag

	while getopts h flag; do
		case "$flag" in
		*) rotatecmd_usage # NOTREACHED
		esac
	done
	shift $(( $OPTIND - 1 ))

	[ $# -eq 0 ] || rotatecmd_usage "Too many arguments" # NOTREACHED

	"$_pgm"-rotate "$@"
}

servicecmd_usage()
{
	local cmd="$1"
	shift 1 # cmd
	local fmt="$1"
	local optfmt="\t%-5s %s\n"
	exec >&2
	if [ "$fmt" ]; then
		shift 1 # fmt
		printf "%s: $fmt\n" "$_pgm" "$@"
	fi
	printf "Usage: %s %s [-ahs]\n" "$_pgm" "$cmd"
	printf "Options:\n"
	printf "$optfmt" "-a" "Operate on \`$_pgm' and \`${_pgm}_stats' service."
	printf "$optfmt" "-h" "Print usage statement and exit."
	printf "$optfmt" "-s" "Operate on \`${_pgm}_stats' service."
	die
}

servicecmd()
{
	local cmd="$1"
	shift 1 # cmd
	local OPTIND=1 OPTARG flag
	local svcnames="$_pgm"
	local name

	while getopts ahs flag; do
		case "$flag" in
		a) svcnames="$_pgm ${_pgm}_stats" ;;
		s) svcnames="${_pgm}_stats" ;;
		*) servicecmd_usage "$cmd" # NOTREACHED
		esac
	done
	shift $(( $OPTIND - 1 ))

	[ $# -eq 0 ] ||
		servicecmd_usage "$cmd" "Too many arguments" # NOTREACHED

	for name in $svcnames; do
		service "$name" "$cmd"
	done
}

showcmd_usage()
{
	local fmt="$1"
	local optfmt="\t%-5s %s\n"
	exec >&2
	if [ "$fmt" ]; then
		shift 1 # fmt
		printf "%s: $fmt\n" "$_pgm" "$@"
	fi
	printf "Usage: %s show [-aghlNnu] [var ...]\n" "$_pgm"
	printf "Options:\n"
	printf "$optfmt" "-a" "Show all variables."
	printf "$optfmt" "-g" "Show global variables (not shown by default)."
	printf "$optfmt" "-h" "Print usage statement and exit."
	printf "$optfmt" "-l" "Show label variables (not shown by default)."
	printf "$optfmt" "-N" "Show only variable names, not their values."
	printf "$optfmt" "-n" "Show only variable values, not their names."
	printf "$optfmt" "-u" "Print unmatched rule."
	die
}

showcmd()
{
	local OPTIND=1 OPTARG flag
	local __show_global_vars=
	local __show_label_vars=
	local __show_names=1
	local __show_unmatched=
	local __show_values=1
	local __ign

	while getopts aghlNnu flag; do
		case "$flag" in
		a) __show_global_vars=1 __show_label_vars=1 ;;
		g) __show_global_vars=1 ;;
		l) __show_label_vars=1 ;;
		N) __show_names=1 __show_values= ;;
		n) __show_values=1 __show_names= ;;
		u) __show_unmatched=1 ;;
		*) showcmd_usage # NOTREACHED
		esac
	done
	shift $(( $OPTIND - 1 ))

	( # Operating in a sub-shell to protect parent namespace

		f_clean_env --except \
			IFS \
			IGNORE \
			NL \
			PATH \
			SUITE_CONFIG \
			SUITE_CONFIG_IGNORE \
			VALID_VARNAME_CHARS \
			__show_global_vars \
			__show_label_vars \
			__show_names \
			__show_unmatched \
			__show_values \
			_pgm

		. "$SUITE_CONFIG" || exit

		#
		# If no args, default to showing all configured variables
		# NB: `listcmd' given no arguments will list all variables
		#
		[ $# -gt 0 ] || set -- $( listcmd ${__show_global_vars:+-g} \
			${__show_label_vars:+-l} )

		#
		# NB: This may seem repetitive, but that is the best way to to
		#     ensure minimum runtime complexity. For example, if we
		#     first looped over every variable to taint-check and then
		#     looped again to produce desired output, we would take
		#     twice as long.
		#
		if [ "$__show_unmatched" ]; then
			__unmatched=
			for __var in "$@"; do
				case "$__var" in
				*_label|*_LABEL) continue ;; # skip
				[0-9]*|*[!$VALID_VARNAME_CHARS]*)
					die "Invalid variable: %s" "$__var" ;;
				esac
				f_getvar $__var __val ||
					die "Unknown variable: %s" "$__var"
				__unmatched="$__unmatched || ( $__val )"
			done
			if [ "$__unmatched" ]; then
				__unmatched="${__unmatched# || }"
				__unmatched="! ( $__unmatched )"
			fi
			if [ "$__show_names" -a "$__show_values" ]; then
				f_shell_escape "$__unmatched" __unmatched
				printf "UNMATCHED='%s'\n" "$__unmatched"
			elif [ "$__show_names" ]; then
				[ "$__unmatched" ] && printf "UNMATCHED\n"
				# NB: exit status here
			elif [ "$__show_values" ]; then
				printf "%s\n" "$__unmatched"
			fi
		elif [ "$__show_names" -a "$__show_values" ]; then
			for __var in "$@"; do
				case "$__var" in
				[0-9]*|*[!$VALID_VARNAME_CHARS]*)
					die "Invalid variable: %s" "$__var" ;;
				esac
				f_getvar $__var __val ||
					die "Unknown variable: %s" "$__var"
				f_shell_escape "$__val" __val
				printf "%s='%s'\n" "$__var" "$__val"
			done
		elif [ "$__show_names" ]; then
			for __var in "$@"; do
				case "$__var" in
				[0-9]*|*[!$VALID_VARNAME_CHARS]*)
					die "Invalid variable: %s" "$__var" ;;
				esac
				f_getvar $__var __ignored ||
					die "Unknown variable: %s" "$__var"
				printf "%s\n" "$__var"
			done
		elif [ "$__show_values" ]; then
			for __var in "$@"; do
				case "$__var" in
				[0-9]*|*[!$VALID_VARNAME_CHARS]*)
					die "Invalid variable: %s" "$__var" ;;
				esac
				f_getvar $__var __val ||
					die "Unknown variable: %s" "$__var"
				printf "%s\n" "$__val"
			done
		fi
	)
}

stdoutcmd_usage()
{
	local fmt="$1"
	local optfmt="\t%-9s %s\n"
	exec >&2
	if [ "$fmt" ]; then
		shift 1 # fmt
		printf "%s: $fmt\n" "$_pgm" "$@"
	fi
	printf "Usage: %s - [-dh]\n" "$_pgm"
	printf "Options:\n"
	printf "$optfmt" "-d" "Debug. Send dtrace(1) script to stdout."
	printf "$optfmt" "-h" "Print usage statement and exit."
	die
}

tailcmd_usage()
{
	local fmt="$1"
	local optfmt="\t%-9s %s\n"
	exec >&2
	if [ "$fmt" ]; then
		shift 1 # fmt
		printf "%s: $fmt\n" "$_pgm" "$@"
	fi
	local numlines="$DEFAULT_TAIL_NUMLINES"
	printf "Usage: %s tail [-fh] [-n num]\n" "$_pgm"
	printf "Options:\n"
	printf "$optfmt" "-f" "Print new lines as they arrive in file."
	printf "$optfmt" "-h" "Print usage statement and exit."
	printf "$optfmt" "-n num" \
		"Start num lines before end of file. Default \`$numlines'."
	die
}

tailcmd()
{
	local OPTIND=1 OPTARG flag
	local follow=
	local numlines="$DEFAULT_TAIL_NUMLINES"

	while getopts fhn: flag; do
		case "$flag" in
		f) follow=1 ;;
		n) numlines="$OPTARG" ;;
		*) tailcmd_usage # NOTREACHED
		esac
	done
	shift $(( $OPTIND - 1 ))

	[ $# -eq 0 ] || tailcmd_usage "Too many arguments" # NOTREACHED

	tail ${follow:+-F} -n "$numlines" "$LOGFILE"
}

tracecmd_usage()
{
	local fmt="$1"
	local optfmt="\t%-5s %s\n"
	exec >&2
	if [ "$fmt" ]; then
		shift 1 # fmt
		printf "%s: $fmt\n" "$_pgm" "$@"
	fi
	printf "Usage: %s show [-hnu] [var ...]\n" "$_pgm"
	printf "Options:\n"
	printf "$optfmt" "-h" "Print usage statement and exit."
	printf "$optfmt" "-i" "Allow tracing IGNORE'd variables."
	printf "$optfmt" "-n" "Dry run. Show dwatch command."
	printf "$optfmt" "-u" "Trace unmatched."
	die
}

tracecmd()
{
	local OPTIND=1 OPTARG flag
	local __allow_ignored=
	local __trace_dry_run=
	local __trace_unmatched=

	while getopts hinu flag; do
		case "$flag" in
		i) __allow_ignored=1 ;;
		n) __trace_dry_run=1 ;;
		u) __trace_unmatched=1 ;;
		*) tracecmd_usage # NOTREACHED
		esac
	done
	shift $(( $OPTIND - 1 ))

	( # Operating in a sub-shell to prevent parent namespace

		f_clean_env --except \
			IFS \
			NL \
			PATH \
			SUITE_CONFIG \
			SUITE_CONFIG_IGNORE \
			SUITE_TRACE_DWATCH_MODULE \
			VALID_VARNAME_CHARS \
			__allow_ignored \
			__trace_dry_run \
			__trace_unmatched \
			_pgm

		. "$SUITE_CONFIG" || exit

		#
		# Unset variables that we should ignore when tracing
		# NB: No error if unset called with zero arguments
		#
		[ "$__allow_ignored" ] || unset $IGNORE
		unset $SUITE_CONFIG_IGNORE

		#
		# Tracing command
		#
		__cmd="dwatch -X $SUITE_TRACE_DWATCH_MODULE"

		#
		# What to trace
		#
		if [ "$__trace_unmatched" ]; then
			[ $# -gt 0 ] || set -- $( listcmd )
			__unmatched=
			for __var in "$@"; do
				case "$__var" in
				*_label|*_LABEL) continue ;; # skip
				[0-9]*|*[!$VALID_VARNAME_CHARS]*)
					die "Invalid variable: %s" "$__var" ;;
				esac
				f_getvar $__var __val ||
					die "Unknown variable: %s" "$__var"
				__unmatched="$__unmatched || ( $__val )"
			done
			if [ "$__unmatched" ]; then
				__unmatched="${__unmatched# || }"
				__unmatched="! ( $__unmatched )"
				f_shell_escape "$__unmatched" __unmatched
				__cmd="$__cmd -t '$__unmatched'"
			fi
		else
			for __var in "$@"; do
				case "$__var" in
				[0-9]*|*[!$VALID_VARNAME_CHARS]*)
					die "Invalid variable: %s" "$__var" ;;
				esac
				if ! f_getvar $__var __val; then
					__ignored=
					for __ignore in $IGNORE; do
						[ "$__var" = "$__ignore" ] ||
							continue
						__ignored=1
						break
					done
					if [ "$__ignored" ]; then
						__var="$__var (IGNORE'd;"
						__var="$__var use \`-i')"
					fi
					die "Unknown variable: %s" "$__var"
				fi
				f_shell_escape "$__val" __val
				__cmd="$__cmd -t '$__val'"
			done
		fi

		#
		# Trace it?
		#
		if [ "$__trace_dry_run" ]; then
			echo "$__cmd"
		else
			eval "$__cmd"
		fi
	)
}

############################################################ MAIN

#
# Process command-line options
#
while getopts c:hv flag; do
	case "$flag" in
	c) SUITE_CONFIG="$OPTARG" ;;
	v) VERSION="${VERSION#*: }"
		echo "${VERSION% $}"
		exit $SUCCESS ;;
	*) usage # NOTREACHED
	esac
done
shift $(( $OPTIND - 1 ))

#
# Process command-line arguments
#
if [ $# -gt 0 ]; then
	CMD="$1"
	shift 1 # CMD

	case "$CMD" in
	-) : fall-through ;;
	e|ed|edi|edit) editcmd "$@" ;;
	l) usage "ambiguous command [list logs]" ;; # NOTREACHED
	li|ls|lis|list) listcmd "$@" ;;
	lo|log|logs) logscmd "$@" ;;
	r) usage "ambiguous command [restart rotate]" ;; # NOTREACHED
	re|res|rest|resta|restar|restart) servicecmd restart "$@" ;;
	ro|rot|rota|rotat|rotate) rotatecmd "$@" ;;
	s) usage "ambiguous command [show start status stop]" ;; # NOTREACHED
	sh|sho|show) showcmd "$@" ;;
	st) usage "ambiguous command [start status stop]" ;; # NOTREACHED
	sta) usage "ambiguous command [start status]" ;; # NOTREACHED
	star|start) : fall-through ;;
	stat|statu|status) servicecmd status "$@" ;;
	sto|stop) servicecmd stop "$@" ;;
	t) usage "ambiguous command [tail trace]" ;; # NOTREACHED
	ta|tai|tail) tailcmd "$@" ;;
	tr|tra|trace) tracecmd "$@" ;;
	*) usage "Unknown command \`%s'" "$CMD" # NOTREACHED
	esac
	retval=$?

	case "$CMD" in
	-) : fall-through ;;
	start) : fall-through ;;
	*) exit $retval
	esac
fi

#
# Require that if no arguments are given, that we are launched from service(8)
# NB: This prevents accidental launching of the service if no arguments given
#
if [ "$RC_PID" ]; then
	: "service(8) start called"
	#
	# NB: We are not called for stop (service(8) handles that itself
	#     utilizing the pid file).
	#
elif [ "$CMD" = "start" ]; then
	: fall-through
elif [ "$CMD" = "-" ]; then
	: fall-through
elif [ "$CMD" ] && [ "$CMD" != "${CMD#-}" ]; then
	usage "Unknown command \`%s'" "$CMD" NOTREACHED
elif [ $# -eq 0 ]; then
	usage "missing command" # NOTREACHED
fi

#
# Process [more] command-line options
#
pgmstart=
suitesvcnames=
if [ "$CMD" = "-" ]; then

	OPTIND=1
	debug=

	while getopts dh flag; do
		case "$flag" in
		d) debug=1 ;;
		*) stdoutcmd_usage # NOTREACHED
		esac
	done
	shift $(( $OPTIND - 1 ))

	#
	# Check command-line arguments
	#
	[ $# -eq 0 ] || stdoutcmd_usage "Too many arguments" # NOTREACHED

	pgmstart=fg

elif [ "${CMD:-start}" = "start" ]; then

	svcnames="$_pgm"
	OPTIND=1
	while getopts ahs flag; do
		case "$flag" in
		a) svcnames="$_pgm ${_pgm}_stats" ;;
		s) svcnames="${_pgm}_stats" ;;
		*) servicecmd_usage start # NOTREACHED
		esac
	done
	shift $(( $OPTIND - 1 ))

	#
	# Check start command-line arguments
	#
	[ $# -eq 0 ] ||
		servicecmd_usage start "Too many arguments" # NOTREACHED

	#
	# NB: Safe to overload argumentspace now
	#
	set -- $svcnames

	#
	# Exit if service(s) already running
	# NB: If there are only other services to start, fall through
	# NB: Track which services are externally in the suite vs this
	#
	pgmstart=
	suitesvcnames=
	for name in $svcnames; do
		if [ "$name" != "$_pgm" ]; then
			suitesvcnames="$suitesvcnames $name"
			continue
		fi
		if status=$( service "$_pgm" status 2>&1 ); then
			printf "%s\n" "$status"
			[ $# -gt 1 ] || exit $FAILURE
		else
			pgmstart=bg
		fi
	done
	suitesvcnames="${suitesvcnames# }"

fi # CMD

#
# Variables used by the dwatch(1) module we are instrumenting
# NB: Unused (but harmless) if we do not end up launching service
#
export JSON_NET_CONFIG="$SUITE_CONFIG"
export REPORT_TYPE

#
# Launch service in foreground?
#
if [ "$pgmstart" = "fg" ]; then
	(
		f_clean_env --except \
			JSON_NET_CONFIG \
			PATH \
			REPORT_TYPE \
			SUITE_SERVICE_DWATCH_MODULE \
			debug
		dwatch -${debug:+d}X $SUITE_SERVICE_DWATCH_MODULE
	)
	exit
fi

#
# Launch service in background
#
if [ "$pgmstart" ]; then
	#
	# Make required directories
	# NB: If not done, background process may not start
	#
	mkdir -p "${ERRFILE%/*}" || exit
	mkdir -p "${LOGFILE%/*}" || exit
	mkdir -p "${PIDFILE%/*}" || exit

	#
	# Start background process
	#
	pid=$(
		f_clean_env --except \
			JSON_NET_CONFIG \
			LOGFILE ERRFILE \
			PATH \
			REPORT_TYPE \
			SUITE_SERVICE_DWATCH_MODULE
		dwatch -X $SUITE_SERVICE_DWATCH_MODULE \
			>> "$LOGFILE" 2> "$ERRFILE" &
		echo $!
	)

	#
	# Wait for dtrace(1) to appear
	#
	while : forever; do
		proctree=$( ps do pid,comm | awk -v pid=$pid '
			$1 == pid { ++found; next }
			found && $2 !~ /^([|`]--|[|-])$/ { exit }
			found
			END { exit !found }
		' ) || die "Unknown error\n%s\n" "$( tail "$ERRFILE" )"
		_pid=$( echo "$proctree" | awk -v comm=dtrace '
			$NF == comm { print $1; exit ++found }
			END { exit !found }
		' ) && break
		sleep 0.25
	done

	#
	# Put dtrace(1) PID in pidfile so service(8) can stop DTrace process
	# NB: Killing dtrace(1) component is proper way to terminate service
	# NB: If $! were simply put into the pidfile, dtrace(1) continues to
	#     run even after using service(8) to stop the service.
	#
	echo "$_pid" > "$PIDFILE"
fi

#
# Launch ancillary services
#
for name in $suitesvcnames; do
	if status=$( service "$name" status 2>&1 ); then
		printf "%s\n" "$status"
		continue
	fi
	errfile="$SUITE_RUN_DIR/${name#${_pgm}_}.stderr"
	service "$name" start ||
		die "Unknown error\n%s\n" "$( tail "$errfile" )"
done

################################################################################
# END
################################################################################
