/*
 * Copyright (c) 2015-2020 Intel, Inc.  All rights reserved.
 * Copyright (c) 2016-2019 IBM Corporation.  All rights reserved.
 * Copyright (c) 2018      Research Organization for Information Science
 *                         and Technology (RIST).  All rights reserved.
 *
 * Copyright (c) 2021-2022 Nanook Consulting.  All rights reserved.
 * $COPYRIGHT$
 *
 * Additional copyrights may follow
 *
 * $HEADER$
 */

#include "src/include/pmix_config.h"

#ifdef HAVE_STRING_H
#    include <string.h>
#endif
#include <fcntl.h>
#ifdef HAVE_UNISTD_H
#    include <unistd.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#    include <sys/types.h>
#endif
#include <ctype.h>

#include "include/pmix.h"
#include "pmix_common.h"

#include "src/class/pmix_list.h"
#include "src/client/pmix_client_ops.h"
#include "src/include/pmix_globals.h"
#include "src/include/pmix_socket_errno.h"
#include "src/mca/bfrops/base/base.h"
#include "src/mca/gds/gds.h"
#include "src/util/pmix_argv.h"
#include "src/util/pmix_error.h"
#include "src/util/pmix_output.h"
#include "src/util/pmix_printf.h"

#include "preg_native.h"
#include "src/mca/preg/base/base.h"

static pmix_status_t generate_node_regex(const char *input, char **regex);
static pmix_status_t generate_ppn(const char *input, char **ppn);
static pmix_status_t parse_nodes(const char *regexp, char ***names);
static pmix_status_t parse_procs(const char *regexp, char ***procs);
static pmix_status_t copy(char **dest, size_t *len, const char *input);
static pmix_status_t pack(pmix_buffer_t *buffer, const char *input);
static pmix_status_t unpack(pmix_buffer_t *buffer, char **regex);
static pmix_status_t release(char *regexp);

pmix_preg_module_t pmix_preg_native_module = {
    .name = "pmix",
    .generate_node_regex = generate_node_regex,
    .generate_ppn = generate_ppn,
    .parse_nodes = parse_nodes,
    .parse_procs = parse_procs,
    .copy = copy,
    .pack = pack,
    .unpack = unpack,
    .release = release
};

static pmix_status_t regex_parse_value_ranges(char *base, char *ranges, int num_digits,
                                              char *suffix, char ***names);
static pmix_status_t regex_parse_value_range(char *base, char *range, int num_digits, char *suffix,
                                             char ***names);
static pmix_status_t pmix_regex_extract_nodes(char *regexp, char ***names);
static pmix_status_t pmix_regex_extract_ppn(char *regexp, char ***procs);

