#!/usr/local/bin/cbsd
# shellcheck shell=sh disable=2034,2154,1090,2166,3037,2086,1091
#v12.1.7
MYARG=""
MYOPTARG="cmd dir jname user"
MYDESC="Execution for 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}dir${N0_COLOR}   - change current directory in jail before execute.
 ${N2_COLOR}cmd${N0_COLOR}   - command to execute. Use quotes if there are spaces or several commands.
 ${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}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 <<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 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=
ojname=
. ${cbsdinit}

[ -n "${jname}" ] && ojname="${jname}"

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

if [ -n "${user}" ]; then
	shift
else
	user="root"
fi

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

	OIFS="${IFS}"
	IFS="~"
	cmd=$( while [ -n "${1}" ]; do
		IFS="~"
		strpos --str="${1}" --search="="
		_pos=$?
		if [ ${_pos} -eq 0 ]; then
			# not params=value form
			echo -n "${1} "
			shift
			continue
		fi

		_arg_len=$( strlen ${1} )
		_pref=$(( _arg_len - _pos ))
		ARG=$( substr --pos=0 --len=${_pos} --str="${1}" )
		VAL=$( substr --pos=$(( _pos + 2 )) --len=${_pref} --str="${1}" )
		case "${ARG}" in
			dir|jname)
				shift
				continue
				;;
		esac
		printf "%s='%s' " "${ARG}" "${VAL}"
		shift
	done )

	# strip extra space
	cmd=$( echo ${cmd} | ${SED_CMD} 's/.$//' )
	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

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

	if [ -n "${jail_list}" ]; then
		for jname in ${jail_list}; do
			jscp ${batchfile} ${jname}:${batchfile}
			jexec jname=${jname} /bin/sh ${batchfile}
			_global_ret=$?
		done
	else
		jscp ${batchfile} ${jname}:${batchfile}
		jexec jname=${jname} /bin/sh ${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=

	# 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}" 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 )
	JQ_CMD=$( which jq )
	[ -z "${CURL_CMD}" ] && err 1 "${N1_COLOR}cloud up requires curl, please install: ${N2_COLOR}pkg install -y curl${N0_COLOR}"
	[ -z "${JQ_CMD}" ] && err 1 "${N1_COLOR}cloud up requires jq, please install: ${N2_COLOR}pkg install -y textproc/jq${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}"
			if [ "${user}" != "root" ]; then
				# additional check for user existance
				_res=$( ${PW_CMD} -R ${path} usershow ${user} 2>&1 ) || err 1 "${N1_COLOR}Unable to find user: ${_res}${N0_COLOR}"
			fi

			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

	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
			exec ${NICE_CMD} -n ${nice} ${SETFIB} ${CPUSET} ${JEXEC_CMD} -U ${user} ${jid} /bin/sh -c "${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}
