#!/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



## ---this code is generated by ckopt
# 	---optsetting
#  -h bool 0
#  -d bool 0
#  -s str 'TERM'
#  -v bool 0
#  -c bool 0
# 	---sprit (casemaker: inner func)
# casemaker h 0 '-h bool 0'
# casemaker d 0 '-d bool 0'
# casemaker s 1 '-s str '"'"'TERM'"'"''
# casemaker v 0 '-v bool 0'
# casemaker c 0 '-c bool 0'


ckopt_bkind=$OPTIND
ckopt_bkerr=$OPTERR
OPTIND=1	# fixed value. posix rule.
OPTERR=0
ckopt_err=0
ckopt_errmsg=''
ckopt_delcnt=1
ckopt_skip=''	# save not opt args strings
ckopt_buf=''

opt_h='0'
opt_d='0'
opt_s='TERM'
opt_v='0'
opt_c='0'
ckopt_fm='hds:vc'

ckopt_tmpfunc () { 
if [ "$2" = "int" ] ; then
	ckopt_buf="opt_"${1#-}		# opt_a
	ckopt_buf="$ckopt_buf"'=`printf "%d" "$'"$ckopt_buf"'"  2>/dev/null`'
	# ... opt_a=`printf "%d" "$opt_a" 2>/dev/null`
	shift 3
	set -- "$ckopt_buf" "$@"
else
	shift 3
fi

for ii
do
	eval "$ii"
	if [ "$?" != "0" ] ; then ckopt_err=1 ; break ;fi
done
}

#---getopts loop. break if all args read or detect '--'
while :
do
if [ 0 -eq "$#" ] || [ "$ckopt_err" = "1" ] ; then
	break
fi
if [ "$1" = "--" ] ; then
	shift ; break
fi

getopts ":$ckopt_fm" ckopt_optbuf "$@"	# ":a:bc:f:" etc...
if [ "$?" = "1" ] ; then 		# normal end (detect normal args). save general args.
	shift $((OPTIND - 1))
	if [ "$#" -eq "0" ] ; then
		break
	fi
	ckopt_buf="ckopt_ar$ckopt_delcnt"	# ar1, 2...
	eval "$ckopt_buf="'"$1"'	# ar3="$1"
	ckopt_delskip="$ckopt_delskip ""$ckopt_buf""='' ;"
	ckopt_buf='"$'"$ckopt_buf"'"'	# ar1 -> "$ar1"
	ckopt_skip="$ckopt_skip $ckopt_buf"
	ckopt_delcnt=$((ckopt_delcnt + 1 ))
	shift
	OPTIND=1
	continue
fi
# --- your setting

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

# 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. 

ckopt_errmsg="-$OPTARG"
ckopt_err=1
done

# --- grub not optional args & clean
if [ "$#" = "0" ] ; then
	ckopt_buf="set -- $ckopt_skip"
else
	ckopt_buf="set -- $ckopt_skip "'"$@"'
fi
eval "$ckopt_buf"		# set -- "$notopt1" 2 3 ...
eval "$ckopt_delskip"		# notopt1="" ;  2 3 ...

OPTIND="$ckopt_bkind"
OPTERR="$ckopt_bkerr"

ckopt_bkind=""
ckopt_bkerr=""
# ckopt_err=""
# ckopt_errmsg=""
ckopt_delcnt=""
ckopt_skip=""
ckopt_buf=""
ckopt_fm=""

if [ "$ckopt_err" != "0" ] ; then
	OPTARG="$ckopt_errmsg"
	ckopt_errmsg=""
	ckopt_err=""
	test 1 = 0		# for return $?=1
fi
## --generator 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
