#!/bin/sh

# -------------------------------------
# Jail management script
# Copyright (c) 2004 Eirik verby
# 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.
#	* The name(s) of the author(s) may not 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 OWNER 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.
# -------------------------------------

##############################################################################
# Configuration section                                                      #
#                                                                            #
# Change the CFGFILE variable to point to your jail configuration file.      #
##############################################################################

# Clear the hook lists (for security reasons)
unset BEFORESTART_HOOKS
unset AFTERSTART_HOOKS
unset BEFORESTOP_HOOKS
unset AFTERSTOP_HOOKS
unset BEFORESTATUS_HOOKS
unset AFTERSTATUS_HOOKS

CFGFILE="/usr/local/etc/jails.conf"
. ${CFGFILE}

PROCFS=`expr "$PROCFS" : "[tT][rR][uU][eE]"`
LINPROCFS=`expr "$LINPROCFS" : "[tT][rR][uU][eE]"`

##############################################################################
# Main function section                                                      #
#                                                                            #
# These functions are wrappers for the action functions below. They do       #
# various sanity checking of input parameters, and verify the existence and  #
# required states of the affected jail(s).                                   #
##############################################################################

usage() {
	## Output usage information
	echo "Usage:" >&2
	echo "jailctl <command> <jail> [<path>]" >&2
	echo "<command> = start|stop|status|create|delete|upgrade|backup|restore" >&2
	echo "<jail> = hostname|all" >&2
	echo "<path> = Backup destination / restore source" >&2
	echo >&2
}

jail_status() {
	## Output the status of one or several jails
	if [ ! $JAIL ] || [ $JAIL = "all" ] || jail_exists ; then
		# Jail exists (or "all" was specified), we can query its status
		if [ ! $JAIL ] || [ $JAIL = "all" ] ; then
			# Output a brief list for all jails
			echo "Jail status (*=running, !=not configured):"
			for JAIL in $JAILS ; do
				# Loop through jails
				JAIL=`expr "$JAIL" : "\(.*\):.*"`
				# Run hooks
				jail_run_hooks before-status
				if jail_exists && jail_running ; then
					# Jail is running
					echo "*$JAIL ($(jail_ip))"
				elif jail_exists ; then
					# Jail not running
					echo " $JAIL ($(jail_ip))"
				else
					# Jail nonexistant or not configured
					echo "!$JAIL ($(jail_ip))"
				fi
				# Run hooks
				jail_run_hooks after-status
			done
		else
			# Output information for a specific jail
			# Run hooks
			jail_run_hooks before-status
			if jail_running ; then
				# Jail is running, be verbose
				echo "$JAIL ($(jail_ip)) is up."
				echo "Process list:"
				# Output process list for jail
				jps
			elif jail_exists; then
				# Jail not running
				echo "$JAIL ($(jail_ip)) is down."
			else
				# Jail nonexistant or not configured
				echo "Unable to query jail $JAIL!"
				echo "Incomplete configuration?"
			fi
			# Run hooks
			jail_run_hooks after-status
		fi
	else
		# No jail was specified, or the specified jail doesn't exist (on disk
		# or in jails.conf. Show usage information)
		echo "No valid jail specified!"
		echo
		usage
	fi
}

jail_start() {
	## Start one or several jails
	if [ $JAIL ] && (jail_exists || [ $JAIL = "all" ]) ; then
		# Jail exists (or "all" was specified), we can attempt to start it
		if [ $JAIL = "all" ] ; then
			# Attempting to start all jails
			for JAIL in $JAILS ; do
				# Loop through jails
				JAIL=`expr "$JAIL" : "\(.*\):.*"`
				if jail_running ; then
					# Jail is running, cannot start
					echo "Jail already running!"
				elif jail_exists ; then
					# Jail not running, starting
					echo "Starting jail $JAIL..."
					jstart
				else
					# Jail nonexistant or not configured
					echo "Unable to start jail $JAIL!"
					echo "Incomplete configuration?"
				fi
			done
		else
			# Start a specific jail
			if jail_running && [ ! $FORCE ] ; then
				# Jail is running, cannot start
				echo "Jail already running!"
			elif jail_exists ; then
				# Jail not running, starting
				echo "Starting jail $JAIL..."
				jstart
			else
				# Jail nonexistant or not configured
				echo "Unable to start jail $JAIL!"
				echo "Incomplete configuration?"
			fi
		fi
	else
		# No jail was specified, or the specified jail doesn't exist (on disk
		# or in jails.conf. Show usage information)
		echo "No valid jail specified!"
		echo
		usage
	fi
}

