==> 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}