#!/usr/bin/perl

# Copyright (C) 2002-2014 NEC Corporation
# All Rights Reserved.
#
# --
# FTLinux DISK utility
#
# [3.0.0] May.27 2008 NEC Corporation
#         - ftdisk was remodeled for Libra.
#         - 1st release
# [3.0.1] Jun.17 2008 NEC Corporation
#         - modified scsiDevname2Serial to get disk's serial number with scsi_id
#           command.
#         - modified ftdisk_striping to check if RAID device deleted with --del 
#           option is RAID0 on SCSI device (Not RAID0 on RAID1 device).
#         - fixed the bug that the comment out character (#) is added one more 
#           when the disk scrubbing turn off in off.
# [4.0.1] Aug.24 2009 NEC Corporation
#         - ftdisk was remodeled for Draco. (16 Disks)
#         - 1st release
# [4.0.2] Nov.26 2009 NEC Corporation
#         - Changed status. (eject both of 2 disks)
#
# [5.0.1] Aug.3 2011 NEC Corporation
#         - 1st release for RHEL 6.1.
# [5.0.2] Dec.20 2013 NEC Corporation
#         - modify hasRaidBuddy function.
#         - zero clear superblock after destroy(RAID1)
#           to re-create after destroy.
#
# [6.0.0] Feb.28 2014 NEC Corporation
#         - ftdisk was remodeled for Cygnus.
#         - 1st release
# [6.0.1] May.28 2014 NEC Corporation
#         - Change filesystem to ext4 from ext3.
#         - not create or repair disk (Reason has MTBF).
# [6.0.2] Oct.27 2014 NEC Corporation
#         - modify make partition to sectors unit.
# --

use File::Basename;
use Getopt::Long;
require '/opt/nec/ftras/lib/ftdisk_utils.pl';

#
my($_ftdisk_bringup_timeout)= 30;           # Default [sec]
my($_ftdisk_bringdown_timeout)= 3;          # Default [sec]
my($_default_auto_repair_interval)= 10;     # Default [min])
my($_scrubmds_cron_path)= '/etc/cron.d/lsb-ft-scrubmds';
my($_scrubmds_script_path)= '/opt/ft/sbin/ft-scrubmds';
my($_scrubmds_default_schedule)= "22 4 * * 0 root $_scrubmds_script_path";
my($_default_md_bitmap)= 'internal';        # Default [internal|none]

#
my($BASENAME)= basename($0);
$ENV{LC_ALL}= 'POSIX';
$SIG{INT}= 'IGNORE';
main();

#
sub main{
    my($cmd)= shift(@ARGV);

    if($cmd eq 'stat'){
        unless(&GetOptions("sys", "log")){
            exit(1);
        }
        &ftdisk_stat($opt_sys, $opt_log);
    }
    #elsif($cmd eq 'up'){
    #    &ftdisk_up($ARGV[0]);
    #}
    #elsif($cmd eq 'down'){
    #    &ftdisk_down($ARGV[0]);
    #}
    elsif($cmd eq 'raidstat'){
        unless(&GetOptions("label","level=i")){
            exit(1);
        }
        &ftdisk_raidstat($opt_label, $opt_level);
    }
    elsif($cmd eq 'repair'){
        unless(&GetOptions("force")){
            exit(1);
        }
        if($ARGV[0]){
            &ftdisk_repair($ARGV[0], $opt_force);
        }
        else{
            &ftdisk_repair_all($opt_force);
        }
    }
    elsif($cmd eq 'remove'){
        &ftdisk_remove($ARGV[0]);
    }
    elsif($cmd eq 'create'){
        unless(&GetOptions("force", "swap=i", "size=s", "label:s")){
            exit(1);
        }
        &ftdisk_create($ARGV[0],
                       $opt_force, $opt_swap, $opt_size, $opt_label);
    }
    elsif($cmd eq 'destroy'){
        &ftdisk_destroy($ARGV[0]);
    }
    elsif($cmd eq 'autorepair'){
        unless(&GetOptions("interval=i")){
            exit(1);
        }
        &ftdisk_autorepair($ARGV[0], $opt_interval);
    }
    elsif($cmd eq 'scrubbing'){
        unless(&GetOptions("interval=s")){
            exit(1);
        }
        &ftdisk_scrubbing($ARGV[0], $opt_interval);
    }
    elsif($cmd eq 'striping'){
        unless(&GetOptions("create=s","delete")){
            exit(1);
        }
        &ftdisk_striping($ARGV[0], $opt_create, $opt_delete);
    }
    elsif($cmd eq 'raidopt'){
        unless(&GetOptions("fast_resync=s", 
                           "resync_speed_min=i", 
                           "resync_speed_max=i")){
            exit(1);
        }
        &ftdisk_raidopt($ARGV[0], 
                        $opt_fast_resync, 
                        $opt_resync_speed_min,
                        $opt_resync_speed_max);
    }
    else{
        &check_arg();  # to show usage
    }

    exit(0);
}

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

    if(!$arg){
        print STDERR <<USAGE;

<<DISK>>
ftdisk stat       [--sys|--log]

<<SOFTWARE-RAID>>
ftdisk raidstat   [--label] [--level=<raid level>]
       repair     [--force] [scsi-slot]
       autorepair [on|off] [--interval=<value(min)>]
       scrubbing  [on|off] [--interval="<time and date fields (cron) *1>"]
       remove     <scsi-slot>
       create     <scsi-slot> 
                  [--force] [--swap=<num>] [--size=<size(s)>] [--label=<name>]
       destroy    <scsi-slot>
       striping   [--create=<md device num(s) *2>|--delete] [md device name]

*1 Refer to CRONTAB(5) which is output by executing the following command.
     # man -S 5 crontab
*2 Mark off raid1 device numbers for the striping array by comma.
     e.g. 1,2,10,11

USAGE
        exit(2);
    }
}

