# -*- mode: sh; -*-
# vim: ft=sh
# This file is intended to be a wrapper to /etc/rc.d/rc.subr
# that makes it easy to run software inside of chroot jails.
# It handles the copying of programs by automatically detecting
# and installing required shared libraries.
#
# NOTE: This file sources /etc/rc.d/rc.subr! DO NOT source it yourself.
#
# To use this file, simply include it as
#     . /etc/rc.d/chroot_base
# Then, use the provided functions in rc_pre and rc_post to
# set up the chroot jail.
#
# The following variables MUST be defined BEFORE sourcing this file:
#     chroot_exec - program to run inside chroot jail
#     chroot_user - user to drop permissions to inside of chroot
#     chroot_group - group to drop permissions to inside of chroot
#     chroot_root - path of chroot jail. Should be owned by root:wheel
#                   and writable only by root
#
# The following variables MAY be defined BEFORE sourcing this file:
#     chroot_home - if set, the value of $HOME is set to this before the program is started
#     chroot_bin - if set, programs will be coppied here instead of to /bin. See chroot_env_exec
#                  for information about adding this to $PATH
#     chroot_env_exec - if set, will be used as the path to the 'env' program in the chroot. You
#                       must install and remove this program yourself. If this is set and chroot_bin
#                       set, chroot bin will be added to PATH
#     chroot_lib - if set, libraries will be coppied here instead of /usr/lib and it will be added
#                  to $LD_LIBRARY_PATH
#     chroot_no_ldso - if set to YES, this script will not touch ld.so in the jail
#     chroot_no_vars - if set to YES, this script will not set any of the daemon* variables or pexp.
#                      In addition, you may ignore all the variables except chroot_root
#
# The following function are defined by this script:
#     chroot_install - copy files to the chroot
#     chroot_install_program - copy a program and all of its libraries into the chroot
#     chroot_rm - delete files from the chroot
#     chroot_rm_program - delete a program from the chroot along with all of its libraries
#     chroot_mkdev - create device files in the chroot's /dev
#     chroot_mknull - create /dev/null in the chroot

# Internal variables used by functions
_chroot_root_dir="${chroot_root%/}/"
if [ -z "${chroot_home}" ]; then
	_chroot_user_home="$(grep "${chroot_user}" /etc/passwd | cut -d ":" -f6)"
else
	_chroot_user_home="${chroot_home}"
fi
if [ -z "${chroot_bin}" ]; then
	_chroot_bin_dir="${_chroot_root_dir}bin/"
	_chroot_env_path="PATH='$(getconf PATH)'"
else
	chroot_bin="${chroot_bin%/}"
	_chroot_bin_dir="${_chroot_root_dir}${chroot_bin#/}/"
	_chroot_env_path="PATH='${chroot_bin}:$(getconf PATH)'"
fi
if [ -z "${chroot_lib}" ]; then
	_chroot_lib_dir="${_chroot_root_dir}usr/lib/"
else
	chroot_lib="${chroot_lib%/}"
	_chroot_lib_dir="${_chroot_root_dir}${chroot_lib#/}/"
	_chroot_env_ld="LD_LIBRARY_PATH='${chroot_lib}'"
fi
_chroot_chroot_cmd="/usr/sbin/chroot -u '${chroot_user}' -g '${chroot_user}' '${chroot_root}'"

# find our env command
if [ -z "${chroot_env_exec}" ]; then
	# There is no env inside the chroot, therefore we run it before chroot
	# However, this means we cannon env PATH because the chroot resets it
	_chroot_env_cmd="/usr/bin/env HOME='${_chroot_user_home}' ${_chroot_env_ld}"
else
	# The user has put env inside the chroot
	# Run it inside, allowing us to set path
	_chroot_inside_env_cmd="${chroot_env_exec} HOME='${_chroot_user_home}' ${_chroot_env_ld} ${_chroot_env_path}"
fi

# Set normal rc.subr variables from chroot_* variables
if [ "${chroot_no_vars}" != "YES" ]; then
	daemon_user="root"
	daemon="${_chroot_env_cmd} ${_chroot_chroot_cmd} ${_chroot_inside_env_cmd} ${chroot_exec}"
fi

# Source rc.subr
. /etc/rc.d/rc.subr

# Fix the pexp for the chroot
if [ "${chroot_no_vars}" != "YES" ]; then
	pexp="$(eval echo ${chroot_exec}${daemon_flags:+ ${daemon_flags}})"
fi

# usage: chroot_install [files ...]
#     Copy files into the chroot. Permissions, owner, group, and flags are preserved.
function chroot_install {
	for file in ${@}; do
		local rel_file
		if echo "${file}" | grep -E '^/' >/dev/null; then
			rel_file="$(echo "${file}" | cut -c '2-')" # remove leading /
		else
			rel_file="${file}"
		fi
		local dir="$(dirname "${rel_file}")"
		if ! [ -d "${_chroot_root_dir}${dir}" ]; then
			mkdir -p "${_chroot_root_dir}${dir}"
			echo "Created '${_chroot_root_dir}${dir}'"
		fi
		cp -Rp "${file}" "${_chroot_root_dir}${rel_file}"
		echo "Installed '${file}' to '${_chroot_root_dir}${rel_file}'"
	done
}

