1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
|
/// @file wasm-fs.cc — strong overrides for emscripten's weak __syscall_* stubs.
///
/// emscripten's STANDALONE_WASM libc provides weak implementations of the
/// Linux syscall wrappers that musl calls internally (open, stat, lstat, …).
/// By default they return -ENOSYS/-EPERM, making all file I/O fail.
///
/// We provide strong (non-weak) definitions here that delegate to the WASI
/// preview-1 functions imported from wasi_snapshot_preview1 — the same
/// module wazero already fully implements with WithDirMount("/", "/").
///
/// wazero pre-opens fd 3 as "/", so all path operations use dirfd=3.
/// Only read-only operations are implemented (the evaluator never writes).
///
/// Return convention (Linux syscall ABI, enforced by musl's __syscall_ret):
/// success → 0 (or positive fd for openat)
/// error → -errno (negative POSIX errno value, NOT -1)
/// DO NOT set errno in these functions — __syscall_ret does that.
#ifdef __EMSCRIPTEN__
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <cstdint>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <wasi/api.h>
// ---------------------------------------------------------------------------
// WASI fd 3 = the root directory pre-opened by wazero via WithDirMount("/","/")
// ---------------------------------------------------------------------------
static constexpr __wasi_fd_t WASI_ROOT_FD = 3;
// ---------------------------------------------------------------------------
// errno mapping: WASI errno → negated POSIX errno (for __syscall_ret)
// ---------------------------------------------------------------------------
static int wasi_to_neg_errno(__wasi_errno_t e)
{
switch (e) {
case __WASI_ERRNO_SUCCESS:
return 0;
case __WASI_ERRNO_NOENT:
return -ENOENT;
case __WASI_ERRNO_NOTDIR:
return -ENOTDIR;
case __WASI_ERRNO_ACCES:
return -EACCES;
case __WASI_ERRNO_PERM:
return -EPERM;
case __WASI_ERRNO_NOTSUP:
return -ENOTSUP;
case __WASI_ERRNO_BADF:
return -EBADF;
case __WASI_ERRNO_INVAL:
return -EINVAL;
case __WASI_ERRNO_ISDIR:
return -EISDIR;
case __WASI_ERRNO_NAMETOOLONG:
return -ENAMETOOLONG;
case __WASI_ERRNO_LOOP:
return -ELOOP;
case __WASI_ERRNO_OVERFLOW:
return -EOVERFLOW;
case __WASI_ERRNO_NOSYS:
return -ENOSYS;
case __WASI_ERRNO_NOTCAPABLE:
return -EACCES;
default:
return -EIO;
}
}
// ---------------------------------------------------------------------------
// Helper: strip leading '/' from an absolute path to make it relative to
// WASI_ROOT_FD. WASI path_open takes paths relative to its dirfd.
// An empty result (path was "/") is replaced with "." (the dir itself).
// ---------------------------------------------------------------------------
static const char * relpath(const char * path)
{
while (*path == '/') {
path++;
}
return (*path == '\0') ? "." : path;
}
// ---------------------------------------------------------------------------
// Helper: fill a struct stat from a __wasi_filestat_t (read-only mode bits)
// ---------------------------------------------------------------------------
static void wasi_filestat_to_stat(const __wasi_filestat_t & wfs, struct stat & st)
{
memset(&st, 0, sizeof(st));
st.st_dev = static_cast<dev_t>(wfs.dev);
st.st_ino = static_cast<ino_t>(wfs.ino);
st.st_nlink = static_cast<nlink_t>(wfs.nlink);
st.st_size = static_cast<off_t>(wfs.size);
st.st_atim.tv_sec = static_cast<time_t>(wfs.atim / 1000000000ULL);
st.st_atim.tv_nsec = static_cast<long>(wfs.atim % 1000000000ULL);
st.st_mtim.tv_sec = static_cast<time_t>(wfs.mtim / 1000000000ULL);
st.st_mtim.tv_nsec = static_cast<long>(wfs.mtim % 1000000000ULL);
st.st_ctim.tv_sec = static_cast<time_t>(wfs.ctim / 1000000000ULL);
st.st_ctim.tv_nsec = static_cast<long>(wfs.ctim % 1000000000ULL);
switch (wfs.filetype) {
case __WASI_FILETYPE_REGULAR_FILE:
st.st_mode = S_IFREG | 0444;
break;
case __WASI_FILETYPE_DIRECTORY:
st.st_mode = S_IFDIR | 0555;
break;
case __WASI_FILETYPE_SYMBOLIC_LINK:
st.st_mode = S_IFLNK | 0444;
break;
case __WASI_FILETYPE_CHARACTER_DEVICE:
st.st_mode = S_IFCHR | 0444;
break;
case __WASI_FILETYPE_BLOCK_DEVICE:
st.st_mode = S_IFBLK | 0444;
break;
default:
st.st_mode = 0;
break;
}
}
// ---------------------------------------------------------------------------
// __syscall_openat(dirfd, path, flags, mode)
// Returns: new fd (≥0) on success, -errno on error.
// ---------------------------------------------------------------------------
extern "C" int __syscall_openat(int /*dirfd*/, intptr_t path_ptr, int flags, ...)
{
const char * path = reinterpret_cast<const char *>(path_ptr);
// Only read-only opens.
if ((flags & O_ACCMODE) != O_RDONLY) {
return -EACCES;
}
__wasi_oflags_t oflags = 0;
if (flags & O_DIRECTORY) {
oflags |= __WASI_OFLAGS_DIRECTORY;
}
__wasi_rights_t rights = __WASI_RIGHTS_FD_READ | __WASI_RIGHTS_FD_SEEK | __WASI_RIGHTS_FD_TELL
| __WASI_RIGHTS_FD_FILESTAT_GET | __WASI_RIGHTS_FD_READDIR;
__wasi_fd_t new_fd = 0;
const char * rel = relpath(path);
__wasi_errno_t err = __wasi_path_open(
WASI_ROOT_FD, __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW, rel, strlen(rel), oflags, rights, rights, 0, &new_fd
);
if (err != __WASI_ERRNO_SUCCESS) {
return wasi_to_neg_errno(err);
}
return static_cast<int>(new_fd);
}
// ---------------------------------------------------------------------------
// __syscall_stat64(path, buf) — stat with symlink following
// Returns: 0 on success, -errno on error.
// ---------------------------------------------------------------------------
extern "C" int __syscall_stat64(intptr_t path_ptr, intptr_t buf_ptr)
{
const char * path = reinterpret_cast<const char *>(path_ptr);
struct stat * buf = reinterpret_cast<struct stat *>(buf_ptr);
const char * rel = relpath(path);
__wasi_filestat_t wfs{};
__wasi_errno_t err =
__wasi_path_filestat_get(WASI_ROOT_FD, __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW, rel, strlen(rel), &wfs);
if (err != __WASI_ERRNO_SUCCESS) {
return wasi_to_neg_errno(err);
}
wasi_filestat_to_stat(wfs, *buf);
return 0;
}
// ---------------------------------------------------------------------------
// __syscall_lstat64(path, buf) — stat without symlink following
// Returns: 0 on success, -errno on error.
// ---------------------------------------------------------------------------
extern "C" int __syscall_lstat64(intptr_t path_ptr, intptr_t buf_ptr)
{
const char * path = reinterpret_cast<const char *>(path_ptr);
struct stat * buf = reinterpret_cast<struct stat *>(buf_ptr);
const char * rel = relpath(path);
__wasi_filestat_t wfs{};
__wasi_errno_t err = __wasi_path_filestat_get(
WASI_ROOT_FD,
0, // no SYMLINK_FOLLOW → lstat behaviour
rel,
strlen(rel),
&wfs
);
if (err != __WASI_ERRNO_SUCCESS) {
return wasi_to_neg_errno(err);
}
wasi_filestat_to_stat(wfs, *buf);
return 0;
}
// ---------------------------------------------------------------------------
// __syscall_newfstatat(dirfd, path, buf, flags)
// flags: AT_SYMLINK_NOFOLLOW = 0x100
// Returns: 0 on success, -errno on error.
// ---------------------------------------------------------------------------
extern "C" int __syscall_newfstatat(int /*dirfd*/, intptr_t path_ptr, intptr_t buf_ptr, int flags)
{
const char * path = reinterpret_cast<const char *>(path_ptr);
struct stat * buf = reinterpret_cast<struct stat *>(buf_ptr);
const char * rel = relpath(path);
__wasi_lookupflags_t lflags =
(flags & 0x100 /*AT_SYMLINK_NOFOLLOW*/) ? 0 : __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW;
__wasi_filestat_t wfs{};
__wasi_errno_t err = __wasi_path_filestat_get(WASI_ROOT_FD, lflags, rel, strlen(rel), &wfs);
if (err != __WASI_ERRNO_SUCCESS) {
return wasi_to_neg_errno(err);
}
wasi_filestat_to_stat(wfs, *buf);
return 0;
}
// ---------------------------------------------------------------------------
// __syscall_fstat64(fd, buf) — stat an already-open fd
// Returns: 0 on success, -errno on error.
// ---------------------------------------------------------------------------
extern "C" int __syscall_fstat64(int fd, intptr_t buf_ptr)
{
struct stat * buf = reinterpret_cast<struct stat *>(buf_ptr);
__wasi_filestat_t wfs{};
__wasi_errno_t err = __wasi_fd_filestat_get(static_cast<__wasi_fd_t>(fd), &wfs);
if (err != __WASI_ERRNO_SUCCESS) {
return wasi_to_neg_errno(err);
}
wasi_filestat_to_stat(wfs, *buf);
return 0;
}
// ---------------------------------------------------------------------------
// __syscall_getdents64(fd, dirp, count)
// Fills Linux dirent structs from WASI fd_readdir output.
// Returns: bytes written to dirp on success, -errno on error.
// ---------------------------------------------------------------------------
// Linux dirent (what musl's getdents64 syscall expects in the buffer).
// dirent64 is aliased to dirent in emscripten's dirent.h.
struct linux_dirent64
{
uint64_t d_ino;
int64_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name[1]; // variable, padded to 8-byte alignment
};
extern "C" int __syscall_getdents64(int fd, intptr_t dirp_ptr, size_t count)
{
uint8_t * out = reinterpret_cast<uint8_t *>(dirp_ptr);
size_t out_rem = count;
size_t out_used = 0;
// Buffer for raw WASI dirent data (stack — safe for single-threaded WASM).
static constexpr size_t RBUF = 4096;
uint8_t rbuf[RBUF];
__wasi_size_t bufused = 0;
__wasi_errno_t err =
__wasi_fd_readdir(static_cast<__wasi_fd_t>(fd), rbuf, RBUF, __WASI_DIRCOOKIE_START, &bufused);
if (err != __WASI_ERRNO_SUCCESS) {
return wasi_to_neg_errno(err);
}
uint8_t * p = rbuf;
uint8_t * end = rbuf + bufused;
while (p + sizeof(__wasi_dirent_t) <= end) {
__wasi_dirent_t wd;
memcpy(&wd, p, sizeof(wd));
p += sizeof(wd);
uint32_t namlen = wd.d_namlen;
if (p + namlen > end) {
break;
}
// Record length: header + name + NUL, rounded up to 8.
size_t reclen = offsetof(linux_dirent64, d_name) + namlen + 1;
reclen = (reclen + 7) & ~size_t(7);
if (reclen > out_rem) {
break; // caller's buffer full
}
auto * ld = reinterpret_cast<linux_dirent64 *>(out);
memset(ld, 0, reclen);
ld->d_ino = static_cast<uint64_t>(wd.d_ino);
ld->d_off = static_cast<int64_t>(wd.d_next);
ld->d_reclen = static_cast<unsigned short>(reclen);
switch (wd.d_type) {
case __WASI_FILETYPE_REGULAR_FILE:
ld->d_type = DT_REG;
break;
case __WASI_FILETYPE_DIRECTORY:
ld->d_type = DT_DIR;
break;
case __WASI_FILETYPE_SYMBOLIC_LINK:
ld->d_type = DT_LNK;
break;
case __WASI_FILETYPE_CHARACTER_DEVICE:
ld->d_type = DT_CHR;
break;
case __WASI_FILETYPE_BLOCK_DEVICE:
ld->d_type = DT_BLK;
break;
default:
ld->d_type = DT_UNKNOWN;
break;
}
memcpy(ld->d_name, p, namlen);
ld->d_name[namlen] = '\0';
out += reclen;
out_rem -= reclen;
out_used += reclen;
p += namlen;
}
return static_cast<int>(out_used);
}
// ---------------------------------------------------------------------------
// __syscall_readlinkat(dirfd, path, buf, bufsiz)
// Returns: bytes written on success, -errno on error.
// ---------------------------------------------------------------------------
extern "C" int __syscall_readlinkat(int /*dirfd*/, intptr_t path_ptr, intptr_t buf_ptr, size_t bufsiz)
{
const char * path = reinterpret_cast<const char *>(path_ptr);
char * buf = reinterpret_cast<char *>(buf_ptr);
const char * rel = relpath(path);
__wasi_size_t bufused = 0;
__wasi_errno_t err = __wasi_path_readlink(
WASI_ROOT_FD,
rel,
strlen(rel),
reinterpret_cast<uint8_t *>(buf),
static_cast<__wasi_size_t>(bufsiz),
&bufused
);
if (err != __WASI_ERRNO_SUCCESS) {
return wasi_to_neg_errno(err);
}
return static_cast<int>(bufused);
}
#endif // __EMSCRIPTEN__
|