| /*****************************************************************************\ |
| * extra_constraints.c |
| ***************************************************************************** |
| * Copyright (C) SchedMD LLC. |
| * |
| * This file is part of Slurm, a resource management program. |
| * For details, see <https://slurm.schedmd.com/>. |
| * Please also read the included file: DISCLAIMER. |
| * |
| * Slurm is free software; you can redistribute it and/or modify it under |
| * the terms of the GNU General Public License as published by the Free |
| * Software Foundation; either version 2 of the License, or (at your option) |
| * any later version. |
| * |
| * In addition, as a special exception, the copyright holders give permission |
| * to link the code of portions of this program with the OpenSSL library under |
| * certain conditions as described in each individual source file, and |
| * distribute linked combinations including the two. You must obey the GNU |
| * General Public License in all respects for all of the code used other than |
| * OpenSSL. If you modify file(s) with this exception, you may extend this |
| * exception to your version of the file(s), but you are not obligated to do |
| * so. If you do not wish to do so, delete this exception statement from your |
| * version. If you delete this exception statement from all source files in |
| * the program, then also delete it here. |
| * |
| * Slurm is distributed in the hope that it will be useful, but WITHOUT ANY |
| * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
| * details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with Slurm; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| \*****************************************************************************/ |
| |
| #include <string.h> |
| |
| #include "src/common/extra_constraints.h" |
| #include "src/common/log.h" |
| #include "src/common/macros.h" |
| #include "src/common/xassert.h" |
| #include "src/common/xmalloc.h" |
| #include "src/common/xstring.h" |
| |
| #define _DEBUG 0 /* Set this to non-zero to see detailed debugging */ |
| |
| #define OP_BEGIN OP_CHILD_AND |
| |
| typedef enum { |
| CMP_INVALID = -2, |
| CMP_LT = -1, |
| CMP_EQ = 0, |
| CMP_GT = 1 |
| } cmp_t; |
| |
| typedef struct { |
| op_t op; |
| char *op_str; |
| } op_tbl_t; |
| |
| #define CHILDREN_LEN 2 /* Starting length of the children array */ |
| |
| static op_tbl_t op_table[] = { |
| { OP_NONE, NULL }, |
| { OP_CHILD_AND, "&" }, |
| { OP_CHILD_AND_COMMA, "," }, |
| { OP_CHILD_OR, "|" }, |
| { OP_LEAF_EQ, "=" }, |
| { OP_LEAF_NE, "!=" }, |
| { OP_LEAF_GT, ">" }, |
| { OP_LEAF_GTE, ">=" }, |
| { OP_LEAF_LT, "<" }, |
| { OP_LEAF_LTE, "<=" }, |
| }; |
| static const int op_table_len = ARRAY_SIZE(op_table); |
| |
| static const char *child_op_chars = ",&|"; |
| static const char *leaf_op_chars = "<>=!"; |
| static const char *op_chars = ",&|<>=!"; |
| |
| static bool extra_constraints_parsing = false; |
| |
| static char *_op2str(op_t op) |
| { |
| return op_table[op].op_str; |
| } |
| |
| static void _element2str(elem_t *el, int indent, char **str, char **pos) |
| { |
| char *newline = *pos ? "\n" : ""; |
| |
| if (el->children) { |
| xstrfmtcatat(*str, pos, "%s%*s{key:\"%s\", value:\"%s\", operator:\"%s\"(%d), num_children:%d, children:%p}", |
| newline, indent, "", el->key, el->value, |
| op_table[el->operator].op_str, el->operator, |
| el->num_children, el->children); |
| } else { |
| xstrfmtcatat(*str, pos, "%s%*s{key:\"%s\", value:\"%s\", operator:\"%s\"(%d)}", |
| newline, indent, "", el->key, el->value, |
| op_table[el->operator].op_str, el->operator); |
| } |
| } |
| |
| static void _tree2str_recursive(elem_t *el, int indent, char **str, char **pos) |
| { |
| if (!el) |
| return; |
| if (!el->num_children) { |
| /* leaf */ |
| _element2str(el, indent, str, pos); |
| return; |
| } |
| xassert(el->children); |
| _element2str(el, indent, str, pos); |
| for (int i = 0; i < el->num_children; i++) { |
| _tree2str_recursive(el->children[i], indent + 4, str, pos); |
| } |
| } |
| |
| extern char *extra_constraints_2str(elem_t *el) |
| { |
| char *pos = NULL; |
| char *output_str = NULL; |
| |
| _tree2str_recursive(el, 0, &output_str, &pos); |
| return output_str; |
| } |
| |
| #if _DEBUG |
| static void _log_element(elem_t *el) |
| { |
| char *str = NULL; |
| char *pos = NULL; |
| |
| _element2str(el, 0, &str, &pos); |
| info("%s", str); |
| xfree(str); |
| } |
| #endif |
| |
| static void _free_null_elem(elem_t **el) |
| { |
| if (*el) { |
| xfree((*el)->children); |
| xfree((*el)->key); |
| xfree((*el)->value); |
| xfree((*el)); |
| } |
| } |
| #define _free_element(el) _free_null_elem(&el) |
| |
| extern bool extra_constraints_enabled(void) |
| { |
| return extra_constraints_parsing; |
| } |
| |
| extern void extra_constraints_free_null(elem_t **el) |
| { |
| if (!(*el)) |
| return; |
| if (!(*el)->num_children) { |
| /* leaf */ |
| _free_element(*el); |
| return; |
| } else { |
| xassert((*el)->children); |
| for (int i = 0; i < (*el)->num_children; i++) { |
| _free_element((*el)->children[i]); |
| } |
| _free_element((*el)); |
| } |
| xfree((*el)); |
| } |
| |
| static elem_t *_alloc_tree(void) |
| { |
| return xmalloc(sizeof(elem_t)); |
| } |
| |
| static void _add_child(elem_t *parent, elem_t *child) |
| { |
| int num_children; |
| int curr_max; |
| |
| xassert(parent); |
| xassert(child); |
| |
| num_children = parent->num_children; |
| curr_max = parent->curr_max_children; |
| if (!parent->children) { |
| parent->children = |
| xcalloc(CHILDREN_LEN, |
| sizeof(*parent->children)); |
| curr_max = CHILDREN_LEN; |
| } else if (num_children == curr_max) { |
| curr_max = num_children * 2; |
| xrecalloc(parent->children, curr_max, |
| sizeof(*parent->children)); |
| } |
| parent->children[num_children] = child; |
| parent->num_children++; |
| parent->curr_max_children = curr_max; |
| } |
| |
| /* |
| * Given a string and a set of valid operator characters, return the matching |
| * operator. Also set *end_out to point to the first invalid character. |
| * Return OP_NONE if the operator was not found. |
| */ |
| static op_t _str2op(char *str, const char *valid_chars, char **end_out) |
| { |
| op_t op = OP_NONE; |
| char save_char; |
| char *end = str; |
| |
| xassert(xstrchr(valid_chars, *str)); |
| |
| while (*end) { |
| if (!xstrchr(valid_chars, *end)) |
| break; |
| end++; |
| } |
| save_char = *end; |
| *end = '\0'; |
| |
| for (int i = OP_BEGIN; i < op_table_len; i++) { |
| op_t tmp = op_table[i].op; |
| if (!xstrcmp(str, _op2str(tmp))) { |
| op = tmp; |
| break; |
| } |
| } |
| /* Automatically convert ',' to '&' */ |
| if (op == OP_CHILD_AND_COMMA) |
| op = OP_CHILD_AND; |
| |
| *end = save_char; |
| *end_out = end; |
| return op; |
| } |
| |
| /* |
| * Return true if the key or value is valid, false otherwise. |
| * A valid key/value is not an empty string and does not have any operator |
| * characters. |
| */ |
| static bool _valid_key_value(char *str) |
| { |
| char *p; |
| |
| if (!str || (*str == '\0')) |
| return false; |
| |
| while (*str) { |
| if ((p = xstrchr(op_chars, *str))) |
| return false; |
| str++; |
| } |
| return true; |
| } |
| |
| /* |
| * Leaf: |
| * <key><op><value> |
| * |
| * Return: SLURM_SUCCESS or SLURM_ERROR |
| */ |
| static elem_t *_parse_leaf(char *str) |
| { |
| char *key; |
| char *val = NULL; |
| char *op_ptr = NULL; |
| op_t op; |
| elem_t *leaf; |
| |
| if (!str) |
| return NULL; |
| |
| /* This is not a leaf if there are paren */ |
| xassert(!xstrchr(str, '(') && !xstrchr(str, ')')); |
| |
| if (*str == '\0') { |
| #if _DEBUG |
| error("Leaf is empty"); |
| #endif |
| return NULL; |
| } |
| |
| key = xstrdup(str); |
| |
| /* Find the first leaf operator character */ |
| op_ptr = key; |
| while (*op_ptr) { |
| if (xstrchr(leaf_op_chars, *op_ptr)) |
| break; |
| op_ptr++; |
| } |
| if (*op_ptr == '\0') { |
| #if _DEBUG |
| error("Could not find a leaf operator \"%s\" in \"%s\"", |
| leaf_op_chars, str); |
| #endif |
| xfree(key); |
| return NULL; |
| } |
| |
| /* Get the operator from the string and a pointer to value */ |
| op = _str2op(op_ptr, leaf_op_chars, &val); |
| |
| if (op == OP_NONE) { |
| /* |
| * The xstrchr check verified that an operator |
| * character exists, but not that the whole |
| * operator string is valid. For example, there |
| * could be repeating operator characters: |
| * xstrchr would return a pointer to the first |
| * one, but _str2op would correctly identify |
| * that the operator is invalid. |
| */ |
| #if _DEBUG |
| { |
| char save_char; |
| save_char = *val; |
| *val = '\0'; |
| error("Invalid operator string: \"%s\"", op_ptr); |
| *val = save_char; |
| } |
| #endif |
| xfree(key); |
| return NULL; |
| } |
| |
| /* NULL-terminate key */ |
| *op_ptr = '\0'; |
| |
| /* Check for invalid characters in key and value: operators */ |
| if (!_valid_key_value(key) || !_valid_key_value(val)) { |
| #if _DEBUG |
| error("Invalid key-op-value: %s", str); |
| #endif |
| xfree(key); |
| return NULL; |
| } |
| |
| /* Build an element */ |
| leaf = xmalloc(sizeof(*leaf)); |
| leaf->operator = op; |
| leaf->key = key; /* Already malloc'd */ |
| leaf->value = xstrdup(val); |
| |
| #if _DEBUG |
| _log_element(leaf); |
| #endif |
| |
| return leaf; |
| } |
| |
| static char *_find_leaf_end(char *str) |
| { |
| char *ptr = str; |
| |
| xassert(str); |
| |
| /* None of the following characters are allowed in a leaf */ |
| while (*ptr) { |
| if (xstrchr(child_op_chars, *ptr) || |
| (*ptr == '(') || (*ptr == ')')) |
| break; |
| ptr++; |
| } |
| return ptr; |
| } |
| |
| /* |
| * Parse a string like the following: |
| * (a=23&(b<=42|c=foo)&d>50)&e=bar |
| * |
| * Parentheses denote a level of the tree. |
| * There are two kinds of operators: child and leaf operators. They are defined |
| * in op_table. |
| * Any particular level of the tree has only one child operator. |
| * Leaves are: |
| * <key><leaf_op><value> |
| * |
| * Operators are not allowed in a key or value. |
| * |
| * The following should succeed: |
| * |
| * a=1 |
| * a=1,b=2 |
| * a=3&(b=asdf|c<24) |
| * (a=1|(b>=2)) |
| * zed<yam,(a=23&(b<=42|c=foo)&d>50)&e=bar |
| * |
| * Spaces are allowed and are considered part of the string: |
| * a= b |
| * |
| * |
| * The following should fail: |
| * |
| * Invalid leaf operator (',') |
| * a,<=6 |
| * |
| * Trailing operator: |
| * a<=6<= |
| * |
| * Multiple child operators in a row: |
| * a=5&&&b=5 |
| * a=5|||b=5 |
| * |
| * Multiple leaf operators in a row: |
| * a====5 |
| * b<=<=5 |
| * |
| * Paren without anything inside |
| * a=5&() |
| * |
| * Different operators at a single level |
| * a=5&b=5|c=5 |
| * (a=1)&(b=2)|(c=3) |
| * |
| * No operator given: |
| * a=1(b=2) |
| * (a=1)(b=2) |
| * (((a=1)b=2)) |
| */ |
| static void _recurse(char **str_ptr, int *level, elem_t *parent, int *rc) |
| { |
| elem_t *child; |
| bool got_leaf = false; |
| /* Implied operator before the first leaf on any level of the tree */ |
| bool got_op = true; |
| |
| xassert(str_ptr); |
| xassert(level); |
| xassert(parent); |
| xassert(rc); |
| |
| while (*rc == SLURM_SUCCESS) { |
| char save_char; |
| char *next; |
| |
| #if _DEBUG |
| info("level=%d, string=\"%s\"", *level, *str_ptr); |
| #endif |
| if (**str_ptr == '\0') { |
| /* End of string. */ |
| if (got_op || !got_leaf) { |
| #if _DEBUG |
| error("Did not end in a leaf. Invalid string"); |
| #endif |
| *rc = SLURM_ERROR; |
| } |
| break; |
| } |
| |
| /* |
| * These first two checks go deeper or shallower in the tree. |
| * Make sure to update str_ptr. |
| * |
| * We can have multiple '(' or ')' in a row. |
| */ |
| if (**str_ptr == '(') { |
| elem_t *child; |
| |
| if (!got_op) { |
| #if _DEBUG |
| error("No boolean operator before leaf. Invalid string: \"%s\"", |
| *str_ptr); |
| #endif |
| *rc = SLURM_ERROR; |
| break; |
| } |
| |
| /* Create a child for this new level and recurse */ |
| child = xmalloc(sizeof(*child)); |
| _add_child(parent, child); |
| *level = *level + 1; |
| (*str_ptr) = *str_ptr + 1; |
| _recurse(str_ptr, level, child, rc); |
| /* |
| * Returned after a ')' is found. |
| * Treat the contents within the parentheses as a leaf |
| * since we expect an operator to come after the |
| * closing paren. |
| */ |
| got_leaf = true; |
| got_op = false; |
| continue; |
| } else if (**str_ptr == ')') { |
| (*str_ptr) = *str_ptr + 1; |
| if (*level) { |
| *level = *level - 1; |
| } else { |
| #if _DEBUG |
| error("Unbalanced parentheses"); |
| #endif |
| *rc = SLURM_ERROR; |
| /* |
| * Do not return because we were not recursively |
| * called to get here. |
| */ |
| break; |
| } |
| if (got_op || !got_leaf) { |
| #if _DEBUG |
| error("Invalid expression inside parentheses, did not end in leaf"); |
| #endif |
| *rc = SLURM_ERROR; |
| } |
| |
| return; |
| } |
| |
| /* Check if we are at a child operator. */ |
| if (xstrchr(child_op_chars, **str_ptr)) { |
| char *tmp_end = NULL; |
| op_t op; |
| |
| if (!got_leaf) { |
| #if _DEBUG |
| error("Got boolean operator without a leaf. Invalid string: \"%s\"", |
| *str_ptr); |
| #endif |
| *rc = SLURM_ERROR; |
| break; |
| } |
| |
| op = _str2op(*str_ptr, child_op_chars, &tmp_end); |
| |
| if (op == OP_NONE) { |
| /* |
| * The xstrchr check verified that an operator |
| * character exists, but not that the whole |
| * operator string is valid. For example, there |
| * could be repeating operator characters: |
| * xstrchr would return a pointer to the first |
| * one, but _str2op would correctly identify |
| * that the operator is invalid. |
| */ |
| #if _DEBUG |
| save_char = *tmp_end; |
| *tmp_end = '\0'; |
| error("Invalid operator string: \"%s\"", |
| *str_ptr); |
| *tmp_end = save_char; |
| #endif |
| *rc = SLURM_ERROR; |
| break; |
| } |
| |
| /* All operators in a single level must be the same. */ |
| if ((parent->operator != OP_NONE) && |
| (parent->operator != op)) { |
| #if _DEBUG |
| error("Operators at a single level must be the same. Got \"%s\" but parent op is \"%s\"", |
| _op2str(op), |
| _op2str(parent->operator)); |
| #endif |
| *rc = SLURM_ERROR; |
| break; |
| } else { |
| parent->operator = op; |
| } |
| *str_ptr = tmp_end; |
| got_op = true; |
| got_leaf = false; |
| continue; |
| } |
| |
| if (!got_op) { |
| #if _DEBUG |
| error("No boolean operator before leaf. Invalid string: \"%s\"", |
| *str_ptr); |
| #endif |
| *rc = SLURM_ERROR; |
| break; |
| } |
| |
| /* |
| * This is a leaf. |
| * NULL-terminate the leaf string and create the leaf at the |
| * the next paren or child operator, or end of string. |
| * Then continue parsing at the next paren or child operator. |
| */ |
| next = _find_leaf_end(*str_ptr); |
| xassert(next); |
| |
| save_char = *next; |
| *next = '\0'; |
| |
| if (!(child = _parse_leaf(*str_ptr))) { |
| xfree(child); |
| *rc = SLURM_ERROR; |
| break; |
| } else |
| _add_child(parent, child); |
| |
| *next = save_char; |
| *str_ptr = next; |
| got_op = false; |
| got_leaf = true; |
| } |
| |
| /* |
| * Underflow should not happen - we should be catching potential |
| * underflow conditions and return an error instead. |
| */ |
| xassert(*level >= 0); |
| |
| if (*level) { |
| /* Unbalanced parentheses or parsing error */ |
| #if _DEBUG |
| if (*rc != SLURM_ERROR) |
| error("Unbalanced parentheses"); |
| #endif |
| *rc = SLURM_ERROR; |
| } |
| } |
| |
| #define NUMBER_COMPARE(a,b,fuzzy,result) \ |
| do {\ |
| if (fuzzy && fuzzy_equal(a, b))\ |
| result = CMP_EQ;\ |
| else if (!fuzzy && (a == b))\ |
| result = CMP_EQ;\ |
| else if (a < b)\ |
| result = CMP_LT;\ |
| else\ |
| result = CMP_GT;\ |
| } while(0); |
| |
| |
| /* |
| * Test if "data" equals, is less than, or is greater than "value" |
| * data.c already has data_check_match(); however, that only checks for |
| * equality and is stricter than we want to be here. |
| */ |
| static cmp_t _compare(data_t *data, char *value) |
| { |
| cmp_t comparison; |
| data_type_t data_type; |
| data_t *value_d; |
| |
| xassert(value); |
| xassert(data); |
| |
| value_d = data_new(); |
| if (!data_set_string(value_d, value)) { |
| data_free(value_d); |
| #if _DEBUG |
| error("%s: Couldn't convert %s to data_t", __func__, value); |
| #endif |
| return CMP_INVALID; |
| } |
| data_type = data_get_type(data); |
| |
| switch (data_type) { |
| case DATA_TYPE_INT_64: |
| { |
| /* |
| * We always do floating point comparison to be less strict on |
| * the user, and if the node data sometimes swaps between |
| * integer and floating point on node updates. |
| */ |
| double tmp1 = (double) data_get_int(data); |
| double tmp2; |
| |
| if (data_convert_type(value_d, DATA_TYPE_FLOAT) != |
| DATA_TYPE_FLOAT) { |
| comparison = CMP_INVALID; |
| } else { |
| tmp2 = data_get_float(value_d); |
| NUMBER_COMPARE(tmp1, tmp2, true, comparison); |
| } |
| break; |
| } |
| case DATA_TYPE_STRING: |
| /* |
| * NOTE: strcmp is not guaranteed to return -1, 0, or 1. It |
| * is guaranteed to return a negative number, zero, or a |
| * positive number. Convert those to our CMP_* values. |
| */ |
| comparison = xstrcmp(data_get_string(data), value); |
| if (comparison < 0) |
| comparison = CMP_LT; |
| else if (comparison > 0) |
| comparison = CMP_GT; |
| else |
| comparison = CMP_EQ; |
| break; |
| case DATA_TYPE_FLOAT: |
| { |
| double tmp1 = data_get_float(data); |
| double tmp2; |
| |
| if (data_convert_type(value_d, DATA_TYPE_FLOAT) != |
| DATA_TYPE_FLOAT) { |
| comparison = CMP_INVALID; |
| } else { |
| tmp2 = data_get_float(value_d); |
| NUMBER_COMPARE(tmp1, tmp2, true, comparison); |
| } |
| break; |
| } |
| case DATA_TYPE_BOOL: |
| { |
| bool tmp1 = data_get_bool(data); |
| bool tmp2; |
| |
| if (data_convert_type(value_d, DATA_TYPE_BOOL) != |
| DATA_TYPE_BOOL) { |
| comparison = CMP_INVALID; |
| } else { |
| tmp2 = data_get_bool(value_d); |
| NUMBER_COMPARE(tmp1, tmp2, false, comparison); |
| } |
| break; |
| } |
| default: |
| comparison = CMP_INVALID; |
| #if _DEBUG |
| info("%s: Data type: %s is invalid", |
| __func__, data_type_to_string(data_type)); |
| #endif |
| break; |
| } |
| FREE_NULL_DATA(value_d); |
| return comparison; |
| } |
| |
| static bool _test(cmp_t comparison, op_t op) |
| { |
| bool rc; |
| |
| if (op == OP_LEAF_EQ) { |
| rc = (comparison == CMP_EQ); |
| } else if (op == OP_LEAF_NE) { |
| rc = (comparison != CMP_EQ); |
| } else if (op == OP_LEAF_GT) { |
| rc = (comparison == CMP_GT); |
| } else if (op == OP_LEAF_GTE) { |
| rc = (comparison >= CMP_EQ); |
| } else if (op == OP_LEAF_LT) { |
| rc = (comparison == CMP_LT); |
| } else if (op == OP_LEAF_LTE) { |
| rc = (comparison <= CMP_EQ); |
| } else { |
| error("%s: Undefined leaf operator %d", __func__, op); |
| rc = false; |
| } |
| |
| return rc; |
| } |
| |
| /* |
| * Test each leaf: the test is true if <data_value> <leaf_op> <leaf_value> |
| * For each test, the key needs to exist in the data_t structure. |
| */ |
| static bool _test_extra_constraints(elem_t *parent, elem_t *el, data_t *data) |
| { |
| bool test_result = false; |
| |
| if (!el) |
| return false; |
| if (!el->num_children) { |
| /* leaf */ |
| data_t *data_ptr = NULL; |
| cmp_t comparison = CMP_INVALID; |
| #if _DEBUG |
| char *data_str = NULL; |
| #endif |
| |
| /* Check that key is in data_t */ |
| data_ptr = data_key_get(data, el->key); |
| if (!data_ptr) { |
| #if _DEBUG |
| info("%s: Key %s not found", __func__, el->key); |
| #endif |
| return false; |
| } |
| #if _DEBUG |
| if (data_get_string_converted(data_ptr, &data_str) != |
| SLURM_SUCCESS) { |
| /* Couldn't convert to string. */ |
| data_str = xstrdup_printf("<Couldn't convert data to string>"); |
| } |
| #endif |
| comparison = _compare(data_ptr, el->value); |
| if (comparison == CMP_INVALID) { |
| #if _DEBUG |
| info("%s: Invalid comparison: \"%s\" %s \"%s\"", |
| __func__, el->value, _op2str(el->operator), |
| data_str); |
| xfree(data_str); |
| #endif |
| return false; |
| } |
| test_result = _test(comparison, el->operator); |
| #if _DEBUG |
| info("%s: Comparison result=%s: \"%s\" %s \"%s\"", |
| __func__, test_result ? "true" : "false", |
| data_str, _op2str(el->operator), el->value); |
| xfree(data_str); |
| #endif |
| |
| return test_result; |
| } |
| |
| xassert(el->children); |
| for (int i = 0; i < el->num_children; i++) { |
| test_result = _test_extra_constraints(el, el->children[i], |
| data); |
| if (el->operator == OP_CHILD_OR) { |
| /* OR: At least one child must pass. */ |
| if (test_result) |
| break; |
| else |
| continue; |
| } else { |
| /* |
| * OP_CHILD_AND or OP_CHILD_NONE which is treated the |
| * same as OP_CHILD_AND. |
| * AND: All children must pass. |
| */ |
| if (test_result) |
| continue; |
| else |
| break; |
| } |
| } |
| return test_result; |
| } |
| |
| /* |
| * Parse a string into a tree |
| */ |
| extern int extra_constraints_parse(char *extra, elem_t **head) |
| { |
| int rc = SLURM_SUCCESS; |
| int level = 0; |
| char *copy, *copy_start; |
| elem_t *tree_head; |
| |
| xassert(head); |
| |
| if (!extra) |
| return SLURM_SUCCESS; |
| if (!extra_constraints_parsing) |
| return SLURM_SUCCESS; |
| |
| #if _DEBUG |
| info("%s: parse %s", __func__, extra); |
| #endif |
| |
| copy = xstrdup(extra); |
| tree_head = _alloc_tree(); |
| /* |
| * _recurse modifies the string pointer, so save a copy of the pointer |
| * to the beginning of the string so it can be free'd. |
| */ |
| copy_start = copy; /* Save a pointer to the beginning of the string */ |
| _recurse(©, &level, tree_head, &rc); |
| if (rc != SLURM_SUCCESS) { |
| error("%s: Parsing %s failed", __func__, extra); |
| FREE_NULL_EXTRA_CONSTRAINTS(tree_head); |
| rc = ESLURM_INVALID_EXTRA; |
| } else { |
| if (tree_head->operator == OP_NONE) { |
| /* |
| * This should only happen if the request was |
| * structured such that the parent has only one child. |
| * In that case, set the operator to AND as the |
| * default. |
| */ |
| xassert(tree_head->num_children == 1); |
| tree_head->operator = OP_CHILD_AND; |
| } |
| #if _DEBUG |
| info("%s: Succeeded parsing %s", __func__, extra); |
| #endif |
| } |
| |
| #if _DEBUG |
| { |
| char *str = extra_constraints_2str(tree_head); |
| info("\n%s", str); |
| xfree(str); |
| } |
| #endif |
| |
| *head = tree_head; |
| xfree(copy_start); |
| return rc; |
| } |
| |
| extern void extra_constraints_set_parsing(bool set) |
| { |
| extra_constraints_parsing = set; |
| } |
| |
| extern bool extra_constraints_test(elem_t *head, data_t *data) |
| { |
| if (!extra_constraints_parsing) |
| return true; |
| if (!head) |
| return true; |
| if (!data) { |
| return false; |
| } |
| |
| return _test_extra_constraints(NULL, head, data); |
| } |