jail_stop() {
	## Stop one or several jails
	if [ $JAIL ] && (jail_exists || [ $JAIL = "all" ]) ; then
		# Jail exists (or "all" was specified), we can attempt to stop it
		if [ $JAIL = "all" ] ; then
			# Attempting to stop all jails
			if [ $VERSION -eq 4 ] && [ `ls /proc | wc -l` -eq 0 ] ; then
				# We are on FreeBSD 4.x, and we have no /proc to rely on
				jstop
			else
				for JAIL in $JAILS ; do
					# Loop through jails
					JAIL=`expr "$JAIL" : "\(.*\):.*"`
					if jail_exists && jail_running ; then
						# Jail is running, stopping
						echo "Stopping jail $JAIL..."
						jstop
					elif jail_exists && ! jail_running ; then
						# Jail not running, cannot stop
						echo "Jail not running ($JAIL)!"
					else
						# Jail nonexistant or not configured
						echo "Unable to stop jail $JAIL!"
						echo "Incomplete configuration?"
					fi
				done
			fi
		else
			if jail_exists && jail_running ; then
				if [ $VERSION -eq 4 ] && [ `ls /proc | wc -l` -eq 0 ] ; then
					# We are on FreeBSD 4.x, and we have no /proc to rely on
					echo 'Without a proc filesystem, you must use "jailctl stop all"!'
				else
				# Jail running, stopping
					echo "Stopping jail $JAIL..."
					jstop
				fi
			elif jail_exists && ! jail_running ; then
				# Jail not running, cannot stop
				echo "Jail not running!"
			else
				# Jail nonexistant or not configured
				echo "Unable to start jail $JAIL!"
				echo "Incomplete configuration?"
			fi
		fi
	else
		# No jail was specified, or the specified jail doesn't exist (on disk
		# or in jails.conf. Show usage information)
		echo "No valid jail specified!"
		echo
		usage
	fi
}

jail_create() {
	## Create a jail
	# Be more specific in distinguishing return codes from jail_exists
	jail_exists ; RC=$?
	if [ $JAIL ] && [ $RC -eq 1 ] ; then
		# If the jail is configured but does not exist on disk, create the jail
		echo "Creating jail $JAIL..."
		jcreate
	else
		# Jail not configured or already exists
		echo "Jail $JAIL cannot be created!"
	fi
	return 0
}

jail_upgrade() {
	## Upgrade one or several jails
	if [ $JAIL ] && (jail_exists || [ $JAIL = "all" ]) ; then
		# Jail exists (or "all" was specified), we can attempt to upgrade it
		if [ $JAIL = "all" ] ; then
			# Attempting to upgrade all jails
			for JAIL in $JAILS ; do
				# Loop through jails
				JAIL=`expr "$JAIL" : "\(.*\):.*"`
				if jail_exists ; then
					# Jail exists and is not running, upgrading
					echo "Upgrading jail $JAIL..."
					jupgrade
				else
					# Jail nonexistant or not configured
					echo "Jail does not exist, or not configured!"
				fi
			done
		else
			if jail_exists ; then
				# Jail exists and is not running, upgrading
				echo "Upgrading jail $JAIL..."
				jupgrade
			else
				# Jail nonexistant or not configured
				echo "Jail does not exist, or not configured!"
			fi
		fi
	else
		# No jail was specified, or the specified jail doesn't exist (on disk
		# or in jails.conf. Show usage information)
		echo "No valid jail specified!"
		echo
		usage
	fi
}

jail_delete() {
	## Delete a specific jail
	if [ $JAIL ] && ((jail_exists && ! jail_running) || [ $FORCE ]) ; then
		# Jail exists and is not running, deleting
		echo "Deleting jail $JAIL..."
		jdelete
	else
		# Jail nonexistant, running or not configured, cannot delete
		echo "Jail $JAIL cannot be deleted!"
	fi
	return 0
}

