Introduce a new function parse_hn_line() that replaces the existing
authorrpe <rpe@openbsd.org>
Mon, 24 Apr 2017 20:31:48 +0000 (20:31 +0000)
committerrpe <rpe@openbsd.org>
Mon, 24 Apr 2017 20:31:48 +0000 (20:31 +0000)
hostname.if(5) parsing code in ifstart().
Add a -n option to netstart to only print the interface configuration
commands instead of executing them.
Add a HN_DIR variable, that points to the directory of the hostname.if
files (default /etc) that allows for future regression tests.

- add new parse_hn_line() function
- change ifstart()
  - rename $if to $_if
  - don't ifconfig or ifconfig create if -n option is used
  - replace hostname.if(5) parsing code with new parse_hn_line()
  - just print configuration commands if -n option is used
- autoconf now happens in ifstart(), remove ifv6autoconf()
- introduce HN_DIR variable for the hostname.if file location
- add handling of the -n option to only print config commands
- ensure -n is only used if interfaces are specified as parameters

Discussed with and positive feedback from many
'commit' deraadt@
OK sthen@

etc/netstart

index f822858..7c1d8ea 100644 (file)
@@ -1,6 +1,6 @@
 #!/bin/sh -
 #
-#      $OpenBSD: netstart,v 1.176 2017/04/08 08:33:05 rpe Exp $
+#      $OpenBSD: netstart,v 1.177 2017/04/24 20:31:48 rpe Exp $
 
 # Turn off Strict Bourne shell mode.
 set +o sh
@@ -19,21 +19,80 @@ stripcom() {
        done <$_file
 }
 
