#!/usr/bin/perl -w

# IBM "vscsisadmin": IBMVSCSIS driver configuration application
#
# Copyright (c) 2004, 2005 International Business Machines.
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# Author(s): Ryan S. Arnold <rsa@us.ibm.com>
#
# This application provides a method to configure, start and stop a vscsi
# server with minimum user interaction.
#
# For further details please reference man page vscsisadmin.8

use strict;
use File::Basename;

use vars '$app_name';
$app_name = "vscsiadmin";

use vars '$app_version';
$app_version = "1.0.0";

use vars '$driver';
$driver = "ibmvscsis";

use vars '$sysfs_node_name';
$sysfs_node_name = "ibmvscsis";

use vars '$noisy';
$noisy = 0;

use vars '$warn';
$warn = 0;

use vars '$stop_path';
$stop_path = '';

use vars '$param_adapter';
my $param_adapter = '';

use vars '$param_bus';
my $param_bus = '';

use vars '$param_target';
my $param_target = '';

use vars '$config_file';
$config_file = "ibmvscsis.conf";

use Getopt::Long;

# Simply output the version information about this helper application.
sub versioninfo {
    print "IBM ", $app_name, " version ",$app_version,"\n";
    print "Copyright (C) 2004, IBM Corporation.\n";
    print "Author(s) Ryan S. Arnold\n";
}

sub verboseprint( $ ) {
    my $output = shift;
    if($noisy > 1) {
        print "$output";
    }
}

sub warnprint( $ ) {
    my $output = shift;
    if($warn > 0 ) {
        print "$output";
    }
}

sub statusprint( $ ) {
    my $output = shift;
    if($noisy > 0 ) {
        print "$output";
    }
}

sub errorprint( $ ) {
    my $output = shift;
    print "$output";
}

# Help information text displayed to the user when they invoke the script with
# the -h tag.
sub helpinfo {
    print "Usage: ", $app_name, " [options]\n";
    print "Options:\n";
    print " -start";
    print "\t\t$app_name will read the $config_file config file and start\n";
    print "\t\t$driver based upon the information contained therein.\n";
    print "\n";
    print "\t\tSupports the following extended commands for selective start:\n";
    print "\n";
    print "\t\t\t-adapter=<number> -bus=<number> -target=<number>\n";
    print "\t\t\t-adapter=<number> -bus=<number>\n";
    print "\t\t\t-adapter=<number>\n";
    print "\n";
    print " -stop";
    print "\t\t$app_name will navigte the $driver sysfs directory and\n";
    print "\t\tterminate the target and bus configurations found therein.\n";
    print "\n";
    print "\t\tSupports the following extended commands for selective stop:\n";
    print "\n";
    print "\t\t\t-adapter=<number> -bus=<number> -target=<number>\n";
    print "\t\t\t-adapter=<number> -bus=<number>\n";
    print "\t\t\t-adapter=<number>\n";
    print "\t\t\t-path=<path to target, bus or adapter>\n";
    print "\n";
    print " -status";
    print "\tOutput a table of ibmvscsis adapter information that highlights\n";
    print "\t\tthe state of the running $driver and denotes the activity\n";
    print "\t\tstate of each adapter:bus:target.\n";

    print "\n";
    print " -help";
    print "\t\tOutput this help text.\n";
    print "\n";
    print "\t\tSee manual page vscsisadmin (8) for further information.\n";
    print "\n";
    print " -noisy";
    print "\t\tA stackable directive that changes the script\n";
    print "\t\tverbosity.  The default noise level of '0' makes $app_name\n";
    print "\t\tsilent on success but verbose on error.  A noise level\n";
    print "\t\tof '1' will output additional success information.  A\n";
    print "\t\tnoisy level of '2' will output $app_name script trace\n";
    print "\t\tinformation.\n";
    print "\n";
    print " -version\tOutput the ", $app_name, " script's version number.\n";
}