#
sub logger{
    my($msg)= @_;
    if(open(LOG, "| /usr/bin/logger -t $BASENAME")){
        print LOG "$msg\n";
        close(LOG);
    }
}

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

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

#
sub invokeProcCmd{
    my($proc, $cmd)= @_;
    if(!open(PROC, "> $proc")){
        &error(1, "Cannot open file: $proc");
    }
    print PROC "$cmd\n";
    if($ENV{'FTDISK_PRINT_PROC_COMMAND'}){
        print STDERR "$cmd\n";
    }
    close(PROC);
}

#
sub invokeScsiCmd{
    my($cmd)= @_;
    &invokeProcCmd($FTDISK_PROC_SCSI_SCSI, "scsi $cmd");
}

#
sub remakeRaidConfig{
    system("/bin/echo 'DEVICE partitions' > /etc/mdadm.conf");
    system("/sbin/mdadm --detail --scan -v | grep '^ARRAY' >> /etc/mdadm.conf");
}

#
sub remakeBootImg{
&logger("remakeBootImg");

    my($kern) = `uname -r`;
    chomp($kern);

    my($imgFile) = "/boot/initramfs-$kern.img";
    if(-f "$imgFile"){
        $update = system("/bin/mv $imgFile $imgFile.bak 2> /dev/null");
        if($update){
            &logger("/bin/mv $imgFile $imgFile.bak is Fail");
            return -1;
        }
    }

#    my($update) = system("/sbin/dracut $imgFile $kern >& /dev/null");
    my($update) = system("/sbin/dracut $imgFile $kern");
    if($update){
        &logger("/sbin/dracut $imgFile $kern is Fail");
        $update = system("/bin/mv $imgFile.bak $imgFile 2> /dev/null");
        if($update){
            &logger("/bin/mv $imgFile.bak $imgFile is Fail");
            return -2;
        }
        return -3;
    }
    return 0;
}

##
sub ftdisk_stat{
    my($prt_sys, $prt_log)= @_;
    
    if($prt_sys and $prt_log){
        &check_arg();
    }
    elsif($prt_log){
        &scsiPrintSyslog();
    }
    else{
        &scsiPrintStat($prt_sys);
    }
}

#
sub scsiPrintStat{
    my($prt_sys)= @_;

    my(@db)= &scsiGetSimpleDB($SCSI_DB_SERIAL2PATH_H,
                              $SCSI_DB_DEVNAME2SERIAL_H,
                              $SCSI_DB_SERIAL2INFO_H);
    my(%ser2pth)  = %{$db[0]};
    my(%dn2ser)   = %{$db[1]};
    my(%ser2info) = %{$db[2]};

    #
    if($prt_sys){
        foreach $slt (1 .. 16){
            my($dn)= &scsiSlot2Devname($slt);
            my($ser)= $dn2ser{$dn};
            if($ser){
                my($kdn)= &udev2kdev($dn);
                printf("  %2d  %-13s %-45s", $slt,
                    sprintf("%s [%d]", "$dn($kdn)", $ser2info{$ser}->[1]),
                    sprintf("%s\/%s\/%s", 
                        $ser2info{$ser}->[2],
                        $ser2info{$ser}->[3],
                        "#$ser")); 
                printf(" %-13s", $ser2pth{$ser});
                print "\n";
            }
            else{
                printf("  %2d  -\n", $slt);
            }
        }
    }
    else{
        my($dn);
        foreach $dn (sort keys %dn2ser){
            my($kdn)= &udev2kdev($dn);
            my($ser)= $dn2ser{$dn};
            if($ser){
                printf("%-13s %-45s",
                    sprintf("%s [%d]", "$dn($kdn)", $ser2info{$ser}->[1]),
                    sprintf("%s\/%s\/%s",
                        $ser2info{$ser}->[2],
                        $ser2info{$ser}->[3],
                        "#$ser"));
                printf(" %-13s", $ser2pth{$ser});
                print "\n";
            }
        }

    }
}

