#!/bin/sh

# Export every property specified on command line ----------------------
__export_props () {
    local _pname

    for i in "$@" ; do
        if [ "$(echo $i | grep -e ".*=.*")" ] ; then
            _pname="$(echo $i | awk 'BEGIN { FS = "=" } ; { print $1 }')"
            __validate_pname $_pname
            export "$i"
            export "iocset_${i}"
        fi
    done

    if [ "$tag" == 'default' ] && ([ "$1" != 'set' ] || [ "$1" != 'get' ]); then
        tag=$(date "+%F@%T")
        echo "  WARNING: tag value \"default\" is a reserved keyword" >&2
        echo "           tag was changed to: $tag" >&2
    fi
}

# Set properties ------------------------------------------------------
__set_jail_prop () {
    local _name _property _dataset _pname _pval _found _CONF \
          _type _state _tag _release _jail_type _jail_zfs_mountpoint \
          _jail_zfs_datasets _old_mountpoint _tmp_mountpoint _zfs_dataset \
          _parent _release_tag _jid _jailed_dataset _jailed_dataset_check \
          _jail_zfs _jail_datasets

    _name="$2"
    _property="$1"

    if [ -z "${_name}" -o -z "${_property}" ] ; then
        __die "missing property or UUID!"
    fi

    # Avoid having a stale list when called.
    export jail_datasets="$(zfs list -d3 -rH -o name ${pool}/iocell \
        | egrep -v \
        "jails$|releases$|templates$|download|${pool}/iocell$|/root$")"

    if [ "$3" ] ; then
        _dataset="$3"
    else
        _dataset="$(__find_jail ${_name})" || exit $?
    fi

    _fulluuid="$(__check_name ${_name} ${_dataset})"

    _pname="$(echo ${_property} | awk 'BEGIN { FS = "=" } ; { print $1 }')"
    _pval="$(echo ${_property} | awk 'BEGIN { FS = "=" } ; { print $2 }')"
    _jail_type="$(__get_jail_prop type ${_fulluuid} ${_dataset})"

    if [ -z "${_pname}" -o -z "${_pval}" ] ; then
        __die "set failed, incorrect property syntax!"
    fi

    _type="$(__validate_pname ${_pname} type)" || exit $?

    case "${_pname}" in
        tag)
            __unlink_tag ${_dataset}
            __link_tag ${_dataset} "${_pval}"
            ;;
        jail_zfs)
            if [ "${jail_zfs}" = "on" ] ; then
                __setup_jail_zfs_datasets "${_fulluuid}" "${_dataset}"

            elif [ "${jail_zfs}" != "off" ] ; then
                __die "The setting for jail_zfs must be either 'on' or 'off'."
            fi

            ;;
        jail_zfs_dataset)
            _jail_zfs="$(__get_jail_prop jail_zfs "${_fulluuid}" "${_dataset}")"

            # Do not create ZFS datasets for a jail unless jail_zfs is on.
            # If it is currently "off", the datasets will be created when
            # it is changed to "on".
            if [ "${_jail_zfs}" = "on" ] ; then
                _state="$(__is_running ${_fulluuid})"
                if [ ! -z "${_state}" ] ; then
                    __die "cannot change ZFS datasets while jail is running!"
                fi

                __setup_jail_zfs_datasets "${_fulluuid}" "${_dataset}" \
                    "${jail_zfs_dataset}"
            fi

            ;;
        bpf)
            # Do they want bpf exposed?
            if [ "${_devfs_string}" != "0" ] ; then
                __bpf_devfs >> /etc/devfs.rules
                service devfs restart 1> /dev/null
            fi
            ;;
        notes)
            # Needed for notes="aaa=bbb" issue #243
            _pval="$(echo ${notes})"
            ;;
        jail_zfs_mountpoint)
            _state="$(__is_running ${_fulluuid})"
            _jail_zfs="$(__get_jail_prop jail_zfs "${_fulluuid}" "${_dataset}")"
            _jail_zfs_datasets="$(__get_jail_prop jail_zfs_dataset \
                "${_fulluuid}" "${_dataset}")"

            # Enforce the rule that jail_zfs_mountpoint must be "none" when
            # the jail has multiple datasets assigned.
            if [ "$(echo "${_jail_zfs_datasets}" | grep '[ 	]')" ] ; then
                if [ "${jail_zfs_mountpoint}" != "none" ] ; then
                    # Do not __die here, as this may be called from
                    # __create_jail, and that could leave the jail incompletely
                    # configured.  Instead, just force the value to "none" and
                    # carry on.
                    __info "With multiple ZFS datasets per jail," \
                          "jail_zfs_mountpoint must be set to 'none'."
                    jail_zfs_mountpoint="none"
                fi
            fi

            _old_mountpoint="$(__get_jail_prop jail_zfs_mountpoint \
                "${_fulluuid}" "${_dataset}")"

            # Nothing to do for the dataset mountpoint properties if:
            # - jail_zfs isn't "on"
            # or
            # - the value isn't changing
            # or
            # - the new value is "none".  We don't make changes when the new
            # value is "none" because "none" is how we say "you have to manage
            # it directly using 'zfs set'".
            if [ "${jail_zfs}" = "on" -a "${jail_zfs_mountpoint}" != "none" -a \
                 "${_old_mountpoint}" != "${jail_zfs_mountpoint}" ] ; then

                if [ ! -z "${_state}" ] ; then
                    __die " jail is running! please stop it before changing " \
                        "mountpoints."
                fi

                for _zfs_dataset in ${_jail_zfs_datasets} ; do

                    _zfs_dataset=$(__zfs_dataset_add_poolname \
                        "${_zfs_dataset}")

                    # If the current value of the dataset's mountpoint prop
                    # doesn't match the old jail_zfs_mountpoint value, assume
                    # that the dataset's mountpoint property has been
                    # manipulated directly and don't change it.
                    _tmp_mountpoint="$(zfs get -Ho value mountpoint \
                        "${_zfs_dataset}")"

                    if [ "${_tmp_mountpoint}" = "${_old_mountpoint}" ] ; then
                        zfs set jailed=off "${_zfs_dataset}"
                        zfs set mountpoint="${jail_zfs_mountpoint}" \
                            "${_zfs_dataset}"
                        zfs set jailed=on "${_zfs_dataset}"
                    fi

                done
            fi
            ;;
        istemplate)
            if [ "${_jail_type}" != "basejail" -a "${_jail_type}" != "template" ] ; then
                __die "templates aren't supported for this jail type (${_jail_type})"
            fi

            if [ "${_pval}" = "yes" ] ; then
                _release_tag="$(__get_jail_prop tag ${_fulluuid} ${_dataset})"
                _state="$(__is_running ${_fulluuid})"

                if [ ! -z "${_state}" ] ; then
                    __die "cannot make ${_release_tag} a template" \
                        "- jail is running!"
                fi

                _jailed_dataset=$(__get_jail_prop jail_zfs_dataset \
                    "${_fulluuid}" "${_dataset}")
                _jailed_dataset_check="$(zfs get -H creation ${_jailed_dataset} > \
                                       /dev/null 2>&1 ; echo $?)"

                # Check to see if the user has the old style of jailed datasets
                if [ "${_jailed_dataset_check}" = "0" ] ; then
                    zfs set jailed=off "${_jailed_dataset}"
                fi

                __check_children "${_fulluuid}" "${_dataset}"

                if [ "$?" -eq 1 ] ; then
                    #shellcheck disable=SC2154
                    if [ "${migrate}" = "yes" ] ; then
                        return 1
                    fi

                    #shellcheck disable=SC2154
                    echo "${error}"
                    return 1
                fi

                __unlink_tag "${_dataset}"
                _tag="$(__get_jail_prop tag ${_fulluuid} ${_dataset})"
                _release="$(__get_jail_prop release ${_fulluuid} ${_dataset})"

                __set_jail_prop type="template" "${_fulluuid}" "${_dataset}"

                if [ -d "${iocroot}/jails/${_fulluuid}" ] ; then
                    # __spinner needs to be recreated here as we use
                    # multiple commands
                    _spinner='/-\|'
                    printf "  INFO: converting ${_tag} to a template:  "

                    while true; do
                        printf '\b%.1s' "${_spinner}"
                        _spinner=${_spinner#?}${_spinner%???}
                        sleep .1
                        done &

                        trap "kill $!" 2 3
                        # Creating jail's /root in it's new home
                        zfs rename -f \
                            "${pool}/iocell/jails/${_fulluuid}" \
                            "${pool}/iocell/templates/${_tag}"
                        _dataset="${pool}/iocell/templates/${_tag}"
                        _jailed_dataset="${pool}/iocell/templates/${_tag}/data"
                        # Check to see if the user has the old style of jailed datasets
                        if [ "${_jailed_dataset_check}" = "0" ] ; then
                            zfs set jailed=on "${_jailed_dataset}"
                        fi
                        printf "\b%1s\n" "done!" ; kill $! && trap " " 2 3
                else
                    __die "${_tag} is already a template"
                fi
            elif [ "${_pval}" = "no" ] ; then
                _template_tag="$(__get_jail_prop tag ${_fulluuid} ${_dataset})"
                _release="$(__get_jail_prop release ${_fulluuid} ${_dataset})"
                _jailed_dataset="${pool}/iocell/templates/${_template_tag}/data"
                _jailed_dataset_check="$(zfs get -H creation ${_jailed_dataset} > \
                                       /dev/null 2>&1 ; echo $?)"

                # We need to ignore a few more datasets here.
                _jail_datasets="$(zfs list -d3 -rH -o name \
                    ${pool}/iocell | egrep -v \
                    "jails$|base|releases|templates$|download|${pool}/iocell$|/root$")"

                for _fs in ${_jail_datasets} ; do
                    # _jail_fs is actually a dataset, so fulluuid needs to
                    # be faked.
                    _uuid=$(__check_name "name" ${_fs} 2> /dev/null)
                    _origin="$(__get_jail_prop origin ${_uuid} ${_fs} | \
                        cut -f1 -d@)"
                    _tag="$(__get_jail_prop tag ${_uuid} ${_jail_fs})"

                    if [ "${_origin}" = "${_dataset}/root" ] ; then
                        __info "${_template_tag} has dependent clone," \
                            "uuid: ${_uuid} (${_tag})"
                        _parent=1
                    fi
                done

                if [ "${_parent}" = "1" ] ; then
                    __die "please destroy before converting ${_template_tag}" \
                        "back to a jail"
                fi

                # Check to see if the user has the old style of jailed datasets
                if [ "${_jailed_dataset_check}" = "0" ] ; then
                    zfs set jailed=off "${_jailed_dataset}"
                fi

                __set_jail_prop type="basejail" "${_fulluuid}" "${_dataset}"

                if [ -d "${iocroot}/templates/${_template_tag}" ] ; then
                    # __spinner needs to be recreated here as we use
                    # multiple commands
                    _spinner='/-\|'
                    printf "  INFO: converting ${_template_tag} to a jail:  "

                    while true; do
                        printf '\b%.1s' "${_spinner}"
                        _spinner=${_spinner#?}${_spinner%???}
                        sleep .1
                        done &

                        trap "kill $!" 2 3

                        # Have to manually specify this for later, as it will
                        # still be the old /iocell/templates location
                        _dataset="${pool}/iocell/jails/${_fulluuid}"

                        # Move jail back to the jails dataset
                        zfs rename -f \
                            "${pool}/iocell/templates/${_template_tag}" \
                            "${pool}/iocell/jails/${_fulluuid}"
                        # Updating the jail tag
                        __link_tag "${_dataset}" "${_template_tag}"
                        # Throw jail through the checking process and unmount
                        # what remains.
                        __check_basejail "${_fulluuid}" > /dev/null 2>&1
                        __umount_basejail "${_fulluuid}" "${_dataset}" \
                            > /dev/null 2>&1

                        _jailed_dataset="${_dataset}/data"
                        # Check to see if the user has the old style of jailed datasets
                        if [ "${_jailed_dataset_check}" = "0" ] ; then
                            zfs set jailed=on "${_jailed_dataset}"
                        fi
                        printf "\b%1s\n" "done!" ; kill $! && trap " " 2 3
                else
                    __die "${_template_tag} is already a jail"
                fi
            fi
            ;;
        allow*)
            # get jid for iocell jails
            _jid=$(jls -j ioc-${_fulluuid} jid 2> /dev/null)

            if [ ! -z "${_jid}" ] ; then
                # These actually still want one _ instead of all periods.
                if [ "${_pname}" = "allow_raw_sockets" \
                    -o "${_pname}" = "allow_socket_af" \
                    -o "${_pname}" = "allow_set_hostname" ] ; then
                    _jail_pname="$(echo ${_pname} | sed 's/_/./')"
                else
                    _jail_pname="$(echo ${_pname} | sed 's/_/./g')"
                fi

                jail -m "jid=${_jid}" "${_jail_pname}=${_pval}"
            fi
            ;;
        template)
            if [ "${_pval}" = "yes" ] ; then
                __die "please set istemplate=yes instead."
            elif [ "${_pval}" = "no" ] ; then
                __die "please set istemplate=no instead."
            fi
            ;;
        hack88)
            _state="$(__is_running ${_fulluuid})"

            if [ ! -z "${_state}" ] ; then
                __die " jail is running! please stop it before changing hack88."
            fi
            ;;
        host_hostname)
            _state="$(__is_running ${_fulluuid})"

            if [ ! -z "${_state}" ] ; then
                __info " jail is running! you may need to restart the jail"\
                "to see the changes."
            fi

            cat ${iocroot}/jails/${_fulluuid}/root/etc/rc.conf | \
                sed -E "s/hostname.*/hostname=\"${host_hostname}\"/g" \
                > ${iocroot}/jails/${_fulluuid}/rc.conf
            mv ${iocroot}/jails/${_fulluuid}/rc.conf \
               ${iocroot}/jails/${_fulluuid}/root/etc/rc.conf
            ;;
    esac

    if [ "${_type}" = "custom" ] ; then
        _mountpoint="$(__get_jail_prop mountpoint ${_fulluuid} ${_dataset})"
        __ucl_set "${_mountpoint}" "${_pname}" "${_pval}"
    elif [ "${_type}" = "native" ] ; then
        zfs set ${_pname}="${_pval}" ${_dataset}
    fi
}

