diff --git a/.kiro/specs/job-array-one-based-index/tasks.md b/.kiro/specs/job-array-one-based-index/tasks.md index 258e8d7a..a170e605 100644 --- a/.kiro/specs/job-array-one-based-index/tasks.md +++ b/.kiro/specs/job-array-one-based-index/tasks.md @@ -1,7 +1,7 @@ # Implementation Plan - [ ] 1. Foundation: bounded job identities and array ID allocation in libxce -- [ ] 1.1 Add the bounded job-identity and element-selection parser +- [x] 1.1 Add the bounded job-identity and element-selection parser - Parse a written identity with an optional single element or element range under the fixed grammar (no sign, no inner whitespace, optional surrounding spaces/tabs), accumulating digits with an overflow guard - Evaluate the checks in the fixed order: syntax, field widths, alignment of the array identity when an element part is present, element-number bound, reversed range; report only the first failure - Provide the array-marking test (tag equals the array tag only), the element-identity composition helper and a message for each rejection diff --git a/src/include/utils.h b/src/include/utils.h index 96cde1b4..878947c2 100644 --- a/src/include/utils.h +++ b/src/include/utils.h @@ -218,6 +218,65 @@ extern int parse_jobid(uint64_t*, char*); extern uint64_t array_jobid(uint64_t); extern uint64_t array_idx(uint64_t); +/* + * Bounded job-identity / element-selection parser (job-array-one-based-index, + * Req 1.5, 5.7, 5.8, 5.12, 5.14, 5.15, 8.1). + * + * Grammar: optional leading/trailing spaces or tabs, then + * `num-num-num-num` (cluster-apiserver-timestamp-sequence), optionally + * followed by `[num]` or `[num-num]`. `num` is one or more decimal digits + * (leading zeros allowed, no sign, no inner whitespace). + * + * Checks, in this order; only the first failure is reported: + * JOBSEL_ESYNTAX grammar (5.15) + * JOBSEL_EWIDTH a field wider than its bit width (5.14) + * JOBSEL_EALIGN element part present but sequence % 32768 != 0 (5.8) + * JOBSEL_EINDEX an element number above JOBID_FIELD_MAX_INDEX (5.7) + * JOBSEL_EREVERSED range whose last number is below its first (5.5) + * Digits are accumulated with an overflow guard, so oversized numbers fail + * the width or index check instead of wrapping. + * + * On JOBSEL_OK: BARE -> `key` is the exact untagged ID, lo == hi == 0; + * SINGLE -> `key` is the array ID (tag 0, low 15 bits clear), lo == hi == k; + * RANGE -> `key` is the array ID, [lo, hi]. A NULL `text` is + * JOBSEL_ESYNTAX; `out` must not be NULL. + */ +#define JOBID_FIELD_MAX_CLUSTER MAX_CLUSTER_ID +#define JOBID_FIELD_MAX_APISERVER MAX_APISERVER_ID +#define JOBID_FIELD_MAX_TIMESTAMP (UMAX_BITS(N_TIMESTAMP_BITS)) +#define JOBID_FIELD_MAX_SEQUENCE (UMAX_BITS(N_SEQUENCE_BITS)) +#define JOBID_FIELD_MAX_INDEX MAX_ARRAY_IDX + +typedef enum { + JOBSEL_OK = 0, + JOBSEL_ESYNTAX, /* 5.15 */ + JOBSEL_EWIDTH, /* 5.14 */ + JOBSEL_EALIGN, /* 5.8 */ + JOBSEL_EINDEX, /* 5.7 */ + JOBSEL_EREVERSED /* 5.5 */ +} jobsel_err_t; + +typedef enum { + JOBSEL_BARE = 0, + JOBSEL_SINGLE, + JOBSEL_RANGE +} jobsel_form_t; + +typedef struct { + uint64_t key; /* BARE: the exact (untagged) ID; else the array ID */ + jobsel_form_t form; + uint32_t lo; /* SINGLE: lo == hi == k */ + uint32_t hi; +} jobsel_t; + +extern jobsel_err_t parse_jobsel(const char* text, jobsel_t* out); +extern const char* jobsel_strerror(jobsel_err_t err); +/* Non-zero iff the ID's tag equals TAG_JOB_ARRAY (tags 2 and 3 are not). */ +extern int is_array_tag(uint64_t jobid); +/* Element ID of number k (masked to 15 bits) in the array `s->key`; 0 if + * `s` is NULL. */ +extern uint64_t jobsel_element_id(const jobsel_t* s, uint32_t k); + /* * xce_units_mode_t / xce_format_bytes_human() / xce_is_bytes_valued_resource() * / xce_format_units_value() -- scheduler-sim-diagnostics spec, task 7 (Req diff --git a/src/lib/meson.build b/src/lib/meson.build index 332dbd93..302cf342 100644 --- a/src/lib/meson.build +++ b/src/lib/meson.build @@ -183,6 +183,7 @@ libxce_test_src = ['testsuite/test.c', 'testsuite/test_zarray.c', 'testsuite/test_config.c', 'testsuite/test_idgen.c', + 'testsuite/test_jobsel.c', 'testsuite/test_delay.c', 'testsuite/test_ordq.c', 'testsuite/test_preason.c', diff --git a/src/lib/testsuite/test.c b/src/lib/testsuite/test.c index f12510c8..a349e002 100644 --- a/src/lib/testsuite/test.c +++ b/src/lib/testsuite/test.c @@ -13,6 +13,7 @@ extern void test_error(void); extern void test_utils(void); extern void test_config(void); extern void test_idgen(void); +extern void test_jobsel(void); extern void test_preason(void); extern void test_ordq(void); extern void test_delay(void); @@ -136,6 +137,7 @@ int main(int argc, char* argv[]) UNITY_BEGIN(); TEST(test_config); TEST(test_idgen); + TEST(test_jobsel); TEST(test_ht_string); TEST(test_ht_int); TEST(test_zarray); diff --git a/src/lib/testsuite/test_jobsel.c b/src/lib/testsuite/test_jobsel.c new file mode 100644 index 00000000..efa5714f --- /dev/null +++ b/src/lib/testsuite/test_jobsel.c @@ -0,0 +1,270 @@ +/* + * test_jobsel.c -- job-array-one-based-index, task 1.1. + * + * Runs the identity rows of the shared `selection-cases` conformance table + * (design.md, Testing Strategy) against parse_jobsel(), plus the helpers + * is_array_tag(), jobsel_element_id() and jobsel_strerror(). + * + * The C surface has no decimal keys and no name selections, so the 5.17 and + * name rows of the shared table do not apply here. `X` below stands for a + * valid, array-capable written identity (`3-5-1700000000-65536`: sequence is + * a multiple of 32768). + */ +#include +#include +#include +#include + +#include "config.h" +#include "utils.h" + +#define X_TXT "3-5-1700000000-65536" + +/* MAKE_JOBID(3, 5, 1700000000, 0, 65536), the array ID / bare key of X. */ +#define X_KEY MAKE_JOBID(3, 5, 1700000000u, 0x0, 65536) + +typedef struct { + const char* text; + jobsel_err_t err; + jobsel_form_t form; /* checked on JOBSEL_OK only */ + uint64_t key; /* checked on JOBSEL_OK only */ + uint32_t lo; + uint32_t hi; +} jobsel_case_t; + +/* clang-format off */ +static const jobsel_case_t selection_cases[] = { + /* Valid */ + { X_TXT, JOBSEL_OK, JOBSEL_BARE, X_KEY, 0, 0 }, + { X_TXT "[1]", JOBSEL_OK, JOBSEL_SINGLE, X_KEY, 1, 1 }, + { X_TXT "[0]", JOBSEL_OK, JOBSEL_SINGLE, X_KEY, 0, 0 }, /* 8.1 */ + { X_TXT "[01]", JOBSEL_OK, JOBSEL_SINGLE, X_KEY, 1, 1 }, /* 1.5 */ + { X_TXT "[1-3]", JOBSEL_OK, JOBSEL_RANGE, X_KEY, 1, 3 }, + { X_TXT "[3-3]", JOBSEL_OK, JOBSEL_RANGE, X_KEY, 3, 3 }, + { X_TXT "[32767]", JOBSEL_OK, JOBSEL_SINGLE, X_KEY, 32767, 32767 }, + { X_TXT "[001-0003]", JOBSEL_OK, JOBSEL_RANGE, X_KEY, 1, 3 }, + /* surrounding spaces/tabs are ignored */ + { " \t" X_TXT "[2] \t", JOBSEL_OK, JOBSEL_SINGLE, X_KEY, 2, 2 }, + { " " X_TXT " ", JOBSEL_OK, JOBSEL_BARE, X_KEY, 0, 0 }, + /* leading zeros in identity fields are the same number */ + { "03-05-01700000000-065536[2]", JOBSEL_OK, JOBSEL_SINGLE, X_KEY, 2, 2 }, + /* bare ordinary identity: no alignment requirement */ + { "3-5-1700000000-65541", JOBSEL_OK, JOBSEL_BARE, + MAKE_JOBID(3, 5, 1700000000u, 0x0, 65541), 0, 0 }, + /* every field at its maximum width */ + { "63-15-4294967295-1048575", JOBSEL_OK, JOBSEL_BARE, + MAKE_JOBID(63, 15, 4294967295u, 0x0, 1048575), 0, 0 }, + { "63-15-4294967295-1015808[32767]", JOBSEL_OK, JOBSEL_SINGLE, + MAKE_JOBID(63, 15, 4294967295u, 0x0, 1015808), 32767, 32767 }, + { "0-0-0-0[0-32767]", JOBSEL_OK, JOBSEL_RANGE, 0, 0, 32767 }, + + /* Syntax (5.15) */ + { X_TXT " [1]", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { X_TXT "[a]", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { X_TXT "[]", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { X_TXT "[1", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { X_TXT "]", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { X_TXT "[+1]", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { X_TXT "[-1]", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { X_TXT "[ 1]", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { X_TXT "[1 ]", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { X_TXT "[1-3:2]", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { X_TXT "[1]x", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { X_TXT "[1-2-3]", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { X_TXT "[1,3]", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { X_TXT "[1][2]", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { X_TXT "[1-]", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { X_TXT "[-3]", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { X_TXT "x", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { X_TXT "\n", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { "", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { " \t ", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { "[1]", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { "3-5-1700000000", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { "3-5-1700000000-65536-1", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { "-5-1700000000-65536", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { "3--1700000000-65536", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { "3-5-1700000000-", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { "+3-5-1700000000-65536", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { "3 -5-1700000000-65536", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { "3-5-17000 00000-65536", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { "123456", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + { "foo[1]", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + /* syntax is checked before everything else, even huge fields */ + { "99999999999999999999-0-0-0[1]x", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, + + /* Widths (5.14) -- with and without an element part */ + { "64-0-1700000000-32768[1]", JOBSEL_EWIDTH, 0, 0, 0, 0 }, + { "0-16-1700000000-32768[1]", JOBSEL_EWIDTH, 0, 0, 0, 0 }, + { "0-0-4294967296-32768[1]", JOBSEL_EWIDTH, 0, 0, 0, 0 }, + { "0-0-1700000000-1048576[1]", JOBSEL_EWIDTH, 0, 0, 0, 0 }, + { "64-0-1700000000-32768", JOBSEL_EWIDTH, 0, 0, 0, 0 }, + { "0-16-1700000000-32768", JOBSEL_EWIDTH, 0, 0, 0, 0 }, + { "0-0-4294967296-32768", JOBSEL_EWIDTH, 0, 0, 0, 0 }, + { "0-0-1700000000-1048576", JOBSEL_EWIDTH, 0, 0, 0, 0 }, + /* too large to represent: must not wrap into a valid value */ + { "18446744073709551616-0-0-0", JOBSEL_EWIDTH, 0, 0, 0, 0 }, + { "0-0-0-99999999999999999999999", JOBSEL_EWIDTH, 0, 0, 0, 0 }, + { "0-0-18446744073709551616-0[1]", JOBSEL_EWIDTH, 0, 0, 0, 0 }, + + /* Alignment (5.8) -- only when an element part is present */ + { "3-5-1700000000-5[3]", JOBSEL_EALIGN, 0, 0, 0, 0 }, + { "3-5-1700000000-65537[1-2]", JOBSEL_EALIGN, 0, 0, 0, 0 }, + { "3-5-1700000000-32767[0]", JOBSEL_EALIGN, 0, 0, 0, 0 }, + + /* Index (5.7) */ + { X_TXT "[32768]", JOBSEL_EINDEX, 0, 0, 0, 0 }, + { X_TXT "[1-40000]", JOBSEL_EINDEX, 0, 0, 0, 0 }, + { X_TXT "[40000-1]", JOBSEL_EINDEX, 0, 0, 0, 0 }, + { X_TXT "[99999999999999999999]", JOBSEL_EINDEX, 0, 0, 0, 0 }, + { X_TXT "[18446744073709551617]", JOBSEL_EINDEX, 0, 0, 0, 0 }, + { X_TXT "[4294967297]", JOBSEL_EINDEX, 0, 0, 0, 0 }, + + /* Reversed (5.5) */ + { X_TXT "[3-1]", JOBSEL_EREVERSED, 0, 0, 0, 0 }, + { X_TXT "[32767-0]", JOBSEL_EREVERSED, 0, 0, 0, 0 }, + + /* Precedence (5.12): first failing check is the only one reported */ + { "0-0-1700000000-1048576[40000]", JOBSEL_EWIDTH, 0, 0, 0, 0 }, + { "3-5-1700000000-5[3-1]", JOBSEL_EALIGN, 0, 0, 0, 0 }, + { "3-5-1700000000-5[40000]", JOBSEL_EALIGN, 0, 0, 0, 0 }, + { "64-0-1700000000-5[3-1]", JOBSEL_EWIDTH, 0, 0, 0, 0 }, + { "64-0-1700000000-5 [3-1]", JOBSEL_ESYNTAX, 0, 0, 0, 0 }, +}; +/* clang-format on */ + +static void test_jobsel_selection_cases(void) +{ + size_t i; + + for (i = 0; i < sizeof(selection_cases) / sizeof(selection_cases[0]); i++) { + const jobsel_case_t* c = &selection_cases[i]; + jobsel_t sel; + jobsel_err_t err; + char msg[256]; + + memset(&sel, 0xa5, sizeof(sel)); + err = parse_jobsel(c->text, &sel); + + snprintf(msg, sizeof(msg), "row %zu: \"%s\" (got %d, want %d)", i, + c->text, (int)err, (int)c->err); + TEST_ASSERT_EQUAL_INT_MESSAGE(c->err, err, msg); + + if (c->err != JOBSEL_OK) { + continue; + } + + TEST_ASSERT_EQUAL_INT_MESSAGE(c->form, sel.form, msg); + TEST_ASSERT_EQUAL_UINT64_MESSAGE(c->key, sel.key, msg); + TEST_ASSERT_EQUAL_UINT32_MESSAGE(c->lo, sel.lo, msg); + TEST_ASSERT_EQUAL_UINT32_MESSAGE(c->hi, sel.hi, msg); + + if (sel.form != JOBSEL_BARE) { + /* postcondition: array ID has tag 0 and low 15 bits clear */ + TEST_ASSERT_EQUAL_UINT64_MESSAGE(0, EXTRACT_ID_TAG(sel.key), msg); + TEST_ASSERT_EQUAL_UINT64_MESSAGE(0, sel.key & MAX_ARRAY_IDX, msg); + } + } +} + +static void test_jobsel_null(void) +{ + jobsel_t sel; + + TEST_ASSERT_EQUAL_INT(JOBSEL_ESYNTAX, parse_jobsel(NULL, &sel)); +} + +static void test_jobsel_field_max_constants(void) +{ + TEST_ASSERT_EQUAL_UINT64(63, JOBID_FIELD_MAX_CLUSTER); + TEST_ASSERT_EQUAL_UINT64(15, JOBID_FIELD_MAX_APISERVER); + TEST_ASSERT_EQUAL_UINT64(4294967295ull, JOBID_FIELD_MAX_TIMESTAMP); + TEST_ASSERT_EQUAL_UINT64(1048575ull, JOBID_FIELD_MAX_SEQUENCE); + TEST_ASSERT_EQUAL_UINT64(32767, JOBID_FIELD_MAX_INDEX); +} + +static void test_jobsel_is_array_tag(void) +{ + uint64_t base = MAKE_JOBID(3, 5, 1700000000u, 0x0, 65536); + + TEST_ASSERT_FALSE(is_array_tag(base)); + TEST_ASSERT_FALSE(is_array_tag(base | 7)); + TEST_ASSERT_TRUE( + is_array_tag(base | ((uint64_t)TAG_JOB_ARRAY << TAG_SHIFT) | 1)); + TEST_ASSERT_TRUE(is_array_tag((uint64_t)TAG_JOB_ARRAY << TAG_SHIFT)); + /* tag equals the array tag only: tags 2 and 3 are not arrays */ + TEST_ASSERT_FALSE(is_array_tag(base | ((uint64_t)0x2 << TAG_SHIFT))); + TEST_ASSERT_FALSE(is_array_tag(base | ((uint64_t)0x3 << TAG_SHIFT))); + TEST_ASSERT_FALSE(is_array_tag(0)); +} + +static void test_jobsel_element_id(void) +{ + jobsel_t sel; + uint32_t ks[] = { 0, 1, 2, 3, 32767 }; + size_t i; + + TEST_ASSERT_EQUAL_INT(JOBSEL_OK, parse_jobsel(X_TXT "[0-32767]", &sel)); + + for (i = 0; i < sizeof(ks) / sizeof(ks[0]); i++) { + uint64_t elem = jobsel_element_id(&sel, ks[i]); + char buf[128]; + char want[128]; + + TEST_ASSERT_EQUAL_UINT64( + MAKE_JOBID(3, 5, 1700000000u, TAG_JOB_ARRAY, 65536 | ks[i]), elem); + TEST_ASSERT_TRUE(is_array_tag(elem)); + TEST_ASSERT_EQUAL_UINT64(sel.key, array_jobid(elem)); + TEST_ASSERT_EQUAL_UINT64(ks[i], array_idx(elem)); + + /* round trip through the display form */ + TEST_ASSERT_TRUE(fmt_jobid(buf, sizeof(buf), elem) > 0); + snprintf(want, sizeof(want), "%s[%u]", X_TXT, ks[i]); + TEST_ASSERT_EQUAL_STRING(want, buf); + } + + /* the widest identity composes without spilling into other fields */ + TEST_ASSERT_EQUAL_INT( + JOBSEL_OK, parse_jobsel("63-15-4294967295-1015808[32767]", &sel)); + TEST_ASSERT_EQUAL_UINT64( + MAKE_JOBID(63, 15, 4294967295u, TAG_JOB_ARRAY, 1015808 | 32767), + jobsel_element_id(&sel, 32767)); + TEST_ASSERT_EQUAL_UINT64(sel.key, + array_jobid(jobsel_element_id(&sel, 32767))); + + /* k is masked to the 15-bit index field */ + TEST_ASSERT_EQUAL_UINT64(jobsel_element_id(&sel, 5), + jobsel_element_id(&sel, 32768 + 5)); + TEST_ASSERT_EQUAL_UINT64(0, jobsel_element_id(NULL, 1)); +} + +static void test_jobsel_strerror(void) +{ + jobsel_err_t errs[] = { JOBSEL_OK, JOBSEL_ESYNTAX, JOBSEL_EWIDTH, + JOBSEL_EALIGN, JOBSEL_EINDEX, JOBSEL_EREVERSED }; + size_t i, j; + + for (i = 0; i < sizeof(errs) / sizeof(errs[0]); i++) { + const char* m = jobsel_strerror(errs[i]); + + TEST_ASSERT_NOT_NULL(m); + TEST_ASSERT_TRUE(strlen(m) > 0); + for (j = 0; j < i; j++) { + TEST_ASSERT_TRUE(strcmp(m, jobsel_strerror(errs[j])) != 0); + } + } + + TEST_ASSERT_NOT_NULL(strstr(jobsel_strerror(JOBSEL_EINDEX), "32767")); + TEST_ASSERT_NOT_NULL(strstr(jobsel_strerror(JOBSEL_EREVERSED), "revers")); + TEST_ASSERT_NOT_NULL(jobsel_strerror((jobsel_err_t)99)); +} + +void test_jobsel(void) +{ + RUN_TEST(test_jobsel_selection_cases); + RUN_TEST(test_jobsel_null); + RUN_TEST(test_jobsel_field_max_constants); + RUN_TEST(test_jobsel_is_array_tag); + RUN_TEST(test_jobsel_element_id); + RUN_TEST(test_jobsel_strerror); +} diff --git a/src/lib/utils.c b/src/lib/utils.c index 85fd6e28..4c822fd9 100644 --- a/src/lib/utils.c +++ b/src/lib/utils.c @@ -709,6 +709,174 @@ uint64_t array_idx(uint64_t jobid) return EXTRACT_ARRAY_IDX(jobid); } +/* + * Scan one `num` (one or more decimal digits) at *pp. The value saturates at + * UINT64_MAX instead of wrapping, so an oversized number always fails the + * caller's width/index bound. Returns false if no digit is present. + */ +static bool jobsel_scan_num(const char** pp, uint64_t* val) +{ + const char* p = *pp; + uint64_t v = 0; + + if ((*p < '0') || (*p > '9')) { + return false; + } + + for (; (*p >= '0') && (*p <= '9'); p++) { + uint64_t d = (uint64_t)(*p - '0'); + + if (v > (UINT64_MAX - d) / 10) { + v = UINT64_MAX; + } else { + v = v * 10 + d; + } + } + + *pp = p; + *val = v; + return true; +} + +static const char* jobsel_skip_blank(const char* p) +{ + while ((*p == ' ') || (*p == '\t')) { + p++; + } + return p; +} + +jobsel_err_t parse_jobsel(const char* text, jobsel_t* out) +{ + const char* p; + uint64_t f[4]; + uint64_t lo = 0, hi = 0; + int nidx = 0; /* 0: bare, 1: [k], 2: [a-b] */ + int i; + + if (out != NULL) { + memset(out, 0x0, sizeof(*out)); + } + + if ((text == NULL) || (out == NULL)) { + return JOBSEL_ESYNTAX; + } + + /* 1. syntax (5.15): the whole text is scanned before any value check */ + p = jobsel_skip_blank(text); + for (i = 0; i < 4; i++) { + if ((i > 0) && (*p++ != '-')) { + return JOBSEL_ESYNTAX; + } + if (!jobsel_scan_num(&p, &f[i])) { + return JOBSEL_ESYNTAX; + } + } + + if (*p == '[') { + p++; + if (!jobsel_scan_num(&p, &lo)) { + return JOBSEL_ESYNTAX; + } + hi = lo; + nidx = 1; + if (*p == '-') { + p++; + if (!jobsel_scan_num(&p, &hi)) { + return JOBSEL_ESYNTAX; + } + nidx = 2; + } + if (*p++ != ']') { + return JOBSEL_ESYNTAX; + } + } + + p = jobsel_skip_blank(p); + if (*p != '\0') { + return JOBSEL_ESYNTAX; + } + + /* 2. field widths (5.14) */ + if ((f[0] > JOBID_FIELD_MAX_CLUSTER) || (f[1] > JOBID_FIELD_MAX_APISERVER) + || (f[2] > JOBID_FIELD_MAX_TIMESTAMP) + || (f[3] > JOBID_FIELD_MAX_SEQUENCE)) { + return JOBSEL_EWIDTH; + } + + /* 3. alignment of the array identity when an element part is present + * (5.8) */ + if ((nidx > 0) && ((f[3] & MAX_ARRAY_IDX) != 0)) { + return JOBSEL_EALIGN; + } + + /* 4. element-number bound (5.7) */ + if ((nidx > 0) + && ((lo > JOBID_FIELD_MAX_INDEX) || (hi > JOBID_FIELD_MAX_INDEX))) { + return JOBSEL_EINDEX; + } + + /* 5. reversed range (5.5) */ + if (hi < lo) { + return JOBSEL_EREVERSED; + } + + /* All fields are within their widths here, so composition cannot spill + * into a neighbouring field. */ + out->key = MAKE_JOBID(f[0], f[1], f[2], 0x0, f[3]); + out->form = (nidx == 0) ? JOBSEL_BARE + : (nidx == 1) ? JOBSEL_SINGLE + : JOBSEL_RANGE; + out->lo = (uint32_t)lo; + out->hi = (uint32_t)hi; + + return JOBSEL_OK; +} + +const char* jobsel_strerror(jobsel_err_t err) +{ + switch (err) { + case JOBSEL_OK: + return "valid job identity"; + case JOBSEL_ESYNTAX: + return "invalid job identity: expected " + "--- optionally " + "followed by [k] or [a-b] (decimal digits only, no sign or " + "whitespace)"; + case JOBSEL_EWIDTH: + return "invalid job identity: a field is out of range (cluster <= 63, " + "apiserver <= 15, timestamp < 2^32, sequence < 2^20)"; + case JOBSEL_EALIGN: + return "invalid job identity: not an array identity"; + case JOBSEL_EINDEX: + return "invalid element selection: element numbers must be at most " + "32767"; + case JOBSEL_EREVERSED: + return "invalid element selection: the range is reversed"; + } + + return "invalid job identity"; +} + +int is_array_tag(uint64_t jobid) +{ + return EXTRACT_ID_TAG(jobid) == TAG_JOB_ARRAY; +} + +uint64_t jobsel_element_id(const jobsel_t* s, uint32_t k) +{ + uint64_t key; + + if (s == NULL) { + return 0; + } + + key = s->key; + return MAKE_JOBID(EXTRACT_ID_CLUSTER(key), EXTRACT_ID_APISERVER(key), + EXTRACT_ID_TIMESTAMP(key), TAG_JOB_ARRAY, + EXTRACT_ARRAY_JOB(key) | (k & MAX_ARRAY_IDX)); +} + /* * xce_format_bytes_human / xce_is_bytes_valued_resource / xce_format_units_value * (scheduler-sim-diagnostics spec, task 7, Requirement 6.1/6.3/6.5) --