#!/usr/bin/perl
$Version  = 'SCST Configurator v2.0.0';

# Configures SCST
#
# Author:       Mark R. Buechler
# License:      GPLv2
# Copyright (c) 2005-2010 Mark R. Buechler

sub usage
  {
    print <<"EndUsage";
$Version

Usage:
     -h, -help, --help       : Show this information.

General Operations
     -config <file>          : Configure SCST given the specified <file>.
     -check_config <file>    : Checks the saved configuration <file>.
     -write_config <file>    : Writes the current configuration to <file>.
     -clear_config           : Clear all SCST configuration.

Query Operations
     -list_handler [<hndlr>] : List all available handlers or specific <hndlr>.
     -list_device [<device>] : List all open devices or specific <device>.
     -list_driver [<driver>] : List all available drivers or specific <driver>.
     -list_target [<target>] : List all available targets or specific <target>.
         [-driver <driver>]
     -list_group [<group>]   : List all configured groups, or specific <group>.  
         [-driver <driver>]
         [-target <target>]

     -list_scst_attr         : List all attributes for SCST.
     -list_hnd_attr <hndlr>  : List all attributes for a given handler.
     -list_dev_attr <device> : List all attributes for a given device.
     -list_drv_attr <driver> : List all attributes for a given driver.
     -list_tgt_attr <target> : List all attributes for a given driver/target.
          -driver <driver>
     -list_lun_attr <lun>    : List all attributes for a driver/target/lun.
          -driver <driver>
          -target <target>
          -group <group>
     -list_ini_attr <ini>    : List all attributes for a driver/target/initiator
          -driver <driver>
          -target <target>
          -group <group>

     -list_sessions          : List all current initiator sessions.

Set Attribute Operations
     -set_scst_attr          : Sets SCST attribute(s) <p> to value <v>.
         -attributes <p=v,...>
     -set_hnd_attr <hndlr>   : Sets handler attribute(s) <p> to value <v>.
         -attributes <p=v,...>
     -set_dev_attr <device>  : Sets device attributes(s) <p> to value <v>. 
         -attributes <p=v,...>
     -set_drv_attr <driver>  : Sets driver attribute(s) <p> to value <v>.
         -attributes <p=v,...>
     -set_tgt_attr <target>  : Sets target attribute(s) <p> to value <v>.
         -driver <driver>
         -attributes <p=v,...>
     -set_grp_attr <group>   : Sets group attribute(s) <p> to value <v>.
         -driver <driver>
         -target <target>
         -attributes <p=v,...>
     -set_lun_attr <lun>     : Sets LUN attribute(s) <p> to value <v>.
         -driver <driver>
         -target <target>
         -group <group>
         -attributes <p=v,...>
     -set_ini_attr <ini>     : Sets initiator attribute(s) <p> to value <v>.
         -driver <driver>
         -target <target>
         -group <group>
         -attributes <p=v,...>

Add Dynamic Attribute Operations
     -add_drv_attr <driver>  : Adds driver attribute(s) <p> to value <v>.
         -attributes <p=v,...>
     -add_tgt_attr <target>  : Adds target attribute(s) <p> to value <v>.
         -driver <driver>
         -attributes <p=v,...>

Remove Dynamic Attribute Operations
     -rem_drv_attr <driver>  : Remove driver attribute(s) <p> to value <v>.
         -attributes <p=v,...>
     -rem_tgt_attr <target>  : Remove target attribute(s) <p> to value <v>.
         -driver <driver>
         -attributes <p=v,...>

Device Operations
     -open_dev <device>      : Adds a new device using handler <handler>.
         -handler <handler>
         -attributes <p=v,...>
     -resync_dev <device>    : Resync the device size with the initiator(s).
     -close_dev <device>     : Closes a device belonging to handler <handler>.
         -handler <handler>

Target Operations
     -add_target <target>    : Add a dynamic target to a capable driver.
         -driver <driver>
     -rem_target <target>    : Remove a dynamic target from a driver.
         -driver <driver>

Group Operations
     -add_group <group>      : Add a group to a given driver & target.
         -driver <driver>
         -target <target>
     -rem_group <group>      : Remove a group from a given driver & target.
         -driver <driver>
         -target <target>
 
Initiator Operations
     -add_init <init>        : Adds an initiator to a group.
         -driver <driver>
         -target <target>
         -group <group>
     -rem_init <user>        : Removes an initiator from a group.
         -driver <driver>
         -target <target>
         -group <group>
     -move_init <init>       : Moves an initiator from one group to another.
         -driver <driver>
         -target <target>
         -group <group 1>
         -to <group 2>
     -clear_inits            : Clear all initiators from a given group.
         -driver <driver>
         -target <target>
         -group <group>

Target LUN Operations
     -add_lun <lun>          : Adds a given device to a group.
         -driver <driver>
         -target <target>
         -group <group>
         -device <device>
         -attributes <p=v,...>
     -rem_lun <lun>          : Remove a LUN from a group.
         -driver <driver>
         -target <target>
         -group <group>
     -replace_lun <lun>      : Replaces a LUN\'s device with a different one.
         -driver <driver>
         -target <target>
         -group <group>
         -device <device>
         -attributes <p=v,...>
     -clear_luns             : Clear all LUNs within a group.
         -driver <driver>
         -target <target>
         -group <group>

Target Driver Operations
     -enable_target <t>      : Enable target mode for a given driver & target.
         -driver <driver>
     -disable_target <t>     : Disable target mode for a given driver & target.
         -driver <driver>
     -issue_lip [<t>]
        [-driver <driver>]   : Issue a LIP for a specific driver/target or for
                               all drivers and targets.

Options
     -nonkey                 : When writing a config file or listing attributes,
                               store/print non-key attributes as well.
     -force                  : Force all configuration changes,
                               even deletions (DANGER!).
     -noprompt               : Do not prompt or pause. Use with caution!
  
Debugging (limited support)
     -debug                  : Debug mode - don\'t do anything destructive.

Examples:
     Open a new device:
       scstadmin -open_dev DISK01 -handler vdisk_fileio \
         -attributes filename=/vdisks/disk01.dsk,read_only

     Setting the T10 Device ID of a device
       scstadmin -set_dev_attr DISK01 -attributes t10_dev_id=0x2345 

     Create a new security group:
       scstadmin -add_group HOST01 -driver qla2x00t \
         -target 50:06:0B:00:00:39:71:78
       
     Add a LUN to a group:
       scstadmin -add_lun 1 -driver qla2x00t -target 50:06:0B:00:00:39:71:78 \
         -group HOST01 -device DISK01 -attributes read_only=1

     Enable target mode for fibre card specifying its WWN
       scstadmin -enable_target 50:06:0B:00:00:39:71:78 -driver qla2x00t

EndUsage
  }

use SCST::SCST 0.9.0;
use Getopt::Long;
use IO::File;
use IO::Dir;
use POSIX;
use strict;

my $_DEF_CONFIG_ = '/etc/scst.conf';

use constant {
TRUE             => 1,
FALSE            => 0,

DEF_CONFIG       => '/etc/scst.conf',
};

my $SCST;
my $CONFIG;
my $CONFIGFILE;
my $_DEBUG_;
my $_NOPROMPT_;

my %CURRENT;

$SIG{INT}  = \&commitSuicide;
$SIG{TERM} = \&commitSuicide;

use vars qw($Version);

&main();

sub getArgs {
	my $applyConfig;
	my $clearConfig;
	my $writeConfig;
	my $checkConfig;

	my $listHandler;
	my $listDevice;
	my $listDriver;
	my $listTarget;
	my $listGroup;
	my $listSessions;

	my $listScstAttr;
	my $listHandlerAttr;
	my $listDeviceAttr;
	my $listGroupAttr;
	my $listDriverAttr;
	my $listTargetAttr;
	my $listLunAttr;
	my $listInitiatorAttr;

	my $setScstAttr;
	my $setHandlerAttr;
	my $setDeviceAttr;
	my $setDriverAttr;
	my $setTargetAttr;
	my $setGroupAttr;
	my $setLunAttr;
	my $setInitiatorAttr;

	my $addDriverAttr;
	my $addTargetAttr;
	my $remDriverAttr;
	my $remTargetAttr;

	my $openDev;
	my $closeDev;
	my $resyncDev;

	my $addTarget;
	my $removeTarget;

	my $addGroup;
	my $removeGroup;

	my $addInitiator;
	my $removeInitiator;
	my $moveInitiator;
	my $clearInitiators;

	my $addLun;
	my $removeLun;
	my $replaceLun;
	my $clearLuns;

	my $enableTarget;
	my $disableTarget;
	my $issueLip;

	my $handler;
	my $attributes;

	my $driver;
	my $target;
	my $group;
	my $to;
	my $device;

	my $show_usage;
	my $nonkey;
	my $force;

	my $p = new Getopt::Long::Parser;

	if (!$p->getoptions('config:s'		=> \$applyConfig,
			    'clear_config'	=> \$clearConfig,
			    'write_config=s'	=> \$writeConfig,
			    'check_config:s'	=> \$checkConfig,

			    'list_handler:s'	=> \$listHandler,
			    'list_device:s'	=> \$listDevice,
			    'list_driver:s'	=> \$listDriver,
			    'list_target:s'	=> \$listTarget,
			    'list_group:s'	=> \$listGroup,
			    'list_sessions'	=> \$listSessions,

			    'list_scst_attr'	=> \$listScstAttr,
			    'list_hnd_attr=s'	=> \$listHandlerAttr,
			    'list_dev_attr=s'	=> \$listDeviceAttr,
			    'list_drv_attr=s'	=> \$listDriverAttr,
			    'list_tgt_attr=s'	=> \$listTargetAttr,
			    'list_grp_attr=s'	=> \$listGroupAttr,
			    'list_lun_attr=s'	=> \$listLunAttr,
			    'list_ini_attr=s'	=> \$listInitiatorAttr,

			    'set_scst_attr'	=> \$setScstAttr,
			    'set_hnd_attr=s'	=> \$setHandlerAttr,
			    'set_dev_attr=s'	=> \$setDeviceAttr,
			    'set_drv_attr=s'	=> \$setDriverAttr,
			    'set_tgt_attr=s'	=> \$setTargetAttr,
			    'set_grp_attr=s'	=> \$setGroupAttr,
			    'set_lun_attr=s'	=> \$setLunAttr,
			    'set_ini_attr=s'	=> \$setInitiatorAttr,

			    'add_drv_attr=s'	=> \$addDriverAttr,
			    'add_tgt_attr=s'	=> \$addTargetAttr,
			    'rem_drv_attr=s'	=> \$remDriverAttr,
			    'rem_tgt_attr=s'	=> \$remTargetAttr,

			    'open_dev=s'	=> \$openDev,
			    'close_dev=s'	=> \$closeDev,
			    'resync_dev=s'	=> \$resyncDev,

			    'add_target=s'	=> \$addTarget,
			    'rem_target=s'	=> \$removeTarget,

			    'add_group=s'	=> \$addGroup,
			    'rem_group=s'	=> \$removeGroup,

			    'add_init=s'	=> \$addInitiator,
			    'rem_init=s'	=> \$removeInitiator,
			    'move_init=s'	=> \$moveInitiator,
			    'clear_inits'	=> \$clearInitiators,

			    'add_lun=s'		=> \$addLun,
			    'rem_lun=s'		=> \$removeLun,
			    'replace_lun=s'	=> \$replaceLun,
			    'clear_luns'	=> \$clearLuns,

			    'enable_target=s'	=> \$enableTarget,
			    'disable_target=s'	=> \$disableTarget,
			    'issue_lip:s'	=> \$issueLip,

			    'handler=s'		=> \$handler,
			    'attributes=s'	=> \$attributes,

			    'driver=s'		=> \$driver,
			    'target=s'		=> \$target,
			    'group=s'		=> \$group,
			    'to=s'		=> \$to,
			    'device=s'		=> \$device,

			    'h'			=> \$show_usage,
			    'help'		=> \$show_usage,

			    'nonkey'		=> \$nonkey,
			    'noprompt'		=> \$_NOPROMPT_,
			    'force'		=> \$force,
			    'debug'             => \$_DEBUG_) ||
	    defined($show_usage))
	{
		usage();
	}

	$_DEBUG_ = TRUE if (defined($_DEBUG_));
	$_NOPROMPT_ = TRUE if (defined($_NOPROMPT_));

	$force = TRUE if (defined($force));
	$nonkey = TRUE if (defined($nonkey));

	my $query_mode = defined($listHandler) || defined($listDevice) || defined($listDriver) ||
	  defined($listTarget) || defined($listGroup) || defined($listSessions) || defined($listScstAttr) ||
	  defined($listHandlerAttr) || defined($listDeviceAttr) || defined($listDriverAttr) ||
	  defined($listTargetAttr) || defined($listGroupAttr) || defined($listLunAttr) || defined($listInitiatorAttr);

	my $set_mode = defined($setScstAttr) + defined($setHandlerAttr) + defined($setDeviceAttr) +
	  defined($setDriverAttr) + defined($setTargetAttr) + defined($setGroupAttr) +
	  defined($setLunAttr) + defined($setInitiatorAttr);

	my $op_mode = defined($clearConfig) + defined($writeConfig) + defined($checkConfig) +
             defined($openDev) + defined($closeDev) + defined($addGroup) + defined($removeGroup) +
	     defined($addInitiator) + defined($removeInitiator) + defined($clearInitiators) +
	     defined($addDriverAttr) + defined($addTargetAttr) + defined($remDriverAttr) + defined($remTargetAttr) +
	     defined($addTarget) + defined($removeTarget) +
	     defined($addLun) + defined($removeLun) + defined($replaceLun) + defined($clearLuns) +
	     defined($enableTarget) + defined($disableTarget) + defined($issueLip);

	if (($query_mode + $set_mode + $op_mode) > 1) {
		print "Please specify only one non-query operation at a time.\n";
		usage();
	}

	if (defined($clearConfig) && !$force) {
		print "Please specify -force with -clear_config.\n";
		usage();
	}

	if (defined($listTargetAttr) && ($driver eq '')) {
		print "Please specify -driver with -list_tgt_attr.\n";
		usage();
	}

	if (defined($listGroupAttr) && (($driver eq '') || ($target eq ''))) {
		print "Please specify -driver and -target with -list_grp_attr.\n";
		usage();
	}

	if (defined($listLunAttr) && (($driver eq '') || ($target eq ''))) {
		print "Please specify -driver -target and -group with -list_lun_attr.\n";
		usage();
	}

	if (defined($listInitiatorAttr) && (($driver eq '') || ($target eq '') || ($group eq ''))) {
		print "Please specify -driver -target and -group with -list_ini_attr.\n";
		usage();
	}

	if (defined($setScstAttr) && ($attributes eq '')) {
		print "Please specify -attributes with -set_scst_attr.\n";
		usage();
	}

	if (defined($setHandlerAttr) && ($attributes eq '')) {
		print "Please specify -attributes with -set_hnd_attr.\n";
		usage();
	}

	if (defined($setDeviceAttr) && ($attributes eq '')) {
		print "Please specify -attributes with -set_dev_attr.\n";
		usage();
	}

	if (defined($setDriverAttr) && ($attributes eq '')) {
		print "Please specify -attributes with -set_drv_attr.\n";
		usage();
	}

	if (defined($setTargetAttr) && (($driver eq '') || ($attributes eq ''))) {
		print "Please specify -driver and -attributes with -set_tgt_attr.\n";
		usage();
	}

	if (defined($setGroupAttr) && (($driver eq '') || ($target eq '') || ($attributes eq ''))) {
		print "Please specify -driver -target and -attributes with -set_grp_attr.\n";
		usage();
	}

	if (defined($setLunAttr) &&
	    (($driver eq '') || ($target eq '') || ($attributes eq ''))) {
		print "Please specify -driver -target -group and -attributes with -set_lun_attr.\n";
		usage();
	}

	if (defined($setInitiatorAttr) &&
	    (($driver eq '') || ($target eq '') || ($group eq '') || ($attributes eq ''))) {
		print "Please specify -driver -target -group and -attributes with -set_ini_attr.\n";
		usage();
	}

	if (defined($addDriverAttr) && ($attributes eq '')) {
		print "Please specify -attributes with -add_drv_attr.\n";
		usage();
	}

	if (defined($addTargetAttr) &&
	    (($driver eq '') || ($attributes eq ''))) {
		print "Please specify -driver and -attributes with -add_tgt_attr.\n";
		usage();
	}

	if (defined($remDriverAttr) && ($attributes eq '')) {
		print "Please specify -attributes with -rem_drv_attr.\n";
		usage();
	}

	if (defined($remTargetAttr) &&
	    (($driver eq '') || ($attributes eq ''))) {
		print "Please specify -driver and -attributes with -rem_tgt_attr.\n";
		usage();
	}

	if ((defined($openDev) || defined($closeDev)) && ($handler eq '')) {
		print "Please specify -handler with -open_dev/-close_dev.\n";
		usage();
	}

	if (defined($addTarget) && ($driver eq '')) {
		print "Please specify -driver with -add_target.\n";
		usage();
	}

	if (defined($removeTarget) && ($driver eq '')) {
		print "Please specify -driver with -rem_target.\n";
		usage();
	}

	if ((defined($addGroup) || defined($removeGroup)) &&
	    (($driver eq '') || ($target eq ''))) {
		print "Please specify -driver and -target with -add_group/-remove_group.\n";
		usage();
	}

	if ((defined($addInitiator) || defined($removeInitiator) || defined($clearInitiators)) &&
	    (($target eq '') || ($driver eq '') || ($group eq ''))) {
		print "Please specify -driver -target and -group with ".
		  "-add_init/-remove_init/-clear_inits.\n";
		usage();
	}

	if (defined($moveInitiator) &&
	    (($driver eq '') || ($target eq '') || ($group eq '') || ($to eq ''))) {
		print "Please specify -driver -target -group and -to with -move_init.\n";
		usage();
	}

	if ((defined($addLun) || defined($replaceLun)) &&
	    (($driver eq '') || ($target eq '') || ($device eq ''))) {
		print "Please specify -driver -target and -device with -add_lun/-replace_lun.\n";
		usage();
	}

	if ((defined($clearLuns) || defined($removeLun)) && (($driver eq '') || ($target eq ''))) {
		print "Please specify -driver and -target with -rem_lun/-clear_luns.\n";
		usage();
	}

	$applyConfig = $_DEF_CONFIG_ if (defined($applyConfig) && ($applyConfig eq ''));
	$checkConfig = $_DEF_CONFIG_ if (defined($checkConfig) && ($checkConfig eq ''));

	my %_attributes;
	if ($attributes) {
		foreach my $attribute (split(/\,/, $attributes)) {
			my $value;

			if ($attribute !~ /\=/) {
				$value = TRUE;
			} else {
				($attribute, $value) = split(/\=/, $attribute, 2);
			}

			$_attributes{$attribute} = $value;
		}
	}

	return ($applyConfig, $clearConfig, $writeConfig, $checkConfig,
		$listScstAttr, $listHandler, $listDevice, $listDriver, $listTarget, $listGroup,
		$listSessions, $listHandlerAttr, $listDeviceAttr, $listDriverAttr, $listTargetAttr,
		$listGroupAttr, $listLunAttr, $listInitiatorAttr, $setScstAttr, $setHandlerAttr,
		$setDeviceAttr, $setDriverAttr, $setTargetAttr, $setGroupAttr, $setLunAttr, $setInitiatorAttr,
		$addDriverAttr, $addTargetAttr, $remDriverAttr, $remTargetAttr,
                $openDev, $closeDev, $resyncDev,
		$addTarget, $removeTarget,
		$addGroup, $removeGroup,
		$addInitiator, $removeInitiator, $moveInitiator, $clearInitiators,
		$addLun, $removeLun, $replaceLun, $clearLuns,
		$enableTarget, $disableTarget, $issueLip,
		$handler, \%_attributes,
		$driver, $target, $group, $to, $device,
		$nonkey, $force);
}