__validate_pname () {
    local _prop _pname _found

    _pname="$1"
    _type="$2"
    _found=0

    _CONF="$CONF_NET
           $CONF_JAIL
           $CONF_RCTL
           $CONF_CUSTOM
           $CONF_SYNC
           $CONF_FTP
           $CONF_GIT"

    for _prop in ${_CONF} ; do
        if [ "${_prop}" = "${_pname}" ] ; then
            _found=1
            if [ "${_type}" ] ; then
                echo "custom"
            fi
            break
        fi
    done

    for _prop in ${CONF_ZFS} ; do
        if [ "${_prop}" = "${_pname}" ] ; then
            _found=1
            if [ "${_type}" ] ; then
                echo "native"
            fi
            break
        fi
    done

    if [ ${_found} -ne "1" ] ; then
        __die "unsupported property: ${_pname}!"
    fi
}

__console () {
    local _name _dataset _fulluuid _login_flags _jexec _exec_fib \
          _state

    _name=$1

    if [ -z ${_name} ] ; then
        __die "missing UUID!"
    fi

    _dataset=$(__find_jail ${_name}) || exit $?

    if [ -z ${_dataset} ] ; then
        __die "${_name} not found!"
    fi

    _fulluuid="$(__check_name ${_name} ${_dataset})"
    _state=$(__is_running ${_fulluuid})
    _login_flags=$(__get_jail_prop login_flags ${_fulluuid} ${_dataset})
    _exec_fib=$(__get_jail_prop exec_fib ${_fulluuid} ${_dataset})
    _jexec="jexec"

    if [ ${_exec_fib} -ne "0" ] ; then
        _jexec="setfib ${_exec_fib} jexec"
    fi

    if [ "${_state}" ] ; then
        ${_jexec} ioc-${_fulluuid} login ${_login_flags}
    elif [ "${_force}" = "1" ] ; then
        __start_jail "${_jail}"
       ${_jexec} ioc-${_fulluuid} login ${_login_flags}
    else
        __die "jail: ${_name} is not running!"
    fi
}