##
sub scsiPrintSyslog{
    if(open(DMESG, "/bin/dmesg | grep scsi |")){
        while(<DMESG>){
            if(/^scsi\d+\s*:/){
                print $_;
            }
            elsif(/^(Attached scsi.*)(sd[a-z|0-9]+)(.*)$/){
                print $_;
            }
        }
    }
    close(DMESG);
}

##
sub ftdisk_up{
    my($path)= @_;

    if($path =~ /^h\d+c\d+t\d+l\d+$/){
        &scsiBringUp($path);
    }
    else{
        &error(1, "Unknown scsi disk: $path");
    }
}

#
sub scsiBringUp{
    my(@paths)= @_;

    if($paths[0]){
        my(@db)= &scsiGetSimpleDB($SCSI_DB_SERIAL2INFO_H,
                                  $SCSI_DB_PATH2SERIAL_H);
        my($loop)= (($db[0]{$db[1]->{$paths[0]}}->[4]) ?
                    ($db[0]{$db[1]->{$paths[0]}}->[4]) :
                    $_ftdisk_bringup_timeout);
        foreach (@paths){
            if(/^h(\d+)c(\d+)t(\d+)l(\d+)$/){
                &logger("bringing up $_");
                &invokeScsiCmd("add-single-device $1 $2 $3 $4");
            }
        }
        $SIG{'INT'}= 'DEFAULT';
        foreach (1 .. $loop){
            if(&scsiExtractLivePaths(@paths) > 0){
                $SIG{'INT'}= 'IGNORE';
                return;
            }
            sleep(1);
        }
        &error(1, "Bringing up reached timeout!");
    }
}

##
sub ftdisk_down{
    my($path)= @_;

    if($path =~ /^h\d+c\d+t\d+l\d+$/){
        &scsiBringDown($path);
    }
    else{
        &error(1, "Unknown scsi disk: $path");
    }
}

#
sub scsiBringDown{
    my(@paths)= @_;

    foreach (@paths){
        if(/^h(\d+)c(\d+)t(\d+)l(\d+)$/){
            &logger("bringing down $_");
            &invokeScsiCmd("remove-single-device $1 $2 $3 $4");
        }
    }
    $SIG{'INT'}= 'DEFAULT';
    foreach (1 .. $_ftdisk_bringdown_timeout){
        if(&scsiExtractLivePaths(@paths) == 0){
            $SIG{'INT'}= 'IGNORE';
            return;
        }
        sleep(1);
    }
    &error(1, "Bringing down reached timeout!");
}

##
sub ftdisk_repair{
    my($slt, $force)= @_;

    if(!&scsiSlot2LivePaths($slt)){
        &error(1, "Unknown disk: slot=$slt");
    };
    my($dn_to)= &ftdiskSlot2Devname($slt);

    my($device_path)= &scsiDevname2HWpath($dn_to);
    my($err) = &isMtbf($device_path);
    if($err){
        &error(1, "Can not repair the disk: slot=$slt\n        ($err)");
    }

    unless($force){$force= 0;}
    &logger("repair($force) $slt");
    &raidHotRepair_a($force);

    my($slt2)= &raidGetSlotBuddy($slt);
    if(&scsiSlot2LivePaths($slt2) == 0){
        &error(1, "There is no buddy disk: slot=$slt");
    }
    my($dn_from)= &ftdiskSlot2Devname($slt2);

    sleep(5);  # wait for repair

    my(%mdstat)= &getMdstat();
    foreach $md (keys %mdstat){
        my($pos)= &getPosOfMdstat($dn_to, @{$mdstat{$md}{'devices'}});
        my($pos2)= &getPosOfMdstat($dn_from, @{$mdstat{$md}{'devices'}});
        next if(($pos < 0) and ($pos2 < 0));
        if($mdstat{$md}{'sync_action'} ne "idle"){
            &error(1, "Currently syncing disks: slot=$slt,$slt2");
        }
    }

    my($cmp)= &diskCompare($dn_from, $dn_to);
    if($cmp != 0){
        if(!($force or $cmp == -2)){
            if($cmp == 1){
                &error(2, "Different disk structure: slot=$slt,$slt2"); 
            }
            &error(2, "Different disk partitions: slot=$slt,$slt2"); 
        }
    }
    if($force or $cmp == -2){  #always if $force
        if(&copyPartitionTable($dn_from, $dn_to, 1)){
            &error(1, "Failed to copy disk partitions: slot=$slt");
        }
        system("/bin/sync");
        system("/bin/sync");
    }
    if(&copyMBLoader($dn_from, $dn_to)){
        &error(1, "Failed to copy MBR: slot=$slt");
    }
    if(&raidHotRepair_b($dn_from, $dn_to) == -1){
        &error(1, "Failed to repair the disk: slot=$slt");
    }

}

