#!/bin/sh
#
# Copyright (c) 2022-2023, Jesús Daniel Colmenares Oviedo <DtxdF@disroot.org>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

lib_load "${LIBDIR}/check_func"
lib_load "${LIBDIR}/colors"
lib_load "${LIBDIR}/copy"
lib_load "${LIBDIR}/files"
lib_load "${LIBDIR}/kern_modules"
lib_load "${LIBDIR}/jail"
lib_load "${LIBDIR}/mount"
lib_load "${LIBDIR}/random"
lib_load "${LIBDIR}/replace"
lib_load "${LIBDIR}/strlen"
lib_load "${LIBDIR}/table"
lib_load "${LIBDIR}/tempfile"
lib_load "${LIBDIR}/zfs"

jail_desc="Creates, removes, lists and configures jails."

jail_main()
{
	local entity="$1"; shift
	if lib_check_empty "${entity}"; then
		jail_usage
		exit ${EX_USAGE}
	fi

	case "${entity}" in
		boot|clean|create|destroy|get|list|mark|priority|rename) ;;
		*) jail_usage; exit ${EX_USAGE} ;;
	esac

	jail_${entity} "$@"
}

jail_boot()
{
	local entity="$1"; shift
	if lib_check_empty "${entity}"; then
		jail_usage
		exit ${EX_USAGE}
	fi

	case "${entity}" in
		off|on) ;;
		*) jail_usage; exit ${EX_USAGE} ;;
	esac

	jail_boot_${entity} "$@"
}

jail_boot_off()
{
	local jail_name="$1"

	_basic_chk_jail "${jail_name}"

	local startupdir="${JAILDIR}/${jail_name}/conf/boot/startup"

	rm -f -- "${startupdir}/boot"
}

jail_boot_on()
{
	local jail_name="$1"

	_basic_chk_jail "${jail_name}"

	local startupdir="${JAILDIR}/${jail_name}/conf/boot/startup"
	if ! mkdir -p "${startupdir}"; then
		lib_err ${EX_IOERR} "Error creating ${startupdir}"
	fi

	touch -- "${startupdir}/boot"
}

jail_clean()
{
	jail_list -HIpt dirty name | while read -r dirty jail_name
	do
		if [ ${dirty} -eq 0 ]; then
			continue
		fi

		jail_destroy "${jail_name}"
	done
}

jail_create()
{
	local _o
	local osarch="${FREEBSD_ARCH}"
	local install_method="${DEFAULT_INSTALL_METHOD}" install_args
	local initscript=
	local jail_name=
	local release_name="${DEFAULT_RELEASE}"
	local jail_type="${JAIL_TYPE_THIN}"
	local template=
	local osversion="${FREEBSD_VERSION}"

	if ! lib_zfs_mkdir "${JAILDIR}" "${ZFS_JAILS_NAME}"; then
		lib_err ${EX_IOERR} "Error creating ${JAILDIR}"
	fi

	if [ $# -eq 0 ]; then
		jail_usage
		exit ${EX_USAGE}
	fi

	while getopts ":a:I:i:F:n:r:T:t:v:" _o; do
		case "${_o}" in
			a|I|i|n|r|T|t|v)
				if lib_check_empty "${OPTARG}"; then
					jail_usage
					exit ${EX_USAGE}
				fi
				;;
		esac

		case "${_o}" in
			a)
				osarch="${OPTARG}"
				;;
			I)
				install_method="${OPTARG}"
				;;
			i)
				initscript="${OPTARG}"
				;;
			n)
				jail_name="${OPTARG}"
				;;
			r)
				release_name="${OPTARG}"
				;;
			T)
				jail_type="${OPTARG}"
				;;
			t)
				template="${OPTARG}"
				;;
			v)
				osversion="${OPTARG}"
				;;
			*)
				jail_usage
				exit ${EX_USAGE}
				;;
		esac
	done
	shift $((OPTIND-1))

	jail_name="$1"
	if lib_check_empty "${jail_name}"; then
		jail_usage
		exit ${EX_USAGE}
	fi

	lib_set_logprefix " [`random_color`${jail_name}${COLOR_DEFAULT}]"

	lib_check_jail_type "${jail_type}"

	if ! lib_check_jailname "${jail_name}"; then
		lib_err ${EX_DATAERR} "Invalid jail name \"${jail_name}\""
	fi

	if [ -n "${initscript}" ]; then
		if [ ! -f "${initscript}" ]; then
			lib_err ${EX_NOINPUT} "The initscript \"${initscript}\" cannot be found."
		fi
	fi

	if [ -n "${template}" ]; then
		if [ ! -f "${template}" ]; then
			lib_err ${EX_NOINPUT} "The template \"${template}\" cannot be found."
		fi
	fi

	local jail_path="${JAILDIR}/${jail_name}"

	install_args=`lib_jailparam_value "${install_method}" =`
	install_method=`lib_jailparam_name "${install_method}" =`

	local conf_keys=
	local notouch=0

	# Create the jail
	case "${install_method}" in
		copy)
			jail_install_copy -j "${jail_name}" -- "${install_args}"
			;;
		clone+jail)
			jail_install_clone+jail -j "${jail_name}" -- "${install_args}"
			;;
		clone+release)
			conf_keys="appjail_version birth osarch osversion jail_type release_name"

			jail_install_clone+release -a "${osarch}" -j "${jail_name}" -r "${release_name}" -t "${jail_type}" -v "${osversion}" -- "${install_args}"
			;;
		empty)
			conf_keys="appjail_version birth jail_type"
			jail_type="${JAIL_TYPE_GENERIC}"

			jail_install_empty -j "${jail_name}"
			;;
		export+jail)
			notouch=1
			jail_install_export+jail -j "${jail_name}" -- "${install_args}"
			;;
		export+root)
			notouch=1
			jail_install_export+root -j "${jail_name}" -- "${install_args}"
			;;
		import+jail)
			conf_keys="appjail_version birth osarch osversion jail_type release_name"
			jail_install_import+jail -j "${jail_name}" -- "${install_args}"
			;;
		import+root)
			jail_install_import+root -j "${jail_name}" -- "${install_args}"
			;;
		standard)
			conf_keys="appjail_version birth osarch osversion jail_type release_name"

			jail_install_standard -a "${osarch}" -j "${jail_name}" -r "${release_name}" -t "${jail_type}" -v "${osversion}"
			;;
		tiny+export)
			notouch=1
			jail_install_tiny+export -j "${jail_name}" -- "${install_args}"
			;;
		tiny+import)
			conf_keys="jail_type release_name"
			jail_install_tiny+import -j "${jail_name}" -r "${release_name}" -t "${jail_type}" -- "${install_args}"
			;;
		zfs+export+jail)
			notouch=1
			jail_install_zfs+export+jail -j "${jail_name}" -- "${install_args}"
			;;
		zfs+export+root)
			notouch=1
			jail_install_zfs+export+root -j "${jail_name}" -- "${install_args}"
			;;
		zfs+import+jail)
			conf_keys="appjail_version birth osarch osversion jail_type release_name"
			jail_install_zfs+import+jail -j "${jail_name}" -- "${install_args}"
			;;
		zfs+import+root)
			jail_install_zfs+import+root -j "${jail_name}" -- "${install_args}"
			;;
		*)
			lib_err ${EX_DATAERR} "Unrecognized ${install_method} method."
			;;
	esac

	if [ ${notouch} -eq 1 ]; then
		lib_info "Done."
		return 0
	fi

	if [ ! -d "${jail_path}" ]; then
		lib_warn "The jail directory has not been created!"
		return 0
	fi
	
	if [ -n "${initscript}" ]; then
		lib_debug "Copying ${initscript} as ${jail_path}/init"

		if ! cp "${initscript}" "${jail_path}/init"; then
			lib_warn "Error copying ${initscript} as ${jail_path}/init"
		fi
	fi

	if ! mkdir -p "${jail_path}/conf"; then
		lib_err ${EX_IOERR} "Error creating ${jail_path}/conf"
	fi

	if [ -n "${template}" ]; then
		lib_debug "Copying ${template} as ${jail_path}/conf/template.conf"

		if ! cp "${template}" "${jail_path}/conf/template.conf"; then
			lib_warn "Error copying ${template} as ${jail_path}/conf/template.conf"
		fi
	fi

	touch "${jail_path}/conf/config.conf"

	local conf_key conf_value
	for conf_key in ${conf_keys}; do
		case "${conf_key}" in
			appjail_version)
				conf_value=`"${APPJAIL_PROGRAM}" version`
				;;
			birth)
				conf_value=`date +"%s"`
				;;
			osarch)
				conf_value="${osarch}"
				;;
			osversion)
				conf_value="${osversion}"
				;;
			jail_type)
				conf_value="${jail_type}"
				;;
			release_name)
				conf_value="${release_name}"
				;;
			*)
				lib_err ${EX_SOFTWARE} "${conf_key} isn't a valid configuration key."
				;;
		esac

		lib_ajconf set -Vt "${jail_path}/conf/config.conf" "${conf_key}=${conf_value}"
	done

	jail_mark_clean "${jail_name}"

	lib_info "Done."
}

jail_install_zfs+export+jail()
{
	_jail_export "zfs" "zfs+export+jail" "/jail" "$@"
}

jail_install_zfs+export+root()
{
	_jail_export "zfs" "zfs+export+root" "" "$@"
}

jail_install_export+jail()
{
	_jail_export "tar" "export+jail" "/jail" "$@"
}

jail_install_export+root()
{
	_jail_export "tar" "export+root" "" "$@"
}

