#!/usr/bin/perl -w
#
#  (C) 2001 Clemson University and The University of Chicago
#
#  See COPYING in top-level directory.
#
# generate a global pvfs2 configuration file based on user input
#
use Term::ReadLine;
use Getopt::Long;

# turn on strictness
use strict 'vars';

# ugly global variables for option parsing
my $opt_protocol = '';
my $opt_port = '';
my $opt_tcpport = '';
my $opt_gmport = '';
my $opt_ibport = '';
my $opt_ioservers = '';
my $opt_metaservers = '';
my $opt_logfile = '';
my $opt_storage = '';
my $opt_trovesync = '1';
my $opt_quiet = '';
my $opt_logging = '';
my $opt_logstamp = '';
my $opt_server_job_timeout = '';
my $opt_client_job_timeout = '';
my $opt_first_handle = '';
my $opt_last_handle = '';
my $opt_root_handle = '';
my $opt_fsid = '';
my $opt_default_num_dfiles = '';

my $opt_security = '0';
my $opt_trusted_port = '';
my $opt_trusted_network = '';
my $opt_trusted_netmask = '';

my $opt_iospec = undef;
my $opt_metaspec = undef;

my %all_endpoints = ();

my $META_ENDPOINT = 0x1;
my $IO_ENDPOINT   = 0x2;

my $OUT = undef;
my $term = undef;

#$num_unexp_reqs = prompt_num("How many unexpected requests should we be prepared to receive?  ");
my $num_unexp_reqs = 50;

# sometimes people use ip addresses instead of hostnames.  perl's default sort
# will sort the ip addresses lexically, not numerically, so we need a slightly
# smarter sorter 

sub get_user_input
{
    my($prompt,$res);
       
    print $OUT "\n";
    $prompt = "* $_[0]";

    my $line = $term->readline($prompt);
    print $OUT "\n";
    return $line;
}

sub valid_number
{
    my($num, $len, $i, $digit);

    $num = $_[0];
    $len = length($num);

    for($i = 0; $i < $len; $i++)
    {
        $digit = substr($num,$i,1);
        if (($digit < 0) || ($digit > 9))
        {
            return 0;
        }
    }
    return (($len > 0) ? 1 : 0);
}

sub prompt_num
{
    my($prompt,$num,$default);
    $prompt = $_[0];
    $default = $_[1];
    do
    {
        $num = get_user_input($prompt);
        if (length($num) == 0)
        {
            return $default;
        }
    } while(!valid_number($num));
    return $num;
}

sub prompt_word
{
    my($prompt,$default,$val);
    $prompt = $_[0];
    $default = $_[1];

    $val = get_user_input($prompt);
    if (length($val) == 0)
    {
        $val = $default;
    }
    return $val;
}

sub parse_hostlist
{
    my $inputline = shift;
    my @components = ();
    my @hosts = ();

    # we want to split the string into components seperated by comma
    # but we don't want split components that have curly brackets.  For example,
    # we need to be sure that "hosta{1-4,8-12},hostb,hostc{1,2,3}" splits to
    # hosta{1-4,8-12}
    # hostb
    # hostc{1,2,3}
    #
    @components = $inputline =~ /(?:,?[ ]*([^{,]+(?:{[^}]+})?))/g;
    foreach my $comp (@components)
    {
        # if we've got a component that has {..}, then expand.
        # match the prefix (hostname) and curly brackets
        if($comp =~ /([^{]+){([^}]+)}/)
        {
            my $prefix = $1;
            my $ranges = $2;
            
            # split the ranges string on the commas
            foreach my $r (split(/,/, $ranges))
            {
                if($r !~ /-/)
                {
                    # only one number, just push it on
                    push @hosts, "$prefix$r";
                }
                else
                {
                    # min and max in this range.  Add each of the indexes
                    my ($s, $f) = $r =~ /([0-9]+)-([0-9]+)/; 
                    for(my $i = $s; $i <= $f; ++$i)
                    {
                        push @hosts, "$prefix$i";
                    }
                }
            }
        }
        else {
            push @hosts, $comp;
        }
    }
    return @hosts;
}

