#
# Copyright 2001-2003 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
#ident	"@(#)Device.pm 1.44     03/04/01 SMI"
#
# Disk Device class

package Cluster::Device;
use strict;
use POSIX;
use Cluster::Common;
use Cluster::RBAC;
use Cluster::RunCommand;
use Sun::Solaris::Utils qw(gettext);
use vars qw(@ISA $VERSION);
$VERSION = '1.00';
@ISA = qw(Cluster::Common);

# Internationalization
# action=
;# gettext("Online")
;# gettext("Offline")

##############################################################################
#
# Class constructor
#
##############################################################################

sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self  = {};
    bless ($self, $class);
    return $self;
}

##############################################################################
#
# Class Variables
#
##############################################################################

# File names and location
my $PFEXEC 	   = '/usr/bin/pfexec';     # Used in execute_cmd()
my $DEVICEIMAGE    = '/images/device/devicegrps_16.gif';
my $VIEW     = '/cgi-bin/device/device_view.pl';
my $CONFIG   = '/cgi-bin/device/device_config.pl';
my $ADD      = '/cgi-bin/device/device_add.pl';
my $SCCONF   = '/usr/cluster/bin/scconf';
my $SCSTAT   = '/usr/cluster/bin/scstat';
my $SCDPM    = '/usr/cluster/bin/scdpm';
my $SCSWITCH = '/usr/cluster/bin/scswitch';
my $METASTAT = '/usr/sbin/metastat';
my $MOUNT    = '/usr/bin/df -k';

# Instantiate cluster objects
my $rbac           = new Cluster::RBAC;
my $runcommand     = new Cluster::RunCommand;

# Set boolean constants
use constant TRUE  => 0;
use constant FALSE => 1;

# command output in memory 
my @SCCONFPOUT; 
my @SCSTATDOUT;
my @DG_LIST;

my @status_icons = (
	["Online", "Online", "ok"], # gettext("Online")
	["Offline", "Offline", "critical"], # gettext("Offline")
	["Degraded", "Degraded", "critical"], # gettext("Degraded")
	["Wait", "Wait", "minor"] # gettext("Wait")
);

my @path_status_icons = (
	["Ok", "Ok", "ok"], # gettext("Ok")
	["Fail", "Fail", "critical"], # gettext("Fail")
	["Unmonitored", "Unmonitored", "minor"], # gettext("Unmonitored")
	["Unknown", "Unknown", "minor"] # gettext("Unknown")
);


##############################################################################
#
# Class methods
#
##############################################################################

sub device_jscript() {
	return "
	function online_dg_submit(dg) {
                self.location.href = '$CONFIG\?step=2&func=Online&devicegroup=' + dg + '&primary=' + document.inputs.primary.options[document.inputs.primary.selectedIndex].value
        }";
}

# Print the header, view and menu bar
sub device_header {
	my ($self, $q, $headername, $devicegroup) = @_;

	# Start the action bar table
        $q->start_action_bar_table($headername);

	if (!defined $devicegroup) {
		# Create the view menu links
		my @viewlinks = (gettext("Device Group Status Table"), $VIEW,
				 gettext("Device Group Topology"),
				"$VIEW\?view=topological");

		# Print the view menu
		$q->view_menu(\@viewlinks, $VIEW);
	}

	# Print the middle of the action table
        $q->mid_action_bar_table();

	# Create the actions menu links
        my @links = ();
	my @action_helpMarkers = ();

	# Add if the user has solaris.cluster.device.admin
	if ($rbac->check_auth($CL_AUTH_DEVICE_ADMIN)) {
		push (@links, gettext("Bring Online/Switch Primary..."),
		      "$CONFIG\?step=1&devicegroup=$devicegroup&func=Online",
		      gettext("Take Offline..."),
		      "$CONFIG\?step=1&devicegroup=$devicegroup&func=Offline");

		push (@action_helpMarkers, "devicepm-online-switch",
		      "devicepm-offline");
	}

	# Add if the user has solaris.cluster.device.modify
	if ($rbac->check_auth($CL_AUTH_DEVICE_MODIFY)) {
		push (@links, gettext("Modify Properties..."),
		     "$CONFIG\?step=1&devicegroup=$devicegroup&func=Props");

		push (@action_helpMarkers, "devicepm-props");
	}

	# Add if the user has solaris.cluster.device.admin
	if ($rbac->check_auth($CL_AUTH_DEVICE_ADMIN)) {
		push (@links, gettext("---------------------"),
		      $VIEW,
		      gettext("Enable Disk Path Monitoring..."),
		      "$CONFIG\?step=1&func=Monitor",
		      gettext("Disable Disk Path Monitoring..."),
		      "$CONFIG\?step=1&func=Unmonitor");

		push (@action_helpMarkers, "separator", "devicepm-monitor-disk",
		      "devicepm-unmonitor-disk");
	}

	# Print if the user has device modify or admin permissions
	if ($rbac->check_auth($CL_AUTH_DEVICE_MODIFY) ||
	    $rbac->check_auth($CL_AUTH_DEVICE_ADMIN)) {
		$q->load_action_helpMarkers(\@action_helpMarkers);
		$q->actions_menu(gettext("Action Menu"), \@links);
	}

        # End the action bar table
        $q->end_action_bar_table();
}

sub get_devicegrouplist
{
	return @DG_LIST;
}

