#!/usr/bin/perl
## BEGIN_ICS_COPYRIGHT8 ****************************************
#
# Copyright (c) 2015-2020, Intel Corporation
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#     * Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of Intel Corporation nor the names of its contributors
#       may be used to endorse or promote products derived from this software
#       without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# END_ICS_COPYRIGHT8   ****************************************

# [ICS VERSION STRING: unknown]

#

use strict;
#use Term::ANSIColor;
#use Term::ANSIColor qw(:constants);
#use File::Basename;
#use Math::BigInt;

# ==========================================================================
# IP address configuration
# used for IPoIB at present but generalized so could support VNIC or
# iPathEther also

my $FirstIPoIBInterface=0; # first device is ib0
my $MAX_HFI_PORTS=20;	# maximum valid ports

# Validate the passed in IP address (or netmask).
# Verify there are enough dots '.' and same number of
# decimal numbers. This is not comprehensive.
#
# inputs:
#	IP address in dot notation.  eg:
#	'a.b.c.d' or 'a.b.c' or 'a.b'
#
# outputs:
#	0 == failure(not a valid IP address)
#	1 == Success.

sub Validate_IP_Addr($)
{
	$_ = shift();		# setup for translate & other cmds

	my($count)= 0;

	# verify there are 3 and only 3 dots, 'a.b.c.d'.
	# translate '.' to self, side-effect is the count.

	$count=(tr/\.//);
	if ( $count < 1 || $count > 3 )
	{
		return 0;
	}

	# accept only decimal digits separated by dots.
	if ( /[0-9\.]/ ) 
	{
		# because we checked dots above, any of the three below is ok
		if ( /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/ ) 
		{
			return 1;	# Success
		}
		if ( /([0-9]+)\.([0-9]+)\.([0-9]+)/ ) 
		{
			return 1;	# Success
		}
		if ( /([0-9]+)\.([0-9]+)/ ) 
		{
			return 1;	# Success
		}
	}
	return 0;	# failure.
}


# Retrieve an IPV4 address in standard 'dot' notation from stdin.
# Perform simple IP address format validation.
#
# returns:
#	IP address string.
#

sub Get_IP_Addr($$)
{
	my($interface) = shift();
	my($default_ip) = shift();

	my($ip,$idx);

	# acquire a valid IP address.
	# sadly last & redo caused runtime errors?

	do 
	{
		if ("$default_ip" ne "")
		{
			print "Enter IPV4 address in dot notation (or dhcp) for $interface [$default_ip]: ";
		} else {
			print "Enter IPV4 address in dot notation (or dhcp) for $interface: ";
		}
		chomp($ip = <STDIN>);
		$ip=remove_whitespace($ip);

		if ( length($ip) == 0 && "$default_ip" ne "" ) 
		{
			$ip = $default_ip;
			$_ = "y";
		}
		else 
		{
			# verify there are enough chars for a valid IP address
			if ( "$ip" ne "dhcp" && Validate_IP_Addr($ip) == 0 ) 
			{
				print("Bad IPV4 address '$ip'\n");
				$_ = "n";
			}
			else 
			{
				do
				{
					# require a response
					print("Is IPV4 address '$ip' correct? (y/n): ");
					chomp($_ = <STDIN>);
					$_=remove_whitespace($_);
				} until (/[YyNn]/);
			}
		}
	} until (/[Yy]/);
	LogPrint "Enter IPV4 address in dot notation for $interface: -> $ip\n";

	return $ip;
}

# Retrieve an IPV4 netmask in standard 'dot' notation from stdin.
# Perform simple IP address format validation.
#
# returns:
#	IP address string.
#

sub Get_IP_Addr_Netmask($$)
{
	my($interface) = shift();
	my($ipaddr) = shift();

	my($idx,$default_netmask,$dot_count);
	my $netmask;

	if ( "$ipaddr" eq "dhcp" ) {
		return "";
	}
	$default_netmask = `/usr/lib/opa/tools/opaipcalc --netmask $ipaddr`;
	$default_netmask =~ s/NETMASK=//;
	$default_netmask =~ s/\n//;
	$_ = $ipaddr;		# setup for translate & other cmds
	$dot_count=(tr/\.//);
	do 
	{
		print "Enter IPV4 netmask in dot notation for $interface $ipaddr [$default_netmask]: ";
		chomp($netmask = <STDIN>);
		$netmask=remove_whitespace($netmask);
		$_=$netmask;

		if ( length($netmask) == 0 ) 
		{
			$netmask = $default_netmask;
			print("Is IPV4 netmask '$netmask' correct? (y/n): ");
			chomp($_ = <STDIN>);
			$_=remove_whitespace($_);
		}
		else 
		{
			# verify there are enough chars for a valid IP address
			if ( Validate_IP_Addr($netmask) == 0 ) 
			{
				print("Bad IPV4 netmask: '$netmask'\n");
				$_ = "n";
			}
			elsif ($dot_count != (tr/\.//))
			{
				print("Inappropriate IPV4 netmask for $ipaddr: '$netmask'\n");
				$_ = "n";
			}
			else 
			{
				print("Is IPV4 netmask '$netmask' correct? (y/n): ");
				chomp($_ = <STDIN>);
				$_=remove_whitespace($_);
			}
		}
	} until (/[Yy]/);
	LogPrint "Enter IPV4 netmask in dot notation for $interface $ipaddr [$default_netmask]: -> $netmask\n";
	return $netmask;
}

# return the next sequential IPV4 address. Last numeric field is incremented.
#
# Assumes a valid dot notation IP address (e.g., 7.7.7.240)
# Increment the 'host' number ('240' in the example) by 1.
#  7.7.7.240 --> 7.7.7.241
#
# input:
#	base IP address in 'dot' notation.
# output:
#	next IP address

sub NextIPaddr($)
{
	my($base) = shift();

	my($next,$hostnum,$idx,$len);

	if ( "$base" eq "dhcp" ) {
		return "$base";
	}
	# locate right-most '.'
	$idx = rindex($base, ".");
	Abort "NextIPaddr: Bad IPV4 address ".$base
		if ($idx == -1);

	$idx += 1;	# move past the dot to the last numeric field portion of
				# the IP address.

	# extract the host number
	$len = length($base);
	$hostnum = substr($base, ($idx), ($len - $idx));

	$hostnum += 1;	# next host number

	# replace the last numeric field with the incremented host number
	$next = $base;
	substr($next,$idx,length($hostnum)) = $hostnum;

	DebugPrint("Next IP '$next'\n");

	return $next
}

# return the next sequential interface name. Last numeric field is incremented.
# if the final field is not numeric, returns ""
sub NextInterface($)
{
	my($base) = shift();

	my($next,$hostnum,$idx,$len);
	my $number;
	my $prefix;

	$_ = $base;
	if ( ! /.*([0-9]+)$/ ) 
	{
		# non numeric name, can't auto-increment
		return "";
	}
	$prefix = $base;
	$prefix =~ s/(.*)([0-9]+)$/$1/;
	$number = $base;
	$number =~ s/(.*)([0-9]+)$/$2/;

	$number += 1;	# next intf number

	return "$prefix$number";
}

# build the ifcfg file for a network device
sub Build_ifcfg($$$)
{
	my($device) = shift();		# device name
	my($ipaddr) = shift();		# ip address for device or "dhcp"
	my($netmask) = shift();		# ip address netmask for device

	my($target, $SysCmd);
	my ($temp);

	$target = "$NETWORK_CONF_DIR/ifcfg-$device";
	print("Creating ifcfg-$device for $ipaddr mask $netmask\n\n");

	if ( "$CUR_DISTRO_VENDOR" eq "UnitedLinux" || "$CUR_DISTRO_VENDOR" eq "SuSE") {
		if ( "$ipaddr" eq "dhcp" ) {
			# append boot protocol type
			$SysCmd = "echo \"BOOTPROTO=\'dhcp\'\" >> $target";
			DebugPrint("cmd '$SysCmd'\n");
			system $SysCmd;
		} else {
			# append boot protocol type
			$SysCmd = "echo \"BOOTPROTO=\'static\'\" >> $target";
			DebugPrint("cmd '$SysCmd'\n");
			system $SysCmd;

			# append the device instance internet protocol address
			$SysCmd = "echo \"IPADDR=\'$ipaddr\'\" >> $target";
			DebugPrint("cmd '$SysCmd'\n");
			system $SysCmd;

			# append the netmask
			$SysCmd = "echo \"NETMASK=\'$netmask\'\" >> $target";
			DebugPrint("cmd '$SysCmd'\n");
			system $SysCmd;

			# append the network
			$temp = `/usr/lib/opa/tools/opaipcalc --network $ipaddr $netmask`;
			chomp($temp);
			$temp =~ s/NETWORK=//;
			$SysCmd = "echo \"NETWORK=\'$temp\'\" >> $target";
			DebugPrint("cmd '$SysCmd'\n");
			system $SysCmd;

			# append the broadcast
			$temp = `/usr/lib/opa/tools/opaipcalc --broadcast $ipaddr $netmask`;
			chomp($temp);
			$temp =~ s/BROADCAST=//;
			$SysCmd = "echo \"BROADCAST=\'$temp\'\" >> $target";
			DebugPrint("cmd '$SysCmd'\n");
			system $SysCmd;

			$SysCmd = "echo \"REMOTE_IPADDR=\'\'\" >> $target";
			DebugPrint("cmd '$SysCmd'\n");
			system $SysCmd;
		}

		$SysCmd = "echo \"STARTMODE=\'hotplug\'\" >> $target";
		DebugPrint("cmd '$SysCmd'\n");
		system $SysCmd;

		$SysCmd = "echo \"WIRELESS='no'\" >> $target";
		DebugPrint("cmd '$SysCmd'\n");
		system $SysCmd;

		# SLES11 and newer have IPOIB_MODE option in ifcfg
		if ( "$CUR_VENDOR_VER" eq "ES123" ) {
			$SysCmd = "echo \"IPOIB_MODE='connected'\" >> $target";
			DebugPrint("cmd '$SysCmd'\n");
			system $SysCmd;
			$SysCmd = "echo \"MTU=65520\" >> $target";
			DebugPrint("cmd '$SysCmd'\n");
			system $SysCmd;
		} else {
			$SysCmd = "echo \"IPOIB_MODE='datagram'\" >> $target";
			DebugPrint("cmd '$SysCmd'\n");
			system $SysCmd;
			NormalPrint "Note: IPoIB Mode is Datagram\n";
		}
	} else {
		# Append the device instance name
		$SysCmd = "echo DEVICE=$device > $target";
		DebugPrint("cmd '$SysCmd'\n");
		system $SysCmd;

		$SysCmd = "echo \"TYPE=\'InfiniBand\'\" >> $target";
                DebugPrint("cmd '$SysCmd'\n");
                system $SysCmd;

		if ( "$ipaddr" eq "dhcp" ) {
			$SysCmd = "echo BOOTPROTO=dhcp >> $target";
			DebugPrint("cmd '$SysCmd'\n");
			system $SysCmd;

			$SysCmd = "echo DHCPCLASS= >> $target";
			DebugPrint("cmd '$SysCmd'\n");
			system $SysCmd;
		} else {

			$SysCmd = "echo BOOTPROTO=static >> $target";
			DebugPrint("cmd '$SysCmd'\n");
			system $SysCmd;

			# Append the device instance Internet Protocol address
			$SysCmd = "echo IPADDR=$ipaddr >> $target";
			DebugPrint("cmd '$SysCmd'\n");
			system $SysCmd;

			# append the network,broadcast
			$SysCmd = "/usr/lib/opa/tools/opaipcalc --network --broadcast $ipaddr $netmask >> $target";
			DebugPrint("cmd '$SysCmd'\n");
			system $SysCmd;

			# append the netmask
			$SysCmd = "echo NETMASK=$netmask >> $target";
			DebugPrint("cmd '$SysCmd'\n");
			system $SysCmd;
		}

		$SysCmd = "echo ONBOOT=yes >> $target";
		DebugPrint("cmd '$SysCmd'\n");
		system $SysCmd;

		$SysCmd = "echo CONNECTED_MODE=no >> $target";
		DebugPrint("cmd '$SysCmd'\n");
		system $SysCmd;
#		$SysCmd = "echo MTU=65520 >> $target";
#		DebugPrint("cmd '$SysCmd'\n");
#		system $SysCmd;
		NormalPrint "Note: IPoIB Mode is Datagram\n";
	}

	system "chown $OWNER $target";
	system "chgrp $GROUP $target";
	system "chmod ugo=r,u=rw $target";
}

sub Config_IP_Manually($$)
{
	my($compname) = shift();
	my($sampledev) = shift();

	print "\n$compname requires an ifcfg file for each $compname device instance.\n";
	print ("Manually create files such as '$NETWORK_CONF_DIR/ifcfg-$sampledev'\n");
}


sub Exist_ifcfg($)
{
	my($prefix) = shift();

	my $ifcfg_wildcard="$NETWORK_CONF_DIR/ifcfg-$prefix"."[0-9]*";
	return ( `ls $ifcfg_wildcard 2>/dev/null` ne "" );
}

#
# Generate the network interface config files for IP over IB
#	/etc/sysconfig/network-scripts/ifcfg-ibl1
#
# Assign IP (Internet Protocol) addresses in standard 'dot' notation to
# IP over IB device instances. Create IP over IB 'ifup ibX' configuration files
# located in '/etc/sysconfig/network-scripts' for each IP over IB port.
#
# **** Currently - ONLY static IP address assignment is supported.
#
# inputs:
#	[0] == are we reconfiguring, if 1, allows editing of existing ifcfg files
#
# outputs:
#	none.
#
sub Config_ifcfg($$$$$)
{
	my($reconfig) = shift();
	my($compname) = shift();
	my($prefix) = shift();
	my($firstdev) = shift();
	my($showoptions) = shift();

	my($max_ports,$port,$a,$inp,$seq,$intf_seq,$default_intf);
	my($netmask);
	my($ipaddr) = "";
	my($interface) = "";
	my(%interfaces) = ();
	my $config_dir;

	$max_ports = 0;
			
	# Check if ifcfg files are present
	my $ifcfg_wildcard="$NETWORK_CONF_DIR/ifcfg-$prefix"."[0-9]*";
	if ( `ls $ifcfg_wildcard 2>/dev/null` ne "" )
	{
		if ($reconfig)
		{
			# always prompt regardless of previous check_keep_config answer
			clear_keep_config("$ifcfg_wildcard");
		}
		if (! $reconfig 
			|| check_keep_config("$ifcfg_wildcard", "$compname ifcfg files", "y"))
		{
			print "Leaving $ifcfg_wildcard unchanged...\n";
			return;
		} 
		print "removing $ifcfg_wildcard\n";
		system "rm -f $ifcfg_wildcard";
	}
	if (GetYesNo("Configure $compname IPV4 addresses now?", "n") == 0)
	{
		# If user answered 'no', then reluctantly bail.
		Config_IP_Manually("$compname","$prefix$firstdev");
		return;
	}

	if ( $showoptions != 0 )
	{
		printf ("\nYou may configure an $compname interface for each HFI port\n");
		printf ("Or you may select to have $compname only run on some HFI ports\n");
		printf ("Or you may select to configure redundant HFI ports with a\n");
		printf ("pair of HFI ports running a single $compname interface\n");
	}
	do
	{
		printf ("How many $compname interfaces would you like to configure? [1]: ");

		$inp = <STDIN>;
		$inp=remove_whitespace($inp);

		if ( length($inp) == 0 ) 
		{
			$max_ports=1;
		} elsif ($inp < 0 || $inp > $MAX_HFI_PORTS)
		{
			printf ("You must specify a number between 0 and $MAX_HFI_PORTS\n");
		} elsif ($inp == 0) {
			LogPrint "How many $compname interfaces would you like to configure? -> $inp\n";
			if (GetYesNo("You specified 0, would you like to skip IP address configuration?", "n") == 1)
			{
				# If user answered to skip, then reluctantly bail.
				Config_IP_Manually("$compname","$prefix$firstdev");
				return;
			}
		} else {
			$max_ports=$inp;
			LogPrint "How many $compname interfaces would you like to configure? -> $inp\n";
		}
	} while ($max_ports == 0);

	print "\nAbout to create $compname ifcfg files in $NETWORK_CONF_DIR\n";

	# Does the user want sequential IP address assignment starting at a base
	# IP address or specify an IP address for each HFI port (a.k.a. IPoIB device
	# instance). 

	if ( $max_ports > 1 ) {
		$intf_seq = GetYesNo("Configure interface names sequentially starting with $prefix$firstdev?", "y");
		$seq = GetYesNo("Assign Internet Addresses sequentially from a base IP address?", "y");
	} else {
		$intf_seq = GetYesNo("Use interface name $prefix$firstdev?", "y");
		$seq = 1;	# doesn't matter
	}

	# Allocate IP addresses and create the network config files '$prefix[firstdev...n]'.

	for($a=$firstdev; $a < $max_ports+$firstdev; $a++)
	{
		if ($intf_seq)
		{
			$interface="$prefix$a";
		} else {
			$default_intf = $interface;
			$interface = "";
			do {
				if ($default_intf eq "" )
				{
					print "Enter $compname interface name: ";
				} else {
					print "Enter $compname interface name [$default_intf]: ";
				}

				chomp($inp = <STDIN>);
				$inp=remove_whitespace($inp);
				if ( length($inp) == 0 )
				{
					$inp=$default_intf;
				}
				if ("$inp" eq "" )
				{
					printf "You must enter an interface name, such as $prefix$firstdev\n";
				} else {
					if (exists $interfaces{$inp})
					{
						print "You have already used $interface, specify a different name\n";
					} else {
						$interface=$inp;
						$interfaces{$interface} = 1;
					}
				}
			} while ("$interface" eq "");
		}
		LogPrint "Enter $compname interface name [$default_intf]: -> $interface\n";
		if ( $seq == 0 || $a == $firstdev) 
		{
			# not sequential assignment, get each IP addr.
			$ipaddr = Get_IP_Addr($interface, $ipaddr);
			$netmask = Get_IP_Addr_Netmask($interface, $ipaddr);
		}

		Build_ifcfg($interface, $ipaddr, $netmask);
		# make sure future prompts start fresh and don't auto remove
		clear_keep_config("$ifcfg_wildcard");

		# generate the next sequential IP address
		$ipaddr = NextIPaddr($ipaddr);
		$interface = NextInterface($interface);
	}
}

# Remove ifcfg files.
# driver should have already been unloaded.
#
sub Remove_ifcfg($$$)
{
	my($WhichDriver) = shift();	# driver name
	my($compname) = shift();
	my($prefix) = shift();

	# This could be an 'update' where we might not want to clobber the
	# IPoIB config files.

	my $ifcfg_wildcard="$NETWORK_CONF_DIR/ifcfg-$prefix"."[0-9]*";
	if ( `ls $ifcfg_wildcard 2>/dev/null` ne "" )
	{
		if (check_keep_config("$ifcfg_wildcard", "$compname ifcfg files", "y"))
		{
			print "Keeping $compname ifcfg files ...\n";
		} else {
			print "removing $ifcfg_wildcard\n";
			system "rm -f $ifcfg_wildcard";
		}
	}
}

sub check_network_config()
{
	my $nm=read_simple_config_param("/etc/sysconfig/network/config", "NETWORKMANAGER");
	if ( "$nm" eq "yes" ) {
		print RED "Please set NETWORKMANAGER=no in /etc/sysconfig/network/config", RESET "\n";
		HitKeyCont;
	}
}
