mirror of
				https://github.com/xdp-project/bpf-examples.git
				synced 2024-05-06 15:54:53 +00:00 
			
		
		
		
	Resolves a race condition where the MAC can change Signed-off-by: Toke Høiland-Jørgensen <toke@toke.dk>
		
			
				
	
	
		
			550 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			550 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
| #!/bin/bash
 | |
| # SPDX-License-Identifier: GPL-2.0-or-later
 | |
| #
 | |
| # Script to setup and manage test environment for the XDP tutorial.
 | |
| # See README.org for instructions on how to use.
 | |
| #
 | |
| # Author:   Toke Høiland-Jørgensen (toke@redhat.com)
 | |
| # Date:     6 March 2019
 | |
| # Copyright (c) 2019 Red Hat
 | |
| 
 | |
| set -o errexit
 | |
| set -o nounset
 | |
| umask 077
 | |
| 
 | |
| source "$(dirname "$0")/config.sh"
 | |
| 
 | |
| NEEDED_TOOLS="ethtool ip tc ping"
 | |
| MAX_NAMELEN=15
 | |
| 
 | |
| # Global state variables that will be set by options etc below
 | |
| GENERATE_NEW=0
 | |
| CLEANUP_FUNC=
 | |
| STATEFILE=
 | |
| CMD=
 | |
| NS=
 | |
| LEGACY_IP=0
 | |
| USE_VLAN=0
 | |
| RUN_ON_INNER=0
 | |
| 
 | |
| # State variables that are written to and read from statefile
 | |
| STATEVARS=(IP6_PREFIX IP4_PREFIX
 | |
|            INSIDE_IP6 INSIDE_IP4 INSIDE_MAC
 | |
|            OUTSIDE_IP6 OUTSIDE_IP4 OUTSIDE_MAC
 | |
|            ENABLE_IPV4 ENABLE_VLAN)
 | |
| IP6_PREFIX=
 | |
| IP4_PREFIX=
 | |
| INSIDE_IP6=
 | |
| INSIDE_IP4=
 | |
| INSIDE_MAC=
 | |
| OUTSIDE_IP6=
 | |
| OUTSIDE_IP4=
 | |
| OUTSIDE_MAC=
 | |
| ENABLE_IPV4=0
 | |
| ENABLE_VLAN=0
 | |
| 
 | |
| die()
 | |
| {
 | |
|     echo "$1" >&2
 | |
|     exit 1
 | |
| }
 | |
| 
 | |
| check_prereq()
 | |
| {
 | |
|     local max_locked_mem=$(ulimit -l)
 | |
| 
 | |
|     for t in $NEEDED_TOOLS; do
 | |
|         which "$t" > /dev/null || die "Missing required tools: $t"
 | |
|     done
 | |
| 
 | |
|     if [ "$EUID" -ne "0" ]; then
 | |
|         die "This script needs root permissions to run."
 | |
|     fi
 | |
| 
 | |
|     [ -d "$STATEDIR" ] || mkdir -p "$STATEDIR" || die "Unable to create state dir $STATEDIR"
 | |
| 
 | |
|     if [ "$max_locked_mem" != "unlimited" ]; then
 | |
| 	ulimit -l unlimited || die "Unable to set ulimit"
 | |
|     fi
 | |
| }
 | |
| 
 | |
| get_nsname()
 | |
| {
 | |
|     local GENERATE=${1:-0}
 | |
| 
 | |
|     if [ -z "$NS" ]; then
 | |
|         [ -f "$STATEDIR/current" ] && NS=$(< "$STATEDIR/current")
 | |
| 
 | |
|         if [ "$GENERATE" -eq "1" ] && [ -z "$NS" -o "$GENERATE_NEW" -eq "1" ]; then
 | |
|             NS=$(printf "%s-%04x" "$GENERATED_NAME_PREFIX" $RANDOM)
 | |
|         fi
 | |
|     fi
 | |
| 
 | |
|     if [ "${#NS}" -gt "$MAX_NAMELEN" ]; then
 | |
|         die "Environment name '$NS' is too long (max $MAX_NAMELEN)"
 | |
|     fi
 | |
| 
 | |
|     STATEFILE="$STATEDIR/${NS}.state"
 | |
| }
 | |
| 
 | |
| ensure_nsname()
 | |
