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

# turn on strictness
use strict 'vars';

# ugly global variables for option parsing
my $opt_protocol = '';
my $opt_port = '';
my $opt_board = '';
my $opt_tcpport = '';
my $opt_tcpbindspecific = '0';
my $opt_gmport = '';
my $opt_mxboard = '';
my $opt_mxendpoint = '';
my $opt_ibport = '';
my $opt_portal = '';
my $opt_ioservers = '';
my $opt_metaservers = '';
my $opt_logfile = '';
my $opt_storage = '';
my $opt_metadata = '';
my $opt_trovesync = '1';
my $opt_trovemethod = '';
my $opt_quiet = '';
my $opt_logging = '';
my $opt_tracing = '';
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_fsname = '';
my $opt_default_num_dfiles = '';
my $opt_default_flow_buffer_size = '';
my $opt_default_flow_buffer_count = '';
my $opt_dist_name = '';
my $opt_dist_params = '';

# reserved handle ranges
my $opt_res_io_ranges = undef;
my $opt_res_meta_ranges = undef;

my $opt_trusted = '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 $default_storage = undef;
my $default_meta_storage = undef;
my $default_logfile = undef;

my $bmi_module = undef;

my $opt_gen_key = 0;

# key-based security options 
my $opt_security_key = undef;
my $opt_keystore = undef;
my $opt_server_key = undef;  # also used with cert-based security
my $opt_security_timeout = undef;

# cert-based security options
my $opt_security_cert = undef;
my $opt_ca_file = undef;
my $opt_user_cert_dn = undef;
my $opt_user_cert_exp = undef;

# LDAP options for cert-based security
my $opt_ldap_hosts = undef;
my $opt_ldap_bind_dn = undef;
my $opt_ldap_bind_password = undef;
my $opt_ldap_search_mode = undef;
my $opt_ldap_search_root = undef;
my $opt_ldap_search_class = undef;
my $opt_ldap_search_attr = undef;
my $opt_ldap_search_scope = undef;
my $opt_ldap_uid_attr = undef;
my $opt_ldap_gid_attr = undef;
my $opt_ldap_search_timeout = undef;


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

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

# script path (just dir)
my $script_dir = $FindBin::Bin;
# OrangeFS root installation path
# E.g. if script is in /opt/orangefs/bin
# then the root is /opt/orangefs
my $fs_path = undef;
if ($script_dir)
{
    my $slash = rindex($script_dir, "/");
    if ($slash != -1)
    {
        $fs_path = substr($script_dir, 0, $slash);        
    }
}

$fs_path = "/opt/orangefs"
    if (!defined(fs_path) || fs_path eq "");

#$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 prompt_or_print
{
    my($opt,$defmsg,$msg,$prompt,$default) = @_;

    if (defined($opt))
    {
        print $OUT "$defmsg: $opt\n";
        return $opt;
    }

    print $OUT "$msg\n";

    my $val = prompt_word($prompt, $default);
    return undef
        if ($val eq "");

    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_ws (@components)
    {
        my $comp;

        # Trim leading and trailing whitespace
        $comp = $comp_ws;
        $comp =~ s/^\s+//;
        $comp =~ s/\s+$//;

        # 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;
            my $suffix = $3;

            # split the ranges string on the commas
            foreach my $r (split(/,/, $ranges))
            {
                if($r !~ /-/)
                {
                    # only one number, just push it on
                }
                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$suffix";
                    }
                }
            }
        }
        else {
            push @hosts, $comp;
        }
    }
    return @hosts;
}

# emit trusted network info
sub emit_trusted
{
    my ($target, $portlist, $network, $netmask) = @_;

    print $target "\t\tTrustedPorts $portlist\n";
    print $target "\t\tTrustedNetwork $network $netmask\n";
}

