#!/usr/local/bin/cbsd
#v19.1.9
CBSDMODULE="jail"
MYARG=""
MYOPTARG="alljails display header jname node order shownode"
MYDESC="List and show status of jails"
[ "${racct}" = "1" ] && . ${distsharedir}/rctl.conf
ADDHELP="
${H3_COLOR}Description${N0_COLOR}:

The script shows the table of environments. You can adjust the content of the column 
through the display= parameter. 
You can limit scope of environment via arg list: 'cbsd jls j1 j2'
Also, you can get list of the environment with specified condition ( by SQL field, e.g.
to show only running jail, you can use: 'cbsd jls status=1'
In addition, you can add your own parameters for the output and external sources 
for this data.

${H3_COLOR}Options${N0_COLOR}:

 ${N2_COLOR}alljails=${N0_COLOR}  - (0 or 1): force to display foreign/remote resources. When 
              sqlreplica=1 and node available, alljails sets to 1 automatically;
 ${N2_COLOR}display=${N0_COLOR}   - comma separated list of columns to display.
              Default value: jid,jname,ip4_addr,host_hostname,path,status
              When sqlrepica and node available:
                nodename,jname,jid,vm_ram,vm_cpus,vm_os_type,ip4_addr,status,vnc_port
              Additional field: real_ip4,etcver
              Default display= setting can be reassigned globally via ~cbsd/jls.conf;
 ${N2_COLOR}header=${N0_COLOR}    - don't print header info;
 ${N2_COLOR}jname=${N0_COLOR}     - limit list by 'jname=' env only, alias for: cbsd jls xx';
 ${N2_COLOR}node=${N0_COLOR}      - list only jails of the specified node;
 ${N2_COLOR}order=${N0_COLOR}     - sort older, 'asc' (default) or 'desc';
 ${N2_COLOR}shownode=1${N0_COLOR} - show node name(s) for listed jails;

When RCTL enabled, additional field available: ${RCTL} ${RCTL_EXTRA}

${H3_COLOR}Examples${N0_COLOR}:

 # cbsd jls
 # cbsd jls myjail1
 # cbsd jls header=0 display=jname,status,ip4_addr,astart
 # cbsd jls display=jname,cpu,vmemoryuse,fsquota,ver,etcver
 # cbsd jls ver=13.1 astart=1

${H3_COLOR}See also${N0_COLOR}:

 cbsd jget --help
 cbsd jrctl --help
 cat ~cbsd/etc/defaults/jls.conf
 https://github.com/cbsd/cbsd/blob/develop/share/docs/general/tag_n_facts.md

"
EXTHELP="wf_jls"
MANPAGE="man cbsd-jls"

. ${subrdir}/nc.subr
. ${strings}
. ${tools}
. ${nodes}

readconf jls.conf
jname=
ojname=
jname_only=
. ${cbsdinit}

[ ! -d "${workdir}" ] && err 1 "${N1_COLOR}jls: no such workdir: ${N2_COLOR}${workdir}${N0_COLOR}"

[ -n "${display}" ] && odisplay="${display}"	# store original display settings
oalljails="${alljails}"				# store original settings, they have more weight vs auto
oshownode="${shownode}"				# store original settings, they have more weight vs auto
ohidden="${hidden}"				# show hidden env?

is_cluster_mode
cluster_mode=$?		# cluster_mode=0 when we have any node

if [ ${cluster_mode} -eq 0 ]; then
	if [ "${oshownode}" != "0" ]; then
		alljails=1
		shownode=1
	fi
fi

# restore manual settings
[ -n "${oalljails}" ] && alljails="${oalljails}"
[ -n "${oshownode}" ] && alljails="${oshownode}"
[ -n "${ohidden}" ] && hidden="${ohidden}"

# defaults
[ -z "${hidden}" ] && hidden=1
show_hidden="${hidden}"
unset hidden
[ -z "${display}" -a -z "${odisplay}" ] && display="jname,jid,ip4_addr,host_hostname,path,status"
[ "${shownode}" = "1" -a -z "${odisplay}" ] && display="nodename,${display}"
[ -z "${order}" ] && order="asc"

#remove commas for loop action on header
mydisplay=$( echo ${display} | ${TR_CMD} ',' '  ' )

# upper for header
myheader=$( echo ${mydisplay} | ${TR_CMD} '[:lower:]' '[:upper:]' )

JLS=""

show_header()
{
	local _header="${H1_COLOR}${BOLD}${myheader}${N0_COLOR}"
	[ ${header} -eq 1 ] && ${ECHO} ${_header}
}

# -j $jname
# -s alternative SQL file
# -u 1 - always show status as "Unregister"
populate_output_data()
{
	local active
	local unregister="0"
	local _node_is_online=0 _md5_node_name _tmp _x

	printf "${N0_COLOR}" # for column sort

	while getopts "j:s:u:" opt; do
		case "${opt}" in
			j) jname="${OPTARG}" ;;
			s) sqlfile="${OPTARG}" ;;
			u) unregister="1" ;;
		esac
		shift $(($OPTIND - 1))
	done

	[ -z "${sqlfile}" ] && sqlfile="local"
	active=$( cbsdsqlro ${sqlfile} "SELECT jid FROM jails WHERE jname='${jname}'" )

	if [ "${sqlfile}" != "local" ]; then
		# pop status variable from node_is_online()
		_md5_node_name=$( ${miscdir}/cbsd_md5 ${sqlfile} )
		eval _node_is_online=\$node_${_md5_node_name}_online
		if [ "${_node_is_online}" = "1" ]; then
			if [ "${active}" != "0" ]; then
				printf "${N2_COLOR}"
			else
				printf "${N4_COLOR}"
			fi
		else
			printf "${N4_COLOR}"		# offline, DGRAY color
		fi
	else
		if [ "${active}" != "0" ]; then
			printf "${N2_COLOR}"
		else
			printf "${N4_COLOR}"
		fi
	fi

	# strip extra spaces + translate to "param1|param2|param3"
	#_rctl_params=$( echo "${RCTL} ${RCTL_EXTRA} " | ${SED_CMD} 's/.$//' | ${TR_CMD} " " "|" )
	#echo "PARA[${_rctl_params}]" >> /tmp/ap

	#populate values for in output string
	for _i in ${mydisplay}; do

		oll=$(( oll + 1 ))
		_val=

		# search for custom column output helper first
		if [ -x "${jailsysdir}/${jname}/facts.d/${_i}" ]; then
			. ${subrdir}/jcreate.subr		# for export_jail_data_for_external_hook
			geniplist ${ip4_addr}			# for ipvX_first_public-like vars
			export_jail_data_for_external_hook
			_val=$( ${jailsysdir}/${jname}/facts.d/${_i} | ${HEAD_CMD} -1 | ${AWK_CMD} '{printf $1}' )
		else
			eval _val=\$$_i
			[ "${unregister}" = "1" -a "${_i}" = "status" ] && _val="Unregister"
			[ -z "${_val}" ] && _val="\-"

			case ${_i} in
				ip4_addr)
					if [ "${_val}" = "\-" ]; then
						_val="0"
					else
						true
						#_val=$( is_special_ip "${_val}" )
					fi
					;;
				real_ip4)
					# real_ip is dynamic/pseudo field,
					# just get it in realtime
					_val=
					if [ ${jid} -gt 0 ]; then
						if [ -x ${path}${IFCONFIG_CMD} ]; then
							_tmp=$( ${JEXEC_CMD} ${jid} ${IFCONFIG_CMD} 2>/dev/null | ${SED_CMD} 's/[^a-zA-Z0-9:\. ]//g' | ${AWK_CMD} '/inet [0-9]+/{print $2}'| while read ip; do
								# skip for localhost
								[ "${ip}" = "127.0.0.1" ] && continue
								# cast 0.0.0.0 to nodeip ?
								#[ "${ip}" = "0.0.0.0" ] && ip="${nodeip}"
								printf "${ip} "
							done )
							# generate comma separated value
							if [ -n "${_tmp}" ]; then
								for _x in ${_tmp}; do
									if [ -z "${_val}" ]; then
										_val="${_x}"
									else
										_val="${_val},${_x}"
									fi
								done
							fi
						fi
					fi
					;;
				etcver)
					# get etcupdate info
					_val=0
					if [ -r ${jailsysdir}/${jname}/etcupdate/ver ]; then
						etcupdate_current_ver=
						. ${jailsysdir}/${jname}/etcupdate/ver
						[ -n "${etcupdate_current_ver}" ] && _val=${etcupdate_current_ver}
					fi
					;;
				datasize|stacksize|coredumpsize|memoryuse|memorylocked|maxproc|openfiles|vmemoryuse|pseudoterminals|swapuse|nthr|msgqqueued|msgqsize|nmsgq|nsem|nsemop|nshm|shmsize|wallclock|pcpu|readbps|writebps|readiops|writeiops|cpu|fsquota|nice|bw)
					# RACCT
					if [ -r ${jailsysdir}/${jname}/helpers/jrctl.sqlite ]; then
						#_tmp=${_i##*rctl_}
						_val=$( cbsdsqlro ${jailsysdir}/${jname}/helpers/jrctl.sqlite "SELECT cur FROM forms WHERE param=\"${_i}\" LIMIT 1"; )
						[ -z "${_val}" ] && _val="\-"
					fi
			esac
		fi
		[ -z "${_val}" ] && _val="\-"
		printf "${_val} "
	done

	printf "${N0_COLOR}\n"
}

