#!/usr/bin/perl

use Getopt::Std;
use XML::LibXML;
use POSIX;

my $ME = $0;

END {
  defined fileno STDOUT or return;
  close STDOUT and return;
  warn "$ME: failed to close standard output: $!\n";
  $? ||= 1;
}

my %device_list;

$_ = $0;
s/.*\///;
my $pname = $_;

#BEGIN_VERSION_GENERATION
$FENCE_RELEASE_NAME="2.0.115";
$REDHAT_COPYRIGHT=("Copyright (C) Red Hat, Inc.  2004  All rights reserved.");
$BUILD_DATE="(built Tue Aug 23 13:16:07 EDT 2016)";
#END_VERSION_GENERATION

sub usage
{
    print "Usage\n";
    print "\n";
    print "$pname [options]\n";
    print "\n";
    print "Options\n";
    print "  -n <node>        IP address or hostname of node to fence\n";
    print "  -h               usage\n";
    print "  -V               version\n";
    print "  -v               verbose\n";

    exit 0;
}

sub version
{
    print "$pname $FENCE_RELEASE_NAME $BUILD_DATE\n";
    print "$REDHAT_COPYRIGHT\n" if ( $REDHAT_COPYRIGHT );

    exit 0;
}

sub fail
{
    ($msg)=@_;

    print $msg."\n" unless defined $opt_q;

    exit 1;
}

sub fail_usage
{
    ($msg)=@_;

    print STDERR $msg."\n" if $msg;
    print STDERR "Please use '-h' for usage.\n";

    exit 1;
}

sub get_cluster_id
{
    my $cluster_id;

    my $cmd = "cman_tool status";
    my @out = qx { $cmd };

    die "Unable to execute cman_tool.\n" if ($?>>8);

    foreach (@out)
    {
	chomp;

	my ($param, $value) = split(/\s*:\s*/, $_);

	if ($param =~ /^cluster\s+id/i) {
	    $cluster_id = $value;
	    last;
	}
    }

    print "[$pname]: get_cluster_id = $cluster_id\n" if $opt_v;

    return $cluster_id;
}

sub get_node_id
{
    ($name)=@_;

    my $xml = XML::LibXML->new();
    my $tree = $xml->parse_file("/etc/cluster/cluster.conf");

    my $xpath = "//cluster/clusternodes/clusternode[\@name='$name']/\@nodeid";

    my $node_id = $tree->findvalue($xpath);

    print "[$pname]: get_node_id ($name) = $node_id\n" if $opt_v;

    return $node_id;
}

sub get_node_name
{
    print "[$pname]: get_node_name = $opt_n\n" if $opt_v;

    return $opt_n;
}

sub get_host_id
{
    my $host_id;

    my $cmd = "cman_tool status";
    my @out = qx { $cmd };

    die "Unable to execute cman_tool.\n" if ($?>>8);

    foreach (@out)
    {
	chomp;

	my ($param, $value) = split(/\s*:\s*/, $_);

	if ($param =~ /^node\s+id/i) {
	    $host_id = $value;
	    last;
	}
    }

    print "[$pname]: get_host_id = $host_id\n" if $opt_v;

    return $host_id;
}

sub get_host_name
{
    my $host_name;

    my $cmd = "cman_tool status";
    my @out = qx { $cmd };

    die "Unable to execute cman_tool.\n" if ($?>>8);

    foreach (@out)
    {
	chomp;

	my ($param, $value) = split(/\s*:\s*/, $_);

	if ($param =~ /^node\s+name/i) {
	    $host_name = $value;
	    last;
	}
    }

    print "[$pname]: get_host_name = $host_name\n" if $opt_v;

    return $host_name;
}

sub get_key
{
    ($name)=@_;

    my $cluster_id = get_cluster_id();
    my $node_id = get_node_id($name);

    if ($node_id == 0) {
	die "Unable to determine nodeid for $name.\n";
    }

    my $key = sprintf "%x%.4x", $cluster_id, $node_id;

    print "[$pname]: get_key ($name) = $key\n" if $opt_v;

    return $key;
}

