mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-06 08:22:24 +01:00
484 lines
16 KiB
JavaScript
484 lines
16 KiB
JavaScript
const DEBUG = false;
|
||
const log = (msg) => DEBUG && console.log(msg);
|
||
|
||
const wasmCache = new Map();
|
||
let wasiInstance;
|
||
|
||
export function setWasiInstance(instance) {
|
||
wasiInstance = instance;
|
||
}
|
||
|
||
export default async function(baseURL, path, opts = {}) {
|
||
const url = new URL(path, baseURL);
|
||
|
||
let wasm;
|
||
if (wasmCache.has(url.pathname)) wasm = wasmCache.get(url.pathname);
|
||
else {
|
||
wasm = await WebAssembly.instantiateStreaming(
|
||
fetch(url), {
|
||
wasi_snapshot_preview1: {
|
||
...wasi,
|
||
},
|
||
env: {
|
||
...wasi,
|
||
...syscalls,
|
||
...javascripts,
|
||
},
|
||
},
|
||
);
|
||
wasmCache.set(url.pathname, wasm);
|
||
}
|
||
setWasiInstance(wasm.instance);
|
||
return wasm;
|
||
}
|
||
|
||
const FS = {};
|
||
let nextFd = 0;
|
||
writeFS(new Uint8Array(0), "/dev/stdin");
|
||
writeFS(new Uint8Array(1024*8), "/dev/stdout");
|
||
writeFS(new Uint8Array(1024*8), "/dev/stderr");
|
||
if (nextFd !== 3) throw new Error("Unexpected next fd");
|
||
|
||
export function writeFS(buffer, path = "") {
|
||
if (!(buffer instanceof Uint8Array)) throw new Error("can only write Uint8Array");
|
||
FS[nextFd] = {
|
||
buffer,
|
||
position: 0,
|
||
path,
|
||
};
|
||
nextFd += 1;
|
||
return nextFd - 1;
|
||
}
|
||
|
||
export function readFS(fd) {
|
||
const file = FS[fd];
|
||
if (!file) throw new Error("file does not exist");
|
||
|
||
let end = file.buffer.length;
|
||
while (end > 0 && file.buffer[end - 1] === 0) end--;
|
||
return file.buffer.subarray(0, end);
|
||
}
|
||
|
||
export function clearFS() {
|
||
Object.keys(FS).forEach((key) => {
|
||
if (key > 2) delete FS[key];
|
||
});
|
||
nextFd = 3;
|
||
}
|
||
|
||
function getFile(path) {
|
||
const allFds = Object.keys(FS);
|
||
for (let i=allFds.length - 1; i>0; i--) {
|
||
if (FS[allFds[i]].path === path) {
|
||
log(` fileopen fd=${i} path=${path}`);
|
||
return FS[allFds[i]];
|
||
}
|
||
}
|
||
throw new Error(`cannot get file "${path}"`);
|
||
}
|
||
|
||
export const syscalls = {
|
||
__syscall_fcntl64: (fd, cmd, varargs) => {
|
||
console.log(`Stubbed __syscall_fcntl64 called with fd=${fd}, cmd=${cmd}, varargs=${varargs}`);
|
||
return -1;
|
||
},
|
||
__syscall_ioctl: (fd, op, varargs) => {
|
||
switch (op) {
|
||
case 21523:
|
||
break;
|
||
default:
|
||
console.log(`Stubbed __syscall_ioctl called with fd=${fd}, request=${op}, varargs=${varargs}`);
|
||
}
|
||
return 0;
|
||
},
|
||
__syscall_unlinkat: (fd) => {
|
||
console.log(`Stubbed __syscall_unlinkat called with fd=${fd}`);
|
||
return -1;
|
||
},
|
||
__syscall_rmdir: (fd) => {
|
||
console.log(`Stubbed __syscall_rmdir called with fd=${fd}`);
|
||
return -1;
|
||
},
|
||
__syscall_newfstatat: (pathPtr, bufPtr) => {
|
||
console.log(`Stubbed __syscall_stat64 called with pathPtr=${pathPtr}, bufPtr=${bufPtr}`);
|
||
return 0; // Return 0 for a successful call
|
||
},
|
||
__syscall_lstat64: () => {
|
||
console.log(`Stubbed __syscall_lstat64 called`);
|
||
return -1;
|
||
},
|
||
__assert_fail: () => {
|
||
console.log(`Stubbed __assert_fail called`);
|
||
return -1;
|
||
},
|
||
__syscall_ftruncate64: () => {
|
||
console.log(`Stubbed __syscall_ftruncate64`);
|
||
return -1;
|
||
},
|
||
__syscall_renameat: () => {
|
||
console.log(`Stubbed __syscall_renameat`);
|
||
return -1;
|
||
},
|
||
};
|
||
|
||
const javascripts = {
|
||
_tzset_js: () => {
|
||
console.log("Initializing time zone settings (stub)");
|
||
},
|
||
_abort_js: () => {
|
||
console.error("WebAssembly module called _abort_js!");
|
||
throw new Error("_abort_js was called");
|
||
},
|
||
_mktime_js: () => {
|
||
console.error("WebAssembly module called _abort_js!");
|
||
throw new Error("_abort_js was called");
|
||
},
|
||
_localtime_js: () => {
|
||
console.error("WebAssembly module called _localtime_js!");
|
||
throw new Error("_localtime_js was called");
|
||
},
|
||
emscripten_date_now: () => {
|
||
console.error("WebAssembly module called emscripten_date_now!");
|
||
throw new Error("_localtime_js was called");
|
||
},
|
||
emscripten_get_now: () => {
|
||
console.error("WebAssembly module called emscripten_get_now!");
|
||
throw new Error("_localtime_js was called");
|
||
},
|
||
emscripten_errn: () => {
|
||
console.error("WebAssembly module called emscripten_errn!");
|
||
throw new Error("_errn was called");
|
||
},
|
||
HaveOffsetConverter: () => {
|
||
console.error("WebAssembly module called HaveOffsetConverter!");
|
||
throw new Error("HaveOffsetConverter was called");
|
||
},
|
||
emscripten_pc_get_function: () => {
|
||
console.error("WebAssembly module called emscripten_pc_get_function!");
|
||
throw new Error("emscripten_pc_get_function was called");
|
||
},
|
||
emscripten_asm_const_int: () => {
|
||
console.error("WebAssembly module called emscripten_asm_const_int!");
|
||
throw new Error("emscripten_asm_const_int was called");
|
||
},
|
||
emscripten_stack_snapshot: () => {
|
||
console.error("WebAssembly module called emscripten_stack_snapshot!");
|
||
throw new Error("emscripten_stack_snapshot was called");
|
||
},
|
||
emscripten_stack_unwind_buffer: () => {
|
||
console.error("WebAssembly module called emscripten_stack_unwind_buffer!");
|
||
throw new Error("emscripten_stack_unwind_buffer was called");
|
||
},
|
||
emscripten_get_heap_max: () => {
|
||
console.error("WebAssembly module called emscripten_get_heap_max!");
|
||
throw new Error("emscripten_get_heap_max was called");
|
||
},
|
||
_timegm_js: () => {
|
||
return 0;
|
||
},
|
||
_gmtime_js: () => {
|
||
return null;
|
||
},
|
||
_munmap_js: () => {
|
||
console.error("WebAssembly module called _munmap_js!");
|
||
throw new Error("_munmap_js was called");
|
||
},
|
||
_mmap_js: () => {
|
||
console.error("WebAssembly module called _mmap_js!");
|
||
throw new Error("_mmap_js was called");
|
||
}
|
||
};
|
||
|
||
export const wasi = {
|
||
|
||
fd_write(fd, iovs, iovs_len, nwritten) {
|
||
if (!FS[fd]) throw new Error(`File descriptor ${fd} does not exist.`);
|
||
|
||
const ioVecArray = new Uint32Array(wasiInstance.exports.memory.buffer, iovs, iovs_len * 2);
|
||
const memory = new Uint8Array(wasiInstance.exports.memory.buffer);
|
||
let totalBytesWritten = 0;
|
||
|
||
for (let i = 0; i < iovs_len * 2; i += 2) {
|
||
const offset = ioVecArray[i];
|
||
const length = ioVecArray[i + 1];
|
||
while (FS[fd].buffer.byteLength - FS[fd].position < length) {
|
||
const newBuffer = new Uint8Array(FS[fd].buffer.byteLength + 1024 * 1024 * 5);
|
||
newBuffer.set(FS[fd].buffer, 0);
|
||
FS[fd].buffer = newBuffer;
|
||
}
|
||
FS[fd].buffer.set(
|
||
memory.subarray(offset, offset + length),
|
||
FS[fd].position
|
||
);
|
||
FS[fd].position += length;
|
||
totalBytesWritten += length;
|
||
}
|
||
new DataView(wasiInstance.exports.memory.buffer).setUint32(
|
||
nwritten,
|
||
totalBytesWritten,
|
||
true,
|
||
);
|
||
if (fd === 1 || fd === 2) {
|
||
FS[fd] = {
|
||
buffer: new Uint8Array(readFS(fd)),
|
||
position: 0,
|
||
path: FS[fd].path || "",
|
||
};
|
||
} else {
|
||
log(`wasi::fd_write fd=${fd}`);
|
||
}
|
||
return 0;
|
||
},
|
||
|
||
fd_read(fd, iovs, iovs_len, nread) {
|
||
const file = FS[fd];
|
||
if (!file) {
|
||
console.error(`Invalid fd: ${fd}`);
|
||
return -1;
|
||
}
|
||
|
||
const ioVecArray = new Uint32Array(wasiInstance.exports.memory.buffer, iovs, iovs_len * 2);
|
||
const memory = new Uint8Array(wasiInstance.exports.memory.buffer);
|
||
let totalBytesRead = 0;
|
||
for (let i = 0; i < iovs_len * 2; i += 2) {
|
||
const offset = ioVecArray[i];
|
||
const length = ioVecArray[i + 1] || 0;
|
||
const bytesToRead = Math.min(
|
||
length,
|
||
file.buffer.length - file.position,
|
||
);
|
||
if (bytesToRead < 0) {
|
||
break;
|
||
}
|
||
memory.set(
|
||
file.buffer.subarray(file.position, file.position + bytesToRead),
|
||
offset,
|
||
);
|
||
file.position += bytesToRead;
|
||
totalBytesRead += bytesToRead;
|
||
}
|
||
log(`wasi::fd_read fd=${fd} iovs_len=${iovs_len} totalBytesRead=${totalBytesRead}`);
|
||
new DataView(wasiInstance.exports.memory.buffer).setUint32(
|
||
nread,
|
||
totalBytesRead,
|
||
true,
|
||
);
|
||
return 0;
|
||
},
|
||
|
||
fd_pread(fd, iovs, iovs_len, offset_lo, offset_hi, nread) {
|
||
const file = FS[fd];
|
||
if (!file) {
|
||
console.error(`Invalid fd: ${fd}`);
|
||
return -1;
|
||
}
|
||
|
||
const start = (offset_hi >>> 0) * 0x100000000 + (offset_lo >>> 0);
|
||
const ioVec = new Uint32Array(wasiInstance.exports.memory.buffer, iovs, iovs_len * 2);
|
||
const mem = new Uint8Array(wasiInstance.exports.memory.buffer);
|
||
|
||
let total = 0;
|
||
for (let i = 0; i < iovs_len * 2; i += 2) {
|
||
const dst = ioVec[i];
|
||
const len = ioVec[i + 1] || 0;
|
||
const avail = Math.max(
|
||
0,
|
||
Math.min(len, file.buffer.length - (start + total)),
|
||
);
|
||
if (avail === 0) {
|
||
console.log(`len=[${len}] buffLength=[${file.buffer.length}] start=[${start}] total=[${total}]`);
|
||
break;
|
||
}
|
||
mem.set(
|
||
file.buffer.subarray(start + total, start + total + avail),
|
||
dst
|
||
);
|
||
total += avail;
|
||
}
|
||
|
||
new DataView(wasiInstance.exports.memory.buffer).setUint32(nread, total, true);
|
||
return 0;
|
||
},
|
||
|
||
fd_seek(fd, offsetBigInt, _, whence) {
|
||
log(`wasi::fd_seek fd=${fd} offset=${offsetBigInt} whence=${whence}`);
|
||
const offset = Number(offsetBigInt);
|
||
const file = FS[fd];
|
||
if (!file) {
|
||
console.error(`Invalid FD: ${fd}`);
|
||
return -1;
|
||
}
|
||
switch (whence) {
|
||
case 0: // SEEK_SET
|
||
file.position = offset;
|
||
break;
|
||
case 1: // SEEK_CUR
|
||
file.position += offset;
|
||
break;
|
||
case 2: // SEEK_END
|
||
file.position = file.buffer.length + offset;
|
||
break;
|
||
default:
|
||
console.log(`fd_seek called with fd=${fd}, offset=${offset}, position=${file.position} whence=${whence}`);
|
||
const error = new Error("fd_seek trace");
|
||
console.log("Invalid whence", whence, error.stack);
|
||
return -1;
|
||
}
|
||
return 0;
|
||
},
|
||
|
||
fd_close(fd) {
|
||
if (!FS[fd]) {
|
||
console.error(`Invalid FD: ${fd}`);
|
||
return -1;
|
||
}
|
||
return 0;
|
||
},
|
||
|
||
_emscripten_memcpy_js(dest, src, num) {
|
||
const memory = new Uint8Array(wasiInstance.exports.memory.buffer);
|
||
memory.set(memory.subarray(src, src + num), dest);
|
||
return dest;
|
||
},
|
||
|
||
emscripten_resize_heap(requested) {
|
||
console.log("Stubbed emscripten_resize_heap called");
|
||
throw new Error("Heap resize not supported");
|
||
},
|
||
|
||
environ_sizes_get() {
|
||
console.log(`Stubbed environ_sizes_get called`);
|
||
return 0;
|
||
},
|
||
|
||
environ_get() {
|
||
console.log(`Stubbed environ_get called`);
|
||
return 0;
|
||
},
|
||
|
||
clock_time_get() {
|
||
console.log(`Stubbed clock_time_get called`);
|
||
return -1;
|
||
},
|
||
|
||
__syscall_openat(dirFd, pathPtr, flags, mode) {
|
||
const memory = new Uint8Array(wasiInstance.exports.memory.buffer);
|
||
let path = "";
|
||
for (let i = pathPtr; memory[i] !== 0; i++) {
|
||
path += String.fromCharCode(memory[i]);
|
||
}
|
||
const allFds = Object.keys(FS);
|
||
for (let i=allFds.length - 1; i>0; i--) {
|
||
if (FS[allFds[i]].path === path) {
|
||
log(` syscall::openat::result fd=${i} path=${path}`);
|
||
return i;
|
||
}
|
||
}
|
||
throw new Error("Unknown file for __syscall_openat");
|
||
},
|
||
|
||
__syscall_stat64(pathPtr, buf) {
|
||
log(` syscall::stat64 pathPtr=${pathPtr}, bufPtr=${buf}`);
|
||
const memory = new Uint8Array(wasiInstance.exports.memory.buffer);
|
||
let path = "";
|
||
for (let i = pathPtr; memory[i] !== 0; i++) {
|
||
path += String.fromCharCode(memory[i]);
|
||
}
|
||
const file = getFile(path);
|
||
const HEAP32 = new Int32Array(wasiInstance.exports.memory.buffer);
|
||
const HEAPU32 = new Uint32Array(wasiInstance.exports.memory.buffer);
|
||
const stat = {
|
||
dev: 1,
|
||
ino: 42,
|
||
mode: 0o100644,
|
||
nlink: 1,
|
||
uid: 1000,
|
||
gid: 1000,
|
||
rdev: 0,
|
||
size: file.buffer.byteLength,
|
||
blksize: 4096,
|
||
blocks: 256,
|
||
atime: new Date(),
|
||
mtime: new Date(),
|
||
ctime: new Date(),
|
||
};
|
||
HEAP32[(buf >> 2)] = stat.dev;
|
||
HEAP32[((buf + 4) >> 2)] = stat.mode;
|
||
HEAPU32[((buf + 8) >> 2)] = stat.nlink;
|
||
HEAP32[((buf + 12) >> 2)] = stat.uid;
|
||
HEAP32[((buf + 16) >> 2)] = stat.gid;
|
||
HEAP32[((buf + 20) >> 2)] = stat.rdev;
|
||
HEAP32[((buf + 24) >> 2)] = stat.size & 0xFFFFFFFF;
|
||
HEAP32[((buf + 28) >> 2)] = Math.floor(stat.size / 4294967296);
|
||
HEAP32[((buf + 32) >> 2)] = stat.blksize;
|
||
HEAP32[((buf + 36) >> 2)] = stat.blocks;
|
||
HEAP32[((buf + 40) >> 2)] = Math.floor(stat.atime.getTime() / 1000);
|
||
HEAP32[((buf + 44) >> 2)] = 0;
|
||
HEAP32[((buf + 48) >> 2)] = (stat.atime.getTime() % 1000) * 1e6;
|
||
HEAP32[((buf + 56) >> 2)] = Math.floor(stat.mtime.getTime() / 1000);
|
||
HEAP32[((buf + 60) >> 2)] = 0;
|
||
HEAP32[((buf + 64) >> 2)] = (stat.mtime.getTime() % 1000) * 1e6;
|
||
HEAP32[((buf + 72) >> 2)] = Math.floor(stat.ctime.getTime() / 1000);
|
||
HEAP32[((buf + 76) >> 2)] = 0;
|
||
HEAP32[((buf + 80) >> 2)] = (stat.ctime.getTime() % 1000) * 1e6;
|
||
HEAP32[((buf + 88) >> 2)] = stat.ino & 0xFFFFFFFF;
|
||
HEAP32[((buf + 92) >> 2)] = Math.floor(stat.ino / 4294967296);
|
||
return 0;
|
||
},
|
||
|
||
__cxa_throw(ptr, type, destructor) {
|
||
console.error(` syscall::cxa_throw ptr=${ptr}, type=${type}, destructor=${destructor}`);
|
||
throw new Error("WebAssembly exception");
|
||
},
|
||
|
||
random_get() {
|
||
console.log(`Stubbed random_get called`);
|
||
return -1;
|
||
},
|
||
|
||
proc_exit() {
|
||
console.log(`Stubbed proc_exit called`);
|
||
return -1;
|
||
},
|
||
|
||
__syscall_fstat64(fd, buf) {
|
||
log(` syscall::fstat64 fd=${fd}, buf=${buf}`);
|
||
const file = FS[fd];
|
||
if (!file) return -1; // EBADF
|
||
|
||
const size = file.buffer.byteLength >>> 0; // ≤ 4 GB
|
||
const nowSec = (Date.now() / 1000) | 0;
|
||
const H32 = new Int32Array(wasiInstance.exports.memory.buffer);
|
||
|
||
/* basic fields */
|
||
H32[buf >> 2] = 1; /* st_dev */
|
||
H32[(buf+4) >> 2] = 0o100644; /* st_mode */
|
||
H32[(buf+8) >> 2] = 1; /* st_nlink */
|
||
H32[(buf+12) >> 2] = 1000; /* st_uid */
|
||
H32[(buf+16) >> 2] = 1000; /* st_gid */
|
||
H32[(buf+20) >> 2] = 0; /* st_rdev */
|
||
|
||
H32[((buf + 24) >> 2)] = size & 0xFFFFFFFF;
|
||
H32[((buf + 28) >> 2)] = Math.floor(size / 4294967296);
|
||
|
||
H32[(buf+32) >> 2] = 4096;
|
||
H32[(buf+36) >> 2] = (size + 511) >> 9;
|
||
|
||
/* st_size lives at byte 40 in Emscripten’s 32-bit stat64 */
|
||
H32[(buf+40) >> 2] = size; /* low 32 bits (high word = 0) */
|
||
H32[(buf+44) >> 2] = 0;
|
||
|
||
H32[(buf+32) >> 2] = 4096; /* st_blksize */
|
||
H32[(buf+36) >> 2] = (size + 511) >> 9; /* st_blocks */
|
||
|
||
/* atime / mtime / ctime: seconds, nsec = 0 */
|
||
for (const off of [48, 56, 64]) {
|
||
H32[((buf+off) >> 2)] = nowSec;
|
||
H32[((buf+off+4) >> 2)] = 0;
|
||
}
|
||
|
||
H32[(buf+72) >> 2] = fd; /* st_ino (low) */
|
||
H32[(buf+76) >> 2] = 0; /* st_ino (high) */
|
||
return 0;
|
||
}
|
||
};
|