taglib-wasm
Version:
TagLib-Wasm is the universal tagging library for TypeScript/JavaScript platforms: Browsers, Node.js, Deno, Bun, Cloudflare Workers, and Electron apps
203 lines (202 loc) • 6.84 kB
JavaScript
import { joinPath as join } from "../utils/path.js";
const WASI_ESUCCESS = 0;
const WASI_EBADF = 8;
const WASI_EINVAL = 28;
const WASI_ENOENT = 44;
const WASI_ENOTCAPABLE = 76;
const OFLAGS_CREAT = 1;
const OFLAGS_TRUNC = 8;
const RIGHTS_FD_WRITE = 1n << 6n;
function createWasiImports(memory, config) {
const fds = /* @__PURE__ */ new Map();
let nextFd = 3;
for (const [virtualPath, realPath] of Object.entries(config.preopens)) {
fds.set(nextFd, { type: "preopen", realPath, virtualPath });
nextFd++;
}
function getMemory() {
return {
u8: new Uint8Array(memory.buffer),
dv: new DataView(memory.buffer)
};
}
function resolvePath(dirFd, pathPtr, pathLen) {
const dir = fds.get(dirFd);
if (dir?.type !== "preopen") return null;
const { u8 } = getMemory();
const relPath = new TextDecoder().decode(
u8.slice(pathPtr, pathPtr + pathLen)
);
const normalized = relPath.replaceAll("\\", "/");
const segments = normalized.split("/");
if (segments.includes("..") || normalized.startsWith("/")) return null;
return join(dir.realPath, normalized);
}
return {
args_get: (_argv, _buf) => WASI_ESUCCESS,
args_sizes_get: (argcPtr, bufSzPtr) => {
const { dv } = getMemory();
dv.setUint32(argcPtr, 0, true);
dv.setUint32(bufSzPtr, 0, true);
return WASI_ESUCCESS;
},
fd_close: (fd) => {
const entry = fds.get(fd);
if (!entry) return WASI_EBADF;
if (entry.type === "file") {
try {
entry.file.close();
} catch {
}
}
fds.delete(fd);
return WASI_ESUCCESS;
},
fd_fdstat_get: (fd, buf) => {
const entry = fds.get(fd);
if (!entry) return WASI_EBADF;
const { dv } = getMemory();
const fileType = entry.type === "preopen" ? 3 : 4;
dv.setUint8(buf, fileType);
dv.setUint16(buf + 2, 0, true);
dv.setBigUint64(buf + 8, 0xFFFFFFFFFFFFFFFFn, true);
dv.setBigUint64(buf + 16, 0xFFFFFFFFFFFFFFFFn, true);
return WASI_ESUCCESS;
},
fd_fdstat_set_flags: (_fd, _flags) => WASI_ESUCCESS,
fd_filestat_set_size: (fd, size) => {
const entry = fds.get(fd);
if (entry?.type !== "file") return WASI_EBADF;
try {
entry.file.truncateSync(Number(size));
return WASI_ESUCCESS;
} catch {
return WASI_EINVAL;
}
},
fd_prestat_get: (fd, buf) => {
const entry = fds.get(fd);
if (entry?.type !== "preopen") return WASI_EBADF;
const { dv } = getMemory();
const pathBytes = new TextEncoder().encode(entry.virtualPath);
dv.setUint32(buf, 0, true);
dv.setUint32(buf + 4, pathBytes.length, true);
return WASI_ESUCCESS;
},
fd_prestat_dir_name: (fd, pathPtr, pathLen) => {
const entry = fds.get(fd);
if (entry?.type !== "preopen") return WASI_EBADF;
const { u8 } = getMemory();
const pathBytes = new TextEncoder().encode(entry.virtualPath);
u8.set(pathBytes.subarray(0, pathLen), pathPtr);
return WASI_ESUCCESS;
},
fd_read: (fd, iovsPtr, iovsLen, nreadPtr) => {
const entry = fds.get(fd);
if (entry?.type !== "file") return WASI_EBADF;
const { u8, dv } = getMemory();
let totalRead = 0;
for (let i = 0; i < iovsLen; i++) {
const bufPtr = dv.getUint32(iovsPtr + i * 8, true);
const bufLen = dv.getUint32(iovsPtr + i * 8 + 4, true);
const target = u8.subarray(bufPtr, bufPtr + bufLen);
const n = entry.file.readSync(target);
if (n === null) break;
totalRead += n;
if (n < bufLen) break;
}
dv.setUint32(nreadPtr, totalRead, true);
return WASI_ESUCCESS;
},
fd_seek: (fd, offset, whence, newoffsetPtr) => {
const entry = fds.get(fd);
if (entry?.type !== "file") return WASI_EBADF;
try {
const newPos = entry.file.seekSync(Number(offset), whence);
const { dv } = getMemory();
dv.setBigInt64(newoffsetPtr, BigInt(newPos), true);
return WASI_ESUCCESS;
} catch {
return WASI_EINVAL;
}
},
fd_write: (fd, iovsPtr, iovsLen, nwrittenPtr) => {
const { u8, dv } = getMemory();
let totalWritten = 0;
for (let i = 0; i < iovsLen; i++) {
const bufPtr = dv.getUint32(iovsPtr + i * 8, true);
const bufLen = dv.getUint32(iovsPtr + i * 8 + 4, true);
const data = u8.subarray(bufPtr, bufPtr + bufLen);
if (fd === 1) {
config.stdout?.(data);
totalWritten += bufLen;
} else if (fd === 2) {
config.stderr?.(data);
totalWritten += bufLen;
} else {
const entry = fds.get(fd);
if (entry?.type !== "file") return WASI_EBADF;
totalWritten += entry.file.writeSync(data);
}
}
dv.setUint32(nwrittenPtr, totalWritten, true);
return WASI_ESUCCESS;
},
path_open: (dirFd, _dirflags, pathPtr, pathLen, oflags, _rightsBase, _rightsInheriting, _fdflags, openedFdPtr) => {
const realPath = resolvePath(dirFd, pathPtr, pathLen);
if (!realPath) return WASI_ENOTCAPABLE;
try {
const wantWrite = (_rightsBase & RIGHTS_FD_WRITE) !== 0n || (oflags & (OFLAGS_CREAT | OFLAGS_TRUNC)) !== 0;
const options = {
read: true,
write: wantWrite,
create: (oflags & OFLAGS_CREAT) !== 0,
truncate: (oflags & OFLAGS_TRUNC) !== 0
};
const file = config.fs.openSync(realPath, options);
const fd = nextFd++;
fds.set(fd, { type: "file", file, path: realPath });
const { dv } = getMemory();
dv.setUint32(openedFdPtr, fd, true);
return WASI_ESUCCESS;
} catch (e) {
if (config.fs.isNotFoundError(e)) return WASI_ENOENT;
return WASI_EINVAL;
}
},
environ_get: (_environ, _buf) => WASI_ESUCCESS,
environ_sizes_get: (countPtr, bufSzPtr) => {
const { dv } = getMemory();
dv.setUint32(countPtr, 0, true);
dv.setUint32(bufSzPtr, 0, true);
return WASI_ESUCCESS;
},
clock_time_get: (_id, _precision, timePtr) => {
const { dv } = getMemory();
dv.setBigUint64(timePtr, BigInt(Date.now()) * 1000000n, true);
return WASI_ESUCCESS;
},
random_get: (bufPtr, bufLen) => {
const buf = new Uint8Array(memory.buffer, bufPtr, bufLen);
crypto.getRandomValues(buf);
return WASI_ESUCCESS;
},
proc_exit: (code) => {
throw new Error(`WASI proc_exit called: code ${code}`);
},
[Symbol.dispose]: () => {
for (const [fd, entry] of fds) {
if (entry.type === "file") {
try {
entry.file.close();
} catch {
}
}
fds.delete(fd);
}
}
};
}
export {
createWasiImports
};