#!/usr/local/bin/cbsd
#v12.0.14
MYARG="mode"
MYOPTARG="jname dsk_controller dsk_path dsk_size bootable display header zvol_opts"
MYDESC="Manage XEN disks"
CBSDMODULE="xen"
EXTHELP="wf_xen"
ADDHELP="mode=attach,detach,remove,list,modify\n\
  - attach - create and attach new disk\n\
  - detach - detach disk from controller (without removing dsk)\n\
  - delete|remove - detach and remove disk\n\
  - modify - modify disk properties (e.g: bootable=, dsk_size=, ..)\n\
  - get_next_free_dsk - out next available dsk name for jname\n\
 modify properties:\n\
  - bootable - set bootable flag (1 - true, - false)\n\
  - dsk_zfs_guid - can be: 'auto'\n\
  - dsk_size - increase disk size (e.g: 20g, +30g )\n\
 mode=attach properties:\n\
  zvol_opts - pass ZFS volume options, e.g: zvol_opts=\"dedup=on sync=disabled\"\n\
 mode=list properties:\n\
  header=0 don't print header\n\
  display= list by comma for column. Default: jname,dsk_controller,dsk_path,dsk_size,bootable,dsk_iops_limit,dsk_mbps_limit\n\
    also available: dsk_zfs_guid\n\
  mode=get properties\n"

. ${subrdir}/nc.subr
. ${system}

bootable=
dsk_iops_limit=
dsk_mbps_limit=
zvol_opts=
. ${cbsdinit}

available_properties="bootable dsk_zfs_guid dsk_size"
available_get_properties="dsk_path bootable dsk_sectorsize dsk_zfs_guid"

. ${subrdir}/xen.subr
. ${subrdir}/virtual.subr
. ${distdir}/share/xen/xen-dsk.subr

dsk_attach()
{
	local _val _ret _zvol_opts _zvol_args

	if [ -z "${dsk_sectorsize}" ]; then
		readconf xen-default-default.conf
		dsk_sectorsize="${default_sectorsize}"
	fi

	# get next free disk
	if [ -z "${dsk_path}" ]; then
		dsk_path=$( get_next_free_dsk -j ${jname} -e xen )
		[ $? -ne 0 ] && dsk_path=
	fi

	for i in jname dsk_controller dsk_path dsk_size; do
		_val=
		eval _val="\$$i"

		case "${i}" in
			dsk_path)
				[ -z "${_val}" ] && err 1 "${N1_COLOR}xen-dsk error: ${N2_COLOR}${i}= ${N1_COLOR}is mandatory${N0_COLOR}"
				# trim .vhd if necessary
				dsk_path=$( echo ${_val} | ${SED_CMD} 's:\.vhd::g' )
				;;
			dsk_controller)
				[ -z "${_val}" ] && err 1 "${N1_COLOR}xen-dsk error: ${N2_COLOR}${i}= ${N1_COLOR}is mandatory${N0_COLOR}"
				# check for valid dsk_controller
				case "${_val}" in
					xvd|sd|hd)
						;;
					*)
						err 1 "${N1_COLOR}xen-dsk error: xen-dsk: unknown dsk_controller ${_val}, valid: ${N2_COLOR}xvd, sd, hd${N0_COLOR}"
						;;
				esac
				;;
		esac

		[ -z "${_val}" ] && err 1 "${N1_COLOR}xen-dsk error: ${N2_COLOR}${i}= ${N1_COLOR}is mandatory${N0_COLOR}"
	done

	if [ -n "${zvol_opts}" ]; then
		for i in ${zvol_opts}; do
			if [ -z "${_zvol_opts}" ]; then
				_zvol_opts="-o ${i}"
			else
				_zvol_opts="${_zvol_opts} -o ${i}"
			fi
		done
	else
		_zvol_opts=
	fi

	if [ -n "${_zvol_opts}" ]; then
		add_dsk -c "${dsk_controller}" -d "${dsk_path}" -s "${dsk_size}" -z "${dsk_sectorsize}" -o "${_zvol_opts}"
		_ret=$?
	else
		add_dsk -c "${dsk_controller}" -d "${dsk_path}" -s "${dsk_size}" -z "${dsk_sectorsize}"
		_ret=$?
	fi
	if [ ${_ret} -eq 0 ]; then
		${ECHO} "${N2_COLOR}${dsk_path}${N1_COLOR} attached${N0_COLOR}"
	else
		${ECHO} "${N2_COLOR}${dsk_path}${N1_COLOR} attach failed${N0_COLOR}"
	fi
	return ${_ret}
}