__exec () {
    local _jexecopts _name _dataset _fulluuid _jexec _exec_fib
    _jexecopts=

    # check for -U or -u to pass to jexec
    while getopts u:U: opt "$@"; do
        case "${opt}" in
            [uU]) _jexecopts="${_jexecopts} -${opt} ${OPTARG}";;
            ?)    __die "invalid exec option: ${opt}"
                  ;;
        esac
    done
    shift $(expr ${OPTIND} - 1)

    _name=$1
    shift

    if [ -z "${_name}" ] ; then
        __die "missing UUID!"
    elif [ "${_name}" = "ALL" ]; then
        local _jails _fulluuid
        _jails="$(__find_jail ALL | \
                awk 'BEGIN { FS = "/" } ; { if (length($NF)==36) { print }}')"

        for _jail in ${_jails} ; do
            # _jail is actually a dataset, so fulluuid needs to be faked.
            _fulluuid=$(__check_name "name" ${_jail} 2> /dev/null)
            jexec ${_jexecopts} ioc-${_fulluuid} "$@"
        done

        exit 0
    fi

    _dataset=$(__find_jail ${_name}) || exit $?

    _fulluuid="$(__check_name ${_name} ${_dataset})"

    _exec_fib=$(__get_jail_prop exec_fib ${_fulluuid} ${_dataset})

    _jexec="jexec"
    if [ "${_exec_fib}" -ne "0" ] ; then
        _jexec="setfib ${_exec_fib} jexec"
    fi

    ${_jexec} ${_jexecopts} ioc-${_fulluuid} "$@"
}