sub get_options_stdin
{
    my $opt;
    my $line = 0;

    while (defined($in = <>))
    {
	$_ = $in;
	chomp;

	# strip leading and trailing whitespace
	s/^\s*//;
	s/\s*$//;

	# skip comments
	next if /^#/;

	$line += 1;
	$opt = $_;

	next unless $opt;

	($name, $val) = split(/\s*=\s*/, $opt);

	if ($name eq "")
	{
	    print STDERR "parse error: illegal name in option $line\n";
	    exit 2;
	}
	elsif ($name eq "agent")
	{
	}
	elsif ($name eq "node")
	{
	    $opt_n = $val;
	}
	elsif ($name eq "nodename")
	{
	    $opt_n = $val;
	}
	elsif ($name eq "verbose")
	{
	    $opt_v = $val;
	}
	else
	{
	    print STDERR "parse error: unknown option \"$opt\"\n";
	}
    }
}

sub get_key_list
{
    ($device) = @_;

    my $cmd = "sg_persist -d $device -i -k";
    my @out = qx { $cmd };

    die "Unable to execute sg_persist.\n" if ($?>>8);

    my %key_list;

    foreach (@out)
    {
	chomp;

	if ($_ =~ /^\s*0x/)
	{
	    s/^\s+0x//;
	    s/\s+$//;

	    $key_list{$_} = undef;
	}
    }

    # DEBUG: use -v option
    #
    if ($opt_v)
    {
	my $count = keys %key_list;
	my $index = 0;

	print "[$pname]: get_key_list: found $count keys registered with $device\n";

	for $key (keys %key_list)
	{
	    print "[$pname]:   ($index) key=$key\n";
	    $index++;
	}
    }

    return %key_list;
}

sub get_scsi_devices
{
    my $cmd = "vgs --config 'global { locking_type = 0 }'" .
              "    --noheadings --separator : -o vg_attr,pv_name,pv_uuid";

    my @out = qx { $cmd 2> /dev/null};

    die "Unable to execute vgs.\n" if ($?>>8);

    foreach (@out)
    {
	chomp;

	my ($vg_attrs, $pv_name, $pv_uuid) = split(/:/, $_);

	if ($vg_attrs =~ /c$/)
	{
	    $device_list{"\U$pv_uuid"} = $pv_name;
	}
    }

    # DEBUG: use -v flag
    #
    if ($opt_v)
    {
	my $count = keys %device_list;
	my $index = 0;

	print "[$pname]: get_scsi_devices: found $count devices\n";

	for $uuid (sort keys %device_list)
	{
	    print "[$pname]:   ($index) device=$device_list{$uuid} UUID=$uuid\n";
	    $index++;
	}
    }
}

sub check_sg_persist
{
    my $cmd = "sg_persist -V";
    my $out = qx { $cmd };

    die "Unable to execute sg_persist.\n" if ($?>>8);
}

sub fence_node
{
    my $host_name = get_host_name();
    my $node_name = get_node_name();

    my $host_key = get_key($host_name);
    my $node_key = get_key($node_name);

    for $uuid (sort keys %device_list)
    {
	my $device = $device_list{$uuid};
	my %key_list = get_key_list($device);

	if (! exists $key_list{$host_key})
	{
	    fail "Unable to perform fence operation.";
	}

	if (! exists $key_list{$node_key})
	{
	    next;
	}

	if ($host_key eq $node_key)
	{
	    $cmd = "sg_persist -n -d $device -o -G -K $host_key -S 0";
	}
	else
	{
	    $cmd = "sg_persist -n -d $device -o -A -K $host_key -S $node_key -T 5";
	}

	my $out = qx { $cmd };

	die "Unable to execute sg_persist ($dev).\n" if ($?>>8);
    }
}

### MAIN #######################################################

if (@ARGV > 0) {

    getopts("n:hqvV") || fail_usage;

    usage if defined $opt_h;
    version if defined $opt_V;

    fail_usage "Unknown parameter." if (@ARGV > 0);

    fail_usage "No '-n' flag specified." unless defined $opt_n;

} else {

    get_options_stdin();

    fail "failed: missing 'node'" unless defined $opt_n;

}

check_sg_persist();

get_scsi_devices();

fence_node();
