feat(job-array-one-based-index): bounded job-identity and element-selection parser in libxce (task 1.1)

parse_jobsel() parses c-a-ts-seq with an optional [k] or [a-b] under a
fixed grammar and check order (syntax, field widths, array-identity
alignment, element bound, reversed range), saturating digits so an
oversized number cannot wrap; plus is_array_tag(), jobsel_element_id(),
jobsel_strerror() and JOBID_FIELD_MAX_* constants. The selection-cases
conformance table runs in the libxce suite.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-01 09:01:57 +00:00
parent 590860f73d
commit 6e6c599d27
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
6 changed files with 501 additions and 1 deletions

View file

@ -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

View file

@ -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

View file

@ -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',

View file

@ -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);

View file

@ -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 <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unity/unity.h>
#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);
}

View file

@ -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 "
"<cluster>-<apiserver>-<timestamp>-<sequence> 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) --