skip to content
==> src/req.c <==
1/* jobs.rs's Submit::parse and Req::parse. */
2#include "mesh/jobs/req.h"
3#include "mesh/abi/base.h"
4#include "mesh/abi/name.h"
5#include "mesh/abi/ops.h"
6#include "mesh/debug_str.h"
7#include "mesh/rs_parse.h"
8
9#include <stdlib.h>
10#include <string.h>
11
12void mj_submit_free(mj_submit *s) {
13    free(s->program);
14    mj_ranges_free(&s->items);
15    free(s->setup);
16    memset(s, 0, sizeof *s);
17}
18
19mj_submit mj_submit_clone(const mj_submit *s) {
20    mj_submit c = *s;
21    c.program = mstrdup(s->program);
22    c.items = mj_ranges_clone(&s->items);
23    c.setup = mj_setup_clone(s->setup, s->nsetup);
24    return c;
25}
26
27void mj_req_free(mj_req *r) {
28    if (r->kind == MJ_REQ_SUBMIT) mj_submit_free(&r->submit);
29    memset(r, 0, sizeof *r);
30}
31
32mj_req mj_req_clone(const mj_req *r) {
33    mj_req c = *r;
34    if (r->kind == MJ_REQ_SUBMIT) c.submit = mj_submit_clone(&r->submit);
35    return c;
36}
37
38/* `0x..` hex, else decimal (jobs.rs's `int`). */
39static bool int_of(const char *s, size_t n, uint32_t *out) {
40    uint64_t v;
41    if (n >= 2 && s[0] == '0' && s[1] == 'x') {
42        if (!rs_parse_uint(s + 2, n - 2, 16, UINT32_MAX, &v)) return false;
43        *out = (uint32_t)v;
44        return true;
45    }
46    return rs_u32(s, n, out);
47}
48
49static bool fail(char **err, char *msg) {
50    if (err) *err = msg;
51    else free(msg);
52    return false;
53}
54
55static char *bad_kv(const char *k, const char *v) {
56    msb b = {0};
57    msb_printf(&b, "%s ", k);
58    mdebug_str(&b, v, strlen(v));
59    msb_puts(&b, "; " MESH_JOBS_SUBMIT_USAGE);
60    return msb_take(&b);
61}
62
63/* Splits s at commas, calling f on each piece; false at the first piece f refuses. */
64static bool each(const char *s, bool (*f)(const char *p, size_t n, void *cx), void *cx) {
65    for (;;) {
66        const char *c = strchr(s, ',');
67        size_t n = c ? (size_t)(c - s) : strlen(s);
68        if (!f(s, n, cx)) return false;
69        if (!c) return true;
70        s = c + 1;
71    }
72}
73
74static bool name_ok(const char *p, size_t n, void *cx) {
75    (void)cx;
76    return mesh_valid_name((const uint8_t *)p, n);
77}
78
79static bool member_bit(const char *p, size_t n, void *cx) {
80    uint32_t k;
81    if (!rs_u32(p, n, &k) || k >= MESH_MAX_SYSTEMS) return false;
82    *(uint32_t *)cx += (uint32_t)1 << k;
83    return true;
84}
85
86typedef struct {
87    mj_setup *v;
88    size_t n;
89} setups;
90
91static bool setup_one(const char *p, size_t n, void *cx) {
92    setups *s = cx;
93    const char *colon = memchr(p, ':', n);
94    uint32_t o, a;
95    if (!colon || !int_of(p, (size_t)(colon - p), &o) || !int_of(colon + 1, n - (size_t)(colon - p) - 1, &a)) return false;
96    s->v = mxrealloc(s->v, (s->n + 1) * sizeof *s->v);
97    s->v[s->n++] = (mj_setup){o, a};
98    return true;
99}
100
101bool mj_submit_parse(const char *const *words, size_t n, mj_submit *out, char **err) {
102    if (n == 0) return fail(err, mstrdup(MESH_JOBS_SUBMIT_USAGE));
103    const char *p = words[0];
104    if (!each(p, name_ok, NULL)) {
105        msb b = {0};
106        mdebug_str(&b, p, strlen(p));
107        msb_puts(&b, ": not a program name (or names joined by commas); " MESH_JOBS_SUBMIT_USAGE);
108        return fail(err, msb_take(&b));
109    }
110    mj_submit s = {.program = mstrdup(p), .op = MESH_OP_JOB, .attempts = 3, .mode = MJ_MODE_COLLECT};
111    if ((n - 1) % 2 != 0) {
112        mj_submit_free(&s);
113        return fail(err, mstrdup(MESH_JOBS_SUBMIT_USAGE));
114    }
115    bool items = false;
116    for (size_t i = 1; i + 1 < n; i += 2) {
117        const char *k = words[i], *v = words[i + 1];
118        size_t vn = strlen(v);
119        bool ok = true;
120        if (strcmp(k, "items") == 0) {
121            uint64_t num;
122            mj_ranges r = {0};
123            if (rs_u64(v, vn, &num) && !strchr(v, ',')) r = mj_ranges_of(0, num);
124            else if (!mj_ranges_parse(v, &r, err)) {
125                mj_submit_free(&s);
126                return false;
127            }
128            mj_ranges_free(&s.items);
129            s.items = r;
130            items = true;
131        } else if (strcmp(k, "op") == 0) ok = int_of(v, vn, &s.op);
132        else if (strcmp(k, "priority") == 0) ok = rs_i32(v, vn, &s.priority);
133        else if (strcmp(k, "attempts") == 0) {
134            uint32_t a;
135            ok = rs_u32(v, vn, &a) && a >= 1 && a <= 100;
136            if (ok) s.attempts = a;
137        } else if (strcmp(k, "deadline") == 0) ok = rs_u64(v, vn, &s.deadline_s);
138        else if (strcmp(k, "results") == 0) ok = mj_mode_parse(v, &s.mode);
139        else if (strcmp(k, "members") == 0) {
140            uint32_t m = 0;
141            ok = each(v, member_bit, &m);
142            if (ok) s.members = m;
143        } else if (strcmp(k, "setup") == 0) {
144            setups su = {0};
145            ok = each(v, setup_one, &su);
146            if (ok) {
147                free(s.setup);
148                s.setup = su.v;
149                s.nsetup = su.n;
150            } else free(su.v);
151        } else ok = false;
152        if (!ok) {
153            mj_submit_free(&s);
154            return fail(err, bad_kv(k, v));
155        }
156    }
157    uint64_t end;
158    char *why = NULL;
159    if (!items || mj_ranges_is_empty(&s.items)) why = mstrdup("no items; " MESH_JOBS_SUBMIT_USAGE);
160    else if (mj_ranges_end(&s.items, &end) && end == UINT64_MAX && s.mode == MJ_MODE_COLLECT)
161        why = mstrdup("an endless job keeps no per-item results: results nonzero or none");
162    else if (s.nsetup > 16) why = mstrdup("setup: at most 16 tasks");
163    if (why) {
164        mj_submit_free(&s);
165        return fail(err, why);
166    }
167    *out = s;
168    return true;
169}
170
171static bool job_id(const char *s, mj_job_id *id, char **err) {
172    if (mj_parse_job(s, id)) return true;
173    msb b = {0};
174    mdebug_str(&b, s, strlen(s));
175    msb_puts(&b, ": a job id is <term>.<n> (`jobs` lists them)");
176    return fail(err, msb_take(&b));
177}
178
179bool mj_req_parse(const char *const *words, size_t n, mj_req *out, char **err) {
180    mj_req r = {0};
181    if (n >= 1 && strcmp(words[0], "submit") == 0) {
182        r.kind = MJ_REQ_SUBMIT;
183        if (!mj_submit_parse(words + 1, n - 1, &r.submit, err)) return false;
184    } else if (n == 2 && strcmp(words[0], "cancel") == 0) {
185        r.kind = MJ_REQ_CANCEL;
186        if (!job_id(words[1], &r.job, err)) return false;
187    } else if (n == 2 && strcmp(words[0], "forget") == 0) {
188        r.kind = MJ_REQ_FORGET;
189        if (!job_id(words[1], &r.job, err)) return false;
190    } else if (n == 3 && strcmp(words[0], "tone") == 0) {
191        r.kind = MJ_REQ_TONE;
192        if (!job_id(words[1], &r.job, err)) return false;
193        const char *t = words[2];
194        if (strcmp(t, "aces") == 0 || strcmp(t, "0") == 0) r.tone = 0;
195        else if (strcmp(t, "reinhard") == 0 || strcmp(t, "1") == 0) r.tone = 1;
196        else {
197            msb b = {0};
198            msb_puts(&b, "tone ");
199            mdebug_str(&b, t, strlen(t));
200            msb_puts(&b, ": aces or reinhard");
201            return fail(err, msb_take(&b));
202        }
203    } else {
204        msb b = {0};
205        msb_puts(&b, "not a job change: ");
206        for (size_t i = 0; i < n; i++) msb_printf(&b, "%s%s", i ? " " : "", words[i]);
207        return fail(err, msb_take(&b));
208    }
209    *out = r;
210    return true;
211}