# emit security options
sub emit_security
{
    my $target = shift;
    my $alias = shift;
    my $seckeyflag = 0;
    my $seccertflag = 0;

    # if securitykey or keystore used, use key-based options    
    $seckeyflag = defined($opt_security_key) || defined($opt_keystore);
    
    # if securitycert or cafile used, use cert-based options
    $seccertflag = defined($opt_security_cert) || defined($opt_ca_file);

    if ($seckeyflag || $seccertflag || $opt_trusted == 1)
    {
        print $target "\n\t<Security>\n";
        if ($seckeyflag || $seccertflag)
        {
           print $target "\t\tTurnOffTimeouts no\n";
        }
        else
        {
           print $target "\t\tTurnOffTimeouts yes\n";
        }
    }
    else
    {
        print $target "\n\t<Security>\n";
        print $target "\t\tTurnOffTimeouts yes\n";
        print $target "\t</Security>\n";
    }

    if ($opt_trusted == 1)
    {
        emit_trusted($target, $opt_trusted_port, $opt_trusted_network,
                     $opt_trusted_netmask);
        print $target "\n"
            if ($seckeyflag || $seccertflag);
    }

    if ($seckeyflag || $seccertflag)
    {
        my $serverkey = get_server_key($alias);
        die "\n*** error: no server key specified\n*** use --serverkey option"
            if (!defined($serverkey));

        print $target "\t\tServerKey $serverkey\n";

        if (defined($opt_security_timeout))
        {
            print $target "\t\tSecurityTimeout $opt_security_timeout\n";
        }
    }

    if ($seckeyflag)
    {
        my $keystore = get_keystore($alias);
        die "\n*** error: no keystore specified\n*** use --keystore option"
            if (!defined($keystore));

        print $target "\t\tKeystore $keystore\n";
    }
    elsif ($seccertflag)
    {
        my $cafile = get_ca_file($alias);
        die "\n*** error: no CA file specified\n*** use --cafile option"
            if (!defined($cafile));

        print $target "\t\tCAFile $cafile\n";

        my $user_cert_dn = get_user_cert_dn($alias);
        print $target "\t\tUserCertDN $user_cert_dn\n"
            if (defined($user_cert_dn));

        print $target "\t\tUserCertExp $opt_user_cert_exp\n"
            if (defined($opt_user_cert_exp));

        if (defined($opt_ldap_hosts) ||
            defined($opt_ldap_bind_dn) ||
            defined($opt_ldap_bind_password) ||
            defined($opt_ldap_search_mode) ||
            defined($opt_ldap_search_root) ||
            defined($opt_ldap_search_class) ||
            defined($opt_ldap_search_attr) ||
            defined($opt_ldap_search_scope) ||
            defined($opt_ldap_uid_attr) ||
            defined($opt_ldap_gid_attr) ||
            defined($opt_ldap_search_timeout))
        {
            print $target "\n\t\t<LDAP>\n";

            print $target "\t\t\tHosts $opt_ldap_hosts\n"
                if (defined($opt_ldap_hosts));
            print $target "\t\t\tBindDN $opt_ldap_bind_dn\n"
                if (defined($opt_ldap_bind_dn));
            print $target "\t\t\tBindPassword $opt_ldap_bind_password\n"
                if (defined($opt_ldap_bind_password));
            print $target "\t\t\tSearchMode $opt_ldap_search_mode\n"
                if (defined($opt_ldap_search_mode));
            print $target "\t\t\tSearchRoot $opt_ldap_search_root\n"
                if (defined($opt_ldap_search_root));
            print $target "\t\t\tSearchClass $opt_ldap_search_class\n"
                if (defined($opt_ldap_search_class));
            print $target "\t\t\tSearchAttr $opt_ldap_search_attr\n"
                if (defined($opt_ldap_search_attr));
            print $target "\t\t\tSearchScope $opt_ldap_search_scope\n"
                if (defined($opt_ldap_search_scope));
            print $target "\t\t\tUIDAttr $opt_ldap_uid_attr\n"
                if (defined($opt_ldap_uid_attr));
            print $target "\t\t\tGIDAttr $opt_ldap_gid_attr\n"
                if (defined($opt_ldap_gid_attr));
            print $target "\t\t\tSearchTimeout $opt_ldap_search_timeout\n"
                if (defined($opt_ldap_search_timeout));

            print $target "\t\t</LDAP>\n";
       }
    }

    if ($seckeyflag || $seccertflag || $opt_trusted == 1)
    {
        print $target "\t</Security>\n";
    }

}

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

    print $target "<Defaults>\n";
    print $target "\tUnexpectedRequests $num_unexp\n";
    print $target "\tEventLogging $logging\n";
    print $target "\tEnableTracing $tracing\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 "\tPrecreateBatchSize 0,1024,1024,1024,32,1024,0\n";
    print $target "\tPrecreateLowThreshold 0,256,256,256,16,256,0\n";

    if(defined($default_storage))
    {
        print $target "\n\tDataStorageSpace " . $default_storage . "\n";
    }

    if(defined($default_meta_storage))
    {
        print $target "\tMetadataStorageSpace " . $default_meta_storage . "\n\n";
    }

    if(defined($default_logfile))
    {
        print $target "\tLogFile " . $default_logfile . "\n";
    }

    if($opt_tcpbindspecific)
    {
        print $target "\tTCPBindSpecific yes\n";
    }

    # if --metaspec is used, security options are done per-server
    # otherwise do them here
    if (!defined($opt_metaspec))
    {
        emit_security($target);
    }

    print $target "</Defaults>\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_distribution
{
    my ($target, $dist_name, $dist_params) = @_;

    if ($dist_name)
    {
        # create distribution section with parameters
        print $target "\t<Distribution>\n";
        print $target "\t\tName $dist_name\n";

        # parse parameters
        if ($dist_params)
        {
            # each param name/value pair separated by comma
            my @params = split /,/, $dist_params;

            foreach (@params)
            {
                # name/value separated by colon
                # (note: colons may appear in param value)
                my $ind = index $_, ":";
                my $pname = substr $_, 0, $ind;
                my $pvalue = substr $_, $ind+1;

                # output parameter
                print $target "\t\tParam $pname\n";
                print $target "\t\tValue $pvalue\n";
            }
        }

        print $target "\t</Distribution>\n";
    }
}

