#!/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}/colors"
lib_load "${LIBDIR}/jail"
lib_load "${LIBDIR}/kern_modules"
lib_load "${LIBDIR}/mksum"
lib_load "${LIBDIR}/mount"
lib_load "${LIBDIR}/random"
lib_load "${LIBDIR}/replace"
lib_load "${LIBDIR}/zfs"

FETCH_MANIFEST_FILE="MANIFEST"

fetch_desc="Fetch and extract components to create jails."

fetch_main()
{
	local entity="$1"; shift
	if lib_check_empty "${entity}"; then
		entity="${DEFAULT_FETCH_METHOD}"
	fi

	case "${entity}" in
		debootstrap|destroy|empty|list|local|src|www) ;;
		*) fetch_usage; exit ${EX_USAGE} ;;
	esac

	fetch_${entity} "$@"
}

fetch_list()
{
	local _o
	local file
	local osarch osversion name
	local opt_escape=1
	local opt_columns=1
	local opt_pretty=1

	while getopts ":eHp" _o; do
		case "${_o}" in
			e)
				opt_escape=0
				;;
			H)
				opt_columns=0
				;;
			p)
				opt_pretty=0
				;;
			*)
				fetch_usage
				exit ${EX_USAGE}
				;;
		esac
	done
	shift $((OPTIND-1))
	
	file="$1"

	osarch=`printf "%s" "${file}" | awk -F/ '{print $1}'`
	osversion=`printf "%s" "${file}" | awk -F/ '{print $2}'`

	if [ -n "${osversion}" ]; then
		keywords="NAME"

		if [ ! -d "${RELEASEDIR}/${osarch}/${osversion}" ]; then
			lib_err ${EX_NOINPUT} "Version ${osversion} not found."
		fi
	elif [ -n "${osarch}" ]; then
		keywords="VERSION\tNAME"

		if [ ! -d "${RELEASEDIR}/${osarch}" ]; then
			lib_err ${EX_NOINPUT} "Arch ${osarch} not found."
		fi
	else
		keywords="ARCH\tVERSION\tNAME"

		if ! lib_zfs_mkdir "${RELEASEDIR}" "${ZFS_RELEASE_NAME}"; then
			lib_err ${EX_IOERR} "Error creating ${RELEASEDIR}"
		fi
	fi

	if [ -n "${osversion}" ]; then
		(cd "${RELEASEDIR}/${osarch}/${osversion}"; find . -depth 1 -maxdepth 3)
	elif [ -n "${osarch}" ]; then
		(cd "${RELEASEDIR}/${osarch}"; find . -depth 2 -maxdepth 3)
	else
		(cd "${RELEASEDIR}"; find . -depth 3 -maxdepth 3)
	fi |\
		sed -Ee 's#\./##' |\
		{
			if [ ${opt_columns} -eq 1 ]; then
				echo -e "${keywords}"
			fi
			while IFS= read -r file; do
				if [ ${opt_pretty} -eq 1 -o ${opt_escape} -eq 1 ]; then
					file=`printf "%s" "${file}" | sed -Ee 's/\t/<TAB>/g'`
				fi

				file=`printf "%s" "${file}" | tr '/' '\t'`

				printf "%s\n" "${file}"
			done
		} | \
			if [ ${opt_pretty} -eq 1 ]; then
				column -ts $'\t'
			else
				cat
			fi
}

