==> src/fleet_doc.c <==1/* fleet.rs: ProgSpec, CycleOp, Group, Doc (encode, decode), can_run. */
2#include "mesh/fleet_doc.h"
3#include "mesh/abi/backend.h"
4#include "mesh/parse.h"
5#include "mesh/rust_fmt.h"
6
7#include <stdlib.h>
8#include <string.h>
9
10static char *dup0(const char *s) { return s ? mt_strdup(s) : NULL; }
11
12mfd_prog mfd_prog_new(const char *dir, uint64_t hash, bool run, const mesh_needs *needs) {
13 mfd_prog p = {0};
14 p.dir = mt_strdup(dir);
15 p.hash = hash;
16 p.run = run;
17 p.needs = msn_clone(needs);
18 p.why = mt_strdup("");
19 return p;
20}
21
22mfd_prog mfd_prog_clone(const mfd_prog *p) {
23 mfd_prog c = *p;
24 c.dir = mt_strdup(p->dir);
25 c.backends = mt_strs_clone(&p->backends);
26 c.prev_dir = dup0(p->prev_dir);
27 c.needs = msn_clone(&p->needs);
28 c.at = ml_opt_clone(&p->at);
29 c.pin = ml_opt_clone(&p->pin);
30 c.why = mt_strdup(p->why);
31 return c;
32}
33
34bool mfd_prog_eq(const mfd_prog *a, const mfd_prog *b) {
35 if (strcmp(a->dir, b->dir) || a->hash != b->hash || a->run != b->run || a->reset != b->reset || a->lockstep != b->lockstep ||
36 !mt_strs_eq(&a->backends, &b->backends) || a->has_prev != b->has_prev)
37 return false;
38 if (a->has_prev && (strcmp(a->prev_dir, b->prev_dir) || a->prev_hash != b->prev_hash)) return false;
39 if (!mesh_needs_eq(&a->needs, &b->needs) || !ml_opt_eq(&a->at, &b->at) || a->has_from != b->has_from || (a->has_from && a->from != b->from)) return false;
40 if (a->has_to != b->has_to || (a->has_to && a->to != b->to) || a->since != b->since || !ml_opt_eq(&a->pin, &b->pin)) return false;
41 return strcmp(a->why, b->why) == 0;
42}
43
44void mfd_prog_free(mfd_prog *p) {
45 free(p->dir);
46 mt_strs_free(&p->backends);
47 free(p->prev_dir);
48 mesh_needs_free(&p->needs);
49 ml_opt_free(&p->at);
50 ml_opt_free(&p->pin);
51 free(p->why);
52 *p = (mfd_prog){0};
53}
54
55bool mfd_prog_here(const mfd_prog *p, uint32_t k) { return ml_opt_has_or_all(&p->at, k) || (p->has_from && p->from == k); }
56
57uint32_t mfd_group_mask(const mfd_group *g) { return ml_mask(&g->members); }
58
59bool mfd_group_rank(const mfd_group *g, uint32_t k, uint32_t *rank) {
60 long i = ml_find(&g->members, k);
61 if (i < 0) return false;
62 *rank = (uint32_t)i;
63 return true;
64}
65
66void mfd_group_order(const mfd_group *g, uint8_t out[MESH_MAX_SYSTEMS]) {
67 memset(out, 0xff, MESH_MAX_SYSTEMS);
68 for (size_t r = 0; r < g->members.n && r < MESH_MAX_SYSTEMS; r++) out[r] = (uint8_t)g->members.v[r];
69}
70
71mfd_group mfd_group_clone(const mfd_group *g) {
72 mfd_group c = *g;
73 c.name = mt_strdup(g->name);
74 c.program = mt_strdup(g->program);
75 c.members = ml_clone(&g->members);
76 return c;
77}
78
79bool mfd_group_eq(const mfd_group *a, const mfd_group *b) {
80 return strcmp(a->name, b->name) == 0 && strcmp(a->program, b->program) == 0 && a->epoch == b->epoch && a->cycle == b->cycle && a->op.kind == b->op.kind &&
81 (a->op.kind != MFD_OP_TASK || (a->op.op == b->op.op && a->op.arg == b->op.arg)) && ml_eq(&a->members, &b->members) && a->state == b->state &&
82 a->on_loss == b->on_loss && a->auto_ms == b->auto_ms;
83}
84
85void mfd_group_free(mfd_group *g) {
86 free(g->name);
87 free(g->program);
88 ml_free(&g->members);
89 *g = (mfd_group){0};
90}
91
92char *mfd_op_word(mfd_op op) {
93 switch (op.kind) {
94 case MFD_OP_NONE: return mt_strdup("none");
95 case MFD_OP_UPGRADE: return mt_strdup("upgrade");
96 case MFD_OP_ROLLBACK: return mt_strdup("rollback");
97 default: return mt_fmt("task:%u:%u", op.op, op.arg);
98 }
99}
100
101bool mfd_op_parse(const char *s, mfd_op *out, char **err) {
102 if (strcmp(s, "none") == 0) return *out = (mfd_op){MFD_OP_NONE, 0, 0}, true;
103 if (strcmp(s, "upgrade") == 0) return *out = (mfd_op){MFD_OP_UPGRADE, 0, 0}, true;
104 if (strcmp(s, "rollback") == 0) return *out = (mfd_op){MFD_OP_ROLLBACK, 0, 0}, true;
105 mt_strs p = mt_split(s, ':');
106 uint32_t o, a;
107 bool ok = p.n == 3 && strcmp(p.v[0], "task") == 0 && mp_u32(p.v[1], &o) && mp_u32(p.v[2], &a);
108 mt_strs_free(&p);
109 if (ok) return *out = (mfd_op){MFD_OP_TASK, o, a}, true;
110 char *q = rf_debug_str(s);
111 *err = mt_fmt("bad op %s", q);
112 free(q);
113 return false;
114}
115
116int mfd_id_cmp(uint64_t at, uint64_t av, uint64_t bt, uint64_t bv) {
117 if (at != bt) return at < bt ? -1 : 1;
118 if (av != bv) return av < bv ? -1 : 1;
119 return 0;
120}
121
122/* ---- the document's maps */
123
124static size_t find_name(char **names, size_t n, const char *key, bool *found) {
125 size_t lo = 0, hi = n;
126 while (lo < hi) {
127 size_t mid = (lo + hi) / 2;
128 int c = strcmp(names[mid], key);
129 if (!c) {
130 *found = true;
131 return mid;
132 }
133 if (c < 0) lo = mid + 1;
134 else hi = mid;
135 }
136 *found = false;
137 return lo;
138}
139
140long mfd_prog_index(const mfd_doc *d, const char *name) {
141 bool f;
142 size_t i = find_name(d->pname, d->nprogs, name, &f);
143 return f ? (long)i : -1;
144}
145
146mfd_prog *mfd_prog_get(const mfd_doc *d, const char *name) {
147 long i = mfd_prog_index(d, name);
148 return i < 0 ? NULL : &d->prog[i];
149}
150
151void mfd_prog_put(mfd_doc *d, const char *name, mfd_prog p) {
152 bool f;
153 size_t i = find_name(d->pname, d->nprogs, name, &f);
154 if (f) {
155 mfd_prog_free(&d->prog[i]);
156 d->prog[i] = p;
157 return;
158 }
159 d->pname = mt_realloc(d->pname, (d->nprogs + 1) * sizeof *d->pname);
160 d->prog = mt_realloc(d->prog, (d->nprogs + 1) * sizeof *d->prog);
161 memmove(d->pname + i + 1, d->pname + i, (d->nprogs - i) * sizeof *d->pname);
162 memmove(d->prog + i + 1, d->prog + i, (d->nprogs - i) * sizeof *d->prog);
163 d->pname[i] = mt_strdup(name);
164 d->prog[i] = p;
165 d->nprogs++;
166}
167
168bool mfd_prog_remove(mfd_doc *d, const char *name) {
169 long i = mfd_prog_index(d, name);
170 if (i < 0) return false;
171 free(d->pname[i]);
172 mfd_prog_free(&d->prog[i]);
173 memmove(d->pname + i, d->pname + i + 1, (d->nprogs - (size_t)i - 1) * sizeof *d->pname);
174 memmove(d->prog + i, d->prog + i + 1, (d->nprogs - (size_t)i - 1) * sizeof *d->prog);
175 d->nprogs--;
176 return true;
177}
178
179mfd_group *mfd_group_of(const mfd_doc *d, const char *program) {
180 for (size_t i = 0; i < d->ngroups; i++)
181 if (strcmp(d->groups[i].program, program) == 0) return &d->groups[i];
182 return NULL;
183}
184
185mfd_group *mfd_group_named(const mfd_doc *d, const char *name) {
186 for (size_t i = 0; i < d->ngroups; i++)
187 if (strcmp(d->groups[i].name, name) == 0) return &d->groups[i];
188 return NULL;
189}
190
191void mfd_group_push(mfd_doc *d, mfd_group g) {
192 d->groups = mt_realloc(d->groups, (d->ngroups + 1) * sizeof *d->groups);
193 d->groups[d->ngroups++] = g;
194}
195
196void mfd_group_remove_at(mfd_doc *d, size_t i) {
197 mfd_group_free(&d->groups[i]);
198 memmove(d->groups + i, d->groups + i + 1, (d->ngroups - i - 1) * sizeof *d->groups);
199 d->ngroups--;
200}
201
202const mt_strs *mfd_place_get(const mfd_doc *d, const char *name) {
203 bool f;
204 size_t i = find_name(d->place_name, d->nplaces, name, &f);
205 return f ? &d->place_words[i] : NULL;
206}
207
208void mfd_place_put(mfd_doc *d, const char *name, mt_strs words) {
209 bool f;
210 size_t i = find_name(d->place_name, d->nplaces, name, &f);
211 if (f) {
212 mt_strs_free(&d->place_words[i]);
213 d->place_words[i] = words;
214 return;
215 }
216 d->place_name = mt_realloc(d->place_name, (d->nplaces + 1) * sizeof *d->place_name);
217 d->place_words = mt_realloc(d->place_words, (d->nplaces + 1) * sizeof *d->place_words);
218 memmove(d->place_name + i + 1, d->place_name + i, (d->nplaces - i) * sizeof *d->place_name);
219 memmove(d->place_words + i + 1, d->place_words + i, (d->nplaces - i) * sizeof *d->place_words);
220 d->place_name[i] = mt_strdup(name);
221 d->place_words[i] = words;
222 d->nplaces++;
223}
224
225bool mfd_place_remove(mfd_doc *d, const char *name) {
226 bool f;
227 size_t i = find_name(d->place_name, d->nplaces, name, &f);
228 if (!f) return false;
229 free(d->place_name[i]);
230 mt_strs_free(&d->place_words[i]);
231 memmove(d->place_name + i, d->place_name + i + 1, (d->nplaces - i - 1) * sizeof *d->place_name);
232 memmove(d->place_words + i, d->place_words + i + 1, (d->nplaces - i - 1) * sizeof *d->place_words);
233 d->nplaces--;
234 return true;
235}
236
237mfd_doc mfd_doc_clone(const mfd_doc *d) {
238 mfd_doc c = {.version = d->version, .term = d->term, .sched_paused = d->sched_paused, .autoreform = d->autoreform, .cordon = d->cordon};
239 for (size_t i = 0; i < d->nprogs; i++) mfd_prog_put(&c, d->pname[i], mfd_prog_clone(&d->prog[i]));
240 for (size_t i = 0; i < d->ngroups; i++) mfd_group_push(&c, mfd_group_clone(&d->groups[i]));
241 for (size_t i = 0; i < d->nplaces; i++) mfd_place_put(&c, d->place_name[i], mt_strs_clone(&d->place_words[i]));
242 return c;
243}
244
245bool mfd_doc_eq(const mfd_doc *a, const mfd_doc *b) {
246 if (a->version != b->version || a->term != b->term || a->nprogs != b->nprogs || a->ngroups != b->ngroups || a->nplaces != b->nplaces ||
247 a->sched_paused != b->sched_paused || a->autoreform != b->autoreform || a->cordon != b->cordon)
248 return false;
249 for (size_t i = 0; i < a->nprogs; i++)
250 if (strcmp(a->pname[i], b->pname[i]) || !mfd_prog_eq(&a->prog[i], &b->prog[i])) return false;
251 for (size_t i = 0; i < a->ngroups; i++)
252 if (!mfd_group_eq(&a->groups[i], &b->groups[i])) return false;
253 for (size_t i = 0; i < a->nplaces; i++)
254 if (strcmp(a->place_name[i], b->place_name[i]) || !mt_strs_eq(&a->place_words[i], &b->place_words[i])) return false;
255 return true;
256}
257
258void mfd_doc_free(mfd_doc *d) {
259 for (size_t i = 0; i < d->nprogs; i++) {
260 free(d->pname[i]);
261 mfd_prog_free(&d->prog[i]);
262 }
263 for (size_t i = 0; i < d->ngroups; i++) mfd_group_free(&d->groups[i]);
264 for (size_t i = 0; i < d->nplaces; i++) {
265 free(d->place_name[i]);
266 mt_strs_free(&d->place_words[i]);
267 }
268 free(d->pname);
269 free(d->prog);
270 free(d->groups);
271 free(d->place_name);
272 free(d->place_words);
273 *d = (mfd_doc){0};
274}
275
276/* ---- text */
277
278static char *opt_list(const ml_opt *o) { return o->some ? ml_text(&o->l) : mt_strdup("*"); }
279
280char *mfd_encode(const mfd_doc *d) {
281 mt_buf b = {0};
282 mt_printf(&b, "doc version %llu term %llu\n", (unsigned long long)d->version, (unsigned long long)d->term);
283 for (size_t i = 0; i < d->nprogs; i++) {
284 const mfd_prog *p = &d->prog[i];
285 char *prev = p->has_prev ? mt_fmt("%s %016llx", p->prev_dir, (unsigned long long)p->prev_hash) : mt_strdup("- 0");
286 char *back = p->backends.n ? mt_join(&p->backends, ",") : mt_strdup("-");
287 char *at = opt_list(&p->at), *pin = opt_list(&p->pin);
288 char *from = p->has_from && p->has_to ? mt_fmt("%u>%u@%llu", p->from, p->to, (unsigned long long)p->since)
289 : p->has_from ? mt_fmt("%u", p->from)
290 : mt_strdup("-");
291 char *needs = msn_encode(&p->needs);
292 char *why = mt_strdup(p->why);
293 for (char *c = why; *c; c++)
294 if (*c == '\n') *c = ' ';
295 mt_printf(&b, "program %s dir %s hash %016llx run %u reset %u lockstep %u backends %s prev %s at %s from %s pin %s needs %s why %s\n", d->pname[i],
296 p->dir, (unsigned long long)p->hash, (unsigned)p->run, p->reset, (unsigned)p->lockstep, back, prev, at, from, pin, needs, why);
297 free(prev), free(back), free(at), free(pin), free(from), free(needs), free(why);
298 }
299 for (size_t i = 0; i < d->ngroups; i++) {
300 const mfd_group *g = &d->groups[i];
301 char *op = mfd_op_word(g->op), *m = ml_text(&g->members);
302 mt_printf(&b, "group %s program %s epoch %u cycle %u op %s members %s state %s on_loss %s auto_ms %u\n", g->name, g->program, g->epoch, g->cycle, op, m,
303 g->state == MFD_RUNNING ? "running" : "paused", g->on_loss == MFD_LOSS_PAUSE ? "pause" : "reform", g->auto_ms);
304 free(op), free(m);
305 }
306 for (size_t i = 0; i < d->nplaces; i++) {
307 char *w = mt_join(&d->place_words[i], ",");
308 mt_printf(&b, "place %s %s\n", d->place_name[i], w);
309 free(w);
310 }
311 ml_list c = ml_members(d->cordon);
312 char *ct = ml_text(&c);
313 mt_printf(&b, "sched paused %u autoreform %u cordon %s\n", (unsigned)d->sched_paused, (unsigned)d->autoreform, ct);
314 free(ct);
315 ml_free(&c);
316 return mt_take(&b);
317}
318
319static char *bad_line(size_t i, const char *line) {
320 char *q = rf_debug_str(line);
321 char *e = mt_fmt("fleet document line %zu: %s", i + 1, q);
322 free(q);
323 return e;
324}
325
326static bool opt_parse(const char *v, ml_opt *out, char **err) {
327 if (strcmp(v, "*") == 0) return *out = ml_none(), true;
328 out->some = true;
329 return ml_parse(v, &out->l, err);
330}
331
332static bool words_are(mt_strs *w, size_t n, const char *const *lit) {
333 if (w->n != n) return false;
334 for (size_t i = 0; i < n; i++)
335 if (lit[i] && strcmp(w->v[i], lit[i])) return false;
336 return true;
337}
338
339static bool program_line(mfd_doc *d, size_t li, const char *line, mt_strs *w, char **err) {
340 char **v = w->v;
341 mfd_prog p = {0};
342 uint64_t u;
343 /* The Rust's evaluation order (it decides which error a line with several gets): prev, the
344 * migration, then the fields as ProgSpec lists them. */
345 if (strcmp(v[15], "-") != 0) {
346 p.has_prev = true;
347 p.prev_dir = mt_strdup(v[15]);
348 if (!mp_hex_u64(v[16], &p.prev_hash)) goto bad;
349 }
350 if (strcmp(v[13], "-") != 0) p.backends = mt_split(v[13], ',');
351 const char *fr = v[20];
352 if (strcmp(fr, "-") != 0) {
353 const char *gt = strchr(fr, '>');
354 if (!gt) {
355 if (!mp_u64(fr, &u)) goto bad;
356 p.has_from = true;
357 p.from = (uint32_t)u;
358 } else {
359 char *f = mt_strndup(fr, (size_t)(gt - fr));
360 const char *at = strchr(gt + 1, '@');
361 char *t = at ? mt_strndup(gt + 1, (size_t)(at - gt - 1)) : NULL;
362 uint64_t fv, tv, sv;
363 bool ok = at && mp_u64(f, &fv) && mp_u64(t, &tv) && mp_u64(at + 1, &sv);
364 free(f);
365 free(t);
366 if (!ok) goto bad;
367 p.has_from = p.has_to = true;
368 p.from = (uint32_t)fv;
369 p.to = (uint32_t)tv;
370 p.since = sv;
371 }
372 }
373 if (!mp_hex_u64(v[5], &p.hash)) goto bad;
374 p.run = strcmp(v[7], "1") == 0;
375 if (!mp_u64(v[9], &u)) goto bad;
376 p.reset = (uint32_t)u;
377 p.lockstep = strcmp(v[11], "1") == 0;
378 if (!msn_decode(v[24], &p.needs, err)) goto fail;
379 if (!opt_parse(v[18], &p.at, err)) goto fail;
380 if (!opt_parse(v[22], &p.pin, err)) goto fail;
381 const char *wy = strstr(line, " why ");
382 p.why = wy ? mt_trim(wy + 5) : mt_strdup("");
383 p.dir = mt_strdup(v[3]);
384 mfd_prog_put(d, v[1], p);
385 return true;
386bad:
387 *err = bad_line(li, line);
388fail:
389 if (!p.why) p.why = mt_strdup("");
390 if (!p.dir) p.dir = mt_strdup("");
391 mfd_prog_free(&p);
392 return false;
393}
394
395static bool is_program(mt_strs *w) {
396 static const char *const keys[] = {"program", NULL, "dir", NULL, "hash", NULL, "run", NULL, "reset", NULL, "lockstep", NULL, "backends", NULL,
397 "prev", NULL, NULL, "at", NULL, "from", NULL, "pin", NULL, "needs", NULL, "why"};
398 if (w->n < 26) return false;
399 for (size_t i = 0; i < 26; i++)
400 if (keys[i] && strcmp(w->v[i], keys[i])) return false;
401 return true;
402}
403
404bool mfd_decode(const char *text, mfd_doc *out, char **err) {
405 static const char *const head[] = {"doc", "version", NULL, "term", NULL};
406 static const char *const group[] = {"group", NULL, "program", NULL, "epoch", NULL, "cycle", NULL, "op", NULL, "members", NULL, "state", NULL, "on_loss", NULL, "auto_ms", NULL};
407 static const char *const place[] = {"place", NULL, NULL};
408 static const char *const sched[] = {"sched", "paused", NULL, "autoreform", NULL, "cordon", NULL};
409 mfd_doc d = {0};
410 bool have_head = false, ok = true;
411 mt_strs ls = mt_lines(text);
412 for (size_t i = 0; i < ls.n && ok; i++) {
413 const char *line = ls.v[i];
414 mt_strs w = mt_words(line);
415 char **v = w.v;
416 if (w.n == 0) {
417 } else if (words_are(&w, 5, head)) {
418 ok = mp_u64(v[2], &d.version) && mp_u64(v[4], &d.term);
419 if (!ok) *err = bad_line(i, line);
420 have_head = true;
421 } else if (is_program(&w)) {
422 ok = program_line(&d, i, line, &w, err);
423 } else if (words_are(&w, 18, group)) {
424 mfd_group g = {0};
425 uint64_t e, c, am;
426 if (!mp_u64(v[5], &e) || !mp_u64(v[7], &c)) {
427 *err = bad_line(i, line);
428 ok = false;
429 } else if (!mfd_op_parse(v[9], &g.op, err) || !ml_parse(v[11], &g.members, err)) {
430 ok = false;
431 } else if ((strcmp(v[13], "running") && strcmp(v[13], "paused")) || (strcmp(v[15], "pause") && strcmp(v[15], "reform")) || !mp_u64(v[17], &am)) {
432 *err = bad_line(i, line);
433 ok = false;
434 ml_free(&g.members);
435 } else {
436 g.name = mt_strdup(v[1]);
437 g.program = mt_strdup(v[3]);
438 g.epoch = (uint32_t)e;
439 g.cycle = (uint32_t)c;
440 g.state = strcmp(v[13], "running") == 0 ? MFD_RUNNING : MFD_PAUSED;
441 g.on_loss = strcmp(v[15], "pause") == 0 ? MFD_LOSS_PAUSE : MFD_LOSS_REFORM;
442 g.auto_ms = (uint32_t)am;
443 mfd_group_push(&d, g);
444 }
445 } else if (words_are(&w, 3, place)) {
446 mfd_place_put(&d, v[1], mt_split(v[2], ','));
447 } else if (words_are(&w, 7, sched)) {
448 ml_list c;
449 if (ml_parse(v[6], &c, err)) {
450 d.sched_paused = strcmp(v[2], "1") == 0;
451 d.autoreform = strcmp(v[4], "1") == 0;
452 d.cordon = ml_mask(&c);
453 ml_free(&c);
454 } else ok = false;
455 } else {
456 *err = bad_line(i, line);
457 ok = false;
458 }
459 mt_strs_free(&w);
460 }
461 mt_strs_free(&ls);
462 if (ok && !have_head) {
463 *err = mt_strdup("fleet document: no `doc version .. term ..` line");
464 ok = false;
465 }
466 if (!ok) {
467 mfd_doc_free(&d);
468 return false;
469 }
470 *out = d;
471 return true;
472}
473
474bool mfd_can_run(const mt_strs *backends, uint32_t kind) {
475 const char *me = kind < MESH_BACKENDS_LEN ? MESH_BACKENDS[kind] : "?";
476 for (size_t i = 0; i < backends->n; i++)
477 if (strcmp(backends->v[i], me) == 0 || strcmp(backends->v[i], "wasm") == 0 || strcmp(backends->v[i], "native") == 0) return true;
478 return false;
479}