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