sub emit_defaults
{
    my ($target, $num_unexp, $bmi_module, $logfile,
        $logging, $logstamp, $server_job_timeout, $client_job_timeout) = @_;

    print $target "<Defaults>\n";
    print $target "\tUnexpectedRequests $num_unexp\n";

    if(defined($logfile))
    {
        # remove quotes from logfile if any
        if ($logfile =~ /\".*\"/)
        {
            $logfile =~ s/\"//g;
        }

        print $target "\tLogFile $logfile\n";
    }

    print $target "\tEventLogging $logging\n";
    print $target "\tLogStamp $logstamp\n";
    print $target "\tBMIModules $bmi_module\n";
    print $target "\tFlowModules flowproto_multiqueue\n";
    print $target "\tPerfUpdateInterval 1000\n";
    print $target "\tServerJobBMITimeoutSecs $server_job_timeout\n";
    print $target "\tServerJobFlowTimeoutSecs $server_job_timeout\n";
    print $target "\tClientJobBMITimeoutSecs $client_job_timeout\n";
    print $target "\tClientJobFlowTimeoutSecs $client_job_timeout\n";
    print $target "\tClientRetryLimit 5\n";
    print $target "\tClientRetryDelayMilliSecs 2000\n";

    print $target "</Defaults>\n";
}

sub emit_security
{
    my ($target, $portlist, $network, $netmask) = @_;

    print $target "<Security>\n";
    print $target "\tTrustedPorts $portlist\n";
    print $target "\tTrustedNetwork $network $netmask\n";
    print $target "</Security>\n";
}

sub emit_aliases
{
    my $target = shift;

    print $target "\n<Aliases>\n";
    foreach my $alias (sort keys %all_endpoints)
    {
        print $target "\tAlias $alias " . 
        get_bmi_endpoint($alias) . "\n";
    }
    print $target "</Aliases>\n";
}

sub emit_filesystem
{
    my ($target, $name, $fs_id, $root_handle, 
        $last_handle, $first_handle, $count,
        $default_num_dfiles) = @_;

    # divide handle range space equally among servers ((2^32)-1 for now)
    my($total_num_handles_available, $start, $end, $i, $step, $num_ranges);
    $num_ranges = $count;
    $total_num_handles_available = $last_handle - $first_handle + 1;

    # since meta and data handle ranges must be split, increment
    # num nodes for calculation purposes below
    $step = sprintf("%lu",($total_num_handles_available / $num_ranges));

    print $target "\n<Filesystem>\n";
    print $target "\tName $name\n";
    print $target "\tID $fs_id\n";
    print $target "\tRootHandle $root_handle\n";
    if($default_num_dfiles > 0)
    {
        print $target "\tDefaultNumDFiles $default_num_dfiles\n";
    }
    print $target "\t<MetaHandleRanges>\n";

    $start = $end = $first_handle - 1;

    my @meta_aliases = get_aliases($META_ENDPOINT);
    @meta_aliases = sort @meta_aliases;
    foreach my $ma (@meta_aliases)
    {
        $start = $end + 1;
        $end += $step;
        print $target "\t\tRange $ma $start-$end\n";
    }

    print $target "\t</MetaHandleRanges>\n";
    print $target "\t<DataHandleRanges>\n";

    my @io_aliases = get_aliases($IO_ENDPOINT);
    @io_aliases = sort @io_aliases;
    foreach my $ia (@io_aliases)
    {
        $start = $end + 1;
        $end += $step;
        print $target "\t\tRange $ia $start-$end\n";
    }
    print $target "\t</DataHandleRanges>\n";

    print $target "\t<StorageHints>\n";

    # only in special cases would someone want to sync data (failover comes to
    # mind)  The default thus should be to sync metadata but not sync data.  
    if ($opt_trovesync == 1) {
        print $target "\t\tTroveSyncMeta yes\n";
        print $target "\t\tTroveSyncData no\n";
    } else {
        print $target "\t\tTroveSyncMeta no\n";
        print $target "\t\tTroveSyncData no\n";
        print $target "\t\tCoalescingHighWatermark infinity\n";
        print $target "\t\tCoalescingLowWatermark 1\n";
    }

    print $target "\t</StorageHints>\n";

    print $target "</Filesystem>\n";
}

sub emit_server_conf
{
    my($target, $node, $storage, $logfile) = @_;

    print $target "StorageSpace $storage\n";
    print $target "HostID \"" . get_bmi_endpoint($node) . "\"\n";
    print $target "LogFile $logfile\n";
}

sub confirm
{
    my($prompt, $char, $valid_char);
    $prompt = shift;
    $valid_char = 0;
    do
    {
        $char = prompt_word($prompt,"-");
        if (($char eq 'y') || ($char eq 'n'))
        {
            $valid_char = 1;
        }
    } while($valid_char == 0);

    return (($char eq 'y') ? 1 : 0);
}

sub specusage
{
    print $OUT <<"THIS"                                
               
  The -iospec and -metaspec options allow a wide variaty of configurations
  to be specified, including multiple endpoints on the same node (different
  ports), different storage locations for endpoints on the same node, etc.

  Both the -iospec and -metaspec options take strings as arguments.
  The format of the strings are comma separated endpoints, where 
  each endpoint is formatted as:
                
    [<proto>://]<host>:<port>[:<storage>][:<logfile>]

  The protocol, storage, and logfile are all optional.
  The port can be a range of the format {#-#,#,#-#,..}.
  If the logfile is specified, the storage path must be as well.
                
  Examples:

    myhosta:3334,myhostb:{3334-3338}
    myhosta:{3334-3338}
    ib://myhosta:3335:/psto,tcp://myhostb:3334:/psto

  Multiple protocols for the same endpoint may also be specified.  The
  format for this type of endpoint is:

    [<proto1>://<host>:<port1>,<proto2>://<host>:<port2>,...]

  In this case, the [] delineate the single endpoint (with multiple protocols)
  from the rest of the spec.  While the protocols and ports are different, the
  host for each uri must be the same.  For example:

    --metaspec="[ib://myhosta:3335,gm://myhosta:6]:/psto,tcp://myhostb:3334"

  This specifies that one endpoint is at myhosta with the infiniband and myrinet
  protocols enabled, and the other endpoint is at myhostb with tcp enabled.

  Note that the --iospec and --metaspec options cannot be used with enumerated
  hosts.  Each endpoint must be a single host.  I.e. tcp://myhost{1-4}:3334
  is not allowed.  This does not preclude multiple endpoints from being
  on the same host, such as tcp://myhost1:{3334-3338}

THIS
    ;;
}

sub usage
{

# dump usage with a single HERE document rather than seperate print
# statements
    print $OUT <<"THIS";    
Usage: pvfs2-genconfig [OPTIONS] <fs.conf> <server.conf>

  The pvfs2-genconfig utility creates configuration files for the
  PVFS2 file system.  The <fs.conf> and <server.conf> arguments are
  manditory and specify the names of the configuration files that will
  be written.  This utility will create one fs.conf file.  It will also
  create a seperate server.conf file for each server in the file system.
  Each server.conf file will be appended with the host name of the server
  to which it belongs.

  EXAMPLE: 'pvfs2-genconfig /tmp/fs.conf /tmp/server.conf' will
  generate a file called /tmp/fs.conf and server specific files
  called /tmp/server.conf-host1, /tmp/server.conf-host2, etc.  
  NOTE: If pvfs2-genconfig is executed with a single argument of "-", 
  then all output is directed to stdout and no files are written.

  All other arguments are optional.  If run without any optional
  arguments, then pvfs2-genconfig will prompt interactively for required
  parameters.

  pvfs2-genconfig can also be executed non-interactively by providing,
  at a minimum, all of the following arguments:

     --logfile     <STRING>     logfile location
     --storage     <STRING>     storage space location
     --quiet                    run silently

  One of the two [] grouped options below must be chosen (for non-interactive):

     [
       --protocol    <PROTO>[,<PROTO>..] protocol(s) to use (tcp,gm,ib)
       --port        <NUM>      port to use (any single protocol)
       --ioservers   <STRING>   hostnames of data servers.  Can be 
                                <prefix>{#-#,#,#-#,...}
       --metaservers <STRING>   hostnames of meta servers.
     ] 
  or 
     [
       --iospec      <STRING>   endpoints of data servers. See --spec-usage
       --metaspec    <STRING>   endpoints of meta servers. See --spec-usage
     ]

  The following arguments are entirely optional, whether your intention is
  to run pvfs2-genconfig in interactive or non-interactive mode:

     --tcpport     <NUM>               TCP port to use
     --gmport      <NUM>               GM port to use
     --ibport      <NUM>               IB port to use
     --logging     <STRING>            debugging mask for log messages
     --logstamp    <STRING>            timestamp type for log messages 
                                       ('none','usec', or 'datetime' are valid)
     --notrovesync                     sync metadata only upon request
     --server-job-timeout <NUM>        server job timeout value (seconds)
     --client-job-timeout <NUM>        server job timeout value (seconds)
     --first-handle <NUM>              first handle value to reserve
     --last-handle  <NUM>              last handle value to reserve
     --root-handle  <NUM>              handle value to reserve for root object
     --fsid         <NUM>              fs identifier value
     --default-num-dfiles <NUM>        number of datafiles to use per file
                                       (defaults to number of I/O servers)
     --trusted      <0|1>              indicate whether trusted connection options need to be emitted

     --help                            This message
     --spec-usage                      Show the usage info for --iospec 
                                       and --metaspec options

THIS

}

sub print_welcome
{
    if (!$opt_quiet) {
        print $OUT <<"WELCOMEMSG"
**********************************************************************
    Welcome to the PVFS2 Configuration Generator:

This interactive script will generate configuration files suitable
for use with a new PVFS2 file system.  Please see the PVFS2 quickstart
guide for details.

**********************************************************************
WELCOMEMSG
        ;;
    }
}

sub get_portlist
{
    my $type;

    if (!$opt_quiet) {
        print $OUT <<"PORTLIST"

You must enter the trusted port ranges that your file system will accept
This must be of the form <port1 - port2>
PORTLIST
        ;;
    }
    $type = prompt_word("Enter port list [Default is 0-65535]: ","0-65535");
    return $type;
}

sub get_network
{
    my $type;

    if (!$opt_quiet) {
        print $OUT <<"NETWORK"
You must enter the network address and network mask to identify list of trusted hosts
This must be of the form <network>, <netmask>
NETWORK
        ;;
    }
    $type = prompt_word("Enter network address, network mask [Default is 0.0.0.0, 0.0.0.0]: ", "0.0.0.0, 0.0.0.0");
    return $type;
}

sub get_protocol
{
    my $type;
    my %port;

    if ($opt_protocol) {
        $type = $opt_protocol;
    } else {
        if (!$opt_quiet) {
            # get network type
            print $OUT <<"PROTOCOL"
You must first select the network protocol that your file system will use.
The only currently supported options are \"tcp\", \"gm\", and \"ib\".
(For multi-homed configurations, use e.g. \"ib,tcp\".)
PROTOCOL
            ;;
        }
        $type = prompt_word("Enter protocol type [Default is tcp]: ","tcp");
    }

    foreach (split(',', $type)) {
        if ($_ eq "tcp") {
            $port{'tcp'} = tcp_get_port();
            if ($opt_security == 1)
            {
                $opt_trusted_port = get_portlist();
                my $str = get_network();
                my $cnt = 0;
                foreach (split(',', $str)) {
                    $_ =~ s/\s/ /g; 
                    $_ =~ s/ +/ /g;
                    $_ =~ s/^ +//;
                    $_ =~ s/ +$//;
                    if ($cnt == 0)
                    {
                        $opt_trusted_network = "tcp://" . $_;
                    }
                    else 
                    {
                        $opt_trusted_netmask = "tcp://" . $_;
                    }
                    $cnt = $cnt + 1;
                }
            }
        } elsif ($_ eq "gm") {
            $port{'gm'} = gm_get_port();
        } elsif ($_ eq "ib") {
            $port{'ib'} = ib_get_port();
        } else {
            die "Sorry.  At this time, only the tcp, gm and ib protocols are available\nfor use with this configuration utility.\n";
        }
    }

    return \%port;
}

sub get_logging
{
    my $logging;
    if ($opt_logging) {
        $logging = $opt_logging;
    } else {
        $logging = "none";
    }
    return $logging;
}

sub get_logstamp
{
    my $logstamp;
    if ($opt_logstamp) {
        $logstamp = $opt_logstamp;
    } else {
        $logstamp = "datetime";
    }
    return $logstamp;
}

sub get_root_handle
{
    my $root_handle;
    if ($opt_root_handle) {
        $root_handle = $opt_root_handle;
    } else {
        $root_handle = 1048576;
    }
    return $root_handle;
}

sub get_fsid
{
    my $fsid;
    if ($opt_fsid) {
        $fsid = $opt_fsid;
    } else {
        # compute a psuedo-random 32 bit file system ID
        $fsid = int((rand() * 2147483647));
    }
    return $fsid;
}

sub get_last_handle
{
    my $last_handle;
    if ($opt_last_handle) {
        $last_handle = $opt_last_handle;
    } else {
        $last_handle = 4294967297;
    }
    return $last_handle;
}

sub get_default_num_dfiles
{
    my $default_num_dfiles;
    if ($opt_default_num_dfiles) {
        $default_num_dfiles = $opt_default_num_dfiles;
    } else {
        $default_num_dfiles = -1;
    }
    return $default_num_dfiles;
}

sub get_first_handle
{
    my $first_handle;
    if ($opt_first_handle) {
        $first_handle = $opt_first_handle;
    } else {
        $first_handle = 4;
    }
    return $first_handle;
}

sub get_server_job_timeout
{
    my $server_job_timeout;
    if ($opt_server_job_timeout) {
        $server_job_timeout = $opt_server_job_timeout;
    } else {
        $server_job_timeout = 30;
    }
    return $server_job_timeout;
}

sub get_client_job_timeout
{
    my $client_job_timeout;
    if ($opt_client_job_timeout) {
        $client_job_timeout = $opt_client_job_timeout;
    } else {
        $client_job_timeout = 300;
    }
    return $client_job_timeout;
}

sub get_logfile
{
    my $logfile;
    if ($opt_logfile) {
        $logfile = $opt_logfile;
    } else {
        if (!$opt_quiet) {
            print $OUT "Choose a file for each server to write log messages to.\n";
        }
        $logfile = prompt_word("Enter log file location [Default is /tmp/pvfs2-server.log]: ","/tmp/pvfs2-server.log");
    }
    return $logfile;
}

sub get_storage
{
    my $storage;
    if ($opt_storage) {
        $storage = $opt_storage;
    } else {
        if (!$opt_quiet) {
            print $OUT "Choose a directory for each server to store data in.\n";
        }
        $storage = prompt_word("Enter directory name: [Default is /pvfs2-storage-space]: ","/pvfs2-storage-space");
    }
    return $storage;
}



# get host port
sub tcp_get_port
{
    my $port;
    if ($opt_tcpport) {
        $port = $opt_tcpport;
    } elsif ($opt_port) {
        $port = $opt_port;
        $opt_port = '';
    } elsif (!$opt_iospec) {
        if (!$opt_quiet) { 
            print $OUT "Choose a TCP/IP port for the servers to listen on.  Note that this\n";
            print $OUT "script assumes that all servers will use the same port number.\n";
        }
        $port = prompt_num("Enter port number [Default is 3334]: ","3334");
    }
    return  $port;
}
sub gm_get_port
{
    my $port;
    if ($opt_gmport) {
        $port = $opt_gmport;
    } elsif ($opt_port) {
        $port = $opt_port;
        $opt_port = '';
    } elsif (!$opt_iospec) {
        if (!$opt_quiet) { 
            print $OUT "Choose a GM port (in the range of 0 to 7) for the servers to listen on. \n";
            print $OUT "This script assumes that all servers will use the same port number.\n";
        }
        $port = prompt_num("Enter port number [Default is 6]: ","6");
    }
    # every myrinet card i've seen has 8 ports.  If myricom makes a card
    # with more than that, we'll have to adapt
    ($port < 8) or die "GM ports must be in the range 0 to 7";

    return  $port;
}
sub ib_get_port
{
    my $port;
    if ($opt_ibport) {
        $port = $opt_ibport;
    } elsif ($opt_port) {
        $port = $opt_port;
        $opt_port = '';
    } elsif(!$opt_iospec) {
        if (!$opt_quiet) { 
            print $OUT "Choose a TCP/IP port for the servers to listen on for IB communications.  Note that this\n";
            print $OUT "script assumes that all servers will use the same port number.\n";
        }
        $port = prompt_num("Enter port number [Default is 3335]: ","3335");
    }
    return  $port;
}

sub get_ionames
{
    my $portmap = shift;
    my $storage = shift;
    my $logfile = shift;
    my $ioline = '';
    if ($opt_ioservers) {
        $ioline = $opt_ioservers;
    } else {
        print $OUT "Next you must list the hostnames of " .
                   "the machines that will act as\n";
        print $OUT "I/O servers.  Acceptable syntax is " .
                   "\"node1, node2, ...\" or \"node{#-#,#,#}\".\n";
        $ioline = prompt_word(
            "Enter hostnames [Default is localhost]: ",
            "localhost");
    }

    my @io_hosts = parse_hostlist($ioline);
    foreach my $io_host (@io_hosts)
    {
        if(exists $all_endpoints{$io_host})
        {
            $all_endpoints{$io_host}->{TYPE} |= $IO_ENDPOINT;
        }
        else
        {

            $all_endpoints{$io_host} = {
                ALIAS => $io_host, 
                TYPE => $IO_ENDPOINT,
                HOSTNAME => $io_host, 
                PORTMAP => $portmap, 
                STORAGE => $storage, 
                LOGFILE => $logfile};
        }
    }
}

sub get_metanames
{
    my $portmap = shift;
    my $storage = shift;
    my $logfile = shift;
    my $metaline = '';
    if ($opt_metaservers) {
        $metaline = $opt_metaservers;
    } else {
        print $OUT "Now list the hostnames of the machines that will act as " .
                   "Metadata\nservers.  This list may or may not overlap " .
                   "with the I/O server list.\n";
        $metaline = prompt_word(
            "Enter hostnames [Default is localhost]: ",
            "localhost");
    }
    my @meta_hosts = parse_hostlist($metaline);
    foreach my $meta_host (@meta_hosts)
    {
        if(exists $all_endpoints{$meta_host})
        {
            $all_endpoints{$meta_host}->{TYPE} |= $META_ENDPOINT;
        }
        else
        {
            $all_endpoints{$meta_host} = {
                ALIAS => $meta_host, 
                TYPE => $META_ENDPOINT,
                HOSTNAME => $meta_host, 
                PORTMAP => $portmap, 
                STORAGE => $storage, 
                LOGFILE => $logfile};
        }
    }
}

sub get_specs
{
    my $type = shift;
    my $line = shift;

    # we need to split the spec string into individual components that
    # specify different endpoints.  The endpoints are separated by commas,
    # but so are the protocols inside square brackets [] and the port ranges
    # inside curly brackets {}, so its a bit tricky to get what we actually
    # want.  Hence the long regex.  
    #
    # The (?: ...) groups a pattern without
    # putting the matched text in the result variables ($1, $2, ..).  The
    # regex tries to match everything that's not a comma, skipping over
    # the [] and {} bits that may contain commas.  The global flag at the
    # end allows it to keep matching until no more matches can be made.
    #
    my @endpoints =
        $line =~ /(?:,?[ ]*((?:\[[^\]]+\])|[^\[{,]+(?:{[^}]+})?[^\[{,]*))/g;

    foreach my $ep (@endpoints)
    {
        my $stor = undef;
        my $logf = undef;
        my $proto = undef;
        my $hostname = undef;
        my $portn = undef;
        if($ep =~ /^\[/)
        {
            # the string must have multiple protocols specified for the same
            # endpoint.  We want to match on [...]:storage:logfile
            # and place the stuff between the [] in $1, and optionally
            # place the matched storage path and logfile in $2 and $3 
            #
            $ep =~ /\[([^\]]+)\](?::([^:]+))?(?::([^:]+))?/;

            $stor = $2;
            $logf = $3;

            if(!defined($1))
            {
                print STDERR "Invalid spec option format: Missing endpoints\n" .
                             "between brackets.\n\n";
                exit(1);
            }

            my @multiproto = split(/,/, $1);
            my $alias_suffix = "";
            my %port = ();
            foreach my $prothost (@multiproto)
            {
                $prothost =~ /([a-z]+):\/\/([^:]+):([0-9]+)/;
                $proto = $1;
                my $hn = $2;
                $portn = $3;

                if(!defined($hostname))
                {
                    $hostname = $hn;
                }
                elsif($hostname ne $hn)
                {
                    print STDERR "Invalid spec option format: multiple" .  
                                 "protocols specified between [] must\n" .
                                 "specify the same host ($hostname != $hn)\n\n";
                    exit(1);
                }

                $port{$proto} = $portn;
                $alias_suffix .= "_" . $proto . $portn;
            }

            my $alias = $hostname . $alias_suffix;
            $all_endpoints{$alias} = {
                ALIAS => $alias, 
                TYPE => $type,
                HOSTNAME => $hostname, 
                PORTMAP => \%port, 
                STORAGE => $stor, 
                LOGFILE => $logf};
        }
        else
        {
            # I'll probably forget what this does in a day or two, hence the
            # comment.  The (?: ...) pattern allows for grouping without
            # putting the matched string in the result variables ($1, $2, ..).
            # The first (?: ...) pattern optionally looks for the protocol.
            # There's a subgroup that matches the actual protocol name and puts
            # it in $1 if it exists.
            # The grouped pattern following that looks for the hostname, and
            # the one after that looks for the port, putting the results
            # in $2 and $3 respectively.  The next two (?: ...) optionally
            # match the storage and logfile respectively, with subgroups that
            # put the actual strings into $4 and $5.
            # 
            $ep =~ /(?:([a-z]+):\/\/)?([^:]+):([^:]+)(?::([^:]+))?(?::([^:]+))?/;
            if(!defined($1))
            {
                # assume tcp for now
                $proto = "tcp";
            }
            else
            {
                $proto = $1;
            }

            if(!defined($2))
            {
                print STDERR "Invalid spec option format: endpoint: $ep\n" .
                "requires a port number\n";
                exit(1);
            }
            $hostname = $2;

            if(!defined($3))
            {
                print STDERR "Invalid spec option format: endpoint\n" .
                "requires a port number\n";
                exit(1);
            }
            my $ranges = $3;

            $stor = $4;
            $logf = $5;

            my ($s, $e);

            foreach my $r (split(/,/, $ranges))
            {
                if($r !~ /-/)
                {
                    $s = $r;
                    $e = $r;
                }
                else
                {
                    if($r =~ /([0-9]+)-([0-9]+)/)
                    {
                        $s = $1;
                        $e = $2;
                    }
                }

                for(my $i = $s; $i <= $e; ++$i)
                {
                    my $portmap = {$proto => $i};
                    my $alias = $hostname . "_" . $proto . $i;

                    if(exists $all_endpoints{$alias})
                    {
                        $all_endpoints{$alias}->{TYPE} |= $type;
                    }
                    else
                    {
                        $all_endpoints{$alias} = {
                            ALIAS => $alias, 
                            TYPE => $type,
                            HOSTNAME => $hostname,
                            PORTMAP => $portmap, 
                            STORAGE => $stor, 
                            LOGFILE => $logf};
                    }
                }
            }
        }
    }
}

sub get_aliases
{
    my $type = shift;
    my @aliases = ();
    foreach my $ep (values %all_endpoints)
    {
        if($ep->{TYPE} & $type)
        {
            push @aliases, $ep->{ALIAS};
        }
    }
    return @aliases;
}

sub get_bmi_endpoint
{
    my $alias = shift;
    my $endpoint = $all_endpoints{$alias};
    my @bmi_list = ();

    foreach my $proto (keys %{$endpoint->{PORTMAP}})
    {
        my $tmpstr = $proto . "://" . $endpoint->{HOSTNAME} . ":" . 
        $endpoint->{PORTMAP}->{$proto};
        push(@bmi_list, $tmpstr);
    }
    return join(",", @bmi_list);
}

sub get_all_protocols
{
    my %protos = ();
    foreach my $ep (keys %all_endpoints)
    {
        my $portmap = $all_endpoints{$ep}->{PORTMAP};

        foreach my $prot (keys %{$all_endpoints{$ep}->{PORTMAP}})
        {
            if(!exists $protos{$prot})
            {
                $protos{$prot} = 1;
            }
        }
    }
    return keys %protos;
}

sub needs_default_value
{
    my $paramname = shift;

    foreach my $ep (keys %all_endpoints)
    {
        if(!defined($all_endpoints{$ep}->{$paramname}))
        {
            return 1;
        }
    }
    return 0;
}

sub set_default_value
{
    my $paramname = shift;
    my $val = shift;

    foreach my $ep (keys %all_endpoints)
    {
        if(!defined($all_endpoints{$ep}->{$paramname}))
        {
            $all_endpoints{$ep}->{$paramname} = $val . "-" . $ep;
        }
    }
}

# ---------------------
# entry point of script
# ---------------------

my $using_stdout = 0;
my $show_help = '';
my $show_specusage = '';

$opt_quiet = 0;
GetOptions('protocol=s'    => \$opt_protocol,
    'port=i'        => \$opt_port,
    'tcpport=i'     => \$opt_tcpport,
    'gmport=i'      => \$opt_gmport,
    'ibport=i'      => \$opt_ibport,
    'ioservers=s'   => \$opt_ioservers,
    'metaservers=s' => \$opt_metaservers,
    'logfile=s'     => \$opt_logfile,
    'logging=s'     => \$opt_logging,
    'logstamp=s'    => \$opt_logstamp,
    'first-handle=i' => \$opt_first_handle,
    'last-handle=i' => \$opt_last_handle,
    'default-num-dfiles=i' => \$opt_default_num_dfiles,
    'root-handle=i' => \$opt_root_handle,
    'fsid=i'        => \$opt_fsid,
    'trusted=i'        => \$opt_security,
    'server-job-timeout=i' => \$opt_server_job_timeout,
    'client-job-timeout=i' => \$opt_client_job_timeout,
    'storage=s'     => \$opt_storage,
    'help'          => \$show_help,
    'quiet!'        => \$opt_quiet,
    'trovesync!'    => \$opt_trovesync,
    'iospec=s'     => \$opt_iospec,
    'metaspec=s'   => \$opt_metaspec, 
    'spec-usage!'  => \$show_specusage,
    '-'           => \$using_stdout)
    or die "Could not parse arguments.  See -h for help.\n";

if(!((($opt_protocol && $opt_port && $opt_ioservers && $opt_metaservers) ||
      ($opt_iospec && $opt_metaspec)) &&
     $opt_quiet && $opt_logfile && $opt_storage))
{
    $term = new Term::ReadLine 'pvfs2-genconfig';
    if(!defined($term))
    {
        print STDERR "Failed to open ReadLine terminal\n";
        exit(1);
    }
}

if($term && $term->OUT)
{
    $OUT = $term->OUT;
}
else
{
    $OUT = \*STDOUT;
}

if($show_help) {
    usage();
    exit;
}

if($show_specusage) {
    specusage();
    exit;
}

my $output_target = undef;
if ($using_stdout) {
    $output_target = \*STDOUT;
}
elsif (@ARGV != 2)
{
    die "Bad arguments.  See -h for help.\n";
}
else
{
    unless (open(FILEOUT, ">", $ARGV[0]))
    {
        die "Can't open specified file $ARGV[0]: $!\n";
    }
    $output_target = \*FILEOUT;
}

die "-port not allowed with -iospec or -metaspec."
if($opt_port ne '' && defined($opt_iospec) && defined($opt_metaspec));
die "-iospec requires -metaspec." 
if(defined($opt_iospec) && !defined($opt_metaspec));
die "-metaspec requires -iospec." 
if(defined($opt_metaspec) && !defined($opt_iospec));

# only open the terminal for reading input if options specified
# on the command line are not enough to gather necessary information

print_welcome();

my $default_storage = undef;
my $default_logfile = undef;
my $bmi_module = undef;

if($opt_metaspec)
{
    get_specs($IO_ENDPOINT, $opt_iospec);
    get_specs($META_ENDPOINT, $opt_metaspec);

    $bmi_module = join(',', (map { "bmi_" . $_ } get_all_protocols()));
}
else
{
    my $portmap = get_protocol();
    $bmi_module = join(',', map("bmi_" . $_, keys (%{$portmap})));

    $default_storage = get_storage();
    $default_logfile = get_logfile();

    get_ionames($portmap, $default_storage, $default_logfile);
    get_metanames($portmap, $default_storage, $default_logfile);
}

# find out if any of the storage or logfile entries in the endpoints
# are undefined, in which case we need to find the default

if(needs_default_value(STORAGE))
{
    if(!defined($default_storage))
    {
        $default_storage = get_storage();
    }
    set_default_value(STORAGE, $default_storage);
    set_default_value(STORAGE, $default_storage);
}

if(needs_default_value(LOGFILE))
{
    if(!defined($default_logfile))
    {
        $default_logfile = get_logfile();
    }

    set_default_value(LOGFILE, $default_logfile);
    set_default_value(LOGFILE, $default_logfile);
}

my $count = scalar(keys %all_endpoints);
my $io_count = scalar(get_aliases($IO_ENDPOINT));
my $meta_count = scalar(get_aliases($META_ENDPOINT));

if (!$opt_quiet) {
    print $OUT <<"FINI"
Configured a total of $count servers:
$io_count of them are I/O servers.
$meta_count of them are Metadata servers.
FINI
    ;;

    if ($opt_security == 1)
    {
        print $OUT "Configured trusted connection settings\n";
        print $OUT "Trusted port list : ", $opt_trusted_port, "\n";
        print $OUT "Trusted network,netmask : ", 
        $opt_trusted_network, ",", $opt_trusted_netmask, "\n";
    }
}

if (!$opt_quiet) {

    my $verify_svr_flag = prompt_word("Would you like to verify server list (y/n) [Default is n]? ","n");

    if($verify_svr_flag eq "y" or $verify_svr_flag eq "yes")
    {
        print $OUT "****** I/O servers:\n";
        foreach my $ios (get_aliases($IO_ENDPOINT))
        {
            print "$ios\n";
        }
        print $OUT "\n****** Metadata servers:\n";
        foreach my $mos (get_aliases($META_ENDPOINT))
        {
            print $OUT "$mos\n";
        }
        my $ok_flag = prompt_word("Does this look ok (y/n) [Default is y]? ","y");
        if(!($ok_flag eq "y" or $ok_flag eq "yes"))
        {
            die "Aborting...\n";
        }
    }
}


my $logging = get_logging();
my $logstamp = get_logstamp();
my $first_handle = get_first_handle();
my $last_handle = get_last_handle();
my $default_num_dfiles = get_default_num_dfiles();
my $root_handle = get_root_handle();
my $fsid = get_fsid();
my $server_job_timeout = get_server_job_timeout();
my $client_job_timeout = get_client_job_timeout();

# ----------------------------------------------------------
# now that we have all the info, emit the configuration data
# ----------------------------------------------------------

# NOTE: we assume that server_aliases and server_addrs
# arrays are properly populated from above

if (!$opt_quiet) {
    print $OUT "Writing fs config file... ";
    if ($using_stdout == 1)
    {
        print $OUT "\n";
    }
}

emit_defaults($output_target, $num_unexp_reqs,
    $bmi_module, $default_logfile, $logging, 
    $logstamp, $server_job_timeout, $client_job_timeout);
if ($opt_security == 1) 
{
    emit_security($output_target, $opt_trusted_port, 
        $opt_trusted_network, $opt_trusted_netmask);
}
emit_aliases($output_target);
emit_filesystem($output_target, "pvfs2-fs", $fsid, $root_handle, 
    $last_handle, $first_handle, $count, $default_num_dfiles);

# close fs.conf
if ($using_stdout == 0)
{
    close($output_target);
}

if (!$opt_quiet) {
    print $OUT "Done.\n";

    print $OUT "Writing ", $count, " server config file(s)... ";
    if ($using_stdout == 1)
    {
        print $OUT "\n";
    }
}

foreach my $ep (sort keys %all_endpoints)
{
    my($filename);

    # and open server.conf files
    if ($using_stdout == 0)
    {
        $filename = "$ARGV[1]-" . $ep;
        unless (open(SERVEROUT, ">", $filename))
        {
            die "Can't open specified file $filename: $!\n";
        }
        $output_target = \*SERVEROUT;
    }

    emit_server_conf(
        $output_target, $ep, 
        $all_endpoints{$ep}->{STORAGE}, 
        $all_endpoints{$ep}->{LOGFILE});

    if ($using_stdout == 0)
    {
        close(SERVEROUT);
    }
}

if (!$opt_quiet) {
    print $OUT "Done.\n";
}

# Local variables:
#  c-indent-level: 4
#  c-basic-offset: 4
# End:
#  
# vim: ts=8 sts=4 sw=4 expandtab