fetch_debootstrap()
{
	# Load linux kernel modules
	lib_modules_linuxmods

	local _o
	local opt_apt_cache_start=1 apt_cache_start="${APT_CACHE_START}"
	local osarch="${DEBOOTSTRAP_ARCH}"
	local mirror="${DEBOOTSTRAP_MIRROR}"
	local release_name="${DEFAULT_RELEASE}"
	local script="${DEBOOTSTRAP_SCRIPT}"
	local errlevel=0

	while getopts ":Aa:c:m:r:S:" _o; do
		case "${_o}" in
			a|c|m|r|S)
				if lib_check_empty "${OPTARG}"; then
					fetch_usage
					exit ${EX_USAGE}
				fi
				;;
		esac

		case "${_o}" in
			A)
				opt_apt_cache_start=0
				;;
			a)
				osarch="${OPTARG}"
				;;
			c)
				apt_cache_start="${OPTARG}"
				;;
			m)
				mirror="${OPTARG}"
				;;
			r)
				release_name="${OPTARG}"
				;;
			S)
				script="${OPTARG}"
				;;
			*)
				fetch_usage
				exit ${EX_USAGE}
				;;
		esac
	done
	shift $((OPTIND-1))

	if ! which -s "debootstrap"; then
		lib_err ${EX_UNAVAILABLE} "debootstrap(8) is not installed. Cannot continue ..."
	fi

	local suite_name="$1"
	if lib_check_empty "${suite_name}"; then
		fetch_usage
		exit ${EX_USAGE}
	fi

	if ! lib_check_number "${apt_cache_start}"; then
		lib_err ${EX_DATAERR} "Invalid number: ${apt_cache_start}"
	fi

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

	_fetch_atomic_init -a "${osarch}" -r "${release_name}" -v "${suite_name}"

	if ! lib_zfs_mkdir "${releasedir}" "${ZFS_RELEASE_NAME}/${osarch}/${suite_name}/${release_name}"; then
		lib_err ${EX_IOERR} "Error creating ${releasedir}"
	fi

	_fetch_atomic_mid "${releasedir}"

	if ! lib_zfs_mkdir "${releasedir}/linux_debootstrap" "${ZFS_RELEASE_NAME}/${osarch}/${suite_name}/${release_name}/linux_debootstrap"; then
		lib_err ${EX_IOERR} "Error creating ${releasedir}/linux_debootstrap"
	fi

	local escape_osarch=`lib_escape_string "${osarch}"`
	local escape_releasedir=`lib_escape_string "${releasedir}"`
	local escape_suite_name=`lib_escape_string "${suite_name}"`
	local debootstrap_cmd=`lib_multi_replace "${DEBOOTSTRAP_CMD}" a "\"${escape_osarch}\"" o "\"${escape_releasedir}/linux_debootstrap\"" s "\"${escape_suite_name}\""`

	if [ -n "${mirror}" -a -n "${script}" ]; then
		debootstrap_cmd="${debootstrap} \"${mirror}\" \"${script}\""
	elif [ -n "${mirror}" ]; then
		debootstrap_cmd="${debootstrap} \"${mirror}\""
	fi

	sh -c "${debootstrap_cmd}"

	errlevel=$?
	if [ ${errlevel} -ne 0 ]; then
		lib_err ${errlevel} "\`${debootstrap_cmd}\` exits with a non-zero exit status."
	fi

	if [ ${opt_apt_cache_start} -eq 1 ]; then
		lib_debug "Increasing APT::Cache-Start to ${apt_cache_start}"

		echo "APT::Cache-Start ${apt_cache_start};" > "${releasedir}/linux_debootstrap/etc/apt/apt.conf.d/00aptitude"
	fi

	_fetch_atomic_end "${releasedir}"

	lib_debug "Done."
}

