#!/usr/local/bin/cbsd
# shellcheck shell=sh disable=2034,2154,1090,2166,3037,2086,1091
#v12.1.7
CIXARG=""
CIXOPTARG="cmd dir environment jname shell user"
MYDESC="Execute command inside jail"
ADDHELP="
${H3_COLOR}Description${N0_COLOR}:

 Run the command in the container from the master environment via jexec command
 If you are in the CBSDfile directory, these environments will always have priority.

 This script supports the reserved word WHERE, which allows you to write SQL condition,
 please see 'schema jails' in ~cbsd/var/db/local SQLite3 DB for flexible queries.

${H3_COLOR}Options${N0_COLOR}:

 ${N2_COLOR}cmd${N0_COLOR}         - command to execute. Use quotes if there are spaces or several commands;
 ${N2_COLOR}dir${N0_COLOR}         - change current directory in jail before execute ( default: '/' );
 ${N2_COLOR}environment${N0_COLOR} - pass environment, e.g.: 'environment=\"FOO=bar\" environment=\"VAR1=boo\"'
               or path to 'env' file;
 ${N2_COLOR}jname${N0_COLOR}       - target jail. If jail='*' or jail='pri*' then execute command on all
               jails or in jails whose names begin with 'pri', e.g. 'prison1', 'prisonX'...;
 ${N2_COLOR}shell${N0_COLOR}       - shell by default. Default is '/bin/sh';
 ${N2_COLOR}user${N0_COLOR}        - execute a command as another user. Default is 'root';

${H3_COLOR}Examples${N0_COLOR}:

 # cbsd jexec ls -la /bin/ WHERE astart=1 AND ver=15.0
 # cbsd jexec jname=test dir=/tmp pwd
 # cbsd jexec jname=test user=nobody whoami
 # cbsd jexec jname=test <<EOF
pwd
hostname
ls -la
env ASSUME_ALWAYS_YES=yes pkg bootstrap -f
EOF

  # env ASSUME_ALWAYS_YES=yes cbsd jexec jname=test pkg bootstrap -f
  # cbsd jexec jname=test cmd=\"pwd; hostname; env ASSUME_ALWAYS_YES=yes pkg bootstrap -f\"
  # cbsd jexec environment=\"VAR1=var1\" environment=\"VAR2=var2\" jname='*' \"hostname; ls -la\"

${H3_COLOR}See also${N0_COLOR}:

 cbsd jlogin --help

"
CBSDMODULE="jail"
EXTHELP="wf_jexec"

. ${subrdir}/nc.subr
cloud_api=0
dir=
odir=
shell=
oshell=
ojname=
environment=
oenvironment=
user=
ouser=

. ${tools}
cixinit

if [ -z "${jname}" -a -n "${ojname}" ]; then
	# inherit jname env
	jname="${ojname}"
fi

Makefile="${CIX_PWD}/CBSDfile"
if [ -r "${Makefile}" ]; then
	[ -z "${CBSDFILE_RECURSIVE}" ] && ${ECHO} "${N1_COLOR}found CBSDfile: ${N2_COLOR}${Makefile}${N0_COLOR}" 1>&2
	. ${Makefile}
	jail_list=$( ${GREP_CMD} -E '^jail_[a-zA-Z0-9_@%:][-a-zA-Z0-9_@%:]*\(\)$' ${Makefile} | ${XARGS_CMD} | ${TR_CMD} -d "()" | ${SED_CMD} s#jail_##g )
	[ -z "${jail_list}" ] && err 1 "${N1_COLOR}${CIX_APP}: no jail found${N0_COLOR}"
	[ -z "${jname}" ] && jname="${jail_list}"

	if [ -n "${CLOUD_URL}" -a -n "${CLOUD_KEY}" ]; then
		cbsd_api=1
	else
		cbsd_api=0
	fi
else
	cbsd_api=0
fi

if [ -z "${jname}" -a -n "${CIXINIT_SQL_CONDITION}" ]; then
	cbsdsqlro_vars local "SELECT jname FROM jails WHERE ${CIXINIT_SQL_CONDITION}" jail_list
fi

if [ -z "${jail_list}" ]; then
	if [ ${cbsd_api} -eq 0 ]; then
		# todo: multiple bexec via all_bhyve_list?
		[ -z "${jname}" ] && err 1 "${N1_COLOR}${CIX_APP}: give me jname${N0_COLOR}"
		#shift  # todo: jname and cmd may have reverse order
	fi
fi

if [ -n "${ojname}" ]; then
	jname="${ojname}"
	unset jail_list
fi

xenvironment=
if [ -z "${cmd}" ]; then
	[ -n "${odir}" ] && dir="${odir}"
	if [ -n "${dir}" ]; then
		cmd0="cd ${dir}"
	fi

	cmd=
	OIFS="${IFS}"
	IFS="~"
	for i in ${CIX_OTHER_ARGS}; do
		strpos --str="${i}" --search="="
		_pos=$?
		if [ ${_pos} -eq 0 ]; then
			# not params=value form
			if [ -z "${cmd}" ]; then
				cmd="${i}"
			else
				cmd="${cmd} ${i}"
			fi
			shift
			continue
		fi

		_arg_len=$( strlen ${i} )
		_pref=$(( _arg_len - _pos ))
		ARG=$( substr --pos=0 --len=${_pos} --str="${i}" )

		case "${ARG}" in
			environment)
				VAL=$( substr --pos=$(( _pos + 2 )) --len=${_pref} --str="${i}" | ${TR_CMD} -d '"' )
				if [ -z "${xenvironment}" ]; then
					xenvironment="${VAL}"
				else
					xenvironment="${xenvironment} ${VAL}"
				fi
				shift
				continue
				;;
			dir|jname|shell|user)
				shift
				continue
				;;
		esac

		if [ -z "${cmd}" ]; then
			cmd="${i}"
		else
			cmd="${cmd} ${i}"
		fi

	done
	IFS="${OIFS}"
