Path: blob/main/core/kernel/src/wasm/posix/index.ts
1068 views
/*12NOTES:3- emscripten/src/library_syscall.js is useful inspiration in some cases!4*/56import forkExec from "./fork-exec";7import epoll from "./epoll";8import netdb from "./netdb";9import netif from "./netif";10import other from "./other";11import sched from "./sched";12import signal from "./signal";13import socket from "./socket";14import spawn from "./spawn";15import stdlib from "./stdlib";16import stdio from "./stdio";17import stat from "./stat";18import termios from "./termios";19import time from "./time";20import unistd from "./unistd";21import wait from "./wait";22import WASI from "wasi-js";23import { initConstants } from "./constants";24import SendToWasm from "../worker/send-to-wasm";25import RecvFromWasm from "../worker/recv-from-wasm";26import constants from "./constants";27import debug from "debug";2829const logNotImplemented = debug("posix:not-implemented");30const logCall = debug("posix:call");31const logReturn = debug("posix:return");32const logError = debug("posix:error");3334// For some reason this code35// import os; print(os.popen('ls').read())36// hangs when run in **linux only** under python-wasm, but not python-wasm-debug,37// except if I set any random env variable here... and then it doesn't hang.38// This is weird.39process.env.__STUPID_HACK__ = "";4041export interface Context {42// IMPORTANT: All of the functionality that implements this and has persistent state,43// *must* store its state in this state object in such a way that the state object can44// be swapped out between calls. There's lot of code that does this by always referring45// to context.state and not capturing state itself directly. Messing this up46// can lead to segfaults when running webassembly subprocesses. You *can* initialize47// this state once at the top of the implementation though, since it is deep copied48// before exec, rather than reset (in posix-context.ts).49state: { [name: string]: any };50fs: FileSystem;51send: SendToWasm;52recv: RecvFromWasm;53wasi: WASI;54run: (args: string[]) => number;55process: {56getpid?: () => number;57getuid?: () => number;58pid?: number;59cwd?: () => string;60};61os: {62loadavg?: () => [number, number, number];63getPriority?: (pid?: number) => number;64setPriority?: (pid: number, priority?: number) => void;65platform?: () => // we care about darwin/linux/win32 for our runtime.66"darwin" | "linux" | "win32" | "aix" | "freebsd" | "openbsd" | "sunos";67};68child_process: {69spawnSync?: (command: string) => number;70};71// The WASM memory (so we can make sense of pointers efficiently).72memory: WebAssembly.Memory;73// Optional module that gets installed on Mac/Linux, but obviously not windows74// for which posix doesn't make sense.75posix: {76getpgid?: () => number;77constants?: { [code: string]: number };78chdir?: (string) => void;79// TODO...80};81free: (ptr: number) => void;82callFunction: (name: string, ...args) => number | undefined;83callWithString: (84func: string | { name: string; dll: string } | Function,85str?: string | string[],86...args87) => number | undefined;88getcwd: () => string;89sleep?: (milliseconds: number) => void;90noStdio: boolean;91}9293// It might in theory be better if we used typescript to say exactly which functions94// are defined. That said, it's not like the WASM side cares about typescript.95export type PosixEnv = { [name: string]: Function };9697export default function posix(context: Context): PosixEnv {98const P = {99...epoll(context),100...forkExec(context),101...netdb(context),102...netif(context),103...other(context),104...sched(context),105...signal(context),106...socket(context),107...spawn(context),108...stat(context),109...stdlib(context),110...stdio(context),111...time(context),112...termios(context),113...unistd(context),114...wait(context),115};116const Q: any = {};117118let nativeErrnoToSymbol: { [code: number]: string } = {};119if (context.posix.constants != null) {120for (const symbol in context.posix.constants) {121nativeErrnoToSymbol[context.posix.constants[symbol]] = symbol;122}123}124function setErrnoFromNative(nativeErrno: number, name: string, args): void {125if (nativeErrno == 0 || isNaN(nativeErrno)) {126// TODO: could put a log or something in that name raised error with no code.127context.callFunction("setErrno", nativeErrno);128return;129}130// The error code comes from native posix, so we translate it to WASI first131const symbol = nativeErrnoToSymbol[nativeErrno];132if (symbol != null) {133const wasiErrno = constants[symbol];134if (wasiErrno != null) {135if (logError.enabled) {136logError({ name, nativeErrno, wasiErrno, symbol, args });137}138context.callFunction("setErrno", wasiErrno);139return;140}141}142143const mesg =144symbol != null145? `WARNING in posix '${name}': Unable to map nativeErrno ${nativeErrno}: add ${symbol} to WASM posix constants in @cowasm/kernel`146: `WARNING in posix '${name}': Unable to map nativeErrno ${nativeErrno}: add native symbol corresponding to errno=${nativeErrno} to the posix-node package`;147console.warn(mesg);148logNotImplemented(mesg);149}150151// It's critical to ensure the directories of the host env is the same as152// the WASM env, if meaningful or possible. This only matters right now153// under node.js, but is really critical there. Thus we wrap *all* posix calls154// in this syncdir below.155// TODO: optimize. This seems dangerously expensive.156let syncdir;157if (context.posix.chdir != null) {158syncdir = () => {159// TODO: it is expected that this may fail, e.g., if we are using a sandbox filesystem160// deal with this in a better way.161try {162context.posix.chdir?.(context.getcwd());163} catch (_err) {}164};165} else {166syncdir = () => {};167}168169for (const name in P) {170Q[name] = (...args) => {171syncdir();172try {173logCall(name, args);174const ret = P[name](...args);175logReturn(name, ret);176return ret;177} catch (err) {178logError(name, err);179if (err.wasiErrno != null) {180context.callFunction("setErrno", err.wasiErrno);181} else if (err.code != null) {182setErrnoFromNative(parseInt(err.code), name, args);183} else {184// err.code not yet set (TODO), so we log and try heuristic.185// On error, for now -1 is returned, and errno should get set to some sort of error indicator186// TODO: how should we set errno?187// @ts-ignore -- this is just temporary while we sort out setting errno...188if (err.name == "NotImplementedError") {189// ENOSYS means "Function not implemented (POSIX.1-2001)."190context.callFunction("setErrno", constants.ENOSYS);191} else {192console.trace(193`WARNING: Posix library call to ${name} raised exception without error code. The raised error is '${err}'`194);195logNotImplemented(196`Posix call to ${name} raised exception without error code`,197err198);199}200}201return err.ret ?? -1;202}203};204}205Q.init = () => {206initConstants(context);207};208return Q;209}210211212