static pmix_status_t generate_node_regex(const char *input, char **regexp)
{
    char *vptr, *vsave;
    char prefix[PMIX_MAX_NODE_PREFIX];
    int i, j, len, startnum, vnum, numdigits;
    bool found, fullval;
    char *suffix, *sfx;
    pmix_regex_value_t *vreg;
    pmix_regex_range_t *range;
    pmix_list_t vids;
    char **regexargs = NULL, *tmp, *tmp2;
    char *cptr;
    pmix_status_t rc;

    /* define the default */
    *regexp = NULL;

    /* setup the list of results */
    PMIX_CONSTRUCT(&vids, pmix_list_t);

    /* cycle thru the array of input values - first copy
     * it so we don't overwrite what we were given*/
    vsave = strdup(input);
    vptr = vsave;
    while (NULL != (cptr = strchr(vptr, ',')) || 0 < strlen(vptr)) {
        if (NULL != cptr) {
            *cptr = '\0';
        }
        /* determine this node's prefix by looking for first non-alpha char */
        fullval = false;
        len = strlen(vptr);
        startnum = -1;
        memset(prefix, 0, PMIX_MAX_NODE_PREFIX);
        for (i = 0, j = 0; i < len; i++) {
            if (!isalpha(vptr[i])) {
                /* found a non-alpha char */
                if (!isdigit(vptr[i])) {
                    /* if it is anything but a digit, we just use
                     * the entire name
                     */
                    fullval = true;
                    break;
                }
                /* count the size of the numeric field - but don't
                 * add the digits to the prefix
                 */
                if (startnum < 0) {
                    /* okay, this defines end of the prefix */
                    startnum = i;
                }
                continue;
            }
            if (startnum < 0) {
                prefix[j++] = vptr[i];
            }
        }
        if (fullval || startnum < 0) {
            /* can't compress this name - just add it to the list */
            vreg = PMIX_NEW(pmix_regex_value_t);
            vreg->prefix = strdup(vptr);
            pmix_list_append(&vids, &vreg->super);
            /* move to the next posn */
            if (NULL == cptr) {
                break;
            }
            vptr = cptr + 1;
            continue;
        }
        /* convert the digits and get any suffix */
        vnum = strtol(&vptr[startnum], &sfx, 10);
        if (NULL != sfx) {
            suffix = strdup(sfx);
            numdigits = (int) (sfx - &vptr[startnum]);
        } else {
            suffix = NULL;
            numdigits = (int) strlen(&vptr[startnum]);
        }

        /* is this value already on our list? */
        found = false;
        PMIX_LIST_FOREACH (vreg, &vids, pmix_regex_value_t) {
            // The regex must preserve ordering of the values.
            // If we disqualified this entry in a previous check then exclude it
            // from future checks as well. This will prevent a later entry from
            // being 'pulled forward' accidentally. For example, given:
            // "a28n01,a99n02,a28n02"
            // Without this 'skip' the loop would have 'a28n02' combine with
            // 'a28n01' jumping over the 'a99n02' entry, and thus not preserving
            // the order of the list when the regex is unpacked.
            if (vreg->skip) {
                continue;
            }

            if (0 < strlen(prefix) && NULL == vreg->prefix) {
                continue;
            }
            if (0 == strlen(prefix) && NULL != vreg->prefix) {
                continue;
            }
            if (0 < strlen(prefix) && NULL != vreg->prefix && 0 != strcmp(prefix, vreg->prefix)) {
                vreg->skip = true;
                continue;
            }
            if (NULL == suffix && NULL != vreg->suffix) {
                continue;
            }
            if (NULL != suffix && NULL == vreg->suffix) {
                continue;
            }
            if (NULL != suffix && NULL != vreg->suffix && 0 != strcmp(suffix, vreg->suffix)) {
                vreg->skip = true;
                continue;
            }
            if (numdigits != vreg->num_digits) {
                vreg->skip = true;
                continue;
            }
            /* found a match - flag it */
            found = true;
            /* get the last range on this nodeid - we do this
             * to preserve order
             */
            range = (pmix_regex_range_t *) pmix_list_get_last(&vreg->ranges);
            if (NULL == range) {
                /* first range for this value */
                range = PMIX_NEW(pmix_regex_range_t);
                range->start = vnum;
                range->cnt = 1;
                pmix_list_append(&vreg->ranges, &range->super);
                break;
            }
            /* see if the value is out of sequence */
            if (vnum != (range->start + range->cnt)) {
                /* start a new range */
                range = PMIX_NEW(pmix_regex_range_t);
                range->start = vnum;
                range->cnt = 1;
                pmix_list_append(&vreg->ranges, &range->super);
                break;
            }
            /* everything matches - just increment the cnt */
            range->cnt++;
            break;
        }
        if (!found) {
            /* need to add it */
            vreg = PMIX_NEW(pmix_regex_value_t);
            if (0 < strlen(prefix)) {
                vreg->prefix = strdup(prefix);
            }
            if (NULL != suffix) {
                vreg->suffix = strdup(suffix);
            }
            vreg->num_digits = numdigits;
            pmix_list_append(&vids, &vreg->super);
            /* record the first range for this value - we took
             * care of values we can't compress above
             */
            range = PMIX_NEW(pmix_regex_range_t);
            range->start = vnum;
            range->cnt = 1;
            pmix_list_append(&vreg->ranges, &range->super);
        }
        if (NULL != suffix) {
            free(suffix);
        }
        /* move to the next posn */
        if (NULL == cptr) {
            break;
        }
        vptr = cptr + 1;
    }
    free(vsave);

    /* begin constructing the regular expression */
    while (NULL != (vreg = (pmix_regex_value_t *) pmix_list_remove_first(&vids))) {
        /* if no ranges, then just add the name */
        if (0 == pmix_list_get_size(&vreg->ranges)) {
            if (NULL != vreg->prefix) {
                PMIx_Argv_append_nosize(&regexargs, vreg->prefix);
            }
            PMIX_RELEASE(vreg);
            continue;
        }
        /* start the regex for this value with the prefix */
        if (NULL != vreg->prefix) {
            if (0 > asprintf(&tmp, "%s[%d:", vreg->prefix, vreg->num_digits)) {
                return PMIX_ERR_NOMEM;
            }
        } else {
            if (0 > asprintf(&tmp, "[%d:", vreg->num_digits)) {
                return PMIX_ERR_NOMEM;
            }
        }
        /* add the ranges */
        while (NULL != (range = (pmix_regex_range_t *) pmix_list_remove_first(&vreg->ranges))) {
            if (1 == range->cnt) {
                if (0 > asprintf(&tmp2, "%s%d,", tmp, range->start)) {
                    return PMIX_ERR_NOMEM;
                }
            } else {
                if (0 > asprintf(&tmp2, "%s%d-%d,", tmp, range->start,
                                 range->start + range->cnt - 1)) {
                    return PMIX_ERR_NOMEM;
                }
            }
            free(tmp);
            tmp = tmp2;
            PMIX_RELEASE(range);
        }
        /* replace the final comma */
        tmp[strlen(tmp) - 1] = ']';
        if (NULL != vreg->suffix) {
            /* add in the suffix, if provided */
            if (0 > asprintf(&tmp2, "%s%s", tmp, vreg->suffix)) {
                return PMIX_ERR_NOMEM;
            }
            free(tmp);
            tmp = tmp2;
        }
        PMIx_Argv_append_nosize(&regexargs, tmp);
        free(tmp);
        PMIX_RELEASE(vreg);
    }

    /* assemble final result */
    if (NULL != regexargs) {
        tmp = PMIx_Argv_join(regexargs, ',');
        if (0 > asprintf(regexp, "pmix[%s]", tmp)) {
            return PMIX_ERR_NOMEM;
        }
        free(tmp);

        /* cleanup */
        PMIx_Argv_free(regexargs);
        rc = PMIX_SUCCESS;
    } else {
        rc = PMIX_ERR_TAKE_NEXT_OPTION;
    }

    PMIX_DESTRUCT(&vids);
    return rc;
}

