#!/usr/local/bin/cbsd
#v11.1.17
globalconf="${distdir}/cbsd.conf";
MYARG=""
MYOPTARG="compress dstdir genmd5 gensize header_extra imgname jexport_exclude jname ls threads"
MYDESC="Export jail into image"
CBSDMODULE="jail"
ADDHELP="
${H3_COLOR}Description${N0_COLOR}:

Export jail into file (*.img). In jname arguments you can set jail for export.
img-file stored in \$workdir/export directory. Original jail after exports is not modified.

You can control compress level via 'compress' arguments. CBSD use xz(1), tools for compress 
images and you can learn in man page about compress diffrence between compress level.

By default CBSD use compress=6. You can disable compression with compress=0.

# Exceptions for jexport

There are situations where you do not want to include one or another information from the container in the exported image. 
For example, if you are exporting a working container having a mounted port of ports in /usr/ports.
In this case, you can help the jexport_exclude parameter, which can be specified globally in the jexport.conf configuration file 
(just copy default jexport.conf from /usr/local/cbsd/etc/defaults/ to ~cbsd/etc/ and adjust the value for jexport_exclude.

If you want to specify alternative exclude for a specific Jail, copy this file to the ~cbsd/jails-system/\$jname/etc/ directory. 
In this case, when you make an jexport call, these exceptions will be applied for $jname container only.

Finally, you can do exclude without a configuration file at all, just listing all exceptions (space separated) 
as the jexport_exclude= parameter when calling the jexport script.

${H3_COLOR}Options${N0_COLOR}:

 ${N2_COLOR}compress${N0_COLOR}        - XZ compress level ( 0 - 9 ). Default is: 6. 0 mean is compression disabled;
 ${N2_COLOR}dstdir${N0_COLOR}          - alternative export dir ( by default - $workdir/exports );
 ${N2_COLOR}genmd5${N0_COLOR}          - generate $imgname.md5 file with md5sum of image;
 ${N2_COLOR}gensize${N0_COLOR}         - generate $imgname.size file with size of image in bytes;
 ${N2_COLOR}header_extra${N0_COLOR}    - can be as header_extra=\"test1=4,param=val\" or path to file. set extra/custom header data;
 ${N2_COLOR}imgname${N0_COLOR}         - alternative image dir ( by default - $jname.img;
 ${N2_COLOR}jexport_exclude${N0_COLOR} - skip/exclude path in jail when export;
 ${N2_COLOR}jname${N0_COLOR}           - environment name;
 ${N2_COLOR}ls${N0_COLOR}              - filter for list: bls or jls;
 ${N2_COLOR}threads${N0_COLOR}         - xz-specific: the number of worker threads to use, default - 0 (inherits CPU cores num);

${H3_COLOR}Examples${N0_COLOR}:

  cbsd jexport jname=test jexport_exclude=\"/tmp/* /usr/ports /var/run/*\"      (exclude /tmp and /usr/ports in image)
  cbsd jexport jname=test jexport_exclude=\"/\"                                 (export settings only, without data!)
  cbsd jexport jname=test compress=0 threads=1                                  (tar only, without compression)

${H3_COLOR}See also${N0_COLOR}:

 cbsd jimport --help
 cbsd jclone --help
 cbsd jrclone --help
 cbsd zfs-migrator --help

"
EXTHELP="wf_jexport"

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

# generate md5 file?
genmd5=0
# generate size file?
gensize=0

header_extra=
jexport_exclude=
ojexport_exclude=
threads=
othreads=
. ${cbsdinit}
[ -n "${jexport_exclude}" ] && ojexport_exclude="${jexport_exclude}"
[ -n "${threads}" ] && othreads="${threads}"
[ -z "${threads}" ] && threads=0

if [ "${mod_cbsd_queue_enabled}" = "YES" -a -z "${MOD_CBSD_QUEUE_DISABLED}" ]; then
	readconf cbsd_queue.conf
	[ -z "${cbsd_queue_backend}" ] && MOD_CBSD_QUEUE_DISABLED="1"
fi

jexport_me()
{
	local _ret
	local _filestats _header_extra_opt
	local _sys_exclude _jexport_exclude_args

	${ECHO} "${N1_COLOR}Exporting (with compress level:${N2_COLOR}${compress}${N1_COLOR}), please stand by: ${N2_COLOR}${jname}${N0_COLOR}"
	local DEST="${dstdir}/${imgname}"
	local JAILRCCONF="${jailsysdir}/${jname}/rc.conf_${jname}"

	# check for already existance and offline
	if [ ! -d "${data}" ]; then
		${ECHO} "${N1_COLOR}No jail data for ${jname}: ${N2_COLOR}${data}${N0_COLOR}"
		return 1
	fi

	_filestats=$( ${MKTEMP_CMD} )

	trap "${RM_CMD} -f ${_filestats}" HUP INT ABRT BUS TERM EXIT

SPACER="___NCSTART_HEADER=1_ \
___NCSTART_RCCONF=1 \
___NCSTART_FSTAB=1 \
___NCSTART_PKGINFO=1 \
___NCSTART_DESCR=1 \
___NCSTART_INFO=1 \
___NCSTART_LOCALFSTAB=1
___NCSTART_SYSDATA=1 \
___NCSTART_DATA=1 \
"

	for _part in ${SPACER}; do
		case ${_part} in
			___NCSTART_HEADER*)
				# here we have a historical conflict where the bhyve stores some CI-related information in the same file (JAILRCCONF)
				# save original ci_\* settings as ${JAILRCCONF}.orig and restore when import, as temporary workaround.
				# todo: storage for CI-related settings, in SQL?
				# workaround for #603: https://github.com/cbsd/cbsd/issues/603
				[ -r ${JAILRCCONF} ] && ${GREP_CMD} ^ci_ ${JAILRCCONF} > ${JAILRCCONF}.orig
				jmkrcconf jname=${jname} > ${JAILRCCONF}
				[ -n "${header_extra}" ] && _header_extra_opt="header_extra=\"${header_extra}\""
				res=$( imgpart mode=pack jname=${jname} compress=${compress} part=header emulator=${emulator} out=${DEST} threads=${threads} ${_header_extra_opt} )
				_ret=$?
				${RM_CMD} -f ${JAILRCCONF}
				[ ${_ret} -ne 0 ] && err 1 "${N1_COLOR}Error in ${_part}: ${res}${N0_COLOR}"
				;;
			___NCSTART_RCCONF*)
				#currenlty we import/export operation for rc.conf via ascii format
				#cause diffrence node can have version of cbsd with incompattible jail schema
				#so create rc.conf for pack operation
				jmkrcconf jname=${jname} > ${JAILRCCONF}
				res=$( replacewdir file0="${JAILRCCONF}" old=${workdir} new="CBSDROOT" )
				_ret=$?
				if [ ${_ret} -ne 0 ]; then
					${RM_CMD} -f ${JAILRCCONF}
					err 1 "${N1_COLOR}Error in ${_part} replacewdir: ${res}${N0_COLOR}"
				fi
				res=$( imgpart mode=pack jname=${jname} part=rcconf out=${DEST} threads=${threads} )
				_ret=$?
				${RM_CMD} -f ${JAILRCCONF}
				[ ${_ret} -ne 0 ] && err 1 "${N1_COLOR}Error in ${_part}: ${res}${N0_COLOR}"
				;;
			___NCSTART_PKGINFO*)
				res=$( imgpart mode=pack jname=${jname} part=pkginfo out=${DEST} threads=${threads} )
				[ $? -ne 0 ] && err 1 "${N1_COLOR}Error in ${_part}: ${res}${N0_COLOR}"
				;;
			___NCSTART_DESCR*)
				res=$( imgpart mode=pack jname=${jname} part=descr out=${DEST} threads=${threads} )
				[ $? -ne 0 ] && err 1 "${N1_COLOR}Error in ${_part}: ${res}${N0_COLOR}"
				;;
			___NCSTART_INFO*)
				imgpart mode=pack jname=${jname} part=info out=${DEST} threads=${threads}
				[ $? -ne 0 ] && err 1 "${N1_COLOR}Error in ${_part}: ${res}${N0_COLOR}"
				;;
			___NCSTART_FSTAB*)
				TMPFSTAB="${ftmpdir}/fstab.$$"
				[ ! -d ${jailfstabdir}/${jname} ] && ${MKDIR_CMD} ${jailfstabdir}/${jname}
				[ ! -f "${mount_fstab}" ] && ${TOUCH_CMD} ${mount_fstab}
				${CP_CMD} ${mount_fstab} ${TMPFSTAB}
				res=$( replacewdir file0="${mount_fstab}" old=${workdir} new="CBSDROOT" )
				[ $? -ne 0 ] && err 1 "${N1_COLOR}Error in ${_part} replacewdir: ${res}${N0_COLOR}"
				res=$( imgpart mode=pack jname=${jname} part=fstab out=${DEST} threads=${threads} )
				[ $? -ne 0 ] && err 1 "${N1_COLOR}Error in ${_part}: ${res}${N0_COLOR}"
				${MV_CMD} ${TMPFSTAB} ${mount_fstab}
				;;
			___NCSTART_LOCALFSTAB*)
				# localfstab is not mandatory file. Errcode is always 0
				if [ -r ${mount_fstab}.local ]; then
					res=$( replacewdir file0="${mount_fstab}.local" old=${workdir} new="CBSDROOT" )
					[ $? -ne 0 ] && err 1 "${N1_COLOR}Error in ${_part} replacewdir: ${res}${N0_COLOR}"
				fi
				res=$( imgpart mode=pack jname=${jname} part=localfstab out=${DEST} threads=${threads} || true )
				[ $? -ne 0 ] && err 1 "${N1_COLOR}Error in ${_part}: ${res}${N0_COLOR}"
				;;
			___NCSTART_SYSDATA*)
				res=$( imgpart mode=pack jname=${jname} part=sysdata out=${DEST} threads=${threads} )
				[ $? -ne 0 ] && err 1 "${N1_COLOR}Error in ${_part}: ${res}${N0_COLOR}"
				;;
			___NCSTART_DATA*)
				#test for zfs mounted & mount if not
				_ebytes=0

				case "${emulator}" in
					jail)
						readconf jexport.conf
						[ -n "${othreads}" ] && threads="${othreads}"
						_sys_exclude=
						[ -n "${ojexport_exclude}" ] && jexport_exclude="${ojexport_exclude}"
						if [ -n "${jexport_exclude}" ]; then
							${ECHO} "${W1_COLOR}${CBSD_APP} warning: ${N1_COLOR}will be excluded by the jexport_exclude settings: ${N1_COLOR}"
							for i in ${jexport_exclude}; do
								${ECHO} "  ${N2_COLOR}* ${i}${N0_COLOR}"
								if [ -n "${_sys_exclude}" ]; then
									_sys_exclude="${_sys_exclude} ${i}"
								else
									_sys_exclude="${i}"
								fi
							done
							_jexport_exclude_args="jexport_exclude=\"${_sys_exclude}\""
						fi
						;;
				esac

				case ${zfsfeat} in
					1)
						. ${subrdir}/zfs.subr
						zfsmnt ${data}
						if [ $? -eq 2 ]; then
							WASNOMOUNT=1
						else
							WASNOMOUNT=0
						fi
						[ ${WASNOMOUNT} -eq 1 ] && ${ZFS_CMD} mount "${ZPOOL}"
						_ebytes=$( ${ZFS_CMD} get -Hpo value used ${ZPOOL} )
						;;
					0)
						case "${emulator}" in
							xen|bhyve)
								# for bhyve/xen we can calculate disk size by stat
								_ebytes=$( ${FIND_CMD} ${jaildatadir}/${jname}-data/ -mindepth 1 -maxdepth 1 -type f | while read _myfile; do
									_tmp=$( ${STAT_CMD} -f %z ${_myfile} )
									_ebytes=$(( _size + _tmp ))
									echo ${_ebytes}
									done | ${TAIL_CMD} -n1 )
								;;
							*)
								;;
						esac
						;;
				esac
				jmkrcconf jname=${jname} > ${JAILRCCONF}
				printf "${H3_COLOR}"
				res=$( imgpart mode=pack jname=${jname} compress=${compress} part=data out=${DEST} filestats=${_filestats} ebytes=${_ebytes} threads=${threads} ${_jexport_exclude_args} )
				_ret=$?
				printf "${N0_COLOR}"
				[ ${_ret} -ne 0 ] && err 1 "${N1_COLOR}Error in ${_part}: ${res}${N0_COLOR}"
				${RM_CMD} -f ${JAILRCCONF}
				[ "${WASNOMOUNT}" = "1" ] && ${ZFS_CMD} unmount "${ZPOOL}"
				;;
		esac
	done

	# Summary stat size
	if [ -s "${DEST}" ]; then
		imgfile=$( ${BASENAME_CMD} ${DEST} )
		img_bsize=$( get_file_bytes ${DEST} 2>/dev/null )

		if [ ${gensize} -eq 1 ]; then
			echo ${img_bsize} > ${DEST}.size
		fi

		if [ ${genmd5} -eq 1 ]; then
			${CAT_CMD} ${DEST} | ${miscdir}/cbsd_md5 > ${DEST}.md5
		fi

		if conv2human "${img_bsize}"; then
			img_size=${convval}
		fi

		img_flat_size=0
		if [ -r ${_filestats} ]; then
			. ${_filestats}
		fi

		if conv2human "${img_flat_size}"; then
			img_flat_size=${convval}
		fi

		${ECHO} "${N1_COLOR}environment flat size: ${N2_COLOR}${img_flat_size}${N1_COLOR}, images size ${imgfile}: ${N2_COLOR}${img_size}${N0_COLOR}"
		${ECHO} "${N1_COLOR}exported image file: ${N2_COLOR}${DEST}${N0_COLOR}"

		return 0
	else
		${ECHO} "${N1_COLOR}Unknown error in jexport${N0_COLOR}"
		return 1
	fi
}