fi

emulator="jail"		# for jname_is_multiple
[ -z "${jail_list}" ] && jname_is_multiple	# import jail_list if jname is mask

_global_ret=0

[ -n "${odir}" ] && dir="${odir}"
if [ -z "${cmd}" ]; then
	batchfile=$( ${MKTEMP_CMD} )
	# todo: test/chec for stdin/blocked
	# shellcheck disable=2162
	while read _line; do
		[ -n "${dir}" ] && echo "cd ${dir}" >> ${batchfile}
		echo "${_line}" >> ${batchfile}
	done

	# set permission for all users due to 'user=' args
	${CHMOD_CMD} 0555 ${batchfile}

	[ -n "${ouser}" ] && user="${ouser}"
	[ -z "${user}" ] && user="root"
	[ -n "${oshell}" ] && shell="${oshell}"
	[ -z "${shell}" ] && shell="/bin/sh"

	if [ -n "${jail_list}" ]; then
		for jname in ${jail_list}; do
			jscp ${batchfile} ${jname}:${batchfile}
			jexec jname=${jname} user=${user} shell=${shell} ${batchfile}
			_global_ret=$?
		done
	else
		jscp ${batchfile} ${jname}:${batchfile}
		jexec jname=${jname} user=${user} shell=${shell} ${batchfile}
		_global_ret=$?
	fi
	${RM_CMD} -f ${batchfile}
	#err 1 "${N1_COLOR}${CIX_APP}: empty command${N0_COLOR}"
	exit ${_global_ret}
fi

if [ -n "${dir}" ]; then
	cmd="cd ${dir}; ${cmd}"
fi

if [ -n "${jail_list}" ]; then
	. ${subrdir}/multiple.subr

	${ECHO} "${N1_COLOR}Hint: Press ${N2_COLOR}'Ctrl+t'${N1_COLOR} to see last logfile line for active task${N0_COLOR}" 1>&2
	task_owner="jexec_multiple"

	task_id=
	task_id_cur=

	[ -n "${ouser}" ] && user="${ouser}"
	[ -z "${user}" ] && user="root"
	[ -n "${oshell}" ] && shell="${oshell}"
	[ -z "${shell}" ] && shell="/bin/sh"

	# spawn command for all jail
	for jname in ${jail_list}; do
		. ${subrdir}/rcconf.subr
		[ ${myjid} -eq 0 ] && continue

		capture task_id_cur task mode=new logfile=${tmpdir}/${task_owner}.${jname}.log.$$ client_id=${jname} autoflush=0 owner=${task_owner} ${ENV_CMD} NOCOLOR=1 /usr/local/bin/cbsd jexec dir="${dir}" user=${user} shell=${shell} jname=${jname} ${cmd}
		task_id="${task_id} ${task_id_cur}"
	done

	multiple_task_id_all=$( echo ${task_id} | ${TR_CMD} " " "," )
	sleep 1
	multiple_processing_spawn -o ${task_owner} -n "jexec"
	echo
	exit 0