# Verify that the systools package is installed.  This package is required for
# using this scripts because this script uses systools to make sysfs queries.
# It then strips relevant data from the systool queries for use in additional
# features.
sub findsystools() {
    #--------------- Look for the systool application -----------------------
    local *WHICH;
    open WHICH, "which systool|";
    my @whicharray = <WHICH>;
    my $whichline = join("\n", @whicharray);

    if ($noisy > 1) {
        print "$app_name\: looking for \"systool\" application.\n";
    }

    if ($whichline eq "") {
        print "$app_name\: systool is not installed or not in the path?\n";
        print "$app_name\: systool is required by the $app_name script.\n";
        return -1;
    }
    if ($noisy > 1) {
        print "$app_name\: found \"systool\" at ", $whichline;
    }

    return 0;
}

# If this function returns a non-empty string then the module is loaded and
# the module's sysfs entry is located at the location given by the return
# value.
sub is_driver_installed() {
    my $val = "";
    my $local_driver = "";
    my $driver_path = "";

    verboseprint("$app_name: is $driver loaded.\n");

    local *SYSTOOL;
    open SYSTOOL, "systool -b vio -D -p|" or die "systool:  $!";

    while (my $line = <SYSTOOL>) {
        ($val) = ($line =~ /^\s*Driver = "(.*)"\s*$/);
        if(defined $val) {
            $local_driver = $1;
            $driver_path = "";
            next;
        }
        ($val) = ($line =~ /^\s*Driver path = "(.*)"\s*$/);
        if(defined $val) {
            $driver_path = $1;
            # grab only the Driver,Driver path pair for $driver
            if ($local_driver eq $driver) {
                verboseprint("$app_name: verified that $driver is loaded at $driver_path\.\n");
                return $driver_path;
            }
            next;
        }
    }

    errorprint("$app_name: $driver is not loaded.\n");
    return "";
}

sub status_target( $ $ $ ) {
    my $bus_path = shift;
    my $target = shift;
    my $bus = shift;
    my $target_path = "$bus_path/$target";
    my $output = "$target:";

    if (! -e "$target_path/active") {
        print "WARNING didn't find $target_path/active attribute.\n";
        return;
    }

    if (! -e "$target_path/device") {
        print "WARNING didn't find $target_path/device attribute.\n";
        return;
    }

    local *DEVICE;
    open DEVICE, "cat $target_path/device |";
    my @data = <DEVICE>;
    close DEVICE;
    my $device = $data[0];
    chomp($device);

   # If there was no device entry we can just bail.
    if ($device eq "") {
        return;
    }

    local *ACTIVE;
    open ACTIVE, "cat $target_path/active |";
    my @activedata = <ACTIVE>;
    close ACTIVE;
    my $active = $activedata[0];
    chomp($active);
    if ($active) {
        $active = " *";
    } else {
        $active = "";
    }
    $output .= "$device";

    local *LOSETUP;
    open LOSETUP, "losetup -a |";
    my @entries = <LOSETUP>;
    close LOSETUP;

    my $loop_back_device = "";
    foreach my $entry (@entries) {
        chomp($entry);
        if ($entry =~ /^$device: \[\w+\]:\w+ \(([A-Za-z0-9\-\_\/]+)\)/) {
            $loop_back_device = $1;
        }
    }

    if ($loop_back_device ne "") {
        $output .= "-->$loop_back_device";
    } else {
        $output .= "";
    }
    print "$active\t$bus:$output\n";
}

sub status_bus( $ $ ) {
    my $adapter_path = shift;
    my $bus = shift;
    my $bus_path = "$adapter_path/$bus";

    local *ATTRIB_DIRHANDLE;
    opendir(ATTRIB_DIRHANDLE, "$bus_path");
    my @target_list = sort readdir ATTRIB_DIRHANDLE;
    closedir ATTRIB_DIRHANDLE;

    my $targetnumber = "";
    foreach my $targetentry (@target_list) {

        if ($targetentry =~ /^target(\w+)/) {
            $targetnumber = $1;
            status_target($bus_path, $targetentry, $bus);
        }

    }

}

sub status_adapter( $ $ ) {
    my $driver_path = shift;
    my $adapter = shift;
    my $adapter_path = "$driver_path/$adapter";

    local *ATTRIB_DIRHANDLE;
    opendir(ATTRIB_DIRHANDLE, "$adapter_path");
    my @bus_list = sort readdir ATTRIB_DIRHANDLE;
    closedir ATTRIB_DIRHANDLE;

    print "$driver:$adapter\n";

    my $busnumber = "";
    foreach my $busentry (@bus_list) {

        if ($busentry =~ /^bus(\w+)/) {
            $busnumber = $1;
            status_bus($adapter_path, $busentry);
        }

    }
}