# Get device group name, state, nodelist and primary
# Note: failback and preferenced are not internationalized here
sub _get_devicegrouplist {
	my (@dg_list, $i, @parts, $line);
	$i = 0;
	my %namemap; # Name to table #
	for (my $j = 0; $j <= $#SCCONFPOUT; $j++) {
		$line = $SCCONFPOUT[$j];
		if ($line =~ /Device group name:/) {
			@parts = split /(\S+)/, $line;
			$dg_list[$i]{'name'} = $parts[7];
			$namemap{$parts[7]} = $i;
			$line = $SCCONFPOUT[++$j];
			@parts = split /(\S+)/, $line;
			$dg_list[$i]{'type'} = $parts[7];	
			$line = $SCCONFPOUT[++$j];
			@parts = split /(\S+)/, $line;
			if ($parts[9] eq "yes") {
				$dg_list[$i]{'failback'} = "enabled";
				;# gettext("enabled");  
			} else {
				$dg_list[$i]{'failback'} = "disabled";
				;# gettext("disabled");
			}
			$line = $SCCONFPOUT[++$j];
			@parts = split /(\:)/, $line;
			$parts[2] =~ s/\s+//g;
			$dg_list[$i]{'nodelist'} = $parts[2]; 
			# XXX Workaround for bad nodelist
			if ($line =~ /Device group ordered node list/) {
			    $dg_list[$i]{'nodelist'} = "";
			    $j--;
			}

			$line = $SCCONFPOUT[++$j];
			@parts = split /(\:)/, $line;
			$parts[2] =~ s/\s+//g;
			if ($parts[2] eq "yes") {
			    $dg_list[$i]{'preferenced'} = "true";
			} else {
			    $dg_list[$i]{'preferenced'} = "false";
			}

			$line = $SCCONFPOUT[++$j];
			# Do a sanity check to prevent mysterious problems
			if ($line !~ /number of secondaries/) {
			    print "Internal Error: scconf version mismatch\n";
			}
			@parts = split /(\:)/, $line;
			$parts[2] =~ s/\s+//g;
			$dg_list[$i]{'secondaries'} = $parts[2]; 
			$i++;
		}
	}

	my @output = $runcommand->scstat("-Dv");
	for (my $k = 0; $k <= $#output; $k++) {
		$line = $output[$k];
		if ($line =~ /Device group servers:/) {
			@parts = split /(\S+)/, $line;
			$i = $namemap{$parts[7]};
			if (!defined($i)) {
				next;
			}
			$parts[9] =~ s/\s+//g;
			if (!($parts[9] eq "-")) {
				$dg_list[$i]{'primary'} = 
					$parts[9];
				$parts[11] =~ s/\s+//g;
				$dg_list[$i]
				    {'secondary_list'} =
				    $parts[11];
			} else {
				$dg_list[$i]{'primary'} = 
					gettext("(none)"); 
				$dg_list[$i]
				    {'secondary_list'} = "(none)";
			}
		} elsif ($line =~ /Device group spares:/) {
			@parts = split /(\S+)/, $line;
			$i = $namemap{$parts[7]};
			if (!defined($i)) {
				next;
			}
			$parts[9] =~ s/\s+//g;
			if (!($parts[9] eq "-")) {
				$dg_list[$i]{'spare_list'} = 
					$parts[9];
			} else {
				$dg_list[$i]{'spare_list'} =
					"(none)";
			}
		} elsif ($line =~ /Device group status:/) {
		        @parts = split /(\S+)/, $line;
			$i = $namemap{$parts[7]};
			if (!defined($i)) {
				next;
			}
			$dg_list[$i]{'status'} = $parts[9];
		}		     
	}
	return (@dg_list);
}

# Get all the mounted directories on this diskgroup.
sub get_mounted_directories($$) {
	my ($self, $dg_name) = @_;

	my @dg_list = $self->get_devicegrouplist();
	my ($node, @mounted);
	my $idx = $self->find_dg_by_name(\@dg_list, $dg_name);
	if ($idx < 0) {
		return;
	}
	
	my $cmd = "/usr/sbin/df -k | grep /dev/md/$dg_name";
	my @output = `$PFEXEC $cmd`;
	if ($? != 0) {
		return;
	}
	for (my $i = 0; $i <= $#output; $i++) {
		my @parts = split(/\s+/, $output[$i]);
		$mounted[$i]{'logicaldisk'} = "$parts[0]";  
		$mounted[$i]{'mounted'} = "$parts[5]";  
	}
	return @mounted;
}

sub find_dg_by_name
{
	my ($self, $dg_list_ref, $dg_name) = @_;
	my @dg_list = @$dg_list_ref;
	for (my $i = 0; $i < $#dg_list + 1; $i++) {
		if ($dg_list[$i]{'name'} eq $dg_name) {
			return $i;
		}
	}
	return (-1); # not found
}

sub config_online($$$$$)
{
	my ($self, $q, $step, $devicegroup, $primary) = @_;

	my @dg_list = $self->get_devicegrouplist();
	if ($#dg_list < 0){
		$q->print_title([gettext("Global Devices"), $VIEW,
		    gettext("Bring Online/Switch Primary"), 
			"$CONFIG\?step=1&devicegroup=$devicegroup&func=Online",
		    gettext("Warning")]);
                print "<form>";
                $q->start_message_table("warning");
                print $q->start_p({ class => "message-header-text" });
                print gettext("No device group has been configured on this cluster");
                print $q->end_p();
                $q->end_message_table($q);
                $q->buttons_table(gettext("  Cancel  "), "self.location.href=\"$VIEW\"" );
                print "</form>";
                return;
        }

	if ($step == 1) {
		# Gather infomation
		$q->print_title([gettext("Global Devices"), $VIEW,
		    gettext("Bring Online/Switch Primary")]);
		print "<form name=inputs action=$CONFIG>";
		print $q->start_table({ cellpadding => 5 });
		print $q->start_Tr();
		print $q->start_td();
		print $q->start_div({ class => "action-window-label-text" });
		print gettext("Device Group:");
		print $q->end_div();
		print $q->end_td();
		print $q->start_td();
		my @dg_links;
		my $j = 0;
		my $selectidx = 0;
		for (my $i = 0; $i <= $#dg_list; $i++) {
			if ($dg_list[$i]{'name'} eq $devicegroup) {
				$selectidx = $i;
			}
			$dg_links[$j++] = $dg_list[$i]{'name'};
			$dg_links[$j++] = "$CONFIG\?step=1&func=Online&devicegroup=$dg_list[$i]{'name'}";
		}
		$q->select_menu(\@dg_links, "dg_menu", $dg_links[1]);
		print $q->start_script();
		print "document.inputs.dg_menu.selectedIndex=$selectidx";
		print $q->end_script();
		print $q->end_td();
		print $q->end_Tr();

		print $q->start_Tr();
		print $q->start_td();
		print $q->start_div({ class => "action-window-label-text" });
		print gettext("Node:");
		print $q->end_div();
		print $q->end_td();
		print $q->start_td();
		my @node_list = $self->get_nodelist_fromstr($dg_list[$selectidx]{'nodelist'});
		print $q->popup_menu({ name => "primary", 
				       values => \@node_list });
		print $q->end_td();
		print $q->end_Tr();
		print $q->end_table();

		$q->buttons_table(gettext(" Bring Online/Switch Primary "),
			"online_dg_submit('$dg_list[$selectidx]{'name'}')",
			gettext("  Cancel  "),
			"self.location.href = \"$VIEW\"" );
		print "</form>";
	} else {
		# Generate command
		my $cmd = "$SCSWITCH -z -D $devicegroup -h $primary";
	
		# Executing the command
		$self->execute_cmd($q, $cmd, "Online", $primary,
			[gettext("Global Devices"), $VIEW,
			    gettext("Bring Online/Switch Primary"),
		     "$CONFIG\?step=1&devicegroup=$devicegroup&func=Online"],
		     $VIEW);
	}
}

