#!/bin/zsh
# network/start.sh — BSD-style interface/route bring-up (v2)
#
# No ipmgmtd, no inetd. Uses /sbin/ifconfig directly for plumbing,
# /sbin/dhcpagent (forked by ifconfig dhcp start) for DHCP, and
# /usr/sbin/route for static routes from /etc/defaultrouter. dlmgmtd is
# required: modern illumos `ifconfig <link> plumb` resolves names through
# it, so we keep it but skip the rest of the SMF networking stack.
#
# Walks /etc/hostname.<if> files (one per interface to configure). Each
# file is line-oriented; see the case statement below for supported syntax.

set -e
set -u

# Enable [[:space:]]## "one or more" repetition in pattern matching.
setopt EXTENDED_GLOB

IFCONFIG=/sbin/ifconfig
DLADM=/sbin/dladm
ROUTE=/usr/sbin/route

# --- Force-load IP STREAMS modules ---
# zyginit's tier ordering runs network/start.sh before SMF's traditional
# autoload triggers fire, so socket(PF_INET) returns EAFNOSUPPORT until
# the ip / ip6 drivers are explicitly loaded. modload is idempotent:
# returns success on already-loaded modules.
/sbin/modload /kernel/drv/ip || true
/sbin/modload /kernel/drv/ip6 || true

# --- Datalink registration ---
# Tell dlmgmtd about every physical link the kernel attached after dlmgmtd
# started (e.g. virtio NICs that came up via devfsadm). Without this,
# `ifconfig vioif0 plumb` returns "no such interface" because dlmgmtd
# can't translate the link name to a DLPI device. Mirrors what SMF's
# network/physical method runs.
$DLADM init-phys || true

# --- Loopback ---
# Plumb lo0 explicitly (the kernel auto-attaches it but we want predictable
# state). `ifconfig lo0 plumb` is idempotent — repeats just succeed.
$IFCONFIG lo0 plumb || true
$IFCONFIG lo0 inet 127.0.0.1 netmask 255.0.0.0 up || true
$IFCONFIG lo0 inet6 ::1/128 up || true

# --- Physical interfaces ---
# Each /etc/hostname.<if> file marks an interface to configure (BSD style).
# The configure_interface() helper plumbs the named link and applies the
# directives in the file.
configure_interface() {
    local iface="$1"
    local cfgfile="/etc/hostname.${iface}"

    if [[ ! -e "$cfgfile" ]]; then
        # No config — leave the interface untouched. (Operator can plumb
        # by-hand or add a config file and re-run; this matches OpenBSD.)
        return 0
    fi

    # Plumb first; downstream lines may add addresses to it.
    $IFCONFIG "$iface" plumb || true

    # If the config file has no non-comment content, just bring it up.
    if ! grep -Eq '^[[:space:]]*[^#[:space:]]' "$cfgfile" 2>/dev/null; then
        $IFCONFIG "$iface" up
        return 0
    fi

    # Walk the file line by line.
    while IFS= read -r line || [[ -n "$line" ]]; do
        # Strip comments, then leading and trailing whitespace.
        # `[[:space:]]##` is zsh extended glob for "one or more whitespace
        # chars" — covers tab-indented lines and multi-space margins that
        # an editor might insert. EXTENDED_GLOB is enabled at script top;
        # the `\#` escape is needed because EXTENDED_GLOB makes bare `#` a
        # pattern operator.
        line="${line%%\#*}"
        line="${line##[[:space:]]##}"
        line="${line%%[[:space:]]##}"
        [[ -z "$line" ]] && continue

        # Split on first whitespace into verb + rest.
        local verb="${line%%[[:space:]]*}"
        local rest=""
        if [[ "$line" == *[[:space:]]* ]]; then
            rest="${line#*[[:space:]]}"
        fi

        # ${=rest} forces word-splitting on $rest — necessary because
        # ifconfig args like `192.168.122.50/24 up` need to arrive as
        # separate arguments, not one quoted string.
        case "$verb" in
            dhcp)
                $IFCONFIG "$iface" dhcp start
                ;;
            inet)
                # `inet <addr>/<prefix>` or `inet <addr> netmask <mask>`
                $IFCONFIG "$iface" inet ${=rest} up
                ;;
            addif)
                $IFCONFIG "$iface" addif ${=rest} up
                ;;
            inet6)
                $IFCONFIG "$iface" inet6 ${=rest} up
                ;;
            up|down)
                $IFCONFIG "$iface" "$verb"
                ;;
            *)
                print -u2 "network: ignoring unknown directive in $cfgfile: $line"
                ;;
        esac
    done < "$cfgfile"
}

# Iterate /etc/hostname.<if> files — the BSD-style marker for "configure
# this interface." Iter 7 boot showed /dev/net/ enumeration silently
# skipped vioif0 on Hammerhead (the directory either doesn't exist or
# doesn't contain DLPI link nodes for virtio NICs); switching to
# hostname.* lookup is both more portable and closer to OpenBSD's
# original semantics.
for cfg in /etc/hostname.*(N); do
    fname="${cfg:t}"
    # ${cfg:t} returns "hostname.vioif0"; strip the prefix.
    iface="${fname#hostname.}"
    # Defensively skip loopback (we already handled lo0 above).
    [[ "$iface" == "lo0" ]] && continue
    configure_interface "$iface"
done

# --- Static default routes ---
# /etc/defaultrouter is one IPv4 address per line. DHCP-acquired routes
# don't need this file; only set one when the operator opts in.
if [[ -r /etc/defaultrouter ]]; then
    while IFS= read -r line || [[ -n "$line" ]]; do
        # Same trim treatment as the hostname.<if> parser. `\#` is
        # escaped because EXTENDED_GLOB is set script-wide.
        line="${line%%\#*}"
        line="${line##[[:space:]]##}"
        line="${line%%[[:space:]]##}"
        [[ -z "$line" ]] && continue
        $ROUTE -n add default "$line" || true
    done < /etc/defaultrouter
fi

exit 0