static pmix_status_t generate_ppn(const char *input, char **regexp)
{
    char **ppn, **npn;
    int i, j, start, end;
    pmix_regex_value_t *vreg;
    pmix_regex_range_t *rng;
    pmix_list_t nodes;
    char *tmp, *tmp2;
    char *cptr;

    /* define the default */
    *regexp = NULL;

    /* setup the list of results */
    PMIX_CONSTRUCT(&nodes, pmix_list_t);

    /* split the input by node */
    ppn = PMIx_Argv_split(input, ';');

    /* for each node, split the input by comma */
    for (i = 0; NULL != ppn[i]; i++) {
        rng = NULL;
        /* create a record for this node */
        vreg = PMIX_NEW(pmix_regex_value_t);
        pmix_list_append(&nodes, &vreg->super);
        /* split the input for this node */
        npn = PMIx_Argv_split(ppn[i], ',');
        /* look at each element */
        for (j = 0; NULL != npn[j]; j++) {
            /* is this a range? */
            if (NULL != (cptr = strchr(npn[j], '-'))) {
                /* terminate the string */
                *cptr = '\0';
                ++cptr;
                start = strtol(npn[j], NULL, 10);
                end = strtol(cptr, NULL, 10);
                /* are we collecting a range? */
                if (NULL == rng) {
                    /* no - better start one */
                    rng = PMIX_NEW(pmix_regex_range_t);
                    rng->start = start;
                    rng->cnt = end - start + 1;
                    pmix_list_append(&vreg->ranges, &rng->super);
                } else {
                    /* is this a continuation of the current range? */
                    if (start == (rng->start + rng->cnt)) {
                        /* just add it to the end of this range */
                        rng->cnt++;
                    } else {
                        /* nope, there is a break - create new range */
                        rng = PMIX_NEW(pmix_regex_range_t);
                        rng->start = start;
                        rng->cnt = end - start + 1;
                        pmix_list_append(&vreg->ranges, &rng->super);
                    }
                }
            } else {
                /* single rank given */
                start = strtol(npn[j], NULL, 10);
                /* are we collecting a range? */
                if (NULL == rng) {
                    /* no - better start one */
                    rng = PMIX_NEW(pmix_regex_range_t);
                    rng->start = start;
                    rng->cnt = 1;
                    pmix_list_append(&vreg->ranges, &rng->super);
                } else {
                    /* is this a continuation of the current range? */
                    if (start == (rng->start + rng->cnt)) {
                        /* just add it to the end of this range */
                        rng->cnt++;
                    } else {
                        /* nope, there is a break - create new range */
                        rng = PMIX_NEW(pmix_regex_range_t);
                        rng->start = start;
                        rng->cnt = 1;
                        pmix_list_append(&vreg->ranges, &rng->super);
                    }
                }
            }
        }
        PMIx_Argv_free(npn);
    }
    PMIx_Argv_free(ppn);

    /* begin constructing the regular expression */
    tmp = strdup("pmix[");
    PMIX_LIST_FOREACH (vreg, &nodes, pmix_regex_value_t) {
        while (NULL != (rng = (pmix_regex_range_t *) pmix_list_remove_first(&vreg->ranges))) {
            if (1 == rng->cnt) {
                if (0 > asprintf(&tmp2, "%s%d,", tmp, rng->start)) {
                    free(tmp);
                    return PMIX_ERR_NOMEM;
                }
            } else {
                if (0 > asprintf(&tmp2, "%s%d-%d,", tmp, rng->start, rng->start + rng->cnt - 1)) {
                    free(tmp);
                    return PMIX_ERR_NOMEM;
                }
            }
            free(tmp);
            tmp = tmp2;
            PMIX_RELEASE(rng);
        }
        /* replace the final comma */
        tmp[strlen(tmp) - 1] = ';';
    }

    /* replace the final semi-colon */
    tmp[strlen(tmp) - 1] = ']';

    /* if this results in a longer answer, then don't do it */
    if (strlen(tmp) > strlen(input)) {
        free(tmp);
        PMIX_LIST_DESTRUCT(&nodes);
        return PMIX_ERR_TAKE_NEXT_OPTION;
    }

    /* assemble final result */
    *regexp = tmp;

    PMIX_LIST_DESTRUCT(&nodes);
    return PMIX_SUCCESS;
}

