### 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 profile, checking whether it exists and is usable
load_profile()
{
    [[ -z "$1" ]] && return 1
    if [[ ! -f "$PROFILE_DIR/$1" ]]; then
        report_err "Profile \"$1\" does not exist"
        return 1
    fi
    report_debug "Loading profile $1"
    INTERFACE=$(. "$PROFILE_DIR/$1"; echo "$INTERFACE")
    report_debug "Configuring interface $INTERFACE"
    if [[ -z "$INTERFACE" ]]; then
        report_err "Profile missing an interface to configure"
        return 1
    fi
    if [[ -f "$IFACE_DIR/$INTERFACE" ]]; then
        report_debug "Interface level configuration enabled: $IFACE_DIR/$INTERFACE"
        . "$IFACE_DIR/$INTERFACE"
    fi
    . "$PROFILE_DIR/$1" # we want profile settings to override, so need to source profile again
    if [[ ! -f "$CONN_DIR/$CONNECTION" ]]; then
        report_err "$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()
{
    for prof in $(find "$STATE_DIR/profiles/" -maxdepth 1 -type f -printf '%f\n'); do
        profile_down "$prof"
    done
}

# Create the state dir and sub directories if they don't already exist
check_make_state_dir()
{
    [[ ! -d "$STATE_DIR" ]] && mkdir -p "$STATE_DIR"/{interfaces,profiles}
    for d in interfaces profiles suspend; do
        [[ ! -d "$STATE_DIR/$d" ]] && mkdir "$STATE_DIR/$d"
    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 "$@"

    check_make_state_dir
    find "$STATE_DIR/profiles/" -maxdepth 1 -type f -printf '%f\n' \
    | while read prof; do
        # the pipe to "while read" will create a subshell
        INTERFACE=$(. "$STATE_DIR/profiles/$prof"; echo "$INTERFACE")
        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
        INTERFACE=$(. "$STATE_DIR/suspend/$prof"; echo "$INTERFACE")
        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
    check_make_state_dir

    local status 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_err "$PROFILE already connected"
        exit 1
    fi

    # 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"

    status=$(check_iface "$INTERFACE")
    report_debug "status reported to profile_up as: $status"
    case "$status" in
        external)
            report_fail "Interface $INTERFACE externally controlled"
            exit 1
            ;;
        disabled)
            report_fail "Interface $INTERFACE is disabled"
            exit 1
            ;;
        "")
            ;;
        *)
            if checkyesno "$CHECK"; then
                report_fail "Interface $INTERFACE already in use"
                exit 1

            # not necessary to sandbox this call or reload PROFILE afterwards
            elif ! interface_down "$INTERFACE"; then
                report_fail
                exit 1
            fi
            ;;
    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"; then
        report_debug profile_up "connect failed"
        report_fail
        #  "$CONN_DIR/$CONNECTION" down "$PROFILE" # 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"
        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
        INTERFACE=$(. "$STATE_DIR/suspend/$prof"; echo "$INTERFACE")
        if [[ "$iface" == "$INTERFACE" ]]; then
            rm "$STATE_DIR/suspend/$prof"
        fi
    done

    report_success
    ); return $?
}

# profile_down profile
#   take profile down
#
profile_down()
{
    (
    check_make_state_dir

    local status PROFILE="$1"   # save PROFILE in a variable so that it's available to PRE_UP/POST_DOWN etc hooks

    load_profile "$PROFILE" || exit 1

    status=$(check_iface "$INTERFACE")
    report_debug "status reported to profile_down as: $status"

    if [[ "$status" != "$PROFILE" ]]; then
        # if interface not available to be controlled by netcfg, then
        # any profiles should have been removed by check_iface
        # else we get here if another profile is running
        report_err "Profile not connected"
        exit 1
    fi

    report_try "$PROFILE down"
    if [[ "$(check_iface "$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"; 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()
{
    local item 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 status=$(check_iface "$1")
    case "$status" in
        disabled) return 0 ;;
        "") return 0 ;;
        external) return 1 ;;
        *) profile_down "$status" ;;
    esac
}


##
# check_iface interface
#   Return 0 if interface unavailable (in use by a profile or externally, or disabled)
#   Return 1 if interface down and available to be used
#
check_iface() {
    if [[ -f "$STATE_DIR/interfaces/$1" ]]; then (
        . "$STATE_DIR/interfaces/$1"
        echo "$PROFILE" # may be: external, disabled, or a profile name
        return 0
        )
        return 0
    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() {
    local INTERFACE
    if [[ "$1" == "up" ]]; then
        INTERFACE=$(. "$PROFILE_DIR/$2"; echo "$INTERFACE")
        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
        INTERFACE=$(. "$STATE_DIR/profiles/$2"; echo "$INTERFACE")
        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
}


is_interface() {
    local INTERFACE="$1"
    if [[ ! -e "/sys/class/net/$INTERFACE" ]]; then
        if ! echo "$INTERFACE" | fgrep -q ":"; then
            return 1
        fi
    fi
    return 0
}

bring_interface()
{
    local INTERFACE="$2"
    case "$1" in
        up)
            if ! ( eval $IFACE_UP ); then
                return 1
            fi
            ip link set dev "$INTERFACE" up &>/dev/null
            sleep "${UP_SLEEP:-2}"
        ;;
        flush|down)
            if ! ( eval $IFACE_DOWN ); then
                return 1
            fi
            ip addr flush dev "$INTERFACE" &>/dev/null
        ;;&
        down)
            ip link set dev "$INTERFACE" down &>/dev/null
        ;;
    esac
}

# vim: ft=sh ts=4 et sw=4:

