#!/usr/bin/perl
# Copyright (c) 2014-2015 Auristor Inc. All Rights Reserved
#
# Convert an OpenAFS CellServDB file into a cellservdb.conf file.
#
use warnings;
use strict;

use File::Basename;
use Getopt::Long;
use Pod::Usage;

my $man  = 0;
my $help = 0;
GetOptions(
    'help|?' => \$help,
    man      => \$man
) or pod2usage(2);
if ($help) { pod2usage(1); }
if ($man) { pod2usage( -verbose => 2 ); }

# If no arguments were given, then allow STDIN to be used only
# if it's not connected to a terminal (otherwise print usage)
if ( ( @ARGV == 0 ) && ( -t STDIN ) ) {
    pod2usage("$0: No files given.");
}

# Old file to convert:
my $cellservdb = $ARGV[0];

# New file to write:
my $conf_file = $ARGV[1] || 'cellservdb.conf';

my @cells = read_cellservdb($cellservdb);
my $gco_date = grand_central_org_date($cellservdb);

exit !( write_conf_file( $conf_file, \@cells, $gco_date) );

# Read a CellServDB file and parse it into a list of cells.
# Argument: a file path to CellServDB.
# Returns a list of cells' data in hashrefs.
#
sub read_cellservdb {
    my $file = shift || die "missing filename argument to read_cellservdb()";

    # The list of hashrefs for all data in CellServDB.
    my @cells;

    my $cell;    # The current cell that we're reading in the CellServDB file.

    # Open file handle to $file.
    my $fh;
    open( $fh, '<', $file )
      || die "could not open CellServDB at $file: $!";

    while ( my $line = <$fh> ) {
    chomp $line;
    $line =~ s/\r//g;
    if ( $line =~ /^>(.+)/ ) {

        # This line is a cell entry. For example:
        #   >cell.example.org      #Example Cell Organization
        # If we were reading a cell from a previous line, push that one
        # onto the list before we reassign $cell.
        if ( defined($cell) ) {
        push( @cells, $cell );
        $cell = {};
        }

        # $1 could be "cell.example.org   ",
        # or "cell.example.org   linked.example.org  linked2.example.org".
	$line =~ /^>(.+)#/;
        my ( $name, @linked_cells ) = split( /\s+/, $1 );

        # Now store this new cell's information.
        $cell->{'name'} = $name;
        if ( @linked_cells > 1 ) {
	    die "cell has multiple linked cells at $file: $!";
	}
        if ( @linked_cells > 0 ) {
        $cell->{'linked_cells'} = \@linked_cells;
        }

        # Check if we have a description for this cell. If so, store it.
        if ( $line =~ /#\s*(.+)$/ ) {
        $cell->{'description'} = $1;
        }
    }
    elsif ( $line =~ /^(\[?[0-9\.]+\]?)/ ) {

        # This line is a server entry. For example:
        #   192.0.2.99      #yfs1.example.org
        # Or a non-voting clone:
        #   [192.0.2.99]      #yfsro1.example.org
        my $server = {};

        my ($ip) = $1 =~ /([0-9\.]+)/;
        my $clone = $1 =~ /\[$ip\]/;

        # Store the IP and clone boolean.
        $server->{'ip'} = $ip;
        if ($clone) {
        $server->{'clone'} = 1;
        }

        # Also store the server's hostname.
        ( $server->{'hostname'} ) = $line =~ /\s+#\s*(.*)$/;

        # Save this server in our list of $cell->{servers}.
        push( @{ $cell->{'servers'} }, $server );
    }
    else {
        print "Unknown CellServDB line $line\n";
    }
    }
    close($fh);

    # Push the final cell's data onto the list.
    push @cells, $cell;

    return @cells;
}

# Read a CellServDB file and determine if this was a grand.central.org public
# CellServDB file. These files have the string "GCO Public CellServDB".
# Argument: a file path to CellServDB.
# Returns a date string, or undef if nothing was found.
#
sub grand_central_org_date {
    my $file = shift || die 'missing filename argument';
    
    # Open file handle to $file.
    my $fh;
    open( $fh, '<', $file )
      || die "could not open CellServDB at $file: $!";

    while ( <$fh> ) {
        if (/GCO Public CellServDB (.+)/) {
            return $1;
        }
    }
    return undef;
}

# Write a cellservdb.conf file, printing cell data in the proper format.
# Arguments: 1) a file path to cellservdb.conf,
#            2) an arrayref to a list of cells' data.
# Returns 'true' if we could write the cellservdb.conf file successfully.
#
sub write_conf_file {
    my $file  = shift || die "missing file argument";
    my $cells = shift || die "missing cells argument";
    my $gco_date = shift;

    # Open file handle to $file.
    my $fh;
    if ($file eq '-') {
        $fh = *STDOUT;
    } else {
        open( $fh, '>', $file )
          || die "could not open cellservdb.conf at $file: $!";
    }

    # Print a comment header, including the date that we ran the conversion.
    print $fh "# Auristor cellservdb client configuration\n";
    print $fh '# Auto-generated from ' . basename($cellservdb) . ' by ' . basename($0) . "\n";
    print $fh '# at ' . localtime() . "\n";
    print $fh "#\n";
    print $fh "# This file should be placed in the yfs-client.conf.d directory\n";
    print $fh "\n";

    # Print [cells] section.
    print $fh "[cells]\n";

    for my $cell ( @{$cells} ) {
        if ( $gco_date ) {
            print $fh "\t# "
                . $cell->{'name'}
                . " imported from GCO Public CellServDB $gco_date\n";
        }
        print $fh "\t" . $cell->{'name'} . " = {\n";
        if ( $cell->{'description'} ) {
            print $fh "\t\tdescription = \"" . $cell->{'description'} . "\"\n";
        }
        if ( $cell->{'linked_cells'} && @{$cell->{'linked_cells'}} > 0 ) {
            print $fh "\t\tlinkedcell = "
                . join(' ', @{$cell->{'linked_cells'}})
                . "\n";
        }
        if ( $cell->{'servers'} ) {
            print $fh "\t\tservers = {\n";
            for my $server ( @{ $cell->{'servers'} } ) {
            print $fh "\t\t\t" . $server->{'hostname'} . " = {\n";
            print $fh "\t\t\t\taddress = " . $server->{'ip'} . "\n";
            if ( $server->{'clone'} ) {
                print $fh "\t\t\t\tclone = yes\n";
            }
            print $fh "\t\t\t}\n";
            }
            print $fh "\t\t}\n";
        }
        print $fh "\t}\n";
    }

    return close($fh);
}

sub prompt {
    my $q = shift || die "missing argument to prompt()";
    local $| = 1;
    print $q;
    chomp( my $answer = <STDIN> );
    return $answer;
}

__END__

=head1 NAME

convertcellservdb - Convert CellServDB files into YFS configuration files

=head1 SYNOPSIS

convertcellservdb [options] [CellServDB file] [cellservdb.conf file]

=head1 OPTIONS

=over 8

=item B<-help>

Print a brief help message and exits.

=item B<-man>

Prints the manual page and exits.

=back

=head1 DESCRIPTION

B<This program> will read the given input CellServDB file and translate the
contents into a cellservdb.conf file. If you do not specify a full path for
the new cellservdb.conf file, then the program will write the file into the
current working directory.

Specify C<-> as the second argument to print the results to STDOUT instead of
writing to a file.

=cut