# usage: chroot_install_program <file> [name]
#     Install the program FILE and all of its shared library dependencies
#     to the chroot. Dependencies are found with ldd. If NAME is provided,
#     the program will be coppied as NAME.
#
# NOTE: By default, the program is installed to /bin in the chroot
function chroot_install_program {
	echo "Installing dependencies for '${1}':"
	for dep in $(ldd "${1}" | tail -n '+4' | grep -Eo '/.+$'); do
		if [ "${dep}" == "/usr/libexec/ld.so" ]; then
			if [ "${chroot_no_ldso}" != "YES" ]; then
				if ! [ -d "${_chroot_root_dir}usr/libexec" ]; then
					mkdir -p "${_chroot_root_dir}usr/libexec"
					echo "  - Created '${_chroot_root_dir}usr/libexec'"
				fi
				install -o root -g bin -m 0444 "${dep}" "${_chroot_root_dir}usr/libexec/ld.so"
				echo "  - Installed '${dep}' to '${_chroot_root_dir}usr/libexec/ld.so'"
			fi
		else
			if ! [ -d "${_chroot_lib_dir}" ]; then
				mkdir -p "${_chroot_lib_dir}"
				echo "  - Created '${_chroot_lib_dir}'"
			fi
			local name="$(basename "${dep}")"
			install -o root -g bin -m 0444 "${dep}" "${_chroot_lib_dir}${name}"
			echo "  - Installed '${dep}' to '${_chroot_lib_dir}${name}'"
		fi
	done
	if ! [ -d "${_chroot_bin_dir}" ]; then
		mkdir -p "${_chroot_bin_dir}"
		echo "Created '${_chroot_bin_dir}'"
	fi
	local name
	if [ -z "${2}" ]; then
		name="$(basename "${1}")"
	else
		name="${2}"
	fi
	install -o root -g bin -m 0755 "${1}" "${_chroot_bin_dir}${name}"
	echo "Installed '${1}' to '${_chroot_bin_dir}${name}'"
}

# usage: chroot_rm_program <file>
#     Find all libraries of program with ldd and delete them.
#     Then, delete the program FILE from /bin of the chroot.
#
# EXAMPLE:
# The usage
#     chroot_rm_program "/usr/local/bin/git"
# would run ldd on /bin/git in the chroot and delete all the
# resulting libraries. It would then remove /bin/git from the
# chroot.
function chroot_rm_program {
	local name="$(basename "${1}")"
	local path="${_chroot_bin_dir}${name}"
	echo "Deleting dependencies for '${1}'"
	for dep in $(ldd "${path}" | tail -n '+4' | grep -Eo '/.+$'); do
		if [ "${dep}" == "/usr/libexec/ld.so" ]; then
			if [ "${chroot_no_ldso}" != "YES" ]; then
				rm -f "${_chroot_root_dir}usr/libexec/ld.so"
				echo "  - Deleted '${_chroot_root_dir}usr/libexec/ld.so'"
			fi
		else
			local name="$(basename "${dep}")"
			rm -f "${_chroot_lib_dir}${name}"
			echo "  - Deleted '${_chroot_lib_dir}${name}'"
		fi
	done
	rm -f "${path}"
	echo "Deleted '${path}'"
}

# usage: chroot_rm [files ...]
#     Remove files from the chroot
function chroot_rm {
	for file in ${@}; do
		if echo "${file}" | grep -E '^/' >/dev/null; then
			file="$(echo "${file}" | cut -c '2-')" # remove leading /
		fi
		rm -rf "${_chroot_root_dir}${file}"
		echo "Deleted '${_chroot_root_dir}${file}'"
	done
}

# usage: chroot_mkdev [names ...]
#     Runs /dev/MAKEDEV with NAMES as the argument in the chroot's
#     /dev directory
function chroot_mkdev {
	if ! [ -d "${_chroot_root_dir}dev" ]; then
		mkdir "${_chroot_root_dir}dev"
		echo "Created '${_chroot_root_dir}dev'"
	fi
	(cd "${_chroot_root_dir}dev" && /dev/MAKEDEV "$@")
	echo "Created device files for '$@' in '${_chroot_root_dir}dev'"
}

# usage: chroot_mknull
#     This is a convinience function that creates /dev/null in the
#     chroot.
function chroot_mknull {
	if ! [ -d "${_chroot_root_dir}dev" ]; then
		mkdir "${_chroot_root_dir}dev"
		echo "Created '${_chroot_root_dir}dev'"
	fi
	mknod -m 0666 "${_chroot_root_dir}dev/null" c 2 2
	echo "Created /dev/null as '${_chroot_root_dir}dev/null'"
}