jail_backup() {
	## Back up one or several jails
	if [ $JAIL ] && (jail_exists || [ $JAIL = "all" ]) ; then
		# Jail exists (or "all" was specified), we can attempt to back it up
		if [ $JAIL = "all" ] ; then
			# Attempting to back up all jails
			for JAIL in $JAILS ; do
				# Loop through jails
				JAIL=`expr "$JAIL" : "\(.*\):.*"`
				if jail_exists && ! jail_running ; then
					# Jail exists and is not running, doing cold backup
					echo "Doing cold backup of jail $JAIL..."
					jbackup cold
				elif jail_exists && jail_running ; then
					# Jail is running, doing warm backup
					echo "Doing warm backup of jail $JAIL..."
					jbackup
				else
					# Jail nonexistant or not configured
					echo "Jail does not exist, or not configured!"
				fi
			done
		else
			if jail_exists && ! jail_running ; then
				# Jail exists and is not running, doing cold backup
				echo "Doing cold backup of jail $JAIL..."
				jbackup cold
			elif jail_exists && jail_running ; then
				# Jail is running, doing warm backup
				echo "Doing warm backup of jail $JAIL..."
				jbackup
			else
				# Jail nonexistant or not configured
				echo "Jail does not exist, or not configured!"
			fi
		fi
	else
		# No jail was specified, or the specified jail doesn't exist (on disk
		# or in jails.conf. Show usage information)
		echo "No valid jail specified!"
		echo
		usage
	fi
}

jail_restore() {
	## Restore a jail
		# Be more specific in distinguishing return codes from jail_exists
	jail_exists ; RC=$?
	if [ $JAIL ] && [ $RC -eq 1 ] ; then
		echo "Restoring jail $JAIL from backup"
		local JP=$(jail_path)
		# Restore the jail
		jrestore
	else
		# No jail was specified, or the specified jail doesn't exist (on disk
		# or in jails.conf. Show usage information)
		echo "No valid jail specified!"
		echo
		usage
	fi
}


#
### Helper function section
#

jail_exists() {
	## Query a jails existence
	local J
	for J in `echo "$JAILS_T" | grep "^$JAIL:"` ; do
		if [ -d "$(jail_path)" ] && \
			[ `expr $(ls -a $(jail_path) | grep -v "^.snap$" | wc -l)` -gt 2 ]; then
			# Jail is configured, its directory exists, and the
			# directory contains more than . and ..
			return 0
		elif [ ! -d "$(jail_path)" ] || \
			( [ -d "$(jail_path)" ] && \
			[ `expr $(ls -a $(jail_path) | grep -v "^.snap$" | wc -l)` -le 2 ]); then
			# Jail is defined, but not yet created (directory missing or empty)
			return 1
		fi
	done
	# Jail doesn't exist
	return 2
}

jail_name() {
	## Query the name of a jail
	## Must be updated to be able to search per IP. Searching per name makes
	## no sense
	local J
	for J in `echo "$JAILS_T" | grep "^$JAIL:"` ; do
		local J_NAME=`expr "$J" : "\(.*\):.*"`
		echo $J_NAME
		return 0
	done
	# Jail not found or error
	return 1
}

jail_ip() {
	## Query the IP of a jail
	local J
	for J in `echo "$JAILS_T" | grep "^$JAIL:"` ; do
		local J_IP=`expr "$J" : ".*:\(.*\);.*"`
		if [ -z "$J_IP" ] ; then
			local J_IP=`expr "$J" : ".*:\(.*\)"`
		fi
		echo $J_IP
		return 0
	done
	# Jail not found or error
	return 1
}

jail_path() {
	## Query the path to a jail
	local J
	for J in `echo "$JAILS_T" | grep "^$JAIL:"` ; do
		local J_NAME=`expr "$J" : "\(.*\):.*"`
		local J_HOME=`expr "$J" : ".*;\(.*\)$"`
		if [ -n "$J_HOME" ] ; then
			if [ `expr "$J_HOME" : ".*\/$"` -gt 0 ] ; then
				local J_PATH="`expr \"$J_HOME\" : \"\(.*\)/$\"`"
			else
				local J_PATH="${J_HOME}/${J_NAME}"
			fi
		else
			local J_PATH="${JAIL_HOME}$J_NAME"
		fi
		echo $J_PATH
		return 0
	done
	# Jail not found or error
	return 1
}

