#!/usr/bin/perl

# Copyright (C) 2002-2014 NEC Corporation
# All Rights Reserved.
#
# --
# VND(Virtual Network Device) Control Tool for FTLinux
#
# VND(Virtual Network Device) Control Tool for FTLinux is based on
# it of version 2.2.1 that developed for Gemini-R.
#
# [3.0.0] May.27 2008 NEC Corporation
#         - vndctl was remodeled for Libra.
#         - 1st release
# [3.0.1] Jul.15 2008 NEC Corporation
#         - vnd_addcfg: Changed default network parameters for making
#                       a new bonding device.
# [3.0.2] Aug.22 2008 NEC Corporation
#         - vnd_down  : Added a function to delete ip targets for ARP
#                       link monitoring.
# [3.0.3] Aug.28 2009 NEC Corporation
#         - vnd_add   : Fixed a bug that vndctl can not add device
#                       after delete all device and restart.
#
# [4.0.1] Sep.7 2009 NEC Corporation
#         - vndctl was remodeled for Draco. (10 slots)
#         - 1st release
# [4.0.2] Nov.26 2009 NEC Corporation
#         - vnd_status: Changed show info only exist eth devices.
# [4.0.3] Dec.22 2009 NEC Corporation
#         - set alias: Changed alias info from e1000 to igb.
#
# [6.0.0] Feb.28 2014 NEC Corporation
#         - vndctl was remodeled for Cygnus. (12 slots)
#         - 1st release
# [6.0.1] May.28 2014 NEC Corporation
#         - set alias: Changed alias info to ixgbe. (10GB NIC)
# --

require '/opt/nec/ftras/lib/pci_utils.pl';

#
$ifcfg_dir= '/etc/sysconfig/network-scripts';

#
%slot_table;
$slot_table{"0600"}= 1;
$slot_table{"0601"}= 2;
$slot_table{"1200"}= 3;
$slot_table{"1201"}= 4;
$slot_table{"0100"}= 5;
$slot_table{"0101"}= 6;
$slot_table{"0200"}= 7;
$slot_table{"0201"}= 8;
$slot_table{"0300"}= 9;
$slot_table{"0301"}= 10;
$slot_table{"0400"}= 11;
$slot_table{"0401"}= 12;

#
$ENV{LC_ALL}= 'POSIX';
main();

#
sub main{
    if(@ARGV == 0){
        &usage_check();  #show usage
    }
    if($ARGV[0] eq 'up'){
        &vnd_up($ARGV[1]);
    }
    elsif($ARGV[0] eq 'down'){
        &vnd_down($ARGV[1]);
    }
    elsif($ARGV[0] eq 'add'){
        &vnd_add($ARGV[1], $ARGV[2]);
    }
    elsif($ARGV[0] eq 'del'){
        &vnd_del($ARGV[1]);
    }
    elsif($ARGV[0] eq 'modify'){
        &vnd_modify($ARGV[1]);
    }
    elsif($ARGV[0] eq 'config'){
        &vnd_config($ARGV[1]);
    }
    elsif($ARGV[0] eq 'ipconf'){
        shift(@ARGV);
        &vnd_ipconf(@ARGV);
    }
    elsif($ARGV[0] eq 'status'){
        &vnd_status($ARGV[1]);
    }
    else{
        &usage_check();  #show usage
    }

    exit(0);
}

#
sub usage_check{
    my($arg)= @_;

    if((!$arg) and ($arg ne 0)){
        print STDERR <<USAGE;
Usage: vndctl status [SLOT|VDEV]
       vndctl add    <SLOT> [VDEV]
       vndctl del    <SLOT|VDEV>
       vndctl modify <SLOT|VDEV>
       vndctl config <SLOT|VDEV>
       vndctl ipconf <SLOT|VDEV> [BOOTPROTO=(dhcp|bootp|none)]
                                 [IPADDR=<IPADDR>] [NETMASK=<NETMASK>]
                                 [GATEWAY=<GATEWAY>] [ONBOOT=(yes|no)]
                                 [HOSTNAME=<HOSTNAME>] [DOMAIN=<DOMAIN>]
                                 [BONDING_OPTS="<OPTIONS>"]
                                 [--del]
       vndctl up     <SLOT|VDEV>
       vndctl down   <SLOT|VDEV>
       
USAGE
        exit(2);
    }
}

