skip to content
==> 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}