@volter/browser-wali 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -0
  3. package/dist/browser-wali-command.d.ts +22 -0
  4. package/dist/browser-wali-command.d.ts.map +1 -0
  5. package/dist/browser-wali-command.js +44 -0
  6. package/dist/browser-wali-command.js.map +1 -0
  7. package/dist/capabilities.d.ts +3 -0
  8. package/dist/capabilities.d.ts.map +1 -0
  9. package/dist/capabilities.js +23 -0
  10. package/dist/capabilities.js.map +1 -0
  11. package/dist/coherent-file-locks.d.ts +23 -0
  12. package/dist/coherent-file-locks.d.ts.map +1 -0
  13. package/dist/coherent-file-locks.js +134 -0
  14. package/dist/coherent-file-locks.js.map +1 -0
  15. package/dist/events.d.ts +30 -0
  16. package/dist/events.d.ts.map +1 -0
  17. package/dist/events.js +289 -0
  18. package/dist/events.js.map +1 -0
  19. package/dist/filesystem-protocol.d.ts +48 -0
  20. package/dist/filesystem-protocol.d.ts.map +1 -0
  21. package/dist/filesystem-protocol.js +27 -0
  22. package/dist/filesystem-protocol.js.map +1 -0
  23. package/dist/filesystem-remote.d.ts +65 -0
  24. package/dist/filesystem-remote.d.ts.map +1 -0
  25. package/dist/filesystem-remote.js +271 -0
  26. package/dist/filesystem-remote.js.map +1 -0
  27. package/dist/filesystem-worker.d.ts +2 -0
  28. package/dist/filesystem-worker.d.ts.map +1 -0
  29. package/dist/filesystem-worker.js +269 -0
  30. package/dist/filesystem-worker.js.map +1 -0
  31. package/dist/filesystem.d.ts +115 -0
  32. package/dist/filesystem.d.ts.map +1 -0
  33. package/dist/filesystem.js +1179 -0
  34. package/dist/filesystem.js.map +1 -0
  35. package/dist/index.d.ts +20 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +18 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/inspect.d.ts +7 -0
  40. package/dist/inspect.d.ts.map +1 -0
  41. package/dist/inspect.js +32 -0
  42. package/dist/inspect.js.map +1 -0
  43. package/dist/local-sockets.d.ts +13 -0
  44. package/dist/local-sockets.d.ts.map +1 -0
  45. package/dist/local-sockets.js +139 -0
  46. package/dist/local-sockets.js.map +1 -0
  47. package/dist/loopback-host.d.ts +14 -0
  48. package/dist/loopback-host.d.ts.map +1 -0
  49. package/dist/loopback-host.js +476 -0
  50. package/dist/loopback-host.js.map +1 -0
  51. package/dist/loopback-protocol.d.ts +41 -0
  52. package/dist/loopback-protocol.d.ts.map +1 -0
  53. package/dist/loopback-protocol.js +2 -0
  54. package/dist/loopback-protocol.js.map +1 -0
  55. package/dist/memory.d.ts +39 -0
  56. package/dist/memory.d.ts.map +1 -0
  57. package/dist/memory.js +520 -0
  58. package/dist/memory.js.map +1 -0
  59. package/dist/module-layout.d.ts +9 -0
  60. package/dist/module-layout.d.ts.map +1 -0
  61. package/dist/module-layout.js +105 -0
  62. package/dist/module-layout.js.map +1 -0
  63. package/dist/network-protocol.d.ts +43 -0
  64. package/dist/network-protocol.d.ts.map +1 -0
  65. package/dist/network-protocol.js +26 -0
  66. package/dist/network-protocol.js.map +1 -0
  67. package/dist/network-worker.d.ts +2 -0
  68. package/dist/network-worker.d.ts.map +1 -0
  69. package/dist/network-worker.js +943 -0
  70. package/dist/network-worker.js.map +1 -0
  71. package/dist/network.d.ts +52 -0
  72. package/dist/network.d.ts.map +1 -0
  73. package/dist/network.js +427 -0
  74. package/dist/network.js.map +1 -0
  75. package/dist/process-adapter.d.ts +14 -0
  76. package/dist/process-adapter.d.ts.map +1 -0
  77. package/dist/process-adapter.js +45 -0
  78. package/dist/process-adapter.js.map +1 -0
  79. package/dist/process-bridge.d.ts +54 -0
  80. package/dist/process-bridge.d.ts.map +1 -0
  81. package/dist/process-bridge.js +150 -0
  82. package/dist/process-bridge.js.map +1 -0
  83. package/dist/program.d.ts +49 -0
  84. package/dist/program.d.ts.map +1 -0
  85. package/dist/program.js +204 -0
  86. package/dist/program.js.map +1 -0
  87. package/dist/runtime.d.ts +9 -0
  88. package/dist/runtime.d.ts.map +1 -0
  89. package/dist/runtime.js +528 -0
  90. package/dist/runtime.js.map +1 -0
  91. package/dist/shared-pipes.d.ts +44 -0
  92. package/dist/shared-pipes.d.ts.map +1 -0
  93. package/dist/shared-pipes.js +306 -0
  94. package/dist/shared-pipes.js.map +1 -0
  95. package/dist/snapshot-filesystem.d.ts +31 -0
  96. package/dist/snapshot-filesystem.d.ts.map +1 -0
  97. package/dist/snapshot-filesystem.js +247 -0
  98. package/dist/snapshot-filesystem.js.map +1 -0
  99. package/dist/stdin.d.ts +36 -0
  100. package/dist/stdin.d.ts.map +1 -0
  101. package/dist/stdin.js +143 -0
  102. package/dist/stdin.js.map +1 -0
  103. package/dist/symlink.d.ts +8 -0
  104. package/dist/symlink.d.ts.map +1 -0
  105. package/dist/symlink.js +66 -0
  106. package/dist/symlink.js.map +1 -0
  107. package/dist/system.d.ts +41 -0
  108. package/dist/system.d.ts.map +1 -0
  109. package/dist/system.js +305 -0
  110. package/dist/system.js.map +1 -0
  111. package/dist/thread-coordinator-worker.d.ts +25 -0
  112. package/dist/thread-coordinator-worker.d.ts.map +1 -0
  113. package/dist/thread-coordinator-worker.js +49 -0
  114. package/dist/thread-coordinator-worker.js.map +1 -0
  115. package/dist/thread-worker.d.ts +2 -0
  116. package/dist/thread-worker.d.ts.map +1 -0
  117. package/dist/thread-worker.js +725 -0
  118. package/dist/thread-worker.js.map +1 -0
  119. package/dist/threads.d.ts +44 -0
  120. package/dist/threads.d.ts.map +1 -0
  121. package/dist/threads.js +154 -0
  122. package/dist/threads.js.map +1 -0
  123. package/dist/types.d.ts +112 -0
  124. package/dist/types.d.ts.map +1 -0
  125. package/dist/types.js +2 -0
  126. package/dist/types.js.map +1 -0
  127. package/dist/websocket-peer.d.ts +38 -0
  128. package/dist/websocket-peer.d.ts.map +1 -0
  129. package/dist/websocket-peer.js +407 -0
  130. package/dist/websocket-peer.js.map +1 -0
  131. package/dist/worker-program-protocol.d.ts +71 -0
  132. package/dist/worker-program-protocol.d.ts.map +1 -0
  133. package/dist/worker-program-protocol.js +2 -0
  134. package/dist/worker-program-protocol.js.map +1 -0
  135. package/dist/worker-program-worker.d.ts +2 -0
  136. package/dist/worker-program-worker.d.ts.map +1 -0
  137. package/dist/worker-program-worker.js +109 -0
  138. package/dist/worker-program-worker.js.map +1 -0
  139. package/dist/worker-program.d.ts +58 -0
  140. package/dist/worker-program.d.ts.map +1 -0
  141. package/dist/worker-program.js +425 -0
  142. package/dist/worker-program.js.map +1 -0
  143. package/package.json +23 -0