# Cleanup release directories
__cleanup_release_dir () {
    local _rel_exist _rel_dir_exist _dwn_rel_exist _dwn_dir_exist _rel_name

    if [ -n "$1" ] ; then
        _rel_name="$1"
    else
        __die "Failed cleanup of RELEASE directories. No RELEASE name defined" >&2
    fi

    _rel_exist=$(zfs get -H -o value name ${pool}/iocell/releases/${_rel_name})
    _dwn_rel_exist=$(zfs get -H -o value name \
        ${pool}/iocell/download/${_rel_name})

    if [ -n "${_rel_exist}" ] ; then
        _rel_dir_exist=$(zfs get -H -o value mountpoint \
            ${pool}/iocell/releases/${_rel_name})
        zfs destroy -fr -- ${pool}/iocell/releases/${_rel_name}
        if [ -d "${_rel_dir_exist}" ]; then
            rm -rf -- ${_rel_dir_exist}
        fi
    fi

    if [ -n "${_dwn_rel_exist}" ] ; then
        _dwn_dir_exist=$(zfs get -H -o value mountpoint \
            ${pool}/iocell/download/${_rel_name})
        zfs destroy -fr -- ${pool}${iocroot}/download/${_rel_name}
        if [ -d "${_dwn_dir_exist}" ]; then
            rm -rf -- ${_dwn_dir_exist}
        fi
    fi
}