| {
 | |
|     [ -z "$NS" ] && die "No environment selected; use --name to select one or 'setup' to create one"
 | |
|     [ -e "$STATEFILE" ] || die "Environment for $NS doesn't seem to exist"
 | |
| 
 | |
|     echo "$NS" > "$STATEDIR/current"
 | |
| 
 | |
|     read_statefile
 | |
| }
 | |
| 
 | |
| get_num()
 | |
| {
 | |
|     local num=1
 | |
|     if [ -f "$STATEDIR/highest_num" ]; then
 | |
|         num=$(( 1 + $(< "$STATEDIR/highest_num" )))
 | |
|     fi
 | |
| 
 | |
|     echo $num > "$STATEDIR/highest_num"
 | |
|     printf "%x" $num
 | |
| }
 | |
| 
 | |
| write_statefile()
 | |
| {
 | |
|     [ -z "$STATEFILE" ] && return 1
 | |
|     echo > "$STATEFILE"
 | |
|     for var in "${STATEVARS[@]}"; do
 | |
|         echo "${var}='$(eval echo '$'$var)'" >> "$STATEFILE"
 | |
|     done
 | |
| }
 | |
| 
 | |
| read_statefile()
 | |
| {
 | |
|     local value
 | |
|     for var in "${STATEVARS[@]}"; do
 | |
|         value=$(source "$STATEFILE"; eval echo '$'$var)
 | |
|         eval "$var=\"$value\""
 | |
|     done
 | |
| }
 | |
| 
 | |
| cleanup_setup()
 | |
| {
 | |
|     echo "Error during setup, removing partially-configured environment '$NS'" >&2
 | |
|     set +o errexit
 | |
|     ip netns del "$NS" 2>/dev/null
 | |
|     ip link del dev "$NS" 2>/dev/null
 | |
|     rm -f "$STATEFILE"
 | |
| }
 | |
| 
 | |
| cleanup_teardown()
 | |
| {
 | |
|     echo "Warning: Errors during teardown, partial environment may be left" >&2
 | |
| }
 | |
| 
 | |
| 
 | |
| cleanup()
 | |
