### Globals
. /usr/lib/network/globals
# will load hooks and they in turn will load /etc/rc.d/functions if they need to

### Profile loading
##
# load_profile profile
#   source the profile
load_profile() {
    validate_profile "$1" || return 1
    . "$PROFILE_DIR/$1"
}
# validate_profile profile
#   check whether profile exists and is usable
validate_profile()
{
    [[ -z "$1" ]] && return 1
    if [[ ! -f "$PROFILE_DIR/$1" ]]; then
        report_fail "Profile \"$1\" does not exist"
        return 1
    fi
    . "$PROFILE_DIR/$1"
    if [[ -z "$INTERFACE" ]]; then
        report_fail "Profile missing an interface to configure"
        return 1
    fi
    if [[ ! -f "$CONN_DIR/$CONNECTION" ]]; then
        report_fail "$CONNECTION is not a valid connection, check spelling or look at examples"
        return 1
    fi
}

### Profile up/down
##
# all_down
#   take all registered profiles down
all_down()
{
    find "$STATE_DIR/profiles/" -maxdepth 1 -type f -printf '%f\n' \
	| while read prof; do
		# the pipe to while read... creates a subshell
        profile_down "$prof"
    done
}

# interface_suspend interface/all [call_profile_down? default=yes]
#   store a list of running profiles and take them down (unless $2 is "no")
interface_suspend() 
{
	report_debug interface_suspend "$@"

    [[ ! -d "$STATE_DIR" ]] && mkdir -p "$STATE_DIR"/{interfaces,profiles}
    [[ ! -d "$STATE_DIR/suspend" ]] && mkdir "$STATE_DIR/suspend"

    find "$STATE_DIR/profiles/" -maxdepth 1 -type f -printf '%f\n' \
	| while read prof; do
        # the pipe to "while read" will create a subshell, so sourced variables will already be in a sandbox
        # we just need to clear INTERFACE which is all we care about
        unset INTERFACE
        . "$STATE_DIR/profiles/$prof"
        if [[ "$1" == all || "$1" == "$INTERFACE" ]]; then
            report_notify "suspending interface $INTERFACE with profile $prof" 
			cp "$STATE_DIR/profiles/$prof" "$STATE_DIR/suspend/"
            if checkyesno "${2:-yes}"; then
				profile_down "$prof"
            fi
        fi
    done
}