##
sub ftdisk_repair_all{
    my($force)= @_;

    &raidHotRepair_a($force);
    foreach $slt (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16){
        next if(&scsiSlot2LivePaths($slt) == 0);
        my($slt2)= &raidGetSlotBuddy($slt);
        if(&scsiSlot2LivePaths($slt2) == 0){
            &warning("There is no buddy disk: slot=$slt");
            next;
        }
        my($dn_to)= &ftdiskSlot2Devname($slt);
        my($dn_from)= &ftdiskSlot2Devname($slt2);

        my($cmp)= &diskCompare($dn_from, $dn_to);
        if($cmp != 0){
            &warning("Different structure disks: slot=$slt,$slt2");
            next;
        }
        if(&raidHotRepair_b($dn_to, $dn_from) == -1){
            &warning("Failed to repair the disk: slot=$slt");
        }
    }
}

##
sub raidHotRepair_a{
    my($force)= @_;

    my($cnt)= 0;
    my(%mdstat)= &getMdstat();
    foreach $md (keys %mdstat){
        my($st)= (&getStatusFromMdstat($md, %mdstat))[0];
        if($st < 0){
            ++$cnt;
            if($st == -1){  #FATAL
                if(&isMounted($md)){
                    &warning("Unrecoverable error: /dev/$md");
                }
                else{
                    system("/sbin/mdadm --stop /dev/$md"); # DO NOT CHECK ERROR
                }
            }
            elsif($st == -2){  #SIMPLEX
                foreach (@{$mdstat{$md}{'devices'}}){
                    if(/^(sd[a-p]\d+)\[\d+\]\(F\)$/){  #fail?
                        system("/sbin/mdadm /dev/$md --fail /dev/$1 2>/dev/null");
                        system("/sbin/mdadm /dev/$md --remove /dev/$1");
                    }
                }
            }
        }
    }

    return $cnt;
}

##
sub raidHotRepair_b{
    my($dn_from, $dn_to)= @_;

    sleep(5);  # wait for disk-manager
    my($cnt)= 0;
    %mdstat= &getMdstat();
    foreach $md (keys %mdstat){
        my(@a)= @{$mdstat{$md}{'devices'}};
        my($pos)= &getPosOfMdstat($dn_from, @a);
        next if($pos == -1);
        next if($mdstat{$md}{'cond'}[$pos] ne 'U');
        my($num)= $a[$pos];
        next unless($num =~ s/^.*?(\d+).*$/$1/);
        $pos= &getPosOfMdstat("$dn_to$num", @a);
        next if($pos >= 0);  #already paired or under recovering
        if($mdstat{$md}{'num'} > 2){
            &warning("Some illegal members exist in the device: /dev/$md");
            return -1;
        }
        else{
            foreach (@{$mdstat{$md}{'devices'}}){
                if(/^(sd[a-z]+\d+)\[\d+\]\(F\)$/){
                    &warning("Some illegal members exist in the device: /dev/$md");
                    return -1;
                }
            }
        }
        if(system("/sbin/mdadm /dev/$md --add /dev/$dn_to$num")){
            return -1;
        }
        ++$cnt;
    }

    return $cnt;
}

##
sub diskCompare{
    my($dn1, $dn2)= @_;

    my(%ptab1)= &getPartitionTables($dn1);
    my(%ptab2)= &getPartitionTables($dn2);

    foreach $key ("heads", "sectors", "cylinders"){
        return 1 if($ptab1{$key} != $ptab2{$key});
    }
    CHECK: {
        my(@a1)= @{$ptab1{'device'}};
        if(@a1 == 0){
            return -1;
        }
        my(@a2)= @{$ptab2{'device'}};
        if(@a2 == 0){
            return -2;
        }
        last if(@a1 != @a2);
        my($num)= scalar @a1;
        while($num-- > 0){
            if($a1[$num]){
                last unless($a2[$num]);
                last CHECK if($a1[$num][2] != $a2[$num][2]);  #Start
                last CHECK if($a1[$num][3] != $a2[$num][3]);  #End
                last CHECK if($a1[$num][4] != $a2[$num][4]);  #Blocks
                last CHECK if($a1[$num][5] ne $a2[$num][5]);  #Id
            }
            else{
                last if($a2[$num]);
            }
        }

        return 0;
    }

    return 2;
}

