From df3577154f55a542613779596d26369e7a0d74b8 Mon Sep 17 00:00:00 2001
From: Mingming Cao <mmc@linux.vnet.ibm.com>
Date: Wed, 16 Feb 2022 00:16:52 -0800
Subject: [PATCH 3/6] Add new wicked functions from suse to manage bonding

suse specific hcnmgr utilities to manipulate suse ifcfg files
as supported by yast2 network.

Signed-off-by: Marius Tomaschewski <mt@suse.com>
---
 scripts/functions.suse | 386 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 386 insertions(+)
 create mode 100644 scripts/functions.suse

diff --git a/scripts/functions.suse b/scripts/functions.suse
new file mode 100644
index 0000000..06696dd
--- /dev/null
+++ b/scripts/functions.suse
@@ -0,0 +1,386 @@
+#! /bin/bash
+#  ^^^^^^^^^
+#
+# Copyright (c) 2022 SUSE LLC
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+# Authors:
+#	Marius Tomaschewski <mt@suse.com>
+#
+# Bash function library for hcnmgr providing utilities to manipulate
+# suse ifcfg config files as supported by yast2 network and wicked.
+#
+###
+
+suse_ifcfg_config_dir="/etc/sysconfig/network"
+
+suse_wlist_find()
+{
+	local item want="$1" ; shift
+	for item in "$@" ; do
+		[ X"$want" = X"$item" ] && return 0
+	done
+	return 1
+}
+suse_wlist_uniq()
+{
+	local item list=()
+	for item in "$@" ; do
+		suse_wlist_find "$item" "${list[@]}" && continue
+		list+=("$item")
+	done
+	echo "${list[@]}"
+}
+suse_ifcfg_unquote()
+{
+	# Ifcfg variables contain sh compatible/sourceable key=values,
+	# but without the full quote and (variable) expansion magic
+	# and the values aren't/don't need to be evaluated by a shell.
+	# Values can contain a space-separated world list, like e.g.:
+	#   "arp_interval arp_ip_target=1.2.3.4,1.2.3.5"
+	# They need quotes, but no nested "foo \"bar baz\"" or escapes
+	# are considered. A value like ' \o/ ' (e.g. a wireless essid)
+	# needs single quotes...
+	local val="$1"
+	case $val in
+	"'"*)  val="${val#"'"}" ; val="${val%"'"*}" ; echo "$val" ;;
+	"\""*) val="${val#\"}"  ; val="${val%\"*}"  ; echo "$val" ;;
+	[[:alpha:]]*) echo "${val%%[[:space:]#]*}" ;;
+	esac
+}
+
+suse_ifcfg_file_name()
+{
+	[ X"$1" != X ] && echo "${suse_ifcfg_config_dir}/ifcfg-${1}"
+}
+suse_ifcfg_file_exists()
+{
+	local file=$(suse_ifcfg_file_name "$1")
+	[ X"$file" != X -a -f "$file" ] && echo "$file"
+}
+suse_ifcfg_file_delete()
+{
+	local file=$(suse_ifcfg_file_name "$1")
+	[ X"$file" != X ] && rm -f -- "$file"
+}
+suse_ifcfg_file_mktemp()
+{
+	local temp="${suse_ifcfg_config_dir}/.ifcfg-${1}.XXXXXX"
+	[ X"$1" != X -a X"$temp" != X ] && mktemp -q -- "$temp" 2>/dev/null
+}
+suse_ifcfg_grep()
+{
+	LANG=C LC_ALL=C grep --no-filename -E "$@"
+}
+
+suse_ifcfg_is_bond_master()
+{
+	local bond="$1"
+	local file=$(suse_ifcfg_file_name "$bond")
+	local line key val=""
+	local BONDING_MASTER="no"
+
+	[ X"$file" != X ] || return 1
+	while read line ; do
+		key="${line%%=*}"
+		val=$(suse_ifcfg_unquote "${line#${key}=}")
+		case $val in
+			yes|YES) BONDING_MASTER="yes" ;;
+			*)       BONDING_MASTER="no"  ;;
+		esac
+	done < <(suse_ifcfg_grep -e '^[ \t]*BONDING_MASTER=' -- \
+			"$file" 2>/dev/null)
+
+	[ X"$BONDING_MASTER" = X"yes" ]
+}
+
+suse_ifcfg_bond_master_template()
+{
+	local slaves=($(suse_wlist_uniq $1))
+	local options=($2)
+	local num=0
+
+	if [ X"${options[*]}" = X ] ; then
+		# default options as used by the hcnmgr
+		options=(mode=active-backup miimon=100 fail_over_mac=follow)
+	fi
+
+	echo "STARTMODE='auto'"
+	echo "BOOTPROTO='none'"
+
+	echo "BONDING_MASTER='yes'"
+	echo "BONDING_MODULE_OPTS='${options[*]}'"
+	for slave in "${slaves[@]}" ; do
+		((num++))
+		echo "BONDING_SLAVE_${num}='$slave'"
+	done
+}
+
+suse_ifcfg_bond_get_options()
+{
+	local bond="$1"
+	local file=$(suse_ifcfg_file_exists "$bond")
+	local line key val=""
+
+	[ X"$file" != X ] || return 1
+	while read line ; do
+		key="${line%%=*}"
+		if [ X"$key" != X ] ; then
+			val=$(suse_ifcfg_unquote "${line#${key}=}")
+		fi
+	done < <(suse_ifcfg_grep -e '^[ \t]*BONDING_MODULE_OPTS=' -- \
+			"$file" 2>/dev/null)
+	echo "$val"
+}
+
+suse_ifcfg_bond_set_options()
+{
+	local bond="$1"
+	local options="$2"
+	local file=$(suse_ifcfg_file_exists "$bond")
+	[ X"$file" != X -a X"$options" != X ] || return 1
+
+	local temp=$(suse_ifcfg_file_mktemp "$bond")
+	[ X"$temp" != X ] || return 1
+
+	if suse_ifcfg_grep -v -e '^[ \t]*BONDING_MODULE_OPTS=' -- \
+		"$file" >> "$temp" 2>/dev/null ; then
+
+		echo "BONDING_MODULE_OPTS='$options'" >>"$temp"
+
+		mv -f -- "$temp" "$file" && return 0
+	fi
+	rm -f -- "$temp"
+	return 1
+}
+
+suse_ifcfg_bond_get_primary()
+{
+	local bond="$1"
+	local file=$(suse_ifcfg_file_exists "$bond")
+	[ X"$file" != X ] || return 1
+
+	local options=($(suse_ifcfg_bond_get_options "$bond"))
+	local opt primary=""
+	for opt in "${options[@]}" ; do
+		case $opt in
+			primary=*)
+				primary="${opt#*=}"
+			;;
+		esac
+	done
+	[ X"$primary" != X ] && echo "$primary"
+}
+
+suse_ifcfg_bond_set_primary()
+{
+	local bond="$1"
+	local primary="$2"
+
+	local file=$(suse_ifcfg_file_name "$bond")
+	[ X"$file" != X ] || return 1
+
+	local options=($(suse_ifcfg_bond_get_options "$bond"))
+	local opt opts=()
+	for opt in "${options[@]}" ; do
+		case $opt in
+			primary=*) ;;
+			*) opts+=("$opt") ;;
+		esac
+	done
+	if [ X"$primary" != X ] ; then
+		opts+=("primary=$primary")
+	fi
+	suse_ifcfg_bond_set_options "$bond" "${opts[*]}"
+}
+
+suse_ifcfg_bond_get_slaves()
+{
+	local bond="$1"
+	local file=$(suse_ifcfg_file_name "$bond")
+	local line var key val="" ${!BONDING_SLAVE*}
+
+	[ X"$file" != X ] || return 1
+	unset ${!BONDING_SLAVE*}
+	while read -r line ; do
+		key="${line%%=*}"
+		val=$(suse_ifcfg_unquote "${line#${key}=}")
+		eval local "${key}='${val}'"
+	done < <(suse_ifcfg_grep -e '^[ \t]*BONDING_SLAVE[[:alnum:]_]*=' -- \
+			"$file" 2>/dev/null)
+	for var in ${!BONDING_SLAVE*} ; do
+		[ X"${!var}" != X ] && echo "${!var}"
+	done
+}
+
+suse_ifcfg_bond_add_slave()
+{
+	local bond="$1"
+	local nslave="$2"
+	local file=$(suse_ifcfg_file_name "$bond")
+	[ X"$nslave" != X -a X"$file" != X ] || return 1
+
+	local oslaves=($(suse_ifcfg_bond_get_slaves "$bond"))
+	suse_wlist_find "$nslave" "${oslaves[@]}"  && return 0
+	local nslaves=("${oslaves[@]}" "$nslave")
+	local slave num=0
+
+	local temp=$(suse_ifcfg_file_mktemp "$bond")
+	[ X"$temp" != X ] || return 1
+
+	if suse_ifcfg_grep -v -e '^[ \t]*BONDING_SLAVE[[:alnum:]_]*=' -- \
+		"$file" >> "$temp" 2>/dev/null ; then
+
+		for slave in "${nslaves[@]}" ; do
+			((num++))
+			echo "BONDING_SLAVE_${num}='$slave'"
+		done >> "$temp" 2>/dev/null
+
+		mv -f -- "$temp" "$file" && return 0
+	fi
+	rm -f -- "$temp"
+	return 1
+}
+
+suse_ifcfg_bond_del_slave()
+{
+	local bond="$1"
+	local oslave="$2"
+	local file=$(suse_ifcfg_file_exists "$bond")
+	[ X"$oslave" != X -a X"$file" != X ] || return 1
+
+	local oslaves=($(suse_ifcfg_bond_get_slaves "$bond"))
+	local slave num=0 nslaves=()
+	for slave in "${oslaves[@]}" ; do
+		if [ X"$slave" != X"$oslave" ] ; then
+			nslaves+=("${slave}")
+		else
+			suse_ifcfg_file_delete "$slave"
+		fi
+	done
+
+	local primary=$(suse_ifcfg_bond_get_primary "$bond")
+	if [ X"$primary" = X"$oslave" ] ; then
+		suse_ifcfg_bond_set_primary "$bond" ""
+	fi
+
+	local temp=$(suse_ifcfg_file_mktemp "$bond")
+	[ X"$temp" != X ] || return 1
+
+	if suse_ifcfg_grep -v -e '^[ \t]*BONDING_SLAVE[[:alnum:]_]*=' -- \
+		"$file" >> "$temp" 2>/dev/null ; then
+
+		for slave in "${nslaves[@]}" ; do
+			((num++))
+			echo "BONDING_SLAVE_${num}='$slave'"
+		done >> "$temp" 2>/dev/null
+
+		mv -f -- "$temp" "$file" && return 0
+	fi
+	rm -f -- "$temp"
+	return 1
+}
+
+suse_ifcfg_bond_create()
+{
+	local bond="$1" ; shift
+	local file=$(suse_ifcfg_file_name "$bond")
+	local temp=$(suse_ifcfg_file_mktemp "$bond")
+
+	[ X"$temp" != X ] || return 1
+	if suse_ifcfg_bond_master_template "$@" >> "$temp" ; then
+		mv -f -- "$temp" "$file" && return 0
+	fi
+	rm -f -- "$temp"
+	return 1
+}
+
+suse_ifcfg_bond_delete()
+{
+	local bond="$1"
+	local file=$(suse_ifcfg_file_name "$bond")
+	local slaves=($(suse_ifcfg_bond_get_slaves "$bond"))
+	local slave
+
+	# delete explicit slave configs if any
+	for slave in "${slaves[@]}" ; do
+		suse_ifcfg_file_delete "$slave"
+	done
+
+	# delete the bond master config file
+	[ X"$file" != X ] && rm -f -- "$file"
+
+	# TODO: cleanup interfaces on top of the bond
+	#       _if_ the hnv setup permit to use them
+	# e.g.: brX { bondX }, brN { bondX.N }
+}
+
+suse_ifcfg_bond_modify()
+{
+	local bond="$1"
+	local nslaves=($(suse_wlist_uniq $2))
+	local noptions=($3)
+
+	if ! suse_ifcfg_is_bond_master "$bond" ; then
+		suse_ifcfg_bond_create "$bond" "${nslaves[*]}" "${noptions[*]}"
+		return $?
+	fi
+
+	local temp=$(suse_ifcfg_file_mktemp "$bond")
+	[ X"$temp" != X ] || return 1
+
+	local options=($(suse_ifcfg_bond_get_options "$bond"))
+	local slaves=($(suse_ifcfg_bond_get_slaves "$bond"))
+	local file=$(suse_ifcfg_file_name "$bond")
+	local slave num=0
+	for slave in "${slaves[@]}" ; do
+		suse_wlist_find "$slave" "${nslaves[*]}" || \
+			suse_ifcfg_file_delete "$slave"
+	done
+
+	if [ X"${noptions[*]}" = X ] ; then
+		local opt primary="" noptions=""
+		for opt in "${options[@]}" ; do
+			case $opt in
+				primary=*) primary="${opt#*=}" ;;
+				*)         noptions+=("$opt")  ;;
+			esac
+		done
+		if [ X"${noptions[*]}" = X ] ; then
+			noptions=(mode=active-backup miimon=100 fail_over_mac=follow)
+		fi
+		if [ X"$primary" != X ] ; then
+			if suse_wlist_find "$primary" "${nslaves[*]}" ; then
+				noptions+=("primary=$primary")
+			fi
+		fi
+	fi
+
+	if suse_ifcfg_grep -v -e '^[ \t]*BONDING_[[:alnum:]_]+=' -- \
+		"$file" >> "$temp" 2>/dev/null ; then
+
+		echo "BONDING_MASTER='yes'"                 >> "$temp"
+		echo "BONDING_MODULE_OPTS='${noptions[*]}'" >> "$temp"
+		for slave in "${nslaves[@]}" ; do
+			((num++))
+			echo "BONDING_SLAVE_${num}='$slave'"
+		done >> "$temp" 2>/dev/null
+
+		mv -f -- "$temp" "$file" && return 0
+	fi
+	rm -f -- "$temp"
+	return 1
+}
+
-- 
2.34.1

