skip to content
==> src/cache.c <==
1/* The cache directory: names, atomic writes, and eviction oldest first. */
2#include "mesh/exec_wasm_cache.h"
3
4#include "mesh/abi/fnv.h"
5
6#include <dirent.h>
7#include <errno.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <sys/stat.h>
12
13static char *fmt_err(const char *a, const char *b) {
14    size_t n = strlen(a) + strlen(b) + 3;
15    char *s = malloc(n);
16    if (s) snprintf(s, n, "%s: %s", a, b);
17    return s;
18}
19
20char *mesh_wasm_cache_path(const char *dir, const uint8_t *wasm, size_t n, uint64_t engine_hash) {
21    size_t len = strlen(dir) + 1 + 16 + 1 + 16 + 6 + 1;
22    char *p = malloc(len);
23    if (p)
24        snprintf(p, len, "%s/%016llx-%016llx.cwasm", dir, (unsigned long long)mesh_fnv1a64(wasm, n),
25                 (unsigned long long)engine_hash);
26    return p;
27}
28
29bool mesh_wasm_cache_dir(const char *dir, char **err) {
30    char *path = strdup(dir);
31    if (!path) return false;
32    for (char *p = path + 1; *p; p++) {
33        if (*p != '/') continue;
34        *p = 0;
35        if (mkdir(path, 0755) != 0 && errno != EEXIST) break;
36        *p = '/';
37    }
38    bool ok = mkdir(path, 0755) == 0 || errno == EEXIST;
39    struct stat st;
40    if (ok && (stat(path, &st) != 0 || !S_ISDIR(st.st_mode))) {
41        errno = ENOTDIR;
42        ok = false;
43    }
44    if (!ok && err) {
45        char what[4096];
46        snprintf(what, sizeof what, "--wasm-cache %s", dir);
47        *err = fmt_err(what, strerror(errno));
48    }
49    free(path);
50    return ok;
51}
52
53bool mesh_wasm_cache_store(const char *path, const uint8_t *bytes, size_t n, char **err) {
54    size_t len = strlen(path) + 5;
55    char *tmp = malloc(len);
56    if (!tmp) return false;
57    /* path.with_extension("tmp"): foo.cwasm -> foo.tmp */
58    snprintf(tmp, len, "%s", path);
59    char *dot = strrchr(tmp, '.');
60    char *slash = strrchr(tmp, '/');
61    if (dot && (!slash || dot > slash)) strcpy(dot, ".tmp");
62    else strcat(tmp, ".tmp");
63    FILE *f = fopen(tmp, "wb");
64    bool ok = f && fwrite(bytes, 1, n, f) == n;
65    if (f && fclose(f) != 0) ok = false;
66    if (ok && rename(tmp, path) != 0) ok = false;
67    if (!ok) {
68        if (err) *err = fmt_err(path, strerror(errno));
69        remove(tmp);
70    }
71    free(tmp);
72    return ok;
73}
74
75bool mesh_wasm_cache_load(const char *path, uint8_t **out, size_t *n) {
76    FILE *f = fopen(path, "rb");
77    if (!f) return false;
78    uint8_t *b = NULL;
79    size_t len = 0, cap = 0;
80    for (;;) {
81        if (len == cap) {
82            cap = cap ? cap * 2 : 65536;
83            uint8_t *q = realloc(b, cap);
84            if (!q) {
85                free(b);
86                fclose(f);
87                return false;
88            }
89            b = q;
90        }
91        size_t k = fread(b + len, 1, cap - len, f);
92        len += k;
93        if (k == 0) break;
94    }
95    bool ok = !ferror(f);
96    fclose(f);
97    if (!ok) {
98        free(b);
99        return false;
100    }
101    *out = b;
102    *n = len;
103    return true;
104}
105
106typedef struct {
107    struct timespec mtime;
108    uint64_t len;
109    char *path;
110} entry;
111
112static int older(const void *a, const void *b) {
113    const entry *x = a, *y = b;
114    if (x->mtime.tv_sec != y->mtime.tv_sec) return x->mtime.tv_sec < y->mtime.tv_sec ? -1 : 1;
115    if (x->mtime.tv_nsec != y->mtime.tv_nsec) return x->mtime.tv_nsec < y->mtime.tv_nsec ? -1 : 1;
116    if (x->len != y->len) return x->len < y->len ? -1 : 1;
117    return strcmp(x->path, y->path);
118}
119
120size_t mesh_wasm_cache_evict(const char *dir, uint64_t cap, void (*on_evict)(const char *path)) {
121    DIR *d = opendir(dir);
122    if (!d) return 0;
123    entry *files = NULL;
124    size_t n = 0, ncap = 0;
125    struct dirent *de;
126    while ((de = readdir(d))) {
127        size_t len = strlen(de->d_name);
128        if (len < 6 || strcmp(de->d_name + len - 6, ".cwasm") != 0) continue;
129        char *path = malloc(strlen(dir) + len + 2);
130        if (!path) break;
131        sprintf(path, "%s/%s", dir, de->d_name);
132        struct stat st;
133        if (stat(path, &st) != 0 || !S_ISREG(st.st_mode)) {
134            free(path);
135            continue;
136        }
137        if (n == ncap) {
138            ncap = ncap ? ncap * 2 : 16;
139            entry *q = realloc(files, ncap * sizeof *q);
140            if (!q) {
141                free(path);
142                break;
143            }
144            files = q;
145        }
146        files[n++] = (entry){st.st_mtim, (uint64_t)st.st_size, path};
147    }
148    closedir(d);
149    qsort(files, n, sizeof *files, older);
150    uint64_t total = 0;
151    for (size_t i = 0; i < n; i++) total += files[i].len;
152    size_t evicted = 0;
153    for (size_t i = 0; i < n; i++) {
154        if (total > cap && remove(files[i].path) == 0) {
155            total -= files[i].len;
156            evicted++;
157            if (on_evict) on_evict(files[i].path);
158        }
159        free(files[i].path);
160    }
161    free(files);
162    return evicted;
163}