#!/bin/bash
# -*- mode: sh; c-basic-offset: 4; tab-width: 8; indent-tabs-mode: nil -*-
# vi: set shiftwidth=4 tabstop=8 softtabstop=4 expandtab:
# :indentSize=4:tabSize=8:noTabs=true:
# vim: filetype=sh

# ---- error handling
set -o errexit;
set -o posix;
set -o pipefail;
set -o errtrace;
set -o nounset

unexpected_error() {
    local errstat=$?
    echo "${g_prog:-$(basename "$0")}: Error! Encountered unexpected error at 'line $(caller)', exiting SMRT Link ${g_actionstr_errstr:-installation}..." 1>&2
    echo 1>&2

    if [[ ! -z "${g_tsreport_errstr:-}" ]] ; then
        echo "$g_tsreport_errstr" 1>&2;
    fi

    exit $errstat;
}
trap unexpected_error ERR;

# Force the path to only what we need (/sbin needed for 'ip addr')
PATH_ORIG=$PATH
PATH="${SMRT_PATH_ADD:-}:/usr/bin:/bin:/sbin"

# ---- error functions

merror() {
    echo "${g_prog:=$0}: Error! ""$@" 1>&2;
    echo 1>&2

    if [[ ! -z "${g_tsreport_errstr:-}" ]] ; then
        echo "$g_tsreport_errstr" 1>&2;
    fi
    exit 1;
}
minterror() {
    echo "${g_prog:=$0}: Internal Error! ""$@" 1>&2;
    exit 1;
}
mwarn() {
    echo "${g_prog:=$0}: Warning! ""$@" 1>&2;
}

# ---- required program check

required_prog_check() {
    # We expect something like this requirement, just to be able to fire off
    # our own versions of programs distributed with the release.
    local reqd_progs="
        dirname
        basename
        readlink
    "
    # While we are not fully self contained, we will also require at least
    # these additional progams in our PATH
    local additional_reqd_progs="
        bash
        rsync
        sed
        grep
        wc
        tr
    "

    local prog;
    for prog in $reqd_progs $additional_reqd_progs; do
        # Check that prog exists in the path
        if ! which "$prog" > /dev/null; then
            merror "Cannot find '$prog'.  Installer/Upgrader requires '$prog' to be found in PATH."
        fi
    done
}
# Check to make sure we have all the programs we need in our PATH beofre we use
# any of them.
required_prog_check;

# ---- pre-globals

g_prog=$(basename "$0");
g_progdir=$(dirname "$0");
g_progdir_abs=$(dirname "$(readlink -f "$0")");

# ---- usage
usage() {
    local exitstat=2;
    if [[ ! -z "${2:-}" ]] ; then
        if [[ ! $2 =~ [[:digit:]]+ ]] ; then
            minterror "usage(): exitstat ($2) must be numeric."
        fi
        exitstat=$2;
    fi

    # Only redirect to stderr on non-zero exit status
    if [[ $exitstat -ne 0 ]] ; then
        exec 1>&2;
    fi

    if [[ ! -z "${1:-}" ]] ; then
        echo "$g_prog: Error! $1"
    fi

    echo "Usage: $g_prog --rootdir dir \\"
    echo "             [--batch] \\"
    echo "             [--upgrade] [--priordir dir] \\"
    echo "             [--reconfig] \\"
    echo "             [--restore] \\"
    echo "             [--version] \\"
    echo "             \\"
    echo "             [--smrttools-only] \\"
    echo "             \\"
    echo "             [--skip-userquery] \\"
    echo "             [--skip-system-check|--skip-hwcheck] \\"
    echo "             [--ignore-system-check|--ignore-hwcheck] \\"
    echo "             [--skip-dnscheck] \\"
    echo "             \\"
    echo "             [--skip-remoteurl-check] \\"
    echo "             [--skip-jms-autodetect] \\"
    echo "             [--skip-systemsanity|--no-systemsanity] \\"
    echo "             [--skip-backup|--no-backup] \\"
    echo "             [--skip-backup-pre|--no-backup-pre] \\"
    echo "             [--skip-backup-post|--no-backup-post] \\"
    echo "             [--skip-import[-canneddata]|--no-import[-canneddata]] \\"
    echo "             \\"
    echo "             [--no-daemon-kill|--no-daemon-stop] \\"
    echo "             [--noflush|--no-flush] \\"
    echo "             [--clearscreen|--clearscreen-mode] \\"
    echo "             [--testmode] \\"
    echo "             [--argfile file] \\"
    echo "             [--configfile file] \\"
    echo "             [--ignore-configfile] \\"
    echo "             [--no-extract] \\"
    echo "             [--extract-bundles-only] \\"
    print_cfg_usageargs;
    echo "             [--help] [--helpall]"
    echo ""
    echo "         --upgrade             -- upgrade an existing installation"
    echo "         --reconfig            -- reconfigure an existing installation"
    echo "         --restore             -- restore a previous install from only"
    echo "                                  smrt_root/userdata/config directory"
    echo "         --version             -- print smrtlink version and exit"
    echo "         --priordir            -- install directory upgrading from"
    echo "         --batch               -- batch mode (non-interactive)"
    echo "         --rootdir             -- smrt rootdir (of self-exracting tarball)"
    echo "         --smrttools-only      -- extract and configure only smrttools"
    echo "         --skip-userquery      -- skip user query"
    echo "         --skip-system-check   -- skip system requirement checks"
    echo "         --skip-hwcheck        --   same as --skip-system-check"
    echo "         --ignore-system-check -- ignore system-check warnings"
    echo "         --ignore-hwcheck      --   same as --ignore-system-check"
    echo "         --skip-dnscheck       -- skip dns check"
    echo "         --skip-remoteurl-check  -- skip remote service url check";
    echo "         --skip-jms-autodetect -- skip jms autodetect from PATH";
    echo "         --skip-systemsanity   -- skip system sanity check";
    echo "         --skip-backup         -- skip pre- and post-upgrade backups";
    echo "         --skip-backup-pre     -- skip the pre-upgrade backup";
    echo "         --skip-backup-post    -- skip the post-upgrade backup";
    echo "         --skip-import-canneddata -- skip flagging import of canneddata";
    echo "         --no-daemon-kill      -- skip check/kill of running daemons"
    echo "         --no-flush            -- do not flush stdin in interactive mode"
    echo "         --clearscreen-mode    -- clear screen between install/upgrade parts "
    echo "         --testmode            -- standalone test mode";
    echo "         --argfile             -- file of command line arguments";
    echo "         --configfile          -- path to configfile (takes precedence over";
    echo "                                  existing config file from previous install)"
    echo "         --ignore-configfile   -- ignore existing configfile (from previous"
    echo "                                  install)"
    echo "         --no-extract          -- do not extract bundles (assumes they exist";
    echo "                                  from previous install)"
    echo "         --extract-bundles-only -- extract bundles only, then exit";

    echo ""
    echo "       Config Options:"
    print_cfg_usagedesc;
    echo ""
    echo "         --help                   -- print this usage";
    echo "         --helpall                -- print this usage and all subcmd usages";

    local progtrunc="${g_prog%%_*}*.run"

    echo ""
    echo "Example Commands:"
    echo ""
    echo "    Extract all smrtlink bundles, then invoke installer without extracting:"
    echo ""
    echo "       $progtrunc --rootdir rootdir --extract-bundles-only"
    echo "       $progtrunc --rootdir rootdir --no-extract"
    echo ""

    if [[ $exitstat -ne 0 ]] ; then
        # Print error again, useful for long usages messages
        if [[ ! -z "${1:-}" ]] ; then
            echo ""
            echo "$g_prog: Error! $1"
        fi
    fi

    # bash only:
    if [[ $exitstat -ne 0 ]] ; then
        echo "  at: $(caller)";
    fi
    exit $exitstat;
}

# ---- argument parsing
# Save off the original args, use as "${g_origargs[@]}" (with double quotes)
declare -a g_origargs;
g_origargs=( ${1+"$@"} )

setargarray() {
    local argstr=$1; shift;

    eval set -- $argstr
    g_argarr=( ${1+"$@"} )
}

arg_to_bytes() {
    local arg=$1; shift;
    local defunit=$1; shift;

    local val;
    local unit;
    local bytes="";

    if  [[ -z "$arg" ]] ||
        [[ x"$arg" == x"__UNSET__" ]] ||
        [[ x"$arg" == x"__DEFAULT__" ]] ; then
        echo "$arg"
        return 0
    fi
    if [[ $arg =~ ^([0-9]+)[[:space:]]*([gGmMkKbB])?[iI]?[bB]?$ ]] ; then
        val="${BASH_REMATCH[1]}"
        unit="${BASH_REMATCH[2]}"
        if [[ -z "$unit" ]]; then
            unit=$defunit;
        fi
        case "$unit" in
            [gG]) bytes=$(( $val * 1024 * 1024 * 1024 ));;
            [mM]) bytes=$(( $val * 1024 * 1024 ));;
            [kK]) bytes=$(( $val * 1024 ));;
            [bB]) bytes=$(( $val ));;
            *)  minterror "arg_to_bytes(): Unrecognized \$defunit: $defunit";;
        esac
    else
        minterror "arg_to_bytes(): Unrecognized memory specification '$arg'.  Expected decimal digits followed by optional G,GB,GiB,M,MB,MiB,K,KB,KiB,B (case insensitive), $defunit if none specified"
    fi

    echo "$bytes"
}
arg_to_MiB() {
    local arg=$1; shift;

    local defunit="M";
    if [[ ! -z "${1:-}" ]] ; then
        defunit=$1;
    fi

    local bytes;
    bytes=$(arg_to_bytes "$arg" "$defunit")
    local MiB="";
    if [[ ! -z "$bytes" ]] ; then
        # Only convert if we actually have a numeric value here (as opposed to
        # somethinge like __UNSET__ or __DEFAULT__)
        if [[ $bytes =~ ^[0-9]+$ ]] ; then
            MiB=$(( $bytes / ( 1024 * 1024 ) ))
        fi
    fi

    echo "$MiB"
}

cfg_arg_to_bytes() {
    local group=$1; shift;
    local name=$1; shift;

    # Use get_cfg_val_raw() here, since we do not want the default
    # substituted yet.
    local tmpval;
    tmpval=$(get_cfg_val_raw "$group" "$name")

    if isset_cfgval "$tmpval"; then
        if [[ x"$tmpval" != x"__DEFAULT__" ]] ; then
            tmpval=$(arg_to_bytes "$tmpval" "G")
            set_cfg_val "$group" "$name" "$tmpval";
        fi
    fi
}

cfg_arg_to_MiB() {
    local group=$1; shift;
    local name=$1; shift;

    # Use get_cfg_val_raw() here, since we do not want the default
    # substituted yet.
    local tmpval;
    tmpval=$(get_cfg_val_raw "$group" "$name")

    if isset_cfgval "$tmpval"; then
        if [[ x"$tmpval" != x"__DEFAULT__" ]] ; then
            tmpval=$(arg_to_MiB "$tmpval")
            set_cfg_val "$group" "$name" "$tmpval";
        fi
    fi
}

achk() {
    [[ $1 -eq 0 ]] && usage "Missing argument to $2 option";
    if [[ x"$3" == x"posint" ]]; then
        if  [[ ! $4 =~ ^[[:digit:]]+$ ]] ||
            [[ $4 =~ ^0+$ ]] ; then
            usage "Argument to $2 option ($4) must be a positive integer"
        fi
    elif [[ $3 =~ ^nonempty(-nodash)? ]]; then
        if [[ -z "$4" ]] ; then
            usage "Argument to $2 option must not be an empty string"
        fi
    elif [[ x"$3" == x"boolean" ]]; then
        if [[ ! $4 =~ ^(true|false)$ ]] ; then
            usage "Argument to $2 option ($4) must be boolean ('true' or 'false')"
        fi
    elif [[ x"$3" == x"file_readable" ]]; then
        if [[ ! -e "$4" ]] ; then
            usage "File argument to $2 option ($4) does not exist"
        elif [[ ! -r "$4" ]] ; then
            usage "File argument to $2 option ($4) is not readable"
        fi
    fi

    if [[ $3 =~ -nodash ]] ; then
        if [[ $4 =~ ^- ]] ; then
            usage "Argument to $2 option ($4) must not start with a '-' dash"
        fi
    fi

}

parseargs_core() {
    while [[ $# != 0 ]]; do
        opt="$1"; shift;
        case "$opt" in
            # Flag with no argument example:
            #   --flag|--fla|--fl|--f)
            #     opt_flag=true;;
            # Option with argument example:
            #   --arg|--ar|--a)
            #     [[ $# -eq 0 ]] && usage;
            #     opt_somearg=$1; shift;;
            --upgrade) opt_upgrade=true;;
            --reconfig) opt_reconfig=true;;
            --restore) opt_restore=true;;
            --version) opt_version=true;;
            --priordir)
                achk $# $opt notempty "${1:-}"
                opt_priordir=$1; shift;;
            --batch) opt_batch=true;;
            --rootdir)
                achk $# $opt notempty "${1:-}"
                opt_rootdir=$1; shift;;
            --smrttools-only) opt_smrttools_only=true;;
            --skip-userquery) opt_skip_userquery=true;;
            --skip-system-check|--skip-hwcheck) opt_skip_system_check=true;;
            --ignore-system-check|--ignore-hwcheck) opt_ignore_system_check=true;;
            --skip-dnscheck) opt_skip_dnscheck=true;;
            --skip-remoteurl-check) opt_skip_remoteurl_check=true;;
            --skip-jms-autodetect) opt_skip_jms_autodetect=true;;
            --skip-systemsanity|--no-systemsanity) opt_skip_systemsanity=true;;
            --skip-backup|--no-backup)
                opt_skip_backup_preupgrade=true;
                opt_skip_backup_postupgrade=true;;
            --skip-backup-pre|--skip-backup-preupgrade|--no-backup-pre|--no-backup-preupgrade)
                opt_skip_backup_preupgrade=true;;
            --skip-backup-post|--skip-backup-postupgrade|--no-backup-post|--no-backup-postupgrade)
                opt_skip_backup_postupgrade=true;;
            --skip-import|--skip-import-canneddata|--no-import|--no-import-canneddata)
                opt_skip_import_canneddata=true;;
            --no-daemon-kill|--no-daemon-stop) opt_no_daemon_kill=true;;
            --argfile|--arg-file)
                achk $# $opt file_readable "${1:-}"
                local -a fileargs;
                local opt_argfile=$1; shift;
                fileargs=$(sed -e '/^[[:space:]]*#/d' "$opt_argfile")
                setargarray "$fileargs"
                parseargs "${g_argarr[@]}"
                ;;
            --noflush|--no-flush) opt_no_flush=true;;
            --clearscreen|--clearscreen-mode) opt_clearscreen_mode=true;;
            --testmode) opt_testmode=true;;
            --configfile)
                achk $# $opt file_readable "${1:-}"
                opt_configfile=$1; shift;;
            --ignore-configfile) opt_ignore_configfile=true;;
            --no-extract) opt_no_extract=true;;
            --extract-bundles-only) opt_extract_bundles_only=true;;

            --computecfg-*-reset)
                if [[ ! $opt =~ ^--computecfg-([0-9]+)-reset$ ]] ; then
                    merror "Unrecognized format for --computecfg-*-reset option: $opt";
                fi
                local reset_group;
                reset_group=$(get_listgroup_from_listnum "computecfg_NN" "${BASH_REMATCH[1]}")
                opt_computecfg_resetlist+=( "$reset_group" );
                add_cfg_listgroup --cmdlineopt "computecfg_NN" "$reset_group"

                if [[ ! \ $opt_computecfg_ignoreconfigfiles\  =~ \ $reset_group\  ]] ; then
                    opt_computecfg_ignoreconfigfiles+=" $reset_group"
                fi
                ;;
            --computecfg-*-delete)
                if [[ ! $opt =~ ^--computecfg-([0-9]+)-delete$ ]] ; then
                    merror "Unrecognized format for --computecfg-*-delete option: $opt";
                fi
                local delete_group;
                delete_group=$(get_listgroup_from_listnum "computecfg_NN" "${BASH_REMATCH[1]}")

                opt_computecfg_deletelist+=( "$delete_group" );
                add_cfg_listgroup --cmdlineopt "computecfg_NN" "$delete_group"
                if [[ ! \ $opt_computecfg_ignoreconfigfiles\  =~ \ $delete_group\  ]] ; then
                    opt_computecfg_ignoreconfigfiles+=" $delete_group"
                fi
                ;;
            --computecfg-*-clone)
                achk $# $opt nonempty-nodash "${1:-}"
                local clone_dst;
                local clone_src;

                if [[ ! $opt =~ ^--computecfg-([0-9]+)-clone$ ]] ; then
                    merror "Unrecognized format for --computecfg-*-clone option: $opt";
                fi
                clone_dst=$(get_listgroup_from_listnum "computecfg_NN" "${BASH_REMATCH[1]}")

                if [[ ! $1 =~ ^(computecfg[-_])?([0-9]+)$ ]] ; then
                    merror "Unrecognized format for --computecfg-*-clone argument: $1";
                fi
                clone_src=$(get_listgroup_from_listnum "computecfg_NN" "${BASH_REMATCH[2]}")
                # In the case of multiple clone options for the same dst,
                # use only the latest
                local found=false;
                if [[ ! -z "${opt_computecfg_clonelist[@]+${opt_computecfg_clonelist[@]}}" ]] ; then
                    local i;
                    for ((i=0; i < ${#opt_computecfg_clonelist[@]} ; i++)); do
                        if [[ ${opt_computecfg_clonelist[$i]} =~ :${clone_dst}$ ]]; then
                            opt_computecfg_clonelist[$i]="$clone_src:$clone_dst";
                            found=true;
                            break;
                        fi
                    done
                fi
                if ! $found; then
                    opt_computecfg_clonelist+=( "$clone_src:$clone_dst" );
                fi
                add_cfg_listgroup --cmdlineopt "computecfg_NN" "$clone_dst"

                if [[ ! \ $opt_computecfg_ignoreconfigfiles\  =~ \ $clone_dst\  ]] ; then
                    opt_computecfg_ignoreconfigfiles+=" $clone_dst"
                fi
                shift;;

            --helpall|-helpall)
                # We have no subprogs to pass --helpall to.  Just issue normal
                # usage and exit (i.e. no need to pass subargs or to run
                # usage in subshell as with the higher level subprog handling
                # of --helpall)
                usage "" 0;;
            -h|-help|--help|--hel|--he|--h) usage "" 0;;
            --) # Ignore the "--" separator, we have no subprogs
                ;;
            *)
                if [[ $opt =~ ^-- ]] ; then
                    stat=0;
                    assign_cfg_cmdlineopt "$opt" ${1+"$@"} || stat=$?
                    if [[ $stat -ne 0 ]] ; then
                        if [[ $opt =~ ^- ]] ; then
                            usage "Unrecognized option: $opt";
                        else
                            local argstr=${1+"$@"};
                            usage "Unexpected extraneous arguments detected: $opt $argstr";
                        fi
                    fi
                    shift "$gret__assign_cfg_cmdlineopt__shiftcnt";
                else
                    usage "Unexpected command line option: $opt"
                fi
        esac
    done

    if ! $opt_version; then
        [[ -z "$opt_rootdir" ]] && usage "Required option '--rootdir' not specified."
    fi



    if $opt_upgrade && $opt_reconfig; then
        usage "Cannot specify both --upgrade and --reconfig."
    elif $opt_upgrade && $opt_restore; then
        usage "Cannot specify both --upgrade and --restore."
    elif $opt_reconfig && $opt_restore; then
        usage "Cannot specify both --reconfig and --restore."
    fi

    # Convert memtotal options to Bytes
    cfg_arg_to_bytes "system" "memtotal"

    # Convert mem options to MiB if units are specfied
    cfg_arg_to_MiB "smrtlink" "services_minmem"
    cfg_arg_to_MiB "smrtlink" "services_maxmem"

    gret_args=( ${1+"$@"} )
}

parseargs() {
    opt_upgrade=false;
    opt_reconfig=false;
    opt_restore=false;
    opt_version=false;
    opt_priordir="";
    opt_batch=false;
    opt_rootdir="";
    opt_smrttools_only=false;
    opt_skip_userquery=false;
    opt_skip_system_check=false;
    opt_ignore_system_check=false;
    opt_skip_dnscheck=false;
    opt_skip_remoteurl_check=false;
    opt_skip_jms_autodetect=false;
    opt_skip_systemsanity=false;
    opt_skip_backup_preupgrade=false;
    opt_skip_backup_postupgrade=false;
    opt_skip_import_canneddata=false;
    opt_no_daemon_kill=false;
    opt_no_flush=false;
    opt_clearscreen_mode=false;
    opt_testmode=false;
    opt_configfile="";
    opt_ignore_configfile=false;
    opt_no_extract=false;
    opt_extract_bundles_only=false;

    opt_computecfg_deletelist=();
    opt_computecfg_resetlist=();
    opt_computecfg_clonelist=();
    opt_computecfg_ignoreconfigfiles=""

    # sets "${gret_args[@]}" to any remaining args after parsing
    parseargs_core ${1+"$@"}
    # The remaining arguments are returned in "${gret_args[@]}", check to make
    # sure we don't have any
    if [[ ! -z "${gret_args[@]+${gret_args[@]}}" ]]; then
        local argstr="${gret_args[@]}"
        usage "Extraneous arguments detected: $argstr"
    fi

    if $opt_upgrade; then
        if [[ -z "$opt_priordir" ]]; then
            if [[ -z "${SMRT_PRIOR_INSTALL_DIR:-}" ]] ; then
                usage "Either --priordir or 'SMRT_PRIOR_INSTALL_DIR' must be set to previous install root";
            else
                opt_priordir=$SMRT_PRIOR_INSTALL_DIR;
            fi
        fi
    fi
}


# ---- globals

# Round $val to the nearest $mod (e.g. 'round $val 16' will round $val to the
# nearest multiple of 16).
round() {
    local val=$1; shift;
    local mod=$1; shift;

    local round;
    round=$(( ((($val + ($mod / 2)) / $mod) * $mod) ))

    echo "$round"
}
# Round $val up to the nearest $mod (e.g. 'round $val 16' will round $val to
# the nearest larger multiple of 16).
roundup() {
    local val=$1; shift;
    local mod=$1; shift;

    local roundup;
    roundup=$(( ((($val + ($mod - 1 )) / $mod) * $mod) ))

    echo "$roundup"
}

# Return a percentage of the total memory (in MiB), rounded to nearest 64MB.
get_mempct_MiB() {
    local pct=$1; shift;
    local min=$1; shift;
    local max=$1; shift;

    local memtotal_bytes;
    memtotal_bytes=$(get_cfg_val "system" "memtotal");

    local memsize_MiB;
    memsize_MiB=$(( ($memtotal_bytes * $pct) / (100 * 1024 * 1024) ))

    # Round to nearest 64MiB boundary
    memsize_MiB=$(round $memsize_MiB 64)

    if [[ ! -z "$max" ]] && [[ $memsize_MiB -gt $max ]] ; then
        memsize_MiB=$max;
    elif [[ ! -z "$min" ]] && [[ $memsize_MiB -lt $min ]] ; then
        memsize_MiB=$min;
    fi

    echo "$memsize_MiB";
}

set_preglobals() {
    # id related variables
    g_runuser=$(id -nu)
    g_rungroup=$(id -ng)
    g_runuid=$(id -u)
    # g_rungid=$(id -g)
    g_full_idstr=$(id)

    # Cache vars
    gcache__get_cfg_index__groupname="";
    gcache__get_cfg_index__lastindex="";
    gcache__get_cfgg_index__group="";
    gcache__get_cfgg_index__lastindex="";

    # Only for bash 4.x (-g option)
    #declare -ag ga_cfgg_groups=();

    ga_cfgg_groups=();
    g_cfgg_basegroup=();
    g_cfgg_basegroup_listidxs=();
    g_cfgg_listgroup=();
    g_cfgg_savegroup=();
    g_cfgg_ignoreconfigfile=();

    # Only for bash 4.x (-g option)
    #declare -ag g_cfg_datatype=();
    #declare -ag g_cfg_default=();
    #declare -ag g_cfg_optional=();
    #declare -ag g_cfg_optstr_alts=();
    #declare -ag g_cfg_optstr_typedstr=();
    #declare -ag g_cfg_printstr_splen=();
    #declare -ag g_cfg_specialcase=();
    #declare -ag g_cfg_usagestr=();
    #declare -ag g_cfg_promptstr=();
    #declare -ag g_cfg_usingstr=();
    #declare -ag g_cfg_settingname=();
    #declare -ag g_cfg_optstr=();
    #declare -ag g_cfg_curval=();
    #declare -ag g_cfg_srcstr=();
    #declare -ag g_cfg_usedefault=();
    #declare -ag g_cfg_basegroup=();

    ga_cfg_groupnames=();
    g_cfg_datatype=();
    g_cfg_default=();
    g_cfg_optional=();
    g_cfg_optstr_alts=();
    g_cfg_optstr_typedstr=();
    g_cfg_printstr_splen=();
    g_cfg_specialcase=();
    g_cfg_usagestr=();
    g_cfg_promptstr=();
    g_cfg_usingstr=();
    g_cfg_settingname=();
    g_cfg_optstr=();
    g_cfg_curval=();
    g_cfg_srcstr=();
    g_cfg_usedefault=();
    g_cfg_basegroup=();

    g_cfg_basegroup=();

    # JMS Settings
    g_jmstype_select_options=(
        "SGE" "OGS" "UGE"
        "PBS" "TORQUE" "PBSPro"
        "LSF" "OpenLava"
        "Slurm" "AWS"
        "OtherJMS__*@@OtherJMS    (Other/Unrecognized Third Party JMS)"
        "CustomJMS__*@@CustomJMS   (Custom JMS)"
        "NONE@@None        (Non-Distributed Mode)");
    g_jmstype_options_str=""
    g_jmstype_options=()
    local i;
    for i in "${g_jmstype_select_options[@]}"; do
        g_jmstype_options_str+=" ${i%%@@*}";
        g_jmstype_options_str=${g_jmstype_options_str# }
        [[ $i =~ ^(OpenLava|Slurm)$ ]] && g_jmstype_options_str+="\\n";
        g_jmstype_options+=( "${i%%@@*}" );
    done

    # Remote Server Settings
    g_eve_server_selections=("production" "staging" "beta" "alpha")
    g_eve_server_selections_str="${g_eve_server_selections[@]}";

    g_eve_official_name="SMRT Link Event Service"
    g_eve_official_servername="SMRT Link Event Server"
    g_eve_url_production="https://smrtlink-eve.pacbcloud.com:8083"
    g_eve_url_staging="https://smrtlink-eve-staging.pacbcloud.com:8083"
    g_eve_url_beta="https://smrtlink-eve-beta.pacbcloud.com:8083"
    g_eve_url_alpha="https://smrtlink-eve-alpha.pacbcloud.com:8083"

    g_update_server_selections=("production" "staging" "beta" "alpha")
    g_update_server_selections_str="${g_update_server_selections[@]}";

    g_cromwell_loglevel_list=("DEBUG" "INFO" "WARN" "ERROR" "FATAL")
    g_cromwell_loglevel_liststr="${g_cromwell_loglevel_list[@]}";

    g_update_official_name="SMRT Link Update Service"
    g_update_official_servername="SMRT Link Update Server"
    g_update_url_production="https://smrtlink-update.pacbcloud.com:443"
    g_update_url_staging="https://smrtlink-update-staging.pacbcloud.com:443"
    g_update_url_beta="https://smrtlink-update-beta.pacbcloud.com:443"
    g_update_url_alpha="https://smrtlink-update-alpha.pacbcloud.com:443"

    g_internet_testurl1="http://pacb.com"
    g_internet_testurl2="http://google.com"

    init_cfg;
}

set_globals() {
    g_topdir=$(readlink -f "$g_progdir/../..")
    g_rwdir="$g_topdir/rwdir"

    g_install_startepoch=$(date +"%s")
    g_install_startdatestr=$(date -d "@$g_install_startepoch")
    #g_install_starttimestr=$(date -d "@$g_install_startepoch" +"%Y%m%d_%H%M%S")

    if $opt_batch; then
        g_batchstr=" (batch mode)"
    else
        g_batchstr=""
    fi

    # Set buildinfo
    # FIXME: should pickup the buildinfo stuff from something generated
    #        by the build and placed in the install directory (e.g.
    #        etc/buildinfo.txt or etc/buildinfo.sh).  Fake the buildinfo here
    #        for now:
    g_buildinfo__libc_version="2.12"
    if ! $opt_testmode; then
        g_buildinfo__versionstr_full=$(cat "$g_topdir/etc/versionstr.txt")
    else
        g_buildinfo__versionstr_full="smrtxxx-testmode_W.X.Y.ZZZZZZ"
    fi

    # Version Strings
    g_versionstr_full=$g_buildinfo__versionstr_full;
    g_versionstr_short=${g_versionstr_full##*_}

    g_versionnum_full=$g_versionstr_short;
    g_versionnum_short=${g_versionnum_full%.*}


    # Userdata Directories
    # The userdata directory will be used for:
    #     config information
    #     log files
    #     database files
    #     pointer to jobs_root
    #     pointer to tmp_dir
    #
    # We expect the userdata directory to be right under the rootdir (either
    # as a directory or as a symlink to a directory).  If it does not exist,
    # we will try to create it (if not, fail with error).
    g_userdata_dir="$opt_rootdir/userdata"
    g_userdata_dirabs=$(readlink -m "$g_userdata_dir")

    g_config_dir="$g_userdata_dirabs/config"
    g_log_dir="$g_userdata_dirabs/log"
    g_archive_dir="$g_userdata_dirabs/archive"

    g_jobsroot_dirlink="$g_userdata_dirabs/jobs_root"
    g_tmpdir_dirlink="$g_userdata_dirabs/tmp_dir"
    g_dbdatadir_dirlink="$g_userdata_dirabs/db_datadir"

    # Support upgrades migrating from the old tarball name 'smrtsuite'.
    # We will basically read the old configfile if the new configfile does
    # not exist.  Once we successfully write out the new config file, we will
    # remove the old config file.
    g_smrtlink_configfile="$g_config_dir/smrtlink.config"

    g_generated_dir="$g_userdata_dirabs/generated"
    g_generated_dir_new="${g_generated_dir}.new"
    g_generated_dir_old="${g_generated_dir}.old"

    # Files generated in the generated.new directory
    g_new_generated_config_dir="$g_generated_dir_new/config"
    g_new_smrtlink_system_configjson="$g_new_generated_config_dir/smrtlink-system-config.json"
    g_new_smrtlink_migrationconfig_json="$g_new_generated_config_dir/migration-config.json"
    g_new_smrtlink_cromwell_config="$g_new_generated_config_dir/cromwell.conf"
    g_new_smrtlink_cromwell_cli_config="$g_new_generated_config_dir/cromwell_cli.conf"

    # Files referenced from the actual generated directory
    g_generated_config_dir="$g_generated_dir/config"
    g_smrtlink_system_configjson="$g_generated_config_dir/smrtlink-system-config.json"
    g_smrtlink_migrationconfig_json="$g_generated_config_dir/migration-config.json"

    g_user_jmsenv_filename="user.jmsenv.ish";
    g_user_jmsenv_dir_reluserdatadir="user_jmsenv"
    g_user_jmsenv_dir_relgeneratedconfigdir="../../../$g_user_jmsenv_dir_reluserdatadir"
    g_user_jmsenv_file="$g_userdata_dir/$g_user_jmsenv_dir_reluserdatadir/$g_user_jmsenv_filename";
    g_user_jmsenv_file_relgeneratedconfigdir="$g_user_jmsenv_dir_relgeneratedconfigdir/$g_user_jmsenv_filename"


    # Logfiles
    #
    # installer:
    g_installer_logdir="$g_log_dir/installer"
    g_install_logfile="$g_installer_logdir/install.log"
    g_upgrade_logfile="$g_installer_logdir/upgrade.log"
    g_reconfigure_logfile="$g_installer_logdir/reconfig.log"
    g_restore_logfile="$g_installer_logdir/restore.log"
    # smrtlink-analysisservices-gui:
    g_slag_logdir="$g_log_dir/smrtlink-analysisservices-gui"

    # database
    g_datbase_logfile="$g_log_dir/database.log"
    g_dbhost="localhost"
    g_dbuser="smrtlink_user"
    g_dbpassword="password"
    g_dbdatabase_smrtlink="smrtlinkdb"

    # Legacy sqlite database file
    g_database_dir="$g_userdata_dirabs/database"
    g_sqlite_database="$g_database_dir/smrtlink_analysis_sqlite.db"

    # services bundler paths
    g_smrtlink_toolsbin="$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/pacbio/smrtlink-analysisservices-gui/tools/bin"
    g_javahome="$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/thirdparty/java/jre/jre_8u192b12-hotspot"

    # Tech Support Troubleshoot Reporting Setup (via Event Service)
    g_tsreport_install_exe="$g_progdir/tsreport-install"
    g_tsreport_errstr=""
    g_tsreport_dir="$g_userdata_dir/tsreport"
    g_tsreport_sluuid_file="$g_tsreport_dir/sluuid"
    g_tsreport_dnsname_file="$g_tsreport_dir/dnsname"
    g_tsreport_eve_url_file="$g_tsreport_dir/eve_url"
    # FIXME: Remove this after all sites upgraded to >7.0.1, no longer needed
    g_tsreport_eve_enable_file="$g_tsreport_dir/eve_enable"

    if $opt_upgrade; then
        g_logfile=$g_upgrade_logfile;
        g_actionstr="Upgrade"
        g_actionstr_lc="upgrade"
        g_actionstr_er="Upgrader"
        g_actionstr_ing="Upgrading"
        g_actionstr_errstr="upgrade"
    elif $opt_reconfig; then
        g_logfile=$g_reconfigure_logfile;
        g_actionstr="Reconfigure"
        g_actionstr_lc="reconfigure"
        g_actionstr_er="Reconfigurer"
        g_actionstr_ing="Reconfiguring"
        g_actionstr_errstr="reconfigure"
    elif $opt_restore; then
        g_logfile=$g_restore_logfile;
        g_actionstr="Restore"
        g_actionstr_lc="restore"
        g_actionstr_er="Restorer"
        g_actionstr_ing="Restoring"
        g_actionstr_errstr="restore"
    else
        g_logfile=$g_install_logfile;
        g_actionstr="Install"
        g_actionstr_lc="install"
        g_actionstr_er="Installer"
        g_actionstr_ing="Installing"
        g_actionstr_errstr="installation"
    fi

    # In the case of an upgrade, set the g_prior* variables.  We should not
    # reference these variables if opt_upgrade is not true.  We should get an
    # error from the 'set -o nounset' setting if we do.
    if $opt_upgrade || $opt_reconfig || $opt_restore; then
        if $opt_upgrade; then
            # prior dir:
            g_priordir=$(readlink -f "$opt_priordir");
        elif $opt_reconfig || $opt_restore; then
            # prior dir:
            g_priordir=$g_topdir;
        else
            minterror "set_globals(): Bad opt_upgrade/opt_reconfig settings"
        fi


        # prior versionnums:
        local versionstr_file;
        if $opt_restore; then
            versionstr_file="$g_smrtlink_configfile"
            statinfo_file="$g_smrtlink_configfile"

            # These should be unused in the restore case
            g_prior_versionnum_full="__UNSET__";
            g_prior_versionnum_short="__UNSET__";
        else
            versionstr_file="$g_priordir/etc/versionstr.txt"
            statinfo_file="$versionstr_file"

            if [[ ! -e "$versionstr_file" ]] ; then
                merror "Could not determine version of prior installation.  '$versionstr_file' does not exist."
            fi
            local verstr;
            verstr=$(cat "$g_priordir/etc/versionstr.txt");
            local vernum_full=${verstr##*_}
            local vernum_short=${vernum_full%.*}

            g_prior_versionnum_full=$vernum_full;
            g_prior_versionnum_short=$vernum_short;
        fi

        # prior user/group:
        g_prior_user=$(stat --printf "%U\n" "$versionstr_file");
        g_prior_uid=$(stat --printf "%u\n" "$versionstr_file");
        g_prior_group=$(stat --printf "%G\n" "$versionstr_file");
        g_prior_gid=$(stat --printf "%g\n" "$versionstr_file");
    fi

    # In case of upgrades, set the prior services bundle install root (for
    # upgrading wso2)
    g_prior_slag_installdir=""
    if $opt_upgrade; then
        g_prior_slag_installdir="$g_priordir/bundles/smrtlink-analysisservices-gui/current/private/pacbio/smrtlink-analysisservices-gui/"
    fi

    # Domainname
    g_domainname=$(hostname -d 2> /dev/null || true)

    # Hardware Requirements
    g_hwreq__numprocs_min=32;
    g_hwreq__memGB_min=64;

    # Cache var for holding physmem size (so we only compute it once)
    g_hw_physmem_bytes="";

    # compute number of processors on this host
    g_hw_numprocs=$(hw_getnumprocs)

    # memory configuration is set in process_smrtlink_config_defaults()


    # Skip screens
    #  -- none for now
    #  -- e.g.  g_skip_system_check=false;

    g_python3_exe="$g_topdir/bundles/smrttools/smrtcmds/bin/python3"
    g_python3_bindir=$(dirname "$g_python3_exe")
    if $opt_testmode; then
        g_python3_exe="python3";
    fi

    g_dig_exe="$g_topdir/bundles/smrtinub/admin/bin/dig"
    g_curl_exe="$g_topdir/bundles/smrttools/current/private/thirdparty/curl/curl_7.64.1/binwrap/curl"

    g_runjmscmd_exe="$g_progdir_abs/runjmscmd"

    # set internal values
    local tmpval;
    tmpval=$(get_cfg_val "internal" "version")
    set_cfg_val "internal" "version" "$tmpval" "internal setting" false

    # FIXME: +++ Remove this after all sites upgraded to >7.0.1 (start) +++
    #        Hacking to remove chunkning on/off flag support.
    g_chunking_deprecated_hack__chunking_off="";
    # FIXME: --- Remove this after all sites upgraded to >7.0.1 (end) ---
}

# ---- subroutines

# -- logging subroutines

redir_init() {
    npipe=$(mktemp -u -t "$g_prog.tmp.XXXXXXXXXX")
    trap "rm -f $npipe" EXIT
    mkfifo "$npipe"
    # Alternatively:
    #  mknod $npipe p
}
redir_on() {
    tee -a <$npipe "$g_logfile" &
    exec 6>&1
    exec 7>&2
    exec 1>$npipe 2>&1
}
redir_off() {
    exec 1>&6 6>&-
    exec 2>&7 7>&-
}

logonly () {
    # echo ${1+"$@"}
    echo ${1+"$@"} >> $g_logfile
}

# Initialize the logfile.  After running this, all output to stdout and
# stderr will go to the log file as well.  To only output to the logfile,
# use the logonly() function.
logfile_setup() {
    # We will dump to a new logfile each time, in the rwdir of the install tree.
    # When available, we will copy the log into the userdata install log file
    # and transfer logging to that file.
    # So for initial logging, the user may need to look into the rwdir in the
    # install directory for the particular version being installed.  But after
    # that (i.e. after the userdata has been established), only that global
    # log file (including logs for all installs or upgrades, should be needed).

    # Create the log dir if it does not exist
    mkdir -p "$(dirname "$g_logfile")"

    # Always append to the log file, so do not remove it here
    # #  rm -f "$g_logfile"

    redir_init;
    redir_on;

    # Initial logging
    logonly ""
    logonly "==== Start ${g_actionstr}${g_batchstr}: $g_install_startepoch: $g_install_startdatestr"
    logonly ""
    logonly "$g_actionstr versionstr: $g_versionstr_full"
    logonly "$g_actionstr cmdline options: ${g_origargs[@]}"
    logonly ""
}

logfile_shutdown() {
    local endepoch;
    local endepochstr;

    endepoch=$(date +"%s")
    enddatestr=$(date -d "@$endepoch")

    redir_off;
    logonly ""
    logonly "==== Duration ${g_actionstr}${g_batchstr}: $(( $endepoch - $g_install_startepoch )) seconds"
    logonly "==== End ${g_actionstr}${g_batchstr}: $endepoch: $enddatestr"
}

tsreport_update_setup() {
    mkdir -p "$g_tsreport_dir"

    # Get the eve_url (from command line or previous setting)
    local eve_url;
    eve_url=$(get_eve_rawurl)
    if [[ ! -z "$eve_url" ]] ; then
        echo "$eve_url" > "$g_tsreport_eve_url_file"
    else
        rm -f "$g_tsreport_eve_url_file"
    fi

    # FIXME: +++ Remove this after all sites upgraded to >7.0.1 (start) +++
    rm -f "$g_tsreport_eve_enable_file"
    # FIXME: --- Remove this after all sites upgraded to >7.0.1 (end) ---

    # Get the dnsname (from command line, previous setting, interactive)
    local dnsname;
    dnsname=$(get_cfg_val "smrtlink" "dnsname");
    if [[ ! -z "$dnsname" ]] ; then
        echo "$dnsname" > "$g_tsreport_dnsname_file"
    else
        rm -f "$g_tsreport_dnsname_file"
    fi

    # Get the smrtlink uuid (from command line or previous setting)
    # Note if the subtarballs have not been extracted, the uuid may not
    # be available yet (because we don't have our local version of python
    # extracted).  We will only compute the sluuid if our version of python
    # is available.  We will change the error message below based on whether
    # the sluuid is available.  If it is not, we will have the user contact
    # Tech Support for in debugging the problem or for a workaround to use
    # the tsreport mechanism.
    local sluuid="";
    if [[ -x "$g_python3_exe" ]] ; then
        sluuid=$(get_cfg_val "install" "sluuid" || true);
    fi
    if [[ ! -z "$sluuid" ]] ; then
        echo "$sluuid" > "$g_tsreport_sluuid_file"
    else
        rm -f "$g_tsreport_sluuid_file"
    fi

    # Set the error string to be reported in case of unexpected_error, merror
    # or abort.  If the sluuid is available and we have everything we need
    # to run the tsreport-install script (java, tech-support-bundler, and
    # tech-support-uploader), then we'll direct the user to run the script.
    local nl=$'\n'
    if "$g_tsreport_install_exe" --runnable > /dev/null; then
        g_tsreport_errstr=$(cat <<-EOF
	  For troubleshooting help, please create a SalesForce case, contact
	  the PacBio Tech Support Team and run the following command to send
	  troubleshooting information to PacBio:

	      $g_progdir/tsreport-install --bundle --upload
	  $nl
	EOF
        )
    else
        g_tsreport_errstr=$(cat <<-EOF
	  For troubleshooting help, please create a SalesForce case and contact
	  the PacBio Tech Support Team.
	  $nl
	EOF
        )
    fi
}

# --

create_userdata() {
    # Create the userdata directory, make sure it is group/other readable
    (umask 0022 && mkdir -p "$g_userdata_dirabs")
}

flush_input() {
    $opt_no_flush && return 0;
    $opt_batch && return 0;

    # Make sure input queue is empty in interactive mode (to ignore characters
    # typed during the tarball extract and such, i.e. basically wait until we
    # present a prompt before taking input)
    local n;
    while read -t 1 n; do
        :
    done
}

printheader() {
    local str;

    echo ""
    echo ""

    # Center justify these (assuming 76 columns)
    str="PacBio SMRT Link ${g_actionstr_er}"
    printf "%*s\n" $(( (${#str} + 76) / 2)) "$str"

    str="($g_versionstr_full) "
    printf "%*s\n" $(( (${#str} + 76) / 2)) "$str"

    echo ""
    echo ""
}


next_screen() {
    $opt_batch && return 0;
    if $opt_clearscreen_mode; then
        clear;
        printheader;
    else
        echo
    fi
}

abort () {
    if $opt_upgrade; then
        echo "Aborting upgrade..."
    elif $opt_reconfig; then
        echo "Aborting reconfigure..."
    else
        echo "Aborting installation..."
    fi
    echo

    if [[ ! -z "${g_tsreport_errstr:-}" ]] ; then
        echo "$g_tsreport_errstr"
    fi
    exit 1;
}


fixup_hacks() {
    local bashtest_output;
    bashtest_output=$(LD_LIBRARY_PATH="$g_topdir/bundles/smrttools/current/private/thirdparty/ncurses/ncurses_6.1/lib" bash -c 'true' 2>&1 )
    if [[ $bashtest_output =~ :\ no\ version\ information\ available\ \( ]] ; then
        echo "Fixup for ncurses and system bash interaction..."
        mv "$g_topdir/bundles/smrttools/current/private/thirdparty/ncurses" "$g_topdir/bundles/smrttools/current/private/thirdparty/ncurses.orig"
        mv "$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/thirdparty/ncurses" "$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/thirdparty/ncurses.orig"
    fi
}

# -- prompting subroutines

uniqifylist() {
    local list=$1; shift;
    local nlist="";

    local elem;
    for elem in $list; do
        case " $nlist " in
            *" $elem "*) ;;
            *) nlist="$nlist $elem";;
        esac
    done
    nlist=${nlist# }

    echo "$nlist";
}


get_selection_number() {
    local selection=$1; shift;
    local -a choices=( ${1+"$@"} );

    local i;

    if [[ $selection =~ ^[[:digit:]]+$ ]]; then
        selection=$(( $selection -1 ));
    fi

    local selnum="";
    for ((i=0; i < ${#choices[@]} ; i++)); do
        [[ x"$selection" == x"$i" ]] && selnum=$i;
        [[ x"$selection" == x"${choices[$i]%%@@*}" ]] && selnum=$i;
        [[ x"$selection" == x"${choices[$i]#*@@}"  ]] && selnum=$i;
    done

    if [[ ! -z "$selnum" ]] ; then
        selnum=$(( $selnum + 1 ))
    fi
    echo "$selnum"
}

# To change the prompt, call the function something like this:
#     PS3='Prompter ($DEF)> ' process_query "1" "${gret_list[@]}"
process_query() {
    local sp="";
    local selstr="Pick an option:";
    local opt;
    for opt in ${1+"$@"}; do
        if [[ x"$opt" == x"--sp" ]]; then
            shift; sp="$1"; shift;
        elif [[ x"$opt" == x"--selstr" ]]; then
            shift; selstr="$1"; shift;
        else
            break;
        fi
    done
    local default=$1; shift;
    local -a choices=( ${1+"$@"} );

    gret_val="";

    local prompt_tmpl='Choice [$DEF]: '
    [[ ! -z "${PS3:-}" ]] && prompt_tmpl=$PS3;
    local DEF;

    local i;
    local ansno;
    local ans;
    local ansno_default;
    ansno_default=$(get_selection_number "$default" "${choices[@]}")
    if [[ -z "$ansno_default" ]] ; then
        minterror "process_query(): illegal default value: $default"
    fi
    local prompt;
    local DEF=$ansno_default;
    eval prompt="\"$prompt_tmpl\"";

    local ansind;
    while true; do
        echo "${sp}${selstr}"
        for ((i=0; i < ${#choices[@]} ; i++)); do
            j=$(( $i + 1 ))
            j=$(printf "%2s" "$j");
            echo "${sp}   $j) ${choices[$i]#*@@}"
        done
        read -p "${sp}$prompt" ansno
        logonly "$ansno"
        [[ -z "$ansno" ]] && ansno=$ansno_default;
        if  [[ $ansno =~ ^[0-9]+$ ]] &&
            [[ $ansno -ge 1 ]] &&
            [[ $ansno -le ${#choices[@]} ]]; then
            # echo "xx4: $ansno: $ans"
            ansind=$(( $ansno - 1 ));
            ans="${choices[$ansind]%%@@*}";
            break;
        fi
        # If we got here, we got an illegal value, loop through again
        #echo "xx7: illegal: $ansno"
        echo "${sp}Invalid selection ($ansno), try again."
        echo
    done
    gret_val=$ans;
}

# -- misc subroutines

# returns "-" if $1<$2, "+" if $1>$2, and "=" if $1==$2
# NOTE: A version number with more fields will compare greater than one
#       with less fields, but otherwise the same.  That is, 1.1 will compare
#       "less than (-)" 1.1.0.
compare_versions() {
    local verstr1=$1; shift
    local verstr2=$1; shift

    local -a ver1_arr;
    local -a ver2_arr;
    ver1_arr=( ${verstr1//./ } )
    ver2_arr=( ${verstr2//./ } )

    local i;
    local maxind="${#ver1_arr[@]}";
    if [[ "${#ver1_arr[@]}" -lt "${#ver2_arr[@]}" ]] ; then
        maxind="${#ver2_arr[@]}"
    fi

    local cmpres="=";
    for ((i=0; i < $maxind; i++)); do
        if [[ -z "${ver1_arr[$i]+set}" ]] ; then
            cmpres="-"
            break
        elif [[ -z "${ver2_arr[$i]+set}" ]] ; then
            cmpres="+"
            break
        elif [[ x"${ver1_arr[$i]}" == x"${ver2_arr[$i]}" ]] ; then
            # These are identical, skip further comparison
            :
        elif [[ ${ver1_arr[$i]} =~ ^[0-9]+$ ]] &&
             [[ ${ver2_arr[$i]} =~ ^[0-9]+$ ]] ; then
            # They are both numeric ints, compare as integers
            if [[ ${ver1_arr[$i]} -lt ${ver2_arr[$i]} ]]; then
                cmpres="-"
                break;
            else
                # We already know they are not equal, must be greater
                cmpres="+"
                break;
            fi
        else
            # At least one is not fully numeric, use 'sort -n' for compare
            local unsorted;
            local sorted;
            unsorted=$(echo "${ver1_arr[$i]}"; echo "${ver2_arr[$i]}")
            sorted=$(echo "$unsorted" | LC_ALL=C sort -n)
            if [[ x"$unsorted" == x"$sorted" ]] ; then
                # The sort order did not chhange, first one is less-than
                cmpres="-"
                break;
            else
                # We already know they are not equal, must be greater
                cmpres="+"
                break;
            fi
        fi
    done
    echo "$cmpres"
}

# Prompt for a yes/no response. Returns 'y' if we get a case-insensitive 'yes',
# 'y' or '' (default) response.   Returns 'n' if we get a case-insensitive
# 'no' or 'n' response.   Will retry otherwise until we get a valid response.
# If second argument is true, then return "" if the default was accepted (user
# hit return or just whitespace).  By specifying 'true' as the second argument,
# the caller can determine the difference between the user just hitting enter
# (to accept the default), and typing the default value.
yesno_prompt_core() {
    local yesdef=$1; shift;
    local promptstr=$1; shift;
    local b_nousedefault=${1:-false}

    local yesno_str="[Y/n]";
    if ! $yesdef; then
        yesno_str="[N/y]";
    fi

    local ans;
    local npromptstr=$promptstr;
    while true; do
        read -p "$npromptstr ${yesno_str}: " ans;
        logonly "$ans";

        if [[ $ans =~ ^[[:space:]]*$ ]] ; then
            if $b_nousedefault; then
                # We got default selection (i.e. "') and b_nousedefault
                # set to true, return "" and let the caller deal with it
                ans=""
            else
                if $yesdef; then
                    ans="y"
                else
                    ans="n"
                fi
            fi
        elif [[ $ans =~ ^([Yy]([Ee][Ss])?)$ ]] ; then
            ans="y"
        elif [[ $ans =~ ^([Nn]([Oo])?)$ ]] ; then
            ans="n"
        else
            local nl=$'\n'
            local sp;
            if [[ $promptstr =~ ^([[:space:]]*) ]]; then
                sp="${BASH_REMATCH[1]}  "
            fi
            npromptstr="${sp}Invalid value ($ans), try again...${nl}${nl}$promptstr"
            continue;
        fi
        break;
    done
    echo "$ans"
}
yesno_prompt() {
    yesno_prompt_core true ${1+"$@"}
}
noyes_prompt() {
    yesno_prompt_core false ${1+"$@"}
}

# Prompt for a string with default value.  If third argument is true, then
# return "" if the default was accepted (user hit return or just whitespace).
# By specifying 'true' as the third argument, the caller can determine the
# difference between the user just hitting enter (to accept the default),
# and typing the default value.
string_prompt() {
    local promptstr=$1; shift;
    local defstr=$1; shift;
    local b_nousedefault=${1:-false}

    local ans;
    read -p "$promptstr [$defstr]: " ans;
    logonly "$ans"
    # treat all whitespace answers as default
    [[ $ans =~ ^[[:space:]]*$ ]] && ans="";
    # if empty answer, use default if it exists, else return ""
    [[ -z "$ans" ]] && ! $b_nousedefault && ans="$defstr";

    echo "$ans"
}

# Prompt for a string with default value (input is not echoed).  If third
# argument is true, then return "" if the default was accepted (user hit
# return or just whitespace).  By specifying 'true' as the third argument,
# the caller can determine the difference between the user just hitting
# enter (to accept the default), and typing the default value.
string_prompt_noecho() {
    local promptstr=$1; shift;
    local defstr=$1; shift;
    local b_nousedefault=${1:-false}

    local ans;
    # Specify '-s' option to avoid echoing input
    read -s -p "$promptstr [$defstr]: " ans;

    logonly "<<INPUT_MASKED>>"
    # treat all whitespace answers as default
    [[ $ans =~ ^[[:space:]]*$ ]] && ans="";
    # if empty answer, use default if it exists, else return ""
    [[ -z "$ans" ]] && ! $b_nousedefault && ans="$defstr";

    echo "$ans"
}

# Prompt for a directory (must exist unless specfied as '$nocheckval' option)
# If fourth argument is true, the return "" if the default was accepted (user
# hit return or just whitespace).  By specifying 'true' as the fourth argument,
# the caller can determine the difference between the user just hitting enter
# (to accept the default), and typing the default value.
direxists_prompt() {
    local promptstr=$1; shift;
    local defstr=$1; shift;
    local nocheckval=$1; shift;
    local b_nousedefault=${1:-false}

    local ans;
    read -p "$promptstr [$defstr]: " ans;
    logonly "$ans"
    [[ -z "$ans" ]] && ! $b_nousedefault && ans="$defstr";
    local ans_withdef=$ans;
    [[ -z "$ans_withdef" ]] && ans_withdef="$defstr";

    local sp="  ";
    if [[ $promptstr =~ ^([[:space:]]+) ]] ; then
        sp="${sp}${BASH_REMATCH[1]}"
    fi

    if [[ x"$ans_withdef" != x"$nocheckval" ]] ; then
        # This answer not an exception to the rule, check the dir exists
        if [[ ! -e "$ans_withdef" ]] ; then
            echo "${sp}Invalid answer, directory '$ans_withdef' does not exist." 1>&2
            ans=$(direxists_prompt "$promptstr" "$defstr" "$nocheckval" "$b_nousedefault")
        elif [[ ! -d "$ans_withdef" ]] ; then
            echo "${sp}Invalid answer, '$ans_withdef' exists, but is not a directory." 1>&2
            ans=$(direxists_prompt "$promptstr" "$defstr" "$nocheckval" "$b_nousedefault")
        fi
    fi

    echo "$ans"
}

direxists_check() {
    local dir=$1; shift;

    if [[ ! -e "$dir" ]] ; then
        echo "Error!  Directory '$dir' does not exist."
        abort;
    elif [[ ! -d "$dir" ]] ; then
        echo "Error!  Directory '$dir' is not a directory."
        abort;
    fi
}

# Prompt for an absolute dir, must either exist or look like an absolute dir
# and contain no spaces.
# If third argument is true, the return "" if the default was accepted (user
# hit return or just whitespace).  By specifying 'true' as the third argument,
# the caller can determine the difference between the user just hitting enter
# (to accept the default), and typing the default value.
dirabs_prompt() {
    local promptstr=$1; shift;
    local defstr=$1; shift;
    local b_nousedefault=${1:-false}

    local ans;
    read -p "$promptstr [$defstr]: " ans;
    logonly "$ans"
    [[ -z "$ans" ]] && ! $b_nousedefault && ans="$defstr";
    local ans_withdef=$ans;
    [[ -z "$ans_withdef" ]] && ans_withdef="$defstr";

    if [[ ! -e "$ans_withdef" ]] ; then
        # The directory doesn't exist, do a sanity check to make sure it looks
        # like an absolute directory and does not contain spaces.
        if [[ ! $ans_withdef =~ ^/ ]] ; then
            echo "Invalid answer, directory '$ans_withdef' must be an absolute path." 1>&2
            ans=$(dirabs_prompt "$promptstr" "$defstr" "$b_nousedefault" )
        fi
        if [[ $ans_withdef =~ [[:space:]] ]] ; then
            echo "Invalid answer, '$ans_withdef' must not contains whitespace." 1>&2
            ans=$(dirabs_prompt "$promptstr" "$defstr" "$b_nousedefault" )
        fi
    fi

    echo "$ans"
}


# -- config subroutines
legacy_configmod() {
    # FIXME: +++ Remove this after all sites upgraded to >7.0.1 (start) +++
    sed -e 's/^[[:space:]]*jmsconfig__nworkers=/smrtlink__nworkers=/' \
        -e 's/^[[:space:]]*jmsselect__jmstype=/computecfg_00__jmstype=/' \
        -e 's/^[[:space:]]*jmsconfig__nproc=/computecfg_00__nproc=/' \
        -e 's/^[[:space:]]*jmsconfig__total_nproc=/computecfg_00__total_nproc=/' \
        -e 's/^[[:space:]]*jmsconfig__chunking='\''false'\''/computecfg_00__maxchunks='\''1'\''/' \
        -e 's/^[[:space:]]*computecfg_\([0-9][0-9]\)__chunking='\''false'\''/computecfg_\1__maxchunks='\''1'\''/' \
        -e 's/^[[:space:]]*jmsconfig__maxchunks=/computecfg_00__maxchunks=/' \
        -e 's/^[[:space:]]*sge__sgeroot=/computecfg_00__sge_sgeroot=/' \
        -e 's/^[[:space:]]*sge__sgecell=/computecfg_00__sge_sgecell=/' \
        -e 's/^[[:space:]]*sge__sgebindir=/computecfg_00__sge_bindir=/' \
        -e 's/^[[:space:]]*sge__sgequeue=/computecfg_00__sge_queue=/' \
        -e 's/^[[:space:]]*sge__sgepe=/computecfg_00__sge_pe=/' \
        -e 's/^[[:space:]]*sge__sgestartargs=/computecfg_00__sge_startargs=/' \
        -e 's/^[[:space:]]*sge__use_settings_file=/computecfg_00__sge_use_settings_file=/' \
        -e 's/^[[:space:]]*lsf__lsfbindir=/computecfg_00__lsf_bindir=/' \
        -e 's/^[[:space:]]*lsf__lsfqueue=/computecfg_00__lsf_queue=/' \
        -e 's/^[[:space:]]*lsf__lsfstartargs=/computecfg_00__lsf_startargs=/' \
        -e 's/^[[:space:]]*pbs__pbsbindir=/computecfg_00__pbs_bindir=/' \
        -e 's/^[[:space:]]*pbs__pbsqueue=/computecfg_00__pbs_queue=/' \
        -e 's/^[[:space:]]*pbs__pbsstartargs=/computecfg_00__pbs_startargs=/' \
        -e 's/^[[:space:]]*slurm__slurmbindir=/computecfg_00__slurm_bindir=/' \
        -e 's/^[[:space:]]*slurm__slurmpartition=/computecfg_00__slurm_partition=/' \
        -e 's/^[[:space:]]*slurm__slurmprestartargs=/computecfg_00__slurm_prestartargs=/' \
        -e 's/^[[:space:]]*slurm__slurmstartargs=/computecfg_00__slurm_startargs=/' \
        -e 's/^[[:space:]]*otherjms__otherjmsname=/computecfg_00__otherjms_name=/' \
        -e 's/^[[:space:]]*otherjms__otherjmsbindir=/computecfg_00__otherjms_bindir=/' \
        -e 's/^[[:space:]]*otherjms__otherjmsqueue=/computecfg_00__otherjms_queue=/' \
        -e 's/^[[:space:]]*otherjms__otherjmsstartargs=/computecfg_00__otherjms_startargs=/' \
        -e 's/^[[:space:]]*customjms__customjmsname=/computecfg_00__customjms_name=/' \
        -e 's/^[[:space:]]*customjms__customjmsbindir=/computecfg_00__customjms_bindir=/' \
        -e 's/^[[:space:]]*customjms__customjmsqueue=/computecfg_00__customjms_queue=/' \
        -e 's/^[[:space:]]*customjms__customjmsstartargs=/computecfg_00__customjms_startargs=/'
    # FIXME: --- Remove this after all sites upgraded to >7.0.1 (end) ---
}

read_config() {
    local infile=$1; shift;

    local groupname
    local re;
    local confignames_re="";
    for groupname in "${ga_cfg_groupnames[@]}"; do
        if [[ $groupname =~ _[0-9]+: ]] ; then
            continue;
        fi
        re=${groupname}
        re="${re//_NN:/_[0-9]\\+:}"
        re="${re//:/__}"
        confignames_re+="${re}\\|"
    done
    confignames_re="${confignames_re%\\|}"

    # Just get the assignments for variables that we recognize
    # As far as quoting goes, we will assume that all values are assigned
    # with single quotes and do not contain any single quotes within the
    # value (only double quotes are allowed in the value).
    local config_assigns;
    config_assigns=$(cat "$infile" | legacy_configmod | sed -n -e 's/^[[:space:]]*//' -e 's/[[:space:]]*#.*//' -e "/^\(${confignames_re}\)='\([^']*\)'/ { s/__/:/; s/\([^:]\+\):\([^=]\+\)='\([^']*\)'.*/assign_cfg_configfileopt '\1' '\2' '\3'/p; }")

    # Eval the assignments to set the globals
    g__assign_cfg_configfileopt___cur_configfile="$infile"
    eval "$config_assigns"
}

write_config_group() {
    local group=$1; shift;
    local outfile_new=$1; shift;

    echo "" >> "$outfile_new"
    echo "# '$group' settings" >> "$outfile_new"

    local i;
    for ((i=0; i < ${#ga_cfg_groupnames[@]} ; i++)); do
        local lgroup="${ga_cfg_groupnames[$i]%%:*}"

        if [[ x"$lgroup" != x"$group" ]] ; then
            continue;
        fi

        local name="${ga_cfg_groupnames[$i]#*:}"
        local configname="${group}__${name}";

        local val;
        local usedefault;
        local optional;
        local specialcase;
        # The caching of the groupname index in get_cfg_index() does not
        # work, since globals are being set inside $() process subsitutions.
        # So instead of these, use the index directly to improve performance.
        #    val=$(get_cfg_val "$group" "$name");
        #    usedefault=$(get_cfg_usedefault "$group" "$name");
        #    optional=$(get_cfg_optional "$group" "$name");
        #    specialcase=$(get_cfg_specialcase "$group" "$name");
        val=$(get_cfg_val_fromindex "$i");
        usedefault=$(get_cfg_usedefault_fromindex "$i");
        optional=$(get_cfg_optional_fromindex "$i");
        specialcase=$(get_cfg_specialcase_fromindex "$i");

        if [[ $specialcase =~ :nostore: ]] ; then
            # Deprecated configuration, do not write it
            :
        elif $usedefault; then
            if $optional; then
                # Optional value is at the default value, don't write it
                :
            else
                # Write the default value
                local splen;
                splen=${g_cfg_printstr_splen[$i]};
                splen=${splen##*,}
                printf "%-${splen}s  # Current val: '$val'\n" "$configname='__USE_DEFAULT__';" >> "$outfile_new"
            fi
        elif [[ ! -z "$val" ]] || isset_cfgval "$val"; then
            # Only allow single quoted values (which can contain double
            # quotes, but not single quoted).
            # Translate any single quotes in the value to double quotes
            val=${val//\'/\"}
            echo "$configname='$val';" >> "$outfile_new"
        else
            # Value is not set
            if $optional; then
                # Optional value is not set, don't write it
                :
            else
                # Non-optional value is not set, issue an error
                minterror "write_config(): Configuration variable '$configname' not set."
            fi
        fi
    done
}

write_config() {
    local outfile=$1; shift;

    local outfile_new="${outfile}.new";

    rm -f "$outfile_new";

    echo "# smrtlink config" >> "$outfile_new"

    local i;
    if [[ ! -z "${ga_cfgg_groups[@]+${ga_cfgg_groups[@]}}" ]]; then
        for ((i=0; i < ${#ga_cfgg_groups[@]} ; i++)); do
            if ${g_cfgg_listgroup[$i]}; then
                # Don't write listgroups, they will get written when pointed
                # to by a basegroup
                :
            elif ${g_cfgg_basegroup[$i]}; then
                # This is basegroup, write out all the listgroups this
                # this basegroup points to (if they are tagged for saving
                # the config)
                local j;
                for j in ${g_cfgg_basegroup_listidxs[$i]}; do
                    if ${g_cfgg_savegroup[$j]}; then
                        write_config_group "${ga_cfgg_groups[$j]}" "$outfile_new"
                    fi
                done
            else
                # This is a normal group (not a basegroup or a listgroup)
                # Write it if it is tagged for saving the config.
                if ${g_cfgg_savegroup[$i]}; then
                    write_config_group "${ga_cfgg_groups[$i]}" "$outfile_new"
                fi
            fi
        done
    fi

    rm -f "$outfile";
    mv "$outfile_new" "$outfile";
}

# Override smrtlink config file with actual settings that may have
# been modified by the user by hand (like the datadir/tempdir symlinks)
override_config_with_detected_settings() {
    # Read the dir symlinks and use those settings instead of what is defined
    # in the config files (the symlink holds the actual value)
    local curtarg;
    local modval;

    modval=""
    if [[ -h "$g_jobsroot_dirlink" ]] ; then
        local jobsroot_dirlink_target;
        jobsroot_dirlink_target=$(readlink "$g_jobsroot_dirlink")
        if [[ ! $jobsroot_dirlink_target =~ ^/ ]] ; then
            local datroot_dirlink_dir;
            jobsroot_dirlink_dir=$(dirname "$g_jobsroot_dirlink");
            jobsroot_dirlink_target="$jobsroot_dirlink_dir/$jobsroot_dirlink_target"
        fi
        curtarg=$(get_cfg_val "datadirs" "jobsroot_dir")
        if [[ x"$curtarg" != x"$jobsroot_dirlink_target" ]] ; then
            modval="$jobsroot_dirlink_target";
        fi
    elif [[ -d "$g_jobsroot_dirlink" ]] ; then
        curtarg=$(get_cfg_val "datadirs" "jobsroot_dir")
        if [[ x"$curtarg" != x"$g_jobsroot_dirlink" ]] ; then
            modval="$g_jobsroot_dirlink";
        fi
    fi
    if [[ ! -z "$modval" ]] ; then
        set_cfg_val "datadirs" "jobsroot_dir" "$modval" \
            "hand modified in previous install" false;
    fi

    modval=""
    if [[ -h "$g_tmpdir_dirlink" ]] ; then
        local tmpdir_dirlink_target;
        tmpdir_dirlink_target=$(readlink "$g_tmpdir_dirlink")
        curtarg=$(get_cfg_val "datadirs" "tmpdir_dir")
        if [[ x"$curtarg" != x"$tmpdir_dirlink_target" ]] ; then
            modval="$tmpdir_dirlink_target";
        fi
    elif [[ -d "$g_tmpdir_dirlink" ]] ; then
        curtarg=$(get_cfg_val "datadirs" "tmpdir_dir")
        if [[ x"$curtarg" != x"$g_tmpdir_dirlink" ]] ; then
            modval="$g_tmpdir_dirlink";
        fi
    fi
    if [[ ! -z "$modval" ]] ; then
        set_cfg_val "datadirs" "tmpdir_dir" "$modval" \
            "hand modified in previous install" false;
    fi

    modval=""
    if [[ -h "$g_dbdatadir_dirlink" ]] ; then
        local dbdatadir_dirlink_target;
        dbdatadir_dirlink_target=$(readlink "$g_dbdatadir_dirlink")
        if [[ ! $dbdatadir_dirlink_target =~ ^/ ]] ; then
            local datroot_dirlink_dir;
            dbdatadir_dirlink_dir=$(dirname "$g_dbdatadir_dirlink");
            dbdatadir_dirlink_target="$dbdatadir_dirlink_dir/$dbdatadir_dirlink_target"
        fi
        curtarg=$(get_cfg_val "database" "dbdatadir")
        if [[ x"$curtarg" != x"$dbdatadir_dirlink_target" ]] ; then
            modval="$dbdatadir_dirlink_target";
        fi
    elif [[ -d "$g_dbdatadir_dirlink" ]] ; then
        curtarg=$(get_cfg_val "datadirs" "dbdatadir_dir")
        if [[ x"$curtarg" != x"$g_dbdatadir_dirlink" ]] ; then
            modval="$g_dbdatadir_dirlink";
        fi
    fi
    if [[ ! -z "$modval" ]] ; then
        set_cfg_val "database" "dbdatadir" "$modval" \
            "hand modified in previous install" false;
    fi


    # FIXME: do we need to read other values from the locations they are
    #        actually used (like reading nproc from the config files where
    #        it got substituted)?  If we allow/expect hand modification of
    #        those, then we should.  And if we allow/expect no hand
    #        modification of those files (and all modifications to go through
    #        smrtlink.config), then it should not be necessary.
}


# Read configfile if specified on the command line with --configfile option
read_cmdline_configfile() {
    if [[ -z "$opt_configfile" ]] ; then
        return 0
    fi

    logonly "Reading config file: $opt_configfile"
    logonly "Reading config file (abspath): $(readlink -f "$opt_configfile")"
    echo "Reading smrtlink config file specified on command line..."
    echo ""
    read_config "$opt_configfile";

}

read_smrtlink_configfile() {
    if $opt_ignore_configfile; then
        # Skip if we are asked to ignore it
        return 0;
    elif [[ ! -z "$opt_configfile" ]] ; then
        # Skip if we already read in a config file specified on the cmdline
        return 0
    fi

    if [[ -r "$g_smrtlink_configfile" ]] ; then
        logonly "Reading config file: $g_smrtlink_configfile"
        logonly "Reading config file (abspath): $(readlink -f "$g_smrtlink_configfile")"
        echo "Reading existing smrtlink config file..."
        echo ""
        read_config "$g_smrtlink_configfile";
    else
        if $opt_upgrade || $opt_reconfig || $opt_restore; then
            echo "Reading existing smrtlink config file..."
            echo "  Could not find existing config file: $g_smrtlink_configfile"
            echo "  Proceeding..."
        fi
    fi
}

write_smrtlink_config() {
    # Write the config file to a tmp file, once successful move it to the
    # real config file location.  The real config file should only get modified
    # if everything worked successfully.
    local configfile=$g_smrtlink_configfile;

    # tmp file
    local configfile_tmp="${configfile}.tmp";

    # Make the config directory if needed
    mkdir -p "$(dirname "$configfile_tmp")"

    # remove the tmpfile if it exists
    rm -f "$configfile_tmp";

    # write the config file to the tmpfile
    write_config "$configfile_tmp";

    # tmp config file written successfully, move it to the actual config file
    mv -f "$configfile_tmp" "$configfile";
}

# -- config functions

isset_cfgval() {
    local val=$1; shift;

    if [[ x"$val" == x"__UNSET__" ]]; then
        return 1;
    fi
    return 0;
}

add_cfg() {
    local i;

    local group="__UNSET__";
    local name="__UNSET__";
    local datatype="__UNSET__";
    local optional="__UNSET__";
    local default="__UNSET__";
    local optstr_alts="__UNSET__";
    local optstr_typedstr="__UNSET__";
    local printstr_splen="__UNSET__";
    local specialcase="__UNSET__";
    local usagestr="__UNSET__";
    local promptstr="__UNSET__";
    local usingstr="__UNSET__";
    local settingname="__UNSET__";

    local badcfg=false;
    local badval=false;
    for i in ${1+"$@"} ; do
        if [[ $i =~ ^group[[:space:]]*:[[:space:]]*(.*) ]] ; then
            group=${BASH_REMATCH[1]};
        elif [[ $i =~ ^name[[:space:]]*:[[:space:]]*(.*) ]] ; then
            name=${BASH_REMATCH[1]};
        elif [[ $i =~ ^datatype[[:space:]]*:[[:space:]]*(.*) ]] ; then
            datatype=${BASH_REMATCH[1]};
            if ! valid_datatype "$datatype"; then
                badval=true;
            fi
        elif [[ $i =~ ^optional[[:space:]]*:[[:space:]]*(.*) ]] ; then
            optional=${BASH_REMATCH[1]};
            if [[ ! $optional =~ ^(true|false)$ ]] ; then
                badval=true;
            fi
        elif [[ $i =~ ^default[[:space:]]*:[[:space:]]*(.*) ]] ; then
            default=${BASH_REMATCH[1]};
        elif [[ $i =~ ^optstr_alt[[:space:]]*:[[:space:]]*(.*) ]] ; then
            [[ x"$optstr_alts" == x"__UNSET__" ]] && optstr_alts="";
            optstr_alts+=" ${BASH_REMATCH[1]}";
            optstr_alts=${optstr_alts# }
        elif [[ $i =~ ^optstr_typedstr[[:space:]]*:[[:space:]]*(.*) ]] ; then
            optstr_typedstr=${BASH_REMATCH[1]};
        elif [[ $i =~ ^printstr_splen[[:space:]]*:[[:space:]]*(.*) ]] ; then
            printstr_splen=${BASH_REMATCH[1]};
        elif [[ $i =~ ^specialcase[[:space:]]*:[[:space:]]*(.*) ]] ; then
            specialcase=${BASH_REMATCH[1]};
        elif [[ $i =~ ^usagestr[[:space:]]*:[[:space:]]*(.*) ]] ; then
            usagestr=${BASH_REMATCH[1]};
        elif [[ $i =~ ^promptstr[[:space:]]*:[[:space:]]*(.*) ]] ; then
            promptstr=${BASH_REMATCH[1]};
        elif [[ $i =~ ^usingstr[[:space:]]*:[[:space:]]*(.*) ]] ; then
            usingstr=${BASH_REMATCH[1]};
        elif [[ $i =~ ^settingname[[:space:]]*:[[:space:]]*(.*) ]] ; then
            settingname=${BASH_REMATCH[1]};
        else
            badcfg=true;
        fi
        if $badcfg || $badval; then
            local str="$group:$name";
            if [[ x"$str" == x":" ]] ; then
                str=""
            else
                str=" for '$str'"
            fi
            if $badcfg; then
                minterror "add_cfg(): Unrecognized cfg setting${str} ($i)"
            fi
            if $badval; then
                minterror "add_cfg(): Illegal cfg setting value${str} ($i)"
            fi
        fi
    done

    [[ x"$group" == x"__UNSET__" ]] && minterror "add_cfg(): 'group' unspecified: "${1+"$@"};
    [[ x"$name" == x"__UNSET__" ]] && minterror "add_cfg(): 'name' input unset: "${1+"$@"};
    [[ x"$datatype" == x"__UNSET__" ]] && minterror "add_cfg(): 'datatype' unspecified: "${1+"$@"};
    [[ x"$optional" == x"__UNSET__" ]] && minterror "add_cfg(): 'optional' unspecified: "${1+"$@"};
    [[ x"$default" == x"__UNSET__" ]] && minterror "add_cfg(): 'default' unspecified: "${1+"$@"};
    [[ x"$printstr_splen" == x"__UNSET__" ]] && minterror "add_cfg(): 'printstr_splen' unspecified: "${1+"$@"};
    [[ ! $printstr_splen =~ ^[0-9]+(,[0-9]+)?(,[0-9]+)?$ ]] && minterror "add_cfg(): 'printstr_splen' must be comma-separated numeric: "${1+"$@"};
    # [[ x"$specialcase" == x"__UNSET__" ]] && minterror "add_cfg(): 'specialcase' unspecified: "${1+"$@"};
    [[ x"$usagestr" == x"__UNSET__" ]] && minterror "add_cfg(): 'usagestr' unspecified: "${1+"$@"};
    [[ x"$promptstr" == x"__UNSET__" ]] && minterror "add_cfg(): 'promptstr' unspecified: "${1+"$@"};
    [[ x"$usingstr" == x"__UNSET__" ]] && minterror "add_cfg(): 'usingstr' unspecified: "${1+"$@"};
    [[ x"$settingname" == x"__UNSET__" ]] && minterror "add_cfg(): 'settingname' unspecified: "${1+"$@"};

    # optstr_alts and specialcase are optional
    [[ x"$optstr_alts" == x"__UNSET__" ]] && optstr_alts="";
    [[ x"$specialcase" == x"__UNSET__" ]] && specialcase="";


    # Sanity check the group (make sure contains no colos or double undercores)
    if [[ $group =~ (__|:) ]] ; then
        minterror "add_cfg(): group ($group) contains double characters (${BASH_REMATCH[1]})"
    fi

    # Add to groups if needed
    local group_found=false;
    if [[ ! -z "${ga_cfgg_groups[@]+${ga_cfgg_groups[@]}}" ]]; then
        for ((i=0; i < ${#ga_cfgg_groups[@]} ; i++)); do
            if [[ x"${ga_cfgg_groups[$i]}" == x"$group" ]] ; then
                group_found=true;

                # Sanity checking...
                # While we are here, check to see if the group looks like a
                # basegroup (ends in _NN and does not contain double
                # underscores), and make sure it is already defined as a
                # basegroup.  That way we can assume that is is a basegroup,
                # by just looking at the pattern (without looking up if it
                # actually defined as a basegroup).  If this ever turns out
                # not to be true, we should check all our assumptions that
                # groups matching <group>_NN are all basegroups.
                if  [[ $group =~ _NN$ ]];  then
                    if ! ${g_cfgg_basegroup[$i]}; then
                        minterror "add_cfg(): group ($group) matches the basegroup pattern, but is not defined as a basegroup"
                    fi
                else
                    if ${g_cfgg_basegroup[$i]}; then
                        minterror "add_cfg(): group ($group) defined as a basegroup, but does not match the basegroup pattern"
                    fi
                fi

                break;
            fi
        done
    fi
    if ! $group_found; then
        # Add this as a normal group (not a basegroup or listgroup).
        # Basegroups will be added by add_cfg_basegroup(), before calling
        # add_cfg().  If this is a group that matched the format of a
        # basegroup, an error would have been caught above.
        ga_cfgg_groups+=( "$group" );
        g_cfgg_basegroup+=( false );
        g_cfgg_basegroup_listidxs+=( "" );
        g_cfgg_listgroup+=( false );
        g_cfgg_savegroup+=( true );
        g_cfgg_ignoreconfigfile+=( false );
    fi

    # Sanity checks
    #   - Check if config already exists (issue error if it does)
    #   - Check to see if alt optstrs already exist
    local altopt;
    local optstr;
    local optstr_alts;
    if [[ ! -z "${ga_cfg_groupnames[@]+${ga_cfg_groupnames[@]}}" ]]; then
        for ((i=0; i < ${#ga_cfg_groupnames[@]} ; i++)); do
            # Check if already exists
            if [[ x"${ga_cfg_groupnames[$i]}" == x"$group:$name" ]] ; then
                minterror "add_cfg(): groupname ($group:$name) already defined";
            fi

            # Check for duplicate options
            for altopt in $optstr_alts; do
                optstr=${g_cfg_optstr[$i]}
                optstr_alt=${g_cfg_optstr_alts[$i]}

                if [[ x"${optstr//-NN/}" == x"${altopt//-NN/}" ]] ; then
                    minterror "add_cfg(): optstr_alt ($altopt) for '$group:$name' matches existing optstr for '${ga_cfg_groupnames[$i]}'";
                fi
                if [[ \ ${optstr_alt//-NN/}\  =~ \ ${altopt//-NN/}\  ]] ; then
                    minterror "add_cfg(): optstr_alt ($altopt) for '$group:$name' matches existing option in optstr_alt (${g_cfg_optstr_alts[$i]}) for '${ga_cfg_groupnames[$i]}'";
                fi
            done
        done
    fi

    # Add the config (we know the config does not exist at this point)
    # This could be a normal config or a basegroup config, but should be
    # treated the same.
    ga_cfg_groupnames+=( "$group:$name" )

    # All the rest of the arrays will match the index of ga_cfg_groupnames
    local index;
    index=$(( ${#ga_cfg_groupnames[@]} - 1));

    g_cfg_datatype[$index]="$datatype";
    g_cfg_default[$index]="$default";
    g_cfg_optional[$index]="$optional";
    g_cfg_optstr_alts[$index]="$optstr_alts";
    if [[ -z "$optstr_typedstr" ]] ; then
        g_cfg_optstr_typedstr[$index]=$(get_typedstr "$datatype")
    else
        g_cfg_optstr_typedstr[$index]=$optstr_typedstr
    fi
    g_cfg_printstr_splen[$index]="$printstr_splen";

    g_cfg_specialcase[$index]="$specialcase";

    g_cfg_usagestr[$index]="$usagestr";
    g_cfg_promptstr[$index]="$promptstr";
    g_cfg_usingstr[$index]="$usingstr";
    g_cfg_settingname[$index]="$settingname";

    # Set the primary option string
    g_cfg_optstr[$index]="--${group//_/-}-${name//_/-}"

    # The 'curval' of "__UNSET__" means that it has not been overridden on the
    # command line, commmand line config file, smrtlink config file from
    # a previous run, or specified interactively.   It will be treated as
    # the default value (same as if set to "__DEFAULT__").  For that case,
    # 'usedefault' should be set to 'true' and 'srcstr' should give an
    # indication to how the default was specified (e.g. 'default' for the
    # program default)
    g_cfg_curval[$index]="__UNSET__";
    g_cfg_srcstr[$index]="default";
    g_cfg_usedefault[$index]=true;

    # Set the basegroup to empty string for normal (non-list) config groups
    g_cfg_basegroup[$index]="";
}

add_cfg_basegroup() {
    local basegroup=$1; shift;

    # Sanity check the group (make sure contains no colos or double undercores)
    if [[ $basegroup =~ (__|:) ]] ; then
        minterror "add_cfg_basegroup(): basegroup ($basegroup) contains illegal characters (${BASH_REMATCH[1]})"
    fi

    # Check to see if the basegroup matches the expected _NN format
    if [[ ! $basegroup =~ _NN$ ]]; then
        minterror "add_cfg_basegroup(): Invalid basegroup format ($basegroup), expected <group>_NN"
    fi

    local found=false;
    local i=0;
    if [[ ! -z "${ga_cfgg_groups[@]+${ga_cfgg_groups[@]}}" ]]; then
        for ((i=0; i < ${#ga_cfgg_groups[@]} ; i++)); do
            if [[ x"$basegroup" == ${ga_cfgg_groups[$i]} ]] ; then
                found=true;
                g_cfgg_basegroup[$i]=true;
                g_cfgg_savegroup[$i]=false;
            fi
        done
    fi
    if ! $found ; then
        # Add this as a basegroup
        ga_cfgg_groups+=( "$basegroup" );
        g_cfgg_basegroup+=( true );
        g_cfgg_basegroup_listidxs+=( "" );
        g_cfgg_listgroup+=( false );
        g_cfgg_savegroup+=( false );
        g_cfgg_ignoreconfigfile+=( false );
    fi
}

trim_listnum() {
    local input=$1; shift;

    if [[ ! 0$input =~ ^0*([0-9]*[0-9])$ ]] ; then
        minterror "trim_listnum(): input ($input) not digits or empty string"
    fi
    echo "${BASH_REMATCH[1]}"
}

trim_intnum() {
    local input=$1; shift;

    local retval=$input;
    if [[ $input =~ ^([-\+]?)0*([0-9]*[0-9])$ ]] ; then
        retval="${BASH_REMATCH[1]}${BASH_REMATCH[2]}";
    else
        minterror "trim_intnum(): input ($input) not a signed int"
    fi
    echo "$retval"
}

get_listgroupnum_from_listnum() {
    local listnum=$1; shift;

    local listgroupnum;
    listnum=$(trim_listnum "$listnum")
    if [[ $listnum -lt 10 ]] ; then
        listgroupnum="0${listnum}";
    else
        listgroupnum="${listnum}";
    fi

    echo "$listgroupnum";

}

get_listgroup_from_listnum() {
    local basegroup=$1; shift;
    local listnum=$1; shift;

    local listgroupnum;
    listgroupnum=$(get_listgroupnum_from_listnum "$listnum")

    local listgroup="${basegroup%_NN}_${listgroupnum}";

    echo "$listgroup";
}

add_cfg_listgroup() {
    local optarg="";
    if [[ $1 =~ ^-- ]] ; then optarg=$1; shift; fi
    local basegroup=$1; shift;
    local group=$1; shift;

    # Sanity check the group and basegroup (make sure contains no colons or
    # double undercores)
    if [[ $group =~ (__|:) ]] ; then
        minterror "add_cfg_listgroup(): group ($group) contains double characters (${BASH_REMATCH[1]})"
    fi
    if [[ $basegroup =~ (__|:) ]] ; then
        minterror "add_cfg_listgroup(): basegroup ($baseagroup) contains double characters (${BASH_REMATCH[1]})"
    elif [[ ! $basegroup =~ _NN$ ]] ; then
        # basegroup does not match the expected _NN format
        minterror "add_cfg_listgroup(): Invalid basegroup format ($basegroup), expected <group>_NN"
    fi

    # Sanity checks
    if [[ -z "${ga_cfg_groupnames[@]+${ga_cfg_groupnames[@]}}" ]]; then
        minterror "add_cfg_listgroup(): No entries in ga_cfg_groupnames[@]"
    fi
    if [[ -z "${ga_cfgg_groups[@]+${ga_cfgg_groups[@]}}" ]]; then
        minterror "add_cfg_listgroup(): No entries in ga_cfgg_group[@]"
    fi

    # Determine if this is a provisional group (not officially defined yet,
    # i.e. only detected on the command line or configfile)
    local provisional_group=false;
    if [[ ! -z "$optarg" ]] ; then
        if [[ x"$optarg" == x"--cmdlineopt" ]] ; then
            provisional_group=true;
        elif [[ x"$optarg" == x"--configfileopt" ]] ; then
            provisional_group=true;
        else
            merror "add_cfg_listgroup(): invalid optarg ($optarg)"
        fi
    fi

    local group_listnum;
    # Check to see if the group matches the expected regex, and compute
    # the group_listnum
    if [[ $group =~ ^${basegroup%_NN}_([0-9]+)$ ]] ; then
        group_listnum=$(trim_listnum "${BASH_REMATCH[1]}")
    else
        minterror "add_cfg_listgroup(): Invalid group format ($group), does not match the regex for a listgroup."
    fi

    # Check that the basegroup is already defined as a group and is defined
    # as a basegroup
    local i;
    local basegroup_found=false;
    local basegroup_index;
    local listgroup_found=false;
    local listgroup_index;
    for ((i=0; i < ${#ga_cfgg_groups[@]} ; i++)); do
        if [[ x"${ga_cfgg_groups[$i]}" == x"$basegroup" ]] ; then
            if ! ${g_cfgg_basegroup[$i]}; then
                minterror "add_cfg_listgroup(): basegroup ($basegroup) is defined as a group, but not a basegroup"
            fi
            if $basegroup_found; then
                minterror "add_cfg_listgroup(): multiple instances of basegroup ($basegroup) defined"
            fi
            basegroup_found=true;
            basegroup_index=$i;
        elif [[ x"${ga_cfgg_groups[$i]}" == x"$group" ]] ; then
            if ! ${g_cfgg_listgroup[$i]}; then
                minterror "add_cfg_listgroup(): group ($group) is defined as a group, but not a listgroup"
            fi
            if $listgroup_found; then
                minterror "add_cfg_listgroup(): multiple instances of listgroup ($group) defined"
            fi
            listgroup_found=true;
            listgroup_index=$i;
        fi
    done
    if ! $basegroup_found; then
        minterror "add_cfg_listgroup(): basegroup ($basegroup) is not defined as an existing group"
    fi


    if $listgroup_found; then
        # Update the savegroup flag.
        # If already set to true, leave as it it (already an official group)
        if ! ${g_cfgg_savegroup[$listgroup_index]} ; then
            if ! $provisional_group; then
                g_cfgg_savegroup[$listgroup_index]=true;
            fi
        fi
    else
        # Add the new listgroup
        ga_cfgg_groups+=( "$group" );
        g_cfgg_basegroup+=( false );
        g_cfgg_basegroup_listidxs+=( "" );
        g_cfgg_listgroup+=( true );
        if $provisional_group; then
            g_cfgg_savegroup+=( true );
        else
            g_cfgg_savegroup+=( true );
        fi
        g_cfgg_ignoreconfigfile+=( false );

        # Update the listgroup indexes in the basegroup (make sure that the
        # group names are sorted by group_listnum, i.e. <group>_01 will
        # always be after <group>_00)
        listgroup_index=$(( ${#ga_cfgg_groups[@]} - 1 ))
        local new_idxlist=""
        local lgroup_idx;
        local lgroup;
        local lgroup_listnum;
        local inserted=false;
        for lgroup_idx in ${g_cfgg_basegroup_listidxs[$basegroup_index]+${g_cfgg_basegroup_listidxs[$basegroup_index]}}; do
            lgroup=${ga_cfgg_groups[$lgroup_idx]};
            lgroup_listnum=$(trim_listnum "${lgroup##*_}");
            if $inserted; then
                new_idxlist+=" $lgroup_idx";
            elif [[ $lgroup_listnum -gt $group_listnum ]] ; then
                new_idxlist+=" $listgroup_index $lgroup_idx";
                inserted=true;
            else
                new_idxlist+=" $lgroup_idx";
            fi
        done
        if ! $inserted; then
            new_idxlist+=" $listgroup_index";
        fi
        new_idxlist=${new_idxlist# }

        # Update the list string
        g_cfgg_basegroup_listidxs[$basegroup_index]=$new_idxlist;
    fi

    # Return if the mapping already exists
    if $listgroup_found; then
        return 0;
    fi

    # Copy all the items in the basegroup into the new group
    local found=false;
    local lgroup;
    local lname;
    local index;
    for ((i=0; i < ${#ga_cfg_groupnames[@]} ; i++)); do
        lgroup=${ga_cfg_groupnames[$i]}
        lgroup=${lgroup%%:*}


        if [[ x"$lgroup" == x"$basegroup" ]] ; then
            found=true;

            lname=${ga_cfg_groupnames[$i]}
            lname=${lname#*:}

            ga_cfg_groupnames+=( "$group:$lname" )
            index=$((${#ga_cfg_groupnames[@]} - 1));

            # Copy all the information from the base groupname (should not be
            # modified from when the base config for the list group was added)
            g_cfg_datatype[$index]=${g_cfg_datatype[$i]}
            g_cfg_default[$index]=${g_cfg_default[$i]}
            g_cfg_optional[$index]=${g_cfg_optional[$i]}

            local optstr;
            local optstr_alts;
            if [[ $group_listnum -eq 0 ]] ; then
                optstr=$( echo "${g_cfg_optstr[$i]}" | sed -e 's/-NN/(-0\+|)/g');
                optstr_alts=$( echo "${g_cfg_optstr_alts[$i]}" | sed -e 's/-NN/(-0\+|)/g');
            else
                optstr=$( echo "${g_cfg_optstr[$i]}" | sed -e "s/-NN/(-0*${group_listnum})/g");
                optstr_alts=$( echo "${g_cfg_optstr_alts[$i]}" | sed -e "s/-NN/(-0*${group_listnum})/g");
            fi

            g_cfg_optstr[$index]=$optstr;
            g_cfg_optstr_alts[$index]=$optstr_alts;
            g_cfg_optstr_typedstr[$index]=${g_cfg_optstr_typedstr[$i]}
            g_cfg_printstr_splen[$index]=${g_cfg_printstr_splen[$i]}
            g_cfg_specialcase[$index]=${g_cfg_specialcase[$i]}
            g_cfg_usagestr[$index]=${g_cfg_usagestr[$i]}
            g_cfg_promptstr[$index]=${g_cfg_promptstr[$i]}
            g_cfg_usingstr[$index]=${g_cfg_usingstr[$i]}
            g_cfg_settingname[$index]=${g_cfg_settingname[$i]}

            g_cfg_curval[$index]=${g_cfg_curval[$i]}
            g_cfg_srcstr[$index]=${g_cfg_srcstr[$i]}
            g_cfg_usedefault[$index]=${g_cfg_usedefault[$i]}

            g_cfg_basegroup[$index]=$basegroup;
        fi
    done

    if ! $found; then
        minterror "add_cfg_listgroup(): No items found in basegroup ($basegroup)"
    fi
}


init_cfg() {

    # 'install' config
    add_cfg "group: install" "name: user" \
        "datatype: nonempty-nodash" \
        "default: $g_runuser" \
        "optstr_alt:" \
        "optstr_typedstr:" \
        "printstr_splen: 30,30,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: __NO_CMDLINEOPT__" \
        "promptstr: __NO_PROMPT__" \
        "usingstr: Using install user" \
        "settingname: Install User"
    add_cfg "group: install" "name: group" \
        "datatype: nonempty-nodash" \
        "default: $g_rungroup" \
        "optstr_alt:" \
        "optstr_typedstr:" \
        "printstr_splen: 30,30,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: __NO_CMDLINEOPT__" \
        "promptstr: __NO_PROMPT__" \
        "usingstr: Using install group" \
        "settingname: Install Group"
    add_cfg "group: install" "name: sluuid" \
        "datatype: nonempty-nodash" \
        "default: __COMPUTED__" \
        "optstr_alt: --smrtlink-uuid --sluuid --uuid" \
        "optstr_typedstr:" \
        "printstr_splen: 30,30,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: smrtlink uuid" \
        "promptstr: __NO_PROMPT__" \
        "usingstr: Using smrtlink uuid" \
        "settingname: Install smrtlink uuid"

    # 'system' config
    add_cfg "group: system" "name: memtotal" \
        "datatype: memsize_gb" \
        "default:  __COMPUTED__" \
        "optstr_alt: --memtotal" \
        "optstr_typedstr: " \
        "printstr_splen: 30,20,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: skip memtotal computation, use supplied value" \
        "promptstr: __NO_PROMPT__" \
        "usingstr: __NO_USINGSTR__" \
        "settingname: Mem Total Size"

    # smrtlink
    # -- dns
    add_cfg "group: smrtlink" "name: dnsname" \
        "datatype: nonempty-nodash" \
        "default: " \
        "optstr_alt: --dnsname" \
        "optstr_typedstr: dnsname" \
        "printstr_splen: 30,30,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: dns name of the install machine" \
        "promptstr: Select the DNS name: " \
        "usingstr: Using dnsname" \
        "settingname: DNS Hostname"

    # Not implementing sharedfs_root yet:
    # add_cfg "group: install" "name: sharedfs_root" \
    #     "datatype: dir:abs" \
    #     "default: __COMPUTED__" \
    #     "optstr_alt: " \
    #     "optstr_typedstr: dir" \
    #     "printstr_splen: 33,33,44" \
    #     "optional: false" \
    #     "specialcase: " \
    #     "usagestr: shared file system root directory" \
    #     "promptstr: Specify the Shared File System Root Directory: " \
    #     "usingstr: Using Shared File System Root" \
    #     "settingname: Shared File System Root"

    # -- ports
    add_cfg "group: smrtlink" "name: gui_port" \
        "datatype: posint" \
        "default: 9090" \
        "optstr_alt:" \
        "optstr_typedstr: port" \
        "printstr_splen: 30,30,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: smrtlink gui http port" \
        "promptstr: Enter the SMRT Link GUI (http) port" \
        "usingstr: Using GUI port" \
        "settingname: GUI Port"
    add_cfg "group: smrtlink" "name: services_port" \
        "datatype: posint" \
        "default: __COMPUTED__" \
        "optstr_alt:" \
        "optstr_typedstr: port" \
        "printstr_splen: 30,30,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: smrtlink webservices port" \
        "promptstr: Enter the SMRT Link Services port" \
        "usingstr: Using services port" \
        "settingname: Services Port"

    # -- mem
    add_cfg "group: smrtlink" "name: services_minmem" \
        "datatype: memsize_mb" \
        "default: __COMPUTED__" \
        "optstr_alt:" \
        "optstr_typedstr:" \
        "printstr_splen: 30,30,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: smrtlink webservices min mem (in MiB)" \
        "promptstr: Enter the SMRT Link Services initial memory (in MB)" \
        "usingstr: Using services initial memory" \
        "settingname: Services initial memory"
    add_cfg "group: smrtlink" "name: services_maxmem" \
        "datatype: memsize_mb" \
        "default: __COMPUTED__" \
        "optstr_alt:" \
        "optstr_typedstr:" \
        "printstr_splen: 30,30,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: smrtlink webservices max mem (in MiB)" \
        "promptstr: Enter the SMRT Link Services maximum memory (in MB)" \
        "usingstr: Using services maximum memory" \
        "settingname: Services maximum memory"


    # mail notification
    add_cfg "group: smrtlink" "name: mail_host" \
        "datatype: str-nodash" \
        "default: " \
        "optstr_alt: --mail-host" \
        "optstr_typedstr: " \
        "printstr_splen: 30,30,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: notification mail host" \
        "promptstr: Enter the SMRT Link notification outgoing mail server host" \
        "usingstr: Using SMRT Link notification outgoing mail server host" \
        "settingname: SMRT Link notification outgoing mail server host"

    add_cfg "group: smrtlink" "name: mail_port" \
        "datatype: posint" \
        "default: 25" \
        "optstr_alt: --mail-port" \
        "optstr_typedstr: port" \
        "printstr_splen: 33,33,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: notification mail port" \
        "promptstr: Enter the SMRT Link notification mail port" \
        "usingstr: Using SMRT Link notification mail port" \
        "settingname: SMRT Link notification mail port"

    add_cfg "group: smrtlink" "name: mail_user" \
        "datatype: str-nodash" \
        "default: " \
        "optstr_alt: --mail-user" \
        "optstr_typedstr: " \
        "printstr_splen: 30,30,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: notification mail user" \
        "promptstr: Enter the SMRT Link notification mail user" \
        "usingstr: Using SMRT Link notification mail user" \
        "settingname: __NO_SETTINGNAME__"

    add_cfg "group: smrtlink" "name: mail_password" \
        "datatype: str" \
        "default: " \
        "optstr_alt: --mail-password" \
        "optstr_typedstr: " \
        "printstr_splen: 30,30,47" \
        "optional: false" \
        "specialcase: :prompt_noecho:" \
        "usagestr: notification mail password" \
        "promptstr: Enter the SMRT Link notification mail password" \
        "usingstr: Using SMRT Link notification mail password" \
        "settingname: __NO_SETTINGNAME__"

    add_cfg "group: smrtlink" "name: nworkers" \
        "datatype: posint" \
        "default: __COMPUTED__" \
        "optstr_alt: --nworkers" \
        "optstr_typedstr:" \
        "printstr_splen: 38,24,52" \
        "optional: false" \
        "specialcase: " \
        "usagestr: maximum number of simultaneous SMRT Analysis\njobs that can be run by the SMRT Link\nserver" \
        "promptstr: Enter the max number of workers 'NWORKERS'" \
        "usingstr: Using NWORKERS" \
        "settingname: NWORKERS"

    # Extended cell use enable
    add_cfg "group: smrtlink" "name: extended_cell_use_enable" \
        "datatype: boolval" \
        "default: false" \
        "optstr_alt: --extended-cell-use" \
        "optstr_typedstr: " \
        "printstr_splen: 30,30,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: __NO_CMDLINEOPT__" \
        "promptstr: __NO_PROMPT__" \
        "usingstr: __NO_USINGSTR__" \
        "settingname: __NO_SETTINGNAME__"

    # Remote URLs (Event Service and Update Service)
    add_cfg "group: remote" "name: eve_server_select" \
        "datatype: list:g_eve_server_selections" \
        "default: production" \
        "optstr_alt: " \
        "optstr_typedstr:" \
        "printstr_splen: 30,30,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: select eve server\n(one of: $g_eve_server_selections_str)" \
        "promptstr: __NO_PROMPT__" \
        "usingstr: Using Event Server Type" \
        "settingname: Event Server Type"
    add_cfg "group: remote" "name: eve_url" \
        "datatype: url_emptystr" \
        "default: " \
        "optstr_alt: --eve-url --smrtlink-event-url --event-server-url" \
        "optstr_typedstr:" \
        "printstr_splen: 30,30,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: Event Service troubleshooting url" \
        "promptstr: Enter the $g_eve_official_name URL" \
        "usingstr: Using '$g_eve_official_name'" \
        "settingname: Event Service URL"

    add_cfg "group: remote" "name: update_server_select" \
        "datatype: list:g_update_server_selections" \
        "default: production" \
        "optstr_alt: " \
        "optstr_typedstr:" \
        "printstr_splen: 30,30,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: select update server\n(one of: $g_update_server_selections_str)" \
        "promptstr: __NO_PROMPT__" \
        "usingstr: Using Update Server Type" \
        "settingname: Update Server Type"
    add_cfg "group: remote" "name: update_url" \
        "datatype: url_emptystr" \
        "default: " \
        "optstr_alt: --remoteurl --remote-bundle-url --update-server-url" \
        "optstr_typedstr: " \
        "printstr_splen: 30,30,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: Update Service URL" \
        "promptstr: Enter $g_update_official_name URL" \
        "usingstr: Using '$g_update_official_name URL'" \
        "settingname: Update Service URL"
    add_cfg "group: remote" "name: update_enable" \
        "datatype: boolval" \
        "default: true" \
        "optstr_alt: --enable-update" \
        "optstr_typedstr:" \
        "printstr_splen: 30,30,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: enable/disable update url" \
        "promptstr: Enable the '$g_update_official_name'?" \
        "usingstr: Using '$g_update_official_name' Enable" \
        "settingname: Update Service Enable"


    # 'cromwell' config
    add_cfg "group: cromwell" "name: port" \
        "datatype: posint" \
        "default: __COMPUTED__" \
        "optstr_alt:" \
        "optstr_typedstr: port" \
        "printstr_splen: 33,33,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: cromwell main webserver port" \
        "promptstr: Enter the Cromwell Server port" \
        "usingstr: Using Cromwell port" \
        "settingname: Cromwell port"

    # 'database' config
    add_cfg "group: database" "name: dbport" \
        "datatype: posint" \
        "default: __COMPUTED__" \
        "optstr_alt:" \
        "optstr_typedstr: port" \
        "printstr_splen: 33,33,47" \
        "optional: false" \
        "specialcase: " \
        "usagestr: smrtlink database port" \
        "promptstr: Enter the SMRT Link Database port" \
        "usingstr: Using SMRT Link Database port" \
        "settingname: SMRT Link Database port"

    add_cfg "group: database" "name: dbdatadir" \
        "datatype: dir" \
        "default: __COMPUTED__" \
        "optstr_alt: --dbdatadir" \
        "optstr_typedstr: dir" \
        "printstr_splen: 33,33,44" \
        "optional: false" \
        "specialcase: " \
        "usagestr: database store directory" \
        "promptstr: Enter the full path to the 'db_datadir' directory" \
        "usingstr: Using Database data dir" \
        "settingname: dbdatadir directory"

    # 'datadirs' config
    add_cfg "group: datadirs" "name: jobsroot_dir" \
        "datatype: dir" \
        "default: __COMPUTED__" \
        "optstr_alt: --jobsroot" \
        "optstr_typedstr: dir" \
        "printstr_splen: 33,33,44" \
        "optional: false" \
        "specialcase: " \
        "usagestr: jobs_root directory" \
        "promptstr: Enter the full path to the 'jobs_root' directory" \
        "usingstr: Using jobs_root" \
        "settingname: jobs_root directory"

    add_cfg "group: datadirs" "name: tmpdir_dir" \
        "datatype: dir:abs" \
        "default: /tmp/smrtlink" \
        "optstr_alt: --tmpdir" \
        "optstr_typedstr: dir" \
        "printstr_splen: 33,33,44" \
        "optional: false" \
        "specialcase: " \
        "usagestr: tmp directory" \
        "promptstr: Enter the full path to the 'tmp_dir' directory" \
        "usingstr: Using tmp_dir" \
        "settingname: tmp_dir directory"

    # 'computecfg_NN' config
    add_cfg_basegroup "computecfg_NN"

    add_cfg "group: computecfg_NN" "name: name" \
        "datatype: nonempty" \
        "default: __COMPUTED__" \
        "optstr_alt: --name-NN" \
        "optstr_typedstr:" \
        "printstr_splen: 38,24,52" \
        "optional: false" \
        "specialcase: " \
        "usagestr: name of the compute config" \
        "promptstr: Enter the compute config 'NAME'" \
        "usingstr: Using NAME" \
        "settingname: NAME"

    add_cfg "group: computecfg_NN" "name: description" \
        "datatype: nonempty" \
        "default: __COMPUTED__" \
        "optstr_alt: --description-NN" \
        "optstr_typedstr:" \
        "printstr_splen: 38,24,52" \
        "optional: true" \
        "specialcase: " \
        "usagestr: description of the compute config" \
        "promptstr: Enter the description of the compute config 'DESCRIPTION'" \
        "usingstr: Using DESCRIPTION" \
        "settingname: DESCRIPTION"

    add_cfg "group: computecfg_NN" "name: enable" \
        "datatype: boolval" \
        "default: true" \
        "optstr_alt: --computecfg-enable-NN" \
        "optstr_typedstr:" \
        "printstr_splen: 38,24,52" \
        "optional: false" \
        "specialcase: " \
        "usagestr: enable compute config (will appear in ui menu)" \
        "promptstr: Enable the compute config?'" \
        "usingstr: Using COMPUTECFG_ENABLE" \
        "settingname: COMPUTECFG_ENABLE"

    add_cfg "group: computecfg_NN" "name: menuorder" \
        "datatype: int" \
        "default: __COMPUTED__" \
        "optstr_alt: --menuorder-NN" \
        "optstr_typedstr:" \
        "printstr_splen: 38,24,52" \
        "optional: false" \
        "specialcase: " \
        "usagestr: order of appearance in UI menu (lower numbers, higher in menu)" \
        "promptstr: Enter the menu order for the compute config" \
        "usingstr: Using MENUORDER" \
        "settingname: MENUORDER"

    add_cfg "group: computecfg_NN" "name: nproc" \
        "datatype: posint" \
        "default: 7" \
        "optstr_alt: --nproc-NN" \
        "optstr_typedstr:" \
        "printstr_splen: 38,24,52" \
        "optional: false" \
        "specialcase: " \
        "usagestr: maximum number of processors per task" \
        "promptstr: Enter the number of processors per task 'NPROC'" \
        "usingstr: Using NPROC" \
        "settingname: NPROC"

    add_cfg "group: computecfg_NN" "name: maxchunks" \
        "datatype: posint" \
        "default: 24" \
        "optstr_alt: --maxchunks-NN" \
        "optstr_typedstr:" \
        "printstr_splen: 38,24,52" \
        "optional: false" \
        "specialcase: " \
        "usagestr: maximum number of parallel chunks per task" \
        "promptstr: Enter the number of parallel chunks per task 'MAXCHUNKS'" \
        "usingstr: Using MAXCHUNKS" \
        "settingname: MAXCHUNKS"

    add_cfg "group: computecfg_NN" "name: jmstype" \
        "datatype: list_icase:g_jmstype_options" \
        "default: __COMPUTED__" \
        "optstr_alt: --jmstype-NN" \
        "optstr_typedstr: jms" \
        "printstr_splen: 39,24,52" \
        "optional: false" \
        "specialcase: " \
        "usagestr: job management system type\n(one of: $g_jmstype_options_str)" \
        "promptstr: Select the JMS type:" \
        "usingstr: Using jmstype" \
        "settingname: JMS_TYPE"

    add_cfg "group: computecfg_NN" "name: cromwell_loglevel" \
        "datatype: list_icase:g_cromwell_loglevel_list" \
        "default: INFO" \
        "optstr_alt: " \
        "optstr_typedstr:" \
        "printstr_splen: 38,24,52" \
        "optional: false" \
        "specialcase: " \
        "usagestr: select cromwell log level\n(one of: $g_cromwell_loglevel_liststr)" \
        "promptstr: Select the Cromwell Log Level:" \
        "usingstr: Using Cromwell Log Level" \
        "settingname: Cromwell Log Level"

    add_cfg "group: computecfg_NN" "name: cromwell_concurrent_job_limit" \
        "datatype: posint" \
        "default: 500" \
        "optstr_alt: " \
        "optstr_typedstr:" \
        "printstr_splen: 38,24,52" \
        "optional: false" \
        "specialcase: " \
        "usagestr: select cromwell concurrent_job_limit" \
        "promptstr: Select the Cromwell concurrent_job_limit:" \
        "usingstr: Using Cromwell concurrent_job_limit" \
        "settingname: Cromwell concurrent_job_limit"



    # 'sge' config
    # NOTE: sgeroot may be empty string (in case where SGE_ROOT is not set
    #       in enviroment, but is set it config script, as on ubuntu)
    add_cfg "group: computecfg_NN" "name: sge_sgeroot" \
        "datatype: str-nodash" \
        "default: " \
        "optstr_alt: --sge-sgeroot-NN --sgeroot-NN" \
        "optstr_typedstr: root" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: SGE root" \
        "promptstr: Specify SGE_ROOT" \
        "usingstr: Using SGE_ROOT" \
        "settingname: SGE_ROOT"

    # NOTE: sgecell may be empty string (in case where SGE_CELL is not set
    #       in enviroment, but is set it config script, as on ubuntu)
    add_cfg "group: computecfg_NN" "name: sge_sgecell" \
        "datatype: str-nodash" \
        "default: " \
        "optstr_alt: --sge-sgecell-NN --sgecell-NN" \
        "optstr_typedstr: cell" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: SGE cell" \
        "promptstr: Specify SGE_CELL" \
        "usingstr: Using SGE_CELL" \
        "settingname: SGE_CELL"

    add_cfg "group: computecfg_NN" "name: sge_bindir" \
        "datatype: dir:spaceok:specialok:abs:exist:readable" \
        "default: " \
        "optstr_alt: --sge-bindir-NN --sgebindir-NN --sge-sgebindir-NN" \
        "optstr_typedstr: dir" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: SGE bindir" \
        "promptstr: Specify SGE_BINDIR" \
        "usingstr: Using SGE_BINDIR" \
        "settingname: SGE_BINDIR"

    add_cfg "group: computecfg_NN" "name: sge_queue" \
        "datatype: nonempty-nodash" \
        "default: " \
        "optstr_alt: --sge-queue-NN --sgequeue-NN --sge-sgequeue-NN" \
        "optstr_typedstr: queue" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: SGE queue" \
        "promptstr: Select the queue to use for SMRT Analysis jobs:" \
        "usingstr: Using SGE_QUEUE" \
        "settingname: SGE_QUEUE"

    add_cfg "group: computecfg_NN" "name: sge_pe" \
        "datatype: nonempty-nodash" \
        "default: " \
        "optstr_alt: --sge-pe-NN --sgepe-NN --sge-sgepe-NN" \
        "optstr_typedstr: pe" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: SGE parallel environment" \
        "promptstr: Select the parallel environment to use for SMRT Analysis jobs:" \
        "usingstr: Using SGE_PE" \
        "settingname: SGE_PE"

    add_cfg "group: computecfg_NN" "name: sge_startargs" \
        "datatype: str" \
        "default: " \
        "optstr_alt: --sge-startargs-NN --sgestartargs-NN --sge-sgestartargs-NN" \
        "optstr_typedstr: str" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: SGE extra args to start command" \
        "promptstr: Specify extra 'qsub' args, SGE_STARTARGS" \
        "usingstr: Using SGE_STARTARGS" \
        "settingname: SGE_STARTARGS"

    add_cfg "group: computecfg_NN" "name: sge_use_settings_file" \
        "datatype: boolval" \
        "default: false" \
        "optstr_alt:" \
        "optstr_typedstr:" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: " \
        "usagestr: __NO_CMDLINEOPT__" \
        "promptstr: __NO_PROMPT__" \
        "usingstr: Using use_settings_file flag" \
        "settingname: SGE Use Settings File"

    # 'lsf' config
    add_cfg "group: computecfg_NN" "name: lsf_bindir" \
        "datatype: dir:spaceok:specialok:abs:exist:readable" \
        "default: " \
        "optstr_alt: --lsf-bindir-NN --lsfbindir-NN --lsf-lsfbindir-NN" \
        "optstr_typedstr: dir" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: LSF bindir" \
        "promptstr: Specify LSF_BINDIR" \
        "usingstr: Using LSF_BINDIR" \
        "settingname: LSF_BINDIR"

    add_cfg "group: computecfg_NN" "name: lsf_queue" \
        "datatype: str-nodash" \
        "default: " \
        "optstr_alt: --lsf-queue-NN --lsfqueue-NN --lsf-lsfqueue-NN" \
        "optstr_typedstr: queue" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: LSF queue" \
        "promptstr: Select LSF_QUEUE:" \
        "usingstr: Using LSF_QUEUE" \
        "settingname: LSF_QUEUE"

    add_cfg "group: computecfg_NN" "name: lsf_startargs" \
        "datatype: str" \
        "default: " \
        "optstr_alt: --lsf-startargs-NN --lsfstartargs-NN --lsf-lsfstarargs-NN" \
        "optstr_typedstr: str" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: LSF extra args to start command" \
        "promptstr: Specify extra 'bsub' args, LSF_STARTARGS" \
        "usingstr: Using LSF_STARTARGS" \
        "settingname: LSF_STARTARGS"

    # 'pbs' config
    add_cfg "group: computecfg_NN" "name: pbs_bindir" \
        "datatype: dir:spaceok:specialok:abs:exist:readable" \
        "default: " \
        "optstr_alt: --pbs-bindir-NN --pbsbindir-NN --pbs-pbsbindir-NN" \
        "optstr_typedstr: dir" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: PBS bindir" \
        "promptstr: Specify PBS_BINDIR" \
        "usingstr: Using PBS_BINDIR" \
        "settingname: PBS_BINDIR"

    add_cfg "group: computecfg_NN" "name: pbs_queue" \
        "datatype: str-nodash" \
        "default: " \
        "optstr_alt: --pbs-queue-NN --pbsqueue-NN --pbs-pbsqueue-NN" \
        "optstr_typedstr: queue" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: PBS queue" \
        "promptstr: Select the PBS_QUEUE:" \
        "usingstr: Using PBS_QUEUE" \
        "settingname: PBS_QUEUE"

    add_cfg "group: computecfg_NN" "name: pbs_startargs" \
        "datatype: str" \
        "default: " \
        "optstr_alt: --pbs-startargs-NN --pbsstartargs-NN --pbs-pbsstartargs-NN" \
        "optstr_typedstr: str" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: PBS extra args to start command" \
        "promptstr: Specify extra 'qsub' args, PBS_STARTARGS" \
        "usingstr: Using PBS_STARTARGS" \
        "settingname: PBS_STARTARGS"

    # 'slurm' config
    add_cfg "group: computecfg_NN" "name: slurm_bindir" \
        "datatype: dir:spaceok:specialok:abs:exist:readable" \
        "default: " \
        "optstr_alt: --slurm-bindir-NN --slurmbindir-NN --slurm-slurmbindir-NN" \
        "optstr_typedstr: dir" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: Slurm bindir" \
        "promptstr: Specify SLURM_BINDIR" \
        "usingstr: Using SLURM_BINDIR" \
        "settingname: SLURM_BINDIR"

    add_cfg "group: computecfg_NN" "name: slurm_partition" \
        "datatype: str-nodash" \
        "default: " \
        "optstr_alt: --slurm-partition-NN --slurmpartition-NN --slurm-slurmpartition-NN" \
        "optstr_typedstr: partition" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: Slurm partition" \
        "promptstr: Select SLURM_PARTITION:" \
        "usingstr: Using SLURM_PARTITION" \
        "settingname: SLURM_PARTITION"

    # FIXME: +++ Remove this after all sites upgraded to >7.0.1 (start) +++
    add_cfg "group: computecfg_NN" "name: slurm_prestartargs" \
        "datatype: str" \
        "default: " \
        "optstr_alt: " \
        "optstr_typedstr: str" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :nostore::nocmdlineallowed:" \
        "usagestr: __NO_CMDLINEOPT__" \
        "promptstr: __NO_PROMPT__" \
        "usingstr: __NO_USINGSTR__" \
        "settingname: __NO_SETTINGNAME__"
    # FIXME: --- Remove this after all sites upgraded to >7.0.1 (end) ---

    add_cfg "group: computecfg_NN" "name: slurm_startargs" \
        "datatype: str" \
        "default: " \
        "optstr_alt: --slurm-startargs-NN --slurmstartargs-NN --slurm-slurmstartargs-NN" \
        "optstr_typedstr: str" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: Slurm extra args to start command" \
        "promptstr: Specify extra 'sbatch' args, SLURM_STARTARGS" \
        "usingstr: Using SLURM_STARTARGS" \
        "settingname: SLURM_STARTARGS"

    # 'otherjms' config
    add_cfg "group: computecfg_NN" "name: otherjms_name" \
        "datatype: nonempty-nodash" \
        "default: " \
        "optstr_alt: --otherjms-name-NN --otherjmsname-NN --otherjms-otherjmsname-NN" \
        "optstr_typedstr: str" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: OtherJMS name" \
        "promptstr: Specify OTHERJMS_NAME" \
        "usingstr: Using OTHERJMS_NAME" \
        "settingname: OTHERJMS_NAME"

    add_cfg "group: computecfg_NN" "name: otherjms_bindir" \
        "datatype: dir:spaceok:specialok:abs:exist:readable" \
        "default: " \
        "optstr_alt: --otherjms-bindir-NN --otherjmsbindir-NN --otherjms-otherjmsbindir-NN" \
        "optstr_typedstr: dir" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: OtherJMS bindir" \
        "promptstr: Specify OTHERJMS_BINDIR" \
        "usingstr: Using OTHERJMS_BINDIR" \
        "settingname: OTHERJMS_BINDIR"

    add_cfg "group: computecfg_NN" "name: otherjms_queue" \
        "datatype: str-nodash" \
        "default: " \
        "optstr_alt: --otherjms-queue-NN --otherjmsqueue-NN --otherjms-otherjmsqueue-NN" \
        "optstr_typedstr: queue" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: OtherJMS queue" \
        "promptstr: Select OTHERJMS_QUEUE:" \
        "usingstr: Using OTHERJMS_QUEUE" \
        "settingname: OTHERJMS_QUEUE"

    add_cfg "group: computecfg_NN" "name: otherjms_startargs" \
        "datatype: str" \
        "default: " \
        "optstr_alt: --otherjms-startargs-NN --otherjmsstartargs-NN --otherjms-otherjmsstartargs-NN" \
        "optstr_typedstr: str" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: OtherJMS extra args to start command" \
        "promptstr: Specify extra start command args, OTHERJMS_STARTARGS" \
        "usingstr: Using OTHERJMS_STARTARGS" \
        "settingname: OTHERJMS_STARTARGS"

    # 'customjms' config
    add_cfg "group: computecfg_NN" "name: customjms_name" \
        "datatype: nonempty-nodash" \
        "default: " \
        "optstr_alt: --customjms-name-NN --customjmsname-NN --customjms-otherjmsname-NN" \
        "optstr_typedstr: str" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: CustomJMS name" \
        "promptstr: Specify CUSTOMJMS_NAME" \
        "usingstr: Using CUSTOMJMS_NAME" \
        "settingname: CUSTOMJMS_NAME"

    add_cfg "group: computecfg_NN" "name: customjms_bindir" \
        "datatype: dir:spaceok:specialok:abs:exist:readable" \
        "default: " \
        "optstr_alt: --customjms-bindir-NN --customjmsbindir-NN --customjms-otherjmsbindir-NN" \
        "optstr_typedstr: dir" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: CustomJMS bindir" \
        "promptstr: Specify CUSTOMJMS_BINDIR" \
        "usingstr: Using CUSTOMJMS_BINDIR" \
        "settingname: CUSTOMJMS_BINDIR"

    add_cfg "group: computecfg_NN" "name: customjms_queue" \
        "datatype: str-nodash" \
        "default: " \
        "optstr_alt: --customjms-queue-NN --customjmsqueue-NN --customjms-otherjmsqueue-NN" \
        "optstr_typedstr: queue" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: CustomJMS queue" \
        "promptstr: Select CUSTOMJMS_QUEUE:" \
        "usingstr: Using CUSTOMJMS_QUEUE" \
        "settingname: CUSTOMJMS_QUEUE"

    add_cfg "group: computecfg_NN" "name: customjms_startargs" \
        "datatype: str" \
        "default: " \
        "optstr_alt: --customjms-startargs-NN --customjmsstartargs-NN --customjms-otherjmsstartargs-NN" \
        "optstr_typedstr: str" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: CustomJMS extra args to start command" \
        "promptstr: Specify extra start command args, CUSTOMJMS_STARTARGS" \
        "usingstr: Using CUSTOMJMS_STARTARGS" \
        "settingname: CUSTOMJMS_STARTARGS"

    add_cfg "group: computecfg_NN" "name: aws_queue" \
        "datatype: nonempty-nodash" \
        "default: " \
        "optstr_alt: --aws-queue-NN --awsqueue-NN --aws-awsqueue-NN" \
        "optstr_typedstr: queue" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: AWS queue" \
        "promptstr: Enter the AWS batch queue to use for SMRT Analysis jobs:" \
        "usingstr: Using AWS_QUEUE" \
        "settingname: AWS_QUEUE"

    add_cfg "group: computecfg_NN" "name: aws_region" \
        "datatype: str" \
        "default: " \
        "optstr_alt: --aws-region-NN" \
        "optstr_typedstr: str" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: AWS region" \
        "promptstr: Specify AWS region" \
        "usingstr: Using AWS_REGION" \
        "settingname: AWS_REGION"

    add_cfg "group: computecfg_NN" "name: aws_disk_root" \
        "datatype: str" \
        "default: " \
        "optstr_alt: --aws-disk-root-NN" \
        "optstr_typedstr: str" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: AWS disk root" \
        "promptstr: Specify AWS disk root" \
        "usingstr: Using AWS_DISK_ROOT" \
        "settingname: AWS_DISK_ROOT"

    add_cfg "group: computecfg_NN" "name: aws_docker" \
        "datatype: str" \
        "default: ubuntu:latest" \
        "optstr_alt: --aws-docker-NN" \
        "optstr_typedstr: str" \
        "printstr_splen: 33,24,52" \
        "optional: true" \
        "specialcase: :interruptable:" \
        "usagestr: AWS Docker image" \
        "promptstr: Specify AWS Docker image" \
        "usingstr: Using AWS_DOCKER" \
        "settingname: AWS_DOCKER"

    add_cfg "group: computecfg_NN" "name: aws_memory" \
        "datatype: posint" \
        "default: 4" \
        "optstr_alt: --aws-memory-NN" \
        "optstr_typedstr:" \
        "printstr_splen: 38,24,52" \
        "optional: true" \
        "specialcase: " \
        "usagestr: select cromwell memory_gb" \
        "promptstr: Select the Cromwell task memory requirement (in GB):" \
        "usingstr: Using Cromwell memory_gb" \
        "settingname: Cromwell memory_gb"

    # 'internal' config
    # This is needed to fix the defaults that were written out to the
    # smrtlink.config file as specified values instead of __USE_DEFAULT__
    # (happened on the second time the smrtlink.config was written out,
    # as in an upgrade)
    add_cfg "group: internal" "name: version" \
        "datatype: nonempty" \
        "default: __COMPUTED__" \
        "optstr_alt:" \
        "optstr_typedstr:" \
        "printstr_splen: 24,24,52" \
        "optional: false" \
        "specialcase: " \
        "usagestr: __NO_CMDLINEOPT__" \
        "promptstr: __NO_PROMPT__" \
        "usingstr: __NO_USINGSTR__" \
        "settingname: __NO_SETTINGNAME__"

    # Add initial computecfg_00 listgroup
    add_cfg_listgroup "computecfg_NN" "computecfg_00"
}

str_contains_whitespace() {
    local str=$1;

    if [[ $str =~ [[:space:]] ]] ; then
        return 0;
    fi
    return 1;
}
str_contains_shspecialchar() {
    local str=$1;

    gret__str_contains_shspecialchar__specialchar=""
    if [[ $str =~ ([][!~$&*(){}:;<>|\\\'\"\`]) ]] ; then
        gret__str_contains_shspecialchar__specialchar="${BASH_REMATCH[1]}"
        return 0;
    fi

    return 1;
}

valid_datatype() {
    local type=$1; shift;

    local ret=0;
    case "$type" in
        "posint") ;;
        "int") ;;
        "memsize_gb") ;;
        "memsize_mb") ;;
        "str") ;;
        "str-nodash") ;;
        "nonempty") ;;
        "nonempty-nodash") ;;
        "url") ;;
        "url_emptystr") ;;
        "boolopt") ;;
        "boolopt_opposite:"*) ;;
        "boolval") ;;
        "file"|"file:"*) ;;
        "dir"|"dir:"*) ;;
        "list:"*) ;;
        "list_icase:"*) ;;
        *) ret=1;;
    esac

    return "$ret"
}

get_typedstr() {
    local type=$1; shift;

    if ! valid_datatype "$type" ; then
        minterror "get_typedstr(): Unrecognized datatype: $type";
    fi

    local ret=$type;
    case "$type" in
        "posint") ret="N";;
        "int") ret="N";;
        "memsize_gb") ret="N [GB|MB|KB|B] (def: GB)";;
        "memsize_mb") ret="N [GB|MB|KB|B] (def: MB)";;
        "str") ret="str";;
        "str-nodash") ret="str";;
        "nonempty") ret="str";;
        "nonempty-nodash") ret="str";;
        "url") ret="url";;
        "url_emptystr") ret="url";;
        "boolopt") ret="bool";;
        "boolopt_opposite:"*) ret="bool";;
        "boolval") ret="bool";;
        "file"|"file:"*) ret="file";;
        "dir"|"dir:"*) ret="dir";;
        "list:"*) ret="val";;
        "list_icase:"*) ret="val" ;;
        *) minterror "get_typedstr(): Unrecognized datatype (2): $type";;
    esac

    echo "$ret"
}

validate_datatype() {
    local type=$1; shift;
    local val=$1; shift;
    local specialcase=$1; shift;

    gret__validate_datatype__errstr=""

    if [[ $specialcase =~ :validdata:${val}: ]]; then
        # We match a special case, ignore rest of the validate checks
        return 0;
    fi

    local val_translate="";
    errstr=""
    case "$type" in
        "posint")
            if [[ ! $val =~ ^[[:digit:]]+$ ]] ||
                [[ $val =~ ^0+$ ]] ; then
                errstr="must be a positive integer"
            fi
            ;;
        "int")
            if [[ ! $val =~ ^[+-]?[[:digit:]]+$ ]]; then
                errstr="must be a signed integer"
            fi
            ;;
        "memsize_gb"|"memsize_mb")
            if [[ ! $val =~ ^([0-9]+)[[:space:]]*([gGmMkKbB])?[iI]?[bB]?$ ]] ; then
                errstr="must be a valid memsize (N [gGmMkKbB[i[bB]]]) string"
            fi
            ;;
        "str")
            # Anything should be fine
            ;;
        "str-nodash")
            if [[ $val =~ ^- ]] ; then
                errstr="must not start with a '-' dash"
            fi
            ;;
        "nonempty")
            if [[ -z "$val" ]] ||
                [[ x"$val" == x"__UNSET__" ]] ||
                [[ x"$val" == x'""' ]] ||
                [[ x"$val" == x"''" ]] ; then

                errstr="must not be an empty string"
            fi
            ;;
        "nonempty-nodash")
            if [[ -z "$val" ]] ||
                [[ x"$val" == x"__UNSET__" ]] ||
                [[ x"$val" == x'""' ]] ||
                [[ x"$val" == x"''" ]] ; then

                errstr="must not be an empty string"
            elif [[ $val =~ ^- ]] ; then
                errstr="must not start with a '-' dash"
            fi
            ;;
        "url")
            if [[ ! $val =~ ^[^:]+:// ]]; then
                errstr="must be a valid url"
            fi
            ;;
        "url_emptystr")
            if [[ ! -z "$val" ]] && [[ ! $val =~ ^[^:]+:// ]]; then
                errstr="must be a valid url or empty string"
            fi
            ;;
        "boolopt")
            if [[ ! $val =~ ^(true|false)$ ]] ; then
                errstr="must be boolean ('true' or 'false')"
            fi
            ;;
        "boolopt_opposite:"*)
            ;;
        "boolval")
            if [[ ! $val =~ ^(true|false)$ ]] ; then
                errstr="must be boolean ('true' or 'false')"
            fi
            ;;
        "file"|"file:"*)
            # Trailing options:
            #    :emptyok:      empty string is allowed
            #    :spaceok:      spaces are allowed in path
            #    :specialok:    special shell chars allowed in path
            #    :abs:          path must be absolute
            #    :exist:        path must exist
            #    :readable:     path must exist and be readable
            #    :writable:     path must exist and be writable
            # For example:
            #    file:spaceok:specialok:abs:exist:readable
            if [[ -z "$val" ]] ; then
                if [[ ! ${type}: =~ :emptyok: ]]; then
                    errstr="empty string, must be a file path"
                fi
            elif [[ ! ${type}: =~ :spaceok: ]] &&
                 str_contains_whitespace "$val"; then
                errstr="file path may not contain spaces";
            elif [[ ! ${type}: =~ :specialok: ]] &&
                 str_contains_shspecialchar "$val"; then
                errstr="file path may not contain '$gret__str_contains_shspecialchar__specialchar' character"
            elif [[ ${type}: =~ :abs: ]] &&
                 [[ ! $val =~ ^/ ]] ; then
                errstr="file is not an absolute path"
            elif [[ ! -e "$val" ]] ; then
                if [[ ${type}: =~ :exist: ]]; then
                    errstr="file does not exist"
                fi
            elif [[ ! -f "$val" ]] ; then
                errstr="path exists, but is not a regular file"
            elif [[ ! -r "$val" ]] &&
                 [[ ${type}: =~ :readable: ]] ; then
                 errstr="file is not readable"
            elif [[ ! -w "$val" ]] &&
                 [[ ${type}: =~ :writable: ]] ; then
                 errstr="file is not writable"
            fi
            ;;
        "dir"|"dir:"*)
            # Trailing options:
            #    :emptyok:      empty string is allowed
            #    :spaceok:      spaces are allowed in path
            #    :specialok:    special shell chars allowed in path
            #    :abs:          path must be absolute
            #    :exist:        path must exist
            #    :readable:     path must exist and be readable
            #    :writable:     path must exist and be writable
            # For example:
            #    dir:spaceok:specialok:abs:exist:readable
            if [[ -z "$val" ]] ; then
                if [[ ! ${type}: =~ :emptyok: ]]; then
                    errstr="empty string, must be a directory path"
                fi
            elif [[ ! ${type}: =~ :spaceok: ]] &&
                 str_contains_whitespace "$val"; then
                errstr="directory path may not contain spaces";
            elif [[ ! ${type}: =~ :specialok: ]] &&
                 str_contains_shspecialchar "$val"; then
                errstr="directory path may not contain '$gret__str_contains_shspecialchar__specialchar' character"
            elif [[ ${type}: =~ :abs: ]] &&
                 [[ ! $val =~ ^/ ]] ; then
                errstr="directory is not an absolute path"
            elif [[ ! -e "$val" ]]; then
                if [[ ${type}: =~ :exist: ]] ; then
                    errstr="directory does not exist"
                fi
            elif [[ ! -d "$val" ]] ; then
                errstr="path exists, but is not a directory"
            elif [[ ! -r "$val" ]] &&
                 [[ ${type}: =~ :readable: ]] ; then
                 errstr="directory is not readable"
            elif [[ ! -w "$val" ]] &&
                 [[ ${type}: =~ :writable: ]] ; then
                 errstr="directory is not writable"
            fi
            ;;
        "list:"*)
            local -a arr;
            eval arr=( \"\${${type#*:}[@]}\" );
            local listel;
            local found=false;
            for listel in "${arr[@]}"; do
                listel=${listel//\*/(.*)}
                if [[ $val =~ ^${listel}$ ]] ; then
                    found=true
                    break;
                fi
            done
            if ! $found; then
                errstr="unrecognized value (must be one of: ${arr[@]})"
            fi
            ;;
        "list_icase:"*)
            local -a arr;
            eval arr=( \"\${${type#*:}[@]}\" );
            local val_uc;
            val_lc=$(echo "$val" | tr "[[:upper:]]" "[[:lower:]]")
            local listel;
            local listel_lc;
            local found=false;
            for listel in "${arr[@]}"; do
                listel_lc=$(echo "$listel" | tr "[[:upper:]]" "[[:lower:]]")
                listel_lc=${listel_lc//\*/(.*)}
                if [[ $val_lc =~ ^${listel_lc}$ ]] ; then
                    found=true
                    val_translate=$listel;
                    if [[ $listel =~ __\*$ ]]; then
                        val_translate="${listel%\*}${val##*__}"
                    fi
                    break;
                fi
            done
            if ! $found; then
                errstr="unrecognized value (must be one of: ${arr[@]})"
            fi
            ;;
        *) minterror "validate_datatypeval(): Unrecognized datatype: $type";;
    esac

    gret__validate_datatype__valtranslate=$val;
    if [[ ! -z "$val_translate" ]] ; then
        gret__validate_datatype__valtranslate=$val_translate;
    fi

    gret__validate_datatype__errstr=$errstr;
    if [[ ! -z "$errstr" ]] ; then
        return 1;
    fi
    return 0;
}

print_cfg_usageargs_group() {
    local b_basegroup=$1; shift;
    local group=$1; shift;

    local i;
    local lgroup;
    local splen
    local optstr;
    local first=true;
    for ((i=0; i < ${#ga_cfg_groupnames[@]} ; i++)); do
        local lgroup="${ga_cfg_groupnames[$i]%%:*}"

        if [[ x"$lgroup" != x"$group" ]] ; then
            continue;
        fi

        if [[ x"${g_cfg_usagestr[$i]}" == x"__NO_CMDLINEOPT__" ]] ; then
            continue;
        fi

        if $first; then
            # Print newline at the start of the group
            echo "             \\"
            first=false;
        fi

        splen=${g_cfg_printstr_splen[$i]};
        splen=${splen%%,*}

        optstr="${g_cfg_optstr[$i]}"
        if [[ ! -z "${g_cfg_optstr_alts[$i]}" ]] ; then
            optstr+="|${g_cfg_optstr_alts[$i]// /|}"
        fi
        if $b_basegroup; then
            local optstr2;

            optstr2="${g_cfg_optstr[$i]}"
            if [[ ! -z "${g_cfg_optstr_alts[$i]}" ]] ; then
                optstr2+="|${g_cfg_optstr_alts[$i]// /|}"
            fi
            optstr2=${optstr2//-NN/}
            optstr+="|$optstr2";
        fi

        printf "             [%-${splen}s %s] %-s\n" \
            "$optstr" "${g_cfg_optstr_typedstr[$i]}" "\\"
    done
}

print_cfg_usageargs() {
    local i;
    if [[ ! -z "${ga_cfgg_groups[@]+${ga_cfgg_groups[@]}}" ]]; then
        for ((i=0; i < ${#ga_cfgg_groups[@]} ; i++)); do
            if ${g_cfgg_listgroup[$i]}; then
                # Don't write listgroups, we only print the basegroups
                :
            elif ${g_cfgg_basegroup[$i]}; then
                print_cfg_usageargs_group true "${ga_cfgg_groups[$i]}"
            else
                print_cfg_usageargs_group false "${ga_cfgg_groups[$i]}"
            fi
        done
    fi
}

print_cfg_usagedesc_group() {
    local b_basegroup=$1; shift;
    local group=$1; shift;


    local i;
    local j;
    local remainder;
    local splen
    for ((i=0; i < ${#ga_cfg_groupnames[@]} ; i++)); do
        local lgroup="${ga_cfg_groupnames[$i]%%:*}"

        if [[ x"$lgroup" != x"$group" ]] ; then
            continue;
        fi

        if [[ x"${g_cfg_usagestr[$i]}" == x"__NO_CMDLINEOPT__" ]] ; then
            continue;
        fi

        splen=${g_cfg_printstr_splen[$i]};
        splen=${splen#*,}
        splen=${splen%%,*}

        printf "         %-${splen}s -- %s\n" \
            "${g_cfg_optstr[$i]}" "${g_cfg_usagestr[$i]%%\\n*}"

        remainder="${g_cfg_usagestr[$i]}"
        while [[ $remainder =~ \\n ]] ; do
            remainder="${remainder#*\\n}"
            printf "         %-${splen}s    %s\n" \
                ""  "${remainder%%\\n*}"
        done

        if [[ ! -z "${g_cfg_optstr_alts[$i]}" ]] ; then
            for j in ${g_cfg_optstr_alts[$i]}; do
                printf "         %-${splen}s -- %s\n" \
                    "  ${j}" "  +- same as above"
            done
        fi

        if $b_basegroup; then
            j="${g_cfg_optstr[$i]}"
            printf "         %-${splen}s -- %s\n" \
                "  ${j//-NN/}" "  +- same as above (where NN is 00)"
            if [[ ! -z "${g_cfg_optstr_alts[$i]}" ]] ; then
                for j in ${g_cfg_optstr_alts[$i]}; do
                    printf "         %-${splen}s -- %s\n" \
                        "  ${j//-NN/}" "  +- same as above (where NN is 00)"
                done
            fi
        fi
    done
}


print_cfg_usagedesc() {
    local i;
    if [[ ! -z "${ga_cfgg_groups[@]+${ga_cfgg_groups[@]}}" ]]; then
        for ((i=0; i < ${#ga_cfgg_groups[@]} ; i++)); do
            if ${g_cfgg_listgroup[$i]}; then
                # Don't write listgroups, we only print the basegroups
                :
            elif ${g_cfgg_basegroup[$i]}; then
                print_cfg_usagedesc_group true "${ga_cfgg_groups[$i]}"
            else
                print_cfg_usagedesc_group false "${ga_cfgg_groups[$i]}"
            fi
        done
    fi
}

assign_cfg_cmdlineopt() {
    local opt=$1; shift;

    local found=false;
    local basegroup_match=false;
    local basegroup="";
    local listgroup="";
    local altopt
    local i;
    local index;
    local group;
    local listnum;
    local optstr_re;
    for ((i=0; i < ${#ga_cfg_groupnames[@]} ; i++)); do
        local group=${ga_cfg_groupnames[$i]};
        group=${group%%:*}

        if [[ $group =~ _NN$ ]] ; then
            # This is a basegroup
            if ! $basegroup_match; then
                optstr_re=${g_cfg_optstr[$i]};
                optstr_re=${optstr_re//-NN/(-[0-9]+|)};
                if [[ $opt =~ ^${optstr_re}$ ]] ; then
                    basegroup_match=true;
                else
                    for altopt in ${g_cfg_optstr_alts[$i]}; do
                        optstr_re=$altopt;
                        optstr_re=${optstr_re//-NN/(-[0-9]+|)};
                        if [[ $opt =~ ^$optstr_re$ ]] ; then
                            basegroup_match=true;
                            break;
                        fi
                    done
                fi
                if $basegroup_match; then
                    basegroup=$group;
                    listnum=${BASH_REMATCH[1]};
                    listnum=${listnum#-}
                    listgroup=$(get_listgroup_from_listnum "$group" "$listnum")
                fi
            fi
        else
            # This is a not a basegroup
            if [[ x"$opt" == x"${g_cfg_optstr[$i]}" ]] ; then
                found=true;
            fi
            for altopt in ${g_cfg_optstr_alts[$i]}; do
                if [[ x"$opt" == x"$altopt" ]] ; then
                    found=true;
                fi
            done
        fi

        if $found; then
            index=$i
            break;
        fi
    done

    if ! $found && $basegroup_match; then
        add_cfg_listgroup --cmdlineopt "$basegroup" "$listgroup"
        # Look throuth the list backwards, since the basegroup matches
        # should be toward the end
        for ((i=$(( ${#ga_cfg_groupnames[@]} - 1 )); i >= 0 ; i--)); do
            if [[ ${ga_cfg_groupnames[$i]} =~ _NN: ]] ; then
                # Skip any basegroups
                continue;
            fi

            if [[ $opt =~ ^${g_cfg_optstr[$i]}$ ]] ; then
                found=true;
                index=$i;
                break;
            else
                for altopt in ${g_cfg_optstr_alts[$i]}; do
                    if [[ $opt =~ ^$altopt$ ]] ; then
                        found=true;
                        index=$i;
                        break;
                    fi
                done
            fi
            if $found; then
                break;
            fi
        done
    fi

    if $found; then
        if [[ ${g_cfg_specialcase[$index]} =~ :nocmdlineallowed: ]] ; then
            found=false;
        fi
    fi

    if ! $found; then
        return 1
    fi

    [[ $# -eq 0 ]] && usage "Missing argument to $opt option";
    local arg=$1;

    if  [[ x"$arg" == x"__USE_DEFAULT__" ]] ||
        [[ x"$arg" == x"__DEFAULT__" ]] ; then

        g_cfg_curval[$index]="__DEFAULT__";
        g_cfg_srcstr[$index]="reset to default on the command line";
        g_cfg_usedefault[$index]=true;
    else
        if ! validate_datatype "${g_cfg_datatype[$index]}" "$arg" "${g_cfg_specialcase[$index]}"; then
            # usage "Datatype mismatch for $opt arg '$arg' ($gret__validate_datatype__errstr)"
            merror "Datatype mismatch for $opt arg '$arg' ($gret__validate_datatype__errstr)"
        fi

        # Use the translated value from the validate_datatype() function
        # (instead of $arg directly) to handle the case insensitive arguments
        # like --jmstype.  We will get the normalized case for use in
        # comparisons later on.
        g_cfg_curval[$index]=$gret__validate_datatype__valtranslate;
        g_cfg_srcstr[$index]="specified on the command line";
        g_cfg_usedefault[$index]=false;
    fi

    gret__assign_cfg_cmdlineopt__shiftcnt=1;
    return 0;
}

assign_cfg_configfileopt() {
    local group=$1; shift;
    local name=$1; shift;
    local val=$1; shift;

    # FIXME: +++ Remove this after all sites upgraded to >7.0.1 (start) +++
    #        Hacking to remove chunkning on/off flag support.
    if  [[ $group =~ ^computecfg_[0-9][0-9]$ ]] &&
        [[ x"$name" == x"maxchunks" ]] &&
        [[ x"$val" == x"1" ]]; then
        # This could be the hack in legacy_configmod() that detected
        # the chunking flag was set to false and forced maxchunks to 1.
        # If so, there may be subsequent setting of maxchunks which we
        # want to ignore.  Set the chunking_off hack flag to take care
        # of that.
        g_chunking_deprecated_hack__chunking_off+=":${group}:";
    fi
    if  [[ $group =~ ^computecfg_[0-9][0-9]$ ]] &&
        [[ x"$name" == x"maxchunks" ]] &&
        [[ $g_chunking_deprecated_hack__chunking_off =~ :${group}: ]] ; then
        # We have already seen chunking turned off (or at least maxchunks
        # set to 1), probably because of the hack in legacy_configmod().
        # Force maxchunks to 1 here
        val="1"
    fi
    # FIXME: --- Remove this after all sites upgraded to >7.0.1 (end) ---

    # If this looks like a listgroup, try to determine the corresponding
    # basegroup and add the basegroup->listgroup mapping
    local i;
    local poss_basegroup;
    if [[ $group =~ (.*)_[0-9]+$ ]] ; then
        poss_basegroup="${BASH_REMATCH[1]}_NN"
        if [[ ! -z "${ga_cfgg_groups[@]+${ga_cfgg_groups[@]}}" ]]; then
            for ((i=0; i < ${#ga_cfgg_groups[@]} ; i++)); do
                if [[ x"$group" = x"${ga_cfgg_groups[$i]}" ]] ; then
                    if ${g_cfgg_ignoreconfigfile[$i]}; then
                        # Ignore this setting (all settings in this group)
                        return 0
                    fi
                fi
            done
            for ((i=0; i < ${#ga_cfgg_groups[@]} ; i++)); do
                if [[ x"$poss_basegroup" = x"${ga_cfgg_groups[$i]}" ]] ; then
                    if ${g_cfgg_basegroup[$i]} ; then
                        add_cfg_listgroup --configfileopt "$poss_basegroup" "$group"
                    fi
                fi
            done
        fi
    fi

    # Use get_cfg_val_raw() here, since we do not want the default
    # substituted yet.
    local curval;
    curval=$(get_cfg_val_raw "$group" "$name");
    if isset_cfgval "$curval" ; then
        # config was already set on the comand line, ignore the setting from
        # the config file.
        return 0;
    fi

    local index;
    index=$(get_cfg_index "$group" "$name")

    if  [[ x"$val" == x"__USE_DEFAULT__" ]] ||
        [[ x"$val" == x"__DEFAULT__" ]] ||
        [[ x"$val" == x"__UNSET__" ]] ; then

        g_cfg_curval[$index]="__DEFAULT__";
        g_cfg_srcstr[$index]="set to default in a previous install";
        g_cfg_usedefault[$index]=true;
    else
        if ! validate_datatype "${g_cfg_datatype[$index]}" "$val" "${g_cfg_specialcase[$index]}"; then
            merror "Datatype mismatch for ${group}__${name} setting '$val' ($gret__validate_datatype__errstr) in '$g__assign_cfg_configfileopt___cur_configfile'"
        fi

        g_cfg_curval[$index]="$val";
        g_cfg_srcstr[$index]="configured in a previous install";
        g_cfg_usedefault[$index]=false;
    fi
}

get_cfgg_index() {
    local group=$1; shift

    if [[ x"$group" == x"$gcache__get_cfgg_index__group" ]] ; then
        # Got a cache match for the last lookup, return it immediately
        echo "$gcache__get_cfgg_index__lastindex"
        return 0;
    fi

    local i;
    local ret=""
    for ((i=0; i < ${#ga_cfgg_groups[@]} ; i++)); do
        if [[ x"$group" == x"${ga_cfgg_groups[$i]}" ]] ; then
            ret=$i;
            break;
        fi
    done

    if [[ -z "$ret" ]] ; then
        merror "get_cfgg_index(): Could not find index for '$group'"
    fi

    gcache__get_cfgg_index__group=$group;
    gcache__get_cfgg_index__lastindex=$ret;
    echo "$ret"
}

get_cfgg_savegroup() {
    local group=$1; shift

    local index;
    index=$(get_cfgg_index "$group")

    echo "${g_cfgg_savegroup[$index]}";
}

get_cfg_index() {
    local group=$1; shift
    local name=$1; shift

    local groupname="$group:$name"

    # Unfortunately, this caching mechanism does not work since we
    # call get_cfg_index() inside process substitution $() (either
    # calling get_cfg_index() or higher level functions like
    # get_cfg_val()).  Inside a process substitution, the setting of
    # the global variables will not survive.
    # So for the cases where caching of the index would be helpful, we
    # will take care of explicitly at the higher levels.
    if [[ x"$groupname" == x"$gcache__get_cfg_index__groupname" ]] ; then
        # Got a cache match for the last lookup, return it immediately
        echo "$gcache__get_cfg_index__lastindex"
        return 0;
    fi

    local i;
    local ret=""
    for ((i=0; i < ${#ga_cfg_groupnames[@]} ; i++)); do
        if [[ x"$groupname" == x"${ga_cfg_groupnames[$i]}" ]] ; then
            ret=$i;
            break;
        fi
    done

    if [[ -z "$ret" ]] ; then
        merror "get_cfg_index(): Could not find index for '$groupname'"
    fi

    gcache__get_cfg_index__groupname=$groupname;
    gcache__get_cfg_index__lastindex=$ret;
    echo "$ret"
}

# Get raw value (without substiting default if needed)
get_cfg_val_raw() {
    local group=$1; shift
    local name=$1; shift

    local index;
    index=$(get_cfg_index "$group" "$name")

    echo "${g_cfg_curval[$index]}";
}


get_cfg_val_fromindex() {
    local index=$1; shift

    local retval="${g_cfg_curval[$index]}";

    if ${g_cfg_usedefault[$index]}; then
        local defval="${g_cfg_default[$index]}";

        if [[ x"$defval" == x"__COMPUTED__" ]] ; then
            local computefunc="get_computed_default__${group}__${name}"
            if [[ ! -z "${g_cfg_basegroup[$index]}" ]] ; then
                computefunc="get_computed_default__${g_cfg_basegroup[$index]}__${name}"
            fi
            # figure out if function exists, if not complain
            if ! typeset -F "$computefunc" > /dev/null 2>&1; then
                minterror "get_cfg_val(): Default computing function '$computefunc' does not exist."
            fi

            retval=$("$computefunc" "$group" "$name")
        else
            retval="$defval";
        fi
    fi

    echo "$retval"
}

get_cfg_val() {
    local group=$1; shift
    local name=$1; shift

    local index;
    index=$(get_cfg_index "$group" "$name")

    get_cfg_val_fromindex "$index";
}

get_cfg_val_nounset() {
    local group=$1; shift
    local name=$1; shift

    local retval
    retval=$(get_cfg_val "$group" "$name")

    # Look for __UNSET__, __DEFAULT__ or anything other internal setting
    if [[ $retval =~ ^__ ]]; then
        minterror "get_cfg_val_nounset(): val for '$group:$name' is not set ($retval)"
    fi

    # Final check of datatype
    local val;
    local datatype;
    local specialcase;
    val=$(get_cfg_val "$group" "$name");
    datatype=$(get_cfg_datatype "$group" "$name");
    specialcase=$(get_cfg_specialcase "$group" "$name");
    if ! validate_datatype "$datatype" "$val" "$specialcase"; then
        minterror "get_cfg_val_nounset(): Datatype mismatch for ${group}__${name} setting '$val' ($gret__validate_datatype__errstr)"
    fi

    echo "$retval"
}

set_cfg_val() {
    local group=$1; shift
    local name=$1; shift
    local val=$1; shift;
    local srcstr;
    local usedefault;
    [[ ! -z "${1+set}" ]] && srcstr=$1;
    [[ ! -z "${2+set}" ]] && usedefault=$2;


    local index;
    index=$(get_cfg_index "$group" "$name")

    g_cfg_curval[$index]=$val;
    if [[ ! -z "${srcstr+set}" ]]; then
        g_cfg_srcstr[$index]=$srcstr;
    fi
    if [[ ! -z "${usedefault+set}" ]]; then
        g_cfg_usedefault[$index]=$usedefault;

        if [[ ! $usedefault =~ ^(true|false)$ ]] ; then
            minterror "set_cfg_val(): Illegal usedefault val ($usedefault)";
        fi
    fi
}

set_cfg_srcstr() {
    local group=$1; shift
    local name=$1; shift
    local srcstr=$1; shift;

    local index;
    index=$(get_cfg_index "$group" "$name")

    g_cfg_srcstr[$index]=$srcstr;
}
set_cfg_usedefault() {
    local group=$1; shift
    local name=$1; shift
    local usedefault=$1; shift;

    local index;
    index=$(get_cfg_index "$group" "$name")

    g_cfg_usedefault[$index]=$usedefault;
}
set_cfg_optional() {
    local group=$1; shift
    local name=$1; shift
    local optional=$1; shift;

    local index;
    index=$(get_cfg_index "$group" "$name")

    g_cfg_optional[$index]=$optional;
}
set_cfg_promptstr() {
    local group=$1; shift
    local name=$1; shift
    local promptstr=$1; shift;

    local index;
    index=$(get_cfg_index "$group" "$name")

    g_cfg_promptstr[$index]=$promptstr;
}


unset_cfg_val() {
    local group=$1; shift
    local name=$1; shift

    local index;
    index=$(get_cfg_index "$group" "$name")

    g_cfg_curval[$index]="__UNSET__"
    g_cfg_srcstr[$index]="default"
    g_cfg_usedefault[$index]=true;
}

get_cfg_srcstr() {
    local group=$1; shift
    local name=$1; shift

    local index;
    index=$(get_cfg_index "$group" "$name")

    retval="${g_cfg_srcstr[$index]}";

    if ${g_cfg_usedefault[$index]}; then
        # Return "computed default" if usedefault is true and we are
        # computing the default (i.e default is "__COMPUTED__").
        if [[ x"${g_cfg_default[$index]}" == x"__COMPUTED__" ]] ; then
            if [[ ! $retval =~ ^computed\  ]] ; then
                if [[ $retval =~ ^set\ to\ (.*) ]] ; then
                    retval="computed ${BASH_REMATCH[1]}"
                else
                    retval="computed $retval"
                fi
            fi
        fi
    fi

    echo "$retval"
}

get_cfg_usedefault_fromindex() {
    local index=$1; shift;
    echo "${g_cfg_usedefault[$index]}";
}
get_cfg_usedefault() {
    local group=$1; shift
    local name=$1; shift

    local index;
    index=$(get_cfg_index "$group" "$name")

    get_cfg_usedefault_fromindex "$index"
}
get_cfg_promptstr() {
    local group=$1; shift
    local name=$1; shift

    local index;
    index=$(get_cfg_index "$group" "$name")

    echo "${g_cfg_promptstr[$index]}";
}
get_cfg_datatype() {
    local group=$1; shift
    local name=$1; shift

    local index;
    index=$(get_cfg_index "$group" "$name")

    echo "${g_cfg_datatype[$index]}";
}
get_cfg_specialcase_fromindex() {
    local index=$1; shift;
    echo "${g_cfg_specialcase[$index]}";
}
get_cfg_specialcase() {
    local group=$1; shift
    local name=$1; shift

    local index;
    index=$(get_cfg_index "$group" "$name")

    get_cfg_specialcase_fromindex "$index"
}
get_cfg_usingstr() {
    local group=$1; shift
    local name=$1; shift

    local index;
    index=$(get_cfg_index "$group" "$name")

    echo "${g_cfg_usingstr[$index]}";
}
get_cfg_settingname() {
    local group=$1; shift
    local name=$1; shift

    local index;
    index=$(get_cfg_index "$group" "$name")

    echo "${g_cfg_settingname[$index]}";
}
get_cfg_optional_fromindex() {
    local index=$1; shift;
    echo "${g_cfg_optional[$index]}";
}
get_cfg_optional() {
    local group=$1; shift
    local name=$1; shift

    local index;
    index=$(get_cfg_index "$group" "$name")

    get_cfg_optional_fromindex "$index"
}


# -- Config computed default functions

# install:
get_computed_default__install__sluuid() {
    local retval="";

    # If we need to compute the default sluuid, first check to see if it
    # already exists in the tsreport directory.  If so, read it in and
    # reuse it if it appears to be a uuid (i.e. try to avoid recomputing the
    # uuid if it has already been computed for a particular installation).
    if [[ -r "$g_tsreport_sluuid_file" ]] ; then
        local sluuid;
        sluuid=$(cat "$g_tsreport_sluuid_file")
        if [[ $sluuid =~ ^[\-a-f0-9]+$ ]] ; then
            # Looks like a valid uuid, use it instead of recomputing
            retval=$sluuid;
        fi
    fi

    if [[ -z "$retval" ]] ; then
        # The sluuid doesn't exist from a previous install attempt, try
        # to generate it.
        retval=$("$g_python3_exe" -c "import uuid ; print(uuid.uuid4())");
    fi
    echo "$retval"
}
# system:
get_computed_default__system__memtotal() {
    echo "$g_hw_physmem_bytes"
}

get_computed_default__install__sharedfs_root() {
    local retval;
    retval=$opt_rootdir;
    if [[ ! $opt_rootdir =~ ^/ ]] ; then
        retval=$(readlink -f "$opt_rootdir")
    fi
    echo "$retval"
}

# smrtlink:
get_computed_default__smrtlink__services_port() {
    local retval;
    retval=$(get_cfg_val "smrtlink" "gui_port");
    retval=$(( $retval + 1 ))
    echo "$retval"
}
get_computed_default__smrtlink__services_maxmem() {
    local retval;
    retval=$(get_mempct_MiB 25 512 32768);
    echo "$retval";
}
get_computed_default__smrtlink__services_minmem() {
    local retval;
    retval=$(get_mempct_MiB 25 512 32768);
    echo "$retval";
}
# database:
get_computed_default__database__dbport() {
    local retval;
    retval=$(get_cfg_val "smrtlink" "services_port");
    retval=$(( $retval + 4 ))
    echo "$retval"
}
get_computed_default__database__dbdatadir() {
    local rootdir;
    rootdir=$opt_rootdir;
    if [[ ! $opt_rootdir =~ ^/ ]] ; then
        rootdir=$(readlink -f "$opt_rootdir")
    fi

    local retval="$rootdir/userdata/db_datadir.default";
    echo "$retval"
}
get_computed_default__cromwell__port() {
    local retval;
    retval=$(get_cfg_val "database" "dbport");
    retval=$(( $retval + 1 ))
    echo "$retval"
}
# datadirs
get_computed_default__datadirs__jobsroot_dir() {
    echo "$g_userdata_dirabs/jobs_root.default"
}
# nworkers
get_computed_default__smrtlink__nworkers() {
    local retval;
    # We will set this to the minimum of 32 and the number of cpu cores on
    # the system
    retval=$(( $g_hw_numprocs ))
    if [[ $retval -gt 32 ]] ; then
        retval=32;
    fi
    echo "$retval"
}
get_computed_default__computecfg_NN__name() {
    local group=$1; shift;

    local retval="SMRT Analysis Compute Configuration"

    local listnum;
    if [[ $group =~ ^computecfg_([0-9]+)$ ]] ; then
        # listnum=${BASH_REMATCH[1]};
        listnum=$(trim_listnum "${BASH_REMATCH[1]}")
        if [[ $listnum -ne 0 ]]; then
            listnum=$(( $listnum + 1 ));
            retval+=" #$listnum"
        fi
    fi
    echo "$retval"
}
get_computed_default__computecfg_NN__description() {
    local group=$1; shift;

    local retval;
    retval=$(get_cfg_val "$group" "name");
    echo "$retval"
}
get_computed_default__computecfg_NN__menuorder() {
    local group=$1; shift;

    local retval="99"

    local listnum;
    if [[ $group =~ ^computecfg_([0-9]+)$ ]] ; then
        listnum=$(trim_listnum "${BASH_REMATCH[1]}")
        listnum=$(( $listnum + 1 ))
        retval=$listnum;
    fi
    echo "$retval"
}
get_computed_default__computecfg_NN__jmstype() {
    local retval;
    retval=$( guess_jmsselect )
    echo "$retval"
}
get_computed_default__computecfg_NN__tmp_dir() {
    local retval;
    retval=$g_tmpdir_dirlink;
    echo "$retval"
}

get_computed_default__internal__version() {
    local retval=$g_versionstr_full;
    echo "$retval"
}

# -- hardware detection functions

hw_getnumprocs() {
    grep -E '^processor[[:space:]]*:' /proc/cpuinfo | wc -l
}

hw_get_physmem_bytes_DirectMapMethod() {
    # Add the DirectMap4k, DirectMap2M, DirectMap1G and any other DirectMap*
    # that shows up in /proc/meminfo, and round to nearest megabyte.
    # This should give us an even GB number (e.g. 32GiB, 64GiB) if all is
    # as expected.  It seems adding DirectMap4k and DirectMap2M gets us close
    # to a round number (off by 64k or 16K or so), so hopefully rounding to
    # the closest megabyte will give us the expected value.
    local hw_physmem_bytes="";

    local directmap_expr;
    directmap_expr=$(sed -n -e '/^[[:space:]]*DirectMap[0-9]\+[a-zA-Z]\+[[:space:]]*:/{s/.*:[[:space:]]*/ /; s/[kK][bB]/ * 1024 /; s/[mM][bB]/ * 1024 * 1024 /; s/[gG][bB]/ * 1024 * 1024 * 1024 /; s/$/+/; p}' /proc/meminfo)
    if [[ ! -z "$directmap_expr" ]] ; then
        # Add a zero to the end to complete the expression (add to the
        # dangling trailing '+' sign):
        directmap_expr="$directmap_expr 0"

        # Evaluate the expression
        hw_physmem_bytes=$(( $directmap_expr ));

        local mb_mult=$(( 1024 *  1024 ))
        local gb_mult=$(( 1024 *  1024 *  1024 ))
        if [[ $hw_physmem_bytes -le $(( 3 * $gb_mult )) ]] ; then
            # Round to the nearest 8 MiB
            hw_physmem_bytes=$(roundup "$hw_physmem_bytes" "$((8 * $mb_mult))")
        else
            # Round to the nearest GiB
            hw_physmem_bytes=$(roundup "$hw_physmem_bytes" "$((1 * $gb_mult))")
        fi
    fi

    echo "$hw_physmem_bytes"
}
hw_get_physmem_bytes_MemTotalMethod() {
    local hw_physmem_bytes="";
    local gb_mult=$(( 1024 *  1024 *  1024 ))
    local mb_mult=$(( 1024 *  1024 ))

    local memtotal_expr;
    memtotal_expr=$(sed -n -e '/^[[:space:]]*MemTotal[[:space:]]*:/{s/.*:[[:space:]]*/ /; s/[kK][bB]/ * 1024 /; s/[mM][bB]/ * 1024 * 1024 /; s/[gG][bB]/ * 1024 * 1024 * 1024 /; p}' /proc/meminfo)

    if [[ ! -z "$memtotal_expr" ]] ; then
        hw_physmem_bytes=$(( $memtotal_expr ));
        if [[ $hw_physmem_bytes -le $(( 1 * $gb_mult )) ]] ; then
            # If less than 1GB, round up to nearest 128MB boundary
            hw_physmem_bytes=$(roundup "$hw_physmem_bytes" "$(( 128 * $mb_mult ))")
        elif [[ $hw_physmem_bytes -le $(( 8 * $gb_mult )) ]] ; then
            # If less than 16 GB, round up to nearest 1GB boundary
            hw_physmem_bytes=$(roundup "$hw_physmem_bytes" "$(( 1 * $gb_mult ))")
        elif [[ $hw_physmem_bytes -le $(( 16 * $gb_mult )) ]] ; then
            # If less than 16 GB, round up to nearest 2GB boundary
            hw_physmem_bytes=$(roundup "$hw_physmem_bytes" "$(( 2 * $gb_mult ))")
        elif [[ $hw_physmem_bytes -le $(( 64 * $gb_mult )) ]] ; then
            # If less than 64 GB, round up to nearest 4GB boundary
            hw_physmem_bytes=$(roundup "$hw_physmem_bytes" "$(( 4 * $gb_mult ))")
        elif [[ $hw_physmem_bytes -le $(( 256 * $gb_mult )) ]] ; then
            # If less than 256 GB, round up to nearest 8GB boundary
            hw_physmem_bytes=$(roundup "$hw_physmem_bytes" "$(( 8 * $gb_mult ))")
        elif [[ $hw_physmem_bytes -le $(( 512 * $gb_mult )) ]] ; then
            # If less than 512 GB, round up to nearest 16GB boundary
            hw_physmem_bytes=$(roundup "$hw_physmem_bytes" "$(( 16 * $gb_mult ))")
        elif [[ $hw_physmem_bytes -le $(( 1024 * $gb_mult )) ]] ; then
            # If less than 1024 GB, round up to nearest 32GB boundary
            hw_physmem_bytes=$(roundup "$hw_physmem_bytes" "$(( 32 * $gb_mult ))")
        else
            # If greater than 1024 GB, round up to nearest 64B boundary
            hw_physmem_bytes=$(roundup "$hw_physmem_bytes" "$(( 64 * $gb_mult ))")
        fi
    fi

    echo "$hw_physmem_bytes"
}
hw_get_physmem_bytes_DmesgMethod() {
    local hw_physmem_bytes="";

    local dmesgmemory_expr;
    dmesgmemory_expr=$( (dmesg ||true) |sed -n -e 's/.*[[:space:]]*Memory[[:space:]]*:[[:space:]]*[0-9]\+[kKmMgG]B\?\/\([0-9]\+[kKmMgG]\)B\?[[:space:]]\+available.*/\1/p;' | sed -e 's/[kK]/ * 1024/; s/[mM]/ * 1024 * 1024/; s/[gG]/ *1024 * 1024 * 1024/;')

    if [[ ! -z "$dmesgmemory_expr" ]] ; then
        # Evaluate the expression
        hw_physmem_bytes=$(( $dmesgmemory_expr ));

        local mb_mult=$(( 1024 *  1024 ))
        local gb_mult=$(( 1024 *  1024 *  1024 ))
        if [[ $hw_physmem_bytes -le $(( 3 * $gb_mult )) ]] ; then
            # Round to the nearest 8 MiB
            hw_physmem_bytes=$(roundup "$hw_physmem_bytes" "$((8 * $mb_mult))")
        else
            # Round to the nearest GiB
            hw_physmem_bytes=$(roundup "$hw_physmem_bytes" "$((1 * $gb_mult))")
        fi
    fi

    echo "$hw_physmem_bytes"
}

hw_get_physmem_bytes() {
    if [[ ! -z "$g_hw_physmem_bytes" ]] ; then
        return 0;
    fi

    local hw_physmem_bytes_DirectMapMethod;
    hw_physmem_bytes_DirectMapMethod=$(hw_get_physmem_bytes_DirectMapMethod)

    local hw_physmem_bytes="";
    local hw_physmem_detectstr=""

    if  [[ ! -z "$hw_physmem_bytes_DirectMapMethod" ]] &&
        [[ $hw_physmem_bytes_DirectMapMethod =~ ^[0-9]+ ]] &&
        [[ $hw_physmem_bytes_DirectMapMethod -gt 1000000 ]]; then

        hw_physmem_bytes="$hw_physmem_bytes_DirectMapMethod"
        hw_physmem_detectstr="Detected Total Memory: $hw_physmem_bytes bytes (from DirectMap, meminfo)"
    fi

    # If the DirectMap method fails, do our best with trying to figure it
    # out from MemTotal in /proc/meminfo.  We should get something with this
    # approach.
    if [[ -z "$hw_physmem_bytes" ]] ; then
        local hw_physmem_bytes_MemTotalMethod;
        hw_physmem_bytes_MemTotalMethod=$(hw_get_physmem_bytes_MemTotalMethod)

        if  [[ ! -z "$hw_physmem_bytes_MemTotalMethod" ]] &&
            [[ $hw_physmem_bytes_MemTotalMethod =~ ^[0-9]+ ]] &&
            [[ $hw_physmem_bytes_MemTotalMethod -gt 1000000 ]]; then

            hw_physmem_bytes="$hw_physmem_bytes_MemTotalMethod"
            hw_physmem_detectstr="Detected Total Memory: $hw_physmem_bytes bytes (from MemTotal, meminfo)"
        fi
    fi

    # If the MemTotal method fails, we are probably in trouble any way, but
    # try dmesg as a last ditch effort.  It is the most flakey of the methods
    # and there is no guarantee that a normal user will even be able to
    # run dmesg (debian is turning that off).
    if [[ -z "$hw_physmem_bytes" ]] ; then
        local hw_physmem_bytes_DmesgMethod;
        hw_physmem_bytes_DmesgMethod=$(hw_get_physmem_bytes_DmesgMethod)

        if  [[ ! -z "$hw_physmem_bytes_DmesgMethod" ]] &&
            [[ $hw_physmem_bytes_DmesgMethod =~ ^[0-9]+ ]] &&
            [[ $hw_physmem_bytes_DmesgMethod -gt 1000000 ]]; then

            hw_physmem_bytes="$hw_physmem_bytes_DmesgMethod"
            hw_physmem_detectstr="Detected Total Memory: $hw_physmem_bytes bytes (from Memory, dmesg)"
        fi
    fi

    # If nothing worked, just bail out with an error message
    if [[ -z "$hw_physmem_bytes" ]] ; then
        merror "Could not determine total memory configuration from system."
    fi

    echo
    echo "$hw_physmem_detectstr"
    echo

    # Return the number of bytes of physical memory
    g_hw_physmem_bytes="$hw_physmem_bytes"
}

# Compute the physical memory, but only if memtotal was not overridden somehow
compute_physmem() {
    local usedefault;
    usedefault=$(get_cfg_usedefault "system" "memtotal")
    if $usedefault; then
        hw_get_physmem_bytes;
    fi
}

# -- jms related subroutines
sge_prompt_queuepe() {
    local computecfg_listgroup=$1; shift;

    local jobdesc=$1; shift;
    local sgeroot=$1; shift;
    local sgecell=$1; shift;
    local sgebindir=$1; shift;


    local sgequeue_val;
    local sgequeue_srcstr;
    sgequeue_val=$(get_cfg_val "$computecfg_listgroup" "sge_queue")
    sgequeue_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "sge_queue")

    local sgepe_val;
    local sgepe_srcstr;
    sgepe_val=$(get_cfg_val "$computecfg_listgroup" "sge_pe")
    sgepe_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "sge_pe")


    qconf_cmd1() {
        if [[ ! -z "$sgebindir" ]]; then
            SGE_ROOT="$sgeroot" SGE_CELL="$sgecell" "$sgebindir/qconf" ${1+"$@"}
        elif [[ -f "$sgeroot/$sgecell/common/settings.sh" ]]; then
            (set +o nounset; . "$sgeroot/$sgecell/common/settings.sh" > /dev/null; SGE_ROOT="$sgeroot" SGE_CELL="$sgecell" "qconf" ${1+"$@"})
        else
            #=-=-= #,, FIXME: what should we do here
            SGE_ROOT="$sgeroot" SGE_CELL="$sgecell" "$sgebindir/qconf" ${1+"$@"}
        fi
    }

    local stat;


    local all_queue_str
    stat=0
    all_queue_str=$(qconf_cmd1 -sql) || stat=$?
    if [[ $stat -ne 0 ]] ; then
        echo "Error in running 'qconf -sql'"
    fi

    local all_pe_str
    stat=0
    all_pe_str=$(qconf_cmd1 -spl) || stat=$?
    if [[ $stat -ne 0 ]] ; then
        echo "Error in running 'qconf -spl'"
    fi


    local -a all_queues;
    all_queues=( $all_queue_str )
    local -a all_pes;
    all_pes=( $all_pe_str )

    local selnum;
    local stat;
    while true; do
        if $opt_batch; then
            if [[ ! -z "$sgequeue_val" ]] ; then
                selnum=$(get_selection_number "$sgequeue_val" "${all_queues[@]}")
                if [[ -z "$selnum" ]]; then
                    echo "Invalid sgequeue specified '$sgequeue_val'.  Must be one of: ${all_queues[@]}"
                    abort;
                fi
            else
                sgequeue_val=${all_queues[0]}
                sgequeue_srcstr="detected setting"
            fi
        else
            # interactive
            if [[ ! -z "$sgequeue_val" ]] ; then
                selnum=$(get_selection_number "$sgequeue_val" "${all_queues[@]}")
            else
                selnum=$(get_selection_number "1" "${all_queues[@]}")
            fi
            [[ -z "$selnum" ]] && selnum=1;
            echo
            echo "Select the queue to use for $jobdesc jobs:"
            process_query "$selnum" "${all_queues[@]}"
            sgequeue_val=$gret_val;
            sgequeue_srcstr="selected interactively"
        fi

        # Determine the valid list of pes for this queue.  Must meet these
        # requirements:
        #    * the pe must be a valid pe
        #        (see "qconf -spl")
        #    * the pe must be in the queue "pe_list"
        #        (see 'qconf -sq queuename |grep pe_list')
        # commas are valid sge list separators, replace with spaces
        local queue_pelist;
        stat=0;
        queue_pelist=( $(qconf_cmd1 -sq "$sgequeue_val" | sed -e ':a;/\\$/{N;s/\\\n[[:space:]]*//;ba}' -e 's/,/ /g' | sed -ne 's/^[[:space:]]*pe_list[[:space:]]\+//p') ) || stat=$?
        if [[ $stat -ne 0 ]] ; then
            echo "Error detected in running 'qconf -sq $sgequeue_val'"
        fi
        local -a valid_pes;
        for pe in "${queue_pelist[@]+${queue_pelist[@]}}"; do
            # Determine if the pe is a valid pe (listed in 'qconf -spl')
            selnum=$(get_selection_number "$pe" "${all_pes[@]}")
            if [[ ! -z "$selnum" ]]; then
                valid_pes=( "${valid_pes[@]+${valid_pes[@]}}" "$pe" );
            fi
        done
        if [[ -z "${valid_pes[@]+${valid_pes[@]}}" ]]; then
            echo
            echo "No valid parallel environments found for queue '$sgequeue_val'."
            echo "A valid parallel environment must exist in 'qconf -spl' and"
            echo "in 'qconf -sq $sgequeue_val | grep pe_list'."
            echo "Please correct the sge configuration or choose another queue."
            if $opt_batch; then
                abort;
            fi
        else
            break;
        fi
    done

    if $opt_batch; then
        if [[ ! -z "$sgepe_val" ]] ; then
            selnum=$(get_selection_number "$sgepe_val" "${valid_pes[@]}")
            if [[ -z "$selnum" ]]; then
                echo "Invalid sgepe specified '$sgepe_val'.  Must be one of: ${valid_pes[@]}"
                abort;
            fi
        else
            sgepe_val=${valid_pes[0]}
            sgepe_srcstr="detected setting"
        fi
    else
        # interactive
        if [[ ! -z "$sgepe_val" ]] ; then
            selnum=$(get_selection_number "$sgepe_val" "${valid_pes[@]}")
        else
            selnum=$(get_selection_number "1" "${valid_pes[@]}")
        fi
        [[ -z "$selnum" ]] && selnum=1;
        echo
        echo "Select the parallel environment to use for $jobdesc jobs:"
        process_query "$selnum" "${valid_pes[@]}"
        sgepe_val=$gret_val;
        sgepe_srcstr="selected interactively"
    fi

    gret_sgequeuepe_sge_queue_val=$sgequeue_val;
    gret_sgequeuepe_sge_queue_srcstr=$sgequeue_srcstr;

    gret_sgequeuepe_sge_pe_val=$sgepe_val;
    gret_sgequeuepe_sge_pe_srcstr=$sgepe_srcstr;
}

sge_check_queue_pe() {
    local queue_desc=$1; shift;
    local sge_queue=$1; shift;
    local sge_pe=$1; shift;
    local sge_root=$1; shift;
    local sge_cell=$1; shift;
    local sge_bindir=$1; shift;
    local b_check_peslots=$1;shift;

    local stat;

    echo
    echo "Checking setting for the $queue_desc queue ($sge_queue, $sge_pe)..."
    if   [[   -z "$sge_queue" ]] && [[   -z "$sge_pe" ]]; then
        # We don't expect that this should happen.  Just give a terse
        # error and abort.
        echo "Unexpected condition for $queue_desc queue:  queue and pe are undefined."
        abort;
    elif [[   -z "$sge_queue" ]] && [[ ! -z "$sge_pe" ]]; then
        # We don't expect that this should happen.  Just give a terse
        # error and abort.
        echo "Unexpected condition for $queue_desc queue:  queue is undefined, but"
        echo "pe is defined ($sge_pe)."
        abort;
    elif [[ ! -z "$sge_queue" ]] && [[   -z "$sge_pe" ]]; then
        # We don't expect that this should happen.  Just give a terse
        # error and abort.
        echo "Unexpected condition for $queue_desc queue:  queue is defined ($sge_queue),"
        echo "but pe is undefined."
        abort;
    elif [[ ! -z "$sge_queue" ]] && [[ ! -z "$sge_pe" ]]; then
        # This case is the valid one, queue and pe defined
        :
    else
        # Musta screwed up the conditional above somehow
        minterror "sge_check_queue_pe(): Invalid conditional in sge_check_queue_pe().  ($sge_queue, $sge_pe)";
    fi

    qconf_cmd() {
        if [[ ! -z "$sge_bindir" ]]; then
            SGE_ROOT="$sge_root" SGE_CELL="$sge_cell" "$sge_bindir/qconf" ${1+"$@"}
        elif [[ -f "$sge_root/$sge_cell/common/settings.sh" ]]; then
            (set +o nounset; . "$sge_root/$sge_cell/common/settings.sh" > /dev/null; SGE_ROOT="$sge_root" SGE_CELL="$sge_cell" "qconf" ${1+"$@"})
        else
            #=-=-= #,, FIXME: what should we do here
            SGE_ROOT="$sge_root" SGE_CELL="$sge_cell" "$sge_bindir/qconf" ${1+"$@"}
        fi
    }

    # Check if the queue is a valid queue
    echo "  Checking that the queue is valid..."
    if ! qconf_cmd -sql | grep -E "^$sge_queue" > /dev/null ; then
        echo
        echo "The $queue_desc queue ($sge_queue) is not a valid queue.  It must"
        echo "be listed in the output of 'qconf -sql'"
        abort;
    fi

    # Check if pe is valid
    echo "  Checking that the pe is valid..."
    if ! qconf_cmd -spl | grep -E "^$sge_pe" > /dev/null ; then
        echo
        echo "The $queue_desc parallel environment ($sge_pe) is not a"
        echo "valid parallel environment.  It must be listed in the"
        echo "output of 'qconf -spl'"
        abort;
    fi

    # Check if pe is in the pe_list of pes for the queue
    echo "  Checking that the pe is in the queue pe_list..."
    local queue_pe_list;
    # commas are valid sge list separators, replace with spaces
    stat=0
    queue_pe_list=$(qconf_cmd -sq "$sge_queue" | sed -e ':a;/\\$/{N;s/\\\n[[:space:]]*//;ba}' -e 's/,/ /g' | sed -ne 's/^[[:space:]]*pe_list[[:space:]]\+//p') || stat=$?
    if [[ $stat -ne 0 ]] ; then
        echo "Error detected in running 'qconf -sq $sge_queue'"
    fi
    if [[ ! " $queue_pe_list " =~ \ $sge_pe\  ]]; then
        echo
        echo "The $queue_desc parallel environment ($sge_pe) is in the"
        echo "pe_list for the queue ($sge_queue).  It must be listed in the"
        echo "output of 'qconf -sq $sge_queue | grep pe_list'"
        abort;
    fi

    if $b_check_peslots; then
        # Check if the allocation_rule for pe is $pe_slots
        echo "  Checking the pe allocation_rule..."
        local allocation_rule;
        # commas are valid sge list separators, replace with spaces
        stat=0
        allocation_rule=$(qconf_cmd -sp "$sge_pe" | sed -e ':a;/\\$/{N;s/\\\n[[:space:]]*//;ba}' -e 's/,/ /g' | sed -ne 's/^[[:space:]]*allocation_rule[[:space:]]\+//p') || stat=$?
        if [[ $stat -ne 0 ]] ; then
            echo "Error detected in running 'qconf -sp $sge_pe'"
        fi
        if [[ x"$allocation_rule" != x'$pe_slots' ]]; then
            echo
            echo "The allocation_rule for the $queue_desc '$sge_pe' parallel environment is"
            echo "set to '$allocation_rule'.  In order to run multiple Celera Assembler"
            echo "sub-jobs in distribution mode, the allocation_rule must be set to"
            echo "'\$pe_slots'.  However, a single Celera Assembler job (no sub-jobs) can"
            echo "still be distributed without changing the allocation_rule.  To change"
            echo "the allocation_rule, select 'n' below and run 'qconf -mp $sge_pe'"
            echo "before reinvoking."
            echo ""
            if $opt_batch; then
                ans="y";
            else
                read -p "Continue without changing the allocation_rule? [Y/n]: " ans;
                logonly "$ans";
                ans=$(echo "$ans" | tr "[[:upper:]]" "[[:lower:]]")
                [[ -z "$ans" ]] && ans="y";
                if [[ $ans =~ ^(y|yes)$ ]] ; then
                    ans="y"
                else
                    ans="n"
                fi
            fi
            if [[ x"$ans" != x"y" ]] ; then
                abort;
            fi
        fi
    fi

    local host;

    # Get the hostlist name for the queue
    echo "  Checking the queue hostlist..."
    local queue_hostlist;
    # commas are valid sge list separators, replace with spaces
    stat=0
    queue_hostlist=$(qconf_cmd -sq "$sge_queue" | sed -e ':a;/\\$/{N;s/\\\n[[:space:]]*//;ba}' -e 's/,/ /g' | sed  -ne 's/^[[:space:]]*hostlist[[:space:]]\+//p') || stat=$?
    if [[ $stat -ne 0 ]] ; then
        echo "Error detected in running 'qconf -sq $sge_queue'"
    fi

    local grouplist;
    grouplist=$(qconf_cmd -shgrpl || true)
    # convert to space separated
    grouplist=$(echo $grouplist)
    # Get a space separated list of hosts for the queue
    local queue_hosts="";
    for host in $queue_hostlist; do
        # NOTE:  it could be possible that it a host listed in the queue
        #        hostlist is simply ignored if it is not an execution host.
        #        If so we probably want to add the host to the queue_hosts
        #        list only if it is an execute host (and eliminate the
        #        excecution host check below).
        if [[ " $grouplist " =~ \ $host\  ]] ; then
            # This is one of the host groups that is defined, replace it
            # with the resolved list of hosts
            local hosts_resolved;
            hosts_resolved=$(qconf_cmd -shgrp_resolved "$host") || true
            # Convert comma separators, just in case
            hosts_resolved=${hosts_resolved//,/ }
            queue_hosts="$queue_hosts $hosts_resolved";
        else
            queue_hosts="$queue_hosts $host";
        fi
    done

    if [[ $queue_hosts =~ ^[[:space:]]*$ ]]; then
        queue_hosts=""
    fi

    if [[ -z "$queue_hosts" ]] ; then
        echo
        echo "The $queue_desc queue ($sge_queue) does not have any hosts in"
        echo "the hostlist.  There must be at least one host in the queue"
        echo "hostlist.   The hostlist can be listed with:"
        echo "    qconf -sq $sge_queue | grep hostlist"
        echo "and host groups listed in the hostlist can be listed with"
        echo "    qconf -shgrp <host_groupname>"
        echo "    qconf -shgrp_tree <host_groupname>"
        echo "    qconf -shgrp_resolved <host_groupname>"
        abort;
    fi
}


prompt_jms_sge() {
    local computecfg_listgroup=$1; shift;

    gret__jmsconfigured=false;

    local sgeroot_val;
    local sgecell_val;
    local sgebindir_val;
    local sge_use_settings_file_val;

    local sgeroot_srcstr;
    local sgecell_srcstr;
    local sgebindir_srcstr;
    local sge_use_settings_file_srcstr;

    sgeroot_val=$(get_cfg_val "$computecfg_listgroup" "sge_sgeroot")
    sgeroot_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "sge_sgeroot")

    sgecell_val=$(get_cfg_val "$computecfg_listgroup" "sge_sgecell")
    sgecell_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "sge_sgecell")

    sgebindir_val=$(get_cfg_val "$computecfg_listgroup" "sge_bindir")
    sgebindir_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "sge_bindir")

    sge_use_settings_file_val=$(get_cfg_val "$computecfg_listgroup" "sge_bindir")
    sge_use_settings_file_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "sge_bindir")

    sgestartargs_val=$(get_cfg_val "$computecfg_listgroup" "sge_startargs")
    sgestartargs_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "sge_startargs")

    echo
    echo "Detecting SGE setup (locations of binaries, SGE_ROOT, SGE_CELL)..."
    if [[ -z "$sgeroot_val" ]] ; then
        if [[ ! -z "${SGE_ROOT:-}" ]] ; then
            sgeroot_val=$SGE_ROOT;
            sgeroot_srcstr="SGE_ROOT environment variable";
        fi
    fi
    if [[ -z "$sgecell_val" ]] ; then
        if [[ ! -z "${SGE_CELL:-}" ]] ; then
            sgecell_val=$SGE_CELL;
            sgecell_srcstr="SGE_CELL environment variable";
        fi
    fi

    local sgeroot_from_wrapper_script="";
    local sgecell_from_wrapper_script="";


    find_sge_bindirs_frompath "$sgebindir_val";
    local subtypebindirs_arr;
    subtypebindirs_arr=( "${gret__find_sge_bindirs_frompath[@]+${gret__find_sge_bindirs_frompath[@]}}" );

    if [[ -z "$sgebindir_val" ]] ; then
        if [[ ! -z "${subtypebindirs_arr[@]+${subtypebindirs_arr[@]}}" ]]; then
            sgebindir_val="${subtypebindirs_arr[0]}"
            sgebindir_val="${sgebindir_val#*,}"
            sgebindir_srcstr="PATH environment variable, $sgebindir_srcstr"
            set_cfg_val "$computecfg_listgroup" "sge_bindir" "$sgebindir_val" "$sgebindir_srcstr" false;
        fi
    fi

    local qconfpath="";
    if [[ ! -z "$sgebindir_val" ]] ; then
        qconfpath="$sgebindir_val/qconf"
    fi

    local shebang;
    local shx_output;
    if [[ -z "$sgeroot_val" ]] && [[ ! -z "$qconfpath" ]] ; then
        # If qconfpath is a shell script, this might be an ubuntu system
        shebang=$(sed -ne '1 { s,^#![[:space:]]*\(/bin/[^[:space:]]*sh\),\1,p }' "$qconfpath");
        if [[ ! -z "$shebang" ]] ; then
            shx_output=$($shebang -x "$qconfpath" --help 2>&1 || true)
            sgeroot_val=$(echo "$shx_output" | sed -ne 's/^+\+[[:space:]]*SGE_ROOT=//p');
            sgecell_val=$(echo "$shx_output" | sed -ne 's/^+\+[[:space:]]*SGE_CELL=//p');
            [[ ! -z "$sgeroot_val" ]] && sgeroot_srcstr="defaults in qconf script";
            [[ ! -z "$sgeroot_val" ]] && sgeroot_from_wrapper_script=$sgeroot_val;
            [[ ! -z "$sgecell_val" ]] && sgecell_srcstr="defaults in qconf script";
            [[ ! -z "$sgecell_val" ]] && sgecell_from_wrapper_script=$sgecell_val;
        fi
    fi
    local sge_sysconfig;
    if [[ -z "$sgeroot_val" ]] ; then
        # if /etc/sysconfig/gridengine exists, this may be a centos rpm sge
        sge_sysconfig="/etc/sysconfig/gridengine"
        if [[ -r "$sge_sysconfig" ]] ; then
            sgeroot_val=$(sed -n -e 's/["'\'']//g' -e 's/^[[:space:]]*SGE_ROOT=//p' "$sge_sysconfig");
            sgecell_val=$(sed -n -e 's/["'\'']//g' -e 's/^[[:space:]]*SGE_CELL=//p' "$sge_sysconfig");
            [[ ! -z "$sgeroot_val" ]] && sgeroot_srcstr="$sge_sysconfig";
            [[ ! -z "$sgecell_val" ]] && sgecell_srcstr="$sge_sysconfig";
        fi
    fi
    local settings_file;
    if [[ -z "$qconfpath" ]] && [[ ! -z "$sgeroot_val" ]] ; then
        # Could be the case of a tarball sge install, try to check the
        # settings.sh file
        if [[ ! -z "$sgecell_val" ]]; then
            settings_file="$sgeroot_val/$sgecell_val/common/settings.sh"
        else
            settings_file="$sgeroot_val/default/common/settings.sh"
        fi
        if [[ -r "$settings_file" ]] ; then
            qconfpath=$(bash -c 'set +o nounset; . '"$settings_file"'; which qconf')
            sgebindir_val=$(dirname "$qconfpath");
            if [[ ! -z "$sgebindir_val" ]]; then
                sgebindir_srcstr="settings file: $settings_file";
                sgebindir_from_settings=$settings_file;
                if [[ -z "$sgecell_val" ]]; then
                    sgecell_val=default;
                    sgecell_srcstr="inferred default value";
                fi
            fi
        fi
    fi

    if [[ ! -z "$sgebindir_val" ]]; then
        if [[ ! -x "$sgebindir_val/qsub" ]]; then
            mwarn "Could not find 'qsub' executable in '$sgebindir_val', but 'qconf' exists";
        fi
    fi

    local detect_error=false;
    if  [[ -z "$sgeroot_val" ]] ||
        [[ -z "$sgecell_val" ]] ||
        [[ -z "$sgebindir_val" ]] ; then
        detect_error=true;
    fi


    if $detect_error || ! $opt_batch; then
        echo
        echo "Detected the following settings:"
        echo  "      SGE_ROOT=$sgeroot_val";
        echo  "      SGE_CELL=$sgecell_val";
        echo  "      SGE_BINDIR=$sgebindir_val";

        echo
        echo  "   Where detected:"
        echo  "      SGE_ROOT          (from '$sgeroot_srcstr')";
        echo  "      SGE_CELL          (from '$sgecell_srcstr')";
        echo  "      SGE_BINDIR        (from '$sgebindir_srcstr')";
        echo
    fi

    local ans;
    if $detect_error; then
        if $opt_batch; then
            echo "Could not determine some SGE settings, please specify them on command-line."
            abort;
        else
            echo "Could not determine some settings, please specify them."
            ans="n"
        fi
    else
        if $opt_batch; then
            ans=y;
        else
            ans=$(yesno_prompt "Are these correct?")
        fi
    fi

    local nsgeroot;
    local nsgecell;
    local nsgebindir;
    if [[ x"$ans" != x"y" ]]; then
        while true; do
            nsgeroot=$sgeroot_val;
            nsgecell=$sgecell_val;
            nsgebindir=$sgebindir_val;

            set_cfg_val "$computecfg_listgroup" "sge_sgeroot" "$sgeroot_val" "$sgeroot_srcstr" false;
            echo
            assign_cfg_val_interactive "$computecfg_listgroup" "sge_sgeroot";
            if $gret__interrupted; then
                return 0
            fi
            nsgeroot=$(get_cfg_val "$computecfg_listgroup" "sge_sgeroot")

            local defcell="";
            local -a cells=( );
            for i in $nsgeroot/*/common; do
                i=${i%/common}
                i=${i#$nsgeroot/}
                if [[ x"$i" == x"*" ]] ; then
                    break;
                fi
                cells=( "${cells[@]+${cells[@]}}" "$i" );
                [[ -z "$defcell" ]] && [[ x"$i" == x"default" ]] && defcell="default";
                [[ x"$i" == x"$nsgecell" ]] && defcell="$nsgecell";
            done
            [[ -z "$defcell" ]] && defcell=1;

            if [[ -z "${cells[@]+${cells[@]}}" ]] ; then
                echo "Could not find any SGE_CELL directories, trying again..."
                echo;
                continue;
            fi

            echo
            echo "Select SGE_CELL:";
            process_query "$defcell" "${cells[@]}"
            nsgecell=$gret_val;

            if [[ ! -z "$nsgeroot" ]] && [[ ! -z "$nsgecell" ]]; then
                settings_file="$nsgeroot/$nsgecell/common/settings.sh"

                if [[ -r "$settings_file" ]] ; then
                    qconfpath=$(bash -c 'set +o nounset; . '"$settings_file"'; which qconf')
                    sgebindir_val=$(dirname "$qconfpath");
                    nsgebindir=$sgebindir_val;
                fi
            fi
            set_cfg_val "$computecfg_listgroup" "sge_sgecell" "$nsgecell" "configured interactively" false;


            while true; do
                set_cfg_val "$computecfg_listgroup" "sge_bindir" "$sgebindir_val" "$sgebindir_srcstr" false;
                echo
                assign_cfg_val_interactive "$computecfg_listgroup" "sge_bindir";
                if $gret__interrupted; then
                    return 0
                fi

                nsgebindir=$(get_cfg_val "$computecfg_listgroup" "sge_bindir")

                if ! verify_sge_bindir "$nsgebindir"; then
                    echo "    Could not find required executables in '$nsgebindir' ($gret__verify_pbs_bindir__missingprogs).  Try again..."
                    echo
                    continue;
                fi
                break;
            done

            if [[ ! -d "$nsgeroot/$nsgecell/common" ]] ; then
                echo "Could not find directory '$nsgeroot/$nsgecell/common', trying again..."
                echo
                continue;
            fi
            # All the values look good, break out of the loop
            sgebindir_val=$nsgebindir;
            sgeroot_val=$nsgeroot;
            sgecell_val=$nsgecell;
            sgebindir_srcstr="configured interactively"
            sgeroot_srcstr="configured interactively"
            sgecell_srcstr="configured interactively"
            break;
        done
    fi

    echo
    echo "Using the following settings:"
    echo "    SGE_ROOT=$sgeroot_val";
    echo "    SGE_CELL=$sgecell_val";
    echo "    SGE_BINDIR=$sgebindir_val";

    local sgequeue_val;
    local sgepe_val;
    sge_prompt_queuepe "$computecfg_listgroup" "SMRT Analysis" "$sgeroot_val" "$sgecell_val" "$sgebindir_val";
    sgequeue_val="$gret_sgequeuepe_sge_queue_val";
    sgequeue_srcstr="$gret_sgequeuepe_sge_queue_srcstr";
    sgepe_val="$gret_sgequeuepe_sge_pe_val";
    sgepe_srcstr="$gret_sgequeuepe_sge_pe_srcstr";


    if [[ x"$sgeroot_val" == x"$sgeroot_from_wrapper_script" ]]; then
        sgeroot_val="";
    fi
    if [[ x"$sgecell_val" == x"$sgecell_from_wrapper_script" ]]; then
        sgecell_val="";
    fi

    local sge_use_settings_file=false;
    if [[ -r "$sgeroot_val/$sgecell_val/common/settings.sh" ]]; then
        sge_use_settings_file=true;
    fi

    if ! $opt_batch; then
        echo
        echo "Additional arguments to the SGE job submission command may be"
        echo "added in SGE_STARTARGS.  The default job submission command is:"
        echo ""
        echo '     qsub -S /bin/bash -V -q ${QUEUE} -N ${JOB_NAME} \'
        echo '         -o ${STDOUT_FILE} -e ${STDERR_FILE} \'
        echo '         -pe ${PE} ${NPROC} ${CMD}'
        echo ""
    fi

    assign_cfg_val_interactive "$computecfg_listgroup" "sge_startargs" "";
    if $gret__interrupted; then
        return 0
    fi


    set_cfg_val "$computecfg_listgroup" "sge_sgeroot" "$sgeroot_val" "$sgeroot_srcstr" false;
    set_cfg_val "$computecfg_listgroup" "sge_sgecell" "$sgecell_val" "$sgecell_srcstr" false;
    set_cfg_val "$computecfg_listgroup" "sge_bindir" "$sgebindir_val" "$sgebindir_srcstr" false;
    set_cfg_val "$computecfg_listgroup" "sge_queue" "$sgequeue_val" "$sgequeue_srcstr" false;
    set_cfg_val "$computecfg_listgroup" "sge_pe" "$sgepe_val" "$sgepe_srcstr" false;
    set_cfg_val "$computecfg_listgroup" "sge_use_settings_file" "$sge_use_settings_file" "computed" false;
    # sgestartargs already set via 'assign_cfg_val_interactive' above

    # Make sure all the settings are marked as not optional, so they get saved
    set_cfg_optional "$computecfg_listgroup" "sge_sgeroot" "false";
    set_cfg_optional "$computecfg_listgroup" "sge_sgecell" "false";
    set_cfg_optional "$computecfg_listgroup" "sge_bindir" "false";
    set_cfg_optional "$computecfg_listgroup" "sge_queue" "false";
    set_cfg_optional "$computecfg_listgroup" "sge_pe" "false";
    set_cfg_optional "$computecfg_listgroup" "sge_startargs" "false";
    set_cfg_optional "$computecfg_listgroup" "sge_use_settings_file" "false";

    # Run sanity checks on the sge queues and parallel environments.
    # Check the peslots setting is set to '$pe_slots'.
    sge_check_queue_pe "SGE" \
        "$sgequeue_val" "$sgepe_val" \
        "$sgeroot_val" "$sgecell_val" "$sgebindir_val" \
        true;

    gret__jmsconfigured=true;
}

prompt_jms_lsf() {
    local computecfg_listgroup=$1; shift;

    gret__jmsconfigured=false;

    local lsfbindir_val;
    local lsfbindir_srcstr;

    local lsfqueue_val;
    local lsfqueue_srcstr;

    local lsfstartargs_val;
    local lsfstartargs_srcstr;

    lsfbindir_val=$(get_cfg_val "$computecfg_listgroup" "lsf_bindir")
    lsfbindir_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "lsf_bindir")

    lsfqueue_val=$(get_cfg_val "$computecfg_listgroup" "lsf_queue")
    lsfqueue_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "lsf_queue")

    lsfstartargs_val=$(get_cfg_val "$computecfg_listgroup" "lsf_startargs")
    lsfstartargs_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "lsf_startargs")

    find_lsf_bindirs_frompath "$lsfbindir_val";
    local subtypebindirs_arr;
    subtypebindirs_arr=( "${gret__find_lsf_bindirs_frompath[@]+${gret__find_lsf_bindirs_frompath[@]}}" );

    if [[ -z "$lsfbindir_val" ]] ; then
        if [[ ! -z "${subtypebindirs_arr[@]+${subtypebindirs_arr[@]}}" ]]; then
            lsfbindir_val="${subtypebindirs_arr[0]}"
            lsfbindir_val="${lsfbindir_val#*,}"
            lsfbindir_srcstr="PATH environment variable, $lsfbindir_srcstr"
            set_cfg_val "$computecfg_listgroup" "lsf_bindir" "$lsfbindir_val" "$lsfbindir_srcstr" false;
        fi
    fi

    # If no queue specified, determine the LSF default queue
    local bqueues_output="";
    local lsf_default_queue="<undetected>";
    if [[ -z "$lsfqueue_val" ]] ; then
        if [[ ! -z "$lsfbindir_val" ]] && [[ -x "$lsfbindir_val/bqueues" ]]; then
            bqueues_output=$("$lsfbindir_val/bqueues" -l || true)
            # Use the sed 'hold' buffer to print out the queue if we detect
            # the default queue in the next line.
            lsf_default_queue=$(echo "$bqueues_output" | sed -ne '/^[[:space:]]*--[[:space:]].*This is the default queue\.\?[[:space:]]*$/I {g;1!p;};/^QUEUE:[[:space:]]*/ { s/^QUEUE:[[:space:]]*//; h}' )
        fi
    fi
    if [[ -z "$lsf_default_queue" ]] ; then
        lsf_default_queue="<undetected>";
    fi

    local detect_error=false;
    if [[ -z "$lsfbindir_val" ]]; then
        detect_error=true;
    elif [[ -z "$lsfqueue_val" ]] &&
         [[ x"$lsf_default_queue" == x"<undetected>" ]] ; then
        detect_error=true;
    fi

    if $detect_error || ! $opt_batch; then
        echo
        echo "Detected the following settings:"
        echo
        echo  "      LSF_BINDIR=$lsfbindir_val";
        if [[ -z "$lsfqueue_val" ]]; then
            echo  "      LSF_QUEUE=   (Use default queue, currently: '$lsf_default_queue')"
        else
            echo  "      LSF_QUEUE=$lsfqueue_val"
        fi
        echo  "      LSF_STARTARGS=$lsfstartargs_val"
        echo
        echo  "   Where detected:"
        echo  "      LSF_BINDIR        (from '$lsfbindir_srcstr')";
        echo  "      LSF_QUEUE         (from '$lsfqueue_srcstr')";
        echo  "      LSF_STARTARGS     (from '$lsfstartargs_srcstr')";
        echo
    fi


    if $opt_batch; then
        if $detect_error; then
            echo "Could not determine LSF_BINDIR setting, please specify on command-line."
            abort;
        fi
        if  [[ -z "$lsfqueue_val" ]] &&
            [[ x"$lsf_default_queue" == x"<undetected>" ]] ; then

            echo
            echo "Could not determine LSF_QUEUE setting, please specify on command-line."
            abort;
        fi
    else
        local ans;
        if [[ -z "$lsfbindir_val" ]]; then
            echo
            echo "Could not determine LSF_BINDIR setting, please specify below."
            ans="n"
        elif [[ -z "$lsfqueue_val" ]] &&
             [[ x"$lsf_default_queue" == x"<undetected>" ]] ; then
            echo
            echo "Could not determine LSF_QUEUE setting, please specify below."
            ans="n"
        else
            ans=$(yesno_prompt "Are these correct?")
        fi

        if [[ x"$ans" != x"y" ]]; then
            local nlsfbindir;
            echo
            while true; do
                assign_cfg_val_interactive "$computecfg_listgroup" "lsf_bindir";
                if $gret__interrupted; then
                    return 0
                fi

                nlsfbindir=$(get_cfg_val "$computecfg_listgroup" "lsf_bindir")
                if verify_lsf_bindir "$nlsfbindir"; then
                    # All the values look good, break out of the loop
                    lsfbindir_val=$nlsfbindir;
                    lsfbindir_srcstr="configured interactively"
                    break;
                fi

                echo "    Could not find required executables in '$nlsfbindir' ($gret__verify_lsf_bindir__missingprogs).  Try again..."
                echo
            done


            local -a all_queues;
            bqueues_output=$("$lsfbindir_val/bqueues" -l || true)
            all_queues=( $(echo "$bqueues_output" | sed -ne 's/^QUEUE:[[:space:]]*//p') )

            if [[ -z "${all_queues[@]+${all_queues[@]}}" ]] ; then
                echo "No queues found, specify manually..."
                assign_cfg_val_interactive "$computecfg_listgroup" "lsf_queue";
                if $gret__interrupted; then
                    return 0
                fi
            else
                # Recompute the default queue (we could be pointed at a
                # different bin directory now)
                lsf_default_queue=$(echo "$bqueues_output" | sed -ne '/^[[:space:]]*--[[:space:]].*This is the default queue\.\?[[:space:]]*$/I {g;1!p;};/^QUEUE:[[:space:]]*/ { s/^QUEUE:[[:space:]]*//; h}' ) || true
                if [[ -z "$lsf_default_queue" ]] ; then
                    lsf_default_queue="<undetected>";
                fi

                # Update the text for the LSF default queue
                if [[ x"$lsf_default_queue" != x"<undetected>" ]]; then
                    local i;
                    for ((i=0; i < ${#all_queues[@]} ; i++)); do
                        if [[ x"${all_queues[$i]}" == x"$lsf_default_queue" ]] ; then
                            all_queues[$i]="${lsf_default_queue}@@$(printf "%-12s  (LSF default queue)" "${lsf_default_queue}")"
                        fi
                    done

                    all_queues+=( "__NONE__@@--NONE--      (Use LSF default queues, currently '$lsf_default_queue')" )
                fi

                local selnum;
                if  [[ -z "$lsfqueue_val" ]] &&
                    [[ x"$lsf_default_queue" != x"<undetected>" ]]; then
                    selnum="__NONE__"
                else
                    selnum=$(get_selection_number "$lsfqueue_val" "${all_queues[@]}")
                    if [[ x"$lsf_default_queue" != x"<undetected>" ]]; then
                        [[ -z "$selnum" ]] && selnum="__NONE__";
                    else
                        [[ -z "$selnum" ]] && selnum="1";
                    fi
                fi

                echo
                echo "  Select LSF_QUEUE:"
                process_query --sp "    "  "$selnum" "${all_queues[@]}"
                echo

                lsfqueue_val=$gret_val;
                [[ x"$lsfqueue_val" == x"__NONE__" ]] && lsfqueue_val="";
                lsfqueue_srcstr="configured interactively"
                set_cfg_val "$computecfg_listgroup" "lsf_queue" "$lsfqueue_val" "$lsfqueue_srcstr" false;
            fi


            if ! $opt_batch; then
                echo
                echo "Additional arguments to the LSF job submission command may be"
                echo "added in LSF_STARTARGS.  The default job submission command is:"
                echo ""
                echo '     bsub -J ${JOB_NAME} -o ${STDOUT_FILE} -e ${STDERR_FILE} \'
                echo '         -n ${NPROC} -q ${QUEUE} -R "span[hosts=1]" ${CMD}'
                echo ""
            fi

            assign_cfg_val_interactive "$computecfg_listgroup" "lsf_startargs";
            if $gret__interrupted; then
                return 0
            fi

        fi
    fi

    # Make sure all the settings are marked as not optional, so they get saved
    set_cfg_optional "$computecfg_listgroup" "lsf_bindir" "false";
    set_cfg_optional "$computecfg_listgroup" "lsf_queue" "false";
    set_cfg_optional "$computecfg_listgroup" "lsf_startargs" "false";

    gret__jmsconfigured=true;
}

prompt_jms_pbs() {
    local computecfg_listgroup=$1; shift;

    gret__jmsconfigured=false;

    local pbsbindir_val;
    local pbsbindir_srcstr;

    local pbsqueue_val;
    local pbsqueue_srcstr;

    local pbsstartargs_val;
    local pbsstartargs_srcstr;

    local pbsstopcmd_val;
    local pbsstopcmd_srcstr;

    pbsbindir_val=$(get_cfg_val "$computecfg_listgroup" "pbs_bindir")
    pbsbindir_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "pbs_bindir")

    pbsqueue_val=$(get_cfg_val "$computecfg_listgroup" "pbs_queue")
    pbsqueue_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "pbs_queue")

    pbsstartargs_val=$(get_cfg_val "$computecfg_listgroup" "pbs_startargs")
    pbsstartargs_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "pbs_startargs")

    find_pbs_bindirs_frompath "$pbsbindir_val";
    local subtypebindirs_arr;
    subtypebindirs_arr=( "${gret__find_pbs_bindirs_frompath[@]+${gret__find_pbs_bindirs_frompath[@]}}" );

    if [[ -z "$pbsbindir_val" ]] ; then
        if [[ ! -z "${subtypebindirs_arr[@]+${subtypebindirs_arr[@]}}" ]]; then
            pbsbindir_val="${subtypebindirs_arr[0]}"
            pbsbindir_val="${pbsbindir_val#*,}"
            pbsbindir_srcstr="PATH environment variable, $pbsbindir_srcstr"
            set_cfg_val "$computecfg_listgroup" "pbs_bindir" "$pbsbindir_val" "$pbsbindir_srcstr" false;
        fi
    fi

    # If no queue specified, determine the PBS default queue
    local pbs_default_queue="";
    if [[ ! -z "$pbsbindir_val" ]] ; then
        pbs_default_queue=$(echo $("$pbsbindir_val/qmgr" -c "list server default_queue" | sed -n -e 's/^[[:space:]]*default_queue[[:space:]]*=[[:space:]]*//p' | sed -ne '1p')) || true
    fi
    if [[ -z "$pbs_default_queue" ]] ; then
        pbs_default_queue="<undetected>";
    fi

    local detect_error=false;
    if [[ -z "$pbsbindir_val" ]]; then
        detect_error=true;
    elif [[ -z "$pbsqueue_val" ]] &&
         [[ x"$pbs_default_queue" == x"<undetected>" ]] ; then
        detect_error=true;
    fi

    if $detect_error || ! $opt_batch; then
        echo
        echo "Detected the following settings:"
        echo
        if [[ -z "$pbsbindir_val" ]]; then
            echo  "   PBS_BINDIR=        (not detected in PATH)";
        else
            echo  "   PBS_BINDIR=$pbsbindir_val";
        fi
        if [[ -z "$pbsqueue_val" ]]; then
            echo  "   PBS_QUEUE=     (Use PBS default queue, currently: '$pbs_default_queue')";
        else
            echo  "   PBS_QUEUE=$pbsqueue_val"
        fi
        echo  "   PBS_STARTARGS=$pbsstartargs_val"
        echo
        echo  "   Where detected:"
        echo  "      PBS_BINDIR        (from '$pbsbindir_srcstr')";
        echo  "      PBS_QUEUE         (from '$pbsqueue_srcstr')";
        echo  "      PBS_STARTARGS     (from '$pbsstartargs_srcstr')";
        echo
    fi

    if $opt_batch; then
        if $detect_error; then
            echo
            echo "Could not determine PBS_BINDIR setting, please specify on command-line."
            abort;
        fi
        if  [[ -z "$pbsqueue_val" ]] &&
            [[ x"$pbs_default_queue" == x"<undetected>" ]] ; then

            echo
            echo "Could not determine PBS_QUEUE setting, please specify on command-line."
            abort;
        fi
    fi

    if ! $opt_batch; then
        local ans;
        if [[ -z "$pbsbindir_val" ]]; then
            echo
            echo "Could not determine PBS_BINDIR setting, please specify below."
            ans="n"
        elif [[ -z "$pbsqueue_val" ]] &&
             [[ x"$pbs_default_queue" == x"<undetected>" ]] ; then
            echo
            echo "Could not determine PBS_QUEUE setting, please specify below."
            ans="n"
        else
            ans=$(yesno_prompt "Are these correct?")
        fi

        if [[ x"$ans" != x"y" ]]; then
            local npbsbindir;
            echo
            while true; do
                assign_cfg_val_interactive "$computecfg_listgroup" "pbs_bindir";
                if $gret__interrupted; then
                    return 0
                fi

                npbsbindir=$(get_cfg_val "$computecfg_listgroup" "pbs_bindir")

                if verify_pbs_bindir "$npbsbindir"; then
                    # All the values look good, break out of the loop
                    pbsbindir_val=$npbsbindir;
                    pbsbindir_srcstr="configured interactively"
                    break;
                fi

                echo "    Could not find required executables in '$npbsbindir' ($gret__verify_pbs_bindir__missingprogs).  Try again..."
                echo
            done

            local -a all_queues;
            local stat;
            pbsqueues=( $("$pbsbindir_val/qstat" -f -Q | sed -n -e 's/^Queue: //p') ) || stat=$?
            all_queues=( "${pbsqueues[@]+${pbsqueues[@]}}" )

            if [[ -z "${all_queues[@]+${all_queues[@]}}" ]] ; then
                if [[ $stat -ne 0 ]] ; then
                    echo "Errorr in running '$pbsbindir_val/qstat -f -Q', no queues found, specify manually..."
                else
                    echo "No queues found, specify manually..."
                fi
                assign_cfg_val_interactive "$computecfg_listgroup" "pbs_queue";
                if $gret__interrupted; then
                    return 0
                fi
            else

                # Recompute the default queue (we could be pointed at a
                # different bin directory now)
                pbs_default_queue=$(echo $("$pbsbindir_val/qmgr" -c "list server default_queue" | sed -n -e 's/^[[:space:]]*default_queue[[:space:]]*=[[:space:]]*//p' | sed -ne '1p')) || true
                if [[ -z "$pbs_default_queue" ]] ; then
                    pbs_default_queue="<undetected>";
                fi

                # Update the text for the PBS default queue
                if [[ x"$pbs_default_queue" != x"<undetected>" ]]; then
                    local i;
                    for ((i=0; i < ${#all_queues[@]} ; i++)); do
                        if [[ x"${all_queues[$i]}" == x"$pbs_default_queue" ]] ; then
                            all_queues[$i]="${pbs_default_queue}@@$(printf "%-12s  (PBS default queue)" "${pbs_default_queue}")"
                        fi
                    done

                    all_queues+=( "__NONE__@@--NONE--      (Use PBS default queues, currently '$pbs_default_queue')" )
                fi

                local selnum;
                if  [[ -z "$pbsqueue_val" ]] &&
                    [[ x"$pbs_default_queue" != x"<undetected>" ]]; then
                    selnum="__NONE__"
                else
                    selnum=$(get_selection_number "$pbsqueue_val" "${all_queues[@]}")
                    if [[ x"$pbs_default_queue" != x"<undetected>" ]]; then
                        [[ -z "$selnum" ]] && selnum="__NONE__";
                    else
                        [[ -z "$selnum" ]] && selnum="1";
                    fi
                fi

                echo
                echo "  Select PBS_QUEUE:"
                process_query --sp "    "  "$selnum" "${all_queues[@]}"
                echo

                pbsqueue_val=$gret_val;
                [[ x"$pbsqueue_val" == x"__NONE__" ]] && pbsqueue_val="";
                pbsqueue_srcstr="configured interactively"
                set_cfg_val "$computecfg_listgroup" "pbs_queue" "$pbsqueue_val" "$pbsqueue_srcstr" false;
            fi

            if ! $opt_batch; then
                echo
                echo "Additional arguments to the PBS job submission command may be"
                echo "added in PBS_STARTARGS.  The default job submission command is:"
                echo ""
                echo '     qsub ${CMD} -S /bin/bash -V -q ${QUEUE} -N ${JOB_NAME} \'
                echo '         -o ${STDOUT_FILE} -e ${STDERR_FILE} -l nodes=1:ppn=${NPROC} -PBS'
                echo ""
            fi

            assign_cfg_val_interactive "$computecfg_listgroup" "pbs_startargs";
        fi
    fi

    # Make sure all the settings are marked as not optional, so they get saved
    set_cfg_optional "$computecfg_listgroup" "pbs_bindir" "false";
    set_cfg_optional "$computecfg_listgroup" "pbs_queue" "false";
    set_cfg_optional "$computecfg_listgroup" "pbs_startargs" "false";

    gret__jmsconfigured=true;
}

prompt_jms_slurm() {
    local computecfg_listgroup=$1; shift;

    gret__jmsconfigured=false;

    local slurmbindir_val;
    local slurmbindir_srcstr;

    local slurmpartition_val;
    local slurmpartition_srcstr;

    local slurmstartargs_val;
    local slurmstartargs_srcstr;

    slurmbindir_val=$(get_cfg_val "$computecfg_listgroup" "slurm_bindir")
    slurmbindir_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "slurm_bindir")


    slurmpartition_val=$(get_cfg_val "$computecfg_listgroup" "slurm_partition")
    slurmpartition_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "slurm_partition")

    slurmstartargs_val=$(get_cfg_val "$computecfg_listgroup" "slurm_startargs")
    slurmstartargs_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "slurm_startargs")

    # FIXME: +++ Remove this after all sites upgraded to >7.0.1 (start) +++
    # If slurm_prestartargs is set, prepend it to slurm_startargs and save it
    local slurmprestartargs_val;
    slurmprestartargs_val=$(get_cfg_val "$computecfg_listgroup" "slurm_prestartargs")
    if [[ ! -z "$slurmprestartargs_val" ]] ; then
        slurmstartargs_val="$slurmprestartargs_val $slurmstartargs_val"
        slurmstartargs_val=${slurmstartargs_val% }
        set_cfg_val "$computecfg_listgroup" "slurm_startargs" "$slurmstartargs_val" "$slurmbindir_srcstr" false;
    fi
    # FIXME: --- Remove this after all sites upgraded to >7.0.1 (end) ---

    find_slurm_bindirs_frompath "$slurmbindir_val";
    local subtypebindirs_arr;
    subtypebindirs_arr=( "${gret__find_slurm_bindirs_frompath[@]+${gret__find_slurm_bindirs_frompath[@]}}" );

    if [[ -z "$slurmbindir_val" ]] ; then
        if [[ ! -z "${subtypebindirs_arr[@]+${subtypebindirs_arr[@]}}" ]]; then
            slurmbindir_val="${subtypebindirs_arr[0]}"
            slurmbindir_val="${slurmbindir_val#*,}"
            slurmbindir_srcstr="PATH environment variable, $slurmbindir_srcstr"
            set_cfg_val "$computecfg_listgroup" "slurm_bindir" "$slurmbindir_val" "$slurmbindir_srcstr" false;
        fi
    fi

    # If no partition specified, determine the Slurm default partition
    local slurm_default_partition="";
    if [[ ! -z "$slurmbindir_val" ]] ; then
        # Handle possible trailing ^M in the sinfo output
        slurm_default_partition=$("$slurmbindir_val/sinfo" --noheader --format="%P" | sed -n -e 's/[[:space:]]*$//' -e 's/\*$//p') || true
    fi
    if [[ -z "$slurm_default_partition" ]] ; then
        slurm_default_partition="<undetected>";
    fi

    local detect_error=false;
    if [[ -z "$slurmbindir_val" ]]; then
        detect_error=true;
    elif [[ -z "$slurmpartition_val" ]] &&
         [[ x"$slurm_default_partition" == x"<undetected>" ]] ; then
        detect_error=true;
    fi

    if $detect_error || ! $opt_batch; then
        echo
        echo "Detected the following settings:"
        echo
        if [[ -z "$slurmbindir_val" ]]; then
            echo  "   SLURM_BINDIR=        (not detected in PATH)";
        else
            echo  "   SLURM_BINDIR=$slurmbindir_val";
        fi
        if [[ -z "$slurmpartition_val" ]]; then
            echo  "   SLURM_PARTITION=     (Use Slurm default partition, currently: '$slurm_default_partition')";
        else
            echo  "   SLURM_PARTITION=$slurmpartition_val"
        fi
        echo  "   SLURM_STARTARGS=$slurmstartargs_val"
        echo
        echo  "   Where detected:"
        echo  "      SLURM_BINDIR        (from '$slurmbindir_srcstr')";
        echo  "      SLURM_PARTITION     (from '$slurmpartition_srcstr')";
        echo  "      SLURM_STARTARGS     (from '$slurmstartargs_srcstr')";
        echo
    fi

    if $opt_batch; then
        if [[ -z "$slurmbindir_val" ]]; then
            echo
            echo "Could not determine SLURM_BINDIR setting, please specify on command-line."
            abort;
        fi
        if  [[ -z "$slurmpartition_val" ]] &&
            [[ x"$slurm_default_partition" == x"<undetected>" ]] ; then

            echo
            echo "Could not determine SLURM_PARTITION setting, please specify on command-line."
            abort;
        fi
    else
        local ans;
        if [[ -z "$slurmbindir_val" ]]; then
            echo
            echo "Could not determine SLURM_BINDIR setting, please specify below."
            ans="n"
        elif [[ -z "$slurmpartition_val" ]] &&
             [[ x"$slurm_default_partition" == x"<undetected>" ]] ; then
            echo
            echo "Could not determine SLURM_PARTITION setting, please specify below."
            ans="n"
        else
            ans=$(yesno_prompt "Are these correct?")
        fi

        if [[ x"$ans" != x"y" ]]; then
            local nslurmbindir;
            echo
            while true; do

                assign_cfg_val_interactive "$computecfg_listgroup" "slurm_bindir";
                if $gret__interrupted; then
                    return 0
                fi

                nslurmbindir=$(get_cfg_val "$computecfg_listgroup" "slurm_bindir")
                if verify_slurm_bindir "$nslurmbindir"; then
                    # All the values look good, break out of the loop
                    slurmbindir_val=$nslurmbindir;
                    slurmbindir_srcstr="configured interactively"
                    break;
                fi

                echo "    Could not find required executables in '$nslurmbindir' ($gret__verify_slurm_bindir__missingprogs).  Try again..."
                echo
            done

            local -a all_partitions;
            local stat
            # Handle possible trailing ^M in the sinfo output
            sinfo_output=$("$slurmbindir_val/sinfo" --noheader --format="%P" | sed -e 's/[[:space:]]*$//' || true) || stat=$?
            all_partitions=( $sinfo_output )
            if [[ -z "${all_partitions[@]+${all_partitions[@]}}" ]] ; then
                if [[ $stat -ne 0 ]] ; then
                    echo "Error in running '$slurmbindir_val/sinfo --noheader --format=%P', no partitions found, specify manually..."
                else
                    echo "No partitions found, specify manually..."
                fi
                assign_cfg_val_interactive "$computecfg_listgroup" "slurm_partition";
                if $gret__interrupted; then
                    return 0
                fi
            else
                # Update the text for the slurm default partition
                slurm_default_partition="<undetected>"
                local i;
                for ((i=0; i < ${#all_partitions[@]} ; i++)); do
                    if [[ ${all_partitions[$i]} =~ (.*)\*$ ]] ; then
                        slurm_default_partition="${BASH_REMATCH[1]}"
                        all_partitions[$i]="${slurm_default_partition}@@$(printf "%-12s  (Slurm default partition)" "${slurm_default_partition}")"
                    fi
                done

                if [[ x"$slurm_default_partition" != x"<undetected>" ]]; then
                    all_partitions+=( "__NONE__@@--NONE--      (Use Slurm default partition, currently '$slurm_default_partition')" )
                fi

                local selnum;
                if  [[ -z "$slurmpartition_val" ]] &&
                    [[ x"$slurm_default_partition" != x"<undetected>" ]]; then
                    selnum="__NONE__"
                else
                    selnum=$(get_selection_number "$slurmpartition_val" "${all_partitions[@]}")

                    if [[ x"$slurm_default_partition" != x"<undetected>" ]]; then
                        [[ -z "$selnum" ]] && selnum="__NONE__";
                    else
                        [[ -z "$selnum" ]] && selnum="1";
                    fi
                fi

                echo
                echo "  Select SLURM_PARTITION:"
                process_query --sp "    "  "$selnum" "${all_partitions[@]}"
                echo

                slurmpartition_val=$gret_val;
                [[ x"$slurmpartition_val" == x"__NONE__" ]] && slurmpartition_val="";
                slurmpartition_srcstr="configured interactively"
                set_cfg_val "$computecfg_listgroup" "slurm_partition" "$slurmpartition_val" "$slurmpartition_srcstr" false;
            fi

            if ! $opt_batch; then
                echo
                echo "Additional arguments to the Slurm job submission command may be"
                echo "added in SLURM_STARTARGS.  The default job submission command is:"
                echo ""
                echo '     sbatch --job-name="${JOB_NAME}" --nodes=1 --ntasks=1 \'
                echo '         --cpus-per-task="${NPROC}" --partition="${PARTITION}" \'
                echo '         -o "$STDOUT_FILE" -e "$STDERR_FILE"  --wrap="${JOB_CMD}"'
                echo ""
            fi

            assign_cfg_val_interactive "$computecfg_listgroup" "slurm_startargs";
            if $gret__interrupted; then
                return 0
            fi
        fi
    fi

    # Make sure all the settings are marked as not optional, so they get saved
    set_cfg_optional "$computecfg_listgroup" "slurm_bindir" "false";
    set_cfg_optional "$computecfg_listgroup" "slurm_partition" "false";
    set_cfg_optional "$computecfg_listgroup" "slurm_startargs" "false";

    gret__jmsconfigured=true;
}

prompt_jms_aws() {
    local computecfg_listgroup=$1; shift;

    gret__jmsconfigured=false;

    local aws_region_val;
    local aws_region_srcstr;

    local aws_queue_val;
    local aws_queue_srcstr;

    local aws_disk_root_val;
    local aws_disk_root_srcstr;

    local aws_docker_val;
    local aws_docker_srcstr;

    local aws_memory_val;
    local aws_memory_srcstr;

    aws_region_val=$(get_cfg_val "$computecfg_listgroup" "aws_region")
    aws_region_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "aws_region")
    aws_queue_val=$(get_cfg_val "$computecfg_listgroup" "aws_queue")
    aws_queue_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "aws_queue")
    aws_disk_root_val=$(get_cfg_val "$computecfg_listgroup" "aws_disk_root")
    aws_disk_root_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "aws_disk_root")
    aws_docker_val=$(get_cfg_val "$computecfg_listgroup" "aws_docker")
    aws_docker_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "aws_docker")
    aws_memory_val=$(get_cfg_val "$computecfg_listgroup" "aws_memory")
    aws_memory_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "aws_memory")

    if ! $opt_batch; then
        echo
        echo "Detected the following settings:"
        echo
        if [[ -z "$aws_region_val" ]]; then
            echo  "   AWS_REGION=        (not configured)";
        else
            echo  "   AWS_REGION=$aws_region_val";
        fi
        if [[ -z "$aws_queue_val" ]]; then
            echo  "   AWS_QUEUE=         (not configured)";
        else
            echo  "   AWS_QUEUE=$aws_queue_val"
        fi
        if [[ -z "$aws_disk_root_val" ]]; then
            echo  "   AWS_DISK_ROOT=         (not configured)";
        else
            echo  "   AWS_DISK_ROOT=$aws_disk_root_val"
        fi
        if [[ -z "$aws_docker_val" ]]; then
            echo  "   AWS_DOCKER=         (not configured)";
        else
            echo  "   AWS_DOCKER=$aws_docker_val"
        fi
        if [[ -z "$aws_memory_val" ]]; then
            echo  "   AWS_MEMORY=         (not configured)";
        else
            echo  "   AWS_MEMORY=$aws_memory_val"
        fi
        echo
        echo  "   Where detected:"
        echo  "      AWS_REGION       (from '$aws_region_srcstr')";
        echo  "      AWS_QUEUE        (from '$aws_queue_srcstr')";
        echo  "      AWS_DISK_ROOT    (from '$aws_disk_root_srcstr')";
        echo  "      AWS_DOCKER       (from '$aws_docker_srcstr')";
        echo  "      AWS_MEMORY       (from '$aws_memory_srcstr')";
        echo
    fi

    if $opt_batch; then
        if [[ -z "$aws_region_val" ]]; then
            echo
            echo "Could not determine AWS_REGION setting, please specify on command-line."
            abort;
        fi
        if [[ -z "$aws_queue_val" ]]; then
            echo
            echo "Could not determine AWS_QUEUE setting, please specify on command-line."
            abort;
        fi
        if [[ -z "$aws_disk_root_val" ]]; then
            echo
            echo "Could not determine AWS_DISK_ROOT setting, please specify on command-line."
            abort;
        fi
        if [[ -z "$aws_docker_val" ]]; then
            echo
            echo "Could not determine AWS_DOCKER setting, please specify on command-line."
            abort;
        fi
        if [[ -z "$aws_memory_val" ]]; then
            echo
            echo "Could not determine AWS_MEMORY setting, please specify on command-line."
            abort;
        fi
    else
        local ans;
        if [[ -z "$aws_region_val" ]]; then
            echo
            echo "Could not determine AWS_REGION setting, please specify below."
            ans="n"
        elif [[ -z "$aws_queue_val" ]]; then
            echo
            echo "Could not determine AWS_QUEUE setting, please specify below."
            ans="n"
        elif [[ -z "$aws_disk_root_val" ]]; then
            echo
            echo "Could not determine AWS_DISK_ROOT setting, please specify below."
            ans="n"
        elif [[ -z "$aws_docker_val" ]]; then
            echo
            echo "Could not determine AWS_DOCKER setting, please specify below."
            ans="n"
        elif [[ -z "$aws_memory_val" ]]; then
            echo
            echo "Could not determine AWS_MEMORY setting, please specify below."
            ans="n"
        else
            ans=$(yesno_prompt "Are these correct?")
        fi

        if [[ x"$ans" != x"y" ]]; then
            assign_cfg_val_interactive "$computecfg_listgroup" "aws_region"
            assign_cfg_val_interactive "$computecfg_listgroup" "aws_queue"
            assign_cfg_val_interactive "$computecfg_listgroup" "aws_disk_root"
            assign_cfg_val_interactive "$computecfg_listgroup" "aws_docker"
            assign_cfg_val_interactive "$computecfg_listgroup" "aws_memory"
            if $gret__interrupted; then
                return 0
            fi
        fi
    fi

    # Make sure all the settings are marked as not optional, so they get saved
    set_cfg_optional "$computecfg_listgroup" "aws_region" "false";
    set_cfg_optional "$computecfg_listgroup" "aws_queue" "false";
    set_cfg_optional "$computecfg_listgroup" "aws_disk_root" "false";
    set_cfg_optional "$computecfg_listgroup" "aws_docker" "false";
    set_cfg_optional "$computecfg_listgroup" "aws_memory" "false";

    gret__jmsconfigured=true;
}

prompt_jms_otherjms() {
    local computecfg_listgroup=$1; shift;

    gret__jmsconfigured=false;

    local otherjmsname_val;
    local otherjmsname_srcstr;

    local otherjmsbindir_val;
    local otherjmsbindir_srcstr;

    local otherjmsqueue_val;
    local otherjmsqueue_srcstr;

    local otherjmsstartargs_val;
    local otherjmsstartargs_srcstr;

    otherjmsname_val=$(get_cfg_val "$computecfg_listgroup" "otherjms_name")
    otherjmsname_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "otherjms_name")

    otherjmsbindir_val=$(get_cfg_val "$computecfg_listgroup" "otherjms_bindir")
    otherjmsbindir_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "otherjms_bindir")

    otherjmsqueue_val=$(get_cfg_val "$computecfg_listgroup" "otherjms_queue")
    otherjmsqueue_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "otherjms_queue")

    otherjmsstartargs_val=$(get_cfg_val "$computecfg_listgroup" "otherjms_startargs")
    otherjmsstartargs_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "otherjms_startargs")

    local detect_error=false;
    if [[ ! -z "$otherjmsname_val" ]]; then
        detect_error=true;
    elif [[ ! -z "$otherjmsbindir_val" ]]; then
        detect_error=true;
    fi

    if $detect_error || ! $opt_batch; then
        echo
        echo "Detected the following settings:"
        echo
        echo  "   OTHERJMS_NAME=$otherjmsname_val";
        echo  "   OTHERJMS_BINDIR=$otherjmsbindir_val";
        echo  "   OTHERJMS_QUEUE=$otherjmsqueue_val"
        echo  "   OTHERJMS_STARTARGS=$otherjmsstartargs_val"
        echo
        echo  "   where detected:"
        echo  "      OTHERJMS_NAME          (from '$otherjmsname_srcstr')";
        echo  "      OTHERJMS_BINDIR        (from '$otherjmsbindir_srcstr')";
        echo  "      OTHERJMS_QUEUE         (from '$otherjmsqueue_srcstr')";
        echo  "      OTHERJMS_STARTARGS     (from '$otherjmsstartargs_srcstr')";
        echo
    fi

    if $opt_batch; then
        if [[ ! -z "$otherjmsname_val" ]]; then
            echo
            echo "Could not determine OTHERJMS_NAME setting, please specify on command-line."
            abort;
        fi
        if [[ ! -z "$otherjmsbindir_val" ]]; then
            echo
            echo "Could not determine OTHERJMS_BINDIR setting, please specify on command-line."
            abort;
        fi
    else
        local ans;
        if [[ -z "$otherjmsname_val" ]]; then
            echo
            echo "Could not determine OTHERJMS_NAME setting, please specify below."
            ans="n"
        elif [[ -z "$otherjmsbindir_val" ]]; then
            echo
            echo "Could not determine OTHERJMS_BINDIR setting, please specify below."
            ans="n"
        else
            ans=$(yesno_prompt "Are these correct?")
        fi

        if [[ x"$ans" != x"y" ]]; then
            echo
            assign_cfg_val_interactive "$computecfg_listgroup" "otherjms_name";
            if $gret__interrupted; then
                return 0
            fi
            assign_cfg_val_interactive "$computecfg_listgroup" "otherjms_bindir";
            if $gret__interrupted; then
                return 0
            fi
            assign_cfg_val_interactive "$computecfg_listgroup" "otherjms_queue";
            if $gret__interrupted; then
                return 0
            fi
            assign_cfg_val_interactive "$computecfg_listgroup" "otherjms_startargs";
            if $gret__interrupted; then
                return 0
            fi
        fi
    fi

    echo

    otherjmsname_val=$(get_cfg_val "$computecfg_listgroup" "otherjms_name")
    set_cfg_val "$computecfg_listgroup" "jmstype" "OtherJMS__${otherjmsname_val}" "configured interactively" false;

    # Make sure all the settings are marked as not optional, so they get saved
    set_cfg_optional "$computecfg_listgroup" "otherjms_name" "false";
    set_cfg_optional "$computecfg_listgroup" "otherjms_bindir" "false";
    set_cfg_optional "$computecfg_listgroup" "otherjms_queue" "false";
    set_cfg_optional "$computecfg_listgroup" "otherjms_startargs" "false";

    gret__jmsconfigured=true;
}

prompt_jms_customjms() {
    local computecfg_listgroup=$1; shift;

    gret__jmsconfigured=false;

    local customjmsname_val;
    local customjmsname_srcstr;

    local customjmsbindir_val;
    local customjmsbindir_srcstr;

    local customjmsqueue_val;
    local customjmsqueue_srcstr;

    local customjmsstartargs_val;
    local customjmsstartargs_srcstr;

    customjmsname_val=$(get_cfg_val "$computecfg_listgroup" "customjms_name")
    customjmsname_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "customjms_name")

    customjmsbindir_val=$(get_cfg_val "$computecfg_listgroup" "customjms_bindir")
    customjmsbindir_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "customjms_bindir")

    customjmsqueue_val=$(get_cfg_val "$computecfg_listgroup" "customjms_queue")
    customjmsqueue_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "customjms_queue")

    customjmsstartargs_val=$(get_cfg_val "$computecfg_listgroup" "customjms_startargs")
    customjmsstartargs_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "customjms_startargs")

    local detect_error=false;
    if [[ ! -z "$customjmsname_val" ]]; then
        detect_error=true;
    elif [[ ! -z "$customjmsbindir_val" ]]; then
        detect_error=true;
    fi

    if $detect_error || ! $opt_batch; then
        echo
        echo "Detected the following settings:"
        echo
        echo  "   CUSTOMJMS_NAME=$customjmsname_val";
        echo  "   CUSTOMJMS_BINDIR=$customjmsbindir_val";
        echo  "   CUSTOMJMS_QUEUE=$customjmsqueue_val"
        echo  "   CUSTOMJMS_STARTARGS=$customjmsstartargs_val"
        echo
        echo  "   where detected:"
        echo  "      CUSTOMJMS_NAME          (from '$customjmsname_srcstr')";
        echo  "      CUSTOMJMS_BINDIR        (from '$customjmsbindir_srcstr')";
        echo  "      CUSTOMJMS_QUEUE         (from '$customjmsqueue_srcstr')";
        echo  "      CUSTOMJMS_STARTARGS     (from '$customjmsstartargs_srcstr')";
        echo
    fi

    if $opt_batch; then
        if [[ ! -z "$customjmsname_val" ]]; then
            echo
            echo "Could not determine CUSTOMJMS_NAME setting, please specify on command-line."
            abort;
        fi
        if [[ ! -z "$customjmsbindir_val" ]]; then
            echo
            echo "Could not determine CUSTOMJMS_BINDIR setting, please specify on command-line."
            abort;
        fi
    else
        local ans;
        if [[ -z "$customjmsname_val" ]]; then
            echo
            echo "Could not determine CUSTOMJMS_NAME setting, please specify below."
            ans="n"
        elif [[ -z "$customjmsbindir_val" ]]; then
            echo
            echo "Could not determine CUSTOMJMS_BINDIR setting, please specify below."
            ans="n"
        else
            ans=$(yesno_prompt "Are these correct?")
        fi

        if [[ x"$ans" != x"y" ]]; then
            echo
            assign_cfg_val_interactive "$computecfg_listgroup" "customjms_name";
            if $gret__interrupted; then
                return 0
            fi
            assign_cfg_val_interactive "$computecfg_listgroup" "customjms_bindir";
            if $gret__interrupted; then
                return 0
            fi
            assign_cfg_val_interactive "$computecfg_listgroup" "customjms_queue";
            if $gret__interrupted; then
                return 0
            fi
            assign_cfg_val_interactive "$computecfg_listgroup" "customjms_startargs";
            if $gret__interrupted; then
                return 0
            fi
        fi
    fi

    echo

    customjmsname_val=$(get_cfg_val "$computecfg_listgroup" "customjms_name")
    set_cfg_val "$computecfg_listgroup" "jmstype" "CustomJMS__${customjmsname_val}" "configured interactively" false;

    # Make sure all the settings are marked as not optional, so they get saved
    set_cfg_optional "$computecfg_listgroup" "customjms_name" "false";
    set_cfg_optional "$computecfg_listgroup" "customjms_bindir" "false";
    set_cfg_optional "$computecfg_listgroup" "customjms_queue" "false";
    set_cfg_optional "$computecfg_listgroup" "customjms_startargs" "false";

    gret__jmsconfigured=true;
}


# -- install screen subroutines


system_sanity_check() {
    # Sanity check for basic system requirements.  Specifically
    #    - running Linux
    #    - 64-bit capable
    #    - libc-2.12 or greater
    # Use --skip-systemsanity to bypass this check
    if $opt_skip_systemsanity; then
        echo "Skipping system sanity check..."
        return;
    fi

    echo "System sanity check..."

    echo "  Checking locale..."
    # Check that locale is as we expect and that the system supports it
    check_locale;


    echo "  Checking uname..."
    local kernel;
    local os;
    local machhw;
    local libcver;
    # Check that kernel is 'Linux'
    kernel=$(uname -s);
    if [[ x"$kernel" != x"Linux" ]]; then
        merror "Unexpected kernel ($kernel) from 'uname -s' (expected 'Linux')";
    fi

    # Check the OS name
    os=$(uname -o);
    if [[ x"$os" != x"GNU/Linux" ]]; then
        merror "Unexpected os ($os) from 'uname -o' (expected 'GNU/Linux')";
    fi

    # Check the machine hardware (64-bit check)
    machhw=$(uname -m);
    if [[ x"$machhw" != x"x86_64" ]]; then
        merror "Unexpected machine hardware ($machhw) from 'uname -m', (expected 'x86_64', 64-bit system)";
    fi

    echo "  Checking libc..."
    # Sanity check the runtime libc version format
    runtime_libcver=$(ldd --version | sed -ne '1 { s/[[:space:]]*$//; s/.*[[:space:]]//; p }')
    if [[ ! $runtime_libcver =~ ^[[:digit:]]+\.[[:digit:]][[:digit:]\.]*$ ]]; then
        merror "Unexpected format for runtime libc version ($runtime_libcver), expected digits in first two fields, followed only by fields of digits";
    fi

    buildtime_libcver=$g_buildinfo__libc_version
    if [[ ! $buildtime_libcver =~ ^[[:digit:]]+\.[[:digit:]][[:digit:]\.]*$ ]]; then
        merror "Unexpected format for buildtime libc version ($buildtime_libcver), expected digits in first two fields, followed only by fields of digits";
    fi

    # Gross sanity check to make sure the runtime version is at least above
    # 2.5 (our minimum version).
    local cmpret;
    cmpret=$(compare_versions "$runtime_libcver" "2.12")
    if [[ x"$cmpret" == x"-" ]] ; then
        merror "Unexpected libc version ($runtime_libcver) from 'ldd --version' (expected 2.12 or greater).  Running on less than libc-2.12 is not supported.";
    fi

    # Check the runtime libc version against the buildtime libc version (should
    # be greater than or equal to buildtime version)
    local cmpret;
    cmpret=$(compare_versions "$runtime_libcver" "$buildtime_libcver")
    if [[ x"$cmpret" == x"-" ]] ; then
        merror "Runtime libc version ($runtime_libcver) is less than buildtime libc version ($buildtime_libcver).  Need to install from a '*_libc-${runtime_libcver}.run' tarball or lower.";
    fi

    if ! "$g_progdir/checkmounts" -v; then
        echo "Error!  Detected unresponsive mount points, exiting..."
        abort;
    fi
}

install_prompt_user() {
    next_screen;
    $opt_batch || cat <<-EOF
	----- Part 1 of 10: SMRT Analysis User ----

	It is recommended to run this script as a designated SMRT Analysis
	user (e.g. smrtanalysis) who will own all smrtpipe jobs and daemon
	processes.

	EOF

    # TODO:herb: detect existing user/group and determine if we can
    #            continue


    # FIXME: need to implement group permissions?
    # Do we need to enable group write permissions for any reason?  If so,
    # we should prompt the user for which group to make the install owned
    # by.  We could assume the user's primary group, but that may not be
    # what is needed.
    #
    # Note that the install directory should ideally be read-only anyway.
    # So we may only need to deal with the rwdir directory and other
    # directories that we may need to write (e.g. userdata).
    #
    # Also note that this should not be an issue if we are just running
    # smrtlink and such (where the webserver and such only will ever run
    # as the smrtanalysis user).  If we will support runnning commands on
    # the command line that may require write access (log files, etc), we
    # may need ot deal with the group permissions (requiring all users
    # running commands to belong to the same group) or maintain separate
    # read-write directories for each user.
    #
    # If we do set group permisions, we should give options to the user
    # to specify group read/write permissions. And we should also set the
    # group 'setgid' bit for all the read-write directories, so that new
    # files and dirs will inherit the group permissions from the parent.
    #
    # For now, we will just not make the install be group writable.

    echo "Current user is '$g_runuser' (primary group: $g_rungroup)"

    if [[ $g_runuid -eq 0 ]] ; then
        # FIXME: If installing as root, we could do this:
        #    - ask who the install user should be
        #    - ask for group to use (if needed)
        #    - try to su to user (exit if failed, continue otherwise)
        echo
        echo "Installing as 'root' is not currently supported"
        echo "Switch to the desired user and restart the install."
        abort;
    fi

    if $opt_batch || $opt_skip_userquery; then
        echo "Using '$g_runuser' as SMRT Analysis user."
    else
        echo
        ans=$(yesno_prompt "Use the '$g_runuser' as the SMRT Analysis user?")
        if [[ x"$ans" == x"n" ]] ; then
            echo
            echo "Switch users to the desired SMRT Analysis user and restart"
            echo "the installer."
            abort;
        fi
    fi

    local sluuid;
    local sluuid_srcstr;
    sluuid=$(get_cfg_val "install" "sluuid");
    sluuid_srcstr=$(get_cfg_srcstr "install" "sluuid");


    set_cfg_val "install" "user" "$g_runuser" "computed" false;
    set_cfg_val "install" "group" "$g_rungroup" "computed" false;
    set_cfg_val "install" "sluuid" "$sluuid" "$sluuid_srcstr" false;

    echo
    print_cfg_usingstr "install" "user"
    print_cfg_usingstr "install" "group"
    print_cfg_usingstr "install" "sluuid"
}


install_prompt_system_check() {
    next_screen;

    # Ulimit requirements
    local ulimitscript="$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/pacbio/smrtlink-analysisservices-gui/bin/check-system-limits"
    g_sysreq__ulimit_openfiles=$(sed -ne 's/^MIN_NFILES=//p' "$ulimitscript")
    g_sysreq__ulimit_maxprocesses=$(sed -ne 's/^MIN_NPROC=//p' "$ulimitscript")
    if [[ ! $g_sysreq__ulimit_openfiles =~ ^[0-9]+$ ]] ; then
        minterror "Could not determine MIN_NPROC ulimit setting"
    fi
    if [[ ! $g_sysreq__ulimit_maxprocesses =~ ^[0-9]+$ ]] ; then
        minterror "Could not determine MIN_NFILES ulimit setting"
    fi

    # Check to make sure that the smrtlink rootdir does not contain any spaces
    # or special shell characters
    local rootdir_abs;
    rootdir_abs=$(readlink -f "$opt_rootdir");
    if str_contains_whitespace "$rootdir_abs" ; then
        merror "Whitespace not allowed in the SMRT Link root directory: $rootdir_abs"
    elif str_contains_shspecialchar "$rootdir_abs" ; then
        merror "Special shell character '$gret__str_contains_shspecialchar__specialchar' not allowed in the SMRT Link root directory: $rootdir_abs"
    fi


    $opt_batch || cat <<-EOF
	----- Part 0 of 10: System Prerequisite Check -----

	Checking system configuration against minimum memory, processor and
	user limit (ulimit) requirements.  SMRT Link requires at least ${g_hwreq__numprocs_min}
	processors and ${g_hwreq__memGB_min}GB of memory.  The soft user limits when starting
	SMRT Link services must be at least ${g_sysreq__ulimit_openfiles} for open files ('ulimit -Sn')
	and ${g_sysreq__ulimit_maxprocesses} for max number of processes ('ulimit -Su').

	EOF

    echo "  Checking requirements..."
    echo

    local memtotal_bytes;
    local memtotal_srcstr;
    local memtotal_usedefault;

    memtotal_bytes=$(get_cfg_val "system" "memtotal");
    memtotal_srcstr=$(get_cfg_srcstr "system" "memtotal");
    memtotal_usedefault=$(get_cfg_usedefault "system" "memtotal");

    g_hw_memtotal_KiB=$(( $memtotal_bytes / 1024 ))
    g_hw_memtotal_MiB=$(( $g_hw_memtotal_KiB   / 1024 ))
    g_hw_memtotal_GiB=$(( $g_hw_memtotal_MiB   / 1024 ))

    local tmp100;
    local tmp100int;
    tmp100=$(( $g_hw_memtotal_MiB * 100  / 1024 ))
    tmp100int=${tmp100%??}
    g_hw_memtotal_GiB_hundredths="${tmp100int}.${tmp100#${tmp100int}}"

    local -a perferrs=();
    local -a fatalerrs=();
    local -a noncritwarns=();
    local anyerr=false;
    local anywarn=false;
    local hardlimiterr=false;

    local memory_chkstr="[OK]"
    if [[ $g_hw_memtotal_GiB -lt $g_hwreq__memGB_min ]] ; then
        memory_chkstr="[ERR]"
        perferrs+=( "ERROR!  Minimum 'System Physical Memory' requirement not met." );
        anyerr=true
    fi
    printf "    System Physical Memory (min: %d):  %7.02f GB %-15s %-6s\n" \
            "$g_hwreq__memGB_min" \
            "$g_hw_memtotal_GiB_hundredths" \
            "($g_hw_memtotal_KiB kB)" \
            "$memory_chkstr"

    # Processor Check
    local processor_chkstr="[OK]"
    if [[ $g_hw_numprocs -lt $g_hwreq__numprocs_min ]] ; then
        processor_chkstr="[ERR]"
        perferrs+=( "ERROR!  Minimum 'Number of Processors' requirement not met." );
        anyerr=true
    fi
    printf "    Number of Processors   (min: %d):  %7d                    %-6s\n" \
        "$g_hwreq__numprocs_min" \
        "$g_hw_numprocs" \
        "$processor_chkstr"


    local ulimit_openfiles_soft;
    local ulimit_openfiles_hard;
    local ulimit_numprocesses_soft;
    local ulimit_numprocesses_hard;
    ulimit_openfiles_soft=$(ulimit -Sn)
    ulimit_openfiles_hard=$(ulimit -Hn)
    ulimit_maxprocesses_soft=$(ulimit -Su)
    ulimit_maxprocesses_hard=$(ulimit -Hu)


    # Set this to true to only issue a warning for soft limit violations
    # (presumably because we will bump the soft limit to the minimum
    # reguired when starting services)
    local softlimit_warnonly=false;

    local ulimit_openfiles_soft_chkstr="[OK]"
    if [[ $ulimit_openfiles_soft -lt $g_sysreq__ulimit_openfiles ]] ; then
        if $softlimit_warnonly; then
            ulimit_openfiles_soft_chkstr="[WARN]"
            noncritwarns+=( "Warning!  Minimum 'Open Files' soft limit requirement not met." );
            anywarn=true
        else
            ulimit_openfiles_soft_chkstr="[ERR]"
            fatalerrs+=( "ERROR!  Minimum 'Open Files' soft limit requirement not met." );
            anyerr=true
        fi
    fi
    local ulimit_openfiles_hard_chkstr="[OK]"
    if [[ $ulimit_openfiles_hard -lt $g_sysreq__ulimit_openfiles ]] ; then
        ulimit_openfiles_hard_chkstr="[ERR]"
        fatalerrs+=( "ERROR!  Minimum 'Open Files' hard limit requirement not met." );
        hardlimiterr=true
        anyerr=true
    fi
    echo
    echo "    Open Files User Limit ('ulimit -n'):"
    printf "      Soft Limit (min: %d):          %7d                    %-6s\n" \
        "$g_sysreq__ulimit_openfiles" \
        "$ulimit_openfiles_soft" \
        "$ulimit_openfiles_soft_chkstr"
    printf "      Hard Limit (min: %d):          %7d                    %-6s\n" \
        "$g_sysreq__ulimit_openfiles" \
        "$ulimit_openfiles_hard" \
        "$ulimit_openfiles_hard_chkstr"

    local ulimit_maxprocesses_soft_chkstr="[OK]"
    if [[ $ulimit_maxprocesses_soft -lt $g_sysreq__ulimit_maxprocesses ]] ; then
        if $softlimit_warnonly; then
            ulimit_maxprocesses_soft_chkstr="[WARN]"
            noncritwarns+=( "Warning!  Minimum 'Max Processes' soft limit requirement not met." );
            anywarn=true
        else
            ulimit_maxprocesses_soft_chkstr="[ERR]"
            fatalerrs+=( "ERROR!  Minimum 'Max Processes' soft limit requirement not met." );
            anyerr=true
        fi
    fi
    local ulimit_maxprocesses_hard_chkstr="[OK]"
    if [[ $ulimit_maxprocesses_hard -lt $g_sysreq__ulimit_maxprocesses ]] ; then
        ulimit_maxprocesses_hard_chkstr="[ERR]"
        fatalerrs+=( "ERROR!  Minimum 'Max Processes' hard limit requirement not met." );
        hardlimiterr=true
        anyerr=true
    fi
    echo

    echo "    Open Files User Limit ('ulimit -u'):"
    printf "      Soft Limit (min: %d):          %7d                    %-6s\n" \
        "$g_sysreq__ulimit_maxprocesses" \
        "$ulimit_maxprocesses_soft" \
        "$ulimit_maxprocesses_soft_chkstr"
    printf "      Hard Limit (min: %d):          %7d                    %-6s\n" \
        "$g_sysreq__ulimit_maxprocesses" \
        "$ulimit_maxprocesses_hard" \
        "$ulimit_maxprocesses_hard_chkstr"



    if $anywarn || $anyerr; then
        echo

        local str;
        local mstr;
        if [[ ! -z "${perferrs[@]+${perferrs[@]}}" ]] ; then
            if [[ ${#perferrs[@]} -gt 1 ]] ; then
                mstr="These errors"
            else
                mstr="This error"
            fi
            echo
            echo "  $mstr may not be critical, but can affect SMRT Link performance:"
            echo
            for str in "${perferrs[@]}"; do
                echo "      $str"
            done
        fi
        if [[ ! -z "${fatalerrs[@]+${fatalerrs[@]}}" ]] ; then
            if [[ ${#fatalerrs[@]} -gt 1 ]] ; then
                mstr="These errors"
            else
                mstr="This error"
            fi
            echo
            if $hardlimiterr; then
                echo "  $mstr WILL prevent smrtlink services from starting, see limits.conf(5):"
            else
                echo "  $mstr WILL prevent smrtlink services from starting:"
            fi
            echo
            for str in "${fatalerrs[@]}"; do
                echo "      $str"
            done
        fi
        if [[ ! -z "${noncritwarns[@]+${noncritwarns[@]}}" ]] ; then
            if [[ ${#noncritwarns[@]} -gt 1 ]] ; then
                mstr="These warnings"
            else
                mstr="This warning"
            fi
            echo
            echo "  $mstr will not prevent smrtlink services from starting.  SMRT Link"
            echo "  will increase the soft limit to the minimum requirement before starting"
            echo "  services."
            echo
            for str in "${noncritwarns[@]}"; do
                echo "      $str"
            done
        fi

        local warnerrstr=""
        if $anywarn && $anyerr; then
            warnerrstr="warnings and errors"
        elif $anywarn; then
            warnerrstr="warnings"
        elif $anyerr; then
            warnerrstr="errors"
        fi
        echo
        if $opt_ignore_system_check; then
            echo "Ignoring system check ${warnerrstr}."
        elif $opt_batch; then
            # Default is to continue now
            echo "System check ${warnerrstr} detected.  (Ignoring in batch mode)"
            #   # abort;
        else
            ans=$(yesno_prompt "Continue the installation?")
            if [[ x"$ans" == x"n" ]] ; then
                abort;
            fi
        fi
    else
        echo
        echo "System hardware and user limit requirements passed."
    fi
}

common_prompt_smrtlink_dns() {
    local -a g_dnscache_input;
    local -a g_dnscache_aliasfq;
    local -a g_dnscache_dnsnamefq;
    local -a g_dnscache_ipaddr;
    local -a g_dnscache_verified;
    g_dnserr=false;
    g_nodnsservers=false;

    getnamelist() {
        local fqname=$1; shift;
        local list;
        local revlist;

        if [[ $fqname =~ ^[0-9\.]+$ ]] ; then
            echo "$fqname";
            return 0;
        fi

        local name=$fqname;
        # Strip trailing dot, if any
        name=${name%.}
        list=$name;
        while true; do
            [[ x"$name" == x"${name%.*}" ]] && break;
            name=${name%.*};
            list="$list $name";
        done
        echo "$list";
    }

    dnslookup_cache() {
        local input=$1; shift;

        gret_dns_input="";
        gret_dns_aliasfq="";
        gret_dns_dnsnamefq="";
        gret_dns_ipaddr="";

        local i;
        local cachesize=0;
        if [[ ! -z "${g_dnscache_input[@]+${g_dnscache_input[@]}}" ]]; then
            cachesize=${#g_dnscache_input[@]}
        fi
        # First, see if we match any of the inputs in the cache
        for ((i=0; i < $cachesize ; i++)); do
            if [[ x"$input" == x"${g_dnscache_input[$i]}" ]] ; then
                gret_dns_input=$input;
                gret_dns_aliasfq=${g_dnscache_aliasfq[$i]};
                gret_dns_dnsnamefq=${g_dnscache_dnsnamefq[$i]};
                gret_dns_ipaddr=${g_dnscache_ipaddr[$i]};
                gret_dns_verified=${g_dnscache_verified[$i]};
                break;
            fi
        done
        # Return if we matched any of the inputs
        [[ ! -z "$gret_dns_input" ]] && return 0;

        # Now, see if we match any of the aliasfq, dnsnamefq or ipaddr in cache
        for ((i=0; i < $cachesize ; i++)); do
            if [[ x"$input" == x"${g_dnscache_aliasfq[$i]}" ]] ; then
                gret_dns_input=$input;
                gret_dns_aliasfq="";
                gret_dns_dnsnamefq=${g_dnscache_dnsnamefq[$i]};
                gret_dns_ipaddr=${g_dnscache_ipaddr[$i]};
                gret_dns_verified=${g_dnscache_verified[$i]};
            elif [[ x"$input" == x"${g_dnscache_dnsnamefq[$i]}" ]] ; then
                gret_dns_input=$input;
                gret_dns_aliasfq="";
                gret_dns_dnsnamefq=${g_dnscache_dnsnamefq[$i]};
                gret_dns_ipaddr=${g_dnscache_ipaddr[$i]};
                gret_dns_verified=${g_dnscache_verified[$i]};
            elif [[ x"$input" == x"${g_dnscache_ipaddr[$i]}" ]] ; then
                gret_dns_input=$input;
                gret_dns_aliasfq="";
                gret_dns_dnsnamefq=${g_dnscache_dnsnamefq[$i]};
                gret_dns_ipaddr=${g_dnscache_ipaddr[$i]};
                gret_dns_verified=${g_dnscache_verified[$i]};
            fi
            # Add to cache if we matched on something other than the input
            if [[ ! -z "$gret_dns_input" ]]; then
                g_dnscache_input=("${g_dnscache_input[@]}" "$gret_dns_input")
                g_dnscache_aliasfq=("${g_dnscache_aliasfq[@]}" "$gret_dns_aliasfq")
                g_dnscache_dnsnamefq=("${g_dnscache_dnsnamefq[@]}" "$gret_dns_dnsnamefq")
                g_dnscache_ipaddr=("${g_dnscache_ipaddr[@]}" "$gret_dns_ipaddr")
                g_dnscache_verified=("${g_dnscache_verified[@]}" "$gret_dns_verified")
            fi

            # Exit immediately if we matched
            [[ ! -z "$gret_dns_input" ]] && return 0;
        done
        return 0;
    }

    dnslookup() {
        local input=$1; shift;

        local mark_unverified_if_not_found=false;
        if [[ x"${1:-}" == x"mark_unverified_if_not_found" ]] ; then
            mark_unverified_if_not_found=true
        fi

        dnslookup_cache "$input";
        [[ ! -z "$gret_dns_input" ]] && return 0;

        local isipaddr=false;
        [[ $input =~ ^[0-9\.]+$ ]] && isipaddr=true;

        # Wasn't in the cache, run the dnslookup commands using dig
        local dnsout;
        local dashx_arg="";
        if $isipaddr; then
            dashx_arg="-x"
        fi

        # Set string flag to indicate if input verified via dns
        local isverified="";

        stat=0;
        if $opt_skip_dnscheck ; then
            dnsout="";
            isverified="unverified, dns check skipped"
        elif $g_nodnsservers; then
            # We already hit a dig error indicating that there are no dns
            # servers available, skip any further dns lookups (since we
            # expect it will hit the same error).
            dnsout="";
            isverified="unverified, no dns servers available"
        else
            dnsout=$("$g_dig_exe" +nocmd $dashx_arg "$input" +search +noall +answer) || stat=$?;
        fi
        if [[ $stat -ne 0 ]] ; then
            if [[ $stat -eq 9 ]] ; then
                # If no dns servers are available, the dig command will exit
                # with status '9' and issue this error statement:
                #    ;; connection timed out; no servers could be reached
                # Set a flag so that we can disable all future dns lookups
                # (since they won't find a dns server either)
                g_nodnsservers=true;
                isverified="unverified, no dns servers available"
            fi
            echo "  Error detected in running 'dig' for dns lookup of '$input'."
            echo "    Failed command:"
            echo "      $g_dig_exe +nocmd $dashx_arg \"$input\" +search +noall +answer"
            echo "    Command output:"
            echo "      $dnsout"
            echo
            g_dnserr=true;
        fi

        gret_dns_input=$input;
        gret_dns_aliasfq=$(echo "$dnsout" | sed -ne '/.*[[:space:]]IN[[:space:]]\+CNAME[[:space:]]*/ {s/\.[[:space:]].*//p }')
        if $isipaddr; then
            gret_dns_dnsnamefq=$(echo "$dnsout" | sed -ne '/.*[[:space:]]IN[[:space:]]\+PTR[[:space:]]*/ {s/.*[[:space:]]//p }')
            gret_dns_dnsnamefq=${gret_dns_dnsnamefq%.}
        else
            gret_dns_dnsnamefq=$(echo "$dnsout" | sed -ne '/.*[[:space:]]IN[[:space:]]\+A[[:space:]]*/ {s/\.[[:space:]].*//p }')
        fi
        gret_dns_ipaddr=$(echo "$dnsout" | sed -ne 's/^.*[[:space:]]IN[[:space:]]\+A[[:space:]]*//p')

        if $isipaddr; then
            # This is an ip address, it always resolves, so force the ipaddr
            gret_dns_ipaddr=$input;
            isverified="";
        fi

        if $mark_unverified_if_not_found; then
            if [[ -z "$gret_dns_ipaddr" ]] && [[ -z "$gret_dns_dnsnamefq" ]] && [[ -z "$gret_dns_aliasfq" ]]; then
                isverified="unverified, no dns entry found"
            fi
        fi

        gret_dns_verified=$isverified;

        # Add the results to the cache
        g_dnscache_input+=( "$gret_dns_input" )
        g_dnscache_aliasfq+=( "$gret_dns_aliasfq" )
        g_dnscache_dnsnamefq+=( "$gret_dns_dnsnamefq" )
        g_dnscache_ipaddr+=( "$gret_dns_ipaddr" )
        g_dnscache_verified+=( "$gret_dns_verified" )
        return 0;
    }

    dnslookup_allnames() {
        local fqname=$1; shift;

        local list;
        list=$(getnamelist "$fqname");
        local name;
        for name in $list; do
            dnslookup "$name"
        done
    }

    get_intf_ipaddrs() {
        local retval="";

        retval=$( (/sbin/ip --oneline -4 addr show 2> /dev/null || true) | sed -ne 's/^[[:space:]]*[0-9]\+:[[:space:]]\+\([^[:space:]]\+\).*[[:space:]]inet[[:space:]]\+\([\.0-9]\+\).*/\1##\2/p')

        if [[ -z "$retval" ]] ; then
            retval=$( (/sbin/ifconfig 2> /dev/null || true) | sed -ne '/^[^[:space:]]/ {s/[[:space:]].*//; h}; /^[[:space:]]*\(inet addr:\|inet \)/ { s/^[[:space:]]*\(inet addr:\|inet \)//; s/[[:space:]].*//; H; g; p }' | sed -e 'N;s/\n/##/');
        fi

        echo "$retval";
    }

    get_dnsnamelist() {
        local def_dnsname=$1; shift
        local def_srcstr=$1; shift

        local defaultval=""
        if [[ ! -z "$def_dnsname" ]] ; then
            defaultval=$(printf "%-12s (default, $def_srcstr)" $def_dnsname)
        fi

        local intf_ipaddrs;
        local intf_ipaddr;
        local intf;
        local ipaddr;
        local hostout;
        local stat;
        local list="";

        gret_list=();

        # Possible options for dns name:
        #   1. short dns hostname           (from 'hostname --fgdn')
        #   2. fully qualified dns hostname (from 'hostname --fqdn')
        #   3. short dns hostname           (from reverse dns lookup of ipaddr)
        #   4. fully qualified dns hostname (from reverse dns lookup of ipaddr)
        #   5. ipaddress
        #   6. dns CNAME alias for the dns hostname
        #
        # The first 5 are all theoreticaly discoverable.  The last one is not
        # (it is not possible to determine all the dns CNAME aliases with a dns
        # query.  The mapping is one way only, that is, you can determine the
        # host given a CNAME aliase, but you cannot query all the CNAME aliases
        # for a given host)..
        #
        # By default we will try to determine and verify all possible dns names
        # of the first 5.  Then we will give the user a menu choice of those,
        # along with allowing them to specify their own alternate name (which
        # we will verify has a dns mapping).
        #
        # We compute the possible choices by doing:
        #   a) look up all versions of the fully qualified hostname
        #   b) look up all versions of the aliases detected in a)
#   c) look up all versions of the dnsnames detected in a)
        #   d) reverse lookup of the ipaddrs detected in a)
        #   e) reverse lookup any ipaddr on the system
        #   f) look up all versions of dnsnames detected in e)
        # Then we go through all the names in the order we queried and
        # find those that map to an ipaddr on this machine.  Those names
        # generate a list that the user will select from

        # a) look up all versions of the fully qualified hostname
        stat=0;
        fqhostname=$(hostname --fqdn 2>&1) || stat=$?;
        if [[ $stat -ne 0 ]] ; then
            # On some systems, 'hostname --fqdn' requires dns to be up and
            # running and the host system validly registered with dns.  If
            # we get an error running 'hostname --fqdn', then try just
            # running 'hostname' and if that fails, ignore the hostname
            # altogether.
            stat=0;
            echo "  Could not detect fully qualified hostname (with 'hostname --fdqn')..."
            echo "    Failed Command:"
            echo "      hostname -fqdn"
            echo "    Error:"
            echo "      $fqhostname"
            echo
            fqhostname=$(hostname 2>&1) || stat=$?;
            if [[ $stat -ne 0 ]] ; then
                echo "  Could not detect short hostname (with 'hostname')..."
                echo "    Failed Command:"
                echo "      hostname"
                echo "    Error:"
                echo "      $fqhostname"
                echo
            fi
        fi
        if [[ ! -z "$fqhostname" ]] ; then
            dnslookup "$fqhostname" "mark_unverified_if_not_found"
            dnslookup "${fqhostname%%.*}" "mark_unverified_if_not_found"
            dnslookup_allnames "$fqhostname";
        fi

        # b) look up all versions of the aliases detected in a)
        if [[ ! -z "${g_dnscache_input[@]+${g_dnscache_input[@]}}" ]] ; then
            for ((i=0; i < ${#g_dnscache_input[@]} ; i++)); do
                list="$list ${g_dnscache_aliasfq[@]}";
            done
            list=$(uniqifylist "$list");
        fi
        for i in $list; do
            dnslookup_allnames "$i";
        done

        # c) look up all versions of the dnsnames detected in a)
        if [[ ! -z "${g_dnscache_input[@]+${g_dnscache_input[@]}}" ]] ; then
            for ((i=0; i < ${#g_dnscache_input[@]} ; i++)); do
                list="$list ${g_dnscache_dnsnamefq[@]}";
            done
            list=$(uniqifylist "$list");
        fi
        for i in $list; do
            dnslookup_allnames "$i";
        done

        # d) reverse lookup of the ipaddrs detected in a)
        if [[ ! -z "${g_dnscache_input[@]+${g_dnscache_input[@]}}" ]] ; then
            for ((i=0; i < ${#g_dnscache_input[@]} ; i++)); do
                list="$list ${g_dnscache_ipaddr[@]}";
            done
            list=$(uniqifylist "$list");
        fi
        for i in $list; do
            dnslookup_allnames "$i";
        done

        # e) reverse lookup any ipaddr on the system
        intf_ipaddrs=$(get_intf_ipaddrs)
        ipaddrs=""
        for intf_ipaddr in $intf_ipaddrs; do
            ipaddr=${intf_ipaddr#*##};
            if [[ $ipaddr =~ ^127\. ]] ; then
                continue
            fi
            ipaddrs="$ipaddrs $ipaddr";
        done
        for i in $ipaddrs; do
            dnslookup_allnames "$i";
        done

        # f) look up all versions of dnsnames detected in e)
        if [[ ! -z "${g_dnscache_input[@]+${g_dnscache_input[@]}}" ]] ; then
            for ((i=0; i < ${#g_dnscache_input[@]} ; i++)); do
                list="$list ${g_dnscache_dnsnamefq[@]}";
            done
            list=$(uniqifylist "$list");
        fi
        for i in $list; do
            dnslookup_allnames "$i";
        done

        if ! $opt_batch && $g_dnserr; then
            # Only issue this prompt if not in batch mode and we detected
            # errors in resolving dns
            ans=$(yesno_prompt "Errors encountered in dns detection, ignore and continue?")
            if [[ x"$ans" == x"n" ]] ; then
                abort;
            fi
        fi
        local strfmtd;
        local -a querylist;
        if [[ ! -z "$defaultval" ]] ; then
            querylist+=( "__DEFAULT__@@$defaultval" )
        fi
        if [[ ! -z "${g_dnscache_input[@]+${g_dnscache_input[@]}}" ]] ; then
            for ((i=0; i < ${#g_dnscache_input[@]} ; i++)); do
                if [[ ! -z ${g_dnscache_verified[$i]} ]] ; then
                    strfmtd=$(printf "%-12s (${g_dnscache_verified[$i]})" ${g_dnscache_input[$i]});
                    querylist+=( "${g_dnscache_input[$i]}@@$strfmtd" )
                else
                [[ -z "${g_dnscache_ipaddr[$i]}" ]] && continue;
                case " $ipaddrs " in
                    *" ${g_dnscache_ipaddr[$i]} "*)
                        if [[ ${g_dnscache_input[$i]} =~ ^[0-9\.]+$ ]]; then
                            for intf_ipaddr in $intf_ipaddrs; do
                                intf=${intf_ipaddr%##*};
                                ipaddr=${intf_ipaddr#*##};
                                if [[ x"$ipaddr" == x"${g_dnscache_input[$i]}" ]]; then
                                    break;
                                fi
                                intf="";
                            done
                            strfmtd=$(printf "%-12s ($intf)" ${g_dnscache_input[$i]});
                            querylist+=( "${g_dnscache_input[$i]}@@$strfmtd" )
                        else
                            querylist+=( "${g_dnscache_input[$i]}" )
                        fi
                        ;;
                esac
                fi
            done
        fi
        querylist+=( "__OTHER__@@Specify an alternate DNS name" )


        # Assign to the gret_list return array from the querylist array,
        # selecing the first thing that looks like a fully qualified domain
        # name as the first item in the list (which will be the default
        # value).  We are recommending to use the fully qualified domain
        # name, since it works better for wso2 certs and such.
        # NOTE: we cannot depend on "hostname -fqdn" returning the
        #       fully qualified donmain name, it is dependent on things like
        #       the order of entries in /etc/hosts, which may vary from
        #       machine to machine.

        # The order that dnsnames will be presented are:
        #   01 - __DEFAULT__ option, if active
        #   02 - any fully qualified name resolved by dns
        #   03 - any short name resolved by dns
        #   04 - any ip address
        #   05 - any fully qualified name, unverified by dns
        #   06 - any short name, unverified by dns
        #   07 - __OTHER__ option

        local -a unsorted;
        for ((i=0; i < ${#querylist[@]} ; i++)); do
            hostname=${querylist[$i]}
            hostname=${hostname%%@@*}

            # Note prepend "xx,$i:" to sort, so we will will sort first on
            # the type, then on the order we detected (e.g. first ipaddress
            # we read will be printed before others)
            if [[ x"$hostname" == x"$def_dnsname" ]] ; then
                # Do not duplicate $def_dnsname (already in __DEFAULT__)
                continue;
            elif [[ x"$hostname" == x"__DEFAULT__" ]] ; then
                unsorted+=(  "01,$i:${querylist[$i]}" );
            elif [[ x"$hostname" == x"__OTHER__" ]] ; then
                unsorted+=(  "07,$i:${querylist[$i]}" );
            elif [[ $hostname =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] ; then
                unsorted+=(  "04,$i:${querylist[$i]}" );
            elif [[ ${querylist[$i]} =~ \(unverified ]]; then
                if [[ $hostname =~ \. ]] ; then
                    unsorted+=( "05,$i:${querylist[$i]}" );
                else
                    unsorted+=( "06,$i:${querylist[$i]}" );
                fi
            else
                if [[ $hostname =~ \. ]] ; then
                    unsorted+=( "02,$i:${querylist[$i]}" );
                else
                    unsorted+=( "03,$i:${querylist[$i]}" );
                fi
            fi
        done

        # Sort the list
        local -a sorted;
        eval sorted=( $(printf "'%s'\n" "${unsorted[@]}" | sort -n) );

        # Set the return gret_list array
        for i in "${sorted[@]}"; do
            gret_list+=( "${i#*:}" );
        done
    }

    local helptxt_gui_port
    helptxt_gui_port=$(get_cfg_val "smrtlink" "gui_port");

    next_screen;
    $opt_batch || cat <<-EOF

	----- Part 2 of 10: SMRT Link Server DNS -----

	The DNS name of the SMRT Link host server is used for creating SMRT
	Link URLs (e.g. http://smrtlink.example.com:${helptxt_gui_port}), ensuring
	an SSL certificate will validate.  If the Domain Name System (DNS)
	does not resolve to the expected address, then an IP address must
	be used.

	EOF

    local def_dnsname;
    local def_srcstr;
    def_dnsname=$(get_cfg_val "smrtlink" "dnsname");
    def_srcstr=$(get_cfg_srcstr "smrtlink" "dnsname");

    # Make sure the 'usedefault' setting is always false for dnsname, so that
    # the dnsname will be written out to the config file and always remain
    # unchanged between upgrades.
    set_cfg_usedefault "smrtlink" "dnsname" false;

    echo "Detecting DNS names..."
    echo

    # This sets gret_list:
    get_dnsnamelist "$def_dnsname" "$def_srcstr"

    if [[ -z "$def_dnsname" ]] ; then
        def_dnsname="${gret_list[0]}"
        def_dnsname="${def_dnsname%%@@*}";
        set_cfg_val "smrtlink" "dnsname" "$def_dnsname" "detected dnsname" false;
    fi
    if ! $opt_batch; then
        process_query "1" "${gret_list[@]}"

        local dnsname;
        if [[ x"$gret_val" == x"__DEFAULT__" ]] ;then
            local srcstr;
            srcstr=$(get_cfg_srcstr "smrtlink" "dnsname");
            srcstr="$srcstr, accepted interactively"
            set_cfg_val "smrtlink" "dnsname" "$def_dnsname" "$srcstr" false;
        elif [[ x"$gret_val" == x"__OTHER__" ]] ;then
            read -p "Specify the DNS name: " ans
            logonly "$ans"
            dnsname=$ans;
            set_cfg_val "smrtlink" "dnsname" "$dnsname" "specified interactively" false;
        else
            dnsname=$gret_val;
            set_cfg_val "smrtlink" "dnsname" "$dnsname" "selected interactively" false;
        fi
    fi

    echo
    print_cfg_usingstr "smrtlink" "dnsname"

    # Update the tsreport information, now that we know the dnsname
    tsreport_update_setup;
}

assign_cfg_val_interactive() {
    local group=$1; shift;
    local name=$1; shift;
    local sp; [[ ! -z "${1+set}" ]] && sp=$1 && shift;

    gret__interrupted=false;

    # Indent two spaces by default
    [[ -z "${sp+set}" ]] && sp="  "

    if $opt_batch; then
        return 0;
    fi

    local curval;
    local usedefault;
    local srcstr;
    local datatype;
    local promptstr;
    curval=$(get_cfg_val "$group" "$name")
    usedefault=$(get_cfg_usedefault "$group" "$name")
    srcstr=$(get_cfg_srcstr "$group" "$name")
    datatype=$(get_cfg_datatype "$group" "$name")
    specialcase=$(get_cfg_specialcase "$group" "$name")
    promptstr=$(get_cfg_promptstr "$group" "$name")

    local ans;
    local ans_withdef;
    local defstr=$curval;

    while true; do

        if [[ x"$datatype" == x"boolval" ]]; then
            if [[ x"$curval" != x"false" ]] ; then
                ans=$(yesno_prompt "${sp}$promptstr")
            else
                ans=$(noyes_prompt "${sp}$promptstr")
            fi
            if [[ x"$ans" == x"y" ]] ; then
                ans=true;
            else
                ans=false;
            fi
        else
            local silentopt="";
            if [[ $specialcase =~ :prompt_noecho: ]] ; then
                # Don't echo prompt input and do not log it, also do not list
                # a default
                if [[ ! -z "$defstr" ]] ; then
                    defstr="<HIDDEN>"
                fi
                silentopt="-s"
            fi

            if [[ $specialcase =~ :interruptable: ]] ; then
                # Allow trapping of a Ctrl-C interrupt on the prompt input.
                # If we get an interrupt, we will set gret__interrupted to
                # true for the higher level to handle.
                # An exit status of 130, i.e. 128 + 2, indicates the read
                # command was killed with SIGNAL number 2, i.e. SIGINT,
                # indicating the user probably typed a Ctrl-C at the input.
                # The funky conditionals after the read just make sure that
                # we get a proper line number on any other error condition
                # when executing the read command.
                local stat=0;
                # Swallow the Ctrl-C interrupt trap
                trap true INT;
                # Run the read, allowing either exit status 0 (normal good
                # status) or 130 (got an interrupt), dying with an error
                # otherwise
                read $silentopt -p "${sp}$promptstr [$defstr]: " ans || stat=$?; [[ $stat -ne 130 ]] && bash -c "exit $stat";
                # Set the Ctrl-C interrupt trap back to the default
                trap - INT;

                if [[ $stat -eq 130 ]] ; then
                    ans="";
                    gret__interrupted=true;
                elif [[ $ans == "__CANCEL__" ]] ||
                     [[ $ans == "__INTERRUPT__" ]]; then
                    ans="";
                    gret__interrupted=true;
                fi
            else
                read $silentopt -p "${sp}$promptstr [$defstr]: " ans;
            fi

            if [[ $specialcase =~ :prompt_noecho: ]] ; then
                logonly "<<INPUT_MASKED>>"
                # The newline was not echoed, print one here
                echo
            else
                logonly "$ans"
            fi
        fi

        # Indent any error or info strings two spaces more than promptsr
        local indentsp="$sp  "
        if [[ $promptstr =~ ^([[:space:]]+) ]] ; then
            indentsp="${indentsp}${BASH_REMATCH[1]}"
        fi

        if $gret__interrupted; then
            local indentsp="$sp  "
            echo
            echo "${indentsp}Got interrupt..."
            echo
            break;
        fi

        ans_withdef=$ans
        [[ -z "$ans_withdef" ]] && ans_withdef="$defstr";


        if  [[ x"$ans_withdef" == x"__DEFAULT__" ]] ||
            [[ x"$ans_withdef" == x"__USE_DEFAULT__" ]] ; then
            # Reset to the default and loop through again
            echo "${indentsp}Resetting to default, enter again..."

            set_cfg_val "$group" "$name" "$ans_withdef" "reset to default interactively" true;
            defstr=$(get_cfg_val "$group" "$name")
            continue;
        elif  [[ x"$ans_withdef" == x"__CANCEL__" ]] ||
              [[ x"$ans_withdef" == x"__INTERRUPT__" ]] ; then
            # Reset to the default and loop through again
            echo "${indentsp}Ignoring '$ans_withdef' request..."
            continue;
        elif [[ x"$ans_withdef" == x"__EMPTY__" ]] ||
             [[ x"$ans_withdef" == x'""' ]] ||
             [[ x"$ans_withdef" == x"''" ]] ; then

            # Set to empty string, since <ENTER> will just accept default
            echo "${indentsp}Setting to empty string..."

            set_cfg_val "$group" "$name" "" "Set to empty string interactively" true;
            defstr=$(get_cfg_val "$group" "$name")
            break;
        elif validate_datatype "$datatype" "$ans_withdef" "$specialcase"; then
            if [[ -z "$ans" ]] ; then
                # set_cfg_srcstr "$group" "$name" "$srcstr, accepted interactively"
                set_cfg_srcstr "$group" "$name" "$srcstr, accepted"
            else
                set_cfg_val "$group" "$name" "$ans_withdef" "configured interactively" false;
            fi
            # Found valid input, break out of input loop
            break;
        else
            echo "${indentsp}Invalid answer '$ans_withdef' ($gret__validate_datatype__errstr).  Try again..." 1>&2
            echo

            continue;
        fi

        srcstr=$(get_cfg_srcstr "$group" "$name")
    done
}

print_cfg_usingstr() {
    local group=$1; shift;
    local name=$1; shift;
#    local usingstr=$1; shift;

    local val;
    local srcstr;
    local usingstr
    local specialcase;
    specialcase=$(get_cfg_specialcase "$group" "$name")

    val=$(get_cfg_val "$group" "$name")
    if [[ ! -z "$val" ]] ; then
        if [[ $specialcase =~ :prompt_noecho: ]] ; then
            val="<masked>"
        fi
    fi
    srcstr=$(get_cfg_srcstr "$group" "$name")
    usingstr=$(get_cfg_usingstr "$group" "$name")

    local usedefault
    local usedefaultstr=""
    usedefault=$(get_cfg_usedefault "$group" "$name")
    usedefaultstr=""
    # Flag default (for debugging mostly)
    #if $usedefault; then
    #    # usedefaultstr="  [default]"
    #    usedefaultstr="  (*)"
    #fi

    echo "$usingstr ($srcstr): $val${usedefaultstr}"
}


common_prompt_smrtlink_server() {
    next_screen;
    $opt_batch || cat <<-EOF

	----- Part 3 of 10: SMRT Link Setup  ----

	SMRT Link requires two ports for proper operation. These ports cannot
	be used for listening by any other processes.  The SMRT Link GUI port
	serves to redirect from an unencrypted http connection to the login
	page using secure https.

	EOF

    assign_cfg_val_interactive "smrtlink" "gui_port";
    assign_cfg_val_interactive "smrtlink" "services_port";

    $opt_batch || cat <<-EOF

	Memory settings must be preset for SMRT Link.  By default, we set the
	initial (-Xms) and maximum (-Xmx) Java heap sizes to the same values.
	The default for SMRT Link services is 25% of the total memory with a
	maximum of 32768 MB.  The default for SMRT Link GUI is 5% of the total
	memory with a maximum of 8192 MB.  We recommend using the defaults.

	EOF

    while true; do
        assign_cfg_val_interactive "smrtlink" "services_minmem";
        assign_cfg_val_interactive "smrtlink" "services_maxmem";

        # Convert mem options to MiB if units are specfied
        cfg_arg_to_MiB "smrtlink" "services_minmem"
        cfg_arg_to_MiB "smrtlink" "services_maxmem"

        local services_minmem;
        local services_maxmem;
        services_minmem=$(get_cfg_val "smrtlink" "services_minmem");
        services_maxmem=$(get_cfg_val "smrtlink" "services_maxmem");

        # Express this conditional as "! -le" rather than "-gt", as it will
        # catch not only the case where the minmem is greater then the maxmem,
        # but it will also catch the case where one or both of the values
        # is not numeric (the exit status of the conditional will be non-zero
        # in either case, so the inversion will cause us to end up in the
        # 'then' clause and exit the script.  Although we will exit on the
        # "must be less than" statement, the non-numeric error will also be
        # printed directly before.
        if [[ ! $services_minmem -le $services_maxmem ]] ; then
            if $opt_batch; then
                merror "SMRT Link Services initial memory ($services_minmem) must be less than or equal to SMRT Link Services maximum memory ($services_maxmem)"
            fi
            echo
            echo "    SMRT Link Services initial memory must be less than or equal to maximum"
            echo "    memory.  Try again..."
            echo
        else
            # All is well, move on.
            break
        fi
    done

    echo
    print_cfg_usingstr "smrtlink" "gui_port"
    print_cfg_usingstr "smrtlink" "services_port"
    print_cfg_usingstr "smrtlink" "services_minmem"
    print_cfg_usingstr "smrtlink" "services_maxmem"
}

common_prompt_database_setup() {
    next_screen;

    $opt_batch || cat <<-EOF

	----- Part 4 of 10: Database Setup  -----

	The SMRT Link server uses a database which will need access to
	a network port and a directory to store database data files.

	The port will only be used to access the database from the install
	host.

	The database data directory should be located on a local (not shared)
	disk partition.  The default location will be in the SMRT Link Local
	File System Root Directory, specified above.

	EOF


    assign_cfg_val_interactive "database" "dbport";

    local dbdatadir_val;
    local dbdatadir_defval;
    dbdatadir_defval=$(get_cfg_val "database" "dbdatadir")
    while true; do
        while true; do
            assign_cfg_val_interactive "database" "dbdatadir";
            dbdatadir_val=$(get_cfg_val "database" "dbdatadir")
            if is_valid_user_dir "$dbdatadir_val"; then
                break;
            fi
            set_cfg_val "database" "dbdatadir" "$dbdatadir_defval" "reset to prev default" true;
        done

        dbdatadir_val=$(get_cfg_val "database" "dbdatadir")
        if [[ ! -e "$dbdatadir_val" ]] ; then
            if ! $opt_batch; then
                ans=$(yesno_prompt "  Directory '$dbdatadir_val' does not exist.  Create it?")
            else
                ans="y"
            fi
            if [[ x"$ans" == x"y" ]] ; then
                echo
                echo "    Creating directory '$dbdatadir_val'...";
                local stat;
                stat=0;
                mkdir -p "$dbdatadir_val" || stat=$?
                if [[ $stat -ne 0 ]] ; then
                    echo
                    echo "Could not create directory, try again..."
                    echo
                    continue;
                fi
            fi
        fi

        # Check to see if dbdatadir is on a local or remote file system
        if ! df -l "$dbdatadir_val" > /dev/null 2>&1 ; then
            local fstype;
            fstype=$(stat -f -c "%T" "$dbdatadir_val")
            cat <<-EOF

	WARNING! WARNING! WARNING! WARNING! WARNING! WARNING!

	The database directory appears to be on a remote filesystem
	(fstype: $fstype).  Depending on network and disk perfomance,
	this may cause performance problems in the SMRT Link system.

	WARNING! WARNING! WARNING! WARNING! WARNING! WARNING!

	EOF

            if ! $opt_batch; then
                ans=$(yesno_prompt "  Directory '$dbdatadir_val' appears to be a remote disk, fstype '$fstype'.  Ignore warning?")
            else
                echo "Directory '$dbdatadir_val' appears to be a remote disk, fstype '$fstype'.  Ignoring warning..."
                ans="y"
            fi
            if [[ x"$ans" == x"y" ]] ; then
                break;
            fi
        fi
        break;
    done

    echo
    print_cfg_usingstr "database" "dbport"
    print_cfg_usingstr "database" "dbdatadir"
}

common_prompt_cromwell_server() {
    next_screen;
    $opt_batch || cat <<-EOF

	----- Part 5 of 10: Cromwell Server Setup  ----

	The Cromwell Workflow Engine Server requires one port for proper
	operation.  This port cannot be used for listening by any other
	process.  Select a port number which does not conflict with any
	other programs.

	EOF

    assign_cfg_val_interactive "cromwell" "port";

    echo
    print_cfg_usingstr "cromwell" "port"
}

is_path_below_dir() {
    local path=$1; shift;
    local dir=$1; shift;

    local path_realabs
    local dir_realabs;
    path_realabs=$(readlink -m "$path")
    dir_realabs=$(readlink -m "$dir")

    local retstat=1;
    case "$path_realabs" in
        "$dir_realabs")   retstat=0;;
        "$dir_realabs"/*) retstat=0;;
    esac

    return $retstat;
}

is_path_same_as_dir() {
    local path=$1; shift;
    local dir=$1; shift;

    local path_realabs;
    local dir_realabs;
    path_realabs=$(readlink -m "$path")
    dir_realabs=$(readlink -m "$dir")

    local retstat=1;
    case "$path_realabs" in
        "$dir_realabs") retstat=0;;
    esac

    return $retstat;
}

is_path_through_symlink() {
    local path=$1; shift;
    local dir=$1; shift;

    local retstat=1;
    case "$path" in
        "$dir") retstat=0;;
        "$dir"/*) retstat=0;;
    esac

    return $retstat;
}

is_valid_user_dir() {
    local dir=$1; shift;

    local retstat=0;

    if is_path_through_symlink "$dir" "$g_jobsroot_dirlink"; then
        echo "    Invalid directory, cannot go through jobs_root symlink.  Try again..."
        retstat=1;
    elif is_path_through_symlink "$dir" "$g_tmpdir_dirlink"; then
        echo "    Invalid directory, cannot go through tmp_dir symlink.  Try again..."
        retstat=1;
    elif is_path_through_symlink "$dir" "$g_dbdatadir_dirlink"; then
        echo "    Invalid directory, cannot go through db_datadir symlink.  Try again..."
        retstat=1;
    elif is_path_same_as_dir "$dir" "$g_userdata_dirabs"; then
        echo "    Invalid directory, same as userdata dir.  Try again..."
        retstat=1;
    elif is_path_same_as_dir "$dir" "$opt_rootdir"; then
        echo "    Invalid directory, same as smrtlink rootdir.  Try again..."
        retstat=1;
    elif is_path_below_dir "$dir" "$g_userdata_dirabs"; then
        # valid to specify dir in the userdata dir, all is well
        :
    elif is_path_below_dir "$dir" "$opt_rootdir"; then
        echo "    Invalid directory, must be in userdata or outside of smrtlink rootdir.  Try again..."
        retstat=1;
    fi

    if [[ $retstat -ne 0 ]] ; then
        echo
        if $opt_batch; then
            abort;
        fi
    fi

    return $retstat;
}

common_prompt_dirsetup() {
    next_screen;

    $opt_batch || cat <<-EOF

	----- Part 6 of 10: User-specific Directories Setup  -----

	The following directories should be configured to point to the
	actual locations:

	    jobs_root
	    tmp_dir

	EOF


    # Determine jobsroot
    $opt_batch || cat <<-EOF
	jobs_root - This directory stores output from SMRT Analysis and needs
	            to be large (> 15TB).

	EOF

    local jobsrootdir_val;
    local jobsrootdir_defval;
    jobsrootdir_defval=$(get_cfg_val "datadirs" "jobsroot_dir")
    while true; do
        while true; do
            assign_cfg_val_interactive "datadirs" "jobsroot_dir";
            jobsrootdir_val=$(get_cfg_val "datadirs" "jobsroot_dir")
            if is_valid_user_dir "$jobsrootdir_val"; then
                break;
            fi
            set_cfg_val "datadirs" "jobsroot_dir" "$jobsrootdir_defval" "reset to prev default" true;
        done

        if [[ ! -e "$jobsrootdir_val" ]] ; then
            if ! $opt_batch; then
                ans=$(yesno_prompt "  Directory '$jobsrootdir_val' does not exist.  Create it?")
            else
                ans="y"
            fi
            if [[ x"$ans" == x"y" ]] ; then
                echo
                echo "    Creating directory '$jobsrootdir_val'...";
                local stat;
                # Make sure the jobs_root gets created with group/other
                # r-x permissions
                stat=0;
                (umask 0022; mkdir -p "$jobsrootdir_val") || stat=$?
                if [[ $stat -ne 0 ]] ; then
                    echo
                    echo "Could not create directory, try again..."
                    echo
                    continue;
                fi
            fi
        fi
        break;
    done

    # Determine tmpdir
    $opt_batch || cat <<-EOF

	tmp_dir - This directory is used for fast I/O operations, and should be
	          a local directory (not NFS mounted) and needs to be large for
	          large genome assembly jobs (>500GB minimum, 1TB recommended).
	          This directory will be automatically created, as needed, on
	          compute cluster nodes.

	          The directory must exist on each cluster node and be writable
	          to the SMRT Link user. If missing, SMRT Link will attempt to
	          create this destination if permissions permit.

	EOF


    local tmpdir_val;
    local tmpdir_defval;
    tmpdir_defval=$(get_cfg_val "datadirs" "tmpdir_dir")
    while true; do
        while true; do
            assign_cfg_val_interactive "datadirs" "tmpdir_dir";
            tmpdir_val=$(get_cfg_val "datadirs" "tmpdir_dir")
            if is_valid_user_dir "$tmpdir_val"; then
                break;
            fi
            set_cfg_val "datadirs" "tmpdir_dir" "$tmpdir_defval" "reset to prev default" true;
        done

        tmpdir_dir_val=$(get_cfg_val "datadirs" "tmpdir_dir")
        if [[ ! -e "$tmpdir_dir_val" ]] ; then
            if ! $opt_batch; then
                ans=$(yesno_prompt "  Directory '$tmpdir_dir_val' does not exist.  Create it?")
            else
                ans="y"
            fi
            if [[ x"$ans" == x"y" ]] ; then
                echo
                echo "    Creating directory '$tmpdir_dir_val'...";
                local stat;
                stat=0;
                mkdir -p "$tmpdir_dir_val" || stat=$?
                if [[ $stat -ne 0 ]] ; then
                    echo
                    echo "Could not create directory, try again..."
                    echo
                    continue;
                fi
            fi
        fi
        break;
    done


    echo
    print_cfg_usingstr "datadirs" "jobsroot_dir"
    print_cfg_usingstr "datadirs" "tmpdir_dir"
}


check_url_available() {
    local url=$1;

    local stat=0;
    "$g_curl_exe" --output /dev/null --max-time 5 --silent --head --fail --insecure "$url" || stat=$?

    return $stat
}

get_eve_rawurl() {
    local retval;

    local eve_url;
    eve_url=$(get_cfg_val "remote" "eve_url")

    retval=$eve_url
    if [[ -z "$eve_url" ]] ; then

        local remote_eve_server_select;
        remote_eve_server_select=$(get_cfg_val "remote" "eve_server_select")

        case "$remote_eve_server_select" in
            "production")  retval=$g_eve_url_production;;
            "staging")     retval=$g_eve_url_staging;;
            "beta")        retval=$g_eve_url_beta;;
            "alpha")       retval=$g_eve_url_alpha;;
            *) minterror "Unrecognized remote_eve_server_select: $remote_eve_server_select";;
        esac
    fi

    echo "$retval";
}

get_eve_url() {
    local retval;

    retval=$(get_eve_rawurl);

    echo "$retval"
}

get_update_enable() {
    local retval;

    retval=$(get_cfg_val "remote" "update_enable")

    echo "$retval"
}

get_update_rawurl() {
    local retval;

    local update_url;
    update_url=$(get_cfg_val "remote" "update_url")

    retval=$update_url
    if [[ -z "$update_url" ]] ; then
        local remote_update_server_select;
        remote_update_server_select=$(get_cfg_val "remote" "update_server_select")

        case "$remote_update_server_select" in
            "production")  retval=$g_update_url_production;;
            "staging")     retval=$g_update_url_staging;;
            "beta")        retval=$g_update_url_beta;;
            "alpha")       retval=$g_update_url_alpha;;
            *) minterror "Unrecognized remote_update_server_select: $remote_update_server_select";;
        esac
    fi

    echo "$retval";
}

get_update_url() {
    local retval;

    local update_enable;
    update_enable=$(get_update_enable)

    retval="";
    if $update_enable; then
        retval=$(get_update_rawurl);
    fi

    echo "$retval"
}


common_prompt_remoteurls() {
    next_screen;

    local helptxt_eve_url;
    local helptxt_update_url;
    helptxt_eve_url=$(get_eve_url);
    helptxt_update_url=$(get_update_url);

    $opt_batch || cat <<-EOF

	---- Part 7 of 10: Remote Service Setup ----

	The '$g_eve_official_name' provides the ability to send to PacBio:
	1) installation troubleshooting logs, 2) analysis failure logs, and
	3) SMRT Link usage information, not including sample names or sequence
	data.

	The '$g_update_official_name' will provide automatic notification and
	installation of chemistry bundle files compatible with new PacBio
	consumables.

	Note: SMRT Link Event Services will be configured in browser UI on first
	      access after install or upgrade.
	EOF


    if ! $opt_batch; then
        local eve_url;
        local update_url;
        eve_url=$(get_eve_url);
        update_url=$(get_update_url);

        local eve_url_available=true;
        local update_url_available=true;

        if $opt_skip_remoteurl_check; then
            echo "Skipping remote URL connectivity test."
        elif [[ ! -z "$eve_url" ]] || [[ ! -z "$update_url" ]] ; then
            echo "  Checking remote service URLs..."

            # Flush any extra input we may have gotten up until now
            # (trying not to mess up the connectivity check output)
            flush_input;

            local -a unavailable_urls=();
            if [[ ! -z "$eve_url" ]] ; then
                local eve_status_url="$eve_url/status";
                echo -n "    Checking $g_eve_official_name URL... "
                if check_url_available "$eve_status_url" ; then
                    echo "ok"
                else
                    echo "unavailable"
                    eve_url_available=false;
                    unavailable_urls+=( "$g_eve_official_name:  $eve_status_url" );
                fi
            fi

            if [[ ! -z "$update_url" ]] ; then
                local update_status_url="$update_url/status";
                echo -n "    Checking $g_update_official_name URL... "
                if check_url_available "$update_status_url" ; then
                    echo "ok"
                else
                    echo "unavailable"
                    update_url_available=false;
                    unavailable_urls+=( "$g_update_official_name: $update_status_url" );
                fi
            fi

            if [[ -z "${unavailable_urls[@]+${unavailable_urls[@]}}" ]]; then
                echo
                echo "All enabled remote service URLs are available."
            else
                local internet_available=false;
                echo
                echo -n "    Checking internet connection... "
                if  check_url_available "$g_internet_testurl1" ||
                    check_url_available "$g_internet_testurl2" ; then
                    echo "ok"
                    internet_available=true;
                else
                    echo "unavailable"
                fi

                echo
                if [[ ${#unavailable_urls[@]} -gt 1 ]] ; then
                    echo "  The following URLs are unavailable: "
                else
                    echo "  The following URL is unavailable: "
                fi
                echo
                local urlstr;
                for urlstr in "${unavailable_urls[@]}"; do
                    echo "    $urlstr"
                done

                echo
                if [[ ${#unavailable_urls[@]} -gt 1 ]] ; then
                    echo "  Test Commands:"
                else
                    echo "  Test Command:"
                fi
                local curlexe;
                if PATH=$PATH_ORIG which "curl" > /dev/null 2>&1; then
                    curlexe="curl"
                else
                    curlexe="$g_curl_exe"
                fi
                echo
                if ! $eve_url_available; then
                    echo "    $curlexe --max-time 5 --head --fail --insecure \"$eve_url/status\""
                fi
                if ! $update_url_available; then
                    echo "    $curlexe --max-time 5 --head --fail --insecure \"$update_url/status\""
                fi


                if ! $eve_url_available; then
                    cat <<-EOF

	    WARNING - Connection with PacBio Event Service failed. You can install
	              SMRT Link, but you will not be able to send troubleshooting
	              information, if needed, through SMRT Link.  Consult the
	              Network Diagram in the Site Preparation Guide and ask your
	              network administrator to open outbound connections to:
	                  $eve_url
	EOF
                fi
                if ! $update_url_available; then
                    cat <<-EOF

	    WARNING - Connection with PacBio Update Service failed. You can install
	              SMRT Link, but you will not be able to receive notifications
	              for chemistry updates through SMRT Link.  Consult the Network
	              Diagram in the Site Preparation Guide and ask your network
	              administrator to open outbound connections to:
	                  $update_url
	EOF
                fi

                # Flush any input we may have gotten during the check
                # (make sure the user sees this prompt)
                flush_input;

                echo
                if $internet_available; then
                    if [[ ${#unavailable_urls[@]} -gt 1 ]] ; then
                        echo "There appears to be a connection to the internet, but the remote servers"
                        echo "are temporarily unavailable."
                    else
                        echo "There appears to be a connection to the internet, but the remote server"
                        echo "is temporarily unavailable."
                    fi
                else
                    echo "There does not appear to be a connection to the internet."
                fi
                if ! $update_url_available; then
                    echo
                    echo "If the Update Server will not be accessible to SMRT Link, then the"
                    echo "'$g_update_official_name' should be disabled by answering 'N' to the"
                    echo "following question."
                fi
            fi
        else
            echo
            echo "No remote URLs enabled, skipping checks..."
        fi
    fi

    echo
    assign_cfg_val_interactive "remote" "update_enable";

    echo
    print_cfg_usingstr "remote" "update_enable"
}

common_prompt_mail_notification() {
    next_screen;
    $opt_batch || cat <<-EOF

	---- Part 8 of 10: SMRT Link Analysis Job Email Notification ----

	SMRT Link can be configured to send email notifications of completed
	analysis jobs to the user who launched the analysis (for both successful
	and failed jobs).

	SMRT Link only supports connections to SMTP Relays without encryption.
	Servers using basic authentication, SSL/TLS, or STARTTLS are not
	supported.

	Email notification is disabled if the outgoing mail server host is
	empty.

	Configure the SMTP mail host and port settings below.

	EOF

    target_emailaddr="";
    mail_auth_disabled=true;
    while true; do
        assign_cfg_val_interactive "smrtlink" "mail_host";

        local mail_host;
        mail_host=$(get_cfg_val "smrtlink" "mail_host";)
        if [[ -z "$mail_host" ]] ; then
            echo
            echo "    No mail host specified.  SMRT Link mail notification disabled."
            break;
        fi

        assign_cfg_val_interactive "smrtlink" "mail_port";

        # Do not prompt for the mail_user and mail_password for now, as
        # authentication is not working yet.
        if ! $mail_auth_disabled; then
        while true; do
            assign_cfg_val_interactive "smrtlink" "mail_user";
            local mail_user;
            mail_user=$(get_cfg_val "smrtlink" "mail_user";)
            if [[ -z "$mail_user" ]] ; then
                echo
                echo "    No mail user specified.  Enabling SMRT Link mail notification, "
                echo "    but disabling mail server authentication."
                unset_cfg_val "smrtlink" "mail_password";
                break;
            else
                assign_cfg_val_interactive "smrtlink" "mail_password";
                local mail_password;
                mail_password=$(get_cfg_val "smrtlink" "mail_password";)
                if [[ -z "$mail_password" ]] ; then
                    echo
                    echo "    Authentication password may not be empty, when authentication user is"

                    echo "    specified.  Specify __EMPTY__, "" or '' as the user to disable"
                    echo "    authentication."
                    echo
                    if $opt_batch; then
                        abort;
                    fi
                    continue;
                fi
                break;
            fi
        done
        fi #false

        if $opt_batch; then
            break;
        fi

        echo
        ans=$(noyes_prompt "  Send a test message?")
        if [[ x"$ans" == x"y" ]] ; then
            while true; do
                ans=$(string_prompt "    Enter target email address:" "$target_emailaddr");
                if [[ ! $ans =~ \@ ]] ; then
                    echo "      Enter valid email address..."
                    continue;
                fi
                target_emailaddr=$ans;
                break;
            done

            local mail_host;
            local mail_port;
            local mail_user;
            local mail_password;
            mail_port=$(get_cfg_val "smrtlink" "mail_port";)
            mail_user=$(get_cfg_val "smrtlink" "mail_user";)
            mail_password=$(get_cfg_val "smrtlink" "mail_password";)

            echo
            echo "      Sending test email address to '$target_emailaddr'..."

            local -a testmail_args=()
            if [[ ! -z "$mail_user" ]]; then
                # If mail user is specified (authentication enabled), then
                # the password must be specified and not empty).  That should
                # be guaranteed wih the checks above.
                testmail_args+=( "--user" "$mail_user" );
                testmail_args+=( "--password" "$mail_password" );
            fi

            local testmail_logfile="$g_installer_logdir/testmail.log";
            local testmail_stdout="$g_installer_logdir/testmail.stdout";
            local testmail_stderr="$g_installer_logdir/testmail.stderr";
            local starting_str="==== Starting send-test-email: $(date)";
            echo  "$starting_str">> "$testmail_logfile"
            echo  "$starting_str">> "$testmail_stdout"
            echo  "$starting_str">> "$testmail_stderr"
            stat=0;
            JAVA_HOME="$g_javahome" JAVA_OPTS="-Djava.library.path=" \
                "$g_smrtlink_toolsbin/send-test-email" \
                    --debug \
                    --log-file "$testmail_logfile" \
                    --host "$mail_host" --port "$mail_port" \
                    "${testmail_args[@]+${testmail_args[@]}}" \
                    "$target_emailaddr" >> "$testmail_stdout" 2>> "$testmail_stderr" || stat=$?;
            # Hide the password that is printed in the clear in the logfile
            sed -i -e 's/\(SendTestEmailOptions(.*,Some(.*),\)Some(..*))$/\1Some(<HIDDEN>))/' "$testmail_logfile"
            echo
            if [[ $stat -ne 0 ]] ; then
                sed -e "1,/^$starting_str/d" "$testmail_logfile"
                # sed -e "1,/^$starting_str/d" "$testmail_stderr"
                echo
                echo "      Encountered error in sending email."
            else
                echo "      Email to '$target_emailaddr' sent successfully."
                echo "      Please check email for test message."
            fi
            echo
            ans=$(yesno_prompt "  Keep current email notification settings?")
            if [[ x"$ans" != x"y" ]] ; then
                continue;
            fi
        fi
        break;
    done

    echo

    print_cfg_usingstr "smrtlink" "mail_host"
    print_cfg_usingstr "smrtlink" "mail_port"
    if ! $mail_auth_disabled; then
        print_cfg_usingstr "smrtlink" "mail_user"
        print_cfg_usingstr "smrtlink" "mail_password"
    fi
}


# Verify that a directory is a valid sge bindir (i.e. contains all the
# required programs).
verify_sge_bindir() {
    local bindir=$1; shift;

    # Sge should have all of these programs in the install directory.  If
    # the directory contains all of them and they are all executable, we will
    # assume we have found a valid sge directory.
    local sge_required_progs="qsub qdel qstat qconf"

    local prog;
    local missingprogs=""
    for prog in $sge_required_progs; do
        if [[ ! -x "$bindir/$prog" ]]; then
            missingprogs+=" $prog"
        fi
    done

    gret__verify_sge_bindir__missingprogs="";
    if [[ ! -z "$missingprogs" ]] ; then
        gret__verify_sge_bindir__missingprogs=${missingprogs# };
        return 1;
    fi

    # Found all the required progs, looks like a valid bindir
    return 0;
}

# Verify that a directory is a valid pbs bindir (i.e. contains all the
# required programs).
verify_pbs_bindir() {
    local bindir=$1; shift;

    # Pbs should have all of these programs in the install directory.  If
    # the directory contains all of them and they are all executable, we will
    # assume we have found a valid pbs directory.
    # Required programs in the bindir for each JMS basetype
    local pbs_required_progs="qsub qdel qstat pbsnodes qmgr"

    local prog;
    local missingprogs=""
    for prog in $pbs_required_progs; do
        if [[ ! -x "$bindir/$prog" ]]; then
            missingprogs+=" $prog"
        fi
    done

    gret__verify_pbs_bindir__missingprogs="";
    if [[ ! -z "$missingprogs" ]] ; then
        gret__verify_pbs_bindir__missingprogs=${missingprogs# };
        return 1;
    fi

    # Found all the required progs, looks like a valid bindir
    return 0;
}

# Verify that a directory is a valid lsf bindir (i.e. contains all the required
# programs).
verify_lsf_bindir() {
    local bindir=$1; shift;

    # LSF (and derivatives) have 'bsub', 'bkill' and 'lsid' in the install
    # directory.  If we find a directory on the path with all three of them
    # in the path, then we will guess we have LSF.
    # Note we have a test machine that has a bsub in /usr/bin (but none of
    # the others) and all three in another directory on the path.  So if we
    # just use the path for bsub, we will not find the real directory.
    local lsf_required_progs="bsub bkill bjobs bqueues lsid"

    local prog;
    local missingprogs=""
    for prog in $lsf_required_progs; do
        if [[ ! -x "$bindir/$prog" ]]; then
            missingprogs+=" $prog"
        fi
    done

    gret__verify_lsf_bindir__missingprogs="";
    if [[ ! -z "$missingprogs" ]] ; then
        gret__verify_lsf_bindir__missingprogs=${missingprogs# };
        return 1;
    fi

    # Found all the required progs, looks like a valid bindir
    return 0;
}

# Verify that a directory is a valid slurm bindir (i.e. contains all the
# required programs).
verify_slurm_bindir() {
    local bindir=$1; shift;

    # Slurm should have all of these programs in the install directory.  If
    # the directory contains all of them and they are all executable, we will
    # assume we have found a valid slurm directory.
    local slurm_required_progs="salloc srun sbatch sinfo"

    local prog;
    local missingprogs=""
    for prog in $slurm_required_progs; do
        if [[ ! -x "$bindir/$prog" ]]; then
            missingprogs+=" $prog"
        fi
    done

    gret__verify_slurm_bindir__missingprogs="";
    if [[ ! -z "$missingprogs" ]] ; then
        gret__verify_slurm_bindir__missingprogs=${missingprogs# };
        return 1;
    fi

    # Found all the required progs, looks like a valid bindir
    return 0;
}

determine_sge_subtype() {
    local bindir=$1; shift;

    local subtype="";
    if ("$bindir/qstat" --help 2> /dev/null|| true) | sed -ne '1p' | grep UGE > /dev/null; then
        subtype="UGE"
    elif ("$bindir/qstat" --help 2> /dev/null|| true) | sed -ne '1p' | grep OGS > /dev/null; then
        subtype="OGS"
    else
        subtype="SGE"
    fi

    echo "$subtype";
}

determine_pbs_subtype() {
    local bindir=$1; shift;

    local subtype="";
    if ("$bindir/qmgr" --version 2> /dev/null|| true) | grep -i PBSPro > /dev/null; then
        subtype="PBSPro"
    elif ("$bindir/qsub" --version 2> /dev/null|| true) | grep -i "version:" > /dev/null; then
        subtype="TORQUE"
    else
        subtype="PBS"
    fi

    echo "$subtype";
}

determine_lsf_subtype() {
    local bindir=$1; shift;

    local subtype="";
    if ("$bindir/lsid" -V 2> /dev/null|| true) | sed -ne '1p' | grep openlava > /dev/null; then
        subtype="OpenLava"
    elif ("$bindir/lsid" 2> /dev/null|| true) | sed -ne '1p' | grep openlava > /dev/null; then
        subtype="OpenLava"
    else
        subtype="LSF"
    fi

    echo "$subtype";
}

determine_slurm_subtype() {
    echo "Slurm"
}

# Convert the colon-separated specified path (e.g. PATH, etc) into its
# components and return them in an array (global array gret__get_patharr)
get_patharr() {
    local path=$1; shift;

    local -a patharr=();
    local dir;
    local pathleft=$path;
    while true; do
        if [[ $pathleft =~ ([^:]*):(.*) ]] ; then
            dir=${BASH_REMATCH[1]};
            pathleft=${BASH_REMATCH[2]};
        else
            dir=$pathleft;
            pathleft="";
        fi

        if [[ -z "$dir" ]] && [[ -z "$pathleft" ]] ; then
            break;
        fi
        if [[ -z "$dir" ]] ; then
            continue
        fi

        patharr+=( "$dir" );
    done

    gret__get_patharr=( "${patharr[@]}" );
}

# Find something that looks like an SGE bindir in the user's original PATH
find_sge_bindirs_frompath() {
    local def_bindir=$1; shift;

    local -a patharr_orig;
    get_patharr "$PATH_ORIG"; patharr_orig=( "${gret__get_patharr[@]}" )

    local -a subtypebindir_arr;

    local pathdir;
    local subtype;
    if [[ ! -z "$def_bindir" ]]; then
        for pathdir in "${patharr_orig[@]}"; do
            if [[ x"$pathdir" != x"$def_bindir" ]]; then
                continue;
            fi
            if verify_sge_bindir "$pathdir" ; then
                subtype=$(determine_sge_subtype "$pathdir");
                subtypebindir_arr+=( "$subtype,$pathdir" );
                break;
            fi
        done
    fi

    for pathdir in "${patharr_orig[@]}"; do
        if [[ x"$pathdir" == x"$def_bindir" ]]; then
            # The def_bindir already handled above
            continue;
        fi
        if verify_sge_bindir "$pathdir" ; then
            subtype=$(determine_sge_subtype "$pathdir");
            subtypebindir_arr+=( "$subtype,$pathdir" );
        fi
    done

    gret__find_sge_bindirs_frompath=( "${subtypebindir_arr[@]+${subtypebindir_arr[@]}}" );
}

# Find something that looks like a PBS bindir in the user's original PATH
find_pbs_bindirs_frompath() {
    local def_bindir=$1; shift;

    local -a patharr_orig;
    get_patharr "$PATH_ORIG"; patharr_orig=( "${gret__get_patharr[@]}" )

    local -a subtypebindir_arr;

    local pathdir;
    local subtype;
    if [[ ! -z "$def_bindir" ]]; then
        for pathdir in "${patharr_orig[@]}"; do
            if [[ x"$pathdir" != x"$def_bindir" ]]; then
                continue;
            fi
            if verify_pbs_bindir "$pathdir" ; then
                subtype=$(determine_pbs_subtype "$pathdir");
                subtypebindir_arr+=( "$subtype,$pathdir" );
                break;
            fi
        done
    fi

    for pathdir in "${patharr_orig[@]}"; do
        if [[ x"$pathdir" == x"$def_bindir" ]]; then
            # The def_bindir already handled above
            continue;
        fi
        if verify_pbs_bindir "$pathdir" ; then
            subtype=$(determine_pbs_subtype "$pathdir");
            subtypebindir_arr+=( "$subtype,$pathdir" );
        fi
    done

    gret__find_pbs_bindirs_frompath=( "${subtypebindir_arr[@]+${subtypebindir_arr[@]}}" );
}

# Find something that looks like an LSF bindir in the user's original PATH
find_lsf_bindirs_frompath() {
    local def_bindir=$1; shift;

    local -a patharr_orig;
    get_patharr "$PATH_ORIG"; patharr_orig=( "${gret__get_patharr[@]}" )

    local -a subtypebindir_arr;

    local pathdir;
    local subtype;
    if [[ ! -z "$def_bindir" ]]; then
        for pathdir in "${patharr_orig[@]}"; do
            if [[ x"$pathdir" != x"$def_bindir" ]]; then
                continue;
            fi
            if verify_lsf_bindir "$pathdir" ; then
                subtype=$(determine_lsf_subtype "$pathdir");
                subtypebindir_arr+=( "$subtype,$pathdir" );
                break;
            fi
        done
    fi

    for pathdir in "${patharr_orig[@]}"; do
        if [[ x"$pathdir" == x"$def_bindir" ]]; then
            # The def_bindir already handled above
            continue;
        fi
        if verify_lsf_bindir "$pathdir" ; then
            subtype=$(determine_lsf_subtype "$pathdir");
            subtypebindir_arr+=( "$subtype,$pathdir" );
        fi
    done

    gret__find_lsf_bindirs_frompath=( "${subtypebindir_arr[@]+${subtypebindir_arr[@]}}" );
}

# Find something that looks like a Slurm bindir in the user's original PATH
find_slurm_bindirs_frompath() {
    local def_bindir=$1; shift;

    local -a patharr_orig;
    get_patharr "$PATH_ORIG"; patharr_orig=( "${gret__get_patharr[@]}" )

    local -a subtypebindir_arr;

    local pathdir;
    local subtype;
    if [[ ! -z "$def_bindir" ]]; then
        for pathdir in "${patharr_orig[@]}"; do
            if [[ x"$pathdir" != x"$def_bindir" ]]; then
                continue;
            fi
            if verify_slurm_bindir "$pathdir" ; then
                subtype=$(determine_slurm_subtype "$pathdir");
                subtypebindir_arr+=( "$subtype,$pathdir" );
                break;
            fi
        done
    fi

    for pathdir in "${patharr_orig[@]}"; do
        if [[ x"$pathdir" == x"$def_bindir" ]]; then
            # The def_bindir already handled above
            continue;
        fi
        if verify_slurm_bindir "$pathdir" ; then
            subtype=$(determine_slurm_subtype "$pathdir");
            subtypebindir_arr+=( "$subtype,$pathdir" );
        fi
    done

    gret__find_slurm_bindirs_frompath=( "${subtypebindir_arr[@]+${subtypebindir_arr[@]}}" );
}

autodetect_jmstype() {
    local computecfg_listgroup="";
    if [[ ! -z "${1:-}" ]] ; then
        computecfg_listgroup=$1;
    fi

    if [[ -z "$computecfg_listgroup" ]] ; then
        if [[ ! -z "${gret__autodetect_jmstype_defval+set}" ]] ; then
            # This has already been called and jmstype has been autodetected
            # (with no specific computecfg defined), keep the same values
            return 0;
        fi
    fi


    local -a autodetect_jmstypesrc_arr=();

    # Determine if a jmstype was specified on the command line (in any of the
    # computecfg listgroups) or selected in a previous install.
    local defval_raw;
    local srcstr;
    local listgroups;
    local listgroup;
    if [[ ! -z "$computecfg_listgroup" ]] ; then
        listgroups="$computecfg_listgroup";
    else
        listgroups=$(get_computecfgs_allenabled);
    fi
    for listgroup in $listgroups; do
        defval_raw=$(get_cfg_val_raw "$listgroup" "jmstype")
        if isset_cfgval "$defval_raw" ; then
            srcstr=$(get_cfg_srcstr "$listgroup" "jmstype")
            autodetect_jmstypesrc_arr+=( "$defval_raw,$srcstr" );
        fi
    done

    # Find all the JMSs the seem to be in the user's original PATH and return
    # them in array in the order found (along with the path where found).

    local -a patharr_orig;
    if ! $opt_skip_jms_autodetect; then
        get_patharr "$PATH_ORIG"; patharr_orig=( "${gret__get_patharr[@]}" )
    else
        patharr_orig=( )
    fi


    local pathdir;
    local jms_subtype;
    for pathdir in "${patharr_orig[@]+${patharr_orig[@]}}"; do
        jms_subtype_guesses="";

        #
        # Detect SGE, PBS and derivatives (SGE, UGE, OGS, PBS, TORQUE, PBSPro)
        #
        # Note that pbs and sge cannot live in the same directory as they
        # both have qsub, qdel and qstat commands.
        jms_subtype="";
        if verify_pbs_bindir "$pathdir" ; then
            jms_subtype=$(determine_pbs_subtype "$pathdir")
        elif verify_sge_bindir "$pathdir" ; then
            jms_subtype=$(determine_sge_subtype "$pathdir")
        fi
        if [[ ! -z "$jms_subtype" ]] ; then
            autodetect_jmstypesrc_arr+=( "$jms_subtype,From PATH: $pathdir" );
        fi

        #
        # Detect LSF and derivatives (LSF, OpenLava)
        #
        jms_subtype="";
        if verify_lsf_bindir "$pathdir" ; then
            jms_subtype=$(determine_lsf_subtype "$pathdir")
        fi
        if [[ ! -z "$jms_subtype" ]] ; then
            autodetect_jmstypesrc_arr+=( "$jms_subtype,From PATH: $pathdir" );
        fi

        #
        # Detect Slurm and derivatives (Slurm)
        #
        jms_subtype="";
        if verify_slurm_bindir "$pathdir" ; then
            jms_subtype="Slurm"
        fi
        if [[ ! -z "$jms_subtype" ]] ; then
            autodetect_jmstypesrc_arr+=( "$jms_subtype,From PATH: $pathdir" );
        fi
    done

    if [[ -z "${autodetect_jmstypesrc_arr[@]+${autodetect_jmstypesrc_arr[@]}}" ]]; then
        autodetect_jmstypesrc_arr+=( "NONE,No JMS Detected" );
    fi

    # Set the return values (an array of all the auto-detected jms types, and
    # the default jms type)
    gret__autodetect_jmstype_arr=( "${autodetect_jmstypesrc_arr[@]}" );
    gret__autodetect_jmstype_defval="${autodetect_jmstypesrc_arr[0]}";
}

guess_jmsselect() {
    autodetect_jmstype;
    echo "${gret__autodetect_jmstype_defval%%,*}"
}

common_prompt_computecfg_single() {
    local computecfg_listgroup=$1; shift;
    local b_displayhelp=$1; shift;

    local name;
    name=$(get_cfg_val "$computecfg_listgroup" "name");
    local str="Configuring '$name' ($computecfg_listgroup)"

    echo
    echo "$str"
    echo "$str" | sed -e 's/./-/g'

    if $b_displayhelp; then
        # Determine nproc
        $opt_batch || cat <<-EOF

	NAME - Specifies a unique name for the compute configuration.

	EOF
    fi

    assign_cfg_val_interactive "$computecfg_listgroup" "name";

    # Don't propmpt for menuorder (mus use "reorder" menu option)
    #  assign_cfg_val_interactive "$computecfg_listgroup" "menuorder";


    if $b_displayhelp; then
        # Determine nproc
        $opt_batch || cat <<-EOF

	NPROC - Specifies the maximum number of slots each task may request
	        upon job submissions to a JMS cluster.  The suggested value
	        is based on the processor count of the SMRT Link head node,
	        but should be set as needed to optimize cluster usage.  One
	        processor core per slot is assumed.

	EOF
    fi

    assign_cfg_val_interactive "$computecfg_listgroup" "nproc";

    if $b_displayhelp; then
        # Determine maxchunks
        $opt_batch || cat <<-EOF

	MAXCHUNKS - Specify the maximum number of parallel chunks when
	            breaking up large dataset files.

	EOF
    fi

    assign_cfg_val_interactive "$computecfg_listgroup" "maxchunks";


    if $b_displayhelp; then
        # Determine cromwell_loglevel
        $opt_batch || cat <<-EOF

	Specify the Cromwell Log Level.

	EOF
    fi

    # if ! $opt_batch; then
    #     local selected_loglevel;
    #     local promptstr
    #     promptstr=$(get_cfg_promptstr "$computecfg_listgroup" "cromwell_loglevel");
    #
    #     echo "$promptstr"
    #     echo
    #     process_query "INFO"  "${g_cromwell_loglevel_list[@]}"
    #     selected_jmstype=$gret_val;
    # fi

    if $b_displayhelp; then
        # Determine cromwell_concurrent_job_limit
        $opt_batch || cat <<-EOF

	Specify the global 'concurrent_job_limit' for Cromwell.

	EOF
    fi

    assign_cfg_val_interactive "$computecfg_listgroup" "cromwell_concurrent_job_limit";

    common_prompt_jmsselect "$computecfg_listgroup" $b_displayhelp;
}

print_computecfg_usingstr() {
    local group=$1; shift;

    print_cfg_usingstr "$group" "name"
    print_cfg_usingstr "$group" "menuorder"
    print_cfg_usingstr "$group" "nproc"
    print_cfg_usingstr "$group" "maxchunks"
    print_cfg_usingstr "$group" "cromwell_loglevel"
    print_cfg_usingstr "$group" "cromwell_concurrent_job_limit"

    print_cfg_usingstr "$group" "jmstype"

    local jmstype;
    local jmsbasetype;
    jmstype=$(get_cfg_val "$group" "jmstype")
    jmsbasetype=$(get_jmsbasetype "$jmstype");

    if [[ x"$jmsbasetype" == x"NONE" ]] ; then
        :
    elif [[ x"$jmsbasetype" == x"SGE" ]] ; then
        print_cfg_usingstr "$group" "sge_sgeroot"
        print_cfg_usingstr "$group" "sge_sgecell"
        print_cfg_usingstr "$group" "sge_bindir"
        print_cfg_usingstr "$group" "sge_queue"
        print_cfg_usingstr "$group" "sge_pe"
        print_cfg_usingstr "$group" "sge_startargs"
        print_cfg_usingstr "$group" "sge_use_settings_file"
    elif [[ x"$jmsbasetype" == x"PBS" ]] ; then
        print_cfg_usingstr "$group" "pbs_bindir"
        print_cfg_usingstr "$group" "pbs_queue"
        print_cfg_usingstr "$group" "pbs_startargs"
    elif [[ x"$jmsbasetype" == x"LSF" ]] ; then
        print_cfg_usingstr "$group" "lsf_bindir"
        print_cfg_usingstr "$group" "lsf_queue"
        print_cfg_usingstr "$group" "lsf_startargs"
    elif [[ x"$jmsbasetype" == x"Slurm" ]] ; then
        print_cfg_usingstr "$group" "slurm_bindir"
        print_cfg_usingstr "$group" "slurm_partition"
        print_cfg_usingstr "$group" "slurm_startargs"
    elif [[ x"$jmsbasetype" == x"AWS" ]] ; then
        print_cfg_usingstr "$group" "aws_region"
        print_cfg_usingstr "$group" "aws_queue"
    elif [[ x"$jmsbasetype" == x"CustomJMS" ]] ; then
        print_cfg_usingstr "$group" "customjms_name"
        print_cfg_usingstr "$group" "customjms_bindir"
        print_cfg_usingstr "$group" "customjms_queue"
        print_cfg_usingstr "$group" "customjms_startargs"
    elif [[ x"$jmsbasetype" == x"OtherJMS" ]] ; then
        print_cfg_usingstr "$group" "otherjms_name"
        print_cfg_usingstr "$group" "otherjms_bindir"
        print_cfg_usingstr "$group" "otherjms_queue"
        print_cfg_usingstr "$group" "otherjms_startargs"
    else
        minterror "print_computecfg_usingstr(): Unrecognized jmsbasetype: $jmsbasetype"
    fi
}


common_prompt_jmsselect_core() {
    local computecfg_listgroup=$1; shift;
    local b_displayhelp=$1; shift;

    autodetect_jmstype "$computecfg_listgroup";

    local autodetect_jmstype_arr=( "${gret__autodetect_jmstype_arr[@]+${gret__autodetect_jmstype_arr[@]}}" );

    local jms_typesrc;

if ! $opt_batch; then
    echo "  Auto-detected the following Job Management Systems:"
    echo
    for jms_typesrc in "${autodetect_jmstype_arr[@]}"; do
        printf "      %-10s  (%s)\n" "${jms_typesrc%%,*}" "${jms_typesrc#*,}"
    done
    echo
fi

    if ! $opt_batch; then
        local -a choices

        local found_none_choice=false;
        local seen=","
        local jms_subtype;
        for jms_typesrc in "${autodetect_jmstype_arr[@]}"; do
            jms_subtype=${jms_typesrc%%,*}
            if [[ ! $seen =~ ,$jms_subtype, ]] ; then
                if [[ x"$jms_subtype" == x"NONE" ]] ; then
                    choices+=( "NONE@@None (Non-Distributed Mode)" );
                    found_none_choice=true;
                else
                    choices+=( "$jms_subtype" );
                fi
                seen="${seen}${jms_subtype},"
            fi
        done
        choices+=( "SelectJMS@@Other JMS" );
        if ! $found_none_choice; then
            choices+=( "NONE@@None (Non-Distributed Mode)" );
        fi

        # Query for the jmstype to use
        local selected_jmstype;
        process_query "1"  "${choices[@]}"
        selected_jmstype=$gret_val;

        # If the choice was "SelectJMS" then provide the full prompt of
        # JMS subtypes.
        if [[ x"$selected_jmstype" == x"SelectJMS" ]] ; then
            choices=( "${g_jmstype_select_options[@]}" );
            local selnum=1;
            if [[ ! -z "${autodetect_jmstype_arr[@]+${autodetect_jmstype_arr[@]}}" ]] ; then
                seldefault="${autodetect_jmstype_arr[0]}";
                seldefault=${seldefault%%,*}
                if [[ $seldefault =~ ^OtherJMS__ ]] ; then
                    seldefault="OtherJMS__*"
                elif [[ $seldefault =~ ^CustomJMS__ ]] ; then
                    seldefault="CustomJMS__*"
                fi
                selnum=$(get_selection_number "$seldefault" "${choices[@]}")
                if [[ -z "$selnum" ]]; then
                    # Could not find the specified jmstype, ignore it and set
                    # the first selection as the default.
                    selnum=1;
                fi
            fi

            # Query for the jmstype to use
            echo
            process_query "$selnum"  "${choices[@]}"
            selected_jmstype=$gret_val;
        fi

        # Set the value that was selected
        set_cfg_val "$computecfg_listgroup" "jmstype" "$selected_jmstype" "selected interactively" "false"
    fi

    # Always turn off usedefault for this one (we don't want the jms
    # selection to change based on the default computation probing the
    # system file system.
    local jmstype_val;
    local jmstype_srcstr;
    jmstype_val=$(get_cfg_val "$computecfg_listgroup" "jmstype")
    jmstype_srcstr=$(get_cfg_srcstr "$computecfg_listgroup" "jmstype")
    set_cfg_val "$computecfg_listgroup" "jmstype" "$jmstype_val" "$jmstype_srcstr" false;


    local jmstype;
    local jmsbasetype;
    jmstype=$(get_cfg_val "$computecfg_listgroup" "jmstype");
    jmsbasetype=$(get_jmsbasetype "$jmstype");

    if [[ x"$jmsbasetype" == x"NONE" ]]; then
        gret__jmsconfigured=true;
    elif [[ x"$jmsbasetype" == x"SGE" ]]; then
        # This is an SGE setup
        prompt_jms_sge "$computecfg_listgroup";
    elif [[ x"$jmsbasetype" == x"LSF" ]]; then
        # This is an LSF setup
        prompt_jms_lsf "$computecfg_listgroup";
    elif [[ x"$jmsbasetype" == x"PBS" ]]; then
        # This is an PBS setup
        prompt_jms_pbs "$computecfg_listgroup";
    elif [[ x"$jmsbasetype" == x"Slurm" ]]; then
        prompt_jms_slurm "$computecfg_listgroup";
    elif [[ x"$jmsbasetype" == x"AWS" ]]; then
        prompt_jms_aws "$computecfg_listgroup";
    elif [[ $jmsbasetype =~ ^OtherJMS__ ]]; then
        prompt_jms_otherjms "$computecfg_listgroup";
    elif [[ $jmsbasetype =~ ^CustomJMS__ ]]; then
        prompt_jms_customjms "$computecfg_listgroup";
    else
        echo
        echo "Auto configuration of '$jmstype' job management system is not supported."
        echo "Please manually configure after installer completes."
        echo;
        if $opt_batch; then
            ans="y"
        else
            ans=$(yesno_prompt "Continue the installation?")
        fi
        if [[ x"$ans" == x"n" ]] ; then
            abort;
        fi
    fi
}

common_prompt_jmsselect() {
    local computecfg_listgroup=$1; shift;
    local b_displayhelp=$1; shift;

    if $b_displayhelp; then
        $opt_batch || cat <<-EOF

	A Job Management System may be used to dispatch jobs to a distributed
	compute environment.  If no Job Management System is specified, the
	system will run in Non-Distributed Mode and all compute jobs will be
	run on locally on the install host.  Available Job Management Systems
	will be detected from the PATH environment variable, but may also
	be selected manually.

	EOF
    fi

    while true; do
        common_prompt_jmsselect_core "$computecfg_listgroup" "$b_displayhelp"
        if $gret__jmsconfigured; then
            break;
        fi
    done

    echo
    print_computecfg_usingstr "$computecfg_listgroup"
}


# -- apply settings subroutines
print_jmsenv_file() {
    local computecfg=$1; shift
    local jms_type=$1; shift
    local jms_basetype=$1; shift
    local sge_root=$1; shift
    local sge_cell=$1; shift
    local jms_bindir=$1; shift
    local jms_queue=$1; shift;
    local sge_pe=$1; shift;
    local jms_startargs=$1; shift;
    local b_sge_use_settings_file=$1; shift;
    local jms_startcmd=$1; shift;
    local jms_stopcmd=$1; shift;

    local jmsname_uc;
    jmsname_uc=$(echo "$jms_type" | tr "[[:lower:]]" "[[:upper:]]")

    cat <<-EOF
	# This file was automatically generated -- DO NOT MODIFY!
	#
	# This file is generated automatically from information provided in the
	# SMRT Link install, upgrade or reconfigure process.  To reconfigure, run:
	#
	#     \$SMRT_ROOT/admin/bin/smrt_reconfig
	#
	# To make site specific changes to the job management system interface or
	# to override these settings, modify this file:
	#
	#     \$SMRT_ROOT/userdata/user_jmsenv/user.jmsenv.ish
	#
	EOF

    echo "JMS_TYPE=\"$jms_type\""
    echo
    echo "# Setup file for '$jms_type' job management system."
    if [[ x"$jms_basetype" == x"SGE" ]]; then
        if $b_sge_use_settings_file ; then
            echo "JMSCONFIG_${jmsname_uc}_ROOT=\"$sge_root\";"
            echo "JMSCONFIG_${jmsname_uc}_CELL=\"$sge_cell\";"
            echo "JMSCONFIG_${jmsname_uc}_BINDIR=\"\";"

            echo "SGE_ROOT=\$JMSCONFIG_${jmsname_uc}_ROOT";
            echo "SGE_CELL=\$JMSCONFIG_${jmsname_uc}_CELL";
            echo "SGE_BINDIR=\$JMSCONFIG_${jmsname_uc}_BINDIR"
            echo
            echo "# Load the settings.sh file (turning off nounset to ignore errors)"
            echo "nounset_save=false; [[ \$- == *u* ]] && nounset_save=true;"
            echo "set +o nounset"
            echo ". \"\${JMSCONFIG_${jmsname_uc}_ROOT}/\${JMSCONFIG_${jmsname_uc}_CELL}/common/settings.sh\""
            echo "! \$nounset_save && set +o nounset"
            echo "  \$nounset_save && set -o nounset"
            echo
        else
            echo "JMSCONFIG_${jmsname_uc}_ROOT=\"$sge_root\";"
            echo "JMSCONFIG_${jmsname_uc}_CELL=\"$sge_cell\";"
            echo "export SGE_ROOT=\$JMSCONFIG_${jmsname_uc}_ROOT;"
            echo "export SGE_CELL=\$JMSCONFIG_${jmsname_uc}_CELL;"

            echo "JMSCONFIG_${jmsname_uc}_BINDIR=\"$jms_bindir\";"
            echo "if [[ ! -z \"\$JMSCONFIG_${jmsname_uc}_BINDIR\" ]]; then"
            echo "    PATH=\"\$JMSCONFIG_${jmsname_uc}_BINDIR:\$PATH\";"
            echo "fi"
        fi
        echo "JMSCONFIG_${jmsname_uc}_QUEUE=\"$jms_queue\";";
        echo "JMSCONFIG_${jmsname_uc}_PE=\"$sge_pe\";";
        # Translate all single quotes to double quotes, just in case
        echo "JMSCONFIG_${jmsname_uc}_START_ARGS='${jms_startargs//\'/\"}';";
        echo

    elif [[ x"$jms_basetype" == x"LSF" ]]; then
        echo "JMSCONFIG_${jmsname_uc}_BINDIR=\"$jms_bindir\";"
        echo "if [[ ! -z \"\$JMSCONFIG_${jmsname_uc}_BINDIR\" ]]; then"
        echo "    PATH=\"\$JMSCONFIG_${jmsname_uc}_BINDIR:\$PATH\";"
        echo "fi"
        echo
        echo "JMSCONFIG_${jmsname_uc}_QUEUE=\"$jms_queue\";";
        # Translate all single quotes to double quotes, just in case
        echo "JMSCONFIG_${jmsname_uc}_START_ARGS='${jms_startargs//\'/\"}';";
        echo
    elif [[ x"$jms_basetype" == x"PBS" ]]; then
        echo "JMSCONFIG_${jmsname_uc}_BINDIR=\"$jms_bindir\";"
        echo "if [[ ! -z \"\$JMSCONFIG_${jmsname_uc}_BINDIR\" ]]; then"
        echo "    PATH=\"\$JMSCONFIG_${jmsname_uc}_BINDIR:$PATH\";"
        echo "fi"
        echo
        echo "JMSCONFIG_${jmsname_uc}_QUEUE=\"$jms_queue\";";
        # Translate all single quotes to double quotes, just in case
        echo "JMSCONFIG_${jmsname_uc}_START_ARGS='${jms_startargs//\'/\"}';";
        echo
    elif [[ x"$jms_basetype" == x"Slurm" ]]; then
        echo "JMSCONFIG_${jmsname_uc}_BINDIR=\"$jms_bindir\";"
        echo "if [[ ! -z \"\$JMSCONFIG_${jmsname_uc}_BINDIR\" ]]; then"
        echo "    PATH=\"\$JMSCONFIG_${jmsname_uc}_BINDIR:\$PATH\";"
        echo "fi"
        echo
        echo "JMSCONFIG_${jmsname_uc}_PARTITION=\"$jms_queue\";";
        # Translate all single quotes to double quotes, just in case
        echo "JMSCONFIG_${jmsname_uc}_START_ARGS='${jms_startargs//\'/\"}';";
        echo
    elif [[ $jms_basetype =~ ^(OtherJMS|CustomJMS)__(.*)$ ]]; then
        echo "JMSCONFIG_${jmsname_uc}_BINDIR=\"$jms_bindir\";"
        echo "if [[ ! -z \"\$JMSCONFIG_${jmsname_uc}_BINDIR\" ]]; then"
        echo "    PATH=\"\$JMSCONFIG_${jmsname_uc}_BINDIR:\$PATH\";"
        echo "fi"
        echo
        echo "JMSCONFIG_${jmsname_uc}_QUEUE=\"$jms_queue\";";
        # Translate all single quotes to double quotes, just in case
        echo "JMSCONFIG_${jmsname_uc}_START_ARGS='${jms_startargs//\'/\"}';";
    else
        merror "print_jmsenv_file(): Unrecognized jms_basetype: $jms_basetype"
    fi

    if [[ ! -z "$jms_startcmd" ]] ; then
        echo "START_CMD=\"$jms_startcmd\";";
    fi
    if [[ ! -z "$jms_stopcmd" ]] ; then
        echo "STOP_CMD=\"$jms_stopcmd\";";
    fi

    echo
    echo "# Source the $g_user_jmsenv_filename file, if available"
    echo 'USER_JMSENV_ISH=$(dirname "${BASH_SOURCE[0]}")/'"$g_user_jmsenv_file_relgeneratedconfigdir"
    echo 'if [[ -r "$USER_JMSENV_ISH" ]]; then'
    echo '    . "$USER_JMSENV_ISH"'
    echo 'fi'

    local computecfg_jmsenv_file="user.${computecfg}.jmsenv.ish"
    echo
    echo "# Source the $computecfg_jmsenv_file file, if available"
    echo 'USER_COMPUTECFG_JMSENV_ISH=$(dirname "${BASH_SOURCE[0]}")/'"$g_user_jmsenv_dir_relgeneratedconfigdir/$computecfg_jmsenv_file"
    echo 'if [[ -r "$USER_COMPUTECFG_JMSENV_ISH" ]]; then'
    echo '    . "$USER_COMPUTECFG_JMSENV_ISH"'
    echo 'fi'

}

get_jmsbasetype() {
    local jmstype=$1; shift;

    case "$jmstype" in
        "SGE")             ret="SGE";;
        "OGS")             ret="SGE";;
        "UGE" )            ret="SGE";;

        "LSF")             ret="LSF";;
        "OpenLava")        ret="LSF";;

        "PBS")             ret="PBS";;
        "TORQUE")          ret="PBS";;
        "PBSPro")          ret="PBS";;

        "Slurm")           ret="Slurm";;

        "AWS")             ret="AWS";;

        "OtherJMS__"*)     ret=$jmstype;;
        "CustomJMS__"*)    ret=$jmstype;;

        "NONE")            ret="NONE";;

        *) merror "get_jmsbasetype(): Unrecognized jmsype: $jmstype";;
    esac

    echo "$ret"
}


get_jmsdoc__title() {
    cat<<EOF
#                        Site-specific JMS Settings
#                        ==========================
#
EOF
}

get_jmsdoc__overview() {
    cat<<EOF
# Overview
# --------
# This file is used to make site-specific modifications to the way SMRT Link
# invokes the Job Management System (JMS) to distribute jobs.
#
# The settings in this file will override variables and/or code in the bash
# script used to start and stop all SMRT Link jobs.   The script is located
# under the SMRT Link install root (\$SMRT_ROOT) at:
#
#       \$SMRT_ROOT/admin/bin/runjmscmd
#
# For reference, the runjmscmd script is invoked by SMRT Link through the
# start and stop templates located at:
#
#       \$SMRT_ROOT/userdata/generated/config/cromwell.json
#
# where these values are substituted by SMRT Link on a per-job basis:
#
#       JOB_ID
#       STDOUT_FILE
#       STDERR_FILE
#       NPROC
#       CMD
#
# But note that these template files are automatically generated during the
# SMRT Link install, upgrade or reconfigure process and should NOT be modified
# (any changes will be lost during the next upgrade or reconfigure).  All site
# specific modifications should be handled below in this file.
#
#
#
EOF
}

get_jmsdoc__testing() {
    cat<<EOF
# Debug and Testing
# -----------------
# The underlying 'runjmscmd' script to start and stop SMRT Link jobs can be
# used directly to debug and test the interface to the Job Management
# System using the --test or --noexec options.
#
# The following commands can be used to print out examples of the JMS commands
# equivalent to those used to start and stop SMRT Link jobs (including the
# changes in his file below):
#
#       \$SMRT_ROOT/admin/bin/runjmscmd --start --test
#       \$SMRT_ROOT/admin/bin/runjmscmd --stop --test
#
# Those commands use hardwired or canned data for the JOB_ID, STDOUT_FILE,
# STDERR_FILE, NPROC and CMD information that is normally provided by SMRT
# Link.
#
# The 'runjmscmd' script can also be used to test out the submission of a job
# to the JMS cluster.  For example, something similar to this command can be
# used to submit a simple test job:
#
#      \$SMRT_ROOT/admin/bin/runjmscmd --start --jobname testjob.hostname --stdoutfile /tmp/testjob.stdout --stderrfile /tmp/testjob.stderr --nproc 1 --cmd /bin/hostname
#
# The same command with the --noexec or -n option added will print out the
# jobs submission command line without actually executing it:
#
#      \$SMRT_ROOT/admin/bin/runjmscmd --start --jobname testjob.hostname --stdoutfile /tmp/testjob.stdout --stderrfile /tmp/testjob.stderr --nproc 1 --cmd /bin/hostname --noexec
#
#
#
EOF
}

get_jmsdoc__currentjms__none() {
    local jmssubtype=$1; shift;
    local jmsbasetype=$1; shift;

    local jmssubtype_uc;
    jmssubtype_uc=$(echo "$jmssubtype" | tr "[[:lower:]]" "[[:upper:]]");

    cat<<EOF
# ############################################################################
#
#                THIS FILE IS NOT IN USE AND WILL BE IGNORED
#                  (SMRT Link is in non-distributed mode)
#
# ############################################################################
#
# Current Job Management System
# -----------------------------
# The current Job Management System configured in the latest install,
# upgrade or reconfigure is:
#
#       ${jmssubtype}
#
# SMRT Link is configured in non-distributed mode.   No Job Management System
# will be used, and all information in this file will be ignored.
#
# To configure a job management system, run this command and answer # 'N' to
# the "Keep all existing settings [Y/n]" prompt:
#
#       \$SMRT_ROOT/admin/bin/smrt_reconfig
#
EOF
}


get_jmsdoc__currentjms() {
    local jmssubtype=$1; shift;
    local jmsbasetype=$1; shift;

    local jmssubtype_uc;
    jmssubtype_uc=$(echo "$jmssubtype" | tr "[[:lower:]]" "[[:upper:]]");

    cat<<EOF
# Current Job Management System
# -----------------------------
# The current Job Management System configured in the latest install,
# upgrade or reconfigure is:
#
#       ${jmssubtype}
#
EOF

    if [[ x"$jmssubtype" != x"$jmsbasetype" ]] ; then
        cat<<EOF
# Note that ${jmssubtype} is a compatible alternative to ${jmsbasetype}.  Any of the
# ${jmssubtype_uc}_* variables below may be replace with the equivalent ${jmsbasetype}_*
# variables.
#
EOF
    fi

    cat<<EOF
# To configure a different job management system, run this command and answer
# 'N' to the "Keep all existing settings [Y/n]" prompt:
#
#       \$SMRT_ROOT/admin/bin/smrt_reconfig
#
#
#
EOF
}


get_jmsdoc__varoverride__sge() {
    local jmssubtype=$1; shift;
    local jmsbasetype=$1; shift;
    local startcmd=$1; shift;
    local stopcmd=$1; shift;

    local jmssubtype_uc;
    jmssubtype_uc=$(echo "$jmssubtype" | tr "[[:lower:]]" "[[:upper:]]");

    cat<<EOF
# Variable Overrides
# ------------------
# The default JMS start and stop functions allow modification of the
# resulting commands by setting variables below.  A common case is to
# add additional options to the command (perhaps to request specific
# resources, or add some site-specific options).  This can be done
# by setting the ${jmssubtype_uc}_START_ARGS variable.  For example:
#
#   ${jmssubtype_uc}_START_ARGS='--opt "site-specific args"'
#
# Although straightforward assignments are the most common, any legal bash
# syntax may be used below to modify the variables that affect the
# resulting start and stop commands.
#
# For the current Job Management System, the following variables are
# supported:
#
#       ${jmssubtype_uc}_JOB_NAME
#          argument to ${startcmd} -N option
#          if empty or unset, -N option will not appear on ${startcmd} command line
#          default: passed from SMRT Link
#       ${jmssubtype_uc}_STDOUT_FILE
#          argument to ${startcmd} -o option
#          if empty or unset, -o option will not appear on ${startcmd} command line
#          default: passed from SMRT Link
#       ${jmssubtype_uc}_STDERR_FILE
#          argument to ${startcmd} -e option
#          if empty or unset, -e option will not appear on ${startcmd} command line
#          default: passed from SMRT Link
#       ${jmssubtype_uc}_NPROC
#          argument to ${startcmd} -pe <pe> <nproc> option
#          if empty or unset, -pe option will not appear on ${startcmd} command line
#          default: passed from SMRT Link
#       ${jmssubtype_uc}_JOB_CMD
#          command to run
#          default: passed from SMRT Link
#
#       ${jmssubtype_uc}_START_CMD
#          command to invoke to start a job
#          default: ${startcmd}
#       ${jmssubtype_uc}_STOP_CMD
#          command to invoke to stop/kill a job
#          default: ${stopcmd}
#       ${jmssubtype_uc}_START_ARGS
#          extra arguments for ${startcmd} (start) command line
#          default: unset
#       ${jmssubtype_uc}_STOP_ARGS
#          extra arguments for ${stopcmd} (stop) command line
#          default: unset
#
#       ${jmssubtype_uc}_QUEUE
#          argument to ${startcmd} -q option
#          if empty or unset, -q option will not appear on ${startcmd} command line
#          default: set via smrtlink install configuration
#       ${jmssubtype_uc}_PE
#          argument to ${startcmd} -pe <pe> <nproc> option
#          if empty or unset, -pe option will not appear on ${startcmd} command line
#          default: set via smrtlink install configuration
#       ${jmssubtype_uc}_SHELL_ARG
#          argument to ${startcmd} -S option
#          if empty or unset, -S option will not appear on ${startcmd} command line
#          default: /bin/bash
#       ${jmssubtype_uc}_SYNC_ARG
#          argument to -sync option
#          if empty or unset, -sync option will not appear on ${startcmd} command line
#          if set to 'y', the submit will be synchronous, wait for completion
#          default: <none>
#       ${jmssubtype_uc}_EXPORTENV_ARG
#          option to pass environment to jobs
#          if empty or unset, -V option will not appear on ${startcmd} command line
#          default: -V
#
#
#
EOF
}


get_jmsdoc__varoverride__pbs() {
    local jmssubtype=$1; shift;
    local jmsbasetype=$1; shift;
    local startcmd=$1; shift;
    local stopcmd=$1; shift;

    local jmssubtype_uc;
    jmssubtype_uc=$(echo "$jmssubtype" | tr "[[:lower:]]" "[[:upper:]]");


    cat<<EOF
# Variable Overrides
# ------------------
# The default JMS start and stop functions allow modification of the
# resulting commands by setting variables below.  A common case is to
# add additional options to the command (perhaps to request specific
# resources, or add some site-specific options).  This can be done
# by setting the ${jmssubtype_uc}_START_ARGS variable.  For example:
#
#   ${jmssubtype_uc}_START_ARGS='--opt "site-specific args"'
#
# Although straightforward assignments are the most common, any legal bash
# syntax may be used below to modify the variables that affect the
# resulting start and stop commands.
#
# For the current Job Management System, the following variables are
# supported:
#
#       ${jmssubtype_uc}_JOB_NAME
#          argument to ${startcmd} -N option
#          if empty or unset, -N option will not appear on ${startcmd} command line
#          default: passed from SMRT Link
#       ${jmssubtype_uc}_STDOUT_FILE
#          argument to ${startcmd} -o option
#          if empty or unset, -o option will not appear on ${startcmd} command line
#          default: passed from SMRT Link
#       ${jmssubtype_uc}_STDERR_FILE
#          argument to ${startcmd} -e option
#          if empty or unset, -e option will not appear on ${startcmd} command line
#          default: passed from SMRT Link
#       ${jmssubtype_uc}_NPROC
#          argument to ${startcmd} '-l "nodes=1:ppn=<nproc>"' option
#          if empty or unset, -l option will not appear on ${startcmd} command line
#          default: passed from SMRT Link
#       ${jmssubtype_uc}_JOB_CMD
#          command to run
#          default: passed from SMRT Link
#
#       ${jmssubtype_uc}_START_CMD
#          command to invoke to start a job
#          set with path to 'qsw' to force a synchronous submit
#          default: ${startcmd}
#       ${jmssubtype_uc}_STOP_CMD
#          command to invoke to stop/kill a job
#          default: ${stopcmd}
#       ${jmssubtype_uc}_START_ARGS
#          extra arguments for ${startcmd} (start) command line
#          default: unset
#       ${jmssubtype_uc}_STOP_ARGS
#          extra arguments for ${stopcmd} (stop) command line
#          default: unset
#
#       ${jmssubtype_uc}_QUEUE
#          argument to ${startcmd} -q option
#          if empty or unset, -q option will not appear on ${startcmd} command line
#          default: set via smrtlink install configuration
#       ${jmssubtype_uc}_SHELL_ARG
#          argument to ${startcmd} -S option
#          if empty or unset, -S option will not appear on ${startcmd} command line
#          default: /bin/bash
#       ${jmssubtype_uc}_EXPORTENV_ARG
#          option to pass environment to jobs
#          if empty or unset, -V option will not appear on ${startcmd} command line
#          default: -V
#       ${jmssubtype_uc}_QSW_PBS_ARG
#          option to flag as PBS command to start wrapper
#          if empty or unset, -PBS option will not appear on ${startcmd} command line
#          requires ${jmssubtype_uc}_START_CMD to be set to 'qsw' to take effect
#          default: -PBS
#
#
#
EOF
}


get_jmsdoc__varoverride__lsf() {
    local jmssubtype=$1; shift;
    local jmsbasetype=$1; shift;
    local startcmd=$1; shift;
    local stopcmd=$1; shift;

    local jmssubtype_uc;
    jmssubtype_uc=$(echo "$jmssubtype" | tr "[[:lower:]]" "[[:upper:]]");


    cat<<EOF
# Variable Overrides
# ------------------
# The default JMS start and stop functions allow modification of the
# resulting commands by setting variables below.  A common case is to
# add additional options to the command (perhaps to request specific
# resources, or add some site-specific options).  This can be done
# by setting the ${jmssubtype_uc}_START_ARGS variable.  For example:
#
#   ${jmssubtype_uc}_START_ARGS='--opt "site-specific args"'
#
# Although straightforward assignments are the most common, any legal bash
# syntax may be used below to modify the variables that affect the
# resulting start and stop commands.
#
# For the current Job Management System, the following variables are
# supported:
#
#       ${jmssubtype_uc}_JOB_NAME
#          argument to ${startcmd} -J option
#          if empty or unset, -J option will not appear on ${startcmd} command line
#          default: passed from SMRT Link
#       ${jmssubtype_uc}_STDOUT_FILE
#          argument to ${startcmd} -o option
#          if empty or unset, -o option will not appear on ${startcmd} command line
#          default: passed from SMRT Link
#       ${jmssubtype_uc}_STDERR_FILE
#          argument to ${startcmd} -e option
#          if empty or unset, -e option will not appear on ${startcmd} command line
#          default: passed from SMRT Link
#       ${jmssubtype_uc}_NPROC
#          argument to ${startcmd} -n option
#          if empty or unset, -n option will not appear on ${startcmd} command line
#          default: passed from SMRT Link
#       ${jmssubtype_uc}_JOB_CMD
#          command to run
#          default: passed from SMRT Link
#
#       ${jmssubtype_uc}_START_CMD
#          command to invoke to start a job
#          default: ${startcmd}
#       ${jmssubtype_uc}_STOP_CMD
#          command to invoke to stop/kill a job
#          default: ${stopcmd}
#       ${jmssubtype_uc}_START_ARGS
#          extra arguments for ${startcmd} (start) command line
#          default: unset
#       ${jmssubtype_uc}_STOP_ARGS
#          extra arguments for ${stopcmd} (stop) command line
#          default: unset
#
#       ${jmssubtype_uc}_QUEUE
#          argument to ${startcmd} -q option
#          if empty or unset, -q option will not appear on ${startcmd} command line
#          default: set via smrtlink install configuration
#       ${jmssubtype_uc}_WAIT_ARG
#          option to wait for job synchronously for completion
#          if empty or unset, no wait options will appear on ${startcmd} command line
#          set to '-K' to wait for job to complete synchronously
#          default: <none>
#       ${jmssubtype_uc}_RESOURCE_ARG
#          argument to ${startcmd} -R option
#          if empty or unset, -R option will not appear on ${startcmd} command line
#          default: <none>
#
#
#
EOF
}

get_jmsdoc__varoverride__slurm() {
    local jmssubtype=$1; shift;
    local jmsbasetype=$1; shift;
    local startcmd=$1; shift;
    local stopcmd=$1; shift;

    local jmssubtype_uc;
    jmssubtype_uc=$(echo "$jmssubtype" | tr "[[:lower:]]" "[[:upper:]]");


    cat<<EOF
# Variable Overrides
# ------------------
# The default JMS start and stop functions allow modification of the
# resulting commands by setting variables below.  A common case is to
# add additional options to the command (perhaps to request specific
# resources, or add some site-specific options).  This can be done
# by setting the ${jmssubtype_uc}_START_ARGS variable.  For example:
#
#   ${jmssubtype_uc}_START_ARGS='--opt "site-specific args"'
#
# Although straightforward assignments are the most common, any legal bash
# syntax may be used below to modify the variables that affect the
# resulting start and stop commands.
#
# For the current Job Management System, the following variables are
# supported (for submitting jobs with 'sbatch'):
#
#       ${jmssubtype_uc}_JOB_NAME
#          argument to ${startcmd} --job-name option
#          if empty or unset, --job-name option will not appear on ${startcmd}
#            command line
#          default: passed from SMRT Link
#       ${jmssubtype_uc}_STDOUT_FILE
#          argument to ${startcmd} -o option
#          if empty or unset, -o option will not appear on ${startcmd} command line
#          default: passed from SMRT Link
#       ${jmssubtype_uc}_STDERR_FILE
#          argument to ${startcmd} -e option
#          if empty or unset, -e option will not appear on ${startcmd} command line
#          default: passed from SMRT Link
#       ${jmssubtype_uc}_NPROC
#          argument to ${startcmd} --cpus-per-task option
#          if empty or unset, --cpus-per-task option will not appear on the
#             ${startcmd} command lines
#          default: passed from SMRT Link
#       ${jmssubtype_uc}_JOB_CMD
#          command to run
#          default: passed from SMRT Link
#
#       ${jmssubtype_uc}_START_CMD
#          command to invoke to start a job
#          default: ${startcmd}
#       ${jmssubtype_uc}_STOP_CMD
#          command to invoke to stop/kill a job
#          default: ${stopcmd}
#       ${jmssubtype_uc}_START_ARGS
#          extra arguments for ${startcmd} (start) command line
#          default: unset
#       ${jmssubtype_uc}_STOP_ARGS
#          extra arguments for ${stopcmd} (stop) command line
#          default: unset
#       ${jmssubtype_uc}_WAIT_ARG
#          set to '--wait' for a synchronous submit, wait until completion
#          default: <none>
#
#       ${jmssubtype_uc}_NODES
#          argument to ${startcmd} --nodes option
#          if empty or unset, --nodes option will not appear on ${startcmd}
#            command line
#          default: set via smrtlink install configuration
#       ${jmssubtype_uc}_NTASKS
#          argument to ${startcmd} --ntasks option
#          if empty or unset, --ntasks option will not appear on ${startcmd}
#            command line
#          default: set via smrtlink install configuration
#       ${jmssubtype_uc}_PARTITION
#          argument to ${startcmd} --partition option
#          if empty or unset, --partition option will not appear on the
#             ${startcmd} command lines
#          default: set via smrtlink install configuration
#       ${jmssubtype_uc}_SHELL_ARG
#          invoke job with --wrap='\$${jmssubtype_uc}_SHELL_ARG -c "command"'
#            using ${jmssubtype_uc}_SHELL_ARG as the shell
#          if empty or unset, the --wrap="command" option will be used
#          default: unset
#
# For using 'salloc' and 'srun' to submit jobs the following should be set
# this file:
#
#       ${jmssubtype_uc}_USE_SALLOCSRUN=true
#
# and the following variables ar supported when ${jmssubtype_uc}_USE_SALLOCSRUN
# is set:
#
#       ${jmssubtype_uc}_PRESTART_CMD
#          precommand to invoke to start a job, e.g. 'salloc'
#          default: salloc
#       ${jmssubtype_uc}_START_CMD
#          command to invoke to start a job, e.g. 'srun'
#          default: srun
#       ${jmssubtype_uc}_PRESTART_ARGS
#          extra arguments for salloc (prestart) command line
#          default: unset
#       ${jmssubtype_uc}_START_ARGS
#          extra arguments for srun (start) command line
#          default: unset
#
#       ${jmssubtype_uc}_SHELL_ARG
#          invoke job with '\$${jmssubtype_uc}_SHELL_ARG -c "command"'
#            using ${jmssubtype_uc}_SHELL_ARG as the shell
#          must not be empty or unset
#          default: /bin/bash
#
#
#
EOF
}


get_jmsdoc__varoverride() {
    local jmssubtype=$1; shift;
    local jmsbasetype=$1; shift;
    local startcmd=$1; shift;
    local stopcmd=$1; shift;

    if [[ x"$jmsbasetype" == x"SGE" ]] ; then
        get_jmsdoc__varoverride__sge "$jmssubtype" "$jmsbasetype" "$startcmd" "$stopcmd"
    elif [[ x"$jmsbasetype" == x"PBS" ]] ; then
        get_jmsdoc__varoverride__pbs "$jmssubtype" "$jmsbasetype" "$startcmd" "$stopcmd"
    elif [[ x"$jmsbasetype" == x"LSF" ]] ; then
        get_jmsdoc__varoverride__lsf "$jmssubtype" "$jmsbasetype" "$startcmd" "$stopcmd"
    elif [[ x"$jmsbasetype" == x"Slurm" ]] ; then
        get_jmsdoc__varoverride__slurm "$jmssubtype" "$jmsbasetype" "$startcmd" "$stopcmd"
    fi

}

get_jmsdoc__funcoverride__othercustom() {
    local jmssubtype=$1; shift;

    local othercustomname="";
    if [[ $jmssubtype =~ ^(OtherJMS|CustomJMS)__(.*) ]] ; then
        othercustomname=${BASH_REMATCH[2]}
    fi

    cat<<EOF
# Function Overrides
# ------------------
# In order to use an unsupported or custom Job Management System,
# the start and stop functions must be specified.
#
# The following functions definitions must be defined below:
#
#       ${othercustomname}_start() {
#           # Add JMS start code here
#           . . .
#       }
#
#       ${othercustomname}_stop() {
#           # Add JMS stop code here
#           . . .
#       }
#
# Those functions should call the underlying JMS commands to submit and
# kill jobs.
#
# The following variables will contain the values passed in from SMRT Link
# and can be used in the construction of the start and stop commands:
#
#       JOBVAR_JOB_NAME
#       JOBVAR_STDOUT_FILE
#       JOBVAR_STDERR_FILE
#       JOBVAR_NPROC
#       JOBVAR_JOB_CMD
#
#
#
EOF
}

get_jmsdoc__funcoverride() {
    local jmssubtype=$1; shift;


    cat<<EOF
# Function Overrides
# ------------------
# To completely override the default start and stop implementations for
# ${jmssubtype}, add the following function below.
#
#       ${jmssubtype}_start() {
#           # Add JMS start code here
#           . . .
#       }
#
#       ${jmssubtype}_stop() {
#           # Add JMS stop code here
#           . . .
#       }
#
# Those functions should call the underlying ${jmssubtype} commands to submit and
# kill jobs.
#
# The following variables will contain the values passed in from SMRT Link
# and can be used in the construction of the start and stop commands:
#
#       JOBVAR_JOB_NAME
#       JOBVAR_STDOUT_FILE
#       JOBVAR_STDERR_FILE
#       JOBVAR_NPROC
#       JOBVAR_JOB_CMD
#
#
#
EOF
}

get_jmsdoc() {
    local jmstypes=$1; shift;

    local jmssubtype;
    local jmsbasetype;
    for jmssubtype in $jmstypes; do
        jmsbasetype=$(get_jmsbasetype "$jmssubtype")

        local startcmd="";
        local stopcmd="";

        if [[ "$jmsbasetype" == "SGE" ]] ; then
            startcmd="qsub"
            stopcmd="qdel"
        elif [[ "$jmsbasetype" == "PBS" ]] ; then
            startcmd="qsub"
            stopcmd="qdel"
        elif [[ "$jmsbasetype" == "LSF" ]] ; then
            startcmd="bsub"
            stopcmd="bkill"
        elif [[ "$jmsbasetype" == "Slurm" ]] ; then
            startcmd="sbatch"
            stopcmd="scancel"
        fi

        get_jmsdoc__title

        if [[ x"$jmsbasetype" == x"NONE" ]] ; then
            get_jmsdoc__currentjms__none "$jmssubtype" "$jmsbasetype"
        elif [[ $jmsbasetype =~ ^(OtherJMS|CustomJMS)__(.*) ]] ; then
            get_jmsdoc__overview
            get_jmsdoc__testing
            get_jmsdoc__currentjms "$jmssubtype" "$jmsbasetype"
            get_jmsdoc__funcoverride__othercustom "$jmssubtype"
        else
            get_jmsdoc__overview
            get_jmsdoc__testing
            get_jmsdoc__currentjms "$jmssubtype" "$jmsbasetype"
            get_jmsdoc__varoverride "$jmssubtype" "$jmsbasetype" "$startcmd" "$stopcmd"
            get_jmsdoc__funcoverride "$jmssubtype"
        fi

    done

}

create_user_jmsenv() {
    local jmstypes=$1; shift;

    # Create or update the user.jmsenv.ish file.
    # By default, we will provide some instructive comments at the top of the
    # file, but leave the rest empty.  If the file already exists, we will
    # update the only comments (if they've changed), otherwise leave the rest
    # of the user code in tact.
    local user_jmsenv_helptext;
    local donotmodify_str="# --- Do not modify generated text above this line"
    # Make sure to ignore error status because the -d '' will not match
    # anything, so read returns non-zero since it thinks it hit the end of
    # the file without reading anything.
    user_jmsenv_helptext=$(get_jmsdoc "$jmstypes");

    local nl=$'\n'
    user_jmsenv_helptext="${user_jmsenv_helptext}${nl}$donotmodify_str, user specific code below --"

    # Create the directory if needed.
    mkdir -p "$(dirname "$g_user_jmsenv_file")"

    local existing_helptext;
    if [[ ! -e "$g_user_jmsenv_file" ]] ; then
        echo "    Creating user.jmsenv.ish file..."
        echo "$user_jmsenv_helptext" > "$g_user_jmsenv_file"
    else
        echo "    Updating help text in user.jmsenv.ish file..."
        local replace_helptext=false;
        # File exists, see if it has our auto generated text on top
        if grep --quiet -E "${donotmodify_str}" "$g_user_jmsenv_file"; then
            existing_helptext=$(sed -n -e "1,/^${donotmodify_str}/p" "$g_user_jmsenv_file")
            if [[ -z "$existing_helptext" ]] ; then
                # We didn't find any help text, leave it that way, so do
                # not modify the file.
                echo "      WARNING! No existing help text found in user.jmsenv.ish!"
                replace_helptext=false;
            elif echo "$existing_helptext" |  grep -E "^[^#]" > /dev/null; then
                # We found something other than a comment in the existing
                # help text, do not modify the file.
                echo "      WARNING! Found non-comments in existing user.jmsenv.ish help text!"
                replace_helptext=false;
            else
                # We have existing help text that is all comments, see if it
                # is different than our current text.  If so, replace it.
                if [[ x"$user_jmsenv_helptext" != x"$existing_helptext" ]] ; then
                    replace_helptext=true;
                else
                    echo "      Help text unchanged in user.jmsenv.ish"
                fi
            fi
        else
            echo "      WARNING! No auto-generated text found in user.jmsenv.ish!"
        fi
        if $replace_helptext; then
            # We need to replace the help text.  Create a new tmp file next to
            # the existing file.
            local tmp_file="$g_user_jmsenv_file.tmp"
            local save_file="$g_user_jmsenv_file.save.1"
            echo "$user_jmsenv_helptext" > "$tmp_file"
            sed -e "1,/^${donotmodify_str}/d" "$g_user_jmsenv_file" >> "$tmp_file"
            cp -a "$g_user_jmsenv_file" "$save_file"
            mv -f "$tmp_file" "$g_user_jmsenv_file"
        else
            echo "        Not updating help text in user.jmsenv.ish file..."
        fi
    fi
}

write_jms_config() {
    local listgroup=$1; shift;
    local b_default=$1; shift;

    local listnum=${listgroup##*_}

    local config_dir="$g_generated_dir_new/config/$listgroup";
    local default_link="$g_generated_dir_new/config/default";

    local jmsenv_ish="$config_dir/jmsenv_${listnum}.ish"

    mkdir -p "$config_dir"

    # Create the default link pointing to the current default computecfg
    if $b_default; then
        rm -f "$default_link"
        ln -s "$listgroup" "$default_link"
    fi

    local jmstype
    jmstype=$(get_cfg_val_nounset "$listgroup" "jmstype");

    local jmsbasetype;
    jmsbasetype=$(get_jmsbasetype "$jmstype")

    # Don't create (or touch) anything if we are not using a JMS.
    [[ x"$jmstype" == x"NONE" ]] && return 0;

    local donotmodifyreadme="$config_dir/DO_NOT_MODIFY_files_in_this_automatically_generated_directory.README.txt"


    mkdir -p "$(dirname "$jmsenv_ish")"
    rm -f "$jmsenv_ish"
    echo "      Generating $(basename "$jmsenv_ish") ..."

    if [[ x"$jmsbasetype" == x"SGE" ]] ; then

        # Note is is valid for sgeroot and sgecell to be empty (though not
        # __UNSET__). It is a valid case for them to be empty when
        # a qsub wrapper script set SGE_ROOT and SGE_CELL (as is the case
        # for sge installs on ubuntu).
        local sge_sgeroot;
        local sge_sgecell;
        local sge_sgebindir;
        local sge_sgequeue;
        local sge_sgepe;
        local sge_sgestartargs;
        local sge_use_settings_file;
        sge_sgeroot=$(get_cfg_val_nounset "$listgroup" "sge_sgeroot");
        sge_sgecell=$(get_cfg_val_nounset "$listgroup" "sge_sgecell");
        sge_sgebindir=$(get_cfg_val_nounset "$listgroup" "sge_bindir");
        sge_sgequeue=$(get_cfg_val_nounset "$listgroup" "sge_queue");
        sge_sgepe=$(get_cfg_val_nounset "$listgroup" "sge_pe");
        sge_sgestartargs=$(get_cfg_val_nounset "$listgroup" "sge_startargs");
        sge_use_settings_file=$(get_cfg_val_nounset "$listgroup" "sge_use_settings_file");

        print_jmsenv_file \
            "$listgroup" \
            "$jmstype" \
            "$jmsbasetype" \
            "$sge_sgeroot" \
            "$sge_sgecell" \
            "$sge_sgebindir" \
            "$sge_sgequeue" \
            "$sge_sgepe" \
            "$sge_sgestartargs" \
            "$sge_use_settings_file" \
            "" \
            "" \
                > "$jmsenv_ish"

    elif [[ x"$jmsbasetype" == x"LSF" ]] ; then

        local lsf_lsfbindir;
        local lsf_lsfqueue;
        local lsf_lsfstartargs;
        lsf_lsfbindir=$(get_cfg_val_nounset "$listgroup" "lsf_bindir");
        lsf_lsfqueue=$(get_cfg_val_nounset "$listgroup" "lsf_queue");
        lsf_lsfstartargs=$(get_cfg_val_nounset "$listgroup" "lsf_startargs");

        print_jmsenv_file \
            "$listgroup" \
            "$jmstype" \
            "$jmsbasetype" \
            "" \
            "" \
            "$lsf_lsfbindir" \
            "$lsf_lsfqueue" \
            "" \
            "$lsf_lsfstartargs" \
            "false" \
            "" \
            "" \
                > "$jmsenv_ish"

    elif [[ x"$jmsbasetype" == x"PBS" ]] ; then

        local pbs_pbsbindir;
        local pbs_pbsqueue;
        local pbs_pbsstartcmd;
        local pbs_pbsstopcmd;
        pbs_pbsbindir=$(get_cfg_val_nounset "$listgroup" "pbs_bindir");
        pbs_pbsqueue=$(get_cfg_val_nounset "$listgroup" "pbs_queue");
        pbs_pbsstartargs=$(get_cfg_val_nounset "$listgroup" "pbs_startargs");

        print_jmsenv_file \
            "$listgroup" \
            "$jmstype" \
            "$jmsbasetype" \
            "" \
            "" \
            "$pbs_pbsbindir" \
            "$pbs_pbsqueue" \
            "" \
            "$pbs_pbsstartargs" \
            "false" \
            "" \
            "" \
                > "$jmsenv_ish"

    elif [[ x"$jmsbasetype" == x"Slurm" ]] ; then

        local slurm_slurmbindir;
        local slurm_slurmpartition;
        local slurm_slurmstartargs;
        slurm_slurmbindir=$(get_cfg_val_nounset "$listgroup" "slurm_bindir");
        slurm_slurmpartition=$(get_cfg_val_nounset "$listgroup" "slurm_partition");
        slurm_slurmstartargs=$(get_cfg_val_nounset "$listgroup" "slurm_startargs");

        print_jmsenv_file \
            "$listgroup" \
            "$jmstype" \
            "$jmsbasetype" \
            "" \
            "" \
            "$slurm_slurmbindir" \
            "$slurm_slurmpartition" \
            "" \
            "$slurm_slurmstartargs" \
            "false" \
            "" \
            "" \
                > "$jmsenv_ish"

    elif [[ $jmsbasetype =~ ^OtherJMS__ ]] ; then

        local otherjms_otherjmsbindir;
        local otherjms_otherjmsqueue;
        local otherjms_otherjmsstartargs;
        otherjms_otherjmsbindir=$(get_cfg_val_nounset "$listgroup" "otherjms_bindir");
        otherjms_otherjmsqueue=$(get_cfg_val_nounset "$listgroup" "otherjms_queue");
        otherjms_otherjmsstartargs=$(get_cfg_val_nounset "$listgroup" "otherjms_startargs");

        print_jmsenv_file \
            "$listgroup" \
            "$jmstype" \
            "$jmsbasetype" \
            "" \
            "" \
            "$otherjms_otherjmsbindir" \
            "$otherjms_otherjmsqueue" \
            "" \
            "$otherjms_otherjmsstartargs" \
            "false" \
            "" \
            "" \
                > "$jmsenv_ish"

    elif [[ $jmsbasetype =~ ^CustomJMS__ ]] ; then

        local customjms_customjmsbindir;
        local customjms_customjmsqueue;
        local customjms_customjmsstartargs;
        customjms_customjmsbindir=$(get_cfg_val_nounset "$listgroup" "customjms_bindir");
        customjms_customjmsqueue=$(get_cfg_val_nounset "$listgroup" "customjms_queue");
        customjms_customjmsstartargs=$(get_cfg_val_nounset "$listgroup" "customjms_startargs");

        print_jmsenv_file \
            "$listgroup" \
            "$jmstype" \
            "$jmsbasetype" \
            "" \
            "" \
            "$customjms_customjmsbindir" \
            "$customjms_customjmsqueue" \
            "" \
            "$customjms_customjmsstartargs" \
            "false" \
            "" \
            "" \
                > "$jmsenv_ish"

    elif [[ x"$jmsbasetype" == x"AWS" ]] ; then
        # nothing to do here since AWS submission works differently
        :
    elif [[ x"$jmsbasetype" == x"NONE" ]] ; then
        # Should not get here
        :
    else
        minterror "apply_settings_jmssettings(): Unrecognized jmsbasetype: $jmsbasetype"
    fi

    computecfg_id=$listgroup;
    computecfg_name=$(get_cfg_val_nounset "$listgroup" "name");
    computecfg_description=$(get_cfg_val_nounset "$listgroup" "description");

    cat > "$donotmodifyreadme" <<-EOF
	# This files in this directory are automatically generated -- DO NOT MODIFY!
	#
	# The template files in this directory are generated automatically from
	# information provided in the SMRT Link install, upgrade or reconfigure
	# process.  To reconfigure, run:
	#
	#     \$SMRT_ROOT/admin/bin/smrt_reconfig
	#
	# To make site specific changes to the job management system interface
	# modify this file (instead of modifying the template files):
	#
	#     \$SMRT_ROOT/userdata/user_jmsenv/user.jmsenv.ish
	#
	EOF
}

write_user_jmsenv_config() {
    local listgroups=$1; shift;

    local listgroup
    local jmstype
    local jmstypes=""
    for listgroup in $listgroups; do
        jmstype=$(get_cfg_val_nounset "$listgroup" "jmstype");

        if [[ ! \ $jmstypes\  =~ \ $jmstype\  ]]; then
            jmstypes+=" $jmstype"
        fi
    done


    # Create the user.jmsenv.ish file (regardless if a JMS is specified
    # or not).  The documentation in the file will indicate how to switch
    # out of non-distribued mode if NONE is selected as the JMS type.
    create_user_jmsenv "$jmstypes"
}

apply_dirlink_settings() {
    echo "  Applying dirlinks settings...."

    local jobsroot_dir;
    local tmpdir_dir;

    jobsroot_dir=$(get_cfg_val_nounset "datadirs" "jobsroot_dir");
    tmpdir_dir=$(get_cfg_val_nounset "datadirs" "tmpdir_dir");


    # Check to see if we have symlinks
    if [[ ! -h "$g_jobsroot_dirlink" ]] && [[ -e "$g_jobsroot_dirlink" ]]; then
        merror "Userdata 'jobs_root' dirlink is not a symbolic link: $g_jobsroot_dirlink"
    fi
    if [[ ! -h "$g_tmpdir_dirlink" ]] && [[ -e "$g_tmpdir_dirlink" ]]; then
        merror "Userdata 'tmp_dir' dirlink is not a symbolic link: $g_tmpdir_dirlink"
    fi

    local jobsroot_linkdir_abs;
    local jobsroot_dirabs;
    local jobsroot_linktarg;
    jobsroot_linkdir_abs=$(readlink -f "$(dirname "$g_jobsroot_dirlink")")
    jobsroot_dirabs=$(readlink -f "$jobsroot_dir")
    jobsroot_linktarg="$jobsroot_dir"
    if [[ $jobsroot_dirabs =~ ^$jobsroot_linkdir_abs/ ]] ; then
        jobsroot_linktarg=${jobsroot_dirabs#$jobsroot_linkdir_abs/};
    fi

    # Now remove and (re)create the jobs_root symlink
    rm -f "$g_jobsroot_dirlink"
    ln -s "$jobsroot_linktarg" "$g_jobsroot_dirlink"

    tmpdir_destdir=$tmpdir_dir
    # Now remove and (re)create the tmp_dir symlink
    rm -f "$g_tmpdir_dirlink"
    ln -s "$tmpdir_dir" "$g_tmpdir_dirlink"
}

apply_database_settings() {
    echo "  Applying database settings...."

    local dbdatadir_dir;
    dbdatadir_dir=$(get_cfg_val_nounset "database" "dbdatadir");

    # Check to see if we have symlinks
    if [[ ! -h "$g_dbdatadir_dirlink" ]] && [[ -e "$g_dbdatadir_dirlink" ]]; then
        merror "Userdata 'dbdatadir' dirlink is not a symbolic link: $g_dbdatadir_dirlink"
    fi

    local dbdatadir_linkdir_abs;
    local dbdatadir_dirabs;
    local dbdatadir_linktarg;
    dbdatadir_linkdir_abs=$(readlink -f "$(dirname "$g_dbdatadir_dirlink")")
    dbdatadir_dirabs=$(readlink -f "$dbdatadir_dir")
    dbdatadir_linktarg="$dbdatadir_dir"
    if [[ $dbdatadir_dirabs =~ ^$dbdatadir_linkdir_abs/ ]] ; then
        dbdatadir_linktarg=${dbdatadir_dirabs#$dbdatadir_linkdir_abs/};
    fi

    # Now remove and (re)create the db_datadir symlink
    rm -f "$g_dbdatadir_dirlink"
    ln -s "$dbdatadir_linktarg" "$g_dbdatadir_dirlink"
}

print_cromwell_config() {
    local listnum=$1; shift;
    local outfile=$1; shift;

    local listgroup;
    listgroup=$(get_listgroup_from_listnum "computecfg_NN" "$listnum")

    echo "      Generating $(basename "$outfile") ..."

    local pipelineId;
    local presetId;
    local name;
    local description;

    local jmstype
    local cromwell_nproc
    local cromwell_maxchunks
    local cromwell_loglevel
    local cromwell_backend
    local cromwell_aws_root
    local cromwell_aws_docker
    local cromwell_aws_memory

    presetId="installer.user.config.cromwell_${listnum}";
    pipelineId=$presetId;
    name=$(get_cfg_val_nounset "$listgroup" "name");
    description=$(get_cfg_val_nounset "$listgroup" "description");

    cromwell_nproc=$(get_cfg_val_nounset "$listgroup" "nproc");
    cromwell_maxchunks=$(get_cfg_val_nounset "$listgroup" "maxchunks");
    cromwell_loglevel=$(get_cfg_val_nounset "$listgroup" "cromwell_loglevel");

    jmstype=$(get_cfg_val_nounset "$listgroup" "jmstype");
    if [[ x"$jmstype" == x"NONE" ]] ; then
        cromwell_backend="Local"
    else
        cromwell_backend="$listgroup";
    fi

    local json_pipelineId;
    local json_presetId;
    local json_name;
    local json_description;

    local json_cromwell_nproc;
    local json_cromwell_maxchunks;
    local json_cromwell_loglevel;
    local json_cromwell_backend;
    local json_cromwell_cloud_options;

    local json_comment;

    json_pipelineId=$(json_str            "$pipelineId");
    json_presetId=$(json_str              "$presetId");
    json_name=$(json_str                  "$name");
    json_description=$(json_str           "$description");

    # Cromwell Engine Options:
    json_cromwell_nproc=$(json_posint     "$cromwell_nproc");
    json_cromwell_maxchunks=$(json_posint     "$cromwell_maxchunks");
    json_cromwell_loglevel=$(json_str     "$cromwell_loglevel");
    json_cromwell_backend=$(json_str      "$cromwell_backend");

    #    File creation comment
    json_comment=$(json_str               "Created by installprompter: $(date)");

    # Cromwell on AWS requires use of Docker, which won't necessarily be
    # available on other systems
    json_cromwell_cloud_options=""
    if [[ x"$jmstype" == x"AWS" ]] ; then
        cromwell_aws_root=$(get_cfg_val_nounset "$listgroup" "aws_disk_root");
        cromwell_aws_docker=$(get_cfg_val_nounset "$listgroup" "aws_docker");
        cromwell_aws_memory=$(get_cfg_val_nounset "$listgroup" "aws_memory");
        json_cromwell_cloud_options=$(cat <<EOF
        "cromwell.engine_options.docker": "$cromwell_aws_docker",
        "cromwell.engine_options.disks": "$cromwell_aws_root 3 EFS",
        "cromwell.engine_options.memory": "${cromwell_aws_memory}G",
EOF
        )
    fi

    # Remove and recreate preset config file (make dir, if needed)
    mkdir -p "$(dirname "$outfile")"
    rm -f "$outfile";

    cat > "$outfile" <<-EOF
	{
	    "pipelineId":   $json_pipelineId,
	    "presetId":     $json_presetId,
	    "name":         $json_name,
	    "description":  $json_description,
	    "options": {
	        $json_cromwell_cloud_options
	        "cromwell.workflow_options.nproc":         $json_cromwell_nproc,
	        "cromwell.workflow_options.max_nchunks":   $json_cromwell_maxchunks,
	        "cromwell.workflow_options.log_level":     $json_cromwell_loglevel,
	        "cromwell.engine_options.backend":         $json_cromwell_backend,
	        "cromwell.engine_options.read_from_cache": false,
	        "cromwell.engine_options.write_to_cache":  true
	    },
	    "taskOptions": {},
	    "_comment": $json_comment
	}
	EOF
}

apply_smrtlink_settings() {
    echo "  Applying smrtlink settings...."

    local install__sluuid
    local smrtlink__dnsname
    local smrtlink__services_port
    local smrtlink__gui_port
    local smrtlink__services_maxmem
    local smrtlink__services_minmem
    local smrtlink__extended_cell_use_enable;

    local remote__eve_url;
    local remote__update_url;

    local database__dbport
    local cromwell__port

    local jmsconfig__nworkers

    local mail__port;
    local mail__host;
    local mail__user;
    local mail__password;

    install__sluuid=$(get_cfg_val_nounset "install" "sluuid");
    smrtlink__dnsname=$(get_cfg_val_nounset "smrtlink" "dnsname");
    smrtlink__services_port=$(get_cfg_val_nounset "smrtlink" "services_port");
    smrtlink__gui_port=$(get_cfg_val_nounset "smrtlink" "gui_port");
    smrtlink__services_maxmem=$(get_cfg_val_nounset "smrtlink" "services_maxmem");
    smrtlink__services_minmem=$(get_cfg_val_nounset "smrtlink" "services_minmem");

    smrtlink__extended_cell_use_enable=$(get_cfg_val_nounset "smrtlink" "extended_cell_use_enable");

    remote__eve_url=$(get_eve_url);
    remote__update_url=$(get_update_url);

    database__dbport=$(get_cfg_val_nounset "database" "dbport");
    cromwell__port=$(get_cfg_val_nounset "cromwell" "port");

    jmsconfig__nworkers=$(get_cfg_val_nounset "smrtlink" "nworkers");

    mail__host=$(get_cfg_val_nounset "smrtlink" "mail_host");
    mail__port=$(get_cfg_val_nounset "smrtlink" "mail_port");
    mail__user=$(get_cfg_val_nounset "smrtlink" "mail_user");
    mail__password=$(get_cfg_val_nounset "smrtlink" "mail_password");

    local computecfgs
    local computecfg
    local jmstype
    local jmstypes=""
    computecfgs=$(get_computecfgs_by_menuorder);
    for computecfg in $computecfgs; do
        local jmstype;
        jmstype=$(get_cfg_val_nounset "$computecfg" "jmstype");
        # Add the jmstype for each computecfg (we may have dupiicates in the
        # case of two compute configs with the same jmstype, but the string
        # will also be able to indicate how many compute configs are in place
        # at each site).
        jmstypes+=",$jmstype"
    done
    jmstypes=${jmstypes#,}

    # Define all the local variables
    # NOTE: Separate the 'local' declaration from the $() command substitution
    #       assignment.  If the declaration and the $() assignment are on the
    #       same line, bash will ignore any non-zero exit status from the
    #       command in the command subsituion.   We want any error to
    #       propagate and cause an error exiting the program with non-zero
    #       status.
    local configjson_host;
    local configjson_services_port;
    local configjson_gui_port;
    local configjson_database_dbport;
    local configjson_cromwell_port;
    local configjson_nworkers;
    local configjson_jobsroot;
    local configjson_tmpdir;
    local configjson_services_maxmem;
    local configjson_services_minmem;
    local configjson_https_enable;
    local configjson_manifest_file;
    local configjson_bundledir;
    local configjson_smrtlink_system_root;
    local configjson_smrtlink_uuid;
    local configjson_db_uri;
    local configjson_logdir;
    local configjson_jmstypes;
    local configjson_eve_url;
    local configjson_remote_update_url;
    local configjson_comment;

    local configjson_mail_host;
    local configjson_mail_port;
    local configjson_mail_user;
    local configjson_mail_password;

    local configjson_extended_cell_use_enable;

    configjson_host=$(json_str "$smrtlink__dnsname");
    configjson_services_port=$(json_posint "$smrtlink__services_port");
    configjson_gui_port=$(json_posint "$smrtlink__gui_port");
    configjson_database_dbport=$(json_posint "$database__dbport");
    configjson_cromwell_port=$(json_posint "$cromwell__port");
    configjson_database_dbdatadir=$(json_str "$g_dbdatadir_dirlink");
    configjson_nworkers=$(json_posint "$jmsconfig__nworkers");

    configjson_jobsroot=$(json_str "$g_jobsroot_dirlink");
    configjson_tmpdir=$(json_str "$g_tmpdir_dirlink");
    configjson_services_maxmem=$(json_posint "$smrtlink__services_maxmem");
    configjson_services_minmem=$(json_posint "$smrtlink__services_minmem");
    configjson_eve_url=$(json_strnull "$remote__eve_url")
    configjson_remote_update_url=$(json_strnull "$remote__update_url")
    configjson_manifest_file=$(json_strnull "$g_topdir/etc/pacbio-manifest.json")
    configjson_bundledir=$(json_strnull "$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/pacbio/smrtlink-analysisservices-gui/resources/pacbio-bundles")
    configjson_smrtlink_system_root=$(json_strnull $(readlink -f "$opt_rootdir"))
    configjson_smrtlink_uuid=$(json_strnull "$install__sluuid")

    configjson_mail_host=$(json_strnull "$mail__host")
    configjson_mail_port=$(json_posint "$mail__port")
    configjson_mail_user=$(json_strnull "$mail__user")
    configjson_mail_password=$(json_strnull "$mail__password")

    configjson_extended_cell_use_enable=$(json_boolean "$smrtlink__extended_cell_use_enable")

    configjson_logdir=$(json_str "$g_slag_logdir");

    configjson_jmstypes=$(json_str "$jmstypes");

    local create_migration_configfile=false;
    if [[ -e "$g_sqlite_database" ]] ; then
        configjson_db_uri=$(json_strnull "$g_sqlite_database");
        create_migration_configfile=true;
    else
        configjson_db_uri=$(json_strnull "");
    fi
    if [[ ! -z "$g_prior_slag_installdir" ]] ; then
        configjson_previous_install_dir=$(json_strnull "$g_prior_slag_installdir");
        create_migration_configfile=true;
    else
        configjson_previous_install_dir=$(json_strnull "");
    fi
    configjson_comment=$(json_str "Created: $(date)");

    # Make sure the logdir exists
    mkdir -p "$g_slag_logdir"

    # We need to supply the old config.json file, but only if the old
    # smrtlink sqlite.db database file exists of if the old install tree
    # exists (upgrade scenario).

    rm -f "$g_new_smrtlink_migrationconfig_json";

    if $create_migration_configfile; then
        echo "    Creating migration-config.json..."

        # Create old migration-config.json (mkdir, if needed)
        mkdir -p "$(dirname "$g_new_smrtlink_migrationconfig_json")"
        cat > "$g_new_smrtlink_migrationconfig_json" <<-EOF
	{
	    "PB_DB_URI": $configjson_db_uri,
	    "PREVIOUS_INSTALL_DIR": $configjson_previous_install_dir,
	    "_comment": $configjson_comment
	}
	EOF

    fi

    presetlist_jsonsnipet=$(get_ordered_computecfg_filelist_jsonsnipet);

    echo "    Creating $(basename "$g_new_smrtlink_system_configjson")..."
    # Remove and recreate config.json (make dir, if needed)
    mkdir -p "$(dirname "$g_new_smrtlink_system_configjson")"
    rm -f "$g_new_smrtlink_system_configjson";

    cat > "$g_new_smrtlink_system_configjson" <<-EOF
	{
	  "smrtflow": {
	    "server": {
	      "port": $configjson_services_port,
	      "manifestFile": $configjson_manifest_file,
	      "eventUrl": $configjson_eve_url,
	      "dnsName": $configjson_host,
	      "bundleDir": $configjson_bundledir
	    },
	    "engine": {
	      "maxWorkers": $configjson_nworkers,
	      "jobRootDir": $configjson_jobsroot,
	      "pbsmrtpipePresets": [
	          $presetlist_jsonsnipet
	      ]
	    },
	    "db": {
	      "properties": {
	        "databaseName": "$g_dbdatabase_smrtlink",
	        "user": "$g_dbuser",
	        "password": "$g_dbpassword",
	        "portNumber": $configjson_database_dbport,
	        "serverName": "$g_dbhost"
	      }
	    },
	    "cromwell": {
	      "host": "localhost",
	      "port": $configjson_cromwell_port
	    }
	  },
	  "pacBioSystem": {
	    "tomcatPort": $configjson_gui_port,
	    "tomcatMemory": 1024,
	    "smrtLinkServerMemoryMin": $configjson_services_minmem,
	    "smrtLinkServerMemoryMax": $configjson_services_maxmem,
	    "tmpDir": $configjson_tmpdir,
	    "logDir": $configjson_logdir,
	    "jmsTypes": $configjson_jmstypes,
	    "pgDataDir": $configjson_database_dbdatadir,
	    "enableCellReuse": $configjson_extended_cell_use_enable,
	    "remoteBundleUrl": $configjson_remote_update_url,
	    "smrtLinkSystemRoot": $configjson_smrtlink_system_root,
	    "smrtLinkSystemId": $configjson_smrtlink_uuid,
	    "mailHost": $configjson_mail_host,
	    "mailPort": $configjson_mail_port,
	    "mailUser": $configjson_mail_user,
	    "mailPassword": $configjson_mail_password
	  },
	  "comment": $configjson_comment
	}
	EOF
}

install_smrtlink_migrationconfig() {
    if [[ -e "$g_smrtlink_migrationconfig_json" ]]; then
        echo "    Installing $(basename "$g_smrtlink_migrationconfig_json")..."

        local migrationconfig_json_link="$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/pacbio/smrtlink-analysisservices-gui/migration-config.json"

        # Remove the link files and relink
        rm -f "$migrationconfig_json_link"
        ln -s "$g_smrtlink_migrationconfig_json" "$migrationconfig_json_link"
    fi
}

install_smrtlink_settings() {
    # Now link the smrtlink-system-config.json in the
    # smrtlink-analysis-services install tree to our json file
    echo "    Installing $(basename "$g_smrtlink_system_configjson")..."

    # Move original config file from install tree out of the way, if needed
    local smrtlink_system_configjson_link="$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/pacbio/smrtlink-analysisservices-gui/smrtlink-system-config.json"
    local smrtlink_system_configjson_link_orig="${smrtlink_system_configjson_link}.orig"
    if [[ -e "$smrtlink_system_configjson_link" ]] ; then
        if [[ ! -e "$smrtlink_system_configjson_link_orig" ]] ; then
            mv "$smrtlink_system_configjson_link" "$smrtlink_system_configjson_link_orig"
        fi
    fi

    # Remove the link file and relink
    local smrtlink_system_configjson_link="$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/pacbio/smrtlink-analysisservices-gui/smrtlink-system-config.json"
    rm -f "$smrtlink_system_configjson_link"
    ln -s "$g_smrtlink_system_configjson" "$smrtlink_system_configjson_link"


    # Validate the configuration file is valid
    echo "    Validating $(basename "$g_smrtlink_system_configjson")..."
    JAVA_HOME="$g_javahome" JAVA_OPTS="-Djava.library.path=" \
        "$g_smrtlink_toolsbin/bundler-validate-config" "$smrtlink_system_configjson_link" >> "$g_logfile"

    smrtlink__dnsname=$(get_cfg_val_nounset "smrtlink" "dnsname");
    smrtlink__services_port=$(get_cfg_val_nounset "smrtlink" "services_port");
    smrtlink__gui_port=$(get_cfg_val_nounset "smrtlink" "gui_port");
}

write_cromwell_conf() {
    local iscli=$1; shift
    local cromwell_conf_outfile=$1; shift;

    local computecfgs;
    computecfgs=$(get_computecfgs_by_menuorder);

    local database_dbport;
    local cromwell_port;
    local cromwell_max_concurrent_workflows;
    database_dbport=$(get_cfg_val_nounset "database" "dbport");
    cromwell_port=$(get_cfg_val_nounset "cromwell" "port");
    cromwell_max_concurrent_workflows=$(get_cfg_val_nounset "smrtlink" "nworkers");
    cromwell_max_concurrent_workflows=$(( 2*cromwell_max_concurrent_workflows ))

    # We need to peek into the individual configs first to determine whether
    # AWS is being used, since this involves config variables outside the
    # backend scope
    local aws_region;
    aws_region=""
    for computecfg in $computecfgs; do
        local jmstype;
        jmstype=$(get_cfg_val_nounset "$computecfg" "jmstype");

        if [[ x"$jmstype" == x"AWS" ]] ; then
            aws_region=$(get_cfg_val_nounset "$computecfg" "aws_region");
            break
        fi
    done

    echo "    Creating $(basename "$cromwell_conf_outfile")..."
    # Remove and recreate cromwell.conf (make dir, if needed)
    mkdir -p "$(dirname "$cromwell_conf_outfile")"

    rm -rf "$cromwell_conf_outfile";
    cat > "$cromwell_conf_outfile" <<-EOF
	include required(classpath("application"))
	system {
	  max-concurrent-workflows = $cromwell_max_concurrent_workflows
	}
	webservice {
	  port = $cromwell_port
	  interface = localhost
	}
	EOF

    if ! $iscli; then
        cat >> "$cromwell_conf_outfile" <<-EOF
	call-caching {
	  enabled = true
	  invalidate-bad-cache-results = true
	}
	EOF
    else
        cat >> "$cromwell_conf_outfile" <<-EOF
	call-caching {
	  enabled = false
	}
	EOF
    fi

    cat >> "$cromwell_conf_outfile" <<-EOF
	workflow-options {
	  workflow-log-temporary = false
	}
	EOF

    if ! $iscli; then
        cat >> "$cromwell_conf_outfile" <<-EOF

	database {
	  profile = "slick.jdbc.PostgresProfile\$"
	  db {
	    driver = "org.postgresql.Driver"
	    url = "jdbc:postgresql://localhost:$database_dbport/cromwell"
	    user = "$g_dbuser"
	    password = "$g_dbpassword"
	    port = $database_dbport
	    connectionTimeout = 5000
	  }
	}
	EOF
    fi

    cat >> "$cromwell_conf_outfile" <<-EOF

	aws {
	  application-name = "cromwell"
	  auths = [{
	    name = "default"
	    scheme = "default"
	  }]
	  region = "$aws_region"
	}

	backend {
	  # Always keep the default at "Local" and specify any other backend
	  # via the computecfg model, in the cromwell_NN.json preset json file.
	  default = "Local"
	  providers {
	    Local {
	      actor-factory = "cromwell.backend.impl.sfs.config.ConfigBackendLifecycleActorFactory"
	      config {
	        concurrent-job-limit = 10
	        filesystems {
	          local {
	            caching {
	              duplication-strategy: [
	              "soft-link"
	              ]
	            }
	            localization: [
	              "soft-link",
	            ]
	          }
	        }
	      }
	    }
	EOF

    echo "      Applying JMS settings to cromwell config..."
    local computecfg
    for computecfg in $computecfgs; do
        local jmstype;
        jmstype=$(get_cfg_val_nounset "$computecfg" "jmstype");

        if [[ x"$jmstype" == x"NONE" ]] ; then
            # For non-distributed mode, we will use the "Local" mode
            # case instead.  The Local mode is required regardless since
            # some task hardwire the default to 'Local'.
            # NOTE: If we were to specify a separate backend that was the
            #       same as "Local" and made it the "default" selection
            #       in the cromwell config above, we hit some mysterious
            #       'java.lang.RuntimeException: Error parsing generated wdl'
            #       exception and cromwell will not run any workflows
            continue;
        elif [[ x"$jmstype" == x"AWS" ]] ; then
            local jobsroot_dir;
            local computecfg_name;
            local computecfg_queue;
            jobsroot_dir=$(get_cfg_val_nounset "datadirs" "jobsroot_dir");
            computecfg_name=$(get_cfg_val_nounset "$computecfg" "name");
            computecfg_queue=$(get_cfg_val_nounset "$computecfg" "aws_queue");

            cat >> "$cromwell_conf_outfile" <<EOF
            $computecfg {
              actor-factory = "cromwell.backend.impl.aws.AwsBatchBackendLifecycleActorFactory"
              config {
                numSubmitAttempts = 10
                numCreateDefinitionAttempts = 10
                root = "$jobsroot_dir/cromwell-executions"
                auth = "default"
                default-runtime-attributes {
                  queueArn = "$computecfg_queue"
                }
                filesystems {
                  local {
                    auth = "default"
                    caching {
                      duplication-strategy =  "reference"
                    }
                  }
                }
              }
            }
EOF
        else

            # set the jobid regex expression for parsing the jobid from the
            # submit command.  Hardwire the regexs that we know here, but
            # it might be better to do this differently (to give the user some
            # control over the regex)
            local jobid_regex;
            local jmsbasetype;
            jmsbasetype=$(get_jmsbasetype "$jmstype")
            if [[ x"$jmsbasetype" == x"SGE" ]] ; then
                jobid_regex="Your job (\\\\d+)"
            elif [[ x"$jmsbasetype" == x"LSF" ]] ; then
                # Best guess for now:
                jobid_regex="Job <(\\\\d+)>.*"
            elif [[ x"$jmsbasetype" == x"PBS" ]] ; then
                # Best guess for now:
                jobid_regex="(\\\\d+)\\\\.\\\\w+"
            elif [[ x"$jmsbasetype" == x"Slurm" ]] ; then
                jobid_regex="Submitted batch job (\\\\d+)"
            elif [[ $jmsbasetype =~ ^OtherJMS__ ]] ; then
                # FIXME: this is just a placeholder for now, should have this
                #        be user specified
                jobid_regex="(\\\\d+)"
            elif [[ $jmsbasetype =~ ^CustomJMS__ ]] ; then
                # FIXME: this is just a placeholder for now, should have this
                #        be user specified
                jobid_regex="(\\\\d+)"
            else
                minterror "apply_cromwell_settings(): Unrecognized jmsbasetype: $jmsbasetype"
            fi

            local computecfg_name;
            local computecfg_description;
            local computecfg_concurrent_job_limit;
            local computecfg_timeout_seconds;
            computecfg_name=$(get_cfg_val_nounset "$computecfg" "name");
            computecfg_description=$(get_cfg_val_nounset "$computecfg" "description");
            computecfg_concurrent_job_limit=$(get_cfg_val "$computecfg" "cromwell_concurrent_job_limit")
            computecfg_timeout_seconds=1800

            cat >> "$cromwell_conf_outfile" <<-EOF
	    $computecfg {
	      actor-factory = "cromwell.backend.impl.sfs.config.ConfigBackendLifecycleActorFactory"
	      config {
	        concurrent-job-limit = $computecfg_concurrent_job_limit
	        glob-link-command = "ln -sL GLOB_PATTERN GLOB_DIRECTORY"
	        exit-code-timeout-seconds = $computecfg_timeout_seconds
	        script-epilogue = ""

	        # Define runtime attributes, settable on a per-workflow or
	        # per-task basis
	        runtime-attributes = """
	            Int cpu = 1
	            Float? memory_gb
	            Float? mempercpu_gb
	        """

	        submit = """
	        $g_runjmscmd_exe \\
	            --start \\
	            --computecfg-id "$computecfg" \\
	            --computecfg-name "$computecfg_name" \\
	            --computecfg-description "$computecfg_description" \\
	            --jobname \${job_name} \\
	            --stdoutfile "\${out}" \\
	            --stderrfile "\${err}" \\
	            --nproc "\${cpu}" \\
	            --cmd "\${script}"
	        """

	        # command for killing/aborting
	        kill = """
	        $g_runjmscmd_exe \\
	            --stop \\
	            --computecfg-id "$computecfg" \\
	            --computecfg-name "$computecfg_name" \\
	            --computecfg-description "$computecfg_description" \\
	            --jobid \${job_id}
	        """

	        # Command used at restart to check if a job is alive
	        check-alive = """
	        $g_runjmscmd_exe \\
	            --status \\
	            --computecfg-id "$computecfg" \\
	            --computecfg-name "$computecfg_name" \\
	            --computecfg-description "$computecfg_description" \\
	            --jobid \${job_id}
	        """

	        # How to search the submit output for a job_id
	        job-id-regex = "$jobid_regex"

	        filesystems {
	          local {
	            localization: [
	             "soft-link","hard-link","copy"
	            ]
	            caching {
	              duplication-strategy: [
	                "soft-link", "hard-link", "copy"
	              ]
	              caching-strategy: "file"
	            }
	          }
	        }
	      }
	    }
	EOF
        fi
    done

    cat >> "$cromwell_conf_outfile" <<-EOF
	  }
	}
	EOF
}

apply_cromwell_settings() {
    echo "  Applying cromwell settings...."
    write_cromwell_conf false "$g_new_smrtlink_cromwell_config"
    write_cromwell_conf true "$g_new_smrtlink_cromwell_cli_config"
}

clean_new_generated_dir () {
    # We should completely create the generated dir from scratch.  If it
    # already exists, it was leftover from a previous install or upgrade
    # and it should be safe to remove.
    if [[ -e "$g_generated_dir_new" ]] ; then
        echo "  Cleaning new generated directory (from previous install)..."
        rm -rf "$g_generated_dir_new"
    fi
}

install_generated_settings() {
    echo "    Installing generated configs..."

    # Do a (more-or-less) atomic install, actually a replacement, of the
    # generated config dir.
    # This is to reduce the chance that the smrtlink upgrade was aborted
    # during the configuration generation phase, potentialy leaving us
    # without some generated files that may be needed when the upgrade
    # is reinvoked.  Specifically, generated smrtlink-system-config.json
    # is required by the priordir/.../dbhelper script in order to do the
    # preupgrade backups.
    if [[ -e "$g_generated_dir" ]] ; then
        mv "$g_generated_dir" "$g_generated_dir_old"
    fi

    # Install the new generated directory
    mv "$g_generated_dir_new" "$g_generated_dir"

    # Remove the old generated dirctory, it should already have been archived
    # and available as the 'userdata/archive/latest' link to the config
    # archive.
    rm -rf "$g_generated_dir_old"
}

extract_bundles() {
    # Do not extract if --reconfig arg was given
    $opt_reconfig && return 0

    # Skip extracting in testmode
    $opt_testmode && return 0;

    if ! $opt_no_extract; then
        echo "Extracting bundles..."
    fi

    # Extract bundles with group/other access
    umask 0022

    local bundlename;
    local bundledir="$g_topdir/bundles"

    bundlename="smrtinub"
    if ! $opt_no_extract || [[ ! -e "$bundledir/$bundlename" ]] ; then
        # Extract if the rootdir doesn't exist or if --no-extract not specified
        echo "  Extracting $bundlename bundle...."
        "$g_topdir/private/bundles/$bundlename/$bundlename"*_*.run --rootdir "$bundledir/$bundlename" --batch
    fi

    bundlename="smrttools"
    if ! $opt_no_extract || [[ ! -e "$bundledir/$bundlename" ]] ; then
        # Extract if the rootdir doesn't exist or if --no-extract not specified
        echo "  Extracting smrttools bundle...."
        "$g_topdir/private/bundles/$bundlename/$bundlename"*_*.run --rootdir "$bundledir/$bundlename" --batch
    fi

    bundlename="smrtlink-analysisservices-gui"
    if ! $opt_no_extract || [[ ! -e "$bundledir/$bundlename" ]] ; then
        # Extract if the rootdir doesn't exist or if --no-extract not specified
        echo "  Extracting $bundlename bundle...."
        "$g_topdir/private/bundles/$bundlename/$bundlename"*_*.run --rootdir "$bundledir/$bundlename" --batch
    fi

    if [[ -e "$bundledir/prerun-patchfiles" ]] ; then
        echo "  Applying patch 'prerun' file updates to extracted bundles...."
        rsync -v -rlptD -c  "$bundledir/prerun-patchfiles/bundles/" "$bundledir"
    fi

    # Turn off group/other access for any new files/dirs created
    umask 0077
}

run_smrtslag_upgrade() {
    # This must be called after apply_smrtlink_settings(), since
    # the <smrtslagroot>/bin/upgrade script depends on the json config files
    # existing in order to set up the database.

    echo "  Running smrtlink-analysisservices-gui upgrade..."

    # Call the smrtlink services bundler upgrade.  This will initialize
    # the postgres database from scratch (including setting up ports, and
    # credentials), create the smrtlink database, import the old sqlite.db
    # database, and initially populate the smrtlinkdb database.
    # All control of the database including backups and such will be part
    # of the services bundle now.
    # FIXME: set path to postgres executables here, but this should really
    #        be done in a binwrapper script around bin/upgrade.
    # FIXME: this should be a properly defined wrapper (i.e. we
    #        shouldn't have to call with our path and java)

    # Display the output of the upgrade script, even though it obviously
    # doesn't match the typical installer output.  During upgrades, it may
    # take a long time to run the upgrades and it is helpful to see what is
    # going on and get an idea of what we are waiting on.  Perhaps the better
    # approach is to have the upgrade exe output stuff that is more similar
    # to the installer output.
    local hide_upgrade_script_output=false;
    if $hide_upgrade_script_output; then
        local sys_path="/usr/bin:/bin"
        local update_script_path="$g_python3_bindir:$sys_path"
        # Use this to set SMRT_PYTHON3_PATH for running dbctrl
        local update_script_python3_path="$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/thirdparty/postgresql/postgresql_9.6.1/binwrap:$sys_path"
        local stat=0;
        JAVA_HOME="$g_javahome" \
        JAVA_OPTS="-Djava.library.path=" \
        PATH="$update_script_path" \
        SMRT_PYTHON3_PATH="$update_script_python3_path" \
            "$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/pacbio/smrtlink-analysisservices-gui/bin/upgrade" >> "$g_logfile" 2>&1 || stat=$?
        if [[ $stat -ne 0 ]] ; then
            local errmsg;
            errmsg=$(tail -100 "$g_logfile" |sed -e '1,/Running smrtlink-analysisservices-gui upgrade/d')
            echo "    Error in running smrtlink-analysisservices-gui upgrade script."
            echo "    See '$g_logfile' for full log, error info below..."
            echo
            echo "$errmsg"
            echo
            echo "Upgrade script exited with non-zero status ($stat), aborting..."
            abort;
        fi
    else
        local sys_path="/usr/bin:/bin"
        local update_script_path="$g_python3_bindir:$sys_path"
        # Use this to set SMRT_PYTHON3_PATH for running dbctrl
        local update_script_python3_path="$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/thirdparty/postgresql/postgresql_9.6.1/binwrap:$sys_path"
        local stat=0;
        JAVA_HOME="$g_javahome" \
        JAVA_OPTS="-Djava.library.path=" \
        PATH="$update_script_path" \
        SMRT_PYTHON3_PATH="$update_script_python3_path" \
            "$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/pacbio/smrtlink-analysisservices-gui/bin/upgrade" || stat=$?
        if [[ $stat -ne 0 ]] ; then
            echo
            echo "Upgrade script exited with non-zero status ($stat), aborting..."
            abort;
        fi
    fi
}

upgrade_database_checkfixbackup_preupgrade() {
    # This must be run before making any modifications to the configs
    # (archiving old configs or writing new configs), since we will be
    # calling the prior version of dbhelper, which assumes everything is
    # still set up like the prior version.

    if ! $opt_upgrade; then
        return 0;
    fi

    # If we haven't run services yet (ever), then the smrtlink database hasn't
    # been populated yet, and it will be empty (no tables in the smrtlinkdb
    # database).  In practice, this shouldn't happen in production installs,
    # but it can happen in the case of testing upgrades without running
    # services (eg install 5.0.1 -> do not start services -> upgrade to 5.1.0)
    local allow_empty_smrtlinkdb_flag="";
    service_logcnt=$(find "$g_slag_logdir" -name secondary-smrt-server.log\* | wc -l)
    if [[ $service_logcnt -eq 0 ]] ; then
        allow_empty_smrtlinkdb_flag="--allow-empty-smrtlinkdb";
    fi
    local skip_preupgrade_backup_flag=""
    if ! $opt_skip_backup_preupgrade; then
        skip_preupgrade_backup_flag="--preupgrade-backup"
    fi

    echo ""
    if [[ -z "$skip_preupgrade_backup_flag" ]] ; then
        echo "Checking databases (preupgrade)..."
    else
        echo "Checking and backing up databases (preupgrade)..."
    fi
    # Make sure to use the prior version of dbhelper (and the database)
    # since whe have not upgraded anything yet.  So we should not be using
    # any new postgres binaries to access the pre-upgraded database and such.
    #
    # Allow an empty cromwell database for an 8.0.0->8.0.0 upgrade without
    # cromwell services getting fired up.  This should be ok, in general,
    # since we assum the cromwell db will be transient (no permanent data
    # in the database).  Need to do this here in the installprompter for
    # 8.0.0 (rather than in dbhelper itself), since we are using the
    # prior installation version of dbhelper.
    "$g_priordir/admin/bin/dbhelper" --checklocale $skip_preupgrade_backup_flag --onstart-restart-db --onexit-stop-db $allow_empty_smrtlinkdb_flag --allow-empty-dbnames cromwell
    echo ""
}

upgrade_database_backup_postupgrade() {
    if ! $opt_upgrade; then
        return 0;
    fi
    if $opt_skip_backup_postupgrade; then
        return 0;
    fi

    echo ""
    echo "Backing up databases (postupgrade)..."
    "$g_progdir/dbhelper" --postupgrade-backup --onexit-stop-db
}

# This is the correct place for any creation of top level symlinks and such,
# since it will run in both the install and upgrade case.
misc_hacks() {
    echo "  Updating smrtcmds link...."
    # Create the toplevel smrtcmds link
    rm -f "$opt_rootdir/smrtcmds";
    ln -s "current/bundles/smrttools/smrtcmds" "$opt_rootdir/smrtcmds";

    echo "  Installing manifests...."
    # Copying version.json file to web root.  Not sure if this it the right
    # place for it, but it at least makes the version information directly
    # accessible from the smrtlink webserver as:
    #      http://smrtlinkhost:PPPP/version.json
    cp -a "$g_topdir/etc/pacbio-manifest.json" "$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/pacbio/smrtlink-analysisservices-gui/tomcat_current/webapps/ROOT/pacbio-manifest.json"
    cp -a "$g_topdir/etc/version.json" "$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/pacbio/smrtlink-analysisservices-gui/tomcat_current/webapps/ROOT/version.json"

    # Also copy version.txt to web root (just so we have a human readable
    # representation of the hierarchy in version.json for now)
    cp -a "$g_topdir/etc/pacbio-manifest.txt" "$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/pacbio/smrtlink-analysisservices-gui/tomcat_current/webapps/ROOT/pacbio-manifest.txt"
    cp -a "$g_topdir/etc/version.txt" "$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/pacbio/smrtlink-analysisservices-gui/tomcat_current/webapps/ROOT/version.txt"

    # Make sure the 'rwdir' exists.  We not only need it for the
    # 'must-import-canneddata' below, but also for things like the
    # MPLCONFIGDIR for matplotlib (in the python wrappers).
    mkdir -p "$g_rwdir"

    # Upon completion of any invocation of the installer (install, upgrade,
    # reconfigure, and restore), set the must-import-canneddata flag to
    # make sure we import the latest barcodes and canneddata.  Actually, we
    # may be able to ignore the reconfigure case, since the canneddata and
    # barcodes shouldn't be changing (should only change on new releases),
    # but reconfiguring should be rare enough, an reimporting should be safe.
    # So we will just flag reimporting any time the installer is run.
    # This can be overridden with the --no-import-canneddata command line
    # option.
    if ! $opt_skip_import_canneddata; then
        > $g_rwdir/must-import-canneddata;
    fi

    # FIXME: +++ Remove this after all sites upgraded to >7.0.1 (start) +++
    # Remove these legacy config files (just before the successful
    # completion of the installer).  They are no longer needed and have
    # already been archived in archive_existing_configs().
    # The following lines can be removed after the 7.0.1 release:
    echo "  Clean up legacy configurations..."
    rm -f "$g_config_dir/preset.xml"
    rm -f "$g_config_dir/smrtlink-system-config.json"
    # FIXME: --- Remove this after all sites upgraded to >7.0.1 (end) ---

    # FIXME: +++ Remove this after all sites upgraded to >7.0.1 (start) +++
    if  $opt_upgrade; then
        if [[ -r "$g_priordir/admin/bin/dbhelper" ]] &&
            ! grep '^umask ' "$g_priordir/admin/bin/dbhelper" > /dev/null; then
            echo "  Modifying userdata permissions..."
            # One time only adjustment permissions in userdata to remove
            # group/other permissions on most files/dirs (during upgrade only)
            # Do not touch the jobs_root or db_datadir directory, but do
            # change the permissions on db_datadir/backups.  The dbstore
            # directory should already be owner read/write only
            local dir
            for dir in "archive" "config" "generated" "log" "tsreport" "user_jmsenv" "db_datadir/backups"; do
                chmod -R go-rwx "$g_userdata_dirabs/$dir"
            done

            # Special case for tmp_dir. Only modify the permissions if it looks
            # like it was created in a previous install (i.e. owned by the
            # smrtlink user, with 755 permissions).   Leave it alone if
            # not owned by smrtlink user or if group or world writeable.
            local tmpdir_abs;
            local tmpdir_uid_mode;
            tmpdir_abs=$(readlink -f "$g_tmpdir_dirlink")
            tmpdir_uid_mode=$(stat --format "%u:%a" "$tmpdir_abs")
            if [[ $tmpdir_uid_mode =~ ${g_runuid}:[0-7]?755$ ]] ; then
                chmod -R go-rwx "$tmpdir_abs"
            fi

            local db_datadir_abs;
            local db_datadir_uid_mode;
            db_datadir_abs=$(readlink -f "$g_dbdatadir_dirlink")
            if [[ x"$db_datadir_abs" == x"$g_userdata_dirabs/db_datadir.default" ]] ; then
                db_datadir_uid_mode=$(stat --format "%u:%a" "$db_datadir_abs")
                if [[ $db_datadir_uid_mode =~ ${g_runuid}:[0-7]?755$ ]] ; then
                    chmod go-rwx "$db_datadir_abs"
                fi
            fi
        fi
    fi
    # FIXME: --- Remove this after all sites upgraded to >7.0.1 (end) ---

    # Fix up .../generated/config permissions, so that users other than the
    # smrtlink install user, can acces the jms configuration files and use
    # them to submit jobs.
    # Basically allow all users to execute in directories under 'generated'
    # and make the config files readable.  So other users can read the config
    # files and use them when invoking runjmscmd, but should not have any
    # other access.
    if [[ -e "$g_generated_dir" ]]; then
        find "$g_generated_dir" -type d -exec chmod 0711 {} \;
    fi
    if [[ -e "$g_generated_config_dir" ]]; then
        local dir;
        for dir in $(find "$g_generated_config_dir" -name computecfg\*); do
            find "$dir" -type f -exec chmod 0644 {} \;
        done
    fi
}


upgrade_versionnum_migrationpath_check() {
    local current_versionnum_short=$g_versionnum_short;

    # Check to make sure that we are are at the expected x.y version number.
    # If not, the upgrade migration paths probably need updating.
    # Any update to the major.minor version number should be accompanied
    # by a change to this function (since we no longer want to support
    # skip upgrades).
    if [[ ! $current_versionnum_short =~ ^9\.0\.0$ ]] ; then
        # Disallow any other upgrades for now.
        # We should reconsider this once we know the details of upgrades to 6.x
        # or beyond
        echo
        echo "Version '$current_versionnum_short' not currently supported by installer."
        echo "Internal Error!  Unrecognized current version ($current_versionnum_short)"
        echo "                 upgrade_versionnum_migrationpath_check() needs to be updated."
        echo
        abort;
    fi

    if ! $opt_upgrade; then
        return 0;
    fi

    # This assignment must come here, after the upgrade check to make sure
    # that the prior version is properly set (it will not be set for install,
    # reconfig and restore actions).
    local prior_versionnum_short=$g_prior_versionnum_short;

    # Make sure we are upgrading via the expected upgrade path, i.e.
    #
    #    3.0.0 -> 3.0.1 -> 3.0.x -> 3.1.0 -> ...
    #
    # Not sure what our upgrade path will be or if we will support
    # skipping upgrades.  But we can enforce whatever restrictions we
    # want to impose here.

    # Sanity check that we are only dealing with three-field version numbers
    if [[ ! $prior_versionnum_short =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
        echo "Upgrading from version '$prior_versionnum_short' is not supported."
        echo "Unrecognized version string ($prior_versionnum_short), expected three digit-only fields, x.y.z"
        echo
        abort
    fi
    if [[ ! $current_versionnum_short =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
        echo "Upgrading to version '$current_versionnum_short' is not supported."
        echo "Unrecognized version string ($current_versionnum_short), expected three digit-only fields, x.y.z"
        echo
        abort
    fi

    # Do not allow any downgrading at all.
    local cmpret;
    cmpret=$(compare_versions "$prior_versionnum_short" "$current_versionnum_short")
    if [[ x"$cmpret" == x"+" ]] ; then
        echo "Downgrading version '$prior_versionnum_short' to '$current_versionnum_short' is not supported."
        echo "Downgrades are not supported."
        echo
        abort;
    fi


    # We are not allowing upgrades from any 1.x or 2.x installations.
    if [[ $prior_versionnum_short =~ ^[12]\. ]] ; then
        echo "Upgrading from version '$prior_versionnum_short' to '$current_versionnum_short' is not supported."
        echo "Previous release must not be a 1.x.x or 2.x.x release."
        echo
        abort;
    fi

    # Sanity check that we are only upgrading to a 9.x release.  Update as
    # necessary when we get to 10.x.
    if [[ ! $current_versionnum_short =~ ^[9]\. ]] ; then
        echo "Upgrading to version '$current_versionnum_short' is not supported."
        echo "Internal Error!  Current release must be a 9.x.x release."
        echo
        abort;
    fi

    # We may not support skip upgrades anymore (requiring that customers stop
    # at each version when upgrading).  Though if we wanted to support skip
    # upgrades going forward, it should technically be possible as long as
    # the database schema upgrades are handled properly.
    #
    # Supported upgrade paths:
    #   3.x.x -> 3.x.x
    #   3.x.x -> 4.0.x
    #   4.0.x -> 4.0.x
    #   4.0.x -> 5.0.x
    #   4.1.x -> 5.0.x (4.1.0 never released, internal development only)
    #   5.0.0 -> 5.0.1
    #   5.0.x -> 5.1.x (5.0.0->5.1.0 or 5.0.1->5.1.0)
    #   5.1.x -> 5.1.x
    #   5.1.x -> 5.2.x (5.2.0 never released, internal development only)
    #   5.2.x -> 5.2.x (5.2.0 never released, internal development only)
    #   --
    #   5.1.x -> 6.0.x
    #   5.2.x -> 6.0.x (5.2.0 never released, internal development only)
    #   6.0.x -> 6.0.x
    #   --
    #   6.0.x -> 6.1.x (6.1.0 never released, internal development/ea only)
    #   6.1.x -> 6.1.x
    #   --
    #   6.0.x -> 7.0.x
    #   6.1.x -> 7.0.x
    #   7.0.x -> 7.0.x
    #   --
    #   6.0.x -> 7.1.x
    #   6.1.x -> 7.1.x (6.1.0 never released, internal development/ea only)
    #   7.0.x -> 7.1.x
    #   7.1.x -> 7.1.x
    #   --
    #   6.0.x -> 8.0.x
    #   6.1.x -> 8.0.x (6.1.0 never released, internal development/ea only)
    #   7.0.x -> 8.0.x
    #   7.1.x -> 8.0.x
    #   8.0.x -> 8.0.x
    #   --
    #   6.0.x -> 8.1.x
    #   6.1.x -> 8.1.x (6.1.0 never released, internal development/ea only)
    #   7.0.x -> 8.1.x
    #   7.1.x -> 8.1.x
    #   8.0.x -> 8.1.x
    #   8.1.x -> 8.1.x
    #   --
    #   8.0.x -> 9.0.x
    #   8.1.x -> 9.0.x (8.1.0 never released, internal development only)
    #   9.0.x -> 9.0.x
    if [[ $current_versionnum_short =~ ^3\. ]] ; then
        # Any 3.x->3.x upgrades are allowed
        if [[ ! $prior_versionnum_short =~ ^3\. ]] ; then
            echo "Upgrading from version '$prior_versionnum_short' to '$current_versionnum_short' is not supported."
            echo "Upgrades to '$current_versionnum_short' must be from some earlier 3.x version"
            echo
            abort;
        fi
    elif [[ $current_versionnum_short =~ ^4\.0\. ]] ; then
        # Any 3.x->4.0 or 4.0->4.0 upgrades are allowed
        if [[ ! $prior_versionnum_short =~ ^(3|4\.0)\. ]] ; then
            echo "Upgrading from version '$prior_versionnum_short' to '$current_versionnum_short' is not supported."
            echo "Upgrades to '$current_versionnum_short' must be from 4.0 or some earlier 3.x version"
            echo
            abort;
        fi
    elif [[ $current_versionnum_short =~ ^5\.0\. ]] ; then
        # Only 4.0->5.0, 4.1(internal)->5.0, and 5.0->5.0 upgrades are allowed
        if [[ ! $prior_versionnum_short =~ ^(4\.[01]|5\.0)\. ]] ; then
            echo "Upgrading from version '$prior_versionnum_short' to '$current_versionnum_short' is not supported."
            echo "Upgrades to '$current_versionnum_short' must be from a 4.0 or 5.0 version"
            echo
            abort;
        fi
    elif [[ $current_versionnum_short =~ ^5\.1\. ]] ; then
        # Only 5.0.->5.1 (i.e. 5.0.[01]->5.1.1), and 5.1->5.1 upgrades allowed
        if [[ ! $prior_versionnum_short =~ ^(5\.[01])\. ]] ; then
            echo "Upgrading from version '$prior_versionnum_short' to '$current_versionnum_short' is not supported."
            echo "Upgrades to '$current_versionnum_short' must be from a 5.0 or 5.1 version"
            echo
            abort;
        fi
    elif [[ $current_versionnum_short =~ ^5\.2\. ]] ; then
        # Only 5.1.->5.2 and 5.2->5.2 upgrades allowed
        if [[ ! $prior_versionnum_short =~ ^(5\.[12])\. ]] ; then
            echo "Upgrading from version '$prior_versionnum_short' to '$current_versionnum_short' is not supported."
            echo "Upgrades to '$current_versionnum_short' must be from a 5.1 or 5.2 version"
            echo
            abort;
        fi
    elif [[ $current_versionnum_short =~ ^6\.0\. ]] ; then
        # Only 5.1->6.0, 5.2(internal)->6.0, and 6.0->6.0 upgrades are allowed
        if [[ ! $prior_versionnum_short =~ ^(5\.[12]|6\.0)\. ]] ; then
            echo "Upgrading from version '$prior_versionnum_short' to '$current_versionnum_short' is not supported."
            echo "Upgrades to '$current_versionnum_short' must be from a 5.1 or 6.0 version"
            echo
            abort;
        fi
    elif [[ $current_versionnum_short =~ ^6\.1\. ]] ; then
        # Only 6.0->6.0, and 6.1->6.1 upgrades are allowed
        if [[ ! $prior_versionnum_short =~ ^(6\.[01])\. ]] ; then
            echo "Upgrading from version '$prior_versionnum_short' to '$current_versionnum_short' is not supported."
            echo "Upgrades to '$current_versionnum_short' must be from a 6.0 or 6.1 version"
            echo
            abort;
        fi
    elif [[ $current_versionnum_short =~ ^7\.0\. ]] ; then
        # Only 6.0->7.0, 6.1->7.0 and 7.0->7.0 upgrades are allowed
        if [[ ! $prior_versionnum_short =~ ^(6\.[01]|7\.0)\. ]] ; then
            echo "Upgrading from version '$prior_versionnum_short' to '$current_versionnum_short' is not supported."
            echo "Upgrades to '$current_versionnum_short' must be from a 6.0, 6.1 or 7.0 version"
            echo
            abort;
        fi
    elif [[ $current_versionnum_short =~ ^7\.1\. ]] ; then
        # Only 6.0->7.0, 6.1->7.0, 7.0->7.1 and 7.1->7.1 upgrades are allowed
        if [[ ! $prior_versionnum_short =~ ^(6\.[01]|7\.[01])\. ]] ; then
            echo "Upgrading from version '$prior_versionnum_short' to '$current_versionnum_short' is not supported."
            echo "Upgrades to '$current_versionnum_short' must be from a 6.0, 6.1, 7.0 or 7.1 version"
            echo
            abort;
        fi
    elif [[ $current_versionnum_short =~ ^8\.0\. ]] ; then
        # Only 6.0->8.0, 6.1->8.0, 7.0->8.0, 7.1->8.0, and 8.0->8.0 upgrades
        # are allowed
        if [[ ! $prior_versionnum_short =~ ^(6\.[01]|7\.[01]|8\.[0])\. ]] ; then
            echo "Upgrading from version '$prior_versionnum_short' to '$current_versionnum_short' is not supported."
            echo "Upgrades to '$current_versionnum_short' must be from a 6.0, 6.1, 7.0, 7.1 or 8.0 version"
            echo
            abort;
        fi
    elif [[ $current_versionnum_short =~ ^8\.1\. ]] ; then
        # Only 6.0->8.1, 6.1->8.1, 7.0->8.1, 7.1->8.1, 8.0->8.1 and
        # 8.1->8.1 upgrades are allowed
        if [[ ! $prior_versionnum_short =~ ^(6\.[01]|7\.[01]|8\.[01])\. ]] ; then
            echo "Upgrading from version '$prior_versionnum_short' to '$current_versionnum_short' is not supported."
            echo "Upgrades to '$current_versionnum_short' must be from a 6.0, 6.1, 7.0, 7.1, 8.0 or 8.1 version"
            echo
            abort;
        fi
    elif [[ $current_versionnum_short =~ ^9\.0\. ]] ; then
        # Only 8.0->9.0, 8.1->9.0 and 9.0->9.0 upgrades are allowed
        if [[ ! $prior_versionnum_short =~ ^(8\.[01]|9\.[0])\. ]] ; then
            echo "Upgrading from version '$prior_versionnum_short' to '$current_versionnum_short' is not supported."
            echo "Upgrades to '$current_versionnum_short' must be from a 8.0, 81 or 9.0 version"
            echo
            abort;
        fi
    else
        # Disallow any other upgrades for now.
        # We should reconsider this once we know the details of upgrades to 9.x
        echo "Upgrading to '$current_versionnum_short' not currently supported."
        echo "Internal Error!  Unrecognized current version ($current_versionnum_short)"
        echo
        abort;

    fi
}

upgrade_prompt_user() {
    next_screen;

    $opt_batch || cat <<-EOF

	----- Part 1 of 10: SMRT Analysis Upgrade Checks ----

	EOF


    echo "Checking current vs prior install user..."

    # We are not going to deal with any of the group permissions possibilities
    # yet.
    #
    # Theoretically, we should be able to deal with setting the group on
    # all the files that we create (default group, setting group ownership
    # to some group we belong to, or via the setgid bit, etc). But we are
    # not really dealing with it in the install, and we are not well set
    # up to handle it in the upgrade (e.g. it is possible to have the install
    # files be owned by a group we don't even belong to, if the setgid bit
    # was set before installation).  Ignore the group settings for now until
    # we have a reason to deal with it and a good model for controlling the
    # group settings in the install and upgrade.
    echo
    echo "  The existing setting for user is:  $g_prior_user (uid: $g_prior_uid)"
    echo "  The current user (that invoked this program) is:  $g_runuser"
    echo

    if [[ $g_runuid -eq 0 ]] ; then
        echo
        echo "$g_actionstr_ing as 'root' is not currently supported"
        echo "Switch to the desired user and restart the $g_actionstr_lc."
        abort;
    fi

    if [[ x"$g_runuser" != x"$g_prior_user" ]] ; then
        echo "Current user '$g_runuser' does not match existing user '$g_prior_user'."
        echo "To keep existing user, abort this $g_actionstr_lc (answer 'n' below) and"
        echo "reinvoke the $g_actionstr_lc as the '$g_prior_user' user."
        echo

        local ans;
        if $opt_batch; then
            ans=n;
        else
            ans=$(yesno_prompt "$g_actionstr using  new '$g_runuser' smrtanalysis user" false)
        fi
        if [[ x"$ans" != x"y" ]] ; then
            echo "Switch to desired user and reinvoke the $g_actionstr_lc."
            abort;
        fi
    fi

    # Set config variables
    # Always set the install__user and install__group to the current
    # user/group.  These are basically unused in the config file anyway,
    # since we detect the previous user from the file owner above.
    set_cfg_val "install" "user" "$g_runuser" "computed default" false;
    set_cfg_val "install" "group" "$g_rungroup" "computed default" false;
}

upgrade_prompt_killservices() {

    # FIXME: cromwell: Se should kill cromwell here if it is still running

    if $opt_no_daemon_kill; then
        echo "Skipping check for running GUI Webserver and SMRTLink Service daemons..."
        return 0
    fi

    echo "Checking for running SMRT Link and SMRT View Webserver and Service daemons..."

    local pid;

    local priordir_abs;
    priordir_abs=$(readlink -f "$g_priordir")

    # prior java executable regex
    local prior_javaexe_re="^$priordir_abs/[^[:space:]]*/bin/java( \(deleted\))?\$"

    # prior smrtlink services cmdline regex
    local prior_smrtlinkservices_cmdline_re="[[:space:]]-cp[[:space:]]+$priordir_abs/[^[:space:]]*/tools/lib/\*[[:space:]].*[[:space:]]com\.pacbio\.secondary\.smrtlink\.app\.SmrtLinkSmrtServer[[:space:]]"

    # prior smrtlink and wso2 catalina basedir regex
    local prior_smrtlinkgui_catalinabasedir_re="^$priordir_abs/[^[:space:]]*/smrtlink-analysisservices-gui/[^/]*tomcat[^/]*\$"
    local prior_smrtlinkwso2am_catalinabasedir_re="^$priordir_abs/[^[:space:]]*/wso2am-[^\]*/.*/[^/]*tomcat[^/]*\$";

    # Get pids from priordir pidfiles
    local prior_smrtlinkgui_pidfile_pid;
    local prior_smrtlinkservices_pidfile_pid;
    local prior_smrtlinkwso2am_pidfile_pid;
    prior_smrtlinkgui_pidfile_pid=$(sed -ne '1{s/^\([0-9]\+\).*/\1/p}'  "$g_priordir/rwdir/run/smrtlink_gui.pid" 2> /dev/null || true)
    prior_smrtlinkservices_pidfile_pid=$(sed -ne '1{s/^\([0-9]\+\).*/\1/p}'  "$g_priordir/bundles/smrtlink-analysisservices-gui/current/private/pacbio/smrtlink-analysisservices-gui/sl-analysis.pid" 2> /dev/null || true)
    prior_smrtlinkwso2am_pidfile_pid=$(sed -ne '1{s/^\([0-9]\+\).*/\1/p}'  "$g_priordir/bundles/smrtlink-analysisservices-gui/current/private/pacbio/smrtlink-analysisservices-gui/wso2am-2.0.0/wso2carbon.pid" 2> /dev/null || true)

    local java_pids;
    java_pids=$( ps ww -o pid,comm --no-headers -u "$g_prior_user" -U "$g_prior_user" | grep java | awk '{print $1}' || true );


    local pid_psstr;
    local pid_javaexe;
    local pid_javaexe_abs;
    local pid_catalinabasedir;
    local pid_catalinabasedir_abs;
    local pid_jarfile
    local smrtlinkgui_pids="";
    local smrtlinkservices_pids="";
    local smrtlinkwso2am_pids="";
    for pid in $java_pids; do

        # Check if this pid match the pid in any of the pid files we know about
        if [[ x"$pid" == x"$prior_smrtlinkgui_pidfile_pid" ]] ; then
            smrtlinkgui_pids="$smrtlinkgui_pids $pid"
            continue;
        elif [[ x"$pid" == x"$prior_smrtlinkservices_pidfile_pid" ]] ; then
            smrtlinkservices_pids="$smrtlinkservices_pids $pid"
            continue;
        elif [[ x"$pid" == x"$prior_smrtlinkwso2am_pidfile_pid" ]] ; then
            smrtlinkwso2am_pids="$smrtlinkwso2am_pids $pid"
            continue;
        fi
        # We'll only get beyond here if there are java pids that aren't in
        # our pid files.  Check to make sure that they are java instances
        # that we expect to be running out of our install tree.

        # Check to see if the java executable is ours
        pid_javaexe_abs=""
        if [[ -e "/proc/$pid/exe" ]] ; then
            pid_javaexe_abs=$( readlink -m "/proc/$pid/exe" || true );
            if  [[ ! $pid_javaexe_abs =~ $prior_javaexe_re ]]; then
                # This is not the specific version of java from the prior
                # install dir
                continue;
            fi
        fi

        # Get the (perhaps truncated to 4096 char) command line for the pid
        pid_psstr=$(ps ww -p "$pid" -o args --no-headers || true);

        # Check to see if it looks like our smrtlink services command line
        # Note that this may be invoke as just "java" without a full path
        # to java.  So if the /proc/$pid/exe link didn't exist above for some
        # reason, we will not check the java exe path (just the classpath
        # and class name)
        if [[ $pid_psstr =~ $prior_smrtlinkservices_cmdline_re ]] ; then
            smrtlinkservices_pids="$smrtlinkservices_pids $pid"
            continue;
        fi

        if [[ -z "$pid_javaexe_abs" ]]; then
            # Check the java path (from ps, since the /proc/$pid/exe link
            # from above doesn't exist) is a full path pointing to one our
            # java exes.  Find the cmdname (first arg of the ps string)
            pid_javaexe=$( echo "$pid_psstr" | sed -ne '1{ s|^\([^[:space:]]*\).*|\1|p }' )
            pid_javaexe_abs=$(readlink -m "$pid_javaexe" || true);
            if  [[ ! $pid_javaexe_abs =~ $prior_javaexe_re ]]; then
                # This is not the specific version of java from the prior
                # install dir
                continue;
            fi
        fi

        # Check to see if they look like any of our smrtlinkgui,
        # smrtlinkwso2am, or smrtlink tomcat command lines (perhaps a running
        # process from a previously removed installation, since it didn't
        # match a pid file)
        pid_catalinabasedir=$(echo "$pid_psstr" | sed -ne 's/.*[[:space:]]-Dcatalina.base=\([^[:space:]]\+\).*/\1/p')
        if [[ ! -z "$pid_catalinabasedir" ]] ; then
            # This looks like it may be one of our tomcat processes, determine
            # if so and which one.
            pid_catalinabasedir_abs=$(readlink -f "$pid_catalinabasedir")
            if [[ $pid_catalinabasedir_abs =~ $prior_smrtlinkgui_catalinabasedir_re ]] ; then
                smrtlinkgui_pids="$smrtlinkgui_pids $pid"
                continue
            elif [[ $pid_catalinabasedir_abs =~ $prior_smrtlinkwso2am_catalinabasedir_re ]] ; then
                smrtlinkwso2am_pids="$smrtlinkwso2am_pids $pid"
                continue
            fi
        fi

    done

    if [[ -z "${smrtlinkgui_pids}${smrtlinkwso2am_pids}${smrtlinkservices_pids}" ]] ; then
        echo
        echo "  No running webservers or service processes found.  Continuing..."
        echo
    else
        echo
        echo "  Found running processes from previous installation:"
        echo
        if [[ ! -z "$smrtlinkgui_pids" ]] ; then
            echo "      SMRT Link GUI webserver       (pid: $smrtlinkgui_pids)";
        fi
        if [[ ! -z "$smrtlinkservices_pids" ]] ; then
            echo "      SMRT Link Services webserver  (pid: $smrtlinkservices_pids)";
        fi
        if [[ ! -z "$smrtlinkwso2am_pids" ]] ; then
            echo "      SMRT Link wso2am webserver    (pid: $smrtlinkwso2am_pids)";
        fi
        echo
        echo "  Install cannot continue with these processes running."

        local ans;
        if $opt_batch; then
            ans="y"
        else
            echo
            ans=$(yesno_prompt "  Kill these processes?" false)
            echo
        fi

        if [[ x"$ans" != x"y" ]] ; then
            abort;
        fi

        for pid in $smrtlinkgui_pids; do
            echo "    Killing SMRT Link GUI webserver, process '$pid'..."
            stat=0;
            kill -TERM "$pid" || stat=$?
            if [[ $stat   -eq 0 ]]; then
                echo "      Process '$pid' killed."
            else
                merror "Could not kill SMRT Link GUI webserver, process '$pid', aborting install..."
            fi
            # FIXME: make sure the process is no longer running (check with ps)
        done

        for pid in $smrtlinkservices_pids; do
            echo "    Killing SMRT Link Services webserver, process '$pid'..."
            stat=0;
            kill -TERM "$pid" || stat=$?
            if [[ $stat   -eq 0 ]]; then
                echo "      Process '$pid' killed."
                # Hack to remove the sl-analysis.pid file
                rm -f "$g_priordir/bundles/smrtlink-analysisservices-gui/current/private/pacbio/smrtlink-analysisservices-gui/sl-analysis.pid"
            else
                merror "Could not kill SMRT Link Services webserver, process '$pid', aborting install..."
            fi
            # FIXME: make sure the process is no longer running (check with ps)
        done

        for pid in $smrtlinkwso2am_pids; do
            echo "    Killing SMRT Link wso2am webserver, process '$pid'..."
            stat=0;
            kill -TERM "$pid" || stat=$?
            if [[ $stat   -eq 0 ]]; then
                echo "      Process '$pid' killed."
            else
                merror "Could not kill SMRT Link wso2am webserver, process '$pid', aborting install..."
            fi
            # FIXME: make sure the process is no longer running (check with ps)
        done

        echo
    fi
}


print_priorsettings_group() {
    local group=$1; shift;
    local b_listgroup=$1; shift;
    local b_multiplecfgs=$1; shift;

    # Initialize the return list of missing settings
    local -a missing_settings=()

    # Print a new line between groups
    echo ""

    local i;
    for ((i=0; i < ${#ga_cfg_groupnames[@]} ; i++)); do
        local lgroup="${ga_cfg_groupnames[$i]%%:*}"
        if [[ x"$lgroup" != x"$group" ]] ; then
            continue
        fi

        local settingname="${g_cfg_settingname[$i]}"
        if [[ x"$settingname" == x"__NO_SETTINGNAME__" ]] ; then
            continue;
        fi

        # Add identifier to listgroups
        if $b_listgroup && $b_multiplecfgs; then
            settingname="($group) $settingname"
        fi

        local name="${ga_cfg_groupnames[$i]#*:}"
        local optional="${g_cfg_optional[$i]}"
        local valraw="${g_cfg_curval[$i]}"

        local val;
        val=$(get_cfg_val "$group" "$name");

        # Note: we no longer special case the optional jms settings and make
        #       them non-optional for the particular jmstype, we will just
        #       treat them all as optional.  The only downside of this is
        #       if there are new jms settings defined, the user may be
        #       prompted with a default of 'Y' for the "Keep all settings"
        #       question.
        if isset_cfgval "$valraw" ; then
            if [[ x"$valraw" == x"__DEFAULT__" ]] ; then
                printf "    %-35s %-20s (default)\n" "$settingname:" "$val"
            else
                printf "    %-35s $val\n" "$settingname:"
            fi
        else
            if ! $optional; then
                missing_settings+=( "$settingname" );
            fi
        fi
    done

    gret__print_priorsettings_group__missing_settings=( "${missing_settings[@]+${missing_settings[@]}}" );
}

upgrade_prompt_priorsettings() {
    echo "Existing Settings:";

    local -a missing_settings=();

    local i;
    if [[ ! -z "${ga_cfgg_groups[@]+${ga_cfgg_groups[@]}}" ]]; then
        for ((i=0; i < ${#ga_cfgg_groups[@]} ; i++)); do
            if ${g_cfgg_listgroup[$i]}; then
                # Do not print the listgroup settings (we print the list of
                # groups for each basegroup instead)
                :
            elif ${g_cfgg_basegroup[$i]}; then
                local j
                local multiplecfgs=false;
                if [[ ${g_cfgg_basegroup_listidxs[$i]} =~ ^[[:space:]]*[^[:space:]]+[[:space:]]+[^[:space:]]+[[:space:]]*$ ]]; then
                    # We have more than one index
                    multiplecfgs=true;
                fi
                for j in ${g_cfgg_basegroup_listidxs[$i]}; do
                    if ! ${g_cfgg_savegroup[$j]}; then
                        contninue;
                    fi
                    print_priorsettings_group "${ga_cfgg_groups[$j]}" true "$multiplecfgs"
                    missing_settings+=( "${gret__print_priorsettings_group__missing_settings[@]+${gret__print_priorsettings_group__missing_settings[@]}}" )
                done
            else
                print_priorsettings_group "${ga_cfgg_groups[$i]}" false false;
                missing_settings+=( "${gret__print_priorsettings_group__missing_settings[@]+${gret__print_priorsettings_group__missing_settings[@]}}" )
            fi
        done
    fi

    # Determine if we should keep all the settings (and avoid prompting the
    # user for any of them), or if we should prompt the user for all of
    # them.  No just prompting for missing ones at the moment.
    local keep_all_settings=false;
    local settingname;
    echo
    if [[ ! -z "${missing_settings[@]+${missing_settings[@]}}" ]] ; then
        echo "Missing the following settings:"
        echo
        for settingname in "${missing_settings[@]}" ; do
            echo "    $settingname";
        done
        echo

        local ans;
        if $opt_batch; then
            ans="y";
        else
            ans=$(yesno_prompt "Configure settings?" false)
        fi
        if [[ x"$ans" != x"y" ]] ; then
            abort;
        fi

    else
        local ans;
        if $opt_batch; then
            ans="y";
        else
            ans=$(yesno_prompt "Keep all existing settings?" false)
        fi
        if [[ x"$ans" == x"y" ]]; then
            keep_all_settings=true;
        fi
    fi
    g_keep_all_settings=$keep_all_settings;
}


# JSON Data Checking Functions
#
# The valid value types for json (according to json.org) are:
#    string
#    number
#    true
#    false
#    null
#    object
#    array
#
# These functions check to make sure the data we are sending to config.json
# are in the format that we expect them.  We currently only expect to use
# double quoted strings, null, positive integers, and booleans (true/false)

# Escape the characters in the user input for a json string.
#
# From the JSON spec:
#   http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
#
#   A string is a sequence of Unicode code points wrapped with quotation
#   marks (U+0022). All characters may be placed within the quotation marks
#   except for the characters that must be  escaped: quotation mark (U+0022),
#   reverse  solidus  (U+005C),  and  the  control  characters  U+0000 to
#   U+001F.
#
# Basically, double quotes, backslash and control characters must be quoted,
# but other characters may be optionally quoted.
#
# Note, the documentation on http://json.org is not quite as clear that the
# escaping of other characters is optional.
#
# These characters must be escaped:
#    "      quotation mark
#    \      reverse solidus
#    uDDDD  control characters between U+0000 and U+001F
#
# The following may be represented as two character escape sequences:
#    \"      quotation mark
#    \\      reverse solidus
#    \/      solidus
#    \b      backspace
#    \f      formfeed
#    \n      newline
#    \r      carriage return
#    \t      horizontal tab
#
# Any other character may be represented as a hexidecimal number according to
# ISO/IEC 10646 (in the form \uDDDD):
#    \uDDDD  unicode character
#
# We do not expect get input with newline, unicode or other control
# characters from the user. So we will only escape the backslash, double
# quotes and the other control characters we recognize.
# Note that this does not support conversion of control characters we don't
# recognize to the \uDDDD format.
json_escape_string() {
    local val=$1; shift;

    local retval;
    retval=$(echo "$val" | \
             sed -e 's,\\,\\\\,g' \
                 -e 's,\",\\",g' \
                 -e 's,\t,\\t,g' \
                 -e 's,\r,\\r,g' \
                 -e 's,\f,\\f,g');

    echo "$retval";
}

# null if empty string, double quoted string otherwise
json_strnull() {
    local val=$1; shift;

    local retval;
    if [[ -z "${val}" ]] ; then
        # Return a naked 'null' if we have the empty string
        retval="null"
    else
        retval=$(json_escape_string "$val");

        # If not empty, return a quoted string
        retval="\"$retval\""
    fi

    echo "$retval";
}

# expect empty or non-empty string
json_strempty() {
    local val=$1; shift;

    local retval;
    retval=$(json_escape_string "$val");

    # Return a double quoted string
    retval="\"$retval\""
    echo "$retval";
}

# expect non-empty string
json_str() {
    local val=$1; shift;

    if [[ -z "${val}" ]] ; then
        minterror "json_str(): unexpected value ($val), expected non-empty string"
    fi

    local retval;
    retval=$(json_escape_string "$val");

    # Return a double quoted string
    retval="\"$retval\""
    echo "$retval";
}

# expect positive integer, issue internal error otherwise
json_posint() {
    local val=$1; shift;

    if [[ ! $val =~ ^[0-9]+$ ]] ; then
        minterror "json_posint(): unexpected value ($val), expected a positive integer (digits only)"
        return 1
    fi

    # Return as an unquoted integer
    local retval="$val"
    echo "$retval";
}

# expect boolean ('true' or 'false'), error otherwise
json_boolean() {
    local val=$1; shift;

    if [[ ! $val =~ ^(true|false) ]] ; then
        minterror "json_boolean(): unexpected value ($val), expected a boolean ('true' or 'false')"
    fi

    # Return as an unquoted 'true' or 'false' value
    local retval="$val"
    echo "$retval";
}

# Smrttools only functions

extract_smrttools_only() {
    # Do not extract if --reconfig arg was given
    $opt_reconfig && return 0

    # Skip extracting in testmode
    $opt_testmode && return 0;

    if ! $opt_no_extract; then
        echo "Extracting bundles..."
    fi

    local bundlename="smrttools"
    local bundledir="$g_topdir/bundles"

    # Remove all the bundle tarballs we don't need
    local bundle;
    for bundle in "$g_topdir/private/bundles/"*; do
        if [[ ! $bundle =~ /${bundlename}$ ]] ; then
            rm -rf "$bundle"
        fi
    done

    # Extract bundles with group/other access
    umask 0022

    if ! $opt_no_extract || [[ ! -e "$bundledir/$bundlename" ]] ; then
        # Extract if the rootdir doesn't exist or if --no-extract not specified
        echo "  Extracting smrttools bundle...."
        "$g_topdir/private/bundles/$bundlename/$bundlename"*_*.run --rootdir "$bundledir/$bundlename" --batch
    fi

    # Create the toplevel smrtcmds link
    rm -f "$opt_rootdir/smrtcmds";
    ln -s "current/bundles/smrttools/smrtcmds" "$opt_rootdir/smrtcmds";

    # Turn off group/other access for any new files/dirs created
    umask 0077

    # Inform the caller of success (will move the 'new' link to 'current', in
    # smrt_reinstall or smrt_reupgrade)
    if [[ ! -z "$SMRT_SUCCESS_FILE" ]] ; then
        touch "$SMRT_SUCCESS_FILE"
    fi

    echo
    echo "SMRT Link $g_actionstr successful."

    logfile_shutdown;
    exit 0;
}

set_smrtlink_env() {
   if [[ ! -z "${LC_ALL+set}" ]] ; then
       _SMRTORIGENV__LC_ALL="$LC_ALL";
   fi
   if [[ ! -z "${LANG+set}" ]] ; then
       _SMRTORIGENV__LANG="$LANG";
   fi
   if [[ ! -z "${LANGUAGE+set}" ]] ; then
       _SMRTORIGENV__LANGUAGE="$LANGUAGE";
   fi

   # NOTE: the LC_ALL= assignment may generate a setlocale error onto stderr.
   #       This is an indication that the locale is not available on the system
   #       and should generate installer errors below in the locale check
   #       Example output:
   #
   #           bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8): No such file or directory
   #

   # Load the locale from globalenv.ish (though we do assume it it en_US.UTF-8
   # in the check_locale() subroutine below)

   # ---- global env

   . "$g_progdir_abs/../../private/runtime-common/lib/globalenv.ish"
}

check_locale() {
    local exp_locale="en_US.UTF-8"
    local exp_languagevar="en_US:en:C"
    local exp_charmap="UTF-8"

    local error=false;
    local warn=false;
    local errorstr=""
    local warnstr=""

    local nl=$'\n'

    if [[ x"$LC_ALL" != x"$exp_locale" ]] ; then
        minterror "LC_ALL value ($LC_ALL) does not match expected value '$exp_locale'"
    fi
    if [[ x"$LANG" != x"$exp_locale" ]] ; then
        minterror "LANG value ($LANG) does not match expected value '$exp_locale'"
    fi
    if [[ x"$LANGUAGE" != x"$exp_languagevar" ]] ; then
        minterror "LANG value ($LANGUAGE) does not match expected value '$exp_languagevar'"
    fi

    # If locale gives any messages on stderr, assume that the locale we
    # specified is not supported by the system.
    # NOTE: We expect LC_ALL, LANG and LANGUAGE to be set above to our
    #       expected values.
    local badlocale;
    badlocale=$(bash -c 'LC_ALL=$LC_ALL' 2>&1)
    if [[ ! -z "$badlocale" ]] ; then
        errorstr+="      [ERR]  locale '$exp_locale' not supported, errors in setting LC_ALL${nl}"
        error=true;
    fi
    badlocale=$((locale > /dev/null) 2>&1)
    if [[ ! -z "$badlocale" ]] ; then
        errorstr+="      [ERR]  locale '$exp_locale' not supported, unexpected 'locale' errors${nl}"
        error=true;
    fi

    # The encoding part of the local (after the period) may be specified in
    # any case, and all hyphens are ignored.  So we should not assume how it
    # happens to show up in the 'locale -a' output (for example on a stock
    # ubuntu-1404 system, it shows up as 'en_US.utf8' even though it may
    # be more commonly specified as 'en_US.UTF-8'.  This regular expression
    # should match any of the possibilities we expect.
    local supportedlocale;
    supportedlocale=$(locale -a | sed -n -e '/en_US\.-*[uU]-*[tT]-*[fF]-*8-*/p')
    if [[ -z "$supportedlocale" ]] ; then
        warnstr+="      [WARN] 'locale -a', supported system locales does not include '$exp_locale'${nl}"
        warn=true;
    fi

    # Sanity check the locale variable settings
    local locale_out;
    locale_unexpected=$(locale | grep -F -v "=\"$exp_locale\"" | grep -F -v "=$exp_locale" | grep -F -v "LANGUAGE=$exp_languagevar" || true)
    if [[ ! -z "$locale_unexpected" ]] ; then
        warnstr+="      [WARN] 'locale', unexpected locale variable settings ($locale_unexpected)${nl}"
        warn=true;
    fi

    # Sanity check the contents of the locale keys to make sure they match
    # our expectations for en_US.UTF-8.
    local charmap;
    charmap=$(locale -k LC_CTYPE | sed -ne 's/charmap="\?\([^"]*\)"\?/\1/p')

    if [[ x"$charmap" != x"$exp_charmap" ]] ; then
        warnstr+="      [WARN] 'locale -k LC_TYPE', 'charmap' key (act: $charmap, exp: $exp_charmap)${nl}"
        warn=true;
    fi

    if $error || $warn ; then
        echo "'locale' command output:" >> "$g_logfile" 2>&1
        locale >> "$g_logfile" 2>&1
        echo "" >> "$g_logfile" 2>&1

        echo "'locale -a' command output:" >> "$g_logfile" 2>&1
        locale -a >> "$g_logfile" 2>&1
        echo "" >> "$g_logfile" 2>&1

        echo "locale settings:" >> "$g_logfile" 2>&1
        for i in $(locale 2> /dev/null |sed -e 's/=.*//' |grep LC_ |grep -v LC_ALL); do    echo "# --- $i"; locale  -v -k "$i";done >> "$g_logfile" 2>&1
        echo "" >> "$g_logfile" 2>&1

        errorstr=${errorstr%${nl}}
        warnstr=${warnstr%${nl}}

        echo ""
        echo "    Unexpected locale settings detected:"
        if [[ ! -z "$errorstr" ]] ;then
            echo "$errorstr"
        fi
        if [[ ! -z "$warnstr" ]] ;then
            echo "$warnstr"
        fi
        if $error; then
            merror "Unexpected locale errors detected, exiting..."
        elif $warn; then
            echo "    Unexpected locale warnings detected, continuing..."
            echo ""
        fi
    fi
}

write_cromwell_config() {
    local listgroup=$1; shift

    local listgroupnum;
    listgroupnum=$(get_listgroupnum_from_listnum "${listgroup##*_}")

    local config_dir="$g_generated_dir_new/config/$listgroup";
    local cromwell_json="$config_dir/cromwell_${listgroupnum}.json"

    mkdir -p "$config_dir"
    print_cromwell_config "$listgroupnum" "$cromwell_json"
}

apply_computecfg_settings() {
    local computecfgs;
    computecfgs=$(get_computecfgs_by_menuorder);

    echo "  Applying JMS settings...."

    local computecfg
    local b_havedefault=false;
    local b_isdefault;
    local enabled;
    for computecfg in $computecfgs; do
        enabled=$(get_cfg_val "$computecfg" "enable")
        b_isdefault=false;
        if ! $b_havedefault && $enabled; then
            # First compute config (in menuorder) that is not disabled is
            # the default computecfg;
            b_isdefault=true;
            b_havedefault=true;
        fi

        echo "    Applying $computecfg settings..."
        write_cromwell_config "$computecfg";
        write_jms_config "$computecfg" $b_isdefault;
    done

    if ! $b_havedefault; then
        minterror "apply_computecfg_settings(): Could not find default computecfg";
    fi

    # Write user_jmsenv.ish
    write_user_jmsenv_config "$computecfgs";
}

set_computecfg_configfile_ignores() {
    local computecfg;

    local index
    for computecfg in $opt_computecfg_ignoreconfigfiles; do
        index=$(get_cfgg_index "$computecfg")
        g_cfgg_ignoreconfigfile[$index]=true;
    done
}

clone_computecfg() {
    local src_group=$1; shift;
    local dst_group=$1; shift;

    if [[ x"$src_group" == x"$dst_group" ]] ; then
        minterror "clone_computecfg(): src and dst group are identical: $src_group"
    fi

    local i;
    local index;
    local group;
    local name;
    for ((i=0; i < ${#ga_cfg_groupnames[@]} ; i++)); do
        group="${ga_cfg_groupnames[$i]%%:*}"
        if [[ x"$group" = x"$src_group" ]] ; then
            name="${ga_cfg_groupnames[$i]#*:}"
            if  [[ x"$name" == x"name" ]] ||
                [[ x"$name" == x"description" ]]; then
                continue;
            fi
            index=$(get_cfg_index "$dst_group" "$name");

            if [[ x"${g_cfg_curval[$index]}" == x"__UNSET__" ]] ; then
                g_cfg_datatype[$index]=${g_cfg_datatype[$i]};
                g_cfg_default[$index]=${g_cfg_default[$i]};
                g_cfg_optional[$index]=${g_cfg_optional[$i]};
                g_cfg_optstr_alts[$index]=${g_cfg_optstr_alts[$i]};
                g_cfg_optstr_typedstr[$index]=${g_cfg_optstr_typedstr[$i]};
                g_cfg_printstr_splen[$index]=${g_cfg_printstr_splen[$i]};
                g_cfg_specialcase[$index]=${g_cfg_specialcase[$i]};
                g_cfg_usagestr[$index]=${g_cfg_usagestr[$i]};
                g_cfg_promptstr[$index]=${g_cfg_promptstr[$i]};
                g_cfg_usingstr[$index]=${g_cfg_usingstr[$i]};
                g_cfg_settingname[$index]=${g_cfg_settingname[$i]};
                g_cfg_optstr[$index]=${g_cfg_optstr[$i]};
                g_cfg_curval[$index]=${g_cfg_curval[$i]};
                g_cfg_srcstr[$index]=${g_cfg_srcstr[$i]};
                g_cfg_usedefault[$index]=${g_cfg_usedefault[$i]};
                g_cfg_basegroup[$index]=${g_cfg_basegroup[$i]};
            fi
        fi
    done
}

modify_computecfg_settings() {
    local clone_info;
    local clone_src;
    local clone_dst;

    # Create any clones specified on the command line
    for clone_info in "${opt_computecfg_clonelist[@]+${opt_computecfg_clonelist[@]}}" ; do
        clone_src=${clone_info%:*};
        clone_dst=${clone_info##*:};
        clone_computecfg "$clone_src" "$clone_dst"
    done

    # Delete computecfg groups specified on the command line
    local group;
    local index;
    for group in "${opt_computecfg_deletelist[@]+${opt_computecfg_deletelist[@]}}"; do
        # Set g_cfgg_savegroup to false to delete the group (will not write
        # out to the config file or to any of the generated settings files)
        index=$(get_cfgg_index "$group")
        g_cfgg_savegroup[$index]=false;
    done

    # Reset computecfg groups specified on the command line
    # No need to do anything here, since the read of the settings for the
    # config file were already inhibited, the group is already reset (plus
    # any settings specified on the command line applied).
}

get_computecfgs_allenabled() {
    local basegroup="computecfg_NN";

    local i;
    local listgroups;
    if [[ ! -z "${ga_cfgg_groups[@]+${ga_cfgg_groups[@]}}" ]]; then
        for ((i=0; i < ${#ga_cfgg_groups[@]} ; i++)); do
            if [[ x"$basegroup" == x"${ga_cfgg_groups[$i]}" ]]; then
                local groupindex;
                local savegroup;
                listgroups=""
                for groupindex in ${g_cfgg_basegroup_listidxs[$i]}; do
                    savegroup="${g_cfgg_savegroup[$groupindex]}"
                    if ! $savegroup; then
                        # Ignore any deleted computecfgs
                        continue;
                    fi

                    listgroups+="${ga_cfgg_groups[$groupindex]} "
                done
            fi
        done
    fi

    echo "$listgroups"
}

get_computecfgs_by_menuorder() {
    local computecfgs;
    computecfgs=$(get_computecfgs_allenabled);

    local sorted_grouporder_str="";

    local group;
    local menuorder;
    local inserted;
    local tmpstr;
    for group in $computecfgs; do
        menuorder=$(get_cfg_val "$group" "menuorder")
        menuorder=$(trim_intnum "$menuorder")
        inserted=false;
        tmpstr=""
        for j in $sorted_grouporder_str; do
            local lmenuorder;
            local lgroup;

            lmenuorder=${j%%:*}
            lgroup=${j#*:}
            if $inserted; then
                tmpstr+=" $lmenuorder:$lgroup";
            elif [[ $lmenuorder -gt $menuorder ]] ; then
                tmpstr+=" ${menuorder}:${group} ${lmenuorder}:$lgroup";
                inserted=true;
            else
                tmpstr+=" $lmenuorder:$lgroup";
            fi
        done
        if ! $inserted; then
            tmpstr+=" $menuorder:$group"
        fi
        sorted_grouporder_str=$tmpstr;
    done

    # Strip the menuorder, leaving us with ordered list of computecfg groups
    tmpstr=""
    for j in $sorted_grouporder_str; do
        tmpstr+=" ${j#*:}";
    done
    sorted_grouporder_str=$tmpstr;
    sorted_grouporder_str=${sorted_grouporder_str# };

    echo "$sorted_grouporder_str";
}

print_setting() {
    local group=$1; shift;
    local name=$1; shift;

    local settingname;
    local val;
    settingname=$(get_cfg_settingname "$group" "$name")
    val=$(get_cfg_val "$group" "$name")
    printf "    %-22s $val\n" "${settingname}:";

}

print_computecfg_settings() {
    local group=$1; shift;

    print_setting "$group" "name"
    print_setting "$group" "menuorder"
    # print_setting "$group" "debug_mode"
    print_setting "$group" "nproc"
    print_setting "$group" "jmstype"

    local jmstype;
    local jmsbasetype;
    jmstype=$(get_cfg_val "$group" "jmstype")
    jmsbasetype=$(get_jmsbasetype "$jmstype");

    if [[ x"$jmstype" == x"NONE" ]] ; then
        :
    elif [[ x"$jmstype" == x"SGE" ]] ; then
        print_setting "$group" "sge_sgeroot"
        print_setting "$group" "sge_sgecell"
        print_setting "$group" "sge_bindir"
        print_setting "$group" "sge_queue"
        print_setting "$group" "sge_pe"
        print_setting "$group" "sge_startargs"
        print_setting "$group" "sge_use_settings_file"
    elif [[ x"$jmstype" == x"PBS" ]] ; then
        print_setting "$group" "pbs_bindir"
        print_setting "$group" "pbs_queue"
        print_setting "$group" "pbs_startargs"
    elif [[ x"$jmstype" == x"LSF" ]] ; then
        print_setting "$group" "lsf_bindir"
        print_setting "$group" "lsf_queue"
        print_setting "$group" "lsf_startargs"
    elif [[ x"$jmstype" == x"Slurm" ]] ; then
        print_setting "$group" "slurm_bindir"
        print_setting "$group" "slurm_partition"
        print_setting "$group" "slurm_startargs"
    elif [[ x"$jmstype" == x"AWS" ]] ; then
        print_setting "$group" "aws_queue"
        print_setting "$group" "aws_region"
    elif [[ x"$jmstype" == x"CustomJMS" ]] ; then
        print_setting "$group" "customjms_name"
        print_setting "$group" "customjms_bindir"
        print_setting "$group" "customjms_queue"
        print_setting "$group" "customjms_startargs"
    elif [[ x"$jmstype" == x"OtherJMS" ]] ; then
        print_setting "$group" "otherjms_name"
        print_setting "$group" "otherjms_bindir"
        print_setting "$group" "otherjms_queue"
        print_setting "$group" "otherjms_startargs"
    else
        minterror "print_computecfg_settings(): Unrecognized jmstype: $jmstype"
    fi
}

get_computecfg_displaystrings_by_menuorder() {

    local computecfgs
    computecfgs=$(get_computecfgs_by_menuorder);

    local maxlen;
    local computecfg;
    local name;
    maxlen=0;
    for computecfg in $computecfgs; do
        name=$(get_cfg_val "$computecfg" "name")
        [[ ${#name} -gt $maxlen ]] && maxlen=${#name}
    done
    [[ $maxlen -gt 45 ]] && maxlen=45;

    local name
    local str;
    local enable;
    local -a cfgarr=();
    local default_flagged=false;
    for computecfg in $computecfgs; do
        name=$(get_cfg_val "$computecfg" "name")
        str=$(printf "$computecfg@@%-${maxlen}s ($computecfg)\n" "$name")
        enabled=$(get_cfg_val "$computecfg" "enable")

        if ! $enabled; then
            str+=" [DISABLED]"
        elif ! $default_flagged ; then
            str+=" [default]"
            default_flagged=true;
        fi
        cfgarr+=( "$str" );
    done

    gret_computecfg_displaystrings_arr=( "${cfgarr[@]}" );
}

get_next_computecfg_listnum() {
    local computecfg
    local computecfgs
    local listnum;
    local next_listnum=-1;
    computecfgs=$(get_computecfgs_by_menuorder);
    for computecfg in $computecfgs ; do
        if [[ $computecfg =~ ^computecfg_([0-9]+)$ ]] ; then
            listnum=$(trim_listnum "${BASH_REMATCH[1]}")
            if [[ $listnum -gt $next_listnum ]] ; then
                next_listnum=$listnum;
            fi
        fi
    done
    next_listnum=$(( $next_listnum + 1 ));

    echo "$next_listnum"
}
get_next_computecfg() {
    local next_listnum;
    next_listnum=$(get_next_computecfg_listnum);

    local next_listgroupnum;
    local next_computecfg;
    next_listgroupnum=$(get_listgroupnum_from_listnum "$next_listnum")
    next_computecfg="computecfg_${next_listgroupnum}"

    echo "$next_computecfg"
}

prompt_computecfg_add() {
    local next_computecfg;
    next_computecfg=$(get_next_computecfg);

    echo
    echo "Adding $next_computecfg:"
    add_cfg_listgroup "computecfg_NN" "$next_computecfg"
    common_prompt_computecfg_single "$next_computecfg" true;
    echo
}


prompt_computecfg_clone() {
    get_computecfg_displaystrings_by_menuorder;
    local -a choices=( "${gret_computecfg_displaystrings_arr[@]}" );
    choices+=( "NONE@@None" );

    echo
    process_query --selstr "Select a compute configuration to clone from:" \
        NONE "${choices[@]}"
    local computecfg=$gret_val;

    if [[ x"$computecfg" == x"NONE" ]] ; then
        :
    else
        local next_computecfg;
        next_computecfg=$(get_next_computecfg);

        local name;
        name=$(get_cfg_val "$computecfg" "name");
        echo
        echo "Cloning '$name' ($computecfg):"

        add_cfg_listgroup "computecfg_NN" "$next_computecfg"
        clone_computecfg "$computecfg" "$next_computecfg"

        common_prompt_computecfg_single "$next_computecfg" true;
    fi
    echo
}

prompt_computecfg_modify() {
    get_computecfg_displaystrings_by_menuorder;
    local -a choices=( "${gret_computecfg_displaystrings_arr[@]}" );
    choices+=( "NONE@@None" );

    echo
    process_query --selstr "Select a compute configuration to modify:" \
        NONE "${choices[@]}"
    local computecfg=$gret_val;

    if [[ x"$computecfg" == x"NONE" ]] ; then
        :
    else
        local name;
        name=$(get_cfg_val "$computecfg" "name");
        echo
        echo "Modifying '$name' ($computecfg):"
        common_prompt_computecfg_single "$computecfg" true;
    fi
    echo
}

prompt_computecfg_show() {
    get_computecfg_displaystrings_by_menuorder;
    local -a choices=( "${gret_computecfg_displaystrings_arr[@]}" );
    choices+=( "NONE@@None" );

    echo
    process_query --selstr "Select a compute configuration to show:" \
        NONE "${choices[@]}"
    local computecfg=$gret_val;

    if [[ x"$computecfg" == x"NONE" ]] ; then
        :
    else
        local name;
        name=$(get_cfg_val "$computecfg" "name");
        echo
        echo "  Showing '$name' ($computecfg):"
        print_computecfg_settings "$computecfg";
    fi
    echo


}

prompt_computecfg_delete() {
    get_computecfg_displaystrings_by_menuorder;
    local -a choices=( "${gret_computecfg_displaystrings_arr[@]}" );

    local enabled_computecfgs="";
    for choice in "${choices[@]}"; do
        if [[ ! $choice =~ \[DISABLED\]$ ]] ; then
            enabled_computecfgs+="${choice%%@@*} "
        fi
    done
    enabled_computecfgs=${enabled_computecfgs% }
    choices+=( "NONE@@None" );

    echo
    echo
    process_query --selstr "Select a compute configuration to delete:" \
        NONE "${choices[@]}"

    local computecfg=$gret_val;

    if [[ x"$computecfg" == x"NONE" ]] ; then
        :
    elif [[ x"$computecfg" == x"$enabled_computecfgs" ]] ; then

        echo
        echo "Cannot delete '$computecfg' (one enabled configuration required)"
        echo
        local ans;
        read -p "Continue..." ans;
    else
        # Delete the configuration
        local i;
        for ((i=0; i < ${#ga_cfgg_groups[@]} ; i++)); do
            if [[ x"${ga_cfgg_groups[$i]}" == x"$computecfg" ]] ; then
                g_cfgg_savegroup[$i]=false;
            fi
        done
    fi
    echo
}

prompt_computecfg_enable() {
    get_computecfg_displaystrings_by_menuorder;

    local -a computecfgstrs=( "${gret_computecfg_displaystrings_arr[@]}" );
    local -a choices=();
    local computecfgstr;
    for computecfgstr in "${computecfgstrs[@]}"; do
        if [[ $computecfgstr =~ \[DISABLED\]$ ]] ; then
            choices+=( "$computecfgstr" );
        fi
    done
    choices+=( "NONE@@None" );

    echo
    echo
    process_query --selstr "Select a compute configuration to enable:" \
        NONE "${choices[@]}"

    local computecfg=$gret_val;

    if [[ x"$computecfg" == x"NONE" ]] ; then
        :
    else
        set_cfg_val "$computecfg" "enable" "true" "configured interactively" false;
    fi
    echo
}

prompt_computecfg_disable() {
    get_computecfg_displaystrings_by_menuorder;

    local -a computecfgstrs=( "${gret_computecfg_displaystrings_arr[@]}" );
    local -a choices=();
    local computecfgstr;
    for computecfgstr in "${computecfgstrs[@]}"; do
        if [[ ! $computecfgstr =~ \[DISABLED\]$ ]] ; then
            choices+=( "$computecfgstr" );
        fi
    done
    choices+=( "NONE@@None" );

    echo
    echo
    process_query --selstr "Select a compute configuration to disable:" \
        NONE "${choices[@]}"

    local computecfg=$gret_val;

    if [[ x"$computecfg" == x"NONE" ]] ; then
        :
    else
        set_cfg_val "$computecfg" "enable" "false" "configured interactively" false;
    fi
    echo
}

prompt_computecfg_reorder() {
    echo
    echo "Define the Menu Order"
    echo
    echo "Lower menu order number appear higher in the UI dropdown menu."
    echo "Lowest menu order number is the default menu selection"
    echo
    echo "Current UI menu order (current order number in [] braces):"

    get_computecfg_displaystrings_by_menuorder;
    local -a computecfgstrs=( "${gret_computecfg_displaystrings_arr[@]}" );
    local menuorder
    local computecfgstr;
    for computecfgstr in "${computecfgstrs[@]}"; do
        menuorder=$(get_cfg_val "${computecfgstr%%@@*}" "menuorder")
        printf "    %-5s ${computecfgstr#*@@}\n" "[$menuorder]"
    done

    echo
    local name
    for computecfgstr in "${computecfgstrs[@]}"; do
        name=$(get_cfg_val "${computecfgstr%%@@*}" "name");
        echo "  For '$name' (${computecfgstr%%@@*}):"
        assign_cfg_val_interactive "${computecfgstr%%@@*}" "menuorder" "    ";
    done
    echo
}

prompt_computecfg_actionmenu() {
    echo
    echo "Current compute configurations:"
    echo
    get_computecfg_displaystrings_by_menuorder;

    local -a computecfgstrs=( "${gret_computecfg_displaystrings_arr[@]}" );
    local anydisabled=false;
    local disabled;
    local enabledcnt=0;
    local computecfgstr;
    for computecfgstr in "${computecfgstrs[@]}"; do
        echo "    ${computecfgstr#*@@}"

        disabled=false;
        if [[ $computecfgstr =~ \[DISABLED\]$ ]] ; then
            disabled=true;
        fi

        if $disabled; then
            anydisabled=true;
        else
            enabledcnt=$(( $enabledcnt + 1 ));
        fi
    done

    local choices=(
        "ADD@@Add a New Configuration"
        "CLONE@@Clone an Existing Configuration"
        "MODIFY@@Modify an Existing Configuration"
        "SHOW@@Show an Existing Configuration"
    );

    if [[ ${#computecfgstrs[@]} -ge 2 ]] ; then
        choices+=( "DELETE@@Delete an Existing Configuration" )
    fi

    if [[ $enabledcnt -ge 2 ]] ; then
        choices+=( "DISABLE@@Disable an Existing Configuration" )
    fi

    if $anydisabled; then
        choices+=( "ENABLE@@Enable an Existing Disabled Configuration" )
    fi
    if [[ ${#computecfgstrs[@]} -ge 2 ]] ; then
        choices+=( "REORDER@@Specify Configuration Order in UI Menu" );
    fi

    choices+=( "DONE@@Exit Configuration Menu" );

    echo
    process_query "DONE"  "${choices[@]}"
    local selection=$gret_val;

    case "$selection" in
        "ADD") prompt_computecfg_add;;
        "CLONE") prompt_computecfg_clone;;
        "MODIFY") prompt_computecfg_modify;;
        "SHOW") prompt_computecfg_show;;
        "DELETE") prompt_computecfg_delete;;
        "DISABLE") prompt_computecfg_disable;;
        "ENABLE") prompt_computecfg_enable;;
        "REORDER") prompt_computecfg_reorder;;
        "DONE") ;;
        *) minterror "Unrecognized compute config menu selection: $selection";;
    esac

    gret_loop_done=false;
    if [[ x"$selection" == x"DONE" ]]; then
        gret_loop_done=true;
    fi
}

prompt_computecfg_actionmenu_loop() {
    while true; do
        prompt_computecfg_actionmenu;
        if $gret_loop_done; then
            break;
        fi
    done
}

common_prompt_computecfg() {
    next_screen;
    $opt_batch || cat <<-EOF

	----- Part 9 of 10: SMRT Link Compute Configuration  -----

	SMRT Link allows for one or more compute configurations to be
	defined for processing analysis jobs.  Each compute configuration
	can specify unique workflow and Job Management System settings,
	completely independent from other compute configurations.

	When configuring a SMRT Analysis job, a drop-down menu under the
	"Advanced Parameter Settings" allows selecting one of multiple
	compute configurations. The selected configuration will be used for
	all JMS cluster submissions of the analysis job.

	EOF

    local computecfg;
    local computecfgs;

    # (Re)configure each computecfg we know about
    local display_help=true;
    computecfgs=$(get_computecfgs_by_menuorder)
    for computecfg in $computecfgs; do
        common_prompt_computecfg_single "$computecfg" $display_help;
        display_help=false;
    done

    echo
    echo "Current compute configurations:"
    echo
    get_computecfg_displaystrings_by_menuorder;
    local computecfgstr
    for computecfgstr in "${gret_computecfg_displaystrings_arr[@]}"; do
        echo "    ${computecfgstr#*@@}"
    done

    echo
    if ! $opt_batch; then
        ans=$(noyes_prompt "Modify or add compute configuration?" false)
        if [[ x"$ans" == x"y" ]] ; then
            prompt_computecfg_actionmenu_loop;
        fi
    fi
}


common_prompt_smrtlink_config() {
    next_screen;
    $opt_batch || cat <<-EOF

	----- Part 10 of 10: SMRT Link Analysis Configuration  -----

	NWORKERS - Specifies the maximum number of simultaneous SMRT Analysis
	           jobs that can be run by the SMRT Link server.  This should
	           be set to no more than the number of processors available
	           on the SMRT Link install machine.  The default is the min
	           of 32 and number of processors on the system.

	EOF

    local nworkers_val;
    local nworkers_defval;
    nworkers_defval=$(get_cfg_val "smrtlink" "nworkers")
    while true; do
        assign_cfg_val_interactive "smrtlink" "nworkers";
        nworkers_val=$(get_cfg_val "smrtlink" "nworkers")
        if [[ $nworkers_val -gt $g_hw_numprocs ]] ; then
            # TODO revisit this later
            echo "    WARNING: the number of configured workers ($nworkers_val) is"
            echo "    greater than the number of processors on the system ($g_hw_numprocs)."
            echo "    This has not been extensively tested and is not recommended."
            echo
            break;
        else
                  break;
        fi
        set_cfg_val "smrtlink" "nworkers" "$nworkers_defval" "reset to prev default" true;
    done

    echo
    print_cfg_usingstr "smrtlink" "nworkers"
}


get_ordered_computecfg_filelist_jsonsnipet() {
    local nl=$'\n';
    local presets_jsonsnipet="";
    local computecfg;
    local computecfgs;
    local listgroupnum;
    local config_dir;
    local cromwell_json;
    computecfgs=$(get_computecfgs_by_menuorder)
    for computecfg in $computecfgs; do
        savegroup=$(get_cfgg_savegroup "$computecfg");
        if ! $savegroup; then
            continue;
        fi
        enabled=$(get_cfg_val "$computecfg" "enable");
        if ! $enabled; then
            continue;
        fi

        listgroupnum=$(get_listgroupnum_from_listnum "${computecfg##*_}")
        config_dir="$g_generated_dir/config/$computecfg";
        cromwell_json="$config_dir/cromwell_${listgroupnum}.json"

        presets_jsonsnipet+="          $(json_str "$cromwell_json"),$nl"
    done
    presets_jsonsnipet="\"${presets_jsonsnipet#*\"}"
    presets_jsonsnipet="${presets_jsonsnipet%,${nl}}$nl"

    echo "$presets_jsonsnipet"
}

archive_existing_configs() {
    if  [[ -e "$g_config_dir" ]] ||
        [[ -e "$g_generated_dir" ]] ; then
        :
    else
        return 0
    fi

    echo "Archiving existing configuration..."

    local old_versionstr;
    old_versionstr=$(get_cfg_val "internal" "version")
    local datestr;
    datestr=$(date +"%Y%m%d-%H%M%S");

    local archivedirname="${old_versionstr}_${datestr}"
    local archivedir="$g_archive_dir/$archivedirname"

    mkdir -p "$archivedir"
    if [[ -e "$g_config_dir" ]] ; then
        # we will copy this directory (instead of moving it) so that we always
        # have a copy of config/smrtlink.config available.  Also for the
        # 6.1.0 (or maybe 7.0.1) upgrade, we should also keep around the
        # config/smrtlink-system-config.json file (in case the installer exits
        # prematurely, that file will still be needed in that location to
        # perform the preupgrade database backup on the next invocation of the
        # installer).
        # We will remove the extraneous legacy files (i.e. config/preset.xml
        # and config/smrtlink-system-config.json in misc_hacks(), right before
        # successfullly exiting the instaler)
        cp -a "$g_config_dir" "$archivedir"
    fi
    if [[ -e "$g_generated_dir" ]] ; then
        # We can move this directory, as we do not depend on it until after
        # the installer exits, and the installer can always regenerate it.
        cp -a "$g_generated_dir" "$archivedir"
    fi

    # Create the latest link
    rm -f "$g_archive_dir/latest"
    ln -s "$archivedirname" "$g_archive_dir/latest"
}

# ---- main

# Force umask here (make sure new files/dirs are not group/other accessible):
umask 0077

# Order of precedence for specifying configuration settings:
#    1. Command line options (Highest priority)
#    2. Config file specified on the command line, --configfile option
#    3. Existing smrtlink config file, from a previous install
#    4. Configuration specified at interactive prompts
#    5. Program defaults (Lowest priority)

set_preglobals;
parseargs ${1+"$@"};
set_globals;

set_smrtlink_env;

if $opt_version; then
    cat "$g_topdir/etc/versionstr.txt"
    exit 0;
fi

# Create userdata directory, with proper permissions, before anything else
# created it via a 'mkdir -p' (potentially with the incorrect umask)
create_userdata;

# After logfile_setup, all stdout/stderr will also go to logfile.
# Use logonly() to only write to logfile.
logfile_setup;

# Initial setup the tech support troubleshooting reporting (via Event service)
tsreport_update_setup;

if $opt_smrttools_only; then
    extract_smrttools_only;
    exit 0;
fi


# Ignore configfile setting for certain computecfgs (in the event of a clone,
# delete or reset operation)
set_computecfg_configfile_ignores;

# Read the config file specified on the command line
read_cmdline_configfile;

# Read the existing smrtlink config file
read_smrtlink_configfile;

# Override with detected settings (e.g. from jobsroot_dir symlink,...)
override_config_with_detected_settings;

# Modify computecfg settings, if needed (for clone, reset, delete)
modify_computecfg_settings;

# Compute physical memory if needed
compute_physmem;

# Extract sub-tarball bundles (smrttools, smrtslag,...)
extract_bundles;

# Update the tsrport information (should be able to compute uuid now)
# Call this before exiting with the --extract-bundles-only option, so that
# we will be ready for running tsreport (even if we only extracted the
# bundles and exited normally, useful for testing if nothing else).
tsreport_update_setup;

# Check system basics (linux, x86_64, libc version, locale,...)
system_sanity_check;

if $opt_extract_bundles_only; then
    # Exit immediately now that bundles have been extracted
    echo "SMRT Link bundles extracted, exiting..."
    exit 0;
fi

# Any fixup hacks we need to run before we start executing;
fixup_hacks;

# Output initial header
printheader;

# Handle the user input
# In interactive mode, make sure that we flush all input up to this point
# (to ignore someone hitting enter at the screen when the tarball extraction
# is happening, for example).
flush_input;


if ! $opt_skip_system_check; then
    install_prompt_system_check;
fi

g_keep_all_settings=false;

# Always call the version checks.  For upgrades it will check for the
# expected migration paths.  For installs, reconfig and restore, it will
# just check to make sure we are on the expected current version (if not,
# the upgrade migration checks are probably out of date).
upgrade_versionnum_migrationpath_check;
if $opt_upgrade || $opt_reconfig || $opt_restore; then
    upgrade_prompt_user;
    upgrade_prompt_killservices;
    upgrade_prompt_priorsettings;
else
    install_prompt_user;
fi

if ! $g_keep_all_settings; then
    common_prompt_smrtlink_dns;
    common_prompt_smrtlink_server;
    common_prompt_database_setup;
    common_prompt_cromwell_server;
    common_prompt_dirsetup;
    common_prompt_remoteurls;
    common_prompt_mail_notification;
    common_prompt_computecfg;
    common_prompt_smrtlink_config;
fi

# Preupgrade backup (only if in --upgrade mode)
upgrade_database_checkfixbackup_preupgrade;

# Archive the existing smrtlink and generated configs
archive_existing_configs;

# Write config file
echo
echo "Updating smrtlink install configuration..."
write_smrtlink_config;

# Clean generated directory

echo
echo "Applying settings..."
clean_new_generated_dir;

apply_computecfg_settings;
apply_smrtlink_settings;
apply_cromwell_settings;
apply_dirlink_settings;
apply_database_settings;

echo "Installing settings..."
install_generated_settings;
install_smrtlink_settings;
install_smrtlink_migrationconfig;

echo
echo "Running services and ui upgrade..."
run_smrtslag_upgrade;

# Postupgrade backup (only if in --upgrade mode)
upgrade_database_backup_postupgrade;


# Run some miscellaneous hacks for now (like creating smrtcmds link at top
# level).
# FIXME: figure out a better place to properly impelement these hacks.
misc_hacks;

# Inform the caller of success (will move the 'new' link to 'current', in
# smrt_reinstall or smrt_reupgrade)
if [[ ! -z "$SMRT_SUCCESS_FILE" ]] ; then
    touch "$SMRT_SUCCESS_FILE"
fi

echo
echo "SMRT Link $g_actionstr successful."

# Skip the initialization of smrtlink for now.  This can be run outside
# of the installer by calling "smrtlink-init" directly, or by whatever
# other manual means we want to use for invoking import-canneddata.
#
#   # Initialize smrtlink (import-canneddata,...)
#   inittype_arg="--install"
#   $opt_upgrade && inittype_arg="--upgrade"
#   $opt_reconfig && inittype_arg="--reconfigure"
#   echo
#   echo "Initializing smrtlink..."
#   "$g_progdir/smrtlink-init" "$inittype_arg"
#
#   echo
#   echo "SMRT Link $g_actionstr and initialization successful."


logfile_shutdown;
exit 0;