dsk_detach()
{
	local _val
	local _devpath

	for i in jname dsk_controller dsk_path; do
		_val=
		eval _val="\$$i"
		case "${i}" in
			dsk_path)
				[ -z "${_val}" ] && err 1 "${N1_COLOR}xen-dsk error: ${N2_COLOR}${i}= ${N1_COLOR}is mandatory${N0_COLOR}"
				# trim .vhd if necessary
				dsk_path=$( echo ${_val} | ${SED_CMD} 's:\.vhd::g' )
				;;
			dsk_controller)
				[ -z "${_val}" ] && err 1 "${N1_COLOR}xen-dsk error: ${N2_COLOR}${i}= ${N1_COLOR}is mandatory${N0_COLOR}"
				# check for valid dsk_controller
				case "${_val}" in
					xvd|sd|hd)
						;;
					*)
						err 1 "${N1_COLOR}xen-dsk error: xen-dsk: unknown dsk_controller ${_val}, valid: ${N2_COLOR}xvd, sd, hd${N0_COLOR}"
						;;
				esac
				;;
		esac

		[ -z "${_val}" ] && err 1 "${N1_COLOR}xen-dsk error: ${N2_COLOR}${i}= ${N1_COLOR}is mandatory${N0_COLOR}"
	done

	# check in media table
	_devpath=$( cbsdsqlro storage_media SELECT path FROM media WHERE jname=\"${jname}\" AND type=\"hdd\" AND name=\"hdd-${dsk_path}.vhd\" LIMIT 1 2>/dev/null )
	[ -z "${_devpath}" ] && err 1 "${N1_COLOR}Unable to find in media DB: SELECT path FROM media WHERE jname=\"${jname}\" AND type=\"hdd\" AND name=\"hdd-${dsk_path}.vhd\"${N0_COLOR}"

	_val=$( cbsdsqlro ${jailsysdir}/${jname}/local.sqlite SELECT dsk_path FROM xendsk WHERE dsk_controller=\"${dsk_controller}\" AND dsk_path=\"${dsk_path}.vhd\" AND dsk_type=\"vhd\" 2>/dev/null )
	[ -z "${_val}" ] && err 1 "${N1_COLOR}Unable to find in xendsk: SELECT dsk_path FROM xendsk WHERE dsk_controller=\"${dsk_controller}\" AND dsk_path=\"${dsk_path}.vhd\" AND dsk_type=\"vhd\"${N0_COLOR}"

	media mode=detach name=hdd-${dsk_path}.vhd path=${_devpath} type=hdd jname=${jname}
	cbsdsqlrw ${jailsysdir}/${jname}/local.sqlite DELETE FROM xendsk WHERE dsk_controller=\"${dsk_controller}\" AND dsk_path=\"${dsk_path}.vhd\" AND dsk_type=\"vhd\"
	[ $? -eq 1 ] && ${ECHO} "${N2_COLOR}${dsk_path}${N1_COLOR} dettached (but not removed!)${N0_COLOR}"
}

dsk_delete()
{
	local _val
	local _devpath

	for i in jname dsk_controller dsk_path; do
		_val=
		eval _val="\$$i"
		case "${i}" in
			dsk_path)
				[ -z "${_val}" ] && err 1 "${N1_COLOR}xen-dsk error: ${N2_COLOR}${i}= ${N1_COLOR}is mandatory${N0_COLOR}"
				# trim .vhd if necessary
				dsk_path=$( echo ${_val} | ${SED_CMD} 's:\.vhd::g' )
				;;
			dsk_controller)
				[ -z "${_val}" ] && err 1 "${N1_COLOR}xen-dsk error: ${N2_COLOR}${i}= ${N1_COLOR}is mandatory${N0_COLOR}"
				# check for valid dsk_controller
				case "${_val}" in
					xvd|sd|hd)
						;;
					*)
						err 1 "${N1_COLOR}xen-dsk error: xen-dsk: unknown dsk_controller ${_val}, valid: ${N2_COLOR}xvd, sd, hd${N0_COLOR}"
						;;
				esac
				;;
		esac

		[ -z "${_val}" ] && err 1 "${N1_COLOR}xen-dsk error: ${N2_COLOR}${i}= ${N1_COLOR}is mandatory${N0_COLOR}"
	done

	# check in media table
	_devpath=$( cbsdsqlro storage_media SELECT path FROM media WHERE jname=\"${jname}\" AND type=\"hdd\" AND name=\"hdd-${dsk_path}.vhd\" LIMIT 1 2>/dev/null )
	[ -z "${_devpath}" ] && err 1 "${N1_COLOR}Unable to find in media DB: SELECT path FROM media WHERE jname=\"${jname}\" AND type=\"hdd\" AND name=\"hdd-${dsk_path}.vhd\"${N0_COLOR}"

	_val=$( cbsdsqlro ${jailsysdir}/${jname}/local.sqlite SELECT dsk_path FROM xendsk WHERE dsk_controller=\"${dsk_controller}\" AND dsk_path=\"${dsk_path}.vhd\" AND dsk_type=\"vhd\" 2>/dev/null )
	[ -z "${_val}" ] && err 1 "${N1_COLOR}Unable to find in xendsk: SELECT dsk_path FROM xendsk WHERE dsk_controller=\"${dsk_controller}\" AND dsk_path=\"${dsk_path}.vhd\" AND dsk_type=\"vhd\"${N0_COLOR}"

	media mode=delete name=hdd-${dsk_path}.vhd path=${_devpath} type=hdd jname=${jname}
	cbsdsqlrw ${jailsysdir}/${jname}/local.sqlite DELETE FROM xendsk WHERE dsk_controller=\"${dsk_controller}\" AND dsk_path=\"${dsk_path}.vhd\" AND dsk_type=\"vhd\"
}

dsk_modify_bootable()
{
	local _bootable _res
	local _old_controller= _old_dsk=

	for i in jname dsk_controller dsk_path; do
		_val=
		eval _val="\$$i"
		case "${i}" in
			dsk_path)
				[ -z "${_val}" ] && err 1 "${N1_COLOR}xen-dsk error: ${N2_COLOR}${i}= ${N1_COLOR}is mandatory${N0_COLOR}"
				# trim .vhd if necessary
				dsk_path=$( echo ${_val} | ${SED_CMD} 's:\.vhd::g' )
				;;
			dsk_controller)
				[ -z "${_val}" ] && err 1 "${N1_COLOR}xen-dsk error: ${N2_COLOR}${i}= ${N1_COLOR}is mandatory${N0_COLOR}"
				# check for valid dsk_controller
				case "${_val}" in
					xvd|sd|hd)
						;;
					*)
						err 1 "${N1_COLOR}xen-dsk error: xen-dsk: unknown dsk_controller ${_val}, valid: ${N2_COLOR}xvd, sd, hd${N0_COLOR}"
						;;
				esac
				;;
		esac

		[ -z "${_val}" ] && err 1 "${N1_COLOR}dsk_modify_bootable error: ${N2_COLOR}${i}= ${N1_COLOR}is mandatory${N0_COLOR}"
	done

	[ ! -r ${jailsysdir}/${jname}/local.sqlite ] && err 1 "${N1_COLOR}No such database: ${N2_COLOR}${jailsysdir}/${jname}/local.sqlite${N0_COLOR}"

	case "${bootable}" in
		[Nn][Oo] | [Ff][Aa][Ll][Ss][Ee] | 0)
			_bootable="false"
			;;
		[Yy][Ee][Ss] | [Tt][Rr][Uu][Ee] | 1)
			_bootable="true"
			;;
		*)
			err 1 "${N1_COLOR}dsk_modify_bootable: unknown bootable: ${N2_COLOR}${bootable}. ${N1_COLOR}Valid: [1|true|yes] and [0|false|no]${N0_COLOR}"
			;;
	esac

	_res=$( cbsdsqlro ${jailsysdir}/${jname}/local.sqlite SELECT dsk_controller,dsk_path FROM xendsk WHERE bootable=${_bootable} LIMIT 1 | ${AWK_CMD} '{printf $1}' )
	sqllist "${_res}" _old_controller _old_dsk

	case "${_bootable}" in
		true)
			if [ "${_old_controller}" = "${dsk_controller}" ]; then
				if [ "${_old_dsk}" = "${dsk_path}" ]; then
					${ECHO} "${N1_COLOR}${jname}: ${dsk_controller} ${dsk_path} already bootable${N0_COLOR}"
					return 0
				fi
			fi

			# currently only one device can be bootable, so set false for all devices first
			cbsdsqlrw ${jailsysdir}/${jname}/local.sqlite UPDATE xendsk SET bootable=false
			[ -n "${_res}" ] && ${ECHO} "${N1_COLOR}Update bootable flag for ${jname}:${_old_controller}:${_old_dsk} -> false${N0_COLOR}"
			${ECHO} "${N1_COLOR}Update bootable flag for ${jname}:${dsk_controller}:${dsk_path} -> ${N2_COLOR}${_bootable}${N0_COLOR}"
			;;
		false)
			if [ "${_old_controller}" = "${dsk_controller}" ]; then
				if [ "${_old_dsk}" = "${dsk_path}" ]; then
					${ECHO} "${N1_COLOR}${jname}: ${dsk_controller} ${dsk_path} already not bootable${N0_COLOR}"
					return 0
				fi
			fi
			${ECHO} "${N1_COLOR}Update bootable flag for ${jname}:${dsk_controller}:${dsk_path} -> ${N2_COLOR}${_bootable}${N0_COLOR}"
			;;
	esac

	cbsdsqlrw ${jailsysdir}/${jname}/local.sqlite UPDATE xendsk SET bootable=${_bootable} WHERE dsk_controller=\"${dsk_controller}\" AND dsk_path=\"${dsk_path}\"
	return 0
}