@@ -0,0 +1,1179 @@
1
+ import { BufferedWaliStdin } from "./stdin.js";
2
+ import { readWaliSymlink, resolveWaliSymlinkPath, writeWaliSymlink } from "./symlink.js";
3
+ const AT_FDCWD = -100;
4
+ const O_ACCMODE = 3;
5
+ const O_WRONLY = 1;
6
+ const O_RDWR = 2;
7
+ const O_CREAT = 0x40;
8
+ const O_EXCL = 0x80;
9
+ const O_TRUNC = 0x200;
10
+ const O_APPEND = 0x400;
11
+ const O_NONBLOCK = 0x800;
12
+ const O_DIRECTORY = 0x1_0000;
13
+ const SEEK_SET = 0;
14
+ const SEEK_CUR = 1;
15
+ const SEEK_END = 2;
16
+ const F_GETLK = 5;
17
+ const F_SETLK = 6;
18
+ const F_SETLKW = 7;
19
+ const F_OFD_GETLK = 36;
20
+ const F_OFD_SETLK = 37;
21
+ const F_OFD_SETLKW = 38;
22
+ const F_UNLCK = 2;
23
+ const F_RDLCK = 0;
24
+ const F_WRLCK = 1;
25
+ const LOCK_SH = 1;
26
+ const LOCK_EX = 2;
27
+ const LOCK_NB = 4;
28
+ const LOCK_UN = 8;
29
+ const AT_SYMLINK_NOFOLLOW = 0x100;
30
+ const AT_REMOVEDIR = 0x200;
31
+ const UTIME_NOW = 0x3fffffffn;
32
+ const UTIME_OMIT = 0x3ffffffen;
33
+ const S_IFREG = 0o100000;
34
+ const S_IFDIR = 0o040000;
35
+ const S_IFLNK = 0o120000;
36
+ export function createStdioAliasRegistry() {
37
+ const aliases = new Int32Array(new SharedArrayBuffer(65_536 * Int32Array.BYTES_PER_ELEMENT));
38
+ aliases.fill(-1);
39
+ aliases[0] = 0;
40
+ aliases[1] = 1;
41
+ aliases[2] = 2;
42
+ return aliases;
43
+ }
44
+ export class WaliFileDescriptorTable {
45
+ options;
46
+ files = new Map();
47
+ specialDescriptors = new Map();
48
+ stdioAliases = new Map();
49
+ descriptorStatusFlags = new Map();
50
+ stdin;
51
+ nextFd = 3;
52
+ nextDescription = 1;
53
+ cwd;
54
+ constructor(options) {
55
+ this.options = options;
56
+ this.cwd = normalizePath("/", options.cwd);
57
+ this.stdin = "read" in options.stdin ? options.stdin : new BufferedWaliStdin(options.stdin);
58
+ }
59
+ imports() {
60
+ return {
61
+ SYS_open: (path, flags, mode) => this.open(this.readCString(Number(path)), Number(flags), Number(mode)),
62
+ SYS_openat: (dirfd, path, flags, mode) => this.openAt(Number(dirfd), this.readCString(Number(path)), Number(flags), Number(mode)),
63
+ SYS_close: (fd) => this.close(Number(fd)),
64
+ SYS_read: (fd, pointer, length) => this.read(Number(fd), Number(pointer), Number(length)),
65
+ SYS_pread64: (fd, pointer, length, offset) => this.pread(Number(fd), Number(pointer), Number(length), Number(offset)),
66
+ SYS_write: (fd, pointer, length) => this.write(Number(fd), Number(pointer), Number(length)),
67
+ SYS_pwrite64: (fd, pointer, length, offset) => this.pwrite(Number(fd), Number(pointer), Number(length), Number(offset)),
68
+ SYS_readv: (fd, iov, count) => this.readv(Number(fd), Number(iov), Number(count)),
69
+ SYS_writev: (fd, iov, count) => this.writev(Number(fd), Number(iov), Number(count)),
70
+ SYS_lseek: (fd, offset, whence) => this.lseek(Number(fd), Number(offset), Number(whence)),
71
+ SYS_ftruncate: (fd, length) => this.ftruncate(Number(fd), Number(length)),
72
+ SYS_fstat: (fd, stat) => this.fstat(Number(fd), Number(stat)),
73
+ SYS_stat: (path, stat) => this.stat(this.readCString(Number(path)), Number(stat), false),
74
+ SYS_lstat: (path, stat) => this.stat(this.readCString(Number(path)), Number(stat), true),
75
+ SYS_newfstatat: (dirfd, path, stat, flags) => this.statAt(Number(dirfd), this.readCString(Number(path)), Number(stat), Number(flags)),
76
+ SYS_access: (path) => this.access(this.readCString(Number(path))),
77
+ SYS_faccessat: (dirfd, path) => this.accessAt(Number(dirfd), this.readCString(Number(path))),
78
+ SYS_faccessat2: (dirfd, path) => this.accessAt(Number(dirfd), this.readCString(Number(path))),
79
+ SYS_chmod: (path, mode) => this.chmod(this.readCString(Number(path)), Number(mode)),
80
+ SYS_fchmod: (fd, mode) => this.fchmod(Number(fd), Number(mode)),
81
+ SYS_fchmodat: (dirfd, path, mode, flags) => this.chmodAt(Number(dirfd), this.readCString(Number(path)), Number(mode), Number(flags)),
82
+ SYS_chdir: (path) => this.chdir(this.readCString(Number(path))),
83
+ SYS_fchdir: (fd) => this.fchdir(Number(fd)),
84
+ SYS_getcwd: (pointer, size) => this.getcwd(Number(pointer), Number(size)),
85
+ SYS_mkdir: (path, mode) => this.mkdir(this.readCString(Number(path)), Number(mode)),
86
+ SYS_mkdirat: (dirfd, path, mode) => this.mkdirAt(Number(dirfd), this.readCString(Number(path)), Number(mode)),
87
+ SYS_statfs: (path, stat) => this.statfs(this.readCString(Number(path)), Number(stat)),
88
+ SYS_fstatfs: (fd, stat) => this.fstatfs(Number(fd), Number(stat)),
89
+ SYS_inotify_init: () => this.inotifyInit(),
90
+ SYS_inotify_init1: () => this.inotifyInit(),
91
+ SYS_inotify_add_watch: (fd, path) => this.inotifyAddWatch(Number(fd), this.readCString(Number(path))),
92
+ SYS_inotify_rm_watch: (fd) => this.specialDescriptors.has(Number(fd)) ? 0n : -9n,
93
+ SYS_rmdir: (path) => this.mutate(() => this.fs().rmdirSync(this.resolveFilesystemPath(this.resolve(this.readCString(Number(path))), false))),
94
+ SYS_unlink: (path) => this.mutate(() => this.fs().unlinkSync(this.resolveFilesystemPath(this.resolve(this.readCString(Number(path))), false))),
95
+ SYS_unlinkat: (dirfd, path, flags) => this.unlinkAt(Number(dirfd), this.readCString(Number(path)), Number(flags)),
96
+ SYS_rename: (from, to) => this.rename(this.readCString(Number(from)), this.readCString(Number(to))),
97
+ SYS_utimensat: (dirfd, path, times, flags) => this.utimensat(Number(dirfd), Number(path), Number(times), Number(flags)),
98
+ SYS_symlink: (target, link) => this.symlink(this.readCString(Number(target)), this.readCString(Number(link)), AT_FDCWD),
99
+ SYS_symlinkat: (target, dirfd, link) => this.symlink(this.readCString(Number(target)), this.readCString(Number(link)), Number(dirfd)),
100
+ SYS_readlink: (path, pointer, size) => this.readlink(this.readCString(Number(path)), Number(pointer), Number(size), AT_FDCWD),
101
+ SYS_readlinkat: (dirfd, path, pointer, size) => this.readlink(this.readCString(Number(path)), Number(pointer), Number(size), Number(dirfd)),
102
+ SYS_dup: (fd) => this.dup(Number(fd)),
103
+ SYS_dup2: (fd, target) => this.dup(Number(fd), Number(target)),
104
+ SYS_dup3: (fd, target) => this.dup(Number(fd), Number(target)),
105
+ // fcntl's third parameter is variadic. WALI transports the ABI register
106
+ // as i64, so its upper 32 bits are unspecified for integer arguments.
107
+ SYS_fcntl: (fd, command, argument) => this.fcntl(Number(fd), Number(command), lowSigned32(argument)),
108
+ SYS_poll: (fds, count, timeout) => this.poll(Number(fds), Number(count), Number(timeout)),
109
+ SYS_getdents64: (fd, pointer, count) => this.getdents64(Number(fd), Number(pointer), Number(count)),
110
+ SYS_fsync: (fd) => this.hasFd(Number(fd)) ? 0n : -9n,
111
+ SYS_fdatasync: (fd) => this.hasFd(Number(fd)) ? 0n : -9n,
112
+ SYS_flock: (fd, operation) => this.flock(Number(fd), Number(operation)),
113
+ };
114
+ }
115
+ writeEnvironmentFile(env, pid) {
116
+ if (!this.options.filesystem || Object.keys(env).length === 0)
117
+ return;
118
+ const path = `/tmp/.browser-runtime-wali-env-${pid}`;
119
+ try {
120
+ this.options.filesystem.mkdirSync("/tmp", { recursive: true });
121
+ }
122
+ catch { /* already present */ }
123
+ const lines = Object.entries(env).map(([name, value]) => {
124
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name) || /[\u0000\r\n]/u.test(value))
125
+ throw new Error(`Invalid WALI environment entry: ${name}`);
126
+ return `${name}=${value}`;
127
+ });
128
+ this.options.filesystem.writeFileSync(path, `${lines.join("\n")}\n`);
129
+ return path;
130
+ }
131
+ copyCString(pointer, value, limit) {
132
+ const encoded = new TextEncoder().encode(`${value}\0`);
133
+ if (limit < encoded.byteLength)
134
+ return 0;
135
+ this.copyIn(pointer, encoded);
136
+ return 1;
137
+ }
138
+ readForMapping(fd, offset, length) {
139
+ const file = this.files.get(fd);
140
+ if (!file || file.directory || offset < 0 || length < 0)
141
+ return;
142
+ return this.fs().readFileSync(file.path).slice(offset, offset + length);
143
+ }
144
+ mappingPath(fd) {
145
+ const file = this.files.get(fd);
146
+ return file && !file.directory ? file.path : undefined;
147
+ }
148
+ writeForMapping(path, offset, value) {
149
+ try {
150
+ if (!Number.isSafeInteger(offset) || offset < 0 || !this.fs().existsSync(path))
151
+ return false;
152
+ const stat = this.fs().statSync(path);
153
+ if (!stat.isFile())
154
+ return false;
155
+ const current = this.fs().readFileSync(path);
156
+ const next = new Uint8Array(Math.max(current.byteLength, offset + value.byteLength));
157
+ next.set(current);
158
+ next.set(value, offset);
159
+ this.fs().writeFileSync(path, next);
160
+ return true;
161
+ }
162
+ catch {
163
+ return false;
164
+ }
165
+ }
166
+ allocateSpecial(descriptor, minimum = 3) {
167
+ const fd = this.allocateFd(Math.max(minimum, this.options.specialDescriptorMinimum ?? 3));
168
+ this.specialDescriptors.set(fd, descriptor);
169
+ return fd;
170
+ }
171
+ exportBrowserProcessPipe(fd) {
172
+ return this.specialDescriptors.get(fd)?.browserProcessPipe;
173
+ }
174
+ currentWorkingDirectory() { return this.cwd; }
175
+ ioctl(fd, request) {
176
+ if (!this.hasFd(fd))
177
+ return;
178
+ // FIONBIO/FIOCLEX/FIONCLEX only mutate descriptor flags. Browser pipes
179
+ // are already nonblocking and close-on-exec is handled by the spawn ABI.
180
+ if (request === 0x5421 || request === 0x5451 || request === 0x5450)
181
+ return 0n;
182
+ return;
183
+ }
184
+ pollDescriptor(fd, events) {
185
+ if (fd < 0 || !this.hasFd(fd))
186
+ return 0x20;
187
+ const special = this.specialDescriptors.get(fd);
188
+ if (special)
189
+ return special.poll?.(events) ?? 0;
190
+ const stdio = this.stdioFd(fd);
191
+ let returned = 0;
192
+ if ((events & 0x1) && stdio === 0 && this.stdin.poll())
193
+ returned |= 0x1;
194
+ if ((events & 0x4) && stdio !== 0)
195
+ returned |= 0x4;
196
+ return returned;
197
+ }
198
+ fs() {
199
+ if (!this.options.filesystem)
200
+ throw errno("ENOSYS");
201
+ return this.options.filesystem;
202
+ }
203
+ memoryBytes() { return new Uint8Array(this.options.memory().buffer); }
204
+ memoryView() { return new DataView(this.options.memory().buffer); }
205
+ copyOut(pointer, length) {
206
+ assertRange(this.options.memory(), pointer, length);
207
+ return this.memoryBytes().slice(pointer, pointer + length);
208
+ }
209
+ copyIn(pointer, value) {
210
+ assertRange(this.options.memory(), pointer, value.byteLength);
211
+ this.memoryBytes().set(value, pointer);
212
+ }
213
+ readCString(pointer) {
214
+ const bytes = this.memoryBytes();
215
+ if (!Number.isSafeInteger(pointer) || pointer < 0 || pointer >= bytes.byteLength)
216
+ throw new WebAssembly.RuntimeError("WALI string pointer is out of bounds.");
217
+ const end = bytes.indexOf(0, pointer);
218
+ if (end < 0 || end - pointer > 1_048_576)
219
+ throw new WebAssembly.RuntimeError("WALI string is not null terminated.");
220
+ // Browser TextDecoder deliberately rejects views backed by
221
+ // SharedArrayBuffer. Copying the short C string also gives the decoder a
222
+ // stable snapshot while another pthread may write process memory.
223
+ return new TextDecoder("utf-8", { fatal: true }).decode(Uint8Array.from(bytes.subarray(pointer, end)));
224
+ }
225
+ resolve(path) { return normalizePath(this.cwd, path); }
226
+ resolveAt(dirfd, path) {
227
+ if (path.startsWith("/"))
228
+ return normalizePath("/", path);
229
+ if (dirfd === AT_FDCWD)
230
+ return this.resolve(path);
231
+ const directory = this.files.get(dirfd);
232
+ if (!directory?.directory)
233
+ throw errno("ENOTDIR");
234
+ return normalizePath(directory.path, path);
235
+ }
236
+ open(path, flags, mode) {
237
+ try {
238
+ return BigInt(this.openResolved(this.resolveFilesystemPath(this.resolve(path)), flags, mode));
239
+ }
240
+ catch (error) {
241
+ return negativeErrno(error);
242
+ }
243
+ }
244
+ openAt(dirfd, path, flags, mode) {
245
+ try {
246
+ return BigInt(this.openResolved(this.resolveFilesystemPath(this.resolveAt(dirfd, path)), flags, mode));
247
+ }
248
+ catch (error) {
249
+ return negativeErrno(error);
250
+ }
251
+ }
252
+ openResolved(path, flags, mode) {
253
+ if (path === "/dev/null") {
254
+ return this.allocateSpecial({
255
+ read: () => new Uint8Array(),
256
+ write: (value) => BigInt(value.byteLength),
257
+ poll: (events) => ((events & 1) ? 1 : 0) | ((events & 4) ? 4 : 0),
258
+ fcntl: (command) => command === 3 ? BigInt(flags) : [1, 2, 4].includes(command) ? 0n : -22n,
259
+ });
260
+ }
261
+ if (path === "/dev/urandom" || path === "/dev/random") {
262
+ return this.allocateSpecial({
263
+ read: (length) => {
264
+ if (!Number.isSafeInteger(length) || length < 0)
265
+ return -22n;
266
+ const value = new Uint8Array(length);
267
+ for (let offset = 0; offset < value.byteLength; offset += 65_536) {
268
+ crypto.getRandomValues(value.subarray(offset, Math.min(offset + 65_536, value.byteLength)));
269
+ }
270
+ return value;
271
+ },
272
+ write: (value) => BigInt(value.byteLength),
273
+ poll: (events) => ((events & 1) ? 1 : 0) | ((events & 4) ? 4 : 0),
274
+ fcntl: (command) => command === 3 ? BigInt(flags) : [1, 2, 4].includes(command) ? 0n : -22n,
275
+ });
276
+ }
277
+ const fs = this.fs();
278
+ const exists = fs.existsSync(path);
279
+ if (!exists && !(flags & O_CREAT))
280
+ throw errno("ENOENT");
281
+ if (exists && (flags & O_CREAT) && (flags & O_EXCL))
282
+ throw errno("EEXIST");
283
+ if (!exists) {
284
+ fs.writeFileSync(path, new Uint8Array());
285
+ fs.chmodSync?.(path, mode & 0o7777);
286
+ this.options.fileLocks?.created?.(path);
287
+ }
288
+ const stat = fs.statSync(path);
289
+ if ((flags & O_DIRECTORY) && !stat.isDirectory())
290
+ throw errno("ENOTDIR");
291
+ if (stat.isDirectory() && (flags & O_ACCMODE) !== 0)
292
+ throw errno("EISDIR");
293
+ if ((flags & O_TRUNC) && (flags & O_ACCMODE) !== 0 && stat.isFile())
294
+ fs.writeFileSync(path, new Uint8Array());
295
+ const fd = this.allocateFd();
296
+ const size = stat.isFile() ? fs.statSync(path).size : 0;
297
+ this.files.set(fd, {
298
+ path,
299
+ flags,
300
+ position: flags & O_APPEND ? size : 0,
301
+ directory: stat.isDirectory(),
302
+ description: this.nextDescription++,
303
+ });
304
+ return fd;
305
+ }
306
+ allocateFd(minimum = 3) {
307
+ this.nextFd = Math.max(this.nextFd, minimum);
308
+ while (this.files.has(this.nextFd) || this.specialDescriptors.has(this.nextFd)
309
+ || this.stdioFd(this.nextFd) !== undefined)
310
+ this.nextFd += 1;
311
+ if (this.nextFd > (this.options.descriptorMaximum ?? 65_535))
312
+ throw errno("EMFILE");
313
+ return this.nextFd++;
314
+ }
315
+ close(fd) {
316
+ if (fd >= 0 && fd <= 2)
317
+ return 0n;
318
+ if (this.stdioAliases.delete(fd)) {
319
+ this.descriptorStatusFlags.delete(fd);
320
+ this.setSharedStdioAlias(fd, undefined);
321
+ return 0n;
322
+ }
323
+ const special = this.specialDescriptors.get(fd);
324
+ if (special) {
325
+ special.close?.();
326
+ this.specialDescriptors.delete(fd);
327
+ return 0n;
328
+ }
329
+ const file = this.files.get(fd);
330
+ if (!file)
331
+ return -9n;
332
+ this.files.delete(fd);
333
+ this.releasePosixLocks(file.path);
334
+ if (![...this.files.values()].some((candidate) => candidate.description === file.description)) {
335
+ this.releaseLockOwner(this.lockOwner("ofd", file));
336
+ this.releaseLockOwner(this.lockOwner("flock", file));
337
+ }
338
+ return 0n;
339
+ }
340
+ read(fd, pointer, length) {
341
+ try {
342
+ if (length < 0)
343
+ throw errno("EINVAL");
344
+ if (this.stdioFd(fd) === 0) {
345
+ if ((this.statusFlags(fd) & O_NONBLOCK) && !this.stdin.poll())
346
+ return -11n;
347
+ const chunk = this.stdin.read(length);
348
+ this.copyIn(pointer, chunk);
349
+ return BigInt(chunk.byteLength);
350
+ }
351
+ const special = this.specialDescriptors.get(fd);
352
+ if (special) {
353
+ const value = special.read?.(length);
354
+ if (value === undefined)
355
+ throw errno("EBADF");
356
+ if (typeof value === "bigint")
357
+ return value;
358
+ const chunk = value.subarray(0, length);
359
+ this.copyIn(pointer, chunk);
360
+ return BigInt(chunk.byteLength);
361
+ }
362
+ const file = this.files.get(fd);
363
+ if (!file)
364
+ throw errno("EBADF");
365
+ if (file.directory)
366
+ throw errno("EISDIR");
367
+ if ((file.flags & O_ACCMODE) === O_WRONLY)
368
+ throw errno("EBADF");
369
+ const content = this.fs().readFileSync(file.path);
370
+ const chunk = content.subarray(file.position, file.position + length);
371
+ this.copyIn(pointer, chunk);
372
+ file.position += chunk.byteLength;
373
+ return BigInt(chunk.byteLength);
374
+ }
375
+ catch (error) {
376
+ return negativeErrno(error);
377
+ }
378
+ }
379
+ write(fd, pointer, length) {
380
+ try {
381
+ const chunk = this.copyOut(pointer, length);
382
+ const stdio = this.stdioFd(fd);
383
+ if (stdio === 1 || stdio === 2)
384
+ return this.options.emit(stdio, chunk);
385
+ const special = this.specialDescriptors.get(fd);
386
+ if (special)
387
+ return special.write?.(chunk) ?? -9n;
388
+ const file = this.files.get(fd);
389
+ if (!file)
390
+ throw errno("EBADF");
391
+ if (file.directory)
392
+ throw errno("EISDIR");
393
+ if ((file.flags & O_ACCMODE) === 0)
394
+ throw errno("EBADF");
395
+ const current = this.fs().readFileSync(file.path);
396
+ if (file.flags & O_APPEND)
397
+ file.position = current.byteLength;
398
+ const size = Math.max(current.byteLength, file.position + chunk.byteLength);
399
+ const next = new Uint8Array(size);
400
+ next.set(current);
401
+ next.set(chunk, file.position);
402
+ this.fs().writeFileSync(file.path, next);
403
+ file.position += chunk.byteLength;
404
+ return BigInt(chunk.byteLength);
405
+ }
406
+ catch (error) {
407
+ return negativeErrno(error);
408
+ }
409
+ }
410
+ pread(fd, pointer, length, offset) {
411
+ const file = this.files.get(fd);
412
+ if (!file)
413
+ return -9n;
414
+ if (file.directory || !Number.isSafeInteger(offset) || offset < 0)
415
+ return -22n;
416
+ const position = file.position;
417
+ file.position = offset;
418
+ try {
419
+ return this.read(fd, pointer, length);
420
+ }
421
+ finally {
422
+ file.position = position;
423
+ }
424
+ }
425
+ pwrite(fd, pointer, length, offset) {
426
+ const file = this.files.get(fd);
427
+ if (!file)
428
+ return -9n;
429
+ if (file.directory || !Number.isSafeInteger(offset) || offset < 0)
430
+ return -22n;
431
+ const position = file.position;
432
+ const flags = file.flags;
433
+ file.position = offset;
434
+ file.flags &= ~O_APPEND;
435
+ try {
436
+ return this.write(fd, pointer, length);
437
+ }
438
+ finally {
439
+ file.position = position;
440
+ file.flags = flags;
441
+ }
442
+ }
443
+ readv(fd, pointer, count) {
444
+ let total = 0n;
445
+ for (let index = 0; index < count; index += 1) {
446
+ const offset = pointer + index * 8;
447
+ assertRange(this.options.memory(), offset, 8);
448
+ const result = this.read(fd, this.memoryView().getUint32(offset, true), this.memoryView().getUint32(offset + 4, true));
449
+ if (result < 0n)
450
+ return total > 0n ? total : result;
451
+ total += result;
452
+ if (result === 0n)
453
+ break;
454
+ }
455
+ return total;
456
+ }
457
+ writev(fd, pointer, count) {
458
+ let total = 0n;
459
+ for (let index = 0; index < count; index += 1) {
460
+ const offset = pointer + index * 8;
461
+ assertRange(this.options.memory(), offset, 8);
462
+ const result = this.write(fd, this.memoryView().getUint32(offset, true), this.memoryView().getUint32(offset + 4, true));
463
+ if (result < 0n)
464
+ return total > 0n ? total : result;
465
+ total += result;
466
+ }
467
+ return total;
468
+ }
469
+ getdents64(fd, pointer, count) {
470
+ try {
471
+ const directory = this.files.get(fd);
472
+ if (!directory)
473
+ throw errno("EBADF");
474
+ if (!directory.directory)
475
+ throw errno("ENOTDIR");
476
+ const entries = [".", "..", ...this.fs().readdirSync(directory.path)];
477
+ let written = 0;
478
+ while (directory.position < entries.length) {
479
+ const name = entries[directory.position];
480
+ const nameBytes = new TextEncoder().encode(name);
481
+ const recordLength = alignTo(19 + nameBytes.byteLength + 1, 8);
482
+ if (recordLength > count - written) {
483
+ if (written === 0)
484
+ throw errno("EINVAL");
485
+ break;
486
+ }
487
+ const record = pointer + written;
488
+ assertRange(this.options.memory(), record, recordLength);
489
+ const entryPath = name === "." ? directory.path : name === ".."
490
+ ? normalizePath(directory.path, "..") : normalizePath(directory.path, name);
491
+ const stat = this.fs().statSync(entryPath);
492
+ this.memoryView().setBigUint64(record, BigInt(directory.position + 1), true);
493
+ this.memoryView().setBigInt64(record + 8, BigInt(directory.position + 1), true);
494
+ this.memoryView().setUint16(record + 16, recordLength, true);
495
+ this.memoryView().setUint8(record + 18, stat.isDirectory() ? 4 : 8);
496
+ this.memoryBytes().fill(0, record + 19, record + recordLength);
497
+ this.memoryBytes().set(nameBytes, record + 19);
498
+ directory.position += 1;
499
+ written += recordLength;
500
+ }
501
+ return BigInt(written);
502
+ }
503
+ catch (error) {
504
+ return negativeErrno(error);
505
+ }
506
+ }
507
+ lseek(fd, offset, whence) {
508
+ try {
509
+ const file = this.files.get(fd);
510
+ if (!file)
511
+ throw errno("EBADF");
512
+ if (file.directory)
513
+ throw errno("EINVAL");
514
+ const base = whence === SEEK_SET ? 0 : whence === SEEK_CUR ? file.position
515
+ : whence === SEEK_END ? this.fs().statSync(file.path).size : undefined;
516
+ if (base === undefined || base + offset < 0)
517
+ throw errno("EINVAL");
518
+ file.position = base + offset;
519
+ return BigInt(file.position);
520
+ }
521
+ catch (error) {
522
+ return negativeErrno(error);
523
+ }
524
+ }
525
+ ftruncate(fd, length) {
526
+ try {
527
+ const file = this.files.get(fd);
528
+ if (!file)
529
+ throw errno("EBADF");
530
+ if (file.directory)
531
+ throw errno("EISDIR");
532
+ if ((file.flags & O_ACCMODE) === 0)
533
+ throw errno("EBADF");
534
+ if (!Number.isSafeInteger(length) || length < 0)
535
+ throw errno("EINVAL");
536
+ const current = this.fs().readFileSync(file.path);
537
+ if (current.byteLength === length)
538
+ return 0n;
539
+ const resized = new Uint8Array(length);
540
+ resized.set(current.subarray(0, length));
541
+ this.fs().writeFileSync(file.path, resized);
542
+ return 0n;
543
+ }
544
+ catch (error) {
545
+ return negativeErrno(error);
546
+ }
547
+ }
548
+ stat(path, pointer, lstat) {
549
+ try {
550
+ const resolved = this.resolveFilesystemPath(this.resolve(path), !lstat);
551
+ const fs = this.fs();
552
+ if (!fs.existsSync(resolved))
553
+ throw errno("ENOENT");
554
+ const target = lstat ? this.readSymlink(resolved) : undefined;
555
+ writeStat(this.memoryView(), pointer, target === undefined
556
+ ? (lstat && fs.lstatSync ? fs.lstatSync(resolved) : fs.statSync(resolved))
557
+ : syntheticSymlinkStat(target));
558
+ return 0n;
559
+ }
560
+ catch (error) {
561
+ return negativeErrno(error);
562
+ }
563
+ }
564
+ statAt(dirfd, path, pointer, flags) {
565
+ try {
566
+ if (flags & ~AT_SYMLINK_NOFOLLOW)
567
+ throw errno("EINVAL");
568
+ const noFollow = Boolean(flags & AT_SYMLINK_NOFOLLOW);
569
+ const resolved = this.resolveFilesystemPath(this.resolveAt(dirfd, path), !noFollow);
570
+ const fs = this.fs();
571
+ if (!fs.existsSync(resolved))
572
+ throw errno("ENOENT");
573
+ const target = noFollow ? this.readSymlink(resolved) : undefined;
574
+ writeStat(this.memoryView(), pointer, target === undefined ? fs.statSync(resolved) : syntheticSymlinkStat(target));
575
+ return 0n;
576
+ }
577
+ catch (error) {
578
+ return negativeErrno(error);
579
+ }
580
+ }
581
+ fstat(fd, pointer) {
582
+ try {
583
+ if (this.stdioFd(fd) !== undefined) {
584
+ writeStat(this.memoryView(), pointer, syntheticTerminalStat());
585
+ return 0n;
586
+ }
587
+ const file = this.files.get(fd);
588
+ if (!file)
589
+ throw errno("EBADF");
590
+ writeStat(this.memoryView(), pointer, this.fs().statSync(file.path));
591
+ return 0n;
592
+ }
593
+ catch (error) {
594
+ return negativeErrno(error);
595
+ }
596
+ }
597
+ access(path) {
598
+ try {
599
+ return this.fs().existsSync(this.resolveFilesystemPath(this.resolve(path))) ? 0n : -2n;
600
+ }
601
+ catch (error) {
602
+ return negativeErrno(error);
603
+ }
604
+ }
605
+ accessAt(dirfd, path) {
606
+ try {
607
+ return this.fs().existsSync(this.resolveFilesystemPath(this.resolveAt(dirfd, path))) ? 0n : -2n;
608
+ }
609
+ catch (error) {
610
+ return negativeErrno(error);
611
+ }
612
+ }
613
+ chmod(path, mode) {
614
+ try {
615
+ const resolved = this.resolveFilesystemPath(this.resolve(path));
616
+ if (!this.fs().existsSync(resolved))
617
+ return -2n;
618
+ this.fs().chmodSync?.(resolved, mode & 0o7777);
619
+ return 0n;
620
+ }
621
+ catch (error) {
622
+ return negativeErrno(error);
623
+ }
624
+ }
625
+ chmodAt(dirfd, path, mode, flags) {
626
+ try {
627
+ if (flags & ~AT_SYMLINK_NOFOLLOW)
628
+ throw errno("EINVAL");
629
+ const resolved = this.resolveFilesystemPath(this.resolveAt(dirfd, path), !(flags & AT_SYMLINK_NOFOLLOW));
630
+ if (!this.fs().existsSync(resolved))
631
+ return -2n;
632
+ this.fs().chmodSync?.(resolved, mode & 0o7777);
633
+ return 0n;
634
+ }
635
+ catch (error) {
636
+ return negativeErrno(error);
637
+ }
638
+ }
639
+ fchmod(fd, mode) {
640
+ if (this.stdioFd(fd) !== undefined || this.specialDescriptors.has(fd))
641
+ return 0n;
642
+ const file = this.files.get(fd);
643
+ if (!file)
644
+ return -9n;
645
+ try {
646
+ this.fs().chmodSync?.(file.path, mode & 0o7777);
647
+ return 0n;
648
+ }
649
+ catch (error) {
650
+ return negativeErrno(error);
651
+ }
652
+ }
653
+ chdir(path) {
654
+ try {
655
+ const resolved = this.resolveFilesystemPath(this.resolve(path));
656
+ if (!this.fs().statSync(resolved).isDirectory())
657
+ throw errno("ENOTDIR");
658
+ this.cwd = resolved;
659
+ return 0n;
660
+ }
661
+ catch (error) {
662
+ return negativeErrno(error);
663
+ }
664
+ }
665
+ fchdir(fd) {
666
+ const file = this.files.get(fd);
667
+ if (!file)
668
+ return -9n;
669
+ if (!file.directory)
670
+ return -20n;
671
+ this.cwd = file.path;
672
+ return 0n;
673
+ }
674
+ getcwd(pointer, size) {
675
+ const encoded = new TextEncoder().encode(`${this.cwd}\0`);
676
+ if (encoded.byteLength > size)
677
+ return -34n;
678
+ this.copyIn(pointer, encoded);
679
+ return BigInt(encoded.byteLength);
680
+ }
681
+ mkdir(path, mode) {
682
+ return this.mutate(() => {
683
+ const resolved = this.resolveFilesystemPath(this.resolve(path), false);
684
+ this.fs().mkdirSync(resolved);
685
+ this.fs().chmodSync?.(resolved, mode & 0o7777);
686
+ });
687
+ }
688
+ mkdirAt(dirfd, path, mode) {
689
+ try {
690
+ const resolved = this.resolveFilesystemPath(this.resolveAt(dirfd, path), false);
691
+ this.fs().mkdirSync(resolved);
692
+ this.fs().chmodSync?.(resolved, mode & 0o7777);
693
+ return 0n;
694
+ }
695
+ catch (error) {
696
+ return negativeErrno(error);
697
+ }
698
+ }
699
+ statfs(path, pointer) {
700
+ try {
701
+ if (!this.fs().existsSync(this.resolveFilesystemPath(this.resolve(path))))
702
+ throw errno("ENOENT");
703
+ writeStatfs(this.memoryView(), pointer);
704
+ return 0n;
705
+ }
706
+ catch (error) {
707
+ return negativeErrno(error);
708
+ }
709
+ }
710
+ fstatfs(fd, pointer) {
711
+ if (!this.hasFd(fd))
712
+ return -9n;
713
+ writeStatfs(this.memoryView(), pointer);
714
+ return 0n;
715
+ }
716
+ inotifyInit() {
717
+ return BigInt(this.allocateSpecial({
718
+ read: () => -11n,
719
+ poll: () => 0,
720
+ fcntl: (command) => command === 3 ? 0n : [1, 2, 4].includes(command) ? 0n : -22n,
721
+ }));
722
+ }
723
+ inotifyAddWatch(fd, path) {
724
+ if (!this.specialDescriptors.has(fd))
725
+ return -9n;
726
+ try {
727
+ return this.fs().existsSync(this.resolveFilesystemPath(this.resolve(path))) ? 1n : -2n;
728
+ }
729
+ catch (error) {
730
+ return negativeErrno(error);
731
+ }
732
+ }
733
+ unlinkAt(dirfd, path, flags) {
734
+ try {
735
+ if (flags & ~AT_REMOVEDIR)
736
+ throw errno("EINVAL");
737
+ const resolved = this.resolveFilesystemPath(this.resolveAt(dirfd, path), false);
738
+ if (flags & AT_REMOVEDIR)
739
+ this.fs().rmdirSync(resolved);
740
+ else
741
+ this.fs().unlinkSync(resolved);
742
+ return 0n;
743
+ }
744
+ catch (error) {
745
+ return negativeErrno(error);
746
+ }
747
+ }
748
+ rename(from, to) {
749
+ return this.mutate(() => this.fs().renameSync(this.resolveFilesystemPath(this.resolve(from), false), this.resolveFilesystemPath(this.resolve(to), false)));
750
+ }
751
+ utimensat(dirfd, pathPointer, timesPointer, flags) {
752
+ try {
753
+ if (flags & ~AT_SYMLINK_NOFOLLOW)
754
+ throw errno("EINVAL");
755
+ const descriptor = pathPointer === 0 ? this.files.get(dirfd) : undefined;
756
+ if (pathPointer === 0 && !descriptor)
757
+ throw errno("EBADF");
758
+ const unresolved = descriptor?.path ?? this.resolveAt(dirfd, this.readCString(pathPointer));
759
+ const path = this.resolveFilesystemPath(unresolved, !(flags & AT_SYMLINK_NOFOLLOW));
760
+ const filesystem = this.fs();
761
+ const stat = filesystem.statSync(path);
762
+ const now = Date.now();
763
+ const readTime = (index, fallback) => {
764
+ if (timesPointer === 0)
765
+ return now;
766
+ const pointer = timesPointer + index * 16;
767
+ assertRange(this.options.memory(), pointer, 16);
768
+ const seconds = this.memoryView().getBigInt64(pointer, true);
769
+ const nanoseconds = this.memoryView().getBigInt64(pointer + 8, true);
770
+ if (nanoseconds === UTIME_NOW)
771
+ return now;
772
+ if (nanoseconds === UTIME_OMIT)
773
+ return fallback;
774
+ if (nanoseconds < 0 || nanoseconds >= 1000000000n)
775
+ throw errno("EINVAL");
776
+ const milliseconds = Number(seconds) * 1_000 + Number(nanoseconds) / 1_000_000;
777
+ if (!Number.isFinite(milliseconds))
778
+ throw errno("EINVAL");
779
+ return milliseconds;
780
+ };
781
+ const atimeMs = readTime(0, stat.atimeMs ?? stat.mtimeMs ?? now);
782
+ const mtimeMs = readTime(1, stat.mtimeMs ?? now);
783
+ if (filesystem.utimesSync)
784
+ filesystem.utimesSync(path, atimeMs, mtimeMs);
785
+ else if (stat.isFile())
786
+ filesystem.writeFileSync(path, filesystem.readFileSync(path));
787
+ return 0n;
788
+ }
789
+ catch (error) {
790
+ return negativeErrno(error);
791
+ }
792
+ }
793
+ readlink(path, pointer, size, dirfd) {
794
+ try {
795
+ if (!Number.isSafeInteger(size) || size < 0)
796
+ throw errno("EINVAL");
797
+ const resolved = this.resolveAt(dirfd, path);
798
+ let target;
799
+ if (resolved === "/proc/self/exe")
800
+ target = this.options.executablePath ?? "/usr/bin/wali-program";
801
+ const descriptorMatch = /^\/proc\/self\/fd\/(\d+)$/u.exec(resolved);
802
+ if (!target && descriptorMatch) {
803
+ const descriptor = this.files.get(Number(descriptorMatch[1]));
804
+ if (descriptor)
805
+ target = descriptor.path;
806
+ }
807
+ target ??= this.readSymlink(resolved);
808
+ // musl realpath(3) probes every path component with readlink(2).
809
+ // Existing non-symlinks must report EINVAL; ENOENT means the component
810
+ // itself is missing and makes canonicalization fail.
811
+ if (!target)
812
+ return this.fs().existsSync(resolved) ? -22n : -2n;
813
+ const encoded = new TextEncoder().encode(target).subarray(0, size);
814
+ this.copyIn(pointer, encoded);
815
+ return BigInt(encoded.byteLength);
816
+ }
817
+ catch (error) {
818
+ return negativeErrno(error);
819
+ }
820
+ }
821
+ symlink(target, link, dirfd) {
822
+ try {
823
+ const resolved = this.resolveFilesystemPath(this.resolveAt(dirfd, link), false);
824
+ writeWaliSymlink(this.fs(), target, resolved);
825
+ return 0n;
826
+ }
827
+ catch (error) {
828
+ return negativeErrno(error);
829
+ }
830
+ }
831
+ resolveFilesystemPath(path, followFinal = true) {
832
+ return resolveWaliSymlinkPath(this.fs(), path, followFinal);
833
+ }
834
+ readSymlink(path) {
835
+ return readWaliSymlink(this.fs(), path);
836
+ }
837
+ dup(fd, target, minimum = 3) {
838
+ const maximum = this.options.descriptorMaximum ?? 65_535;
839
+ const stdio = this.stdioFd(fd);
840
+ if (stdio !== undefined) {
841
+ const next = target ?? this.allocateFd(minimum);
842
+ if (next < 0 || next > maximum)
843
+ return -9n;
844
+ if (next === fd)
845
+ return BigInt(next);
846
+ this.stdioAliases.set(next, stdio);
847
+ this.descriptorStatusFlags.set(next, this.statusFlags(fd));
848
+ this.setSharedStdioAlias(next, stdio);
849
+ return BigInt(next);
850
+ }
851
+ const file = this.files.get(fd);
852
+ if (!file)
853
+ return -9n;
854
+ const next = target ?? this.allocateFd(minimum);
855
+ if (next < 0 || next > maximum)
856
+ return -9n;
857
+ this.files.set(next, { ...file });
858
+ return BigInt(next);
859
+ }
860
+ fcntl(fd, command, argument) {
861
+ if (!this.hasFd(fd))
862
+ return -9n;
863
+ const special = this.specialDescriptors.get(fd);
864
+ if (special) {
865
+ if (command === 0 || command === 1030) {
866
+ return special.duplicate?.(Math.max(argument, 3), command === 1030) ?? -22n;
867
+ }
868
+ return special.fcntl?.(command, argument) ?? -22n;
869
+ }
870
+ const file = this.files.get(fd);
871
+ if (command === 0 || command === 1030)
872
+ return this.dup(fd, undefined, Math.max(argument, 3));
873
+ if (command === 1 || command === 2)
874
+ return 0n;
875
+ if (command === 3)
876
+ return BigInt(this.statusFlags(fd));
877
+ if (command === 4) {
878
+ const stdio = this.stdioFd(fd);
879
+ if (stdio !== undefined)
880
+ this.descriptorStatusFlags.set(fd, argument);
881
+ else {
882
+ const file = this.files.get(fd);
883
+ if (file)
884
+ file.flags = (file.flags & O_ACCMODE) | (argument & ~O_ACCMODE);
885
+ }
886
+ return 0n;
887
+ }
888
+ if (command === F_GETLK || command === F_OFD_GETLK
889
+ || command === F_SETLK || command === F_SETLKW
890
+ || command === F_OFD_SETLK || command === F_OFD_SETLKW) {
891
+ return file ? this.recordLock(file, command, argument) : -9n;
892
+ }
893
+ return -22n;
894
+ }
895
+ recordLock(file, command, pointer) {
896
+ try {
897
+ const requested = this.readRecordLock(file, pointer);
898
+ const namespace = command >= F_OFD_GETLK ? "ofd" : "posix";
899
+ const owner = this.lockOwner(namespace, file);
900
+ const client = this.options.fileLocks;
901
+ if (!client || !owner) {
902
+ if (command === F_GETLK || command === F_OFD_GETLK)
903
+ this.memoryView().setInt16(pointer, F_UNLCK, true);
904
+ return 0n;
905
+ }
906
+ if (requested.mode === undefined) {
907
+ return BigInt(client.request({ operation: "unlock", owner, namespace, path: file.path, range: requested.range }).result);
908
+ }
909
+ if (command === F_GETLK || command === F_OFD_GETLK) {
910
+ const response = client.request({
911
+ operation: "query", owner, namespace, path: file.path, range: requested.range,
912
+ mode: requested.mode, pid: this.options.pid ?? 1,
913
+ });
914
+ if (response.result < 0)
915
+ return BigInt(response.result);
916
+ if (!response.conflict)
917
+ this.memoryView().setInt16(pointer, F_UNLCK, true);
918
+ else
919
+ this.writeRecordLock(pointer, response.conflict.mode, response.conflict, response.conflict.pid);
920
+ return 0n;
921
+ }
922
+ const blocking = command === F_SETLKW || command === F_OFD_SETLKW;
923
+ return BigInt(client.request({
924
+ operation: "set", owner, namespace, path: file.path, range: requested.range,
925
+ mode: requested.mode, pid: this.options.pid ?? 1, blocking,
926
+ }).result);
927
+ }
928
+ catch (error) {
929
+ return negativeErrno(error);
930
+ }
931
+ }
932
+ readRecordLock(file, pointer) {
933
+ assertRange(this.options.memory(), pointer, 28);
934
+ const view = this.memoryView();
935
+ const type = view.getInt16(pointer, true);
936
+ const whence = view.getInt16(pointer + 2, true);
937
+ const offset = safeOffset(view.getBigInt64(pointer + 8, true));
938
+ const length = safeOffset(view.getBigInt64(pointer + 16, true));
939
+ const base = whence === SEEK_SET ? 0 : whence === SEEK_CUR ? file.position
940
+ : whence === SEEK_END ? this.fs().statSync(file.path).size : undefined;
941
+ if (base === undefined)
942
+ throw errno("EINVAL");
943
+ let start = base + offset;
944
+ let end;
945
+ if (length < 0) {
946
+ end = start;
947
+ start += length;
948
+ }
949
+ else if (length > 0)
950
+ end = start + length;
951
+ if (!Number.isSafeInteger(start) || start < 0 || (end !== undefined && (!Number.isSafeInteger(end) || end <= start))) {
952
+ throw errno("EINVAL");
953
+ }
954
+ const mode = type === F_RDLCK ? "read" : type === F_WRLCK ? "write" : type === F_UNLCK ? undefined : false;
955
+ if (mode === false)
956
+ throw errno("EINVAL");
957
+ return { ...(mode ? { mode } : {}), range: { start, ...(end === undefined ? {} : { end }) } };
958
+ }
959
+ writeRecordLock(pointer, mode, range, pid) {
960
+ const view = this.memoryView();
961
+ view.setInt16(pointer, mode === "read" ? F_RDLCK : F_WRLCK, true);
962
+ view.setInt16(pointer + 2, SEEK_SET, true);
963
+ view.setBigInt64(pointer + 8, BigInt(range.start), true);
964
+ view.setBigInt64(pointer + 16, BigInt(range.end === undefined ? 0 : range.end - range.start), true);
965
+ view.setInt32(pointer + 24, pid, true);
966
+ }
967
+ flock(fd, operation) {
968
+ const file = this.files.get(fd);
969
+ if (!file)
970
+ return this.hasFd(fd) ? 0n : -9n;
971
+ const client = this.options.fileLocks;
972
+ const owner = this.lockOwner("flock", file);
973
+ if (!client || !owner)
974
+ return 0n;
975
+ if (operation & LOCK_UN) {
976
+ return BigInt(client.request({
977
+ operation: "unlock", owner, namespace: "flock", path: file.path, range: { start: 0 },
978
+ }).result);
979
+ }
980
+ const mode = operation & LOCK_EX ? "write" : operation & LOCK_SH ? "read" : undefined;
981
+ if (!mode || operation & ~(LOCK_SH | LOCK_EX | LOCK_NB | LOCK_UN))
982
+ return -22n;
983
+ return BigInt(client.request({
984
+ operation: "set", owner, namespace: "flock", path: file.path, range: { start: 0 },
985
+ mode, pid: this.options.pid ?? 1, blocking: !(operation & LOCK_NB),
986
+ }).result);
987
+ }
988
+ lockOwner(namespace, file) {
989
+ const base = this.options.lockOwner;
990
+ if (!base)
991
+ return;
992
+ return namespace === "posix" ? `${base}:posix` : `${base}:${namespace}:${file.description}`;
993
+ }
994
+ releasePosixLocks(path) {
995
+ const owner = this.options.lockOwner ? `${this.options.lockOwner}:posix` : undefined;
996
+ if (!owner || !this.options.fileLocks)
997
+ return;
998
+ this.options.fileLocks.request({
999
+ operation: "unlock", owner, namespace: "posix", path, range: { start: 0 },
1000
+ });
1001
+ }
1002
+ releaseLockOwner(owner) {
1003
+ if (owner && this.options.fileLocks)
1004
+ this.options.fileLocks.request({ operation: "release-owner", owner });
1005
+ }
1006
+ poll(pointer, count, timeout) {
1007
+ if (!Number.isSafeInteger(count) || count < 0)
1008
+ return -22n;
1009
+ if (!Number.isSafeInteger(timeout))
1010
+ return -22n;
1011
+ const scan = () => {
1012
+ let ready = 0;
1013
+ for (let index = 0; index < count; index += 1) {
1014
+ const offset = pointer + index * 8;
1015
+ assertRange(this.options.memory(), offset, 8);
1016
+ const fd = this.memoryView().getInt32(offset, true);
1017
+ const events = this.memoryView().getInt16(offset + 4, true);
1018
+ const returned = this.pollDescriptor(fd, events);
1019
+ this.memoryView().setInt16(offset + 6, returned, true);
1020
+ if (returned !== 0)
1021
+ ready += 1;
1022
+ }
1023
+ return ready;
1024
+ };
1025
+ const ready = scan();
1026
+ if (ready !== 0 || timeout === 0)
1027
+ return BigInt(ready);
1028
+ this.stdin.wait?.(timeout);
1029
+ return BigInt(scan());
1030
+ }
1031
+ hasFd(fd) {
1032
+ return (fd >= 0 && fd <= 2) || this.stdioAliases.has(fd)
1033
+ || this.files.has(fd) || this.specialDescriptors.has(fd);
1034
+ }
1035
+ stdioFd(fd) {
1036
+ if (fd >= 0 && fd <= 2)
1037
+ return fd;
1038
+ const local = this.stdioAliases.get(fd);
1039
+ if (local !== undefined)
1040
+ return local;
1041
+ const shared = this.options.stdioAliasRegistry?.[fd];
1042
+ return shared === 0 || shared === 1 || shared === 2 ? shared : undefined;
1043
+ }
1044
+ setSharedStdioAlias(fd, target) {
1045
+ if (fd < 0 || fd >= (this.options.stdioAliasRegistry?.length ?? 0))
1046
+ return;
1047
+ Atomics.store(this.options.stdioAliasRegistry, fd, target ?? -1);
1048
+ }
1049
+ statusFlags(fd) {
1050
+ const stored = this.descriptorStatusFlags.get(fd);
1051
+ if (stored !== undefined)
1052
+ return stored;
1053
+ const stdio = this.stdioFd(fd);
1054
+ if (stdio !== undefined)
1055
+ return stdio === 0 ? 0 : O_WRONLY;
1056
+ return this.files.get(fd)?.flags ?? O_RDWR;
1057
+ }
1058
+ mutate(operation) {
1059
+ try {
1060
+ operation();
1061
+ return 0n;
1062
+ }
1063
+ catch (error) {
1064
+ return negativeErrno(error);
1065
+ }
1066
+ }
1067
+ }
1068
+ function normalizePath(cwd, path) {
1069
+ if (!path)
1070
+ return cwd;
1071
+ const components = [];
1072
+ const value = path.startsWith("/") ? path : `${cwd}/${path}`;
1073
+ for (const component of value.replaceAll("\\", "/").split("/")) {
1074
+ if (!component || component === ".")
1075
+ continue;
1076
+ if (component === "..")
1077
+ components.pop();
1078
+ else
1079
+ components.push(component);
1080
+ }
1081
+ return `/${components.join("/")}`;
1082
+ }
1083
+ function alignTo(value, alignment) {
1084
+ return Math.ceil(value / alignment) * alignment;
1085
+ }
1086
+ function lowSigned32(value) {
1087
+ return Number(BigInt.asIntN(32, typeof value === "bigint" ? value : BigInt(value)));
1088
+ }
1089
+ function safeOffset(value) {
1090
+ const result = Number(value);
1091
+ if (!Number.isSafeInteger(result))
1092
+ throw errno("EINVAL");
1093
+ return result;
1094
+ }
1095
+ function assertRange(memory, pointer, length) {
1096
+ if (!Number.isSafeInteger(pointer) || !Number.isSafeInteger(length) || pointer < 0 || length < 0
1097
+ || pointer + length > memory.buffer.byteLength)
1098
+ throw new WebAssembly.RuntimeError("WALI filesystem memory access is out of bounds.");
1099
+ }
1100
+ function writeStat(view, pointer, stat) {
1101
+ if (pointer < 0 || pointer + 144 > view.byteLength)
1102
+ throw new WebAssembly.RuntimeError("WALI stat pointer is out of bounds.");
1103
+ for (let offset = 0; offset < 144; offset += 4)
1104
+ view.setUint32(pointer + offset, 0, true);
1105
+ view.setBigUint64(pointer, BigInt(stat.dev ?? 1), true);
1106
+ view.setBigUint64(pointer + 8, BigInt(stat.ino ?? 1), true);
1107
+ view.setBigUint64(pointer + 16, BigInt(stat.nlink ?? 1), true);
1108
+ const kind = stat.isSymbolicLink?.() ? S_IFLNK : stat.isDirectory() ? S_IFDIR : S_IFREG;
1109
+ view.setUint32(pointer + 24, kind | (stat.mode ?? (stat.isDirectory() ? 0o755 : 0o644)), true);
1110
+ view.setUint32(pointer + 28, stat.uid ?? 1000, true);
1111
+ view.setUint32(pointer + 32, stat.gid ?? 1000, true);
1112
+ view.setBigUint64(pointer + 40, BigInt(stat.rdev ?? 0), true);
1113
+ view.setBigInt64(pointer + 48, BigInt(stat.size), true);
1114
+ view.setBigInt64(pointer + 56, BigInt(stat.blksize ?? 4096), true);
1115
+ view.setBigInt64(pointer + 64, BigInt(stat.blocks ?? Math.ceil(stat.size / 512)), true);
1116
+ writeStatTime(view, pointer + 72, stat.atimeMs ?? stat.mtimeMs ?? Date.now());
1117
+ writeStatTime(view, pointer + 88, stat.mtimeMs ?? Date.now());
1118
+ writeStatTime(view, pointer + 104, stat.ctimeMs ?? stat.mtimeMs ?? Date.now());
1119
+ }
1120
+ function syntheticSymlinkStat(target) {
1121
+ return {
1122
+ isFile: () => false,
1123
+ isDirectory: () => false,
1124
+ isSymbolicLink: () => true,
1125
+ size: new TextEncoder().encode(target).byteLength,
1126
+ mode: 0o777,
1127
+ };
1128
+ }
1129
+ function writeStatfs(view, pointer) {
1130
+ if (pointer < 0 || pointer + 120 > view.byteLength)
1131
+ throw new WebAssembly.RuntimeError("WALI statfs pointer is out of bounds.");
1132
+ for (let offset = 0; offset < 120; offset += 4)
1133
+ view.setUint32(pointer + offset, 0, true);
1134
+ view.setBigInt64(pointer, 0x01021994n, true); // TMPFS_MAGIC
1135
+ view.setBigInt64(pointer + 8, 4096n, true);
1136
+ view.setBigUint64(pointer + 16, 262144n, true);
1137
+ view.setBigUint64(pointer + 24, 196608n, true);
1138
+ view.setBigUint64(pointer + 32, 196608n, true);
1139
+ view.setBigUint64(pointer + 40, 1048576n, true);
1140
+ view.setBigUint64(pointer + 48, 1048000n, true);
1141
+ view.setBigUint64(pointer + 64, 255n, true);
1142
+ view.setBigUint64(pointer + 72, 4096n, true);
1143
+ }
1144
+ function writeStatTime(view, pointer, milliseconds) {
1145
+ const nanoseconds = BigInt(Math.floor(milliseconds * 1_000_000));
1146
+ view.setBigInt64(pointer, nanoseconds / 1000000000n, true);
1147
+ view.setBigInt64(pointer + 8, nanoseconds % 1000000000n, true);
1148
+ }
1149
+ function syntheticTerminalStat() {
1150
+ return {
1151
+ isFile: () => false,
1152
+ isDirectory: () => false,
1153
+ size: 0,
1154
+ mode: 0o020666,
1155
+ mtimeMs: Date.now(),
1156
+ };
1157
+ }
1158
+ function errno(errnoName) {
1159
+ return Object.assign(new Error(errnoName), { errnoName });
1160
+ }
1161
+ function negativeErrno(error) {
1162
+ const message = error instanceof Error ? error.message : String(error);
1163
+ const name = typeof error === "object" && error !== null
1164
+ ? ("errnoName" in error ? String(error.errnoName) : "code" in error ? String(error.code)
1165
+ : /\b(E[A-Z]+)\b/u.exec(message)?.[1] ?? "")
1166
+ : /\b(E[A-Z]+)\b/u.exec(message)?.[1] ?? "";
1167
+ const code = {
1168
+ EPERM: 1n, ENOENT: 2n, EIO: 5n, EAGAIN: 11n, ENOMEM: 12n, EACCES: 13n,
1169
+ EFAULT: 14n, EBUSY: 16n, EEXIST: 17n, EXDEV: 18n, ENOTDIR: 20n, EISDIR: 21n,
1170
+ EINVAL: 22n, EMFILE: 24n, ENOSPC: 28n, ESPIPE: 29n, EROFS: 30n, ERANGE: 34n,
1171
+ ENOSYS: 38n, ENOTEMPTY: 39n, ELOOP: 40n, ENAMETOOLONG: 36n,
1172
+ };
1173
+ if (name in code)
1174
+ return -code[name];
1175
+ if (error instanceof WebAssembly.RuntimeError || error instanceof RangeError)
1176
+ throw error;
1177
+ return -5n;
1178
+ }
1179
+ //# sourceMappingURL=filesystem.js.map