###
sub ftdisk_create{
    my($slt, $force, $swap, $sizes, $label)= @_;

    &check_arg($slt);
    if(!&scsiExtractLivePaths(&scsiSlot2Path($slt))){
        &error(1, "Unknown disk: slot=$slt");
    }
    &logger("create($force) $slt swap:$swap size:$sizes label:$label");
    my($dn)= &scsiSlot2Devname($slt);
    my($slt2)= &raidGetSlotBuddy($slt);
    my($dn2)= &scsiSlot2Devname($slt2);

    my(@asizes)= split(/,/, "0,$sizes");
    foreach $num (1 .. $#asizes){
        if($asizes[$num] == -1){
            $#asizes= $num;
            last;
        }
        if($asizes[$num] !~ /^\d*$/){
            &error(1, "Invalid partition size: $asizes[$num]");
        }
    }

    if(&isRaided($dn) or &isMounted($dn)){
        &error(1, "Already used disk: slot=$slt");
    }
    if(&isRaided($dn2) or &isMounted($dn2)){
        &error(1, "Already used disk: slot=$slt2");
    }
    my($device_path) = &scsiDevname2HWpath($dn);
    my($err) = &isMtbf($device_path);
    if($err){
        &error(1, "Can not create the disk: slot=$slt\n        ($err)");
    }
    $device_path = &scsiDevname2HWpath($dn2);
    $err = &isMtbf($device_path);
    if($err){
        &error(1, "Can not create the disk: slot=$slt2\n        ($err)");
    }

    if($#asizes == 0){
        my($cmp)= &diskCompare($dn, $dn2);
        if($cmp == -1){
            &error(1, "No disk partitions: slot=$slt");
        }
        unless($force){
            if($cmp == 1){
                &error(2, "Different disk structure: slot=$slt,$slt2"); 
            }
            if($cmp == 2){
                &error(2, "Different disk partitions: slot=$slt,$slt2"); 
            }
            last if($cmp == 0);
        }
    }
    else{
        #
        # If sfdisk are not with '-H255' and '-S63',
        # this should NOT be required.
        #  (July 15, 2002)
        #
#        if(&makePartitionTable($dn, 'fd', @asizes) == -1){
        if(&makePartitionTable64($dn, 'fd', @asizes) == -1){
            &error(1, "Failed to make disk partitions: slot=$slt");
        }
        system("/bin/sleep 1");
        system("/bin/sync");
        system("/bin/sync");
    }
    if(&copyPartitionTable($dn, $dn2, 0)){
        &error(1, "Failed to copy disk partitions: slot=$slt2");
    }
    system("/bin/sleep 1");
    system("/bin/sync");
    system("/bin/sync");

    if(&raidHotStart(1, 0, $swap, $label, $_default_md_bitmap, $dn, $dn2) == -1){
        &error(1, "Failed to start raid disks: slot=$slt,$slt2");
    }
}