sub config_offline($$$$)
{
	my ($self, $q, $step, $devicegroup) = @_;

	my @dg_list = $self->get_devicegrouplist();
	if ($#dg_list < 0){
		$q->print_title([gettext("Global Devices"), $VIEW,
		    gettext("Take Offline"),
			"$CONFIG\?step=1&devicegroup=$devicegroup&func=Offline",
		    gettext("Warning")]);
                print "<form>";
                $q->start_message_table("warning");
                print $q->start_p({ class => "message-header-text" });
                print gettext("No device group has been configured on this cluster");
                print $q->end_p();
                $q->end_message_table($q);
                $q->buttons_table(gettext("  Cancel  "), "self.location.href=\"$VIEW\"" );
                print "</form>";
                return;
        }

	if ($step == 1) {
		# Gather infomation
		$q->print_title([gettext("Global Devices"), $VIEW,
		    gettext("Take Offline")]);
		print "<form name=inputs action=$CONFIG>";
		print $q->input({ type => "hidden",
                                name => "func",
                                value => "Offline" });
		print $q->input({ type => "hidden",
                                name => "step",
                                value => 2 });
		print $q->start_table({ cellpadding => 5 });
		print $q->start_Tr();
		print $q->start_td();
		print $q->start_div({ class => "action-window-label-text" });
		print gettext("Device Group:");
		print $q->end_div();
		print $q->end_td();
		print $q->start_td();
		print "<select name=devicegroup>";
		my $selectidx = 0;
		for (my $i = 0; $i <= $#dg_list; $i++) {
			if ($dg_list[$i]{'name'} eq $devicegroup) {
				$selectidx = $i;
			}
			print "<option value=\"$dg_list[$i]{'name'}\">$dg_list[$i]{'name'}";
		}
		print "</select>";
		print $q->start_script();
		print "document.inputs.devicegroup.selectedIndex=$selectidx";
		print $q->end_script();
		print $q->end_td();
		print $q->end_Tr();
		print $q->end_table();

		$q->buttons_table(gettext(" Offline Device Group "),
                	"document.inputs.submit()",
			gettext("  Cancel  "),
			"self.location.href = \"$VIEW\"" );
		print "</form>";
	} else {
		# Generate command
		my $cmd = "$SCSWITCH -F -D $devicegroup"; 
	
		# Executing the command
		$self->execute_cmd($q, $cmd, "Offline", "",
			[gettext("Global Devices"), $VIEW,
			    gettext("Offline Device Group"),
		     "$CONFIG\?step=1&devicegroup=$devicegroup&func=Offline"],
		     $VIEW);
	}
}

# Subroutine to implement property change
sub config_props($$$$)
{
	my ($self, $q, $step, $devicegroup) = @_;

	my @dg_list = $self->get_devicegrouplist();
	if ($#dg_list < 0){
        }

	if ($step == 1) {
		# Gather infomation
		$q->print_title([gettext("Global Devices"), $VIEW,
		    gettext("Modify Properties")]);
		print "<form name=inputs action=$CONFIG>";
		print $q->input({ type => "hidden",
                                name => "func",
                                value => "Props" });
		print $q->input({ type => "hidden",
                                name => "step",
                                value => 2 });
		print $q->start_table({ cellpadding => 5 });
		print $q->start_Tr();
		print $q->start_td();
		print $q->start_div({ class => "action-window-label-text" });
		print gettext("Device Group:");
		print $q->end_div();
		print $q->end_td();
		print $q->start_td();
		print "<select name=devicegroup>";
		my $selectidx = 0;
		for (my $i = 0; $i <= $#dg_list; $i++) {
			if ($dg_list[$i]{'name'} eq $devicegroup) {
				$selectidx = $i;
			}
			print "<option value=\"$dg_list[$i]{'name'}\">$dg_list[$i]{'name'}";
		}
		print "</select>";
		print $q->start_script();
		print "document.inputs.devicegroup.selectedIndex=$selectidx";
		print $q->end_script();
		print $q->end_td();
		print $q->end_Tr();
		print $q->end_table();

		$q->buttons_table(gettext(" Modify Properties... "),
                	"document.inputs.submit()",
			gettext("  Cancel  "),
			"self.location.href = \"$VIEW\"" );
		print "</form>";
	} elsif ($step == 2) {
		# Display table
		$q->print_title([gettext("Global Devices"), $VIEW,
		    gettext("Modify Properties")]);
	$self->devicegroup_mod_table($q, $devicegroup);
	} else {
		# Load the device data
		my @dg_list = $self->get_devicegrouplist();

		my $idx = $self->find_dg_by_name(\@dg_list, $devicegroup);
		if ($idx < 0) {
		    $q->print_title([gettext("Global Devices"), $VIEW,
			gettext("Modify Properties"),
			    "$CONFIG\?step=1&devicegroup=$devicegroup&func=Props",
			gettext("Warning")]);
		    print "<form>";
		    $q->start_message_table("warning");
		    print $q->start_p({ class => "message-header-text" });
		    print sprintf(gettext("Global Device %s is not configured on this cluster"), $devicegroup);
		    print $q->end_p();
		    $q->end_message_table($q);
		    $q->buttons_table(gettext("  Cancel  "), "self.location.href=\"$VIEW\"" );
		    print "</form>";
		    return;
		}

		# Generate command
		my $cmd = "";
		my $warning;
		if ($q->param('failback') ne $dg_list[$idx]{'failback'}) {
		    $cmd .= ",failback=".$q->param('failback');
		}

		if ($q->param('secondaries') ne $dg_list[$idx]{'secondaries'}) {
		    if ($q->param('secondaries') eq "0") {
			# Default
			$cmd .= ",numsecondaries=";
		    } else {
			$cmd .= ",numsecondaries=".$q->param('secondaries');
		    }
		}


		if ($q->param('node_list') ne $dg_list[$idx]{'nodelist'}) {
		    my $new_nodelist = $q->param('node_list');
		    $new_nodelist =~ s/,/:/g; # convert separator to colon
		    $cmd .= ",nodelist=$new_nodelist";
		    if ($q->param('preferenced') eq "false") {
			$warning = gettext("Note: \"preferenced\" option is set to true if node order is changed.");
		    }
		} else {
		    if ($q->param('preferenced') ne $dg_list[$idx]{'preferenced'}) {
			$cmd .= ",preferenced=".$q->param('preferenced');
		    }
		}


		if ($cmd eq "") {
		    $q->print_title([gettext("Global Devices"), $VIEW,
			gettext("Modify Properties"),
			    "$CONFIG\?step=1&devicegroup=$devicegroup&func=Props",
			gettext("Warning")]);
		    print "<form>";
		    $q->start_message_table("warning");
		    print $q->start_p({ class => "message-header-text" });
		    print sprintf(gettext("No changes made to Global Device %s"), $devicegroup);
		    print $q->end_p();
		    $q->end_message_table($q);
		    $q->buttons_table(gettext("  View Device Group Information   "), "self.location.href=\"$VIEW\"" );
		    print "</form>";
		    return;
		}
		$cmd = "$SCCONF -c -D name=$devicegroup".$cmd;
	
		# Executing the command
		$self->execute_cmd($q, $cmd, "Configure", "",
			[gettext("Global Devices"), $VIEW,
			    gettext("Modify Properties"),
		     "$CONFIG\?step=2&devicegroup=$devicegroup&func=Props"],
		     "$VIEW?devicegroup=$devicegroup", $warning);
	}
}

