skip to content
==> src/state_file.c <==
1/* loader.rs's read_state and write_state. */
2#include "mesh/state_file.h"
3#include "mesh/loader.h"
4#include "mesh/text.h"
5
6#include "mesh/abi/name.h"
7
8#include <errno.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12
13static char *os_error(int e) { return mesh_fmt("%s (os error %d)", strerror(e), e); }
14
15void mesh_state_free(mesh_saved *set, size_t n) {
16    for (size_t i = 0; i < n; i++) {
17        free(set[i].name);
18        free(set[i].dir);
19    }
20    free(set);
21}
22
23/* Splits on Rust's whitespace; up to max words; returns the count (more than max: max + 1). */
24static size_t words(const char *s, size_t n, const char **w, size_t *wn, size_t max) {
25    size_t count = 0;
26    for (;;) {
27        mesh_trim_start(&s, &n);
28        if (n == 0) return count;
29        size_t k = 0;
30        while (k < n && mesh_utf8_ws_len(s + k, n - k) == 0) k++;
31        if (count < max) {
32            w[count] = s;
33            wn[count] = k;
34        }
35        count++;
36        if (count > max) return count;
37        s += k;
38        n -= k;
39    }
40}
41
42bool mesh_state_read(const char *path, mesh_saved **set, size_t *n, char **err) {
43    *set = NULL;
44    *n = 0;
45    FILE *f = fopen(path, "rb");
46    if (!f) {
47        if (errno == ENOENT) return true;
48        char *e = os_error(errno);
49        mesh_errf(err, "--state %s: %s", path, e);
50        free(e);
51        return false;
52    }
53    mesh_text t = {0};
54    char buf[8192];
55    size_t k;
56    while ((k = fread(buf, 1, sizeof buf, f)) > 0) mesh_text_putn(&t, buf, k);
57    fclose(f);
58    const char *text = mesh_text_str(&t);
59    size_t len = t.len, i = 0, lineno = 0;
60    bool ok = true;
61    while (i < len && ok) {
62        const char *line = text + i;
63        const char *nl = memchr(line, '\n', len - i);
64        size_t ln = nl ? (size_t)(nl - line) : len - i;
65        i += ln + (nl ? 1 : 0);
66        if (ln > 0 && line[ln - 1] == '\r') ln--;
67        lineno++;
68        const char *l = line;
69        mesh_trim(&l, &ln);
70        if (ln == 0 || l[0] == '#') continue;
71        const char *w[3];
72        size_t wn[3];
73        size_t c = words(l, ln, w, wn, 3);
74        bool good = c == 3;
75        char *name = NULL, *dir = NULL;
76        if (good) {
77            name = mesh_strndup(w[0], wn[0]);
78            dir = mesh_strndup(w[1], wn[1]);
79            char *cn = mesh_catalog_name(dir, NULL);
80            good = mesh_valid_name((const uint8_t *)name, strlen(name)) && cn && wn[2] == 1 && (w[2][0] == '0' || w[2][0] == '1');
81            free(cn);
82        }
83        if (!good) {
84            free(name);
85            free(dir);
86            mesh_text q = {0};
87            mesh_text_debug_str(&q, l, ln);
88            mesh_errf(err, "--state %s line %zu: expected `name dir 0|1`, got %s", path, lineno, mesh_text_str(&q));
89            mesh_text_free(&q);
90            ok = false;
91            break;
92        }
93        *set = mesh_realloc(*set, (*n + 1) * sizeof **set);
94        (*set)[(*n)++] = (mesh_saved){name, dir, w[2][0] == '1'};
95    }
96    mesh_text_free(&t);
97    if (!ok) {
98        mesh_state_free(*set, *n);
99        *set = NULL;
100        *n = 0;
101    }
102    return ok;
103}
104
105bool mesh_state_write(const char *path, const mesh_saved *set, size_t n, char **err) {
106    mesh_text s = {0};
107    mesh_text_puts(&s, "# meshd --state: loaded programs, in slot order (name, dir under --programs, running)\n");
108    for (size_t i = 0; i < n; i++) mesh_text_printf(&s, "%s %s %u\n", set[i].name, set[i].dir, set[i].running ? 1u : 0u);
109    /* Rust's path.with_extension("tmp"): the extension replaced, or added. */
110    const char *slash = strrchr(path, '/');
111    const char *base = slash ? slash + 1 : path;
112    const char *dot = strrchr(base, '.');
113    char *tmp = dot && dot != base ? mesh_fmt("%.*s.tmp", (int)(dot - path), path) : mesh_fmt("%s.tmp", path);
114    FILE *f = fopen(tmp, "wb");
115    bool ok = f && fwrite(mesh_text_str(&s), 1, s.len, f) == s.len;
116    int e0 = errno;
117    if (f && fclose(f) != 0 && ok) {
118        ok = false;
119        e0 = errno;
120    }
121    if (ok && rename(tmp, path) != 0) {
122        ok = false;
123        e0 = errno;
124    }
125    if (!ok) {
126        char *e = os_error(e0);
127        mesh_errf(err, "--state %s: %s", path, e);
128        free(e);
129    }
130    free(tmp);
131    mesh_text_free(&s);
132    return ok;
133}