sub status() {
    #cruise through sysfs looking for configurations with device entries
    #denote whether they are active or inactive

    my $driver_path = is_driver_installed();
    if ($driver_path eq "") {
        print "$driver is not loaded.\n";
        return -1;
    }

    local *ATTRIB_DIRHANDLE;
    opendir(ATTRIB_DIRHANDLE, "$driver_path");
    my @adapter_list = sort readdir ATTRIB_DIRHANDLE;
    closedir ATTRIB_DIRHANDLE;

    foreach my $adapter (@adapter_list) {

        # Only operate on vscsi server adapters
        if (-e "$driver_path/$adapter/name") {
            # maybe we should check the value of name and make sure it is
            # v-scsi-host?
            status_adapter($driver_path, $adapter);
        }
    }

}

sub find_config_file() {

    local *DIRHANDLE;
    opendir(DIRHANDLE, "/etc");
    my @allfiles = readdir DIRHANDLE;
    closedir DIRHANDLE;

    my $dirlist = join(":", @allfiles);

    if ($dirlist !~ /:$config_file/) {
        return 0;
    }
    return 1;
}

sub find_adapter( $ ) {
    my $adapter_number = shift;
    my $local_driver;
    my $local_device;
    my $local_device_path;
    my $val = "";

    local *SYSTOOL;
    open SYSTOOL, "systool -b vio -D -p|" or die "systool:  $!";

    while (my $line = <SYSTOOL>) {
        ($val) = ($line =~ /^\s*Driver = "(.*)"\s*$/);
        if(defined $val) {
            $local_driver = $1;
            $local_device = "";
            $local_device_path = "";
            next;
        }

        ($val) = ($line =~ /^\s*Device = "(.*)"\s*$/);
        if(defined $val) {
            $local_device = $1;
            #reset with each Device or else we might pick up a stale one from
            #a previous device.
            $local_device_path = "";
            next;
        }

        ($val) = ($line =~ /^\s*Device path = "(.*)"\s*$/);
        if(defined $val) {
            $local_device_path = $1;

            # Since this block is only true if the previous block was for the
            # right driver this can guarentee that all adapters that are
            # found are valid vscsis host adapters.
            if (($local_driver eq $driver)
                and ($local_device eq $adapter_number)) {
                close SYSTOOL;
                return $local_device_path;
            }
            next;
        }
    }
    close SYSTOOL;
    return "";
}

# This function assumes a valid adapter path
sub find_bus( $ $ ) {
    my $adapter_path = shift;
    my $bus = shift;

    my $middle = "|  *";

    # $adapter_path should ne ""
    if($adapter_path eq "") {
        errorprint("$app_name: find_bus invoked with empty adapter_path.\n");
        return "";
    }

    if(! -d "$adapter_path") {
        errorprint("$app_name: sysfs entry for adapter at: \"$adapter_path\" does not exists.\n");
        return "";
    }

    local *ATTRIB_DIRHANDLE;
    opendir(ATTRIB_DIRHANDLE, "$adapter_path");
    my @attributes = readdir ATTRIB_DIRHANDLE;
    closedir ATTRIB_DIRHANDLE;

    my $attribute_list = "";
    $attribute_list = join(":", @attributes);

    if ($attribute_list !~ /:bus$bus/) {
        # Bus doesn't exist
        return "";
    }
    verboseprint("$middle $app_name: $adapter_path/bus$bus already exists.\n");
    return "$adapter_path/bus$bus";
}

sub find_target( $ $ ) {
    my $bus_path = shift;
    my $target = shift;

    my $middle = "|  *";

    # $bus_path should ne ""
    if($bus_path eq "") {
        errorprint("$app_name: find_target invoked with empty bus_path.\n");
        return "";
    }

    if(! -d "$bus_path") {
        errorprint("$app_name: sysfs entry for bus at: \"$bus_path\" does not exists.\n");
        return "";
    }

    local *ATTRIB_DIRHANDLE;
    opendir(ATTRIB_DIRHANDLE, "$bus_path");
    my @attributes = readdir ATTRIB_DIRHANDLE;
    closedir ATTRIB_DIRHANDLE;

    my $attribute_list = "";
    $attribute_list = join(":", @attributes);

    if ($attribute_list !~ /:target$target/) {

        # Target doesn't exist
        return "";
    }
    verboseprint("$middle $app_name: $bus_path/target$target already exists.\n");
    return "$bus_path/target$target";
}