_jail_export()
{
	local _o
	local errlevel=0
	local jail_name=
	local type="$1"; shift
	local function="$1"; shift
	local extra="$1"; shift

	if [ -z "${function}" ]; then
		lib_err ${EX_USAGE} "usage: _jail_export type function [extra] args"
	fi

	if [ $# -eq 0 ]; then
		lib_err ${EX_USAGE} "usage: jail_install_${function} -j jail_name output:out_name [portable] [compress:algo]"
	fi

	while getopts ":j:" _o; do
		case "${_o}" in
			j)
				jail_name="${OPTARG}"
				;;
			*)
				jail_install_${function} # usage
				;;
		esac
	done
	shift $((OPTIND-1))

	if [ -z "${jail_name}" ]; then
		jail_install_${function} # usage
	fi

	local args="$1"
	if lib_check_empty "${args}"; then
		lib_err ${EX_DATAERR} "${function} syntax: output:out_name [portable] [compress:algo]"
	fi

	lib_info "Exporting ${jail_name} ..."

	case "${type}" in
		tar)
			_chk_jail_export "${jail_name}"
			;;
		zfs)
			_chk_jail_export_zfs "${jail_name}"
			;;
		*)
			lib_err ${EX_NOINPUT} -- "${type}: type not found."
			;;
	esac

	local params
	local total_items
	local current_index=0

	params=`lib_split_jailparams "${args}"` || exit $?
	total_items=`printf "%s\n" "${params}" | wc -l`

	local portable=0
	local compress= output=
	local parameter= value= arg

	while [ ${current_index} -lt ${total_items} ]; do 
		current_index=$((current_index+1))
		arg=`printf "%s\n" "${params}" | head -${current_index} | tail -n 1`
		parameter=`lib_jailparam_name "${arg}" :`
		value=`lib_jailparam_value "${arg}" :`

		case "${parameter}" in
			portable)
				portable=1
				;;
			compress)
				compress="${value}"
				;;
			output)
				output="${value}"
				;;
			*) 
				lib_err ${EX_NOINPUT} -- "${parameter}: parameter not found."
				;;
		esac
	done

	if lib_check_empty "${output}"; then
		lib_err ${EX_DATAERR} "${function} syntax: output:out_name [portable] [compress:algo]"
	fi

	lib_debug "Generating ${output} ..."

	case "${type}" in
		tar)
			local tar_args
			if lib_check_empty "${compress}"; then
				tar_args=
			else
				tar_args=`_compress2tar_args "${compress}"`

				errlevel=$?
				if [ ${errlevel} -ne 0 ]; then
					return ${errlevel}
				fi
			fi

			local jail_path="${JAILDIR}/${jail_name}${extra}"
			if [ ! -d "${jail_path}" ]; then
				lib_err ${EX_NOINPUT} "Cannot find the \`${jail_path}\` directory."
			fi

			local escape_jail_path=`lib_escape_string "${jail_path}"`
			local escape_output=`lib_escape_string "${output}"`

			local include_files
			if [ ${portable} -eq 0 -o "${function}" = "export+jail" ]; then
				include_files="."
			else
				mkdir -p "${jail_path}/jail" || exit $?
				touch "${jail_path}/init" || exit $?
				chmod +x "${jail_path}/init" || exit $?
				mkdir -p "${jail_path}/conf" || exit $?
				touch "${jail_path}/conf/config.conf" || exit $?
				mkdir -p "${jail_path}/conf/volumes" || exit $?

				include_files="./jail/ ./init ./conf/volumes/ ./conf/config.conf"
			fi

			if ! sh -c "tar -C \"${escape_jail_path}\" ${tar_args} -cf \"${escape_output}\" ${include_files}"; then
				lib_err ${EX_CANTCREAT} "Error generating ${output}."
			fi
			;;
		zfs)
			if lib_check_empty "${compress}"; then
				if ! lib_zfs_copy2out "${ZFS_JAILS_NAME}/${jail_name}${extra}" > "${output}"; then
					lib_err ${EX_CANTCREAT} "Error generating ${output}."
				fi
			else
				local compress_cmd
				compress_cmd=`_compress2cmd "${compress}"`

				errlevel=$?
				if [ ${errlevel} -ne 0 ]; then
					return ${errlevel}
				fi

				if ! lib_zfs_copy2out "${ZFS_JAILS_NAME}/${jail_name}${extra}" | sh -c "${compress_cmd}" > "${output}"; then
					lib_err ${EX_CANTCREAT} "Error generating ${output}."
				fi
			fi
			;;
	esac
}

jail_install_import+jail()
{
	_jail_import "tar" "import+jail" "/jail" "$@"
}

jail_install_import+root()
{
	_jail_import "tar" "import+root" "" "$@"
}

jail_install_zfs+import+jail()
{
	_jail_import "zfs" "zfs+import+jail" "/jail" "$@"
}

jail_install_zfs+import+root()
{
	_jail_import "zfs" "zfs+import+root" "" "$@"
}

_jail_import()
{
	local _o
	local errlevel=0
	local jail_name=
	local type="$1"; shift
	local function="$1"; shift
	local extra="$1"; shift

	if [ -z "${function}" ]; then
		lib_err ${EX_USAGE} "usage: _jail_import type function [extra] args"
	fi

	if [ $# -eq 0 ]; then
		lib_err ${EX_USAGE} "usage: jail_install_${function} -j jail_name input:in_file [portable] [compress:algo]"
	fi

	while getopts ":j:" _o; do
		case "${_o}" in
			j)
				jail_name="${OPTARG}"
				;;
			*)
				jail_install_${function} # usage
				;;
		esac
	done
	shift $((OPTIND-1))

	if [ -z "${jail_name}" ]; then
		jail_install_${function} # usage
	fi

	_chk_jail "${jail_name}"

	local args="$1"
	if lib_check_empty "${args}"; then
		lib_err ${EX_DATAERR} "${function} syntax: input:in_file [portable] [compress:algo]"
	fi

	local params
	local total_items
	local current_index=0

	params=`lib_split_jailparams "${args}"` || exit $?
	total_items=`printf "%s\n" "${params}" | wc -l`

	local portable=0
	local compress= input=
	local parameter= value= arg

	while [ ${current_index} -lt ${total_items} ]; do 
		current_index=$((current_index+1))
		arg=`printf "%s\n" "${params}" | head -${current_index} | tail -n 1`
		parameter=`lib_jailparam_name "${arg}" :`
		value=`lib_jailparam_value "${arg}" :`

		case "${parameter}" in
			portable)
				portable=1
				;;
			compress)
				compress="${value}"
				;;
			input)
				input="${value}"
				;;
			*) 
				lib_err ${EX_NOINPUT} -- "${parameter}: parameter not found."
				;;
		esac
	done

	if lib_check_empty "${input}"; then
		lib_err ${EX_DATAERR} "${function} syntax: input:in_file [portable] [compress:algo]"
	fi

	if [ ! -f "${input}" ]; then
		lib_err ${EX_NOINPUT} "The input \"${input}\" cannot be found."
	fi

	local jail_path="${JAILDIR}/${jail_name}${extra}"

	case "${type}" in
		tar)
			jail_install_empty -j "${jail_name}"

			lib_info "Importing ${input} as ${jail_name} ..."

			local escape_jail_path
			escape_jail_path=`lib_escape_string "${jail_path}"`

			local escape_input
			escape_input=`lib_escape_string "${input}"`

			local include_files=
			if [ ${portable} -eq 1 -a "${function}" = "import+root" ]; then
				include_files="./jail/ ./init ./conf/volumes/ ./conf/config.conf"
			fi

			if ! sh -c "tar -C \"${escape_jail_path}\" ${TAR_DECOMPRESS_ARGS} -xf \"${escape_input}\" ${include_files}"; then
				jail_mark_dirty "${jail_name}"

				lib_err ${EX_IOERR} "Error importing ${input} as ${jail_name}."
			fi
			;;
		zfs)
			lib_info "Importing ${input} as ${jail_name} ..."

			local decompress_cmd

			if lib_check_empty "${compress}"; then
				decompress_cmd=`_mimetype2decompress_cmd "${input}"`
			else
				decompress_cmd=`_decompress2cmd "${compress}"`
			fi

			errlevel=$?
			if [ ${errlevel} -ne 0 ]; then
				return ${errlevel}
			fi

			case "${function}" in
				zfs+import+jail)
					if ! lib_zfs_mkdir "${jail_path}" "${ZFS_JAILS_NAME}/${jail_name}"; then
						lib_err ${EX_IOERR} "Error creating ${jail_path}"
					fi
					;;
			esac

			if lib_check_empty "${decompress_cmd}"; then
				if ! lib_zfs_in2copy "${ZFS_JAILS_NAME}/${jail_name}${extra}" "${jail_path}" < "${input}"; then
					jail_mark_dirty "${jail_name}"

					lib_err ${EX_IOERR} "Error importing ${input} as ${jail_name}."
				fi
			else
				if ! sh -c "${decompress_cmd}" < "${input}" | lib_zfs_in2copy "${ZFS_JAILS_NAME}/${jail_name}${extra}" "${jail_path}"; then
					jail_mark_dirty "${jail_name}"

					lib_err ${EX_IOERR} "Error importing ${input} as ${jail_name}."
				fi
			fi
			;;
		*)
			lib_err ${EX_NOINPUT} -- "${type}: type not found."
			;;
	esac
}