jail_running() {
	## Query the running state of a jail
	if [ $VERSION -ge 5 ] ; then
		# We are on FreeBSD 5.x, using jls(1) tool
		local JLS="`/usr/sbin/jls | grep \"\/${JAIL}$\"`"
		if [ ! "$JLS" ] ; then
			# Jail is not running
			return 1
		fi
		for i in "$JLS" ; do
			# Fetching output string, concatenating
			local J_LIST="$J_LIST $i"
		done
		# Setting JAIL_ID variable; this is the system jail ID
		JAIL_ID=`echo $J_LIST | cut -d \  -f 1`
		local JPS=`expr "\`/usr/sbin/jexec $JAIL_ID /bin/ps ax | grep -v \"ps\ ax\" | wc -l | cut -f 2\`" : "[[:space:]]*\([0-9]*\).*"`
		if [ "$JPS" -lt 2 ] ; then
			# Jail is not running (no processes, anyway)
			return 1
		fi
	else
		# We are on FreeBSD 4.x, use old dirty trick
		if [ ! -f "/var/run/jails/${JAIL}.running" ] ; then
			# Jail is not running
			return 1
		fi
	fi
	# Jail is running
	return 0
}


#
### Activity function section
#

jail_run_hooks() {
	## Select a hook list to run
	case $1 in
		before-start)
			jrunhooks "$BEFORESTART_HOOKS"
			;;
		after-start)
			jrunhooks "$AFTERSTART_HOOKS"
			;;
		before-stop)
			jrunhooks "$BEFORESTOP_HOOKS"
			;;
		after-stop)
			jrunhooks "$AFTERSTOP_HOOKS"
			;;
		before-status)
			jrunhooks "$BEFORESTATUS_HOOKS"
			;;
		after-status)
			jrunhooks "$AFTERSTATUS_HOOKS"
			;;
	esac
}

jps() {
	## List running processes in a jail
	if [ $VERSION -ge 5 ] ; then
		# We are on FreeBSD 5.x, use jexec(1) tool
		/usr/sbin/jexec $JAIL_ID ps auxwww
	else
		# We are on FreeBSD 4.x, use old dirty trick (requires /proc on host!)
		echo "CMD PID TIME UTIME STIME"
		cat /proc/*/status | grep \" ${JAIL} \" | cut -d \  -f 1 -f 2 -f 8 -f 9 -f 10
	fi
	return 0
}

jstart() {
	## Start a jail
	local JP=$(jail_path)
	local IP=$(jail_ip)
	if [ $PROCFS -gt 0 ] ; then
		# Mount proc filesystem into jail
		/sbin/mount_procfs procfs ${JP}/proc
	fi
	if [ $LINPROCFS -gt 0 ] ; then
		# Mount Linux proc filesystem into jail
		/sbin/mount_linprocfs linprocfs ${JP}/compat/linux/proc
	fi

	if [ $VERSION -ge 5 ] ; then
		# Run hooks
		jail_run_hooks before-start
		# We are on FreeBSD 5.x, use devfs
		/sbin/mount_devfs devfs ${JP}/dev
		devfs -m ${JP}/dev ruleset 4
		devfs -m ${JP}/dev rule applyset
	fi

	# Bring up network interface alias and start jail
	ifconfig $IF inet $IP netmask 0xffffffff alias
	echo >> ${JP}/var/log/jailstart.log
	echo $(date) >> ${JP}/var/log/jailstart.log
	jail $JP $JAIL $IP /bin/sh /etc/rc 2>&1 >${JP}/var/log/jailstart.log &

	if [ $VERSION -eq 4 ] ; then
		# We're on FreeBSD 4.x, Create run file
		touch /var/run/jails/${JAIL}.running
	fi

	if [ $VERSION -ge 5 ] ; then
		# Run hooks
		jail_run_hooks after-start
	fi

	return 0
}

jstop() {
	## Stop a jail
	local JP=$(jail_path)
	if [ $VERSION -ge 5 ] ; then
		# Run hooks
		jail_run_hooks before-stop
	fi

	local IP=$(jail_ip)
	echo "Sending TERM signal to jail processes..."
	if [ $VERSION -ge 5 ] ; then
		# We are on FreeBSD 5.x, use jexec(1) tool
		/usr/sbin/jexec $JAIL_ID /bin/sh /etc/rc.shutdown
		sleep 2
		/usr/sbin/jexec $JAIL_ID kill -15 -1
		# Waiting for processes to die
		sleep 4
		while jail_running ; do
			# Some processes are still running, do a kill -9 -1
			echo "Some processes would not terminate; sending KILL signal..."
			/usr/sbin/jexec $JAIL_ID kill -9 -1
			# Give processes some time to die
			sleep 2
		done
		umount -f ${JP}/dev
	else
		# We are on FreeBSD 4.x
		if [ "$JAIL" = "all" ] ; then
			# /proc is unavailable, so we can only stop ALL jails at once
			local PS="`ps ax|cut -c 1-16|grep J|cut -d \  -f 1`"
			for PID in "$PS" ; do
				kill -15 $PID 2>/dev/null 1>/dev/null
			done
			# Waiting for processes to die
			sleep 4
			local PS="`ps ax|cut -c 1-16|grep J|cut -d \  -f 1`"
			while [ "$PS" ] ; do
				# Some processes are still running, do a kill -9 on each
				echo "Some processes would not terminate; sending KILL signal..."
				for PID in "$PS" ; do
					# Sending KILL signal to all processes in the jail
					kill -9 $PID 2>/dev/null 1>/dev/null
				done
				# Give processes some time to die
				sleep 2
				local PS="`ps ax|cut -c 1-16|grep J|cut -d \  -f 1`"
			done
		else
			# Use /proc filesystem (REQUIRED for single-jail operation!)
			local PS="`cat /proc/*/status | cut -d \  -f 2 -f 15 2>/dev/null | grep \" ${JAIL} \" | cut -d \  -f 1`"
			for PID in "$PS" ; do
				# Sending TERM signal to all processes in the jail
				kill -15 $PID 2>/dev/null 1>/dev/null
			done
			# Waiting for processes to die
			sleep 4
			local PS="`cat /proc/*/status | cut -d \  -f 2 -f 15 2>/dev/null | grep \" ${JAIL} \" | cut -d \  -f 1`"
			while [ "$PS" ] ; do
				# Some processes are still running, do a kill -9 on each
				echo "Some processes would not terminate; sending KILL signal..."
				for PID in "$PS" ; do
					# Sending KILL signal to all processes in the jail
					kill -9 $PID 2>/dev/null 1>/dev/null
				done
				# Give processes some time to die
				sleep 2
				local PS="`cat /proc/*/status | cut -d \  -f 2 -f 15 2>/dev/null | grep \" ${JAIL} \" | cut -d \  -f 1`"
			done
		fi
	fi

	if [ $PROCFS -gt 0 ] ; then
		# Unmount the jail proc filesystem
		umount -f ${JP}/proc
	fi
	if [ $LINPROCFS -gt 0 ] ; then
		# Unmount the jail Linux proc filesystem
		umount -f ${JP}/compat/linux/proc
	fi

	if [ $VERSION -eq 4 ] ; then
		# We are on FreeBSD 4.x, remove runfile
		rm /var/run/jails/${JAIL}.running
	fi

	# Bring down network interface alias
	ifconfig $IF inet $IP netmask 0xffffffff -alias

	if [ $VERSION -ge 5 ] ; then
		# Run hooks
		jail_run_hooks after-stop
	fi

	return 0
}

