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