[ -z "${compress}" ] && compress=6
[ -z "${dstdir}" ] && dstdir="${exportdir}"
[ ! -d "${dstdir}" ] && ${MKDIR_CMD} -p ${dstdir}

if [ -z "${ls}" ]; then
	ls="jls"
else
	shift
fi

[ -z "${jname}" -a -n "${1}" ] && jname="${1}"

if [ -n "${jname}" ]; then

	case "${ls}" in
		bls)
			emulator="bhyve"	# for jname_is_multiple
			;;
		*)
			emulator="jail"		# for jname_is_multiple
			;;
	esac
	jname_is_multiple

	if [ -n "${jail_list}" ]; then
		# recursion for masked jails
		for i in ${jail_list}; do
			jexport jname=${i} compress=${compress}
		done
		exit 0
	fi
else
	select_jail_by_list -s "List of all jails" -e ${ls}
	[ -z "${jname}" ] && err 1 "${N1_COLOR}Give me jname${N0_COLOR}"
fi

if [ -z "${imgname}" ]; then
	imgname="${jname}.img"
else
	imgname="${imgname}.img"
fi

. ${subrdir}/rcconf.subr

[ $? -eq 1 ] && err 1 "${N1_COLOR}No such jail: ${N2_COLOR}${jname}${N0_COLOR}"