# Fetch release and prepare base ZFS filesystems-----------
__fetch_release () {
    local _rel_exist _release _host_release _non_interactive _dist _ports \
          _ports_string _grepmatch

    _release=$(uname -r|cut -f 1,2 -d'-')
    # Needed to check for PC-BSD import
    _host_release=$(uname -r|cut -f 1,2 -d'-')
    _grepmatch='STABLE$|PRERELEASE$|CURRENT$|BETA$'

    for _args in "$@" ; do
        case "${_args}" in
            -P|-p) _ports=1
                ;;
        esac
    done

    if [ -z "${iocset_release}" ] ; then
        __print_release
        echo -n "Please select a release [${_release}]: "
        read _answer

        if [ -z "${_answer}" ] ; then
            _answer="${_release}"
        else
            _release="${_answer}"
            for _rel in $(echo ${supported}) ; do
                if [ "${_answer}" == "${_rel}" ] ; then
                    _release="${_rel}"
                    match="1"
                    break
                fi
            done

            if [ -z ${match} ] ; then
                echo "Invalid release ${_release} specified, exiting.." >&2
                exit 1
            fi
        fi
    else
        _release="${iocset_release}"
    fi



    _rel_exist=$(zfs list -H | grep -w ^${pool}/iocell/releases/${_release})

    if [ -z "${_rel_exist}" ] ; then
        zfs create -o compression=${compression} -p ${pool}/iocell/releases/${_release}/root
        zfs create -o compression=${compression} -p ${pool}/iocell/download/${_release}
    fi

    if [ -z "${ftpdir}" ] ; then
        # Everything but RELEASE is stored here
        if [ "$(echo ${_release} | egrep ${_grepmatch})" ] ; then
            ftpdir="/pub/FreeBSD/snapshots/x86_64/${_release}"
        else
            ftpdir="/pub/FreeBSD/releases/x86_64/${_release}"
        fi
    fi

    if [ -e "/usr/local/tmp/iocell-dist" -a "${_release}" = "${_host_release}" ] ; then
        cd "/usr/local/tmp/iocell-dist"
        _dist=1
    else
        cd ${iocroot}/download/${_release}
        _dist=0
    fi

    # If $ftplocaldir is set fetch from a local dir else fetch from remote
    # If fetch fails for remote or local remove new directories
    for file in $ftpfiles ; do
        if [ ! -e "$file" ] ; then
            if [ -n "$ftplocaldir" ] ; then
                fetch file://${ftplocaldir}/${file}
                if [ $? -ne 0 ] ; then
                    echo "Failed fetching local $file.." >&2
                    __cleanup_release_dir ${_release}
                    return 1
                fi
            else
                fetch http://${ftphost}${ftpdir}/${file}
                if [ $? -ne 0 ] ; then
                    echo "Failed fetching remote $file.." >&2
                    __cleanup_release_dir ${_release}
                    return 1
                fi
            fi
        fi
    done

    for _file in $(echo ${ftpfiles}) ; do
        if [ -e "${_file}" ] ; then
            echo "Extracting: ${_file}"
            chflags -R noschg ${iocroot}/releases/${_release}/root
            tar -C ${iocroot}/releases/${_release}/root -xf ${_file}
        fi
    done

    # Create these dirs for basejails
    if [ ! -e ${iocroot}/releases/${_release}/root/usr/home ] ; then
        mkdir ${iocroot}/releases/${_release}/root/usr/home > /dev/null 2>&1
        cd ${iocroot}/releases/${_release}/root && ln -s usr/home home 2>&1
    fi

    mkdir ${iocroot}/releases/${_release}/root/compat > /dev/null 2>&1
    mkdir ${iocroot}/releases/${_release}/root/usr/ports > /dev/null 2>&1

    # mount devfs for chroot
    mount -t devfs devfs ${iocroot}/releases/${_release}/root/dev

    cat /etc/resolv.conf > \
            ${iocroot}/releases/${_release}/root/etc/resolv.conf

    if [ ! "$(echo ${_release} | grep -E ${_grepmatch})" ] ; then

        echo "* Updating base jail template."
        sleep 2


        if [ -e ${iocroot}/releases/${_release}/root/etc/freebsd-update.conf ] \
            && [ -z "$ftplocaldir" ] ; then

            case "${_release}" in
                9.3-RELEASE | 10.1-RELEASE)
                    # Remove the interactive check in freebsd-update
                    sed 's/\[ ! -t 0 \]/false/' \
                        ${iocroot}/releases/${_release}/root/usr/sbin/freebsd-update \
                        > ${iocroot}/releases/${_release}/root/tmp/freebsd-update
                    chmod +x ${iocroot}/releases/${_release}/root/tmp/freebsd-update
                    chroot ${iocroot}/releases/${_release}/root \
                        env UNAME_r="${_release}" env PAGER="/bin/cat" \
                        /tmp/freebsd-update fetch
                    rm ${iocroot}/releases/${_release}/root/tmp/freebsd-update
                    ;;
                *)
                    chroot ${iocroot}/releases/${_release}/root \
                        env UNAME_r="${_release}" env PAGER="/bin/cat" \
                        freebsd-update --not-running-from-cron fetch
                    ;;
                esac

            chroot ${iocroot}/releases/${_release}/root \
                env UNAME_r="${_release}" env PAGER="/bin/cat" \
                freebsd-update install
        fi
    fi

    if [ "${_dist}" = "1" ] ; then
        rm -rf "/usr/local/tmp/iocell-dist"
    fi

    if [ "${_ports}" = "1" ] ; then
        echo "* Updating ports for release: ${_release}."
        sleep 2

        case "${_release}" in
            9.3-RELEASE)
                # Remove the interactive check in portsnap
                sed 's/\[ ! -t 0 \]/false/' \
                    ${iocroot}/releases/${_release}/root/usr/sbin/portsnap \
                    > ${iocroot}/releases/${_release}/root/tmp/portsnap
                chmod +x ${iocroot}/releases/${_release}/root/tmp/portsnap
                chroot ${iocroot}/releases/${_release}/root \
                    env UNAME_r="${_release}" env PAGER="/bin/cat" \
                    /tmp/portsnap fetch extract
                rm ${iocroot}/releases/${_release}/root/tmp/portsnap
                ;;
            *)
                chroot ${iocroot}/releases/${_release}/root \
                    env UNAME_r="${_release}" env PAGER="/bin/cat" \
                    portsnap --interactive fetch extract
                ;;
            esac
        mkdir ${iocroot}/releases/${_release}/root/var/ports > /dev/null 2>&1
        mkdir ${iocroot}/releases/${_release}/root/var/ports/distfiles \
            > /dev/null 2>&1
        mkdir ${iocroot}/releases/${_release}/root/var/ports/packages \
            > /dev/null 2>&1
        if [ -e "${iocroot}/releases/${_release}/root/etc/make.conf" ] ; then
            _ports_string="$(fgrep -xq \
                          "## IOCAGE -- Allow a read-only /usr/ports" \
                          ${iocroot}/releases/${_release}/root/etc/make.conf \
                          ; echo $?)"
            if [ "${_ports_string}" != "0" ] ; then
                __ports_make_conf >> \
                    ${iocroot}/releases/${_release}/root/etc/make.conf
                sed -i '' s/UNAME_r=REPLACE/UNAME_r="${_release}"/ \
                    "${iocroot}/releases/${_release}/root/etc/make.conf"
            fi
        else
            __ports_make_conf > ${iocroot}/releases/${_release}/root/etc/make.conf
            sed -i '' s/UNAME_r=REPLACE/UNAME_r="${_release}"/ \
                "${iocroot}/releases/${_release}/root/etc/make.conf"
        fi
    fi

    rm -f ${iocroot}/releases/${_release}/root/etc/resolv.conf
    mkdir ${iocroot}/releases/${_release}/root/etcupdate 2> /dev/null
    chroot ${iocroot}/releases/${_release}/root \
        etcupdate build /etcupdate/etcupdate-${_release}.tbz
    umount ${iocroot}/releases/${_release}/root/dev
}