dsk_modify_dsk_sectorsize()
{
	local _res _old_val

	for i in jname dsk_controller dsk_path; do
		_val=
		eval _val="\$$i"
		case "${i}" in
			dsk_path)
				[ -z "${_val}" ] && err 1 "${N1_COLOR}xen-dsk error: ${N2_COLOR}${i}= ${N1_COLOR}is mandatory${N0_COLOR}"
				# trim .vhd if necessary
				dsk_path=$( echo ${_val} | ${SED_CMD} 's:\.vhd::g' )
				;;
			dsk_controller)
				[ -z "${_val}" ] && err 1 "${N1_COLOR}xen-dsk error: ${N2_COLOR}${i}= ${N1_COLOR}is mandatory${N0_COLOR}"
				# check for valid dsk_controller
				case "${_val}" in
					xvd|sd|hd)
						;;
					*)
						err 1 "${N1_COLOR}xen-dsk error: xen-dsk: unknown dsk_controller ${_val}, valid: ${N2_COLOR}xvd, sd, hd${N0_COLOR}"
						;;
				esac
				;;
		esac

	[ -z "${_val}" ] && err 1 "${N1_COLOR}xen-dsk error: ${N2_COLOR}${i}= ${N1_COLOR}is mandatory${N0_COLOR}"
	done

	[ ! -r ${jailsysdir}/${jname}/local.sqlite ] && err 1 "${N1_COLOR}No such database: ${N2_COLOR}${jailsysdir}/${jname}/local.sqlite${N0_COLOR}"

	_old_val=$( cbsdsqlro ${jailsysdir}/${jname}/local.sqlite SELECT dsk_sectorsize FROM xendsk WHERE dsk_controller=\"${dsk_controller}\" AND dsk_path=\"${dsk_path}\" LIMIT 1 | ${AWK_CMD} '{printf $1}' )
	[ -z "${_old_val}" ] && err 1 "${N1_COLOR}${CBSD_APP}: unable to get current dsk_sectorsize values for: dsk_controller=${N2_COLOR}${dsk_controller}${N1_COLOR} AND dsk_path=${N2_COLOR}${dsk_path}${N0_COLOR}"
	[ "${_old_val}" = "${dsk_sectorsize}" ] && return 0
	cbsdsqlrw ${jailsysdir}/${jname}/local.sqlite UPDATE xendsk SET dsk_sectorsize=\"${dsk_sectorsize}\" WHERE dsk_controller=\"${dsk_controller}\" AND dsk_path=\"${dsk_path}\"

	cbsdlogger NOTICE "${CBSD_APP}: modify sectorsize ${_old_val} -> ${dsk_sectorsize} (dsk_controller: ${dsk_controller}, dsk_path: ${dsk_path})"
	${ECHO} "${N1_COLOR}dsk_sectorsize: ${N2_COLOR}changed${N0_COLOR}"
	return 0
}