jail_install_copy()
{
	local _o
	local jail_name=

	if [ $# -eq 0 ]; then
		lib_err ${EX_USAGE} "usage: jail_install_copy -j jail_name jail2copy"
	fi

	while getopts ":j:" _o; do
		case "${_o}" in
			j)
				jail_name="${OPTARG}"
				;;
			*)
				jail_install_copy # usage
				;;
		esac
	done
	shift $((OPTIND-1))

	if [ -z "${jail_name}" ]; then
		jail_install_copy # usage
	fi

	_chk_jail "${jail_name}"

	local jail2copy="$1"
	if lib_check_empty "${jail2copy}"; then
		lib_err ${EX_DATAERR} "copy syntax: jail2copy"
	fi

	_basic_chk_jail "${jail2copy}"

	lib_info "Copying ${jail2copy} to ${jail_name} ..."

	if [ "${ENABLE_ZFS}" != "0" ]; then
		if ! lib_zfs_copy "${ZFS_JAILS_NAME}/${jail2copy}" "${ZFS_JAILS_NAME}/${jail_name}" "${JAILDIR}/${jail_name}"; then
			lib_err ${EX_IOERR} "Error copying ${ZFS_JAILS_NAME}/${jail2copy}"
		fi
	else
		if lib_jail_exists "${jail_name}"; then
			if lib_jail_created_by_appjail "${jail_name}"; then
				lib_warn -- "${jail_name} is currently running."
				exit 0
			fi
		fi

		if ! cp -a "${JAILDIR}/${jail2copy}" "${JAILDIR}/${jail_name}"; then
			lib_err ${EX_IOERR} "Error copying ${JAILDIR}/${jail2copy}"
		fi
	fi
}

jail_install_clone+jail()
{
	local _o
	local jail_name=

	if [ "${ENABLE_ZFS}" = "0" ]; then
		lib_err ${EX_CONFIG} "zfs is not enabled in the configuration file. The clone+jail method cannot be used."
	fi

	if [ $# -eq 0 ]; then
		lib_err ${EX_USAGE} "usage: jail_install_clone+jail -j jail_name jail2clone@snapname"
	fi

	while getopts ":j:" _o; do
		case "${_o}" in
			j)
				jail_name="${OPTARG}"
				;;
			*)
				jail_install_clone+jail # usage
		esac
	done
	shift $((OPTIND-1))

	if [ -z "${jail_name}" ]; then
		jail_install_clone+jail # usage
	fi

	_chk_jail "${jail_name}"

	local args="$1"
	if lib_check_empty "${args}" || ! echo "${args}" | grep -Eq ".+@.+"; then
		lib_err ${EX_DATAERR} "clone+jail syntax: jail2clone@snapname"
	fi

	local jail2clone=`echo "${args}" | sed -Ee 's/(.+)@.+/\1/'`
	if ! lib_check_jailname "${jail2clone}"; then
		lib_err ${EX_DATAERR} "Invalid jail name \"${jail2clone}\""
	fi

	local snapshot_name=`echo "${args}" | sed -Ee 's/.+@(.+)/\1/'`
	local dataset="${ZFS_JAILS_NAME}/${jail2clone}"

	if ! lib_check_zfs_fs "${ZFS_JAILS_NAME}" "${jail2clone}"; then
		lib_err ${EX_NOINPUT} "Cannot find the jail \`${jail2clone}\`"
	fi

	lib_info "Cloning ${jail2clone} to ${jail_name} ..."

	#
	# root
	#
	if ! lib_check_zfs_snapshot "${dataset}" "${snapshot_name}"; then
		lib_debug "Creating snapshot ${dataset}@${snapshot_name} ..."

		if ! lib_zfs_snapshot "${dataset}" "${snapshot_name}"; then
			lib_err ${EX_CANTCREAT} "Cannot create ${snapshot_name} snapshot for ${dataset}."
		fi
	fi

	lib_debug "Cloning ${dataset}@${snapshot_name} to ${ZFS_JAILS_NAME}/${jail_name}"

	if ! lib_zfs_clone "${dataset}@${snapshot_name}" "${ZFS_JAILS_NAME}/${jail_name}"; then
		lib_err ${EX_CANTCREAT} "Cannot clone ${dataset}@${snapshot_name} to ${ZFS_JAILS_NAME}/${jail_name}"
	fi

	#
	# jail
	#
	dataset="${dataset}/jail"
	if ! lib_check_zfs_snapshot "${dataset}" "${snapshot_name}"; then
		lib_debug "Creating snapshot ${dataset}@${snapshot_name} ..."

		if ! lib_zfs_snapshot "${dataset}" "${snapshot_name}"; then
			lib_err ${EX_CANTCREAT} "Cannot create ${snapshot_name} snapshot for ${dataset}."
		fi
	fi

	lib_debug "Cloning ${dataset}@${snapshot_name} to ${ZFS_JAILS_NAME}/${jail_name}/jail"

	if ! lib_zfs_clone "${dataset}@${snapshot_name}" "${ZFS_JAILS_NAME}/${jail_name}/jail"; then
		lib_err ${EX_CANTCREAT} "Cannot clone ${dataset}@${snapshot_name} to ${ZFS_JAILS_NAME}/${jail_name}"
	fi
}

jail_install_clone+release()
{
	local _o
	local osarch=
	local jail_name=
	local release_name=
	local jail_type=
	local osversion=

	if [ "${ENABLE_ZFS}" = "0" ]; then
		lib_err ${EX_CONFIG} "zfs is not enabled in the configuration file. The clone+release method cannot be used."
	fi

	if [ $# -eq 0 ]; then
		lib_err ${EX_USAGE} "usage: jail_install_clone+release -a osarch -j jail_name -r release_name -t jail_type -v osversion snapname"
	fi

	while getopts ":a:j:r:t:v:" _o; do
		case "${_o}" in
			a)
				osarch="${OPTARG}"
				;;
			j)
				jail_name="${OPTARG}"
				;;
			r)
				release_name="${OPTARG}"
				;;
			t)
				jail_type="${OPTARG}"
				;;
			v)
				osversion="${OPTARG}"
				;;
			*)
				jail_install_clone+release # usage
				;;
		esac
	done
	shift $((OPTIND-1))

	if [ -z "${osarch}" -o -z "${jail_name}" -o -z "${release_name}" -o -z "${jail_type}" -o -z "${osversion}" ]; then
		jail_install_clone+release # usage
	fi

	_chk_jail "${jail_name}"

        local snapshot_name="$1"
        if lib_check_empty "${snapshot_name}"; then
                lib_err ${EX_DATAERR} "clone+release syntax: snapname"
        fi

	lib_info "Cloning ${osarch}/${osversion}/${release_name} to ${jail_name} ..."

	_warn_dirty_release "${RELEASEDIR}/${osarch}/${osversion}/${release_name}"

	local extra=

	case "${jail_type}" in
		${JAIL_TYPE_THICK})
			extra="/release"
			;;
		${JAIL_TYPE_DEBOOTSTRAP})
			extra="/linux_debootstrap"
			;;
		*)
			lib_err ${EX_DATAERR} "Valid types for clone+release are: ${JAIL_TYPE_THICK} and ${JAIL_TYPE_DEBOOTSTRAP}."
			;;
	esac

	if ! lib_check_zfs_fs "${ZFS_RELEASE_NAME}" "${osarch}/${osversion}/${release_name}${extra}"; then
		lib_err ${EX_NOINPUT} "Cannot find the release \`${release_name}\`"
	fi

	local dataset="${ZFS_RELEASE_NAME}/${osarch}/${osversion}/${release_name}${extra}"

	if ! lib_check_zfs_snapshot "${dataset}" "${snapshot_name}"; then
		if ! lib_zfs_snapshot "${dataset}" "${snapshot_name}"; then
			lib_err ${EX_CANTCREAT} "Cannot create ${snapshot_name} snapshot for ${dataset}."
		fi
	fi

	if ! lib_zfs_mkdir "${jail_path}" "${ZFS_JAILS_NAME}/${jail_name}"; then
		lib_err ${EX_IOERR} "Error creating ${jail_path}"
	fi

	if ! lib_zfs_clone "${dataset}@${snapshot_name}" "${ZFS_JAILS_NAME}/${jail_name}/jail"; then
		lib_err ${EX_CANTCREAT} "Cannot clone ${dataset}@${snapshot_name} to ${ZFS_JAILS_NAME}/${jail_name}"
	fi

	# post-installation
	case "${jail_type}" in
		${JAIL_TYPE_DEBOOTSTRAP})
			jail_install_standard_linux_debootstrap \
				-C \
				-a "${osarch}" \
				-j "${jail_name}" \
				-r "${release_name}" \
				-v "${osversion}"
			;;
	esac
}

jail_install_empty()
{
	local _o
	local jail_name=

	if [ $# -eq 0 ]; then
		lib_err ${EX_USAGE} "usage: jail_install_empty -j jail_name"
	fi

	while getopts ":j:" _o; do
		case "${_o}" in
			j)
				jail_name="${OPTARG}"
				;;
			*)
				jail_install_empty # usage
				;;
		esac
	done

	if [ -z "${jail_name}" ]; then
		jail_install_empty # usage
	fi

	_chk_jail "${jail_name}"

	local jail_path="${JAILDIR}/${jail_name}"
	local dataset="${ZFS_JAILS_NAME}/${jail_name}"

	lib_info "Creating an empty jail ..."

	if ! lib_zfs_mkdir "${jail_path}" "${dataset}"; then
		lib_err ${EX_IOERR} "Error creating ${jail_path}"
	fi

	if ! lib_zfs_mkdir "${jail_path}/jail" "${dataset}/jail"; then
		lib_err ${EX_IOERR} "Error creating ${jail_path}/jail"
	fi
}

