#!/usr/local/bin/cbsd
# shellcheck shell=sh disable=2034,2154,1090,2166,3037,2086,1091
#v12.1.7
MYARG=""
MYOPTARG="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.

${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 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=
jname=
ojname=
environment=
oenvironment=
user=
ouser=
. ${cbsdinit}

[ -n "${jname}" ] && ojname="${jname}"
[ -n "${dir}" ] && odir="${dir}"
[ -n "${shell}" ] && oshell="${shell}"
[ -n "${user}" ] && ouser="${ouser}"

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

Makefile="${CBSD_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}${CBSD_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 "${jail_list}" ]; then
	if [ ${cbsd_api} -eq 0 ]; then
		# todo: multiple bexec via all_bhyve_list?
		[ -z "${jname}" ] && err 1 "${N1_COLOR}${CBSD_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 ${*}; 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}${CBSD_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

#		echo "C:[${cmd}]"
#		continue

		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="${cmd}" 2>/dev/null )
		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
	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}${CBSD_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}${CBSD_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} -ne 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=$( /usr/local/cbsd/misc/elf_tables --osname ${path}/bin/bash )
		LOGIN_STR="/bin/bash"
	elif [ -f "${path}/bin/sh" ]; then
		OSNAME=$( /usr/local/cbsd/misc/elf_tables --osname ${path}/bin/sh )
		LOGIN_STR="/bin/sh"
	elif [ -f ${path}/bin/busybox ]; then
		OSNAME=$( /usr/local/cbsd/misc/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} /usr/local/cbsd/misc/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}