# all_suspend
#   store a list of running profiles and take them down
all_suspend() {
    interface_suspend all
}
# all_resume
#   resume suspended interfaces
#   optional arguments: interfaces not to resume (e.g., because they're disabled)
all_resume()
{
	report_debug all_resume "$@"
    find "$STATE_DIR/suspend/" -maxdepth 1 -type f -printf '%f\n' \
	| while read prof; do
        # the pipe to "while read" will create a subshell, so sourced variables will already be in a sandbox
        # we just need to clear INTERFACE which is all we care about
        unset INTERFACE
        . "$STATE_DIR/suspend/$prof"
        if [[ $# -eq 0 || ! " $* " =~ " $INTERFACE " ]]; then
            report_notify "resuming interface $INTERFACE with profile $prof"
		profile_up "$prof"
            rm -f "$STATE_DIR/suspend/$prof"   # if profile_up succeeds, it will have already removed this
        fi
    done
}

# profile_up profile
#   put all profiles up
#
profile_up()
{
    ( 
    # Keep inside subshell so that options from one profile don't cross to others
    # exit 1 used in a subshell is effectively exiting a new process
    [[ ! -d "$STATE_DIR" ]] && mkdir -p "$STATE_DIR"/{interfaces,profiles,suspend}
	local PROFILE="$1"	# save PROFILE in a variable so that it's available to PRE_UP/POST_DOWN etc hooks
    
    load_profile "$PROFILE" || exit 1

    check_profile "$PROFILE" && report_fail "$PROFILE already connected" &&  exit 1

    # NETWORKS_EXCLUSIVE, rc.conf: Profiles are globally mutually exclusive
    # EXCLUSIVE, network.d/profile: Individual profile is mutually exclusive
    if checkyesno "$NETWORKS_EXCLUSIVE" || checkyesno "$EXCLUSIVE"; then
        all_down
    fi

    report_try "$PROFILE up"
    
    case $(check_iface $INTERFACE) in
        up)
            if checkyesno $CHECK; then
                report_fail "Interface $INTERFACE already in use"
                exit 1
            else
                interface_down $INTERFACE || exit 1
                load_profile "$PROFILE"
            fi
        ;;
        external)
            report_fail "Interface $INTERFACE externally controlled"
            exit 1
        ;;
    esac
   
	if ! ( eval $PRE_UP ); then		# JP: sandbox the eval so variables don't bleed into current function
		report_debug profile_up "PRE_UP failed"
		report_fail
		exit 1
    fi
    
    if ! "$CONN_DIR/$CONNECTION" up "$PROFILE" "$2"; then
		report_debug profile_up "connect failed"
        report_fail
		#  "$CONN_DIR/$CONNECTION" down "$PROFILE" "$2" # JP: should we do this to make sure?
        exit 1
    fi
    
	if ! ( eval $POST_UP ); then	# JP: sandbox the eval
		report_debug profile_up "POST_UP failed"
		report_fail
		# failing POST_UP will take interface down
		"$CONN_DIR/$CONNECTION" down "$PROFILE" "$2"
		exit 1
	fi
    
    set_profile up "$PROFILE"
    unset EXCLUSIVE 
    
    # Successfully running a new profile; erase any suspended profiles on this interface
    local iface="$INTERFACE"
    find "$STATE_DIR/suspend/" -maxdepth 1 -type f -printf '%f\n' \
	| while read prof; do
        # the pipe to "while read" will create a subshell, so sourced variables will already be in a sandbox
        # we just need to clear INTERFACE which is all we care about
        unset INTERFACE
        . "$STATE_DIR/suspend/$prof"
        if [[ "$iface" == "$INTERFACE" ]]; then
            rm "$STATE_DIR/suspend/$prof"
        fi
    done

    report_success
    ); return $? 
}

# profile_down profile
#   take profile down
#
profile_down()
{
    (
    [[ ! -d "$STATE_DIR" ]] && mkdir -p "$STATE_DIR"/{interfaces,profiles,suspend}
    
	local PROFILE="$1"	# save PROFILE in a variable so that it's available to PRE_UP/POST_DOWN etc hooks

    load_profile "$PROFILE" || exit 1
    
    if ! check_profile "$PROFILE"; then
        report_fail "Profile not connected" 
        exit 1
    fi
    
    report_try "$PROFILE down"
    if [[ "$(get_iface_prof $INTERFACE)" == "external" ]]; then
        report_fail "$interface was connected by another application"
        exit 1
    fi
    
	if ! ( eval $PRE_DOWN ); then	# JP: sandbox the eval
		report_debug profile_down "PRE_DOWN failed"
		# true	# JP: did we want failing PRE_DOWN to leave the profile active?
		report_fail
		exit 1
	fi
    
    if ! "$CONN_DIR/$CONNECTION" down "$PROFILE" "$2"; then
		report_debug profile_up "disconnect failed"
        report_fail
        exit 1
    fi
    
	if ! ( eval $POST_DOWN ); then	# JP: sandbox the eval
		report_debug profile_down "POST_DOWN failed"
		report_fail
		exit 1
	fi
    
    set_profile down "$PROFILE"
    report_success
    ); return $?
}

# Check if variable is a member of an array
inarray()
{
search="$1"
shift 
for item in "$@"; do
    if [[ "$item" == "$search" ]]; then
        return 0
    fi
done
return 1
}

quirk() {
inarray "$1" "${QUIRKS[@]}"
return $?
}

# interface_down interface
#   take interface down
#
interface_down()
{
    local prof=$(get_iface_prof "$1")
    profile_down "$prof"
    return $?
}


