/* * 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 #endif #include #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #include #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(®exargs, 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(®exargs, 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; }