jcreate() {
	## Create a jail
	local JP=$(jail_path)
	local IP=$(jail_ip)

	# Create jail directory
	mkdir -p $JP
	# Populate jail directory
	jpopulate
	# Initialize jail directory contents
	jinit
	# Remove unneeded files and clean up
	jcleanup
	return 0
}

jupgrade() {
	## Upgrade a jail
	local JP=$(jail_path)
	# Run mergemaster to prepare the jail for upgrade
	mergemaster -pi -D $JP
	# Populate jail directory
	jpopulate
	# Run mergemaster to update default configuration files
	mergemaster -ci -D $JP
	# Remove unneeded files and clean up
	jcleanup
	if jail_running ; then
		echo "Jail running, please restart!"
	fi
}

jdelete() {
	## Delete a jail
	local JP=$(jail_path)
	m_search=""
	if [ $PROCFS -gt 0 ] ; then m_search="${JP}/proc" ; fi
	if [ $LINPROCFS -gt 0 ] ; then
		if [ -n "${m_search}" ] ; then m_search="${m_search}\|" ; fi
		m_search="${m_search}${JP}/compat/linux/proc"
	fi
	if [ $VERSION -ge 5 ] ; then
		if [ -n "${m_search}" ] ; then m_search="${m_search}\|" ; fi
		m_search="${m_search}${JP}/dev"
	fi
	MOUNTS=`mount | grep "$JP" | grep -v "${m_search}"`
	MOUNTS_NO=`echo -n $MOUNTS | wc -l`
	if [ $MOUNTS_NO -gt 0 ]; then
		echo "WARNING: Mounted directories found in ${JP}:"
		echo $MOUNTS
		echo -n "Unmount ('n' will cancel delete)? [y/n] "
		read ANS
		if [ x$ANS = xy ]; then
			for i in `echo $MOUNTS | cut -d \( -f 1 | cut -d \  -f 3` ; do
				umount -f "$i"
			done
			MOUNTS=`mount | grep "$JP" | grep -v "${m_search}"`
			MOUNTS_NO=`echo -n $MOUNTS | wc -l`
			echo $MOUNTS_NO
			if [ $MOUNTS_NO -gt 0 ] ; then
				echo "Unmounting failed."
			else
				echo "All filesystems unmounted successfully. Deleting jail."
				# Removing SCHG flag from jail tree
				chflags -R noschg $JP
				# Removing jail directory
				rm -Rf $JP
			fi
		fi
	else
		# Removing SCHG flag from jail tree
		chflags -R noschg $JP
		# Removing jail directory
		rm -Rf $JP
	fi
}