static pmix_status_t parse_nodes(const char *regexp, char ***names)
{
    char *tmp, *ptr;
    pmix_status_t rc;

    /* set default */
    *names = NULL;

    /* protect against bozo */
    if (NULL == regexp) {
        return PMIX_SUCCESS;
    }

    /* protect the input string */
    tmp = strdup(regexp);
    /* strip the trailing bracket */
    tmp[strlen(tmp) - 1] = '\0';

    /* the regex generator used to create this regex
     * is tagged at the beginning of the string */
    if (NULL == (ptr = strchr(tmp, '['))) {
        free(tmp);
        return PMIX_ERR_BAD_PARAM;
    }
    *ptr = '\0';
    ++ptr;

    /* if it was done by PMIx, use that parser */
    if (0 == strcmp(tmp, "pmix")) {
        if (PMIX_SUCCESS != (rc = pmix_regex_extract_nodes(ptr, names))) {
            PMIX_ERROR_LOG(rc);
        }
    } else {
        /* this isn't an error - let someone else try */
        rc = PMIX_ERR_TAKE_NEXT_OPTION;
    }
    free(tmp);
    return rc;
}
static pmix_status_t parse_procs(const char *regexp, char ***procs)
{
    char *tmp, *ptr;
    pmix_status_t rc;

    /* set default */
    *procs = NULL;

    /* protect against bozo */
    if (NULL == regexp) {
        return PMIX_SUCCESS;
    }

    /* protect the input string */
    tmp = strdup(regexp);
    /* strip the trailing bracket */
    tmp[strlen(tmp) - 1] = '\0';

    /* the regex generator used to create this regex
     * is tagged at the beginning of the string */
    if (NULL == (ptr = strchr(tmp, '['))) {
        free(tmp);
        return PMIX_ERR_BAD_PARAM;
    }
    *ptr = '\0';
    ++ptr;

    /* if it was done by PMIx, use that parser */
    if (0 == strcmp(tmp, "pmix")) {
        if (PMIX_SUCCESS != (rc = pmix_regex_extract_ppn(ptr, procs))) {
            PMIX_ERROR_LOG(rc);
        }
    } else {
        /* this isn't an error - let someone else try */
        rc = PMIX_ERR_TAKE_NEXT_OPTION;
    }
    free(tmp);
    return rc;
}