sub emit_filesystem
{
    my ($target, $name, $fs_id, 
        $root_handle, $last_handle, $first_handle, 
        $curr_ranges, $res_io_ranges, $res_meta_ranges,
        $default_num_dfiles, $default_flow_buffer_size,
        $default_flow_buffer_count) = @_;

    # divide handle range space equally among servers ((2^63)-1 for now)
    my($total_num_handles_available, $start, $end, $i, $step, $stuffing);
    $total_num_handles_available = $last_handle->copy();
    $total_num_handles_available->bsub($first_handle);
    $total_num_handles_available->binc();

    # current assigned ranges + reserved ranges
    my $total_ranges = $curr_ranges + $res_io_ranges + $res_meta_ranges;        

    # since meta and data handle ranges must be split, increment
    # num nodes for calculation purposes below
    $step = $total_num_handles_available->copy();
    $step->bdiv($total_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";
    }

    # Rules for default stuffing setting: only enable it if every I/O server
    # is also a metadata server, otherwise we would tend to unbalance by
    # always stuffing on the subset that does metadata.  User can override
    # if desired.
    $stuffing = "yes";
    foreach my $alias (keys %all_endpoints)
    {
        if($all_endpoints{$alias}->{TYPE} & $IO_ENDPOINT)
        {
            if(!($all_endpoints{$alias}->{TYPE} & $META_ENDPOINT))
            {
                $stuffing = "no";
            }
        }
    }
    print $target "\tFileStuffing $stuffing\n";

    my $meta_count = scalar(get_aliases($META_ENDPOINT));
    print $target "\tDistrDirServersInitial 1\n";
#    print $target "\tDistrDirServersMax $meta_count\n";
    print $target "\tDistrDirServersMax 1\n";
    print $target "\tDistrDirSplitSize 100\n";

    print $target "\t<MetaHandleRanges>\n";
    $start = $end = $first_handle->copy();
    $start->bdec();
    $end->bdec();

    my @meta_aliases = get_aliases($META_ENDPOINT);
    @meta_aliases = sort @meta_aliases;    
    my $ranges = @meta_aliases + $res_meta_ranges;
    for ($i = 0; $i < $ranges; $i++)
    {
        $start = $end->copy();
        $start->binc();
        $end->badd($step);
        if ($i < @meta_aliases)
        {
            print $target "\t\tRange $meta_aliases[$i] $start-$end\n";
        }
        else
        {
            print $target "#\t\tRange (future) $start-$end   (reserved for future use)\n";
        }
    }

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

    my @io_aliases = get_aliases($IO_ENDPOINT);
    @io_aliases = sort @io_aliases;
    $ranges = @io_aliases + $res_io_ranges;
    for ($i = 0; $i < $ranges; $i++)
    {
        $start = $end->copy();
        $start->binc();
        $end->badd($step);
        if ($i < @io_aliases)
        {
            print $target "\t\tRange $io_aliases[$i] $start-$end\n";
        }
        else
        {
            print $target "#\t\tRange (future) $start-$end   (reserved for future use)\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";
    }

    if($opt_trovemethod ne "")
    {
        print $target "\t\tTroveMethod $opt_trovemethod\n";
    }
    else
    {
        print $target "\t\tTroveMethod alt-aio\n";
    }

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

    if($opt_gen_key ne "0")
    {
        emit_fs_key($target);
    }

    if($default_flow_buffer_size > 0)
    {
        print $target "\tFlowBufferSizeBytes $default_flow_buffer_size\n";
    }
 
    if($default_flow_buffer_count > 0)
    {
        print $target "\tFlowBuffersPerFlow $default_flow_buffer_count\n";
    }

    emit_distribution($target, $opt_dist_name, $opt_dist_params);

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

sub emit_serveropts
{
    my $target = shift;

    foreach my $alias (sort keys %all_endpoints)
    {
        my $endpoint = $all_endpoints{$alias};
        print $target "\n<ServerOptions>\n";
        print $target "\tServer $alias\n";
        print $target "\tDataStorageSpace $endpoint->{STORAGE}\n";
        print $target "\tMetadataStorageSpace $endpoint->{METASTORAGE}\n";
        print $target "\tLogFile $endpoint->{LOGFILE}\n";

        emit_security($target, $alias);

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


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

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

sub emit_fs_key
{
    my ($target, @path, $openssl_cmd, $b64_rand);

    $target = $_[0];

    $openssl_cmd = undef;

    @path = split(/:/, $ENV{PATH});
    for my $p (@path)
    {
        if( -x "$p/openssl" )
        {
            $openssl_cmd = "$p/openssl";
        }
    }

    if(!defined($openssl_cmd))
    {
        print STDERR "\n\nFailed to find openssl executable in PATH\n\n";
        exit 1;
    }

    $b64_rand = `$openssl_cmd rand -base64 20 2> /dev/null`;
    chomp($b64_rand);
    print $target "\tSecretKey $b64_rand\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,mx://myhosta:0:3]:/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>

  The pvfs2-genconfig utility creates a configuration file for the
  OrangeFS (aka PVFS2) file system. The <fs.conf> argument is
  mandatory and specifies the name of the configuration file that will
  be written. This utility will create the fs.conf file.  

  EXAMPLE: 'pvfs2-genconfig /tmp/fs.conf' will generate a file 
           called /tmp/fs.conf.
  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 with --quiet
  and one of the two [] grouped options below:

     [
       --protocol    <PROTO>[,<PROTO>..] protocol(s) to use (tcp,gm,mx,ib,portals)
       --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:

     --fspath      <STRING>            root path of OrangeFS installation
     --tcpport     <NUM>               TCP port to use (default: 3334)
     --tcpbindspecific                 Bind TCP only to specific interfaces
     --gmport      <NUM>               GM port to use (default: 6)
     --mxboard     <NUM>               MX board to use (default is 0)
     --mxendpoint  <NUM>               MX endpoint to use (default is 3)
     --ibport      <NUM>               IB port to use (default is 3335)
     --portal      <NUM>               Portals index for
                                       listening server (default is 5)
     --logging     <STRING>            debugging mask for log messages
     --tracing                         Enable event tracing in the server
     --logstamp    <STRING>            timestamp type for log messages 
                                       ('none','usec', or 'datetime' are valid)
     --storage     <STRING>            path to OrangeFS storage (data) directory.
     --metadata    <STRING>            path to OrangeFS metadata directory.
     --logfile     <STRING>            file to place server logging.
     --notrovesync                     sync metadata only upon request
     --server-job-timeout <NUM>        server job timeout value (seconds)
     --client-job-timeout <NUM>        server job timeout value (seconds)
     --trove-method <STRING>           specify a trove method
     --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
     --fsname      <STRING>            fs name
     --res-io-ranges <NUM>             number of I/O server handle ranges to reserve 
                                       for future use
     --res-meta-ranges <NUM>           number of metadata server handle ranges to reserve
                                       for future use                                  
     --default-num-dfiles <NUM>        number of datafiles to use per file
                                       (defaults to number of I/O servers)
     --flow-buffer-size <NUM>          set flowbuffersize in bytes            
     --flow-buffer-count <NUM>         set flow buffers per flow
     --dist-name <STRING>              datafile distribution type: 'simple_stripe' (default), 'basic_dist',
                                       'varstrip_dist' or 'twod_stripe'
     --dist-params <STRING>            datafile distribution parameters in form:
                                          <param_name>:<param_value>[,<param_name>:<param_value>...]
                                       see documentation for distribution information
     --trusted     <0|1>               indicate whether trusted connection options need to be emitted
     --genkey                          optionally generates a secret key for the 
                                       filesystem(s).
     --securitykey                     prompt for default key-based security values
     --keystore    <STRING>            keystore for each server. With --metaspec, magic value _ALIAS_
                                       will be replaced with each defined alias' value.
     --serverkey   <STRING>            private key for each server. With --metaspec, magic value _ALIAS_ 
                                       will be  replaced in the path with each defined alias' value.
     --securitytimeout <NUM>           timeout for security operations in seconds
     --securitycert                    prompt for default cert-based security values
     --cafile      <STRING>            path to certificate authority. With --metaspec, magic value
                                       _ALIAS_ will be replaced with each defined alias' value.
     --ldaphosts   <STRING>            comma-separated list of LDAP host URIs
                                       Example: "ldap://myldap.mysite.com,ldaps://myldapssl.mysite.com"
     --ldapbinddn  <STRING>            DN of user that will bind to LDAP for certificate mapping 
                                       operations. Example: "cn=admin,dc=mysite,dc=com"
     --ldapbindpassword <STRING>       Password of LDAP bind user. Literal password or "file:<path>"
     --ldapsearchmode <CN|DN>          LDAP search mode; by common name (CN) or distinguished name (DN)
     --ldapsearchroot <STRING>         DN of LDAP container to use for certificate mapping
     --ldapsearchclass <STRING>        Class name of LDAP objects to search for certificate mapping
     --ldapsearchattr <STRING>         LDAP attribute name to use for certificate mapping
     --ldapsearchscope <STRING>        LDAP search scope; 'onelevel' or 'subtree'
     --ldapuidattr <STRING>            LDAP attribute that contains UID values
     --ldapgidattr <STRING>            LDAP attribute that contains GID values
     --ldapsearchtimeout <NUM>         timeout for LDAP searches in seconds
     --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 OrangeFS Configuration Generator:

This interactive script will generate a configuration file suitable for use
with a new OrangeFS (aka PVFS2) file system.  Please see the OrangeFS 
documentation at http://www.orangefs.org/documentation 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 currently supported options are \"tcp\", \"gm\", \"mx\", \"ib\", and \"portals\".
(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_trusted == 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 "mx") {
            $port{'mx'} = mx_get_endpoint();
        } elsif ($_ eq "ib") {
            $port{'ib'} = ib_get_port();
        } elsif ($_ eq "portals") {
            $port{'portals'} = portals_get_portal();
        } else {
            die "Sorry.  At this time, only the tcp, gm, mx, ib, and portals 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_tracing
{
    my $tracing;
    if ($opt_tracing) {
        $tracing = "yes";
    } else {
        $tracing = "no";
    }
    return $tracing;
}

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_fsname
{
    my $fsname;
    if ($opt_fsname) {
        $fsname = $opt_fsname;
    } else {
        $fsname = "orangefs";
    }
    return $fsname;
}

sub get_last_handle
{
    my $last_handle;
    if ($opt_last_handle) {
        $last_handle = Math::BigInt->new($opt_last_handle);
    } else {
        $last_handle = Math::BigInt->new('0x7FFFFFFFFFFFFFFF');  # 2^63
    }
    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_default_flow_buffer_size
{
    my $default_flow_buffer_size;
    if($opt_default_flow_buffer_size ne '') {
        $default_flow_buffer_size = $opt_default_flow_buffer_size;
    } else {
        $default_flow_buffer_size = -1;
    }
    return $default_flow_buffer_size;
}

sub get_default_flow_buffer_count
{
    my $default_flow_buffer_count;
    if($opt_default_flow_buffer_count ne '') {
        $default_flow_buffer_count = $opt_default_flow_buffer_count;
    } else {
        $default_flow_buffer_count = -1;
    }
    return $default_flow_buffer_count;
}

sub get_first_handle
{
    my $first_handle;
    if ($opt_first_handle) {
        $first_handle = Math::BigInt->new($opt_first_handle);
    } else {
        $first_handle = Math::BigInt->new(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 = "/var/log/orangefs-server.log";
    if ($opt_logfile) {
        $logfile = $opt_logfile;
    } elsif (!$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 $logfile]: ","$logfile");
    }
    return $logfile;
}

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

sub get_meta_storage
{
    my $metastorage = "${fs_path}/storage/meta";
    if ($opt_metadata) {
        $metastorage = $opt_metadata;
    } elsif ($opt_storage) {
        # use same dir as data if specified
        $metastorage = $opt_storage;
    } elsif (!$opt_quiet) {
            print $OUT "Choose a directory for each server to store metadata in.\n";
            $metastorage = prompt_word("Enter directory name: [Default is ${fs_path}/storage/meta]: ","${fs_path}/storage/meta");
    }
    return $metastorage;
}

# get host port
sub tcp_get_port
{
    my $port = 3334;
    if ($opt_tcpport) {
        $port = $opt_tcpport;
    } 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 = 6;
    if ($opt_gmport) {
        $port = $opt_gmport;
    } 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 mx_get_endpoint
{
    my $port = 3;
    my $board = 0;

    if ($opt_mxboard) {
        $board = $opt_mxboard;
    } elsif (!$opt_iospec) {
        if (!$opt_quiet) { 
            print $OUT "Choose an MX board (in the range of 0 to 4) for the servers to listen on. \n";
            print $OUT "This script assumes that all servers will use the same board number.\n";
            $board = prompt_num("Enter board number [Default is 0]: ","0");
        }
    }
    # The number of boards is only limited by the number of PCI-X or PCI-Express
    # slots in the machine. This is reasonable maximum.
    ($board < 8) or die "MX board must be in the range 0 to 7";

    if ($opt_mxendpoint) {
        $port = $opt_mxendpoint;
    } elsif (!$opt_iospec) {
        if (!$opt_quiet) { 
            print $OUT "Choose an MX endpoint (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 3]: ","3");
        }
    }
    # The number of endpoints in MX is configurable. The default value is 4 
    # but may be changing to 8. It can be higher, but this is reasonable.
    ($port < 8) or die "MX endpoint must be in the range 0 to 7";

    $port = $board . ":" . $port;
    return  $port;
}

sub ib_get_port
{
    my $port = 3335;
    if ($opt_ibport) {
        $port = $opt_ibport;
    } 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 portals_get_portal
{
    my $port = 5;
    if ($opt_portal) {
        $port = $opt_portal;
    } elsif(!$opt_iospec) {
        if (!$opt_quiet) { 
            print $OUT "Choose a portal index for the servers to listen on for communications.  Note that this\n";
            print $OUT "script assumes that all servers will use the same portal index.\n";
            $port = prompt_num("Enter portal index [Default is 5]: ","5");
        }
    }
    return $port;
}

sub get_ionames
{
    my $portmap = shift;
    my $storage = shift;
    my $metastorage = shift;
    my $logfile = shift;
    my $ioline = '';
    if ($opt_ioservers) {
        $ioline = $opt_ioservers;
    } else {
        print $OUT "Next you must list the hostnames of " .
                   "the I/O servers.\nAcceptable 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,
                METASTORAGE => $metastorage, 
                LOGFILE => $logfile};
        }
    }
}

sub get_metanames
{
    my $portmap = shift;
    my $storage = shift;
    my $metastorage = shift;
    my $logfile = shift;
    my $metaline = '';
    my @meta_hosts;
    if ($opt_metaservers) {
        $metaline = $opt_metaservers;
        @meta_hosts = parse_hostlist($metaline);
    } 
    else 
    {
        print $OUT "Use same servers for metadata? (recommended)\n";
        $metaline = prompt_word(
            "Enter yes or no [Default is yes]: ",
            "yes");
        if($metaline =~ /^yes$/i)
        {
            foreach my $alias (keys %all_endpoints)
            {
                $all_endpoints{$alias}->{TYPE} |= $META_ENDPOINT;
            }
        }
        else
        {
            print $OUT "Now list the hostnames of the metadata servers.\n" .
                       "This list may or may not overlap " .
                       "with the I/O server list.\n";
            $metaline = prompt_word(
                "Enter hostnames [Default is localhost]: ",
                "localhost");
            @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,
                METASTORAGE => $metastorage, 
                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 $mstor = undef;
        my $logf = undef;
        my $proto = undef;
        my $hostname = undef;
        my $portn = undef;
        my $boardn = 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 meta path in $2 and $3,
            # logfile is in $4
            #
            $ep =~ /\[([^\]]+)\](?::([^:]+))?(?::([^:]+))?/;

            $stor = $2;
            $mstor = $3;
            $logf = $4;

            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;

                # special casing for mx
                if($proto =~ /mx/)
                {
                    $boardn = $3;
                    $portn = $4;
                }
                else
                {
                    $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} = "$boardn:$portn";
                $alias_suffix .= "_" . $proto . $boardn . "_" . $portn;
            }

            my $alias = $hostname . $alias_suffix;
            $all_endpoints{$alias} = {
                ALIAS => $alias, 
                TYPE => $type,
                HOSTNAME => $hostname, 
                PORTMAP => \%port, 
                STORAGE => $stor,
                METASTORAGE => $mstor, 
                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.
            # 
            my $count = 
                $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 $branges = $3;
            my $pranges = $4;
            $stor = $5;
            $mstor = $6;
            $logf = $7;

            if($proto !~ /mx/)
            {
                $logf = $stor;
                $stor = $mstor;
                $mstor = $pranges;
                $pranges = $branges;
                $branges = undef;
            }

            my ($s, $e);

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

                for(my $i = $s; $i <= $e; ++$i)
                {
                    if($proto !~ /mx/)
                    {
                        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, 
                                METASTORAGE => $mstor,
                                LOGFILE => $logf};
                        }
                    }
                    else
                    {
                        if(!defined($branges))
                        {
                            my $portmap = {$proto => "$branges:$i"};
                            my $alias = $hostname . "_" . $proto . $branges . "_" . $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,
                                    METASTORAGE => $mstor,
                                    LOGFILE => $logf};
                            }
                        }

                        my ($bs, $be);

                        foreach my $br (split(/,/, $branges))
                        {
                            if($br !~ /-/)
                            {
                                $bs = $br;
                                $be = $br;
                            }
                            else
                            {
                                if($br =~ /([0-9]+)-([0-9]+)/)
                                {
                                    $bs = $1;
                                    $be = $2;
                                }
                            }

                            for(my $bi = $bs; $bi <= $be; ++$bi)
                            {
                                my $portmap = {$proto => "$bi:$i"};
                                my $alias = $hostname . "_" . $proto . $bi . "_" . $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,
                                        METASTORAGE => $mstor,
                                        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 get_server_key
{
    my $keypath = $opt_server_key;
    my $alias = shift;

    # just return keypath if no alias
    return $keypath
        if (!defined($alias));

    if(defined($keypath))
    {
        $keypath =~ s/_ALIAS_/$alias/g;
        return $keypath;
    }
    return undef;
}

sub get_keystore
{
    my $keystore = $opt_keystore;
    my $alias = shift;

    # just return keystore if no alias
    return $keystore
        if (!defined($alias));

    if(defined($keystore))
    {
        $keystore =~ s/_ALIAS_/$alias/g;
        return $keystore;
    }
    return undef;
}

sub get_ca_file
{
    my $cafile = $opt_ca_file;
    my $alias = shift;

    # just return cafile if no alias
    return $cafile
        if (!defined($alias));

    if (defined($cafile))
    {
        $cafile =~ s/_ALIAS_/$alias/g;
        return $cafile;
    }
    return undef;
}

sub get_user_cert_dn
{
    my $user_cert_dn = $opt_user_cert_dn;
    my $alias = shift;

    # just return user cert dn if no alias
    return $user_cert_dn
        if (!defined($alias));

    if (defined($user_cert_dn))
    {
        $user_cert_dn =~ s/_ALIAS_/$alias/g;
        return $user_cert_dn;
    }
    return undef;
}

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;
        }
    }
}

# prompt for security info
sub get_security_info
{
    if (defined($opt_security_key) || defined($opt_keystore))
    {
        print $OUT "Using key-based security...\n\n";

        $opt_server_key = prompt_or_print($opt_server_key,
                                          "Using server key file",
                                          "Specify the server key file.",
                                          "Enter server key file location " .
                                          "[Default is ${fs_path}/etc/orangefs-serverkey.pem]: ",
                                          "${fs_path}/etc/orangefs-serverkey.pem");

        $opt_keystore = prompt_or_print($opt_keystore,
                                         "Using keystore file",
                                         "Specify the keystore file.",
                                         "Enter keystore file location " .
                                         "[Default is ${fs_path}/etc/orangefs-keystore]: ",
                                         "${fs_path}/etc/orangefs-keystore");
    }
    elsif (defined($opt_security_cert) || defined($opt_ca_file))
    {
        print $OUT "Using certificate-based security...\n\n";

        $opt_ca_file = prompt_or_print($opt_ca_file,
                                       "Using CA certificate file",
                                       "Specify the Certificate Authority (CA) certificate file.",
                                       "Enter CA certificate file location " .
                                       "[Default is ${fs_path}/etc/orangefs-ca-cert.pem]: ",
                                       "${fs_path}/etc/orangefs-ca-cert.pem");

        $opt_server_key = prompt_or_print($opt_server_key,
                                          "Using CA private key file",
                                          "Specify the CA private key file.",
                                          "Enter CA private key file location: " .
                                          "[Default is ${fs_path}/etc/orangefs-ca-cert-key.pem]: ",
                                          "${fs_path}/etc/orangefs-ca-cert-key.pem");

        $opt_user_cert_dn = prompt_or_print($opt_user_cert_dn,
                                            "Using user certificate DN",
                                            "Specify the user certificate root DN.",
                                            "Enter user certificate root DN [Default is \"C=US, O=OrangeFS\"]: ",
                                            "\"C=US, O=OrangeFS\"");

        # insert quotes if needed
        if (defined($opt_user_cert_dn) && substr($opt_user_cert_dn, 0, 1) ne "\"")
        {
            $opt_user_cert_dn = "\"$opt_user_cert_dn\"";
        }

        $opt_user_cert_exp = prompt_or_print($opt_user_cert_exp,
                                             "Using user certificate expiration (days)",
                                             "Specify the user certificate expiration.",
                                             "Enter the user certificate expiration in days [Default is 365]:",
                                             "365");

        $opt_ldap_hosts = prompt_or_print($opt_ldap_hosts,
                                          "Using LDAP host list",
                                          "Specify the LDAP host list (ldap[s]://host[:port],...).",
                                          "Enter LDAP host list [Default is ldap://localhost]: ",
                                          "ldap://localhost");
        
        $opt_ldap_bind_dn = prompt_or_print($opt_ldap_bind_dn,
                                            "Using LDAP bind DN",
                                            "Specify the LDAP bind user DN (DN format).",
                                            "Enter LDAP bind DN (Example: cn=admin,dc=acme,dc=com): ",
                                            "");

        $opt_ldap_bind_password = prompt_or_print($opt_ldap_bind_password,
                                                  "Using LDAP bind password",
                                                  "Specify the LDAP bind password (password or file:{path}).",
                                                  "Enter LDAP bind password: ",
                                                  "");

        $opt_ldap_search_mode = prompt_or_print($opt_ldap_search_mode,
                                                "Using LDAP search mode",
                                                "Specify the LDAP search mode (CN or DN).",
                                                "Enter LDAP search mode [Default is CN]: ",
                                                "CN");

        $opt_ldap_search_root = prompt_or_print($opt_ldap_search_root,
                                                "Using LDAP search root",
                                                "Specify the LDAP search root object (DN format).",
                                                "Enter LDAP search root (Example: ou=users,dc=acme,dc=com): ",
                                                "");

        $opt_ldap_search_class = prompt_or_print($opt_ldap_search_class,
                                                 "Using LDAP search class",
                                                 "Specify the LDAP search class.",
                                                 "Enter LDAP search class [Default is inetOrgPerson]: ",
                                                 "inetOrgPerson");

        $opt_ldap_search_attr = prompt_or_print($opt_ldap_search_attr,
                                                "Using LDAP search attribute",
                                                "Specify the LDAP search attribute.",
                                                "Enter LDAP search attribute [Default is cn]: ",
                                                "cn");

        $opt_ldap_search_scope = prompt_or_print($opt_ldap_search_scope,
                                                 "Using LDAP search scope",
                                                 "Specify the LDAP search scope (onelevel or subtree).",
                                                 "Enter LDAP search scope [Default is subtree]: ",
                                                 "subtree");

        $opt_ldap_uid_attr = prompt_or_print($opt_ldap_uid_attr,
                                             "Using LDAP UID attribute",
                                             "Specify the LDAP UID attribute.",
                                             "Enter LDAP UID attribute [Default is uidNumber]: ",
                                             "uidNumber");

        $opt_ldap_gid_attr = prompt_or_print($opt_ldap_gid_attr,
                                             "Using LDAP GID attribute",
                                             "Specify the LDAP GID attribute.",
                                             "Enter LDAP GID attribute [Default is gidNumber]: ",
                                             "gidNumber");
    }
}

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

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

$opt_quiet = 0;
GetOptions('protocol=s' => \$opt_protocol,
    'tcpport=i'     => \$opt_tcpport,
    'tcpbindspecific' => \$opt_tcpbindspecific,
    'gmport=i'      => \$opt_gmport,
    'mxboard=i'     => \$opt_mxboard,
    'mxendpoint=i'  => \$opt_mxendpoint,
    'ibport=i'      => \$opt_ibport,
    'portal=i'      => \$opt_portal,
    'ioservers=s'   => \$opt_ioservers,
    'metaservers=s' => \$opt_metaservers,
    'logfile=s'     => \$opt_logfile,
    'logging=s'     => \$opt_logging,
    'tracing'       => \$opt_tracing,
    'logstamp=s'    => \$opt_logstamp,
    'first-handle=i' => \$opt_first_handle,
    'last-handle=i' => \$opt_last_handle,
    'default-num-dfiles=i' => \$opt_default_num_dfiles,
    'flow-buffer-size=i' => \$opt_default_flow_buffer_size,
    'flow-buffer-count=i' => \$opt_default_flow_buffer_count,
    'root-handle=i' => \$opt_root_handle,
    'fsid=i'        => \$opt_fsid,
    'fsname=s'      => \$opt_fsname,
    'res-io-ranges=i' => \$opt_res_io_ranges,
    'res-meta-ranges=i' => \$opt_res_meta_ranges,
    'trusted=i'     => \$opt_trusted,
    'server-job-timeout=i' => \$opt_server_job_timeout,
    'client-job-timeout=i' => \$opt_client_job_timeout,
    'fspath=s'      => \$fs_path, # directly set fs_path
    'storage=s'     => \$opt_storage,
    'metadata=s'    => \$opt_metadata,
    'help'          => \$show_help,
    'quiet!'        => \$opt_quiet,
    'trovesync!'    => \$opt_trovesync,
    'trove-method=s' => \$opt_trovemethod,
    'iospec=s'      => \$opt_iospec,
    'metaspec=s'    => \$opt_metaspec,
    'dist-name=s'   => \$opt_dist_name,
    'dist-params=s' => \$opt_dist_params,
    'spec-usage!'   => \$show_specusage,
    'genkey!'       => \$opt_gen_key,
    'securitykey',  => \$opt_security_key,
    'keystore=s'    => \$opt_keystore,
    'serverkey=s'   => \$opt_server_key,
    'securitytimeout=i' => \$opt_security_timeout,
    'securitycert'  => \$opt_security_cert,
    'cafile=s'      => \$opt_ca_file,
    'usercertdn=s'  => \$opt_user_cert_dn,
    'usercertexp=i' => \$opt_user_cert_exp,
    'ldaphosts=s'   => \$opt_ldap_hosts,
    'ldapbinddn=s'  => \$opt_ldap_bind_dn,
    'ldapbindpassword=s' => \$opt_ldap_bind_password,
    'ldapsearchmode=s' => \$opt_ldap_search_mode,
    'ldapsearchroot=s' => \$opt_ldap_search_root,
    'ldapsearchclass=s' => \$opt_ldap_search_class,
    'ldapsearchattr=s' => \$opt_ldap_search_attr,
    'ldapsearchscope=s' => \$opt_ldap_search_scope,
    'ldapuidattr=s' => \$opt_ldap_uid_attr,
    'ldapgidattr=s' => \$opt_ldap_gid_attr,
    'ldapsearchtimeout=i' => \$opt_ldap_search_timeout,
    '-'             => \$using_stdout)
    or die "Could not parse arguments.  See -h for help.\n";

if($opt_quiet)
{
    if(
        !($opt_protocol && $opt_ioservers && $opt_metaservers) 
        &&
        !($opt_iospec && $opt_metaspec)
    )
    {
        # quiet requires full specification of server addresses somehow
        die "Invalid arguments for --quiet usage.  See -h for help\n";
    }
}
else
{

    $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 != 1)
{
    die "Bad arguments.  Use -h for help.\n";
}
else
{
    unless (open(FILEOUT, ">", $ARGV[0]))
    {
        die "Can't open specified file $ARGV[0]: $!\n";
    }
    $output_target = \*FILEOUT;
}

# options check
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));

# security options check
die "--keystore requires --serverkey"
    if (defined($opt_keystore) && !defined($opt_server_key));
die "--serverkey requires --keystore or --cafile"
    if (defined($opt_server_key) && !defined($opt_keystore) && !defined($opt_ca_file));
die "--capath requires --serverkey"
    if (defined($opt_ca_file) && !defined($opt_server_key));
die "--securitykey and --securitycert are exclusive"
    if (defined($opt_security_key) && defined($opt_security_cert));

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

print_welcome();

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_meta_storage = get_meta_storage();
    $default_logfile = get_logfile();

    get_ionames($portmap, $default_storage, $default_logfile);
    get_metanames($portmap, $default_meta_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(METASTORAGE))
{
    if(!defined($default_meta_storage))
    {
        $default_meta_storage = get_meta_storage();
    }
    set_default_value(METASTORAGE, $default_meta_storage);
    set_default_value(METASTORAGE, $default_meta_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));

# may also be enabled on command-line
$opt_security_key = 1 
    if (-e "$script_dir/.pvfs2-genconfig-key");
$opt_security_cert = 1
    if (-e "$script_dir/.pvfs2-genconfig-cert");
# prompt for security options
if (!$opt_quiet && 
    (defined($opt_security_key) || defined($opt_security_cert))) {
    get_security_info
}

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_trusted == 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 $tracing = get_tracing();
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 $default_flow_buffer_size = get_default_flow_buffer_size();
my $default_flow_buffer_count = get_default_flow_buffer_count();
my $root_handle = get_root_handle();
my $res_io_ranges = defined($opt_res_io_ranges) ? $opt_res_io_ranges : 0;
my $res_meta_ranges = defined($opt_res_meta_ranges) ? $opt_res_meta_ranges : 0;
my $fsid = get_fsid();
my $fsname = get_fsname();
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... ";
}

emit_defaults($output_target, $num_unexp_reqs,
              $bmi_module, $default_logfile, $logging, $tracing,
              $logstamp, $server_job_timeout, $client_job_timeout);

emit_aliases($output_target);

emit_filesystem($output_target, $fsname, $fsid, 
                $root_handle, $last_handle, $first_handle,
                $io_count + $meta_count, $res_io_ranges, $res_meta_ranges, 
                $default_num_dfiles, 
                $default_flow_buffer_size, $default_flow_buffer_count);

if ($opt_metaspec) {
    emit_serveropts($output_target);
}

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

    my $req_limit = 65536;

    if(-s $ARGV[0] > $req_limit)
    {
        my $size = (-s $ARGV[0]);
        print STDERR
"Warning: Generated config file: " . $ARGV[0] . "\n" .
"has size: $size, which is larger than the current OrangeFS request: $req_limit\n" .
"Increase the value of PVFS_REQ_LIMIT_CONFIG_FILE_BYTES in src/proto/pvfs2-req-proto.h\n";
    }
}

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

if (!$opt_quiet && defined($opt_security_key))
{    
    print $OUT "\nPlease see the documentation for information on configuring " .
               "your client and server keys.\n";
}

if (!$opt_quiet && defined($opt_security_cert))
{
    print $OUT "\nPlease see the documentation for information on configuring " .
               "your CA and user certificates.\n";
}

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


