#!/bin/sh
# platform ... devuan dash
# GPL_3+
cat << 'EEE' > /dev/null
/* killtree .... kill process family. bourne-shell script
 * Copyright (C) 2018,2019 Momi-g
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
EEE


# ini=`cat << 'END'
#	-h bool 0
#	-d bool 0
#	-s str 'TERM'
#	-v bool 0
#	-c bool 0
# END
# `
# buf=`ckopt "$ini"`
# eval "$buf"


## ---this code is generated by ckopt
#	---optsetting
#
#	-h bool 0
#	-d bool 0
#	-s str 'TERM'
#	-v bool 0
#	-c bool 0

ckopt_func () { 
if [ "$1" = "-d" ] ; then
	eval "opt_${2#?}"'="$3"'		# opt_?="$3"
	return 0
fi

ckopt_opt="$2"
if [ "$1" = "-e" ] ; then
shift 4
while [ $# -gt 0 ]
do
if [ "$1" != "${1#[#]e}" ] || [ "$1" != "${1#[#]E}" ] ; then
	eval "${1#[#]?}"
	if [ "$?" != "0" ] ; then
		OPTARG="errmsg $ckopt_opt $1"
		return 1
	fi
fi
shift
done
return 0
fi

if [ "$1" = "-c" ] ; then
shift 3
printf '%s\n' "$1" | tr '[:upper:]' '[:lower:]' | awk '$1 ~ /int/ {exit 1}'
	if [ "$?" = "1" ] ; then
		shift
		set -- "dummy" 'printf "%d" "$OPTARG" >/dev/null 2>&1' "$@"
	fi
shift
while [ $# -gt 0 ]
do
	eval "$1"
	if [ "$?" != "0" ] ; then
		OPTARG="errmsg $ckopt_opt $1"
		return 1
	fi
shift
done
return 0
fi

echo "$0: bug. sleep" >/dev/stderr
while :
do
	sleep 1000
done
exit 1
}

# ---dflset

OPTIND=1	# fixed value. posix rule.
OPTERR=0
ckopt_buf=''
ckopt_opt=''

 ckopt_func -d -h bool 0
 ckopt_func -d -d bool 0
 ckopt_func -d -s str 'TERM'
 ckopt_func -d -v bool 0
 ckopt_func -d -c bool 0

#---getopts loop. break if all args read or detect '--'
while :
do
if [ 0 -eq "$#" ] || [ "${OPTARG%% *}" = "errmsg" ] ; then
	break
fi
check_opt='$'"$OPTIND"
eval "check_opt=\"$check_opt\""
if [ "$check_opt" = "--" ] ; then
	shift $OPTIND; break
fi
getopts ":h:d:s:v:c:" ckopt_opt "$@"		# ":a:bc:f:" etc...
if [ "$?" = "1" ] ; then	# detect end/normal args. save general args.
	shift $((OPTIND - 1))
	if [ "$#" -eq "0" ] ; then
		break
	fi
	ckopt_buf="$ckopt_buf "`printf '%s' "$1" | sed -e "{        
	s#'#'\"'\"'#g
	s/^/'/g
	s/$/'/g
	}"`
	shift
	OPTIND=1
	continue
fi
# --- your setting

if [ "$ckopt_opt" = "h" ] ; then
	opt_h="$OPTARG"
	ckopt_func -c -h bool 0
	continue
fi
if [ "$ckopt_opt" = "d" ] ; then
	opt_d="$OPTARG"
	ckopt_func -c -d bool 0
	continue
fi
if [ "$ckopt_opt" = "s" ] ; then
	opt_s="$OPTARG"
	ckopt_func -c -s str 'TERM'
	continue
fi
if [ "$ckopt_opt" = "v" ] ; then
	opt_v="$OPTARG"
	ckopt_func -c -v bool 0
	continue
fi
if [ "$ckopt_opt" = "c" ] ; then
	opt_c="$OPTARG"
	ckopt_func -c -c bool 0
	continue
fi
# --- your setting end

# hit err. ckopt chars...
# err detect@silent mode
# $?=1 ... detect optend or '--'
# ':' ... detect option, but dont have subargs (OPTARG="factor").
# '?' ... detect unsupported option char (OPTARG="factor") or args end (OPTARG="blank").
# OPTARG ... "" is optend. "a/b/c..." is invalid option 
# OPTIND ... if err, OPTIND indicates next (new) arg pos. 

OPTARG="errmsg -$OPTARG invalid option / misses subargs"
done

# --- post process. set not optional args & clean & #eE last cmd.

for ii in 1	# dummyjump logic
do
	OPTIND=1
	# skip
	test "${OPTARG%% *}" != "errmsg" || break
	ckopt_buf="set -- $ckopt_buf"' "$@"'	# set -- 'cmd' .. "$@"
	eval "$ckopt_buf"

	# run #E cmd.  cmd + test $? ... xn
 ckopt_func -e -h bool 0
 ckopt_func -e -d bool 0
 ckopt_func -e -s str 'TERM'
 ckopt_func -e -v bool 0
 ckopt_func -e -c bool 0
done

if [ "${OPTARG%% *}" = "errmsg" ] ; then
	printf "$0: opterr. %s\n" "$OPTARG" >/dev/stderr ; while : ; do sleep 1000 ; done ; exit 1
	test 1 = 0
fi
## ---generate by ckopt end


if [ "$?" != "0" ] ; then
	echo "$0: args err($OPTARG). see -h. sleep." >/dev/stderr ; while : ; do sleep 1000 ; done
	exit 1
fi


# arg intck
for ii
do
	printf '%d' "$ii" >/dev/null 2>&1
	if [ "$?" != "0" ] ; then
		opt_h="1"
		break
	fi
done

if [ "$opt_h" = "1" ] || [ $# -eq 0 ] ; then
cat << 'EEE'
HowTo (show/kill process family. bourne-shell script)
opt: -h, -d(isp, not kill), -s(ignal), -v(erbose), -c(hildren only)
------
ex.) ~$ killtree 1111 -d
>>>	0 ppid10 1111	# 1111 -+- 3237 --- 3238
	1 ppid1111 3237	#       +- 3241
	1 ppid1111 3241
	2 ppid3237 3238	# generation : parent pid : child pid

ex.) ~$ killtree 1111
>>> kill all processes from youngest generation. kill 3238->3237,3241->1111

ex.) ~$ killtree 1111 -s USR1
>>>	kill using SIGUSR1.  -s opt is based on 'kill' opt. see 'kill -l'. 

ex.) ~$ killtree 1111 -v	#... show report to stdout
>>> try: 1111 3237 3238 3241
   suc: 1111 3238
   rest: 3241		(signal denied etc)
   lost: 3237 	(not found. race condition etc)

ex.) ~$ killtree 1111 -c	# >>>	kill without parent (1111).
EEE
exit 0
fi


#--main_func
# func_killtree 1111 TERM cvd	>>> pid signal optionchar(-d ...d, -c -v ...cv)
func_killtree() {
	(

kt_buf=""

kt_nowpid=$$
kt_tgtpid="$1"	# 1234 etc
kt_signal="$2"	# TERM USR1 etc
kt_opts="$3"	# dvc -> -c -v -d ... 1 chars

kt_pslist=`ps -eo ppid,pid`
kt_gen=0
kt_cnt=1
kt_ppids=""
kt_add=""
kt_result=""
kt_failed=""

kt_req=""
kt_rest=""
kt_remove=""
kt_lost=""
kt_suc=""

# skip init pid
kt_cpidcmd=`cat << 'EEE'
for ii in $kt_buf
do
	echo "$kt_pslist" | awk -v gen=$kt_gen -v ppid=$ii -v skip=$kt_nowpid '
	$1 == ppid && $2 != skip {print gen " ppid" $1 " " $2}'
done
EEE
`

# remove this script pid
kt_buf=`echo "$kt_pslist" | awk -v pid=$kt_tgtpid '$2 == pid {print $1}'`
kt_result="$kt_gen ppid$kt_buf $kt_tgtpid"		# 0 ppid1234 1111	gen/ppid/cpid 
while :
do
	kt_ppids=`echo "$kt_result" | awk -v gen=$kt_gen '$1 == gen {print $NF}' `
	kt_gen=$((kt_gen+1))
	kt_buf="$kt_ppids"
	kt_add=`eval "$kt_cpidcmd"`
	kt_result=`printf '%s\n%s\n' "$kt_result" "$kt_add" | grep -v '^$'`
	
	#ck diff
	kt_buf=`echo "$kt_result" | wc -l`
	if [ "$kt_buf" = "$kt_cnt" ] ; then
		break
	fi
	kt_cnt="$kt_buf"
done
# result=`echo "$result" | sort -nr `

kt_buf=`echo "$kt_opts" | tr -dc 'c'`
if [ "$kt_buf" != "" ] ; then
	kt_result=`echo "$kt_result" | grep -v '^0'`
fi

kt_buf=`echo "$kt_opts" | tr -dc 'd'`
if [ "$kt_buf" != "" ] ; then
	echo "$kt_result"
else
	kt_buf=`echo "$kt_result" | sort -nr | awk '{printf("%s ",$NF)}'`
	kt_failed=""
	for kt_ii in $kt_buf
	do
		kill -0 $kt_ii >/dev/null 2>&1
		if [ "$?" != "0" ] ; then
			kt_failed="$kt_failed
$kt_ii"
		else
			kill "-$kt_signal" $kt_ii >/dev/null 2>&1
		fi
	done

	kt_buf=`echo "$kt_opts" | tr -dc 'v'`
	if [ "$kt_buf" != "" ] ; then
		# disp report
		kt_req=`echo "$kt_result" | awk '{print $NF }' | sort -n`
		kt_buf=`ps -eo pid`	# now running all process
		
		kt_rest=`printf '%s\n%s\n' "$kt_buf" "$kt_req" | sort -n | uniq -d`	# dup only
		kt_remove=`printf '%s\n%s\n' "$kt_req" "$kt_rest" | sort -n | uniq -u`	# uniq only
		kt_lost=`printf '%s\n%s\n' "$kt_remove" "$kt_failed" | sort -n | uniq -d `
		kt_suc=`printf '%s\n%s\n' "$kt_remove" "$kt_lost" | sort -n | uniq -u`

		set -- $kt_req
		echo "try: $*"
		set -- $kt_suc
		echo "suc: $*"
		set -- $kt_rest
		echo "rest: $*"
		set -- $kt_lost
		echo "lost: $*"
	fi
fi

#--- clean	>> omit. use subshell '()'
#	kt_buf=""
#	kt_nowpid=""
#	kt_tgtpid=""
#	kt_signal=""
#	kt_opts=""
#	
#	kt_pslist=""
#	kt_gen=""
#	kt_cnt=""
#	kt_ppids=""
#	kt_add=""
#	kt_result=""
#	kt_failed=""
#	
#	kt_req=""
#	kt_rest=""
#	kt_remove=""
#	kt_lost=""
#	kt_suc=""
#	
#	kt_cpidcmd=""
)
}


#--- main
optstr=""
if [ "$opt_d" = "1" ] ; then
	optstr="$optstr""d"
fi
if [ "$opt_v" = "1" ] ; then
	optstr="$optstr""v"
fi
if [ "$opt_c" = "1" ] ; then
	optstr="$optstr""c"
fi

# func_killtree 1111 TERM cvd	>>> pid signal optionchar(-d ...d, -c -v ...cv)
for ii
do
	func_killtree "$ii" "$opt_s" "$optstr"
done