static pmix_status_t copy(char **dest, size_t *len, const char *input)
{
    if (0 != strncmp(input, "pmix", 4)) {
        return PMIX_ERR_TAKE_NEXT_OPTION;
    }

    *dest = strdup(input);
    *len = strlen(input) + 1;
    return PMIX_SUCCESS;
}

static pmix_status_t pack(pmix_buffer_t *buffer, const char *input)
{
    size_t slen;
    char *ptr;

    if (0 != strncmp(input, "pmix", 4)) {
        return PMIX_ERR_TAKE_NEXT_OPTION;
    }

    /* extract the size */
    slen = strlen(input) + 1; // retain the NULL terminator

    /* ensure the buffer has enough space */
    ptr = pmix_bfrop_buffer_extend(buffer, slen);
    if (NULL == ptr) {
        return PMIX_ERR_NOMEM;
    }

    /* xfer the data */
    memcpy(ptr, input, slen);
    buffer->bytes_used += slen;
    buffer->pack_ptr += slen;

    return PMIX_SUCCESS;
}

static pmix_status_t unpack(pmix_buffer_t *buffer, char **regex)
{
    char *ptr;

    ptr = buffer->unpack_ptr;

    if (0 != strncmp(ptr, "pmix", 4)) {
        return PMIX_ERR_TAKE_NEXT_OPTION;
    }

    *regex = strdup(ptr);
    buffer->unpack_ptr += strlen(ptr) + 1;

    if (NULL == *regex) {
        return PMIX_ERR_NOMEM;
    }
    return PMIX_SUCCESS;
}

static pmix_status_t pmix_regex_extract_nodes(char *regexp, char ***names)
{
    int i, j, k, len;
    pmix_status_t ret;
    char *base;
    char *orig, *suffix;
    bool found_range = false;
    bool more_to_come = false;
    int num_digits;

    /* set the default */
    *names = NULL;

    if (NULL == regexp) {
        return PMIX_SUCCESS;
    }

    orig = base = strdup(regexp);
    if (NULL == base) {
        PMIX_ERROR_LOG(PMIX_ERR_OUT_OF_RESOURCE);
        return PMIX_ERR_OUT_OF_RESOURCE;
    }

    pmix_output_verbose(1, pmix_preg_base_framework.framework_output,
                         "pmix:extract:nodes: checking list: %s", regexp);

    do {
        /* Find the base */
        len = strlen(base);
        for (i = 0; i <= len; ++i) {
            if (base[i] == '[') {
                /* we found a range. this gets dealt with below */
                base[i] = '\0';
                found_range = true;
                break;
            }
            if (base[i] == ',') {
                /* we found a singleton value, and there are more to come */
                base[i] = '\0';
                found_range = false;
                more_to_come = true;
                break;
            }
            if (base[i] == '\0') {
                /* we found a singleton value */
                found_range = false;
                more_to_come = false;
                break;
            }
        }
        if (i == 0 && !found_range) {
            /* we found a special character at the beginning of the string */
            free(orig);
            return PMIX_ERR_BAD_PARAM;
        }

        if (found_range) {
            /* If we found a range, get the number of digits in the numbers */
            i++; /* step over the [ */
            for (j = i; j < len; j++) {
                if (base[j] == ':') {
                    base[j] = '\0';
                    break;
                }
            }
            if (j >= len) {
                /* we didn't find the number of digits */
                free(orig);
                return PMIX_ERR_BAD_PARAM;
            }
            num_digits = strtol(&base[i], NULL, 10);
            i = j + 1; /* step over the : */
            /* now find the end of the range */
            for (j = i; j < len; ++j) {
                if (base[j] == ']') {
                    base[j] = '\0';
                    break;
                }
            }
            if (j >= len) {
                /* we didn't find the end of the range */
                free(orig);
                return PMIX_ERR_BAD_PARAM;
            }
            /* check for a suffix */
            if (j + 1 < len && base[j + 1] != ',') {
                /* find the next comma, if present */
                for (k = j + 1; k < len && base[k] != ','; k++)
                    ;
                if (k < len) {
                    base[k] = '\0';
                }
                suffix = strdup(&base[j + 1]);
                if (k < len) {
                    base[k] = ',';
                }
                j = k - 1;
            } else {
                suffix = NULL;
            }
            pmix_output_verbose(1, pmix_preg_base_framework.framework_output,
                                 "regex:extract:nodes: parsing range %s %s %s", base, base + i,
                                 suffix);

            ret = regex_parse_value_ranges(base, base + i, num_digits, suffix, names);
            if (NULL != suffix) {
                free(suffix);
            }
            if (PMIX_SUCCESS != ret) {
                free(orig);
                return ret;
            }
            if (j + 1 < len && base[j + 1] == ',') {
                more_to_come = true;
                base = &base[j + 2];
            } else {
                more_to_come = false;
            }
        } else {
            /* If we didn't find a range, just add the value */
            if (PMIX_SUCCESS != (ret = PMIx_Argv_append_nosize(names, base))) {
                PMIX_ERROR_LOG(ret);
                free(orig);
                return ret;
            }
            /* step over the comma */
            i++;
            /* set base equal to the (possible) next base to look at */
            base = &base[i];
        }
    } while (more_to_come);

    free(orig);

    /* All done */
    return ret;
}