dsk_modify_dsk_zfs_guid()
{
	local _res _old_val tmp_zvol= _is_zvol
	local _dsk_fullpath _zvol_pref= _ret

	[ ${zfsfeat} -ne 1 ] && err 1 "${N1_COLOR}zfsfeat disabled${N0_COLOR}"
	[ "${dsk_zfs_guid}" != "auto" ] && err 1 "${N1_COLOR}dsk_zfs_guid valid values: 'auto'${N0_COLOR}"

	for i in jname dsk_controller dsk_path; do
		_val=
		case "${i}" in
			dsk_path)
				[ -z "${_val}" ] && err 1 "${N1_COLOR}xen-dsk error: ${N2_COLOR}${i}= ${N1_COLOR}is mandatory${N0_COLOR}"
				# trim .vhd if necessary
				dsk_path=$( echo ${_val} | ${SED_CMD} 's:\.vhd::g' )
				;;
			dsk_controller)
				[ -z "${_val}" ] && err 1 "${N1_COLOR}xen-dsk error: ${N2_COLOR}${i}= ${N1_COLOR}is mandatory${N0_COLOR}"
				# check for valid dsk_controller
				case "${_val}" in
					xvd|sd|hd)
						;;
					*)
						err 1 "${N1_COLOR}xen-dsk error: xen-dsk: unknown dsk_controller ${_val}, valid: ${N2_COLOR}xvd, sd, hd${N0_COLOR}"
						;;
				esac
				;;
		esac
	[ -z "${_val}" ] && err 1 "${N1_COLOR}xen-dsk error: ${N2_COLOR}${i}= ${N1_COLOR}is mandatory${N0_COLOR}"
	done

	[ ! -r ${jailsysdir}/${jname}/local.sqlite ] && err 1 "${N1_COLOR}No such database: ${N2_COLOR}${jailsysdir}/${jname}/local.sqlite${N0_COLOR}"

	_old_val=$( cbsdsqlro ${jailsysdir}/${jname}/local.sqlite SELECT dsk_zfs_guid FROM xendsk WHERE dsk_controller=\"${dsk_controller}\" AND dsk_path=\"${dsk_path}\" LIMIT 1 | ${AWK_CMD} '{printf $1}' )
	[ -z "${_old_val}" ] && err 1 "${N1_COLOR}${CBSD_APP}: unable to get current dsk_zfs_guid values for: dsk_controller=${N2_COLOR}${dsk_controller}${N1_COLOR} AND dsk_path=${N2_COLOR}${dsk_path}${N0_COLOR}"

	_dsk_fullpath="${jaildatadir}/${jname}-${jaildatapref}/${dsk_path}"

	. ${subrdir}/zfs.subr

	_res=$( get_dsk_zfs_guid -p ${_dsk_fullpath} 2>/dev/null )
	_ret=$?
	[ ${_ret} -ne 0 ] && err 0 "${N1_COLOR}skipp dsk_modify_dsk_zfs_guid error: unable to determine guid for: ${N2_COLOR}${_dsk_fullpath}${N0_COLOR}"
	[ -z "${_res}" ] && err 0 "${N1_COLOR}skipp dsk_modify_dsk_zfs_guid error: unable to determine guid for: ${N2_COLOR}${_is_zvol}${N0_COLOR}"
	[ "${_old_val}" = "${_res}" ] && return 0
	cbsdsqlrw ${jailsysdir}/${jname}/local.sqlite UPDATE xendsk SET dsk_zfs_guid=\"${_res}\" WHERE dsk_controller=\"${dsk_controller}\" AND dsk_path=\"${dsk_path}\"
	cbsdlogger NOTICE "${CBSD_APP}: modify dsk_zfs_guid ${_old_val} -> ${dsk_sectorsize} (dsk_controller: ${dsk_controller}, dsk_path: ${dsk_path})"
	${ECHO} "${N1_COLOR}dsk_zfs_guid: ${N2_COLOR}changed${N0_COLOR}"
	return 0
}

