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