/*
 * Parse one or more ranges in a set
 *
 * @param base     The base text of the value name
 * @param *ranges  A pointer to a range. This can contain multiple ranges
 *                 (i.e. "1-3,10" or "5" or "9,0100-0130,250")
 * @param ***names An argv array to add the newly discovered values to
 */
static pmix_status_t regex_parse_value_ranges(char *base, char *ranges, int num_digits,
                                              char *suffix, char ***names)
{
    int i, len;
    pmix_status_t ret;
    char *start, *orig;

    /* Look for commas, the separator between ranges */

    len = strlen(ranges);
    for (orig = start = ranges, i = 0; i < len; ++i) {
        if (',' == ranges[i]) {
            ranges[i] = '\0';
            ret = regex_parse_value_range(base, start, num_digits, suffix, names);
            if (PMIX_SUCCESS != ret) {
                PMIX_ERROR_LOG(ret);
                return ret;
            }
            start = ranges + i + 1;
        }
    }

    /* Pick up the last range, if it exists */

    if (start < orig + len) {

        pmix_output_verbose(1, pmix_preg_base_framework.framework_output,
                             "regex:parse:ranges: parse range %s (2)", start);

        ret = regex_parse_value_range(base, start, num_digits, suffix, names);
        if (PMIX_SUCCESS != ret) {
            PMIX_ERROR_LOG(ret);
            return ret;
        }
    }

    /* All done */
    return PMIX_SUCCESS;
}

/*
 * Parse a single range in a set and add the full names of the values
 * found to the names argv
 *
 * @param base     The base text of the value name
 * @param *ranges  A pointer to a single range. (i.e. "1-3" or "5")
 * @param ***names An argv array to add the newly discovered values to
 */