| {
 | |
|     [ -n "$CLEANUP_FUNC" ] && $CLEANUP_FUNC
 | |
| 
 | |
|     [ -d "$STATEDIR" ] || return 0
 | |
| 
 | |
|     local statefiles=("$STATEDIR"/*.state)
 | |
| 
 | |
|     if [ "${#statefiles[*]}" -eq 1 ] && [ ! -e "${statefiles[0]}" ]; then
 | |
|         rm -f "${STATEDIR}/highest_num" "${STATEDIR}/current"
 | |
|         rmdir "$STATEDIR"
 | |
|     fi
 | |
| }
 | |
| 
 | |
| iface_macaddr()
 | |
| {
 | |
|     local iface="$1"
 | |
|     local ns="${2:-}"
 | |
|     local output
 | |
| 
 | |
|     if [ -n "$ns" ]; then
 | |
|         output=$(ip -br -n "$ns" link show dev "$iface")
 | |
|     else
 | |
|         output=$(ip -br link show dev "$iface")
 | |
|     fi
 | |
|     echo "$output" | awk '{print $3}'
 | |
| }
 | |
| 
 | |
| set_sysctls()
 | |
| {
 | |
|     local iface="$1"
 | |
|     local in_ns="${2:-}"
 | |
|     local nscmd=
 | |
| 
 | |
|     [ -n "$in_ns" ] && nscmd="ip netns exec $in_ns"
 | |
|     local sysctls=(accept_dad
 | |
|                    accept_ra
 | |
|                    mldv1_unsolicited_report_interval
 | |
|                    mldv2_unsolicited_report_interval)
 | |
| 
 | |
|     for s in ${sysctls[*]}; do
 | |
|         $nscmd sysctl -w net.ipv6.conf.$iface.${s}=0 >/dev/null
 | |
|     done
 | |
| }
 | |
| 
 | |
| wait_for_dev()
 | |
| {
 | |
|     local iface="$1"
 | |
|     local in_ns="${2:-}"
 | |
|     local retries=5 # max retries
 | |
|     local nscmd=
 | |
| 
 | |
|     [ -n "$in_ns" ] && nscmd="ip netns exec $in_ns"
 | |
|     while [ "$retries" -gt "0" ]; do
 | |
|         if ! $nscmd ip addr show dev $iface | grep -q tentative; then return 0; fi
 | |
|         sleep 0.5
 | |
|         retries=$((retries -1))
 | |
|     done
 | |
| }
 | |
| 
 | |
| get_vlan_prefix()
 | |
| {
 | |
|     # Split the IPv6 prefix, and add the VLAN ID to the upper byte of the fourth
 | |
|     # element in the prefix. This will break if the global prefix config doesn't
 | |
|     # have exactly three elements in it.
 | |
|     local prefix="$1"
 | |
|     local vid="$2"
 | |
|     (IFS=:; set -- $prefix; printf "%s:%s:%s:%x::" "$1" "$2" "$3" $(($4 + $vid * 4096)))
 | |
| }
 | |
| 
 | |
| setup()
 | |
| {
 | |
|     get_nsname 1
 | |
| 
 | |
|     echo "Setting up new environment '$NS'"
 | |
| 
 | |
|     [ -e "$STATEFILE" ] && die "Environment for '$NS' already exists"
 | |
| 
 | |
|     local NUM=$(get_num "$NS")
 | |
|     local PEERNAME="testl-ve-$NUM"
 | |
|     [ -z "$IP6_PREFIX" ] && IP6_PREFIX="${IP6_SUBNET}:${NUM}::"
 | |
|     [ -z "$IP4_PREFIX" ] && IP4_PREFIX="${IP4_SUBNET}.$((0x$NUM))."
 | |
| 
 | |
|     INSIDE_IP6="${IP6_PREFIX}2"
 | |
|     INSIDE_IP4="${IP4_PREFIX}2"
 | |
|     OUTSIDE_IP6="${IP6_PREFIX}1"
 | |
|     OUTSIDE_IP4="${IP4_PREFIX}1"
 | |
| 
 | |
|     CLEANUP_FUNC=cleanup_setup
 | |
| 
 | |
|     if ! mount | grep -q /sys/fs/bpf; then
 | |
|         mount -t bpf bpf /sys/fs/bpf/
 | |
|     fi
 | |
| 
 | |
|     ip netns add "$NS"
 | |
|     ip link add dev "$NS" type veth peer name veth0 netns "$NS"
 | |
| 
 | |
|     set_sysctls $NS
 | |
|     ip link set dev "$NS" up
 | |
|     ip addr add dev "$NS" "${OUTSIDE_IP6}/${IP6_PREFIX_SIZE}"
 | |
|     ethtool -K "$NS" rxvlan off txvlan off
 | |
|     # Prevent neighbour queries on the link
 | |
|     INSIDE_MAC=$(iface_macaddr veth0 "$NS")
 | |
|     ip neigh add "$INSIDE_IP6" lladdr "$INSIDE_MAC" dev "$NS" nud permanent
 | |
| 
 | |
|     set_sysctls veth0 "$NS"
 | |
|     ip -n "$NS" link set dev lo up
 | |
|     ip -n "$NS" link set dev veth0 up
 | |
|     ip -n "$NS" addr add dev veth0 "${INSIDE_IP6}/${IP6_PREFIX_SIZE}"
 | |
|     ip netns exec "$NS" ethtool -K veth0 rxvlan off txvlan off
 | |
|     # Prevent neighbour queries on the link
 | |
|     OUTSIDE_MAC=$(iface_macaddr "$NS")
 | |
|     ip -n "$NS" neigh add "$OUTSIDE_IP6" lladdr "$OUTSIDE_MAC" dev veth0 nud permanent
 | |
|     # Add route for whole test subnet, to make it easier to communicate between
 | |
|     # namespaces
 | |
|     ip -n "$NS" route add "${IP6_SUBNET}::/$IP6_FULL_PREFIX_SIZE" via "$OUTSIDE_IP6" dev veth0
 | |
| 
 | |
|     if [ "$LEGACY_IP" -eq "1" ]; then
 | |
|         ip addr add dev "$NS" "${OUTSIDE_IP4}/${IP4_PREFIX_SIZE}"
 | |
|         ip -n "$NS" addr add dev veth0 "${INSIDE_IP4}/${IP4_PREFIX_SIZE}"
 | |
|         ip neigh add "$INSIDE_IP4" lladdr "$INSIDE_MAC" dev "$NS" nud permanent
 | |
|         ip -n "$NS" neigh add "$OUTSIDE_IP4" lladdr "$OUTSIDE_MAC" dev veth0 nud permanent
 | |
|         ip -n "$NS" route add "${IP4_SUBNET}/${IP4_FULL_PREFIX_SIZE}" via "$OUTSIDE_IP4" dev veth0
 | |
|         ENABLE_IPV4=1
 | |
|     else
 | |
|         ENABLE_IPV4=0
 | |
|     fi
 | |
| 
 | |
|     if [ "$USE_VLAN" -eq "1" ]; then
 | |
|         ENABLE_VLAN=1
 | |
|         for vid in "${VLAN_IDS[@]}"; do
 | |
|             local vlpx="$(get_vlan_prefix "$IP6_PREFIX" "$vid")"
 | |
|             local inside_ip="${vlpx}2"
 | |
|             local outside_ip="${vlpx}1"
 | |
|             ip link add dev "${NS}.$vid" link "$NS" type vlan id "$vid"
 | |
|             ip link set dev "${NS}.$vid" up
 | |
|             ip addr add dev "${NS}.$vid" "${outside_ip}/${IP6_PREFIX_SIZE}"
 | |
|             ip neigh add "$inside_ip" lladdr "$INSIDE_MAC" dev "${NS}.$vid" nud permanent
 | |
|             set_sysctls "${NS}/$vid"
 | |
| 
 | |
|             ip -n "$NS" link add dev "veth0.$vid" link "veth0" type vlan id "$vid"
 | |
|             ip -n "$NS" link set dev "veth0.$vid" up
 | |
|             ip -n "$NS" addr add dev "veth0.$vid" "${inside_ip}/${IP6_PREFIX_SIZE}"
 | |
|             ip -n "$NS" neigh add "$outside_ip" lladdr "$OUTSIDE_MAC" dev "veth0.$vid" nud permanent
 | |
|             set_sysctls "veth0/$vid" "$NS"
 | |
|         done
 | |
|     else
 | |
|         ENABLE_VLAN=0
 | |
|     fi
 | |
| 
 | |
|     write_statefile
 | |
| 
 | |
|     CLEANUP_FUNC=
 | |
| 
 | |
|     echo -n "Setup environment '$NS' with peer ip ${INSIDE_IP6}"
 | |
|     [ "$ENABLE_IPV4" -eq "1" ] && echo " and ${INSIDE_IP4}." || echo "."
 | |
|     echo "Waiting for interface configuration to settle..."
 | |
|     echo ""
 | |
|     wait_for_dev "$NS" && wait_for_dev veth0 "$NS"
 | |
| 
 | |
|     LEGACY_IP=0 USE_VLAN=0 run_ping -c 1
 | |
| 
 | |
|     echo "$NS" > "$STATEDIR/current"
 | |
| }
 | |
| 
 | |
| teardown()
 | |
| {
 | |
|     get_nsname && ensure_nsname "$NS"
 | |
| 
 | |
|     echo "Tearing down environment '$NS'"
 | |
| 
 | |
|     CLEANUP_FUNC=cleanup_teardown
 | |
| 
 | |
|     ip link del dev "$NS"
 | |
|     ip netns del "$NS"
 | |
|     rm -f "$STATEFILE"
 | |
|     [ -d "/sys/fs/bpf/$NS" ] && rmdir "/sys/fs/bpf/$NS" || true
 | |
| 
 | |
|     if [ -f "$STATEDIR/current" ]; then
 | |
|         local CUR=$(< "$STATEDIR/current" )
 | |
|         [[ "$CUR" == "$NS" ]] && rm -f "$STATEDIR/current"
 | |
|     fi
 | |
| 
 | |
|     CLEANUP_FUNC=
 | |
| }
 | |
| 
 | |
| reset()
 | |
| {
 | |
|     teardown && setup
 | |
| }
 | |
| 
 | |
| ns_exec()
 | |
| {
 | |
|     get_nsname && ensure_nsname "$NS"
 | |
| 
 | |
|     ip netns exec "$NS" env TESTENV_NAME="$NS" "$SETUP_SCRIPT" "$@"
 | |
| }
 | |
| 
 | |
| enter()
 | |
| {
 | |
|     ns_exec "${SHELL:-bash}"
 | |
| }
 | |
| 
 | |
| run_ping()
 | |
| {
 | |
|     local PING
 | |
|     local IP
 | |
| 
 | |
|     get_nsname && ensure_nsname "$NS"
 | |
| 
 | |
|     echo "Running ping from inside test environment:"
 | |
|     echo ""
 | |
| 
 | |
|     if [ "$LEGACY_IP" -eq "1" ]; then
 | |
|         PING=$(which ping)
 | |
|         IP="${OUTSIDE_IP4}"
 | |
|         [ "$USE_VLAN" -eq "0" ] || die "Can't use --legacy-ip and --vlan at the same time."
 | |
|         [ "$ENABLE_IPV4" -eq "1" ] || die "No legacy IP addresses configured in environment."
 | |
|     else
 | |
|         PING=$(which ping6 2>/dev/null || which ping)
 | |
|         if [ "$USE_VLAN" -eq "0" ]; then
 | |
|             IP="${OUTSIDE_IP6}"
 | |
|         else
 | |
|             [ "$ENABLE_VLAN" -eq "1" ] || die "No VLANs configured in environment."
 | |
|             IP="$(get_vlan_prefix "$IP6_PREFIX" "${VLAN_IDS[0]}")1"
 | |
|         fi
 | |
|     fi
 | |
| 
 | |
|     ns_exec "$PING" "$IP" "$@"
 | |
| }
 | |
| 
 | |
| run_tcpdump()
 | |
| {
 | |
|     get_nsname && ensure_nsname "$NS"
 | |
| 
 | |
|     if [ "$RUN_ON_INNER" -eq "1" ]; then
 | |
|         ns_exec tcpdump -nei veth0 "$@"
 | |
|     else
 | |
|         tcpdump -nei "$NS" "$@"
 | |
|     fi
 | |
| }
 | |
| 
 | |
| status()
 | |
| {
 | |
|     get_nsname
 | |
| 
 | |
|     echo "Currently selected environment: ${NS:-None}"
 | |
|     if [ -n "$NS" ] && [ -e "$STATEFILE" ]; then
 | |
|         read_statefile
 | |
|         echo -n "  Namespace:      "; ip netns | grep "^$NS"
 | |
|         echo    "  Prefix:         ${IP6_PREFIX}/${IP6_PREFIX_SIZE}"
 | |
|         [ "$ENABLE_IPV4" -eq "1" ] && echo    "  Legacy prefix:  ${IP4_PREFIX}0/${IP4_PREFIX_SIZE}"
 | |
|         echo -n "  Iface:          "; ip -br a show dev "$NS" | sed 's/\s\+/ /g'
 | |
|     fi
 | |
|     echo ""
 | |
| 
 | |
|     echo "All existing environments:"
 | |
|     for f in "$STATEDIR"/*.state; do
 | |
|         if [ ! -e "$f" ]; then
 | |
|             echo "  No environments exist"
 | |
|             break
 | |
|         fi
 | |
|         NAME=$(basename "$f" .state)
 | |
|         echo "  $NAME"
 | |
|     done
 | |
| }
 | |
| 
 | |
| print_alias()
 | |
| {
 | |
|     local scriptname="$(readlink -e "$0")"
 | |
|     local sudo=
 | |
| 
 | |
|     [ -t 1 ] && echo "Eval this with \`eval \$($0 alias)\` to create shell alias" >&2
 | |
| 
 | |
|     if [ "$EUID" -ne "0" ]; then
 | |
|         sudo="sudo "
 | |
|         echo "WARNING: Creating sudo alias; be careful, this script WILL execute arbitrary programs" >&2
 | |
|     fi
 | |
| 
 | |
|     echo "" >&2
 | |
| 
 | |
| 
 | |
|     echo "alias t='$sudo$scriptname'"
 | |
| }
 | |
| 
 | |
| usage()
 | |
| {
 | |
|     local FULL=${1:-}
 | |
| 
 | |
|     echo "Usage: $0 [options] <command> [param]"
 | |
|     echo ""
 | |
|     echo "Commands:"
 | |
|     echo "setup                   Setup and initialise new environment"
 | |
|     echo "teardown                Tear down existing environment"
 | |
|     echo "reset                   Reset environment to original state"
 | |
|     echo "exec <command>          Exec <command> inside test environment"
 | |
|     echo "enter                   Execute shell inside test environment"
 | |
|     echo "ping                    Run ping inside test environment"
 | |
|     echo "status (or st)          Show status of test environment"
 | |
|     echo "load                    Load XDP program on outer interface"
 | |
|     echo "unload                  Unload XDP program on outer interface"
 | |
|     echo "tcpdump                 Run on outer interface (or inner with --inner)"
 | |
|     echo ""
 | |
| 
 | |
|     if [ -z "$FULL" ] ; then
 | |
|         echo "Use --help to see the list of options."
 | |
|         exit 1
 | |
|     fi
 | |
| 
 | |
|     echo "Options:"
 | |
|     echo "-h, --help          Show this usage text"
 | |
|     echo ""
 | |
|     echo "-n, --name <name>   Set name of test environment. If not set, the last used"
 | |
|     echo "                    name will be used, or a new one generated."
 | |
|     echo ""
 | |
|     echo "-g, --gen-new       Generate a new test environment name even though an existing"
 | |
|     echo "                    environment is selected as the current one."
 | |
|     echo ""
 | |
|     echo "    --legacy-ip     Enable legacy IP (IPv4) support."
 | |
|     echo "                    For setup and reset commands this enables configuration of legacy"
 | |
|     echo "                    IP addresses on the interface, for the ping command it switches to"
 | |
|     echo "                    legacy ping."
 | |
|     echo ""
 | |
|     echo "    --vlan          Enable VLAN support."
 | |
|     echo "                    When used with the setup and reset commands, these VLAN IDs will"
 | |
|     echo "                    be configured: ${VLAN_IDS[*]}. The VLAN interfaces are named as"
 | |
|     echo "                    <ifname>.<vlid>."
 | |
|     echo "                    When used with the ping command, the pings will be sent on the"
 | |
|     echo "                    first VLAN ID (${VLAN_IDS[0]})."
 | |
|     echo ""
 | |
|     echo "    --inner         Use with tcpdump command to run on inner interface."
 | |
|     echo ""
 | |
|     exit 1
 | |
| }
 | |
| 
 | |
| 
 | |
| OPTS="hn:gl:s:"
 | |
| LONGOPTS="help,name:,gen-new,legacy-ip,vlan,inner"
 | |
| 
 | |
| OPTIONS=$(getopt -o "$OPTS" --long "$LONGOPTS" -- "$@")
 | |
| [ "$?" -ne "0" ] && usage >&2 || true
 | |
| 
 | |
| eval set -- "$OPTIONS"
 | |
| 
 | |
| 
 | |
| while true; do
 | |
|     arg="$1"
 | |
|     shift
 | |
| 
 | |
|     case "$arg" in
 | |
|         -h | --help)
 | |
|             usage full >&2
 | |
|             ;;
 | |
|         -n | --name)
 | |
|             NS="$1"
 | |
|             shift
 | |
|             ;;
 | |
|         -g | --gen-new)
 | |
|             GENERATE_NEW=1
 | |
|             ;;
 | |
|         --legacy-ip)
 | |
|             LEGACY_IP=1
 | |
|             ;;
 | |
|         --vlan)
 | |
|             USE_VLAN=1
 | |
|             ;;
 | |
|         --inner)
 | |
|             RUN_ON_INNER=1
 | |
|             ;;
 | |
|         -- )
 | |
|             break
 | |
|             ;;
 | |
|     esac
 | |
| done
 | |
| 
 | |
| [ "$#" -eq 0 ] && usage >&2
 | |
| 
 | |
| case "$1" in
 | |
|     st|sta|status)
 | |
|         CMD=status
 | |
|         ;;
 | |
|     setup|teardown|reset|enter)
 | |
|         CMD="$1"
 | |
|         ;;
 | |
|     "exec")
 | |
|         CMD=ns_exec
 | |
|         ;;
 | |
|     ping|tcpdump)
 | |
|         CMD="run_$1"
 | |
|         ;;
 | |
|     "alias")
 | |
|         print_alias
 | |
|         exit 0
 | |
|         ;;
 | |
|     "help")
 | |
|         usage full >&2
 | |
|         ;;
 | |
|     *)
 | |
|         usage >&2
 | |
|         ;;
 | |
| esac
 | |
| 
 | |
| shift
 | |
| trap cleanup EXIT
 | |
| check_prereq
 | |
| $CMD "$@"
 |