jail_install_standard()
{
	local _o
	local osarch=
	local jail_name=
	local release_name=
	local jail_type=
	local osversion=
	
	if [ $# -eq 0 ]; then
		lib_err ${EX_USAGE} "usage: jail_install_standard -a osarch -j jail_name -r release_name -t jail_type -v osversion"
	fi

	while getopts ":a:j:r:t:v:" _o; do
		case "${_o}" in
			a)
				osarch="${OPTARG}"
				;;
			j)
				jail_name="${OPTARG}"
				;;
			r)
				release_name="${OPTARG}"
				;;
			t)
				jail_type="${OPTARG}"
				;;
			v)
				osversion="${OPTARG}"
				;;
			*)
				jail_install_standard # usage
				;;
		esac
	done

	if [ -z "${osarch}" -o -z "${jail_name}" -o -z "${release_name}" -o -z "${jail_type}" -o -z "${osversion}" ]; then
		jail_install_standard # usage
	fi

	_chk_jail "${jail_name}"

	local releasedir="${RELEASEDIR}/${osarch}/${osversion}/${release_name}"

	if [ ! -d "${releasedir}" ]; then
		lib_err ${EX_NOINPUT} "Cannot find the \`${releasedir}\` directory. Use \`appjail fetch\` to create it."
	fi

	lib_info "Creating a standard jail (${jail_type}) ..."

	_warn_dirty_release "${releasedir}"

	case "${jail_type}" in
		${JAIL_TYPE_THICK})
			if ! lib_zfs_mkdir "${jail_path}" "${ZFS_JAILS_NAME}/${jail_name}"; then
				lib_err ${EX_IOERR} "Error creating ${jail_path}"
			fi
			;;
		${JAIL_TYPE_THIN}|${JAIL_TYPE_DEBOOTSTRAP})
			if ! lib_zfs_mkdir "${jail_path}" "${ZFS_JAILS_NAME}/${jail_name}"; then
				lib_err ${EX_IOERR} "Error creating ${jail_path}"
			fi

			if ! lib_zfs_mkdir "${jail_path}/jail" "${ZFS_JAILS_NAME}/${jail_name}/jail"; then
				lib_err ${EX_IOERR} "Error creating ${jail_path}/jail"
			fi
		;;
	esac

	case "${jail_type}" in
		${JAIL_TYPE_THICK})
			jail_install_standard_thickjail -a "${osarch}" -j "${jail_name}" -r "${release_name}" -v "${osversion}"
			;;
		${JAIL_TYPE_THIN})
			jail_install_standard_thinjail "${releasedir}/release" "${jail_path}/jail"
			;;
		${JAIL_TYPE_DEBOOTSTRAP})
			jail_install_standard_linux_debootstrap -a "${osarch}" -j "${jail_name}" -r "${release_name}" -v "${osversion}"
			;;
		*)
			lib_err ${EX_DATAERR} "Invalid jail type: ${jail_type}"
			;;
	esac
}

jail_install_standard_thickjail()
{
	local _o
	local osarch=
	local jail_name=
	local release_name=
	local osversion=

	if [ $# -eq 0 ]; then
		lib_err ${EX_USAGE} "usage: jail_install_standard_thickjail -a osarch -j jail_name -r release_name -v osversion"
	fi

	while getopts ":a:j:r:v:" _o; do
		case "${_o}" in
			a)
				osarch="${OPTARG}"
				;;
			j)
				jail_name="${OPTARG}"
				;;
			r)
				release_name="${OPTARG}"
				;;
			v)
				osversion="${OPTARG}"
				;;
			*)
				jail_install_standard_thickjail # usage
				;;
		esac
	done

	local jail_path="${JAILDIR}/${jail_name}/jail"
	local releasedir="${RELEASEDIR}/${osarch}/${osversion}/${release_name}/release"

	if [ ! -d "${releasedir}" ]; then
		lib_err ${EX_NOINPUT} "Cannot find the \`${releasedir}\` directory. Use \`appjail fetch\` to create it."
	fi

	lib_info "Creating a thickjail ..."

	if [ "${ENABLE_ZFS}" != "0" ]; then
		local dataset="${ZFS_RELEASE_NAME}/${osarch}/${osversion}/${release_name}/release"

		lib_debug "Copying (zfs): ${dataset} to ${ZFS_JAILS_NAME}/${jail_name}/jail"

		if ! lib_zfs_copy "${dataset}" "${ZFS_JAILS_NAME}/${jail_name}/jail" "${jail_path}"; then
			lib_err ${EX_IOERR} "Error copying ${dataset}"
		fi
	else
		lib_debug "Copying: cp -a \"${releasedir}/\" \"${jail_path}\""

		if ! cp -a "${releasedir}/" "${jail_path}"; then
			lib_err ${EX_IOERR} "Error copying ${releasedir}/"
		fi
	fi
}

jail_install_standard_thinjail()
{
	local source dest
	local dir file rootdir

	source="$1"; dest="$2"

	if [ -z "${source}" -o -z "${dest}" ]; then
		lib_err ${EX_USAGE} "usage: install_thin_jail source dest"
	fi

	if [ ! -d "${source}" ]; then
		lib_err ${EX_NOINPUT} "Cannot find the \`${source}\` directory. Use \`appjail fetch\` to create it."
	fi

	lib_info "Creating a thinjail ..."

	# rw-only files
	while read -r file; do
		dir=`dirname "${file}"`

		if [ "${dir}" == "." ]; then
			rootdir="${dest}"
		else
			rootdir="${dest}/${dir}"
			
			if ! mkdir -p -- "${rootdir}"; then
				lib_err ${EX_IOERR} "Error creating ${rootdir}"
			fi
		fi

		if [ ! -e "${source}/${file}" ]; then
			lib_warn -- "${file}: No such file or directory. Ignoring..."
			continue
		fi

		lib_debug "Copying: cp -a \"${source}/${file}\" \"${rootdir}\""

		if ! cp -a "${source}/${file}" "${rootdir}"; then
			lib_err ${EX_IOERR} "Error copying ${source}/${file}"
		fi
	done < "${FILESDIR}/include-thinjail.files"

	# ro-only files
	while read -r file; do
		lib_debug "Linking /.appjail/${file} -> ${dest}/${file}"

		if ! ln -Ffs "/.appjail/${file}" "${dest}/${file}"; then
			lib_err ${EX_IOERR} "Error linking ${dest}/${file}"
		fi
	done < "${FILESDIR}/exclude-thinjail.files"

	if ! mkdir -p -- "${dest}/.appjail"; then
		lib_err ${EX_IOERR} "Error creating ${dest}/.appjail"
	fi
}

jail_install_standard_linux_debootstrap()
{
	local _o
	local opt_copy=1
	local osarch=
	local jail_name=
	local release_name=
	local osversion=

	if [ $# -eq 0 ]; then
		lib_err ${EX_USAGE} "usage: jail_install_standard_linux_debootstrap [-C] -a osarch -j jail_name -r release_name -v osversion"
	fi

	while getopts ":Ca:j:r:v:" _o; do
		case "${_o}" in
			C)
				opt_copy=0
				;;
			a)
				osarch="${OPTARG}"
				;;
			j)
				jail_name="${OPTARG}"
				;;
			r)
				release_name="${OPTARG}"
				;;
			v)
				osversion="${OPTARG}"
				;;
			*)
				jail_install_standard_linux_debootstrap # usage
				;;
		esac
	done

	local jail_path="${JAILDIR}/${jail_name}/jail"
	local releasedir="${RELEASEDIR}/${osarch}/${osversion}/${release_name}/linux_debootstrap"

	if [ ! -d "${releasedir}" ]; then
		lib_err ${EX_NOINPUT} "Cannot find the \`${releasedir}\` directory. Use \`appjail fetch\` to create it."
	fi

	# Load linux kernel modules
	lib_modules_linuxmods

	lib_info "Creating a linux+debootstrap jail ..."

	if [ ${opt_copy} -eq 1 ]; then
		lib_debug "Copying: cp -a \"${releasedir}/\" \"${jail_path}\""

		if ! cp -a "${releasedir}/" "${jail_path}"; then
			lib_err ${EX_IOERR} "Error copying ${releasedir}/"
		fi
	fi

	mkdir -p "${jail_path}/dev"
	mkdir -p "${jail_path}/dev/shm"
	mkdir -p "${jail_path}/dev/fd"
	mkdir -p "${jail_path}/proc"
	mkdir -p "${jail_path}/sys"

	local escape_dest=`lib_escape_string "${jail_path}"`

	lib_atexit_add "umount \"${escape_dest}/dev/shm\" > /dev/null 2>&1"
	lib_atexit_add "umount \"${escape_dest}/dev/fd\" > /dev/null 2>&1"
	lib_atexit_add "umount \"${escape_dest}/dev\" > /dev/null 2>&1"
	lib_atexit_add "umount \"${escape_dest}/proc\" > /dev/null 2>&1"
	lib_atexit_add "umount \"${escape_dest}/sys\" > /dev/null 2>&1"

	if ! mount -t devfs devfs "${jail_path}/dev"; then
		lib_err ${EX_SOFTWARE} "Error mounting \`devfs\` to ${jail_path}/dev"
	fi

	if ! mount -t tmpfs -o rw,size=1g,mode=1777 tmpfs "${jail_path}/dev/shm"; then
		lib_err ${EX_SOFTWARE} "Error mounting \`tmpfs\` to ${jail_path}/dev/shm"
	fi

	if ! mount -t fdescfs -o rw,linrdlnk fdescfs "${jail_path}/dev/fd"; then
		lib_err ${EX_SOFTWARE} "Error mounting \`fdescfs\` to ${jail_path}/dev/fd"
	fi

	if ! mount -t linprocfs linprocfs "${jail_path}/proc"; then
		lib_err ${EX_SOFTWARE} "Error mounting \`linprocfs\` to ${jail_path}/proc"
	fi

	if ! mount -t linsysfs linsysfs "${jail_path}/sys"; then
		lib_err ${EX_SOFTWARE} "Error mounting \`linsysfs\` to ${jail_path}/sys"
	fi

	if ! chroot "${jail_path}" sh -c "rm -f /var/cache/apt/archives/rsyslog*.deb"; then
		lib_err ${EX_SOFTWARE} "Error removing: /var/cache/apt/archives/rsyslog*.deb"
	fi

	if ! chroot "${jail_path}" sh -c "DEBIAN_FRONTEND=noninteractive dpkg --force-depends --force-confdef --force-confold -i /var/cache/apt/archives/*.deb"; then
		lib_err ${EX_SOFTWARE} "Error installing *.deb packages."
	fi

	if ! chroot "${jail_path}" sh -c "apt update -y"; then
		lib_warn "Error updating repositories: run \`appjail cmd jexec \"${jail_name}\" apt update\` manually when possible."
	fi

	umount "${jail_path}/dev/shm"
	umount "${jail_path}/dev/fd"
	umount "${jail_path}/dev"
	umount "${jail_path}/proc"
	umount "${jail_path}/sys"
}