##
# check_iface interface
#   Return 0 if interface up
#   Return 1 if interface down
#
check_iface() {
    if [[ -f "$STATE_DIR/interfaces/$1" ]]; then (
        . "$STATE_DIR/interfaces/$1"
        if [[ "$PROFILE" == "external" ]]; then
            echo "external"
        else
            echo "up"
        fi        
        exit 0
        )
    else
        return 1
    fi
}

# get_iface_prof interface
#   Echo interface profile and return 0 if up
#   Return 1 if down.
#
get_iface_prof() {
    if check_iface "$1" &>/dev/null; then
        . "$STATE_DIR/interfaces/$1"
        echo "$PROFILE"
    else
        return 1
    fi
}

# list_profiles
#  Outputs a list of all profiles
list_profiles() {
    # JP: follow aliases with -L, also skip profiles that start with '.' or end with '~' or '.conf' (so profile.conf can be the wpa.conf file for profile)
	find -L "$PROFILE_DIR/" -maxdepth 1 -type f -not -name '*~' -not -name '*.conf' -not -name '.*' -printf "%f\n"
}
# check_profile profile
#   Return 0 if profile registered as being up
#   Return 1 if profile not registered
#
check_profile() {
    [[ -f "$STATE_DIR/profiles/$1" && ! -f "$STATE_DIR/suspend/$1" ]] && return 0
    return 1
} 

### Status setting functions
##
# set_profile up/down profile
#   Set profile state, either up or down
#
set_profile() {
    if [[ "$1" == "up" ]]; then
        (   # subshell creates sandbox for sourced variables
        . "$PROFILE_DIR/$2"
        cp "$PROFILE_DIR/$2" "$STATE_DIR/profiles/"
        echo "$2" > "$STATE_DIR/last_profile"
        set_iface up "$INTERFACE" "$2"
		)
    elif [[ "$1" == "down" && -f "$STATE_DIR/profiles/$2" ]]; then    # JP: skip if profile not already up 
        (   # subshell
        . "$STATE_DIR/profiles/$2"
        rm "$STATE_DIR/profiles/$2"
        set_iface down "$INTERFACE" "$2"
		)
    fi
}

# set_iface up/down interface [profile]
#   Set interface status to up/down
#   optionally link it to a profile.
#
set_iface() {
    local PROFILE="$3"
    [[ -z "$PROFILE" ]] && PROFILE=external
    if [[ "$1" == "up" ]]; then
        echo "PROFILE=$PROFILE" > "$STATE_DIR/interfaces/$2"
    elif [[ "$1" == "down" ]]; then
        rm -f "$STATE_DIR/interfaces/$2"    # JP: add -f so we don't complain if the interface isn't up
    fi    
}

set_interface()
{
    INTERFACE="$2"
    case "$1" in
        up|up-old)
            at_interface_up
			if [ "$1" = old ]; then
				ifconfig "$INTERFACE" up
			else
				ip link set dev "$INTERFACE" up
			fi
            sleep "${UP_SLEEP:-2}"
        ;;
        down|forcedown|down-old|forcedown-old)
            at_interface_down
			if [ "${1%-old}" != "$1" ]; then
				## ?
				if ! quirk nodown || [ "$1" = forcedown-old ]; then
					ifconfig "$INTERFACE" down
				fi
			else
				ip addr flush dev "$INTERFACE" &>/dev/null
				if ! quirk nodown || [ "$1" = forcedown ]; then
					ip link set dev "$INTERFACE" down &>/dev/null
				fi
			fi
        ;;
        *)
            return 1
        ;;
    esac
}


### From FreeBSD's /etc/rc.subr
##
# checkyesno var
#    Test $1 variable, and warn if not set to YES or NO.
#    Return 0 if it's "yes" (et al), nonzero otherwise.
#
checkyesno()
{
    local _value="${1}"
    #debug "checkyesno: $1 is set to $_value."
    case "$_value" in

        #    "yes", "true", "on", or "1"
    [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
        return 0
        ;;

        #    "no", "false", "off", or "0"
    [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0)
        return 1
        ;;
    *)
        #warn "\$${1} is not set properly - see ${rcvar_manpage}."
        return 1
        ;;
    esac
}