sub main {
	my $rc = 0;

	STDOUT->autoflush(1);

	# We need to run as root
	if ( $> ) {die("This program must run as root.\n");}

	my ($applyConfig, $clearConfig, $writeConfig, $checkConfig,
	    $listScstAttr, $listHandler, $listDevice, $listDriver, $listTarget, $listGroup,
	    $listSessions, $listHandlerAttr, $listDeviceAttr, $listDriverAttr, $listTargetAttr,
	    $listGroupAttr, $listLunAttr, $listInitiatorAttr, $setScstAttr, $setHandlerAttr,
	    $setDeviceAttr, $setDriverAttr, $setTargetAttr, $setGroupAttr, $setLunAttr, $setInitiatorAttr,
	    $addDriverAttr, $addTargetAttr, $remDriverAttr, $remTargetAttr,
	    $openDev, $closeDev, $resyncDev,
	    $addTarget, $removeTarget,
	    $addGroup, $removeGroup,
	    $addInitiator, $removeInitiator, $moveInitiator, $clearInitiators,
	    $addLun, $removeLun, $replaceLun, $clearLuns,
	    $enableTarget, $disableTarget, $issueLip,
	    $handler, $attributes,
	    $driver, $target, $group, $to, $device,
	    $nonkey, $force) = getArgs();

	$SCST = new SCST::SCST($_DEBUG_);

	my $rc = readWorkingConfig($force);
	exit $rc if ($rc);

	my $all_good;

	SWITCH: {
		defined($applyConfig) && do {
			$CONFIGFILE = $applyConfig;
			$rc = checkConfiguration();
			immediateExit("Configuration has errors, aborting.") if ($rc);
			last if ($force && prompt());
			my $changes = applyConfiguration($force);
			$rc = issueLip() if ($changes);
			last SWITCH;
		};
		defined($checkConfig) && do {
			$CONFIGFILE = $checkConfig;
			$rc = checkConfiguration();
			last SWITCH;
		};
		defined($writeConfig) && do {
			$CONFIGFILE = $writeConfig;
			$rc = writeConfiguration($nonkey);
			last SWITCH;
		};
		defined($clearConfig) && do {
			last if (prompt());
			$rc = clearConfiguration();
			$rc = issueLip() if (!$rc);
			last SWITCH;
		};
		defined($listHandler) && do {
			$rc = listHandlers($listHandler);
			$all_good = TRUE;
		};
		defined($listDevice) && do {
			$rc = listDevices($listDevice, $nonkey);
			$all_good = TRUE;
		};
		defined($listDriver) && do {
			$rc = listDrivers($listDriver);
			$all_good = TRUE;
		};
		defined($listTarget) && do {
			$rc = listTargets($driver, $listTarget);
			$all_good = TRUE;
		};
		defined($listGroup) && do {
			$rc = listGroups($driver, $target, $listGroup);
			$all_good = TRUE;
		};
		defined($listSessions) && do {
			$rc = listSessions();
			$all_good = TRUE;
		};
		defined($listScstAttr) && do {
			$rc = listScstAttributes($nonkey);
			$all_good = TRUE;
		};
		defined($listHandlerAttr) && do {
			$rc = listHandlerAttributes($listHandlerAttr, $nonkey);
			$all_good = TRUE;
		};
		defined($listDeviceAttr) && do {
			$rc = listDevice($listDeviceAttr, $nonkey);
			$all_good = TRUE;
		};
		defined($listDriverAttr) && do {
			$rc = listDriverAttributes($listDriverAttr, $nonkey);
			$all_good = TRUE;
		};
		defined($listTargetAttr) && do {
			$rc = listTargetAttributes($driver, $listTargetAttr, $nonkey);
			$all_good = TRUE;
		};
		defined($listGroupAttr) && do {
			$rc = listGroupAttributes($driver, $target, $listGroupAttr, $nonkey);
			$all_good = TRUE;
		};
		defined($listLunAttr) && do {
			$rc = listLunAttributes($driver, $target, $group, $listLunAttr, $nonkey);
			$all_good = TRUE;
		};
		defined($listInitiatorAttr) && do {
			$rc = listInitiatorAttributes($driver, $target, $group, $listInitiatorAttr, $nonkey);
			$all_good = TRUE;
		};
		defined($setScstAttr) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			my $changes = setScstAttributes($attributes, TRUE);
			print "\t-> Done, $changes change(s) made.\n";
			last SWITCH;
		};
		defined($setHandlerAttr) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			my $changes = setHandlerAttributes($setHandlerAttr, $attributes, TRUE);
			print "\t-> Done, $changes change(s) made.\n";
			last SWITCH;
		};
		defined($setDeviceAttr) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			my $changes = setDeviceAttributes($setDeviceAttr, $attributes, TRUE);
			print "\t-> Done, $changes change(s) made.\n";
			last SWITCH;
		};
		defined($setDriverAttr) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			my $changes = setDriverAttributes($setDriverAttr, $attributes, FALSE);
			print "\t-> Done, $changes change(s) made.\n";
			last SWITCH;
		};
		defined($setTargetAttr) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			my $changes = setTargetAttributes($driver, $setTargetAttr, $attributes, FALSE);
			print "\t-> Done, $changes change(s) made.\n";
			last SWITCH;
		};
		defined($setGroupAttr) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			my $changes = setGroupAttributes(undef, $driver, $target, $setGroupAttr, $attributes, TRUE);
			print "\t-> Done, $changes change(s) made.\n";
			last SWITCH;
		};
		defined($setLunAttr) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			my $changes = setLunAttributes($driver, $target, $setLunAttr, $attributes, $group, TRUE);
			print "\t-> Done, $changes change(s) made.\n";
			last SWITCH;
		};
		defined($setInitiatorAttr) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			my $changes = setInitiatorAttributes($driver, $target, $group, $setInitiatorAttr, $attributes, TRUE);
			print "\t-> Done, $changes change(s) made.\n";
			last SWITCH;
		};
		defined($addDriverAttr) && do {
			print "\n-> Making requested changes.\n";
			$rc = addDriverDynamicAttributes($addDriverAttr, $attributes);
			$rc = issueLip() if (!$rc);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($addTargetAttr) && do {
			print "\n-> Making requested changes.\n";
			$rc = addTargetDynamicAttributes($driver, $addTargetAttr, $attributes);
			$rc = issueLip() if (!$rc);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($remDriverAttr) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			$rc = removeDriverDynamicAttributes($remDriverAttr, $attributes);
			$rc = issueLip() if (!$rc);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($remTargetAttr) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			$rc = removeTargetDynamicAttributes($driver, $remTargetAttr, $attributes);
			$rc = issueLip() if (!$rc);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($openDev) && do {
			print "\n-> Making requested changes.\n";
			$rc = openDevice($handler, $openDev, $attributes);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($closeDev) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			$rc = closeDevice($handler, $closeDev, $force);
			$rc = issueLip() if (!$rc);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($resyncDev) && do {
			print "\n-> Making requested changes.\n";
			$rc = resyncDevice($resyncDev);
			$rc = issueLip() if (!$rc);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($addTarget) && do {
			print "\n-> Making requested changes.\n";
			$rc = addVirtualTarget($driver, $addTarget, $attributes);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($removeTarget) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			$rc = removeVirtualTarget($driver, $removeTarget);
			$rc = issueLip($driver) if (!$rc);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($addGroup) && do {
			print "\n-> Making requested changes.\n";
			$rc = addGroup($driver, $target, $addGroup);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($removeGroup) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			$rc = removeGroup($driver, $target, $removeGroup, $force);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($addInitiator) && do {
			print "\n-> Making requested changes.\n";
			$rc = addInitiator($driver, $target, $group, $addInitiator);
			$rc = issueLip($driver, $target) if (!$rc);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($removeInitiator) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			$rc = removeInitiator($driver, $target, $group, $removeInitiator);
			$rc = issueLip($driver, $target) if (!$rc);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($moveInitiator) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			$rc = moveInitiator($driver, $target, $group, $moveInitiator, $to);
			$rc = issueLip($driver, $target) if (!$rc);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($clearInitiators) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			$rc = clearInitiators($driver, $target, $group);
			$rc = issueLip($driver, $target) if (!$rc);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($addLun) && do {
			print "\n-> Making requested changes.\n";
			$rc = addLun($driver, $target, $device, $addLun, $attributes, $group);
			$rc = issueLip($driver, $target) if (!$rc);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($removeLun) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			$rc = removeLun($driver, $target, $removeLun, $group);
			$rc = issueLip($driver, $target) if (!$rc);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($replaceLun) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			$rc = replaceLun($driver, $target, $group, $replaceLun, $device, $attributes);
			$rc = issueLip($driver, $target) if (!$rc);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($clearLuns) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			$rc = clearLuns($driver, $target, $group);
			$rc = issueLip($driver, $target) if (!$rc);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($enableTarget) && do {
			print "\n-> Making requested changes.\n";
			$rc = enableTarget($driver, $enableTarget);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($disableTarget) && do {
			last if (prompt());
			print "\n-> Making requested changes.\n";
			$rc = disableTarget($driver, $disableTarget);
			print "\t-> Done.\n";
			last SWITCH;
		};
		defined($issueLip) && do {
			print "\n-> Making requested changes.\n";
			$rc = issueLip($driver, $issueLip);
			print "\t-> Done.\n";
			last SWITCH;
		};

		if (!$all_good) {
			print "No valid operations specified.\n";
			usage();
			exit TRUE;
		}
	}

	issueWarning($SCST->errorString($rc)) if ($rc);

	print "\nAll done.\n";

	exit $rc;
}

sub readWorkingConfig {
	my $force = shift;

	print "\nCollecting current configuration: ";

	%CURRENT = ();

	# Get current handlers/devices

	my $handlers = $SCST->handlers();
	immediateExit($SCST->errorString());

	foreach my $handler (@{$handlers}) {
		my $devices = $SCST->devicesByHandler($handler);
		immediateExit($SCST->errorString());
		$CURRENT{'handler'}->{$handler} = $devices;
	}

	# Get current assignments

	my $drivers = $SCST->drivers();
	immediateExit($SCST->errorString());

	foreach my $driver (@{$drivers}) {
		my %empty;
		$CURRENT{'assign'}->{$driver} = \%empty;
		my $targets = $SCST->targets($driver);
		immediateExit($SCST->errorString());
		foreach my $target (@{$targets}) {
			my %empty;
			$CURRENT{'assign'}->{$driver}->{$target} = \%empty;

			my $luns = $SCST->luns($driver, $target);

			$CURRENT{'assign'}->{$driver}->{$target}->{'LUN'} = $luns if (defined($luns));

			my $groups = $SCST->groups($driver, $target);
			immediateExit($SCST->errorString());
			foreach my $group (@{$groups}) {
				$CURRENT{'assign'}->{$driver}->{$target}->{'GROUP'}->{$group}->{'INITIATORS'} =
				  $SCST->initiators($driver, $target, $group);
				immediateExit($SCST->errorString());

				$CURRENT{'assign'}->{$driver}->{$target}->{'GROUP'}->{$group}->{'LUN'} =
				  $SCST->luns($driver, $target, $group);
				immediateExit($SCST->errorString());
			}
		}
	}

	print "done.\n\n";

	# Perform some basic checks

	# Check for initiators belonging to more than one group
	foreach my $driver (keys %{$CURRENT{'assign'}}) {
		foreach my $target (keys %{$CURRENT{'assign'}->{$driver}}) {
			my $current = $CURRENT{'assign'}->{$driver}->{$target}->{'GROUP'};
			my %seen_init;

			foreach my $group (keys %{$current}) {
				my $initiators = $$current{$group}->{'INITIATORS'};

				foreach my $init (@{$initiators}) {
					if (defined($seen_init{$init})) {
						if (!$force) {
							print "\t-> FATAL: Initiator '$init' belongs to more than one groups ".
							  "for driver/target '$driver/target', aborting. Use -force to override.\n";
							return TRUE;
						}
					}

					$seen_init{$init}++;
				}
			}
		}
	}

	return FALSE;
}

sub writeConfiguration {
	my $nonkey = shift;

	if (-f $CONFIGFILE) {
		if (!unlink $CONFIGFILE) {
			print "Failed to save current configuration, specified ".
			  "file exists and cannot be deleted.\n";
			return TRUE;
		}
	}

	my $io = new IO::File $CONFIGFILE, O_CREAT|O_WRONLY;

	if (!$io) {
		print "Failed to save configuration to file '$CONFIGFILE': $!\n";
		return 1;
	}

	print "Writing current configuration to file '$CONFIGFILE'.. ";

	print $io "# Automatically generated by $Version.\n\n";

	my $attributes = $SCST->scstAttributes();
	immediateExit($SCST->errorString());

	my %nattrs;

	foreach my $attribute (sort keys %{$attributes}) {
		next if (defined($$attributes{$attribute}->{'set'}));

		if (!$$attributes{$attribute}->{'static'}) {
			if (defined($$attributes{$attribute}->{'keys'})) {
				foreach my $key (%{$$attributes{$attribute}->{'keys'}}) {
					my $value = $$attributes{$attribute}->{'keys'}->{$key}->{'value'};
					$value = "\"$value\"" if ($value =~ / /);
					print $io "$attribute $value\n"
					  if (defined($value) && ($value ne ''));
				}
			} elsif ($nonkey) {
				my $value = $$attributes{$attribute}->{'value'};
				$value = "\"$value\"" if ($value =~ / /);
				$nattrs{$attribute} = $value
				  if (defined($value) && ($value ne ''));
			}
		}
	}

	if ($nonkey && (scalar keys %nattrs)) {
		print $io "# Non-key attributes\n";
		foreach my $attr (keys %nattrs) {
			my $value = $nattrs{$attr};
			print $io "$attr $value\n";
		}
	}

	print $io "\n";

	foreach my $handler (sort keys %{$CURRENT{'handler'}}) {
		my $handler_buff;
		my $handler_buff_nk;

		my $handler_attrs = $SCST->deviceCreateAttributes($handler);
		$attributes = $SCST->handlerAttributes($handler);

		foreach my $attribute (sort keys %{$attributes}) {
			next if (defined($$attributes{$attribute}->{'set'}));

			if (!$$attributes{$attribute}->{'static'}) {
				if (defined($$attributes{$attribute}->{'keys'})) {
					foreach my $key (@{$$attributes{$attribute}->{'keys'}}) {
						my $value = $$attributes{$attribute}->{'keys'}->{$key}->{'value'};
						$value = "\"$value\"" if ($value =~ / /);
						$handler_buff .= "\t$attribute $value\n" if (defined($value));
					}
				} elsif (($attribute eq 'enabled') || $nonkey) {
					my $value = $$attributes{$attribute}->{'value'};
					$value = "\"$value\"" if ($value =~ / /);
					if ($attribute eq 'enabled') {
						$handler_buff .= "\t$attribute $value\n" if (defined($value));
					} else {
						$handler_buff_nk .= "\t$attribute $value\n" if (defined($value));
					}
				}
			}
		}

		my $devices = $CURRENT{'handler'}->{$handler};

		my $device_buff;
		foreach my $device (sort @{$devices}) {
			$device_buff .= "\tDEVICE $device";

			$attributes = $SCST->deviceAttributes($device);

			my $attribute_buff;
			my $attribute_buff_nk;
			foreach my $attribute (sort keys %{$attributes}) {
				next if (defined($$attributes{$attribute}->{'set'}));

				if (!defined($$handler_attrs{$attribute})) {
					if (!$$attributes{$attribute}->{'static'}) {
						if (defined($$attributes{$attribute}->{'keys'})) {
							foreach my $key (keys %{$$attributes{$attribute}->{'keys'}}) {
								my $value = $$attributes{$attribute}->{'keys'}->{$key}->{'value'};
								$value = "\"$value\"" if ($value =~ / /);
								$attribute_buff .= "\t\t$attribute $value\n"
								  if (defined($value));
							}
						} elsif (($attribute eq 'enabled') || $nonkey) {
							my $value = $$attributes{$attribute}->{'value'};
							$value = "\"$value\"" if ($value =~ / /);
							if ($attribute eq 'enabled') {
								$attribute_buff .= "\t\t$attribute $value\n"
								 if (defined($value));
							} else {
								$attribute_buff_nk .= "\t\t$attribute $value\n"
								 if (defined($value));
							}
						}
					}
				}
			}

			$attribute_buff .= "\n" if ($attribute_buff);
			$attribute_buff_nk .= "\n" if ($attribute_buff_nk);

			if (!defined($$attributes{'scsi_device'})) {
				my $create_buff;

				foreach my $attribute (sort keys %{$attributes}) {
					next if (defined($$attributes{$attribute}->{'set'}));

					if (defined($$handler_attrs{$attribute})) {
						if (defined($$attributes{$attribute}->{'keys'})) {
							foreach my $key (keys %{$$attributes{$attribute}->{'keys'}}) {
								my $value = $$attributes{$attribute}->{'keys'}->{$key}->{'value'};
								$value = "\"$value\"" if ($value =~ / /);
								$create_buff .= "\t\t$attribute $value\n"
								  if (defined($value));
							}
						# Shouldn't be any non-key create attributes
						}
					}
				}

				if ($create_buff) {
					$attribute_buff .= $create_buff;
				}
			}

			if ($attribute_buff_nk) {
				$attribute_buff .= "\n" if ($attribute_buff);
				$attribute_buff .= "\t\t# Non-key attributes\n";
				$attribute_buff .= $attribute_buff_nk;
			}

			$attribute_buff =~ s/\n+$/\n/;

			if ($attribute_buff) {
				$device_buff .= " {\n";
				$device_buff .= $attribute_buff;
				$device_buff .= "\t}\n\n";
			} else {
				$device_buff .= "\n";
			}
		}

		$device_buff =~ s/\n+$/\n/;

		$handler_buff .= $device_buff;

		if ($handler_buff_nk) {
			$handler_buff .= "\t# Non-key attributes\n";
			$handler_buff .= $handler_buff_nk;
		}

		if ($handler_buff) {
			print $io "HANDLER $handler {\n";
			print $io $handler_buff;
			print $io "}\n\n";
		}
	}

	foreach my $driver (sort keys %{$CURRENT{'assign'}}) {
		my $driver_buff;

		my $drv_attrs = $SCST->driverAttributes($driver);

		my $drv_attr_buff;
		my $drv_attr_buff_nk;
		foreach my $attr (keys %{$drv_attrs}) {
			next if ($$drv_attrs{$attr}->{'static'});
			next if ($$drv_attrs{$attr}->{'set'});

			if (defined($$drv_attrs{$attr}->{'keys'})) {
				foreach my $key (keys %{$$drv_attrs{$attr}->{'keys'}}) {
					my $value = $$drv_attrs{$attr}->{'keys'}->{$key}->{'value'};
					$value = "\"$value\"" if ($value =~ / /);
					$drv_attr_buff .= "\t$attr $value\n"
					  if (defined($value));
				}
			} elsif (($attr eq 'enabled') || $nonkey) {
				my $value = $$drv_attrs{$attr}->{'value'};
				$value = "\"$value\"" if ($value =~ /\s/);
				if ($attr eq 'enabled') {
					$drv_attr_buff .= "\t$attr $value\n"
					  if (defined($value));
				} else {
					$drv_attr_buff_nk .= "\t$attr $value\n"
					  if (defined($value));
				}
			}
		}

		$drv_attr_buff .= "\n" if ($drv_attr_buff);
		$drv_attr_buff_nk .= "\n" if ($drv_attr_buff_nk);

		my $targets = $CURRENT{'assign'}->{$driver};
		my $tgt_attrs = $SCST->targetCreateAttributes($driver);

		my $target_buff;
		foreach my $target (sort keys %{$targets}) {
			$target_buff .= "\tTARGET $target";

			my $attributes = $SCST->targetAttributes($driver, $target);

			my $attribute_buff;
			my $attribute_buff_nk;

			if (defined($$attributes{'hw_target'}) &&
			  ($$attributes{'hw_target'}->{'value'} == TRUE)) {
				$attribute_buff = "\t\tHW_TARGET\n\n";
			}

			foreach my $attr (keys %{$attributes}) {
				next if ($$attributes{$attr}->{'static'} &&
					 !defined($$tgt_attrs{$attr}));
				next if ($$attributes{$attr}->{'set'});

				if (defined($$attributes{$attr}->{'keys'})) {
					foreach my $key (keys %{$$attributes{$attr}->{'keys'}}) {
						my $value = $$attributes{$attr}->{'keys'}->{$key}->{'value'};
						$value = "\"$value\"" if ($value =~ / /);
						$attribute_buff .= "\t\t$attr $value\n"
						  if (defined($value));
					}
				} elsif (($attr eq 'hw_target') || ($attr eq 'enabled') || $nonkey) {
					my $value = $$attributes{$attr}->{'value'};
					$value = "\"$value\"" if ($value =~ /\s/);
					if ($attr eq 'enabled') {
						$attribute_buff .= "\t\t$attr $value\n"
						  if (defined($value));
					} else {
						$attribute_buff_nk .= "\t\t$attr $value\n"
						  if (defined($value));
					}
				}
			}

			$attribute_buff .= "\n" if ($attribute_buff);
			$attribute_buff_nk .= "\n" if ($attribute_buff_nk);

			my $luns = $CURRENT{'assign'}->{$driver}->{$target}->{'LUN'};
			my $lun_attrs = $SCST->lunCreateAttributes($driver, $target);

			my $t_lun_buff;
			foreach my $lun (sort numerically keys %{$luns}) {
				my $lun_dev = $$luns{$lun};

				$t_lun_buff .= "\t\tLUN $lun $lun_dev";

				$attributes = $SCST->lunAttributes($driver, $target, $lun);

				my $l_attribute_buff;
				my $l_attribute_buff_nk;
				foreach my $attribute (sort keys %{$attributes}) {
					next if (defined($$attributes{$attribute}->{'set'}));

					if (!$$attributes{$attribute}->{'static'} ||
					    defined($$lun_attrs{$attribute})) {
						if (defined($$attributes{$attribute}->{'keys'})) {
							foreach my $key (keys %{$$attributes{$attribute}->{'keys'}}) {
								my $value = $$attributes{$attribute}->{'keys'}->{$key}->{'value'};
								$value = "\"$value\"" if ($value =~ / /);
								$l_attribute_buff .= "\t\t\t$attribute $value\n"
								  if (defined($value));
							}
						} elsif (($attribute eq 'enabled') || $nonkey) {
							my $value = $$attributes{$attribute}->{'value'};
							$value = "\"$value\"" if ($value =~ / /);
							if (defined($$lun_attrs{$attribute})) {
								$l_attribute_buff .= "\t\t\t$attribute $value\n"
								  if (defined($value));
							} elsif ($attribute eq 'enabled') {
								$l_attribute_buff .= "\t\t\t$attribute $value\n"
								  if (defined($value));
							} else {
								$l_attribute_buff_nk .= "\t\t\t$attribute $value\n"
								  if (defined($value));
							}
						}
					}
				}

				if ($l_attribute_buff_nk) {
					$l_attribute_buff .= "\t\t\t\t# Non-key attributes\n";
					$l_attribute_buff .= $l_attribute_buff_nk;
				}

				if ($l_attribute_buff) {
					$t_lun_buff .= " {\n";
					$t_lun_buff .= $l_attribute_buff;
					$t_lun_buff .= "\t\t}\n\n";
				} else {
					$t_lun_buff .= "\n";
				}
			}

			$t_lun_buff .= "\n" if ($t_lun_buff);
			$t_lun_buff =~ s/\n+$/\n\n/;

			my $groups = $CURRENT{'assign'}->{$driver}->{$target}->{'GROUP'};

			my $group_buff;
			foreach my $group (sort keys %{$groups}) {
				my $lun_attrs = $SCST->lunCreateAttributes($driver, $target, $group);
				my $ini_attrs = $SCST->initiatorCreateAttributes($driver, $target, $group);

				$group_buff .= "\t\tGROUP $group";

				my $luns = $CURRENT{'assign'}->{$driver}->{$target}->{'GROUP'}->{$group}->{'LUN'};

				my $lun_buff;
				foreach my $lun (sort numerically keys %{$luns}) {
					my $lun_dev = $$luns{$lun};

					$lun_buff .= "\t\t\tLUN $lun $lun_dev";

					$attributes = $SCST->lunAttributes($driver, $target, $lun, $group);

					my $l_attribute_buff;
					my $l_attribute_buff_nk;
					foreach my $attribute (sort keys %{$attributes}) {
						next if (defined($$attributes{$attribute}->{'set'}));

						if (!$$attributes{$attribute}->{'static'} ||
						    defined($$lun_attrs{$attribute})) {
							if (defined($$attributes{$attribute}->{'keys'})) {
								foreach my $key (keys %{$$attributes{$attribute}->{'keys'}}) {
									my $value = $$attributes{$attribute}->{'keys'}->{$key}->{'value'};
									$value = "\"$value\"" if ($value =~ / /);
									$l_attribute_buff .= "\t\t\t\t$attribute $value\n"
									  if (defined($value));
								}
							} elsif (($attribute eq 'enabled') || $nonkey) {
								my $value = $$attributes{$attribute}->{'value'};
								$value = "\"$value\"" if ($value =~ / /);
								if (defined($$lun_attrs{$attribute})) {
									$l_attribute_buff .= "\t\t\t\t$attribute $value\n"
									  if (defined($value));
								} elsif ($attribute eq 'enabled') {
									$l_attribute_buff .= "\t\t\t\t$attribute $value\n"
									  if (defined($value));
								} else {
									$l_attribute_buff_nk .= "\t\t\t\t$attribute $value\n"
									  if (defined($value));
								}
							}
						}
					}

					if ($l_attribute_buff_nk) {
						$l_attribute_buff .= "\t\t\t\t# Non-key attributes\n";
						$l_attribute_buff .= $l_attribute_buff_nk;
					}

					if ($l_attribute_buff) {
						$lun_buff .= " {\n";
						$lun_buff .= $l_attribute_buff;
						$lun_buff .= "\t\t\t}\n";
					} else {
						$lun_buff .= "\n";
					}
				}

				my $inits = $CURRENT{'assign'}->{$driver}->{$target}->{'GROUP'}->{$group}->{'INITIATORS'};

				my $init_buff;
				foreach my $init (@{$inits}) {
					$init_buff .= "\n\t\t\tINITIATOR $init";

					$attributes = $SCST->initiatorAttributes($driver, $target, $group, $init);

					my $i_attribute_buff;
					my $i_attribute_buff_nk;
					foreach my $attribute (sort keys %{$attributes}) {
						next if (defined($$attributes{$attribute}->{'set'}));

						if (!$$attributes{$attribute}->{'static'} ||
						    defined($$ini_attrs{$attribute})) {
							if (defined($$attributes{$attribute}->{'keys'})) {
								foreach my $key (keys %{$$attributes{$attribute}->{'keys'}}) {
									my $value = $$attributes{$attribute}->{'keys'}->{$key}->{'value'};
									$value = "\"$value\"" if ($value =~ / /);
									$i_attribute_buff .= "\t\t\t\t$attribute $value\n"
									  if (defined($value));
								}
							} elsif (($attribute eq 'enabled') || $nonkey) {
								my $value = $$attributes{$attribute}->{'value'};
								$value = "\"$value\"" if ($value =~ / /);
								if ($attribute eq 'enabled') {
									$i_attribute_buff .= "\t\t\t\t$attribute $value\n"
									  if (defined($value));
								} else {
									$i_attribute_buff_nk .= "\t\t\t\t$attribute $value\n"
									  if (defined($value));
								}
							}
						}
					}

					if ($i_attribute_buff_nk) {
						$i_attribute_buff .= "\t\t\t\t# Non-key attributes\n";
						$i_attribute_buff .= $i_attribute_buff_nk;
					}

					if ($i_attribute_buff) {
						$init_buff .= " {\n";
						$init_buff .= $attribute_buff;
						$init_buff .= "\t\t\t}\n";
					} else {
						$init_buff .= "\n";
					}
				}

				if ($lun_buff || $init_buff) {
					$group_buff .= " {\n";

					if ($lun_buff) {
						$group_buff .= $lun_buff;
					}

					if ($init_buff) {
						$group_buff .= $init_buff;
					}
				}

				my $grp_attributes = $SCST->groupAttributes($driver, $target, $group);

				my $g_attribute_buff;
				my $g_attribute_buff_nk;
				foreach my $attribute (sort keys %{$grp_attributes}) {
					next if (defined($$grp_attributes{$attribute}->{'set'}));

					if (!$$grp_attributes{$attribute}->{'static'}) {
						if (defined($$grp_attributes{$attribute}->{'keys'})) {
							foreach my $key (keys %{$$grp_attributes{$attribute}->{'keys'}}) {
								my $value = $$grp_attributes{$attribute}->{'keys'}->{$key}->{'value'};
								$value = "\"$value\"" if ($value =~ / /);
								$g_attribute_buff .= "\t\t\t$attribute $value\n"
								  if (defined($value));
							}
						} elsif (($attribute eq 'enabled') || $nonkey) {
							my $value = $$grp_attributes{$attribute}->{'value'};
							$value = "\"$value\"" if ($value =~ / /);
							if ($attribute eq 'enabled') {
								$g_attribute_buff .= "\t\t\t$attribute $value\n"
								  if (defined($value));
							} else {
								$g_attribute_buff_nk .= "\t\t\t$attribute $value\n"
								  if (defined($value));
							}
						}
					}
				}

				if ($g_attribute_buff_nk) {
					$g_attribute_buff .= "\n" if ($g_attribute_buff);
					$g_attribute_buff .= "\t\t\t# Non-key attributes\n";
					$g_attribute_buff .= $g_attribute_buff_nk;
				}

				if ($g_attribute_buff) {
					$group_buff .= "\n";
					$group_buff .= $g_attribute_buff;
				}

				if ($group_buff && ($lun_buff || $init_buff ||
						    $g_attribute_buff || $g_attribute_buff_nk)) {
					$group_buff .= "\t\t}\n\n";
					$group_buff =~ s/\n+$/\n/;
				}

				$group_buff .= "\n" if ($group_buff);
			}

			if ($attribute_buff_nk) {
				$attribute_buff .= "\t\t# Non-key attributes\n";
				$attribute_buff .= $attribute_buff_nk;
			}

			if ($attribute_buff || $t_lun_buff || $group_buff ) {
				$target_buff .= " {\n";

				$target_buff .= $attribute_buff;
				$target_buff .= $t_lun_buff;
				$target_buff .= $group_buff;

				$target_buff =~ s/\n\n$/\n/;
				$target_buff .= "\t}\n\n";
			} else {
				$target_buff .= "\n";
			}
		}

		if ($drv_attr_buff_nk) {
			$drv_attr_buff .= "\t# Non-key attributes\n";
			$drv_attr_buff .= $drv_attr_buff_nk;
		}

		$driver_buff .= $drv_attr_buff;
		$driver_buff .= $target_buff;
		$driver_buff =~ s/\n\n$/\n/;

		if ($driver_buff) {
			print $io "TARGET_DRIVER $driver {\n";
			print $io $driver_buff;
			print $io "}\n\n";
		}
	}

	close $io;

	return FALSE;
}

sub checkConfiguration {
	my $no_drivers;
	my $no_handlers;
	my $warnings = 0;
	my $errors = 0;

	print "-> Checking configuration file '$CONFIGFILE' for errors.\n";

	readConfigFile() if (!$CONFIG);

	if (!scalar keys %{$CONFIG}) {
		print "-> WARNING: Configuration is empty or unreadable, aborting.\n\n";
		exit 0;
	}

	# Check for a minimum sane configuration
	if (!defined($$CONFIG{'TARGET_DRIVER'}) ||
	    !(scalar keys %{$$CONFIG{'TARGET_DRIVER'}})) {
		print "\t-> WARNING: No TARGET_DRIVER section defined. ".
		  "No target drivers will be configured.\n\n";
		$no_drivers = TRUE;
		$warnings++;
	}

	if (!defined($$CONFIG{'HANDLER'}) ||
	    !(scalar keys %{$$CONFIG{'HANDLER'}})) {
		print "\t-> WARNING: No HANDLER section defined. ".
		  "Only physical media will be configured for targets.\n\n";
		$no_handlers = TRUE;
		$warnings++;
	}

	if ($no_drivers && $no_handlers) {
		print "FATAL: No target drivers or handlers defined, aborting!\n";
		$errors++;
	}

	if (!$no_drivers) {
		foreach my $driver (keys %{$$CONFIG{'TARGET_DRIVER'}}) {
			my $no_targets;

			if (!defined($CURRENT{'assign'}->{$driver})) {
				print "\t-> WARNING: Target driver '$driver' is not loaded or available.\n\n";
				$warnings++;
				next;
			}

			if (!defined($$CONFIG{'TARGET_DRIVER'}->{$driver}->{'TARGET'}) ||
			    !(scalar keys %{$$CONFIG{'TARGET_DRIVER'}->{$driver}->{'TARGET'}})) {
				print "\t-> WARNING: Driver '$driver' has no configured targets.\n\n";
				$warnings++;
				$no_targets = TRUE;
			}

			if (!$no_targets) {
				foreach my $target (keys %{$$CONFIG{'TARGET_DRIVER'}->{$driver}->{'TARGET'}}) {
					my $groups = $$CONFIG{'TARGET_DRIVER'}->{$driver}->{'TARGET'}->{$target}->{'GROUP'};
					my %seen_init;
					foreach my $group (keys %{$groups}) {
						foreach my $initiator (keys %{$$groups{$group}->{'INITIATOR'}}) {
							if (defined($seen_init{$initiator})) {
								print "\t-> FATAL: Initiator '$initiator' belongs to more than one group.\n".
								  "\t     Initiators can only belong to one group at a time for a given target.\n";
								$errors++;
							}

							$seen_init{$initiator}++;
						}
					}

					if (!defined($CURRENT{'assign'}->{$driver}->{$target})) {
						if (!$SCST->driverIsVirtualCapable($driver)) {
							print "\t-> FATAL: Target '$target' for driver '$driver' ".
							  "does not exist.\n";
							$errors++;
						}
					}
				}
			}
		}
	}

	my %cdevices;

	foreach my $handler (sort keys %{$$CONFIG{'HANDLER'}}) {
		if (!$SCST->handlerExists($handler)) {
			print "\t-> WARNING: No such handler '$handler' available, ignoring.\n";
			delete $$CONFIG{'HANDLER'}->{$handler};
			$warnings++;
			next;
		}

		foreach my $device (sort keys %{$$CONFIG{'HANDLER'}->{$handler}->{'DEVICE'}}) {
			# Since some people may get confused with how to open
			# a vcdrom, we'll support having '/dev/cdrom' instead of just 'cdrom'.
			if ($device =~ /^\/dev\//) {
				my $_device = $device;
				$_device =~ s/^\/dev\///o;
				my $tree = $$CONFIG{'HANDLER'}->{$handler}->{'DEVICE'}->{$device};
				print "\t-> WARNING: Device '$device' configured for handler '$handler' may ".
				  "not contain the full /dev path, please change to '$_device'.\n\n";
				delete $$CONFIG{'HANDLER'}->{$handler}->{'DEVICE'}->{$device};
				$$CONFIG{'HANDLER'}->{$handler}->{'DEVICE'}->{$_device} = $tree;
				$warnings++;
			} elsif ($device =~ /\//) {
				print "\t-> FATAL: Device '$device' configured for handler '$handler' may not ".
				  "contain character '/'.\n";
				$errors++;
			}

			$cdevices{$device}++;
		}
	}

	foreach my $driver (keys %{$$CONFIG{'TARGET_DRIVER'}}) {
		foreach my $target (keys %{$$CONFIG{'TARGET_DRIVER'}->{$driver}->{'TARGET'}}) {
			my $tgt = $$CONFIG{'TARGET_DRIVER'}->{$driver}->{'TARGET'}->{$target};

			if (defined($$tgt{'LUN'})) {
				foreach my $lun (keys %{$$tgt{'LUN'}}) {
					foreach my $device (keys %{$$tgt{'LUN'}->{$lun}}) {
						if (!defined($cdevices{$device})) {
							print "\t-> WARNING: Device '$device' associated with driver/target ".
							  "'$driver/$target' at LUN $lun is not defined within configuration, ".
							  "removing it.\n\n";
							delete $$tgt{'LUN'}->{$lun};
							$warnings++;
						}
					}
				}

				if (!defined($$tgt{'LUN'}->{'0'})) {
					print "\t-> WARNING: No LUN 0 defined for driver/target '$driver/$target'. ".
					  "Many initiators require a LUN 0 to be defined.\n\n";
					$warnings++;
				}
			}

			if (defined($$tgt{'GROUP'})) {
				foreach my $group (keys %{$$tgt{'GROUP'}}) {
					if (defined($$tgt{'GROUP'}->{$group}->{'LUN'})) {
						foreach my $lun (keys %{$$tgt{'GROUP'}->{$group}->{'LUN'}}) {
							foreach my $device (keys %{$$tgt{'GROUP'}->{$group}->{'LUN'}->{$lun}}) {
								if (!defined($cdevices{$device})) {
									print "\t-> WARNING: Device '$device' associated with ".
									  "driver/target '$driver/$target' at LUN $lun is not ".
									  "defined within configuration, removing it.\n\n";
									delete $$tgt{'GROUP'}->{$group}->{'LUN'}->{$lun};
									$warnings++;
								}
							}
						}

						if (!defined($$tgt{'GROUP'}->{$group}->{'LUN'}->{'0'})) {
							print "\t-> WARNING: No LUN 0 defined for driver/target/group '$driver/$target/$group'. ".
							  "Many initiators require a LUN 0 to be defined.\n\n";
							$warnings++;
						}
					}
				}
			}
		}
	}

	if ($errors) {
		print "\t-> Done, $errors errors found.\n";
		return TRUE;
	}

	print "\t-> Done, $warnings warnings found.\n\n";

	return FALSE;
}

sub applyConfiguration {
	my $force = shift;
	my $changes = 0;

	readConfigFile() if (!$CONFIG);

	print "-> Applying configuration.\n";

	# Apply config deletions
	if ($force) {
		$changes += applyConfigAssignments($CONFIG, $force, TRUE);
		my $rc = readWorkingConfig($force);
		exit $rc if ($rc);
	}

	# Apply config additions
	$changes += applyConfigDevices($CONFIG, $force);
	$changes += applyConfigAssignments($CONFIG, $force);

	# And SCST attributes..
	my %_attributes;
	foreach my $item (keys %{$CONFIG}) {
		next if ($item eq 'HANDLER');
		next if ($item eq 'TARGET_DRIVER');
		$_attributes{$item} = $$CONFIG{$item};
	}

	my $attributes = configToAttr(\%_attributes);

	$changes += setScstAttributes($attributes);

	print "\t-> Done, $changes change(s) made.\n";

	return $changes;
}

sub applyConfigDevices {
	my $config = shift;
	my $deletions = shift;
	my $changes = 0;

	my $handlers = $CURRENT{'handler'};

	foreach my $handler (keys %{$handlers}) {
		foreach my $device (@{$$handlers{$handler}}) {
			if (!defined($$config{'HANDLER'}->{$handler}->{'DEVICE'}->{$device})) {
				my $attributes = $SCST->deviceAttributes($device);

				if ($deletions) {
					closeDevice($handler, $device, $deletions);
					$changes++;
				} else {
					print "\t-> Device '$device' is not in configuration. Use -force to close it.\n";
				}
			}
		}
	}

	$handlers = $$config{'HANDLER'};

	foreach my $handler (sort keys %{$handlers}) {
		if (defined($$handlers{$handler}->{'DEVICE'})) {
			my $devices = $$handlers{$handler}->{'DEVICE'};

			foreach my $device (sort keys %{$devices}) {
				my %_attributes;
				my %_cattributes;
				foreach my $item (keys %{$$devices{$device}}) {
					$_attributes{$item} = $$devices{$device}->{$item};
				}

				my $attributes = configToAttr(\%_attributes);
				my $create_attrs = configToAttr(\%_attributes);
				my $possible = $SCST->deviceCreateAttributes($handler);
				immediateExit($SCST->errorString());
				filterCreateAttributes($possible, $create_attrs, FALSE);
				filterCreateAttributes($possible, $attributes, TRUE);

				if (handlerHasDevice($handler, $device)) {
					my $old_create_attrs = $SCST->deviceAttributes($device);
					immediateExit($SCST->errorString());
					filterCreateAttributes($possible, $old_create_attrs, FALSE);

					if (compareToKeyAttribute($create_attrs, $old_create_attrs)) {
						print "\t-> Device '$device' is configured differently.\n";

						if ($deletions) {
							print "\t  -> Closing and re-opening with new attributes.\n";
							closeDevice($handler, $device, $deletions);
							openDevice($handler, $device, $create_attrs);
							$changes += 2;
							my $rc = readWorkingConfig($deletions);
							exit $rc if ($rc);
						} else {
							print "\t  -> Use -force to re-open device with new attributes. ".
							  "NOTE: This will disrupt all initiators using this device.\n";
						}
					}

					if (scalar keys %{$attributes}) {
						$changes += setDeviceAttributes($device, $attributes, FALSE);
					}

					next;
				}

				openDevice($handler, $device, $create_attrs);
				$changes++;

				if (scalar keys %{$attributes}) {
					$changes += setDeviceAttributes($device, $attributes, $deletions);
				}
			}
		}

		my %_attributes;
		foreach my $item (keys %{$$handlers{$handler}}) {
			next if ($item eq 'DEVICE');
			$_attributes{$item} = $$handlers{$handler}->{$item};
		}

		my $attributes = configToAttr(\%_attributes);

		if (scalar keys %{$attributes}) {
			$changes += setHandlerAttributes($handler, $attributes, $deletions);
		}

	}

	return $changes;
}

sub applyConfigAssignments {
	my $config = shift;
	my $deletions = shift;
	my $only_del = shift;
	my $changes = 0;

	my $assignments = $CURRENT{'assign'};

	foreach my $driver (keys %{$assignments}) {
		foreach my $target (keys %{$$assignments{$driver}}) {
			my $luns = $$assignments{$driver}->{$target}->{'LUN'};
			my $def_group = $$config{'TARGET_DRIVER'}->{$driver}->{'TARGET'}->{$target};

			foreach my $lun (keys %{$luns}) {
				my $device = $$luns{$lun};

				if (!defined($$def_group{'LUN'}->{$lun}->{$device})) {
					if ($deletions) {
						removeLun($driver, $target, $lun);
						$changes++;
					} else {
						print "\t-> Device '$device' at LUN '$lun' is not in configuration ".
						  "for driver/target '$driver/$target'. ".
						  "Use -force to remove it.\n";
					}
				} else {
					my $c_attrs = configToAttr($$config{'TARGET_DRIVER'}->{$driver}->{'TARGET'}->{$target}->{'LUN'}->{$lun}->{$device});
					my $o_attrs = $SCST->lunAttributes($driver, $target, $lun);
					immediateExit($SCST->errorString());
					my $possible = $SCST->lunCreateAttributes($driver, $target);
					immediateExit($SCST->errorString());

					filterCreateAttributes($possible, $c_attrs, FALSE);

					if (compareToKeyAttribute($c_attrs, $o_attrs)) {
						print "\t-> Assigned device '$device' in target '$target' at ".
						  "LUN '$lun' is configured differently.\n";

						if ($deletions) {
							print "\t  -> Re-assigning device with new attributes.\n";
							removeLun($driver, $target, $lun);
							addLun($driver, $target, $device, $lun, $c_attrs);
							$changes += 2;
						} else {
							print "\t  -> Use -force to re-assign device with new attributes. ".
							  "NOTE: This will disrupt all initiators using this device.\n";
						}
					}
				}
			}

			foreach my $group (keys %{$$assignments{$driver}->{$target}->{'GROUP'}}) {
				my $luns = $$assignments{$driver}->{$target}->{'GROUP'}->{$group}->{'LUN'};

				my $def_group = $$config{'TARGET_DRIVER'}->{$driver}->{'TARGET'}->{$target}->{'GROUP'}->{$group};

				foreach my $lun (keys %{$luns}) {
					my $device = $$luns{$lun};

					if (!defined($$def_group{'LUN'}->{$lun}->{$device})) {
						if ($deletions) {
							removeLun($driver, $target, $lun, $group);
							$changes++;
						} else {
							print "\t-> Device '$device' at LUN '$lun' is not in configuration ".
							  "for driver/target/group '$driver/$target/$group'. ".
							  "Use -force to remove it.\n";
						}
					} else {
						my $c_attrs = configToAttr($$config{'TARGET_DRIVER'}->{$driver}->{'TARGET'}->{$target}->{'GROUP'}->{$group}->{'LUN'}->{$lun}->{$device});
						my $o_attrs = $SCST->lunAttributes($driver, $target, $lun, $group);
						immediateExit($SCST->errorString());
						my $possible = $SCST->lunCreateAttributes($driver, $target, $group);
						immediateExit($SCST->errorString());

						filterCreateAttributes($possible, $c_attrs, FALSE);

						if (compareToKeyAttribute($c_attrs, $o_attrs)) {
							print "\t-> Assigned device '$device' in group '$group' ".
							  "at LUN '$lun' is configured differently.\n";

							if ($deletions) {
								print "\t  -> Re-assigning device with new attributes.\n";
								removeLun($driver, $target, $lun, $group);
								addLun($driver, $target, $device, $lun, $c_attrs, $group);
								$changes += 2;
							} else {
								print "\t  -> Use -force to re-assign device with new attributes. ".
								  "NOTE: This will disrupt all initiators using this device.\n";
							}
						}
					}
				}

				if (!defined($$config{'TARGET_DRIVER'}->{$driver}->{'TARGET'}->{$target}->{'GROUP'}->{$group})) {
					if ($deletions) {
						removeGroup($driver, $target, $group);
						$changes++;
					} else {
						print "\t-> Group '$group' is not in configuration. Use -force to remove.\n";
					}

					next;
				}
			}
		}
	}

	return $changes if ($only_del);

	$assignments = $$config{'TARGET_DRIVER'};

	foreach my $driver (sort keys %{$assignments}) {
		if (!defined($CURRENT{'assign'}->{$driver})) {
			immediateExit("Target driver '$driver' is not loaded or available.");
		}

		my %_attributes;
		my %special;
		foreach my $item (keys %{$$assignments{$driver}}) {
			if ($item eq 'TARGET') {
				$changes += applyTargetAssignments($driver, $$assignments{$driver}->{$item},
				  $deletions);
			} elsif ($item ne 'enabled') {
				# Enable driver last
				$_attributes{$item} = $$assignments{$driver}->{$item};
			} else {
				$special{$item} = $$assignments{$driver}->{$item};

			}
		}

		my $attributes = configToAttr(\%_attributes);

		if (scalar keys %{$attributes}) {
			$changes += setDriverAttributes($driver, $attributes, $deletions);
		}

		$attributes = configToAttr(\%special);
		my $d_attributes = $SCST->driverAttributes($driver);

		if (defined($$d_attributes{'enabled'}) &&
		    ($$d_attributes{'enabled'}->{'value'} != $$attributes{'enabled'})) {
			setDriverAttribute($driver, 'enabled', $$attributes{'enabled'});
			$changes++;
		}
	}

	return $changes;
}

sub applyInitiatorAssignments {
	my $driver = shift;
	my $target = shift;
	my $groups = shift;
	my $deletions = shift;
	my %configured;
	my %running;
	my $changes = 0;

	my $current = $CURRENT{'assign'}->{$driver}->{$target}->{'GROUP'};

	foreach my $group (keys %{$current}) {
		my $initiators = $$current{$group}->{'INITIATORS'};

		foreach my $init (@{$initiators}) {
			$running{$init} = $group;
		}
	}

	foreach my $group (keys %{$groups}) {
		my $initiators = $$groups{$group}->{'INITIATOR'};

		foreach my $init (keys %{$initiators}) {
			$configured{$init} = $group;
		}
	}

	foreach my $init (keys %running) {
		if (!defined($configured{$init})) {
			my $group = $running{$init};

			if ($deletions) {
				removeInitiator($driver, $target, $group, $init);
				$changes++;
			} else {
				print "\t-> Initiator '$init' is not in configuration ".
				  "for driver/target/group '$driver/$target/$group'. ".
				  "Use -force to remove it.\n";
			}
		} else {
			if ($configured{$init} ne $running{$init}) {
				if ($deletions) {
					moveInitiator($driver, $target, $running{$init}, $init, $configured{$init});
					$changes++;
				} else {
					print "\t-> Initiator '$init' is moving to group '".
					  $configured{$init}."' from group '".$running{$init}."'. ".
					  "Use -force to move it.\n";
				}
			}
		}
	}

	foreach my $init (keys %configured) {
		if (!defined($running{$init})) {
			addInitiator($driver, $target, $configured{$init}, $init);
			$changes++;
		}
	}

	return $changes;
}

sub applyTargetAssignments {
	my $driver = shift;
	my $targets = shift;
	my $deletions = shift;
	my $changes = 0;

	foreach my $target (keys %{$CURRENT{'assign'}->{$driver}}) {
		if (!defined($$targets{$target})) {
			my $isVirtual = ($SCST->targetType($driver, $target) == $SCST::SCST::TGT_TYPE_VIRTUAL);

			if ($deletions && $isVirtual) {
				my $rc = removeVirtualTarget($driver, $target);
				immediateExit($SCST->errorString($rc)) if ($rc);
				$changes++;
			} else {
				print "\t-> Virtual target '$target' for driver '$driver' is not in configuration. ".
				  "Use -force to remove it.\n";
			}
		}
	}

	my $possible;

	my $is_virtual = $SCST->driverIsVirtualCapable($driver);

	if ($is_virtual) {
		$possible = $SCST->targetCreateAttributes($driver);
	}

	foreach my $target (sort keys %{$targets}) {
		if (!defined($CURRENT{'assign'}->{$driver}->{$target})) {
			if (!$is_virtual) {
				immediateExit("Target '$target' for driver '$driver' does not exist.");
			} else {
				my %_attributes;

				foreach my $item (keys %{$$targets{$target}}) {
					next if ($item eq 'GROUP');
					next if ($item eq 'LUN');
					$_attributes{$item} = $$targets{$target}->{$item};
				}

				my $attributes = configToAttr(\%_attributes);

				if (defined($$attributes{'HW_TARGET'})) {
					immediateExit("Hardware target '$target' for driver '$driver' does not exist.");
				}					

				filterCreateAttributes($possible, $attributes, FALSE);

				my $rc = addVirtualTarget($driver, $target, $attributes);
				immediateExit($SCST->errorString($rc)) if ($rc);
				$changes++ if (!$rc);
			}
		}

		# Apply attribute changes first in case we want a different address method applied
		my %_attributes;
		foreach my $item (keys %{$$targets{$target}}) {
			# Handle enabled attribute last
			next if (($item eq 'GROUP') || ($item eq 'LUN') || ($item eq 'enabled'));
			$_attributes{$item} = $$targets{$target}->{$item};
		}

		my $attributes = configToAttr(\%_attributes);
		filterCreateAttributes($possible, $attributes, TRUE);
		$changes += setTargetAttributes($driver, $target, $attributes, $deletions);

		%_attributes = ();
		foreach my $item (keys %{$$targets{$target}}) {
			if ($item eq 'GROUP') {
				$changes += applyGroupAssignments($driver, $target, $$targets{$target}->{$item});
				$changes += applyInitiatorAssignments($driver, $target, $$targets{$target}->{$item}, $deletions);
			} elsif ($item eq 'LUN') {
				$changes += applyLunAssignments($driver, $target, undef, $$targets{$target}->{$item});
			} elsif ($item eq 'enabled') {
				$_attributes{$item} = $$targets{$target}->{$item};
			}
		}

		$attributes = configToAttr(\%_attributes);
		my $t_attributes = $SCST->targetAttributes($driver, $target);

		if (defined($$t_attributes{'enabled'}) &&
		    ($$t_attributes{'enabled'}->{'value'} != $$attributes{'enabled'})) {
			setTargetAttribute($driver, $target, 'enabled', $$attributes{'enabled'});
			$changes++;
		}
	}

	return $changes;
}

sub applyGroupAssignments {
	my $driver = shift;
	my $target = shift;
	my $groups = shift;
	my $changes = 0;

	foreach my $group (sort keys %{$groups}) {
		if (!defined($CURRENT{'assign'}->{$driver}->{$target}->{'GROUP'}->{$group})) {
			addGroup($driver, $target, $group);
			$changes++;
		}


		my %_attributes;
		foreach my $item (keys %{$$groups{$group}}) {
			if ($item eq 'LUN') {
				$changes += applyLunAssignments($driver, $target, $group,
				  $$groups{$group}->{$item});
			} elsif ($item eq 'INITIATOR') {
				next;
			} else {
				$_attributes{$item} = $$groups{$group}->{$item};
			}
		}

		my $attributes = configToAttr(\%_attributes);

		$changes += setGroupAttributes(undef, $driver, $target, $group, $attributes);
	}

	return $changes;
}

sub applyLunAssignments {
	my $driver = shift;
	my $target = shift;
	my $group = shift;
	my $luns = shift;
	my $c_luns;
	my $changes = 0;

	if ($group) {
		$c_luns = $CURRENT{'assign'}->{$driver}->{$target}->{'GROUP'}->{$group}->{'LUN'};
	} else {
		$c_luns = $CURRENT{'assign'}->{$driver}->{$target}->{'LUN'};
	}

	foreach my $lun (sort keys %{$luns}) {
		if (!defined($$c_luns{$lun})) {
			if ((scalar keys %{$$luns{$lun}}) > 1) {
				immediateExit("Invalid configuration encountered. ".
				  "Driver/target/group/lun $driver/$target/$group/$lun ".
				  "has multiple devices assigned.");
			}

			foreach my $device (sort keys %{$$luns{$lun}}) {
				my $attributes = configToAttr($$luns{$lun}->{$device});
				addLun($driver, $target, $device, $lun, $attributes, $group);
				$changes++;
			}
		}
	}

	return $changes;
}

sub filterCreateAttributes {
	my $creates = shift;
	my $attrs = shift;
	my $keep = shift;

	foreach my $attribute (keys %{$attrs}) {
		my $good = defined($$creates{$attribute}) ? TRUE : FALSE;

		if (($keep && $good) || (!$keep && !$good)) {
			delete $$attrs{$attribute};
		}
	}
}

sub compareToKeyAttribute {
	my $first = shift;
	my $second = shift;

	foreach my $attr (keys %{$first}) {
		if (defined($$second{$attr}->{'keys'})) {
			next if (!defined($$second{$attr}->{'keys'}->{'0'}->{'value'}) && ($$first{$attr} == 0));
			return TRUE if ($$first{$attr} ne $$second{$attr}->{'keys'}->{'0'}->{'value'});
		} else {
			next if (!defined($$second{$attr}->{'value'}) && ($$first{$attr} == 0));
			return TRUE if ($$first{$attr} ne $$second{$attr}->{'value'});
		}
	}

	foreach my $attr (keys %{$second}) {
		if (defined($$second{$attr}->{'keys'}) && !defined($$first{$attr})) {
			return TRUE;
		}
	}

	return FALSE;
}

sub clearConfiguration {
	my $assignments = $CURRENT{'assign'};

	print "-> Clearing running configuration.\n";

	foreach my $driver (sort keys %{$assignments}) {
		foreach my $target (sort keys %{$$assignments{$driver}}) {
			foreach my $group (sort keys %{$$assignments{$driver}->{$target}->{'GROUP'}}) {
				clearInitiators($driver, $target, $group);
				clearLuns($driver, $target, $group);
				removeGroup($driver, $target, $group, TRUE);
			}

			clearLuns($driver, $target);
			removeTargetDynamicAttributes($driver, $target);

			if ($SCST->targetType($driver, $target) == $SCST::SCST::TGT_TYPE_VIRTUAL) {
				my $rc = removeVirtualTarget($driver, $target);
				issueWarning($SCST->errorString($rc)) if ($rc);
			}
		}

		clearDriverDynamicAttributes($driver);
	}

	my $handlers = $CURRENT{'handler'};

	foreach my $handler (sort keys %{$handlers}) {
		foreach my $device (@{$$handlers{$handler}}) {
			my $attributes = $SCST->deviceAttributes($device);
			closeDevice($handler, $device, TRUE);
		}
	}

	# Todo - check return code

	my $drivers = $SCST->drivers();

	foreach my $driver (@{$drivers}) {
		my $targets = $SCST->targets($driver);

		foreach my $target (@{$targets}) {
			disableTarget($driver, $target);
		}

		disableDriver($driver);
	}

	print "\t-> Configuration cleared.\n";

	return FALSE;
}

sub addVirtualTarget {
	my $driver = shift;
	my $target = shift;
	my $attributes = shift;

	# Enable all hardware targets before creating virtual ones
	my $targets = $SCST->targets($driver);

	foreach my $_target (@{$targets}) {
		my $attributes = $SCST->targetAttributes($driver, $_target);

		if (defined($$attributes{'hw_target'}) &&
		    !$$attributes{'enabled'}->{'value'}) {
			enableTarget($driver, $_target);
		}
	}

	print "\t-> Creating target '$target' for driver '$driver': ";
	my $rc = $SCST->addVirtualTarget($driver, $target, $attributes);
	print "done.\n";

	return $rc;
}

sub removeVirtualTarget {
	my $driver = shift;
	my $target = shift;

	print "\t-> Removing virtual target '$target' from driver '$driver': ";
	my $rc = $SCST->removeVirtualTarget($driver, $target);
	print "done.\n";

	return $rc;
}	

####################################################################

sub listHandlers {
	my $handler = shift;

	return listHandler($handler) if ($handler);

	my $handlers = $SCST->handlers();

	my $l_handler;
	foreach my $handler (@{$handlers}) {
		$l_handler = length($handler) if ($l_handler < length($handler));
	}

	print "\tHandler\n";
	print "\t";
	for (my $x = 0; $x < $l_handler; $x++) {
		print "-";
	};
	print "\n";

	foreach my $handler (@{$handlers}) {
		print "\t$handler\n";
	}	

	return FALSE;
}

sub listHandler {
	my $handler = shift;
	my %toprint;

	my $got_handler = ($handler ne '');
	my $handlers = $SCST->handlers();

	my $l_device;
	my $l_handler;

	foreach my $_handler (@{$handlers}) {
		$handler = $_handler if (!$got_handler);

		if ($handler eq $_handler) {
			$toprint{$handler}++;

			$l_handler = length($handler) if ($l_handler < length($handler));

			my $devices = $SCST->devicesByHandler($handler);

			if ($#{$devices} == -1) {
				push @{$devices}, '-';
			}

			foreach my $device (@{$devices}) {
				$l_device = length($device) if ($l_device < length($device));
			}
		}
	}

	if (scalar keys %toprint) {
		printf("\t%-*s     %-*s\n", $l_handler, 'Handler', $l_device, 'Device');
		print "\t";
		for (my $x = 0; $x < ($l_handler + $l_device + 5); $x++) {
			print "-";
		};
		print "\n";

		foreach my $handler (keys %toprint) {
			my $devices = $SCST->devicesByHandler($handler);
			my $first = TRUE;

			if ($#{$devices} == -1) {
				push @{$devices}, '-';
			}

			foreach my $device (@{$devices}) {
				if ($first) {
					printf("\t%-*s    %-*s\n", $l_handler, $handler, $l_device, $device);
					$first = FALSE;
				} else {
					printf("\t%-*s    %-*s\n", $l_handler, '', $l_device, $device);
				}
			}
		}
	} else {
		print "No such handler '$handler' found.\n";
	}

	return FALSE;
}

sub listDevices {
	my $device = shift;
	my $nonkey = shift;

	return listDevice($device, $nonkey) if ($device ne '');
	return listHandler();
}

sub listDevice {
	my $device = shift;
	my $nonkey = shift;

	my $attributes = $SCST->deviceAttributes($device);

	return TRUE if issueWarning($SCST->errorString());

	if (!scalar(keys %{$attributes})) {
		print "No such device '$device' exists.\n";
		return;
	}

	return listAttributes($attributes, $nonkey);
}

sub listDrivers {
	my $driver = shift;

	return listTargets($driver, undef) if ($driver ne '');

	my $drivers = $SCST->drivers();

	my $l_driver;
	foreach my $driver (@{$drivers}) {
		$l_driver = length($driver) if ($l_driver < length($driver));
	}

	print "\tDriver\n";
	print "\t";
	for (my $x = 0; $x < $l_driver; $x++) {
		print "-";
	}
	print "\n";

	foreach my $driver (@{$drivers}) {
		print "\t$driver\n";
	}

	return FALSE;
}

sub listTargets {
	my $driver = shift;
	my $target = shift;
	my %toprint;

	return listGroups($driver, $target, undef) if (($target ne '') && ($driver ne ''));

	my $got_driver = ($driver ne '');

	my $drivers = $SCST->drivers();

	my $l_driver;
	my $l_target;

	foreach my $_driver (@{$drivers}) {
		$driver = $_driver if (!$got_driver);

		if ($driver eq $_driver) {
			$toprint{$driver}++;

			$l_driver = length($driver) if ($l_driver < length($driver));

			my $targets = $SCST->targets($driver);

			foreach my $target (@{$targets}) {
				$l_target = length($target) if ($l_target < length($target));
			}
		}
	}

	if (scalar(keys %toprint)) {
		printf("\t%-*s %-*s\n", $l_driver, 'Driver', $l_target, 'Target');
		print "\t";
		for (my $x = 0; $x < ($l_driver + $l_target + 1); $x++) {
			print "-";
		}
		print "\n";

		my %p;

		foreach my $driver (keys %toprint) {
			my $targets = $SCST->targets($driver);

			foreach my $target (@{$targets}) {
				if (!defined($p{$driver})) {
					printf("\t%-*s %-*s\n", $l_driver, $driver, $l_target, $target);
					$p{$driver}++;
				} else {
					printf("\t%-*s %-*s\n", $l_driver, '', $l_target, $target);
				}
			}
		}
	} else {
		print "No such driver '$driver' exists.\n";
	}

	return FALSE;
}

sub listSessions {
	my $drivers = $SCST->drivers();

	foreach my $driver (@{$drivers}) {
		my $targets = $SCST->targets($driver);

		foreach my $target (@{$targets}) {
			my $had_sessions = FALSE;

			print "Driver/Target: $driver/$target\n\n";

			my $sessions = $SCST->sessions($driver, $target);
			foreach my $session (keys %{$sessions}) {
				print "\tSession: $session\n\n";

				my %attributes;
				foreach my $attr (keys %{$$sessions{$session}}) {
					if ($attr eq 'luns') {           
						foreach my $lun (keys %{$$sessions{$session}->{'luns'}}) {
							$attributes{"LUN $lun"}->{'value'} =     
							  $$sessions{$session}->{'luns'}->{$lun};
						}
					} else {
						$attributes{$attr}->{'value'} = $$sessions{$session}->{$attr}->{'value'};
					}
				}

				listAttributes(\%attributes, TRUE);

				print "\n";

				$had_sessions = TRUE;
			}

			if (!$had_sessions) {
				print "\t(no sessions)\n\n";
			}
		}
	}

	return FALSE;
}

sub listGroup {
	my $driver = shift;
	my $target = shift;
	my $group = shift;
	my $luns = shift;
	my $initiators = shift;

	$luns = $SCST->luns($driver, $target, $group) if (!$luns);
	$initiators = $SCST->initiators($driver, $target, $group)
	  if (($initiators eq '') && ($group ne ''));

	return TRUE if issueWarning($SCST->errorString());

	if ((keys %{$luns}) || ($#{$initiators} > -1)) {
		my $l_device;
		my $l_initiator;

		foreach my $lun (keys %{$luns}) {
			$l_device = length($$luns{$lun}) if (length($$luns{$lun}) > $l_device);
		}

		foreach my $initiator (@{$initiators}) {
			$l_initiator = length($initiator) if (length($initiator) > $l_initiator);
		}

		print "Assigned LUNs:\n\n";

		if (keys %{$luns}) {
			printf("\t%-4s %-*s\n", 'LUN', $l_device, 'Device');
			print "\t";
			for (my $x = 0; $x < ($l_device + 5); $x++) {
				print "-";
			}
			print "\n";

			foreach my $lun (sort keys %{$luns}) {
				my $device = $$luns{$lun};

				printf("\t%-4s %-*s\n", $lun, $l_device, $$luns{$lun});
			}
		} else {
			print "\t(none)\n";
		}

		if (defined($group)) {
			print "\nAssigned Initiators:\n\n";

			if ($#{$initiators} > -1) {
				print "\tInitiator\n";
				print "\t";
				for (my $x = 0; $x < $l_initiator; $x++) {
					print "-";
				}
				print "\n";

				foreach my $initiator (@{$initiators}) {
					print "\t$initiator\n";
				}
			} else {
				print "\t(none)\n";
			}
		}		
	} else {
		if (defined($group)) {
			print "Group '$group' has no associated LUNs or initiators.\n";
		} else {
			print "Driver/target '$driver/$target' has no associated LUNs.\n";
		}
	}

	return FALSE;
}

sub listGroups {
	my $driver = shift;
	my $target = shift;
	my $group = shift;
	my $found = FALSE;

	if (($group ne '') && ($driver ne '') && ($target ne '')) {
		return listGroup($driver, $target, $group);
	}

	my $got_group = $group;
	my $got_target = $target;
	my $got_driver = $driver;

	my $drivers = $SCST->drivers();

	foreach my $_driver (@{$drivers}) {
		$driver = $_driver if (!$got_driver);

		if ($driver eq $_driver) {
			my $targets = $SCST->targets($driver);
			foreach my $_target (@{$targets}) {
				$target = $_target if (!$got_target);

				if ($target eq $_target) {
					$found++;
					print "Driver: $driver\n";
					print "Target: $target\n\n";

					listGroup($driver, $target, undef);

					print "\n";

					my $groups = $SCST->groups($driver, $target);
					foreach my $_group (@{$groups}) {
						$group = $_group if (!$got_group);

						if ($group eq $_group) {
							print "Group: $group\n\n";

							listGroup($driver, $target, $group);

							print "\n\n";
						}
					}
				}
			}
		}
	}

	if (!$found) {
		if ($got_driver && $got_target) {
			print "Driver/target '$driver/$target' not found\n";
		} elsif (!$got_target) {
			print "Driver '$driver' not found.\n";
		} else {
			print "Target '$target' not found.\n";
		}
	}

	return FALSE;
}

sub listExported {
	my $device = shift;
	my $attributes = shift;

	$attributes = $SCST->deviceAttributes($device) if (!$attributes);

	return TRUE if issueWarning($SCST->errorString());

	if (keys %{$$attributes{'exported'}}) {
		my $exported = $$attributes{'exported'}->{'value'};

		my $l_driver;
		my $l_target;
		my $l_group;

		foreach my $driver (keys %{$exported}) {
			$l_driver = length($driver)
			  if (length($driver) > $l_driver);

			foreach my $target (keys %{$$exported{$driver}}) {
				$l_target = length($target)
				  if (length($target) > $l_target);

				foreach my $group (keys %{$$exported{$driver}->{$target}}) {
					$l_group = length($group)
					  if (length($group) > $l_group);
				}
			}
		}

		print "Device '$device' is currently in use by the following:\n\n";
		printf("\t%-*s %-*s %-*s %-4s\n", $l_driver, 'Driver', $l_target, 'Target',
		  $l_group, 'Group', 'Lun');
		print "\t";
		for (my $x = 0; $x < ($l_driver + $l_target + $l_group + 5); $x++) {
			print "-";
		}
		print "\n";

		foreach my $driver (sort keys %{$exported}) {
			foreach my $target (sort keys %{$$exported{$driver}}) {
				foreach my $group (sort keys %{$$exported{$driver}->{$target}}) {
					my $lun = $$exported{$driver}->{$target}->{$group};
					printf("\t%-*s %-*s %-*s %-4s\n", $l_driver, $driver,
					  $l_target, $target, $l_group, $group, $lun);
				}
			}
		}
	} else {
		print "Device '$device' is not currently in use by any group.\n";
	}

	return FALSE;
}

sub listAttributes {
	my $attributes = shift;
	my $nonkey = shift;
	my $l_attr = 9;
	my $l_value = 5;

	foreach my $attribute (keys %{$attributes}) {
		if (defined($$attributes{$attribute}->{'keys'})) {
			$l_attr = length($attribute) if ($l_attr < length($attribute));

			foreach my $key (keys %{$$attributes{$attribute}->{'keys'}}) {
				my $value = $$attributes{$attribute}->{'keys'}->{$key}->{'value'};
				$l_value = length($value) if ($l_value < length($value));
			}
		} elsif ($nonkey) {
			$l_attr = length($attribute) if ($l_attr < length($attribute));
			my $value = $$attributes{$attribute}->{'value'};
			if (($attribute eq 'trace_level') && $value) {
				foreach my $level (split(/\|/, $value)) {
					$level =~ s/^\s+//; $level =~ s/\s+$//;
					$l_value = length($level) if ($l_value < length($level));
				}

				next;
			}
			$l_value = length($value) if ($l_value < length($value));
		}
	}

	printf("\t%-*s     %-*s     %-*s     %-*s\n", $l_attr, 'Attribute', $l_value,
	  'Value', 9, 'Writable', 3, 'KEY');
	print "\t";
	for (my $x = 0; $x < ($l_attr + $l_value + 27); $x++) {
		print "-";
	};
	print "\n";

	my $found = FALSE;

	foreach my $attribute (keys %{$attributes}) {
		my $first = TRUE;

		if (defined($$attributes{$attribute}->{'keys'})) {
			foreach my $key (keys %{$$attributes{$attribute}->{'keys'}}) {
				my $value = $$attributes{$attribute}->{'keys'}->{$key}->{'value'};
				my $static = ($$attributes{$attribute}->{'static'}) ? 'No' : 'Yes';
				$value = '<not set>' if ($value eq '');
				if ($first) {
					printf("\t%-*s     %-*s     %-*s     %-*s\n",
					  $l_attr, $attribute, $l_value, $value, 9, $static, 3, 'Yes');
					$first = FALSE;
				} else {
					printf("\t%-*s     %-*s     %-*s     %-*s\n",
					  $l_attr, '', $l_value, $value, 9, $static, 3, 'Yes');
				}

				$found++;
			}
		} elsif ($nonkey) {
			my $value = $$attributes{$attribute}->{'value'};
			my $static = ($$attributes{$attribute}->{'static'}) ? 'No' : 'Yes';
			if (($attribute eq 'trace_level') && $value) {
				foreach my $level (split(/\|/, $value)) {
					$level =~ s/^\s+//; $level =~ s/\s+$//;
					if ($first) {
						printf("\t%-*s     %-*s     %-*s     %-*s\n",
						  $l_attr, $attribute, $l_value, $level, 9, $static, 3, 'No');
						$first = FALSE;
					} else {
						printf("\t%-*s     %-*s     %-*s     %-*s\n",
						  $l_attr, '', $l_value, $level, 9, $static, 3, 'No');
					}

					$found++;
				}

				next;
			}

			next if ((ref($value) eq 'HASH') || (ref($value) eq 'ARRAY'));

			$value = '<n/a>' if (!defined($value));
			$value = '<not set>' if ($value eq '');
			printf("\t%-*s     %-*s     %-*s     %-*s\n",
			  $l_attr, $attribute, $l_value, $value, 9, $static, 3, 'No');

			$found++;
		}
	}

	if (!$found) {
		print "\t(none)\n";
	}

	if ($nonkey && defined($$attributes{'trace_level'})) {
		my $found = FALSE;
		print "\n\tPossible trace levels:\n".
		  "\t  (use trace_level=\"add <level>\", none, all or default to set):\n";

		my $count = 0;
		foreach my $entry (@{$$attributes{'trace_level'}->{'set'}}) {
			print "\t\t" if (!$count);
			print "$entry, ";
			$count++;
			if ($count == 5) {
				print "\n";
				$count = 0;
			}
		}

		print "\n";
	}

	return FALSE;
}

sub listScstAttributes {
	my $nonkey = shift;

	my $attributes = $SCST->scstAttributes();

	return TRUE if issueWarning($SCST->errorString());

	if (!scalar(keys %{$attributes})) {
		print "FATAL: Unable to get a list of SCST attributes! Please make sure SCST is loaded.\n";
		return;
	}

	return listAttributes($attributes, $nonkey);
}

sub listHandlerAttributes {
	my $handler = shift;
	my $nonkey = shift;

	my $attributes = $SCST->handlerAttributes($handler);

	return TRUE if issueWarning($SCST->errorString());

	if (!scalar(keys %{$attributes})) {
		print "No such handler '$handler' found.\n";
		return;
	}

	my $rc = listAttributes($attributes, $nonkey);
	return $rc if $rc;

	$attributes = $SCST->deviceCreateAttributes($handler);

	return TRUE if issueWarning($SCST->errorString());
	return TRUE if (!scalar keys %{$attributes});

	print "\n\tDevice CREATE attributes available\n";
	print "\t----------------------------------\n";

	foreach my $attribute (keys %{$attributes}) {
		print "\t$attribute\n";
	}

	return FALSE;
}

sub listDriverAttributes {
	my $driver = shift;
	my $nonkey = shift;

	my $attributes = $SCST->driverAttributes($driver);

	return TRUE if issueWarning($SCST->errorString());

	my $rc = listAttributes($attributes, $nonkey);
	return $rc if ($rc);

	if ($SCST->driverIsVirtualCapable($driver)) {
		$attributes = $SCST->driverDynamicAttributes($driver);
		return TRUE if issueWarning($SCST->errorString());

		if (scalar keys %{$attributes}) {
			print "\n\tDynamic attributes available\n";
			print "\t----------------------------\n";

			foreach my $attribute (keys %{$attributes}) {
				print "\t$attribute\n";
			}
		}

		my $attributes = $SCST->targetCreateAttributes($driver);

		return TRUE if issueWarning($SCST->errorString());
		return TRUE if (!scalar keys %{$attributes});

		print "\n\tTarget CREATE attributes available:\n";
		print "\t-----------------------------------\n";

		foreach my $attribute (keys %{$attributes}) {
			print "\t$attribute\n";
		}
	}

	return FALSE;
}

sub listTargetAttributes {
	my $driver = shift;
	my $target = shift;
	my $nonkey = shift;

	my $attributes = $SCST->targetAttributes($driver, $target);

	return TRUE if issueWarning($SCST->errorString());

	if (!scalar(keys %{$attributes})) {
		print "No such driver/target '$driver/$target' found.\n";
		return;
	}

	my $rc = listAttributes($attributes, $nonkey);
	return $rc if $rc;

	if ($SCST->driverIsVirtualCapable($driver)) {
		$attributes = $SCST->targetDynamicAttributes($driver);
		return TRUE if issueWarning($SCST->errorString());

		if (scalar keys %{$attributes}) {
			print "\n\tDynamic attributes available\n";
			print "\t----------------------------\n";

			foreach my $attribute (keys %{$attributes}) {
				print "\t$attribute\n";
			}
		}
	}

	$attributes = $SCST->lunCreateAttributes($driver, $target);

	return TRUE if issueWarning($SCST->errorString());
	return TRUE if (!scalar keys %{$attributes});

	print "\n\tLUN CREATE attributes available\n";
	print "\t-------------------------------\n";

	foreach my $attribute (keys %{$attributes}) {
		print "\t$attribute\n";
	}

	return FALSE;
}

sub listGroupAttributes {
	my $driver = shift;
	my $target = shift;
	my $group = shift;
	my $nonkey = shift;

	my $attributes = $SCST->groupAttributes($driver, $target, $group);

	return TRUE if issueWarning($SCST->errorString());

	if (!scalar(keys %{$attributes})) {
		print "No such driver/target/group '$driver/$target/$group' found.\n";
		return;
	}

	my $rc = listAttributes($attributes, $nonkey);
	return $rc if $rc;

	$attributes = $SCST->lunCreateAttributes($driver, $target, $group);

	return TRUE if issueWarning($SCST->errorString());
	return TRUE if (!scalar keys %{$attributes});

	print "\n\tLUN CREATE attributes available\n";
	print "\t-------------------------------\n";

	foreach my $attribute (keys %{$attributes}) {
		print "\t$attribute\n";
	}

	$attributes = $SCST->initiatorCreateAttributes($driver, $target, $group);

	return TRUE if issueWarning($SCST->errorString());
	return TRUE if (!scalar keys %{$attributes});

	print "\n\tInitiator CREATE attributes available\n";
	print "\t-------------------------------------\n";

	foreach my $attribute (keys %{$attributes}) {
		print "\t$attribute\n";
	}

	return FALSE;
}

sub listLunAttributes {
	my $driver = shift;
	my $target = shift;
	my $group = shift;
	my $lun = shift;
	my $nonkey = shift;

	my $attributes = $SCST->lunAttributes($driver, $target, $lun, $group);

	return TRUE if issueWarning($SCST->errorString());

	if (!scalar(keys %{$attributes})) {
		if ($group ne '') {
			print "No such driver/target/group/lun '$driver/$target/$group/$lun' found.\n";
		} else {
			print "No such driver/target/lun '$driver/$target/$lun' found.\n";
		}
		return;
	}

	return listAttributes($attributes, $nonkey);
}

sub listInitiatorAttributes {
	my $driver = shift;
	my $target = shift;
	my $group = shift;
	my $initiator = shift;
	my $nonkey = shift;

	my $attributes = $SCST->initiatorAttributes($driver, $target, $group, $initiator);

	# As if writing, initiators didn't have attributes. This will
	# allow us to support it in the future.
	if ($SCST->errorString() =~ /Not a directory/) {
		print "Initiators do not (yet) have attributes.\n";
		return;
	}

	return TRUE if issueWarning($SCST->errorString());

	if (!scalar(keys %{$attributes})) {
		print "No such driver/target/group/initiator '$driver/$target/$group/$initiator' found.\n";
		return;
	}

	return listAttributes($attributes, $nonkey);
}

####################################################################

sub setScstAttribute {
	shift;
	shift;
	shift;
	shift;
	my $attribute = shift;
	my $value = shift;

	print "\t-> Setting SCST attribute '$attribute' to value '$value': ";
	my $rc = $SCST->setScstAttribute($attribute, $value);
	print "done.\n";

	return $rc;
}

sub setScstAttributes {
	my $attributes = shift;
	my $showset = shift;

	my $error = "\t-> WARNING: SCST lacks the settable attribute '%s', ignoring.\n\n";
	my $_attributes = $SCST->scstAttributes();

	return TRUE if issueWarning($SCST->errorString());

	return setAttributes(undef, undef, undef, undef, $attributes,
	  $_attributes, $error, \&setScstAttribute, $showset);
}

sub setDeviceAttribute {
	shift;
	shift;
	shift;
	my $device = shift;
	my $attribute = shift;
	my $value = shift;

	print "\t-> Setting device attribute '$attribute' to value '$value' for device '$device': ";
	my $rc = $SCST->setDeviceAttribute($device, $attribute, $value);
	print "done.\n";

	return $rc;
}

sub setDeviceAttributes {
	my $device = shift;
	my $attributes = shift;
	my $showset = shift;

	my $error = "\t-> WARNING: Device '$device' lacks the settable ".
	  "attribute '%s', ignoring.\n\n";
	my $_attributes = $SCST->deviceAttributes($device);

	return TRUE if issueWarning($SCST->errorString());

	return setAttributes(undef, undef, undef, $device, $attributes,
	  $_attributes, $error, \&setDeviceAttribute, $showset);
}

sub setHandlerAttribute {
	shift;
	shift;
	shift;
	my $handler = shift;
	my $attribute = shift;
	my $value = shift;

	print "\t-> Setting handler attribute '$attribute' to value '$value' for handler '$handler': ";
	my $rc = $SCST->setHandlerAttribute($handler, $attribute, $value);
	print "done.\n";

	return $rc;
}

sub setHandlerAttributes {
	my $handler = shift;
	my $attributes = shift;
	my $showset = shift;

	my $error = "\t-> WARNING: Handler '$handler' lacks the settable ".
	  "attribute '%s', ignoring.\n\n";
	my $_attributes = $SCST->handlerAttributes($handler);

	return TRUE if issueWarning($SCST->errorString());

	return setAttributes(undef, undef, undef, $handler, $attributes,
	  $_attributes, $error, \&setHandlerAttribute, $showset);
}

sub setGroupAttribute {
	shift;
	my $driver = shift;
	my $target = shift;
	my $group = shift;
	my $attribute = shift;
	my $value = shift;

	print "\t-> Setting group attribute '$attribute' to value '$value' for group '$group': ";
	my $rc = $SCST->setGroupAttribute($driver, $target, $group, $attribute, $value);
	print "done.\n";

	return $rc;
}

sub setGroupAttributes {
	shift;
	my $driver = shift;
	my $target = shift;
	my $group = shift;
	my $attributes = shift;
	my $showset = shift;

	my $error = "\t-> WARNING: Driver/target/group '$driver/$target/$group' lacks the settable ".
	  "attribute '%s', ignoring.\n\n";
	my $_attributes = $SCST->groupAttributes($driver, $target, $group);

	return TRUE if issueWarning($SCST->errorString());

	return setAttributes(undef, $driver, $target, $group, $attributes,
	  $_attributes, $error, \&setGroupAttribute, $showset);
}

sub setLunAttribute {
	my $driver = shift;
	my $target = shift;
	my $lun = shift;
	my $group = shift;
	my $attribute = shift;
	my $value = shift;

	if ($group) {
		print "\t-> Setting LUN attribute '$attribute' to value '$value' for ".
		  "driver/target/group/LUN '$driver/$target/$group/$lun': ";
	} else {
		print "\t-> Setting LUN attribute '$attribute' to value '$value' for ".
		  "driver/target/LUN '$driver/$target/$lun': ";
	}

	my $rc = $SCST->setLunAttribute($driver, $target, $lun, $attribute, $value, $group);
	print "done.\n";

	return $rc;
}

sub setLunAttributes {
	my $driver = shift;
	my $target = shift;
	my $lun = shift;
	my $attributes = shift;
	my $group = shift;
	my $showset = shift;
	my $error;

	if ($group) {
		$error = "\t-> WARNING: Driver/target/group/LUN '$driver/$target/$group/$lun' ".
		  "lacks the settable attribute '%s', ignoring.\n\n";
	} else {
		$error = "\t-> WARNING: Driver/target/LUN '$driver/$target/$lun' lacks the settable ".
		  "attribute '%s', ignoring.\n\n";
	}

	my $_attributes = $SCST->lunAttributes($driver, $target, $lun, $group);

	return TRUE if issueWarning($SCST->errorString());

	return setAttributes($driver, $target, $lun, $group, $attributes,
	  $_attributes, $error, \&setLunAttribute, $showset);
}

sub setInitiatorAttribute {
	my $driver = shift;
	my $target = shift;
	my $group = shift;
	my $initiator = shift;
	my $attribute = shift;
	my $value = shift;

	print "\t-> Setting initiator attribute '$attribute' to value '$value' for ".
	  "driver/target/group/initiator '$driver/$target/$group/initiator': ";
	my $rc = $SCST->setInitiatorAttribute($driver, $target, $group,
	  $initiator, $attribute, $value);
	print "done.\n";

	return $rc;
}

sub setInitiatorAttributes {
	my $driver = shift;
	my $target = shift;
	my $group = shift;
	my $initiator = shift;
	my $attributes = shift;
	my $showset = shift;

	my $_attributes = $SCST->initiatorAttributes($driver, $target, $group, $initiator);

	return TRUE if issueWarning($SCST->errorString());

	# As if writing, initiators didn't have attributes. This will
	# allow us to support it in the future.
	if ($SCST->errorString() =~ /Not a directory/) {
		print "Initiators do not (yet) have attributes.\n";
		return;
	}

	my $error = "\t-> WARNING: Driver/target/group/initiator '$driver/$target/$group/$initiator' ".
	  "lacks the settable attribute '%s', ignoring.\n\n";

	return setAttributes($driver, $target, $group, $initiator, $attributes,
	  $_attributes, $error, \&setInitiatorAttribute, $showset);
}

# Sets any non-dynamic attributes
sub setAttributes {
	my $level1 = shift;
	my $level2 = shift;
	my $level3 = shift;
	my $level4 = shift;
	my $attributes = shift;
	my $_attributes = shift;
	my $error = shift;
	my $callback = shift;
	my $showset = shift;
	my %toset;
	my $changes = 0;

	# build caches for easier matching
	foreach my $attribute (keys %{$attributes}) {
		if (ref($$attributes{$attribute}) eq 'ARRAY') {
			foreach my $value (@{$$attributes{$attribute}}) {
				$toset{$attribute}->{$value}++;
			}
		} else {
			$toset{$attribute}->{$$attributes{$attribute}}++;
		}
	}

	my $existing = cacheAttributes($_attributes);

	foreach my $attribute (keys %toset) {
		foreach my $value (keys %{$toset{$attribute}}) {
			if (!defined($$existing{$attribute})) {
				print sprintf($error, $attribute);
				next;
			}

			# already set, move on
			if (defined($$existing{$attribute}->{$value})) {
				print "\t-> Attribute '$attribute' already set to value '$value', ignoring.\n"
				  if ($showset);
				next;
			}

			# Set the existing attribute
			my $rc = $callback->($level1, $level2, $level3, $level4, $attribute, $value);
			issueWarning($SCST->errorString($rc)) if ($rc);
			$changes++ if (!$rc);
		}
	}

	return $changes;
}

sub setDriverAttribute {
	my $driver = shift;
	my $attribute = shift;
	my $value = shift;

	if ($attribute eq 'enabled') {
		my $onoff = $value ? 'Enabling' : 'Disabling';
		print "\t-> $onoff driver '$driver': ";
	} else {
		print "\t-> Setting driver attribute '$attribute' to value '$value' for driver '$driver': ";
	}

	my $rc = $SCST->setDriverAttribute($driver, $attribute, $value);
	print "done.\n";

	return $rc;
}

sub setDriverAttributes {
	my $driver = shift;
	my $attributes = shift;
	my $deletions = shift;
	my %toset;
	my $changes = 0;

	my $driverCap = $SCST->driverIsVirtualCapable($driver);
	my $_attributes = $SCST->driverAttributes($driver);

	return 0 if issueWarning($SCST->errorString());

	# build caches for easier matching
	foreach my $attribute (keys %{$attributes}) {
		if (ref($$attributes{$attribute}) eq 'ARRAY') {
			foreach my $value (@{$$attributes{$attribute}}) {
				$toset{$attribute}->{$value} = FALSE;
			}
		} else {
			$toset{$attribute}->{$$attributes{$attribute}} = FALSE;
		}
	}

	my $existing = cacheAttributes($_attributes);

	# add/change
	foreach my $attribute (keys %toset) {
		foreach my $value (keys %{$toset{$attribute}}) {
			if (!defined($$existing{$attribute}) &&
			    $driverCap && !$SCST->checkDriverDynamicAttributes($driver, $attribute)) {
				addDriverDynamicAttribute($driver, $attribute, $value);
				$changes++;
				next;
			} elsif (!defined($$existing{$attribute})) {
				print "\t-> WARNING: Driver '$driver' lacks the settable attribute ".
				  "'$attribute', ignoring.\n\n";
				next;
			}

			# already set, move on
			if (defined($$existing{$attribute}->{$value})) {
				$$existing{$attribute}->{$value} = TRUE;
				next;
			}

			# Set the existing attribute
			if ($driverCap && !$SCST->checkDriverDynamicAttributes($driver, $attribute)) {
				addDriverDynamicAttribute($driver, $attribute, $value);
				$changes++;
			} else {
				my $rc = setDriverAttribute($driver, $attribute, $value);
				issueWarning($SCST->errorString($rc)) if ($rc);
			}

			$$existing{$attribute}->{$value} = TRUE;
		}
	}

	foreach my $attribute (keys %{$existing}) {
		next if (!$driverCap || $SCST->checkDriverDynamicAttributes($driver, $attribute));

		foreach my $value (keys %{$$existing{$attribute}}) {
			if (!$$existing{$attribute}->{$value}) {
				if ($deletions) {
					removeDriverDynamicAttribute($driver, $attribute, $value);
					$changes++;
				} else {
					print "\t-> Driver dynamic attribute '$attribute' with value '$value' ".
					  "is not in configuration. Use -force to remove it.\n";
				}
			}
		}
	}

	return $changes;
}

sub addDriverDynamicAttributes {
	my $driver = shift;
	my $attributes = shift;

	my $_attributes = $SCST->driverAttributes($driver);

	return TRUE if issueWarning($SCST->errorString());

	my $existing = cacheAttributes($_attributes);

	foreach my $attribute (keys %{$attributes}) {
		if (defined($$existing{$attribute}->{$$attributes{$attribute}})) {
			my $value = $$attributes{$attribute};
			print "\t-> Attribute/value '$attribute/$value' already exists for driver '$driver'.\n";
			next;
		}

		addDriverDynamicAttribute($driver, $attribute, $$attributes{$attribute});
	}

	return FALSE;
}

sub addDriverDynamicAttribute {
	my $driver = shift;
	my $attribute = shift;
	my $value = shift;

	print "\t-> Adding driver attribute '$attribute' with value '$value' for driver '$driver': ";
	my $rc = $SCST->addDriverDynamicAttribute($driver, $attribute, $value);
	print "done.\n";

	immediateExit($SCST->errorString($rc)) if ($rc); 

	return FALSE;
}

sub removeDriverDynamicAttributes {
	my $driver = shift;
	my $attributes = shift;

	my $_attributes = $SCST->driverAttributes($driver);

	return TRUE if issueWarning($SCST->errorString());

	my $existing = cacheAttributes($_attributes);

	foreach my $attribute (keys %{$attributes}) {
		if (!defined($$existing{$attribute}->{$$attributes{$attribute}})) {
			my $value = $$attributes{$attribute};
			print "\t-> Attribute/value '$attribute/$value' doesn't exist for driver '$driver'.\n";
			next;
		}

		removeDriverDynamicAttribute($driver, $attribute, $$attributes{$attribute});
	}

	return FALSE;
}

sub removeDriverDynamicAttribute {
	my $driver = shift;
	my $attribute = shift;
	my $value = shift;

	return TRUE if (!$SCST->driverIsVirtualCapable($driver));

	print "\t-> Removing dynamic attribute '$attribute' with value '$value' for driver '$driver': ";
	my $rc = $SCST->removeDriverDynamicAttribute($driver, $attribute, $value);
	print "done.\n";

	immediateExit($SCST->errorString($rc)) if ($rc);

	return FALSE;
}

sub setTargetAttribute {
	my $driver = shift;
	my $target = shift;
	my $attribute = shift;
	my $value = shift;

	if ($attribute eq 'enabled') {
		my $onoff = $value ? 'Enabling' : 'Disabling';
		print "\t-> $onoff driver/target '$driver/$target': ";
	} else {
		print "\t-> Setting target attribute '$attribute' to value '$value' for ".
		  "driver/target '$driver/$target': ";
	}

	my $rc = $SCST->setTargetAttribute($driver, $target, $attribute, $value);
	print "done.\n";

	immediateExit($SCST->errorString($rc)) if ($rc);

	return FALSE;
}

sub setTargetAttributes {
	my $driver = shift;
	my $target = shift;
	my $attributes = shift;
	my $deletions = shift;
	my %toset;
	my $changes = 0;

	my $driverCap = $SCST->driverIsVirtualCapable($driver);
	my $_attributes = $SCST->targetAttributes($driver, $target);

	return 0 if issueWarning($SCST->errorString());

	# build caches for easier matching
	foreach my $attribute (keys %{$attributes}) {
		if (ref($$attributes{$attribute}) eq 'ARRAY') {
			foreach my $value (@{$$attributes{$attribute}}) {
				$toset{$attribute}->{$value} = FALSE;
			}
		} else {
			$toset{$attribute}->{$$attributes{$attribute}} = FALSE;
		}
	}

	my $existing = cacheAttributes($_attributes);

	# add/change
	foreach my $attribute (keys %toset) {
		foreach my $value (keys %{$toset{$attribute}}) {
			next if ($attribute eq 'HW_TARGET');
			if (!defined($$existing{$attribute}) &&
			    $driverCap && !$SCST->checkTargetDynamicAttributes($driver, $attribute)) {
				addTargetDynamicAttribute($driver, $target, $attribute, $value);
				$changes++;
				next;
			} elsif (!defined($$existing{$attribute})) {
				print "\t-> WARNING: Driver/target '$driver/$target' lacks the settable attribute ".
				  "'$attribute', ignoring.\n\n";
				next;
			}

			# already set, move on
			if (defined($$existing{$attribute}->{$value})) {
				$$existing{$attribute}->{$value} = TRUE;
				next;
			}

			# Set the existing attribute
			if ($driverCap && !$SCST->checkTargetDynamicAttributes($driver, $attribute)) {
				addTargetDynamicAttribute($driver, $target, $attribute, $value);
				$changes++;
			} else {
				my $rc = setTargetAttribute($driver, $target, $attribute, $value);
				issueWarning($SCST->errorString($rc)) if ($rc);
				$changes++ if (!$rc);
			}

			$$existing{$attribute}->{$value} = TRUE;
		}
	}

	foreach my $attribute (keys %{$existing}) {
		next if (!$driverCap || $SCST->checkTargetDynamicAttributes($driver, $attribute));

		foreach my $value (keys %{$$existing{$attribute}}) {
			if (!$$existing{$attribute}->{$value}) {
				if ($deletions) {
					removeTargetDynamicAttribute($driver, $target, $attribute, $value);
					$changes++;
				} else {
					print "\t-> Target dynamic attribute '$attribute' with value '$value' ".
					  "is not in configuration. Use -force to remove it.\n";
				}
			}
		}
	}

	return $changes;
}

sub addTargetDynamicAttributes {
	my $driver = shift;
	my $target = shift;
	my $attributes = shift;

	my $_attributes = $SCST->targetAttributes($driver, $target);

	return TRUE if issueWarning($SCST->errorString());

	my $existing = cacheAttributes($_attributes);

	foreach my $attribute (keys %{$attributes}) {
		if (defined($$existing{$attribute}->{$$attributes{$attribute}})) {
			my $value = $$attributes{$attribute};
			print "\t-> Attribute/value '$attribute/$value' already exists for driver/target ".
			  "'$driver/$target'.\n";
			next;
		}

		addTargetDynamicAttribute($driver, $target, $attribute, $$attributes{$attribute});
	}

	return FALSE;
}

sub addTargetDynamicAttribute {
	my $driver = shift;
	my $target = shift;
	my $attribute = shift;
	my $value = shift;

	print "\t-> Adding driver attribute '$attribute' with value '$value' ".
	  "for driver/target '$driver/$target': ";
	my $rc = $SCST->addTargetDynamicAttribute($driver, $target, $attribute, $value);
	print "done.\n";

	immediateExit($SCST->errorString($rc)) if ($rc); 

	return FALSE;
}

sub removeTargetDynamicAttributes {
	my $driver = shift;
	my $target = shift;
	my $attributes = shift;

	my $_attributes = $SCST->targetAttributes($driver, $target);

	return TRUE if issueWarning($SCST->errorString());

	my $existing = cacheAttributes($_attributes);

	foreach my $attribute (keys %{$attributes}) {
		if (!defined($$existing{$attribute}->{$$attributes{$attribute}})) {
			my $value = $$attributes{$attribute};
			print "\t-> Attribute/value '$attribute/$value' doesn't exist for driver/target ".
			  "'$driver/$target'.\n";
			next;
		}

		removeTargetDynamicAttribute($driver, $target, $attribute, $$attributes{$attribute});
	}

	return FALSE;
}

sub removeTargetDynamicAttribute {
	my $driver = shift;
	my $target = shift;
	my $attribute = shift;
	my $value = shift;

	return TRUE if (!$SCST->driverIsVirtualCapable($driver));

	print "\t-> Removing dynamic attribute '$attribute' with value '$value' for driver/target '$driver/$target': ";
	my $rc = $SCST->removeTargetDynamicAttribute($driver, $target, $attribute, $value);
	print "done.\n";

	immediateExit($SCST->errorString($rc)) if ($rc);

	return FALSE;
}

####################################################################

sub openDevice {
	my $handler = shift;
	my $device = shift;
	my $attributes = shift;

	print "\t-> Opening device '$device' using handler '$handler': ";

	my $rc = $SCST->openDevice($handler, $device, $attributes);

	print "done.\n";

	immediateExit($SCST->errorString($rc)) if ($rc);

	return FALSE;
}

sub closeDevice {
	my $handler = shift;
	my $device = shift;
	my $force = shift;

	my $attributes = $SCST->deviceAttributes($device);

	if (!$force) {
		if (keys %{$$attributes{'exported'}}) {
			listExported($device, $attributes);
			immediateExit("Device '$device' still in use, aborting. ".
			  "Use -force to override.");
		}
	}

	print "\t-> Closing device '$device' using handler '$handler': ";

	my $rc = $SCST->closeDevice($handler, $device);

	print "done.\n";

	immediateExit($SCST->errorString($rc)) if ($rc);

	return FALSE;
}

sub resyncDevice {
	my $device = shift;
	my %attributes = ('resync_size', 1);

	return setDeviceAttributes($device, \%attributes);
}

####################################################################

sub addGroup {
	my $driver = shift;
	my $target = shift;
	my $group = shift;

	print "\t-> Adding new group '$group' to driver/target '$driver/$target': ";

	my $rc = $SCST->addGroup($driver, $target, $group);

	print "done.\n";

	immediateExit($SCST->errorString($rc)) if ($rc);

	return FALSE;
}

sub removeGroup {
	my $driver = shift;
	my $target = shift;
	my $group = shift;
	my $force = shift;

	if (!$force) {
		my $luns = $SCST->luns($driver, $target, $group);
		my $initiators = $SCST->initiators($driver, $target, $group);

		if ((keys %{$luns}) || ($#{$initiators} > -1)) {
			listGroup($driver, $target, $group, $luns, $initiators);
			immediateExit("Group is still in use, aborting. Use -force to override.");
		}
	}

	print "\t-> Removing group '$group' from driver/target '$driver/$target': ";

	my $rc = $SCST->removeGroup($driver, $target, $group);

	print "done.\n";

	immediateExit($SCST->errorString($rc)) if ($rc);

	return FALSE;
}

####################################################################

sub addInitiator {
	my $driver = shift;
	my $target = shift;
	my $group = shift;
	my $initiator = shift;

	my $current = $CURRENT{'assign'}->{$driver}->{$target}->{'GROUP'};

	foreach my $group (keys %{$current}) {
		my $initiators = $$current{$group}->{'INITIATORS'};

		foreach my $init (@{$initiators}) {
			if ($init eq $initiator) {
				print "\t-> Initiator '$initiator' already belongs to group '$group' ".
				  "for driver/target '$driver/target', ignoring.\n";
				return;
			}
		}
	}

	print "\t-> Adding new initiator '$initiator' to driver/target/group ".
	  "'$driver/$target/$group': ";

	my $rc = $SCST->addInitiator($driver, $target, $group, $initiator);

	print "done.\n";

	immediateExit($SCST->errorString($rc)) if ($rc);

	return FALSE;
}

sub removeInitiator {
	my $driver = shift;
	my $target = shift;
	my $group = shift;
	my $initiator = shift;

	print "\t-> Removing initiator '$initiator' from driver/target/group ".
	  "'$driver/$target/$group': ";

	my $rc = $SCST->removeInitiator($driver, $target, $group, $initiator);

	print "done.\n";

	immediateExit($SCST->errorString($rc)) if ($rc);

	return FALSE;
}

sub moveInitiator {
	my $driver = shift;
	my $target = shift;
	my $group = shift;
	my $initiator = shift;
	my $to = shift;

	print "\t-> Moving initiator '$initiator' from group '$group' to group '$to': ";

	my $rc = $SCST->moveInitiator($driver, $target, $group, $to, $initiator);

	print "done.\n";

	immediateExit($SCST->errorString($rc)) if ($rc);

	return FALSE;
}

sub clearInitiators {
	my $driver  = shift;
	my $target = shift;
	my $group = shift;

	print "\t-> Removing all initiators driver/target/group '$driver/$target/$group': ";

	my $rc = $SCST->clearInitiators($driver, $target, $group);

	print "done.\n";

	immediateExit($SCST->errorString($rc)) if ($rc);

	return FALSE;
}

####################################################################

sub addLun {
	my $driver = shift;
	my $target = shift;
	my $device = shift;
	my $lun = shift;
	my $attributes = shift;
	my $group = shift;

	if ($group) {
		print "\t-> Adding device '$device' at LUN $lun to driver/target/group ".
		  "'$driver/$target/$group': ";
	} else {
		print "\t-> Adding device '$device' at LUN $lun to driver/target ".
		  "'$driver/$target': ";
	}

	my $rc = $SCST->addLun($driver, $target, $device, $lun, $attributes, $group);

	print "done.\n";

	immediateExit($SCST->errorString($rc)) if ($rc);

	return FALSE;
}

sub removeLun {
	my $driver = shift;
	my $target = shift;
	my $lun = shift;
	my $group = shift;

	if ($group) {
		print "\t-> Removing LUN $lun from driver/target/group ".
		  "'$driver/$target/$group': ";
	} else {
		print "\t-> Removing LUN $lun from driver/target ".
		  "'$driver/$target': ";
	}

	my $rc = $SCST->removeLun($driver, $target, $lun, $group);

	print "done.\n";

	immediateExit($SCST->errorString($rc)) if ($rc);

	return FALSE;
}

sub replaceLun {
	my $driver = shift;
	my $target = shift;
	my $group = shift;
	my $lun = shift;
	my $device = shift;
	my $attributes = shift;

	print "\t-> Replacing device at LUN $lun with device '$device' ".
	  "in driver/target/group '$driver/$target/$group': ";

	my $rc = $SCST->replaceLun($driver, $target, $lun, $device, $attributes, $group);

	print "done.\n";

	immediateExit($SCST->errorString($rc)) if ($rc);

	return FALSE;
}

sub clearLuns {
	my $driver = shift;
	my $target = shift;
	my $group = shift;

	if ($group) {
		print "\t-> Removing all LUNs from driver/target/group '$driver/$target/$group': ";
	} else {
		print "\t-> Removing all LUNs from driver/target '$driver/$target': ";
	}

	my $rc = $SCST->clearLuns($driver, $target, $group);

	print "done.\n";

	immediateExit($SCST->errorString($rc)) if ($rc);

	return FALSE;
}

sub clearDriverDynamicAttributes {
	my $driver = shift;

	return TRUE if (!$SCST->driverIsVirtualCapable($driver));

	print "\t-> Removing all dynamic attributes from driver '$driver': ";

	my $attributes = $SCST->driverAttributes($driver);
	my $dynamic = $SCST->driverDynamicAttributes($driver);

	foreach my $attribute (keys %{$attributes}) {
		if (defined($$dynamic{$attribute})) {
			if (defined($$attributes{$attribute}->{'keys'})) {
				foreach my $key (keys %{$$attributes{$attribute}->{'keys'}}) {
					my $value = $$attributes{$attribute}->{'keys'}->{$key}->{'value'};
					my $rc = $SCST->removeDriverDynamicAttribute($driver, $attribute, $value);

					immediateExit($SCST->errorString($rc)) if ($rc);
				}
			}
		}
	}

	print "done.\n";

	return FALSE;
}

sub clearTargetDynamicAttributes {
	my $driver = shift;
	my $target = shift;

	return TRUE if (!$SCST->driverIsVirtualCapable($driver));

	print "\t-> Removing all dynamic attributes from driver/target '$driver/$target': ";

	my $attributes = $SCST->targetAttributes($driver, $target);
	my $dynamic = $SCST->targetDynamicAttributes($driver);

	foreach my $attribute (keys %{$attributes}) {
		if (defined($$dynamic{$attribute})) {
			if (defined($$attributes{$attribute}->{'keys'})) {
				foreach my $key (keys %{$$attributes{$attribute}->{'keys'}}) {
					my $value = $$attributes{$attribute}->{'keys'}->{$key}->{'value'};
					my $rc = $SCST->removeTargetDynamicAttribute($driver, $target, $attribute, $value);

					immediateExit($SCST->errorString($rc)) if ($rc);
				}
			}
		}
	}

	print "done.\n";

	return FALSE;
}

####################################################################

sub enableTarget {
	my $driver = shift;
	my $target = shift;
	my %attributes = ('enabled', 1);

	setTargetAttributes($driver, $target, \%attributes);

	return FALSE;
}

sub disableTarget {
	my $driver = shift;
	my $target = shift;
	my %attributes = ('enabled', 0);

	setTargetAttributes($driver, $target, \%attributes);

	return FALSE;
}

sub enableDriver {
	my $driver = shift;
	my %attributes = ('enabled', 1);

	my $attrs = $SCST->driverAttributes($driver);
	return TRUE if (!defined($$attrs{'enabled'}));

	return setDriverAttributes($driver, \%attributes);
}

sub disableDriver {
	my $driver = shift;
	my %attributes = ('enabled', 0);

	my $attrs = $SCST->driverAttributes($driver);
	return TRUE if (!defined($$attrs{'enabled'}));

	return setDriverAttributes($driver, \%attributes);
}

sub issueLip {
	my $driver = shift;
	my $target = shift;

	if (defined($driver) && defined($target)) {
		return _issueLip($driver, $target);
	} else {
		my $drivers = $SCST->drivers();

		foreach my $driver(@{$drivers}) {
			my $targets = $SCST->targets($driver);

			foreach my $target (@{$targets}) {
				my $rc = _issueLip($driver, $target);

				return $rc if ($rc);
			}
		}
	}

	return FALSE;
}

sub _issueLip {
	my $driver = shift;
	my $target = shift;
	my $rc = FALSE;

	my $attributes = $SCST->targetAttributes($driver, $target);

	if (!defined($$attributes{'host'})) {
		#print "\t-> Driver/target '$driver/$target' has no 'host' attribute, ignoring.\n";
		return FALSE;
	}

	my $dir = new IO::Handle;

	if (!(opendir $dir, $$attributes{'host'}->{'value'}.'/device/fc_host')) {
		#print "\t-> Driver/target is not a fibre channel target, ignoring.\n";
		return FALSE;
	}

	foreach my $host (readdir($dir)) {
		next if (($host eq '.') || ($host eq '..'));

		my $lip = $$attributes{'host'}->{'value'}.'/device/fc_host/'.$host.'/issue_lip';

		if (-w $lip) {
			my $io = new IO::File $lip, O_WRONLY;

			print "WARNING: Failed to open file '$lip' for writing.\n"
			  if (!$io);

			print "\t-> Issuing LIP on fibre channel driver/target '$driver/$target' ($host): ";

			my $bytes = syswrite($io, 1, 1);

			print "done.\n";

			if (!$bytes) {
				print "WARNING: Failed to issue LIP on driver/target '$driver/$target'.\n";
				$rc = TRUE;
			}

			close $io;
		} else {
			#print "Fibre channel driver/target '$driver/$target' lacks the ability to ".
			#  "issue LIPs, ignoring.\n";
		}
	}

	close $dir;

	return $rc;
}

####################################################################

sub readConfigFile {
	my $buffer;
	my @stanza;
	my $level;

	my $io = new IO::File $CONFIGFILE, O_RDONLY;

	immediateExit("Unable to open configuration file '$CONFIGFILE': $!")
	  if (!$io);

	while (my $line = <$io>) {
		my($test, undef) = split(/\#/, $line, 2);
		if ($test =~ /\[(.*)\s+.*\]/) {
			my $parm = $1;

			if (($parm eq 'HANDLER') || ($parm eq 'GROUP') ||
			    ($parm eq 'ASSIGNMENT')) {
				print "\nNOTE: Using a deprecated configuration file. ".
				  "I will attempt to convert it for you.\n\n";

				return readOldConfigFile();
			} 
		}

		$buffer .= $line;
	}

	my @buff_a;
	@buff_a = split(//, $buffer);

	$CONFIG = parseStanza(\@buff_a);

	return FALSE;
}

sub parseStanza {
	my $buffer = shift;
	my $line;
	my %hash;
	my $attribute;
	my $value;
	my $value2;
	my $quoted;

	while ($#{$buffer} > -1) {
		my $char = shift @{$buffer};

		if ($char eq '{') {
			my $child = parseStanza($buffer);

			if ($line) {
				parseLine($line, \%hash, $child);
				$line = undef;
			}

			next;
		}

		return \%hash if ($char eq '}');

		if ($char eq "\n") {
			my %empty;
			parseLine($line, \%hash, \%empty);
			$line = undef;
		} else {
			$line .= $char;
		}
	}

	return \%hash;
}	

sub parseLine {
	my $line = shift;
	my $hash = shift;
	my $child = shift;

	($line, undef) = split(/\#/, $line, 2);
	return if ($line =~ /^\s*$/);

	$line =~ s/^\s+//; $line =~ s/\s+$//;

	my @elements;
	while ($line =~ m/"([^"\\]*(\\.[^"\\]*)*)"|([^\s]+)/g) {
		push @elements, defined($1) ? $1:$3;
	}

	my $attribute = @elements[0];
	my $value     = @elements[1];
	my $value2    = @elements[2];

	if (defined($attribute) && defined($value) && defined($value2)) {
		$$hash{$attribute}->{$value}->{$value2} = $child;
	} elsif (defined($attribute) && defined($value)) {
		$$hash{$attribute}->{$value} = $child;
	} elsif (defined($attribute)) {
		$$hash{$attribute} = $child;
	}
}

sub readOldConfigFile {
	my $ignoreError = shift;
	my %config;
	my $section;
	my $last_section;
	my $arg;
	my $last_arg;
	my %empty;         

	my $io = new IO::File $CONFIGFILE, O_RDONLY;

	if (!$io) {
		return undef if ($ignoreError);

		die("FATAL: Unable to open specified configuration file 'CONFIGFILE': $!\n");
	}

	while (my $line = <$io>) {
		($line, undef) = split(/\#/, $line, 2);
		$line =~ s/^\s+//; $line =~ s/\s+$//;

		if ($line =~ /^\[(.*)\]$/) {
			($section, $arg) = split(/\s+/, $1, 2);

			$arg = 'default' if ($section eq 'OPTIONS');

			if ($last_arg && ($last_arg ne $arg) &&
			    !defined($config{$last_section}->{$last_arg})) {         
				$config{$last_section}->{$last_arg} = \%empty;
			}

			$last_arg = $arg;
			$last_section = $section;
		} elsif ($section && $arg && $line) {
			my($attribute, $value) = split(/\s+/, $line, 2);

			if ($section eq 'OPTIONS') {
				$value = TRUE if (($value == 1) ||
						  ($value =~ /^TRUE$/i) ||
						  ($value =~ /^YES$/i));
				$value = FALSE if (($value == 0) ||
						   ($value =~ /^FALSE$/i) ||
						   ($value =~ /^NO$/i));
			}

			push @{$config{$section}->{$arg}->{$attribute}}, $value;
		}
	}

	close $io;

	# Convert to our new format
	my %new;

	my %o_converts = ('WRITE_THROUGH' => 'write_through',
			  'WT'            => 'write_through',
			  'READ_ONLY'     => 'read_only',
			  'RO'            => 'read_only',
			  'NV_CACHE'      => 'nv_cache',
			  'NV'            => 'nv_cache',
			  'REMOVABLE'     => 'removable',
			  'RM'            => 'removable');

	foreach my $handler (keys %{$config{'HANDLER'}}) {
		foreach my $device (@{$config{'HANDLER'}->{$handler}->{'DEVICE'}}) {
			my($device, $path, $options, $bsize, $t10) = split(/\,/, $device);

			my $_handler = $handler;

			foreach my $option (split(/\|/, $options)) {
				if (($option eq 'NULLIO') || ($option eq 'NIO')) {
					$_handler = 'vdisk_nullio' if ($_handler eq 'vdisk');
				} elsif (($option eq 'BLOCKIO') || ($option eq 'BIO')) {
					$_handler = 'vdisk_blockio' if ($_handler eq 'vdisk');
				}
			}

			if ($_handler eq 'vdisk') {
				$_handler = 'vdisk_fileio';
			}

			foreach my $option (split(/\|/, $options)) {
				if (defined($o_converts{$option})) {
					%{$new{'HANDLER'}->{$_handler}->{'DEVICE'}->{$device}->{$o_converts{$option}}->{'1'}} = ();
				}
			}

			%{$new{'HANDLER'}->{$_handler}->{'DEVICE'}->{$device}->{'t10_dev_id'}->{$t10}} = ()
			  if ($t10);
			%{$new{'HANDLER'}->{$_handler}->{'DEVICE'}->{$device}->{'filename'}->{$path}} = ()
			  if ($path);
			%{$new{'HANDLER'}->{$_handler}->{'DEVICE'}->{$device}->{'blocksize'}->{$bsize}} = ()
			  if ($bsize);
		}
	}

	my $drivers = $SCST->drivers();
	my %added_targets;

	# Handle default groups
	foreach my $group (keys %{$config{'ASSIGNMENT'}}) {
		next if ($group !~ /^Default/);

		if ($group eq 'Default') { # Add to all targets
			foreach my $device (@{$config{'ASSIGNMENT'}->{$group}->{'DEVICE'}}) {
				my($device, $lun) = split(/\,/, $device);

				foreach my $driver (@{$drivers}) {
					my $targets = $SCST->targets($driver);

					foreach my $target (@{$targets}) {
						$new{'TARGET_DRIVER'}->{$driver}->{'TARGET'}->{$target}->{'LUN'}->{$lun}->{$device} = {};
					}
				}
			}
		} else {
			my $group_t = $group;
			$group_t =~ s/^Default\_//;
			my $found_t = FALSE;
			# Find the associated target
			foreach my $driver (@{$drivers}) {
				my $targets = $SCST->targets($driver);

				foreach my $target (@{$targets}) {
					if ($target eq $group_t) {
						foreach my $device (@{$config{'ASSIGNMENT'}->{$group}->{'DEVICE'}}) {
							my($device, $lun) = split(/\,/, $device);
							$new{'TARGET_DRIVER'}->{$driver}->{'TARGET'}->{$target}->{'LUN'}->{$lun}->{$device} = {};
						}

						$found_t = TRUE;
					}
				}
			}

			if (!$found_t) {
				print "NOTE: Unable to find target $group. I will add this target to ".
				  "all drivers capable of creating targets.\n";

				foreach my $driver (@{$drivers}) {
					if ($SCST->driverIsVirtualCapable($driver)) {
						foreach my $device (@{$config{'ASSIGNMENT'}->{$group}->{'DEVICE'}}) {
							my($device, $lun) = split(/\,/, $device);
							$new{'TARGET_DRIVER'}->{$driver}->{'TARGET'}->{$group_t}->{'LUN'}->{$lun}->{$device} = {};
							$added_targets{$driver}->{$group_t} = TRUE;
						}
					}
				}
			}
		}
	}

	my $has_enabled = FALSE;
	foreach my $wwn (@{$config{'TARGETS'}->{'enable'}->{'HOST'}}) {
		my $target = formatTarget($wwn);
		my $driver = findTargetDriver($target);

		if ($driver) {
			%{$new{'TARGET_DRIVER'}->{$driver}->{'TARGET'}->{$target}->{'enabled'}->{'1'}} = ();
			addAllGroupsToTarget(\%config, \%new, $driver, $target);
			$has_enabled = TRUE;
		} else {
			immediateExit("Unable to determine target driver information for target '$wwn'. ".
			  "Please ensure this target driver is loaded.");
		}
	}

	if (!$has_enabled) {
		print "NOTE: No targets set to 'enable' within your configuration file. Please be sure to ".
		  "save write your configuration with -write_config and review it carefully.\n\n";

		# Fill in with known targets, all disabled.
		foreach my $driver (@{$drivers}) {
			my $targets = $SCST->targets($driver);

			foreach my $target (@{$targets}) {
				addAllGroupsToTarget(\%config, \%new, $driver, $target);
			}
		}

		# As well for any created targets
		foreach my $driver (keys %added_targets) {
			foreach my $target (keys %{$added_targets{$driver}}) {
				addAllGroupsToTarget(\%config, \%new, $driver, $target);
			}
		}
	}

	# Fix drivers 'enabled' attributes
	foreach my $driver (keys %{$new{'TARGET_DRIVER'}}) {
		next if ($driver =~ /^qla/);

		my $attributes = $SCST->driverAttributes($driver);

		if (defined($$attributes{'enabled'})) {
			$new{'TARGET_DRIVER'}->{$driver}->{'enabled'}->{'1'} = {};
		}

		foreach my $target (keys %{$new{'TARGET_DRIVER'}->{$driver}->{'TARGET'}}) {
			next if (defined($new{'TARGET_DRIVER'}->{$driver}->{'TARGET'}->{'enabled'}));

			$new{'TARGET_DRIVER'}->{$driver}->{'TARGET'}->{$target}->{'enabled'}->{'1'} = {};
		}
	}

	$CONFIG = \%new;
}

sub addAllGroupsToTarget {
	my $config = shift;
	my $new = shift;
	my $driver = shift;
	my $target = shift;

	foreach my $group (keys %{$$config{'GROUP'}}) {
		if (defined($$config{'GROUP'}->{$group}->{'USER'})) {
			%{$$new{'TARGET_DRIVER'}->{$driver}->{'TARGET'}->{$target}->{'GROUP'}->{$group}} = ();

			foreach my $user (@{$$config{'GROUP'}->{$group}->{'USER'}}) {
				%{$$new{'TARGET_DRIVER'}->{$driver}->{'TARGET'}->{$target}->{'GROUP'}->{$group}->{'INITIATOR'}->{$user}} = ();
			}
		}
	}

	foreach my $group (keys %{$$config{'ASSIGNMENT'}}) {
		next if ($group =~ /^Default/);

		foreach my $device (@{$$config{'ASSIGNMENT'}->{$group}->{'DEVICE'}}) {
			my($device, $lun) = split(/\,/, $device);
			%{$$new{'TARGET_DRIVER'}->{$driver}->{'TARGET'}->{$target}->{'GROUP'}->{$group}->{'LUN'}->{$lun}->{$device}} = ();
		}
	}

	return FALSE;
}

sub findTargetDriver {
	my $target = shift;

	foreach my $driver (keys %{$CURRENT{'assign'}}) {
		foreach my $_target (keys %{$CURRENT{'assign'}->{$driver}}) {
			$_target =~ tr/A-Z/a-z/;

			return $driver if ($_target eq $target);
		}
	}

	return undef;
}

sub formatTarget {
	my $target = shift;

	if ($target =~ /^0x/) {
		$target =~ s/^0x//;
		my($o1, $o2, $o3, $o4, $o5, $o6, $o7, $o8) = unpack("A2A2A2A2A2A2A2A2", $target);
		$target = "$o1:$o2:$o3:$o4:$o5:$o6:$o7:$o8";
	}

	$target =~ tr/A-Z/a-z/;

	return $target;
}

####################################################################

sub handlerHasDevice {
	my $handler = shift;
	my $device = shift;

	return FALSE if (!defined($CURRENT{'handler'}->{$handler}));

	foreach my $_device (@{$CURRENT{'handler'}->{$handler}}) {
		return TRUE if ($_device eq $device);
	}

	return FALSE;
}

sub groupHasInitiator {
	my $initiators = shift;
	my $initiator = shift;

	foreach my $_initiator (@{$initiators}) {
		return TRUE if ($_initiator eq $initiator);
	}

	return FALSE;
}

sub configToAttr {
	my $config = shift;
	my %attributes;

	foreach my $attr (keys %{$config}) {
		if (!scalar keys %{$$config{$attr}}) {
				$attributes{$attr} = '';
		} elsif ((keys %{$$config{$attr}}) > 1) {
			foreach my $value (keys %{$$config{$attr}}) {
				push @{$attributes{$attr}}, $value;
			}
		} else {
			foreach my $value (keys %{$$config{$attr}}) {
				if (keys %{$$config{$attr}->{$value}}) {
					immediateExit("Invalid configuration encountered. ".
					  "Attribute '$attr' has an invalid value.");
				}

				$attributes{$attr} = $value;
			}
		}
	}

	return \%attributes;
}			

sub cacheAttributes {
	my $attributes = shift;
	my %cache;

	foreach my $attribute (keys %{$attributes}) {
		next if ($$attributes{$attribute}->{'static'});

		if (defined($$attributes{$attribute}->{'keys'})) {
			foreach my $key (keys %{$$attributes{$attribute}->{'keys'}}) {
				my $value = $$attributes{$attribute}->{'keys'}->{$key}->{'value'};
				$cache{$attribute}->{$value} = FALSE;
			}
		} else {
			my $value = $$attributes{$attribute}->{'value'};
			$cache{$attribute}->{$value} = FALSE;
		}
	}

	return \%cache;
}

sub numerically {
	$a <=> $b;
}

####################################################################

# If we have an unread error from SCST, exit immediately
sub immediateExit {
	my $error = shift;

	return FALSE if (!$error);

	print "\n\nFATAL: Received the following error:\n\n\t";
	print "$error\n\n";

	exit 1;
}

sub issueWarning {
	my $error = shift;

	return FALSE if (!$error);

	print "\n\nWARNING: Received the following error:\n\n\t";
	print "$error\n\n";

	return TRUE;
}

sub prompt {
	return FALSE if ($_NOPROMPT_);

	print "Performing this action may result in lost or corrupt data, ".
	  "are you sure you wish to continue (y/[n]) ? ";
	my $answer = <STDIN>;

	if (($answer =~ /^y$/i) || ($answer =~ /^yes$/i)) {
		return FALSE;
	}

	print "Aborting action.\n";

	return TRUE;
}

# Hey! Stop that!
sub commitSuicide {
	print "\n\nAborting immediately.\n";
	exit 1;
}