# $1 - which file from. Eg: local
show_jaildata_from_sql()
{
	local _i

	#   set sqlfile for ". rcconf" including
	if [ -n "${1}" ]; then
		sqlfile="$1"
	else
		sqlfile="local"
	fi
	# debug query
	#echo "jname,hidden FROM jails WHERE ( emulator=\"jail\" OR emulator=\"qemu-arm-static\" OR emulator=\"qemu-mips64-static\" OR emulator=\"qemu-aarch64-static\" OR emulator=\"qemu-ppc64-static\" OR emulator=\"qemu-riscv64-static\" ) ${additional_condition} ORDER BY jname ${order}" > /tmp/sql
	cbsdsqlro ${sqlfile} SELECT jname,hidden FROM jails WHERE \( emulator=\"jail\" OR emulator=\"qemu-arm-static\" OR emulator=\"qemu-mips64-static\" OR emulator=\"qemu-aarch64-static\" OR emulator=\"qemu-ppc64-static\" OR emulator=\"qemu-riscv64-static\" \) ${additional_condition} ORDER BY jname ${order} | while read jname hidden; do

		if [ -n "${jname_only}" ]; then
			# limited output
			_skip=1
			for i in ${jname_only}; do
				[ "${i}" = "${jname}" ] && _skip=0
			done
			[ ${_skip} -eq 1 ] && continue
		fi

		_status=""
		. ${subrdir}/rcconf.subr

		# skip hidden env when show_hidden=0
		[ ${hidden} -eq 1 -a ${show_hidden} -eq 0 ] && continue

		[ "${baserw}" = "1" ] && path=${data}
		[ "${emulator}" = "bhyve" ] && continue
		[ "${emulator}" = "virtualbox" ] && continue

		conv_status
		populate_output_data -j ${jname} -s ${sqlfile}

	done
}

show_local()
{
	local _errcode _status

	show_header
	show_jaildata_from_sql local

	# Unregister area
	[ ! -d "${jailrcconfdir}" ] && return 0
	ip4_addr="\-"
	host_hostname="\-"
	path="\-"
	jid="0"

	for J in $( ${LS_CMD} ${jailrcconfdir} ); do
		[ "${J}" = "dir.id" ] && continue
		jname=""
		[ ! -r ${jailrcconfdir}/${J} ] && continue
		. ${jailrcconfdir}/${J}
		# for jail only
		[ "${emulator}" != "jail" ] && continue
		[ -z "${jname}" ] && continue
		populate_output_data -u 1 -j ${jname}
		${ECHO} ${_status}
	done
}

show_remote()
{
	show_header

	[ -z "${node}" ] && node=$( cbsdsqlro nodes SELECT nodename FROM nodelist 2>/dev/null | ${XARGS_CMD} )

	for _n in ${node}; do
		nodename="${_n}"
		# init and export into $node_${md5_node_name}_online node status
		node_is_online -n ${nodename} -e 1
		show_jaildata_from_sql ${_n}
	done
}

show_jails()
{
	if [ -n "${node}" ]; then
		show_remote
		exit 0
	fi

	if [ "${alljails}" = "1" ]; then
		show_local
		header=0
		show_remote
	else
		show_local
	fi
}

#### MAIN
[ -z "${header}" ] && header=1
sqldelimer=" "

additional_condition=

# parse args
OIFS="${IFS}"
IFS="~"
cmd="$@"
IFS="${OIFS}"
while [ -n "${1}" ]; do
	IFS="~"
	strpos --str="${1}" --search="="
	_pos=$?
	if [ ${_pos} -eq 0 ]; then
		# not params=value form
		#printf "${1} "         # (printf handles -args (with dashes)
		#echo -n "${1} "
		if [ -z "${jname}" ]; then
			jname="${1}"
		else
			jname="${jname} ${1}"
		fi
		shift
		IFS="${OIFS}"
		continue
	else
		# its patam=value data, skip for existed data
		param=$( substr --pos=0 --len=${_pos} --str="${1}" )
		case "${param}" in
			alljails|display|header|jname|node|order|shownode)
				# known params, skip
				shift
				continue
				;;
			*)
				len=$( strlen "${1}" )
				VAL=$( substr --pos=$(( ${_pos} +2 )) --len=${len} --str="${1}" )
				case "${param}" in
					status)
						case "${VAL}" in
							on|On|ON)
								VAL="1"
								;;
							off|Off|OFF)
								VAL="0"
								;;
						esac
						;;
				esac

				if [ -z "${additional_condition}" ]; then
					additional_condition="AND ${param}=\"${VAL}\""
				else
					additional_condition="${additional_condition} AND ${param}=\"${VAL}\""
				fi
				;;
		esac
	fi
	shift
	IFS="${OIFS}"
done

IFS="${OIFS}"

jname_only="${jname}"

show_jails | ${COLUMN_CMD} -t

exit 0