jail_install_tiny+export()
{
	local _o
	local jail_name=
	local errlevel=0

	if [ $# -eq 0 ]; then
		lib_err ${EX_USAGE} "usage: jail_install_tiny+export -j jail_name files:file_list output:out_name [compress:algo]"
	fi

	while getopts ":j:" _o; do
		case "${_o}" in
			j)
				jail_name="${OPTARG}"
				;;
			*)
				jail_install_tiny+export # usage
				;;
		esac
	done
	shift $((OPTIND-1))

	if [ -z "${jail_name}" ]; then
		jail_install_tiny+export # usage
	fi

	_chk_jail_export "${jail_name}"

	local jail_path="${JAILDIR}/${jail_name}"

	local args="$1"
	if lib_check_empty "${args}"; then
		lib_err ${EX_DATAERR} "tiny+export syntax: files:file_list output:out_name [compress:algo]"
	fi

	lib_info "Exporting ${jail_name} as a tiny jail ..."

	local params
	local total_items
	local current_index=0

	params=`lib_split_jailparams "${args}"` || exit $?
	total_items=`printf "%s\n" "${params}" | wc -l`

	local compress= files= output=
	local parameter= value= arg

	while [ ${current_index} -lt ${total_items} ]; do 
		current_index=$((current_index+1))
		arg=`printf "%s\n" "${params}" | head -${current_index} | tail -n 1`
		parameter=`lib_jailparam_name "${arg}" :`
		value=`lib_jailparam_value "${arg}" :`

		case "${parameter}" in
			compress)
				compress="${value}"
				;;
			files)
				files="${value}"
				;;
			output)
				output="${value}"
				;;
			*) 
				lib_err ${EX_NOINPUT} -- "${parameter}: parameter not found."
				;;
		esac
	done

	if lib_check_empty "${files}" || lib_check_empty "${output}"; then
		lib_err ${EX_DATAERR} "tiny+export syntax: files:file_list output:out_name [compress:algo]"
	fi

	local tar_args
	if lib_check_empty "${compress}"; then
		tar_args=
	else
		tar_args=`_compress2tar_args "${compress}"`

		errlevel=$?
		if [ ${errlevel} -ne 0 ]; then
			return ${errlevel}
		fi
	fi

	if [ ! -f "${files}" ]; then
		lib_err ${EX_NOINPUT} "The file (${files}) does not exist."
	fi

	local tempdir
	tempdir=`lib_generate_tempdir`

	errlevel=$?
	if [ ${errlevel} -ne 0 ]; then
		return ${errlevel}
	fi

	local escape_tempdir=`lib_escape_string "${tempdir}"`

	lib_atexit_add "chflags -R 0 \"${escape_tempdir}\" > /dev/null 2>&1"
	lib_atexit_add "rm -rf \"${escape_tempdir}\" > /dev/null 2>&1"

	# Create /jail
	if ! mkdir -p "${tempdir}/jail"; then
		lib_err ${EX_IOERR} "Error creating ${tempdir}/jail"
	fi

	# Copy files
	lib_safe_copy_lst -l "${files}" -s "${jail_path}/jail" -d "${tempdir}/jail"

	# Create /conf
	if ! mkdir -p "${tempdir}/conf"; then
		lib_err ${EX_IOERR} "Error creating ${tempdir}/conf"
	fi

	# Copy conf/config.conf
	if [ -f "${jail_path}/conf/config.conf" ]; then
		if ! cp -a "${jail_path}/conf/config.conf" "${tempdir}/conf"; then
			lib_err ${EX_IOERR} "Error copying ${jail_path}/conf/config.conf"
		fi
	fi

	local escape_tempdir=`lib_escape_string "${tempdir}"`
	local escape_output=`lib_escape_string "${output}"`

	# Compress
	lib_debug "Generating ${output} ..."
	if ! sh -c "tar -C \"${escape_tempdir}\" ${tar_args} -cf \"${escape_output}\" ."; then
		lib_err ${EX_CANTCREAT} "Error generating ${output}."
	fi

	# Clean up
	chflags -R 0 "${tempdir}" > /dev/null 2>&1
	rm -rf "${tempdir}" > /dev/null 2>&1
}

jail_install_tiny+import()
{
	local _o
	local jail_name=
	local release_name=
	local jail_type=
	local errlevel=0
	
	if [ $# -eq 0 ]; then
		lib_err ${EX_USAGE} "usage: jail_install_tiny+import -j jail_name -r release_name -t jail_type -- path/to/appjail_file"
	fi

	while getopts ":j:r:t:" _o; do
		case "${_o}" in
			j)
				jail_name="${OPTARG}"
				;;
			r)
				release_name="${OPTARG}"
				;;
			t)
				jail_type="${OPTARG}"
				;;
			*)
				jail_install_tiny+import # usage
				;;
		esac
	done
	shift $((OPTIND-1))

	if [ -z "${jail_name}" -o -z "${release_name}" -o -z "${jail_type}" ]; then
		jail_install_tiny+import # usage
	fi

	case "${jail_type}" in
		${JAIL_TYPE_THICK}|${JAIL_TYPE_THIN}) ;;
		*) lib_err ${EX_DATAERR} "Valid types for tiny+import are: ${JAIL_TYPE_THICK} and ${JAIL_TYPE_THIN}." ;;
	esac

	local appjail_file="$1"
	if lib_check_empty "${appjail_file}"; then
		lib_err ${EX_DATAERR} "tiny+import syntax: path/to/appjail_file"
	fi

	local tempdir
	tempdir=`lib_generate_tempdir`

	errlevel=$?
	if [ ${errlevel} -ne 0 ]; then
		return ${errlevel}
	fi

	lib_debug "Trying to extract conf/config.conf from ${appjail_file} ..."

	if ! tar -C "${tempdir}" --strip-components 2 -xf "${appjail_file}" ./conf/config.conf; then
		lib_err ${EX_IOERR} "An error occurred when extracting conf/config.conf from ${appjail_file}."
	fi

	local conf_keys="osarch osversion jail_type"
	local conf_key conf_value
	# From config.conf
	local param_osarch param_osversion param_jail_type

	for conf_key in ${conf_keys}; do
		conf_value=`lib_ajconf get -Vnit "${tempdir}/config.conf" "${conf_key}"`

		if lib_check_empty "${conf_value}"; then
			lib_err ${EX_CONFIG} -- "${conf_key} is empty or not defined in the configuration file!"
		fi

		setvar param_${conf_key} "${conf_value}"
	done

	case "${param_jail_type}" in
		${JAIL_TYPE_THICK}|${JAIL_TYPE_THIN}) ;;
		*) lib_err ${EX_DATAERR} "This jail uses an invalid jail type (${param_jail_type}) for tiny+import. Valid types are: ${JAIL_TYPE_THICK} and ${JAIL_TYPE_THIN}." ;;
	esac

	jail_install_standard -a "${param_osarch}" -j "${jail_name}" -r "${release_name}" -t "${jail_type}" -v "${param_osversion}"

	local jail_path="${JAILDIR}/${jail_name}"

	jail_mark_dirty "${jail_name}"

	local escape_tempdir=`lib_escape_string "${tempdir}"`
	lib_atexit_add "rm -rf \"${escape_tempdir}\" > /dev/null 2>&1"

	local escape_appjail_file=`lib_escape_string "${appjail_file}"`

	lib_debug "Decompressing ${appjail_file} ..."

	if ! sh -c "tar -C \"${jail_path}\" ${TAR_DECOMPRESS_ARGS} -xf \"${appjail_file}\""; then
		lib_err ${EX_IOERR} "An error occurred when extracting ${appjail_file} to ${jail_path} ..."
	fi

	jail_mark_clean "${jail_name}"
}