sub add_bus_and_target( $ $ $ ) {
    my $adapter_path = shift;
    my $bus = shift;
    my $target = shift;
    my $bus_path = "";
    my $target_path = "";

    my $begin = "|-.-";
    my $end =   "| `-- ";

    if ($adapter_path eq "") {
        errorprint("$app_name: add_bus_and_target invoked with empty \$adapter_path.\n");
        return "";
    }

    $bus_path = find_bus($adapter_path, $bus);
    if ($bus_path eq "") {
        #echo num busses++ to the adapter num_busses entry which will
        #automatically add the bus entry to the adapter entry.

        if( ! -e "$adapter_path/num_buses") {
            errorprint("$app_name: sysfs attribute \"$adapter_path/num_buses\" does not exists.\n");
            return "";
        }

        local *NUM_BUSES_FILE;
        open NUM_BUSES_FILE, "$adapter_path/num_buses";
        my $num_buses = int(<NUM_BUSES_FILE>);

        # This should account for missordered bus entries in the config file.
        # If a config entry asks for bus3 to be created before bus2 has been
        # created then this app will create 4 bus targets to accomadate bus3
        # and when bus2 is read from the config it won't need to be created.
        if (($bus + 1) > ($num_buses + 1)) {
            $num_buses = $bus + 1; # bus number + 1 (for indexing correction)
        } else {
            $num_buses = $num_buses + 1;
        }

        verboseprint("$begin Set $adapter_path/num_buses to $num_buses\.\n");
        verboseprint("$end Added bus$bus to $adapter_path\.\n");
        `echo $num_buses > $adapter_path/num_buses 2>/dev/null`;
        close NUM_BUSES_FILE;

        $bus_path = find_bus($adapter_path, $bus);
        if ($bus_path eq "") {
            errorprint("$app_name: bus$bus not found after supposed addition.\n");
            return "";
        }
    }

    $target_path = find_target($bus_path, $target);
    if ($target_path eq "") {

        if( ! -e "$bus_path/num_targets") {
            errorprint("$app_name: sysfs attribute \"$bus_path/num_targets\" does not exists.\n");
            return "";
        }

        local *NUM_TARGETS_FILE;
        open NUM_TARGETS_FILE, "$bus_path/num_targets";
        my $num_targets = int(<NUM_TARGETS_FILE>);

        # Same reasoning as with creating buses noted above.
        if (($target + 1) > ($num_targets + 1)) {
            $num_targets = $target + 1; # target num + 1 (for idx correction)
        } else {
            $num_targets = $num_targets + 1;
        }
        verboseprint("$begin Set $bus_path/num_targets to $num_targets\.\n");
        verboseprint("$end Added target$target to bus$bus.\n");
        `echo $num_targets > $bus_path/num_targets 2>/dev/null`;
        close NUM_TARGETS_FILE;

        $target_path = find_target($bus_path, $target);
    }

    # It is possible that $target_path might be empty if the config file was
    # not well formed.
    return $target_path;
}

sub get_start_pattern( $ $ $ ) {
    my $adapter = shift;
    my $bus = shift;
    my $target = shift;

    my $start_pattern = "";
    #check for constraints
    if ($adapter ne "") {
        if ($bus ne "") {
            if ($target ne "") {
                $start_pattern = "($adapter):bus($bus):target($target)\\s*";
            } else {
                $start_pattern = "($adapter):bus($bus):target(\\d+)\\s*";
            }
        } else {
            $start_pattern = "($adapter):bus(\\d+):target(\\d+)\\s*";
        }
    } else {
        $start_pattern = "(\\w+):bus(\\d+):target(\\d+)\\s*";
    }
    return $start_pattern;
}