#
sub error{
    my($code, $msg)= @_;
    print STDERR "Error: $msg\n";
    exit($code);
}

#
sub warning{
    my($msg)= @_;
    print STDERR "Warning: $msg\n";
}

##
#
sub vnd_up{
    my($arg)= @_;

    &usage_check($arg);
    my($vdev)= (&arg2vdev($arg))[0];

    system("$ifcfg_dir/ifup $vdev");
}

#
sub vnd_down{
    my($arg)= @_;

    &usage_check($arg);
    my($vdev)= (&arg2vdev($arg))[0];
    
    # delete ip targets for ARP link monitoring
    foreach (split(/\s+/, `cat /sys/class/net/$vdev/bonding/arp_ip_target`)){
        if(!open(SYS_CLASS_NET, "> /sys/class/net/$vdev/bonding/arp_ip_target")){
            &error(1, "Cannot open file: /sys/class/net/$vdev/bonding/arp_ip_target");
        }
        print SYS_CLASS_NET "-$_\n";
        close(SYS_CLASS_NET);
    }

    system("$ifcfg_dir/ifdown $vdev");
}

#
sub vnd_add{
    my($arg, $arg2)= @_;

    &usage_check($arg);
    my($vdev, $slot);

    if($arg =~ /^(\d*)$/){
        if(($1 < 1) or ($1 > 12)){
            &error(1, "invalid argument: $arg");
        }
        $slot= $1;
        $vdev= $arg2;
    }
    else{
        &error(1, "invalid argument: $arg");
    }

    if(&slot2vdev($slot)){
        &error(1, "already added: slot=$arg");
    }

    if($vdev){
        if($vdev !~ /^bond\d+$/){
            &error(1, "invalid name: $vdev");
        }
        my(%vdevs)= &vnd_conf();
        if($vdevs{$vdev}){
            &error(1, "already used name: $vdev");
        }
    }
    else{
        $vdev= &assign_vdev();
    }

    my($bond) = 0;
    if(!open(LSMOD, "/sbin/lsmod |")){
        &error(1, "lsmod error");
    }
    while(<LSMOD>){
        if(/^bonding.+$/){
            $bond = 1;
            last;
        }
    }
    if($bond == 0){
        my($ret) = system("/sbin/modprobe bonding");
        if($ret != 0){
            &error(1, "modprobe error");
        }
    }

    my(%ifc)= &ifc_status();
    unless($ifc{$vdev}){
        &error(1, "unknown bonding interface: $vdev");
    }

    my(@rdevs)= &ifc_slot2rdevs($slot);
    if(@rdevs == 0){
        &pci_modprobe("igb");
        @rdevs= &ifc_slot2rdevs($slot);
        if(@rdevs == 0){
            &error(1, "missing network card");
        }
    }
    my($drvr) = &ifc_driver($rdevs[0]);

    &modules_add_alias($vdev, "bonding");
    foreach $r (@rdevs){
#        &modules_add_alias($r, "igb");
        &modules_add_alias($r, $drvr);
    }
    &vnd_addcfg($vdev, @rdevs);
    #system("/sbin/modprobe bonding");
}

#
sub vnd_addcfg{
    my($vdev, @rdevs)= @_;

    unless(-f "$ifcfg_dir/ifcfg-$vdev"){
        if(!open(CONF, "> $ifcfg_dir/ifcfg-$vdev")){
            &error(1, "cannot open file: $ifcfg_dir/ifcfg-$vdevr");
        }
        print CONF "DEVICE=$vdev\n";
        print CONF "BONDING_OPTS=\"miimon=100 mode=1\"\n";
        print CONF "ONBOOT=yes\n";
        print CONF "BOOTPROTO=dhcp\n";
        close(CONF);
    }

    foreach $r (@rdevs){
        if(!open(CONF, "> $ifcfg_dir/ifcfg-$r")){
            &error(1, "cannot open file: $ifcfg_dir/ifcfg-$r");
        }
        print CONF "DEVICE=$r\n";
        print CONF "MASTER=$vdev\n";
        print CONF "ONBOOT=no\n";
        print CONF "SLAVE=yes\n";
        print CONF "TYPE=Ethernet\n";
        close(CONF);
    }
}