jail_destroy()
{
	local _o
	local opt_force=0 fflag=
	local opt_all_dependents=0 Rflag=

	while getopts ":fR" _o; do
		case "${_o}" in
			f)
				opt_force=1
				;;
			R)
				opt_all_dependents=1
				;;
			*)
				jail_usage
				exit ${EX_USAGE}
				;;
		esac
	done
	shift $((OPTIND-1))

	local jail_name="$1"
	if [ -z "${jail_name}" ]; then
		jail_usage
		exit ${EX_USAGE}
	fi

	lib_set_logprefix " [`random_color`${jail_name}${COLOR_DEFAULT}]"

	_jail_chkjfolder "${jail_name}"

	errlevel=$?
	if [ ${errlevel} -ne 0 ]; then
		return ${errlevel}
	fi

	lib_debug "Removing \`${jail_name}\` jail..."

	if [ "${ENABLE_ZFS}" != "0" ]; then
		lib_debug "Using zfs-destroy(8) ..."

		if [ ${opt_force} -eq 1 ]; then
			fflag="-f"
		fi

		if [ ${opt_all_dependents} -eq 1 ]; then
			Rflag="-R"
		fi

		if ! lib_zfs_rrmfs ${fflag} ${Rflag} "${ZFS_JAILS_NAME}/${jail_name}"; then
			lib_err ${EX_IOERR} "Error destroying ${ZFS_JAILS_NAME}/${jail_name}"
		fi
	else
		lib_debug "Removing \`noschg\` flag..."
		if ! chflags -R noschg "${JAILDIR}/${jail_name}"; then
			lib_err ${EX_IOERR} "Error removing the \`noschg\` flag to ${JAILDIR}/${jail_name}"
		fi
	fi

	lib_debug "Removing files..."
	if ! rm -rf "${JAILDIR}/${jail_name}"; then
		lib_err ${EX_IOERR} "Error removing ${JAILDIR}/${jail_name}"
	fi

	lib_debug -- "${jail_name} was removed."
}

jail_mark()
{
	local entity="$1"; shift
	if lib_check_empty "${entity}"; then
		jail_usage
		exit ${EX_USAGE}
	fi

	case "${entity}" in
		clean|dirty|locked|unlocked) ;;
		*) jail_usage; exit ${EX_USAGE} ;;
	esac

	jail_mark_${entity} "$@"
}

jail_mark_clean()
{
	local jail_name="$1"

	_basic_chk_jail "${jail_name}"

	local jail_path="${JAILDIR}/${jail_name}"
	if [ -f "${jail_path}/.done" ]; then
		lib_warn -- "${jail_name} is already clean."
	else
		touch -- "${jail_path}/.done"
	fi
}

jail_mark_dirty()
{
	local jail_name="$1"

	_basic_chk_jail "${jail_name}"

	local jail_path="${JAILDIR}/${jail_name}"
	if [ -f "${jail_path}/.done" ]; then
		rm -f "${jail_path}/.done"
	else
		lib_warn -- "${jail_name} is already dirty."
	fi
}

jail_mark_locked()
{
	local jail_name="$1"

	_basic_chk_jail "${jail_name}"

	local lockdir="${JAILDIR}/${jail_name}/conf/boot/lock"
	if ! mkdir -p "${lockdir}"; then
		lib_err ${EX_IOERR} "Error creating ${lockdir}"
	fi

	if [ -f "${lockdir}/locked" ]; then
		lib_err ${EX_CANTCREAT} "The ${jail_name} jail has been locked. Use \`appjail jail mark unlocked\` to fix it."
	fi

	lib_debug "Locking ${jail_name} ..."

	touch -- "${lockdir}/locked"
}

jail_mark_unlocked()
{
	local jail_name="$1"

	_basic_chk_jail "${jail_name}"

	local lockdir="${JAILDIR}/${jail_name}/conf/boot/lock"

	if [ ! -f "${lockdir}/locked" ]; then
		return 0
	fi

	lib_debug "Unlocking ${jail_name} ..."

	rm -f -- "${lockdir}/locked"
}

jail_get()
{
	local _o
	local jail_name

	local flag_appjail_version=0
	local flag_arch=0
	local flag_boot=0
	local flag_created=0
	local flag_dirty=0
	local flag_devfs_ruleset=0
	local flag_hostname=0
	local flag_ip4=0
	local flag_ip6=0
	local flag_locked=0
	local flag_name=0
	local flag_network_ip4=0
	local flag_networks=0
	local flag_path=0
	local flag_priority=0
	local flag_ports=0
	local flag_release_name=0
	local flag_status=0
	local flag_type=0
	local flag_version=0
	local flag_version_extra=0

	lib_table_init "jail_get"

	lib_table_disable_escape
	lib_table_disable_columns
	lib_table_disable_empty
	lib_table_disable_pretty
	lib_table_disable_tabulate
	
	while getopts ":eHIpt" _o; do
		case "${_o}" in
			e)
				lib_table_enable_escape
				;;
			H)
				lib_table_enable_columns
				;;
			I)
				lib_table_enable_empty
				;;
			p)
				lib_table_enable_pretty
				;;
			t)
				lib_table_enable_tabulate
				;;
			*)
				jail_usage
				exit ${EX_USAGE}
				;;
		esac
	done
	shift $((OPTIND-1))

	jail_name="$1"; shift

	_basic_chk_jail "${jail_name}"

	lib_set_logprefix " [`random_color`${jail_name}${COLOR_DEFAULT}]"

	# General files
	local config_file="${JAILDIR}/${jail_name}/conf/config.conf"
	local template_conf="${JAILDIR}/${jail_name}/conf/template.conf"

	# Status
	local status_config=0
	local status_template=0

	if [ -f "${config_file}" ]; then
		status_config=1
	fi

	if [ -f "${template_conf}" ]; then
		status_template=1
	fi

	if [ $# -eq 0 ]; then
		set -- "status" "name" "type" "version" "ports" "network_ip4"
	fi

	local keyword
	for keyword in "$@"; do
		if lib_check_empty "${keyword}"; then
			continue
		fi

		local value=

		case "${keyword}" in
			appjail_version)
				if [ ${flag_appjail_version} -eq 1 ]; then
					continue
				fi

				if [ ${status_config} -eq 1 ]; then
					value=`lib_ajconf get -t "${config_file}" -Vni appjail_version`
				fi

				flag_appjail_version=1
				;;
			arch)
				if [ ${flag_arch} -eq 1 ]; then
					continue
				fi

				if [ ${status_config} -eq 1 ]; then
					value=`lib_ajconf get -t "${config_file}" -Vni osarch`
				fi
				
				flag_arch=1
				;;
			boot)
				if [ ${flag_boot} -eq 1 ]; then
					continue
				fi

				if [ -f "${JAILDIR}/${jail_name}/conf/boot/startup/boot" ]; then
					value=1
				else
					value=0
				fi

				flag_boot=1
				;;
			created)
				if [ ${flag_created} -eq 1 ]; then
					continue
				fi

				if [ ${status_config} -eq 1 ]; then
					value=`lib_ajconf get -t "${config_file}" -Vni birth`

					if ! lib_check_empty "${value}"; then
						if ! lib_check_number "${value}"; then
							lib_warn -- "${value} is not a valid number to calculate the birth."
						else
							value=`date -r${value} +"${CREATED_FORMAT}"`

							if [ $? -ne 0 ]; then
								lib_warn "Error calculating the birth."
							fi
						fi
					fi
				fi

				flag_created=1
				;;
			dirty)
				if [ ${flag_dirty} -eq 1 ]; then
					continue
				fi

				if [ -f "${JAILDIR}/${jail_name}/.done" ]; then
					value="0"
				else
					value="1"
				fi

				flag_dirty=1
				;;
			devfs_ruleset)
				if [ ${flag_devfs_ruleset} -eq 1 ]; then
					continue
				fi

				if [ -f "${JAILDIR}/${jail_name}/conf/boot/devfs_ruleset" ]; then
					value=`head -1 -- "${JAILDIR}/${jail_name}/conf/boot/devfs_ruleset"`
				fi

				flag_devfs_ruleset=1
				;;
			hostname)
				if [ ${flag_hostname} -eq 1 ]; then
					continue
				fi

				if [ ${status_template} -eq 1 ]; then
					value=`lib_ajconf getColumn -j "${jail_name}" -Ppi host.hostname | tr '\n' ',' | sed -Ee 's/,$/\n/'`
				fi

				flag_hostname=1
				;;
			ip4)
				if [ ${flag_ip4} -eq 1 ]; then
					continue
				fi

				if [ ${status_template} -eq 1 ]; then
					value=`lib_ajconf getColumn -j "${jail_name}" -Ppi ip4.addr | tr '\n' ',' | sed -Ee 's/,$/\n/'`
				fi

				flag_ip4=1
				;;
			ip6)
				if [ ${flag_ip6} -eq 1 ]; then
					continue
				fi

				if [ ${status_template} -eq 1 ]; then
					value=`lib_ajconf getColumn -j "${jail_name}" -Ppi ip6.addr | tr '\n' ',' | sed -Ee 's/,$/\n/'`
				fi

				flag_ip6=1
				;;
			locked)
				if [ ${flag_locked} -eq 1 ]; then
					continue
				fi

				if [ -f "${JAILDIR}/${jail_name}/conf/boot/lock/locked" ]; then
					value=1
				else
					value=0
				fi

				flag_locked=1
				;;
			name)
				if [ ${flag_name} -eq 1 ]; then
					continue
				fi

				value="${jail_name}"

				flag_name=1
				;;
			network_ip4)
				if [ ${flag_network_ip4} -eq 1 ]; then
					continue
				fi

				if [ -d "${JAILDIR}/${jail_name}/conf/boot/network" ]; then
					if [ `ls -A -- "${JAILDIR}/${jail_name}/conf/boot/network" | wc -l` -gt 0 ]; then
						value=`"${APPJAIL_PROGRAM}" network hosts -Rj "${jail_name}" 2> /dev/null | tr '\n' ',' | sed -Ee 's/,$/\n/'`
					fi
				fi

				flag_network_ip4=1
				;;
			networks)
				if [ ${flag_networks} -eq 1 ]; then
					continue
				fi

				if [ -d "${JAILDIR}/${jail_name}/conf/boot/network" ]; then
					if [ `ls -A -- "${JAILDIR}/${jail_name}/conf/boot/network" | wc -l` -gt 0 ]; then
						value=`"${APPJAIL_PROGRAM}" network hosts -ej "${jail_name}" 2> /dev/null | tr '\n' ',' | sed -Ee 's/,$/\n/'`
					fi
				fi

				flag_networks=1
				;;
			path)
				if [ ${flag_path} -eq 1 ]; then
					continue
				fi

				value="${JAILDIR}/${jail_name}/jail"

				flag_path=1
				;;
			priority)
				if [ ${flag_priority} -eq 1 ]; then
					continue
				fi

				if [ -f "${JAILDIR}/${jail_name}/conf/boot/startup/priority" ]; then
					value=`head -1 "${JAILDIR}/${jail_name}/conf/boot/startup/priority"`
				else
					value=0
				fi

				flag_priority=1
				;;
			ports)
				if [ ${flag_ports} -eq 1 ]; then
					continue
				fi

				if [ -d "${JAILDIR}/${jail_name}/conf/boot/expose" ]; then
					if [ `ls -A -- "${JAILDIR}/${jail_name}/conf/boot/expose" | wc -l` -gt 0 ]; then
						value=`"${APPJAIL_PROGRAM}" expose list -HIpt -- "${jail_name}" ports protocol | sed -Ee 's#(.+) (.+)#\1/\2#' | tr '\n' ',' | sed -Ee 's/,$//'`
					fi
				fi

				flag_ports=1
				;;
			release_name)
				if [ ${flag_release_name} -eq 1 ]; then
					continue
				fi

				if [ ${status_config} -eq 1 ]; then
					value=`lib_ajconf get -t "${config_file}" -Vni release_name`
				fi

				flag_release_name=1
				;;
			status)
				if [ ${flag_status} -eq 1 ]; then
					continue
				fi

				value="DOWN"
				if lib_jail_exists "${jail_name}"; then
					if lib_jail_created_by_appjail "${jail_name}"; then
						value="UP"
					fi
				fi

				flag_status=1
				;;
			type)
				if [ ${flag_type} -eq 1 ]; then
					continue
				fi

				if [ ${status_config} -eq 1 ]; then
					value=`lib_ajconf get -t "${config_file}" -Vni jail_type`
				fi

				flag_type=1
				;;
			version)
				if [ ${flag_version} -eq 1 ]; then
					continue
				fi

				if [ ${status_config} -eq 1 ]; then
					value=`lib_ajconf get -t "${config_file}" -Vni osversion`
				fi

				flag_version=1
				;;
			version_extra)
				if [ ${flag_version_extra} -eq 1 ]; then
					continue
				fi

				if [ ${status_config} -eq 1 ]; then
					value=`_jail_get_version_extra "${config_file}"`
				fi

				flag_version_extra=1
				;;
			*)
				lib_warn -- "${keyword}: keyword not found."
				continue
				;;
		esac

		lib_table_set "${keyword}" "${value}"
	done

	lib_table_print
}