+# Parse and "unpack" a hostname.if(5) line given as positional parameters.
+# Fill the _cmds array with the resulting interface configuration commands.
+parse_hn_line() {
+       local _af=0 _name=1 _mask=2 _bc=3 _prefix=2 _c _cmd _prev _daddr
+       set -A _c -- "$@"
+       set -o noglob
+
+       case ${_c[_af]} in
+       ''|*([[:blank:]])'#'*)
+               return
+               ;;
+       inet)   ((${#_c[*]} > 1)) || return
+               [[ ${_c[_name]} == alias ]] && _mask=3 _bc=4
+               [[ -n ${_c[_mask]} ]] && _c[_mask]="netmask ${_c[_mask]}"
+               if [[ -n ${_c[_bc]} ]]; then
+                       _c[_bc]="broadcast ${_c[_bc]}"
+                       [[ ${_c[_bc]} == *NONE ]] && _c[_bc]=
+               fi
+               _cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
+               ;;
+       inet6)  ((${#_c[*]} > 1)) || return
+               if [[ ${_c[_name]} == autoconf ]]; then
+                       _cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
+                       rtsolif="$rtsolif $_if"
+                       return
+               fi
+               [[ ${_c[_name]} == alias ]] && _prefix=3
+               [[ -n ${_c[_prefix]} ]] && _c[_prefix]="prefixlen ${_c[_prefix]}"
+               _cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
+               ;;
+       dest)   ((${#_c[*]} == 2)) && _daddr=${_c[1]} || return
+               _prev=$((${#_cmds[*]} - 1))
+               ((_prev >= 0)) || return
+               set -A _c -- ${_cmds[_prev]}
+               _name=3
+               [[ ${_c[_name]} == alias ]] && _name=4
+               _c[_name]="${_c[_name]} $_daddr"
+               _cmds[$_prev]="${_c[@]}"
+               ;;
+       dhcp)   _c[0]=
+               _cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]} down;dhclient $_if"
+               dhcpif="$dhcpif $_if"
+               ;;
+       rtsol)  # XXX Support the rtsol keyword for some time to enable a smooth
+               # XXX transition to autoconf.
+               _c[0]=
+               _cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]} up"
+               _cmds[${#_cmds[*]}]="ifconfig $_if inet6 autoconf"
+               rtsolif="$rtsolif $_if"
+               ;;
+       '!'*)   _cmd=$(print -- "${_c[@]}" | sed 's/\$if/'$_if'/g')
+               _cmds[${#_cmds[*]}]="${_cmd#!}"
+               ;;
+       *)      _cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
+               ;;
+       esac
+       unset _c
+}
+
 # Start a single interface.
 # Usage: ifstart if1
 ifstart() {
-       # Note: Do not rename the 'if' variable which is documented as being
-       # usable in hostname.if(5) files.
-       local if=$1 _file=/etc/hostname.$1 _stat
+       local _if=$1 _file=$HN_DIR/hostname.$1 _cmds _i=0 _line _stat
+       set -A _cmds
 
        # Interface names must be alphanumeric only.  We check to avoid
        # configuring backup or temp files, and to catch the "*" case.
-       [[ $if != +([[:alpha:]])+([[:digit:]]) ]] && return
+       [[ $_if != +([[:alpha:]])+([[:digit:]]) ]] && return
 
        if [[ ! -f $_file ]]; then
                echo "netstart: $_file: No such file or directory"
                return
        fi
+
        # Not using stat(1), we can't rely on having /usr yet.
        set -A _stat -- $(ls -nL $_file)
        if [ "${_stat[0]#???????} ${_stat[2]} ${_stat[3]}" != "--- 0 0" ]; then
@@ -41,97 +100,30 @@ ifstart() {
                chmod -LR o-rwx $_file
                chown -LR root.wheel $_file
        fi
-       # Check for ifconfig'able interface.
-       (ifconfig $if || ifconfig $if create) >/dev/null 2>&1 || return
-
-       # Now parse the hostname.* file.
-       while :; do
-               if [ "$cmd2" ]; then
-                       # We are carrying over from the 'read dt dtaddr'
-                       # last time.
-                       set -- $cmd2
-                       af=$1 name=$2 mask=$3 bcaddr=$4 ext1=$5 cmd2=
-                       # Make sure and get any remaining args in ext2,
-                       # like the read below.
-                       i=1
-                       while [ $i -lt 6 -a -n "$1" ]; do shift; let i=i+1; done
-                       ext2="$@"
+
+       # Check for ifconfig'able interface, except if -n option is specified.
+       if ! $PRINT_ONLY; then
+               (ifconfig $_if || ifconfig $_if create) >/dev/null 2>&1 ||
+               return
+       fi
+
+       # Parse the hostname.if(5) file and fill _cmds array with interface
+       # configuration commands.
+       set -o noglob
+       while IFS= read -- _line; do
+               parse_hn_line $_line
+       done <$_file
+
+       # Apply the interface configuration commands stored in _cmds array.
+       while ((_i < ${#_cmds[*]})); do
+               if $PRINT_ONLY; then
+                       print -r -- "${_cmds[_i]}"
                else
-                       # Read the next line or exit the while loop.
-                       read af name mask bcaddr ext1 ext2 || break
+                       eval "${_cmds[_i]}"
                fi
-               # $af can be "dhcp", "up", "rtsol", an address family, commands,
-               # or a comment.
-               case "$af" in
-               "#"*|"")
-                       # Skip comments and empty lines.
-                       continue
-                       ;;
-               "!"*) # Parse commands.
-                       cmd="${af#*!} ${name} ${mask} ${bcaddr} ${ext1} ${ext2}"
-                       ;;
-               "dhcp")
-                       [ "$name" = "NONE" ] && name=
-                       [ "$mask" = "NONE" ] && mask=
-                       [ "$bcaddr" = "NONE" ] && bcaddr=
-                       cmd="ifconfig $if $name $mask $bcaddr $ext1 $ext2 down"
-                       cmd="$cmd;dhclient $if"
-                       dhcpif="$dhcpif $if"
-                       ;;
-               "rtsol")
-                       rtsolif="$rtsolif $if"
-                       cmd="ifconfig $if $name $mask $bcaddr $ext1 $ext2 up"
-                       ;;
-               *)
-                       read dt dtaddr
-                       if [ "$name" = "alias" ]; then
-                               # Perform a 'shift' of sorts.
-                               alias=$name
-                               name=$mask
-                               mask=$bcaddr
-                               bcaddr=$ext1
-                               ext1=$ext2
-                               ext2=
-                       else
-                               alias=
-                       fi
-                       cmd="ifconfig $if $af $alias $name"
-                       case "$dt" in
-                       dest)
-                               cmd="$cmd $dtaddr"
-                               ;;
-                       *)
-                               cmd2="$dt $dtaddr"
-                               ;;
-                       esac
-                       case $af in
-                       inet)
-                               if [ ! -n "$name" ]; then
-                                       echo "/etc/hostname.$if: inet alone is invalid"
-                                       return
-                               fi
-                               [ "$mask" ] && cmd="$cmd netmask $mask"
-                               if [ "$bcaddr" -a "X$bcaddr" != "XNONE" ]; then
-                                       cmd="$cmd broadcast $bcaddr"
-                               fi
-                               ;;
-                       inet6)
-                               if [ ! -n "$name" ]; then
-                                       echo "/etc/hostname.$if: inet6 alone is invalid"
-                                       return
-                               fi
-                               [ "$mask" ] && cmd="$cmd prefixlen $mask"
-                               cmd="$cmd $bcaddr"
-                               ;;
-                       *)
-                               cmd="$cmd $mask $bcaddr"
-                               ;;
-                       esac
-                       cmd="$cmd $ext1 $ext2"
-                       ;;
-               esac
-               eval "$cmd"
-       done <$_file
+               ((_i++))
+       done
+       unset _cmds
 }
 
 # Start multiple interfaces by driver name.
@@ -196,6 +188,23 @@ fi
 FUNCS_ONLY=1 . /etc/rc.d/rc.subr
 _rc_parse_conf
 
+HN_DIR=${HN_DIR:-/etc}
+PRINT_ONLY=false
+USAGE="USAGE: ${0##*/} [-n] [interface ...]"
+while getopts ":n" opt; do
+       case $opt in
+       n)      PRINT_ONLY=true;;
+       *)      print -u2 "$USAGE"; exit 1;;
+       esac
+done
+shift $((OPTIND-1))
+
+# Option -n is only supported if interface names are specified as parameters.
+if $PRINT_ONLY && (($# == 0)); then
+       print -u2 "Missing parameters.\n$USAGE"
+       exit 1
+fi
+
 # If we were invoked with a list of interface names, just reconfigure these
 # interfaces (or bridges), add default routes and return.
 if (($# > 0)); then