jbackup() {
	## Back up a jail
	local JP=$(jail_path)

	# Determine target file for backup
	if [ -n "$CMD" ] ; then
		TARGET=$CMD
	else
		TARGET="${BACKUPDIR}/${JAIL}.tar"
	fi

	# Run backup
	if [ ! "$1" = "cold" -a $VERSION -ge 5 ] ; then
		# Run warm backup - FreeBSD 5 only
		/usr/sbin/jexec $JAIL_ID /usr/bin/tar --one-file-system -C / $BACKUP_EXCLUDE -cf - ./. | gzip --fast > ${TARGET}.gz
	else
		# Run cold backup
		chroot $JP /usr/bin/tar --one-file-system -C / $BACKUP_EXCLUDE -cf - ./. | gzip --fast > ${TARGET}.gz
	fi
}

jrestore() {
	## Restore a jail from backup
	# Create jail home
	mkdir -p $JP && cd $JP

	# Determine source file for backup
	if [ -n "$CMD" ] ; then
		SOURCE=$CMD
	else
		SOURCE="${BACKUPDIR}/${JAIL}.tar.gz"
	fi

	# Restore
	tar -zpxf $SOURCE
}


#
### Activity helper function section
#

jpopulate() {
	## Populate a jail directory
	cd /usr/src
	# Running installworld into jail directory
	make installworld ${INSTALLWORLD_FLAGS} DESTDIR=${JP} 2>&1 | grep '>>>'
}

jcleanup() {
	## Remove unneeded files and clean up a jail
	# Copying the most recent list of files to delete
	if [ $VERSION -ge 5 ] ; then
		cp ${JAIL_HOME}addons/dellist5.txt $JP/dellist.txt
	else
		cp ${JAIL_HOME}addons/dellist4.txt $JP/dellist.txt
	fi
	# Removing protection from files to be deleted
	chroot $JP chflags -R noschg $(cat ${JP}/dellist.txt) 2>/dev/null 1>/dev/null
	# Deleting files
	chroot $JP rm -Rf $(cat ${JP}/dellist.txt) 2>/dev/null 1>/dev/null

	# Changing binaries to be jail compatible
	chroot ${JP} ln -f /usr/bin/true /sbin/mount
	chroot ${JP} ln -f /usr/bin/true /sbin/umount
	chroot ${JP} ln -f /usr/bin/true /sbin/swapon
	chroot ${JP} ln -f /usr/bin/true /sbin/swapoff
	chroot ${JP} ln -f /usr/bin/true /sbin/init
	chroot ${JP} ln -f /usr/bin/true /sbin/adjkerntz
	chroot ${JP} ln -f /usr/bin/true /sbin/ifconfig

	# Copy timezone information from host
	cp /etc/localtime ${JP}/etc/localtime
}