_jail_get_version_extra()
{
	local config_file="$1"

	if [ -z "${config_file}" ]; then
		lib_err ${EX_USAGE} "usage: _jail_get_version_extra config_file"
	fi

	local osarch
	osarch=`lib_ajconf get -Vnt "${config_file}" osarch` || return $?

	if lib_check_empty "${osarch}"; then
		return 0
	fi

	local osversion
	osversion=`lib_ajconf get -Vnt "${config_file}" osversion` || return $?

	if lib_check_empty "${osversion}"; then
		return 0
	fi

	local release_name
	release_name=`lib_ajconf get -Vnt "${config_file}" release_name` || return $?

	if lib_check_empty "${release_name}"; then
		return 0
	fi

	local releasedir="${RELEASEDIR}/${osarch}/${osversion}/${release_name}"

	if [ ! -f "${releasedir}/.from_src" ] || [ ! -f "${releasedir}/.srcdir" ]; then
		return 0
	fi

	local srcdir
	srcdir=`head -1 -- "${releasedir}/.srcdir" 2> /dev/null` || return $?

	local param_h="${srcdir}/sys/sys/param.h"

	if [ ! -f "${param_h}" ]; then
		return 0
	fi

	awk '/^#define[[:space:]]*__FreeBSD_version/ {print $3}' "${param_h}" 2> /dev/null
}

jail_list()
{
	local _o
	local opt_escape=1 eflag=
	local opt_columns=1 Hflag=
	local opt_empty=0 Iflag=
	local opt_pretty=1 pflag=
	local opt_tabulate=1 tflag=
	local jail_name=

	while getopts ":eHIptj:" _o; do
		case "${_o}" in
			j)
				if lib_check_empty "${OPTARG}"; then
					jail_usage
					exit ${EX_USAGE}
				fi
				;;
		esac

		case "${_o}" in
			e)
				opt_escape=0
				;;
			H)
				opt_columns=0
				;;
			I)
				opt_empty=1
				;;
			p)
				opt_pretty=0
				;;
			t)
				opt_tabulate=0
				;;
			j)
				jail_name="${OPTARG}"
				;;
			*)
				jail_usage
				exit ${EX_USAGE}
				;;
		esac
	done
	shift $((OPTIND-1))

	if [ ${opt_escape} -eq 1 ]; then
		eflag="-e"
	fi

	if [ ${opt_columns} -eq 1 ]; then
		Hflag="-H"
	fi

	if [ ${opt_empty} -eq 1 ]; then
		Iflag="-I"
	fi

	if [ ${opt_pretty} -eq 1 ]; then
		pflag="-p"
	fi

	if [ ${opt_tabulate} -eq 1 ]; then
		tflag="-t"
	fi

	if [ -n "${jail_name}" ]; then
		jail_get ${eflag} ${Hflag} ${Iflag} ${pflag} ${tflag} -- "${jail_name}" "$@"
		return $?
	fi

	if [ ! -d "${JAILDIR}" ] || [ `lib_countfiles "${JAILDIR}"` -eq 0 ]; then
		return
	fi

	local output
	output=`lib_generate_tempfile` || exit $?

	local escape_output
	escape_output=`lib_escape_string "${output}"`

	lib_atexit_add "rm -f \"${escape_output}\""

	local first=1
	local jobs=0

	for jail_name in "${JAILDIR}/"*; do
		jail_name="${jail_name##*/}"

		if [ ${first} -eq 1 ]; then
			jail_get ${eflag} ${Hflag} ${Iflag} ${tflag} -- "${jail_name}" "$@"

			first=0
		else
			jail_get ${eflag} ${Iflag} ${tflag} -- "${jail_name}" "$@" &

			jobs=$((jobs+1))

			# Limit the number of jobs per CPU.
			if [ ${jobs} -ge ${JOBS} ]; then
				# Reset.
				jobs=0

				wait || exit $?
			fi
		fi
	done > "${output}"

	wait || exit $?

	if [ ${opt_pretty} -eq 1 ]; then
		column -ts $'\t' -- "${output}"
	else
		cat -- "${output}"
	fi
}

jail_priority()
{
	local _o
	local priority=

	while getopts ":p:" _o; do
		case "${_o}" in
			p)
				if lib_check_empty "${OPTARG}"; then
					jail_usage
					exit ${EX_USAGE}
				fi
				;;
		esac

		case "${_o}" in
			p)
				priority="${OPTARG}"
				;;
			*)
				jail_usage
				exit ${EX_USAGE}
				;;
		esac
	done
	shift $((OPTIND-1))

	if ! lib_check_number "${priority}"; then
		lib_err ${EX_DATAERR} "priority must be a number!"
	fi

	local jail_name="$1"

	_basic_chk_jail "${jail_name}"

	local startupdir="${JAILDIR}/${jail_name}/conf/boot/startup"
	if ! mkdir -p "${startupdir}"; then
		lib_err ${EX_IOERR} "Error creating ${startupdir}"
	fi

	echo "${priority}" > "${startupdir}/priority"
}

jail_rename()
{
	local errlevel
	local old new

	old="$1"; new="$2"
	if lib_check_empty "${old}" || lib_check_empty "${new}"; then
		jail_usage
		exit ${EX_USAGE}
	fi

	_jail_chkjfolder "${old}"

	errlevel=$?
	if [ ${errlevel} -ne 0 ]; then
		return ${errlevel}
	fi

	if ! lib_check_jailname "${new}"; then
		lib_err ${EX_DATAERR} "Invalid jail name \"${new}\""
	fi

	if [ -e "${JAILDIR}/${new}" ]; then
		lib_err ${EX_CANTCREAT} -- "The ${new} jail already exists."
	fi

	lib_debug "Renaming ${old} to ${new} ..."

	if [ "${ENABLE_ZFS}" != "0" ]; then
		if ! lib_zfs_jail_rename "${old}" "${new}"; then
			lib_err ${EX_SOFTWARE} "Cannot rename ${old} to ${new}."
		fi

		if ! rm -rf "${JAILDIR}/${old}"; then
			lib_err ${EX_SOFTWARE} "Cannot remove ${JAILDIR}/${old}"
		fi
	else
		if ! mv "${JAILDIR}/${old}" "${JAILDIR}/${new}"; then
			lib_err ${EX_SOFTWARE} "Cannot rename ${old} to ${new}."
		fi
	fi
}