dsk_modify()
{
	local _val
	local _devpath _prop_num=0

	# determine properties
	for i in ${available_properties}; do
		_val=
		eval _val="\$$i"
		[ -z "${_val}" ] && continue
		dsk_modify_${i}
		_prop_num=$(( _prop_num + 1 ))
	done

	[ ${_prop_num} -eq 0 ] && ${ECHO} "${N1_COLOR}available properties: ${N2_COLOR}${available_properties}${N0_COLOR}"
}

dsk_get()
{
	local _val
	local _devpath _prop_num=0
	local _filter= _mysql=

	# determine properties
	for i in ${available_get_properties}; do
		_val=
		eval _val="\$$i"

		if [ "${i}" = "dsk_path" ]; then
			# trim/cast .vhd if necessary
			_val=$( echo ${_val} | ${SED_CMD} 's:\.vhd::g' )
			_val="${_val}.vhd"
		fi

		[ -z "${_val}" ] && continue
		if [ -z "${_filter}" ]; then
			_filter="${i}=\"${_val}\""
		else
			_filter="${_filter} AND ${i}=\"${_val}\""
		fi
		_prop_num=$(( _prop_num + 1 ))
	done

	if [ ${_prop_num} -eq 0 ]; then
		${ECHO} "${N1_COLOR}set filter from available properties (select .. WHERE): ${N2_COLOR}${available_properties}${N0_COLOR}"
		return 1
	fi

	_mysql="SELECT ${get_value} FROM xendsk WHERE ${_filter}"
	# echo "${_mysql}"
	cbsdsqlro ${jailsysdir}/${jname}/local.sqlite ${_mysql}
}