if [ ${jid} -ne 0 -a "${emulator}" = "bhyve" ]; then
	err 1 "${N1_COLOR}VM is online${N0_COLOR}"
fi

# CBSD QUEUE
if [ "${mod_cbsd_queue_enabled}" = "YES" -a -z "${MOD_CBSD_QUEUE_DISABLED}" ]; then

	case "${emulator}" in
		jail)
			[ -n "${cbsd_jail_queue_name}" ] && ${cbsd_queue_backend} cbsd_queue_name=${cbsd_jail_queue_name} id=${jname} cmd=jexport status=1
			;;
		bhyve)
			[ -n "${cbsd_bhyve_queue_name}" ] && ${cbsd_queue_backend} cbsd_queue_name=${cbsd_bhyve_queue_name} id=${jname} cmd=jexport status=1
			;;
	esac

	${cbsd_queue_backend} cbsd_queue_name=${cbsd_import_queue_name} cmd=message msg="{\"id\":\"${jname}.img\",\"cmd\":\"jexport\",\"status\":\"1\",\"data\":{\"status\":\"1\",\"node\":\"local\",\"jname\":\"${jname}.img\",\"impsize\":\"0\",\"imptype\":\"${emulator}\"}}"
	${cbsd_queue_backend} cbsd_queue_name=${cbsd_import_queue_name} cmd=message msg="{\"cmd\":\"tooltip\",\"type\":\"information\",\"timeout\":10000,\"author\":\"Export\",\"msg\":\"${jname}#export#started...\"}"

