├── .npmignore ├── test ├── sandbox_outer │ ├── sandbox │ │ ├── notadir │ │ ├── subdir1 │ │ │ ├── loop1 │ │ │ └── input.txt │ │ ├── subdir2 │ │ │ ├── loop2 │ │ │ └── input_link.txt │ │ ├── input.txt │ │ └── subdir │ │ │ └── outside.txt │ └── outside.txt ├── c │ ├── preopen_populates.c │ ├── exitcode.c │ ├── fd_prestat_get_refresh.c │ ├── symlink_loop.c │ ├── notdir.c │ ├── symlink_escape.c │ ├── cant_dotdot.c │ ├── stdin.c │ ├── read_file.c │ ├── follow_symlink.c │ ├── write_file.c │ ├── clock_getres.c │ ├── getentropy.c │ ├── read_file_twice.c │ ├── gettimeofday.c │ ├── getrusage.c │ ├── poll.c │ └── stat.c ├── runner.js └── run.js ├── example ├── cowsay.wasm └── cowsay.rs ├── .gitignore ├── binding.gyp ├── .github └── FUNDING.yml ├── README.md ├── package.json ├── run.js ├── LICENSE ├── src ├── binding.cc └── index.js └── CODE_OF_CONDUCT.md /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /test/sandbox_outer/sandbox/notadir: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/sandbox_outer/outside.txt: -------------------------------------------------------------------------------- 1 | hello from the outside! 2 | -------------------------------------------------------------------------------- /test/sandbox_outer/sandbox/subdir1/loop1: -------------------------------------------------------------------------------- 1 | ../subdir2/loop2 -------------------------------------------------------------------------------- /test/sandbox_outer/sandbox/subdir2/loop2: -------------------------------------------------------------------------------- 1 | ../subdir1/loop1 -------------------------------------------------------------------------------- /test/sandbox_outer/sandbox/input.txt: -------------------------------------------------------------------------------- 1 | hello from input.txt 2 | -------------------------------------------------------------------------------- /test/sandbox_outer/sandbox/subdir/outside.txt: -------------------------------------------------------------------------------- 1 | ../../outside.txt -------------------------------------------------------------------------------- /test/c/preopen_populates.c: -------------------------------------------------------------------------------- 1 | int main(void) { 2 | return 0; 3 | } 4 | -------------------------------------------------------------------------------- /test/sandbox_outer/sandbox/subdir1/input.txt: -------------------------------------------------------------------------------- 1 | hello from input.txt 2 | -------------------------------------------------------------------------------- /test/sandbox_outer/sandbox/subdir2/input_link.txt: -------------------------------------------------------------------------------- 1 | ../subdir1/input.txt -------------------------------------------------------------------------------- /test/c/exitcode.c: -------------------------------------------------------------------------------- 1 | // EXIT: 120 2 | 3 | int main() { 4 | return 120; 5 | } 6 | -------------------------------------------------------------------------------- /example/cowsay.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devsnek/node-wasi/HEAD/example/cowsay.wasm -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /test/out 2 | /test/sandbox_outer/sandbox/output.txt 3 | /test/sandbox_outer/sandbox/testdir 4 | /build 5 | node_modules 6 | -------------------------------------------------------------------------------- /test/c/fd_prestat_get_refresh.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(void) { 4 | isatty(1); 5 | __builtin_wasm_memory_grow(0, 1); 6 | isatty(1); 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /test/c/symlink_loop.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | FILE* file = fopen("/sandbox/subdir1/loop1", "r"); 7 | assert(file == NULL); 8 | assert(errno == ELOOP); 9 | } 10 | -------------------------------------------------------------------------------- /test/c/notdir.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | DIR* dir = opendir("/sandbox/notadir"); 7 | assert(dir == NULL); 8 | assert(errno == ENOTDIR); 9 | 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /test/c/symlink_escape.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | FILE* file = fopen("/sandbox/subdir/outside.txt", "r"); 7 | assert(file == NULL); 8 | assert(errno == ENOTCAPABLE); 9 | } 10 | -------------------------------------------------------------------------------- /test/c/cant_dotdot.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | FILE* file = fopen("/sandbox/../outside.txt", "r"); 7 | assert(file == NULL); 8 | assert(errno == ENOTCAPABLE); 9 | 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /test/c/stdin.c: -------------------------------------------------------------------------------- 1 | // STDIN: hello world 2 | // STDOUT: hello world 3 | 4 | #include 5 | 6 | int main(void) { 7 | char x[32]; 8 | 9 | if (fgets(x, sizeof x, stdin) == NULL) { 10 | return ferror(stdin); 11 | } 12 | if (fputs(x, stdout) == EOF) { 13 | return ferror(stdout); 14 | } 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /test/c/read_file.c: -------------------------------------------------------------------------------- 1 | // STDOUT: hello from input.txt\n 2 | 3 | #include 4 | #include 5 | 6 | int main() { 7 | FILE* file = fopen("/sandbox/input.txt", "r"); 8 | assert(file != NULL); 9 | 10 | char c = fgetc(file); 11 | while (c != EOF) { 12 | assert(fputc(c, stdout) != EOF); 13 | c = fgetc(file); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [{ 3 | 'target_name': 'wasi', 4 | 'sources': [ 5 | 'src/binding.cc', 6 | ], 7 | 'cflags': [ '-Werror', '-Wall', '-Wextra', '-Wpedantic', '-Wunused-parameter', '-fno-exceptions' ], 8 | 'cflags_cc': [ '-Werror', '-Wall', '-Wextra', '-Wpedantic', '-Wunused-parameter', '-fno-exceptions' ], 9 | }], 10 | } 11 | -------------------------------------------------------------------------------- /test/c/follow_symlink.c: -------------------------------------------------------------------------------- 1 | // STDOUT: hello from input.txt\n 2 | 3 | #include 4 | #include 5 | 6 | int main() { 7 | FILE* file = fopen("/sandbox/subdir2/input_link.txt", "r"); 8 | assert(file != NULL); 9 | 10 | char c = fgetc(file); 11 | while (c != EOF) { 12 | assert(fputc(c, stdout) != EOF); 13 | c = fgetc(file); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/c/write_file.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static char* message = "hello, file!"; 6 | 7 | int main() { 8 | FILE* file = fopen("/tmp/output.txt", "w"); 9 | assert(file != NULL); 10 | 11 | int nwritten = fprintf(file, "%s", message); 12 | assert(nwritten == strlen(message)); 13 | 14 | assert(fclose(file) == 0); 15 | } 16 | -------------------------------------------------------------------------------- /test/c/clock_getres.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | struct timespec ts; 6 | 7 | // supported clocks 8 | assert(clock_getres(CLOCK_REALTIME, &ts) == 0); 9 | assert(clock_getres(CLOCK_MONOTONIC, &ts) == 0); 10 | assert(clock_getres(CLOCK_PROCESS_CPUTIME_ID, &ts) == 0); 11 | assert(clock_getres(CLOCK_THREAD_CPUTIME_ID, &ts) == 0); 12 | } 13 | -------------------------------------------------------------------------------- /test/c/getentropy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | char buf[256] = {0}; 6 | assert(getentropy(buf, 256) == 0); 7 | 8 | for (int i = 0; i < 256; i++) { 9 | if (buf[i] != 0) { 10 | return 0; 11 | } 12 | } 13 | 14 | // if this ever is reached, we either have a bug or should buy a lottery 15 | // ticket 16 | return 1; 17 | } 18 | -------------------------------------------------------------------------------- /test/c/read_file_twice.c: -------------------------------------------------------------------------------- 1 | // STDOUT: hello from input.txt\nhello from input.txt\n 2 | 3 | #include 4 | #include 5 | 6 | int main() { 7 | for (int i = 0; i < 2; i++) { 8 | FILE* file = fopen("/sandbox/input.txt", "r"); 9 | assert(file != NULL); 10 | 11 | char c = fgetc(file); 12 | while (c != EOF) { 13 | assert(fputc(c, stdout) != EOF); 14 | c = fgetc(file); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [devsnek] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: # Replace with a single custom sponsorship URL 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-wasi 2 | 3 | # This module and WASI are still in early development! 4 | # Expect bugs and breaking changes! 5 | 6 | A module to use WASI modules with Node.js. 7 | 8 | ```js 9 | const WASI = require('wasi'); 10 | 11 | const wasi = new WASI({ 12 | // preopenDirectories: { '.': '.' }, 13 | }); 14 | 15 | const inst = new WebAssembly.Instance(module, { 16 | wasi_unstable: wasi.exports, 17 | }); 18 | wasi.setMemory(inst.exports.memory); 19 | 20 | inst.exports._start(); 21 | ``` 22 | -------------------------------------------------------------------------------- /test/c/gettimeofday.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | struct timeval tv1; 7 | gettimeofday(&tv1, NULL); 8 | 9 | for (int i = 0; i < 1000; i++) { 10 | } 11 | 12 | struct timeval tv2; 13 | gettimeofday(&tv2, NULL); 14 | 15 | // assert that some time has passed 16 | long long s1 = tv1.tv_sec; 17 | long long us1 = tv1.tv_usec; 18 | long long s2 = tv2.tv_sec; 19 | long long us2 = tv2.tv_usec; 20 | assert(s1 <= s2); 21 | if (s1 == s2) { 22 | // strictly less than, so the timestamps can't be equal 23 | assert(us1 < us2); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/runner.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const os = require('os'); 6 | const WASI = require('..'); 7 | 8 | const bin = fs.readFileSync(process.argv[2]); 9 | 10 | const mod = new WebAssembly.Module(bin); 11 | 12 | const wasi = new WASI({ 13 | preopenDirectories: { 14 | '/sandbox': path.resolve(__dirname, 'sandbox_outer', 'sandbox'), 15 | '/tmp': os.tmpdir(), 16 | }, 17 | }); 18 | 19 | const instance = new WebAssembly.Instance(mod, { 20 | wasi_unstable: wasi.exports, 21 | }); 22 | 23 | wasi.setMemory(instance.exports.memory); 24 | 25 | instance.exports._start(); 26 | -------------------------------------------------------------------------------- /test/c/getrusage.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | struct rusage ru1; 6 | getrusage(RUSAGE_SELF, &ru1); 7 | 8 | for (int i = 0; i < 1000; i++) { 9 | } 10 | 11 | struct rusage ru2; 12 | getrusage(RUSAGE_SELF, &ru2); 13 | 14 | // assert that some time has passed 15 | long long s1 = ru1.ru_utime.tv_sec; 16 | long long us1 = ru1.ru_utime.tv_usec; 17 | long long s2 = ru2.ru_utime.tv_sec; 18 | long long us2 = ru2.ru_utime.tv_usec; 19 | assert(s1 <= s2); 20 | if (s1 == s2) { 21 | // strictly less than, so the timestamps can't be equal 22 | assert(us1 < us2); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wasi", 3 | "version": "0.0.6", 4 | "description": "WASI for Node.js", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "node test/run.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/devsnek/node-wasi.git" 12 | }, 13 | "bin": { 14 | "wasi": "run.js" 15 | }, 16 | "author": "devsnek", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/devsnek/node-wasi/issues" 20 | }, 21 | "homepage": "https://github.com/devsnek/node-wasi#readme", 22 | "dependencies": { 23 | "bindings": "^1.5.0" 24 | }, 25 | "gypfile": true 26 | } 27 | -------------------------------------------------------------------------------- /test/c/poll.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main(void) { 7 | struct pollfd fds[2]; 8 | time_t before, now; 9 | int ret; 10 | 11 | fds[0] = (struct pollfd){.fd = 1, .events = POLLOUT, .revents = 0}; 12 | fds[1] = (struct pollfd){.fd = 2, .events = POLLOUT, .revents = 0}; 13 | 14 | ret = poll(fds, 2, -1); 15 | assert(ret == 2); 16 | assert(fds[0].revents == POLLOUT); 17 | assert(fds[1].revents == POLLOUT); 18 | 19 | fds[0] = (struct pollfd){.fd = 0, .events = POLLIN, .revents = 0}; 20 | time(&before); 21 | ret = poll(fds, 1, 2000); 22 | time(&now); 23 | assert(ret == 0); 24 | assert(now - before >= 2); 25 | 26 | sleep(1); 27 | time(&now); 28 | assert(now - before >= 3); 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /run.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | /** 6 | * A simple WASI runner which exposes the 7 | * full environment and arguments to the 8 | * WASI binary. 9 | * 10 | * :wasm:M::\x00\x61\x73\x6d::wasi: 11 | */ 12 | 13 | const fs = require('fs'); 14 | const WASI = require('.'); 15 | 16 | if (!process.argv[2]) { 17 | process.stdout.write(`wasi runner 18 | A simple WASI runner which exposes the full 19 | environment, arguments, and cwd to the WASI 20 | binary. 21 | 22 | :wasm:M::\\x00\\x61\\x73\\x6d::wasi: 23 | 24 | USAGE: 25 | wasi ... 26 | `); 27 | process.exit(1); 28 | } 29 | 30 | const bin = fs.readFileSync(process.argv[2]); 31 | 32 | const mod = new WebAssembly.Module(bin); 33 | 34 | const wasi = new WASI({ 35 | preopenDirectories: { '.': '.' }, 36 | env: process.env, 37 | args: process.argv.slice(2), 38 | }); 39 | const instance = new WebAssembly.Instance(mod, { 40 | wasi_unstable: wasi.exports, 41 | }); 42 | 43 | wasi.setMemory(instance.exports.memory); 44 | 45 | instance.exports._start(); 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Gus Caplan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to 5 | deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /test/c/stat.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define BASE_DIR "/tmp" 9 | #define OUTPUT_DIR BASE_DIR "/testdir" 10 | #define PATH OUTPUT_DIR "/output.txt" 11 | #define SIZE 500 12 | 13 | int main(void) { 14 | struct stat st; 15 | int fd; 16 | int ret; 17 | off_t pos; 18 | 19 | (void)st; 20 | ret = mkdir(OUTPUT_DIR, 0755); 21 | assert(ret == 0); 22 | 23 | fd = open(PATH, O_CREAT | O_WRONLY, 0666); 24 | assert(fd != -1); 25 | 26 | pos = lseek(fd, SIZE - 1, SEEK_SET); 27 | assert(pos == SIZE - 1); 28 | 29 | ret = (int)write(fd, "", 1); 30 | assert(ret == 1); 31 | 32 | ret = fstat(fd, &st); 33 | assert(ret == 0); 34 | assert(st.st_size == SIZE); 35 | 36 | ret = close(fd); 37 | assert(ret == 0); 38 | 39 | ret = access(PATH, R_OK); 40 | assert(ret == 0); 41 | 42 | ret = stat(PATH, &st); 43 | assert(ret == 0); 44 | assert(st.st_size == SIZE); 45 | 46 | ret = unlink(PATH); 47 | assert(ret == 0); 48 | 49 | ret = stat(PATH, &st); 50 | assert(ret == -1); 51 | 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /test/run.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const cp = require('child_process'); 6 | const assert = require('assert'); 7 | 8 | const SKIP = [ 9 | 'poll', 10 | ]; 11 | 12 | const C_DIR = path.resolve(__dirname, 'c'); 13 | const OUT_DIR = path.resolve(__dirname, 'out'); 14 | const RUNNER_PATH = path.resolve(__dirname, 'runner.js'); 15 | 16 | function exec(command, { stdin } = {}) { 17 | try { 18 | const stdout = cp.execSync(command, { 19 | input: stdin, 20 | }); 21 | return { code: 0, stdout: stdout.toString() }; 22 | } catch (e) { 23 | if (e.status) { 24 | return { code: e.status, stdout: '' }; 25 | } 26 | throw e; 27 | } 28 | } 29 | 30 | function cleanup() { 31 | exec(`rm -rf ${__dirname}/sandbox_outer/sandbox/testdir`); 32 | exec(`rm -rf ${__dirname}/sandbox_outer/sandbox/output.txt`); 33 | } 34 | 35 | function parseOptions(string) { 36 | const options = {}; 37 | for (const line of string.split('\n')) { 38 | if (!line.startsWith('//')) { 39 | break; 40 | } 41 | const [key, value] = line.slice(3).split(': '); 42 | if (!key || !value) { 43 | continue; // eslint-disable-line no-continue 44 | } 45 | options[key] = value.replace(/\\n/g, '\n'); 46 | } 47 | return options; 48 | } 49 | 50 | const files = process.argv[2] ? [process.argv[2]] : fs.readdirSync(C_DIR); 51 | 52 | files.forEach((filename) => { 53 | const file = filename.split('.')[0]; 54 | 55 | if (SKIP.includes(file)) { 56 | return; 57 | } 58 | 59 | const source = fs.readFileSync(path.join(C_DIR, filename), 'utf8'); 60 | const options = parseOptions(source); 61 | 62 | console.log(file); // eslint-disable-line no-console 63 | 64 | cleanup(); 65 | 66 | exec(`/opt/wasi-sdk/bin/clang ${C_DIR}/${filename} -target wasm32-wasi -o ${OUT_DIR}/${file}.wasm`); 67 | 68 | const { code, stdout } = exec(`node --experimental-wasm-bigint ${RUNNER_PATH} ${OUT_DIR}/${file}.wasm`, { 69 | stdin: options.STDIN, 70 | }); 71 | 72 | if (options.EXIT) { 73 | assert.strictEqual(code, +options.EXIT); 74 | } else { 75 | assert.strictEqual(code, 0); 76 | } 77 | 78 | assert.strictEqual(stdout, options.STDOUT || ''); 79 | }); 80 | 81 | cleanup(); 82 | -------------------------------------------------------------------------------- /example/cowsay.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Read}; 2 | 3 | fn main() -> std::io::Result<()> { 4 | let mut args = std::env::args().into_iter().collect::>(); 5 | let mut input = if args.len() > 1 { 6 | args.remove(0); 7 | args 8 | } else { 9 | let mut data = String::new(); 10 | io::stdin().read_to_string(&mut data)?; 11 | data.split_ascii_whitespace().map(String::from).collect::>() 12 | }; 13 | 14 | let mut output = Vec::new(); 15 | loop { 16 | let mut line = String::new(); 17 | while !input.is_empty() { 18 | if input[0].len() > 40 { 19 | line += &input.remove(0); 20 | break; 21 | } 22 | if input[0].len() + line.len() > 40 { 23 | break; 24 | } 25 | line += " "; 26 | line += &input.remove(0); 27 | } 28 | output.push(line); 29 | if input.is_empty() { 30 | break; 31 | } 32 | } 33 | 34 | if output.len() == 1 { 35 | let len = output[0].len() + 1; 36 | println!(" {}", "_".repeat(len)); 37 | println!("<{} >", output[0]); 38 | println!(" {}", "-".repeat(len)); 39 | } else { 40 | let len = output[0].len(); 41 | println!(" {}", "_".repeat(len + 1)); 42 | for (i, v) in output.iter().enumerate() { 43 | println!( 44 | "{}{:width$} {}", 45 | if i == 0 { 46 | "/" 47 | } else if i == output.len() - 1 { 48 | "\\" 49 | } else { 50 | "|" 51 | }, 52 | v, 53 | if i == 0 { 54 | "\\" 55 | } else if i == output.len() - 1 { 56 | "/" 57 | } else { 58 | "|" 59 | }, 60 | width = len, 61 | ); 62 | } 63 | println!(" {}", "-".repeat(len + 1)); 64 | } 65 | 66 | println!( 67 | " \\ ^__^ 68 | \\ (oo)\\_______ 69 | (__)\\ )\\/\\ 70 | ||----w | 71 | || ||"); 72 | 73 | Ok(()) 74 | } 75 | -------------------------------------------------------------------------------- /src/binding.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef _WIN32 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | #include 6 | #include 7 | #else 8 | #include 9 | #include 10 | #include 11 | #endif 12 | #include 13 | 14 | #define NAPI_EXPERIMENTAL 15 | #include 16 | 17 | namespace node { 18 | namespace wasi { 19 | 20 | napi_value Seek(napi_env env, napi_callback_info info) { 21 | napi_status status; 22 | 23 | napi_value args[3]; 24 | size_t argc = 3; 25 | status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); 26 | if (status != napi_ok) { 27 | return nullptr; 28 | } 29 | 30 | uint32_t fd; 31 | status = napi_get_value_uint32(env, args[0], &fd); 32 | if (status != napi_ok) { 33 | return nullptr; 34 | } 35 | 36 | uint64_t offset; 37 | bool lossless; 38 | status = napi_get_value_bigint_uint64(env, args[1], &offset, &lossless); 39 | if (status != napi_ok) { 40 | return nullptr; 41 | } 42 | 43 | int32_t whence; 44 | status = napi_get_value_int32(env, args[2], &whence); 45 | if (status != napi_ok) { 46 | return nullptr; 47 | } 48 | 49 | #ifdef _WIN32 50 | int64_t new_offset = _lseeki64(fd, offset, whence); 51 | #else 52 | off_t new_offset = lseek(fd, offset, whence); 53 | #endif // _WIN32 54 | 55 | napi_value result; 56 | if (new_offset == -1) { 57 | status = napi_create_int32(env, errno, &result); 58 | } else { 59 | status = napi_create_bigint_uint64(env, new_offset, &result); 60 | } 61 | if (status != napi_ok) { 62 | return nullptr; 63 | } 64 | 65 | return result; 66 | } 67 | 68 | napi_value SchedYield(napi_env, napi_callback_info) { 69 | #ifdef _WIN32 70 | SwitchToThread(); 71 | #else 72 | sched_yield(); 73 | #endif // WIN32 74 | return nullptr; 75 | } 76 | 77 | napi_value Realtime(napi_env env, napi_callback_info) { 78 | uv_timeval64_t tv; 79 | uv_gettimeofday(&tv); 80 | 81 | uint64_t ns = tv.tv_sec * 1e9; 82 | ns += tv.tv_usec * 1000; 83 | 84 | napi_value result; 85 | napi_status status = napi_create_bigint_uint64(env, ns, &result); 86 | 87 | if (status != napi_ok) { 88 | return nullptr; 89 | } 90 | 91 | return result; 92 | } 93 | 94 | napi_value Init(napi_env env, napi_value exports) { 95 | napi_status status; 96 | 97 | #define V(f, name) \ 98 | { \ 99 | napi_value func; \ 100 | status = napi_create_function( \ 101 | env, name, NAPI_AUTO_LENGTH, f, nullptr, &func); \ 102 | if (status != napi_ok) { \ 103 | return nullptr; \ 104 | } \ 105 | status = napi_set_named_property(env, exports, name, func); \ 106 | if (status != napi_ok) { \ 107 | return nullptr; \ 108 | } \ 109 | } 110 | V(Seek, "seek"); 111 | V(SchedYield, "schedYield"); 112 | V(Realtime, "realtime"); 113 | #undef V 114 | 115 | #define V(n) \ 116 | { \ 117 | napi_value result; \ 118 | napi_create_int32(env, n, &result); \ 119 | napi_set_named_property(env, exports, #n, result); \ 120 | } 121 | V(SEEK_SET); 122 | V(SEEK_CUR); 123 | V(SEEK_END); 124 | 125 | 126 | return exports; 127 | } 128 | 129 | } // namespace wasi 130 | } // namespace node 131 | 132 | NAPI_MODULE(wasi, node::wasi::Init) 133 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at me@gus.host. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable no-unused-vars */ 4 | /* eslint-disable arrow-body-style */ 5 | /* eslint max-len: ["error", { "code": 80 }] */ 6 | 7 | const binding = require('bindings')('wasi'); 8 | 9 | const crypto = require('crypto'); 10 | const fs = require('fs'); 11 | const path = require('path'); 12 | const perfHooks = require('perf_hooks'); 13 | const { isatty: isTTY } = require('tty'); 14 | const { Buffer } = require('buffer'); 15 | 16 | const WASI_ESUCCESS = 0; 17 | const WASI_E2BIG = 1; 18 | const WASI_EACCES = 2; 19 | const WASI_EADDRINUSE = 3; 20 | const WASI_EADDRNOTAVAIL = 4; 21 | const WASI_EAFNOSUPPORT = 5; 22 | const WASI_EAGAIN = 6; 23 | const WASI_EALREADY = 7; 24 | const WASI_EBADF = 8; 25 | const WASI_EBADMSG = 9; 26 | const WASI_EBUSY = 10; 27 | const WASI_ECANCELED = 11; 28 | const WASI_ECHILD = 12; 29 | const WASI_ECONNABORTED = 13; 30 | const WASI_ECONNREFUSED = 14; 31 | const WASI_ECONNRESET = 15; 32 | const WASI_EDEADLK = 16; 33 | const WASI_EDESTADDRREQ = 17; 34 | const WASI_EDOM = 18; 35 | const WASI_EDQUOT = 19; 36 | const WASI_EEXIST = 20; 37 | const WASI_EFAULT = 21; 38 | const WASI_EFBIG = 22; 39 | const WASI_EHOSTUNREACH = 23; 40 | const WASI_EIDRM = 24; 41 | const WASI_EILSEQ = 25; 42 | const WASI_EINPROGRESS = 26; 43 | const WASI_EINTR = 27; 44 | const WASI_EINVAL = 28; 45 | const WASI_EIO = 29; 46 | const WASI_EISCONN = 30; 47 | const WASI_EISDIR = 31; 48 | const WASI_ELOOP = 32; 49 | const WASI_EMFILE = 33; 50 | const WASI_EMLINK = 34; 51 | const WASI_EMSGSIZE = 35; 52 | const WASI_EMULTIHOP = 36; 53 | const WASI_ENAMETOOLONG = 37; 54 | const WASI_ENETDOWN = 38; 55 | const WASI_ENETRESET = 39; 56 | const WASI_ENETUNREACH = 40; 57 | const WASI_ENFILE = 41; 58 | const WASI_ENOBUFS = 42; 59 | const WASI_ENODEV = 43; 60 | const WASI_ENOENT = 44; 61 | const WASI_ENOEXEC = 45; 62 | const WASI_ENOLCK = 46; 63 | const WASI_ENOLINK = 47; 64 | const WASI_ENOMEM = 48; 65 | const WASI_ENOMSG = 49; 66 | const WASI_ENOPROTOOPT = 50; 67 | const WASI_ENOSPC = 51; 68 | const WASI_ENOSYS = 52; 69 | const WASI_ENOTCONN = 53; 70 | const WASI_ENOTDIR = 54; 71 | const WASI_ENOTEMPTY = 55; 72 | const WASI_ENOTRECOVERABLE = 56; 73 | const WASI_ENOTSOCK = 57; 74 | const WASI_ENOTSUP = 58; 75 | const WASI_ENOTTY = 59; 76 | const WASI_ENXIO = 60; 77 | const WASI_EOVERFLOW = 61; 78 | const WASI_EOWNERDEAD = 62; 79 | const WASI_EPERM = 63; 80 | const WASI_EPIPE = 64; 81 | const WASI_EPROTO = 65; 82 | const WASI_EPROTONOSUPPORT = 66; 83 | const WASI_EPROTOTYPE = 67; 84 | const WASI_ERANGE = 68; 85 | const WASI_EROFS = 69; 86 | const WASI_ESPIPE = 70; 87 | const WASI_ESRCH = 71; 88 | const WASI_ESTALE = 72; 89 | const WASI_ETIMEDOUT = 73; 90 | const WASI_ETXTBSY = 74; 91 | const WASI_EXDEV = 75; 92 | const WASI_ENOTCAPABLE = 76; 93 | 94 | const WASI_SIGABRT = 0; 95 | const WASI_SIGALRM = 1; 96 | const WASI_SIGBUS = 2; 97 | const WASI_SIGCHLD = 3; 98 | const WASI_SIGCONT = 4; 99 | const WASI_SIGFPE = 5; 100 | const WASI_SIGHUP = 6; 101 | const WASI_SIGILL = 7; 102 | const WASI_SIGINT = 8; 103 | const WASI_SIGKILL = 9; 104 | const WASI_SIGPIPE = 10; 105 | const WASI_SIGQUIT = 11; 106 | const WASI_SIGSEGV = 12; 107 | const WASI_SIGSTOP = 13; 108 | const WASI_SIGTERM = 14; 109 | const WASI_SIGTRAP = 15; 110 | const WASI_SIGTSTP = 16; 111 | const WASI_SIGTTIN = 17; 112 | const WASI_SIGTTOU = 18; 113 | const WASI_SIGURG = 19; 114 | const WASI_SIGUSR1 = 20; 115 | const WASI_SIGUSR2 = 21; 116 | const WASI_SIGVTALRM = 22; 117 | const WASI_SIGXCPU = 23; 118 | const WASI_SIGXFSZ = 24; 119 | 120 | const WASI_FILETYPE_UNKNOWN = 0; 121 | const WASI_FILETYPE_BLOCK_DEVICE = 1; 122 | const WASI_FILETYPE_CHARACTER_DEVICE = 2; 123 | const WASI_FILETYPE_DIRECTORY = 3; 124 | const WASI_FILETYPE_REGULAR_FILE = 4; 125 | const WASI_FILETYPE_SOCKET_DGRAM = 5; 126 | const WASI_FILETYPE_SOCKET_STREAM = 6; 127 | const WASI_FILETYPE_SYMBOLIC_LINK = 7; 128 | 129 | const WASI_FDFLAG_APPEND = 0x0001; 130 | const WASI_FDFLAG_DSYNC = 0x0002; 131 | const WASI_FDFLAG_NONBLOCK = 0x0004; 132 | const WASI_FDFLAG_RSYNC = 0x0008; 133 | const WASI_FDFLAG_SYNC = 0x0010; 134 | 135 | const WASI_RIGHT_FD_DATASYNC = 0x0000000000000001n; 136 | const WASI_RIGHT_FD_READ = 0x0000000000000002n; 137 | const WASI_RIGHT_FD_SEEK = 0x0000000000000004n; 138 | const WASI_RIGHT_FD_FDSTAT_SET_FLAGS = 0x0000000000000008n; 139 | const WASI_RIGHT_FD_SYNC = 0x0000000000000010n; 140 | const WASI_RIGHT_FD_TELL = 0x0000000000000020n; 141 | const WASI_RIGHT_FD_WRITE = 0x0000000000000040n; 142 | const WASI_RIGHT_FD_ADVISE = 0x0000000000000080n; 143 | const WASI_RIGHT_FD_ALLOCATE = 0x0000000000000100n; 144 | const WASI_RIGHT_PATH_CREATE_DIRECTORY = 0x0000000000000200n; 145 | const WASI_RIGHT_PATH_CREATE_FILE = 0x0000000000000400n; 146 | const WASI_RIGHT_PATH_LINK_SOURCE = 0x0000000000000800n; 147 | const WASI_RIGHT_PATH_LINK_TARGET = 0x0000000000001000n; 148 | const WASI_RIGHT_PATH_OPEN = 0x0000000000002000n; 149 | const WASI_RIGHT_FD_READDIR = 0x0000000000004000n; 150 | const WASI_RIGHT_PATH_READLINK = 0x0000000000008000n; 151 | const WASI_RIGHT_PATH_RENAME_SOURCE = 0x0000000000010000n; 152 | const WASI_RIGHT_PATH_RENAME_TARGET = 0x0000000000020000n; 153 | const WASI_RIGHT_PATH_FILESTAT_GET = 0x0000000000040000n; 154 | const WASI_RIGHT_PATH_FILESTAT_SET_SIZE = 0x0000000000080000n; 155 | const WASI_RIGHT_PATH_FILESTAT_SET_TIMES = 0x0000000000100000n; 156 | const WASI_RIGHT_FD_FILESTAT_GET = 0x0000000000200000n; 157 | const WASI_RIGHT_FD_FILESTAT_SET_SIZE = 0x0000000000400000n; 158 | const WASI_RIGHT_FD_FILESTAT_SET_TIMES = 0x0000000000800000n; 159 | const WASI_RIGHT_PATH_SYMLINK = 0x0000000001000000n; 160 | const WASI_RIGHT_PATH_REMOVE_DIRECTORY = 0x0000000002000000n; 161 | const WASI_RIGHT_PATH_UNLINK_FILE = 0x0000000004000000n; 162 | const WASI_RIGHT_POLL_FD_READWRITE = 0x0000000008000000n; 163 | const WASI_RIGHT_SOCK_SHUTDOWN = 0x0000000010000000n; 164 | 165 | const RIGHTS_ALL = WASI_RIGHT_FD_DATASYNC | WASI_RIGHT_FD_READ 166 | | WASI_RIGHT_FD_SEEK | WASI_RIGHT_FD_FDSTAT_SET_FLAGS | WASI_RIGHT_FD_SYNC 167 | | WASI_RIGHT_FD_TELL | WASI_RIGHT_FD_WRITE | WASI_RIGHT_FD_ADVISE 168 | | WASI_RIGHT_FD_ALLOCATE | WASI_RIGHT_PATH_CREATE_DIRECTORY 169 | | WASI_RIGHT_PATH_CREATE_FILE | WASI_RIGHT_PATH_LINK_SOURCE 170 | | WASI_RIGHT_PATH_LINK_TARGET | WASI_RIGHT_PATH_OPEN | WASI_RIGHT_FD_READDIR 171 | | WASI_RIGHT_PATH_READLINK | WASI_RIGHT_PATH_RENAME_SOURCE 172 | | WASI_RIGHT_PATH_RENAME_TARGET | WASI_RIGHT_PATH_FILESTAT_GET 173 | | WASI_RIGHT_PATH_FILESTAT_SET_SIZE | WASI_RIGHT_PATH_FILESTAT_SET_TIMES 174 | | WASI_RIGHT_FD_FILESTAT_GET | WASI_RIGHT_FD_FILESTAT_SET_TIMES 175 | | WASI_RIGHT_FD_FILESTAT_SET_SIZE | WASI_RIGHT_PATH_SYMLINK 176 | | WASI_RIGHT_PATH_UNLINK_FILE | WASI_RIGHT_PATH_REMOVE_DIRECTORY 177 | | WASI_RIGHT_POLL_FD_READWRITE | WASI_RIGHT_SOCK_SHUTDOWN; 178 | 179 | const RIGHTS_BLOCK_DEVICE_BASE = RIGHTS_ALL; 180 | const RIGHTS_BLOCK_DEVICE_INHERITING = RIGHTS_ALL; 181 | 182 | const RIGHTS_CHARACTER_DEVICE_BASE = RIGHTS_ALL; 183 | const RIGHTS_CHARACTER_DEVICE_INHERITING = RIGHTS_ALL; 184 | 185 | const RIGHTS_REGULAR_FILE_BASE = WASI_RIGHT_FD_DATASYNC | WASI_RIGHT_FD_READ 186 | | WASI_RIGHT_FD_SEEK | WASI_RIGHT_FD_FDSTAT_SET_FLAGS | WASI_RIGHT_FD_SYNC 187 | | WASI_RIGHT_FD_TELL | WASI_RIGHT_FD_WRITE | WASI_RIGHT_FD_ADVISE 188 | | WASI_RIGHT_FD_ALLOCATE | WASI_RIGHT_FD_FILESTAT_GET 189 | | WASI_RIGHT_FD_FILESTAT_SET_SIZE | WASI_RIGHT_FD_FILESTAT_SET_TIMES 190 | | WASI_RIGHT_POLL_FD_READWRITE; 191 | const RIGHTS_REGULAR_FILE_INHERITING = 0n; 192 | 193 | const RIGHTS_DIRECTORY_BASE = WASI_RIGHT_FD_FDSTAT_SET_FLAGS 194 | | WASI_RIGHT_FD_SYNC | WASI_RIGHT_FD_ADVISE | WASI_RIGHT_PATH_CREATE_DIRECTORY 195 | | WASI_RIGHT_PATH_CREATE_FILE | WASI_RIGHT_PATH_LINK_SOURCE 196 | | WASI_RIGHT_PATH_LINK_TARGET | WASI_RIGHT_PATH_OPEN | WASI_RIGHT_FD_READDIR 197 | | WASI_RIGHT_PATH_READLINK | WASI_RIGHT_PATH_RENAME_SOURCE 198 | | WASI_RIGHT_PATH_RENAME_TARGET | WASI_RIGHT_PATH_FILESTAT_GET 199 | | WASI_RIGHT_PATH_FILESTAT_SET_SIZE | WASI_RIGHT_PATH_FILESTAT_SET_TIMES 200 | | WASI_RIGHT_FD_FILESTAT_GET | WASI_RIGHT_FD_FILESTAT_SET_TIMES 201 | | WASI_RIGHT_PATH_SYMLINK | WASI_RIGHT_PATH_UNLINK_FILE 202 | | WASI_RIGHT_PATH_REMOVE_DIRECTORY | WASI_RIGHT_POLL_FD_READWRITE; 203 | const RIGHTS_DIRECTORY_INHERITING = RIGHTS_DIRECTORY_BASE 204 | | RIGHTS_REGULAR_FILE_BASE; 205 | 206 | const RIGHTS_SOCKET_BASE = WASI_RIGHT_FD_READ | WASI_RIGHT_FD_FDSTAT_SET_FLAGS 207 | | WASI_RIGHT_FD_WRITE | WASI_RIGHT_FD_FILESTAT_GET 208 | | WASI_RIGHT_POLL_FD_READWRITE | WASI_RIGHT_SOCK_SHUTDOWN; 209 | const RIGHTS_SOCKET_INHERITING = RIGHTS_ALL; 210 | 211 | const RIGHTS_TTY_BASE = WASI_RIGHT_FD_READ | WASI_RIGHT_FD_FDSTAT_SET_FLAGS 212 | | WASI_RIGHT_FD_WRITE | WASI_RIGHT_FD_FILESTAT_GET 213 | | WASI_RIGHT_POLL_FD_READWRITE; 214 | const RIGHTS_TTY_INHERITING = 0n; 215 | 216 | const WASI_CLOCK_MONOTONIC = 0; 217 | const WASI_CLOCK_PROCESS_CPUTIME_ID = 1; 218 | const WASI_CLOCK_REALTIME = 2; 219 | const WASI_CLOCK_THREAD_CPUTIME_ID = 3; 220 | 221 | const WASI_EVENTTYPE_CLOCK = 0; 222 | const WASI_EVENTTYPE_FD_READ = 1; 223 | const WASI_EVENTTYPE_FD_WRITE = 2; 224 | 225 | const WASI_FILESTAT_SET_ATIM = 1 << 0; 226 | const WASI_FILESTAT_SET_ATIM_NOW = 1 << 1; 227 | const WASI_FILESTAT_SET_MTIM = 1 << 2; 228 | const WASI_FILESTAT_SET_MTIM_NOW = 1 << 3; 229 | 230 | const WASI_O_CREAT = 1 << 0; 231 | const WASI_O_DIRECTORY = 1 << 1; 232 | const WASI_O_EXCL = 1 << 2; 233 | const WASI_O_TRUNC = 1 << 3; 234 | 235 | const WASI_PREOPENTYPE_DIR = 0; 236 | 237 | const WASI_DIRCOOKIE_START = 0; 238 | 239 | const WASI_STDIN_FILENO = 0; 240 | const WASI_STDOUT_FILENO = 1; 241 | const WASI_STDERR_FILENO = 2; 242 | 243 | const WASI_WHENCE_CUR = 0; 244 | const WASI_WHENCE_END = 1; 245 | const WASI_WHENCE_SET = 2; 246 | 247 | // http://man7.org/linux/man-pages/man3/errno.3.html 248 | const ERROR_MAP = { 249 | E2BIG: WASI_E2BIG, 250 | EACCES: WASI_EACCES, 251 | EADDRINUSE: WASI_EADDRINUSE, 252 | EADDRNOTAVAIL: WASI_EADDRNOTAVAIL, 253 | EAFNOSUPPORT: WASI_EAFNOSUPPORT, 254 | EALREADY: WASI_EALREADY, 255 | EAGAIN: WASI_EAGAIN, 256 | // EBADE: WASI_EBADE, 257 | EBADF: WASI_EBADF, 258 | // EBADFD: WASI_EBADFD, 259 | EBADMSG: WASI_EBADMSG, 260 | // EBADR: WASI_EBADR, 261 | // EBADRQC: WASI_EBADRQC, 262 | // EBADSLT: WASI_EBADSLT, 263 | EBUSY: WASI_EBUSY, 264 | ECANCELED: WASI_ECANCELED, 265 | ECHILD: WASI_ECHILD, 266 | // ECHRNG: WASI_ECHRNG, 267 | // ECOMM: WASI_ECOMM, 268 | ECONNABORTED: WASI_ECONNABORTED, 269 | ECONNREFUSED: WASI_ECONNREFUSED, 270 | ECONNRESET: WASI_ECONNRESET, 271 | EDEADLOCK: WASI_EDEADLK, 272 | EDESTADDRREQ: WASI_EDESTADDRREQ, 273 | EDOM: WASI_EDOM, 274 | EDQUOT: WASI_EDQUOT, 275 | EEXIST: WASI_EEXIST, 276 | EFAULT: WASI_EFAULT, 277 | EFBIG: WASI_EFBIG, 278 | EHOSTDOWN: WASI_EHOSTUNREACH, 279 | EHOSTUNREACH: WASI_EHOSTUNREACH, 280 | // EHWPOISON: WASI_EHWPOISON, 281 | EIDRM: WASI_EIDRM, 282 | EILSEQ: WASI_EILSEQ, 283 | EINPROGRESS: WASI_EINPROGRESS, 284 | EINTR: WASI_EINTR, 285 | EINVAL: WASI_EINVAL, 286 | EIO: WASI_EIO, 287 | EISCONN: WASI_EISCONN, 288 | EISDIR: WASI_EISDIR, 289 | // EISNAM: WASI_EISNAM, 290 | // EKEYEXPIRED: WASI_EKEYEXPIRED, 291 | // EKEYREJECTED: WASI_EKEYREJECTED, 292 | // EKEYREVOKED: WASI_EKEYREVOKED, 293 | // EL2HLT: WASI_EL2HLT, 294 | // EL2NSYNC: WASI_EL2NSYNC, 295 | // EL3HLT: WASI_EL3HLT, 296 | // EL3RST: WASI_EL3RST, 297 | // ELIBACC: WASI_ELIBACC, 298 | // ELIBBAD: WASI_ELIBBAD, 299 | // ELIBMAX: WASI_ELIBMAX, 300 | // ELIBSCN: WASI_ELIBSCN, 301 | // ELIBEXEC: WASI_ELIBEXEC, 302 | // ELNRANGE: WASI_ELNRANGE, 303 | ELOOP: WASI_ELOOP, 304 | // EMEDIUMTYPE: WASI_EMEDIUMTYPE, 305 | EMFILE: WASI_EMFILE, 306 | EMLINK: WASI_EMLINK, 307 | EMSGSIZE: WASI_EMSGSIZE, 308 | EMULTIHOP: WASI_EMULTIHOP, 309 | ENAMETOOLONG: WASI_ENAMETOOLONG, 310 | ENETDOWN: WASI_ENETDOWN, 311 | ENETRESET: WASI_ENETRESET, 312 | ENETUNREACH: WASI_ENETUNREACH, 313 | ENFILE: WASI_ENFILE, 314 | // ENOANO: WASI_ENOANO, 315 | ENOBUFS: WASI_ENOBUFS, 316 | // ENODATA: WASI_ENODATA, 317 | ENODEV: WASI_ENODEV, 318 | ENOENT: WASI_ENOENT, 319 | ENOEXEC: WASI_ENOEXEC, 320 | // ENOKEY: WASI_ENOKEY, 321 | ENOLCK: WASI_ENOLCK, 322 | ENOLINK: WASI_ENOLINK, 323 | // ENOMEDIUM: WASI_ENOMEDIUM, 324 | ENOMEM: WASI_ENOMEM, 325 | ENOMSG: WASI_ENOMSG, 326 | // ENONET: WASI_ENONET, 327 | // ENOPKG: WASI_ENOPKG, 328 | ENOPROTOOPT: WASI_ENOPROTOOPT, 329 | ENOSPC: WASI_ENOSPC, 330 | // ENOSR: WASI_ENOSR, 331 | // ENOSTR: WASI_ENOSTR, 332 | ENOSYS: WASI_ENOSYS, 333 | // ENOTBLK: WASI_ENOTBLK, 334 | ENOTCONN: WASI_ENOTCONN, 335 | ENOTDIR: WASI_ENOTDIR, 336 | ENOTEMPTY: WASI_ENOTEMPTY, 337 | ENOTRECOVERABLE: WASI_ENOTRECOVERABLE, 338 | ENOTSOCK: WASI_ENOTSOCK, 339 | ENOTTY: WASI_ENOTTY, 340 | // ENOTUNIQ: WASI_ENOTUNIQ, 341 | ENXIO: WASI_ENXIO, 342 | EOVERFLOW: WASI_EOVERFLOW, 343 | EOWNERDEAD: WASI_EOWNERDEAD, 344 | EPERM: WASI_EPERM, 345 | // EPFNOSUPPORT: WASI_EPFNOSUPPORT, 346 | EPIPE: WASI_EPIPE, 347 | EPROTO: WASI_EPROTO, 348 | EPROTONOSUPPORT: WASI_EPROTONOSUPPORT, 349 | EPROTOTYPE: WASI_EPROTOTYPE, 350 | ERANGE: WASI_ERANGE, 351 | // EREMCHG: WASI_EREMCHG, 352 | // EREMOTE: WASI_EREMOTE, 353 | // EREMOTEIO: WASI_EREMOTEIO, 354 | // ERESTART: WASI_ERESTART, 355 | // ERFKILL: WASI_ERFKILL, 356 | EROFS: WASI_EROFS, 357 | // ESHUTDOWN: WASI_ESHUTDOWN, 358 | ESPIPE: WASI_ESPIPE, 359 | // ESOCKTNOSUPPORT: WASI_ESOCKTNOSUPPORT, 360 | ESRCH: WASI_ESRCH, 361 | ESTALE: WASI_ESTALE, 362 | // ESTRPIPE: WASI_ESTRPIPE, 363 | // ETIME: WASI_ETIME, 364 | ETIMEDOUT: WASI_ETIMEDOUT, 365 | // ETOOMANYREFS: WASI_ETOOMANYREFS, 366 | ETXTBSY: WASI_ETXTBSY, 367 | // EUCLEAN: WASI_EUCLEAN, 368 | // EUNATCH: WASI_EUNATCH, 369 | // EUSERS: WASI_EUSERS, 370 | EXDEV: WASI_EXDEV, 371 | // EXFULL: WASI_EXFULL, 372 | }; 373 | 374 | const SIGNAL_MAP = { 375 | __proto__: null, 376 | [WASI_SIGHUP]: 'SIGHUP', 377 | [WASI_SIGINT]: 'SIGINT', 378 | [WASI_SIGQUIT]: 'SIGQUIT', 379 | [WASI_SIGILL]: 'SIGILL', 380 | [WASI_SIGTRAP]: 'SIGTRAP', 381 | [WASI_SIGABRT]: 'SIGABRT', 382 | // [WASI_SIGIOT]: 'SIGIOT', 383 | [WASI_SIGBUS]: 'SIGBUS', 384 | [WASI_SIGFPE]: 'SIGFPE', 385 | [WASI_SIGKILL]: 'SIGKILL', 386 | [WASI_SIGUSR1]: 'SIGUSR1', 387 | [WASI_SIGSEGV]: 'SIGSEGV', 388 | [WASI_SIGUSR2]: 'SIGUSR2', 389 | [WASI_SIGPIPE]: 'SIGPIPE', 390 | [WASI_SIGALRM]: 'SIGALRM', 391 | [WASI_SIGTERM]: 'SIGTERM', 392 | [WASI_SIGCHLD]: 'SIGCHLD', 393 | [WASI_SIGCONT]: 'SIGCONT', 394 | [WASI_SIGSTOP]: 'SIGSTOP', 395 | [WASI_SIGTSTP]: 'SIGTSTP', 396 | [WASI_SIGTTIN]: 'SIGTTIN', 397 | [WASI_SIGTTOU]: 'SIGTTOU', 398 | [WASI_SIGURG]: 'SIGURG', 399 | [WASI_SIGXCPU]: 'SIGXCPU', 400 | [WASI_SIGXFSZ]: 'SIGXFSZ', 401 | [WASI_SIGVTALRM]: 'SIGVTALRM', 402 | // [WASI_SIGPROF]: 'SIGPROF', 403 | // [WASI_SIGWINCH]: 'SIGWINCH', 404 | // [WASI_SIGIO]: 'SIGIO', 405 | // [WASI_SIGINFO]: 'SIGINFO', 406 | // [WASI_SIGSYS]: 'SIGSYS', 407 | }; 408 | 409 | const msToNs = (ms) => { 410 | const msInt = Math.trunc(ms); 411 | const decimal = BigInt(Math.round((ms - msInt) * 1000)); 412 | const ns = BigInt(msInt) * 1000n; 413 | return ns + decimal; 414 | }; 415 | 416 | const CPUTIME_START = msToNs(perfHooks.performance.timeOrigin); 417 | 418 | const now = (clockId) => { 419 | switch (clockId) { 420 | case WASI_CLOCK_MONOTONIC: 421 | return process.hrtime.bigint(); 422 | case WASI_CLOCK_REALTIME: 423 | return binding.realtime(); 424 | case WASI_CLOCK_PROCESS_CPUTIME_ID: 425 | case WASI_CLOCK_THREAD_CPUTIME_ID: 426 | return process.hrtime.bigint() - CPUTIME_START; 427 | default: 428 | return null; 429 | } 430 | }; 431 | 432 | const wrap = (f) => (...args) => { 433 | try { 434 | return f(...args); 435 | } catch (e) { 436 | if (typeof e === 'number') { 437 | return e; 438 | } 439 | if (e.errno) { 440 | return ERROR_MAP[e.code] || WASI_EINVAL; 441 | } 442 | throw e; 443 | } 444 | }; 445 | 446 | const translateFileAttributes = (fd, stats) => { 447 | switch (true) { 448 | case stats.isBlockDevice(): 449 | return { 450 | filetype: WASI_FILETYPE_BLOCK_DEVICE, 451 | rightsBase: RIGHTS_BLOCK_DEVICE_BASE, 452 | rightsInheriting: RIGHTS_BLOCK_DEVICE_INHERITING, 453 | }; 454 | case stats.isCharacterDevice(): { 455 | const filetype = WASI_FILETYPE_CHARACTER_DEVICE; 456 | if (fd !== undefined && isTTY(fd)) { 457 | return { 458 | filetype, 459 | rightsBase: RIGHTS_TTY_BASE, 460 | rightsInheriting: RIGHTS_TTY_INHERITING, 461 | }; 462 | } 463 | return { 464 | filetype, 465 | rightsBase: RIGHTS_CHARACTER_DEVICE_BASE, 466 | rightsInheriting: RIGHTS_CHARACTER_DEVICE_INHERITING, 467 | }; 468 | } 469 | case stats.isDirectory(): 470 | return { 471 | filetype: WASI_FILETYPE_DIRECTORY, 472 | rightsBase: RIGHTS_DIRECTORY_BASE, 473 | rightsInheriting: RIGHTS_DIRECTORY_INHERITING, 474 | }; 475 | case stats.isFIFO(): 476 | return { 477 | filetype: WASI_FILETYPE_SOCKET_STREAM, 478 | rightsBase: RIGHTS_SOCKET_BASE, 479 | rightsInheriting: RIGHTS_SOCKET_INHERITING, 480 | }; 481 | case stats.isFile(): 482 | return { 483 | filetype: WASI_FILETYPE_REGULAR_FILE, 484 | rightsBase: RIGHTS_REGULAR_FILE_BASE, 485 | rightsInheriting: RIGHTS_REGULAR_FILE_INHERITING, 486 | }; 487 | case stats.isSocket(): 488 | return { 489 | filetype: WASI_FILETYPE_SOCKET_STREAM, 490 | rightsBase: RIGHTS_SOCKET_BASE, 491 | rightsInheriting: RIGHTS_SOCKET_INHERITING, 492 | }; 493 | case stats.isSymbolicLink(): 494 | return { 495 | filetype: WASI_FILETYPE_SYMBOLIC_LINK, 496 | rightsBase: 0n, 497 | rightsInheriting: 0n, 498 | }; 499 | default: 500 | return { 501 | filetype: WASI_FILETYPE_UNKNOWN, 502 | rightsBase: 0n, 503 | rightsInheriting: 0n, 504 | }; 505 | } 506 | }; 507 | 508 | const stat = (wasi, fd) => { 509 | const entry = wasi.FD_MAP.get(fd); 510 | if (!entry) { 511 | throw WASI_EBADF; 512 | } 513 | if (entry.filetype === undefined) { 514 | const stats = fs.fstatSync(entry.real); 515 | const { 516 | filetype, rightsBase, rightsInheriting, 517 | } = translateFileAttributes(fd, stats); 518 | entry.filetype = filetype; 519 | if (entry.rights === undefined) { 520 | entry.rights = { 521 | base: rightsBase, 522 | inheriting: rightsInheriting, 523 | }; 524 | } 525 | } 526 | return entry; 527 | }; 528 | 529 | class WASI { 530 | constructor({ preopenDirectories = { '.': '.' }, env = {}, args = [] } = {}) { 531 | this.memory = undefined; 532 | this.view = undefined; 533 | 534 | this.FD_MAP = new Map([ 535 | [WASI_STDIN_FILENO, { 536 | real: 0, 537 | filetype: undefined, 538 | rights: undefined, 539 | path: undefined, 540 | }], 541 | [WASI_STDOUT_FILENO, { 542 | real: 1, 543 | filetype: undefined, 544 | rights: undefined, 545 | path: undefined, 546 | }], 547 | [WASI_STDERR_FILENO, { 548 | real: 2, 549 | filetype: undefined, 550 | rights: undefined, 551 | path: undefined, 552 | }], 553 | ]); 554 | 555 | for (const [k, v] of Object.entries(preopenDirectories)) { 556 | const real = fs.openSync(v); 557 | const newfd = [...this.FD_MAP.keys()].reverse()[0] + 1; 558 | this.FD_MAP.set(newfd, { 559 | real, 560 | filetype: WASI_FILETYPE_DIRECTORY, 561 | rights: { 562 | base: RIGHTS_DIRECTORY_BASE, 563 | inheriting: RIGHTS_DIRECTORY_INHERITING, 564 | }, 565 | fakePath: k, 566 | path: v, 567 | }); 568 | } 569 | 570 | const getiovs = (iovs, iovsLen) => { 571 | // iovs* -> [iov, iov, ...] 572 | // __wasi_ciovec_t { 573 | // void* buf, 574 | // size_t buf_len, 575 | // } 576 | 577 | this.refreshMemory(); 578 | 579 | const buffers = Array.from({ length: iovsLen }, (_, i) => { 580 | const ptr = iovs + (i * 8); 581 | const bufPtr = this.view.getUint32(ptr, true); 582 | const bufLen = this.view.getUint32(ptr + 4, true); 583 | return new Uint8Array(this.memory.buffer, bufPtr, bufLen); 584 | }); 585 | 586 | return buffers; 587 | }; 588 | 589 | const CHECK_FD = (fd, rights) => { 590 | const stats = stat(this, fd); 591 | if (rights !== 0 && (stats.rights.base & rights) === 0) { 592 | throw WASI_EPERM; 593 | } 594 | return stats; 595 | }; 596 | 597 | this.exports = { 598 | args_get: (argv, argvBuf) => { 599 | this.refreshMemory(); 600 | let coffset = argv; 601 | let offset = argvBuf; 602 | args.forEach((a) => { 603 | this.view.setUint32(coffset, offset, true); 604 | coffset += 4; 605 | offset += Buffer.from(this.memory.buffer).write(`${a}\0`, offset); 606 | }); 607 | return WASI_ESUCCESS; 608 | }, 609 | args_sizes_get: (argc, argvBufSize) => { 610 | this.refreshMemory(); 611 | this.view.setUint32(argc, args.length, true); 612 | const size = args.reduce((acc, a) => acc + Buffer.byteLength(a) + 1, 0); 613 | this.view.setUint32(argvBufSize, size, true); 614 | return WASI_ESUCCESS; 615 | }, 616 | environ_get: (environ, environBuf) => { 617 | this.refreshMemory(); 618 | let coffset = environ; 619 | let offset = environBuf; 620 | const cache = Buffer.from(this.memory.buffer); 621 | Object.entries(env) 622 | .forEach(([key, value]) => { 623 | this.view.setUint32(coffset, offset, true); 624 | coffset += 4; 625 | offset += cache.write(`${key}=${value}\0`, offset); 626 | }); 627 | return WASI_ESUCCESS; 628 | }, 629 | environ_sizes_get: (environCount, environBufSize) => { 630 | this.refreshMemory(); 631 | const envProcessed = Object.entries(env) 632 | .map(([key, value]) => `${key}=${value}\0`); 633 | const size = envProcessed.reduce((acc, e) => 634 | acc + Buffer.byteLength(e), 0); 635 | this.view.setUint32(environCount, envProcessed.length, true); 636 | this.view.setUint32(environBufSize, size, true); 637 | return WASI_ESUCCESS; 638 | }, 639 | clock_res_get: (clockId, resolution) => { 640 | this.view.setBigUint64(resolution, 0n); 641 | return WASI_ESUCCESS; 642 | }, 643 | clock_time_get: (clockId, precision, time) => { 644 | const n = now(clockId); 645 | if (n === null) { 646 | return WASI_EINVAL; 647 | } 648 | this.view.setBigUint64(time, n, true); 649 | return WASI_ESUCCESS; 650 | }, 651 | fd_advise: wrap((fd, offset, len, advice) => { 652 | CHECK_FD(fd, WASI_RIGHT_FD_ADVISE); 653 | return WASI_ENOSYS; 654 | }), 655 | fd_allocate: wrap((fd, offset, len) => { 656 | CHECK_FD(fd, WASI_RIGHT_FD_ALLOCATE); 657 | return WASI_ENOSYS; 658 | }), 659 | fd_close: wrap((fd) => { 660 | const stats = CHECK_FD(fd, 0); 661 | fs.closeSync(stats.real); 662 | this.FD_MAP.delete(fd); 663 | return WASI_ESUCCESS; 664 | }), 665 | fd_datasync: (fd) => { 666 | const stats = CHECK_FD(fd, WASI_RIGHT_FD_DATASYNC); 667 | fs.fdatasyncSync(stats.real); 668 | return WASI_ESUCCESS; 669 | }, 670 | fd_fdstat_get: wrap((fd, bufPtr) => { 671 | const stats = CHECK_FD(fd, 0); 672 | this.refreshMemory(); 673 | this.view.setUint8(bufPtr, stats.filetype); // FILETYPE u8 674 | this.view.setUint16(bufPtr + 2, 0, true); // FDFLAG u16 675 | this.view.setUint16(bufPtr + 4, 0, true); // FDFLAG u16 676 | this.view.setBigUint64(bufPtr + 8, stats.rights.base, true); // u64 677 | this.view.setBigUint64( 678 | bufPtr + 8 + 8, stats.rights.inheriting, true, 679 | ); // u64 680 | return WASI_ESUCCESS; 681 | }), 682 | fd_fdstat_set_flags: wrap((fd, flags) => { 683 | CHECK_FD(fd, WASI_RIGHT_FD_FDSTAT_SET_FLAGS); 684 | return WASI_ENOSYS; 685 | }), 686 | fd_fdstat_set_rights: wrap((fd, fsRightsBase, fsRightsInheriting) => { 687 | const stats = CHECK_FD(fd, 0); 688 | const nrb = stats.rights.base | fsRightsBase; 689 | if (nrb > stats.rights.base) { 690 | return WASI_EPERM; 691 | } 692 | const nri = stats.rights.inheriting | fsRightsInheriting; 693 | if (nri > stats.rights.inheriting) { 694 | return WASI_EPERM; 695 | } 696 | stats.rights.base = nrb; 697 | stats.rights.inheriting = nri; 698 | return WASI_ESUCCESS; 699 | }), 700 | fd_filestat_get: wrap((fd, bufPtr) => { 701 | const stats = CHECK_FD(fd, WASI_RIGHT_FD_FILESTAT_GET); 702 | const rstats = fs.fstatSync(stats.real); 703 | this.refreshMemory(); 704 | this.view.setBigUint64(bufPtr, BigInt(rstats.dev), true); 705 | bufPtr += 8; 706 | this.view.setBigUint64(bufPtr, BigInt(rstats.ino), true); 707 | bufPtr += 8; 708 | this.view.setUint8(bufPtr, stats.filetype); 709 | bufPtr += 4; 710 | this.view.setUint32(bufPtr, Number(rstats.nlink), true); 711 | bufPtr += 4; 712 | this.view.setBigUint64(bufPtr, BigInt(rstats.size), true); 713 | bufPtr += 8; 714 | this.view.setBigUint64(bufPtr, msToNs(rstats.atimeMs), true); 715 | bufPtr += 8; 716 | this.view.setBigUint64(bufPtr, msToNs(rstats.mtimeMs), true); 717 | bufPtr += 8; 718 | this.view.setBigUint64(bufPtr, msToNs(rstats.ctimeMs), true); 719 | bufPtr += 8; 720 | return WASI_ESUCCESS; 721 | }), 722 | fd_filestat_set_size: wrap((fd, stSize) => { 723 | const stats = CHECK_FD(fd, WASI_RIGHT_FD_FILESTAT_SET_SIZE); 724 | fs.ftruncate(stats.real, Number(stSize)); 725 | return WASI_ESUCCESS; 726 | }), 727 | fd_filestat_set_times: wrap((fd, stAtim, stMtim, fstflags) => { 728 | const stats = CHECK_FD(fd, WASI_RIGHT_FD_FILESTAT_SET_TIMES); 729 | const n = now(WASI_CLOCK_REALTIME); 730 | const atimNow = (fstflags & WASI_FILESTAT_SET_ATIM_NOW) 731 | === WASI_FILESTAT_SET_ATIM_NOW; 732 | const mtimNow = (fstflags & WASI_FILESTAT_SET_MTIM_NOW) 733 | === WASI_FILESTAT_SET_MTIM_NOW; 734 | fs.futimesSync( 735 | stats.real, 736 | atimNow ? n : stAtim, 737 | mtimNow ? n : stMtim, 738 | ); 739 | return WASI_ESUCCESS; 740 | }), 741 | fd_prestat_get: wrap((fd, bufPtr) => { 742 | const stats = CHECK_FD(fd, 0); 743 | if (!stats.path) { 744 | return WASI_EINVAL; 745 | } 746 | this.refreshMemory(); 747 | this.view.setUint8(bufPtr, WASI_PREOPENTYPE_DIR); 748 | this.view.setUint32( 749 | bufPtr + 4, Buffer.byteLength(stats.fakePath), true, 750 | ); 751 | return WASI_ESUCCESS; 752 | }), 753 | fd_prestat_dir_name: wrap((fd, pathPtr, pathLen) => { 754 | const stats = CHECK_FD(fd, 0); 755 | if (!stats.path) { 756 | return WASI_EINVAL; 757 | } 758 | this.refreshMemory(); 759 | Buffer.from(this.memory.buffer) 760 | .write(stats.fakePath, pathPtr, pathLen, 'utf8'); 761 | return WASI_ESUCCESS; 762 | }), 763 | fd_pwrite: wrap((fd, iovs, iovsLen, offset, nwritten) => { 764 | const stats = CHECK_FD(fd, WASI_RIGHT_FD_WRITE | WASI_RIGHT_FD_SEEK); 765 | let written = 0; 766 | getiovs(iovs, iovsLen) 767 | .forEach((iov) => { 768 | let w = 0; 769 | while (w < iov.byteLength) { 770 | w += fs.writeSync( 771 | stats.real, iov, w, iov.byteLength - w, offset + written + w, 772 | ); 773 | } 774 | written += w; 775 | }); 776 | this.view.setUint32(nwritten, written, true); 777 | return WASI_ESUCCESS; 778 | }), 779 | fd_write: wrap((fd, iovs, iovsLen, nwritten) => { 780 | const stats = CHECK_FD(fd, WASI_RIGHT_FD_WRITE); 781 | let written = 0; 782 | getiovs(iovs, iovsLen) 783 | .forEach((iov) => { 784 | let w = 0; 785 | while (w < iov.byteLength) { 786 | w += fs.writeSync(stats.real, iov, w, iov.byteLength - w); 787 | } 788 | written += w; 789 | }); 790 | this.view.setUint32(nwritten, written, true); 791 | return WASI_ESUCCESS; 792 | }), 793 | fd_pread: wrap((fd, iovs, iovsLen, offset, nread) => { 794 | const stats = CHECK_FD(fd, WASI_RIGHT_FD_READ | WASI_RIGHT_FD_SEEK); 795 | let read = 0; 796 | getiovs(iovs, iovsLen) 797 | .forEach((iov) => { 798 | let r = 0; 799 | while (r < iov.byteLength) { 800 | r += fs.readSync( 801 | stats.real, iov, r, iov.byteLength - r, offset + read + r, 802 | ); 803 | } 804 | read += r; 805 | }); 806 | this.view.setUint32(nread, read, true); 807 | return WASI_ESUCCESS; 808 | }), 809 | fd_read: wrap((fd, iovs, iovsLen, nread) => { 810 | const stats = CHECK_FD(fd, WASI_RIGHT_FD_READ); 811 | let read = 0; 812 | outer: // eslint-disable-line no-labels 813 | for (const iov of getiovs(iovs, iovsLen)) { 814 | let r = 0; 815 | while (r < iov.byteLength) { 816 | const rr = fs.readSync(stats.real, iov, r, iov.byteLength - r); 817 | r += rr; 818 | read += rr; 819 | if (rr === 0) { 820 | break outer; // eslint-disable-line no-labels 821 | } 822 | } 823 | } 824 | this.view.setUint32(nread, read, true); 825 | return WASI_ESUCCESS; 826 | }), 827 | fd_readdir: wrap((fd, bufPtr, bufLen, cookie, bufusedPtr) => { 828 | const stats = CHECK_FD(fd, WASI_RIGHT_FD_READDIR); 829 | this.refreshMemory(); 830 | const entries = fs.readdirSync(stats.path, { withFileTypes: true }); 831 | const startPtr = bufPtr; 832 | const cache = Buffer.from(this.memory.buffer); 833 | for (let i = Number(cookie); i < entries.length; i += 1) { 834 | const entry = entries[i]; 835 | this.view.setBigUint64(bufPtr, BigInt(i + 1), true); 836 | bufPtr += 8; 837 | const rstats = fs.statSync(path.resolve(stats.path, entry.name)); 838 | this.view.setBigUint64(bufPtr, BigInt(rstats.ino), true); 839 | bufPtr += 8; 840 | this.view.setUint32(bufPtr, Buffer.byteLength(entry.name), true); 841 | bufPtr += 4; 842 | let filetype; 843 | switch (true) { 844 | case rstats.isBlockDevice(): 845 | filetype = WASI_FILETYPE_BLOCK_DEVICE; 846 | break; 847 | case rstats.isCharacterDevice(): 848 | filetype = WASI_FILETYPE_CHARACTER_DEVICE; 849 | break; 850 | case rstats.isDirectory(): 851 | filetype = WASI_FILETYPE_DIRECTORY; 852 | break; 853 | case rstats.isFIFO(): 854 | filetype = WASI_FILETYPE_SOCKET_STREAM; 855 | break; 856 | case rstats.isFile(): 857 | filetype = WASI_FILETYPE_REGULAR_FILE; 858 | break; 859 | case rstats.isSocket(): 860 | filetype = WASI_FILETYPE_SOCKET_STREAM; 861 | break; 862 | case rstats.isSymbolicLink(): 863 | filetype = WASI_FILETYPE_SYMBOLIC_LINK; 864 | break; 865 | default: 866 | filetype = WASI_FILETYPE_UNKNOWN; 867 | break; 868 | } 869 | this.view.setUint8(bufPtr, filetype); 870 | bufPtr += 1; 871 | bufPtr += 3; // padding 872 | bufPtr += cache.write(entry.name, bufPtr, bufLen - bufPtr); 873 | bufPtr += (8 % bufPtr); // padding 874 | } 875 | const bufused = bufPtr - startPtr; 876 | this.view.setUint32(bufusedPtr, bufused, true); 877 | return WASI_ESUCCESS; 878 | }), 879 | fd_renumber: wrap((from, to) => { 880 | CHECK_FD(from, 0); 881 | CHECK_FD(to, 0); 882 | fs.closeSync(this.FD_MAP.get(to).real); 883 | this.FD_MAP.set(to, this.FD_MAP.get(from)); 884 | this.FD_MAP.delete(from); 885 | return WASI_ESUCCESS; 886 | }), 887 | fd_seek: wrap((fd, offset, whence, newOffsetPtr) => { 888 | const stats = CHECK_FD(fd, WASI_RIGHT_FD_SEEK); 889 | const newOffset = binding.seek(stats.real, offset, { 890 | [WASI_WHENCE_CUR]: binding.SEEK_CUR, 891 | [WASI_WHENCE_END]: binding.SEEK_END, 892 | [WASI_WHENCE_SET]: binding.SEEK_SET, 893 | }[whence]); 894 | if (typeof newOffset === 'number') { // errno 895 | throw newOffset; 896 | } 897 | this.refreshMemory(); 898 | this.view.setBigUint64(newOffsetPtr, newOffset, true); 899 | return WASI_ESUCCESS; 900 | }), 901 | fd_tell: wrap((fd, offsetPtr) => { 902 | const stats = CHECK_FD(fd, WASI_RIGHT_FD_TELL); 903 | const currentOffset = binding.seek(stats.real, 0n, binding.SEEK_CUR); 904 | if (typeof currentOffset === 'number') { // errno 905 | throw currentOffset; 906 | } 907 | this.refreshMemory(); 908 | this.view.setBigUint64(offsetPtr, currentOffset, true); 909 | return WASI_ESUCCESS; 910 | }), 911 | fd_sync: wrap((fd) => { 912 | const stats = CHECK_FD(fd, WASI_RIGHT_FD_SYNC); 913 | fs.fsyncSync(stats.real); 914 | return WASI_ESUCCESS; 915 | }), 916 | path_create_directory: wrap((fd, pathPtr, pathLen) => { 917 | const stats = CHECK_FD(fd, WASI_RIGHT_PATH_CREATE_DIRECTORY); 918 | if (!stats.path) { 919 | return WASI_EINVAL; 920 | } 921 | this.refreshMemory(); 922 | const p = Buffer.from(this.memory.buffer, pathPtr, pathLen).toString(); 923 | fs.mkdirSync(path.resolve(stats.path, p)); 924 | return WASI_ESUCCESS; 925 | }), 926 | path_filestat_get: wrap((fd, flags, pathPtr, pathLen, bufPtr) => { 927 | const stats = CHECK_FD(fd, WASI_RIGHT_PATH_FILESTAT_GET); 928 | if (!stats.path) { 929 | return WASI_EINVAL; 930 | } 931 | this.refreshMemory(); 932 | const p = Buffer.from(this.memory.buffer, pathPtr, pathLen).toString(); 933 | const rstats = fs.statSync(path.resolve(stats.path, p)); 934 | this.view.setBigUint64(bufPtr, BigInt(rstats.dev), true); 935 | bufPtr += 8; 936 | this.view.setBigUint64(bufPtr, BigInt(rstats.ino), true); 937 | bufPtr += 8; 938 | this.view.setUint8(bufPtr, 939 | translateFileAttributes(undefined, rstats).filetype); 940 | bufPtr += 4; 941 | this.view.setUint32(bufPtr, Number(rstats.nlink), true); 942 | bufPtr += 4; 943 | this.view.setBigUint64(bufPtr, BigInt(rstats.size), true); 944 | bufPtr += 8; 945 | this.view.setBigUint64(bufPtr, msToNs(rstats.atimeMs), true); 946 | bufPtr += 8; 947 | this.view.setBigUint64(bufPtr, msToNs(rstats.mtimeMs), true); 948 | bufPtr += 8; 949 | this.view.setBigUint64(bufPtr, msToNs(rstats.ctimeMs), true); 950 | bufPtr += 8; 951 | return WASI_ESUCCESS; 952 | }), 953 | path_filestat_set_times: 954 | wrap((fd, fstflags, pathPtr, pathLen, stAtim, stMtim) => { 955 | const stats = CHECK_FD(fd, WASI_RIGHT_PATH_FILESTAT_SET_TIMES); 956 | if (!stats.path) { 957 | return WASI_EINVAL; 958 | } 959 | this.refreshMemory(); 960 | const n = now(WASI_CLOCK_REALTIME); 961 | const atimNow = (fstflags & WASI_FILESTAT_SET_ATIM_NOW) 962 | === WASI_FILESTAT_SET_ATIM_NOW; 963 | const mtimNow = (fstflags & WASI_FILESTAT_SET_MTIM_NOW) 964 | === WASI_FILESTAT_SET_MTIM_NOW; 965 | const p = Buffer.from(this.memory.buffer, pathPtr, pathLen) 966 | .toString(); 967 | fs.utimesSync( 968 | path.resolve(stats.path, p), 969 | atimNow ? n : stAtim, 970 | mtimNow ? n : stMtim, 971 | ); 972 | return WASI_ESUCCESS; 973 | }), 974 | path_link: wrap( 975 | (oldFd, oldFlags, oldPath, oldPathLen, newFd, newPath, newPathLen) => { 976 | const ostats = CHECK_FD(oldFd, WASI_RIGHT_PATH_LINK_SOURCE); 977 | const nstats = CHECK_FD(newFd, WASI_RIGHT_PATH_LINK_TARGET); 978 | if (!ostats.path || !nstats.path) { 979 | return WASI_EINVAL; 980 | } 981 | this.refreshMemory(); 982 | const op = Buffer.from( 983 | this.memory.buffer, oldPath, oldPathLen, 984 | ).toString(); 985 | const np = Buffer.from( 986 | this.memory.buffer, newPath, newPathLen, 987 | ).toString(); 988 | fs.linkSync( 989 | path.resolve(ostats.path, op), path.resolve(nstats.path, np), 990 | ); 991 | return WASI_ESUCCESS; 992 | }, 993 | ), 994 | path_open: wrap((dirfd, dirflags, pathPtr, pathLen, oflags, 995 | fsRightsBase, fsRightsInheriting, fsFlags, fd) => { 996 | const stats = CHECK_FD(dirfd, WASI_RIGHT_PATH_OPEN); 997 | 998 | const read = (fsRightsBase 999 | & (WASI_RIGHT_FD_READ | WASI_RIGHT_FD_READDIR)) !== 0n; 1000 | const write = (fsRightsBase 1001 | & (WASI_RIGHT_FD_DATASYNC 1002 | | WASI_RIGHT_FD_WRITE 1003 | | WASI_RIGHT_FD_ALLOCATE 1004 | | WASI_RIGHT_FD_FILESTAT_SET_SIZE)) !== 0n; 1005 | 1006 | let noflags; 1007 | if (write && read) { 1008 | noflags = fs.constants.O_RDWR; 1009 | } else if (read) { 1010 | noflags = fs.constants.O_RDONLY; 1011 | } else if (write) { 1012 | noflags = fs.constants.O_WRONLY; 1013 | } 1014 | 1015 | let neededBase = WASI_RIGHT_PATH_OPEN; 1016 | let neededInheriting = fsRightsBase | fsRightsInheriting; 1017 | 1018 | if ((oflags & WASI_O_CREAT) !== 0) { 1019 | noflags |= fs.constants.O_CREAT; 1020 | neededBase |= WASI_RIGHT_PATH_CREATE_FILE; 1021 | } 1022 | if ((oflags & WASI_O_DIRECTORY) !== 0) { 1023 | noflags |= fs.constants.O_DIRECTORY; 1024 | } 1025 | if ((oflags & WASI_O_EXCL) !== 0) { 1026 | noflags |= fs.constants.O_EXCL; 1027 | } 1028 | if ((oflags & WASI_O_TRUNC) !== 0) { 1029 | noflags |= fs.constants.O_TRUNC; 1030 | neededBase |= WASI_RIGHT_PATH_FILESTAT_SET_SIZE; 1031 | } 1032 | 1033 | // Convert file descriptor flags. 1034 | if ((fsFlags & WASI_FDFLAG_APPEND) !== 0) { 1035 | noflags |= fs.constants.O_APPEND; 1036 | } 1037 | if ((fsFlags & WASI_FDFLAG_DSYNC) !== 0) { 1038 | if (fs.constants.O_DSYNC) { 1039 | noflags |= fs.constants.O_DSYNC; 1040 | } else { 1041 | noflags |= fs.constants.O_SYNC; 1042 | } 1043 | neededInheriting |= WASI_RIGHT_FD_DATASYNC; 1044 | } 1045 | if ((fsFlags & WASI_FDFLAG_NONBLOCK) !== 0) { 1046 | noflags |= fs.constants.O_NONBLOCK; 1047 | } 1048 | if ((fsFlags & WASI_FDFLAG_RSYNC) !== 0) { 1049 | if (fs.constants.O_RSYNC) { 1050 | noflags |= fs.constants.O_RSYNC; 1051 | } else { 1052 | noflags |= fs.constants.O_SYNC; 1053 | } 1054 | neededInheriting |= WASI_RIGHT_FD_SYNC; 1055 | } 1056 | if ((fsFlags & WASI_FDFLAG_SYNC) !== 0) { 1057 | noflags |= fs.constants.O_SYNC; 1058 | neededInheriting |= WASI_RIGHT_FD_SYNC; 1059 | } 1060 | if (write && (noflags 1061 | & (fs.constants.O_APPEND | fs.constants.O_TRUNC)) === 0) { 1062 | neededInheriting |= WASI_RIGHT_FD_SEEK; 1063 | } 1064 | 1065 | this.refreshMemory(); 1066 | const p = Buffer.from(this.memory.buffer, pathPtr, pathLen).toString(); 1067 | const fullUnresolved = path.resolve(stats.path, p); 1068 | if (path.relative(stats.path, fullUnresolved).startsWith('..')) { 1069 | return WASI_ENOTCAPABLE; 1070 | } 1071 | let full; 1072 | try { 1073 | full = fs.realpathSync(fullUnresolved); 1074 | if (path.relative(stats.path, full).startsWith('..')) { 1075 | return WASI_ENOTCAPABLE; 1076 | } 1077 | } catch (e) { 1078 | if (e.code === 'ENOENT') { 1079 | full = fullUnresolved; 1080 | } else { 1081 | throw e; 1082 | } 1083 | } 1084 | const realfd = fs.openSync(full, noflags); 1085 | 1086 | const newfd = [...this.FD_MAP.keys()].reverse()[0] + 1; 1087 | this.FD_MAP.set(newfd, { 1088 | real: realfd, 1089 | filetype: undefined, 1090 | rights: { 1091 | base: neededBase, 1092 | inheriting: neededInheriting, 1093 | }, 1094 | path: full, 1095 | }); 1096 | stat(this, newfd); 1097 | this.view.setUint32(fd, newfd, true); 1098 | 1099 | return WASI_ESUCCESS; 1100 | }), 1101 | path_readlink: wrap((fd, pathPtr, pathLen, buf, bufLen, bufused) => { 1102 | const stats = CHECK_FD(fd, WASI_RIGHT_PATH_READLINK); 1103 | if (!stats.path) { 1104 | return WASI_EINVAL; 1105 | } 1106 | this.refreshMemory(); 1107 | const p = Buffer.from(this.memory.buffer, pathPtr, pathLen).toString(); 1108 | const full = path.resolve(stats.path, p); 1109 | const r = fs.readlinkSync(full); 1110 | const used = Buffer.from(this.memory.buffer).write(r, buf, bufLen); 1111 | this.view.setUint32(bufused, used, true); 1112 | return WASI_ESUCCESS; 1113 | }), 1114 | path_remove_directory: wrap((fd, pathPtr, pathLen) => { 1115 | const stats = CHECK_FD(fd, WASI_RIGHT_PATH_REMOVE_DIRECTORY); 1116 | if (!stats.path) { 1117 | return WASI_EINVAL; 1118 | } 1119 | this.refreshMemory(); 1120 | const p = Buffer.from(this.memory.buffer, pathPtr, pathLen).toString(); 1121 | fs.rmdirSync(path.resolve(stats.path, p)); 1122 | return WASI_ESUCCESS; 1123 | }), 1124 | path_rename: wrap( 1125 | (oldFd, oldPath, oldPathLen, newFd, newPath, newPathLen) => { 1126 | const ostats = CHECK_FD(oldFd, WASI_RIGHT_PATH_RENAME_SOURCE); 1127 | const nstats = CHECK_FD(newFd, WASI_RIGHT_PATH_RENAME_TARGET); 1128 | if (!ostats.path || !nstats.path) { 1129 | return WASI_EINVAL; 1130 | } 1131 | this.refreshMemory(); 1132 | const op = Buffer.from( 1133 | this.memory.buffer, oldPath, oldPathLen, 1134 | ).toString(); 1135 | const np = Buffer.from( 1136 | this.memory.buffer, newPath, newPathLen, 1137 | ).toString(); 1138 | fs.renameSync( 1139 | path.resolve(ostats.path, op), path.resolve(nstats.path, np), 1140 | ); 1141 | return WASI_ESUCCESS; 1142 | }, 1143 | ), 1144 | path_symlink: wrap((oldPath, oldPathLen, fd, newPath, newPathLen) => { 1145 | const stats = CHECK_FD(fd, WASI_RIGHT_PATH_SYMLINK); 1146 | if (!stats.path) { 1147 | return WASI_EINVAL; 1148 | } 1149 | this.refreshMemory(); 1150 | const op = Buffer.from( 1151 | this.memory.buffer, oldPath, oldPathLen, 1152 | ).toString(); 1153 | const np = Buffer.from( 1154 | this.memory.buffer, newPath, newPathLen, 1155 | ).toString(); 1156 | fs.symlinkSync(op, path.resolve(stats.path, np)); 1157 | return WASI_ESUCCESS; 1158 | }), 1159 | path_unlink_file: wrap((fd, pathPtr, pathLen) => { 1160 | const stats = CHECK_FD(fd, WASI_RIGHT_PATH_UNLINK_FILE); 1161 | if (!stats.path) { 1162 | return WASI_EINVAL; 1163 | } 1164 | this.refreshMemory(); 1165 | const p = Buffer.from(this.memory.buffer, pathPtr, pathLen).toString(); 1166 | fs.unlinkSync(path.resolve(stats.path, p)); 1167 | return WASI_ESUCCESS; 1168 | }), 1169 | poll_oneoff: (sin, sout, nsubscriptions, nevents) => { 1170 | let eventc = 0; 1171 | let waitEnd = 0; 1172 | this.refreshMemory(); 1173 | for (let i = 0; i < nsubscriptions; i += 1) { 1174 | const userdata = this.view.getBigUint64(sin, true); 1175 | sin += 8; 1176 | const type = this.view.getUint8(sin); 1177 | sin += 1; 1178 | switch (type) { 1179 | case WASI_EVENTTYPE_CLOCK: { 1180 | sin += 7; // padding 1181 | const identifier = this.view.getBigUint64(sin, true); 1182 | sin += 8; 1183 | const clockid = this.view.getUint32(sin, true); 1184 | sin += 4; 1185 | sin += 4; // padding 1186 | const timestamp = this.view.getBigUint64(sin, true); 1187 | sin += 8; 1188 | const precision = this.view.getBigUint64(sin, true); 1189 | sin += 8; 1190 | const subclockflags = this.view.getUint16(sin, true); 1191 | sin += 2; 1192 | sin += 6; // padding 1193 | 1194 | const absolute = subclockflags === 1; 1195 | 1196 | let e = WASI_ESUCCESS; 1197 | const n = now(clockid); 1198 | if (n === null) { 1199 | e = WASI_EINVAL; 1200 | } else { 1201 | const end = absolute ? timestamp : n + timestamp; 1202 | waitEnd = end > waitEnd ? end : waitEnd; 1203 | } 1204 | 1205 | this.view.setBigUint64(sout, userdata, true); 1206 | sout += 8; 1207 | this.view.setUint16(sout, e, true); // error 1208 | sout += 2; // pad offset 2 1209 | this.view.setUint8(sout, WASI_EVENTTYPE_CLOCK); 1210 | sout += 1; // pad offset 3 1211 | sout += 5; // padding to 8 1212 | 1213 | eventc += 1; 1214 | 1215 | break; 1216 | } 1217 | case WASI_EVENTTYPE_FD_READ: 1218 | case WASI_EVENTTYPE_FD_WRITE: { 1219 | sin += 3; // padding 1220 | const fd = this.view.getUint32(sin, true); 1221 | sin += 4; 1222 | 1223 | this.view.setBigUint64(sout, userdata, true); 1224 | sout += 8; 1225 | this.view.setUint16(sout, WASI_ENOSYS, true); // error 1226 | sout += 2; // pad offset 2 1227 | this.view.setUint8(sout, type); 1228 | sout += 1; // pad offset 3 1229 | sout += 5; // padding to 8 1230 | 1231 | eventc += 1; 1232 | 1233 | break; 1234 | } 1235 | default: 1236 | return WASI_EINVAL; 1237 | } 1238 | } 1239 | 1240 | this.view.setUint32(nevents, eventc, true); 1241 | 1242 | while (process.hrtime.bigint() < waitEnd) { 1243 | // nothing 1244 | } 1245 | 1246 | return WASI_ESUCCESS; 1247 | }, 1248 | proc_exit: (rval) => { 1249 | process.exit(rval); 1250 | return WASI_ESUCCESS; 1251 | }, 1252 | proc_raise: (sig) => { 1253 | if (!(sig in SIGNAL_MAP)) { 1254 | return WASI_EINVAL; 1255 | } 1256 | process.kill(process.pid, SIGNAL_MAP[sig]); 1257 | return WASI_ESUCCESS; 1258 | }, 1259 | random_get: (bufPtr, bufLen) => { 1260 | this.refreshMemory(); 1261 | crypto.randomFillSync( 1262 | new Uint8Array(this.memory.buffer), bufPtr, bufLen, 1263 | ); 1264 | return WASI_ESUCCESS; 1265 | }, 1266 | sched_yield() { 1267 | binding.schedYield(); 1268 | return WASI_ESUCCESS; 1269 | }, 1270 | sock_recv() { 1271 | return WASI_ENOSYS; 1272 | }, 1273 | sock_send() { 1274 | return WASI_ENOSYS; 1275 | }, 1276 | sock_shutdown() { 1277 | return WASI_ENOSYS; 1278 | }, 1279 | }; 1280 | } 1281 | 1282 | refreshMemory() { 1283 | if (this.view === undefined || this.view.byteLength === 0) { 1284 | this.view = new DataView(this.memory.buffer); 1285 | } 1286 | } 1287 | 1288 | setMemory(m) { 1289 | this.memory = m; 1290 | } 1291 | } 1292 | 1293 | module.exports = WASI; 1294 | --------------------------------------------------------------------------------