fi

if [ ${cbsd_api} -eq 1 ]; then
	[ -z "${CURL_CMD}" ] && capture CURL_CMD which curl
	[ -z "${CURL_CMD}" ] && err 1 "${N1_COLOR}cloud up requires curl, please install: ${N2_COLOR}pkg install -y curl${N0_COLOR}"
	[ -z "${CBSDFILE_RECURSIVE}" ] && ${ECHO} "${N1_COLOR}main cloud api: ${N2_COLOR}${CLOUD_URL}${N0_COLOR}" 1>&2
	_cid=$( ${miscdir}/cbsd_md5 "${CLOUD_KEY}" )

	# restore scope in jname=XXX specified
	[ -n "${ojname}" ] && all_jail_list="${ojname}"

	for jname in ${all_jail_list}; do
		_ssh=$( ${CURL_CMD} --no-progress-meter -H "cid:${_cid}" ${CLOUD_URL}/api/v1/status/${jname} 2>&1 )
		_ret=$?
		if [ ${_ret} -ne 0 ]; then
			${ECHO} "${N1_COLOR}${CIX_APP} error: curl error1: ${N2_COLOR}${_ssh}${N0_COLOR}"
			${ECHO} "${CURL_CMD} --no-progress-meter -H \"cid:XXXXX\" ${CLOUD_URL}/api/v1/status/${jname}"
			_global_ret=$(( _global_ret + 1 ))
			continue
		fi

		_ssh_string=$( echo "${_ssh}" | ${JQ_CMD} '.ssh_string' | ${TR_CMD} -d '"' )
		_ssh_pref=$( substr --pos=0 --len=3 --str="${_ssh_string}" )
		if [ "${_ssh_pref}" != "ssh" ]; then
			${ECHO} "${N1_COLOR}${CIX_APP} error: curl error2, no ssh_string:\n${N2_COLOR}${_ssh}${N0_COLOR}"
			${ECHO} "${CURL_CMD} --no-progress-meter -H \"cid:XXXXX\" ${CLOUD_URL}/api/v1/status/${jname}"
			_global_ret=$(( _global_ret + 1 ))
			continue
		fi
		_ssh_len=$( strlen "${_ssh_string}" )
		_ssh_post=$( substr --pos=5 --len=${_ssh_len} --str="${_ssh_string}" )
		#echo "${SSH_CMD} ${_ssh_post}"
		# rewrite
		if [ -n "${SUDO_USER}" ]; then
			if [ -r /home/${SUDO_USER}/.ssh/id_ed25519 ]; then
				_ssh_sudo_arg="-oIdentityFile=/home/${SUDO_USER}/.ssh/id_ed25519"
			elif [ -r /home/${SUDO_USER}/.ssh/id_rsa ]; then
				_ssh_sudo_arg="-oIdentityFile=/home/${SUDO_USER}/.ssh/id_rsa"
			else
				date
			fi
		fi
		jexec_cmd="${SSH_CMD} -T -oStrictHostKeyChecking=no -oBatchMode=yes -oConnectTimeout=5 -oServerAliveInterval=10 ${_ssh_sudo_arg} ${_ssh_post}"

		echo "[jexec debug]: ${jexec_cmd}"
		${jexec_cmd} <<CBSD_EOF
${cmd}
CBSD_EOF
	done
	exit ${_global_ret}