#
sub vnd_del{
    my($arg)= @_;

    &usage_check($arg);
    my($vdev)= (&arg2vdev($arg))[0];
    my(%vdevs)= &vdev_status();
    if($vdevs{$vdev}{'Status'} ne 'OFFLINE'
       or $vdevs{$vdev}{'RealDevice'}){
        &error(1, "$vdev: cannot delete in active");
    }

    my(@rdevs)= &ifc_slot2rdevs((&arg2vdev($arg))[1]);

    &vnd_rmcfg($vdev, @rdevs);
    foreach $r (@rdevs){
        &modules_del_alias($r);
    }
    &modules_del_alias($vdev);
}

#
sub vnd_rmcfg{
    my($vdev, @rdevs)= @_;

    foreach $r (@rdevs){
        if(-f "$ifcfg_dir/ifcfg-$r"){
            unlink("$ifcfg_dir/ifcfg-$r");
        }
    }
    my(%vdevs)= &vnd_conf();
    unless($vdevs{$vdev}){
        if(-f "$ifcfg_dir/ifcfg-$vdev"){
            unlink("$ifcfg_dir/ifcfg-$vdev");
        }
    }
}

#
sub vnd_modify{
    my($arg)= @_;

    &usage_check($arg);
    my(@vd)= &arg2vdev($arg);

    my(@rdevs1)= &vdev2devs($vd[0]);
    shift @rdevs1;

    my(@rdevs2);
    if(@vd == 2){
        @rdevs2= &ifc_slot2rdevs($vd[1]);
    }

    #
    if(@rdevs2 == 0){
        if(-f "$ifcfg_dir/ifcfg-$vd[0]"){
            unlink("$ifcfg_dir/ifcfg-$vd[0]");
        }
        foreach $r (@rdevs1){
            if(-f "$ifcfg_dir/ifcfg-$r"){
                unlink("$ifcfg_dir/ifcfg-$r");
            }
        }
        return;
    }

    # DO NOT DEPEND ON THE LENGTH OF @rdevs1 AND @rdevs2
    my($drvr);
    SEARCH: foreach $rdev2 (@rdevs2){
        foreach $rdev1 (@rdevs1){
            next SEARCH if($rdev1 eq $rdev2);
        }
        $drvr = &ifc_driver($rdev2);
#        &modules_add_alias($rdev2, "igb");
        &modules_add_alias($rdev2, "$drvr");
        &vnd_addcfg($vd[0], $rdev2);
    }
    SEARCH: foreach $rdev1 (@rdevs1){
        foreach $rdev2 (@rdevs2){
            next SEARCH if($rdev1 eq $rdev2);
        }
        &modules_del_alias($rdev1);
        &vnd_rmcfg($vd[0], $rdev1);
    }

}

#
sub vnd_config{
    my($arg)= @_;
    my(%conf);

    &usage_check($arg);
    my($vdev)= (&arg2vdev($arg))[0];

    print "[Virtual Network Setting]\n";
    print "*Boot Protocol? [none/dhcp/bootp] ";
    chomp($input= <STDIN>);
    if($input =~ /^[nN](one)?$/){
        $conf{'BOOTPROTO'}= 'none';

        print "*IP address? ";
        chomp($input= <STDIN>);
        if($input =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/){
            $conf{'IPADDR'}= $input;
        }
        elsif($input){
            print STDERR "Invalid input: $input\n";
            return;
        }

        print "*Netmask? ";
        chomp($input= <STDIN>);
        if($input =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/){
            $conf{'NETMASK'}= $input;
        }
        elsif($input){
            print STDERR "Invalid input: $input\n";
            return;
        }

        print "*Default gateway (IP)? ";
        chomp($input= <STDIN>);
        if($input =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/){
            $conf{'GATEWAY'}= $input;
        }
        elsif($input){
            print STDERR "Invalid input: $input\n";
            return;
        }
        if(!$conf{'IPADDR'}){
            &error(1, "address required: IPADDR=<IPADDR>");
        }
        if(!$conf{'NETMASK'}){
            &error(1, "address required: NETMASK=<NETMASK>");
        }
    }
    elsif($input =~ /^([dD](hcp)?|[bB](ootp)?)$/){
        $conf{'BOOTPROTO'}= 'dhcp'  if($input =~ /^[dD]/);
        $conf{'BOOTPROTO'}= 'bootp' if($input =~ /^[bB]/);
        $conf{'IPADDR'}= "";
        $conf{'NETMASK'}= "";
        $conf{'NETWORK'}= "";
        $conf{'GATEWAY'}= "";
    }
    elsif(!$input){
        return;
    }
    else
    {
        print STDERR "Invalid input: $input\n";
        return;
    }

    print("\n*Are you sure to set it? [y/n] ");
    chomp($input= <STDIN>);
    if($input=~ /^[yY]([eE][sS])?$/){
        foreach $key (keys %conf){
            &ifcfg_set_parameter($vdev, $key, $conf{$key});
        }
    }
    elsif($input=~ /^[nN]([oO])?$/){
    }
    else{
        print STDERR "Invalid input: $input\n";
    }

    print("\n");
    &vnd_ipconf($vdev);

    #system("/usr/sbin/netconfig -d $vdev");
}