sub config_monitor($$$$)
{
	my ($self, $q, $step, @diskpaths) = @_;
	
	if ($step == 1) {
		my @dp_list = $self->get_unmonitored_disks();
		if ($#dp_list < 0) {
			$q->print_title([gettext("Global Devices"), $VIEW,
					gettext("Enable Disk Path Monitoring"), 
					"$CONFIG\?step=1&func=Monitor",
					gettext("Warning")]);
			print $q->start_form();
			$q->start_message_table("warning");
			print $q->start_p({ class => "message-header-text" });
			print gettext("All disk paths are currently being " .
				      "monitored.");
			print $q->end_p();
			$q->end_message_table();
			$q->buttons_table(gettext("  Cancel  "),
					  "window.location.href=\"$VIEW\"" );
			print $q->end_form();
		} else {
			$q->print_title([gettext("Global Devices"), $VIEW,
					 gettext("Enable Disk Path Monitoring")]);
			print $q->start_form({ NAME => "inputs",
					       ACTION => $CONFIG });
			print $q->input({ type => "hidden",
					  name => "func",
					  value => "Monitor" });
			print $q->input({ type => "hidden",
					  name => "step",
					  value => 2 });	
			print $q->start_table({ cellpadding => 5 });
			print $q->start_Tr();
			print $q->start_td({ valign => "top" });
			print $q->start_div({ class => 
					      "action-window-label-text" });
			print gettext("The following disk paths are not " .
				      "being monitored. Select one or more " .
				      "disk paths to be added to the set of " .
				      "monitored disk paths:");
			print $q->end_div();
			print $q->end_td();
			print $q->start_td();
			print $q->scrolling_list({ NAME => "diskpath",
						   VALUES => \@dp_list,
						   SIZE => 10,
						   MULTIPLE => 'true'
						   });
			print $q->end_td();
			print $q->end_Tr();
			print $q->end_table();
			
			$q->buttons_table(gettext(" Monitor Disks "),
					  "this.form.submit()",
					  gettext("  Cancel  "),
					  "self.location.href = \"$VIEW\"" );
			print $q->end_form();
		}
	} else {
		# generate the command
		my $pathlist;
		foreach my $diskpath (sort @diskpaths) {
			$pathlist .= " -m $diskpath";
		}

		my $cmd = "$SCDPM $pathlist";

		# Executing the command
		$self->execute_cmd($q, $cmd, "Monitor", "",
				   [gettext("Global Devices"), $VIEW,
				    gettext("Enable Disk Path Monitoring"),
				    "$CONFIG\?step=1&func=Monitor"],
				   $VIEW);
	}
}

sub config_unmonitor($$$@)
{
	my ($self, $q, $step, @diskpaths) = @_;
	
	if ($step == 1) {
		my @dp_list = $self->get_monitored_disks();
		if ($#dp_list < 0) {
			$q->print_title([gettext("Global Devices"), $VIEW,
				 gettext("Disable Disk Path Monitoring"), 
				 "$CONFIG\?step=1&func=Unmonitor",
				 gettext("Warning")]);
			print $q->start_form();
			$q->start_message_table("warning");
			print $q->start_p({ class => "message-header-text" });
			print gettext("All disk paths are currently " .
				      "unmonitored.");
			print $q->end_p();
			$q->end_message_table();
			$q->buttons_table(gettext("  Cancel  "),
					  "window.location.href=\"$VIEW\"" );
			print $q->end_form();
		} else {
			$q->print_title([gettext("Global Devices"), $VIEW,
				 gettext("Disable Disk Path Monitoring")]);
			print $q->start_form({ NAME => "inputs",
					       ACTION => $CONFIG });
			print $q->input({ type => "hidden",
					  name => "func",
					  value => "Unmonitor" });
			print $q->input({ type => "hidden",
					  name => "step",
					  value => 2 });	
			print $q->start_table({ cellpadding => 5 });
			print $q->start_Tr();
			print $q->start_td({ valign => "top" });
			print $q->start_div({ class =>
					      "action-window-label-text" });
			print gettext("The following disk paths are " .
				      "currently being monitored. Select " .
				      "one or more disk paths to remove " .
				      "from the set of monitored disk paths:");
			print $q->end_div();
			print $q->end_td();

			print $q->start_td();
			print $q->scrolling_list({ NAME => "diskpath",
						   VALUES => \@dp_list,
						   SIZE => 10,
						   MULTIPLE => 'true'
						   });
			print $q->end_td();
			print $q->end_Tr();
			print $q->end_table();
			
			$q->buttons_table(gettext(" Unmonitor Disks "),
					  "this.form.submit()",
					  gettext("  Cancel  "),
					  "self.location.href = \"$VIEW\"" );
			print $q->end_form();
		}
	} else {
		# generate the command
		my $pathlist;
		foreach my $diskpath (sort @diskpaths) {
			$pathlist .= " -u $diskpath";
		}

		my $cmd = "$SCDPM $pathlist";

		# Executing the command
		$self->execute_cmd($q, $cmd, "Unmonitor", "",
				   [gettext("Global Devices"), $VIEW,
				    gettext("Disable Disk Path Monitoring"),
				    "$CONFIG\?step=1&func=Unmonitor"],
				   $VIEW);
	}
}