fetch_destroy()
{
	local _o
	local opt_force=0 fflag=
	local opt_all_dependents=0 Rflag=
	local osarch="${FREEBSD_ARCH}"
	local osversion="${FREEBSD_VERSION}"
	local release_name

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

	while getopts ":fRa:v:" _o; do
		case "${_o}" in
			a|v)
				if lib_check_empty "${OPTARG}"; then
					fetch_usage
					exit ${EX_USAGE}
				fi
				;;
		esac

		case "${_o}" in
			f)
				opt_force=1
				;;
			R)
				opt_all_dependents=1
				;;
			a)
				osarch="${OPTARG}"
				;;
			v)
				osversion="${OPTARG}"
				;;
			*)
				fetch_usage
				exit ${EX_USAGE}
				;;
		esac
	done
	shift $((OPTIND-1))

	release_name="$1"
	if lib_check_empty "${release_name}"; then
		fetch_usage
		exit ${EX_USAGE}
	fi

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

	releasedir="${RELEASEDIR}/${osarch}/${osversion}/${release_name}"
	if [ ! -d "${releasedir}" ]; then
		lib_err ${EX_NOINPUT} "Cannot find the release \`${release_name}\`."
	fi

	used_by=`lib_mountpoint_mounted -p "${releasedir}"`
	if [ -n "${used_by}" ]; then
		lib_warn "The release directory (${releasedir}) has been used by:"
		printf "%s\n" "${used_by}" | while IFS= read -r line; do
			lib_warn "    - ${line}"
		done
		return ${EX_NOPERM}
	fi

	lib_debug "Removing \`${release_name}\` release..."

	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_RELEASE_NAME}/${osarch}/${osversion}/${release_name}"; then
			lib_err ${EX_IOERR} "Error destroying ${ZFS_RELEASE_NAME}/${osarch}/${osversion}/${release_name}"
		fi
	else
		lib_debug "Removing \`noschg\` flag..."
		if ! chflags -R noschg "${releasedir}"; then
			lib_err ${EX_IOERR} "Error removing the \`noschg\` flag to ${releasedir}"
		fi
	fi

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

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

fetch_local()
{
	_fetch_cmd -m local "$@"
}

fetch_www()
{
	_fetch_cmd -m www "$@"
}

fetch_empty()
{
	local osarch="any"
	local osversion="any"
	local _o

	while getopts ":a:v:" _o; do
		case "${_o}" in
			a|v)
				if lib_check_empty "${OPTARG}"; then
					fetch_usage
					exit ${EX_USAGE}
				fi
				;;
		esac

		case "${_o}" in
			a)
				osarch="${OPTARG}"
				;;
			v)
				osversion="${OPTARG}"
				;;
			*)
				fetch_usage
				exit ${EX_USAGE}
				;;
		esac
	done
	shift $((OPTIND-1))

	local release_name="$1"

	if lib_check_empty "${release_name}"; then
		release_name="${DEFAULT_RELEASE}"
	fi

	if ! lib_zfs_mkdir "${RELEASEDIR}" "${ZFS_RELEASE_NAME}"; then
		lib_err ${EX_IOERR} "Error creating ${RELEASEDIR}"
	fi

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

	if ! lib_zfs_mkdir "${releasedir}/release" "${ZFS_RELEASE_NAME}/${osarch}/${osversion}/${release_name}/release"; then
		lib_err ${EX_IOERR} "Error creating ${releasedir}/release"
	fi

	_fetch_atomic_init -a "${osarch}" -r "${release_name}" -v "${osversion}"

	if ! lib_zfs_mkdir "${releasedir}" "${ZFS_RELEASE_NAME}/${osarch}/${osversion}/${release_name}"; then
		lib_err ${EX_IOERR} "Error creating ${releasedir}"
	fi

	_fetch_atomic_mid "${releasedir}"

	touch -- "${releasedir}/.empty"

	_fetch_atomic_end "${releasedir}"

	printf "%s\n" "${releasedir}"
}