#
sub vnd_ipconf{
    my($arg, @args)= @_;

    &usage_check($arg);
    my($vdev)= (&arg2vdev($arg))[0];
    my(%conf);
    if(open(CONF, "$ifcfg_dir/ifcfg-$vdev")){
        while(<CONF>){
            if(/(\w+?)=(\".+\"|.+)/){
                $conf{$1}= $2;
            }
        }
    }
    close(CONF);

    if(@args == 0){
        foreach $key ('DEVICE', 'ONBOOT', 'BOOTPROTO', 'IPADDR',
                      'NETMASK', 'GATEWAY', 'HOSTNAME', 'DOMAIN',
                      'BONDING_OPTS'){
            print "$key=$conf{$key}\n" if($conf{$key});
        }
        return;
    }

    #
    foreach (@args){
        if(/^--del$/){
            unlink("$ifcfg_dir/ifcfg-$vdev");
            return;
        }
        if(/^BOOTPROTO=(\S*)/){
            $conf{'BOOTPROTO'}= $1;
            if($1 =~ /^(dhcp|bootp)$/){
                undef($conf{'IPADDR'});
            }
        }
        elsif(/^IPADDR=(\S*)/){
            $conf{'IPADDR'}= $1;
            $conf{'BOOTPROTO'}= 'none' if($1);
        }
        elsif(/^NETMASK=(\S*)/)   {$conf{'NETMASK'}= $1;}
        elsif(/^GATEWAY=(\S*)/)   {$conf{'GATEWAY'}= $1;}
        elsif(/^HOSTNAME=(\S*)/)  {$conf{'HOSTNAME'}= $1;}
        elsif(/^DOMAIN=(\S*)/)    {$conf{'DOMAIN'}= $1;}
        elsif(/^ONBOOT=(yes|no)$/){$conf{'ONBOOT'}= $1;}
        elsif(/^BONDING_OPTS=(|\s*([^= ]+=[^= ]+\s+)*[^= ]+=[^= ]+\s*)$/)
                                  {$conf{'BONDING_OPTS'}= ($1) ? ("\"$1\"") : ("");}
        else{
            &error(1, "invalid argument: $_");
        }
    }
    unless($conf{'BOOTPROTO'} =~ /^(dhcp|bootp)$/){
        if(!$conf{'IPADDR'}){
            &error(1, "address required: IPADDR=<IPADDR>");
        }
        if(!$conf{'NETMASK'}){
            &error(1, "address required: NETMASK=<NETMASK>");
        }
    }

    ifcfg_set_parameter($vdev, "BOOTPROTO", $conf{'BOOTPROTO'});
    ifcfg_set_parameter($vdev, "IPADDR", $conf{'IPADDR'});
    ifcfg_set_parameter($vdev, "NETMASK", $conf{'NETMASK'});
    ifcfg_set_parameter($vdev, "GATEWAY", $conf{'GATEWAY'});
    ifcfg_set_parameter($vdev, "HOSTNAME", $conf{'HOSTNAME'});
    ifcfg_set_parameter($vdev, "DOMAIN", $conf{'DOMAIN'});
    ifcfg_set_parameter($vdev, "ONBOOT", $conf{'ONBOOT'});
    ifcfg_set_parameter($vdev, "BONDING_OPTS", $conf{'BONDING_OPTS'});

    &vnd_ipconf($vdev);
}

#
sub vnd_status{
    my($arg)= @_;
    my(@vd);
    my(%ext_slot);

    if($arg){
        @vd= &arg2vdev($arg);
    }
    else{
        @vd= ($arg eq 0) ? (&arg2vdev($arg)) : ();
    }
    my(%buf);
    my(%ifc)= &ifc_status();

    #
    print "--Virtual Network Status--\n";
    print "BondingDevice Slot Status  InetAddress     RXErrors TXErrors Collisions\n";
    my(%vdevs)= &vdev_status();
    foreach $v (sort keys %vdevs){
        next if((@vd > 0) and ($v ne $vd[0]));
        my($num) = 0;
        if($v =~ /bond(\d*).*/){
            $num = $1;
        }
        next if($num >= 10);
        print sprintf("%-14s%-5s%-8s%-16s%-9s%-9s%-s\n",
            $v, $vdevs{$v}{'Slot'}, $vdevs{$v}{'Status'},
            $vdevs{$v}{'InetAddress'}, $vdevs{$v}{'RXErrors'},
            $vdevs{$v}{'TXErrors'}, $vdevs{$v}{'Collisions'});
        if(@vd > 0){
            foreach (@{$ifc{$vd[0]}}){
                print "        $_\n";
            }
        }
    }
    if((@vd == 0) or
       ((@vd > 0) and ($vd[0] eq 'bond10'))){
        if($vdevs{'bond10'}{'Status'}){
            print sprintf("%-14s%-5s%-8s%-16s%-9s%-9s%-s\n",
            'bond10', $vdevs{'bond10'}{'Slot'}, $vdevs{'bond10'}{'Status'},
            $vdevs{'bond10'}{'InetAddress'}, $vdevs{'bond10'}{'RXErrors'},
            $vdevs{'bond10'}{'TXErrors'}, $vdevs{'bond10'}{'Collisions'});
            foreach (@{$ifc{$vd[0]}}){
                print "        $_\n";
            }
        }
    }
    if((@vd == 0) or
       ((@vd > 0) and ($vd[0] eq 'bond11'))){
        if($vdevs{'bond11'}{'Status'}){
            print sprintf("%-14s%-5s%-8s%-16s%-9s%-9s%-s\n",
            'bond11', $vdevs{'bond11'}{'Slot'}, $vdevs{'bond11'}{'Status'},
            $vdevs{'bond11'}{'InetAddress'}, $vdevs{'bond11'}{'RXErrors'},
            $vdevs{'bond11'}{'TXErrors'}, $vdevs{'bond11'}{'Collisions'});
            foreach (@{$ifc{$vd[0]}}){
                print "        $_\n";
            }
        }
    }
    if(@vd == 1){
        return;
    }

    %ext_slot = &slot_exist();
    #
    print "\nSlot        RealDevice Status           Interface LinkState LinkSpeed\n";
    foreach $slot (1..12){
        next if((@vd > 0) and ($slot != $vd[1]));
        next unless($ext_slot{$slot});
        $buf{'Top'}= sprintf("%-4s top    -", $slot);
        $buf{'Bottom'}= "     bottom -";
        foreach $v (sort keys %vdevs){
            if($slot == $vdevs{$v}{'Slot'}){
                foreach $r (keys %{$vdevs{$v}{'RealDevice'}}){
                    if(&rdev2cru($r) eq "10"){
                        $buf{'Top'}=~ s/-//g;
                        $buf{'Top'} .= sprintf("%-11s%-17s%-10s%-10s%-s",
                            $r,
                            $vdevs{$v}{'RealDevice'}{$r}{'Status'},
                            $vdevs{$v}{'RealDevice'}{$r}{'Interface'},
                            $vdevs{$v}{'RealDevice'}{$r}{'LinkState'},
                            $vdevs{$v}{'RealDevice'}{$r}{'LinkSpeed'});
                        if(@vd > 0){
                            foreach(@{$ifc{$r}}){
                                $buf{'Top'} .= sprintf("\n        $_");
                            }
                            $buf{'Top'} .= "\n";
                        }
                    }
                    if(&rdev2cru($r) eq "11"){
                        $buf{'Bottom'}=~ s/-//g;
                        $buf{'Bottom'} .= sprintf("%-11s%-17s%-10s%-10s%-s",
                            $r,
                            $vdevs{$v}{'RealDevice'}{$r}{'Status'},
                            $vdevs{$v}{'RealDevice'}{$r}{'Interface'},
                            $vdevs{$v}{'RealDevice'}{$r}{'LinkState'},
                            $vdevs{$v}{'RealDevice'}{$r}{'LinkSpeed'});
                        if(@vd > 0){
                            foreach(@{$ifc{$r}}){
                                $buf{'Bottom'} .= sprintf("\n        $_");
                            }
                            $buf{'Bottom'} .= "\n";
                        }
                    }
                }
                last;
            }
        }
        print "$buf{'Top'}\n";
        print "$buf{'Bottom'}\n";
    }
}

##
#
sub slot_exist{
    my($slot);
    my(%ethexist);

    if(!open(SLOT, "/sbin/ifconfig -a | grep eth |")){
        print "cannot execute command: '/sbin/ifconfig'";
        exit(1);
    }

    while(<SLOT>){
        if(/^(eth\d{6})\s+(\S.*)$/){
            $slot = rdev2slot($1);
            $ethexist{$slot} = 1;
        }
    }
    return %ethexist;
}

##
#
sub modules_add_alias{
    my($name, $value)= @_;

    &modules_set_config("alias $name", "$value");
}

#
sub modules_del_alias{
    my($name)= @_;

    &modules_reset_config("alias $name");
}

#
sub modules_read_config{
    my(@lines);

    if(!open(CONF, "/etc/modprobe.d/ft-network.conf")){
        &error(1, "cannot open file: /etc/modprobe.d/ft-network.conf");
    }
    while(<CONF>){
        chomp($_);
        $_ =~ s/\s+$//;
        unless(/^\s*#/){
            $_ =~ s/^\s+//;
            $_ =~ s/\s+/ /g;
        }
        $lines[$#lines+1]= $_;
    }
    close(CONF);

    return @lines;
}

#
sub if_read_config{
    my($dev)= @_;
    my(@lines);

    if(!open(CONF, "$ifcfg_dir/ifcfg-$dev")){
        &error(1, "cannot open file: $ifcfg_dir/ifcfg-$dev");
    }
    while(<CONF>){
        chomp($_);
        $_ =~ s/\s+$//;
        unless(/^\s*#/){
            if(/^\s*(\S+)\s*=\s*(\".*\")$/){
                $_ = "$1=$2";
            }
            else{
                $_ =~ s/\s+//g;
            }
        }
        $lines[$#lines+1]= $_;
    }
    close(CONF);

    return @lines;
}

#
sub modules_write_config{
    my(@lines)= @_;

    if(!open(CONF, "> /etc/modprobe.d/ft-network.conf")){
        &error(1, "cannot write file: /etc/modprobe.d/ft-network.conf");
    }
    print CONF "$_\n" foreach (@lines);
    close(CONF);

    #system("/sbin/depmod");
}

#
sub if_write_config{
    my($dev, @lines)= @_;

    if(!open(CONF, "> $ifcfg_dir/ifcfg-$dev")){
        &error(1, "cannot write file: $ifcfg_dir/ifcfg-$dev");
    }
    print CONF "$_\n" foreach (@lines);
    close(CONF);
}

#
sub modules_set_config{
    my($key, $value)= @_;

    #
    my(@lines)= &modules_read_config();
    my($idx)= 0;
    for(; $lines[$idx]; ++$idx){
        if($lines[$idx] =~ /^$key\s(.*)/){
            return if($1 eq $value);
            unless($value){
                splice(@lines, $idx, 1);
            }
            last;
        }
    }
    if($value){
        $lines[$idx]= "$key $value";
    }

    #
    my(@vdev_lines);
    my(@rdev_lines);
    my(@option_lines);
    my(@sort_lines);

    foreach(@lines){
        if(/^alias bond/){
            $vdev_lines[$#vdev_lines+1]= $_;
        }
        elsif(/^alias eth/){
            $rdev_lines[$#rdev_lines+1]= $_;
        }
        elsif(/^options/){
            $option_lines[$#option_lines+1]= $_;
        }
        else{
            $sort_lines[$#sort_lines+1]= $_;
        }
    }

    unshift @sort_lines, sort @rdev_lines;
    unshift @sort_lines, sort @option_lines;
    unshift @sort_lines, sort @vdev_lines;

    &modules_write_config(@sort_lines);
}

#
sub ifcfg_set_parameter{
    my($dev, $key, $param)= @_;
    my(@lines)= &if_read_config($dev);
    my($idx)= 0;

    for(; $lines[$idx]; ++$idx){
        if($lines[$idx] =~ /^\s*$key=(.+)$/){
            unless($param){
                splice(@lines, $idx, 1);
            }
            last;
        }
    }
    if($param){
        $lines[$idx]= "$key=$param";
    }
    &if_write_config($dev, @lines);
}

#
sub modules_reset_config{
    my($key)= @_;

    &modules_set_config($key);
}

##
#
sub assign_vdev{
    my(@using);
    my(%vdevs)= &vdev_status();
    foreach $v (keys %vdevs){
        if($vdevs{$v}{'Slot'}){
            if($v =~ /^bond(\d+)$/){
                $using[$1]= 1;
            }
        }
    }

    my($num)= 0;
    for(;;){
        last if(!$using[$num] and ! -f "$ifcfg_dir/ifcfg-bond$num");
        ++$num;
    }

    return "bond$num";
}

#
sub arg2vdev{
    my($arg)= @_;
    
    if($arg =~ /^bond\d+$/){
        $vdev= $arg;
        my(%ifc)= &ifc_status();
        unless($ifc{$vdev}){
            &error(1, "unknown bonding interface: $arg");
        }
        my(@devs)= &vdev2devs($vdev);
        if(@devs == 0){
            &error(1, "unconfigured bonding interface: $arg");
        }
        elsif(@devs == 1){
            return ($vdev);
        }
        return ($vdev, &rdev2slot($devs[1]));
    }
    elsif($arg =~ /^(\d*)$/){
        if(($1 < 1) or ($1 > 12)){
            &error(1, "invalid argument: $arg");
        }
        my($slot)= $1;
        my(@rdevs)= &ifc_slot2rdevs($slot);
        if(@rdevs == 0){
            &pci_modprobe("igb");
            @rdevs= &ifc_slot2rdevs($slot);
            if(@rdevs == 0){
                &error(1, "missing network card: slot=$arg");
            }
        }
        my($vdev)= &slot2vdev($slot);
        if(!$vdev){
            &error(1, "unconfigured bonding interface: slot=$arg");
        }
        return ($vdev, $slot);
    }
    else{
        &error(1, "invalid argument: $arg");
    }
}

#
sub slot2vdev{
    my($slot)= @_;

    my(%vdevs)= &vnd_conf();

    foreach $v (keys %vdevs){
        if($slot == &rdev2slot($vdevs{$v}[0])){
            return $v;
        }
    }

    return undef;
}

#
sub rdev2slot{
    my($rdev)= @_;

    my(@a)= &rdev_breakup($rdev);
    return (@a > 0) ? ($slot_table{"$a[2]$a[3]"}) : ();
}

#
sub rdev2cru{
    my($rdev)= @_;

    my(@a)= &rdev_breakup($rdev);

    return (@a > 0) ? ($a[1]) : ();
}

#
sub vdev2devs{
    my($vdev)= @_;

    my(%vdevs)= &vnd_conf();

    foreach $v (keys %vdevs){
        if($vdev eq $v){
            return ($vdev, @{$vdevs{$v}});
        }
    }

    return ();
}

#
sub ifc_slot2rdevs{
    my($slot)= @_;

    my(@ifaces);
    my(%ifc)= &ifc_status();

    foreach $r (keys %ifc){
        my(@a)= &rdev_breakup($r);
        if(@a > 0){
            if($slot == $slot_table{"$a[2]$a[3]"}){
                $ifaces[$#ifaces+1]= $r;
            }
        }
    }

    return @ifaces;
}

#
sub ifc_status{
    my(%ifconf, $dev);

    if(!open(CMD, "/sbin/ifconfig -a |")){
        print "cannot execute command: '/sbin/ifconfig'";
        exit(1);
    }
    while(<CMD>){
        if(/^(\S+)\s+(\S.*)$/){
            $dev= $1;
            $ifconf{$dev}= [$2];
        }
        elsif($dev){
            if(/^\s+(\S.*)$/){
                push(@{$ifconf{$dev}}, $1);
            }
            else{
                $dev= '';
            }
        }
    }
    close(CMD);

    return %ifconf;
}

#
sub vdev_status{
    my(%vdevs);

    if(!open(CMD, "/opt/ft/bin/ftsmaint lsvnd |")){
        print "cannot execute command: 'ftsmaint lsvnd'";
        exit(1);
    }

    while(<CMD>){
        if(/^GroupName/){
            next;
        }
        elsif(/^Member/){
            next;
        }
        elsif(/^(bond\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+$/){
            $vdevs{$1}{'Status'}= $2;
            $vdevs{$1}{'InetAddress'}= $3;
            $vdevs{$1}{'RXErrors'}= $4;
            $vdevs{$1}{'TXErrors'}= $5;
            $vdevs{$1}{'Collisions'}= $6;
        }
        elsif(/^(eth\d{6})\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+$/){
            $vdevs{$2}{'RealDevice'}{$1}{'Status'}= $3;
            $vdevs{$2}{'RealDevice'}{$1}{'Interface'}= $4;
            $vdevs{$2}{'RealDevice'}{$1}{'LinkState'}= $5;
            $vdevs{$2}{'RealDevice'}{$1}{'LinkSpeed'}= $6;
            $vdevs{$2}{'Slot'}= &rdev2slot($1);
        }
        else{
            next;
        }
    }
    close(CMD);

    return %vdevs;
}

#
sub vnd_conf{
    my(@rdevs);

    if(!open(CMD, "/bin/ls -1 $ifcfg_dir |")){
        print "cannot execute command: 'ls $ifcfg_dir'";
        exit(1);
    }
    while(<CMD>){
        chomp($_);
        if(/^ifcfg-(eth\d{6})$/){
            $rdevs[$#rdevs+1]= $1;
        }
    }
    close(CMD);

    my(%vdevs);
    my($master);
    my($slave);

    foreach $r (@rdevs){
        if(!open(CONF, "$ifcfg_dir/ifcfg-$r")){
            &error(1, "cannot open file: $ifcfg_dir/ifcfg-$r");
            exit(1);
        }
        while(<CONF>){
            chomp($_);
            if(/^SLAVE=(yes|no)$/){
                $slave= $1;
            }
            if(/^MASTER=(bond\d+)$/){
                $master= $1;
            }
        }
        if($slave eq 'yes' and -f "$ifcfg_dir/ifcfg-$master"){
            push(@{$vdevs{$master}}, $r);
        }
        close(CONF);
    }

    return %vdevs;
}

#
sub rdev_breakup{
    my($rdev)= @_;

    if($rdev =~ /^(eth)(\d{2})(\d{2})(\d{2})$/){
        return ($1, $2, $3, $4);
    }
    else{
        return ();
    }
}

#
sub rdevs_check{
    my(@args)= @_;

    my(@r);
    foreach $rdev (@args){
        my(@a)= &rdev_breakup($rdev);
        if(@a == 0){
            return -1;
        }
        if(@r == 0){
            @r= @a;
        }
        else{
            if(($r[0] ne $a[0]) or ($r[2] ne $a[2]) or ($r[3] ne $a[3])){
                return -1;
            }
        }
    }

    return 0;
}

#
sub ifc_driver{
    my($nic) = @_;
    my($drvr) = 'igb';
    if(!open(INFO, "/sbin/ethtool -i $nic |")){
        print "cannot execute command: '/sbin/ethtool'";
        return 0;
    }

    while(<INFO>){
        if(/^driver\:\s*(\w*)/){
            $drvr = $1;
            last;
        }
    }
    close(INFO);
    
    if(($drvr ne 'igb') and ($drvr ne 'ixgbe')){
        $drvr = 'igb';
    }
    return $drvr;
}

##