#sub config_remove
#{
#	my ($self, $q, $item_type, $item_name) = @_;
#
#	my ($cmd);
#	
#	$cmd = "$SCCONF -r -D name=$item_name";
#	
#	print $q->center();
#	print $q->font({ color => "black" });
#	print sprintf(gettext("The command to remove the %s(%s) is:"), $item_type, $item_name); 
#	print $q->br(), $q->br(), $q->start_i();
#	print $cmd;
#	print $q->br(),$q->br(), $q->end_i();
#	print $q->end_font();
#	print "<FORM>";
#	print "<INPUT TYPE=button VALUE=".gettext("Back")." onClick=\"history.back()\">"; 
#	print $q->indent(3);
#	# replace space with + for passing argument
#	$cmd =~ tr/ /+/;
#	print "<INPUT TYPE=button VALUE=\"".gettext("Continue")."\" onClick=\"self.location.href='$CONFIG\?type=$item_type&name=$item_name&action=Run&cmd=$cmd'\">"; 
#	print "</FORM>";
#
#	print $q->end_font();
#	print $q->end_center();
#}

# Get a list of nodes from a string with node name separated by ,
sub get_nodelist_fromstr
{
	my ($self, $nodeliststr) = @_;
	my (@node_list, @parts); 
	my $idx = 0;
	@parts = split /(\,)/, $nodeliststr;
	for (my $i = 0; $i < $#parts + 1; $i = $i + 2) {
		$node_list[$idx++] = $parts[$i];
	}
	return (@node_list);
}

sub execute_cmd($$$$$)
{
	my ($self, $q, $cmd, $action, $newprimary, $title, $link,
	    $warning) = @_;

	my $output = `$PFEXEC $cmd 2>&1`;
	my $ret = $?;
	if ($ret == 0) {
		pop @$title; # Don't use last link
		$q->print_title($title);
		print $q->start_form();
                $q->start_message_table("info");
                print $q->start_p({ class => "message-header-text" });
		if ($action eq "Online") {
			print sprintf(gettext("Primary Successfully Set to %s"), $newprimary);
		} elsif ($action eq "Offline") {
			print gettext("Device Group Successfully Taken Offline");
		} elsif ($action eq "Configure") {
			print gettext("Device Group Properties Modified");
		} elsif ($action eq "Monitor") {
			print gettext("Disk Paths Now Monitored");
		} elsif ($action eq "Unmonitor") {
			print gettext("Disk Paths No Longer Being Monitored");
		}
                print $q->end_p();
		if (defined($warning)) {
		    print $q->start_p({ class => "message-description-text" });
		    print $warning;
		    print $q->end_p();
		}
                print $q->start_p({ class => "message-description-text" });
                print gettext("The following command completed without error:");
                print $q->end_p();
                print $q->start_p({ class => "message-command-text" });
                print $cmd;
                print $q->end_p();
                $q->end_message_table();
                $q->buttons_table(gettext(" View Device Group Information "),
                                "self.location.href= \"$link\"" );
		print $q->end_form();
	} else {
		push @$title, gettext("Error"); # Add error to title
		$q->print_title($title);
		print $q->start_form();
                $q->start_message_table("error");
                print $q->start_p({ class => "message-header-text" });
		if ($action eq "Online") {
			print sprintf(gettext("Primary Not Set to %s"), $newprimary);
		} elsif ($action eq "Offline") {
			print gettext("Device Group Not Taken Offline");
		} elsif ($action eq "Configure") {
			print gettext("Device Group Properties Not Modified");
		} elsif ($action eq "Monitor") {
			print gettext("Disk Path Not Monitored");
		} elsif ($action eq "Unmonitor") {
			print gettext("Disk Path Not Unmonitored");
		}
                print $q->end_p();
                print $q->start_p({ class => "message-description-text" });
                print gettext("The command:");
                print $q->end_p();
                print $q->start_p({ class => "message-command-text" });
                print $cmd;
                print $q->end_p();
                print $q->start_p({ class => "message-description-text" });
                print gettext("Failed with the following error:");
                print $q->end_p();
                print $q->start_p({ class => "message-command-text" });
                print $output;
                print $q->end_p();

		if ($output =~ /Failed to change device group/) {
		    print $q->start_p({ class => "message-description-text" });
		    print gettext("The disk group may need to be online to perform that command.");
		    print $q->end_p();
		}
                $q->end_message_table();

		$q->buttons_table(gettext("  < Back  "), "history.back()",
                        gettext("  Cancel  "),
                        "self.location.href = \"$link\"" );
		print $q->end_form();
        }
}

# Get useful commands output into memory 
sub get_commandoutput
{
	my ($self) = @_;
	my $updated;
	($updated, @SCCONFPOUT) = $runcommand->scconfp_cache();

	if ($updated == TRUE) {
		@DG_LIST = $self->_get_devicegrouplist();
	}	
}

##############################################################################
#
# Updated Cluster Manager Look and Feel functions.
#
# See http://spgweb.eng/hci/behret/sc3x-cm/ for more information.
#
##############################################################################

#
# Print the global devices table
#

