wasi: allow WASI stdio to be configured

This commit adds stdin, stderr, and stdout options to WASI, which
allow the stdio streams to be configured.

PR-URL: https://github.com/nodejs/node/pull/33544
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: David Carlier <devnexen@gmail.com>
Reviewed-By: Juan José Arboleda <soyjuanarbol@gmail.com>
Reviewed-By: Zeyu Yang <himself65@outlook.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
cjihrig
2020-03-26 00:22:13 -04:00
committed by Ruben Bridgewater
parent e2d3230565
commit e3b54b4d1c
5 changed files with 71 additions and 5 deletions

View File

@@ -66,6 +66,12 @@ added:
process via the `__wasi_proc_exit()` function. Setting this option to `true`
causes `wasi.start()` to return the exit code rather than terminate the
process. **Default:** `false`.
* `stdin` {integer} The file descriptor used as standard input in the
WebAssembly application. **Default:** `0`.
* `stdout` {integer} The file descriptor used as standard output in the
WebAssembly application. **Default:** `1`.
* `stderr` {integer} The file descriptor used as standard error in the
WebAssembly application. **Default:** `2`.
### `wasi.start(instance)`
<!-- YAML

View File

@@ -16,6 +16,7 @@ const { isArrayBuffer } = require('internal/util/types');
const {
validateArray,
validateBoolean,
validateInt32,
validateObject,
} = require('internal/validators');
const { WASI: _WASI } = internalBinding('wasi');
@@ -51,7 +52,13 @@ class WASI {
}
}
const wrap = new _WASI(args, env, preopens);
const { stdin = 0, stdout = 1, stderr = 2 } = options;
validateInt32(stdin, 'options.stdin', 0);
validateInt32(stdout, 'options.stdout', 0);
validateInt32(stderr, 'options.stderr', 0);
const stdio = [stdin, stdout, stderr];
const wrap = new _WASI(args, env, preopens, stdio);
for (const prop in wrap) {
wrap[prop] = FunctionPrototypeBind(wrap[prop], wrap);

View File

@@ -163,10 +163,11 @@ void WASI::DecreaseAllocatedSize(size_t size) {
void WASI::New(const FunctionCallbackInfo<Value>& args) {
CHECK(args.IsConstructCall());
CHECK_EQ(args.Length(), 3);
CHECK_EQ(args.Length(), 4);
CHECK(args[0]->IsArray());
CHECK(args[1]->IsArray());
CHECK(args[2]->IsArray());
CHECK(args[3]->IsArray());
Environment* env = Environment::GetCurrent(args);
Local<Context> context = env->context();
@@ -174,9 +175,15 @@ void WASI::New(const FunctionCallbackInfo<Value>& args) {
const uint32_t argc = argv->Length();
uvwasi_options_t options;
options.in = 0;
options.out = 1;
options.err = 2;
Local<Array> stdio = args[3].As<Array>();
CHECK_EQ(stdio->Length(), 3);
options.in = stdio->Get(context, 0).ToLocalChecked()->
Int32Value(context).FromJust();
options.out = stdio->Get(context, 1).ToLocalChecked()->
Int32Value(context).FromJust();
options.err = stdio->Get(context, 2).ToLocalChecked()->
Int32Value(context).FromJust();
options.fd_table_size = 3;
options.argc = argc;
options.argv =

View File

@@ -25,6 +25,18 @@ assert.throws(() => { new WASI({ preopens: 'fhqwhgads' }); },
assert.throws(() => { new WASI({ returnOnExit: 'fhqwhgads' }); },
{ code: 'ERR_INVALID_ARG_TYPE', message: /\breturnOnExit\b/ });
// If stdin is not an int32 and not undefined, it should throw.
assert.throws(() => { new WASI({ stdin: 'fhqwhgads' }); },
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bstdin\b/ });
// If stdout is not an int32 and not undefined, it should throw.
assert.throws(() => { new WASI({ stdout: 'fhqwhgads' }); },
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bstdout\b/ });
// If stderr is not an int32 and not undefined, it should throw.
assert.throws(() => { new WASI({ stderr: 'fhqwhgads' }); },
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bstderr\b/ });
// If options is provided, but not an object, the constructor should throw.
[null, 'foo', '', 0, NaN, Symbol(), true, false, () => {}].forEach((value) => {
assert.throws(() => { new WASI(value); },

View File

@@ -0,0 +1,34 @@
// Flags: --experimental-wasi-unstable-preview1 --experimental-wasm-bigint
'use strict';
require('../common');
const tmpdir = require('../common/tmpdir');
const { strictEqual } = require('assert');
const { closeSync, openSync, readFileSync, writeFileSync } = require('fs');
const { join } = require('path');
const { WASI } = require('wasi');
const modulePath = join(__dirname, 'wasm', 'stdin.wasm');
const buffer = readFileSync(modulePath);
const stdinFile = join(tmpdir.path, 'stdin.txt');
const stdoutFile = join(tmpdir.path, 'stdout.txt');
const stderrFile = join(tmpdir.path, 'stderr.txt');
tmpdir.refresh();
// Write 33 x's. The test's buffer only holds 31 x's + a terminator.
writeFileSync(stdinFile, 'x'.repeat(33));
const stdin = openSync(stdinFile, 'r');
const stdout = openSync(stdoutFile, 'a');
const stderr = openSync(stderrFile, 'a');
const wasi = new WASI({ stdin, stdout, stderr, returnOnExit: true });
const importObject = { wasi_snapshot_preview1: wasi.wasiImport };
(async () => {
const { instance } = await WebAssembly.instantiate(buffer, importObject);
strictEqual(wasi.start(instance), 0);
closeSync(stdin);
closeSync(stdout);
closeSync(stderr);
strictEqual(readFileSync(stdoutFile, 'utf8').trim(), 'x'.repeat(31));
strictEqual(readFileSync(stderrFile, 'utf8').trim(), '');
})();