fetch_src()
{
	local _o
	local opt_build=0
	local opt_delete_old=1
	local opt_world=1
	local opt_kernel=0
	local opt_distribution=1
	local opt_distrib_dirs=1
	local arch="${TARGET_ARCH}"
	local jobs="${JOBS}"
	local kernel="${KERNEL}"
	local source_tree="${SRCDIR}"
	local errlevel

	while getopts ":bDIkNRa:j:K:s:" _o; do
		case "${_o}" in
			a|j|K|s)
				if lib_check_empty "${OPTARG}"; then
					fetch_usage
					exit ${EX_USAGE}
				fi
				;;
		esac

		case "${_o}" in
			b)
				opt_build=1
				;;
			D)
				opt_delete_old=0
				;;
			I)
				opt_world=0
				;;
			k)
				opt_kernel=1
				;;
			N)
				opt_distribution=0
				;;
			R)
				opt_distrib_dirs=0
				;;
			a)
				arch="${OPTARG}"
				;;
			j)
				jobs="${OPTARG}"
				;;
			K)
				kernel="${OPTARG}"
				;;
			s)
				source_tree="${OPTARG}"
				;;
			*)
				fetch_usage
				exit ${EX_USAGE}
				;;
		esac
	done
	shift $((OPTIND-1))

	local release_name="$1"; shift
	
	if [ "${release_name}" = "-" ] || lib_check_empty "${release_name}"; then
		release_name="${DEFAULT_RELEASE}"
	fi

	# Some checks.

	if [ ! -d "${source_tree}" ]; then
		lib_err ${EX_NOINPUT} -- "${source_tree}: Source tree cannot be found."
	fi

	if ! lib_check_number "${jobs}"; then
		lib_err ${EX_DATAERR} -- "${jobs}: Jobs must be a number."
	fi

	# TARGET/TARGET_ARCH.

	local target target_arch

	target=`lib_jailparam_name "${arch}" /`
	
	if ! lib_check_target "${source_tree}" "${target}"; then
		lib_err ${EX_NOINPUT} -- "${target}: Invalid TARGET."
	fi

	target_arch=`lib_jailparam_value "${arch}" /`

	if ! lib_check_empty "${target_arch}"; then
		if lib_check_ispath "${target_arch}" || ! lib_check_target_arch "${source_tree}" "${target_arch}"; then
			lib_err ${EX_DATAERR} -- "${target_arch}: Invalid TARGET_ARCH."
		fi
	fi

	if [ ${opt_kernel} -eq 1 ]; then
		if ! lib_check_kernelconf "${source_tree}" "${target}" "${kernel}"; then
			lib_err ${EX_NOINPUT} -- "${kernel}: Kernel configuration cannot be found."
		fi
	fi

	local newvers_file="${source_tree}/sys/conf/newvers.sh"

	if [ ! -f "${newvers_file}" ]; then
		lib_err ${EX_OSFILE} -- "${newvers_file}: File required but does not exist."
	fi

	local revision branch

	revision=`grep -Ee '^REVISION=' "${newvers_file}" | sed -Ee 's/.+="([^"]+)"/\1/'`
	branch=`grep -Ee '^BRANCH=' "${newvers_file}" | sed -Ee 's/.+="([^"]+)"/\1/'`
	
	if lib_check_empty "${revision}" || lib_check_empty "${branch}"; then
		lib_err ${EX_OSFILE} -- "${newvers_file}: REVISION or BRANCH cannot be obtained."
	fi

	local release="${revision}-${branch}"
	# Remove -pXX.
	release=`printf "%s" "${release}" | sed -Ee 's/\-p[0-9]+$//'`

	local releasedir="${RELEASEDIR}/${target}/${release}/${release_name}"

	if ! lib_zfs_mkdir "${releasedir}/release" "${ZFS_RELEASE_NAME}/${target}/${release}/${release_name}/release"; then
		lib_err ${EX_IOERR} "Error creating ${releasedir}/release"
	fi

	if ! lib_zfs_mkdir "${releasedir}" "${ZFS_RELEASE_NAME}/${target}/${release}/${release_name}"; then
		lib_err ${EX_IOERR} "Error creating ${releasedir}"
	fi

	if ! touch -- "${releasedir}/.from_src"; then
		lib_err ${EX_IOERR} "Error creating ${releasedir}/.from_src"
	fi

	# Write options used by update jail.
	
	printf "%s" "${arch}" > "${releasedir}/.arch" || exit $?
	printf "%s" "${target}" > "${releasedir}/.target" || exit $?
	if ! lib_check_empty "${target_arch}"; then
		printf "%s" "${target_arch}" > "${releasedir}/.target_arch" || exit $?
	fi
	printf "%s" "${kernel}" > "${releasedir}/.kernconf" || exit $?
	printf "%s" "${source_tree}" > "${releasedir}/.srcdir" || exit $?

	# TARGET_ARCH variable.

	local _target_arch_arg=

	if ! lib_check_empty "${target_arch}"; then
		_target_arch_arg="TARGET_ARCH=${target_arch}"
	fi

	# Log.

	local logname
	logname=`sh -c "${BUILDLOG_NAME}"`

	errlevel=$?
	if [ ${errlevel} -ne 0 ]; then
		lib_err ${errlevel} "{BUILDLOG_NAME} exits with a non-zero exit status."
	fi

	if lib_check_ispath "${logname}"; then
		lib_err ${EX_DATAERR} -- "${logname}: invalid log name."
	fi

	lib_zfs_mklogdir "releases" "${release_name}" "build"

	local build_name="releases/${release_name}/build/${logname}"
	local buildlog="${LOGDIR}/${build_name}"

	lib_info "Build log will be ${build_name}"

	if [ ${opt_build} -eq 1 ]; then
		rm -f -- "${releasedir}/.done_buildworld" || exit $?

		lib_info "Starting buildworld with ${jobs} jobs ..."

		make -C "${source_tree}" -j${jobs} buildworld \
			TARGET="${target}" ${_target_arch_arg} ${MAKEARGS} "$@" >> "${buildlog}" 2>&1

		errlevel=$?

		if [ ${errlevel} -eq 0 ]; then
			lib_info "buildworld finished!"

			touch -- "${releasedir}/.done_buildworld" || exit $?
		else
			lib_err ${errlevel} "buildworld failed. Run \`appjail logs read ${build_name}\` for details."
		fi

		if [ ${opt_kernel} -eq 1 ]; then
			rm -f -- "${releasedir}/.buildkernel"

			lib_info "Starting buildkernel with ${jobs} jobs ..."

			make -C "${source_tree}" -j${jobs} buildkernel \
				TARGET="${target}" ${_target_arch_arg} \
				KERNCONF="${kernel}" ${MAKEARGS} "$@" >> "${buildlog}" 2>&1

			errlevel=$?

			if [ ${errlevel} -eq 0 ]; then
				lib_info "buildkernel finished!"

				touch -- "${releasedir}/.buildkernel"
			else
				lib_err ${errlevel} "buildkernel failed. Run \`appjail logs read ${build_name}\` for details."
			fi
		fi
	fi

	if [ ${opt_world} -eq 1 ]; then
		rm -f -- "${releasedir}/.done_installworld" || exit $?

		lib_info "Starting installworld with ${jobs} jobs ..."

		make -C "${source_tree}" -j${jobs} installworld \
			DESTDIR="${releasedir}/release" DB_FROM_SRC=1 \
			TARGET="${target}" ${_target_arch_arg} \
			${MAKEARGS} "$@" >> "${buildlog}" 2>&1

		errlevel=$?

		if [ ${errlevel} -eq 0 ]; then
			lib_info "installworld finished!"

			touch -- "${releasedir}/.done_installworld" || exit $?
		else
			lib_err ${errlevel} "installworld failed. Run \`appjail logs read ${build_name}\` for details."
		fi
	fi

	if [ ${opt_distrib_dirs} -eq 1 ]; then
		rm -f -- "${releasedir}/.done_distrib-dirs" || exit $?

		lib_info "Starting distrib-dirs ..."

		make -C "${source_tree}" -j${jobs} distrib-dirs \
			DESTDIR="${releasedir}/release" DB_FROM_SRC=1 \
			TARGET="${target}" ${_target_arch_arg} \
			${MAKEARGS} "$@" >> "${buildlog}" 2>&1

		errlevel=$?

		if [ ${errlevel} -eq 0 ]; then
			lib_info "distrib-dirs finished!"

			touch -- "${releasedir}/.done_distrib-dirs" || exit $?
		else
			lib_err ${errlevel} "distrib-dirs failed. Run \`appjail logs read ${build_name}\` for details."
		fi
	fi

	if [ ${opt_distribution} -eq 1 ]; then
		rm -f -- "${releasedir}/.done_distribution" || exit $?

		lib_info "Starting distribution ..."

		make -C "${source_tree}" -j${jobs} distribution \
			DESTDIR="${releasedir}/release" DB_FROM_SRC=1 \
			TARGET="${target}" ${_target_arch_arg} \
			${MAKEARGS} "$@" >> "${buildlog}" 2>&1

		errlevel=$?

		if [ ${errlevel} -eq 0 ]; then
			lib_info "distribution finished!"

			touch -- "${releasedir}/.done_distribution" || exit $?
		else
			lib_err ${errlevel} "distribution failed. Run \`appjail logs read ${build_name}\` for details."
		fi
	fi

	if [ ${opt_kernel} -eq 1 ]; then
		rm -f -- "${releasedir}/.done_installkernel" || exit $?

		lib_info "Starting installkernel with ${jobs} jobs ..."

		make -C "${source_tree}" -j${jobs} installkernel \
			DESTDIR="${releasedir}/release" KERNCONF="${kernel}" \
			TARGET="${target}" ${_target_arch_arg} \
			${MAKEARGS} "$@" >> "${buildlog}" 2>&1
		
		errlevel=$?

		if [ ${errlevel} -eq 0 ]; then
			lib_info "installkernel finished!"

			touch -- "${releasedir}/.done_installkernel" || exit $?
		else
			lib_err ${errlevel} "installkernel failed. Run \`appjail logs read ${build_name}\` for details."
		fi
	fi

	if [ ${opt_delete_old} -eq 1 ]; then
		rm -f -- "${releasedir}/.done_delete-old" || exit $?

		lib_info "Starting delete-old delete-old-libs ..."

		"${APPJAIL_PROGRAM}" deleteOld release \
			-a "${target}" -v "${release}" -- "${release_name}" >> "${buildlog}" 2>&1
	
		errlevel=$?

		if [ ${errlevel} -eq 0 ]; then
			lib_info "delete-old delete-old-libs finished!"

			touch -- "${releasedir}/.done_delete-old" || exit $?
		else
			lib_err ${errlevel} "delete-old delete-old-libs failed. Run \`appjail logs read ${build_name}\` for details."
		fi
	fi
}