# reads tag property from given jail dataset
# creates symlink from $iocroot/jails/<uuid> to $iocroot/tags/<tag>
__link_tag () {
    local _dataset _mountpoint _tag _readlink_tag _readlink_mount _tag_date _altroot
    _dataset=$1
    _tag="$2"

    # If creation wants to set a tag.
    if [ ! -z "${tag}" -a -z "${_tag}" ] ; then
        _tag="${tag}"
    fi

    if _mountpoint=$(zfs get -H -o value mountpoint ${_dataset}) ; then
        _readlink_mount="$(readlink ${iocroot}/tags/${_tag})"
        _readlink_tag="$(readlink ${iocroot}/tags/${_tag} | cut -d \/ -f 4)"
        _tag_date=$(date "+%F@%T")
        mkdir -p ${iocroot}/tags
        if [ ! -e ${iocroot}/tags/${_tag} ] ; then
            _altroot="$(zpool get -H altroot "${pool}" | awk '{print $3}')"
            if [ "${_altroot}" != "-" ]; then
                _mountpoint=${_mountpoint#${iocroot}}
                cd ${iocroot}/tags
                ln -s ..${_mountpoint} ${_tag}
            else
                ln -s ${_mountpoint} ${iocroot}/tags/${_tag}
            fi
        elif [ -e ${iocroot}/tags/${_tag} ] && [ "${_readlink_mount}" != "${_mountpoint}" ] ; then
            echo " "
            echo "  WARNING: Replacing tag ${_tag}!" >&2
            echo "  Renaming ${_readlink_tag}'s tag to ${_tag_date}." >&2
            echo " "
            if [ "${_altroot}" != "-" ]; then
                _mountpoint=${_mountpoint#${iocroot}}
                cd ${iocroot}/tags
                ln -s ..${_mountpoint} ${_tag}
                ln -s ${_readlink_mount} ${iocroot}/tags/${_tag_date}
                __ucl_set "${_readlink_mount}" "tag" "${_tag_date}"
            else
                # Link new tag to jail, and relink old jail to new tag.
                ln -shf ${_mountpoint} ${iocroot}/tags/${_tag}
                ln -s ${_readlink_mount} ${iocroot}/tags/${_tag_date}
                __ucl_set "${_readlink_mount}" "tag" "${_tag_date}"
            fi
        fi
    else
        __die "no such dataset: ${_dataset}!"
    fi
}

# removes all symlinks found in $iocroot/tags pointing to the given jail dataset
__unlink_tag () {
    local _dataset _mountpoint
    _dataset=$1

    if _mountpoint=$(zfs get -H -o value mountpoint ${_dataset}) ; then
        find ${iocroot}/tags -type l -lname "${_mountpoint}*" \
            -exec rm -f \{\} \; > /dev/null 2>&1
        _mountpoint=..${_mountpoint#${iocroot}}
        find ${iocroot}/tags -type l -lname "${_mountpoint}*" \
            -exec rm -f \{\} \; > /dev/null 2>&1
    fi
}

# Log error to stderr
__error () {
    echo "  ERROR: $@" >&2
}

# Log error to stderr and exit
__die () {
    echo "  ERROR: $@" >&2
    exit 1
}

# Log info to stdout
__info () {
    echo "  INFO: $@" >&2
}

# Takes two arguments one is which text gets displayed and the other
# is the command
__spinner () {
    local _spinner
    _spinner='/-\|'

    printf "${1}"

    while true; do
        printf '\b%.1s' "${_spinner}"
        _spinner=${_spinner#?}${_spinner%???}
        sleep .1
    done &

    trap "kill $!" 2 3
    $2
    printf "\b%1s\n" "done!" ; kill $! && trap " " 2 3
    return $?
}

# The make.conf entries needed for read-only ports in a jail
__ports_make_conf () {
cat << 'EOT'

## IOCAGE -- Allow a read-only /usr/ports
UNAME_ENV+=     UNAME_s=FreeBSD
UNAME_ENV+=     UNAME_r=REPLACE
UNAME_ENV+=     UNAME_v="${UNAME_s} ${UNAME_r}"
.MAKEFLAGS:     ${UNAME_ENV}
MAKE_ENV+=      ${UNAME_ENV}
CONFIGURE_ENV+= ${UNAME_ENV}
SCRIPTS_ENV+=   ${UNAME_ENV}
WRKDIRPREFIX=   /var/ports
DISTDIR=        /var/ports/distfiles
PACKAGES=       /var/ports/packages
EOT
}

# Return the default network interface
__get_default_iface() {
    which -s dispatcherctl

    if [ $? -eq 0 ] ; then
        dispatcherctl call container.get_default_interface | jq -r .
    else
        netstat -f inet -nrW | grep '^default' | awk '{ print $6 }'
    fi
}

__pkg_wrapper () {
    local _name _command _jid _fulluuid _dataset
    shift
    _name="$1"
    shift
    _command="$*"
    _dataset="$(__find_jail ${_name})"
    _fulluuid="$(__check_name ${_name} ${_dataset})"
    _jid=$(jls -j ioc-${_fulluuid} jid 2> /dev/null)


    if [ -z "${_jid}" ]; then
        __die "${_name} is not running."
    fi

    pkg -j ${_jid} ${_command}
}

# Requires three arguments. The mountpoint of the jail, the property to be set,
# and the value of that property.
__ucl_set () {
    local _mountpoint _property _value _create

    _mountpoint="$1"
    _property="$2"
    _value="$3"
    _create="$4"

    #If creating a jail this will not exist.
    if [ "${_create}" != "1" ] ; then
        uclcmd set -u -f ${_mountpoint}/config -o ${_mountpoint}/config.new \
            -t string -- "${_property}" "${_value}"

        if [ $? -eq 0 ] ; then
            mv ${_mountpoint}/config ${_mountpoint}/config.old
            mv ${_mountpoint}/config.new ${_mountpoint}/config
        fi
    else
        uclcmd set -u -f ${_mountpoint}/config -t string -- "${_property}" \
            "${_value}"
    fi
}