fi

. ${subrdir}/time.subr
st_time=$( ${DATE_CMD} +%s )

jexport_me
ret=$?

# CBSD QUEUE
if [ "${mod_cbsd_queue_enabled}" = "YES" -a -z "${MOD_CBSD_QUEUE_DISABLED}" ]; then

	case "${emulator}" in
		jail)
			[ -n "${cbsd_jail_queue_name}" ] && ${cbsd_queue_backend} cbsd_queue_name=${cbsd_jail_queue_name} id=${jname} cmd=jexport status=2
			;;
		bhyve)
			[ -n "${cbsd_bhyve_queue_name}" ] && ${cbsd_queue_backend} cbsd_queue_name=${cbsd_bhyve_queue_name} id=${jname} cmd=jexport status=2
			;;
	esac

	${cbsd_queue_backend} cbsd_queue_name=${cbsd_import_queue_name} id=${jname}.img cmd=update impsize=${img_bsize} status=1
	${cbsd_queue_backend} cbsd_queue_name=${cbsd_import_queue_name} cmd=message msg="{\"id\":\"${jname}.img\",\"cmd\":\"jexport\",\"status\":\"2\",\"data\":{\"status\":\"2\",\"node\":\"local\",\"jname\":\"${jname}.img\",\"impsize\":\"${img_bsize}\",\"imptype\":\"${emulator}\"}}"
	tmp_img_size=$( echo ${img_size} | ${TR_CMD} ' ' '#' )
	${cbsd_queue_backend} cbsd_queue_name=${cbsd_import_queue_name} cmd=message msg="{\"cmd\":\"tooltip\",\"type\":\"success\",\"timeout\":10000,\"author\":\"Export\",\"msg\":\"${jname}#export#completed:${tmp_img_size}\"}"
fi

end_time=$( ${DATE_CMD} +%s )
diff_time=$(( end_time - st_time ))
diff_time=$( displaytime ${diff_time} )
${ECHO} "${N1_COLOR}${CBSD_APP} done ${N2_COLOR}in ${diff_time}${N0_COLOR}"

exit ${ret}