_fetch_cmd()
{
	local _o
	local opt_chk_manifest=1
	local freebsd_arch="${FREEBSD_ARCH}"
	local method="${DEFAULT_METHOD}"
	local release_name="${DEFAULT_RELEASE}"
	local downloadurl="${DOWNLOADURL}"
	local freebsd_version="${FREEBSD_VERSION}"
	local errlevel=0
	local component

	while getopts ":Ca:m:r:u:v:" _o; do
		case "${_o}" in
			a|m|r|u|v)
				if lib_check_empty "${OPTARG}"; then
					fetch_usage
					exit ${EX_USAGE}
				fi
				;;
		esac

		case "${_o}" in
			C)
				opt_chk_manifest=0
				;;
			a)
				freebsd_arch="${OPTARG}"
				;;
			m)
				method="${OPTARG}"
				;;
			r)
				release_name="${OPTARG}"
				;;
			u)
				downloadurl="${OPTARG}"
				;;
			v)
				freebsd_version="${OPTARG}"
				;;
			*)
				fetch_usage
				exit ${EX_USAGE}
				;;
		esac
	done
	shift $((OPTIND-1))

	if [ $# -eq 0 ]; then
		local args_list
		local total_items
		local current_index=0

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

		while [ ${current_index} -lt ${total_items} ]; do 
			current_index=$((current_index+1))
			component=`printf "%s\n" "${args_list}" | head -${current_index} | tail -n 1`

			set -- "$@" "${component}"
		done
	fi

	if [ $# -eq 0 ]; then
		fetch_usage
		exit ${EX_USAGE}
	fi
	
	if ! lib_zfs_mkdir "${COMPONENTSDIR}" "${ZFS_COMPONENTS_NAME}"; then
		lib_err ${EX_IOERR} "Error creating ${COMPONENTSDIR}"
	fi

	if ! lib_zfs_mkdir "${RELEASEDIR}" "${ZFS_RELEASE_NAME}"; then
		lib_err ${EX_IOERR} "Error creating ${RELEASEDIR}"
	fi

	local downloadurl=`lib_multi_replace "${downloadurl}" a "${freebsd_arch}" v "${freebsd_version}"`
	local componentsdir="${COMPONENTSDIR}/${freebsd_arch}/${freebsd_version}/${release_name}"
	local releasedir="${RELEASEDIR}/${freebsd_arch}/${freebsd_version}/${release_name}"

	if ! lib_zfs_mkdir "${componentsdir}" "${ZFS_COMPONENTS_NAME}/${freebsd_arch}/${freebsd_version}/${release_name}"; then
		lib_err ${EX_IOERR} "Error creating ${componentsdir}"
	fi

	if ! lib_zfs_mkdir "${releasedir}/release" "${ZFS_RELEASE_NAME}/${freebsd_arch}/${freebsd_version}/${release_name}/release"; then
		lib_err ${EX_IOERR} "Error creating ${releasedir}/release"
	fi

	_fetch_atomic_init -a "${freebsd_arch}" -r "${release_name}" -v "${freebsd_version}"

	if ! lib_zfs_mkdir "${releasedir}" "${ZFS_RELEASE_NAME}/${freebsd_arch}/${freebsd_version}/${release_name}"; then
		lib_err ${EX_IOERR} "Error creating ${releasedir}"
	fi

	# Download the MANIFEST
	if [ ${opt_chk_manifest} -eq 1 ]; then
		_fetch "${method}" "${downloadurl}" "${FETCH_MANIFEST_FILE}" "${componentsdir}/${FETCH_MANIFEST_FILE}"

		for component in "$@"; do
			local checksum
			checksum=`_fetch_gethash_manifest "${component}" "${componentsdir}/${FETCH_MANIFEST_FILE}"`

			if lib_check_empty "${checksum}"; then
				lib_err ${EX_DATAERR} -- "${component}: hash not found in the MANIFEST file."
			fi
		done
	fi

	for component in "$@"; do
		_fetch "${method}" "${downloadurl}" "${component}" "${componentsdir}/${component}"

		if [ ${opt_chk_manifest} -eq 1 ]; then
			_fetch_chk_manifest "${componentsdir}" "${component}" "${componentsdir}/${FETCH_MANIFEST_FILE}"
		fi

		_fetch_atomic_mid "${releasedir}"

		lib_debug "Extracting ${component}: tar ${TAR_DECOMPRESS_ARGS} -xf \"${componentsdir}/${component}\" -C \"${releasedir}/release\""

		if ! tar ${TAR_DECOMPRESS_ARGS} -xf "${componentsdir}/${component}" -C "${releasedir}/release"; then
			lib_err ${EX_IOERR} "Error extracting ${component}."
		fi

		lib_debug "Done: ${component}"
	done

	_fetch_atomic_end "${releasedir}"
}

_fetch_chk_manifest()
{
	local dir="$1" file="$2" manifest_file="$3"
	if [ -z "${file}" -o -z "${manifest_file}" ]; then
		lib_err ${EX_USAGE} "usage: _fetch_chk_manifest dir file manifest_file"
	fi

	lib_debug "Checking checksum for ${file} ..."

	local checksum
	checksum=`_fetch_gethash_manifest "${file}" "${dir}/${FETCH_MANIFEST_FILE}"`

	lib_debug "MANIFEST: ${checksum}"

	local file_checksum
	file_checksum=`lib_mksum "${dir}/${file}"`

	lib_debug "FILE: ${file_checksum}"

	if [ "${checksum}" != "${file_checksum}" ]; then
		lib_err ${EX_DATAERR} -- "${file}: checksum error: ${checksum} != ${file_checksum}"
	fi
}

_fetch_gethash_manifest()
{
	local file="$1" manifest_file="$2"
	if [ -z "${file}" -o -z "${manifest_file}" ]; then
		lib_err ${EX_USAGE} "usage: _fetch_gethash_manifest file manifest_file"
	fi

	grep -F -- "${file}" "${manifest_file}" | awk '{print $2}'
}

_fetch()
{
	local errlevel
	local method downloadurl file output

	method="$1"
	downloadurl="$2"
	file="$3"
	output="$4"

	case "${method}" in
		local)
			if [ ! -f "${downloadurl}/${file}" ]; then
				lib_err ${EX_NOINPUT} "${file} is not a file or cannot be read."
			fi

			cp -a -- "${downloadurl}/${file}" "${output}"
			
			errlevel=$?
			if [ ${errlevel} -ne 0 ]; then
				lib_err ${errlevel} "Error copying ${downloadurl}/${file}"
			fi
			;;
		www)
			local escape_file=`lib_escape_string "${file}"`
			local escape_downloadurl=`lib_escape_string "${downloadurl}"`
			local escape_output=`lib_escape_string "${output}"`
			local fetch_cmd=`lib_multi_replace "${WWW_CMD}" c "\"${escape_file}\"" o "\"${escape_output}\"" u "\"${escape_downloadurl}\""`

			lib_debug "Running (${method}): ${fetch_cmd}"

			sh -c "${fetch_cmd}"

			errlevel=$?
			if [ ${errlevel} -ne 0 ]; then
				lib_err ${errlevel} "\`${fetch_cmd}\` exits with a non-zero exit status."
			fi
			;;
		*)
			lib_err ${EX_DATAERR} "Invalid method: ${method}"
			;;
	esac
}