sub global_devices_table($$) {
	my ($self, $q) = @_;

	# Set the headers for the device group status table. 
	my @deviceheaders = (gettext("Name"), "33%",
			     "&nbsp;", 14,
			     gettext("Status"), "20%",
			     gettext("Current Primary"), "47%");

	# Determine the number of columns from the headers
	my $numcols = ($#deviceheaders + 1) / 2;

	# Start the property table
	$q->start_prop_table(gettext("Global Devices"), \@deviceheaders,
		$DEVICEIMAGE); 

	# Get the list of device groups. Each element of the returned
	# array is hash table, containing the fields of: name, status,
	# primary, and nodelist.

	# Load the device data
	my @devlist = $self->get_devicegrouplist();

	# If we don't have any device groups, print a message
	if ($#devlist == -1) {
		# Start a table row and cell with the colspan of the table
		$q->start_prop_tr();
		$q->start_prop_td({ COLSPAN => 4 });

		# Print the message in table-label-text style
		$q->start_table_text('table-label-text');
		print gettext("No global devices have been configured on this cluster");
		$q->end_table_text();

		# End the cell and row
		$q->end_prop_td();
		$q->end_prop_tr();
	}

	# Cycle through the list of device groups and print the status
	for (my $i=0; $i<=$#devlist; $i++) {

		# Get the node status
		my ($name, $status, $primary, $nodelist);

		$name = $devlist[$i]{'name'};
		$status = $devlist[$i]{'status'};
		$primary = $devlist[$i]{'primary'};
		$nodelist = $devlist[$i]{'nodelist'};

		# Start a table row for the devicegroup
		$q->start_prop_tr();

		# Print a table cell for the nodename
		$q->start_prop_td();

		# This function will print an icon and a link. It takes the
		# label text, the alt tag text, the link to point to, and the
		# icon to display before the text.
		$q->table_link_text($name, 
				    gettext("Global Device"),
				    "$VIEW?devicegroup=$devlist[$i]{'name'}");

		$q->end_prop_td();

		# Print a table cell for the status
		$q->start_prop_td();

		# This function will print the correctly-colored & styled
		# text for the given status.
		$q->table_status_text($status, \@status_icons);

		# End the table cell and table row
		$q->end_prop_td();

		# Start the current primaries table cell
		$q->start_prop_td();

		# Print the primary
		$q->start_table_text('table-normal-text');
		print $primary;
		$q->end_table_text();

		# End the cell and row
		$q->end_prop_td();
		$q->end_prop_tr();

		# If we're not on our last row, print the line row divider
		if ($i < $#devlist) {
		    $q->line_row($numcols);
		}
	}
	$q->end_prop_table();
}


#
# Print the monitored devices table
#

sub monitored_disks_table($$) {
	my ($self, $q) = @_;

	# Set the headers for the device group status table. 
	my @deviceheaders = (gettext("Disk Path"), "53%",
			     "&nbsp;", 14, # For the status icon & text
			     gettext("Status"), "47%");

	# Determine the number of columns from the headers
	my $numcols = ($#deviceheaders + 1) / 2;

	# Start the property table
	$q->start_prop_table(gettext("Disk Paths"), \@deviceheaders,
		$DEVICEIMAGE); 

	# Get the list of monitored disks in a hash, keyed by disk
	# path, with the status as the value.
	my %dpmstatus = $self->get_dpm_disks();
	my @diskpaths = sort keys %dpmstatus;

	# If we don't have any monitored disks, print a message
	if ($#diskpaths == -1) {
		# Start a table row and cell with the colspan of the table
		$q->start_prop_tr();
		$q->start_prop_td();

		# Print the message in table-label-text style
		$q->start_table_text('table-label-text');
		print gettext("Disk path monitoring has not been configured " .
			      "for any disks on this cluster");
		$q->end_table_text();

		# End the cell and row
		$q->end_prop_td();
		$q->end_prop_tr();
	}

	# Cycle through the list of device groups and print the status
	for (my $i=0; $i<=$#diskpaths; $i++) {
		my $diskpath = $diskpaths[$i];
		my $status = $dpmstatus{$diskpath};

		# Start a table row for the devicegroup
		$q->start_prop_tr();

		# Print a table cell for the nodename
		$q->start_prop_td();

		# Print the disk path
		$q->start_table_text('table-normal-text');
		print $diskpath;
		$q->end_table_text();

		$q->end_prop_td();

		# Start the status text cell
		$q->start_prop_td();

		# This function will print the correctly-colored & styled
		# text for the given status.
		$q->table_status_text($status, \@path_status_icons);

		# End the table cell and table row
		$q->end_prop_td();
		$q->end_prop_tr();

		# If we're not on our last row, print the line row divider
		if ($i < $#diskpaths) {
		    $q->line_row($numcols);
		}
	}
	$q->end_prop_table();
}

# Print the specified devicegroup's properties 
sub devicegroup_table($$$)
{
	my ($self, $q, $devicegroup) = @_;

	# Set the headers for the device group property table 
        my @headers = (gettext("Property"), "50%",
			 "&nbsp;", "14",
                         gettext("Value"), "50%");

	# Determine the number of columns from the headers
        my $numcols = ($#headers + 1) / 2;

	# Start the property table
        $q->start_prop_table("Global Device Properties - $devicegroup",
			\@headers, $DEVICEIMAGE);

	# Load the device data
        my @dg_list = $self->get_devicegrouplist();

	my $idx = $self->find_dg_by_name(\@dg_list, $devicegroup);
	if ($idx < 0) {
		# If we can't find the devicegroup name, print error 
		# Start a table row and cell with the colspan of the table
		$q->start_prop_tr();
		$q->start_prop_td({ COLSPAN => 3 });

		# Print the message in table-label-text style
		$q->start_table_text('table-label-text');
		print sprintf(gettext("Global Device %s is not configured on this cluster"), $devicegroup);
		$q->end_table_text();

		# End the cell and row
		$q->end_prop_td();
		$q->end_prop_tr();
	        $q->end_prop_table();
		return;
        }

	$q->start_prop_tr();
	$q->start_prop_td();
	$q->start_table_text('table-label-text');
	print gettext("Current Status:");
	$q->end_table_text();
        $q->end_prop_td();
        $q->start_prop_td();
	$q->table_status_text($dg_list[$idx]{'status'}, \@status_icons);
	$q->end_prop_td();
        $q->end_prop_tr();
        $q->line_row($numcols);
	
	$q->start_prop_tr();
	$q->start_prop_td();
	$q->start_table_text('table-label-text');
	print gettext("Device Group Name:");
	$q->end_table_text();
        $q->end_prop_td();
        $q->start_prop_td();
	$q->table_status_text($dg_list[$idx]{'name'});
	$q->end_prop_td();
        $q->end_prop_tr();
        $q->line_row($numcols);
	
	$q->start_prop_tr();
	$q->start_prop_td();
	$q->start_table_text('table-label-text');
	print gettext("Device Group Type:");
	$q->end_table_text();
        $q->end_prop_td();
        $q->start_prop_td();
	$q->table_status_text($dg_list[$idx]{'type'});
	$q->end_prop_td();
        $q->end_prop_tr();
        $q->line_row($numcols);
	
	$q->start_prop_tr();
	$q->start_prop_td();
	$q->start_table_text('table-label-text');
	print gettext("Primary:");
	$q->end_table_text();
        $q->end_prop_td();
        $q->start_prop_td();
	$q->table_status_text($dg_list[$idx]{'primary'});
	$q->end_prop_td();
        $q->end_prop_tr();
        $q->line_row($numcols);
	
	$q->start_prop_tr();
	$q->start_prop_td();
	$q->start_table_text('table-label-text');
	print gettext("Secondaries:");
	$q->end_table_text();
        $q->end_prop_td();
        $q->start_prop_td();
	my $nodes;
	$nodes = $dg_list[$idx]{'secondary_list'};
	$nodes =~ s/,/, /g; # Improve formatting
	$q->table_status_text($nodes);
	$q->end_prop_td();
        $q->end_prop_tr();
        $q->line_row($numcols);
	
	$q->start_prop_tr();
	$q->start_prop_td();
	$q->start_table_text('table-label-text');
	print gettext("Spares:");
	$q->end_table_text();
        $q->end_prop_td();
        $q->start_prop_td();
	$nodes = $dg_list[$idx]{'spare_list'};
	$nodes =~ s/,/, /g; # Improve formatting
	$q->table_status_text($nodes);
	$q->end_prop_td();
        $q->end_prop_tr();
        $q->line_row($numcols);
	
	$q->start_prop_tr();
	$q->start_prop_td();
	$q->start_table_text('table-label-text');
	print gettext("Nodelist:");
	$q->end_table_text();
        $q->end_prop_td();
        $q->start_prop_td();
	$nodes = $dg_list[$idx]{'nodelist'};
	$nodes =~ s/,/, /g; # Improve formatting
	$q->table_status_text($nodes);
	$q->end_prop_td();
        $q->end_prop_tr();
        $q->line_row($numcols);
	
	$q->start_prop_tr();
	$q->start_prop_td();
	$q->start_table_text('table-label-text');
	print gettext("Failback:");
	$q->end_table_text();
        $q->end_prop_td();
        $q->start_prop_td();
	$q->table_status_text(gettext($dg_list[$idx]{'failback'}));
	$q->end_prop_td();
        $q->end_prop_tr();
        $q->line_row($numcols);

	$q->start_prop_tr();
	$q->start_prop_td();
	$q->start_table_text('table-label-text');
	print gettext("Preference Node List:");
	$q->end_table_text();
        $q->end_prop_td();
        $q->start_prop_td();
	$q->table_status_text(gettext($dg_list[$idx]{'preferenced'}));
	$q->end_prop_td();
        $q->end_prop_tr();
        $q->line_row($numcols);

	$q->start_prop_tr();
	$q->start_prop_td();
	$q->start_table_text('table-label-text');
	print gettext("Desired Number of Secondaries:");
	$q->end_table_text();
        $q->end_prop_td();
        $q->start_prop_td();
	$q->table_status_text($dg_list[$idx]{'secondaries'});
	$q->end_prop_td();
        $q->end_prop_tr();

	# Print mounted file system if there is any
	my @mounted = $self->get_mounted_directories($devicegroup);
	for (my $i = 0; $i <= $#mounted; $i++) {
		$q->line_row($numcols);
		$q->start_prop_tr();
		$q->start_prop_td();
		$q->start_table_text('table-label-text');
		print gettext("Logical Disk:");
		$q->end_table_text();
		$q->end_prop_td();
		$q->start_prop_td();
		$q->table_status_text($mounted[$i]{'logicaldisk'});
		$q->end_prop_td();
		$q->end_prop_tr();
		$q->line_row($numcols);
	
		$q->start_prop_tr();
		$q->start_prop_td();
		$q->start_table_text('table-label-text');
		print gettext("Mounted Filesystem:");
		$q->end_table_text();
		$q->end_prop_td();
		$q->start_prop_td();
		$q->table_status_text($mounted[$i]{'mounted'});
		$q->end_prop_td();
		$q->end_prop_tr();
	}
	$q->end_prop_table();
}
	
# Print the specified devicegroup's properties to be modified
sub devicegroup_mod_table($$$)
{
	my ($self, $q, $devicegroup) = @_;

	print $q->_script($q->orderbox_jscript());

	# Load the device data
        my @dg_list = $self->get_devicegrouplist();

	my $idx = $self->find_dg_by_name(\@dg_list, $devicegroup);
	if ($idx < 0) {
		# If we can't find the devicegroup name, print error 

		# Print the message in table-label-text style
		print sprintf(gettext("Global Device %s is not configured on this cluster"), $devicegroup);

		return;
        }


    # construct the available & selected node scrolling lists
    my @nodelist = $self->get_nodelist_fromstr($dg_list[$idx]{'nodelist'});
    my $spacer = "____________________";
    my @selectednodeslist = (@nodelist,"ignoreMe"); # noI18N
    my @availnodeslist = ("ignoreMe");
    my %nodelistlabels;
    foreach my $node (@nodelist) {
      $nodelistlabels{$node} =  $node;
    }
    $nodelistlabels{"ignoreMe"} = $spacer; # noI18N

    # Start the form
    print $q->start_form({ ACTION => "$CONFIG",
			   METHOD => "POST"
			   });

    # State information
    print $q->input ({ type => "hidden",
		       name => "step",
		       value => "3"});

    print $q->input ({ type => "hidden",
		       name => "devicegroup",
		       value => $devicegroup});

    print $q->input ({ type => "hidden",
		       name => "func",
		       value => "Props"});

    # Start the action table
    $q->start_action_table();

    # Input desired secondaries
    $q->start_action_tr();
    $q->start_action_td();
    print $q->CGI::div({CLASS => "action-window-label-text"}, 
	gettext("Desired Number of Secondaries:"));
    $q->end_action_td();
    $q->start_action_td();

    # Secondary choices are default (0) or 1 to one less than the
    # number of nodes, which is $#nodelist.
    my (@secvalues, %seclabels);
    for (my $i = 0; $i <= $#nodelist; $i++) {
	push @secvalues, $i;
	$seclabels{$i} = $i;
    }
    $seclabels{0} = "default";
    print $q->popup_menu({ NAME      => "secondaries",
		      VALUES	    => \@secvalues,
		      DEFAULT	=> $dg_list[$idx]{'secondaries'},
		      LABELS	=> \%seclabels
		      });
    $q->end_action_td();
    $q->end_action_tr();

    # Input failback
    $q->start_action_tr();
    $q->start_action_td();
    print $q->CGI::div({CLASS => "action-window-label-text"}, 
	gettext("Failback:"));
    $q->end_action_td();
    $q->start_action_td();
    my %edlabels = ("enabled" => gettext("Enabled"),
	"disabled" => gettext("Disabled"));
    print $q->popup_menu({ NAME      => "failback",
		      VALUES	    => ["enabled", "disabled"],
		      DEFAULT		=> $dg_list[$idx]{'failback'},
		      LABELS	=> \%edlabels
		      });
    $q->end_action_td();
    $q->end_action_tr();

    # Input preferenced
    $q->start_action_tr();
    $q->start_action_td();
    print $q->CGI::div({CLASS => "action-window-label-text"}, 
	gettext("Preference Node List:"));
    $q->end_action_td();
    $q->start_action_td();
    my %tflabels = ("true" => gettext("True"),
	"false" => gettext("False"));
    print $q->popup_menu({ NAME      => "preferenced",
		      VALUES	    => ["true", "false"],
		      DEFAULT		=> $dg_list[$idx]{'preferenced'},
		      LABELS	=> \%tflabels
		      });
    $q->end_action_td();
    $q->end_action_tr();

    # Node list

    $q->start_action_tr();
    $q->start_action_td({ VALIGN => "top" });
    print $q->CGI::div({CLASS => "action-window-label-text"},
        gettext("Node List:"));
    $q->end_action_td();

    $q->start_action_td({ VALIGN => "top" });

	# print "<br> -- WIDGET START SIMPLE 1 --<br>";

    print $q->start_table({ BORDER => 0 });

    print $q->start_Tr();
    print $q->start_td();
    # The following hidden input carries data from the selected nodes list
    print $q->input ({ type => "hidden",
		       name => "node_list" });

    print $q->scrolling_list({ single => "true",
			       size => "9",
			       name => "selnodes",
			       values => \@selectednodeslist,
			       labels => \%nodelistlabels
			     });
    print $q->end_td();

    print $q->start_td();

    print $q->input({ type => "button",
		      value => gettext("  Up  "),
		      onclick => "vertical_shift(this.form.selnodes,0);
				  bundleListAsInput(this.form.selnodes,this.form.node_list)" });
		      print $q->br();
    print $q->input({ type => "button",
		      value => gettext("Down"),
		      onclick => "vertical_shift(this.form.selnodes,1);
				  bundleListAsInput(this.form.selnodes,this.form.node_list)" });
    print $q->end_td();

    print $q->end_Tr();
    print $q->end_table();

	# print "<br> -- WIDGET SIMPLE END --<br>";

    $q->end_action_td();
    $q->end_action_tr();


    # End the table
    $q->end_action_table();

    # Start the action button table
    $q->start_action_button_table();
    $q->start_action_tr({ HEIGHT  => 15 });
    $q->start_action_td({ COLSPAN => 2 });
    print $q->img({ SRC    => "/images/dot.gif",
		    ALT    => "",
		    HEIGHT => 1,
		    WIDTH  => 1
		    });
    $q->end_action_td();
    $q->end_action_tr();

    $q->start_action_tr({ HEIGHT  => 1 });
    $q->start_action_td({ COLSPAN => 2,
			  CLASS => "action-line-row"
			  });
    print $q->img({ SRC    => "/images/dot.gif",
		    ALT    => "",
		    HEIGHT => 1
		    });
    $q->end_action_td();
    $q->end_action_tr();

    $q->start_action_tr({ HEIGHT  => 6 });
    $q->start_action_td({ COLSPAN => 2,
			  HEIGHT  => 5
			  });
    print $q->img({ SRC    => "/images/dot.gif",
		    ALT    => "",
		    HEIGHT => 6
		    });
    $q->end_action_td();
    $q->end_action_tr();

    $q->start_action_tr();
    $q->start_action_td({ ALIGN => "RIGHT",
			  WIDTH => "80%" 
			  });
    print $q->input({ TYPE => "SUBMIT",
		      NAME => "doit",
                      onclick => "bundleListAsInput(this.form.selnodes,this.form.node_list)",
		      VALUE => gettext(" Modify Properties ")
		      });
    $q->end_action_td();
    $q->start_action_td({ ALIGN => "RIGHT",
			  WIDTH => "20%" 
			  });
    print $q->input({ TYPE => "BUTTON",
		      VALUE => gettext(" Cancel "),
		      ONCLICK => "history.back()"
		      });
    $q->end_action_tr();
    $q->end_action_td();

    # End the table
    $q->end_action_table();

    # End the form
    print $q->input({ TYPE  => "HIDDEN",
		      NAME  => "func",
		      VALUE => "create"
		      });
    print $q->end_form();
}

# Get the hash of all disks & their status as reported through scdpm
sub get_dpm_disks() {
	my ($self) = @_;
	my ($empty, %devstatus);
	my ($ret, @devlist) = $runcommand->scdpm("-p all");
	foreach my $line (@devlist) {
		my ($devpath, $status);
		($empty, $devpath, $empty, $status) = split /(\S+)/, $line;
		$devstatus{$devpath} = $status;
	}

	return %devstatus;
}

# Get the list of monitored disks
sub get_monitored_disks() {
	my ($self) = @_;
	my (@monitored_disks);
	my %devstatus = $self->get_dpm_disks();
	foreach my $disk (sort keys %devstatus) {
		if ($devstatus{$disk} ne "Unmonitored") {
			push @monitored_disks, $disk;
		}
	}

	return @monitored_disks;
}

# Get the list of unmonitored disks across the cluster
sub get_unmonitored_disks() {
	my ($self) = @_;
	my (@unmonitored_disks);
	my %devstatus = $self->get_dpm_disks();
	foreach my $disk (sort keys %devstatus) {
		if ($devstatus{$disk} eq "Unmonitored") {
			push @unmonitored_disks, $disk;
		}
	}

	return @unmonitored_disks;
}

sub member {
    my ($item, @array) = @_;

    # Return position of element if we find it
    for (my $i = 0; $i <= $#array; $i++) {
        if ($item eq $array[$i]) {
            return $i;
        }
    }

    # return -1 if we didn't find element
    return -1;
}

# Return true
1;