else
	. ${subrdir}/rcconf.subr
	[ $? -eq 1 ] && err 1 "${N1_COLOR}no such jail: ${N2_COLOR}${jname}${N0_COLOR}"
	[ "${emulator}" = "bhyve" ] && err 1 "${N1_COLOR}Not for bhyve mode${N0_COLOR}"

	[ ${jid} -eq 0 ] && err 1 "Not running"
	#rctl/limits area
	. ${subrdir}/rctl.subr
	[ -z "${nice}" ] && nice="0"

	 _formfile="${jailsysdir}/${jname}/helpers/jrctl.sqlite"
	[ -r "${_formfile}" ] && nice=$( cbsdsqlro ${_formfile} "SELECT cur FROM forms WHERE param='nice'" )
	[ -z "${nice}" ] && nice="0"

	if [ ${exec_fib} -eq 0 ]; then
		SETFIB=""
	else
		SETFIB="${SETFIB_CMD} ${exec_fib}"
	fi

	if [ "${cpuset}" = "0" ]; then
		CPUSET=""
	else
		CPUSET="${CPUSET_CMD} -c -l ${cpuset}"
	fi

	[ ${baserw} -eq 1 ] && path="${data}"

	# is linux?
	if [ -f "${path}/bin/bash" ]; then
		OSNAME=$( ${miscdir}/elf_tables --osname ${path}/bin/bash )
		LOGIN_STR="/bin/bash"
	elif [ -f "${path}/bin/sh" ]; then
		OSNAME=$( ${miscdir}/elf_tables --osname ${path}/bin/sh )
		LOGIN_STR="/bin/sh"
	elif [ -f ${path}/bin/busybox ]; then
		OSNAME=$( ${miscdir}/elf_tables --osname ${path}/bin/busybox )
		LOGIN_STR="/bin/sh"
	else
		err 1 "${N1_COLOR}Unknown environment, unable to jexec: ${path}${N0_COLOR}"
	fi

	case "${OSNAME}" in
		freebsd)
			[ -n "${ouser}" ] && user="${ouser}"
			[ -z "${user}" ] && user="root"
			if [ "${emulator}" != "jail" -a -n "${emulator}" ]; then
				. ${subrdir}/emulator.subr
				init_usermode_emul
				# inherit emulator_flags
				LOGIN_STR="/bin/${emulator} ${LOGIN_CMD} -f ${user}"
			else
				LOGIN_STR=""
			fi
			;;
		*)
			true
			;;
	esac

	first_cmd=
	for first_cmd in ${cmd}; do
		break
	done

	if [ -x "${jailsysdir}/${jname}/cmd/${first_cmd}" ]; then
		. ${subrdir}/jcreate.subr		# for export_jail_data_for_external_hook
		export_jail_data_for_external_hook
		echo "[debug] external command: ${first_cmd}" 1>&2
		exec ${jailsysdir}/${jname}/cmd/${cmd}
	fi

	[ -n "${ouser}" ] && user="${ouser}"
	[ -n "${odir}" ] && dir="${odir}"
	[ -n "${oshell}" ] && shell="${oshell}"
	[ -z "${user}" ] && user="root"
	[ -z "${dir}" ] && dir="/"
	[ -z "${shell}" ] && shell="/bin/sh"

	if [ -z "${LOGIN_STR}" ]; then
		if [ "${platform}" = "DragonFly" ]; then
			# shellcheck disable=2153
			exec ${NICE_CMD} -n ${nice} ${JEXEC_CMD} ${jid} /bin/sh -c "${cmd}"
			ret=$?
		elif [ "${ver}" = "empty" ]; then
			# non-FreeBSD env, (-U) + $user not supported ?
			exec ${NICE_CMD} -n ${nice} ${SETFIB} ${CPUSET} ${JEXEC_CMD} ${jid} /bin/sh -c "${cmd}"
			ret=$?
		else
			# old behavior
			#exec ${NICE_CMD} -n ${nice} ${SETFIB} ${CPUSET} ${JEXEC_CMD} -U ${user} ${jid} /bin/sh -c "${cmd}"
			# with exec
			exec ${NICE_CMD} -n ${nice} ${SETFIB} ${CPUSET} ${miscdir}/jexec_env ${jname} ${user} ${dir} ${shell} "${cmd}"
			ret=$?
		fi
	else
		if [ "${platform}" = "DragonFly" ]; then
			exec ${NICE_CMD} -n ${nice} ${SETFIB} ${CPUSET} ${JEXEC_CMD} ${jid} ${LOGIN_STR} <<CBSD_EOF
${cmd}
CBSD_EOF
			ret=$?
		elif [ "${ver}" = "empty" -o "${OSNAME}" != "freebsd" ]; then
			# non-FreeBSD env, (-U) + $user not supported ?
			exec ${NICE_CMD} -n ${nice} ${SETFIB} ${CPUSET} ${JEXEC_CMD} ${jid} ${LOGIN_STR} <<CBSD_EOF
${cmd}
CBSD_EOF
			ret=$?
		else
			exec ${NICE_CMD} -n ${nice} ${SETFIB} ${CPUSET} ${JEXEC_CMD} -U ${user} ${jid} ${LOGIN_STR} <<CBSD_EOF
${cmd}
CBSD_EOF
			ret=$?
		fi
	fi
fi

exit ${ret}
