==> src/loader.c <==1/* loader.rs: catalog names, confinement under --programs, reading a program, hashes. */
2#include "mesh/loader.h"
3
4#include "mesh/abi/fnv.h"
5
6#include <dirent.h>
7#include <errno.h>
8#include <limits.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <sys/stat.h>
13
14/* Rust's io::Error Display. */
15static char *os_error(int e) { return mesh_fmt("%s (os error %d)", strerror(e), e); }
16
17static char *slurp(const char *path, size_t *n) {
18 FILE *f = fopen(path, "rb");
19 if (!f) return NULL;
20 mesh_text t = {0};
21 char buf[65536];
22 size_t k;
23 while ((k = fread(buf, 1, sizeof buf, f)) > 0) mesh_text_putn(&t, buf, k);
24 int bad = ferror(f);
25 fclose(f);
26 if (bad) {
27 mesh_text_free(&t);
28 errno = EIO;
29 return NULL;
30 }
31 *n = t.len;
32 return mesh_text_take(&t);
33}
34
35void mesh_payload_copy(mesh_payload *dst, const mesh_payload *src) {
36 dst->dir = mesh_strdup(src->dir);
37 dst->manifest = mesh_strdup(src->manifest);
38 dst->ptx_len = src->ptx_len;
39 dst->ptx = src->ptx_len ? mesh_alloc(src->ptx_len) : NULL;
40 if (src->ptx_len) memcpy(dst->ptx, src->ptx, src->ptx_len);
41}
42
43void mesh_payload_free(mesh_payload *p) {
44 free(p->dir);
45 free(p->manifest);
46 free(p->ptx);
47 *p = (mesh_payload){0};
48}
49
50char *mesh_catalog_name(const char *spec, char **err) {
51 size_t n = strlen(spec);
52 static const char suffix[] = "/program.toml";
53 if (n >= sizeof suffix - 1 && strcmp(spec + n - (sizeof suffix - 1), suffix) == 0) n -= sizeof suffix - 1;
54 while (n > 0 && spec[n - 1] == '/') n--;
55 bool ok = n > 0 && n <= 64;
56 for (size_t i = 0; ok && i < n; i++) ok = (spec[i] >= 'a' && spec[i] <= 'z') || (spec[i] >= '0' && spec[i] <= '9') || spec[i] == '-';
57 if (ok) return mesh_strndup(spec, n);
58 char *q = mesh_debug_str(spec);
59 mesh_errf(err, "manifest: %s: programs are named [a-z0-9-] under --programs (no paths)", q);
60 free(q);
61 return NULL;
62}
63
64/* Rust's Path::starts_with on canonical paths: whole components. */
65static bool path_within(const char *dir, const char *base) {
66 size_t n = strlen(base);
67 if (strncmp(dir, base, n) != 0) return false;
68 return dir[n] == 0 || dir[n] == '/' || (n > 0 && base[n - 1] == '/');
69}
70
71char *mesh_loader_resolve(const char *root, const char *spec, char **err) {
72 char *name = mesh_catalog_name(spec, err);
73 if (!name) return NULL;
74 char base[PATH_MAX], dir[PATH_MAX];
75 if (!realpath(root, base)) {
76 char *e = os_error(errno);
77 mesh_errf(err, "manifest: --programs %s: %s", root, e);
78 free(e);
79 free(name);
80 return NULL;
81 }
82 char *joined = mesh_fmt("%s/%s", root, name);
83 if (!realpath(joined, dir)) {
84 char *e = os_error(errno);
85 mesh_errf(err, "manifest: %s: %s", name, e);
86 free(e);
87 free(joined);
88 free(name);
89 return NULL;
90 }
91 free(joined);
92 struct stat st;
93 if (!path_within(dir, base) || stat(dir, &st) != 0 || !S_ISDIR(st.st_mode)) {
94 mesh_errf(err, "manifest: %s: not a program dir under --programs", name);
95 free(name);
96 return NULL;
97 }
98 free(name);
99 return mesh_strdup(dir);
100}
101
102bool mesh_loader_read(const char *root, const char *spec, bool need_ptx, mesh_payload *out, char **err) {
103 *out = (mesh_payload){0};
104 char *dir = mesh_loader_resolve(root, spec, err);
105 if (!dir) return false;
106 mesh_manifest m;
107 char *ptx_path = NULL, *why = NULL;
108 if (!mesh_manifest_load(dir, &m, &ptx_path, &why)) {
109 mesh_errf(err, "manifest: %s", why);
110 free(why);
111 free(dir);
112 return false;
113 }
114 char *toml = mesh_fmt("%s/program.toml", dir);
115 size_t tn;
116 char *text = slurp(toml, &tn);
117 free(toml);
118 free(dir);
119 if (!text) {
120 char *e = os_error(errno);
121 mesh_errf(err, "manifest: %s", e);
122 free(e);
123 goto fail;
124 }
125 /* A native program is an executable per target: no PTX. */
126 need_ptx = need_ptx && !mesh_manifest_native(&m);
127 size_t pn = 0;
128 char *ptx = slurp(ptx_path, &pn);
129 if (!ptx) {
130 int e0 = errno;
131 if (need_ptx || e0 != ENOENT) {
132 char *e = os_error(e0);
133 mesh_errf(err, "manifest: %s (%s): %s", ptx_path, m.name, e);
134 free(e);
135 free(text);
136 goto fail;
137 }
138 pn = 0;
139 }
140 out->dir = mesh_catalog_name(spec, NULL);
141 out->manifest = text;
142 out->ptx = (uint8_t *)ptx;
143 out->ptx_len = pn;
144 if (pn == 0) {
145 free(out->ptx);
146 out->ptx = NULL;
147 }
148 free(ptx_path);
149 mesh_manifest_free(&m);
150 return true;
151fail:
152 free(ptx_path);
153 mesh_manifest_free(&m);
154 return false;
155}
156
157uint64_t mesh_image_hash(const uint8_t *code, size_t n, uint32_t param) {
158 if (param == 0) return mesh_fnv1a64(code, n);
159 uint8_t *b = mesh_alloc(n + 11);
160 if (n) memcpy(b, code, n);
161 memcpy(b + n, "\0param=", 7);
162 for (int i = 0; i < 4; i++) b[n + 7 + (size_t)i] = (uint8_t)(param >> (8 * i));
163 uint64_t h = mesh_fnv1a64(b, n + 11);
164 free(b);
165 return h;
166}
167
168uint64_t mesh_native_identity(const char *manifest, const char *source, uint32_t param) {
169 const char *s = source;
170 size_t sn = strlen(source);
171 mesh_trim(&s, &sn);
172 mesh_text b = {0};
173 mesh_text_puts(&b, manifest);
174 mesh_text_putn(&b, "\0source=", 8);
175 mesh_text_putn(&b, s, sn);
176 uint64_t h = mesh_image_hash((const uint8_t *)mesh_text_str(&b), b.len, param);
177 mesh_text_free(&b);
178 return h;
179}
180
181bool mesh_loader_identity(const char *root, const char *spec, mesh_payload *p, mesh_manifest *m, uint64_t *hash, char **err) {
182 if (!mesh_loader_read(root, spec, false, p, err)) return false;
183 char *why = NULL;
184 if (!mesh_manifest_parse(p->manifest, strlen(p->manifest), m, &why)) {
185 mesh_errf(err, "manifest: %s: %s", spec, why);
186 free(why);
187 mesh_payload_free(p);
188 return false;
189 }
190 if (p->ptx_len) {
191 *hash = mesh_image_hash(p->ptx, p->ptx_len, m->param);
192 return true;
193 }
194 char *dir = mesh_loader_resolve(root, spec, err);
195 if (!dir) {
196 mesh_manifest_free(m);
197 mesh_payload_free(p);
198 return false;
199 }
200 for (size_t i = 0; i < m->nbackends; i++) {
201 const char *b = m->backends[i];
202 char *path = NULL;
203 if (strcmp(b, "native") == 0) path = mesh_fmt("%s/source.hash", dir);
204 else if (strcmp(b, "wasm") == 0) path = mesh_fmt("%s/%s.wasm", dir, m->name);
205 else if (strcmp(b, "metal") == 0) path = mesh_fmt("%s/program.metal", dir);
206 if (!path) continue;
207 size_t n;
208 char *bytes = slurp(path, &n);
209 free(path);
210 if (!bytes) continue;
211 *hash = strcmp(b, "native") == 0 ? mesh_native_identity(p->manifest, bytes, m->param)
212 : mesh_image_hash((const uint8_t *)bytes, n, m->param);
213 free(bytes);
214 free(dir);
215 return true;
216 }
217 free(dir);
218 mesh_text t = {0};
219 for (size_t i = 0; i < m->nbackends; i++) mesh_text_printf(&t, "%s%s", i ? ", " : "", m->backends[i]);
220 mesh_errf(err, "manifest: %s: no image in the catalog dir for any of its backends (%s)", spec, mesh_text_str(&t));
221 mesh_text_free(&t);
222 mesh_manifest_free(m);
223 mesh_payload_free(p);
224 return false;
225}
226
227bool mesh_check_hash(const char *name, const char *what, uint64_t hash, const uint64_t *expect, char **err) {
228 if (expect && *expect != hash)
229 return mesh_errf(err, "peer: %s: the %s received hashes to %016llx, the peer announced %016llx", name, what,
230 (unsigned long long)hash, (unsigned long long)*expect);
231 return true;
232}
233
234static int by_name(const void *a, const void *b) { return strcmp(*(char *const *)a, *(char *const *)b); }
235
236bool mesh_catalog(const char *root, char ***names, size_t *n, char **err) {
237 *names = NULL;
238 *n = 0;
239 DIR *d = opendir(root);
240 if (!d) {
241 char *e = os_error(errno);
242 mesh_errf(err, "--programs %s: %s", root, e);
243 free(e);
244 return false;
245 }
246 struct dirent *ent;
247 while ((ent = readdir(d))) {
248 if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
249 char *toml = mesh_fmt("%s/%s/program.toml", root, ent->d_name);
250 struct stat st;
251 bool file = stat(toml, &st) == 0 && S_ISREG(st.st_mode);
252 free(toml);
253 if (!file) continue;
254 char *name = mesh_catalog_name(ent->d_name, NULL);
255 if (!name) continue;
256 /* catalog_name accepts a trailing "/program.toml"; a dir entry is the name itself. */
257 if (strcmp(name, ent->d_name) != 0) {
258 free(name);
259 continue;
260 }
261 *names = mesh_realloc(*names, (*n + 1) * sizeof **names);
262 (*names)[(*n)++] = name;
263 }
264 closedir(d);
265 qsort(*names, *n, sizeof **names, by_name);
266 return true;
267}