_fetch_atomic_init()
{
	local _o
	local osarch=
	local release_name=
	local osversion=

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

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

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

	if [ -f "${releasedir}/.done" ]; then
		lib_warn "The ${osarch}/${osversion}/${release_name} release is already created."
		exit 0
	fi

	if [ -f "${releasedir}/.inprogress" ]; then
		lib_warn -- "${releasedir} is dirty. Removing..."
		if ! fetch_destroy -a "${osarch}" -v "${osversion}" -- "${release_name}"; then
			lib_err ${EX_SOFTWARE} "There was a problem destroying the dirty release \`${release_name}\`. You must remove it using \`appjail fetch destroy\`."
		fi
	fi
}

_fetch_atomic_mid()
{
	local releasedir="$1"

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

	if ! touch "${releasedir}/.inprogress"; then
		lib_err ${EX_IOERR} "Error creating ${releasedir}/.inprogress"
	fi
}

_fetch_atomic_end()
{
	local releasedir="$1"

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

	if ! mv "${releasedir}/.inprogress" "${releasedir}/.done"; then
		lib_err ${EX_IOERR} "Error when renaming \`${releasedir}/.inprogress\` to \`${releasedir}/.done\`"
	fi
}

fetch_help()
{
	man 1 appjail-fetch
}

fetch_usage()
{
cat << EOF
usage: fetch debootstrap [-A] [-a <arch>] [-c <cache-start>] [-m <mirror>]
               [-r <name>] [-S <script>] <suite>
       fetch destroy [-fR] [-a <arch>] [-v <version>] <release>
       fetch empty [-a <arch>] [-v <version>] [<name>]
       fetch list <arch>[/<version>]
       fetch local [-C] [-a <arch>] [-r <name>] [-u <url>] [-v <version>]
               [<component> ...]
       fetch src [-bDIkNR] [-a <target>[/<target-arch>]] [-j <jobs>]
               [-K <kernel>] [-s <source-tree>] [-|<name>] [<args> ...]
       fetch www [-C] [-a <arch>] [-r <name>] [-u <url>] [-v <version>]
               [<component> ...]
EOF
}