static pmix_status_t regex_parse_value_range(char *base, char *range, int num_digits, char *suffix,
                                             char ***names)
{
    char *str, tmp[132];
    size_t i, k, start, end;
    size_t base_len, len;
    bool found;
    pmix_status_t ret;

    if (NULL == base || NULL == range) {
        return PMIX_ERROR;
    }

    len = strlen(range);
    base_len = strlen(base);
    /* Silence compiler warnings; start and end are always assigned
     properly, below */
    start = end = 0;

    /* Look for the beginning of the first number */

    for (found = false, i = 0; i < len; ++i) {
        if (isdigit((int) range[i])) {
            if (!found) {
                start = strtol(range + i, NULL, 10);
                found = true;
                break;
            }
        }
    }
    if (!found) {
        PMIX_ERROR_LOG(PMIX_ERR_NOT_FOUND);
        return PMIX_ERR_NOT_FOUND;
    }

    /* Look for the end of the first number */

    for (found = false; i < len; ++i) {
        if (!isdigit(range[i])) {
            break;
        }
    }

    /* Was there no range, just a single number? */

    if (i >= len) {
        end = start;
        found = true;
    } else {
        /* Nope, there was a range.  Look for the beginning of the second
         * number
         */
        for (; i < len; ++i) {
            if (isdigit(range[i])) {
                end = strtol(range + i, NULL, 10);
                found = true;
                break;
            }
        }
    }
    if (!found) {
        PMIX_ERROR_LOG(PMIX_ERR_NOT_FOUND);
        return PMIX_ERR_NOT_FOUND;
    }

    /* Make strings for all values in the range */

    len = base_len + num_digits + 32;
    if (NULL != suffix) {
        len += strlen(suffix);
    }
    str = (char *) malloc(len);
    if (NULL == str) {
        PMIX_ERROR_LOG(PMIX_ERR_OUT_OF_RESOURCE);
        return PMIX_ERR_OUT_OF_RESOURCE;
    }
    for (i = start; i <= end; ++i) {
        memset(str, 0, len);
        strcpy(str, base);
        /* we need to zero-pad the digits */
        for (k = 0; k < (size_t) num_digits; k++) {
            str[k + base_len] = '0';
        }
        memset(tmp, 0, 132);
        pmix_snprintf(tmp, 132, "%lu", (unsigned long) i);
        for (k = 0; k < strlen(tmp); k++) {
            str[base_len + num_digits - k - 1] = tmp[strlen(tmp) - k - 1];
        }
        /* if there is a suffix, add it */
        if (NULL != suffix) {
            strcat(str, suffix);
        }
        ret = PMIx_Argv_append_nosize(names, str);
        if (PMIX_SUCCESS != ret) {
            PMIX_ERROR_LOG(ret);
            free(str);
            return ret;
        }
    }
    free(str);

    /* All done */
    return PMIX_SUCCESS;
}

static pmix_status_t pmix_regex_extract_ppn(char *regexp, char ***procs)
{
    char **rngs, **nds, *t, **ps = NULL;
    int i, j, k, start, end;

    /* split on semi-colons for nodes */
    nds = PMIx_Argv_split(regexp, ';');
    for (j = 0; NULL != nds[j]; j++) {
        /* for each node, split it by comma */
        rngs = PMIx_Argv_split(nds[j], ',');
        /* parse each element */
        for (i = 0; NULL != rngs[i]; i++) {
            /* look for a range */
            if (NULL == (t = strchr(rngs[i], '-'))) {
                /* just one value */
                PMIx_Argv_append_nosize(&ps, rngs[i]);
            } else {
                /* handle the range */
                *t = '\0';
                start = strtol(rngs[i], NULL, 10);
                ++t;
                end = strtol(t, NULL, 10);
                for (k = start; k <= end; k++) {
                    if (0 > asprintf(&t, "%d", k)) {
                        PMIx_Argv_free(nds);
                        PMIx_Argv_free(rngs);
                        return PMIX_ERR_NOMEM;
                    }
                    PMIx_Argv_append_nosize(&ps, t);
                    free(t);
                }
            }
        }
        PMIx_Argv_free(rngs);
        /* create the node entry */
        t = PMIx_Argv_join(ps, ',');
        PMIx_Argv_append_nosize(procs, t);
        free(t);
        PMIx_Argv_free(ps);
        ps = NULL;
    }

    PMIx_Argv_free(nds);
    return PMIX_SUCCESS;
}

static pmix_status_t release(char *regexp)
{
    if (NULL == regexp) {
        return PMIX_SUCCESS;
    }

    if (0 != strncmp(regexp, "pmix", 4)) {
        return PMIX_ERR_TAKE_NEXT_OPTION;
    }
    free(regexp);
    return PMIX_SUCCESS;
}