# The parameters may be used to constrain the start operation to a particular
# target on a bus, on an adapter, or simply all the targets under a bus on an
# adapter, or all targets on all buses under and adapter.
sub start_all( $ $ $ ) {

    my $constrain_adapter = shift;
    my $constrain_bus = shift;
    my $constrain_target = shift;

    #tabled output prefixes.
    my $begin =  "|-.-";
    my $middle = "| |--";
    my $end =    "| `--";

    #read the entire config file into one string and work from there.
    my $file_contents;
    {
        local *CONFIG_FILE;
        open CONFIG_FILE, "/etc/$config_file" or die "/etc/$config_file missing: $!";
        local $/ = undef;
        $file_contents = <CONFIG_FILE>;
        close CONFIG_FILE;
    }

    my $start_pattern = get_start_pattern($constrain_adapter, $constrain_bus, $constrain_target);
    if ($start_pattern eq "") {
        errorprint("$app_name: start pattern is empty!\n");
        return;
    }

    # Split into config entry blocks and process each block one at a time.
    # m means "Let ^ and $ match next to embedded \n
    # This strips the ibmvscsis: portion off the headers
    my @entries = split /^ibmvscsis:/m, $file_contents;

    statusprint(".- $app_name: Reading config file and starting vscsis server.\n");
    foreach my $entry (@entries) {

        #header information
        my $adapter = "";
        my $bus = "";
        my $target = "";

        #paths derived from header information
        my $adapter_path = "";
        my $target_path = "";

        #attributes in adapter/bus/target/
        my $loop_file = "";
        my $device_path = "";
        my $valid = 0;
        my $type = "";
        my $active = 1; #entries active by default

        #return value
        my $val = undef;

        #only match those config entries defined by $start_pattern
        ($val) = ($entry =~ /$start_pattern/i);
        if (defined $val) {
            ($adapter, $bus, $target) = ($1, $2, $3);

            $adapter_path = find_adapter($adapter);
            if ($adapter_path ne "") {
                $target_path = add_bus_and_target($adapter_path, $bus, $target);
                if ($target_path eq "") {
                    errorprint("$app_name: creation of bus$bus:target$target under adapter:$adapter failed.\n");
                    next; #bail on this entry
                } else {
                    # kind of missleading because the target may already exist,
                    # but for output purposes this should be ok.
                    statusprint("$begin vty-server\@$adapter/bus$bus/target$target created.\n");
                }

                #precautionary, though it shouldn't be necessary if vscsis is working
                #properly
                if (! -e "$target_path/device") {
                    errorprint("$app_name: target$target/device attribute doesn't exist.\n");
                    next;
                } elsif (! -e "$target_path/type") {
                    errorprint("$app_name: target$target/type attribute doesn't exist.\n");
                    next;
                } elsif (! -e "$target_path/active") {
                    errorprint("$app_name: target$target/active attribute doesn't exist.\n");
                    next;
                }

            } else {
                warnprint("$begin vty-server\@$adapter not present in sysfs.\n");
                next; #bail on this entry
            }
            $val = undef;
        } else {
            #ignore empty entry blocks (may be a defect in the split)
            next; #bail on this entry
        }

        ($val) = ($entry =~ /\n\s*none\s*/i);
        if (defined $val) {
            statusprint("$end Found placeholder entry, ignoring.\n");
            #no further configuration required for placeholder entries
            next;
        }

        # Must have a device_path element to be valid.
        ($val) = ($entry =~ /\n\s*device_path=\"(.*)\"\s*/i);
        if (defined $val) {
            $device_path = $1;
            $val = undef;
        } else {
            #This is an invalid entry and no device elements will be written
            #to sysfs.
            statusprint("$end Didn't find device path for bus$bus:target$target, ignoring entry.\n");
            next;
        }


        ($val) = ($entry =~ /\n\s*loop_file=\"(.*)\"\s*/i);
        if (defined $val) {
            $loop_file = $1;
            if (! -e "$loop_file") {
                statusprint("$end loop_file \"$loop_file\" not found, ignoring entry.\n");
                next;
            }
            $val = undef;
        }

        # defaults to block device if there is not a type element specified
        # in the future the type could be read from the /dev/ entry
        # pointed to by the "device" element, unless of course a flat file
        # .iso is set up as a loop device.
        ($val) = ($entry =~ /\n\s*type=\"(.*)\"\s*/i);
        if (defined $val) {
            $type = $1;
            $val = undef;
        } else {
            verboseprint("$middle didn't find 'type' element, defaulting to 'block'.\n");
            $type = "b";
        }

        # active by default
        ($val) = ($entry =~ /\n\s*inactive\s*/i);
        if (defined $val) {
            $active = 0;
            $val = undef;
        }

        #We checked to make sure all the attributes are present earlier.

        #Check if the target is active before writing any data to the target
        #entry.  This is a good enough method for checking dupe config entries as
        #well.
        local *ACTIVE;
        open ACTIVE, "cat $target_path/active |";
        my @activedata = <ACTIVE>;
        close ACTIVE;
        chomp($activedata[0]);
        if ($activedata[0] eq "1") {
            statusprint("$end vty-server\@$adapter:bus$bus:target$target already active.\n");
            next;
        }

        # Setup loop file to loop device if loop_file is present, we checked
        # for its existence earlier.
        if ($loop_file ne "") {
            `losetup $device_path $loop_file 2>/dev/null`;
            statusprint("$middle Mapping loop device $device_path\-->$loop_file\n");
        }

        # By this point we know $target/device exists and so does $device_path so
        # just write it to the attribute.
        `echo $device_path > $target_path/device 2>/dev/null`;
        statusprint("$middle wrote \"$device_path\" to target$target/device.\n");

        #write type to target/type in sysfs
        `echo $type > $target_path/type 2>/dev/null`;
        statusprint("$middle wrote \"$type\" to target$target/type.\n");

        if ($active) {
            statusprint "$end Activating $adapter:bus$bus:target$target\.\n";
            `echo 1 > $target_path/active 2>/dev/null`;
        } else {
            statusprint("$end $adapter:bus$bus:target$target left inactive by request.\n");
        }
    }
    statusprint("`- $app_name Finished reading config file and starting targets.\n");
}

sub start() {

    # If all of these eq "" then there are no start constraints and all
    # targets on all buses and adapters will be started.
    start_all( $param_adapter, $param_bus, $param_target );
}

sub stop_target( $ $ ) {
    #Axiom 1: Target entries with missing attributes are malformed and are
    #         not touched.
    #Axiom 2: The ibmvscsis driver prevents activation of a target if the
    #         device attribute is empty (not set).  Therefore if the device is
    #         unset this function is considered complete for such targets.
    #Rule 1:  Always detach the loop device even if the target isn't active.
    #Rule 2:  Always deactivate an active connection.

    my $target_path = shift;
    my $target = shift;
    my $end = "| | `--";
    my $middle = "| | |--";

    if (! -e "$target_path/active") {
        statusprint("$end incomplete target entry, no \"active\" attribute.\n");
        return;
    }

    if (! -e "$target_path/device") {
        statusprint("$end incomplete target entry, no \"device\" attribute.\n");
        return;
    }

    local *ACTIVE;
    open ACTIVE, "cat $target_path/active |";
    my @activedata = <ACTIVE>;
    close ACTIVE;
    chomp($activedata[0]);
    my $active = $activedata[0];

    local *DEVICE;
    open DEVICE, "cat $target_path/device |";
    my @devicedata = <DEVICE>;
    close DEVICE;
    my $device = $devicedata[0];
    chomp($device);

    if ($device eq "") {
        statusprint ("$end no device associated with $target\.\n");
        return;
    }

    local *LOSETUP;
    open LOSETUP, "losetup -a |";
    my @entries = <LOSETUP>;
    close LOSETUP;

    my $loop_back_device = "";
    foreach my $entry (@entries) {
        chomp($entry);
        if ($entry =~ /^$device: \[\w+\]:\w+ \(([A-Za-z0-9\-\_\/]+)\)/) {
            $loop_back_device = $1;
        }
    }

    if ($active eq "0" and $loop_back_device ne "") {
        # Not active and there is still a loop device, detach loop anyway.
        statusprint("$middle $target not active .\n");
        statusprint("$end detaching loop device $device from $loop_back_device\n");
        `losetup -d $device 2>/dev/null`;
    } elsif ($active eq "1" and $loop_back_device ne "") {
        #It is active and there is a loop device
        statusprint("$middle deactivating $target\.\n");
        statusprint("$end detaching loop device $device from $loop_back_device\n");
        `echo 0 > $target_path/active 2>/dev/null`;
        `losetup -d $device 2>/dev/null`;
    } elsif ($active eq "1") {
        statusprint("$end deactivating $target\n");
        `echo 0 > $target_path/active 2>/dev/null`;
    } else {
        statusprint("$end $target not active.\n");
    }
}

sub stop_bus( $ $ ) {
    my $bus_path = shift;
    my $bus = shift;

    my $middle = "| |--";
    my $end = "| `--";

    if(! -d "$bus_path") {
        errorprint("$app_name: $bus sysfs entry $bus_path does not exist.\n");
        return;
    }

    opendir(ATTRIB_DIRHANDLE, "$bus_path");
    my @tentry_list = sort readdir ATTRIB_DIRHANDLE;
    closedir ATTRIB_DIRHANDLE;

    my $target = "";

    # work on targets in reverse order
    #foreach my $entry (reverse @entry_list) {
    foreach my $tentry (@tentry_list) {

        if ($tentry =~ /^target(\w+)/) {
            $target = $1;
            statusprint("$middle stopping $tentry\n");
            stop_target("$bus_path/$tentry", $tentry);
        }
    }

    `echo 0 > $bus_path/num_targets 2>/dev/null`;
    statusprint("$end removed all targets from $bus\.\n");

}

sub stop_adapter( $ $ ) {
    my $adapter_path = shift;
    my $adapter = shift;
    my $end = "`--";
    my $middle = "|--";

    if(! -d "$adapter_path") {
        errorprint("$app_name: adapter $adapter sysfs entry $adapter_path does not exist.\n");
        return;
    }

    opendir(ATTRIB_DIRHANDLE, "$adapter_path");
    my @bentry_list = sort readdir ATTRIB_DIRHANDLE;
    closedir ATTRIB_DIRHANDLE;

    my $bus = "";
    # work on adapters in reverse order
    foreach my $bentry (@bentry_list) {

        if ($bentry =~ /^bus(\w+)/) {
            $bus = $1;
            statusprint("$middle stopping $bentry\n");
            stop_bus("$adapter_path/$bentry", $bentry);
        }
    }

    `echo 0 > $adapter_path/num_buses 2>/dev/null`;
    statusprint("$end removed all buses from adapter $adapter\.\n");
}

# The stop_all function is indescriminate.  It'll clear out the bus & target
# configuration for every single vscsis adapter, regardless of whether that
# adapter has an entry in the config file.
sub stop_all() {
    my $end = "`";
    my $middle = "-";

    my $driver_path = is_driver_installed();
    if ($driver_path eq "") {
        print "$driver is not loaded.\n";
        return -1;
    }

    opendir(ATTRIB_DIRHANDLE, "$driver_path");
    my @adapter_list = sort readdir ATTRIB_DIRHANDLE;
    closedir ATTRIB_DIRHANDLE;

    statusprint("$app_name: stopping vscsi server.\n");

    foreach my $adapter (@adapter_list) {

        # Only operate on vscsi server adapters
        if (-e "$driver_path/$adapter/name") {
            # maybe we should check the value of name and make sure it is
            # v-scsi-host?
            statusprint( ".- stopping vty-server\@$adapter\.\n");
            stop_adapter("$driver_path/$adapter", $adapter);
        }
    }

    statusprint("$app_name: vscsi server stopped.\n");
}


# This function simply parses a path that was a script arguement and breaks it
# into adapter, bus, and target.
sub stop_path() {
    my $val = undef;

    # if stop path is defined it cancels out trailing adapter, bus, and
    # target settings.
    if (! -d "$stop_path" ) {
        errorprint("$app_name: path $stop_path not found.\n");
        return;
    }
    # the \/?$ catches a trailing '/' on the path, or not
    ($val) = ($stop_path =~ /\/ibmvscsis\/(.*)\/bus(\d*)\/target(\d*)\/?$/);
    if (defined $val) {
        $param_adapter = $1;
        $param_bus = $2;
        $param_target = $3;
        $val = undef;
        return;
    }
    ($val) = ($stop_path =~ /\/ibmvscsis\/(.*)\/bus(\d*)\/?$/);
    if (defined $val) {
        $param_adapter = $1;
        $param_bus = $2;
        $param_target = "";
        $val = undef;
        return;
    }
    ($val) = ($stop_path =~ /\/ibmvscsis\/(\w+)\/?$/);
    if (defined $val) {
        $param_adapter = $1;
        $param_bus = "";
        $param_target = "";
        $val = undef;
    }
}

sub stop() {

    if ($stop_path ne "" ) {
        #divide the path into adapter, bus, and path.
        stop_path();
    }

    #stop an entire adapter if there is no bus specified.
    if ($param_adapter ne "") {
        my $adapter_path = find_adapter($param_adapter);
        if ($adapter_path eq "") {
            errorprint("$app_name: no path for vty-server\@$param_adapter\.\n");
            return;
        }

        if ($param_bus ne "") {
            my $bus_path = "$adapter_path/bus$param_bus";
            if (! -d "$bus_path") {
                errorprint("$app_name: path $bus_path not found.\n");
                return;
            }

            if ($param_target ne "") {
                statusprint(".--- Stopping vty-server\@$param_adapter/bus$param_bus/target$param_target\.\n");
                #stop the target on the adapter/bus only
                stop_target("$bus_path/target$param_target","target$param_target");
                statusprint("`--- Stopped.\n");
                return
            }
            #stop the whole adapter/bus

            statusprint(".-- Stopping all targets on vty-server\@$param_adapter/bus$param_bus\.\n");
            statusprint("|`.\n");
            stop_bus("$adapter_path/bus$param_bus","bus$param_bus");
            statusprint("`-- Stopped.\n");
            return;
        }
        #stop the whole adapter
        statusprint( ".- stopping vty-server\@$param_adapter\.\n");
        stop_adapter("$adapter_path", $param_adapter);
        return;
    }

    #default, with no args is to stop it all
    stop_all();

}
my $PSERIES_PLATFORM = dirname(__FILE__) . "/pseries_platform";

my $perldumpenv='perl -MData::Dumper -e '."'".
    '\$Data::Dumper::Terse=1;print Dumper(\%ENV);'."'";

eval '%ENV=('.$1.')' if `bash -c "
        . $PSERIES_PLATFORM;
        $perldumpenv"`
    =~ /^\s*\{(.*)\}\s*$/mxs;

if ($ENV{'platform'} != $ENV{'PLATFORM_PSERIES_LPAR'}) {
	print "$app_name: is not supported on the $ENV{'platform_name'} platform\n";
	exit 1;
}

my $help = '';
my $version = '';
my $stop = '';
my $start = '';
my $status = '';

my $num_options = @ARGV;

GetOptions (
                'help|?' => \$help,
                'noisy+' => \$noisy,# noisy is incremental,
                                    # 0 : silent (except on error) [default]
                                    # 1 : status (successes)
                                    # 2 : verbose operation trace
                'status' => \$status,

                'stop' => \$stop,
                # and
                'path=s' => \$stop_path,
                # or
                'adapter=s' => \$param_adapter,
                'bus=s' => \$param_bus,
                'target=s' => \$param_target,

                'start' => \$start,
                #also with adapter, bus, and target

                'version' => \$version,
                'warn+' => \$warn,
            );

    # An empty invocation of this script will result in the help text being
    # output.  If help text has been specified then this script will terminate
    # after outputing the help text without completing further operations.
    if ($num_options == 0 || $help) {
        helpinfo();
        exit;
    }

    if ($version) {
        versioninfo();
        exit;
    }

    if ($noisy > 1) {
        verboseprint("$app_name: executing in verbose mode.\n");
    }

    if ($warn > 0) {
        verboseprint("$app_name: executing with warn enabled.\n");
    }

    #--------------- Look for the systool application -----------------------
    # The systool application is required for invoking most/many of these
    # operations so we'll express it as a standard requirement.
    if (findsystools()) {
        exit;
    }

    if ($status) {
        status();
        exit;
    }

    if ($start) {
        start();
        exit;
    }

    if ($stop) {
        stop();
        exit;
    }

    exit;