_jail_chkjfolder()
{
	local jail_name
	local mounted

	jail_name="$1"
	if [ -z "${jail_name}" ]; then
		lib_err ${EX_USAGE} "_jail_chkjfolder jail_name"
	fi

	_basic_chk_jail "${jail_name}"
	
	if lib_jail_exists "${jail_name}"; then
		if lib_jail_created_by_appjail "${jail_name}"; then
			lib_warn -- "${jail_name} is currently running."
			return ${EX_NOPERM}
		fi
	fi

	local tflag=
	if [ "${ENABLE_ZFS}" != 0 ]; then
		tflag="-t nozfs"
	fi

	mounted=`lib_mountpoint_mounted -F '%2 -> %1' ${tflag} "${JAILDIR}/${jail_name}/"`
	if [ -n "${mounted}" ]; then
		lib_warn "The jail directory (${JAILDIR}/${jail_name}) has one or more mounted file systems:"
		printf "%s\n" "${mounted}" | while IFS= read -r line; do
			lib_warn "    - ${line}"
		done
		return ${EX_NOPERM}
	fi
}

_basic_chk_jail()
{
	local jail_name="$1"
	if lib_check_empty "${jail_name}"; then
		jail_usage
		exit ${EX_USAGE}
	fi

	if ! lib_check_jailname "${jail_name}"; then
		lib_err ${EX_DATAERR} "Invalid jail name \"${jail_name}\""
	fi

	if [ ! -d "${JAILDIR}/${jail_name}" ]; then
		lib_err ${EX_NOINPUT} "Cannot find the jail \`${jail_name}\`"
	fi
}

_chk_jail()
{
	local jail_name="$1"
	
	if [ -z "${jail_name}" ]; then
		lib_err ${EX_USAGE} "_chk_jail jail_name"
	fi

	if ! lib_check_jailname "${jail_name}"; then
		lib_err ${EX_DATAERR} "Invalid jail name \"${jail_name}\""
	fi

	local jail_path="${JAILDIR}/${jail_name}"
	if [ -f "${jail_path}/.done" ]; then
		lib_err ${EX_CANTCREAT} "The \"${jail_name}\" jail is already created."
	fi

	if [ -d "${jail_path}" ] && [ `ls -A "${jail_path}" | wc -l` -gt 0 ]; then
		lib_warn -- "${jail_name} is dirty. Removing..."

		if ! jail_destroy "${jail_name}"; then
			lib_err ${EX_SOFTWARE} "There was a problem destroying the dirty jail \`${jail_name}\`. You must remove it using \`appjail jail destroy\`."
		fi
	fi
}

_chk_jail_export()
{
	local jail_name="$1"

	if [ -z "${jail_name}" ]; then
		lib_err ${EX_USAGE} "_chk_jail_export jail_name"
	fi

	_basic_chk_jail "${jail_name}"

	if lib_jail_exists "${jail_name}"; then
		if lib_jail_created_by_appjail "${jail_name}"; then
			lib_warn -- "${jail_name} is currently running."
			exit 0
		fi
	fi
}

_chk_jail_export_zfs()
{
	local jail_name="$1"

	if [ -z "${jail_name}" ]; then
		lib_err ${EX_USAGE} "_chk_jail_export_zfs jail_name"
	fi

	_basic_chk_jail "${jail_name}"
}

_compress2cmd()
{
	local compress="$1"

	if [ -z "${compress}" ]; then
		lib_err ${EX_USAGE} "usage: _compress2cmd compress"
	fi

	local cmd
	case "${compress}" in
		bzip)
			cmd="${BZIP_COMPRESS_CMD}"
			;;
		gzip)
			cmd="${GZIP_COMPRESS_CMD}"
			;;
		lrzip)
			cmd="${LRZIP_COMPRESS_CMD}"
			;;
		lz4)
			cmd="${LZ4_COMPRESS_CMD}"
			;;
		lzma)
			cmd="${LZMA_COMPRESS_CMD}"
			;;
		lzop)
			cmd="${LZOP_COMPRESS_CMD}"
			;;
		xz)
			cmd="${XZ_COMPRESS_CMD}"
			;;
		zstd)
			cmd="${ZSTD_COMPRESS_CMD}"
			;;
		*)
			lib_err ${EX_DATAERR} "Invalid compression algorithm: ${compress}"
			;;
	esac

	echo "${cmd}"
}

_mimetype2decompress_cmd()
{
	local errlevel=0
	local input="$1"

	if [ -z "${input}" ]; then
		lib_err ${EX_USAGE} "usage: _mimetype2decompress_cmd input"
	fi

	local mime_type
	mime_type=`lib_files_getmime "${input}"`

	errlevel=$?
	if [ ${errlevel} -ne 0 ]; then
		lib_err ${errlevel} "Error in determining the mime type."
	fi

	local cmd=
	case "${mime_type}" in
		application/x-bzip2)
			cmd=`_decompress2cmd "bzip"`
			;;
		application/gzip)
			cmd=`_decompress2cmd "gzip"`
			;;
		application/x-lz4)
			cmd=`_decompress2cmd "lz4"`
			;;
		application/x-lzma)
			cmd=`_decompress2cmd "lzma"`
			;;
		application/x-xz)
			cmd=`_decompress2cmd "xz"`
			;;
		application/zstd)
			cmd=`_decompress2cmd "zstd"`
			;;
		application/octet-stream)
			local file_type=`file -Eb -- "${input}"`

			errlevel=$?
			if [ ${errlevel} -ne 0 ]; then
				lib_err ${errlevel} "Error in determining the file type."
			fi

			case "${file_type}" in
				"ZFS snapshot"*)
					;;
				lzop*)
					cmd=`_decompress2cmd "lzop"`
					;;
				LRZIP*)
					cmd=`_decompress2cmd "lrzip"`
					;;
				*)
					lib_err ${EX_DATAERR} "Invalid file type: ${file_type}"
					;;
			esac
			;;
		*)
			lib_err ${EX_DATAERR} "Invalid file type: ${mime_type}"
			;;
	esac

	echo "${cmd}"
}

_decompress2cmd()
{
	local compress="$1"

	if [ -z "${compress}" ]; then
		lib_err ${EX_USAGE} "usage: _decompress2cmd compress"
	fi

	local compress_args
	case "${compress}" in
		bzip)
			compress_args="${BZIP_DECOMPRESS_CMD}"
			;;
		gzip)
			compress_args="${GZIP_DECOMPRESS_CMD}"
			;;
		lrzip)
			compress_args="${LRZIP_DECOMPRESS_CMD}"
			;;
		lz4)
			compress_args="${LZ4_DECOMPRESS_CMD}"
			;;
		lzma)
			compress_args="${LZMA_DECOMPRESS_CMD}"
			;;
		lzop)
			compress_args="${LZOP_DECOMPRESS_CMD}"
			;;
		xz)
			compress_args="${XZ_DECOMPRESS_CMD}"
			;;
		zstd)
			compress_args="${ZSTD_DECOMPRESS_CMD}"
			;;
		*)
			lib_err ${EX_DATAERR} "Invalid compression algorithm: ${compress}"
			;;
	esac

	echo "${compress_args}"
}

_compress2tar_args()
{
	local compress="$1"

	if [ -z "${compress}" ]; then
		lib_err ${EX_USAGE} "usage: _compress2tar_args compress"
	fi

	local tar_args
	case "${compress}" in
		bzip)
			tar_args="${TAR_BZIP_ARGS}"
			;;
		gzip)
			tar_args="${TAR_GZIP_ARGS}"
			;;
		lrzip)
			tar_args="${TAR_LRZIP_ARGS}"
			;;
		lz4)
			tar_args="${TAR_LZ4_ARGS}"
			;;
		lzma)
			tar_args="${TAR_LZMA_ARGS}"
			;;
		lzop)
			tar_args="${TAR_LZOP_ARGS}"
			;;
		xz)
			tar_args="${TAR_XZ_ARGS}"
			;;
		zstd)
			tar_args="${TAR_ZSTD_ARGS}"
			;;
		*)
			lib_err ${EX_DATAERR} "Invalid compression algorithm: ${compress}"
			;;
	esac

	echo "${tar_args}"
}

_warn_dirty_release()
{
	local releasedir="$1"

	if [ -z "${releasedir}" ]; then
		lib_err ${EX_USAGE} "usage: _warn_dirty_release releasedir"
	fi

	if [ ! -f "${releasedir}/.done" ]; then
		lib_warn "Dirty release directory detected, please note that your jail will not be able to run properly."
		lib_warn "Consider reinstalling the release directory used by this jail."
	fi
}

jail_help()
{
	man 1 appjail-jail
}

jail_usage()
{
	cat << EOF
usage: jail boot [off|on] <jail>
       jail clean
       jail create [-a <architecture>] [-I <install-method>] [-i <initscript>]
               [-r <release>] [-T <type>] [-t <template>] [-v <version>] <jail>
       jail destroy [-fR] <jail>
       jail get [-eHIpt] <jail> [<keyword> ...]
       jail list [-eHIpt] [-j <jail>] [<keyword> ...]
       jail mark [clean|dirty] <jail>
       jail mark [locked|unlocked] <jail>
       jail priority -p <priority> <jail>
       jail rename <jail> <new-name>
EOF
}