if [ -n "${jname}" ]; then
	. ${subrdir}/rcconf.subr
	[ $? -eq 1 ] && err 1 "${N1_COLOR}xen-dsk: no such domain here: ${N2_COLOR}${jname}${N0_COLOR}"
	[ "${emulator}" != "xen" ] && err 1 "${N1_COLOR}xen-dsk: not in XEN mode: ${N2_COLOR}${jname}${N0_COLOR}"
fi

case "${mode}" in
	attach)
		dsk_attach
		;;
	delete|remove)
		dsk_delete
		;;
	detach)
		dsk_detach
		;;
	list)
		[ -z "${display}" ] && display="jname,dsk_controller,dsk_path,dsk_size,bootable"
		[ -z "${header}" ] && header="1"
		xen-dsk-list header="${header}" display="${display}"
		;;
	modify)
		dsk_modify
		;;
	get)
		[ -z "${jname}" ] && err 1 "${N1_COLOR}jname= is mandatory${N0_COLOR}"
		. ${distsharedir}/xendsk.conf
		# filter params=val and $argx value
		get_value=
		for i in $*; do
			# jname=XX dsk_path=YY test
			strpos --str="${i}" --search="="
			_pos=$?
			if [ ${_pos} -eq 0 ]; then
				if [ -z "${get_value}" ]; then
					get_value="${i}"
				else
					get_value="${get_value},${i}"
				fi
			fi
		done
		if [ -z "${get_value}" ]; then
			${ECHO} "${N1_COLOR}Empty value for get. Use: cbsd xen-dsk mode=get <filter> XXX, where XXX from:${N0_COLOR}"
			${ECHO} "   ${N2_COLOR}${MYCOL}${N0_COLOR}"
			exit 1
		fi
		dsk_get
		;;
	get_next_free_dsk)
		[ -z "${jname}" ] && err 1 "${N1_COLOR}jname= is mandatory${N0_COLOR}"
		next_dsk=$( get_next_free_dsk -j ${jname} -e xen )
		[ $? -ne 0 ] && return 1
		printf "${next_dsk}"
		return 0
		;;
	*)
		err 1 "${N1_COLOR}Unknown mode: ${N2_COLOR}${mode}${N0_COLOR}"
		;;
esac