jinit() {
	## Install default set of configuration files
	cd /usr/src/etc
	# Installing distribution files to jail directory
	make distribution DESTDIR=${JP} -DNO_MAKEDEV_RUN 2>/dev/null 1>/dev/null

	# Create directories in jail
	mkdir -p ${JP}/proc
	mkdir -p ${JP}/usr/home
	mkdir -p ${JP}/root/.ssh
	mkdir -p ${JP}/compat/linux/proc
	mkdir -p ${JP}/usr/local/bin
	chroot ${JP} ln -sf /usr/home /home

	# Update passwd database with default root user/pw
	IFS2=$IFS
	IFS=$(echo -e '\n\t')
	if [ -z "$BATCH" ] ; then
		echo "Setting root password in jail"
		chroot ${JP} /usr/bin/passwd root
	else
		for L in $(cat ${JP}/etc/master.passwd) ; do
			if [ "$L" = 'root::0:0::0:0:Charlie &:/root:/bin/csh' ] ; then
				echo "root:${ROOT_PW}:0:0::0:0:Charlie &:/root:/bin/csh" >> ${JP}/tmp/jailctl.001
			else
				echo $L >> ${JP}/tmp/jailctl.001
			fi
		done
		pwd_mkdb -p -d ${JP}/etc ${JP}/tmp/jailctl.001
		IFS=$IFS2
	fi

	# Install jail hostname and IP into hosts file
	JAIL_HOST=$(expr $JAIL : "\([a-zA-Z0-9\-]*\)\..*")
	JAIL_DOMAIN=$(expr $JAIL : "${JAIL_HOST}\.\(.*\)")
	echo "$IP $JAIL $JAIL_HOST" >> ${JP}/etc/hosts

	# Create new rc.conf
	echo '# Default jail rc.conf' > ${JP}/etc/rc.conf
	for L in $RC_CONF ; do
		echo $L >> ${JP}/etc/rc.conf
	done

	# Update SSH configuration
	sed -i .jailctl -Ee "s/#?PermitRootLogin no/PermitRootLogin yes/" ${JP}/etc/ssh/sshd_config

	# Update resolv.conf
	echo "domain $JAIL_DOMAIN" > ${JP}/etc/resolv.conf
	echo "nameserver $NAMESERVER" >> ${JP}/etc/resolv.conf

	# Creating symlinks
	chroot ${JP} ln -sf /dev/null /kernel
	if [ $VERSION -ge 5 ] ; then
		# We are on FreeBSD 5.x, work around distribution bug
		chroot ${JP} ln -sf /lib/libm.so.3 /lib/libm.so.2
	fi

	if [ $VERSION -eq 4 ] ; then
		# We are on FreeBSD 4.x, initializing device tree
		cd ${JP}/dev
		sh MAKEDEV jail
	fi

	# Installing addons
	cp -R ${JAIL_HOME}addons/* ${JP}/

	# Starting jail for the first time, calling runme.sh to install software
	ifconfig $IF inet $IP netmask 0xffffffff alias
	JSTART=$(jail $JP $JAIL $IP /bin/sh /runme.sh)
	ifconfig $IF inet $IP netmask 0xffffffff -alias

	# Output commmands used to run installation script for easy re-run
	echo "ifconfig $IF inet $IP netmask 0xffffffff alias"
	echo "jail $JP $JAIL $IP /bin/sh /runme.sh"
	echo "ifconfig $IF inet $IP netmask 0xffffffff -alias"
}

jrunhooks() {
	## Run a hook list
	# Find jail ID on FreeBSD >5
	JAIL_ID=0
	if [ $VERSION -ge 5 ] && jail_running ; then
		local JLS="`/usr/sbin/jls | grep \"\/${JAIL}$\"`"
		for i in "$JLS" ; do
			# Fetching output string, concatenating
			local J_LIST="$J_LIST $i"
		done
		# Setting JAIL_ID variable; this is the system jail ID
		JAIL_ID=`echo $J_LIST | cut -d \  -f 1`
	fi

	for HOOK in $1; do
		$HOOK $JAIL $JAIL_ID
	done
}

#
### Main block
#

## Get current working directory
CWD=$(pwd)

## Get command line parameters
ACTION=$1
JAIL=$2
CMD=$3

if [ "$CMD" = "force" ] ; then
	FORCE=1
fi

## Load jail config into newline separated list
JAILS_T=`echo "$JAILS" | sed y/" "/"\n"/`

## Checking current FreeBSD version
VERSION="`uname -r | cut -c 1`"

if [ $VERSION -eq 4 ] ; then
	# We are on FreeBSD 4.x, creating statefile directory
	if [ ! -d "/var/run/jails" ] ; then
		mkdir -p /var/run/jails
	fi
fi

case "$ACTION" in
status)		jail_status		;;
start)		jail_start		;;
stop)		jail_stop		;;
create)		jail_create		;;
upgrade)	jail_upgrade	;;
delete)		jail_delete		;;
backup)		jail_backup		;;
restore)	jail_restore	;;
*)			usage			;;

esac

cd $CWD

exit 0