##
sub raidHotStart{
    my($level, $md_seek, $swap, $label, $bitmap, @devs)= @_;
    my($ret);

    my(%tabs, @ptab, $create_num);
    if($level == 1){
        %tabs= &getPartitionTables($devs[0]);
        @ptab= @{$tabs{'device'}};
        $create_num= $#ptab;
    }
    elsif($level == 0){
        $create_num= 1;
    }
    else{
        return -1;
    }
    
    foreach $num (1 .. $create_num){
        if($level == 1){
            my(@a)= @{$ptab[$num]};
            next if($a[5] ne 'fd');
        }
        my($md)= &getNewMdname($md_seek);
        my($create_opt)= sprintf("%s --level=%d --raid-devices=%d", 
                             (($bitmap) ? "--bitmap=$bitmap" : ""), 
                             $level, 
                             $#devs+1);
        foreach(@devs){
            $create_opt= sprintf("$create_opt /dev/$_$num") if($level == 1);
            $create_opt= sprintf("$create_opt /dev/$_") if($level == 0);
        }
        $ret = system("/sbin/mdadm --create /dev/$md $create_opt");
        if($ret){
            if($level == 0){
                $ret = isExist($md);
                if($ret){
                    $ret = system("/sbin/mdadm --stop /dev/$md");
                }
                return -1;
            }
            sleep(1);
            $ret = system("/sbin/mdadm --create /dev/$md $create_opt");
            if($ret){
                return -1;
            }
        }
        if($num == $swap){
            system("/sbin/mkswap /dev/$md");
        }
        else{
            my($opt)= "-t ext4";
            if($label){
                $opt .= ($#ptab == 1) ? " -L $label" : " -L ${label}_s$num";
            }
            system("/sbin/mkfs $opt /dev/$md");
        }
    }
    &remakeRaidConfig();
    $ret = &remakeBootImg();

    return 0;
}

###
sub ftdisk_remove{
    my($slt)= @_;

    &check_arg($slt);
    if(!&scsiSlot2LivePaths($slt)){
        &error(1, "Unknown disk: slot=$slt");
    };
    
    &logger("remove $slt");

    my($dn)= &scsiSlot2Devname($slt);
    my($cnt, $solo_p, $sync_p, $striped_p)= &getMdstatNotice($dn);
    if($cnt > 0){
        if(@{$solo_p} > 0){
            &error(1, "Cannot remove the disk with solo slice(s)");
        }
        if(@{$sync_p} > 0){
            &error(1, "Cannot remove the disk with syncing slice(s)");
        }
    }
    if($cnt > 0){
        if(&raidHotRemove($dn) == -1){
            &error(1, "Failed to remove the disk correctly: slot=$slt");
        }
    }
    #&scsiBringDownSlot($slt);
}

##
sub raidHotRemove{
    my($dn)= @_;
    my($ret);

    my(%mdstat)= &getMdstat();
    foreach $md (keys %mdstat){
        foreach (@{$mdstat{$md}{'devices'}}){
            if(/^($dn\d+)\[\d+\].*$/){
                #DO NOT CHECK "(F)" TO SKIP "--fail"
                if(system("/sbin/mdadm /dev/$md --fail /dev/$1 2>/dev/null") or
                   system("/sbin/mdadm /dev/$md --remove /dev/$1")){
                        $ret = &remakeBootImg();
                        return -1;
                }
            }
        }
    }
    &remakeRaidConfig();
    $ret = &remakeBootImg();

    return 0;
}

##
sub getMdstatNotice{
    my($dn)= @_;

    my($cnt)= 0;
    my(@solo, @sync, @striped);
    my(%mdstat)= &getMdstat();

    foreach $md (keys %mdstat){
        my($pos)= &getPosOfMdstat($dn, @{$mdstat{$md}{'devices'}});
        next if($pos < 0);
        ++$cnt;
        if($mdstat{$md}{'sync_action'} ne "idle"){
            $sync[$#sync+1]= $md;
        }
        unless(&hasRaidBuddy($pos, @{$mdstat{$md}{'cond'}})){
            $solo[$#solo+1]= $md;
        }
        if(&isRaided($md)){
            $striped[$#striped+1]= $md;
        }
    }

    return ($cnt, [@solo], [@sync], [@striped]);

    sub hasRaidBuddy{
        my($pos, @cond)= @_;
        my($i)= 0;          ###### CHECK for => foreach
        my($cnt) = 0;

        if($cond[0] eq 'U'){
            $cnt++;
        }
        if($cond[1] eq 'U'){
            $cnt++;
        }
        if($cond[$pos] eq 'U'){
            $cnt--;
        }

        return $cnt;
    }
}

###
sub ftdisk_destroy{
    my($slt)= @_;
    my($ret);

    &check_arg($slt);
    &logger("destroy $slt");
    my($slt2)= &raidGetSlotBuddy($slt);
    my($dn)= &scsiSlot2Devname($slt);
    my($dn2)= &scsiSlot2Devname($slt2);

    #my($up, $up2);
    if(&scsiSlot2LivePaths($slt) > 0){
        if(@{(&getMdstatNotice($dn))[3]} > 0 or &isMounted($dn)){
            &error(1, "Currently used disk(s)");
        }
        if(@{(&getMdstatNotice($dn))[2]} > 0){
            &error(1, "Currently syncing disk(s)");
        }
        #$up= 1;
    }
    if(&scsiSlot2LivePaths($slt2) > 0){
        if(@{(&getMdstatNotice($dn2))[3]} > 0 or &isMounted($dn2)){
            &error(1, "Currently used disk(s)");
        }
        if(@{(&getMdstatNotice($dn2))[2]} > 0){
            &error(1, "Currently syncing disk(s)");
        }
        #$up2= 1;
    }

    my(%mdstat)= &getMdstat();
    foreach $md (keys %mdstat){
        my($pos)= &getPosOfMdstat($dn, @{$mdstat{$md}{'devices'}});
        my($pos2)= &getPosOfMdstat($dn2, @{$mdstat{$md}{'devices'}});
        next if(($pos < 0) and ($pos2 < 0));
        if(&raidHotStop($md) == -1){
            $ret = &remakeBootImg();
            &error(1, "Failed to stop disks: slot=$slt,$slt2");
        }
        foreach (@{$mdstat{$md}{'devices'}}){
            if(/^(sd\w*).*/){
                system("/sbin/mdadm --zero-superblock /dev/$1");
            }
        }
    }
    #&scsiBringDownSlot($slt) if($up);
    #&scsiBringDownSlot($slt2) if($up2);
    $ret = &remakeBootImg();
}

##
sub raidHotStop{
    my($md)= @_;

    if(system("/sbin/mdadm --stop /dev/$md")){
        return -1;
    }
    &remakeRaidConfig();
    return 0;
}

###
sub ftdisk_autorepair{
    my($sub, $interval)= @_;
    
    my($raidrepair)= '/etc/cron.d/raidrepair';
    my($on)= (-f $raidrepair);
    
    if(!$sub and !$interval){
        printf "%s\n", ($on) ? 'on' : 'off';
    }
    elsif($sub eq 'on'){
        if(!$on and !$interval){
            if(!open(ON, "> $raidrepair")){
                &error(1, "Cannot open the file: $raidrepair");
            }
            print ON "*/${_default_auto_repair_interval} * * * * root ${FTDISK_BINDIR}/${BASENAME} repair\n";
            close(ON);
        }
        elsif(($interval =~ /^\d+$/) and ($interval >= 1)
                                  and ($interval <= 59)){
            if(!open(ON, "> $raidrepair")){
                &error(1, "Cannot open the file: $raidrepair");
            }
            print ON "*/${interval} * * * * root ${FTDISK_BINDIR}/${BASENAME} repair\n";
            close(ON);
        }
        else{
            &check_arg();
        }
        chmod(0644, $raidrepair);
    }
    elsif($sub eq 'off'){
        unlink($raidrepair) if($on);
    }
    else{
        &check_arg();
    }
}

###
sub ftdisk_scrubbing{
    my($sub, $interval)= @_;

    unless(-f $_scrubmds_cron_path){
        if(!open(DEFAULT, "> $_scrubmds_cron_path")){
            &error(1, "Cannot open the file: $_scrubmds_cron_path");
        }
        print DEFAULT "# execute the disk scrubbing on schdule.\n";
        print DEFAULT $_scrubmds_default_schedule;
        close(DEFAULT);
        chmod(0644, $_scrubmds_cron_path);
    }
    chomp(my($cron)= `/bin/grep $_scrubmds_script_path $_scrubmds_cron_path 2> /dev/null`);
    my($on)= ($cron !~ /#/);
    
    if(!$sub and !$interval){
        printf "%s\n", ($on) ? "on [ $cron ]" : "off";
    }
    elsif($sub eq 'on'){
        if(!$interval){
            my($expression)= "\"s,#\\(.*$_scrubmds_script_path\\),\\1,g\"";
            if(`/bin/sed -i -e $expression $_scrubmds_cron_path`){
                &error(1, "Cannot open the file: $_scrubmds_cron_path");
            }
        }
        elsif($interval =~ /^((\d|\w|\,|\/|\-|\*)+\s+){4}(\d|\w|\,|\/|\-|\*)+$/){
            my($expression)= "\"s,.*\\($_scrubmds_script_path\\),${interval} root \\1,g\"";
            if(`/bin/sed -i -e $expression $_scrubmds_cron_path`){
                &error(1, "Cannot open the file: $_scrubmds_cron_path");
            }
        }
        else{
            &check_arg();
        }
    }
    elsif($sub eq 'off'){
        if($on){
            my($expression)= "\"s,\\(.*$_scrubmds_script_path\\),#\\1,g\"";
            if(`/bin/sed -i -e $expression $_scrubmds_cron_path`){
                &error(1, "Cannot open the file: $_scrubmds_cron_path");
            }
        }
    }
    else{
        &check_arg();
    }
}

#
sub ftdiskSlot2Devname{
    my($slt)= @_;

    if(($slt < 1) or ($slt > 16)){
        &error(1, "<slot> must be 1 - 16");
    }
    my($dn)= &scsiSlot2Devname($slt);
    if(!$dn){
        &error(1, "Cannot get device name: slot=$slt");
    }

    return $dn;
}

#
sub scsiSlot2LivePaths{
    my($slt)= @_;

    return &scsiExtractLivePaths(&scsiSlot2Path($slt));
}


##
sub getStatusFromMdstat{
    my($mdname, %mdstat)= @_;

    if($mdstat{$mdname}{'recovery'}){
        return (1, "RECOVERY($mdstat{$mdname}{'recovery'})");
    }
    elsif($mdstat{$mdname}{'resync'}){
        my($disp);
        if($mdstat{$mdname}{'sync_action'} eq 'resync'){
            $disp= "RESYNC";
        }
	    elsif($mdstat{$mdname}{'sync_action'} eq 'check'){
            $disp= "CHECK";
        }
        elsif($mdstat{$mdname}{'sync_action'} eq 'repair'){
            $disp= "REPAIR";
        }
        else{
            $disp= "RESYNC";
        }
        if($mdstat{$mdname}{'resync'} =~ /^([\d\.%]+)$/){
            $disp .= "($1)";
        }
        return (1, $disp);
    }
    else{
        my($cnt)= 0;
        if($mdstat{$mdname}{'stat'} eq 'active'){
            return (0, 'ACTIVE') if($mdstat{$mdname}{'level'} == 0);
            my(@a)= @{$mdstat{$mdname}{'cond'}};
            foreach (@{$mdstat{$mdname}{'devices'}}){
                ++$cnt if((/^[\w\d]+\[(\d+)\]$/) and $a[$1] eq 'U');  # not (F) and 'U'
            }
        }
        if($cnt == 0){
            return (-1, 'ERROR');
        }
        elsif($cnt == 1){
            $cnt = &checkDiskStatus($mdname);
            if($cnt == 0){
                return (-1, 'ERROR');
            }
            return (-2, 'SIMPLEX');
        }
        else{
            return (0, 'DUPLEX');
        }
    }
}

sub checkDiskStatus{
    my($mddevice) = @_;
    my($slinknum) = 0;
    if(open(FCOUNT, "/bin/ls -1 /sys/block/$mddevice/slaves | wc |")){
        while(<FCOUNT>){
            if(/^\s*\d*\s*(\d*).*$/){
                $slinknum = $1;
                last;
            }
        }
        close(FCOUNT);
    }
    if($slinknum == 1){
        if(open(CHKLINK, "/usr/bin/file /sys/block/$mddevice/slaves/* |")){
            while(<CHKLINK>){
                if(/^.*broken.*$/){
                    $slinknum = 0;
                    last;
                }
            }
            close(CHKLINK);
        }
    }
    return $slinknum;
}

###
sub ftdisk_raidstat{
    &raidPrintStat(@_);
}

##
sub raidPrintStat{
    my($label, $level)= @_;

    my(%mdstat)= &getMdstat();
    if(%mdstat > 0){
        foreach $md (sort devcmp keys %mdstat){
            next if($level ne '' and $mdstat{$md}{'level'} != $level);
            my($lab)= &devName2Label($md);
            printf("%-6s", $md);
            printf("%-15s ", &devName2Partition($md));
            printf("%-15s ", (($lab) ? ("($lab)") : ("( - )")) ) if($label);
            printf("%-15s ", (&getStatusFromMdstat($md, %mdstat))[1]);
            foreach $dn (sort @{$mdstat{$md}{'devices'}}){
                &printMember($mdstat{$md}{'level'}, $dn, @{$mdstat{$md}{'cond'}});
            }
            print "\n";
        }
    }

    #
    sub printMember{
        my($lv, $dn, @conds)= @_;
        if($dn =~ /^(sd[a-p]|md)(\d+)+\[(\d+)\](\((F)\))?/){
            if($lv eq '0'){
                printf("%s ", "$1$2");
            }
            else{
                printf("%s(%d)%-6s ", 
                    (($5) ? 'F' : ($conds[$3] eq 'U') ? ' ' : 'R'),
                    &scsiDevname2Slot($1),
                    "$1$2");
            }
        }
    }

    #
    sub devcmp{
        my(@a)= split(/(\d+)$/, $a);
        my(@b)= split(/(\d+)$/, $b);
        ($a[0] cmp $b[0]) or $a[1] <=> $b[1];
    }
}

###
sub ftdisk_striping{
    my($name, $create, $delete)= @_;

    my($ret);
    my(@devs);
    my(%mdstat)= &getMdstat();
    
    if($create ne '' and $delete eq ''){
        my($seek_start)= 0;
        if($name =~ /^md(\d)+$/){
            my($seek_start)= $2;
            if($name ne &getNewMdname($seek_start)){
                &error(1, "Already used the device name: $name");
            }
        }
        my($used_devs, $unkown_devs, $invalid_value);
        foreach $num (split(/,/, $create)){
            if(&isMounted("md$num") or &isRaided("md$num")){
                $used_dev .= "md$num ";
            }
            if($mdstat{"md$num"}{'level'} ne '1'){
                $unkown_devs .= "md$num ";
            }
            if($num !~ /^\d+$/){
                $invalid_value .= "$num ";
            }
            $devs[$#devs+1]= "md$num";
        }
        &error(1, "Invalid option value: $invalid_value") if($invalid_value);
        &error(1, "Unknown RAID1 device: $unkown_devs")   if($unkown_devs);
        &error(1, "Currently used device: $used_dev")     if($used_dev);

        if(&raidHotStart(0, $seek_start, 0, 0, undef, @devs) == -1){
            &error(1, "Failed to start striping array");
        }
    }
    elsif($create eq '' and $delete ne ''){
        if($name =~ /^md\d+$/){
            if($mdstat{"$name"}{'level'} ne '0'){
                &error(1, "Unknown RAID1+0 device: $name");
            }
            elsif($mdstat{"$name"}{'level'} eq '0'){
                foreach(@{$mdstat{$name}{'devices'}}){
                    &error(1, "Unknown RAID1+0 device: $name") if(/^sd[a-z]+.*/);
                }
            }
            if(&isMounted($name)){
                &error(1, "Currently used device: $name");
            }
            if(&raidHotStop($name) == -1){
                &error(1, "Failed to delete striping array");
            }
            foreach(@{$mdstat{$name}{'devices'}}){
                if(/^(md\d+).*/){
                    system("/sbin/mdadm --zero-superblock /dev/$1");
                }
            }
            $ret = &remakeBootImg();
        }
        else{
            &check_arg($name);
        }
    }
    elsif($create eq '' and $delete eq ''){
        &raidPrintStat(1, 0)
    }
    else{
        &check_arg();
    }
}

###
sub ftdisk_raidopt{
    my($md, $fast_resync, $speed_min, $speed_max)= @_;

    &check_arg($md);
    if($fast_resync =~ /^(on|off)$/){
        if(&setMdFastSync($md, $fast_resync)){
            &error(1, "Failed to set the parameter for $md: fast_resync=$fast_resync");
        }
    }
    if($speed_min =~ /^\d+$/){
        if(&setMdSyncSpeed($md, "min", $speed_min)){
            &error(1, "Failed to set the parameter for $md: resync_speed_min=$speed_min");
        }
    }
    if($speed_max =~ /^\d+$/){
        if(&setMdSyncSpeed($md, "max", $speed_max)){
            &error(1, "Failed to set the parameter for $md: resync_speed_max=$speed_max");
        }
    }
}

##
sub scsiBringUpSlot{
    my($slt)= @_;

    my(@paths)= &scsiSlot2Path($slt);

    &scsiBringUp(@paths);
    return 1;
}

##
sub scsiBringDownSlot{
    my($slt)= @_;

    my(@paths)= &scsiExtractLivePaths(&scsiSlot2Path($slt));
    if(@paths == 0){
        return 0;
    }

    &scsiBringDown(@paths);
    return 1;
}
