#-
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2021, 2023 The FreeBSD Foundation
#
# This software was developed by Mark Johnston under sponsorship from
# the FreeBSD Foundation.
#
# This software was developed by Jake Freeland under sponsorship from
# the FreeBSD Foundation.
#

readonly SYSLOGD_UDP_PORT="5140"
readonly SYSLOGD_CONFIG="${PWD}/syslog.conf"
readonly SYSLOGD_LOCAL_SOCKET="${PWD}/log.sock"
readonly SYSLOGD_PIDFILE="${PWD}/syslogd.pid"
readonly SYSLOGD_LOCAL_PRIVSOCKET="${PWD}/logpriv.sock"
readonly SYSLOGD_LOGFILE="${PWD}/log"

# Start a private syslogd instance.
syslogd_start()
{
    local jail bind_arg conf_file pid_file socket privsocket
    local opt next other_args

    OPTIND=1
    while getopts ":b:f:j:P:p:S:" opt; do
        case "${opt}" in
        b)
            bind_arg="${bind_arg} -b ${OPTARG}"
            ;;
        f)
            conf_file="${OPTARG}"
            ;;
        j)
            jail="jexec ${OPTARG}"
            ;;
        P)
            pid_file="${OPTARG}"
            ;;
        p)
            socket="${OPTARG}"
            ;;
        S)
            privsocket="${OPTARG}"
            ;;
        ?)
            opt="${OPTARG}"
            next="$(eval echo \${${OPTIND}})"

            case "${next}" in
            -* | "")
                other_args="${other_args} -${opt}"
                shift $((OPTIND - 1))
                ;;
            *)
                other_args="${other_args} -${opt} ${next}"
                shift ${OPTIND}
                ;;
            esac

            # Tell getopts to continue parsing.
            OPTIND=1
            ;;
        :)
            atf_fail "The -${OPTARG} flag requires an argument"
            ;;
        esac
    done

    socket=${socket:-${SYSLOGD_LOCAL_SOCKET}}
    if [ -S "${socket}" ]; then
        atf_fail "socket ${socket} already exists"
    fi

    # Setup loopback so we can deliver messages to ourself.
    if [ $($jail sysctl -n security.jail.vnet) -ne 0 ]; then
        atf_check $jail ifconfig lo0 inet 127.0.0.1/8
    fi

    $jail syslogd \
        ${bind_arg:--b :${SYSLOGD_UDP_PORT}} \
        -C \
        -d \
        -f "${conf_file:-${SYSLOGD_CONFIG}}" \
        -H \
        -P "${pid_file:-${SYSLOGD_PIDFILE}}" \
        -p "${socket}" \
        -S "${privsocket:-${SYSLOGD_LOCAL_PRIVSOCKET}}" \
        ${other_args} \
        &

    # Give syslogd a bit of time to spin up.
    if grep -q "[[:space:]]${SYSLOGD_LOGFILE}$" "${conf_file:-${SYSLOGD_CONFIG}}"; then
        while [ ! -f "${SYSLOGD_LOGFILE}" ]; do
            sleep 0.1
        done
    fi
    while [ "$((i+=1))" -le 20 ]; do
        [ -S "${socket}" ] && return
        sleep 0.1
    done
    atf_fail "timed out waiting for syslogd to start"
}

# Simple logger(1) wrapper.
syslogd_log()
{
    atf_check -s exit:0 -o empty -e empty logger $*
}

syslogd_log_jail()
{
    local jailname=$1
    shift
    atf_check -s exit:0 -o empty -e empty jexec ${jailname} logger $*
}

# Make syslogd reload its configuration file.
syslogd_reload()
{
    atf_check truncate -s 0 ${SYSLOGD_LOGFILE}
    atf_check pkill -HUP -F "${1:-${SYSLOGD_PIDFILE}}"
    sleep 0.1
}

# Stop a private syslogd instance.
syslogd_stop()
{
    local pid_file="${1:-${SYSLOGD_PIDFILE}}"
    local socket_file="${2:-${SYSLOGD_LOCAL_SOCKET}}"
    local privsocket_file="${3:-${SYSLOGD_LOCAL_PRIVSOCKET}}"

    pid=$(cat "${pid_file}")
    if pkill -F "${pid_file}"; then
        wait "${pid}"
        rm -f "${pid_file}" "${socket_file}" "${privsocket_file}"
    fi
}

# Check required kernel module.
syslogd_check_req()
{
    type=$1

    if kldstat -q -n if_${type}.ko; then
        return
    fi

    if ! kldload -n -q if_${type}; then
        atf_skip "if_${type}.ko is required to run this test."
        return
    fi
}

# Make a jail and save its name to the created_jails.lst file.
# Accepts a name and optional arguments.
syslogd_mkjail()
{
    jailname=$1
    shift
    args=$*

    atf_check jail -c name=${jailname} ${args} persist

    echo $jailname >> created_jails.lst
}

# Remove epair interfaces and jails.
syslogd_cleanup()
{
    if [ -f created_jails.lst ]; then
        while read jailname; do
            jail -r ${jailname}
        done < created_jails.lst
        rm created_jails.lst
    fi

    if [ -f epair ]; then
        ifconfig $(cat epair) destroy
    fi
}

# Check the last entry in the log file for a given message.
syslogd_check_log_nopoll()
{
    local msg=$1

    atf_check -o match:"${msg}" tail -n 1 "${SYSLOGD_LOGFILE}"
}

# Same as above, but first wait for syslogd to write to the log file.
syslogd_check_log()
{
    local msg=$1

    atf_check -r 10 -o match:"${msg}" tail -n 1 "${SYSLOGD_LOGFILE}"
}

# Make sure no log entry matching the given message exists.
syslogd_check_log_nomatch()
{
    local msg=$1

    sleep 0.5
    atf_check -o not-match:"${msg}" cat "${SYSLOGD_LOGFILE}"
}
